ResourceVersion.vb 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. ''' <summary>
  2. ''' 社区资源的单个可下载版本。
  3. ''' </summary>
  4. Public Class ResourceVersion
  5. '基础字段
  6. ''' <summary>
  7. ''' 用于唯一性鉴别该版本的 ID。CurseForge 中为 123456 的大整数,Modrinth 中为英文乱码的 Version 字段。
  8. ''' </summary>
  9. Public Id As String
  10. ''' <summary>
  11. ''' 该版本所对应的单一资源类别。
  12. ''' </summary>
  13. Public ResourceType As ResourceTypes
  14. '描述性字段
  15. ''' <summary>
  16. ''' 版本描述名。
  17. ''' 并非文件名或版本号,是由上传者完全自定义的字段。
  18. ''' </summary>
  19. Public Display As String
  20. ''' <summary>
  21. ''' 发布时间。
  22. ''' </summary>
  23. Public ReleaseDate As Date
  24. ''' <summary>
  25. ''' 下载量计数。
  26. ''' 注意,仅为一个平台的计数,无法反映多平台加起来的下载量,且 CurseForge 可能错误地返回 0。
  27. ''' </summary>
  28. Public DownloadCount As Integer
  29. ''' <summary>
  30. ''' 版本号。
  31. ''' 不一定是标准格式。CurseForge 默认为 Nothing。
  32. ''' </summary>
  33. Public Version As String
  34. ''' <summary>
  35. ''' 所有支持的 Mod 加载器列表。可能为 <see cref="ModLoaders.None"/>。
  36. ''' </summary>
  37. Public ModLoaders As ModLoaders = ModLoaders.None
  38. ''' <summary>
  39. ''' 支持的游戏版本列表。类型包括:"26.1.5","26.1","26.1 预览版","1.18.5","1.18","1.18 预览版","21w15a","未知版本"。
  40. ''' </summary>
  41. Public GameVersions As List(Of String)
  42. ''' <summary>
  43. ''' 发布状态。
  44. ''' </summary>
  45. Public ReleaseType As ReleaseTypes
  46. ''' <summary>
  47. ''' 发布状态的友好描述。
  48. ''' 例如:"正式版","Beta 版"。
  49. ''' </summary>
  50. Public ReadOnly Property ReleaseTypeDisplay As String
  51. Get
  52. Select Case ReleaseType
  53. Case ReleaseTypes.Release
  54. Return "正式版"
  55. Case ReleaseTypes.Beta
  56. Return If(ModeDebug, "Beta 版", "测试版")
  57. Case Else
  58. Return If(ModeDebug, "Alpha 版", "早期测试版")
  59. End Select
  60. End Get
  61. End Property
  62. Public Enum ReleaseTypes
  63. 'https://docs.curseforge.com/rest-api/#tocS_FileReleaseType
  64. Release = 1
  65. Beta = 2
  66. Alpha = 3
  67. End Enum
  68. '下载信息
  69. ''' <summary>
  70. ''' 下载信息是否可用。
  71. ''' </summary>
  72. Public ReadOnly Property DownloadAvailable As Boolean
  73. Get
  74. Return FileName IsNot Nothing AndAlso DownloadUrls IsNot Nothing
  75. End Get
  76. End Property
  77. ''' <summary>
  78. ''' 文件名。
  79. ''' </summary>
  80. Public FileName As String = Nothing
  81. ''' <summary>
  82. ''' 对应文件的所有可能的下载 URL。
  83. ''' </summary>
  84. Public DownloadUrls As List(Of String)
  85. ''' <summary>
  86. ''' 对应文件的 SHA1 或 MD5。
  87. ''' </summary>
  88. Public Hash As String = Nothing
  89. ''' <summary>
  90. ''' 文件大小。不可用时为 -1。
  91. ''' </summary>
  92. Public Size As Integer = -1
  93. ''' <summary>
  94. ''' 作为该版本依赖项的项目 ID。
  95. ''' 它们可能没有加载,在加载后会添加到 Dependencies 中(主要是因为 Modrinth 返回的是字符串 ID 而非 Slug,导致 Project.Id 查询不到)。
  96. ''' </summary>
  97. Private RawDependencies As New List(Of String)
  98. ''' <summary>
  99. ''' 作为该版本依赖项的项目 ID。
  100. ''' </summary>
  101. Public Dependencies As New List(Of String)
  102. '初始化
  103. ''' <summary>
  104. ''' 从平台 API 返回的 JSON 中初始化实例。若出错会抛出异常。
  105. ''' </summary>
  106. Public Shared Function FromPlatformJson(Data As JObject, ExpectedResourceType As ResourceTypes) As ResourceVersion
  107. Dim Result As New ResourceVersion
  108. With Result
  109. .ResourceType = ExpectedResourceType
  110. If Data.ContainsKey("gameId") Then
  111. #Region "CurseForge"
  112. '简单信息
  113. .Id = Data("id")
  114. .Display = Data("displayName").ToString.Replace(" ", "").Trim(" ")
  115. .Version = Nothing
  116. .ReleaseDate = Data("fileDate")
  117. .ReleaseType = CType(Data("releaseType").ToObject(Of Integer), ReleaseTypes)
  118. .DownloadCount = Data("downloadCount")
  119. .FileName = Data("fileName")
  120. .Size = Data("fileLength")
  121. .Hash = CType(Data("hashes"), JArray).ToList.FirstOrDefault(Function(s) s("algo").ToObject(Of Integer) = 1)?("value")
  122. If .Hash Is Nothing Then .Hash = CType(Data("hashes"), JArray).ToList.FirstOrDefault(Function(s) s("algo").ToObject(Of Integer) = 2)?("value")
  123. 'DownloadAddress
  124. Dim Url = Data("downloadUrl").ToString
  125. If Url = "" Then Url = $"https://edge.forgecdn.net/files/{CInt(.Id.ToString.Substring(0, 4))}/{CInt(.Id.ToString.Substring(4))}/{ .FileName}"
  126. .DownloadUrls = ParseCurseForgeDownloadUrls(Url.Replace(.FileName, EscapeUtils.UrlEscape(.FileName))) '对脑残 CurseForge 的下载地址进行多种修正
  127. .DownloadUrls.Add(DlSourceModGet(Url)) '添加镜像源;注意 MCIM 源不支持 URL 编码后的文件名,必须传入 URL 编码前的文件名
  128. .DownloadUrls = .DownloadUrls.Distinct.ToList '最终去重
  129. 'Dependencies
  130. If Data.ContainsKey("dependencies") Then
  131. .RawDependencies = Data("dependencies").
  132. Where(Function(d) d("relationType").ToObject(Of Integer) = 3 AndAlso '种类为依赖
  133. d("modId").ToObject(Of Integer) <> 306612 AndAlso d("modId").ToObject(Of Integer) <> 634179). '排除 Fabric API 和 Quilt API
  134. Select(Function(d) d("modId").ToString).ToList
  135. End If
  136. 'GameVersions
  137. Dim RawVersions As List(Of String) = Data("gameVersions").Select(Function(t) t.ToString.Trim.Lower).ToList
  138. .GameVersions = RawVersions.
  139. Where(Function(v) McVersion.IsFormatFit(v)).
  140. Select(Function(v) v.Replace("-snapshot", " 预览版")).
  141. Distinct.ToList
  142. If .GameVersions.IsSingle Then
  143. .GameVersions = .GameVersions.ToList
  144. ElseIf .GameVersions.Count > 1 Then
  145. .GameVersions = .GameVersions.SortByComparison(AddressOf CompareVersionGE).ToList
  146. If .ResourceType = ResourceTypes.ModPack Then .GameVersions = { .GameVersions(0)}.ToList '整合包理应只 “支持” 一个版本
  147. Else
  148. .GameVersions = New List(Of String) From {"未知版本"}
  149. End If
  150. 'ModLoaders
  151. .ModLoaders = ModLoaders.None
  152. For Each Loader In EnumUtils.GetAllFlags(Of ModLoaders)()
  153. If RawVersions.Contains(Loader.ToString.Lower) Then .ModLoaders = .ModLoaders Or Loader
  154. Next
  155. #End Region
  156. Else
  157. #Region "Modrinth"
  158. '简单信息
  159. .Id = Data("id")
  160. .Display = Data("name").ToString.Replace(" ", "").Trim(" ")
  161. .Version = Data("version_number")
  162. .ReleaseDate = Data("date_published")
  163. .ReleaseType = If(Data("version_type").ToString = "release", ReleaseTypes.Release, If(Data("version_type").ToString = "beta", ReleaseTypes.Beta, ReleaseTypes.Alpha))
  164. .DownloadCount = Data("downloads")
  165. If CType(Data("files"), JArray).Any() Then '可能为空
  166. Dim File As JToken = Data("files")(0)
  167. .FileName = File("filename")
  168. .DownloadUrls = New List(Of String) From {File("url"), DlSourceModGet(File("url"))}.Distinct.ToList '同时添加了镜像源
  169. .Size = File("size")
  170. .Hash = File("hashes")("sha1")
  171. End If
  172. '类别
  173. '结果可能混杂着 Mod、数据包和服务端插件
  174. Dim RawLoaders As List(Of String) = Data("loaders").Select(Function(v) v.ToString).ToList
  175. .ModLoaders = ModLoaders.None
  176. If .ResourceType.HasFlag(ResourceTypes.Mod) OrElse .ResourceType.HasFlag(ResourceTypes.DataPack) Then
  177. If RawLoaders.Intersect({"bukkit", "folia", "paper", "purpur", "spigot"}).Any() Then .ResourceType = ResourceTypes.Plugin 'Veinminer Enchantment 同时支持服务端与 Fabric
  178. If RawLoaders.Contains("datapack") Then .ResourceType = ResourceTypes.DataPack
  179. If EnumUtils.GetAllFlags(Of ModLoaders)().Any() Then .ResourceType = ResourceTypes.Mod
  180. Else
  181. '使用传入的类别,不作修改(#8377)
  182. End If
  183. 'Loaders
  184. For Each Loader As ModLoaders In EnumUtils.GetAllFlags(Of ModLoaders)()
  185. If RawLoaders.Contains(Loader.ToString.Lower) Then .ModLoaders = .ModLoaders Or Loader
  186. Next
  187. 'Dependencies
  188. If Data.ContainsKey("dependencies") Then
  189. .RawDependencies = Data("dependencies").
  190. Where(Function(d) d("dependency_type") = "required" AndAlso '种类为依赖
  191. d("project_id") <> "P7dR8mSH" AndAlso d("project_id") <> "qvIfYCYJ" AndAlso '排除 Fabric API 和 Quilt API
  192. d("project_id").ToString.Length > 0). '有时候真的会空……
  193. Select(Function(d) d("project_id").ToString).ToList
  194. End If
  195. 'GameVersions
  196. Dim RawVersions As List(Of String) = Data("game_versions").Select(Function(t) t.ToString.Trim.Lower).ToList
  197. .GameVersions = RawVersions.Where(Function(v) v.Contains(".")).
  198. Select(Function(v) If(v.Contains("-"), v.BeforeFirst("-") & " 预览版", If(v.StartsWithF("b1."), "远古版本", v))).
  199. Distinct.ToList
  200. If .GameVersions.IsSingle Then
  201. '无需处理
  202. ElseIf .GameVersions.Count > 1 Then
  203. .GameVersions = .GameVersions.SortByComparison(AddressOf CompareVersionGE).ToList
  204. If .ResourceType = ResourceTypes.ModPack Then .GameVersions = New List(Of String) From { .GameVersions(0)} '整合包理应只 “支持” 一个版本
  205. ElseIf RawVersions.Any(Function(v) v.RegexCheck("[0-9]{2}w[0-9]{2}[a-z]")) Then
  206. .GameVersions = RawVersions.Where(Function(v) v.RegexCheck("[0-9]{2}w[0-9]{2}[a-z]")).ToList
  207. Else
  208. .GameVersions = New List(Of String) From {"未知版本"}
  209. End If
  210. #End Region
  211. End If
  212. End With
  213. Return Result
  214. End Function
  215. ''' <summary>
  216. ''' 从项目 ID 获取其下的全部版本列表,并获取其前置项目信息。
  217. ''' </summary>
  218. Public Shared Function FromProjectId(ProjectId As String, Platform As ResourcePlatforms, Optional LoadDependencies As Boolean = True) As List(Of ResourceVersion)
  219. If Not Platform.Flags.IsSingle Then Throw New ArgumentException($"必须指定单一平台,当前值为 {Platform}")
  220. Dim TargetProject = ResourceProject.FromProjectId(ProjectId, Platform)
  221. '获取项目对象的文件列表
  222. If Not ProjectFilesCache.ContainsKey(ProjectId) Then
  223. Logger.Info($"开始获取项目版本列表:{ProjectId}")
  224. Dim ResultJsonArray As JArray = Nothing
  225. If Platform = ResourcePlatforms.CurseForge Then
  226. For RetryCount As Integer = 0 To 2
  227. Dim ResultJson As JObject = DlModRequest($"https://api.curseforge.com/v1/mods/{ProjectId}/files?pageSize=" &
  228. (10000 + RetryCount)) '每次重试多请求一个文件,以避免触发 CDN 缓存
  229. 'HMCL 一次性请求了 10000 个文件,虽然不知道会不会出问题但先这样吧……(#5522)
  230. '之前只请求一部分文件的方法备份如下:
  231. 'If TargetProject.Type = CompType.Mod Then 'Mod 使用每个版本最新的文件
  232. ' ResultJsonArray = DlModRequest("https://api.curseforge.com/v1/mods/files", HttpMethod.Post, "{""fileIds"": [" & Join(TargetProject.CurseForgeFileIds, ",") & "]}", "application/json")("data")
  233. 'Else '否则使用全部文件
  234. ' ResultJsonArray = DlModRequest($"https://api.curseforge.com/v1/mods/{ProjectId}/files?pageSize=999")("data")
  235. 'End If
  236. If ResultJson("pagination")("resultCount").ToObject(Of Integer) = ResultJson("pagination")("totalCount").ToObject(Of Integer) Then
  237. ResultJsonArray = ResultJson("data")
  238. Exit For
  239. ElseIf RetryCount < 2 Then
  240. Logger.Warn($"CurseForge 返回的资源版本列表存在缺失,即将进行第 {RetryCount + 1} 次重试") '#6224
  241. Logger.Info($"返回的原始内容如下:{vbCrLf}{ResultJson}")
  242. Else
  243. Logger.Info($"CurseForge 返回的资源版本列表存在缺失,返回的原始内容如下:{vbCrLf}{ResultJson}")
  244. Throw New Exception("CurseForge 返回的资源版本列表存在缺失")
  245. End If
  246. Next
  247. Else 'Modrinth
  248. ResultJsonArray = DlModRequest($"https://api.modrinth.com/v2/project/{ProjectId}/version?include_changelog=false")
  249. End If
  250. ProjectFilesCache(ProjectId) = ResultJsonArray.Select(Function(a) ResourceVersion.FromPlatformJson(a, TargetProject.Types)).
  251. Where(Function(a) a.DownloadAvailable).
  252. DistinctBy(Function(a) a.Id).ToList 'CurseForge 可能会重复返回相同项(#1330)
  253. End If
  254. Dim Result As List(Of ResourceVersion) = ProjectFilesCache(ProjectId)
  255. If Not LoadDependencies Then Return Result
  256. '加载前置信息
  257. Dim Dependencies As List(Of String) = Result.SelectMany(Function(f) f.RawDependencies).Distinct().ToList
  258. If Dependencies.Any Then
  259. Logger.Info($"项目 {ProjectId} 中需要加载的前置:{Dependencies.Join(",")}")
  260. For Each DependencyProject In ResourceProject.FromProjectIds(Dependencies, TargetProject.Platform)
  261. For Each File In Result
  262. If File.RawDependencies.Contains(DependencyProject.Id) AndAlso DependencyProject.Id <> ProjectId AndAlso Not File.Dependencies.Contains(DependencyProject.Id) Then
  263. File.Dependencies.Add(DependencyProject.Id)
  264. End If
  265. Next
  266. Next
  267. End If
  268. Return Result
  269. End Function
  270. ''' <summary>
  271. ''' 项目 ID 对应的版本信息的缓存。不会保存到文件。
  272. ''' </summary>
  273. Protected Friend Shared ProjectFilesCache As New Dictionary(Of String, List(Of ResourceVersion))
  274. '序列化
  275. ''' <summary>
  276. ''' 从缓存 JSON 读取字段数据。
  277. ''' </summary>
  278. Public Shared Function FromCacheJson(Data As JObject) As ResourceVersion
  279. Dim Result As New ResourceVersion
  280. With Result
  281. .ResourceType = Data("ResourceType").ToObject(Of ResourceTypes)
  282. .Id = Data("Id").ToString
  283. .Display = Data("DisplayName").ToString
  284. If Data.ContainsKey("Version") Then .Version = Data("Version").ToString
  285. .ReleaseDate = Data("ReleaseDate").ToObject(Of Date)
  286. .DownloadCount = Data("DownloadCount").ToObject(Of Integer)
  287. .ReleaseType = Data("ReleaseType").ToObject(Of ReleaseTypes)
  288. If Data.ContainsKey("FileName") Then .FileName = Data("FileName").ToString
  289. If Data.ContainsKey("DownloadUrls") Then .DownloadUrls = Data("DownloadUrls").ToObject(Of List(Of String))
  290. If Data.ContainsKey("ModLoaders") Then .ModLoaders = Data("ModLoaders").ToObject(Of ModLoaders)
  291. If Data.ContainsKey("Size") Then .Size = Data("Size").ToObject(Of Integer)
  292. If Data.ContainsKey("Hash") Then .Hash = Data("Hash").ToString
  293. If Data.ContainsKey("GameVersions") Then .GameVersions = Data("GameVersions").ToObject(Of List(Of String))
  294. If Data.ContainsKey("RawDependencies") Then .RawDependencies = Data("RawDependencies").ToObject(Of List(Of String))
  295. If Data.ContainsKey("Dependencies") Then .Dependencies = Data("Dependencies").ToObject(Of List(Of String))
  296. End With
  297. Return Result
  298. End Function
  299. ''' <summary>
  300. ''' 转为缓存 JSON。
  301. ''' </summary>
  302. Public Function ToCacheJson() As JObject
  303. Dim Result As New JObject
  304. With Result
  305. .Add("ResourceType", ResourceType)
  306. .Add("Id", Id)
  307. If Version IsNot Nothing Then .Add("Version", Version)
  308. .Add("DisplayName", Display)
  309. .Add("ReleaseDate", ReleaseDate)
  310. .Add("DownloadCount", DownloadCount)
  311. .Add("ModLoaders", ModLoaders)
  312. .Add("GameVersions", New JArray(GameVersions))
  313. .Add("ReleaseType", CInt(ReleaseType))
  314. If FileName IsNot Nothing Then .Add("FileName", FileName)
  315. If DownloadUrls IsNot Nothing Then .Add("DownloadUrls", New JArray(DownloadUrls))
  316. If Hash IsNot Nothing Then .Add("Hash", Hash)
  317. If Size >= 0 Then .Add("Size", Size)
  318. .Add("RawDependencies", New JArray(RawDependencies))
  319. .Add("Dependencies", New JArray(Dependencies))
  320. End With
  321. Return Result
  322. End Function
  323. '杂项
  324. ''' <summary>
  325. ''' 转为列表项。
  326. ''' </summary>
  327. Public Function ToListItem(ClickHandler As MyListItem.ClickEventHandler, Optional SaveAsButtonHandler As MyIconButton.ClickEventHandler = Nothing,
  328. Optional IsBadDisplay As Boolean = False) As MyVirtualizingElement(Of MyListItem)
  329. Return New MyVirtualizingElement(Of MyListItem)(
  330. Function()
  331. Dim Result As New MyListItem
  332. With Result
  333. '基础属性
  334. .SnapsToDevicePixels = True
  335. .Height = 42
  336. .Type = MyListItem.CheckType.Clickable
  337. .Tag = Me
  338. .Title = If(IsBadDisplay, FileName, Display)
  339. .Logo = $"{PathImage}ReleaseTypes/{ReleaseType}.png"
  340. AddHandler .Click, ClickHandler
  341. '描述
  342. .Info =
  343. Iterator Function()
  344. If ModLoaders <> ModLoaders.None Then Yield ModLoaders.Flags.Join("、"c)
  345. If .Title <> FileName.BeforeLast(".") Then Yield FileName.BeforeLast(".")
  346. If Dependencies.Any Then Yield $"{Dependencies.Count} 项前置"
  347. If GameVersions.All(Function(v) Not v.Contains(".") OrElse {"w", "snapshot", "rc", "pre", "experimental", "-"}.Any(Function(s) v.ContainsIgnoreCase(s))) Then Yield $"游戏版本 {GameVersions.Join("、"c)}"
  348. If DownloadCount > 0 Then Yield $"下载 {If(DownloadCount > 100000, Math.Round(DownloadCount / 10000) & " 万", DownloadCount & " ")}次" 'CurseForge 的下载次数经常错误地返回 0
  349. Yield $"更新于 {GetTimeSpanString(ReleaseDate - Date.Now, False)}"
  350. If ReleaseType <> ReleaseTypes.Release Then Yield ReleaseTypeDisplay
  351. End Function().Join(","c)
  352. '另存为按钮
  353. If SaveAsButtonHandler IsNot Nothing Then
  354. Dim BtnSave As New MyIconButton With {.Logo = Logo.IconButtonSave, .ToolTip = "另存为"}
  355. ToolTipService.SetPlacement(BtnSave, Primitives.PlacementMode.Center)
  356. ToolTipService.SetVerticalOffset(BtnSave, 30)
  357. ToolTipService.SetHorizontalOffset(BtnSave, 2)
  358. AddHandler BtnSave.Click, SaveAsButtonHandler
  359. .Buttons = {BtnSave}
  360. End If
  361. End With
  362. Return Result
  363. End Function) With {.Height = 42}
  364. End Function
  365. ''' <summary>
  366. ''' 获取下载信息。
  367. ''' </summary>
  368. ''' <param name="LocalFileOrFolder">目标本地文件夹,或完整的文件路径。会自动判断类型。</param>
  369. Public Function ToNetFile(LocalFileOrFolder As String,
  370. Optional TrackDownloadReason As DownloadReason? = Nothing, Optional TrackGameVersion As String = Nothing, Optional TrackLoader As ModLoaders = ModLoaders.None) As NetFile
  371. Return New NetFile(
  372. ParseModrinthTrackArguments(DownloadUrls, TrackDownloadReason, TrackGameVersion, TrackLoader),
  373. LocalFileOrFolder & If(LocalFileOrFolder.EndsWithF("\"), FileName, ""),
  374. New FileChecker(Hash:=Hash, ActualSize:=Size),
  375. SimulateBrowserHeaders:=True)
  376. End Function
  377. ''' <summary>
  378. ''' 添加 Modrinth 追踪参数。
  379. ''' </summary>
  380. Public Shared Iterator Function ParseModrinthTrackArguments(Urls As List(Of String), Optional TrackDownloadReason As DownloadReason? = Nothing, Optional TrackGameVersion As String = Nothing, Optional TrackLoader As ModLoaders = ModLoaders.None) As IEnumerable(Of String)
  381. Dim Arguments As New Dictionary(Of String, String)
  382. If TrackDownloadReason IsNot Nothing Then Arguments.Add("mr_download_reason", TrackDownloadReason.ToString.Lower)
  383. If TrackGameVersion IsNot Nothing Then Arguments.Add("mr_game_version", TrackGameVersion)
  384. If TrackLoader.Flags.Any Then Arguments.Add("mr_loader", TrackLoader.Flags.First.ToString.Lower)
  385. Dim Argument = Arguments.Select(Function(a) $"{a.Key}={a.Value}").Join("&")
  386. For Each Url In Urls
  387. If Not Url.ContainsIgnoreCase("cdn.modrinth.com/") Then Yield Url
  388. Yield $"{Url}{If(Url.Contains("?"), "&", "?")}{Argument}"
  389. Next
  390. End Function
  391. Public Enum DownloadReason
  392. Standalone
  393. Dependency
  394. ModPack
  395. Update
  396. End Enum
  397. ''' <summary>
  398. ''' 重新整理 CurseForge 的下载地址。
  399. ''' </summary>
  400. Public Shared Function ParseCurseForgeDownloadUrls(Url As String) As List(Of String)
  401. Return {
  402. Url.Replace("-service.overwolf.wtf", ".forgecdn.net").Replace("://edge.", "://mediafilez.").Replace("://media.", "://mediafilez."),
  403. Url.Replace("://edge.", "://mediafilez.").Replace("://media.", "://mediafilez."),
  404. Url.Replace("-service.overwolf.wtf", ".forgecdn.net"),
  405. Url.Replace("://media.", "://edge."),
  406. Url
  407. }.Distinct.ToList
  408. End Function
  409. Public Overrides Function ToString() As String
  410. Return $"{Id}: {FileName}"
  411. End Function
  412. End Class