aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tools/Crupest.V2ray/Crupest.V2ray/Config.cs14
-rw-r--r--tools/Crupest.V2ray/Crupest.V2ray/GeoDataManager.cs138
-rw-r--r--tools/Crupest.V2ray/Crupest.V2ray/Program.cs12
-rw-r--r--tools/Crupest.V2ray/Crupest.V2ray/ProxyFile.cs14
-rw-r--r--tools/Crupest.V2ray/Crupest.V2ray/SurgeConfigGenerator.cs60
-rw-r--r--tools/Crupest.V2ray/Crupest.V2ray/V2rayConfig.cs10
-rw-r--r--tools/Crupest.V2ray/Crupest.V2ray/V2rayHostMacherConfig.cs16
-rw-r--r--tools/Crupest.V2ray/Crupest.V2ray/V2rayHosts.cs2
-rw-r--r--tools/Crupest.V2ray/Crupest.V2ray/V2rayRouting.cs6
-rw-r--r--tools/Crupest.V2ray/Crupest.V2ray/proxy.txt1
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