2.5.0
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user