This repository was archived by the owner on Oct 20, 2026. It is now read-only.
Repository navigation
Expand file tree
/
Copy pathModLibrary.vb
More file actions
304 lines (269 loc) · 14.4 KB
/
Copy pathModLibrary.vb
File metadata and controls
304 lines (269 loc) · 14.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
Imports System
Imports System.Collections.Generic
Imports System.IO
Imports System.Net.Http
Imports System.Threading.Tasks
Imports Newtonsoft.Json
Imports Newtonsoft.Json.Linq
Public Module ModLibrary
Private _httpClient As HttpClient = Nothing
Private ReadOnly Property HttpClient As HttpClient
Get
If _httpClient Is Nothing Then
_httpClient = New HttpClient()
_httpClient.Timeout = TimeSpan.FromMinutes(3)
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("NextCraftLaunch/1.0")
_httpClient.DefaultRequestHeaders.AcceptLanguage.ParseAdd("zh-CN, zh, en-US, en")
_httpClient.DefaultRequestHeaders.Add("Accept-Charset", "utf-8")
End If
Return _httpClient
End Get
End Property
Private Const SEARCH_URL As String = "https://api.modrinth.com/v2/search?query={0}&facets={1}&limit={2}&index={3}&lang=zh-CN"
Private Const PROJECT_URL As String = "https://api.modrinth.com/v2/project/{0}?lang=zh-CN"
Private Const VERSIONS_URL As String = "https://api.modrinth.com/v2/project/{0}/version?lang=zh-CN"
Public Async Function SearchMods(query As String,
Optional gameVersion As String = "",
Optional loader As String = "",
Optional limit As Integer = 30,
Optional sortIndex As String = "downloads") As Task(Of List(Of ModLibraryEntry))
Dim result As New List(Of ModLibraryEntry)()
Try
Dim facets As New List(Of String)()
facets.Add("[""project_type:mod""]")
If Not String.IsNullOrWhiteSpace(gameVersion) Then
facets.Add("[""versions:""" & gameVersion & """""]")
End If
If Not String.IsNullOrWhiteSpace(loader) Then
facets.Add("[""categories:""" & loader & """""]")
End If
Dim facetsJson As String = "[" & String.Join(",", facets) & "]"
Dim url As String = String.Format(SEARCH_URL, Uri.EscapeDataString(query),
Uri.EscapeDataString(facetsJson), limit, sortIndex)
Dim responseMsg As HttpResponseMessage = Await HttpClient.GetAsync(url)
responseMsg.EnsureSuccessStatusCode()
Dim bytes As Byte() = Await responseMsg.Content.ReadAsByteArrayAsync()
Dim json As String = System.Text.Encoding.UTF8.GetString(bytes)
Dim root As JObject = JObject.Parse(json)
Dim hits As JArray = TryCast(root("hits"), JArray)
If hits Is Nothing Then Return result
For Each item As JToken In hits
Try
Dim entry As New ModLibraryEntry()
entry.Slug = item("slug")?.ToString()
Dim titleZh As String = String.Empty
Dim displayTitle As String = item("title")?.ToString()
' 尝试读取 Modrinth 返回的中文标题(如有)
Try
Dim titleField As JToken = item("title")
If titleField IsNot Nothing Then
titleZh = titleField.ToString()
If Not String.IsNullOrWhiteSpace(titleZh) Then
displayTitle = titleZh
End If
End If
Catch
End Try
' 若标题为英文且 slug/其他字段有中文,也保留原标题
entry.Title = displayTitle
entry.Description = item("description")?.ToString()
entry.Author = item("author")?.ToString()
entry.Downloads = If(item("downloads") IsNot Nothing, item("downloads").Value(Of Long)(), 0L)
entry.IconUrl = item("icon_url")?.ToString()
entry.ProjectId = item("project_id")?.ToString()
entry.DateCreated = item("date_created")?.ToString()
entry.LatestVersion = item("latest_version")?.ToString()
Dim categories As JArray = TryCast(item("categories"), JArray)
If categories IsNot Nothing Then
Dim catList As New List(Of String)()
For Each c In categories
catList.Add(c.ToString())
Next
entry.Categories = catList
End If
Dim versions As JArray = TryCast(item("versions"), JArray)
If versions IsNot Nothing Then
Dim verList As New List(Of String)()
For Each v In versions
verList.Add(v.ToString())
Next
entry.GameVersions = verList
End If
If Not String.IsNullOrWhiteSpace(entry.Slug) Then
result.Add(entry)
End If
Catch
End Try
Next
Catch ex As Exception
End Try
Return result
End Function
Public Async Function GetProjectInfo(slug As String) As Task(Of ModLibraryEntry)
Try
Dim url As String = String.Format(PROJECT_URL, slug)
Dim responseMsg As HttpResponseMessage = Await HttpClient.GetAsync(url)
responseMsg.EnsureSuccessStatusCode()
Dim bytes As Byte() = Await responseMsg.Content.ReadAsByteArrayAsync()
Dim json As String = System.Text.Encoding.UTF8.GetString(bytes)
Dim obj As JObject = JObject.Parse(json)
Dim entry As New ModLibraryEntry()
entry.Slug = obj("slug")?.ToString()
entry.Title = obj("title")?.ToString()
entry.Description = obj("description")?.ToString()
entry.Author = obj("author")?.ToString()
entry.Downloads = If(obj("downloads") IsNot Nothing, obj("downloads").Value(Of Long)(), 0L)
entry.IconUrl = obj("icon_url")?.ToString()
entry.ProjectId = obj("id")?.ToString()
Return entry
Catch
Return Nothing
End Try
End Function
Public Async Function GetLatestVersionDownload(slug As String,
Optional targetGameVersion As String = "",
Optional targetLoader As String = "forge") As Task(Of ModDownloadInfo)
Try
Dim url As String = String.Format(VERSIONS_URL, slug)
Dim responseMsg As HttpResponseMessage = Await HttpClient.GetAsync(url)
responseMsg.EnsureSuccessStatusCode()
Dim bytes As Byte() = Await responseMsg.Content.ReadAsByteArrayAsync()
Dim json As String = System.Text.Encoding.UTF8.GetString(bytes)
Dim versions As JArray = JArray.Parse(json)
For Each ver As JToken In versions
Try
Dim versionNumber As String = ver("version_number")?.ToString()
Dim gameVersions As JArray = TryCast(ver("game_versions"), JArray)
Dim loaders As JArray = TryCast(ver("loaders"), JArray)
If Not String.IsNullOrWhiteSpace(targetGameVersion) Then
Dim versionMatch As Boolean = False
If gameVersions IsNot Nothing Then
For Each gv In gameVersions
If gv.ToString() = targetGameVersion Then
versionMatch = True
Exit For
End If
Next
End If
If Not versionMatch Then Continue For
End If
If Not String.IsNullOrWhiteSpace(targetLoader) Then
Dim loaderMatch As Boolean = False
If loaders IsNot Nothing Then
For Each l In loaders
If l.ToString().ToLower() = targetLoader.ToLower() Then
loaderMatch = True
Exit For
End If
Next
End If
If Not loaderMatch Then Continue For
End If
Dim files As JArray = TryCast(ver("files"), JArray)
If files IsNot Nothing AndAlso files.Count > 0 Then
Dim firstFile As JObject = TryCast(files(0), JObject)
If firstFile IsNot Nothing Then
Dim info As New ModDownloadInfo()
info.DownloadUrl = firstFile("url")?.ToString()
info.FileName = firstFile("filename")?.ToString()
info.Size = If(firstFile("size") IsNot Nothing, firstFile("size").Value(Of Long)(), 0L)
info.VersionNumber = versionNumber
Dim hashes As JObject = TryCast(firstFile("hashes"), JObject)
If hashes IsNot Nothing Then
info.Sha1Hash = hashes("sha1")?.ToString()
info.Sha512Hash = hashes("sha512")?.ToString()
End If
Return info
End If
End If
Catch
End Try
Next
Catch
End Try
Return Nothing
End Function
Public Async Function DownloadModFile(downloadUrl As String,
savePath As String,
progressCallback As Action(Of Integer, String)) As Task(Of Boolean)
Try
Dim dir As String = Path.GetDirectoryName(savePath)
If Not String.IsNullOrWhiteSpace(dir) AndAlso Not Directory.Exists(dir) Then
Directory.CreateDirectory(dir)
End If
Using response As HttpResponseMessage = Await HttpClient.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead)
response.EnsureSuccessStatusCode()
Dim contentLength As Long = 0
If response.Content.Headers.ContentLength.HasValue Then
contentLength = response.Content.Headers.ContentLength.Value
End If
Using contentStream As Stream = Await response.Content.ReadAsStreamAsync()
Using fileStream As New FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None)
Dim buffer As Byte() = New Byte(8191) {}
Dim totalRead As Long = 0
Dim bytesRead As Integer = 0
Dim lastReportedPercent As Integer = -1
Do
bytesRead = Await contentStream.ReadAsync(buffer, 0, buffer.Length)
If bytesRead > 0 Then
Await fileStream.WriteAsync(buffer, 0, bytesRead)
totalRead += bytesRead
If contentLength > 0 AndAlso progressCallback IsNot Nothing Then
Dim percent As Integer = CInt((totalRead * 100L) \ contentLength)
If percent < 0 Then percent = 0
If percent > 100 Then percent = 100
If percent <> lastReportedPercent Then
lastReportedPercent = percent
progressCallback(percent, "已下载 " & (totalRead / 1024 / 1024).ToString("0.00") &
" / " & (contentLength / 1024 / 1024).ToString("0.00") & " MB")
End If
End If
End If
Loop While bytesRead > 0
fileStream.Flush()
End Using
End Using
End Using
Return True
Catch ex As Exception
progressCallback?.Invoke(0, "下载失败:" & ex.Message)
Return False
End Try
End Function
End Module
Public Class ModLibraryEntry
Public Property Slug As String = String.Empty
Public Property Title As String = String.Empty
Public Property Description As String = String.Empty
Public Property Author As String = String.Empty
Public Property Downloads As Long = 0
Public Property IconUrl As String = String.Empty
Public Property ProjectId As String = String.Empty
Public Property DateCreated As String = String.Empty
Public Property LatestVersion As String = String.Empty
Public Property Categories As List(Of String) = New List(Of String)()
Public Property GameVersions As List(Of String) = New List(Of String)()
Public Property SelectedDownloadInfo As ModDownloadInfo = Nothing
Public ReadOnly Property DownloadsText As String
Get
If Downloads >= 1000000 Then
Return (Downloads / 1000000.0).ToString("0.0") & "M"
ElseIf Downloads >= 1000 Then
Return (Downloads / 1000.0).ToString("0.0") & "K"
Else
Return Downloads.ToString()
End If
End Get
End Property
Public Overrides Function ToString() As String
Return Title & " — " & Author & " (" & DownloadsText & " 下载)"
End Function
End Class
Public Class ModDownloadInfo
Public Property DownloadUrl As String = String.Empty
Public Property FileName As String = String.Empty
Public Property Size As Long = 0
Public Property VersionNumber As String = String.Empty
Public Property Sha1Hash As String = String.Empty
Public Property Sha512Hash As String = String.Empty
End Class