using System.Reflection;
using System.Text.Json;
using Microsoft.Web.WebView2.Core;
using Windows.Storage.Streams;
namespace XFEEDToolBox;
// Isolated news-only host: no toolbox runtime, game connection or automation.
public partial class App : Application
{
public static NewsPreviewRuntime Runtime { get; } = new();
private Window? window;
public App() => InitializeComponent();
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
var panel = new Controls.GalnetNewsPanel();
window = new() { Title = "银河新闻预览", Content = new ScrollViewer { Content = panel } };
var arguments = Environment.GetCommandLineArgs(); var index = Array.IndexOf(arguments, "--verify-news");
if (index >= 0 && index + 1 < arguments.Length)
{
window.AppWindow.Resize(new(1440, 1080));
window.AppWindow.Move(new(-20000, -20000));
window.AppWindow.Show(false);
_ = VerifyAsync(panel, arguments[index + 1]);
}
else { window.AppWindow.Resize(new(1280, 700)); window.Activate(); }
}
private async Task VerifyAsync(Controls.GalnetNewsPanel panel, string report)
{
string? failure = null; JsonElement? carousel = null, article = null;
var started = DateTimeOffset.UtcNow;
try
{
async Task<JsonElement> Wait(Func<WebView2?> getView, string condition)
{
var deadline = DateTimeOffset.UtcNow.AddSeconds(60);
while (DateTimeOffset.UtcNow < deadline)
{
if (getView() is { CoreWebView2: not null } view)
{
try
{
var result = await view.ExecuteScriptAsync(condition);
using var json = JsonDocument.Parse(result);
if (json.RootElement.ValueKind == JsonValueKind.Object) return json.RootElement.Clone();
}
catch (Exception) { }
}
await Task.Delay(100);
}
throw new TimeoutException("Native news WebView did not reach the expected state.");
}
async Task Capture(WebView2 view, string path)
{
// Allow the WebView compositor to present freshly decoded media.
await Task.Delay(500);
using var stream = new InMemoryRandomAccessStream();
await view.CoreWebView2.CapturePreviewAsync(CoreWebView2CapturePreviewImageFormat.Png, stream);
using var reader = new DataReader(stream.GetInputStreamAt(0));
await reader.LoadAsync((uint)stream.Size); var pixels = new byte[(int)stream.Size]; reader.ReadBytes(pixels);
await File.WriteAllBytesAsync(path, pixels);
}
var browser = (WebView2)panel.FindName("NewsBrowser");
carousel = await Wait(() => browser, "window.galnet?.inspect().count===6 && /[\\u3400-\\u9fff]/.test(document.getElementById('slide-title').textContent) && document.getElementById('cover').naturalWidth>0 ? ({...window.galnet.inspect(),title:document.getElementById('slide-title').textContent}) : null");
await Capture(browser, Path.Combine(Path.GetDirectoryName(report)!, "native-carousel.png"));
await browser.ExecuteScriptAsync("document.getElementById('read-more').click()");
WebView2? Reader() => typeof(Controls.GalnetNewsPanel).GetField("articleBrowser", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(panel) as WebView2;
article = await Wait(Reader, "document.body.className==='reader-mode' && innerWidth>=600 && /[\\u3400-\\u9fff]/.test(document.getElementById('article-body').textContent) && document.getElementById('article-cover').naturalWidth>0 ? ({width:innerWidth,title:document.getElementById('article-title').textContent,paragraphs:document.querySelectorAll('#article-body p').length,translation:document.getElementById('article-translation').textContent}) : null");
await Capture(Reader()!, Path.Combine(Path.GetDirectoryName(report)!, "native-article.png"));
await Reader()!.ExecuteScriptAsync("document.getElementById('language').click()");
var original = await Reader()!.ExecuteScriptAsync("document.getElementById('article-translation').textContent");
if (!original.Contains("英文原文", StringComparison.Ordinal) && !JsonSerializer.Deserialize<string>(original)!.Contains("英文原文", StringComparison.Ordinal))
throw new InvalidOperationException("Native article original-language toggle failed.");
}
catch (Exception error) { failure = error.ToString(); }
await File.WriteAllTextAsync(report, JsonSerializer.Serialize(new { passed = failure is null, started, finished = DateTimeOffset.UtcNow, carousel, article, failure, gameCallsInvoked = false }, new JsonSerializerOptions { WriteIndented = true }));
window?.Close(); Environment.Exit(failure is null ? 0 : 1);
}
}
public sealed class NewsPreviewRuntime
{
public string DataDirectory { get; } = Path.Combine(AppContext.BaseDirectory, "preview-data");
public void Log(string category, string message) => System.Diagnostics.Debug.WriteLine(category + ": " + message);
}
using System.Reflection;
using System.Text.Json;
using Microsoft.Web.WebView2.Core;
using Windows.Storage.Streams;
namespace XFEEDToolBox;
// Isolated news-only host: no toolbox runtime, game connection or automation.
public partial class App : Application
{
public static NewsPreviewRuntime Runtime { get; } = new();
private Window? window;
public App() => InitializeComponent();
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
var panel = new Controls.GalnetNewsPanel();
window = new() { Title = "银河新闻预览", Content = new ScrollViewer { Content = panel } };
var arguments = Environment.GetCommandLineArgs(); var index = Array.IndexOf(arguments, "--verify-news");
if (index >= 0 && index + 1 < arguments.Length)
{
window.AppWindow.Resize(new(1440, 1080));
window.AppWindow.Move(new(-20000, -20000));
window.AppWindow.Show(false);
_ = VerifyAsync(panel, arguments[index + 1]);
}
else { window.AppWindow.Resize(new(1280, 700)); window.Activate(); }
}
private async Task VerifyAsync(Controls.GalnetNewsPanel panel, string report)
{
string? failure = null; JsonElement? carousel = null, article = null;
var started = DateTimeOffset.UtcNow;
try
{
async Task<JsonElement> Wait(Func<WebView2?> getView, string condition)
{
var deadline = DateTimeOffset.UtcNow.AddSeconds(60);
while (DateTimeOffset.UtcNow < deadline)
{
if (getView() is { CoreWebView2: not null } view)
{
try
{
var result = await view.ExecuteScriptAsync(condition);
using var json = JsonDocument.Parse(result);
if (json.RootElement.ValueKind == JsonValueKind.Object) return json.RootElement.Clone();
}
catch (Exception) { }
}
await Task.Delay(100);
}
throw new TimeoutException("Native news WebView did not reach the expected state.");
}
async Task Capture(WebView2 view, string path)
{
// Allow the WebView compositor to present freshly decoded media.
await Task.Delay(500);
using var stream = new InMemoryRandomAccessStream();
await view.CoreWebView2.CapturePreviewAsync(CoreWebView2CapturePreviewImageFormat.Png, stream);
using var reader = new DataReader(stream.GetInputStreamAt(0));
await reader.LoadAsync((uint)stream.Size); var pixels = new byte[(int)stream.Size]; reader.ReadBytes(pixels);
await File.WriteAllBytesAsync(path, pixels);
}
var browser = (WebView2)panel.FindName("NewsBrowser");
carousel = await Wait(() => browser, "window.galnet?.inspect().count===6 && /[\\u3400-\\u9fff]/.test(document.getElementById('slide-title').textContent) && document.getElementById('cover').naturalWidth>0 ? ({...window.galnet.inspect(),title:document.getElementById('slide-title').textContent}) : null");
await Capture(browser, Path.Combine(Path.GetDirectoryName(report)!, "native-carousel.png"));
await browser.ExecuteScriptAsync("document.getElementById('read-more').click()");
WebView2? Reader() => typeof(Controls.GalnetNewsPanel).GetField("articleBrowser", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(panel) as WebView2;
article = await Wait(Reader, "document.body.className==='reader-mode' && innerWidth>=600 && /[\\u3400-\\u9fff]/.test(document.getElementById('article-body').textContent) && document.getElementById('article-cover').naturalWidth>0 ? ({width:innerWidth,title:document.getElementById('article-title').textContent,paragraphs:document.querySelectorAll('#article-body p').length,translation:document.getElementById('article-translation').textContent}) : null");
await Capture(Reader()!, Path.Combine(Path.GetDirectoryName(report)!, "native-article.png"));
await Reader()!.ExecuteScriptAsync("document.getElementById('language').click()");
var original = await Reader()!.ExecuteScriptAsync("document.getElementById('article-translation').textContent");
if (!original.Contains("英文原文", StringComparison.Ordinal) && !JsonSerializer.Deserialize<string>(original)!.Contains("英文原文", StringComparison.Ordinal))
throw new InvalidOperationException("Native article original-language toggle failed.");
}
catch (Exception error) { failure = error.ToString(); }
await File.WriteAllTextAsync(report, JsonSerializer.Serialize(new { passed = failure is null, started, finished = DateTimeOffset.UtcNow, carousel, article, failure, gameCallsInvoked = false }, new JsonSerializerOptions { WriteIndented = true }));
window?.Close(); Environment.Exit(failure is null ? 0 : 1);
}
}
public sealed class NewsPreviewRuntime
{
public string DataDirectory { get; } = Path.Combine(AppContext.BaseDirectory, "preview-data");
public void Log(string category, string message) => System.Diagnostics.Debug.WriteLine(category + ": " + message);
}