package steamimmich import ( "bytes" "encoding/json" "fmt" "io" "mime/multipart" "net/http" "os" "path/filepath" "time" ) type ImmichAPIUploadResponse struct { ID string `json:"id"` Status string `json:"status"` } type ImmichAPIAssetPutRequest struct { Ids []string `json:"ids"` Description string `json:"description,omitempty"` } func uploadToImmich(filePath, appID, baseURL, apiKey, deviceID string) (*ImmichAPIUploadResponse, error) { file, err := os.Open(filePath) if err != nil { return nil, fmt.Errorf("open file: %w", err) } defer file.Close() fileInfo, err := file.Stat() if err != nil { return nil, fmt.Errorf("stat file: %w", err) } // Create multipart form var body bytes.Buffer writer := multipart.NewWriter(&body) // Attach the image file part, err := writer.CreateFormFile("assetData", filepath.Base(filePath)) if err != nil { return nil, fmt.Errorf("create form file: %w", err) } if _, err := io.Copy(part, file); err != nil { return nil, fmt.Errorf("copy file data: %w", err) } modTime := fileInfo.ModTime().UTC().Format(time.RFC3339) createTime := modTime // If you don't have a separate created time // Required metadata fields writer.WriteField("deviceAssetId", fmt.Sprintf("%s-%s", appID, filepath.Base(filePath))) writer.WriteField("deviceId", deviceID) writer.WriteField("fileCreatedAt", createTime) writer.WriteField("fileModifiedAt", modTime) writer.WriteField("isFavorite", "false") writer.Close() // Build request req, err := http.NewRequest("POST", fmt.Sprintf("%s/api/assets", baseURL), &body) if err != nil { return nil, fmt.Errorf("create request: %w", err) } req.Header.Set("x-api-key", apiKey) req.Header.Set("Content-Type", writer.FormDataContentType()) req.Header.Set("Accept", "application/json") client := &http.Client{Timeout: 60 * time.Second} resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("POST to Immich: %w", err) } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) if resp.StatusCode >= 300 { return nil, fmt.Errorf("Immich upload failed (%d): %s", resp.StatusCode, string(respBody)) } var result ImmichAPIUploadResponse if err := json.Unmarshal(respBody, &result); err != nil { return nil, fmt.Errorf("Failed to parse immich response: %s", err) } return &result, nil } func setAssetMetadata(assets []string, description, baseURL, apiKey string) error { body, _ := json.Marshal(ImmichAPIAssetPutRequest{ Ids: assets, Description: description, }) req, err := http.NewRequest("PUT", fmt.Sprintf("%s/api/assets", baseURL), bytes.NewBuffer(body)) if err != nil { return err } req.Header.Set("x-api-key", apiKey) req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") _, err = http.DefaultClient.Do(req) if err != nil { return err } return nil }