153 lines
3.8 KiB
Go

package steamimmich
import (
"bytes"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"time"
)
type uploadResponse struct {
ID string `json:"id"`
Status string `json:"status"`
}
type assetPutRequest struct {
Ids []string `json:"ids"`
Description string `json:"description,omitempty"`
}
type bulkIdResponseDto struct {
Error string `json:"error"`
Id string `json:"id"`
Success bool `json:"success"`
}
func uploadToImmich(filePath, appID, baseURL, apiKey, deviceID string) (*uploadResponse, 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 uploadResponse
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(assetPutRequest{
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
}
func addAssetsToAlbum(assets []string, album, baseURL, apiKey string) error {
body, _ := json.Marshal(struct {
IDs []string `json:"ids"`
}{IDs: assets})
req, err := http.NewRequest("PUT", fmt.Sprintf("%s/api/albums/%s/assets", baseURL, album), 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")
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
respBody, _ := io.ReadAll(res.Body)
if res.StatusCode >= 300 {
return fmt.Errorf("Failed to add assets to album (%d): %s", res.StatusCode, string(respBody))
}
var result []bulkIdResponseDto
if err := json.Unmarshal(respBody, &result); err != nil {
return fmt.Errorf("Failed to parse immich response: %s", err)
}
return nil
}