using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using BetterGenshinImpact.View.Windows;
using BetterGenshinImpact.ViewModel.Message;
using CommunityToolkit.Mvvm.Messaging;
using Newtonsoft.Json.Linq;
using System.Net;
using BetterGenshinImpact.GameTask;
namespace BetterGenshinImpact.Core.Script.WebView;
///
/// 给 WebView 提供的桥接类
/// 用于调用 C# 方法
///
[ClassInterface(ClassInterfaceType.AutoDual)]
[ComVisible(true)]
public sealed class RepoWebBridge
{
private static readonly HashSet AllowedTextExtensions = new(StringComparer.OrdinalIgnoreCase)
{
".txt", ".md", ".json", ".js", ".ts",
".vue", ".css", ".html", ".csv", ".xml",
".yaml", ".yml", ".ini", ".config"
};
private static readonly HashSet AllowedImageExtensions = new(StringComparer.OrdinalIgnoreCase)
{
".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".bmp", ".ico"
};
public async Task GetRepoJson()
{
try
{
if (!Directory.Exists(ScriptRepoUpdater.CenterRepoPath))
{
throw new InvalidOperationException("仓库文件夹不存在,请至少成功更新一次仓库!");
}
string localRepoJsonPath = GetRepoJsonPath();
return await File.ReadAllTextAsync(localRepoJsonPath);
}
catch (Exception ex)
{
await ThemedMessageBox.ErrorAsync(ex.Message, "获取仓库信息失败");
return string.Empty;
}
}
public async void ImportUri(string url)
{
try
{
await ScriptRepoUpdater.Instance.ImportScriptFromUri(url, false);
WeakReferenceMessenger.Default.Send(new RefreshDataMessage("Refresh"));
}
catch (Exception e)
{
await ThemedMessageBox.ErrorAsync(e.Message, "订阅脚本链接失败!");
}
}
public async Task GetUserConfigJson()
{
string userConfigPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "User", "config.json");
if (!File.Exists(userConfigPath))
{
await ThemedMessageBox.ErrorAsync($"用户配置文件不存在: {userConfigPath}", "获取用户配置失败");
return string.Empty;
}
return await File.ReadAllTextAsync(userConfigPath);
}
///
/// 获取当前仓库的已订阅脚本路径列表(JSON 数组)。
/// 相比 GetUserConfigJson() 更轻量,仅返回当前仓库的订阅路径。
///
public string GetSubscribedScriptPaths()
{
try
{
var paths = ScriptRepoUpdater.GetSubscribedPathsForCurrentRepo();
if (paths.Count > 0)
{
return Newtonsoft.Json.JsonConvert.SerializeObject(paths);
}
return "[]";
}
catch
{
return "[]";
}
}
public Task GetFile(string relPath)
{
try
{
// URL 解码路径(处理中文文件名)
relPath = WebUtility.UrlDecode(relPath);
string filePath = Path.Combine(ScriptRepoUpdater.CenterRepoPath, "repo", relPath)
.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
// 验证解析后的路径在允许的目录范围内
string normalizedBasePath = Path.GetFullPath(Path.Combine(ScriptRepoUpdater.CenterRepoPath, "repo"));
string normalizedFilePath = Path.GetFullPath(filePath);
if (!normalizedFilePath.StartsWith(normalizedBasePath, StringComparison.OrdinalIgnoreCase))
{
return Task.FromResult("404");
}
string extension = Path.GetExtension(filePath).ToLower();
if (AllowedTextExtensions.Contains(extension))
{
// 读取文本文件
string? content = ScriptRepoUpdater.Instance.ReadFileFromCenterRepo(relPath);
return Task.FromResult(string.IsNullOrEmpty(content) ? "404" : content);
}
else if (AllowedImageExtensions.Contains(extension))
{
// 读取图片文件,返回 Base64 编码
byte[]? bytes = ScriptRepoUpdater.Instance.ReadBinaryFileFromCenterRepo(relPath);
if (bytes == null || bytes.Length == 0)
{
return Task.FromResult("404");
}
string base64 = Convert.ToBase64String(bytes);
return Task.FromResult(base64);
}
return Task.FromResult("404");
}
catch
{
return Task.FromResult("404");
}
}
private static string GetMimeType(string extension)
{
return extension.ToLower() switch
{
".png" => "image/png",
".jpg" or ".jpeg" => "image/jpeg",
".gif" => "image/gif",
".bmp" => "image/bmp",
".webp" => "image/webp",
".svg" => "image/svg+xml",
".ico" => "image/x-icon",
_ => "application/octet-stream"
};
}
public async Task UpdateSubscribed(string path)
{
try
{
if (!Directory.Exists(ScriptRepoUpdater.CenterRepoPath))
{
throw new InvalidOperationException("仓库文件夹不存在,请至少成功更新一次仓库!");
}
string localRepoJsonPath = GetRepoJsonPath();
string json = await File.ReadAllTextAsync(localRepoJsonPath);
var jsonObj = Newtonsoft.Json.JsonConvert.DeserializeObject(json);
if (jsonObj?["indexes"] is not JArray indexes) return false;
string[] pathParts = path.Split('/');
ProcessPathRecursively(indexes, pathParts, 0);
string modifiedJson = Newtonsoft.Json.JsonConvert.SerializeObject(jsonObj, Newtonsoft.Json.Formatting.Indented);
await File.WriteAllTextAsync(localRepoJsonPath, modifiedJson);
return true;
}
catch (Exception ex)
{
await ThemedMessageBox.ErrorAsync(ex.Message, "信息更新失败");
return false;
}
}
public async Task ClearUpdate()
{
try
{
string? repoJsonPath = Directory
.GetFiles(ScriptRepoUpdater.CenterRepoPath, "repo.json", SearchOption.AllDirectories)
.FirstOrDefault();
if (string.IsNullOrEmpty(repoJsonPath))
{
throw new FileNotFoundException("找不到原始 repo.json 文件");
}
string targetPath = ScriptRepoUpdater.RepoUpdatedJsonPath;
File.Copy(repoJsonPath, targetPath, overwrite: true);
return true;
}
catch (Exception ex)
{
await ThemedMessageBox.ErrorAsync($"清空更新标记失败: {ex.Message}", "操作失败");
return false;
}
}
// 设置新手引导标志位
public bool SetGuideStatus(bool status)
{
try
{
var scriptConfig = TaskContext.Instance().Config.ScriptConfig;
scriptConfig.GuideStatus = status;
return true;
}
catch (Exception e)
{
Console.WriteLine(e);
return false;
}
}
// 获取新手引导标志位
public bool GetGuideStatus()
{
try
{
return TaskContext.Instance().Config.ScriptConfig.GuideStatus;
}
catch (Exception e)
{
Console.WriteLine(e);
return false;
}
}
private static string GetRepoJsonPath()
{
string updatedRepoJsonPath = ScriptRepoUpdater.RepoUpdatedJsonPath;
if (File.Exists(updatedRepoJsonPath))
{
return updatedRepoJsonPath;
}
string? repoJson = Directory
.GetFiles(ScriptRepoUpdater.CenterRepoPath, "repo.json", SearchOption.AllDirectories)
.FirstOrDefault();
return repoJson ?? throw new FileNotFoundException("repo.json 仓库索引文件不存在,请至少成功更新一次仓库!");
}
internal static void ProcessPathRecursively(JArray array, string[] pathParts, int currentIndex)
{
foreach (JObject item in array.OfType())
{
if (item["name"]?.ToString() != pathParts[currentIndex]) continue;
if (currentIndex == pathParts.Length - 1)
{
ResetHasUpdateFlag(item);
}
else if (item["children"] is JArray children)
{
ProcessPathRecursively(children, pathParts, currentIndex + 1);
}
break;
}
}
internal static void ResetHasUpdateFlag(JObject node)
{
if (node["hasUpdate"] is { Type: JTokenType.Boolean } hasUpdate &&
(bool)hasUpdate)
{
node["hasUpdate"] = false;
}
if (node["children"] is JArray children)
{
foreach (JObject child in children.OfType())
{
ResetHasUpdateFlag(child);
}
}
}
}