From ffe68ec75769fbb0b16ee556da945724fff6b84d Mon Sep 17 00:00:00 2001 From: TechnoStrife Date: Sun, 22 Nov 2020 18:26:22 +0300 Subject: [PATCH] Add dependency check when adding or updating a mod - Add 'members intent is disabled' message --- .gitignore | 1 + commands/admin/mod.go | 108 ++++++++++++++++++++++++++++++++++++++++-- discord/cache.go | 7 ++- support/semver.go | 11 +++++ support/utils.go | 25 ++++++++++ 5 files changed, 148 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index a035981..f2bdbdd 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ release/** .env config.json FactoCord-3.0 +FactoCord-3.0.exe FactoCord3 FactoCord error.log diff --git a/commands/admin/mod.go b/commands/admin/mod.go index d6391d0..41cb71d 100644 --- a/commands/admin/mod.go +++ b/commands/admin/mod.go @@ -12,6 +12,7 @@ import ( "net/http" "os" "path" + "regexp" "strings" "github.com/maxsupermanhd/FactoCord-3.0/support" @@ -121,8 +122,10 @@ type modRelease struct { DownloadUrl string `json:"download_url"` SHA1 string FileName string `json:"file_name"` + Name string Version string InfoJson struct { + Dependencies []string FactorioVersion string `json:"factorio_version"` } `json:"info_json"` } @@ -304,6 +307,7 @@ func modsAdd(s *discordgo.Session, mods *ModJSON, modDescriptions *[]modDescript toDownload = append(toDownload, release) inserted := mods.sortedInsert(desc.ModEntry()) if inserted { + release.Name = desc.name addedMods.Append(desc.String()) } else { alreadyAdded.Append(desc.String()) @@ -327,6 +331,7 @@ func modsAdd(s *discordgo.Session, mods *ModJSON, modDescriptions *[]modDescript res += alreadyAdded.RenderNotEmpty() res += userErrors.RenderNotEmpty() } + res += checkDependencies(toDownload, files) if support.Config.ModPortalToken == "" { res += "\n**No token to download mods**" @@ -428,10 +433,12 @@ func modsUpdate(s *discordgo.Session, mods *ModJSON, modDescriptions *[]modDescr for _, x := range toDownload { downloadQueue <- x } + + dependencies := checkDependencies(toDownload, files) if updateAll { - return updatedMods.Render() + alreadyUpdated.RenderNotEmpty() + userErrors.RenderNotEmpty() + return updatedMods.Render() + alreadyUpdated.RenderNotEmpty() + userErrors.RenderNotEmpty() + dependencies } else { - return updatedMods.Render() + userErrors.RenderNotEmpty() + return updatedMods.Render() + userErrors.RenderNotEmpty() + dependencies } } @@ -575,7 +582,7 @@ func removeModFiles(files *modsFilesT, modname string) (found []modDescriptionT, } func checkModPortal(desc *modDescriptionT, factorioVersion string) (*modRelease, string, error) { - resp, err := http.Get(fmt.Sprintf("https://mods.factorio.com/api/mods/%s", desc.name)) + resp, err := http.Get(fmt.Sprintf("https://mods.factorio.com/api/mods/%s/full", desc.name)) if err != nil { return nil, "", err } @@ -619,6 +626,101 @@ func checkModPortal(desc *modDescriptionT, factorioVersion string) (*modRelease, } } +var dependencyRegexp = regexp.MustCompile(`^(!|\?|\(\?\))? ?([A-Za-z0-9\-_ ]+)( ([<>]?=?) (\d+\.\d+\.\d+))?$`) + +func checkDependencies(newMods []*modRelease, files *modsFilesT) string { + installed := map[string][]*support.SemanticVersionT{} + for _, mod := range newMods { + installed[mod.Name] = append(installed[mod.Name], support.SemanticVersionPanic(mod.Version)) + } + for name, modFiles := range files.versions { + for _, file := range modFiles { + installed[name] = append(installed[name], &file.version) + } + } + + missingModsList := support.DefaultTextList("\n**Missing dependencies:**") + incompatibleModsList := support.DefaultTextList("\n**Incompatible Mods:**") + wrongVersionMods := support.DefaultTextList("\n**Wrong version is installed:**") + var addMods []string + var updateMods []string + + for _, mod := range newMods { + for _, dependency := range mod.InfoJson.Dependencies { + match := dependencyRegexp.FindStringSubmatch(dependency) + prefix := match[1] + name := strings.TrimSpace(match[2]) + compare := match[4] + depVersion := match[5] + if name == "base" { + continue // TODO check factorio version + } + if prefix == "?" || prefix == "(?)" { + continue // optional dependency + } + versions, found := installed[name] + if prefix == "!" { + if found { + incompatibleModsList.Append(fmt.Sprintf("%s is incompatible with %s", mod.Name, name)) + } + continue + } + if !found { + missingModsList.Append(dependency) + if compare == "" || strings.Contains(compare, ">") { + addMods = append(addMods, support.QuoteSpace(name)) + } else if compare != "<" { + addMods = append(addMods, support.QuoteSpace(fmt.Sprintf("%s==%s", name, depVersion))) + } + continue + } + if compare == "" { + continue + } + matched := false + for _, modVersion := range versions { + if support.CompareOp(modVersion.Compare(support.SemanticVersionPanic(depVersion)), compare) { + matched = true + break + } + } + if !matched { + if compare == "" || strings.Contains(compare, ">") { + updateMods = append(updateMods, support.QuoteSpace(name)) + } else if compare != "<" { + updateMods = append(updateMods, support.QuoteSpace(fmt.Sprintf("%s==%s", name, depVersion))) + } + versionsStr := "" + for _, version := range versions { // fucking golang + if versionsStr != "" { + versionsStr += ", " + } + versionsStr += version.Full + } + wrongVersionMods.Append(fmt.Sprintf( + "%s (%s) doesn't satisfy the dependency condition '%s %s' of %s", + name, versionsStr, compare, depVersion, mod.Name, + )) + } + } + } + res := missingModsList.RenderNotEmpty() + incompatibleModsList.RenderNotEmpty() + wrongVersionMods.RenderNotEmpty() + if len(addMods) != 0 || len(updateMods) != 0 { + if len(addMods) != 0 && len(updateMods) != 0 { + res += "\nIt is recommended to run these commands:" + } else { + res += "\nIt is recommended to run this command:" + } + } + if len(addMods) != 0 { + res += support.FormatUsage("\n `$mod add " + strings.Join(addMods, " ") + "`") + } + if len(updateMods) != 0 { + res += support.FormatUsage("\n `$mod update " + strings.Join(updateMods, " ") + "`") + } + return res +} + func compareFactorioVersions(modVersion, factorioVersion string) bool { if modVersion == "0.18" { return factorioVersion == "0.18" || factorioVersion == "1.0" diff --git a/discord/cache.go b/discord/cache.go index ebebe38..65db0cf 100644 --- a/discord/cache.go +++ b/discord/cache.go @@ -30,7 +30,12 @@ func CacheDiscordMembers(session *discordgo.Session) (count int) { for { members, err := session.GuildMembers(support.GuildID, after, limit) if err != nil { - support.Panik(err, "... when requesting members") + if resterr, ok := err.(*discordgo.RESTError); ok && resterr.Message.Code == 50001 { // Missing access + fmt.Println("You need to enable \"members intent\" for the bot " + + "if you want to use nickname colors and pings. https://i.imgur.com/PdHNJFm.png") + } else { + support.Panik(err, "... when requesting members") + } return } for _, member := range members { diff --git a/support/semver.go b/support/semver.go index 2bdf245..f2ed9fb 100644 --- a/support/semver.go +++ b/support/semver.go @@ -35,6 +35,17 @@ func SemanticVersionPanic(s string) *SemanticVersionT { return v } +func (v *SemanticVersionT) Compare(v2 *SemanticVersionT) int { + if v.Equal(v2) { + return 0 + } + if v.NewerThan(v2) { + return 1 + } else { + return -1 + } +} + func (v *SemanticVersionT) Equal(v2 *SemanticVersionT) bool { return v.Full == "" || v2.Full == "" || v.Full == v2.Full } diff --git a/support/utils.go b/support/utils.go index 4853544..abc3787 100644 --- a/support/utils.go +++ b/support/utils.go @@ -153,6 +153,13 @@ func QuoteSplit(s string, quote string) ([]string, bool) { return res, mismatched } +func QuoteSpace(s string) string { + if strings.ContainsRune(s, ' ') { + s = "\"" + s + "\"" + } + return s +} + func Unique(strs []string) []string { s := make([]string, len(strs)) copy(s, strs) @@ -334,3 +341,21 @@ type CommandDoc struct { Doc string Subcommands []CommandDoc } + +func CompareOp(cmp int, op string) bool { + switch op { + case "=", "==": + return cmp == 0 + case ">": + return cmp == 1 + case ">=": + return cmp >= 0 + case "<": + return cmp == -1 + case "<=": + return cmp <= 0 + } + err := fmt.Errorf("`%s` is not a comparison operator", op) + Panik(err, "") + panic(err) +}