v2rayN/v2rayN/ServiceLib/Handler/ConfigHandler.cs

1852 lines
62 KiB
C#

using System.Data;
using System.Text.RegularExpressions;
namespace ServiceLib.Handler
{
/// <summary>
/// 本软件配置文件处理类
/// </summary>
public class ConfigHandler
{
private static readonly string _configRes = Global.ConfigFileName;
private static readonly object _objLock = new();
#region ConfigHandler
/// <summary>
/// 载入配置文件
/// </summary>
/// <param name="config"></param>
/// <returns></returns>
public static Config? LoadConfig()
{
Config? config = null;
var result = Utils.LoadResource(Utils.GetConfigPath(_configRes));
if (Utils.IsNotEmpty(result))
{
config = JsonUtils.Deserialize<Config>(result);
}
else
{
if (File.Exists(Utils.GetConfigPath(_configRes)))
{
Logging.SaveLog("LoadConfig Exception");
return null;
}
}
config ??= new Config();
config.CoreBasicItem ??= new()
{
LogEnabled = false,
Loglevel = "warning",
MuxEnabled = false,
};
if (config.Inbound == null)
{
config.Inbound = new List<InItem>();
InItem inItem = new()
{
Protocol = EInboundProtocol.socks.ToString(),
LocalPort = 10808,
UdpEnabled = true,
SniffingEnabled = true,
RouteOnly = false,
};
config.Inbound.Add(inItem);
}
else
{
if (config.Inbound.Count > 0)
{
config.Inbound[0].Protocol = EInboundProtocol.socks.ToString();
}
}
config.RoutingBasicItem ??= new()
{
EnableRoutingAdvanced = true
};
if (Utils.IsNullOrEmpty(config.RoutingBasicItem.DomainStrategy))
{
config.RoutingBasicItem.DomainStrategy = Global.DomainStrategies[0];//"IPIfNonMatch";
}
config.KcpItem ??= new KcpItem
{
Mtu = 1350,
Tti = 50,
UplinkCapacity = 12,
DownlinkCapacity = 100,
ReadBufferSize = 2,
WriteBufferSize = 2,
Congestion = false
};
config.GrpcItem ??= new GrpcItem
{
IdleTimeout = 60,
HealthCheckTimeout = 20,
PermitWithoutStream = false,
InitialWindowsSize = 0,
};
config.TunModeItem ??= new TunModeItem
{
EnableTun = false,
Mtu = 9000,
};
config.GuiItem ??= new()
{
EnableStatistics = false,
};
config.MsgUIItem ??= new();
config.UiItem ??= new UIItem()
{
EnableAutoAdjustMainLvColWidth = true
};
config.UiItem.MainColumnItem ??= new();
if (Utils.IsNullOrEmpty(config.UiItem.CurrentLanguage))
{
if (Thread.CurrentThread.CurrentCulture.Name.Equals("zh-cn", StringComparison.CurrentCultureIgnoreCase))
{
config.UiItem.CurrentLanguage = Global.Languages[0];
}
else
{
config.UiItem.CurrentLanguage = Global.Languages[2];
}
}
config.ConstItem ??= new ConstItem();
if (Utils.IsNullOrEmpty(config.ConstItem.DefIEProxyExceptions))
{
config.ConstItem.DefIEProxyExceptions = Global.IEProxyExceptions;
}
config.SpeedTestItem ??= new();
if (config.SpeedTestItem.SpeedTestTimeout < 10)
{
config.SpeedTestItem.SpeedTestTimeout = 10;
}
if (Utils.IsNullOrEmpty(config.SpeedTestItem.SpeedTestUrl))
{
config.SpeedTestItem.SpeedTestUrl = Global.SpeedTestUrls[0];
}
if (Utils.IsNullOrEmpty(config.SpeedTestItem.SpeedPingTestUrl))
{
config.SpeedTestItem.SpeedPingTestUrl = Global.SpeedPingTestUrl;
}
config.Mux4RayItem ??= new()
{
Concurrency = 8,
XudpConcurrency = 16,
XudpProxyUDP443 = "reject"
};
config.Mux4SboxItem ??= new()
{
Protocol = Global.SingboxMuxs[0],
MaxConnections = 8
};
config.HysteriaItem ??= new()
{
UpMbps = 100,
DownMbps = 100
};
config.ClashUIItem ??= new();
config.SystemProxyItem ??= new();
config.WebDavItem ??= new();
return config;
}
/// <summary>
/// 保参数
/// </summary>
/// <param name="config"></param>
/// <returns></returns>
public static async Task<int> SaveConfig(Config config, bool reload = true)
{
await ToJsonFile(config);
return 0;
}
/// <summary>
/// 存储文件
/// </summary>
/// <param name="config"></param>
private static async Task ToJsonFile(Config config)
{
lock (_objLock)
{
try
{
//save temp file
var resPath = Utils.GetConfigPath(_configRes);
var tempPath = $"{resPath}_temp";
if (JsonUtils.ToFile(config, tempPath) != 0)
{
return;
}
if (File.Exists(resPath))
{
File.Delete(resPath);
}
//rename
File.Move(tempPath, resPath);
}
catch (Exception ex)
{
Logging.SaveLog("ToJsonFile", ex);
}
}
}
#endregion ConfigHandler
#region Server
public static async Task<int> AddServer(Config config, ProfileItem profileItem)
{
var item = await AppHandler.Instance.GetProfileItem(profileItem.IndexId);
if (item is null)
{
item = profileItem;
}
else
{
item.CoreType = profileItem.CoreType;
item.Remarks = profileItem.Remarks;
item.Address = profileItem.Address;
item.Port = profileItem.Port;
item.Id = profileItem.Id;
item.AlterId = profileItem.AlterId;
item.Security = profileItem.Security;
item.Flow = profileItem.Flow;
item.Network = profileItem.Network;
item.HeaderType = profileItem.HeaderType;
item.RequestHost = profileItem.RequestHost;
item.Path = profileItem.Path;
item.StreamSecurity = profileItem.StreamSecurity;
item.Sni = profileItem.Sni;
item.AllowInsecure = profileItem.AllowInsecure;
item.Fingerprint = profileItem.Fingerprint;
item.Alpn = profileItem.Alpn;
item.PublicKey = profileItem.PublicKey;
item.ShortId = profileItem.ShortId;
item.SpiderX = profileItem.SpiderX;
}
var ret = item.ConfigType switch
{
EConfigType.VMess => await AddVMessServer(config, item),
EConfigType.Shadowsocks => await AddShadowsocksServer(config, item),
EConfigType.SOCKS => await AddSocksServer(config, item),
EConfigType.HTTP => await AddHttpServer(config, item),
EConfigType.Trojan => await AddTrojanServer(config, item),
EConfigType.VLESS => await AddVlessServer(config, item),
EConfigType.Hysteria2 => await AddHysteria2Server(config, item),
EConfigType.TUIC => await AddTuicServer(config, item),
EConfigType.WireGuard => await AddWireguardServer(config, item),
_ => -1,
};
return ret;
}
/// <summary>
/// Add or edit server
/// </summary>
/// <param name="config"></param>
/// <param name="profileItem"></param>
/// <returns></returns>
public static async Task<int> AddVMessServer(Config config, ProfileItem profileItem, bool toFile = true)
{
profileItem.ConfigType = EConfigType.VMess;
profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx();
profileItem.Security = profileItem.Security.TrimEx();
profileItem.Network = profileItem.Network.TrimEx();
profileItem.HeaderType = profileItem.HeaderType.TrimEx();
profileItem.RequestHost = profileItem.RequestHost.TrimEx();
profileItem.Path = profileItem.Path.TrimEx();
profileItem.StreamSecurity = profileItem.StreamSecurity.TrimEx();
if (!Global.VmessSecurities.Contains(profileItem.Security))
{
return -1;
}
if (profileItem.Id.IsNullOrEmpty())
{
return -1;
}
await AddServerCommon(config, profileItem, toFile);
return 0;
}
/// <summary>
/// 移除服务器
/// </summary>
/// <param name="config"></param>
/// <param name="indexes"></param>
/// <returns></returns>
public static async Task<int> RemoveServer(Config config, List<ProfileItem> indexes)
{
var subid = "TempRemoveSubId";
foreach (var item in indexes)
{
item.Subid = subid;
}
await SQLiteHelper.Instance.UpdateAllAsync(indexes);
await RemoveServerViaSubid(config, subid, false);
return 0;
}
/// <summary>
/// 克隆服务器
/// </summary>
/// <param name="config"></param>
/// <param name="index"></param>
/// <returns></returns>
public static async Task<int> CopyServer(Config config, List<ProfileItem> indexes)
{
foreach (var it in indexes)
{
var item = await AppHandler.Instance.GetProfileItem(it.IndexId);
if (item is null)
{
continue;
}
var profileItem = JsonUtils.DeepCopy(item);
profileItem.IndexId = string.Empty;
profileItem.Remarks = $"{item.Remarks}-clone";
if (profileItem.ConfigType == EConfigType.Custom)
{
profileItem.Address = Utils.GetConfigPath(profileItem.Address);
if (await AddCustomServer(config, profileItem, false) == 0)
{
}
}
else
{
await AddServerCommon(config, profileItem, true);
}
}
return 0;
}
/// <summary>
/// 设置活动服务器
/// </summary>
/// <param name="config"></param>
/// <param name="item"></param>
/// <returns></returns>
public static async Task<int> SetDefaultServerIndex(Config config, string? indexId)
{
if (Utils.IsNullOrEmpty(indexId))
{
return -1;
}
config.IndexId = indexId;
await ToJsonFile(config);
return 0;
}
public static async Task<int> SetDefaultServer(Config config, List<ProfileItemModel> lstProfile)
{
if (lstProfile.Exists(t => t.IndexId == config.IndexId))
{
return 0;
}
var count = await SQLiteHelper.Instance.TableAsync<ProfileItem>().CountAsync(t => t.IndexId == config.IndexId);
if (count > 0)
{
return 0;
}
if (lstProfile.Count > 0)
{
return await SetDefaultServerIndex(config, lstProfile.FirstOrDefault(t => t.Port > 0)?.IndexId);
}
var item = await SQLiteHelper.Instance.TableAsync<ProfileItem>().Where(t => t.Port > 0).FirstOrDefaultAsync();
return await SetDefaultServerIndex(config, item.IndexId);
}
public static async Task<ProfileItem?> GetDefaultServer(Config config)
{
var item = await AppHandler.Instance.GetProfileItem(config.IndexId);
if (item is null)
{
var item2 = await SQLiteHelper.Instance.TableAsync<ProfileItem>().FirstOrDefaultAsync();
await SetDefaultServerIndex(config, item2?.IndexId);
return item2;
}
return item;
}
/// <summary>
/// 移动服务器
/// </summary>
/// <param name="config"></param>
/// <param name="lstProfile"></param>
/// <param name="index"></param>
/// <param name="eMove"></param>
/// <returns></returns>
public static async Task<int> MoveServer(Config config, List<ProfileItem> lstProfile, int index, EMove eMove, int pos = -1)
{
int count = lstProfile.Count;
if (index < 0 || index > lstProfile.Count - 1)
{
return -1;
}
for (int i = 0; i < lstProfile.Count; i++)
{
ProfileExHandler.Instance.SetSort(lstProfile[i].IndexId, (i + 1) * 10);
}
var sort = 0;
switch (eMove)
{
case EMove.Top:
{
if (index == 0)
{
return 0;
}
sort = ProfileExHandler.Instance.GetSort(lstProfile[0].IndexId) - 1;
break;
}
case EMove.Up:
{
if (index == 0)
{
return 0;
}
sort = ProfileExHandler.Instance.GetSort(lstProfile[index - 1].IndexId) - 1;
break;
}
case EMove.Down:
{
if (index == count - 1)
{
return 0;
}
sort = ProfileExHandler.Instance.GetSort(lstProfile[index + 1].IndexId) + 1;
break;
}
case EMove.Bottom:
{
if (index == count - 1)
{
return 0;
}
sort = ProfileExHandler.Instance.GetSort(lstProfile[^1].IndexId) + 1;
break;
}
case EMove.Position:
sort = pos * 10 + 1;
break;
}
ProfileExHandler.Instance.SetSort(lstProfile[index].IndexId, sort);
return 0;
}
/// <summary>
/// 添加自定义服务器
/// </summary>
/// <param name="config"></param>
/// <param name="profileItem"></param>
/// <returns></returns>
public static async Task<int> AddCustomServer(Config config, ProfileItem profileItem, bool blDelete)
{
var fileName = profileItem.Address;
if (!File.Exists(fileName))
{
return -1;
}
var ext = Path.GetExtension(fileName);
string newFileName = $"{Utils.GetGuid()}{ext}";
//newFileName = Path.Combine(Utile.GetTempPath(), newFileName);
try
{
File.Copy(fileName, Utils.GetConfigPath(newFileName));
if (blDelete)
{
File.Delete(fileName);
}
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
return -1;
}
profileItem.Address = newFileName;
profileItem.ConfigType = EConfigType.Custom;
if (Utils.IsNullOrEmpty(profileItem.Remarks))
{
profileItem.Remarks = $"import custom@{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")}";
}
await AddServerCommon(config, profileItem, true);
return 0;
}
/// <summary>
/// Add or edit server
/// </summary>
/// <param name="config"></param>
/// <param name="profileItem"></param>
/// <returns></returns>
public static async Task<int> EditCustomServer(Config config, ProfileItem profileItem)
{
var item = await AppHandler.Instance.GetProfileItem(profileItem.IndexId);
if (item is null)
{
item = profileItem;
}
else
{
item.Remarks = profileItem.Remarks;
item.Address = profileItem.Address;
item.CoreType = profileItem.CoreType;
item.DisplayLog = profileItem.DisplayLog;
item.PreSocksPort = profileItem.PreSocksPort;
}
if (await SQLiteHelper.Instance.UpdateAsync(item) > 0)
{
return 0;
}
else
{
return -1;
}
//ToJsonFile(config);
}
/// <summary>
/// Add or edit server
/// </summary>
/// <param name="config"></param>
/// <param name="profileItem"></param>
/// <returns></returns>
public static async Task<int> AddShadowsocksServer(Config config, ProfileItem profileItem, bool toFile = true)
{
profileItem.ConfigType = EConfigType.Shadowsocks;
profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx();
profileItem.Security = profileItem.Security.TrimEx();
if (!AppHandler.Instance.GetShadowsocksSecurities(profileItem).Contains(profileItem.Security))
{
return -1;
}
if (profileItem.Id.IsNullOrEmpty())
{
return -1;
}
await AddServerCommon(config, profileItem, toFile);
return 0;
}
/// <summary>
/// Add or edit server
/// </summary>
/// <param name="config"></param>
/// <param name="profileItem"></param>
/// <returns></returns>
public static async Task<int> AddSocksServer(Config config, ProfileItem profileItem, bool toFile = true)
{
profileItem.ConfigType = EConfigType.SOCKS;
profileItem.Address = profileItem.Address.TrimEx();
await AddServerCommon(config, profileItem, toFile);
return 0;
}
/// <summary>
/// Add or edit server
/// </summary>
/// <param name="config"></param>
/// <param name="profileItem"></param>
/// <returns></returns>
public static async Task<int> AddHttpServer(Config config, ProfileItem profileItem, bool toFile = true)
{
profileItem.ConfigType = EConfigType.HTTP;
profileItem.Address = profileItem.Address.TrimEx();
await AddServerCommon(config, profileItem, toFile);
return 0;
}
/// <summary>
/// Add or edit server
/// </summary>
/// <param name="config"></param>
/// <param name="profileItem"></param>
/// <returns></returns>
public static async Task<int> AddTrojanServer(Config config, ProfileItem profileItem, bool toFile = true)
{
profileItem.ConfigType = EConfigType.Trojan;
profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx();
if (Utils.IsNullOrEmpty(profileItem.StreamSecurity))
{
profileItem.StreamSecurity = Global.StreamSecurity;
}
if (profileItem.Id.IsNullOrEmpty())
{
return -1;
}
await AddServerCommon(config, profileItem, toFile);
return 0;
}
/// <summary>
/// Add or edit server
/// </summary>
/// <param name="config"></param>
/// <param name="profileItem"></param>
/// <returns></returns>
public static async Task<int> AddHysteria2Server(Config config, ProfileItem profileItem, bool toFile = true)
{
profileItem.ConfigType = EConfigType.Hysteria2;
profileItem.CoreType = ECoreType.sing_box;
profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx();
profileItem.Path = profileItem.Path.TrimEx();
profileItem.Network = string.Empty;
if (Utils.IsNullOrEmpty(profileItem.StreamSecurity))
{
profileItem.StreamSecurity = Global.StreamSecurity;
}
if (profileItem.Id.IsNullOrEmpty())
{
return -1;
}
await AddServerCommon(config, profileItem, toFile);
return 0;
}
/// <summary>
/// Add or edit server
/// </summary>
/// <param name="config"></param>
/// <param name="profileItem"></param>
/// <returns></returns>
public static async Task<int> AddTuicServer(Config config, ProfileItem profileItem, bool toFile = true)
{
profileItem.ConfigType = EConfigType.TUIC;
profileItem.CoreType = ECoreType.sing_box;
profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx();
profileItem.Security = profileItem.Security.TrimEx();
profileItem.Network = string.Empty;
if (!Global.TuicCongestionControls.Contains(profileItem.HeaderType))
{
profileItem.HeaderType = Global.TuicCongestionControls.FirstOrDefault()!;
}
if (Utils.IsNullOrEmpty(profileItem.StreamSecurity))
{
profileItem.StreamSecurity = Global.StreamSecurity;
}
if (Utils.IsNullOrEmpty(profileItem.Alpn))
{
profileItem.Alpn = "h3";
}
if (profileItem.Id.IsNullOrEmpty())
{
return -1;
}
await AddServerCommon(config, profileItem, toFile);
return 0;
}
/// <summary>
/// Add or edit server
/// </summary>
/// <param name="config"></param>
/// <param name="profileItem"></param>
/// <returns></returns>
public static async Task<int> AddWireguardServer(Config config, ProfileItem profileItem, bool toFile = true)
{
profileItem.ConfigType = EConfigType.WireGuard;
profileItem.CoreType = ECoreType.sing_box;
profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx();
profileItem.PublicKey = profileItem.PublicKey.TrimEx();
profileItem.Path = profileItem.Path.TrimEx();
profileItem.RequestHost = profileItem.RequestHost.TrimEx();
profileItem.Network = string.Empty;
if (profileItem.ShortId.IsNullOrEmpty())
{
profileItem.ShortId = Global.TunMtus.FirstOrDefault();
}
if (profileItem.Id.IsNullOrEmpty())
{
return -1;
}
await AddServerCommon(config, profileItem, toFile);
return 0;
}
public static async Task<int> SortServers(Config config, string subId, string colName, bool asc)
{
var lstModel = await AppHandler.Instance.ProfileItems(subId, "");
if (lstModel.Count <= 0)
{
return -1;
}
var lstProfileExs = ProfileExHandler.Instance.ProfileExs;
var lstProfile = (from t in lstModel
join t3 in lstProfileExs on t.IndexId equals t3.IndexId into t3b
from t33 in t3b.DefaultIfEmpty()
select new ProfileItemModel
{
IndexId = t.IndexId,
ConfigType = t.ConfigType,
Remarks = t.Remarks,
Address = t.Address,
Port = t.Port,
Security = t.Security,
Network = t.Network,
StreamSecurity = t.StreamSecurity,
Delay = t33 == null ? 0 : t33.Delay,
Speed = t33 == null ? 0 : t33.Speed,
Sort = t33 == null ? 0 : t33.Sort
}).ToList();
Enum.TryParse(colName, true, out EServerColName name);
var propertyName = string.Empty;
switch (name)
{
case EServerColName.ConfigType:
case EServerColName.Remarks:
case EServerColName.Address:
case EServerColName.Port:
case EServerColName.Network:
case EServerColName.StreamSecurity:
propertyName = name.ToString();
break;
case EServerColName.DelayVal:
propertyName = "Delay";
break;
case EServerColName.SpeedVal:
propertyName = "Speed";
break;
case EServerColName.SubRemarks:
propertyName = "Subid";
break;
default:
return -1;
}
var items = lstProfile.AsQueryable();
if (asc)
{
lstProfile = items.OrderBy(propertyName).ToList();
}
else
{
lstProfile = items.OrderByDescending(propertyName).ToList();
}
for (int i = 0; i < lstProfile.Count; i++)
{
ProfileExHandler.Instance.SetSort(lstProfile[i].IndexId, (i + 1) * 10);
}
if (name == EServerColName.DelayVal)
{
var maxSort = lstProfile.Max(t => t.Sort) + 10;
foreach (var item in lstProfile)
{
if (item.Delay <= 0)
{
ProfileExHandler.Instance.SetSort(item.IndexId, maxSort);
}
}
}
if (name == EServerColName.SpeedVal)
{
var maxSort = lstProfile.Max(t => t.Sort) + 10;
foreach (var item in lstProfile)
{
if (item.Speed <= 0)
{
ProfileExHandler.Instance.SetSort(item.IndexId, maxSort);
}
}
}
return 0;
}
/// <summary>
/// Add or edit server
/// </summary>
/// <param name="config"></param>
/// <param name="profileItem"></param>
/// <returns></returns>
public static async Task<int> AddVlessServer(Config config, ProfileItem profileItem, bool toFile = true)
{
profileItem.ConfigType = EConfigType.VLESS;
profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx();
profileItem.Security = profileItem.Security.TrimEx();
profileItem.Network = profileItem.Network.TrimEx();
profileItem.HeaderType = profileItem.HeaderType.TrimEx();
profileItem.RequestHost = profileItem.RequestHost.TrimEx();
profileItem.Path = profileItem.Path.TrimEx();
profileItem.StreamSecurity = profileItem.StreamSecurity.TrimEx();
if (!Global.Flows.Contains(profileItem.Flow))
{
profileItem.Flow = Global.Flows.First();
}
if (profileItem.Id.IsNullOrEmpty())
{
return -1;
}
if (Utils.IsNotEmpty(profileItem.Security) && profileItem.Security != Global.None)
{
profileItem.Security = Global.None;
}
await AddServerCommon(config, profileItem, toFile);
return 0;
}
public static async Task<Tuple<int, int>> DedupServerList(Config config, string subId)
{
var lstProfile = await AppHandler.Instance.ProfileItems(subId);
List<ProfileItem> lstKeep = new();
List<ProfileItem> lstRemove = new();
if (!config.GuiItem.KeepOlderDedupl) lstProfile.Reverse();
foreach (ProfileItem item in lstProfile)
{
if (!lstKeep.Exists(i => CompareProfileItem(i, item, false)))
{
lstKeep.Add(item);
}
else
{
lstRemove.Add(item);
}
}
await RemoveServer(config, lstRemove);
return new Tuple<int, int>(lstProfile.Count, lstKeep.Count);
}
public static async Task<int> AddServerCommon(Config config, ProfileItem profileItem, bool toFile = true)
{
profileItem.ConfigVersion = 2;
if (Utils.IsNotEmpty(profileItem.StreamSecurity))
{
if (profileItem.StreamSecurity != Global.StreamSecurity
&& profileItem.StreamSecurity != Global.StreamSecurityReality)
{
profileItem.StreamSecurity = string.Empty;
}
else
{
if (Utils.IsNullOrEmpty(profileItem.AllowInsecure))
{
profileItem.AllowInsecure = config.CoreBasicItem.DefAllowInsecure.ToString().ToLower();
}
if (Utils.IsNullOrEmpty(profileItem.Fingerprint) && profileItem.StreamSecurity == Global.StreamSecurityReality)
{
profileItem.Fingerprint = config.CoreBasicItem.DefFingerprint;
}
}
}
if (Utils.IsNotEmpty(profileItem.Network) && !Global.Networks.Contains(profileItem.Network))
{
profileItem.Network = Global.DefaultNetwork;
}
var maxSort = -1;
if (Utils.IsNullOrEmpty(profileItem.IndexId))
{
profileItem.IndexId = Utils.GetGuid(false);
maxSort = ProfileExHandler.Instance.GetMaxSort();
}
if (!toFile && maxSort < 0)
{
maxSort = ProfileExHandler.Instance.GetMaxSort();
}
if (maxSort > 0)
{
ProfileExHandler.Instance.SetSort(profileItem.IndexId, maxSort + 1);
}
if (toFile)
{
await SQLiteHelper.Instance.ReplaceAsync(profileItem);
}
return 0;
}
private static bool CompareProfileItem(ProfileItem o, ProfileItem n, bool remarks)
{
if (o == null || n == null)
{
return false;
}
return o.ConfigType == n.ConfigType
&& o.Address == n.Address
&& o.Port == n.Port
&& o.Id == n.Id
&& o.AlterId == n.AlterId
&& o.Security == n.Security
&& o.Network == n.Network
&& o.HeaderType == n.HeaderType
&& o.RequestHost == n.RequestHost
&& o.Path == n.Path
&& (o.ConfigType == EConfigType.Trojan || o.StreamSecurity == n.StreamSecurity)
&& o.Flow == n.Flow
&& o.Sni == n.Sni
&& (!remarks || o.Remarks == n.Remarks);
}
private static async Task<int> RemoveProfileItem(Config config, string indexId)
{
try
{
var item = await AppHandler.Instance.GetProfileItem(indexId);
if (item == null)
{
return 0;
}
if (item.ConfigType == EConfigType.Custom)
{
File.Delete(Utils.GetConfigPath(item.Address));
}
await SQLiteHelper.Instance.DeleteAsync(item);
}
catch (Exception ex)
{
Logging.SaveLog("Remove Item", ex);
}
return 0;
}
public static async Task<RetResult> AddCustomServer4Multiple(Config config, List<ProfileItem> selecteds, ECoreType coreType)
{
var indexId = Utils.GetMd5(Global.CoreMultipleLoadConfigFileName);
var configPath = Utils.GetConfigPath(Global.CoreMultipleLoadConfigFileName);
var result = await CoreConfigHandler.GenerateClientMultipleLoadConfig(config, configPath, selecteds, coreType);
if (result.Success != true)
{
return result;
}
var fileName = configPath;
if (!File.Exists(fileName))
{
return result;
}
var profileItem = await AppHandler.Instance.GetProfileItem(indexId) ?? new();
profileItem.IndexId = indexId;
profileItem.Remarks = coreType == ECoreType.sing_box ? ResUI.menuSetDefaultMultipleServer : ResUI.menuSetDefaultLoadBalanceServer;
profileItem.Address = Global.CoreMultipleLoadConfigFileName;
profileItem.ConfigType = EConfigType.Custom;
profileItem.CoreType = coreType;
await AddServerCommon(config, profileItem, true);
result.Data = indexId;
return result;
}
#endregion Server
#region Batch add servers
/// <summary>
/// 批量添加服务器
/// </summary>
/// <param name="config"></param>
/// <param name="strData"></param>
/// <param name="subid"></param>
/// <returns>成功导入的数量</returns>
private static async Task<int> AddBatchServers(Config config, string strData, string subid, bool isSub, List<ProfileItem> lstOriSub)
{
if (Utils.IsNullOrEmpty(strData))
{
return -1;
}
string subFilter = string.Empty;
//remove sub items
if (isSub && Utils.IsNotEmpty(subid))
{
await RemoveServerViaSubid(config, subid, isSub);
subFilter = (await AppHandler.Instance.GetSubItem(subid))?.Filter ?? "";
}
int countServers = 0;
//Check for duplicate indexId
List<string>? lstDbIndexId = null;
List<ProfileItem> lstAdd = new();
var arrData = strData.Split(Environment.NewLine.ToCharArray()).Where(t => !t.IsNullOrEmpty());
if (isSub)
{
arrData = arrData.Distinct();
}
foreach (string str in arrData)
{
//maybe sub
if (!isSub && (str.StartsWith(Global.HttpsProtocol) || str.StartsWith(Global.HttpProtocol)))
{
if (await AddSubItem(config, str) == 0)
{
countServers++;
}
continue;
}
var profileItem = FmtHandler.ResolveConfig(str, out string msg);
if (profileItem is null)
{
continue;
}
//exist sub items
if (isSub && Utils.IsNotEmpty(subid))
{
var existItem = lstOriSub?.FirstOrDefault(t => t.IsSub == isSub
&& config.UiItem.EnableUpdateSubOnlyRemarksExist ? t.Remarks == profileItem.Remarks : CompareProfileItem(t, profileItem, true));
if (existItem != null)
{
//Check for duplicate indexId
if (lstDbIndexId is null)
{
lstDbIndexId = await AppHandler.Instance.ProfileItemIndexes("");
}
if (lstAdd.Any(t => t.IndexId == existItem.IndexId)
|| lstDbIndexId.Any(t => t == existItem.IndexId))
{
profileItem.IndexId = string.Empty;
}
else
{
profileItem.IndexId = existItem.IndexId;
}
}
//filter
if (Utils.IsNotEmpty(subFilter))
{
if (!Regex.IsMatch(profileItem.Remarks, subFilter))
{
continue;
}
}
}
profileItem.Subid = subid;
profileItem.IsSub = isSub;
var addStatus = profileItem.ConfigType switch
{
EConfigType.VMess => await AddVMessServer(config, profileItem, false),
EConfigType.Shadowsocks => await AddShadowsocksServer(config, profileItem, false),
EConfigType.SOCKS => await AddSocksServer(config, profileItem, false),
EConfigType.Trojan => await AddTrojanServer(config, profileItem, false),
EConfigType.VLESS => await AddVlessServer(config, profileItem, false),
EConfigType.Hysteria2 => await AddHysteria2Server(config, profileItem, false),
EConfigType.TUIC => await AddTuicServer(config, profileItem, false),
EConfigType.WireGuard => await AddWireguardServer(config, profileItem, false),
_ => -1,
};
if (addStatus == 0)
{
countServers++;
lstAdd.Add(profileItem);
}
}
if (lstAdd.Count > 0)
{
await SQLiteHelper.Instance.InsertAllAsync(lstAdd);
}
await ToJsonFile(config);
return countServers;
}
private static async Task<int> AddBatchServers4Custom(Config config, string strData, string subid, bool isSub, List<ProfileItem> lstOriSub)
{
if (Utils.IsNullOrEmpty(strData))
{
return -1;
}
var subItem = await AppHandler.Instance.GetSubItem(subid);
var subRemarks = subItem?.Remarks;
var preSocksPort = subItem?.PreSocksPort;
List<ProfileItem>? lstProfiles = null;
//Is sing-box array configuration
if (lstProfiles is null || lstProfiles.Count <= 0)
{
lstProfiles = SingboxFmt.ResolveFullArray(strData, subRemarks);
}
//Is v2ray array configuration
if (lstProfiles is null || lstProfiles.Count <= 0)
{
lstProfiles = V2rayFmt.ResolveFullArray(strData, subRemarks);
}
if (lstProfiles != null && lstProfiles.Count > 0)
{
if (isSub && Utils.IsNotEmpty(subid))
{
await RemoveServerViaSubid(config, subid, isSub);
}
int count = 0;
foreach (var it in lstProfiles)
{
it.Subid = subid;
it.IsSub = isSub;
it.PreSocksPort = preSocksPort;
if (await AddCustomServer(config, it, true) == 0)
{
count++;
}
}
if (count > 0)
{
return count;
}
}
ProfileItem? profileItem = null;
//Is sing-box configuration
if (profileItem is null)
{
profileItem = SingboxFmt.ResolveFull(strData, subRemarks);
}
//Is v2ray configuration
if (profileItem is null)
{
profileItem = V2rayFmt.ResolveFull(strData, subRemarks);
}
//Is Clash configuration
if (profileItem is null)
{
profileItem = ClashFmt.ResolveFull(strData, subRemarks);
}
//Is hysteria configuration
if (profileItem is null)
{
profileItem = Hysteria2Fmt.ResolveFull2(strData, subRemarks);
}
if (profileItem is null)
{
profileItem = Hysteria2Fmt.ResolveFull(strData, subRemarks);
}
//Is naiveproxy configuration
if (profileItem is null)
{
profileItem = NaiveproxyFmt.ResolveFull(strData, subRemarks);
}
if (profileItem is null || Utils.IsNullOrEmpty(profileItem.Address))
{
return -1;
}
if (isSub && Utils.IsNotEmpty(subid))
{
await RemoveServerViaSubid(config, subid, isSub);
}
if (isSub && lstOriSub?.Count == 1)
{
profileItem.IndexId = lstOriSub[0].IndexId;
}
profileItem.Subid = subid;
profileItem.IsSub = isSub;
profileItem.PreSocksPort = preSocksPort;
if (await AddCustomServer(config, profileItem, true) == 0)
{
return 1;
}
else
{
return -1;
}
}
private static async Task<int> AddBatchServers4SsSIP008(Config config, string strData, string subid, bool isSub, List<ProfileItem> lstOriSub)
{
if (Utils.IsNullOrEmpty(strData))
{
return -1;
}
if (isSub && Utils.IsNotEmpty(subid))
{
await RemoveServerViaSubid(config, subid, isSub);
}
var lstSsServer = ShadowsocksFmt.ResolveSip008(strData);
if (lstSsServer?.Count > 0)
{
int counter = 0;
foreach (var ssItem in lstSsServer)
{
ssItem.Subid = subid;
ssItem.IsSub = isSub;
if (await AddShadowsocksServer(config, ssItem) == 0)
{
counter++;
}
}
await ToJsonFile(config);
return counter;
}
return -1;
}
public static async Task<int> AddBatchServers(Config config, string strData, string subid, bool isSub)
{
if (Utils.IsNullOrEmpty(strData))
{
return -1;
}
List<ProfileItem>? lstOriSub = null;
if (isSub && Utils.IsNotEmpty(subid))
{
lstOriSub = await AppHandler.Instance.ProfileItems(subid);
}
var counter = 0;
if (Utils.IsBase64String(strData))
{
counter = await AddBatchServers(config, Utils.Base64Decode(strData), subid, isSub, lstOriSub);
}
if (counter < 1)
{
counter = await AddBatchServers(config, strData, subid, isSub, lstOriSub);
}
if (counter < 1)
{
counter = await AddBatchServers(config, Utils.Base64Decode(strData), subid, isSub, lstOriSub);
}
if (counter < 1)
{
counter = await AddBatchServers4SsSIP008(config, strData, subid, isSub, lstOriSub);
}
//maybe other sub
if (counter < 1)
{
counter = await AddBatchServers4Custom(config, strData, subid, isSub, lstOriSub);
}
return counter;
}
#endregion Batch add servers
#region Sub & Group
/// <summary>
/// add sub
/// </summary>
/// <param name="config"></param>
/// <param name="url"></param>
/// <returns></returns>
public static async Task<int> AddSubItem(Config config, string url)
{
//already exists
var count = await SQLiteHelper.Instance.TableAsync<SubItem>().CountAsync(e => e.Url == url);
if (count > 0)
{
return 0;
}
SubItem subItem = new()
{
Id = string.Empty,
Url = url
};
try
{
var uri = new Uri(url);
var queryVars = Utils.ParseQueryString(uri.Query);
subItem.Remarks = queryVars["remarks"] ?? "import_sub";
}
catch (UriFormatException)
{
return 0;
}
return await AddSubItem(config, subItem);
}
public static async Task<int> AddSubItem(Config config, SubItem subItem)
{
var item = await AppHandler.Instance.GetSubItem(subItem.Id);
if (item is null)
{
item = subItem;
}
else
{
item.Remarks = subItem.Remarks;
item.Url = subItem.Url;
item.MoreUrl = subItem.MoreUrl;
item.Enabled = subItem.Enabled;
item.AutoUpdateInterval = subItem.AutoUpdateInterval;
item.UserAgent = subItem.UserAgent;
item.Sort = subItem.Sort;
item.Filter = subItem.Filter;
item.UpdateTime = subItem.UpdateTime;
item.ConvertTarget = subItem.ConvertTarget;
item.PrevProfile = subItem.PrevProfile;
item.NextProfile = subItem.NextProfile;
item.PreSocksPort = subItem.PreSocksPort;
}
if (Utils.IsNullOrEmpty(item.Id))
{
item.Id = Utils.GetGuid(false);
if (item.Sort <= 0)
{
var maxSort = 0;
if (await SQLiteHelper.Instance.TableAsync<SubItem>().CountAsync() > 0)
{
var lstSubs = (await AppHandler.Instance.SubItems());
maxSort = lstSubs.LastOrDefault()?.Sort ?? 0;
}
item.Sort = maxSort + 1;
}
}
if (await SQLiteHelper.Instance.ReplaceAsync(item) > 0)
{
return 0;
}
else
{
return -1;
}
}
/// <summary>
/// 移除服务器
/// </summary>
/// <param name="config"></param>
/// <param name="subid"></param>
/// <returns></returns>
public static async Task<int> RemoveServerViaSubid(Config config, string subid, bool isSub)
{
if (Utils.IsNullOrEmpty(subid))
{
return -1;
}
var customProfile = await SQLiteHelper.Instance.TableAsync<ProfileItem>().Where(t => t.Subid == subid && t.ConfigType == EConfigType.Custom).ToListAsync();
if (isSub)
{
await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileItem where isSub = 1 and subid = '{subid}'");
}
else
{
await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileItem where subid = '{subid}'");
}
foreach (var item in customProfile)
{
File.Delete(Utils.GetConfigPath(item.Address));
}
return 0;
}
public static async Task<int> DeleteSubItem(Config config, string id)
{
var item = await AppHandler.Instance.GetSubItem(id);
if (item is null)
{
return 0;
}
await SQLiteHelper.Instance.DeleteAsync(item);
await RemoveServerViaSubid(config, id, false);
return 0;
}
public static async Task<int> MoveToGroup(Config config, List<ProfileItem> lstProfile, string subid)
{
foreach (var item in lstProfile)
{
item.Subid = subid;
}
await SQLiteHelper.Instance.UpdateAllAsync(lstProfile);
return 0;
}
#endregion Sub & Group
#region Routing
public static async Task<int> SaveRoutingItem(Config config, RoutingItem item)
{
if (Utils.IsNullOrEmpty(item.Id))
{
item.Id = Utils.GetGuid(false);
}
if (await SQLiteHelper.Instance.ReplaceAsync(item) > 0)
{
return 0;
}
else
{
return -1;
}
}
/// <summary>
/// AddBatchRoutingRules
/// </summary>
/// <param name="config"></param>
/// <param name="strData"></param>
/// <returns></returns>
public static async Task<int> AddBatchRoutingRules(RoutingItem routingItem, string strData)
{
if (Utils.IsNullOrEmpty(strData))
{
return -1;
}
var lstRules = JsonUtils.Deserialize<List<RulesItem>>(strData);
if (lstRules == null)
{
return -1;
}
foreach (var item in lstRules)
{
item.Id = Utils.GetGuid(false);
}
routingItem.RuleNum = lstRules.Count;
routingItem.RuleSet = JsonUtils.Serialize(lstRules, false);
if (Utils.IsNullOrEmpty(routingItem.Id))
{
routingItem.Id = Utils.GetGuid(false);
}
if (await SQLiteHelper.Instance.ReplaceAsync(routingItem) > 0)
{
return 0;
}
else
{
return -1;
}
}
/// <summary>
/// MoveRoutingRule
/// </summary>
/// <param name="routingItem"></param>
/// <param name="index"></param>
/// <param name="eMove"></param>
/// <returns></returns>
public static async Task<int> MoveRoutingRule(List<RulesItem> rules, int index, EMove eMove, int pos = -1)
{
int count = rules.Count;
if (index < 0 || index > rules.Count - 1)
{
return -1;
}
switch (eMove)
{
case EMove.Top:
{
if (index == 0)
{
return 0;
}
var item = JsonUtils.DeepCopy(rules[index]);
rules.RemoveAt(index);
rules.Insert(0, item);
break;
}
case EMove.Up:
{
if (index == 0)
{
return 0;
}
var item = JsonUtils.DeepCopy(rules[index]);
rules.RemoveAt(index);
rules.Insert(index - 1, item);
break;
}
case EMove.Down:
{
if (index == count - 1)
{
return 0;
}
var item = JsonUtils.DeepCopy(rules[index]);
rules.RemoveAt(index);
rules.Insert(index + 1, item);
break;
}
case EMove.Bottom:
{
if (index == count - 1)
{
return 0;
}
var item = JsonUtils.DeepCopy(rules[index]);
rules.RemoveAt(index);
rules.Add(item);
break;
}
case EMove.Position:
{
var removeItem = rules[index];
var item = JsonUtils.DeepCopy(rules[index]);
rules.Insert(pos, item);
rules.Remove(removeItem);
break;
}
}
return 0;
}
public static async Task<int> SetDefaultRouting(Config config, RoutingItem routingItem)
{
if (await SQLiteHelper.Instance.TableAsync<RoutingItem>().Where(t => t.Id == routingItem.Id).CountAsync() > 0)
{
config.RoutingBasicItem.RoutingIndexId = routingItem.Id;
}
await ToJsonFile(config);
return 0;
}
public static async Task<RoutingItem> GetDefaultRouting(Config config)
{
var item = await AppHandler.Instance.GetRoutingItem(config.RoutingBasicItem.RoutingIndexId);
if (item is null)
{
var item2 = await SQLiteHelper.Instance.TableAsync<RoutingItem>().FirstOrDefaultAsync(t => t.Locked == false);
await SetDefaultRouting(config, item2);
return item2;
}
return item;
}
public static async Task<int> InitRouting(Config config, bool blImportAdvancedRules = false)
{
if (string.IsNullOrEmpty(config.ConstItem.RouteRulesTemplateSourceUrl))
{
await InitBuiltinRouting(config, blImportAdvancedRules);
}
else
{
await InitExternalRouting(config, blImportAdvancedRules);
}
return 0;
}
public static async Task<int> InitExternalRouting(Config config, bool blImportAdvancedRules = false)
{
var downloadHandle = new DownloadService();
var templateContent = await downloadHandle.TryDownloadString(config.ConstItem.RouteRulesTemplateSourceUrl, true, "");
if (string.IsNullOrEmpty(templateContent))
return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback
var template = JsonUtils.Deserialize<RoutingTemplate>(templateContent);
if (template == null)
return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback
var items = await AppHandler.Instance.RoutingItems();
var maxSort = items.Count;
if (!blImportAdvancedRules && items.Where(t => t.Remarks.StartsWith(template.Version)).ToList().Count > 0)
{
return 0;
}
for (var i = 0; i < template.RoutingItems.Length; i++)
{
var item = template.RoutingItems[i];
if (string.IsNullOrEmpty(item.Url) && string.IsNullOrEmpty(item.RuleSet))
continue;
var ruleSetsString = !string.IsNullOrEmpty(item.RuleSet)
? item.RuleSet
: await downloadHandle.TryDownloadString(item.Url, true, "");
if (string.IsNullOrEmpty(ruleSetsString))
continue;
item.Remarks = $"{template.Version}-{item.Remarks}";
item.Enabled = true;
item.Sort = ++maxSort;
item.Url = string.Empty;
await AddBatchRoutingRules(item, ruleSetsString);
//first rule as default at first startup
if (!blImportAdvancedRules && i == 0)
{
await SetDefaultRouting(config, item);
}
}
return 0;
}
public static async Task<int> InitBuiltinRouting(Config config, bool blImportAdvancedRules = false)
{
var ver = "V3-";
var items = await AppHandler.Instance.RoutingItems();
if (!blImportAdvancedRules && items.Where(t => t.Remarks.StartsWith(ver)).ToList().Count > 0)
{
return 0;
}
var maxSort = items.Count;
//Bypass the mainland
var item2 = new RoutingItem()
{
Remarks = $"{ver}绕过大陆(Whitelist)",
Url = string.Empty,
Sort = maxSort + 1,
};
await AddBatchRoutingRules(item2, Utils.GetEmbedText(Global.CustomRoutingFileName + "white"));
//Blacklist
var item3 = new RoutingItem()
{
Remarks = $"{ver}黑名单(Blacklist)",
Url = string.Empty,
Sort = maxSort + 2,
};
await AddBatchRoutingRules(item3, Utils.GetEmbedText(Global.CustomRoutingFileName + "black"));
//Global
var item1 = new RoutingItem()
{
Remarks = $"{ver}全局(Global)",
Url = string.Empty,
Sort = maxSort + 3,
};
await AddBatchRoutingRules(item1, Utils.GetEmbedText(Global.CustomRoutingFileName + "global"));
if (!blImportAdvancedRules)
{
await SetDefaultRouting(config, item2);
}
return 0;
}
public static async Task<RoutingItem?> GetLockedRoutingItem(Config config)
{
return await SQLiteHelper.Instance.TableAsync<RoutingItem>().FirstOrDefaultAsync(it => it.Locked == true);
}
public static async Task RemoveRoutingItem(RoutingItem routingItem)
{
await SQLiteHelper.Instance.DeleteAsync(routingItem);
}
#endregion Routing
#region DNS
public static async Task<int> InitBuiltinDNS(Config config)
{
var items = await AppHandler.Instance.DNSItems();
if (items.Count <= 0)
{
var item = new DNSItem()
{
Remarks = "V2ray",
CoreType = ECoreType.Xray,
};
await SaveDNSItems(config, item);
var item2 = new DNSItem()
{
Remarks = "sing-box",
CoreType = ECoreType.sing_box,
};
await SaveDNSItems(config, item2);
}
return 0;
}
public static async Task<int> SaveDNSItems(Config config, DNSItem item)
{
if (item == null)
{
return -1;
}
if (Utils.IsNullOrEmpty(item.Id))
{
item.Id = Utils.GetGuid(false);
}
if (await SQLiteHelper.Instance.ReplaceAsync(item) > 0)
{
return 0;
}
else
{
return -1;
}
}
public static async Task<DNSItem> GetExternalDNSItem(ECoreType type, string url)
{
var currentItem = await AppHandler.Instance.GetDNSItem(type);
var downloadHandle = new DownloadService();
var templateContent = await downloadHandle.TryDownloadString(url, true, "");
if (string.IsNullOrEmpty(templateContent))
return currentItem;
var template = JsonUtils.Deserialize<DNSItem>(templateContent);
if (template == null)
return currentItem;
if (!string.IsNullOrEmpty(template.NormalDNS))
template.NormalDNS = await downloadHandle.TryDownloadString(template.NormalDNS, true, "");
if (!string.IsNullOrEmpty(template.TunDNS))
template.TunDNS = await downloadHandle.TryDownloadString(template.TunDNS, true, "");
template.Id = currentItem.Id;
template.Enabled = currentItem.Enabled;
template.Remarks = currentItem.Remarks;
template.CoreType = type;
return template;
}
#endregion DNS
#region Regional Presets
public static async Task<bool> ApplyRegionalPreset(Config config, EPresetType type)
{
switch (type)
{
case EPresetType.Default:
config.ConstItem.GeoSourceUrl = "";
config.ConstItem.SrsSourceUrl = "";
config.ConstItem.RouteRulesTemplateSourceUrl = "";
await SQLiteHelper.Instance.DeleteAllAsync<DNSItem>();
await InitBuiltinDNS(config);
return true;
case EPresetType.Russia:
config.ConstItem.GeoSourceUrl = Global.GeoFilesSources[1];
config.ConstItem.SrsSourceUrl = Global.SingboxRulesetSources[1];
config.ConstItem.RouteRulesTemplateSourceUrl = Global.RoutingRulesSources[1];
await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.Xray, Global.DNSTemplateSources[1] + "v2ray.json"));
await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.sing_box, Global.DNSTemplateSources[1] + "sing_box.json"));
return true;
}
return false;
}
#endregion Regional Presets
}
}