fix(modpack): fixed curseforge import by url
This commit is contained in:
@@ -51,7 +51,6 @@ func (i *CurseForgeImporter) downloadAndProcessFile(url string) (hash string, si
|
|||||||
return "", 0, fmt.Errorf("bad status downloading file: %s", resp.Status)
|
return "", 0, fmt.Errorf("bad status downloading file: %s", resp.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Используем тот же processFile, что и в SimpleZipImporter
|
|
||||||
baseImporter := SimpleZipImporter{StoragePath: i.StoragePath}
|
baseImporter := SimpleZipImporter{StoragePath: i.StoragePath}
|
||||||
return baseImporter.processFile(resp.Body)
|
return baseImporter.processFile(resp.Body)
|
||||||
}
|
}
|
||||||
@@ -72,101 +71,27 @@ func (i *CurseForgeImporter) getFileDownloadURL(projectID, fileID int) (string,
|
|||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
if resp.StatusCode == http.StatusFound || resp.StatusCode == http.StatusMovedPermanently {
|
||||||
|
location, err := resp.Location()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get redirect location: %w", err)
|
||||||
|
}
|
||||||
|
return location.String(), nil
|
||||||
|
}
|
||||||
return "", fmt.Errorf("bad status getting download URL: %s", resp.Status)
|
return "", fmt.Errorf("bad status getting download URL: %s", resp.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
var apiResp CurseForgeFileResponse
|
bodyBytes, err := io.ReadAll(resp.Body)
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return apiResp.Data.DownloadURL, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Import реализует основной метод для CurseForge.
|
|
||||||
func (i *CurseForgeImporter) Import(zipPath string) ([]models.ModpackFile, error) {
|
|
||||||
r, err := zip.OpenReader(zipPath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return "", fmt.Errorf("failed to read download URL response body: %w", err)
|
||||||
}
|
|
||||||
defer r.Close()
|
|
||||||
|
|
||||||
var manifestFile *zip.File
|
|
||||||
for _, f := range r.File {
|
|
||||||
if f.Name == "manifest.json" {
|
|
||||||
manifestFile = f
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if manifestFile == nil {
|
downloadURL := string(bodyBytes)
|
||||||
return nil, fmt.Errorf("manifest.json not found in archive")
|
if downloadURL == "" {
|
||||||
|
return "", fmt.Errorf("received empty download URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
mfReader, err := manifestFile.Open()
|
return downloadURL, nil
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer mfReader.Close()
|
|
||||||
|
|
||||||
var manifest CurseForgeManifest
|
|
||||||
if err := json.NewDecoder(mfReader).Decode(&manifest); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse manifest.json: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var files []models.ModpackFile
|
|
||||||
|
|
||||||
// 1. Обрабатываем файлы из манифеста (моды)
|
|
||||||
for _, modFile := range manifest.Files {
|
|
||||||
downloadURL, err := i.getFileDownloadURL(modFile.ProjectID, modFile.FileID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get download url for fileID %d: %w", modFile.FileID, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
hash, size, err := i.downloadAndProcessFile(downloadURL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to process downloaded file for fileID %d: %w", modFile.FileID, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Путь для модов обычно "mods/filename.jar", но у нас нет имени файла.
|
|
||||||
// Лаунчеру это не важно, он будет сохранять по хешу.
|
|
||||||
// Для манифеста мы можем сгенерировать путь.
|
|
||||||
relativePath := filepath.Join("mods", fmt.Sprintf("%d-%d.jar", modFile.ProjectID, modFile.FileID))
|
|
||||||
|
|
||||||
files = append(files, models.ModpackFile{
|
|
||||||
RelativePath: relativePath,
|
|
||||||
FileHash: hash,
|
|
||||||
FileSize: size,
|
|
||||||
DownloadURL: downloadURL,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Обрабатываем файлы из папки overrides
|
|
||||||
baseImporter := SimpleZipImporter{StoragePath: i.StoragePath}
|
|
||||||
for _, f := range r.File {
|
|
||||||
if strings.HasPrefix(f.Name, manifest.Overrides+"/") && !f.FileInfo().IsDir() {
|
|
||||||
fileReader, err := f.Open()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
hash, size, err := baseImporter.processFile(fileReader)
|
|
||||||
if err != nil {
|
|
||||||
fileReader.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
fileReader.Close()
|
|
||||||
|
|
||||||
relativePath := strings.TrimPrefix(f.Name, manifest.Overrides+"/")
|
|
||||||
files = append(files, models.ModpackFile{
|
|
||||||
RelativePath: relativePath,
|
|
||||||
FileHash: hash,
|
|
||||||
FileSize: size,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return files, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// findModpackBySlug ищет ID проекта по его "слагу" (части URL).
|
// findModpackBySlug ищет ID проекта по его "слагу" (части URL).
|
||||||
@@ -220,14 +145,12 @@ func (i *CurseForgeImporter) getLatestModpackFileURL(projectID int) (string, err
|
|||||||
return "", fmt.Errorf("no files found for projectID %d", projectID)
|
return "", fmt.Errorf("no files found for projectID %d", projectID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Последний загруженный файл обычно идет первым в списке
|
|
||||||
latestFile := filesResp.Data[0]
|
latestFile := filesResp.Data[0]
|
||||||
return latestFile.DownloadURL, nil
|
return latestFile.DownloadURL, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DownloadModpackFromURL скачивает zip-архив модпака по URL страницы CurseForge.
|
// DownloadModpackFromURL скачивает zip-архив модпака по URL страницы CurseForge.
|
||||||
func (i *CurseForgeImporter) DownloadModpackFromURL(pageURL string) (string, error) {
|
func (i *CurseForgeImporter) DownloadModpackFromURL(pageURL string) (string, error) {
|
||||||
// 1. Парсим URL, чтобы извлечь slug
|
|
||||||
parsedURL, err := url.Parse(pageURL)
|
parsedURL, err := url.Parse(pageURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("invalid url: %w", err)
|
return "", fmt.Errorf("invalid url: %w", err)
|
||||||
@@ -235,21 +158,18 @@ func (i *CurseForgeImporter) DownloadModpackFromURL(pageURL string) (string, err
|
|||||||
slug := path.Base(parsedURL.Path)
|
slug := path.Base(parsedURL.Path)
|
||||||
log.Printf("Importer: Extracted slug '%s' from URL", slug)
|
log.Printf("Importer: Extracted slug '%s' from URL", slug)
|
||||||
|
|
||||||
// 2. Находим ID проекта по slug
|
|
||||||
projectID, err := i.findModpackBySlug(slug)
|
projectID, err := i.findModpackBySlug(slug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
log.Printf("Importer: Found projectID %d for slug '%s'", projectID, slug)
|
log.Printf("Importer: Found projectID %d for slug '%s'", projectID, slug)
|
||||||
|
|
||||||
// 3. Получаем URL для скачивания последнего файла
|
|
||||||
downloadURL, err := i.getLatestModpackFileURL(projectID)
|
downloadURL, err := i.getLatestModpackFileURL(projectID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
log.Printf("Importer: Found download URL: %s", downloadURL)
|
log.Printf("Importer: Found download URL: %s", downloadURL)
|
||||||
|
|
||||||
// 4. Скачиваем zip-архив во временный файл
|
|
||||||
tempFile, err := os.CreateTemp("", "modpack-*.zip")
|
tempFile, err := os.CreateTemp("", "modpack-*.zip")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -274,3 +194,84 @@ func (i *CurseForgeImporter) DownloadModpackFromURL(pageURL string) (string, err
|
|||||||
log.Printf("Importer: Successfully downloaded modpack to %s", tempFile.Name())
|
log.Printf("Importer: Successfully downloaded modpack to %s", tempFile.Name())
|
||||||
return tempFile.Name(), nil
|
return tempFile.Name(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Import реализует основной метод для CurseForge.
|
||||||
|
func (i *CurseForgeImporter) Import(zipPath string) ([]models.ModpackFile, error) {
|
||||||
|
r, err := zip.OpenReader(zipPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
var manifestFile *zip.File
|
||||||
|
for _, f := range r.File {
|
||||||
|
if f.Name == "manifest.json" {
|
||||||
|
manifestFile = f
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if manifestFile == nil {
|
||||||
|
return nil, fmt.Errorf("manifest.json not found in archive")
|
||||||
|
}
|
||||||
|
|
||||||
|
mfReader, err := manifestFile.Open()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer mfReader.Close()
|
||||||
|
|
||||||
|
var manifest CurseForgeManifest
|
||||||
|
if err := json.NewDecoder(mfReader).Decode(&manifest); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse manifest.json: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var files []models.ModpackFile
|
||||||
|
|
||||||
|
for _, modFile := range manifest.Files {
|
||||||
|
downloadURL, err := i.getFileDownloadURL(modFile.ProjectID, modFile.FileID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get download url for fileID %d: %w", modFile.FileID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
hash, size, err := i.downloadAndProcessFile(downloadURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to process downloaded file for fileID %d: %w", modFile.FileID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
relativePath := filepath.Join("mods", fmt.Sprintf("%d-%d.jar", modFile.ProjectID, modFile.FileID))
|
||||||
|
|
||||||
|
files = append(files, models.ModpackFile{
|
||||||
|
RelativePath: relativePath,
|
||||||
|
FileHash: hash,
|
||||||
|
FileSize: size,
|
||||||
|
DownloadURL: downloadURL,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
baseImporter := SimpleZipImporter{StoragePath: i.StoragePath}
|
||||||
|
for _, f := range r.File {
|
||||||
|
if strings.HasPrefix(f.Name, manifest.Overrides+"/") && !f.FileInfo().IsDir() {
|
||||||
|
fileReader, err := f.Open()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hash, size, err := baseImporter.processFile(fileReader)
|
||||||
|
if err != nil {
|
||||||
|
fileReader.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fileReader.Close()
|
||||||
|
|
||||||
|
relativePath := strings.TrimPrefix(f.Name, manifest.Overrides+"/")
|
||||||
|
files = append(files, models.ModpackFile{
|
||||||
|
RelativePath: relativePath,
|
||||||
|
FileHash: hash,
|
||||||
|
FileSize: size,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user