package nextcloud import ( "encoding/json" "fmt" "io" "strings" "time" ) type ocsShareRecord struct { ID flexShareID `json:"id"` Path string `json:"path"` ShareType int `json:"share_type"` Permissions int `json:"permissions"` URL string `json:"url"` Expiration string `json:"expiration"` ShareWith *string `json:"share_with"` ShareWithDisplayName string `json:"share_with_displayname"` Label string `json:"label"` Token string `json:"token"` UIDOwner string `json:"uid_owner"` DisplayNameOwner string `json:"displayname_owner"` UIDFileOwner string `json:"uid_file_owner"` DisplayNameFileOwner string `json:"displayname_file_owner"` Stime int64 `json:"stime"` Note string `json:"note"` ItemType string `json:"item_type"` Password *string `json:"password"` } // flexShareID accepts Nextcloud share ids as JSON string or number. type flexShareID string func (f *flexShareID) UnmarshalJSON(b []byte) error { var s string if err := json.Unmarshal(b, &s); err == nil { *f = flexShareID(strings.TrimSpace(s)) return nil } var n json.Number if err := json.Unmarshal(b, &n); err == nil { *f = flexShareID(n.String()) return nil } return fmt.Errorf("invalid share id") } func (f flexShareID) String() string { return string(f) } func decodeOCSShareRecords(raw json.RawMessage) ([]ocsShareRecord, error) { raw = json.RawMessage(strings.TrimSpace(string(raw))) if len(raw) == 0 || string(raw) == "null" || string(raw) == "[]" { return nil, nil } if raw[0] == '[' { var list []ocsShareRecord if err := json.Unmarshal(raw, &list); err != nil { return nil, err } return list, nil } var one ocsShareRecord if err := json.Unmarshal(raw, &one); err != nil { return nil, err } return []ocsShareRecord{one}, nil } func decodeOCSShareResponse(body io.Reader, fallbackPath string) (*ShareInfo, error) { var ocsResp struct { OCS struct { Data json.RawMessage `json:"data"` } `json:"ocs"` } if err := json.NewDecoder(body).Decode(&ocsResp); err != nil { return nil, err } items, err := decodeOCSShareRecords(ocsResp.OCS.Data) if err != nil { return nil, err } if len(items) == 0 { return nil, fmt.Errorf("empty share response") } info := mapOCSShareRecord(items[0], fallbackPath) return &info, nil } func mapOCSShareRecord(item ocsShareRecord, fallbackPath string) ShareInfo { path := strings.TrimSpace(item.Path) if path == "" { path = fallbackPath } shareWith := "" if item.ShareWith != nil { shareWith = strings.TrimSpace(*item.ShareWith) } expiration := strings.TrimSpace(item.Expiration) info := ShareInfo{ ID: item.ID.String(), Path: path, ShareType: item.ShareType, Permissions: item.Permissions, URL: item.URL, ExpiresAt: expiration, ShareWith: shareWith, ShareWithDisplayName: strings.TrimSpace(item.ShareWithDisplayName), Label: strings.TrimSpace(item.Label), Token: strings.TrimSpace(item.Token), OwnerID: strings.TrimSpace(item.UIDOwner), OwnerDisplayName: strings.TrimSpace(item.DisplayNameOwner), FileOwnerID: strings.TrimSpace(item.UIDFileOwner), FileOwnerDisplayName: strings.TrimSpace(item.DisplayNameFileOwner), Note: strings.TrimSpace(item.Note), ItemType: strings.TrimSpace(item.ItemType), HasPassword: item.Password != nil && strings.TrimSpace(*item.Password) != "", } if ts := item.Stime; ts > 0 { info.CreatedAt = time.Unix(ts, 0).UTC().Format(time.RFC3339) } if item.ShareType == 3 { if strings.EqualFold(info.Label, "internal") { info.AccessMode = "internal" } else { info.AccessMode = "public" } } return info }