1
0
mirror of https://github.com/packwiz/packwiz.git synced 2025-04-28 00:56:31 +02:00
2024-04-15 14:48:57 -04:00

202 lines
4.6 KiB
Go

package github
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/packwiz/packwiz/core"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var GithubRegex = regexp.MustCompile("https?://(?:www\\.)?github\\.com/([^/]+/[^/]+)")
// installCmd represents the install command
var installCmd = &cobra.Command{
Use: "install [mod]",
Short: "Install mods from github releases",
Aliases: []string{"add", "get"},
Args: cobra.ArbitraryArgs,
Run: func(cmd *cobra.Command, args []string) {
pack, err := core.LoadPack()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if len(args) == 0 || len(args[0]) == 0 {
fmt.Println("You must specify a mod.")
os.Exit(1)
}
//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 slug 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 := GithubRegex.FindStringSubmatch(args[0])
if matches != nil && len(matches) == 2 {
slug = matches[1]
} else {
slug = args[0]
}
mod, err := fetchMod(slug)
if err != nil {
fmt.Println("Failed to get the mod ", err)
os.Exit(1)
}
installMod(mod, pack)
},
}
func init() {
githubCmd.AddCommand(installCmd)
}
const githubApiUrl = "https://api.github.com/"
func installMod(mod Mod, pack core.Pack) error {
fmt.Printf("Found repo %s: '%s'.\n", mod.Slug, mod.Description)
latestVersion, err := getLatestVersion(mod.Slug, "")
if err != nil {
return fmt.Errorf("failed to get latest version: %v", err)
}
if latestVersion.URL == "" {
return errors.New("mod is not available for this Minecraft version (use the acceptable-game-versions option to accept more) or mod loader")
}
return installVersion(mod, latestVersion, pack)
}
func getLatestVersion(slug string, branch string) (ModReleases, error) {
var modReleases []ModReleases
var release ModReleases
resp, err := http.Get(githubApiUrl + "repos/" + slug + "/releases")
if err != nil {
return release, err
}
if resp.StatusCode == 404 {
return release, fmt.Errorf("mod not found (for URL %v)", githubApiUrl+"repos/"+slug+"/releases")
}
if resp.StatusCode != 200 {
return release, fmt.Errorf("invalid response status %v for URL %v", resp.Status, githubApiUrl+"repos/"+slug+"/releases")
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return release, err
}
err = json.Unmarshal(body, &modReleases)
if err != nil {
return release, err
}
for _, r := range modReleases {
if r.TargetCommitish == branch {
return r, nil
}
}
return modReleases[0], nil
}
func installVersion(mod Mod, version ModReleases, pack core.Pack) error {
var files = version.Assets
if len(files) == 0 {
return errors.New("version doesn't have any files attached")
}
// TODO: add some way to allow users to pick which file to install?
var file = files[0]
for _, v := range version.Assets {
if strings.HasSuffix(v.Name, ".jar") {
file = v
}
}
//Install the file
fmt.Printf("Installing %s from version %s\n", file.URL, version.Name)
index, err := pack.LoadIndex()
if err != nil {
return err
}
updateMap := make(map[string]map[string]interface{})
updateMap["github"], err = ghUpdateData{
Slug: mod.Slug,
InstalledVersion: version.TagName,
Branch: version.TargetCommitish,
}.ToMap()
if err != nil {
return err
}
hash, error := file.getSha1()
if error != nil || hash == "" {
return errors.New("file doesn't have a hash")
}
modMeta := core.Mod{
Name: mod.Title,
FileName: file.Name,
Side: "unknown",
Download: core.ModDownload{
URL: file.BrowserDownloadURL,
HashFormat: "sha1",
Hash: hash,
},
Update: updateMap,
}
var path string
folder := viper.GetString("meta-folder")
if folder == "" {
folder = "mods"
}
path = modMeta.SetMetaPath(filepath.Join(viper.GetString("meta-folder-base"), folder, mod.Title+core.MetaExtension))
// If the file already exists, this will overwrite it!!!
// TODO: Should this be improved?
// Current strategy is to go ahead and do stuff without asking, with the assumption that you are using
// VCS anyway.
format, hash, err := modMeta.Write()
if err != nil {
return err
}
err = index.RefreshFileWithHash(path, format, hash, true)
if err != nil {
return err
}
err = index.Write()
if err != nil {
return err
}
err = pack.UpdateIndexHash()
if err != nil {
return err
}
err = pack.Write()
if err != nil {
return err
}
return nil
}