|
|
const proxyUtil = require('scripts/proxyUitl')
|
|
|
const updateUtil = require('scripts/updateUtil')
|
|
|
const cu = require('scripts/colorUtil')
|
|
|
const ruleUpdateUtil = require('scripts/ruleUpdateUtil')
|
|
|
const colorUtil = require('scripts/colorUtil')
|
|
|
|
|
|
const FILE = 'data.js'
|
|
|
const PROXY_HEADER = 'ProxyHeader'
|
|
|
|
|
|
const settingKeys = ['widgetSettings', 'generalSettings', 'proxyGroupSettings', 'customSettings', 'hostSettings', 'urlrewriteSettings', 'ssidSettings', 'headerrewriteSettings', 'hostnameSettings', 'mitmSettings']
|
|
|
|
|
|
if (!$file.exists(FILE)) {
|
|
|
$file.write({
|
|
|
data: $data({ "string": JSON.stringify({ "urls": [] }) }),
|
|
|
path: FILE
|
|
|
})
|
|
|
}
|
|
|
|
|
|
String.prototype.reverse = function () {
|
|
|
return this.toString().split('').reverse().join('')
|
|
|
}
|
|
|
|
|
|
String.prototype.contains = function (sub) {
|
|
|
return this.indexOf(sub) > -1
|
|
|
}
|
|
|
|
|
|
setDefaultSettings()
|
|
|
|
|
|
let screenHeight = $device.info.screen.height
|
|
|
const screenWidth = $device.info.screen.width
|
|
|
|
|
|
const iPhoneX = $device.isIphoneX
|
|
|
if (iPhoneX) {
|
|
|
screenHeight -= 48
|
|
|
}
|
|
|
|
|
|
const statusBarHeight = iPhoneX ? 44 : 20
|
|
|
const navBarHeight = 45
|
|
|
|
|
|
const selectedColor = $color("#c1dcf0")
|
|
|
const btnOnFg = colorUtil.getColor("usualBtnOnFg")
|
|
|
const btnOnBg = colorUtil.getColor("usualBtnOnBg")
|
|
|
const btnOffFg = colorUtil.getColor("usualBtnOffFg")
|
|
|
const btnOffBg = colorUtil.getColor("usualBtnOffBg")
|
|
|
|
|
|
function renderUI() {
|
|
|
$ui.render({
|
|
|
props: {
|
|
|
title: "lhie1规则",
|
|
|
navBarHidden: true,
|
|
|
statusBarHidden: colorUtil.getColor("statusBar", true) === 'clear' ? true : false,
|
|
|
statusBarStyle: colorUtil.getColor("statusBar", true) === '#ffffff' ? 1 : 0,
|
|
|
id: "bodyView",
|
|
|
},
|
|
|
events: {
|
|
|
appeared: function (sender) {
|
|
|
// $("bodyView").runtimeValue().$viewController().$navigationController().$interactivePopGestureRecognizer().$delegate()
|
|
|
// $("bodyView").runtimeValue().$viewController().$navigationController().$interactivePopGestureRecognizer().$setDelegate(null)
|
|
|
if (typeof $ui.window.next !== 'undefined') {
|
|
|
console.error('警告:正在调试模式运行,界面可能会被遮挡,请从JSBox主界面运行此脚本!')
|
|
|
$addin.restart()
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
views: [{
|
|
|
type: "view",
|
|
|
props: {
|
|
|
id: "navBar",
|
|
|
bgcolor: colorUtil.getColor("navBar")
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.height.equalTo(navBarHeight + statusBarHeight)
|
|
|
make.width.equalTo(view.super)
|
|
|
},
|
|
|
views: [{
|
|
|
type: "lottie",
|
|
|
props: {
|
|
|
id: "navLoadingIcon",
|
|
|
loop: false,
|
|
|
speed: 3,
|
|
|
src: "assets/balloons.json"
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.size.equalTo($size(screenWidth, screenHeight))
|
|
|
make.bottom.equalTo(navBarHeight + statusBarHeight + 80)
|
|
|
// make.right.equalTo(view.prev.left).offset(-15)
|
|
|
// make.bottom.equalTo(view.super).offset(100)
|
|
|
}
|
|
|
}, {
|
|
|
type: "label",
|
|
|
props: {
|
|
|
text: "lhie1规则",
|
|
|
textColor: colorUtil.getColor("navTitleText"),
|
|
|
font: $font("bold", 25)
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.height.equalTo(navBarHeight)
|
|
|
make.top.equalTo(statusBarHeight)
|
|
|
make.left.equalTo(15)
|
|
|
}
|
|
|
}, {
|
|
|
type: "image",
|
|
|
props: {
|
|
|
icon: $icon("204", colorUtil.getColor("navIconRight"), $size(25, 25)),
|
|
|
bgcolor: $color("clear")
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.right.equalTo(view.super).offset(-15)
|
|
|
make.height.width.equalTo(25)
|
|
|
make.bottom.equalTo(view.super).offset(-10)
|
|
|
},
|
|
|
events: {
|
|
|
tapped: sender => {
|
|
|
archivesHandler()
|
|
|
}
|
|
|
}
|
|
|
}, {
|
|
|
type: "image",
|
|
|
props: {
|
|
|
icon: $icon("074", colorUtil.getColor("navIconLeft"), $size(25, 25)),
|
|
|
bgcolor: $color("clear")
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.right.equalTo(view.prev.left).offset(-15)
|
|
|
make.height.width.equalTo(25)
|
|
|
make.bottom.equalTo(view.super).offset(-10)
|
|
|
},
|
|
|
events: {
|
|
|
tapped: sender => {
|
|
|
// $clipboard.text = 'GxsAtS84U7'
|
|
|
// $app.openURL("alipay://")
|
|
|
// $app.openURL("https://qr.alipay.com/c1x047207ryk0wiaj6m6ye3")
|
|
|
$ui.alert({
|
|
|
title: '感谢支持',
|
|
|
message: '作者投入大量时间和精力对脚本进行开发和完善,你愿意给他赏杯咖啡支持一下吗?',
|
|
|
actions: [{
|
|
|
title: "支付宝",
|
|
|
handler: () => {
|
|
|
$app.openURL($qrcode.decode($file.read("assets/thankyou2.jpg").image))
|
|
|
}
|
|
|
}, {
|
|
|
title: "微信",
|
|
|
handler: () => {
|
|
|
$quicklook.open({
|
|
|
image: $file.read("assets/thankyou.jpg").image
|
|
|
})
|
|
|
}
|
|
|
}, {
|
|
|
title: "返回"
|
|
|
}]
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
},]
|
|
|
}, {
|
|
|
type: "view",
|
|
|
props: {
|
|
|
id: "mainView"
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.height.equalTo(view.super).offset(navBarHeight + statusBarHeight);
|
|
|
make.width.equalTo(view.super)
|
|
|
make.top.equalTo(navBarHeight + statusBarHeight)
|
|
|
},
|
|
|
views: [{
|
|
|
type: "input",
|
|
|
props: {
|
|
|
id: "fileName",
|
|
|
text: '',
|
|
|
placeholder: "配置名(lhie1)"
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.width.equalTo(view.super).offset(-120)
|
|
|
make.height.equalTo(40)
|
|
|
make.left.top.equalTo(10)
|
|
|
},
|
|
|
events: {
|
|
|
changed: sender => {
|
|
|
saveWorkspace()
|
|
|
},
|
|
|
returned: sender => {
|
|
|
sender.blur()
|
|
|
}
|
|
|
}
|
|
|
}, {
|
|
|
type: "button",
|
|
|
props: {
|
|
|
type: $btnType.contactAdd,
|
|
|
id: "serverURL",
|
|
|
titleColor: colorUtil.getColor("importBtnText"),
|
|
|
title: " 导入节点",
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.width.equalTo(100)
|
|
|
make.height.equalTo(40)
|
|
|
make.right.equalTo(-10)
|
|
|
make.top.equalTo(10)
|
|
|
},
|
|
|
events: {
|
|
|
tapped: sender => {
|
|
|
importMenu({
|
|
|
handler: nodeImportHandler
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
}, {
|
|
|
type: "matrix",
|
|
|
props: {
|
|
|
id: "serverControl",
|
|
|
columns: 3,
|
|
|
scrollEnabled: false,
|
|
|
itemHeight: 40,
|
|
|
bgcolor: $color("#f0f5f5"),
|
|
|
data: genControlItems(),
|
|
|
template: [{
|
|
|
type: "label",
|
|
|
props: {
|
|
|
id: "title",
|
|
|
align: $align.center,
|
|
|
font: $font(14),
|
|
|
textColor: colorUtil.getColor("controlBtnText"),
|
|
|
autoFontSize: true
|
|
|
},
|
|
|
layout: $layout.fill
|
|
|
}],
|
|
|
info: {}
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.width.equalTo(view.super).offset(-20)
|
|
|
make.centerX.equalTo(view.super)
|
|
|
make.height.equalTo(40)
|
|
|
make.top.equalTo($("serverURL").bottom).offset(10)
|
|
|
},
|
|
|
events: {
|
|
|
didSelect: (sender, indexPath, data) => {
|
|
|
let btnTitle = data.title.text
|
|
|
if (btnTitle === '节点倒序') {
|
|
|
reverseServerGroup()
|
|
|
} else if (btnTitle === '删除分组') {
|
|
|
deleteServerGroup()
|
|
|
} else if (btnTitle === '特殊代理') {
|
|
|
specialProxyGroup();
|
|
|
} else {
|
|
|
groupShortcut()
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}, {
|
|
|
type: "list",
|
|
|
props: {
|
|
|
id: "serverEditor",
|
|
|
data: [],
|
|
|
separatorHidden: true,
|
|
|
reorder: true,
|
|
|
actions: [{
|
|
|
title: "delete",
|
|
|
handler: (sender, indexPath) => {
|
|
|
saveWorkspace()
|
|
|
}
|
|
|
}, {
|
|
|
title: "编辑",
|
|
|
color: $color("tint"),
|
|
|
handler: (sender, indexPath) => {
|
|
|
let od = sender.data
|
|
|
let section = od[indexPath.section]
|
|
|
let item = section.rows[indexPath.row]
|
|
|
showAlterDialog(section.title, item.proxyLink, (newSec, newLink) => {
|
|
|
item.proxyLink = newLink
|
|
|
item.proxyName.text = newLink.split(/\s*=/)[0]
|
|
|
section.rows[indexPath.row] = item
|
|
|
section.title = newSec
|
|
|
sender.data = formatListData(od)
|
|
|
saveWorkspace()
|
|
|
})
|
|
|
}
|
|
|
}],
|
|
|
borderWidth: 2,
|
|
|
borderColor: $color("#f0f5f5"),
|
|
|
template: {
|
|
|
views: [{
|
|
|
type: 'label',
|
|
|
props: {
|
|
|
id: 'proxyName',
|
|
|
align: $align.left,
|
|
|
line: 1,
|
|
|
textColor: colorUtil.getColor("editorItemText"),
|
|
|
font: $font(16),
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.width.equalTo(view.super).offset(-60)
|
|
|
make.height.equalTo(view.super)
|
|
|
make.left.equalTo(view.super).offset(15)
|
|
|
}
|
|
|
}, {
|
|
|
type: "image",
|
|
|
props: {
|
|
|
id: "proxyAuto",
|
|
|
icon: $icon("062", colorUtil.getColor("editorItemIcon"), $size(15, 15)),
|
|
|
bgcolor: $color("clear"),
|
|
|
hidden: true
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.right.equalTo(view.super).offset(-15)
|
|
|
make.size.equalTo($size(15, 15))
|
|
|
make.centerY.equalTo(view.super)
|
|
|
}
|
|
|
}]
|
|
|
},
|
|
|
// radius: 5
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.width.equalTo(view.super).offset(-20)
|
|
|
make.centerX.equalTo(view.super)
|
|
|
make.height.equalTo(screenHeight - 330 - (typeof $ui.window.next !== 'undefined' ? 45 : 0))
|
|
|
make.top.equalTo($("serverControl").bottom)
|
|
|
},
|
|
|
events: {
|
|
|
didSelect: (sender, indexPath, data) => {
|
|
|
let proxyName = data.proxyName.text
|
|
|
let isSelected = !data.proxyAuto.hidden
|
|
|
let controlInfo = $("serverControl").info
|
|
|
let currentGroup = controlInfo.currentProxyGroup
|
|
|
let customProxyGroup = controlInfo.customProxyGroup || {}
|
|
|
if (isSelected) {
|
|
|
data.proxyAuto.hidden = true
|
|
|
customProxyGroup[currentGroup] = customProxyGroup[currentGroup].filter(i => i !== proxyName)
|
|
|
} else {
|
|
|
data.proxyAuto.hidden = false
|
|
|
customProxyGroup[currentGroup].push(proxyName)
|
|
|
}
|
|
|
let uiData = sender.data
|
|
|
uiData[indexPath.section].rows[indexPath.row] = data
|
|
|
sender.data = uiData
|
|
|
$("serverControl").info = controlInfo
|
|
|
saveWorkspace()
|
|
|
},
|
|
|
reorderFinished: data => {
|
|
|
$thread.background({
|
|
|
delay: 0,
|
|
|
handler: function () {
|
|
|
$("serverEditor").data = formatListData(data)
|
|
|
saveWorkspace()
|
|
|
}
|
|
|
})
|
|
|
},
|
|
|
forEachItem: (view, indexPath) => {
|
|
|
if (indexPath.row % 2 === 1) {
|
|
|
view.bgcolor = $color("#f1f8ff")
|
|
|
} else {
|
|
|
view.bgcolor = $color("white")
|
|
|
}
|
|
|
},
|
|
|
pulled: sender => {
|
|
|
let listSections = $("serverEditor").data.filter(i => /^http/.test(i.url))
|
|
|
linkHandler(listSections.map(i => i.url).join('\n'), {
|
|
|
handler: (res, name, url, type) => {
|
|
|
sender.endRefreshing()
|
|
|
nodeImportHandler(res, name, url, type)
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
}, {
|
|
|
type: "input",
|
|
|
props: {
|
|
|
id: "serverSuffixEditor",
|
|
|
placeholder: '节点后缀',
|
|
|
text: '',
|
|
|
font: $font(18),
|
|
|
type: $kbType.ascii
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.top.equalTo(view.prev.bottom).offset(10)
|
|
|
make.width.equalTo(view.prev).offset(-125)
|
|
|
make.height.equalTo(45)
|
|
|
make.left.equalTo(view.prev.left)
|
|
|
},
|
|
|
events: {
|
|
|
changed: sender => {
|
|
|
saveWorkspace()
|
|
|
},
|
|
|
returned: sender => {
|
|
|
sender.blur()
|
|
|
}
|
|
|
}
|
|
|
}, {
|
|
|
type: "view",
|
|
|
props: {
|
|
|
id: "outputFormatLabel",
|
|
|
bgcolor: $color("#f1f1f1"),
|
|
|
radius: 6
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.right.equalTo(view.super.right).offset(-10)
|
|
|
make.top.equalTo(view.prev)
|
|
|
make.height.equalTo(view.prev)
|
|
|
make.width.equalTo(120)
|
|
|
},
|
|
|
views: [{
|
|
|
type: "image",
|
|
|
props: {
|
|
|
data: $file.read('assets/menu_icon.png'),
|
|
|
bgcolor: $color("clear"),
|
|
|
hidden: true
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.left.equalTo(view.super).offset(10)
|
|
|
make.height.width.equalTo(view.super.height).offset(-25)
|
|
|
make.centerY.equalTo(view.super)
|
|
|
}
|
|
|
}, {
|
|
|
type: "image",
|
|
|
props: {
|
|
|
id: "outputFormatIcon",
|
|
|
data: $file.read('assets/today_surge.png'),
|
|
|
bgcolor: $color("clear")
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.left.equalTo(view.super).offset(5)
|
|
|
make.height.width.equalTo(view.super.height).offset(-15)
|
|
|
make.centerY.equalTo(view.super).offset(1)
|
|
|
}
|
|
|
}, {
|
|
|
type: 'label',
|
|
|
props: {
|
|
|
textColor: colorUtil.getColor("outputFormatText"),
|
|
|
id: 'outputFormatType',
|
|
|
text: 'Surge3 Pro',
|
|
|
align: $align.center,
|
|
|
font: $font("bold", 16),
|
|
|
autoFontSize: true,
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.height.equalTo(view.super)
|
|
|
make.width.equalTo(view.super).offset(-45)
|
|
|
make.left.equalTo(view.prev.right)
|
|
|
make.top.equalTo(view.super)
|
|
|
}
|
|
|
}],
|
|
|
events: {
|
|
|
tapped: sender => {
|
|
|
renderOutputFormatMenu(sender)
|
|
|
}
|
|
|
}
|
|
|
}, {
|
|
|
type: "matrix",
|
|
|
props: {
|
|
|
id: "usualSettings",
|
|
|
columns: 4,
|
|
|
itemHeight: 40,
|
|
|
spacing: 5,
|
|
|
scrollEnabled: false,
|
|
|
data: [{
|
|
|
title: { text: 'ADS', bgcolor: btnOnFg, textColor: btnOffFg }
|
|
|
}, {
|
|
|
title: { text: 'MITM', bgcolor: btnOnFg, textColor: btnOffFg }
|
|
|
}, {
|
|
|
title: { text: 'Emoji', bgcolor: btnOnFg, textColor: btnOffFg }
|
|
|
}, {
|
|
|
title: { text: '导出', bgcolor: btnOnFg, textColor: btnOffFg }
|
|
|
}],
|
|
|
template: [{
|
|
|
type: "label",
|
|
|
props: {
|
|
|
id: "title",
|
|
|
align: $align.center,
|
|
|
font: $font(14),
|
|
|
radius: 5,
|
|
|
borderColor: btnOnBg,
|
|
|
borderWidth: 0.3,
|
|
|
},
|
|
|
layout: $layout.fill
|
|
|
}]
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.width.equalTo(view.super).offset(-10)
|
|
|
make.centerX.equalTo(view.super)
|
|
|
make.height.equalTo(50)
|
|
|
make.top.equalTo($("serverSuffixEditor").bottom).offset(5)
|
|
|
},
|
|
|
events: {
|
|
|
didSelect: (sender, indexPath, data) => {
|
|
|
if (indexPath.row === 2) {
|
|
|
refreshListEmoji(isEmoji())
|
|
|
}
|
|
|
data.title.bgcolor = cu.isEqual(data.title.bgcolor, btnOnBg) ? btnOffBg : btnOnBg
|
|
|
data.title.textColor = cu.isEqual(data.title.bgcolor, btnOnFg) ? btnOffFg : btnOnFg
|
|
|
let uiData = $("usualSettings").data
|
|
|
uiData[indexPath.row] = data
|
|
|
$("usualSettings").data = uiData
|
|
|
saveWorkspace()
|
|
|
}
|
|
|
}
|
|
|
}]
|
|
|
}, {
|
|
|
type: "button",
|
|
|
props: {
|
|
|
id: "advanceBtn",
|
|
|
titleColor: colorUtil.getColor("advanceBtnFg"),
|
|
|
title: "进阶设置",
|
|
|
bgcolor: colorUtil.getColor("advanceBtnBg")
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.width.equalTo((screenWidth / 2 - 15) * 0.686 - 10)
|
|
|
make.left.equalTo(10)
|
|
|
make.height.equalTo(40)
|
|
|
make.top.equalTo($("usualSettings").bottom).offset(5)
|
|
|
},
|
|
|
events: {
|
|
|
tapped: sender => {
|
|
|
renderAdvanceUI()
|
|
|
}
|
|
|
}
|
|
|
}, {
|
|
|
type: "button",
|
|
|
props: {
|
|
|
id: "aboutBtn",
|
|
|
titleColor: colorUtil.getColor("aboutBtnFg"),
|
|
|
title: "关于",
|
|
|
bgcolor: colorUtil.getColor("aboutBtnBg")
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.height.equalTo(40)
|
|
|
make.width.equalTo((screenWidth / 2 - 15) * 0.314 + 5)
|
|
|
make.left.equalTo($("advanceBtn").right).offset(5)
|
|
|
make.top.equalTo($("usualSettings").bottom).offset(5)
|
|
|
},
|
|
|
events: {
|
|
|
tapped: sender => {
|
|
|
renderAboutUI()
|
|
|
}
|
|
|
}
|
|
|
}, {
|
|
|
type: "button",
|
|
|
props: {
|
|
|
id: "genBtn",
|
|
|
titleColor: colorUtil.getColor("genBtnFg"),
|
|
|
bgcolor: colorUtil.getColor("genBtnBg"),
|
|
|
title: "生成配置"
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.width.equalTo((screenWidth - 10) * 0.5 - 5)
|
|
|
make.height.equalTo(40)
|
|
|
make.right.equalTo(view.super).offset(-10)
|
|
|
make.top.equalTo($("usualSettings").bottom).offset(5)
|
|
|
},
|
|
|
events: {
|
|
|
tapped: sender => {
|
|
|
makeConf({
|
|
|
onStart: () => {
|
|
|
$("progressView").hidden = false
|
|
|
$ui.animate({
|
|
|
duration: 0.2,
|
|
|
animation: function () {
|
|
|
$("progressView").alpha = 1
|
|
|
}
|
|
|
})
|
|
|
},
|
|
|
onProgress: (p, hint) => {
|
|
|
hint !== '' && ($("loadingHintLabel").text = hint)
|
|
|
$("progressBar").value = p
|
|
|
},
|
|
|
onDone: res => {
|
|
|
$ui.animate({
|
|
|
duration: 0.3,
|
|
|
animation: function () {
|
|
|
$("progressView").alpha = 0
|
|
|
},
|
|
|
completion: function () {
|
|
|
$("loadingHintLabel").text = '处理中,请稍后'
|
|
|
$("progressBar").value = 0
|
|
|
$("progressView").hidden = true
|
|
|
}
|
|
|
})
|
|
|
exportConf(res.fileName, res.fileData, res.target, res.actionSheet, false, () => {
|
|
|
$http.stopServer()
|
|
|
})
|
|
|
$app.listen({
|
|
|
resume: function () {
|
|
|
$http.stopServer()
|
|
|
}
|
|
|
})
|
|
|
},
|
|
|
onError: res => {
|
|
|
$("progressView").value = 0
|
|
|
$("progressView").hidden = true
|
|
|
}
|
|
|
})
|
|
|
},
|
|
|
longPressed: sender => {
|
|
|
$share.sheet(['data.js', $file.read('data.js')])
|
|
|
}
|
|
|
}
|
|
|
}, {
|
|
|
type: "blur",
|
|
|
props: {
|
|
|
id: "progressView",
|
|
|
style: 1,
|
|
|
alpha: 0,
|
|
|
hidden: true
|
|
|
},
|
|
|
layout: $layout.fill,
|
|
|
views: [{
|
|
|
type: "label",
|
|
|
props: {
|
|
|
id: "loadingHintLabel",
|
|
|
text: "处理中,请稍后",
|
|
|
textColor: $color("black"),
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.centerX.equalTo(view.super)
|
|
|
make.centerY.equalTo(view.super).offset(-30)
|
|
|
}
|
|
|
}, {
|
|
|
type: "progress",
|
|
|
props: {
|
|
|
id: "progressBar",
|
|
|
value: 0
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.width.equalTo(screenWidth * 0.8)
|
|
|
make.center.equalTo(view.super)
|
|
|
make.height.equalTo(3)
|
|
|
}
|
|
|
}]
|
|
|
},]
|
|
|
})
|
|
|
}
|
|
|
|
|
|
let nodeImportHandler = (res, name, url, type = -1) => {
|
|
|
// 如果是托管,url不为undefined
|
|
|
// console.log([res, name, url])
|
|
|
let listData = $("serverEditor").data || []
|
|
|
let existsSec = listData.find(item => item.url === url)
|
|
|
if (!res || res.length === 0) {
|
|
|
$ui.alert({
|
|
|
title: `${existsSec ? '更新' : '获取'}失败`,
|
|
|
message: `${existsSec ? existsSec.title : url}`
|
|
|
})
|
|
|
return
|
|
|
}
|
|
|
let section = existsSec || { title: name, rows: [], url: url }
|
|
|
section.rows = []
|
|
|
let controlInfo = $("serverControl").info
|
|
|
for (let idx in res) {
|
|
|
if (res[idx].split("=")[1].trim() == 'direct') {
|
|
|
// 过滤直连
|
|
|
continue
|
|
|
}
|
|
|
let selected = controlInfo.customProxyGroup[controlInfo.currentProxyGroup].indexOf(res[idx].split('=')[0].trim()) > -1
|
|
|
section.rows.push({
|
|
|
proxyName: { text: res[idx].split('=')[0].trim() },
|
|
|
proxyLink: res[idx],
|
|
|
proxyAuto: { hidden: !selected },
|
|
|
proxyType: type
|
|
|
})
|
|
|
}
|
|
|
if (!existsSec) {
|
|
|
listData.unshift(section)
|
|
|
}
|
|
|
$("serverEditor").data = listData
|
|
|
saveWorkspace()
|
|
|
}
|
|
|
|
|
|
function formatListData(data) {
|
|
|
if (!data || data.length === 0) {
|
|
|
return []
|
|
|
}
|
|
|
let noGroup = []
|
|
|
data = data.map(i => {
|
|
|
if (i.title === '' || i.rows.length === 0) {
|
|
|
noGroup = noGroup.concat(i.rows)
|
|
|
return null
|
|
|
}
|
|
|
return i
|
|
|
}).filter(i => i !== null)
|
|
|
if (noGroup.length > 0) {
|
|
|
data.unshift({ title: "", rows: noGroup })
|
|
|
}
|
|
|
return data
|
|
|
}
|
|
|
|
|
|
|
|
|
function loading(on) {
|
|
|
let iconView = $("navLoadingIcon")
|
|
|
if (on) {
|
|
|
// iconView.play()
|
|
|
} else {
|
|
|
// iconView.pause()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function refreshListEmoji(isEmoji) {
|
|
|
function addEmoji(emojiSet, name) {
|
|
|
let minIdx = 300;
|
|
|
let resEmoji = '';
|
|
|
for (let idx in emojiSet) {
|
|
|
let reg = `(${emojiSet[idx].slice(1).join('|')})`
|
|
|
let matcher = name.match(new RegExp(reg))
|
|
|
if (matcher && matcher.index < minIdx) {
|
|
|
minIdx = matcher.index
|
|
|
resEmoji = emojiSet[idx][0]
|
|
|
}
|
|
|
}
|
|
|
return minIdx !== 300 ? `${resEmoji} ${name}` : name
|
|
|
}
|
|
|
|
|
|
function removeEmoji(emojiSet, name) {
|
|
|
let emoji = emojiSet.map(i => i[0])
|
|
|
let reg = `(${emoji.join('|')}) `
|
|
|
return name.replace(new RegExp(reg, 'g'), '')
|
|
|
}
|
|
|
|
|
|
let serverEditorData = $("serverEditor").data
|
|
|
|
|
|
loading(true)
|
|
|
$http.get({
|
|
|
url: "https://raw.githubusercontent.com/Fndroid/country_emoji/master/emoji.json" + `?t=${new Date().getTime()}`
|
|
|
}).then(resp => {
|
|
|
loading(false)
|
|
|
let emojiSet = resp.data
|
|
|
$("serverEditor").data = serverEditorData.map(sec => {
|
|
|
sec.rows = sec.rows.map(i => {
|
|
|
i.proxyLink = isEmoji ? removeEmoji(emojiSet, i.proxyLink) : addEmoji(emojiSet, i.proxyLink)
|
|
|
i.proxyName.text = i.proxyLink.split(/\s*=/)[0]
|
|
|
return i
|
|
|
})
|
|
|
return sec
|
|
|
})
|
|
|
saveWorkspace()
|
|
|
}).catch(error => {
|
|
|
loading(false)
|
|
|
$ui.alert("Emoji配置获取失败")
|
|
|
})
|
|
|
}
|
|
|
|
|
|
function showAlterDialog(reg, rep, callback) {
|
|
|
let fontSize = $text.sizeThatFits({
|
|
|
text: rep,
|
|
|
width: screenWidth - 100,
|
|
|
font: $font(16)
|
|
|
})
|
|
|
let view = {
|
|
|
type: "blur",
|
|
|
layout: $layout.fill,
|
|
|
props: {
|
|
|
id: "alertBody",
|
|
|
style: 1,
|
|
|
alpha: 0
|
|
|
},
|
|
|
views: [{
|
|
|
type: "view",
|
|
|
props: {
|
|
|
id: "alterMainView",
|
|
|
bgcolor: $color("#ccc"),
|
|
|
smoothRadius: 10
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.height.equalTo(230 + fontSize.height);
|
|
|
make.width.equalTo(view.super).offset(-60);
|
|
|
make.center.equalTo(view.super)
|
|
|
},
|
|
|
events: {
|
|
|
tapped: sender => { }
|
|
|
},
|
|
|
views: [{
|
|
|
type: "label",
|
|
|
props: {
|
|
|
text: "组别名称",
|
|
|
font: $font("bold", 16)
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.top.equalTo(view.super).offset(20);
|
|
|
make.left.equalTo(view.super).offset(10);
|
|
|
}
|
|
|
}, {
|
|
|
type: "input",
|
|
|
props: {
|
|
|
id: "alterInputSection",
|
|
|
text: reg,
|
|
|
autoFontSize: true
|
|
|
},
|
|
|
events: {
|
|
|
returned: sender => {
|
|
|
sender.blur()
|
|
|
}
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.top.equalTo(view.prev.bottom).offset(10);
|
|
|
make.width.equalTo(view.super).offset(-20);
|
|
|
make.centerX.equalTo(view.super)
|
|
|
make.left.equalTo(view.super).offset(10);
|
|
|
make.height.equalTo(40)
|
|
|
}
|
|
|
}, {
|
|
|
type: "label",
|
|
|
props: {
|
|
|
text: "节点信息",
|
|
|
font: $font("bold", 16)
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.top.equalTo(view.prev.bottom).offset(15);
|
|
|
make.left.equalTo(view.super).offset(10);
|
|
|
}
|
|
|
}, {
|
|
|
type: "text",
|
|
|
props: {
|
|
|
id: "alberInputLink",
|
|
|
text: rep,
|
|
|
autoFontSize: true,
|
|
|
radius: 6,
|
|
|
font: $font(16),
|
|
|
bgcolor: $color("#eff0f2"),
|
|
|
insets: $insets(10, 5, 10, 5)
|
|
|
},
|
|
|
events: {
|
|
|
returned: sender => {
|
|
|
sender.blur()
|
|
|
}
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.top.equalTo(view.prev.bottom).offset(10);
|
|
|
make.width.equalTo(view.super).offset(-20);
|
|
|
make.centerX.equalTo(view.super)
|
|
|
make.left.equalTo(view.super).offset(10);
|
|
|
make.height.equalTo(fontSize.height + 20)
|
|
|
}
|
|
|
}, {
|
|
|
type: 'button',
|
|
|
props: {
|
|
|
icon: $icon("064", $color("#fff"), $size(20, 20)),
|
|
|
id: 'confirmBtn',
|
|
|
radius: 25
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.height.width.equalTo(50)
|
|
|
make.bottom.equalTo(view.super).offset(-10)
|
|
|
make.right.equalTo(view.super).offset(-10)
|
|
|
},
|
|
|
events: {
|
|
|
tapped: sender => {
|
|
|
callback && callback($("alterInputSection").text, $("alberInputLink").text);
|
|
|
$("alertBody").remove();
|
|
|
}
|
|
|
}
|
|
|
}]
|
|
|
}],
|
|
|
events: {
|
|
|
tapped: sender => {
|
|
|
sender.remove()
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
$("bodyView").add(view)
|
|
|
$ui.animate({
|
|
|
duration: 0.2,
|
|
|
animation: () => {
|
|
|
$("alertBody").alpha = 1
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
|
|
|
function renderOutputFormatMenu(superView) {
|
|
|
$("bodyView").add({
|
|
|
type: "view",
|
|
|
props: {
|
|
|
id: "outputFormatSelectorView",
|
|
|
alpha: 0
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.height.width.equalTo(view.super)
|
|
|
make.center.equalTo(view.super)
|
|
|
},
|
|
|
views: [{
|
|
|
type: "blur",
|
|
|
props: {
|
|
|
style: 2,
|
|
|
alpha: 1,
|
|
|
},
|
|
|
layout: $layout.fill,
|
|
|
events: {
|
|
|
tapped: sender => {
|
|
|
hideView(sender);
|
|
|
}
|
|
|
}
|
|
|
}, {
|
|
|
type: "list",
|
|
|
props: {
|
|
|
id: "outputFormatSelectorItems",
|
|
|
radius: 15,
|
|
|
rowHeight: 50,
|
|
|
alwaysBounceVertical: false,
|
|
|
data: ['Surge 3 TF', 'Surge 3 Pro', 'Surge 2', 'Quantumult'],
|
|
|
frame: resetFrame(superView.frame),
|
|
|
header: {
|
|
|
type: "label",
|
|
|
props: {
|
|
|
text: "选择导出格式",
|
|
|
height: 50,
|
|
|
font: $font("bold", 15),
|
|
|
align: 1
|
|
|
}
|
|
|
},
|
|
|
separatorHidden: true
|
|
|
},
|
|
|
events: {
|
|
|
didSelect: (sender, indexPath, data) => {
|
|
|
let type = 'surge'
|
|
|
if (data === 'Quantumult') {
|
|
|
type = 'quan'
|
|
|
} else if (data === 'Surge 2') {
|
|
|
type = 'surge2'
|
|
|
}
|
|
|
$("outputFormatType").text = data
|
|
|
$("outputFormatIcon").data = $file.read(`assets/today_${type}.png`)
|
|
|
saveWorkspace()
|
|
|
hideView(sender)
|
|
|
}
|
|
|
}
|
|
|
}]
|
|
|
})
|
|
|
|
|
|
$ui.animate({
|
|
|
duration: 0.3,
|
|
|
damping: 0.8,
|
|
|
velocity: 0.3,
|
|
|
animation: () => {
|
|
|
superView.scale(1.2)
|
|
|
$("outputFormatSelectorView").alpha = 1
|
|
|
$("outputFormatSelectorItems").frame = $rect(80, screenHeight - 430 + navBarHeight + statusBarHeight, screenWidth - 90, 250)
|
|
|
}
|
|
|
})
|
|
|
|
|
|
function hideView(sender) {
|
|
|
$ui.animate({
|
|
|
duration: 0.2,
|
|
|
velocity: 0.5,
|
|
|
animation: () => {
|
|
|
superView.scale(1)
|
|
|
$("outputFormatSelectorView").alpha = 0;
|
|
|
$("outputFormatSelectorItems").frame = resetFrame(superView.frame);
|
|
|
},
|
|
|
completion: () => {
|
|
|
sender.super.remove();
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function resetFrame(frame) {
|
|
|
return $rect(frame.x, frame.y + navBarHeight + statusBarHeight, frame.width, frame.height)
|
|
|
}
|
|
|
|
|
|
function archivesHandler() {
|
|
|
const ARCHIVES = $addin.current.name + '/archivesFiles'
|
|
|
if (!$drive.exists(ARCHIVES)) {
|
|
|
$drive.mkdir(ARCHIVES)
|
|
|
}
|
|
|
console.log($drive.list(ARCHIVES))
|
|
|
let latestVersion = ($app.info.build * 1) > 335
|
|
|
let getFiles = function () {
|
|
|
return $drive.list(ARCHIVES).map(i => {
|
|
|
let path = i.runtimeValue().invoke('pathComponents').rawValue()
|
|
|
let name = latestVersion ? i : path[path.length - 1]
|
|
|
return {
|
|
|
archiveName: {
|
|
|
text: name,
|
|
|
textColor: name === $cache.get('currentArchive') ? $color("red") : $color("black")
|
|
|
}
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
$("bodyView").add({
|
|
|
type: "view",
|
|
|
props: {
|
|
|
id: "archivesView",
|
|
|
alpha: 0
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.height.width.equalTo(view.super)
|
|
|
make.center.equalTo(view.super)
|
|
|
},
|
|
|
views: [{
|
|
|
type: "blur",
|
|
|
props: {
|
|
|
style: 2,
|
|
|
alpha: 1,
|
|
|
},
|
|
|
layout: $layout.fill,
|
|
|
events: {
|
|
|
tapped: sender => {
|
|
|
$ui.animate({
|
|
|
duration: 0.2,
|
|
|
animation: () => {
|
|
|
$("archivesView").alpha = 0
|
|
|
$("archivesList").frame = $rect(0, 0, screenWidth, screenHeight)
|
|
|
},
|
|
|
completion: () => {
|
|
|
sender.super.remove()
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
}, {
|
|
|
type: "list",
|
|
|
props: {
|
|
|
id: "archivesList",
|
|
|
radius: 15,
|
|
|
data: getFiles(),
|
|
|
header: {
|
|
|
type: "label",
|
|
|
props: {
|
|
|
text: "配置备份",
|
|
|
height: 50,
|
|
|
font: $font("bold", 20),
|
|
|
align: $align.center
|
|
|
}
|
|
|
},
|
|
|
template: {
|
|
|
props: {
|
|
|
bgcolor: $color("clear")
|
|
|
},
|
|
|
views: [
|
|
|
{
|
|
|
type: "label",
|
|
|
props: {
|
|
|
id: "archiveName"
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.width.equalTo(view.super).offset(-20)
|
|
|
make.left.equalTo(view.super).offset(10)
|
|
|
make.height.equalTo(view.super)
|
|
|
}
|
|
|
}
|
|
|
]
|
|
|
},
|
|
|
actions: [{
|
|
|
title: "删除",
|
|
|
color: $color('red'),
|
|
|
handler: (sender, indexPath) => {
|
|
|
let fileName = sender.object(indexPath).archiveName.text
|
|
|
let success = $drive.delete(ARCHIVES + '/' + fileName)
|
|
|
if (success) {
|
|
|
sender.data = getFiles()
|
|
|
}
|
|
|
}
|
|
|
}, {
|
|
|
title: "导出",
|
|
|
handler: (sender, indexPath) => {
|
|
|
let fileName = sender.object(indexPath).archiveName.text
|
|
|
$share.sheet(['data.js', $drive.read(ARCHIVES + "/" + fileName)])
|
|
|
}
|
|
|
}, {
|
|
|
title: "覆盖",
|
|
|
color: $color("tint"),
|
|
|
handler: (sender, indexPath) => {
|
|
|
let filename = sender.object(indexPath).archiveName.text
|
|
|
let success = $drive.write({
|
|
|
data: $file.read('data.js'),
|
|
|
path: ARCHIVES + '/' + filename
|
|
|
})
|
|
|
$ui.toast("配置文件覆盖" + (success ? "成功" : "失败"))
|
|
|
}
|
|
|
}]
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.height.equalTo(view.super).dividedBy(12 / 7)
|
|
|
make.width.equalTo(view.super).dividedBy(12 / 9)
|
|
|
make.center.equalTo(view.super)
|
|
|
},
|
|
|
events: {
|
|
|
didSelect: (sender, indexPath, item) => {
|
|
|
let data = item.archiveName.text
|
|
|
if (/\..*?\.icloud/.test(data)) {
|
|
|
$ui.alert(`备份文件不在本地,请先进入iCloud下载,路径为:文件 -> JSBox -> ${$addin.current.name} -> archivesFiles`)
|
|
|
return
|
|
|
}
|
|
|
let success = $file.write({
|
|
|
data: $drive.read(ARCHIVES + '/' + data),
|
|
|
path: "data.js"
|
|
|
})
|
|
|
if (success) {
|
|
|
$cache.set('currentArchive', data)
|
|
|
$app.notify({
|
|
|
name: 'loadData'
|
|
|
})
|
|
|
$ui.animate({
|
|
|
duration: 0.2,
|
|
|
animation: () => {
|
|
|
$("archivesView").alpha = 0
|
|
|
$("archivesList").frame = $rect(0, 0, screenWidth, screenHeight)
|
|
|
},
|
|
|
completion: () => {
|
|
|
sender.super.remove()
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}, {
|
|
|
type: "button",
|
|
|
props: {
|
|
|
title: "+",
|
|
|
circular: true,
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.bottom.equalTo(view.prev)
|
|
|
make.right.equalTo(view.prev).offset(-5)
|
|
|
make.height.width.equalTo(50)
|
|
|
},
|
|
|
events: {
|
|
|
tapped: sender => {
|
|
|
$input.text({
|
|
|
type: $kbType.default,
|
|
|
placeholder: "请输入备份文件名",
|
|
|
handler: function (text) {
|
|
|
if (text === '') {
|
|
|
$ui.error("无法创建备份")
|
|
|
return
|
|
|
}
|
|
|
let success = $drive.write({
|
|
|
data: $file.read('data.js'),
|
|
|
path: ARCHIVES + '/' + text
|
|
|
})
|
|
|
if (success) {
|
|
|
$cache.set('currentArchive', text)
|
|
|
sender.prev.data = getFiles()
|
|
|
}
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
}]
|
|
|
})
|
|
|
|
|
|
$ui.animate({
|
|
|
duration: .3,
|
|
|
damping: 0.8,
|
|
|
velocity: 0.3,
|
|
|
animation: () => {
|
|
|
$("archivesView").alpha = 1
|
|
|
$("archivesList").scale(1.1)
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
|
|
|
// function specialProxyGroup() {
|
|
|
// if (getRulesReplacement()) {
|
|
|
// $ui.alert('检测到有规则替换,无法使用特殊代理设置')
|
|
|
// return
|
|
|
// }
|
|
|
// let groups = getProxyGroups();
|
|
|
// const menuItems = groups.concat(['🚀 Direct', '查看设置', '清除设置']);
|
|
|
// $ui.menu({
|
|
|
// items: menuItems,
|
|
|
// handler: function (mTitle, idx) {
|
|
|
// if (idx === menuItems.length - 1) {
|
|
|
// $("serverEditor").info = {};
|
|
|
// saveWorkspace();
|
|
|
// }
|
|
|
// else if (idx === menuItems.length - 2) {
|
|
|
// let videoProxy = $("serverEditor").info;
|
|
|
// let output = [];
|
|
|
// for (let k in videoProxy) {
|
|
|
// output.push(`${k} - ${videoProxy[k]}`);
|
|
|
// }
|
|
|
// $ui.alert(output.length > 0 ? output.join('\n') : "无设置特殊代理");
|
|
|
// }
|
|
|
// else {
|
|
|
// let videoReg = require('scripts/videoReg')
|
|
|
// $ui.menu({
|
|
|
// items: Object.keys(videoReg),
|
|
|
// handler: function (title, idx) {
|
|
|
// let proxyName = mTitle;
|
|
|
// let videoProxy = $("serverEditor").info;
|
|
|
// videoProxy[title] = proxyName;
|
|
|
// $("serverEditor").info = videoProxy;
|
|
|
// saveWorkspace();
|
|
|
// }
|
|
|
// });
|
|
|
// }
|
|
|
// }
|
|
|
// });
|
|
|
// }
|
|
|
|
|
|
function genControlItems() {
|
|
|
let currentProxyGroup = PROXY_HEADER
|
|
|
try {
|
|
|
currentProxyGroup = $("serverControl").info.currentProxyGroup
|
|
|
} catch (e) { }
|
|
|
return [{
|
|
|
title: { text: '节点倒序' }
|
|
|
}, {
|
|
|
title: { text: currentProxyGroup }
|
|
|
}, {
|
|
|
title: { text: '删除分组' }
|
|
|
}]
|
|
|
}
|
|
|
|
|
|
function getProxyGroups() {
|
|
|
let fileData = JSON.parse($file.read(FILE).string)
|
|
|
let proxyGroupSettings = fileData.proxyGroupSettings
|
|
|
let groups = proxyGroupSettings.split(/[\n\r]/).filter(i => /^(?!\/\/)[\s\S]+=[\s\S]+/.test(i)).map(i => i.split('=')[0].trim())
|
|
|
return groups
|
|
|
}
|
|
|
|
|
|
function groupShortcut() {
|
|
|
let controlInfo = $("serverControl").info
|
|
|
let currentProxyGroup = controlInfo.currentProxyGroup || PROXY_HEADER
|
|
|
let customProxyGroup = controlInfo.customProxyGroup || {}
|
|
|
let menuItems = Object.keys(customProxyGroup).sort()
|
|
|
$("bodyView").add({
|
|
|
type: "view",
|
|
|
props: {
|
|
|
id: "placeholderView",
|
|
|
alpha: 0
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.height.width.equalTo(view.super)
|
|
|
make.center.equalTo(view.super)
|
|
|
},
|
|
|
views: [{
|
|
|
type: "blur",
|
|
|
props: {
|
|
|
style: 2,
|
|
|
alpha: 1,
|
|
|
},
|
|
|
layout: $layout.fill,
|
|
|
events: {
|
|
|
tapped: sender => {
|
|
|
removeAnimation()
|
|
|
}
|
|
|
}
|
|
|
}, {
|
|
|
type: "list",
|
|
|
props: {
|
|
|
id: "placeholderList",
|
|
|
radius: 15,
|
|
|
data: menuItems,
|
|
|
header: {
|
|
|
type: "label",
|
|
|
props: {
|
|
|
text: "占位符",
|
|
|
height: 50,
|
|
|
font: $font("bold", 20),
|
|
|
align: $align.center
|
|
|
}
|
|
|
},
|
|
|
actions: [{
|
|
|
title: "删除",
|
|
|
color: $color('red'),
|
|
|
handler: (sender, indexPath) => {
|
|
|
let title = sender.object(indexPath)
|
|
|
if ([PROXY_HEADER, 'Proxy Header'].indexOf(title) > -1) {
|
|
|
$ui.error("此占位符无法删除")
|
|
|
return
|
|
|
}
|
|
|
delete customProxyGroup[title]
|
|
|
$("serverControl").info = controlInfo
|
|
|
saveWorkspace()
|
|
|
$("placeholderList").data = Object.keys(customProxyGroup).sort()
|
|
|
}
|
|
|
}, {
|
|
|
title: "重命名",
|
|
|
handler: (sender, indexPath) => {
|
|
|
let title = sender.object(indexPath)
|
|
|
if ([PROXY_HEADER, 'Proxy Header'].indexOf(title) > -1) {
|
|
|
$ui.error("此占位符无法重命名")
|
|
|
return
|
|
|
}
|
|
|
$input.text({
|
|
|
type: $kbType.default,
|
|
|
placeholder: title,
|
|
|
handler: function (text) {
|
|
|
if (sender.data.indexOf(text) > -1) {
|
|
|
$ui.error("此名称已被占用")
|
|
|
} else {
|
|
|
customProxyGroup[text] = customProxyGroup[title]
|
|
|
delete customProxyGroup[title]
|
|
|
if ($("serverControl").info.currentProxyGroup === title) {
|
|
|
switchToGroup(text)
|
|
|
}
|
|
|
$("serverControl").info = controlInfo
|
|
|
saveWorkspace()
|
|
|
sender.data = Object.keys(customProxyGroup).sort()
|
|
|
}
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
}, {
|
|
|
title: "删除节点",
|
|
|
color: $color('tint'),
|
|
|
handler: (sender, indexPath) => {
|
|
|
let title = sender.object(indexPath)
|
|
|
let headers = customProxyGroup[title]
|
|
|
let editorData = $("serverEditor").data
|
|
|
editorData.map(section => {
|
|
|
section.rows = section.rows.filter(item => headers.indexOf(item.proxyName.text) === -1)
|
|
|
return section
|
|
|
})
|
|
|
$("serverEditor").data = editorData
|
|
|
saveWorkspace()
|
|
|
$ui.toast("已删除占位符对应节点")
|
|
|
removeAnimation()
|
|
|
}
|
|
|
}]
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.height.equalTo(view.super).dividedBy(12 / 7)
|
|
|
make.width.equalTo(view.super).dividedBy(12 / 9)
|
|
|
make.center.equalTo(view.super)
|
|
|
},
|
|
|
events: {
|
|
|
didSelect: (sender, indexPath, data) => {
|
|
|
$ui.toast(`当前占位符为:${data}`)
|
|
|
switchToGroup(data)
|
|
|
removeAnimation()
|
|
|
}
|
|
|
}
|
|
|
}, {
|
|
|
type: "button",
|
|
|
props: {
|
|
|
title: "+",
|
|
|
circular: true,
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.bottom.equalTo(view.prev)
|
|
|
make.right.equalTo(view.prev).offset(-5)
|
|
|
make.height.width.equalTo(50)
|
|
|
},
|
|
|
events: {
|
|
|
tapped: sender => {
|
|
|
$input.text({
|
|
|
type: $kbType.default,
|
|
|
placeholder: "建议:xxxHeader",
|
|
|
handler: function (text) {
|
|
|
if ([PROXY_HEADER, 'Proxy Header', ''].indexOf(text) > -1) {
|
|
|
$ui.error("占位符名称冲突")
|
|
|
return
|
|
|
}
|
|
|
customProxyGroup[text] = []
|
|
|
$("serverControl").info = controlInfo
|
|
|
saveWorkspace()
|
|
|
$("placeholderList").data = Object.keys(customProxyGroup).sort()
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
}]
|
|
|
})
|
|
|
|
|
|
function removeAnimation() {
|
|
|
$ui.animate({
|
|
|
duration: 0.2,
|
|
|
animation: () => {
|
|
|
$("placeholderView").alpha = 0
|
|
|
$("placeholderList").frame = resetFrame($("serverEditor").frame)
|
|
|
},
|
|
|
completion: () => {
|
|
|
$("placeholderView").remove()
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
|
|
|
$ui.animate({
|
|
|
duration: .3,
|
|
|
damping: 0.8,
|
|
|
animation: () => {
|
|
|
$("placeholderView").alpha = 1
|
|
|
$("placeholderList").scale(1.1)
|
|
|
}
|
|
|
})
|
|
|
|
|
|
function switchToGroup(title) {
|
|
|
let group = customProxyGroup[title];
|
|
|
// 保存当前编辑策略组
|
|
|
controlInfo.currentProxyGroup = title;
|
|
|
$("serverControl").info = controlInfo;
|
|
|
// 恢复选中的策略组UI
|
|
|
let listData = $("serverEditor").data || [];
|
|
|
listData = listData.map(section => {
|
|
|
section.rows = section.rows.map(item => {
|
|
|
item.proxyAuto.hidden = !(group.indexOf(item.proxyName.text) > -1)
|
|
|
return item;
|
|
|
});
|
|
|
return section;
|
|
|
});
|
|
|
$("serverEditor").data = listData;
|
|
|
$("serverControl").data = genControlItems()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function listReplace(sender, indexPath, obj) {
|
|
|
let oldData = sender.data
|
|
|
if (indexPath.section != null) {
|
|
|
oldData[indexPath.section].rows[indexPath.row] = obj
|
|
|
} else {
|
|
|
oldData[indexPath.row] = obj
|
|
|
}
|
|
|
sender.data = oldData
|
|
|
}
|
|
|
|
|
|
function getAutoRules(url, done, hint = '') {
|
|
|
return new Promise((resolve, reject) => {
|
|
|
$http.get({
|
|
|
url: url,
|
|
|
handler: function (resp) {
|
|
|
if (done) done(hint)
|
|
|
resolve(resp.data)
|
|
|
}
|
|
|
})
|
|
|
})
|
|
|
}
|
|
|
|
|
|
function importMenu(params) {
|
|
|
let staticItems = ['剪贴板', '二维码', '空节点']
|
|
|
$ui.menu({
|
|
|
items: staticItems,
|
|
|
handler: function (title, idx) {
|
|
|
if (title === staticItems[0]) {
|
|
|
let clipText = $clipboard.text
|
|
|
linkHandler(clipText, params)
|
|
|
} else if (title === staticItems[1]) {
|
|
|
$qrcode.scan({
|
|
|
handler(string) {
|
|
|
linkHandler(string, params)
|
|
|
}
|
|
|
})
|
|
|
} else if (title === staticItems[2]) {
|
|
|
params.handler(['Empty Node = '], 'Default', `emptynode${new Date().getTime()}`)
|
|
|
}
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
|
|
|
function isEmoji() {
|
|
|
try {
|
|
|
let advanceSettings = JSON.parse($file.read(FILE).string)
|
|
|
let workspace = advanceSettings.workspace
|
|
|
let usualData = workspace.usualData
|
|
|
|
|
|
let usualValue = function (key) {
|
|
|
return usualData.find(i => i.title.text == key) ? usualData.find(i => i.title.text == key).title.bgcolor : false
|
|
|
}
|
|
|
return usualValue('Emoji')
|
|
|
} catch (e) {
|
|
|
return false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function linkHandler(url, params) {
|
|
|
let emoji = isEmoji()
|
|
|
let servers = {
|
|
|
shadowsocks: [],
|
|
|
surge: [],
|
|
|
online: [],
|
|
|
vmess: [],
|
|
|
ignore: [],
|
|
|
shadowsocksr: []
|
|
|
}
|
|
|
|
|
|
if (!url) {
|
|
|
$ui.alert('没有识别到有效链接')
|
|
|
return
|
|
|
}
|
|
|
|
|
|
let urls = url.split(/[\r\n]+/g).map(i => i.trim()).filter(i => i !== '')
|
|
|
urls.forEach(item => {
|
|
|
if (/^ss:\/\//.test(item)) {
|
|
|
servers.shadowsocks.push(item)
|
|
|
} else if (/^https?:\/\//.test(item)) {
|
|
|
servers.online.push(item)
|
|
|
} else if (/[\S\s]+=[\s]*(custom|ss|http|https|socks5|socks5-tls|external)/.test(item)) {
|
|
|
servers.surge.push(item)
|
|
|
} else if (/^vmess:\/\//.test(item)) {
|
|
|
servers.vmess.push(item)
|
|
|
} else if (/^ssr:\/\//.test(item)) {
|
|
|
servers.shadowsocksr.push(item)
|
|
|
} else {
|
|
|
servers.ignore.push(item)
|
|
|
}
|
|
|
})
|
|
|
|
|
|
let updateHint = ''
|
|
|
updateHint += servers.shadowsocks.length > 0 ? `\nShadowsocks链接${servers.shadowsocks.length}个\n` : ''
|
|
|
updateHint += servers.shadowsocksr.length > 0 ? `\nShadowsocksR链接${servers.shadowsocksr.length}个\n` : ''
|
|
|
updateHint += servers.surge.length > 0 ? `\nSurge链接${servers.surge.length}个\n` : ''
|
|
|
updateHint += servers.vmess.length > 0 ? `\nV2Ray链接${servers.vmess.length}个\n` : ''
|
|
|
updateHint += servers.online.length > 0 ? `\n托管或订阅${servers.online.length}个\n` : ''
|
|
|
// $ui.alert({
|
|
|
// title: '更新概况',
|
|
|
// message: updateHint
|
|
|
// })
|
|
|
|
|
|
function addEmoji(emojiSet, link) {
|
|
|
let name = link.split(/=/)[0]
|
|
|
let minIdx = 300;
|
|
|
let resEmoji = '';
|
|
|
for (let idx in emojiSet) {
|
|
|
let reg = `(${emojiSet[idx].slice(1).join('|')})`
|
|
|
let matcher = name.match(new RegExp(reg))
|
|
|
if (matcher && matcher.index < minIdx) {
|
|
|
minIdx = matcher.index
|
|
|
resEmoji = emojiSet[idx][0]
|
|
|
}
|
|
|
}
|
|
|
return minIdx !== 300 ? `${resEmoji} ${link}` : link
|
|
|
}
|
|
|
|
|
|
function detailHandler(emojiSet = null) {
|
|
|
for (let k in servers) {
|
|
|
if (servers[k].length === 0) {
|
|
|
continue
|
|
|
}
|
|
|
if (k === 'shadowsocks') {
|
|
|
let res = proxyUtil.proxyFromURL(servers[k])
|
|
|
params.handler(emojiSet ? res.servers.map(i => addEmoji(emojiSet, i)) : res.servers, res.sstag, servers[k].join('\n'))
|
|
|
} else if (k === 'surge') {
|
|
|
let urls = servers[k].map(i => i.replace(/,[\s]*udp-relay=true/, ''))
|
|
|
let result = []
|
|
|
for (let idx in urls) {
|
|
|
result[idx] = urls[idx]
|
|
|
}
|
|
|
$delay(0.3, function () {
|
|
|
params.handler(emojiSet ? result.map(i => addEmoji(emojiSet, i)) : result, urls.length > 1 ? `批量Surge链接(${urls.length})` : result[0].split('=')[0].trim(), urls.join('\n'))
|
|
|
})
|
|
|
} else if (k === 'online') {
|
|
|
loading(true)
|
|
|
proxyUtil.proxyFromConf({
|
|
|
urls: servers[k],
|
|
|
handler: res => {
|
|
|
console.log('res', res);
|
|
|
loading(false)
|
|
|
params.handler(emojiSet ? res.servers.map(i => addEmoji(emojiSet, i)) : res.servers, res.filename, res.url, res.type)
|
|
|
}
|
|
|
})
|
|
|
} else if (k === 'vmess') {
|
|
|
let res = proxyUtil.proxyFromVmess(servers[k])
|
|
|
params.handler(emojiSet ? res.servers.map(i => addEmoji(emojiSet, i)) : res.servers, res.sstag, servers[k].join('\n'))
|
|
|
} else if (k === 'shadowsocksr') {
|
|
|
let res = proxyUtil.proxyFromSSR(servers[k])
|
|
|
params.handler(emojiSet ? res.servers.map(i => addEmoji(emojiSet, i)) : res.servers, res.sstag, servers[k].join('\n'))
|
|
|
} else {
|
|
|
$ui.alert('剪贴板存在无法识别的行:\n\n' + servers.ignore.join('\n') + '\n\n以上行将被丢弃!')
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (emoji) {
|
|
|
loading(true)
|
|
|
$http.get({
|
|
|
url: "https://raw.githubusercontent.com/Fndroid/country_emoji/master/emoji.json" + `?t=${new Date().getTime()}`
|
|
|
}).then(resp => {
|
|
|
loading(false)
|
|
|
let emojiSet = resp.data
|
|
|
detailHandler(emojiSet)
|
|
|
}).catch(error => {
|
|
|
loading(false)
|
|
|
$ui.alert("Emoji配置获取失败")
|
|
|
})
|
|
|
} else {
|
|
|
detailHandler(null)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function write2file(key, value) {
|
|
|
let content = JSON.parse($file.read(FILE).string)
|
|
|
content[key] = value
|
|
|
$file.write({
|
|
|
data: $data({ "string": JSON.stringify(content) }),
|
|
|
path: FILE
|
|
|
})
|
|
|
}
|
|
|
|
|
|
function renderAdvanceUI() {
|
|
|
let previewData = JSON.parse($file.read(FILE).string)
|
|
|
let placeHolders = Object.keys(previewData.workspace.customProxyGroup)
|
|
|
let inputViewData = []
|
|
|
for (let idx in settingKeys) {
|
|
|
let content = previewData[settingKeys[idx]]
|
|
|
let view = {
|
|
|
type: "text",
|
|
|
props: {
|
|
|
text: content,
|
|
|
bgcolor: $color("#f0f5f5"),
|
|
|
font: $font(14)
|
|
|
},
|
|
|
events: {
|
|
|
didChange: sender => {
|
|
|
let content = sender.text
|
|
|
if (sender.text == '') {
|
|
|
content = $file.read('defaultConf/' + settingKeys[idx]).string
|
|
|
sender.text = content
|
|
|
}
|
|
|
write2file(settingKeys[idx], content)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
let phWidth = placeHolders.reduce((pre, cur) => (cur.length * 10 + pre), 0) + (placeHolders.length + 1) * 5
|
|
|
if (idx == 2) {
|
|
|
view.props.accessoryView = {
|
|
|
type: "view",
|
|
|
props: {
|
|
|
height: 44,
|
|
|
bgcolor: $color("#ced4d4")
|
|
|
},
|
|
|
views: [{
|
|
|
type: "label",
|
|
|
props: {
|
|
|
text: "完成",
|
|
|
align: $align.center
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.right.equalTo(view.super)
|
|
|
make.top.equalTo(view.super)
|
|
|
make.height.equalTo(view.super)
|
|
|
make.width.equalTo(60)
|
|
|
},
|
|
|
events: {
|
|
|
tapped: sender => {
|
|
|
$("inputViews").views[2].blur()
|
|
|
}
|
|
|
}
|
|
|
}, {
|
|
|
type: "scroll",
|
|
|
props: {
|
|
|
alwaysBounceHorizontal: true,
|
|
|
alwaysBounceVertical: false,
|
|
|
showsHorizontalIndicator: false,
|
|
|
contentSize: $size(phWidth, 34)
|
|
|
},
|
|
|
views: placeHolders.map((i, idx) => {
|
|
|
return {
|
|
|
type: 'label',
|
|
|
props: {
|
|
|
text: i,
|
|
|
lines: 1,
|
|
|
font: $font("bold", 15),
|
|
|
align: $align.center,
|
|
|
bgcolor: $color("#fff"),
|
|
|
radius: 5,
|
|
|
textColor: $color('#000')
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.size.equalTo($size(i.length * 10, 34))
|
|
|
make.centerY.equalTo(view.super)
|
|
|
// make.top.equalTo(view.suepr).offset(5)
|
|
|
if (idx > 0) {
|
|
|
make.left.equalTo(view.prev.right).offset(5)
|
|
|
} else {
|
|
|
make.left.equalTo(5)
|
|
|
}
|
|
|
},
|
|
|
events: {
|
|
|
tapped: sender => {
|
|
|
let inputView = $("inputViews").views[2]
|
|
|
inputView.runtimeValue().$insertText(sender.text)
|
|
|
// let sr = inputView.selectedRange
|
|
|
// let old = inputView.text.split('')
|
|
|
// old.splice(sr.location, sr.length, sender.text)
|
|
|
// inputView.text = old.join('')
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}),
|
|
|
layout: (make, view) => {
|
|
|
make.left.equalTo(view.super)
|
|
|
make.top.equalTo(view.super)
|
|
|
make.height.equalTo(view.super).offset(0)
|
|
|
make.width.equalTo(view.super).offset(-60)
|
|
|
}
|
|
|
}]
|
|
|
}
|
|
|
}
|
|
|
inputViewData.push(view)
|
|
|
}
|
|
|
let genControlBnts = function (idx) {
|
|
|
let titleTexts = ['小组件流量', '常规', '代理分组', '代理规则', '本地DNS映射', 'URL重定向', 'SSID', 'Header修改', '主机名', '配置根证书']
|
|
|
const sbgc = colorUtil.getColor("advanceGridOnBg")
|
|
|
const stc = colorUtil.getColor("advanceGridOnFg")
|
|
|
const dbgc = colorUtil.getColor("advanceGridOffBg")
|
|
|
const dtc = colorUtil.getColor("advanceGridOffFg")
|
|
|
return titleTexts.map((item, i) => {
|
|
|
return {
|
|
|
title: { text: item, bgcolor: i === idx ? sbgc : dbgc, radius: 5, color: i == idx ? stc : dtc }
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
$ui.push({
|
|
|
type: "scroll",
|
|
|
props: {
|
|
|
title: "进阶设置",
|
|
|
navBarHidden: true,
|
|
|
statusBarHidden: colorUtil.getColor("statusBar", true) === 'clear' ? true : false,
|
|
|
statusBarStyle: colorUtil.getColor("statusBar", true) === '#ffffff' ? 1 : 0,
|
|
|
},
|
|
|
views: [{
|
|
|
type: "view",
|
|
|
props: {
|
|
|
id: "navBar",
|
|
|
bgcolor: colorUtil.getColor("navBar")
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.height.equalTo(navBarHeight + statusBarHeight)
|
|
|
make.width.equalTo(view.super)
|
|
|
},
|
|
|
views: [{
|
|
|
type: "label",
|
|
|
props: {
|
|
|
text: "进阶设置",
|
|
|
textColor: colorUtil.getColor("navTitleText"),
|
|
|
font: $font("bold", 25)
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.height.equalTo(navBarHeight)
|
|
|
make.top.equalTo(statusBarHeight)
|
|
|
make.left.equalTo(15)
|
|
|
}
|
|
|
}, {
|
|
|
type: "image",
|
|
|
props: {
|
|
|
icon: $icon("225", colorUtil.getColor("navIconRight"), $size(25, 25)),
|
|
|
bgcolor: $color("clear")
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.right.equalTo(view.super).offset(-15)
|
|
|
make.height.width.equalTo(25)
|
|
|
make.bottom.equalTo(view.super).offset(-10)
|
|
|
},
|
|
|
events: {
|
|
|
tapped: sender => {
|
|
|
$ui.pop()
|
|
|
}
|
|
|
}
|
|
|
}]
|
|
|
}, {
|
|
|
type: "gallery",
|
|
|
props: {
|
|
|
id: "inputViews",
|
|
|
items: inputViewData,
|
|
|
interval: 0,
|
|
|
smoothRadius: 10,
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.height.equalTo(screenHeight - 325 - statusBarHeight - navBarHeight)
|
|
|
make.width.equalTo(view.super).offset(-20)
|
|
|
make.centerX.equalTo(view.super)
|
|
|
make.top.equalTo(navBarHeight + statusBarHeight + 10)
|
|
|
},
|
|
|
events: {
|
|
|
changed: sender => {
|
|
|
let idx = sender.page
|
|
|
$("settingsControl").data = genControlBnts(idx)
|
|
|
}
|
|
|
}
|
|
|
}, {
|
|
|
type: "matrix",
|
|
|
props: {
|
|
|
columns: 2,
|
|
|
id: "settingsControl",
|
|
|
itemHeight: 40,
|
|
|
bgcolor: $color("#ffffff"),
|
|
|
spacing: 3,
|
|
|
data: genControlBnts(0),
|
|
|
template: [{
|
|
|
type: "label",
|
|
|
props: {
|
|
|
id: "title",
|
|
|
align: $align.center,
|
|
|
font: $font("bold", 14)
|
|
|
},
|
|
|
layout: $layout.fill
|
|
|
}]
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.height.equalTo(220)
|
|
|
make.centerX.equalTo(view.super)
|
|
|
make.width.equalTo(view.super).offset(-13)
|
|
|
make.top.equalTo(view.prev.bottom).offset(5)
|
|
|
},
|
|
|
events: {
|
|
|
didSelect: (sender, indexPath, data) => {
|
|
|
let idx = indexPath.row
|
|
|
$("inputViews").page = idx
|
|
|
}
|
|
|
}
|
|
|
}, {
|
|
|
type: "label",
|
|
|
props: {
|
|
|
text: "上述设置点击完成生效,清空保存一次恢复默认",
|
|
|
font: $font(12),
|
|
|
textColor: $color("#595959"),
|
|
|
align: $align.center
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.top.equalTo(view.prev.bottom).offset(0)
|
|
|
make.width.equalTo(view.super)
|
|
|
make.height.equalTo(30)
|
|
|
make.centerX.equalTo(view.super)
|
|
|
}
|
|
|
}, {
|
|
|
type: "button",
|
|
|
props: {
|
|
|
title: '还原全部进阶设置',
|
|
|
bgcolor: $color("#ff6840")
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.width.equalTo(view.super).offset(-40)
|
|
|
make.centerX.equalTo(view.super)
|
|
|
make.top.equalTo(view.prev.bottom).offset(10)
|
|
|
make.height.equalTo(40)
|
|
|
},
|
|
|
events: {
|
|
|
tapped: sender => {
|
|
|
$ui.alert({
|
|
|
title: "提示",
|
|
|
message: "是否还原配置,还原后无法恢复",
|
|
|
actions: [{
|
|
|
title: 'Cancel',
|
|
|
handler: () => { }
|
|
|
}, {
|
|
|
title: 'OK',
|
|
|
handler: () => {
|
|
|
let previewData = JSON.parse($file.read(FILE).string)
|
|
|
for (let idx in settingKeys) {
|
|
|
let defaultValue = $file.read(`defaultConf/${settingKeys[idx]}`).string
|
|
|
previewData[settingKeys[idx]] = defaultValue
|
|
|
}
|
|
|
$file.write({
|
|
|
data: $data({ "string": JSON.stringify(previewData) }),
|
|
|
path: FILE
|
|
|
})
|
|
|
$ui.pop()
|
|
|
}
|
|
|
}]
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
}]
|
|
|
})
|
|
|
}
|
|
|
|
|
|
function renderAboutUI() {
|
|
|
let previewMD = function (title, filePath) {
|
|
|
$ui.push({
|
|
|
props: {
|
|
|
title: title
|
|
|
},
|
|
|
views: [{
|
|
|
type: "markdown",
|
|
|
props: {
|
|
|
id: "",
|
|
|
content: $file.read(filePath).string
|
|
|
},
|
|
|
layout: $layout.fill
|
|
|
}]
|
|
|
})
|
|
|
}
|
|
|
|
|
|
$ui.push({
|
|
|
props: {
|
|
|
title: "关于",
|
|
|
id: "aboutMainView",
|
|
|
navBarHidden: true,
|
|
|
statusBarHidden: colorUtil.getColor("statusBar", true) === 'clear' ? true : false,
|
|
|
statusBarStyle: colorUtil.getColor("statusBar", true) === '#ffffff' ? 1 : 0,
|
|
|
},
|
|
|
views: [{
|
|
|
type: "view",
|
|
|
props: {
|
|
|
id: "navBar",
|
|
|
bgcolor: colorUtil.getColor("navBar")
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.height.equalTo(navBarHeight + statusBarHeight)
|
|
|
make.width.equalTo(view.super)
|
|
|
},
|
|
|
views: [{
|
|
|
type: "label",
|
|
|
props: {
|
|
|
text: "脚本相关",
|
|
|
textColor: colorUtil.getColor("navTitleText"),
|
|
|
font: $font("bold", 25)
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.height.equalTo(navBarHeight)
|
|
|
make.top.equalTo(statusBarHeight)
|
|
|
make.left.equalTo(15)
|
|
|
}
|
|
|
}, {
|
|
|
type: "image",
|
|
|
props: {
|
|
|
icon: $icon("225", colorUtil.getColor("navIconRight"), $size(25, 25)),
|
|
|
bgcolor: $color("clear")
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.right.equalTo(view.super).offset(-15)
|
|
|
make.height.width.equalTo(25)
|
|
|
make.bottom.equalTo(view.super).offset(-10)
|
|
|
},
|
|
|
events: {
|
|
|
tapped: sender => {
|
|
|
$ui.pop()
|
|
|
}
|
|
|
}
|
|
|
}]
|
|
|
}, {
|
|
|
type: "scroll",
|
|
|
props: {
|
|
|
id: "mainAboutView",
|
|
|
contentSize: $size(0, 1000)
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.top.equalTo(navBarHeight + statusBarHeight);
|
|
|
make.width.equalTo(view.super)
|
|
|
make.height.equalTo(view.super).offset(navBarHeight + statusBarHeight)
|
|
|
},
|
|
|
views: [{
|
|
|
type: "label",
|
|
|
props: {
|
|
|
text: "文档说明",
|
|
|
font: $font(13),
|
|
|
textColor: $color("#505050")
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.top.equalTo(view.super).offset(10)
|
|
|
make.height.equalTo(30)
|
|
|
make.left.equalTo(15)
|
|
|
}
|
|
|
}, {
|
|
|
type: "list",
|
|
|
props: {
|
|
|
data: ["🗂 脚本简介", "🛠 使用手册", "📃 更新日志", "🖥 论坛导航"],
|
|
|
scrollEnabled: false
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.width.equalTo(view.super)
|
|
|
make.top.equalTo(view.prev.bottom).offset(0)
|
|
|
make.height.equalTo(180)
|
|
|
},
|
|
|
events: {
|
|
|
didSelect: (sender, indexPath, data) => {
|
|
|
if (indexPath.row === 0) {
|
|
|
previewMD(data, 'docs.md')
|
|
|
} else if (indexPath.row === 1) {
|
|
|
$safari.open({
|
|
|
url: "https://github.com/Fndroid/jsbox_script/wiki/Rules-lhie1"
|
|
|
})
|
|
|
} else if (indexPath.row === 2) {
|
|
|
previewMD(data, 'updateLog.md')
|
|
|
} else {
|
|
|
$safari.open({
|
|
|
url: "https://jsboxbbs.com/d/290-lhie1"
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}, {
|
|
|
type: "label",
|
|
|
props: {
|
|
|
text: "外部拓展",
|
|
|
font: $font(13),
|
|
|
textColor: $color("#505050")
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.top.equalTo(view.prev.bottom).offset(20)
|
|
|
make.height.equalTo(30)
|
|
|
make.left.equalTo(15)
|
|
|
}
|
|
|
}, {
|
|
|
type: "list",
|
|
|
props: {
|
|
|
data: ["🤖️ Rules-lhie1托管", "🎩 Quantumult去FA更新"],
|
|
|
scrollEnabled: false
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.width.equalTo(view.super)
|
|
|
make.top.equalTo(view.prev.bottom).offset(0)
|
|
|
make.height.equalTo(90)
|
|
|
},
|
|
|
events: {
|
|
|
didSelect: (sender, indexPath, data) => {
|
|
|
if (indexPath.row === 0) {
|
|
|
$app.openURL("https://t.me/rules_lhie1_bot")
|
|
|
} else {
|
|
|
$safari.open({
|
|
|
url: "https://jsboxbbs.com/d/474-quantumult-filter-action",
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}, {
|
|
|
type: "label",
|
|
|
props: {
|
|
|
text: "致谢捐献",
|
|
|
font: $font(13),
|
|
|
textColor: $color("#505050")
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.top.equalTo(view.prev.bottom).offset(20)
|
|
|
make.height.equalTo(30)
|
|
|
make.left.equalTo(15)
|
|
|
}
|
|
|
}, {
|
|
|
type: "list",
|
|
|
props: {
|
|
|
data: ["🙏 捐献打赏名单", "👍 赏杯咖啡支持作者"],
|
|
|
scrollEnabled: false
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.width.equalTo(view.super)
|
|
|
make.top.equalTo(view.prev.bottom).offset(0)
|
|
|
make.height.equalTo(90)
|
|
|
},
|
|
|
events: {
|
|
|
didSelect: (sender, indexPath, data) => {
|
|
|
if (indexPath.row === 0) {
|
|
|
$http.get('https://raw.githubusercontent.com/Fndroid/sponsor_list/master/sponsors.md').then(res => {
|
|
|
let success = $file.write({
|
|
|
data: $data({ string: res.data }),
|
|
|
path: 'donate.md'
|
|
|
})
|
|
|
success && previewMD(data, 'donate.md')
|
|
|
})
|
|
|
} else if (indexPath.row === 1) {
|
|
|
$ui.alert({
|
|
|
title: '感谢支持',
|
|
|
message: '作者投入大量时间和精力对脚本进行开发和完善,你愿意给他赏杯咖啡支持一下吗?',
|
|
|
actions: [{
|
|
|
title: "支付宝",
|
|
|
handler: () => {
|
|
|
$app.openURL($qrcode.decode($file.read("assets/thankyou2.jpg").image))
|
|
|
}
|
|
|
}, {
|
|
|
title: "微信",
|
|
|
handler: () => {
|
|
|
$quicklook.open({
|
|
|
image: $file.read("assets/thankyou.jpg").image
|
|
|
})
|
|
|
}
|
|
|
}, {
|
|
|
title: "返回"
|
|
|
}]
|
|
|
})
|
|
|
} else {
|
|
|
// $clipboard.text = 'GxsAtS84U7'
|
|
|
// $app.openURL("alipay://")
|
|
|
$app.openURL("https://qr.alipay.com/c1x047207ryk0wiaj6m6ye3")
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}, {
|
|
|
type: "label",
|
|
|
props: {
|
|
|
text: "反馈联系",
|
|
|
font: $font(13),
|
|
|
textColor: $color("#505050")
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.top.equalTo(view.prev.bottom).offset(20)
|
|
|
make.height.equalTo(30)
|
|
|
make.left.equalTo(15)
|
|
|
}
|
|
|
}, {
|
|
|
type: "list",
|
|
|
props: {
|
|
|
data: ["📠 Telegram", "💡 GitHub", "📅 Channel"],
|
|
|
scrollEnabled: false
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.width.equalTo(view.super)
|
|
|
make.top.equalTo(view.prev.bottom).offset(0)
|
|
|
make.height.equalTo(140)
|
|
|
},
|
|
|
events: {
|
|
|
didSelect: (sender, indexPath, data) => {
|
|
|
if (indexPath.row === 0) {
|
|
|
$safari.open({
|
|
|
url: "https://t.me/Rules_lhie1",
|
|
|
})
|
|
|
} else if (indexPath.row === 1) {
|
|
|
$safari.open({
|
|
|
url: "https://github.com/Fndroid/jsbox_script/tree/master/Rules-lhie1/README.md",
|
|
|
})
|
|
|
} else {
|
|
|
$safari.open({
|
|
|
url: "https://t.me/Fndroids",
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}, {
|
|
|
type: "label",
|
|
|
props: {
|
|
|
text: "版本号:" + updateUtil.getCurVersion(),
|
|
|
font: $font(13),
|
|
|
textColor: $color("#505050")
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.top.equalTo(view.prev.bottom).offset(20)
|
|
|
make.height.equalTo(30)
|
|
|
make.centerX.equalTo(view.super)
|
|
|
}
|
|
|
}]
|
|
|
}]
|
|
|
})
|
|
|
}
|
|
|
|
|
|
function deleteServerGroup() {
|
|
|
let serverData = $("serverEditor").data
|
|
|
let sections = serverData.map(i => {
|
|
|
if (i.title === '') {
|
|
|
return '无分组节点'
|
|
|
}
|
|
|
return i.title
|
|
|
})
|
|
|
$ui.menu({
|
|
|
items: sections.concat(['全部删除', '关键字删除']),
|
|
|
handler: function (title, idx) {
|
|
|
if (title === '全部删除') {
|
|
|
$("serverEditor").data = []
|
|
|
} else if (title === '关键字删除') {
|
|
|
$input.text({
|
|
|
type: $kbType.default,
|
|
|
placeholder: "关键字,空格隔开",
|
|
|
text: $("serverControl").info.deleteKeywords || '',
|
|
|
handler: function (text) {
|
|
|
let keywords = text.split(/\s+/g).filter(i => i !== '')
|
|
|
let editorData = $("serverEditor").data
|
|
|
editorData.map(section => {
|
|
|
section.rows = section.rows.filter(item => keywords.every(k => !(new RegExp(k, 'g')).test(item.proxyName.text)))
|
|
|
return section
|
|
|
})
|
|
|
$("serverEditor").data = editorData
|
|
|
let controlInfo = $("serverControl").info
|
|
|
controlInfo.deleteKeywords = text
|
|
|
$("serverControl").info = controlInfo
|
|
|
saveWorkspace()
|
|
|
}
|
|
|
})
|
|
|
} else {
|
|
|
serverData.splice(idx, 1)
|
|
|
$("serverEditor").data = serverData
|
|
|
}
|
|
|
saveWorkspace()
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
|
|
|
function reverseServerGroup() {
|
|
|
let serverData = $("serverEditor").data
|
|
|
let sections = serverData.map(i => i.title)
|
|
|
if (sections.length === 1) {
|
|
|
serverData[0].rows.reverse()
|
|
|
$("serverEditor").data = serverData
|
|
|
saveWorkspace()
|
|
|
return
|
|
|
}
|
|
|
$ui.menu({
|
|
|
items: sections.concat(['组别倒序']),
|
|
|
handler: function (title, idx) {
|
|
|
if (idx === sections.length) {
|
|
|
$("serverEditor").data = serverData.reverse()
|
|
|
} else {
|
|
|
serverData[idx].rows.reverse()
|
|
|
$("serverEditor").data = serverData
|
|
|
}
|
|
|
saveWorkspace()
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
|
|
|
let filePartReg = function (name) {
|
|
|
let reg = `\\[${name}\\]([\\S\\s]*?)(?:\\[General\\]|\\[Replica\\]|\\[Proxy\\]|\\[Proxy Group\\]|\\[Rule\\]|\\[Host\\]|\\[URL Rewrite\\]|\\[Header Rewrite\\]|\\[SSID Setting\\]|\\[MITM\\]|\\[URL-REJECTION\\]|\\[HOST\\]|\\[POLICY\\]|\\[REWRITE\\]|\\[Script\\]|$)`
|
|
|
return new RegExp(reg)
|
|
|
}
|
|
|
|
|
|
function setUpWorkspace() {
|
|
|
$app.listen({
|
|
|
ready: function () {
|
|
|
$("navLoadingIcon").play({
|
|
|
fromProgress: 0,
|
|
|
toProgress: 0.6,
|
|
|
})
|
|
|
$app.notify({
|
|
|
name: 'loadData'
|
|
|
})
|
|
|
},
|
|
|
resume: () => {
|
|
|
|
|
|
},
|
|
|
loadData: () => {
|
|
|
let file = JSON.parse($file.read(FILE).string)
|
|
|
if (file && file.workspace) {
|
|
|
let workspace = file.workspace
|
|
|
$("fileName").text = workspace.fileName || ''
|
|
|
$("serverSuffixEditor").text = workspace.serverSuffix || ''
|
|
|
$("serverURL").info = workspace.withEmoji || false
|
|
|
let customProxyGroup = workspace.customProxyGroup || {}
|
|
|
let defaultGroupName = PROXY_HEADER
|
|
|
if (!(defaultGroupName in customProxyGroup)) {
|
|
|
customProxyGroup[defaultGroupName] = []
|
|
|
}
|
|
|
let defaultGroup = customProxyGroup[defaultGroupName]
|
|
|
$("serverEditor").data = workspace.serverData.map(section => {
|
|
|
section.rows.map(item => {
|
|
|
item.proxyName = {
|
|
|
text: item.proxyName.text
|
|
|
}
|
|
|
item.proxyAuto = {
|
|
|
hidden: !(defaultGroup.indexOf(item.proxyName.text) > -1)
|
|
|
}
|
|
|
return item
|
|
|
})
|
|
|
return section
|
|
|
})
|
|
|
let usualSettingsData = workspace.usualData
|
|
|
let nd = $("usualSettings").data.map(item => {
|
|
|
let sd = usualSettingsData.find(i => i.title.text === item.title.text)
|
|
|
if (sd) {
|
|
|
item.title.bgcolor = sd.title.bgcolor ? btnOnBg : btnOffBg
|
|
|
item.title.textColor = sd.title.textColor ? btnOnFg : btnOffFg
|
|
|
} else {
|
|
|
item.title.bgcolor = btnOffFg
|
|
|
item.title.textColor = btnOffFg
|
|
|
}
|
|
|
return item
|
|
|
})
|
|
|
$("usualSettings").data = nd
|
|
|
$("serverControl").info = {
|
|
|
deleteKeywords: workspace.deleteKeywords || '',
|
|
|
customProxyGroup: customProxyGroup,
|
|
|
currentProxyGroup: defaultGroupName
|
|
|
}
|
|
|
let outputFormat = workspace.outputFormat || 'Surge3'
|
|
|
let type = 'surge'
|
|
|
if (outputFormat === 'Quantumult') {
|
|
|
type = 'quan'
|
|
|
} else if (outputFormat === 'Surge 2') {
|
|
|
type = 'surge2'
|
|
|
}
|
|
|
$("outputFormatType").text = outputFormat
|
|
|
$("outputFormatIcon").data = $file.read(`assets/today_${type}.png`)
|
|
|
} else if (file && !file.workspace) {
|
|
|
let customProxyGroup = {}
|
|
|
let defaultGroupName = PROXY_HEADER
|
|
|
customProxyGroup[defaultGroupName] = []
|
|
|
let defaultGroup = customProxyGroup[defaultGroupName]
|
|
|
$("serverControl").info = {
|
|
|
deleteKeywords: '',
|
|
|
customProxyGroup: customProxyGroup,
|
|
|
currentProxyGroup: defaultGroupName
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
|
|
|
function saveWorkspace() {
|
|
|
let workspace = {
|
|
|
fileName: $("fileName").text,
|
|
|
serverData: $("serverEditor").data,
|
|
|
withEmoji: $("serverURL").info || false,
|
|
|
usualData: $("usualSettings").data.map(i => {
|
|
|
i.title.bgcolor = cu.isEqual(btnOnBg, i.title.bgcolor)
|
|
|
i.title.textColor = cu.isEqual(btnOnFg, i.title.textColor)
|
|
|
return i
|
|
|
}),
|
|
|
outputFormat: $("outputFormatType").text,
|
|
|
serverSuffix: $("serverSuffixEditor").text,
|
|
|
deleteKeywords: $("serverControl").info.deleteKeywords || '',
|
|
|
customProxyGroup: $("serverControl").info.customProxyGroup || {}
|
|
|
}
|
|
|
let file = JSON.parse($file.read(FILE).string)
|
|
|
file.workspace = workspace
|
|
|
$file.write({
|
|
|
data: $data({ string: JSON.stringify(file) }),
|
|
|
path: FILE
|
|
|
})
|
|
|
}
|
|
|
|
|
|
|
|
|
function setDefaultSettings() {
|
|
|
let previewData = JSON.parse($file.read(FILE).string)
|
|
|
for (let idx in settingKeys) {
|
|
|
if (typeof previewData[settingKeys[idx]] === 'undefined' || previewData[settingKeys[idx]] == "") {
|
|
|
let defaultValue = ' '
|
|
|
if ($file.exists(`defaultConf/${settingKeys[idx]}`)) {
|
|
|
defaultValue = $file.read(`defaultConf/${settingKeys[idx]}`).string
|
|
|
}
|
|
|
previewData[settingKeys[idx]] = defaultValue
|
|
|
}
|
|
|
}
|
|
|
$file.write({
|
|
|
data: $data({ "string": JSON.stringify(previewData) }),
|
|
|
path: FILE
|
|
|
})
|
|
|
}
|
|
|
|
|
|
function autoGen() {
|
|
|
$ui.render({
|
|
|
props: {
|
|
|
title: ""
|
|
|
},
|
|
|
layout: $layout.fill,
|
|
|
views: [{
|
|
|
type: "blur",
|
|
|
props: {
|
|
|
id: "progressView",
|
|
|
style: 1
|
|
|
},
|
|
|
layout: $layout.fill,
|
|
|
views: [{
|
|
|
type: "label",
|
|
|
props: {
|
|
|
id: "loadingHintLabel",
|
|
|
text: "处理中,请稍后",
|
|
|
textColor: $color("black"),
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.centerX.equalTo(view.super)
|
|
|
make.centerY.equalTo(view.super).offset(-30)
|
|
|
}
|
|
|
}, {
|
|
|
type: "progress",
|
|
|
props: {
|
|
|
id: "progressBar",
|
|
|
value: 0
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.width.equalTo(screenWidth * 0.8)
|
|
|
make.center.equalTo(view.super)
|
|
|
make.height.equalTo(3)
|
|
|
}
|
|
|
}, {
|
|
|
type: "button",
|
|
|
props: {
|
|
|
title: "CLOSE"
|
|
|
},
|
|
|
layout: (make, view) => {
|
|
|
make.width.equalTo(80)
|
|
|
make.top.equalTo(view.prev.bottom).offset(20)
|
|
|
make.centerX.equalTo(view.super)
|
|
|
},
|
|
|
events: {
|
|
|
tapped: sender => {
|
|
|
$http.stopServer()
|
|
|
$app.close()
|
|
|
}
|
|
|
}
|
|
|
}]
|
|
|
}]
|
|
|
})
|
|
|
$app.listen({
|
|
|
ready: function () {
|
|
|
makeConf({
|
|
|
onProgress: (p, hint) => {
|
|
|
hint !== '' && ($("loadingHintLabel").text = hint)
|
|
|
$("progressBar").value = p
|
|
|
},
|
|
|
onDone: res => {
|
|
|
exportConf(res.fileName, res.fileData, res.target, res.actionSheet, true, () => {
|
|
|
$http.stopServer()
|
|
|
$app.close()
|
|
|
})
|
|
|
},
|
|
|
onError: res => {
|
|
|
$ui.alert("无法生成配置文件,可能是规则仓库发生变化或网络出现问题")
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
|
|
|
function makeConf(params) {
|
|
|
'onStart' in params && params.onStart()
|
|
|
try {
|
|
|
let pu = {
|
|
|
prototype: "https://raw.githubusercontent.com/lhie1/Rules/master/Surge/Prototype.conf",
|
|
|
apple: 'https://raw.githubusercontent.com/lhie1/Rules/master/Auto/Apple.conf',
|
|
|
direct: 'https://raw.githubusercontent.com/lhie1/Rules/master/Auto/DIRECT.conf',
|
|
|
proxy: 'https://raw.githubusercontent.com/lhie1/Rules/master/Auto/PROXY.conf',
|
|
|
reject: 'https://raw.githubusercontent.com/lhie1/Rules/master/Auto/REJECT.conf',
|
|
|
testflight: 'https://raw.githubusercontent.com/lhie1/Rules/master/Surge/TestFlight.conf',
|
|
|
host: 'https://raw.githubusercontent.com/lhie1/Rules/master/Auto/HOST.conf',
|
|
|
urlrewrite: 'https://raw.githubusercontent.com/lhie1/Rules/master/Auto/URL%20Rewrite.conf',
|
|
|
urlreject: 'https://raw.githubusercontent.com/lhie1/Rules/master/Auto/URL%20REJECT.conf',
|
|
|
headerrewrite: 'https://raw.githubusercontent.com/lhie1/Rules/master/Auto/Header%20Rewrite.conf',
|
|
|
hostname: 'https://raw.githubusercontent.com/lhie1/Rules/master/Auto/Hostname.conf',
|
|
|
mitm: 'https://raw.githubusercontent.com/lhie1/Rules/master/Surge/MITM.conf',
|
|
|
quanretcp: 'https://raw.githubusercontent.com/lhie1/Rules/master/Quantumult/Quantumult.conf',
|
|
|
quanextra: 'https://raw.githubusercontent.com/lhie1/Rules/master/Quantumult/Quantumult_Extra_JS.conf',
|
|
|
quanrejection: 'https://raw.githubusercontent.com/lhie1/Rules/master/Quantumult/Quantumult_URL.conf',
|
|
|
localhost: 'http://127.0.0.1/fndroid'
|
|
|
}
|
|
|
let advanceSettings = JSON.parse($file.read(FILE).string)
|
|
|
let workspace = advanceSettings.workspace
|
|
|
let usualData = workspace.usualData
|
|
|
let customProxyGroup = workspace.customProxyGroup
|
|
|
|
|
|
let usualValue = function (key) {
|
|
|
return usualData.find(i => i.title.text == key) ? usualData.find(i => i.title.text == key).title.bgcolor : false
|
|
|
}
|
|
|
|
|
|
let ads = usualValue('ADS')
|
|
|
let isMitm = usualValue('MITM')
|
|
|
let isActionSheet = usualValue('导出')
|
|
|
|
|
|
let outputFormat = workspace.outputFormat
|
|
|
let surge2 = outputFormat === 'Surge 2'
|
|
|
let isQuan = outputFormat === 'Quantumult'
|
|
|
let testflight = outputFormat === 'Surge 3 TF'
|
|
|
|
|
|
let serverEditorData = workspace.serverData
|
|
|
if (isQuan) {
|
|
|
serverEditorData = serverEditorData.map(i => {
|
|
|
let rows = i.rows.map(s => {
|
|
|
let containsOP = /obfs_param/.test(s.proxyLink)
|
|
|
s.proxyLink = s.proxyLink.replace(/,\s*group\s*=[^,]*/, '')
|
|
|
if (containsOP) {
|
|
|
s.proxyLink = s.proxyLink.replace(/obfs_param/, `group=${i.title}, obfs_param`)
|
|
|
} else {
|
|
|
s.proxyLink += `, group=${i.title}`
|
|
|
}
|
|
|
return s
|
|
|
})
|
|
|
i.rows = rows
|
|
|
return i
|
|
|
})
|
|
|
}
|
|
|
let flatServerData = serverEditorData.reduce((all, cur) => {
|
|
|
return {
|
|
|
rows: all.rows.concat(cur.rows)
|
|
|
}
|
|
|
}, { rows: [] }).rows
|
|
|
|
|
|
let proxyNameLegal = function (name) {
|
|
|
return flatServerData.map(i => i.proxyName.text).concat(getProxyGroups()).concat(['🚀 Direct']).find(i => i === name) !== undefined
|
|
|
}
|
|
|
|
|
|
let proxySuffix = workspace.serverSuffix.split(/\s*,\s*/g).map(i => i.replace(/\s/g, '')).filter(i => i !== '')
|
|
|
let proxies = flatServerData.map(i => {
|
|
|
let notExistSuffix = proxySuffix.filter((ps, idx) => {
|
|
|
if (idx === 0 && ps === '') return true
|
|
|
return i.proxyLink.indexOf(ps) < 0
|
|
|
})
|
|
|
let containsOP = /obfs_param/.test(i.proxyLink)
|
|
|
if (containsOP) {
|
|
|
i.proxyLink = i.proxyLink.replace(/obfs_param/, `${notExistSuffix.join(',')},obfs_param`)
|
|
|
} else if (notExistSuffix.length > 0) {
|
|
|
i.proxyLink += `,${notExistSuffix.join(',')}`
|
|
|
}
|
|
|
return i.proxyLink.replace('http://omgib13x8.bkt.clouddn.com/SSEncrypt.module', 'https://github.com/lhie1/Rules/blob/master/SSEncrypt.module?raw=true')
|
|
|
}).filter((item, idx, self) => {
|
|
|
let proxyName = item.split(/=/)[0].trim()
|
|
|
return self.findIndex(i => {
|
|
|
let pn = i.split('=')[0].trim()
|
|
|
return proxyName === pn
|
|
|
}) === idx
|
|
|
})
|
|
|
proxies = proxies.join('\n')
|
|
|
let proxyHeaders = flatServerData.map(i => i.proxyName.text).join(', ')
|
|
|
let rules = ''
|
|
|
let prototype = ''
|
|
|
let host = ''
|
|
|
let urlRewrite = ''
|
|
|
let urlReject = ''
|
|
|
let headerRewrite = ''
|
|
|
let hostName = ''
|
|
|
let rename = null
|
|
|
let rulesReplacement = getRulesReplacement()
|
|
|
|
|
|
let pgs = 0
|
|
|
|
|
|
let onPgs = function (hint) {
|
|
|
pgs += 0.1
|
|
|
'onProgress' in params && params.onProgress(pgs, hint)
|
|
|
}
|
|
|
|
|
|
let emptyPromise = function (done, hint = '') {
|
|
|
if (done) done(hint)
|
|
|
return Promise.resolve('')
|
|
|
}
|
|
|
|
|
|
let promiseArray = [
|
|
|
getAutoRules(pu.prototype, onPgs, '成功取回配置模板'), // 0
|
|
|
rulesReplacement ? getAutoRules(rulesReplacement, onPgs, '成功取回替换配置') : getAutoRules(isQuan || testflight ? pu.direct : pu.apple, onPgs, '成功取回APPLE规则'), // 1
|
|
|
!ads || rulesReplacement ? emptyPromise(onPgs) : getAutoRules(isQuan || testflight ? pu.localhost : pu.reject, onPgs, '成功取回Reject规则'), // 2
|
|
|
rulesReplacement ? emptyPromise(onPgs) : getAutoRules(isQuan || testflight ? pu.quanretcp : pu.proxy, onPgs, '成功取回Proxy规则'), // 3
|
|
|
rulesReplacement ? emptyPromise(onPgs) : getAutoRules(isQuan || testflight ? pu.quanextra : pu.direct, onPgs, '成功取回Direct规则'), // 4
|
|
|
rulesReplacement ? emptyPromise(onPgs) : getAutoRules(pu.host, onPgs, '成功取回Host'), // 5
|
|
|
rulesReplacement ? emptyPromise(onPgs) : getAutoRules(pu.urlrewrite, onPgs, '成功取回URL Rewrite'), // 6
|
|
|
!ads || rulesReplacement ? emptyPromise(onPgs) : getAutoRules(isQuan ? pu.quanrejection : pu.urlreject, onPgs, '成功取回URL Reject'), // 7
|
|
|
rulesReplacement ? emptyPromise(onPgs) : getAutoRules(pu.headerrewrite, onPgs, '成功取回Header Rewrite'), // 8
|
|
|
!ads || rulesReplacement ? emptyPromise(onPgs) : getAutoRules(pu.hostname, onPgs, '成功取回MITM Hostname'), // 9
|
|
|
]
|
|
|
|
|
|
// 获取RULE-SET
|
|
|
let ruleSets = []
|
|
|
if (!testflight) {
|
|
|
ruleSets = advanceSettings.customSettings.split(/[\r\n]/g).map(i => {
|
|
|
if (/^RULE-SET\s*,\s*(.*?)\s*,\s*(.*)/.test(i)) {
|
|
|
return {
|
|
|
url: RegExp.$1,
|
|
|
policy: RegExp.$2
|
|
|
}
|
|
|
}
|
|
|
return null
|
|
|
}).filter(i => i)
|
|
|
}
|
|
|
console.log('ruleSets', ruleSets);
|
|
|
|
|
|
promiseArray = promiseArray.concat(ruleSets.map(i => getAutoRules(i.url)))
|
|
|
|
|
|
Promise.all(promiseArray).then(v => {
|
|
|
prototype = v[0]
|
|
|
if (rulesReplacement) {
|
|
|
let repRules = v[1].match(filePartReg('Rule'))
|
|
|
let repHost = v[1].match(filePartReg('Host'))
|
|
|
let repUrlRewrite = v[1].match(filePartReg('URL Rewrite'))
|
|
|
let repHeaderRewrite = v[1].match(filePartReg('Header Rewrite'))
|
|
|
let repHostName = v[1].match(/hostname\s*=\s*(.*?)[\n\r]/)
|
|
|
repRules && repRules[1] && (v[1] = repRules[1])
|
|
|
repHost && repHost[1] && (v[5] = repHost[1])
|
|
|
repUrlRewrite && repUrlRewrite[1] && (v[6] = '[URL Rewrite]\n' + repUrlRewrite[1])
|
|
|
repHeaderRewrite && repHeaderRewrite[1] && (v[8] = '[Header Rewrite]\n' + repHeaderRewrite[1])
|
|
|
repHostName && repHostName[1] && (v[9] = repHostName[1])
|
|
|
}
|
|
|
|
|
|
if (isQuan && !rulesReplacement && /\[TCP\]([\s\S]*)\/\/ Detect local network/.test(v[3])) {
|
|
|
let tcpRules = `${v[4]}\n${RegExp.$1}`.split(/[\n\r]+/g)
|
|
|
if (!ads) {
|
|
|
tcpRules = tcpRules.filter(i => !/^.*?,\s*REJECT\s*$/.test(i))
|
|
|
}
|
|
|
let surgeLan = v[1].split(/[\r\n]/g).filter(i => /.*?,\s*DIRECT/.test(i))
|
|
|
tcpRules = tcpRules.map(r => {
|
|
|
if (surgeLan.indexOf(r) > -1) return r
|
|
|
r = r.replace(/(^.*?,.*?,\s*)选择YouTube Music的策略(.*$)/, '$1🍃 Proxy$2')
|
|
|
r = r.replace(/(^.*?,.*?,\s*)选择TVB\/Viu的策略(.*$)/, '$1🍃 Proxy$2')
|
|
|
r = r.replace(/(^.*?,.*?,\s*)选择Vidol的策略(.*$)/, '$1🍃 Proxy$2')
|
|
|
r = r.replace(/(^.*?,.*?,\s*)选择Hulu的策略(.*$)/, '$1🍃 Proxy$2')
|
|
|
r = r.replace(/(^.*?,.*?,\s*)选择Spotify的策略(.*$)/, '$1🍃 Proxy$2')
|
|
|
r = r.replace(/(^.*?,.*?,\s*)选择Google的策略,不懂就不选(.*$)/, '$1🍃 Proxy$2')
|
|
|
r = r.replace(/(^.*?,.*?,\s*)选择微软服务的策略,不懂就选择DIRECT(.*$)/, '$1🍂 Domestic$2')
|
|
|
r = r.replace(/(^.*?,.*?,\s*)选择PayPal的策略,不懂就选择DIRECT(.*$)/, '$1🍂 Domestic$2')
|
|
|
r = r.replace(/(^.*?,.*?,\s*)选择Apple的策略,不懂就选择DIRECT(.*$)/, '$1🍎 Only$2')
|
|
|
r = r.replace(/(^.*?,.*?,\s*)选择Netflix的策略,不懂就不选(.*$)/, '$1🍃 Proxy$2')
|
|
|
r = r.replace(/(^.*?,.*?,\s*)选择国外流媒体的策略(.*$)/, '$1🍃 Proxy$2')
|
|
|
r = r.replace(/(^.*?,.*?,\s*)DIRECT(.*$)/i, '$1🍂 Domestic$2')
|
|
|
r = r.replace(/(^.*?,.*?,\s*)PROXY(.*$)/i, '$1🍃 Proxy$2')
|
|
|
r = r.replace(/^DOMAIN(.*?)🍃 Proxy\s*$/, 'DOMAIN$1🍃 Proxy,force-remote-dns')
|
|
|
return r
|
|
|
})
|
|
|
v[1] = tcpRules.join('\n')
|
|
|
v[2] = ''
|
|
|
v[3] = ''
|
|
|
v[4] = ''
|
|
|
v[7] = v[7].replace(/hostname = /, '# hostname = ')
|
|
|
}
|
|
|
|
|
|
if (testflight && !rulesReplacement) {
|
|
|
let autoNewPrefix = 'https://raw.githubusercontent.com/lhie1/Rules/master/Surge/Surge%203/Provider'
|
|
|
v[1] = `RULE-SET,SYSTEM,DIRECT\nRULE-SET,${autoNewPrefix}/Apple.list,🍎 Only`
|
|
|
v[2] = ads ? `RULE-SET,${autoNewPrefix}/Reject.list,REJECT` : ''
|
|
|
v[3] = `RULE-SET,${autoNewPrefix}/AsianTV.list,🍂 Domestic\nRULE-SET,${autoNewPrefix}/GlobalTV.list,🍃 Proxy\nRULE-SET,${autoNewPrefix}/Proxy.list,🍃 Proxy`
|
|
|
v[4] = `RULE-SET,${autoNewPrefix}/Domestic.list,🍂 Domestic\nRULE-SET,LAN,DIRECT`
|
|
|
}
|
|
|
|
|
|
rules += `\n${v[1]}\n${v[2].replace(/REJECT/g, surge2 || isQuan ? "REJECT" : "REJECT-TINYGIF")}\n${v[3]}\n${v[4]}\n`
|
|
|
host = v[5]
|
|
|
urlRewrite += v[6]
|
|
|
urlReject += v[7]
|
|
|
headerRewrite = v[8]
|
|
|
hostName = v[9].split('\n')
|
|
|
|
|
|
let seperateLines = function (content, rules = false) {
|
|
|
let addRules = content.split('\n').filter(i => !/^-/.test(i)).map(i => i.trim())
|
|
|
if (rules && !testflight && promiseArray.length > 10) {
|
|
|
addRules = addRules.filter(i => !/^\s*RULE-SET/.test(i))
|
|
|
for (let i = 10; i < promiseArray.length; i++) {
|
|
|
let policy = ruleSets[i - 10].policy
|
|
|
addRules = addRules.concat(v[i].split(/[\r\n]/g).map(i => {
|
|
|
console.log('i', i);
|
|
|
if (/^(.+?),(.+?),(.+)$/.test(i)) {
|
|
|
return `${RegExp.$1 + ',' + RegExp.$2},${policy},${RegExp.$3}${!testflight ? ',force-remote-dns' : ''}`
|
|
|
}else if (/^(.+?),(.+?)(?=$|\/\/|\#)/.test(i)) {
|
|
|
return `${RegExp.$1},${RegExp.$2},${policy}${!testflight ? ',force-remote-dns' : ''}`
|
|
|
}
|
|
|
return i
|
|
|
}))
|
|
|
}
|
|
|
}
|
|
|
console.log('adru', addRules)
|
|
|
|
|
|
let res = {
|
|
|
add: addRules,
|
|
|
delete: content.split("\n").filter(i => /^-/.test(i)).map(i => i.replace(/^-/, '').trim())
|
|
|
}
|
|
|
return res
|
|
|
}
|
|
|
|
|
|
let prettyInsert = function (lines) {
|
|
|
return '\n\n' + lines.join('\n') + '\n\n'
|
|
|
}
|
|
|
|
|
|
// 配置代理分组
|
|
|
if (advanceSettings.proxyGroupSettings) {
|
|
|
let pgs = advanceSettings.proxyGroupSettings
|
|
|
rename = pgs.match(/\/\/\s*rename\s*:\s*(.*?)(?:\n|\r|$)/)
|
|
|
pgs = pgs.replace(/Proxy Header/g, proxyHeaders)
|
|
|
for (let name in customProxyGroup) {
|
|
|
let nameReg = new RegExp(`,\\s*${name}`, 'g')
|
|
|
let serverNames = customProxyGroup[name]
|
|
|
serverNames = serverNames.filter(i => proxyNameLegal(i))
|
|
|
pgs = pgs.replace(nameReg, ',' + (serverNames.join(',') || flatServerData.map(i => i.proxyName.text).join(',')))
|
|
|
}
|
|
|
prototype = prototype.replace(/\[Proxy Group\][\s\S]+\[Rule\]/, pgs + '\n\n[Rule]')
|
|
|
} else {
|
|
|
prototype = prototype.replace(/Proxy Header/g, proxyHeaders)
|
|
|
prototype = prototype.replace(/ProxyHeader/g, customProxyGroup[PROXY_HEADER].filter(i => proxyNameLegal(i)).join(',') || flatServerData.map(i => i.proxyName.text).join(','))
|
|
|
}
|
|
|
// 配置常规设置
|
|
|
if (advanceSettings.generalSettings) {
|
|
|
prototype = prototype.replace(/\[General\][\s\S]+\[Proxy\]/, advanceSettings.generalSettings + '\n\n[Proxy]')
|
|
|
}
|
|
|
// 配置自定义规则
|
|
|
let customRules = seperateLines(advanceSettings.customSettings, true)
|
|
|
let rulesList = rules.split(/[\r\n]/g)
|
|
|
let deleteList = customRules.add.map(i => {
|
|
|
if (/^(.*?),(.*?),/.test(i)) {
|
|
|
let type = RegExp.$1
|
|
|
let content = RegExp.$2
|
|
|
return `${type},${content}`
|
|
|
}
|
|
|
})
|
|
|
rules = rulesList.filter(i => deleteList.findIndex(d => i.startsWith(d)) === -1).join('\n')
|
|
|
customRules.delete.forEach(i => rules = rules.replace(i, ''))
|
|
|
// 配置本地DNS映射
|
|
|
let userHost = seperateLines(advanceSettings.hostSettings)
|
|
|
userHost.delete.forEach(i => host = host.replace(i, ''))
|
|
|
// 配置URL重定向
|
|
|
let userUrl = seperateLines(advanceSettings.urlrewriteSettings)
|
|
|
userUrl.delete.forEach(i => {
|
|
|
urlRewrite = urlRewrite.replace(i, '')
|
|
|
urlReject = urlReject.replace(i, '')
|
|
|
})
|
|
|
// 配置Header修改
|
|
|
let userHeader = seperateLines(advanceSettings.headerrewriteSettings)
|
|
|
userHeader.delete.forEach(i => headerRewrite = headerRewrite.replace(i, ''))
|
|
|
// 配置SSID
|
|
|
let userSSID = advanceSettings.ssidSettings
|
|
|
// 配置MITM的Hostname
|
|
|
let userHostname = seperateLines(advanceSettings.hostnameSettings)
|
|
|
userHostname.delete.forEach(i => {
|
|
|
if (hostName.indexOf(i) >= 0) {
|
|
|
hostName.splice(hostName.indexOf(i), 1)
|
|
|
}
|
|
|
})
|
|
|
|
|
|
function ssr2ss(proxies) {
|
|
|
let proxyList = proxies.split(/\n/);
|
|
|
let res = proxyList.map(proxy => {
|
|
|
if (/=\s*shadowsocksr/.test(proxy)) {
|
|
|
return proxy.replace(/=\s*shadowsocksr/g, '= custom').replace(/"/g, '').replace(/,\s*(protocol|protocol_param|obfs|obfs_param)[^,$]+/g, '') + ', https://github.com/lhie1/Rules/blob/master/SSEncrypt.module?raw=true'
|
|
|
} else {
|
|
|
return proxy
|
|
|
}
|
|
|
})
|
|
|
return res.join('\n')
|
|
|
}
|
|
|
|
|
|
// if (isQuan) {
|
|
|
// prototype = prototype.replace(/\/\/ Detect local network/, `${prettyInsert(customRules.add)}\n`)
|
|
|
// } else {
|
|
|
prototype = prototype.replace('# Custom', prettyInsert(customRules.add))
|
|
|
// }
|
|
|
prototype = prototype.replace('Proxys', isQuan ? proxies : ssr2ss(proxies))
|
|
|
if (rulesReplacement) {
|
|
|
prototype = prototype.replace(/\[Rule\][\s\S]*FINAL\s*,[^\r\n]+/, `[Rule]\n${prettyInsert(customRules.add)}\n${rules}\n`)
|
|
|
} else {
|
|
|
prototype = prototype.replace('# All Rules', rules)
|
|
|
}
|
|
|
prototype = prototype.replace('# Host', "[Host]\n" + host + prettyInsert(userHost.add))
|
|
|
prototype = prototype.replace('# URL Rewrite', urlRewrite.replace(/307/g, surge2 ? '302' : '307') + prettyInsert(userUrl.add))
|
|
|
prototype = prototype.replace('# URL REJECT', urlReject)
|
|
|
prototype = prototype.replace('# SSID', '[SSID Setting]\n' + userSSID)
|
|
|
prototype = prototype.replace('# Header Rewrite', headerRewrite + prettyInsert(userHeader.add))
|
|
|
//fix by shenqinci 2019年03月20日:当只有userHostName时,配置文件出现第一行为空的问题
|
|
|
let finalHostNames = hostName.concat(userHostname.add).filter(i => i).join(', ')
|
|
|
if (finalHostNames !== '') {
|
|
|
prototype = prototype.replace('// Hostname', 'hostname = ' + finalHostNames)
|
|
|
}
|
|
|
|
|
|
if (isMitm) {
|
|
|
prototype = prototype.replace('# MITM', advanceSettings.mitmSettings)
|
|
|
} else {
|
|
|
prototype = prototype.replace(/\[MITM\][\s\S]*$/, '')
|
|
|
}
|
|
|
|
|
|
function genQuanPolices(content) {
|
|
|
let items = content.split(/[\n\r]+/).filter(i => i !== '' && !/^\/\//.test(i)).map(sta => {
|
|
|
let matcher = sta.match(/^(.*?)=(.*?),(.*?)$/);
|
|
|
if (/^(.*?)=(.*?),(.*?)$/.test(sta)) {
|
|
|
let pName = RegExp.$1
|
|
|
let pType = RegExp.$2
|
|
|
let pNodes = RegExp.$3
|
|
|
let data = pNodes.split(/,/g)
|
|
|
if (/url-test/.test(pType) || /fallback/.test(pType)) {
|
|
|
let v = data.filter(i => !/(?:url|interval|tolerance|timeout)\s*=\s*/.test(i))
|
|
|
return {
|
|
|
name: pName,
|
|
|
sta: ' auto',
|
|
|
data: v
|
|
|
}
|
|
|
} else if (/select/.test(pType)) {
|
|
|
return {
|
|
|
name: pName,
|
|
|
sta: pType.replace(/select/, 'static'),
|
|
|
data: data
|
|
|
}
|
|
|
} else if (/round-robin/.test(pType)) {
|
|
|
return {
|
|
|
name: pName,
|
|
|
sta: 'balance, round-robin',
|
|
|
data: data
|
|
|
}
|
|
|
} else {
|
|
|
return {
|
|
|
name: pName,
|
|
|
sta: 'ssid',
|
|
|
data: data
|
|
|
}
|
|
|
}
|
|
|
} else {
|
|
|
return null
|
|
|
}
|
|
|
}).filter(i => i !== null)
|
|
|
items.push({
|
|
|
name: '🚀 Direct',
|
|
|
sta: 'static',
|
|
|
data: ["DIRECT"]
|
|
|
})
|
|
|
let policies = items.map(i => {
|
|
|
if (i.sta.contains('auto') || i.sta.contains('balance, round-robin')) {
|
|
|
return `${i.name} : ${i.sta}\n${i.data.join('\n')}`
|
|
|
} else if (i.sta.contains('static')) {
|
|
|
return `${i.name} : ${i.sta}, ${i.data[0]}\n${i.data.join('\n')}`
|
|
|
} else if (i.sta === 'ssid') {
|
|
|
let wifi = i.data.find(i => /default\s*=/.test(i))
|
|
|
let cellular = i.data.find(i => /cellular\s*=/.test(i)) || 'cellular = DIRECT'
|
|
|
let left = i.data.filter(i => i !== wifi && i !== cellular).map(i => {
|
|
|
let p = i.split('=')
|
|
|
return p[0].replace(/"/g, '') + '=' + p.slice(1).join('=')
|
|
|
})
|
|
|
return `${i.name} : ${wifi.replace(/default\s*=/, 'wifi =')}, ${cellular}\n${left.join('\n')}`
|
|
|
}
|
|
|
})
|
|
|
return policies.map(i => {
|
|
|
if (rename && rename[1]) {
|
|
|
i = globalRename(rename, i) // 圈特殊性
|
|
|
}
|
|
|
return $text.base64Encode(i)
|
|
|
})
|
|
|
}
|
|
|
|
|
|
function seperateRejection(reject) {
|
|
|
let lines = reject.split(/[\r\n]+/)
|
|
|
let res = { reject: [], rewrite: []}
|
|
|
lines.forEach(l => {
|
|
|
if (/(.*?\s+\-\s+reject)/.test(l)) {
|
|
|
res.reject.push(RegExp.$1)
|
|
|
} else if (/(.*?\s+url\s(302|307|modify|simple\-response)\s.*$)/.test(l)) {
|
|
|
res.rewrite.push(RegExp.$1)
|
|
|
}
|
|
|
})
|
|
|
return res
|
|
|
}
|
|
|
|
|
|
function genQuanRewrite(content) {
|
|
|
let items = content.split(/[\n\r]+/).filter(i => i !== '' && /^(?!\/\/|#)/.test(i)).map(i => {
|
|
|
if (/^(.*?)\s+(.*?)\s+(.*?)\s*$/.test(i)) {
|
|
|
let type = RegExp.$3
|
|
|
return `${RegExp.$1} url ${type === 'header' ? 'modify' : type} ${RegExp.$2}`
|
|
|
}
|
|
|
return ''
|
|
|
}).join('\n')
|
|
|
return items
|
|
|
}
|
|
|
|
|
|
function genQuanRewriteTinyPng(reject, rewrite) {
|
|
|
let rejects = reject.split(/[\n\r]/g).filter(i => /.*?\s*-\s*reject/.test(i)).map(i => i.replace(/(.*?)\s*-\s*reject\s*$/, '$1'))
|
|
|
let items = rejects.map(i => `${i} url simple-response SFRUUC8xLjEgMjAwIE9LDQpTZXJ2ZXI6IG5naW54DQpDb250ZW50LVR5cGU6IGltYWdlL3BuZw0KQ29udGVudC1MZW5ndGg6IDU2DQpDb25uZWN0aW9uOiBjbG9zZQ0KDQqJUE5HDQoaCgAAAA1JSERSAAAAAQAAAAEIBgAAAB8VxIkAAAALSURBVHicY2AAAgAABQABel6rPw==`)
|
|
|
items = items.concat(rewrite.split(/[\n\r]+/).filter(i => i !== '' && /^(?!\/\/|#)/.test(i)).map(i => {
|
|
|
if (/^(.*?)\s+(.*?)\s+(.*?)\s*$/.test(i)) {
|
|
|
let type = RegExp.$3
|
|
|
return `${RegExp.$1} url ${type === 'header' ? 'modify' : type} ${RegExp.$2}`
|
|
|
}
|
|
|
return ''
|
|
|
}))
|
|
|
return items.join('\n')
|
|
|
}
|
|
|
|
|
|
function genQuanPart(name, content) {
|
|
|
return `\n[${name}]\n${content}\n`
|
|
|
}
|
|
|
|
|
|
if (isQuan) {
|
|
|
prototype = prototype.replace(/☁️ Others, dns-failed/, '☁️ Others')
|
|
|
let proxyGroup = prototype.match(filePartReg('Proxy Group'))
|
|
|
if (proxyGroup && proxyGroup[1]) {
|
|
|
let policies = genQuanPolices(proxyGroup[1])
|
|
|
prototype += genQuanPart('POLICY', policies.join('\n'))
|
|
|
}
|
|
|
userUrl.add.forEach(i => {
|
|
|
// if (/reject\s*$/.test(i)) {
|
|
|
// urlReject += `\n${i}\n`
|
|
|
// } else {
|
|
|
// urlRewrite += `\n${i}\n`
|
|
|
// }
|
|
|
let rule = i
|
|
|
if (/(.*?)\s+(.*?)\s+(307|302|header|reject)\s*$/.test(i)) {
|
|
|
if (RegExp.$3 === 'reject') {
|
|
|
rule = i
|
|
|
} else if (RegExp.$3 === 'header') {
|
|
|
rule = `${RegExp.$1} url modify ${RegExp.$2}`
|
|
|
} else {
|
|
|
rule = `${RegExp.$1} url ${RegExp.$3} ${RegExp.$2}`
|
|
|
}
|
|
|
}
|
|
|
urlReject = `\n${rule}\n${urlReject}`
|
|
|
})
|
|
|
let quanRe = seperateRejection(urlReject)
|
|
|
prototype += genQuanPart('URL-REJECTION', quanRe.reject.join('\n'))
|
|
|
prototype += genQuanPart('REWRITE', quanRe.rewrite.join('\n'))
|
|
|
// prototype += genQuanPart('REWRITE', genQuanRewriteTinyPng(urlReject, urlRewrite))
|
|
|
prototype += genQuanPart('HOST', host + prettyInsert(userHost.add))
|
|
|
let sourceType = 'false, true, false';
|
|
|
let sourceTypeParam = proxySuffix.find(x => /\s*source-type\s*=\s*[0-7]\s*(?:,|$)/.test(x))
|
|
|
if (sourceTypeParam) {
|
|
|
let type = sourceTypeParam.match(/\s*source-type\s*=\s*([0-7])/)[1] * 1;
|
|
|
sourceType = `${type & 4 ? 'true' : 'false'}, ${type & 2 ? 'true' : 'false'}, ${type & 1 ? 'true' : 'false'}`
|
|
|
}
|
|
|
console.log(serverEditorData)
|
|
|
prototype += genQuanPart('SOURCE', serverEditorData.filter(i => {
|
|
|
// let isSSR = i.rows.find(l => /^.*?=\s*(?=shadowsocksr|vmess)/.test(l.proxyLink))
|
|
|
// return isSSR !== undefined
|
|
|
return i.rows.find(i => i.proxyType > 0)
|
|
|
}).map(i => {
|
|
|
return `${i.title}, server, ${i.url}, ${sourceType}, ${i.title}`
|
|
|
}).join('\n') + (rulesReplacement ? "" : "\nlhie1, filter, https://raw.githubusercontent.com/lhie1/Rules/master/Quantumult/Quantumult.conf, true\nlhie1_extra, filter, https://raw.githubusercontent.com/lhie1/Rules/master/Quantumult/Quantumult_Extra.conf, true\nlhie1, blacklist, https://raw.githubusercontent.com/lhie1/Rules/master/Quantumult/Quantumult_URL.conf, true\n"))
|
|
|
let customDNS = prototype.match(/dns-server\s*=\s*(.*?)(?:\n|\r|$)/)
|
|
|
if (customDNS && customDNS[1]) {
|
|
|
prototype += genQuanPart('DNS', customDNS[1])
|
|
|
}
|
|
|
let widgetProxies = customProxyGroup['WidgetHeader'] || null
|
|
|
if (widgetProxies) {
|
|
|
widgetProxies = widgetProxies.filter(i => proxyNameLegal(i))
|
|
|
prototype += genQuanPart('BACKUP-SERVER', widgetProxies.join('\n'))
|
|
|
}
|
|
|
prototype = prototype.replace(/\[SSID Setting\]/, "[SUSPEND-SSID]").replace(/\ssuspend=true/g, '')
|
|
|
}
|
|
|
|
|
|
if (rename && rename[1]) {
|
|
|
prototype = globalRename(rename, prototype);
|
|
|
}
|
|
|
|
|
|
let fn = (workspace.fileName || 'lhie1') + '.conf'
|
|
|
|
|
|
let exportTarget = 0
|
|
|
|
|
|
if (surge2) {
|
|
|
exportTarget = 1
|
|
|
}
|
|
|
|
|
|
if (isQuan) {
|
|
|
exportTarget = 2
|
|
|
}
|
|
|
|
|
|
if ('onDone' in params) {
|
|
|
ruleUpdateUtil.getGitHubFilesSha({
|
|
|
handler: sha => {
|
|
|
if (sha) {
|
|
|
ruleUpdateUtil.setFilesSha(sha)
|
|
|
}
|
|
|
params.onDone({
|
|
|
target: exportTarget,
|
|
|
actionSheet: isActionSheet,
|
|
|
fileName: fn,
|
|
|
fileData: prototype
|
|
|
})
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
}).catch(e => {
|
|
|
console.error(e.stack)
|
|
|
})
|
|
|
} catch (e) {
|
|
|
console.error(e.stack)
|
|
|
'onError' in params && params.onError(e)
|
|
|
}
|
|
|
|
|
|
function globalRename(rename, prototype) {
|
|
|
let renamePat = rename[1].split(/\s*,\s*/g).filter(i => i.indexOf('=') > -1).map(i => {
|
|
|
let sp = i.reverse().split(/\s*=(?!\\)\s*/g);
|
|
|
return sp.map(i => i.reverse().trim().replace(">", ',')).reverse();
|
|
|
});
|
|
|
console.log(renamePat)
|
|
|
renamePat.forEach(i => {
|
|
|
let oldName = i[0];
|
|
|
let newName = i[1].replace(/\\=/g, '=');
|
|
|
let oldNameReg = new RegExp(oldName, 'g');
|
|
|
prototype = prototype.replace(oldNameReg, newName);
|
|
|
});
|
|
|
return prototype;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function getRulesReplacement(content = '') {
|
|
|
let advanceSettings = content ? content : JSON.parse($file.read(FILE).string)
|
|
|
if (advanceSettings.customSettings) {
|
|
|
let cs = advanceSettings.customSettings;
|
|
|
let pat = cs.match(/\/\/\s*replacement\s*:\s*(.*?)(?:\n|\r|$)/);
|
|
|
if (pat && pat[1]) {
|
|
|
return pat[1];
|
|
|
}
|
|
|
}
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
function exportConf(fileName, fileData, exportTarget, actionSheet, isAuto, actionSheetCancel) {
|
|
|
let surge3 = exportTarget === 0
|
|
|
let surge2 = exportTarget === 1
|
|
|
let isQuan = exportTarget === 2
|
|
|
if (surge2 || surge3) {
|
|
|
let fnReg = /^[\x21-\x2A\x2C-\x2E\x30-\x3B\x3D\x3F-\x5B\x5D\x5F\x61-\x7B\x7D-\x7E]+$/
|
|
|
if (actionSheet || !fnReg.test(fileName)) {
|
|
|
$share.sheet({
|
|
|
items: [fileName, $data({ "string": fileData })],
|
|
|
handler: success => {
|
|
|
if (!success && actionSheetCancel) {
|
|
|
actionSheetCancel()
|
|
|
}
|
|
|
}
|
|
|
})
|
|
|
} else {
|
|
|
if (!$file.exists("confs")) {
|
|
|
$file.mkdir("confs")
|
|
|
} else {
|
|
|
$file.list('confs').forEach(i => $file.delete('confs/' + i))
|
|
|
}
|
|
|
$file.write({
|
|
|
data: $data({ "string": fileData }),
|
|
|
path: `confs/${fileName}`
|
|
|
})
|
|
|
$http.startServer({
|
|
|
path: "confs/",
|
|
|
handler: res => {
|
|
|
let serverUrl = `http://127.0.0.1:${res.port}/`
|
|
|
$http.get({
|
|
|
url: serverUrl + "list?path=",
|
|
|
handler: function (resp) {
|
|
|
if (resp.response.statusCode == 200) {
|
|
|
let surgeScheme = `surge${surge2 ? "" : "3"}:///install-config?url=${encodeURIComponent(serverUrl + "download?path=" + fileName)}`
|
|
|
$app.openURL(surgeScheme)
|
|
|
$delay(10, () => {
|
|
|
$http.stopServer()
|
|
|
if (isAuto) {
|
|
|
$app.close()
|
|
|
}
|
|
|
})
|
|
|
} else {
|
|
|
$ui.alert("内置服务器启动失败,请重试")
|
|
|
}
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
} else if (isQuan) {
|
|
|
if (actionSheet) {
|
|
|
$share.sheet({
|
|
|
items: [fileName, $data({ "string": fileData })],
|
|
|
handler: success => {
|
|
|
if (!success && actionSheetCancel) {
|
|
|
actionSheetCancel()
|
|
|
}
|
|
|
}
|
|
|
})
|
|
|
} else {
|
|
|
$clipboard.text = fileData
|
|
|
$app.openURL("quantumult://settings?configuration=clipboard&autoclear=1")
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function genServerFiles(name, data) {
|
|
|
$file.write({
|
|
|
data: $data({ "string": data }),
|
|
|
path: `confs/${name}`
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function urlsaveBase64Encode(url) {
|
|
|
return $text.base64Encode(url).replace(/\+/g, '-').replace(/\\/g, '_').replace(/=/g, '')
|
|
|
}
|
|
|
|
|
|
module.exports = {
|
|
|
renderUI: renderUI,
|
|
|
setUpWorkspace: setUpWorkspace,
|
|
|
autoGen: autoGen,
|
|
|
getRulesReplacement: getRulesReplacement
|
|
|
}
|