mirror of
https://github.com/BeyondDimension/SteamTools.git
synced 2026-10-19 07:01:35 +08:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a33dcd30bc | |||
| ac3c348a93 | |||
| bca80245bb | |||
| 0f6c127ad3 | |||
| c087a02303 | |||
| 6c84a876ff | |||
| df27068329 | |||
| f10df31edd | |||
| 902919cf98 | |||
| a6aec5ad62 | |||
| 2fd651fd25 | |||
| 67615028e1 | |||
| 1eca159e23 | |||
| d1d9ca82df | |||
| 88bd2dbfe9 | |||
| ae2b2970db |
@@ -68,19 +68,19 @@ jobs:
|
||||
run: dotnet --info
|
||||
|
||||
- name: Build BD.WTTS.Client.AppHost
|
||||
run: dotnet build -c Release src\BD.WTTS.Client.AppHost\BD.WTTS.Client.AppHost.csproj --nologo -v q --property:WarningLevel=1;DebugType=pdbonly
|
||||
run: dotnet build -c Release src\BD.WTTS.Client.AppHost\BD.WTTS.Client.AppHost.csproj --nologo -v q --property:WarningLevel=1 --property:DebugType=pdbonly
|
||||
|
||||
- name: Build BD.WTTS.Client.Plugins.Accelerator.ReverseProxy
|
||||
run: dotnet build -c Release src\BD.WTTS.Client.Plugins.Accelerator.ReverseProxy\BD.WTTS.Client.Plugins.Accelerator.ReverseProxy.csproj --nologo -v q --property:WarningLevel=1;DebugType=pdbonly
|
||||
run: dotnet build -c Release src\BD.WTTS.Client.Plugins.Accelerator.ReverseProxy\BD.WTTS.Client.Plugins.Accelerator.ReverseProxy.csproj --nologo -v q --property:WarningLevel=1 --property:DebugType=pdbonly
|
||||
|
||||
- name: Build BD.WTTS.Client.Avalonia.App
|
||||
run: dotnet build -c Release src\BD.WTTS.Client.Avalonia.App\BD.WTTS.Client.Avalonia.App.csproj --nologo -v q --property:WarningLevel=1;DebugType=pdbonly
|
||||
run: dotnet build -c Release src\BD.WTTS.Client.Avalonia.App\BD.WTTS.Client.Avalonia.App.csproj --nologo -v q --property:WarningLevel=1 --property:DebugType=pdbonly
|
||||
|
||||
- name: Build BD.WTTS.Client.Avalonia.Designer.HostApp
|
||||
run: dotnet build -c Release src\BD.WTTS.Client.Avalonia.Designer.HostApp\BD.WTTS.Client.Avalonia.Designer.HostApp.csproj --nologo -v q --property:WarningLevel=1;DebugType=pdbonly
|
||||
run: dotnet build -c Release src\BD.WTTS.Client.Avalonia.Designer.HostApp\BD.WTTS.Client.Avalonia.Designer.HostApp.csproj --nologo -v q --property:WarningLevel=1 --property:DebugType=pdbonly
|
||||
|
||||
- name: Test
|
||||
run: dotnet test src/BD.WTTS.UnitTest/BD.WTTS.UnitTest.csproj -c Release -p:GeneratePackageOnBuild=false --nologo -v q --property:WarningLevel=1;DebugType=pdbonly
|
||||
run: dotnet test src/BD.WTTS.UnitTest/BD.WTTS.UnitTest.csproj -c Release -p:GeneratePackageOnBuild=false --nologo -v q --property:WarningLevel=1 --property:DebugType=pdbonly
|
||||
|
||||
# release:
|
||||
# name: Release
|
||||
|
||||
@@ -964,8 +964,8 @@
|
||||
},
|
||||
{
|
||||
"TypeName": "bool",
|
||||
"PropertyName": "UseDoh",
|
||||
"DefaultValue": "true",
|
||||
"PropertyName": "UseDoh2",
|
||||
"DefaultValue": "false",
|
||||
"DefaultValueIsConst": true,
|
||||
"Summary": "启用 DNS over HTTPS",
|
||||
"IsRegionOrEndregion": null,
|
||||
|
||||
+1
-1
Submodule ref/SteamClient updated: fddb907049...2bbd99f0ee
Submodule ref/WTTS.MicroServices.ClientSDK updated: 5f3111baad...2d718b32ae
@@ -13,6 +13,7 @@ public static class WindowTransparencyLevelExtensions
|
||||
WindowBackgroundMaterial.Blur => WindowTransparencyLevel.Blur,
|
||||
WindowBackgroundMaterial.AcrylicBlur => WindowTransparencyLevel.AcrylicBlur,
|
||||
WindowBackgroundMaterial.Mica => WindowTransparencyLevel.Mica,
|
||||
WindowBackgroundMaterial.Transparent => WindowTransparencyLevel.Transparent,
|
||||
_ => WindowTransparencyLevel.None,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -135,11 +135,11 @@ public class ReactiveAppWindow<TViewModel> : AppWindow, IViewFor<TViewModel>, IV
|
||||
|
||||
if (SizePosition.Height > 0 &&
|
||||
primaryScreenBounds.Height >= SizePosition.Height)
|
||||
Height = SizePosition.Height;
|
||||
Height = SizePosition.Height - 31; // appwindow偏移量
|
||||
|
||||
if (SizePosition.X > 0 && SizePosition.Y > 0)
|
||||
{
|
||||
var leftTopPoint = new PixelPoint(SizePosition.X, SizePosition.Y);
|
||||
var leftTopPoint = new PixelPoint(SizePosition.X, SizePosition.Y + 31);
|
||||
var rightBottomPoint = new PixelPoint(SizePosition.X + (int)Width, SizePosition.Y + (int)Height);
|
||||
if (primaryScreenBounds.Contains(leftTopPoint) &&
|
||||
primaryScreenBounds.Contains(rightBottomPoint))
|
||||
|
||||
@@ -218,6 +218,7 @@
|
||||
<TextBlock Text="{Binding Path=User.NickName, Mode=OneWay, Source={x:Static s:UserService.Current}}" />
|
||||
<Border
|
||||
Padding="5,1"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="2"
|
||||
IsVisible="{Binding Path=User.UserType, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static enum:UserType.Sponsor}}">
|
||||
|
||||
@@ -341,6 +341,7 @@
|
||||
<ComboBox SelectedItem="{Binding Source={x:Static ms:UISettings.WindowBackgroundMaterial}, Path=Value, Mode=TwoWay}">
|
||||
<ComboBox.Items>
|
||||
<spp:WindowBackgroundMaterial>None</spp:WindowBackgroundMaterial>
|
||||
<spp:WindowBackgroundMaterial>Transparent</spp:WindowBackgroundMaterial>
|
||||
<spp:WindowBackgroundMaterial>Blur</spp:WindowBackgroundMaterial>
|
||||
<spp:WindowBackgroundMaterial>AcrylicBlur</spp:WindowBackgroundMaterial>
|
||||
<spp:WindowBackgroundMaterial>Mica</spp:WindowBackgroundMaterial>
|
||||
|
||||
+2
-2
@@ -12,10 +12,10 @@ sealed class LifetimeHttpHandler : DelegatingHandler
|
||||
|
||||
public LifeTimeKey LifeTimeKey { get; }
|
||||
|
||||
public LifetimeHttpHandler(IDomainResolver domainResolver, LifeTimeKey lifeTimeKey, TimeSpan lifeTime, Action<LifetimeHttpHandler> deactivateAction)
|
||||
public LifetimeHttpHandler(IDomainResolver domainResolver, IWebProxy webProxy, LifeTimeKey lifeTimeKey, TimeSpan lifeTime, Action<LifetimeHttpHandler> deactivateAction)
|
||||
{
|
||||
LifeTimeKey = lifeTimeKey;
|
||||
InnerHandler = new ReverseProxyHttpClientHandler(lifeTimeKey.DomainConfig, domainResolver);
|
||||
InnerHandler = new ReverseProxyHttpClientHandler(lifeTimeKey.DomainConfig, domainResolver, webProxy);
|
||||
timer = new Timer(OnTimerCallback, deactivateAction, lifeTime, Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ public class ReverseProxyHttpClient : HttpMessageInvoker
|
||||
// = new(new ProductHeaderValue(Constants.HARDCODED_APP_NAME_NEW + "_" + DateTimeOffset.UtcNow.Ticks, ThisAssembly.Version));
|
||||
|
||||
public ReverseProxyHttpClient(IDomainConfig domainConfig, IDomainResolver domainResolver)
|
||||
: this(new ReverseProxyHttpClientHandler(domainConfig, domainResolver), disposeHandler: true)
|
||||
: this(new ReverseProxyHttpClientHandler(domainConfig, domainResolver, HttpNoProxy.Instance), disposeHandler: true)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
+195
-1
@@ -7,12 +7,14 @@ sealed class ReverseProxyHttpClientHandler : DelegatingHandler
|
||||
{
|
||||
readonly IDomainConfig domainConfig;
|
||||
readonly IDomainResolver domainResolver;
|
||||
readonly IWebProxy webProxy;
|
||||
readonly TimeSpan connectTimeout = TimeSpan.FromSeconds(10d);
|
||||
|
||||
public ReverseProxyHttpClientHandler(IDomainConfig domainConfig, IDomainResolver domainResolver)
|
||||
public ReverseProxyHttpClientHandler(IDomainConfig domainConfig, IDomainResolver domainResolver, IWebProxy webProxy)
|
||||
{
|
||||
this.domainConfig = domainConfig;
|
||||
this.domainResolver = domainResolver;
|
||||
this.webProxy = webProxy;
|
||||
InnerHandler = CreateSocketsHttpHandler();
|
||||
}
|
||||
|
||||
@@ -53,12 +55,199 @@ sealed class ReverseProxyHttpClientHandler : DelegatingHandler
|
||||
{
|
||||
Proxy = HttpNoProxy.Instance,
|
||||
UseProxy = false,
|
||||
PreAuthenticate = false,
|
||||
UseCookies = false,
|
||||
AllowAutoRedirect = false,
|
||||
AutomaticDecompression = DecompressionMethods.None,
|
||||
ConnectCallback = ConnectCallback,
|
||||
EnableMultipleHttp2Connections = true,
|
||||
RequestHeaderEncodingSelector = (_, _) => Encoding.UTF8,
|
||||
ResponseHeaderEncodingSelector = (_, _) => Encoding.UTF8,
|
||||
};
|
||||
|
||||
private async ValueTask<Stream> ConnectThroughProxyAsync(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
if (context.InitialRequestMessage.RequestUri == null)
|
||||
{
|
||||
throw new InvalidOperationException("Request URI is null");
|
||||
}
|
||||
var proxyUri = webProxy.GetProxy(context.InitialRequestMessage.RequestUri);
|
||||
if (proxyUri == null)
|
||||
{
|
||||
throw new InvalidOperationException("Proxy URI is null");
|
||||
}
|
||||
|
||||
var proxyEndPoint = new DnsEndPoint(proxyUri.Host, proxyUri.Port);
|
||||
var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
|
||||
await socket.ConnectAsync(proxyEndPoint, cancellationToken);
|
||||
|
||||
var stream = new NetworkStream(socket, ownsSocket: true);
|
||||
|
||||
// 确定代理类型
|
||||
var proxyType = DetermineProxyType(proxyUri);
|
||||
|
||||
switch (proxyType)
|
||||
{
|
||||
case ExternalProxyType.Http:
|
||||
await ConnectHttpProxy(stream, context, cancellationToken);
|
||||
break;
|
||||
case ExternalProxyType.Socks4:
|
||||
await ConnectSocks4Proxy(stream, context, cancellationToken);
|
||||
break;
|
||||
case ExternalProxyType.Socks5:
|
||||
await ConnectSocks5Proxy(stream, context, cancellationToken);
|
||||
break;
|
||||
}
|
||||
|
||||
if (context.InitialRequestMessage.RequestUri.Scheme == "https" || context.InitialRequestMessage.RequestUri.Port == 443)
|
||||
{
|
||||
var requestContext = context.InitialRequestMessage.GetRequestContext();
|
||||
var tlsSniValue = requestContext.TlsSniValue;
|
||||
var sslStream = new SslStream(stream, leaveInnerStreamOpen: false);
|
||||
await sslStream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions
|
||||
{
|
||||
TargetHost = tlsSniValue.Value,
|
||||
RemoteCertificateValidationCallback = (sender, cert, chain, errors) => true,
|
||||
}, cancellationToken);
|
||||
|
||||
return sslStream;
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
private ExternalProxyType DetermineProxyType(Uri proxyUri)
|
||||
{
|
||||
return proxyUri.Scheme.ToLower() switch
|
||||
{
|
||||
"socks4" => ExternalProxyType.Socks4,
|
||||
"socks5" => ExternalProxyType.Socks5,
|
||||
_ => ExternalProxyType.Http
|
||||
};
|
||||
|
||||
throw new NotImplementedException("Proxy type determination needs to be implemented");
|
||||
}
|
||||
|
||||
private async Task ConnectHttpProxy(NetworkStream stream, SocketsHttpConnectionContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
var credentials = webProxy.Credentials as NetworkCredential;
|
||||
var authHeader = credentials != null
|
||||
? $"Proxy-Authorization: Basic {Convert.ToBase64String(Encoding.ASCII.GetBytes($"{credentials.UserName}:{credentials.Password}"))}\r\n"
|
||||
: string.Empty;
|
||||
|
||||
var connectRequest = $"CONNECT {context.DnsEndPoint.Host}:{context.DnsEndPoint.Port} HTTP/1.1\r\n" +
|
||||
$"Host: {context.DnsEndPoint.Host}:{context.DnsEndPoint.Port}\r\n" +
|
||||
$"{authHeader}\r\n";
|
||||
|
||||
var connectBytes = Encoding.ASCII.GetBytes(connectRequest);
|
||||
await stream.WriteAsync(connectBytes, cancellationToken);
|
||||
|
||||
var buffer = new byte[1024];
|
||||
var bytesRead = await stream.ReadAsync(buffer, cancellationToken);
|
||||
var response = Encoding.ASCII.GetString(buffer, 0, bytesRead);
|
||||
|
||||
if (!response.StartsWith("HTTP/1.1 200"))
|
||||
{
|
||||
throw new Exception($"Proxy connection failed: {response}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConnectSocks4Proxy(NetworkStream stream, SocketsHttpConnectionContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
var credentials = webProxy.Credentials as NetworkCredential;
|
||||
var userId = credentials?.UserName ?? string.Empty;
|
||||
|
||||
var destinationAddress = await GetIPEndPointsAsync(context.DnsEndPoint, cancellationToken).FirstAsync();
|
||||
var portBytes = BitConverter.GetBytes((ushort)context.DnsEndPoint.Port);
|
||||
if (BitConverter.IsLittleEndian) Array.Reverse(portBytes);
|
||||
|
||||
var request = new byte[] { 0x04, 0x01 }
|
||||
.Concat(portBytes)
|
||||
.Concat(destinationAddress.Address.GetAddressBytes())
|
||||
.Concat(Encoding.ASCII.GetBytes(userId))
|
||||
.Concat(new byte[] { 0x00 })
|
||||
.ToArray();
|
||||
|
||||
await stream.WriteAsync(request, cancellationToken);
|
||||
|
||||
var response = new byte[8];
|
||||
await stream.ReadAsync(response, cancellationToken);
|
||||
|
||||
if (response[1] != 0x5A)
|
||||
{
|
||||
throw new Exception($"SOCKS4 proxy connection failed: {response[1]}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConnectSocks5Proxy(NetworkStream stream, SocketsHttpConnectionContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
var credentials = webProxy.Credentials as NetworkCredential;
|
||||
|
||||
// SOCKS5 握手
|
||||
byte[] authMethod = credentials != null ? new byte[] { 0x02 } : new byte[] { 0x00 };
|
||||
await stream.WriteAsync(new byte[] { 0x05, 0x01 }.Concat(authMethod).ToArray(), cancellationToken);
|
||||
|
||||
var response = new byte[2];
|
||||
await stream.ReadAsync(response, cancellationToken);
|
||||
|
||||
if (response[0] != 0x05)
|
||||
{
|
||||
throw new Exception("SOCKS5 handshake failed");
|
||||
}
|
||||
|
||||
// 如果需要身份验证
|
||||
if (response[1] == 0x02 && credentials != null)
|
||||
{
|
||||
var userBytes = Encoding.ASCII.GetBytes(credentials.UserName);
|
||||
var passBytes = Encoding.ASCII.GetBytes(credentials.Password);
|
||||
var authRequest = new byte[] { 0x01, (byte)userBytes.Length }
|
||||
.Concat(userBytes)
|
||||
.Concat(new byte[] { (byte)passBytes.Length })
|
||||
.Concat(passBytes)
|
||||
.ToArray();
|
||||
|
||||
await stream.WriteAsync(authRequest, cancellationToken);
|
||||
|
||||
var authResponse = new byte[2];
|
||||
await stream.ReadAsync(authResponse, cancellationToken);
|
||||
|
||||
if (authResponse[1] != 0x00)
|
||||
{
|
||||
throw new Exception("SOCKS5 authentication failed");
|
||||
}
|
||||
}
|
||||
|
||||
// 发送连接请求
|
||||
byte[] addressBytes;
|
||||
byte addressType;
|
||||
if (IPAddress.TryParse(context.DnsEndPoint.Host, out IPAddress? ipAddress))
|
||||
{
|
||||
addressBytes = ipAddress.GetAddressBytes();
|
||||
addressType = (byte)(ipAddress.AddressFamily == AddressFamily.InterNetwork ? 1 : 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
addressBytes = Encoding.ASCII.GetBytes(context.DnsEndPoint.Host);
|
||||
addressType = 3; // 域名
|
||||
}
|
||||
|
||||
var request = new byte[] { 0x05, 0x01, 0x00, addressType }
|
||||
.Concat(addressType == 3 ? new[] { (byte)addressBytes.Length } : Array.Empty<byte>())
|
||||
.Concat(addressBytes)
|
||||
.Concat(BitConverter.GetBytes((ushort)context.DnsEndPoint.Port).Reverse())
|
||||
.ToArray();
|
||||
|
||||
await stream.WriteAsync(request, cancellationToken);
|
||||
|
||||
var connectResponse = new byte[10];
|
||||
await stream.ReadAsync(connectResponse, cancellationToken);
|
||||
|
||||
if (connectResponse[1] != 0x00)
|
||||
{
|
||||
throw new Exception($"SOCKS5 connection failed: {connectResponse[1]}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连接回调
|
||||
/// </summary>
|
||||
@@ -67,6 +256,11 @@ sealed class ReverseProxyHttpClientHandler : DelegatingHandler
|
||||
/// <returns></returns>
|
||||
async ValueTask<Stream> ConnectCallback(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
if (webProxy != null && !HttpNoProxy.IsNoProxy(webProxy))
|
||||
{
|
||||
return await ConnectThroughProxyAsync(context, cancellationToken);
|
||||
}
|
||||
|
||||
var innerExceptions = new List<Exception>();
|
||||
var ipEndPoints = GetIPEndPointsAsync(context.DnsEndPoint, cancellationToken);
|
||||
|
||||
|
||||
+20
-23
@@ -1,3 +1,4 @@
|
||||
using System.Net;
|
||||
using MediaTypeHeaderValue = System.Net.Http.Headers.MediaTypeHeaderValue;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
@@ -140,11 +141,6 @@ sealed class HttpLocalRequestMiddleware
|
||||
return null;
|
||||
}
|
||||
|
||||
StringValues cookie = context.Request.Headers["cookie-steamTool"];
|
||||
if (StringValues.IsNullOrEmpty(cookie))
|
||||
cookie = context.Request.Headers["Cookie"];
|
||||
StringValues referer = context.Request.Headers["Referer-steamTool"];
|
||||
|
||||
context.Response.Headers.AccessControlAllowOrigin = context.Request.Headers.Origin.Count == 0 ? "*" : context.Request.Headers.Origin;
|
||||
context.Response.Headers.AccessControlAllowHeaders = "*";
|
||||
context.Response.Headers.AccessControlAllowMethods = "*";
|
||||
@@ -166,29 +162,30 @@ sealed class HttpLocalRequestMiddleware
|
||||
Method = methodObj,
|
||||
Content = hasReqContent ? new StreamContent(context.Request.Body) : null,
|
||||
};
|
||||
req.Headers.UserAgent.ParseAdd(context.Request.Headers.UserAgent);
|
||||
if (!StringValues.IsNullOrEmpty(cookie))
|
||||
foreach (var item in context.Request.Headers)
|
||||
{
|
||||
CookieHttpClient.CookieContainer.Add(new Cookie
|
||||
{
|
||||
CommentUri = requestUri,
|
||||
Domain = requestUri.Host,
|
||||
Value = cookie,
|
||||
});
|
||||
// req.Headers.Add("Cookie", cookie);
|
||||
}
|
||||
if (!StringValues.IsNullOrEmpty(referer))
|
||||
{
|
||||
try
|
||||
{
|
||||
var refererUri = new Uri(referer.ToString());
|
||||
req.Headers.Referrer = refererUri;
|
||||
}
|
||||
catch
|
||||
var headers = item.Key.ToLower();
|
||||
if (headers.EndsWith("-steamtool"))
|
||||
{
|
||||
|
||||
if (headers.Equals("cookie", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
CookieHttpClient.CookieContainer.Add(new Cookie
|
||||
{
|
||||
CommentUri = requestUri,
|
||||
Domain = requestUri.Host,
|
||||
Value = item.Value
|
||||
});
|
||||
}
|
||||
if (headers.Equals("referer", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var refererUri = new Uri(item.Value.ToString());
|
||||
req.Headers.Referrer = refererUri;
|
||||
}
|
||||
req.Headers.TryAddWithoutValidation(item.Key.TrimEnd("-steamtool"), (IEnumerable<string?>)item.Value);
|
||||
}
|
||||
}
|
||||
req.Headers.UserAgent.ParseAdd(context.Request.Headers.UserAgent);
|
||||
if (hasReqContent)
|
||||
{
|
||||
req.Content!.Headers.ContentType = MediaTypeHeaderValue.Parse(context.Request.ContentType.ThrowIsNull());
|
||||
|
||||
+25
-11
@@ -1,5 +1,6 @@
|
||||
// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.HttpServer/HttpReverseProxyMiddleware.cs
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Yarp.ReverseProxy.Forwarder;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
@@ -52,7 +53,20 @@ sealed partial class HttpReverseProxyMiddleware
|
||||
|
||||
if (TryGetDomainConfig(url, out var domainConfig) == false)
|
||||
{
|
||||
await next(context);
|
||||
if (reverseProxyConfig.Service.TwoLevelAgentEnable)
|
||||
{
|
||||
var httpClient = httpClientFactory.CreateHttpClient("GlobalProxy", defaultDomainConfig);
|
||||
var destinationPrefix = GetDestinationPrefix(context.Request.Scheme, context.Request.Host, null);
|
||||
var error = await httpForwarder.SendAsync(context, destinationPrefix, httpClient, ForwarderRequestConfig.Empty, HttpTransformer.Empty);
|
||||
if (error != ForwarderError.None)
|
||||
{
|
||||
await HandleErrorAsync(context, error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await next(context);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -95,22 +109,22 @@ sealed partial class HttpReverseProxyMiddleware
|
||||
context.Request.Headers.UserAgent = domainConfig.UserAgent.Replace("${origin}", context.Request.Headers.UserAgent, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
var forwarderRequestConfig = new ForwarderRequestConfig()
|
||||
{
|
||||
Version = context.Request.Protocol switch
|
||||
{
|
||||
var protocol when protocol.StartsWith("HTTP/2") => System.Net.HttpVersion.Version20,
|
||||
var protocol when protocol.StartsWith("HTTP/3") => System.Net.HttpVersion.Version30,
|
||||
_ => System.Net.HttpVersion.Version11,
|
||||
},
|
||||
};
|
||||
//var forwarderRequestConfig = new ForwarderRequestConfig()
|
||||
//{
|
||||
// Version = context.Request.Protocol switch
|
||||
// {
|
||||
// var protocol when protocol.StartsWith("HTTP/2") => System.Net.HttpVersion.Version20,
|
||||
// var protocol when protocol.StartsWith("HTTP/3") => System.Net.HttpVersion.Version30,
|
||||
// _ => System.Net.HttpVersion.Version11,
|
||||
// },
|
||||
//};
|
||||
|
||||
if (domainConfig.IsServerSideProxy)
|
||||
{
|
||||
SetWattHeaders(context, reverseProxyConfig.Service.ServerSideProxyToken);
|
||||
}
|
||||
|
||||
var error = await httpForwarder.SendAsync(context, destinationPrefix, httpClient, forwarderRequestConfig, HttpTransformer.Empty);
|
||||
var error = await httpForwarder.SendAsync(context, destinationPrefix, httpClient, ForwarderRequestConfig.Empty, HttpTransformer.Empty);
|
||||
|
||||
if (error != ForwarderError.None)
|
||||
{
|
||||
|
||||
+15
-2
@@ -1,11 +1,14 @@
|
||||
// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.Http/HttpClientFactory.cs
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
using System.Net;
|
||||
|
||||
namespace BD.WTTS.Services.Implementation;
|
||||
|
||||
sealed class ReverseProxyHttpClientFactory : IReverseProxyHttpClientFactory
|
||||
{
|
||||
readonly IDomainResolver domainResolver;
|
||||
readonly IWebProxy webProxy;
|
||||
|
||||
/// <summary>
|
||||
/// 首次生命周期
|
||||
@@ -27,9 +30,19 @@ sealed class ReverseProxyHttpClientFactory : IReverseProxyHttpClientFactory
|
||||
/// </summary>
|
||||
readonly ConcurrentDictionary<LifeTimeKey, Lazy<LifetimeHttpHandler>> httpHandlerLazyCache = new();
|
||||
|
||||
public ReverseProxyHttpClientFactory(IDomainResolver domainResolver)
|
||||
public ReverseProxyHttpClientFactory(IDomainResolver domainResolver, IReverseProxyConfig reverseProxyConfig)
|
||||
{
|
||||
this.domainResolver = domainResolver;
|
||||
if (reverseProxyConfig.Service.TwoLevelAgentEnable)
|
||||
{
|
||||
this.webProxy = new WebProxy($"{reverseProxyConfig.Service.TwoLevelAgentProxyType}://{reverseProxyConfig.Service.TwoLevelAgentIp}:{reverseProxyConfig.Service.TwoLevelAgentPortId}", true, null, null);
|
||||
if (!string.IsNullOrEmpty(reverseProxyConfig.Service.TwoLevelAgentUserName) || !string.IsNullOrEmpty(reverseProxyConfig.Service.TwoLevelAgentPassword))
|
||||
{
|
||||
webProxy.Credentials = new NetworkCredential(reverseProxyConfig.Service.TwoLevelAgentUserName, reverseProxyConfig.Service.TwoLevelAgentPassword);
|
||||
}
|
||||
}
|
||||
else
|
||||
this.webProxy = HttpNoProxy.Instance;
|
||||
}
|
||||
|
||||
public ReverseProxyHttpClient CreateHttpClient(string domain, IDomainConfig domainConfig)
|
||||
@@ -43,7 +56,7 @@ sealed class ReverseProxyHttpClientFactory : IReverseProxyHttpClientFactory
|
||||
}
|
||||
|
||||
LifetimeHttpHandler CreateLifetimeHttpHandler(LifeTimeKey lifeTimeKey, TimeSpan lifeTime)
|
||||
=> new(domainResolver, lifeTimeKey, lifeTime, OnLifetimeHttpHandlerDeactivate);
|
||||
=> new(domainResolver, webProxy, lifeTimeKey, lifeTime, OnLifetimeHttpHandlerDeactivate);
|
||||
|
||||
void OnLifetimeHttpHandlerDeactivate(LifetimeHttpHandler lifetimeHttpHandler)
|
||||
{
|
||||
|
||||
+6
-3
@@ -164,10 +164,13 @@ abstract class ReverseProxyServiceImpl : IReverseProxySettings
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(reverseProxySettings.ProxyIp))
|
||||
return StartProxyResultCode.Exception;
|
||||
var inUsePort = SocketHelper.IsUsePort(IPAddress.Parse(reverseProxySettings.ProxyIp!), 443);
|
||||
if (inUsePort)
|
||||
if (reverseProxySettings.ProxyMode == ProxyMode.Hosts)
|
||||
{
|
||||
return StartProxyResultCode.BindPortError;
|
||||
var inUsePort = SocketHelper.IsUsePort(IPAddress.Parse(reverseProxySettings.ProxyIp!), 443);
|
||||
if (inUsePort)
|
||||
{
|
||||
return StartProxyResultCode.BindPortError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-8
@@ -80,17 +80,19 @@ sealed partial class YarpReverseProxyServiceImpl : ReverseProxyServiceImpl, IRev
|
||||
builder.WebHost.UseKestrel(options =>
|
||||
{
|
||||
options.AddServerHeader = false;
|
||||
options.RequestHeaderEncodingSelector = _ => Encoding.UTF8;
|
||||
options.ResponseHeaderEncodingSelector = _ => Encoding.UTF8;
|
||||
options.NoLimit();
|
||||
#if WINDOWS
|
||||
#if !NET7_0_OR_GREATER
|
||||
if (OperatingSystem2.IsWindows7())
|
||||
{
|
||||
//https://github.com/dotnet/aspnetcore/issues/22563
|
||||
options.ConfigureHttpsDefaults(httpsOptions =>
|
||||
{
|
||||
httpsOptions.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13;
|
||||
});
|
||||
}
|
||||
if (OperatingSystem2.IsWindows7())
|
||||
{
|
||||
//https://github.com/dotnet/aspnetcore/issues/22563
|
||||
options.ConfigureHttpsDefaults(httpsOptions =>
|
||||
{
|
||||
httpsOptions.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13;
|
||||
});
|
||||
}
|
||||
#endif
|
||||
//options.ListenSshReverseProxy();
|
||||
//options.ListenGitReverseProxy();
|
||||
|
||||
@@ -63,7 +63,7 @@ partial class ProxyService
|
||||
string? twoLevelAgentPassword = ProxySettings.TwoLevelAgentPassword.Value;
|
||||
string? proxyDNS = ProxySettings.ProxyMasterDns.Value;
|
||||
bool isSupportIpv6 = await RefreshIpv6Support();
|
||||
bool useDoh = ProxySettings.UseDoh.Value;
|
||||
bool useDoh = ProxySettings.UseDoh2.Value;
|
||||
string? customDohAddres = ProxySettings.CustomDohAddres.Value;
|
||||
|
||||
Lazy<IPAddress> proxyIp_ = new(() => ReverseProxySettings.GetProxyIp(proxyIp));
|
||||
|
||||
@@ -148,7 +148,7 @@ public partial interface IProxySettings
|
||||
/// <summary>
|
||||
/// 启用 DNS over HTTPS
|
||||
/// </summary>
|
||||
bool UseDoh { get; set; }
|
||||
bool UseDoh2 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 自定义 DNS over HTTPS 地址
|
||||
@@ -268,7 +268,7 @@ public partial interface IProxySettings
|
||||
/// <summary>
|
||||
/// 启用 DNS over HTTPS的默认值
|
||||
/// </summary>
|
||||
const bool DefaultUseDoh = true;
|
||||
const bool DefaultUseDoh2 = false;
|
||||
|
||||
/// <summary>
|
||||
/// 自定义 DNS over HTTPS 地址的默认值
|
||||
|
||||
@@ -189,7 +189,7 @@ public sealed partial class ProxySettings_ : IProxySettings, ISettings, ISetting
|
||||
/// 启用 DNS over HTTPS
|
||||
/// </summary>
|
||||
[MPKey(21), MP2Key(21), JsonPropertyOrder(21)]
|
||||
public bool UseDoh { get; set; } = IProxySettings.DefaultUseDoh;
|
||||
public bool UseDoh2 { get; set; } = IProxySettings.DefaultUseDoh2;
|
||||
|
||||
/// <summary>
|
||||
/// 自定义 DNS over HTTPS 地址
|
||||
@@ -356,8 +356,8 @@ public static partial class ProxySettings
|
||||
/// <summary>
|
||||
/// 启用 DNS over HTTPS
|
||||
/// </summary>
|
||||
public static SettingsStructProperty<bool, ProxySettings_> UseDoh { get; }
|
||||
= new(DefaultUseDoh);
|
||||
public static SettingsStructProperty<bool, ProxySettings_> UseDoh2 { get; }
|
||||
= new(DefaultUseDoh2);
|
||||
|
||||
/// <summary>
|
||||
/// 自定义 DNS over HTTPS 地址
|
||||
|
||||
+2
-2
@@ -11,8 +11,8 @@ public sealed class ProxySettingsWindowViewModel : WindowViewModel
|
||||
Title = DisplayName;
|
||||
}
|
||||
|
||||
public IEnumerable<ExternalProxyType> ProxyTypes { get; }
|
||||
= Enum2.GetAll<ExternalProxyType>();
|
||||
//public IEnumerable<ExternalProxyType> ProxyTypes { get; }
|
||||
// = Enum2.GetAll<ExternalProxyType>();
|
||||
|
||||
public IEnumerable<string> ProxyDNSs { get; } = new[]
|
||||
{
|
||||
|
||||
@@ -531,6 +531,23 @@
|
||||
<StackPanel Margin="10">
|
||||
<DockPanel>
|
||||
<TextBlock Text="{Binding Name}" />
|
||||
<Border
|
||||
Padding="5,0"
|
||||
HorizontalAlignment="Left"
|
||||
CornerRadius="5"
|
||||
IsVisible="{Binding ProxyType, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static enum:ProxyType.ServerAccelerate}}"
|
||||
ToolTip.Tip="Beta 测试加速项,不稳定且随时可能调整">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
|
||||
<GradientStop Offset="0.0" Color="#43cbff" />
|
||||
<GradientStop Offset="1.0" Color="#9708cc" />
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="10"
|
||||
Text="Beta" />
|
||||
</Border>
|
||||
</DockPanel>
|
||||
<Separator Classes="Separator" />
|
||||
</StackPanel>
|
||||
@@ -737,7 +754,7 @@
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
</StackPanel.Styles>
|
||||
<Button Theme="{StaticResource TransparentButton}">
|
||||
<Button Theme="{StaticResource TransparentButton}" ToolTip.ShowDelay="0">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock>
|
||||
<Run FontSize="16" Text="Q:为什么加速后流量一直显示 0KB ?" />
|
||||
@@ -750,7 +767,7 @@
|
||||
<Run Text="A:可以尝试执行以下操作进行修复,关闭加速,点击加速设置-移除证书再安装证书,重开加速。或者加速设置-代理设置-尝试更换doh地址或者关闭doh使用dns等方案。" />
|
||||
</TextBlock>
|
||||
</ToolTip.Tip>
|
||||
<ui:FontIcon FontSize="22" Glyph="" />
|
||||
<ui:FontIcon FontSize="20" Glyph="" />
|
||||
</Button>
|
||||
<TextBlock Text="{x:Static spp:Strings.CommunityFix_ProxyMode}" />
|
||||
<TextBlock Text="{Binding Source={x:Static ms:ProxySettings.ProxyMode}, Path=Value, Mode=OneWay, Converter={StaticResource EnumLocalizationNameConverter}}" />
|
||||
|
||||
@@ -35,17 +35,22 @@
|
||||
Text="{Binding Source={x:Static ms:ProxySettings.ProxyMasterDns}, Path=Value, Mode=TwoWay}" />
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
<!--<DockPanel>
|
||||
<Label Content="监听端口"></Label>
|
||||
<NumericUpDown Value="{Binding Source={x:Static ms:ProxySettings.HostProxyPortId},Path=Value, Mode=TwoWay}"
|
||||
FormatString="0"
|
||||
Maximum="65535"/>
|
||||
</DockPanel>-->
|
||||
|
||||
<ui:SettingsExpanderItem Description="{Binding Path=Res.Settings_Proxy_SystemProxyPortId_Tip, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
|
||||
<TextBlock Text="{Binding Path=Res.Settings_Proxy_SystemProxyPortId, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<NumericUpDown
|
||||
MaxWidth="200"
|
||||
FormatString="0"
|
||||
Maximum="65535"
|
||||
Value="{Binding Source={x:Static ms:ProxySettings.SystemProxyPortId}, Path=Value, Mode=TwoWay}" />
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<!--<DockPanel>
|
||||
<TextBlock Text="IPV6支持:" />
|
||||
<TextBlock Text="IPV6地址" ></TextBlock>
|
||||
<Button Content="{Binding Path=Res.Refresh,Mode=OneWay,Source={x:Static resx:R.Current}}"></Button>
|
||||
<Button Content="{Binding Path=Res.Refresh,Mode=OneWay,Source={x:Static s:ResourceService.Current}}"></Button>
|
||||
</DockPanel>-->
|
||||
<ui:SettingsExpanderItem>
|
||||
<TextBlock Text="{Binding Path=Res.CommunityFix_AutoRunProxy, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
@@ -64,7 +69,7 @@
|
||||
<ui:SettingsExpanderItem>
|
||||
<TextBlock Text="{Binding Path=Res.CommunityFix_EnableUseDoh, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<ToggleSwitch Classes="LeftContent" IsChecked="{Binding Source={x:Static ms:ProxySettings.UseDoh}, Path=Value, Mode=TwoWay}" />
|
||||
<ToggleSwitch Classes="LeftContent" IsChecked="{Binding Source={x:Static ms:ProxySettings.UseDoh2}, Path=Value, Mode=TwoWay}" />
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
@@ -89,12 +94,72 @@
|
||||
<Button Content="选择证书"/>
|
||||
</DockPanel>-->
|
||||
|
||||
<ui:SettingsExpanderItem Description="*该选项勾选后会导致程序的加速完全不生效,请确认是仅需要脚本功能后再启用">
|
||||
<ui:SettingsExpanderItem Description="*该选项勾选后会导致程序的加速完全不生效,请确认是配合二级代理实现仅需要脚本功能后再启用">
|
||||
<TextBlock Text="{Binding Path=Res.CommunityFix_ProxyOnlyOnScript, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<ToggleSwitch Classes="LeftContent" IsChecked="{Binding Source={x:Static ms:ProxySettings.OnlyEnableProxyScript}, Path=Value, Mode=TwoWay}" />
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
</ui:SettingsExpander>
|
||||
|
||||
|
||||
<ui:SettingsExpander
|
||||
Description="*改动后需重启加速才能生效"
|
||||
Header="{Binding Path=Res.Settings_Proxy_TwoLevelAgent, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
IsExpanded="True">
|
||||
<ui:SettingsExpander.Footer>
|
||||
<ToggleSwitch IsChecked="{Binding Source={x:Static ms:ProxySettings.TwoLevelAgentEnable}, Path=Value, Mode=TwoWay}" />
|
||||
</ui:SettingsExpander.Footer>
|
||||
|
||||
<ui:SettingsExpanderItem>
|
||||
<TextBlock Text="{Binding Path=Res.Settings_Proxy_ProxyType, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<ui:FAComboBox MinWidth="200" SelectedItem="{Binding Source={x:Static ms:ProxySettings.TwoLevelAgentProxyType}, Path=Value, Mode=TwoWay}">
|
||||
<ui:FAComboBox.Items>
|
||||
<spp:ExternalProxyType>Http</spp:ExternalProxyType>
|
||||
<spp:ExternalProxyType>Socks4</spp:ExternalProxyType>
|
||||
<spp:ExternalProxyType>Socks5</spp:ExternalProxyType>
|
||||
</ui:FAComboBox.Items>
|
||||
</ui:FAComboBox>
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<ui:SettingsExpanderItem>
|
||||
<TextBlock Text="{Binding Path=Res.Settings_Proxy_IPAddress, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<TextBox MinWidth="200" Text="{Binding Source={x:Static ms:ProxySettings.TwoLevelAgentIp}, Path=Value, Mode=TwoWay}" />
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<ui:SettingsExpanderItem>
|
||||
<TextBlock Text="{Binding Path=Res.Settings_Proxy_Port, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<NumericUpDown
|
||||
MinWidth="200"
|
||||
FormatString="0"
|
||||
Maximum="65535"
|
||||
Minimum="80"
|
||||
Value="{Binding Source={x:Static ms:ProxySettings.TwoLevelAgentPortId}, Path=Value, Mode=TwoWay}" />
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<ui:SettingsExpanderItem>
|
||||
<TextBlock Text="{Binding Path=Res.Settings_Proxy_UserName, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<TextBox MinWidth="200" Text="{Binding Source={x:Static ms:ProxySettings.TwoLevelAgentUserName}, Path=Value, Mode=TwoWay}" />
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<ui:SettingsExpanderItem>
|
||||
<TextBlock Text="{Binding Path=Res.Settings_Proxy_Password, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<TextBox
|
||||
MinWidth="200"
|
||||
Classes="revealPasswordButton"
|
||||
PasswordChar="{StaticResource PasswordChar}"
|
||||
Text="{Binding Source={x:Static ms:ProxySettings.TwoLevelAgentPassword}, Path=Value, Mode=TwoWay}" />
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
</ui:SettingsExpander>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
|
||||
@@ -475,6 +475,27 @@ public static class AuthenticatorHelper
|
||||
return filePath;
|
||||
}
|
||||
|
||||
public static async Task<IEnumerable<IFileResult>?> MultipleSelectFolderPath(string? fileExtension = null)
|
||||
{
|
||||
var options = new PickOptions();
|
||||
if (!string.IsNullOrEmpty(fileExtension))
|
||||
{
|
||||
AvaloniaFilePickerFileTypeFilter fileTypes = new AvaloniaFilePickerFileTypeFilter.Item[] {
|
||||
new($"{fileExtension} Files") {
|
||||
Patterns = new[] { $"*{fileExtension}", },
|
||||
//MimeTypes
|
||||
//AppleUniformTypeIdentifiers =
|
||||
},
|
||||
};
|
||||
options.FileTypes = fileTypes;
|
||||
}
|
||||
var filePaths = await FilePicker2.PickMultipleAsync(options);
|
||||
|
||||
if (!filePaths.Any_Nullable())
|
||||
Toast.Show(ToastIcon.Warning, Strings.FilePathNotExist);
|
||||
return filePaths;
|
||||
}
|
||||
|
||||
public static async Task<bool> VerifyMaxValue()
|
||||
{
|
||||
var auths = await AuthenticatorHelper.GetAllSourceAuthenticatorAsync();
|
||||
|
||||
+31
-24
@@ -1,5 +1,6 @@
|
||||
using BD.WTTS.UI.Views.Pages;
|
||||
using WinAuth;
|
||||
using static SteamKit2.DepotManifest;
|
||||
using SJsonSerializer = System.Text.Json.JsonSerializer;
|
||||
|
||||
namespace BD.WTTS.UI.ViewModels;
|
||||
@@ -16,6 +17,8 @@ public class AuthenticatorImportPageViewModel : ViewModelBase
|
||||
{
|
||||
AuthenticatorImportMethods = new List<AuthenticatorImportMethod>
|
||||
{
|
||||
new AuthenticatorImportMethod(Strings.LocalAuth_Import.Format(Strings.LocalAuth_JoinSteamAuthenticator), Strings.LocalAuth_JoinSteamAuthenticatorDesc,
|
||||
PageType: typeof(JoinSteamAuthenticatorPage)),
|
||||
new AuthenticatorImportMethod(Strings.Auth_SteamLoginImport, Strings.Steam_UserLoginTip,
|
||||
PageType: typeof(SteamLoginImportPage)),
|
||||
new AuthenticatorImportMethod(Strings.LocalAuth_Import.Format(Strings.SteamGuard), Strings.LocalAuth_SteamGuardImport,
|
||||
@@ -26,8 +29,6 @@ public class AuthenticatorImportPageViewModel : ViewModelBase
|
||||
if (await AuthenticatorHelper.VerifyMaxValue())
|
||||
await ImportFromSdaFile();
|
||||
})),
|
||||
new AuthenticatorImportMethod(Strings.LocalAuth_Import.Format(Strings.LocalAuth_JoinSteamAuthenticator), Strings.LocalAuth_JoinSteamAuthenticatorDesc,
|
||||
PageType: typeof(JoinSteamAuthenticatorPage)),
|
||||
new AuthenticatorImportMethod(Strings.LocalAuth_Import.Format(Strings.WattToolKitV2), Strings.LocalAuth_WattToolKitV2Import,
|
||||
Command: ReactiveCommand.Create(async () =>
|
||||
{
|
||||
@@ -68,34 +69,40 @@ public class AuthenticatorImportPageViewModel : ViewModelBase
|
||||
{
|
||||
try
|
||||
{
|
||||
var filePath = await AuthenticatorHelper.SelectFolderPath(FileEx.maFile);
|
||||
var filePaths = await AuthenticatorHelper.MultipleSelectFolderPath(FileEx.maFile);
|
||||
|
||||
if (string.IsNullOrEmpty(filePath)) return;
|
||||
if (!filePaths.Any_Nullable())
|
||||
return;
|
||||
|
||||
var text = await File.ReadAllTextAsync(filePath);
|
||||
|
||||
var sdaFileModel = SJsonSerializer.Deserialize(text, ImportFileModelJsonContext.Default.SdaFileModel);
|
||||
sdaFileModel.ThrowIsNull();
|
||||
var steamDataModel = new SdaFileConvertToSteamDataModel(sdaFileModel);
|
||||
|
||||
SteamAuthenticator steamAuthenticator = new()
|
||||
foreach (var file in filePaths)
|
||||
{
|
||||
DeviceId = sdaFileModel.DeviceId,
|
||||
Serial = sdaFileModel.SerialNumber,
|
||||
SecretKey = Convert.FromBase64String(sdaFileModel.SharedSecret),
|
||||
SteamData = SJsonSerializer.Serialize(steamDataModel, ImportFileModelJsonContext.Default.SdaFileConvertToSteamDataModel),
|
||||
};
|
||||
if (!File.Exists(file.FullPath)) return;
|
||||
var text = await File.ReadAllTextAsync(file.FullPath);
|
||||
|
||||
var authDto =
|
||||
new AuthenticatorDTO()
|
||||
var sdaFileModel = SJsonSerializer.Deserialize(text, ImportFileModelJsonContext.Default.SdaFileModel);
|
||||
sdaFileModel.ThrowIsNull();
|
||||
var steamDataModel = new SdaFileConvertToSteamDataModel(sdaFileModel);
|
||||
|
||||
SteamAuthenticator steamAuthenticator = new()
|
||||
{
|
||||
Name = $"(Steam){steamAuthenticator.AccountName}",
|
||||
Value = steamAuthenticator,
|
||||
Created = DateTimeOffset.Now,
|
||||
DeviceId = sdaFileModel.DeviceId,
|
||||
Serial = sdaFileModel.SerialNumber,
|
||||
SecretKey = Convert.FromBase64String(sdaFileModel.SharedSecret),
|
||||
SteamData = SJsonSerializer.Serialize(steamDataModel, ImportFileModelJsonContext.Default.SdaFileConvertToSteamDataModel),
|
||||
};
|
||||
if (await AuthenticatorHelper.SaveAuthenticator(authDto))
|
||||
{
|
||||
Toast.Show(ToastIcon.Success, Strings.ModelContent_ImportSuccessful_.Format(authDto.Name));
|
||||
|
||||
var authDto =
|
||||
new AuthenticatorDTO()
|
||||
{
|
||||
Name = $"(Steam){steamAuthenticator.AccountName}",
|
||||
Value = steamAuthenticator,
|
||||
Created = DateTimeOffset.Now,
|
||||
};
|
||||
|
||||
if (await AuthenticatorHelper.SaveAuthenticator(authDto))
|
||||
{
|
||||
Toast.Show(ToastIcon.Success, Strings.ModelContent_ImportSuccessful_.Format(authDto.Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
using AppResources = BD.WTTS.Client.Resources.Strings;
|
||||
|
||||
using BD.SteamClient.Models;
|
||||
using BD.SteamClient.Services;
|
||||
using WinAuth;
|
||||
using AppResources = BD.WTTS.Client.Resources.Strings;
|
||||
using WSteamClient = WinAuth.SteamClient;
|
||||
|
||||
namespace BD.WTTS.UI.ViewModels;
|
||||
@@ -73,11 +72,55 @@ public sealed partial class SteamTradePageViewModel : WindowViewModel
|
||||
}
|
||||
else
|
||||
{
|
||||
IsLogged = true;
|
||||
IsLogged = await CheckAndRefreshToken();
|
||||
|
||||
await GetConfirmations(_steamClient);
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<bool> CheckAndRefreshToken()
|
||||
{
|
||||
IsLoading = true;
|
||||
|
||||
// 不具备检查条件 没有保持的会话信息
|
||||
if (_steamClient == null)
|
||||
return false;
|
||||
|
||||
if (_steamClient.Session == null || _steamAuthenticator == null)
|
||||
return false;
|
||||
|
||||
// token 依旧有效 直接返回
|
||||
if (!_steamClient.Session.IsAccessTokenExpired())
|
||||
return true;
|
||||
|
||||
try
|
||||
{
|
||||
// 是否有 refresh token
|
||||
string? refreshToken = _steamClient.Session?.RefreshToken;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(refreshToken))
|
||||
return false;
|
||||
|
||||
var session = _steamClient.Session;
|
||||
|
||||
// 刷新 access token (有效时间同样为一天)
|
||||
await session!.RefreshAccessToken(_steamClient);
|
||||
|
||||
// 使用原先获取的具体派生类型 修改数据
|
||||
_steamAuthenticator.SessionData = session.ToString();
|
||||
|
||||
// 通过原始令牌引用保存信息
|
||||
await AuthenticatorHelper.SaveAuthenticator(_authenticatorDto)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Refresh()
|
||||
{
|
||||
if (_steamClient?.IsLoggedIn() == true)
|
||||
@@ -314,7 +357,6 @@ public sealed partial class SteamTradePageViewModel : WindowViewModel
|
||||
// Content = Strings.LocalAuth_AuthTrade_MessageBoxTip_.Format(statusText)
|
||||
// }, statusText, isCancelButton: true, isDialog: false))
|
||||
//{
|
||||
|
||||
Toast.Show(ToastIcon.Info, Strings.LocalAuth_AuthTrade_ConfirmTip_.Format(statusText));
|
||||
|
||||
if (!await ChangeTradeStatus(status, selectedList)) return;
|
||||
|
||||
@@ -123,28 +123,59 @@
|
||||
Text="{Binding Path=Res.GameRelated_Borderless_MoveMouseTip, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
</WrapPanel>
|
||||
|
||||
<StackPanel
|
||||
<Grid
|
||||
HorizontalAlignment="Center"
|
||||
ColumnDefinitions="Auto,*"
|
||||
IsVisible="{ReflectionBinding !!SelectWindow}"
|
||||
Orientation="Vertical"
|
||||
Spacing="10">
|
||||
<TextBlock TextWrapping="WrapWithOverflow">
|
||||
<Run Text="{Binding Path=Res.GameRelated_Borderless_Title, Mode=OneWay, Source={x:Static s:ResourceService.Current}, StringFormat={}{0}:}" />
|
||||
<Run Text="{ReflectionBinding SelectWindow.Title, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="WrapWithOverflow">
|
||||
<Run Text="{Binding Path=Res.GameRelated_Borderless_ClassName, Mode=OneWay, Source={x:Static s:ResourceService.Current}, StringFormat={}{0}:}" />
|
||||
<Run Text="{ReflectionBinding SelectWindow.ClassName, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="WrapWithOverflow">
|
||||
<Run Text="{Binding Path=Res.GameRelated_Borderless_Process, Mode=OneWay, Source={x:Static s:ResourceService.Current}, StringFormat={}{0}:}" />
|
||||
<Run Text="{ReflectionBinding SelectWindow.Name, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="WrapWithOverflow">
|
||||
<Run Text="{Binding Path=Res.GameRelated_Borderless_Path, Mode=OneWay, Source={x:Static s:ResourceService.Current}, StringFormat={}{0}:}" />
|
||||
<Run Text="{ReflectionBinding SelectWindow.Path, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
RowDefinitions="Auto,Auto,Auto,Auto">
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding Path=Res.GameRelated_Borderless_Title, Mode=OneWay, Source={x:Static s:ResourceService.Current}, StringFormat={}{0}:}" />
|
||||
<TextBox
|
||||
Grid.Column="1"
|
||||
Margin="0,0,0,10"
|
||||
IsReadOnly="True"
|
||||
Text="{ReflectionBinding SelectWindow.Title,
|
||||
Mode=OneWay}"
|
||||
TextWrapping="WrapWithOverflow" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Path=Res.GameRelated_Borderless_Process, Mode=OneWay, Source={x:Static s:ResourceService.Current}, StringFormat={}{0}:}" />
|
||||
<TextBox
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,0,10"
|
||||
IsReadOnly="True"
|
||||
Text="{ReflectionBinding SelectWindow.Title,
|
||||
Mode=OneWay}"
|
||||
TextWrapping="WrapWithOverflow" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Path=Res.GameRelated_Borderless_ClassName, Mode=OneWay, Source={x:Static s:ResourceService.Current}, StringFormat={}{0}:}" />
|
||||
<TextBox
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,0,10"
|
||||
IsReadOnly="True"
|
||||
Text="{ReflectionBinding SelectWindow.Name,
|
||||
Mode=OneWay}"
|
||||
TextWrapping="WrapWithOverflow" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Path=Res.GameRelated_Borderless_Path, Mode=OneWay, Source={x:Static s:ResourceService.Current}, StringFormat={}{0}:}" />
|
||||
<TextBox
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,0,10"
|
||||
IsReadOnly="True"
|
||||
Text="{ReflectionBinding SelectWindow.Path,
|
||||
Mode=OneWay}"
|
||||
TextWrapping="WrapWithOverflow" />
|
||||
</Grid>
|
||||
|
||||
<WrapPanel HorizontalAlignment="Center" Orientation="Horizontal">
|
||||
<WrapPanel.Styles>
|
||||
|
||||
@@ -129,13 +129,7 @@ public sealed partial class IdleCardPageViewModel : ViewModelBase
|
||||
}
|
||||
else
|
||||
{
|
||||
RunState = false;
|
||||
StopIdle();
|
||||
RunOrStopAutoNext(false);
|
||||
RunOrStopAutoCardDropCheck(false);
|
||||
IdleTime = default;
|
||||
ResetCurrentIdle();
|
||||
ChangeRunTxt();
|
||||
StopIdleRun();
|
||||
}
|
||||
|
||||
RunLoaingState = false;
|
||||
@@ -147,6 +141,17 @@ public sealed partial class IdleCardPageViewModel : ViewModelBase
|
||||
}
|
||||
}
|
||||
|
||||
private void StopIdleRun()
|
||||
{
|
||||
RunState = false;
|
||||
StopIdle();
|
||||
RunOrStopAutoNext(false);
|
||||
RunOrStopAutoCardDropCheck(false);
|
||||
IdleTime = default;
|
||||
ResetCurrentIdle();
|
||||
ChangeRunTxt();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 手动切换下一个游戏
|
||||
/// </summary>
|
||||
@@ -377,7 +382,7 @@ public sealed partial class IdleCardPageViewModel : ViewModelBase
|
||||
{
|
||||
try
|
||||
{
|
||||
IdleGameList.Clear();
|
||||
MainThread2.BeginInvokeOnMainThread(IdleGameList.Clear);
|
||||
var badges = Badges.Where(w => w.CardsRemaining != 0);
|
||||
var apps = SteamIdleSettings.IdleSequentital.Value switch
|
||||
{
|
||||
@@ -386,8 +391,7 @@ public sealed partial class IdleCardPageViewModel : ViewModelBase
|
||||
IdleSequentital.Mostvalue => badges.OrderByDescending(o => o.RegularAvgPrice).Select(s => new IdleApp(s)),
|
||||
_ => badges.Select(s => new IdleApp(s)),
|
||||
};
|
||||
foreach (var app in apps)
|
||||
IdleGameList.Add(app);
|
||||
MainThread2.BeginInvokeOnMainThread(() => EnumerableExtensions.AddRange(IdleGameList, apps));
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -5,6 +5,8 @@ public enum WindowBackgroundMaterial : byte
|
||||
{
|
||||
None = 0,
|
||||
|
||||
Transparent = 1,
|
||||
|
||||
Blur = 2,
|
||||
|
||||
AcrylicBlur = 3,
|
||||
|
||||
+75
-3
@@ -4196,7 +4196,7 @@ namespace BD.WTTS.Client.Resources {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 共存令牌 的本地化字符串。
|
||||
/// 查找类似 Steam App 令牌共存 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string LocalAuth_JoinSteamAuthenticator {
|
||||
get {
|
||||
@@ -4304,7 +4304,7 @@ namespace BD.WTTS.Client.Resources {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 在 Watt Toolkit 中与官方 Steam App 中使用相同的令牌信息 的本地化字符串。
|
||||
/// 查找类似 在 Watt Toolkit 中与官方 Steam App 中使用相同的令牌数据导入方式(推荐✨) 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string LocalAuth_JoinSteamAuthenticatorDesc {
|
||||
get {
|
||||
@@ -4403,7 +4403,7 @@ namespace BD.WTTS.Client.Resources {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 使用系统内置的账户加密来保护和加密你的数据。这将把你的令牌锁定在这台计算机上,即使令牌文件被复制,也无法解密出令牌数据。单如果你要格式化你的硬盘、重新安装系统或删除这个系统用户账户,你必须先关掉此功能,否则你的令牌会再也无法解密!! 的本地化字符串。
|
||||
/// 查找类似 使用系统内置的账户加密来保护和加密你的数据。这将把你的令牌锁定在这台计算机上,即使令牌文件被复制,也无法解密出令牌数据。如果你要格式化你的硬盘、重新安装系统或删除这个系统用户账户,你必须先关掉此功能,否则你的令牌会再也无法解密!! 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string LocalAuth_ProtectionAuth_IsOnlyCurrentComputerEncryptTip {
|
||||
get {
|
||||
@@ -6324,6 +6324,42 @@ namespace BD.WTTS.Client.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 地址 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string Settings_Proxy_IPAddress {
|
||||
get {
|
||||
return ResourceManager.GetString("Settings_Proxy_IPAddress", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 密码 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string Settings_Proxy_Password {
|
||||
get {
|
||||
return ResourceManager.GetString("Settings_Proxy_Password", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 端口 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string Settings_Proxy_Port {
|
||||
get {
|
||||
return ResourceManager.GetString("Settings_Proxy_Port", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 代理类型 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string Settings_Proxy_ProxyType {
|
||||
get {
|
||||
return ResourceManager.GetString("Settings_Proxy_ProxyType", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 代理监听 IP 地址 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -6333,6 +6369,42 @@ namespace BD.WTTS.Client.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 代理监听端口 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string Settings_Proxy_SystemProxyPortId {
|
||||
get {
|
||||
return ResourceManager.GetString("Settings_Proxy_SystemProxyPortId", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 仅对 PAC 与 System 代理模式生效 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string Settings_Proxy_SystemProxyPortId_Tip {
|
||||
get {
|
||||
return ResourceManager.GetString("Settings_Proxy_SystemProxyPortId_Tip", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 二级代理 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string Settings_Proxy_TwoLevelAgent {
|
||||
get {
|
||||
return ResourceManager.GetString("Settings_Proxy_TwoLevelAgent", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 用户名 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string Settings_Proxy_UserName {
|
||||
get {
|
||||
return ResourceManager.GetString("Settings_Proxy_UserName", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 *需重启软件后生效 的本地化字符串。
|
||||
/// </summary>
|
||||
|
||||
@@ -206,7 +206,7 @@
|
||||
<data name="And" xml:space="preserve">
|
||||
<value>和</value>
|
||||
</data>
|
||||
<data name="ArchiSteamFarmPlus" xml:space="preserve">
|
||||
<data name="ArchiSteamFarmPlus" xml:space="preserve">
|
||||
<value>ASF</value>
|
||||
</data>
|
||||
<data name="ASF_AddBot" xml:space="preserve">
|
||||
@@ -639,7 +639,7 @@
|
||||
<value>令牌仅在这台电脑上可解密</value>
|
||||
</data>
|
||||
<data name="LocalAuth_ProtectionAuth_IsOnlyCurrentComputerEncryptTip" xml:space="preserve">
|
||||
<value>使用系统内置的账户加密来保护和加密你的数据。这将把你的令牌锁定在这台计算机上,即使令牌文件被复制,也无法解密出令牌数据。单如果你要格式化你的硬盘、重新安装系统或删除这个系统用户账户,你必须先关掉此功能,否则你的令牌会再也无法解密!!</value>
|
||||
<value>使用系统内置的账户加密来保护和加密你的数据。这将把你的令牌锁定在这台计算机上,即使令牌文件被复制,也无法解密出令牌数据。如果你要格式化你的硬盘、重新安装系统或删除这个系统用户账户,你必须先关掉此功能,否则你的令牌会再也无法解密!!</value>
|
||||
</data>
|
||||
<data name="LocalAuth_ProtectionAuth_Password" xml:space="preserve">
|
||||
<value>自定义密码</value>
|
||||
@@ -1181,6 +1181,30 @@
|
||||
<data name="Settings_Proxy_SystemProxyIp" xml:space="preserve">
|
||||
<value>代理监听 IP 地址</value>
|
||||
</data>
|
||||
<data name="Settings_Proxy_SystemProxyPortId" xml:space="preserve">
|
||||
<value>代理监听端口</value>
|
||||
</data>
|
||||
<data name="Settings_Proxy_SystemProxyPortId_Tip" xml:space="preserve">
|
||||
<value>仅对 PAC 与 System 代理模式生效</value>
|
||||
</data>
|
||||
<data name="Settings_Proxy_TwoLevelAgent" xml:space="preserve">
|
||||
<value>二级代理</value>
|
||||
</data>
|
||||
<data name="Settings_Proxy_ProxyType" xml:space="preserve">
|
||||
<value>代理类型</value>
|
||||
</data>
|
||||
<data name="Settings_Proxy_IPAddress" xml:space="preserve">
|
||||
<value>地址</value>
|
||||
</data>
|
||||
<data name="Settings_Proxy_Port" xml:space="preserve">
|
||||
<value>端口</value>
|
||||
</data>
|
||||
<data name="Settings_Proxy_UserName" xml:space="preserve">
|
||||
<value>用户名</value>
|
||||
</data>
|
||||
<data name="Settings_Proxy_Password" xml:space="preserve">
|
||||
<value>密码</value>
|
||||
</data>
|
||||
<data name="Settings_Proxy_CustomDNS" xml:space="preserve">
|
||||
<value>自定义代理 DNS</value>
|
||||
</data>
|
||||
@@ -2977,10 +3001,10 @@
|
||||
<value>验证令牌错误</value>
|
||||
</data>
|
||||
<data name="LocalAuth_JoinSteamAuthenticator" xml:space="preserve">
|
||||
<value>共存令牌</value>
|
||||
<value>Steam App 令牌共存</value>
|
||||
</data>
|
||||
<data name="LocalAuth_JoinSteamAuthenticatorDesc" xml:space="preserve">
|
||||
<value>在 Watt Toolkit 中与官方 Steam App 中使用相同的令牌信息</value>
|
||||
<value>在 Watt Toolkit 中与官方 Steam App 中使用相同的令牌数据导入方式(推荐✨)</value>
|
||||
</data>
|
||||
<data name="LocalAuth_JoinSteamAuthenticator_ConfirmSteamAppAuthenticatorCode" xml:space="preserve">
|
||||
<value>确认 Steam App 令牌验证码</value>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using static BD.WTTS.Services.IMicroServiceClient;
|
||||
using AppResources = BD.WTTS.Client.Resources.Strings;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace BD.WTTS.Services;
|
||||
@@ -18,6 +17,8 @@ public sealed class NoticeService : ReactiveObject
|
||||
[Reactive]
|
||||
public int UnreadNotificationsCount { get; set; }
|
||||
|
||||
private Timer _timer;
|
||||
|
||||
//[Reactive]
|
||||
//public ObservableCollection<NoticeGroupDTO> NoticeTypes { get; set; }
|
||||
|
||||
@@ -26,6 +27,12 @@ public sealed class NoticeService : ReactiveObject
|
||||
public NoticeService()
|
||||
{
|
||||
Notices = new ObservableCollection<OfficialMessageItemDTO>();
|
||||
|
||||
_timer = new Timer(async _ =>
|
||||
{
|
||||
await GetNewsAsync();
|
||||
}, null, TimeSpan.FromHours(2), TimeSpan.FromHours(2));
|
||||
|
||||
//NoticesSource = new(x => x.Id);
|
||||
//NoticeTypes = new();
|
||||
}
|
||||
@@ -78,6 +85,15 @@ public sealed class NoticeService : ReactiveObject
|
||||
VerifyNotificationsHasRead();
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
public void ClrearLastLookNoticeDateTime()
|
||||
{
|
||||
GeneralSettings.LastLookNoticeDateTime.Value = default;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// public async Task GetNewsAsync()
|
||||
// {
|
||||
// var data = await notificationRepo.GetAllAsync(w => w.ExpirationTime > DateTimeOffset.Now);
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// ReSharper disable once CheckNamespace
|
||||
using BD.Common.Columns;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
|
||||
namespace BD.WTTS.UI.ViewModels;
|
||||
|
||||
@@ -90,6 +88,12 @@ public sealed partial class DebugPageViewModel : TabItemViewModel
|
||||
case "gc":
|
||||
GC.Collect();
|
||||
break;
|
||||
case "refreshnews":
|
||||
#if DEBUG
|
||||
NoticeService.Current.ClrearLastLookNoticeDateTime();
|
||||
#endif
|
||||
await NoticeService.Current.GetNewsAsync();
|
||||
break;
|
||||
case "login":
|
||||
if (cmds.Length > 1)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user