z_mirror/fmm
git clone https://git.raiguard.me/z_mirror/fmm
811753c
updating
1package fmm 2 3import ( 4"bytes" 5"encoding/json" 6"errors" 7"fmt" 8"io" 9"mime/multipart" 10"net/http" 11"net/url" 12"os" 13"path" 14 15"github.com/cavaliergopher/grab/v3" 16) 17 18type ModPortal struct { 19apiKey string 20baseVersion * Version 21downloadPath string 22mods map [string ]* PortalModInfo 23playerData PlayerData 24server string 25} 26 27// // GetBulkModInfo fetches information for all of the given mods from the mod portal. 28// func (p *ModPortal) GetBulkModInfo(mods []ModIdent) error { 29// fmt.Println("fetching mod info") 30// modNames := []string{} 31// for _, mod := range mods { 32// modNames = append(modNames, mod.Name) 33// } 34// namelist := url.QueryEscape(strings.Join(modNames, ",")) 35 36// path, err := url.JoinPath(p.server, "api/mods") 37// if err != nil { 38// return err 39// } 40 41// bodyContent := struct { 42// Namelist string `json:"namelist"` 43// }{namelist} 44// bodyBytes, err := json.Marshal(bodyContent) 45// if err != nil { 46// return err 47// } 48 49// fmt.Println(path) 50// req, err := http.NewRequest(http.MethodGet, path, bytes.NewReader(bodyBytes)) 51// if err != nil { 52// return err 53// } 54// // q := req.URL.Query() 55// // q.Add("page_size", "max") 56// // req.URL.RawQuery = q.Encode() 57// res, err := http.DefaultClient.Do(req) 58// if err != nil { 59// return err 60// } 61// if res.StatusCode != http.StatusOK { 62// return errors.New(fmt.Sprintf("error code %d", res.StatusCode)) 63// } 64 65// body, err := io.ReadAll(res.Body) 66// if err != nil { 67// return err 68// } 69// res.Body.Close() 70 71// fmt.Println(string(body)) 72 73// var modInfos PortalBulkModInfo 74// err = json.Unmarshal(body, &modInfos) 75// if err != nil { 76// return err 77// } 78 79// for _, result := range modInfos.Results { 80// p.mods[result.Name] = &result 81// } 82 83// return nil 84// } 85 86// GetModInfo fetches information for the given mod from the mod portal. 87func (p * ModPortal )GetModInfo (name string ) (* PortalModInfo ,error ) { 88if mod := p .mods [name ];mod != nil { 89return mod ,nil 90 } 91 92fmt .Println ("fetching info for" ,name )// TODO: Relocate this 93url ,err := url .JoinPath (p .server ,"api/mods" ,name ,"full" ) 94if err != nil { 95return nil ,err 96 } 97res ,err := http .Get (url ) 98if err != nil { 99return nil ,err 100 } 101if res .StatusCode != http .StatusOK { 102return nil ,errors .New (fmt .Sprintf ("%s was not found on the mod portal" ,name )) 103 } 104 105body ,err := io .ReadAll (res .Body ) 106if err != nil { 107return nil ,err 108 } 109res .Body .Close () 110 111var mod PortalModInfo 112err = json .Unmarshal (body ,& mod ) 113if err != nil { 114return nil ,err 115 } 116 117p .mods [name ]= & mod 118 119return & mod ,nil 120} 121 122// GetMatchingRelease fetches information for the newest release matching the given dependency. 123func (p * ModPortal )GetMatchingRelease (dep * Dependency ) (* PortalModRelease ,error ) { 124mod ,err := p .GetModInfo (dep .Name ) 125if err != nil { 126return nil ,err 127 } 128// Iterate backwards to get the newest release first 129for i := len (mod .Releases )- 1 ;i >= 0 ;i -- { 130release := & mod .Releases [i ] 131if dep .Test (& release .Version )&& release .compatibleWithBaseVersion (p .baseVersion ) { 132return release ,nil 133 } 134 } 135 136return nil ,ErrNoCompatibleRelease 137} 138 139// DownloadMatchingRelease downloads the latest mod release matching the given dependency. 140// Returns the filepath of the newly downloaded mod. 141func (p * ModPortal )DownloadMatchingRelease (dep * Dependency ) (string ,error ) { 142if p .playerData .Token == "" { 143return "" ,errors .New ("token was not specified" ) 144 } 145if p .playerData .Username == "" { 146return "" ,errors .New ("username was not specified" ) 147 } 148release ,err := p .GetMatchingRelease (dep ) 149if err != nil { 150return "" ,err 151 } 152 153downloadUrl := fmt .Sprintf ( 154"%s/%s?username=%s&token=%s" , 155p .server , 156release .DownloadUrl , 157p .playerData .Username , 158p .playerData .Token , 159 ) 160outPath := path .Join (p .downloadPath ,release .FileName ) 161 162fmt .Printf ("downloading %s\n" ,release .FileName )// TODO: This doesn't belong here 163res ,err := grab .Get (outPath ,downloadUrl ) 164if err != nil { 165return "" ,err 166 } 167return res .Filename ,nil 168} 169 170// DownloadLatestRelease downloads the latest release compatible with the current base version. 171func (p * ModPortal )DownloadLatestRelease (name string ) (string ,error ) { 172return p .DownloadMatchingRelease (& Dependency { 173Name :name , 174Kind :DependencyRequired , 175Req :VersionAny , 176 }) 177} 178 179func (p * ModPortal )DownloadRelease (name string ,version * Version ) (string ,error ) { 180if version == nil { 181return p .DownloadLatestRelease (name ) 182 } 183return p .DownloadMatchingRelease (& Dependency { 184Name :name , 185Version :version , 186Kind :DependencyRequired , 187Req :VersionEq , 188 }) 189} 190 191// UploadMod uploads the given file to the mod portal. 192func (p * ModPortal )UploadMod (filepath string )error { 193// Init upload 194initUploadBody := & bytes.Buffer {} 195w := multipart .NewWriter (initUploadBody ) 196ident := NewModIdent (path .Base (filepath )) 197w .WriteField ("mod" ,ident .Name ) 198w .Close () 199url ,err := url .JoinPath (p .server ,"api/v2/mods/releases/init_upload" ) 200if err != nil { 201return err 202 } 203req ,err := http .NewRequest (http .MethodPost ,url ,initUploadBody ) 204if err != nil { 205return err 206 } 207req .Header .Set ("Authorization" ,fmt .Sprintf ("Bearer %s" ,p .apiKey )) 208req .Header .Set ("Content-Type" ,w .FormDataContentType ()) 209res ,err := http .DefaultClient .Do (req ) 210if err != nil { 211return err 212 } 213var decoded ModInitUploadRes 214err = json .NewDecoder (res .Body ).Decode (& decoded ) 215if err != nil { 216return err 217 } 218if res .StatusCode != http .StatusOK { 219return errors .New (* decoded .Message ) 220 } 221defer res .Body .Close () 222 223// Open file 224file ,err := os .Open (filepath ) 225if err != nil { 226return err 227 } 228defer file .Close () 229 230fmt .Printf ("uploading %s\n" ,filepath )// TODO: Relocate this 231 232// Upload file 233uploadBody := & bytes.Buffer {} 234w = multipart .NewWriter (uploadBody ) 235part ,err := w .CreateFormFile ("file" ,path .Base (file .Name ())) 236io .Copy (part ,file ) 237w .Close () 238 239r ,err := http .NewRequest ("POST" ,* decoded .UploadUrl ,uploadBody ) 240if err != nil { 241return err 242 } 243r .Header .Add ("Content-Type" ,w .FormDataContentType ()) 244http .DefaultClient .Do (r ) 245 246return nil 247} 248 249type ModInitUploadRes struct { 250UploadUrl * string `json:"upload_url"` 251Message * string // When an error occurs 252} 253 254type PortalBulkModInfo struct { 255Results []PortalModInfo `json:"results"` 256} 257 258type PortalModInfo struct { 259Name string 260Releases []PortalModRelease 261Title string 262} 263 264type PortalModRelease struct { 265DownloadUrl string `json:"download_url"` 266FileName string `json:"file_name"` 267InfoJson infoJson `json:"info_json"` 268Version Version `json:"version"` 269} 270 271func (r * PortalModRelease )compatibleWithBaseVersion (baseVersion * Version )bool { 272if r .InfoJson .FactorioVersion [0 ]!= baseVersion [0 ]|| r .InfoJson .FactorioVersion [1 ]!= baseVersion [1 ] { 273return false 274 } 275 276for _ ,dep := range r .InfoJson .Dependencies { 277if dep .Name == "base" { 278if dep .Version == nil { 279return true 280 } 281return dep .Test (baseVersion ) 282 } 283 } 284return true 285}