Started work on adding mod downloader

Read from mod-list.json in mod dir and download each mod in list.
This requires reading from factorio mod portal api, and extracting download url and calling it with username and api token.

new field in config.yml username and apitoken, is needed to download mod files from mod.factorio.com
This commit is contained in:
2026-04-30 12:06:49 -06:00
parent b2f485269b
commit 2548261d66
7 changed files with 151 additions and 7 deletions

21
cli.go
View File

@@ -6,7 +6,8 @@ import (
) )
func cliToolMode() { func cliToolMode() {
var c = readCfg(fmConfig) var c = readCfg("config.yml")
modlist := string(c.Server.ServDir) + "/mods/mod-list.json"
if verifyConfig(c) { if verifyConfig(c) {
if len(os.Args) > 1 { if len(os.Args) > 1 {
switch os.Args[1] { switch os.Args[1] {
@@ -18,6 +19,22 @@ func cliToolMode() {
fmt.Printf("Start Server: %s start\nStop Server: %s stop\n", os.Args[0], os.Args[0]) fmt.Printf("Start Server: %s start\nStop Server: %s stop\n", os.Args[0], os.Args[0])
fmt.Printf("Run backup\n\tFull backup: %s backup full", os.Args[0]) fmt.Printf("Run backup\n\tFull backup: %s backup full", os.Args[0])
fmt.Printf("\n\tBackup saves: %s backup saves\n", os.Args[0]) fmt.Printf("\n\tBackup saves: %s backup saves\n", os.Args[0])
case "mod":
if len(os.Args) > 2 {
switch os.Args[2] {
case "download":
if !findmodlist(modlist) {
fmt.Printf("FAILED TO FIND MOD LIST")
} else {
fmt.Printf("found %s\n", modlist)
downloadMods(modlist)
}
default:
fmt.Println("Invalid mod option: use 'update'")
}
} else {
fmt.Println("Missing mod option: use 'update'")
}
case "backup": case "backup":
if len(os.Args) > 2 { if len(os.Args) > 2 {
switch os.Args[2] { switch os.Args[2] {
@@ -35,7 +52,7 @@ func cliToolMode() {
fmt.Printf("Unknown command: %s. Use 'start', 'stop', or 'backup'.\n", os.Args[1]) fmt.Printf("Unknown command: %s. Use 'start', 'stop', or 'backup'.\n", os.Args[1])
} }
} else { } else {
fmt.Println("Use 'start', 'stop', or 'backup' command\nex. factoryman start\nTo configure edit 'conifg.yml'") fmt.Println("Use 'start', 'stop', or 'backup' command\nex. factoryman start\nTo configure edit 'config.yml'")
} }
} }
} }

View File

@@ -9,8 +9,6 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
const fmConfig = "config.yml"
type GoConfig struct { type GoConfig struct {
Server struct { // server specific settings Server struct { // server specific settings
ServDir string `yaml:"serverFolder"` ServDir string `yaml:"serverFolder"`
@@ -24,6 +22,8 @@ type GoConfig struct {
BackupDir string `yaml:"backupDir"` BackupDir string `yaml:"backupDir"`
UseScreen bool `yaml:"screen"` UseScreen bool `yaml:"screen"`
ScreenName string `yaml:"screenName"` ScreenName string `yaml:"screenName"`
UserName string `yaml:"username"`
ApiToken string `yaml:"apitoken"`
} `yaml:"factoryman"` } `yaml:"factoryman"`
} }

View File

@@ -1,6 +1,6 @@
server: server:
serverFolder: "factorio" serverFolder: "factorio"
worldFile: "factorio/data/saves/newworld.zip" worldFile: "factorio/saves/test.zip"
serverSettings: "factorio/data/server-settings.json" serverSettings: "factorio/data/server-settings.json"
serverExec: "factorio/bin/x64/factorio" serverExec: "factorio/bin/x64/factorio"
port: 34197 port: 34197
@@ -8,4 +8,6 @@ server:
factoryman: factoryman:
screen: True screen: True
screenName: "Factorio" screenName: "Factorio"
backupDir: "factorio/backups" backupDir: "factorio/backups"
username: ""
apitoken: ""

Binary file not shown.

37
jsonStruts.go Normal file
View File

@@ -0,0 +1,37 @@
package main
type Mod struct {
Name string `json:"name"`
Enabled bool `json:"enabled"`
Version string `json:"version,omitempty"`
}
type ModList struct {
Mods []Mod `json:"mods"`
}
type ModPortal struct {
Category string `json:"category"`
DownloadCount int `json:"downloads_count"`
Name string `json:"name"`
Owner string `json:"owner"`
Releases []Release `json:"releases"`
Score float64 `json:"score"`
Summary string `json:"summary"`
Thumbnail string `json:"thumbnail"`
Title string `json:"title"`
}
type Release struct {
DownloadUrl string `json:"download_url"`
Filename string `json:"file_name"`
InfoJson InfoJSON `json:"info_json"`
ReleaseTime string `json:"released_at"`
Sha1 string `json:"sha1"`
Version string `json:"version"`
}
type InfoJSON struct {
Dependencies []string `json:"dependencies"`
FactorioVersion string `json:"factorio_version"`
}

View File

@@ -11,7 +11,7 @@ func startStopServer(cmd string, con GoConfig) {
switch cmd { switch cmd {
case "start": case "start":
x := fmt.Sprintf("%s --port %d --server-settings %s --start-server %s", con.Server.ServExec, con.Server.ServPort, con.Server.ServCfg, con.Server.WorldFile) x := fmt.Sprintf("%s --port %d --server-settings %s --start-server %s", con.Server.ServExec, con.Server.ServPort, con.Server.ServCfg, con.Server.WorldFile)
if con.Factoryman.UseScreen { // if screen enabled in confing.yml if con.Factoryman.UseScreen { // if screen enabled in config.yml
fmt.Println("Starting factorio server in screen session") fmt.Println("Starting factorio server in screen session")
startScreenCmd := exec.Command("screen", "-dmS", con.Factoryman.ScreenName, "bash", "-c", x, "; exec sh") startScreenCmd := exec.Command("screen", "-dmS", con.Factoryman.ScreenName, "bash", "-c", x, "; exec sh")
startScreenCmd.Stdout = os.Stdout startScreenCmd.Stdout = os.Stdout

88
mods.go Normal file
View File

@@ -0,0 +1,88 @@
package main
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
)
func findmodlist(modlist string) bool {
if !isItReal(modlist) {
return false
} else {
return true
}
}
func download(filedest string, url string) error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
out, err := os.Create(filedest)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, resp.Body)
return err
}
func downloadMods(modlist string) {
var c = readCfg("config.yml")
fmt.Printf("Updating mods from %s\n", modlist)
file, err := os.Open(modlist)
if err != nil {
log.Fatalf("Error reading modlist: %v", err)
}
defer file.Close()
var modList ModList
decoder := json.NewDecoder(file)
if err := decoder.Decode(&modList); err != nil {
log.Fatalf("Error reading JSON: %v", err)
}
for _, mod := range modList.Mods {
switch mod.Name {
case "base":
fmt.Println("Skipping base...")
case "elevated-rails":
fmt.Println("Skipping elevated-rails...")
case "quality":
fmt.Println("Skipping quality...")
case "space-age":
fmt.Println("Skipping space-age...")
default:
modportalurl := fmt.Sprintf("https://mods.factorio.com/api/mods/%s", mod.Name)
resp, err := http.Get(modportalurl)
if err != nil {
log.Fatalf("Error reading JSON: %v", err)
}
defer resp.Body.Close()
var moddata ModPortal
if err := json.NewDecoder(resp.Body).Decode(&moddata); err != nil {
log.Fatalf("Error reading JSON: %v", err)
}
fmt.Printf("Mod: %s, Ver: %s, Enabled: %t\n", mod.Name, mod.Version, mod.Enabled)
accessToken := fmt.Sprintf("?username=%s&token=%s", c.Factoryman.UserName, c.Factoryman.ApiToken)
modDownloadUrl := fmt.Sprintf("https://mods.factorio.com%s%s", moddata.Releases[len(moddata.Releases)-1].DownloadUrl, accessToken)
fmt.Println(modDownloadUrl)
downloadErr := download(string(c.Server.ServDir+"/mods/"+moddata.Releases[len(moddata.Releases)-1].Filename), modDownloadUrl)
if downloadErr != nil {
log.Fatalf("Error downloading: %v", downloadErr)
}
fmt.Printf("Downloaded: %s\n", moddata.Releases[len(moddata.Releases)-1].Filename)
}
}
}