misc: copy all clashx files

This commit is contained in:
mrFq1
2023-05-25 23:20:44 +08:00
parent edc1529027
commit bbaf15f5b8
12 changed files with 1371 additions and 83 deletions

View File

@@ -15,6 +15,17 @@
0155D39629F2342F00869830 /* TrafficGraphView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0155D39529F2342F00869830 /* TrafficGraphView.swift */; };
0155D39829F23BDE00869830 /* OverviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0155D39729F23BDE00869830 /* OverviewView.swift */; };
0172CB5129E5AE670072DDEF /* SwiftUIViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172CB5029E5AE670072DDEF /* SwiftUIViewExtensions.swift */; };
0172F12F2A1FB06100EE2B6D /* DateFormatter+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F12C2A1FB06100EE2B6D /* DateFormatter+.swift */; };
0172F1312A1FB06100EE2B6D /* String+Encode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F12E2A1FB06100EE2B6D /* String+Encode.swift */; };
0172F1352A1FB0B900EE2B6D /* ConfigManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F1342A1FB0B900EE2B6D /* ConfigManager.swift */; };
0172F1372A1FB0CD00EE2B6D /* ApiRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F1362A1FB0CD00EE2B6D /* ApiRequest.swift */; };
0172F1392A1FB0E900EE2B6D /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F1382A1FB0E900EE2B6D /* Logger.swift */; };
0172F1412A1FB10D00EE2B6D /* ClashConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F13B2A1FB10C00EE2B6D /* ClashConnection.swift */; };
0172F1422A1FB10D00EE2B6D /* ClashRuleProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F13C2A1FB10D00EE2B6D /* ClashRuleProvider.swift */; };
0172F1432A1FB10D00EE2B6D /* ClashRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F13D2A1FB10D00EE2B6D /* ClashRule.swift */; };
0172F1442A1FB10D00EE2B6D /* ClashConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F13E2A1FB10D00EE2B6D /* ClashConfig.swift */; };
0172F1452A1FB10D00EE2B6D /* ClashProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F13F2A1FB10D00EE2B6D /* ClashProvider.swift */; };
0172F1462A1FB10D00EE2B6D /* ClashProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F1402A1FB10D00EE2B6D /* ClashProxy.swift */; };
017753C029EF7FB2006999DB /* APIServerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017753BF29EF7FB1006999DB /* APIServerItem.swift */; };
017DCADD29E83BFD00B9622A /* RuleProviderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017DCADC29E83BFD00B9622A /* RuleProviderView.swift */; };
017F9AAA2A0DFEBD00B81497 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 017F9AA92A0DFEBD00B81497 /* Introspect */; };
@@ -36,22 +47,10 @@
0192318329DD70B400539EDD /* SidebarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0192318229DD70B400539EDD /* SidebarItem.swift */; };
0192318529DD7DCD00539EDD /* SidebarItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0192318429DD7DCD00539EDD /* SidebarItemView.swift */; };
0192318729DD83FF00539EDD /* OverviewTopItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0192318629DD83FF00539EDD /* OverviewTopItemView.swift */; };
0192B5B729DE5098002CDBF3 /* ApiRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0192B5B629DE5098002CDBF3 /* ApiRequest.swift */; };
0192B5BA29DE50F8002CDBF3 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 0192B5B929DE50F8002CDBF3 /* Alamofire */; };
0192B5BD29DE5113002CDBF3 /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = 0192B5BC29DE5113002CDBF3 /* SwiftyJSON */; };
0192B5C029DE5134002CDBF3 /* Starscream in Frameworks */ = {isa = PBXBuildFile; productRef = 0192B5BF29DE5134002CDBF3 /* Starscream */; };
0192B5CA29DE5151002CDBF3 /* ClashConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0192B5C229DE5150002CDBF3 /* ClashConfig.swift */; };
0192B5CB29DE5151002CDBF3 /* ClashProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0192B5C329DE5150002CDBF3 /* ClashProvider.swift */; };
0192B5CD29DE5151002CDBF3 /* ClashProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0192B5C529DE5150002CDBF3 /* ClashProxy.swift */; };
0192B5CF29DE5151002CDBF3 /* ClashConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0192B5C729DE5150002CDBF3 /* ClashConnection.swift */; };
0192B5D029DE5151002CDBF3 /* ClashRuleProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0192B5C829DE5150002CDBF3 /* ClashRuleProvider.swift */; };
0192B5D129DE5151002CDBF3 /* ClashRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0192B5C929DE5150002CDBF3 /* ClashRule.swift */; };
0192B5D429DE5190002CDBF3 /* CocoaLumberjackSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 0192B5D329DE5190002CDBF3 /* CocoaLumberjackSwift */; };
0192B5D629DE5207002CDBF3 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0192B5D529DE5206002CDBF3 /* Logger.swift */; };
0192B5F629DE5262002CDBF3 /* ConfigManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0192B5E029DE5261002CDBF3 /* ConfigManager.swift */; };
0192B61129DE5292002CDBF3 /* Array+Safe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0192B60629DE5292002CDBF3 /* Array+Safe.swift */; };
0192B61429DE5292002CDBF3 /* String+Encode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0192B60929DE5292002CDBF3 /* String+Encode.swift */; };
0192B61A29DE5292002CDBF3 /* DateFormatter+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0192B60F29DE5292002CDBF3 /* DateFormatter+.swift */; };
019D6A8729F015DF00A6AC02 /* ArrayExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 019D6A8629F015DF00A6AC02 /* ArrayExtensions.swift */; };
019D6A9629F194C600A6AC02 /* DSFSparkline in Frameworks */ = {isa = PBXBuildFile; productRef = 019D6A9529F194C600A6AC02 /* DSFSparkline */; };
01A351A229DD8F440054894E /* RuleItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A351A129DD8F440054894E /* RuleItemView.swift */; };
@@ -76,6 +75,17 @@
0155D39529F2342F00869830 /* TrafficGraphView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrafficGraphView.swift; sourceTree = "<group>"; };
0155D39729F23BDE00869830 /* OverviewView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverviewView.swift; sourceTree = "<group>"; };
0172CB5029E5AE670072DDEF /* SwiftUIViewExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIViewExtensions.swift; sourceTree = "<group>"; };
0172F12C2A1FB06100EE2B6D /* DateFormatter+.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DateFormatter+.swift"; sourceTree = "<group>"; };
0172F12E2A1FB06100EE2B6D /* String+Encode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Encode.swift"; sourceTree = "<group>"; };
0172F1342A1FB0B900EE2B6D /* ConfigManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigManager.swift; sourceTree = "<group>"; };
0172F1362A1FB0CD00EE2B6D /* ApiRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApiRequest.swift; sourceTree = "<group>"; };
0172F1382A1FB0E900EE2B6D /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
0172F13B2A1FB10C00EE2B6D /* ClashConnection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashConnection.swift; sourceTree = "<group>"; };
0172F13C2A1FB10D00EE2B6D /* ClashRuleProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashRuleProvider.swift; sourceTree = "<group>"; };
0172F13D2A1FB10D00EE2B6D /* ClashRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashRule.swift; sourceTree = "<group>"; };
0172F13E2A1FB10D00EE2B6D /* ClashConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashConfig.swift; sourceTree = "<group>"; };
0172F13F2A1FB10D00EE2B6D /* ClashProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashProvider.swift; sourceTree = "<group>"; };
0172F1402A1FB10D00EE2B6D /* ClashProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashProxy.swift; sourceTree = "<group>"; };
017753BF29EF7FB1006999DB /* APIServerItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIServerItem.swift; sourceTree = "<group>"; };
017DCADC29E83BFD00B9622A /* RuleProviderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleProviderView.swift; sourceTree = "<group>"; };
017F9AAB2A0E0B2300B81497 /* ProxyGroupRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyGroupRowView.swift; sourceTree = "<group>"; };
@@ -98,18 +108,6 @@
0192318229DD70B400539EDD /* SidebarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarItem.swift; sourceTree = "<group>"; };
0192318429DD7DCD00539EDD /* SidebarItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarItemView.swift; sourceTree = "<group>"; };
0192318629DD83FF00539EDD /* OverviewTopItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverviewTopItemView.swift; sourceTree = "<group>"; };
0192B5B629DE5098002CDBF3 /* ApiRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ApiRequest.swift; path = ../../../ClashX.Meta/ClashX/General/ApiRequest.swift; sourceTree = "<group>"; };
0192B5C229DE5150002CDBF3 /* ClashConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashConfig.swift; sourceTree = "<group>"; };
0192B5C329DE5150002CDBF3 /* ClashProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashProvider.swift; sourceTree = "<group>"; };
0192B5C529DE5150002CDBF3 /* ClashProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashProxy.swift; sourceTree = "<group>"; };
0192B5C729DE5150002CDBF3 /* ClashConnection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashConnection.swift; sourceTree = "<group>"; };
0192B5C829DE5150002CDBF3 /* ClashRuleProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashRuleProvider.swift; sourceTree = "<group>"; };
0192B5C929DE5150002CDBF3 /* ClashRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashRule.swift; sourceTree = "<group>"; };
0192B5D529DE5206002CDBF3 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Logger.swift; path = ../../../ClashX.Meta/ClashX/Basic/Logger.swift; sourceTree = "<group>"; };
0192B5E029DE5261002CDBF3 /* ConfigManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigManager.swift; sourceTree = "<group>"; };
0192B60629DE5292002CDBF3 /* Array+Safe.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+Safe.swift"; sourceTree = "<group>"; };
0192B60929DE5292002CDBF3 /* String+Encode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Encode.swift"; sourceTree = "<group>"; };
0192B60F29DE5292002CDBF3 /* DateFormatter+.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DateFormatter+.swift"; sourceTree = "<group>"; };
019D6A8629F015DF00A6AC02 /* ArrayExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayExtensions.swift; sourceTree = "<group>"; };
01A351A129DD8F440054894E /* RuleItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleItemView.swift; sourceTree = "<group>"; };
01A351A829DD9CB00054894E /* Connections.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Connections.swift; sourceTree = "<group>"; };
@@ -152,6 +150,44 @@
path = APISetting;
sourceTree = "<group>";
};
0172F12B2A1FB05900EE2B6D /* Extensions */ = {
isa = PBXGroup;
children = (
0172F12C2A1FB06100EE2B6D /* DateFormatter+.swift */,
0172F12E2A1FB06100EE2B6D /* String+Encode.swift */,
);
path = Extensions;
sourceTree = "<group>";
};
0172F1322A1FB0AA00EE2B6D /* General */ = {
isa = PBXGroup;
children = (
0172F1332A1FB0B000EE2B6D /* Managers */,
);
path = General;
sourceTree = "<group>";
};
0172F1332A1FB0B000EE2B6D /* Managers */ = {
isa = PBXGroup;
children = (
0172F1342A1FB0B900EE2B6D /* ConfigManager.swift */,
);
path = Managers;
sourceTree = "<group>";
};
0172F13A2A1FB10300EE2B6D /* Models */ = {
isa = PBXGroup;
children = (
0172F13E2A1FB10D00EE2B6D /* ClashConfig.swift */,
0172F13B2A1FB10C00EE2B6D /* ClashConnection.swift */,
0172F13F2A1FB10D00EE2B6D /* ClashProvider.swift */,
0172F1402A1FB10D00EE2B6D /* ClashProxy.swift */,
0172F13D2A1FB10D00EE2B6D /* ClashRule.swift */,
0172F13C2A1FB10D00EE2B6D /* ClashRuleProvider.swift */,
);
path = Models;
sourceTree = "<group>";
};
018003AF2A136D3E0070226E /* Providers */ = {
isa = PBXGroup;
children = (
@@ -253,57 +289,15 @@
0192B5B529DE506D002CDBF3 /* ClashX Links */ = {
isa = PBXGroup;
children = (
0192B60429DE5292002CDBF3 /* Extensions */,
0192B5D729DE5261002CDBF3 /* General */,
0192B5B629DE5098002CDBF3 /* ApiRequest.swift */,
0192B5D529DE5206002CDBF3 /* Logger.swift */,
0192B5C129DE5150002CDBF3 /* Models */,
0172F1362A1FB0CD00EE2B6D /* ApiRequest.swift */,
0172F1382A1FB0E900EE2B6D /* Logger.swift */,
0172F1322A1FB0AA00EE2B6D /* General */,
0172F12B2A1FB05900EE2B6D /* Extensions */,
0172F13A2A1FB10300EE2B6D /* Models */,
);
path = "ClashX Links";
sourceTree = "<group>";
};
0192B5C129DE5150002CDBF3 /* Models */ = {
isa = PBXGroup;
children = (
0192B5C229DE5150002CDBF3 /* ClashConfig.swift */,
0192B5C329DE5150002CDBF3 /* ClashProvider.swift */,
0192B5C529DE5150002CDBF3 /* ClashProxy.swift */,
0192B5C729DE5150002CDBF3 /* ClashConnection.swift */,
0192B5C829DE5150002CDBF3 /* ClashRuleProvider.swift */,
0192B5C929DE5150002CDBF3 /* ClashRule.swift */,
);
name = Models;
path = ../../../ClashX.Meta/ClashX/Models;
sourceTree = "<group>";
};
0192B5D729DE5261002CDBF3 /* General */ = {
isa = PBXGroup;
children = (
0192B5D829DE5261002CDBF3 /* Managers */,
);
name = General;
path = ../../../ClashX.Meta/ClashX/General;
sourceTree = "<group>";
};
0192B5D829DE5261002CDBF3 /* Managers */ = {
isa = PBXGroup;
children = (
0192B5E029DE5261002CDBF3 /* ConfigManager.swift */,
);
path = Managers;
sourceTree = "<group>";
};
0192B60429DE5292002CDBF3 /* Extensions */ = {
isa = PBXGroup;
children = (
0192B60629DE5292002CDBF3 /* Array+Safe.swift */,
0192B60929DE5292002CDBF3 /* String+Encode.swift */,
0192B60F29DE5292002CDBF3 /* DateFormatter+.swift */,
);
name = Extensions;
path = ../../../ClashX.Meta/ClashX/Extensions;
sourceTree = "<group>";
};
01A351A029DD8F210054894E /* Rules */ = {
isa = PBXGroup;
children = (
@@ -459,47 +453,46 @@
01DCEFB32A150FB300DBBDB3 /* ProxyProvidersRowView.swift in Sources */,
0192318729DD83FF00539EDD /* OverviewTopItemView.swift in Sources */,
0192318029DD5E0B00539EDD /* LogsView.swift in Sources */,
0172F1392A1FB0E900EE2B6D /* Logger.swift in Sources */,
01505C4E2A14AAEB001ACC4F /* ProviderProxiesView.swift in Sources */,
0172F1412A1FB10D00EE2B6D /* ClashConnection.swift in Sources */,
0192318529DD7DCD00539EDD /* SidebarItemView.swift in Sources */,
0172F1442A1FB10D00EE2B6D /* ClashConfig.swift in Sources */,
0172F1452A1FB10D00EE2B6D /* ClashProvider.swift in Sources */,
0192317E29DD5E0100539EDD /* ConfigView.swift in Sources */,
0155D39629F2342F00869830 /* TrafficGraphView.swift in Sources */,
01A3EF042A120103003038B5 /* DBProxyStorage.swift in Sources */,
0172F1432A1FB10D00EE2B6D /* ClashRule.swift in Sources */,
017F9AAC2A0E0B2300B81497 /* ProxyGroupRowView.swift in Sources */,
0172F1372A1FB0CD00EE2B6D /* ApiRequest.swift in Sources */,
017DCADD29E83BFD00B9622A /* RuleProviderView.swift in Sources */,
0192318329DD70B400539EDD /* SidebarItem.swift in Sources */,
0155D39829F23BDE00869830 /* OverviewView.swift in Sources */,
0192B5D629DE5207002CDBF3 /* Logger.swift in Sources */,
0192B5CB29DE5151002CDBF3 /* ClashProvider.swift in Sources */,
0192B5CF29DE5151002CDBF3 /* ClashConnection.swift in Sources */,
018C836C29E17505006366D3 /* ClashApiDatasStorage.swift in Sources */,
018A61BD29E9A2ED008608C0 /* APISettingView.swift in Sources */,
0172F1462A1FB10D00EE2B6D /* ClashProxy.swift in Sources */,
010F693B29ED639A00BAAFB5 /* ClashServerAppStorage.swift in Sources */,
01F885D329E04E21008241EB /* ProxyGroupView.swift in Sources */,
0192315F29DD4DCF00539EDD /* ClashX_DashboardApp.swift in Sources */,
018003B12A136DDB0070226E /* ProvidersView.swift in Sources */,
0192B5CD29DE5151002CDBF3 /* ClashProxy.swift in Sources */,
0192B61129DE5292002CDBF3 /* Array+Safe.swift in Sources */,
01A351A229DD8F440054894E /* RuleItemView.swift in Sources */,
01F885D529E053DE008241EB /* ProxyItemView.swift in Sources */,
0192B5D029DE5151002CDBF3 /* ClashRuleProvider.swift in Sources */,
0172F1312A1FB06100EE2B6D /* String+Encode.swift in Sources */,
01DCEFB12A150E8B00DBBDB3 /* RuleProvidersRowView.swift in Sources */,
01F5E3F42A1E53F4008F3DEB /* ConfigItemView.swift in Sources */,
0172F1352A1FB0B900EE2B6D /* ConfigManager.swift in Sources */,
0192317129DD566000539EDD /* SidebarView.swift in Sources */,
0192B5D129DE5151002CDBF3 /* ClashRule.swift in Sources */,
0192317C29DD5DF200539EDD /* ConnectionsView.swift in Sources */,
0172F12F2A1FB06100EE2B6D /* DateFormatter+.swift in Sources */,
017753C029EF7FB2006999DB /* APIServerItem.swift in Sources */,
0172F1422A1FB10D00EE2B6D /* ClashRuleProvider.swift in Sources */,
015278082A15F9FD00516236 /* ProxyProviderInfoView.swift in Sources */,
0192B61429DE5292002CDBF3 /* String+Encode.swift in Sources */,
0192317829DD5DA500539EDD /* ProxiesView.swift in Sources */,
0192B5F629DE5262002CDBF3 /* ConfigManager.swift in Sources */,
0192B61A29DE5292002CDBF3 /* DateFormatter+.swift in Sources */,
01F885CF29DFD8DF008241EB /* CollectionsTableView.swift in Sources */,
018AFEA52A1B5F7A0076E66B /* ProgressButton.swift in Sources */,
01505C4A2A147B84001ACC4F /* DBProviderStorage.swift in Sources */,
0192B5CA29DE5151002CDBF3 /* ClashConfig.swift in Sources */,
0172CB5129E5AE670072DDEF /* SwiftUIViewExtensions.swift in Sources */,
01A351A929DD9CB00054894E /* Connections.swift in Sources */,
0192B5B729DE5098002CDBF3 /* ApiRequest.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@@ -0,0 +1,543 @@
//
// ApiRequest.swift
// ClashX
//
// Created by CYC on 2018/7/30.
// Copyright © 2018 yichengchen. All rights reserved.
//
import Alamofire
import Cocoa
import Starscream
import SwiftyJSON
import SwiftUI
protocol ApiRequestStreamDelegate: AnyObject {
func didUpdateTraffic(up: Int, down: Int)
func didGetLog(log: String, level: String)
func streamStatusChanged()
}
typealias ErrorString = String
struct ClashVersion: Decodable {
let version: String
let meta: Bool?
}
class ApiRequest {
static let shared = ApiRequest()
private var proxyRespCache: ClashProxyResp?
private lazy var logQueue = DispatchQueue(label: "com.ClashX.core.log")
static let clashRequestQueue = DispatchQueue(label: "com.clashx.clashRequestQueue")
@objc enum ProviderType: Int {
case rule, proxy
func apiString() -> String {
self == .proxy ? "proxies" : "rules"
}
func logString() -> String {
self == .proxy ? "Proxy" : "Rule"
}
}
private init() {
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 604800
configuration.timeoutIntervalForResource = 604800
configuration.httpMaximumConnectionsPerHost = 100
configuration.requestCachePolicy = .reloadIgnoringLocalCacheData
alamoFireManager = Session(configuration: configuration)
}
private static func authHeader() -> HTTPHeaders {
let secret = ConfigManager.shared.overrideSecret ?? ConfigManager.shared.apiSecret
return (secret.count > 0) ? ["Authorization": "Bearer \(secret)"] : [:]
}
@discardableResult
private static func req(
_ url: String,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default
)
-> DataRequest {
guard ConfigManager.shared.isRunning else {
return AF.request("")
}
return shared.alamoFireManager
.request(ConfigManager.apiUrl + url,
method: method,
parameters: parameters,
encoding: encoding,
headers: authHeader())
}
weak var delegate: ApiRequestStreamDelegate?
private var trafficWebSocket: WebSocket?
private var loggingWebSocket: WebSocket?
private var trafficWebSocketRetryDelay: TimeInterval = 1
private var loggingWebSocketRetryDelay: TimeInterval = 1
private var trafficWebSocketRetryTimer: Timer?
private var loggingWebSocketRetryTimer: Timer?
private var alamoFireManager: Session
static func requestVersion(completeHandler: @escaping ((ClashVersion?) -> Void)) {
shared.alamoFireManager
.request(ConfigManager.apiUrl + "/version",
method: .get,
headers: authHeader())
.responseDecodable(of: ClashVersion.self) {
resp in
switch resp.result {
case let .success(ver):
completeHandler(ver)
case let .failure(err):
completeHandler(nil)
}
}
}
static func requestConfig(completeHandler: @escaping ((ClashConfig) -> Void)) {
req("/configs").responseDecodable(of: ClashConfig.self) {
resp in
switch resp.result {
case let .success(config):
completeHandler(config)
case let .failure(err):
Logger.log(err.localizedDescription)
// NSUserNotificationCenter.default.post(title: "Error", info: err.localizedDescription)
}
}
}
static func updateOutBoundMode(mode: ClashProxyMode, callback: ((Bool) -> Void)? = nil) {
req("/configs", method: .patch, parameters: ["mode": mode.rawValue], encoding: JSONEncoding.default)
.responseData { response in
switch response.result {
case .success:
callback?(true)
case .failure:
callback?(false)
}
}
}
static func updateLogLevel(level: ClashLogLevel, callback: ((Bool) -> Void)? = nil) {
req("/configs", method: .patch, parameters: ["log-level": level.rawValue], encoding: JSONEncoding.default).responseData(completionHandler: { response in
switch response.result {
case .success:
callback?(true)
case .failure:
callback?(false)
}
})
}
static func requestProxyGroupList(completeHandler: ((ClashProxyResp) -> Void)? = nil) {
req("/proxies").responseData {
res in
let proxies = ClashProxyResp(try? res.result.get())
ApiRequest.shared.proxyRespCache = proxies
completeHandler?(proxies)
}
}
static func requestProxyProviderList(completeHandler: ((ClashProviderResp) -> Void)? = nil) {
req("/providers/proxies")
.responseDecodable(of: ClashProviderResp.self, decoder: ClashProviderResp.decoder) { resp in
switch resp.result {
case let .success(providerResp):
completeHandler?(providerResp)
case let .failure(err):
Logger.log("requestProxyProviderList error \(err.localizedDescription)")
completeHandler?(ClashProviderResp())
}
}
}
static func updateAllowLan(allow: Bool, completeHandler: (() -> Void)? = nil) {
Logger.log("update allow lan:\(allow)", level: .debug)
req("/configs",
method: .patch,
parameters: ["allow-lan": allow],
encoding: JSONEncoding.default).response {
_ in
completeHandler?()
}
}
static func updateProxyGroup(group: String, selectProxy: String, callback: @escaping ((Bool) -> Void)) {
req("/proxies/\(group.encoded)",
method: .put,
parameters: ["name": selectProxy],
encoding: JSONEncoding.default)
.responseData { response in
callback(response.response?.statusCode == 204)
}
}
static func getAllProxyList(callback: @escaping (([ClashProxyName]) -> Void)) {
requestProxyGroupList {
proxyInfo in
let lists: [ClashProxyName] = proxyInfo.proxiesMap["GLOBAL"]?.all ?? []
callback(lists)
}
}
static func getMergedProxyData(complete: ((ClashProxyResp?) -> Void)? = nil) {
let group = DispatchGroup()
group.enter()
group.enter()
var provider: ClashProviderResp?
var proxyInfo: ClashProxyResp?
group.notify(queue: .main) {
guard let proxyInfo = proxyInfo, let proxyprovider = provider else {
assertionFailure()
complete?(nil)
return
}
proxyInfo.updateProvider(proxyprovider)
complete?(proxyInfo)
}
ApiRequest.requestProxyProviderList {
proxyprovider in
provider = proxyprovider
group.leave()
}
ApiRequest.requestProxyGroupList {
proxy in
proxyInfo = proxy
group.leave()
}
}
static func getProxyDelay(proxyName: String, callback: @escaping ((Int) -> Void)) {
req("/proxies/\(proxyName.encoded)/delay",
method: .get,
parameters: ["timeout": 2500, "url": ConfigManager.shared.benchMarkUrl])
.responseData { res in
switch res.result {
case let .success(value):
let json = JSON(value)
callback(json["delay"].intValue)
case .failure:
callback(0)
}
}
}
static func getGroupDelay(groupName: String, callback: @escaping (([String: Int]) -> Void)) {
req("/group/\(groupName.encoded)/delay",
method: .get,
parameters: ["timeout": 2500, "url": ConfigManager.shared.benchMarkUrl])
.responseData { res in
switch res.result {
case let .success(value):
let dic = try? JSONDecoder().decode([String: Int].self, from: value)
callback(dic ?? [:])
case .failure:
callback([:])
}
}
}
static func getRules(completeHandler: @escaping ([ClashRule]) -> Void) {
req("/rules").responseData { res in
guard let data = try? res.result.get() else { return }
ClashRuleProviderResp.init()
let rule = ClashRuleResponse.fromData(data)
completeHandler(rule.rules ?? [])
}
}
static func healthCheck(proxy: ClashProviderName, completeHandler: (() -> Void)? = nil) {
Logger.log("HeathCheck for \(proxy) started")
req("/providers/proxies/\(proxy.encoded)/healthcheck").response { res in
if res.response?.statusCode == 204 {
Logger.log("HeathCheck for \(proxy) finished")
} else {
Logger.log("HeathCheck for \(proxy) failed:\(res.response?.statusCode ?? -1)")
}
completeHandler?()
}
}
}
// MARK: - Connections
extension ApiRequest {
static func getConnections(completeHandler: @escaping (ClashConnectionSnapShot) -> Void) {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(DateFormatter.js)
req("/connections").responseDecodable(of: ClashConnectionSnapShot.self, decoder: decoder) { resp in
switch resp.result {
case let .success(snapshot):
completeHandler(snapshot)
case .failure:
return
// assertionFailure()
// completeHandler(ClashConnectionSnapShot())
}
}
}
static func closeConnection(_ conn: ClashConnection) {
req("/connections/".appending(conn.id), method: .delete).response { _ in }
}
static func closeAllConnection() {
req("/connections", method: .delete).response { _ in }
}
}
// MARK: - Meta
extension ApiRequest {
static func updateAllProviders(for type: ProviderType, completeHandler: ((Int) -> Void)? = nil) {
var failuresCount = 0
let group = DispatchGroup()
group.enter()
if type == .proxy {
requestProxyProviderList { resp in
resp.allProviders.filter {
$0.value.vehicleType == .HTTP
}.forEach {
group.enter()
updateProvider(for: .proxy, name: $0.key) {
if !$0 {
failuresCount += 1
}
group.leave()
}
}
group.leave()
}
} else {
requestRuleProviderList { resp in
resp.allProviders.forEach {
group.enter()
updateProvider(for: .rule, name: $0.key) {
if !$0 {
failuresCount += 1
}
group.leave()
}
}
group.leave()
}
}
group.notify(queue: .main) {
completeHandler?(failuresCount)
}
}
static func updateProvider(for type: ProviderType, name: String, completeHandler: ((Bool) -> Void)? = nil) {
let s = "Update \(type.logString()) Provider"
Logger.log("\(s) \(name)")
req("/providers/\(type.apiString())/\(name)", method: .put).response {
let re = $0.response?.statusCode == 204
Logger.log("\(s) \(name) \(re ? "success" : "failed")")
completeHandler?(re)
}
}
static func requestRuleProviderList(completeHandler: @escaping (ClashRuleProviderResp) -> Void) {
req("/providers/rules")
.responseDecodable(of: ClashRuleProviderResp.self, decoder: ClashProviderResp.decoder) { resp in
switch resp.result {
case let .success(providerResp):
completeHandler(providerResp)
case let .failure(err):
Logger.log("Get Rule providers error \(err.errorDescription ?? "unknown")" )
completeHandler(ClashRuleProviderResp())
}
}
}
static func updateGEO(completeHandler: ((Bool) -> Void)? = nil) {
Logger.log("UpdateGEO")
req("/configs/geo", method: .post).response {
let re = $0.response?.statusCode == 204
completeHandler?(re)
// Logger.log("UpdateGEO \(re ? "success" : "failed")")
Logger.log("Updating GEO Databases...")
}
}
static func updateTun(enable: Bool, completeHandler: (() -> Void)? = nil) {
Logger.log("update tun:\(enable)", level: .debug)
req("/configs",
method: .patch,
parameters: ["tun": ["enable": enable]],
encoding: JSONEncoding.default).response {
_ in
completeHandler?()
}
}
static func updateSniffing(enable: Bool, completeHandler: (() -> Void)? = nil) {
Logger.log("update sniffing:\(enable)", level: .debug)
req("/configs",
method: .patch,
parameters: ["sniffing": enable],
encoding: JSONEncoding.default).response {
_ in
completeHandler?()
}
}
static func flushFakeipCache(completeHandler: ((Bool) -> Void)? = nil) {
Logger.log("FlushFakeipCache")
req("/cache/fakeip/flush",
method: .post).response {
let re = $0.response?.statusCode == 204
completeHandler?(re)
Logger.log("FlushFakeipCache \(re ? "success" : "failed")")
}
}
}
// MARK: - Stream Apis
extension ApiRequest {
func resetStreamApis() {
resetLogStreamApi()
resetTrafficStreamApi()
}
func resetLogStreamApi() {
loggingWebSocketRetryTimer?.invalidate()
loggingWebSocketRetryTimer = nil
loggingWebSocketRetryDelay = 1
requestLog()
}
func resetTrafficStreamApi() {
trafficWebSocketRetryTimer?.invalidate()
trafficWebSocketRetryTimer = nil
trafficWebSocketRetryDelay = 1
requestTrafficInfo()
}
private func requestTrafficInfo() {
trafficWebSocketRetryTimer?.invalidate()
trafficWebSocketRetryTimer = nil
trafficWebSocket?.disconnect(forceTimeout: 0.5)
let socket = WebSocket(url: URL(string: ConfigManager.apiUrl.appending("/traffic"))!)
for header in ApiRequest.authHeader() {
socket.request.setValue(header.value, forHTTPHeaderField: header.name)
}
socket.delegate = self
socket.connect()
trafficWebSocket = socket
}
private func requestLog() {
loggingWebSocketRetryTimer?.invalidate()
loggingWebSocketRetryTimer = nil
loggingWebSocket?.disconnect(forceTimeout: 1)
let uriString = "/logs?level=".appending(ConfigManager.selectLoggingApiLevel.rawValue)
let socket = WebSocket(url: URL(string: ConfigManager.apiUrl.appending(uriString))!)
for header in ApiRequest.authHeader() {
socket.request.setValue(header.value, forHTTPHeaderField: header.name)
}
socket.delegate = self
socket.callbackQueue = logQueue
socket.connect()
loggingWebSocket = socket
}
}
extension ApiRequest: WebSocketDelegate {
func websocketDidConnect(socket: WebSocketClient) {
guard let webSocket = socket as? WebSocket else { return }
if webSocket == trafficWebSocket {
trafficWebSocketRetryDelay = 1
Logger.log("trafficWebSocket did Connect", level: .debug)
ConfigManager.shared.isRunning = true
delegate?.streamStatusChanged()
} else {
loggingWebSocketRetryDelay = 1
Logger.log("loggingWebSocket did Connect", level: .debug)
}
}
func websocketDidDisconnect(socket: WebSocketClient, error: Error?) {
if (socket as? WebSocket) == trafficWebSocket {
ConfigManager.shared.isRunning = false
delegate?.streamStatusChanged()
}
guard let err = error else {
return
}
Logger.log(err.localizedDescription, level: .error)
guard let webSocket = socket as? WebSocket else { return }
if webSocket == trafficWebSocket {
Logger.log("trafficWebSocket did disconnect", level: .debug)
trafficWebSocketRetryTimer?.invalidate()
trafficWebSocketRetryTimer =
Timer.scheduledTimer(withTimeInterval: trafficWebSocketRetryDelay, repeats: false, block: {
[weak self] _ in
if self?.trafficWebSocket?.isConnected == true { return }
self?.requestTrafficInfo()
})
trafficWebSocketRetryDelay *= 2
} else {
Logger.log("loggingWebSocket did disconnect", level: .debug)
loggingWebSocketRetryTimer =
Timer.scheduledTimer(withTimeInterval: loggingWebSocketRetryDelay, repeats: false, block: {
[weak self] _ in
if self?.loggingWebSocket?.isConnected == true { return }
self?.requestLog()
})
loggingWebSocketRetryDelay *= 2
}
}
func websocketDidReceiveMessage(socket: WebSocketClient, text: String) {
guard let webSocket = socket as? WebSocket else { return }
let json = JSON(parseJSON: text)
if webSocket == trafficWebSocket {
delegate?.didUpdateTraffic(up: json["up"].intValue, down: json["down"].intValue)
} else {
delegate?.didGetLog(log: json["payload"].stringValue, level: json["type"].string ?? "info")
}
}
func websocketDidReceiveData(socket: WebSocketClient, data: Data) {}
}

View File

@@ -0,0 +1,24 @@
//
// DateFormatter+.swift
// ClashX
//
// Created by yicheng 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
}
static var provider: DateFormatter {
let f = DateFormatter()
f.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSSZZ"
return f
}
}

View File

@@ -0,0 +1,15 @@
//
// String+Encode.swift
// ClashX
//
// Created by yicheng on 2019/12/11.
// Copyright © 2019 west2online. All rights reserved.
//
import Cocoa
extension String {
var encoded: String {
return addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? ""
}
}

View File

@@ -0,0 +1,59 @@
//
// ConfigManager.swift
// ClashX
//
// Created by CYC on 2018/6/12.
// Copyright © 2018 yichengchen. All rights reserved.
//
import Cocoa
import Foundation
class ConfigManager {
static let shared = ConfigManager()
var apiPort = "9090"
var apiSecret: String = ""
var overrideApiURL: URL?
var overrideSecret: String?
var isRunning: Bool = false {
didSet {
NotificationCenter.default.post(.init(name: .init("ClashRunningStateChanged")))
}
}
var benchMarkUrl: String = UserDefaults.standard.string(forKey: "benchMarkUrl") ?? "http://cp.cloudflare.com/generate_204" {
didSet {
UserDefaults.standard.set(benchMarkUrl, forKey: "benchMarkUrl")
}
}
static var apiUrl: String {
if let override = shared.overrideApiURL {
return override.absoluteString
}
return "http://127.0.0.1:\(shared.apiPort)"
}
static var webSocketUrl: String {
if let override = shared.overrideApiURL, var comp = URLComponents(url: override, resolvingAgainstBaseURL: true) {
if comp.scheme == "https" {
comp.scheme = "wss"
} else {
comp.scheme = "ws"
}
return comp.url?.absoluteString ?? ""
}
return "ws://127.0.0.1:\(shared.apiPort)"
}
static var selectLoggingApiLevel: ClashLogLevel {
get {
return ClashLogLevel(rawValue: UserDefaults.standard.string(forKey: "selectLoggingApiLevel") ?? "") ?? .info
}
set {
UserDefaults.standard.set(newValue.rawValue, forKey: "selectLoggingApiLevel")
}
}
}

View File

@@ -0,0 +1,52 @@
//
// Logger.swift
// ClashX
//
// Created by CYC on 2018/8/7.
// Copyright © 2018 yichengchen. All rights reserved.
//
import Foundation
import CocoaLumberjackSwift
class Logger {
static let shared = Logger()
var fileLogger: DDFileLogger = DDFileLogger()
private init() {
#if DEBUG
DDLog.add(DDOSLogger.sharedInstance)
#endif
// default time zone is "UTC"
let dataFormatter = DateFormatter()
dataFormatter.setLocalizedDateFormatFromTemplate("YYYY/MM/dd HH:mm:ss:SSS")
fileLogger.logFormatter = DDLogFileFormatterDefault.init(dateFormatter: dataFormatter)
fileLogger.rollingFrequency = TimeInterval(60 * 60 * 24) // 24 hours
fileLogger.logFileManager.maximumNumberOfLogFiles = 3
DDLog.add(fileLogger)
dynamicLogLevel = ConfigManager.selectLoggingApiLevel.toDDLogLevel()
}
private func logToFile(msg: String, level: ClashLogLevel) {
switch level {
case .debug, .silent:
DDLogDebug(msg)
case .error:
DDLogError(msg)
case .info:
DDLogInfo(msg)
case .warning:
DDLogWarn(msg)
case .unknow:
DDLogWarn(msg)
}
}
static func log(_ msg: String, level: ClashLogLevel = .info) {
shared.logToFile(msg: "[\(level.rawValue)] \(msg)", level: level)
}
func logFilePath() -> String {
return fileLogger.logFileManager.sortedLogFilePaths.first ?? ""
}
}

View File

@@ -0,0 +1,110 @@
//
// ClashConfig.swift
// ClashX
//
// Created by CYC on 2018/7/30.
// Copyright © 2018 yichengchen. All rights reserved.
//
import Foundation
import CocoaLumberjackSwift
enum ClashProxyMode: String, Codable {
case rule
case global
case direct
}
extension ClashProxyMode {
var name: String {
switch self {
case .rule: return NSLocalizedString("Rule", comment: "")
case .global: return NSLocalizedString("Global", comment: "")
case .direct: return NSLocalizedString("Direct", comment: "")
}
}
}
enum ClashLogLevel: String, Codable {
case info
case warning
case error
case debug
case silent
case unknow = "unknown"
func toDDLogLevel() -> DDLogLevel {
switch self {
case .info:
return .info
case .warning:
return .warning
case .error:
return .error
case .debug:
return .debug
case .silent:
return .off
case .unknow:
return .error
}
}
}
class ClashConfig: Codable {
var port: Int
var socksPort: Int
var redirPort: Int
var allowLan: Bool
var mixedPort: Int
var mode: ClashProxyMode
var logLevel: ClashLogLevel
var sniffing: Bool
var ipv6: Bool
var tun: Tun
var interfaceName: String
struct Tun: Codable {
let enable: Bool
let device: String
let stack: String
// let dns-hijack: [String]
// let auto-route: Bool
// let auto-detect-interface: Bool
}
var usedHttpPort: Int {
if mixedPort > 0 {
return mixedPort
}
return port
}
var usedSocksPort: Int {
if mixedPort > 0 {
return mixedPort
}
return socksPort
}
private enum CodingKeys: String, CodingKey {
case port, socksPort = "socks-port", redirPort = "redir-port", mixedPort = "mixed-port", allowLan = "allow-lan", mode, logLevel = "log-level", sniffing, tun, interfaceName = "interface-name", ipv6
}
static func fromData(_ data: Data) -> ClashConfig? {
let decoder = JSONDecoder()
do {
return try decoder.decode(ClashConfig.self, from: data)
} catch let err {
Logger.log((err as NSError).description, level: .error)
return nil
}
}
func copy() -> ClashConfig? {
guard let data = try? JSONEncoder().encode(self) else { return nil }
let copy = try? JSONDecoder().decode(ClashConfig.self, from: data)
return copy
}
}

View File

@@ -0,0 +1,110 @@
//
// ClashConnection.swift
// ClashX
//
// Created by yicheng on 2019/10/28.
// Copyright © 2019 west2online. All rights reserved.
//
import Cocoa
import DifferenceKit
struct ClashConnectionSnapShot: Codable {
let downloadTotal: Int
let uploadTotal: Int
let connections: [ClashConnection]
}
struct ClashConnection: Codable, Hashable {
let id: String
let chains: [String]
let upload: Int64
let download: Int64
let start: Date
let rule: String
let rulePayload: String
let metadata: MetaConnectionData
}
struct MetaConnectionData: Codable, Hashable {
let uid: Int
let network: String
let type: String
let sourceIP: String
let destinationIP: String
let sourcePort: String
let destinationPort: String
let inboundIP: String
let inboundPort: String
let inboundName: String
let host: String
let dnsMode: String
let process: String
let processPath: String
let specialProxy: String
let specialRules: String
let remoteDestination: String
let sniffHost: String
}
class ClashConnectionObject: NSObject, Differentiable {
@objc let id: String
@objc let host: String
@objc let sniffHost: String
@objc let process: String
@objc let download: Int64
@objc let upload: Int64
let downloadString: String
let uploadString: String
let chains: [String]
@objc let chainString: String
@objc let ruleString: String
@objc let startDate: Date
let startString: String
@objc let source: String
@objc let destinationIP: String?
@objc let type: String
var differenceIdentifier: String {
return id
}
func isContentEqual(to source: ClashConnectionObject) -> Bool {
download == source.download &&
upload == source.upload &&
startString == source.startString
}
init(_ conn: ClashConnection) {
let byteCountFormatter = ByteCountFormatter()
let startFormatter = RelativeDateTimeFormatter()
startFormatter.unitsStyle = .short
let metadata = conn.metadata
id = conn.id
host = "\(metadata.host == "" ? metadata.destinationIP : metadata.host):\(metadata.destinationPort)"
sniffHost = metadata.sniffHost == "" ? "-" : metadata.sniffHost
process = metadata.process
download = conn.download
downloadString = byteCountFormatter.string(fromByteCount: conn.download)
upload = conn.upload
uploadString = byteCountFormatter.string(fromByteCount: conn.upload)
chains = conn.chains
chainString = conn.chains.reversed().joined(separator: "/")
ruleString = conn.rulePayload == "" ? conn.rule : "\(conn.rule) :: \(conn.rulePayload)"
startDate = conn.start
startString = startFormatter.localizedString(for: conn.start, relativeTo: Date())
source = "\(metadata.sourceIP):\(metadata.sourcePort)"
destinationIP = [metadata.remoteDestination,
metadata.destinationIP,
metadata.host].first(where: { $0 != "" })
type = "\(metadata.type)(\(metadata.network))"
}
}

View File

@@ -0,0 +1,67 @@
//
// 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 })
}()
init() {
allProviders = [:]
}
static var decoder: JSONDecoder {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(DateFormatter.js)
return decoder
}
private enum CodingKeys: String, CodingKey {
case allProviders = "providers"
}
}
class ClashProvider: Codable {
let id = UUID().uuidString
enum ProviderType: String, Codable {
case Proxy
case String
}
enum ProviderVehicleType: String, Codable {
case HTTP
case File
case Compatible
case Unknown
}
let name: ClashProviderName
let proxies: [ClashProxy]
let type: ProviderType
let vehicleType: ProviderVehicleType
let updatedAt: Date?
let subscriptionInfo: ClashProviderSubInfo?
}
class ClashProviderSubInfo: Codable {
let upload: Int64
let download: Int64
let total: Int64
let expire: Int
private enum CodingKeys: String, CodingKey {
case upload = "Upload",
download = "Download",
total = "Total",
expire = "Expire"
}
}

View File

@@ -0,0 +1,248 @@
//
// ClashProxy.swift
// ClashX
//
// Created by CYC on 2019/3/17.
// Copyright © 2019 west2online. All rights reserved.
//
import Cocoa
import SwiftyJSON
enum ClashProxyType: String, Codable {
case urltest = "URLTest"
case fallback = "Fallback"
case loadBalance = "LoadBalance"
case select = "Selector"
case direct = "Direct"
case reject = "Reject"
case shadowsocks = "Shadowsocks"
case shadowsocksR = "ShadowsocksR"
case socks5 = "Socks5"
case http = "Http"
case vmess = "Vmess"
case snell = "Snell"
case trojan = "Trojan"
case relay = "Relay"
case vless = "Vless"
case hysteria = "Hysteria"
case wireguardMeta = "WireGuard"
case wireguard = "Wireguard"
case tuic = "Tuic"
case pass = "Pass"
case unknown = "Unknown"
static let proxyGroups: [ClashProxyType] = [.select, .urltest, .fallback, .loadBalance]
var isAutoGroup: Bool {
switch self {
case .urltest, .fallback, .loadBalance:
return true
default:
return false
}
}
static func isProxyGroup(_ proxy: ClashProxy) -> Bool {
switch proxy.type {
case .select, .urltest, .fallback, .loadBalance, .relay: return true
default: return false
}
}
static func isBuiltInProxy(_ proxy: ClashProxy) -> Bool {
switch proxy.name {
case "DIRECT", "REJECT", "PASS": return true
default: return false
}
}
}
typealias ClashProxyName = String
typealias ClashProviderName = String
class ClashProxySpeedHistory: Codable {
let time: Date
let delay: Int
let meanDelay: Int?
class HisDateFormaterInstance {
static let shared = HisDateFormaterInstance()
lazy var formater: DateFormatter = {
var f = DateFormatter()
f.dateFormat = "HH:mm"
return f
}()
}
lazy var delayInt: Int = {
meanDelay ?? delay
}()
lazy var delayDisplay: String = {
switch delayInt {
case 0: return NSLocalizedString("fail", comment: "")
default: return "\(delayInt) ms"
}
}()
lazy var dateDisplay: String = {
return HisDateFormaterInstance.shared.formater.string(from: time)
}()
lazy var displayString: String = "\(dateDisplay) \(delayDisplay)"
}
class ClashProxy: Codable {
let id = UUID().uuidString
let name: ClashProxyName
let type: ClashProxyType
let all: [ClashProxyName]?
let history: [ClashProxySpeedHistory]
let now: ClashProxyName?
weak var enclosingResp: ClashProxyResp?
weak var enclosingProvider: ClashProvider?
let udp: Bool
let xudp: Bool
let tfo: Bool
enum SpeedtestAbleItem {
case proxy(name: ClashProxyName)
case provider(name: ClashProxyName, provider: ClashProviderName)
case group(name: ClashProxyName)
}
private static var nameLengthCachedMap = [ClashProxyName: CGFloat]()
static func cleanCache() {
nameLengthCachedMap.removeAll()
}
lazy var speedtestAble: [SpeedtestAbleItem] = {
guard let resp = enclosingResp, let allProxys = all else { return [] }
var proxys = [SpeedtestAbleItem]()
for proxy in allProxys {
if let p = resp.proxiesMap[proxy] {
if !ClashProxyType.isProxyGroup(p) {
if let provider = p.enclosingProvider {
proxys.append(.provider(name: p.name, provider: provider.name))
} else {
proxys.append(.proxy(name: p.name))
}
} else {
proxys.append(.group(name: p.name))
}
}
}
return proxys
}()
lazy var isSpeedTestable: Bool = {
return speedtestAble.count > 0
}()
private enum CodingKeys: String, CodingKey {
case type, all, history, now, name, udp, xudp, tfo
}
lazy var maxProxyNameLength: CGFloat = {
let rect = CGSize(width: CGFloat.greatestFiniteMagnitude, height: 20)
let lengths = all?.compactMap({ name -> CGFloat in
if let length = ClashProxy.nameLengthCachedMap[name] {
return length
}
let rects = CGSize(width: CGFloat.greatestFiniteMagnitude, height: 20)
let attr = [NSAttributedString.Key.font: NSFont.menuBarFont(ofSize: 14)]
let length = (name as NSString)
.boundingRect(with: rect,
options: .usesLineFragmentOrigin,
attributes: attr).width
ClashProxy.nameLengthCachedMap[name] = length
return length
})
return lengths?.max() ?? 0
}()
}
class ClashProxyResp {
var proxies: [ClashProxy]
var proxiesMap: [ClashProxyName: ClashProxy]
var enclosingProviderResp: ClashProviderResp?
init(_ data: Data?) {
guard let data
else {
self.proxiesMap = [:]
self.proxies = []
return
}
let proxies = JSON(data)["proxies"]
var proxiesModel = [ClashProxy]()
var proxiesMap = [ClashProxyName: ClashProxy]()
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(DateFormatter.js)
for value in proxies.dictionaryValue.values {
guard let data = try? value.rawData() else {
continue
}
guard let proxy = try? decoder.decode(ClashProxy.self, from: data) else {
continue
}
proxiesModel.append(proxy)
proxiesMap[proxy.name] = proxy
}
self.proxiesMap = proxiesMap
self.proxies = proxiesModel
for proxy in self.proxies {
proxy.enclosingResp = self
}
}
func updateProvider(_ providerResp: ClashProviderResp) {
enclosingProviderResp = providerResp
for provider in providerResp.providers.values {
for proxy in provider.proxies {
proxy.enclosingProvider = provider
proxiesMap[proxy.name] = proxy
proxies.append(proxy)
}
}
}
lazy var proxiesSortMap: [ClashProxyName: Int] = {
var map = [ClashProxyName: Int]()
for (idx, proxy) in (self.proxiesMap["GLOBAL"]?.all ?? []).enumerated() {
map[proxy] = idx
}
return map
}()
lazy var proxyGroups: [ClashProxy] = {
return proxies.filter {
ClashProxyType.isProxyGroup($0)
}.sorted(by: { proxiesSortMap[$0.name] ?? -1 < proxiesSortMap[$1.name] ?? -1 })
}()
lazy var longestProxyGroupName = {
return proxyGroups.max { $1.name.count > $0.name.count }?.name ?? ""
}()
lazy var maxProxyNameLength: CGFloat = {
let rect = CGSize(width: CGFloat.greatestFiniteMagnitude, height: 20)
let attr = [NSAttributedString.Key.font: NSFont.menuBarFont(ofSize: 0)]
return (self.longestProxyGroupName as NSString)
.boundingRect(with: rect,
options: .usesLineFragmentOrigin,
attributes: attr).width
}()
}

View File

@@ -0,0 +1,36 @@
//
// ClashRule.swift
// ClashX
//
// Created by CYC on 2018/10/27.
// Copyright © 2018 west2online. All rights reserved.
//
import Foundation
import Cocoa
class ClashRule: NSObject, Codable, Identifiable {
@objc let type: String
@objc let payload: String?
@objc let proxy: String?
init(type: String, payload: String?, proxy: String?) {
self.type = type
self.payload = payload
self.proxy = proxy
}
}
class ClashRuleResponse: Codable {
var rules: [ClashRule]?
static func empty() -> ClashRuleResponse {
return ClashRuleResponse()
}
static func fromData(_ data: Data) -> ClashRuleResponse {
let decoder = JSONDecoder()
let model = try? decoder.decode(ClashRuleResponse.self, from: data)
return model ?? ClashRuleResponse.empty()
}
}

View File

@@ -0,0 +1,31 @@
//
// ClashRuleProvider.swift
// ClashX Meta
import Foundation
class ClashRuleProviderResp: Codable {
let allProviders: [ClashProxyName: ClashRuleProvider]
init() {
allProviders = [:]
}
static var decoder: JSONDecoder {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(DateFormatter.js)
return decoder
}
private enum CodingKeys: String, CodingKey {
case allProviders = "providers"
}
}
class ClashRuleProvider: NSObject, Codable {
@objc let name: ClashProviderName
let ruleCount: Int
@objc let behavior: String
@objc let type: String
let updatedAt: Date?
}