This commit is contained in:
龙腾猫跃
2023-02-16 22:21:04 +08:00
parent a9bf0b682a
commit 6dc8789ac0
32 changed files with 3605 additions and 2453 deletions
@@ -335,7 +335,7 @@
''' </summary>
Public ReleaseTime As String
''' <summary>
''' 需要的最低 Forge 版本。空字符串为无限制,“28.1.56” 表示版本号,“1161” 表示版本号的最后一位。
''' 需要的最低 Forge 版本。空字符串为无限制,Nothing 为不兼容,“28.1.56” 表示版本号,“1161” 表示版本号的最后一位。
''' </summary>
Public RequiredForgeVersion As String
End Class
@@ -389,6 +389,7 @@
.Inherit = Name(i).ToString.Split(" ")(0),
.NameFile = If(Name(i).ToLower.Contains("pre"), "preview_", "") & "OptiFine_" & Name(i).Replace(" ", "_") & ".jar",
.RequiredForgeVersion = Forge(i).Replace("Forge ", "").Replace("#", "")}
If Entry.RequiredForgeVersion.Contains("N/A") Then Entry.RequiredForgeVersion = Nothing
Entry.NameVersion = Entry.Inherit & "-OptiFine_" & Name(i).ToString.Replace(" ", "_").Replace(Entry.Inherit & "_", "")
Versions.Add(Entry)
Next
@@ -413,6 +414,7 @@
.NameFile = Token("filename").ToString,
.RequiredForgeVersion = If(Token("forge"), "").ToString.Replace("Forge ", "").Replace("#", "")
}
If Entry.RequiredForgeVersion.Contains("N/A") Then Entry.RequiredForgeVersion = Nothing
Entry.NameVersion = Entry.Inherit & "-OptiFine_" & (Token("type").ToString & " " & Token("patch").ToString).Replace(".0 ", " ").Replace(" ", "_").Replace(Entry.Inherit & "_", "")
Versions.Add(Entry)
Next
@@ -944,719 +946,14 @@
''' <summary>
''' Fabric API 列表,官方源。
''' </summary>
Public DlFabricApiLoader As New LoaderTask(Of Integer, List(Of DlCfFile))("Fabric API List Loader",
Sub(Task As LoaderTask(Of Integer, List(Of DlCfFile))) Task.Output = DlCfGetFiles(306612, False))
Public DlFabricApiLoader As New LoaderTask(Of Integer, List(Of CompFile))("Fabric API List Loader",
Sub(Task As LoaderTask(Of Integer, List(Of CompFile))) Task.Output = CompFilesGet("fabric-api", False))
''' <summary>
''' OptiFabric 列表,官方源。
''' </summary>
Public DlOptiFabricLoader As New LoaderTask(Of Integer, List(Of DlCfFile))("OptiFabric List Loader",
Sub(Task As LoaderTask(Of Integer, List(Of DlCfFile))) Task.Output = DlCfGetFiles(322385, False))
#End Region
#Region "DlCfProject | CurseForge 工程"
Private DlCfProjectDb As Dictionary(Of String, DlCfDbEntry) = Nothing
Private DlCfProjectCache As New Dictionary(Of Integer, DlCfProject)
''' <summary>
''' CurseForge 工程列表获取、搜索请求。
''' </summary>
Public Class DlCfProjectRequest
Public CategoryId As Integer = 0 '421: Mod(?), 4780: Fabric
Public IsModPack As Boolean = False
Public GameVersion As String = Nothing
Public PageSize As Integer = 50
Public SearchFilter As String = Nothing
Public Index As Integer? = Nothing
Public ModLoader As Integer? = Nothing
Public Function GetAddress() As String
GetAddress = "https://api.curseforge.com/v1/mods/search?gameId=432&sortField=Featured&sortOrder=desc" &
"&pageSize=" & PageSize &
"&categoryId=" & CategoryId &
If(IsModPack, "&classId=4471", "&classId=6") &
If(String.IsNullOrEmpty(GameVersion), "", "&gameVersion=" & GameVersion) &
If(String.IsNullOrEmpty(SearchFilter), "", "&searchFilter=" & Net.WebUtility.UrlEncode(SearchFilter)) &
If(Index IsNot Nothing, "&index=" & Index, "") &
If(ModLoader IsNot Nothing AndAlso ModLoader > 0, "&modLoaderType=" & ModLoader, "")
End Function
#Region "相同判断"
Public Overrides Function Equals(obj As Object) As Boolean
Dim request = TryCast(obj, DlCfProjectRequest)
Return request IsNot Nothing AndAlso request.GetAddress = GetAddress()
End Function
Public Shared Operator =(left As DlCfProjectRequest, right As DlCfProjectRequest) As Boolean
Return EqualityComparer(Of DlCfProjectRequest).Default.Equals(left, right)
End Operator
Public Shared Operator <>(left As DlCfProjectRequest, right As DlCfProjectRequest) As Boolean
Return Not left = right
End Operator
#End Region
End Class
''' <summary>
''' CurseForge 工程信息。
''' </summary>
Public Class DlCfProject
Public Id As Integer
Public Name As String
Public Description As String
Public Website As String
Public DateUpdate As Date
Public DownloadCount As Integer
Public ModLoaders As New List(Of String)
Public GameVersionDesc As String
Private CategoryDesc As List(Of String)
Private Thumb As String
Public IsModPack As Boolean
Public FileIndexes As New List(Of Integer)
Public Files As List(Of DlCfFile)
Private _MCBBS As Integer?
Public Property MCBBS As Integer?
Get
ReleaseCfDatabase()
If _MCBBS Is Nothing Then
If Website IsNot Nothing Then
Dim Keyword As String = Website.TrimEnd("/").Split("/").Last
If DlCfProjectDb.ContainsKey(Keyword) Then _MCBBS = DlCfProjectDb(Keyword).MCBBS
End If
End If
Return _MCBBS
End Get
Set(value As Integer?)
_MCBBS = value
End Set
End Property
Private _ChineseName As String
Public Property ChineseName As String
Get
ReleaseCfDatabase()
If _ChineseName Is Nothing Then
_ChineseName = Name
If Website IsNot Nothing Then
Dim Keyword As String = Website.TrimEnd("/").Split("/").Last
If DlCfProjectDb.ContainsKey(Keyword) Then
Dim Result = DlCfProjectDb(Keyword)
If Not String.IsNullOrEmpty(Result.ChineseName) Then '使用原名
_ChineseName = Result.ChineseName
End If
End If
End If
End If
Return _ChineseName
End Get
Set(value As String)
_ChineseName = value
End Set
End Property
Private _WikiId As Integer = -1
''' <summary>
''' MCMOD 的页面 ID。若为 0 则代表没有对应页面。
''' </summary>
Public Property WikiId As Integer
Get
ReleaseCfDatabase()
If _WikiId = -1 Then
If Website IsNot Nothing Then
Dim Keyword As String = Website.TrimEnd("/").Split("/").Last
If DlCfProjectDb.ContainsKey(Keyword) Then
_WikiId = DlCfProjectDb(Keyword).WikiId
Else
_WikiId = 0
End If
End If
End If
Return _WikiId
End Get
Set(value As Integer)
_WikiId = value
End Set
End Property
''' <summary>
''' 从 CurseForge 工程 Json 项中初始化实例。若出错会抛出异常。
''' </summary>
Public Sub New(Data As JObject)
'https://api.curseforge.com/v1/mods/search?gameId=432&pageSize=5&categoryID=421
Id = Data("id")
Name = Data("name")
Description = Data("summary")
Website = Data("links")("websiteUrl")
DateUpdate = Data("dateModified")
DownloadCount = Data("downloadCount")
IsModPack = Not Website.Contains("/mc-mods/")
'获取 Mod Loader 列表
ModLoaders = New List(Of String)
For Each File In If(Data("latestFiles"), {})
Dim NewFile As New DlCfFile(File, IsModPack)
If Not NewFile.IsAvailable Then Continue For
ModLoaders.AddRange(NewFile.ModLoaders)
Next
ModLoaders = ModLoaders.Distinct.ToList
'用一个偷懒的方式让 Forge 排在 Fabric 的前面
ModLoaders = ModLoaders.Select(Function(s) s.Replace("Forge", "Aorge")).ToList()
ModLoaders.Sort()
ModLoaders = ModLoaders.Select(Function(s) s.Replace("Aorge", "Forge")).ToList()
'获取游戏版本
Dim GameVersions As New List(Of Integer)
For Each File In If(Data("latestFilesIndexes"), {})
Dim Version As String = File("gameVersion")
If Not Version.Contains("1.") Then Continue For
GameVersions.Add(Version.Split(".")(1).Split("-").First)
FileIndexes.Add(File("fileId"))
Next
GameVersions = Sort(GameVersions.Distinct.ToList, AddressOf VersionSortBoolean)
If GameVersions.Count = 0 Then
GameVersionDesc = ""
Else
Dim SpaVersions As New List(Of String)
For i = 0 To GameVersions.Count - 1
Dim StartVersion As Integer = GameVersions(i), EndVersion As Integer = GameVersions(i)
For ii = i + 1 To GameVersions.Count - 1
If GameVersions(ii) = EndVersion - 1 Then
EndVersion = GameVersions(ii)
i = ii
Else
Exit For
End If
Next
If StartVersion = EndVersion Then
SpaVersions.Add("1." & StartVersion)
ElseIf StartVersion >= McVersionHighest Then
SpaVersions.Add("1." & EndVersion & "+")
ElseIf EndVersion <= 7 Then
SpaVersions.Add("1." & StartVersion & "-")
Exit For
ElseIf StartVersion - EndVersion = 1 Then
SpaVersions.Add("1." & StartVersion & ", 1." & EndVersion)
Else
SpaVersions.Add("1." & StartVersion & "~1." & EndVersion)
End If
Next
GameVersionDesc = "[" & Join(SpaVersions, ", ") & "] "
End If
'获取分类
Dim Categories = Data("categories")
Dim CategoryId As New List(Of Integer)
For Each CategoryToken In Categories
CategoryId.Add(CategoryToken("id"))
Next
CategoryId = ArrayNoDouble(CategoryId)
CategoryId.Sort()
CategoryDesc = New List(Of String)
For Each Category In CategoryId
Dim Desc As String
Select Case Category
Case 406 : Desc = "世界生成"
Case 407 : Desc = "生物群系"
Case 409 : Desc = "天然结构"
Case 410 : Desc = "维度"
Case 411 : Desc = "生物"
Case 412 : Desc = "科技"
Case 414 : Desc = "交通与移动"
Case 415 : Desc = "管道与物流"
Case 417 : Desc = "能源"
Case 4558 : Desc = "红石"
Case 420 : Desc = "仓储"
Case 416 : Desc = "农业"
Case 436 : Desc = "食物"
Case 419 : Desc = "魔法"
Case 434 : Desc = "装备与工具"
Case 422 : Desc = "冒险与探索"
Case 424 : Desc = "建筑与装饰"
Case 423 : Desc = "信息显示"
Case 425 : Desc = "杂项"
Case 421 : Desc = "支持库"
Case 435 : Desc = "服务器"
Case 4471 : Desc = "科幻"
Case 4483 : Desc = "战斗"
Case 4477 : Desc = "小游戏"
Case 4478 : Desc = "任务"
Case 4484 : Desc = "多人"
Case 4476 : Desc = "探索"
Case 4736 : Desc = "空岛"
Case 4475 : Desc = "冒险"
Case 4487 : Desc = "FTB"
Case 4480 : Desc = "基于地图"
Case 4479 : Desc = "硬核"
Case 4472 : Desc = "科技"
Case 4473 : Desc = "魔法"
Case 4481 : Desc = "小型整合"
Case 4482 : Desc = "大型整合"
Case Else : Continue For
End Select
CategoryDesc.Add(Desc)
Next
If CategoryDesc.Count = 0 Then CategoryDesc.Add("杂类")
'获取图片
If Data("logo").Count > 0 Then
If GetPixelSize(1) > 1.25 Then
Thumb = Data("logo")("thumbnailUrl").ToString '使用 256x256 图标
Else
Thumb = Data("logo")("thumbnailUrl").ToString.Replace("/256/256/", "/64/64/") '使用 64x64 图标
End If
End If
'保存缓存
If Not DlCfProjectCache.ContainsKey(Id) OrElse DlCfProjectCache(Id).Files Is Nothing Then DictionaryAdd(DlCfProjectCache, Id, Me)
End Sub
Public Function ToCfItem(ShowVersionDesc As Boolean, ShowLoaderDesc As Boolean, Optional OnClick As MyCfItem.ClickEventHandler = Nothing) As MyCfItem
Dim NewItem As New MyCfItem With {.Tag = Me}
NewItem.LabTitle.Text = ChineseName
NewItem.LabInfo.Text = Description.Replace(vbCr, "").Replace(vbLf, "")
NewItem.LabLeft.Text = (If(ModLoaders.Count > 0 AndAlso ShowLoaderDesc, "[" & Join(ModLoaders, "/") & "] ", "") &
If(ShowVersionDesc, GameVersionDesc, "")).Replace("] [", " ") &
Join(CategoryDesc, ",") & " (" &
GetTimeSpanString(DateUpdate - Date.Now) & "更新," &
If(DownloadCount > 100000, Math.Round(DownloadCount / 10000) & " 万次下载", DownloadCount & " 次下载") & ")"
If Thumb Is Nothing Then
NewItem.Logo = "pack://application:,,,/images/Icons/NoIcon.png"
Else
NewItem.Logo = Thumb
End If
If OnClick IsNot Nothing Then
AddHandler NewItem.Click, OnClick
NewItem.HasAnimation = True
Else
NewItem.HasAnimation = False
End If
'结束
Return NewItem
End Function
Public Overrides Function Equals(obj As Object) As Boolean
Dim project = TryCast(obj, DlCfProject)
Return project IsNot Nothing AndAlso Id = project.Id
End Function
Public Shared Operator =(left As DlCfProject, right As DlCfProject) As Boolean
Return EqualityComparer(Of DlCfProject).Default.Equals(left, right)
End Operator
Public Shared Operator <>(left As DlCfProject, right As DlCfProject) As Boolean
Return Not left = right
End Operator
End Class
Public Class DlCfProjectResult
Public Projects As List(Of DlCfProject)
Public Index As Integer
Public RealCount As Long
End Class
''' <summary>
''' CurseForge 工程列表获取事件。
''' </summary>
Public Sub DlCfProjectSub(Task As LoaderTask(Of DlCfProjectRequest, DlCfProjectResult))
'拒绝 1.13- Quilt(这个版本根本没有 Quilt)
If Not Task.Input.IsModPack AndAlso
If(Task.Input.ModLoader, 0) = 5 AndAlso VersionSortInteger(If(Task.Input.GameVersion, "1.15"), "1.14") = -1 Then
Throw New Exception("Quilt 不支持 Minecraft " & Task.Input.GameVersion)
End If
Dim RawFilter As String = If(Task.Input.SearchFilter, "").Trim
Task.Input.SearchFilter = RawFilter
RawFilter = RawFilter.ToLower
Log("[Download] CurseForge 工程列表搜索原始文本:" & RawFilter)
'中文请求关键字处理
Dim IsChineseSearch As Boolean = RegexCheck(RawFilter, "[\u4e00-\u9fbb]")
If IsChineseSearch AndAlso Not String.IsNullOrEmpty(RawFilter) Then
If Task.Input.IsModPack Then Throw New Exception("整合包搜索仅支持英文")
ReleaseCfDatabase()
'构造搜索请求
Dim SearchEntries As New List(Of SearchEntry(Of DlCfDbEntry))
For Each Entry In DlCfProjectDb.Values
If Entry.ChineseName.Contains("动态的树") Then Continue For '傻逼 Mod 的附属太多了
SearchEntries.Add(New SearchEntry(Of DlCfDbEntry) With {
.Item = Entry,
.SearchSource = New List(Of KeyValuePair(Of String, Double)) From {
New KeyValuePair(Of String, Double)(Entry.ChineseName & Entry.CurseForgeId, 1)
}
})
Next
'获取搜索结果
Dim SearchResults = Search(SearchEntries, Task.Input.SearchFilter, 3)
If SearchResults.Count = 0 Then Throw New Exception("无搜索结果,请尝试搜索英文名称")
Dim SearchResult As String = ""
For i = 0 To Math.Min(4, SearchResults.Count - 1) '就算全是准确的,也最多只要 5 个
If Not SearchResults(i).AbsoluteRight AndAlso i >= Math.Min(2, SearchResults.Count - 1) Then Exit For '把 3 个结果拼合以提高准确度
SearchResult += SearchResults(i).Item.CurseForgeId.Replace("-", " ") & " "
SearchResult += SearchResults(i).Item.ChineseName.Replace(" (", "|").Split("|").Last.TrimEnd(") ").Replace(" - ", "§").Split("§").First & " "
Next
Log("[Download] CurseForge 中文搜索原始关键词:" & SearchResult, LogLevel.Developer)
'去除常见连接词
Dim RealFilter As String = ""
For Each Word In SearchResult.Split(" ")
If {"the", "of", "a", "mod"}.Contains(Word.ToLower) OrElse Val(Word) > 0 Then Continue For
If SearchResult.Split(" ").Count > 3 AndAlso {"ftb"}.Contains(Word.ToLower) Then Continue For
RealFilter += Word.TrimStart("{[(").TrimEnd("}])") & " "
Next
Task.Input.SearchFilter = RealFilter
Log("[Download] CurseForge 中文搜索最终关键词:" & RealFilter, LogLevel.Developer)
End If
'驼峰英文请求关键字处理
Dim SpacedKeywords = RegexReplace(Task.Input.SearchFilter, "$& ", "([A-Z]+|[a-z]+?)(?=[A-Z]+[a-z]+[a-z ]*)")
Dim ConnectedKeywords = Task.Input.SearchFilter.Replace(" ", "")
Dim AllPossibleKeywords = (SpacedKeywords & " " & If(IsChineseSearch, Task.Input.SearchFilter, ConnectedKeywords & " " & RawFilter)).ToLower
'最终处理关键字:分割、去重
Dim RightKeywords As New List(Of String)
For Each Keyword In AllPossibleKeywords.Split(" ")
If Keyword.Trim = "" Then Continue For
If {"forge", "fabric", "for", "mod",
"forge/fabric", "fabric/forge", "fabric/quilt", "quilt/fabric"}.Contains(Keyword) Then 'https://github.com/Hex-Dragon/PCL2/issues/208
Log("[Download] 已跳过搜索关键词:" & Keyword, LogLevel.Developer)
Continue For
End If
RightKeywords.Add(Keyword)
Next
If RawFilter.Length > 0 AndAlso RightKeywords.Count = 0 Then
Task.Input.SearchFilter = RawFilter '全都被过滤掉了
Else
Task.Input.SearchFilter = Join(ArrayNoDouble(RightKeywords), " ").ToLower
End If
'例外项:OptiForge、OptiFabric(拆词后因为包含 Forge/Fabric 导致无法搜到实际的 Mod)
If RawFilter.Replace(" ", "").ToLower.Contains("optiforge") Then Task.Input.SearchFilter = "optiforge"
If RawFilter.Replace(" ", "").ToLower.Contains("optifabric") Then Task.Input.SearchFilter = "optifabric"
Log("[Download] CurseForge 工程列表搜索最终文本:" & Task.Input.SearchFilter, LogLevel.Developer)
'正式获取
Dim Url = Task.Input.GetAddress()
Log("[Download] 开始获取 CurseForge 工程列表:" & Url)
Dim RequestResult As JObject = NetGetCodeByRequestRetry(Url, IsJson:=True, Encode:=Encoding.UTF8)
Dim FileList = GetCfProjectListFromJson(RequestResult("data"), Task.Input.IsModPack)
If FileList.Count = 0 Then Throw New Exception("无搜索结果")
'重新排序
If Not String.IsNullOrEmpty(Task.Input.SearchFilter) Then
'排序分 = 搜索相对相似度 + 默认排序相对活跃度
Dim Scores As New Dictionary(Of DlCfProject, Double)
Dim Entry As New List(Of SearchEntry(Of DlCfProject))
For i = 1 To FileList.Count
Dim File = FileList(i - 1)
Scores.Add(File, (1 - (i / FileList.Count)))
Entry.Add(New SearchEntry(Of DlCfProject) With {.Item = File, .SearchSource = New List(Of KeyValuePair(Of String, Double)) From {
New KeyValuePair(Of String, Double)(If(IsChineseSearch, File.ChineseName, File.Name), 1),
New KeyValuePair(Of String, Double)(File.Description, 0.05)}})
Next
Dim SearchResult = Search(Entry, RawFilter, 101, -1)
For Each OneResult In SearchResult
Scores(OneResult.Item) += OneResult.Similarity / SearchResult(0).Similarity
Next
'根据排序分得出结果
Dim ResultList = Sort(Scores.ToList, Function(Left As KeyValuePair(Of DlCfProject, Double), Right As KeyValuePair(Of DlCfProject, Double))
Return Left.Value > Right.Value
End Function)
FileList = New List(Of DlCfProject)
For Each Result In ResultList
FileList.Add(Result.Key)
Next
End If
Task.Output = New DlCfProjectResult With {.Projects = FileList, .Index = RequestResult("pagination")("index"), .RealCount = RequestResult("pagination")("totalCount")}
End Sub
''' <summary>
''' 从 API 返回的 Json 数组中获取工程信息数组。
''' </summary>
Private Function GetCfProjectListFromJson(Json As JArray, IsModPack As Boolean) As List(Of DlCfProject)
Dim FileList As New List(Of DlCfProject)
For Each JsonEntry As JObject In Json
Dim Result = GetCfProjectFromJson(JsonEntry, IsModPack)
If Result IsNot Nothing Then FileList.Add(Result)
Next
Return FileList
End Function
''' <summary>
''' 从 API 返回的 Json 中获取工程信息。
''' </summary>
Private Function GetCfProjectFromJson(Json As JObject, IsModPack As Boolean) As DlCfProject
If Json("links")("websiteUrl") Is Nothing Then
Log("[Download] 发现关键项为 Nothing 的工程:" & If(Json, "").ToString, LogLevel.Debug)
Return Nothing
End If
If IsModPack = Json("links")("websiteUrl").ToString.Contains("curseforge.com/minecraft/mc-mods") Then
Log("[Download] 返回的工程与要求的类别不一致:" & Json("links")("websiteUrl").ToString, LogLevel.Debug)
Return Nothing
End If
Return New DlCfProject(Json)
End Function
''' <summary>
''' 读取 Mod 数据库。
''' </summary>
Private Sub ReleaseCfDatabase()
If DlCfProjectDb IsNot Nothing Then Exit Sub
DlCfProjectDb = New Dictionary(Of String, DlCfDbEntry)
Dim i As Integer = 0
For Each Line In DecodeBytes(GetResources("ModData")).Split(vbLf)
i += 1
If Line = "" Then Continue For
For Each EntryData As String In Line.Split("¨")
Dim Entry = New DlCfDbEntry
Dim SplitedLine = EntryData.Split("|")
Entry.CurseForgeId = SplitedLine(0)
Entry.WikiId = i
If SplitedLine.Count >= 2 Then
Entry.ChineseName = SplitedLine(1)
Entry.MCBBS = If(SplitedLine.Count >= 3, SplitedLine(2), Nothing)
If Entry.ChineseName.Contains("*") Then '处理 *
Entry.ChineseName = Entry.ChineseName.Replace("*", " (" &
String.Join(" ", Entry.CurseForgeId.Split("-").Select(Function(w) w.Substring(0, 1).ToUpper & w.Substring(1, w.Length - 1))) & ")")
End If
End If
DlCfProjectDb(Entry.CurseForgeId) = Entry
Next
Next
End Sub
Public Class DlCfDbEntry
''' <summary>
''' McMod 的 ID。
''' </summary>
Public WikiId As Integer
''' <summary>
''' 中文译名。若为空字符串则使用英文名。
''' </summary>
Public ChineseName As String = ""
''' <summary>
''' MCBBS 发布帖编号(例如 195107)。若为 Nothing 则没有发布帖。
''' </summary>
Public MCBBS As Integer?
''' <summary>
''' CurseForge ID(例如 advanced-solar-panels)。
''' </summary>
Public CurseForgeId As String
Public Overrides Function ToString() As String
Return CurseForgeId & "|" & WikiId & "|" & ChineseName & If(MCBBS Is Nothing, "", "|" & MCBBS)
End Function
End Class
#End Region
#Region "DlCfFile | CurseForge Files"
Public DlCfFileLoader As New LoaderTask(Of KeyValuePair(Of Integer, Boolean), List(Of DlCfFile))("DlCfFile Main", Sub(Task As LoaderTask(Of KeyValuePair(Of Integer, Boolean), List(Of DlCfFile))) Task.Output = DlCfGetFiles(Task.Input.Key, Task.Input.Value))
''' <summary>
''' CurseForge 工程下的单一版本文件。
''' </summary>
Public Class DlCfFile
Public IsModPack As Boolean
Public FileId As Integer
Public DisplayName As String
Public [Date] As Date
Public GameVersion As String()
Public ModLoaders As New List(Of String)
Public ReleaseType As Integer
Public FileName As String
Public DownloadCount As Integer
Public Dependencies As New List(Of Integer)
Private DownloadAddress As String()
Public ReadOnly Property ReleaseTypeString As String
Get
Select Case ReleaseType
Case 1
Return "正式版"
Case 2
Return If(ModeDebug, "Beta 测试版", "测试版")
Case Else
Return If(ModeDebug, "Alpha 测试版", "测试版")
End Select
End Get
End Property
Public ReadOnly Property IsAvailable As Boolean
Get
Return DisplayName IsNot Nothing AndAlso GameVersion IsNot Nothing AndAlso FileName IsNot Nothing AndAlso DownloadAddress(0).StartsWith("http")
End Get
End Property
''' <summary>
''' 从 CurseForge 文件 Json 项中初始化实例。若出错会抛出异常。
''' </summary>
Public Sub New(Data As JObject, IsModPack As Boolean)
FileId = Data("id")
DisplayName = Data("displayName").ToString.Replace(" ", "").Trim(" ")
[Date] = Data("fileDate")
ReleaseType = Data("releaseType")
DownloadCount = Data("downloadCount")
FileName = Data("fileName")
Me.IsModPack = IsModPack
'获取下载地址
Dim Url = Data("downloadUrl").ToString
If Url = "" Then Url = "https://media.forgecdn.net/files/" & FileId.ToString.Substring(0, 4) & "/" & FileId.ToString.Substring(4).TrimStart("0") & "/" & FileName
Url = Url.Replace(FileName, Net.WebUtility.UrlEncode(FileName)) '对文件名进行编码
DownloadAddress = ArrayNoDouble({Url.Replace("-service.overwolf.wtf", ".forgecdn.net").Replace("://edge", "://media"), Url.Replace("-service.overwolf.wtf", ".forgecdn.net"), Url.Replace("://edge", "://media"), Url})
'获取前置 Mod 的 Addon ID
If Not IsModPack Then
For Each Dep In Data("dependencies")
If Dep("relationType").ToObject(Of Integer) = 3 AndAlso '是一个依赖
Dep("modId").ToObject(Of Integer) <> 306612 Then '排除 Fabric API
Dependencies.Add(Dep("modId"))
End If
Next
End If
'获取游戏版本与 Mod 加载器列表
Dim Versions As New List(Of String)
For Each Version In Data("gameVersions").Select(Function(t) t.ToString.Trim.ToLower)
If Version.StartsWith("1.") OrElse Version.Contains("w") Then
Versions.Add(Version.Replace("-snapshot", " 快照"))
ElseIf Version = "forge" OrElse Version = "fabric" OrElse Version = "quilt" OrElse Version = "rift" Then
ModLoaders.Add(Version.First.ToString.ToUpper & Version.Substring(1))
End If
Next
If Versions.Count > 1 Then
GameVersion = Sort(Versions, AddressOf VersionSortBoolean).ToArray
If IsModPack Then GameVersion = {GameVersion(0)}
ElseIf Versions.Count = 1 Then
GameVersion = Versions.ToArray
Else
GameVersion = {"未知版本"}
End If
'用一个偷懒的方式让 Forge 排在 Fabric 的前面
ModLoaders = ModLoaders.Select(Function(s) s.Replace("Forge", "Aorge")).ToList()
ModLoaders.Sort()
ModLoaders = ModLoaders.Select(Function(s) s.Replace("Aorge", "Forge")).ToList()
End Sub
''' <summary>
''' 获取对应的下载文件信息。
''' </summary>
''' <param name="LocalAddress">本地文件夹或文件地址。</param>
Public Function GetDownloadFile(LocalAddress As String, IsFullPath As Boolean) As NetFile
Return New NetFile(DownloadAddress, LocalAddress & If(IsFullPath, "", FileName))
End Function
Public Function ToListItem(OnClick As MyListItem.ClickEventHandler, Optional OnSaveClick As MyIconButton.ClickEventHandler = Nothing) As MyListItem
'获取描述信息
Dim Info As String = ""
If Not IsModPack Then
Info += If(ModLoaders.Count > 0, Join(ModLoaders, "/"), "") &
If(GameVersion.Count > 1, If(ModLoaders.Count > 0, " ", "") & Join(GameVersion, "、").Replace("-snapshot", " 快照"), "")
If Info <> "" Then Info = "适用于 " & Info & ","
Info += If(ModeDebug AndAlso Dependencies.Count > 0, Dependencies.Count & " 个前置 Mod,", "")
End If
'If DownloadCount > 0 Then 'DownloadCount 目前的返回内容完全不正确,经常是 0,干脆不显示了
' Info += If(DownloadCount > 100000, Math.Round(DownloadCount / 10000) & " 万次下载,", DownloadCount & " 次下载,")
'End If
Info += GetTimeSpanString([Date] - Date.Now) & "更新"
Info += If(ReleaseType <> 1, "," & ReleaseTypeString, "")
'建立控件
Dim NewItem As New MyListItem With {
.Title = DisplayName, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Me,
.Info = Info
}
Select Case ReleaseType
Case 1 'R
NewItem.Logo = "pack://application:,,,/images/Icons/R.png"
Case 2 'B
NewItem.Logo = "pack://application:,,,/images/Icons/B.png"
Case Else 'A
NewItem.Logo = "pack://application:,,,/images/Icons/A.png"
End Select
AddHandler NewItem.Click, OnClick
'建立另存为按钮
If OnSaveClick 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, OnSaveClick
NewItem.Buttons = {BtnSave}
NewItem.PaddingRight = 35
End If
'结束
Return NewItem
End Function
Public Overrides Function ToString() As String
Return DisplayName
End Function
End Class
''' <summary>
''' 获取某个 CurseForge 工程下的全部文件列表。
''' 必须在工作线程执行,失败会抛出异常。
''' </summary>
Public Function DlCfGetFiles(ProjectId As Integer, IsModPack As Boolean) As List(Of DlCfFile)
'获取工程对象
Dim TargetProject As DlCfProject
If DlCfProjectCache.ContainsKey(ProjectId) Then
TargetProject = DlCfProjectCache(ProjectId)
Else
TargetProject = GetCfProjectFromJson(NetGetCodeByRequestRetry("https://api.curseforge.com/v1/mods/" & ProjectId, IsJson:=True, Encode:=Encoding.UTF8)("data"), IsModPack)
End If
'获取工程对象的文件列表
If TargetProject.Files Is Nothing Then
Log("[Download] 开始获取 CurseForge 工程 ID 为 " & ProjectId & " 的文件列表")
Dim Json As JArray
If TargetProject.IsModPack Then
'整合包使用全部文件
Json = NetGetCodeByRequestRetry("https://api.curseforge.com/v1/mods/" & ProjectId & "/files?pageSize=999", Accept:="application/json", IsJson:=True)("data")
Else
'Mod 使用每个版本最新的文件
Json = GetJson(NetRequestRetry("https://api.curseforge.com/v1/mods/files", "POST", "{""fileIds"": [" & Join(TargetProject.FileIndexes, ",") & "]}", "application/json"))("data")
End If
Dim Files As New Dictionary(Of Integer, DlCfFile)
For Each JsonEntry As JObject In Json
Dim NewFile As New DlCfFile(JsonEntry, IsModPack)
If NewFile.IsAvailable AndAlso Not Files.ContainsKey(NewFile.FileId) Then Files.Add(NewFile.FileId, NewFile)
Next
TargetProject.Files = Files.Values.ToList
End If
'获取前置 Mod 列表
If IsModPack Then GoTo ExitSub
Dim Deps As New List(Of Integer)
For Each File In TargetProject.Files
For Each AddonId In File.Dependencies
If Not Deps.Contains(AddonId) AndAlso Not DlCfProjectCache.ContainsKey(AddonId) Then Deps.Add(AddonId)
Next
Next
If Deps.Count > 0 Then
'获取前置 Mod 工程信息
Log("[Download] 文件列表中需要获取的前置 Mod:" & Join(Deps, ","))
GetCfProjectListFromJson(GetJson(NetRequestRetry("https://api.curseforge.com/v1/mods", "POST", "{""modIds"": [" & Join(Deps, ",") & "]}", "application/json"))("data"), False)
End If
'返回
ExitSub:
Return TargetProject.Files
End Function
Public Sub DlCfFilesPreload(Stack As StackPanel, Entrys As List(Of DlCfFile), OnClick As MyListItem.ClickEventHandler)
'获取卡片对应的前置 ID
'如果为整合包就不会有 Dependencies 信息,所以不用管
Dim Deps As New List(Of Integer)
For Each File In Entrys
For Each AddonId In If(File.Dependencies, New List(Of Integer))
If Not Deps.Contains(AddonId) Then
If Not DlCfProjectCache.ContainsKey(AddonId) Then
Log("[Download] 未找到 ID 为 " & AddonId & " 的前置 Mod 信息", LogLevel.Developer)
Continue For
End If
Deps.Add(AddonId)
End If
Next
Next
Deps.Remove(306612) '移除 Fabric API
If Deps.Count = 0 Then Exit Sub
Deps.Sort()
'添加开头间隔
Stack.Children.Add(New TextBlock With {.Text = "前置 Mod", .FontSize = 14, .HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(6, 2, 0, 5)})
'添加前置 Mod 列表
For Each Dep In Deps
Dim Item = DlCfProjectCache(Dep).ToCfItem(False, True, AddressOf FrmDownloadMod.ProjectClick)
'Item.Margin = New Thickness(5, 0, 0, 0)
Stack.Children.Add(Item)
Next
'添加结尾间隔
Stack.Children.Add(New TextBlock With {.Text = "可选版本", .FontSize = 14, .HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(6, 12, 0, 5)})
End Sub
Public DlOptiFabricLoader As New LoaderTask(Of Integer, List(Of CompFile))("OptiFabric List Loader",
Sub(Task As LoaderTask(Of Integer, List(Of CompFile))) Task.Output = CompFilesGet("322385", True))
#End Region