/* Surge conf builder 作者:https://t.me/miniers 修改:lhie1 */ const _ = require('lodash'); $app.autoKeyboardEnabled = true; $app.keyboardToolbarEnabled = true; let baseUrl = "https://raw.githubusercontent.com/lhie1/Rules/master/", SurgeList = [ 'Apple', 'DIRECT', 'HOST', 'Hostname', 'PROXY', 'REJECT', 'URL Rewrite', 'URL REJECT', 'Header Rewrite', { name: 'TestFlight', disabled: true, path: 'Surge/TestFlight' } ]; let remoteIsDone = 0; let remoteRule = {}; const fields = [{ name: 'filename', text: '生成的配置文件名(必填)' }, { name: 'General', text: '(必填)' }, { name: 'Proxy', text: '(必填)' }, { name: 'AutoGroup', text: '自定义Auto组自动切换的节点,只需填入节点名称且务必与Proxy中保持一致,留空默认全选,用英文逗号分隔(若出错则说明输入有误)' }, { name: 'Custom', }, { name: "ipRule" }, { name: 'Delete', text: '删除所有包含该字符的规则,按行分隔' }, { name: 'Rewrite', }, 'Host', 'SSID', { name: 'extProxy', text: '扩展代理,不会增加到自动生成的代理组' }, { name: 'hostname', text: 'MITM中的hostname' }, { name: 'extProxyGroup', text: '自定义代理组' }, 'MITM', ]; let config; if ($drive.exists("Surge.json")) { try { let file = $drive.read("Surge.json"); config = JSON.parse($drive.read("Surge.json").string) } catch (err) { initConfig() } } else { initConfig() } //初始化配置 function initConfig() { config = { filename: 'Surge.conf', remoteSwitch: {}, 'General': `// Auto loglevel = notify dns-server = system,1.2.4.8,80.80.80.80,80.80.81.81,1.1.1.1,1.0.0.1 skip-proxy = 127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,100.64.0.0/10,17.0.0.0/8,localhost,*.local,*.crashlytics.com,::ffff:0.0.0.0/1,::ffff:128.0.0.0/1 // iOS external-controller-access = lhie1@0.0.0.0:6170 allow-wifi-access = true // macOS interface = 0.0.0.0 socks-interface = 0.0.0.0 port = 8888 socks-port = 8889 enhanced-mode-by-rule = false // Auto exclude-simple-hostnames = true ipv6 = true replica = false [Replica] hide-apple-request = true hide-crashlytics-request = true use-keyword-filter = false`, 'Proxy': `🇺🇸 US = custom,us.com,1234,rc4-md5,password,http://omgib13x8.bkt.clouddn.com/SSEncrypt.module 🇭🇰 HK = custom,hk.com,1234,rc4-md5,password,http://omgib13x8.bkt.clouddn.com/SSEncrypt.module 🇯🇵 JP = custom,jp.com,1234,rc4-md5,password,http://omgib13x8.bkt.clouddn.com/SSEncrypt.module 🇸🇬 SG = custom,sg.com,1234,rc4-md5,password,http://omgib13x8.bkt.clouddn.com/SSEncrypt.module `, 'AutoGroup': `🇺🇸 US,🇭🇰 HK,🇯🇵 JP,🇸🇬 SG`, 'Custom': "", 'Rewrite': "", 'Delete': "", 'Host': "", 'SSID': "", 'extProxy': "", 'hostname': "", 'extProxyGroup': "", 'MITM': `skip-server-cert-verify = true tcp-connection = false enable = true ca-passphrase = 4B676386 ca-p12 = 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`, } } function saveConfig() { var success = $drive.write({ data: $data({ string: JSON.stringify(config) }), path: "Surge.json" }) } function buildFile() { var ProxyList = getProxyName(config.Proxy).join(','); var AutoList = (`${config.AutoGroup}` != "") ? `${config.AutoGroup}`:`${ProxyList}`; var cushostname = config.hostname.split('\n').join(',') var result = ` [General] ${config.General} [Proxy] 🚀 Direct = direct # my Proxy ${config.Proxy} # my extProxy ${config.extProxy} [Proxy Group] 🍃 Proxy = select, 🏃 Auto, 🚀 Direct,${ProxyList} 🍂 Domestic = select, 🚀 Direct, 🍃 Proxy 🍎 Only = select, 🚀 Direct,${ProxyList} ☁️ Others = select,🚀 Direct,🍃 Proxy 🏃 Auto = url-test,${AutoList},url = http://www.gstatic.com/generate_204, interval = 1200 # my extProxyGroup ${config.extProxyGroup} [Rule] # my custom ${config.Custom} # remote Apple ${remoteRule.Apple} # remote REJECT ${remoteRule.REJECT} # remote PROXY ${remoteRule.PROXY} # remote DIRECT ${remoteRule.DIRECT} # my ipRule ${config.ipRule} // Detect local network GEOIP,CN,🍂 Domestic // Use Proxy for all others FINAL,☁️ Others,dns-failed [Host] # my host ${config.Host} # remote HOST ${remoteRule['HOST']} [URL Rewrite] # my Rewrite ${config.Rewrite} # remote URL Rewrite ${remoteRule['URL Rewrite']} # remote URL REJECT ${remoteRule['URL REJECT']} [Header Rewrite] # remote Header Rewrite ${remoteRule['Header Rewrite']} ${remoteRule['TestFlight']} [SSID Setting] # my SSID ${config.SSID} # MITM ${config.MITM?'[MITM]':''} ${config.MITM?`hostname = ${cushostname?cushostname+",":""}${remoteRule.Hostname.split('\n').join(',')}`:''} ${config.MITM}`; $share.sheet([config.filename || "Surge.conf", $data({ string: result })]); $ui.loading(false) } function getProxyName(proxys) { return proxys.match(/.+(?=\s=\s(custom|http|https|socks5|socks5-tls))/g) } function build() { _.forEach(SurgeList, function(name) { let path, regex; let disabled; if (_.isObject(name)) { regex = name.regex; path = name.path; name = name.name; } else { path = `Auto/${name}`; } if (config.remoteSwitch[name] === false) { remoteIsDone++; remoteRule[name] = ''; return; } $console.info(name); $console.info(encodeURI([baseUrl, path, '.conf'].join(''))); $http.get({ url: encodeURI([baseUrl, path, '.conf'].join('')), handler: function({ data }) { if (!data) { $console.warn(`${name}拉取数据失败`) } remoteRule[name] = removeDeleteRule(data, name); if (regex && remoteRule[name]) { remoteRule[name] = _.get(remoteRule[name].match(new RegExp(regex, 's')), 1, remoteRule[name]) } remoteIsDone++; if (remoteIsDone >= SurgeList.length) { saveConfig(); buildFile(); } } }) }) } const removeDeleteRule = function(rule, name) { const deleteds = config.Delete.split('\n'); const orgRules = rule.split('\n'); let result = [], deleted = []; orgRules.forEach(function(line) { let isDeleted = false; deleteds.forEach(function(del) { if (del && line.match(del)) { isDeleted = true; } }); !isDeleted && result.push(line); }); // $ui.alert(deleted.join('\n')) return result.join('\n') }; const changeRemoteSwitch = function(key) { let switchBtn = $(`switch-${key}`); config.remoteSwitch[key] = !switchBtn.on; switchBtn.on = config.remoteSwitch[key]; console.log(`${key} = ${switchBtn.on}`) } $ui.render({ layout: $layout.fill, views: [{ type: "button", props: { title: "生成配置" }, layout: function(make) { make.left.right.top.inset(10) make.height.equalTo(32) }, events: { tapped: function(sender) { $ui.loading(true) fields.forEach(function(cus) { let cfg = cus; if (typeof cus === 'string') { cfg = { name: cus } } config[cfg.name] = $(cfg.name).text }) build() // //selectItem() } } }, { type: "list", layout: function(make) { make.left.bottom.right.equalTo(0) make.top.equalTo($("button").bottom).offset(0) }, events: { rowHeight: function(sender, indexPath) { return fields.length === indexPath.section ? 50 : 150; } }, props: { data: fields.map(function(cus) { let cfg = cus; if (typeof cus === 'string') { cfg = { name: cus } } return { title: `${cfg.name} ${cfg.text?`(${cfg.text})`:''}`, rows: [{ type: "text", props: { id: cfg.name, text: config[cfg.name] || '', }, layout: function(make) { make.edges.inset(5); } }] } }).concat([{ title: `远程规则开关`, rows: SurgeList.map(function(key) { if (_.isObject(key)) { const disabled = key.disabled; key = key.name; if (disabled && _.isUndefined(config.remoteSwitch[key])) { config.remoteSwitch[key] = false; } } return { type: 'view', props: {}, layout: $layout.fill, events: { tapped: function(sender) { changeRemoteSwitch(key); } }, views: [{ type: `switch`, props: { id: `switch-${key}`, on: config.remoteSwitch[key] !== false }, layout: function(make, view) { make.left.top.equalTo(10) }, events: { changed: function(sender) { changeRemoteSwitch(key); } } }, { type: 'label', props: { text: key, align: $align.center }, layout: function(make) { make.top.equalTo(15); make.left.equalTo(90) } } ] } }) }]) } }] })