From 8f9383768b077b80fb6ca5fed957b4014dc199d3 Mon Sep 17 00:00:00 2001 From: SlimeSB <86453765+SlimeSB@users.noreply.github.com> Date: Fri, 15 May 2026 14:53:41 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=AF=B9Modrinth?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=8C=85=E5=92=8CCurseForge=E6=9D=90?= =?UTF-8?q?=E8=B4=A8=E5=8C=85=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增`modrinth-datapack-`和`texture-packs-`前缀处理逻辑 - 更新Diff页面支持显示数据包和材质包对比 - 修复CommandProcessor中Modrinth文件路径格式 - 更新CommentBuilder支持数据包和材质包评论生成 - 扩展CurseManager和ModrinthManager支持多加载器参数 --- CFPABot/Azusa/Pages/Diff.razor | 54 +++++++++ CFPABot/Command/CommandProcessor.cs | 39 ++++++- CFPABot/Controllers/UtilsController.cs | 14 ++- CFPABot/Utils/CommentBuilder.cs | 150 ++++++++++++++++++++++--- CFPABot/Utils/CurseManager.cs | 2 +- CFPABot/Utils/ModrinthManager.cs | 25 ++++- 6 files changed, 258 insertions(+), 26 deletions(-) diff --git a/CFPABot/Azusa/Pages/Diff.razor b/CFPABot/Azusa/Pages/Diff.razor index 308efc0..6f7abc2 100644 --- a/CFPABot/Azusa/Pages/Diff.razor +++ b/CFPABot/Azusa/Pages/Diff.razor @@ -740,6 +740,34 @@ try { + if (slug.StartsWith("modrinth-datapack-")) + { + slug = slug["modrinth-datapack-".Length..]; + var addon = await ModrinthManager.GetMod(slug); + foreach (var modVersion in versions) + { + var loaders = new[] { "datapack" }; + var modEnFile = await ModrinthManager.GetModEnFile(addon, modVersion.ToVersionDirectory().ToMCVersion(), loaders, LangType.EN); + var modCnFile = await ModrinthManager.GetModEnFile(addon, modVersion.ToVersionDirectory().ToMCVersion(), loaders, LangType.CN); + + if (modEnFile.files != null && modEnFile.files.FirstOrDefault() is { } f2) + { + var s = $"{modCnFile.downloadFileName} 的英文语言文件"; + + textsV.Add(s); + langV.Add(LangFileWrapper.FromContent(f2)); + } + + if (modCnFile.files != null && modCnFile.files.FirstOrDefault() is { } f) + { + var s = $"{modCnFile.downloadFileName} 的中文语言文件"; + + textsV.Add(s); + langV.Add(LangFileWrapper.FromContent(f)); + } + } + return; + } if (slug.StartsWith("modrinth-")) { slug = slug["modrinth-".Length..]; @@ -767,6 +795,32 @@ } return; } + else if (slug.StartsWith("texture-packs-")) + { + slug = slug["texture-packs-".Length..]; + var addon = await CurseManager.GetAddon(slug); + foreach (var modVersion in versions) + { + var modEnFile = await CurseManager.GetModEnFile(addon, modVersion.ToVersionDirectory().ToMCVersion(), LangType.EN); + var modCnFile = await CurseManager.GetModEnFile(addon, modVersion.ToVersionDirectory().ToMCVersion(), LangType.CN); + + if (modEnFile.files != null && modEnFile.files.FirstOrDefault() is { } f2) + { + var s = $"{modCnFile.downloadFileName} 的英文语言文件"; + + textsV.Add(s); + langV.Add(LangFileWrapper.FromContent(f2)); + } + + if (modCnFile.files != null && modCnFile.files.FirstOrDefault() is { } f) + { + var s = $"{modCnFile.downloadFileName} 的中文语言文件"; + + textsV.Add(s); + langV.Add(LangFileWrapper.FromContent(f)); + } + } + } else { var addon = await CurseManager.GetAddon(slug); diff --git a/CFPABot/Command/CommandProcessor.cs b/CFPABot/Command/CommandProcessor.cs index d12698e..f457f57 100644 --- a/CFPABot/Command/CommandProcessor.cs +++ b/CFPABot/Command/CommandProcessor.cs @@ -154,7 +154,42 @@ public static async Task RunInternal(int prid, string content, int commentID, Gi MCVersion.v1122 => "en_us.lang", _ => "en_us.json" }; - if (curseForgeID.StartsWith("modrinth-")) + if (curseForgeID.StartsWith("modrinth-datapack-")) + { + var originalID = curseForgeID; + curseForgeID = curseForgeID["modrinth-datapack-".Length..]; + var addon = await ModrinthManager.GetMod(curseForgeID); + + var modID = await ModrinthManager.GetModID(addon, version, new[] { "datapack" }, true, false); + var (files, downloadFileName) = await ModrinthManager.GetModEnFile(addon, version, new[] { "datapack" }, LangType.EN); + if (files.Length != 1) + { + sb.AppendLine(Locale.Command_update_en_MultipleLangFiles); + continue; + } + + sb.AppendLine(string.Format(Locale.Command_update_en_Success, downloadFileName)); + var f = files[0]; + using var sr = new MemoryStream(f.ToUTF8Bytes()).CreateStreamReader(Encoding.UTF8); + using var sw = File.Open( + Path.Combine(r.WorkingDirectory, + $"projects/assets/{curseForgeID}/{versionString}/{modID}/lang/{versionFile}"), + FileMode.Create).CreateStreamWriter(new UTF8Encoding(false)); + + switch (version) + { + case MCVersion.v1122: + new LangFormatter(sr, sw).Format(); + break; + default: + new JsonFormatter(sr, sw).Format(); + break; + } + + r.AddAllFiles(); + r.Commit($"Update en_us file for {(curseForgeID.Replace("\"", "\\\""))}", user); + } + else if (curseForgeID.StartsWith("modrinth-")) { curseForgeID = curseForgeID["modrinth-".Length..]; var addon = await ModrinthManager.GetMod(curseForgeID); @@ -221,7 +256,7 @@ public static async Task RunInternal(int prid, string content, int commentID, Gi r.AddAllFiles(); r.Commit($"Update en_us file for {(curseForgeID.Replace("\"", "\\\""))}", user); } - + } if (line.StartsWith("/add-co-author ")) diff --git a/CFPABot/Controllers/UtilsController.cs b/CFPABot/Controllers/UtilsController.cs index bea9321..845ee65 100644 --- a/CFPABot/Controllers/UtilsController.cs +++ b/CFPABot/Controllers/UtilsController.cs @@ -93,12 +93,24 @@ public async Task GetModName(string slug) { try { - if (slug.StartsWith("modrinth-")) + if (slug.StartsWith("modrinth-datapack-")) + { + slug = slug["modrinth-datapack-".Length..]; + var mod = await ModrinthManager.GetMod(slug); + return Content(mod.Title); + } + else if (slug.StartsWith("modrinth-")) { slug = slug["modrinth-".Length..]; var mod = await ModrinthManager.GetMod(slug); return Content(mod.Title); } + else if (slug.StartsWith("texture-packs-")) + { + slug = slug["texture-packs-".Length..]; + var mod = await CurseManager.GetAddon(slug); + return Content(mod.Name); + } else { var mod = await CurseManager.GetAddon(slug); diff --git a/CFPABot/Utils/CommentBuilder.cs b/CFPABot/Utils/CommentBuilder.cs index f715dc8..9886f79 100644 --- a/CFPABot/Utils/CommentBuilder.cs +++ b/CFPABot/Utils/CommentBuilder.cs @@ -257,7 +257,7 @@ public async Task UpdateModLinkSegment(FileDiff[] diffs) var addons = new List(); var tasks = new List(); - foreach (var modid in modids.Where(x => x != "1UNKNOWN" && x != "0-modrinth-mod" && !x.StartsWith("modrinth-"))) + foreach (var modid in modids.Where(x => x != "1UNKNOWN" && x != "0-modrinth-mod" && !x.StartsWith("modrinth-") && !x.StartsWith("texture-packs-") && !x.StartsWith("modrinth-datapack-"))) { tasks.Add(Task.Run(async () => { @@ -280,7 +280,7 @@ public async Task UpdateModLinkSegment(FileDiff[] diffs) var client = new ModrinthClient(); - var modrinthMods = modInfos.Where(x => x.CurseForgeID.StartsWith("modrinth-")).Select(m => m.CurseForgeID.Substring("modrinth-".Length)).Distinct().ToArray(); + var modrinthMods = modInfos.Where(x => x.CurseForgeID.StartsWith("modrinth-") && !x.CurseForgeID.StartsWith("modrinth-datapack-")).Select(m => m.CurseForgeID.Substring("modrinth-".Length)).Distinct().ToArray(); var sbModrinthError = new StringBuilder(); var modrinthList = new List<(string slug, string url, string iconUrl, string name)>(); @@ -346,7 +346,41 @@ public async Task UpdateModLinkSegment(FileDiff[] diffs) sb.AppendLine(); } - if (addons.Count == 0 && modrinthList.Count == 0) + // texture-packs (CurseForge 材质包) + var texturePackSlugs = modInfos.Where(x => x.CurseForgeID.StartsWith("texture-packs-")) + .Select(m => m.CurseForgeID.Substring("texture-packs-".Length)).Distinct().ToArray(); + var texturePackList = new List<(string prefixedSlug, Mod addon)>(); + foreach (var realSlug in texturePackSlugs) + { + try + { + var addon = await CurseManager.GetAddon(realSlug); + texturePackList.Add(($"texture-packs-{realSlug}", addon)); + } + catch (CheckException e) + { + lock (sb) { sb.AppendLine(e.Message); } + } + } + + // modrinth-datapack (Modrinth 数据包) + var datapackMods = modInfos.Where(x => x.CurseForgeID.StartsWith("modrinth-datapack-")) + .Select(m => m.CurseForgeID.Substring("modrinth-datapack-".Length)).Distinct().ToArray(); + var datapackList = new List<(string prefixedSlug, string url, string iconUrl, string name)>(); + foreach (var realSlug in datapackMods) + { + try + { + var project = await client.Project.GetAsync(realSlug); + datapackList.Add(($"modrinth-datapack-{realSlug}", project.Url, project.IconUrl, project.Title)); + } + catch (ModrinthApiException e) + { + sb.AppendLine($"Modrinth Datapack 检索遇到问题:{e.Message}"); + } + } + + if (addons.Count == 0 && modrinthList.Count == 0 && texturePackList.Count == 0 && datapackList.Count == 0) { sb.AppendLine("ℹ 此 PR 没有检测到 CurseForge/Modrinth 模组修改。"); return; @@ -372,6 +406,35 @@ public async Task UpdateModLinkSegment(FileDiff[] diffs) modCount++; } + foreach (var (prefixedSlug, url, iconUrl, name) in datapackList) + { + sb1.AppendLine($"| " + + /* Thumbnail*/ $" |" + + /* Mod Name */ $" [**{name.Trim().Replace("[", "\\[").Replace("]", "\\]").Replace("|", "\\|")}**]({url}) |" + + /* Source */ $" " + + /* Mcmod */ $" [🟩 MCMOD](https://cn.bing.com/search?q=site:mcmod.cn%20{HttpUtility.UrlEncode(name)}) \\|" + + /* Compare */ $" [:file_folder: 对比(Azusa)](https://cfpa.cyan.cafe/Azusa/Diff/{PullRequestID}/{prefixedSlug}) |" + + /* Mod DL */ $" Modrinth Datapack |" + + "" + ); + modCount++; + } + + foreach (var (prefixedSlug, addon) in texturePackList) + { + Interlocked.Increment(ref modCount); + var infos = modInfos.Where(i => i.CurseForgeID == prefixedSlug).ToArray(); + var versions = infos.Select(i => i.Version).ToArray(); + sb1.AppendLine($"| " + + /* Thumbnail*/ $"{await CurseManager.GetThumbnailText(addon).ConfigureAwait(false)} |" + + /* Mod Name */ $" [**{addon.Name.Trim().Replace("[", "\\[").Replace("]", "\\]").Replace("|", "\\|")}**]({addon.Links.WebsiteUrl}) |" + + /* Source */ $" {CurseManager.GetRepoText(addon)} \\|" + + /* Mcmod */ $" [🟩 MCMOD](https://cn.bing.com/search?q=site:mcmod.cn%20{HttpUtility.UrlEncode(addon.Name)}) \\|" + + /* Compare */ $" [:file_folder: 对比(Azusa)](https://cfpa.cyan.cafe/Azusa/Diff/{PullRequestID}/{prefixedSlug}) |" + + /* Mod DL */ $" {await CurseManager.GetModRepoLinkText(addon, infos).ConfigureAwait(false)} |" + + ""); + } + foreach (var addon in addons.AsParallel().AsSequential().Select(async d1 => { Interlocked.Increment(ref modCount); @@ -934,7 +997,17 @@ public async Task UpdateCheckSegment(FileDiff[] diffs) try { var slug = names[2]; - if (slug.StartsWith("modrinth-")) + if (slug.StartsWith("modrinth-datapack-")) + { + var realSlug = slug["modrinth-datapack-".Length..]; + var addon = await ModrinthManager.GetMod(realSlug); + var modDomain = + await ModrinthManager.GetModID(addon, names[3].ToMCStandardVersion(), new[] { "datapack" }, true, false); + var rdir = $"projects/assets/{slug}/{names[3]}/{modDomain}/lang/"; + sb.AppendLine($" 自动找到该模组 Domain 为 `{modDomain}`,可能正确文件夹为 `{rdir}`。使用命令 `/mv \"{names.Take(4).Connect("/")}/\" \"{rdir}\"` 来移动路径。"); + sb.AppendLine(); + } + else if (slug.StartsWith("modrinth-")) { slug = slug["modrinth-".Length..]; var addon = await ModrinthManager.GetMod(slug); @@ -944,13 +1017,23 @@ public async Task UpdateCheckSegment(FileDiff[] diffs) sb.AppendLine($" 自动找到该模组 Domain 为 `{modDomain}`,可能正确文件夹为 `{rdir}`。使用命令 `/mv \"{names.Take(4).Connect("/")}/\" \"{rdir}\"` 来移动路径。"); sb.AppendLine(); } + else if (slug.StartsWith("texture-packs-")) + { + var realSlug = slug["texture-packs-".Length..]; + var addon = await CurseManager.GetAddon(realSlug); + var modDomain = + await CurseManager.GetModID(addon, names[3].ToMCStandardVersion(), true, false); + var rdir = $"projects/assets/{slug}/{names[3]}/{modDomain}/lang/"; + sb.AppendLine($" 自动找到该模组 Domain 为 `{modDomain}`,可能正确文件夹为 `{rdir}`。使用命令 `/mv \"{names.Take(4).Connect("/")}/\" \"{rdir}\"` 来移动路径。"); + sb.AppendLine(); + } else { var addon = await CurseManager.GetAddon(names[2]); var modDomain = await CurseManager.GetModID(addon, names[3].ToMCStandardVersion(), true, false); var rdir = $"projects/assets/{names[2]}/{names[3]}/{modDomain}/lang/"; - sb.AppendLine($" 自动找到该模组 Domain 为 `{modDomain}`,可能正确文件夹为 `{rdir}`。使用命令 `/mv \"{names.Take(4).Connect("/")}/\" \"{rdir}\"` 来移动路径。"); + sb.AppendLine($" 自动找到该模组 Mod Domain 为 `{modDomain}`,可能正确文件夹为 `{rdir}`。使用命令 `/mv \"{names.Take(4).Connect("/")}/\" \"{rdir}\"` 来移动路径。"); sb.AppendLine(); } @@ -974,12 +1057,33 @@ public async Task UpdateCheckSegment(FileDiff[] diffs) sb.AppendLine($"⚠ 检测到一个语言文件,但提交路径不正常。缺少了 {{ModDomain}} 或 {{CurseForge 项目名}} 文件夹。请检查提交路径:`{diff.To}`;"); try { - var addon = await CurseManager.GetAddon(names[2]); - var modDomain = - await CurseManager.GetModID(addon, names[3].ToMCStandardVersion(), true, false); - var rdir = $"projects/assets/{names[2]}/{names[3]}/{modDomain}/lang/"; - sb.AppendLine($" 自动找到了该模组的 Mod Domain 为 `{modDomain}`,可能的正确文件夹为 `{rdir}`。 你可以使用命令 `/mv \"{names.Take(5).Connect("/")}/\" \"{rdir}\"` 来移动路径。"); - sb.AppendLine(); + var slug = names[2]; + if (slug.StartsWith("modrinth-datapack-")) + { + var realSlug = slug["modrinth-datapack-".Length..]; + var addon = await ModrinthManager.GetMod(realSlug); + var modDomain = await ModrinthManager.GetModID(addon, names[3].ToMCStandardVersion(), new[] { "datapack" }, true, false); + var rdir = $"projects/assets/{slug}/{names[3]}/{modDomain}/lang/"; + sb.AppendLine($" 自动找到了该模组的 Mod Domain 为 `{modDomain}`,可能的正确文件夹为 `{rdir}`。 你可以使用命令 `/mv \"{names.Take(5).Connect("/")}/\" \"{rdir}\"` 来移动路径。"); + sb.AppendLine(); + } + else if (slug.StartsWith("texture-packs-")) + { + var realSlug = slug["texture-packs-".Length..]; + var addon = await CurseManager.GetAddon(realSlug); + var modDomain = await CurseManager.GetModID(addon, names[3].ToMCStandardVersion(), true, false); + var rdir = $"projects/assets/{slug}/{names[3]}/{modDomain}/lang/"; + sb.AppendLine($" 自动找到了该模组的 Mod Domain 为 `{modDomain}`,可能的正确文件夹为 `{rdir}`。 你可以使用命令 `/mv \"{names.Take(5).Connect("/")}/\" \"{rdir}\"` 来移动路径。"); + sb.AppendLine(); + } + else + { + var addon = await CurseManager.GetAddon(names[2]); + var modDomain = await CurseManager.GetModID(addon, names[3].ToMCStandardVersion(), true, false); + var rdir = $"projects/assets/{names[2]}/{names[3]}/{modDomain}/lang/"; + sb.AppendLine($" 自动找到了该模组的 Mod Domain 为 `{modDomain}`,可能的正确文件夹为 `{rdir}`。 你可以使用命令 `/mv \"{names.Take(5).Connect("/")}/\" \"{rdir}\"` 来移动路径。"); + sb.AppendLine(); + } } catch (Exception) { @@ -1062,7 +1166,11 @@ public async Task UpdateCheckSegment(FileDiff[] diffs) try { - if (curseID != "1UNKNOWN" && curseID != "0-modrinth-mod" && !curseID.StartsWith("modrinth-")) + if (curseID.StartsWith("texture-packs-")) + { + addon = await CurseManager.GetAddon(curseID["texture-packs-".Length..]); + } + else if (curseID != "1UNKNOWN" && curseID != "0-modrinth-mod" && !curseID.StartsWith("modrinth-")) addon = await CurseManager.GetAddon(curseID); } catch (Exception) @@ -1101,7 +1209,12 @@ public async Task UpdateCheckSegment(FileDiff[] diffs) try { - if (curseID != "1UNKNOWN" && curseID != "0-modrinth-mod" && !curseID.StartsWith("modrinth-")) + if (curseID.StartsWith("texture-packs-")) + { + var realSlug = curseID["texture-packs-".Length..]; + addon = await CurseManager.GetAddon(realSlug); + } + else if (curseID != "1UNKNOWN" && curseID != "0-modrinth-mod" && !curseID.StartsWith("modrinth-")) addon = await CurseManager.GetAddon(curseID); } catch (Exception) @@ -1109,7 +1222,8 @@ public async Task UpdateCheckSegment(FileDiff[] diffs) sb.AppendLine(string.Format(Locale.Check_ModID_ModNotFound, curseID, versionString)); } - if (addon != null && addon.Slug != curseID) + var curseIDForApi = curseID.StartsWith("texture-packs-") ? curseID["texture-packs-".Length..] : curseID; + if (addon != null && addon.Slug != curseIDForApi) { sb.AppendLine("❌ 检测到此模组作者更改了 Slug 名,请使用以下命令进行路径移动:"); sb.AppendLine("```"); @@ -1158,7 +1272,9 @@ public async Task UpdateCheckSegment(FileDiff[] diffs) Project z = null; try { - if (curseID.StartsWith("modrinth-")) + if (curseID.StartsWith("modrinth-datapack-")) + z = await ModrinthManager.GetMod(curseID["modrinth-datapack-".Length..]); + else if (curseID.StartsWith("modrinth-")) z = await ModrinthManager.GetMod(curseID["modrinth-".Length..]); } catch (Exception) @@ -1168,7 +1284,9 @@ public async Task UpdateCheckSegment(FileDiff[] diffs) if (z != null) try { - var filemodid = await ModrinthManager.GetModIDForCheck(z, mcVersion); + var filemodid = curseID.StartsWith("modrinth-datapack-") + ? await ModrinthManager.GetModIDForCheck(z, mcVersion, new[] { "datapack" }) + : await ModrinthManager.GetModIDForCheck(z, mcVersion); if (filemodid == null || filemodid.Length == 0) { sb.AppendLine(string.Format(Locale.Check_ModID_ModIDNotFound, "modrinth~" + modid)); diff --git a/CFPABot/Utils/CurseManager.cs b/CFPABot/Utils/CurseManager.cs index 06f1e56..cf8e65e 100644 --- a/CFPABot/Utils/CurseManager.cs +++ b/CFPABot/Utils/CurseManager.cs @@ -550,7 +550,7 @@ public static async Task Update() static void AddMapping(List addons) { - foreach (var addon in addons.Where(s => s.GameId == 432 && s.Links.WebsiteUrl.StartsWith("https://www.curseforge.com/minecraft/mc-mods/"))) + foreach (var addon in addons.Where(s => s.GameId == 432 && (s.Links.WebsiteUrl.StartsWith("https://www.curseforge.com/minecraft/mc-mods/") || s.Links.WebsiteUrl.StartsWith("https://www.curseforge.com/minecraft/texture-packs/")))) lock (ModIDMappingMetadata.Instance) { ModIDMappingMetadata.Instance.Mapping[addon.Slug] = (int)addon.Id; diff --git a/CFPABot/Utils/ModrinthManager.cs b/CFPABot/Utils/ModrinthManager.cs index 1eeda85..c52f7a4 100644 --- a/CFPABot/Utils/ModrinthManager.cs +++ b/CFPABot/Utils/ModrinthManager.cs @@ -21,13 +21,20 @@ public static Task GetMod(string slug) return Instance.Project.GetAsync(slug); } - public static async Task GetModID(Project addon, MCVersion? version, bool enforcedLang = false, + static string[] DefaultLoaders(MCVersion? version) => + version?.ToString().Contains("fabric") == true ? new[] { "fabric" } : new[] { "fabric", "forge" }; + + public static Task GetModID(Project addon, MCVersion? version, bool enforcedLang = false, + bool connect = true) + => GetModID(addon, version, DefaultLoaders(version), enforcedLang, connect); + + public static async Task GetModID(Project addon, MCVersion? version, string[] loaders, bool enforcedLang = false, bool connect = true) { if (version == null) return "未知"; try { - var versions = await Instance.Version.GetProjectVersionListAsync(addon.Slug, new []{ version.ToString().Contains("fabric") ? "fabric": "forge"}); + var versions = await Instance.Version.GetProjectVersionListAsync(addon.Slug, loaders); if (versions.FirstOrDefault(f => f.GameVersions.Any(x=> x.StartsWith(version.Value.ToStandardVersionString()))) is { } file) { var fileName = await Download.DownloadFile(file.Files.First().Url); // 我好累 @@ -51,12 +58,15 @@ public static async Task GetModID(Project addon, MCVersion? version, boo return "未知"; } - public static async Task GetModIDForCheck(Project addon, MCVersion? version) + public static Task GetModIDForCheck(Project addon, MCVersion? version) + => GetModIDForCheck(addon, version, DefaultLoaders(version)); + + public static async Task GetModIDForCheck(Project addon, MCVersion? version, string[] loaders) { if (version == null) return null; try { - var versions = await Instance.Version.GetProjectVersionListAsync(addon.Slug, new []{ version.ToString().Contains("fabric") ? "fabric": "forge"}); + var versions = await Instance.Version.GetProjectVersionListAsync(addon.Slug, loaders); if (versions.Where(x => x.GameVersions.Any(y => y.StartsWith(version.Value.ToStandardVersionString()))).FirstOrDefault() is {} file) { var fileName = await Download.DownloadFile(file.Files.Any(x => x.FileName.ToLower().Contains("fabric") && version.ToString().Contains("fabric"))?file.Files.First(x => x.FileName.ToLower().Contains("fabric")).Url: file.Files.First().Url); @@ -79,12 +89,15 @@ public static async Task GetModIDForCheck(Project addon, MCVersion? ve return null; } - public static async Task<(string[] files, string downloadFileName)> GetModEnFile(Project addon, MCVersion? version, LangType type) + public static Task<(string[] files, string downloadFileName)> GetModEnFile(Project addon, MCVersion? version, LangType type) + => GetModEnFile(addon, version, DefaultLoaders(version), type); + + public static async Task<(string[] files, string downloadFileName)> GetModEnFile(Project addon, MCVersion? version, string[] loaders, LangType type) { if (version == null) return (null, null); try { - var versions = await Instance.Version.GetProjectVersionListAsync(addon.Slug, new[] { version.ToString().Contains("fabric") ? "fabric" : "forge" }); + var versions = await Instance.Version.GetProjectVersionListAsync(addon.Slug, loaders); if (versions.Where(x => x.GameVersions.Any(y => y.StartsWith(version.Value.ToStandardVersionString()))).FirstOrDefault() is { } file) { var d = file.Files.Any(x => x.FileName.ToLower().Contains("fabric") && version.ToString().Contains("fabric")) ? file.Files.First(x => x.FileName.ToLower().Contains("fabric")) : file.Files.First(); From b366f07c75ed636736a98c985c720427bb68329d Mon Sep 17 00:00:00 2001 From: SlimeSB <86453765+SlimeSB@users.noreply.github.com> Date: Fri, 15 May 2026 16:57:31 +0800 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20texture-packs-/?= =?UTF-8?q?modrinth-datapack-=20=E5=89=8D=E7=BC=80=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E7=9A=84=2010=20=E4=B8=AA=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 高优先级 CommentBuilder.cs:1226-1235 — Slug 变更提示 mv 目的地拼接 addon.Slug(不带前缀),材质包缺少 texture-packs-,导致路径错位 CommandProcessor.cs:157-191 — /update-en modrinth-datapack- 路径写入时使用剥离后的裸 slug,丢失前缀;originalID 定义了但未使用,改用 originalID CommandProcessor.cs:226-231 — /update-en else 分支未剥离 texture-packs- 前缀就传入 CurseManager.GetAddon,映射查找失败 中高优先级 ModrinthManager.cs:24-25 — DefaultLoaders 非 fabric 默认返回 {"fabric","forge"} 两个 loader,旧行为为 {"forge"},影响普通 modrinth- 模组的文件筛选 中优先级 Diff.razor:743-823 — GetModFiles() 直接修改页面字段 slug,后续 AddVersion() 等用裸 slug 比对含前缀的 CurseForgeSlug,全部匹配失败;改为局部变量 UtilsController.cs:47-49 — ModID 端点 slug 参数未剥离 texture-packs- 前缀就调用 GetAddon UtilsController.cs:53 — GetAllModFilesInRepo 用带前缀 slug 查 Mapping,材质包全部被 Where(cfid != 0) 静默过滤 CompareController.cs:170-171 — Compare 页面 modid 来自路径含 texture-packs-,直接传 GetAddon 失败 CurseManager.cs:530 — LastID 在空 Mapping 上调用 Max() 抛异常,补充 DefaultIfEmpty(0) 低优先级 CommandProcessor.cs:489-491 — /add-mapping 将 texture-packs-xxx 作为 key 存放,校验时 API 返回裸 slug,addon.Slug != slug 恒真 --- CFPABot/Azusa/Pages/Diff.razor | 12 ++++---- CFPABot/Command/CommandProcessor.cs | 11 ++++++-- CFPABot/Controllers/CompareController.cs | 3 +- CFPABot/Controllers/UtilsController.cs | 4 ++- CFPABot/Utils/CommentBuilder.cs | 3 +- CFPABot/Utils/CurseManager.cs | 36 ++++++++++++++---------- CFPABot/Utils/ModrinthManager.cs | 2 +- 7 files changed, 43 insertions(+), 28 deletions(-) diff --git a/CFPABot/Azusa/Pages/Diff.razor b/CFPABot/Azusa/Pages/Diff.razor index 6f7abc2..f7b7282 100644 --- a/CFPABot/Azusa/Pages/Diff.razor +++ b/CFPABot/Azusa/Pages/Diff.razor @@ -742,8 +742,8 @@ { if (slug.StartsWith("modrinth-datapack-")) { - slug = slug["modrinth-datapack-".Length..]; - var addon = await ModrinthManager.GetMod(slug); + var realSlug = slug["modrinth-datapack-".Length..]; + var addon = await ModrinthManager.GetMod(realSlug); foreach (var modVersion in versions) { var loaders = new[] { "datapack" }; @@ -770,8 +770,8 @@ } if (slug.StartsWith("modrinth-")) { - slug = slug["modrinth-".Length..]; - var addon = await ModrinthManager.GetMod(slug); + var realSlug = slug["modrinth-".Length..]; + var addon = await ModrinthManager.GetMod(realSlug); foreach (var modVersion in versions) { var modEnFile = await ModrinthManager.GetModEnFile(addon, modVersion.ToVersionDirectory().ToMCVersion(), LangType.EN); @@ -797,8 +797,8 @@ } else if (slug.StartsWith("texture-packs-")) { - slug = slug["texture-packs-".Length..]; - var addon = await CurseManager.GetAddon(slug); + var realSlug = slug["texture-packs-".Length..]; + var addon = await CurseManager.GetAddon(realSlug); foreach (var modVersion in versions) { var modEnFile = await CurseManager.GetModEnFile(addon, modVersion.ToVersionDirectory().ToMCVersion(), LangType.EN); diff --git a/CFPABot/Command/CommandProcessor.cs b/CFPABot/Command/CommandProcessor.cs index f457f57..3d7bf3d 100644 --- a/CFPABot/Command/CommandProcessor.cs +++ b/CFPABot/Command/CommandProcessor.cs @@ -173,7 +173,7 @@ public static async Task RunInternal(int prid, string content, int commentID, Gi using var sr = new MemoryStream(f.ToUTF8Bytes()).CreateStreamReader(Encoding.UTF8); using var sw = File.Open( Path.Combine(r.WorkingDirectory, - $"projects/assets/{curseForgeID}/{versionString}/{modID}/lang/{versionFile}"), + $"projects/assets/{originalID}/{versionString}/{modID}/lang/{versionFile}"), FileMode.Create).CreateStreamWriter(new UTF8Encoding(false)); switch (version) @@ -187,7 +187,7 @@ public static async Task RunInternal(int prid, string content, int commentID, Gi } r.AddAllFiles(); - r.Commit($"Update en_us file for {(curseForgeID.Replace("\"", "\\\""))}", user); + r.Commit($"Update en_us file for {(originalID.Replace("\"", "\\\""))}", user); } else if (curseForgeID.StartsWith("modrinth-")) { @@ -225,7 +225,10 @@ public static async Task RunInternal(int prid, string content, int commentID, Gi } else { - var addon = await CurseManager.GetAddon(curseForgeID); + var cfIDForApi = curseForgeID; + if (curseForgeID.StartsWith("texture-packs-")) + cfIDForApi = curseForgeID["texture-packs-".Length..]; + var addon = await CurseManager.GetAddon(cfIDForApi); var modID = await CurseManager.GetModID(addon, version, true, false); var (files, downloadFileName) = await CurseManager.GetModEnFile(addon, version, LangType.EN); @@ -487,6 +490,8 @@ void Format(LangFileType? type = null) // continue; // } var slug = args[0]; + if (slug.StartsWith("texture-packs-")) + slug = slug["texture-packs-".Length..]; var curseForgeProjectID = args[1]; var curseForgeProjectIDInt = curseForgeProjectID.ToInt(); ModIDMappingMetadata.Instance.Mapping[slug] = curseForgeProjectIDInt; diff --git a/CFPABot/Controllers/CompareController.cs b/CFPABot/Controllers/CompareController.cs index 57e6a3f..9e690d4 100644 --- a/CFPABot/Controllers/CompareController.cs +++ b/CFPABot/Controllers/CompareController.cs @@ -167,7 +167,8 @@ async Task R(string l) CurseForge.APIClient.Models.Mods.Mod addon = null; try { - addon = await CurseManager.GetAddon(modid); + var cfSlug = modid.StartsWith("texture-packs-") ? modid["texture-packs-".Length..] : modid; + addon = await CurseManager.GetAddon(cfSlug); } catch (Exception e) { diff --git a/CFPABot/Controllers/UtilsController.cs b/CFPABot/Controllers/UtilsController.cs index 845ee65..9d8b626 100644 --- a/CFPABot/Controllers/UtilsController.cs +++ b/CFPABot/Controllers/UtilsController.cs @@ -44,13 +44,15 @@ public IActionResult GitHubToken() [HttpGet("ModID")] public async Task ModID([FromQuery]string slug, [FromQuery] string versionString) { + if (slug.StartsWith("texture-packs-")) + slug = slug["texture-packs-".Length..]; return await CurseManager.GetModID(await CurseManager.GetAddon(slug), versionString.ToMCVersion(), true, false); } [HttpGet("GetAllModFilesInRepo")] public async Task GetAllModFilesInRepo() { - var mods = ModList.ModListConfig.Instance.ModLists.Select(x => new {slug=x.modSlug, cfid= ModIDMappingMetadata.Instance.Mapping.GetValueOrDefault(x.modSlug), versions = x.versions.Select(y => y.version.ToVersionDirectory()) }) + var mods = ModList.ModListConfig.Instance.ModLists.Select(x => new {slug=x.modSlug, cfid= ModIDMappingMetadata.Instance.Mapping.GetValueOrDefault(x.modSlug.StartsWith("texture-packs-") ? x.modSlug["texture-packs-".Length..] : x.modSlug), versions = x.versions.Select(y => y.version.ToVersionDirectory()) }) .Where(x => x.cfid != 0); return new JsonResult(mods); diff --git a/CFPABot/Utils/CommentBuilder.cs b/CFPABot/Utils/CommentBuilder.cs index 9886f79..2793acb 100644 --- a/CFPABot/Utils/CommentBuilder.cs +++ b/CFPABot/Utils/CommentBuilder.cs @@ -1225,9 +1225,10 @@ public async Task UpdateCheckSegment(FileDiff[] diffs) var curseIDForApi = curseID.StartsWith("texture-packs-") ? curseID["texture-packs-".Length..] : curseID; if (addon != null && addon.Slug != curseIDForApi) { + var slugPrefix = curseID.StartsWith("texture-packs-") ? "texture-packs-" : ""; sb.AppendLine("❌ 检测到此模组作者更改了 Slug 名,请使用以下命令进行路径移动:"); sb.AppendLine("```"); - sb.AppendLine($"/mv projects/assets/{curseID}/{versionString}/ projects/assets/{addon.Slug}/{versionString}/"); + sb.AppendLine($"/mv projects/assets/{curseID}/{versionString}/ projects/assets/{slugPrefix}{addon.Slug}/{versionString}/"); sb.AppendLine($"/add-mapping {addon.Slug} {addon.Id}"); sb.AppendLine("```"); sb.AppendLine(); diff --git a/CFPABot/Utils/CurseManager.cs b/CFPABot/Utils/CurseManager.cs index cf8e65e..f98f076 100644 --- a/CFPABot/Utils/CurseManager.cs +++ b/CFPABot/Utils/CurseManager.cs @@ -527,7 +527,7 @@ public class ModIDMappingMetadata : Configuration { public Dictionary Mapping { get; set; } = new(); public DateTime LastUpdate { get; set; } - [JsonIgnore] public int LastID => Mapping.Values.Max(); + [JsonIgnore] public int LastID => Mapping.Values.DefaultIfEmpty(0).Max(); } static class CurseForgeClientExtensions @@ -556,20 +556,26 @@ static void AddMapping(List addons) ModIDMappingMetadata.Instance.Mapping[addon.Slug] = (int)addon.Id; } } - // public static async Task Build() - // { - // var client = new ForgeClient(); - // var config = ModIDMappingMetadata.Instance; - // for (int i = 0; i < 40; i++) - // { - // var addons = await client.Addons.RetriveAddons(Enumerable.Range(i * 20000 + 1, 20000).ToArray()); - // AddMapping(addons); - // Console.WriteLine($"初始化 Mapping: {i + 1}/40"); - // } - // config.LastUpdate = DateTime.Now; - // ModIDMappingMetadata.Save(); - // } - // + public static async Task Build() + { + var config = ModIDMappingMetadata.Instance; + for (int i = 0; i < 40; i++) + { + try + { + var ids = Enumerable.Range(i * 20000 + 1, 20000).Select(x => x).ToList(); + var addons = await CurseManager.GetCfClient().GetModsByIdListAsync(new GetModsByIdsListRequestBody() { ModIds = ids }); + AddMapping(addons.Data); + } + catch (Exception e) + { + Log.Error(e, $"Build mapping batch {i + 1}/40 failed"); + } + } + config.LastUpdate = DateTime.Now; + ModIDMappingMetadata.Save(); + } + // static SemaphoreSlim semaphore = new SemaphoreSlim(1); // public static async Task UpdateIfRequired() // { diff --git a/CFPABot/Utils/ModrinthManager.cs b/CFPABot/Utils/ModrinthManager.cs index c52f7a4..38f8a55 100644 --- a/CFPABot/Utils/ModrinthManager.cs +++ b/CFPABot/Utils/ModrinthManager.cs @@ -22,7 +22,7 @@ public static Task GetMod(string slug) } static string[] DefaultLoaders(MCVersion? version) => - version?.ToString().Contains("fabric") == true ? new[] { "fabric" } : new[] { "fabric", "forge" }; + version?.ToString().Contains("fabric") == true ? new[] { "fabric" } : new[] { "forge" }; public static Task GetModID(Project addon, MCVersion? version, bool enforcedLang = false, bool connect = true) From c6b1f81b36aa7ae29182352688c89cfccd57de2f Mon Sep 17 00:00:00 2001 From: SlimeSB <86453765+SlimeSB@users.noreply.github.com> Date: Fri, 15 May 2026 17:13:47 +0800 Subject: [PATCH 3/4] =?UTF-8?q?feat(CommentBuilder):=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=20Modrinth=20=E6=A8=A1=E7=BB=84=E8=B7=AF=E5=BE=84=E5=BB=BA?= =?UTF-8?q?=E8=AE=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为 `modrinth-` 前缀的 slug 添加 Mod Domain 查找和路径建议 - 复用现有的 `/mv` 命令路径移动提示逻辑 --- CFPABot/Utils/CommentBuilder.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CFPABot/Utils/CommentBuilder.cs b/CFPABot/Utils/CommentBuilder.cs index 2793acb..500f862 100644 --- a/CFPABot/Utils/CommentBuilder.cs +++ b/CFPABot/Utils/CommentBuilder.cs @@ -1067,6 +1067,15 @@ public async Task UpdateCheckSegment(FileDiff[] diffs) sb.AppendLine($" 自动找到了该模组的 Mod Domain 为 `{modDomain}`,可能的正确文件夹为 `{rdir}`。 你可以使用命令 `/mv \"{names.Take(5).Connect("/")}/\" \"{rdir}\"` 来移动路径。"); sb.AppendLine(); } +else if (slug.StartsWith("modrinth-")) + { + var realSlug = slug["modrinth-".Length..]; + var addon = await ModrinthManager.GetMod(realSlug); + var modDomain = await ModrinthManager.GetModID(addon, names[3].ToMCStandardVersion(), true, false); + var rdir = $"projects/assets/{names[2]}/{names[3]}/{modDomain}/lang/"; + sb.AppendLine($" 自动找到了该模组的 Mod Domain 为 `{modDomain}`,可能的正确文件夹为 `{rdir}`。 你可以使用命令 `/mv \"{names.Take(5).Connect("/")}/\" \"{rdir}\"` 来移动路径。"); + sb.AppendLine(); + } else if (slug.StartsWith("texture-packs-")) { var realSlug = slug["texture-packs-".Length..]; From 872aa094a18d22787b4393d2f12483cfc1c45101 Mon Sep 17 00:00:00 2001 From: SlimeSB <86453765+SlimeSB@users.noreply.github.com> Date: Fri, 15 May 2026 17:20:22 +0800 Subject: [PATCH 4/4] =?UTF-8?q?style:=20=E4=BF=AE=E6=AD=A3=20`CommentBuild?= =?UTF-8?q?er.cs`=20=E4=B8=AD=20`else=20if`=20=E7=BC=A9=E8=BF=9B=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CFPABot/Utils/CommentBuilder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CFPABot/Utils/CommentBuilder.cs b/CFPABot/Utils/CommentBuilder.cs index 500f862..57f064c 100644 --- a/CFPABot/Utils/CommentBuilder.cs +++ b/CFPABot/Utils/CommentBuilder.cs @@ -1007,7 +1007,7 @@ public async Task UpdateCheckSegment(FileDiff[] diffs) sb.AppendLine($" 自动找到该模组 Domain 为 `{modDomain}`,可能正确文件夹为 `{rdir}`。使用命令 `/mv \"{names.Take(4).Connect("/")}/\" \"{rdir}\"` 来移动路径。"); sb.AppendLine(); } - else if (slug.StartsWith("modrinth-")) + else if (slug.StartsWith("modrinth-")) { slug = slug["modrinth-".Length..]; var addon = await ModrinthManager.GetMod(slug); @@ -1067,7 +1067,7 @@ public async Task UpdateCheckSegment(FileDiff[] diffs) sb.AppendLine($" 自动找到了该模组的 Mod Domain 为 `{modDomain}`,可能的正确文件夹为 `{rdir}`。 你可以使用命令 `/mv \"{names.Take(5).Connect("/")}/\" \"{rdir}\"` 来移动路径。"); sb.AppendLine(); } -else if (slug.StartsWith("modrinth-")) + else if (slug.StartsWith("modrinth-")) { var realSlug = slug["modrinth-".Length..]; var addon = await ModrinthManager.GetMod(realSlug);