fix: apply some suggestions

This commit is contained in:
Tricked 2022-06-16 07:51:06 +02:00 committed by unilock
parent faec4f4738
commit 837b4db760
3 changed files with 186 additions and 362 deletions

View File

@ -1,6 +1,14 @@
package github
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"time"
"github.com/mitchellh/mapstructure"
"github.com/packwiz/packwiz/cmd"
"github.com/packwiz/packwiz/core"
"github.com/spf13/cobra"
@ -17,54 +25,163 @@ func init() {
core.Updaters["github"] = ghUpdater{}
}
func fetchRepo(slug string) (Repo, error) {
var repo Repo
res, err := http.Get(githubApiUrl + "repos/" + slug)
if err != nil {
return repo, err
}
defer res.Body.Close()
repoBody, err := ioutil.ReadAll(res.Body)
if err != nil {
return repo, err
}
err = json.Unmarshal(repoBody, &repo)
if err != nil {
return repo, err
}
return repo, nil
}
func fetchMod(slug string) (Mod, error) {
var mod Mod
repo, err := fetchRepo(slug)
if err != nil {
return mod, err
}
release, err := getLatestVersion(slug, "")
if err != nil {
return mod, err
}
mod = Mod{
ID: repo.Name,
Slug: slug,
Team: repo.Owner.Login,
Title: repo.Name,
Description: repo.Description,
Published: repo.CreatedAt,
Updated: release.CreatedAt,
License: repo.License,
ClientSide: "unknown",
ServerSide: "unknown",
Categories: repo.Topics,
}
if mod.ID == "" {
return mod, errors.New("invalid json whilst fetching mod: " + slug)
}
return mod, nil
}
type ModReleases struct {
URL string `json:"url"`
AssetsURL string `json:"assets_url"`
UploadURL string `json:"upload_url"`
HTMLURL string `json:"html_url"`
ID int `json:"id"`
Author struct {
Login string `json:"login"`
ID int `json:"id"`
NodeID string `json:"node_id"`
AvatarURL string `json:"avatar_url"`
GravatarID string `json:"gravatar_id"`
URL string `json:"url"`
HTMLURL string `json:"html_url"`
FollowersURL string `json:"followers_url"`
FollowingURL string `json:"following_url"`
GistsURL string `json:"gists_url"`
StarredURL string `json:"starred_url"`
SubscriptionsURL string `json:"subscriptions_url"`
OrganizationsURL string `json:"organizations_url"`
ReposURL string `json:"repos_url"`
EventsURL string `json:"events_url"`
ReceivedEventsURL string `json:"received_events_url"`
Type string `json:"type"`
SiteAdmin bool `json:"site_admin"`
} `json:"author"`
URL string `json:"url"`
NodeID string `json:"node_id"`
TagName string `json:"tag_name"`
TargetCommitish string `json:"target_commitish"` // The branch of the release
Name string `json:"name"`
Draft bool `json:"draft"`
Prerelease bool `json:"prerelease"`
CreatedAt string `json:"created_at"`
PublishedAt string `json:"published_at"`
Assets []Asset `json:"assets"`
TarballURL string `json:"tarball_url"`
ZipballURL string `json:"zipball_url"`
Body string `json:"body"`
Reactions struct {
URL string `json:"url"`
TotalCount int `json:"total_count"`
Num1 int `json:"+1"`
Num10 int `json:"-1"`
Laugh int `json:"laugh"`
Hooray int `json:"hooray"`
Confused int `json:"confused"`
Heart int `json:"heart"`
Rocket int `json:"rocket"`
Eyes int `json:"eyes"`
} `json:"reactions"`
}
type Asset struct {
URL string `json:"url"`
Name string `json:"name"`
UpdatedAt time.Time `json:"updated_at"`
BrowserDownloadURL string `json:"browser_download_url"`
}
type Repo struct {
ID int `json:"id"`
Name string `json:"name"`
FullName string `json:"full_name"`
Owner struct {
Login string `json:"login"`
} `json:"owner"`
Description string `json:"description"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
License struct {
Key string `json:"key"`
Name string `json:"name"`
SpdxID string `json:"spdx_id"`
URL string `json:"url"`
NodeID string `json:"node_id"`
} `json:"license"`
Topics []string `json:"topics"`
}
func (u ghUpdateData) ToMap() (map[string]interface{}, error) {
newMap := make(map[string]interface{})
err := mapstructure.Decode(u, &newMap)
return newMap, err
}
type License struct {
Id string `json:"id"` //The license id of a mod, retrieved from the licenses get route
Name string `json:"name"` //The long for name of a license
Url string `json:"url"` //The URL to this license
}
type Mod struct {
ID string `json:"id"` //The ID of the mod, encoded as a base62 string
Slug string `json:"slug"` //The slug of a mod, used for vanity URLs
Team string `json:"team"` //The id of the team that has ownership of this mod
Title string `json:"title"` //The title or name of the mod
Description string `json:"description"` //A short description of the mod
// Body string `json:"body"` //A long form description of the mod.
// BodyUrl string `json:"body_url"` //DEPRECATED The link to the long description of the mod (Optional)
Published string `json:"published"` //The date at which the mod was first published
Updated string `json:"updated"` //The date at which the mod was updated
License struct {
Key string `json:"key"`
Name string `json:"name"`
SpdxID string `json:"spdx_id"`
URL string `json:"url"`
NodeID string `json:"node_id"`
} `json:"license"`
ClientSide string `json:"client_side"` //The support range for the client mod - required, optional, unsupported, or unknown
ServerSide string `json:"server_side"` //The support range for the server mod - required, optional, unsupported, or unknown
// Downloads int `json:"downloads"` //The total number of downloads the mod has
Categories []string `json:"categories"` //A list of the categories that the mod is in
Versions []string `json:"versions"` //A list of ids for versions of the mod
IconUrl string `json:"icon_url"` //The URL of the icon of the mod (Optional)
IssuesUrl string `json:"issues_url"` //An optional link to where to submit bugs or issues with the mod (Optional)
SourceUrl string `json:"source_url"` //An optional link to the source code for the mod (Optional)
WikiUrl string `json:"wiki_url"` //An optional link to the mod's wiki page or other relevant information (Optional)
DiscordUrl string `json:"discord_url"` //An optional link to the mod's discord (Optional)
}
func (u Asset) getSha1() (string, error) {
// TODO potentionally cache downloads to speed things up and avoid getting ratelimited by github!
mainHasher, err := core.GetHashImpl("sha1")
resp, err := http.Get(u.BrowserDownloadURL)
if err != nil {
return "", err
}
if resp.StatusCode == 404 {
return "", fmt.Errorf("Asset not found")
}
if resp.StatusCode != 200 {
return "", fmt.Errorf("Invalid response code: %d", resp.StatusCode)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
mainHasher.Write(body)
hash := mainHasher.Sum(nil)
return mainHasher.HashToString(hash), nil
}

View File

@ -8,19 +8,20 @@ import (
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/mitchellh/mapstructure"
"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 a mod from a github URL",
Short: "Install mods from github releases",
Aliases: []string{"add", "get"},
Args: cobra.ArbitraryArgs,
Run: func(cmd *cobra.Command, args []string) {
@ -35,23 +36,27 @@ var installCmd = &cobra.Command{
fmt.Println("You must specify a mod.")
os.Exit(1)
}
if strings.HasSuffix(args[0], "/") {
fmt.Println("Url cant have a leading slash!")
os.Exit(1)
}
slug := strings.Replace(args[0], "https://github.com/", "", 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
if len(strings.Split(args[0], "/")) == 1 {
//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]
}
if strings.Contains(slug, "/releases") {
slug = strings.Split(slug, "/releases")[0]
}
mod, err := fetchMod(slug)
if err != nil {
fmt.Println("Failed to get the mod ", err)
os.Exit(1)
}
installMod(mod, pack)
},
}
@ -62,80 +67,10 @@ func init() {
const githubApiUrl = "https://api.github.com/"
func fetchMod(slug string) (Mod, error) {
var modReleases []ModReleases
var mod Mod
resp, err := http.Get(githubApiUrl + "repos/" + slug + "/releases")
if err != nil {
return mod, err
}
if resp.StatusCode == 404 {
return mod, fmt.Errorf("mod not found (for URL %v)", githubApiUrl+"repos/"+slug+"/releases")
}
if resp.StatusCode != 200 {
return mod, 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 mod, err
}
err = json.Unmarshal(body, &modReleases)
if err != nil {
return mod, err
}
var repoData Repo
repoResp, err := http.Get(githubApiUrl + "repos/" + slug)
if err != nil {
return mod, err
}
defer repoResp.Body.Close()
repoBody, err := ioutil.ReadAll(repoResp.Body)
if err != nil {
return mod, err
}
err = json.Unmarshal(repoBody, &repoData)
if err != nil {
return mod, err
}
release := modReleases[0]
mod = Mod{
ID: repoData.Name,
Slug: slug,
Team: repoData.Owner.Login,
Title: repoData.Name,
Description: repoData.Description,
Published: repoData.CreatedAt,
Updated: release.CreatedAt,
License: repoData.License,
ClientSide: "unknown",
ServerSide: "unknown",
Categories: repoData.Topics,
}
if mod.ID == "" {
return mod, errors.New("invalid json whilst fetching mod: " + slug)
}
return mod, nil
}
func installMod(mod Mod, pack core.Pack) error {
fmt.Printf("Found mod %s: '%s'.\n", mod.Title, mod.Description)
fmt.Printf("Found repo %s: '%s'.\n", mod.Slug, mod.Description)
latestVersion, err := getLatestVersion(mod.Slug, pack, "")
latestVersion, err := getLatestVersion(mod.Slug, "")
if err != nil {
return fmt.Errorf("failed to get latest version: %v", err)
}
@ -146,7 +81,7 @@ func installMod(mod Mod, pack core.Pack) error {
return installVersion(mod, latestVersion, pack)
}
func getLatestVersion(slug string, pack core.Pack, branch string) (ModReleases, error) {
func getLatestVersion(slug string, branch string) (ModReleases, error) {
var modReleases []ModReleases
var release ModReleases
@ -206,7 +141,7 @@ func installVersion(mod Mod, version ModReleases, pack core.Pack) error {
updateMap := make(map[string]map[string]interface{})
updateMap["github"], err = ghUpdateData{
ModID: mod.Slug,
Slug: mod.Slug,
InstalledVersion: version.TagName,
Branch: version.TargetCommitish,
}.ToMap()
@ -264,231 +199,3 @@ func installVersion(mod Mod, version ModReleases, pack core.Pack) error {
}
return nil
}
func (u ghUpdateData) ToMap() (map[string]interface{}, error) {
newMap := make(map[string]interface{})
err := mapstructure.Decode(u, &newMap)
return newMap, err
}
type License struct {
Id string `json:"id"` //The license id of a mod, retrieved from the licenses get route
Name string `json:"name"` //The long for name of a license
Url string `json:"url"` //The URL to this license
}
type Mod struct {
ID string `json:"id"` //The ID of the mod, encoded as a base62 string
Slug string `json:"slug"` //The slug of a mod, used for vanity URLs
Team string `json:"team"` //The id of the team that has ownership of this mod
Title string `json:"title"` //The title or name of the mod
Description string `json:"description"` //A short description of the mod
// Body string `json:"body"` //A long form description of the mod.
// BodyUrl string `json:"body_url"` //DEPRECATED The link to the long description of the mod (Optional)
Published string `json:"published"` //The date at which the mod was first published
Updated string `json:"updated"` //The date at which the mod was updated
License struct {
Key string `json:"key"`
Name string `json:"name"`
SpdxID string `json:"spdx_id"`
URL string `json:"url"`
NodeID string `json:"node_id"`
} `json:"license"`
ClientSide string `json:"client_side"` //The support range for the client mod - required, optional, unsupported, or unknown
ServerSide string `json:"server_side"` //The support range for the server mod - required, optional, unsupported, or unknown
// Downloads int `json:"downloads"` //The total number of downloads the mod has
Categories []string `json:"categories"` //A list of the categories that the mod is in
Versions []string `json:"versions"` //A list of ids for versions of the mod
IconUrl string `json:"icon_url"` //The URL of the icon of the mod (Optional)
IssuesUrl string `json:"issues_url"` //An optional link to where to submit bugs or issues with the mod (Optional)
SourceUrl string `json:"source_url"` //An optional link to the source code for the mod (Optional)
WikiUrl string `json:"wiki_url"` //An optional link to the mod's wiki page or other relevant information (Optional)
DiscordUrl string `json:"discord_url"` //An optional link to the mod's discord (Optional)
}
type Asset struct {
URL string `json:"url"`
ID int `json:"id"`
NodeID string `json:"node_id"`
Name string `json:"name"`
Label interface{} `json:"label"`
Uploader struct {
Login string `json:"login"`
ID int `json:"id"`
NodeID string `json:"node_id"`
AvatarURL string `json:"avatar_url"`
GravatarID string `json:"gravatar_id"`
URL string `json:"url"`
HTMLURL string `json:"html_url"`
FollowersURL string `json:"followers_url"`
FollowingURL string `json:"following_url"`
GistsURL string `json:"gists_url"`
StarredURL string `json:"starred_url"`
SubscriptionsURL string `json:"subscriptions_url"`
OrganizationsURL string `json:"organizations_url"`
ReposURL string `json:"repos_url"`
EventsURL string `json:"events_url"`
ReceivedEventsURL string `json:"received_events_url"`
Type string `json:"type"`
SiteAdmin bool `json:"site_admin"`
} `json:"uploader"`
ContentType string `json:"content_type"`
State string `json:"state"`
Size int `json:"size"`
DownloadCount int `json:"download_count"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
BrowserDownloadURL string `json:"browser_download_url"`
}
func (u Asset) getSha1() (string, error) {
// TODO potentionally cache downloads to speed things up and avoid getting ratelimited by github!
mainHasher, err := core.GetHashImpl("sha1")
resp, err := http.Get(u.BrowserDownloadURL)
if err != nil {
return "", err
}
if resp.StatusCode == 404 {
return "", fmt.Errorf("Asset not found")
}
if resp.StatusCode != 200 {
return "", fmt.Errorf("Invalid response code: %d", resp.StatusCode)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
mainHasher.Write(body)
hash := mainHasher.Sum(nil)
return mainHasher.HashToString(hash), nil
}
type Repo struct {
ID int `json:"id"`
NodeID string `json:"node_id"`
Name string `json:"name"`
FullName string `json:"full_name"`
Private bool `json:"private"`
Owner struct {
Login string `json:"login"`
ID int `json:"id"`
NodeID string `json:"node_id"`
AvatarURL string `json:"avatar_url"`
GravatarID string `json:"gravatar_id"`
URL string `json:"url"`
HTMLURL string `json:"html_url"`
FollowersURL string `json:"followers_url"`
FollowingURL string `json:"following_url"`
GistsURL string `json:"gists_url"`
StarredURL string `json:"starred_url"`
SubscriptionsURL string `json:"subscriptions_url"`
OrganizationsURL string `json:"organizations_url"`
ReposURL string `json:"repos_url"`
EventsURL string `json:"events_url"`
ReceivedEventsURL string `json:"received_events_url"`
Type string `json:"type"`
SiteAdmin bool `json:"site_admin"`
} `json:"owner"`
HTMLURL string `json:"html_url"`
Description string `json:"description"`
Fork bool `json:"fork"`
URL string `json:"url"`
ForksURL string `json:"forks_url"`
KeysURL string `json:"keys_url"`
CollaboratorsURL string `json:"collaborators_url"`
TeamsURL string `json:"teams_url"`
HooksURL string `json:"hooks_url"`
IssueEventsURL string `json:"issue_events_url"`
EventsURL string `json:"events_url"`
AssigneesURL string `json:"assignees_url"`
BranchesURL string `json:"branches_url"`
TagsURL string `json:"tags_url"`
BlobsURL string `json:"blobs_url"`
GitTagsURL string `json:"git_tags_url"`
GitRefsURL string `json:"git_refs_url"`
TreesURL string `json:"trees_url"`
StatusesURL string `json:"statuses_url"`
LanguagesURL string `json:"languages_url"`
StargazersURL string `json:"stargazers_url"`
ContributorsURL string `json:"contributors_url"`
SubscribersURL string `json:"subscribers_url"`
SubscriptionURL string `json:"subscription_url"`
CommitsURL string `json:"commits_url"`
GitCommitsURL string `json:"git_commits_url"`
CommentsURL string `json:"comments_url"`
IssueCommentURL string `json:"issue_comment_url"`
ContentsURL string `json:"contents_url"`
CompareURL string `json:"compare_url"`
MergesURL string `json:"merges_url"`
ArchiveURL string `json:"archive_url"`
DownloadsURL string `json:"downloads_url"`
IssuesURL string `json:"issues_url"`
PullsURL string `json:"pulls_url"`
MilestonesURL string `json:"milestones_url"`
NotificationsURL string `json:"notifications_url"`
LabelsURL string `json:"labels_url"`
ReleasesURL string `json:"releases_url"`
DeploymentsURL string `json:"deployments_url"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
PushedAt string `json:"pushed_at"`
GitURL string `json:"git_url"`
SSHURL string `json:"ssh_url"`
CloneURL string `json:"clone_url"`
SvnURL string `json:"svn_url"`
Homepage string `json:"homepage"`
Size int `json:"size"`
StargazersCount int `json:"stargazers_count"`
WatchersCount int `json:"watchers_count"`
Language string `json:"language"`
HasIssues bool `json:"has_issues"`
HasProjects bool `json:"has_projects"`
HasDownloads bool `json:"has_downloads"`
HasWiki bool `json:"has_wiki"`
HasPages bool `json:"has_pages"`
ForksCount int `json:"forks_count"`
MirrorURL interface{} `json:"mirror_url"`
Archived bool `json:"archived"`
Disabled bool `json:"disabled"`
OpenIssuesCount int `json:"open_issues_count"`
License struct {
Key string `json:"key"`
Name string `json:"name"`
SpdxID string `json:"spdx_id"`
URL string `json:"url"`
NodeID string `json:"node_id"`
} `json:"license"`
AllowForking bool `json:"allow_forking"`
IsTemplate bool `json:"is_template"`
Topics []string `json:"topics"`
Visibility string `json:"visibility"`
Forks int `json:"forks"`
OpenIssues int `json:"open_issues"`
Watchers int `json:"watchers"`
DefaultBranch string `json:"default_branch"`
TempCloneToken interface{} `json:"temp_clone_token"`
Organization struct {
Login string `json:"login"`
ID int `json:"id"`
NodeID string `json:"node_id"`
AvatarURL string `json:"avatar_url"`
GravatarID string `json:"gravatar_id"`
URL string `json:"url"`
HTMLURL string `json:"html_url"`
FollowersURL string `json:"followers_url"`
FollowingURL string `json:"following_url"`
GistsURL string `json:"gists_url"`
StarredURL string `json:"starred_url"`
SubscriptionsURL string `json:"subscriptions_url"`
OrganizationsURL string `json:"organizations_url"`
ReposURL string `json:"repos_url"`
EventsURL string `json:"events_url"`
ReceivedEventsURL string `json:"received_events_url"`
Type string `json:"type"`
SiteAdmin bool `json:"site_admin"`
} `json:"organization"`
NetworkCount int `json:"network_count"`
SubscribersCount int `json:"subscribers_count"`
}

View File

@ -10,7 +10,7 @@ import (
)
type ghUpdateData struct {
ModID string `mapstructure:"mod-id"` // The slug of the repo but named modId for consistency reasons
Slug string `mapstructure:"slug"`
InstalledVersion string `mapstructure:"version"`
Branch string `mapstructure:"branch"`
}
@ -40,7 +40,7 @@ func (u ghUpdater) CheckUpdate(mods []core.Mod, mcVersion string, pack core.Pack
data := rawData.(ghUpdateData)
newVersion, err := getLatestVersion(data.ModID, pack, data.Branch)
newVersion, err := getLatestVersion(data.Slug, data.Branch)
if err != nil {
results[i] = core.UpdateCheck{Error: fmt.Errorf("failed to get latest version: %v", err)}
continue
@ -61,7 +61,7 @@ func (u ghUpdater) CheckUpdate(mods []core.Mod, mcVersion string, pack core.Pack
results[i] = core.UpdateCheck{
UpdateAvailable: true,
UpdateString: mod.FileName + " -> " + newFilename,
CachedState: cachedStateStore{data.ModID, newVersion},
CachedState: cachedStateStore{data.Slug, newVersion},
}
}
@ -91,7 +91,7 @@ func (u ghUpdater) DoUpdate(mods []*core.Mod, cachedState []interface{}) error {
HashFormat: "sha1",
Hash: hash,
}
mod.Update["github"]["version"] = version.ID
mod.Update["github"]["version"] = version.TagName
}
return nil