Merge branch 'master' into master

This commit is contained in:
freekof 2026-03-03 10:30:12 +08:00 committed by GitHub
commit fa9709da07
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 617 additions and 648 deletions

View file

@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<Version>7.19.0</Version>
<Version>7.19.1</Version>
</PropertyGroup>
<PropertyGroup>

View file

@ -22,7 +22,7 @@
<PackageVersion Include="Semi.Avalonia" Version="11.3.7.3" />
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="11.2.0.1" />
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7.3" />
<PackageVersion Include="NLog" Version="6.1.0" />
<PackageVersion Include="NLog" Version="6.1.1" />
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
<PackageVersion Include="WebDav.Client" Version="2.9.0" />

View file

@ -497,6 +497,13 @@ public class Utils
return false;
}
var ext = Path.GetExtension(domain);
if (ext.IsNotEmpty()
&& ext[1..].ToLowerInvariant() is "json" or "txt" or "xml" or "cfg" or "ini" or "log" or "yaml" or "yml" or "toml")
{
return false;
}
return Uri.CheckHostName(domain) == UriHostNameType.Dns;
}

View file

@ -49,6 +49,7 @@ public class Global
public const string DirectTag = "direct";
public const string BlockTag = "block";
public const string DnsTag = "dns-module";
public const string DirectDnsTag = "direct-dns";
public const string BalancerTagSuffix = "-round";
public const string StreamSecurity = "tls";
public const string StreamSecurityReality = "reality";
@ -90,6 +91,22 @@ public class Global
public const int Hysteria2DefaultHopInt = 10;
public const string PolicyGroupExcludeKeywords = @"剩余|过期|到期|重置|[Rr]emaining|[Ee]xpir|[Rr]eset";
public const string PolicyGroupDefaultAllFilter = $"^(?!.*(?:{PolicyGroupExcludeKeywords})).*$";
public static readonly List<string> PolicyGroupDefaultFilterList =
[
// All nodes (exclude traffic/expiry info)
PolicyGroupDefaultAllFilter,
// Low multiplier nodes, e.g. ×0.1, 0.5x, 0.1倍
@"^.*(?:[×xX✕*]\s*0\.[0-9]+|0\.[0-9]+\s*[×xX✕*倍]).*$",
// Dedicated line nodes, e.g. IPLC, IEPL
$@"^(?!.*(?:{PolicyGroupExcludeKeywords})).*(?:专线|IPLC|IEPL|中转).*$",
// Japan nodes
$@"^(?!.*(?:{PolicyGroupExcludeKeywords})).*(?:日本|\\b[Jj][Pp]\\b|🇯🇵|[Jj]apan).*$",
];
public static readonly List<string> IEProxyProtocols =
[
"{ip}:{http_port}",

View file

@ -5,6 +5,39 @@ public record CoreConfigContextBuilderResult(CoreConfigContext Context, NodeVali
public bool Success => ValidatorResult.Success;
}
/// <summary>
/// Holds the results of a full context build, including the main context and an optional
/// pre-socks context (e.g. for TUN protection or pre-socks chaining).
/// </summary>
public record CoreConfigContextBuilderAllResult(
CoreConfigContextBuilderResult MainResult,
CoreConfigContextBuilderResult? PreSocksResult)
{
/// <summary>True only when both the main result and (if present) the pre-socks result succeeded.</summary>
public bool Success => MainResult.Success && (PreSocksResult?.Success ?? true);
/// <summary>
/// Merges all errors and warnings from the main result and the optional pre-socks result
/// into a single <see cref="NodeValidatorResult"/> for unified notification.
/// </summary>
public NodeValidatorResult CombinedValidatorResult => new(
[.. MainResult.ValidatorResult.Errors, .. PreSocksResult?.ValidatorResult.Errors ?? []],
[.. MainResult.ValidatorResult.Warnings, .. PreSocksResult?.ValidatorResult.Warnings ?? []]);
/// <summary>
/// The main context with TunProtectSsPort/ProxyRelaySsPort and ProtectDomainList merged in
/// from the pre-socks result (if any). Pass this to the core runner.
/// </summary>
public CoreConfigContext ResolvedMainContext => PreSocksResult is not null
? MainResult.Context with
{
TunProtectSsPort = PreSocksResult.Context.TunProtectSsPort,
ProxyRelaySsPort = PreSocksResult.Context.ProxyRelaySsPort,
ProtectDomainList = [.. MainResult.Context.ProtectDomainList ?? [], .. PreSocksResult.Context.ProtectDomainList ?? []],
}
: MainResult.Context;
}
public class CoreConfigContextBuilder
{
/// <summary>
@ -75,6 +108,79 @@ public class CoreConfigContextBuilder
return new CoreConfigContextBuilderResult(context, validatorResult);
}
/// <summary>
/// Builds the main <see cref="CoreConfigContext"/> for <paramref name="node"/> and, when
/// the main build succeeds, also builds the optional pre-socks context required for TUN
/// protection or pre-socks proxy chaining.
/// </summary>
public static async Task<CoreConfigContextBuilderAllResult> BuildAll(Config config, ProfileItem node)
{
var mainResult = await Build(config, node);
if (!mainResult.Success)
{
return new CoreConfigContextBuilderAllResult(mainResult, null);
}
var preResult = await BuildPreSocksIfNeeded(mainResult.Context);
return new CoreConfigContextBuilderAllResult(mainResult, preResult);
}
/// <summary>
/// Determines whether a pre-socks context is required for <paramref name="nodeContext"/>
/// and, if so, builds and returns it. Returns <c>null</c> when no pre-socks core is needed.
/// </summary>
private static async Task<CoreConfigContextBuilderResult?> BuildPreSocksIfNeeded(CoreConfigContext nodeContext)
{
var config = nodeContext.AppConfig;
var node = nodeContext.Node;
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
var preSocksItem = ConfigHandler.GetPreSocksItem(config, node, coreType);
if (preSocksItem != null)
{
var preSocksResult = await Build(nodeContext.AppConfig, preSocksItem);
return preSocksResult with
{
Context = preSocksResult.Context with
{
ProtectDomainList = [.. nodeContext.ProtectDomainList ?? [], .. preSocksResult.Context.ProtectDomainList ?? []],
}
};
}
if (!nodeContext.IsTunEnabled
|| coreType != ECoreType.Xray
|| node.ConfigType == EConfigType.Custom)
{
return null;
}
var tunProtectSsPort = Utils.GetFreePort();
var proxyRelaySsPort = Utils.GetFreePort();
var preItem = new ProfileItem()
{
CoreType = ECoreType.sing_box,
ConfigType = EConfigType.Shadowsocks,
Address = Global.Loopback,
Port = proxyRelaySsPort,
Password = Global.None,
};
preItem.SetProtocolExtra(preItem.GetProtocolExtra() with
{
SsMethod = Global.None,
});
var preResult2 = await Build(nodeContext.AppConfig, preItem);
return preResult2 with
{
Context = preResult2.Context with
{
ProtectDomainList = [.. nodeContext.ProtectDomainList ?? [], .. preResult2.Context.ProtectDomainList ?? []],
TunProtectSsPort = tunProtectSsPort,
ProxyRelaySsPort = proxyRelaySsPort,
}
};
}
/// <summary>
/// Resolves a node into the context, optionally wrapping it in a subscription-level proxy chain.
/// Returns the effective (possibly replaced) node and the validation result.

View file

@ -231,6 +231,7 @@ public static class ConfigHandler
item.Address = profileItem.Address;
item.Port = profileItem.Port;
item.Username = profileItem.Username;
item.Password = profileItem.Password;
item.Network = profileItem.Network;
@ -1165,46 +1166,28 @@ public static class ConfigHandler
/// <summary>
/// Create a group server that combines multiple servers for load balancing
/// Generates a configuration file that references multiple servers
/// Generates a PolicyGroup profile with references to the sub-items
/// </summary>
/// <param name="config">Current configuration</param>
/// <param name="selecteds">Selected servers to combine</param>
/// <param name="coreType">Core type to use (Xray or sing_box)</param>
/// <param name="multipleLoad">Load balancing algorithm</param>
/// <param name="subItem">Sub-item for grouping</param>
/// <returns>Result object with success state and data</returns>
public static async Task<RetResult> AddGroupServer4Multiple(Config config, List<ProfileItem> selecteds, ECoreType coreType, EMultipleLoad multipleLoad, string? subId)
public static async Task<RetResult> AddGroupAllServer(Config config, SubItem? subItem)
{
var result = new RetResult();
var indexId = Utils.GetGuid(false);
var childProfileIndexId = Utils.List2String(selecteds.Select(p => p.IndexId).ToList());
var subId = subItem?.Id;
if (subId.IsNullOrEmpty())
{
result.Success = false;
return result;
}
var remark = subId.IsNullOrEmpty() ? string.Empty : $"{(await AppManager.Instance.GetSubItem(subId))?.Remarks} ";
if (coreType == ECoreType.Xray)
{
remark += multipleLoad switch
{
EMultipleLoad.LeastPing => ResUI.menuGenGroupMultipleServerXrayLeastPing,
EMultipleLoad.Fallback => ResUI.menuGenGroupMultipleServerXrayFallback,
EMultipleLoad.Random => ResUI.menuGenGroupMultipleServerXrayRandom,
EMultipleLoad.RoundRobin => ResUI.menuGenGroupMultipleServerXrayRoundRobin,
EMultipleLoad.LeastLoad => ResUI.menuGenGroupMultipleServerXrayLeastLoad,
_ => ResUI.menuGenGroupMultipleServerXrayRoundRobin,
};
}
else if (coreType == ECoreType.sing_box)
{
remark += multipleLoad switch
{
EMultipleLoad.LeastPing => ResUI.menuGenGroupMultipleServerSingBoxLeastPing,
EMultipleLoad.Fallback => ResUI.menuGenGroupMultipleServerSingBoxFallback,
_ => ResUI.menuGenGroupMultipleServerSingBoxLeastPing,
};
}
var indexId = Utils.GetGuid(false);
var remark = $"{subItem.Remarks} - {ResUI.TbConfigTypePolicyGroup}";
var profile = new ProfileItem
{
IndexId = indexId,
CoreType = coreType,
CoreType = ECoreType.Xray,
ConfigType = EConfigType.PolicyGroup,
Remarks = remark,
IsSub = false
@ -1215,8 +1198,10 @@ public static class ConfigHandler
}
var extraItem = new ProtocolExtraItem
{
ChildItems = childProfileIndexId,
MultipleLoad = multipleLoad,
MultipleLoad = EMultipleLoad.LeastPing,
GroupType = profile.ConfigType.ToString(),
SubChildItems = subId,
Filter = Global.PolicyGroupDefaultAllFilter,
};
profile.SetProtocolExtra(extraItem);
var ret = await AddServerCommon(config, profile, true);
@ -1225,6 +1210,92 @@ public static class ConfigHandler
return result;
}
private static string CombineWithDefaultAllFilter(string regionPattern)
{
return $"^(?!.*(?:{Global.PolicyGroupExcludeKeywords})).*(?:{regionPattern}).*$";
}
private static readonly Dictionary<string, string> PolicyGroupRegionFilters = new()
{
{ "JP", "日本|\\b[Jj][Pp]\\b|🇯🇵|[Jj]apan" },
{ "US", "美国|\\b[Uu][Ss]\\b|🇺🇸|[Uu]nited [Ss]tates|\\b[Uu][Ss][Aa]\\b" },
{ "HK", "香港|\\b[Hh][Kk]\\b|🇭🇰|[Hh]ong ?[Kk]ong" },
{ "TW", "台湾|台灣|\\b[Tt][Ww]\\b|🇹🇼|[Tt]aiwan" },
{ "KR", "韩国|\\b[Kk][Rr]\\b|🇰🇷|[Kk]orea" },
{ "SG", "新加坡|\\b[Ss][Gg]\\b|🇸🇬|[Ss]ingapore" },
{ "DE", "德国|\\b[Dd][Ee]\\b|🇩🇪|[Gg]ermany" },
{ "FR", "法国|\\b[Ff][Rr]\\b|🇫🇷|[Ff]rance" },
{ "GB", "英国|\\b[Gg][Bb]\\b|🇬🇧|[Uu]nited [Kk]ingdom|[Bb]ritain" },
{ "CA", "加拿大|🇨🇦|[Cc]anada" },
{ "AU", "澳大利亚|\\b[Aa][Uu]\\b|🇦🇺|[Aa]ustralia" },
{ "RU", "俄罗斯|\\b[Rr][Uu]\\b|🇷🇺|[Rr]ussia" },
{ "BR", "巴西|\\b[Bb][Rr]\\b|🇧🇷|[Bb]razil" },
{ "IN", "印度|🇮🇳|[Ii]ndia" },
{ "VN", "越南|\\b[Vv][Nn]\\b|🇻🇳|[Vv]ietnam" },
{ "ID", "印度尼西亚|\\b[Ii][Dd]\\b|🇮🇩|[Ii]ndonesia" },
{ "MX", "墨西哥|\\b[Mm][Xx]\\b|🇲🇽|[Mm]exico" }
};
public static async Task<RetResult> AddGroupRegionServer(Config config, SubItem? subItem)
{
var result = new RetResult();
var subId = subItem?.Id;
if (subId.IsNullOrEmpty())
{
result.Success = false;
return result;
}
var childProfiles = await AppManager.Instance.ProfileItems(subId);
List<string> indexIdList = [];
foreach (var regionFilter in PolicyGroupRegionFilters)
{
var indexId = Utils.GetGuid(false);
var remark = $"{subItem.Remarks} - {ResUI.TbConfigTypePolicyGroup} - {regionFilter.Key}";
var profile = new ProfileItem
{
IndexId = indexId,
CoreType = ECoreType.Xray,
ConfigType = EConfigType.PolicyGroup,
Remarks = remark,
IsSub = false
};
if (!subId.IsNullOrEmpty())
{
profile.Subid = subId;
}
var extraItem = new ProtocolExtraItem
{
MultipleLoad = EMultipleLoad.LeastPing,
GroupType = profile.ConfigType.ToString(),
SubChildItems = subId,
Filter = CombineWithDefaultAllFilter(regionFilter.Value),
};
profile.SetProtocolExtra(extraItem);
var matchedChildProfiles = childProfiles?.Where(p =>
p != null &&
p.IsValid() &&
!p.ConfigType.IsComplexType() &&
(extraItem.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, extraItem.Filter))
)
.ToList() ?? [];
if (matchedChildProfiles.Count == 0)
{
continue;
}
var ret = await AddServerCommon(config, profile, true);
if (ret == 0)
{
indexIdList.Add(indexId);
}
}
result.Success = indexIdList.Count > 0;
result.Data = indexIdList;
return result;
}
/// <summary>
/// Get a SOCKS server profile for pre-SOCKS functionality
/// Used when TUN mode is enabled or when a custom config has a pre-SOCKS port
@ -1251,47 +1322,6 @@ public static class ConfigHandler
return itemSocks;
}
public static CoreConfigContext? GetPreSocksCoreConfigContext(CoreConfigContext nodeContext)
{
var config = nodeContext.AppConfig;
var node = nodeContext.Node;
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
var preSocksItem = GetPreSocksItem(config, node, coreType);
if (preSocksItem != null)
{
return nodeContext with { Node = preSocksItem, };
}
if ((!nodeContext.IsTunEnabled)
|| coreType != ECoreType.Xray
|| node.ConfigType == EConfigType.Custom)
{
return null;
}
var tunProtectSsPort = Utils.GetFreePort();
var proxyRelaySsPort = Utils.GetFreePort();
var preItem = new ProfileItem()
{
CoreType = ECoreType.sing_box,
ConfigType = EConfigType.Shadowsocks,
Address = Global.Loopback,
Port = proxyRelaySsPort,
Password = Global.None,
};
preItem.SetProtocolExtra(preItem.GetProtocolExtra() with
{
SsMethod = Global.None,
});
var preContext = nodeContext with
{
Node = preItem,
TunProtectSsPort = tunProtectSsPort,
ProxyRelaySsPort = proxyRelaySsPort,
};
return preContext;
}
/// <summary>
/// Remove servers with invalid test results (timeout)
/// Useful for cleaning up subscription lists

View file

@ -99,11 +99,9 @@ public static class CoreConfigHandler
};
var builderResult = await CoreConfigContextBuilder.Build(config, dummyNode);
var context = builderResult.Context;
var ids = selecteds.Where(serverTestItem => !serverTestItem.IndexId.IsNullOrEmpty())
.Select(serverTestItem => serverTestItem.IndexId);
var nodes = await AppManager.Instance.GetProfileItemsByIndexIds(ids);
foreach (var node in nodes)
foreach (var testItem in selecteds)
{
var node = testItem.Profile;
var (actNode, _) = await CoreConfigContextBuilder.ResolveNodeAsync(context, node, true);
if (node.IndexId == actNode.IndexId)
{

View file

@ -242,6 +242,30 @@ public sealed class AppManager
.ToListAsync();
}
public async Task<Dictionary<string, ProfileItem>> GetProfileItemsByIndexIdsAsMap(IEnumerable<string> indexIds)
{
var items = await GetProfileItemsByIndexIds(indexIds);
return items.ToDictionary(it => it.IndexId);
}
public async Task<List<ProfileItem>> GetProfileItemsOrderedByIndexIds(IEnumerable<string> indexIds)
{
var idList = indexIds.Where(id => !id.IsNullOrEmpty()).Distinct().ToList();
if (idList.Count == 0)
{
return [];
}
var items = await SQLiteHelper.Instance.TableAsync<ProfileItem>()
.Where(it => idList.Contains(it.IndexId))
.ToListAsync();
var itemMap = items.ToDictionary(it => it.IndexId);
return idList.Select(id => itemMap.GetValueOrDefault(id))
.Where(item => item != null)
.ToList();
}
public async Task<ProfileItem?> GetProfileItemViaRemarks(string? remarks)
{
if (remarks.IsNullOrEmpty())

View file

@ -57,27 +57,19 @@ public class CoreManager
}
}
public async Task LoadCore(CoreConfigContext? context)
/// <param name="mainContext">Resolved main context (with pre-socks ports already merged if applicable).</param>
/// <param name="preContext">Optional pre-socks context passed to <see cref="CoreStartPreService"/>.</param>
public async Task LoadCore(CoreConfigContext? mainContext, CoreConfigContext? preContext)
{
if (context == null)
if (mainContext == null)
{
await UpdateFunc(false, ResUI.CheckServerSettings);
return;
}
var contextMod = context;
var node = contextMod.Node;
var node = mainContext.Node;
var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName);
var preContext = ConfigHandler.GetPreSocksCoreConfigContext(contextMod);
if (preContext is not null)
{
contextMod = contextMod with
{
TunProtectSsPort = preContext.TunProtectSsPort,
ProxyRelaySsPort = preContext.ProxyRelaySsPort,
};
}
var result = await CoreConfigHandler.GenerateClientConfig(contextMod, fileName);
var result = await CoreConfigHandler.GenerateClientConfig(mainContext, fileName);
if (result.Success != true)
{
await UpdateFunc(true, result.Msg);
@ -96,7 +88,7 @@ public class CoreManager
await WindowsUtils.RemoveTunDevice();
}
await CoreStart(contextMod);
await CoreStart(mainContext);
await CoreStartPreService(preContext);
if (_processService != null)
{
@ -106,7 +98,7 @@ public class CoreManager
public async Task<ProcessService?> LoadCoreConfigSpeedtest(List<ServerTestItem> selecteds)
{
var coreType = selecteds.Any(t => Global.SingboxOnlyConfigType.Contains(t.ConfigType)) ? ECoreType.sing_box : ECoreType.Xray;
var coreType = selecteds.FirstOrDefault()?.CoreType == ECoreType.sing_box ? ECoreType.sing_box : ECoreType.Xray;
var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false));
var configPath = Utils.GetBinConfigPath(fileName);
var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, configPath, selecteds, coreType);

View file

@ -52,10 +52,10 @@ public class GroupProfileManager
return false;
}
foreach (var child in childIds)
var childItems = await AppManager.Instance.GetProfileItemsByIndexIds(childIds);
foreach (var childItem in childItems)
{
var childItem = await AppManager.Instance.GetProfileItem(child);
if (await HasCycle(child, childItem?.GetProtocolExtra(), visited, stack))
if (await HasCycle(childItem.IndexId, childItem?.GetProtocolExtra(), visited, stack))
{
return true;
}
@ -103,26 +103,7 @@ public class GroupProfileManager
return [];
}
var childProfiles = await AppManager.Instance.GetProfileItemsByIndexIds(childProfileIds);
if (childProfiles == null || childProfiles.Count == 0)
{
return [];
}
var profileMap = childProfiles
.Where(p => p != null && !p.IndexId.IsNullOrEmpty())
.GroupBy(p => p!.IndexId!)
.ToDictionary(g => g.Key, g => g.First());
var ordered = new List<ProfileItem>(childProfileIds.Count);
foreach (var id in childProfileIds)
{
if (id != null && profileMap.TryGetValue(id, out var item) && item != null)
{
ordered.Add(item);
}
}
var ordered = await AppManager.Instance.GetProfileItemsOrderedByIndexIds(childProfileIds);
return ordered;
}

View file

@ -38,4 +38,25 @@ public class NoticeManager
Enqueue(msg);
SendMessage(msg);
}
/// <summary>
/// Sends each error and warning in <paramref name="validatorResult"/> to the message panel
/// and enqueues a summary snack notification (capped at 10 messages).
/// Returns <c>true</c> when there were any messages so the caller can decide on early-return
/// based on <see cref="NodeValidatorResult.Success"/>.
/// </summary>
public bool NotifyValidatorResult(NodeValidatorResult validatorResult)
{
var msgs = new List<string>([.. validatorResult.Errors, .. validatorResult.Warnings]);
if (msgs.Count == 0)
{
return false;
}
foreach (var msg in msgs)
{
SendMessage(msg);
}
Enqueue(Utils.List2String(msgs.Take(10).ToList(), true));
return true;
}
}

View file

@ -10,4 +10,6 @@ public class ServerTestItem
public bool AllowTest { get; set; }
public bool NeedAutoFillRemarks { get; set; }
public int QueueNum { get; set; }
public required ProfileItem Profile { get; set; }
public ECoreType CoreType { get; set; }
}

View file

@ -220,12 +220,6 @@ public class DnsServer4Ray
public List<string>? domains { get; set; }
public bool? skipFallback { get; set; }
public List<string>? expectedIPs { get; set; }
public List<string>? unexpectedIPs { get; set; }
public string? clientIp { get; set; }
public string? queryStrategy { get; set; }
public int? timeoutMs { get; set; }
public bool? disableCache { get; set; }
public bool? finalQuery { get; set; }
public string? tag { get; set; }
}

View file

@ -834,6 +834,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 All configurations 的本地化字符串。
/// </summary>
public static string menuAllServers {
get {
return ResourceManager.GetString("menuAllServers", resourceCulture);
}
}
/// <summary>
/// 查找类似 Backup and Restore 的本地化字符串。
/// </summary>
@ -1006,74 +1015,20 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Generate Policy Group from Multiple Profiles 的本地化字符串。
/// 查找类似 Generate Policy Group 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServer {
public static string menuGenGroupServer {
get {
return ResourceManager.GetString("menuGenGroupMultipleServer", resourceCulture);
return ResourceManager.GetString("menuGenGroupServer", resourceCulture);
}
}
/// <summary>
/// 查找类似 Fallback by sing-box 的本地化字符串。
/// 查找类似 Group by Region 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerSingBoxFallback {
public static string menuGenRegionGroup {
get {
return ResourceManager.GetString("menuGenGroupMultipleServerSingBoxFallback", resourceCulture);
}
}
/// <summary>
/// 查找类似 LeastPing by sing-box 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerSingBoxLeastPing {
get {
return ResourceManager.GetString("menuGenGroupMultipleServerSingBoxLeastPing", resourceCulture);
}
}
/// <summary>
/// 查找类似 Fallback by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayFallback {
get {
return ResourceManager.GetString("menuGenGroupMultipleServerXrayFallback", resourceCulture);
}
}
/// <summary>
/// 查找类似 LeastLoad by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayLeastLoad {
get {
return ResourceManager.GetString("menuGenGroupMultipleServerXrayLeastLoad", resourceCulture);
}
}
/// <summary>
/// 查找类似 LeastPing by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayLeastPing {
get {
return ResourceManager.GetString("menuGenGroupMultipleServerXrayLeastPing", resourceCulture);
}
}
/// <summary>
/// 查找类似 Random by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayRandom {
get {
return ResourceManager.GetString("menuGenGroupMultipleServerXrayRandom", resourceCulture);
}
}
/// <summary>
/// 查找类似 RoundRobin by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayRoundRobin {
get {
return ResourceManager.GetString("menuGenGroupMultipleServerXrayRoundRobin", resourceCulture);
return ResourceManager.GetString("menuGenRegionGroup", resourceCulture);
}
}

View file

@ -1371,24 +1371,6 @@
<data name="TbPorts7Tips" xml:space="preserve">
<value>مخفی و پورت می شود، با کاما (،) جدا می شود</value>
</data>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>Generate Policy Group from Multiple Profiles</value>
</data>
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<value>چند سرور تصادفی توسط Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<value>چند سرور RoundRobin توسط Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<value>چند سرور LeastPing توسط Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<value>چند سرور LeastLoad توسط Xray</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>LeastPing چند سرور توسط sing-box</value>
</data>
<data name="menuExportConfig" xml:space="preserve">
<value>صادر کردن سرور</value>
</data>
@ -1533,12 +1515,6 @@
<data name="TbFallback" xml:space="preserve">
<value>Fallback</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by sing-box</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by Xray</value>
</data>
<data name="MsgCoreNotSupportNetwork" xml:space="preserve">
<value>Core '{0}' does not support network type '{1}'</value>
</data>
@ -1686,4 +1662,13 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
<value>Subscription next proxy {0} not found. Skipping.</value>
</data>
<data name="menuGenGroupServer" xml:space="preserve">
<value>Generate Policy Group</value>
</data>
<data name="menuAllServers" xml:space="preserve">
<value>All configurations</value>
</data>
<data name="menuGenRegionGroup" xml:space="preserve">
<value>Group by Region</value>
</data>
</root>

View file

@ -1368,24 +1368,6 @@
<data name="TbPorts7Tips" xml:space="preserve">
<value>Écrase le port ; pour plusieurs groupes, séparer par virgules (,)</value>
</data>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>Générer un groupe de stratégie depuis plusieurs profils</value>
</data>
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<value>Xray aléatoire (multi-sélection)</value>
</data>
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<value>Xray équilibrage (tourniquet) multi-sélection</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<value>Xray latence minimale (multi-sélection)</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<value>Xray le plus stable (multi-sélection)</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>sing-box latence minimale (multi-sélection)</value>
</data>
<data name="menuExportConfig" xml:space="preserve">
<value>Exporter</value>
</data>
@ -1530,12 +1512,6 @@
<data name="TbFallback" xml:space="preserve">
<value>Basculement (failover)</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>sing-box basculement (multi-sélection)</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>Xray basculement (multi-sélection)</value>
</data>
<data name="MsgCoreNotSupportNetwork" xml:space="preserve">
<value>Le cœur « {0} » ne prend pas en charge le type de réseau « {1} »</value>
</data>
@ -1683,4 +1659,13 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
<value>Subscription next proxy {0} not found. Skipping.</value>
</data>
<data name="menuGenGroupServer" xml:space="preserve">
<value>Generate Policy Group</value>
</data>
<data name="menuAllServers" xml:space="preserve">
<value>All configurations</value>
</data>
<data name="menuGenRegionGroup" xml:space="preserve">
<value>Group by Region</value>
</data>
</root>

View file

@ -1371,24 +1371,6 @@
<data name="TbPorts7Tips" xml:space="preserve">
<value>A portot lefedi, vesszővel (,) elválasztva</value>
</data>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>Generate Policy Group from Multiple Profiles</value>
</data>
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<value>Több konfiguráció véletlenszerűen Xray szerint</value>
</data>
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<value>Több konfiguráció RoundRobin Xray szerint</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<value>Több konfiguráció legkisebb pinggel Xray szerint</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<value>Több konfiguráció legkisebb terheléssel Xray szerint</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>Több konfiguráció legkisebb pinggel sing-box szerint</value>
</data>
<data name="menuExportConfig" xml:space="preserve">
<value>Konfiguráció exportálása</value>
</data>
@ -1533,12 +1515,6 @@
<data name="TbFallback" xml:space="preserve">
<value>Fallback</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by sing-box</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by Xray</value>
</data>
<data name="MsgCoreNotSupportNetwork" xml:space="preserve">
<value>Core '{0}' does not support network type '{1}'</value>
</data>
@ -1686,4 +1662,13 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
<value>Subscription next proxy {0} not found. Skipping.</value>
</data>
<data name="menuGenGroupServer" xml:space="preserve">
<value>Generate Policy Group</value>
</data>
<data name="menuAllServers" xml:space="preserve">
<value>All configurations</value>
</data>
<data name="menuGenRegionGroup" xml:space="preserve">
<value>Group by Region</value>
</data>
</root>

View file

@ -1371,24 +1371,6 @@
<data name="TbPorts7Tips" xml:space="preserve">
<value>Will cover the port, separate with commas (,)</value>
</data>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>Generate Policy Group from Multiple Profiles</value>
</data>
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<value>Random by Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<value>RoundRobin by Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<value>LeastPing by Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<value>LeastLoad by Xray</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>LeastPing by sing-box</value>
</data>
<data name="menuExportConfig" xml:space="preserve">
<value>Export</value>
</data>
@ -1533,12 +1515,6 @@
<data name="TbFallback" xml:space="preserve">
<value>Fallback</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>Fallback by sing-box</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>Fallback by Xray</value>
</data>
<data name="MsgCoreNotSupportNetwork" xml:space="preserve">
<value>Core '{0}' does not support network type '{1}'</value>
</data>
@ -1686,4 +1662,13 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
<value>Subscription next proxy {0} not found. Skipping.</value>
</data>
<data name="menuGenGroupServer" xml:space="preserve">
<value>Generate Policy Group</value>
</data>
<data name="menuAllServers" xml:space="preserve">
<value>All configurations</value>
</data>
<data name="menuGenRegionGroup" xml:space="preserve">
<value>Group by Region</value>
</data>
</root>

View file

@ -1371,24 +1371,6 @@
<data name="TbPorts7Tips" xml:space="preserve">
<value>Заменит указанный порт, перечисляйте через запятую (,)</value>
</data>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>Generate Policy Group from Multiple Profiles</value>
</data>
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<value>Случайный (Xray)</value>
</data>
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<value>Круговой (Xray)</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<value>Минимальное RTT (минимальное время туда-обратно) (Xray)</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<value>Минимальная нагрузка (Xray)</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>Минимальное RTT (минимальное время туда-обратно) (sing-box)</value>
</data>
<data name="menuExportConfig" xml:space="preserve">
<value>Экспортировать конфигурацию</value>
</data>
@ -1533,12 +1515,6 @@
<data name="TbFallback" xml:space="preserve">
<value>Fallback</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by sing-box</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by Xray</value>
</data>
<data name="MsgCoreNotSupportNetwork" xml:space="preserve">
<value>Core '{0}' does not support network type '{1}'</value>
</data>
@ -1686,4 +1662,13 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
<value>Subscription next proxy {0} not found. Skipping.</value>
</data>
<data name="menuGenGroupServer" xml:space="preserve">
<value>Generate Policy Group</value>
</data>
<data name="menuAllServers" xml:space="preserve">
<value>All configurations</value>
</data>
<data name="menuGenRegionGroup" xml:space="preserve">
<value>Group by Region</value>
</data>
</root>

View file

@ -1368,24 +1368,6 @@
<data name="TbPorts7Tips" xml:space="preserve">
<value>会覆盖端口,多组时用逗号 (,) 隔开</value>
</data>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>多选生成策略组</value>
</data>
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<value>多选随机 Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<value>多选负载均衡 Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<value>多选最低延迟 Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<value>多选最稳定 Xray</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>多选最低延迟 sing-box</value>
</data>
<data name="menuExportConfig" xml:space="preserve">
<value>导出</value>
</data>
@ -1530,12 +1512,6 @@
<data name="TbFallback" xml:space="preserve">
<value>故障转移</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>多选故障转移 sing-box</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>多选故障转移 Xray</value>
</data>
<data name="MsgCoreNotSupportNetwork" xml:space="preserve">
<value>核心 '{0}' 不支持网络类型 '{1}'</value>
</data>
@ -1683,4 +1659,13 @@
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
<value>订阅后置节点 {0} 未找到,已跳过。</value>
</data>
<data name="menuGenGroupServer" xml:space="preserve">
<value>一键生成策略组</value>
</data>
<data name="menuAllServers" xml:space="preserve">
<value>全部配置项</value>
</data>
<data name="menuGenRegionGroup" xml:space="preserve">
<value>按地区分组</value>
</data>
</root>

View file

@ -127,7 +127,7 @@
<value>設定格式不正確</value>
</data>
<data name="CustomServerTips" xml:space="preserve">
<value>注意,自訂設定完全依賴您自己的設定,不能使用所有設定功能。如需使用系統代理請手動修改偵聽埠。</value>
<value>注意,自訂設定完全依賴您自行輸入的內容,部分功能可能無法使用。如需啟用系統代理,請手動調整監聽埠。</value>
</data>
<data name="Downloading" xml:space="preserve">
<value>下載開始...</value>
@ -139,7 +139,7 @@
<value>生成預設設定檔失敗</value>
</data>
<data name="FailedGetDefaultConfiguration" xml:space="preserve">
<value>取預設設定失敗</value>
<value>取預設設定失敗</value>
</data>
<data name="FailedImportedCustomServer" xml:space="preserve">
<value>匯入自訂設定失敗</value>
@ -148,7 +148,7 @@
<value>讀取設定失敗</value>
</data>
<data name="FillCorrectServerPort" xml:space="preserve">
<value>請填寫正確格式的埠</value>
<value>請填寫有效的埠號</value>
</data>
<data name="FillLocalListeningPort" xml:space="preserve">
<value>請填寫本機偵聽埠</value>
@ -247,7 +247,7 @@
<value>非 VMess 或 SS 協定</value>
</data>
<data name="NotFoundCore" xml:space="preserve">
<value>在資料夾 ({0}) 下未找到 Core 檔案 (檔案名: {1}),請下載後放入資料夾,下載網址: {2}</value>
<value>在資料夾 ({0}) 中找不到 Core 檔案(檔名:{1})。請下載後放入該資料夾。下載網址:{2}</value>
</data>
<data name="NoValidQRcodeFound" xml:space="preserve">
<value>掃描完成,未發現有效二維碼</value>
@ -304,7 +304,7 @@
<value>是否確定移除規則?</value>
</data>
<data name="RoutingRuleDetailRequiredTips" xml:space="preserve">
<value>{0}必填其中一項.</value>
<value>{0}至少需填寫其中一項。</value>
</data>
<data name="LvRemarks" xml:space="preserve">
<value>別名</value>
@ -385,7 +385,7 @@
<value>所有</value>
</data>
<data name="FillServerAddressCustom" xml:space="preserve">
<value>請瀏覽匯入設定</value>
<value>請選擇要匯入的設定檔</value>
</data>
<data name="Speedtesting" xml:space="preserve">
<value>測試中...</value>
@ -472,7 +472,7 @@
<value>語言 (需重啟)</value>
</data>
<data name="menuAddServerViaClipboard" xml:space="preserve">
<value>從剪貼簿入分享連結</value>
<value>從剪貼簿入分享連結</value>
</data>
<data name="menuAddServerViaScan" xml:space="preserve">
<value>掃描螢幕上的二維碼</value>
@ -616,10 +616,10 @@
<value>SNI</value>
</data>
<data name="TbStreamSecurity" xml:space="preserve">
<value>傳輸層安全 (TLS)</value>
<value>傳輸層安全 (TLS)</value>
</data>
<data name="TipNetwork" xml:space="preserve">
<value>*預設 TCP選錯會無法連</value>
<value>*預設 TCP選錯會無法連</value>
</data>
<data name="TbCoreType" xml:space="preserve">
<value>Core 類型</value>
@ -652,7 +652,7 @@
<value>SOCKS 埠</value>
</data>
<data name="TipPreSocksPort" xml:space="preserve">
<value>*自訂設定的 Socks 埠值,可不設定;當設定此值後,將使用 Xray/sing-box (Tun) 額外啟動一個前置 Socks 服務,提供分流和速度顯示等功能</value>
<value>*自訂設定的 Socks 埠值,可留空;當設定此值後,將使用 Xray/sing-box (Tun) 額外啟動一個前置 Socks 服務,提供分流和速度顯示等功能</value>
</data>
<data name="TbBrowse" xml:space="preserve">
<value>瀏覽</value>
@ -1309,7 +1309,7 @@
<value>安裝字體到系統中,選擇或填入字體名稱,重新啟動後生效</value>
</data>
<data name="menuExitTips" xml:space="preserve">
<value>是否確定退出?</value>
<value>確定退出</value>
</data>
<data name="LvMemo" xml:space="preserve">
<value>備註備忘</value>
@ -1368,24 +1368,6 @@
<data name="TbPorts7Tips" xml:space="preserve">
<value>會覆蓋埠,多組時用逗號 (,) 隔開</value>
</data>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>多選生成策略組</value>
</data>
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<value>多選隨機 Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<value>多選負載平衡 Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<value>多選最低延遲 Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<value>多選最穩定 Xray</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>多選最低延遲 sing-box</value>
</data>
<data name="menuExportConfig" xml:space="preserve">
<value>匯出</value>
</data>
@ -1513,13 +1495,13 @@
<value>策略組類型</value>
</data>
<data name="menuAddPolicyGroupServer" xml:space="preserve">
<value>添加策略組</value>
<value>新增策略組</value>
</data>
<data name="menuAddProxyChainServer" xml:space="preserve">
<value>添加鏈式代理</value>
<value>新增鏈式代理</value>
</data>
<data name="menuAddChildServer" xml:space="preserve">
<value>添加子配置</value>
<value>新增子配置</value>
</data>
<data name="menuRemoveChildServer" xml:space="preserve">
<value>刪除子配置</value>
@ -1530,12 +1512,6 @@
<data name="TbFallback" xml:space="preserve">
<value>容錯移轉</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>多選容錯移轉 sing-box</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>多選容錯移轉 Xray</value>
</data>
<data name="MsgCoreNotSupportNetwork" xml:space="preserve">
<value>核心 '{0}' 不支援網路類型 '{1}'</value>
</data>
@ -1615,72 +1591,81 @@
<value>EchForceQuery</value>
</data>
<data name="TbFullCertTips" xml:space="preserve">
<value>Full certificate (chain), PEM format</value>
<value>完整憑證PEM 格式</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Certificate fingerprint (SHA-256)</value>
<value>憑證指紋SHA-256</value>
</data>
<data name="TbServeStale" xml:space="preserve">
<value>Serve Stale</value>
<value>提供過期快取(Serve Stale</value>
</data>
<data name="TbParallelQuery" xml:space="preserve">
<value>Parallel Query</value>
<value>并行查詢</value>
</data>
<data name="TbDomesticDNSTips" xml:space="preserve">
<value>By default, invoked only during routing for resolution</value>
<value>預設僅在路由期間進行解析時調用</value>
</data>
<data name="TbRemoteDNSTips" xml:space="preserve">
<value>By default, invoked only during routing for resolution; ensure the remote server can reach this DNS</value>
<value>預設僅在路由期間進行解析時調用;請確保遠端伺服器能連線至此 DNS</value>
</data>
<data name="TbDirectResolveStrategyTips" xml:space="preserve">
<value>If unset or "AsIs", DNS resolution uses the system DNS; otherwise, the internal DNS module is used.</value>
<value>若未設定或為 "AsIs",使用系統 DNS 解析;否則將使用內建 DNS 模組。</value>
</data>
<data name="TbRemoteResolveStrategyTips" xml:space="preserve">
<value>If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used.</value>
<value>若未設定或為 "AsIs",由遠端伺服器的 DNS 解析;否則將使用內建 DNS 模組。</value>
</data>
<data name="TbHopInt7" xml:space="preserve">
<value>Port hopping interval</value>
<value>連接埠跳轉間隔</value>
</data>
<data name="menuServerListPreview" xml:space="preserve">
<value>Configuration item preview</value>
<value>子配置項預覽</value>
</data>
<data name="TbFinalmask" xml:space="preserve">
<value>Finalmask</value>
</data>
<data name="MsgRoutingRuleOutboundNodeWarning" xml:space="preserve">
<value>Routing rule {0} outbound node {1} warning: {2}</value>
<value>路由規則 {0} 的出站節點 {1} 發出警告:{2}</value>
</data>
<data name="MsgRoutingRuleOutboundNodeError" xml:space="preserve">
<value>Routing rule {0} outbound node {1} error: {2}. Fallback to proxy node only.</value>
<value>路由規則 {0} 的出站節點 {1} 發生錯誤:{2}。已回退為僅使用代理節點。</value>
</data>
<data name="MsgGroupCycleDependency" xml:space="preserve">
<value>Group {0} has a cycle dependency on child node {1}. Skipping this node.</value>
<value>節點組 {0} 與子節點 {1} 存在循環依賴。已跳過此節點。</value>
</data>
<data name="MsgGroupChildNodeWarning" xml:space="preserve">
<value>Group {0} child node {1} warning: {2}</value>
<value>節點組 {0} 的子節點 {1} 發出警告:{2}</value>
</data>
<data name="MsgGroupChildNodeError" xml:space="preserve">
<value>Group {0} child node {1} error: {2}. Skipping this node.</value>
<value>節點組 {0} 的子節點 {1} 發生錯誤:{2}。已跳過此節點。</value>
</data>
<data name="MsgGroupChildGroupNodeWarning" xml:space="preserve">
<value>Group {0} child group node {1} warning: {2}</value>
<value>節點組 {0} 的子節點組 {1} 發出警告:{2}</value>
</data>
<data name="MsgGroupChildGroupNodeError" xml:space="preserve">
<value>Group {0} child group node {1} error: {2}. Skipping this node.</value>
<value>節點組 {0} 的子節點組 {1} 發生錯誤:{2}。已跳過此節點。</value>
</data>
<data name="MsgGroupNoValidChildNode" xml:space="preserve">
<value>Group {0} has no valid child node.</value>
<value>節點組 {0} 沒有可用的有效子節點。</value>
</data>
<data name="MsgRoutingRuleEmptyOutboundTag" xml:space="preserve">
<value>Routing rule {0} has an empty outbound tag. Fallback to proxy node only.</value>
<value>路由規則 {0} 的出站標籤為空。已回退為僅使用代理節點。</value>
</data>
<data name="MsgRoutingRuleOutboundNodeNotFound" xml:space="preserve">
<value>Routing rule {0} outbound node {1} not found. Fallback to proxy node only.</value>
<value>找不到路由規則 {0} 的出站節點 {1}。已回退為僅使用代理節點。</value>
</data>
<data name="MsgSubscriptionPrevProfileNotFound" xml:space="preserve">
<value>Subscription previous proxy {0} not found. Skipping.</value>
<value>找不到訂閱的前一個代理 {0}。已跳過。</value>
</data>
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
<value>Subscription next proxy {0} not found. Skipping.</value>
<value>找不到訂閱的下一個代理 {0}。已跳過。</value>
</data>
<data name="menuGenGroupServer" xml:space="preserve">
<value>生成策略組</value>
</data>
<data name="menuAllServers" xml:space="preserve">
<value>所有配置項</value>
</data>
<data name="menuGenRegionGroup" xml:space="preserve">
<value>按區域分組</value>
</data>
</root>

View file

@ -149,11 +149,11 @@ public partial class CoreConfigSingboxService(CoreConfigContext context)
foreach (var it in selecteds)
{
if (!Global.SingboxSupportConfigType.Contains(it.ConfigType))
if (!(Global.SingboxSupportConfigType.Contains(it.ConfigType) || it.ConfigType.IsGroupType()))
{
continue;
}
if (it.Port <= 0)
if (!it.ConfigType.IsComplexType() && it.Port <= 0)
{
continue;
}

View file

@ -148,15 +148,20 @@ public partial class CoreConfigSingboxService
_coreConfig.dns ??= new Dns4Sbox();
_coreConfig.dns.rules ??= [];
_coreConfig.dns.rules.AddRange(new[]
_coreConfig.dns.rules.Add(new() { ip_accept_any = true, server = Global.SingboxHostsDNSTag });
if (context.ProtectDomainList.Count > 0)
{
new Rule4Sbox { ip_accept_any = true, server = Global.SingboxHostsDNSTag },
new Rule4Sbox
_coreConfig.dns.rules.Add(new()
{
server = Global.SingboxDirectDNSTag,
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom),
domain = context.ProtectDomainList.ToList(),
},
});
}
_coreConfig.dns.rules.AddRange(new[]
{
new Rule4Sbox
{
server = Global.SingboxRemoteDNSTag,
@ -428,7 +433,11 @@ public partial class CoreConfigSingboxService
localDnsServer.tag = tag;
dns4Sbox.servers.Add(localDnsServer);
dns4Sbox.rules.Insert(0, BuildProtectDomainRule());
var protectDomainRule = BuildProtectDomainRule();
if (protectDomainRule != null)
{
dns4Sbox.rules.Insert(0, protectDomainRule);
}
_coreConfig.dns = dns4Sbox;
}
@ -450,8 +459,12 @@ public partial class CoreConfigSingboxService
_coreConfig.dns?.servers?.Add(localDnsServer);
}
private Rule4Sbox BuildProtectDomainRule()
private Rule4Sbox? BuildProtectDomainRule()
{
if (context.ProtectDomainList.Count == 0)
{
return null;
}
return new()
{
server = Global.SingboxLocalDNSTag,

View file

@ -112,11 +112,11 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
foreach (var it in selecteds)
{
if (!Global.XraySupportConfigType.Contains(it.ConfigType))
if (!(Global.XraySupportConfigType.Contains(it.ConfigType) || it.ConfigType.IsGroupType()))
{
continue;
}
if (it.Port <= 0)
if (!it.ConfigType.IsComplexType() && it.Port <= 0)
{
continue;
}
@ -180,13 +180,13 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
//rule
RulesItem4Ray rule = new()
{
inboundTag = new List<string> { inbound.tag },
inboundTag = [inbound.tag],
outboundTag = tag,
type = "field"
};
if (isBalancer)
{
rule.balancerTag = tag;
rule.balancerTag = tag + Global.BalancerTagSuffix;
rule.outboundTag = null;
}
_coreConfig.routing.rules.Add(rule);
@ -301,6 +301,7 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
GenLog();
_coreConfig.outbounds.Clear();
GenOutbounds();
GenStatistic();
var protectNode = new ProfileItem()
{
@ -326,18 +327,17 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
Node = protectNode,
}).BuildProxyOutbound("tun-project-ss"));
_coreConfig.routing.rules ??= [];
var hasBalancer = _coreConfig.routing.balancers is { Count: > 0 };
_coreConfig.routing.rules =
[
new()
{
inboundTag = new List<string> { "proxy-relay-ss" },
outboundTag = hasBalancer ? null : Global.ProxyTag,
balancerTag = hasBalancer ? Global.ProxyTag + Global.BalancerTagSuffix: null,
type = "field"
}
];
_coreConfig.inbounds.Clear();
_coreConfig.routing.rules.Add(new()
{
inboundTag = ["proxy-relay-ss"],
outboundTag = hasBalancer ? null : Global.ProxyTag,
balancerTag = hasBalancer ? Global.ProxyTag + Global.BalancerTagSuffix : null,
type = "field"
});
//_coreConfig.inbounds.Clear();
var configNode = JsonUtils.ParseJson(JsonUtils.Serialize(_coreConfig))!;
configNode["inbounds"]!.AsArray().Add(new

View file

@ -71,6 +71,25 @@ public partial class CoreConfigV2rayService
dnsItem.enableParallelQuery = simpleDnsItem?.ParallelQuery is true ? true : null;
// DNS routing
var directDnsTags = dnsItem.servers
.Select(server =>
{
var tagNode = (server as JsonObject)?["tag"];
return tagNode is JsonValue value && value.TryGetValue<string>(out var tag) ? tag : null;
})
.Where(tag => tag is not null && tag.StartsWith(Global.DirectDnsTag, StringComparison.Ordinal))
.Select(tag => tag!)
.ToList();
if (directDnsTags.Count > 0)
{
_coreConfig.routing.rules.Add(new()
{
type = "field",
inboundTag = directDnsTags,
outboundTag = Global.DirectTag,
});
}
var finalRule = BuildFinalRule();
dnsItem.tag = Global.DnsTag;
_coreConfig.routing.rules.Add(new()
@ -78,7 +97,7 @@ public partial class CoreConfigV2rayService
type = "field",
inboundTag = [Global.DnsTag],
outboundTag = finalRule.outboundTag,
balancerTag = finalRule.balancerTag
balancerTag = finalRule.balancerTag,
});
_coreConfig.dns = dnsItem;
@ -212,11 +231,13 @@ public partial class CoreConfigV2rayService
dnsItem.servers ??= [];
var directDnsTagIndex = 1;
AddDnsServers(remoteDNSAddress, proxyDomainList);
AddDnsServers(directDNSAddress, directDomainList);
AddDnsServers(directDNSAddress, directDomainList, true);
AddDnsServers(remoteDNSAddress, proxyGeositeList);
AddDnsServers(directDNSAddress, directGeositeList);
AddDnsServers(directDNSAddress, expectedDomainList, expectedIPs);
AddDnsServers(directDNSAddress, directGeositeList, true);
AddDnsServers(directDNSAddress, expectedDomainList, true, expectedIPs);
if (dnsServerDomains.Count > 0)
{
AddDnsServers(bootstrapDNSAddress, dnsServerDomains);
@ -234,8 +255,21 @@ public partial class CoreConfigV2rayService
useDirectDns = noDomain && noProcess && isAnyIp && isAnyPort && isAnyNetwork;
}
var defaultDnsServers = useDirectDns ? directDNSAddress : remoteDNSAddress;
dnsItem.servers.AddRange(defaultDnsServers);
if (!useDirectDns)
{
dnsItem.servers.AddRange(remoteDNSAddress);
}
else
{
foreach (var dns in directDNSAddress)
{
var dnsServer = CreateDnsServer(dns, []);
dnsServer.tag = $"{Global.DirectDnsTag}-{directDnsTagIndex++}";
dnsServer.skipFallback = false;
dnsItem.servers.Add(JsonUtils.SerializeToNode(dnsServer,
new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }));
}
}
return;
static List<string> ParseDnsAddresses(string? dnsInput, string defaultAddress)
@ -249,7 +283,7 @@ public partial class CoreConfigV2rayService
return addresses.Count > 0 ? addresses : new List<string> { defaultAddress };
}
static object? CreateDnsServer(string dnsAddress, List<string> domains, List<string>? expectedIPs = null)
static DnsServer4Ray CreateDnsServer(string dnsAddress, List<string> domains, List<string>? expectedIPs = null)
{
var (domain, scheme, port, path) = Utils.ParseUrl(dnsAddress);
var domainFinal = dnsAddress;
@ -272,13 +306,10 @@ public partial class CoreConfigV2rayService
domains = domains.Count > 0 ? domains : null,
expectedIPs = expectedIPs?.Count > 0 ? expectedIPs : null
};
return JsonUtils.SerializeToNode(dnsServer, new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
});
return dnsServer;
}
void AddDnsServers(List<string> dnsAddresses, List<string> domains, List<string>? expectedIPs = null)
void AddDnsServers(List<string> dnsAddresses, List<string> domains, bool isDirectDns = false, List<string>? expectedIPs = null)
{
if (domains.Count <= 0)
{
@ -286,7 +317,14 @@ public partial class CoreConfigV2rayService
}
foreach (var dnsAddress in dnsAddresses)
{
dnsItem.servers.Add(CreateDnsServer(dnsAddress, domains, expectedIPs));
var dnsServer = CreateDnsServer(dnsAddress, domains, expectedIPs);
if (isDirectDns)
{
dnsServer.tag = $"{Global.DirectDnsTag}-{directDnsTagIndex++}";
}
var dnsServerNode = JsonUtils.SerializeToNode(dnsServer,
new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull });
dnsItem.servers.Add(dnsServerNode);
}
}
}
@ -399,7 +437,7 @@ public partial class CoreConfigV2rayService
FillDnsDomainsCustom(obj);
_coreConfig.dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(obj));
_coreConfig.dns = obj;
}
catch (Exception ex)
{

View file

@ -67,19 +67,27 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
private async Task<List<ServerTestItem>> GetClearItem(ESpeedActionType actionType, List<ProfileItem> selecteds)
{
var lstSelected = new List<ServerTestItem>();
foreach (var it in selecteds)
var lstSelected = new List<ServerTestItem>(selecteds.Count);
var ids = selecteds.Where(it => !it.IndexId.IsNullOrEmpty()
&& it.ConfigType != EConfigType.Custom
&& (it.ConfigType.IsComplexType() || it.Port > 0))
.Select(it => it.IndexId)
.ToList();
var profileMap = await AppManager.Instance.GetProfileItemsByIndexIdsAsMap(ids);
for (var i = 0; i < selecteds.Count; i++)
{
if (it.ConfigType.IsComplexType())
var it = selecteds[i];
if (it.ConfigType == EConfigType.Custom)
{
continue;
}
if (it.Port <= 0)
if (!it.ConfigType.IsComplexType() && it.Port <= 0)
{
continue;
}
var profile = profileMap.GetValueOrDefault(it.IndexId, it);
lstSelected.Add(new ServerTestItem()
{
IndexId = it.IndexId,
@ -88,6 +96,9 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
ConfigType = it.ConfigType,
NeedAutoFillRemarks = it.Remarks.IsNullOrEmpty(),
QueueNum = selecteds.IndexOf(it)
QueueNum = i,
Profile = profile,
CoreType = AppManager.Instance.GetCoreType(profile, it.ConfigType),
});
}
@ -397,8 +408,8 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
private List<List<ServerTestItem>> GetTestBatchItem(List<ServerTestItem> lstSelected, int pageSize)
{
List<List<ServerTestItem>> lstTest = new();
var lst1 = lstSelected.Where(t => Global.XraySupportConfigType.Contains(t.ConfigType)).ToList();
var lst2 = lstSelected.Where(t => Global.SingboxOnlyConfigType.Contains(t.ConfigType)).ToList();
var lst1 = lstSelected.Where(t => t.CoreType == ECoreType.Xray).ToList();
var lst2 = lstSelected.Where(t => t.CoreType == ECoreType.sing_box).ToList();
for (var num = 0; num < (int)Math.Ceiling(lst1.Count * 1.0 / pageSize); num++)
{

View file

@ -99,15 +99,8 @@ public class AddGroupServerViewModel : MyReactiveObject
Filter = protocolExtra?.Filter;
var childIndexIds = Utils.String2List(protocolExtra?.ChildItems) ?? [];
foreach (var item in childIndexIds)
{
var child = await AppManager.Instance.GetProfileItem(item);
if (child == null)
{
continue;
}
ChildItemsObs.Add(child);
}
var childItemList = await AppManager.Instance.GetProfileItemsOrderedByIndexIds(childIndexIds);
ChildItemsObs.AddRange(childItemList);
}
public async Task ChildRemoveAsync()

View file

@ -546,24 +546,15 @@ public class MainWindowViewModel : MyReactiveObject
NoticeManager.Instance.Enqueue(ResUI.CheckServerSettings);
return;
}
var (context, validatorResult) = await CoreConfigContextBuilder.Build(_config, profileItem);
var msgs = new List<string>([..validatorResult.Errors, ..validatorResult.Warnings]);
if (msgs.Count > 0)
var allResult = await CoreConfigContextBuilder.BuildAll(_config, profileItem);
if (NoticeManager.Instance.NotifyValidatorResult(allResult.CombinedValidatorResult) && !allResult.Success)
{
foreach (var msg in msgs)
{
NoticeManager.Instance.SendMessage(msg);
}
NoticeManager.Instance.Enqueue(Utils.List2String(msgs.Take(10).ToList(), true));
if (!validatorResult.Success)
{
return;
}
return;
}
await Task.Run(async () =>
{
await LoadCore(context);
await LoadCore(allResult.ResolvedMainContext, allResult.PreSocksResult?.Context);
await SysProxyHandler.UpdateSysProxy(_config, false);
await Task.Delay(1000);
});
@ -604,9 +595,9 @@ public class MainWindowViewModel : MyReactiveObject
RxApp.MainThreadScheduler.Schedule(() => BlReloadEnabled = enabled);
}
private async Task LoadCore(CoreConfigContext? context)
private async Task LoadCore(CoreConfigContext? mainContext, CoreConfigContext? preContext)
{
await CoreManager.Instance.LoadCore(context);
await CoreManager.Instance.LoadCore(mainContext, preContext);
}
#endregion core job

View file

@ -255,19 +255,7 @@ public class ProfilesSelectViewModel : MyReactiveObject
{
return null;
}
var lst = new List<ProfileItem>();
foreach (var sp in SelectedProfiles)
{
if (string.IsNullOrEmpty(sp?.IndexId))
{
continue;
}
var item = await AppManager.Instance.GetProfileItem(sp.IndexId);
if (item != null)
{
lst.Add(item);
}
}
var lst = await AppManager.Instance.GetProfileItemsOrderedByIndexIds(SelectedProfiles.Select(sp => sp?.IndexId));
if (lst.Count == 0)
{
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer);

View file

@ -8,6 +8,7 @@ public class ProfilesViewModel : MyReactiveObject
private string _serverFilter = string.Empty;
private Dictionary<string, bool> _dicHeaderSort = new();
private SpeedtestService? _speedtestService;
private string? _pendingSelectIndexId;
#endregion private prop
@ -43,13 +44,8 @@ public class ProfilesViewModel : MyReactiveObject
public ReactiveCommand<Unit, Unit> CopyServerCmd { get; }
public ReactiveCommand<Unit, Unit> SetDefaultServerCmd { get; }
public ReactiveCommand<Unit, Unit> ShareServerCmd { get; }
public ReactiveCommand<Unit, Unit> GenGroupMultipleServerXrayRandomCmd { get; }
public ReactiveCommand<Unit, Unit> GenGroupMultipleServerXrayRoundRobinCmd { get; }
public ReactiveCommand<Unit, Unit> GenGroupMultipleServerXrayLeastPingCmd { get; }
public ReactiveCommand<Unit, Unit> GenGroupMultipleServerXrayLeastLoadCmd { get; }
public ReactiveCommand<Unit, Unit> GenGroupMultipleServerXrayFallbackCmd { get; }
public ReactiveCommand<Unit, Unit> GenGroupMultipleServerSingBoxLeastPingCmd { get; }
public ReactiveCommand<Unit, Unit> GenGroupMultipleServerSingBoxFallbackCmd { get; }
public ReactiveCommand<Unit, Unit> GenGroupAllServerCmd { get; }
public ReactiveCommand<Unit, Unit> GenGroupRegionServerCmd { get; }
//servers move
public ReactiveCommand<Unit, Unit> MoveTopCmd { get; }
@ -134,33 +130,13 @@ public class ProfilesViewModel : MyReactiveObject
{
await ShareServerAsync();
}, canEditRemove);
GenGroupMultipleServerXrayRandomCmd = ReactiveCommand.CreateFromTask(async () =>
GenGroupAllServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.Random);
await GenGroupAllServer();
}, canEditRemove);
GenGroupMultipleServerXrayRoundRobinCmd = ReactiveCommand.CreateFromTask(async () =>
GenGroupRegionServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.RoundRobin);
}, canEditRemove);
GenGroupMultipleServerXrayLeastPingCmd = ReactiveCommand.CreateFromTask(async () =>
{
await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.LeastPing);
}, canEditRemove);
GenGroupMultipleServerXrayLeastLoadCmd = ReactiveCommand.CreateFromTask(async () =>
{
await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.LeastLoad);
}, canEditRemove);
GenGroupMultipleServerXrayFallbackCmd = ReactiveCommand.CreateFromTask(async () =>
{
await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.Fallback);
}, canEditRemove);
GenGroupMultipleServerSingBoxLeastPingCmd = ReactiveCommand.CreateFromTask(async () =>
{
await GenGroupMultipleServer(ECoreType.sing_box, EMultipleLoad.LeastPing);
}, canEditRemove);
GenGroupMultipleServerSingBoxFallbackCmd = ReactiveCommand.CreateFromTask(async () =>
{
await GenGroupMultipleServer(ECoreType.sing_box, EMultipleLoad.Fallback);
await GenGroupRegionServer();
}, canEditRemove);
//servers move
@ -392,15 +368,14 @@ public class ProfilesViewModel : MyReactiveObject
ProfileItems.AddRange(lstModel);
if (lstModel.Count > 0)
{
var selected = lstModel.FirstOrDefault(t => t.IndexId == _config.IndexId);
if (selected != null)
ProfileItemModel? selected = null;
if (!_pendingSelectIndexId.IsNullOrEmpty())
{
SelectedProfile = selected;
}
else
{
SelectedProfile = lstModel.First();
selected = lstModel.FirstOrDefault(t => t.IndexId == _pendingSelectIndexId);
_pendingSelectIndexId = null;
}
selected ??= lstModel.FirstOrDefault(t => t.IndexId == _config.IndexId);
SelectedProfile = selected ?? lstModel.First();
}
await _updateView?.Invoke(EViewAction.DispatcherRefreshServersBiz, null);
@ -481,14 +456,7 @@ public class ProfilesViewModel : MyReactiveObject
var orderProfiles = SelectedProfiles?.OrderBy(t => t.Sort);
if (latest)
{
foreach (var profile in orderProfiles)
{
var item = await AppManager.Instance.GetProfileItem(profile.IndexId);
if (item is not null)
{
lstSelected.Add(item);
}
}
lstSelected.AddRange(await AppManager.Instance.GetProfileItemsOrderedByIndexIds(orderProfiles.Select(sp => sp?.IndexId)));
}
else
{
@ -641,29 +609,29 @@ public class ProfilesViewModel : MyReactiveObject
await _updateView?.Invoke(EViewAction.ShareServer, url);
}
private async Task GenGroupMultipleServer(ECoreType coreType, EMultipleLoad multipleLoad)
private async Task GenGroupAllServer()
{
var lstSelected = await GetProfileItems(true);
if (lstSelected == null)
{
return;
}
var ret = await ConfigHandler.AddGroupServer4Multiple(_config, lstSelected, coreType, multipleLoad, SelectedSub?.Id);
var ret = await ConfigHandler.AddGroupAllServer(_config, SelectedSub);
if (ret.Success != true)
{
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
return;
}
if (ret?.Data?.ToString() == _config.IndexId)
_pendingSelectIndexId = ret.Data?.ToString();
await RefreshServers();
}
private async Task GenGroupRegionServer()
{
var ret = await ConfigHandler.AddGroupRegionServer(_config, SelectedSub);
if (ret.Success != true)
{
await RefreshServers();
Reload();
}
else
{
await SetDefaultServer(ret?.Data?.ToString());
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
return;
}
var indexIdList = ret.Data as List<string>;
_pendingSelectIndexId = indexIdList?.FirstOrDefault();
await RefreshServers();
}
public async Task SortServer(string colName)
@ -789,18 +757,9 @@ public class ProfilesViewModel : MyReactiveObject
}
var (context, validatorResult) = await CoreConfigContextBuilder.Build(_config, item);
var msgs = new List<string>([..validatorResult.Errors, ..validatorResult.Warnings]);
if (msgs.Count > 0)
if (NoticeManager.Instance.NotifyValidatorResult(validatorResult) && !validatorResult.Success)
{
foreach (var msg in msgs)
{
NoticeManager.Instance.SendMessage(msg);
}
NoticeManager.Instance.Enqueue(Utils.List2String(msgs.Take(10).ToList(), true));
if (!validatorResult.Success)
{
return;
}
return;
}
if (blClipboard)
@ -829,18 +788,9 @@ public class ProfilesViewModel : MyReactiveObject
return;
}
var (context, validatorResult) = await CoreConfigContextBuilder.Build(_config, item);
var msgs = new List<string>([..validatorResult.Errors, ..validatorResult.Warnings]);
if (msgs.Count > 0)
if (NoticeManager.Instance.NotifyValidatorResult(validatorResult) && !validatorResult.Success)
{
foreach (var msg in msgs)
{
NoticeManager.Instance.SendMessage(msg);
}
NoticeManager.Instance.Enqueue(Utils.List2String(msgs.Take(10).ToList(), true));
if (!validatorResult.Success)
{
return;
}
return;
}
var result = await CoreConfigHandler.GenerateClientConfig(context, fileName);
if (result.Success != true)

View file

@ -95,7 +95,7 @@
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerList}">
<Grid
Margin="{StaticResource Margin8}"
ColumnDefinitions="300,Auto,Auto"
ColumnDefinitions="Auto,Auto,Auto"
RowDefinitions="Auto,Auto,Auto">
<TextBlock
@ -108,7 +108,7 @@
x:Name="cmbSubChildItems"
Grid.Row="0"
Grid.Column="1"
Width="200"
Width="600"
Margin="{StaticResource Margin4}"
DisplayMemberBinding="{Binding Remarks}"
ItemsSource="{Binding SubItems}" />
@ -117,7 +117,8 @@
Grid.Column="2"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbPolicyGroupSubChildTip}" />
Text="{x:Static resx:ResUI.TbPolicyGroupSubChildTip}"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="1"
@ -125,12 +126,14 @@
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.LvFilter}" />
<TextBox
x:Name="txtFilter"
<ComboBox
x:Name="cmbFilter"
Grid.Row="1"
Grid.Column="1"
Width="600"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center" />
VerticalAlignment="Center"
IsEditable="True" />
</Grid>
</TabItem>
@ -184,11 +187,11 @@
Binding="{Binding ConfigType}"
Header="{x:Static resx:ResUI.LvServiceType}" />
<DataGridTextColumn
Width="150"
Width="200"
Binding="{Binding Remarks}"
Header="{x:Static resx:ResUI.LvRemarks}" />
<DataGridTextColumn
Width="120"
Width="200"
Binding="{Binding Address}"
Header="{x:Static resx:ResUI.LvAddress}" />
<DataGridTextColumn
@ -226,11 +229,11 @@
Binding="{Binding ConfigType}"
Header="{x:Static resx:ResUI.LvServiceType}" />
<DataGridTextColumn
Width="150"
Width="200"
Binding="{Binding Remarks}"
Header="{x:Static resx:ResUI.LvRemarks}" />
<DataGridTextColumn
Width="120"
Width="200"
Binding="{Binding Address}"
Header="{x:Static resx:ResUI.LvAddress}" />
<DataGridTextColumn

View file

@ -29,6 +29,7 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
ResUI.TbRoundRobin,
ResUI.TbLeastLoad,
};
cmbFilter.ItemsSource = Global.PolicyGroupDefaultFilterList;
switch (profileItem.ConfigType)
{
@ -53,7 +54,7 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
this.Bind(ViewModel, vm => vm.PolicyGroupType, v => v.cmbPolicyGroupType.SelectedValue).DisposeWith(disposables);
//this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbSubChildItems.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSubItem, v => v.cmbSubChildItems.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Filter, v => v.txtFilter.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Filter, v => v.cmbFilter.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedChild, v => v.lstChild.SelectedItem).DisposeWith(disposables);
@ -147,14 +148,7 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
private async void MenuAddChild_Click(object? sender, RoutedEventArgs e)
{
var selectWindow = new ProfilesSelectWindow();
if (ViewModel?.SelectedSource?.ConfigType == EConfigType.PolicyGroup)
{
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true);
}
else
{
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true);
}
selectWindow.SetConfigTypeFilter([EConfigType.Custom], exclude: true);
selectWindow.AllowMultiSelect(true);
var result = await selectWindow.ShowDialog<bool?>(this);
if (result == true)

View file

@ -90,17 +90,17 @@
Header="{x:Static resx:ResUI.LvServiceType}"
Tag="ConfigType" />
<DataGridTextColumn
Width="120"
Width="200"
Binding="{Binding Remarks}"
Header="{x:Static resx:ResUI.LvRemarks}"
Tag="Remarks" />
<DataGridTextColumn
Width="120"
Width="200"
Binding="{Binding Address}"
Header="{x:Static resx:ResUI.LvAddress}"
Tag="Address" />
<DataGridTextColumn
Width="60"
Width="100"
Binding="{Binding Port}"
Header="{x:Static resx:ResUI.LvPort}"
Tag="Port" />

View file

@ -193,14 +193,9 @@
<MenuItem x:Name="menuExport2ShareUrlBase64" Header="{x:Static resx:ResUI.menuExport2ShareUrlBase64}" />
</MenuItem>
<Separator />
<MenuItem Header="{x:Static resx:ResUI.menuGenGroupMultipleServer}">
<MenuItem x:Name="menuGenGroupMultipleServerXrayRandom" Header="{x:Static resx:ResUI.menuGenGroupMultipleServerXrayRandom}" />
<MenuItem x:Name="menuGenGroupMultipleServerXrayRoundRobin" Header="{x:Static resx:ResUI.menuGenGroupMultipleServerXrayRoundRobin}" />
<MenuItem x:Name="menuGenGroupMultipleServerXrayLeastPing" Header="{x:Static resx:ResUI.menuGenGroupMultipleServerXrayLeastPing}" />
<MenuItem x:Name="menuGenGroupMultipleServerXrayLeastLoad" Header="{x:Static resx:ResUI.menuGenGroupMultipleServerXrayLeastLoad}" />
<Separator />
<MenuItem x:Name="menuGenGroupMultipleServerSingBoxLeastPing" Header="{x:Static resx:ResUI.menuGenGroupMultipleServerSingBoxLeastPing}" />
<MenuItem x:Name="menuGenGroupMultipleServerSingBoxFallback" Header="{x:Static resx:ResUI.menuGenGroupMultipleServerSingBoxFallback}" />
<MenuItem Header="{x:Static resx:ResUI.menuGenGroupServer}">
<MenuItem x:Name="menuGenGroupAllServer" Header="{x:Static resx:ResUI.menuAllServers}" />
<MenuItem x:Name="menuGenGroupRegionServer" Header="{x:Static resx:ResUI.menuGenRegionGroup}" />
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>

View file

@ -60,12 +60,8 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
this.BindCommand(ViewModel, vm => vm.CopyServerCmd, v => v.menuCopyServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SetDefaultServerCmd, v => v.menuSetDefaultServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ShareServerCmd, v => v.menuShareServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayRandomCmd, v => v.menuGenGroupMultipleServerXrayRandom).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayRoundRobinCmd, v => v.menuGenGroupMultipleServerXrayRoundRobin).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayLeastPingCmd, v => v.menuGenGroupMultipleServerXrayLeastPing).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayLeastLoadCmd, v => v.menuGenGroupMultipleServerXrayLeastLoad).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerSingBoxLeastPingCmd, v => v.menuGenGroupMultipleServerSingBoxLeastPing).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerSingBoxFallbackCmd, v => v.menuGenGroupMultipleServerSingBoxFallback).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupAllServerCmd, v => v.menuGenGroupAllServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupRegionServerCmd, v => v.menuGenGroupRegionServer).DisposeWith(disposables);
//servers move
//this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables);

View file

@ -59,7 +59,7 @@ public partial class SubEditWindow : WindowBase<SubEditViewModel>
private async void BtnSelectPrevProfile_Click(object? sender, RoutedEventArgs e)
{
var selectWindow = new ProfilesSelectWindow();
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true);
selectWindow.SetConfigTypeFilter([EConfigType.Custom], exclude: true);
var result = await selectWindow.ShowDialog<bool?>(this);
if (result == true)
{
@ -74,7 +74,7 @@ public partial class SubEditWindow : WindowBase<SubEditViewModel>
private async void BtnSelectNextProfile_Click(object? sender, RoutedEventArgs e)
{
var selectWindow = new ProfilesSelectWindow();
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true);
selectWindow.SetConfigTypeFilter([EConfigType.Custom], exclude: true);
var result = await selectWindow.ShowDialog<bool?>(this);
if (result == true)
{

View file

@ -147,7 +147,7 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
@ -163,7 +163,7 @@
x:Name="cmbSubChildItems"
Grid.Row="0"
Grid.Column="1"
Width="200"
Width="600"
Margin="{StaticResource Margin4}"
DisplayMemberPath="Remarks"
Style="{StaticResource DefComboBox}" />
@ -173,7 +173,8 @@
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbPolicyGroupSubChildTip}" />
Text="{x:Static resx:ResUI.TbPolicyGroupSubChildTip}"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="1"
@ -182,14 +183,15 @@
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.LvFilter}" />
<TextBox
x:Name="txtFilter"
<ComboBox
x:Name="cmbFilter"
Grid.Row="1"
Grid.Column="1"
Width="600"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
AcceptsReturn="True"
Style="{StaticResource DefTextBox}" />
IsEditable="True"
Style="{StaticResource DefComboBox}" />
</Grid>
</TabItem>
@ -251,11 +253,11 @@
Binding="{Binding ConfigType}"
Header="{x:Static resx:ResUI.LvServiceType}" />
<DataGridTextColumn
Width="150"
Width="200"
Binding="{Binding Remarks}"
Header="{x:Static resx:ResUI.LvRemarks}" />
<DataGridTextColumn
Width="120"
Width="200"
Binding="{Binding Address}"
Header="{x:Static resx:ResUI.LvAddress}" />
<DataGridTextColumn
@ -292,11 +294,11 @@
Binding="{Binding ConfigType}"
Header="{x:Static resx:ResUI.LvServiceType}" />
<DataGridTextColumn
Width="150"
Width="200"
Binding="{Binding Remarks}"
Header="{x:Static resx:ResUI.LvRemarks}" />
<DataGridTextColumn
Width="120"
Width="200"
Binding="{Binding Address}"
Header="{x:Static resx:ResUI.LvAddress}" />
<DataGridTextColumn

View file

@ -24,6 +24,7 @@ public partial class AddGroupServerWindow
ResUI.TbRoundRobin,
ResUI.TbLeastLoad,
};
cmbFilter.ItemsSource = Global.PolicyGroupDefaultFilterList;
switch (profileItem.ConfigType)
{
@ -48,7 +49,7 @@ public partial class AddGroupServerWindow
this.Bind(ViewModel, vm => vm.PolicyGroupType, v => v.cmbPolicyGroupType.Text).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbSubChildItems.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSubItem, v => v.cmbSubChildItems.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Filter, v => v.txtFilter.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Filter, v => v.cmbFilter.Text).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.ChildItemsObs, v => v.lstChild.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedChild, v => v.lstChild.SelectedItem).DisposeWith(disposables);
@ -127,14 +128,7 @@ public partial class AddGroupServerWindow
private async void MenuAddChild_Click(object sender, RoutedEventArgs e)
{
var selectWindow = new ProfilesSelectWindow();
if (ViewModel?.SelectedSource?.ConfigType == EConfigType.PolicyGroup)
{
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true);
}
else
{
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true);
}
selectWindow.SetConfigTypeFilter([EConfigType.Custom], exclude: true);
selectWindow.AllowMultiSelect(true);
if (selectWindow.ShowDialog() == true)
{

View file

@ -31,13 +31,13 @@ public partial class DNSSettingWindow
this.Bind(ViewModel, vm => vm.AddCommonHosts, v => v.togAddCommonHosts.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.FakeIP, v => v.togFakeIP.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.BlockBindingQuery, v => v.togBlockBindingQuery.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.DirectDNS, v => v.cmbDirectDNS.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.RemoteDNS, v => v.cmbRemoteDNS.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.BootstrapDNS, v => v.cmbBootstrapDNS.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Strategy4Freedom, v => v.cmbDirectDNSStrategy.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Strategy4Proxy, v => v.cmbRemoteDNSStrategy.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.DirectDNS, v => v.cmbDirectDNS.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.RemoteDNS, v => v.cmbRemoteDNS.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.BootstrapDNS, v => v.cmbBootstrapDNS.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Strategy4Freedom, v => v.cmbDirectDNSStrategy.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Strategy4Proxy, v => v.cmbRemoteDNSStrategy.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Hosts, v => v.txtHosts.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.DirectExpectedIPs, v => v.cmbDirectExpectedIPs.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.DirectExpectedIPs, v => v.cmbDirectExpectedIPs.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.ParallelQuery, v => v.togParallelQuery.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.ServeStale, v => v.togServeStale.IsChecked).DisposeWith(disposables);

View file

@ -119,17 +119,17 @@
ExName="ConfigType"
Header="{x:Static resx:ResUI.LvServiceType}" />
<base:MyDGTextColumn
Width="150"
Width="200"
Binding="{Binding Remarks}"
ExName="Remarks"
Header="{x:Static resx:ResUI.LvRemarks}" />
<base:MyDGTextColumn
Width="120"
Width="200"
Binding="{Binding Address}"
ExName="Address"
Header="{x:Static resx:ResUI.LvAddress}" />
<base:MyDGTextColumn
Width="60"
Width="100"
Binding="{Binding Port}"
ExName="Port"
Header="{x:Static resx:ResUI.LvPort}" />

View file

@ -240,32 +240,15 @@
Header="{x:Static resx:ResUI.menuExport2ShareUrlBase64}" />
</MenuItem>
<Separator />
<MenuItem Header="{x:Static resx:ResUI.menuGenGroupMultipleServer}">
<MenuItem Header="{x:Static resx:ResUI.menuGenGroupServer}">
<MenuItem
x:Name="menuGenGroupMultipleServerXrayRandom"
x:Name="menuGenGroupAllServer"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuGenGroupMultipleServerXrayRandom}" />
Header="{x:Static resx:ResUI.menuAllServers}" />
<MenuItem
x:Name="menuGenGroupMultipleServerXrayRoundRobin"
x:Name="menuGenGroupRegionServer"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuGenGroupMultipleServerXrayRoundRobin}" />
<MenuItem
x:Name="menuGenGroupMultipleServerXrayLeastPing"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuGenGroupMultipleServerXrayLeastPing}" />
<MenuItem
x:Name="menuGenGroupMultipleServerXrayLeastLoad"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuGenGroupMultipleServerXrayLeastLoad}" />
<Separator />
<MenuItem
x:Name="menuGenGroupMultipleServerSingBoxLeastPing"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuGenGroupMultipleServerSingBoxLeastPing}" />
<MenuItem
x:Name="menuGenGroupMultipleServerSingBoxFallback"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuGenGroupMultipleServerSingBoxFallback}" />
Header="{x:Static resx:ResUI.menuGenRegionGroup}" />
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>

View file

@ -54,12 +54,8 @@ public partial class ProfilesView
this.BindCommand(ViewModel, vm => vm.CopyServerCmd, v => v.menuCopyServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SetDefaultServerCmd, v => v.menuSetDefaultServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ShareServerCmd, v => v.menuShareServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayRandomCmd, v => v.menuGenGroupMultipleServerXrayRandom).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayRoundRobinCmd, v => v.menuGenGroupMultipleServerXrayRoundRobin).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayLeastPingCmd, v => v.menuGenGroupMultipleServerXrayLeastPing).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayLeastLoadCmd, v => v.menuGenGroupMultipleServerXrayLeastLoad).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerSingBoxLeastPingCmd, v => v.menuGenGroupMultipleServerSingBoxLeastPing).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerSingBoxFallbackCmd, v => v.menuGenGroupMultipleServerSingBoxFallback).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupAllServerCmd, v => v.menuGenGroupAllServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupRegionServerCmd, v => v.menuGenGroupRegionServer).DisposeWith(disposables);
//servers move
this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables);

View file

@ -53,7 +53,7 @@ public partial class SubEditWindow
private async void BtnSelectPrevProfile_Click(object sender, RoutedEventArgs e)
{
var selectWindow = new ProfilesSelectWindow();
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true);
selectWindow.SetConfigTypeFilter([EConfigType.Custom], exclude: true);
if (selectWindow.ShowDialog() == true)
{
var profile = await selectWindow.ProfileItem;
@ -67,7 +67,7 @@ public partial class SubEditWindow
private async void BtnSelectNextProfile_Click(object sender, RoutedEventArgs e)
{
var selectWindow = new ProfilesSelectWindow();
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true);
selectWindow.SetConfigTypeFilter([EConfigType.Custom], exclude: true);
if (selectWindow.ShowDialog() == true)
{
var profile = await selectWindow.ProfileItem;