From 7b1d0a0376bf7a3596dc25e9afc96d909e07af00 Mon Sep 17 00:00:00 2001 From: Jim Myhrberg Date: Sun, 11 Dec 2022 20:55:18 +0000 Subject: [PATCH] feat(collections): add support for collections --- client.go | 1 + collection_data.go | 56 ++++++++++++++++++++++ collection_jobs.go | 60 ++++++++++++++++++++++++ collections.go | 114 +++++++++++++++++++++++++++++++++++++++++++++ recent_jobs.go | 22 +++++++++ 5 files changed, 253 insertions(+) create mode 100644 collection_data.go create mode 100644 collection_jobs.go create mode 100644 collections.go diff --git a/client.go b/client.go index 5f2fc97..5be6292 100644 --- a/client.go +++ b/client.go @@ -19,6 +19,7 @@ var ( ErrInvalidAuthToken = fmt.Errorf("%w: invalid auth token", Err) ErrInvalidAPIURL = fmt.Errorf("%w: invalid API URL", Err) ErrInvalidHTTPClient = fmt.Errorf("%w: invalid HTTP client", Err) + ErrNotFound = fmt.Errorf("%w: not found", Err) ErrResponse = fmt.Errorf("%w: response", Err) ErrResponseStatus = fmt.Errorf("%w: response status", ErrResponse) diff --git a/collection_data.go b/collection_data.go new file mode 100644 index 0000000..b507e38 --- /dev/null +++ b/collection_data.go @@ -0,0 +1,56 @@ +package midjourney + +import "context" + +type CollectionData struct { + Filters *CollectionFilters `json:"filters,omitempty"` +} + +type CollectionFilters struct { + OrderBy string `json:"orderBy,omitempty"` + JobType string `json:"jobType,omitempty"` + UserIDRankedScore string `json:"user_id_ranked_score,omitempty"` + ShowFilters bool `json:"showFilters,omitempty"` +} + +func (c *Client) PutCollectionData( + ctx context.Context, + collectionID string, + data *CollectionData, +) (*Collection, error) { + if collectionID == "" { + return nil, ErrCollectionIDRequired + } + + req := &Collection{ + ID: collectionID, + Data: data, + } + resp := &Collection{} + + err := c.Put(ctx, "app/collections/", nil, req, resp) + + return resp, err +} + +func (c *Client) PutCollectionFilters( + ctx context.Context, + collectionID string, + filters *CollectionFilters, +) (*Collection, error) { + if collectionID == "" { + return nil, ErrCollectionIDRequired + } + + req := &Collection{ + ID: collectionID, + Data: &CollectionData{ + Filters: filters, + }, + } + resp := &Collection{} + + err := c.Put(ctx, "app/collections/", nil, req, resp) + + return resp, err +} diff --git a/collection_jobs.go b/collection_jobs.go new file mode 100644 index 0000000..91ea76b --- /dev/null +++ b/collection_jobs.go @@ -0,0 +1,60 @@ +package midjourney + +import ( + "context" + "fmt" + "net/http" +) + +var ErrJobIDsRequired = fmt.Errorf("%w: job IDs required", Err) + +type collectionJobsRequest struct { + CollectionID string `json:"collection_id,omitempty"` + JobIDs []string `json:"job_ids,omitempty"` +} + +type CollectionJobsResult struct { + Failures []string `json:"failures,omitempty"` + Success bool `json:"success,omitempty"` + Successes []string `json:"successes,omitempty"` +} + +func (c *Client) CollectionJobsAdd( + ctx context.Context, + collectionID string, + jobIDs []string, +) (*CollectionJobsResult, error) { + return c.collectionJobs(ctx, http.MethodPut, collectionID, jobIDs) +} + +func (c *Client) CollectionJobsRemove( + ctx context.Context, + collectionID string, + jobIDs []string, +) (*CollectionJobsResult, error) { + return c.collectionJobs(ctx, http.MethodDelete, collectionID, jobIDs) +} + +func (c *Client) collectionJobs( + ctx context.Context, + method string, + collectionID string, + jobIDs []string, +) (*CollectionJobsResult, error) { + if collectionID == "" { + return nil, ErrCollectionIDRequired + } + if len(jobIDs) == 0 { + return nil, ErrJobIDsRequired + } + + var resp *CollectionJobsResult + + err := c.Request( + ctx, method, "app/collections-jobs/", nil, + &collectionJobsRequest{CollectionID: collectionID, JobIDs: jobIDs}, + resp, + ) + + return resp, err +} diff --git a/collections.go b/collections.go new file mode 100644 index 0000000..c3cbc59 --- /dev/null +++ b/collections.go @@ -0,0 +1,114 @@ +package midjourney + +import ( + "context" + "fmt" + "net/url" +) + +var ( + ErrCollectionIDRequired = fmt.Errorf("%w: collection id required", Err) + ErrCollectionNotFound = fmt.Errorf("%w: collection", ErrNotFound) +) + +type Collection struct { + CoverJobID string `json:"cover_job_id,omitempty"` + Created string `json:"created,omitempty"` + CreatorAvatarJobID string `json:"creator_avatar_job_id,omitempty"` + CreatorCoverJobID string `json:"creator_cover_job_id,omitempty"` + CreatorID string `json:"creator_id,omitempty"` + CreatorUsername string `json:"creator_username,omitempty"` + Data *CollectionData `json:"data,omitempty"` + Description string `json:"description,omitempty"` + Hidden bool `json:"hidden,omitempty"` + ID string `json:"id,omitempty"` + NumJobs int `json:"num_jobs,omitempty"` + Public bool `json:"public,omitempty"` + PublicEditable bool `json:"public_editable,omitempty"` + SearchTerms []string `json:"search_terms,omitempty"` + Title string `json:"title,omitempty"` + Workspaces []string `json:"workspaces,omitempty"` +} + +type CollectionsQuery struct { + UserID string `url:"user_id,omitempty"` + CollectionID string `url:"collection_id,omitempty"` +} + +func (cq *CollectionsQuery) URLValues() url.Values { + v := url.Values{} + + if cq.UserID != "" { + v.Set("user_id", cq.UserID) + } + if cq.CollectionID != "" { + v.Set("collection_id", cq.CollectionID) + } + + return v +} + +func (c *Client) Collections( + ctx context.Context, + query *CollectionsQuery, +) ([]*Collection, error) { + var collections []*Collection + + err := c.Get(ctx, "app/collections/", query.URLValues(), &collections) + + return collections, err +} + +func (c *Client) GetCollection( + ctx context.Context, + collectionID string, +) (*Collection, error) { + if collectionID == "" { + return nil, ErrCollectionIDRequired + } + + q := &CollectionsQuery{CollectionID: collectionID} + + var cols []*Collection + + // Deletion of a collection is strangely done by setting the hidden flag to + // true. This is a bit confusing, but it's how the API works. + err := c.Get(ctx, "app/collections/", q.URLValues(), &cols) + + if len(cols) == 0 { + return nil, fmt.Errorf("%w: id=%s", ErrCollectionNotFound, collectionID) + } + + return cols[0], err +} + +func (c *Client) PutCollection( + ctx context.Context, + collection *Collection, +) (*Collection, error) { + var col *Collection + + err := c.Put(ctx, "app/collections/", nil, collection, col) + + return col, err +} + +func (c *Client) DeleteCollection( + ctx context.Context, + collectionID string, +) (*Collection, error) { + if collectionID == "" { + return nil, ErrCollectionIDRequired + } + + var col *Collection + + // Deletion of a collection is strangely done by setting the hidden flag to + // true. This is a bit confusing, but it's how the API works. + err := c.Put( + ctx, "app/collections/", nil, + &Collection{ID: collectionID, Hidden: true}, col, + ) + + return col, err +} diff --git a/recent_jobs.go b/recent_jobs.go index 104a081..52e47b3 100644 --- a/recent_jobs.go +++ b/recent_jobs.go @@ -33,6 +33,7 @@ type RecentJobsQuery struct { JobStatus JobStatus UserID string UserIDLiked string + CollectionID string FromDate time.Time Page int Prompt string @@ -64,6 +65,9 @@ func (rjq *RecentJobsQuery) URLValues() url.Values { if rjq.UserIDLiked != "" { v.Set("userIdLiked", rjq.UserIDLiked) } + if rjq.CollectionID != "" { + v.Set("collectionID", rjq.CollectionID) + } if !rjq.FromDate.IsZero() { v.Set("fromDate", rjq.FromDate.Format(FromDateFormat)) } @@ -183,3 +187,21 @@ func (c *Client) Bookmarks( Dedupe: true, }) } + +func (c *Client) CollectionFeed( + ctx context.Context, + collectionID string, +) (*RecentJobs, error) { + if collectionID == "" { + return nil, ErrCollectionIDRequired + } + + return c.RecentJobs(ctx, &RecentJobsQuery{ + Amount: 50, + JobType: JobTypeNull, + OrderBy: OrderNew, + JobStatus: JobStatusCompleted, + CollectionID: collectionID, + Dedupe: true, + }) +}