diff options
| -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.cs | 123 | ||||
| -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.cs | 58 | ||||
| -rw-r--r-- | tools/Crupest.SecretTool/Crupest.SecretTool/Routing.cs | 97 | ||||
| -rw-r--r-- | tools/Crupest.SecretTool/Crupest.SecretTool/StaticHosts.cs | 40 | ||||
| -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-x | tools/Crupest.SecretTool/build-secret.bash (renamed from tools/Crupest.V2ray/build-secret.bash) | 2 | ||||
| -rwxr-xr-x | tools/Crupest.SecretTool/tools/cru-proxy-edit (renamed from tools/Crupest.V2ray/tools/cru-proxy-edit) | 2 | ||||
| -rwxr-xr-x | tools/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.cs | 14 | ||||
| -rw-r--r-- | tools/Crupest.V2ray/Crupest.V2ray/V2rayHostMacherConfig.cs | 109 | ||||
| -rw-r--r-- | tools/Crupest.V2ray/Crupest.V2ray/V2rayHosts.cs | 42 | ||||
| -rw-r--r-- | tools/Crupest.V2ray/Crupest.V2ray/V2rayProxy.cs | 58 | ||||
| -rw-r--r-- | tools/Crupest.V2ray/Crupest.V2ray/V2rayRouting.cs | 87 | ||||
| -rw-r--r-- | tools/Crupest.V2ray/Crupest.V2ray/V2rayV5StaticHostRule.cs | 122 | 
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)); -        } -    } -}  | 
