diff options
| -rwxr-xr-x | tools/V2rayConfigGen/.gitignore | 2 | ||||
| -rwxr-xr-x | tools/V2rayConfigGen/V2rayConfigGen.sln | 30 | ||||
| -rwxr-xr-x | tools/V2rayConfigGen/V2rayConfigGen/.gitignore | 1 | ||||
| -rwxr-xr-x | tools/V2rayConfigGen/V2rayConfigGen/FileUtility.cs | 94 | ||||
| -rwxr-xr-x | tools/V2rayConfigGen/V2rayConfigGen/Program.cs | 22 | ||||
| -rwxr-xr-x | tools/V2rayConfigGen/V2rayConfigGen/V2rayConfig.cs | 48 | ||||
| -rwxr-xr-x | tools/V2rayConfigGen/V2rayConfigGen/V2rayConfigGen.csproj | 22 | ||||
| -rwxr-xr-x | tools/V2rayConfigGen/V2rayConfigGen/V2rayRouting.cs | 52 | ||||
| -rwxr-xr-x | tools/V2rayConfigGen/V2rayConfigGen/V2rayRoutingRule.cs | 27 | ||||
| -rwxr-xr-x | tools/V2rayConfigGen/V2rayConfigGen/V2rayRoutingRuleMatcher.cs | 72 | ||||
| -rwxr-xr-x | tools/V2rayConfigGen/V2rayConfigGen/V2rayVmessProxy.cs | 52 | ||||
| -rwxr-xr-x | tools/V2rayConfigGen/V2rayConfigGen/config.json.template | 63 | ||||
| -rwxr-xr-x | tools/V2rayConfigGen/V2rayConfigGen/proxy.txt | 16 | 
13 files changed, 501 insertions, 0 deletions
| diff --git a/tools/V2rayConfigGen/.gitignore b/tools/V2rayConfigGen/.gitignore new file mode 100755 index 0000000..1746e32 --- /dev/null +++ b/tools/V2rayConfigGen/.gitignore @@ -0,0 +1,2 @@ +bin +obj diff --git a/tools/V2rayConfigGen/V2rayConfigGen.sln b/tools/V2rayConfigGen/V2rayConfigGen.sln new file mode 100755 index 0000000..e864a06 --- /dev/null +++ b/tools/V2rayConfigGen/V2rayConfigGen.sln @@ -0,0 +1,30 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.34024.191 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "V2rayConfigGen", "V2rayConfigGen\V2rayConfigGen.csproj", "{05719320-C91F-4D2A-82A5-1D0247DDB389}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F4C2CE80-CDF8-4B08-8912-D1F0F14196AD}" +	ProjectSection(SolutionItems) = preProject +		.gitignore = .gitignore +	EndProjectSection +EndProject +Global +	GlobalSection(SolutionConfigurationPlatforms) = preSolution +		Debug|Any CPU = Debug|Any CPU +		Release|Any CPU = Release|Any CPU +	EndGlobalSection +	GlobalSection(ProjectConfigurationPlatforms) = postSolution +		{05719320-C91F-4D2A-82A5-1D0247DDB389}.Debug|Any CPU.ActiveCfg = Debug|Any CPU +		{05719320-C91F-4D2A-82A5-1D0247DDB389}.Debug|Any CPU.Build.0 = Debug|Any CPU +		{05719320-C91F-4D2A-82A5-1D0247DDB389}.Release|Any CPU.ActiveCfg = Release|Any CPU +		{05719320-C91F-4D2A-82A5-1D0247DDB389}.Release|Any CPU.Build.0 = Release|Any CPU +	EndGlobalSection +	GlobalSection(SolutionProperties) = preSolution +		HideSolutionNode = FALSE +	EndGlobalSection +	GlobalSection(ExtensibilityGlobals) = postSolution +		SolutionGuid = {B1E8FD9C-9157-4F4E-8265-4B37F30EEC5E} +	EndGlobalSection +EndGlobal diff --git a/tools/V2rayConfigGen/V2rayConfigGen/.gitignore b/tools/V2rayConfigGen/V2rayConfigGen/.gitignore new file mode 100755 index 0000000..c936492 --- /dev/null +++ b/tools/V2rayConfigGen/V2rayConfigGen/.gitignore @@ -0,0 +1 @@ +vmess.txt diff --git a/tools/V2rayConfigGen/V2rayConfigGen/FileUtility.cs b/tools/V2rayConfigGen/V2rayConfigGen/FileUtility.cs new file mode 100755 index 0000000..08de673 --- /dev/null +++ b/tools/V2rayConfigGen/V2rayConfigGen/FileUtility.cs @@ -0,0 +1,94 @@ +using System.Text.Json; +using System.Text.RegularExpressions; + +namespace Crupest.V2ray; + +public static partial class FileUtility +{ +    public static List<string> ReadList(string str) +    { +        return str.Split("\n", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToList(); +    } + +    public static Dictionary<string, string> ReadDictionary(string str, bool keyToLower = true) +    { +        var lines = str.Split("\n", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); +        var result = new Dictionary<string, string>(); +        for (int lineNumber = 0; lineNumber < lines.Length; lineNumber++) +        { +            var line = lines[lineNumber]; +            if (!line.Contains('=')) +            { +                throw new FormatException($"Line {lineNumber + 1} does not contain a '='."); +            } +            var equalIndex = line.IndexOf('='); +            var key = line[..equalIndex].Trim(); +            if (keyToLower) key = key.ToLower(); +            var value = line[(equalIndex + 1)..].Trim(); +            result[key] = value; +        } +        return result; +    } + +    public static List<string> ReadListFile(string path, bool required = true) +    { +        if (File.Exists(path)) +        { +            return ReadList(File.ReadAllText(path)); +        } +        else +        { +            if (required) +            { +                throw new FileNotFoundException($"File {path} is required but it does not exist."); +            } +            return new(); +        } +    } + +    public static Dictionary<string, string> ReadDictionaryFile(string path, bool required = true, bool keyToLower = true) +    { +        if (File.Exists(path)) +        { +            return ReadDictionary(File.ReadAllText(path), keyToLower); +        } +        else +        { +            if (required) +            { +                throw new FileNotFoundException($"File {path} is required but it does not exist."); +            } +            return new(); +        } +    } + +    private static Regex TemplateValuePattern { get; } = CreateTemplateValuePattern(); + +    [GeneratedRegex(@"\$\{\s*([_a-zA-Z][_a-zA-Z0-9]*)\s*\}")] +    private static partial Regex CreateTemplateValuePattern(); + +    public static string TextFromTemplate(string template, Dictionary<string, string> dict) +    { +        return TemplateValuePattern.Replace(template, (match) => +        { +            var key = match.Groups[1].Value; +            if (dict.ContainsKey(key)) +            { +                return dict[key]; +            } +            return match.Value; +        }); +    } + +    public static string JsonFormat(string json) +    { +        var options = new JsonSerializerOptions +        { +            WriteIndented = true, +            AllowTrailingCommas = true, +            ReadCommentHandling = JsonCommentHandling.Skip +        }; + +        return JsonSerializer.Serialize(JsonSerializer.Deserialize<JsonDocument>(json, options), options); +    } +} diff --git a/tools/V2rayConfigGen/V2rayConfigGen/Program.cs b/tools/V2rayConfigGen/V2rayConfigGen/Program.cs new file mode 100755 index 0000000..bebdc7a --- /dev/null +++ b/tools/V2rayConfigGen/V2rayConfigGen/Program.cs @@ -0,0 +1,22 @@ +using System.Reflection; + +namespace Crupest.V2ray; + +public static class Program +{ +    public const string ConfigTemplateFile = "config.json.template"; +    public const string VmessConfigFile = "vmess.txt"; +    public const string ProxyGeoSitesFile = "proxy.txt"; + +    public static void Main(string[] args) +    { +        var exeLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); +        var config = V2rayConfig.FromFiles( +            Path.Combine(exeLocation, ConfigTemplateFile), +            Path.Combine(exeLocation, VmessConfigFile), +            Path.Combine(exeLocation, ProxyGeoSitesFile) +        ); + +        Console.Write(config.ToJson()); +    } +} diff --git a/tools/V2rayConfigGen/V2rayConfigGen/V2rayConfig.cs b/tools/V2rayConfigGen/V2rayConfigGen/V2rayConfig.cs new file mode 100755 index 0000000..0d8b0bb --- /dev/null +++ b/tools/V2rayConfigGen/V2rayConfigGen/V2rayConfig.cs @@ -0,0 +1,48 @@ +using System.Text.Json; + +namespace Crupest.V2ray; + +public class V2rayConfig +{ +    private const string VmessAnchor = "VMESS_PROXY_ANCHOR"; +    private const string RoutingAnchor = "ROUTING_ANCHOR"; + +    public V2rayConfig(string template, V2rayVmessProxy vmess, V2rayRouting router) { +        Template = template; +        Vmess = vmess; +        Routing = router; +    } + +    public string Template { get; set; } +    public V2rayVmessProxy Vmess { get; set; } +    public V2rayRouting Routing { get; set; } + +    public string ToJson(bool pretty = true) +    { +        var jsonOptions = new JsonSerializerOptions(new JsonSerializerOptions +        { +            PropertyNamingPolicy = JsonNamingPolicy.CamelCase, +            DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, +        }); + +        var templateValues = new Dictionary<string, string> +        { +            [VmessAnchor] = JsonSerializer.Serialize(Vmess.ToOutboundJsonObject(), jsonOptions), +            [RoutingAnchor] = JsonSerializer.Serialize(Routing.ToJsonObject(), jsonOptions) +        }; + +        return FileUtility.JsonFormat(FileUtility.TextFromTemplate(Template, templateValues)); +    } + +    public static V2rayConfig FromFiles(string templatePath, string vmessPath, string routingPath) +    { +        var template = File.ReadAllText(templatePath); +        var vmessDict = FileUtility.ReadDictionaryFile(vmessPath); +        var proxyRoutingList = FileUtility.ReadListFile(routingPath); + +        var vmess = V2rayVmessProxy.FromDictionary(vmessDict); +        var routing = V2rayRouting.FromStringList(proxyRoutingList); + +        return new V2rayConfig(template, vmess, routing); +    } +} diff --git a/tools/V2rayConfigGen/V2rayConfigGen/V2rayConfigGen.csproj b/tools/V2rayConfigGen/V2rayConfigGen/V2rayConfigGen.csproj new file mode 100755 index 0000000..38f0937 --- /dev/null +++ b/tools/V2rayConfigGen/V2rayConfigGen/V2rayConfigGen.csproj @@ -0,0 +1,22 @@ +<Project Sdk="Microsoft.NET.Sdk"> + +  <PropertyGroup> +    <OutputType>Exe</OutputType> +    <TargetFramework>net7.0</TargetFramework> +    <ImplicitUsings>enable</ImplicitUsings> +    <Nullable>enable</Nullable> +  </PropertyGroup> + +  <ItemGroup> +    <None Update="config.json.template"> +      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> +    </None> +    <None Update="proxy.txt"> +      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> +    </None> +    <None Update="vmess.txt"> +      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> +    </None> +  </ItemGroup> + +</Project> diff --git a/tools/V2rayConfigGen/V2rayConfigGen/V2rayRouting.cs b/tools/V2rayConfigGen/V2rayConfigGen/V2rayRouting.cs new file mode 100755 index 0000000..fe59491 --- /dev/null +++ b/tools/V2rayConfigGen/V2rayConfigGen/V2rayRouting.cs @@ -0,0 +1,52 @@ +namespace Crupest.V2ray; + +public record V2rayRouting(List<V2rayRoutingRule> Rules, string DomainStrategy = "IpOnDemand") +{ +    public record DomainRuleJsonObject(List<string> Domains, string OutboundTag, string Type = "field"); + +    public record IpRuleJsonObject(List<string> Ip, string OutboundTag, string Type = "field"); + +    public record RoutingJsonObject(string DomainStrategy, List<object> Rules); + +    public V2rayRouting() : this(new List<V2rayRoutingRule>()) +    { + +    } + +    public RoutingJsonObject ToJsonObject() +    { +        var ruleJsonObjects = new List<object>(); + +        foreach (var(outBoundTag, proxyRules) in V2rayRoutingRule.GroupByOutboundTag(Rules)) +        { +            foreach (var (matchByKind, rules) in V2rayRoutingRule.GroupByMatchByKind(proxyRules)) +            { +                ruleJsonObjects.Add( +                    matchByKind switch +                    { +                        V2rayRoutingRuleMatcher.MatchByKind.Ip => new IpRuleJsonObject(rules.Select(r => r.Matcher.ToString()).ToList(), outBoundTag), +                        V2rayRoutingRuleMatcher.MatchByKind.Domain => new DomainRuleJsonObject(rules.Select(r => r.Matcher.ToString()).ToList(), outBoundTag), +                        _ => throw new Exception("Unknown match by kind."), +                    } +                ); +            } +        } + +        return new RoutingJsonObject(DomainStrategy ,ruleJsonObjects); +    } + +    public static V2rayRouting FromStringList(List<string> list, string outboundTag = "proxy") +    { +        var router = new V2rayRouting(); + +        foreach (var line in list) +        { +            var matcher = V2rayRoutingRuleMatcher.Parse(line); +            if (matcher != null) +                router.Rules.Add(new V2rayRoutingRule(matcher, outboundTag)); +        } + +        return router; +    } +} + diff --git a/tools/V2rayConfigGen/V2rayConfigGen/V2rayRoutingRule.cs b/tools/V2rayConfigGen/V2rayConfigGen/V2rayRoutingRule.cs new file mode 100755 index 0000000..23c08e7 --- /dev/null +++ b/tools/V2rayConfigGen/V2rayConfigGen/V2rayRoutingRule.cs @@ -0,0 +1,27 @@ +using System.Linq; + +namespace Crupest.V2ray; + +public record V2rayRoutingRule(V2rayRoutingRuleMatcher Matcher, string OutboundTag) +{ +    public static Dictionary<string, List<V2rayRoutingRule>> GroupByOutboundTag(List<V2rayRoutingRule> rules) +    { +        var result = new Dictionary<string, List<V2rayRoutingRule>>(); +        foreach (var group in rules.GroupBy(r => r.OutboundTag)) +        { +            result[group.Key] = group.ToList(); +        } +        return result; +    } + +    public static Dictionary<V2rayRoutingRuleMatcher.MatchByKind, List<V2rayRoutingRule>> GroupByMatchByKind(List<V2rayRoutingRule> rules) +    { +        var result = new Dictionary<V2rayRoutingRuleMatcher.MatchByKind, List<V2rayRoutingRule>>(); +        foreach (var group in rules.GroupBy(r => r.Matcher.MatchBy)) +        { +            result[group.Key] = group.ToList(); +        } +        return result; +    } +} + diff --git a/tools/V2rayConfigGen/V2rayConfigGen/V2rayRoutingRuleMatcher.cs b/tools/V2rayConfigGen/V2rayConfigGen/V2rayRoutingRuleMatcher.cs new file mode 100755 index 0000000..be27231 --- /dev/null +++ b/tools/V2rayConfigGen/V2rayConfigGen/V2rayRoutingRuleMatcher.cs @@ -0,0 +1,72 @@ +namespace Crupest.V2ray; + +public record V2rayRoutingRuleMatcher(V2rayRoutingRuleMatcher.MatchKind Kind, string Value) +{ +    public enum MatchByKind +    { +        Domain, +        Ip +    } + +    public enum MatchKind +    { +        GeoIp, +        GeoSite, +        DomainPlain, +        DomainSuffix, +        DomainRegex, +        DomainFull, +    } + +    public MatchByKind MatchBy +    { +        get +        { +            return Kind switch +            { +                MatchKind.GeoIp => MatchByKind.Ip, +                _ => MatchByKind.Domain +            }; +        } +    } + +    public static V2rayRoutingRuleMatcher? Parse(string line) +    { +        if (line.IndexOf('#') != -1) +        { +            line = line[..line.IndexOf('#')]; +        } + +        line = line.Trim(); + +        if (line.Length == 0) { return null; } + +        var kind = MatchKind.DomainSuffix; + +        foreach (var name in Enum.GetNames<MatchKind>()) { +            if (line.StartsWith(name)) { +                kind = Enum.Parse<MatchKind>(name); +                line = line[name.Length..]; +                line = line.Trim(); +                break; +            } +        } + +        return new V2rayRoutingRuleMatcher(kind, line); +    } + + +    public override string ToString() +    { +        return Kind switch +        { +            MatchKind.GeoSite => $"geosite:{Value}", +            MatchKind.GeoIp => $"geoip:{Value}", +            MatchKind.DomainPlain => Value, +            MatchKind.DomainSuffix => $"domain:{Value}", +            MatchKind.DomainFull => $"full:{Value}", +            MatchKind.DomainRegex => $"regexp:{Value}", +            _ => throw new Exception("Unknown matcher kind."), +        }; +    } +} diff --git a/tools/V2rayConfigGen/V2rayConfigGen/V2rayVmessProxy.cs b/tools/V2rayConfigGen/V2rayConfigGen/V2rayVmessProxy.cs new file mode 100755 index 0000000..25d466e --- /dev/null +++ b/tools/V2rayConfigGen/V2rayConfigGen/V2rayVmessProxy.cs @@ -0,0 +1,52 @@ +namespace Crupest.V2ray; + +public class V2rayVmessProxy +{ +    public record VmessOutboundJsonObject(string Protocol, SettingsJsonObject Settings, string Tag, StreamSettingsJsonObject StreamSettings) +    { +        public static VmessOutboundJsonObject ByWs(string address, int port, string uuid, string tag, string path) +        { +            return new VmessOutboundJsonObject("vmess", new SettingsJsonObject( +                new List<VnextJsonObject>{ new VnextJsonObject(address, port, new List<VnextUserJsonObject> { new VnextUserJsonObject(uuid) }) } +            ), tag, StreamSettingsJsonObject.Ws(path)); +        } +    } + +    public record SettingsJsonObject(List<VnextJsonObject> Vnext); + +    public record VnextJsonObject(string Address, int Port, List<VnextUserJsonObject> Users); + +    public record VnextUserJsonObject(string Id, int AlterId = 0, string Security = "auto", int Level = 0); + +    public record StreamSettingsJsonObject(string Network, string Security, WsSettingsJsonObject WsSettings) { +        public static StreamSettingsJsonObject Ws(string path) +        { +            return new StreamSettingsJsonObject("ws", "tls", new WsSettingsJsonObject(path, new())); +        } +    } + +    public record WsSettingsJsonObject(string Path, Dictionary<string, string> Headers); + +    public string Host { get; set; } +    public int Port { get; set; } +    public string Path { get; set; } +    public string UserId { get; set; } + + +    public V2rayVmessProxy(string host, int port, string userId, string path) { +        Host = host; +        Port = port; +        UserId = userId; +        Path = path; +    } + +    public VmessOutboundJsonObject ToOutboundJsonObject(string tag = "proxy") +    { +        return VmessOutboundJsonObject.ByWs(Host, Port, UserId, tag, Path); +    } + +    public static V2rayVmessProxy FromDictionary(Dictionary<string, string> dict) +    { +        return new V2rayVmessProxy(dict["host"], int.Parse(dict["port"]), dict["userid"], dict["path"]); +    } +} diff --git a/tools/V2rayConfigGen/V2rayConfigGen/config.json.template b/tools/V2rayConfigGen/V2rayConfigGen/config.json.template new file mode 100755 index 0000000..9ba8af6 --- /dev/null +++ b/tools/V2rayConfigGen/V2rayConfigGen/config.json.template @@ -0,0 +1,63 @@ +{ +  "log": { +    "loglevel": "warning" +  }, +  "inbounds": [ +    { +      "port": 2081, +      "listen": "127.0.0.1", +      "tag": "socks-inbound", +      "protocol": "socks", +      "settings": { +        "auth": "noauth" +      } +    }, +    { +      "port": 2080, +      "listen": "127.0.0.1", +      "tag": "http-inbound", +      "protocol": "http", +      "settings": { +        "auth": "noauth" +      } +    } +  ], +  "outbounds": [ +    { +      "protocol": "freedom", +      "settings": {}, +      "tag": "direct" +    }, +    { +      "protocol": "blackhole", +      "settings": {}, +      "tag": "blocked" +    }, +    ${VMESS_PROXY_ANCHOR} +  ], +  "routing": ${ROUTING_ANCHOR}, +  "dns": { +    "hosts": {}, +    "servers": [ +      "https://doh.pub/dns-query", +      "1.1.1.1", +      "8.8.8.8", +      "localhost" +    ] +  }, +  "policy": { +    "levels": { +      "0": { +        "uplinkOnly": 0, +        "downlinkOnly": 0 +      } +    }, +    "system": { +      "statsInboundUplink": false, +      "statsInboundDownlink": false, +      "statsOutboundUplink": false, +      "statsOutboundDownlink": false +    } +  }, +  "other": {} +} diff --git a/tools/V2rayConfigGen/V2rayConfigGen/proxy.txt b/tools/V2rayConfigGen/V2rayConfigGen/proxy.txt new file mode 100755 index 0000000..4e9d3e8 --- /dev/null +++ b/tools/V2rayConfigGen/V2rayConfigGen/proxy.txt @@ -0,0 +1,16 @@ +GeoSite github +GeoSite google +GeoSite youtube +GeoSite twitter +GeoSite facebook +GeoSite discord +GeoSite reddit +GeoSite wikimedia +GeoSite stackexchange +GeoSite libgen +GeoSite python +GeoSite ruby +GeoSite creativecommons +GeoSite sci-hub +GeoSite v2ray +GeoSite imgur | 
