214 lines
5.3 KiB
Go
214 lines
5.3 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"`
|
|
}
|
|
|
|
type assetBulkUploadCheckItem struct {
|
|
Checksum string `json:"checksum"`
|
|
Id string `json:"id"` // Not the assetID. Just a arbitrarily id string
|
|
}
|
|
|
|
type assetBulkUploadCheckResult struct {
|
|
Action string // can be "accept" or "reject"
|
|
AssetId string
|
|
Id string // Not the assetID. Just a arbitrary id string
|
|
IsTrashed bool
|
|
Reason string // can be "duplicate" or "unsupportedFormat" (i don't know what they mean by this)
|
|
}
|
|
|
|
type APIKeyTransport struct {
|
|
APIKey string
|
|
}
|
|
|
|
func (t *APIKeyTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
req = req.Clone(req.Context())
|
|
req.Header.Add("x-api-key", t.APIKey)
|
|
return http.DefaultTransport.RoundTrip(req)
|
|
}
|
|
|
|
func newImmichHttpClient(apiKey string) http.Client {
|
|
transport := APIKeyTransport{
|
|
APIKey: apiKey,
|
|
}
|
|
|
|
return http.Client{
|
|
Transport: &transport,
|
|
}
|
|
}
|
|
|
|
func uploadToImmich(filePath, appID, baseURL, deviceID string, httpClient *http.Client) (*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)
|
|
}
|
|
|
|
var body bytes.Buffer
|
|
writer := multipart.NewWriter(&body)
|
|
|
|
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
|
|
|
|
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()
|
|
|
|
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("Content-Type", writer.FormDataContentType())
|
|
req.Header.Set("Accept", "application/json")
|
|
|
|
resp, err := httpClient.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("parse immich response: %s", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
func setAssetMetadata(assets []string, description, baseURL string, httpClient http.Client) 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("Content-Type", "application/json")
|
|
req.Header.Set("Accept", "application/json")
|
|
|
|
_, err = httpClient.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func addAssetsToAlbum(assets []string, album, baseURL string, httpClient http.Client) 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("Content-Type", "application/json")
|
|
req.Header.Set("Accept", "application/json")
|
|
|
|
res, err := httpClient.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer res.Body.Close()
|
|
|
|
respBody, _ := io.ReadAll(res.Body)
|
|
if res.StatusCode != 200 {
|
|
return fmt.Errorf("add assets to album (%d): %s", res.StatusCode, string(respBody))
|
|
}
|
|
|
|
var result []bulkIdResponseDto
|
|
if err := json.Unmarshal(respBody, &result); err != nil {
|
|
return fmt.Errorf("parse immich response: %s", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func bulkCheckAssets(assets []assetBulkUploadCheckItem, baseURL string, httpClient http.Client) ([]assetBulkUploadCheckResult, error) {
|
|
body, _ := json.Marshal(struct {
|
|
Assets []assetBulkUploadCheckItem `json:"assets"`
|
|
}{Assets: assets})
|
|
|
|
req, err := http.NewRequest("POST", fmt.Sprintf("%s/api/assets/bulk-upload-check", baseURL), bytes.NewBuffer(body))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Accept", "application/json")
|
|
|
|
res, err := httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer res.Body.Close()
|
|
|
|
respBody, _ := io.ReadAll(res.Body)
|
|
if res.StatusCode != 200 {
|
|
return nil, fmt.Errorf("buck check assets %d - %s ", res.StatusCode, string(respBody))
|
|
}
|
|
|
|
var result struct {
|
|
Results []assetBulkUploadCheckResult `json:"results"`
|
|
}
|
|
if err := json.Unmarshal(respBody, &result); err != nil {
|
|
return nil, fmt.Errorf("parse immich response: %s", err)
|
|
}
|
|
|
|
return result.Results, nil
|
|
}
|