Compare commits

...

5 commits

Author SHA1 Message Date
JieXu
dbf12af3a0
Merge bc1c42a2a2 into 120e8d0686 2025-08-16 16:15:31 +03:30
2dust
120e8d0686 Fixed a bug in parsing subscription result
Some checks are pending
release Linux / build (Release) (push) Waiting to run
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
https://github.com/2dust/v2rayN/issues/7734
2025-08-16 20:05:56 +08:00
2dust
186b56aed9 Refactor SubscriptionHandler and add exception capture 2025-08-16 18:01:12 +08:00
2dust
c560fe13fe Adjust the tun mtu value list
https://github.com/2dust/v2rayN/issues/7775
2025-08-16 17:18:17 +08:00
2dust
95e3ebd815 up 7.14.0
Some checks are pending
release Linux / build (Release) (push) Waiting to run
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
2025-08-15 17:18:16 +08:00
6 changed files with 206 additions and 114 deletions

View file

@ -1,7 +1,7 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<Version>7.13.7</Version> <Version>7.14.0</Version>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>

View file

@ -466,7 +466,9 @@ public class Global
1280, 1280,
1408, 1408,
1500, 1500,
9000 4064,
9000,
65535
]; ];
public static readonly List<string> TunStacks = public static readonly List<string> TunStacks =

View file

@ -1412,6 +1412,11 @@ public class ConfigHandler
{ {
profileItem = V2rayFmt.ResolveFull(strData, subRemarks); profileItem = V2rayFmt.ResolveFull(strData, subRemarks);
} }
//Is Html Page
if (profileItem is null && HtmlPageFmt.IsHtmlPage(strData))
{
return -1;
}
//Is Clash configuration //Is Clash configuration
if (profileItem is null) if (profileItem is null)
{ {

View file

@ -220,14 +220,7 @@ public class BaseFmt
protected static bool Contains(string str, params string[] s) protected static bool Contains(string str, params string[] s)
{ {
foreach (var item in s) return s.All(item => str.Contains(item, StringComparison.OrdinalIgnoreCase));
{
if (str.Contains(item, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
} }
protected static string WriteAllText(string strData, string ext = "json") protected static string WriteAllText(string strData, string ext = "json")

View file

@ -0,0 +1,11 @@
using SkiaSharp;
namespace ServiceLib.Handler.Fmt;
public class HtmlPageFmt : BaseFmt
{
public static bool IsHtmlPage(string strData)
{
return Contains(strData, "<html", "<!doctype html", "<head");
}
}

View file

@ -15,63 +15,144 @@ public class SubscriptionHandler
foreach (var item in subItem) foreach (var item in subItem)
{ {
var id = item.Id.TrimEx(); try
var url = item.Url.TrimEx(); {
var userAgent = item.UserAgent.TrimEx(); if (!IsValidSubscription(item, subId))
{
continue;
}
var hashCode = $"{item.Remarks}->"; var hashCode = $"{item.Remarks}->";
if (id.IsNullOrEmpty() || url.IsNullOrEmpty() || (subId.IsNotEmpty() && item.Id != subId))
{
//_updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgNoValidSubscription}");
continue;
}
if (!url.StartsWith(Global.HttpsProtocol) && !url.StartsWith(Global.HttpProtocol))
{
continue;
}
if (item.Enabled == false) if (item.Enabled == false)
{ {
updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgSkipSubscriptionUpdate}"); updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgSkipSubscriptionUpdate}");
continue; continue;
} }
// Create download handler
var downloadHandle = CreateDownloadHandler(hashCode, updateFunc);
updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgStartGettingSubscriptions}");
// Get all subscription content (main subscription + additional subscriptions)
var result = await DownloadAllSubscriptions(config, item, blProxy, downloadHandle);
// Process download result
await ProcessDownloadResult(config, item.Id, result, hashCode, updateFunc);
updateFunc?.Invoke(false, "-------------------------------------------------------");
}
catch (Exception ex)
{
var hashCode = $"{item.Remarks}->";
Logging.SaveLog("UpdateSubscription", ex);
updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgFailedImportSubscription}: {ex.Message}");
updateFunc?.Invoke(false, "-------------------------------------------------------");
}
}
updateFunc?.Invoke(true, $"{ResUI.MsgUpdateSubscriptionEnd}");
}
private static bool IsValidSubscription(SubItem item, string subId)
{
var id = item.Id.TrimEx();
var url = item.Url.TrimEx();
if (id.IsNullOrEmpty() || url.IsNullOrEmpty())
{
return false;
}
if (subId.IsNotEmpty() && item.Id != subId)
{
return false;
}
if (!url.StartsWith(Global.HttpsProtocol) && !url.StartsWith(Global.HttpProtocol))
{
return false;
}
return true;
}
private static DownloadService CreateDownloadHandler(string hashCode, Action<bool, string> updateFunc)
{
var downloadHandle = new DownloadService(); var downloadHandle = new DownloadService();
downloadHandle.Error += (sender2, args) => downloadHandle.Error += (sender2, args) =>
{ {
updateFunc?.Invoke(false, $"{hashCode}{args.GetException().Message}"); updateFunc?.Invoke(false, $"{hashCode}{args.GetException().Message}");
}; };
return downloadHandle;
}
updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgStartGettingSubscriptions}"); private static async Task<string> DownloadSubscriptionContent(DownloadService downloadHandle, string url, bool blProxy, string userAgent)
//one url
url = Utils.GetPunycode(url);
//convert
if (item.ConvertTarget.IsNotEmpty())
{ {
var subConvertUrl = config.ConstItem.SubConvertUrl.IsNullOrEmpty() ? Global.SubConvertUrls.FirstOrDefault() : config.ConstItem.SubConvertUrl;
url = string.Format(subConvertUrl!, Utils.UrlEncode(url));
if (!url.Contains("target="))
{
url += string.Format("&target={0}", item.ConvertTarget);
}
if (!url.Contains("config="))
{
url += string.Format("&config={0}", Global.SubConvertConfig.FirstOrDefault());
}
}
var result = await downloadHandle.TryDownloadString(url, blProxy, userAgent); var result = await downloadHandle.TryDownloadString(url, blProxy, userAgent);
// If download with proxy fails, try direct connection
if (blProxy && result.IsNullOrEmpty()) if (blProxy && result.IsNullOrEmpty())
{ {
result = await downloadHandle.TryDownloadString(url, false, userAgent); result = await downloadHandle.TryDownloadString(url, false, userAgent);
} }
//more url return result ?? string.Empty;
}
private static async Task<string> DownloadAllSubscriptions(Config config, SubItem item, bool blProxy, DownloadService downloadHandle)
{
// Download main subscription content
var result = await DownloadMainSubscription(config, item, blProxy, downloadHandle);
// Process additional subscription links (if any)
if (item.ConvertTarget.IsNullOrEmpty() && item.MoreUrl.TrimEx().IsNotEmpty()) if (item.ConvertTarget.IsNullOrEmpty() && item.MoreUrl.TrimEx().IsNotEmpty())
{ {
result = await DownloadAdditionalSubscriptions(item, result, blProxy, downloadHandle);
}
return result;
}
private static async Task<string> DownloadMainSubscription(Config config, SubItem item, bool blProxy, DownloadService downloadHandle)
{
// Prepare subscription URL and download directly
var url = Utils.GetPunycode(item.Url.TrimEx());
// If conversion is needed
if (item.ConvertTarget.IsNotEmpty())
{
var subConvertUrl = config.ConstItem.SubConvertUrl.IsNullOrEmpty()
? Global.SubConvertUrls.FirstOrDefault()
: config.ConstItem.SubConvertUrl;
url = string.Format(subConvertUrl!, Utils.UrlEncode(url));
if (!url.Contains("target="))
{
url += string.Format("&target={0}", item.ConvertTarget);
}
if (!url.Contains("config="))
{
url += string.Format("&config={0}", Global.SubConvertConfig.FirstOrDefault());
}
}
// Download and return result directly
return await DownloadSubscriptionContent(downloadHandle, url, blProxy, item.UserAgent);
}
private static async Task<string> DownloadAdditionalSubscriptions(SubItem item, string mainResult, bool blProxy, DownloadService downloadHandle)
{
var result = mainResult;
// If main subscription result is Base64 encoded, decode it first
if (result.IsNotEmpty() && Utils.IsBase64String(result)) if (result.IsNotEmpty() && Utils.IsBase64String(result))
{ {
result = Utils.Base64Decode(result); result = Utils.Base64Decode(result);
} }
// Process additional URL list
var lstUrl = item.MoreUrl.TrimEx().Split(",") ?? []; var lstUrl = item.MoreUrl.TrimEx().Split(",") ?? [];
foreach (var it in lstUrl) foreach (var it in lstUrl)
{ {
@ -81,53 +162,53 @@ public class SubscriptionHandler
continue; continue;
} }
var result2 = await downloadHandle.TryDownloadString(url2, blProxy, userAgent); var additionalResult = await DownloadSubscriptionContent(downloadHandle, url2, blProxy, item.UserAgent);
if (blProxy && result2.IsNullOrEmpty())
if (additionalResult.IsNotEmpty())
{ {
result2 = await downloadHandle.TryDownloadString(url2, false, userAgent); // Process additional subscription results, add to main result
} if (Utils.IsBase64String(additionalResult))
if (result2.IsNotEmpty())
{ {
if (Utils.IsBase64String(result2)) result += Environment.NewLine + Utils.Base64Decode(additionalResult);
{
result += Environment.NewLine + Utils.Base64Decode(result2);
} }
else else
{ {
result += Environment.NewLine + result2; result += Environment.NewLine + additionalResult;
}
} }
} }
} }
return result;
}
private static async Task ProcessDownloadResult(Config config, string id, string result, string hashCode, Action<bool, string> updateFunc)
{
if (result.IsNullOrEmpty()) if (result.IsNullOrEmpty())
{ {
updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgSubscriptionDecodingFailed}"); updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgSubscriptionDecodingFailed}");
return;
} }
else
{
updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgGetSubscriptionSuccessfully}"); updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgGetSubscriptionSuccessfully}");
if (result?.Length < 99)
// If result is too short, display content directly
if (result.Length < 99)
{ {
updateFunc?.Invoke(false, $"{hashCode}{result}"); updateFunc?.Invoke(false, $"{hashCode}{result}");
} }
// Add servers to configuration
var ret = await ConfigHandler.AddBatchServers(config, result, id, true); var ret = await ConfigHandler.AddBatchServers(config, result, id, true);
if (ret <= 0) if (ret <= 0)
{ {
Logging.SaveLog("FailedImportSubscription"); Logging.SaveLog("FailedImportSubscription");
Logging.SaveLog(result); Logging.SaveLog(result);
} }
// Update completion message
updateFunc?.Invoke(false, updateFunc?.Invoke(false,
ret > 0 ret > 0
? $"{hashCode}{ResUI.MsgUpdateSubscriptionEnd}" ? $"{hashCode}{ResUI.MsgUpdateSubscriptionEnd}"
: $"{hashCode}{ResUI.MsgFailedImportSubscription}"); : $"{hashCode}{ResUI.MsgFailedImportSubscription}");
} }
updateFunc?.Invoke(false, "-------------------------------------------------------");
//await ConfigHandler.DedupServerList(config, id);
}
updateFunc?.Invoke(true, $"{ResUI.MsgUpdateSubscriptionEnd}");
}
} }