From 969124d9e7457d7786ac0e724f4a95ff2f0453d6 Mon Sep 17 00:00:00 2001 From: yicheng <11733500+yichengchen@users.noreply.github.com> Date: Sat, 14 Dec 2019 17:52:44 +0800 Subject: [PATCH] support api for proxy provider [beta] [appcenter] [notarize] --- ClashX.xcodeproj/project.pbxproj | 8 +++ ClashX/AppDelegate.swift | 6 +++ ClashX/Extensions/DateFormatter+.swift | 18 +++++++ ClashX/General/ApiRequest.swift | 7 +++ ClashX/General/Managers/MenuItemFactory.swift | 52 +++++++++++++------ ClashX/Models/ClashProvider.swift | 50 ++++++++++++++++++ ClashX/Models/ClashProxy.swift | 28 ++++++---- ClashX/go.mod | 2 +- ClashX/go.sum | 4 +- 9 files changed, 146 insertions(+), 29 deletions(-) create mode 100644 ClashX/Extensions/DateFormatter+.swift create mode 100644 ClashX/Models/ClashProvider.swift diff --git a/ClashX.xcodeproj/project.pbxproj b/ClashX.xcodeproj/project.pbxproj index 772ed07..83584f4 100644 --- a/ClashX.xcodeproj/project.pbxproj +++ b/ClashX.xcodeproj/project.pbxproj @@ -64,6 +64,8 @@ F935B2F42307CD32009E4D33 /* ProxyConfigHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = F935B2F32307CD32009E4D33 /* ProxyConfigHelper.m */; }; F935B2FA23083EE6009E4D33 /* ProxySettingTool.m in Sources */ = {isa = PBXBuildFile; fileRef = F935B2F923083EE6009E4D33 /* ProxySettingTool.m */; }; F935B2FC23085515009E4D33 /* SystemProxyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F935B2FB23085515009E4D33 /* SystemProxyManager.swift */; }; + F939724C23A4B33500FE5A3F /* ClashProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F939724B23A4B33500FE5A3F /* ClashProvider.swift */; }; + F939724E23A4DB0600FE5A3F /* DateFormatter+.swift in Sources */ = {isa = PBXBuildFile; fileRef = F939724D23A4DB0600FE5A3F /* DateFormatter+.swift */; }; F976275C23634DF8000EDEFE /* LoginServiceKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = F976275B23634DF8000EDEFE /* LoginServiceKit.swift */; }; F977FAAC2366790500C17F1F /* AutoUpgardeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F977FAAB2366790500C17F1F /* AutoUpgardeManager.swift */; }; F977FAAE23669D6400C17F1F /* ConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F977FAAD23669D6400C17F1F /* ConnectionManager.swift */; }; @@ -181,6 +183,8 @@ F935B2F823083EE6009E4D33 /* ProxySettingTool.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ProxySettingTool.h; sourceTree = ""; }; F935B2F923083EE6009E4D33 /* ProxySettingTool.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ProxySettingTool.m; sourceTree = ""; }; F935B2FB23085515009E4D33 /* SystemProxyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemProxyManager.swift; sourceTree = ""; }; + F939724B23A4B33500FE5A3F /* ClashProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClashProvider.swift; sourceTree = ""; }; + F939724D23A4DB0600FE5A3F /* DateFormatter+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+.swift"; sourceTree = ""; }; F976275B23634DF8000EDEFE /* LoginServiceKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginServiceKit.swift; sourceTree = ""; }; F977FAAB2366790500C17F1F /* AutoUpgardeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoUpgardeManager.swift; sourceTree = ""; }; F977FAAD23669D6400C17F1F /* ConnectionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionManager.swift; sourceTree = ""; }; @@ -232,6 +236,7 @@ F92D0B29236C759100575E15 /* NSTextField+Vibrancy.swift */, F9E754D1239CC28D00CEE7CC /* NSAlert+Extension.swift */, F9E8F34523A12B89002DE5E8 /* String+Encode.swift */, + F939724D23A4DB0600FE5A3F /* DateFormatter+.swift */, ); path = Extensions; sourceTree = ""; @@ -281,6 +286,7 @@ 499A485722ED715200F6C675 /* RemoteConfigModel.swift */, F915A4612366ADEF004840BE /* ClashConnection.swift */, F92D0B23236BC12000575E15 /* SavedProxyModel.swift */, + F939724B23A4B33500FE5A3F /* ClashProvider.swift */, ); path = Models; sourceTree = ""; @@ -620,6 +626,7 @@ 4952C3D02117027C004A4FA8 /* ConfigFileManager.swift in Sources */, 49BC061C212931F4005A0FE7 /* AboutViewController.swift in Sources */, 4949D154213242F600EF85E6 /* Paths.swift in Sources */, + F939724E23A4DB0600FE5A3F /* DateFormatter+.swift in Sources */, F915A4622366ADEF004840BE /* ClashConnection.swift in Sources */, F977FAAE23669D6400C17F1F /* ConnectionManager.swift in Sources */, 495340B320DE68C300B0D3FF /* StatusItemView.swift in Sources */, @@ -660,6 +667,7 @@ 4960A6DB2136529200B940C9 /* JSBridgeHandler.swift in Sources */, 493AEAE5221AE7230016FE98 /* ProxyMenuItem.swift in Sources */, 499A485E22ED9B7C00F6C675 /* NSTableView+Reload.swift in Sources */, + F939724C23A4B33500FE5A3F /* ClashProvider.swift in Sources */, 49862FA0218418C600A1D5EC /* ClashRule.swift in Sources */, 49C9EF64223E78F5005D8B6A /* ClashProxy.swift in Sources */, F9E8F34623A12B89002DE5E8 /* String+Encode.swift in Sources */, diff --git a/ClashX/AppDelegate.swift b/ClashX/AppDelegate.swift index d62df7e..3a4cd38 100644 --- a/ClashX/AppDelegate.swift +++ b/ClashX/AppDelegate.swift @@ -546,10 +546,16 @@ extension AppDelegate { extension AppDelegate { func registCrashLogger() { + #if DEBUG + return + #endif Fabric.with([Crashlytics.self]) } func failLaunchProtect() { + #if DEBUG + return + #endif let x = UserDefaults.standard var launch_fail_times: Int = 0 if let xx = x.object(forKey: "launch_fail_times") as? Int { launch_fail_times = xx } diff --git a/ClashX/Extensions/DateFormatter+.swift b/ClashX/Extensions/DateFormatter+.swift new file mode 100644 index 0000000..be0de0e --- /dev/null +++ b/ClashX/Extensions/DateFormatter+.swift @@ -0,0 +1,18 @@ +// +// DateFormatter+.swift +// ClashX +// +// Created by Etan Chen on 2019/12/14. +// Copyright © 2019 west2online. All rights reserved. +// + +import Cocoa + +extension DateFormatter { + static var js: DateFormatter { + let dateFormatter = DateFormatter() + dateFormatter.locale = Locale(identifier: NSCalendar.Identifier.ISO8601.rawValue) + dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SZ" + return dateFormatter + } +} diff --git a/ClashX/General/ApiRequest.swift b/ClashX/General/ApiRequest.swift index 1fdf1f2..318e424 100644 --- a/ClashX/General/ApiRequest.swift +++ b/ClashX/General/ApiRequest.swift @@ -166,6 +166,13 @@ class ApiRequest { ApiRequest.shared.proxyRespCache = proxies } + static func requestProxyProviderList(completeHandler: ((ClashProviderResp) -> Void)? = nil) { + req("/providers/proxies").responseData { res in + let provider = ClashProviderResp.create(try? res.result.get()) + completeHandler?(provider) + } + } + static func updateAllowLan(allow: Bool, completeHandler: (() -> Void)? = nil) { req("/configs", method: .patch, diff --git a/ClashX/General/Managers/MenuItemFactory.swift b/ClashX/General/Managers/MenuItemFactory.swift index d9ba864..1a08f5b 100644 --- a/ClashX/General/Managers/MenuItemFactory.swift +++ b/ClashX/General/Managers/MenuItemFactory.swift @@ -17,26 +17,31 @@ class MenuItemFactory { return } - ApiRequest.requestProxyGroupList() { - proxyInfo in - var menuItems = [NSMenuItem]() + ApiRequest.requestProxyProviderList() { + proxyprovider in - for proxy in proxyInfo.proxyGroups { - var menu: NSMenuItem? - switch proxy.type { - case .select: menu = self.generateSelectorMenuItem(proxyGroup: proxy, proxyInfo: proxyInfo) - case .urltest, .fallback: menu = generateUrlTestFallBackMenuItem(proxyGroup: proxy, proxyInfo: proxyInfo) - case .loadBalance: - menu = generateLoadBalanceMenuItem(proxyGroup: proxy, proxyInfo: proxyInfo) - default: continue - } + ApiRequest.requestProxyGroupList() { + proxyInfo in + proxyInfo.updateProvider(proxyprovider) - if let menu = menu { - menuItems.append(menu) - menu.isEnabled = true + var menuItems = [NSMenuItem]() + for proxy in proxyInfo.proxyGroups { + var menu: NSMenuItem? + switch proxy.type { + case .select: menu = self.generateSelectorMenuItem(proxyGroup: proxy, proxyInfo: proxyInfo) + case .urltest, .fallback: menu = generateUrlTestFallBackMenuItem(proxyGroup: proxy, proxyInfo: proxyInfo) + case .loadBalance: + menu = generateLoadBalanceMenuItem(proxyGroup: proxy, proxyInfo: proxyInfo) + default: continue + } + + if let menu = menu { + menuItems.append(menu) + menu.isEnabled = true + } } + completionHandler(menuItems.reversed()) } - completionHandler(menuItems.reversed()) } } @@ -152,6 +157,21 @@ class MenuItemFactory { return menu } + static func generateProviderMenuItems(_ provider: ClashProvider) -> NSMenuItem? { + let menu = NSMenuItem(title: provider.name, action: nil, keyEquivalent: "") + let submenu = NSMenu(title: provider.name) + + for proxy in provider.proxies { + let proxyMenuItem = NSMenuItem(title: proxy.name, action: #selector(empty), keyEquivalent: "") + proxyMenuItem.target = MenuItemFactory.self + proxyMenuItem.submenu = generateHistoryMenu(proxy) + submenu.addItem(proxyMenuItem) + } + + menu.submenu = submenu + return menu + } + static func generateSwitchConfigMenuItems() -> [NSMenuItem] { var items = [NSMenuItem]() for config in ConfigManager.getConfigFilesList() { diff --git a/ClashX/Models/ClashProvider.swift b/ClashX/Models/ClashProvider.swift new file mode 100644 index 0000000..4a7460b --- /dev/null +++ b/ClashX/Models/ClashProvider.swift @@ -0,0 +1,50 @@ +// +// ClashProvider.swift +// ClashX +// +// Created by yichengchen on 2019/12/14. +// Copyright © 2019 west2online. All rights reserved. +// + +import Cocoa + +class ClashProviderResp: Codable { + let allProviders: [ClashProxyName: ClashProvider] + lazy var providers: [ClashProxyName: ClashProvider] = { + return allProviders.filter({ $0.value.vehicleType != .Compatible }) + }() + + private init() { + allProviders = [:] + } + + static func create(_ data: Data?) -> ClashProviderResp { + guard let data = data else { return ClashProviderResp() } + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .formatted(DateFormatter.js) + return (try? decoder.decode(ClashProviderResp.self, from: data)) ?? ClashProviderResp() + } + + private enum CodingKeys: String, CodingKey { + case allProviders = "providers" + } +} + +class ClashProvider: Codable { + enum ProviderType: String, Codable { + case Proxy + case String + } + + enum ProviderVehicleType: String, Codable { + case HTTP + case File + case Compatible + case Unknown + } + + let name: String + let proxies: [ClashProxy] + let type: ProviderType + let vehicleType: ProviderVehicleType +} diff --git a/ClashX/Models/ClashProxy.swift b/ClashX/Models/ClashProxy.swift index ce2cd48..88cea56 100644 --- a/ClashX/Models/ClashProxy.swift +++ b/ClashX/Models/ClashProxy.swift @@ -67,11 +67,12 @@ class ClashProxySpeedHistory: Codable { } class ClashProxy: Codable { - var name: ClashProxyName = "" + let name: ClashProxyName let type: ClashProxyType let all: [ClashProxyName]? let history: [ClashProxySpeedHistory] let now: ClashProxyName? + var providerProxy = false weak var enclosingResp: ClashProxyResp? = nil lazy var speedtestAble: [ClashProxyName] = { @@ -79,14 +80,16 @@ class ClashProxy: Codable { var proxys = [ClashProxyName]() for proxy in allProxys { if let p = resp.proxiesMap[proxy], !ClashProxyType.isProxyGroup(p) { - proxys.append(proxy) + if !p.providerProxy { + proxys.append(proxy) + } } } return proxys }() private enum CodingKeys: String, CodingKey { - case type, all, history, now + case type, all, history, now, name } lazy var maxProxyName: String = { @@ -105,7 +108,7 @@ class ClashProxy: Codable { class ClashProxyResp { let proxies: [ClashProxy] - let proxiesMap: [ClashProxyName: ClashProxy] + var proxiesMap: [ClashProxyName: ClashProxy] init(_ data: Any?) { guard @@ -122,18 +125,14 @@ class ClashProxyResp { var proxiesMap = [ClashProxyName: ClashProxy]() let decoder = JSONDecoder() - let dateFormatter = DateFormatter() - dateFormatter.locale = Locale(identifier: NSCalendar.Identifier.ISO8601.rawValue) - dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SZ" - decoder.dateDecodingStrategy = .formatted(dateFormatter) - for (key, value) in proxies { + decoder.dateDecodingStrategy = .formatted(DateFormatter.js) + for value in proxies.values { guard let data = try? JSONSerialization.data(withJSONObject: value, options: .prettyPrinted) else { continue } guard let proxy = try? decoder.decode(ClashProxy.self, from: data) else { continue } - proxy.name = key proxiesModel.append(proxy) proxiesMap[proxy.name] = proxy } @@ -145,6 +144,15 @@ class ClashProxyResp { } } + func updateProvider(_ providerResp: ClashProviderResp) { + for provider in providerResp.providers.values { + for proxy in provider.proxies { + proxy.providerProxy = true + proxiesMap[proxy.name] = proxy + } + } + } + lazy var proxiesSortMap: [ClashProxyName: Int] = { var map = [ClashProxyName: Int]() for (idx, proxy) in (self.proxiesMap["GLOBAL"]?.all ?? []).enumerated() { diff --git a/ClashX/go.mod b/ClashX/go.mod index 2d22d60..8768bd4 100644 --- a/ClashX/go.mod +++ b/ClashX/go.mod @@ -1,7 +1,7 @@ module github.com/yichengchen/clashX/ClashX require ( - github.com/Dreamacro/clash v0.16.1-0.20191212162924-0822b526d5d8 + github.com/Dreamacro/clash v0.16.1-0.20191214101333-eae06a4a7d3e github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 ) diff --git a/ClashX/go.sum b/ClashX/go.sum index ccf2315..9e75c24 100644 --- a/ClashX/go.sum +++ b/ClashX/go.sum @@ -1,5 +1,5 @@ -github.com/Dreamacro/clash v0.16.1-0.20191212162924-0822b526d5d8 h1:GeVCWlqa0YfzWDFAzTRKfhGqL8WolYmY5fHDY1xwUfI= -github.com/Dreamacro/clash v0.16.1-0.20191212162924-0822b526d5d8/go.mod h1:3AN6ikXSGG+ZHpQQMirTGu8t3tokzyLU/NUXb5QYZ9Q= +github.com/Dreamacro/clash v0.16.1-0.20191214101333-eae06a4a7d3e h1:W3sFYmxO8s77tkcLnHQTrVmhWZXohOrEU1Bvxb+HgoE= +github.com/Dreamacro/clash v0.16.1-0.20191214101333-eae06a4a7d3e/go.mod h1:3AN6ikXSGG+ZHpQQMirTGu8t3tokzyLU/NUXb5QYZ9Q= github.com/Dreamacro/go-shadowsocks2 v0.1.5 h1:BizWSjmwzAyQoslz6YhJYMiAGT99j9cnm9zlxVr+kyI= github.com/Dreamacro/go-shadowsocks2 v0.1.5/go.mod h1:LSXCjyHesPY3pLjhwff1mQX72ItcBT/N2xNC685cYeU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=