package provider import ( "archive/zip" "conjure-os/lib/config" "conjure-os/lib/models" "encoding/json" "fmt" "io" "io/fs" "log" "net/http" "net/url" "os" "path/filepath" "strings" "gopkg.in/yaml.v3" ) var token string func Update() { //requestURL := fmt.Sprintf("http://localhost:%d", 8080) //login(requestURL) //allGames(requestURL) } type AuthResponse struct { Token string `json:"token"` } func login(requestURL string) { client := &http.Client{} form := url.Values{ "username": {"test"}, "password": {"test"}} req, _ := http.NewRequest("POST", requestURL+"/login", strings.NewReader(form.Encode())) req.Header.Set("API-Version", "1") req.Header.Add("Content-Type", "application/x-www-form-urlencoded") res, err := client.Do(req) check(err) var auth AuthResponse err = json.NewDecoder(res.Body).Decode(&auth) check(err) token = auth.Token } func allGames(requestURL string) { client := &http.Client{} req, _ := http.NewRequest("GET", requestURL+"/games", nil) req.Header.Set("Authorization", token) req.Header.Set("API-Version", "1") res, err := client.Do(req) check(err) fmt.Printf("client: status code: %d\n", res.StatusCode) } func GetOrSetEnvKey(key string, defaultValue string) string { value, exist := os.LookupEnv(key) if exist { return value } err := os.Setenv(key, defaultValue) if err != nil { log.Println(err) } return defaultValue } func extractZipToSiblingFolder(zipPath string) error { // Determine destination folder name (same name as zip file, without .zip) zipBase := strings.TrimSuffix(filepath.Base(zipPath), ".conj") destDir := filepath.Join(filepath.Dir(zipPath), zipBase) // Delete destination folder if it exists if _, err := os.Stat(destDir); err == nil { err = os.RemoveAll(destDir) if err != nil { return fmt.Errorf("failed to remove existing folder: %v", err) } } // Open the zip archive r, err := zip.OpenReader(zipPath) if err != nil { return err } defer r.Close() // Create the destination directory err = os.MkdirAll(destDir, os.ModePerm) if err != nil { return err } for _, f := range r.File { destPath := filepath.Join(destDir, f.Name) // ZipSlip protection if !strings.HasPrefix(destPath, filepath.Clean(destDir)+string(os.PathSeparator)) { return fmt.Errorf("illegal file path: %s", destPath) } if f.FileInfo().IsDir() { if err := os.MkdirAll(destPath, os.ModePerm); err != nil { return err } continue } // Ensure parent directories exist if err := os.MkdirAll(filepath.Dir(destPath), os.ModePerm); err != nil { return err } // Create and copy the file srcFile, err := f.Open() if err != nil { return err } defer srcFile.Close() dstFile, err := os.OpenFile(destPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) if err != nil { return err } defer dstFile.Close() if _, err := io.Copy(dstFile, srcFile); err != nil { return err } } return nil } func extractZipsInFolder(folder string) error { entries, err := os.ReadDir(folder) if err != nil { return fmt.Errorf("reading directory failed: %w", err) } for _, entry := range entries { if entry.IsDir() { continue } if strings.HasSuffix(strings.ToLower(entry.Name()), ".conj") { zipPath := filepath.Join(folder, entry.Name()) fmt.Println("Extracting:", zipPath) if err := extractZipToSiblingFolder(zipPath); err != nil { return fmt.Errorf("failed to extract %s: %w", zipPath, err) } } } return nil } func ExtractGame(game models.Game) string { gamePath := filepath.Join(config.GetDefaultConjureGamesDirectory(), fmt.Sprintf("%s.conj", game.Id)) err := extractZipToSiblingFolder(gamePath) check(err) gamePath = filepath.Join(config.GetDefaultConjureGamesDirectory(), game.Id) err = extractZipsInFolder(gamePath) check(err) gamePath = filepath.Join(gamePath, game.Files) return gamePath } func GetConjureGameInfo() []models.Game { gamePath := config.GetDefaultConjureGamesDirectory() entrie, err := os.ReadDir(gamePath) if err != nil { log.Fatal(err) } var games []models.Game for _, e := range entrie { if e.IsDir() { newGames := readFolder(e, gamePath) games = append(newGames, games...) } else if filepath.Ext(e.Name()) == ".conj" { zipPath := filepath.Join(gamePath, e.Name()) r, err := zip.OpenReader(zipPath) if err != nil { log.Fatal(err) } defer r.Close() fmt.Println("Contents of", zipPath) for _, f := range r.File { printIndentedPath(f.Name) if f.Name == "metadata.txt" { rc, err := f.Open() if err != nil { log.Fatal(err) } defer rc.Close() fmt.Println("Contents of metadata.txt:") metadata, err := io.ReadAll(rc) game := parseGameInfo(metadata) games = append(games, game) if err != nil { log.Fatal(err) } } } } } if len(games) > 0 { fmt.Println("Found Conjure Games: " + string(rune(len(games)))) } else { fmt.Println("No Conjure games Found") } return games } // Helper to print tree-like structure func printIndentedPath(path string) { parts := strings.Split(path, "/") for i, part := range parts { if part == "" { continue } fmt.Print(strings.Repeat(" ", i)) fmt.Println(part) } } func readFolder(entry fs.DirEntry, path string) []models.Game { newPath := path + "/" + entry.Name() entries, err := os.ReadDir(newPath) check(err) var games []models.Game for _, e := range entries { if e.IsDir() { games = append(games, readFolder(e, newPath)...) } else { filenamesplit := strings.Split(e.Name(), ".") if filenamesplit[1] == "conf" && filenamesplit[2] == "yml" { game := parseGameInfoFromFile(newPath + "/" + e.Name()) games = append(games, game) } } } return games } func parseGameInfoFromFile(path string) models.Game { data, err := os.ReadFile(path) check(err) return parseGameInfo(data) } func parseGameInfo(data []byte) models.Game { game := models.Game{} err := yaml.Unmarshal(data, &game) check(err) return game } func check(e error) { if e != nil { panic(e) } }