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.
138 lines
7.3 KiB
Go
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
|
|
}
|