428 lines
22 KiB
VB.net
428 lines
22 KiB
VB.net
''' <summary>
|
||
''' 社区资源的单个可下载版本。
|
||
''' </summary>
|
||
Public Class ResourceVersion
|
||
|
||
'基础字段
|
||
|
||
''' <summary>
|
||
''' 用于唯一性鉴别该版本的 ID。CurseForge 中为 123456 的大整数,Modrinth 中为英文乱码的 Version 字段。
|
||
''' </summary>
|
||
Public Id As String
|
||
''' <summary>
|
||
''' 该版本所对应的单一资源类别。
|
||
''' </summary>
|
||
Public ResourceType As ResourceTypes
|
||
|
||
'描述性字段
|
||
|
||
''' <summary>
|
||
''' 版本描述名。
|
||
''' 并非文件名或版本号,是由上传者完全自定义的字段。
|
||
''' </summary>
|
||
Public Display As String
|
||
''' <summary>
|
||
''' 发布时间。
|
||
''' </summary>
|
||
Public ReleaseDate As Date
|
||
''' <summary>
|
||
''' 下载量计数。
|
||
''' 注意,仅为一个平台的计数,无法反映多平台加起来的下载量,且 CurseForge 可能错误地返回 0。
|
||
''' </summary>
|
||
Public DownloadCount As Integer
|
||
''' <summary>
|
||
''' 版本号。
|
||
''' 不一定是标准格式。CurseForge 默认为 Nothing。
|
||
''' </summary>
|
||
Public Version As String
|
||
''' <summary>
|
||
''' 所有支持的 Mod 加载器列表。可能为 <see cref="ModLoaders.None"/>。
|
||
''' </summary>
|
||
Public ModLoaders As ModLoaders = ModLoaders.None
|
||
''' <summary>
|
||
''' 支持的游戏版本列表。类型包括:"26.1.5","26.1","26.1 预览版","1.18.5","1.18","1.18 预览版","21w15a","未知版本"。
|
||
''' </summary>
|
||
Public GameVersions As List(Of String)
|
||
|
||
''' <summary>
|
||
''' 发布状态。
|
||
''' </summary>
|
||
Public ReleaseType As ReleaseTypes
|
||
''' <summary>
|
||
''' 发布状态的友好描述。
|
||
''' 例如:"正式版","Beta 版"。
|
||
''' </summary>
|
||
Public ReadOnly Property ReleaseTypeDisplay As String
|
||
Get
|
||
Select Case ReleaseType
|
||
Case ReleaseTypes.Release
|
||
Return "正式版"
|
||
Case ReleaseTypes.Beta
|
||
Return If(ModeDebug, "Beta 版", "测试版")
|
||
Case Else
|
||
Return If(ModeDebug, "Alpha 版", "早期测试版")
|
||
End Select
|
||
End Get
|
||
End Property
|
||
Public Enum ReleaseTypes
|
||
'https://docs.curseforge.com/rest-api/#tocS_FileReleaseType
|
||
Release = 1
|
||
Beta = 2
|
||
Alpha = 3
|
||
End Enum
|
||
|
||
'下载信息
|
||
|
||
''' <summary>
|
||
''' 下载信息是否可用。
|
||
''' </summary>
|
||
Public ReadOnly Property DownloadAvailable As Boolean
|
||
Get
|
||
Return FileName IsNot Nothing AndAlso DownloadUrls IsNot Nothing
|
||
End Get
|
||
End Property
|
||
''' <summary>
|
||
''' 文件名。
|
||
''' </summary>
|
||
Public FileName As String = Nothing
|
||
''' <summary>
|
||
''' 对应文件的所有可能的下载 URL。
|
||
''' </summary>
|
||
Public DownloadUrls As List(Of String)
|
||
''' <summary>
|
||
''' 对应文件的 SHA1 或 MD5。
|
||
''' </summary>
|
||
Public Hash As String = Nothing
|
||
''' <summary>
|
||
''' 文件大小。不可用时为 -1。
|
||
''' </summary>
|
||
Public Size As Integer = -1
|
||
''' <summary>
|
||
''' 作为该版本依赖项的项目 ID。
|
||
''' 它们可能没有加载,在加载后会添加到 Dependencies 中(主要是因为 Modrinth 返回的是字符串 ID 而非 Slug,导致 Project.Id 查询不到)。
|
||
''' </summary>
|
||
Private RawDependencies As New List(Of String)
|
||
''' <summary>
|
||
''' 作为该版本依赖项的项目 ID。
|
||
''' </summary>
|
||
Public Dependencies As New List(Of String)
|
||
|
||
'初始化
|
||
|
||
''' <summary>
|
||
''' 从平台 API 返回的 JSON 中初始化实例。若出错会抛出异常。
|
||
''' </summary>
|
||
Public Shared Function FromPlatformJson(Data As JObject, ExpectedResourceType As ResourceTypes) As ResourceVersion
|
||
Dim Result As New ResourceVersion
|
||
With Result
|
||
.ResourceType = ExpectedResourceType
|
||
If Data.ContainsKey("gameId") Then
|
||
#Region "CurseForge"
|
||
'简单信息
|
||
.Id = Data("id")
|
||
.Display = Data("displayName").ToString.Replace(" ", "").Trim(" ")
|
||
.Version = Nothing
|
||
.ReleaseDate = Data("fileDate")
|
||
.ReleaseType = CType(Data("releaseType").ToObject(Of Integer), ReleaseTypes)
|
||
.DownloadCount = Data("downloadCount")
|
||
.FileName = Data("fileName")
|
||
.Size = Data("fileLength")
|
||
.Hash = CType(Data("hashes"), JArray).ToList.FirstOrDefault(Function(s) s("algo").ToObject(Of Integer) = 1)?("value")
|
||
If .Hash Is Nothing Then .Hash = CType(Data("hashes"), JArray).ToList.FirstOrDefault(Function(s) s("algo").ToObject(Of Integer) = 2)?("value")
|
||
'DownloadAddress
|
||
Dim Url = Data("downloadUrl").ToString
|
||
If Url = "" Then Url = $"https://edge.forgecdn.net/files/{CInt(.Id.ToString.Substring(0, 4))}/{CInt(.Id.ToString.Substring(4))}/{ .FileName}"
|
||
.DownloadUrls = ParseCurseForgeDownloadUrls(Url.Replace(.FileName, EscapeUtils.UrlEscape(.FileName))) '对脑残 CurseForge 的下载地址进行多种修正
|
||
.DownloadUrls.Add(DlSourceModGet(Url)) '添加镜像源;注意 MCIM 源不支持 URL 编码后的文件名,必须传入 URL 编码前的文件名
|
||
.DownloadUrls = .DownloadUrls.Distinct.ToList '最终去重
|
||
'Dependencies
|
||
If Data.ContainsKey("dependencies") Then
|
||
.RawDependencies = Data("dependencies").
|
||
Where(Function(d) d("relationType").ToObject(Of Integer) = 3 AndAlso '种类为依赖
|
||
d("modId").ToObject(Of Integer) <> 306612 AndAlso d("modId").ToObject(Of Integer) <> 634179). '排除 Fabric API 和 Quilt API
|
||
Select(Function(d) d("modId").ToString).ToList
|
||
End If
|
||
'GameVersions
|
||
Dim RawVersions As List(Of String) = Data("gameVersions").Select(Function(t) t.ToString.Trim.Lower).ToList
|
||
.GameVersions = RawVersions.
|
||
Where(Function(v) McVersion.IsFormatFit(v)).
|
||
Select(Function(v) v.Replace("-snapshot", " 预览版")).
|
||
Distinct.ToList
|
||
If .GameVersions.IsSingle Then
|
||
.GameVersions = .GameVersions.ToList
|
||
ElseIf .GameVersions.Count > 1 Then
|
||
.GameVersions = .GameVersions.SortByComparison(AddressOf CompareVersionGE).ToList
|
||
If .ResourceType = ResourceTypes.ModPack Then .GameVersions = { .GameVersions(0)}.ToList '整合包理应只 “支持” 一个版本
|
||
Else
|
||
.GameVersions = New List(Of String) From {"未知版本"}
|
||
End If
|
||
'ModLoaders
|
||
.ModLoaders = ModLoaders.None
|
||
For Each Loader In EnumUtils.GetAllFlags(Of ModLoaders)()
|
||
If RawVersions.Contains(Loader.ToString.Lower) Then .ModLoaders = .ModLoaders Or Loader
|
||
Next
|
||
#End Region
|
||
Else
|
||
#Region "Modrinth"
|
||
'简单信息
|
||
.Id = Data("id")
|
||
.Display = Data("name").ToString.Replace(" ", "").Trim(" ")
|
||
.Version = Data("version_number")
|
||
.ReleaseDate = Data("date_published")
|
||
.ReleaseType = If(Data("version_type").ToString = "release", ReleaseTypes.Release, If(Data("version_type").ToString = "beta", ReleaseTypes.Beta, ReleaseTypes.Alpha))
|
||
.DownloadCount = Data("downloads")
|
||
If CType(Data("files"), JArray).Any() Then '可能为空
|
||
Dim File As JToken = Data("files")(0)
|
||
.FileName = File("filename")
|
||
.DownloadUrls = New List(Of String) From {File("url"), DlSourceModGet(File("url"))}.Distinct.ToList '同时添加了镜像源
|
||
.Size = File("size")
|
||
.Hash = File("hashes")("sha1")
|
||
End If
|
||
'Loaders
|
||
'结果可能混杂着 Mod、数据包和服务端插件
|
||
Dim RawLoaders As List(Of String) = Data("loaders").Select(Function(v) v.ToString).ToList
|
||
.ModLoaders = ModLoaders.None
|
||
If .ResourceType.HasFlag(ResourceTypes.Mod) OrElse .ResourceType.HasFlag(ResourceTypes.DataPack) Then
|
||
If RawLoaders.Intersect({"bukkit", "folia", "paper", "purpur", "spigot"}).Any() Then .ResourceType = ResourceTypes.Plugin 'Veinminer Enchantment 同时支持服务端与 Fabric
|
||
If RawLoaders.Contains("datapack") Then .ResourceType = ResourceTypes.DataPack
|
||
For Each Loader As ModLoaders In EnumUtils.GetAllFlags(Of ModLoaders)()
|
||
If RawLoaders.Contains(Loader.ToString.Lower) Then .ModLoaders = .ModLoaders Or Loader : .ResourceType = ResourceTypes.Mod
|
||
Next
|
||
Else
|
||
'使用传入的类别,不作修改(#8377)
|
||
End If
|
||
'Dependencies
|
||
If Data.ContainsKey("dependencies") Then
|
||
.RawDependencies = Data("dependencies").
|
||
Where(Function(d) d("dependency_type") = "required" AndAlso '种类为依赖
|
||
d("project_id") <> "P7dR8mSH" AndAlso d("project_id") <> "qvIfYCYJ" AndAlso '排除 Fabric API 和 Quilt API
|
||
d("project_id").ToString.Length > 0). '有时候真的会空……
|
||
Select(Function(d) d("project_id").ToString).ToList
|
||
End If
|
||
'GameVersions
|
||
Dim RawVersions As List(Of String) = Data("game_versions").Select(Function(t) t.ToString.Trim.Lower).ToList
|
||
.GameVersions = RawVersions.Where(Function(v) v.Contains(".")).
|
||
Select(Function(v) If(v.Contains("-"), v.BeforeFirst("-") & " 预览版", If(v.StartsWithF("b1."), "远古版本", v))).
|
||
Distinct.ToList
|
||
If .GameVersions.IsSingle Then
|
||
'无需处理
|
||
ElseIf .GameVersions.Count > 1 Then
|
||
.GameVersions = .GameVersions.SortByComparison(AddressOf CompareVersionGE).ToList
|
||
If .ResourceType = ResourceTypes.ModPack Then .GameVersions = New List(Of String) From { .GameVersions(0)} '整合包理应只 “支持” 一个版本
|
||
ElseIf RawVersions.Any(Function(v) v.RegexCheck("[0-9]{2}w[0-9]{2}[a-z]")) Then
|
||
.GameVersions = RawVersions.Where(Function(v) v.RegexCheck("[0-9]{2}w[0-9]{2}[a-z]")).ToList
|
||
Else
|
||
.GameVersions = New List(Of String) From {"未知版本"}
|
||
End If
|
||
#End Region
|
||
End If
|
||
End With
|
||
Return Result
|
||
End Function
|
||
''' <summary>
|
||
''' 从项目 ID 获取其下的全部版本列表,并获取其前置项目信息。
|
||
''' </summary>
|
||
Public Shared Function FromProjectId(ProjectId As String, Platform As ResourcePlatforms, Optional LoadDependencies As Boolean = True) As List(Of ResourceVersion)
|
||
If Not Platform.Flags.IsSingle Then Throw New ArgumentException($"必须指定单一平台,当前值为 {Platform}")
|
||
Dim TargetProject = ResourceProject.FromProjectId(ProjectId, Platform)
|
||
'获取项目对象的文件列表
|
||
If Not ProjectFilesCache.ContainsKey(ProjectId) Then
|
||
Logger.Info($"开始获取项目版本列表:{ProjectId}")
|
||
Dim ResultJsonArray As JArray = Nothing
|
||
If Platform = ResourcePlatforms.CurseForge Then
|
||
For RetryCount As Integer = 0 To 2
|
||
Dim ResultJson As JObject = DlModRequest($"https://api.curseforge.com/v1/mods/{ProjectId}/files?pageSize=" &
|
||
(10000 + RetryCount)) '每次重试多请求一个文件,以避免触发 CDN 缓存
|
||
'HMCL 一次性请求了 10000 个文件,虽然不知道会不会出问题但先这样吧……(#5522)
|
||
'之前只请求一部分文件的方法备份如下:
|
||
'If TargetProject.Type = CompType.Mod Then 'Mod 使用每个版本最新的文件
|
||
' ResultJsonArray = DlModRequest("https://api.curseforge.com/v1/mods/files", HttpMethod.Post, "{""fileIds"": [" & Join(TargetProject.CurseForgeFileIds, ",") & "]}", "application/json")("data")
|
||
'Else '否则使用全部文件
|
||
' ResultJsonArray = DlModRequest($"https://api.curseforge.com/v1/mods/{ProjectId}/files?pageSize=999")("data")
|
||
'End If
|
||
If ResultJson("pagination")("resultCount").ToObject(Of Integer) = ResultJson("pagination")("totalCount").ToObject(Of Integer) Then
|
||
ResultJsonArray = ResultJson("data")
|
||
Exit For
|
||
ElseIf RetryCount < 2 Then
|
||
Logger.Warn($"CurseForge 返回的资源版本列表存在缺失,即将进行第 {RetryCount + 1} 次重试") '#6224
|
||
Logger.Info($"返回的原始内容如下:{vbCrLf}{ResultJson}")
|
||
Else
|
||
Logger.Info($"CurseForge 返回的资源版本列表存在缺失,返回的原始内容如下:{vbCrLf}{ResultJson}")
|
||
Throw New Exception("CurseForge 返回的资源版本列表存在缺失")
|
||
End If
|
||
Next
|
||
Else 'Modrinth
|
||
ResultJsonArray = DlModRequest($"https://api.modrinth.com/v2/project/{ProjectId}/version?include_changelog=false")
|
||
End If
|
||
ProjectFilesCache(ProjectId) = ResultJsonArray.Select(Function(a) ResourceVersion.FromPlatformJson(a, TargetProject.Types)).
|
||
Where(Function(a) a.DownloadAvailable).
|
||
DistinctBy(Function(a) a.Id).ToList 'CurseForge 可能会重复返回相同项(#1330)
|
||
End If
|
||
Dim Result As List(Of ResourceVersion) = ProjectFilesCache(ProjectId)
|
||
If Not LoadDependencies Then Return Result
|
||
'加载前置信息
|
||
Dim Dependencies As List(Of String) = Result.SelectMany(Function(f) f.RawDependencies).Distinct().ToList
|
||
If Dependencies.Any Then
|
||
Logger.Info($"项目 {ProjectId} 中需要加载的前置:{Dependencies.Join(",")}")
|
||
For Each DependencyProject In ResourceProject.FromProjectIds(Dependencies, TargetProject.Platform)
|
||
For Each File In Result
|
||
If File.RawDependencies.Contains(DependencyProject.Id) AndAlso DependencyProject.Id <> ProjectId AndAlso Not File.Dependencies.Contains(DependencyProject.Id) Then
|
||
File.Dependencies.Add(DependencyProject.Id)
|
||
End If
|
||
Next
|
||
Next
|
||
End If
|
||
Return Result
|
||
End Function
|
||
''' <summary>
|
||
''' 项目 ID 对应的版本信息的缓存。不会保存到文件。
|
||
''' </summary>
|
||
Protected Friend Shared ProjectFilesCache As New Dictionary(Of String, List(Of ResourceVersion))
|
||
|
||
'序列化
|
||
|
||
''' <summary>
|
||
''' 从缓存 JSON 读取字段数据。
|
||
''' </summary>
|
||
Public Shared Function FromCacheJson(Data As JObject) As ResourceVersion
|
||
Dim Result As New ResourceVersion
|
||
With Result
|
||
.ResourceType = Data("ResourceType").ToObject(Of ResourceTypes)
|
||
.Id = Data("Id").ToString
|
||
.Display = Data("DisplayName").ToString
|
||
If Data.ContainsKey("Version") Then .Version = Data("Version").ToString
|
||
.ReleaseDate = Data("ReleaseDate").ToObject(Of Date)
|
||
.DownloadCount = Data("DownloadCount").ToObject(Of Integer)
|
||
.ReleaseType = Data("ReleaseType").ToObject(Of ReleaseTypes)
|
||
If Data.ContainsKey("FileName") Then .FileName = Data("FileName").ToString
|
||
If Data.ContainsKey("DownloadUrls") Then .DownloadUrls = Data("DownloadUrls").ToObject(Of List(Of String))
|
||
If Data.ContainsKey("ModLoaders") Then .ModLoaders = Data("ModLoaders").ToObject(Of ModLoaders)
|
||
If Data.ContainsKey("Size") Then .Size = Data("Size").ToObject(Of Integer)
|
||
If Data.ContainsKey("Hash") Then .Hash = Data("Hash").ToString
|
||
If Data.ContainsKey("GameVersions") Then .GameVersions = Data("GameVersions").ToObject(Of List(Of String))
|
||
If Data.ContainsKey("RawDependencies") Then .RawDependencies = Data("RawDependencies").ToObject(Of List(Of String))
|
||
If Data.ContainsKey("Dependencies") Then .Dependencies = Data("Dependencies").ToObject(Of List(Of String))
|
||
End With
|
||
Return Result
|
||
End Function
|
||
''' <summary>
|
||
''' 转为缓存 JSON。
|
||
''' </summary>
|
||
Public Function ToCacheJson() As JObject
|
||
Dim Result As New JObject
|
||
With Result
|
||
.Add("ResourceType", ResourceType)
|
||
.Add("Id", Id)
|
||
If Version IsNot Nothing Then .Add("Version", Version)
|
||
.Add("DisplayName", Display)
|
||
.Add("ReleaseDate", ReleaseDate)
|
||
.Add("DownloadCount", DownloadCount)
|
||
.Add("ModLoaders", ModLoaders)
|
||
.Add("GameVersions", New JArray(GameVersions))
|
||
.Add("ReleaseType", CInt(ReleaseType))
|
||
If FileName IsNot Nothing Then .Add("FileName", FileName)
|
||
If DownloadUrls IsNot Nothing Then .Add("DownloadUrls", New JArray(DownloadUrls))
|
||
If Hash IsNot Nothing Then .Add("Hash", Hash)
|
||
If Size >= 0 Then .Add("Size", Size)
|
||
.Add("RawDependencies", New JArray(RawDependencies))
|
||
.Add("Dependencies", New JArray(Dependencies))
|
||
End With
|
||
Return Result
|
||
End Function
|
||
|
||
'杂项
|
||
|
||
''' <summary>
|
||
''' 转为列表项。
|
||
''' </summary>
|
||
Public Function ToListItem(ClickHandler As MyListItem.ClickEventHandler, Optional SaveAsButtonHandler As MyIconButton.ClickEventHandler = Nothing,
|
||
Optional IsBadDisplay As Boolean = False) As MyVirtualizingElement(Of MyListItem)
|
||
Return New MyVirtualizingElement(Of MyListItem)(
|
||
Function()
|
||
Dim Result As New MyListItem
|
||
With Result
|
||
|
||
'基础属性
|
||
.SnapsToDevicePixels = True
|
||
.Height = 42
|
||
.Type = MyListItem.CheckType.Clickable
|
||
.Tag = Me
|
||
.Title = If(IsBadDisplay, FileName, Display)
|
||
.Logo = $"{PathImage}ReleaseTypes/{ReleaseType}.png"
|
||
AddHandler .Click, ClickHandler
|
||
|
||
'描述
|
||
.Info =
|
||
Iterator Function()
|
||
If .Title <> FileName.BeforeLast(".") Then Yield FileName.BeforeLast(".")
|
||
If Dependencies.Any Then Yield $"{Dependencies.Count} 项前置"
|
||
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)}"
|
||
If DownloadCount > 0 Then Yield $"下载 {If(DownloadCount > 100000, Math.Round(DownloadCount / 10000) & " 万", DownloadCount)} 次" 'CurseForge 的下载次数经常错误地返回 0
|
||
Yield $"更新于 {GetTimeSpanString(ReleaseDate - Date.Now, False)}"
|
||
If ReleaseType <> ReleaseTypes.Release Then Yield ReleaseTypeDisplay
|
||
End Function().Join(","c)
|
||
|
||
'另存为按钮
|
||
If SaveAsButtonHandler IsNot Nothing Then
|
||
Dim BtnSave As New MyIconButton With {.Logo = Logo.IconButtonSave, .ToolTip = "另存为"}
|
||
ToolTipService.SetPlacement(BtnSave, Primitives.PlacementMode.Center)
|
||
ToolTipService.SetVerticalOffset(BtnSave, 30)
|
||
ToolTipService.SetHorizontalOffset(BtnSave, 2)
|
||
AddHandler BtnSave.Click, SaveAsButtonHandler
|
||
.Buttons = {BtnSave}
|
||
End If
|
||
|
||
End With
|
||
Return Result
|
||
End Function) With {.Height = 42}
|
||
End Function
|
||
''' <summary>
|
||
''' 获取下载信息。
|
||
''' </summary>
|
||
''' <param name="LocalFileOrFolder">目标本地文件夹,或完整的文件路径。会自动判断类型。</param>
|
||
Public Function ToNetFile(LocalFileOrFolder As String,
|
||
Optional TrackDownloadReason As DownloadReason? = Nothing, Optional TrackGameVersion As String = Nothing, Optional TrackLoader As ModLoaders = ModLoaders.None) As NetFile
|
||
Return New NetFile(
|
||
ParseModrinthTrackArguments(DownloadUrls, TrackDownloadReason, TrackGameVersion, TrackLoader),
|
||
LocalFileOrFolder & If(LocalFileOrFolder.EndsWithF("\"), FileName, ""),
|
||
New FileChecker(Hash:=Hash, ActualSize:=Size),
|
||
SimulateBrowserHeaders:=True)
|
||
End Function
|
||
''' <summary>
|
||
''' 添加 Modrinth 追踪参数。
|
||
''' </summary>
|
||
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)
|
||
Dim Arguments As New Dictionary(Of String, String)
|
||
If TrackDownloadReason IsNot Nothing Then Arguments.Add("mr_download_reason", TrackDownloadReason.ToString.Lower)
|
||
If TrackGameVersion IsNot Nothing Then Arguments.Add("mr_game_version", TrackGameVersion)
|
||
If TrackLoader.Flags.Any Then Arguments.Add("mr_loader", TrackLoader.Flags.First.ToString.Lower)
|
||
Dim Argument = Arguments.Select(Function(a) $"{a.Key}={a.Value}").Join("&")
|
||
For Each Url In Urls
|
||
If Not Url.ContainsIgnoreCase("cdn.modrinth.com/") Then Yield Url
|
||
Yield $"{Url}{If(Url.Contains("?"), "&", "?")}{Argument}"
|
||
Next
|
||
End Function
|
||
Public Enum DownloadReason
|
||
Standalone
|
||
Dependency
|
||
ModPack
|
||
Update
|
||
End Enum
|
||
''' <summary>
|
||
''' 重新整理 CurseForge 的下载地址。
|
||
''' </summary>
|
||
Public Shared Function ParseCurseForgeDownloadUrls(Url As String) As List(Of String)
|
||
Return {
|
||
Url.Replace("-service.overwolf.wtf", ".forgecdn.net").Replace("://edge.", "://mediafilez.").Replace("://media.", "://mediafilez."),
|
||
Url.Replace("://edge.", "://mediafilez.").Replace("://media.", "://mediafilez."),
|
||
Url.Replace("-service.overwolf.wtf", ".forgecdn.net"),
|
||
Url.Replace("://media.", "://edge."),
|
||
Url
|
||
}.Distinct.ToList
|
||
End Function
|
||
Public Overrides Function ToString() As String
|
||
Return $"{Id}: {FileName}"
|
||
End Function
|
||
|
||
End Class
|