diff --git a/ClashJS/.output/ClashJS.box b/ClashJS/.output/ClashJS.box new file mode 100644 index 0000000..125a1f6 Binary files /dev/null and b/ClashJS/.output/ClashJS.box differ diff --git a/ClashJS/README.md b/ClashJS/README.md new file mode 100644 index 0000000..e69de29 diff --git a/ClashJS/assets/icon.png b/ClashJS/assets/icon.png new file mode 100644 index 0000000..c9685f4 Binary files /dev/null and b/ClashJS/assets/icon.png differ diff --git a/ClashJS/config.json b/ClashJS/config.json new file mode 100644 index 0000000..ffe35d0 --- /dev/null +++ b/ClashJS/config.json @@ -0,0 +1,23 @@ +{ + "info": { + "name": "ClashJS", + "url": "", + "version": "1.0.1", + "author": "", + "website": "", + "types": 0 + }, + "settings": { + "minSDKVer": "1.0.0", + "minOSVer": "10.0.0", + "idleTimerDisabled": false, + "autoKeyboardEnabled": false, + "keyboardToolbarEnabled": false, + "rotateDisabled": false + }, + "widget": { + "height": 0, + "tintColor": "", + "iconColor": "" + } +} diff --git a/ClashJS/main.js b/ClashJS/main.js new file mode 100644 index 0000000..a7fb3c9 --- /dev/null +++ b/ClashJS/main.js @@ -0,0 +1,6 @@ +console.clear() +console.log('开始调试...') + +const _indexView = require('scripts/indexView') + +_indexView.render() diff --git a/ClashJS/scripts/app.js b/ClashJS/scripts/app.js new file mode 100644 index 0000000..7a783f5 --- /dev/null +++ b/ClashJS/scripts/app.js @@ -0,0 +1,7 @@ +function sayHello() { + $ui.alert($l10n('HELLO_WORLD')); +} + +module.exports = { + sayHello: sayHello +} \ No newline at end of file diff --git a/ClashJS/scripts/indexData.js b/ClashJS/scripts/indexData.js new file mode 100644 index 0000000..fb050d8 --- /dev/null +++ b/ClashJS/scripts/indexData.js @@ -0,0 +1,151 @@ +module.exports.getProxiesInfo = async (address) => { + let url = `http://${address}/proxies` + let resp = await $http.get(url) + if ('proxies' in resp.data) { + return genListData(resp.data.proxies) + } + return null +} + +let genListData = (proxies) => { + let pName = Object.keys(proxies) + let res = [] + pName.forEach(pn => { + let proxy = proxies[pn] + if (proxy.type === 'Selector') { + res.push({ + title: pn, + rows: proxy.all.map(pan => { + let latency = '-- ms' + try { + let p = proxies[pan] + if (p && p.history.length > 0) { + latency = `${p.history[p.history.length - 1].delay} ms` + } + } catch (_) { } + return { + proxyName: { text: pan }, + checkedIcon: { icon: pan === proxy.now ? $icon("136", $color("tint"), $size(20, 20)) : null }, + latencyText: { text: latency } + } + }) + }) + } else if (proxy.type === 'URLTest' || proxy.type === 'Fallback') { + let latency = '-- ms' + try { + let p = proxies[proxy.now] + if (p && p.history.length > 0) { + latency = `${p.history[p.history.length - 1].delay} ms` + } + } catch (_) { } + res.push({ + title: pn, + rows: [{ + proxyName: { text: proxy.now }, + checkedIcon: { icon: $icon("089", $color("tint"), $size(20, 20)) }, + latencyText: { text: latency } + }] + }) + } else if (proxy.type === 'Direct') { + let latency = '-- ms' + try { + let p = proxies[pn] + if (p && p.history.length > 0) { + latency = `${p.history[p.history.length - 1].delay} ms` + } + } catch (_) { } + res.push({ + title: pn, + rows: [{ + proxyName: { text: pn }, + checkedIcon: { icon: $icon("101", $color("tint"), $size(20, 20)) }, + latencyText: { text: latency } + }] + }) + } + }) + return res +} + +module.exports.switchProxy = async (address, group, proxy) => { + let url = `http://${address}/proxies/${encodeURI(group)}` + try { + let resp = await $http.request({ + url: url, + method: 'PUT', + body: { + name: proxy + } + }) + return resp.response.statusCode === 204 + } catch(e) { + return false + } +} + +module.exports.proxyNow = async (address) => { + let url = `http://${address}/proxies` + let resp = await $http.get(url) + if (resp.response.statusCode === 200) { + let proxies = resp.data.proxies + let selectors = Object.keys(proxies).map(k => { + if (proxies[k].type !== 'Selector') return null + let obj = {} + obj.name = k + obj.now = proxies[k].now + return obj + }).filter(proxy => proxy) + return selectors + } + return [] +} + +module.exports.getConfig = async (address) => { + let url = `http://${address}/configs` + let resp = await $http.get(url) + return resp.data +} + +module.exports.switchMode = async (address, mode) => { + let url = `http://${address}/configs` + let resp = await $http.request({ + url: url, + method: 'PATCH', + body: { + mode: mode + } + }) +} + +module.exports.latencyTest = async (address, nodes) => { + let res = await Promise.all(nodes.map(node => { + let url = `http://${address}/proxies/${encodeURI(node)}/delay?timeout=5000&url=${encodeURI('http://www.gstatic.com/generate_204')}` + return $http.get(url) + })) + return nodes.map((node, idx) => { + return { + name: node, + delay: res[idx].data.delay + } + }) +} + +module.exports.sniffAddress = async () => { + try { + if ($device.networkType !== 1) return + let addr = $device.wlanAddress + let arr = Array.from(new Array(255), (val, idx) => idx + 1) + let res = await Promise.all(arr.map(i => { + let url = `http://${addr.replace(/.(\d*?)$/, `.${i}`)}:8080/` + return $http.request({ + url: url, + method: "HEAD", + timeout: 2 + }) + })) + let idx = res.findIndex(i => i.response) + return idx === -1 ? '' : addr.replace(/.(\d*?)$/, `.${idx + 1}`) + ':8080' + } catch (e) { + console.error(e.stack) + } +} \ No newline at end of file diff --git a/ClashJS/scripts/indexView.js b/ClashJS/scripts/indexView.js new file mode 100644 index 0000000..593da42 --- /dev/null +++ b/ClashJS/scripts/indexView.js @@ -0,0 +1,298 @@ +const _data = require('./indexData') +const _logView = require('./logView') +const _latencyView = require('./latencyView') + +module.exports.render = async () => { + $ui.render({ + props: { + title: "Clash面板", + bgcolor: $color("tint"), + navButtons: [{ + title: "Title", + icon: "121", + handler: async () => { + _logView.render($("urlInputView").text) + } + }] + }, + views: [{ + type: 'button', + props: { + id: 'proxyWayBtn', + title: "Mode", + bgcolor: $color("#e7eaed"), + titleColor: $color("black") + }, + layout: (make, view) => { + make.top.equalTo(view.super).offset(10) + make.width.equalTo(100) + make.height.equalTo(40) + make.right.equalTo(view.super).offset(-10) + }, + events: { + tapped: sender => { + modeMenu().then(res => { }) + } + } + }, { + type: 'input', + props: { + id: 'urlInputView', + placeholder: '192.168.xx.xx:8080', + text: $cache.get('address') || '' + }, + layout: (make, view) => { + make.top.equalTo(view.super).offset(10) + make.width.equalTo(view.super).offset(-130) + make.height.equalTo(40) + make.left.equalTo(view.super).offset(10) + }, + events: { + returned: async sender => { + $cache.set('address', sender.text) + sender.blur() + await loadData() + } + } + }, { + type: 'view', + props: { + bgcolor: $color("white") + }, + layout: (make, view) => { + make.width.equalTo(view.super).offset(-20) + make.top.equalTo(view.prev.bottom).offset(30) + make.left.equalTo(view.super).offset(10) + make.height.equalTo(0) + } + }, { + type: 'menu', + props: { + id: 'proxyGroupMenu', + smoothRadius: 8 + }, + layout: (make, view) => { + make.height.equalTo(40) + make.width.equalTo(view.super).offset(-20) + make.top.equalTo(view.prev.prev.bottom).offset(10) + make.left.equalTo(view.super).offset(10) + }, + events: { + changed: sender => { + let index = sender.index + let listData = $("mainProxyList").data.slice(0, index) + let secNum = listData.length + let itemNum = 0 + listData.forEach(i => { + itemNum += i.rows.length + }) + let offset = secNum * 27 + itemNum * 50 + $("mainProxyList").scrollToOffset($point(0, offset)); + } + } + }, { + type: 'list', + props: { + id: 'mainProxyList', + separatorHidden: true, + rowHeight: 50, + radius: 7, + data: [], + template: { + props: { + + }, + views: [{ + type: 'label', + props: { + id: 'proxyName', + font: $font(14), + textColor: $color("black") + }, + layout: (make, view) => { + make.left.equalTo(view.super).offset(10) + make.centerY.equalTo(view.super).offset(-10) + make.width.equalTo(view.super).offset(-50) + } + }, { + type: 'image', + props: { + id: 'checkedIcon', + bgcolor: $color("clear") + }, + layout: (make, view) => { + make.left.equalTo(view.prev.right).offset(15) + make.centerY.equalTo(view.super) + make.size.equalTo($size(15, 15)) + } + }, { + type: 'label', + props: { + id: 'latencyText', + font: $font(12), + textColor: $color("tint") + }, + layout: (make, view) => { + make.centerY.equalTo(view.super).offset(10) + make.left.equalTo(view.super).offset(10) + make.width.equalTo(view.super) + make.height.equalTo(14) + } + },] + }, + actions: [{ + title: '延迟历史', + color: $color("tint"), + handler: (sender, idxPath) => { + let data = sender.object(idxPath) + _latencyView.render($("urlInputView").text, data.proxyName.text) + } + }] + }, + layout: (make, view) => { + make.top.equalTo(view.prev.bottom).offset(10) + make.width.equalTo(view.super).offset(-20) + make.centerX.equalTo(view.super) + make.height.equalTo(view.super).offset(-160 - ($device.isIphoneX ? 40 : 0)) + }, + events: { + didSelect: async (sender, indexPath, data) => { + let address = $("urlInputView").text + let oldData = sender.data + if (data.checkedIcon.icon === null) { + let sec = oldData[indexPath.section].title + let success = await _data.switchProxy(address, sec, data.proxyName.text) + if (success) { + oldData[indexPath.section].rows = oldData[indexPath.section].rows.map(row => { + let copy = row + if (row.proxyName.text === data.proxyName.text) { + copy.checkedIcon.icon = $icon("136", $color("tint"), $size(20, 20)) + } else { + copy.checkedIcon.icon = null + } + return copy + }) + sender.data = oldData + } + } + let proxyNowArr = await _data.proxyNow(address) + $cache.set('proxyNow', proxyNowArr) + }, + forEachItem: (view, indexPath) => { + view.borderWidth = 0.5 + view.borderColor = $rgba(40, 40, 40, 0.1) + }, + pulled: async sender => { + await testLatency(true); + } + } + }, { + type: 'button', + props: { + id: 'latencyBtn', + title: 'Test Latency', + bgcolor: $rgba(255, 255, 255) + }, + layout: (make, view) => { + make.top.equalTo(view.prev.bottom).offset(5) + make.height.equalTo(35) + make.width.equalTo(view.super).offset(-20) + make.centerX.equalTo(view.super) + }, + events: { + tapped: async sender => { + await testLatency() + } + }, + views: [] + },] + }) + $("urlInputView").text === '' && guessAddress() + await loadData() +} + +let modeMenu = async () => { + let res = await $ui.menu({ + items: ['Global', 'Rule', 'Direct'] + }) + await _data.switchMode($("urlInputView").text, res.title) + await loadData() +} + +let loadData = async () => { + let address = $("urlInputView").text + // 恢复选中策略 + let proxyNow = $cache.get('proxyNow') || [] + await Promise.all(proxyNow.map(pn => _data.switchProxy(address, pn.name, pn.now))) + + let [listData, config] = await Promise.all([_data.getProxiesInfo(address), _data.getConfig(address)]) + let mode = config.mode + listData = listData.filter(i => { + if (mode === 'Global') { + return i.title === 'GLOBAL' + } else if (mode === 'Direct') { + return i.title === 'DIRECT' + } + return i.title !== 'GLOBAL' && i.title !== 'DIRECT' + }) + $("proxyGroupMenu").items = listData.map(i => i.title) + $("proxyGroupMenu").index = 0 + $('proxyWayBtn').title = config.mode + $('mainProxyList').data = listData +} + +let guessAddress = async () => { + let address = await _data.sniffAddress() + if (address) { + $device.taptic(1) + $ui.alert({ + title: "提示", + message: `嗅探到当前网络下Clash的目标地址可能为 ${address} ,是否直接使用?`, + actions: [{ + title: "取消", + handler: () => { + + } + }, { + title: "好的", + handler: async () => { + $("urlInputView").text = address + $cache.set('address', address) + await loadData() + } + }] + }) + } +} + + + + +async function testLatency(pull = false) { + !pull && $ui.loading("测试中...") + let listData = $("mainProxyList").data; + let nodeNames = []; + listData.forEach(sec => { + sec.rows.forEach(node => { + let t = node.proxyName.text; + if (nodeNames.indexOf(t) === -1) { + nodeNames.push(t); + } + }); + }); + let nodeLat = await _data.latencyTest($("urlInputView").text, nodeNames); + $("mainProxyList").data = listData.map(sec => { + let rows = sec.rows; + rows = rows.map(node => { + let delay = nodeLat.find(n => n.name === node.proxyName.text).delay; + node.latencyText = { + text: delay ? `${delay} ms` : 'Timeout' + }; + return node; + }); + return sec; + }); + pull && $("mainProxyList").endRefreshing(); + !pull && $ui.loading(false) +} + diff --git a/ClashJS/scripts/latencyData.js b/ClashJS/scripts/latencyData.js new file mode 100644 index 0000000..e8df80b --- /dev/null +++ b/ClashJS/scripts/latencyData.js @@ -0,0 +1,4 @@ +module.exports.getHistory = async (address, name, callback) => { + let resp = await $http.get(`http://${address}/proxies/${encodeURIComponent(name)}`) + callback(resp.data) +} \ No newline at end of file diff --git a/ClashJS/scripts/latencyView.js b/ClashJS/scripts/latencyView.js new file mode 100644 index 0000000..a9a548a --- /dev/null +++ b/ClashJS/scripts/latencyView.js @@ -0,0 +1,69 @@ +const _data = require('./latencyData') + +module.exports.render = (address, proxyName) => { + $ui.push({ + props: { + title: proxyName, + bgcolor: $color("tint") + }, + views: [{ + type: 'list', + props: { + id: 'mainHistoryView', + template: { + props: {}, + views: [{ + type: 'label', + props: { + id: 'logContent', + lines: 0, + font: $font("bold", 16) + }, + layout: (make, view) => { + // make.width.equalTo(view.super).offset(-20) + make.left.equalTo(view.super).offset(10) + make.centerY.equalTo(view.super).offset(0) + }, + events: {}, + views: [] + }, { + type: 'label', + props: { + id: 'logTime', + lines: 0, + font: $font(13), + }, + layout: (make, view) => { + // make.width.equalTo(view.super).offset(-20) + make.right.equalTo(view.super).offset(-10) + make.centerY.equalTo(view.super).offset(0) + }, + events: {}, + views: [] + },] + } + }, + layout: $layout.fill, + events: { + // rowHeight: (sender, indexPath) => { + // let text = sender.object(indexPath) + // let size = $text.sizeThatFits({ + // text: text.logContent.text, + // width: sender.frame.width - 20, + // font: $font("bold", 15), + // lineSpacing: 2, // Optional + // }) + // return size.height + 20 + // } + } + },] + }) + _data.getHistory(address, proxyName, res => { + $("mainHistoryView").data = res.history.map(h => { + return { + logContent: { text: h.delay + ' ms' }, + logTime: { text: new Date(h.time).toLocaleString() } + } + }) + }) +} \ No newline at end of file diff --git a/ClashJS/scripts/logData.js b/ClashJS/scripts/logData.js new file mode 100644 index 0000000..e2d5287 --- /dev/null +++ b/ClashJS/scripts/logData.js @@ -0,0 +1,45 @@ +module.exports.startLogging = (address, callback) => { + $define({ + type: "ChunkController: NSObject", + events: { + "connection:didReceiveData:": function (conn, data) { + var strData = $text.decodeData({ + data: data.rawValue(), + encoding: 4 + }) + strData.split(/[\r\n]+/).filter(i => i).forEach(log => { + try { + let jsonLog = JSON.parse(log) + callback({ + event: "log", + content: jsonLog + }) + } catch (e) { + // console.error(e.stack) + } + }) + }, + "connection:didReceiveResponse:": function (conn, resp) { + // console.log('resp got') + }, + "connection:didFailWithError:": function (conn, error) { + console.error(error.rawValue()) + callback({ + event: "error", + content: {} + }) + }, + connectionDidFinishLoading: function (conn) { + + }, + sendRequestWithDelegate: function (address) { + let url = $objc("NSURL").$URLWithString(`http://${address}/logs`) + let request = $objc("NSURLRequest").$requestWithURL_cachePolicy_timeoutInterval(url, 0, 30) + $objc("NSURLConnection").$alloc().$initWithRequest_delegate_startImmediately(request, self, true) + // conn.$start() + } + } + }); + + $objc("ChunkController").$alloc().$init().$sendRequestWithDelegate(address) +} \ No newline at end of file diff --git a/ClashJS/scripts/logView.js b/ClashJS/scripts/logView.js new file mode 100644 index 0000000..35167e9 --- /dev/null +++ b/ClashJS/scripts/logView.js @@ -0,0 +1,79 @@ +const _data = require('./logData') + +module.exports.render = (address) => { + $ui.push({ + props: { + title: "日志", + bgcolor: $color("tint") + }, + views: [{ + type: 'list', + props: { + id: 'mainLogView', + template: { + props: {}, + views: [{ + type: 'label', + props: { + id: 'logContent', + lines: 0, + font: $font("bold", 15) + }, + layout: (make, view) => { + make.width.equalTo(view.super).offset(-20) + make.left.equalTo(view.super).offset(10) + make.centerY.equalTo(view.super).offset(-10) + }, + events: {}, + views: [] + }, { + type: 'label', + props: { + id: 'logTime', + lines: 0, + font: $font(12), + }, + layout: (make, view) => { + make.width.equalTo(view.super).offset(-20) + make.left.equalTo(view.super).offset(10) + make.bottom.equalTo(view.super).offset(-5) + }, + events: {}, + views: [] + },] + } + }, + layout: $layout.fill, + events: { + rowHeight: (sender, indexPath) => { + let text = sender.object(indexPath) + let size = $text.sizeThatFits({ + text: text.logContent.text, + width: sender.frame.width - 20, + font: $font("bold", 15), + lineSpacing: 2, // Optional + }) + return size.height + 20 + } + } + },] + }) + _data.startLogging(address, log => { + if (log.event === 'log') { + let oldData = $("mainLogView").data + let listLength = oldData.length + let indexPath = $indexPath(0, listLength) + oldData.push({ + logContent: { text: log.content.payload }, + logTime: { text: new Date().toLocaleTimeString() } + }) + $("mainLogView").data = oldData + $("mainLogView").scrollTo({ + indexPath: indexPath, + animate: true + }) + } else if (log.event === 'error') { + // $ui.alert('链接出现异常,请重新进查看日志') + } + }) +} \ No newline at end of file diff --git a/ClashJS/strings/en.strings b/ClashJS/strings/en.strings new file mode 100644 index 0000000..ae42947 --- /dev/null +++ b/ClashJS/strings/en.strings @@ -0,0 +1,3 @@ +"OK" = "OK"; +"DONE" = "Done"; +"HELLO_WORLD" = "Hello, World!"; diff --git a/ClashJS/strings/zh-Hans.strings b/ClashJS/strings/zh-Hans.strings new file mode 100644 index 0000000..b2d0c5d --- /dev/null +++ b/ClashJS/strings/zh-Hans.strings @@ -0,0 +1,3 @@ +"OK" = "好的"; +"DONE" = "完成"; +"HELLO_WORLD" = "你好,世界!"; diff --git a/Github.js b/Github.js new file mode 100644 index 0000000..8970642 --- /dev/null +++ b/Github.js @@ -0,0 +1,30 @@ +let socketLogger = require("socketLogger") +typeof socketLogger.init === 'function' && socketLogger.init('192.168.50.229') +// SocketLogger Auto Generation Code + +$ui.render({ + props: { + navBarHidden: true, + statusBarStyle: 1, + }, + views: [{ + type: "view", + layout: $layout.fill, + props: { + bgcolor: $color("#24292e") + } + },{ + type: "web", + props: { + url: "https://www.github.com", + toolbar: true, + showsProgress: false, + bounces: false + }, + layout: (make, view) => { + make.top.equalTo(22) + make.height.equalTo(view.super).offset(22) + make.width.equalTo(view.super) + } + }] +}) \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e3745d6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Fndroid + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Later/.vscode/launch.json b/Later/.vscode/launch.json new file mode 100644 index 0000000..5540689 --- /dev/null +++ b/Later/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "ScoketLogger", + "address": "localhost", + "port": 9229 + } + ] +} \ No newline at end of file diff --git a/Later/README.md b/Later/README.md new file mode 100644 index 0000000..e69de29 diff --git a/Later/assets/icon.png b/Later/assets/icon.png new file mode 100644 index 0000000..c9685f4 Binary files /dev/null and b/Later/assets/icon.png differ diff --git a/Later/config.json b/Later/config.json new file mode 100644 index 0000000..15c7297 --- /dev/null +++ b/Later/config.json @@ -0,0 +1,23 @@ +{ + "info": { + "name": "Later", + "url": "", + "version": "1.0.0", + "author": "Fndroid", + "website": "", + "types": 0 + }, + "settings": { + "minSDKVer": "1.0.0", + "minOSVer": "10.0.0", + "idleTimerDisabled": false, + "autoKeyboardEnabled": false, + "keyboardToolbarEnabled": false, + "rotateDisabled": false + }, + "widget": { + "height": 0, + "tintColor": "", + "iconColor": "" + } +} diff --git a/Later/main.js b/Later/main.js new file mode 100644 index 0000000..f852614 --- /dev/null +++ b/Later/main.js @@ -0,0 +1,79 @@ +let socketLogger = require("socketLogger") +typeof socketLogger.init === 'function' && socketLogger.init('192.168.50.229') +// SocketLogger Auto Generation Code + +console.clear() + +async function main() { + if (typeof $context.query.url === 'string') { + let url = $context.query.url + if (/^https?:\/\//.test(url)) { + $app.openURL(url) + } else { + $quicklook.open({ + url: url + }) + } + } else if (typeof $context.query.image === 'string') { + $quicklook.open({ + image: $file.read($context.query.image).image + }) + } else if (typeof $context.query.text === 'string') { + $quicklook.open({ + text: $context.query.text + }) + } else if (typeof $context.query.data === 'object') { + $quicklook.open({ + type: 'jpg', + data: $context.query.data + }) + } + let push = ($app.env === $env.action || $app.env === $env.safari) + if (!$file.exists('imgs')) { + $file.mkdir('imgs') + } + let allItems = $context.allItems + console.log('all', allItems) + let notification = { + mute: true, + script: $addin.current.name, + title: `Pin at ${new Date().toLocaleString()}`, + } + if (typeof allItems.data === 'object') { + notification.body = `文件` + notification.query = { + data: allItems.data[0] + } + } else if (typeof allItems.image === 'object') { + let success = $file.write({ + data: allItems.image[0].png.jpg(0.2), + path: 'imgs/noti.png' + }) + if (success) { + notification.attachments = ['imgs/noti.png'] + notification.query = { + image: "imgs/noti.png" + } + } + } else if (typeof allItems.text === 'object') { + notification.body = allItems.text[0] + notification.query = { + text: allItems.text[0] + } + } else if (typeof allItems.safari === 'object') { + notification.body = `链接:${allItems.safari.items.title}` + notification.query = { + url: allItems.safari.items.baseURI + } + } else if (typeof allItems.link === 'object') { + notification.body = `链接:${allItems.link[0]}` + notification.query = { + url: allItems.link[0] + } + } + if (push) { + $push.schedule(notification) + } +} + +main() \ No newline at end of file diff --git a/Later/scripts/app.js b/Later/scripts/app.js new file mode 100644 index 0000000..7a783f5 --- /dev/null +++ b/Later/scripts/app.js @@ -0,0 +1,7 @@ +function sayHello() { + $ui.alert($l10n('HELLO_WORLD')); +} + +module.exports = { + sayHello: sayHello +} \ No newline at end of file diff --git a/Later/scripts/utils/screen.js b/Later/scripts/utils/screen.js new file mode 100644 index 0000000..c8ffc05 --- /dev/null +++ b/Later/scripts/utils/screen.js @@ -0,0 +1,13 @@ +function getScreenInfo() { + let deviceInfo = $device.info + let isIphoneX = $device.isIphoneX + return { + width: deviceInfo.screen.width, + height: deviceInfo.screen.height - (isIphoneX ? 44 : 0), + statusHieght: isIphoneX ? 44 : 20 + } +} + +module.exports = { + getScreenInfo: getScreenInfo +} \ No newline at end of file diff --git a/Later/scripts/views/extension.js b/Later/scripts/views/extension.js new file mode 100644 index 0000000..c0b2c8b --- /dev/null +++ b/Later/scripts/views/extension.js @@ -0,0 +1,57 @@ +const _screenU = require('scripts/utils/screen') + +module.exports.main = (callback) => { + return { + props: { + id: "extensionManiView", + navBarHidden: true, + bgcolor: $color("#323232") + }, + views: [{ + type: "label", + props: { + textColor: $color("white"), + text: "Add to Later", + font: $font("bold", 30), + }, + layout: (make, view) => { + make.top.equalTo(_screenU.statusHeight).offset(30) + make.left.equalTo(view.super).offset(10) + } + }, { + type: "image", + props: { + icon: $icon("225", $color("white"), $size(25, 25)), + bgcolor: $color("clear") + }, + layout: (make, view) => { + make.centerY.equalTo(view.prev) + make.right.equalTo(-10) + }, + events: { + tapped: _ => { + $context.close() + } + } + }, { + type: "input", + props: { + radius: 10, + darkKeyboard: true, + placeholder: "提醒内容" + }, + layout: (make, view) => { + make.height.equalTo(40) + make.width.equalTo(view.super).offset(-20) + make.center.equalTo(view.super) + // make.edges.insets($insets(0, 10, 0, 10)) + }, + events: { + returned: sender => { + sender.blur() + typeof callback === 'function' && callback(sender.text) + } + } + }] + } +} \ No newline at end of file diff --git a/Later/scripts/views/index.js b/Later/scripts/views/index.js new file mode 100644 index 0000000..e69de29 diff --git a/Later/strings/en.strings b/Later/strings/en.strings new file mode 100644 index 0000000..ae42947 --- /dev/null +++ b/Later/strings/en.strings @@ -0,0 +1,3 @@ +"OK" = "OK"; +"DONE" = "Done"; +"HELLO_WORLD" = "Hello, World!"; diff --git a/Later/strings/zh-Hans.strings b/Later/strings/zh-Hans.strings new file mode 100644 index 0000000..b2d0c5d --- /dev/null +++ b/Later/strings/zh-Hans.strings @@ -0,0 +1,3 @@ +"OK" = "好的"; +"DONE" = "完成"; +"HELLO_WORLD" = "你好,世界!"; diff --git a/QX RuleSet.js b/QX RuleSet.js new file mode 100644 index 0000000..1900a0f --- /dev/null +++ b/QX RuleSet.js @@ -0,0 +1,646 @@ +$app.autoKeyboardEnabled = true +$app.keyboardToolbarEnabled = true + +let fileServer = null + +const DEFAULTDATA = { + list: [] +} + +const DATAFILE = 'data.json' +if (!$file.exists(DATAFILE)) { + $file.write({ + data: $data({ string: JSON.stringify(DEFAULTDATA) }), + path: DATAFILE + }) +} + +let render = () => { + const SI = screenInfo() + $ui.render({ + props: { + title: "QX RuleSet", + id: "mainView", + navButtons: [{ + title: "Title", + icon: "165", // Or you can use icon name + symbol: "checkmark.seal", // SF symbols are supported + handler: async () => { + let text = await $input.text({ + type: $kbType.number, + placeholder: "URL of data.json" + }) + let links = $detector.link(text) + if (links.length > 0) { + $file.write({ + data: $data({ url: links[0] }), + path: DATAFILE + }) + render() + } + } + }, { + title: "Title", + icon: "074", // Or you can use icon name + symbol: "checkmark.seal", // SF symbols are supported + handler: function () { + showGiftDialog(); + } + }] + }, + views: [{ + type: "list", + props: { + id: "mainList", + bgcolor: $color("clear"), + separatorHidden: true, + rowHeight: 65, + reorder: true, + data: loadDBData(), + actions: [{ + title: "Preview", + color: $color("tint"), + handler: (sender, indexPath) => { + const item = sender.object(indexPath).raw + $ui.preview({ + title: item.policy, + url: item.url + }) + } + },{ + title: "Remove", + color: $color("#e10601"), + handler: (sender, indexPath) => { + sender.delete(indexPath) + updateData() + } + }, { + title: "On / Off", + handler: (sender, indexPath) => { + let oldData = sender.data + const item = oldData[indexPath.row].raw + item.enable = !item.enable + oldData[indexPath.row] = rawToTemplete(item) + sender.data = oldData + updateData() + } + }], + footer: { + type: "view", + props: { + height: 100, + }, + events: { + tapped: sender => { + showGiftDialog() + } + }, + views: [{ + type: "label", + props: { + text: "为了吃饭!", + textColor: $color("#AAAAAA"), + align: $align.center, + font: $font(13) + }, + layout: (make, view) => { + make.width.equalTo(view.super).offset(-140) + make.height.equalTo(view.super) + } + }] + }, + template: { + props: { + bgcolor: $color("clear") + }, + views: [{ + type: "label", + props: { + id: "url", + lines: 1, + font: $font('Menlo-Regular', 13), + }, + layout: (make, view) => { + make.left.equalTo(view.super).offset(SI.padding) + make.width.equalTo(view.super).offset(- (SI.padding * 2)) + make.top.equalTo(view.super).offset(15) + } + }, { + type: "label", + props: { + id: "policy", + radius: 2, + font: $font('Menlo-Regular', 13) + }, + layout: (make, view) => { + make.left.equalTo(view.super).offset(SI.padding) + make.top.equalTo(view.prev.bottom).offset(5) + make.height.equalTo(20) + } + }, { + type: "label", + props: { + id: "note", + borderColor: $color('tint'), + borderWidth: 1, + bgcolor: $color('white'), + radius: 2, + font: $font('Menlo-Regular', 11.5) + }, + layout: (make, view) => { + make.left.equalTo(view.prev.right).offset(-10) + make.top.equalTo(view.prev.prev.bottom).offset(5) + make.height.equalTo(20) + } + }] + } + }, + layout: $layout.fill, + events: { + didSelect: (sender, indexPath, data) => { + let oldData = sender.data + const item = oldData[indexPath.row] + showAlterDialog(item.raw.url, item.raw.policy, item.raw.note, (view, url, policy, note) => { + if (url && policy) { + oldData[indexPath.row] = rawToTemplete({ url, policy, note: note || "无备注" }) + sender.data = oldData + updateData() + } else { + $ui.alert('URL和Policy不能为空!') + } + }) + }, + reorderFinished: (data) => { + updateData() + } + } + }, { + type: "button", + props: { + icon: $icon("104", $color("white"), $size(20, 20)), + bgcolor: $color("tint"), + radius: 25 + }, + layout: (make, view) => { + make.height.width.equalTo(50) + make.bottom.equalTo(SI.screenHeight).offset(-20) + make.right.equalTo(view.super).offset(-80) + }, + events: { + tapped: sender => { + showAlterDialog('', '', '', (view, url, policy, note) => { + if (url && policy) { + $("mainList").insert({ + index: $('mainList').data.length, + value: rawToTemplete({ url, policy, note: note || "无备注" }) + }) + updateData() + } else { + $ui.alert('URL和Policy不能为空!') + } + }) + } + } + }, { + type: "button", + props: { + icon: $icon("049", $color("white"), $size(20, 20)), + bgcolor: $color("tint"), + radius: 25 + }, + layout: (make, view) => { + make.height.width.equalTo(50) + make.bottom.equalTo(SI.screenHeight).offset(-20) + make.right.equalTo(view.super).offset(-20) + }, + events: { + tapped: async (sender) => { + await generateRules() + } + } + }] + }) +} + +function showGiftDialog() { + $ui.alert({ + title: '感谢支持', + message: '作者投入大量时间和精力对脚本进行开发和完善,你愿意给他赏杯咖啡支持一下吗?', + actions: [{ + title: "支付宝", + handler: () => { + $app.openURL("https://qr.alipay.com/FKX09054QNRTGGKQZJN6E6"); + } + }, { + title: "微信", + handler: async () => { + $quicklook.open({ + image: $data({ url: 'https://github.com/Fndroid/jsbox_script/blob/master/Rules-lhie1/assets/thankyou.jpg?raw=true' }).image + }); + } + }, { + title: "返回" + }] + }); +} + +function updateData() { + let data = readData() + let listData = $("mainList").data + data.list = listData.map(l => l.raw) + $file.write({ + data: $data({ string: JSON.stringify(data) }), + path: DATAFILE + }) +} + +function rawToTemplete(values) { + const SI = screenInfo() + const count = SI.screenWidth * 76 / 640 - 3 + let url = values.url + if (!values.hasOwnProperty('enable')) { + values.enable = true + } + const l = url.length + const part = count / 2 + if (url.length > count) { + url = url.slice(0, part) + '...' + url.slice(l - part, l) + } + const gray = $color("gray") + const black = $color("black") + const white = $color("white") + const tint = $color('tint') + let itemData = { + raw: values, + url: { text: `${url}`, textColor: values.enable ? black : gray }, + policy: { text: ` ${values.policy} `, textColor: values.enable ? white : white, bgcolor: values.enable ? tint : gray }, + note: { text: ` ${values.note} `, textColor: values.enable ? black : gray, borderColor: values.enable ? tint : gray } + } + return itemData +} + +function readData() { + let data = '' + const file = $file.read(DATAFILE) + data = JSON.parse(file.string) + return data +} + +function loadDBData() { + let data = readData() + if (!data || !data.list) return [] + let listData = []; + for (let i = 0; i < data.list.length; i++) { + let values = data.list[i]; + let templeteData = rawToTemplete(values); + listData.push(templeteData); + } + return listData; +} + +async function generateRules() { + $ui.loading(true) + const listData = loadDBData().map(i => i.raw).filter(i => i.enable) + const policies = listData.map(i => i.policy) + const urls = listData.map(i => i.url) + const contents = await downloadAll(urls) + const emptyURLs = urls.filter((_, i) => { + return contents[i] === '' + }) + const newContents = contents.map(c => modifyRules(c)) + const newContentsWithPolicy = newContents.map((c, i) => { + c = c.map(r => `${r}, ${policies[i]}`) + return c + }) + const result = newContentsWithPolicy.map(c => c.join('\n')).join('\n') + serveContent(result, async url => { + $ui.loading(false) + await $http.get(url) + const msg = emptyURLs.length === 0 ? 'RuleSet下载成功,进入QuantumultX更新引用即可' : `如下链接下载失败:\n${emptyURLs.join('\n')}` + $ui.alert({ + title: "完成", + message: msg, + actions: [{ + title: "Cancel", + disabled: false, + handler: null + }, { + title: "Copy & Go", + handler: function () { + $clipboard.text = url + open_app('com.crossutility.quantumult-x') + } + }] + }) + }) + +} + +function open_app(appid) { + $objc("LSApplicationWorkspace").invoke("defaultWorkspace").invoke("openApplicationWithBundleID", appid) +} + +async function downloadAll(urls) { + return await Promise.all(urls.map(url => download(url))) +} + +async function download(url) { + let content = '' + try { + const resp = await $http.get(url) + if (resp.response.statusCode === 200) { + content = resp.data + } + } catch (e) { } + return content +} + +function modifyRules(raw) { + const lines = raw.split(/[\r\n]/) + let result = lines.map(l => { + if (/^(#|\/\/|;)/.test(l)) return null + if (/^(USER\-AGENT|DOMAIN|DOMAIN\-SUFFIX|DOMAIN\-KEYWORD|IP\-CIDR|FINAL|GEOIP)\s*,(.+?)(?:,|$)/.test(l)) { + if (RegExp.$1.trim() === 'FINAL') { + return RegExp.$1 + } + return `${RegExp.$1.trim()}, ${RegExp.$2.trim()}` + } + return null + }).filter(l => l) + return result +} + +function serveContent(content, callback) { + const serverPort = 9898 + try { + if (fileServer) { + fileServer.stop() + } + let server = $server.new() + fileServer = server + let serverOptions = { + port: serverPort, + } + server.addHandler({ + response: req => { + return { + type: 'data', + props: { + text: content, + headers: { + } + } + } + } + }) + server.listen({ + didStart: server => { + callback(`http://localhost:${server.port}`) + } + }) + server.start(serverOptions) + } + catch (e) { + callback('') + } +} + +function isJson(str) { + try { + JSON.parse(str) + } catch (e) { + return false + } + return true +} + +function showAlterDialog(group, method, url, callback) { + 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(350); + make.width.equalTo(view.super).offset(-60); + make.center.equalTo(view.super) + }, + events: { + tapped: sender => { } + }, + views: [{ + type: "label", + props: { + text: "URL", + 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: "alterInputURL", + placeholder: "https://xxx", + text: group, + 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: "Policy", + font: $font("bold", 16) + }, + layout: (make, view) => { + make.top.equalTo(view.prev.bottom).offset(15); + make.left.equalTo(view.super).offset(10); + } + }, { + type: "input", + props: { + id: "alberInputPolicy", + text: method, + placeholder: "PROXY", + autoFontSize: true + }, + events: { + // tapped: sender => { + // // $("methodView").hidden = false + // } + }, + 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: "Note", + font: $font("bold", 16) + }, + layout: (make, view) => { + make.top.equalTo(view.prev.bottom).offset(15); + make.left.equalTo(view.super).offset(10); + } + }, { + type: "input", + props: { + id: "alberInputNote", + text: url, + placeholder: "代理", + 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: '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(-15) + make.right.equalTo(view.super).offset(-15) + }, + events: { + tapped: sender => { + callback && callback($("alertBody"), $("alterInputURL").text, $("alberInputPolicy").text, $("alberInputNote").text); + $("alertBody").remove(); + } + } + }, { + type: "button", + props: { + icon: $icon("018", $color("#fff"), $size(20, 20)), + id: 'cameraBtn', + radius: 25, + hidden: true + }, + layout: (make, view) => { + make.height.width.equalTo(50) + make.bottom.equalTo(view.super).offset(-15) + make.left.equalTo(view.super).offset(15) + }, + events: { + tapped: async sender => { + let qr = await $qrcode.scan({}) + if (qr) { + $("alberInputURL").text = qr + } + } + } + }, { + type: "view", + props: { + id: "methodView", + hidden: true, + bgcolor: $color("#dcdcdc") + }, + views: [{ + type: "label", + props: { + text: "确认", + font: $font("bold", 16) + }, + layout: (make, view) => { + make.bottom.equalTo(view.super).offset(-10) + make.right.equalTo(view.super).offset(-20) + make.height.equalTo(40) + }, + events: { + tapped: sender => { + const methods = ['none', 'aes-128-cfb', 'aes-128-gcm', 'chacha20-ietf-poly1305'] + let selectedRows = $("methodPicker").selectedRows[0] + $("alberInputMethod").text = methods[selectedRows] + $("methodView").hidden = true + } + } + }, { + type: "picker", + props: { + id: "methodPicker", + items: [['none', 'aes-128-cfb', 'aes-128-gcm', 'chacha20-ietf-poly1305']] + }, + layout: (make, view) => { + make.bottom.equalTo(view.prev.top) + make.height.equalTo(view.super).offset(-50) + make.width.equalTo(view.super) + } + }], + layout: $layout.fill + }], + }], + events: { + tapped: sender => { + sender.remove() + } + } + } + $("mainView").add(view) + $ui.animate({ + duration: 0.2, + animation: () => { + $("alertBody").alpha = 1 + } + }) +} + +let screenInfo = () => { + let screenHeight = $device.info.screen.height + const screenWidth = $device.info.screen.width + + const iPhoneX = screenWidth == 375 && screenHeight == 812 + if (iPhoneX) { + screenHeight -= 48 + } + + const statusBarHeight = iPhoneX ? 44 : 20 + const navBarHeight = 45 + return { + screenHeight: screenHeight, + screenWidth: screenWidth, + statusBarHeight: statusBarHeight, + navBarHeight: navBarHeight, + padding: 10 + } +} + +render() diff --git a/Quan2Clash/.output/Quan2Clash.box b/Quan2Clash/.output/Quan2Clash.box new file mode 100644 index 0000000..8934e44 Binary files /dev/null and b/Quan2Clash/.output/Quan2Clash.box differ diff --git a/Quan2Clash/README.md b/Quan2Clash/README.md new file mode 100644 index 0000000..e69de29 diff --git a/Quan2Clash/assets/demo.conf b/Quan2Clash/assets/demo.conf new file mode 100644 index 0000000..f410f23 --- /dev/null +++ b/Quan2Clash/assets/demo.conf @@ -0,0 +1,50 @@ +# 20190328_203915 + +[SERVER] +ss = shadowsocks, 1.1.1.1, 1234, aes-128-cfb, "password", upstream-proxy=false, upstream-proxy-auth=false, obfs=http +vmess = vmess, 1.1.1.1, 1234, auto, "17124d2b-6090-4cea-8711-a331713d50a5", over-tls=true, tls-host=cert.test, certificate=0, obfs=http, obfs-header="Host: 1.1.1.1[Rr][Nn]User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148" + +[SOURCE] + +[BACKUP-SERVER] + +[SUSPEND-SSID] + +[POLICY] +dXJsLXRlc3QgOiBhdXRvCnNzCnZtZXNzCg== +c2VsZWN0IDogc3RhdGljLCBzcwpzcwp2bWVzcwo= +bG9hZC1iYWxhbmNlIDogYmFsYW5jZSwgcm91bmQtcm9iaW4Kc3MKdm1lc3MK + +[DNS] + +[REWRITE] + +[URL-REJECTION] + +[TCP] +HOST-SUFFIX,googleapis.com,PROXY,resolve-on-proxy +HOST-SUFFIX,instagram.com,PROXY,resolve-on-proxy +HOST-SUFFIX,cdninstagram.com,PROXY,resolve-on-proxy +HOST-SUFFIX,fbcdn.net,PROXY,resolve-on-proxy +HOST-SUFFIX,facebook.com,PROXY,resolve-on-proxy +HOST-SUFFIX,fb.com,PROXY,resolve-on-proxy +HOST-SUFFIX,twitch.tv,PROXY,resolve-on-proxy +HOST-SUFFIX,gstatic.com,PROXY +HOST-KEYWORD,google,PROXY,resolve-on-proxy +IP-CIDR,10.0.0.0/8,DIRECT,no-resolve +IP-CIDR,127.0.0.0/8,DIRECT,no-resolve +IP-CIDR,172.16.0.0/12,DIRECT,no-resolve +IP-CIDR,192.168.0.0/16,DIRECT,no-resolve +GEOIP,CN,DIRECT +FINAL,PROXY + +[GLOBAL] +SELECTED,ss + +[HOST] + +[STATE] +STATE,AUTO + +[MITM] + diff --git a/Quan2Clash/assets/icon.png b/Quan2Clash/assets/icon.png new file mode 100644 index 0000000..c9685f4 Binary files /dev/null and b/Quan2Clash/assets/icon.png differ diff --git a/Quan2Clash/assets/quan_demo.gif b/Quan2Clash/assets/quan_demo.gif new file mode 100644 index 0000000..1a77031 Binary files /dev/null and b/Quan2Clash/assets/quan_demo.gif differ diff --git a/Quan2Clash/assets/thankyou2.jpg b/Quan2Clash/assets/thankyou2.jpg new file mode 100644 index 0000000..2cd5c60 Binary files /dev/null and b/Quan2Clash/assets/thankyou2.jpg differ diff --git a/Quan2Clash/config.json b/Quan2Clash/config.json new file mode 100644 index 0000000..312492c --- /dev/null +++ b/Quan2Clash/config.json @@ -0,0 +1,23 @@ +{ + "info": { + "name": "Quan2Clash", + "url": "", + "version": "1.0.1", + "author": "", + "website": "", + "types": 0 + }, + "settings": { + "minSDKVer": "1.0.0", + "minOSVer": "10.0.0", + "idleTimerDisabled": false, + "autoKeyboardEnabled": false, + "keyboardToolbarEnabled": false, + "rotateDisabled": false + }, + "widget": { + "height": 0, + "tintColor": "", + "iconColor": "" + } +} diff --git a/Quan2Clash/main.js b/Quan2Clash/main.js new file mode 100644 index 0000000..edaba7d --- /dev/null +++ b/Quan2Clash/main.js @@ -0,0 +1,71 @@ +const yaml = require('scripts/js-yaml') +const ini = require('scripts/ini') +const converter = require('scripts/converter') +const demoView = require('scripts/demoView') + +if ($app.env !== $env.action) { + $ui.menu({ + items: ['使用演示 🌄', '运行DEMO 🏖', '打赏作者 🍗'], + handler: (t, idx) => { + if (idx === 0) { + demoView.render() + } else if (idx === 1) { + main() + } else { + $app.openURL($qrcode.decode($file.read("assets/thankyou2.jpg").image)) + } + } + }) + return +} + +function main() { + let profileContent = $file.read("assets/demo.conf").string + + try { + profileContent = $context.dataItems[0].string + } catch (e) { + + } + + let iniObj = ini.parse(profileContent) + // console.log(iniObj.policies) + let clashObj = converter.convert(iniObj) + // console.log(clashObj['Proxy Group']) + + let server = $server.new() + + let serverOptions = { + port: 9090, + } + + server.addHandler({ + response: req => { + console.log('req:', req.url) + let fileName = "Quantumult" + if (/:9090\/(.+?)$/.test(req.url)) { + fileName = RegExp.$1 + } + return { + type: 'data', + props: { + text: yaml.dump(clashObj), + headers: { + "Content-Disposition": `attachment;filename="${fileName}.yml"` + } + } + } + } + }) + + server.listen({ + didStart: server => { + // console.log('server:', server) + $ui.alert(`转换成功\n\nhttp://${$device.wlanAddress}:${server.port}`) + } + }) + + server.start(serverOptions) +} + +main() \ No newline at end of file diff --git a/Quan2Clash/scripts/app.js b/Quan2Clash/scripts/app.js new file mode 100644 index 0000000..7a783f5 --- /dev/null +++ b/Quan2Clash/scripts/app.js @@ -0,0 +1,7 @@ +function sayHello() { + $ui.alert($l10n('HELLO_WORLD')); +} + +module.exports = { + sayHello: sayHello +} \ No newline at end of file diff --git a/Quan2Clash/scripts/converter.js b/Quan2Clash/scripts/converter.js new file mode 100644 index 0000000..4851263 --- /dev/null +++ b/Quan2Clash/scripts/converter.js @@ -0,0 +1,206 @@ +function convert(iniObj) { + let filters = iniObj.filters + let servers = iniObj.servers + let policies = iniObj.policies + + let clashFilters = filterModify(filters) + let clashServers = serverModify(servers).filter(s => s) + + let serverNames = clashServers.map(server => server.name).concat(['DIRECT', 'REJECT']) + let policyNames = policies.map(policy => { + if (/(.*?):/.test(policy)) { + return RegExp.$1.trim() + } + }) + let clashPolicies = policyModify(policies, serverNames.concat(policyNames)) + let hasDefaultProxy = clashFilters.find(f => /,\s*PROXY$/.test(f)) + if (hasDefaultProxy) { + clashPolicies.unshift({ + type: 'select', + name: 'PROXY', + proxies: serverNames + }) + } + + return { + "Proxy": clashServers, + "Proxy Group": sortPolicies(clashPolicies), + "Rule": clashFilters + } +} + +// 拓扑排序 +function sortPolicies(p) { + let policies = JSON.parse(JSON.stringify(p)) + // 计算节点度 + function pn(polices, policy) { + return polices.filter(p => p.proxies.indexOf(policy.name) > -1).length + } + + let result = [] + while (policies.length > 0) { + let nodeIdx = policies.findIndex(p => pn(policies, p) === 0) + result.unshift(policies[nodeIdx]) + policies.splice(nodeIdx, 1) + } + + return result +} + +function serverModify(servers) { + return servers.map(server => { + if (/(.*?)=\s*shadowsocks\s*,(.*)/.test(server)) { + return clashShadowsocks(RegExp.$1, RegExp.$2) + } else if (/(.*?)=\s*vmess\s*,(.*)/.test(server)) { + return clashVmess(RegExp.$1, RegExp.$2) + } + return null + }) +} + +function clashShadowsocks(name, paramsStr) { + try { + let params = paramsStr.split(/,/).map(i => i.trim()) + let ssObj = { + name: name.trim(), + type: 'ss', + server: params[0], + port: params[1], + cipher: params[2], + password: params[3].replace(/"/g, '') + } + let obfsType = paramByKey('obfs', params) + if (obfsType) { + ssObj['plugin'] = 'obfs' + ssObj['plugin-opts'] = { + "mode": obfsType, + "host": paramByKey("obfs-host", params) || 'bing.com' + } + } + return ssObj + } catch (e) { + console.error(`Shadowsocks "${name}" could not be converted!`) + return null + } +} + +function clashVmess(name, paramsStr) { + try { + let params = paramsStr.split(/,/).map(i => i.trim()) + let cipher = params[2] + if (cipher === 'chacha20-ietf-poly1305') { + cipher = 'chacha20-poly1305' + } else if (cipher === 'aes-128-cfb') { + cipher = 'auto' + } + let vmessObj = { + name: name.trim(), + type: 'vmess', + server: params[0], + port: params[1], + cipher: cipher, + uuid: params[3].replace(/"/g, ''), + alterId: 0 + } + let overTls = paramByKey("over-tls", params) + if (overTls === 'true') { + vmessObj['tls'] = true + } + let skipCert = paramByKey("certificate", params) + if (skipCert) { + vmessObj['skip-cert-verify'] = skipCert === '0' + } + let obfs = paramByKey('obfs', params) + if (obfs === 'ws') { + vmessObj['network'] = 'ws' + vmessObj['ws-path'] = paramByKey('obfs-path', params) || '/path' + let headersStr = paramByKey('obfs-header', params) + // console.log('headersStr:', headersStr) + if (headersStr) { + try { + vmessObj['ws-headers'] = convertHeaders(headersStr) + } catch (e) { + vmessObj['ws-headers'] = { "Host": "v2ray.com" } + } + } + } + return vmessObj + } catch (e) { + console.error(e.stack) + console.error(`Shadowsocks "${name}" could not be converted!`) + return null + } +} + +function convertHeaders(str) { + let ps = str.replace(/(^"|"$)/, '').split('[Rr][Nn]') + let obj = {} + ps.forEach(p => { + if (/(.*?):(.+)/.test(p)) { + obj[RegExp.$1.trim()] = RegExp.$2.trim() + } + }) + return obj +} + +function paramByKey(key, params) { + let param = params.find(p => { + let sp = p.split('=') + if (sp.length === 2 && sp[0].trim() === key.trim()) { + return true + } + return false + }) + if (param) { + return param.split("=")[1] + } + return null +} + +function policyModify(policies, serverNames) { + return policies.map(policy => { + if (/(.*?):\s*auto\s*\n([\s\S]+)/.test(policy)) { + return { + name: RegExp.$1.trim(), + type: "url-test", + proxies: RegExp.$2.split(/\n/).filter(i => i !== '' && serverNames.indexOf(i) > -1), + url: "http://www.gstatic.com/generate_204", + interval: 300 + } + } else if (/(.*?):\s*static\s*,\s*(.*?)\s*\n([\s\S]+)/.test(policy)) { + return { + name: RegExp.$1.trim(), + type: 'select', + proxies: [RegExp.$2, ...RegExp.$3.split('\n').filter(i => i !== '' && i !== RegExp.$2 && serverNames.indexOf(i) > -1)] + } + } else if (/(.*?):\s*balance\s*,\s*round-robin\s*\n([\s\S]+)/.test(policy)) { + return { + name: RegExp.$1.trim(), + type: 'load-balance', + proxies: RegExp.$2.split(/\n/).filter(i => i && serverNames.indexOf(i) > -1), + url: "http://www.gstatic.com/generate_204", + interval: 300 + } + } + console.error(`"${policy}" could not be converted!`) + return null + }).filter(i => i) +} + +function filterModify(filters) { + return filters.map(filter => { + if (/FINAL,([^,]+)/.test(filter)) { + return "MATCH," + RegExp.$1 + } else if (/HOST(\-*[^,]*?),([^,]+?),([^,]+)/.test(filter)) { + return `DOMAIN${RegExp.$1},${RegExp.$2},${RegExp.$3}` + } else if (/(IP-CIDR|GEOIP)\s*,([^,]+?),([^,]+)/.test(filter)) { + return `${RegExp.$1},${RegExp.$2},${RegExp.$3}` + } + console.error(`"${filter}" could not be converted!`) + return null + }).filter(i => i) +} + +module.exports = { + convert +} \ No newline at end of file diff --git a/Quan2Clash/scripts/demoView.js b/Quan2Clash/scripts/demoView.js new file mode 100644 index 0000000..f48bd32 --- /dev/null +++ b/Quan2Clash/scripts/demoView.js @@ -0,0 +1,21 @@ +module.exports.render = () => { + $ui.render( + { + type: 'view', + props: { + id: '' + }, + layout: $layout.fill, + events: {}, + views: [{ + type: "image", + props: { + data: $data({ + path: "assets/quan_demo.gif" + }) + }, + layout: $layout.fill + }] + } + ) +} \ No newline at end of file diff --git a/Quan2Clash/scripts/ini.js b/Quan2Clash/scripts/ini.js new file mode 100644 index 0000000..1450764 --- /dev/null +++ b/Quan2Clash/scripts/ini.js @@ -0,0 +1,32 @@ +function parse(str) { + let tcpSec = getSec('TCP', str) + let filters = seperateLine(tcpSec) + + let serverSec = getSec('SERVER', str) + let servers = seperateLine(serverSec) + + let policySec = getSec('POLICY', str) + let policies = seperateLine(policySec).map(p => $text.base64Decode(p)) + + return { + filters, + servers, + policies + } +} + +function seperateLine(str) { + return str.split(/[\n\r]/).filter(i => i) +} + +function getSec(name, content) { + let regStr = `\\[${name}\\]([\\S\\s]*?)(?:\n\\[|$)` + let reg = new RegExp(regStr) + if (reg.test(content)) { + return RegExp.$1 + } +} + +module.exports = { + parse +} \ No newline at end of file diff --git a/Quan2Clash/scripts/js-yaml.js b/Quan2Clash/scripts/js-yaml.js new file mode 100644 index 0000000..0ba1dc3 --- /dev/null +++ b/Quan2Clash/scripts/js-yaml.js @@ -0,0 +1 @@ +!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).jsyaml=e()}}(function(){return function o(a,s,c){function u(t,e){if(!s[t]){if(!a[t]){var n="function"==typeof require&&require;if(!e&&n)return n(t,!0);if(l)return l(t,!0);var i=new Error("Cannot find module '"+t+"'");throw i.code="MODULE_NOT_FOUND",i}var r=s[t]={exports:{}};a[t][0].call(r.exports,function(e){return u(a[t][1][e]||e)},r,r.exports,o,a,s,c)}return s[t].exports}for(var l="function"==typeof require&&require,e=0;e=i.flowLevel;switch(H(r,n,i.indent,t,function(e){return function(e,t){var n,i;for(n=0,i=e.implicitTypes.length;n"+V(r,i.indent)+Z(L(function(t,n){var e,i,r=/(\n+)([^\n]*)/g,o=function(){var e=t.indexOf("\n");return e=-1!==e?e:t.length,r.lastIndex=e,z(t.slice(0,e),n)}(),a="\n"===t[0]||" "===t[0];for(;i=r.exec(t);){var s=i[1],c=i[2];e=" "===c[0],o+=s+(a||e||""===c?"":"\n")+z(c,n),a=e}return o}(r,t),e));case $:return'"'+function(e){for(var t,n,i,r="",o=0;ot&&o tag resolver accepts not "'+c+'" style');i=s.represent[c](t,c)}e.dump=i}return!0}return!1}function Q(e,t,n,i,r,o){e.tag=null,e.dump=n,J(e,n,!1)||J(e,n,!0);var a=p.call(e.dump);i&&(i=e.flowLevel<0||e.flowLevel>t);var s,c,u="[object Object]"===a||"[object Array]"===a;if(u&&(c=-1!==(s=e.duplicates.indexOf(n))),(null!==e.tag&&"?"!==e.tag||c||2!==e.indent&&0 "+e.dump)}return!0}function X(e,t){var n,i,r=[],o=[];for(function e(t,n,i){var r,o,a;if(null!==t&&"object"==typeof t)if(-1!==(o=n.indexOf(t)))-1===i.indexOf(o)&&i.push(o);else if(n.push(t),Array.isArray(t))for(o=0,a=t.length;o>10),56320+(e-65536&1023))}for(var M=new Array(256),T=new Array(256),d=0;d<256;d++)M[d]=f(d)?1:0,T[d]=f(d);function h(e,t){this.input=e,this.filename=t.filename||null,this.schema=t.schema||a,this.onWarning=t.onWarning||null,this.legacy=t.legacy||!1,this.json=t.json||!1,this.listener=t.listener||null,this.implicitTypes=this.schema.compiledImplicit,this.typeMap=this.schema.compiledTypeMap,this.length=e.length,this.position=0,this.line=0,this.lineStart=0,this.lineIndent=0,this.documents=[]}function m(e,t){return new i(t,new r(e.filename,e.input,e.position,e.line,e.position-e.lineStart))}function L(e,t){throw m(e,t)}function D(e,t){e.onWarning&&e.onWarning.call(null,m(e,t))}var U={YAML:function(e,t,n){var i,r,o;null!==e.version&&L(e,"duplication of %YAML directive"),1!==n.length&&L(e,"YAML directive accepts exactly one argument"),null===(i=/^([0-9]+)\.([0-9]+)$/.exec(n[0]))&&L(e,"ill-formed argument of the YAML directive"),r=parseInt(i[1],10),o=parseInt(i[2],10),1!==r&&L(e,"unacceptable YAML version of the document"),e.version=n[0],e.checkLineBreaks=o<2,1!==o&&2!==o&&D(e,"unsupported YAML version of the document")},TAG:function(e,t,n){var i,r;2!==n.length&&L(e,"TAG directive accepts exactly two arguments"),i=n[0],r=n[1],l.test(i)||L(e,"ill-formed tag handle (first argument) of the TAG directive"),y.call(e.tagMap,i)&&L(e,'there is a previously declared suffix for "'+i+'" tag handle'),p.test(r)||L(e,"ill-formed tag prefix (second argument) of the TAG directive"),e.tagMap[i]=r}};function q(e,t,n,i){var r,o,a,s;if(tt)&&0!==i)L(e,"bad indentation of a sequence entry");else if(e.lineIndentt?d=1:e.lineIndent===t?d=0:e.lineIndentt?d=1:e.lineIndent===t?d=0:e.lineIndentt)&&(V(e,t,b,!0,r)&&(m?d=e.result:h=e.result),m||(R(e,l,p,f,d,h,o,a),f=d=h=null),P(e,!0,-1),s=e.input.charCodeAt(e.position)),e.lineIndent>t&&0!==s)L(e,"bad indentation of a mapping entry");else if(e.lineIndentu&&(u=e.lineIndent),j(o))l++;else{if(e.lineIndent=t){a=!0,l=e.input.charCodeAt(e.position);continue}e.position=o,e.line=s,e.lineStart=c,e.lineIndent=u;break}}a&&(q(e,r,o,!1),K(e,e.line-s),r=o=e.position,a=!1),I(l)||(o=e.position+1),l=e.input.charCodeAt(++e.position)}return q(e,r,o,!1),!!e.result||(e.kind=p,e.result=f,!1)}(e,p,x===n)&&(m=!0,null===e.tag&&(e.tag="?")):(m=!0,null===e.tag&&null===e.anchor||L(e,"alias node should not have any properties")),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):0===d&&(m=s&&$(e,f))),null!==e.tag&&"!"!==e.tag)if("?"===e.tag){for(c=0,u=e.implicitTypes.length;c tag; it should be "'+l.kind+'", not "'+e.kind+'"'),l.resolve(e.result)?(e.result=l.construct(e.result),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):L(e,"cannot resolve a node with !<"+e.tag+"> explicit tag")):L(e,"unknown tag !<"+e.tag+">");return null!==e.listener&&e.listener("close",e),null!==e.tag||null!==e.anchor||m}function Z(e){var t,n,i,r,o=e.position,a=!1;for(e.version=null,e.checkLineBreaks=e.legacy,e.tagMap={},e.anchorMap={};0!==(r=e.input.charCodeAt(e.position))&&(P(e,!0,-1),r=e.input.charCodeAt(e.position),!(0t/2-1){n=" ... ",i+=5;break}for(r="",o=this.position;ot/2-1){r=" ... ",o-=5;break}return a=this.buffer.slice(i,o),s.repeat(" ",e)+n+a+r+"\n"+s.repeat(" ",e+this.position-i+n.length)+"^"},i.prototype.toString=function(e){var t,n="";return this.name&&(n+='in "'+this.name+'" '),n+="at line "+(this.line+1)+", column "+(this.column+1),e||(t=this.getSnippet())&&(n+=":\n"+t),n},t.exports=i},{"./common":2}],7:[function(e,t,n){"use strict";var i=e("./common"),r=e("./exception"),o=e("./type");function a(e,t,i){var r=[];return e.include.forEach(function(e){i=a(e,t,i)}),e[t].forEach(function(n){i.forEach(function(e,t){e.tag===n.tag&&e.kind===n.kind&&r.push(t)}),i.push(n)}),i.filter(function(e,t){return-1===r.indexOf(t)})}function s(e){this.include=e.include||[],this.implicit=e.implicit||[],this.explicit=e.explicit||[],this.implicit.forEach(function(e){if(e.loadKind&&"scalar"!==e.loadKind)throw new r("There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported.")}),this.compiledImplicit=a(this,"implicit",[]),this.compiledExplicit=a(this,"explicit",[]),this.compiledTypeMap=function(){var e,t,n={scalar:{},sequence:{},mapping:{},fallback:{}};function i(e){n[e.kind][e.tag]=n.fallback[e.tag]=e}for(e=0,t=arguments.length;e>16&255),s.push(a>>8&255),s.push(255&a)),a=a<<6|o.indexOf(i.charAt(t));return 0==(n=r%4*6)?(s.push(a>>16&255),s.push(a>>8&255),s.push(255&a)):18==n?(s.push(a>>10&255),s.push(a>>2&255)):12==n&&s.push(a>>4&255),c?c.from?c.from(s):new c(s):s},predicate:function(e){return c&&c.isBuffer(e)},represent:function(e){var t,n,i="",r=0,o=e.length,a=u;for(t=0;t>18&63],i+=a[r>>12&63],i+=a[r>>6&63],i+=a[63&r]),r=(r<<8)+e[t];return 0==(n=o%3)?(i+=a[r>>18&63],i+=a[r>>12&63],i+=a[r>>6&63],i+=a[63&r]):2==n?(i+=a[r>>10&63],i+=a[r>>4&63],i+=a[r<<2&63],i+=a[64]):1==n&&(i+=a[r>>2&63],i+=a[r<<4&63],i+=a[64],i+=a[64]),i}})},{"../type":13}],15:[function(e,t,n){"use strict";var i=e("../type");t.exports=new i("tag:yaml.org,2002:bool",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t=e.length;return 4===t&&("true"===e||"True"===e||"TRUE"===e)||5===t&&("false"===e||"False"===e||"FALSE"===e)},construct:function(e){return"true"===e||"True"===e||"TRUE"===e},predicate:function(e){return"[object Boolean]"===Object.prototype.toString.call(e)},represent:{lowercase:function(e){return e?"true":"false"},uppercase:function(e){return e?"TRUE":"FALSE"},camelcase:function(e){return e?"True":"False"}},defaultStyle:"lowercase"})},{"../type":13}],16:[function(e,t,n){"use strict";var i=e("../common"),r=e("../type"),o=new RegExp("^(?:[-+]?(?:0|[1-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*|[-+]?\\.(?:inf|Inf|INF)|\\.(?:nan|NaN|NAN))$");var a=/^[-+]?[0-9]+e/;t.exports=new r("tag:yaml.org,2002:float",{kind:"scalar",resolve:function(e){return null!==e&&!(!o.test(e)||"_"===e[e.length-1])},construct:function(e){var t,n,i,r;return n="-"===(t=e.replace(/_/g,"").toLowerCase())[0]?-1:1,r=[],0<="+-".indexOf(t[0])&&(t=t.slice(1)),".inf"===t?1==n?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY:".nan"===t?NaN:0<=t.indexOf(":")?(t.split(":").forEach(function(e){r.unshift(parseFloat(e,10))}),t=0,i=1,r.forEach(function(e){t+=e*i,i*=60}),n*t):n*parseFloat(t,10)},predicate:function(e){return"[object Number]"===Object.prototype.toString.call(e)&&(e%1!=0||i.isNegativeZero(e))},represent:function(e,t){var n;if(isNaN(e))switch(t){case"lowercase":return".nan";case"uppercase":return".NAN";case"camelcase":return".NaN"}else if(Number.POSITIVE_INFINITY===e)switch(t){case"lowercase":return".inf";case"uppercase":return".INF";case"camelcase":return".Inf"}else if(Number.NEGATIVE_INFINITY===e)switch(t){case"lowercase":return"-.inf";case"uppercase":return"-.INF";case"camelcase":return"-.Inf"}else if(i.isNegativeZero(e))return"-0.0";return n=e.toString(10),a.test(n)?n.replace("e",".e"):n},defaultStyle:"lowercase"})},{"../common":2,"../type":13}],17:[function(e,t,n){"use strict";var i=e("../common"),r=e("../type");function o(e){return 48<=e&&e<=57||65<=e&&e<=70||97<=e&&e<=102}function a(e){return 48<=e&&e<=55}function s(e){return 48<=e&&e<=57}t.exports=new r("tag:yaml.org,2002:int",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t,n=e.length,i=0,r=!1;if(!n)return!1;if("-"!==(t=e[i])&&"+"!==t||(t=e[++i]),"0"===t){if(i+1===n)return!0;if("b"===(t=e[++i])){for(i++;i { + const SI = screenInfo() + $ui.render({ + props: { + title: "V2Ray Tools", + id: "mainView" + }, + views: [{ + type: "list", + props: { + id: "mainList", + bgcolor: $color("clear"), + separatorHidden: true, + rowHeight: 90, + data: loadDBData(), + actions: [{ + title: "Remove", + color: $color("#e10601"), + handler: (sender, indexPath) => { + let item = sender.object(indexPath) + console.log('item', item); + let res = db.update({ + sql: "DELETE FROM subInfo WHERE rowid = ?", + args: [item.raw.rowid] + }) + if (res.result) { + sender.delete(indexPath) + } + } + }, { + title: "Edit", + handler: (sender, indexPath) => { + let item = sender.object(indexPath) + showAlterDialog(item.raw.title, item.raw.method, item.raw.url,(view, group, method, url) => { + console.log(group, method, url) + if (url) { + let res = db.update({ + sql: 'UPDATE subInfo SET title = ?, method = ?, url = ? WHERE rowid = ?', + args: [group || 'Fndroid', method || 'chacha20-ietf-poly1305', url, item.raw.rowid] + }) + if (res.result) { + view.remove() + $("mainList").data = loadDBData() + } else { + $ui.alert('Something happened, please try again.') + } + } else { + $ui.alert('URL is required!') + } + }) + } + }], + template: { + props: { + bgcolor: $color("clear") + }, + views: [{ + type: "label", + props: { + id: "group", + // font: $font(20), + }, + layout: (make, view) => { + make.left.equalTo(view.super).offset(SI.padding) + make.top.equalTo(view.super).offset(10) + } + }, { + type: "label", + props: { + id: "method", + }, + layout: (make, view) => { + make.left.equalTo(view.super).offset(SI.padding) + make.top.equalTo(view.prev.bottom).offset(5) + } + }, { + type: "label", + props: { + id: "url" + }, + layout: (make, view) => { + make.left.equalTo(view.super).offset(SI.padding) + make.top.equalTo(view.prev.bottom).offset(5) + } + }] + } + }, + layout: $layout.fill, + events: { + didSelect: async (sender, indexPath, data) => { + $ui.loading(true) + let url = data.raw.url + let rawLinks = null + if (/^http/.test(url)) { + let resp = await $http.get(url) + let respData = resp.data + // 兼容不规范ssr链接 + let noPaddingData = respData + let padding = noPaddingData.length % 4 == 0 ? 0 : 4 - noPaddingData.length % 4 + for (let i = 0; i < padding; i++) { + noPaddingData += '=' + } + let decodedData = $text.base64Decode(respData) || $text.base64Decode(noPaddingData) + rawLinks = decodedData.split(/[\n\r\|\s]+/g).filter(i => i !== '' && /^vmess:\/\//.test(i)); + } else if (/^vmess/.test(url)) { + rawLinks = [url] + } + let links = decodeVmess(rawLinks, data.raw.title, data.raw.method); + $clipboard.text = links.map(i => `vmess://${urlsafeBase64Encode(i)}`).join('\n') + $ui.loading(false) + $ui.alert({ + title: "识别完成", + message: `共计识别到${links.length}个V2Ray链接,已复制至剪贴板,请在Quantumult中通过URI导入`, + actions: [{ + title: "Cancle", + handler: () => {} + }, { + title: "Quantumult", + handler: () => { + $app.openURL("quantumult://") + } + }] + }) + + } + } + }, { + type: "button", + props: { + icon: $icon("104", $color("white"), $size(20, 20)), + bgcolor: $color("tint"), + radius: 25 + }, + layout: (make, view) => { + make.height.width.equalTo(50) + make.bottom.equalTo(SI.screenHeight).offset(-20) + make.right.equalTo(view.super).offset(-20) + }, + events: { + tapped: sender => { + showAlterDialog('', '', '',(view, group, method, url) => { + console.log(group, method, url) + if (url) { + let res = db.update({ + sql: 'INSERT INTO subInfo VALUES(?, ?, ?)', + args: [group || 'Fndroid', method || 'chacha20-ietf-poly1305', url] + }) + if (res.result) { + view.remove() + $("mainList").data = loadDBData() + } else { + $ui.alert('数据保存失败,请重试!') + } + } else { + $ui.alert('链接不能为空!') + } + }) + } + } + }] + }) +} + +function loadDBData() { + let res = db.query({ + sql: 'SELECT rowid, * from subInfo' + }); + let result = res.result; + let listData = []; + while (result.next()) { + let values = result.values; + let domain = '' + if (/(https?:\/\/.*?)(?:\/|$)/.test(values.url)) { + domain = `${RegExp.$1}/※※※` + } else if (/^vmess:/.test(values.url)) { + + domain = 'vmess://※※※' + } + let itemData = { + raw: values, + group: { text: `分组:${values.title}` }, + method: { text: `加密:${values.method}` }, + url: { text: `链接:${domain}` } + }; + listData.push(itemData); + } + return listData; +} + +function decodeVmess(links, group, method) { + let result = [] + + for (let idx in links) { + let link = links[idx] + + if (/^vmess:\/\/(.*?)$/.test(link)) { + let content = urlsafeBase64Decode(RegExp.$1) + if (isJson(content)) { + // v2rayN style + let jsonConf = JSON.parse(content) + const ua = 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/16A5366a' + let obfs = `,obfs=${jsonConf.net === 'ws' ? 'ws' : 'http'},obfs-path="${jsonConf.path || '/'}",obfs-header="Host:${jsonConf.host || jsonConf.add}[Rr][Nn]User-Agent:${ua}"` + let quanVmess = `${jsonConf.ps} = vmess,${jsonConf.add},${jsonConf.port},${method},"${jsonConf.id}",group=${group},over-tls=${jsonConf.tls === 'tls' ? 'true' : 'false'},certificate=1${jsonConf.type === 'none' && jsonConf.net !== 'ws' ? '' : obfs}` + result.push(quanVmess) + } else { + // Quantumult style + result.push(content) + } + } + } + return result +} + +function isJson(str) { + try { + JSON.parse(str) + } catch(e) { + return false + } + return true +} + +function urlsafeBase64Decode(base64) { + // Add removed at end '=' + base64 += Array(5 - base64.length % 4).join('='); + base64 = base64 + .replace(/\-/g, '+') // Convert '-' to '+' + .replace(/\_/g, '/'); // Convert '_' to '/' + return $text.base64Decode(base64).replace(/\u0000/, ''); +} + +function urlsafeBase64Encode(url) { + return $text.base64Encode(url).replace(/\-/g, '+').replace(/\\/g, '_').replace(/=+$/, '') +} + +function showAlterDialog(group, method, url, callback) { + 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(350); + make.width.equalTo(view.super).offset(-60); + make.center.equalTo(view.super) + }, + events: { + tapped: sender => { } + }, + views: [{ + type: "label", + props: { + text: "Group", + 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: "alterInputGroup", + placeholder: "Fndroid", + text: group, + 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: "Method", + font: $font("bold", 16) + }, + layout: (make, view) => { + make.top.equalTo(view.prev.bottom).offset(15); + make.left.equalTo(view.super).offset(10); + } + }, { + type: "input", + props: { + id: "alberInputMethod", + text: method, + placeholder: "chacha20-ietf-poly1305", + autoFontSize: true + }, + events: { + tapped: sender => { + $("methodView").hidden = false + } + }, + 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: "URL", + font: $font("bold", 16) + }, + layout: (make, view) => { + make.top.equalTo(view.prev.bottom).offset(15); + make.left.equalTo(view.super).offset(10); + } + }, { + type: "input", + props: { + id: "alberInputURL", + text: url, + placeholder: "Required, https:// or vmess://", + 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: '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(-15) + make.right.equalTo(view.super).offset(-15) + }, + events: { + tapped: sender => { + callback && callback($("alertBody"), $("alterInputGroup").text, $("alberInputMethod").text, $("alberInputURL").text); + $("alertBody").remove(); + } + } + }, { + type: "button", + props: { + icon: $icon("018", $color("#fff"), $size(20, 20)), + id: 'cameraBtn', + radius: 25 + }, + layout: (make, view) => { + make.height.width.equalTo(50) + make.bottom.equalTo(view.super).offset(-15) + make.left.equalTo(view.super).offset(15) + }, + events: { + tapped: async sender => { + let qr = await $qrcode.scan({}) + if (qr) { + $("alberInputURL").text = qr + } + } + } + }, { + type: "view", + props: { + id: "methodView", + hidden: true, + bgcolor: $color("#dcdcdc") + }, + views: [{ + type: "label", + props: { + text: "确认", + font: $font("bold", 16) + }, + layout: (make, view) => { + make.bottom.equalTo(view.super).offset(-10) + make.right.equalTo(view.super).offset(-20) + make.height.equalTo(40) + }, + events: { + tapped: sender => { + const methods = ['none', 'aes-128-cfb', 'aes-128-gcm', 'chacha20-ietf-poly1305'] + let selectedRows = $("methodPicker").selectedRows[0] + $("alberInputMethod").text = methods[selectedRows] + $("methodView").hidden = true + } + } + }, { + type: "picker", + props: { + id: "methodPicker", + items: [['none', 'aes-128-cfb', 'aes-128-gcm', 'chacha20-ietf-poly1305']] + }, + layout: (make, view) => { + make.bottom.equalTo(view.prev.top) + make.height.equalTo(view.super).offset(-50) + make.width.equalTo(view.super) + } + }], + layout: $layout.fill + }], + }], + events: { + tapped: sender => { + sender.remove() + } + } + } + $("mainView").add(view) + $ui.animate({ + duration: 0.2, + animation: () => { + $("alertBody").alpha = 1 + } + }) +} + +let screenInfo = () => { + let screenHeight = $device.info.screen.height + const screenWidth = $device.info.screen.width + + const iPhoneX = screenWidth == 375 && screenHeight == 812 + if (iPhoneX) { + screenHeight -= 48 + } + + const statusBarHeight = iPhoneX ? 44 : 20 + const navBarHeight = 45 + return { + screenHeight: screenHeight, + screenWidth: screenWidth, + statusBarHeight: statusBarHeight, + navBarHeight: navBarHeight, + padding: 10 + } +} + +render() diff --git a/QuantumultNoFA.js b/QuantumultNoFA.js new file mode 100644 index 0000000..54dd1f0 --- /dev/null +++ b/QuantumultNoFA.js @@ -0,0 +1,335 @@ +/* + +说明: + +此脚本(QuantumultNoFA)仅用于更新Quantumult的Filter部分,可以免去每次Update都要设置的麻烦 + +1. 格式:正则(选择部分) = 替换(节点或Policy名) +2. 如果替换部分在Quantumult不存在,则默认是PROXY + +例如: + +把默认的PROXY统统改为“🍃 Proxy”,可以这么写: +,(?:PROXY|Proxy) = ,🍃 Proxy + +*/ + +$app.autoKeyboardEnabled = true + +const CONF = 'https://raw.githubusercontent.com/lhie1/Rules/master/Quantumult/Quantumult.conf' + +const CACHEKEY = 'listData' + +function urlsaveBase64Encode(url) { + return $text.base64Encode(url).replace(/\-/g, '+').replace(/\\/g, '_').replace(/=+$/, '') +} + +const DEFAULT = [{ + reg: { text: "选择Google的Policy,不懂就不选" }, + rep: { text: "PROXY" } +}, { + reg: { text: "选择微软服务的Policy,不懂就选择DIRECT" }, + rep: { text: "DIRECT" } +}, { + reg: { text: "选择PayPal的Policy,不懂就选择DIRECT" }, + rep: { text: "DIRECT" } +}, { + reg: { text: "选择Apple的Policy,不懂就选择DIRECT" }, + rep: { text: "DIRECT" } +}, { + reg: { text: "选择Netflix的Policy,不懂就不选" }, + rep: { text: "PROXY" } +}, { + reg: { text: ",\s*(?:PROXY|Proxy)" }, + rep: { text: ",PROXY" } +}, { + reg: { text: ",DIRECT" }, + rep: { text: ",DIRECT" } +}, { + reg: { text: ",REJECT" }, + rep: { text: ",REJECT" } +}] + +let cacheData = $cache.get(CACHEKEY) + +$ui.render({ + props: { + title: "Quantumult", + navBarHidden: true, + statusBarStyle: 0 + }, + views: [{ + type: "view", + props: { + id: "mainView" + }, + layout: $layout.fill, + views: [{ + type: 'list', + props: { + reorder: true, + data: cacheData || DEFAULT, + rowHeight: 60, + id: "mainList", + actions: [{ + title: "delete", + handler: (sender, indexPath) => { + $cache.set(CACHEKEY, sender.data) + } + }], + footer: { + type: "view", + props: { + height: 65 + } + }, + template: { + props: {}, + views: [{ + type: "label", + props: { + id: "reg", + font: $font("bold", 16) + }, + layout: (make, view) => { + make.height.equalTo(view.super.height).multipliedBy(0.5); + make.width.equalTo(view.super).offset(-30); + make.left.equalTo(view.super).offset(15); + make.top.equalTo(view.super); + } + }, { + type: "label", + props: { + id: "rep", + font: $font("bold", 16), + textColor: $color("#777") + }, + layout: (make, view) => { + make.height.equalTo(view.super.height).multipliedBy(0.5); + make.width.equalTo(view.super).offset(-30); + make.left.equalTo(view.super).offset(15); + make.top.equalTo(view.prev.bottom) + } + }] + } + }, + layout: (make, view) => { + make.height.equalTo(view.super).offset(-20) + make.width.equalTo(view.super) + make.centerX.equalTo(view.super) + make.top.equalTo(20) + }, + events: { + didSelect: (sender, indexPath, data) => { + let item = sender.object(indexPath) + showAlterDialog(item.reg.text, item.rep.text, (newReg, newRep) => { + console.log(newReg, newRep) + let oldData = sender.data; + oldData[indexPath.row] = { + reg: { text: newReg }, + rep: { text: newRep } + } + sender.data = oldData + $cache.set(CACHEKEY, sender.data) + }) + }, + reorderFinished: function (data) { + $cache.set(CACHEKEY, data) + } + } + }, { + type: 'button', + props: { + icon: $icon("165", $color("#fff"), $size(20, 20)), + id: 'genBtn', + radius: 25 + }, + layout: (make, view) => { + make.height.width.equalTo(50) + make.bottom.right.equalTo(view.super).offset(-15) + }, + events: { + tapped: async _ => { + let pattern = $("mainList").data + try { + await startGen(pattern) + } catch (e) { + console.error(e.stack) + } + } + } + }, { + type: 'button', + props: { + icon: $icon("104", $color("#fff"), $size(20, 20)), + id: 'addBtn', + radius: 25 + }, + layout: (make, view) => { + make.height.width.equalTo(50) + make.bottom.equalTo(view.super).offset(-15) + make.right.equalTo(view.prev.left).offset(-15) + }, + events: { + tapped: sender => { + showAlterDialog('', '', (newReg, newRep) => { + let oldData = $("mainList").data; + oldData.push({ + reg: { text: newReg }, + rep: { text: newRep } + }) + $("mainList").data = oldData; + $cache.set(CACHEKEY, $("mainList").data) + }) + } + } + }] + }] +}) + +function showAlterDialog(reg, rep, callback) { + 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(250); + 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: "alterInputReg", + 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: "input", + props: { + id: "alberInputRep", + text: rep, + 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: '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(-15) + make.right.equalTo(view.super).offset(-15) + }, + events: { + tapped: sender => { + callback && callback($("alterInputReg").text, $("alberInputRep").text); + $("alertBody").remove(); + } + } + }] + }], + events: { + tapped: sender => { + sender.remove() + } + } + } + $("mainView").add(view) + $ui.animate({ + duration: 0.2, + animation: () => { + $("alertBody").alpha = 1 + } + }) +} + +async function startGen(pattern) { + let resp = await $http.get(CONF) + let data = resp.data + + let patterns = pattern.map(i => { + return [i.reg.text, i.rep.text] + }) + + patterns.forEach(i => { + data = data.replace(new RegExp(i[0], 'g'), i[1]) + }) + + console.log(data) + + var success = $file.write({ + data: $data({ string: data }), + path: "Fndroid.conf" + }) + if (success) { + let result = await $http.startServer({}) + let url = `http://127.0.0.1:${result.port}/download?path=Fndroid.conf` + let test = await $http.get(url) + const REJECT = 'https://raw.githubusercontent.com/lhie1/Rules/master/Quantumult/Quantumult_URL.conf' + if (test.data) { + let scheme = `quantumult://configuration?filter=${urlsaveBase64Encode(url)}&rejection=${urlsaveBase64Encode(REJECT)}` + $app.openURL(scheme) + } + } +} \ No newline at end of file diff --git a/QuantumultTools/.output/QuantumultTools.box b/QuantumultTools/.output/QuantumultTools.box new file mode 100644 index 0000000..f005bfc Binary files /dev/null and b/QuantumultTools/.output/QuantumultTools.box differ diff --git a/QuantumultTools/.vscode/launch.json b/QuantumultTools/.vscode/launch.json new file mode 100644 index 0000000..d7138da --- /dev/null +++ b/QuantumultTools/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "JSBox", + "port": 9229 + } + ] +} \ No newline at end of file diff --git a/QuantumultTools/README.md b/QuantumultTools/README.md new file mode 100644 index 0000000..e69de29 diff --git a/QuantumultTools/assets/icon.png b/QuantumultTools/assets/icon.png new file mode 100644 index 0000000..c9685f4 Binary files /dev/null and b/QuantumultTools/assets/icon.png differ diff --git a/QuantumultTools/config.json b/QuantumultTools/config.json new file mode 100644 index 0000000..b08e09e --- /dev/null +++ b/QuantumultTools/config.json @@ -0,0 +1,23 @@ +{ + "info": { + "name": "QuantumultTools", + "url": "", + "version": "1.0.1", + "author": "Fndroid", + "website": "", + "types": 0 + }, + "settings": { + "minSDKVer": "1.0.0", + "minOSVer": "10.0.0", + "idleTimerDisabled": false, + "autoKeyboardEnabled": false, + "keyboardToolbarEnabled": false, + "rotateDisabled": false + }, + "widget": { + "height": 0, + "tintColor": "", + "iconColor": "" + } +} diff --git a/QuantumultTools/main.js b/QuantumultTools/main.js new file mode 100644 index 0000000..d4be81d --- /dev/null +++ b/QuantumultTools/main.js @@ -0,0 +1,7 @@ +let socketLogger = require("socketLogger") +typeof socketLogger.init === 'function' && socketLogger.init('192.168.50.229') +// SocketLogger Auto Generation Code + +const homeView = require('scripts/homeView/view') + +homeView.render() \ No newline at end of file diff --git a/QuantumultTools/scripts/app.js b/QuantumultTools/scripts/app.js new file mode 100644 index 0000000..7a783f5 --- /dev/null +++ b/QuantumultTools/scripts/app.js @@ -0,0 +1,7 @@ +function sayHello() { + $ui.alert($l10n('HELLO_WORLD')); +} + +module.exports = { + sayHello: sayHello +} \ No newline at end of file diff --git a/QuantumultTools/scripts/homeView/view.js b/QuantumultTools/scripts/homeView/view.js new file mode 100644 index 0000000..a4f22ea --- /dev/null +++ b/QuantumultTools/scripts/homeView/view.js @@ -0,0 +1,104 @@ +const screenUtil = require('scripts/utils/screen') + +const v2raySubView = require('scripts/v2raySubView/view') + +function render() { + let screenInfo = screenUtil.screenInfo() + $ui.render({ + props: { + id: "bodyView", + navBarHidden: true, + bgcolor: $color("#2d2d2d") + }, + events: { + appeared: function (sender) { + $("bodyView").runtimeValue().$viewController().$navigationController().$interactivePopGestureRecognizer().$delegate() + $("bodyView").runtimeValue().$viewController().$navigationController().$interactivePopGestureRecognizer().$setDelegate(null) + } + }, + views: [{ + type: "view", + layout: (make, view) => { + make.height.equalTo(screenInfo.navBarHeight) + make.width.equalTo(view.super).offset(-(screenInfo.padding * 2)) + make.top.equalTo(screenInfo.statusBarHeight) + make.centerX.equalTo(view.super) + }, + views: [{ + type: "label", + props: { + text: "Quantumult工具箱", + font: $font("bold", 22), + textColor: $color("white") + }, + layout: (make, view) => { + make.centerY.equalTo(view.super) + make.left.equalTo(view.super).offset(screenInfo.padding) + } + }] + }, { + type: "view", + layout: (make, view) => { + make.height.equalTo(screenInfo.screenHeight - screenInfo.navBarHeight - screenInfo.statusBarHeight) + make.width.equalTo(view.super).offset(-(screenInfo.padding * 2)) + make.top.equalTo(view.prev.bottom) + make.centerX.equalTo(view.super) + }, + views: [{ + type: "list", + props: { + bgcolor: $color("clear"), + separatorHidden: true, + rowHeight: 70, + data: [{ + itemTitle: { text: "订阅转换" }, + itemDesc: { text: "识别v2rayN格式订阅" } + }, { + itemTitle: { text: "规则更新" }, + itemDesc: { text: "更新分流规则和链接阻止" } + }, { + itemTitle: { text: "Rules-lhie1" }, + itemDesc: { text: "节点和规则管理器" } + }], + template: { + props: { + bgcolor: $color("clear") + }, + views: [{ + type: "label", + props: { + id: "itemTitle", + font: $font(20), + textColor: $color("white") + }, + layout: (make, view) => { + make.left.equalTo(view.super).offset(screenInfo.padding) + make.top.equalTo(view.super).offset(10) + } + }, { + type: "label", + props: { + id: "itemDesc", + textColor: $color("#888888") + }, + layout: (make, view) => { + make.left.equalTo(view.super).offset(screenInfo.padding) + make.top.equalTo(view.prev.bottom).offset(5) + } + }] + } + }, + layout: $layout.fill, + events: { + didSelect: (sender, indexPath, data) => { + v2raySubView.push() + } + } + }] + }] + }) +} + +module.exports = { + render: render +} \ No newline at end of file diff --git a/QuantumultTools/scripts/utils/screen.js b/QuantumultTools/scripts/utils/screen.js new file mode 100644 index 0000000..6002a81 --- /dev/null +++ b/QuantumultTools/scripts/utils/screen.js @@ -0,0 +1,19 @@ +module.exports.screenInfo = function () { + let screenHeight = $device.info.screen.height + const screenWidth = $device.info.screen.width + + const iPhoneX = screenWidth == 375 && screenHeight == 812 + if (iPhoneX) { + screenHeight -= 48 + } + + const statusBarHeight = iPhoneX ? 44 : 20 + const navBarHeight = 45 + return { + screenHeight: screenHeight, + screenWidth: screenWidth, + statusBarHeight: statusBarHeight, + navBarHeight: navBarHeight, + padding: 10 + } +} \ No newline at end of file diff --git a/QuantumultTools/scripts/utils/update.js b/QuantumultTools/scripts/utils/update.js new file mode 100644 index 0000000..61093e9 --- /dev/null +++ b/QuantumultTools/scripts/utils/update.js @@ -0,0 +1,52 @@ + +const urlUtil = require('scripts/utils/url') +const promise = require('scripts/utils/promise') + +const GITHUB_RAW_PREFIX = 'https://raw.githubusercontent.com/Fndroid/jsbox_script/master/QuantumultTools' + +function currentVersion() { + return $file.read('version.fndroid').string +} + +async function latestVersion() { + let resp = await $http.get(urlUtil.withTS(`${GITHUB_RAW_PREFIX}/version.fndroid`)) + return resp.data +} + +async function latestBox() { + let resp = await $http.download({ + url: urlUtil.withTS(`${GITHUB_RAW_PREFIX}/.output/QuantumultTools.box`), + showsProgress: false + }) + return resp.data +} + +async function updateHandler() { + let lv = versionWeight(await latestVersion()) + let cv = versionWeight(currentVersion()) + if (lv > cv) { + let boxData = await latestBox() + $addin.save({ + name: $addin.current.name, + data: boxData, + handler: success => { + if (success) { + $ui.toast('新版本已成功安装,下次启动生效') + } + } + }) + } +} + +function versionWeight(versionStr) { + let pow = 1 + return versionStr.split(/\./g).reverse().reduce((w, c) => { + let s = w + c * pow + pow *= 100 + return s + }, 0) +} + +module.exports = { + updateHandler: updateHandler +} \ No newline at end of file diff --git a/QuantumultTools/scripts/utils/url.js b/QuantumultTools/scripts/utils/url.js new file mode 100644 index 0000000..886756c --- /dev/null +++ b/QuantumultTools/scripts/utils/url.js @@ -0,0 +1,13 @@ + +function withTS(url) { + let ts = new Date().getTime() + if (/\?/.test(url)) { + return `${url}&t=${ts}` + } else { + return `${url}?t=${ts}` + } +} + +module.exports = { + withTS: withTS +} \ No newline at end of file diff --git a/QuantumultTools/scripts/v2raySubView/view.js b/QuantumultTools/scripts/v2raySubView/view.js new file mode 100644 index 0000000..fdadac5 --- /dev/null +++ b/QuantumultTools/scripts/v2raySubView/view.js @@ -0,0 +1,45 @@ +const screenUtil = require('scripts/utils/screen') + +function push() { + let screenInfo = screenUtil.screenInfo() + $ui.push({ + props: { + navBarHidden: true, + bgcolor: $color("#2d2d2d") + }, + views: [{ + type: "view", + layout: (make, view) => { + make.height.equalTo(screenInfo.navBarHeight) + make.width.equalTo(view.super).offset(-(screenInfo.padding * 2)) + make.top.equalTo(screenInfo.statusBarHeight) + make.centerX.equalTo(view.super) + }, + views: [{ + type: "label", + props: { + text: "v2rayN订阅转换", + font: $font("bold", 22), + textColor: $color("white") + }, + layout: (make, view) => { + make.centerY.equalTo(view.super) + make.left.equalTo(view.super).offset(screenInfo.padding) + } + }] + }, { + type: "view", + layout: (make, view) => { + make.height.equalTo(screenInfo.screenHeight - screenInfo.navBarHeight - screenInfo.statusBarHeight) + make.width.equalTo(view.super).offset(-(screenInfo.padding * 2)) + make.top.equalTo(view.prev.bottom) + make.centerX.equalTo(view.super) + }, + views: [] + }] + }) +} + +module.exports = { + push: push +} \ No newline at end of file diff --git a/QuantumultTools/strings/en.strings b/QuantumultTools/strings/en.strings new file mode 100644 index 0000000..ae42947 --- /dev/null +++ b/QuantumultTools/strings/en.strings @@ -0,0 +1,3 @@ +"OK" = "OK"; +"DONE" = "Done"; +"HELLO_WORLD" = "Hello, World!"; diff --git a/QuantumultTools/strings/zh-Hans.strings b/QuantumultTools/strings/zh-Hans.strings new file mode 100644 index 0000000..b2d0c5d --- /dev/null +++ b/QuantumultTools/strings/zh-Hans.strings @@ -0,0 +1,3 @@ +"OK" = "好的"; +"DONE" = "完成"; +"HELLO_WORLD" = "你好,世界!"; diff --git a/QuantumultTools/version.fndroid b/QuantumultTools/version.fndroid new file mode 100644 index 0000000..7f20734 --- /dev/null +++ b/QuantumultTools/version.fndroid @@ -0,0 +1 @@ +1.0.1 \ No newline at end of file diff --git a/README.md b/README.md index b789d87..b2e385a 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,59 @@ -# 我的JSBox脚本集合 +# 我的JSBox工作台 + +### 个人脚本 #### 1. [学期日历](https://jsboxbbs.com/d/86--) + 显示学期进度的小工具 #### 2. [图片模糊](https://jsboxbbs.com/d/99--) + 对图片进行简单模糊的小工具 #### 3. [双拼练习](https://jsboxbbs.com/d/108--) + 双拼练习小工具,有统计和键位图 #### 4. [SS(R)订阅脚本](https://jsboxbbs.com/d/110-ss-r) + 更新订阅的小工具,可配合Potatso Lite使用 #### 5. [微信应用锁](https://jsboxbbs.com/d/120--/5) + 给微信加个锁 #### 6. [JSBox图标生成器](https://jsboxbbs.com/d/135-jsbox) + 生成旧版JSBox图标 #### 7. [文字下划线](https://jsboxbbs.com/d/167--) + 给文字生成下划线 #### 8. [Picsew工具](https://jsboxbbs.com/d/177-picsew) + 常用的Picsew动作 #### 9. [SS转Surge](https://jsboxbbs.com/d/193-ss-surge-proxy) + 从Shadowsocks链接提取Surge链接 + +#### 10. [lhie1规则生成](https://jsboxbbs.com/d/290-lhie1) + 生成Surge2/3和Quantumult规则的工具 + +#### 11. [Quantumul规则更新去Filter Action](https://jsboxbbs.com/d/474-quantumult-filter-action) + 一键更新Quantumult的规则,并可设置自定义策略组 + +#### 12. [Script Gallery脚本版](https://jsboxbbs.com/d/536-script-gallery) + 官方脚本Gallery的脚本版 + +### JSBox工具 + +#### 1. [JSBox Logger](https://jsboxbbs.com/d/543-jsbox-pc) + 远程调试JSBox的工具,可以调试Extension、Widget和Siri + +#### 2. [JSBox cli 完善版](https://jsboxbbs.com/d/579-jsbox-cli) + 加强版jsbox-cli,可以直接开启Jsbox Logger + +### 打赏名单 +[以上的一切都离不开各位的支持](https://github.com/Fndroid/jsbox_script/blob/master/Rules-lhie1/donate.md) + +### 许可 + +请遵守开源协议并注明作者及出处。 -#### 10. [Surge3配置生成](https://jsboxbbs.com/d/214-surge3) -*欢迎打赏,感谢老板!* +### 打赏 ![感谢支持](https://raw.githubusercontent.com/Fndroid/jsbox_script/master/imgs/thankyou.jpg) diff --git a/Rules-lazy/.output/Rules-lazy.box b/Rules-lazy/.output/Rules-lazy.box new file mode 100644 index 0000000..9e4db03 Binary files /dev/null and b/Rules-lazy/.output/Rules-lazy.box differ diff --git a/Rules-lazy/.output/Rules-lhie1.box b/Rules-lazy/.output/Rules-lhie1.box new file mode 100644 index 0000000..d2adb35 Binary files /dev/null and b/Rules-lazy/.output/Rules-lhie1.box differ diff --git a/Rules-lazy/README.md b/Rules-lazy/README.md new file mode 100644 index 0000000..0ced9d4 --- /dev/null +++ b/Rules-lazy/README.md @@ -0,0 +1,32 @@ +## lhie1规则生成 + +### 简介 +经过一周的迭代更新,目前脚本已经可以完全替代Workflow甚至能做到Workflow不能做到的如: +1. 由于没有运行动画,生成速度快 +2. 能自由对节点进行排序和筛选等操作 +3. 提供小组件支持,可以快速判断规则是否有更新并快速生成一次配置 +4. 提供Safari拓展支持,可以像Surge3一样在浏览网页时添加对应的规则 +5. 能识别批量ss://或surge链接 +6. 支持URL Scheme一键生成配置文件 + +更多介绍请参考: + +[Rules-lhie1脚本简介](https://github.com/Fndroid/jsbox_script/blob/master/Rules-lhie1/docs.md) + + +[Rules-lhie1脚本更新日志](https://github.com/Fndroid/jsbox_script/blob/master/Rules-lhie1/updateLog.md) + +[打赏名单](https://github.com/Fndroid/jsbox_script/blob/master/Rules-lhie1/donate.md) + +### 界面截图 + +![](https://github.com/Fndroid/jsbox_script/blob/master/imgs/Rules-lhie1_pre3.jpg?raw=true) +![](https://github.com/Fndroid/jsbox_script/blob/master/imgs/Rules-lhie1_pre4.jpg?raw=true) +![](https://github.com/Fndroid/jsbox_script/blob/master/imgs/Rules-lhie1_pre5.jpg?raw=true) + +### 下载脚本 + +[下载Rules-lhie1](https://xteko.com/redir?name=Rules-lhie1&url=https://raw.githubusercontent.com/Fndroid/jsbox_script/master/Rules-lhie1/.output/Rules-lhie1.box) + +### 感谢支持 +![](https://raw.githubusercontent.com/Fndroid/jsbox_script/master/imgs/thankyou.jpg) \ No newline at end of file diff --git a/Rules-lazy/assets/close_icon.png b/Rules-lazy/assets/close_icon.png new file mode 100644 index 0000000..2805913 Binary files /dev/null and b/Rules-lazy/assets/close_icon.png differ diff --git a/Rules-lazy/assets/icon.png b/Rules-lazy/assets/icon.png new file mode 100644 index 0000000..13f1d40 Binary files /dev/null and b/Rules-lazy/assets/icon.png differ diff --git a/Rules-lazy/assets/new_rules_tag.png b/Rules-lazy/assets/new_rules_tag.png new file mode 100644 index 0000000..a9555cb Binary files /dev/null and b/Rules-lazy/assets/new_rules_tag.png differ diff --git a/Rules-lazy/assets/new_version_tag.png b/Rules-lazy/assets/new_version_tag.png new file mode 100644 index 0000000..9c7516b Binary files /dev/null and b/Rules-lazy/assets/new_version_tag.png differ diff --git a/Rules-lazy/assets/selected_icon.png b/Rules-lazy/assets/selected_icon.png new file mode 100644 index 0000000..0f1342c Binary files /dev/null and b/Rules-lazy/assets/selected_icon.png differ diff --git a/Rules-lazy/assets/thankyou.jpg b/Rules-lazy/assets/thankyou.jpg new file mode 100644 index 0000000..892823a Binary files /dev/null and b/Rules-lazy/assets/thankyou.jpg differ diff --git a/Rules-lazy/assets/thankyou2.jpg b/Rules-lazy/assets/thankyou2.jpg new file mode 100644 index 0000000..2cd5c60 Binary files /dev/null and b/Rules-lazy/assets/thankyou2.jpg differ diff --git a/Rules-lazy/assets/today_jsbox.png b/Rules-lazy/assets/today_jsbox.png new file mode 100644 index 0000000..980e083 Binary files /dev/null and b/Rules-lazy/assets/today_jsbox.png differ diff --git a/Rules-lazy/assets/today_pull.png b/Rules-lazy/assets/today_pull.png new file mode 100644 index 0000000..fc0f192 Binary files /dev/null and b/Rules-lazy/assets/today_pull.png differ diff --git a/Rules-lazy/assets/today_surge.png b/Rules-lazy/assets/today_surge.png new file mode 100644 index 0000000..f74857f Binary files /dev/null and b/Rules-lazy/assets/today_surge.png differ diff --git a/Rules-lazy/config.json b/Rules-lazy/config.json new file mode 100644 index 0000000..a7c3938 --- /dev/null +++ b/Rules-lazy/config.json @@ -0,0 +1,22 @@ +{ + "info": { + "name": "", + "url": "", + "version": "1.0.6", + "author": "", + "website": "", + "types": 0 + }, + "settings": { + "minSDKVer": "1.0.0", + "minOSVer": "10.0.0", + "idleTimerDisabled": false, + "autoKeyboardEnabled": false, + "keyboardToolbarEnabled": false, + "rotateDisabled": false + }, + "widget": { + "tintColor": "", + "iconColor": "" + } +} diff --git a/Rules-lazy/defaultConf/customSettings b/Rules-lazy/defaultConf/customSettings new file mode 100644 index 0000000..b55c643 --- /dev/null +++ b/Rules-lazy/defaultConf/customSettings @@ -0,0 +1 @@ +# Custom \ No newline at end of file diff --git a/Rules-lazy/defaultConf/generalSettings b/Rules-lazy/defaultConf/generalSettings new file mode 100644 index 0000000..2cfcc42 --- /dev/null +++ b/Rules-lazy/defaultConf/generalSettings @@ -0,0 +1,25 @@ +[General] +loglevel = notify +dns-server = 119.29.29.29, 182.254.116.116, 1.1.1.1, 1.0.0.1, system +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,169.254.0.0/16,224.0.0.0/4,240.0.0.0/4 + +// iOS +allow-wifi-access = true +external-controller-access = Hackl0us@0.0.0.0:6170 + +// macOS +interface = 0.0.0.0 +socks-interface = 0.0.0.0 +port = 6152 +socks-port = 6153 +enhanced-mode-by-rule = false + +// Universal +exclude-simple-hostnames = true +ipv6 = true +replica = false + +[Replica] +hide-apple-request = true +hide-crashlytics-request = true +use-keyword-filter = false \ No newline at end of file diff --git a/Rules-lazy/defaultConf/headerrewriteSettings b/Rules-lazy/defaultConf/headerrewriteSettings new file mode 100644 index 0000000..9785a7b --- /dev/null +++ b/Rules-lazy/defaultConf/headerrewriteSettings @@ -0,0 +1 @@ +// custom header rewrite \ No newline at end of file diff --git a/Rules-lazy/defaultConf/hostSettings b/Rules-lazy/defaultConf/hostSettings new file mode 100644 index 0000000..9db2d42 --- /dev/null +++ b/Rules-lazy/defaultConf/hostSettings @@ -0,0 +1 @@ +// custom host \ No newline at end of file diff --git a/Rules-lazy/defaultConf/hostnameSettings b/Rules-lazy/defaultConf/hostnameSettings new file mode 100644 index 0000000..e69de29 diff --git a/Rules-lazy/defaultConf/mitmSettings b/Rules-lazy/defaultConf/mitmSettings new file mode 100644 index 0000000..fef3a96 --- /dev/null +++ b/Rules-lazy/defaultConf/mitmSettings @@ -0,0 +1,3 @@ +enable = true +ca-passphrase = 4B676386 +ca-p12 = MIIJtAIBAzCCCX4GCSqGSIb3DQEHAaCCCW8EgglrMIIJZzCCA9cGCSqGSIb3DQEHBqCCA8gwggPEAgEAMIIDvQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQI6Y6Nt7P0s1QCAggAgIIDkE4px9tUmX4zyAE2qK9f761b7vkat/g7X4gjWSPRtrdovsbnP05XaNdYF8sRn+GktrbqJ6m4LwPe1GUCDht8vuno76ZPAKdT5LVxAeKKJIz8+kqvdKh5COwMSHUD8SqJpncfiH90xu/HmzPbIPCKIE89ZWTRDECmJc9bwH97kefu+U/FB6suMVyEKD7oKhYcjY7110DLNe0okD+MMOLZkMv2DcPb/B9RqKCNAT86bFyF2jtsvyQ15WxkILb03R8Pal1LqkDD9P+r0tTjSRNLKKzWXK0blQeL3teZcusClXPUWo3wZZwNe+8kfUoe23vm62TjSIdYF0gi7G2wpoIIlSlijiPffFFfvG6FS2Y976uLPZb1MonWRdjBYYwry180YQJOyWZQQOR+lWj01lp9o5GaYLKNRNGfrGdsbHx/xKcYEX7Fo/SycUQhzvDh0YbgYz09VNVsbKQDRj0lnxYLnJTLVX0DMmNlWWT6qMwXJ7HLYVT8sgA51h/meUfHmpzI1Qv9k8T/KZQtcVpHSWZ2LXdmwwLJ1A4VgQWxPS7a2GisrYs8DJbDLqaCpRrCyTqpOUclvZ/ONFqiqvJNbuzg33clgutbQNIxoyqJ5A9VDvbKcwgEq91KdSfsQ1shpS/lxGNCsfF+kFcgD95YS2ZfQ5QoFMszoSMCIkz/juc0aLbrGehpmrtd+LGOjomE/y7m8zJ2AxBLQpKSICRu6Dcz0nC2Jgf25/NJlUuX4kIZJyz0MxBBNreUzcevpFIIgsUpwlYAKZKP01/clVV+mVyax49RRVZttMKTaLymSeKO0lGqi9xzbnd0TCtmzN6wp4UpwtISxqLju3fTcgiWnCYRiEY7JZcaAO02J8C8dRsGU1lOBJOJ6hksPwbJ6B52maLmF3cu7WBG5RAmx/MtvJrvzNZYAyord6jjThcfQp8bMv1evmo8BDDpQ6FQb6TR8W9GvLSH21iLbuRFygDnzkKQ+s+LyiO3G0LNseNLxILEcxBgSx0hzoh7/k/MVaO+p0w5csf+VIlOLcew+7Oen5KJFRXhcUDKR3Km7cdcOPv8M8lqvHeScfga6X+W83B+u1+fYhkE8rwlFPj/bUk11A+fbThnM6K069DMh8388S9Tz8otf7zKzad24mUHWSx37GJx1jg0T3dVHegx2JJ3iBLQlGXxF+JiXY/DEeT0VxXJZXt2QbPY9LQ3McIKngeHKpYh4zCCBYgGCSqGSIb3DQEHAaCCBXkEggV1MIIFcTCCBW0GCyqGSIb3DQEMCgECoIIE7jCCBOowHAYKKoZIhvcNAQwBAzAOBAgB2aT5GqvE2AICCAAEggTIPV10t7HrCN6H+dB2i0z4MuGWtSblZVax8UGxygolskl9x7ATpi5+Wo7CpW1EQljzCUmIWygajuqSwvywT5clhuXplenLIXMJcknaf/IjoqF847TY0qSvnOJm+ywoLZ8MhOjTjSNk2N6c+szhr46eSu+1AnMD/fGdh+Z/Xp0i3BdqD4DO+9r8fmjoJlQ1I6ieI6M0baQc/yVPYXJfwZPGYfB3BukXdq1TXtByuCowA8lezoiHQ8EtAongtN2mekiUtHtwK4qHqLqEdr15arht8cFQPXxsX/OE09D/UjzOu04goqEctIBofbUhRMfBprarMfa6qZcFf5saUT7OWk7uC+LcRXd0ZF9XqCTe87u7030jbIh3zuRAX64xbvsowrs8cxM1OaOeqW0Fr0XydsmoPg6r+XlXXyItZvSJllumJHoB2QcpQO03hrRcBuhm7PT5pBG36S4DxwDRBPxID+kWLzbDKJUvsxyX6FyRfn2pySX03w2GcPxlqCexkVHRYInL+MPEUJ8zPOfBx7GvpZStkadnoIo2Zf0t6miHYoxuw81IL6jBQpMaWwL1TMWnioisvoE3zn3oHCTLXWoLwUEKXNX0tQ7IokuXdG7abeD3iBRwXyX6q5jbeQ4PfLll9utT17YSswz2WDoX8fwyiiv+DEZgfWUA5Fc56eVX1w5JIUN/SnqfB1DLcXI4KzSlhZBk+P2MYHCZFTYiFEBYS+A5TPIdt4nhK8+LSA7PA9YyTNSBn09nRjMNtrfSvp5tL4DHkNOPsn4Uxtpl1PW1xxkbfWS3WK3LRAM84jTIE06pqpixSube62F/GqTi5DwyDOdobehgkfXxmQmjTKe4HvIXYC12Mfl9r5VY/gnVLNV0z5PoEA3ycRNWBhXL4BkRyQijZTLm7oH/xP+wWKdOimbJnxAzl8jhiTkJKZGSgj63pioTC60eyDQo8Dh6BdASuSnIdLoqDjB3vFA1Gbp9rOI7YLEYp0bXBzwdbsamasuQKN5juqzvOhmTIKTskx+IKUBRzZ1+5Zse5606PCKMOPyXYgkk42XfdZiyXcmLo/LvHWsxHYvsLspegLRJBDl+HREOzSKoT1FYW//twJaYh34928E1Ek5BeMUzRYrVJklPwAhUoUTpZuZE+kGdCqCBlTq2fN6CEcMB4t88TjGWDS1AGby9zQTK23NQkdTTx0sBnWZDecLAWk1xTdHxV/dvfnQGgByQKilmDf9meeRFFZn89uMX9SK3hhQ3bAh1Z4lTpqLYNyi7j3QSrhhn9ByLL8awH8Hn71EIRznw7mGGOIcyVKQQsE6Z7a7xMMKHhTvdjLwVpTsSLp46nTmBgk7AluBATeJM9FQpOrP142ZtHRifAFUSuhWLhBXYE+NYRaQT1VJQJU5FLadgUQzRGQuvi3dBkM6zXJapEB94OOvq1QjP6bt0SJXVW26+tqBS4tpcqUUJ5fTrfDzAX1ZuVbSSAQw73wNwSsd6OikYfIsg5jL+WnBMPOXnwTKdR3cUYynoqmbLf8A39m+EyRg5Z4kEZksxLxQ9oQH2O+XfgvFbRq9C/POwJV01knSRwjQCjvE6kr7bJSAF/DEekJMhP96ayZ1ZgzUDv+aazlPP2fLIjf4wYmg5h5+Et6GGMWwwIwYJKoZIhvcNAQkVMRYEFAk2eIvfMVYoZsDby30OzlTkJrwGMEUGCSqGSIb3DQEJFDE4HjYAUwB1AHIAZwBlACAARwBlAG4AZQByAGEAdABlAGQAIABDAEEAIAA0AEIANgA3ADYAMwA4ADYwLTAhMAkGBSsOAwIaBQAEFBY2VuZtNCmmQeiV3UDh7JuSWFqPBAj+OgUq8sPPwA== diff --git a/Rules-lazy/defaultConf/proxyGroupSettings b/Rules-lazy/defaultConf/proxyGroupSettings new file mode 100644 index 0000000..ffdbdc5 --- /dev/null +++ b/Rules-lazy/defaultConf/proxyGroupSettings @@ -0,0 +1,2 @@ +[Proxy Group] +Proxy = select, DIRECT, ProxyHTTP, ProxyHTTPS, ProxySOCKS5, ProxySOCKS5TLS, Custom \ No newline at end of file diff --git a/Rules-lazy/defaultConf/ssidSettings b/Rules-lazy/defaultConf/ssidSettings new file mode 100644 index 0000000..0b22e8b --- /dev/null +++ b/Rules-lazy/defaultConf/ssidSettings @@ -0,0 +1 @@ +// custom ssid \ No newline at end of file diff --git a/Rules-lazy/defaultConf/urlrewriteSettings b/Rules-lazy/defaultConf/urlrewriteSettings new file mode 100644 index 0000000..e164c55 --- /dev/null +++ b/Rules-lazy/defaultConf/urlrewriteSettings @@ -0,0 +1 @@ +// custom url rewrite \ No newline at end of file diff --git a/Rules-lazy/docs.md b/Rules-lazy/docs.md new file mode 100644 index 0000000..3940c58 --- /dev/null +++ b/Rules-lazy/docs.md @@ -0,0 +1,60 @@ +### Rules-lhie1脚本使用简介 + +#### 主界面名词 +- 去广告:加载去广告规则(现有总规则6000+) +- 开启MITM:不选中则不使用MITM证书,选中后默认使用lhie1的证书(CA 4B676386),可以在进阶设置中进行自定义 +- 节点后缀:用于在节点后添加UDP、TFO和插件等功能 +- 导出配置:采用Action Sheet将.conf配置文件导出,不打开则尝试直接导出配置文件到Surge 3 +- 进阶设置:可自行配置常规(General)、代理分组(Proxy Group)、代理规则(Custom Rule)、本地DNS映射(Host)、URL重定向(URL Rewrite)、Header修改(Header Rewrite)、SSID、主机名(Hostname)和配置根证书(MITM),也可一键还原全部进阶设置 +- 生成配置:拉取规则生成.conf配置文件 + +#### 节点导入编辑 +> 这是脚本的一个特点,可以对节点进行编辑筛选等操作。 + +- 导入、更新:可以通过剪贴板或二维码导入,支持``ss://链接``、``Surge链接``和``托管地址``,支持批量导入(每行一个) +- 倒序:将节点顺序颠倒,方便不同机场节点选择 +- 占位符:为策略组添加占位符,默认Auto组的占位符是ProxyHeader +- 特殊代理:节点左滑即可将节点设置为某些链接的特殊代理,按钮可以设置Direct、查看设置和清除特殊代理设置 +- 节点删除:在节点列表左滑选择删除 +- 节点重命名:节点列表左滑选择重命名(方便部分不带节点名节点改名) +- 节点排序:节点列表长按拖动即可调整顺序 + +#### 新手指导 +熟悉以上内容后,只需根据自己的需求,导入节点,在列表中选择测速节点,选中开关,拉取规则生成即可享受最新的lhie1规则,而且在托管没有发生变化的情况下,每次进入脚本都可以直接拉取生成最新规则不需要重新配置。 + +#### 进阶设置 +> 进阶设置配置完毕后即可自动保存 + +- 常规(General):常规配置内容,可以设置DNS、Replica等内容 +- 代理分组(Proxy Group):配置代理组内容,每个组别都可以通过``Proxy Header(有空格)``代表所有节点名,``ProxyHeader(无空格)``代替Auto节点名 +- 代理规则(Custom Rule):自定义规则配置,将需要添加的规则添加到规则的最上面,如果要删除lhie1的规则,在规则前添加``-``即可,添加的优先级较高 +- 本地DNS映射(Host):自定义本地DNS映射 +- URL重定向(URL Rewrite):自定义URL重定向 +- Header修改(Header Rewrite):自定义Header修改 +- SSID:自定义SSID +- 主机名(Hostname):自定义主机名 +- MITM:用于配置根证书,默认为lhie1的证书(CA 4B676386),也可自行配置 + +注: +1.以上进阶设置请在“知道自己在做什么”的情况下修改; +2.如果信息填错导致Surge无法启动,可以将对应部分清空保存即可自动恢复为默认,也可一键还原全部进阶设置。 + +#### JSBox小组件 +> 可在iOS的Widget页面中添加使用,使用前需先在JSBox的小组件设置中选中该脚本。小组件视图高度可考虑设置为120。 + +- Surge3启动/停止运行(红色Surge3图标):利用Surge3提供的URL Scheme,可做到Surge3一键启动/停止运行 +- 生成配置(绿色下载图标):一键生成配置,并根据个人脚本中的设置,自动执行导出到Surge3或者打开导出配置的系统选单 +- 打开脚本(蓝色JSBox图标):从小组件进到该JSBox脚本的主界面 + +> 当规则有更新时小组件会有角标提醒及更新内容提示 + +#### URL Scheme +脚本支持自动根据配置拉取最新规则,URL Scheme如下: +``` +jsbox://run?name=Rules-lhie1&auto=1 +``` + +#### 最后 +目前该JSBox脚本只支持生成Surge3的配置,生成配置的各项功能已经能替代大部分的Workflow的自定义功能。 + +如觉得这个脚本好用,愿意打赏,感激不尽。 diff --git a/Rules-lazy/donate.md b/Rules-lazy/donate.md new file mode 100644 index 0000000..7cf1a51 --- /dev/null +++ b/Rules-lazy/donate.md @@ -0,0 +1,21 @@ +### 感谢名单 + +> 仅日期排序,如隐私原因不需公布请私聊告知 + +- BakaMiku +- King👑 +- x1n +- 剛剛好 +- 匿名 * n +- T Dug? +- Drew +- shawn +- 风碎的云 +- 純屬 娛樂 +- plplpmmmmh +- ngsooooo +- Mornwind +- Nicked +- Wangsc1 M + +衷心感谢对脚本和我支持的各位! diff --git a/Rules-lazy/main.js b/Rules-lazy/main.js new file mode 100644 index 0000000..b28b22c --- /dev/null +++ b/Rules-lazy/main.js @@ -0,0 +1,49 @@ +const app = require('scripts/app') +const today = require('scripts/today') +const extension = require('scripts/extension') +const updateUtil = require('scripts/updateUtil') + +$app.autoKeyboardEnabled = true +$app.rotateDisabled = true +// $app.keyboardToolbarEnabled = true + +let query = $context.query + +let bid = $objc("NSBundle").invoke("mainBundle.bundleIdentifier").rawValue() + +$objc('notification').invoke('objectForKey') + +if (query.auto == 1) { + app.autoGen() + return +} + +if ($app.env === $env.today) { + today.renderTodayUI(bid) + return +} else if ($app.env === $env.safari) { + extension.renderExtensionUI() + return +} else if ($app.env === $env.action) { + extension.collectRules() + return +} + +app.setUpWorkspace() + +app.renderUI() + +updateUtil.getLatestVersion({ + handler: version => { + console.log([version, updateUtil.getCurVersion()]) + if (updateUtil.needUpdate(version, updateUtil.getCurVersion())) { + $http.get({ + url: 'https://raw.githubusercontent.com/Fndroid/jsbox_script/master/Rules-lhie1/updateLog.md' + '?t=' + new Date().getTime(), + handler: resp=> { + updateUtil.updateScript(version) + } + }) + + } + } +}) diff --git a/Rules-lazy/scripts/app.js b/Rules-lazy/scripts/app.js new file mode 100644 index 0000000..9cae31c --- /dev/null +++ b/Rules-lazy/scripts/app.js @@ -0,0 +1,1541 @@ +const proxyUtil = require('scripts/proxyUitl') +const updateUtil = require('scripts/updateUtil') +const su = require('scripts/sizeUtil') +const cu = require('scripts/colorUtil') +const videoReg = require('scripts/videoReg') +const ruleUpdateUtil = require('scripts/ruleUpdateUtil') +const customViews = require('scripts/customViews') + +const FILE = 'data.js' +const PROXY_HEADER = 'ProxyHeader' + +const settingKeys = ['generalSettings', 'proxyGroupSettings', 'customSettings', 'hostSettings', 'urlrewriteSettings', 'headerrewriteSettings', 'ssidSettings', 'hostnameSettings', 'mitmSettings'] + +if (!$file.exists(FILE)) { + $file.write({ + data: $data({ "string": JSON.stringify({ "urls": [] }) }), + path: FILE + }) +} + +setDefaultSettings() + +let screenHeight = $device.info.screen.height +const screenWidth = $device.info.screen.width + +const iPhoneX = screenWidth == 375 && screenHeight == 812 +if (iPhoneX) { + screenHeight -= 48 +} + +const selectedColor = $color("#c1dcf0") +const defaultColor = $color("#ffffff") +const tintColor = $color("#ff6666") +const blackColor = $color("#000000") + +function renderUI() { + $ui.render({ + props: { + title: "Surge3规则生成" + }, + views: [{ + type: "view", + props: { + id: "mainView" + }, + layout: $layout.fill, + views: [{ + type: "input", + props: { + id: "fileName", + text: '', + placeholder: "配置名(lhie1)" + }, + layout: (make, view) => { + make.width.equalTo(screenWidth / 2 - 15) + make.height.equalTo(40) + make.left.top.equalTo(10) + }, + events: { + returned: sender => { + $("fileName").blur() + saveWorkspace() + } + } + }, { + type: "button", + props: { + type: $btnType.contactAdd, + id: "serverURL", + title: " 添加、更新节点" + }, + layout: (make, view) => { + make.width.equalTo(screenWidth / 2 - 15) + make.height.equalTo(40) + make.right.equalTo(-10) + make.top.equalTo(10) + }, + events: { + tapped: sender => { + importMenu({ + handler: (res, name, url) => { + // 如果是托管,url不为undefined + console.log([res, name, url]) + if (!res) { + $ui.alert("没有检测到节点信息") + } + let listData = $("serverEditor").data || [] + let existsSec = listData.find(item => item.url === url) + let section = existsSec || { title: name, rows: [], url: url } + let selectedRows = [] + if (existsSec) { + selectedRows = section.rows.filter(i => cu.isEqual(i.proxyName.bgcolor, selectedColor)).map(i => i.proxyName.text) + } + console.error(selectedRows) + section.rows = [] + for (let idx in res) { + if (res[idx].split("=")[1].trim() == 'direct') { + // 过滤直连 + continue + } + let selected = selectedRows.indexOf(res[idx].split('=')[0].trim()) > -1 + section.rows.push({ + proxyName: { text: res[idx].split('=')[0].trim(), bgcolor: selected ? selectedColor : defaultColor }, + proxyLink: res[idx] + }) + } + if (!existsSec) { + listData.push(section) + } + $("serverEditor").data = listData + saveWorkspace() + } + }) + } + } + }, { + type: "matrix", + props: { + id: "serverControl", + columns: 4, + scrollEnabled: false, + itemHeight: 40, + bgcolor: $color("#f0f5f5"), + data: genControlItems(), + template: [{ + type: "label", + props: { + id: "title", + align: $align.center, + font: $font(14), + 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) => { + if (indexPath.item == 0) { + reverseServerGroup() + } else if (indexPath.item == 1) { + groupShortcut() + } else if (indexPath.item == 3) { + deleteServerGroup() + } else { + 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 { + $ui.menu({ + items: Object.keys(videoReg), + handler: function (title, idx) { + let proxyName = mTitle + let videoProxy = $("serverEditor").info + videoProxy[title] = proxyName + $("serverEditor").info = videoProxy + saveWorkspace() + } + }) + } + } + }) + } + } + } + }, { + type: "list", + props: { + id: "serverEditor", + data: [], + separatorHidden: true, + reorder: true, + actions: [{ + title: "delete", + handler: (sender, indexPath) => { + saveWorkspace() + } + }, { + title: "重命名", + handler: (sender, indexPath) => { + $ui.menu({ + items: ["节点重命名", "组别重命名"], + handler: function (title, idx) { + if (idx === 0) { + let titleText = sender.object(indexPath).proxyName.text + $input.text({ + type: $kbType.default, + placeholder: "请输入节点名", + text: titleText == '无节点名称' ? "" : titleText, + handler: function (text) { + let obj = sender.object(indexPath) + obj.proxyName.text = text + let proxyURLNoName = obj.proxyLink.split("=") + proxyURLNoName.shift() + obj.proxyLink = `${text} =${proxyURLNoName.join("=")}` + listReplace(sender, indexPath, obj) + saveWorkspace() + } + }) + } else { + let od = sender.data + $input.text({ + type: $kbType.default, + placeholder: "请输入组别名称", + text: od[indexPath.section].title, + handler: function (text) { + od[indexPath.section].title = text + sender.data = od + saveWorkspace() + } + }) + } + } + }) + + } + }, { + title: "特殊代理", + handler: (sender, indexPath) => { + let proxyName = sender.object(indexPath).proxyName.text + $ui.menu({ + items: Object.keys(videoReg), + handler: function (title, idx) { + let videoProxy = sender.info + videoProxy[title] = proxyName + sender.info = videoProxy + saveWorkspace() + } + }) + } + }], + borderWidth: 2, + borderColor: $color("#f0f5f5"), + template: { + views: [{ + type: 'label', + props: { + id: 'proxyName', + align: $align.center, + autoFontSize: true + }, + layout: $layout.fill + }] + }, + // radius: 5 + }, + layout: (make, view) => { + make.width.equalTo(view.super).offset(-20) + make.centerX.equalTo(view.super) + make.height.equalTo(screenHeight - 330) + make.top.equalTo($("serverControl").bottom) + }, + events: { + didSelect: (sender, indexPath, data) => { + let proxyName = data.proxyName.text + let isSelected = cu.isEqual(data.proxyName.bgcolor, selectedColor) + let controlInfo = $("serverControl").info + let currentGroup = controlInfo.currentProxyGroup + console.log(currentGroup) + let customProxyGroup = controlInfo.customProxyGroup || {} + if (isSelected) { + data.proxyName.bgcolor = defaultColor + customProxyGroup[currentGroup] = customProxyGroup[currentGroup].filter(i => i !== proxyName) + } else { + data.proxyName.bgcolor = selectedColor + customProxyGroup[currentGroup].push(proxyName) + } + let uiData = sender.data + uiData[indexPath.section].rows[indexPath.row] = data + sender.data = uiData + $("serverControl").info = controlInfo + saveWorkspace() + }, + reorderFinished: data => { + $("serverEditor").data = data + saveWorkspace() + } + } + }, { + type: "input", + props: { + id: "serverSuffixEditor", + placeholder: ',udp-relay=true,tfo=true(节点后缀)', + text: '', + font: $font(18), + type: $kbType.ascii + }, + layout: (make, view) => { + make.top.equalTo(view.prev.bottom).offset(10) + make.width.equalTo(view.prev) + make.height.equalTo(45) + make.centerX.equalTo(view.super) + }, + events: { + returned: sender => { + $("serverSuffixEditor").blur() + saveWorkspace() + } + } + }, { + type: "matrix", + props: { + id: "usualSettings", + columns: 3, + itemHeight: 40, + spacing: 5, + scrollEnabled: false, + data: [{ + title: { text: '去广告', bgcolor: defaultColor, textColor: blackColor } + }, { + title: { text: '开启MITM', bgcolor: defaultColor, textColor: blackColor } + }, { + title: { text: '导出配置', bgcolor: defaultColor, textColor: blackColor } + }], + template: [{ + type: "label", + props: { + id: "title", + align: $align.center, + font: $font(14), + radius: 5, + borderColor: tintColor, + 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) => { + data.title.bgcolor = cu.isEqual(data.title.bgcolor, tintColor) ? defaultColor : tintColor + data.title.textColor = cu.isEqual(data.title.bgcolor, tintColor) ? defaultColor : blackColor + let uiData = $("usualSettings").data + uiData[indexPath.row] = data + $("usualSettings").data = uiData + saveWorkspace() + } + } + }] + }, { + type: "button", + props: { + id: "advanceBtn", + title: "进阶设置", + bgcolor: $color("#808080") + }, + 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", + title: "关于", + bgcolor: $color("#808080") + }, + 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", + 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 => { + $("progressBar").value = p + }, + onDone: res => { + $ui.animate({ + duration: 0.3, + animation: function () { + $("progressView").alpha = 0 + }, + completion: function () { + $("progressView").value = 0 + $("progressView").hidden = true + } + }) + exportConf(res.fileName, res.fileData, res.actionSheet, () => { + $http.stopServer() + }) + $app.listen({ + resume: function () { + $http.stopServer() + } + }) + }, + onError: res => { + $("progressView").value = 0 + $("progressView").hidden = true + } + }) + } + } + }, { + type: "blur", + props: { + id: "progressView", + style: 1, + alpha: 0, + hidden: true + }, + layout: $layout.fill, + views: [{ + type: "label", + props: { + 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) + } + }] + }] + }) +} + +function genControlItems() { + let currentProxyGroup = PROXY_HEADER + try { + currentProxyGroup = $("serverControl").info.currentProxyGroup + } catch(e) {} + return [{ + title: { text: '节点倒序' } + }, { + title: { text: currentProxyGroup } + }, { + title: { text: '特殊代理' } + }, { + 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).concat(['新增占位符']).filter(i => i !== currentProxyGroup) + $ui.menu({ + items: menuItems, + handler: function (title, idx) { + if (idx === menuItems.length - 1) { + $input.text({ + type: $kbType.default, + placeholder: "占位符,在进阶设置中使用", + handler: function (text) { + if ([PROXY_HEADER, 'Proxy Header'].indexOf(text) > -1) { + $ui.error("占位符名称冲突") + return + } + customProxyGroup[text] = [] + $("serverControl").info = controlInfo + saveWorkspace() + $ui.toast(`切换到占位符:${text}`) + switchToGroup(text) + } + }) + } else { + $ui.alert({ + title: "请选择", + message: "占位符代表一组节点名称,配合进阶设置可以进行自定义策略组", + actions: [{ + title: '编辑', + handler: () => { + switchToGroup(title) + } + }, { + title: '复制', + handler: () => { + $clipboard.text = title + $ui.toast("已复制到剪贴板") + } + }, { + title: '删除', + handler: () => { + if ([PROXY_HEADER, 'Proxy Header'].indexOf(title) > -1) { + $ui.error("此占位符无法删除") + return + } + delete customProxyGroup[title] + $("serverControl").info = controlInfo + saveWorkspace() + $ui.toast(`已删除占位符:${title}`) + } + }, { + title: '取消' + }] + }) + } + } + }) + + 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.proxyName.bgcolor = group.indexOf(item.proxyName.text) > -1 ? selectedColor : defaultColor; + 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 getPrototype(done) { + return new Promise((resolve, reject) => { + $http.get({ + url: "https://raw.githubusercontent.com/lhie1/Rules/master/Surge/Prototype.conf", + handler: function (resp) { + if (done) done() + resolve(resp.data) + } + }) + }) +} + +function getAutoRules(url, done) { + return new Promise((resolve, reject) => { + $http.get({ + url: url, + handler: function (resp) { + if (done) done() + 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]) { + let listSections = $("serverEditor").data + linkHandler(listSections.map(i => i.url).join('\n'), params) + } + } + }) +} + +function linkHandler(url, params) { + let servers = { + shadowsocks: [], + surge: [], + online: [], + ignore: [] + } + + 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|http|https|socks5|socks5-tls)/.test(item)) { + servers.surge.push(item) + } else { + servers.ignore.push(item) + } + }) + + console.log(servers) + + for (let k in servers) { + if (servers[k].length === 0) { + continue + } + if (k === 'shadowsocks') { + console.log(servers[k]) + proxyUtil.proxyFromURL({ + ssURL: servers[k], + handler: res => { + params.handler(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(result, urls.length > 1 ? `批量Surge链接(${urls.length})` : result[0].split('=')[0].trim(), urls.join('\n')) + }) + } else if (k === 'online') { + $ui.loading(true) + proxyUtil.proxyFromConf({ + urls: servers[k], + handler: res => { + $ui.loading(false) + params.handler(res.servers, res.filename, res.url) + } + }) + } else { + $ui.alert('剪贴板存在无法识别的行:\n\n' + servers.ignore.join('\n') + '\n\n以上行将被丢弃!') + } + } +} + +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 inputViewData = [] + for (let idx in settingKeys) { + let content = previewData[settingKeys[idx]] + inputViewData.push({ + type: "text", + props: { + text: content, + bgcolor: $color("#f0f5f5"), + font: $font(14) + }, + events: { + didEndEditing: sender => { + let content = sender.text + if (sender.text == '') { + content = $file.read('defaultConf/' + settingKeys[idx]).string + sender.text = content + } + write2file(settingKeys[idx], content) + } + } + }) + } + let genControlBnts = function (idx) { + let titleTexts = ['常规', '代理分组', '代理规则', '本地DNS映射', 'URL重定向', 'Header修改', 'SSID', '主机名', '配置根证书'] + const sbgc = $color("#ffda40") + const stc = $color("#034769") + const dbgc = $color("#63add0") + const dtc = $color("#ffffff") + 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: "进阶设置" + }, + views: [{ + type: "gallery", + props: { + id: "inputViews", + items: inputViewData, + interval: 0 + }, + layout: (make, view) => { + make.height.equalTo(view.super).dividedBy(2) + make.width.equalTo(view.super) + }, + events: { + changed: sender => { + let idx = sender.page + $("settingsControl").data = genControlBnts(idx) + } + } + }, { + type: "matrix", + props: { + columns: 3, + id: "settingsControl", + itemHeight: 40, + bgcolor: $color("#ffffff"), + spacing: 3, + data: genControlBnts(0), + template: [{ + type: "label", + props: { + id: "title", + align: $align.center, + font: $font(14) + }, + layout: $layout.fill + }] + }, + layout: (make, view) => { + make.height.equalTo(130) + make.centerX.equalTo(view.super) + make.width.equalTo(view.super).offset(0) + make.top.equalTo(view.prev.bottom).offset(5) + }, + events: { + didSelect: (sender, indexPath, data) => { + let idx = indexPath.row + $("settingsControl").data = genControlBnts(idx) + $("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: "关于" + }, + views: [{ + type: "view", + props: { + id: "mainAboutView" + }, + layout: $layout.fill, + 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: ["🗂 脚本简介", "📃 更新日志", "🖥 论坛导航"] + }, + layout: (make, view) => { + make.width.equalTo(view.super) + make.top.equalTo(view.prev.bottom).offset(0) + make.height.equalTo(135) + }, + events: { + didSelect: (sender, indexPath, data) => { + if (indexPath.row == 0) { + previewMD(data, 'docs.md') + } else if (indexPath.row == 1) { + 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: ["🙏 捐献打赏名单", "👍 赏杯咖啡支持作者"] + }, + 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) { + previewMD(data, 'donate.md') + } else { + $ui.menu({ + items: ["支付宝", "微信"], + handler: function (title, idx) { + if (idx == 0) { + $app.openURL($qrcode.decode($file.read("assets/thankyou2.jpg").image)) + } else { + $quicklook.open({ + image: $file.read("assets/thankyou.jpg").image + }) + } + } + }) + } + } + } + }, { + 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"] + }, + 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) { + $safari.open({ + url: "https://t.me/Fndroid", + }) + } else { + $safari.open({ + url: "https://github.com/Fndroid/jsbox_script/tree/master/Rules-lhie1/README.md", + }) + } + } + } + }, { + 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 => 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 => item.proxyName.text.indexOf(k) === -1)) + 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() + } + }) +} + +function autoServerGroup() { + let serverData = $("serverEditor").data + let sections = serverData.map(i => i.title) + if (sections.length === 1) { + let allSelected = serverData[0].rows.every(item => cu.isEqual(item.proxyName.bgcolor, selectedColor)) + serverData[0].rows.map(item => { + item.proxyName.bgcolor = allSelected ? defaultColor : selectedColor + return item + }) + $("serverEditor").data = serverData + saveWorkspace() + return + } + $ui.menu({ + items: sections.concat(['全部Auto']), + handler: function (title, idx) { + if (idx === sections.length) { + let flatData = serverData.reduce((all, cur) => { + return { rows: all.rows.concat(cur.rows) } + }).rows + let needColor = defaultColor + if (!flatData.every(i => cu.isEqual(i.proxyName.bgcolor, selectedColor))) { + needColor = selectedColor + } + serverData.map(sec => { + sec.rows.map(item => { + item.proxyName.bgcolor = needColor + return item + }) + return sec + }) + } else { + let sectionData = serverData[idx] + let needColor = defaultColor + if (!sectionData.rows.every(i => cu.isEqual(i.proxyName.bgcolor, selectedColor))) { + needColor = selectedColor + } + sectionData.rows.map(item => { + item.proxyName.bgcolor = needColor + return item + }) + } + $("serverEditor").data = serverData + saveWorkspace() + } + }) +} + +function setUpWorkspace() { + $app.listen({ + ready: function () { + let file = JSON.parse($file.read(FILE).string) + if (file && file.workspace) { + let workspace = file.workspace + $("fileName").text = workspace.fileName || '' + $("serverSuffixEditor").text = workspace.serverSuffix || '' + 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.bgcolor = defaultGroup.indexOf(item.proxyName.text) > -1 ? selectedColor : defaultColor + 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 ? tintColor : defaultColor + item.title.textColor = sd.title.textColor ? defaultColor : blackColor + } + return item + }) + $("usualSettings").data = nd + $("serverEditor").info = workspace.videoProxy || {} + $("serverControl").info = { + deleteKeywords: workspace.deleteKeywords || '', + customProxyGroup: customProxyGroup, + currentProxyGroup: defaultGroupName + } + } 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.map(section => { + // 如果节点选上,则color为true + section.rows.map(item => { + item.proxyName.bgcolor = cu.isEqual(selectedColor, item.proxyName.bgcolor) + return item + }) + return section + }), + usualData: $("usualSettings").data.map(i => { + i.title.bgcolor = cu.isEqual(tintColor, i.title.bgcolor) + i.title.textColor = cu.isEqual(defaultColor, i.title.textColor) + return i + }), + serverSuffix: $("serverSuffixEditor").text, + videoProxy: $("serverEditor").info || {}, + 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 (!(settingKeys[idx] in previewData) || previewData[settingKeys[idx]] == "") { + let defaultValue = $file.read(`defaultConf/${settingKeys[idx]}`).string + previewData[settingKeys[idx]] = defaultValue + } + } + $file.write({ + data: $data({ "string": JSON.stringify(previewData) }), + path: FILE + }) +} + +function autoGen() { + let settings = JSON.parse($file.read(FILE).string) + console.log(settings) + $ui.render({ + props: { + title: "" + }, + layout: $layout.fill, + views: [{ + type: "blur", + props: { + id: "progressView", + style: 1 + }, + layout: $layout.fill, + views: [{ + type: "label", + props: { + 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) + } + }] + }] + }) + $app.listen({ + ready: function () { + makeConf({ + onStart: () => { + console.log('start') + }, + onProgress: p => { + $("progressBar").value = p + }, + onDone: res => { + exportConf(res.fileName, res.fileData, res.actionSheet, () => { + $http.stopServer() + $app.close() + }) + $app.listen({ + resume: function () { + $http.stopServer() + $app.close() + } + }) + }, + onError: res => { + $ui.alert("无法生成配置文件,可能是规则仓库发生变化或网络出现问题") + } + }) + }, + exit: function () { + + } + }) +} + +function makeConf(params) { + 'onStart' in params && params.onStart() + try { + let pu = { + 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' + } + 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('去广告') + let isMitm = usualValue('开启MITM') + let isActionSheet = usualValue('导出配置') + + let serverEditorData = workspace.serverData + 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, '')) + let proxies = flatServerData.map(i => { + let notExistSuffix = proxySuffix.filter((ps, idx) => { + if (idx === 0 && ps === '') return true + return i.proxyLink.indexOf(ps) < 0 + }) + console.log(notExistSuffix) + return i.proxyLink + notExistSuffix.join(',') + }).join('\n') + let proxyHeaders = flatServerData.map(i => i.proxyName.text).join(', ') + let rules = '' + let prototype = '' + let testFlight = '' + let host = '' + let urlRewrite = '' + let urlReject = '' + let headerRewrite = '' + let hostName = '' + let mitm = '' + + let pgs = 0 + + let onPgs = function () { + pgs += 0.1 + 'onProgress' in params && params.onProgress(pgs) + } + + let emptyPromise = function (done) { + if (done) done() + return Promise.resolve('') + } + + let promiseArray = [ + getPrototype(onPgs), // 0 + getAutoRules(pu.apple, onPgs), // 1 + getAutoRules(pu.reject, onPgs), // 2 + getAutoRules(pu.proxy, onPgs), // 3 + getAutoRules(pu.direct, onPgs), // 4 + getAutoRules(pu.host, onPgs), // 5 + getAutoRules(pu.urlrewrite, onPgs), // 6 + getAutoRules(pu.urlreject, onPgs), // 7 + getAutoRules(pu.headerrewrite, onPgs), // 8 + getAutoRules(pu.hostname, onPgs) // 9 + ] + + if (!ads) { + promiseArray[2] = emptyPromise(onPgs) + promiseArray[7] = emptyPromise(onPgs) + } + + Promise.all(promiseArray).then(v => { + console.log(v) + prototype = v[0] + rules += `\n${v[1]}\n${v[2].replace(/REJECT/g, "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) { + return { + add: content.split('\n').filter(i => !i.startsWith('-')).map(i => i.trim()), + delete: content.split("\n").filter(i => i.startsWith('-')).map(i => i.replace('-', '').trim()) + } + } + + let prettyInsert = function (lines) { + return '\n\n' + lines.join('\n') + '\n\n' + } + + // 配置代理分组 + if (advanceSettings.proxyGroupSettings) { + let pgs = advanceSettings.proxyGroupSettings + pgs = pgs.replace(/Proxy Header/g, proxyHeaders) + for (let name in customProxyGroup) { + let nameReg = new RegExp(name, 'g') + let serverNames = customProxyGroup[name] + serverNames = serverNames.filter(i => proxyNameLegal(i)) + pgs = pgs.replace(nameReg, serverNames.join(',') || 'DIRECT') + } + 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(',') || 'DIRECT') + } + // 配置常规设置 + if (advanceSettings.generalSettings) { + prototype = prototype.replace(/\[General\][\s\S]+\[Proxy\]/, advanceSettings.generalSettings + '\n\n[Proxy]') + } + // 配置自定义规则 + let customRules = seperateLines(advanceSettings.customSettings) + 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) + } + }) + + // 视频代理处理 + let videoProxy = workspace.videoProxy + for (let videoType in videoProxy) { + let proxyName = videoProxy[videoType] + if (!proxyNameLegal(proxyName)) continue + rules.match(videoReg[videoType]).forEach(i => { + rules = rules.replace(i, i.replace('🍃 Proxy', proxyName)) + }) + } + + prototype = prototype.replace('# Custom', prettyInsert(customRules.add)) + prototype = prototype.replace('Proxys', proxies) + prototype = prototype.replace('# All Rules', rules) + prototype = prototype.replace('# Host', host + prettyInsert(userHost.add)) + prototype = prototype.replace('# URL Rewrite', urlRewrite + prettyInsert(userUrl.add)) + prototype = prototype.replace('# URL REJECT', urlReject) + prototype = prototype.replace('# SSID', userSSID) + prototype = prototype.replace('# Header Rewrite', headerRewrite + prettyInsert(userHeader.add)) + prototype = prototype.replace('// Hostname', 'hostname = ' + hostName.concat(userHostname.add.filter(i => i != '')).join(', ')) + + if (isMitm) { + prototype = prototype.replace('# MITM', advanceSettings.mitmSettings) + } else { + prototype = prototype.replace('# MITM', "") + } + + let fn = (workspace.fileName || 'lhie1') + '.conf' + + if ('onDone' in params) { + ruleUpdateUtil.getGitHubFilesSha({ + handler: sha => { + if (sha) { + ruleUpdateUtil.setFilesSha(sha) + } else { + console.log('sha 获取失败') + } + } + }) + params.onDone({ + actionSheet: isActionSheet, + fileName: fn, + fileData: prototype + }) + } + }).catch(e => { + console.error(e) + }) + } catch (e) { + 'onError' in params && params.onError(e) + } +} + +function exportConf(fileName, fileData, actionSheet, actionSheetCancel) { + 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 = `surge3:///install-config?url=${encodeURIComponent(serverUrl + "download?path=" + fileName)}` + $app.openURL(surgeScheme) + } else { + $ui.alert("内置服务器启动失败,请重试") + } + } + }) + } + }) + } +} + +module.exports = { + renderUI: renderUI, + setUpWorkspace: setUpWorkspace, + autoGen: autoGen +} \ No newline at end of file diff --git a/Rules-lazy/scripts/colorUtil.js b/Rules-lazy/scripts/colorUtil.js new file mode 100644 index 0000000..bcfbf83 --- /dev/null +++ b/Rules-lazy/scripts/colorUtil.js @@ -0,0 +1,7 @@ +function isEqual(color1, color2) { + return color1.runtimeValue().invoke('isEqual', color2) +} + +module.exports = { + isEqual: isEqual +} \ No newline at end of file diff --git a/Rules-lazy/scripts/extension.js b/Rules-lazy/scripts/extension.js new file mode 100644 index 0000000..fe722fb --- /dev/null +++ b/Rules-lazy/scripts/extension.js @@ -0,0 +1,285 @@ +const app = require('scripts/app') + +const FILE = 'data.js' + +let screenHeight = $device.info.screen.height +const screenWidth = $device.info.screen.width + +const iPhoneX = screenWidth == 375 && screenHeight == 812 +if (iPhoneX) { + screenHeight -= 48 +} + +let renderExtensionUI = function () { + const host = $context.safari.items.location.hostname + $ui.render({ + props: { + title: "增加规则" + }, + views: [{ + type: "scroll", + props: { + id: "mainScrollView", + bgcolor: $color("#f0f5f5") + }, + layout: $layout.fill, + views: [{ + type: "label", + props: { + text: "当前网页路径" + }, + layout: (make, view) => { + make.width.equalTo(view.super) + make.left.equalTo(5) + make.height.equalTo(30) + make.top.equalTo(view.super.top).offset(10) + } + }, { + type: "text", + props: { + editable: false, + text: $context.safari.items.baseURI + }, + layout: (make, view) => { + make.top.equalTo(view.prev.bottom).offset(5) + make.width.equalTo(view.super) + make.height.equalTo(60) + } + }, { + type: "label", + props: { + text: "规则匹配项" + }, + layout: (make, view) => { + make.width.equalTo(view.super) + make.left.equalTo(5) + make.height.equalTo(30) + make.top.equalTo(view.prev.bottom).offset(10) + } + }, { + type: "list", + props: { + rowHeight: 60, + id: "styleList", + template: { + views: [{ + type: "label", + props: { + id: "styleName", + font: $font("bold", 16) + }, + layout: (make, view) => { + make.width.equalTo(view.super) + make.height.equalTo(view.super).dividedBy(2) + make.left.equalTo(view.super.left).offset(15) + make.top.equalTo(view.super.top).offset(5) + } + }, { + type: "label", + props: { + id: "styleValue", + }, + layout: (make, view) => { + make.width.equalTo(view.super) + make.height.equalTo(view.super).dividedBy(2) + make.left.equalTo(view.super.left).offset(15) + make.top.equalTo(view.prev.bottom).offset(-10) + } + }, { + type: "image", + props: { + id: "styleSelected", + bgcolor: $color("#ffffff") + }, + layout: (make, view) => { + make.centerY.equalTo(view.super) + make.height.width.equalTo(20) + make.right.equalTo(view.super.right).offset(-20) + } + }] + } + }, + layout: (make, view) => { + make.top.equalTo(view.prev.bottom).offset(5) + make.width.equalTo(view.super) + make.height.equalTo(0) + }, + events: { + didSelect: (sender, indexPath, data) => { + let uiData = sender.data.map((i, idx) => { + return { + styleName: i.styleName, + styleValue: i.styleValue, + styleSelected: { data: null } + } + }) + uiData[indexPath.row].styleSelected = { data: $file.read("assets/selected_icon.png") } + sender.data = uiData + sender.info = indexPath.row + } + } + }, { + type: "label", + props: { + text: "选择代理" + }, + layout: (make, view) => { + make.width.equalTo(view.super) + make.left.equalTo(5) + make.height.equalTo(30) + make.top.equalTo(view.prev.bottom).offset(10) + } + }, { + type: "list", + props: { + id: "proxyList", + rowHeight: 40, + template: { + views: [{ + type: "label", + props: { + id: "proxyName" + }, + layout: (make, view) => { + make.centerY.equalTo(view.super) + make.width.equalTo(view.super).offset(-80) + make.height.equalTo(view.super) + make.left.equalTo(view.super.left).offset(15) + } + }, { + type: "image", + props: { + id: "proxySelected", + bgcolor: $color("#ffffff") + }, + layout: (make, view) => { + make.centerY.equalTo(view.super) + make.height.width.equalTo(20) + make.right.equalTo(view.super.right).offset(-20) + } + }] + } + }, + layout: (make, view) => { + make.top.equalTo(view.prev.bottom).offset(5) + make.width.equalTo(view.super) + make.height.equalTo(0) + }, + events: { + didSelect: (sender, indexPath, data) => { + let uiData = sender.data.map((i, idx) => { + return { + proxyName: i.proxyName, + proxySelected: { data: null } + } + }) + uiData[indexPath.row].proxySelected = { data: $file.read("assets/selected_icon.png") } + sender.data = uiData + sender.info = indexPath.row + } + } + }, { + type: "label", + props: { + text: "添加规则", + align: $align.center, + bgcolor: $color("#fff") + }, + layout: (make, view) => { + make.width.equalTo(view.super) + make.height.equalTo(40) + make.top.equalTo(view.prev.bottom).offset(20) + }, + events: { + tapped: sender => { + if ($("styleList").info !== null && $("proxyList").info !== null) { + let styleName = $("styleList").data[$("styleList").info].styleName.text + let styleValue = $("styleList").data[$("styleList").info].styleValue.text + let proxy = $("proxyList").data[$("proxyList").info].proxyName.text + let rule = [styleName, styleValue, proxy].join(',') + saveRule(rule) + $context.close() + } else { + $ui.alert("请选择匹配项和代理") + } + } + } + }] + }] + }) + let styles = genStyleDefaultData(host) + let styleListHeight = styles.length * 60 + $("styleList").data = styles + $("styleList").updateLayout(make => { + make.height.equalTo(styleListHeight) + }) + let savedData = JSON.parse($file.read(FILE).string) + let proxyGroup = savedData.proxyGroupSettings.split('\n').filter(i => /^[\s\S]+=/.test(i)).map(i => i.split(/[\s]*=/)[0]) + let flatServerData = savedData.workspace.serverData.reduce((all, cur) => { + return { + rows: all.rows.concat(cur.rows) + } + }).rows + let proxies = ['🚀 Direct'].concat(proxyGroup, ['REJECT', 'REJECT-TINYGIF'], flatServerData.map(i => i.proxyName.text)) + let proxiesData = genProxyDefaultData(proxies) + let proxiesListHeight = proxiesData.length * 40 + $("proxyList").data = proxiesData + $("proxyList").updateLayout(make => { + make.height.equalTo(proxiesListHeight) + }) + resizeScrollView(styleListHeight + proxiesListHeight) + +} + +let resizeScrollView = function (listHeight) { + $("mainScrollView").contentSize = $size(screenWidth, 350 + listHeight) +} + +let genStyleDefaultData = function (domain) { + let dp = domain.split('.') + let res = [] + res.push({ + styleName: { text: 'DOMAIN' }, + styleValue: { text: domain } + }) + return res.concat(dp.map((item, idx, obj) => { + return { + styleName: { text: 'DOMAIN-SUFFIX' }, + styleValue: { text: obj.slice(idx).join('.') } + } + })) +} + +let saveRule = function (rule) { + let data = JSON.parse($file.read(FILE).string) + data.customSettings += `\n${rule}\n` + $file.write({ + data: $data({ "string": JSON.stringify(data) }), + path: FILE + }) +} + +let genProxyDefaultData = function (proxies) { + return proxies.map(i => { + return { + proxyName: { text: i } + } + }) +} + +function collectRules() { + let confFile = $context.data.string + let reg = /\[Rule\]\n([\s\S]+)\n# Custom/ + let matcher = confFile.match(reg) + if (matcher.length === 2) { + let rules = matcher[1] + saveRule(rules) + } + $context.close() +} + + +module.exports = { + renderExtensionUI: renderExtensionUI, + collectRules: collectRules +} \ No newline at end of file diff --git a/Rules-lazy/scripts/filenameUtil.js b/Rules-lazy/scripts/filenameUtil.js new file mode 100644 index 0000000..a7ddc37 --- /dev/null +++ b/Rules-lazy/scripts/filenameUtil.js @@ -0,0 +1,22 @@ +const knownURLs = [ + {domain: 'api.rixcloud.io', name: 'rixCloud'} +] + +function getConfName(url) { + let matchConst = knownURLs.find(i => url.indexOf(i.domain) > -1) + if (matchConst) { + return matchConst.name + } else { + let path = url.split('?')[0].split('/') + let filename = path[path.length - 1] + if (filename.indexOf('.conf') == filename.length - 5) { + return filename.substring(0, filename.length - 5) + } else { + return filename + } + } +} + +module.exports = { + getConfName: getConfName +} \ No newline at end of file diff --git a/Rules-lazy/scripts/proxyUitl.js b/Rules-lazy/scripts/proxyUitl.js new file mode 100644 index 0000000..9dd7a32 --- /dev/null +++ b/Rules-lazy/scripts/proxyUitl.js @@ -0,0 +1,106 @@ +const filenameUtil = require('scripts/filenameUtil') + +String.prototype.strictTrim = function() { + let trimed = this.trim() + if ((matcher = trimed.match(/([\s\S]+),$/)) !== null) { + return matcher[1] + } + return trimed +} + +function promiseConf(url) { + return new Promise((resolve, reject) => { + $http.get({ + url: url, + handler: function (resp) { + let data = resp.data + let filename = url + try { + let matcher = resp.response.runtimeValue().invoke('allHeaderFields').rawValue()["Content-Disposition"].match(/filename=(.*?).conf/) + filename = matcher[1] + } catch (e) { + filename = filenameUtil.getConfName(url) + } + let servers = data.match(/\[Proxy\]([\s\S]*?)\[Proxy Group\]/) + if (servers != null) { + resolve({ + servers: servers[1], + filename: filename + }) + } else { + reject() + } + } + }) + }) +} + +function getServersFromConfFile(params) { + let promiseArray = params.urls.map(i => promiseConf(i)) + Promise.all(promiseArray).then(confs => { + confs.forEach((res, idx) => { + console.log(res.servers) + let servers = res.servers.split(/[\n\r]+/).filter(item => item !== '').map(i => i.strictTrim()) + params.handler({ servers: servers, filename: res.filename, url: params.urls[idx] }) + }) + }).catch(reason => { + params.handler(null) + }) +} + +function decodeScheme(params) { + let urls = params.ssURL + let result = [] + let tag + + for (let idx in urls) { + let url = urls[idx] + let method, password, hostname, port, plugin + if (!url.includes('#')) { + let name = '无节点名称' + url += `#${name}` + } + tag = $text.URLDecode(url.match(/#(.*?)$/)[1]) + if (url.includes('?')) { + // tag = $text.URLDecode(url.match(/#(.*?)$/)[1]) + let mdps = url.match(/ss:\/\/(.*?)@/)[1] + let padding = 4 - mdps.length % 4 + if (padding < 4) { + mdps += Array(padding + 1).join('=') + } + let userinfo = $text.base64Decode(mdps) + method = userinfo.split(':')[0] + password = userinfo.split(':')[1] + let htpr = url.match(/@(.*?)\?/)[1].replace('\/', '') + hostname = htpr.split(':')[0] + port = htpr.split(':')[1] + let ps = $text.URLDecode(url.match(/\?(.*?)#/)[1]) + let obfsMatcher = ps.match(/obfs=(.*?)(;|$)/) + let obfsHostMatcher = ps.match(/obfs-host=(.*?)(;|$)/) + if (obfsMatcher) { + let obfs = obfsMatcher[1] + let obfsHost = obfsHostMatcher? obfsHostMatcher[1] : 'cloudfront.net' + plugin = `obfs=${obfs}, obfs-host=${obfsHost}` + } + } else { + let mdps = url.match(/ss:\/\/(.*?)#/)[1] + let padding = 4 - mdps.length % 4 + if (padding < 4) { + mdps += Array(padding + 1).join('=') + } + [method, password, hostname, port] = $text.base64Decode(mdps).split(/[:,@]/) + } + let proxy = `${tag} = custom, ${hostname}, ${port}, ${method}, ${password}, http://omgib13x8.bkt.clouddn.com/SSEncrypt.module` + if (plugin != undefined) { + proxy += `, ${plugin}` + } + result[idx] = proxy + } + + params.handler({ servers: result, sstag: result.length > 1? `批量ss节点(${result.length})`: tag }) +} + +module.exports = { + proxyFromConf: getServersFromConfFile, + proxyFromURL: decodeScheme +} \ No newline at end of file diff --git a/Rules-lazy/scripts/ruleUpdateUtil.js b/Rules-lazy/scripts/ruleUpdateUtil.js new file mode 100644 index 0000000..5cd0377 --- /dev/null +++ b/Rules-lazy/scripts/ruleUpdateUtil.js @@ -0,0 +1,60 @@ +const repo = 'https://api.github.com/repos/lhie1/Rules/contents/Auto' +const commits = 'https://api.github.com/repos/lhie1/Rules/commits?path=Auto&sha=master' + +const FILE = 'data.js' + +function checkUpdate(oldSha, newSha) { + return Object.keys(newSha).some(i => oldSha[i] !== newSha[i]) +} + +function setFilesSha(sha) { + let file = JSON.parse($file.read(FILE).string) + file['repoSha'] = sha + $file.write({ + data: $data({ "string": JSON.stringify(file) }), + path: FILE + }) +} + +function getFilesSha() { + let file = JSON.parse($file.read(FILE).string) + return file['repoSha'] || {} +} + +function getGitHubFilesSha(params) { + $http.get({ + url: repo, + handler: function (resp) { + if (resp.response.statusCode === 200) { + let res = {} + resp.data.forEach(i => { + res[i.name] = i.sha + }) + params.handler(res) + } else { + params.handler({}) + } + } + }) +} + +function getLatestCommitMessage(params) { + $http.get({ + url: commits, + handler: function(resp) { + if (resp.response.statusCode === 200 && resp.data.length > 0) { + params.handler(resp.data[0]) + } else { + params.handler(null) + } + } + }) +} + +module.exports = { + checkUpdate: checkUpdate, + getGitHubFilesSha: getGitHubFilesSha, + setFilesSha: setFilesSha, + getFilesSha: getFilesSha, + getLatestCommitMessage: getLatestCommitMessage +} \ No newline at end of file diff --git a/Rules-lazy/scripts/sizeUtil.js b/Rules-lazy/scripts/sizeUtil.js new file mode 100644 index 0000000..e976afa --- /dev/null +++ b/Rules-lazy/scripts/sizeUtil.js @@ -0,0 +1,8 @@ +function pc(x) { + let sw = $device.info.screen.width + return x * sw / 100 +} + +module.exports = { + pc: pc +} \ No newline at end of file diff --git a/Rules-lazy/scripts/today.js b/Rules-lazy/scripts/today.js new file mode 100644 index 0000000..48b4ef1 --- /dev/null +++ b/Rules-lazy/scripts/today.js @@ -0,0 +1,240 @@ +const ruleUpdateUtil = require('scripts/ruleUpdateUtil') +const updateUtil = require('scripts/updateUtil') + +const loadingHint = "检查规则/脚本更新..." + +const scriptName = $addin.current.name + +const sw = $device.info.screen.width + +let pm = function (method) { + return new Promise((resolve, reject) => { + method({ + handler: res => { + resolve(res) + } + }) + }) +} + +function renderTodayUI(bid) { + let isLauncher = bid === 'app.cyan.jsbox.ghost' + let checks = [pm(ruleUpdateUtil.getGitHubFilesSha), pm(updateUtil.getLatestVersion)] + Promise.all(checks).then(res => { + let canUpdate = ruleUpdateUtil.checkUpdate(ruleUpdateUtil.getFilesSha(), res[0]) + let newVersion = updateUtil.needUpdate(res[1], updateUtil.getCurVersion()) + $("newTag").hidden = !canUpdate + $("newVersionTag").hidden = !newVersion + return canUpdate ? pm(ruleUpdateUtil.getLatestCommitMessage) : Promise.resolve() + }).then(res => { + $("updateStatus").text = res? res.commit.message : "" + }) + $ui.render({ + props: { + id: "todayMainView", + title: "Surge3规则生成", + frame: $rect(0, 0, sw, 110), + hideNavbar: true, + navBarHidden: true, + bgcolor: $color("clear"), + }, + views: [{ + type: "blur", + props: { + id: "close", + style: 1, + radius: 0, + hidden: !isLauncher + }, + layout: (make, view) => { + make.width.height.equalTo(view.super).offset(10) + make.top.equalTo(view.super.top).offset(-10) + }, + events: { + tapped: sender => { + $app.close(0.3) + } + } + }, { + type: "view", + props: { + id: "", + }, + layout: (make, view) => { + make.height.equalTo(110) + make.width.equalTo(view.super).offset(0) + make.center.equalTo(view.super) + }, + views: [{ + type: "label", + props: { + id: "updateStatus", + text: "Rules-lhie1 by Fndroid", + font: $font(12), + textColor: $rgba(50, 50, 50, .3) + }, + layout: (make, view) => { + make.top.equalTo(view.super.top).offset(5) + make.centerX.equalTo(view.super) + } + }, { + type: "label", + props: { + id: "updateStatus", + text: loadingHint, + font: $font(12), + textColor: $rgba(50, 50, 50, .5) + }, + layout: (make, view) => { + make.bottom.equalTo(view.super.bottom).offset(-5) + make.centerX.equalTo(view.super) + } + }, { + type: "image", + props: { + id: "pullBtn", + data: $file.read("assets/today_pull.png"), + radius: 25, + bgcolor: $rgba(255, 255, 255, 0) + }, + layout: (make, view) => { + make.width.height.equalTo(55) + make.centerY.equalTo(view.super).offset(-10) + make.centerX.equalTo(view.super) + }, + events: { + tapped: sender => { + $app.openURL(`jsbox://run?name=${encodeURIComponent(scriptName)}&auto=1`) + } + }, + }, { + type: "image", + props: { + id: "surgeBtn", + data: $file.read("assets/today_surge.png"), + radius: 25, + bgcolor: $rgba(255, 255, 255, 0) + }, + layout: (make, view) => { + make.width.height.equalTo(55) + make.centerY.equalTo(view.super).offset(-10) + make.right.equalTo(view.prev.left).offset(- (sw / 9)) + }, + events: { + tapped: sender => { + $app.openURL("surge3:///toggle?autoclose=true") + } + } + }, { + type: "image", + props: { + id: "jsboxBtn", + data: $file.read("assets/today_jsbox.png"), + radius: 25, + bgcolor: $rgba(255, 255, 255, 0) + }, + layout: (make, view) => { + make.width.height.equalTo(50) + make.centerY.equalTo(view.super).offset(-10) + make.left.equalTo(view.prev.prev.right).offset((sw / 9)) + }, + events: { + tapped: sender => { + $app.openURL(`jsbox://run?name=${encodeURIComponent(scriptName)}`) + } + } + }, { + type: "label", + props: { + text: "更新规则", + font: $font(12), + textColor: $rgba(50, 50, 50, .8), + align: $align.center + }, + layout: (make, view) => { + make.height.equalTo(10) + make.top.equalTo($("pullBtn").bottom) + make.width.equalTo($("pullBtn").width) + make.centerX.equalTo($("pullBtn")) + } + }, { + type: "label", + props: { + text: "Surge开关", + font: $font(12), + textColor: $rgba(50, 50, 50, .8), + align: $align.center + }, + layout: (make, view) => { + make.height.equalTo(10) + make.top.equalTo(view.prev.top) + make.width.equalTo($("pullBtn").width) + make.centerX.equalTo($("surgeBtn")) + } + }, { + type: "label", + props: { + text: "脚本设置", + font: $font(12), + textColor: $rgba(50, 50, 50, .8), + align: $align.center + }, + layout: (make, view) => { + make.height.equalTo(10) + make.top.equalTo($("pullBtn").bottom) + make.width.equalTo($("pullBtn").width) + make.centerX.equalTo($("jsboxBtn")) + } + }, { + type: "image", + props: { + id: "newTag", + data: $file.read("assets/new_rules_tag.png"), + bgcolor: $rgba(255, 255, 255, 0), + hidden: true + }, + layout: (make, view) => { + make.width.height.equalTo(15) + make.centerY.equalTo(view.super).offset(-20) + make.left.equalTo($("pullBtn").right).offset(-10) + } + }, { + type: "image", + props: { + id: "newVersionTag", + data: $file.read("assets/new_version_tag.png"), + bgcolor: $rgba(255, 255, 255, 0), + hidden: true + }, + layout: (make, view) => { + make.width.height.equalTo(15) + make.centerY.equalTo(view.super).offset(-20) + make.left.equalTo($("jsboxBtn").right).offset(-10) + } + }, { + type: "image", + props: { + id: "closeBtn", + data: $file.read("assets/close_icon.png"), + bgcolor: $rgba(255, 255, 255, 0), + hidden: !isLauncher, + alpha: 0.7 + }, + layout: (make, view) => { + make.width.height.equalTo(20) + make.top.equalTo(view.super.top).offset(10) + make.right.equalTo(view.super.right).offset(-10) + }, + events: { + tapped: sender => { + $app.close(.2) + } + } + }] + }] + }) +} + +module.exports = { + renderTodayUI: renderTodayUI +} \ No newline at end of file diff --git a/Rules-lazy/scripts/updateUtil.js b/Rules-lazy/scripts/updateUtil.js new file mode 100644 index 0000000..a52f682 --- /dev/null +++ b/Rules-lazy/scripts/updateUtil.js @@ -0,0 +1,50 @@ +function getCurVersion() { + let version = $file.read("version.fndroid").string + return version +} + +function getLatestVersion(params) { + $http.get({ + url: 'https://raw.githubusercontent.com/Fndroid/jsbox_script/master/Rules-lhie1/version.fndroid' + '?t=' + new Date().getTime(), + handler: res => { + params.handler(res.data) + } + }) +} + +function updateScript(version) { + let url = 'https://raw.githubusercontent.com/Fndroid/jsbox_script/master/Rules-lhie1/.output/Rules-lhie1.box' + '?t=' + new Date().getTime() + const scriptName = $addin.current.name + $http.download({ + url: url, + handler: resp => { + let box = resp.data + $addin.save({ + name: scriptName, + data: box, + handler: (success) => { + if (success) { + let donateList = $file.read("donate.md").string + let names = donateList.split(/[\r\n]+/).filter(i => i!== '') + $ui.toast(`静默更新完成,感谢${names.length - 3}位老板`) + } + } + }) + } + }) +} + +function needUpdate(nv, ov) { + let getVersionWeight = i => { + return i.split('.').map(i => i * 1).reduce((s, i) => s * 100 + i) + } + return getVersionWeight(nv) > getVersionWeight(ov) +} + + +module.exports = { + getCurVersion: getCurVersion, + getLatestVersion: getLatestVersion, + updateScript: updateScript, + needUpdate: needUpdate +} \ No newline at end of file diff --git a/Rules-lazy/scripts/videoReg.js b/Rules-lazy/scripts/videoReg.js new file mode 100644 index 0000000..a8482d7 --- /dev/null +++ b/Rules-lazy/scripts/videoReg.js @@ -0,0 +1,8 @@ +module.exports = { + Netflix: /(?=USER-AGENT,Argo\*,|DOMAIN-SUFFIX,netflix.com,|DOMAIN-SUFFIX,netflix.net,|DOMAIN-SUFFIX,nflxext.com,|DOMAIN-SUFFIX,nflximg.com,|DOMAIN-SUFFIX,nflximg.net,|DOMAIN-SUFFIX,nflxvideo.net,)[^\n]+/g, + Spotify: /(?=PROCESS-NAME,Spotify,|DOMAIN-SUFFIX,spoti.fi,|DOMAIN-KEYWORD,spotify,)[^\n]+/g, + YouTube: /(?=USER-AGENT,com.google.ios.youtube\*,|USER-AGENT,YouTube\*,|DOMAIN-SUFFIX,googlevideo.com,|DOMAIN-SUFFIX,youtube.com,)[^\n]+/g, + MytvSUPER: /(?=DOMAIN-KEYWORD,nowtv100,|DOMAIN-KEYWORD,rthklive,|DOMAIN-SUFFIX,mytvsuper.com,|DOMAIN-SUFFIX,tvb.com,)[^\n]+/g, + BBC: /(?=USER-AGENT,BBCiPlayer\*,|DOMAIN-KEYWORD,co.uk,|DOMAIN-KEYWORD,uk-live,|DOMAIN-SUFFIX,bbc.co,|DOMAIN-SUFFIX,bbc.com,)[^\n]+/g, + LINE: /(?=DOMAIN-SUFFIX,lin.ee,|DOMAIN-SUFFIX,line.me,|DOMAIN-SUFFIX,line.naver.jp,|DOMAIN-SUFFIX,line-apps.com,|DOMAIN-SUFFIX,line-cdn.net,|DOMAIN-SUFFIX,line-scdn.net,|DOMAIN-SUFFIX,nhncorp.jp,|IP-CIDR,103.2.28.0\/22,|IP-CIDR,119.235.224.0\/21,|IP-CIDR,119.235.232.0\/23,|IP-CIDR,119.235.235.0\/24,|IP-CIDR,119.235.236.0\/23,|IP-CIDR,125.6.146.0\/24,|IP-CIDR,125.6.149.0\/24,|IP-CIDR,125.6.190.0\/24,|IP-CIDR,125.209.208.0\/20,|IP-CIDR,203.104.103.0\/24,|IP-CIDR,203.104.128.0\/20,|IP-CIDR,203.174.66.64\/26,|IP-CIDR,203.174.77.0\/24,)[^\n]+/g +} \ No newline at end of file diff --git a/Rules-lazy/strings/en.strings b/Rules-lazy/strings/en.strings new file mode 100644 index 0000000..ae42947 --- /dev/null +++ b/Rules-lazy/strings/en.strings @@ -0,0 +1,3 @@ +"OK" = "OK"; +"DONE" = "Done"; +"HELLO_WORLD" = "Hello, World!"; diff --git a/Rules-lazy/strings/zh-Hans.strings b/Rules-lazy/strings/zh-Hans.strings new file mode 100644 index 0000000..b2d0c5d --- /dev/null +++ b/Rules-lazy/strings/zh-Hans.strings @@ -0,0 +1,3 @@ +"OK" = "好的"; +"DONE" = "完成"; +"HELLO_WORLD" = "你好,世界!"; diff --git a/Rules-lazy/updateLog.md b/Rules-lazy/updateLog.md new file mode 100644 index 0000000..209da7f --- /dev/null +++ b/Rules-lazy/updateLog.md @@ -0,0 +1,187 @@ +**如打赏名单有遗漏,请TG联系我加一下** + +#### 2018.06.13 +1. 更新脚本简介 + +#### 2018.06.12 +1. 更新方式改为静默更新,有新版本将不会再弹出提示更新而在后台静默更新,更新后可不重启脚本生成规则,修复因更新脚本造成打断操作而产生心里阴影面积过大的问题 +2. 修复ProxyHeader占位符丢失问题 +3. 去除重复后缀 +4. 先进行占位符替换,避免影响General的内容,占位符命名请不要与Proxy Group里面的内容相同 + +#### 2018.06.11 +1. 调整界面 +2. 去除UPD设置选项,增加节点后缀设置,可用于UDP、TFO和插件设置 +3. 修复undefined问题 + +#### 2018.06.10 +1. 生成配置时检查特殊代理设置服务器是否存在,不存在则忽略避免报错 +2. 删除**批量Auto**选项 +3. 增加**策略组别**选项,用于生成用于进阶设置的占位符,和ProxyHeader类似 +4. 修复无法删除策略组占位符问题 +5. 增加特殊代理可选择策略组功能 +6. 修复特殊代理direct无效问题 + +如担心数据丢失,可对脚本内data.js文件进行备份 + +#### 2018.06.07 +1. 修复脚本名称更改后升级和小组件异常问题 + +#### 2018.06.06 +1. 删除导入节点时对组别进行保存的功能,因为有列表更新,所以貌似也没什么用 +2. 默认给所有节点打开tfo,开关明天做 + +#### 2018.06.03 +1. 删除分组时可以选择按照关键字删除 +2. 修复单个组别无法进行关键字删除的问题 + +#### 2018.06.02 +1. 小组件/启动器在规则有更新时显示最新的commit message + +#### 2018.05.27 +1. 适配两种奇葩的机场托管节点识别(分别是在节点前面加空格和节点后面加逗号) +2. 修复部分ss节点无法识别问题 +3. 修复windows下Shadowsocks二维码无法识别问题 +4. 打开小组件或启动器时同时检查脚本和规则更新 + +#### 2018.05.26 +1. 修复obfs识别问题 +2. 调整更新等待时间 +3. 区分启动器和小组件样式,启动器加入关闭按钮(性能模式启动器有效) + +#### 2018.05.25 +1. 修正不添加节点不能导出规则问题 +2. 调整启动器启动样式,启动后点击上下两部分空白可退出脚本 + +#### 2018.05.22 +1. 调整Widget样式 + +#### 2018.05.15 +1. 增加一个特性:在Surge里面将配置文件(.conf)分享到脚本,脚本会将当前配置的自定义规则(通过Surge的Extension添加的)追加到进阶设置里。 + +> 1的识别的配置文件需要时脚本生成的且进阶设置的代理规则包括``# Custom``注释 + +#### 2018.05.13 +1. 调整导入节点表现,当列表中已有该组时,会更新已有组 +2. 导入节点增加“更新节点列表”选项,用于升级列表中所有组 + +> 上述1,2仅对1.6.19及以后导入的组别生效 + +#### 2018.05.12 +1. 调整Widget样式,去除启动器启动时的导航栏 +2. 去除输入法上方的提示栏 +3. 修复定义ProxyHeader失败问题 + +#### 2018.05.11 +1. 增加对http/https/socks5/socks5-tls代理类型的支持,统一理解为Surge链接 +2. 增强剪贴板导入,以行为单位支持批量ss链接、Surge链接和托管地址混合导入 + +#### 2018.05.10 +1. 修复Safari拓展节点消失问题 +2. 优化拉取速度,更新规则耗时更短 +3. 当节点列表只有一个分组时,节点倒序、批量Auto和删除节点将不会弹出菜单,直接操作当前唯一组 + +#### 2018.05.09 +1. 调整UI +2. 增加组别支持,每次导入的节点会被分为一组,可以根据组别进行倒序、Auto和删除操作 +3. 取消对``固定脚本``的支持,列表外上滑将不再减少高度 +4. 节点重命名选项可以对所在组别进行重命名 + +> 由于列表结构改变,原本节点列表的数据会丢失,请重新导入 + +#### 2018.05.08 +1. 增加Safari拓展,在页面添加规则 +2. 修复图标过大导致JSBox启动器无法载入问题 +3. 调整``全部Auto``,如果当前已经全选,则全部反选一次 +4. 调整``导入节点``,如果列表中已经存在,则不会重复显示 +5. 修复自动生成时选择取消Action Sheet时脚本不退出问题 + +#### 2018.05.07 +1. 增加小组件支持 +2. 修复批量导入时不能识别``\r``换行的问题 +3. 自动生成后回到JSBox自动退出脚本 +4. 特殊代理增加单独查看、清除按钮,支持选择Direct +5. 调整小组件按钮位置,规则无更新时下方小字不再显示 + +#### 2018.05.06 +1. .conf的所有配置项现在都可以在进阶配置中定义,大部分设置可以使用``-(减号)``对lhie1配置剔除 +2. 内部代码调整,异常请反馈 +3. 部分UI调整 +4. 增加特殊代理设置,左滑节点即可设置 +5. 文档修正,感谢**Mornwind**的PR + +#### 2018.05.05 +1. 增加去自定义规则删除(优先级低于添加),写法: + ![](https://github.com/Fndroid/jsbox_script/blob/master/imgs/Rules-lhie1_pre2.jpg?raw=true) +2. 修复进阶设置Gallery切换按钮不联动的问题 +3. 增加关于页面 + + 感谢**Mornwind、几位匿名赞赏者**的咖啡 + +#### 2018.05.04 +1. 检测文件名,不合法则采用Action Sheet分享 +2. 由于无法检测脚本是否在``固定脚本``中运行,所以增加了上下滑动列表高度变化,虽然很突兀,但是没办法 +3. 增加节点重命名和排序 +4. 进阶设置界面调整,DNS设置改为General,MITM默认为lhie1的,不开启则为空 +5. 修正Surge3跳转 +6. 增加URL Scheme支持,添加``auto=1``即可根据配置自动生成一次规则 + +#### 2018.05.03 +1. 增加直接导出到Surge功能,但有限制,进入Surge后不要等待过长时间确定安装 +2. 更改TestFlight选项名为UDP +3. 尝试突破Surge加载问题 +4. 修复托管direct多余问题 + + 感谢**Nicked、Mornwind**的咖啡 + +#### 2018.05.02 + +1. 增加批量导入ss节点(剪贴板) +2. 增加批量导入Surge节点(剪贴板) +3. 增加生成的过渡动画代替默认的loading +4. 修复TINYGIF的问题 +5. 增加配置Proxy Group,``ProxyHeader``为Auto节点名,``Proxy Header``为所有节点名,可更改Auto设置 +6. 删除导入的相册选择选项,如需此操作请更新JSBox + + 感谢**Wangsc1 M**的咖啡 + +#### 2018.05.01 + +1. 跳转到主应用运行 +2. 调整界面(节点操作栏不再跟随滚动,开关说明) +3. 解除底部按钮固定,适配iPhoneX屏幕 + +> 无法运行无解,我无法复现... + +#### 2018.04.30 A + +1. 每次退出脚本先保存环境,下次进入可以直接使用 +2. 修改图标 +3. 支持导入Surge节点 +4. 修复``墙洞``托管无法识别问题 + +> 仅测试了cordcloud和rixCloud的托管导入,其他如果不能导入的,要不把托管借我测试再改密,要不等别人借我测试 + +> 脚本无法启动问题已经提交JSBox作者调试 + +#### 2018.04.30 + +1. 修复TF无效(节点无udp-relay=true)的问题 +2. 修复升级日志遮挡问题 +3. 由于``$addin.save``无回调,增加2s防止极端情况更新失败 +4. 调整界面,去掉Auto显示框,改为列表显示形式 +5. 增加``MITM``、``DNS``和``Rule Custom``设置 +6. 修正配置可能丢失的问题 +7. 修复输入框遮挡问题 +8. 修正配置文件与wf部分区别 +9. 增加MITM开关 + +> iOS 10.3.3如果能进入脚本,请TG和我说一下 + +#### 2018.04.29 + +1. 不选Auto时,Auto则为DIRECT,生成后请手动选择Proxy +2. 保存的``托管``或``ss://``改为以名称显示(可能不能很好兼容上版本) +3. 增加倒序和全部添加到Auto功能 +3. 调整界面大小 +4. 修正无法识别托管文件名的问题 \ No newline at end of file diff --git a/Rules-lazy/version.fndroid b/Rules-lazy/version.fndroid new file mode 100644 index 0000000..8227ad9 --- /dev/null +++ b/Rules-lazy/version.fndroid @@ -0,0 +1 @@ +1.6.46 \ No newline at end of file diff --git a/Rules-lhie1-Quantumult/.output/Rules-lhie1-Quantumult.box b/Rules-lhie1-Quantumult/.output/Rules-lhie1-Quantumult.box new file mode 100644 index 0000000..7256931 Binary files /dev/null and b/Rules-lhie1-Quantumult/.output/Rules-lhie1-Quantumult.box differ diff --git a/Rules-lhie1-Quantumult/.output/Rules-lhie1.box b/Rules-lhie1-Quantumult/.output/Rules-lhie1.box new file mode 100644 index 0000000..83ac243 Binary files /dev/null and b/Rules-lhie1-Quantumult/.output/Rules-lhie1.box differ diff --git a/Rules-lhie1-Quantumult/README.md b/Rules-lhie1-Quantumult/README.md new file mode 100644 index 0000000..daa289d --- /dev/null +++ b/Rules-lhie1-Quantumult/README.md @@ -0,0 +1,32 @@ +## lhie1规则生成 + +### 简介 +经过一周的迭代更新,目前脚本已经可以完全替代Workflow甚至能做到Workflow不能做到的如: +1. 由于没有运行动画,生成速度快 +2. 能自由对节点进行排序、筛选和排序等操作 +3. 提供小组件支持,可以快速判断规则是否有更新并快速生成一次配置 +4. 提供Safari拓展支持,可以像Surge3一样在浏览网页时添加对应的规则 +5. 能识别批量ss://或surge链接 +6. 支持URL Scheme一键生成配置文件 + +更多介绍请参考: + +[Rules-lhie1脚本简介](https://github.com/Fndroid/jsbox_script/blob/master/Rules-lhie1/docs.md) + + +[Rules-lhie1脚本更新日志](https://github.com/Fndroid/jsbox_script/blob/master/Rules-lhie1/updateLog.md) + +[打赏名单](https://github.com/Fndroid/jsbox_script/blob/master/Rules-lhie1/donate.md) + +### 界面截图 + +![](https://github.com/Fndroid/jsbox_script/blob/master/imgs/Rules-lhie1_pre3.jpg?raw=true) +![](https://github.com/Fndroid/jsbox_script/blob/master/imgs/Rules-lhie1_pre4.jpg?raw=true) +![](https://github.com/Fndroid/jsbox_script/blob/master/imgs/Rules-lhie1_pre5.jpg?raw=true) + +### 下载脚本 + +[下载Rules-lhie1](https://xteko.com/redir?name=Rules-lhie1&url=https://raw.githubusercontent.com/Fndroid/jsbox_script/master/Rules-lhie1/.output/Rules-lhie1.box) + +### 感谢支持 +![](https://raw.githubusercontent.com/Fndroid/jsbox_script/master/imgs/thankyou.jpg) \ No newline at end of file diff --git a/Rules-lhie1-Quantumult/assets/close_icon.png b/Rules-lhie1-Quantumult/assets/close_icon.png new file mode 100644 index 0000000..2805913 Binary files /dev/null and b/Rules-lhie1-Quantumult/assets/close_icon.png differ diff --git a/Rules-lhie1-Quantumult/assets/icon.png b/Rules-lhie1-Quantumult/assets/icon.png new file mode 100644 index 0000000..13f1d40 Binary files /dev/null and b/Rules-lhie1-Quantumult/assets/icon.png differ diff --git a/Rules-lhie1-Quantumult/assets/new_rules_tag.png b/Rules-lhie1-Quantumult/assets/new_rules_tag.png new file mode 100644 index 0000000..a9555cb Binary files /dev/null and b/Rules-lhie1-Quantumult/assets/new_rules_tag.png differ diff --git a/Rules-lhie1-Quantumult/assets/new_version_tag.png b/Rules-lhie1-Quantumult/assets/new_version_tag.png new file mode 100644 index 0000000..9c7516b Binary files /dev/null and b/Rules-lhie1-Quantumult/assets/new_version_tag.png differ diff --git a/Rules-lhie1-Quantumult/assets/selected_icon.png b/Rules-lhie1-Quantumult/assets/selected_icon.png new file mode 100644 index 0000000..0f1342c Binary files /dev/null and b/Rules-lhie1-Quantumult/assets/selected_icon.png differ diff --git a/Rules-lhie1-Quantumult/assets/thankyou.jpg b/Rules-lhie1-Quantumult/assets/thankyou.jpg new file mode 100644 index 0000000..892823a Binary files /dev/null and b/Rules-lhie1-Quantumult/assets/thankyou.jpg differ diff --git a/Rules-lhie1-Quantumult/assets/thankyou2.jpg b/Rules-lhie1-Quantumult/assets/thankyou2.jpg new file mode 100644 index 0000000..2cd5c60 Binary files /dev/null and b/Rules-lhie1-Quantumult/assets/thankyou2.jpg differ diff --git a/Rules-lhie1-Quantumult/assets/today_jsbox.png b/Rules-lhie1-Quantumult/assets/today_jsbox.png new file mode 100644 index 0000000..980e083 Binary files /dev/null and b/Rules-lhie1-Quantumult/assets/today_jsbox.png differ diff --git a/Rules-lhie1-Quantumult/assets/today_pull.png b/Rules-lhie1-Quantumult/assets/today_pull.png new file mode 100644 index 0000000..fc0f192 Binary files /dev/null and b/Rules-lhie1-Quantumult/assets/today_pull.png differ diff --git a/Rules-lhie1-Quantumult/assets/today_surge.png b/Rules-lhie1-Quantumult/assets/today_surge.png new file mode 100644 index 0000000..f74857f Binary files /dev/null and b/Rules-lhie1-Quantumult/assets/today_surge.png differ diff --git a/Rules-lhie1-Quantumult/config.json b/Rules-lhie1-Quantumult/config.json new file mode 100644 index 0000000..a7c3938 --- /dev/null +++ b/Rules-lhie1-Quantumult/config.json @@ -0,0 +1,22 @@ +{ + "info": { + "name": "", + "url": "", + "version": "1.0.6", + "author": "", + "website": "", + "types": 0 + }, + "settings": { + "minSDKVer": "1.0.0", + "minOSVer": "10.0.0", + "idleTimerDisabled": false, + "autoKeyboardEnabled": false, + "keyboardToolbarEnabled": false, + "rotateDisabled": false + }, + "widget": { + "tintColor": "", + "iconColor": "" + } +} diff --git a/Rules-lhie1-Quantumult/defaultConf/customSettings b/Rules-lhie1-Quantumult/defaultConf/customSettings new file mode 100644 index 0000000..b55c643 --- /dev/null +++ b/Rules-lhie1-Quantumult/defaultConf/customSettings @@ -0,0 +1 @@ +# Custom \ No newline at end of file diff --git a/Rules-lhie1-Quantumult/defaultConf/dnsSettings b/Rules-lhie1-Quantumult/defaultConf/dnsSettings new file mode 100644 index 0000000..b83944e --- /dev/null +++ b/Rules-lhie1-Quantumult/defaultConf/dnsSettings @@ -0,0 +1,2 @@ +[DNS] +system,1.2.4.8,80.80.80.80,80.80.81.81,1.1.1.1,1.0.0.1 \ No newline at end of file diff --git a/Rules-lhie1-Quantumult/defaultConf/headerrewriteSettings b/Rules-lhie1-Quantumult/defaultConf/headerrewriteSettings new file mode 100644 index 0000000..9785a7b --- /dev/null +++ b/Rules-lhie1-Quantumult/defaultConf/headerrewriteSettings @@ -0,0 +1 @@ +// custom header rewrite \ No newline at end of file diff --git a/Rules-lhie1-Quantumult/defaultConf/hostSettings b/Rules-lhie1-Quantumult/defaultConf/hostSettings new file mode 100644 index 0000000..9db2d42 --- /dev/null +++ b/Rules-lhie1-Quantumult/defaultConf/hostSettings @@ -0,0 +1 @@ +// custom host \ No newline at end of file diff --git a/Rules-lhie1-Quantumult/defaultConf/hostnameSettings b/Rules-lhie1-Quantumult/defaultConf/hostnameSettings new file mode 100644 index 0000000..e69de29 diff --git a/Rules-lhie1-Quantumult/defaultConf/proxyGroupSettings b/Rules-lhie1-Quantumult/defaultConf/proxyGroupSettings new file mode 100644 index 0000000..60e66aa --- /dev/null +++ b/Rules-lhie1-Quantumult/defaultConf/proxyGroupSettings @@ -0,0 +1,20 @@ +[POLICY] +🍃 Proxy : static, 🏃 Auto +🏃 Auto +🚀 Direct +Proxy Header + +🍂 Domestic : static, 🚀 Direct +🚀 Direct +🍃 Proxy + +🍎 Only : static, 🚀 Direct +🚀 Direct +Proxy Header + +☁️ Others : static, 🚀 Direct +🚀 Direct +🍃 Proxy + +🏃 Auto : auto +ProxyHeader \ No newline at end of file diff --git a/Rules-lhie1-Quantumult/defaultConf/urlrewriteSettings b/Rules-lhie1-Quantumult/defaultConf/urlrewriteSettings new file mode 100644 index 0000000..e164c55 --- /dev/null +++ b/Rules-lhie1-Quantumult/defaultConf/urlrewriteSettings @@ -0,0 +1 @@ +// custom url rewrite \ No newline at end of file diff --git a/Rules-lhie1-Quantumult/docs.md b/Rules-lhie1-Quantumult/docs.md new file mode 100644 index 0000000..aa09bb0 --- /dev/null +++ b/Rules-lhie1-Quantumult/docs.md @@ -0,0 +1,59 @@ +### Rules-lhie1脚本使用简介 + +#### 主界面名词 +- 去广告:加载去广告规则(现有总规则6000+) +- 开启MITM:不选中则不使用MITM证书,选中后默认使用lhie1的证书(CA 4B676386),可以在进阶设置中进行自定义 +- UDP:对节点进行UDP支持 +- 导出配置:采用Action Sheet将.conf配置文件导出,不打开则尝试直接导出配置文件到Surge 3 +- 进阶设置:可自行配置常规(General)、代理分组(Proxy Group)、代理规则(Custom Rule)、本地DNS映射(Host)、URL重定向(URL Rewrite)、Header修改(Header Rewrite)、SSID、主机名(Hostname)和配置根证书(MITM),也可一键还原全部进阶设置 +- 生成配置:拉取规则生成.conf配置文件 + +#### 节点导入编辑 +> 这是脚本的一个特点,可以对节点进行编辑筛选等操作。 + +- 导入:可以通过剪贴板或二维码导入,支持``ss://链接``、``Surge链接``和``托管地址``,前两者也支持批量导入 +- 倒序:将节点顺序颠倒,方便不同机场节点选择 +- Auto:在列表中节点选中即为加入Auto测速,不选择默认Auto为Direct,此时需要手动选择节点否则就是直连,全选状态下再次点击则全反选 +- 特殊代理:节点左滑即可将节点设置为某些链接的特殊代理,按钮可以设置Direct、查看设置和清除特殊代理设置 +- 节点删除:在节点列表左滑选择删除 +- 节点重命名:节点列表左滑选择重命名(方便部分不带节点名节点改名) +- 节点排序:节点列表长按拖动即可调整顺序 + +#### 新手指导 +熟悉以上内容后,只需根据自己的需求,导入节点,选择Auto,选中开关,拉取规则生成即可享受最新的lhie1规则,而且在托管没有发生变化的情况下,每次进入脚本都可以直接拉取生成最新规则不需要重新配置。 + +#### 进阶设置 +> 进阶设置配置完毕后即可自动保存 + +- 常规(General):常规配置内容,可以设置DNS、Replica等内容 +- 代理分组(Proxy Group):配置代理组内容,每个组别都可以通过``Proxy Header(有空格)``代表所有节点名,``ProxyHeader(无空格)``代替Auto节点名 +- 代理规则(Custom Rule):自定义规则配置,将需要添加的规则添加到规则的最上面,如果要删除lhie1的规则,在规则前添加``-``即可,添加的优先级较高 +- 本地DNS映射(Host):自定义本地DNS映射 +- URL重定向(URL Rewrite):自定义URL重定向 +- Header修改(Header Rewrite):自定义Header修改 +- SSID:自定义SSID +- 主机名(Hostname):自定义主机名 +- MITM:用于配置根证书,默认为lhie1的证书(CA 4B676386),也可自行配置 + +注: +1.以上进阶设置请在“知道自己在做什么”的情况下修改; +2.如果信息填错导致Surge无法启动,可以将对应部分清空保存即可自动恢复为默认,也可一键还原全部进阶设置。 + +#### JSBox小组件 +> 可在iOS的Widget页面中添加使用,使用前需先在JSBox的小组件设置中选中该脚本。小组件视图高度可考虑设置为120。 + +- Surge3启动/停止运行(红色Surge3图标):利用Surge3提供的URL Scheme,可做到Surge3一键启动/停止运行 +- 生成配置(绿色下载图标):一键生成配置,并根据个人脚本中的设置,自动执行导出到Surge3或者打开导出配置的系统选单 +- 打开脚本(蓝色JSBox图标):从小组件进到该JSBox脚本的主界面 +- 规则更新提示:规则有更新时,小组件下方会有一行小字进行提示,如规则无更新则没有小字 + +#### URL Scheme +脚本支持自动根据配置拉取最新规则,URL Scheme如下: +``` +jsbox://run?name=Rules-lhie1&auto=1 +``` + +#### 最后 +目前该JSBox脚本只支持生成Surge3的配置,生成配置的各项功能已经能替代大部分的Workflow的自定义功能。 + +如觉得这个脚本好用,愿意打赏,感激不尽。 diff --git a/Rules-lhie1-Quantumult/donate.md b/Rules-lhie1-Quantumult/donate.md new file mode 100644 index 0000000..e83ed2a --- /dev/null +++ b/Rules-lhie1-Quantumult/donate.md @@ -0,0 +1,18 @@ +### 感谢名单 + +> 仅日期排序,如隐私原因不需公布请私聊告知 + +- 剛剛好 +- 匿名 * n +- T Dug? +- Drew +- shawn +- 风碎的云 +- 純屬 娛樂 +- plplpmmmmh +- ngsooooo +- Mornwind +- Nicked +- Wangsc1 M + +衷心感谢对脚本和我支持的各位! diff --git a/Rules-lhie1-Quantumult/main.js b/Rules-lhie1-Quantumult/main.js new file mode 100644 index 0000000..626e6e8 --- /dev/null +++ b/Rules-lhie1-Quantumult/main.js @@ -0,0 +1,79 @@ +const app = require('scripts/app') +const today = require('scripts/today') +const extension = require('scripts/extension') +const updateUtil = require('scripts/updateUtil') + +$app.autoKeyboardEnabled = true +$app.rotateDisabled = true +// $app.keyboardToolbarEnabled = true + +let query = $context.query + +let bid = $objc("NSBundle").invoke("mainBundle.bundleIdentifier").rawValue() + +if (query.auto == 1) { + app.autoGen() + return +} + +if ($app.env === $env.today) { + today.renderTodayUI(bid) + return +} else if ($app.env === $env.safari) { + extension.renderExtensionUI() + return +} else if ($app.env === $env.action) { + extension.collectRules() + return +} + +app.setUpWorkspace() + +app.renderUI() + +updateUtil.getLatestVersion({ + handler: version => { + console.log([version, updateUtil.getCurVersion()]) + if (updateUtil.needUpdate(version, updateUtil.getCurVersion())) { + $http.get({ + url: 'https://raw.githubusercontent.com/Fndroid/jsbox_script/master/Rules-lhie1/updateLog.md' + '?t=' + new Date().getTime(), + handler: resp=> { + $ui.push({ + props: { + title: "更新可用" + }, + views: [{ + type: "markdown", + props: { + id: "", + content: `## 新版:${version}\n> 当前:${updateUtil.getCurVersion()}\n\n${resp.data}` + }, + layout: (make, view) => { + make.width.equalTo(view.super) + make.height.equalTo(view.super).offset(-50) + } + }, { + type: 'button', + props: { + title: "更新", + id: "updateBtn" + }, + layout: (make, view)=> { + make.bottom.equalTo(view.super).offset(-10) + make.width.equalTo(view.super).offset(-30) + make.centerX.equalTo(view.super) + make.height.equalTo(40) + }, + events: { + tapped: sender=> { + updateUtil.updateScript(version) + } + } + }] + }) + } + }) + + } + } +}) diff --git a/Rules-lhie1-Quantumult/scripts/app.js b/Rules-lhie1-Quantumult/scripts/app.js new file mode 100644 index 0000000..59c2fae --- /dev/null +++ b/Rules-lhie1-Quantumult/scripts/app.js @@ -0,0 +1,1388 @@ +const proxyUtil = require('scripts/proxyUitl') +const updateUtil = require('scripts/updateUtil') +const su = require('scripts/sizeUtil') +const cu = require('scripts/colorUtil') +const videoReg = require('scripts/videoReg') +const ruleUpdateUtil = require('scripts/ruleUpdateUtil') + +const FILE = 'data.js' + +const settingKeys = ['dnsSettings', 'proxyGroupSettings', 'customSettings', 'hostSettings', 'urlrewriteSettings', 'headerrewriteSettings', 'hostnameSettings'] + +if (!$file.exists(FILE)) { + $file.write({ + data: $data({ "string": JSON.stringify({ "urls": [] }) }), + path: FILE + }) +} + +setDefaultSettings() + +let screenHeight = $device.info.screen.height +const screenWidth = $device.info.screen.width + +const iPhoneX = screenWidth == 375 && screenHeight == 812 +if (iPhoneX) { + screenHeight -= 48 +} + +const selectedColor = $color("#c1dcf0") +const defaultColor = $color("#ffffff") +const tintColor = $color("#ff6666") +const blackColor = $color("#000000") + +function renderUI() { + $ui.render({ + props: { + title: "Surge3规则生成" + }, + views: [{ + type: "scroll", + props: { + id: "mainView" + }, + layout: $layout.fill, + views: [{ + type: "input", + props: { + id: "fileName", + text: '', + placeholder: "配置名(lhie1)" + }, + layout: (make, view) => { + make.width.equalTo(screenWidth / 2 - 15) + // make.centerX.equalTo(view.super) + make.height.equalTo(40) + make.left.top.equalTo(10) + }, + events: { + returned: sender => { + $("fileName").blur() + saveWorkspace() + } + } + }, { + type: "button", + props: { + id: "serverURL", + title: "导入节点" + }, + layout: (make, view) => { + make.width.equalTo(screenWidth / 2 - 15) + make.height.equalTo(40) + make.left.equalTo($("fileName").right).offset(10) + make.top.right.equalTo(10) + }, + events: { + tapped: sender => { + importMenu({ + handler: (res, name, url) => { + // 如果是托管,url不为undefined + console.log([res, name, url]) + if (!res) { + $ui.alert("没有检测到节点信息") + } + let listData = $("serverEditor").data || [] + let existsSec = listData.find(item => item.url === url) + let section = existsSec || { title: name, rows: [], url: url } + let selectedRows = [] + if (existsSec) { + selectedRows = section.rows.filter(i => cu.isEqual(i.proxyName.bgcolor, selectedColor)).map(i => i.proxyLink) + } + console.error(selectedRows) + section.rows = [] + for (let idx in res) { + if (res[idx].split("=")[1].trim() == 'direct') { + // 过滤直连 + continue + } + let selected = selectedRows.indexOf(res[idx]) >= 0 + section.rows.push({ + proxyName: { text: res[idx].split('=')[0].trim(), bgcolor: selected ? selectedColor : defaultColor }, + proxyLink: res[idx] + }) + } + if (!existsSec) { + listData.push(section) + } + $("serverEditor").data = listData + saveWorkspace() + } + }) + } + } + }, { + type: "matrix", + props: { + id: "serverControl", + columns: 4, + // radius: 5, + itemHeight: 40, + bgcolor: $color("#f0f5f5"), + data: [{ + title: { text: '节点倒序' } + }, { + title: { text: '批量Auto' } + }, { + title: { text: '特殊代理' } + }, { + title: { text: '删除分组' } + }], + template: [{ + type: "label", + props: { + id: "title", + align: $align.center, + font: $font(14) + }, + layout: $layout.fill + }] + }, + 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) => { + if (indexPath.item == 0) { + reverseServerGroup() + } else if (indexPath.item == 1) { + autoServerGroup() + } else if (indexPath.item == 3) { + deleteServerGroup() + } else { + $ui.menu({ + items: ['🚀 Direct', '查看设置', '清除设置'], + handler: function (title, idx) { + if (idx === 0) { + $ui.menu({ + items: Object.keys(videoReg), + handler: function (title, idx) { + let proxyName = '🚀 Direct' + let videoProxy = $("serverEditor").info + videoProxy[title] = proxyName + $("serverEditor").info = videoProxy + saveWorkspace() + } + }) + } else if (idx === 2) { + $("serverEditor").info = {} + saveWorkspace() + } else { + let videoProxy = $("serverEditor").info + let output = [] + for (let k in videoProxy) { + output.push(`${k} - ${videoProxy[k]}`) + } + $ui.alert(output.length > 0 ? output.join('\n') : "无设置特殊代理") + } + } + }) + } + } + } + }, { + type: "list", + props: { + id: "serverEditor", + data: [], + separatorHidden: true, + reorder: true, + actions: [{ + title: "delete", + handler: (sender, indexPath) => { + saveWorkspace() + } + }, { + title: "重命名", + handler: (sender, indexPath) => { + $ui.menu({ + items: ["节点重命名", "组别重命名"], + handler: function (title, idx) { + if (idx === 0) { + let titleText = sender.object(indexPath).proxyName.text + $input.text({ + type: $kbType.default, + placeholder: "请输入节点名", + text: titleText == '无节点名称' ? "" : titleText, + handler: function (text) { + let obj = sender.object(indexPath) + obj.proxyName.text = text + let proxyURLNoName = obj.proxyLink.split("=") + proxyURLNoName.shift() + obj.proxyLink = `${text} =${proxyURLNoName.join("=")}` + listReplace(sender, indexPath, obj) + saveWorkspace() + } + }) + } else { + let od = sender.data + $input.text({ + type: $kbType.default, + placeholder: "请输入组别名称", + text: od[indexPath.section].title, + handler: function (text) { + od[indexPath.section].title = text + sender.data = od + saveWorkspace() + } + }) + } + } + }) + + } + }, { + title: "特殊代理", + handler: (sender, indexPath) => { + let proxyName = sender.object(indexPath).proxyName.text + $ui.menu({ + items: Object.keys(videoReg), + handler: function (title, idx) { + let videoProxy = sender.info + videoProxy[title] = proxyName + sender.info = videoProxy + saveWorkspace() + } + }) + } + }], + borderWidth: 2, + borderColor: $color("#f0f5f5"), + template: { + views: [{ + type: 'label', + props: { + id: 'proxyName', + align: $align.center, + autoFontSize: true + }, + layout: $layout.fill + }] + }, + // radius: 5 + }, + layout: (make, view) => { + make.width.equalTo(view.super).offset(-20) + make.centerX.equalTo(view.super) + make.height.equalTo(screenHeight - 330) + make.top.equalTo($("serverControl").bottom) + }, + events: { + didSelect: (sender, indexPath, data) => { + let proxyName = data.proxyName.text + data.proxyName.bgcolor = cu.isEqual(data.proxyName.bgcolor, selectedColor) ? defaultColor : selectedColor + let uiData = sender.data + uiData[indexPath.section].rows[indexPath.row] = data + sender.data = uiData + saveWorkspace() + }, + reorderFinished: data => { + $("serverEditor").data = data + saveWorkspace() + } + } + }, { + type: "matrix", + props: { + id: "usualSettings", + columns: 2, + // radius: 5, + itemHeight: 40, + // bgcolor: $color("#f0f5f5"), + // borderWidth: 1, + // borderColor: $color("#f0f5f5"), + spacing: 5, + selectable: true, + data: [{ + title: { text: '去广告', bgcolor: defaultColor, textColor: blackColor } + }, { + title: { text: '开启MITM', bgcolor: defaultColor, textColor: blackColor } + }, { + title: { text: 'UDP', bgcolor: defaultColor, textColor: blackColor } + }, { + title: { text: '导出配置', bgcolor: defaultColor, textColor: blackColor } + }], + template: [{ + type: "label", + props: { + id: "title", + align: $align.center, + font: $font(16), + radius: 5, + borderColor: tintColor, + borderWidth: 0.3, + }, + layout: $layout.fill + }] + }, + layout: (make, view) => { + make.width.equalTo(view.super).offset(-10) + make.centerX.equalTo(view.super) + make.height.equalTo(100) + make.top.equalTo($("serverEditor").bottom).offset(10) + }, + events: { + didSelect: (sender, indexPath, data) => { + data.title.bgcolor = cu.isEqual(data.title.bgcolor, tintColor) ? defaultColor : tintColor + data.title.textColor = cu.isEqual(data.title.bgcolor, tintColor) ? defaultColor : blackColor + let uiData = $("usualSettings").data + uiData[indexPath.row] = data + $("usualSettings").data = uiData + saveWorkspace() + } + } + },] + }, { + type: "button", + props: { + id: "advanceBtn", + title: "进阶设置", + bgcolor: $color("#808080") + }, + layout: (make, view) => { + make.width.equalTo((screenWidth / 2 - 15) * 0.686 - 10) + // make.centerX.equalTo(view.super) + make.left.equalTo(10) + make.height.equalTo(40) + make.top.equalTo($("usualSettings").bottom).offset(5) + }, + events: { + tapped: sender => { + renderAdvanceUI() + } + } + }, { + type: "button", + props: { + id: "aboutBtn", + title: "关于", + bgcolor: $color("#808080") + }, + 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", + title: "生成配置" + }, + layout: (make, view) => { + make.width.equalTo((screenWidth - 10) * 0.5 - 5) + // make.centerX.equalTo(view.super) + 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 => { + $("progressBar").value = p + }, + onDone: res => { + $ui.animate({ + duration: 0.3, + animation: function () { + $("progressView").alpha = 0 + }, + completion: function () { + $("progressView").value = 0 + $("progressView").hidden = true + } + }) + exportConf(res.fileName, res.fileData, res.actionSheet, () => { + $http.stopServer() + }) + $app.listen({ + resume: function () { + $http.stopServer() + } + }) + }, + onError: res => { + $("progressView").value = 0 + $("progressView").hidden = true + } + }) + } + } + }, { + type: "blur", + props: { + id: "progressView", + style: 1, + alpha: 0, + hidden: true + }, + layout: $layout.fill, + views: [{ + type: "label", + props: { + 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) + } + }] + }] + }) + +} + +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 getPrototype(done) { + return new Promise((resolve, reject) => { + $http.get({ + url: "https://raw.githubusercontent.com/lhie1/Rules/master/Surge/Prototype.conf", + handler: function (resp) { + if (done) done() + resolve(resp.data) + } + }) + }) +} + +function getAutoRules(url, done) { + return new Promise((resolve, reject) => { + $http.get({ + url: url, + handler: function (resp) { + if (done) done() + resolve(resp.data) + } + }) + }) +} + +function importMenu(params) { + let staticItems = ['剪贴板', '二维码', '更新列表节点'] + let savedURLS = JSON.parse($file.read(FILE).string).urls + console.log(savedURLS) + for (let i = 0; i < savedURLS.length && i < 3; i++) { + staticItems.unshift(savedURLS[i].name || savedURLS[i]) + } + $ui.menu({ + items: staticItems, + handler: function (title, idx) { + let staticIdx = idx - savedURLS.length + if (staticIdx == 0) { + let clipText = $clipboard.text + linkHandler(clipText, params) + } else if (staticIdx == 1) { + $qrcode.scan({ + handler(string) { + linkHandler(string, params) + } + }) + } else if (staticIdx == 2) { + let listSections = $("serverEditor").data + linkHandler(listSections.map(i => i.url).join('\n'), params) + } else { + let lm = savedURLS.length - 1 + let url = savedURLS[lm - idx].url || savedURLS[lm - idx] + console.log([url, idx]) + linkHandler(url, params) + } + } + }) +} + +function linkHandler(url, params) { + let servers = { + shadowsocks: [], + surge: [], + online: [], + ignore: [] + } + + 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|http|https|socks5|socks5-tls)/.test(item)) { + servers.surge.push(item) + } else { + servers.ignore.push(item) + } + }) + + console.log(servers) + + for (let k in servers) { + if (servers[k].length === 0) { + continue + } + if (k === 'shadowsocks') { + console.log(servers[k]) + proxyUtil.proxyFromURL({ + ssURL: servers[k], + handler: res => { + params.handler(res.servers, res.sstag, servers[k].join('\n')) + saveURL(servers[k].join('\n'), res.sstag) + } + }) + } 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(result, urls.length > 1 ? `批量Surge链接(${urls.length})` : result[0].split('=')[0].trim(), urls.join('\n')) + saveURL(urls.join('\n'), urls.length > 1 ? `批量Surge链接(${urls.length})` : result[0].split('=')[0].trim()) + }) + } else if (k === 'online') { + $ui.loading(true) + proxyUtil.proxyFromConf({ + urls: servers[k], + handler: res => { + $ui.loading(false) + params.handler(res.servers, res.filename, res.url) + saveURL(res.url, res.filename) + } + }) + } else { + $ui.alert('剪贴板存在无法识别的行:\n\n' + servers.ignore.join('\n') + '\n\n以上行将被丢弃!') + } + } +} + +function saveURL(url, name) { + let settings = JSON.parse($file.read(FILE).string) + let urls = settings.urls + let idx = -1 + urls.forEach((item, i) => { + if (item == url || item.url == url) { + idx = i + } + }) + if (idx > -1) { + urls.splice(idx, 1) + } + urls.push({ url: url, name: name }) + if (urls.length > 3) { + urls.shift() + } + $file.write({ + data: $data({ "string": JSON.stringify(settings) }), + path: FILE + }) +} + +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 inputViewData = [] + for (let idx in settingKeys) { + let content = previewData[settingKeys[idx]] + inputViewData.push({ + type: "text", + props: { + text: content, + bgcolor: $color("#f0f5f5"), + font: $font(14) + }, + events: { + didEndEditing: sender => { + let content = sender.text + if (sender.text == '') { + content = $file.read('defaultConf/' + settingKeys[idx]).string + sender.text = content + } + write2file(settingKeys[idx], content) + } + } + }) + } + let genControlBnts = function (idx) { + let titleTexts = ['DNS', '代理分组', '代理规则', '本地DNS映射', 'URL重定向', 'Header修改', '主机名'] + const sbgc = $color("#ffda40") + const stc = $color("#034769") + const dbgc = $color("#63add0") + const dtc = $color("#ffffff") + 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: "进阶设置" + }, + views: [{ + type: "gallery", + props: { + id: "inputViews", + items: inputViewData, + interval: 0 + }, + layout: (make, view) => { + make.height.equalTo(view.super).dividedBy(2) + make.width.equalTo(view.super) + }, + events: { + changed: sender => { + let idx = sender.page + $("settingsControl").data = genControlBnts(idx) + } + } + }, { + type: "matrix", + props: { + columns: 3, + id: "settingsControl", + itemHeight: 40, + bgcolor: $color("#ffffff"), + spacing: 3, + data: genControlBnts(0), + template: [{ + type: "label", + props: { + id: "title", + align: $align.center, + font: $font(14) + }, + layout: $layout.fill + }] + }, + layout: (make, view) => { + make.height.equalTo(130) + make.centerX.equalTo(view.super) + make.width.equalTo(view.super).offset(0) + make.top.equalTo(view.prev.bottom).offset(5) + }, + events: { + didSelect: (sender, indexPath, data) => { + let idx = indexPath.row + $("settingsControl").data = genControlBnts(idx) + $("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: "关于" + }, + views: [{ + type: "view", + props: { + id: "mainAboutView" + }, + layout: $layout.fill, + 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: ["🗂 脚本简介", "📃 更新日志", "🖥 论坛导航"] + }, + layout: (make, view) => { + make.width.equalTo(view.super) + make.top.equalTo(view.prev.bottom).offset(0) + make.height.equalTo(135) + }, + events: { + didSelect: (sender, indexPath, data) => { + if (indexPath.row == 0) { + previewMD(data, 'docs.md') + } else if (indexPath.row == 1) { + previewMD(data, 'updateLog.md') + } else { + $app.openURL("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: ["🙏 捐献打赏名单", "👍 赏杯咖啡支持作者"] + }, + 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) { + previewMD(data, 'donate.md') + } else { + $ui.menu({ + items: ["支付宝", "微信"], + handler: function (title, idx) { + if (idx == 0) { + $app.openURL($qrcode.decode($file.read("assets/thankyou2.jpg").image)) + } else { + $quicklook.open({ + image: $file.read("assets/thankyou.jpg").image + }) + } + } + }) + } + } + } + }, { + 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"] + }, + 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/Fndroid") + } else { + $app.openURL("https://github.com/Fndroid/jsbox_script/tree/master/Rules-lhie1") + } + } + } + }, { + 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 => i.title) + if (sections.length === 1) { + $("serverEditor").data = [] + saveWorkspace() + return + } + $ui.menu({ + items: sections.concat(['全部删除']), + handler: function (title, idx) { + if (idx === sections.length) { + $("serverEditor").data = [] + } 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() + } + }) +} + +function autoServerGroup() { + let serverData = $("serverEditor").data + let sections = serverData.map(i => i.title) + if (sections.length === 1) { + let allSelected = serverData[0].rows.every(item => cu.isEqual(item.proxyName.bgcolor, selectedColor)) + serverData[0].rows.map(item => { + item.proxyName.bgcolor = allSelected ? defaultColor : selectedColor + return item + }) + $("serverEditor").data = serverData + saveWorkspace() + return + } + $ui.menu({ + items: sections.concat(['全部Auto']), + handler: function (title, idx) { + if (idx === sections.length) { + let flatData = serverData.reduce((all, cur) => { + return { rows: all.rows.concat(cur.rows) } + }).rows + let needColor = defaultColor + if (!flatData.every(i => cu.isEqual(i.proxyName.bgcolor, selectedColor))) { + needColor = selectedColor + } + serverData.map(sec => { + sec.rows.map(item => { + item.proxyName.bgcolor = needColor + return item + }) + return sec + }) + } else { + let sectionData = serverData[idx] + let needColor = defaultColor + if (!sectionData.rows.every(i => cu.isEqual(i.proxyName.bgcolor, selectedColor))) { + needColor = selectedColor + } + sectionData.rows.map(item => { + item.proxyName.bgcolor = needColor + return item + }) + } + $("serverEditor").data = serverData + saveWorkspace() + } + }) +} + +function setUpWorkspace() { + $app.listen({ + ready: function () { + let file = JSON.parse($file.read(FILE).string) + if (file && file.workspace) { + let workspace = file.workspace + console.log(file) + $("fileName").text = workspace.fileName + $("serverEditor").data = workspace.serverData.map(section => { + section.rows.map(item => { + item.proxyName.bgcolor = item.proxyName.bgcolor ? selectedColor : defaultColor + 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 ? tintColor : defaultColor + item.title.textColor = sd.title.textColor ? defaultColor : blackColor + } + return item + }) + $("usualSettings").data = nd + // videoProxy = workspace.videoProxy + $("serverEditor").info = workspace.videoProxy || {} + } + } + }) +} + +function saveWorkspace() { + let workspace = { + fileName: $("fileName").text, + serverData: $("serverEditor").data.map(section => { + // 如果节点选上,则color为true + section.rows.map(item => { + item.proxyName.bgcolor = cu.isEqual(selectedColor, item.proxyName.bgcolor) + return item + }) + return section + }), + usualData: $("usualSettings").data.map(i => { + i.title.bgcolor = cu.isEqual(tintColor, i.title.bgcolor) + i.title.textColor = cu.isEqual(defaultColor, i.title.textColor) + return i + }), + videoProxy: $("serverEditor").info || {} + } + 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 (!(settingKeys[idx] in previewData) || previewData[settingKeys[idx]] == "") { + let defaultValue = $file.read(`defaultConf/${settingKeys[idx]}`).string + previewData[settingKeys[idx]] = defaultValue + } + } + $file.write({ + data: $data({ "string": JSON.stringify(previewData) }), + path: FILE + }) +} + +function autoGen() { + let settings = JSON.parse($file.read(FILE).string) + console.log(settings) + $ui.render({ + props: { + title: "" + }, + layout: $layout.fill, + views: [{ + type: "blur", + props: { + id: "progressView", + style: 1 + }, + layout: $layout.fill, + views: [{ + type: "label", + props: { + 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) + } + }] + }] + }) + $app.listen({ + ready: function () { + makeConf({ + onStart: () => { + console.log('start') + }, + onProgress: p => { + $("progressBar").value = p + }, + onDone: res => { + exportConf(res.fileName, res.fileData, res.actionSheet, () => { + $http.stopServer() + $app.close() + }) + $app.listen({ + resume: function () { + $http.stopServer() + $app.close() + } + }) + }, + onError: res => { + $ui.alert("无法生成配置文件,可能是规则仓库发生变化或网络出现问题") + } + }) + }, + exit: function () { + + } + }) +} + +function makeConf(params) { + 'onStart' in params && params.onStart() + try { + let pu = { + 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' + } + 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 + } + + let ads = usualValue('去广告') + let isMitm = usualValue('开启MITM') + let isTF = usualValue('UDP') + let isActionSheet = usualValue('导出配置') + + let serverEditorData = workspace.serverData + let flatServerData = serverEditorData.reduce((all, cur) => { + return { + rows: all.rows.concat(cur.rows) + } + }, { rows: [] }).rows + + let autoGroup = flatServerData.filter(i => i.proxyName.bgcolor).map(i => i.proxyName.text).join('\n') || 'DIRECT' + let proxies = flatServerData.map(i => { + return i.proxyLink + (isTF ? ',udp-relay=true' : '') + }).join('\n') + let proxyHeaders = flatServerData.map(i => i.proxyName.text).join('\n') + let rules = '' + let prototype = '' + let testFlight = '' + let host = '' + let urlRewrite = '' + let urlReject = '' + let headerRewrite = '' + let hostName = '' + let mitm = '' + + let pgs = 0 + + let onPgs = function () { + pgs += 0.1 + 'onProgress' in params && params.onProgress(pgs) + } + + let emptyPromise = function (done) { + if (done) done() + return Promise.resolve('') + } + + let promiseArray = [ + getAutoRules(pu.apple, onPgs), // 0 + getAutoRules(pu.reject, onPgs), // 1 + getAutoRules(pu.proxy, onPgs), // 2 + getAutoRules(pu.direct, onPgs), // 3 + getAutoRules(pu.host, onPgs), // 4 + getAutoRules(pu.urlrewrite, onPgs), // 5 + getAutoRules(pu.urlreject, onPgs), // 6 + getAutoRules(pu.hostname, onPgs) // 7 + ] + + if (!ads) { + promiseArray[1] = emptyPromise(onPgs) + promiseArray[6] = emptyPromise(onPgs) + } + + Promise.all(promiseArray).then(v => { + prototype = '' + rules += `\n${v[0]}\n${v[1]}\n${v[2]}\n${v[3]}\n` + host = v[4] + urlRewrite = v[5].split(/[\n\r]/g) + .map(i => i.trim()).filter(i => { + let part = i.split(/\s/g) + return part.length === 3 + }).map(i => { + let part = i.split(' ') + return part[2] === '302' ? `${part[0]} url 302 ${part[1]}` : `${part[0]} url modify ${part[1]}` + }).join('\n') + urlReject += v[6] + hostName = v[7].split('\n') + + let seperateLines = function (content) { + return { + add: content.split('\n').filter(i => !i.startsWith('-')).map(i => i.trim()), + delete: content.split("\n").filter(i => i.startsWith('-')).map(i => i.replace('-', '').trim()) + } + } + + let prettyInsert = function (lines) { + return '\n\n' + lines.join('\n') + '\n\n' + } + + let confBlock = function (name, content) { + return `[${name}]\n${content}\n` + } + + // 配置代理分组 + if (advanceSettings.proxyGroupSettings) { + let proxyGroup = advanceSettings.proxyGroupSettings + proxyGroup = proxyGroup.replace(/Proxy Header/g, proxyHeaders) + proxyGroup = proxyGroup.replace(/ProxyHeader/g, autoGroup) + let pp = proxyGroup.replace(/\[POLICY\]\r\n/, '').split('\r\n\r\n') + console.log(pp.map(i => $text.base64Encode(i))) + prototype += confBlock('POLICY', pp.map(i => $text.base64Encode(i)).join('\n')) + } + // 配置dns设置 + if (advanceSettings.dnsSettings) { + prototype = prototype + advanceSettings.dnsSettings + } + // 配置自定义规则 + let customRules = seperateLines(advanceSettings.customSettings) + 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, '') + }) + // 配置MITM的Hostname + let userHostname = seperateLines(advanceSettings.hostnameSettings) + userHostname.delete.forEach(i => { + if (hostName.indexOf(i) >= 0) { + hostName.splice(hostName.indexOf(i), 1) + } + }) + + // 视频代理处理 + let videoProxy = workspace.videoProxy + for (let videoType in videoProxy) { + let proxyName = videoProxy[videoType] + rules.match(videoReg[videoType]).forEach(i => { + rules = rules.replace(i, i.replace('🍃 Proxy', proxyName)) + }) + } + + prototype += confBlock('rule', prettyInsert(customRules.add) + rules + '\nGEOIP,CN,🍂 Domestic \nFINAL,☁️ Others\n') + prototype += confBlock('Proxy', proxies) + prototype += confBlock('HOST', host + prettyInsert(userHost.add)) + prototype += confBlock('REWRITE', urlRewrite + prettyInsert(userUrl.add)) + prototype += confBlock('URL-REJECTION', urlReject) + prototype += confBlock('MITM', 'hostname = ' + hostName.concat(userHostname.add.filter(i => i != '')).join(', ')) + + let fn = (workspace.fileName || 'lhie1') + '.conf' + + if ('onDone' in params) { + ruleUpdateUtil.getGitHubFilesSha({ + handler: sha => { + if (sha) { + ruleUpdateUtil.setFilesSha(sha) + } else { + console.log('sha 获取失败') + } + } + }) + params.onDone({ + actionSheet: isActionSheet, + fileName: fn, + fileData: prototype + }) + } + }).catch(e => { + console.error(e) + }) + } catch (e) { + 'onError' in params && params.onError(e) + } +} + +function exportConf(fileName, fileData, actionSheet, actionSheetCancel) { + 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 = `surge3:///install-config?url=${encodeURIComponent(serverUrl + "download?path=" + fileName)}` + $app.openURL(surgeScheme) + } else { + $ui.alert("内置服务器启动失败,请重试") + } + } + }) + } + }) + } +} + +module.exports = { + renderUI: renderUI, + setUpWorkspace: setUpWorkspace, + autoGen: autoGen +} \ No newline at end of file diff --git a/Rules-lhie1-Quantumult/scripts/colorUtil.js b/Rules-lhie1-Quantumult/scripts/colorUtil.js new file mode 100644 index 0000000..bcfbf83 --- /dev/null +++ b/Rules-lhie1-Quantumult/scripts/colorUtil.js @@ -0,0 +1,7 @@ +function isEqual(color1, color2) { + return color1.runtimeValue().invoke('isEqual', color2) +} + +module.exports = { + isEqual: isEqual +} \ No newline at end of file diff --git a/Rules-lhie1-Quantumult/scripts/extension.js b/Rules-lhie1-Quantumult/scripts/extension.js new file mode 100644 index 0000000..fe722fb --- /dev/null +++ b/Rules-lhie1-Quantumult/scripts/extension.js @@ -0,0 +1,285 @@ +const app = require('scripts/app') + +const FILE = 'data.js' + +let screenHeight = $device.info.screen.height +const screenWidth = $device.info.screen.width + +const iPhoneX = screenWidth == 375 && screenHeight == 812 +if (iPhoneX) { + screenHeight -= 48 +} + +let renderExtensionUI = function () { + const host = $context.safari.items.location.hostname + $ui.render({ + props: { + title: "增加规则" + }, + views: [{ + type: "scroll", + props: { + id: "mainScrollView", + bgcolor: $color("#f0f5f5") + }, + layout: $layout.fill, + views: [{ + type: "label", + props: { + text: "当前网页路径" + }, + layout: (make, view) => { + make.width.equalTo(view.super) + make.left.equalTo(5) + make.height.equalTo(30) + make.top.equalTo(view.super.top).offset(10) + } + }, { + type: "text", + props: { + editable: false, + text: $context.safari.items.baseURI + }, + layout: (make, view) => { + make.top.equalTo(view.prev.bottom).offset(5) + make.width.equalTo(view.super) + make.height.equalTo(60) + } + }, { + type: "label", + props: { + text: "规则匹配项" + }, + layout: (make, view) => { + make.width.equalTo(view.super) + make.left.equalTo(5) + make.height.equalTo(30) + make.top.equalTo(view.prev.bottom).offset(10) + } + }, { + type: "list", + props: { + rowHeight: 60, + id: "styleList", + template: { + views: [{ + type: "label", + props: { + id: "styleName", + font: $font("bold", 16) + }, + layout: (make, view) => { + make.width.equalTo(view.super) + make.height.equalTo(view.super).dividedBy(2) + make.left.equalTo(view.super.left).offset(15) + make.top.equalTo(view.super.top).offset(5) + } + }, { + type: "label", + props: { + id: "styleValue", + }, + layout: (make, view) => { + make.width.equalTo(view.super) + make.height.equalTo(view.super).dividedBy(2) + make.left.equalTo(view.super.left).offset(15) + make.top.equalTo(view.prev.bottom).offset(-10) + } + }, { + type: "image", + props: { + id: "styleSelected", + bgcolor: $color("#ffffff") + }, + layout: (make, view) => { + make.centerY.equalTo(view.super) + make.height.width.equalTo(20) + make.right.equalTo(view.super.right).offset(-20) + } + }] + } + }, + layout: (make, view) => { + make.top.equalTo(view.prev.bottom).offset(5) + make.width.equalTo(view.super) + make.height.equalTo(0) + }, + events: { + didSelect: (sender, indexPath, data) => { + let uiData = sender.data.map((i, idx) => { + return { + styleName: i.styleName, + styleValue: i.styleValue, + styleSelected: { data: null } + } + }) + uiData[indexPath.row].styleSelected = { data: $file.read("assets/selected_icon.png") } + sender.data = uiData + sender.info = indexPath.row + } + } + }, { + type: "label", + props: { + text: "选择代理" + }, + layout: (make, view) => { + make.width.equalTo(view.super) + make.left.equalTo(5) + make.height.equalTo(30) + make.top.equalTo(view.prev.bottom).offset(10) + } + }, { + type: "list", + props: { + id: "proxyList", + rowHeight: 40, + template: { + views: [{ + type: "label", + props: { + id: "proxyName" + }, + layout: (make, view) => { + make.centerY.equalTo(view.super) + make.width.equalTo(view.super).offset(-80) + make.height.equalTo(view.super) + make.left.equalTo(view.super.left).offset(15) + } + }, { + type: "image", + props: { + id: "proxySelected", + bgcolor: $color("#ffffff") + }, + layout: (make, view) => { + make.centerY.equalTo(view.super) + make.height.width.equalTo(20) + make.right.equalTo(view.super.right).offset(-20) + } + }] + } + }, + layout: (make, view) => { + make.top.equalTo(view.prev.bottom).offset(5) + make.width.equalTo(view.super) + make.height.equalTo(0) + }, + events: { + didSelect: (sender, indexPath, data) => { + let uiData = sender.data.map((i, idx) => { + return { + proxyName: i.proxyName, + proxySelected: { data: null } + } + }) + uiData[indexPath.row].proxySelected = { data: $file.read("assets/selected_icon.png") } + sender.data = uiData + sender.info = indexPath.row + } + } + }, { + type: "label", + props: { + text: "添加规则", + align: $align.center, + bgcolor: $color("#fff") + }, + layout: (make, view) => { + make.width.equalTo(view.super) + make.height.equalTo(40) + make.top.equalTo(view.prev.bottom).offset(20) + }, + events: { + tapped: sender => { + if ($("styleList").info !== null && $("proxyList").info !== null) { + let styleName = $("styleList").data[$("styleList").info].styleName.text + let styleValue = $("styleList").data[$("styleList").info].styleValue.text + let proxy = $("proxyList").data[$("proxyList").info].proxyName.text + let rule = [styleName, styleValue, proxy].join(',') + saveRule(rule) + $context.close() + } else { + $ui.alert("请选择匹配项和代理") + } + } + } + }] + }] + }) + let styles = genStyleDefaultData(host) + let styleListHeight = styles.length * 60 + $("styleList").data = styles + $("styleList").updateLayout(make => { + make.height.equalTo(styleListHeight) + }) + let savedData = JSON.parse($file.read(FILE).string) + let proxyGroup = savedData.proxyGroupSettings.split('\n').filter(i => /^[\s\S]+=/.test(i)).map(i => i.split(/[\s]*=/)[0]) + let flatServerData = savedData.workspace.serverData.reduce((all, cur) => { + return { + rows: all.rows.concat(cur.rows) + } + }).rows + let proxies = ['🚀 Direct'].concat(proxyGroup, ['REJECT', 'REJECT-TINYGIF'], flatServerData.map(i => i.proxyName.text)) + let proxiesData = genProxyDefaultData(proxies) + let proxiesListHeight = proxiesData.length * 40 + $("proxyList").data = proxiesData + $("proxyList").updateLayout(make => { + make.height.equalTo(proxiesListHeight) + }) + resizeScrollView(styleListHeight + proxiesListHeight) + +} + +let resizeScrollView = function (listHeight) { + $("mainScrollView").contentSize = $size(screenWidth, 350 + listHeight) +} + +let genStyleDefaultData = function (domain) { + let dp = domain.split('.') + let res = [] + res.push({ + styleName: { text: 'DOMAIN' }, + styleValue: { text: domain } + }) + return res.concat(dp.map((item, idx, obj) => { + return { + styleName: { text: 'DOMAIN-SUFFIX' }, + styleValue: { text: obj.slice(idx).join('.') } + } + })) +} + +let saveRule = function (rule) { + let data = JSON.parse($file.read(FILE).string) + data.customSettings += `\n${rule}\n` + $file.write({ + data: $data({ "string": JSON.stringify(data) }), + path: FILE + }) +} + +let genProxyDefaultData = function (proxies) { + return proxies.map(i => { + return { + proxyName: { text: i } + } + }) +} + +function collectRules() { + let confFile = $context.data.string + let reg = /\[Rule\]\n([\s\S]+)\n# Custom/ + let matcher = confFile.match(reg) + if (matcher.length === 2) { + let rules = matcher[1] + saveRule(rules) + } + $context.close() +} + + +module.exports = { + renderExtensionUI: renderExtensionUI, + collectRules: collectRules +} \ No newline at end of file diff --git a/Rules-lhie1-Quantumult/scripts/filenameUtil.js b/Rules-lhie1-Quantumult/scripts/filenameUtil.js new file mode 100644 index 0000000..a7ddc37 --- /dev/null +++ b/Rules-lhie1-Quantumult/scripts/filenameUtil.js @@ -0,0 +1,22 @@ +const knownURLs = [ + {domain: 'api.rixcloud.io', name: 'rixCloud'} +] + +function getConfName(url) { + let matchConst = knownURLs.find(i => url.indexOf(i.domain) > -1) + if (matchConst) { + return matchConst.name + } else { + let path = url.split('?')[0].split('/') + let filename = path[path.length - 1] + if (filename.indexOf('.conf') == filename.length - 5) { + return filename.substring(0, filename.length - 5) + } else { + return filename + } + } +} + +module.exports = { + getConfName: getConfName +} \ No newline at end of file diff --git a/Rules-lhie1-Quantumult/scripts/proxyUitl.js b/Rules-lhie1-Quantumult/scripts/proxyUitl.js new file mode 100644 index 0000000..9dd7a32 --- /dev/null +++ b/Rules-lhie1-Quantumult/scripts/proxyUitl.js @@ -0,0 +1,106 @@ +const filenameUtil = require('scripts/filenameUtil') + +String.prototype.strictTrim = function() { + let trimed = this.trim() + if ((matcher = trimed.match(/([\s\S]+),$/)) !== null) { + return matcher[1] + } + return trimed +} + +function promiseConf(url) { + return new Promise((resolve, reject) => { + $http.get({ + url: url, + handler: function (resp) { + let data = resp.data + let filename = url + try { + let matcher = resp.response.runtimeValue().invoke('allHeaderFields').rawValue()["Content-Disposition"].match(/filename=(.*?).conf/) + filename = matcher[1] + } catch (e) { + filename = filenameUtil.getConfName(url) + } + let servers = data.match(/\[Proxy\]([\s\S]*?)\[Proxy Group\]/) + if (servers != null) { + resolve({ + servers: servers[1], + filename: filename + }) + } else { + reject() + } + } + }) + }) +} + +function getServersFromConfFile(params) { + let promiseArray = params.urls.map(i => promiseConf(i)) + Promise.all(promiseArray).then(confs => { + confs.forEach((res, idx) => { + console.log(res.servers) + let servers = res.servers.split(/[\n\r]+/).filter(item => item !== '').map(i => i.strictTrim()) + params.handler({ servers: servers, filename: res.filename, url: params.urls[idx] }) + }) + }).catch(reason => { + params.handler(null) + }) +} + +function decodeScheme(params) { + let urls = params.ssURL + let result = [] + let tag + + for (let idx in urls) { + let url = urls[idx] + let method, password, hostname, port, plugin + if (!url.includes('#')) { + let name = '无节点名称' + url += `#${name}` + } + tag = $text.URLDecode(url.match(/#(.*?)$/)[1]) + if (url.includes('?')) { + // tag = $text.URLDecode(url.match(/#(.*?)$/)[1]) + let mdps = url.match(/ss:\/\/(.*?)@/)[1] + let padding = 4 - mdps.length % 4 + if (padding < 4) { + mdps += Array(padding + 1).join('=') + } + let userinfo = $text.base64Decode(mdps) + method = userinfo.split(':')[0] + password = userinfo.split(':')[1] + let htpr = url.match(/@(.*?)\?/)[1].replace('\/', '') + hostname = htpr.split(':')[0] + port = htpr.split(':')[1] + let ps = $text.URLDecode(url.match(/\?(.*?)#/)[1]) + let obfsMatcher = ps.match(/obfs=(.*?)(;|$)/) + let obfsHostMatcher = ps.match(/obfs-host=(.*?)(;|$)/) + if (obfsMatcher) { + let obfs = obfsMatcher[1] + let obfsHost = obfsHostMatcher? obfsHostMatcher[1] : 'cloudfront.net' + plugin = `obfs=${obfs}, obfs-host=${obfsHost}` + } + } else { + let mdps = url.match(/ss:\/\/(.*?)#/)[1] + let padding = 4 - mdps.length % 4 + if (padding < 4) { + mdps += Array(padding + 1).join('=') + } + [method, password, hostname, port] = $text.base64Decode(mdps).split(/[:,@]/) + } + let proxy = `${tag} = custom, ${hostname}, ${port}, ${method}, ${password}, http://omgib13x8.bkt.clouddn.com/SSEncrypt.module` + if (plugin != undefined) { + proxy += `, ${plugin}` + } + result[idx] = proxy + } + + params.handler({ servers: result, sstag: result.length > 1? `批量ss节点(${result.length})`: tag }) +} + +module.exports = { + proxyFromConf: getServersFromConfFile, + proxyFromURL: decodeScheme +} \ No newline at end of file diff --git a/Rules-lhie1-Quantumult/scripts/ruleUpdateUtil.js b/Rules-lhie1-Quantumult/scripts/ruleUpdateUtil.js new file mode 100644 index 0000000..5cd0377 --- /dev/null +++ b/Rules-lhie1-Quantumult/scripts/ruleUpdateUtil.js @@ -0,0 +1,60 @@ +const repo = 'https://api.github.com/repos/lhie1/Rules/contents/Auto' +const commits = 'https://api.github.com/repos/lhie1/Rules/commits?path=Auto&sha=master' + +const FILE = 'data.js' + +function checkUpdate(oldSha, newSha) { + return Object.keys(newSha).some(i => oldSha[i] !== newSha[i]) +} + +function setFilesSha(sha) { + let file = JSON.parse($file.read(FILE).string) + file['repoSha'] = sha + $file.write({ + data: $data({ "string": JSON.stringify(file) }), + path: FILE + }) +} + +function getFilesSha() { + let file = JSON.parse($file.read(FILE).string) + return file['repoSha'] || {} +} + +function getGitHubFilesSha(params) { + $http.get({ + url: repo, + handler: function (resp) { + if (resp.response.statusCode === 200) { + let res = {} + resp.data.forEach(i => { + res[i.name] = i.sha + }) + params.handler(res) + } else { + params.handler({}) + } + } + }) +} + +function getLatestCommitMessage(params) { + $http.get({ + url: commits, + handler: function(resp) { + if (resp.response.statusCode === 200 && resp.data.length > 0) { + params.handler(resp.data[0]) + } else { + params.handler(null) + } + } + }) +} + +module.exports = { + checkUpdate: checkUpdate, + getGitHubFilesSha: getGitHubFilesSha, + setFilesSha: setFilesSha, + getFilesSha: getFilesSha, + getLatestCommitMessage: getLatestCommitMessage +} \ No newline at end of file diff --git a/Rules-lhie1-Quantumult/scripts/sizeUtil.js b/Rules-lhie1-Quantumult/scripts/sizeUtil.js new file mode 100644 index 0000000..e976afa --- /dev/null +++ b/Rules-lhie1-Quantumult/scripts/sizeUtil.js @@ -0,0 +1,8 @@ +function pc(x) { + let sw = $device.info.screen.width + return x * sw / 100 +} + +module.exports = { + pc: pc +} \ No newline at end of file diff --git a/Rules-lhie1-Quantumult/scripts/today.js b/Rules-lhie1-Quantumult/scripts/today.js new file mode 100644 index 0000000..82cc1f0 --- /dev/null +++ b/Rules-lhie1-Quantumult/scripts/today.js @@ -0,0 +1,238 @@ +const ruleUpdateUtil = require('scripts/ruleUpdateUtil') +const updateUtil = require('scripts/updateUtil') + +const loadingHint = "检查规则/脚本更新..." + +const sw = $device.info.screen.width + +let pm = function (method) { + return new Promise((resolve, reject) => { + method({ + handler: res => { + resolve(res) + } + }) + }) +} + +function renderTodayUI(bid) { + let isLauncher = bid === 'app.cyan.jsbox.ghost' + let checks = [pm(ruleUpdateUtil.getGitHubFilesSha), pm(updateUtil.getLatestVersion)] + Promise.all(checks).then(res => { + let canUpdate = ruleUpdateUtil.checkUpdate(ruleUpdateUtil.getFilesSha(), res[0]) + let newVersion = updateUtil.needUpdate(res[1], updateUtil.getCurVersion()) + $("newTag").hidden = !canUpdate + $("newVersionTag").hidden = !newVersion + return canUpdate ? pm(ruleUpdateUtil.getLatestCommitMessage) : Promise.resolve() + }).then(res => { + $("updateStatus").text = res? res.commit.message : "" + }) + $ui.render({ + props: { + id: "todayMainView", + title: "Surge3规则生成", + frame: $rect(0, 0, sw, 110), + hideNavbar: true, + navBarHidden: true, + bgcolor: $color("clear"), + }, + views: [{ + type: "blur", + props: { + id: "close", + style: 1, + radius: 0, + hidden: !isLauncher + }, + layout: (make, view) => { + make.width.height.equalTo(view.super).offset(10) + make.top.equalTo(view.super.top).offset(-10) + }, + events: { + tapped: sender => { + $app.close(0.3) + } + } + }, { + type: "view", + props: { + id: "", + }, + layout: (make, view) => { + make.height.equalTo(110) + make.width.equalTo(view.super).offset(0) + make.center.equalTo(view.super) + }, + views: [{ + type: "label", + props: { + id: "updateStatus", + text: "Rules-lhie1 by Fndroid", + font: $font(12), + textColor: $rgba(50, 50, 50, .3) + }, + layout: (make, view) => { + make.top.equalTo(view.super.top).offset(5) + make.centerX.equalTo(view.super) + } + }, { + type: "label", + props: { + id: "updateStatus", + text: loadingHint, + font: $font(12), + textColor: $rgba(50, 50, 50, .5) + }, + layout: (make, view) => { + make.bottom.equalTo(view.super.bottom).offset(-5) + make.centerX.equalTo(view.super) + } + }, { + type: "image", + props: { + id: "pullBtn", + data: $file.read("assets/today_pull.png"), + radius: 25, + bgcolor: $rgba(255, 255, 255, 0) + }, + layout: (make, view) => { + make.width.height.equalTo(55) + make.centerY.equalTo(view.super).offset(-10) + make.centerX.equalTo(view.super) + }, + events: { + tapped: sender => { + $app.openURL("jsbox://run?name=Rules-lhie1&auto=1") + } + }, + }, { + type: "image", + props: { + id: "surgeBtn", + data: $file.read("assets/today_surge.png"), + radius: 25, + bgcolor: $rgba(255, 255, 255, 0) + }, + layout: (make, view) => { + make.width.height.equalTo(55) + make.centerY.equalTo(view.super).offset(-10) + make.right.equalTo(view.prev.left).offset(- (sw / 9)) + }, + events: { + tapped: sender => { + $app.openURL("surge3:///toggle?autoclose=true") + } + } + }, { + type: "image", + props: { + id: "jsboxBtn", + data: $file.read("assets/today_jsbox.png"), + radius: 25, + bgcolor: $rgba(255, 255, 255, 0) + }, + layout: (make, view) => { + make.width.height.equalTo(50) + make.centerY.equalTo(view.super).offset(-10) + make.left.equalTo(view.prev.prev.right).offset((sw / 9)) + }, + events: { + tapped: sender => { + $app.openURL("jsbox://run?name=Rules-lhie1") + } + } + }, { + type: "label", + props: { + text: "更新规则", + font: $font(12), + textColor: $rgba(50, 50, 50, .8), + align: $align.center + }, + layout: (make, view) => { + make.height.equalTo(10) + make.top.equalTo($("pullBtn").bottom) + make.width.equalTo($("pullBtn").width) + make.centerX.equalTo($("pullBtn")) + } + }, { + type: "label", + props: { + text: "Surge开关", + font: $font(12), + textColor: $rgba(50, 50, 50, .8), + align: $align.center + }, + layout: (make, view) => { + make.height.equalTo(10) + make.top.equalTo(view.prev.top) + make.width.equalTo($("pullBtn").width) + make.centerX.equalTo($("surgeBtn")) + } + }, { + type: "label", + props: { + text: "脚本设置", + font: $font(12), + textColor: $rgba(50, 50, 50, .8), + align: $align.center + }, + layout: (make, view) => { + make.height.equalTo(10) + make.top.equalTo($("pullBtn").bottom) + make.width.equalTo($("pullBtn").width) + make.centerX.equalTo($("jsboxBtn")) + } + }, { + type: "image", + props: { + id: "newTag", + data: $file.read("assets/new_rules_tag.png"), + bgcolor: $rgba(255, 255, 255, 0), + hidden: true + }, + layout: (make, view) => { + make.width.height.equalTo(15) + make.centerY.equalTo(view.super).offset(-20) + make.left.equalTo($("pullBtn").right).offset(-10) + } + }, { + type: "image", + props: { + id: "newVersionTag", + data: $file.read("assets/new_version_tag.png"), + bgcolor: $rgba(255, 255, 255, 0), + hidden: true + }, + layout: (make, view) => { + make.width.height.equalTo(15) + make.centerY.equalTo(view.super).offset(-20) + make.left.equalTo($("jsboxBtn").right).offset(-10) + } + }, { + type: "image", + props: { + id: "closeBtn", + data: $file.read("assets/close_icon.png"), + bgcolor: $rgba(255, 255, 255, 0), + hidden: !isLauncher, + alpha: 0.7 + }, + layout: (make, view) => { + make.width.height.equalTo(20) + make.top.equalTo(view.super.top).offset(10) + make.right.equalTo(view.super.right).offset(-10) + }, + events: { + tapped: sender => { + $app.close(.2) + } + } + }] + }] + }) +} + +module.exports = { + renderTodayUI: renderTodayUI +} \ No newline at end of file diff --git a/Rules-lhie1-Quantumult/scripts/updateUtil.js b/Rules-lhie1-Quantumult/scripts/updateUtil.js new file mode 100644 index 0000000..5e4af7b --- /dev/null +++ b/Rules-lhie1-Quantumult/scripts/updateUtil.js @@ -0,0 +1,62 @@ +function getCurVersion() { + let version = $file.read("version.fndroid").string + return version +} + +function getLatestVersion(params) { + $http.get({ + url: 'https://raw.githubusercontent.com/Fndroid/jsbox_script/master/Rules-lhie1/version.fndroid' + '?t=' + new Date().getTime(), + handler: res => { + params.handler(res.data) + } + }) +} + +function updateScript(version) { + let url = 'https://raw.githubusercontent.com/Fndroid/jsbox_script/master/Rules-lhie1/.output/Rules-lhie1.box' + '?t=' + new Date().getTime() + $ui.loading(true) + $http.download({ + url: url, + progress: (writed, total) => { + if (writed == total) { + $delay(1, function() { + $ui.alert({ + message: "更新成功,是否重启?", + actions: [{ + title: "Cancel", + handler: function () {} + }, { + title: "OK", + handler: function () { + $addin.run("Rules-lhie1") + } + }] + }) + }) + } + }, + handler: resp => { + $ui.loading(false) + let box = resp.data + $addin.save({ + name: "Rules-lhie1", + data: box + }) + } + }) +} + +function needUpdate(nv, ov) { + let getVersionWeight = i => { + return i.split('.').map(i => i * 1).reduce((s, i) => s * 100 + i) + } + return getVersionWeight(nv) > getVersionWeight(ov) +} + + +module.exports = { + getCurVersion: getCurVersion, + getLatestVersion: getLatestVersion, + updateScript: updateScript, + needUpdate: needUpdate +} \ No newline at end of file diff --git a/Rules-lhie1-Quantumult/scripts/videoReg.js b/Rules-lhie1-Quantumult/scripts/videoReg.js new file mode 100644 index 0000000..a8482d7 --- /dev/null +++ b/Rules-lhie1-Quantumult/scripts/videoReg.js @@ -0,0 +1,8 @@ +module.exports = { + Netflix: /(?=USER-AGENT,Argo\*,|DOMAIN-SUFFIX,netflix.com,|DOMAIN-SUFFIX,netflix.net,|DOMAIN-SUFFIX,nflxext.com,|DOMAIN-SUFFIX,nflximg.com,|DOMAIN-SUFFIX,nflximg.net,|DOMAIN-SUFFIX,nflxvideo.net,)[^\n]+/g, + Spotify: /(?=PROCESS-NAME,Spotify,|DOMAIN-SUFFIX,spoti.fi,|DOMAIN-KEYWORD,spotify,)[^\n]+/g, + YouTube: /(?=USER-AGENT,com.google.ios.youtube\*,|USER-AGENT,YouTube\*,|DOMAIN-SUFFIX,googlevideo.com,|DOMAIN-SUFFIX,youtube.com,)[^\n]+/g, + MytvSUPER: /(?=DOMAIN-KEYWORD,nowtv100,|DOMAIN-KEYWORD,rthklive,|DOMAIN-SUFFIX,mytvsuper.com,|DOMAIN-SUFFIX,tvb.com,)[^\n]+/g, + BBC: /(?=USER-AGENT,BBCiPlayer\*,|DOMAIN-KEYWORD,co.uk,|DOMAIN-KEYWORD,uk-live,|DOMAIN-SUFFIX,bbc.co,|DOMAIN-SUFFIX,bbc.com,)[^\n]+/g, + LINE: /(?=DOMAIN-SUFFIX,lin.ee,|DOMAIN-SUFFIX,line.me,|DOMAIN-SUFFIX,line.naver.jp,|DOMAIN-SUFFIX,line-apps.com,|DOMAIN-SUFFIX,line-cdn.net,|DOMAIN-SUFFIX,line-scdn.net,|DOMAIN-SUFFIX,nhncorp.jp,|IP-CIDR,103.2.28.0\/22,|IP-CIDR,119.235.224.0\/21,|IP-CIDR,119.235.232.0\/23,|IP-CIDR,119.235.235.0\/24,|IP-CIDR,119.235.236.0\/23,|IP-CIDR,125.6.146.0\/24,|IP-CIDR,125.6.149.0\/24,|IP-CIDR,125.6.190.0\/24,|IP-CIDR,125.209.208.0\/20,|IP-CIDR,203.104.103.0\/24,|IP-CIDR,203.104.128.0\/20,|IP-CIDR,203.174.66.64\/26,|IP-CIDR,203.174.77.0\/24,)[^\n]+/g +} \ No newline at end of file diff --git a/Rules-lhie1-Quantumult/strings/en.strings b/Rules-lhie1-Quantumult/strings/en.strings new file mode 100644 index 0000000..ae42947 --- /dev/null +++ b/Rules-lhie1-Quantumult/strings/en.strings @@ -0,0 +1,3 @@ +"OK" = "OK"; +"DONE" = "Done"; +"HELLO_WORLD" = "Hello, World!"; diff --git a/Rules-lhie1-Quantumult/strings/zh-Hans.strings b/Rules-lhie1-Quantumult/strings/zh-Hans.strings new file mode 100644 index 0000000..b2d0c5d --- /dev/null +++ b/Rules-lhie1-Quantumult/strings/zh-Hans.strings @@ -0,0 +1,3 @@ +"OK" = "好的"; +"DONE" = "完成"; +"HELLO_WORLD" = "你好,世界!"; diff --git a/Rules-lhie1-Quantumult/updateLog.md b/Rules-lhie1-Quantumult/updateLog.md new file mode 100644 index 0000000..9b914d3 --- /dev/null +++ b/Rules-lhie1-Quantumult/updateLog.md @@ -0,0 +1,152 @@ +打赏请尽量留下名字,你们就是脚本的贡献者! + +#### 2018.06.02 +1. 小组件/启动器在规则有更新时显示最新的commit message + +#### 2018.05.27 +1. 适配两种奇葩的机场托管节点识别(分别是在节点前面加空格和节点后面加逗号) +2. 修复部分ss节点无法识别问题 +3. 修复windows下Shadowsocks二维码无法识别问题 +4. 打开小组件或启动器时同时检查脚本和规则更新 + +#### 2018.05.26 +1. 修复obfs识别问题 +2. 调整更新等待时间 +3. 区分启动器和小组件样式,启动器加入关闭按钮(性能模式启动器有效) + +#### 2018.05.25 +1. 修正不添加节点不能导出规则问题 +2. 调整启动器启动样式,启动后点击上下两部分空白可退出脚本 + +#### 2018.05.22 +1. 调整Widget样式 + +#### 2018.05.15 +1. 增加一个特性:在Surge里面将配置文件(.conf)分享到脚本,脚本会将当前配置的自定义规则(通过Surge的Extension添加的)追加到进阶设置里。 + +> 1的识别的配置文件需要时脚本生成的且进阶设置的代理规则包括``# Custom``注释 + +#### 2018.05.13 +1. 调整导入节点表现,当列表中已有该组时,会更新已有组 +2. 导入节点增加“更新节点列表”选项,用于升级列表中所有组 + +> 上述1,2仅对1.6.19及以后导入的组别生效 + +#### 2018.05.12 +1. 调整Widget样式,去除启动器启动时的导航栏 +2. 去除输入法上方的提示栏 +3. 修复定义ProxyHeader失败问题 + +#### 2018.05.11 +1. 增加对http/https/socks5/socks5-tls代理类型的支持,统一理解为Surge链接 +2. 增强剪贴板导入,以行为单位支持批量ss链接、Surge链接和托管地址混合导入 + +#### 2018.05.10 +1. 修复Safari拓展节点消失问题 +2. 优化拉取速度,更新规则耗时更短 +3. 当节点列表只有一个分组时,节点倒序、批量Auto和删除节点将不会弹出菜单,直接操作当前唯一组 + +#### 2018.05.09 +1. 调整UI +2. 增加组别支持,每次导入的节点会被分为一组,可以根据组别进行倒序、Auto和删除操作 +3. 取消对``固定脚本``的支持,列表外上滑将不再减少高度 +4. 节点重命名选项可以对所在组别进行重命名 + +> 由于列表结构改变,原本节点列表的数据会丢失,请重新导入 + +#### 2018.05.08 +1. 增加Safari拓展,在页面添加规则 +2. 修复图标过大导致JSBox启动器无法载入问题 +3. 调整``全部Auto``,如果当前已经全选,则全部反选一次 +4. 调整``导入节点``,如果列表中已经存在,则不会重复显示 +5. 修复自动生成时选择取消Action Sheet时脚本不退出问题 + +#### 2018.05.07 +1. 增加小组件支持 +2. 修复批量导入时不能识别``\r``换行的问题 +3. 自动生成后回到JSBox自动退出脚本 +4. 特殊代理增加单独查看、清除按钮,支持选择Direct +5. 调整小组件按钮位置,规则无更新时下方小字不再显示 + +#### 2018.05.06 +1. .conf的所有配置项现在都可以在进阶配置中定义,大部分设置可以使用``-(减号)``对lhie1配置剔除 +2. 内部代码调整,异常请反馈 +3. 部分UI调整 +4. 增加特殊代理设置,左滑节点即可设置 +5. 文档修正,感谢**Mornwind**的PR + +#### 2018.05.05 +1. 增加去自定义规则删除(优先级低于添加),写法: + ![](https://github.com/Fndroid/jsbox_script/blob/master/imgs/Rules-lhie1_pre2.jpg?raw=true) +2. 修复进阶设置Gallery切换按钮不联动的问题 +3. 增加关于页面 + + 感谢**Mornwind、几位匿名赞赏者**的咖啡 + +#### 2018.05.04 +1. 检测文件名,不合法则采用Action Sheet分享 +2. 由于无法检测脚本是否在``固定脚本``中运行,所以增加了上下滑动列表高度变化,虽然很突兀,但是没办法 +3. 增加节点重命名和排序 +4. 进阶设置界面调整,DNS设置改为General,MITM默认为lhie1的,不开启则为空 +5. 修正Surge3跳转 +6. 增加URL Scheme支持,添加``auto=1``即可根据配置自动生成一次规则 + +#### 2018.05.03 +1. 增加直接导出到Surge功能,但有限制,进入Surge后不要等待过长时间确定安装 +2. 更改TestFlight选项名为UDP +3. 尝试突破Surge加载问题 +4. 修复托管direct多余问题 + + 感谢**Nicked、Mornwind**的咖啡 + +#### 2018.05.02 + +1. 增加批量导入ss节点(剪贴板) +2. 增加批量导入Surge节点(剪贴板) +3. 增加生成的过渡动画代替默认的loading +4. 修复TINYGIF的问题 +5. 增加配置Proxy Group,``ProxyHeader``为Auto节点名,``Proxy Header``为所有节点名,可更改Auto设置 +6. 删除导入的相册选择选项,如需此操作请更新JSBox + + 感谢**Wangsc1 M**的咖啡 + +#### 2018.05.01 + +1. 跳转到主应用运行 +2. 调整界面(节点操作栏不再跟随滚动,开关说明) +3. 解除底部按钮固定,适配iPhoneX屏幕 + +> 无法运行无解,我无法复现... + +#### 2018.04.30 A + +1. 每次退出脚本先保存环境,下次进入可以直接使用 +2. 修改图标 +3. 支持导入Surge节点 +4. 修复``墙洞``托管无法识别问题 + +> 仅测试了cordcloud和rixCloud的托管导入,其他如果不能导入的,要不把托管借我测试再改密,要不等别人借我测试 + +> 脚本无法启动问题已经提交JSBox作者调试 + +#### 2018.04.30 + +1. 修复TF无效(节点无udp-relay=true)的问题 +2. 修复升级日志遮挡问题 +3. 由于``$addin.save``无回调,增加2s防止极端情况更新失败 +4. 调整界面,去掉Auto显示框,改为列表显示形式 +5. 增加``MITM``、``DNS``和``Rule Custom``设置 +6. 修正配置可能丢失的问题 +7. 修复输入框遮挡问题 +8. 修正配置文件与wf部分区别 +9. 增加MITM开关 + +> iOS 10.3.3如果能进入脚本,请TG和我说一下 + +#### 2018.04.29 + +1. 不选Auto时,Auto则为DIRECT,生成后请手动选择Proxy +2. 保存的``托管``或``ss://``改为以名称显示(可能不能很好兼容上版本) +3. 增加倒序和全部添加到Auto功能 +3. 调整界面大小 +4. 修正无法识别托管文件名的问题 \ No newline at end of file diff --git a/Rules-lhie1-Quantumult/version.fndroid b/Rules-lhie1-Quantumult/version.fndroid new file mode 100644 index 0000000..753198d --- /dev/null +++ b/Rules-lhie1-Quantumult/version.fndroid @@ -0,0 +1 @@ +1.6.40 \ No newline at end of file diff --git a/Rules-lhie1/.output/Rules-lhie1.box b/Rules-lhie1/.output/Rules-lhie1.box new file mode 100644 index 0000000..a38d9ef Binary files /dev/null and b/Rules-lhie1/.output/Rules-lhie1.box differ diff --git a/Rules-lhie1/.vscode/launch.json b/Rules-lhie1/.vscode/launch.json new file mode 100644 index 0000000..01b4070 --- /dev/null +++ b/Rules-lhie1/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "启动程序", + "console": "internalConsole", + "address": "localhost", + "port": 9229 + } + ] +} \ No newline at end of file diff --git a/Rules-lhie1/README.md b/Rules-lhie1/README.md new file mode 100644 index 0000000..8bb976b --- /dev/null +++ b/Rules-lhie1/README.md @@ -0,0 +1,30 @@ +## lhie1规则生成 + +### 简介 +经过一周的迭代更新,目前脚本已经可以完全替代Workflow甚至能做到Workflow不能做到的如: +1. 由于没有运行动画,生成速度快 +2. 能自由对节点进行排序和筛选等操作 +3. 提供小组件支持,可以快速判断规则是否有更新并快速生成一次配置 +4. 提供Safari拓展支持,可以像Surge3一样在浏览网页时添加对应的规则 +5. 能识别批量ss://或surge链接 +6. 支持URL Scheme一键生成配置文件 + +更多介绍请参考: + +[Rules-lhie1脚本简介](https://github.com/Fndroid/jsbox_script/blob/master/Rules-lhie1/docs.md) + + +[Rules-lhie1脚本更新日志](https://github.com/Fndroid/jsbox_script/blob/master/Rules-lhie1/updateLog.md) + +[打赏名单](https://github.com/Fndroid/jsbox_script/blob/master/Rules-lhie1/donate.md) + +### 界面截图 + +![](https://github.com/Fndroid/jsbox_script/blob/master/imgs/Rules-lhie1_pre6.png?raw=true) + +### 下载脚本 + +[下载Rules-lhie1](https://xteko.com/redir?name=Rules-lhie1&url=https://raw.githubusercontent.com/Fndroid/jsbox_script/master/Rules-lhie1/.output/Rules-lhie1.box) + +### 感谢支持 +![](https://raw.githubusercontent.com/Fndroid/jsbox_script/master/imgs/thankyou.jpg) \ No newline at end of file diff --git a/Rules-lhie1/assets/balloons.json b/Rules-lhie1/assets/balloons.json new file mode 100644 index 0000000..0ed2b5a --- /dev/null +++ b/Rules-lhie1/assets/balloons.json @@ -0,0 +1 @@ +{"v":"5.1.1","fr":60,"ip":0,"op":600,"w":1440,"h":2560,"nm":"Balloon Animation","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"pinkBalloon ","sr":1,"ks":{"o":{"a":0,"k":70,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.852},"o":{"x":0.167,"y":0.151},"n":"0p833_0p852_0p167_0p151","t":52,"s":[856,3484.5,0],"e":[875.959,-515.333,0],"to":[3.32643795013428,-666.638854980469,0],"ti":[-3.32643795013428,666.638854980469,0]},{"t":350}],"ix":2},"a":{"a":0,"k":[400.438,352.479,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":2,"ty":"el","s":{"a":0,"k":[177.031,247.812],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[10.025,120.696],[12.355,149.564],[2.966,147.251],[-7.063,148.633],[-4.733,120.696]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0.465349324544,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[400.437,338.703],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[128.744,107.385],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":80,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"greenBalloon 4","sr":1,"ks":{"o":{"a":0,"k":70,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.829},"o":{"x":0.167,"y":0.173},"n":"0p833_0p829_0p167_0p173","t":52,"s":[56,3300.5,0],"e":[315.448,-162.334,0],"to":[43.2412719726562,-577.138977050781,0],"ti":[-43.2412719726562,577.138977050781,0]},{"t":350}],"ix":2},"a":{"a":0,"k":[400.438,352.479,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":2,"ty":"el","s":{"a":0,"k":[177.031,247.812],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[10.025,120.696],[12.355,149.564],[2.966,147.251],[-7.063,148.633],[-4.733,120.696]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,1,0.528860234279,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[400.437,338.703],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[128.744,107.385],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":80,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"greenBalloon 3","sr":1,"ks":{"o":{"a":0,"k":70,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.851},"o":{"x":0.167,"y":0.16},"n":"0p833_0p851_0p167_0p16","t":148,"s":[1240,2924.5,0],"e":[1275.959,-478.276,0],"to":[5.99310493469238,-567.12939453125,0],"ti":[-5.99310493469238,567.12939453125,0]},{"t":402}],"ix":2},"a":{"a":0,"k":[400.438,352.479,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":2,"ty":"el","s":{"a":0,"k":[177.031,247.812],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[10.025,120.696],[12.355,149.564],[2.966,147.251],[-7.063,148.633],[-4.733,120.696]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,1,0.528860234279,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[400.437,338.703],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[128.744,107.385],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":80,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"greenBalloon 2","sr":1,"ks":{"o":{"a":0,"k":70,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.834},"o":{"x":0.167,"y":0.167},"n":"0p833_0p834_0p167_0p167","t":0,"s":[198,2837.667,0],"e":[762,-368.5,0],"to":[94,-534.361145019531,0],"ti":[-94,534.361145019531,0]},{"t":271}],"ix":2},"a":{"a":0,"k":[400.438,352.479,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":2,"ty":"el","s":{"a":0,"k":[177.031,247.812],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[10.025,120.696],[12.355,149.564],[2.966,147.251],[-7.063,148.633],[-4.733,120.696]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,1,0.528860234279,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[400.437,338.703],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[128.744,107.385],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":80,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"blueBalloon","sr":1,"ks":{"o":{"a":0,"k":70,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.83},"o":{"x":0.167,"y":0.198},"n":"0p833_0p83_0p167_0p198","t":23,"s":[1235.406,3127.833,0],"e":[761.634,-588.86,0],"to":[-78.9620056152344,-619.448791503906,0],"ti":[78.9620056152344,619.448791503906,0]},{"t":344}],"ix":2},"a":{"a":0,"k":[400.438,352.479,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":2,"ty":"el","s":{"a":0,"k":[177.031,247.812],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[10.025,120.696],[12.355,149.564],[2.966,147.251],[-7.063,148.633],[-4.733,120.696]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.46011020436,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[400.437,338.703],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[128.744,107.385],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":80,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"yellowBalloon","sr":1,"ks":{"o":{"a":0,"k":70,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.856},"o":{"x":0.167,"y":0.167},"n":"0p833_0p856_0p167_0p167","t":0,"s":[1235.406,3127.833,0],"e":[1211.959,-627.333,0],"to":[-3.90793800354004,-625.861022949219,0],"ti":[3.90793800354004,625.861022949219,0]},{"t":271}],"ix":2},"a":{"a":0,"k":[400.438,352.479,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":2,"ty":"el","s":{"a":0,"k":[177.031,247.812],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[10.025,120.696],[12.355,149.564],[2.966,147.251],[-7.063,148.633],[-4.733,120.696]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.987591911765,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[400.437,338.703],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[128.744,107.385],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":80,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"purpleBalloon","sr":1,"ks":{"o":{"a":0,"k":70,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.845},"o":{"x":0.167,"y":0.167},"n":"0p833_0p845_0p167_0p167","t":0,"s":[776,3020.5,0],"e":[539.959,-454.276,0],"to":[-39.3402290344238,-579.12939453125,0],"ti":[39.3402290344238,579.12939453125,0]},{"t":271}],"ix":2},"a":{"a":0,"k":[400.438,352.479,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":2,"ty":"el","s":{"a":0,"k":[177.031,247.812],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[10.025,120.696],[12.355,149.564],[2.966,147.251],[-7.063,148.633],[-4.733,120.696]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.462959289551,0,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[400.437,338.703],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[128.744,107.385],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":80,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"blueBallloon","sr":1,"ks":{"o":{"a":0,"k":70,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.863},"o":{"x":0.167,"y":0.134},"n":"0p833_0p863_0p167_0p134","t":106,"s":[512,2724.5,0],"e":[1162,-464.5,0],"to":[108.333335876465,-531.5,0],"ti":[-108.333335876465,531.5,0]},{"t":330}],"ix":2},"a":{"a":0,"k":[400.438,352.479,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":2,"ty":"el","s":{"a":0,"k":[177.031,247.812],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[10.025,120.696],[12.355,149.564],[2.966,147.251],[-7.063,148.633],[-4.733,120.696]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.721047674441,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[400.437,338.703],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[128.744,107.385],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":80,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"blueBalloon","sr":1,"ks":{"o":{"a":0,"k":70,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.869},"o":{"x":0.167,"y":0.128},"n":"0p833_0p869_0p167_0p128","t":191,"s":[1232,2996.5,0],"e":[831.959,-273.776,0],"to":[-60.6597709655762,-578.37939453125,0],"ti":[66.6735610961914,545.046020507812,0]},{"t":408}],"ix":2},"a":{"a":0,"k":[400.438,352.479,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":2,"ty":"el","s":{"a":0,"k":[177.031,247.812],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[10.025,120.696],[12.355,149.564],[2.966,147.251],[-7.063,148.633],[-4.733,120.696]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.497150914809,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[400.437,338.703],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[128.744,107.385],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":80,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"redBallloon","sr":1,"ks":{"o":{"a":0,"k":70,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.885},"o":{"x":0.167,"y":0.112},"n":"0p833_0p885_0p167_0p112","t":191,"s":[848,3476.5,0],"e":[604.041,-273.776,0],"to":[-60.6597709655762,-578.37939453125,0],"ti":[40.6597709655762,625.046020507812,0]},{"t":408}],"ix":2},"a":{"a":0,"k":[400.438,352.479,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":2,"ty":"el","s":{"a":0,"k":[177.031,247.812],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[10.025,120.696],[12.355,149.564],[2.966,147.251],[-7.063,148.633],[-4.733,120.696]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0.392279561361,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[400.437,338.703],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[128.744,107.385],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":80,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"yellowBallloon 3","sr":1,"ks":{"o":{"a":0,"k":70,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.877},"o":{"x":0.167,"y":0.121},"n":"0p833_0p877_0p167_0p121","t":191,"s":[272,3204.5,0],"e":[312.041,-293.667,0],"to":[6.67356204986572,-583.02783203125,0],"ti":[-6.67356204986572,583.02783203125,0]},{"t":408}],"ix":2},"a":{"a":0,"k":[400.438,352.479,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":2,"ty":"el","s":{"a":0,"k":[177.031,247.812],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[10.025,120.696],[12.355,149.564],[2.966,147.251],[-7.063,148.633],[-4.733,120.696]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.949172794118,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[400.437,338.703],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[128.744,107.385],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":80,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"yellowBallloon 2","sr":1,"ks":{"o":{"a":0,"k":70,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.841},"o":{"x":0.167,"y":0.156},"n":"0p833_0p841_0p167_0p156","t":121,"s":[512,2924.5,0],"e":[539.959,-454.276,0],"to":[4.65977144241333,-563.12939453125,0],"ti":[-4.65977144241333,563.12939453125,0]},{"t":391}],"ix":2},"a":{"a":0,"k":[400.438,352.479,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":2,"ty":"el","s":{"a":0,"k":[177.031,247.812],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[10.025,120.696],[12.355,149.564],[2.966,147.251],[-7.063,148.633],[-4.733,120.696]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.949172794118,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[400.437,338.703],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[128.744,107.385],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":80,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"yellowBallloon","sr":1,"ks":{"o":{"a":0,"k":70,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.863},"o":{"x":0.167,"y":0.134},"n":"0p833_0p863_0p167_0p134","t":106,"s":[1376,2804.5,0],"e":[312.041,-293.667,0],"to":[-177.326431274414,-516.361145019531,0],"ti":[177.326431274414,516.361145019531,0]},{"t":331}],"ix":2},"a":{"a":0,"k":[400.438,352.479,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":2,"ty":"el","s":{"a":0,"k":[177.031,247.812],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[10.025,120.696],[12.355,149.564],[2.966,147.251],[-7.063,148.633],[-4.733,120.696]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.949172794118,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[400.437,338.703],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[128.744,107.385],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":80,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"redBalloonNew","sr":1,"ks":{"o":{"a":0,"k":70,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.847},"o":{"x":0.167,"y":0.167},"n":"0p833_0p847_0p167_0p167","t":0,"s":[1016,2740.5,0],"e":[1098,-774.166,0],"to":[13.6666669845581,-585.777709960938,0],"ti":[-13.6666669845581,585.777709960938,0]},{"t":271}],"ix":2},"a":{"a":0,"k":[400.438,352.479,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":2,"ty":"el","s":{"a":0,"k":[177.031,247.812],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[10.025,120.696],[12.355,149.564],[2.966,147.251],[-7.063,148.633],[-4.733,120.696]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0.404779561361,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[400.437,338.703],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[128.744,107.385],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":80,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"greenBalloonNew","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":0,"s":[198,2984.5,0],"e":[718,-260,0],"to":[86.6666641235352,-540.75,0],"ti":[-86.6666641235352,540.75,0]},{"t":360}],"ix":2},"a":{"a":0,"k":[-100.906,408.167,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":2,"ty":"el","s":{"a":0,"k":[177.031,247.812],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[10.025,120.696],[12.355,149.564],[2.966,147.251],[-7.063,148.633],[-4.733,120.696]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,1,0.903125,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-100.906,394.391],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[128.744,107.385],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":80,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/Rules-lhie1/assets/close_icon.png b/Rules-lhie1/assets/close_icon.png new file mode 100644 index 0000000..2805913 Binary files /dev/null and b/Rules-lhie1/assets/close_icon.png differ diff --git a/Rules-lhie1/assets/icon.png b/Rules-lhie1/assets/icon.png new file mode 100644 index 0000000..13f1d40 Binary files /dev/null and b/Rules-lhie1/assets/icon.png differ diff --git a/Rules-lhie1/assets/menu_icon.png b/Rules-lhie1/assets/menu_icon.png new file mode 100644 index 0000000..918e225 Binary files /dev/null and b/Rules-lhie1/assets/menu_icon.png differ diff --git a/Rules-lhie1/assets/new_rules_tag.png b/Rules-lhie1/assets/new_rules_tag.png new file mode 100644 index 0000000..a9555cb Binary files /dev/null and b/Rules-lhie1/assets/new_rules_tag.png differ diff --git a/Rules-lhie1/assets/new_version_tag.png b/Rules-lhie1/assets/new_version_tag.png new file mode 100644 index 0000000..9c7516b Binary files /dev/null and b/Rules-lhie1/assets/new_version_tag.png differ diff --git a/Rules-lhie1/assets/selected_icon.png b/Rules-lhie1/assets/selected_icon.png new file mode 100644 index 0000000..0f1342c Binary files /dev/null and b/Rules-lhie1/assets/selected_icon.png differ diff --git a/Rules-lhie1/assets/thankyou.jpg b/Rules-lhie1/assets/thankyou.jpg new file mode 100644 index 0000000..892823a Binary files /dev/null and b/Rules-lhie1/assets/thankyou.jpg differ diff --git a/Rules-lhie1/assets/thankyou2.jpg b/Rules-lhie1/assets/thankyou2.jpg new file mode 100644 index 0000000..2cd5c60 Binary files /dev/null and b/Rules-lhie1/assets/thankyou2.jpg differ diff --git a/Rules-lhie1/assets/today_jsbox.png b/Rules-lhie1/assets/today_jsbox.png new file mode 100644 index 0000000..980e083 Binary files /dev/null and b/Rules-lhie1/assets/today_jsbox.png differ diff --git a/Rules-lhie1/assets/today_pull.png b/Rules-lhie1/assets/today_pull.png new file mode 100644 index 0000000..fc0f192 Binary files /dev/null and b/Rules-lhie1/assets/today_pull.png differ diff --git a/Rules-lhie1/assets/today_quan.png b/Rules-lhie1/assets/today_quan.png new file mode 100644 index 0000000..6110aa3 Binary files /dev/null and b/Rules-lhie1/assets/today_quan.png differ diff --git a/Rules-lhie1/assets/today_quan_off.png b/Rules-lhie1/assets/today_quan_off.png new file mode 100644 index 0000000..8c3cd5e Binary files /dev/null and b/Rules-lhie1/assets/today_quan_off.png differ diff --git a/Rules-lhie1/assets/today_surge.png b/Rules-lhie1/assets/today_surge.png new file mode 100644 index 0000000..f74857f Binary files /dev/null and b/Rules-lhie1/assets/today_surge.png differ diff --git a/Rules-lhie1/assets/today_surge2.png b/Rules-lhie1/assets/today_surge2.png new file mode 100644 index 0000000..dd8fd9c Binary files /dev/null and b/Rules-lhie1/assets/today_surge2.png differ diff --git a/Rules-lhie1/assets/today_surge_off.png b/Rules-lhie1/assets/today_surge_off.png new file mode 100644 index 0000000..efe381f Binary files /dev/null and b/Rules-lhie1/assets/today_surge_off.png differ diff --git a/Rules-lhie1/config.json b/Rules-lhie1/config.json new file mode 100644 index 0000000..794874e --- /dev/null +++ b/Rules-lhie1/config.json @@ -0,0 +1,22 @@ +{ + "info": { + "name": "Rules-lhie1", + "url": "https://raw.githubusercontent.com/Fndroid/jsbox_script/master/Rules-lhie1/.output/Rules-lhie1.box", + "version": "1.8.1", + "author": "Fndroid", + "website": "https://github.com/Fndroid/jsbox_script/tree/master/Rules-lhie1", + "types": 15 + }, + "settings": { + "minSDKVer": "1.0.0", + "minOSVer": "10.0.0", + "idleTimerDisabled": false, + "autoKeyboardEnabled": false, + "keyboardToolbarEnabled": false, + "rotateDisabled": false + }, + "widget": { + "tintColor": "", + "iconColor": "" + } +} diff --git a/Rules-lhie1/defaultConf/customSettings b/Rules-lhie1/defaultConf/customSettings new file mode 100644 index 0000000..b55c643 --- /dev/null +++ b/Rules-lhie1/defaultConf/customSettings @@ -0,0 +1 @@ +# Custom \ No newline at end of file diff --git a/Rules-lhie1/defaultConf/generalSettings b/Rules-lhie1/defaultConf/generalSettings new file mode 100644 index 0000000..01550f0 --- /dev/null +++ b/Rules-lhie1/defaultConf/generalSettings @@ -0,0 +1,30 @@ +[General] +// Auto +loglevel = notify +dns-server = system,1.2.4.8,223.5.5.5,223.6.6.6 +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 + +// 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 +show-error-page-for-reject = true + +// Auto +exclude-simple-hostnames = true +ipv6 = true +replica = false + +[Replica] +hide-apple-request = true +hide-crashlytics-request = true +hide-udp = false +use-keyword-filter = false \ No newline at end of file diff --git a/Rules-lhie1/defaultConf/headerrewriteSettings b/Rules-lhie1/defaultConf/headerrewriteSettings new file mode 100644 index 0000000..9785a7b --- /dev/null +++ b/Rules-lhie1/defaultConf/headerrewriteSettings @@ -0,0 +1 @@ +// custom header rewrite \ No newline at end of file diff --git a/Rules-lhie1/defaultConf/hostSettings b/Rules-lhie1/defaultConf/hostSettings new file mode 100644 index 0000000..9db2d42 --- /dev/null +++ b/Rules-lhie1/defaultConf/hostSettings @@ -0,0 +1 @@ +// custom host \ No newline at end of file diff --git a/Rules-lhie1/defaultConf/hostnameSettings b/Rules-lhie1/defaultConf/hostnameSettings new file mode 100644 index 0000000..e69de29 diff --git a/Rules-lhie1/defaultConf/mitmSettings b/Rules-lhie1/defaultConf/mitmSettings new file mode 100644 index 0000000..fef3a96 --- /dev/null +++ b/Rules-lhie1/defaultConf/mitmSettings @@ -0,0 +1,3 @@ +enable = true +ca-passphrase = 4B676386 +ca-p12 = MIIJtAIBAzCCCX4GCSqGSIb3DQEHAaCCCW8EgglrMIIJZzCCA9cGCSqGSIb3DQEHBqCCA8gwggPEAgEAMIIDvQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQI6Y6Nt7P0s1QCAggAgIIDkE4px9tUmX4zyAE2qK9f761b7vkat/g7X4gjWSPRtrdovsbnP05XaNdYF8sRn+GktrbqJ6m4LwPe1GUCDht8vuno76ZPAKdT5LVxAeKKJIz8+kqvdKh5COwMSHUD8SqJpncfiH90xu/HmzPbIPCKIE89ZWTRDECmJc9bwH97kefu+U/FB6suMVyEKD7oKhYcjY7110DLNe0okD+MMOLZkMv2DcPb/B9RqKCNAT86bFyF2jtsvyQ15WxkILb03R8Pal1LqkDD9P+r0tTjSRNLKKzWXK0blQeL3teZcusClXPUWo3wZZwNe+8kfUoe23vm62TjSIdYF0gi7G2wpoIIlSlijiPffFFfvG6FS2Y976uLPZb1MonWRdjBYYwry180YQJOyWZQQOR+lWj01lp9o5GaYLKNRNGfrGdsbHx/xKcYEX7Fo/SycUQhzvDh0YbgYz09VNVsbKQDRj0lnxYLnJTLVX0DMmNlWWT6qMwXJ7HLYVT8sgA51h/meUfHmpzI1Qv9k8T/KZQtcVpHSWZ2LXdmwwLJ1A4VgQWxPS7a2GisrYs8DJbDLqaCpRrCyTqpOUclvZ/ONFqiqvJNbuzg33clgutbQNIxoyqJ5A9VDvbKcwgEq91KdSfsQ1shpS/lxGNCsfF+kFcgD95YS2ZfQ5QoFMszoSMCIkz/juc0aLbrGehpmrtd+LGOjomE/y7m8zJ2AxBLQpKSICRu6Dcz0nC2Jgf25/NJlUuX4kIZJyz0MxBBNreUzcevpFIIgsUpwlYAKZKP01/clVV+mVyax49RRVZttMKTaLymSeKO0lGqi9xzbnd0TCtmzN6wp4UpwtISxqLju3fTcgiWnCYRiEY7JZcaAO02J8C8dRsGU1lOBJOJ6hksPwbJ6B52maLmF3cu7WBG5RAmx/MtvJrvzNZYAyord6jjThcfQp8bMv1evmo8BDDpQ6FQb6TR8W9GvLSH21iLbuRFygDnzkKQ+s+LyiO3G0LNseNLxILEcxBgSx0hzoh7/k/MVaO+p0w5csf+VIlOLcew+7Oen5KJFRXhcUDKR3Km7cdcOPv8M8lqvHeScfga6X+W83B+u1+fYhkE8rwlFPj/bUk11A+fbThnM6K069DMh8388S9Tz8otf7zKzad24mUHWSx37GJx1jg0T3dVHegx2JJ3iBLQlGXxF+JiXY/DEeT0VxXJZXt2QbPY9LQ3McIKngeHKpYh4zCCBYgGCSqGSIb3DQEHAaCCBXkEggV1MIIFcTCCBW0GCyqGSIb3DQEMCgECoIIE7jCCBOowHAYKKoZIhvcNAQwBAzAOBAgB2aT5GqvE2AICCAAEggTIPV10t7HrCN6H+dB2i0z4MuGWtSblZVax8UGxygolskl9x7ATpi5+Wo7CpW1EQljzCUmIWygajuqSwvywT5clhuXplenLIXMJcknaf/IjoqF847TY0qSvnOJm+ywoLZ8MhOjTjSNk2N6c+szhr46eSu+1AnMD/fGdh+Z/Xp0i3BdqD4DO+9r8fmjoJlQ1I6ieI6M0baQc/yVPYXJfwZPGYfB3BukXdq1TXtByuCowA8lezoiHQ8EtAongtN2mekiUtHtwK4qHqLqEdr15arht8cFQPXxsX/OE09D/UjzOu04goqEctIBofbUhRMfBprarMfa6qZcFf5saUT7OWk7uC+LcRXd0ZF9XqCTe87u7030jbIh3zuRAX64xbvsowrs8cxM1OaOeqW0Fr0XydsmoPg6r+XlXXyItZvSJllumJHoB2QcpQO03hrRcBuhm7PT5pBG36S4DxwDRBPxID+kWLzbDKJUvsxyX6FyRfn2pySX03w2GcPxlqCexkVHRYInL+MPEUJ8zPOfBx7GvpZStkadnoIo2Zf0t6miHYoxuw81IL6jBQpMaWwL1TMWnioisvoE3zn3oHCTLXWoLwUEKXNX0tQ7IokuXdG7abeD3iBRwXyX6q5jbeQ4PfLll9utT17YSswz2WDoX8fwyiiv+DEZgfWUA5Fc56eVX1w5JIUN/SnqfB1DLcXI4KzSlhZBk+P2MYHCZFTYiFEBYS+A5TPIdt4nhK8+LSA7PA9YyTNSBn09nRjMNtrfSvp5tL4DHkNOPsn4Uxtpl1PW1xxkbfWS3WK3LRAM84jTIE06pqpixSube62F/GqTi5DwyDOdobehgkfXxmQmjTKe4HvIXYC12Mfl9r5VY/gnVLNV0z5PoEA3ycRNWBhXL4BkRyQijZTLm7oH/xP+wWKdOimbJnxAzl8jhiTkJKZGSgj63pioTC60eyDQo8Dh6BdASuSnIdLoqDjB3vFA1Gbp9rOI7YLEYp0bXBzwdbsamasuQKN5juqzvOhmTIKTskx+IKUBRzZ1+5Zse5606PCKMOPyXYgkk42XfdZiyXcmLo/LvHWsxHYvsLspegLRJBDl+HREOzSKoT1FYW//twJaYh34928E1Ek5BeMUzRYrVJklPwAhUoUTpZuZE+kGdCqCBlTq2fN6CEcMB4t88TjGWDS1AGby9zQTK23NQkdTTx0sBnWZDecLAWk1xTdHxV/dvfnQGgByQKilmDf9meeRFFZn89uMX9SK3hhQ3bAh1Z4lTpqLYNyi7j3QSrhhn9ByLL8awH8Hn71EIRznw7mGGOIcyVKQQsE6Z7a7xMMKHhTvdjLwVpTsSLp46nTmBgk7AluBATeJM9FQpOrP142ZtHRifAFUSuhWLhBXYE+NYRaQT1VJQJU5FLadgUQzRGQuvi3dBkM6zXJapEB94OOvq1QjP6bt0SJXVW26+tqBS4tpcqUUJ5fTrfDzAX1ZuVbSSAQw73wNwSsd6OikYfIsg5jL+WnBMPOXnwTKdR3cUYynoqmbLf8A39m+EyRg5Z4kEZksxLxQ9oQH2O+XfgvFbRq9C/POwJV01knSRwjQCjvE6kr7bJSAF/DEekJMhP96ayZ1ZgzUDv+aazlPP2fLIjf4wYmg5h5+Et6GGMWwwIwYJKoZIhvcNAQkVMRYEFAk2eIvfMVYoZsDby30OzlTkJrwGMEUGCSqGSIb3DQEJFDE4HjYAUwB1AHIAZwBlACAARwBlAG4AZQByAGEAdABlAGQAIABDAEEAIAA0AEIANgA3ADYAMwA4ADYwLTAhMAkGBSsOAwIaBQAEFBY2VuZtNCmmQeiV3UDh7JuSWFqPBAj+OgUq8sPPwA== diff --git a/Rules-lhie1/defaultConf/proxyGroupSettings b/Rules-lhie1/defaultConf/proxyGroupSettings new file mode 100644 index 0000000..87d991c --- /dev/null +++ b/Rules-lhie1/defaultConf/proxyGroupSettings @@ -0,0 +1,10 @@ +[Proxy Group] +🍃 Proxy = select,🏃 Auto,🚀 Direct,Proxy Header + +🍂 Domestic = select,🚀 Direct,🍃 Proxy + +🍎 Only = select,🚀 Direct,Proxy Header + +☁️ Others = select,🚀 Direct,🍃 Proxy + +🏃 Auto = url-test,ProxyHeader,url = http://www.gstatic.com/generate_204,interval = 1200 \ No newline at end of file diff --git a/Rules-lhie1/defaultConf/ssidSettings b/Rules-lhie1/defaultConf/ssidSettings new file mode 100644 index 0000000..0b22e8b --- /dev/null +++ b/Rules-lhie1/defaultConf/ssidSettings @@ -0,0 +1 @@ +// custom ssid \ No newline at end of file diff --git a/Rules-lhie1/defaultConf/urlrewriteSettings b/Rules-lhie1/defaultConf/urlrewriteSettings new file mode 100644 index 0000000..e164c55 --- /dev/null +++ b/Rules-lhie1/defaultConf/urlrewriteSettings @@ -0,0 +1 @@ +// custom url rewrite \ No newline at end of file diff --git a/Rules-lhie1/defaultConf/widgetSettings b/Rules-lhie1/defaultConf/widgetSettings new file mode 100644 index 0000000..554a5db --- /dev/null +++ b/Rules-lhie1/defaultConf/widgetSettings @@ -0,0 +1,2 @@ +# 带流量信息的订阅或托管链接 +# 每行一个,格式为:name=url \ No newline at end of file diff --git a/Rules-lhie1/docs.md b/Rules-lhie1/docs.md new file mode 100644 index 0000000..3940c58 --- /dev/null +++ b/Rules-lhie1/docs.md @@ -0,0 +1,60 @@ +### Rules-lhie1脚本使用简介 + +#### 主界面名词 +- 去广告:加载去广告规则(现有总规则6000+) +- 开启MITM:不选中则不使用MITM证书,选中后默认使用lhie1的证书(CA 4B676386),可以在进阶设置中进行自定义 +- 节点后缀:用于在节点后添加UDP、TFO和插件等功能 +- 导出配置:采用Action Sheet将.conf配置文件导出,不打开则尝试直接导出配置文件到Surge 3 +- 进阶设置:可自行配置常规(General)、代理分组(Proxy Group)、代理规则(Custom Rule)、本地DNS映射(Host)、URL重定向(URL Rewrite)、Header修改(Header Rewrite)、SSID、主机名(Hostname)和配置根证书(MITM),也可一键还原全部进阶设置 +- 生成配置:拉取规则生成.conf配置文件 + +#### 节点导入编辑 +> 这是脚本的一个特点,可以对节点进行编辑筛选等操作。 + +- 导入、更新:可以通过剪贴板或二维码导入,支持``ss://链接``、``Surge链接``和``托管地址``,支持批量导入(每行一个) +- 倒序:将节点顺序颠倒,方便不同机场节点选择 +- 占位符:为策略组添加占位符,默认Auto组的占位符是ProxyHeader +- 特殊代理:节点左滑即可将节点设置为某些链接的特殊代理,按钮可以设置Direct、查看设置和清除特殊代理设置 +- 节点删除:在节点列表左滑选择删除 +- 节点重命名:节点列表左滑选择重命名(方便部分不带节点名节点改名) +- 节点排序:节点列表长按拖动即可调整顺序 + +#### 新手指导 +熟悉以上内容后,只需根据自己的需求,导入节点,在列表中选择测速节点,选中开关,拉取规则生成即可享受最新的lhie1规则,而且在托管没有发生变化的情况下,每次进入脚本都可以直接拉取生成最新规则不需要重新配置。 + +#### 进阶设置 +> 进阶设置配置完毕后即可自动保存 + +- 常规(General):常规配置内容,可以设置DNS、Replica等内容 +- 代理分组(Proxy Group):配置代理组内容,每个组别都可以通过``Proxy Header(有空格)``代表所有节点名,``ProxyHeader(无空格)``代替Auto节点名 +- 代理规则(Custom Rule):自定义规则配置,将需要添加的规则添加到规则的最上面,如果要删除lhie1的规则,在规则前添加``-``即可,添加的优先级较高 +- 本地DNS映射(Host):自定义本地DNS映射 +- URL重定向(URL Rewrite):自定义URL重定向 +- Header修改(Header Rewrite):自定义Header修改 +- SSID:自定义SSID +- 主机名(Hostname):自定义主机名 +- MITM:用于配置根证书,默认为lhie1的证书(CA 4B676386),也可自行配置 + +注: +1.以上进阶设置请在“知道自己在做什么”的情况下修改; +2.如果信息填错导致Surge无法启动,可以将对应部分清空保存即可自动恢复为默认,也可一键还原全部进阶设置。 + +#### JSBox小组件 +> 可在iOS的Widget页面中添加使用,使用前需先在JSBox的小组件设置中选中该脚本。小组件视图高度可考虑设置为120。 + +- Surge3启动/停止运行(红色Surge3图标):利用Surge3提供的URL Scheme,可做到Surge3一键启动/停止运行 +- 生成配置(绿色下载图标):一键生成配置,并根据个人脚本中的设置,自动执行导出到Surge3或者打开导出配置的系统选单 +- 打开脚本(蓝色JSBox图标):从小组件进到该JSBox脚本的主界面 + +> 当规则有更新时小组件会有角标提醒及更新内容提示 + +#### URL Scheme +脚本支持自动根据配置拉取最新规则,URL Scheme如下: +``` +jsbox://run?name=Rules-lhie1&auto=1 +``` + +#### 最后 +目前该JSBox脚本只支持生成Surge3的配置,生成配置的各项功能已经能替代大部分的Workflow的自定义功能。 + +如觉得这个脚本好用,愿意打赏,感激不尽。 diff --git a/Rules-lhie1/donate.md b/Rules-lhie1/donate.md new file mode 100644 index 0000000..1d29b5f --- /dev/null +++ b/Rules-lhie1/donate.md @@ -0,0 +1 @@ +感谢所有可爱的支持者! \ No newline at end of file diff --git a/Rules-lhie1/main.js b/Rules-lhie1/main.js new file mode 100644 index 0000000..a033505 --- /dev/null +++ b/Rules-lhie1/main.js @@ -0,0 +1,55 @@ +console.clear() + +const app = require('scripts/app') +const init = require('scripts/init') +const today = require('scripts/today') +const extension = require('scripts/extension') +const updateUtil = require('scripts/updateUtil') +const siri = require('scripts/siri') + +$app.autoKeyboardEnabled = true +$app.rotateDisabled = true +$app.keyboardToolbarEnabled = true + +let query = $context.query + +$objc('notification').invoke('objectForKey') + +if (query.auto == 1) { + app.autoGen() + return +} + +if ($app.env === $env.today) { + today.renderTodayUI() + return +} else if ($app.env === $env.safari) { + extension.renderExtensionUI() + return +} else if ($app.env === $env.action) { + extension.collectRules() + return +} else if ($app.env === $env.siri) { + siri.renderSiriUI() + return +} + +app.setUpWorkspace() + +init.asyncInitialize() + +app.renderUI() + +updateUtil.getLatestVersion({ + handler: version => { + if (updateUtil.needUpdate(version, updateUtil.getCurVersion())) { + $http.get({ + url: 'https://raw.githubusercontent.com/Fndroid/jsbox_script/master/Rules-lhie1/updateLog.md' + '?t=' + new Date().getTime(), + handler: resp => { + updateUtil.updateScript(version) + } + }) + + } + } +}) diff --git a/Rules-lhie1/restart.fndroid b/Rules-lhie1/restart.fndroid new file mode 100644 index 0000000..f32a580 --- /dev/null +++ b/Rules-lhie1/restart.fndroid @@ -0,0 +1 @@ +true \ No newline at end of file diff --git a/Rules-lhie1/scripts/app.js b/Rules-lhie1/scripts/app.js new file mode 100644 index 0000000..cca7eb5 --- /dev/null +++ b/Rules-lhie1/scripts/app.js @@ -0,0 +1,3012 @@ +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 +} diff --git a/Rules-lhie1/scripts/colorUtil.js b/Rules-lhie1/scripts/colorUtil.js new file mode 100644 index 0000000..e79e0dd --- /dev/null +++ b/Rules-lhie1/scripts/colorUtil.js @@ -0,0 +1,60 @@ +/* + +提示:自定义颜色请建立_custom/color.js文件以覆盖默认颜色, +格式: + +module.exports = { + editorItemText: "tint", + editorItemIcon: "tint", +} + +*/ + +const DEFAULT = { + statusBar: "#000000", // 状态栏文字颜色,#ffffff为白色,clear隐藏,其他为黑色 + navBar: "#ffffff", // 导航栏颜色 + navTitleText: "tint", // 标题栏文字颜色 + navIconLoading: "tint", // 加载图标颜色 + navIconLeft: "tint", // 标题栏红包按钮文字颜色 + navIconRight: "tint", // 标题栏备份按钮文字颜色 + importBtnText: "tint", // 导入按钮文字颜色 + controlBtnText: "#000000", // 操作栏文字颜色 + editorItemText: "tint", // 服务器列表文字颜色 + editorItemIcon: "tint", // 服务器列表图标颜色 + outputFormatText: "tint", // 输出格式文字颜色 + usualBtnOnBg: "#ff6666", // 常规选项按钮开启背景色 + usualBtnOnFg: "#ffffff", // 常规选项按钮开启前景色 + usualBtnOffBg: "#ffffff", // 常规选项按钮关闭背景色 + usualBtnOffFg: "#000000", // 常规选项按钮关闭前景色 + advanceBtnBg: "#808080", // 进阶按钮背景色 + advanceBtnFg: "#ffffff", // 进阶按钮前景色 + aboutBtnBg: "#808080", // 关于按钮背景色 + aboutBtnFg: "#ffffff", // 关于按钮前景色 + genBtnBg: "tint", // 生成按钮背景色 + genBtnFg: "#ffffff", // 生成按钮前景色 + advanceGridOnBg: "#ffda40", // 进阶设置网格选中背景色 + advanceGridOnFg: "#034769", // 进阶设置网格选中前景色 + advanceGridOffBg: "#63add0", // 进阶设置网格默认背景色 + advanceGridOffFg: "#ffffff", // 进阶设置网格默认前景色 +} + +const customColor = $file.exists("_custom/color.js") ? require('_custom/color.js') : {} + +function isEqual(color1, color2) { + return color1.runtimeValue().invoke('isEqual', color2) +} + +function getColor(name, hex = false) { + let resColor = "tint" + if (name in customColor) { + resColor = customColor[name] + } else if (name in DEFAULT) { + resColor = DEFAULT[name] + } + return hex ? resColor : $color(resColor) +} + +module.exports = { + isEqual: isEqual, + getColor: getColor +} \ No newline at end of file diff --git a/Rules-lhie1/scripts/extension.js b/Rules-lhie1/scripts/extension.js new file mode 100644 index 0000000..2efd07e --- /dev/null +++ b/Rules-lhie1/scripts/extension.js @@ -0,0 +1,325 @@ +const app = require('scripts/app') + +const FILE = 'data.js' + +let screenHeight = $device.info.screen.height +const screenWidth = $device.info.screen.width + +const iPhoneX = screenWidth == 375 && screenHeight == 812 +if (iPhoneX) { + screenHeight -= 48 +} + +let renderExtensionUI = function () { + const host = $context.safari.items.location.hostname + $ui.render({ + props: { + title: "增加规则" + }, + views: [{ + type: "scroll", + props: { + id: "mainScrollView", + bgcolor: $color("#f0f5f5") + }, + layout: $layout.fill, + views: [{ + type: "label", + props: { + text: "当前网页路径" + }, + layout: (make, view) => { + make.width.equalTo(view.super) + make.left.equalTo(5) + make.height.equalTo(30) + make.top.equalTo(view.super.top).offset(10) + } + }, { + type: "text", + props: { + editable: false, + text: $context.safari.items.baseURI + }, + layout: (make, view) => { + make.top.equalTo(view.prev.bottom).offset(5) + make.width.equalTo(view.super) + make.height.equalTo(60) + } + }, { + type: "label", + props: { + text: "规则匹配项" + }, + layout: (make, view) => { + make.width.equalTo(view.super) + make.left.equalTo(5) + make.height.equalTo(30) + make.top.equalTo(view.prev.bottom).offset(10) + } + }, { + type: "list", + props: { + rowHeight: 60, + id: "styleList", + template: { + views: [{ + type: "label", + props: { + id: "styleName", + font: $font("bold", 16) + }, + layout: (make, view) => { + make.width.equalTo(view.super) + make.height.equalTo(view.super).dividedBy(2) + make.left.equalTo(view.super.left).offset(15) + make.top.equalTo(view.super.top).offset(5) + } + }, { + type: "label", + props: { + id: "styleValue", + }, + layout: (make, view) => { + make.width.equalTo(view.super) + make.height.equalTo(view.super).dividedBy(2) + make.left.equalTo(view.super.left).offset(15) + make.top.equalTo(view.prev.bottom).offset(-10) + } + }, { + type: "image", + props: { + id: "styleSelected", + bgcolor: $color("#ffffff") + }, + layout: (make, view) => { + make.centerY.equalTo(view.super) + make.height.width.equalTo(20) + make.right.equalTo(view.super.right).offset(-20) + } + }] + } + }, + layout: (make, view) => { + make.top.equalTo(view.prev.bottom).offset(5) + make.width.equalTo(view.super) + make.height.equalTo(0) + }, + events: { + didSelect: (sender, indexPath, data) => { + let uiData = sender.data.map((i, idx) => { + return { + styleName: i.styleName, + styleValue: i.styleValue, + styleSelected: { data: null } + } + }) + uiData[indexPath.row].styleSelected = { data: $file.read("assets/selected_icon.png") } + sender.data = uiData + sender.info = indexPath.row + } + } + }, { + type: "label", + props: { + text: "选择代理" + }, + layout: (make, view) => { + make.width.equalTo(view.super) + make.left.equalTo(5) + make.height.equalTo(30) + make.top.equalTo(view.prev.bottom).offset(10) + } + }, { + type: "list", + props: { + id: "proxyList", + rowHeight: 40, + template: { + views: [{ + type: "label", + props: { + id: "proxyName" + }, + layout: (make, view) => { + make.centerY.equalTo(view.super) + make.width.equalTo(view.super).offset(-80) + make.height.equalTo(view.super) + make.left.equalTo(view.super.left).offset(15) + } + }, { + type: "image", + props: { + id: "proxySelected", + bgcolor: $color("#ffffff") + }, + layout: (make, view) => { + make.centerY.equalTo(view.super) + make.height.width.equalTo(20) + make.right.equalTo(view.super.right).offset(-20) + } + }] + } + }, + layout: (make, view) => { + make.top.equalTo(view.prev.bottom).offset(5) + make.width.equalTo(view.super) + make.height.equalTo(0) + }, + events: { + didSelect: (sender, indexPath, data) => { + let uiData = sender.data.map((i, idx) => { + return { + proxyName: i.proxyName, + proxySelected: { data: null } + } + }) + uiData[indexPath.row].proxySelected = { data: $file.read("assets/selected_icon.png") } + sender.data = uiData + sender.info = indexPath.row + } + } + }, { + type: "label", + props: { + text: "添加规则", + align: $align.center, + bgcolor: $color("#fff") + }, + layout: (make, view) => { + make.width.equalTo(view.super) + make.height.equalTo(40) + make.top.equalTo(view.prev.bottom).offset(20) + }, + events: { + tapped: sender => { + if ($("styleList").info !== null && $("proxyList").info !== null) { + let styleName = $("styleList").data[$("styleList").info].styleName.text + let styleValue = $("styleList").data[$("styleList").info].styleValue.text + let proxy = $("proxyList").data[$("proxyList").info].proxyName.text + let rule = [styleName, styleValue, proxy].join(',') + saveRule(rule) + $context.close() + } else { + $ui.alert("请选择匹配项和代理") + } + } + } + }] + }] + }) + let styles = genStyleDefaultData(host) + let styleListHeight = styles.length * 60 + $("styleList").data = styles + $("styleList").updateLayout(make => { + make.height.equalTo(styleListHeight) + }) + let savedData = JSON.parse($file.read(FILE).string) + let proxyGroup = savedData.proxyGroupSettings.split('\n').filter(i => /^(?!\/|#)[\s\S]+=/.test(i)).map(i => i.split(/[\s]*=/)[0]) + let flatServerData = savedData.workspace.serverData.reduce((all, cur) => { + return { + rows: all.rows.concat(cur.rows) + } + }).rows + let proxies = ['🚀 Direct'].concat(proxyGroup, ['REJECT', 'REJECT-TINYGIF'], flatServerData.map(i => i.proxyName.text)) + let proxiesData = genProxyDefaultData(proxies) + let proxiesListHeight = proxiesData.length * 40 + $("proxyList").data = proxiesData + $("proxyList").updateLayout(make => { + make.height.equalTo(proxiesListHeight) + }) + resizeScrollView(styleListHeight + proxiesListHeight) + +} + +let resizeScrollView = function (listHeight) { + $("mainScrollView").contentSize = $size(screenWidth, 350 + listHeight) +} + +let genStyleDefaultData = function (domain) { + let dp = domain.split('.') + let res = [] + res.push({ + styleName: { text: 'DOMAIN' }, + styleValue: { text: domain } + }) + return res.concat(dp.map((item, idx, obj) => { + return { + styleName: { text: 'DOMAIN-SUFFIX' }, + styleValue: { text: obj.slice(idx).join('.') } + } + })) +} + +let saveRule = function (rule) { + let data = JSON.parse($file.read(FILE).string) + data.customSettings += `\n${rule}\n` + $file.write({ + data: $data({ "string": JSON.stringify(data) }), + path: FILE + }) +} + +let genProxyDefaultData = function (proxies) { + return proxies.map(i => { + return { + proxyName: { text: i } + } + }) +} + +function collectRules() { + let confFile = $context.data.string + let surgeReg = /\[Rule\]\n([\s\S]+)\n# Custom/ + let quanReg = /\[SERVER\]\n([\s\S]+)\n\[SOURCE\]/ + if (surgeReg.test(confFile)) { + let matcher = confFile.match(surgeReg) + if (matcher.length === 2) { + let rules = matcher[1] + saveRule(rules) + } + $context.close() + } else if (quanReg.test(confFile)) { + let matcher = confFile.match(quanReg); + let serversRaw = matcher[1] + let servers = serversRaw.split(/[\r\n]+/).filter(i => /.*?=/.test(i)) + let serverDataItem = { + title: "Quantumult导出节点", + url: "", + rows: servers.map(server => { + return { + proxyLink: server, + proxyName: { + bgcolor: false, + text: server.split(/=/)[0].trim() + } + } + }) + } + let file = JSON.parse($file.read(FILE).string); + let workspace = file.workspace; + let serverData = workspace.serverData; + serverData.unshift(serverDataItem); + let success = $file.write({ + data: $data({ "string": JSON.stringify(file) }), + path: FILE + }) + $ui.alert({ + title: "导入" + success ? '成功': '失败', + message: success ? `已经成功导入${serverDataItem.rows.length}个服务器至脚本,请重启脚本查看` : '文件格式有误', + actions: [{ + title: 'OK', + handler: () => { + $context.close() + } + }] + }); + } else { + $ui.alert("分享文件不合法") + } +} + + +module.exports = { + renderExtensionUI: renderExtensionUI, + collectRules: collectRules +} \ No newline at end of file diff --git a/Rules-lhie1/scripts/filenameUtil.js b/Rules-lhie1/scripts/filenameUtil.js new file mode 100644 index 0000000..76d9660 --- /dev/null +++ b/Rules-lhie1/scripts/filenameUtil.js @@ -0,0 +1,23 @@ +const knownURLs = [ + {domain: 'api.rixcloud.io', name: 'rixCloud'}, + {domain: 'www.muncloud.net', name: 'MunCloud'} +] + +function getConfName(url) { + let matchConst = knownURLs.find(i => url.indexOf(i.domain) > -1) + if (matchConst) { + return matchConst.name + } else { + let path = url.split('?')[0].split('/') + let filename = path[path.length - 1] + if (filename.indexOf('.conf') == filename.length - 5) { + return filename.substring(0, filename.length - 5) + } else { + return filename + } + } +} + +module.exports = { + getConfName: getConfName +} \ No newline at end of file diff --git a/Rules-lhie1/scripts/init.js b/Rules-lhie1/scripts/init.js new file mode 100644 index 0000000..006afd5 --- /dev/null +++ b/Rules-lhie1/scripts/init.js @@ -0,0 +1,21 @@ +function asyncInitialize() { + updateSpecialReg() +} + +function updateSpecialReg() { + $http.download({ + showsProgress: false, + url: "https://raw.githubusercontent.com/Fndroid/specialReg/master/specialReg.js?t=" + new Date().getTime() + }).then(resp => { + if (resp.response.statusCode === 200) { + let success = $file.write({ + data: resp.data, + path: "scripts/videoReg.js" + }); + } + }) +} + +module.exports = { + asyncInitialize: asyncInitialize +} \ No newline at end of file diff --git a/Rules-lhie1/scripts/proxyUitl.js b/Rules-lhie1/scripts/proxyUitl.js new file mode 100644 index 0000000..00af0b6 --- /dev/null +++ b/Rules-lhie1/scripts/proxyUitl.js @@ -0,0 +1,318 @@ +const filenameUtil = require('scripts/filenameUtil') + +String.prototype.strictTrim = function () { + let trimed = this.trim() + if ((matcher = trimed.match(/([\s\S]+),$/)) !== null) { + return matcher[1] + } + return trimed +} + +function urlsafeBase64Encode(url) { + return $text.base64Encode(url).replace(/\-/g, '+').replace(/\\/g, '_').replace(/=+$/, '') +} + +function urlsafeBase64Decode(base64) { + // Add removed at end '=' + base64 += Array(5 - base64.length % 4).join('='); + base64 = base64 + .replace(/\-/g, '+') // Convert '-' to '+' + .replace(/\_/g, '/'); // Convert '_' to '/' + return $text.base64Decode(base64).replace(/\u0000/, ''); +} + +function promiseConf(url) { + return new Promise((resolve, reject) => { + $http.get({ + url: url, + header: { + 'User-Agent': 'Surge/1174 CFNetwork/962 Darwin/18.0.0' + }, + handler: function (resp) { + let data = resp.data + '' + let filename = url + try { + let matcher = resp.response.runtimeValue().invoke('allHeaderFields').rawValue()["Content-Disposition"].match(/filename="?(.*?)(?:.conf|"|$)/) + filename = matcher[1] + } catch (e) { + filename = filenameUtil.getConfName(url) + } + // 兼容不规范ssr链接 + let noPaddingData = data + let padding = noPaddingData.length % 4 == 0 ? 0 : 4 - noPaddingData.length % 4 + for (let i = 0; i < padding; i++) { + noPaddingData += '=' + } + let decodedData = $text.base64Decode(data) || $text.base64Decode(noPaddingData) + if (/\[Proxy\]([\s\S]*?)\[Proxy Group\]/.test(data)) { + // Surge托管 + resolve({ + servers: RegExp.$1, + filename: filename, + type: 0 + }) + } else if (/^(ssr|ss|vmess):\/\//.test(decodedData)) { + let rawLinks = decodedData.split(/[\n\r\|\s]+/g).filter(i => i !== '' && /^(ssr|ss|vmess):\/\//.test(i)); + let output = rawLinks.map(i => { + if (/^ssr:\/\//.test(i)) { + let res = decodeSSR([i]) + return res.servers + } else if (/^ss:\/\//.test(i)) { + let res = decodeScheme([i]) + return res.servers + } else { + let res = decodeVmess([i]) + return res.servers + } + }) + resolve({ + servers: output.reduce((p, c) => { + return p.concat(c) + }, []).join('\n'), + filename: getDomain(url), + type: 4 + }) + } else if (/^ssr:\/\//.test(decodedData)) { + // SSR订阅 + let rawLinks = decodedData.split(/[\n\r\|\s]+/g).filter(i => i !== '' && /^ssr:\/\//.test(i)); + let res = decodeSSR(rawLinks); + resolve({ + servers: res.servers.join('\n'), + filename: res.sstag || filename, + type: 1 + }) + } else if (/^ss:\/\//.test(decodedData)) { + // SS订阅 + let rawLinks = decodedData.split(/[\n\r\|\s]+/g).filter(i => i !== '' && /^ss:\/\//.test(i)); + let serInfo = decodeScheme(rawLinks); + resolve({ + servers: serInfo.servers.join('\n'), + filename: serInfo.sstag || filename, + type: 2 + }) + } else if (/^vmess:\/\//.test(decodedData)) { + let rawLinks = decodedData.split(/[\n\r\|\s]+/g).filter(i => i !== '' && /^vmess:\/\//.test(i)); + console.log('rawLinks', typeof rawLinks); + let res = decodeVmess(rawLinks); + console.log('res', res); + resolve({ + servers: res.servers.join('\n'), + filename: res.sstag || filename, + type: 3 + }) + } else { + resolve() + } + } + }) + }) +} + +function getDomain(url) { + if (/https?:\/\/.*?\.(.*?)\.(.*?)(?=\/|$)/.test(url)) { + return `${RegExp.$1.trim()}.${RegExp.$2.trim()}` + } + return '批量导入' +} + +function decodeSSR(links) { + let tag = '' + let first = '' + function getParam(key, content) { + let reg = new RegExp(`${key}=(.*?)(?:&|$)`); + let matcher = content.match(reg); + return matcher && matcher[1] ? matcher[1] : ''; + } + let decodedLinks = links.map(i => { + let rawContentMatcher = i.match(/^ssr:\/\/(.*?)$/); + if (rawContentMatcher && rawContentMatcher[1]) { + let rawContent = urlsafeBase64Decode(rawContentMatcher[1]); + let rawContentParts = rawContent.split(/\/*\?/g) + let paramsMatcher = rawContentParts[0].match(/^(.*?):(.*?):(.*?):(.*?):(.*?):(.*?)$/); + if (paramsMatcher && paramsMatcher.length === 7) { + let host = paramsMatcher[1]; + let port = paramsMatcher[2]; + let protocol = paramsMatcher[3]; + let method = paramsMatcher[4]; + let obfs = paramsMatcher[5]; + let pass = urlsafeBase64Decode(paramsMatcher[6]); + let obfsparam = ''; + let protoparam = ''; + let group = ''; + let remarks = ''; + if (rawContentParts.length > 1) { + let target = rawContentParts[1]; + obfsparam = urlsafeBase64Decode(getParam('obfsparam', target)); + protoparam = urlsafeBase64Decode(getParam('protoparam', target)); + group = urlsafeBase64Decode(getParam('group', target)); + remarks = urlsafeBase64Decode(getParam('remarks', target)); + } + if (tag === '' && group !== '') { + tag = group; + } + let finalName = remarks === '' ? `${host}:${port}` : remarks + first = finalName + let res = `${finalName} = shadowsocksr, ${host}, ${port}, ${method}, "${pass}", protocol=${protocol}, obfs=${obfs}`; + res += protoparam ? `, protocol_param=${protoparam}` : ''; + res += obfsparam ? `, obfs_param="${obfsparam}"` : ''; + return res; + } + else { + return ''; + } + } + else { + return ''; + } + }); + let sstag = first + if (decodedLinks.length > 1) { + sstag = `批量SSR节点(${decodedLinks.length})` + } + if (tag !== '') { + sstag = tag + } + return { servers: decodedLinks, sstag: sstag } +} + +function getServersFromConfFile(params) { + let promiseArray = params.urls.map(i => promiseConf(i)) + Promise.all(promiseArray).then(confs => { + for (let idx in confs) { + let res = confs[idx] + let type = res ? res.type : -1 + let filename = res ? res.filename : ''; + let servers = res ? res.servers.split(/[\n\r]+/).filter(item => item !== '').map(i => i.strictTrim()) : []; + params.handler({ servers: servers, filename: filename, url: params.urls[idx], type: type }) + } + }).catch(reason => { + console.error(reason.stack) + params.handler(null) + }) +} + +function isJson(str) { + try { + JSON.parse(str) + } catch (e) { + return false + } + return true +} + +function decodeVmess(links) { + let result = [] + let tag = '' + + for (let idx in links) { + let link = links[idx] + + if (/^vmess:\/\/(.*?)$/.test(link)) { + let content = urlsafeBase64Decode(RegExp.$1) + if (isJson(content)) { + // v2rayN style + let jsonConf = JSON.parse(content) + let group = '' + const ua = 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/16A5366a' + let obfs = `,obfs=${jsonConf.net === 'ws' ? 'ws' : 'http'},obfs-path="${jsonConf.path || '/'}",obfs-header="Host:${jsonConf.host || jsonConf.add}[Rr][Nn]User-Agent:${ua}"` + let quanVmess = `${jsonConf.ps} = vmess,${jsonConf.add},${jsonConf.port},chacha20-ietf-poly1305,"${jsonConf.id}",group=${group},over-tls=${jsonConf.tls === 'tls' ? 'true' : 'false'},certificate=1${jsonConf.type === 'none' && jsonConf.net !== 'ws' ? '' : obfs}` + result.push(quanVmess) + } else { + // Quantumult style + if (/group=(.*?),/.test(content)) { + tag = RegExp.$1 + } + result.push(content) + } + } + } + return { servers: result, sstag: tag || `批量V2Ray节点(${result.length})` } +} + +function decodeScheme(urls) { + // let urls = params.ssURL + let result = [] + let tag + let group = '' + + for (let idx in urls) { + let url = urls[idx] + let method, password, hostname, port, plugin + if (!url.includes('#')) { + let name = '无节点名称' + url += `#${name}` + } + tag = $text.URLDecode(url.match(/#(.*?)$/)[1]) + if (url.includes('?')) { + // tag = $text.URLDecode(url.match(/#(.*?)$/)[1]) + let mdps = url.match(/ss:\/\/(.*?)@/)[1] + let padding = 4 - mdps.length % 4 + if (padding < 4) { + mdps += Array(padding + 1).join('=') + } + let userinfo = $text.base64Decode(mdps) + method = userinfo.split(':')[0] + password = userinfo.split(':')[1] + let htpr = url.match(/@(.*?)\?/)[1].replace('\/', '') + hostname = htpr.split(':')[0] + port = htpr.split(':')[1] + let ps = $text.URLDecode(url.match(/\?(.*?)#/)[1]) + let obfsMatcher = ps.match(/obfs=(.*?)(;|$)/) + let obfsHostMatcher = ps.match(/obfs-host=(.*?)(&|;|$)/) + if (obfsMatcher) { + let obfs = obfsMatcher[1] + let obfsHost = obfsHostMatcher ? obfsHostMatcher[1] : 'cloudfront.net' + plugin = `obfs=${obfs}, obfs-host=${obfsHost}` + } + if (/group=(.*)(&|;|$)/.test(ps)) { + group = $text.base64Decode(RegExp.$1.trim()) + } + } else { + if (/ss:\/\/([^#]*)/.test(url)) { + let mdps = RegExp.$1 + if (/^(.*)@(.*?):(.*?)$/.test(mdps)) { + hostname = RegExp.$2 + port = RegExp.$3 + let methodAndPass = urlsafeBase64Decode(RegExp.$1) + console.log('methodAndPass', methodAndPass); + if (/^(.*?):(.*?)$/.test(methodAndPass)) { + method = RegExp.$1 + password = RegExp.$2 + } + } else { + let padding = 4 - mdps.length % 4 + if (padding < 4) { + mdps += Array(padding + 1).join('=') + } + if (/^(.*?):(.*)@(.*?):(.*?)$/.test($text.base64Decode(mdps))) { + method = RegExp.$1 + password = RegExp.$2 + hostname = RegExp.$3 + port = RegExp.$4 + } + } + } + } + let proxy = `${tag} = custom, ${hostname}, ${port}, ${method}, ${password}, https://github.com/lhie1/Rules/blob/master/SSEncrypt.module?raw=true` + if (plugin != undefined) { + proxy += `, ${plugin}` + } + result[idx] = proxy + } + let outName = '' + if (group) { + outName = group + } else if (result.length === 1) { + outName = tag + } else { + outName = `批量ss节点(${result.length})` + } + return { servers: result, sstag: outName } +} + +module.exports = { + proxyFromConf: getServersFromConfFile, + proxyFromURL: decodeScheme, + proxyFromVmess: decodeVmess, + proxyFromSSR: decodeSSR +} \ No newline at end of file diff --git a/Rules-lhie1/scripts/ruleUpdateUtil.js b/Rules-lhie1/scripts/ruleUpdateUtil.js new file mode 100644 index 0000000..2cc202c --- /dev/null +++ b/Rules-lhie1/scripts/ruleUpdateUtil.js @@ -0,0 +1,87 @@ +let githubRawReg = /^https:\/\/raw\.githubusercontent\.com\/(.*?)\/(.*?)\/master\/(.*?)$/ + +const FILE = 'data.js' + +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 getSoftwareType() { + let file = JSON.parse($file.read(FILE).string) + let workspace = file.workspace + let outputFormat = workspace.outputFormat + if (outputFormat === 'Surge 3 TF') { + return 0 + } else if (outputFormat === 'Surge 2') { + return 2 + } else if (outputFormat === 'Quantumult') { + return 3 + } + return 1 +} + +function checkUpdate(oldSha, newSha) { + return Object.keys(newSha).some(i => oldSha[i] !== newSha[i]) +} + +function setFilesSha(sha) { + let file = JSON.parse($file.read(FILE).string) + file['repoSha'] = sha + $file.write({ + data: $data({ "string": JSON.stringify(file) }), + path: FILE + }) +} + +function getFilesSha() { + let file = JSON.parse($file.read(FILE).string) + return file['repoSha'] || {} +} + +function getGitHubFilesSha(params) { + params.handler({}) +} + +function getRepoInfo() { + let owner = 'lhie1'; + let repoName = 'Rules'; + let filePath = 'Auto'; + let softwareType = getSoftwareType() + if (softwareType === 0) { + filePath = 'Auto_New' + } else if (softwareType === 3) { + filePath = 'Quantumult' + } + console.log(filePath) + let rulesRep = getRulesReplacement(); + if (rulesRep) { + let reg = rulesRep.match(githubRawReg); + if (reg && reg.length === 4) { + owner = reg[1]; + repoName = reg[2]; + filePath = reg[3]; + } + } + return { owner, repoName, filePath }; +} + +function getLatestCommitMessage(params) { + params.handler(null) +} + +module.exports = { + checkUpdate: checkUpdate, + getGitHubFilesSha: getGitHubFilesSha, + setFilesSha: setFilesSha, + getFilesSha: getFilesSha, + getLatestCommitMessage: getLatestCommitMessage, + getRepoInfo: getRepoInfo +} \ No newline at end of file diff --git a/Rules-lhie1/scripts/siri.js b/Rules-lhie1/scripts/siri.js new file mode 100644 index 0000000..269289f --- /dev/null +++ b/Rules-lhie1/scripts/siri.js @@ -0,0 +1,14 @@ +const ruleUpdateUtil = require('scripts/ruleUpdateUtil'); + +function siriRun() { + +} + +function renderSiriUI() { + $intents.height = 100 +} + +module.exports = { + siriRun: siriRun, + renderSiriUI: renderSiriUI +} \ No newline at end of file diff --git a/Rules-lhie1/scripts/sizeUtil.js b/Rules-lhie1/scripts/sizeUtil.js new file mode 100644 index 0000000..e976afa --- /dev/null +++ b/Rules-lhie1/scripts/sizeUtil.js @@ -0,0 +1,8 @@ +function pc(x) { + let sw = $device.info.screen.width + return x * sw / 100 +} + +module.exports = { + pc: pc +} \ No newline at end of file diff --git a/Rules-lhie1/scripts/socketLogger.js b/Rules-lhie1/scripts/socketLogger.js new file mode 100644 index 0000000..0fbee49 --- /dev/null +++ b/Rules-lhie1/scripts/socketLogger.js @@ -0,0 +1,49 @@ +module.exports = { + init: (address, port, clearFirst = true, debug = true) => { + var oldLog = console.log; + var oldInfo = console.info; + var oldWarn = console.warn; + var oldError = console.error; + let start = clearFirst + var socket = $socket.new(`ws://${address}:${port}`); + socket.open() + console.log = function (obj) { + if (debug) { + let msg = JSON.stringify({ type: 'log', args: Array.prototype.slice.call(arguments) }); + start = sendMessage(start, socket, msg); + } + oldLog.apply(console, arguments); + } + console.info = function (obj) { + if (debug) { + let msg = JSON.stringify({ type: 'info', args: Array.prototype.slice.call(arguments) }); + start = sendMessage(start, socket, msg); + } + oldInfo.apply(console, arguments); + } + console.warn = function (obj) { + if (debug) { + let msg = JSON.stringify({ type: 'warn', args: Array.prototype.slice.call(arguments) }); + start = sendMessage(start, socket, msg); + } + oldWarn.apply(console, arguments); + } + console.error = function (obj) { + if (debug) { + let msg = JSON.stringify({ type: 'error', args: Array.prototype.slice.call(arguments) }); + start = sendMessage(start, socket, msg); + } + oldError.apply(console, arguments); + } + } +} + +function sendMessage(start, socket, msg) { + if (start) { + socket.send(JSON.stringify({ type: "_open" })); + start = false; + } + socket.send(msg); + return start; +} + diff --git a/Rules-lhie1/scripts/today.js b/Rules-lhie1/scripts/today.js new file mode 100644 index 0000000..5744268 --- /dev/null +++ b/Rules-lhie1/scripts/today.js @@ -0,0 +1,468 @@ +const ruleUpdateUtil = require('scripts/ruleUpdateUtil') +const updateUtil = require('scripts/updateUtil') + +const loadingHint = "检查规则/脚本更新..." + +const scriptName = $addin.current.name + +const FILE = 'data.js' + +let pm = function (method) { + return new Promise((resolve, reject) => { + method({ + handler: res => { + resolve(res) + } + }) + }) +} + +function getIfaData() { + return Object.keys($network.ifa_data).filter(i => i.indexOf('utun') > -1) +} + +function vpnStatus() { + try { + // 支持最新TF($network.proxy_settings) + let proxySettings = $network.proxy_settings + let proxyScoped = proxySettings['__SCOPED__'] + let lans = Object.keys(proxyScoped) + return lans.find(i => i.indexOf('utun') > -1) ? 1 : 0 + } catch (e) { + // 兼容旧版,需要手动设置状态 + if ($cache.get("surgeOn")) { + let surgeOn = $cache.get('surgeOn') + let nowIfa = getIfaData() + let oldIfa = surgeOn.ifaKeys + if (nowIfa.length === oldIfa.length) { + return surgeOn.status ? 1 : 0 + } else { + return surgeOn.status ? 0 : 1 + } + } + return -1 + } +} + +function genSrugeLabel(status, isQuan) { + if (status === -1) { + return '长按设置' + } else if (status === 0) { + return isQuan ? '开启Quantumult' : '开启Surge' + } else { + return isQuan ? '关闭Quantumult' : '关闭Surge' + } +} + +function requestHead(url) { + if (!/^https?:\/\//.test(url)) return Promise.resolve('') + return new Promise((resolve, reject) => { + $http.request({ + method: "HEAD", + url: url, + header: { + // 'User-Agent': 'Quantumult' + }, + handler: function (resp) { + let headerFields = resp.response.runtimeValue().$allHeaderFields().rawValue(); + if ('Subscription-userinfo' in headerFields) { + resolve(headerFields['Subscription-userinfo']) + } else if ('subscription-userinfo' in headerFields) { + resolve(headerFields['subscription-userinfo']) + } else { + resolve('') + } + } + }) + }) +} + +function parseUsage(usageStr) { + let uploadMatcher = usageStr.match(/upload=(\d+)(?:;|$)/) + let downloadMatcher = usageStr.match(/download=(\d+)(?:;|$)/) + let totalMatcher = usageStr.match(/total=(\d+)(?:;|$)/) + let upload = 0 + let download = 0 + let total = 0 + if (uploadMatcher && uploadMatcher[1]) upload = uploadMatcher[1] * 1 + if (downloadMatcher && downloadMatcher[1]) download = downloadMatcher[1] * 1 + if (totalMatcher && totalMatcher[1]) total = totalMatcher[1] * 1 + return { + upload: upload, + download: download, + total: total + } +} + +function widgetSettings(file) { + let items = file.widgetSettings.split(/[\r\n]+/g).filter(i => /^.*?=\s*http/.test(i)) + return items.map(i => { + let p = i.split(/=/) + return { + name: p[0].trim(), + url: p.slice(1).join('=').trim() + } + }) +} + +function renderTodayUI() { + let file = JSON.parse($file.read(FILE).string) + let workspace = file.workspace + let widget = widgetSettings(file); + let groupNames = workspace.serverData.map(i => i.title).concat(widget.map(i => i.name)) + let groupURLs = workspace.serverData.map(i => i.url).concat(widget.map(i => i.url)).map(i => requestHead(i)) + Promise.all(groupURLs).then(res => { + console.log(res) + let usageData = [] + for (let idx in res) { + if (res[idx] === '') continue + let usage = parseUsage(res[idx]) + const GB = Math.pow(1024, 3) + usageData.push({ + groupName: { + text: `${groupNames[idx]}` + }, + usageProgress: { + value: (usage.download + usage.upload) / usage.total + }, + usageDetail: { + text: `↑ ${(usage.upload / GB).toFixed(2)}GB ↓ ${(usage.download / GB).toFixed(2)}GB ≡ ${((usage.total - usage.download - usage.upload) / GB).toFixed(2)}GB` + }, + usageDetail2: { + text: `${groupNames[idx]} ( ${(usage.total / GB).toFixed(2)}GB )` + } + }) + } + $("usageView").data = usageData + $("usageView").updateLayout(make => { + make.height.equalTo(usageData.length * 50) + }) + $widget.height = 110 + (usageData.length * 50) + }) + $widget.modeChanged = mode => { + if (mode === 1) { + $widget.height = 110 + ($("usageView").data.length * 50) + } + } + let outputFormat = workspace.outputFormat + let surge2 = outputFormat === 'Surge 2' + let isQuan = outputFormat === 'Quantumult' + let isLauncher = $app.widgetIndex < 0 || $app.widgetIndex > 2 + let checks = [pm(ruleUpdateUtil.getGitHubFilesSha), pm(updateUtil.getLatestVersion)] + let vStatus = vpnStatus() + Promise.all(checks).then(res => { + let canUpdate = ruleUpdateUtil.checkUpdate(ruleUpdateUtil.getFilesSha(), res[0]) + let newVersion = updateUtil.needUpdate(res[1], updateUtil.getCurVersion()) + $("newTag").hidden = !canUpdate + $("newVersionTag").hidden = !newVersion + return canUpdate ? pm(ruleUpdateUtil.getLatestCommitMessage) : Promise.resolve() + }).then(res => { + let { owner, repoName, filePath } = ruleUpdateUtil.getRepoInfo() + $("updateStatus").text = res ? res.commit.message : `${owner}\/${repoName}` + }) + let targetAppOn = $file.read("assets/today_surge.png") + let targetAppOff = $file.read("assets/today_surge_off.png") + if (isQuan) { + targetAppOn = $file.read("assets/today_quan.png") + targetAppOff = $file.read("assets/today_quan_off.png") + } else if (surge2) { + targetAppOn = $file.read("assets/today_surge2.png") + } + $ui.render({ + props: { + id: "todayMainView", + title: "Surge3规则生成", + hideNavbar: true, + navBarHidden: true, + }, + views: [{ + type: "blur", + props: { + id: "close", + style: 1, + radius: 0, + hidden: !isLauncher + }, + layout: (make, view) => { + make.width.height.equalTo(view.super).offset(10) + make.top.equalTo(view.super.top).offset(-10) + }, + events: { + tapped: sender => { + $app.close(0.3) + } + } + }, { + type: "view", + props: { + id: "", + }, + layout: (make, view) => { + make.height.equalTo(110) + make.width.equalTo(view.super).offset(-60) + make.centerX.equalTo(view.super) + }, + views: [{ + type: "label", + props: { + id: "updateStatus", + text: "Rules-lhie1 by Fndroid", + font: $font(12), + textColor: $rgba(50, 50, 50, .3) + }, + layout: (make, view) => { + make.top.equalTo(view.super.top).offset(5) + make.centerX.equalTo(view.super) + } + }, { + type: "label", + props: { + id: "updateStatus", + text: loadingHint, + font: $font(12), + textColor: $rgba(50, 50, 50, .3) + }, + layout: (make, view) => { + make.bottom.equalTo(view.super.bottom).offset(-5) + make.centerX.equalTo(view.super) + } + }, { + type: "image", + props: { + id: "pullBtn", + data: $file.read("assets/today_pull.png"), + radius: 25, + bgcolor: $rgba(255, 255, 255, 0) + }, + layout: (make, view) => { + make.width.height.equalTo(55) + make.centerY.equalTo(view.super).offset(-10) + make.centerX.equalTo(view.super) + }, + events: { + tapped: sender => { + $app.openURL(`jsbox://run?name=${encodeURIComponent(scriptName)}&auto=1`) + } + }, + }, { + type: "image", + props: { + id: "surgeBtn", + data: vStatus === 0 ? targetAppOff : targetAppOn, + radius: 25, + bgcolor: $rgba(255, 255, 255, 0) + }, + layout: (make, view) => { + make.width.height.equalTo(55) + make.centerY.equalTo(view.super).offset(-10) + // make.left.equalTo(view.prev.left).offset(-(sw / 3.5)) + make.left.equalTo(view.super) + console.log('width', $widget.width) + }, + events: { + tapped: sender => { + let url = `surge${surge2 ? "" : "3"}:///toggle?autoclose=true` + if (isQuan) { + url = 'quantumult://' + (vStatus === 0 ? 'start' : 'stop') + } + $app.openURL(url) + }, + longPressed: sender => { + $ui.alert({ + title: "初始设置", + message: '请选择当前VPN开关状态?', + actions: [{ + title: '已关闭', + handler: () => { + $cache.set("surgeOn", { + status: false, + ifaKeys: getIfaData() + }) + } + }, { + title: '已开启', + handler: () => { + $cache.set("surgeOn", { + status: true, + ifaKeys: getIfaData() + }) + } + }] + }) + } + } + }, { + type: "image", + props: { + id: "jsboxBtn", + data: $file.read("assets/today_jsbox.png"), + radius: 25, + bgcolor: $rgba(255, 255, 255, 0) + }, + layout: (make, view) => { + make.width.height.equalTo(50) + make.centerY.equalTo(view.super).offset(-10) + // make.right.equalTo(view.prev.prev.right).offset((sw / 3.5)) + make.right.equalTo(view.super) + }, + events: { + tapped: sender => { + $app.openURL(`jsbox://run?name=${encodeURIComponent(scriptName)}`) + } + } + }, { + type: "label", + props: { + text: "更新规则", + font: $font(12), + textColor: $rgba(50, 50, 50, .8), + align: $align.center + }, + layout: (make, view) => { + make.height.equalTo(12) + make.top.equalTo($("pullBtn").bottom) + make.width.equalTo($("pullBtn").width) + make.centerX.equalTo($("pullBtn")) + } + }, { + type: "label", + props: { + id: "surgeLabel", + text: genSrugeLabel(vStatus, isQuan), + font: $font(12), + textColor: $rgba(50, 50, 50, .8), + align: $align.center + }, + layout: (make, view) => { + make.height.equalTo(12) + make.top.equalTo(view.prev.top) + make.centerX.equalTo($("surgeBtn")) + } + }, { + type: "label", + props: { + text: "脚本设置", + font: $font(12), + textColor: $rgba(50, 50, 50, .8), + align: $align.center + }, + layout: (make, view) => { + make.height.equalTo(12) + make.top.equalTo($("pullBtn").bottom) + make.width.equalTo($("pullBtn").width) + make.centerX.equalTo($("jsboxBtn")) + } + }, { + type: "image", + props: { + id: "newTag", + data: $file.read("assets/new_rules_tag.png"), + bgcolor: $rgba(255, 255, 255, 0), + hidden: true + }, + layout: (make, view) => { + make.width.height.equalTo(15) + make.centerY.equalTo(view.super).offset(-20) + make.left.equalTo($("pullBtn").right).offset(-10) + } + }, { + type: "image", + props: { + id: "newVersionTag", + data: $file.read("assets/new_version_tag.png"), + bgcolor: $rgba(255, 255, 255, 0), + hidden: true + }, + layout: (make, view) => { + make.width.height.equalTo(15) + make.centerY.equalTo(view.super).offset(-20) + make.left.equalTo($("jsboxBtn").right).offset(-10) + } + }, { + type: "image", + props: { + id: "closeBtn", + data: $file.read("assets/close_icon.png"), + bgcolor: $rgba(255, 255, 255, 0), + hidden: !isLauncher, + alpha: 0.7 + }, + layout: (make, view) => { + make.width.height.equalTo(20) + make.top.equalTo(view.super.top).offset(10) + make.right.equalTo(view.super.right).offset(-10) + }, + events: { + tapped: sender => { + $app.close(.2) + } + } + }] + }, { + type: 'list', + props: { + id: "usageView", + data: [], + rowHeight: 50, + alwaysBounceVertical: false, + bgcolor: $color("clear"), + separatorHidden: true, + template: { + props: { + bgcolor: $color("clear") + }, + views: [{ + type: "progress", + props: { + id: 'usageProgress' + }, + layout: function (make, view) { + make.centerY.equalTo(view.super).offset(-3); + make.centerX.equalTo(view.super); + make.height.equalTo(3) + make.width.equalTo(view.super).multipliedBy(1).offset(-50) + }, + views: [{ + type: 'label', + props: { + id: 'usageDetail', + align: $align.center, + font: $font("bold", 10), + textColor: $color("#595959") + }, + layout: (make, view) => { + make.width.equalTo(view.super) + make.height.equalTo(20) + make.top.equalTo(view.super).offset(-20) + make.centerX.equalTo(view.super) + } + }, { + type: 'label', + props: { + id: 'usageDetail2', + align: $align.center, + font: $font("bold", 12), + textColor: $color("#595959") + }, + layout: (make, view) => { + make.width.equalTo(view.super) + make.height.equalTo(23) + make.top.equalTo(view.super) + make.centerX.equalTo(view.super) + } + }] + }] + } + }, + layout: (make, view) => { + make.top.equalTo(110) + make.width.equalTo(view.super) + make.height.width.equalTo(100) + } + }] + }) +} + +module.exports = { + renderTodayUI: renderTodayUI +} diff --git a/Rules-lhie1/scripts/updateUtil.js b/Rules-lhie1/scripts/updateUtil.js new file mode 100644 index 0000000..0a0f5dc --- /dev/null +++ b/Rules-lhie1/scripts/updateUtil.js @@ -0,0 +1,60 @@ +function getCurVersion() { + let version = $file.read("version.fndroid").string + return version +} + +function needRestart() { + return $http.get({ + url: 'https://raw.githubusercontent.com/Fndroid/jsbox_script/master/Rules-lhie1/restart.fndroid' + }) +} + +function getLatestVersion(params) { + $http.get({ + url: 'https://raw.githubusercontent.com/Fndroid/jsbox_script/master/Rules-lhie1/version.fndroid' + '?t=' + new Date().getTime(), + handler: res => { + params.handler(res.data) + } + }) +} + +function updateScript(version) { + let url = 'https://raw.githubusercontent.com/Fndroid/jsbox_script/master/Rules-lhie1/.output/Rules-lhie1.box' + '?t=' + new Date().getTime() + const scriptName = $addin.current.name + let downloadBox = $http.download({ + url: url + }) + Promise.all([downloadBox, needRestart()]).then(res => { + let box = res[0].data + let restart = /true/.test(res[1].data) + $addin.save({ + name: scriptName, + data: box, + handler: (success) => { + if (success) { + $ui.toast(`更新完成`) + if (restart) { + $delay(0.3, () => { + $addin.run(scriptName) + }) + } + } + } + }) + }) +} + +function needUpdate(nv, ov) { + let getVersionWeight = i => { + return i.split('.').map(i => i * 1).reduce((s, i) => s * 100 + i) + } + return getVersionWeight(nv) > getVersionWeight(ov) +} + + +module.exports = { + getCurVersion: getCurVersion, + getLatestVersion: getLatestVersion, + updateScript: updateScript, + needUpdate: needUpdate +} diff --git a/Rules-lhie1/scripts/videoReg.js b/Rules-lhie1/scripts/videoReg.js new file mode 100644 index 0000000..a8482d7 --- /dev/null +++ b/Rules-lhie1/scripts/videoReg.js @@ -0,0 +1,8 @@ +module.exports = { + Netflix: /(?=USER-AGENT,Argo\*,|DOMAIN-SUFFIX,netflix.com,|DOMAIN-SUFFIX,netflix.net,|DOMAIN-SUFFIX,nflxext.com,|DOMAIN-SUFFIX,nflximg.com,|DOMAIN-SUFFIX,nflximg.net,|DOMAIN-SUFFIX,nflxvideo.net,)[^\n]+/g, + Spotify: /(?=PROCESS-NAME,Spotify,|DOMAIN-SUFFIX,spoti.fi,|DOMAIN-KEYWORD,spotify,)[^\n]+/g, + YouTube: /(?=USER-AGENT,com.google.ios.youtube\*,|USER-AGENT,YouTube\*,|DOMAIN-SUFFIX,googlevideo.com,|DOMAIN-SUFFIX,youtube.com,)[^\n]+/g, + MytvSUPER: /(?=DOMAIN-KEYWORD,nowtv100,|DOMAIN-KEYWORD,rthklive,|DOMAIN-SUFFIX,mytvsuper.com,|DOMAIN-SUFFIX,tvb.com,)[^\n]+/g, + BBC: /(?=USER-AGENT,BBCiPlayer\*,|DOMAIN-KEYWORD,co.uk,|DOMAIN-KEYWORD,uk-live,|DOMAIN-SUFFIX,bbc.co,|DOMAIN-SUFFIX,bbc.com,)[^\n]+/g, + LINE: /(?=DOMAIN-SUFFIX,lin.ee,|DOMAIN-SUFFIX,line.me,|DOMAIN-SUFFIX,line.naver.jp,|DOMAIN-SUFFIX,line-apps.com,|DOMAIN-SUFFIX,line-cdn.net,|DOMAIN-SUFFIX,line-scdn.net,|DOMAIN-SUFFIX,nhncorp.jp,|IP-CIDR,103.2.28.0\/22,|IP-CIDR,119.235.224.0\/21,|IP-CIDR,119.235.232.0\/23,|IP-CIDR,119.235.235.0\/24,|IP-CIDR,119.235.236.0\/23,|IP-CIDR,125.6.146.0\/24,|IP-CIDR,125.6.149.0\/24,|IP-CIDR,125.6.190.0\/24,|IP-CIDR,125.209.208.0\/20,|IP-CIDR,203.104.103.0\/24,|IP-CIDR,203.104.128.0\/20,|IP-CIDR,203.174.66.64\/26,|IP-CIDR,203.174.77.0\/24,)[^\n]+/g +} \ No newline at end of file diff --git a/Rules-lhie1/strings/en.strings b/Rules-lhie1/strings/en.strings new file mode 100644 index 0000000..ae42947 --- /dev/null +++ b/Rules-lhie1/strings/en.strings @@ -0,0 +1,3 @@ +"OK" = "OK"; +"DONE" = "Done"; +"HELLO_WORLD" = "Hello, World!"; diff --git a/Rules-lhie1/strings/zh-Hans.strings b/Rules-lhie1/strings/zh-Hans.strings new file mode 100644 index 0000000..b2d0c5d --- /dev/null +++ b/Rules-lhie1/strings/zh-Hans.strings @@ -0,0 +1,3 @@ +"OK" = "好的"; +"DONE" = "完成"; +"HELLO_WORLD" = "你好,世界!"; diff --git a/Rules-lhie1/updateLog.md b/Rules-lhie1/updateLog.md new file mode 100644 index 0000000..fe4f5d1 --- /dev/null +++ b/Rules-lhie1/updateLog.md @@ -0,0 +1,546 @@ +**如打赏名单有遗漏,请TG联系我加一下** + +#### 2019.05.06 +1. 修复规则仓库调整导致的不兼容问题 -v1.9.70 + +#### 2019.04.15 +1. 修复“删除节点”逻辑问题 -v1.9.69 + +#### 2019.03.21 +1. 修复Quantumult自定义url rewrite不生效问题 -v1.9.67 + +#### 2019.03.20 +1. 修复规则仓库调整导致的不兼容问题 -v1.9.64 +2. 修复当只有userHostName时,配置文件出现第一行为空的问题 -v1.9.65 +3. 修复Quantumult规则变化导致的问题 -v1.9.66 + +#### 2019.02.25 +1. 修复规则仓库调整导致的不兼容问题 -v1.9.63 +2. 修复ss节点密码带有@导致不能识别问题 -v1.9.63 + +#### 2019.02.07 +1. 修复RuleSet注释无法识别问题 -v1.9.62 + +#### 2019.01.04 +1. 修复进阶设置占位符滚动问题 -v1.9.61 + +#### 2019.01.01 +1. 修复导出到Quantumult时RULE-SET转换错误问题 -v1.9.60 + +#### 2018.12.30 +1. rename替换中使用``>``代替``,`` -v1.9.59 + +#### 2018.12.21 +1. 支持导入混合类型订阅 -v1.9.58 + +#### 2018.12.20 +1. 修复RULE-SET被注释依然转换问题 -v1.9.57 + +#### 2018.11.24 +1. 修复非TF导出带有RULE-SET问题 -v1.9.56 + +#### 2018.11.19 +1. 修复Quantumult和Surge TF无法使用replacement问题 -v1.9.54 + +#### 2018.11.17 +1. 修复Widget布局问题 -v1.9.52 +2. 自定义规则对所有导出支持RULE-SET -v1.9.52 + +#### 2018.11.16 +1. 去除特殊代理 -v1.9.51 +2. 去除RuleSet的时间戳 -v1.9.51 + +#### 2018.11.14 +1. Quantumult所有Rejection部分使用最新Rewrite实现1x1像素过滤 -v1.9.50 +2. 替换支付宝红包 -v1.9.50 + +#### 2018.11.08 +1. 修复SSEncrypt.module失效问题 -v1.9.47 +2. 修复重复策略名无法导出问题 -v1.9.48 + +#### 2018.11.03 +1. 修复Quantumult问题 -v1.9.45 + +#### 2018.11.01 +1. 允许添加空节点 -v1.9.43 + +#### 2018.10.30 +1. 修复Shadowsocks订阅识别问题 -v1.9.39 +2. 修复Quantumult规则导出问题 -v1.9.40 +3. 修复SurgeTF规则导出问题 -v1.9.40 + +#### 2018.10.29 +1. 调整Quantumult自定义规则位置于GEOIP,CN上面 -v1.9.38 + +#### 2018.10.27 +1. 修复Quantumult所有LAN规则没有设置为DIRECT的问题 -v1.9.37 + +#### 2018.10.22 +1. 修正备份问题 -v1.9.36 + +#### 2018.10.18 +1. 恢复占位符重命名 -v1.9.35 +1. 代理分组输入框提供所有占位符名称选择 -v1.9.35 + +#### 2018.10.17 +1. 修复动画资源过大问题 -v1.9.34 + +#### 2018.10.15 +1. 小组件流量显示调整,设置时等号两边可以留空格 -v1.9.33 + +#### 2018.10.14 +1. 小组件规则更新信息根据APP显示 -v1.9.32 + +#### 2018.10.10 +1. 修复Shadowsocks链接导入错误 -v1.9.30 +2. 修复Shadowsocks订阅在Quantumult不生成的问题 -v1.9.31 + +#### 2018.10.05 +1. 调整调试状态栏显示状态下UI被遮挡的问题 -v1.9.28 + +#### 2018.10.03 +1. 替换占位符操作“重命名”为“删除节点” -v1.9.26 +2. 修复ssid策略组名称问题 -v1.9.27 + +#### 2018.10.01 +1. 调整RULE-SET中的URL,添加时间戳强制刷新 -v1.9.24 +2. 支持添加类型为round-robin的策略组,配置如select,对应Quantumult轮询 -v1.9.24 +3. 修复规则替换问题 -v1.9.25 + +#### 2018.09.29 +1. 生成Quantumult规则时,自动生成对应的favorite -v1.9.22 +2. 增加列表下拉更新节点 -v1.9.23 + +#### 2018.09.28 +1. 修复Quantumult无法导出问题 -v1.9.15 +2. 增加升级重启逻辑,仅在需要时重启脚本 -v1.9.16 +3. 修复SSID设置失效问题 -v1.9.17 +4. 增加导出Surge3 TF格式规则 -v1.9.18 +5. 修复图标问题 -v1.9.19 +6. 修复Bug -v1.9.21 + +#### 2018.09.25 +1. 修复特殊代理设置后无法生成配置文件问题 -v1.9.13 + +#### 2018.09.24 +1. 调整列表样式 -v1.9.12 + +#### 2018.09.20 +1. 修复特殊代理设置无效问题 -v1.9.11 + +#### 2018.09.19 +1. 调整Quantumult规则 -v1.9.9 + +#### 2018.09.16 +1. 调整ss链接识别 -v1.9.5 +2. 更改Quantulult导出的规则为Jacky Y维护的规则 -v1.9.7 +3. 增加备份文件选择后的提示 -v1.9.7 + +#### 2018.09.13 +1. 修复首次打开无法导入节点问题 -v1.9.4 +2. 允许空白分组,并在生成时清除其group信息 -v1.9.4 + +#### 2018.09.11 +1. 新添加节点显示在最顶部 -v1.9.3 +2. 修复v2ray订阅不生成问题 -v1.9.3 + +#### 2018.09.10 +1. 支持导入v2订阅(Quantumult和v2rayN),去除对Shadowrocket和Kitsunebi的vmess格式支持 -v1.9.1 +2. v2rayN的vmess默认加密方式改为chacha20-ietf-poly1305 -1.9.2 + +#### 2018.08.30 +1. 修复bug -v1.8.4 + +#### 2018.08.28 +1. 修复加载慢或无法加载的问题 -v1.8.2 + +#### 2018.08.26 +1. 修复Bug -v1.7.89 +2. 适配Quantumult的SSID suspend -v1.8.1 + +#### 2018.08.23 +1. 调整导航栏样式 -v1.7.82 +2. 增加Loading动画 -v1.7.83 + +#### 2018.08.22 +1. 允许定义部分UI(主界面和进阶设置)颜色,且不会因更新覆盖 -v1.7.80 + +#### 2018.08.21 +1. 增加从Quantumult导出节点,导出文件是选择JSBox即可 -v1.7.76 +2. 恢复SSID设置 -1.7.77 + +#### 2018.08.20 +1. 增加小组件流量显示自定义 -v1.7.75 + +#### 2018.08.19 +1. 修复SSR订阅识别Quantumult无法联网问题 -v1.7.73 +2. 修复Bug -v1.7.74 + +#### 2018.08.18 +1. 增加节点编辑,去除重命名 -v1.7.71 +2. 兼容某些不合规范的订阅 -v1.7.72 + +#### 2018.08.17 +1. 特殊代理设置会在脚本启动时从Github更新,地址:https://github.com/Fndroid/specialReg -v1.7.70 + +#### 2018.08.13 +1. 更新默认General配置,更新需要清空进阶设置里的“常规” -v1.7.69 + +#### 2018.08.12 +1. Emoji功能独立开关 -v1.7.66 +2. 修复更新节点时批量节点被恢复问题 -v1.7.66 +3. 部分机场订阅不规范无法识别问题 -v1.7.67 + +#### 2018.08.11 +1. Emoji更新可以对所有节点生效 -v1.7.64 +2. 修复bug -v1.7.65 + +#### 2018.08.09 +1. 修复SSR转换时模块丢失问题 -v1.7.63 + +#### 2018.08.07 +1. SSR链接导出到Surge时仅支持plain/origin兼容模式 -v1.7.62 +2. 订阅识别不支持混合类型,仅仅会读取首个节点类型,不同的将被抛弃 -v1.7.62 + +#### 2018.08.06 +1. 增加识别external类型Surge链接 -v1.7.61 +2. 导出为Surge时尝试将链接转换为ss链接 -v1.7.61 + +#### 2018.08.05 +1. 更新节点列表时可以带上国旗Emoji -v1.7.60 + +#### 2018.08.03 +1. 修复拓展选择时注释被误识别的问题 -v1.7.59 +2. 调整下载逻辑 -v1.7.59 + +#### 2018.08.01 +1. 修复Quantumult添加后缀被误识别为混淆参数的问题 -v1.7.58 + +#### 2018.07.30 +1. 增加后缀source-type=n,n合法值为0-7,用于Quantumult导出时设置订阅的OPTION选项 -v1.7.57 +2. 增加307重定向识别 -v1.7.57 + +#### 2018.07.28 +1. 修复某些设备小组件字体被遮挡的问题 -v1.7.56 + +#### 2018.07.27 +1. 增加识别Shadowsocks订阅 -v1.7.54 +2. 修复某些机场不能显示流量问题 -v1.7.54 +3. 增加节点更新/识别失败提示 -v1.7.55 + +#### 2018.07.25 +1. 调整小组件流量显示样式 -v1.7.52 +2. 修复流量信息和组名不对应问题 -v1.7.53 + +#### 2018.07.24 +1. 修复一个很难触发的Bug -v1.7.51 + +#### 2018.07.23 +1. 界面调整 -v1.7.50 + +#### 2018.07.22 +1. Quantumult调整REJECT规则部分,解决某些广告不能屏蔽问题(如腾讯视频) -v1.7.47 +2. 删除昨晚加班解决的问题 -v1.7.48 +3. 修复小Bug -v1.7.49 + +#### 2018.07.21 +1. 修复Quantumult导出时自定义URLRewrite或URLReject丢失问题 -v1.7.44 +2. 修复Quantumult导出时因节点名称不合法而丢弃的问题 -v1.7.45 + +#### 2018.07.20 +1. 关于页面增加托管Bot入口 -v1.7.41 +2. 长按“生成配置”按钮可以快速导出data.js文件 -v1.7.41 +3. 小组件会根据配置使用Quantumult支持的流量显示方式显示流量信息 -v1.7.42 +4. 调整小组件样式 -v1.7.43 + +#### 2018.07.19 +1. 更新托管文件名获取逻辑 -v1.7.40 + +#### 2018.07.18 +1. 调整小组件和启动器区分逻辑 -v1.7.38 +2. 调整主界面UI -v1.7.38 +3. 区分Surge2/3的图标 -v1.7.39 + +#### 2018.07.17 +1. 生成Quantumult配置时,如检测到SSR链接,则在Favorite里生成对应订阅 -v1.7.37 + +#### 2018.07.16 +1. 配置文件增加覆盖功能 -v1.7.36 + +#### 2018.07.15 +1. 支持识别Kitsunebi和Shadowrocket的V2Ray链接 - v1.7.35 + +#### 2018.07.13 +1. 修复SSR托管无法识别问题 - v1.7.32 +2. 增加更新/导入识别结果提示 - v1.7.32 +3. 增加都某些机场不规范订阅的识别 -v1.7.33 +4. 调整节点列表样式 -v1.7.33 + +#### 2018.07.12 +1. 导入Quantumult显示分组(vmess除外) - v1.7.30 +2. 支持识别SSR订阅和批量导入 - v1.7.31 +3. 支持识别V2Ray订阅和批量导入 - v1.7.31 + +#### 2018.07.10 +1. Quantumult最新TF已经修正obfs-host问题,所以删除hack逻辑 - v1.7.28 +2. 测试识别V2Ray链接(vmess://) - v1.7.29 + +#### 2018.07.09 +1. 修复下滑问题 - v1.7.27 +2. 修复Quantumult无法获取配置DNS问题 - v1.7.27 +3. 去除URL Scheme导出filter和rejection的特性,因为Quantumult无法识别里面的Policy - v1.7.27 +4. 支持使用特殊占位符``WidgetHeader``来配置Quantumult的Widget节点 - v1.7.27 + +#### 2018.07.08 +1. hack解决圈obfs-host识别问题 - v1.7.26 +2. 修复占位符会替换策略组名问题(占位符不要与节点名冲突)- v1.7.26 +3. 增加Quantumult小组件开关 - v1.7.26 +4. 导出为Quantumult时,不选择导出则只更新filter和rejection两部分 - v1.7.26 + +#### 2018.07.07 +1. 支持导出为Quantumult格式配置(beta),测试前请备份配置先 - v1.7.25 + +#### 2018.07.06 +1. Surge开关使用最新API判断,可不预先设置 - v1.7.24 + +#### 2018.07.05 +1. 调整Surge开关状态逻辑,如不能显示请反馈 - v1.7.22 +2. Surge开关状态需要先进行初始设置(长按图标即可) - v1.7.23 + +#### 2018.07.03 +1. 小组件点击逻辑不变,但会显示Surge开关状态 + +#### 2018.06.30 +1. 修复Surge2不开启MITM时报错的问题 + +#### 2018.06.26 +1. 增加占位符重命名 +2. 修复批量导入节点在更新时会重复出现的问题 + +#### 2018.06.24 +1. 关键字删除时,可以使用正则表达式,但除了``\s``(空格),这是分隔符 + +#### 2018.06.23 +1. 列表不选择时默认为DIRECT改为默认全选 + +#### 2018.06.22 +1. 调整右上角按钮,分别改为支付宝红包领取和备份当前配置 +2. 调整UI +3. 备份改为存储到iCloud +4. 修复可以创建空占位符问题 + +#### 2018.06.21 +1. ``// replacement``注释现在将会替换除General、Proxy Group和MITM的CA部分(替换规则不需要重新生成证书)外的其他内容 +2. 修复部分情况下hostname为空的错误 + +#### 2018.06.20 +1. 脚本右上角增加导出data.js文件选项,方便后续生成订阅 + +#### 2018.06.19 +1. 修复完成按钮无法保存后缀问题 + +#### 2018.06.18 +1. rename注释可以识别等号,但需要使用转义``\=``,每个替换的左边可以是一个正则表达式,右边为替换字符串 +2. 适配新版TF,脚本主界面右上角可以直接进入Surge + +#### 2018.06.17 +1. 直接导出到Surge的时间设置为10s,10s后会关闭开启的内置服务器防止耗电过多问题 + +#### 2018.06.16 +1. 为保证小组件快捷生成成功率,取消生成后回到JSBox自动退出功能,添加按钮手动操作 +2. 小组件支持显示规则替换仓库信息(仅支持Github托管master分支的配置) +3. 修复rename和replacement注释因边界无法识别问题 + +#### 2018.06.15 +1. 支持重命名默认策略组,需要在进阶设置中代理分组中添加注释:// rename: o1 = n1, o2 = n2 +2. 支持替换lhie1的所有规则,可以配合分组代理加载其他规则,用法在进阶设置中代理规则添加注释:// replacement: https://xxxx + +#### 2018.06.14 +1. 支持Surge2(未测试,因为我没有Surge2) +2. 修复Surge2的跳转问题 + +#### 2018.06.13 +1. 更新脚本简介 + +#### 2018.06.12 +1. 更新方式改为静默更新,有新版本将不会再弹出提示更新而在后台静默更新,更新后可不重启脚本生成规则,修复因更新脚本造成打断操作而产生心里阴影面积过大的问题 +2. 修复ProxyHeader占位符丢失问题 +3. 去除重复后缀 +4. 先进行占位符替换,避免影响General的内容,占位符命名请不要与Proxy Group里面的内容相同 + +#### 2018.06.11 +1. 调整界面 +2. 去除UPD设置选项,增加节点后缀设置,可用于UDP、TFO和插件设置 +3. 修复undefined问题 + +#### 2018.06.10 +1. 生成配置时检查特殊代理设置服务器是否存在,不存在则忽略避免报错 +2. 删除**批量Auto**选项 +3. 增加**策略组别**选项,用于生成用于进阶设置的占位符,和ProxyHeader类似 +4. 修复无法删除策略组占位符问题 +5. 增加特殊代理可选择策略组功能 +6. 修复特殊代理direct无效问题 + +如担心数据丢失,可对脚本内data.js文件进行备份 + +#### 2018.06.07 +1. 修复脚本名称更改后升级和小组件异常问题 + +#### 2018.06.06 +1. 删除导入节点时对组别进行保存的功能,因为有列表更新,所以貌似也没什么用 +2. 默认给所有节点打开tfo,开关明天做 + +#### 2018.06.03 +1. 删除分组时可以选择按照关键字删除 +2. 修复单个组别无法进行关键字删除的问题 + +#### 2018.06.02 +1. 小组件/启动器在规则有更新时显示最新的commit message + +#### 2018.05.27 +1. 适配两种奇葩的机场托管节点识别(分别是在节点前面加空格和节点后面加逗号) +2. 修复部分ss节点无法识别问题 +3. 修复windows下Shadowsocks二维码无法识别问题 +4. 打开小组件或启动器时同时检查脚本和规则更新 + +#### 2018.05.26 +1. 修复obfs识别问题 +2. 调整更新等待时间 +3. 区分启动器和小组件样式,启动器加入关闭按钮(性能模式启动器有效) + +#### 2018.05.25 +1. 修正不添加节点不能导出规则问题 +2. 调整启动器启动样式,启动后点击上下两部分空白可退出脚本 + +#### 2018.05.22 +1. 调整Widget样式 + +#### 2018.05.15 +1. 增加一个特性:在Surge里面将配置文件(.conf)分享到脚本,脚本会将当前配置的自定义规则(通过Surge的Extension添加的)追加到进阶设置里。 + +> 1的识别的配置文件需要时脚本生成的且进阶设置的代理规则包括``# Custom``注释 + +#### 2018.05.13 +1. 调整导入节点表现,当列表中已有该组时,会更新已有组 +2. 导入节点增加“更新节点列表”选项,用于升级列表中所有组 + +> 上述1,2仅对1.6.19及以后导入的组别生效 + +#### 2018.05.12 +1. 调整Widget样式,去除启动器启动时的导航栏 +2. 去除输入法上方的提示栏 +3. 修复定义ProxyHeader失败问题 + +#### 2018.05.11 +1. 增加对http/https/socks5/socks5-tls代理类型的支持,统一理解为Surge链接 +2. 增强剪贴板导入,以行为单位支持批量ss链接、Surge链接和托管地址混合导入 + +#### 2018.05.10 +1. 修复Safari拓展节点消失问题 +2. 优化拉取速度,更新规则耗时更短 +3. 当节点列表只有一个分组时,节点倒序、批量Auto和删除节点将不会弹出菜单,直接操作当前唯一组 + +#### 2018.05.09 +1. 调整UI +2. 增加组别支持,每次导入的节点会被分为一组,可以根据组别进行倒序、Auto和删除操作 +3. 取消对``固定脚本``的支持,列表外上滑将不再减少高度 +4. 节点重命名选项可以对所在组别进行重命名 + +> 由于列表结构改变,原本节点列表的数据会丢失,请重新导入 + +#### 2018.05.08 +1. 增加Safari拓展,在页面添加规则 +2. 修复图标过大导致JSBox启动器无法载入问题 +3. 调整``全部Auto``,如果当前已经全选,则全部反选一次 +4. 调整``导入节点``,如果列表中已经存在,则不会重复显示 +5. 修复自动生成时选择取消Action Sheet时脚本不退出问题 + +#### 2018.05.07 +1. 增加小组件支持 +2. 修复批量导入时不能识别``\r``换行的问题 +3. 自动生成后回到JSBox自动退出脚本 +4. 特殊代理增加单独查看、清除按钮,支持选择Direct +5. 调整小组件按钮位置,规则无更新时下方小字不再显示 + +#### 2018.05.06 +1. .conf的所有配置项现在都可以在进阶配置中定义,大部分设置可以使用``-(减号)``对lhie1配置剔除 +2. 内部代码调整,异常请反馈 +3. 部分UI调整 +4. 增加特殊代理设置,左滑节点即可设置 +5. 文档修正,感谢**Mornwind**的PR + +#### 2018.05.05 +1. 增加去自定义规则删除(优先级低于添加),写法: + ![](https://github.com/Fndroid/jsbox_script/blob/master/imgs/Rules-lhie1_pre2.jpg?raw=true) +2. 修复进阶设置Gallery切换按钮不联动的问题 +3. 增加关于页面 + + 感谢**Mornwind、几位匿名赞赏者**的咖啡 + +#### 2018.05.04 +1. 检测文件名,不合法则采用Action Sheet分享 +2. 由于无法检测脚本是否在``固定脚本``中运行,所以增加了上下滑动列表高度变化,虽然很突兀,但是没办法 +3. 增加节点重命名和排序 +4. 进阶设置界面调整,DNS设置改为General,MITM默认为lhie1的,不开启则为空 +5. 修正Surge3跳转 +6. 增加URL Scheme支持,添加``auto=1``即可根据配置自动生成一次规则 + +#### 2018.05.03 +1. 增加直接导出到Surge功能,但有限制,进入Surge后不要等待过长时间确定安装 +2. 更改TestFlight选项名为UDP +3. 尝试突破Surge加载问题 +4. 修复托管direct多余问题 + + 感谢**Nicked、Mornwind**的咖啡 + +#### 2018.05.02 + +1. 增加批量导入ss节点(剪贴板) +2. 增加批量导入Surge节点(剪贴板) +3. 增加生成的过渡动画代替默认的loading +4. 修复TINYGIF的问题 +5. 增加配置Proxy Group,``ProxyHeader``为Auto节点名,``Proxy Header``为所有节点名,可更改Auto设置 +6. 删除导入的相册选择选项,如需此操作请更新JSBox + + 感谢**Wangsc1 M**的咖啡 + +#### 2018.05.01 + +1. 跳转到主应用运行 +2. 调整界面(节点操作栏不再跟随滚动,开关说明) +3. 解除底部按钮固定,适配iPhoneX屏幕 + +> 无法运行无解,我无法复现... + +#### 2018.04.30 A + +1. 每次退出脚本先保存环境,下次进入可以直接使用 +2. 修改图标 +3. 支持导入Surge节点 +4. 修复``墙洞``托管无法识别问题 + +> 仅测试了cordcloud和rixCloud的托管导入,其他如果不能导入的,要不把托管借我测试再改密,要不等别人借我测试 + +> 脚本无法启动问题已经提交JSBox作者调试 + +#### 2018.04.30 + +1. 修复TF无效(节点无udp-relay=true)的问题 +2. 修复升级日志遮挡问题 +3. 由于``$addin.save``无回调,增加2s防止极端情况更新失败 +4. 调整界面,去掉Auto显示框,改为列表显示形式 +5. 增加``MITM``、``DNS``和``Rule Custom``设置 +6. 修正配置可能丢失的问题 +7. 修复输入框遮挡问题 +8. 修正配置文件与wf部分区别 +9. 增加MITM开关 + +> iOS 10.3.3如果能进入脚本,请TG和我说一下 + +#### 2018.04.29 + +1. 不选Auto时,Auto则为DIRECT,生成后请手动选择Proxy +2. 保存的``托管``或``ss://``改为以名称显示(可能不能很好兼容上版本) +3. 增加倒序和全部添加到Auto功能 +3. 调整界面大小 +4. 修正无法识别托管文件名的问题 diff --git a/Rules-lhie1/version.fndroid b/Rules-lhie1/version.fndroid new file mode 100644 index 0000000..f6626a8 --- /dev/null +++ b/Rules-lhie1/version.fndroid @@ -0,0 +1 @@ +1.9.80 \ No newline at end of file diff --git a/ScriptGallery.js b/ScriptGallery.js new file mode 100644 index 0000000..a981f4b --- /dev/null +++ b/ScriptGallery.js @@ -0,0 +1,312 @@ +const socketLogger = require('socketLogger') +'init' in socketLogger && socketLogger.init('192.168.50.229') + +const screenWidth = $device.info.screen.width + +const isIphoneX = $device.isIphoneX + +const CN_MENU = ['最新', '精选', '文本', '工具', '开发者', '生活', '服务', '娱乐', '图片', 'Safari'] +const EN_MENU = ['New', 'Featured', 'Text', 'Utility', 'Developer', 'Life', 'Service', '4Fun', 'Image', 'Safari'] + +function render(statusHeight) { + $ui.render({ + props: { + id: "bodyView", + title: "", + statusBarStyle: 1, + navBarHidden: true + }, + views: [{ + type: "view", + props: { + bgcolor: $color("#343a40") + }, + layout: (make, view) => { + make.height.equalTo(view.super); + make.width.equalTo(view.super); + }, + views: [{ + type: "label", + props: { + text: "Loading...", + font: $font("ChalkboardSE-Light", 30), + color: $color("white"), + align: $align.center + }, + layout: $layout.center + }] + }, { + type: "menu", + props: { + id: "menuView", + alpha: 0, + items: CN_MENU, + bgcolor: $color("#fff") + }, + layout: (make, view) => { + make.bottom.equalTo(view.super).offset(-(isIphoneX ? 48 : 0)); + make.height.equalTo(44) + make.width.equalTo(view.super); + }, + events: { + changed: sender => { + let items = sender.items + let idx = sender.index + $("webView").eval({ + script: `$10('${items[idx]}')` + }) + } + } + }, { + type: "web", + props: { + alpha: 0, + id: "webView", + showsProgress: false, + bounces: false, + canGoForward: true, + url: "https://xteko.com/gallery", + script: () => { + window.onload = function () { + fetch("https://xteko.com/store/meta?lang=" + $0).then(function (n) { + return n.json() + }).then(function (n) { + $3 = n.result; + $5() + }).then(_ => { + addOnClickHandler() + }) + }; + + function $10(n) { + var t = $3.map(function (n) { + return n.category + }); + if (t.indexOf(n) === -1) { + $4 = null + } else { + $4 = n + } + $5() + addOnClickHandler() + } + + function addOnClickHandler() { + let parentCard = document.getElementsByClassName("content-table-mobile")[0]; + parentCard.addEventListener('click', e => { + let target = e.target + if (target.className === 'btn btn-primary addin-btn') { + $notify("hrefClick", target.href) + e.stopPropagation() + return false + } + }, true) + let cards = document.getElementsByClassName("card-body item-body"); + for (var i = 0; i < cards.length; ++i) { + var element = cards[i]; + element.addEventListener('click', e => { + var source = e.currentTarget + $notify("cardClick", source.innerHTML) + return false + }) + } + + let catMenu = document.getElementById("category-menu"); + catMenu.style.display = "none" + + } + + let navItems = document.getElementsByClassName("nav-item"); + navItems[0].parentNode.removeChild(navItems[0]) + navItems[0].parentNode.removeChild(navItems[0]) + + navItems[0].addEventListener('click', e => { + $notify("switchLng", e.target.href) + }) + + } + }, + layout: (make, view) => { + make.height.equalTo(view.super).offset(-statusHeight - 44 - (isIphoneX ? 48 : 0)); + make.width.equalTo(view.super) + make.top.equalTo(view.super).offset(statusHeight) + }, + events: { + hrefClick: async href => { + console.log('href', href); + let resp = await $http.get({ + url: href, + header: { + "USER-AGENT": "Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1" + } + }); + let urlMatcher = resp.data.match(/(jsbox:\/\/.*?)'/) + if (urlMatcher && urlMatcher[1]) { + $app.openURL(urlMatcher[1]); + } + }, + cardClick: async html => { + let titleMatcher = html.match(/addin-title">(.*?)<\/span/) + let descMatcher = html.match(/card-text addin-text">([\s\S]*?)<\/p>/) + let urlMatcher = html.match(/href="(.*?)"/) + if (titleMatcher && titleMatcher[1] && descMatcher && descMatcher[1] && urlMatcher && urlMatcher[1]) { + let shortenUrl = await $http.shorten(urlMatcher[1]) + showAlterDialog(titleMatcher[1], descMatcher[1], shortenUrl) + } + }, + didFinish: function (sender, navigation) { + $ui.animate({ + duration: 0.3, + animation: function () { + $("webView").alpha = 1 + $("menuView").alpha = 1 + $("emptyView").alpha = 1 + } + }); + }, + switchLng: href => { + $("menuView").items = /\/en$/.test(href) ? EN_MENU : CN_MENU + $("menuView").index = 0 + } + } + }, { + type: "view", + props: { + id: "emptyView", + bgcolor: $color("#eff0f2"), + hidden: !isIphoneX, + alpha: 0 + }, + layout: (make, view) => { + make.height.equalTo(48); + make.bottom.equalTo(0); + make.width.equalTo(view.super); + } + }] + }); +} + + +function showAlterDialog(title, content, url) { + let fontSize = $text.sizeThatFits({ + text: content, + width: screenWidth - 70, + font: $font(16) + }) + let view = { + type: "blur", + layout: $layout.fill, + props: { + id: "alertBody", + style: 1, + alpha: 0 + }, + views: [{ + type: "view", + props: { + bgcolor: $color("#343a40"), + smoothRadius: 8, + id: "detailShareView" + }, + layout: (make, view) => { + make.height.equalTo(170 + fontSize.height); + make.width.equalTo(view.super).offset(-30); + make.centerX.equalTo(view.super) + make.centerY.equalTo(view.super).offset(-25) + }, + events: { + tapped: sender => { } + }, + views: [{ + type: "label", + props: { + text: `${title}`, + textColor: $color("white"), + font: $font("bold", 20) + }, + layout: (make, view) => { + make.top.equalTo(view.super).offset(20); + make.centerX.equalTo(view.super) + } + }, { + type: "text", + props: { + text: content, + autoFontSize: true, + radius: 6, + font: $font(16), + bgcolor: $color("#fff"), + insets: $insets(10, 5, 10, 5), + editable: false, + }, + 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: 'image', + layout: (make, view) => { + make.top.equalTo(view.prev.bottom).offset(10); + make.size.equalTo($size(70, 70)); + make.left.equalTo(view.super).offset(10) + }, + props: { + smoothRadius: 5, + data: $qrcode.encode(url).png + } + }, { + type: 'label', + layout: (make, view) => { + make.top.equalTo(view.prev); + make.height.equalTo(70) + make.right.equalTo(view.super).offset(-10) + }, + props: { + text: "Powered by JSBox", + textColor: $color("#ccc"), + font: $font("Chalkduster", 18) + } + }] + }, { + type: "button", + props: { + icon: $icon("022", $color("#343a40"), $size(30, 30)), + bgcolor: $color("clear") + }, + layout: (make, view) => { + make.top.equalTo(view.prev.bottom).offset(20) + make.centerX.equalTo(view.super) + }, + events: { + tapped: sender => { + let snapShot = $("detailShareView").snapshot + $share.sheet([snapShot]); + } + } + }], + events: { + tapped: sender => { + sender.remove() + } + } + } + $("bodyView").add(view) + $ui.animate({ + duration: 0.2, + animation: () => { + $("alertBody").alpha = 1 + } + }) +} + +let statusRect = $objc("UIApplication").$sharedApplication().$statusBarFrame(); + +render(statusRect.height) diff --git a/Searcher/.output/Searcher.box b/Searcher/.output/Searcher.box new file mode 100644 index 0000000..649f1bf Binary files /dev/null and b/Searcher/.output/Searcher.box differ diff --git a/Searcher/README.md b/Searcher/README.md new file mode 100644 index 0000000..e69de29 diff --git a/Searcher/assets/icon.png b/Searcher/assets/icon.png new file mode 100644 index 0000000..c9685f4 Binary files /dev/null and b/Searcher/assets/icon.png differ diff --git a/Searcher/config.json b/Searcher/config.json new file mode 100644 index 0000000..e222b11 --- /dev/null +++ b/Searcher/config.json @@ -0,0 +1,23 @@ +{ + "info": { + "name": "", + "url": "", + "version": "1.0.0", + "author": "", + "website": "", + "types": 0 + }, + "settings": { + "minSDKVer": "1.0.0", + "minOSVer": "10.0.0", + "idleTimerDisabled": false, + "autoKeyboardEnabled": false, + "keyboardToolbarEnabled": false, + "rotateDisabled": false + }, + "widget": { + "height": 0, + "tintColor": "", + "iconColor": "" + } +} diff --git a/Searcher/main.js b/Searcher/main.js new file mode 100644 index 0000000..c9c9caa --- /dev/null +++ b/Searcher/main.js @@ -0,0 +1,3 @@ +var app = require('scripts/app'); + +app.renderMainUI() \ No newline at end of file diff --git a/Searcher/scripts/app.js b/Searcher/scripts/app.js new file mode 100644 index 0000000..5d89fc5 --- /dev/null +++ b/Searcher/scripts/app.js @@ -0,0 +1,30 @@ +function renderMainUI() { + $ui.render({ + props: { + title: "搜索" + }, + views: [{ + type: "view", + props: { + id: "mainView" + }, + layout: $layout.fill, + views: [{ + type: "input", + props: { + + }, + layout: (make, view) => { + make.height.equalTo(50); + make.width.equalTo(view.super).offset(-40); + make.centerY.equalTo(view.super).offset(-60); + make.centerX.equalTo(view.super); + } + }] + }] + }) +} + +module.exports = { + renderMainUI: renderMainUI +} \ No newline at end of file diff --git a/Searcher/strings/en.strings b/Searcher/strings/en.strings new file mode 100644 index 0000000..ae42947 --- /dev/null +++ b/Searcher/strings/en.strings @@ -0,0 +1,3 @@ +"OK" = "OK"; +"DONE" = "Done"; +"HELLO_WORLD" = "Hello, World!"; diff --git a/Searcher/strings/zh-Hans.strings b/Searcher/strings/zh-Hans.strings new file mode 100644 index 0000000..b2d0c5d --- /dev/null +++ b/Searcher/strings/zh-Hans.strings @@ -0,0 +1,3 @@ +"OK" = "好的"; +"DONE" = "完成"; +"HELLO_WORLD" = "你好,世界!"; diff --git a/URLShorten.js b/URLShorten.js new file mode 100644 index 0000000..f3f3b54 --- /dev/null +++ b/URLShorten.js @@ -0,0 +1,54 @@ +/* + +用途:识别剪贴板文本中所有链接,并将其转换为短链接 + +bypassDomain: +定义忽略转换的完整域名 + +*/ + + +async function main() { + let content = $clipboard.text + + let bypassDomain = ['t.me'] + + let urls = $detector.link(content).filter(url => { + return !bypassDomain.some(s => url.includes(`://${s}`)) + }) + + let pms = urls.map(url => { + return shorten(url) + }) + + let shorts = await Promise.all(pms) + + let result = urls.reduce((acc, cur, curIdx) => { + return acc.replace(cur, shorts[curIdx]) + }, content) + + $ui.alert({ + title: `共计识别出${urls.length}个链接`, + message: result, + actions: [{ + title: 'Copy', + handler: () => { + $clipboard.text = result + } + }, { + title: 'Cancel' + }] + }) +} + +await main() + +async function shorten(url) { + let short = url + try { + short = await $http.shorten(url) + } catch(e) { + + } + return short +} \ No newline at end of file diff --git a/har_resend.js b/har_resend.js new file mode 100644 index 0000000..ed96ece --- /dev/null +++ b/har_resend.js @@ -0,0 +1,106 @@ +let socketLogger = require("socketLogger") +typeof socketLogger.init === 'function' && socketLogger.init('192.168.50.229') +// SocketLogger Auto Generation Code + +console.clear() + +let importRequest = () => { + let data = $context.data + let success = $file.write({ + data: data, + path: "thor.json" + }); + $ui.alert(success ? '成功添加至脚本' : '添加到脚本失败,请重试') +} + +let handleRequest = async () => { + if (!$file.exists("thor.json")) { + $ui.alert('请先导入一个.har文件') + return + } + let infoJson = JSON.parse($file.read('thor.json').string) + let requestArr = infoJson.log.entries.sort((e1, e2) => { + return new Date(e1.startedDateTime).getTime() > new Date(e2.startedDateTime).getTime() + }) + + let resArr = [] + + for (const req of requestArr) { + let resp = await requestWithInof(req) + resArr.push(resp) + } + + if ($app.env === $env.siri) { + siriIntent(resArr[resArr.length -1].data); + } else { + $ui.alert({ + title: `共${resArr.length}个请求,最后响应体如下`, + message: resArr[resArr.length -1].data + }) + } +} + +let requestWithInof = async (req) => { + let requestInof = req.request + let httpOptions = { + method: requestInof.method, + url: requestInof.url, + header: genJsonHeaders(requestInof.headers) + } + if (requestInof.bodySize > 0) { + let contentType = requestInof.postData.mimeType + let bodyText = requestInof.postData.text + if (contentType === 'application/x-www-form-urlencoded') { + httpOptions.body = genJsonBody(bodyText) + } else if (contentType === 'application/json') { + httpOptions.body = JSON.parse(bodyText) + } else { + let text = `暂不支持请求体类型『${contentType}』` + $ui.alert(text) + siriIntent(text) + return + } + } + let resp = await $http.request(httpOptions) + return resp +} + +let genJsonHeaders = (arr) => { + let headers = {} + arr.forEach(e => { + if (e.name === 'Cookie') return + headers[e.name] = e.value + }); + return headers +} + +let genJsonBody = str => { + let body = {} + str.split('&').forEach(i => { + if (/^(.*?)=(.*)$/.test(i)) { + body[RegExp.$1] = RegExp.$2 + } + }) + return body +} + +let main = async () => { + let env = $app.env + if (env === $env.action) { + importRequest() + } else if (env === $env.siri || env === $env.app) { + await handleRequest() + } +} + +main().then(_ => {}) + +function siriIntent(text) { + $thread.main({ + delay: 0, + handler: function () { + $intents.finish(text); + } + }); +} + diff --git a/imgs/IMB_MCwoH4.GIF b/imgs/IMB_MCwoH4.GIF new file mode 100644 index 0000000..e9894d6 Binary files /dev/null and b/imgs/IMB_MCwoH4.GIF differ diff --git a/imgs/Rules-lhie1_pre1.jpg b/imgs/Rules-lhie1_pre1.jpg new file mode 100644 index 0000000..6532917 Binary files /dev/null and b/imgs/Rules-lhie1_pre1.jpg differ diff --git a/imgs/Rules-lhie1_pre2.jpg b/imgs/Rules-lhie1_pre2.jpg new file mode 100644 index 0000000..1caa598 Binary files /dev/null and b/imgs/Rules-lhie1_pre2.jpg differ diff --git a/imgs/Rules-lhie1_pre3.jpg b/imgs/Rules-lhie1_pre3.jpg new file mode 100644 index 0000000..25247b0 Binary files /dev/null and b/imgs/Rules-lhie1_pre3.jpg differ diff --git a/imgs/Rules-lhie1_pre4.jpg b/imgs/Rules-lhie1_pre4.jpg new file mode 100644 index 0000000..23d0872 Binary files /dev/null and b/imgs/Rules-lhie1_pre4.jpg differ diff --git a/imgs/Rules-lhie1_pre5.jpg b/imgs/Rules-lhie1_pre5.jpg new file mode 100644 index 0000000..f0fa6bf Binary files /dev/null and b/imgs/Rules-lhie1_pre5.jpg differ diff --git a/imgs/Rules-lhie1_pre6.png b/imgs/Rules-lhie1_pre6.png new file mode 100644 index 0000000..119d89e Binary files /dev/null and b/imgs/Rules-lhie1_pre6.png differ diff --git a/imgs/har_resend_pre1.jpg b/imgs/har_resend_pre1.jpg new file mode 100644 index 0000000..60dbee7 Binary files /dev/null and b/imgs/har_resend_pre1.jpg differ diff --git a/imgs/quanNOFA_pre1.jpg b/imgs/quanNOFA_pre1.jpg new file mode 100644 index 0000000..f1caf3e Binary files /dev/null and b/imgs/quanNOFA_pre1.jpg differ diff --git a/imgs/scriptGallery_pre1.png b/imgs/scriptGallery_pre1.png new file mode 100644 index 0000000..dace0b1 Binary files /dev/null and b/imgs/scriptGallery_pre1.png differ diff --git a/imgs/updateCheck_pre1.jpg b/imgs/updateCheck_pre1.jpg new file mode 100644 index 0000000..4384cd2 Binary files /dev/null and b/imgs/updateCheck_pre1.jpg differ diff --git a/imgs/wiki_rules_d1.png b/imgs/wiki_rules_d1.png new file mode 100644 index 0000000..61b5bcd Binary files /dev/null and b/imgs/wiki_rules_d1.png differ diff --git a/imgs/wiki_rules_d10.png b/imgs/wiki_rules_d10.png new file mode 100644 index 0000000..747b84e Binary files /dev/null and b/imgs/wiki_rules_d10.png differ diff --git a/imgs/wiki_rules_d11.png b/imgs/wiki_rules_d11.png new file mode 100644 index 0000000..1d08ab6 Binary files /dev/null and b/imgs/wiki_rules_d11.png differ diff --git a/imgs/wiki_rules_d2.png b/imgs/wiki_rules_d2.png new file mode 100644 index 0000000..8599dce Binary files /dev/null and b/imgs/wiki_rules_d2.png differ diff --git a/imgs/wiki_rules_d3.png b/imgs/wiki_rules_d3.png new file mode 100644 index 0000000..06b7e70 Binary files /dev/null and b/imgs/wiki_rules_d3.png differ diff --git a/imgs/wiki_rules_d4.png b/imgs/wiki_rules_d4.png new file mode 100644 index 0000000..3d13591 Binary files /dev/null and b/imgs/wiki_rules_d4.png differ diff --git a/imgs/wiki_rules_d5.png b/imgs/wiki_rules_d5.png new file mode 100644 index 0000000..703ff20 Binary files /dev/null and b/imgs/wiki_rules_d5.png differ diff --git a/imgs/wiki_rules_d6.png b/imgs/wiki_rules_d6.png new file mode 100644 index 0000000..0cdb2c2 Binary files /dev/null and b/imgs/wiki_rules_d6.png differ diff --git a/imgs/wiki_rules_d7.png b/imgs/wiki_rules_d7.png new file mode 100644 index 0000000..e93a505 Binary files /dev/null and b/imgs/wiki_rules_d7.png differ diff --git a/imgs/wiki_rules_d8.png b/imgs/wiki_rules_d8.png new file mode 100644 index 0000000..e9aaadb Binary files /dev/null and b/imgs/wiki_rules_d8.png differ diff --git a/imgs/wiki_rules_d9.png b/imgs/wiki_rules_d9.png new file mode 100644 index 0000000..45b5965 Binary files /dev/null and b/imgs/wiki_rules_d9.png differ diff --git a/ss2surge.js b/ss2surge.js index 8acbd0e..94437c5 100644 --- a/ss2surge.js +++ b/ss2surge.js @@ -7,6 +7,7 @@ function decodeScheme(url) { url += '#Unnamed' } tag = $text.URLDecode(url.match(/#(.*?)$/)[1]) + console.log(url.match(/#(.*?)$/)) if (url.includes('?')) { // tag = $text.URLDecode(url.match(/#(.*?)$/)[1]) let mdps = url.match(/ss:\/\/(.*?)@/)[1] @@ -17,13 +18,20 @@ function decodeScheme(url) { let userinfo = $text.base64Decode(mdps) method = userinfo.split(':')[0] password = userinfo.split(':')[1] + console.log([method, password]) let htpr = url.match(/@(.*?)\?/)[1].replace('\/', '') hostname = htpr.split(':')[0] port = htpr.split(':')[1] + console.log([hostname, port]) let ps = $text.URLDecode(url.match(/\?(.*?)#/)[1]) - let obfs = ps.match(/obfs=(.*?);/)[1] - let obfsHost = ps.match(/obfs-host=(.*?)$/)[1] - plugin = `obfs=${obfs}, obfs-host=${obfsHost}` + console.log(ps) + let obfsMatcher = ps.match(/obfs=(.*?)(;|$)/) + let obfsHostMatcher = ps.match(/obfs-host=(.*?)(;|$)/) + if (obfsMatcher) { + let obfs = obfsMatcher[1] + let obfsHost = obfsHostMatcher? obfsHostMatcher[1] : 'cloudfront.net' + plugin = `obfs=${obfs}, obfs-host=${obfsHost}` + } } else { let mdps = url.match(/ss:\/\/(.*?)#/)[1] let padding = 4 - mdps.length % 4 @@ -37,7 +45,7 @@ function decodeScheme(url) { proxy += `, ${plugin}` } return proxy - } catch { + } catch(e) { return null } } @@ -65,7 +73,7 @@ function showResult(result) { } $ui.menu({ - items: ['剪贴板读取链接', '扫描二维码', '相册选取二维码'], + items: ['剪贴板读取链接', '扫描二维码'], handler: function(title, idx) { if (idx == 0) { let result = decodeScheme($clipboard.text) @@ -73,20 +81,11 @@ $ui.menu({ } else if (idx == 1) { $qrcode.scan({ handler(string) { + console.log(string) let result = decodeScheme(string) showResult(result) } }) - }else { - $photo.pick({ - format: "image", - handler: function(resp) { - var image = resp.image - let result = decodeScheme($qrcode.decode(image)) - showResult(result) - - } - }) } } }) diff --git a/test.js b/test.js index d18c0e2..f7148ae 100644 --- a/test.js +++ b/test.js @@ -1 +1,16 @@ -$ui.toast("hello world") +async function test() { + return $http.get({ + url: "https://www.baidu.com?t=" + new Date().getTime() + }) +} + +async function main() { + let start = new Date().getTime() + let loop = 20 + for (var i = 0; i < loop; i++) { + await test() + } + console.log(`${loop}个请求共计耗时:${(new Date().getTime() - start) / 1000}s`) +} + +main().then(_ => { }) diff --git a/test/inject.js b/test/inject.js new file mode 100644 index 0000000..ae9c86b --- /dev/null +++ b/test/inject.js @@ -0,0 +1,5 @@ +window.onclick = e => { + let target = e.target; + let href = target.href; + $notify("hrefClick", href) + } diff --git a/test/ssr_demo b/test/ssr_demo index 4dda50b..c916af9 100644 --- a/test/ssr_demo +++ b/test/ssr_demo @@ -1 +1 @@ -c3NyOi8vZEhjMUxuaDJhV1JsYnk1alpqbzBORE02WVhWMGFGOWhaWE14TWpoZmJXUTFPbUZsY3kweU5UWXRZMlppT25Sc2N6RXVNbDkwYVdOclpYUmZZWFYwYURwaFJXUk1WVzA0ZVM4X2IySm1jM0JoY21GdFBWcHRVbXBOTWtreVRVUnJkV0pYYkdwamJUbDZZakphTUV4dFRuWmlVU1p3Y205MGIzQmhjbUZ0UFU1cVFUVlBhMW8wVW14c1NXVlJKbkpsYldGeWEzTTlWbXRzVVRWWkxYYzFjbTB0VGxORWJYVk1hbTFwU1Rodk5Wa3lRalZaUTA0MU5EWklTMU5CZEVsRVJYZE5SVE5zZFV0aWJISnlNR2RNVTBFd1RrUk5aelUyZFhZMVdTMXFOVmt5VmpVMmRYWTFXUzFxTldGVFlUVTFVMjgxYjJrekptZHliM1Z3UFZkUGJVRnVMVmMyY0djCnNzcjovL2MyYzFMbmgyYVdSbGJ5NWpaam8wTkRNNllYVjBhRjloWlhNeE1qaGZiV1ExT21GbGN5MHlOVFl0WTJaaU9uUnNjekV1TWw5MGFXTnJaWFJmWVhWMGFEcGhSV1JNVlcwNGVTOF9iMkptYzNCaGNtRnRQVnB0VW1wTk1ra3lUVVJyZFdKWGJHcGpiVGw2WWpKYU1FeHRUblppVVNad2NtOTBiM0JoY21GdFBVNXFRVFZQYTFvMFVteHNTV1ZSSm5KbGJXRnlhM005Vm10c1VUVndZWGMxV1hGbk5Wb3lhRTVUUkc5d05HSndiM0JJYkdvMGVtNXhTWFJFVkdwSloweFRRWGxOUkVKT05XSnBiVFZoTmpsSlF6Qm5Ua1JSZWtsUFpYSnlMVmRRYnkxWFRteGxaWEp5TFZkUWJ5MVhhMjExWlZWeFQyRkpkSGNtWjNKdmRYQTlWMDl0UVc0dFZ6Wndadwpzc3I6Ly9kbWt4TG5oMmFXUmxieTVqWmpvME5ETTZZWFYwYUY5aFpYTXhNamhmYldRMU9tRmxjeTB5TlRZdFkyWmlPblJzY3pFdU1sOTBhV05yWlhSZllYVjBhRHBoUldSTVZXMDRlUzhfYjJKbWMzQmhjbUZ0UFZwdFVtcE5Na2t5VFVScmRXSlhiR3BqYlRsNllqSmFNRXh0VG5aaVVTWndjbTkwYjNCaGNtRnRQVTVxUVRWUGExbzBVbXhzU1dWUkpuSmxiV0Z5YTNNOVZtdHNVVFUzZFRBMVRHMW1OVGR4ZWsxVFJHMTFUR3B0YVVsZmIzQTBZbkJ2Y0Voc2FqUjZibkZKZEVSVWFrbG5URk5CZUUxRVFrNDFZbWx0TldFMk9VbERNR2RPUkZGNlNVOWxjbkl0VjFCdkxWZE9iR1ZsY25JdFYxQnZMVmRyYlhWbFZYRlBZVWwwZHlabmNtOTFjRDFYVDIxQmJpMVhObkJuCnNzcjovL2RYTTNMbmgyYVdSbGJ5NWpaam8wTkRNNllYVjBhRjloWlhNeE1qaGZiV1ExT21GbGN5MHlOVFl0WTJaaU9uUnNjekV1TWw5MGFXTnJaWFJmWVhWMGFEcGhSV1JNVlcwNGVTOF9iMkptYzNCaGNtRnRQVnB0VW1wTk1ra3lUVVJyZFdKWGJHcGpiVGw2WWpKYU1FeHRUblppVVNad2NtOTBiM0JoY21GdFBVNXFRVFZQYTFvMFVteHNTV1ZSSm5KbGJXRnlhM005Vm10c1VUVTNOazgxV25VNVRubEViWFZNYW0xcFNWOXZjRFJpY0c5d1NHeHFOSHB1Y1VsMFJGUnFTV2RNVTBGNFRVUkNUalZpYVcwMVlUWTVTVU13WjA1RVVYcEpUMlZ5Y2kxWFVHOHRWMDVzWldWeWNpMVhVRzh0VjJ0dGRXVlZjVTloU1hSM0ptZHliM1Z3UFZkUGJVRnVMVmMyY0djCnNzcjovL2RXc3hMbmgyYVdSbGJ5NWpaam8wTkRNNllYVjBhRjloWlhNeE1qaGZiV1ExT21GbGN5MHlOVFl0WTJaaU9uUnNjekV1TWw5MGFXTnJaWFJmWVhWMGFEcGhSV1JNVlcwNGVTOF9iMkptYzNCaGNtRnRQVnB0VW1wTk1ra3lUVVJyZFdKWGJHcGpiVGw2WWpKYU1FeHRUblppVVNad2NtOTBiM0JoY21GdFBVNXFRVFZQYTFvMFVteHNTV1ZSSm5KbGJXRnlhM005Vm10c1VUWkpkWGcxV25VNVRWTkViM0EwWW5CdmNFVm5URk5CZUUxRVFrNDFZbWx0TldFMk9VbERNR2RPUkZGNlNVOWxjbkl0VjFCdkxWZE9iR1ZsY25JdFYxQnZMVmRyYlhWbFZYRlBZVWwwZHlabmNtOTFjRDFYVDIxQmJpMVhObkJuCnNzcjovL2FHc3lMbmgyYVdSbGJ5NWpaam8wTkRNNllYVjBhRjloWlhNeE1qaGZiV1ExT21GbGN5MHlOVFl0WTJaaU9uUnNjekV1TWw5MGFXTnJaWFJmWVhWMGFEcGhSV1JNVlcwNGVTOF9iMkptYzNCaGNtRnRQVnB0VW1wTk1ra3lUVVJyZFdKWGJHcGpiVGw2WWpKYU1FeHRUblppVVNad2NtOTBiM0JoY21GdFBVNXFRVFZQYTFvMFVteHNTV1ZSSm5KbGJXRnlhM005Vm10c1VUWmhZVm8xY21sMlRXbEViWFZNYW0xcFNWOXZjRFJpY0c5d1JXZE1VMEY2VFVVemJIVkxZbXh5Y2pCblRGTkJNRTVFVFdjMU5uVjJOVmt0YWpWWk1sWTFOblYyTlZrdGFqVmhVMkUxTlZOdk5XOXBNeVpuY205MWNEMVhUMjFCYmkxWE5uQm4Kc3NyOi8vYUdzMExuaDJhV1JsYnk1alpqbzBORE02WVhWMGFGOWhaWE14TWpoZmJXUTFPbUZsY3kweU5UWXRZMlppT25Sc2N6RXVNbDkwYVdOclpYUmZZWFYwYURwaFJXUk1WVzA0ZVM4X2IySm1jM0JoY21GdFBWcHRVbXBOTWtreVRVUnJkV0pYYkdwamJUbDZZakphTUV4dFRuWmlVU1p3Y205MGIzQmhjbUZ0UFU1cVFUVlBhMW8wVW14c1NXVlJKbkpsYldGeWEzTTlWbXRzVVRaaFlWbzFjbWwyVGtORWJYVk1hbTFwU1Y5dmNEUmljRzl3UmtsVE1WRm5URk5CZVUxRVFrNDFZbWx0TldFMk9VbERNR2RPUkZGNlNVOWxjbkl0VjFCdkxWZE9iR1ZsY25JdFYxQnZMVmRyYlhWbFZYRlBZVWwwZHlabmNtOTFjRDFYVDIxQmJpMVhObkJuCnNzcjovL2NuVXhMbmgyYVdSbGJ5NWpaam8wTkRNNllYVjBhRjloWlhNeE1qaGZiV1ExT21GbGN5MHlOVFl0WTJaaU9uUnNjekV1TWw5MGFXTnJaWFJmWVhWMGFEcGhSV1JNVlcwNGVTOF9iMkptYzNCaGNtRnRQVnB0VW1wTk1ra3lUVVJyZFdKWGJHcGpiVGw2WWpKYU1FeHRUblppVVNad2NtOTBiM0JoY21GdFBVNXFRVFZQYTFvMFVteHNTV1ZSSm5KbGJXRnlhM005TlZsWFRqWk1VelUxVEMxRk5UY3lXRFZ3WVhaTlUwUnNhR0Y2Ym0wMGIyZEpRekJuVFZSQmQxUmxWelJ3ZFZkMWRsTkJkRWxFVVRCTmVVUnVjVFpmYkdvMlVHeHFXbGh1Y1RaZmJHbzJVR3h3U25KdWJFdHFiV2xNWXlabmNtOTFjRDFYVDIxQmJpMVhObkJuCnNzcjovL2RYTXlMbmgyYVdSbGJ5NWpaam8wTkRNNllYVjBhRjloWlhNeE1qaGZiV1ExT21GbGN5MHlOVFl0WTJaaU9uUnNjekV1TWw5MGFXTnJaWFJmWVhWMGFEcGhSV1JNVlcwNGVTOF9iMkptYzNCaGNtRnRQVnB0VW1wTk1ra3lUVVJyZFdKWGJHcGpiVGw2WWpKYU1FeHRUblppVVNad2NtOTBiM0JoY21GdFBVNXFRVFZQYTFvMFVteHNTV1ZSSm5KbGJXRnlhM005TlZsWFRqWk1VelUxTnpaUE5WcDFPVTFwUkd4b1lYcHViVFJ2WjB4VFFYaE5SRUpPTldKcGJUVmhOamxKUXpCblRrUlJla2xQWlhKeUxWZFFieTFYVG14bFpYSnlMVmRRYnkxWGEyMTFaVlZ4VDJGSmRIY21aM0p2ZFhBOVYwOXRRVzR0Vnpad1p3Cg +dm1lc3M6Ly9VMkZ0Y0d4bExURWdQU0IyYldWemN5d2dNakF5TGpJd01pNHlNREl1TWpBeUxDQTBORE1zSUc1dmJtVXNJQ0ppTXpsaVlURmpOQzFoTURWaExUUTRNMlF0T1dJeE1TMHpOemt4Wm1GbU1EQTRPRElpTENCbmNtOTFjRDF3Y205MmFXUmxjaXdnYjNabGNpMTBiSE05ZEhKMVpTd2dkR3h6TFdodmMzUTlaWGhoYlhCc1pTNWpiMjBzSUdObGNuUnBabWxqWVhSbFBURXNJRzlpWm5NOWQzTXNJRzlpWm5NdGNHRjBhRDBpTDNkemNHRjBhQ0lzSUc5aVpuTXRhR1ZoWkdWeVBTSkliM04wT2lCbGVHRnRjR3hsTG1OdmJWdFNjbDFiVG01ZFZYTmxjaTFCWjJWdWREb2dWR1Z6ZENCQloyVnVkQ0k9 diff --git a/test/surge b/test/surge new file mode 100644 index 0000000..03fee4b --- /dev/null +++ b/test/surge @@ -0,0 +1,820 @@ +[Error] +网络连接失败 + +[General] +loglevel = notify +skip-proxy = 192.168.0.0/16, 193.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12, 100.64.0.0/10, 127.0.0.1, localhost, *.local +bypass-system = true +dns-server = 119.29.29.29,223.5.5.5,1.0.0.1 +allow-wifi-access = false +collapse-policy-group-items = true +enhanced-mode-by-rule = false +exclude-simple-hostnames = true +hide-crashlytics-request = true +ipv6 = true +replica = false + +[Proxy] + + +[Proxy Group] +Proxy = Direct + +[Host] +localhost = 127.0.0.1 +store.steampowered.com = 23.50.18.229 +autodraw.com = 216.239.38.21 +www.w3schools.com = 192.229.173.207 + +[Rule] +# REJECT Start + +// Tracking-Protection +DOMAIN-SUFFIX,google-analytics.com,REJECT +DOMAIN-SUFFIX,adjust.com,REJECT +DOMAIN-SUFFIX,appsflyer.com,REJECT +DOMAIN-SUFFIX,umengcloud.com,REJECT +DOMAIN-SUFFIX,umeng.com,REJECT +DOMAIN-SUFFIX,cnzz.com,REJECT +DOMAIN-SUFFIX,growingio.com,REJECT +DOMAIN-SUFFIX,inmobi.com,REJECT +DOMAIN-SUFFIX,inmobi.cn,REJECT +DOMAIN-SUFFIX,lnk0.com,REJECT +DOMAIN-SUFFIX,serving-sys.com,REJECT +DOMAIN,cdn.segment.com,REJECT +DOMAIN,data.flurry.com,REJECT +DOMAIN,static.jpush.cn,REJECT +DOMAIN,js.users.51.la,REJECT + +// Advertisement-Protection +DOMAIN-SUFFIX,googleadservices.com,REJECT +DOMAIN-SUFFIX,googlesyndication.com,REJECT +DOMAIN-SUFFIX,googleadsserving.cn,REJECT +DOMAIN-SUFFIX,admaster.com.cn,REJECT +DOMAIN-SUFFIX,adinall.com,REJECT +DOMAIN-SUFFIX,aerserv.com,REJECT +DOMAIN-SUFFIX,adview.cn,REJECT +DOMAIN-SUFFIX,cocounion.com,REJECT +DOMAIN-SUFFIX,ctrmi.com,REJECT +DOMAIN-SUFFIX,doubleclick.net,REJECT +DOMAIN-SUFFIX,irs01.com,REJECT +DOMAIN-SUFFIX,kejet.net,REJECT +DOMAIN-SUFFIX,miaozhen.com,REJECT +DOMAIN-SUFFIX,moatads.com,REJECT +DOMAIN-SUFFIX,mopub.com,REJECT +DOMAIN-SUFFIX,qchannel01.cn,REJECT +DOMAIN-SUFFIX,tanx.com,REJECT +DOMAIN-SUFFIX,tuanxue360.net,REJECT +DOMAIN,ad.12306.cn,REJECT +DOMAIN,adxserver.ad.cmvideo.cn,REJECT +DOMAIN,ads-twitter.com,REJECT +DOMAIN,adshow.58.com,REJECT +DOMAIN,ads.simplepath.com,REJECT +DOMAIN,ads.nexage.com,REJECT +DOMAIN,adplatform.vrtcal.com,REJECT +DOMAIN,ad.jiemian.com,REJECT +DOMAIN,ggs.myzaker.com,REJECT +DOMAIN,adshows.21cn.com,REJECT +DOMAIN,ad-cn.jovcloud.com,REJECT +DOMAIN,sugar.zhihu.com,REJECT + +// Baidu +DOMAIN-SUFFIX,baidustatic.com,REJECT +DOMAIN,hm.baidu.com,REJECT +DOMAIN,hmma.baidu.com,REJECT +DOMAIN,mobads.baidu.com,REJECT +DOMAIN,mobads-logs.baidu.com,REJECT +DOMAIN,wn.pos.baidu.com,REJECT +// -Baidu_high-acc-ip +DOMAIN,ps.map.baidu.com,REJECT +DOMAIN,sv.map.baidu.com,REJECT +DOMAIN,offnavi.map.baidu.com,REJECT +DOMAIN,newvector.map.baidu.com,REJECT +DOMAIN,ulog.imap.baidu.com,REJECT +DOMAIN,newloc.map.n.shifen.com,REJECT + +// Ali +DOMAIN,adash-c.ut.taobao.com,REJECT +DOMAIN,adashxgc.ut.taobao.com,REJECT +DOMAIN,adashbc.ut.taobao.com,REJECT +DOMAIN,adash.man.aliyuncs.com,REJECT +DOMAIN,apoll.m.taobao.com,REJECT +DOMAIN,amdc.m.taobao.com,REJECT + +// Tencent +DOMAIN,pingma.qq.com,REJECT +DOMAIN,mi.gdt.qq.com,REJECT +// -TencentVideo +DOMAIN-SUFFIX,l.qq.com,REJECT +DOMAIN,mtrace.qq.com,REJECT + +// 163 +DOMAIN-SUFFIX,analytics.126.net,REJECT +DOMAIN,iadmat.nosdn.127.net,REJECT +DOMAIN,static.ws.126.net,REJECT + +// AutoNavi +DOMAIN,nbsdk-baichuan.alicdn.com,REJECT + +// iQiyi +DOMAIN-SUFFIX,cupid.iqiyi.com,REJECT +DOMAIN,ifacelog.iqiyi.com,REJECT +DOMAIN,mbdlog.iqiyi.com,REJECT +DOMAIN,msg.71.am,REJECT +IP-CIDR,101.227.97.240/32,REJECT,no-resolve +IP-CIDR,101.227.200.28/32,REJECT,no-resolve +IP-CIDR,124.192.153.42/32,REJECT,no-resolve + +// Youku +DOMAIN-SUFFIX,atm.youku.com,REJECT +DOMAIN,ad.mobile.youku.com,REJECT +DOMAIN,iyes.youku.com,REJECT + +// Sohu +DOMAIN-SUFFIX,ads.sohu.com,REJECT +// -SohuVideo +DOMAIN-SUFFIX,aty.sohu.com,REJECT +DOMAIN,imp.optaim.com,REJECT +DOMAIN,v2.reachmax.cn,REJECT +DOMAIN,data.vod.itc.cn,REJECT + +// Hunan_TV +DOMAIN-SUFFIX,da.mgtv.com,REJECT +DOMAIN-SUFFIX,da.hunantv.com,REJECT +DOMAIN-SUFFIX,log.hunantv.com,REJECT +DOMAIN,log.v2.hunantv.com,REJECT +DOMAIN,v2.log.hunantv.com,REJECT + +// PPTV +DOMAIN,asimgs.pplive.cn,REJECT + +// Le +DOMAIN-SUFFIX,webp2p.letv.com,REJECT +DOMAIN,ark.letv.com,REJECT +DOMAIN,g3.letv.com,REJECT +DOMAIN,n.mark.letv.com,REJECT + +// XiMaLaYa +DOMAIN,ad.ximalaya.com,REJECT +DOMAIN,adse.ximalaya.com,REJECT + +// Moji +DOMAIN,ad.api.moji.com,REJECT +DOMAIN,v1.log.moji.com,REJECT + +// Kugou and Kuwo +DOMAIN,adserviceretry.kugou.com,REJECT +DOMAIN,ads.service.kugou.com,REJECT +DOMAIN,adsfile.bssdlbig.kugou.com,REJECT +DOMAIN,log.stat.kugou.com,REJECT +DOMAIN,log.web.kugou.com,REJECT +DOMAIN,kgmobilestat.kugou.com,REJECT +DOMAIN,kgmobilestatbak.kugou.com,REJECT +DOMAIN,mobilelog.kugou.com,REJECT +DOMAIN,mobilead.kuwo.cn,REJECT +DOMAIN,rich.kuwo.cn,REJECT + +// Internet Service Provider Hijack +DOMAIN-SUFFIX,cszlks.com,REJECT +DOMAIN-SUFFIX,freedrive.cn,REJECT +DOMAIN-SUFFIX,fkku194.com,REJECT +DOMAIN-SUFFIX,fjlqqc.com,REJECT +DOMAIN-SUFFIX,kumihua.com,REJECT +DOMAIN-SUFFIX,mlnbike.com,REJECT +DOMAIN-SUFFIX,quanliyouxi.cn,REJECT +DOMAIN-SUFFIX,feih.com.cn,REJECT +DOMAIN-SUFFIX,51chumoping.com,REJECT +IP-CIDR,47.89.59.182/32,REJECT,no-resolve +// -ChinaNet +IP-CIDR,61.160.200.223/32,REJECT,no-resolve +IP-CIDR,61.160.200.242/32,REJECT,no-resolve +IP-CIDR,61.160.200.252/32,REJECT,no-resolve +IP-CIDR,111.175.220.164/32,REJECT,no-resolve +IP-CIDR,124.232.160.178/32,REJECT,no-resolve +IP-CIDR,175.6.223.15/32,REJECT,no-resolve +IP-CIDR,183.59.53.237/32,REJECT,no-resolve +IP-CIDR,218.93.127.37/32,REJECT,no-resolve +IP-CIDR,221.231.6.79/32,REJECT,no-resolve +IP-CIDR,222.186.61.91/32,REJECT,no-resolve +IP-CIDR,222.186.61.95/32,REJECT,no-resolve +IP-CIDR,222.186.61.96/32,REJECT,no-resolve +// -ChinaUnicom +// -Dr.Peng +IP-CIDR,10.72.25.13/32,REJECT,no-resolve +IP-CIDR,115.182.16.79/32,REJECT,no-resolve +IP-CIDR,118.144.88.126/32,REJECT,no-resolve +IP-CIDR,118.144.88.215/32,REJECT,no-resolve +IP-CIDR,124.14.21.147/32,REJECT,no-resolve +IP-CIDR,124.14.21.151/32,REJECT,no-resolve +IP-CIDR,180.166.52.24/32,REJECT,no-resolve +IP-CIDR,220.115.251.25/32,REJECT,no-resolve +IP-CIDR,222.73.156.235/32,REJECT,no-resolve + +// Blacklist +DOMAIN-SUFFIX,makeding.com,REJECT +DOMAIN-SUFFIX,mairuan.com,REJECT + +# REJECT End + +# Proxy Start + +// Apple +DOMAIN-SUFFIX,appsto.re,Proxy +DOMAIN-SUFFIX,me.com,Proxy +DOMAIN,s.mzstatic.com,Proxy +DOMAIN,gspe1-ssl.ls.apple.com,Proxy,force-remote-dns +DOMAIN,news-events.apple.com,Proxy,force-remote-dns +DOMAIN,news-client.apple.com,Proxy,force-remote-dns +DOMAIN,lookup-api.apple.com,Proxy +DOMAIN,api-glb-sea.smoot.apple.com,Proxy +DOMAIN,books.itunes.apple.com,Proxy +DOMAIN,hls.itunes.apple.com,Proxy + +// Google +DOMAIN-SUFFIX,1e100.net,Proxy,force-remote-dns +DOMAIN-SUFFIX,abc.xyz,Proxy,force-remote-dns +DOMAIN-SUFFIX,admob.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,agoogleaday.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,ampproject.org,Proxy,force-remote-dns +DOMAIN-SUFFIX,ampproject.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,ampproject.net,Proxy,force-remote-dns +DOMAIN-SUFFIX,android.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,androidify.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,appspot.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,blogger.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,blogblog.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,certificate-transparency.org,Proxy,force-remote-dns +DOMAIN-SUFFIX,chrome.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,chromecast.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,chromeexperiments.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,chromercise.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,chromestatus.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,chromium.org,Proxy,force-remote-dns +DOMAIN-SUFFIX,com.google,Proxy,force-remote-dns +DOMAIN-SUFFIX,data-vocabulary.org,Proxy,force-remote-dns +DOMAIN-SUFFIX,deja.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,digisfera.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,domains.google,Proxy,force-remote-dns +DOMAIN-SUFFIX,feedburner.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,g.co,Proxy,force-remote-dns +DOMAIN-SUFFIX,get.how,Proxy,force-remote-dns +DOMAIN-SUFFIX,getmdl.io,Proxy,force-remote-dns +DOMAIN-SUFFIX,ggpht.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,gmail.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,gmodules.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,goo.gl,Proxy,force-remote-dns +DOMAIN-SUFFIX,google.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,googleapis.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,googleartproject.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,googleblog.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,googlecode.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,googlecommerce.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,googledomains.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,googleearth.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,googledrive.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,googlegroups.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,googlehosted.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,googleideas.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,googlelabs.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,googlemail.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,googleplay.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,googleplus.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,googlesource.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,googlesyndication.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,googletagmanager.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,googletagservices.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,googleusercontent.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,googlevideo.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,googlezip.net,Proxy,force-remote-dns +DOMAIN-SUFFIX,gstatic.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,gstatic.cn,Proxy,force-remote-dns +DOMAIN-SUFFIX,gvt0.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,gvt1.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,blogspot.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,html5rocks.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,iam.soy,Proxy,force-remote-dns +DOMAIN-SUFFIX,igoogle.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,like.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,nic.google,Proxy,force-remote-dns +DOMAIN-SUFFIX,panoramio.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,picasaweb.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,polymer-project.org,Proxy,force-remote-dns +DOMAIN-SUFFIX,thinkwithgoogle.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,tiltbrush.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,recaptcha.net,Proxy,force-remote-dns +DOMAIN-SUFFIX,registry.google,Proxy,force-remote-dns +DOMAIN-SUFFIX,tensorflow.org,Proxy,force-remote-dns +DOMAIN-SUFFIX,webmproject.org,Proxy,force-remote-dns +DOMAIN-SUFFIX,whatbrowser.org,Proxy,force-remote-dns +DOMAIN-SUFFIX,withgoogle.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,youtu.be,Proxy,force-remote-dns +DOMAIN-SUFFIX,youtube.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,youtube-nocookie.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,ytimg.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,zynamics.com,Proxy,force-remote-dns + +// A +DOMAIN-SUFFIX,amazon.com,Proxy +DOMAIN-SUFFIX,amazonaws.com,Proxy +DOMAIN-SUFFIX,archive.org,Proxy +DOMAIN-SUFFIX,archive.is,Proxy +DOMAIN-SUFFIX,archive.li,Proxy +DOMAIN-SUFFIX,archives.gov,Proxy +DOMAIN-SUFFIX,appdownloader.net,Proxy +DOMAIN-SUFFIX,apk-dl.com,Proxy +DOMAIN-SUFFIX,apkfind.com,Proxy +DOMAIN-SUFFIX,apkpure.com,Proxy +DOMAIN-SUFFIX,apigee.com,Proxy +DOMAIN-SUFFIX,aol.com,Proxy +DOMAIN-SUFFIX,aolcdn.com,Proxy +DOMAIN-SUFFIX,anthonycalzadilla.com,Proxy +DOMAIN-SUFFIX,android-x86.org,Proxy +DOMAIN-SUFFIX,ancsconf.org,Proxy +DOMAIN-SUFFIX,allconnected.co,Proxy +DOMAIN-SUFFIX,apkleecher.com,Proxy +DOMAIN-SUFFIX,appsonplaystore.com,Proxy +DOMAIN-SUFFIX,awsstatic.com,Proxy +DOMAIN-SUFFIX,async.be,Proxy +DOMAIN-SUFFIX,appshopper.com,Proxy +DOMAIN-SUFFIX,armorgames.com,Proxy +DOMAIN-SUFFIX,avanquest.com,Proxy +DOMAIN-SUFFIX,adaymag.com,Proxy +DOMAIN-SUFFIX,app-measurement.com,Proxy +DOMAIN-SUFFIX,akamaiedge.net,Proxy + +// B +DOMAIN-SUFFIX,bbc.com,Proxy +DOMAIN-SUFFIX,books.com.tw,Proxy +DOMAIN-SUFFIX,bloomberg.com,Proxy +DOMAIN-SUFFIX,bloglovin.com,Proxy +DOMAIN-SUFFIX,bitshare.com,Proxy +DOMAIN-SUFFIX,bitcointalk.org,Proxy +DOMAIN-SUFFIX,bit.do,Proxy +DOMAIN-SUFFIX,bit.ly,Proxy +DOMAIN-SUFFIX,bigsound.org,Proxy +DOMAIN-SUFFIX,bbtoystore.com,Proxy +DOMAIN-SUFFIX,boxun.com,Proxy +DOMAIN-SUFFIX,bandwagonhost.com,Proxy +DOMAIN-SUFFIX,bitvise.com,Proxy +DOMAIN-SUFFIX,blessing.studio,Proxy +DOMAIN-SUFFIX,btso.pw,Proxy +DOMAIN-SUFFIX,binance.com,Proxy +DOMAIN-SUFFIX,binance.cloud,Proxy +DOMAIN-SUFFIX,bitstamp.net,Proxy +DOMAIN-SUFFIX,beechat.io,Proxy +DOMAIN-SUFFIX,bing.com,Proxy +DOMAIN-SUFFIX,businessweek.com,Proxy +DOMAIN-SUFFIX,bitfinex.com,Proxy +DOMAIN-SUFFIX,bibox.com,Proxy +DOMAIN-SUFFIX,bitmex.com,Proxy +DOMAIN-SUFFIX,box.com,Proxy + +// C +DOMAIN-SUFFIX,cnn.com,Proxy +DOMAIN-SUFFIX,cdninstagram.com,Proxy +DOMAIN-SUFFIX,cbc.ca,Proxy +DOMAIN-SUFFIX,census.gov,Proxy +DOMAIN-SUFFIX,cloudfront.net,Proxy +DOMAIN-SUFFIX,cn-proxy.com,Proxy +DOMAIN-SUFFIX,cccat.cc,Proxy +DOMAIN-SUFFIX,codepen.io,Proxy +DOMAIN-SUFFIX,cbsistatic.com,Proxy +DOMAIN-SUFFIX,coinegg.com,Proxy +DOMAIN-SUFFIX,css.network,Proxy +DOMAIN-SUFFIX,cradio.live,Proxy + +// D +DOMAIN-SUFFIX,dw.com,Proxy +DOMAIN-SUFFIX,duckduckgo.com,Proxy +DOMAIN-SUFFIX,dropbox.com,Proxy +DOMAIN-SUFFIX,dropboxstatic.com,Proxy +DOMAIN-SUFFIX,dropboxusercontent.com,Proxy +DOMAIN-SUFFIX,disconnect.me,Proxy +DOMAIN-SUFFIX,dcmilitary.com,Proxy +DOMAIN-SUFFIX,digitaltrends.com,Proxy +DOMAIN-SUFFIX,daolan.net,Proxy +DOMAIN-SUFFIX,dol.gov,Proxy +DOMAIN-SUFFIX,disqus.com,Proxy +DOMAIN-SUFFIX,discuss.com.hk,Proxy +DOMAIN-SUFFIX,discord.gg,Proxy +DOMAIN-SUFFIX,discordapp.com,Proxy +DOMAIN-SUFFIX,discordapp.net,Proxy +DOMAIN-SUFFIX,doub.io,Proxy +DOMAIN-SUFFIX,dnvod.tv,Proxy +DOMAIN-SUFFIX,dailymotion.com,Proxy +DOMAIN-SUFFIX,dwnews.com,Proxy +DOMAIN-SUFFIX,dwnews.net,Proxy +DOMAIN-SUFFIX,devmate.com,Proxy + +// E +DOMAIN-SUFFIX,extmatrix.com,Proxy +DOMAIN-SUFFIX,easybib.com,Proxy +DOMAIN-SUFFIX,easybib.com,Proxy +DOMAIN-SUFFIX,economist.com,Proxy +DOMAIN-SUFFIX,edgecastcdn.net,Proxy +DOMAIN-SUFFIX,everhelper.me,Proxy +DOMAIN-SUFFIX,envato-static.com,Proxy + +// F +DOMAIN-SUFFIX,facebook.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,facebook.net,Proxy,force-remote-dns +DOMAIN-SUFFIX,fbcdn.net,Proxy,force-remote-dns +DOMAIN-SUFFIX,fb.com,Proxy +DOMAIN-SUFFIX,fb.me,Proxy +DOMAIN-SUFFIX,freeopenProxy.com,Proxy +DOMAIN-SUFFIX,fzlm.net,Proxy +DOMAIN-SUFFIX,flitto.com,Proxy +DOMAIN-SUFFIX,flipkart.com,Proxy +DOMAIN-SUFFIX,flickr.com,Proxy +DOMAIN-SUFFIX,staticflickr.com,Proxy +DOMAIN-SUFFIX,fdlstatic.com,Proxy +DOMAIN-SUFFIX,fastly.net,Proxy +DOMAIN-SUFFIX,flumeapp.com,Proxy +DOMAIN-SUFFIX,free-proxy.cz,Proxy +DOMAIN-SUFFIX,fc2.com,Proxy +DOMAIN-SUFFIX,firebaseio.com,Proxy +DOMAIN-SUFFIX,flightradar24.com,Proxy + +// G +DOMAIN-SUFFIX,getcloudapp.com,Proxy +DOMAIN-SUFFIX,gunsamerica.com,Proxy +DOMAIN-SUFFIX,gravatar.com,Proxy +DOMAIN-SUFFIX,getlantern.org,Proxy +DOMAIN-SUFFIX,getfoxyProxy.org,Proxy +DOMAIN-SUFFIX,go.com,Proxy +DOMAIN-SUFFIX,go.jp,Proxy +DOMAIN-SUFFIX,gfw.press,Proxy +DOMAIN-SUFFIX,getdropbox.com,Proxy +DOMAIN-SUFFIX,github.io,Proxy +DOMAIN-SUFFIX,gitbooks.io,Proxy +DOMAIN-SUFFIX,greatfire.org,Proxy +DOMAIN-SUFFIX,getsync.com,Proxy +DOMAIN-SUFFIX,gumroad.com,Proxy +DOMAIN-SUFFIX,gdax.com,Proxy +DOMAIN-SUFFIX,gate.io,Proxy +DOMAIN-SUFFIX,gracecode.com,Proxy +DOMAIN,gist.github.com,Proxy + +// H +DOMAIN-SUFFIX,howtoforge.com,Proxy +DOMAIN-SUFFIX,hootsuite.com,Proxy +DOMAIN-SUFFIX,homedepot.com,Proxy +DOMAIN-SUFFIX,hulu.com,Proxy +DOMAIN-SUFFIX,hk01.com,Proxy +DOMAIN-SUFFIX,huobi.com,Proxy +DOMAIN-SUFFIX,huobi.pro,Proxy +DOMAIN-SUFFIX,huobipro.com,Proxy +DOMAIN-SUFFIX,hitbtc.com,Proxy + +// I +DOMAIN-SUFFIX,instagram.com,Proxy +DOMAIN-SUFFIX,icoco.com,Proxy +DOMAIN-SUFFIX,imgur.com,Proxy +DOMAIN-SUFFIX,instructables.com,Proxy +DOMAIN-SUFFIX,ift.tt,Proxy +DOMAIN-SUFFIX,is.gd,Proxy +DOMAIN-SUFFIX,imgbus.com,Proxy +DOMAIN-SUFFIX,i-scmp.com,Proxy +DOMAIN-SUFFIX,iask.ca,Proxy +DOMAIN-SUFFIX,issuu.com,Proxy +DOMAIN-SUFFIX,ixquick.com,Proxy +DOMAIN-SUFFIX,init.shop,Proxy +DOMAIN-SUFFIX,initiummall.com,Proxy +DOMAIN-SUFFIX,ipfs.io,Proxy + +// J +DOMAIN-SUFFIX,jwpcdn.com,Proxy +DOMAIN-SUFFIX,jwpltx.com,Proxy + +// K +DOMAIN-SUFFIX,kenengba.com,Proxy +DOMAIN-SUFFIX,keepvid.com,Proxy +DOMAIN-SUFFIX,kobo.com,Proxy +DOMAIN-SUFFIX,kucoin.com,Proxy + +// L +DOMAIN-SUFFIX,line.me,Proxy +DOMAIN-SUFFIX,line-apps.com,Proxy +DOMAIN-SUFFIX,line-cdn.net,Proxy +DOMAIN-SUFFIX,line-scdn.net,Proxy +DOMAIN-SUFFIX,logmein.com,Proxy +DOMAIN-SUFFIX,lvv2.com,Proxy +DOMAIN-SUFFIX,live.com,Proxy +DOMAIN-SUFFIX,linkcoin.pro,Proxy + +// M +DOMAIN-SUFFIX,mp3buscador.com,Proxy +DOMAIN-SUFFIX,medium.com,Proxy +DOMAIN-SUFFIX,mlssoccer.com,Proxy +DOMAIN-SUFFIX,marketwatch.com,Proxy +DOMAIN-SUFFIX,nih.gov,Proxy +DOMAIN-SUFFIX,mycnnews.com,Proxy +DOMAIN-SUFFIX,maplestage.com,Proxy +DOMAIN-SUFFIX,mozilla.net,Proxy +DOMAIN-SUFFIX,mobypicture.com,Proxy +DOMAIN-SUFFIX,msn.com,Proxy + +// N +DOMAIN-SUFFIX,nytimes.com,Proxy +DOMAIN-SUFFIX,nytimg.com,Proxy +DOMAIN-SUFFIX,nyti.ms,Proxy +DOMAIN-SUFFIX,nytstyle.com,Proxy +DOMAIN-SUFFIX,nyt.com,Proxy +DOMAIN-SUFFIX,nrk.no,Proxy +DOMAIN-SUFFIX,newipnow.com,Proxy +DOMAIN-SUFFIX,ndr.de,Proxy +DOMAIN-SUFFIX,nasa.gov,Proxy +DOMAIN-SUFFIX,netflix.com,Proxy +DOMAIN-SUFFIX,netflix.net,Proxy +DOMAIN-SUFFIX,nflxext.com,Proxy +DOMAIN-SUFFIX,nflximg.com,Proxy +DOMAIN-SUFFIX,nflximg.net,Proxy +DOMAIN-SUFFIX,nflxvideo.net,Proxy +DOMAIN-SUFFIX,nflxso.net,Proxy +DOMAIN-SUFFIX,nintendo.com,Proxy +DOMAIN-SUFFIX,nssurge.com,Proxy +DOMAIN-SUFFIX,neverware.com,Proxy +DOMAIN-SUFFIX,ngrok.cc,Proxy +DOMAIN-SUFFIX,naver.jp,Proxy + +// O +DOMAIN-SUFFIX,onlineyoutube.com,Proxy +DOMAIN-SUFFIX,osha.gov,Proxy +DOMAIN-SUFFIX,optimizely.com,Proxy +DOMAIN-SUFFIX,owncloud.org,Proxy +DOMAIN-SUFFIX,ow.ly,Proxy +DOMAIN-SUFFIX,oup.com,Proxy +DOMAIN-SUFFIX,ok.ru,Proxy +DOMAIN-SUFFIX,oloadcdn.net,Proxy +DOMAIN-SUFFIX,okex.com,Proxy + +// P +DOMAIN-SUFFIX,psiphon3.com,Proxy +DOMAIN-SUFFIX,puffinbrowser.com,Proxy +DOMAIN-SUFFIX,pubu.com.tw,Proxy +DOMAIN-SUFFIX,proxfree.com,Proxy +DOMAIN-SUFFIX,popo.tw,Proxy +DOMAIN-SUFFIX,pokemon.com,Proxy +DOMAIN-SUFFIX,pastebin.com,Proxy +DOMAIN-SUFFIX,pandora.com,Proxy +DOMAIN-SUFFIX,pinterest.com,Proxy +DOMAIN-SUFFIX,pinimg.com,Proxy +DOMAIN-SUFFIX,pandora.com,Proxy +DOMAIN-SUFFIX,potatso.com,Proxy +DOMAIN-SUFFIX,pscp.tv,Proxy +DOMAIN-SUFFIX,putty.org,Proxy +DOMAIN-SUFFIX,pixiv.net,Proxy +DOMAIN-SUFFIX,pixiv.org,Proxy +DOMAIN-SUFFIX,pixnet.net,Proxy +DOMAIN-SUFFIX,pentoy.hk,Proxy +DOMAIN-SUFFIX,psu.edu,Proxy +DOMAIN-SUFFIX,poloniex.com,Proxy + +// R +DOMAIN-SUFFIX,rsf.org,Proxy +DOMAIN-SUFFIX,rileyguide.com,Proxy +DOMAIN-SUFFIX,rfi.fr,Proxy +DOMAIN-SUFFIX,reuters.com,Proxy +DOMAIN-SUFFIX,readmoo.com,Proxy +DOMAIN-SUFFIX,readingtimes.com.tw,Proxy +DOMAIN-SUFFIX,resilio.com,Proxy +DOMAIN-SUFFIX,rawgit.com,Proxy +DOMAIN-SUFFIX,rackcdn.com,Proxy +DOMAIN-SUFFIX,rthk.hk,Proxy + +// S +DOMAIN-SUFFIX,scribd.com,Proxy +DOMAIN-SUFFIX,sydneytoday.com,Proxy +DOMAIN-SUFFIX,surrenderat20.net,Proxy +DOMAIN-SUFFIX,surfeasy.com.au,Proxy +DOMAIN-SUFFIX,sugarsync.com,Proxy +DOMAIN-SUFFIX,stumbleupon.com,Proxy +DOMAIN-SUFFIX,storify.com,Proxy +DOMAIN-SUFFIX,startpage.com,Proxy +DOMAIN-SUFFIX,starp2p.com,Proxy +DOMAIN-SUFFIX,state.gov,Proxy +DOMAIN-SUFFIX,spike.com,Proxy +DOMAIN-SUFFIX,sowers.org.hk,Proxy +DOMAIN-SUFFIX,soundcloud.com,Proxy +DOMAIN-SUFFIX,sockslist.net,Proxy +DOMAIN-SUFFIX,snapchat.com,Proxy +DOMAIN-SUFFIX,smh.com.au,Proxy +DOMAIN-SUFFIX,slideshare.net,Proxy +DOMAIN-SUFFIX,skype.com,Proxy +DOMAIN-SUFFIX,sketchappsources.com,Proxy +DOMAIN-SUFFIX,sidelinesnews.com,Proxy +DOMAIN-SUFFIX,shadowsocks.org,Proxy +DOMAIN-SUFFIX,search.com,Proxy +DOMAIN-SUFFIX,sciencemag.org,Proxy +DOMAIN-SUFFIX,ssa.gov,Proxy +DOMAIN-SUFFIX,shutterstock.com,Proxy +DOMAIN-SUFFIX,sciencedaily.com,Proxy +DOMAIN-SUFFIX,signalsitemap.com,Proxy +DOMAIN-SUFFIX,surge.run,Proxy +DOMAIN-SUFFIX,swtch.com,Proxy +DOMAIN-SUFFIX,scmp.com,Proxy +DOMAIN-SUFFIX,stel.com,Proxy +DOMAIN-SUFFIX,steamcommunity.com,Proxy +DOMAIN-SUFFIX,slack.com,Proxy +DOMAIN-SUFFIX,slack-edge.com,Proxy +DOMAIN-SUFFIX,slack-msgs.com,Proxy +DOMAIN-SUFFIX,sndcdn.com,Proxy + +// T +DOMAIN-SUFFIX,twtkr.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,twimg.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,twitthat.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,twitterrific.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,twittercounter.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,twittergadget.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,twitterfeed.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,twitter4j.org,Proxy,force-remote-dns +DOMAIN-SUFFIX,twttr.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,twitter.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,t.co,Proxy,force-remote-dns +DOMAIN-SUFFIX,tv.com,Proxy +DOMAIN-SUFFIX,tumblr.com,Proxy +DOMAIN-SUFFIX,tmblr.co,Proxy +DOMAIN-SUFFIX,txmblr.com,Proxy +DOMAIN-SUFFIX,turbobit.net,Proxy +DOMAIN-SUFFIX,tt-rss.org,Proxy +DOMAIN-SUFFIX,trulyergonomic.com,Proxy +DOMAIN-SUFFIX,trendsmap.com,Proxy +DOMAIN-SUFFIX,transparency.org,Proxy +DOMAIN-SUFFIX,traffichaus.com,Proxy +DOMAIN-SUFFIX,torrentz.eu,Proxy +DOMAIN-SUFFIX,torrentproject.se,Proxy +DOMAIN-SUFFIX,torrentprivacy.com,Proxy +DOMAIN-SUFFIX,torproject.org,Proxy +DOMAIN-SUFFIX,torcn.com,Proxy +DOMAIN-SUFFIX,tokyocn.com,Proxy +DOMAIN-SUFFIX,togetter.com,Proxy +DOMAIN-SUFFIX,tinychat.com,Proxy +DOMAIN-SUFFIX,tiny.cc,Proxy +DOMAIN-SUFFIX,time.com,Proxy +DOMAIN-SUFFIX,thewgo.org,Proxy +DOMAIN-SUFFIX,thepiratebay.org,Proxy +DOMAIN-SUFFIX,thebobs.com,Proxy +DOMAIN-SUFFIX,telegram.org,Proxy +DOMAIN-SUFFIX,telegram.me,Proxy +DOMAIN-SUFFIX,telegram.show,Proxy +DOMAIN-SUFFIX,technorati.com,Proxy +DOMAIN-SUFFIX,talkboxapp.com,Proxy +DOMAIN-SUFFIX,talkonly.net,Proxy +DOMAIN-SUFFIX,talk853.com,Proxy +DOMAIN-SUFFIX,tabtter.jp,Proxy +DOMAIN-SUFFIX,tablesgenerator.com,Proxy +DOMAIN-SUFFIX,tomshardware.com,Proxy +DOMAIN-SUFFIX,theverge.com,Proxy +DOMAIN-SUFFIX,twishort.com,Proxy +DOMAIN-SUFFIX,tdesktop.com,Proxy +DOMAIN-SUFFIX,t.me,Proxy +DOMAIN-SUFFIX,tv-static.net,Proxy +DOMAIN-SUFFIX,theinitium.com,Proxy +DOMAIN-SUFFIX,trellocdn.com,Proxy +DOMAIN-SUFFIX,tvcdn.de,Proxy +DOMAIN-SUFFIX,textnow.me,Proxy + +// U +DOMAIN-SUFFIX,ustream.tv,Proxy +DOMAIN-SUFFIX,uspto.gov,Proxy +DOMAIN-SUFFIX,usma.edu,Proxy +DOMAIN-SUFFIX,us.to,Proxy +DOMAIN-SUFFIX,urlparser.com,Proxy +DOMAIN-SUFFIX,uProxy.org,Proxy +DOMAIN-SUFFIX,uploaded.net,Proxy +DOMAIN-SUFFIX,untraceable.us,Proxy +DOMAIN-SUFFIX,unpo.org,Proxy +DOMAIN-SUFFIX,unblocksites.co,Proxy +DOMAIN-SUFFIX,unblockdmm.com,Proxy +DOMAIN-SUFFIX,uhdwallpapers.org,Proxy +DOMAIN-SUFFIX,ugo.com,Proxy +DOMAIN-SUFFIX,udn.com,Proxy +DOMAIN-SUFFIX,uchicago.edu,Proxy +DOMAIN-SUFFIX,usgs.gov,Proxy +DOMAIN-SUFFIX,umblr.com,Proxy +DOMAIN-SUFFIX,unpkg.com,Proxy + +// V +DOMAIN-SUFFIX,vpngate.net,Proxy +DOMAIN-SUFFIX,vocativ.com,Proxy +DOMAIN-SUFFIX,visibletweets.com,Proxy +DOMAIN-SUFFIX,vimperator.org,Proxy +DOMAIN-SUFFIX,vimeo.com,Proxy +DOMAIN-SUFFIX,vimeocdn.com,Proxy +DOMAIN-SUFFIX,vidinfo.org,Proxy +DOMAIN-SUFFIX,videomega.tv,Proxy +DOMAIN-SUFFIX,vid.me,Proxy +DOMAIN-SUFFIX,viber.com,Proxy +DOMAIN-SUFFIX,veoh.com,Proxy +DOMAIN-SUFFIX,venchina.com,Proxy +DOMAIN-SUFFIX,vansky.com,Proxy +DOMAIN-SUFFIX,vanpeople.com,Proxy +DOMAIN-SUFFIX,van001.com,Proxy +DOMAIN-SUFFIX,v2ray.com,Proxy +DOMAIN-SUFFIX,verizonwireless.com,Proxy +DOMAIN-SUFFIX,vzw.com,Proxy +DOMAIN-SUFFIX,voachinese.com,Proxy +DOMAIN-SUFFIX,vnet.link,Proxy +DOMAIN-SUFFIX,voanews.com,Proxy +DOMAIN-SUFFIX,vanilla-js.com,Proxy +DOMAIN-SUFFIX,vjmedia.com.hk,Proxy + +// W +DOMAIN-SUFFIX,wwitv.com,Proxy +DOMAIN-SUFFIX,wsj.com,Proxy +DOMAIN-SUFFIX,wsj.net,Proxy +DOMAIN-SUFFIX,wordpress.com,Proxy +DOMAIN-SUFFIX,wp.com,Proxy +DOMAIN-SUFFIX,wow.com,Proxy +DOMAIN-SUFFIX,worldcat.org,Proxy +DOMAIN-SUFFIX,wn.com,Proxy +DOMAIN-SUFFIX,wikipedia.org,Proxy +DOMAIN-SUFFIX,wikipedia.com,Proxy +DOMAIN-SUFFIX,wikimedia.org,Proxy +DOMAIN-SUFFIX,wikileaks.info,Proxy +DOMAIN-SUFFIX,wikileaks-forum.com,Proxy +DOMAIN-SUFFIX,wikileaks.org,Proxy +DOMAIN-SUFFIX,westpoint.edu,Proxy +DOMAIN-SUFFIX,westca.com,Proxy +DOMAIN-SUFFIX,wenxuecity.com,Proxy +DOMAIN-SUFFIX,webwarper.net,Proxy +DOMAIN-SUFFIX,websnapr.com,Proxy +DOMAIN-SUFFIX,weblagu.com,Proxy +DOMAIN-SUFFIX,webfreer.com,Proxy +DOMAIN-SUFFIX,web2project.net,Proxy +DOMAIN-SUFFIX,wattpad.com,Proxy +DOMAIN-SUFFIX,winudf.com,Proxy +DOMAIN-SUFFIX,web.de,Proxy +DOMAIN-SUFFIX,wanqu.co,Proxy +DOMAIN-SUFFIX,whoer.net,Proxy +DOMAIN-SUFFIX,whatsapp.net,Proxy,force-remote-dns +DOMAIN-SUFFIX,whatsapp.com,Proxy,force-remote-dns +DOMAIN-SUFFIX,webpagefx.com,Proxy + +// X +DOMAIN-SUFFIX,xuite.net,Proxy +DOMAIN-SUFFIX,xanga.com,Proxy + +// Y +DOMAIN-SUFFIX,yahoo.com,Proxy +DOMAIN-SUFFIX,yimg.com,Proxy +DOMAIN-SUFFIX,yourlisten.com,Proxy +DOMAIN-SUFFIX,youmaker.com,Proxy +DOMAIN-SUFFIX,yorkbbs.ca,Proxy +DOMAIN-SUFFIX,yidio.com,Proxy +DOMAIN-SUFFIX,yes-news.com,Proxy +DOMAIN-SUFFIX,yesasia.com,Proxy +DOMAIN-SUFFIX,yeeyi.com,Proxy +DOMAIN-SUFFIX,yasni.co.uk,Proxy +DOMAIN-SUFFIX,yastatic.net,Proxy + +// Z +DOMAIN-SUFFIX,zacebook.com,Proxy +DOMAIN-SUFFIX,zalmos.com,Proxy +DOMAIN-SUFFIX,zaobao.com.sg,Proxy +DOMAIN-SUFFIX,zeutch.com,Proxy +DOMAIN-SUFFIX,zeronet.io,Proxy +DOMAIN-SUFFIX,zdassets.com,Proxy +DOMAIN-SUFFIX,zhowkev.in,Proxy +DOMAIN-SUFFIX,zb.com,Proxy + + +// 0-9 +DOMAIN-SUFFIX,4everProxy.com,Proxy +DOMAIN-SUFFIX,4shared.com,Proxy +DOMAIN-SUFFIX,6park.com,Proxy + +// Telegram +IP-CIDR,91.108.0.0/16,Proxy,no-resolve +IP-CIDR,149.154.0.0/16,Proxy,no-resolve + +// LINE +IP-CIDR,103.2.28.0/20,Proxy,no-resolve +IP-CIDR,119.235.224.0/20,Proxy,no-resolve +IP-CIDR,125.209.222.0/16,Proxy,no-resolve +IP-CIDR,147.92.128.0/21,Proxy,no-resolve +IP-CIDR,203.104.128.0/19,Proxy,no-resolve + +// Others Proxy +IP-CIDR,18.0.0.0/8,Proxy,no-resolve +IP-CIDR,31.0.0.0/8,Proxy,no-resolve +IP-CIDR,35.0.0.0/8,Proxy,no-resolve +IP-CIDR,52.0.0.0/8,Proxy,no-resolve +IP-CIDR,54.0.0.0/8,Proxy,no-resolve +IP-CIDR,64.0.0.0/8,Proxy,no-resolve +IP-CIDR,75.0.0.0/8,Proxy,no-resolve +IP-CIDR,158.0.0.0/8,Proxy,no-resolve +IP-CIDR,169.0.0.0/8,Proxy,no-resolve +IP-CIDR,174.0.0.0/8,Proxy,no-resolve +IP-CIDR,199.0.0.0/8,Proxy,no-resolve + +# Proxy End + +GEOIP, CN, DIRECT +FINAL, Proxy, dns-failed + +[URL Rewrite] +// Redirect_Google_Service +^https?://(www.)?g.cn https://www.google.com 302 +^https?://(www.)?google.cn https://www.google.com 302 diff --git a/test/surgetest.conf b/test/surgetest.conf new file mode 100644 index 0000000..59e0b2d --- /dev/null +++ b/test/surgetest.conf @@ -0,0 +1,2174 @@ +#!MANAGED-CONFIG https://raw.githubusercontent.com/Fndroid/jsbox_script/master/test/surgetest.conf + +[General] +服务器更新失败了,请重新上传,服务器更新失败了,请重新上传,服务器更新失败了,请重新上传,服务器更新失败了,请重新上传,服务器更新失败了,请重新上传,服务器更新失败了,请重新上传,服务器更新失败了,请重新上传,服务器更新失败了,请重新上传, +bypass-system = true +skip-proxy = 10.0.0.0/8, 17.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, localhost, *.local,*.crashlytics.com, *.epaylinks.cn +bypass-tun = 10.0.0.0/8, 127.0.0.0/24, 172.0.0.0/8, 192.168.0.0/16 +dns-server = 116.62.155.61, 119.29.29.29, 182.254.116.116, 1.1.1.1, 1.0.0.1, 208.67.220.220:443, 8.8.4.4, system +loglevel = notify +replica = false +ipv6 = true + +[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 + +[Proxy Group] +rixCloud = select, 🇺🇸 US, 🇭🇰 HK, 🇯🇵 JP,🇸🇬 SG + + +[Rule] +# Default +DOMAIN,gs.apple.com,rixCloud,force-remote-dns +DOMAIN-SUFFIX,apple.com,DIRECT +DOMAIN-SUFFIX,icloud.com,DIRECT +DOMAIN-SUFFIX,itunes.com,DIRECT +DOMAIN-SUFFIX,mzstatic.com,DIRECT +DOMAIN-SUFFIX,icloud-content.com,DIRECT,force-remote-dns +USER-AGENT,com.apple.appstored*,DIRECT,force-remote-dns +USER-AGENT,*AssistantServices*,rixCloud +# JD +DOMAIN,bdsp.x.jd.com,REJECT +DOMAIN,im-x.jd.com,REJECT +DOMAIN,jzt.jd.com,REJECT +DOMAIN,kepler.jd.com,REJECT +DOMAIN,keplerapi.jd.com,REJECT +DOMAIN,stat.m.jd.com,REJECT +DOMAIN,x.jd.com,REJECT +DOMAIN-SUFFIX,jd.com,DIRECT +# Netflix +DOMAIN-SUFFIX,netflix.com,rixCloud +DOMAIN-SUFFIX,netflix.net,rixCloud +DOMAIN-SUFFIX,nflxext.com,rixCloud +DOMAIN-SUFFIX,nflximg.com,rixCloud +DOMAIN-SUFFIX,nflximg.net,rixCloud +DOMAIN-SUFFIX,nflxvideo.net,rixCloud +# myTV Super +DOMAIN-KEYWORD,nowtv100,rixCloud +DOMAIN-KEYWORD,rthklive,rixCloud +DOMAIN-SUFFIX,mytvsuper.com,rixCloud +DOMAIN-SUFFIX,tvb.com,rixCloud +# Line +DOMAIN-SUFFIX,lin.ee,rixCloud +DOMAIN-SUFFIX,line.me,rixCloud +DOMAIN-SUFFIX,line.naver.jp,rixCloud,force-remote-dns +DOMAIN-SUFFIX,line-apps.com,rixCloud +DOMAIN-SUFFIX,line-cdn.net,rixCloud +DOMAIN-SUFFIX,line-scdn.net,rixCloud +DOMAIN-SUFFIX,nhncorp.jp,rixCloud +# rixCloud +DOMAIN-SUFFIX,0rz.tw,rixCloud +DOMAIN-SUFFIX,0to255.com,rixCloud +DOMAIN-SUFFIX,12bet.com,rixCloud +DOMAIN-SUFFIX,1bao.org,rixCloud +DOMAIN-SUFFIX,21andy.com,rixCloud +DOMAIN-SUFFIX,2shared.com,rixCloud +DOMAIN-SUFFIX,301works.org,rixCloud +DOMAIN-SUFFIX,4chan.org,rixCloud +DOMAIN-SUFFIX,4sq.com,rixCloud +DOMAIN-SUFFIX,500px.com,rixCloud +DOMAIN-SUFFIX,50webs.com,rixCloud +DOMAIN-SUFFIX,666kb.com,rixCloud +DOMAIN-SUFFIX,6v6dota.com,rixCloud +DOMAIN-SUFFIX,7capture.com,rixCloud +DOMAIN-SUFFIX,888.com,rixCloud +DOMAIN-SUFFIX,9001700.com,rixCloud +DOMAIN-SUFFIX,91porn.com,rixCloud +DOMAIN-SUFFIX,9bis.com,rixCloud +DOMAIN-SUFFIX,9bis.net,rixCloud +DOMAIN-SUFFIX,a5.com.ru,rixCloud +DOMAIN-SUFFIX,app-measurement.com,rixCloud +DOMAIN-SUFFIX,abc.pp.ru,rixCloud +DOMAIN-SUFFIX,acs-nccu.org,rixCloud +DOMAIN-SUFFIX,addictedtocoffee.de,rixCloud +DOMAIN-SUFFIX,adultkeep.net,rixCloud +DOMAIN-SUFFIX,advanscene.com,rixCloud +DOMAIN-SUFFIX,app-masurement.com,rixCloud +DOMAIN-SUFFIX,aenhancers.com,rixCloud +DOMAIN-SUFFIX,ajsands.com,rixCloud +DOMAIN-SUFFIX,akiba-online.com,rixCloud +DOMAIN-SUFFIX,alasbarricadas.org,rixCloud +DOMAIN-SUFFIX,alexdong.com,rixCloud +DOMAIN-SUFFIX,allaboutalpha.com,rixCloud +DOMAIN-SUFFIX,allgirlsallowed.org,rixCloud +DOMAIN-SUFFIX,allmovie.com,rixCloud +DOMAIN-SUFFIX,all-that-is-interesting.com,rixCloud +DOMAIN-SUFFIX,alternate-tools.com,rixCloud +DOMAIN-SUFFIX,alvinalexander.com,rixCloud +DOMAIN-SUFFIX,alwaysdata.com,rixCloud +DOMAIN-SUFFIX,alwaysdata.net,rixCloud +DOMAIN-SUFFIX,amazon.com,rixCloud +DOMAIN-SUFFIX,amazonaws.com,rixCloud +DOMAIN-SUFFIX,americanfeedmagazine.com,rixCloud +DOMAIN-SUFFIX,amiblockedornot.com,rixCloud +DOMAIN-SUFFIX,amnesty.org,rixCloud +DOMAIN-SUFFIX,amnestyusa.org,rixCloud +DOMAIN-SUFFIX,analyticsvidhya.com,rixCloud +DOMAIN-SUFFIX,andfaraway.net,rixCloud +DOMAIN-SUFFIX,android.com,rixCloud +DOMAIN-SUFFIX,angularjs.org,rixCloud +DOMAIN-SUFFIX,anobii.com,rixCloud +DOMAIN-SUFFIX,a-normal-day.com,rixCloud +DOMAIN-SUFFIX,anthonycalzadilla.com,rixCloud +DOMAIN-SUFFIX,antiwave.net,rixCloud +DOMAIN-SUFFIX,aobo.com.au,rixCloud +DOMAIN-SUFFIX,aol.ca,rixCloud +DOMAIN-SUFFIX,aol.com,rixCloud +DOMAIN-SUFFIX,aolnews.com,rixCloud +DOMAIN-SUFFIX,ap.org,rixCloud +DOMAIN-SUFFIX,apiary.io,rixCloud +DOMAIN-SUFFIX,apigee.com,rixCloud +DOMAIN-SUFFIX,appledaily.com,rixCloud +DOMAIN-SUFFIX,appledaily.com.hk,rixCloud +DOMAIN-SUFFIX,appledaily.com.tw,rixCloud +DOMAIN-SUFFIX,appspot.com,rixCloud,force-remote-dns +DOMAIN-SUFFIX,archive.is,rixCloud +DOMAIN-SUFFIX,archive.org,rixCloud +DOMAIN-SUFFIX,arctosia.com,rixCloud +DOMAIN-SUFFIX,areca-backup.org,rixCloud +DOMAIN-SUFFIX,art-or-porn.com,rixCloud +DOMAIN-SUFFIX,artsy.net,rixCloud +DOMAIN-SUFFIX,asahichinese.com,rixCloud +DOMAIN-SUFFIX,asdfg.jp,rixCloud +DOMAIN-SUFFIX,asiaharvest.org,rixCloud +DOMAIN-SUFFIX,asianews.it,rixCloud +DOMAIN-SUFFIX,askstudent.com,rixCloud +DOMAIN-SUFFIX,atlassian.com,rixCloud +DOMAIN-SUFFIX,avast.com,rixCloud +DOMAIN-SUFFIX,awardwinningfjords.com,rixCloud +DOMAIN-SUFFIX,awflasher.com,rixCloud +DOMAIN-SUFFIX,axureformac.com,rixCloud +DOMAIN-SUFFIX,badassjs.com,rixCloud +DOMAIN-SUFFIX,badoo.com,rixCloud +DOMAIN-SUFFIX,baidu.jp,rixCloud +DOMAIN-SUFFIX,baywords.com,rixCloud +DOMAIN-SUFFIX,bbc.co.uk,rixCloud +DOMAIN-SUFFIX,bbc.com,rixCloud +DOMAIN-SUFFIX,bbc.in,rixCloud +DOMAIN-SUFFIX,bbcchinese.com,rixCloud +DOMAIN-SUFFIX,bbg.gov,rixCloud +DOMAIN-SUFFIX,bebo.com,rixCloud +DOMAIN-SUFFIX,berlintwitterwall.com,rixCloud +DOMAIN-SUFFIX,bestforchina.org,rixCloud +DOMAIN-SUFFIX,bet365.com,rixCloud +DOMAIN-SUFFIX,betfair.com,rixCloud +DOMAIN-SUFFIX,bettween.com,rixCloud +DOMAIN-SUFFIX,bewww.net,rixCloud +DOMAIN-SUFFIX,bfsh.hk,rixCloud +DOMAIN-SUFFIX,bigcommerce.com,rixCloud +DOMAIN-SUFFIX,billywr.com,rixCloud +DOMAIN-SUFFIX,birdhouseapp.com,rixCloud +DOMAIN-SUFFIX,bit.ly,rixCloud +DOMAIN-SUFFIX,bitbucket.com,rixCloud +DOMAIN-SUFFIX,bitbucket.org,rixCloud +DOMAIN-SUFFIX,bitly.com,rixCloud +DOMAIN-SUFFIX,bitshare.com,rixCloud +DOMAIN-SUFFIX,bitsnoop.com,rixCloud +DOMAIN-SUFFIX,blackbirdmusicgroup.com,rixCloud +DOMAIN-SUFFIX,blinkx.com,rixCloud +DOMAIN-SUFFIX,blip.tv,rixCloud +DOMAIN-SUFFIX,blogcatalog.com,rixCloud +DOMAIN-SUFFIX,blogger.com,rixCloud +DOMAIN-SUFFIX,bloglovin.com,rixCloud +DOMAIN-SUFFIX,blogs.com,rixCloud +DOMAIN-SUFFIX,bloodshed.net,rixCloud +DOMAIN-SUFFIX,bloomberg.com,rixCloud +DOMAIN-SUFFIX,bloomberg.de,rixCloud +DOMAIN-SUFFIX,bobulate.com,rixCloud +DOMAIN-SUFFIX,bookshelfporn.com,rixCloud +DOMAIN-SUFFIX,bootstrapcdn.com,rixCloud +DOMAIN-SUFFIX,box.com,rixCloud +DOMAIN-SUFFIX,box.net,rixCloud +DOMAIN-SUFFIX,boxcar.io,rixCloud +DOMAIN-SUFFIX,braumeister.org,rixCloud +DOMAIN-SUFFIX,break.com,rixCloud +DOMAIN-SUFFIX,breakingtweets.com,rixCloud +DOMAIN-SUFFIX,brightkite.com,rixCloud +DOMAIN-SUFFIX,brizzly.com,rixCloud +DOMAIN-SUFFIX,brucewang.net,rixCloud +DOMAIN-SUFFIX,bugclub.org,rixCloud +DOMAIN-SUFFIX,businessinsider.com,rixCloud +DOMAIN-SUFFIX,cafepress.com,rixCloud +DOMAIN-SUFFIX,calameo.com,rixCloud +DOMAIN-SUFFIX,calebelston.com,rixCloud +DOMAIN-SUFFIX,cari.com.my,rixCloud +DOMAIN-SUFFIX,caspion.com,rixCloud +DOMAIN-SUFFIX,castfire.com,rixCloud +DOMAIN-SUFFIX,catcatbox.com,rixCloud +DOMAIN-SUFFIX,catch22.net,rixCloud +DOMAIN-SUFFIX,cbc.ca,rixCloud +DOMAIN-SUFFIX,cbsnews.com,rixCloud +DOMAIN-SUFFIX,cc.com,rixCloud +DOMAIN-SUFFIX,cdnews.com.tw,rixCloud +DOMAIN-SUFFIX,cecc.gov,rixCloud +DOMAIN-SUFFIX,cellulo.info,rixCloud +DOMAIN-SUFFIX,c-est-simple.com,rixCloud +DOMAIN-SUFFIX,chandoo.org,rixCloud +DOMAIN-SUFFIX,change.org,rixCloud +DOMAIN-SUFFIX,cherrysave.com,rixCloud +DOMAIN-SUFFIX,chevronwp7.com,rixCloud +DOMAIN-SUFFIX,china21.org,rixCloud +DOMAIN-SUFFIX,chinaaid.org,rixCloud +DOMAIN-SUFFIX,chinadigitaltimes.net,rixCloud +DOMAIN-SUFFIX,chinageeks.org,rixCloud +DOMAIN-SUFFIX,chinahush.com,rixCloud +DOMAIN-SUFFIX,chinalawandpolicy.com,rixCloud +DOMAIN-SUFFIX,chinalawtranslate.com,rixCloud +DOMAIN-SUFFIX,chinese-memorial.org,rixCloud +DOMAIN-SUFFIX,chinesetalks.net,rixCloud +DOMAIN-SUFFIX,chrispederick.com,rixCloud +DOMAIN-SUFFIX,chrispederick.net,rixCloud +DOMAIN-SUFFIX,chromium.org,rixCloud +DOMAIN-SUFFIX,cipherdyne.org,rixCloud +DOMAIN-SUFFIX,citizenlab.org,rixCloud +DOMAIN-SUFFIX,citizensradio.org,rixCloud +DOMAIN-SUFFIX,cjb.net,rixCloud +DOMAIN-SUFFIX,classicalguitarblog.net,rixCloud +DOMAIN-SUFFIX,clb.org.hk,rixCloud +DOMAIN-SUFFIX,clientsfromhell.net,rixCloud +DOMAIN-SUFFIX,cloudflare.com,rixCloud +DOMAIN-SUFFIX,cloudfront.com,rixCloud +DOMAIN-SUFFIX,cms.gov,rixCloud +DOMAIN-SUFFIX,cmule.org,rixCloud +DOMAIN-SUFFIX,cnn.com,rixCloud +DOMAIN-SUFFIX,co.hk,rixCloud +DOMAIN-SUFFIX,co.uk,rixCloud +DOMAIN-SUFFIX,cochina.co,rixCloud +DOMAIN-SUFFIX,cocoapods.org,rixCloud +DOMAIN-SUFFIX,codeboxapp.com,rixCloud +DOMAIN-SUFFIX,codeshare.io,rixCloud +DOMAIN-SUFFIX,codeskulptor.org,rixCloud +DOMAIN-SUFFIX,collateralmurder.com,rixCloud +DOMAIN-SUFFIX,collateralmurder.org,rixCloud +DOMAIN-SUFFIX,comedycentral.com,rixCloud +DOMAIN-SUFFIX,content.office.net,rixCloud +DOMAIN-SUFFIX,cotweet.com,rixCloud +DOMAIN-SUFFIX,couchdbwiki.com,rixCloud +DOMAIN-SUFFIX,cpj.org,rixCloud +DOMAIN-SUFFIX,crashlytics.com,rixCloud +DOMAIN-SUFFIX,crd-net.org,rixCloud +DOMAIN-SUFFIX,crd-net.org,rixCloud +DOMAIN-SUFFIX,cubicle17.com,rixCloud +DOMAIN-SUFFIX,curvefish.com,rixCloud +DOMAIN-SUFFIX,dadazim.com,rixCloud +DOMAIN-SUFFIX,dafagood.com,rixCloud +DOMAIN-SUFFIX,dailidaili.com,rixCloud +DOMAIN-SUFFIX,dailyme.com,rixCloud +DOMAIN-SUFFIX,danke4china.net,rixCloud +DOMAIN-SUFFIX,danwei.org,rixCloud +DOMAIN-SUFFIX,date.fm,rixCloud +DOMAIN-SUFFIX,davidslog.com,rixCloud +DOMAIN-SUFFIX,davidziegler.net,rixCloud +DOMAIN-SUFFIX,dayabook.com,rixCloud +DOMAIN-SUFFIX,dayoneapp.com,rixCloud +DOMAIN-SUFFIX,ddfprod.com,rixCloud +DOMAIN-SUFFIX,debian.org,rixCloud +DOMAIN-SUFFIX,deck.ly,rixCloud +DOMAIN-SUFFIX,delcamp.net,rixCloud +DOMAIN-SUFFIX,delicious.com,rixCloud +DOMAIN-SUFFIX,democrats.org,rixCloud +DOMAIN-SUFFIX,derekhsu.homeip.net,rixCloud +DOMAIN-SUFFIX,desc.se,rixCloud +DOMAIN-SUFFIX,de-sci.org,rixCloud +DOMAIN-SUFFIX,deutsche-welle.de,rixCloud +DOMAIN-SUFFIX,dev102.com,rixCloud +DOMAIN-SUFFIX,deviantart.com,rixCloud +DOMAIN-SUFFIX,devio.us,rixCloud +DOMAIN-SUFFIX,digicert.com,rixCloud +DOMAIN-SUFFIX,diigo.com,rixCloud +DOMAIN-SUFFIX,directcreative.com,rixCloud +DOMAIN-SUFFIX,discogs.com,rixCloud +DOMAIN-SUFFIX,disp.cc,rixCloud +DOMAIN-SUFFIX,djangosnippets.org,rixCloud +DOMAIN-SUFFIX,dlsite.com,rixCloud +DOMAIN-SUFFIX,domaintools.com,rixCloud +DOMAIN-SUFFIX,dorcel.com,rixCloud +DOMAIN-SUFFIX,dotplane.com,rixCloud +DOMAIN-SUFFIX,dotsub.com,rixCloud +DOMAIN-SUFFIX,doxygen.org,rixCloud +DOMAIN-SUFFIX,dphk.org,rixCloud +DOMAIN-SUFFIX,drahelas.ru,rixCloud +DOMAIN-SUFFIX,drewolanoff.com,rixCloud +DOMAIN-SUFFIX,dropbox.com,rixCloud +DOMAIN-SUFFIX,dropboxstatic.com,rixCloud +DOMAIN-SUFFIX,dropboxusercontent.com,rixCloud +DOMAIN-SUFFIX,dtic.mil,rixCloud +DOMAIN-SUFFIX,dtiserv.com,rixCloud +DOMAIN-SUFFIX,duckduckgo.com,rixCloud +DOMAIN-SUFFIX,duckmylife.com,rixCloud +DOMAIN-SUFFIX,duihua.org,rixCloud +DOMAIN-SUFFIX,duoweitimes.com,rixCloud +DOMAIN-SUFFIX,duplicati.com,rixCloud +DOMAIN-SUFFIX,dw.com,rixCloud +DOMAIN-SUFFIX,dw.de,rixCloud +DOMAIN-SUFFIX,dwheeler.com,rixCloud +DOMAIN-SUFFIX,dw-world.com,rixCloud +DOMAIN-SUFFIX,dw-world.de,rixCloud +DOMAIN-SUFFIX,dyndns.org,rixCloud +DOMAIN-SUFFIX,dzze.com,rixCloud +DOMAIN-SUFFIX,eamonnbrennan.com,rixCloud +DOMAIN-SUFFIX,eastturkistangovernmentinexile.us,rixCloud +DOMAIN-SUFFIX,ebookbrowse.com,rixCloud +DOMAIN-SUFFIX,ebookee.com,rixCloud +DOMAIN-SUFFIX,echofon.com,rixCloud +DOMAIN-SUFFIX,edicypages.com,rixCloud +DOMAIN-SUFFIX,edoors.com,rixCloud +DOMAIN-SUFFIX,entersect.co.za,rixCloud +DOMAIN-SUFFIX,efksoft.com,rixCloud +DOMAIN-SUFFIX,e-gold.com,rixCloud +DOMAIN-SUFFIX,e-hentai.org,rixCloud +DOMAIN-SUFFIX,elpais.com,rixCloud +DOMAIN-SUFFIX,emacsblog.org,rixCloud +DOMAIN-SUFFIX,emory.edu,rixCloud +DOMAIN-SUFFIX,engadget.com,rixCloud +DOMAIN-SUFFIX,entersekt.com,rixCloud +DOMAIN-SUFFIX,epochtimes.de,rixCloud +DOMAIN-SUFFIX,epochtimes.fr,rixCloud +DOMAIN-SUFFIX,epochtimes.ru,rixCloud +DOMAIN-SUFFIX,epochtimes.se,rixCloud +DOMAIN-SUFFIX,epochtimes-bg.com,rixCloud +DOMAIN-SUFFIX,epochtimes-romania.com,rixCloud +DOMAIN-SUFFIX,epochtimestr.com,rixCloud +DOMAIN-SUFFIX,erabaru.net,rixCloud +DOMAIN-SUFFIX,erepublik.com,rixCloud +DOMAIN-SUFFIX,eriversoft.com,rixCloud +DOMAIN-SUFFIX,eroacg.org,rixCloud +DOMAIN-SUFFIX,eurekavpt.com,rixCloud +DOMAIN-SUFFIX,everyday-carry.com,rixCloud +DOMAIN-SUFFIX,exploader.net,rixCloud +DOMAIN-SUFFIX,extremetube.com,rixCloud +DOMAIN-SUFFIX,fabric.io,rixCloud +DOMAIN-SUFFIX,facebook.com,rixCloud +DOMAIN-SUFFIX,facebookquotes4u.com,rixCloud +DOMAIN-SUFFIX,facesofnyfw.com,rixCloud +DOMAIN-SUFFIX,falsefire.com,rixCloud +DOMAIN-SUFFIX,fapdu.com,rixCloud +DOMAIN-SUFFIX,farwestchina.com,rixCloud +DOMAIN-SUFFIX,fastly.net,rixCloud +DOMAIN-SUFFIX,faststone.org,rixCloud +DOMAIN-SUFFIX,favstar.fm,rixCloud +DOMAIN-SUFFIX,feedbooks.mobi,rixCloud +DOMAIN-SUFFIX,feedly.com,rixCloud +DOMAIN-SUFFIX,felixcat.net,rixCloud +DOMAIN-SUFFIX,feministteacher.com,rixCloud +DOMAIN-SUFFIX,filefactory.com,rixCloud +DOMAIN-SUFFIX,fileserve.com,rixCloud +DOMAIN-SUFFIX,fillthesquare.org,rixCloud +DOMAIN-SUFFIX,firefox.com,rixCloud +DOMAIN-SUFFIX,firefox.org,rixCloud +DOMAIN-SUFFIX,firstfivefollowers.com,rixCloud +DOMAIN-SUFFIX,fizzik.com,rixCloud +DOMAIN-SUFFIX,flecheinthepeche.fr,rixCloud +DOMAIN-SUFFIX,flickr.com,rixCloud +DOMAIN-SUFFIX,flickrhivemind.net,rixCloud +DOMAIN-SUFFIX,fmnnow.com,rixCloud +DOMAIN-SUFFIX,forums-free.com,rixCloud +DOMAIN-SUFFIX,foxbusiness.com,rixCloud +DOMAIN-SUFFIX,franklc.com,rixCloud +DOMAIN-SUFFIX,freakshare.com,rixCloud +DOMAIN-SUFFIX,fredwilson.vc,rixCloud +DOMAIN-SUFFIX,free.fr,rixCloud +DOMAIN-SUFFIX,fb.com,rixCloud +DOMAIN-SUFFIX,freedomhouse.org,rixCloud +DOMAIN-SUFFIX,freelotto.com,rixCloud +DOMAIN-SUFFIX,freemorenews.com,rixCloud +DOMAIN-SUFFIX,freenetproject.org,rixCloud +DOMAIN-SUFFIX,freenewscn.com,rixCloud +DOMAIN-SUFFIX,freessh.us,rixCloud +DOMAIN-SUFFIX,freewallpaper4.me,rixCloud +DOMAIN-SUFFIX,freewebs.com,rixCloud +DOMAIN-SUFFIX,frequency.com,rixCloud +DOMAIN-SUFFIX,friendfeed.com,rixCloud +DOMAIN-SUFFIX,fring.com,rixCloud +DOMAIN-SUFFIX,frommel.net,rixCloud +DOMAIN-SUFFIX,frontlinedefenders.org,rixCloud +DOMAIN-SUFFIX,fsurf.com,rixCloud +DOMAIN-SUFFIX,fulue.com,rixCloud +DOMAIN-SUFFIX,futureme.org,rixCloud +DOMAIN-SUFFIX,fxnetworks.com,rixCloud +DOMAIN-SUFFIX,fzh999.net,rixCloud +DOMAIN-SUFFIX,g.co,rixCloud +DOMAIN-SUFFIX,gabocorp.com,rixCloud +DOMAIN-SUFFIX,gartlive.com,rixCloud +DOMAIN-SUFFIX,gazotube.com,rixCloud +DOMAIN-SUFFIX,geekmanuals.com,rixCloud +DOMAIN-SUFFIX,genuitec.com,rixCloud +DOMAIN-SUFFIX,geohot.com,rixCloud +DOMAIN-SUFFIX,geometrictools.com,rixCloud +DOMAIN-SUFFIX,getcloudapp.com,rixCloud +DOMAIN-SUFFIX,get-digital-help.com,rixCloud +DOMAIN-SUFFIX,getsmartlinks.com,rixCloud +DOMAIN-SUFFIX,ggpht.com,rixCloud +DOMAIN-SUFFIX,ggssl.com,rixCloud +DOMAIN-SUFFIX,ghost.org,rixCloud +DOMAIN-SUFFIX,giganews.com,rixCloud +DOMAIN-SUFFIX,gigporno.ru,rixCloud +DOMAIN-SUFFIX,gimpshop.com,rixCloud +DOMAIN-SUFFIX,girlbanker.com,rixCloud +DOMAIN-SUFFIX,gitbooks.io,rixCloud +DOMAIN-SUFFIX,github.com,rixCloud +DOMAIN-SUFFIX,github.io,rixCloud +DOMAIN-SUFFIX,githubapp.com,rixCloud +DOMAIN-SUFFIX,githubusercontent.com,rixCloud +DOMAIN-SUFFIX,git-scm.com,rixCloud +DOMAIN-SUFFIX,glennhilton.com,rixCloud +DOMAIN-SUFFIX,globaljihad.net,rixCloud +DOMAIN-SUFFIX,globalrescue.net,rixCloud +DOMAIN-SUFFIX,globalsign.com,rixCloud +DOMAIN-SUFFIX,globalvoicesonline.org,rixCloud +DOMAIN-SUFFIX,gmail.com,rixCloud +DOMAIN-SUFFIX,golan.org,rixCloud +DOMAIN-SUFFIX,golang.org,rixCloud +DOMAIN-SUFFIX,goldwave.com,rixCloud +DOMAIN-SUFFIX,goo.gl,rixCloud +DOMAIN-SUFFIX,google.cn,rixCloud +DOMAIN-SUFFIX,google.co.id,rixCloud +DOMAIN-SUFFIX,google.co.jp,rixCloud +DOMAIN-SUFFIX,google.com,rixCloud +DOMAIN-SUFFIX,google.com.hk,rixCloud +DOMAIN-SUFFIX,google.com.sg,rixCloud +DOMAIN-SUFFIX,google.com.tw,rixCloud +DOMAIN-SUFFIX,google.org,rixCloud +DOMAIN-SUFFIX,googlecode.com,rixCloud +DOMAIN-SUFFIX,googledrive.com,rixCloud +DOMAIN-SUFFIX,googlegroups.com,rixCloud +DOMAIN-SUFFIX,googlemail.com,rixCloud +DOMAIN-SUFFIX,googlepages.com,rixCloud +DOMAIN-SUFFIX,googlesile.com,rixCloud +DOMAIN-SUFFIX,googlesource.com,rixCloud +DOMAIN-SUFFIX,googleusercontent.com,rixCloud +DOMAIN-SUFFIX,googlevideo.com,rixCloud +DOMAIN-SUFFIX,gopetition.com,rixCloud +DOMAIN-SUFFIX,gospelherald.com,rixCloud +DOMAIN-SUFFIX,gotw.ca,rixCloud +DOMAIN-SUFFIX,gradconnection.com,rixCloud +DOMAIN-SUFFIX,grandtrial.org,rixCloud +DOMAIN-SUFFIX,graphis.ne.jp,rixCloud +DOMAIN-SUFFIX,gravatar.com,rixCloud +DOMAIN-SUFFIX,graylog2.org,rixCloud +DOMAIN-SUFFIX,greatfire.org,rixCloud +DOMAIN-SUFFIX,great-firewall.com,rixCloud +DOMAIN-SUFFIX,gstatic.com,rixCloud +DOMAIN-SUFFIX,gtricks.com,rixCloud +DOMAIN-SUFFIX,gutteruncensored.com,rixCloud +DOMAIN-SUFFIX,gvt1.com,rixCloud +DOMAIN-SUFFIX,gvt2.com,rixCloud +DOMAIN-SUFFIX,gvt3.com,rixCloud +DOMAIN-SUFFIX,gyalwarinpoche.com,rixCloud +DOMAIN-SUFFIX,hackthatphone.net,rixCloud +DOMAIN-SUFFIX,hahlo.com,rixCloud +DOMAIN-SUFFIX,harunyahya.com,rixCloud +DOMAIN-SUFFIX,h-china.org,rixCloud +DOMAIN-SUFFIX,hechaji.com,rixCloud +DOMAIN-SUFFIX,helloandroid.com,rixCloud +DOMAIN-SUFFIX,hellonewyork.us,rixCloud +DOMAIN-SUFFIX,helpzhuling.org,rixCloud +DOMAIN-SUFFIX,highrockmedia.com,rixCloud +DOMAIN-SUFFIX,hikinggfw.org,rixCloud +DOMAIN-SUFFIX,hk32168.com,rixCloud +DOMAIN-SUFFIX,hkhkhk.com,rixCloud +DOMAIN-SUFFIX,hkjp.org,rixCloud +DOMAIN-SUFFIX,hkreporter.com,rixCloud +DOMAIN-SUFFIX,hloli.net,rixCloud +DOMAIN-SUFFIX,homeservershow.com,rixCloud +DOMAIN-SUFFIX,hongmeimei.com,rixCloud +DOMAIN-SUFFIX,hootsuite.com,rixCloud +DOMAIN-SUFFIX,howtoforge.com,rixCloud +DOMAIN-SUFFIX,hqcdp.org,rixCloud +DOMAIN-SUFFIX,hrw.org,rixCloud +DOMAIN-SUFFIX,ht.ly,rixCloud +DOMAIN-SUFFIX,htl.li,rixCloud +DOMAIN-SUFFIX,htmldog.com,rixCloud +DOMAIN-SUFFIX,htxt.it,rixCloud +DOMAIN-SUFFIX,hua-yue.net,rixCloud +DOMAIN-SUFFIX,hudatoriq.web.id,rixCloud +DOMAIN-SUFFIX,hugoroy.eu,rixCloud +DOMAIN-SUFFIX,hulu.com,rixCloud +DOMAIN-SUFFIX,huping.net,rixCloud +DOMAIN-SUFFIX,hwinfo.com,rixCloud +DOMAIN-SUFFIX,hyperrate.com,rixCloud +DOMAIN-SUFFIX,i1.hk,rixCloud +DOMAIN-SUFFIX,i2p2.de,rixCloud +DOMAIN-SUFFIX,ialmostlaugh.com,rixCloud +DOMAIN-SUFFIX,iask.bz,rixCloud +DOMAIN-SUFFIX,ibiblio.org,rixCloud +DOMAIN-SUFFIX,iblogserv-f.net,rixCloud +DOMAIN-SUFFIX,ibtimes.com,rixCloud +DOMAIN-SUFFIX,icij.org,rixCloud +DOMAIN-SUFFIX,icl-fi.org,rixCloud +DOMAIN-SUFFIX,iconpaper.org,rixCloud +DOMAIN-SUFFIX,identi.ca,rixCloud +DOMAIN-SUFFIX,ifcss.org,rixCloud +DOMAIN-SUFFIX,ifjc.org,rixCloud +DOMAIN-SUFFIX,ift.tt,rixCloud +DOMAIN-SUFFIX,ifttt.com,rixCloud +DOMAIN-SUFFIX,ig.com.br,rixCloud +DOMAIN-SUFFIX,igfw.net,rixCloud +DOMAIN-SUFFIX,igvita.com,rixCloud +DOMAIN-SUFFIX,ihostimg.com,rixCloud +DOMAIN-SUFFIX,illusionfactory.com,rixCloud +DOMAIN-SUFFIX,imageshack.us,rixCloud +DOMAIN-SUFFIX,imagezilla.net,rixCloud +DOMAIN-SUFFIX,imdb.com,rixCloud +DOMAIN-SUFFIX,img.ly,rixCloud +DOMAIN-SUFFIX,imgchili.net,rixCloud +DOMAIN-SUFFIX,imlive.com,rixCloud +DOMAIN-SUFFIX,in.com,rixCloud +DOMAIN-SUFFIX,incredibox.fr,rixCloud +DOMAIN-SUFFIX,informer.com,rixCloud +DOMAIN-SUFFIX,instagram.com,rixCloud +DOMAIN-SUFFIX,instapaper.com,rixCloud +DOMAIN-SUFFIX,internet.org,rixCloud +DOMAIN-SUFFIX,internetdefenseleague.org,rixCloud +DOMAIN-SUFFIX,inxian.com,rixCloud +DOMAIN-SUFFIX,iphone-dev.org,rixCloud +DOMAIN-SUFFIX,iphonehacks.com,rixCloud +DOMAIN-SUFFIX,ipicture.ru,rixCloud +DOMAIN-SUFFIX,iredmail.org,rixCloud +DOMAIN-SUFFIX,ironicsoftware.com,rixCloud +DOMAIN-SUFFIX,ironpython.net,rixCloud +DOMAIN-SUFFIX,isaacmao.com,rixCloud +DOMAIN-SUFFIX,ismprofessional.net,rixCloud +DOMAIN-SUFFIX,israbox.com,rixCloud +DOMAIN-SUFFIX,istef.info,rixCloud +DOMAIN-SUFFIX,istockphoto.com,rixCloud +DOMAIN-SUFFIX,it-eproducts.com,rixCloud +DOMAIN-SUFFIX,ithome.com.tw,rixCloud +DOMAIN-SUFFIX,itshidden.com,rixCloud +DOMAIN-SUFFIX,itshidden.eu,rixCloud +DOMAIN-SUFFIX,izihost.org,rixCloud +DOMAIN-SUFFIX,j.mp,rixCloud +DOMAIN-SUFFIX,jackjia.com,rixCloud +DOMAIN-SUFFIX,jayparkinsonmd.com,rixCloud +DOMAIN-SUFFIX,jetbrains.com,rixCloud +DOMAIN-SUFFIX,jgoodies.com,rixCloud +DOMAIN-SUFFIX,jitouch.com,rixCloud +DOMAIN-SUFFIX,joachims.org,rixCloud +DOMAIN-SUFFIX,jtvnw.net,rixCloud +DOMAIN-SUFFIX,joeedelman.com,rixCloud +DOMAIN-SUFFIX,joeyrobert.org,rixCloud +DOMAIN-SUFFIX,journalofdemocracy.org,rixCloud +DOMAIN-SUFFIX,jqueryui.com,rixCloud +DOMAIN-SUFFIX,jshint.com,rixCloud +DOMAIN-SUFFIX,junauza.com,rixCloud +DOMAIN-SUFFIX,justtristan.com,rixCloud +DOMAIN-SUFFIX,jwmusic.org,rixCloud +DOMAIN-SUFFIX,jyxf.net,rixCloud +DOMAIN-SUFFIX,kaltura.com,rixCloud +DOMAIN-SUFFIX,ka-wai.com,rixCloud +DOMAIN-SUFFIX,kcsoftwares.com,rixCloud +DOMAIN-SUFFIX,kechara.com,rixCloud +DOMAIN-SUFFIX,keepandshare.com,rixCloud +DOMAIN-SUFFIX,kinghost.com,rixCloud +DOMAIN-SUFFIX,klip.me,rixCloud +DOMAIN-SUFFIX,kodingen.com,rixCloud +DOMAIN-SUFFIX,kompozer.net,rixCloud +DOMAIN-SUFFIX,konachan.com,rixCloud +DOMAIN-SUFFIX,koolsolutions.com,rixCloud +DOMAIN-SUFFIX,ktzhk.com,rixCloud +DOMAIN-SUFFIX,kurtmunger.com,rixCloud +DOMAIN-SUFFIX,kyohk.net,rixCloud +DOMAIN-SUFFIX,labiennale.org,rixCloud +DOMAIN-SUFFIX,labs.nodesnoop.com,rixCloud +DOMAIN-SUFFIX,lagranepoca.com,rixCloud +DOMAIN-SUFFIX,laogai.org,rixCloud +DOMAIN-SUFFIX,larsgeorge.com,rixCloud +DOMAIN-SUFFIX,lastpass.com,rixCloud +DOMAIN-SUFFIX,latelinenews.com,rixCloud +DOMAIN-SUFFIX,latimes.com,rixCloud +DOMAIN-SUFFIX,law.com,rixCloud +DOMAIN-SUFFIX,lenwhite.com,rixCloud +DOMAIN-SUFFIX,lerosua.org,rixCloud +DOMAIN-SUFFIX,lesscss.org,rixCloud +DOMAIN-SUFFIX,lightbox.com,rixCloud +DOMAIN-SUFFIX,linksalpha.com,rixCloud +DOMAIN-SUFFIX,linpie.com,rixCloud +DOMAIN-SUFFIX,linuxconfig.org,rixCloud +DOMAIN-SUFFIX,linuxquestions.org,rixCloud +DOMAIN-SUFFIX,linuxreviews.org,rixCloud +DOMAIN-SUFFIX,list.ly,rixCloud +DOMAIN-SUFFIX,listentoyoutube.com,rixCloud +DOMAIN-SUFFIX,lithium.com,rixCloud +DOMAIN-SUFFIX,littlebigdetails.com,rixCloud +DOMAIN-SUFFIX,liudejun.com,rixCloud +DOMAIN-SUFFIX,livedoor.com,rixCloud +DOMAIN-SUFFIX,livedoor.jp,rixCloud +DOMAIN-SUFFIX,liveleak.com,rixCloud +DOMAIN-SUFFIX,livestream.com,rixCloud +DOMAIN-SUFFIX,livingstream.com,rixCloud +DOMAIN-SUFFIX,lizhizhuangbi.com,rixCloud +DOMAIN-SUFFIX,lockdown.com,rixCloud +DOMAIN-SUFFIX,logiqx.com,rixCloud +DOMAIN-SUFFIX,loiclemeur.com,rixCloud +DOMAIN-SUFFIX,longtermly.net,rixCloud +DOMAIN-SUFFIX,lookingglasstheatre.org,rixCloud +DOMAIN-SUFFIX,lookpic.com,rixCloud +DOMAIN-SUFFIX,loved.hk,rixCloud +DOMAIN-SUFFIX,lovequicksilver.com,rixCloud +DOMAIN-SUFFIX,lsd.org.hk,rixCloud +DOMAIN-SUFFIX,lsm.org,rixCloud +DOMAIN-SUFFIX,lyricsquote.com,rixCloud +DOMAIN-SUFFIX,macys.com,rixCloud +DOMAIN-SUFFIX,mad-ar.ch,rixCloud +DOMAIN-SUFFIX,madmenunbuttoned.com,rixCloud +DOMAIN-SUFFIX,mail-archive.com,rixCloud +DOMAIN-SUFFIX,makemymood.com,rixCloud +DOMAIN-SUFFIX,malaysiakini.com,rixCloud +DOMAIN-SUFFIX,marc.info,rixCloud +DOMAIN-SUFFIX,marco.org,rixCloud +DOMAIN-SUFFIX,markmail.org,rixCloud +DOMAIN-SUFFIX,markmilian.com,rixCloud +DOMAIN-SUFFIX,martau.com,rixCloud +DOMAIN-SUFFIX,martincartoons.com,rixCloud +DOMAIN-SUFFIX,marxist.com,rixCloud +DOMAIN-SUFFIX,marxist.net,rixCloud +DOMAIN-SUFFIX,marxists.org,rixCloud +DOMAIN-SUFFIX,mashable.com,rixCloud +DOMAIN-SUFFIX,matainja.com,rixCloud +DOMAIN-SUFFIX,maturejp.com,rixCloud +DOMAIN-SUFFIX,md-t.org,rixCloud +DOMAIN-SUFFIX,mediafire.com,rixCloud +DOMAIN-SUFFIX,medium.com,rixCloud +DOMAIN-SUFFIX,meetup.com,rixCloud +DOMAIN-SUFFIX,mefeedia.com,rixCloud +DOMAIN-SUFFIX,megabyet.net,rixCloud +DOMAIN-SUFFIX,megurineluka.com,rixCloud +DOMAIN-SUFFIX,memrijttm.org,rixCloud +DOMAIN-SUFFIX,messenger.com,rixCloud +DOMAIN-SUFFIX,messengerplatform.fb.com,rixCloud +DOMAIN-SUFFIX,michaelanti.com,rixCloud +DOMAIN-SUFFIX,michaelmarketl.com,rixCloud +DOMAIN-SUFFIX,middle-way.net,rixCloud +DOMAIN-SUFFIX,mingpaony.com,rixCloud +DOMAIN-SUFFIX,minimalmac.com,rixCloud +DOMAIN-SUFFIX,mininova.org,rixCloud +DOMAIN-SUFFIX,miroguide.com,rixCloud +DOMAIN-SUFFIX,mixedmedialabs.com,rixCloud +DOMAIN-SUFFIX,mixpod.com,rixCloud +DOMAIN-SUFFIX,mk5000.com,rixCloud +DOMAIN-SUFFIX,mobatek.net,rixCloud +DOMAIN-SUFFIX,mobile01.com,rixCloud +DOMAIN-SUFFIX,mobileways.de,rixCloud +DOMAIN-SUFFIX,moby.to,rixCloud +DOMAIN-SUFFIX,mobypicture.com,rixCloud +DOMAIN-SUFFIX,modfetish.com,rixCloud +DOMAIN-SUFFIX,modmyi.com,rixCloud +DOMAIN-SUFFIX,moegirl.org,rixCloud +DOMAIN-SUFFIX,mondex.org,rixCloud +DOMAIN-SUFFIX,mongodb.org,rixCloud +DOMAIN-SUFFIX,mytvsuper.com,rixCloud +DOMAIN-SUFFIX,monlamit.org,rixCloud +DOMAIN-SUFFIX,morningsun.org,rixCloud +DOMAIN-SUFFIX,movabletype.com,rixCloud +DOMAIN-SUFFIX,mozilla.com,rixCloud +DOMAIN-SUFFIX,mozilla.net,rixCloud +DOMAIN-SUFFIX,mozilla.org,rixCloud +DOMAIN-SUFFIX,mp3ye.eu,rixCloud +DOMAIN-SUFFIX,mpinews.com,rixCloud +DOMAIN-SUFFIX,msn.com,rixCloud +DOMAIN-SUFFIX,msn.com.tw,rixCloud +DOMAIN-SUFFIX,m-sport.co.uk,rixCloud +DOMAIN-SUFFIX,mthruf.com,rixCloud +DOMAIN-SUFFIX,mtv.co.uk,rixCloud +DOMAIN-SUFFIX,multiply.com,rixCloud +DOMAIN-SUFFIX,muzi.com,rixCloud +DOMAIN-SUFFIX,mx981.com,rixCloud +DOMAIN-SUFFIX,myaudiocast.com,rixCloud +DOMAIN-SUFFIX,mychinamyhome.com,rixCloud +DOMAIN-SUFFIX,myeclipseide.com,rixCloud +DOMAIN-SUFFIX,mypopescu.com,rixCloud +DOMAIN-SUFFIX,mysinablog.com,rixCloud +DOMAIN-SUFFIX,myspace.com,rixCloud +DOMAIN-SUFFIX,naacoalition.org,rixCloud +DOMAIN-SUFFIX,nabble.com,rixCloud +DOMAIN-SUFFIX,naitik.net,rixCloud +DOMAIN-SUFFIX,natado.com,rixCloud +DOMAIN-SUFFIX,navicat.com,rixCloud +DOMAIN-SUFFIX,navigeaters.com,rixCloud +DOMAIN-SUFFIX,ncol.com,rixCloud +DOMAIN-SUFFIX,ned.org,rixCloud +DOMAIN-SUFFIX,neighborhoodr.com,rixCloud +DOMAIN-SUFFIX,nekoslovakia.net,rixCloud +DOMAIN-SUFFIX,net.picidae.net,rixCloud +DOMAIN-SUFFIX,netcolony.com,rixCloud +DOMAIN-SUFFIX,netlog.com,rixCloud +DOMAIN-SUFFIX,network54.com,rixCloud +DOMAIN-SUFFIX,networkedblogs.com,rixCloud +DOMAIN-SUFFIX,newgrounds.com,rixCloud +DOMAIN-SUFFIX,newsminer.com,rixCloud +DOMAIN-SUFFIX,newspeak.cc,rixCloud +DOMAIN-SUFFIX,newyorktimes.com,rixCloud +DOMAIN-SUFFIX,nexton-net.jp,rixCloud +DOMAIN-SUFFIX,ngrok.com,rixCloud +DOMAIN-SUFFIX,nicovideo.jp,rixCloud +DOMAIN-SUFFIX,ning.com,rixCloud +DOMAIN-SUFFIX,nintendium.com,rixCloud +DOMAIN-SUFFIX,nobelprize.org,rixCloud +DOMAIN-SUFFIX,nintendo.com,rixCloud +DOMAIN-SUFFIX,nobodycanstop.us,rixCloud +DOMAIN-SUFFIX,nodesnoop.com,rixCloud +DOMAIN-SUFFIX,noobbox.com,rixCloud +DOMAIN-SUFFIX,nps.gov,rixCloud +DOMAIN-SUFFIX,nrk.no,rixCloud +DOMAIN-SUFFIX,ntd.tv,rixCloud +DOMAIN-SUFFIX,ntdtv.org,rixCloud +DOMAIN-SUFFIX,ntdtv.ru,rixCloud +DOMAIN-SUFFIX,nuexpo.com,rixCloud +DOMAIN-SUFFIX,nurgo-software.com,rixCloud +DOMAIN-SUFFIX,nuzzel.com,rixCloud +DOMAIN-SUFFIX,nyt.com,rixCloud +DOMAIN-SUFFIX,nytco.com,rixCloud +DOMAIN-SUFFIX,nytimes.com,rixCloud +DOMAIN-SUFFIX,offbeatchina.com,rixCloud +DOMAIN-SUFFIX,office365.com,rixCloud +DOMAIN-SUFFIX,ogaoga.org,rixCloud +DOMAIN-SUFFIX,oizoblog.com,rixCloud +DOMAIN-SUFFIX,okayfreedom.com,rixCloud +DOMAIN-SUFFIX,old-cat.net,rixCloud +DOMAIN-SUFFIX,oldjav.org,rixCloud +DOMAIN-SUFFIX,olumpo.com,rixCloud +DOMAIN-SUFFIX,omgili.com,rixCloud +DOMAIN-SUFFIX,omy.sg,rixCloud +DOMAIN-SUFFIX,onedrive.live.com,rixCloud +DOMAIN-SUFFIX,onion.city,rixCloud +DOMAIN-SUFFIX,onlylady.cn,rixCloud +DOMAIN-SUFFIX,onmoon.net,rixCloud +DOMAIN-SUFFIX,opendemocracy.net,rixCloud +DOMAIN-SUFFIX,openid.net,rixCloud +DOMAIN-SUFFIX,openleaks.org,rixCloud +DOMAIN-SUFFIX,openwrt.org,rixCloud +DOMAIN-SUFFIX,opera.com,rixCloud +DOMAIN-SUFFIX,opera-mini.net,rixCloud +DOMAIN-SUFFIX,orzdream.com,rixCloud +DOMAIN-SUFFIX,orzistic.org,rixCloud +DOMAIN-SUFFIX,osmdroid.net,rixCloud +DOMAIN-SUFFIX,over-blog.com,rixCloud +DOMAIN-SUFFIX,ow.ly,rixCloud +DOMAIN-SUFFIX,owind.com,rixCloud +DOMAIN-SUFFIX,owl.li,rixCloud +DOMAIN-SUFFIX,pacificpoker.com,rixCloud +DOMAIN-SUFFIX,pagodabox.com,rixCloud +DOMAIN-SUFFIX,palm.com,rixCloud +DOMAIN-SUFFIX,pandora.com,rixCloud +DOMAIN-SUFFIX,paper.li,rixCloud +DOMAIN-SUFFIX,paper-replika.com,rixCloud +DOMAIN-SUFFIX,parade.com,rixCloud +DOMAIN-SUFFIX,parislemon.com,rixCloud +DOMAIN-SUFFIX,pastie.org,rixCloud +DOMAIN-SUFFIX,path.com,rixCloud +DOMAIN-SUFFIX,pbs.org,rixCloud +DOMAIN-SUFFIX,pbwiki.com,rixCloud +DOMAIN-SUFFIX,pbworks.com,rixCloud +DOMAIN-SUFFIX,pbxes.com,rixCloud +DOMAIN-SUFFIX,pbxes.org,rixCloud +DOMAIN-SUFFIX,pcdiscuss.com,rixCloud +DOMAIN-SUFFIX,peacefire.org,rixCloud +DOMAIN-SUFFIX,pekingduck.org,rixCloud +DOMAIN-SUFFIX,pentalogic.net,rixCloud +DOMAIN-SUFFIX,perlhowto.com,rixCloud +DOMAIN-SUFFIX,pervertedmilfs.com,rixCloud +DOMAIN-SUFFIX,philly.com,rixCloud +DOMAIN-SUFFIX,phonegap.com,rixCloud +DOMAIN-SUFFIX,photofocus.com,rixCloud +DOMAIN-SUFFIX,phuquocservices.com,rixCloud +DOMAIN-SUFFIX,picidae.net,rixCloud +DOMAIN-SUFFIX,picturesocial.com,rixCloud +DOMAIN-SUFFIX,pilotmoon.com,rixCloud +DOMAIN-SUFFIX,pinboard.in,rixCloud +DOMAIN-SUFFIX,ping.fm,rixCloud +DOMAIN-SUFFIX,pinimg.com,rixCloud +DOMAIN-SUFFIX,pinterest.com,rixCloud +DOMAIN-SUFFIX,pinoy-n.com,rixCloud +DOMAIN-SUFFIX,pixelqi.com,rixCloud +DOMAIN-SUFFIX,placemix.com,rixCloud +DOMAIN-SUFFIX,planetsuzy.org,rixCloud +DOMAIN-SUFFIX,playboy.com,rixCloud +DOMAIN-SUFFIX,plixi.com,rixCloud +DOMAIN-SUFFIX,plus28.com,rixCloud +DOMAIN-SUFFIX,plusbb.com,rixCloud +DOMAIN-SUFFIX,pokerstars.com,rixCloud +DOMAIN-SUFFIX,popularpages.net,rixCloud +DOMAIN-SUFFIX,pornhd.com,rixCloud +DOMAIN-SUFFIX,pornrapidshare.com,rixCloud +DOMAIN-SUFFIX,pornstar.com,rixCloud +DOMAIN-SUFFIX,pornstarclub.com,rixCloud +DOMAIN-SUFFIX,porntube.com,rixCloud +DOMAIN-SUFFIX,pose.com,rixCloud +DOMAIN-SUFFIX,post.ly,rixCloud +DOMAIN-SUFFIX,posterous.com,rixCloud +DOMAIN-SUFFIX,power.com,rixCloud +DOMAIN-SUFFIX,powercx.com,rixCloud +DOMAIN-SUFFIX,powerpointninja.com,rixCloud +DOMAIN-SUFFIX,pp.ru,rixCloud +DOMAIN-SUFFIX,presentationzen.com,rixCloud +DOMAIN-SUFFIX,privacybox.de,rixCloud +DOMAIN-SUFFIX,procopytips.com,rixCloud +DOMAIN-SUFFIX,prosiben.de,rixCloud +DOMAIN-SUFFIX,provideocoalition.com,rixCloud +DOMAIN-SUFFIX,psiphon.ca,rixCloud +DOMAIN-SUFFIX,puffinbrowser.com,rixCloud +DOMAIN-SUFFIX,pure18.com,rixCloud +DOMAIN-SUFFIX,pureconcepts.net,rixCloud +DOMAIN-SUFFIX,putlocker.com,rixCloud +DOMAIN-SUFFIX,putty.org,rixCloud +DOMAIN-SUFFIX,pypa.io,rixCloud +DOMAIN-SUFFIX,python.com,rixCloud +DOMAIN-SUFFIX,python.org,rixCloud +DOMAIN-SUFFIX,qtrac.eu,rixCloud +DOMAIN-SUFFIX,qx.net,rixCloud +DOMAIN-SUFFIX,radio.com,rixCloud +DOMAIN-SUFFIX,radioaustralia.net.au,rixCloud +DOMAIN-SUFFIX,radiotime.com,rixCloud +DOMAIN-SUFFIX,radiovaticana.org,rixCloud +DOMAIN-SUFFIX,radiovncr.com,rixCloud +DOMAIN-SUFFIX,ranyunfei.com,rixCloud +DOMAIN-SUFFIX,rapidgator.net,rixCloud +DOMAIN-SUFFIX,realitykings.com,rixCloud +DOMAIN-SUFFIX,recaptcha.net,rixCloud +DOMAIN-SUFFIX,reflectivecode.com,rixCloud +DOMAIN-SUFFIX,renyurenquan.org,rixCloud +DOMAIN-SUFFIX,rerouted.org,rixCloud +DOMAIN-SUFFIX,retweeteffect.com,rixCloud +DOMAIN-SUFFIX,retweetist.com,rixCloud +DOMAIN-SUFFIX,retweetrank.com,rixCloud +DOMAIN-SUFFIX,reuters.com,rixCloud +DOMAIN-SUFFIX,revleft.com,rixCloud +DOMAIN-SUFFIX,rfa.org,rixCloud +DOMAIN-SUFFIX,rfamobile.org,rixCloud +DOMAIN-SUFFIX,rferl.org,rixCloud +DOMAIN-SUFFIX,rfi.fr,rixCloud +DOMAIN-SUFFIX,rfi.my,rixCloud +DOMAIN-SUFFIX,rhcloud.com,rixCloud +DOMAIN-SUFFIX,riku.me,rixCloud +DOMAIN-SUFFIX,rnw.org,rixCloud +DOMAIN-SUFFIX,robtex.com,rixCloud +DOMAIN-SUFFIX,rockmelt.com,rixCloud +DOMAIN-SUFFIX,rsf.org,rixCloud +DOMAIN-SUFFIX,rsf-chinese.org,rixCloud +DOMAIN-SUFFIX,ruanyifeng.com,rixCloud +DOMAIN-SUFFIX,rutube.ru,rixCloud +DOMAIN-SUFFIX,rxhj.net,rixCloud +DOMAIN-SUFFIX,s135.com,rixCloud +DOMAIN-SUFFIX,sacom.hk,rixCloud +DOMAIN-SUFFIX,s-a-d.de,rixCloud +DOMAIN-SUFFIX,salvation.org.hk,rixCloud +DOMAIN-SUFFIX,sammyjs.org,rixCloud +DOMAIN-SUFFIX,samsoff.es,rixCloud +DOMAIN-SUFFIX,savemedia.com,rixCloud +DOMAIN-SUFFIX,savevid.com,rixCloud +DOMAIN-SUFFIX,saveyoutube.com,rixCloud +DOMAIN-SUFFIX,sbs.com.au,rixCloud +DOMAIN-SUFFIX,scdn.co,rixCloud +DOMAIN-SUFFIX,scmp.com,rixCloud +DOMAIN-SUFFIX,scmpchinese.com,rixCloud +DOMAIN-SUFFIX,scribd.com,rixCloud +DOMAIN-SUFFIX,scriptspot.com,rixCloud +DOMAIN-SUFFIX,search.com,rixCloud +DOMAIN-SUFFIX,seeedstudio.com,rixCloud +DOMAIN-SUFFIX,seesmic.com,rixCloud +DOMAIN-SUFFIX,seezone.net,rixCloud +DOMAIN-SUFFIX,segment.io,rixCloud +DOMAIN-SUFFIX,sendoid.com,rixCloud +DOMAIN-SUFFIX,sendspace.com,rixCloud +DOMAIN-SUFFIX,seraph.me,rixCloud +DOMAIN-SUFFIX,sethwklein.net,rixCloud +DOMAIN-SUFFIX,sfileydy.com,rixCloud +DOMAIN-SUFFIX,shapeservices.com,rixCloud +DOMAIN-SUFFIX,sharkdolphin.com,rixCloud +DOMAIN-SUFFIX,shaunthesheep.com,rixCloud +DOMAIN-SUFFIX,sheikyermami.com,rixCloud +DOMAIN-SUFFIX,shellmix.com,rixCloud +DOMAIN-SUFFIX,shenyunperformingarts.org,rixCloud +DOMAIN-SUFFIX,shinychan.com,rixCloud +DOMAIN-SUFFIX,shizhao.org,rixCloud +DOMAIN-SUFFIX,shodanhq.com,rixCloud +DOMAIN-SUFFIX,shopping.com,rixCloud +DOMAIN-SUFFIX,slack-edge.com,rixCloud +DOMAIN-SUFFIX,slack.com,rixCloud +DOMAIN-SUFFIX,simpleproductivityblog.com,rixCloud +DOMAIN-SUFFIX,sina.com,rixCloud +DOMAIN-SUFFIX,sina.com.hk,rixCloud +DOMAIN-SUFFIX,sina.com.tw,rixCloud +DOMAIN-SUFFIX,sinocism.com,rixCloud +DOMAIN-SUFFIX,site90.net,rixCloud +DOMAIN-SUFFIX,sitemaps.org,rixCloud +DOMAIN-SUFFIX,sitetag.us,rixCloud +DOMAIN-SUFFIX,skimtube.com,rixCloud +DOMAIN-SUFFIX,skyhighpremium.com,rixCloud +DOMAIN-SUFFIX,skype.com,rixCloud +DOMAIN-SUFFIX,slavasoft.com,rixCloud +DOMAIN-SUFFIX,slheng.com,rixCloud +DOMAIN-SUFFIX,slideshare.net,rixCloud +DOMAIN-SUFFIX,smartmailcloud.com,rixCloud +DOMAIN-SUFFIX,smhric.org,rixCloud +DOMAIN-SUFFIX,sneakme.net,rixCloud +DOMAIN-SUFFIX,sobees.com,rixCloud +DOMAIN-SUFFIX,socialwhale.com,rixCloud +DOMAIN-SUFFIX,sogoo.org,rixCloud +DOMAIN-SUFFIX,soh.tw,rixCloud +DOMAIN-SUFFIX,solozorro.tk,rixCloud +DOMAIN-SUFFIX,so-news.com,rixCloud +DOMAIN-SUFFIX,sopcast.com,rixCloud +DOMAIN-SUFFIX,sopcast.org,rixCloud +DOMAIN-SUFFIX,sorting-algorithms.com,rixCloud +DOMAIN-SUFFIX,soumo.info,rixCloud +DOMAIN-SUFFIX,soundcloud.com,rixCloud +DOMAIN-SUFFIX,soup.io,rixCloud +DOMAIN-SUFFIX,soupofmedia.com,rixCloud +DOMAIN-SUFFIX,sourceforge.net,rixCloud +DOMAIN-SUFFIX,sowiki.net,rixCloud +DOMAIN-SUFFIX,speedpluss.org,rixCloud +DOMAIN-SUFFIX,sproutcore.com,rixCloud +DOMAIN-SUFFIX,squarespace.com,rixCloud +DOMAIN-SUFFIX,sstatic.net,rixCloud +DOMAIN-SUFFIX,stackfile.com,rixCloud +DOMAIN-SUFFIX,stackoverflow.com,rixCloud +DOMAIN-SUFFIX,state.gov,rixCloud +DOMAIN-SUFFIX,steel-storm.com,rixCloud +DOMAIN-SUFFIX,stickam.com,rixCloud +DOMAIN-SUFFIX,stonegames.net,rixCloud +DOMAIN-SUFFIX,storagenewsletter.com,rixCloud +DOMAIN-SUFFIX,stoweboyd.com,rixCloud +DOMAIN-SUFFIX,streamingthe.net,rixCloud +DOMAIN-SUFFIX,streetvoice.com,rixCloud +DOMAIN-SUFFIX,stupidvideos.com,rixCloud +DOMAIN-SUFFIX,subito.it,rixCloud +DOMAIN-SUFFIX,sugarsync.com,rixCloud +DOMAIN-SUFFIX,surfeasy.com.au,rixCloud +DOMAIN-SUFFIX,surrenderat20.net,rixCloud +DOMAIN-SUFFIX,swagbucks.com,rixCloud +DOMAIN-SUFFIX,swift-tools.net,rixCloud +DOMAIN-SUFFIX,sydneytoday.com,rixCloud +DOMAIN-SUFFIX,syncback.com,rixCloud +DOMAIN-SUFFIX,sysadmin1138.net,rixCloud +DOMAIN-SUFFIX,sysresccd.org,rixCloud +DOMAIN-SUFFIX,syx86.cn,rixCloud +DOMAIN-SUFFIX,t.co,rixCloud +DOMAIN-SUFFIX,t35.com,rixCloud +DOMAIN-SUFFIX,t66y.com,rixCloud +DOMAIN-SUFFIX,taa-usa.org,rixCloud +DOMAIN-SUFFIX,tafaward.com,rixCloud +DOMAIN-SUFFIX,tagwalk.com,rixCloud +DOMAIN-SUFFIX,taiwantt.org.tw,rixCloud +DOMAIN-SUFFIX,taiwanyes.com,rixCloud +DOMAIN-SUFFIX,tampabay.com,rixCloud +DOMAIN-SUFFIX,taolun.info,rixCloud +DOMAIN-SUFFIX,taragana.com,rixCloud +DOMAIN-SUFFIX,target.com,rixCloud +DOMAIN-SUFFIX,taweet.com,rixCloud +DOMAIN-SUFFIX,teamseesmic.com,rixCloud +DOMAIN-SUFFIX,techparaiso.com,rixCloud +DOMAIN-SUFFIX,teck.in,rixCloud +DOMAIN-SUFFIX,telecomspace.com,rixCloud +DOMAIN-SUFFIX,telegram.org,rixCloud +DOMAIN-SUFFIX,theampfactory.com,rixCloud +DOMAIN-SUFFIX,theappleblog.com,rixCloud +DOMAIN-SUFFIX,thebcomplex.com,rixCloud +DOMAIN-SUFFIX,theblemish.com,rixCloud +DOMAIN-SUFFIX,thebodyshop-usa.com,rixCloud +DOMAIN-SUFFIX,thechinabeat.org,rixCloud +DOMAIN-SUFFIX,thedailywh.at,rixCloud +DOMAIN-SUFFIX,thedieline.com,rixCloud +DOMAIN-SUFFIX,theguardian.co,rixCloud +DOMAIN-SUFFIX,theguardian.com,rixCloud +DOMAIN-SUFFIX,thehun.net,rixCloud +DOMAIN-SUFFIX,thepiratebay.org,rixCloud +DOMAIN-SUFFIX,thepiratebay.se,rixCloud +DOMAIN-SUFFIX,thesartorialist.com,rixCloud +DOMAIN-SUFFIX,thisiswhyyouarefat.com,rixCloud +DOMAIN-SUFFIX,thkphoto.com,rixCloud +DOMAIN-SUFFIX,thomasbernhard.org,rixCloud +DOMAIN-SUFFIX,tidyread.com,rixCloud +DOMAIN-SUFFIX,tiffanyarment.com,rixCloud +DOMAIN-SUFFIX,time.com,rixCloud +DOMAIN-SUFFIX,tiney.com,rixCloud +DOMAIN-SUFFIX,tinychat.com,rixCloud +DOMAIN-SUFFIX,tinypaste.com,rixCloud +DOMAIN-SUFFIX,ttvnw.net,rixCloud +DOMAIN-SUFFIX,tinypic.com,rixCloud +DOMAIN-SUFFIX,tinyurl.com,rixCloud +DOMAIN-SUFFIX,tiscali.it,rixCloud +DOMAIN-SUFFIX,tistory.com,rixCloud +DOMAIN-SUFFIX,titanpad.com,rixCloud +DOMAIN-SUFFIX,tjholowaychuk.com,rixCloud +DOMAIN-SUFFIX,tmagazine.com,rixCloud +DOMAIN-SUFFIX,tmi.me,rixCloud +DOMAIN-SUFFIX,tomayko.com,rixCloud +DOMAIN-SUFFIX,toodoc.com,rixCloud +DOMAIN-SUFFIX,toonel.net,rixCloud +DOMAIN-SUFFIX,topify.com,rixCloud +DOMAIN-SUFFIX,topshare.us,rixCloud +DOMAIN-SUFFIX,topshareware.com,rixCloud +DOMAIN-SUFFIX,topstyle4.com,rixCloud +DOMAIN-SUFFIX,torproject.org,rixCloud +DOMAIN-SUFFIX,touch99.com,rixCloud +DOMAIN-SUFFIX,treemall.com.tw,rixCloud +DOMAIN-SUFFIX,tripod.com,rixCloud +DOMAIN-SUFFIX,trustedbi.com,rixCloud +DOMAIN-SUFFIX,truveo.com,rixCloud +DOMAIN-SUFFIX,tsemtulku.com,rixCloud +DOMAIN-SUFFIX,tsquare.tv,rixCloud +DOMAIN-SUFFIX,tt-rss.org,rixCloud +DOMAIN-SUFFIX,tumblr.com,rixCloud +DOMAIN-SUFFIX,tumblweed.org,rixCloud +DOMAIN-SUFFIX,tunein.com,rixCloud +DOMAIN-SUFFIX,turbobit.net,rixCloud +DOMAIN-SUFFIX,tv.com,rixCloud +DOMAIN-SUFFIX,tvb.com,rixCloud +DOMAIN-SUFFIX,tvunetworks.com,rixCloud +DOMAIN-SUFFIX,twapperkeeper.com,rixCloud +DOMAIN-SUFFIX,tweetbackup.com,rixCloud +DOMAIN-SUFFIX,tweetboard.com,rixCloud +DOMAIN-SUFFIX,tweetdeck.com,rixCloud +DOMAIN-SUFFIX,tweetedtimes.com,rixCloud +DOMAIN-SUFFIX,tweetmeme.com,rixCloud +DOMAIN-SUFFIX,tweetree.com,rixCloud +DOMAIN-SUFFIX,tweetwally.com,rixCloud +DOMAIN-SUFFIX,twibbon.com,rixCloud +DOMAIN-SUFFIX,twiffo.com,rixCloud +DOMAIN-SUFFIX,twimg.com,rixCloud +DOMAIN-SUFFIX,twindexx.com,rixCloud +DOMAIN-SUFFIX,twistar.cc,rixCloud +DOMAIN-SUFFIX,twister.net.co,rixCloud +DOMAIN-SUFFIX,twistory.net,rixCloud +DOMAIN-SUFFIX,twit2d.com,rixCloud +DOMAIN-SUFFIX,twitch.tv,rixCloud +DOMAIN-SUFFIX,twitlonger.com,rixCloud +DOMAIN-SUFFIX,twitoaster.com,rixCloud +DOMAIN-SUFFIX,twitpic.com,rixCloud +DOMAIN-SUFFIX,twittbot.net,rixCloud +DOMAIN-SUFFIX,twitter.com,rixCloud +DOMAIN-SUFFIX,twitter.jp,rixCloud +DOMAIN-SUFFIX,twitthat.com,rixCloud +DOMAIN-SUFFIX,twitturly.com,rixCloud +DOMAIN-SUFFIX,twitzap.com,rixCloud +DOMAIN-SUFFIX,twiyia.com,rixCloud +DOMAIN-SUFFIX,twstar.net,rixCloud +DOMAIN-SUFFIX,twt.fm,rixCloud +DOMAIN-SUFFIX,twtkr.com,rixCloud +DOMAIN-SUFFIX,twtrland.com,rixCloud +DOMAIN-SUFFIX,twttr.com,rixCloud +DOMAIN-SUFFIX,tynsoe.org,rixCloud +DOMAIN-SUFFIX,typepad.com,rixCloud +DOMAIN-SUFFIX,ubnt.com,rixCloud +DOMAIN-SUFFIX,uderzo.it,rixCloud +DOMAIN-SUFFIX,ugo.com,rixCloud +DOMAIN-SUFFIX,uhrp.org,rixCloud +DOMAIN-SUFFIX,uighur.nl,rixCloud +DOMAIN-SUFFIX,uighurbiz.net,rixCloud +DOMAIN-SUFFIX,uncyclomedia.org,rixCloud +DOMAIN-SUFFIX,uncyclopedia.info,rixCloud +DOMAIN-SUFFIX,uncyclopedia.tw,rixCloud +DOMAIN-SUFFIX,uni.cc,rixCloud +DOMAIN-SUFFIX,unicode.org,rixCloud +DOMAIN-SUFFIX,unpo.org,rixCloud +DOMAIN-SUFFIX,uocn.org,rixCloud +DOMAIN-SUFFIX,updatestar.com,rixCloud +DOMAIN-SUFFIX,uploaded.net,rixCloud +DOMAIN-SUFFIX,uploaded.to,rixCloud +DOMAIN-SUFFIX,urlparser.com,rixCloud +DOMAIN-SUFFIX,us.to,rixCloud +DOMAIN-SUFFIX,usa.gov,rixCloud +DOMAIN-SUFFIX,usgs.gov,rixCloud +DOMAIN-SUFFIX,ustream.tv,rixCloud +DOMAIN-SUFFIX,uushare.com,rixCloud +DOMAIN-SUFFIX,uwants.com,rixCloud +DOMAIN-SUFFIX,uyghurcongress.org,rixCloud +DOMAIN-SUFFIX,uyghurensemble.co.uk,rixCloud +DOMAIN-SUFFIX,uyghurpen.org,rixCloud +DOMAIN-SUFFIX,vaayoo.com,rixCloud +DOMAIN-SUFFIX,vcf-online.org,rixCloud +DOMAIN-SUFFIX,velkaepocha.sk,rixCloud +DOMAIN-SUFFIX,veoh.com,rixCloud +DOMAIN-SUFFIX,verizon.net,rixCloud +DOMAIN-SUFFIX,viber.com,rixCloud +DOMAIN-SUFFIX,videomo.com,rixCloud +DOMAIN-SUFFIX,vidoemo.com,rixCloud +DOMAIN-SUFFIX,viki.com,rixCloud +DOMAIN-SUFFIX,vimeo.com,rixCloud +DOMAIN-SUFFIX,vimeocdn.com,rixCloud +DOMAIN-SUFFIX,vimgolf.com,rixCloud +DOMAIN-SUFFIX,vimperator.org,rixCloud +DOMAIN-SUFFIX,vinniev.com,rixCloud +DOMAIN-SUFFIX,vivthomas.com,rixCloud +DOMAIN-SUFFIX,vmixcore.com,rixCloud +DOMAIN-SUFFIX,voa.gov,rixCloud +DOMAIN-SUFFIX,voacantonese.com,rixCloud +DOMAIN-SUFFIX,voachinese.com,rixCloud +DOMAIN-SUFFIX,voanews.com,rixCloud +DOMAIN-SUFFIX,vocn.tv,rixCloud +DOMAIN-SUFFIX,vooplayer.com,rixCloud +DOMAIN-SUFFIX,voy.com,rixCloud +DOMAIN-SUFFIX,w.org,rixCloud +DOMAIN-SUFFIX,w3.org,rixCloud +DOMAIN-SUFFIX,w3schools.com,rixCloud +DOMAIN-SUFFIX,waffle1999.com,rixCloud +DOMAIN-SUFFIX,wallhaven.cc,rixCloud +DOMAIN-SUFFIX,wallpapercasa.com,rixCloud +DOMAIN-SUFFIX,wanderinghorse.net,rixCloud +DOMAIN-SUFFIX,washeng.net,rixCloud +DOMAIN-SUFFIX,wattpad.com,rixCloud +DOMAIN-SUFFIX,wav.tv,rixCloud +DOMAIN-SUFFIX,web2project.net,rixCloud +DOMAIN-SUFFIX,webfee.tk,rixCloud +DOMAIN-SUFFIX,weblagu.com,rixCloud +DOMAIN-SUFFIX,webs.com,rixCloud +DOMAIN-SUFFIX,webshots.com,rixCloud +DOMAIN-SUFFIX,websitepulse.com,rixCloud +DOMAIN-SUFFIX,websnapr.com,rixCloud +DOMAIN-SUFFIX,webwarper.net,rixCloud +DOMAIN-SUFFIX,webworkerdaily.com,rixCloud +DOMAIN-SUFFIX,wepn.info,rixCloud +DOMAIN-SUFFIX,whatblocked.com,rixCloud +DOMAIN-SUFFIX,whippedass.com,rixCloud +DOMAIN-SUFFIX,whydidyoubuymethat.com,rixCloud +DOMAIN-SUFFIX,wikia.com,rixCloud +DOMAIN-SUFFIX,wikibooks.org,rixCloud +DOMAIN-SUFFIX,wikileaks.ch,rixCloud +DOMAIN-SUFFIX,wikileaks.org,rixCloud +DOMAIN-SUFFIX,wikileaks.pl,rixCloud +DOMAIN-SUFFIX,wikilivres.info,rixCloud +DOMAIN-SUFFIX,wikimapia.org,rixCloud +DOMAIN-SUFFIX,wikimedia.org,rixCloud +DOMAIN-SUFFIX,wikinews.org,rixCloud +DOMAIN-SUFFIX,wikipedia.com,rixCloud +DOMAIN-SUFFIX,wikipedia.org,rixCloud +DOMAIN-SUFFIX,wikisource.org,rixCloud +DOMAIN-SUFFIX,wikiwiki.jp,rixCloud +DOMAIN-SUFFIX,willw.net,rixCloud +DOMAIN-SUFFIX,wiredbytes.com,rixCloud +DOMAIN-SUFFIX,wiredpen.com,rixCloud +DOMAIN-SUFFIX,wireshark.org,rixCloud +DOMAIN-SUFFIX,wixoo.com,rixCloud +DOMAIN-SUFFIX,woeser.com,rixCloud +DOMAIN-SUFFIX,wordboner.com,rixCloud +DOMAIN-SUFFIX,wordpress.com,rixCloud +DOMAIN-SUFFIX,wordpress.org,rixCloud +DOMAIN-SUFFIX,wordproaudio.com,rixCloud +DOMAIN-SUFFIX,wordsandturds.com,rixCloud +DOMAIN-SUFFIX,workatruna.com,rixCloud +DOMAIN-SUFFIX,worldcat.org,rixCloud +DOMAIN-SUFFIX,worstthingieverate.com,rixCloud +DOMAIN-SUFFIX,wp.com,rixCloud +DOMAIN-SUFFIX,wpmudev.org,rixCloud +DOMAIN-SUFFIX,wwitv.com,rixCloud +DOMAIN-SUFFIX,www.w3schools.com,rixCloud +DOMAIN-SUFFIX,wzyboy.im,rixCloud +DOMAIN-SUFFIX,xcritic.com,rixCloud +DOMAIN-SUFFIX,xda-developers.com,rixCloud +DOMAIN-SUFFIX,xiaod.in,rixCloud +DOMAIN-SUFFIX,xing.com,rixCloud +DOMAIN-SUFFIX,xml-training-guide.com,rixCloud +DOMAIN-SUFFIX,xmusic.fm,rixCloud +DOMAIN-SUFFIX,xpdo.net,rixCloud +DOMAIN-SUFFIX,xpud.org,rixCloud +DOMAIN-SUFFIX,xthost.info,rixCloud +DOMAIN-SUFFIX,xvedios.com,rixCloud +DOMAIN-SUFFIX,x-wall.org,rixCloud +DOMAIN-SUFFIX,xxxx.com.au,rixCloud +DOMAIN-SUFFIX,yahoo.co.jp,rixCloud +DOMAIN-SUFFIX,yahoo.com,rixCloud +DOMAIN-SUFFIX,yahoo.com.hk,rixCloud +DOMAIN-SUFFIX,yahoo.com.tw,rixCloud +DOMAIN-SUFFIX,yandex.com,rixCloud +DOMAIN-SUFFIX,yandex.ru,rixCloud +DOMAIN-SUFFIX,yasni.co.uk,rixCloud +DOMAIN-SUFFIX,yeelou.com,rixCloud +DOMAIN-SUFFIX,yfrog.com,rixCloud +DOMAIN-SUFFIX,yidio.com,rixCloud +DOMAIN-SUFFIX,yogichen.org,rixCloud +DOMAIN-SUFFIX,youtu.be,rixCloud +DOMAIN-SUFFIX,youtube.com,rixCloud +DOMAIN-SUFFIX,youtubecn.com,rixCloud +DOMAIN-SUFFIX,ytimg.com,rixCloud +DOMAIN-SUFFIX,zarias.com,rixCloud +DOMAIN-SUFFIX,zattoo.com,rixCloud +DOMAIN-SUFFIX,zengjinyan.org,rixCloud +DOMAIN-SUFFIX,zeutch.com,rixCloud +DOMAIN-SUFFIX,zftpserver.com,rixCloud +DOMAIN-SUFFIX,zhongmeng.org,rixCloud +DOMAIN-SUFFIX,ziddu.com,rixCloud +DOMAIN-SUFFIX,zinio.com,rixCloud +DOMAIN-SUFFIX,ziplib.com,rixCloud +DOMAIN-SUFFIX,zoho.com,rixCloud +DOMAIN-SUFFIX,zomobo.net,rixCloud +DOMAIN-SUFFIX,zonble.net,rixCloud +DOMAIN-SUFFIX,zootool.com,rixCloud +DOMAIN-SUFFIX,zoozle.net,rixCloud +DOMAIN-SUFFIX,zyan.cc,rixCloud +# DIRECT +DOMAIN-SUFFIX,126.net,DIRECT +DOMAIN-SUFFIX,12306.com,DIRECT +DOMAIN-SUFFIX,163.com,DIRECT +DOMAIN-SUFFIX,360.com,DIRECT +DOMAIN-SUFFIX,36kr.com,DIRECT +DOMAIN-SUFFIX,360buyimg.com,DIRECT +DOMAIN-SUFFIX,52pojie.cn,DIRECT +DOMAIN-SUFFIX,acfun.tv,DIRECT +DOMAIN-SUFFIX,acgvideo.com,DIRECT +DOMAIN,amazon.cn,DIRECT +DOMAIN,amazon.com,DIRECT +DOMAIN-SUFFIX,aixifan.com,DIRECT +DOMAIN-SUFFIX,alicdn.com,DIRECT +DOMAIN-SUFFIX,amap.com,DIRECT +DOMAIN,apt.cydia.com,DIRECT +DOMAIN,apt.feng.com,DIRECT +DOMAIN-SUFFIX,bababian.com,DIRECT +DOMAIN-SUFFIX,bdstatic.com,DIRECT +DOMAIN-SUFFIX,bilibili.com,DIRECT +DOMAIN-SUFFIX,bing.com,DIRECT +DOMAIN-SUFFIX,caiyunapp.com,DIRECT +DOMAIN-SUFFIX,ccgslb.com,DIRECT +DOMAIN-SUFFIX,crashlytics.com,DIRECT +DOMAIN-SUFFIX,ccgslb.net,DIRECT +DOMAIN-SUFFIX,chinacache.net,DIRECT +DOMAIN-SUFFIX,clouddn.com,DIRECT +DOMAIN-SUFFIX,cloudmagic.com,DIRECT +DOMAIN-SUFFIX,cmfu.com,DIRECT +DOMAIN-SUFFIX,crashlytics.com,DIRECT +DOMAIN-SUFFIX,cnbeta.com,DIRECT +DOMAIN-SUFFIX,cnbetacdn.com,DIRECT +DOMAIN-SUFFIX,cdn-apple.com,DIRECT +DOMAIN-SUFFIX,dingtalk.com,DIRECT +DOMAIN-SUFFIX,douban.com,DIRECT +DOMAIN-SUFFIX,doubanio.com,DIRECT +DOMAIN-SUFFIX,duokan.com,DIRECT +DOMAIN-SUFFIX,dytt8.net,DIRECT +DOMAIN-SUFFIX,easou.com,DIRECT +DOMAIN-SUFFIX,edu.cn,DIRECT +DOMAIN-SUFFIX,fresh-ideas.cc,DIRECT +DOMAIN-SUFFIX,fir.im,DIRECT +DOMAIN,feng.com,DIRECT +DOMAIN-SUFFIX,frdic.com,DIRECT +DOMAIN-SUFFIX,geetest.com,DIRECT +DOMAIN-SUFFIX,godic.net,DIRECT +DOMAIN,font.fonts.googleapis.com,DIRECT +DOMAIN-SUFFIX,gtimg.com,DIRECT +DOMAIN-SUFFIX,haosou.com,DIRECT +DOMAIN-SUFFIX,hdslb.com,DIRECT +DOMAIN-SUFFIX,helpshift.com,DIRECT +DOMAIN,h5.qzone.qq.com,DIRECT +DOMAIN-SUFFIX,hdslb.net,DIRECT +DOMAIN,images-amazon.com,DIRECT +DOMAIN-SUFFIX,iciba.com,DIRECT +DOMAIN,iqiyi.com,DIRECT +DOMAIN,ifeng.com,DIRECT +DOMAIN-SUFFIX,ifengimg.com,DIRECT +DOMAIN-SUFFIX,interface.sina.cn,DIRECT +DOMAIN-SUFFIX,icon.55bbs.com,DIRECT +DOMAIN,iface2.iqiyi.com,DIRECT +DOMAIN-SUFFIX,iqshw.com,DIRECT +DOMAIN-SUFFIX,ipip.net,DIRECT +DOMAIN-SUFFIX,jiathis.com,DIRECT +DOMAIN-SUFFIX,jianshu.com,DIRECT +DOMAIN-SUFFIX,jianshu.io,DIRECT +DOMAIN-SUFFIX,jianshuapi.com,DIRECT +DOMAIN-SUFFIX,knewone.com,DIRECT +DOMAIN-SUFFIX,kaola.com,DIRECT +DOMAIN,letv.com,DIRECT +DOMAIN,lecloud.com,DIRECT +DOMAIN,letvcloud.com,DIRECT +DOMAIN-SUFFIX,luoo.net,DIRECT +DOMAIN-SUFFIX,mcgi.v.qq.com,DIRECT +DOMAIN-SUFFIX,microsoft.com,DIRECT +DOMAIN-SUFFIX,miaopai.com,DIRECT +DOMAIN-SUFFIX,moke.com,DIRECT +DOMAIN,m.baidu.com,DIRECT +DOMAIN-SUFFIX,myqcloud.com,DIRECT +DOMAIN-SUFFIX,netease.com,DIRECT +DOMAIN-SUFFIX,ourdvs.com,DIRECT +DOMAIN-SUFFIX,oschina.net,DIRECT +DOMAIN-SUFFIX,pstatp.com,DIRECT +DOMAIN,pptv.com,DIRECT +DOMAIN-SUFFIX,panda.tv,DIRECT +DOMAIN-KEYWORD,paypal,DIRECT +DOMAIN-SUFFIX,pdim.gs,DIRECT +DOMAIN-SUFFIX,qbox.me,DIRECT +DOMAIN-SUFFIX,qcloud.com,DIRECT +DOMAIN-SUFFIX,qdmm.com,DIRECT +DOMAIN-SUFFIX,qhimg.com,DIRECT +DOMAIN-SUFFIX,qiniucdn.com,DIRECT +DOMAIN-SUFFIX,qiniudn.com,DIRECT +DOMAIN-SUFFIX,qidian.com,DIRECT +DOMAIN,qiyi.com,DIRECT +DOMAIN,qq.com,DIRECT +DOMAIN-SUFFIX,qqurl.com,DIRECT +DOMAIN-SUFFIX,rrmj.tv,DIRECT +DOMAIN-SUFFIX,ruguoapp.com,DIRECT +DOMAIN,res.wx.qq.com,DIRECT +DOMAIN-SUFFIX,sinaapp.com,DIRECT +DOMAIN-SUFFIX,snwx.com,DIRECT +DOMAIN-SUFFIX,supercell.net,DIRECT +DOMAIN-SUFFIX,so.com,DIRECT +DOMAIN-SUFFIX,sogou.com,DIRECT +DOMAIN-SUFFIX,sinaimg.cn,DIRECT +DOMAIN-SUFFIX,sogoucdn.com,DIRECT +DOMAIN-SUFFIX,sr.symcd.com,DIRECT +DOMAIN,sohu.com,DIRECT +DOMAIN,sohu.com.cn,DIRECT +DOMAIN-SUFFIX,sjzhushou.com,DIRECT +DOMAIN-SUFFIX,soso.com,DIRECT +DOMAIN-SUFFIX,sspai.com,DIRECT +DOMAIN-SUFFIX,suning.com,DIRECT +DOMAIN-SUFFIX,taobaocdn.com,DIRECT +DOMAIN-SUFFIX,tenpay.com,DIRECT +DOMAIN-SUFFIX,tietuku.com,DIRECT +DOMAIN-SUFFIX,tmall.com,DIRECT +DOMAIN,tudou.com,DIRECT +DOMAIN-SUFFIX,upyun.com,DIRECT +DOMAIN-SUFFIX,upaiyun.com,DIRECT +DOMAIN-SUFFIX,uxengine.net,DIRECT +DOMAIN-SUFFIX,v2ex.co,DIRECT +DOMAIN-SUFFIX,v2ex.com,DIRECT +DOMAIN-SUFFIX,weibo.com,DIRECT +DOMAIN-SUFFIX,weiphone.net,DIRECT +DOMAIN,weibo.cn,DIRECT +DOMAIN-SUFFIX,weiphone.com,DIRECT +DOMAIN-SUFFIX,windowsupdate.com,DIRECT +DOMAIN-SUFFIX,xunlei.com,DIRECT +DOMAIN-SUFFIX,xitek.com,DIRECT +DOMAIN-SUFFIX,yhd.com,DIRECT +DOMAIN-SUFFIX,yinxiang.com,DIRECT +DOMAIN-SUFFIX,yixia.com,DIRECT +DOMAIN-SUFFIX,ykimg.com,DIRECT +DOMAIN,youku.com,DIRECT +DOMAIN,youdao.com,DIRECT +DOMAIN-SUFFIX,zeroxy.com,DIRECT +DOMAIN-SUFFIX,zealer.com,DIRECT +DOMAIN,www.zybang.com,DIRECT +DOMAIN-SUFFIX,zhihu.com,DIRECT +DOMAIN-SUFFIX,zhimg.com,DIRECT +# REJECT +DOMAIN-SUFFIX,00oo00.com,REJECT +DOMAIN-SUFFIX,0563d.com,REJECT +DOMAIN-SUFFIX,0311zs.net,REJECT +DOMAIN-SUFFIX,168kk.com,REJECT +DOMAIN-SUFFIX,13328888882.com,REJECT +DOMAIN-SUFFIX,1571537.com,REJECT +DOMAIN-SUFFIX,18tzx.com,REJECT +DOMAIN-SUFFIX,19950501.com,REJECT +DOMAIN-SUFFIX,168.it168.com,REJECT +DOMAIN-SUFFIX,2-80.cn,REJECT +DOMAIN-SUFFIX,200218.com,REJECT +DOMAIN-SUFFIX,37cs.com,REJECT +DOMAIN-SUFFIX,3apf.com,REJECT +DOMAIN-SUFFIX,50bang.org,REJECT +DOMAIN-SUFFIX,51.la,REJECT +DOMAIN-SUFFIX,5lyq.com,REJECT +DOMAIN-SUFFIX,51ads.com,REJECT +DOMAIN-SUFFIX,51yes.com,REJECT +DOMAIN-SUFFIX,5leji.com,REJECT +DOMAIN-SUFFIX,5style.net,REJECT +DOMAIN-SUFFIX,51vipedu.com,REJECT +DOMAIN-SUFFIX,5iwp.top,REJECT +DOMAIN-SUFFIX,5207470.com,REJECT +DOMAIN-SUFFIX,5334.com,REJECT +DOMAIN-SUFFIX,5y9nfpes.52pk.com,REJECT +DOMAIN-SUFFIX,56.itc.cn,REJECT +DOMAIN-SUFFIX,58wanwan.net,REJECT +DOMAIN-SUFFIX,6604.org,REJECT +DOMAIN-SUFFIX,6dad.com,REJECT +DOMAIN-SUFFIX,778669.com,REJECT +DOMAIN-SUFFIX,720in.com,REJECT +DOMAIN,7pulxu.z0.glb.cloudcdn.com,REJECT +DOMAIN-SUFFIX,801.tianya.cn,REJECT +DOMAIN-SUFFIX,8ox.cn,REJECT +DOMAIN-SUFFIX,8556.com,REJECT +DOMAIN-SUFFIX,86str.com,REJECT +DOMAIN-SUFFIX,88lmimg.com,REJECT +DOMAIN-SUFFIX,95105012.com,REJECT +DOMAIN-SUFFIX,990km.com,REJECT +DOMAIN-SUFFIX,ad-brix.com,REJECT +DOMAIN-SUFFIX,aac-dmp.cn,REJECT +DOMAIN-SUFFIX,adsame.com,REJECT +DOMAIN-SUFFIX,appcoachs.net,REJECT +DOMAIN,afp.alicdn.com,REJECT +DOMAIN,amdc.m.taobao.com,REJECT +DOMAIN-SUFFIX,adhouyi.com,REJECT +DOMAIN-SUFFIX,ad-stir.com,REJECT +DOMAIN-SUFFIX,ad.mobile.youku.com,REJECT +DOMAIN-SUFFIX,alllget.com,REJECT +DOMAIN,adcdn.hatena.ne.jp,REJECT +DOMAIN,adm.10jqka.com.cn,REJECT +DOMAIN,adcdn.goo.ne.jp,REJECT +DOMAIN-SUFFIX,ad-mapps.com,REJECT +DOMAIN-SUFFIX,atm.youku.com,REJECT +DOMAIN,apppv.zol.com.cn,REJECT +DOMAIN-SUFFIX,aiwuqi.com.cn,REJECT +DOMAIN-SUFFIX,admx.baixing.com,REJECT +DOMAIN-SUFFIX,adspod.cn,REJECT +DOMAIN-SUFFIX,akamaiedge.net,REJECT +DOMAIN,ai0.pub.funshion.com,REJECT +DOMAIN-SUFFIX,admartzone.com,REJECT +DOMAIN,atanx.alicdn.com,REJECT +DOMAIN,appstat.dfcfw.com,REJECT +DOMAIN,atanx2.alicdn.com,REJECT +DOMAIN,adinall.com,REJECT +DOMAIN-SUFFIX,admaster.com.cn,REJECT +DOMAIN-SUFFIX,acs86.com,REJECT +DOMAIN-SUFFIX,adchina.com,REJECT +DOMAIN-SUFFIX,ad2you.com,REJECT +DOMAIN-SUFFIX,agkn.com,REJECT +DOMAIN-SUFFIX,adpro.cn,REJECT +DOMAIN,ad.ximalaya.com,REJECT +DOMAIN,adse.ximalaya.com,REJECT +DOMAIN-SUFFIX,adcome.cn,REJECT +DOMAIN,adashx4yt.m.taobao.com,REJECT +DOMAIN,acs4baichuan.m.taobao.com,REJECT +DOMAIN,ad.thsi.cn,REJECT +DOMAIN,api.newad.ifeng.com,REJECT +DOMAIN,api.iapps.ifeng.com,REJECT +DOMAIN-SUFFIX,adinfuse.com,REJECT +DOMAIN-SUFFIX,afp.qiyi.com,REJECT +DOMAIN-SUFFIX,avazutracking.net,REJECT +DOMAIN-SUFFIX,admob.com,REJECT +DOMAIN-SUFFIX,adofthyear.com,REJECT +DOMAIN-SUFFIX,ads.linkedin.com,REJECT +DOMAIN,alog.umeng.co,REJECT +DOMAIN,alog.umeng.com,REJECT +DOMAIN,au.umeng.com,REJECT +DOMAIN-SUFFIX,adsage.cn,REJECT +DOMAIN-SUFFIX,adsage.com,REJECT +DOMAIN-SUFFIX,aiwen.cc,REJECT +DOMAIN-SUFFIX,agrantsem.com,REJECT +DOMAIN,adsh.m.taobao.com,REJECT +DOMAIN-SUFFIX,adjust.com,REJECT +DOMAIN-SUFFIX,adm.72zx.com,REJECT +DOMAIN-KEYWORD,adsmogo,REJECT +DOMAIN-SUFFIX,api.biligame.com,REJECT +DOMAIN,adash.m.taobao.com,REJECT +DOMAIN-SUFFIX,ads.mobclix.com,REJECT +DOMAIN-SUFFIX,adview.cn,REJECT +DOMAIN-SUFFIX,adwhirl.com,REJECT +DOMAIN,ads-fa.cdn.spotify.com,REJECT +DOMAIN-SUFFIX,adwo.com,REJECT +DOMAIN-SUFFIX,ad.sina.com.cn,REJECT +DOMAIN-SUFFIX,adm.leju.sina.com.cn,REJECT +DOMAIN-SUFFIX,atm.sina.com,REJECT +DOMAIN-SUFFIX,ad.unimhk.com,REJECT +DOMAIN,api.cupid.iqiyi.com,REJECT +DOMAIN-SUFFIX,aduu.cn,REJECT +DOMAIN-SUFFIX,advertising.com,REJECT +DOMAIN-SUFFIX,adxmi.com,REJECT +DOMAIN-SUFFIX,adxchina.cn,REJECT +DOMAIN-SUFFIX,appsflyer.com,REJECT +DOMAIN-SUFFIX,adzerk.net,REJECT +DOMAIN-SUFFIX,anquan.org,REJECT +DOMAIN-SUFFIX,appads.com,REJECT +DOMAIN,ad.qingting.fm,REJECT +DOMAIN,admgr.qingting.fm,REJECT +DOMAIN-SUFFIX,applifier.com,REJECT +DOMAIN,ads.service.kugou.com,REJECT +DOMAIN,apk.shouji.koowo.com,REJECT +DOMAIN-SUFFIX,alimama.cn,REJECT +DOMAIN-SUFFIX,alimama.com,REJECT +DOMAIN-SUFFIX,ad.api.3g.tudou.com,REJECT +DOMAIN-SUFFIX,adcontrol.tudou.com,REJECT +DOMAIN-SUFFIX,ad-survey.com,REJECT +DOMAIN-SUFFIX,ad-plus.cn,REJECT +DOMAIN-SUFFIX,adtg.widerplanet.com,REJECT +DOMAIN-SUFFIX,all.rising.com.cn,REJECT +DOMAIN-SUFFIX,agn.aty.sohu.com,REJECT +DOMAIN-SUFFIX,adplay.tudou.com,REJECT +DOMAIN-SUFFIX,analytics.techweb.com.cn,REJECT +DOMAIN,ark.letv.com,REJECT +DOMAIN-SUFFIX,analytics.163.com,REJECT +DOMAIN-SUFFIX,appsdt.com,REJECT +DOMAIN-SUFFIX,adcolony.com,REJECT +DOMAIN-SUFFIX,analytics.126.net,REJECT +DOMAIN-SUFFIX,adnxs.com,REJECT +DOMAIN-SUFFIX,api.amplitude.com,REJECT +DOMAIN-SUFFIX,analytics-union.xunlei.com,REJECT +DOMAIN-SUFFIX,applovin.com,REJECT +DOMAIN-SUFFIX,adnetworkperformance.com,REJECT +DOMAIN-SUFFIX,appintop.com,REJECT +DOMAIN,adeventtracker.spotify.com,REJECT +DOMAIN-SUFFIX,adgeo.163.com,REJECT +DOMAIN-SUFFIX,admaster.mobi,REJECT +DOMAIN-SUFFIX,allyes.com,REJECT +DOMAIN-SUFFIX,ad.api.3g.youku.com,REJECT +DOMAIN-SUFFIX,adm.dxpmedia.com,REJECT +DOMAIN-SUFFIX,api.m.baidu.com,REJECT +DOMAIN-SUFFIX,ad.xiaomi.com,REJECT +DOMAIN-SUFFIX,bs.da.hunantv.com,REJECT +DOMAIN-SUFFIX,bccyyc.com,REJECT +DOMAIN-SUFFIX,bbs2.chinaz.com,REJECT +DOMAIN-SUFFIX,bdj.tianya.cn,REJECT +DOMAIN-SUFFIX,beacon.sina.com.cn,REJECT +DOMAIN-SUFFIX,baidustatic.com,REJECT +DOMAIN-SUFFIX,baifendian.com,REJECT +DOMAIN-SUFFIX,boss.qzone.qq.com,REJECT +DOMAIN-SUFFIX,bossbj.com,REJECT +DOMAIN-SUFFIX,bricknet.com,REJECT +DOMAIN-SUFFIX,bluekai.com,REJECT +DOMAIN-SUFFIX,baoyatu.cc,REJECT +DOMAIN,bdcpro.techweb.com.cn,REJECT +DOMAIN-SUFFIX,brand.sogou.com,REJECT +DOMAIN-SUFFIX,bytedance.com,REJECT +DOMAIN-SUFFIX,baidu9635.com,REJECT +DOMAIN,bbsanalytics.weiphone.net,REJECT +DOMAIN,bduserlog.eastmoney.com,REJECT +DOMAIN-SUFFIX,bdjiaoben.wmxa.cn,REJECT +DOMAIN-SUFFIX,bdimg.cnkang.com,REJECT +DOMAIN-SUFFIX,betrad.com,REJECT +DOMAIN-SUFFIX,boardx.huanqiu.com,REJECT +DOMAIN-SUFFIX,bfdcdn.com,REJECT +DOMAIN-SUFFIX,baichuan.baidu.com,REJECT +DOMAIN-SUFFIX,bobo.163.com,REJECT +DOMAIN-SUFFIX,biddingos.com,REJECT +DOMAIN,bce.baidu.com,REJECT +DOMAIN-SUFFIX,biddingx.com,REJECT +DOMAIN,bam.nr-data.net,REJECT +DOMAIN-SUFFIX,bxb.oupeng.com,REJECT +DOMAIN-SUFFIX,cc.xtgreat.com,REJECT +DOMAIN-SUFFIX,ccs.ucweb.com,REJECT +DOMAIN-SUFFIX,cnzz.com,REJECT +DOMAIN,cms.laifeng.com,REJECT +DOMAIN-SUFFIX,cmcm.com,REJECT +DOMAIN-SUFFIX,cocounion.com,REJECT +DOMAIN-SUFFIX,cpro.baidu.com,REJECT +DOMAIN-SUFFIX,chartboost.com,REJECT +DOMAIN-SUFFIX,cpc.brand.sogou.com,REJECT +DOMAIN-SUFFIX,cms.laifeng.com,REJECT +DOMAIN-SUFFIX,coolnay.com,REJECT +DOMAIN-SUFFIX,coolaiy.com,REJECT +DOMAIN-SUFFIX,cbjslog.baidu.com,REJECT +DOMAIN-SUFFIX,cjlaoshi.com,REJECT +DOMAIN-SUFFIX,cl3.webterren.com,REJECT +DOMAIN-SUFFIX,cl.webterren.com,REJECT +DOMAIN-SUFFIX,ccxhd.net,REJECT +DOMAIN-SUFFIX,confirmation.gameloft.com,REJECT +DOMAIN-SUFFIX,comic.hanhande.com,REJECT +DOMAIN-SUFFIX,comic.52pk.com,REJECT +DOMAIN-SUFFIX,cmcore.com,REJECT +DOMAIN-SUFFIX,coremetrics.com,REJECT +DOMAIN-SUFFIX,cpro.qqtn.com,REJECT +DOMAIN-SUFFIX,c5.72zx.com,REJECT +DOMAIN-SUFFIX,cast.ra.icast.cn,REJECT +DOMAIN-SUFFIX,cps.laifeng.com,REJECT +DOMAIN-SUFFIX,cdn.zampdsp.com,REJECT +DOMAIN,cn.wifi.com,REJECT +DOMAIN,counter.kingsoft.com,REJECT +DOMAIN-SUFFIX,cpro.baidu.com.cn,REJECT +DOMAIN-SUFFIX,c.ns8d.com,REJECT +DOMAIN-SUFFIX,csbew.com,REJECT +DOMAIN-SUFFIX,c0563.com,REJECT +DOMAIN,c.l.qq.com,REJECT +DOMAIN-SUFFIX,cre.dp.sina.cn,REJECT +DOMAIN-SUFFIX,capp.simsimi.com,REJECT +DOMAIN-SUFFIX,ckm.iqiyi.com,REJECT +DOMAIN-SUFFIX,colorfulapp.com,REJECT +DOMAIN,click.tianyaui.com,REJECT +DOMAIN-SUFFIX,cywl5.com,REJECT +DOMAIN,collect.tianya.cn,REJECT +DOMAIN-SUFFIX,ctsywy.com,REJECT +DOMAIN-SUFFIX,cdjyk.net,REJECT +DOMAIN-SUFFIX,cpro.cr173.com,REJECT +DOMAIN-SUFFIX,counter.yesky.com,REJECT +DOMAIN-SUFFIX,cupid.iqiyi.com,REJECT +DOMAIN-SUFFIX,cnzz.net,REJECT +DOMAIN-SUFFIX,chinassp.cn,REJECT +DOMAIN-SUFFIX,cbjs.baidu.com,REJECT +DOMAIN-SUFFIX,c.youdao.com,REJECT +DOMAIN,crs.baidu.com,REJECT +DOMAIN-SUFFIX,domob.cn,REJECT +DOMAIN-SUFFIX,d7.sina.com.cn,REJECT +DOMAIN-SUFFIX,dmp.sina.cn,REJECT +DOMAIN-SUFFIX,d0.sina.com.cn,REJECT +DOMAIN-SUFFIX,deaohudong.com,REJECT +DOMAIN,data.vod.itc.cn,REJECT +DOMAIN,dj1.baidu.com,REJECT +DOMAIN,dxp.baidu.com,REJECT +DOMAIN-SUFFIX,d31qbv1cthcecs.cloudfront.net,REJECT +DOMAIN-SUFFIX,dxpmedia.com,REJECT +DOMAIN-SUFFIX,dsp.youdao.com,REJECT +DOMAIN-SUFFIX,dcads.sina.com.cn,REJECT +DOMAIN-SUFFIX,de.as.pptv.com,REJECT +DOMAIN,data.video.qiyi.com,REJECT +DOMAIN,dm.toutiao.com,REJECT +DOMAIN-SUFFIX,dap.gentags.net,REJECT +DOMAIN,dsp-impr2.youdao.com,REJECT +DOMAIN-SUFFIX,dd.wenwo.com,REJECT +DOMAIN,dol.tianya.cn,REJECT +DOMAIN,dev.dc.letv.com,REJECT +DOMAIN-SUFFIX,domob.com.cn,REJECT +DOMAIN-SUFFIX,dnspro.cn,REJECT +DOMAIN-SUFFIX,dongsi107.cn,REJECT +DOMAIN,d.kugou.com,REJECT +DOMAIN-SUFFIX,dmp.org.cn,REJECT +DOMAIN,datax.baidu.com,REJECT +DOMAIN-SUFFIX,domob.org,REJECT +DOMAIN-SUFFIX,dragonparking.com,REJECT +DOMAIN,d3rt1990lpmkn.cloudfront.com,REJECT +DOMAIN-SUFFIX,doubleclick.net,REJECT +DOMAIN-KEYWORD,duomeng,REJECT +DOMAIN-SUFFIX,duomeng.net,REJECT +DOMAIN-SUFFIX,domobcdn.com,REJECT +DOMAIN,dsp.fout.jp,REJECT +DOMAIN-SUFFIX,duomeng.org,REJECT +DOMAIN-SUFFIX,d1.sina.com.cn,REJECT +DOMAIN,dc.letv.com,REJECT +DOMAIN-SUFFIX,dsp-impr2.youdao.com,REJECT +DOMAIN-SUFFIX,eff.inte.sogou.com,REJECT +DOMAIN,eclick.baidu.com,REJECT +DOMAIN-SUFFIX,en.leturich.org,REJECT +DOMAIN-SUFFIX,ekeide.com,REJECT +DOMAIN-SUFFIX,economy.gmw.cn,REJECT +DOMAIN-SUFFIX,eiv.baidu.com,REJECT +DOMAIN,exp.3g.ifeng.com,REJECT +DOMAIN-SUFFIX,egouz.com,REJECT +DOMAIN-SUFFIX,easemob.com,REJECT +DOMAIN-SUFFIX,endsp.net,REJECT +DOMAIN,erebor.doban.com,REJECT +DOMAIN,entry.baidu.com,REJECT +DOMAIN-SUFFIX,findjapan.cn,REJECT +DOMAIN-SUFFIX,fastlylb.net,REJECT +DOMAIN-SUFFIX,fastapi.net,REJECT +DOMAIN-SUFFIX,fugetech.com,REJECT +DOMAIN-SUFFIX,file.market.xiaomi.com,REJECT +DOMAIN-SUFFIX,flurry.com,REJECT +DOMAIN-SUFFIX,feih.com.cn,REJECT +DOMAIN,file.ws.126.net,REJECT +DOMAIN,fsf.org,REJECT +DOMAIN,fz.letv.com,REJECT +DOMAIN-SUFFIX,forjs.net,REJECT +DOMAIN-SUFFIX,gdt.qq.com,REJECT +DOMAIN,g.q4ik.com,REJECT +DOMAIN,g1.163.com,REJECT +DOMAIN-SUFFIX,gg.gao7.com,REJECT +DOMAIN-SUFFIX,gtags.net,REJECT +DOMAIN,g3.letv.com,REJECT +DOMAIN-SUFFIX,google-analytics.com,REJECT +DOMAIN-SUFFIX,googleadsserving.cn,REJECT +DOMAIN-SUFFIX,googlesyndication.com,REJECT +DOMAIN-SUFFIX,googleadservices.com,REJECT +DOMAIN-SUFFIX,googletagservices.com,REJECT +DOMAIN-SUFFIX,googeadsserving.cn,REJECT +DOMAIN-SUFFIX,guomob.com,REJECT +DOMAIN-SUFFIX,game.ifeng.com,REJECT +DOMAIN-SUFFIX,gclick.cn,REJECT +DOMAIN-SUFFIX,ggjia.cn,REJECT +DOMAIN-SUFFIX,ggdoubi.com,REJECT +DOMAIN-SUFFIX,goyoo.cn,REJECT +DOMAIN,gad.kugpu.com,REJECT +DOMAIN,gcapi.sy.kugou.com,REJECT +DOMAIN-SUFFIX,gouzb.com,REJECT +DOMAIN,game.kuwo.cn,REJECT +DOMAIN,gg.kogou.com,REJECT +DOMAIN-SUFFIX,ggfeng.com,REJECT +DOMAIN-SUFFIX,gvt1.com,REJECT +DOMAIN,gma.alicdn.com,REJECT +DOMAIN-SUFFIX,goyoo.com.cn,REJECT +DOMAIN,g.tbcdn.cn,REJECT +DOMAIN-SUFFIX,goyoogroup.cn,REJECT +DOMAIN,gorgon.youdao.com,REJECT +DOMAIN-SUFFIX,guanggaojia.cn,REJECT +DOMAIN-SUFFIX,guangzhuiyuan.com,REJECT +DOMAIN-SUFFIX,good.ta80.com,REJECT +DOMAIN-SUFFIX,gw5.push.mcp.weibo.cn,REJECT +DOMAIN-SUFFIX,gouzhibao.cn,REJECT +DOMAIN-SUFFIX,goto.sms.sohu.com,REJECT +DOMAIN-SUFFIX,gs.mydown.yesky.com,REJECT +DOMAIN,haitaoad.nosdn.127.net,REJECT +DOMAIN-SUFFIX,h04.hxsame.hexun.com,REJECT +DOMAIN-SUFFIX,h5channel.51pgzs.com,REJECT +DOMAIN-SUFFIX,haowj.com.cn,REJECT +DOMAIN,hainan.net,REJECT +DOMAIN-SUFFIX,hytgj.com,REJECT +DOMAIN-SUFFIX,huimee.net,REJECT +DOMAIN-SUFFIX,hzkcbs.com,REJECT +DOMAIN-SUFFIX,hxadt.com,REJECT +DOMAIN-SUFFIX,haiyunx.com,REJECT +DOMAIN-SUFFIX,hxsame.hexun.com,REJECT +DOMAIN,hm.baidu.com.cn,REJECT +DOMAIN-SUFFIX,hwcdn.net,REJECT +DOMAIN,hmma.baidu.com,REJECT +DOMAIN,hm.baidu.com,REJECT +DOMAIN,hydra.alibaba.com,REJECT +DOMAIN-SUFFIX,img.ujian.cc,REJECT +DOMAIN-SUFFIX,img.twcczhu.com,REJECT +DOMAIN-SUFFIX,impact.applifier.com,REJECT +DOMAIN-SUFFIX,inmobi.com,REJECT +DOMAIN-SUFFIX,iwencai.com,REJECT +DOMAIN-SUFFIX,immob.cn,REJECT +DOMAIN-SUFFIX,ipinyou.com,REJECT +DOMAIN-SUFFIX,intely.cn,REJECT +DOMAIN-SUFFIX,impservice2.youdao.com,REJECT +DOMAIN-SUFFIX,i.go.sohu.com,REJECT +DOMAIN-SUFFIX,inte.sogou.com,REJECT +DOMAIN-SUFFIX,iuyhjn.com,REJECT +DOMAIN-SUFFIX,itv.hexun.com,REJECT +DOMAIN-SUFFIX,img.il8r.com,REJECT +DOMAIN-SUFFIX,iwstat.tudou.com,REJECT +DOMAIN-SUFFIX,irs01.com,REJECT +DOMAIN,inmobisdk-a.akamaihd.net,REJECT +DOMAIN-SUFFIX,icast.cn,REJECT +DOMAIN-SUFFIX,impservice.union.youdao.com,REJECT +DOMAIN-SUFFIX,ifashion.ifeng.com,REJECT +DOMAIN-SUFFIX,imageplus.baidu.com,REJECT +DOMAIN,iis3g.deliver.ifeng.com,REJECT +DOMAIN-SUFFIX,i8app.cn,REJECT +DOMAIN-SUFFIX,idougou.cn,REJECT +DOMAIN-SUFFIX,idougou.com,REJECT +DOMAIN,ipengtai.huanqiu.com,REJECT +DOMAIN-SUFFIX,idougou.com.cn,REJECT +DOMAIN-SUFFIX,i8.com.cn,REJECT +DOMAIN,ibsnssdk.com,REJECT +DOMAIN-SUFFIX,i8card.com,REJECT +DOMAIN-SUFFIX,i8os.cn,REJECT +DOMAIN,ios.bugly.qq.com,REJECT +DOMAIN,ipcheck1.39.net,REJECT +DOMAIN-SUFFIX,i8ad.cn,REJECT +DOMAIN-SUFFIX,ibianli.com.cn,REJECT +DOMAIN-SUFFIX,icafe8.cn,REJECT +DOMAIN-SUFFIX,i8cn.com,REJECT +DOMAIN-SUFFIX,ipduo.cn,REJECT +DOMAIN-SUFFIX,id528.com,REJECT +DOMAIN-SUFFIX,imochy.com,REJECT +DOMAIN-SUFFIX,ip000.cn,REJECT +DOMAIN-SUFFIX,i8desk.com,REJECT +DOMAIN-SUFFIX,index8.cn,REJECT +DOMAIN,ichannel.snssdk.com,REJECT +DOMAIN-SUFFIX,ingameads.gameloft.com,REJECT +DOMAIN-SUFFIX,img.fd7c.com,REJECT +DOMAIN-SUFFIX,inmobicdn.net,REJECT +DOMAIN-SUFFIX,j.pcpop.com,REJECT +DOMAIN-SUFFIX,jiaoyueba.com,REJECT +DOMAIN,js-agent.newrelic.com,REJECT +DOMAIN-SUFFIX,joyslink.com,REJECT +DOMAIN-SUFFIX,juejdkio.com,REJECT +DOMAIN-SUFFIX,j.qiqiyii.com,REJECT +DOMAIN-SUFFIX,jiaadx.com,REJECT +DOMAIN-SUFFIX,jxwcbf.com,REJECT +DOMAIN-SUFFIX,jermr.com,REJECT +DOMAIN,jump.luna.58.com,REJECT +DOMAIN-SUFFIX,js.revsci.net,REJECT +DOMAIN-SUFFIX,jisucn.com,REJECT +DOMAIN-SUFFIX,jusha.com,REJECT +DOMAIN-SUFFIX,jzv1.com,REJECT +DOMAIN-SUFFIX,jiafe.com,REJECT +DOMAIN,jiaoben.eastday.com,REJECT +DOMAIN-SUFFIX,jixing8.com,REJECT +DOMAIN-SUFFIX,kejet.net,REJECT +DOMAIN-SUFFIX,keyrun.cn,REJECT +DOMAIN-SUFFIX,ksyun.com,REJECT +DOMAIN-SUFFIX,kxwan.com,REJECT +DOMAIN-SUFFIX,kdatacenter.com,REJECT +DOMAIN,keytui.com,REJECT +DOMAIN-SUFFIX,ktplay.cn,REJECT +DOMAIN-SUFFIX,kukuplay.com,REJECT +DOMAIN-SUFFIX,kxbeta.com,REJECT +DOMAIN-SUFFIX,kuangtech.com,REJECT +DOMAIN-SUFFIX,log.mix.sina.com.cn,REJECT +DOMAIN-SUFFIX,lstat.youku.com,REJECT +DOMAIN-SUFFIX,linkpage.cn,REJECT +DOMAIN,logger.qingting.fm,REJECT +DOMAIN,log.stat.kugou.com,REJECT +DOMAIN,log.kuwo.cn,REJECT +DOMAIN-SUFFIX,lewifi.cn,REJECT +DOMAIN-SUFFIX,lzplugin.bshare.cn,REJECT +DOMAIN-SUFFIX,lives.l.qq.com,REJECT +DOMAIN,log.snssdk.com,REJECT +DOMAIN-SUFFIX,love.bobo.com,REJECT +DOMAIN-SUFFIX,link0.com,REJECT +DOMAIN-SUFFIX,ljrtb.cn,REJECT +DOMAIN,livep.l.qq.com,REJECT +DOMAIN,linkvans.com,REJECT +DOMAIN-SUFFIX,lianmeng.360.cn,REJECT +DOMAIN-SUFFIX,localytics.com,REJECT +DOMAIN,livew.l.qq.com,REJECT +DOMAIN-SUFFIX,lnk0.com,REJECT +DOMAIN-SUFFIX,latrobe.edu.au,REJECT +DOMAIN-SUFFIX,log.da.hunantv.com,REJECT +DOMAIN-SUFFIX,log.v2.hunantv.com,REJECT +DOMAIN-SUFFIX,mtab.clickmon.co.kr,REJECT +DOMAIN-SUFFIX,map.dxpmedia.com,REJECT +DOMAIN-SUFFIX,market.xiaomi.com,REJECT +DOMAIN-SUFFIX,mct01.com,REJECT +DOMAIN-SUFFIX,mega.mlt01.com,REJECT +DOMAIN-SUFFIX,mmstat.com,REJECT +DOMAIN-SUFFIX,my5525.net,REJECT +DOMAIN-SUFFIX,media.tianjimedia.com,REJECT +DOMAIN-SUFFIX,media.yesky.com,REJECT +DOMAIN,meta.video.qiyi.com,REJECT +DOMAIN-SUFFIX,m.sa.sm.cn,REJECT +DOMAIN-SUFFIX,madmini.com,REJECT +DOMAIN-SUFFIX,mipujia.com,REJECT +DOMAIN-SUFFIX,mixpanel.com,REJECT +DOMAIN-SUFFIX,moogos.com,REJECT +DOMAIN-SUFFIX,miaozhen.com,REJECT +DOMAIN-SUFFIX,mobpro.cn,REJECT +DOMAIN-SUFFIX,mobaders.com,REJECT +DOMAIN-SUFFIX,mediav.com,REJECT +DOMAIN,manager.channel.aliyun.com,REJECT +DOMAIN,mcgi.v.qq.com,REJECT +DOMAIN,mvads.kugou.com,REJECT +DOMAIN,mengmob.com,REJECT +DOMAIN-SUFFIX,myhard.com,REJECT +DOMAIN-SUFFIX,m.aty.sohu.com,REJECT +DOMAIN-SUFFIX,mobads.baidu.com,REJECT +DOMAIN-SUFFIX,mobads-logs.baidu.com,REJECT +DOMAIN-SUFFIX,miaoz.com.cn,REJECT +DOMAIN-SUFFIX,media8.cn,REJECT +DOMAIN,mobileapi.speedtest.net,REJECT +DOMAIN-SUFFIX,madgic.mobi,REJECT +DOMAIN-SUFFIX,mmg.aty.sohu.com,REJECT +DOMAIN-SUFFIX,mediasmart.es,REJECT +DOMAIN-SUFFIX,minisplat.cn,REJECT +DOMAIN-SUFFIX,mookie1.com,REJECT +DOMAIN-SUFFIX,majorserving.com,REJECT +DOMAIN-SUFFIX,mopub.com,REJECT +DOMAIN-SUFFIX,mob.com,REJECT +DOMAIN-SUFFIX,mydas.mobi,REJECT +DOMAIN-SUFFIX,mathtag.com,REJECT +DOMAIN-SUFFIX,mb.hd.sohu.com.cn,REJECT +DOMAIN,nsclick.baidu.com,REJECT +DOMAIN-SUFFIX,nstat.tudou.com,REJECT +DOMAIN-SUFFIX,nethotlink.com,REJECT +DOMAIN-SUFFIX,number1cp.cn,REJECT +DOMAIN-SUFFIX,niuxgame77.com,REJECT +DOMAIN,news.l.qq.com,REJECT +DOMAIN-SUFFIX,oimageb4.ydstatic.com,REJECT +DOMAIN-SUFFIX,oimageb5.ydstatic.com,REJECT +DOMAIN-SUFFIX,optaim.com,REJECT +DOMAIN-SUFFIX,optimix.cn,REJECT +DOMAIN-SUFFIX,optimix.asia,REJECT +DOMAIN-SUFFIX,onetad.com,REJECT +DOMAIN-SUFFIX,otherlevels.com,REJECT +DOMAIN-SUFFIX,oc.umeng.com,REJECT +DOMAIN-SUFFIX,pos.baidu.com,REJECT +DOMAIN,p.l.qq.com,REJECT +DOMAIN-SUFFIX,p-log.ykimg.com,REJECT +DOMAIN-SUFFIX,pubnative.net,REJECT +DOMAIN,pv.funshion.com,REJECT +DOMAIN,po.funshion.com,REJECT +DOMAIN-SUFFIX,p.l.ykimg.com,REJECT +DOMAIN-SUFFIX,px.3.cn,REJECT +DOMAIN-SUFFIX,pushwoosh.com,REJECT +DOMAIN-SUFFIX,push.zhanzhang.baidu.com,REJECT +DOMAIN-SUFFIX,pd.brand.sogou.com,REJECT +DOMAIN,pgdt.gtimg.cn,REJECT +DOMAIN-SUFFIX,pups.baidu.com,REJECT +DOMAIN-SUFFIX,pups.bdimg.com,REJECT +DOMAIN,policy.jd.com,REJECT +DOMAIN,plog.dc.letv.com,REJECT +DOMAIN,pro.letv.com,REJECT +DOMAIN,pro.hoye.letv.com,REJECT +DOMAIN-SUFFIX,p.inte.sogou.com,REJECT +DOMAIN-SUFFIX,punchbox.org,REJECT +DOMAIN,partner.toutiao.com,REJECT +DOMAIN,pixel.wp.com,REJECT +DOMAIN-SUFFIX,pv.alert.autohome.com.cn,REJECT +DOMAIN-SUFFIX,playnimei.cn,REJECT +DOMAIN-SUFFIX,pailady.com,REJECT +DOMAIN,paopao.iqiyi.com,REJECT +DOMAIN-SUFFIX,pro.cn,REJECT +DOMAIN,pingma.qq.com,REJECT +DOMAIN-SUFFIX,p0y.cn,REJECT +DOMAIN-SUFFIX,pcookie.mlt01.com,REJECT +DOMAIN-SUFFIX,profile.ams50.cloudfront.net,REJECT +DOMAIN-SUFFIX,quote.tool.hexun.com,REJECT +DOMAIN-SUFFIX,qf.56.com,REJECT +DOMAIN-SUFFIX,qchannel01.cn,REJECT +DOMAIN-SUFFIX,qqabcd8899.cn,REJECT +DOMAIN-SUFFIX,qtmojo.com,REJECT +DOMAIN-SUFFIX,qos.117go.com,REJECT +DOMAIN-SUFFIX,qhres.com,REJECT +DOMAIN-SUFFIX,rtax.criteo.com,REJECT +DOMAIN,r.yy.com,REJECT +DOMAIN-SUFFIX,ra.icast.cn,REJECT +DOMAIN-SUFFIX,rp.baidu.com,REJECT +DOMAIN-SUFFIX,run.wmxa.cn,REJECT +DOMAIN-SUFFIX,rollout.io,REJECT +DOMAIN-SUFFIX,rhocdn.net,REJECT +DOMAIN-SUFFIX,rd.da.netease.com,REJECT +DOMAIN-SUFFIX,rwq.youle55.com,REJECT +DOMAIN,rtmonitor.kugou.com,REJECT +DOMAIN-SUFFIX,ssl-static.baifendian.com,REJECT +DOMAIN,su.bdimg.com,REJECT +DOMAIN-SUFFIX,static1.bfdcdn.com,REJECT +DOMAIN-SUFFIX,show.xiazai8844.net,REJECT +DOMAIN-SUFFIX,szhk.com,REJECT +DOMAIN-SUFFIX,supersonicads.com,REJECT +DOMAIN-SUFFIX,sax.sina.com.cn,REJECT +DOMAIN,simba.m.taobao.com,REJECT +DOMAIN-SUFFIX,ssacdn.com,REJECT +DOMAIN,supersonicads-a.akamaihd.net,REJECT +DOMAIN-SUFFIX,smartadserver.com,REJECT +DOMAIN-SUFFIX,sax.sina.cn,REJECT +DOMAIN-SUFFIX,sijiys.com,REJECT +DOMAIN,stat.letv.com,REJECT +DOMAIN-SUFFIX,sp.qf.56.com,REJECT +DOMAIN-SUFFIX,serving-sys.com,REJECT +DOMAIN-SUFFIX,static.applifier.com,REJECT +DOMAIN-SUFFIX,stat.tudou.com,REJECT +DOMAIN-SUFFIX,stat.youku.com,REJECT +DOMAIN,strip.taobaocdn.com,REJECT +DOMAIN-SUFFIX,stats.tudou.com,REJECT +DOMAIN,stat.it168.com,REJECT +DOMAIN-SUFFIX,simaba.taobao.com,REJECT +DOMAIN-SUFFIX,slog.sina.cn,REJECT +DOMAIN,show.re.taobao.com,REJECT +DOMAIN,sp1.baidu.com,REJECT +DOMAIN-SUFFIX,ssp-ces.gameloft.com,REJECT +DOMAIN-SUFFIX,show.58dtpf.com,REJECT +DOMAIN-SUFFIX,sohu.wrating.com,REJECT +DOMAIN-SUFFIX,show.pipi7788.com,REJECT +DOMAIN-SUFFIX,statics.motu.pagechoice.net,REJECT +DOMAIN-SUFFIX,so.hexun.com,REJECT +DOMAIN-SUFFIX,s.cr-nielsen.com,REJECT +DOMAIN-SUFFIX,sykty.com,REJECT +DOMAIN,stat.10jqka.com.cn,REJECT +DOMAIN-SUFFIX,score.my.tv.sohu.com,REJECT +DOMAIN-SUFFIX,sdkapp.mobile.sina.cn,REJECT +DOMAIN,sdkinit.taobao.com,REJECT +DOMAIN,sdk.e.qq.com,REJECT +DOMAIN-SUFFIX,scorecardresearch.com,REJECT +DOMAIN-SUFFIX,souluo.net,REJECT +DOMAIN,st.vq.ku6.cn,REJECT +DOMAIN-SUFFIX,s.go.sohu.com,REJECT +DOMAIN-SUFFIX,shared.youdao.com,REJECT +DOMAIN-SUFFIX,shunwang.cn,REJECT +DOMAIN-SUFFIX,static.bshare.cn,REJECT +DOMAIN,stat.funshion.net,REJECT +DOMAIN-SUFFIX,tanx.com,REJECT +DOMAIN-SUFFIX,telemetryverification.net,REJECT +DOMAIN-SUFFIX,tongji.linezing.com,REJECT +DOMAIN-SUFFIX,tiqcdn.com,REJECT +DOMAIN-SUFFIX,talkingdata.net,REJECT +DOMAIN-SUFFIX,txt789.com,REJECT +DOMAIN-SUFFIX,tapjoyads.com,REJECT +DOMAIN-SUFFIX,track.ra.icast.cn,REJECT +DOMAIN-SUFFIX,tui1999.com,REJECT +DOMAIN,t.l.qq.com,REJECT +DOMAIN-SUFFIX,thescenseproject.com,REJECT +DOMAIN-SUFFIX,tailorforme.com,REJECT +DOMAIN-SUFFIX,ttwgou.com,REJECT +DOMAIN-SUFFIX,tg.1155t.cn,REJECT +DOMAIN-SUFFIX,trxxn.com,REJECT +DOMAIN-SUFFIX,tcss.qq.com,REJECT +DOMAIN,tongji.techweb.com.cn,REJECT +DOMAIN,testm.10jqka.com.cn,REJECT +DOMAIN-SUFFIX,umsns.com,REJECT +DOMAIN,uu.qq.com,REJECT +DOMAIN-SUFFIX,umeng.co,REJECT +DOMAIN-SUFFIX,umengcloud.com,REJECT +DOMAIN,update.mobile.kugou.com,REJECT +DOMAIN-SUFFIX,umeng.net,REJECT +DOMAIN-SUFFIX,umtrack.com,REJECT +DOMAIN-SUFFIX,ushaqi.com,REJECT +DOMAIN-SUFFIX,union.xunlei.com,REJECT +DOMAIN-SUFFIX,uyunad.com,REJECT +DOMAIN,u.l.qq.com,REJECT +DOMAIN-SUFFIX,unity3d.com,REJECT +DOMAIN-SUFFIX,utrack.hexun.com,REJECT +DOMAIN-SUFFIX,union.youdao.com,REJECT +DOMAIN-SUFFIX,umapi.cn,REJECT +DOMAIN-SUFFIX,u1.img.mobile.sina.cn,REJECT +DOMAIN-SUFFIX,vip.yule8.net,REJECT +DOMAIN-SUFFIX,vipp.rwjfs.com,REJECT +DOMAIN-SUFFIX,v2.log.hunantv.com,REJECT +DOMAIN-SUFFIX,v1.ujian.cc,REJECT +DOMAIN-SUFFIX,vungle.cn,REJECT +DOMAIN-SUFFIX,videojj.com,REJECT +DOMAIN-SUFFIX,vamaker.com,REJECT +DOMAIN-SUFFIX,vtac.cn,REJECT +DOMAIN-SUFFIX,vgdtimg.cn,REJECT +DOMAIN-SUFFIX,webterren.com,REJECT +DOMAIN-SUFFIX,wuxian.fd.zol-img.com.cn,REJECT +DOMAIN-SUFFIX,wanju12345.com,REJECT +DOMAIN-SUFFIX,wifi6.cn,REJECT +DOMAIN-SUFFIX,w8.com.cn,REJECT +DOMAIN-SUFFIX,whxhxwz.com,REJECT +DOMAIN-SUFFIX,wrating.com,REJECT +DOMAIN-SUFFIX,wan.sogou.com,REJECT +DOMAIN,wan.liebao.cn,REJECT +DOMAIN,wt.39.net,REJECT +DOMAIN-SUFFIX,wkanx.com,REJECT +DOMAIN-SUFFIX,waps.cn,REJECT +DOMAIN-SUFFIX,wzjmr.com,REJECT +DOMAIN-SUFFIX,wiyun.com,REJECT +DOMAIN,wifiapi01.51y5.net,REJECT +DOMAIN,wifiapi02.51y5.net,REJECT +DOMAIN-SUFFIX,wooboo.com.cn,REJECT +DOMAIN-SUFFIX,wqmobile.com,REJECT +DOMAIN,webstat.kuwo.cn,REJECT +DOMAIN-SUFFIX,wmdeal.qtmojo.com,REJECT +DOMAIN-SUFFIX,xs.houyi.baofeng.net,REJECT +DOMAIN-SUFFIX,x.da.hunantv.com,REJECT +DOMAIN-SUFFIX,xiaoyun.com,REJECT +DOMAIN-SUFFIX,xrtzf.com,REJECT +DOMAIN-SUFFIX,xiyou-g.com,REJECT +DOMAIN-SUFFIX,xingjuhe.com,REJECT +DOMAIN-SUFFIX,xunio.com,REJECT +DOMAIN-SUFFIX,xiaoyunwifi.cn,REJECT +DOMAIN-SUFFIX,xihuan8.cn,REJECT +DOMAIN-SUFFIX,xihuanba.cn,REJECT +DOMAIN,xiuxiu.mobile.meitudata.com,REJECT +DOMAIN-SUFFIX,youmi.net,REJECT +DOMAIN-SUFFIX,yes1.feng.com,REJECT +DOMAIN-SUFFIX,yieldmo.com,REJECT +DOMAIN-SUFFIX,y0game.com,REJECT +DOMAIN-SUFFIX,youzu.com,REJECT +DOMAIN-SUFFIX,yunpro.cn,REJECT +DOMAIN-SUFFIX,yoyi.tv,REJECT +DOMAIN-SUFFIX,yandui.com,REJECT +DOMAIN-SUFFIX,youth.cn,REJECT +DOMAIN-SUFFIX,youxiaoad.com,REJECT +DOMAIN-SUFFIX,yhdichan.com,REJECT +DOMAIN-SUFFIX,yoyi.com.cn,REJECT +DOMAIN-SUFFIX,ychun03.com,REJECT +DOMAIN-SUFFIX,ymrej.com,REJECT +DOMAIN-SUFFIX,znsv.baidu.com,REJECT +DOMAIN-SUFFIX,zdw.w8.com.cn,REJECT +DOMAIN-SUFFIX,zhiziyun.com,REJECT +DOMAIN-SUFFIX,ziffdavis.com,REJECT +DOMAIN-SUFFIX,zhongxinghuanyu.com,REJECT +DOMAIN-SUFFIX,zdbb.net,REJECT +DOMAIN-SUFFIX,zhaochina.com,REJECT +DOMAIN-SUFFIX,zzrcz.com,REJECT +DOMAIN-SUFFIX,zyiis.net,REJECT +# KEYWORD +DOMAIN-KEYWORD,aka,rixCloud,force-remote-dns +DOMAIN-KEYWORD,amazon,rixCloud,force-remote-dns +DOMAIN-KEYWORD,appledaily,rixCloud,force-remote-dns +DOMAIN-KEYWORD,blogspot,rixCloud,force-remote-dns +DOMAIN-KEYWORD,fbcdn,rixCloud,force-remote-dns +DOMAIN-KEYWORD,facebook,rixCloud,force-remote-dns +DOMAIN-KEYWORD,gmail,rixCloud,force-remote-dns +DOMAIN-KEYWORD,google,rixCloud,force-remote-dns +DOMAIN-KEYWORD,gstatic,rixCloud,force-remote-dns +DOMAIN-KEYWORD,instagram,rixCloud,force-remote-dns +DOMAIN-SUFFIX,cdninstagram.com,rixCloud,force-remote-dns +DOMAIN-KEYWORD,spotify,rixCloud,force-remote-dns +DOMAIN-SUFFIX,scdn.co,rixCloud,force-remote-dns +DOMAIN-KEYWORD,twitter,rixCloud,force-remote-dns +DOMAIN-KEYWORD,twimg,rixCloud,force-remote-dns +DOMAIN-KEYWORD,youtube,rixCloud,force-remote-dns +DOMAIN-KEYWORD,360buy,DIRECT,force-remote-dns +DOMAIN-KEYWORD,alipay,DIRECT,force-remote-dns +DOMAIN-KEYWORD,baidupcs,DIRECT,force-remote-dns +DOMAIN-KEYWORD,taobao,DIRECT,force-remote-dns +DOMAIN-KEYWORD,admob,REJECT,force-remote-dns +DOMAIN-KEYWORD,analysis,REJECT,force-remote-dns +DOMAIN-KEYWORD,domob,REJECT,force-remote-dns +DOMAIN-KEYWORD,dnserror,REJECT,force-remote-dns +DOMAIN-KEYWORD,openx,REJECT,force-remote-dns +DOMAIN-KEYWORD,telegram,rixCloud,force-remote-dns +# USER-AGENT +USER-AGENT,Pinterest*,rixCloud +USER-AGENT,Slack*,rixCloud +# IPCIDR +IP-CIDR,1.201.0.0/24,rixCloud,no-resolve +IP-CIDR,27.0.236.0/22,rixCloud,no-resolve +IP-CIDR,43.249.72.0/24,rixCloud,no-resolve +IP-CIDR,64.30.224.0/24,rixCloud,no-resolve +IP-CIDR,91.108.4.0/22,rixCloud,no-resolve +IP-CIDR,91.108.56.0/22,rixCloud,no-resolve +IP-CIDR,109.239.140.0/24,rixCloud,no-resolve +IP-CIDR,117.18.232.0/21,rixCloud,no-resolve +IP-CIDR,149.154.160.0/20,rixCloud,no-resolve +IP-CIDR,194.68.28.0/22,rixCloud,no-resolve +IP-CIDR,216.58.197.100/32,rixCloud,no-resolve +IP-CIDR,10.0.0.0/8,DIRECT,no-resolve +IP-CIDR,127.0.0.0/8,DIRECT,no-resolve +IP-CIDR,172.16.0.0/12,DIRECT,no-resolve +IP-CIDR,192.168.0.0/16,DIRECT,no-resolve +IP-CIDR,14.63.198.45/32,REJECT,no-resolve +IP-CIDR,23.42.186.24/32,REJECT,no-resolve +IP-CIDR,23.66.147.48/32,REJECT,no-resolve +IP-CIDR,59.110.16.35/32,REJECT,no-resolve +IP-CIDR,101.227.12.0/23,REJECT,no-resolve +IP-CIDR,101.227.14.0/24,REJECT,no-resolve +IP-CIDR,101.227.119.0/24,REJECT,no-resolve +IP-CIDR,101.227.200.0/24,REJECT,no-resolve +IP-CIDR,103.2.28.0/22,rixCloud,no-resolve +IP-CIDR,103.27.148.0/22,rixCloud,no-resolve +IP-CIDR,103.246.56.0/22,rixCloud,no-resolve +IP-CIDR,106.11.11.86/32,REJECT,no-resolve +IP-CIDR,107.20.211.189/32,REJECT,no-resolve +IP-CIDR,108.61.200.51/32,REJECT,no-resolve +IP-CIDR,110.76.140.0/22,rixCloud,no-resolve +IP-CIDR,111.63.135.0/24,REJECT,no-resolve +IP-CIDR,111.206.13.60/30,REJECT,no-resolve +IP-CIDR,111.206.13.64/28,REJECT,no-resolve +IP-CIDR,111.206.13.80/32,REJECT,no-resolve +IP-CIDR,111.206.13.250/31,REJECT,no-resolve +IP-CIDR,111.206.22.0/24,REJECT,no-resolve +IP-CIDR,113.61.104.0/22,rixCloud,no-resolve +IP-CIDR,113.207.57.24/32,REJECT,no-resolve +IP-CIDR,115.29.247.48/32,REJECT,no-resolve +IP-CIDR,116.206.22.7/32,REJECT,no-resolve +IP-CIDR,117.34.51.0/24,REJECT,no-resolve +IP-CIDR,119.188.13.0/24,REJECT,no-resolve +IP-CIDR,119.235.224.0/21,rixCloud,no-resolve +IP-CIDR,119.235.232.0/23,rixCloud,no-resolve +IP-CIDR,119.235.235.0/24,rixCloud,no-resolve +IP-CIDR,119.235.236.0/23,rixCloud,no-resolve +IP-CIDR,120.26.151.246/32,REJECT,no-resolve +IP-CIDR,120.55.199.139/32,REJECT,no-resolve +IP-CIDR,120.77.11.172/32,REJECT,no-resolve +IP-CIDR,120.132.63.203/32,REJECT,no-resolve +IP-CIDR,121.42.144.95/32,REJECT,no-resolve +IP-CIDR,121.43.75.169/32,REJECT,no-resolve +IP-CIDR,121.201.11.95/32,REJECT,no-resolve +IP-CIDR,121.201.108.2/32,REJECT,no-resolve +IP-CIDR,121.251.255.0/24,REJECT,no-resolve +IP-CIDR,122.72.50.17/32,REJECT,no-resolve +IP-CIDR,122.226.223.163/32,REJECT,no-resolve +IP-CIDR,123.57.94.184/32,REJECT,no-resolve +IP-CIDR,123.125.111.0/24,REJECT,no-resolve +IP-CIDR,123.125.117.0/24,REJECT,no-resolve +IP-CIDR,123.125.118.0/24,REJECT,no-resolve +IP-CIDR,123.206.1.246/32,REJECT,no-resolve +IP-CIDR,124.116.241.0/24,REJECT,no-resolve +IP-CIDR,125.6.146.0/24,rixCloud,no-resolve +IP-CIDR,125.6.149.0/24,rixCloud,no-resolve +IP-CIDR,125.6.190.0/24,rixCloud,no-resolve +IP-CIDR,140.205.36.26/32,REJECT,no-resolve +IP-CIDR,180.76.190.68/32,REJECT,no-resolve +IP-CIDR,183.131.79.130/32,REJECT,no-resolve +IP-CIDR,203.104.103.0/24,rixCloud,no-resolve +IP-CIDR,203.104.128.0/20,rixCloud,no-resolve +IP-CIDR,203.174.66.64/26,rixCloud,no-resolve +IP-CIDR,203.174.77.0/24,rixCloud,no-resolve +IP-CIDR,211.137.132.89/32,REJECT,no-resolve +IP-CIDR,221.179.183.0/24,REJECT,no-resolve +IP-CIDR,221.179.191.0/24,REJECT,no-resolve +# Other +GEOIP,CN,DIRECT +FINAL,rixCloud,dns-failed + +[URL Rewrite] +# Rewrite +^http://www.google.cn http://www.google.com header +^http://youtube.com https://youtube.com 302 +^http://www.rixcloud.com https://www.rixcloud.com 302 +^https://rixcloud.com https://www.rixcloud.com 302 +^https://www.rixcloud.com https://www.rixcloud.com 302 +^http://www.rixcloud.org https://www.rixcloud.com 302 +^http://rixcloud.org https://www.rixcloud.com 302 +^https://www.rixcloud.org https://www.rixcloud.com 302 +^http://rixcloud.org https://www.rixcloud.com 302 +^http://www.rixcloud.cn https://www.rixcloud.com 302 +^http://rixcloud.cn https://www.rixcloud.com 302 +^https://www.rixcloud.cn https://www.rixcloud.com 302 +^http://rixcloud.cn https://www.rixcloud.com 302 + +[Host] +ip6-localhost = ::1 +ip6-loopback = ::1 +rixcloud.com = 104.16.58.5 +www.rixcloud.com = 104.16.58.5 +my.rixcloud.com = 104.16.110.208 +*.taobao.com = server:223.6.6.6 +*.tmall.com = server:223.6.6.6 +*.jd.com = server:119.28.28.28 +*.qq.com = server:119.28.28.28 +*.tencent.com = server:119.28.28.28 diff --git a/text2img.js b/text2img.js new file mode 100644 index 0000000..4339d74 --- /dev/null +++ b/text2img.js @@ -0,0 +1,39 @@ +$ui.render({ + props: { + + }, + views: [{ + type: "view", + props: { + id: "bottomView", + bgcolor: $color("#fff") + }, + layout: (make, view) => { + make.width.equalTo(view.super) + make.center.equalTo(view.super) + make.height.equalTo(view.views[0]).offset(40) + }, + views: [{ + type: "label", + props: { + text: $clipboard.text, + lines: 0 + }, + layout: (make, view) => { + make.left.equalTo(view.super).offset(20) + make.centerY.equalTo(view.super) + make.width.equalTo(view.super).offset(-40) + }, + events: { + tapped: sender => { + + } + }, + }] + }] +}) + +$delay(.1, function () { + $share.wechat($("bottomView").snapshot) + $app.close() +}) diff --git a/updateCheck.js b/updateCheck.js new file mode 100644 index 0000000..8b6b644 --- /dev/null +++ b/updateCheck.js @@ -0,0 +1,129 @@ +let updateScript = function(scriptData) { + let name = scriptData.scriptName.text + let url = scriptData.data.url + let icon = scriptData.data.icon + let installUrl = `jsbox://install?url=${encodeURI(url)}&name=${encodeURI(name)}&icon=${encodeURI(icon)}` + $app.openURL(installUrl) +} + +let renderUI = function (UIData) { + $ui.render({ + props: { + title: "脚本更新" + }, + views: [{ + type: "list", + props: { + id: "mainList", + data: UIData, + template: { + views: [{ + type: 'label', + props: { + id: 'scriptName', + }, + layout: (make, view) => { + make.height.equalTo(view.super) + make.left.equalTo(view.super).offset(10) + } + }, { + type: 'label', + props: { + id: 'scriptCanUpdate' + }, + layout: (make, view) => { + make.height.equalTo(view.super) + make.right.equalTo(view.super).offset(-10) + } + }] + } + }, + layout: $layout.fill, + events: { + didSelect: (sender, indexPath, data) => { + console.log(data.scriptName.text) + if (data.newData) { + updateScript(data) + } + // let scriptUrl = data.data.url + // data.scriptCanUpdate = '' + // $("mainList").delete(indexPath) + // $("mainList").insert({ + // indexPath: indexPath, + // value: data + // }) + } + } + }] + }) +} + +$app.tips("更新完后重新运行即可检查是否成功") + +renderUI([]) + +let scripts = $addin.list.filter(item => item.url) + +let presentData = [] + +let checkScript = function (scriptInfo) { + return new Promise((resolve, reject) => { + $http.get({ + url: scriptInfo.data.url, + handler: function (resp) { + let response = resp.response + let scriptData = resp.data + if (scriptInfo.data.data.string && scriptInfo.data.data.string != scriptData) { + console.log() + scriptInfo.scriptCanUpdate = { text: '可更新' } + scriptInfo.newData = scriptData + } + + resolve(scriptInfo) + // if (response.statusCode == 200) { + // + // // $console.info(scriptInfo.data) + // resolve(scriptInfo) + // } else { + // reject() + // } + } + }) + }) +} + +for (let k in scripts) { + presentData.push({ + scriptName: { text: scripts[k].name }, + scriptCanUpdate: { text: '' }, + data: scripts[k] + }) +} + +let updatedData = [] + +let promiseData = presentData.map(item => checkScript(item)) + +$ui.loading(true) + +for (let i = 0, p = Promise.resolve(); i < promiseData.length; i++) { + p = p.then(d => { + updatedData.push(d) + return promiseData[i] + }) + if (i == promiseData.length - 1) { + p.then((d) => { + setTimeout(() => { + updatedData.push(d) + $ui.loading(false) + $("mainList").data = updatedData + }, 1); + }).catch(() => { + setTimeout(() => { + $ui.loading(false) + }, 1); + }) + } +} + + diff --git a/wechalock1.1.js b/wechalock1.1.js index 885e71b..559b896 100644 --- a/wechalock1.1.js +++ b/wechalock1.1.js @@ -1,5 +1,6 @@ FILE = "password.js" + if ($file.exists(FILE)) { let pass = $file.read(FILE).string $ui.render({