aboutsummaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2024-10-03 14:29:09 +0800
committercrupest <crupest@outlook.com>2024-10-03 14:29:09 +0800
commit39c51129d32659dd93b4d1ab9bd945a0c57df2f9 (patch)
tree300745f607c2e40aa7525408c9025c6a1e768110 /tools
parent29f91f56829266668f0b65620e8e902218d61c33 (diff)
downloadcrupest-39c51129d32659dd93b4d1ab9bd945a0c57df2f9.tar.gz
crupest-39c51129d32659dd93b4d1ab9bd945a0c57df2f9.tar.bz2
crupest-39c51129d32659dd93b4d1ab9bd945a0c57df2f9.zip
Rename secret tool.
Diffstat (limited to 'tools')
-rw-r--r--tools/Crupest.SecretTool/.gitignore (renamed from tools/Crupest.V2ray/.gitignore)0
-rw-r--r--tools/Crupest.SecretTool/Crupest.SecretTool.sln (renamed from tools/Crupest.V2ray/CrupestV2ray.sln)15
-rw-r--r--tools/Crupest.SecretTool/Crupest.SecretTool/.gitignore (renamed from tools/Crupest.V2ray/Crupest.V2ray/.gitignore)0
-rw-r--r--tools/Crupest.SecretTool/Crupest.SecretTool/Config.cs (renamed from tools/Crupest.V2ray/Crupest.V2ray/Config.cs)24
-rw-r--r--tools/Crupest.SecretTool/Crupest.SecretTool/Controller.cs (renamed from tools/Crupest.V2ray/Crupest.V2ray/V2rayController.cs)20
-rw-r--r--tools/Crupest.SecretTool/Crupest.SecretTool/Crupest.SecretTool.csproj (renamed from tools/Crupest.V2ray/Crupest.V2ray/Crupest.V2ray.csproj)0
-rw-r--r--tools/Crupest.SecretTool/Crupest.SecretTool/FileWatcher.cs (renamed from tools/Crupest.V2ray/Crupest.V2ray/FileWatcher.cs)2
-rw-r--r--tools/Crupest.SecretTool/Crupest.SecretTool/GeoDataManager.cs (renamed from tools/Crupest.V2ray/Crupest.V2ray/GeoDataManager.cs)46
-rw-r--r--tools/Crupest.SecretTool/Crupest.SecretTool/HostMatchConfig.cs123
-rw-r--r--tools/Crupest.SecretTool/Crupest.SecretTool/Program.cs (renamed from tools/Crupest.V2ray/Crupest.V2ray/Program.cs)50
-rw-r--r--tools/Crupest.SecretTool/Crupest.SecretTool/Properties/PublishProfiles/FolderProfile.pubxml (renamed from tools/Crupest.V2ray/Crupest.V2ray/Properties/PublishProfiles/FolderProfile.pubxml)2
-rw-r--r--tools/Crupest.SecretTool/Crupest.SecretTool/Proxy.cs58
-rw-r--r--tools/Crupest.SecretTool/Crupest.SecretTool/Routing.cs97
-rw-r--r--tools/Crupest.SecretTool/Crupest.SecretTool/StaticHosts.cs40
-rw-r--r--tools/Crupest.SecretTool/Crupest.SecretTool/SurgeConfigGenerator.cs (renamed from tools/Crupest.V2ray/Crupest.V2ray/SurgeConfigGenerator.cs)26
-rw-r--r--tools/Crupest.SecretTool/Crupest.SecretTool/Template.cs (renamed from tools/Crupest.V2ray/Crupest.V2ray/Template.cs)2
-rw-r--r--tools/Crupest.SecretTool/Crupest.SecretTool/ToolConfig.cs (renamed from tools/Crupest.V2ray/Crupest.V2ray/V2rayConfig.cs)31
-rw-r--r--tools/Crupest.SecretTool/Crupest.SecretTool/V4ConfigJsonObjects.cs (renamed from tools/Crupest.V2ray/Crupest.V2ray/V2rayV4ConfigJsonObjects.cs)4
-rw-r--r--tools/Crupest.SecretTool/Crupest.SecretTool/V5ConfigJsonObjects.cs (renamed from tools/Crupest.V2ray/Crupest.V2ray/V2rayV5ConfigJsonObjects.cs)4
-rw-r--r--tools/Crupest.SecretTool/Crupest.SecretTool/config.json.template (renamed from tools/Crupest.V2ray/Crupest.V2ray/config.json.template)4
-rw-r--r--tools/Crupest.SecretTool/Crupest.SecretTool/config.v5.json.template (renamed from tools/Crupest.V2ray/Crupest.V2ray/config.v5.json.template)0
-rw-r--r--tools/Crupest.SecretTool/Crupest.SecretTool/hosts.txt (renamed from tools/Crupest.V2ray/Crupest.V2ray/hosts.txt)0
-rw-r--r--tools/Crupest.SecretTool/Crupest.SecretTool/proxy.txt (renamed from tools/Crupest.V2ray/Crupest.V2ray/proxy.txt)0
-rwxr-xr-xtools/Crupest.SecretTool/build-secret.bash (renamed from tools/Crupest.V2ray/build-secret.bash)2
-rwxr-xr-xtools/Crupest.SecretTool/tools/cru-proxy-edit (renamed from tools/Crupest.V2ray/tools/cru-proxy-edit)2
-rwxr-xr-xtools/Crupest.SecretTool/tools/cru-proxy-log (renamed from tools/Crupest.V2ray/tools/cru-proxy-log)4
-rw-r--r--tools/Crupest.SecretTool/tools/crupest-secret-tool.service (renamed from tools/Crupest.V2ray/tools/crupest-v2ray.service)2
-rw-r--r--tools/Crupest.SecretTool/tools/life.crupest.secret-tool.plist (renamed from tools/Crupest.V2ray/tools/life.crupest.v2ray.plist)8
-rw-r--r--tools/Crupest.V2ray/Crupest.V2ray/ProxyFile.cs14
-rw-r--r--tools/Crupest.V2ray/Crupest.V2ray/V2rayHostMacherConfig.cs109
-rw-r--r--tools/Crupest.V2ray/Crupest.V2ray/V2rayHosts.cs42
-rw-r--r--tools/Crupest.V2ray/Crupest.V2ray/V2rayProxy.cs58
-rw-r--r--tools/Crupest.V2ray/Crupest.V2ray/V2rayRouting.cs87
-rw-r--r--tools/Crupest.V2ray/Crupest.V2ray/V2rayV5StaticHostRule.cs122
34 files changed, 445 insertions, 553 deletions
diff --git a/tools/Crupest.V2ray/.gitignore b/tools/Crupest.SecretTool/.gitignore
index ac4d8a4..ac4d8a4 100644
--- a/tools/Crupest.V2ray/.gitignore
+++ b/tools/Crupest.SecretTool/.gitignore
diff --git a/tools/Crupest.V2ray/CrupestV2ray.sln b/tools/Crupest.SecretTool/Crupest.SecretTool.sln
index 3045b4e..fde4347 100644
--- a/tools/Crupest.V2ray/CrupestV2ray.sln
+++ b/tools/Crupest.SecretTool/Crupest.SecretTool.sln
@@ -1,4 +1,4 @@
-
+
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.7.34024.191
@@ -8,7 +8,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.gitignore = .gitignore
EndProjectSection
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Crupest.V2ray", "Crupest.V2ray\Crupest.V2ray.csproj", "{154D49F2-242E-4384-8D34-73774231AA75}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Crupest.SecretTool", "Crupest.SecretTool\Crupest.SecretTool.csproj", "{D6335AE4-FD22-49CD-9624-37371F3B4F82}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -16,17 +16,14 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {154D49F2-242E-4384-8D34-73774231AA75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {154D49F2-242E-4384-8D34-73774231AA75}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {154D49F2-242E-4384-8D34-73774231AA75}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {154D49F2-242E-4384-8D34-73774231AA75}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D6335AE4-FD22-49CD-9624-37371F3B4F82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D6335AE4-FD22-49CD-9624-37371F3B4F82}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D6335AE4-FD22-49CD-9624-37371F3B4F82}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D6335AE4-FD22-49CD-9624-37371F3B4F82}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
- GlobalSection(NestedProjects) = preSolution
- {154D49F2-242E-4384-8D34-73774231AA75} = {F4C2CE80-CDF8-4B08-8912-D1F0F14196AD}
- EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B1E8FD9C-9157-4F4E-8265-4B37F30EEC5E}
EndGlobalSection
diff --git a/tools/Crupest.V2ray/Crupest.V2ray/.gitignore b/tools/Crupest.SecretTool/Crupest.SecretTool/.gitignore
index c936492..c936492 100644
--- a/tools/Crupest.V2ray/Crupest.V2ray/.gitignore
+++ b/tools/Crupest.SecretTool/Crupest.SecretTool/.gitignore
diff --git a/tools/Crupest.V2ray/Crupest.V2ray/Config.cs b/tools/Crupest.SecretTool/Crupest.SecretTool/Config.cs
index 2ef18f0..ff58551 100644
--- a/tools/Crupest.V2ray/Crupest.V2ray/Config.cs
+++ b/tools/Crupest.SecretTool/Crupest.SecretTool/Config.cs
@@ -1,4 +1,4 @@
-namespace Crupest.V2ray;
+namespace Crupest.SecretTool;
public record ConfigItem(string Value, int LineNumber);
@@ -12,20 +12,24 @@ public class DictionaryConfig(string configString, List<string>? requiredKeys =
foreach (var line in lines)
{
- var trimmedLine = line.Trim();
- if (string.IsNullOrEmpty(trimmedLine) || trimmedLine.StartsWith('#'))
+ var l = line;
+ var beginOfComment = l.IndexOf('#');
+ if (beginOfComment >= 0)
{
- lineNumber++;
- continue;
+ l = line[..beginOfComment];
}
-
- var equalIndex = trimmedLine.IndexOf('=');
- if (equalIndex == -1)
+ l = l.Trim();
+ if (!string.IsNullOrEmpty(l))
{
- throw new FormatException($"No '=' found in line {lineNumber}.");
+ var equalIndex = l.IndexOf('=');
+ if (equalIndex == -1)
+ {
+ throw new FormatException($"No '=' found in line {lineNumber}.");
+ }
+
+ config.Add(l[..equalIndex].Trim(), new ConfigItem(l[(equalIndex + 1)..].Trim(), lineNumber));
}
- config.Add(trimmedLine[..equalIndex].Trim(), new ConfigItem(trimmedLine[(equalIndex + 1)..].Trim(), lineNumber));
lineNumber++;
}
diff --git a/tools/Crupest.V2ray/Crupest.V2ray/V2rayController.cs b/tools/Crupest.SecretTool/Crupest.SecretTool/Controller.cs
index 4656216..0803b01 100644
--- a/tools/Crupest.V2ray/Crupest.V2ray/V2rayController.cs
+++ b/tools/Crupest.SecretTool/Crupest.SecretTool/Controller.cs
@@ -1,10 +1,10 @@
using System.Diagnostics;
-namespace Crupest.V2ray;
+namespace Crupest.SecretTool;
-public class V2rayController(string executablePath, string configPath, string? assetPath)
+public class Controller(string executablePath, string configPath, string? assetPath)
{
- public const string V2rayAssetEnvironmentVariableName = "v2ray.location.asset";
+ public const string ToolAssetEnvironmentVariableName = "v2ray.location.asset";
public static string? FindExecutable(string contentDir, out bool isLocal, string? executableName = null)
{
@@ -16,11 +16,11 @@ public class V2rayController(string executablePath, string configPath, string? a
executableName += ".exe";
}
- var localV2rayPath = Path.Combine(contentDir, executableName);
- if (File.Exists(localV2rayPath))
+ var localToolPath = Path.Combine(contentDir, executableName);
+ if (File.Exists(localToolPath))
{
isLocal = true;
- return localV2rayPath;
+ return localToolPath;
}
var paths = Environment.GetEnvironmentVariable("PATH")?.Split(Path.PathSeparator);
@@ -28,10 +28,10 @@ public class V2rayController(string executablePath, string configPath, string? a
{
foreach (var p in paths)
{
- var v2rayPath = Path.Combine(p, executableName);
- if (File.Exists(v2rayPath))
+ var toolPath = Path.Combine(p, executableName);
+ if (File.Exists(toolPath))
{
- return v2rayPath;
+ return toolPath;
}
}
}
@@ -57,7 +57,7 @@ public class V2rayController(string executablePath, string configPath, string? a
startInfo.ArgumentList.Add(ConfigPath);
if (AssetPath is not null)
{
- startInfo.EnvironmentVariables[V2rayAssetEnvironmentVariableName] = AssetPath;
+ startInfo.EnvironmentVariables[ToolAssetEnvironmentVariableName] = AssetPath;
}
process.StartInfo = startInfo;
diff --git a/tools/Crupest.V2ray/Crupest.V2ray/Crupest.V2ray.csproj b/tools/Crupest.SecretTool/Crupest.SecretTool/Crupest.SecretTool.csproj
index 0812e4c..0812e4c 100644
--- a/tools/Crupest.V2ray/Crupest.V2ray/Crupest.V2ray.csproj
+++ b/tools/Crupest.SecretTool/Crupest.SecretTool/Crupest.SecretTool.csproj
diff --git a/tools/Crupest.V2ray/Crupest.V2ray/FileWatcher.cs b/tools/Crupest.SecretTool/Crupest.SecretTool/FileWatcher.cs
index 547adeb..193874f 100644
--- a/tools/Crupest.V2ray/Crupest.V2ray/FileWatcher.cs
+++ b/tools/Crupest.SecretTool/Crupest.SecretTool/FileWatcher.cs
@@ -1,4 +1,4 @@
-namespace Crupest.V2ray;
+namespace Crupest.SecretTool;
public class FileWatcher(string directory, List<string> fileNames)
{
diff --git a/tools/Crupest.V2ray/Crupest.V2ray/GeoDataManager.cs b/tools/Crupest.SecretTool/Crupest.SecretTool/GeoDataManager.cs
index 3dce3f6..2d9e2eb 100644
--- a/tools/Crupest.V2ray/Crupest.V2ray/GeoDataManager.cs
+++ b/tools/Crupest.SecretTool/Crupest.SecretTool/GeoDataManager.cs
@@ -1,6 +1,6 @@
using System.IO.Compression;
-namespace Crupest.V2ray;
+namespace Crupest.SecretTool;
public interface IGeoSiteEntry
{
@@ -13,7 +13,7 @@ public record GeoSiteIncludeEntry(string Value, string ContainingSite) : IGeoSit
public bool IsInclude => true;
}
-public record GeoSiteRuleEntry(V2rayHostMatcherKind Kind, string Value, List<string> Attributes, string ContainingSite) : IGeoSiteEntry
+public record GeoSiteRuleEntry(HostMatchKind Kind, string Value, List<string> Attributes, string ContainingSite) : IGeoSiteEntry
{
public bool IsInclude => false;
}
@@ -45,26 +45,26 @@ public record GeoSite(string Name, List<IGeoSiteEntry> Entries)
throw new FormatException($"Invalid geo site rule '{name}' in line {line}. More than one ':'.");
}
- V2rayHostMatcherKind kind;
+ HostMatchKind kind;
if (segments.Length == 2)
{
kind = segments[0] switch
{
- "domain" => kind = V2rayHostMatcherKind.DomainSuffix,
- "full" => kind = V2rayHostMatcherKind.DomainFull,
- "keyword" => kind = V2rayHostMatcherKind.DomainKeyword,
- "regexp" => kind = V2rayHostMatcherKind.DomainRegex,
+ "domain" => kind = HostMatchKind.DomainSuffix,
+ "full" => kind = HostMatchKind.DomainFull,
+ "keyword" => kind = HostMatchKind.DomainKeyword,
+ "regexp" => kind = HostMatchKind.DomainRegex,
_ => throw new FormatException($"Invalid geo site rule '{name}' in line {line}. Unknown matcher.")
};
}
else
{
- kind = V2rayHostMatcherKind.DomainSuffix;
+ kind = HostMatchKind.DomainSuffix;
}
var domainSegments = segments[^1].Split('@', StringSplitOptions.TrimEntries);
var domain = domainSegments[0];
- if (kind != V2rayHostMatcherKind.DomainRegex && Uri.CheckHostName(domain) != UriHostNameType.Dns)
+ if (kind != HostMatchKind.DomainRegex && Uri.CheckHostName(domain) != UriHostNameType.Dns)
{
throw new FormatException($"Invalid geo site rule '{name}' in line {line}. Invalid domain.");
}
@@ -108,11 +108,11 @@ public class GeoSiteData(string directory)
}
public List<GeoSiteRuleEntry> GetEntriesRecursive(List<string> sites,
- List<V2rayHostMatcherKind>? onlyMatcherKinds = null, List<string>? onlyAttributes = null)
+ List<HostMatchKind>? onlyMatcherKinds = null, List<string>? onlyAttributes = null)
{
List<GeoSiteRuleEntry> entries = [];
HashSet<string> visited = [];
- HashSet<V2rayHostMatcherKind>? kinds = onlyMatcherKinds?.ToHashSet();
+ HashSet<HostMatchKind>? kinds = onlyMatcherKinds?.ToHashSet();
void Visit(string site)
{
@@ -164,12 +164,16 @@ public class GeoDataManager
public const string GeoSiteFileName = "geosite.dat";
public const string GeoIpFileName = "geoip.dat";
public const string GeoIpCnFileName = "geoip-only-cn-private.dat";
- public const string V2rayGithubOrganization = "v2fly";
- public const string V2rayGeoSiteGithubRepository = "domain-list-community";
- public const string V2rayGeoIpGithubRepository = "geoip";
- public const string V2rayGeoSiteCnGithubReleaseFilename = "dlc.dat";
- public const string V2rayGeoIpGithubReleaseFilename = "geoip.dat";
- public const string V2rayGeoIpCnGithubReleaseFilename = "geoip-only-cn-private.dat";
+
+ public static class ToolGithub
+ {
+ public const string Organization = "v2fly";
+ public const string GeoSiteRepository = "domain-list-community";
+ public const string GeoIpRepository = "geoip";
+ public const string GeoSiteReleaseFilename = "dlc.dat";
+ public const string GeoIpReleaseFilename = "geoip.dat";
+ public const string GeoIpCnReleaseFilename = "geoip-only-cn-private.dat";
+ }
public static GeoDataManager Instance { get; } = new GeoDataManager();
@@ -179,9 +183,9 @@ public class GeoDataManager
{
Assets =
[
- new("geosite", GeoSiteFileName, V2rayGithubOrganization, V2rayGeoSiteGithubRepository, V2rayGeoSiteGithubRepository),
- new("geoip", GeoIpFileName, V2rayGithubOrganization, V2rayGeoIpGithubRepository, V2rayGeoIpGithubReleaseFilename),
- new("geoip-cn", GeoIpCnFileName, V2rayGithubOrganization, V2rayGeoIpGithubRepository, V2rayGeoIpCnGithubReleaseFilename),
+ new("geosite", GeoSiteFileName, ToolGithub.Organization, ToolGithub.GeoSiteRepository, ToolGithub.GeoSiteRepository),
+ new("geoip", GeoIpFileName, ToolGithub.Organization, ToolGithub.GeoIpRepository, ToolGithub.GeoIpReleaseFilename),
+ new("geoip-cn", GeoIpCnFileName, ToolGithub.Organization, ToolGithub.GeoIpRepository, ToolGithub.GeoIpCnReleaseFilename),
];
}
@@ -272,7 +276,7 @@ public class GeoDataManager
{
var archivePath = Path.Combine(tempDirectoryPath, zipFileName);
var extractPath = Path.Combine(tempDirectoryPath, "repo");
- GithubDownloadRepository(httpClient, V2rayGithubOrganization, V2rayGeoSiteGithubRepository, archivePath, silent);
+ GithubDownloadRepository(httpClient, ToolGithub.Organization, ToolGithub.GeoSiteRepository, archivePath, silent);
if (!silent) { Console.WriteLine($"Extract geo data to {extractPath}."); }
Directory.CreateDirectory(extractPath);
Unzip(archivePath, extractPath);
diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool/HostMatchConfig.cs b/tools/Crupest.SecretTool/Crupest.SecretTool/HostMatchConfig.cs
new file mode 100644
index 0000000..5cc0c3d
--- /dev/null
+++ b/tools/Crupest.SecretTool/Crupest.SecretTool/HostMatchConfig.cs
@@ -0,0 +1,123 @@
+namespace Crupest.SecretTool;
+
+public enum HostMatchKind
+{
+ DomainFull,
+ DomainSuffix,
+ DomainKeyword,
+ DomainRegex,
+ Ip,
+ GeoSite,
+ GeoIp,
+}
+
+public static class HostMatchKindExtensions
+{
+ public static bool IsDomain(this HostMatchKind kind)
+ {
+ return kind.IsNonRegexDomain() || kind == HostMatchKind.DomainRegex;
+ }
+
+ public static bool IsNonRegexDomain(this HostMatchKind kind)
+ {
+ return kind is HostMatchKind.DomainFull or HostMatchKind.DomainSuffix or HostMatchKind.DomainKeyword;
+ }
+
+
+ public static List<HostMatchKind> DomainMatchKinds { get; } = [HostMatchKind.DomainFull, HostMatchKind.DomainSuffix, HostMatchKind.DomainKeyword, HostMatchKind.DomainRegex];
+
+ public static List<HostMatchKind> NonRegexDomainMatchKinds { get; } = [HostMatchKind.DomainFull, HostMatchKind.DomainSuffix, HostMatchKind.DomainKeyword];
+}
+
+public record HostMatchConfigItem(HostMatchKind Kind, string MatchString, List<string> Values);
+
+public class HostMatchConfig(string configString, List<HostMatchKind> allowedMatchKinds, int minComponentCount = -1, int maxComponentCount = -1)
+{
+ private static List<HostMatchConfigItem> Parse(string configString, List<HostMatchKind> allowedMatchKinds, int minComponentCount = -1, int maxComponentCount = -1)
+ {
+ var items = new ListConfig(configString).Config;
+ var result = new List<HostMatchConfigItem>();
+
+ foreach (var item in items)
+ {
+ var lineNumber = item.LineNumber;
+ var line = item.Value;
+ var hasExplicitMatchKind = false;
+ var segments = line.Split(' ', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToList();
+
+ foreach (var matchKind in Enum.GetValues<HostMatchKind>())
+ {
+ var matchKindName = Enum.GetName(matchKind) ?? throw new Exception("No such match kind.");
+ hasExplicitMatchKind = true;
+ if (segments[0] == matchKindName)
+ {
+ if (segments.Count < 2)
+ {
+ throw new FormatException($"Explicit match item needs a value in line {lineNumber}.");
+ }
+ if (allowedMatchKinds.Contains(matchKind))
+ {
+ if (matchKind.IsNonRegexDomain() && Uri.CheckHostName(matchKindName) != UriHostNameType.Dns)
+ {
+ throw new FormatException($"Invalid domain format in line {lineNumber}.");
+ }
+
+ var components = segments[2..].ToList();
+ if (minComponentCount > 0 && components.Count < minComponentCount)
+ {
+ throw new FormatException($"Too few components in line {lineNumber}, at least {minComponentCount} required.");
+ }
+ if (maxComponentCount >= 0 && components.Count > maxComponentCount)
+ {
+ throw new FormatException($"Too many components in line {lineNumber}, only {maxComponentCount} allowed.");
+ }
+ result.Add(new HostMatchConfigItem(matchKind, segments[1], components));
+ }
+ else
+ {
+ throw new FormatException($"Match kind {matchKindName} is not allowed at line {lineNumber}.");
+ }
+ }
+ }
+
+ if (!hasExplicitMatchKind)
+ {
+ if (minComponentCount > 0 && segments.Count - 1 < minComponentCount)
+ {
+ throw new FormatException($"Too few components in line {lineNumber}, at least {minComponentCount} required.");
+ }
+ if (maxComponentCount >= 0 && segments.Count - 1 > maxComponentCount)
+ {
+ throw new FormatException($"Too many components in line {lineNumber}, only {maxComponentCount} allowed.");
+ }
+ result.Add(new HostMatchConfigItem(HostMatchKind.DomainSuffix, segments[0], segments.Count == 1 ? [] : segments[1..]));
+ }
+ }
+ return result;
+ }
+
+ public string ConfigString { get; } = configString;
+ public List<HostMatchKind> AllowedMatchKinds { get; } = allowedMatchKinds;
+ public int MinComponentCount { get; } = minComponentCount;
+ public int MaxComponentCount { get; } = maxComponentCount;
+ public List<HostMatchConfigItem> Items { get; } = Parse(configString, allowedMatchKinds, minComponentCount, maxComponentCount);
+}
+
+public class HostMatchConfigFile
+{
+ public HostMatchConfigFile(string path, List<HostMatchKind> allowedMatchKinds, int minComponentCount = -1, int maxComponentCount = -1)
+ {
+ Path = path;
+ FileContent = File.ReadAllText(path);
+ Config = new HostMatchConfig(FileContent, allowedMatchKinds, minComponentCount, maxComponentCount); ;
+ }
+
+ public string Path { get; }
+ public string FileContent { get; }
+ public HostMatchConfig Config { get; }
+}
+
+public class ProxyFile(string path) :
+ HostMatchConfigFile(path, [.. Enum.GetValues<HostMatchKind>()], maxComponentCount: 0)
+{
+}
diff --git a/tools/Crupest.V2ray/Crupest.V2ray/Program.cs b/tools/Crupest.SecretTool/Crupest.SecretTool/Program.cs
index 0e98861..afbcde9 100644
--- a/tools/Crupest.V2ray/Crupest.V2ray/Program.cs
+++ b/tools/Crupest.SecretTool/Crupest.SecretTool/Program.cs
@@ -1,56 +1,56 @@
using System.Reflection;
-namespace Crupest.V2ray;
+namespace Crupest.SecretTool;
public static class Program
{
- public static string Name { get; } = typeof(Program).Namespace ?? throw new Exception("Can't get the name of Crupest.V2ray.");
+ public static string Name { get; } = typeof(Program).Namespace ?? throw new Exception("Can't get the name of Crupest.SecretTool.");
- public static string CrupestV2rayDirectory { get; } =
+ public static string CrupestSecretToolDirectory { get; } =
Environment.GetEnvironmentVariable("CRUPEST_V2RAY_DIR") ??
Path.GetFullPath(Path.GetDirectoryName(
- Assembly.GetExecutingAssembly().Location) ?? throw new Exception("Can't get the path of Crupest.V2ray."));
+ Assembly.GetExecutingAssembly().Location) ?? throw new Exception("Can't get the path of Crupest.SecretTool."));
private const string ConfigOutputFileName = "config.json";
private const string SurgeRuleSetChinaOutputFileName = "ChinaRuleSet.txt";
private const string SurgeRuleSetGlobalOutputFileName = "GlobalRuleSet.txt";
- public static void RunV2rayAndWatchConfigChange()
+ public static void RunToolAndWatchConfigChange()
{
- var v2rayPath = V2rayController.FindExecutable(CrupestV2rayDirectory, out var isLocal) ??
- throw new Exception("Can't find v2ray executable either in Crupest.V2ray directory or in PATH.");
+ var executablePath = Controller.FindExecutable(CrupestSecretToolDirectory, out var isLocal) ??
+ throw new Exception("Can't find v2ray executable either in Crupest.SecretTool directory or in PATH.");
string? assetsPath;
if (isLocal)
{
- assetsPath = CrupestV2rayDirectory;
- var assetsComplete = GeoDataManager.Instance.HasAllAssets(CrupestV2rayDirectory, out var missing);
+ assetsPath = CrupestSecretToolDirectory;
+ var assetsComplete = GeoDataManager.Instance.HasAllAssets(CrupestSecretToolDirectory, out var missing);
if (!assetsComplete)
{
- throw new Exception($"Missing assets: {string.Join(", ", missing)} in {CrupestV2rayDirectory}. This v2ray is local. So only use assets in Crupest.V2ray directory.");
+ throw new Exception($"Missing assets: {string.Join(", ", missing)} in {CrupestSecretToolDirectory}. This v2ray is local. So only use assets in Crupest.SecretTool directory.");
}
}
else
{
- assetsPath = CrupestV2rayDirectory;
- var assetsComplete = GeoDataManager.Instance.HasAllAssets(CrupestV2rayDirectory, out var missing);
+ assetsPath = CrupestSecretToolDirectory;
+ var assetsComplete = GeoDataManager.Instance.HasAllAssets(CrupestSecretToolDirectory, out var missing);
if (!assetsComplete)
{
- Console.WriteLine($"Missing assets: {string.Join(", ", missing)} in {CrupestV2rayDirectory}. This v2ray is global. So fallback to its own assets.");
+ Console.WriteLine($"Missing assets: {string.Join(", ", missing)} in {CrupestSecretToolDirectory}. This v2ray is global. So fallback to its own assets.");
assetsPath = null;
}
}
- var v2rayController = new V2rayController(v2rayPath, Path.Combine(CrupestV2rayDirectory, ConfigOutputFileName), assetsPath);
- var configFileWatcher = new FileWatcher(CrupestV2rayDirectory, V2rayConfig.ConfigFileNames);
+ var controller = new Controller(executablePath, Path.Combine(CrupestSecretToolDirectory, ConfigOutputFileName), assetsPath);
+ var configFileWatcher = new FileWatcher(CrupestSecretToolDirectory, ToolConfig.ConfigFileNames);
- V2rayConfig.FromDirectoryAndWriteToFile(CrupestV2rayDirectory, Path.Join(CrupestV2rayDirectory, ConfigOutputFileName));
- v2rayController.Start();
+ ToolConfig.FromDirectoryAndWriteToFile(CrupestSecretToolDirectory, Path.Join(CrupestSecretToolDirectory, ConfigOutputFileName));
+ controller.Start();
configFileWatcher.OnChanged += () =>
{
- V2rayConfig.FromDirectoryAndWriteToFile(CrupestV2rayDirectory, Path.Join(CrupestV2rayDirectory, ConfigOutputFileName));
- v2rayController.Restart();
+ ToolConfig.FromDirectoryAndWriteToFile(CrupestSecretToolDirectory, Path.Join(CrupestSecretToolDirectory, ConfigOutputFileName));
+ controller.Restart();
};
configFileWatcher.Run();
@@ -67,28 +67,28 @@ public static class Program
var verb = args[0].ToLower();
if (verb == "download-geodata" || verb == "dg")
{
- GeoDataManager.Instance.Download(CrupestV2rayDirectory, false);
+ GeoDataManager.Instance.Download(CrupestSecretToolDirectory, false);
return;
}
else if (verb == "generate-surge-rule-set" || verb == "gs")
{
SurgeConfigGenerator.GenerateTo(
- Path.Join(CrupestV2rayDirectory, "proxy.txt"),
- Path.Join(CrupestV2rayDirectory, SurgeRuleSetChinaOutputFileName),
- Path.Join(CrupestV2rayDirectory, SurgeRuleSetGlobalOutputFileName),
+ Path.Join(CrupestSecretToolDirectory, "proxy.txt"),
+ Path.Join(CrupestSecretToolDirectory, SurgeRuleSetChinaOutputFileName),
+ Path.Join(CrupestSecretToolDirectory, SurgeRuleSetGlobalOutputFileName),
true, false
);
return;
}
else if (verb == "generate" || verb == "g")
{
- var config = V2rayConfig.FromDirectory(CrupestV2rayDirectory);
+ var config = ToolConfig.FromDirectory(CrupestSecretToolDirectory);
Console.Out.WriteLine(config.ToJsonStringV4());
return;
}
throw new Exception("Invalid command line arguments.");
}
- RunV2rayAndWatchConfigChange();
+ RunToolAndWatchConfigChange();
}
}
diff --git a/tools/Crupest.V2ray/Crupest.V2ray/Properties/PublishProfiles/FolderProfile.pubxml b/tools/Crupest.SecretTool/Crupest.SecretTool/Properties/PublishProfiles/FolderProfile.pubxml
index bbdd2ad..5fca454 100644
--- a/tools/Crupest.V2ray/Crupest.V2ray/Properties/PublishProfiles/FolderProfile.pubxml
+++ b/tools/Crupest.SecretTool/Crupest.SecretTool/Properties/PublishProfiles/FolderProfile.pubxml
@@ -6,7 +6,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
- <PublishDir>bin\Release\net7.0\publish\</PublishDir>
+ <PublishDir>bin\Release\net8.0\publish\</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<_TargetId>Folder</_TargetId>
</PropertyGroup>
diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool/Proxy.cs b/tools/Crupest.SecretTool/Crupest.SecretTool/Proxy.cs
new file mode 100644
index 0000000..638edb6
--- /dev/null
+++ b/tools/Crupest.SecretTool/Crupest.SecretTool/Proxy.cs
@@ -0,0 +1,58 @@
+namespace Crupest.SecretTool;
+
+public abstract class Proxy(string tag) : IV4ConfigObject
+{
+ public string Tag { get; set; } = tag;
+
+ public abstract V4ConfigJsonObjects.Outbound ToJsonObjectV4();
+
+ object IV4ConfigObject.ToJsonObjectV4()
+ {
+ return ToJsonObjectV4();
+ }
+}
+
+public class HttpProxy(string host, int port, string tag) : Proxy(tag)
+{
+ public string Host { get; set; } = host;
+ public int Port { get; set; } = port;
+
+ public override V4ConfigJsonObjects.Outbound ToJsonObjectV4()
+ {
+ return new V4ConfigJsonObjects.Outbound(Tag, "http",
+ new V4ConfigJsonObjects.HttpOutboundSettings([new V4ConfigJsonObjects.HttpOutboundServer(Host, Port, [])]),
+ null
+ );
+ }
+}
+
+
+public class VmessProxy(string host, int port, string userId, string path, string tag) : Proxy(tag)
+{
+ public string Host { get; set; } = host;
+ public int Port { get; set; } = port;
+ public string Path { get; set; } = path;
+ public string UserId { get; set; } = userId;
+
+ public override V4ConfigJsonObjects.Outbound ToJsonObjectV4()
+ {
+ return new V4ConfigJsonObjects.Outbound(Tag, "vmess",
+ new V4ConfigJsonObjects.VmessOutboundSettings(
+ [new V4ConfigJsonObjects.VnextServer(Host, Port, [new V4ConfigJsonObjects.VnextServerUser(UserId, 0, "auto", 0)])]),
+ new V4ConfigJsonObjects.WsStreamSettings("ws", "tls", new V4ConfigJsonObjects.WsSettings(Path, new() { ["Host"] = Host }))
+ );
+ }
+
+ public static VmessProxy CreateFromConfigString(string configString, string tag)
+ {
+ var config = new DictionaryConfig(configString, ["host", "port", "userid", "path"]);
+ var portString = config.GetItemCaseInsensitive("port").Value;
+ if (!int.TryParse(portString, out var port) || port <= 0)
+ {
+ throw new FormatException($"Invalid port number: {portString}: not an integer or is a invalid number.");
+ }
+ return new VmessProxy(config.GetItemCaseInsensitive("host").Value, port,
+ config.GetItemCaseInsensitive("userid").Value, config.GetItemCaseInsensitive("path").Value, tag
+ );
+ }
+}
diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool/Routing.cs b/tools/Crupest.SecretTool/Crupest.SecretTool/Routing.cs
new file mode 100644
index 0000000..dbced0e
--- /dev/null
+++ b/tools/Crupest.SecretTool/Crupest.SecretTool/Routing.cs
@@ -0,0 +1,97 @@
+namespace Crupest.SecretTool;
+
+public record RoutingRule(HostMatchKind MatchKind, string MatchString, string OutboundTag) : IV4ConfigObject
+{
+ public string ToolConfigString => MatchKind switch
+ {
+ HostMatchKind.DomainFull => $"full:{MatchString}",
+ HostMatchKind.DomainSuffix => $"domain:{MatchString}",
+ HostMatchKind.DomainKeyword => MatchString,
+ HostMatchKind.DomainRegex => $"regexp:{MatchString}",
+ HostMatchKind.Ip => MatchString,
+ HostMatchKind.GeoSite => $"geosite:{MatchString}",
+ HostMatchKind.GeoIp => $"geoip:{MatchString}",
+ _ => throw new ArgumentException("Invalid matcher kind.")
+ };
+
+ public static Dictionary<string, List<RoutingRule>> GroupByOutboundTag(List<RoutingRule> rules)
+ => rules.GroupBy(r => r.OutboundTag).Select(g => (g.Key, g.ToList())).ToDictionary();
+
+ public static Dictionary<HostMatchKind, List<RoutingRule>> GroupByMatchKind(List<RoutingRule> rules)
+ => rules.GroupBy(r => r.MatchKind).Select(g => (g.Key, g.ToList())).ToDictionary();
+
+ public static List<List<RoutingRule>> GroupByOutboundTagAndMatcherKind(List<RoutingRule> rules)
+ => GroupByOutboundTag(rules).Values.SelectMany((groupByTag) => GroupByMatchKind(groupByTag).Values).ToList();
+
+ public static V4ConfigJsonObjects.RoutingRule ListToJsonObject(List<RoutingRule> rules)
+ {
+ if (rules.Count == 0)
+ {
+ throw new ArgumentException("Rule list is empty.");
+ }
+
+ var matchKind = rules[0].MatchKind;
+ var outboundTag = rules[0].OutboundTag;
+
+ if (rules.Any(r => r.OutboundTag != outboundTag) || rules.Any(r => r.MatchKind != matchKind))
+ {
+ throw new ArgumentException("Rules must have the same matcher kind and outbound tag.");
+ }
+
+ List<string> toolConfigList = rules.Select(r => r.ToolConfigString).ToList();
+
+ return new V4ConfigJsonObjects.RoutingRule(OutboundTag: outboundTag,
+ Ip: (matchKind is HostMatchKind.Ip or HostMatchKind.GeoIp) ? toolConfigList : null,
+ Domains: (matchKind.IsDomain() || matchKind == HostMatchKind.GeoSite) ? toolConfigList : null
+ );
+ }
+
+ public RoutingRule CloneGeositeWithCnAttribute(string outboundTag)
+ {
+ if (MatchKind is not HostMatchKind.GeoSite)
+ {
+ throw new ArgumentException("Matcher kind must be GeoSite.");
+ }
+
+ return new RoutingRule(HostMatchKind.GeoSite, $"{MatchString}@cn", outboundTag);
+ }
+
+ public V4ConfigJsonObjects.RoutingRule ToJsonObjectV4() => ListToJsonObject([this]);
+
+ object IV4ConfigObject.ToJsonObjectV4() => ToJsonObjectV4();
+}
+
+public record Routing(List<RoutingRule> Rules, bool DirectGeositeCn = true, string DomainStrategy = "IpOnDemand") : IV4ConfigObject
+{
+ public List<RoutingRule> CreateGeositeCnDirectRules()
+ {
+ return Rules.Where(r => r.MatchKind is HostMatchKind.GeoSite)
+ .Select(r => r.CloneGeositeWithCnAttribute("direct")).ToList();
+ }
+
+ public V4ConfigJsonObjects.Routing ToJsonObjectV4(bool directGeositeCn = true)
+ {
+ List<V4ConfigJsonObjects.RoutingRule> ruleJsonObjects = [];
+
+ if (directGeositeCn)
+ {
+ ruleJsonObjects.Add(RoutingRule.ListToJsonObject(CreateGeositeCnDirectRules()));
+ }
+
+ ruleJsonObjects.AddRange(RoutingRule.GroupByOutboundTagAndMatcherKind(Rules).Select(RoutingRule.ListToJsonObject));
+
+ return new V4ConfigJsonObjects.Routing(ruleJsonObjects);
+ }
+
+ object IV4ConfigObject.ToJsonObjectV4() => ToJsonObjectV4();
+
+ public static Routing FromProxyFile(ProxyFile proxyFile, string outboundTag, bool directGeositeCn)
+ {
+
+ return new Routing(
+ proxyFile.Config.Items.Select(
+ i => new RoutingRule(i.Kind, i.MatchString, outboundTag)).ToList(),
+ directGeositeCn
+ );
+ }
+}
diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool/StaticHosts.cs b/tools/Crupest.SecretTool/Crupest.SecretTool/StaticHosts.cs
new file mode 100644
index 0000000..b112e1c
--- /dev/null
+++ b/tools/Crupest.SecretTool/Crupest.SecretTool/StaticHosts.cs
@@ -0,0 +1,40 @@
+namespace Crupest.SecretTool;
+
+public record StaticHostRule(HostMatchKind MatchKind, string MatchString, List<string> ResolveResult)
+{
+ public string AddressString()
+ {
+ return MatchKind switch
+ {
+ HostMatchKind.DomainFull => MatchString,
+ HostMatchKind.DomainSuffix => $"domain:{MatchString}",
+ HostMatchKind.DomainKeyword => $"keyword:{MatchString}",
+ HostMatchKind.DomainRegex => $"regexp:{MatchString}",
+ _ => throw new ArgumentOutOfRangeException($"Match kind {MatchKind} is not allowed in static host rule."),
+ };
+ }
+
+ public object ResolveResultToJsonObject()
+ {
+ return ResolveResult.Count == 1 ? ResolveResult[0] : ResolveResult;
+ }
+}
+
+public class StaticHosts(List<StaticHostRule> rules) : IV4ConfigObject
+{
+ public List<StaticHostRule> Rules { get; } = rules;
+
+ public Dictionary<string, object> ToJsonObjectV4() =>
+ Rules.ToDictionary(rule => rule.AddressString(), rule => rule.ResolveResultToJsonObject());
+
+ object IV4ConfigObject.ToJsonObjectV4()
+ {
+ return ToJsonObjectV4();
+ }
+
+ public static StaticHosts CreateFromHostMatchConfigString(string configString)
+ {
+ var config = new HostMatchConfig(configString, HostMatchKindExtensions.DomainMatchKinds, minComponentCount: 1);
+ return new StaticHosts(config.Items.Select(i => new StaticHostRule(i.Kind, i.MatchString, [.. i.Values])).ToList());
+ }
+}
diff --git a/tools/Crupest.V2ray/Crupest.V2ray/SurgeConfigGenerator.cs b/tools/Crupest.SecretTool/Crupest.SecretTool/SurgeConfigGenerator.cs
index bd52234..451db3e 100644
--- a/tools/Crupest.V2ray/Crupest.V2ray/SurgeConfigGenerator.cs
+++ b/tools/Crupest.SecretTool/Crupest.SecretTool/SurgeConfigGenerator.cs
@@ -1,44 +1,44 @@
-namespace Crupest.V2ray;
+namespace Crupest.SecretTool;
public class SurgeConfigGenerator(ProxyFile proxyFile, GeoSiteData geoSiteData)
{
public ProxyFile ProxyFile { get; } = proxyFile;
public GeoSiteData GeoSiteData { get; } = geoSiteData;
- private static string ToSurgeRuleString(V2rayHostMatcherKind kind, string value)
+ private static string ToSurgeRuleString(HostMatchKind kind, string value)
{
var ruleType = kind switch
{
- V2rayHostMatcherKind.DomainFull => "DOMAIN",
- V2rayHostMatcherKind.DomainSuffix => "DOMAIN-SUFFIX",
- V2rayHostMatcherKind.DomainKeyword => "DOMAIN-KEYWORD",
- V2rayHostMatcherKind.DomainRegex => "URL-REGEX",
+ HostMatchKind.DomainFull => "DOMAIN",
+ HostMatchKind.DomainSuffix => "DOMAIN-SUFFIX",
+ HostMatchKind.DomainKeyword => "DOMAIN-KEYWORD",
+ HostMatchKind.DomainRegex => "URL-REGEX",
_ => throw new Exception("Unacceptable matcher kind for Surge rule.")
};
return $"{ruleType},{value}";
}
- private static List<V2rayHostMatcherKind> DomainMatcherKinds { get; } = [
- V2rayHostMatcherKind.DomainFull, V2rayHostMatcherKind.DomainKeyword,
- V2rayHostMatcherKind.DomainRegex, V2rayHostMatcherKind.DomainSuffix,
+ private static List<HostMatchKind> DomainMatcherKinds { get; } = [
+ HostMatchKind.DomainFull, HostMatchKind.DomainKeyword,
+ HostMatchKind.DomainRegex, HostMatchKind.DomainSuffix,
];
public string GenerateChinaRuleSet()
{
- var geoSites = ProxyFile.MatcherConfig.Items.Where(i => i.Kind == V2rayHostMatcherKind.GeoSite).Select(i => i.Matcher).ToList();
+ var geoSites = ProxyFile.Config.Items.Where(i => i.Kind == HostMatchKind.GeoSite).Select(i => i.MatchString).ToList();
var cnRules = GeoSiteData.GetEntriesRecursive(geoSites, DomainMatcherKinds, ["cn"]).ToList();
return string.Join('\n', cnRules.Select(r => ToSurgeRuleString(r.Kind, r.Value)));
}
public string GenerateGlobalRuleSet()
{
- var geoSites = ProxyFile.MatcherConfig.Items.Where(i => i.Kind == V2rayHostMatcherKind.GeoSite).Select(i => i.Matcher).ToList();
+ var geoSites = ProxyFile.Config.Items.Where(i => i.Kind == HostMatchKind.GeoSite).Select(i => i.MatchString).ToList();
var nonCnRules = GeoSiteData.GetEntriesRecursive(geoSites, DomainMatcherKinds).Where(e => !e.Attributes.Contains("cn")).ToList();
- var domainRules = ProxyFile.MatcherConfig.Items.Where(i => DomainMatcherKinds.Contains(i.Kind)).ToList();
+ var domainRules = ProxyFile.Config.Items.Where(i => DomainMatcherKinds.Contains(i.Kind)).ToList();
return string.Join('\n', [
..nonCnRules.Select(r => ToSurgeRuleString(r.Kind, r.Value)),
- ..domainRules.Select(r => ToSurgeRuleString(r.Kind, r.Matcher))
+ ..domainRules.Select(r => ToSurgeRuleString(r.Kind, r.MatchString))
]);
}
diff --git a/tools/Crupest.V2ray/Crupest.V2ray/Template.cs b/tools/Crupest.SecretTool/Crupest.SecretTool/Template.cs
index 9c137b0..1fe91b1 100644
--- a/tools/Crupest.V2ray/Crupest.V2ray/Template.cs
+++ b/tools/Crupest.SecretTool/Crupest.SecretTool/Template.cs
@@ -1,7 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Text;
-namespace Crupest.V2ray;
+namespace Crupest.SecretTool;
public class Template
{
diff --git a/tools/Crupest.V2ray/Crupest.V2ray/V2rayConfig.cs b/tools/Crupest.SecretTool/Crupest.SecretTool/ToolConfig.cs
index e81a6cb..4fe9a40 100644
--- a/tools/Crupest.V2ray/Crupest.V2ray/V2rayConfig.cs
+++ b/tools/Crupest.SecretTool/Crupest.SecretTool/ToolConfig.cs
@@ -1,14 +1,14 @@
using System.Text.Json;
using System.Text.Json.Serialization;
-namespace Crupest.V2ray;
+namespace Crupest.SecretTool;
-public interface IV2rayV4ConfigObject
+public interface IV4ConfigObject
{
object ToJsonObjectV4();
}
-public class V2rayConfig(Template template, List<V2rayProxy> proxies, V2rayRouting router, V2rayHosts? hosts)
+public class ToolConfig(Template template, List<Proxy> proxies, Routing router, StaticHosts? hosts)
{
private class JsonInterfaceConverter<Interface> : JsonConverter<Interface>
{
@@ -42,7 +42,7 @@ public class V2rayConfig(Template template, List<V2rayProxy> proxies, V2rayRouti
private const string RoutingAnchor = "ROUTING_ANCHOR";
private const string HostsAnchor = "HOSTS_ANCHOR";
- public const string AddCnAttributeToGeositeEnvironmentVariable = "CRUPEST_V@RAY_GEOSITE_USE_CN";
+ public const string AddCnAttributeToGeositeEnvironmentVariable = "CRUPEST_V2RAY_GEOSITE_USE_CN";
private static bool UseCnGeoSite => Environment.GetEnvironmentVariable(AddCnAttributeToGeositeEnvironmentVariable) switch
{
@@ -51,9 +51,9 @@ public class V2rayConfig(Template template, List<V2rayProxy> proxies, V2rayRouti
};
public Template Template { get; set; } = template;
- public List<V2rayProxy> Proxies { get; set; } = proxies;
- public V2rayRouting Routing { get; set; } = router;
- public V2rayHosts Hosts { get; set; } = hosts is null ? new V2rayHosts([]) : hosts;
+ public List<Proxy> Proxies { get; set; } = proxies;
+ public Routing Routing { get; set; } = router;
+ public StaticHosts Hosts { get; set; } = hosts is null ? new StaticHosts([]) : hosts;
public string ToJsonStringV4(bool pretty = true)
{
@@ -63,8 +63,9 @@ public class V2rayConfig(Template template, List<V2rayProxy> proxies, V2rayRouti
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
});
- jsonOptions.Converters.Add(new JsonInterfaceConverter<V2rayV4ConfigJsonObjects.IOutboundSettings>());
- jsonOptions.Converters.Add(new JsonInterfaceConverter<V2rayV4ConfigJsonObjects.IOutboundStreamSettings>());
+ // TODO: Make interface converter generic.
+ jsonOptions.Converters.Add(new JsonInterfaceConverter<V4ConfigJsonObjects.IOutboundSettings>());
+ jsonOptions.Converters.Add(new JsonInterfaceConverter<V4ConfigJsonObjects.IOutboundStreamSettings>());
var templateValues = new Dictionary<string, string>
{
@@ -89,7 +90,7 @@ public class V2rayConfig(Template template, List<V2rayProxy> proxies, V2rayRouti
}
}
- public static V2rayConfig FromFiles(string templatePath, string vmessPath, string proxyPath, string? hostsPath)
+ public static ToolConfig FromFiles(string templatePath, string vmessPath, string proxyPath, string? hostsPath)
{
foreach (var path in new List<string>([templatePath, vmessPath, proxyPath]))
{
@@ -122,12 +123,12 @@ public class V2rayConfig(Template template, List<V2rayProxy> proxies, V2rayRouti
file = templatePath;
var template = new Template(templateString);
file = vmessPath;
- var vmess = V2rayVmessProxy.CreateFromConfigString(vmessString, "proxy");
+ var vmess = VmessProxy.CreateFromConfigString(vmessString, "proxy");
file = proxyPath;
- var routing = proxyFile.ToV2rayRouting("proxy", UseCnGeoSite);
+ var routing = Routing.FromProxyFile(proxyFile, "proxy", UseCnGeoSite);
file = hostsPath ?? "";
- var hosts = hostsString is not null ? V2rayHosts.CreateFromHostMatcherConfigString(hostsString) : null;
- return new V2rayConfig(template, [vmess], routing, hosts);
+ var hosts = hostsString is not null ? StaticHosts.CreateFromHostMatchConfigString(hostsString) : null;
+ return new ToolConfig(template, [vmess], routing, hosts);
}
catch (Exception e)
{
@@ -135,7 +136,7 @@ public class V2rayConfig(Template template, List<V2rayProxy> proxies, V2rayRouti
}
}
- public static V2rayConfig FromDirectory(string directory)
+ public static ToolConfig FromDirectory(string directory)
{
return FromFiles(
Path.Join(directory, ConfigTemplateFileName),
diff --git a/tools/Crupest.V2ray/Crupest.V2ray/V2rayV4ConfigJsonObjects.cs b/tools/Crupest.SecretTool/Crupest.SecretTool/V4ConfigJsonObjects.cs
index 672af71..3e81dbb 100644
--- a/tools/Crupest.V2ray/Crupest.V2ray/V2rayV4ConfigJsonObjects.cs
+++ b/tools/Crupest.SecretTool/Crupest.SecretTool/V4ConfigJsonObjects.cs
@@ -1,6 +1,6 @@
-namespace Crupest.V2ray;
+namespace Crupest.SecretTool;
-public static class V2rayV4ConfigJsonObjects
+public static class V4ConfigJsonObjects
{
public interface IObject;
public interface IOutboundSettings : IObject;
diff --git a/tools/Crupest.V2ray/Crupest.V2ray/V2rayV5ConfigJsonObjects.cs b/tools/Crupest.SecretTool/Crupest.SecretTool/V5ConfigJsonObjects.cs
index 56d64ca..a50e9be 100644
--- a/tools/Crupest.V2ray/Crupest.V2ray/V2rayV5ConfigJsonObjects.cs
+++ b/tools/Crupest.SecretTool/Crupest.SecretTool/V5ConfigJsonObjects.cs
@@ -1,6 +1,6 @@
-namespace Crupest.V2ray;
+namespace Crupest.SecretTool;
-public static class V2rayV5ConfigJsonObjects
+public static class V5ConfigJsonObjects
{
public record OutboundObject(string Protocol, object Settings, string Tag, object? StreamSettings)
{
diff --git a/tools/Crupest.V2ray/Crupest.V2ray/config.json.template b/tools/Crupest.SecretTool/Crupest.SecretTool/config.json.template
index 686006c..424e996 100644
--- a/tools/Crupest.V2ray/Crupest.V2ray/config.json.template
+++ b/tools/Crupest.SecretTool/Crupest.SecretTool/config.json.template
@@ -4,7 +4,7 @@
},
"inbounds": [
{
- "port": 3081,
+ "port": 2081,
"listen": "127.0.0.1",
"tag": "socks-inbound",
"protocol": "socks",
@@ -13,7 +13,7 @@
}
},
{
- "port": 3080,
+ "port": 2080,
"listen": "127.0.0.1",
"tag": "http-inbound",
"protocol": "http",
diff --git a/tools/Crupest.V2ray/Crupest.V2ray/config.v5.json.template b/tools/Crupest.SecretTool/Crupest.SecretTool/config.v5.json.template
index 01ccf7a..01ccf7a 100644
--- a/tools/Crupest.V2ray/Crupest.V2ray/config.v5.json.template
+++ b/tools/Crupest.SecretTool/Crupest.SecretTool/config.v5.json.template
diff --git a/tools/Crupest.V2ray/Crupest.V2ray/hosts.txt b/tools/Crupest.SecretTool/Crupest.SecretTool/hosts.txt
index 88d5015..88d5015 100644
--- a/tools/Crupest.V2ray/Crupest.V2ray/hosts.txt
+++ b/tools/Crupest.SecretTool/Crupest.SecretTool/hosts.txt
diff --git a/tools/Crupest.V2ray/Crupest.V2ray/proxy.txt b/tools/Crupest.SecretTool/Crupest.SecretTool/proxy.txt
index 6273e35..6273e35 100644
--- a/tools/Crupest.V2ray/Crupest.V2ray/proxy.txt
+++ b/tools/Crupest.SecretTool/Crupest.SecretTool/proxy.txt
diff --git a/tools/Crupest.V2ray/build-secret.bash b/tools/Crupest.SecretTool/build-secret.bash
index bc5c7ee..8878049 100755
--- a/tools/Crupest.V2ray/build-secret.bash
+++ b/tools/Crupest.SecretTool/build-secret.bash
@@ -34,7 +34,7 @@ echo "Enter \"secret\" dir..."
pushd "$secret_dir"
echo "Begin to build..."
-dotnet publish Crupest.V2ray -c Release -o "$secret_dir/publish" --sc -r "$1"
+dotnet publish Crupest.SecretTool -c Release -o "$secret_dir/publish" --sc -r "$1"
popd
diff --git a/tools/Crupest.V2ray/tools/cru-proxy-edit b/tools/Crupest.SecretTool/tools/cru-proxy-edit
index 9ba6cbc..51a33e1 100755
--- a/tools/Crupest.V2ray/tools/cru-proxy-edit
+++ b/tools/Crupest.SecretTool/tools/cru-proxy-edit
@@ -2,7 +2,7 @@
set -e
-p="$HOME/codes/crupest/tools/Crupest.V2ray/publish/proxy.txt"
+p="$HOME/codes/crupest/tools/Crupest.SecretTool/publish/proxy.txt"
if [[ ! -f "$p" ]]; then
echo "File $p does not exist!" >&2
diff --git a/tools/Crupest.V2ray/tools/cru-proxy-log b/tools/Crupest.SecretTool/tools/cru-proxy-log
index 0ac800c..6ec6ee1 100755
--- a/tools/Crupest.V2ray/tools/cru-proxy-log
+++ b/tools/Crupest.SecretTool/tools/cru-proxy-log
@@ -4,9 +4,9 @@ set -e
if [[ -e /proc ]]; then
# I don't believe your system is Linux but there is no /proc.
- exec journalctl --user -u crupest-v2ray "$@"
+ exec journalctl --user -u crupest-secret-tool "$@"
elif [[ "$(uname)" == "Darwin" ]]; then
- exec less "$HOME/.local/state/Crupest.V2ray/log"
+ exec less "$HOME/.local/state/Crupest.SecretTool/log"
else
echo "Not supported on systems other than macOS and Linux now." >&2
exit 1
diff --git a/tools/Crupest.V2ray/tools/crupest-v2ray.service b/tools/Crupest.SecretTool/tools/crupest-secret-tool.service
index afe840f..df6d172 100644
--- a/tools/Crupest.V2ray/tools/crupest-v2ray.service
+++ b/tools/Crupest.SecretTool/tools/crupest-secret-tool.service
@@ -2,7 +2,7 @@
Description=crupest v2ray service
[Service]
-ExecStart=%h/.local/bin/Crupest.V2ray
+ExecStart=%h/.local/bin/Crupest.SecretTool
[Install]
WantedBy=default.target
diff --git a/tools/Crupest.V2ray/tools/life.crupest.v2ray.plist b/tools/Crupest.SecretTool/tools/life.crupest.secret-tool.plist
index 4569ae2..bdfe490 100644
--- a/tools/Crupest.V2ray/tools/life.crupest.v2ray.plist
+++ b/tools/Crupest.SecretTool/tools/life.crupest.secret-tool.plist
@@ -3,16 +3,16 @@
<plist version="1.0">
<dict>
<key>Label</key>
- <string>life.crupest.v2ray</string>
+ <string>life.crupest.secret-tool</string>
<key>ProgramArguments</key>
<array>
- <string>/Users/crupest/.local/bin/Crupest.V2ray</string>
+ <string>/Users/crupest/.local/bin/Crupest.SecretTool</string>
</array>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
- <string>/Users/crupest/.local/state/Crupest.V2ray/log</string>
+ <string>/Users/crupest/.local/state/Crupest.SecretTool/log</string>
<key>StandardErrorPath</key>
- <string>/Users/crupest/.local/state/Crupest.V2ray/error</string>
+ <string>/Users/crupest/.local/state/Crupest.SecretTool/error</string>
</dict>
</plist>
diff --git a/tools/Crupest.V2ray/Crupest.V2ray/ProxyFile.cs b/tools/Crupest.V2ray/Crupest.V2ray/ProxyFile.cs
deleted file mode 100644
index ca5ca56..0000000
--- a/tools/Crupest.V2ray/Crupest.V2ray/ProxyFile.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-namespace Crupest.V2ray;
-
-public class ProxyFile(string path) :
- HostMatcherConfigFile(path, [.. Enum.GetValues<V2rayHostMatcherKind>()], maxComponentCount: 0)
-{
- public V2rayRouting ToV2rayRouting(string outboundTag, bool directGeositeCn)
- {
- return new V2rayRouting(
- MatcherConfig.Items.Select(
- i => new V2rayRoutingRule(i.Kind, i.Matcher, outboundTag)).ToList(),
- directGeositeCn
- );
- }
-}
diff --git a/tools/Crupest.V2ray/Crupest.V2ray/V2rayHostMacherConfig.cs b/tools/Crupest.V2ray/Crupest.V2ray/V2rayHostMacherConfig.cs
deleted file mode 100644
index 36ae44b..0000000
--- a/tools/Crupest.V2ray/Crupest.V2ray/V2rayHostMacherConfig.cs
+++ /dev/null
@@ -1,109 +0,0 @@
-namespace Crupest.V2ray;
-
-public enum V2rayHostMatcherKind
-{
- DomainFull,
- DomainSuffix,
- DomainKeyword,
- DomainRegex,
- Ip,
- GeoSite,
- GeoIp,
-}
-
-public record V2rayHostMatcherItem(V2rayHostMatcherKind Kind, string Matcher, List<string> Values);
-
-public class V2rayHostMatcherConfig(string configString, List<V2rayHostMatcherKind> allowedMatchers, int minComponentCount = -1, int maxComponentCount = -1)
-{
- static bool IsDomainMatcher(V2rayHostMatcherKind kind) => kind switch
- {
- V2rayHostMatcherKind.DomainFull => true,
- V2rayHostMatcherKind.DomainSuffix => true,
- V2rayHostMatcherKind.DomainKeyword => true,
- V2rayHostMatcherKind.DomainRegex => true,
- _ => false,
- };
-
- private static List<V2rayHostMatcherItem> Parse(string configString, List<V2rayHostMatcherKind> allowedMatchers, int minComponentCount = -1, int maxComponentCount = -1)
- {
- var items = new ListConfig(configString).Config;
- var result = new List<V2rayHostMatcherItem>();
-
- foreach (var item in items)
- {
- var lineNumber = item.LineNumber;
- var line = item.Value;
- var hasExplicitMatcher = false;
- var segments = line.Split(' ', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToList();
-
- foreach (var matcher in Enum.GetValues<V2rayHostMatcherKind>())
- {
- var matcherName = Enum.GetName(matcher) ?? throw new Exception("No such matcher.");
- hasExplicitMatcher = true;
- if (segments[0] == matcherName)
- {
- if (segments.Count < 2)
- {
- throw new FormatException($"Explicit matcher needs a value in line {lineNumber}.");
- }
- if (allowedMatchers.Contains(matcher))
- {
- if (IsDomainMatcher(matcher) && matcher == V2rayHostMatcherKind.DomainRegex && Uri.CheckHostName(matcherName) != UriHostNameType.Dns)
- {
- throw new FormatException($"Invalid domain format in line {lineNumber}.");
- }
-
- var components = segments[2..].ToList();
- if (minComponentCount > 0 && components.Count < minComponentCount)
- {
- throw new FormatException($"Too few components in line {lineNumber}, at least {minComponentCount} required.");
- }
- if (maxComponentCount >= 0 && components.Count > maxComponentCount)
- {
- throw new FormatException($"Too many components in line {lineNumber}, only {maxComponentCount} allowed.");
- }
- result.Add(new V2rayHostMatcherItem(matcher, segments[1], components));
- }
- else
- {
- throw new FormatException($"Matcher {matcherName} is not allowed at line {lineNumber}.");
- }
- }
- }
-
- if (!hasExplicitMatcher)
- {
- if (minComponentCount > 0 && segments.Count - 1 < minComponentCount)
- {
- throw new FormatException($"Too few components in line {lineNumber}, at least {minComponentCount} required.");
- }
- if (maxComponentCount >= 0 && segments.Count - 1 > maxComponentCount)
- {
- throw new FormatException($"Too many components in line {lineNumber}, only {maxComponentCount} allowed.");
- }
- result.Add(new V2rayHostMatcherItem(V2rayHostMatcherKind.DomainSuffix, segments[0], segments.Count == 1 ? [] : segments[1..]));
- }
- }
- return result;
- }
-
- public string ConfigString { get; } = configString;
- public List<V2rayHostMatcherKind> AllowedMatchers { get; } = allowedMatchers;
- public int MinComponentCount { get; } = minComponentCount;
- public int MaxComponentCount { get; } = maxComponentCount;
- public List<V2rayHostMatcherItem> Items { get; } = Parse(configString, allowedMatchers, minComponentCount, maxComponentCount);
-}
-
-public class HostMatcherConfigFile
-{
- public HostMatcherConfigFile(string path, List<V2rayHostMatcherKind> allowedMatchers, int minComponentCount = -1, int maxComponentCount = -1)
- {
- Path = path;
- FileContent = File.ReadAllText(path);
- MatcherConfig = new V2rayHostMatcherConfig(FileContent, allowedMatchers, minComponentCount, maxComponentCount); ;
- }
-
- public string Path { get; }
- public string FileContent { get; }
- public V2rayHostMatcherConfig MatcherConfig { get; }
-}
diff --git a/tools/Crupest.V2ray/Crupest.V2ray/V2rayHosts.cs b/tools/Crupest.V2ray/Crupest.V2ray/V2rayHosts.cs
deleted file mode 100644
index e9bf8cf..0000000
--- a/tools/Crupest.V2ray/Crupest.V2ray/V2rayHosts.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-namespace Crupest.V2ray;
-
-public record V2rayHostRule(V2rayHostMatcherKind MatcherKind, string MatcherString, List<string> ResolveResult)
-{
- public string AddressString()
- {
- return MatcherKind switch
- {
- V2rayHostMatcherKind.DomainFull => MatcherString,
- V2rayHostMatcherKind.DomainSuffix => $"domain:{MatcherString}",
- V2rayHostMatcherKind.DomainKeyword => $"keyword:{MatcherString}",
- V2rayHostMatcherKind.DomainRegex => $"regexp:{MatcherString}",
- _ => throw new ArgumentOutOfRangeException($"Matcher {MatcherKind} is not allowed in host rule."),
- };
- }
-
- public object ResolveResultToJsonObject()
- {
- return ResolveResult.Count == 1 ? ResolveResult[0] : ResolveResult;
- }
-}
-
-public class V2rayHosts(List<V2rayHostRule> rules) : IV2rayV4ConfigObject
-{
- public List<V2rayHostRule> Rules { get; } = rules;
-
- public Dictionary<string, object> ToJsonObjectV4() =>
- Rules.ToDictionary(rule => rule.AddressString(), rule => rule.ResolveResultToJsonObject());
-
- object IV2rayV4ConfigObject.ToJsonObjectV4()
- {
- return ToJsonObjectV4();
- }
-
- public static V2rayHosts CreateFromHostMatcherConfigString(string configString)
- {
- var matcherConfig = new V2rayHostMatcherConfig(configString,
- [V2rayHostMatcherKind.DomainFull, V2rayHostMatcherKind.DomainKeyword, V2rayHostMatcherKind.DomainRegex, V2rayHostMatcherKind.DomainSuffix], minComponentCount: 1);
-
- return new V2rayHosts(matcherConfig.Items.Select(i => new V2rayHostRule(i.Kind, i.Matcher, [.. i.Values])).ToList());
- }
-}
diff --git a/tools/Crupest.V2ray/Crupest.V2ray/V2rayProxy.cs b/tools/Crupest.V2ray/Crupest.V2ray/V2rayProxy.cs
deleted file mode 100644
index bcb2b51..0000000
--- a/tools/Crupest.V2ray/Crupest.V2ray/V2rayProxy.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-namespace Crupest.V2ray;
-
-public abstract class V2rayProxy(string tag) : IV2rayV4ConfigObject
-{
- public string Tag { get; set; } = tag;
-
- public abstract V2rayV4ConfigJsonObjects.Outbound ToJsonObjectV4();
-
- object IV2rayV4ConfigObject.ToJsonObjectV4()
- {
- return ToJsonObjectV4();
- }
-}
-
-public class V2rayHttpProxy(string host, int port, string tag) : V2rayProxy(tag)
-{
- public string Host { get; set; } = host;
- public int Port { get; set; } = port;
-
- public override V2rayV4ConfigJsonObjects.Outbound ToJsonObjectV4()
- {
- return new V2rayV4ConfigJsonObjects.Outbound(Tag, "http",
- new V2rayV4ConfigJsonObjects.HttpOutboundSettings([new V2rayV4ConfigJsonObjects.HttpOutboundServer(Host, Port, [])]),
- null
- );
- }
-}
-
-
-public class V2rayVmessProxy(string host, int port, string userId, string path, string tag) : V2rayProxy(tag)
-{
- public string Host { get; set; } = host;
- public int Port { get; set; } = port;
- public string Path { get; set; } = path;
- public string UserId { get; set; } = userId;
-
- public override V2rayV4ConfigJsonObjects.Outbound ToJsonObjectV4()
- {
- return new V2rayV4ConfigJsonObjects.Outbound(Tag, "vmess",
- new V2rayV4ConfigJsonObjects.VmessOutboundSettings(
- [new V2rayV4ConfigJsonObjects.VnextServer(Host, Port, [new V2rayV4ConfigJsonObjects.VnextServerUser(UserId, 0, "auto", 0)])]),
- new V2rayV4ConfigJsonObjects.WsStreamSettings("ws", "tls", new V2rayV4ConfigJsonObjects.WsSettings(Path, new() { ["Host"] = Host }))
- );
- }
-
- public static V2rayVmessProxy CreateFromConfigString(string configString, string tag)
- {
- var config = new DictionaryConfig(configString, ["host", "port", "userid", "path"]);
- var portString = config.GetItemCaseInsensitive("port").Value;
- if (!int.TryParse(portString, out var port) || port <= 0)
- {
- throw new FormatException($"Invalid port number: {portString}: not an integer or is a invalid number.");
- }
- return new V2rayVmessProxy(config.GetItemCaseInsensitive("host").Value, port,
- config.GetItemCaseInsensitive("userid").Value, config.GetItemCaseInsensitive("path").Value, tag
- );
- }
-}
diff --git a/tools/Crupest.V2ray/Crupest.V2ray/V2rayRouting.cs b/tools/Crupest.V2ray/Crupest.V2ray/V2rayRouting.cs
deleted file mode 100644
index f385233..0000000
--- a/tools/Crupest.V2ray/Crupest.V2ray/V2rayRouting.cs
+++ /dev/null
@@ -1,87 +0,0 @@
-namespace Crupest.V2ray;
-
-public record V2rayRoutingRule(V2rayHostMatcherKind MatcherKind, string MatcherString, string OutboundTag) : IV2rayV4ConfigObject
-{
- public string ComposedMatcherString => MatcherKind switch
- {
- V2rayHostMatcherKind.DomainFull => $"full:{MatcherString}",
- V2rayHostMatcherKind.DomainSuffix => $"domain:{MatcherString}",
- V2rayHostMatcherKind.DomainKeyword => MatcherString,
- V2rayHostMatcherKind.DomainRegex => $"regexp:{MatcherString}",
- V2rayHostMatcherKind.Ip => MatcherString,
- V2rayHostMatcherKind.GeoSite => $"geosite:{MatcherString}",
- V2rayHostMatcherKind.GeoIp => $"geoip:{MatcherString}",
- _ => throw new ArgumentException("Invalid matcher kind.")
- };
-
- public static Dictionary<string, List<V2rayRoutingRule>> GroupByOutboundTag(List<V2rayRoutingRule> rules)
- => rules.GroupBy(r => r.OutboundTag).Select(g => (g.Key, g.ToList())).ToDictionary();
-
- public static Dictionary<V2rayHostMatcherKind, List<V2rayRoutingRule>> GroupByMatcherKind(List<V2rayRoutingRule> rules)
- => rules.GroupBy(r => r.MatcherKind).Select(g => (g.Key, g.ToList())).ToDictionary();
-
- public static List<List<V2rayRoutingRule>> GroupByOutboundTagAndMatcherKind(List<V2rayRoutingRule> rules)
- => GroupByOutboundTag(rules).Values.SelectMany((groupByTag) => GroupByMatcherKind(groupByTag).Values).ToList();
-
- public static V2rayV4ConfigJsonObjects.RoutingRule ListToJsonObject(List<V2rayRoutingRule> rules)
- {
- if (rules.Count == 0)
- {
- throw new ArgumentException("Rule list is empty.");
- }
-
- var matcherKind = rules[0].MatcherKind;
- var outboundTag = rules[0].OutboundTag;
-
- if (rules.Any(r => r.OutboundTag != outboundTag) || rules.Any(r => r.MatcherKind != matcherKind))
- {
- throw new ArgumentException("Rules must have the same matcher kind and outbound tag.");
- }
-
- List<string> composedMatcherStringList = rules.Select(r => r.ComposedMatcherString).ToList();
-
- return new V2rayV4ConfigJsonObjects.RoutingRule(OutboundTag: outboundTag,
- Ip: (matcherKind is V2rayHostMatcherKind.Ip or V2rayHostMatcherKind.GeoIp) ? composedMatcherStringList : null,
- Domains: (matcherKind is V2rayHostMatcherKind.DomainFull or V2rayHostMatcherKind.DomainSuffix or V2rayHostMatcherKind.DomainKeyword or V2rayHostMatcherKind.DomainRegex or V2rayHostMatcherKind.GeoSite) ? composedMatcherStringList : null
- );
- }
-
- public V2rayRoutingRule CloneGeositeWithCnAttribute(string outboundTag)
- {
- if (MatcherKind is not V2rayHostMatcherKind.GeoSite)
- {
- throw new ArgumentException("Matcher kind must be GeoSite.");
- }
-
- return new V2rayRoutingRule(V2rayHostMatcherKind.GeoSite, $"{MatcherString}@cn", outboundTag);
- }
-
- public V2rayV4ConfigJsonObjects.RoutingRule ToJsonObjectV4() => ListToJsonObject([this]);
-
- object IV2rayV4ConfigObject.ToJsonObjectV4() => ToJsonObjectV4();
-}
-
-public record V2rayRouting(List<V2rayRoutingRule> Rules, bool DirectGeositeCn = true, string DomainStrategy = "IpOnDemand") : IV2rayV4ConfigObject
-{
- public List<V2rayRoutingRule> CreateGeositeCnDirectRules()
- {
- return Rules.Where(r => r.MatcherKind is V2rayHostMatcherKind.GeoSite)
- .Select(r => r.CloneGeositeWithCnAttribute("direct")).ToList();
- }
-
- public V2rayV4ConfigJsonObjects.Routing ToJsonObjectV4(bool directGeositeCn = true)
- {
- List<V2rayV4ConfigJsonObjects.RoutingRule> ruleJsonObjects = [];
-
- if (directGeositeCn)
- {
- ruleJsonObjects.Add(V2rayRoutingRule.ListToJsonObject(CreateGeositeCnDirectRules()));
- }
-
- ruleJsonObjects.AddRange(V2rayRoutingRule.GroupByOutboundTagAndMatcherKind(Rules).Select(V2rayRoutingRule.ListToJsonObject));
-
- return new V2rayV4ConfigJsonObjects.Routing(ruleJsonObjects);
- }
-
- object IV2rayV4ConfigObject.ToJsonObjectV4() => ToJsonObjectV4();
-}
diff --git a/tools/Crupest.V2ray/Crupest.V2ray/V2rayV5StaticHostRule.cs b/tools/Crupest.V2ray/Crupest.V2ray/V2rayV5StaticHostRule.cs
deleted file mode 100644
index cdead3c..0000000
--- a/tools/Crupest.V2ray/Crupest.V2ray/V2rayV5StaticHostRule.cs
+++ /dev/null
@@ -1,122 +0,0 @@
-using System.Net;
-
-namespace Crupest.V2ray;
-
-public interface IV2rayStaticHostResolveResult
-{
- IDictionary<string, object> GetJsonProperties();
-}
-
-public class V2rayStaticHostDomainResolveResult : IV2rayStaticHostResolveResult
-{
- public V2rayStaticHostDomainResolveResult(string domain)
- {
- Domain = domain;
- }
-
- public string Domain { get; }
-
- public IDictionary<string, object> GetJsonProperties()
- {
- return new Dictionary<string, object>
- {
-
- ["proxiedDomain"] = Domain
- };
- }
-}
-
-public class V2rayStaticHostIpResolveResult : IV2rayStaticHostResolveResult
-{
- public V2rayStaticHostIpResolveResult(IEnumerable<string> ips)
- {
- Ips = ips.ToList();
- }
-
- public IReadOnlyList<string> Ips { get; }
-
- public IDictionary<string, object> GetJsonProperties()
- {
- return new Dictionary<string, object>
- {
- ["ip"] = Ips
- };
- }
-}
-
-
-public class V2rayV5StaticHostRule(V2rayV5StaticHostRule.MatcherKind matcher, string domain, IV2rayStaticHostResolveResult resolveResult)
-{
- public enum MatcherKind
- {
- Full,
- Subdomain,
- Keyword,
- Regex
- }
-
- public MatcherKind Matcher { get; } = matcher;
- public string Domain { get; } = domain;
- public IV2rayStaticHostResolveResult ResolveResult { get; } = resolveResult;
-
- public Dictionary<string, object> ToJsonObject()
- {
- var result = new Dictionary<string, object>
- {
- ["type"] = Enum.GetName(Matcher)!,
- ["domain"] = Domain
- };
-
- foreach (var (key, value) in ResolveResult.GetJsonProperties())
- {
- result.Add(key, value);
- }
-
- return result;
- }
-
- public static V2rayV5StaticHostRule IpRule(MatcherKind matcher, string domain, IEnumerable<string> ips)
- {
- return new V2rayV5StaticHostRule(matcher, domain, new V2rayStaticHostIpResolveResult(ips));
- }
-
- public static V2rayV5StaticHostRule DomainRule(MatcherKind matcher, string domain, string resolvedDomain)
- {
- return new V2rayV5StaticHostRule(matcher, domain, new V2rayStaticHostDomainResolveResult(resolvedDomain));
- }
-
- public static V2rayV5StaticHostRule Parse(string str)
- {
- var components = str.Trim().Split(' ', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToList();
-
- if (components.Count <= 1)
- {
- throw new FormatException("The str only has one or no component.");
- }
-
- var matcher = MatcherKind.Subdomain;
-
- if (Enum.TryParse<MatcherKind>(components[0], out var m))
- {
- matcher = m;
- components.RemoveAt(0);
- }
-
- if (components.Count <= 1)
- {
- throw new FormatException("The str only has one component after remove matcher.");
- }
-
- var domain = components[0];
- components.RemoveAt(0);
-
- if (components.Count > 1 || IPAddress.TryParse(components[0], out var _))
- {
- return new V2rayV5StaticHostRule(matcher, domain, new V2rayStaticHostIpResolveResult(components));
- }
- else
- {
- return new V2rayV5StaticHostRule(matcher, domain, new V2rayStaticHostDomainResolveResult(domain));
- }
- }
-}