diff options
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/Crupest.V2ray/Crupest.V2ray/Config.cs | 14 | ||||
| -rw-r--r-- | tools/Crupest.V2ray/Crupest.V2ray/GeoDataManager.cs | 138 | ||||
| -rw-r--r-- | tools/Crupest.V2ray/Crupest.V2ray/Program.cs | 12 | ||||
| -rw-r--r-- | tools/Crupest.V2ray/Crupest.V2ray/ProxyFile.cs | 14 | ||||
| -rw-r--r-- | tools/Crupest.V2ray/Crupest.V2ray/SurgeConfigGenerator.cs | 60 | ||||
| -rw-r--r-- | tools/Crupest.V2ray/Crupest.V2ray/V2rayConfig.cs | 10 | ||||
| -rw-r--r-- | tools/Crupest.V2ray/Crupest.V2ray/V2rayHostMacherConfig.cs | 16 | ||||
| -rw-r--r-- | tools/Crupest.V2ray/Crupest.V2ray/V2rayHosts.cs | 2 | ||||
| -rw-r--r-- | tools/Crupest.V2ray/Crupest.V2ray/V2rayRouting.cs | 6 | ||||
| -rw-r--r-- | tools/Crupest.V2ray/Crupest.V2ray/proxy.txt | 1 | 
10 files changed, 234 insertions, 39 deletions
diff --git a/tools/Crupest.V2ray/Crupest.V2ray/Config.cs b/tools/Crupest.V2ray/Crupest.V2ray/Config.cs index fc71007..2ef18f0 100644 --- a/tools/Crupest.V2ray/Crupest.V2ray/Config.cs +++ b/tools/Crupest.V2ray/Crupest.V2ray/Config.cs @@ -69,13 +69,17 @@ public class ListConfig(string configString)          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]; +            } +            l = l.Trim(); +            if (!string.IsNullOrEmpty(l)) +            { +                config.Add(new ConfigItem(l, lineNumber));              } -            config.Add(new ConfigItem(trimmedLine, lineNumber));              lineNumber++;          } diff --git a/tools/Crupest.V2ray/Crupest.V2ray/GeoDataManager.cs b/tools/Crupest.V2ray/Crupest.V2ray/GeoDataManager.cs index 75f071b..3dce3f6 100644 --- a/tools/Crupest.V2ray/Crupest.V2ray/GeoDataManager.cs +++ b/tools/Crupest.V2ray/Crupest.V2ray/GeoDataManager.cs @@ -33,7 +33,7 @@ public record GeoSite(string Name, List<IGeoSiteEntry> Entries)                  var include = value["include:".Length..].Trim();                  if (include.Length == 0 || include.Contains(' '))                  { -                    throw new FormatException($"Invalid geo site rule in line {line}. Invalid include value."); +                    throw new FormatException($"Invalid geo site rule '{name}' in line {line}. Invalid include value.");                  }                  entries.Add(new GeoSiteIncludeEntry(include, name));                  continue; @@ -42,7 +42,7 @@ public record GeoSite(string Name, List<IGeoSiteEntry> Entries)              var segments = value.Split(':', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);              if (segments.Length > 2)              { -                throw new FormatException($"Invalid geo site rule in line {line}. More than one ':'."); +                throw new FormatException($"Invalid geo site rule '{name}' in line {line}. More than one ':'.");              }              V2rayHostMatcherKind kind; @@ -54,7 +54,7 @@ public record GeoSite(string Name, List<IGeoSiteEntry> Entries)                      "full" => kind = V2rayHostMatcherKind.DomainFull,                      "keyword" => kind = V2rayHostMatcherKind.DomainKeyword,                      "regexp" => kind = V2rayHostMatcherKind.DomainRegex, -                    _ => throw new FormatException($"Invalid geo site rule in line {line}. Unknown matcher.") +                    _ => throw new FormatException($"Invalid geo site rule '{name}' in line {line}. Unknown matcher.")                  };              }              else @@ -64,17 +64,17 @@ public record GeoSite(string Name, List<IGeoSiteEntry> Entries)              var domainSegments = segments[^1].Split('@', StringSplitOptions.TrimEntries);              var domain = domainSegments[0]; -            if (Uri.CheckHostName(domain) != UriHostNameType.Dns) +            if (kind != V2rayHostMatcherKind.DomainRegex && Uri.CheckHostName(domain) != UriHostNameType.Dns)              { -                throw new FormatException($"Invalid geo site rule in line {line}. Invalid domain."); +                throw new FormatException($"Invalid geo site rule '{name}' in line {line}. Invalid domain.");              }              List<string> attributes = []; -            foreach (var s in domainSegments) +            foreach (var s in domainSegments[1..])              {                  if (s.Length == 0)                  { -                    throw new FormatException($"Invalid geo site rule in line {line}. Empty attribute value."); +                    throw new FormatException($"Invalid geo site rule '{name}' in line {line}. Empty attribute value.");                  }                  attributes.Add(s);              } @@ -85,16 +85,15 @@ public record GeoSite(string Name, List<IGeoSiteEntry> Entries)      }  } -public class GeoSiteDataParser(string directory) +public class GeoSiteData(string directory)  {      private static List<GeoSite> Parse(string directory)      {          var sites = new List<GeoSite>(); -        foreach (var file in Directory.GetFileSystemEntries(directory)) +        foreach (var path in Directory.GetFileSystemEntries(directory))          { -            var path = Path.Combine(directory, file);              var content = File.ReadAllText(path); -            sites.Add(GeoSite.Parse(file, content)); +            sites.Add(GeoSite.Parse(Path.GetFileName(path), content));          }          return sites;      } @@ -102,6 +101,62 @@ public class GeoSiteDataParser(string directory)      public string DataDirectory { get; } = directory;      public List<GeoSite> Sites { get; } = Parse(directory); + +    public GeoSite? GetSite(string name) +    { +        return Sites.Where(s => s.Name == name).FirstOrDefault(); +    } + +    public List<GeoSiteRuleEntry> GetEntriesRecursive(List<string> sites, +        List<V2rayHostMatcherKind>? onlyMatcherKinds = null, List<string>? onlyAttributes = null) +    { +        List<GeoSiteRuleEntry> entries = []; +        HashSet<string> visited = []; +        HashSet<V2rayHostMatcherKind>? kinds = onlyMatcherKinds?.ToHashSet(); + +        void Visit(string site) +        { +            if (visited.Contains(site)) +            { +                return; +            } + +            visited.Add(site); +            var siteData = GetSite(site); +            if (siteData == null) +            { +                return; +            } +            foreach (var entry in siteData.Entries) +            { +                if (entry is GeoSiteIncludeEntry includeEntry) +                { +                    Visit(includeEntry.Value); +                } +                else if (entry is GeoSiteRuleEntry geoSiteRuleEntry) +                { +                    if (kinds != null && !kinds.Contains(geoSiteRuleEntry.Kind)) +                    { +                        continue; +                    } + +                    if (onlyAttributes != null && !geoSiteRuleEntry.Attributes.Intersect(onlyAttributes).Any()) +                    { +                        continue; +                    } + +                    entries.Add(geoSiteRuleEntry); +                } +            } +        } + +        foreach (var s in sites) +        { +            Visit(s); +        } + +        return entries; +    }  }  public class GeoDataManager @@ -132,6 +187,15 @@ public class GeoDataManager      public List<GeoDataAsset> Assets { get; set; } +    public GeoSiteData? GeoSiteData { get; set; } + +    public GeoSiteData GetOrCreateGeoSiteData(bool clean, bool silent) +    { +        if (GeoSiteData is not null) { return GeoSiteData; } +        GeoSiteData = DownloadAndGenerateGeoSiteData(clean, silent); +        return GeoSiteData; +    } +      private static string GetReleaseFileUrl(string user, string repo, string fileName)      {          return $"https://github.com/{user}/{repo}/releases/latest/download/{fileName}"; @@ -181,11 +245,14 @@ public class GeoDataManager          return $"https://github.com/{user}/{repo}/archive/refs/heads/master.zip";      } -    private static void GithubDownloadRepository(HttpClient httpClient, string user, string repo, string outputPath) +    private static void GithubDownloadRepository(HttpClient httpClient, string user, string repo, string outputPath, bool silent)      { -        using var responseStream = httpClient.GetStreamAsync(GetGithubRepositoryArchiveUrl(user, repo)).Result; +        var url = GetGithubRepositoryArchiveUrl(user, repo); +        if (!silent) { Console.WriteLine($"Begin to download data from {url} to {outputPath}."); } +        using var responseStream = httpClient.GetStreamAsync(url).Result;          using var outputFileStream = File.OpenWrite(outputPath);          responseStream.CopyTo(outputFileStream); +        if (!silent) { Console.WriteLine("Succeeded to download."); }      }      private static void Unzip(string zipPath, string outputPath) @@ -194,16 +261,47 @@ public class GeoDataManager          zip.ExtractToDirectory(outputPath);      } -    private string DownloadAndExtractGeoSiteRepository(bool silent) +    private static string DownloadAndExtractGeoDataRepository(bool cleanTempDirIfFailed, bool silent, out string tempDirectoryPath)      { +        tempDirectoryPath = "";          const string zipFileName = "v2ray-geosite-master.zip";          using var httpClient = new HttpClient();          var tempDirectory = Directory.CreateTempSubdirectory(Program.Name); -        var archivePath = Path.Combine(tempDirectory.FullName, zipFileName); -        var extractPath = Path.Combine(tempDirectory.FullName, "repo"); -        GithubDownloadRepository(httpClient, V2rayGithubOrganization, V2rayGeoSiteGithubRepository, archivePath); -        Directory.CreateDirectory(extractPath); -        Unzip(archivePath, extractPath); -        return Path.Join(extractPath, "domain-list-community-master"); +        tempDirectoryPath = tempDirectory.FullName; +        try +        { +            var archivePath = Path.Combine(tempDirectoryPath, zipFileName); +            var extractPath = Path.Combine(tempDirectoryPath, "repo"); +            GithubDownloadRepository(httpClient, V2rayGithubOrganization, V2rayGeoSiteGithubRepository, archivePath, silent); +            if (!silent) { Console.WriteLine($"Extract geo data to {extractPath}."); } +            Directory.CreateDirectory(extractPath); +            Unzip(archivePath, extractPath); +            if (!silent) { Console.WriteLine($"Extraction done."); } +            return Path.Join(extractPath, "domain-list-community-master"); +        } +        catch (Exception) +        { +            if (cleanTempDirIfFailed) +            { +                Directory.Delete(tempDirectoryPath, true); +            } +            throw; +        } +    } + +    private static GeoSiteData DownloadAndGenerateGeoSiteData(bool clean, bool silent) +    { +        var repoDirectory = DownloadAndExtractGeoDataRepository(clean, silent, out var tempDirectoryPath); +        try +        { +            return new GeoSiteData(Path.Join(repoDirectory, "data")); +        } +        finally +        { +            if (clean) +            { +                Directory.Delete(tempDirectoryPath, true); +            } +        }      }  } diff --git a/tools/Crupest.V2ray/Crupest.V2ray/Program.cs b/tools/Crupest.V2ray/Crupest.V2ray/Program.cs index a40aa21..0e98861 100644 --- a/tools/Crupest.V2ray/Crupest.V2ray/Program.cs +++ b/tools/Crupest.V2ray/Crupest.V2ray/Program.cs @@ -12,6 +12,8 @@ public static class Program              Assembly.GetExecutingAssembly().Location) ?? throw new Exception("Can't get the path of Crupest.V2ray."));      private const string ConfigOutputFileName = "config.json"; +    private const string SurgeRuleSetChinaOutputFileName = "ChinaRuleSet.txt"; +    private const string SurgeRuleSetGlobalOutputFileName = "GlobalRuleSet.txt";      public static void RunV2rayAndWatchConfigChange()      { @@ -68,6 +70,16 @@ public static class Program                  GeoDataManager.Instance.Download(CrupestV2rayDirectory, 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), +                    true, false +                ); +                return; +            }              else if (verb == "generate" || verb == "g")              {                  var config = V2rayConfig.FromDirectory(CrupestV2rayDirectory); diff --git a/tools/Crupest.V2ray/Crupest.V2ray/ProxyFile.cs b/tools/Crupest.V2ray/Crupest.V2ray/ProxyFile.cs new file mode 100644 index 0000000..ca5ca56 --- /dev/null +++ b/tools/Crupest.V2ray/Crupest.V2ray/ProxyFile.cs @@ -0,0 +1,14 @@ +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/SurgeConfigGenerator.cs b/tools/Crupest.V2ray/Crupest.V2ray/SurgeConfigGenerator.cs new file mode 100644 index 0000000..bd52234 --- /dev/null +++ b/tools/Crupest.V2ray/Crupest.V2ray/SurgeConfigGenerator.cs @@ -0,0 +1,60 @@ +namespace Crupest.V2ray; + +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) +    { +        var ruleType = kind switch +        { +            V2rayHostMatcherKind.DomainFull => "DOMAIN", +            V2rayHostMatcherKind.DomainSuffix => "DOMAIN-SUFFIX", +            V2rayHostMatcherKind.DomainKeyword => "DOMAIN-KEYWORD", +            V2rayHostMatcherKind.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, +    ]; + +    public string GenerateChinaRuleSet() +    { +        var geoSites = ProxyFile.MatcherConfig.Items.Where(i => i.Kind == V2rayHostMatcherKind.GeoSite).Select(i => i.Matcher).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 nonCnRules = GeoSiteData.GetEntriesRecursive(geoSites, DomainMatcherKinds).Where(e => !e.Attributes.Contains("cn")).ToList(); +        var domainRules = ProxyFile.MatcherConfig.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)) +        ]); +    } + +    public static SurgeConfigGenerator Create(string proxyFilePath, bool clean, bool silent) +    { +        var proxyFile = new ProxyFile(proxyFilePath); +        var geoSiteData = GeoDataManager.Instance.GetOrCreateGeoSiteData(clean, silent); +        return new SurgeConfigGenerator(proxyFile, geoSiteData); +    } + +    public static void GenerateTo(string proxyFilePath, string cnPath, string globalPath, bool clean, bool silent) +    { +        var generator = Create(proxyFilePath, clean, silent); +        File.WriteAllText(cnPath, generator.GenerateChinaRuleSet()); +        if (!silent) Console.WriteLine($"China rule set written to {cnPath}."); +        File.WriteAllText(globalPath, generator.GenerateGlobalRuleSet()); +        if (!silent) Console.WriteLine($"Global rule set written to {globalPath}."); +    } +} diff --git a/tools/Crupest.V2ray/Crupest.V2ray/V2rayConfig.cs b/tools/Crupest.V2ray/Crupest.V2ray/V2rayConfig.cs index 3758f47..e81a6cb 100644 --- a/tools/Crupest.V2ray/Crupest.V2ray/V2rayConfig.cs +++ b/tools/Crupest.V2ray/Crupest.V2ray/V2rayConfig.cs @@ -99,7 +99,8 @@ public class V2rayConfig(Template template, List<V2rayProxy> proxies, V2rayRouti              }          } -        string templateString, vmessString, routingString; +        ProxyFile proxyFile = new(proxyPath); +        string templateString, vmessString;          string? hostsString;          string file = ""; @@ -109,9 +110,6 @@ public class V2rayConfig(Template template, List<V2rayProxy> proxies, V2rayRouti              templateString = File.ReadAllText(templatePath);              file = vmessPath;              vmessString = File.ReadAllText(vmessPath); -            file = proxyPath; -            routingString = File.ReadAllText(proxyPath); -            file = proxyPath;              hostsString = hostsPath is not null ? File.ReadAllText(hostsPath) : null;          }          catch (Exception e) @@ -126,9 +124,9 @@ public class V2rayConfig(Template template, List<V2rayProxy> proxies, V2rayRouti              file = vmessPath;              var vmess = V2rayVmessProxy.CreateFromConfigString(vmessString, "proxy");              file = proxyPath; -            var routing = V2rayRouting.CreateFromConfigString(routingString, "proxy", UseCnGeoSite); +            var routing = proxyFile.ToV2rayRouting("proxy", UseCnGeoSite);              file = hostsPath ?? ""; -            var hosts = hostsString is not null ? V2rayHosts.CreateFromConfigString(hostsString) : null; +            var hosts = hostsString is not null ? V2rayHosts.CreateFromHostMatcherConfigString(hostsString) : null;              return new V2rayConfig(template, [vmess], routing, hosts);          }          catch (Exception e) diff --git a/tools/Crupest.V2ray/Crupest.V2ray/V2rayHostMacherConfig.cs b/tools/Crupest.V2ray/Crupest.V2ray/V2rayHostMacherConfig.cs index f444b5d..36ae44b 100644 --- a/tools/Crupest.V2ray/Crupest.V2ray/V2rayHostMacherConfig.cs +++ b/tools/Crupest.V2ray/Crupest.V2ray/V2rayHostMacherConfig.cs @@ -48,7 +48,7 @@ public class V2rayHostMatcherConfig(string configString, List<V2rayHostMatcherKi                      }                      if (allowedMatchers.Contains(matcher))                      { -                        if (IsDomainMatcher(matcher) && Uri.CheckHostName(matcherName) != UriHostNameType.Dns) +                        if (IsDomainMatcher(matcher) && matcher == V2rayHostMatcherKind.DomainRegex && Uri.CheckHostName(matcherName) != UriHostNameType.Dns)                          {                              throw new FormatException($"Invalid domain format in line {lineNumber}.");                          } @@ -93,3 +93,17 @@ public class V2rayHostMatcherConfig(string configString, List<V2rayHostMatcherKi      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 index 1d1c6d8..e9bf8cf 100644 --- a/tools/Crupest.V2ray/Crupest.V2ray/V2rayHosts.cs +++ b/tools/Crupest.V2ray/Crupest.V2ray/V2rayHosts.cs @@ -32,7 +32,7 @@ public class V2rayHosts(List<V2rayHostRule> rules) : IV2rayV4ConfigObject          return ToJsonObjectV4();      } -    public static V2rayHosts CreateFromConfigString(string configString) +    public static V2rayHosts CreateFromHostMatcherConfigString(string configString)      {          var matcherConfig = new V2rayHostMatcherConfig(configString,              [V2rayHostMatcherKind.DomainFull, V2rayHostMatcherKind.DomainKeyword, V2rayHostMatcherKind.DomainRegex, V2rayHostMatcherKind.DomainSuffix], minComponentCount: 1); diff --git a/tools/Crupest.V2ray/Crupest.V2ray/V2rayRouting.cs b/tools/Crupest.V2ray/Crupest.V2ray/V2rayRouting.cs index 080a7b4..f385233 100644 --- a/tools/Crupest.V2ray/Crupest.V2ray/V2rayRouting.cs +++ b/tools/Crupest.V2ray/Crupest.V2ray/V2rayRouting.cs @@ -84,10 +84,4 @@ public record V2rayRouting(List<V2rayRoutingRule> Rules, bool DirectGeositeCn =      }      object IV2rayV4ConfigObject.ToJsonObjectV4() => ToJsonObjectV4(); - -    public static V2rayRouting CreateFromConfigString(string configString, string outboundTag, bool directGeositeCn = true) -    { -        var matcherConfig = new V2rayHostMatcherConfig(configString, [.. Enum.GetValues<V2rayHostMatcherKind>()], maxComponentCount: 0); -        return new V2rayRouting(matcherConfig.Items.Select(i => new V2rayRoutingRule(i.Kind, i.Matcher, outboundTag)).ToList(), directGeositeCn); -    }  } diff --git a/tools/Crupest.V2ray/Crupest.V2ray/proxy.txt b/tools/Crupest.V2ray/Crupest.V2ray/proxy.txt index 33d3913..6273e35 100644 --- a/tools/Crupest.V2ray/Crupest.V2ray/proxy.txt +++ b/tools/Crupest.V2ray/Crupest.V2ray/proxy.txt @@ -32,6 +32,7 @@ GeoSite docker  GeoSite v2ray  GeoSite homebrew +GeoSite azure  GeoSite jsdelivr  GeoSite fastly  GeoSite heroku  | 
