Mod metadata retrieval

This commit is contained in:
comp500 2019-05-11 01:43:34 +01:00
parent 6c820a3748
commit 3fdac51d22
No known key found for this signature in database
GPG Key ID: 214C822FFEC586B5
3 changed files with 111 additions and 20 deletions

View File

@ -2,6 +2,7 @@ package core
import ( import (
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"errors"
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
@ -89,3 +90,12 @@ func (pack Pack) Write() error {
return enc.Encode(pack) return enc.Encode(pack)
} }
// GetMCVersion gets the version of Minecraft this pack uses, if it has been correctly specified
func (pack Pack) GetMCVersion() (string, error) {
mcVersion, ok := pack.Versions["minecraft"]
if !ok {
return "", errors.New("No Minecraft version specified in modpack!")
}
return mcVersion, nil
}

View File

@ -91,24 +91,43 @@ func cmdInstall(flags core.Flags, mod string) error {
if len(mod) == 0 { if len(mod) == 0 {
return cli.NewExitError("You must specify a mod.", 1) return cli.NewExitError("You must specify a mod.", 1)
} }
//fmt.Println("Not implemented yet!") pack, err := core.LoadPack(flags)
if err != nil {
return cli.NewExitError(err, 1)
}
index, err := pack.LoadIndex()
if err != nil {
return cli.NewExitError(err, 1)
}
mcVersion, err := pack.GetMCVersion()
if err != nil {
return cli.NewExitError(err, 1)
}
done, modID, fileID, err := getFileIDsFromString(mod) done, modID, fileID, err := getFileIDsFromString(mod)
if err != nil { if err != nil {
fmt.Println(err) return cli.NewExitError(err, 1)
} }
if !done { if !done {
done, modID, err = getModIDFromString(mod) done, modID, err = getModIDFromString(mod)
if err != nil { if err != nil {
fmt.Println(err) return cli.NewExitError(err, 1)
} }
} }
// TODO: fallback to CurseMeta search // TODO: fallback to CurseMeta search
// TODO: how to do interactive choices? automatically assume version? ask mod from list? choose first? // TODO: how to do interactive choices? automatically assume version? ask mod from list? choose first?
fmt.Printf("ids: %d %d %v", modID, fileID, done) fmt.Printf("ids: %d %d %v\n", modID, fileID, done)
if done {
fmt.Println(mcVersion)
info, err := getModInfo(modID)
fmt.Println(err)
fmt.Println(info)
_ = index
}
return nil return nil
} }

View File

@ -1,4 +1,5 @@
package curseforge package curseforge
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
@ -7,6 +8,7 @@ import (
"io" "io"
"net/http" "net/http"
"strconv" "strconv"
"strings"
"time" "time"
) )
@ -106,13 +108,13 @@ const (
// modInfo is a subset of the deserialised JSON response from the Staging CurseMeta API for mods (addons) // modInfo is a subset of the deserialised JSON response from the Staging CurseMeta API for mods (addons)
type modInfo struct { type modInfo struct {
Name string `json:"name"` Name string `json:"name"`
Slug string `json:"slug"` Slug string `json:"slug"`
ID int `json:"id"` ID int `json:"id"`
LatestFiles []modFile `json:"latestFiles"` LatestFiles []modFileInfo `json:"latestFiles"`
GameVersionLatestFiles []struct { GameVersionLatestFiles []struct {
// TODO: check how twitch launcher chooses which one to use, when you are on beta/alpha channel?! // TODO: check how twitch launcher chooses which one to use, when you are on beta/alpha channel?!
// or does it not have the concept of channels?! // or does it not have the concept of release channels?!
GameVersion string `json:"gameVersion"` GameVersion string `json:"gameVersion"`
ID int `json:"projectFileId"` ID int `json:"projectFileId"`
Name string `json:"projectFileName"` Name string `json:"projectFileName"`
@ -120,7 +122,7 @@ type modInfo struct {
} `json:"gameVersionLatestFiles"` } `json:"gameVersionLatestFiles"`
} }
func getModInfo(modid int) (modInfo, error) { func getModInfo(modID int) (modInfo, error) {
// Uses the Staging CurseMeta api // Uses the Staging CurseMeta api
var response struct { var response struct {
modInfo modInfo
@ -128,7 +130,7 @@ func getModInfo(modid int) (modInfo, error) {
} }
client := &http.Client{} client := &http.Client{}
idStr := strconv.Itoa(modid) idStr := strconv.Itoa(modID)
req, err := http.NewRequest("GET", "https://staging_cursemeta.dries007.net/api/v3/direct/addon/"+idStr, nil) req, err := http.NewRequest("GET", "https://staging_cursemeta.dries007.net/api/v3/direct/addon/"+idStr, nil)
if err != nil { if err != nil {
@ -153,25 +155,85 @@ func getModInfo(modid int) (modInfo, error) {
return modInfo{}, fmt.Errorf("Error requesting mod metadata: %s", response.Description) return modInfo{}, fmt.Errorf("Error requesting mod metadata: %s", response.Description)
} }
if response.ID != modid { if response.ID != modID {
return modInfo{}, fmt.Errorf("Unexpected addon ID in CurseForge response: %d/%d", modid, response.ID) return modInfo{}, fmt.Errorf("Unexpected addon ID in CurseForge response: %d/%d", modID, response.ID)
} }
return response.modInfo, nil return response.modInfo, nil
} }
type modFile struct { const cfDateFormatString = "2006-01-02T15:04:05.999"
ID int `json:"id"`
FileName string `json:"fileNameOnDisk"` type cfDateFormat struct {
FriendlyName string `json:"fileName"` time.Time
Date time.Time `json:"fileDate"` }
Length int `json:"fileLength"`
FileType int `json:"releaseType"` func (f *cfDateFormat) UnmarshalJSON(input []byte) error {
trimmed := strings.Trim(string(input), `"`)
time, err := time.Parse(cfDateFormatString, trimmed)
if err != nil {
return err
}
f.Time = time
return nil
}
// modFileInfo is a subset of the deserialised JSON response from the Staging CurseMeta API for mod files
type modFileInfo struct {
ID int `json:"id"`
FileName string `json:"fileNameOnDisk"`
FriendlyName string `json:"fileName"`
Date cfDateFormat `json:"fileDate"`
Length int `json:"fileLength"`
FileType int `json:"releaseType"`
// fileStatus? means latest/preferred? // fileStatus? means latest/preferred?
GameVersions []string `json:"gameVersion"` GameVersions []string `json:"gameVersion"`
Fingerprint int `json:"packageFingerprint"`
Dependencies []struct { Dependencies []struct {
ModID int `json:"addonId"` ModID int `json:"addonId"`
Type int `json:"type"` Type int `json:"type"`
} `json:"dependencies"` } `json:"dependencies"`
} }
func getFileInfo(modID int, fileID int) (modFileInfo, error) {
// Uses the Staging CurseMeta api
var response struct {
modFileInfo
curseMetaError
}
client := &http.Client{}
modIDStr := strconv.Itoa(modID)
fileIDStr := strconv.Itoa(fileID)
req, err := http.NewRequest("GET", "https://staging_cursemeta.dries007.net/api/v3/direct/addon/"+modIDStr+"/file/"+fileIDStr, nil)
if err != nil {
return modFileInfo{}, err
}
// TODO: make this configurable application-wide
req.Header.Set("User-Agent", "comp500/packwiz client")
req.Header.Set("Accept", "application/json")
resp, err := client.Do(req)
if err != nil {
return modFileInfo{}, err
}
err = json.NewDecoder(resp.Body).Decode(&response)
if err != nil && err != io.EOF {
return modFileInfo{}, err
}
if response.Error {
return modFileInfo{}, fmt.Errorf("Error requesting mod file metadata: %s", response.Description)
}
if response.ID != fileID {
return modFileInfo{}, fmt.Errorf("Unexpected file ID in CurseForge response: %d/%d", modID, response.ID)
}
return response.modFileInfo, nil
}