mirror of
https://github.com/MaxiFan/TunnelX.git
synced 2026-05-18 23:54:50 +03:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cfaf2ae481 | |||
| ae61021c7c | |||
| 3145b9a01e | |||
| 04d8d52ba9 | |||
| bae98cf9e9 |
@@ -95,10 +95,12 @@ jobs:
|
||||
}
|
||||
|
||||
$artifactName = "TunnelX-$tag-standalone-compressed.exe"
|
||||
$frameworkArtifactName = "TunnelX-$tag-framework-dependent-win-x64.zip"
|
||||
|
||||
"version=$version" >> $env:GITHUB_OUTPUT
|
||||
"tag=$tag" >> $env:GITHUB_OUTPUT
|
||||
"artifact_name=$artifactName" >> $env:GITHUB_OUTPUT
|
||||
"framework_artifact_name=$frameworkArtifactName" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Update version and changelog
|
||||
id: release_notes
|
||||
@@ -213,6 +215,63 @@ jobs:
|
||||
"checksum=$checksum" >> $env:GITHUB_OUTPUT
|
||||
"sha256=$hash" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Publish framework-dependent package
|
||||
run: >
|
||||
dotnet publish AppTunnel\AppTunnel.csproj
|
||||
-c Release
|
||||
-r win-x64
|
||||
--self-contained false
|
||||
-p:DebugType=None
|
||||
-p:DebugSymbols=false
|
||||
-o publish\TunnelX-framework-dependent
|
||||
|
||||
- name: Package framework-dependent asset
|
||||
id: framework_package
|
||||
shell: pwsh
|
||||
run: |
|
||||
$publishDir = "publish/TunnelX-framework-dependent"
|
||||
$assetName = "${{ steps.meta.outputs.framework_artifact_name }}"
|
||||
$asset = "publish/$assetName"
|
||||
$checksum = "$asset.sha256"
|
||||
$readme = Join-Path $publishDir "README.txt"
|
||||
|
||||
if (-not (Test-Path (Join-Path $publishDir "TunnelX.exe"))) {
|
||||
throw "Framework-dependent executable was not found in $publishDir"
|
||||
}
|
||||
|
||||
$readmeLines = @(
|
||||
"TunnelX framework-dependent package",
|
||||
"===================================",
|
||||
"",
|
||||
"This package is smaller than the standalone download because it does not include the .NET runtime.",
|
||||
"",
|
||||
"Use this ZIP only if Microsoft .NET 8 Desktop Runtime (x64) is already installed on this Windows PC.",
|
||||
"If .NET 8 Desktop Runtime is not installed, download the standalone TunnelX EXE instead.",
|
||||
"",
|
||||
"Download .NET 8 Desktop Runtime:",
|
||||
"https://dotnet.microsoft.com/en-us/download/dotnet/8.0",
|
||||
"",
|
||||
"Run:",
|
||||
"1. Extract the ZIP to a folder.",
|
||||
"2. Run TunnelX.exe as Administrator.",
|
||||
"",
|
||||
"Recommended for most users:",
|
||||
"TunnelX standalone compressed EXE."
|
||||
)
|
||||
($readmeLines -join "`r`n") | Set-Content -Encoding UTF8 -LiteralPath $readme
|
||||
|
||||
if (Test-Path $asset) {
|
||||
Remove-Item -LiteralPath $asset -Force
|
||||
}
|
||||
Compress-Archive -Path (Join-Path $publishDir "*") -DestinationPath $asset -Force
|
||||
|
||||
$hash = (Get-FileHash -Algorithm SHA256 -LiteralPath $asset).Hash.ToLowerInvariant()
|
||||
"$hash $assetName" | Set-Content -Encoding ASCII -LiteralPath $checksum
|
||||
|
||||
"asset=$asset" >> $env:GITHUB_OUTPUT
|
||||
"checksum=$checksum" >> $env:GITHUB_OUTPUT
|
||||
"sha256=$hash" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Upload workflow artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
@@ -220,6 +279,8 @@ jobs:
|
||||
path: |
|
||||
${{ steps.package.outputs.asset }}
|
||||
${{ steps.package.outputs.checksum }}
|
||||
${{ steps.framework_package.outputs.asset }}
|
||||
${{ steps.framework_package.outputs.checksum }}
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Create GitHub release
|
||||
@@ -231,9 +292,22 @@ jobs:
|
||||
$title = "TunnelX $tag"
|
||||
$runUrl = "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
$sha256 = "${{ steps.package.outputs.sha256 }}".ToUpperInvariant()
|
||||
$frameworkSha256 = "${{ steps.framework_package.outputs.sha256 }}".ToUpperInvariant()
|
||||
$artifactName = "${{ steps.meta.outputs.artifact_name }}"
|
||||
$frameworkArtifactName = "${{ steps.meta.outputs.framework_artifact_name }}"
|
||||
|
||||
$notes = Get-Content -Raw -LiteralPath "${{ steps.release_notes.outputs.notes_file }}"
|
||||
$downloadLines = @(
|
||||
"## دانلودها",
|
||||
"",
|
||||
"- پیشنهادشده برای بیشتر کاربران: ``$artifactName``. این نسخه مستقل است و نیازی به نصب جداگانه .NET ندارد.",
|
||||
"- بسته کمحجمتر برای کاربرانی که Microsoft .NET 8 Desktop Runtime نسخه x64 را نصب دارند: ``$frameworkArtifactName``. فایل ZIP را استخراج کنید و ``TunnelX.exe`` را با دسترسی Administrator اجرا کنید. داخل ZIP یک ``README.txt`` هم برای توضیح همین پیشنیاز قرار دارد.",
|
||||
"",
|
||||
"## Downloads",
|
||||
"",
|
||||
"- Recommended for most users: ``$artifactName``. This is the standalone self-contained EXE and does not require a separate .NET installation.",
|
||||
"- Smaller package for users who already have Microsoft .NET 8 Desktop Runtime (x64): ``$frameworkArtifactName``. Extract the ZIP and run ``TunnelX.exe`` as Administrator. The ZIP includes ``README.txt`` with the same requirement."
|
||||
)
|
||||
$provenanceLines = @(
|
||||
"<!-- release-provenance:start -->",
|
||||
"## Build provenance",
|
||||
@@ -243,9 +317,10 @@ jobs:
|
||||
"- Run: $runUrl",
|
||||
"- Commit: ``${{ steps.release_commit.outputs.sha }}``",
|
||||
"- SHA256: ``$sha256 $artifactName``",
|
||||
"- SHA256: ``$frameworkSha256 $frameworkArtifactName``",
|
||||
"<!-- release-provenance:end -->"
|
||||
)
|
||||
$notes = "$($notes.Trim())`n`n$($provenanceLines -join "`n")"
|
||||
$notes = "$($notes.Trim())`n`n$($downloadLines -join "`n")`n`n$($provenanceLines -join "`n")"
|
||||
|
||||
$notesFile = Join-Path $env:RUNNER_TEMP "final-release-notes.md"
|
||||
$notes | Set-Content -Encoding UTF8 -LiteralPath $notesFile
|
||||
@@ -254,6 +329,8 @@ jobs:
|
||||
"release", "create", $tag,
|
||||
"${{ steps.package.outputs.asset }}",
|
||||
"${{ steps.package.outputs.checksum }}",
|
||||
"${{ steps.framework_package.outputs.asset }}",
|
||||
"${{ steps.framework_package.outputs.checksum }}",
|
||||
"--title", $title,
|
||||
"--notes-file", $notesFile,
|
||||
"--latest"
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
<PackageLicenseExpression>GPL-3.0-or-later</PackageLicenseExpression>
|
||||
<NeutralLanguage>fa-IR</NeutralLanguage>
|
||||
<!-- Version Management -->
|
||||
<Version>1.2.30</Version>
|
||||
<AssemblyVersion>1.2.30.0</AssemblyVersion>
|
||||
<FileVersion>1.2.30.0</FileVersion>
|
||||
<Version>1.2.32</Version>
|
||||
<AssemblyVersion>1.2.32.0</AssemblyVersion>
|
||||
<FileVersion>1.2.32.0</FileVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
|
||||
|
||||
+55
-39
@@ -275,8 +275,8 @@
|
||||
<TabItem Style="{StaticResource ModernTabItem}" ToolTip="وضعیت اتصال VPN و کنترل اتصال">
|
||||
<TabItem.Header>
|
||||
<StackPanel>
|
||||
<TextBlock Text="⚡" FontSize="13" HorizontalAlignment="Center"/>
|
||||
<TextBlock Text="اتصال VPN" FontSize="11" FontWeight="Medium" TextAlignment="Center"/>
|
||||
<TextBlock Text="⚡" FontSize="12" HorizontalAlignment="Center"/>
|
||||
<TextBlock Text="اتصال VPN" FontSize="10" FontWeight="Medium" TextAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
<views:ConnectionTabView/>
|
||||
@@ -284,8 +284,8 @@
|
||||
<TabItem Style="{StaticResource ModernTabItem}" ToolTip="انتخاب برنامههایی که باید از تونل عبور کنند">
|
||||
<TabItem.Header>
|
||||
<StackPanel>
|
||||
<TextBlock Text="📱" FontSize="13" HorizontalAlignment="Center"/>
|
||||
<TextBlock Text="برنامهها" FontSize="11" FontWeight="Medium" TextAlignment="Center"/>
|
||||
<TextBlock Text="📱" FontSize="12" HorizontalAlignment="Center"/>
|
||||
<TextBlock Text="برنامهها" FontSize="10" FontWeight="Medium" TextAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
<views:AppsTabView/>
|
||||
@@ -294,8 +294,8 @@
|
||||
<TabItem Style="{StaticResource ModernTabItem}" ToolTip="تنظیمات عمومی تونل و DNS">
|
||||
<TabItem.Header>
|
||||
<StackPanel>
|
||||
<TextBlock Text="⚙" FontSize="13" HorizontalAlignment="Center"/>
|
||||
<TextBlock Text="تنظیمات" FontSize="11" FontWeight="Medium" TextAlignment="Center"/>
|
||||
<TextBlock Text="⚙" FontSize="12" HorizontalAlignment="Center"/>
|
||||
<TextBlock Text="تنظیمات" FontSize="10" FontWeight="Medium" TextAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
<views:SettingsTabView/>
|
||||
@@ -305,8 +305,8 @@
|
||||
<TabItem Style="{StaticResource ModernTabItem}" ToolTip="قوانین Include و Exclude مسیرها">
|
||||
<TabItem.Header>
|
||||
<StackPanel>
|
||||
<TextBlock Text="🧭" FontSize="13" HorizontalAlignment="Center"/>
|
||||
<TextBlock Text="قوانین مسیر" FontSize="11" FontWeight="Medium" TextAlignment="Center"/>
|
||||
<TextBlock Text="🧭" FontSize="12" HorizontalAlignment="Center"/>
|
||||
<TextBlock Text="قوانین مسیر" FontSize="10" FontWeight="Medium" TextAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
|
||||
@@ -548,8 +548,8 @@
|
||||
<TabItem Style="{StaticResource ModernTabItem}" ToolTip="نمایش ترافیک، تاریخچه و آمار اتصال">
|
||||
<TabItem.Header>
|
||||
<StackPanel>
|
||||
<TextBlock Text="📊" FontSize="13" HorizontalAlignment="Center"/>
|
||||
<TextBlock Text="ترافیک/تاریخچه" FontSize="10.5" FontWeight="Medium" TextAlignment="Center"/>
|
||||
<TextBlock Text="📊" FontSize="12" HorizontalAlignment="Center"/>
|
||||
<TextBlock Text="ترافیک/تاریخچه" FontSize="9.5" FontWeight="Medium" TextAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
|
||||
@@ -753,6 +753,17 @@
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
|
||||
<!-- ███ TAB 6: HELP ███ -->
|
||||
<TabItem Style="{StaticResource ModernTabItem}" ToolTip="راهنما و عیبیابی">
|
||||
<TabItem.Header>
|
||||
<StackPanel>
|
||||
<TextBlock Text="❔" FontSize="12" HorizontalAlignment="Center"/>
|
||||
<TextBlock Text="راهنما" FontSize="10" FontWeight="Medium" TextAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
<views:HelpTabView/>
|
||||
</TabItem>
|
||||
|
||||
</TabControl>
|
||||
|
||||
</Grid>
|
||||
@@ -801,6 +812,18 @@
|
||||
Margin="8,0,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Grid.Column="1"
|
||||
Text="{Binding GitHubInstallCountText}"
|
||||
Visibility="{Binding HasGitHubInstallCount, Converter={StaticResource BoolToVis}}"
|
||||
Foreground="{StaticResource TextSecondaryBrush}"
|
||||
FontSize="12"
|
||||
FontWeight="SemiBold"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FlowDirection="{Binding AppFlowDirection}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
Margin="12,0"/>
|
||||
|
||||
<StackPanel Grid.Column="3"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="{Binding AppEndHorizontalAlignment}"
|
||||
@@ -822,22 +845,6 @@
|
||||
Margin="8,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
ToolTip="حمایت از پروژه"/>
|
||||
<Button Content="راهنما"
|
||||
Click="OnShowHelpClick"
|
||||
Style="{StaticResource SecondaryButton}"
|
||||
Padding="9,4"
|
||||
FontSize="10"
|
||||
Margin="8,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
ToolTip="راهنما و عیبیابی"/>
|
||||
<Button Content="GitHub"
|
||||
Command="{Binding OpenGitHubCommand}"
|
||||
Style="{StaticResource SecondaryButton}"
|
||||
Padding="9,4"
|
||||
FontSize="10"
|
||||
Margin="8,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
ToolTip="باز کردن صفحه GitHub پروژه TunnelX"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
@@ -859,23 +866,31 @@
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Log Header -->
|
||||
<Border Grid.Row="0" Padding="12,10" Background="#11FFFFFF">
|
||||
<Border Grid.Row="0" Padding="8,6" Background="#11FFFFFF">
|
||||
<Grid FlowDirection="{Binding AppFlowDirection}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Text="🔍 جزئیات عملکرد" FontSize="13" FontWeight="SemiBold"
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
|
||||
Text="🔍 جزئیات عملکرد" FontSize="12" FontWeight="SemiBold"
|
||||
Foreground="{StaticResource TextPrimaryBrush}"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,0,4"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
<ComboBox x:Name="LogFilterCombo"
|
||||
Grid.Column="1"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
SelectedIndex="0"
|
||||
SelectionChanged="OnLogFilterChanged"
|
||||
Margin="0,0,6,0"
|
||||
MinWidth="86"
|
||||
Margin="0,0,4,0"
|
||||
MinWidth="74"
|
||||
HorizontalAlignment="{Binding AppStartHorizontalAlignment}"
|
||||
FontSize="10"
|
||||
Style="{StaticResource DarkComboBox}">
|
||||
<ComboBoxItem Content="همه" Tag="All"/>
|
||||
<ComboBoxItem Content="خطا" Tag="Error"/>
|
||||
@@ -883,18 +898,19 @@
|
||||
<ComboBoxItem Content="DNS" Tag="Dns"/>
|
||||
<ComboBoxItem Content="Route" Tag="Route"/>
|
||||
</ComboBox>
|
||||
<StackPanel Grid.Column="2"
|
||||
<StackPanel Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Orientation="Horizontal"
|
||||
FlowDirection="{Binding AppFlowDirection}">
|
||||
<Button Content="🗑" ToolTip="پاک کردن" Click="OnLogClearClick"
|
||||
<Button Content="{Binding LogClearButtonText}" ToolTip="{Binding LogClearToolTipText}" Click="OnLogClearClick"
|
||||
Style="{StaticResource SecondaryButton}"
|
||||
Padding="8,4" FontSize="12" Margin="0,0,4,0"/>
|
||||
<Button Content="⚠" ToolTip="کپی آخرین خطا یا هشدار" Click="OnLogCopyLastErrorClick"
|
||||
Padding="6,3" FontSize="9.5" Margin="0,0,3,0"/>
|
||||
<Button Content="{Binding LogCopyErrorButtonText}" ToolTip="{Binding LogCopyErrorToolTipText}" Click="OnLogCopyLastErrorClick"
|
||||
Style="{StaticResource SecondaryButton}"
|
||||
Padding="8,4" FontSize="12" Margin="0,0,4,0"/>
|
||||
<Button Content="📋" ToolTip="کپی کردن" Click="OnLogCopyClick"
|
||||
Padding="6,3" FontSize="9.5" Margin="0,0,3,0"/>
|
||||
<Button Content="{Binding LogCopyAllButtonText}" ToolTip="{Binding LogCopyAllToolTipText}" Click="OnLogCopyClick"
|
||||
Style="{StaticResource SecondaryButton}"
|
||||
Padding="8,4" FontSize="12"/>
|
||||
Padding="6,3" FontSize="9.5"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
@@ -299,7 +299,7 @@
|
||||
<!-- Tab header bar with bottom border -->
|
||||
<Border Grid.Row="0" Background="{StaticResource SurfaceBrush}"
|
||||
BorderBrush="{StaticResource BorderBrush}" BorderThickness="0,0,0,1"
|
||||
Padding="6,6,6,4">
|
||||
Padding="4,4,4,3">
|
||||
<TabPanel IsItemsHost="True" HorizontalAlignment="Center"/>
|
||||
</Border>
|
||||
<!-- Tab content -->
|
||||
@@ -319,9 +319,9 @@
|
||||
<Setter Property="TextOptions.TextFormattingMode" Value="Ideal"/>
|
||||
<Setter Property="TextOptions.TextRenderingMode" Value="Auto"/>
|
||||
<Setter Property="TextOptions.TextHintingMode" Value="Fixed"/>
|
||||
<Setter Property="Padding" Value="6,6"/>
|
||||
<Setter Property="Width" Value="104"/>
|
||||
<Setter Property="MinHeight" Value="50"/>
|
||||
<Setter Property="Padding" Value="4,4"/>
|
||||
<Setter Property="Width" Value="88"/>
|
||||
<Setter Property="MinHeight" Value="44"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
@@ -341,13 +341,13 @@
|
||||
<ContentPresenter Grid.Row="0" ContentSource="Header"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,0,5"/>
|
||||
Margin="0,0,0,3"/>
|
||||
<!-- Bottom indicator line -->
|
||||
<Border x:Name="indicator" Grid.Row="1"
|
||||
Height="3" CornerRadius="1.5"
|
||||
Height="2" CornerRadius="1"
|
||||
Background="Transparent"
|
||||
HorizontalAlignment="Stretch"
|
||||
Margin="10,0"/>
|
||||
Margin="8,0"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
|
||||
@@ -6,6 +6,7 @@ public static class AppInfo
|
||||
public const string CreatorName = "MaxFan";
|
||||
public const string GitHubUrl = "https://github.com/MaxiFan/TunnelX";
|
||||
public const string LatestReleaseUrl = GitHubUrl + "/releases/latest";
|
||||
public const string TelegramContactUrl = "https://t.me/maxifaan";
|
||||
public const string LicenseName = "GPL-3.0-or-later";
|
||||
public const string PayPalEmail = "gallafan@gmail.com";
|
||||
public const string PayPalDonateUrl = "https://www.paypal.com/donate/?business=gallafan%40gmail.com¤cy_code=USD";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Net.Http;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace AppTunnel.Services;
|
||||
@@ -14,6 +15,8 @@ public static class GitHubReleaseChecker
|
||||
{
|
||||
private const string LatestReleaseApi =
|
||||
"https://api.github.com/repos/MaxiFan/TunnelX/releases/latest";
|
||||
private const string ReleasesApi =
|
||||
"https://api.github.com/repos/MaxiFan/TunnelX/releases";
|
||||
|
||||
public static async Task<GitHubReleaseInfo?> GetLatestReleaseAsync(CancellationToken ct)
|
||||
{
|
||||
@@ -47,9 +50,78 @@ public static class GitHubReleaseChecker
|
||||
return new GitHubReleaseInfo(version, tag, name, url, prerelease);
|
||||
}
|
||||
|
||||
public static async Task<long?> GetAppDownloadCountAsync(CancellationToken ct, int proxyPort = 0)
|
||||
{
|
||||
if (proxyPort > 0)
|
||||
{
|
||||
var proxied = await TryGetAppDownloadCountAsync(ct, proxyPort);
|
||||
if (proxied.HasValue)
|
||||
return proxied;
|
||||
}
|
||||
|
||||
return await TryGetAppDownloadCountAsync(ct, proxyPort: 0);
|
||||
}
|
||||
|
||||
private static async Task<long?> TryGetAppDownloadCountAsync(CancellationToken ct, int proxyPort)
|
||||
{
|
||||
using var handler = new HttpClientHandler();
|
||||
if (proxyPort > 0)
|
||||
{
|
||||
handler.Proxy = new WebProxy($"http://127.0.0.1:{proxyPort}");
|
||||
handler.UseProxy = true;
|
||||
}
|
||||
|
||||
using var http = new HttpClient(handler)
|
||||
{
|
||||
Timeout = TimeSpan.FromSeconds(10)
|
||||
};
|
||||
http.DefaultRequestHeaders.UserAgent.ParseAdd("TunnelX");
|
||||
|
||||
using var response = await http.GetAsync(ReleasesApi, ct);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
return null;
|
||||
|
||||
await using var stream = await response.Content.ReadAsStreamAsync(ct);
|
||||
using var json = await JsonDocument.ParseAsync(stream, cancellationToken: ct);
|
||||
if (json.RootElement.ValueKind != JsonValueKind.Array)
|
||||
return null;
|
||||
|
||||
long total = 0;
|
||||
foreach (var release in json.RootElement.EnumerateArray())
|
||||
{
|
||||
if (!release.TryGetProperty("assets", out var assets) || assets.ValueKind != JsonValueKind.Array)
|
||||
continue;
|
||||
|
||||
foreach (var asset in assets.EnumerateArray())
|
||||
{
|
||||
var name = asset.TryGetProperty("name", out var nameElement)
|
||||
? nameElement.GetString() ?? ""
|
||||
: "";
|
||||
if (!IsAppDownloadAsset(name))
|
||||
continue;
|
||||
|
||||
if (asset.TryGetProperty("download_count", out var countElement) &&
|
||||
countElement.TryGetInt64(out var count))
|
||||
total += count;
|
||||
}
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
public static bool TryParseVersion(string value, out Version version)
|
||||
{
|
||||
value = (value ?? "").Trim().TrimStart('v', 'V');
|
||||
return Version.TryParse(value, out version!);
|
||||
}
|
||||
|
||||
private static bool IsAppDownloadAsset(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
return false;
|
||||
|
||||
return (name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase) ||
|
||||
name.EndsWith(".zip", StringComparison.OrdinalIgnoreCase)) &&
|
||||
!name.EndsWith(".sha256", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,7 +350,11 @@ public sealed class LocalizationService : INotifyPropertyChanged
|
||||
["خطا"] = "Error",
|
||||
["هشدار"] = "Warning",
|
||||
["پاک کردن"] = "Clear",
|
||||
["کپی خطا"] = "Copy error",
|
||||
["کپی همه"] = "Copy all",
|
||||
["پاک کردن همه لاگها"] = "Clear all logs",
|
||||
["کپی آخرین خطا یا هشدار"] = "Copy latest error or warning",
|
||||
["کپی کردن همه لاگها"] = "Copy all logs",
|
||||
["کپی کردن"] = "Copy",
|
||||
["در حال بارگذاری..."] = "Loading...",
|
||||
|
||||
@@ -435,6 +439,9 @@ public sealed class LocalizationService : INotifyPropertyChanged
|
||||
["حمایت و تماس"] = "Support and Contact",
|
||||
["حمایت با پیپل"] = "Donate with PayPal",
|
||||
["حمایت"] = "Donate",
|
||||
["محل تبلیغات شما"] = "Your ad space",
|
||||
["درخواست تبلیغ"] = "Request advertising",
|
||||
["تبلیغ شما میتواند در معرض دید کاربران TunnelX با بیش از {0} نصب از GitHub باشد."] = "Your ad can reach TunnelX users backed by more than {0} GitHub installs.",
|
||||
["کپی اطلاعات حمایت"] = "Copy donation info",
|
||||
["اگر TunnelX برایتان مفید بوده، میتوانید با PayPal یا کریپتو از توسعه آن حمایت کنید."] = "If TunnelX has been useful, you can support its development with PayPal or crypto.",
|
||||
["پرداخت با PayPal"] = "Pay with PayPal",
|
||||
@@ -448,6 +455,7 @@ public sealed class LocalizationService : INotifyPropertyChanged
|
||||
["تنظیمات این کانفیگ بعد از ذخیره در لیست پروفایلها نمایش داده میشود."] = "This config appears in the profile list after saving.",
|
||||
["اطلاعات پروفایل"] = "Profile Info",
|
||||
["نام پروفایل"] = "Profile Name",
|
||||
["نام پروفایل *"] = "Profile Name *",
|
||||
["مثلاً کار، تلگرام، گیمینگ..."] = "e.g. Work, Telegram, Gaming...",
|
||||
["تنظیمات اتصال"] = "Connection Settings",
|
||||
["کانفیگ V2Ray / Xray"] = "V2Ray / Xray Config",
|
||||
@@ -532,6 +540,8 @@ public sealed class LocalizationService : INotifyPropertyChanged
|
||||
["حالت انتخابی"] = "Selected Mode",
|
||||
["ترافیک کل سیستم از تونل عبور خواهد کرد؛ برای وقتی مناسب است که همه برنامهها باید پشت تونل باشند."] = "All system traffic will use the tunnel; useful when every app must be behind the tunnel.",
|
||||
["فقط برنامهها و مقصدهای انتخابی از تونل عبور میکنند؛ بقیه ترافیک مستقیم میماند."] = "Only selected apps and destinations use the tunnel; the rest stays direct.",
|
||||
["پروفایل فعال"] = "Active profile",
|
||||
["پروفایل فعال: {0}"] = "Active profile: {0}",
|
||||
["متصل به پراکسی"] = "Connected to proxy",
|
||||
["متصل به VPN"] = "Connected to VPN",
|
||||
["توقف تست"] = "Stop test",
|
||||
@@ -544,6 +554,7 @@ public sealed class LocalizationService : INotifyPropertyChanged
|
||||
["بررسی نسخه جدید ناموفق بود. اتصال اینترنت یا GitHub را بررسی کنید."] = "Version check failed. Check your internet connection or GitHub access.",
|
||||
["نسخه جدید آماده است: {0} - برای دانلود از GitHub باز کنید."] = "New version available: {0} - open GitHub to download.",
|
||||
["TunnelX بهروز است. نسخه فعلی: {0}"] = "TunnelX is up to date. Current version: {0}",
|
||||
["تعداد نصب این برنامه از گیت هاب: {0}"] = "Installations from GitHub: {0}",
|
||||
["بررسی بروزرسانی به زمان مجاز نرسید."] = "Update check timed out.",
|
||||
["بررسی بروزرسانی ناموفق بود: {0}"] = "Update check failed: {0}",
|
||||
["آماده تست و اتصال"] = "Ready to test and connect",
|
||||
|
||||
@@ -91,6 +91,7 @@ public class ProfileService
|
||||
public bool AutoConnectOnStartup { get; set; } = false;
|
||||
public string? LastActiveProfileId { get; set; } = null;
|
||||
public string Language { get; set; } = LocalizationService.AutoLanguage;
|
||||
public long? GitHubAppDownloadCount { get; set; } = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -617,6 +617,11 @@ public class V2RayTunnelProvider : ITunnelProvider
|
||||
};
|
||||
if (security == "reality")
|
||||
{
|
||||
tlsObj["utls"] = new JsonObject
|
||||
{
|
||||
["enabled"] = true,
|
||||
["fingerprint"] = query.GetValueOrDefault("fp", "chrome")
|
||||
};
|
||||
tlsObj["reality"] = new JsonObject
|
||||
{
|
||||
["enabled"] = true,
|
||||
|
||||
@@ -161,6 +161,8 @@ public partial class MainViewModel
|
||||
_currentVpnGatewayIp = _vpnService.Status.VpnGatewayIp;
|
||||
_connectionStartTime = DateTime.Now;
|
||||
LastActiveProfileId = _selectedProfile?.Id;
|
||||
OnPropertyChanged(nameof(ConnectedProfileName));
|
||||
OnPropertyChanged(nameof(SelectedProfileSummaryText));
|
||||
RaiseHealthStatusChanged();
|
||||
|
||||
StartTrafficRouterForCurrentStatus(resetAppCounters: true);
|
||||
@@ -172,6 +174,7 @@ public partial class MainViewModel
|
||||
? _vpnService.Status.SingBoxMixedPort
|
||||
: _trafficRouter.Socks5Port;
|
||||
_ = RefreshExitIpAsync(exitIpProxyPort);
|
||||
_ = RefreshGitHubInstallCountAsync(exitIpProxyPort);
|
||||
|
||||
}
|
||||
else
|
||||
@@ -463,6 +466,7 @@ public partial class MainViewModel
|
||||
? _vpnService.Status.SingBoxMixedPort
|
||||
: _trafficRouter.Socks5Port;
|
||||
_ = RefreshExitIpAsync(exitIpProxyPort);
|
||||
_ = RefreshGitHubInstallCountAsync(exitIpProxyPort);
|
||||
if (wasFullRoute)
|
||||
{
|
||||
_isFullRouteEnabled = _trafficRouter.SetFullRouteEnabled(true);
|
||||
|
||||
@@ -73,6 +73,7 @@ public partial class MainViewModel : INotifyPropertyChanged
|
||||
OpenOpenVpnCommunityDownloadCommand = new RelayCommand(_ => OpenExternalLink(OpenVpnCommunityDownloadUrl));
|
||||
OpenGitHubCommand = new RelayCommand(_ => OpenExternalLink(AppInfo.GitHubUrl));
|
||||
OpenDonateCommand = new RelayCommand(_ => OpenExternalLink(AppInfo.PayPalDonateUrl));
|
||||
OpenAdRequestCommand = new RelayCommand(_ => OpenExternalLink(AppInfo.TelegramContactUrl));
|
||||
CopyDonationInfoCommand = new RelayCommand(_ => CopyDonationInfoToClipboard());
|
||||
CheckForUpdatesCommand = new RelayCommand(_ => _ = CheckForUpdatesAsync(false), _ => !IsCheckingForUpdates);
|
||||
OpenLatestReleaseCommand = new RelayCommand(_ => OpenExternalLink(LatestReleaseUrl), _ => !string.IsNullOrWhiteSpace(LatestReleaseUrl));
|
||||
@@ -380,6 +381,17 @@ public partial class MainViewModel : INotifyPropertyChanged
|
||||
public string AppGitHubUrl => AppInfo.GitHubUrl;
|
||||
public string AppLicenseText => AppInfo.LicenseName;
|
||||
public string AppLicenseDisplayText => LocalizationService.Instance.Format("لایسنس: {0}", AppInfo.LicenseName);
|
||||
public string AdPlaceholderTitleText => LocalizationService.Instance.T("محل تبلیغات شما");
|
||||
public string AdRequestButtonText => LocalizationService.Instance.T("درخواست تبلیغ");
|
||||
public string AdAudienceText => _githubInstallCount.HasValue
|
||||
? LocalizationService.Instance.Format("تبلیغ شما میتواند در معرض دید کاربران TunnelX با بیش از {0} نصب از GitHub باشد.", GitHubInstallCountDisplay)
|
||||
: "";
|
||||
public string LogClearButtonText => LocalizationService.Instance.T("پاک کردن");
|
||||
public string LogCopyErrorButtonText => LocalizationService.Instance.T("کپی خطا");
|
||||
public string LogCopyAllButtonText => LocalizationService.Instance.T("کپی همه");
|
||||
public string LogClearToolTipText => LocalizationService.Instance.T("پاک کردن همه لاگها");
|
||||
public string LogCopyErrorToolTipText => LocalizationService.Instance.T("کپی آخرین خطا یا هشدار");
|
||||
public string LogCopyAllToolTipText => LocalizationService.Instance.T("کپی کردن همه لاگها");
|
||||
public string DonatePayPalText => LocalizationService.Instance.IsRightToLeft
|
||||
? $"پیپل: {AppInfo.PayPalEmail}"
|
||||
: $"PayPal: {AppInfo.PayPalEmail}";
|
||||
@@ -438,6 +450,20 @@ public partial class MainViewModel : INotifyPropertyChanged
|
||||
}
|
||||
}
|
||||
|
||||
private long? _githubInstallCount;
|
||||
private int _githubInstallCountRequestId;
|
||||
|
||||
public bool HasGitHubInstallCount => _githubInstallCount.HasValue;
|
||||
|
||||
public string GitHubInstallCountText => _githubInstallCount.HasValue
|
||||
? LocalizationService.Instance.Format(
|
||||
"تعداد نصب این برنامه از گیت هاب: {0}",
|
||||
GitHubInstallCountDisplay)
|
||||
: "";
|
||||
|
||||
private string GitHubInstallCountDisplay =>
|
||||
(_githubInstallCount ?? 0).ToString("N0", System.Globalization.CultureInfo.InvariantCulture);
|
||||
|
||||
public string UpdateButtonText => IsCheckingForUpdates
|
||||
? LocalizationService.Instance.T("در حال بررسی...")
|
||||
: LocalizationService.Instance.T("بررسی بروزرسانی");
|
||||
@@ -819,6 +845,14 @@ public partial class MainViewModel : INotifyPropertyChanged
|
||||
? LocalizationService.Instance.T("متصل به پراکسی")
|
||||
: LocalizationService.Instance.T("متصل به VPN");
|
||||
|
||||
public string ConnectedProfileName => string.IsNullOrWhiteSpace(SelectedProfileName)
|
||||
? LocalizationService.Instance.T("پروفایل فعال")
|
||||
: SelectedProfileName;
|
||||
|
||||
public string SelectedProfileSummaryText => LocalizationService.Instance.Format(
|
||||
"پروفایل فعال: {0}",
|
||||
ConnectedProfileName);
|
||||
|
||||
private string ActiveCoreName => CurrentTunnelType switch
|
||||
{
|
||||
TunnelType.L2tpIpsec => "L2TP",
|
||||
@@ -1036,6 +1070,7 @@ public partial class MainViewModel : INotifyPropertyChanged
|
||||
public ICommand OpenOpenVpnCommunityDownloadCommand { get; }
|
||||
public ICommand OpenGitHubCommand { get; }
|
||||
public ICommand OpenDonateCommand { get; }
|
||||
public ICommand OpenAdRequestCommand { get; }
|
||||
public ICommand CopyDonationInfoCommand { get; }
|
||||
public ICommand CheckForUpdatesCommand { get; }
|
||||
public ICommand OpenLatestReleaseCommand { get; }
|
||||
@@ -1134,6 +1169,61 @@ public partial class MainViewModel : INotifyPropertyChanged
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshGitHubInstallCountAsync(int proxyPort)
|
||||
{
|
||||
var requestId = ++_githubInstallCountRequestId;
|
||||
|
||||
try
|
||||
{
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(12));
|
||||
var count = await GitHubReleaseChecker.GetAppDownloadCountAsync(cts.Token, proxyPort);
|
||||
if (requestId != _githubInstallCountRequestId || !IsConnected)
|
||||
return;
|
||||
|
||||
if (count.HasValue)
|
||||
{
|
||||
SetGitHubInstallCount(count.Value, persist: true);
|
||||
Logger.Info($"[GITHUB-STATS] App downloads={count.Value}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning("[GITHUB-STATS] App download count unavailable");
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Logger.Warning("[GITHUB-STATS] App download count timed out");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warning($"[GITHUB-STATS] App download count failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void SetGitHubInstallCount(long? count, bool persist = false)
|
||||
{
|
||||
if (_githubInstallCount == count)
|
||||
{
|
||||
if (persist && _appSettings.GitHubAppDownloadCount != count)
|
||||
{
|
||||
_appSettings.GitHubAppDownloadCount = count;
|
||||
_profileService.SaveAppSettings(_appSettings);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
_githubInstallCount = count;
|
||||
if (persist)
|
||||
{
|
||||
_appSettings.GitHubAppDownloadCount = count;
|
||||
_profileService.SaveAppSettings(_appSettings);
|
||||
}
|
||||
|
||||
OnPropertyChanged(nameof(HasGitHubInstallCount));
|
||||
OnPropertyChanged(nameof(GitHubInstallCountText));
|
||||
OnPropertyChanged(nameof(AdAudienceText));
|
||||
}
|
||||
|
||||
private void PasteConfigFromClipboard()
|
||||
{
|
||||
try
|
||||
@@ -1347,6 +1437,8 @@ public partial class MainViewModel : INotifyPropertyChanged
|
||||
|
||||
_selectedProfile.Name = remark;
|
||||
OnPropertyChanged(nameof(SelectedProfileName));
|
||||
OnPropertyChanged(nameof(ConnectedProfileName));
|
||||
OnPropertyChanged(nameof(SelectedProfileSummaryText));
|
||||
}
|
||||
|
||||
private static string ExtractConfigRemark(string config)
|
||||
@@ -1395,8 +1487,12 @@ public partial class MainViewModel : INotifyPropertyChanged
|
||||
LocalizationService.Instance.Initialize(_appSettings.Language);
|
||||
_startWithWindows = _appSettings.StartWithWindows;
|
||||
_autoConnectOnStartup = _appSettings.AutoConnectOnStartup;
|
||||
_githubInstallCount = _appSettings.GitHubAppDownloadCount;
|
||||
OnPropertyChanged(nameof(StartWithWindows));
|
||||
OnPropertyChanged(nameof(AutoConnectOnStartup));
|
||||
OnPropertyChanged(nameof(HasGitHubInstallCount));
|
||||
OnPropertyChanged(nameof(GitHubInstallCountText));
|
||||
OnPropertyChanged(nameof(AdAudienceText));
|
||||
OnPropertyChanged(nameof(LanguageToggleText));
|
||||
OnPropertyChanged(nameof(AppIsRightToLeft));
|
||||
OnPropertyChanged(nameof(AppFlowDirection));
|
||||
@@ -1429,8 +1525,18 @@ public partial class MainViewModel : INotifyPropertyChanged
|
||||
OnPropertyChanged(nameof(DonatePayPalText));
|
||||
OnPropertyChanged(nameof(CryptoDonationText));
|
||||
OnPropertyChanged(nameof(AppCreatorText));
|
||||
OnPropertyChanged(nameof(AdPlaceholderTitleText));
|
||||
OnPropertyChanged(nameof(AdRequestButtonText));
|
||||
OnPropertyChanged(nameof(AdAudienceText));
|
||||
OnPropertyChanged(nameof(LogClearButtonText));
|
||||
OnPropertyChanged(nameof(LogCopyErrorButtonText));
|
||||
OnPropertyChanged(nameof(LogCopyAllButtonText));
|
||||
OnPropertyChanged(nameof(LogClearToolTipText));
|
||||
OnPropertyChanged(nameof(LogCopyErrorToolTipText));
|
||||
OnPropertyChanged(nameof(LogCopyAllToolTipText));
|
||||
OnPropertyChanged(nameof(UpdateButtonText));
|
||||
OnPropertyChanged(nameof(UpdateStatusText));
|
||||
OnPropertyChanged(nameof(GitHubInstallCountText));
|
||||
OnPropertyChanged(nameof(ConnectButtonText));
|
||||
OnPropertyChanged(nameof(ConnectButtonToolTip));
|
||||
OnPropertyChanged(nameof(StatusText));
|
||||
@@ -1452,6 +1558,8 @@ public partial class MainViewModel : INotifyPropertyChanged
|
||||
OnPropertyChanged(nameof(HealthIpv6Text));
|
||||
OnPropertyChanged(nameof(HealthRoutesText));
|
||||
OnPropertyChanged(nameof(ConnectedBadgeText));
|
||||
OnPropertyChanged(nameof(ConnectedProfileName));
|
||||
OnPropertyChanged(nameof(SelectedProfileSummaryText));
|
||||
OnPropertyChanged(nameof(PingButtonText));
|
||||
OnPropertyChanged(nameof(ConnectedServerPingButtonText));
|
||||
OnPropertyChanged(nameof(ServerPingButtonText));
|
||||
|
||||
@@ -24,6 +24,8 @@ public partial class MainViewModel
|
||||
_selectedProfile = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(SelectedProfileName));
|
||||
OnPropertyChanged(nameof(ConnectedProfileName));
|
||||
OnPropertyChanged(nameof(SelectedProfileSummaryText));
|
||||
RaiseProfileCardChanged();
|
||||
if (value != null)
|
||||
LoadProfileIntoUi(value);
|
||||
@@ -86,6 +88,8 @@ public partial class MainViewModel
|
||||
_selectedProfile = Profiles[0];
|
||||
OnPropertyChanged(nameof(SelectedProfile));
|
||||
OnPropertyChanged(nameof(SelectedProfileName));
|
||||
OnPropertyChanged(nameof(ConnectedProfileName));
|
||||
OnPropertyChanged(nameof(SelectedProfileSummaryText));
|
||||
LoadProfileIntoUi(Profiles[0]);
|
||||
}
|
||||
|
||||
@@ -253,7 +257,7 @@ public partial class MainViewModel
|
||||
SaveCurrentProfileState();
|
||||
var profile = new ConnectionProfile
|
||||
{
|
||||
Name = LocalizationService.Instance.Format("پروفایل {0}", Profiles.Count + 1),
|
||||
Name = "",
|
||||
MixedProxyPort = MixedProxyPort,
|
||||
AutoTuneMtu = AutoTuneMtu,
|
||||
EnableDnsOptimization = IsDnsOptimizationEnabled,
|
||||
@@ -379,6 +383,8 @@ public partial class MainViewModel
|
||||
private void RaiseProfileCardChanged()
|
||||
{
|
||||
OnPropertyChanged(nameof(ProfileCountText));
|
||||
OnPropertyChanged(nameof(ConnectedProfileName));
|
||||
OnPropertyChanged(nameof(SelectedProfileSummaryText));
|
||||
OnPropertyChanged(nameof(ActiveProfileTypeText));
|
||||
OnPropertyChanged(nameof(ActiveProfileEndpointText));
|
||||
OnPropertyChanged(nameof(ProfileSaveHintText));
|
||||
|
||||
@@ -252,12 +252,11 @@
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
</StackPanel>
|
||||
<TextBlock FontSize="10"
|
||||
Text="{Binding SelectedProfileSummaryText}"
|
||||
Foreground="{StaticResource TextSecondaryBrush}"
|
||||
Margin="0,4,0,0"
|
||||
TextTrimming="CharacterEllipsis">
|
||||
<Run Text="پروفایل فعال: "/>
|
||||
<Run Text="{Binding SelectedProfile.Name}" FontWeight="SemiBold"/>
|
||||
</TextBlock>
|
||||
FontWeight="SemiBold"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
</StackPanel>
|
||||
|
||||
<Button Grid.Column="2"
|
||||
@@ -671,10 +670,13 @@
|
||||
Foreground="{StaticResource SuccessBrush}"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0,2,0,0"/>
|
||||
<TextBlock Text="{Binding SelectedProfile.Name}" FontSize="9"
|
||||
<TextBlock Text="{Binding ConnectedProfileName}" FontSize="10"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{StaticResource TextSecondaryBrush}"
|
||||
HorizontalAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
TextAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
MaxWidth="120"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
@@ -909,6 +911,48 @@
|
||||
</Button.Style>
|
||||
</Button>
|
||||
|
||||
<Border Background="#24E8803A"
|
||||
BorderBrush="#66E8803A"
|
||||
BorderThickness="1"
|
||||
CornerRadius="20"
|
||||
Padding="24,18"
|
||||
Margin="0,16,0,0"
|
||||
MinWidth="560"
|
||||
MaxWidth="760"
|
||||
HorizontalAlignment="Stretch">
|
||||
<StackPanel HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FlowDirection="{Binding AppFlowDirection}">
|
||||
<TextBlock Text="{Binding AdPlaceholderTitleText}"
|
||||
FontSize="20"
|
||||
FontWeight="Bold"
|
||||
Foreground="{StaticResource TextPrimaryBrush}"
|
||||
HorizontalAlignment="Center"
|
||||
TextAlignment="Center"/>
|
||||
<TextBlock Text="{Binding AdAudienceText}"
|
||||
Visibility="{Binding HasGitHubInstallCount, Converter={StaticResource BoolToVis}}"
|
||||
FontSize="12"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{StaticResource TextSecondaryBrush}"
|
||||
HorizontalAlignment="Center"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap"
|
||||
MaxWidth="560"
|
||||
Margin="0,6,0,0"/>
|
||||
<Button
|
||||
Content="{Binding AdRequestButtonText}"
|
||||
Command="{Binding OpenAdRequestCommand}"
|
||||
Style="{StaticResource PrimaryButton}"
|
||||
Padding="18,10"
|
||||
FontSize="13"
|
||||
MinWidth="130"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0,12,0,0"
|
||||
VerticalAlignment="Center"
|
||||
ToolTip="{Binding AdRequestButtonText}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
</StackPanel>
|
||||
<!-- End Connected State -->
|
||||
|
||||
|
||||
@@ -73,10 +73,20 @@
|
||||
<Border Style="{StaticResource CardPanel}" Padding="12" Margin="0,0,0,8">
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource SectionHeader}" Text="اطلاعات پروفایل"/>
|
||||
<TextBlock Style="{StaticResource FieldLabel}" Text="نام پروفایل"/>
|
||||
<TextBox Style="{StaticResource ModernTextBox}"
|
||||
<TextBlock Style="{StaticResource FieldLabel}" Text="نام پروفایل *"/>
|
||||
<TextBox x:Name="ProfileNameField"
|
||||
Style="{StaticResource ModernTextBox}"
|
||||
Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"
|
||||
Tag="مثلاً کار، تلگرام، گیمینگ..."/>
|
||||
Tag="مثلاً کار، تلگرام، گیمینگ..."
|
||||
TextChanged="OnProfileNameTextChanged"/>
|
||||
<TextBlock x:Name="ProfileNameValidationText"
|
||||
Text="نام پروفایل را وارد کنید"
|
||||
Visibility="Collapsed"
|
||||
FontSize="10"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{StaticResource WarningBrush}"
|
||||
Margin="0,4,0,0"
|
||||
TextWrapping="Wrap"/>
|
||||
|
||||
<TextBlock Style="{StaticResource FieldLabel}" Text="نوع اتصال" Margin="0,8,0,3"/>
|
||||
<ComboBox Style="{StaticResource DarkComboBox}"
|
||||
|
||||
@@ -79,6 +79,13 @@ public partial class ProfileEditorDialog : Window
|
||||
|
||||
private void OnSaveClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_profile.Name = (_profile.Name ?? "").Trim();
|
||||
if (string.IsNullOrWhiteSpace(_profile.Name))
|
||||
{
|
||||
ShowProfileNameError();
|
||||
return;
|
||||
}
|
||||
|
||||
_profile.Password = L2tpPasswordField.Password;
|
||||
_profile.PreSharedKey = PskField.Password;
|
||||
_profile.OpenVpnPassword = OpenVpnPasswordField.Password;
|
||||
@@ -125,6 +132,30 @@ public partial class ProfileEditorDialog : Window
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnProfileNameTextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(ProfileNameField.Text))
|
||||
ClearProfileNameError();
|
||||
}
|
||||
|
||||
private void ShowProfileNameError()
|
||||
{
|
||||
var warningBrush = TryFindResource("WarningBrush") as System.Windows.Media.Brush ?? System.Windows.Media.Brushes.Orange;
|
||||
ProfileNameField.BorderBrush = warningBrush;
|
||||
ProfileNameField.BorderThickness = new Thickness(2);
|
||||
ProfileNameValidationText.Text = LocalizationService.Instance.T("نام پروفایل را وارد کنید");
|
||||
ProfileNameValidationText.Visibility = Visibility.Visible;
|
||||
ValidationText.Text = "";
|
||||
ProfileNameField.Focus();
|
||||
}
|
||||
|
||||
private void ClearProfileNameError()
|
||||
{
|
||||
ProfileNameField.ClearValue(BorderBrushProperty);
|
||||
ProfileNameField.ClearValue(BorderThicknessProperty);
|
||||
ProfileNameValidationText.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void OnCancelClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = false;
|
||||
|
||||
@@ -2,6 +2,23 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 1.2.32 - 2026-05-18
|
||||
|
||||
### English
|
||||
|
||||
- Fixed VLESS REALITY configs in the sing-box path by enabling uTLS with the configured fingerprint, preventing startup failures that reported `uTLS is required by reality client`.
|
||||
- Replaced icon-only log-panel actions with compact localized text buttons for clearing logs, copying the latest error, and copying all logs.
|
||||
|
||||
### فارسی
|
||||
|
||||
- مشکل کانفیگهای VLESS REALITY در مسیر sing-box اصلاح شد؛ uTLS با fingerprint کانفیگ فعال میشود تا خطای `uTLS is required by reality client` هنگام شروع اتصال رخ ندهد.
|
||||
- دکمههای فقطآیکونی پنل لاگ با دکمههای متنی و فشرده جایگزین شدند تا پاک کردن لاگ، کپی آخرین خطا و کپی همه لاگها در فارسی و انگلیسی واضح باشد.
|
||||
|
||||
## 1.2.31 - 2026-05-18
|
||||
|
||||
- Update release packaging and connection UX
|
||||
- Update README contact and localization notes
|
||||
|
||||
## 1.2.30 - 2026-05-18
|
||||
|
||||
### English
|
||||
@@ -119,3 +136,5 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+8
-3
@@ -4,7 +4,7 @@
|
||||
|
||||
فارسی | <span dir="ltr">[English](README.md)</span> | <span dir="ltr">[Русский](README.md#русский)</span> | <span dir="ltr">[简体中文](README.md#简体中文)</span>
|
||||
|
||||
<span dir="ltr">TunnelX</span> یک نرمافزار آزاد و رایگان برای ویندوز است که توسط **<span dir="ltr">MaxFan</span>** ساخته شده و برای مدیریت تونل، ویپیان و <span dir="ltr">Split Tunneling</span> استفاده میشود. این برنامه میتواند ترافیک برنامههای انتخابشده، مقصدهای مشخص، یا کل سیستم را از تونل عبور دهد و همزمان مسیر عادی شبکه را برای مقصدهای محلی یا مستثنیشده حفظ کند.
|
||||
<span dir="ltr">TunnelX</span> یک نرمافزار آزاد و رایگان برای ویندوز است که توسط **<span dir="ltr">MaxFan</span>** ساخته شده و برای مدیریت تونل، ویپیان و <span dir="ltr">Split Tunneling</span> استفاده میشود. این برنامه میتواند ترافیک برنامههای انتخابشده، مقصدهای مشخص، یا کل سیستم را از تونل عبور دهد و همزمان مسیر عادی شبکه را برای مقصدهای محلی یا مستثنیشده حفظ کند. رابط برنامه دوزبانه است، زبان سیستم را تشخیص میدهد و چینش راستبهچپ/چپبهراست را برای فارسی و انگلیسی رعایت میکند.
|
||||
|
||||
## کاربرد برنامه
|
||||
|
||||
@@ -21,7 +21,8 @@
|
||||
- پروکسی <span dir="ltr">SOCKS5</span> محلی روی <span dir="ltr">`127.0.0.1`</span> برای ابزارهایی که تنظیم پروکسی داخلی دارند
|
||||
- تغییر مسیر <span dir="ltr">DNS</span>، مسدودسازی <span dir="ltr">IPv6</span>، محافظ نشت، عیبیابی <span dir="ltr">route</span> و تاریخچه مصرف تونل
|
||||
- مدیریت چند پروفایل، کپی/ویرایش کانفیگها، تست سرور، تشخیص <span dir="ltr">IP</span> خروجی و اعلان بروزرسانی
|
||||
- رابط کاربری فارسیمحور برای ویندوز
|
||||
- رابط کاربری فارسی و انگلیسی با تشخیص خودکار زبان، دکمه تغییر زبان و رعایت کامل راستبهچپ/چپبهراست
|
||||
- انتخاب پورت داخلی آزاد برای <span dir="ltr">V2Ray/Xray</span> تا خطاهای اشغال بودن پورتهای <span dir="ltr">`2080/2081`</span> کمتر شود
|
||||
|
||||
## شروع سریع
|
||||
|
||||
@@ -131,9 +132,13 @@ dotnet publish AppTunnel\AppTunnel.csproj -c Release -r win-x64 --self-contained
|
||||
|
||||
<span dir="ltr">TunnelX</span> آزاد و رایگان است. حمایت مالی کاملا اختیاری است و فقط به نگهداری و توسعه پروژه کمک میکند.
|
||||
|
||||
برای ارتباط مستقیم، درخواست پشتیبانی، سفارشیسازی خصوصی یا سفارش توسعه، از طریق تلگرام پیام بدهید: <span dir="ltr">[t.me/maxifaan](https://t.me/maxifaan)</span>
|
||||
|
||||
خدمات پولی میتواند به صورت جداگانه برای پشتیبانی خصوصی، راهاندازی، بیلد اختصاصی، سفارشیسازی برای شرکتها، یا توسعه برنامهای مشابه ارائه شود. این خدمات پولی حقوقی را که مجوز <span dir="ltr">GPL</span> به کاربران میدهد محدود نمیکند.
|
||||
|
||||
گزینههای حمایت و راههای تماس از طریق <span dir="ltr">GitHub Sponsors/Funding</span> یا فایل <span dir="ltr">`docs/DONATE.md`</span> در دسترس هستند.
|
||||
پذیرش تبلیغات ثابت داخل <span dir="ltr">TunnelX</span> امکانپذیر است. تبلیغات بهصورت مستقیم با نگهدارنده هماهنگ میشود، از طریق شبکههای تبلیغاتی یا سایتهای واسط نمایش داده نمیشود و با هدف ساده، ثابت و امن ماندن تجربه کاربر انجام میشود.
|
||||
|
||||
گزینههای حمایت مالی از طریق <span dir="ltr">GitHub Sponsors/Funding</span> یا فایل <span dir="ltr">`docs/DONATE.md`</span> در دسترس هستند.
|
||||
|
||||
## نکته ایمنی و سلب مسئولیت
|
||||
|
||||
|
||||
@@ -2,24 +2,28 @@
|
||||
|
||||
[فارسی](README.fa.md) | English | [Русский](#русский) | [简体中文](#简体中文)
|
||||
|
||||
TunnelX is a free and open-source Windows split-tunneling client built by **MaxFan**. It routes selected apps, selected destinations, or the whole system through supported tunnel cores while keeping local and excluded destinations on the normal network path.
|
||||
TunnelX is a free and open-source Windows split-tunneling client built by **MaxFan**. It routes selected apps, selected destinations, or the whole system through supported tunnel cores while keeping local and excluded destinations on the normal network path. The app supports Persian and English UI modes with automatic system-language detection and correct RTL/LTR layout handling.
|
||||
|
||||
## Русский
|
||||
|
||||
TunnelX — бесплатный клиент split tunneling для Windows от **MaxFan**. Он позволяет направлять через VPN, V2Ray/Xray, OpenVPN или SOCKS5/HTTP Proxy только выбранные приложения, выбранные домены/IP или весь системный трафик.
|
||||
TunnelX — бесплатный клиент split tunneling для Windows от **MaxFan**. Он позволяет направлять через VPN, V2Ray/Xray, OpenVPN или SOCKS5/HTTP Proxy только выбранные приложения, выбранные домены/IP или весь системный трафик. Интерфейс поддерживает персидский и английский языки с автоматическим выбором языка системы и корректным RTL/LTR отображением.
|
||||
|
||||
Основные возможности: профили L2TP/IPsec, V2Ray/Xray, SOCKS5/HTTP Proxy и OpenVPN Community; выбор приложений для туннеля; правила include/exclude для доменов и IP; режим Full Route; локальный прокси `127.0.0.1`; отображение публичного выходного IP; история трафика; защита от DNS/IPv6/leak проблем.
|
||||
|
||||
Для обычного использования скачайте последний standalone-файл из [GitHub Releases](https://github.com/MaxiFan/TunnelX/releases/latest), запустите TunnelX от имени Administrator, создайте профиль подключения, выберите приложения для туннеля и подключитесь. Отдельная установка .NET Runtime для standalone-сборки не требуется.
|
||||
|
||||
Связаться с автором можно в Telegram: [t.me/maxifaan](https://t.me/maxifaan).
|
||||
|
||||
## 简体中文
|
||||
|
||||
TunnelX 是由 **MaxFan** 构建的免费 Windows 分流隧道客户端。它可以只让选定的应用、指定的域名/IP,或整个系统流量通过 VPN、V2Ray/Xray、OpenVPN 或 SOCKS5/HTTP Proxy,同时让本地或排除的目标继续走普通网络。
|
||||
TunnelX 是由 **MaxFan** 构建的免费 Windows 分流隧道客户端。它可以只让选定的应用、指定的域名/IP,或整个系统流量通过 VPN、V2Ray/Xray、OpenVPN 或 SOCKS5/HTTP Proxy,同时让本地或排除的目标继续走普通网络。应用支持波斯语和英语界面,可自动检测系统语言并正确处理 RTL/LTR 布局。
|
||||
|
||||
主要功能包括:L2TP/IPsec、V2Ray/Xray、SOCKS5/HTTP Proxy 和 OpenVPN Community 配置文件;按应用分流;域名/IP include 与 exclude 规则;Full Route 全局模式;本地 `127.0.0.1` 代理;公网出口 IP 显示;流量历史;DNS、IPv6 与泄漏防护诊断。
|
||||
|
||||
普通用户可以从 [GitHub Releases](https://github.com/MaxiFan/TunnelX/releases/latest) 下载最新 standalone 版本,以 Administrator 权限运行 TunnelX,创建连接配置,选择需要进入隧道的应用,然后连接。standalone 版本不需要单独安装 .NET Runtime。
|
||||
|
||||
可通过 Telegram 联系作者:[t.me/maxifaan](https://t.me/maxifaan)。
|
||||
|
||||
## Features
|
||||
|
||||
- App-based split tunneling for selected Windows processes
|
||||
@@ -31,7 +35,8 @@ TunnelX 是由 **MaxFan** 构建的免费 Windows 分流隧道客户端。它可
|
||||
- Local SOCKS5 proxy for tools that need `127.0.0.1`
|
||||
- DNS redirect, IPv6 blocking, leak guard, route diagnostics, and traffic history
|
||||
- Multiple profiles, duplicate/edit flows, server tests, public exit IP detection, and release update checks
|
||||
- Persian-first Windows desktop UI
|
||||
- Persian and English desktop UI with automatic language detection, manual language switching, and correct RTL/LTR layout behavior
|
||||
- Dynamic local port selection for V2Ray/Xray internals to reduce `2080/2081` binding conflicts
|
||||
|
||||
## Quick Start
|
||||
|
||||
@@ -137,9 +142,13 @@ TunnelX is licensed under **GPL-3.0-or-later**. Commercial use is allowed under
|
||||
|
||||
TunnelX is free and open-source. Donations are optional and help keep the project maintained.
|
||||
|
||||
For direct contact, support requests, private customization, or paid development work, message MaxFan on Telegram: [t.me/maxifaan](https://t.me/maxifaan).
|
||||
|
||||
Paid services may be available separately for private support, deployment help, custom builds, company-specific customization, or development of a similar application. These paid services do not limit the rights granted by the GPL license.
|
||||
|
||||
Use the GitHub funding button or see `docs/DONATE.md` for donation and contact options.
|
||||
Fixed advertising placements may be available inside TunnelX. Advertising is handled directly with the maintainer, is not served through third-party ad networks or intermediary websites, and is intended to stay simple, static, and safe for users.
|
||||
|
||||
Use the GitHub funding button or see `docs/DONATE.md` for donation options.
|
||||
|
||||
## Safety Notice
|
||||
|
||||
|
||||
Reference in New Issue
Block a user