mirror of
https://github.com/packwiz/packwiz.git
synced 2025-04-19 13:06:30 +02:00
WIP curseforge integration
This commit is contained in:
parent
5f92843226
commit
857f692fc6
@ -1,8 +1,9 @@
|
|||||||
package core
|
package core
|
||||||
// UpdateParsers stores all the update parsers that packwiz can use. Add your own update systems to this map.
|
// UpdateParsers stores all the update parsers that packwiz can use. Add your own update systems to this map.
|
||||||
var UpdateParsers map[string]UpdateParser = make(map[string]UpdateParser)
|
var UpdateParsers = make(map[string]UpdateParser)
|
||||||
|
|
||||||
// UpdateParser takes an unparsed interface{}, and returns an Updater for a mod file
|
// UpdateParser takes an unparsed interface{} (as a map[string]interface{}), and returns an Updater for a mod file.
|
||||||
|
// This can be done using the mapstructure library or your own parsing methods.
|
||||||
type UpdateParser interface {
|
type UpdateParser interface {
|
||||||
ParseUpdate(interface{}) (Updater, error)
|
ParseUpdate(interface{}) (Updater, error)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package curseforge
|
package curseforge
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/comp500/packwiz/core"
|
"github.com/comp500/packwiz/core"
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
@ -29,11 +31,84 @@ func init() {
|
|||||||
})
|
})
|
||||||
core.UpdateParsers["curseforge"] = cfUpdateParser{}
|
core.UpdateParsers["curseforge"] = cfUpdateParser{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var fileIDRegexes = [...]*regexp.Regexp{
|
||||||
|
regexp.MustCompile("https?:\\/\\/minecraft\\.curseforge\\.com\\/projects\\/(.+)\\/files\\/(\\d+)"),
|
||||||
|
regexp.MustCompile("https?:\\/\\/(?:www\\.)?curseforge\\.com\\/minecraft\\/mc-mods\\/(.+)\\/download\\/(\\d+)"),
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFileIDsFromString(mod string) (bool, int, int, error) {
|
||||||
|
for _, v := range fileIDRegexes {
|
||||||
|
matches := v.FindStringSubmatch(mod)
|
||||||
|
if matches != nil && len(matches) == 3 {
|
||||||
|
modID, err := modIDFromSlug(matches[1])
|
||||||
|
fileID, err := strconv.Atoi(matches[2])
|
||||||
|
if err != nil {
|
||||||
|
return true, 0, 0, err
|
||||||
|
}
|
||||||
|
return true, modID, fileID, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, 0, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var modSlugRegexes = [...]*regexp.Regexp{
|
||||||
|
regexp.MustCompile("https?:\\/\\/minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)"),
|
||||||
|
regexp.MustCompile("https?:\\/\\/(?:www\\.)?curseforge\\.com\\/minecraft\\/mc-mods\\/([^\\/]+)"),
|
||||||
|
// Exact slug matcher
|
||||||
|
regexp.MustCompile("[a-z][\\da-z\\-]{0,127}"),
|
||||||
|
}
|
||||||
|
|
||||||
|
func getModIDFromString(mod string) (bool, int, error) {
|
||||||
|
// Check if it's just a number first
|
||||||
|
modID, err := strconv.Atoi(mod)
|
||||||
|
if err == nil && modID > 0 {
|
||||||
|
return true, modID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range modSlugRegexes {
|
||||||
|
matches := v.FindStringSubmatch(mod)
|
||||||
|
if matches != nil {
|
||||||
|
var slug string
|
||||||
|
if len(matches) == 2 {
|
||||||
|
slug = matches[1]
|
||||||
|
} else if len(matches) == 1 {
|
||||||
|
slug = matches[0]
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
modID, err := modIDFromSlug(slug)
|
||||||
|
if err != nil {
|
||||||
|
return true, 0, err
|
||||||
|
}
|
||||||
|
return true, modID, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
func cmdInstall(flags core.Flags, mod string) error {
|
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!")
|
//fmt.Println("Not implemented yet!")
|
||||||
|
|
||||||
|
done, modID, fileID, err := getFileIDsFromString(mod)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !done {
|
||||||
|
done, modID, err = getModIDFromString(mod)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: fallback to CurseMeta search
|
||||||
|
// TODO: how to do interactive choices? automatically assume version? ask mod from list? choose first?
|
||||||
|
|
||||||
|
fmt.Printf("ids: %d %d %v", modID, fileID, done)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,6 +122,8 @@ func (u cfUpdateParser) ParseUpdate(updateUnparsed interface{}) (core.Updater, e
|
|||||||
|
|
||||||
type cfUpdater struct {
|
type cfUpdater struct {
|
||||||
ProjectID int `mapstructure:"project-id"`
|
ProjectID int `mapstructure:"project-id"`
|
||||||
|
FileID int `mapstructure:"file-id"`
|
||||||
|
ReleaseChannel string `mapstructure:"release-channel"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u cfUpdater) DoUpdate(mod core.Mod) (bool, error) {
|
func (u cfUpdater) DoUpdate(mod core.Mod) (bool, error) {
|
||||||
|
86
curseforge/request.go
Normal file
86
curseforge/request.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package curseforge
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddonSlugRequest is sent to the CurseProxy GraphQL api to get the id from a slug
|
||||||
|
type AddonSlugRequest struct {
|
||||||
|
Query string `json:"query"`
|
||||||
|
Variables struct {
|
||||||
|
Slug string `json:"slug"`
|
||||||
|
} `json:"variables"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddonSlugResponse is received from the CurseProxy GraphQL api to get the id from a slug
|
||||||
|
type AddonSlugResponse struct {
|
||||||
|
Data struct {
|
||||||
|
Addons []struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
} `json:"addons"`
|
||||||
|
} `json:"data"`
|
||||||
|
Exception string `json:"exception"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Stacktrace []string `json:"stacktrace"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Most of this is shamelessly copied from my previous attempt at modpack management:
|
||||||
|
// https://github.com/comp500/modpack-editor/blob/master/query.go
|
||||||
|
func modIDFromSlug(slug string) (int, error) {
|
||||||
|
request := AddonSlugRequest{
|
||||||
|
Query: `
|
||||||
|
query getIDFromSlug($slug: String) {
|
||||||
|
{
|
||||||
|
addons(slug: $slug) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
request.Variables.Slug = slug
|
||||||
|
|
||||||
|
// Uses the curse.nikky.moe GraphQL api
|
||||||
|
var response AddonSlugResponse
|
||||||
|
client := &http.Client{}
|
||||||
|
|
||||||
|
requestBytes, err := json.Marshal(request)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", "https://curse.nikky.moe/graphql", bytes.NewBuffer(requestBytes))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: make this configurable application-wide
|
||||||
|
req.Header.Set("User-Agent", "comp500/packwiz client")
|
||||||
|
req.Header.Set("Accept", "application/json")
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&response)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(response.Exception) > 0 || len(response.Message) > 0 {
|
||||||
|
return 0, fmt.Errorf("Error requesting id for slug: %s", response.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(response.Data.Addons) < 1 {
|
||||||
|
return 0, errors.New("Addon not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Data.Addons[0].ID, nil
|
||||||
|
}
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user