z_mirror/fmm
git clone https://git.raiguard.me/z_mirror/fmm
8b551e4
predep-parsing
1package main 2 3import ( 4"bytes" 5"encoding/json" 6"errors" 7"fmt" 8"io" 9"mime/multipart" 10"net/http" 11"os" 12"path" 13 14"github.com/cavaliergopher/grab/v3" 15) 16 17const initUploadUrl string = "https://mods.factorio.com/api/v2/mods/releases/init_upload" 18 19func portalDownloadMod (mod Dependency ,dir * Dir )error { 20url := fmt .Sprintf ("https://mods.factorio.com/api/mods/%s/full" ,mod .Ident .Name ) 21res ,err := http .Get (url ) 22if err != nil { 23return err 24 } 25 26body ,err := io .ReadAll (res .Body ) 27if err != nil { 28return err 29 } 30res .Body .Close () 31 32var unmarshaled PortalFullMod 33err = json .Unmarshal (body ,& unmarshaled ) 34if err != nil { 35return err 36 } 37 38// Check releases from newest to oldest and find the first matching one 39var release * PortalModRelease 40for i := len (unmarshaled .Releases )- 1 ;i >= 0 ;i -= 1 { 41toCheck := unmarshaled .Releases [i ] 42if mod .Test (& ModIdent {mod .Ident .Name ,& toCheck .Version }) { 43release = & toCheck 44break 45 } 46 } 47 48if release == nil { 49return errors .New (fmt .Sprintf ("%s was not found on the mod portal" , 50mod .Ident .toString ())) 51 } 52 53downloadUrl := fmt .Sprintf ("https://mods.factorio.com/%s?username=%s&token=%s" , 54release .DownloadUrl ,downloadUsername ,downloadToken ) 55outPath := path .Join (modsDir ,release .FileName ) 56 57fmt .Printf ("Downloading %s\n" ,release .FileName ) 58if _ ,err := grab .Get (outPath ,downloadUrl );err != nil { 59return err 60 } 61 62return nil 63} 64 65func portalUploadMod (filepath string )error { 66// Init upload 67initUploadBody := & bytes.Buffer {} 68w := multipart .NewWriter (initUploadBody ) 69ident := newModIdent (path .Base (filepath )) 70w .WriteField ("mod" ,ident .Name ) 71w .Close () 72req ,err := http .NewRequest ("POST" ,initUploadUrl ,initUploadBody ) 73if err != nil { 74return err 75 } 76req .Header .Set ("Authorization" ,fmt .Sprintf ("Bearer %s" ,apiKey )) 77req .Header .Set ("Content-Type" ,w .FormDataContentType ()) 78res ,err := http .DefaultClient .Do (req ) 79if err != nil { 80return err 81 } 82var decoded ModInitUploadRes 83err = json .NewDecoder (res .Body ).Decode (& decoded ) 84if err != nil { 85return err 86 } 87if res .StatusCode != http .StatusOK { 88return errors .New (* decoded .Message ) 89 } 90defer res .Body .Close () 91 92// Open file 93file ,err := os .Open (filepath ) 94if err != nil { 95return err 96 } 97defer file .Close () 98 99fmt .Printf ("Uploading %s\n" ,filepath ) 100 101// Upload file 102uploadBody := & bytes.Buffer {} 103w = multipart .NewWriter (uploadBody ) 104part ,err := w .CreateFormFile ("file" ,path .Base (file .Name ())) 105io .Copy (part ,file ) 106w .Close () 107 108r ,err := http .NewRequest ("POST" ,* decoded .UploadUrl ,uploadBody ) 109if err != nil { 110return err 111 } 112r .Header .Add ("Content-Type" ,w .FormDataContentType ()) 113http .DefaultClient .Do (r ) 114 115return nil 116} 117 118func portalGetDependencies (mod ModIdent ) ([]Dependency ,error ) { 119fmt .Println ("Fetching dependencies for" ,mod .toString ()) 120url := fmt .Sprintf ("https://mods.factorio.com/api/mods/%s/full" ,mod .Name ) 121res ,err := http .Get (url ) 122if err != nil { 123return nil ,err 124 } 125if res .StatusCode != http .StatusOK { 126return nil ,errors .New (fmt .Sprintf ("%s was not found on the mod portal" ,mod .toString ())) 127 } 128 129body ,err := io .ReadAll (res .Body ) 130if err != nil { 131return nil ,err 132 } 133res .Body .Close () 134 135var unmarshaled PortalFullMod 136err = json .Unmarshal (body ,& unmarshaled ) 137if err != nil { 138return nil ,err 139 } 140 141version := mod .Version 142if version != nil { 143for _ ,release := range unmarshaled .Releases { 144if release .Version == * version { 145return release .InfoJson .Dependencies ,nil 146 } 147 } 148return nil ,nil 149 } 150 151return unmarshaled .Releases [len (unmarshaled .Releases )- 1 ].InfoJson .Dependencies ,nil 152} 153 154type ModInitUploadRes struct { 155UploadUrl * string `json:"upload_url"` 156Message * string // When an error occurs 157} 158 159type PortalFullMod struct { 160Name string 161Releases []PortalModRelease 162Title string 163} 164 165type PortalModRelease struct { 166DownloadUrl string `json:"download_url"` 167FileName string `json:"file_name"` 168InfoJson InfoJson `json:"info_json"` 169Version Version 170}