mirror of
https://github.com/packwiz/packwiz.git
synced 2025-04-19 21:16:30 +02:00
Modrinth: Parse non-mod and CDN URLs, bring more in line with CF impl
This commit is contained in:
parent
a25a651748
commit
8937960d52
@ -9,7 +9,6 @@ import (
|
|||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/packwiz/packwiz/core"
|
"github.com/packwiz/packwiz/core"
|
||||||
@ -17,14 +16,11 @@ import (
|
|||||||
"gopkg.in/dixonwille/wmenu.v4"
|
"gopkg.in/dixonwille/wmenu.v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
var modSiteRegex = regexp.MustCompile("modrinth\\.com/mod/([^/]+)/?.*$")
|
|
||||||
var versionSiteRegex = regexp.MustCompile("modrinth\\.com/mod/([^/]+)/version/([^/]+)/?$")
|
|
||||||
|
|
||||||
// installCmd represents the install command
|
// installCmd represents the install command
|
||||||
var installCmd = &cobra.Command{
|
var installCmd = &cobra.Command{
|
||||||
Use: "install [mod]",
|
Use: "add [URL|slug|search]",
|
||||||
Short: "Install a mod from a modrinth URL, slug, ID or search",
|
Short: "Add a project from a Modrinth URL, slug/project ID or search",
|
||||||
Aliases: []string{"add", "get"},
|
Aliases: []string{"install", "get"},
|
||||||
Args: cobra.ArbitraryArgs,
|
Args: cobra.ArbitraryArgs,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
pack, err := core.LoadPack()
|
pack, err := core.LoadPack()
|
||||||
@ -39,98 +35,118 @@ var installCmd = &cobra.Command{
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(args) == 0 || len(args[0]) == 0 {
|
// If project/version IDs/version file name is provided in command line, use those
|
||||||
fmt.Println("You must specify a mod.")
|
var projectID, versionID, versionFilename string
|
||||||
|
if projectIDFlag != "" {
|
||||||
|
projectID = projectIDFlag
|
||||||
|
}
|
||||||
|
if versionIDFlag != "" {
|
||||||
|
versionID = versionIDFlag
|
||||||
|
}
|
||||||
|
if versionFilenameFlag != "" {
|
||||||
|
versionFilename = versionFilenameFlag
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len(args) == 0 || len(args[0]) == 0) && projectID == "" {
|
||||||
|
fmt.Println("You must specify a project; with the ID flags, or by passing a URL, slug or search term directly.")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there are more than 1 argument, go straight to searching - URLs/Slugs should not have spaces!
|
// Try interpreting the argument as a slug/project ID, or project/version/CDN URL
|
||||||
if len(args) > 1 {
|
var version string
|
||||||
err = installViaSearch(strings.Join(args, " "), pack, &index)
|
parsedSlug, err := parseSlugOrUrl(args[0], &projectID, &version, &versionID, &versionFilename)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to parse URL: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if version != "" && versionID == "" {
|
||||||
|
// TODO: resolve version (could be an ID, could be a version number) into ID
|
||||||
|
versionID = version
|
||||||
|
}
|
||||||
|
|
||||||
|
// Got version ID; install using this ID
|
||||||
|
if versionID != "" {
|
||||||
|
err = installVersionById(versionID, versionFilename, pack, &index)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed installing mod: %s\n", err)
|
fmt.Printf("Failed to add project: %s\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//Try interpreting the arg as a version url
|
// Look up project ID
|
||||||
matches := versionSiteRegex.FindStringSubmatch(args[0])
|
if projectID != "" {
|
||||||
if matches != nil && len(matches) == 3 {
|
// Modrinth transparently handles slugs/project IDs in their API; we don't have to detect which one it is.
|
||||||
err = installVersionById(matches[2], pack, &index)
|
var project *modrinthApi.Project
|
||||||
if err != nil {
|
project, err = mrDefaultClient.Projects.Get(projectID)
|
||||||
fmt.Printf("Failed installing mod: %s\n", err)
|
if err == nil {
|
||||||
os.Exit(1)
|
// We found a project with that id/slug
|
||||||
}
|
err = installProject(project, versionFilename, pack, &index)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//Try interpreting the arg as a modId or slug.
|
|
||||||
//Modrinth transparently handles slugs/mod ids in their api; we don't have to detect which one it is.
|
|
||||||
var modStr string
|
|
||||||
|
|
||||||
//Try to see if it's a site, if extract the id/slug from the url.
|
|
||||||
//Otherwise, interpret the arg as a id/slug straight up
|
|
||||||
matches = modSiteRegex.FindStringSubmatch(args[0])
|
|
||||||
if matches != nil && len(matches) == 2 {
|
|
||||||
modStr = matches[1]
|
|
||||||
} else {
|
|
||||||
modStr = args[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
mod, err := mrDefaultClient.Projects.Get(modStr)
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
//We found a mod with that id/slug
|
|
||||||
err = installMod(mod, pack, &index)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Failed installing mod: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
//This wasn't a valid modid/slug, try to search for it instead:
|
|
||||||
//Don't bother to search if it looks like a url though
|
|
||||||
if matches == nil {
|
|
||||||
err = installViaSearch(args[0], pack, &index)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed installing mod: %s\n", err)
|
fmt.Printf("Failed to add project: %s\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
} else {
|
return
|
||||||
fmt.Printf("Failed installing mod: %s\n", err)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arguments weren't a valid slug/project ID, try to search for it instead (if it was not parsed as a URL)
|
||||||
|
if projectID == "" || parsedSlug {
|
||||||
|
err = installViaSearch(strings.Join(args, " "), versionFilename, !parsedSlug, pack, &index)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to add project: %s\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Failed to add project: %s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func installViaSearch(query string, pack core.Pack, index *core.Index) error {
|
func installVersionById(versionId string, versionFilename string, pack core.Pack, index *core.Index) error {
|
||||||
|
version, err := mrDefaultClient.Versions.Get(versionId)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to fetch version %s: %v", versionId, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
project, err := mrDefaultClient.Projects.Get(*version.ProjectID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to fetch project %s: %v", *version.ProjectID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return installVersion(project, version, versionFilename, pack, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
func installViaSearch(query string, versionFilename string, autoAcceptFirst bool, pack core.Pack, index *core.Index) error {
|
||||||
mcVersion, err := pack.GetMCVersion()
|
mcVersion, err := pack.GetMCVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
results, err := getModIdsViaSearch(query, append([]string{mcVersion}, viper.GetStringSlice("acceptable-game-versions")...))
|
fmt.Println("Searching Modrinth...")
|
||||||
|
|
||||||
|
results, err := getProjectIdsViaSearch(query, append([]string{mcVersion}, viper.GetStringSlice("acceptable-game-versions")...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(results) == 0 {
|
if len(results) == 0 {
|
||||||
return errors.New("no results found")
|
return errors.New("no projects found")
|
||||||
}
|
}
|
||||||
|
|
||||||
if viper.GetBool("non-interactive") || len(results) == 1 {
|
if viper.GetBool("non-interactive") || (len(results) == 1 && autoAcceptFirst) {
|
||||||
//Install the first mod
|
// Install the first project found
|
||||||
mod, err := mrDefaultClient.Projects.Get(*results[0].ProjectID)
|
project, err := mrDefaultClient.Projects.Get(*results[0].ProjectID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return installMod(mod, pack, index)
|
return installProject(project, versionFilename, pack, index)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Create menu for the user to choose the correct mod
|
// Create menu for the user to choose the correct project
|
||||||
menu := wmenu.NewMenu("Choose a number:")
|
menu := wmenu.NewMenu("Choose a number:")
|
||||||
menu.Option("Cancel", nil, false, nil)
|
menu.Option("Cancel", nil, false, nil)
|
||||||
for i, v := range results {
|
for i, v := range results {
|
||||||
@ -140,39 +156,37 @@ func installViaSearch(query string, pack core.Pack, index *core.Index) error {
|
|||||||
|
|
||||||
menu.Action(func(menuRes []wmenu.Opt) error {
|
menu.Action(func(menuRes []wmenu.Opt) error {
|
||||||
if len(menuRes) != 1 || menuRes[0].Value == nil {
|
if len(menuRes) != 1 || menuRes[0].Value == nil {
|
||||||
return errors.New("Cancelled!")
|
return errors.New("project selection cancelled")
|
||||||
}
|
}
|
||||||
|
|
||||||
//Get the selected mod
|
// Get the selected project
|
||||||
selectedMod, ok := menuRes[0].Value.(*modrinthApi.SearchResult)
|
selectedProject, ok := menuRes[0].Value.(*modrinthApi.SearchResult)
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("error converting interface from wmenu")
|
return errors.New("error converting interface from wmenu")
|
||||||
}
|
}
|
||||||
|
|
||||||
//Install the selected mod
|
// Install the selected project
|
||||||
mod, err := mrDefaultClient.Projects.Get(*selectedMod.ProjectID)
|
project, err := mrDefaultClient.Projects.Get(*selectedProject.ProjectID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return installMod(mod, pack, index)
|
return installProject(project, versionFilename, pack, index)
|
||||||
})
|
})
|
||||||
|
|
||||||
return menu.Run()
|
return menu.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func installMod(mod *modrinthApi.Project, pack core.Pack, index *core.Index) error {
|
func installProject(project *modrinthApi.Project, versionFilename string, pack core.Pack, index *core.Index) error {
|
||||||
fmt.Printf("Found mod %s: '%s'.\n", *mod.Title, *mod.Description)
|
latestVersion, err := getLatestVersion(*project.ID, pack)
|
||||||
|
|
||||||
latestVersion, err := getLatestVersion(*mod.ID, pack)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get latest version: %v", err)
|
return fmt.Errorf("failed to get latest version: %v", err)
|
||||||
}
|
}
|
||||||
if latestVersion.ID == nil {
|
if latestVersion.ID == nil {
|
||||||
return errors.New("mod is not available for this Minecraft version (use the acceptable-game-versions option to accept more) or mod loader")
|
return errors.New("mod not available for the configured Minecraft version(s) (use the acceptable-game-versions option to accept more) or loader")
|
||||||
}
|
}
|
||||||
|
|
||||||
return installVersion(mod, latestVersion, pack, index)
|
return installVersion(project, latestVersion, versionFilename, pack, index)
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxCycles = 20
|
const maxCycles = 20
|
||||||
@ -183,11 +197,13 @@ type depMetadataStore struct {
|
|||||||
fileInfo *modrinthApi.File
|
fileInfo *modrinthApi.File
|
||||||
}
|
}
|
||||||
|
|
||||||
func installVersion(mod *modrinthApi.Project, version *modrinthApi.Version, pack core.Pack, index *core.Index) error {
|
func installVersion(project *modrinthApi.Project, version *modrinthApi.Version, versionFilename string, pack core.Pack, index *core.Index) error {
|
||||||
if len(version.Files) == 0 {
|
if len(version.Files) == 0 {
|
||||||
return errors.New("version doesn't have any files attached")
|
return errors.New("version doesn't have any files attached")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: explicitly reject modpacks
|
||||||
|
|
||||||
if len(version.Dependencies) > 0 {
|
if len(version.Dependencies) > 0 {
|
||||||
// TODO: could get installed version IDs, and compare to install the newest - i.e. preferring pinned versions over getting absolute latest?
|
// TODO: could get installed version IDs, and compare to install the newest - i.e. preferring pinned versions over getting absolute latest?
|
||||||
installedProjects := getInstalledProjectIDs(index)
|
installedProjects := getInstalledProjectIDs(index)
|
||||||
@ -275,7 +291,6 @@ func installVersion(mod *modrinthApi.Project, version *modrinthApi.Version, pack
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: add some way to allow users to pick which file to install?
|
|
||||||
var file = latestVersion.Files[0]
|
var file = latestVersion.Files[0]
|
||||||
// Prefer the primary file
|
// Prefer the primary file
|
||||||
for _, v := range latestVersion.Files {
|
for _, v := range latestVersion.Files {
|
||||||
@ -318,19 +333,16 @@ func installVersion(mod *modrinthApi.Project, version *modrinthApi.Version, pack
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: add some way to allow users to pick which file to install?
|
|
||||||
var file = version.Files[0]
|
var file = version.Files[0]
|
||||||
// Prefer the primary file
|
// Prefer the primary file
|
||||||
for _, v := range version.Files {
|
for _, v := range version.Files {
|
||||||
if *v.Primary {
|
if (*v.Primary) || (versionFilename != "" && versionFilename == *v.Filename) {
|
||||||
file = v
|
file = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Install the file
|
// Create the metadata file
|
||||||
fmt.Printf("Installing %s from version %s\n", *file.Filename, *version.VersionNumber)
|
err := createFileMeta(project, version, file, index)
|
||||||
|
|
||||||
err := createFileMeta(mod, version, file, index)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -347,24 +359,26 @@ func installVersion(mod *modrinthApi.Project, version *modrinthApi.Version, pack
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Project \"%s\" successfully added! (%s)\n", *project.Title, *file.Filename)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createFileMeta(mod *modrinthApi.Project, version *modrinthApi.Version, file *modrinthApi.File, index *core.Index) error {
|
func createFileMeta(project *modrinthApi.Project, version *modrinthApi.Version, file *modrinthApi.File, index *core.Index) error {
|
||||||
updateMap := make(map[string]map[string]interface{})
|
updateMap := make(map[string]map[string]interface{})
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
updateMap["modrinth"], err = mrUpdateData{
|
updateMap["modrinth"], err = mrUpdateData{
|
||||||
ModID: *mod.ID,
|
ProjectID: *project.ID,
|
||||||
InstalledVersion: *version.ID,
|
InstalledVersion: *version.ID,
|
||||||
}.ToMap()
|
}.ToMap()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
side := getSide(mod)
|
side := getSide(project)
|
||||||
if side == "" {
|
if side == "" {
|
||||||
return errors.New("version doesn't have a side that's supported. Server: " + *mod.ServerSide + " Client: " + *mod.ClientSide)
|
return errors.New("version doesn't have a side that's supported. Server: " + *project.ServerSide + " Client: " + *project.ClientSide)
|
||||||
}
|
}
|
||||||
|
|
||||||
algorithm, hash := getBestHash(file)
|
algorithm, hash := getBestHash(file)
|
||||||
@ -373,7 +387,7 @@ func createFileMeta(mod *modrinthApi.Project, version *modrinthApi.Version, file
|
|||||||
}
|
}
|
||||||
|
|
||||||
modMeta := core.Mod{
|
modMeta := core.Mod{
|
||||||
Name: *mod.Title,
|
Name: *project.Title,
|
||||||
FileName: *file.Filename,
|
FileName: *file.Filename,
|
||||||
Side: side,
|
Side: side,
|
||||||
Download: core.ModDownload{
|
Download: core.ModDownload{
|
||||||
@ -387,11 +401,12 @@ func createFileMeta(mod *modrinthApi.Project, version *modrinthApi.Version, file
|
|||||||
folder := viper.GetString("meta-folder")
|
folder := viper.GetString("meta-folder")
|
||||||
if folder == "" {
|
if folder == "" {
|
||||||
folder = "mods"
|
folder = "mods"
|
||||||
|
// TODO: vary based on project type
|
||||||
}
|
}
|
||||||
if mod.Slug != nil {
|
if project.Slug != nil {
|
||||||
path = modMeta.SetMetaPath(filepath.Join(viper.GetString("meta-folder-base"), folder, *mod.Slug+core.MetaExtension))
|
path = modMeta.SetMetaPath(filepath.Join(viper.GetString("meta-folder-base"), folder, *project.Slug+core.MetaExtension))
|
||||||
} else {
|
} else {
|
||||||
path = modMeta.SetMetaPath(filepath.Join(viper.GetString("meta-folder-base"), folder, core.SlugifyName(*mod.Title)+core.MetaExtension))
|
path = modMeta.SetMetaPath(filepath.Join(viper.GetString("meta-folder-base"), folder, core.SlugifyName(*project.Title)+core.MetaExtension))
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the file already exists, this will overwrite it!!!
|
// If the file already exists, this will overwrite it!!!
|
||||||
@ -406,20 +421,14 @@ func createFileMeta(mod *modrinthApi.Project, version *modrinthApi.Version, file
|
|||||||
return index.RefreshFileWithHash(path, format, hash, true)
|
return index.RefreshFileWithHash(path, format, hash, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func installVersionById(versionId string, pack core.Pack, index *core.Index) error {
|
var projectIDFlag string
|
||||||
version, err := mrDefaultClient.Versions.Get(versionId)
|
var versionIDFlag string
|
||||||
if err != nil {
|
var versionFilenameFlag string
|
||||||
return fmt.Errorf("failed to fetch version %s: %v", versionId, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
mod, err := mrDefaultClient.Projects.Get(*version.ProjectID)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to fetch mod %s: %v", *version.ProjectID, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return installVersion(mod, version, pack, index)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
modrinthCmd.AddCommand(installCmd)
|
modrinthCmd.AddCommand(installCmd)
|
||||||
|
|
||||||
|
installCmd.Flags().StringVar(&projectIDFlag, "project-id", "", "The Modrinth project ID to use")
|
||||||
|
installCmd.Flags().StringVar(&versionIDFlag, "version-id", "", "The Modrinth version ID to use")
|
||||||
|
installCmd.Flags().StringVar(&versionFilenameFlag, "version-filename", "", "The Modrinth version filename to use")
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,8 @@ import (
|
|||||||
"github.com/unascribed/FlexVer/go/flexver"
|
"github.com/unascribed/FlexVer/go/flexver"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var modrinthCmd = &cobra.Command{
|
var modrinthCmd = &cobra.Command{
|
||||||
@ -27,7 +29,7 @@ func init() {
|
|||||||
mrDefaultClient.UserAgent = core.UserAgent
|
mrDefaultClient.UserAgent = core.UserAgent
|
||||||
}
|
}
|
||||||
|
|
||||||
func getModIdsViaSearch(query string, versions []string) ([]*modrinthApi.SearchResult, error) {
|
func getProjectIdsViaSearch(query string, versions []string) ([]*modrinthApi.SearchResult, error) {
|
||||||
facets := make([]string, 0)
|
facets := make([]string, 0)
|
||||||
for _, v := range versions {
|
for _, v := range versions {
|
||||||
facets = append(facets, "versions:"+v)
|
facets = append(facets, "versions:"+v)
|
||||||
@ -36,9 +38,7 @@ func getModIdsViaSearch(query string, versions []string) ([]*modrinthApi.SearchR
|
|||||||
res, err := mrDefaultClient.Projects.Search(&modrinthApi.SearchOptions{
|
res, err := mrDefaultClient.Projects.Search(&modrinthApi.SearchOptions{
|
||||||
Limit: 5,
|
Limit: 5,
|
||||||
Index: "relevance",
|
Index: "relevance",
|
||||||
// Filters by mod since currently only mods and modpacks are supported by Modrinth
|
Query: query,
|
||||||
Facets: [][]string{facets, {"project_type:mod"}},
|
|
||||||
Query: query,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -47,16 +47,66 @@ func getModIdsViaSearch(query string, versions []string) ([]*modrinthApi.SearchR
|
|||||||
return res.Hits, nil
|
return res.Hits, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLatestVersion(modID string, pack core.Pack) (*modrinthApi.Version, error) {
|
var urlRegexes = [...]*regexp.Regexp{
|
||||||
|
// Slug/version number regex from https://github.com/modrinth/labrinth/blob/1679a3f844497d756d0cf272c5374a5236eabd42/src/util/validate.rs#L8
|
||||||
|
regexp.MustCompile("^https?://modrinth\\.com/(?P<projectType>[^/]+)/(?P<slug>[a-zA-Z0-9!@$()`.+,_\"-]{3,64})(?:/version/(?P<version>[a-zA-Z0-9!@$()`.+,_\"-]{1,32}))?"),
|
||||||
|
// Version/project IDs are more restrictive: [a-zA-Z0-9]+ (base62)
|
||||||
|
regexp.MustCompile("^https?://cdn\\.modrinth\\.com/data/(?P<slug>[a-zA-Z0-9]+)/versions/(?P<versionID>[a-zA-Z0-9]+)/(?P<filename>[^/]+)$"),
|
||||||
|
regexp.MustCompile("^(?P<slug>[a-zA-Z0-9!@$()`.+,_\"-]{3,64})$"),
|
||||||
|
}
|
||||||
|
|
||||||
|
const slugRegexIdx = 2
|
||||||
|
|
||||||
|
var projectTypes = []string{
|
||||||
|
"mod", "plugin", "datapack", "shader", "resourcepack", "modpack",
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSlugOrUrl(input string, slug *string, version *string, versionID *string, filename *string) (parsedSlug bool, err error) {
|
||||||
|
for regexIdx, r := range urlRegexes {
|
||||||
|
matches := r.FindStringSubmatch(input)
|
||||||
|
if matches != nil {
|
||||||
|
if i := r.SubexpIndex("projectType"); i >= 0 {
|
||||||
|
if !slices.Contains(projectTypes, matches[i]) {
|
||||||
|
err = errors.New("unknown project type: " + matches[i])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i := r.SubexpIndex("slug"); i >= 0 {
|
||||||
|
*slug = matches[i]
|
||||||
|
}
|
||||||
|
if i := r.SubexpIndex("version"); i >= 0 {
|
||||||
|
*version = matches[i]
|
||||||
|
}
|
||||||
|
if i := r.SubexpIndex("versionID"); i >= 0 {
|
||||||
|
*versionID = matches[i]
|
||||||
|
}
|
||||||
|
if i := r.SubexpIndex("filename"); i >= 0 {
|
||||||
|
var parsed string
|
||||||
|
parsed, err = url.PathUnescape(matches[i])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
*filename = parsed
|
||||||
|
}
|
||||||
|
parsedSlug = regexIdx == slugRegexIdx
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLatestVersion(projectID string, pack core.Pack) (*modrinthApi.Version, error) {
|
||||||
mcVersion, err := pack.GetMCVersion()
|
mcVersion, err := pack.GetMCVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
gameVersions := append([]string{mcVersion}, viper.GetStringSlice("acceptable-game-versions")...)
|
gameVersions := append([]string{mcVersion}, viper.GetStringSlice("acceptable-game-versions")...)
|
||||||
|
|
||||||
result, err := mrDefaultClient.Versions.ListVersions(modID, modrinthApi.ListVersionsOptions{
|
result, err := mrDefaultClient.Versions.ListVersions(projectID, modrinthApi.ListVersionsOptions{
|
||||||
GameVersions: gameVersions,
|
GameVersions: gameVersions,
|
||||||
Loaders: pack.GetLoaders(),
|
Loaders: pack.GetLoaders(),
|
||||||
|
// TODO: change based on project type? or just add iris/optifine/datapack/vanilla/minecraft as default loaders
|
||||||
|
// TODO: add "datapack" as a loader *if* a path to store datapacks in is configured?
|
||||||
})
|
})
|
||||||
|
|
||||||
if len(result) == 0 {
|
if len(result) == 0 {
|
||||||
@ -75,7 +125,8 @@ func getLatestVersion(modID string, pack core.Pack) (*modrinthApi.Version, error
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
//Semver is equal, compare date instead
|
// FlexVer comparison is equal, compare date instead
|
||||||
|
// TODO: flag to force comparing by date?
|
||||||
if v.DatePublished.After(*latestValidVersion.DatePublished) {
|
if v.DatePublished.After(*latestValidVersion.DatePublished) {
|
||||||
latestValidVersion = v
|
latestValidVersion = v
|
||||||
}
|
}
|
||||||
@ -143,8 +194,8 @@ func getInstalledProjectIDs(index *core.Index) []string {
|
|||||||
if ok {
|
if ok {
|
||||||
updateData, ok := data.(mrUpdateData)
|
updateData, ok := data.(mrUpdateData)
|
||||||
if ok {
|
if ok {
|
||||||
if len(updateData.ModID) > 0 {
|
if len(updateData.ProjectID) > 0 {
|
||||||
installedProjects = append(installedProjects, updateData.ModID)
|
installedProjects = append(installedProjects, updateData.ProjectID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type mrUpdateData struct {
|
type mrUpdateData struct {
|
||||||
ModID string `mapstructure:"mod-id"`
|
// TODO(format): change to "project-id"
|
||||||
|
ProjectID string `mapstructure:"mod-id"`
|
||||||
|
// TODO(format): change to "version-id"
|
||||||
InstalledVersion string `mapstructure:"version"`
|
InstalledVersion string `mapstructure:"version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,8 +31,8 @@ func (u mrUpdater) ParseUpdate(updateUnparsed map[string]interface{}) (interface
|
|||||||
}
|
}
|
||||||
|
|
||||||
type cachedStateStore struct {
|
type cachedStateStore struct {
|
||||||
ModID string
|
ProjectID string
|
||||||
Version *modrinthApi.Version
|
Version *modrinthApi.Version
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u mrUpdater) CheckUpdate(mods []core.Mod, mcVersion string, pack core.Pack) ([]core.UpdateCheck, error) {
|
func (u mrUpdater) CheckUpdate(mods []core.Mod, mcVersion string, pack core.Pack) ([]core.UpdateCheck, error) {
|
||||||
@ -45,7 +47,7 @@ func (u mrUpdater) CheckUpdate(mods []core.Mod, mcVersion string, pack core.Pack
|
|||||||
|
|
||||||
data := rawData.(mrUpdateData)
|
data := rawData.(mrUpdateData)
|
||||||
|
|
||||||
newVersion, err := getLatestVersion(data.ModID, pack)
|
newVersion, err := getLatestVersion(data.ProjectID, pack)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
results[i] = core.UpdateCheck{Error: fmt.Errorf("failed to get latest version: %v", err)}
|
results[i] = core.UpdateCheck{Error: fmt.Errorf("failed to get latest version: %v", err)}
|
||||||
continue
|
continue
|
||||||
@ -72,7 +74,7 @@ func (u mrUpdater) CheckUpdate(mods []core.Mod, mcVersion string, pack core.Pack
|
|||||||
results[i] = core.UpdateCheck{
|
results[i] = core.UpdateCheck{
|
||||||
UpdateAvailable: true,
|
UpdateAvailable: true,
|
||||||
UpdateString: mod.FileName + " -> " + *newFilename,
|
UpdateString: mod.FileName + " -> " + *newFilename,
|
||||||
CachedState: cachedStateStore{data.ModID, newVersion},
|
CachedState: cachedStateStore{data.ProjectID, newVersion},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user