Refactor profile item config (#8659)
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release Linux / rpm (push) Blocked by required conditions
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run

* Refactor

* Add hysteria2 bandwidth and hop interval support

* Upgrade config version and rename

* Refactor id and security

* Refactor flow

* Fix hy2 bbr

* Fix warning CS0618

* Remove unused code

* Fix hy2 migrate

* Fix

* Refactor

* Refactor ProfileItem protocol extra handling

* Refactor, use record instead of class

* Hy2 SalamanderPass

* Fix Tuic

* Fix group

* Fix Tuic

* Fix hy2 Brutal Bandwidth

* Clean Code

* Fix

* Support interval range

* Add Username

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
This commit is contained in:
DHR60 2026-02-05 11:48:33 +00:00 committed by GitHub
parent d9843dc775
commit 677e81f9a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 951 additions and 802 deletions

View file

@ -90,6 +90,8 @@ public class Global
public const string SingboxFakeDNSTag = "fake_dns"; public const string SingboxFakeDNSTag = "fake_dns";
public const string SingboxEchDNSTag = "ech_dns"; public const string SingboxEchDNSTag = "ech_dns";
public const int Hysteria2DefaultHopInt = 10;
public static readonly List<string> IEProxyProtocols = public static readonly List<string> IEProxyProtocols =
[ [
"{ip}:{http_port}", "{ip}:{http_port}",

View file

@ -230,12 +230,8 @@ public static class ConfigHandler
item.Remarks = profileItem.Remarks; item.Remarks = profileItem.Remarks;
item.Address = profileItem.Address; item.Address = profileItem.Address;
item.Port = profileItem.Port; item.Port = profileItem.Port;
item.Ports = profileItem.Ports;
item.Id = profileItem.Id; item.Password = profileItem.Password;
item.AlterId = profileItem.AlterId;
item.Security = profileItem.Security;
item.Flow = profileItem.Flow;
item.Network = profileItem.Network; item.Network = profileItem.Network;
item.HeaderType = profileItem.HeaderType; item.HeaderType = profileItem.HeaderType;
@ -258,6 +254,7 @@ public static class ConfigHandler
item.CertSha = profileItem.CertSha; item.CertSha = profileItem.CertSha;
item.EchConfigList = profileItem.EchConfigList; item.EchConfigList = profileItem.EchConfigList;
item.EchForceQuery = profileItem.EchForceQuery; item.EchForceQuery = profileItem.EchForceQuery;
item.ProtoExtra = profileItem.ProtoExtra;
} }
var ret = item.ConfigType switch var ret = item.ConfigType switch
@ -290,19 +287,22 @@ public static class ConfigHandler
profileItem.ConfigType = EConfigType.VMess; profileItem.ConfigType = EConfigType.VMess;
profileItem.Address = profileItem.Address.TrimEx(); profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx(); profileItem.Password = profileItem.Password.TrimEx();
profileItem.Security = profileItem.Security.TrimEx(); profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with
{
VmessSecurity = profileItem.GetProtocolExtra().VmessSecurity?.TrimEx()
});
profileItem.Network = profileItem.Network.TrimEx(); profileItem.Network = profileItem.Network.TrimEx();
profileItem.HeaderType = profileItem.HeaderType.TrimEx(); profileItem.HeaderType = profileItem.HeaderType.TrimEx();
profileItem.RequestHost = profileItem.RequestHost.TrimEx(); profileItem.RequestHost = profileItem.RequestHost.TrimEx();
profileItem.Path = profileItem.Path.TrimEx(); profileItem.Path = profileItem.Path.TrimEx();
profileItem.StreamSecurity = profileItem.StreamSecurity.TrimEx(); profileItem.StreamSecurity = profileItem.StreamSecurity.TrimEx();
if (!Global.VmessSecurities.Contains(profileItem.Security)) if (!Global.VmessSecurities.Contains(profileItem.GetProtocolExtra().VmessSecurity))
{ {
return -1; return -1;
} }
if (profileItem.Id.IsNullOrEmpty()) if (profileItem.Password.IsNullOrEmpty())
{ {
return -1; return -1;
} }
@ -360,11 +360,6 @@ public static class ConfigHandler
{ {
} }
} }
else if (profileItem.ConfigType.IsGroupType())
{
var profileGroupItem = await AppManager.Instance.GetProfileGroupItem(it.IndexId);
await AddGroupServerCommon(config, profileItem, profileGroupItem, true);
}
else else
{ {
await AddServerCommon(config, profileItem, true); await AddServerCommon(config, profileItem, true);
@ -610,14 +605,17 @@ public static class ConfigHandler
profileItem.ConfigType = EConfigType.Shadowsocks; profileItem.ConfigType = EConfigType.Shadowsocks;
profileItem.Address = profileItem.Address.TrimEx(); profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx(); profileItem.Password = profileItem.Password.TrimEx();
profileItem.Security = profileItem.Security.TrimEx(); profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with
{
SsMethod = profileItem.GetProtocolExtra().SsMethod?.TrimEx()
});
if (!AppManager.Instance.GetShadowsocksSecurities(profileItem).Contains(profileItem.Security)) if (!AppManager.Instance.GetShadowsocksSecurities(profileItem).Contains(profileItem.GetProtocolExtra().SsMethod))
{ {
return -1; return -1;
} }
if (profileItem.Id.IsNullOrEmpty()) if (profileItem.Password.IsNullOrEmpty())
{ {
return -1; return -1;
} }
@ -678,12 +676,12 @@ public static class ConfigHandler
profileItem.ConfigType = EConfigType.Trojan; profileItem.ConfigType = EConfigType.Trojan;
profileItem.Address = profileItem.Address.TrimEx(); profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx(); profileItem.Password = profileItem.Password.TrimEx();
if (profileItem.StreamSecurity.IsNullOrEmpty()) if (profileItem.StreamSecurity.IsNullOrEmpty())
{ {
profileItem.StreamSecurity = Global.StreamSecurity; profileItem.StreamSecurity = Global.StreamSecurity;
} }
if (profileItem.Id.IsNullOrEmpty()) if (profileItem.Password.IsNullOrEmpty())
{ {
return -1; return -1;
} }
@ -708,18 +706,24 @@ public static class ConfigHandler
//profileItem.CoreType = ECoreType.sing_box; //profileItem.CoreType = ECoreType.sing_box;
profileItem.Address = profileItem.Address.TrimEx(); profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx(); profileItem.Password = profileItem.Password.TrimEx();
profileItem.Path = profileItem.Path.TrimEx();
profileItem.Network = string.Empty; profileItem.Network = string.Empty;
if (profileItem.StreamSecurity.IsNullOrEmpty()) if (profileItem.StreamSecurity.IsNullOrEmpty())
{ {
profileItem.StreamSecurity = Global.StreamSecurity; profileItem.StreamSecurity = Global.StreamSecurity;
} }
if (profileItem.Id.IsNullOrEmpty()) if (profileItem.Password.IsNullOrEmpty())
{ {
return -1; return -1;
} }
profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with
{
SalamanderPass = profileItem.GetProtocolExtra().SalamanderPass?.TrimEx(),
UpMbps = profileItem.GetProtocolExtra().UpMbps is null or < 0 ? config.HysteriaItem.UpMbps : profileItem.GetProtocolExtra().UpMbps,
DownMbps = profileItem.GetProtocolExtra().DownMbps is null or < 0 ? config.HysteriaItem.DownMbps : profileItem.GetProtocolExtra().DownMbps,
HopInterval = profileItem.GetProtocolExtra().HopInterval?.TrimEx(),
});
await AddServerCommon(config, profileItem, toFile); await AddServerCommon(config, profileItem, toFile);
@ -741,8 +745,8 @@ public static class ConfigHandler
profileItem.CoreType = ECoreType.sing_box; profileItem.CoreType = ECoreType.sing_box;
profileItem.Address = profileItem.Address.TrimEx(); profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx(); profileItem.Username = profileItem.Username.TrimEx();
profileItem.Security = profileItem.Security.TrimEx(); profileItem.Password = profileItem.Password.TrimEx();
profileItem.Network = string.Empty; profileItem.Network = string.Empty;
if (!Global.TuicCongestionControls.Contains(profileItem.HeaderType)) if (!Global.TuicCongestionControls.Contains(profileItem.HeaderType))
@ -758,7 +762,7 @@ public static class ConfigHandler
{ {
profileItem.Alpn = "h3"; profileItem.Alpn = "h3";
} }
if (profileItem.Id.IsNullOrEmpty()) if (profileItem.Password.IsNullOrEmpty())
{ {
return -1; return -1;
} }
@ -781,17 +785,17 @@ public static class ConfigHandler
profileItem.ConfigType = EConfigType.WireGuard; profileItem.ConfigType = EConfigType.WireGuard;
profileItem.Address = profileItem.Address.TrimEx(); profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx(); profileItem.Password = profileItem.Password.TrimEx();
profileItem.PublicKey = profileItem.PublicKey.TrimEx(); profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with
profileItem.Path = profileItem.Path.TrimEx();
profileItem.RequestHost = profileItem.RequestHost.TrimEx();
profileItem.Network = string.Empty;
if (profileItem.ShortId.IsNullOrEmpty())
{ {
profileItem.ShortId = Global.TunMtus.First().ToString(); WgPublicKey = profileItem.GetProtocolExtra().WgPublicKey?.TrimEx(),
} WgPresharedKey = profileItem.GetProtocolExtra().WgPresharedKey?.TrimEx(),
WgInterfaceAddress = profileItem.GetProtocolExtra().WgInterfaceAddress?.TrimEx(),
WgReserved = profileItem.GetProtocolExtra().WgReserved?.TrimEx(),
WgMtu = profileItem.GetProtocolExtra().WgMtu is null or <= 0 ? Global.TunMtus.First() : profileItem.GetProtocolExtra().WgMtu,
});
if (profileItem.Id.IsNullOrEmpty()) if (profileItem.Password.IsNullOrEmpty())
{ {
return -1; return -1;
} }
@ -815,14 +819,13 @@ public static class ConfigHandler
profileItem.CoreType = ECoreType.sing_box; profileItem.CoreType = ECoreType.sing_box;
profileItem.Address = profileItem.Address.TrimEx(); profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx(); profileItem.Password = profileItem.Password.TrimEx();
profileItem.Security = profileItem.Security.TrimEx();
profileItem.Network = string.Empty; profileItem.Network = string.Empty;
if (profileItem.StreamSecurity.IsNullOrEmpty()) if (profileItem.StreamSecurity.IsNullOrEmpty())
{ {
profileItem.StreamSecurity = Global.StreamSecurity; profileItem.StreamSecurity = Global.StreamSecurity;
} }
if (profileItem.Id.IsNullOrEmpty()) if (profileItem.Password.IsNullOrEmpty())
{ {
return -1; return -1;
} }
@ -860,7 +863,7 @@ public static class ConfigHandler
Remarks = t.Remarks, Remarks = t.Remarks,
Address = t.Address, Address = t.Address,
Port = t.Port, Port = t.Port,
Security = t.Security, //Security = t.Security,
Network = t.Network, Network = t.Network,
StreamSecurity = t.StreamSecurity, StreamSecurity = t.StreamSecurity,
Delay = t33?.Delay ?? 0, Delay = t33?.Delay ?? 0,
@ -959,26 +962,25 @@ public static class ConfigHandler
profileItem.ConfigType = EConfigType.VLESS; profileItem.ConfigType = EConfigType.VLESS;
profileItem.Address = profileItem.Address.TrimEx(); profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx(); profileItem.Password = profileItem.Password.TrimEx();
profileItem.Security = profileItem.Security.TrimEx();
profileItem.Network = profileItem.Network.TrimEx(); profileItem.Network = profileItem.Network.TrimEx();
profileItem.HeaderType = profileItem.HeaderType.TrimEx(); profileItem.HeaderType = profileItem.HeaderType.TrimEx();
profileItem.RequestHost = profileItem.RequestHost.TrimEx(); profileItem.RequestHost = profileItem.RequestHost.TrimEx();
profileItem.Path = profileItem.Path.TrimEx(); profileItem.Path = profileItem.Path.TrimEx();
profileItem.StreamSecurity = profileItem.StreamSecurity.TrimEx(); profileItem.StreamSecurity = profileItem.StreamSecurity.TrimEx();
if (!Global.Flows.Contains(profileItem.Flow)) var vlessEncryption = profileItem.GetProtocolExtra().VlessEncryption?.TrimEx();
var flow = profileItem.GetProtocolExtra().Flow?.TrimEx() ?? string.Empty;
profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with
{ {
profileItem.Flow = Global.Flows.First(); VlessEncryption = vlessEncryption.IsNullOrEmpty() ? Global.None : vlessEncryption,
} Flow = Global.Flows.Contains(flow) ? flow : Global.Flows.First(),
if (profileItem.Id.IsNullOrEmpty()) });
if (profileItem.Password.IsNullOrEmpty())
{ {
return -1; return -1;
} }
if (profileItem.Security.IsNullOrEmpty())
{
profileItem.Security = Global.None;
}
await AddServerCommon(config, profileItem, toFile); await AddServerCommon(config, profileItem, toFile);
@ -1033,7 +1035,7 @@ public static class ConfigHandler
/// <returns>0 if successful</returns> /// <returns>0 if successful</returns>
public static async Task<int> AddServerCommon(Config config, ProfileItem profileItem, bool toFile = true) public static async Task<int> AddServerCommon(Config config, ProfileItem profileItem, bool toFile = true)
{ {
profileItem.ConfigVersion = 2; profileItem.ConfigVersion = 3;
if (profileItem.StreamSecurity.IsNotEmpty()) if (profileItem.StreamSecurity.IsNotEmpty())
{ {
@ -1077,42 +1079,12 @@ public static class ConfigHandler
if (toFile) if (toFile)
{ {
profileItem.SetProtocolExtra();
await SQLiteHelper.Instance.ReplaceAsync(profileItem); await SQLiteHelper.Instance.ReplaceAsync(profileItem);
} }
return 0; return 0;
} }
public static async Task<int> AddGroupServerCommon(Config config, ProfileItem profileItem, ProfileGroupItem profileGroupItem, bool toFile = true)
{
var maxSort = -1;
if (profileItem.IndexId.IsNullOrEmpty())
{
profileItem.IndexId = Utils.GetGuid(false);
maxSort = ProfileExManager.Instance.GetMaxSort();
}
var groupType = profileItem.ConfigType == EConfigType.ProxyChain ? EConfigType.ProxyChain.ToString() : profileGroupItem.MultipleLoad.ToString();
profileItem.Address = $"{profileItem.CoreType}-{groupType}";
if (maxSort > 0)
{
ProfileExManager.Instance.SetSort(profileItem.IndexId, maxSort + 1);
}
if (toFile)
{
await SQLiteHelper.Instance.ReplaceAsync(profileItem);
if (profileGroupItem != null)
{
profileGroupItem.IndexId = profileItem.IndexId;
await ProfileGroupItemManager.Instance.SaveItemAsync(profileGroupItem);
}
else
{
ProfileGroupItemManager.Instance.GetOrCreateAndMarkDirty(profileItem.IndexId);
await ProfileGroupItemManager.Instance.SaveTo();
}
}
return 0;
}
/// <summary> /// <summary>
/// Compare two profile items to determine if they represent the same server /// Compare two profile items to determine if they represent the same server
/// Used for deduplication and server matching /// Used for deduplication and server matching
@ -1128,17 +1100,23 @@ public static class ConfigHandler
return false; return false;
} }
var oProtocolExtra = o.GetProtocolExtra();
var nProtocolExtra = n.GetProtocolExtra();
return o.ConfigType == n.ConfigType return o.ConfigType == n.ConfigType
&& AreEqual(o.Address, n.Address) && AreEqual(o.Address, n.Address)
&& o.Port == n.Port && o.Port == n.Port
&& AreEqual(o.Id, n.Id) && AreEqual(o.Password, n.Password)
&& AreEqual(o.Security, n.Security) && AreEqual(oProtocolExtra.VlessEncryption, nProtocolExtra.VlessEncryption)
&& AreEqual(oProtocolExtra.SsMethod, nProtocolExtra.SsMethod)
&& AreEqual(oProtocolExtra.VmessSecurity, nProtocolExtra.VmessSecurity)
&& AreEqual(o.Network, n.Network) && AreEqual(o.Network, n.Network)
&& AreEqual(o.HeaderType, n.HeaderType) && AreEqual(o.HeaderType, n.HeaderType)
&& AreEqual(o.RequestHost, n.RequestHost) && AreEqual(o.RequestHost, n.RequestHost)
&& AreEqual(o.Path, n.Path) && AreEqual(o.Path, n.Path)
&& (o.ConfigType == EConfigType.Trojan || o.StreamSecurity == n.StreamSecurity) && (o.ConfigType == EConfigType.Trojan || o.StreamSecurity == n.StreamSecurity)
&& AreEqual(o.Flow, n.Flow) && AreEqual(oProtocolExtra.Flow, nProtocolExtra.Flow)
&& AreEqual(oProtocolExtra.SalamanderPass, nProtocolExtra.SalamanderPass)
&& AreEqual(o.Sni, n.Sni) && AreEqual(o.Sni, n.Sni)
&& AreEqual(o.Alpn, n.Alpn) && AreEqual(o.Alpn, n.Alpn)
&& AreEqual(o.Fingerprint, n.Fingerprint) && AreEqual(o.Fingerprint, n.Fingerprint)
@ -1199,7 +1177,7 @@ public static class ConfigHandler
var indexId = Utils.GetGuid(false); var indexId = Utils.GetGuid(false);
var childProfileIndexId = Utils.List2String(selecteds.Select(p => p.IndexId).ToList()); var childProfileIndexId = Utils.List2String(selecteds.Select(p => p.IndexId).ToList());
var remark = subId.IsNullOrEmpty() ? string.Empty : $"{(await AppManager.Instance.GetSubItem(subId)).Remarks} "; var remark = subId.IsNullOrEmpty() ? string.Empty : $"{(await AppManager.Instance.GetSubItem(subId))?.Remarks} ";
if (coreType == ECoreType.Xray) if (coreType == ECoreType.Xray)
{ {
remark += multipleLoad switch remark += multipleLoad switch
@ -1233,13 +1211,12 @@ public static class ConfigHandler
{ {
profile.Subid = subId; profile.Subid = subId;
} }
var profileGroup = new ProfileGroupItem var extraItem = new ProtocolExtraItem
{ {
ChildItems = childProfileIndexId, ChildItems = childProfileIndexId, MultipleLoad = multipleLoad,
MultipleLoad = multipleLoad,
IndexId = indexId,
}; };
var ret = await AddGroupServerCommon(config, profile, profileGroup, true); profile.SetProtocolExtra(extraItem);
var ret = await AddServerCommon(config, profile, true);
result.Success = ret == 0; result.Success = ret == 0;
result.Data = indexId; result.Data = indexId;
return result; return result;
@ -1261,7 +1238,7 @@ public static class ConfigHandler
var tun2SocksAddress = node.Address; var tun2SocksAddress = node.Address;
if (node.ConfigType.IsGroupType()) if (node.ConfigType.IsGroupType())
{ {
var lstAddresses = (await ProfileGroupItemManager.GetAllChildDomainAddresses(node.IndexId)).ToList(); var lstAddresses = (await GroupProfileManager.GetAllChildDomainAddresses(node)).ToList();
if (lstAddresses.Count > 0) if (lstAddresses.Count > 0)
{ {
tun2SocksAddress = Utils.List2String(lstAddresses); tun2SocksAddress = Utils.List2String(lstAddresses);

View file

@ -20,7 +20,7 @@ public class AnytlsFmt : BaseFmt
Port = parsedUrl.Port, Port = parsedUrl.Port,
}; };
var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo); var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo);
item.Id = rawUserInfo; item.Password = rawUserInfo;
var query = Utils.ParseQueryString(parsedUrl.Query); var query = Utils.ParseQueryString(parsedUrl.Query);
ResolveUriQuery(query, ref item); ResolveUriQuery(query, ref item);
@ -39,7 +39,7 @@ public class AnytlsFmt : BaseFmt
{ {
remark = "#" + Utils.UrlEncode(item.Remarks); remark = "#" + Utils.UrlEncode(item.Remarks);
} }
var pw = item.Id; var pw = item.Password;
var dicQuery = new Dictionary<string, string>(); var dicQuery = new Dictionary<string, string>();
ToUriQuery(item, Global.None, ref dicQuery); ToUriQuery(item, Global.None, ref dicQuery);

View file

@ -21,11 +21,6 @@ public class BaseFmt
protected static int ToUriQuery(ProfileItem item, string? securityDef, ref Dictionary<string, string> dicQuery) protected static int ToUriQuery(ProfileItem item, string? securityDef, ref Dictionary<string, string> dicQuery)
{ {
if (item.Flow.IsNotEmpty())
{
dicQuery.Add("flow", item.Flow);
}
if (item.StreamSecurity.IsNotEmpty()) if (item.StreamSecurity.IsNotEmpty())
{ {
dicQuery.Add("security", item.StreamSecurity); dicQuery.Add("security", item.StreamSecurity);
@ -208,7 +203,6 @@ public class BaseFmt
protected static int ResolveUriQuery(NameValueCollection query, ref ProfileItem item) protected static int ResolveUriQuery(NameValueCollection query, ref ProfileItem item)
{ {
item.Flow = GetQueryValue(query, "flow");
item.StreamSecurity = GetQueryValue(query, "security"); item.StreamSecurity = GetQueryValue(query, "security");
item.Sni = GetQueryValue(query, "sni"); item.Sni = GetQueryValue(query, "sni");
item.Alpn = GetQueryDecoded(query, "alpn"); item.Alpn = GetQueryDecoded(query, "alpn");

View file

@ -19,16 +19,19 @@ public class Hysteria2Fmt : BaseFmt
item.Address = url.IdnHost; item.Address = url.IdnHost;
item.Port = url.Port; item.Port = url.Port;
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
item.Id = Utils.UrlDecode(url.UserInfo); item.Password = Utils.UrlDecode(url.UserInfo);
var query = Utils.ParseQueryString(url.Query); var query = Utils.ParseQueryString(url.Query);
ResolveUriQuery(query, ref item); ResolveUriQuery(query, ref item);
item.Path = GetQueryDecoded(query, "obfs-password");
item.Ports = GetQueryDecoded(query, "mport");
if (item.CertSha.IsNullOrEmpty()) if (item.CertSha.IsNullOrEmpty())
{ {
item.CertSha = GetQueryDecoded(query, "pinSHA256"); item.CertSha = GetQueryDecoded(query, "pinSHA256");
} }
item.SetProtocolExtra(item.GetProtocolExtra() with
{
Ports = GetQueryDecoded(query, "mport"),
SalamanderPass = GetQueryDecoded(query, "obfs-password"),
});
return item; return item;
} }
@ -49,15 +52,16 @@ public class Hysteria2Fmt : BaseFmt
} }
var dicQuery = new Dictionary<string, string>(); var dicQuery = new Dictionary<string, string>();
ToUriQueryLite(item, ref dicQuery); ToUriQueryLite(item, ref dicQuery);
var protocolExtraItem = item.GetProtocolExtra();
if (item.Path.IsNotEmpty()) if (!protocolExtraItem.SalamanderPass.IsNullOrEmpty())
{ {
dicQuery.Add("obfs", "salamander"); dicQuery.Add("obfs", "salamander");
dicQuery.Add("obfs-password", Utils.UrlEncode(item.Path)); dicQuery.Add("obfs-password", Utils.UrlEncode(protocolExtraItem.SalamanderPass));
} }
if (item.Ports.IsNotEmpty()) if (!protocolExtraItem.Ports.IsNullOrEmpty())
{ {
dicQuery.Add("mport", Utils.UrlEncode(item.Ports.Replace(':', '-'))); dicQuery.Add("mport", Utils.UrlEncode(protocolExtraItem.Ports.Replace(':', '-')));
} }
if (!item.CertSha.IsNullOrEmpty()) if (!item.CertSha.IsNullOrEmpty())
{ {
@ -70,7 +74,7 @@ public class Hysteria2Fmt : BaseFmt
dicQuery.Add("pinSHA256", Utils.UrlEncode(sha)); dicQuery.Add("pinSHA256", Utils.UrlEncode(sha));
} }
return ToUri(EConfigType.Hysteria2, item.Address, item.Port, item.Id, dicQuery, remark); return ToUri(EConfigType.Hysteria2, item.Address, item.Port, item.Password, dicQuery, remark);
} }
public static ProfileItem? ResolveFull2(string strData, string? subRemarks) public static ProfileItem? ResolveFull2(string strData, string? subRemarks)

View file

@ -12,7 +12,8 @@ public class ShadowsocksFmt : BaseFmt
{ {
return null; return null;
} }
if (item.Address.Length == 0 || item.Port == 0 || item.Security.Length == 0 || item.Id.Length == 0)
if (item.Address.Length == 0 || item.Port == 0 || item.GetProtocolExtra().SsMethod.IsNullOrEmpty() || item.Password.Length == 0)
{ {
return null; return null;
} }
@ -40,7 +41,7 @@ public class ShadowsocksFmt : BaseFmt
// item.port); // item.port);
//url = Utile.Base64Encode(url); //url = Utile.Base64Encode(url);
//new Sip002 //new Sip002
var pw = Utils.Base64Encode($"{item.Security}:{item.Id}", true); var pw = Utils.Base64Encode($"{item.GetProtocolExtra().SsMethod}:{item.Password}", true);
// plugin // plugin
var plugin = string.Empty; var plugin = string.Empty;
@ -136,8 +137,8 @@ public class ShadowsocksFmt : BaseFmt
{ {
return null; return null;
} }
item.Security = details.Groups["method"].Value; item.SetProtocolExtra(item.GetProtocolExtra() with { SsMethod = details.Groups["method"].Value });
item.Id = details.Groups["password"].Value; item.Password = details.Groups["password"].Value;
item.Address = details.Groups["hostname"].Value; item.Address = details.Groups["hostname"].Value;
item.Port = details.Groups["port"].Value.ToInt(); item.Port = details.Groups["port"].Value.ToInt();
return item; return item;
@ -166,8 +167,8 @@ public class ShadowsocksFmt : BaseFmt
{ {
return null; return null;
} }
item.Security = userInfoParts.First(); item.SetProtocolExtra(item.GetProtocolExtra() with { SsMethod = userInfoParts.First() });
item.Id = Utils.UrlDecode(userInfoParts.Last()); item.Password = Utils.UrlDecode(userInfoParts.Last());
} }
else else
{ {
@ -178,8 +179,8 @@ public class ShadowsocksFmt : BaseFmt
{ {
return null; return null;
} }
item.Security = userInfoParts.First(); item.SetProtocolExtra(item.GetProtocolExtra() with { SsMethod = userInfoParts.First() });
item.Id = userInfoParts.Last(); item.Password = userInfoParts.Last();
} }
var queryParameters = Utils.ParseQueryString(parsedUrl.Query); var queryParameters = Utils.ParseQueryString(parsedUrl.Query);
@ -275,7 +276,6 @@ public class ShadowsocksFmt : BaseFmt
} }
} }
} }
return item; return item;
} }
@ -300,11 +300,11 @@ public class ShadowsocksFmt : BaseFmt
var ssItem = new ProfileItem() var ssItem = new ProfileItem()
{ {
Remarks = it.remarks, Remarks = it.remarks,
Security = it.method, Password = it.password,
Id = it.password,
Address = it.server, Address = it.server,
Port = it.server_port.ToInt() Port = it.server_port.ToInt()
}; };
ssItem.SetProtocolExtra(new ProtocolExtraItem() { SsMethod = it.method });
lst.Add(ssItem); lst.Add(ssItem);
} }
return lst; return lst;

View file

@ -33,7 +33,7 @@ public class SocksFmt : BaseFmt
remark = "#" + Utils.UrlEncode(item.Remarks); remark = "#" + Utils.UrlEncode(item.Remarks);
} }
//new //new
var pw = Utils.Base64Encode($"{item.Security}:{item.Id}", true); var pw = Utils.Base64Encode($"{item.Username}:{item.Password}", true);
return ToUri(EConfigType.SOCKS, item.Address, item.Port, pw, null, remark); return ToUri(EConfigType.SOCKS, item.Address, item.Port, pw, null, remark);
} }
@ -78,9 +78,8 @@ public class SocksFmt : BaseFmt
} }
item.Address = arr1[1][..indexPort]; item.Address = arr1[1][..indexPort];
item.Port = arr1[1][(indexPort + 1)..].ToInt(); item.Port = arr1[1][(indexPort + 1)..].ToInt();
item.Security = arr21.First(); item.Username = arr21.First();
item.Id = arr21[1]; item.Password = arr21[1];
return item; return item;
} }
@ -98,15 +97,14 @@ public class SocksFmt : BaseFmt
Address = parsedUrl.IdnHost, Address = parsedUrl.IdnHost,
Port = parsedUrl.Port, Port = parsedUrl.Port,
}; };
// parse base64 UserInfo // parse base64 UserInfo
var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo); var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo);
var userInfo = Utils.Base64Decode(rawUserInfo); var userInfo = Utils.Base64Decode(rawUserInfo);
var userInfoParts = userInfo.Split([':'], 2); var userInfoParts = userInfo.Split([':'], 2);
if (userInfoParts.Length == 2) if (userInfoParts.Length == 2)
{ {
item.Security = userInfoParts.First(); item.Username = userInfoParts.First();
item.Id = userInfoParts[1]; item.Password = userInfoParts[1];
} }
return item; return item;

View file

@ -20,9 +20,10 @@ public class TrojanFmt : BaseFmt
item.Address = url.IdnHost; item.Address = url.IdnHost;
item.Port = url.Port; item.Port = url.Port;
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
item.Id = Utils.UrlDecode(url.UserInfo); item.Password = Utils.UrlDecode(url.UserInfo);
var query = Utils.ParseQueryString(url.Query); var query = Utils.ParseQueryString(url.Query);
item.SetProtocolExtra(item.GetProtocolExtra() with { Flow = GetQueryValue(query, "flow") });
ResolveUriQuery(query, ref item); ResolveUriQuery(query, ref item);
return item; return item;
@ -40,8 +41,12 @@ public class TrojanFmt : BaseFmt
remark = "#" + Utils.UrlEncode(item.Remarks); remark = "#" + Utils.UrlEncode(item.Remarks);
} }
var dicQuery = new Dictionary<string, string>(); var dicQuery = new Dictionary<string, string>();
if (!item.GetProtocolExtra().Flow.IsNullOrEmpty())
{
dicQuery.Add("flow", item.GetProtocolExtra().Flow);
}
ToUriQuery(item, null, ref dicQuery); ToUriQuery(item, null, ref dicQuery);
return ToUri(EConfigType.Trojan, item.Address, item.Port, item.Id, dicQuery, remark); return ToUri(EConfigType.Trojan, item.Address, item.Port, item.Password, dicQuery, remark);
} }
} }

View file

@ -24,8 +24,8 @@ public class TuicFmt : BaseFmt
var userInfoParts = rawUserInfo.Split(new[] { ':' }, 2); var userInfoParts = rawUserInfo.Split(new[] { ':' }, 2);
if (userInfoParts.Length == 2) if (userInfoParts.Length == 2)
{ {
item.Id = userInfoParts.First(); item.Username = userInfoParts.First();
item.Security = userInfoParts.Last(); item.Password = userInfoParts.Last();
} }
var query = Utils.ParseQueryString(url.Query); var query = Utils.ParseQueryString(url.Query);
@ -53,6 +53,6 @@ public class TuicFmt : BaseFmt
dicQuery.Add("congestion_control", item.HeaderType); dicQuery.Add("congestion_control", item.HeaderType);
return ToUri(EConfigType.TUIC, item.Address, item.Port, $"{item.Id}:{item.Security}", dicQuery, remark); return ToUri(EConfigType.TUIC, item.Address, item.Port, $"{item.Username ?? ""}:{item.Password}", dicQuery, remark);
} }
} }

View file

@ -9,7 +9,6 @@ public class VLESSFmt : BaseFmt
ProfileItem item = new() ProfileItem item = new()
{ {
ConfigType = EConfigType.VLESS, ConfigType = EConfigType.VLESS,
Security = Global.None
}; };
var url = Utils.TryUri(str); var url = Utils.TryUri(str);
@ -21,10 +20,14 @@ public class VLESSFmt : BaseFmt
item.Address = url.IdnHost; item.Address = url.IdnHost;
item.Port = url.Port; item.Port = url.Port;
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
item.Id = Utils.UrlDecode(url.UserInfo); item.Password = Utils.UrlDecode(url.UserInfo);
var query = Utils.ParseQueryString(url.Query); var query = Utils.ParseQueryString(url.Query);
item.Security = GetQueryValue(query, "encryption", Global.None); item.SetProtocolExtra(item.GetProtocolExtra() with
{
VlessEncryption = GetQueryValue(query, "encryption", Global.None),
Flow = GetQueryValue(query, "flow")
});
item.StreamSecurity = GetQueryValue(query, "security"); item.StreamSecurity = GetQueryValue(query, "security");
ResolveUriQuery(query, ref item); ResolveUriQuery(query, ref item);
@ -44,16 +47,14 @@ public class VLESSFmt : BaseFmt
remark = "#" + Utils.UrlEncode(item.Remarks); remark = "#" + Utils.UrlEncode(item.Remarks);
} }
var dicQuery = new Dictionary<string, string>(); var dicQuery = new Dictionary<string, string>();
if (item.Security.IsNotEmpty()) dicQuery.Add("encryption",
!item.GetProtocolExtra().VlessEncryption.IsNullOrEmpty() ? item.GetProtocolExtra().VlessEncryption : Global.None);
if (!item.GetProtocolExtra().Flow.IsNullOrEmpty())
{ {
dicQuery.Add("encryption", item.Security); dicQuery.Add("flow", item.GetProtocolExtra().Flow);
}
else
{
dicQuery.Add("encryption", Global.None);
} }
ToUriQuery(item, Global.None, ref dicQuery); ToUriQuery(item, Global.None, ref dicQuery);
return ToUri(EConfigType.VLESS, item.Address, item.Port, item.Id, dicQuery, remark); return ToUri(EConfigType.VLESS, item.Address, item.Port, item.Password, dicQuery, remark);
} }
} }

View file

@ -23,15 +23,16 @@ public class VmessFmt : BaseFmt
{ {
return null; return null;
} }
var vmessQRCode = new VmessQRCode var vmessQRCode = new VmessQRCode
{ {
v = item.ConfigVersion, v = 2,
ps = item.Remarks.TrimEx(), ps = item.Remarks.TrimEx(),
add = item.Address, add = item.Address,
port = item.Port, port = item.Port,
id = item.Id, id = item.Password,
aid = item.AlterId, aid = int.TryParse(item.GetProtocolExtra()?.AlterId, out var result) ? result : 0,
scy = item.Security, scy = item.GetProtocolExtra().VmessSecurity ?? "",
net = item.Network, net = item.Network,
type = item.HeaderType, type = item.HeaderType,
host = item.RequestHost, host = item.RequestHost,
@ -71,15 +72,16 @@ public class VmessFmt : BaseFmt
item.Network = Global.DefaultNetwork; item.Network = Global.DefaultNetwork;
item.HeaderType = Global.None; item.HeaderType = Global.None;
item.ConfigVersion = vmessQRCode.v; //item.ConfigVersion = vmessQRCode.v;
item.Remarks = Utils.ToString(vmessQRCode.ps); item.Remarks = Utils.ToString(vmessQRCode.ps);
item.Address = Utils.ToString(vmessQRCode.add); item.Address = Utils.ToString(vmessQRCode.add);
item.Port = vmessQRCode.port; item.Port = vmessQRCode.port;
item.Id = Utils.ToString(vmessQRCode.id); item.Password = Utils.ToString(vmessQRCode.id);
item.AlterId = vmessQRCode.aid; item.SetProtocolExtra(new ProtocolExtraItem
item.Security = Utils.ToString(vmessQRCode.scy); {
AlterId = vmessQRCode.aid.ToString(),
item.Security = vmessQRCode.scy.IsNotEmpty() ? vmessQRCode.scy : Global.DefaultSecurity; VmessSecurity = vmessQRCode.scy.IsNullOrEmpty() ? Global.DefaultSecurity : vmessQRCode.scy,
});
if (vmessQRCode.net.IsNotEmpty()) if (vmessQRCode.net.IsNotEmpty())
{ {
item.Network = vmessQRCode.net; item.Network = vmessQRCode.net;
@ -105,7 +107,6 @@ public class VmessFmt : BaseFmt
var item = new ProfileItem var item = new ProfileItem
{ {
ConfigType = EConfigType.VMess, ConfigType = EConfigType.VMess,
Security = "auto"
}; };
var url = Utils.TryUri(str); var url = Utils.TryUri(str);
@ -117,7 +118,12 @@ public class VmessFmt : BaseFmt
item.Address = url.IdnHost; item.Address = url.IdnHost;
item.Port = url.Port; item.Port = url.Port;
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
item.Id = Utils.UrlDecode(url.UserInfo); item.Password = Utils.UrlDecode(url.UserInfo);
item.SetProtocolExtra(new ProtocolExtraItem
{
VmessSecurity = "auto",
});
var query = Utils.ParseQueryString(url.Query); var query = Utils.ParseQueryString(url.Query);
ResolveUriQuery(query, ref item); ResolveUriQuery(query, ref item);

View file

@ -20,14 +20,17 @@ public class WireguardFmt : BaseFmt
item.Address = url.IdnHost; item.Address = url.IdnHost;
item.Port = url.Port; item.Port = url.Port;
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
item.Id = Utils.UrlDecode(url.UserInfo); item.Password = Utils.UrlDecode(url.UserInfo);
var query = Utils.ParseQueryString(url.Query); var query = Utils.ParseQueryString(url.Query);
item.PublicKey = GetQueryDecoded(query, "publickey"); item.SetProtocolExtra(item.GetProtocolExtra() with
item.Path = GetQueryDecoded(query, "reserved"); {
item.RequestHost = GetQueryDecoded(query, "address"); WgPublicKey = GetQueryDecoded(query, "publickey"),
item.ShortId = GetQueryDecoded(query, "mtu"); WgReserved = GetQueryDecoded(query, "reserved"),
WgInterfaceAddress = GetQueryDecoded(query, "address"),
WgMtu = int.TryParse(GetQueryDecoded(query, "mtu"), out var mtuVal) ? mtuVal : 1280,
});
return item; return item;
} }
@ -46,22 +49,19 @@ public class WireguardFmt : BaseFmt
} }
var dicQuery = new Dictionary<string, string>(); var dicQuery = new Dictionary<string, string>();
if (item.PublicKey.IsNotEmpty()) if (!item.GetProtocolExtra().WgPublicKey.IsNullOrEmpty())
{ {
dicQuery.Add("publickey", Utils.UrlEncode(item.PublicKey)); dicQuery.Add("publickey", Utils.UrlEncode(item.GetProtocolExtra().WgPublicKey));
} }
if (item.Path.IsNotEmpty()) if (!item.GetProtocolExtra().WgReserved.IsNullOrEmpty())
{ {
dicQuery.Add("reserved", Utils.UrlEncode(item.Path)); dicQuery.Add("reserved", Utils.UrlEncode(item.GetProtocolExtra().WgReserved));
} }
if (item.RequestHost.IsNotEmpty()) if (!item.GetProtocolExtra().WgInterfaceAddress.IsNullOrEmpty())
{ {
dicQuery.Add("address", Utils.UrlEncode(item.RequestHost)); dicQuery.Add("address", Utils.UrlEncode(item.GetProtocolExtra().WgInterfaceAddress));
} }
if (item.ShortId.IsNotEmpty()) dicQuery.Add("mtu", Utils.UrlEncode(item.GetProtocolExtra().WgMtu > 0 ? item.GetProtocolExtra().WgMtu.ToString() : "1280"));
{ return ToUri(EConfigType.WireGuard, item.Address, item.Port, item.Password, dicQuery, remark);
dicQuery.Add("mtu", Utils.UrlEncode(item.ShortId));
}
return ToUri(EConfigType.WireGuard, item.Address, item.Port, item.Id, dicQuery, remark);
} }
} }

View file

@ -128,23 +128,25 @@ public class ActionPrecheckManager
} }
} }
var protocolExtra = item.GetProtocolExtra();
switch (item.ConfigType) switch (item.ConfigType)
{ {
case EConfigType.VMess: case EConfigType.VMess:
if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id)) if (item.Password.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Password))
{ {
errors.Add(string.Format(ResUI.InvalidProperty, "Id")); errors.Add(string.Format(ResUI.InvalidProperty, "Password"));
} }
break; break;
case EConfigType.VLESS: case EConfigType.VLESS:
if (item.Id.IsNullOrEmpty() || (!Utils.IsGuidByParse(item.Id) && item.Id.Length > 30)) if (item.Password.IsNullOrEmpty() || (!Utils.IsGuidByParse(item.Password) && item.Password.Length > 30))
{ {
errors.Add(string.Format(ResUI.InvalidProperty, "Id")); errors.Add(string.Format(ResUI.InvalidProperty, "Password"));
} }
if (!Global.Flows.Contains(item.Flow)) if (!Global.Flows.Contains(protocolExtra.Flow ?? string.Empty))
{ {
errors.Add(string.Format(ResUI.InvalidProperty, "Flow")); errors.Add(string.Format(ResUI.InvalidProperty, "Flow"));
} }
@ -152,14 +154,14 @@ public class ActionPrecheckManager
break; break;
case EConfigType.Shadowsocks: case EConfigType.Shadowsocks:
if (item.Id.IsNullOrEmpty()) if (item.Password.IsNullOrEmpty())
{ {
errors.Add(string.Format(ResUI.InvalidProperty, "Id")); errors.Add(string.Format(ResUI.InvalidProperty, "Password"));
} }
if (string.IsNullOrEmpty(item.Security) || !Global.SsSecuritiesInSingbox.Contains(item.Security)) if (string.IsNullOrEmpty(protocolExtra.SsMethod) || !Global.SsSecuritiesInSingbox.Contains(protocolExtra.SsMethod))
{ {
errors.Add(string.Format(ResUI.InvalidProperty, "Security")); errors.Add(string.Format(ResUI.InvalidProperty, "SsMethod"));
} }
break; break;
@ -202,37 +204,22 @@ public class ActionPrecheckManager
{ {
var errors = new List<string>(); var errors = new List<string>();
ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var group); var hasCycle = await GroupProfileManager.HasCycle(item.IndexId, item.GetProtocolExtra());
if (group is null || group.NotHasChild())
{
errors.Add(string.Format(ResUI.GroupEmpty, item.Remarks));
return errors;
}
var hasCycle = ProfileGroupItemManager.HasCycle(item.IndexId);
if (hasCycle) if (hasCycle)
{ {
errors.Add(string.Format(ResUI.GroupSelfReference, item.Remarks)); errors.Add(string.Format(ResUI.GroupSelfReference, item.Remarks));
return errors; return errors;
} }
var childIds = new List<string>(); var (childItems, _) = await GroupProfileManager.GetChildProfileItems(item);
var subItems = await ProfileGroupItemManager.GetSubChildProfileItems(group);
childIds.AddRangeSafe(subItems.Select(p => p.IndexId));
childIds.AddRangeSafe(Utils.String2List(group.ChildItems));
foreach (var child in childIds) foreach (var childItem in childItems)
{ {
var childErrors = new List<string>(); var childErrors = new List<string>();
if (child.IsNullOrEmpty())
{
continue;
}
var childItem = await AppManager.Instance.GetProfileItem(child);
if (childItem is null) if (childItem is null)
{ {
childErrors.Add(string.Format(ResUI.NodeTagNotExist, child)); childErrors.Add(string.Format(ResUI.NodeTagNotExist, ""));
continue; continue;
} }

View file

@ -81,7 +81,9 @@ public sealed class AppManager
SQLiteHelper.Instance.CreateTable<ProfileExItem>(); SQLiteHelper.Instance.CreateTable<ProfileExItem>();
SQLiteHelper.Instance.CreateTable<DNSItem>(); SQLiteHelper.Instance.CreateTable<DNSItem>();
SQLiteHelper.Instance.CreateTable<FullConfigTemplateItem>(); SQLiteHelper.Instance.CreateTable<FullConfigTemplateItem>();
#pragma warning disable CS0618
SQLiteHelper.Instance.CreateTable<ProfileGroupItem>(); SQLiteHelper.Instance.CreateTable<ProfileGroupItem>();
#pragma warning restore CS0618
return true; return true;
} }
@ -94,6 +96,11 @@ public sealed class AppManager
_ = StatePort; _ = StatePort;
_ = StatePort2; _ = StatePort2;
Task.Run(async () =>
{
await MigrateProfileExtra();
}).Wait();
return true; return true;
} }
@ -225,15 +232,6 @@ public sealed class AppManager
return await SQLiteHelper.Instance.TableAsync<ProfileItem>().FirstOrDefaultAsync(it => it.Remarks == remarks); return await SQLiteHelper.Instance.TableAsync<ProfileItem>().FirstOrDefaultAsync(it => it.Remarks == remarks);
} }
public async Task<ProfileGroupItem?> GetProfileGroupItem(string indexId)
{
if (indexId.IsNullOrEmpty())
{
return null;
}
return await SQLiteHelper.Instance.TableAsync<ProfileGroupItem>().FirstOrDefaultAsync(it => it.IndexId == indexId);
}
public async Task<List<RoutingItem>?> RoutingItems() public async Task<List<RoutingItem>?> RoutingItems()
{ {
return await SQLiteHelper.Instance.TableAsync<RoutingItem>().OrderBy(t => t.Sort).ToListAsync(); return await SQLiteHelper.Instance.TableAsync<RoutingItem>().OrderBy(t => t.Sort).ToListAsync();
@ -264,6 +262,119 @@ public sealed class AppManager
return await SQLiteHelper.Instance.TableAsync<FullConfigTemplateItem>().FirstOrDefaultAsync(it => it.CoreType == eCoreType); return await SQLiteHelper.Instance.TableAsync<FullConfigTemplateItem>().FirstOrDefaultAsync(it => it.CoreType == eCoreType);
} }
public async Task MigrateProfileExtra()
{
#pragma warning disable CS0618
var list = await SQLiteHelper.Instance.TableAsync<ProfileGroupItem>().ToListAsync();
var groupItems = new ConcurrentDictionary<string, ProfileGroupItem>(list.Where(t => !string.IsNullOrEmpty(t.IndexId)).ToDictionary(t => t.IndexId!));
const int pageSize = 500;
var offset = 0;
while (true)
{
var sql = $"SELECT * FROM ProfileItem WHERE ConfigVersion < 3 LIMIT {pageSize} OFFSET {offset}";
var batch = await SQLiteHelper.Instance.QueryAsync<ProfileItem>(sql);
if (batch is null || batch.Count == 0)
{
break;
}
var batchSuccessCount = 0;
foreach (var item in batch)
{
try
{
var extra = item.GetProtocolExtra();
if (item.ConfigType is EConfigType.PolicyGroup or EConfigType.ProxyChain)
{
extra = extra with { GroupType = nameof(item.ConfigType) };
groupItems.TryGetValue(item.IndexId, out var groupItem);
if (groupItem != null && !groupItem.NotHasChild())
{
extra = extra with
{
ChildItems = groupItem.ChildItems,
SubChildItems = groupItem.SubChildItems,
Filter = groupItem.Filter,
MultipleLoad = groupItem.MultipleLoad,
};
}
}
switch (item.ConfigType)
{
case EConfigType.Shadowsocks:
extra = extra with { SsMethod = item.Security.NullIfEmpty() };
break;
case EConfigType.VMess:
extra = extra with
{
AlterId = item.AlterId.ToString(),
VmessSecurity = item.Security.NullIfEmpty(),
};
break;
case EConfigType.VLESS:
extra = extra with
{
Flow = item.Flow.NullIfEmpty(),
VlessEncryption = item.Security,
};
break;
case EConfigType.Hysteria2:
extra = extra with
{
SalamanderPass = item.Path.NullIfEmpty(),
Ports = item.Ports.NullIfEmpty(),
UpMbps = _config.HysteriaItem.UpMbps,
DownMbps = _config.HysteriaItem.DownMbps,
HopInterval = _config.HysteriaItem.HopInterval.ToString(),
};
break;
case EConfigType.TUIC:
item.Username = item.Id;
item.Id = item.Security;
item.Password = item.Security;
break;
case EConfigType.HTTP:
case EConfigType.SOCKS:
item.Username = item.Security;
break;
case EConfigType.WireGuard:
extra = extra with
{
WgPublicKey = item.PublicKey.NullIfEmpty(),
WgInterfaceAddress = item.RequestHost.NullIfEmpty(),
WgReserved = item.Path.NullIfEmpty(),
WgMtu = int.TryParse(item.ShortId, out var mtu) ? mtu : 1280
};
break;
}
item.SetProtocolExtra(extra);
item.Password = item.Id;
item.ConfigVersion = 3;
await SQLiteHelper.Instance.UpdateAsync(item);
batchSuccessCount++;
}
catch (Exception ex)
{
Logging.SaveLog($"MigrateProfileExtra Error: {ex}");
}
}
// Only increment offset by the number of failed items that remain in the result set
// Successfully updated items are automatically excluded from future queries due to ConfigVersion = 3
offset += batch.Count - batchSuccessCount;
}
//await ProfileGroupItemManager.Instance.ClearAll();
#pragma warning restore CS0618
}
#endregion SqliteHelper #endregion SqliteHelper
#region Core Type #region Core Type

View file

@ -0,0 +1,180 @@
namespace ServiceLib.Manager;
public class GroupProfileManager
{
public static async Task<bool> HasCycle(ProfileItem item)
{
return await HasCycle(item.IndexId, item.GetProtocolExtra());
}
public static async Task<bool> HasCycle(string? indexId, ProtocolExtraItem? extraInfo)
{
return await HasCycle(indexId, extraInfo, new HashSet<string>(), new HashSet<string>());
}
public static async Task<bool> HasCycle(string? indexId, ProtocolExtraItem? extraInfo, HashSet<string> visited, HashSet<string> stack)
{
if (indexId.IsNullOrEmpty() || extraInfo == null)
{
return false;
}
if (stack.Contains(indexId))
{
return true;
}
if (visited.Contains(indexId))
{
return false;
}
visited.Add(indexId);
stack.Add(indexId);
try
{
if (extraInfo.GroupType.IsNullOrEmpty())
{
return false;
}
if (extraInfo.ChildItems.IsNullOrEmpty())
{
return false;
}
var childIds = Utils.String2List(extraInfo.ChildItems)
?.Where(p => !string.IsNullOrEmpty(p))
.ToList();
if (childIds == null)
{
return false;
}
foreach (var child in childIds)
{
var childItem = await AppManager.Instance.GetProfileItem(child);
if (await HasCycle(child, childItem?.GetProtocolExtra(), visited, stack))
{
return true;
}
}
return false;
}
finally
{
stack.Remove(indexId);
}
}
public static async Task<(List<ProfileItem> Items, ProtocolExtraItem? Extra)> GetChildProfileItems(ProfileItem profileItem)
{
var protocolExtra = profileItem?.GetProtocolExtra();
return (await GetChildProfileItemsByProtocolExtra(protocolExtra), protocolExtra);
}
public static async Task<List<ProfileItem>> GetChildProfileItemsByProtocolExtra(ProtocolExtraItem? protocolExtra)
{
if (protocolExtra == null)
{
return new();
}
var items = await GetSelectedChildProfileItems(protocolExtra);
var subItems = await GetSubChildProfileItems(protocolExtra);
items.AddRange(subItems);
return items;
}
public static async Task<List<ProfileItem>> GetSelectedChildProfileItems(ProtocolExtraItem? extra)
{
if (extra == null || extra.ChildItems.IsNullOrEmpty())
{
return new();
}
var childProfiles = (await Task.WhenAll(
(Utils.String2List(extra.ChildItems) ?? new())
.Where(p => !p.IsNullOrEmpty())
.Select(AppManager.Instance.GetProfileItem)
))
.Where(p =>
p != null &&
p.IsValid() &&
p.ConfigType != EConfigType.Custom
)
.ToList();
return childProfiles;
}
public static async Task<List<ProfileItem>> GetSubChildProfileItems(ProtocolExtraItem? extra)
{
if (extra == null || extra.SubChildItems.IsNullOrEmpty())
{
return new();
}
var childProfiles = await AppManager.Instance.ProfileItems(extra.SubChildItems ?? string.Empty);
return childProfiles?.Where(p =>
p != null &&
p.IsValid() &&
!p.ConfigType.IsComplexType() &&
(extra.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, extra.Filter))
)
.ToList() ?? new();
}
public static async Task<HashSet<string>> GetAllChildDomainAddresses(ProfileItem profileItem)
{
var childAddresses = new HashSet<string>();
var (childItems, _) = await GetChildProfileItems(profileItem);
foreach (var child in childItems)
{
if (!child.IsComplex())
{
childAddresses.Add(child.Address);
}
else if (child.ConfigType.IsGroupType())
{
var subAddresses = await GetAllChildDomainAddresses(child);
foreach (var addr in subAddresses)
{
childAddresses.Add(addr);
}
}
}
return childAddresses;
}
public static async Task<HashSet<string>> GetAllChildEchQuerySni(ProfileItem profileItem)
{
var childAddresses = new HashSet<string>();
var (childItems, _) = await GetChildProfileItems(profileItem);
foreach (var childNode in childItems)
{
if (!childNode.IsComplex() && !childNode.EchConfigList.IsNullOrEmpty())
{
if (childNode.StreamSecurity == Global.StreamSecurity
&& childNode.EchConfigList?.Contains("://") == true)
{
var idx = childNode.EchConfigList.IndexOf('+');
childAddresses.Add(idx > 0 ? childNode.EchConfigList[..idx] : childNode.Sni);
}
else
{
childAddresses.Add(childNode.Sni);
}
}
else if (childNode.ConfigType.IsGroupType())
{
var subAddresses = await GetAllChildDomainAddresses(childNode);
foreach (var addr in subAddresses)
{
childAddresses.Add(addr);
}
}
}
return childAddresses;
}
}

View file

@ -1,388 +0,0 @@
namespace ServiceLib.Manager;
public class ProfileGroupItemManager
{
private static readonly Lazy<ProfileGroupItemManager> _instance = new(() => new());
private ConcurrentDictionary<string, ProfileGroupItem> _items = new();
public static ProfileGroupItemManager Instance => _instance.Value;
private static readonly string _tag = "ProfileGroupItemManager";
private ProfileGroupItemManager()
{
}
public async Task Init()
{
await InitData();
}
// Read-only getters: do not create or mark dirty
public bool TryGet(string indexId, out ProfileGroupItem? item)
{
item = null;
if (string.IsNullOrWhiteSpace(indexId))
{
return false;
}
return _items.TryGetValue(indexId, out item);
}
public ProfileGroupItem? GetOrDefault(string indexId)
{
return string.IsNullOrWhiteSpace(indexId) ? null : (_items.TryGetValue(indexId, out var v) ? v : null);
}
private async Task InitData()
{
await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileGroupItem where IndexId not in ( select indexId from ProfileItem )");
var list = await SQLiteHelper.Instance.TableAsync<ProfileGroupItem>().ToListAsync();
_items = new ConcurrentDictionary<string, ProfileGroupItem>(list.Where(t => !string.IsNullOrEmpty(t.IndexId)).ToDictionary(t => t.IndexId!));
}
private ProfileGroupItem AddProfileGroupItem(string indexId)
{
var profileGroupItem = new ProfileGroupItem()
{
IndexId = indexId,
ChildItems = string.Empty,
MultipleLoad = EMultipleLoad.LeastPing
};
_items[indexId] = profileGroupItem;
return profileGroupItem;
}
private ProfileGroupItem GetProfileGroupItem(string indexId)
{
if (string.IsNullOrEmpty(indexId))
{
indexId = Utils.GetGuid(false);
}
return _items.GetOrAdd(indexId, AddProfileGroupItem);
}
public async Task ClearAll()
{
await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileGroupItem ");
_items.Clear();
}
public async Task SaveTo()
{
try
{
var lstExists = await SQLiteHelper.Instance.TableAsync<ProfileGroupItem>().ToListAsync();
var existsMap = lstExists.Where(t => !string.IsNullOrEmpty(t.IndexId)).ToDictionary(t => t.IndexId!);
var lstInserts = new List<ProfileGroupItem>();
var lstUpdates = new List<ProfileGroupItem>();
foreach (var item in _items.Values)
{
if (string.IsNullOrEmpty(item.IndexId))
{
continue;
}
if (existsMap.ContainsKey(item.IndexId))
{
lstUpdates.Add(item);
}
else
{
lstInserts.Add(item);
}
}
try
{
if (lstInserts.Count > 0)
{
await SQLiteHelper.Instance.InsertAllAsync(lstInserts);
}
if (lstUpdates.Count > 0)
{
await SQLiteHelper.Instance.UpdateAllAsync(lstUpdates);
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
public ProfileGroupItem GetOrCreateAndMarkDirty(string indexId)
{
return GetProfileGroupItem(indexId);
}
public async ValueTask DisposeAsync()
{
await SaveTo();
}
public async Task SaveItemAsync(ProfileGroupItem item)
{
if (item is null)
{
throw new ArgumentNullException(nameof(item));
}
if (string.IsNullOrWhiteSpace(item.IndexId))
{
throw new ArgumentException("IndexId required", nameof(item));
}
_items[item.IndexId] = item;
try
{
var lst = await SQLiteHelper.Instance.TableAsync<ProfileGroupItem>().Where(t => t.IndexId == item.IndexId).ToListAsync();
if (lst != null && lst.Count > 0)
{
await SQLiteHelper.Instance.UpdateAllAsync(new List<ProfileGroupItem> { item });
}
else
{
await SQLiteHelper.Instance.InsertAllAsync(new List<ProfileGroupItem> { item });
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
#region Helper
public static bool HasCycle(string? indexId)
{
return HasCycle(indexId, new HashSet<string>(), new HashSet<string>());
}
public static bool HasCycle(string? indexId, HashSet<string> visited, HashSet<string> stack)
{
if (indexId.IsNullOrEmpty())
{
return false;
}
if (stack.Contains(indexId))
{
return true;
}
if (visited.Contains(indexId))
{
return false;
}
visited.Add(indexId);
stack.Add(indexId);
try
{
Instance.TryGet(indexId, out var groupItem);
if (groupItem == null || groupItem.ChildItems.IsNullOrEmpty())
{
return false;
}
var childIds = Utils.String2List(groupItem.ChildItems)
.Where(p => !string.IsNullOrEmpty(p))
.ToList();
if (childIds == null)
{
return false;
}
foreach (var child in childIds)
{
if (HasCycle(child, visited, stack))
{
return true;
}
}
return false;
}
finally
{
stack.Remove(indexId);
}
}
public static async Task<(List<ProfileItem> Items, ProfileGroupItem? Group)> GetChildProfileItems(string? indexId)
{
Instance.TryGet(indexId, out var profileGroupItem);
if (profileGroupItem == null || profileGroupItem.NotHasChild())
{
return (new List<ProfileItem>(), profileGroupItem);
}
var items = new List<ProfileItem>();
items.AddRange(await GetSubChildProfileItems(profileGroupItem));
items.AddRange(await GetChildProfileItems(profileGroupItem));
return (items, profileGroupItem);
}
public static async Task<List<ProfileItem>> GetChildProfileItems(ProfileGroupItem? group)
{
if (group == null || group.ChildItems.IsNullOrEmpty())
{
return new();
}
var childProfiles = (await Task.WhenAll(
Utils.String2List(group.ChildItems)
.Where(p => !p.IsNullOrEmpty())
.Select(AppManager.Instance.GetProfileItem)
))
.Where(p =>
p != null &&
p.IsValid() &&
p.ConfigType != EConfigType.Custom
)
.ToList();
return childProfiles;
}
public static async Task<List<ProfileItem>> GetSubChildProfileItems(ProfileGroupItem? group)
{
if (group == null || group.SubChildItems.IsNullOrEmpty())
{
return new();
}
var childProfiles = await AppManager.Instance.ProfileItems(group.SubChildItems);
return childProfiles.Where(p =>
p != null &&
p.IsValid() &&
!p.ConfigType.IsComplexType() &&
(group.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, group.Filter))
)
.ToList();
}
public static async Task<HashSet<string>> GetAllChildDomainAddresses(string indexId)
{
// include grand children
var childAddresses = new HashSet<string>();
if (!Instance.TryGet(indexId, out var groupItem) || groupItem == null)
{
return childAddresses;
}
if (groupItem.SubChildItems.IsNotEmpty())
{
var subItems = await GetSubChildProfileItems(groupItem);
subItems.ForEach(p => childAddresses.Add(p.Address));
}
var childIds = Utils.String2List(groupItem.ChildItems) ?? [];
foreach (var childId in childIds)
{
var childNode = await AppManager.Instance.GetProfileItem(childId);
if (childNode == null)
{
continue;
}
if (!childNode.IsComplex())
{
childAddresses.Add(childNode.Address);
}
else if (childNode.ConfigType.IsGroupType())
{
var subAddresses = await GetAllChildDomainAddresses(childNode.IndexId);
foreach (var addr in subAddresses)
{
childAddresses.Add(addr);
}
}
}
return childAddresses;
}
public static async Task<HashSet<string>> GetAllChildEchQuerySni(string indexId)
{
// include grand children
var childAddresses = new HashSet<string>();
if (!Instance.TryGet(indexId, out var groupItem) || groupItem == null)
{
return childAddresses;
}
if (groupItem.SubChildItems.IsNotEmpty())
{
var subItems = await GetSubChildProfileItems(groupItem);
foreach (var childNode in subItems)
{
if (childNode.EchConfigList.IsNullOrEmpty())
{
continue;
}
if (childNode.StreamSecurity == Global.StreamSecurity
&& childNode.EchConfigList?.Contains("://") == true)
{
var idx = childNode.EchConfigList.IndexOf('+');
childAddresses.Add(idx > 0 ? childNode.EchConfigList[..idx] : childNode.Sni);
}
else
{
childAddresses.Add(childNode.Sni);
}
}
}
var childIds = Utils.String2List(groupItem.ChildItems) ?? [];
foreach (var childId in childIds)
{
var childNode = await AppManager.Instance.GetProfileItem(childId);
if (childNode == null)
{
continue;
}
if (!childNode.IsComplex() && !childNode.EchConfigList.IsNullOrEmpty())
{
if (childNode.StreamSecurity == Global.StreamSecurity
&& childNode.EchConfigList?.Contains("://") == true)
{
var idx = childNode.EchConfigList.IndexOf('+');
childAddresses.Add(idx > 0 ? childNode.EchConfigList[..idx] : childNode.Sni);
}
else
{
childAddresses.Add(childNode.Sni);
}
}
else if (childNode.ConfigType.IsGroupType())
{
var subAddresses = await GetAllChildDomainAddresses(childNode.IndexId);
foreach (var addr in subAddresses)
{
childAddresses.Add(addr);
}
}
}
return childAddresses;
}
#endregion Helper
}

View file

@ -1,5 +1,6 @@
namespace ServiceLib.Models; namespace ServiceLib.Models;
[Obsolete("Use ProtocolExtraItem instead.")]
[Serializable] [Serializable]
public class ProfileGroupItem public class ProfileGroupItem
{ {

View file

@ -3,16 +3,17 @@ namespace ServiceLib.Models;
[Serializable] [Serializable]
public class ProfileItem : ReactiveObject public class ProfileItem : ReactiveObject
{ {
private ProtocolExtraItem? _protocolExtraCache;
public ProfileItem() public ProfileItem()
{ {
IndexId = string.Empty; IndexId = string.Empty;
ConfigType = EConfigType.VMess; ConfigType = EConfigType.VMess;
ConfigVersion = 2; ConfigVersion = 3;
Address = string.Empty; Address = string.Empty;
Port = 0; Port = 0;
Id = string.Empty; Password = string.Empty;
AlterId = 0; Username = string.Empty;
Security = string.Empty;
Network = string.Empty; Network = string.Empty;
Remarks = string.Empty; Remarks = string.Empty;
HeaderType = string.Empty; HeaderType = string.Empty;
@ -21,7 +22,6 @@ public class ProfileItem : ReactiveObject
StreamSecurity = string.Empty; StreamSecurity = string.Empty;
AllowInsecure = string.Empty; AllowInsecure = string.Empty;
Subid = string.Empty; Subid = string.Empty;
Flow = string.Empty;
} }
#region function #region function
@ -81,7 +81,7 @@ public class ProfileItem : ReactiveObject
switch (ConfigType) switch (ConfigType)
{ {
case EConfigType.VMess: case EConfigType.VMess:
if (Id.IsNullOrEmpty() || !Utils.IsGuidByParse(Id)) if (Password.IsNullOrEmpty() || !Utils.IsGuidByParse(Password))
{ {
return false; return false;
} }
@ -89,12 +89,12 @@ public class ProfileItem : ReactiveObject
break; break;
case EConfigType.VLESS: case EConfigType.VLESS:
if (Id.IsNullOrEmpty() || (!Utils.IsGuidByParse(Id) && Id.Length > 30)) if (Password.IsNullOrEmpty() || (!Utils.IsGuidByParse(Password) && Password.Length > 30))
{ {
return false; return false;
} }
if (!Global.Flows.Contains(Flow)) if (!Global.Flows.Contains(GetProtocolExtra().Flow ?? string.Empty))
{ {
return false; return false;
} }
@ -102,12 +102,13 @@ public class ProfileItem : ReactiveObject
break; break;
case EConfigType.Shadowsocks: case EConfigType.Shadowsocks:
if (Id.IsNullOrEmpty()) if (Password.IsNullOrEmpty())
{ {
return false; return false;
} }
if (string.IsNullOrEmpty(Security) || !Global.SsSecuritiesInSingbox.Contains(Security)) if (string.IsNullOrEmpty(GetProtocolExtra().SsMethod)
|| !Global.SsSecuritiesInSingbox.Contains(GetProtocolExtra().SsMethod))
{ {
return false; return false;
} }
@ -125,6 +126,22 @@ public class ProfileItem : ReactiveObject
return true; return true;
} }
public void SetProtocolExtra(ProtocolExtraItem extraItem)
{
_protocolExtraCache = extraItem;
ProtoExtra = JsonUtils.Serialize(extraItem, false);
}
public void SetProtocolExtra()
{
ProtoExtra = JsonUtils.Serialize(_protocolExtraCache, false);
}
public ProtocolExtraItem GetProtocolExtra()
{
return _protocolExtraCache ??= JsonUtils.Deserialize<ProtocolExtraItem>(ProtoExtra) ?? new ProtocolExtraItem();
}
#endregion function #endregion function
[PrimaryKey] [PrimaryKey]
@ -134,10 +151,8 @@ public class ProfileItem : ReactiveObject
public int ConfigVersion { get; set; } public int ConfigVersion { get; set; }
public string Address { get; set; } public string Address { get; set; }
public int Port { get; set; } public int Port { get; set; }
public string Ports { get; set; } public string Password { get; set; }
public string Id { get; set; } public string Username { get; set; }
public int AlterId { get; set; }
public string Security { get; set; }
public string Network { get; set; } public string Network { get; set; }
public string Remarks { get; set; } public string Remarks { get; set; }
public string HeaderType { get; set; } public string HeaderType { get; set; }
@ -147,7 +162,6 @@ public class ProfileItem : ReactiveObject
public string AllowInsecure { get; set; } public string AllowInsecure { get; set; }
public string Subid { get; set; } public string Subid { get; set; }
public bool IsSub { get; set; } = true; public bool IsSub { get; set; } = true;
public string Flow { get; set; }
public string Sni { get; set; } public string Sni { get; set; }
public string Alpn { get; set; } = string.Empty; public string Alpn { get; set; } = string.Empty;
public ECoreType? CoreType { get; set; } public ECoreType? CoreType { get; set; }
@ -164,4 +178,21 @@ public class ProfileItem : ReactiveObject
public string CertSha { get; set; } public string CertSha { get; set; }
public string EchConfigList { get; set; } public string EchConfigList { get; set; }
public string EchForceQuery { get; set; } public string EchForceQuery { get; set; }
public string ProtoExtra { get; set; }
[Obsolete("Use ProtocolExtraItem.Ports instead.")]
public string Ports { get; set; }
[Obsolete("Use ProtocolExtraItem.AlterId instead.")]
public int AlterId { get; set; }
[Obsolete("Use ProtocolExtraItem.Flow instead.")]
public string Flow { get; set; }
[Obsolete("Use ProfileItem.Password instead.")]
public string Id { get; set; }
[Obsolete("Use ProtocolExtraItem.xxx instead.")]
public string Security { get; set; }
} }

View file

@ -0,0 +1,38 @@
namespace ServiceLib.Models;
public record ProtocolExtraItem
{
// vmess
public string? AlterId { get; init; }
public string? VmessSecurity { get; init; }
// vless
public string? Flow { get; init; }
public string? VlessEncryption { get; init; }
//public string? VisionSeed { get; init; }
// shadowsocks
//public string? PluginArgs { get; init; }
public string? SsMethod { get; init; }
// wireguard
public string? WgPublicKey { get; init; }
public string? WgPresharedKey { get; init; }
public string? WgInterfaceAddress { get; init; }
public string? WgReserved { get; init; }
public int? WgMtu { get; init; }
// hysteria2
public string? SalamanderPass { get; init; }
public int? UpMbps { get; init; }
public int? DownMbps { get; init; }
public string? Ports { get; init; }
public string? HopInterval { get; init; }
// group profile
public string? GroupType { get; init; }
public string? ChildItems { get; init; }
public string? SubChildItems { get; init; }
public string? Filter { get; init; }
public EMultipleLoad? MultipleLoad { get; init; }
}

View file

@ -473,7 +473,7 @@ public class HysteriaSettings4Ray
public class HysteriaUdpHop4Ray public class HysteriaUdpHop4Ray
{ {
public string? ports { get; set; } public string? ports { get; set; }
public int? interval { get; set; } public string? interval { get; set; }
} }
public class FinalMask4Ray public class FinalMask4Ray

View file

@ -2997,6 +2997,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Port hopping interval 的本地化字符串。
/// </summary>
public static string TbHopInt7 {
get {
return ResourceManager.GetString("TbHopInt7", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 UUID(id) 的本地化字符串。 /// 查找类似 UUID(id) 的本地化字符串。
/// </summary> /// </summary>

View file

@ -1665,4 +1665,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbRemoteResolveStrategyTips" xml:space="preserve"> <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>If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used.</value>
</data> </data>
<data name="TbHopInt7" xml:space="preserve">
<value>Port hopping interval</value>
</data>
</root> </root>

View file

@ -1662,4 +1662,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbRemoteResolveStrategyTips" xml:space="preserve"> <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>If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used.</value>
</data> </data>
<data name="TbHopInt7" xml:space="preserve">
<value>Port hopping interval</value>
</data>
</root> </root>

View file

@ -1665,4 +1665,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbRemoteResolveStrategyTips" xml:space="preserve"> <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>If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used.</value>
</data> </data>
<data name="TbHopInt7" xml:space="preserve">
<value>Port hopping interval</value>
</data>
</root> </root>

View file

@ -1665,4 +1665,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbRemoteResolveStrategyTips" xml:space="preserve"> <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>If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used.</value>
</data> </data>
<data name="TbHopInt7" xml:space="preserve">
<value>Port hopping interval</value>
</data>
</root> </root>

View file

@ -1665,4 +1665,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbRemoteResolveStrategyTips" xml:space="preserve"> <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>If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used.</value>
</data> </data>
<data name="TbHopInt7" xml:space="preserve">
<value>Port hopping interval</value>
</data>
</root> </root>

View file

@ -1662,4 +1662,7 @@
<data name="TbRemoteResolveStrategyTips" xml:space="preserve"> <data name="TbRemoteResolveStrategyTips" xml:space="preserve">
<value>当未选择或 "AsIs" 时,由远程服务器端 DNS 解析;否则,使用内部 DNS 模块解析。</value> <value>当未选择或 "AsIs" 时,由远程服务器端 DNS 解析;否则,使用内部 DNS 模块解析。</value>
</data> </data>
<data name="TbHopInt7" xml:space="preserve">
<value>端口跳跃间隔</value>
</data>
</root> </root>

View file

@ -1662,4 +1662,7 @@
<data name="TbRemoteResolveStrategyTips" xml:space="preserve"> <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>If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used.</value>
</data> </data>
<data name="TbHopInt7" xml:space="preserve">
<value>Port hopping interval</value>
</data>
</root> </root>

View file

@ -242,7 +242,7 @@ public partial class CoreConfigSingboxService
} }
else if (node?.ConfigType.IsGroupType() == true) else if (node?.ConfigType.IsGroupType() == true)
{ {
var queryServerNames = (await ProfileGroupItemManager.GetAllChildEchQuerySni(node.IndexId)).ToList(); var queryServerNames = (await GroupProfileManager.GetAllChildEchQuerySni(node)).ToList();
if (queryServerNames.Count > 0) if (queryServerNames.Count > 0)
{ {
singboxConfig.dns.rules.Add(new() singboxConfig.dns.rules.Add(new()

View file

@ -6,6 +6,7 @@ public partial class CoreConfigSingboxService
{ {
try try
{ {
var protocolExtra = node.GetProtocolExtra();
outbound.server = node.Address; outbound.server = node.Address;
outbound.server_port = node.Port; outbound.server_port = node.Port;
outbound.type = Global.ProtocolTypes[node.ConfigType]; outbound.type = Global.ProtocolTypes[node.ConfigType];
@ -14,11 +15,11 @@ public partial class CoreConfigSingboxService
{ {
case EConfigType.VMess: case EConfigType.VMess:
{ {
outbound.uuid = node.Id; outbound.uuid = node.Password;
outbound.alter_id = node.AlterId; outbound.alter_id = int.TryParse(protocolExtra.AlterId, out var result) ? result : 0;
if (Global.VmessSecurities.Contains(node.Security)) if (Global.VmessSecurities.Contains(protocolExtra.VmessSecurity))
{ {
outbound.security = node.Security; outbound.security = protocolExtra.VmessSecurity;
} }
else else
{ {
@ -31,8 +32,9 @@ public partial class CoreConfigSingboxService
} }
case EConfigType.Shadowsocks: case EConfigType.Shadowsocks:
{ {
outbound.method = AppManager.Instance.GetShadowsocksSecurities(node).Contains(node.Security) ? node.Security : Global.None; outbound.method = AppManager.Instance.GetShadowsocksSecurities(node).Contains(protocolExtra.SsMethod)
outbound.password = node.Id; ? protocolExtra.SsMethod : Global.None;
outbound.password = node.Password;
if (node.Network == nameof(ETransport.tcp) && node.HeaderType == Global.TcpHeaderHttp) if (node.Network == nameof(ETransport.tcp) && node.HeaderType == Global.TcpHeaderHttp)
{ {
@ -88,37 +90,37 @@ public partial class CoreConfigSingboxService
case EConfigType.SOCKS: case EConfigType.SOCKS:
{ {
outbound.version = "5"; outbound.version = "5";
if (node.Security.IsNotEmpty() if (node.Username.IsNotEmpty()
&& node.Id.IsNotEmpty()) && node.Password.IsNotEmpty())
{ {
outbound.username = node.Security; outbound.username = node.Username;
outbound.password = node.Id; outbound.password = node.Password;
} }
break; break;
} }
case EConfigType.HTTP: case EConfigType.HTTP:
{ {
if (node.Security.IsNotEmpty() if (node.Username.IsNotEmpty()
&& node.Id.IsNotEmpty()) && node.Password.IsNotEmpty())
{ {
outbound.username = node.Security; outbound.username = node.Username;
outbound.password = node.Id; outbound.password = node.Password;
} }
break; break;
} }
case EConfigType.VLESS: case EConfigType.VLESS:
{ {
outbound.uuid = node.Id; outbound.uuid = node.Password;
outbound.packet_encoding = "xudp"; outbound.packet_encoding = "xudp";
if (node.Flow.IsNullOrEmpty()) if (!protocolExtra.Flow.IsNullOrEmpty())
{ {
await GenOutboundMux(node, outbound); outbound.flow = protocolExtra.Flow;
} }
else else
{ {
outbound.flow = node.Flow; await GenOutboundMux(node, outbound);
} }
await GenOutboundTransport(node, outbound); await GenOutboundTransport(node, outbound);
@ -126,7 +128,7 @@ public partial class CoreConfigSingboxService
} }
case EConfigType.Trojan: case EConfigType.Trojan:
{ {
outbound.password = node.Id; outbound.password = node.Password;
await GenOutboundMux(node, outbound); await GenOutboundMux(node, outbound);
await GenOutboundTransport(node, outbound); await GenOutboundTransport(node, outbound);
@ -134,23 +136,28 @@ public partial class CoreConfigSingboxService
} }
case EConfigType.Hysteria2: case EConfigType.Hysteria2:
{ {
outbound.password = node.Id; outbound.password = node.Password;
if (node.Path.IsNotEmpty()) if (!protocolExtra.SalamanderPass.IsNullOrEmpty())
{ {
outbound.obfs = new() outbound.obfs = new()
{ {
type = "salamander", type = "salamander",
password = node.Path.TrimEx(), password = protocolExtra.SalamanderPass.TrimEx(),
}; };
} }
outbound.up_mbps = _config.HysteriaItem.UpMbps > 0 ? _config.HysteriaItem.UpMbps : null; outbound.up_mbps = protocolExtra?.UpMbps is { } su and >= 0
outbound.down_mbps = _config.HysteriaItem.DownMbps > 0 ? _config.HysteriaItem.DownMbps : null; ? su
if (node.Ports.IsNotEmpty() && (node.Ports.Contains(':') || node.Ports.Contains('-') || node.Ports.Contains(','))) : _config.HysteriaItem.UpMbps;
outbound.down_mbps = protocolExtra?.DownMbps is { } sd and >= 0
? sd
: _config.HysteriaItem.DownMbps;
var ports = protocolExtra?.Ports?.IsNullOrEmpty() == false ? protocolExtra.Ports : null;
if ((!ports.IsNullOrEmpty()) && (ports.Contains(':') || ports.Contains('-') || ports.Contains(',')))
{ {
outbound.server_port = null; outbound.server_port = null;
outbound.server_ports = node.Ports.Split(',') outbound.server_ports = ports.Split(',')
.Select(p => p.Trim()) .Select(p => p.Trim())
.Where(p => p.IsNotEmpty()) .Where(p => p.IsNotEmpty())
.Select(p => .Select(p =>
@ -159,21 +166,38 @@ public partial class CoreConfigSingboxService
return port.Contains(':') ? port : $"{port}:{port}"; return port.Contains(':') ? port : $"{port}:{port}";
}) })
.ToList(); .ToList();
outbound.hop_interval = _config.HysteriaItem.HopInterval > 0 ? $"{_config.HysteriaItem.HopInterval}s" : null; outbound.hop_interval = _config.HysteriaItem.HopInterval >= 5
? $"{_config.HysteriaItem.HopInterval}s"
: $"{Global.Hysteria2DefaultHopInt}s";
if (int.TryParse(protocolExtra.HopInterval, out var hiResult))
{
outbound.hop_interval = hiResult >= 5 ? $"{hiResult}s" : outbound.hop_interval;
}
else if (protocolExtra.HopInterval?.Contains('-') ?? false)
{
// may be a range like 5-10
var parts = protocolExtra.HopInterval.Split('-');
if (parts.Length == 2 && int.TryParse(parts[0], out var hiL) &&
int.TryParse(parts[0], out var hiH))
{
var hi = (hiL + hiH) / 2;
outbound.hop_interval = hi >= 5 ? $"{hi}s" : outbound.hop_interval;
}
}
} }
break; break;
} }
case EConfigType.TUIC: case EConfigType.TUIC:
{ {
outbound.uuid = node.Id; outbound.uuid = node.Username;
outbound.password = node.Security; outbound.password = node.Password;
outbound.congestion_control = node.HeaderType; outbound.congestion_control = node.HeaderType;
break; break;
} }
case EConfigType.Anytls: case EConfigType.Anytls:
{ {
outbound.password = node.Id; outbound.password = node.Password;
break; break;
} }
} }
@ -191,7 +215,9 @@ public partial class CoreConfigSingboxService
{ {
try try
{ {
endpoint.address = Utils.String2List(node.RequestHost); var protocolExtra = node.GetProtocolExtra();
endpoint.address = Utils.String2List(protocolExtra.WgInterfaceAddress);
endpoint.type = Global.ProtocolTypes[node.ConfigType]; endpoint.type = Global.ProtocolTypes[node.ConfigType];
switch (node.ConfigType) switch (node.ConfigType)
@ -200,16 +226,17 @@ public partial class CoreConfigSingboxService
{ {
var peer = new Peer4Sbox var peer = new Peer4Sbox
{ {
public_key = node.PublicKey, public_key = protocolExtra.WgPublicKey,
reserved = Utils.String2List(node.Path)?.Select(int.Parse).ToList(), pre_shared_key = protocolExtra.WgPresharedKey,
reserved = Utils.String2List(protocolExtra.WgReserved)?.Select(int.Parse).ToList(),
address = node.Address, address = node.Address,
port = node.Port, port = node.Port,
// TODO default ["0.0.0.0/0", "::/0"] // TODO default ["0.0.0.0/0", "::/0"]
allowed_ips = new() { "0.0.0.0/0", "::/0" }, allowed_ips = new() { "0.0.0.0/0", "::/0" },
}; };
endpoint.private_key = node.Id; endpoint.private_key = node.Password;
endpoint.mtu = node.ShortId.IsNullOrEmpty() ? Global.TunMtus.First() : node.ShortId.ToInt(); endpoint.mtu = protocolExtra.WgMtu > 0 ? protocolExtra.WgMtu : Global.TunMtus.First();
endpoint.peers = new() { peer }; endpoint.peers = [peer];
break; break;
} }
} }
@ -453,13 +480,13 @@ public partial class CoreConfigSingboxService
{ {
return -1; return -1;
} }
var hasCycle = ProfileGroupItemManager.HasCycle(node.IndexId); var hasCycle = await GroupProfileManager.HasCycle(node);
if (hasCycle) if (hasCycle)
{ {
return -1; return -1;
} }
var (childProfiles, profileGroupItem) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId); var (childProfiles, profileExtraItem) = await GroupProfileManager.GetChildProfileItems(node);
if (childProfiles.Count <= 0) if (childProfiles.Count <= 0)
{ {
return -1; return -1;
@ -467,13 +494,14 @@ public partial class CoreConfigSingboxService
switch (node.ConfigType) switch (node.ConfigType)
{ {
case EConfigType.PolicyGroup: case EConfigType.PolicyGroup:
var multipleLoad = profileExtraItem?.MultipleLoad ?? EMultipleLoad.LeastPing;
if (ignoreOriginChain) if (ignoreOriginChain)
{ {
await GenOutboundsList(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, baseTagName); await GenOutboundsList(childProfiles, singboxConfig, multipleLoad, baseTagName);
} }
else else
{ {
await GenOutboundsListWithChain(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, baseTagName); await GenOutboundsListWithChain(childProfiles, singboxConfig, multipleLoad, baseTagName);
} }
break; break;
@ -585,7 +613,7 @@ public partial class CoreConfigSingboxService
if (node.ConfigType.IsGroupType()) if (node.ConfigType.IsGroupType())
{ {
var (childProfiles, profileGroupItem) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId); var (childProfiles, profileExtraItem) = await GroupProfileManager.GetChildProfileItems(node);
if (childProfiles.Count <= 0) if (childProfiles.Count <= 0)
{ {
continue; continue;
@ -594,7 +622,8 @@ public partial class CoreConfigSingboxService
var ret = node.ConfigType switch var ret = node.ConfigType switch
{ {
EConfigType.PolicyGroup => EConfigType.PolicyGroup =>
await GenOutboundsListWithChain(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, childBaseTagName), await GenOutboundsListWithChain(childProfiles, singboxConfig,
profileExtraItem?.MultipleLoad ?? EMultipleLoad.LeastPing, childBaseTagName),
EConfigType.ProxyChain => EConfigType.ProxyChain =>
await GenChainOutboundsList(childProfiles, singboxConfig, childBaseTagName), await GenChainOutboundsList(childProfiles, singboxConfig, childBaseTagName),
_ => throw new NotImplementedException() _ => throw new NotImplementedException()
@ -763,7 +792,7 @@ public partial class CoreConfigSingboxService
if (node.ConfigType.IsGroupType()) if (node.ConfigType.IsGroupType())
{ {
var (childProfiles, profileGroupItem) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId); var (childProfiles, profileExtraItem) = await GroupProfileManager.GetChildProfileItems(node);
if (childProfiles.Count <= 0) if (childProfiles.Count <= 0)
{ {
continue; continue;
@ -772,7 +801,7 @@ public partial class CoreConfigSingboxService
var ret = node.ConfigType switch var ret = node.ConfigType switch
{ {
EConfigType.PolicyGroup => EConfigType.PolicyGroup =>
await GenOutboundsList(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, childBaseTagName), await GenOutboundsList(childProfiles, singboxConfig, profileExtraItem?.MultipleLoad ?? EMultipleLoad.LeastPing, childBaseTagName),
EConfigType.ProxyChain => EConfigType.ProxyChain =>
await GenChainOutboundsList(childProfiles, singboxConfig, childBaseTagName), await GenChainOutboundsList(childProfiles, singboxConfig, childBaseTagName),
_ => throw new NotImplementedException() _ => throw new NotImplementedException()

View file

@ -6,6 +6,7 @@ public partial class CoreConfigV2rayService
{ {
try try
{ {
var protocolExtra = node.GetProtocolExtra();
var muxEnabled = node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled; var muxEnabled = node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled;
switch (node.ConfigType) switch (node.ConfigType)
{ {
@ -35,12 +36,12 @@ public partial class CoreConfigV2rayService
usersItem = vnextItem.users.First(); usersItem = vnextItem.users.First();
} }
usersItem.id = node.Id; usersItem.id = node.Password;
usersItem.alterId = node.AlterId; usersItem.alterId = int.TryParse(protocolExtra?.AlterId, out var result) ? result : 0;
usersItem.email = Global.UserEMail; usersItem.email = Global.UserEMail;
if (Global.VmessSecurities.Contains(node.Security)) if (Global.VmessSecurities.Contains(protocolExtra.VmessSecurity))
{ {
usersItem.security = node.Security; usersItem.security = protocolExtra.VmessSecurity;
} }
else else
{ {
@ -66,8 +67,9 @@ public partial class CoreConfigV2rayService
} }
serversItem.address = node.Address; serversItem.address = node.Address;
serversItem.port = node.Port; serversItem.port = node.Port;
serversItem.password = node.Id; serversItem.password = node.Password;
serversItem.method = AppManager.Instance.GetShadowsocksSecurities(node).Contains(node.Security) ? node.Security : "none"; serversItem.method = AppManager.Instance.GetShadowsocksSecurities(node).Contains(protocolExtra.SsMethod)
? protocolExtra.SsMethod : "none";
serversItem.ota = false; serversItem.ota = false;
serversItem.level = 1; serversItem.level = 1;
@ -95,13 +97,13 @@ public partial class CoreConfigV2rayService
serversItem.method = null; serversItem.method = null;
serversItem.password = null; serversItem.password = null;
if (node.Security.IsNotEmpty() if (node.Username.IsNotEmpty()
&& node.Id.IsNotEmpty()) && node.Password.IsNotEmpty())
{ {
SocksUsersItem4Ray socksUsersItem = new() SocksUsersItem4Ray socksUsersItem = new()
{ {
user = node.Security, user = node.Username ?? "",
pass = node.Id, pass = node.Password,
level = 1 level = 1
}; };
@ -138,17 +140,16 @@ public partial class CoreConfigV2rayService
{ {
usersItem = vnextItem.users.First(); usersItem = vnextItem.users.First();
} }
usersItem.id = node.Id; usersItem.id = node.Password;
usersItem.email = Global.UserEMail; usersItem.email = Global.UserEMail;
usersItem.encryption = node.Security; usersItem.encryption = protocolExtra.VlessEncryption;
if (node.Flow.IsNullOrEmpty()) if (!protocolExtra.Flow.IsNullOrEmpty())
{ {
await GenOutboundMux(node, outbound, muxEnabled, muxEnabled); usersItem.flow = protocolExtra.Flow;
} }
else else
{ {
usersItem.flow = node.Flow;
await GenOutboundMux(node, outbound, false, muxEnabled); await GenOutboundMux(node, outbound, false, muxEnabled);
} }
outbound.settings.servers = null; outbound.settings.servers = null;
@ -168,7 +169,7 @@ public partial class CoreConfigV2rayService
} }
serversItem.address = node.Address; serversItem.address = node.Address;
serversItem.port = node.Port; serversItem.port = node.Port;
serversItem.password = node.Id; serversItem.password = node.Password;
serversItem.ota = false; serversItem.ota = false;
serversItem.level = 1; serversItem.level = 1;
@ -199,16 +200,16 @@ public partial class CoreConfigV2rayService
} }
var peer = new WireguardPeer4Ray var peer = new WireguardPeer4Ray
{ {
publicKey = node.PublicKey, publicKey = protocolExtra.WgPublicKey ?? "",
endpoint = address + ":" + node.Port.ToString() endpoint = address + ":" + node.Port.ToString()
}; };
var setting = new Outboundsettings4Ray var setting = new Outboundsettings4Ray
{ {
address = Utils.String2List(node.RequestHost), address = Utils.String2List(protocolExtra.WgInterfaceAddress),
secretKey = node.Id, secretKey = node.Password,
reserved = Utils.String2List(node.Path)?.Select(int.Parse).ToList(), reserved = Utils.String2List(protocolExtra.WgReserved)?.Select(int.Parse).ToList(),
mtu = node.ShortId.IsNullOrEmpty() ? Global.TunMtus.First() : node.ShortId.ToInt(), mtu = protocolExtra.WgMtu > 0 ? protocolExtra.WgMtu : Global.TunMtus.First(),
peers = new List<WireguardPeer4Ray> { peer } peers = [peer]
}; };
outbound.settings = setting; outbound.settings = setting;
outbound.settings.vnext = null; outbound.settings.vnext = null;
@ -509,28 +510,38 @@ public partial class CoreConfigV2rayService
break; break;
case "hysteria": case "hysteria":
var protocolExtra = node.GetProtocolExtra();
var ports = protocolExtra?.Ports;
int? upMbps = protocolExtra?.UpMbps is { } su and >= 0
? su
: _config.HysteriaItem.UpMbps;
int? downMbps = protocolExtra?.DownMbps is { } sd and >= 0
? sd
: _config.HysteriaItem.UpMbps;
var hopInterval = !protocolExtra.HopInterval.IsNullOrEmpty()
? protocolExtra.HopInterval
: (_config.HysteriaItem.HopInterval >= 5
? _config.HysteriaItem.HopInterval
: Global.Hysteria2DefaultHopInt).ToString();
HysteriaUdpHop4Ray? udpHop = null; HysteriaUdpHop4Ray? udpHop = null;
if (node.Ports.IsNotEmpty() && if (!ports.IsNullOrEmpty() &&
(node.Ports.Contains(':') || node.Ports.Contains('-') || node.Ports.Contains(','))) (ports.Contains(':') || ports.Contains('-') || ports.Contains(',')))
{ {
udpHop = new() udpHop = new HysteriaUdpHop4Ray
{ {
ports = node.Ports.Replace(':', '-'), ports = ports.Replace(':', '-'),
interval = _config.HysteriaItem.HopInterval > 0 interval = hopInterval,
? _config.HysteriaItem.HopInterval
: null,
}; };
} }
HysteriaSettings4Ray hysteriaSettings = new() streamSettings.hysteriaSettings = new()
{ {
version = 2, version = 2,
auth = node.Id, auth = node.Password,
up = _config.HysteriaItem.UpMbps > 0 ? $"{_config.HysteriaItem.UpMbps}mbps" : null, up = upMbps > 0 ? $"{upMbps}mbps" : null,
down = _config.HysteriaItem.DownMbps > 0 ? $"{_config.HysteriaItem.DownMbps}mbps" : null, down = downMbps > 0 ? $"{downMbps}mbps" : null,
udphop = udpHop, udphop = udpHop,
}; };
streamSettings.hysteriaSettings = hysteriaSettings; if (!protocolExtra.SalamanderPass.IsNullOrEmpty())
if (node.Path.IsNotEmpty())
{ {
streamSettings.finalmask ??= new(); streamSettings.finalmask ??= new();
streamSettings.finalmask.udp = streamSettings.finalmask.udp =
@ -538,7 +549,7 @@ public partial class CoreConfigV2rayService
new Mask4Ray new Mask4Ray
{ {
type = "salamander", type = "salamander",
settings = new MaskSettings4Ray { password = node.Path.TrimEx(), } settings = new MaskSettings4Ray { password = protocolExtra.SalamanderPass.TrimEx(), }
} }
]; ];
} }
@ -592,13 +603,13 @@ public partial class CoreConfigV2rayService
{ {
return -1; return -1;
} }
var hasCycle = ProfileGroupItemManager.HasCycle(node.IndexId); var hasCycle = await GroupProfileManager.HasCycle(node);
if (hasCycle) if (hasCycle)
{ {
return -1; return -1;
} }
var (childProfiles, profileGroupItem) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId); var (childProfiles, profileExtraItem) = await GroupProfileManager.GetChildProfileItems(node);
if (childProfiles.Count <= 0) if (childProfiles.Count <= 0)
{ {
return -1; return -1;
@ -627,8 +638,9 @@ public partial class CoreConfigV2rayService
//add balancers //add balancers
if (node.ConfigType == EConfigType.PolicyGroup) if (node.ConfigType == EConfigType.PolicyGroup)
{ {
await GenObservatory(v2rayConfig, profileGroupItem.MultipleLoad, baseTagName); var multipleLoad = profileExtraItem?.MultipleLoad ?? EMultipleLoad.LeastPing;
await GenBalancer(v2rayConfig, profileGroupItem.MultipleLoad, baseTagName); await GenObservatory(v2rayConfig, multipleLoad, baseTagName);
await GenBalancer(v2rayConfig, multipleLoad, baseTagName);
} }
} }
catch (Exception ex) catch (Exception ex)
@ -737,7 +749,7 @@ public partial class CoreConfigV2rayService
if (node.ConfigType.IsGroupType()) if (node.ConfigType.IsGroupType())
{ {
var (childProfiles, _) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId); var (childProfiles, _) = await GroupProfileManager.GetChildProfileItems(node);
if (childProfiles.Count <= 0) if (childProfiles.Count <= 0)
{ {
continue; continue;
@ -891,7 +903,7 @@ public partial class CoreConfigV2rayService
if (node.ConfigType.IsGroupType()) if (node.ConfigType.IsGroupType())
{ {
var (childProfiles, _) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId); var (childProfiles, _) = await GroupProfileManager.GetChildProfileItems(node);
if (childProfiles.Count <= 0) if (childProfiles.Count <= 0)
{ {
continue; continue;

View file

@ -79,8 +79,8 @@ public class AddGroupServerViewModel : MyReactiveObject
public async Task Init() public async Task Init()
{ {
ProfileGroupItemManager.Instance.TryGet(SelectedSource.IndexId, out var profileGroup); var protocolExtra = SelectedSource.GetProtocolExtra();
PolicyGroupType = (profileGroup?.MultipleLoad ?? EMultipleLoad.LeastPing) switch PolicyGroupType = (protocolExtra?.MultipleLoad ?? EMultipleLoad.LeastPing) switch
{ {
EMultipleLoad.LeastPing => ResUI.TbLeastPing, EMultipleLoad.LeastPing => ResUI.TbLeastPing,
EMultipleLoad.Fallback => ResUI.TbFallback, EMultipleLoad.Fallback => ResUI.TbFallback,
@ -93,22 +93,18 @@ public class AddGroupServerViewModel : MyReactiveObject
var subs = await AppManager.Instance.SubItems(); var subs = await AppManager.Instance.SubItems();
subs.Add(new SubItem()); subs.Add(new SubItem());
SubItems.AddRange(subs); SubItems.AddRange(subs);
SelectedSubItem = SubItems.Where(s => s.Id == profileGroup?.SubChildItems).FirstOrDefault(); SelectedSubItem = SubItems.FirstOrDefault(s => s.Id == protocolExtra?.SubChildItems);
Filter = profileGroup?.Filter; Filter = protocolExtra?.Filter;
var childItemMulti = ProfileGroupItemManager.Instance.GetOrCreateAndMarkDirty(SelectedSource?.IndexId); var childIndexIds = Utils.String2List(protocolExtra?.ChildItems) ?? [];
if (childItemMulti != null) foreach (var item in childIndexIds)
{ {
var childIndexIds = Utils.String2List(childItemMulti.ChildItems) ?? []; var child = await AppManager.Instance.GetProfileItem(item);
foreach (var item in childIndexIds) if (child == null)
{ {
var child = await AppManager.Instance.GetProfileItem(item); continue;
if (child == null)
{
continue;
}
ChildItemsObs.Add(child);
} }
ChildItemsObs.Add(child);
} }
} }
@ -205,38 +201,32 @@ public class AddGroupServerViewModel : MyReactiveObject
{ {
return; return;
} }
var childIndexIds = new List<string>();
foreach (var item in ChildItemsObs) SelectedSource.SetProtocolExtra(SelectedSource.GetProtocolExtra() with
{ {
if (item.IndexId.IsNullOrEmpty()) ChildItems =
Utils.List2String(ChildItemsObs.Where(s => !s.IndexId.IsNullOrEmpty()).Select(s => s.IndexId).ToList()),
MultipleLoad = PolicyGroupType switch
{ {
continue; var s when s == ResUI.TbLeastPing => EMultipleLoad.LeastPing,
} var s when s == ResUI.TbFallback => EMultipleLoad.Fallback,
childIndexIds.Add(item.IndexId); var s when s == ResUI.TbRandom => EMultipleLoad.Random,
} var s when s == ResUI.TbRoundRobin => EMultipleLoad.RoundRobin,
var profileGroup = ProfileGroupItemManager.Instance.GetOrCreateAndMarkDirty(SelectedSource.IndexId); var s when s == ResUI.TbLeastLoad => EMultipleLoad.LeastLoad,
profileGroup.ChildItems = Utils.List2String(childIndexIds); _ => EMultipleLoad.LeastPing,
profileGroup.MultipleLoad = PolicyGroupType switch },
{ SubChildItems = SelectedSubItem?.Id,
var s when s == ResUI.TbLeastPing => EMultipleLoad.LeastPing, Filter = Filter,
var s when s == ResUI.TbFallback => EMultipleLoad.Fallback, });
var s when s == ResUI.TbRandom => EMultipleLoad.Random,
var s when s == ResUI.TbRoundRobin => EMultipleLoad.RoundRobin,
var s when s == ResUI.TbLeastLoad => EMultipleLoad.LeastLoad,
_ => EMultipleLoad.LeastPing,
};
profileGroup.SubChildItems = SelectedSubItem?.Id; var hasCycle = await GroupProfileManager.HasCycle(SelectedSource.IndexId, SelectedSource.GetProtocolExtra());
profileGroup.Filter = Filter;
var hasCycle = ProfileGroupItemManager.HasCycle(profileGroup.IndexId);
if (hasCycle) if (hasCycle)
{ {
NoticeManager.Instance.Enqueue(string.Format(ResUI.GroupSelfReference, remarks)); NoticeManager.Instance.Enqueue(string.Format(ResUI.GroupSelfReference, remarks));
return; return;
} }
if (await ConfigHandler.AddGroupServerCommon(_config, SelectedSource, profileGroup, true) == 0) if (await ConfigHandler.AddServerCommon(_config, SelectedSource) == 0)
{ {
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess); NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
_updateView?.Invoke(EViewAction.CloseWindow, null); _updateView?.Invoke(EViewAction.CloseWindow, null);

View file

@ -17,6 +17,47 @@ public class AddServerViewModel : MyReactiveObject
[Reactive] [Reactive]
public string CertSha { get; set; } public string CertSha { get; set; }
[Reactive]
public string SalamanderPass { get; set; }
[Reactive]
public int AlterId { get; set; }
[Reactive]
public string Ports { get; set; }
[Reactive]
public int UpMbps { get; set; }
[Reactive]
public int DownMbps { get; set; }
[Reactive]
public string HopInterval { get; set; }
[Reactive]
public string Flow { get; set; }
[Reactive]
public string VmessSecurity { get; set; }
[Reactive]
public string VlessEncryption { get; set; }
[Reactive]
public string SsMethod { get; set; }
[Reactive]
public string WgPublicKey { get; set; }
//[Reactive]
//public string WgPresharedKey { get; set; }
[Reactive]
public string WgInterfaceAddress { get; set; }
[Reactive]
public string WgReserved { get; set; }
[Reactive]
public int WgMtu { get; set; }
public ReactiveCommand<Unit, Unit> FetchCertCmd { get; } public ReactiveCommand<Unit, Unit> FetchCertCmd { get; }
public ReactiveCommand<Unit, Unit> FetchCertChainCmd { get; } public ReactiveCommand<Unit, Unit> FetchCertChainCmd { get; }
public ReactiveCommand<Unit, Unit> SaveCmd { get; } public ReactiveCommand<Unit, Unit> SaveCmd { get; }
@ -63,6 +104,22 @@ public class AddServerViewModel : MyReactiveObject
CoreType = SelectedSource?.CoreType?.ToString(); CoreType = SelectedSource?.CoreType?.ToString();
Cert = SelectedSource?.Cert?.ToString() ?? string.Empty; Cert = SelectedSource?.Cert?.ToString() ?? string.Empty;
CertSha = SelectedSource?.CertSha?.ToString() ?? string.Empty; CertSha = SelectedSource?.CertSha?.ToString() ?? string.Empty;
var protocolExtra = SelectedSource?.GetProtocolExtra();
Ports = protocolExtra?.Ports ?? string.Empty;
AlterId = int.TryParse(protocolExtra?.AlterId, out var result) ? result : 0;
Flow = protocolExtra?.Flow ?? string.Empty;
SalamanderPass = protocolExtra?.SalamanderPass ?? string.Empty;
UpMbps = protocolExtra?.UpMbps ?? _config.HysteriaItem.UpMbps;
DownMbps = protocolExtra?.DownMbps ?? _config.HysteriaItem.DownMbps;
HopInterval = protocolExtra?.HopInterval.IsNullOrEmpty() ?? true ? Global.Hysteria2DefaultHopInt.ToString() : protocolExtra.HopInterval;
VmessSecurity = protocolExtra?.VmessSecurity?.IsNullOrEmpty() == false ? protocolExtra.VmessSecurity : Global.DefaultSecurity;
VlessEncryption = protocolExtra?.VlessEncryption.IsNullOrEmpty() == false ? protocolExtra.VlessEncryption : Global.None;
SsMethod = protocolExtra?.SsMethod ?? string.Empty;
WgPublicKey = protocolExtra?.WgPublicKey ?? string.Empty;
WgInterfaceAddress = protocolExtra?.WgInterfaceAddress ?? string.Empty;
WgReserved = protocolExtra?.WgReserved ?? string.Empty;
WgMtu = protocolExtra?.WgMtu ?? 1280;
} }
private async Task SaveServerAsync() private async Task SaveServerAsync()
@ -87,12 +144,12 @@ public class AddServerViewModel : MyReactiveObject
} }
if (SelectedSource.ConfigType == EConfigType.Shadowsocks) if (SelectedSource.ConfigType == EConfigType.Shadowsocks)
{ {
if (SelectedSource.Id.IsNullOrEmpty()) if (SelectedSource.Password.IsNullOrEmpty())
{ {
NoticeManager.Instance.Enqueue(ResUI.FillPassword); NoticeManager.Instance.Enqueue(ResUI.FillPassword);
return; return;
} }
if (SelectedSource.Security.IsNullOrEmpty()) if (SsMethod.IsNullOrEmpty())
{ {
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectEncryption); NoticeManager.Instance.Enqueue(ResUI.PleaseSelectEncryption);
return; return;
@ -100,7 +157,7 @@ public class AddServerViewModel : MyReactiveObject
} }
if (SelectedSource.ConfigType is not EConfigType.SOCKS and not EConfigType.HTTP) if (SelectedSource.ConfigType is not EConfigType.SOCKS and not EConfigType.HTTP)
{ {
if (SelectedSource.Id.IsNullOrEmpty()) if (SelectedSource.Password.IsNullOrEmpty())
{ {
NoticeManager.Instance.Enqueue(ResUI.FillUUID); NoticeManager.Instance.Enqueue(ResUI.FillUUID);
return; return;
@ -109,6 +166,23 @@ public class AddServerViewModel : MyReactiveObject
SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : (ECoreType)Enum.Parse(typeof(ECoreType), CoreType); SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : (ECoreType)Enum.Parse(typeof(ECoreType), CoreType);
SelectedSource.Cert = Cert.IsNullOrEmpty() ? string.Empty : Cert; SelectedSource.Cert = Cert.IsNullOrEmpty() ? string.Empty : Cert;
SelectedSource.CertSha = CertSha.IsNullOrEmpty() ? string.Empty : CertSha; SelectedSource.CertSha = CertSha.IsNullOrEmpty() ? string.Empty : CertSha;
SelectedSource.SetProtocolExtra(SelectedSource.GetProtocolExtra() with
{
Ports = Ports.NullIfEmpty(),
AlterId = AlterId > 0 ? AlterId.ToString() : null,
Flow = Flow.NullIfEmpty(),
SalamanderPass = SalamanderPass.NullIfEmpty(),
UpMbps = UpMbps >= 0 ? UpMbps : null,
DownMbps = DownMbps >= 0 ? DownMbps : null,
HopInterval = HopInterval.NullIfEmpty(),
VmessSecurity = VmessSecurity.NullIfEmpty(),
VlessEncryption = VlessEncryption.NullIfEmpty(),
SsMethod = SsMethod.NullIfEmpty(),
WgPublicKey = WgPublicKey.NullIfEmpty(),
WgInterfaceAddress = WgInterfaceAddress.NullIfEmpty(),
WgReserved = WgReserved.NullIfEmpty(),
WgMtu = WgMtu >= 576 ? WgMtu : null,
});
if (await ConfigHandler.AddServer(_config, SelectedSource) == 0) if (await ConfigHandler.AddServer(_config, SelectedSource) == 0)
{ {

View file

@ -259,7 +259,6 @@ public class MainWindowViewModel : MyReactiveObject
await ConfigHandler.InitBuiltinDNS(_config); await ConfigHandler.InitBuiltinDNS(_config);
await ConfigHandler.InitBuiltinFullConfigTemplate(_config); await ConfigHandler.InitBuiltinFullConfigTemplate(_config);
await ProfileExManager.Instance.Init(); await ProfileExManager.Instance.Init();
await ProfileGroupItemManager.Instance.Init();
await CoreManager.Instance.Init(_config, UpdateHandler); await CoreManager.Instance.Init(_config, UpdateHandler);
TaskManager.Instance.RegUpdateTask(_config, UpdateTaskHandler); TaskManager.Instance.RegUpdateTask(_config, UpdateTaskHandler);

View file

@ -209,7 +209,7 @@ public class ProfilesSelectViewModel : MyReactiveObject
Remarks = t.Remarks, Remarks = t.Remarks,
Address = t.Address, Address = t.Address,
Port = t.Port, Port = t.Port,
Security = t.Security, //Security = t.Security,
Network = t.Network, Network = t.Network,
StreamSecurity = t.StreamSecurity, StreamSecurity = t.StreamSecurity,
Subid = t.Subid, Subid = t.Subid,

View file

@ -446,7 +446,7 @@ public class ProfilesViewModel : MyReactiveObject
Remarks = t.Remarks, Remarks = t.Remarks,
Address = t.Address, Address = t.Address,
Port = t.Port, Port = t.Port,
Security = t.Security, //Security = t.Security,
Network = t.Network, Network = t.Network,
StreamSecurity = t.StreamSecurity, StreamSecurity = t.StreamSecurity,
Subid = t.Subid, Subid = t.Subid,

View file

@ -360,7 +360,7 @@
Grid.Row="2" Grid.Row="2"
ColumnDefinitions="300,Auto,Auto" ColumnDefinitions="300,Auto,Auto"
IsVisible="False" IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto"> RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto">
<TextBlock <TextBlock
Grid.Row="1" Grid.Row="1"
@ -407,6 +407,41 @@
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbPorts7Tips}" /> Text="{x:Static resx:ResUI.TbPorts7Tips}" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbHopInt7}" />
<TextBox
x:Name="txtHopInt7"
Grid.Row="4"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsHysteriaBandwidth}" />
<StackPanel
Grid.Row="5"
Grid.Column="1"
Orientation="Horizontal">
<TextBox
x:Name="txtUpMbps7"
Width="90"
Margin="{StaticResource Margin4}"
Watermark="Up" />
<TextBox
x:Name="txtDownMbps7"
Width="90"
Margin="{StaticResource Margin4}"
Watermark="Down" />
</StackPanel>
</Grid> </Grid>
<Grid <Grid
x:Name="gridTuic" x:Name="gridTuic"

View file

@ -39,10 +39,6 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
case EConfigType.VMess: case EConfigType.VMess:
gridVMess.IsVisible = true; gridVMess.IsVisible = true;
cmbSecurity.ItemsSource = Global.VmessSecurities; cmbSecurity.ItemsSource = Global.VmessSecurities;
if (profileItem.Security.IsNullOrEmpty())
{
profileItem.Security = Global.DefaultSecurity;
}
break; break;
case EConfigType.Shadowsocks: case EConfigType.Shadowsocks:
@ -59,10 +55,6 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
gridVLESS.IsVisible = true; gridVLESS.IsVisible = true;
lstStreamSecurity.Add(Global.StreamSecurityReality); lstStreamSecurity.Add(Global.StreamSecurityReality);
cmbFlow5.ItemsSource = Global.Flows; cmbFlow5.ItemsSource = Global.Flows;
if (profileItem.Security.IsNullOrEmpty())
{
profileItem.Security = Global.None;
}
break; break;
case EConfigType.Trojan: case EConfigType.Trojan:
@ -119,59 +111,62 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
switch (profileItem.ConfigType) switch (profileItem.ConfigType)
{ {
case EConfigType.VMess: case EConfigType.VMess:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.AlterId, v => v.txtAlterId.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.AlterId, v => v.txtAlterId.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.cmbSecurity.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.VmessSecurity, v => v.cmbSecurity.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled.IsChecked).DisposeWith(disposables);
break; break;
case EConfigType.Shadowsocks: case EConfigType.Shadowsocks:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId3.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId3.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.cmbSecurity3.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SsMethod, v => v.cmbSecurity3.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled3.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled3.IsChecked).DisposeWith(disposables);
break; break;
case EConfigType.SOCKS: case EConfigType.SOCKS:
case EConfigType.HTTP: case EConfigType.HTTP:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId4.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId4.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity4.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtSecurity4.Text).DisposeWith(disposables);
break; break;
case EConfigType.VLESS: case EConfigType.VLESS:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId5.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId5.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Flow, v => v.cmbFlow5.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Flow, v => v.cmbFlow5.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity5.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.VlessEncryption, v => v.txtSecurity5.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled5.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled5.IsChecked).DisposeWith(disposables);
break; break;
case EConfigType.Trojan: case EConfigType.Trojan:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId6.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId6.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Flow, v => v.cmbFlow6.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Flow, v => v.cmbFlow6.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled6.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled6.IsChecked).DisposeWith(disposables);
break; break;
case EConfigType.Hysteria2: case EConfigType.Hysteria2:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId7.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Path, v => v.txtPath7.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SalamanderPass, v => v.txtPath7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Ports, v => v.txtPorts7.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Ports, v => v.txtPorts7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.HopInterval, v => v.txtHopInt7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.UpMbps, v => v.txtUpMbps7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.DownMbps, v => v.txtDownMbps7.Text).DisposeWith(disposables);
break; break;
case EConfigType.TUIC: case EConfigType.TUIC:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId8.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtId8.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity8.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtSecurity8.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType8.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType8.SelectedValue).DisposeWith(disposables);
break; break;
case EConfigType.WireGuard: case EConfigType.WireGuard:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId9.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.PublicKey, v => v.txtPublicKey9.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.WgPublicKey, v => v.txtPublicKey9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Path, v => v.txtPath9.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.WgReserved, v => v.txtPath9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.RequestHost, v => v.txtRequestHost9.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.WgInterfaceAddress, v => v.txtRequestHost9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.ShortId, v => v.txtShortId9.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.WgMtu, v => v.txtShortId9.Text).DisposeWith(disposables);
break; break;
case EConfigType.Anytls: case EConfigType.Anytls:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId10.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId10.Text).DisposeWith(disposables);
break; break;
} }
this.Bind(ViewModel, vm => vm.SelectedSource.Network, v => v.cmbNetwork.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Network, v => v.cmbNetwork.SelectedValue).DisposeWith(disposables);

View file

@ -487,6 +487,8 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="300" /> <ColumnDefinition Width="300" />
@ -547,6 +549,47 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}" Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbPorts7Tips}" /> Text="{x:Static resx:ResUI.TbPorts7Tips}" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbHopInt7}" />
<TextBox
x:Name="txtHopInt7"
Grid.Row="4"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
Style="{StaticResource DefTextBox}" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbSettingsHysteriaBandwidth}" />
<StackPanel
Grid.Row="5"
Grid.Column="1"
Orientation="Horizontal">
<TextBox
x:Name="txtUpMbps7"
Width="90"
Margin="{StaticResource Margin4}"
materialDesign:HintAssist.Hint="Up"
Style="{StaticResource DefTextBox}" />
<TextBox
x:Name="txtDownMbps7"
Width="90"
Margin="{StaticResource Margin4}"
materialDesign:HintAssist.Hint="Down"
Style="{StaticResource DefTextBox}" />
</StackPanel>
</Grid> </Grid>
<Grid <Grid
x:Name="gridTuic" x:Name="gridTuic"

View file

@ -34,10 +34,6 @@ public partial class AddServerWindow
case EConfigType.VMess: case EConfigType.VMess:
gridVMess.Visibility = Visibility.Visible; gridVMess.Visibility = Visibility.Visible;
cmbSecurity.ItemsSource = Global.VmessSecurities; cmbSecurity.ItemsSource = Global.VmessSecurities;
if (profileItem.Security.IsNullOrEmpty())
{
profileItem.Security = Global.DefaultSecurity;
}
break; break;
case EConfigType.Shadowsocks: case EConfigType.Shadowsocks:
@ -54,10 +50,6 @@ public partial class AddServerWindow
gridVLESS.Visibility = Visibility.Visible; gridVLESS.Visibility = Visibility.Visible;
lstStreamSecurity.Add(Global.StreamSecurityReality); lstStreamSecurity.Add(Global.StreamSecurityReality);
cmbFlow5.ItemsSource = Global.Flows; cmbFlow5.ItemsSource = Global.Flows;
if (profileItem.Security.IsNullOrEmpty())
{
profileItem.Security = Global.None;
}
break; break;
case EConfigType.Trojan: case EConfigType.Trojan:
@ -114,59 +106,62 @@ public partial class AddServerWindow
switch (profileItem.ConfigType) switch (profileItem.ConfigType)
{ {
case EConfigType.VMess: case EConfigType.VMess:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.AlterId, v => v.txtAlterId.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.AlterId, v => v.txtAlterId.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.cmbSecurity.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.VmessSecurity, v => v.cmbSecurity.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled.IsChecked).DisposeWith(disposables);
break; break;
case EConfigType.Shadowsocks: case EConfigType.Shadowsocks:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId3.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId3.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.cmbSecurity3.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SsMethod, v => v.cmbSecurity3.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled3.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled3.IsChecked).DisposeWith(disposables);
break; break;
case EConfigType.SOCKS: case EConfigType.SOCKS:
case EConfigType.HTTP: case EConfigType.HTTP:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId4.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId4.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity4.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtSecurity4.Text).DisposeWith(disposables);
break; break;
case EConfigType.VLESS: case EConfigType.VLESS:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId5.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId5.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Flow, v => v.cmbFlow5.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Flow, v => v.cmbFlow5.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity5.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.VlessEncryption, v => v.txtSecurity5.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled5.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled5.IsChecked).DisposeWith(disposables);
break; break;
case EConfigType.Trojan: case EConfigType.Trojan:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId6.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId6.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Flow, v => v.cmbFlow6.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Flow, v => v.cmbFlow6.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled6.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled6.IsChecked).DisposeWith(disposables);
break; break;
case EConfigType.Hysteria2: case EConfigType.Hysteria2:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId7.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Path, v => v.txtPath7.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SalamanderPass, v => v.txtPath7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Ports, v => v.txtPorts7.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Ports, v => v.txtPorts7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.HopInterval, v => v.txtHopInt7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.UpMbps, v => v.txtUpMbps7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.DownMbps, v => v.txtDownMbps7.Text).DisposeWith(disposables);
break; break;
case EConfigType.TUIC: case EConfigType.TUIC:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId8.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtId8.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity8.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtSecurity8.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType8.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType8.Text).DisposeWith(disposables);
break; break;
case EConfigType.WireGuard: case EConfigType.WireGuard:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId9.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.PublicKey, v => v.txtPublicKey9.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.WgPublicKey, v => v.txtPublicKey9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Path, v => v.txtPath9.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.WgReserved, v => v.txtPath9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.RequestHost, v => v.txtRequestHost9.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.WgInterfaceAddress, v => v.txtRequestHost9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.ShortId, v => v.txtShortId9.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.WgMtu, v => v.txtShortId9.Text).DisposeWith(disposables);
break; break;
case EConfigType.Anytls: case EConfigType.Anytls:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId10.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId10.Text).DisposeWith(disposables);
break; break;
} }
this.Bind(ViewModel, vm => vm.SelectedSource.Network, v => v.cmbNetwork.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Network, v => v.cmbNetwork.Text).DisposeWith(disposables);