diff --git a/gamebanana/download.go b/gamebanana/download.go new file mode 100644 index 0000000..c93dcae --- /dev/null +++ b/gamebanana/download.go @@ -0,0 +1,143 @@ +package gamebanana + +import ( + "errors" + "fmt" + "io" + "io/fs" + "net/http" + "os" + "path" + "path/filepath" + "strings" + + "github.com/charmbracelet/log" + "github.com/gen2brain/go-unarr" +) + +var dlurl string + +func DownloadGameBananaMod(id, outdir string) (error) { + modData, err := GetGameBananaMod(id) + if err != nil { + return err + } + + // create temporary file to download mod to + tmpFolderDownload, err := os.MkdirTemp("", "diva-mod-download-*") + log.Info("created temp folder for downloading", "name", tmpFolderDownload) + if err != nil { + return err + } + + // really really jank, but api doesn't really have a better way of doing this + for _, x := range modData.FilesAFiles { + dlurl = x.SDownloadURL + break + } + + fileDlPath := path.Join(tmpFolderDownload, "download") + fileDl, err := os.Create(fileDlPath) + if err != nil { + return err + } + defer fileDl.Close() + + // download mod + resp, err := http.Get(dlurl) + log.Info("downloading mod", "url", dlurl, "modid", id, "path", fileDl) + // TODO: handle error code properly + if err != nil || resp.StatusCode != 200 { + return err + } + defer resp.Body.Close() + + // save to temp file + _, err = io.Copy(fileDl, resp.Body) + if err != nil { + return err + } + + // extract files to same directory + a, err := unarr.NewArchive(fileDlPath) + if err != nil { + return err + } + + _, err = a.Extract(tmpFolderDownload) + if err != nil { + return err + } + + // such jank but ive spent wayyy too long on this + var moddir string = "" + err = filepath.Walk(tmpFolderDownload, func(filePath string, _ fs.FileInfo, err error) error { + if path.Base(filePath) == "config.toml" { + moddir = path.Dir(filePath) + log.Info("found mod folder", "configloc", filePath) + } + + return nil + }) + + if moddir == "" { + errors.New("config.toml not found in mod dir") + } + + if err != nil { + return err + } + + // create a folder put the mod in + outpath := path.Join(outdir, fmt.Sprintf("%s@%d", id, modData.Udate)) + err = os.Mkdir(outpath, 0600) + if err != nil { + return err + } + + err = filepath.Walk(moddir, func(filePath string, fileinfo fs.FileInfo, err error) error { + relativepath := strings.Replace(filePath, moddir, "", 1) + newpath := path.Join(outdir, relativepath) + + if relativepath == "" { return nil } + + if fileinfo.IsDir() == true { + log.Warn("found directory, creating it!", "path", filePath, "relpath", relativepath, "newpath", newpath) + err = os.Mkdir(newpath, 0744) + if err != nil { return err } + return nil + } + + var src *os.File + var dst *os.File + + if src, err = os.Open(filePath); err != nil { + return err + } + defer src.Close() + + if dst, err = os.Create(newpath); err != nil { + return err + } + defer dst.Close() + + if _, err = io.Copy(dst, src); err != nil { + return err + } + + log.Info("copied file!!!", "src", filePath, "dst", newpath, "relativepath", relativepath) + return os.Chmod(newpath, 0744) + + }) + + if err != nil { + return err + } + + // TODO: cleanup temp files + err = os.RemoveAll(fileDlPath) + if err != nil { + return err + } + return nil +} diff --git a/gamebanana/download_test.go b/gamebanana/download_test.go new file mode 100644 index 0000000..5ca1ed4 --- /dev/null +++ b/gamebanana/download_test.go @@ -0,0 +1,24 @@ +package gamebanana + +import ( + "os" + "path" + "testing" +) + +func TestDownloadGameBananaMod(t *testing.T) { + tmpdir := t.TempDir() + err := DownloadGameBananaMod("602180", tmpdir) + if err != nil { + t.Error("failed to download game banana mod", err) + } + + // udate is the time the mod was last updated + // in unix time, used here as a version number + // modID@udate + expectedPathToExist := path.Join(tmpdir, "602180@1750696716") + + if _, err := os.Stat(expectedPathToExist); err != nil { + t.Error("mod doesn't have expected name or doesn't exist") + } +} diff --git a/gamebanana/mods.go b/gamebanana/mods.go index b4ce7da..823eff8 100644 --- a/gamebanana/mods.go +++ b/gamebanana/mods.go @@ -29,24 +29,7 @@ type GameBananaMod struct { Description string `json:"description"` Downloads int `json:"downloads"` FeedbackInstructions string `json:"feedback_instructions"` - FilesAFiles struct { - Num884808 struct { - IDRow string `json:"_idRow"` - SFile string `json:"_sFile"` - NFilesize int `json:"_nFilesize"` - TsDateAdded int `json:"_tsDateAdded"` - NDownloadCount int `json:"_nDownloadCount"` - SDownloadURL string `json:"_sDownloadUrl"` - SMd5Checksum string `json:"_sMd5Checksum"` - SAnalysisState string `json:"_sAnalysisState"` - SAnalysisResult string `json:"_sAnalysisResult"` - SAnalysisResultVerbose string `json:"_sAnalysisResultVerbose"` - SAvState string `json:"_sAvState"` - SAvResult string `json:"_sAvResult"` - BIsArchived bool `json:"_bIsArchived"` - BHasContents bool `json:"_bHasContents"` - } `json:"884808"` - } `json:"Files().aFiles()"` + FilesAFiles map[string]gamebananaDownloadInformation `json:"Files().aFiles()"` GameName string `json:"Game().name"` InstallInstructions string `json:"install_instructions"` IsObsolete string `json:"is_obsolete"` @@ -91,6 +74,22 @@ type GameBananaMod struct { WithholdBIsWithheld bool `json:"Withhold().bIsWithheld()"` } +type gamebananaDownloadInformation struct { + IDRow string `json:"_idRow"` + SFile string `json:"_sFile"` + NFilesize int `json:"_nFilesize"` + TsDateAdded int `json:"_tsDateAdded"` + NDownloadCount int `json:"_nDownloadCount"` + SDownloadURL string `json:"_sDownloadUrl"` + SMd5Checksum string `json:"_sMd5Checksum"` + SAnalysisState string `json:"_sAnalysisState"` + SAnalysisResult string `json:"_sAnalysisResult"` + SAnalysisResultVerbose string `json:"_sAnalysisResultVerbose"` + SAvState string `json:"_sAvState"` + SAvResult string `json:"_sAvResult"` + BIsArchived bool `json:"_bIsArchived"` + BHasContents bool `json:"_bHasContents"` +} func GetGameBananaMod(id string) (GameBananaMod, error) { // build URL params @@ -105,7 +104,7 @@ func GetGameBananaMod(id string) (GameBananaMod, error) { params.Add("itemid", id) params.Add("format", "json_min") params.Add("return_keys", "1") - params.Add("fields", "apps_used,authors,Category().name,catid,contestid,creator,Credits().aAuthors(),Credits().aAuthorsAndGroups(),Credits().ssvAuthorNames(),date,description,downloads,feedback_instructions,Files().aFiles(),Game().name,install_instructions,is_obsolete,lastpost_date,lastpost_userid,likes,mdate,modnote,name,Nsfw().bIsNsfw(),obsol_notice,Owner().name,postcount,Posts().LastPost().idPosterRow(),Posts().LastPost().sText(),Posts().LastPost().tsDateAdded(),Posts().Postcount().nPostCount(),Preview().sStructuredDataFullsizeUrl(),Preview().sSubFeedImageUrl(),RootCategory().id,RootCategory().name,screenshots,studioid,text,Trash().bIsTrashed(),udate,Updates().aGetLatestUpdates(),Updates().aLatestUpdates(),Updates().bSubmissionHasUpdates(),Updates().nUpdatesCount(),Url().sDownloadUrl(),Url().sEditUrl(),Url().sEmbeddablesUrl(),Url().sHistoryUrl(),Url().sProfileUrl(),Url().sTrashUrl(),Url().sUntrashUrl(),Url().sUpdatesUrl(),Url().sWithholdUrl(),userid,views,Withhold().bIsWithheld()") + params.Add("fields", "apps_used,authors,Category().name,catid,contestid,creator,Credits().ssvAuthorNames(),date,description,downloads,feedback_instructions,Files().aFiles(),Game().name,install_instructions,is_obsolete,lastpost_date,lastpost_userid,likes,mdate,modnote,name,Nsfw().bIsNsfw(),obsol_notice,Owner().name,postcount,Posts().LastPost().idPosterRow(),Posts().LastPost().sText(),Posts().LastPost().tsDateAdded(),Posts().Postcount().nPostCount(),Preview().sStructuredDataFullsizeUrl(),Preview().sSubFeedImageUrl(),RootCategory().id,RootCategory().name,screenshots,studioid,text,Trash().bIsTrashed(),udate,Updates().aGetLatestUpdates(),Updates().aLatestUpdates(),Updates().bSubmissionHasUpdates(),Updates().nUpdatesCount(),Url().sDownloadUrl(),Url().sEditUrl(),Url().sEmbeddablesUrl(),Url().sHistoryUrl(),Url().sProfileUrl(),Url().sTrashUrl(),Url().sUntrashUrl(),Url().sUpdatesUrl(),Url().sWithholdUrl(),userid,views,Withhold().bIsWithheld()") base.RawQuery = params.Encode() // send request diff --git a/go.mod b/go.mod index cf9254e..27df0a8 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.24.5 require ( github.com/charmbracelet/log v0.4.2 + github.com/gen2brain/go-unarr v0.2.4 github.com/google/go-cmp v0.5.8 ) diff --git a/go.sum b/go.sum index fe5804d..78ea2ae 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,8 @@ github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQ github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gen2brain/go-unarr v0.2.4 h1:Iu2kqtGfkLBSQoTFwMkSCmp0g3GrEM/XMVWzo9TQr/Y= +github.com/gen2brain/go-unarr v0.2.4/go.mod h1:0kdy3HtjKBcEaewifXZguHCvt4qD9V8iJCx4FPEOWT8= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=