diva/gamebanana/mods.go
Nico 527994abba
gamebanana: add function for downloading mods
kinda janky with the implementation right now, later i would like to
improve the way files are copied and allow for mods with multiple
downloads attached to the mod. probably split off some of the
functionality later to add installing mods not on gamebanana easier.
2025-08-30 00:04:45 +10:00

138 lines
7.3 KiB
Go

package gamebanana
import (
"encoding/json"
"io"
"net/http"
"net/url"
"github.com/charmbracelet/log"
)
// all fields for the "mod" type got at GET api.gamebanana.com/Core/Item/Data/AllowedFields?itemtype=Mod
// struct is gotten from requesting all fields and then converting them with https://transform.tools/json-to-go
type GameBananaMod struct {
AppsUsed string `json:"apps_used"`
Authors string `json:"authors"`
CategoryName string `json:"Category().name"`
Catid int `json:"catid"`
Contestid int `json:"contestid"`
Creator string `json:"creator"`
CreditsAAuthors [][]string `json:"Credits().aAuthors()"`
CreditsAAuthorsAndGroups struct {
KeyAuthor [][]string `json:"Key Author"`
ModelSources [][]string `json:"Model Sources"`
SpecialThanks [][]string `json:"Special Thanks"`
} `json:"Credits().aAuthorsAndGroups()"`
CreditsSsvAuthorNames string `json:"Credits().ssvAuthorNames()"`
Date int `json:"date"`
Description string `json:"description"`
Downloads int `json:"downloads"`
FeedbackInstructions string `json:"feedback_instructions"`
FilesAFiles map[string]gamebananaDownloadInformation `json:"Files().aFiles()"`
GameName string `json:"Game().name"`
InstallInstructions string `json:"install_instructions"`
IsObsolete string `json:"is_obsolete"`
LastpostDate int `json:"lastpost_date"`
LastpostUserid int `json:"lastpost_userid"`
Likes int `json:"likes"`
Mdate int `json:"mdate"`
Modnote string `json:"modnote"`
Name string `json:"name"`
NsfwBIsNsfw bool `json:"Nsfw().bIsNsfw()"`
ObsolNotice string `json:"obsol_notice"`
OwnerName string `json:"Owner().name"`
Postcount int `json:"postcount"`
PostsLastPostIDPosterRow int `json:"Posts().LastPost().idPosterRow()"`
PostsLastPostSText string `json:"Posts().LastPost().sText()"`
PostsLastPostTsDateAdded int `json:"Posts().LastPost().tsDateAdded()"`
PostsPostcountNPostCount int `json:"Posts().Postcount().nPostCount()"`
PreviewSStructuredDataFullsizeURL string `json:"Preview().sStructuredDataFullsizeUrl()"`
PreviewSSubFeedImageURL string `json:"Preview().sSubFeedImageUrl()"`
RootCategoryID int `json:"RootCategory().id"`
RootCategoryName string `json:"RootCategory().name"`
Screenshots string `json:"screenshots"`
Studioid int `json:"studioid"`
Text string `json:"text"`
TrashBIsTrashed bool `json:"Trash().bIsTrashed()"`
Udate int `json:"udate"`
UpdatesAGetLatestUpdates []interface{} `json:"Updates().aGetLatestUpdates()"`
UpdatesALatestUpdates []interface{} `json:"Updates().aLatestUpdates()"`
UpdatesBSubmissionHasUpdates bool `json:"Updates().bSubmissionHasUpdates()"`
UpdatesNUpdatesCount int `json:"Updates().nUpdatesCount()"`
URLSDownloadURL string `json:"Url().sDownloadUrl()"`
URLSEditURL string `json:"Url().sEditUrl()"`
URLSEmbeddablesURL string `json:"Url().sEmbeddablesUrl()"`
URLSHistoryURL string `json:"Url().sHistoryUrl()"`
URLSProfileURL string `json:"Url().sProfileUrl()"`
URLSTrashURL string `json:"Url().sTrashUrl()"`
URLSUntrashURL string `json:"Url().sUntrashUrl()"`
URLSUpdatesURL string `json:"Url().sUpdatesUrl()"`
URLSWithholdURL string `json:"Url().sWithholdUrl()"`
Userid int `json:"userid"`
Views int `json:"views"`
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
base, err := url.Parse("https://api.gamebanana.com/Core/Item/Data")
if err != nil {
log.Error("failed to parse URL", "err", err)
return GameBananaMod{}, err
}
params := url.Values{}
params.Add("itemtype", "Mod")
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().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
log.Info("got request url for Mod information", "id", id, "url", base.String())
resp, err := http.Get(base.String())
if err != nil || resp.StatusCode != 200 {
log.Error("failed to request URL", "url", base.String(), "err", err, "httpstatus", resp.StatusCode)
return GameBananaMod{}, err
}
defer resp.Body.Close()
// decode response
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Error("failed to decode body into bytes")
return GameBananaMod{}, err
}
var dataStruct GameBananaMod
err = json.Unmarshal(body, &dataStruct)
if err != nil {
log.Error("failed to decode request body", "err", err)
log.Debug(string(body))
return GameBananaMod{}, err
}
return dataStruct, nil
}