From 4ac9657e323e8c3b246cc59cc909ba2106a8bd5c Mon Sep 17 00:00:00 2001 From: SoXX Date: Thu, 19 Oct 2023 14:20:05 +0200 Subject: [PATCH] feat: added getting of posts --- README.md | 24 ++-- example/lowlevel/post.go | 38 ++++++ pkg/e621/endpoints/post.go | 106 +++++++++++++++- pkg/e621/endpoints/post_test.go | 217 ++++++++++++++++++++++++++++++++ pkg/e621/model/post.go | 91 ++++++++++++++ 5 files changed, 462 insertions(+), 14 deletions(-) create mode 100644 example/lowlevel/post.go create mode 100644 pkg/e621/endpoints/post_test.go create mode 100644 pkg/e621/model/post.go diff --git a/README.md b/README.md index af5eb08..652278f 100644 --- a/README.md +++ b/README.md @@ -29,15 +29,15 @@ _High Level API_ _Low Level API_ -| Area | Complete | Comment | -|:-----------------|:------------------:|:--------| -| Authentication | :x: | | -| Posts | :x: | | -| Tags | :heavy_minus_sign: | | -| Tag aliases | :x: | | -| Tag implications | :x: | | -| Notes | :x: | | -| Pools | :x: | | -| Users | :heavy_check_mark: | | -| Favorites | :x: | | -| DB export | :x: | | \ No newline at end of file +| Area | Get | Set | +|:-----------------|:------------------:|:----| +| Authentication | :x: | | +| Posts | :heavy_check_mark: | | +| Tags | :heavy_minus_sign: | | +| Tag aliases | :x: | | +| Tag implications | :x: | | +| Notes | :x: | | +| Pools | :x: | | +| Users | :heavy_check_mark: | | +| Favorites | :x: | | +| DB export | :x: | | \ No newline at end of file diff --git a/example/lowlevel/post.go b/example/lowlevel/post.go new file mode 100644 index 0000000..06eba07 --- /dev/null +++ b/example/lowlevel/post.go @@ -0,0 +1,38 @@ +package main + +import ( + "git.dragse.it/anthrove/e621-to-graph/pkg/e621/endpoints" + "git.dragse.it/anthrove/e621-to-graph/pkg/e621/model" + "log" + "net/http" +) + +func main() { + requestContext := model.RequestContext{ + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (selloo)", + Username: "selloo", + APIKey: "zt7XAaewLqP4AWvQi5WrLNwN", + } + + log.Println("Getting single post: ") + client := http.Client{} + post, err := endpoints.GetPost(client, requestContext, "4353480") + if err != nil { + log.Println(err) + } + log.Println(post.File.URL) + log.Println("----------") + + log.Println("Getting list of posts: ") + query := map[string]string{ + "limit": "5", + } + + postList, err := endpoints.GetPosts(client, requestContext, query) + if err != nil { + log.Println(err) + } + log.Println(len(postList)) + log.Println("----------") +} diff --git a/pkg/e621/endpoints/post.go b/pkg/e621/endpoints/post.go index a182af2..c023dbd 100644 --- a/pkg/e621/endpoints/post.go +++ b/pkg/e621/endpoints/post.go @@ -1,4 +1,106 @@ package endpoints -// Get all users (); endpint to use is e621.net/users.json -// Get specific user +import ( + "encoding/json" + "fmt" + "git.dragse.it/anthrove/e621-to-graph/internal/utils" + "git.dragse.it/anthrove/e621-to-graph/pkg/e621/model" + "log" + "net/http" +) + +// GetPost allows you to get a post by ID +func GetPost(client http.Client, requestContext model.RequestContext, ID string) (model.Post, error) { + + // Create a new HTTP GET request to fetch Tag information from the specified 'host' and 'ID'. + r, err := http.NewRequest("GET", fmt.Sprintf("%s/posts/%s.json", requestContext.Host, ID), nil) + if err != nil { + // Log the error and return an empty Tag struct and the error. + log.Println(err) + return model.Post{}, err + } + + r.Header.Set("User-Agent", requestContext.UserAgent) + r.Header.Add("Accept", "application/json") + r.SetBasicAuth(requestContext.Username, requestContext.APIKey) + + // Send the request using the provided HTTP client. + resp, err := client.Do(r) + if err != nil { + // Log the error and return an empty Tag struct and the error. + log.Println(err) + return model.Post{}, err + } + + // Check if the HTTP response status code indicates success (2xx range). + if resp.StatusCode < 200 || resp.StatusCode > 300 { + // If the status code is outside the 2xx range, return an error based on the status code. + return model.Post{}, utils.StatusCodesToError(resp.StatusCode) + } + + // Initialize a Tag struct to store the response data. + var post model.PostResponse + + // Decode the JSON response into the Tag struct. + err = json.NewDecoder(resp.Body).Decode(&post) + + if err != nil { + // Log the error and return an empty Tag struct and the error. + log.Println(err) + return model.Post{}, err + } + + // Return the Tag information and no error (nil). + return post.Post, nil +} + +// GetPosts allows you to get a list of posts +func GetPosts(client http.Client, requestContext model.RequestContext, query map[string]string) ([]model.Post, error) { + + // Create a new HTTP GET request to fetch Tag information from the specified 'host' and 'ID'. + r, err := http.NewRequest("GET", fmt.Sprintf("%s/posts.json", requestContext.Host), nil) + if err != nil { + // Log the error and return an empty Tag struct and the error. + log.Println(err) + return nil, err + } + + r.Header.Set("User-Agent", requestContext.UserAgent) + r.Header.Add("Accept", "application/json") + r.SetBasicAuth(requestContext.Username, requestContext.APIKey) + + // Append query parameters to the request URL. + q := r.URL.Query() + for k, v := range query { + q.Add(k, v) + } + r.URL.RawQuery = q.Encode() + + // Send the request using the provided HTTP client. + resp, err := client.Do(r) + if err != nil { + // Log the error and return an empty Tag struct and the error. + log.Println(err) + return nil, err + } + + // Check if the HTTP response status code indicates success (2xx range). + if resp.StatusCode < 200 || resp.StatusCode > 300 { + // If the status code is outside the 2xx range, return an error based on the status code. + return nil, utils.StatusCodesToError(resp.StatusCode) + } + + // Initialize a Tag struct to store the response data. + var posts model.PostResponse + + // Decode the JSON response into the Tag struct. + err = json.NewDecoder(resp.Body).Decode(&posts) + if err != nil { + // Log the error and return an empty Tag struct and the error. + log.Println(err) + return nil, err + } + + // Return the Tag information and no error (nil). + return posts.Posts, nil +} diff --git a/pkg/e621/endpoints/post_test.go b/pkg/e621/endpoints/post_test.go new file mode 100644 index 0000000..60bf2ae --- /dev/null +++ b/pkg/e621/endpoints/post_test.go @@ -0,0 +1,217 @@ +package endpoints + +import ( + "git.dragse.it/anthrove/e621-to-graph/pkg/e621/model" + "github.com/jarcoal/httpmock" + "log" + "net/http" + "testing" +) + +func TestGetPost(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + postResponse := model.PostResponse{ + Posts: nil, + Post: model.Post{ + ID: 5316565561, + CreatedAt: "2023-10-15T03:58:27.272-04:00", + UpdatedAt: "2023-10-19T02:47:33.597-04:00", + File: model.File{ + Width: 759, + Height: 980, + EXT: "jpg", + Size: 640942, + Md5: "36e229e910638c7aebe65a500f16f3ee", + URL: "https://static1.e621.net/data/36/e2/36e229e910638c7aebe65a500f16f3ee.jpg", + }, + Preview: model.Preview{}, + Sample: model.Sample{}, + Score: model.Score{}, + Tags: model.Tags{}, + LockedTags: nil, + ChangeSeq: 0, + Flags: model.Flags{}, + Rating: "", + FavCount: 0, + Sources: nil, + Pools: nil, + Relationships: model.Relationships{}, + ApproverID: nil, + UploaderID: 0, + Description: "", + CommentCount: 0, + IsFavorited: false, + HasNotes: false, + Duration: nil, + }, + } + jsonResponser, err := httpmock.NewJsonResponder(200, postResponse) + if err != nil { + t.Error(err) + return + } + httpmock.RegisterResponder("GET", "https://e621.net/posts/5316565561.json", jsonResponser) + + requestContext := model.RequestContext{ + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: "memo", + APIKey: "123456", + } + + client := http.Client{} + post, err := GetPost(client, requestContext, "5316565561") + if err != nil { + t.Error(err) + return + } + + if post.ID == postResponse.Post.ID && post.CreatedAt == postResponse.Post.CreatedAt && post.UpdatedAt == postResponse.Post.UpdatedAt && post.File.URL == postResponse.Post.File.URL { + return + } + + t.Errorf("Respons did not match mock data:\nOriginal: %v\nMock: %v", post, postResponse) + +} + +func TestGetPosts(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + postList := []model.Post{ + { + ID: 5316565561, + CreatedAt: "2023-10-15T03:58:27.272-04:00", + UpdatedAt: "2023-10-19T02:47:33.597-04:00", + File: model.File{ + Width: 759, + Height: 980, + EXT: "jpg", + Size: 640942, + Md5: "36e229e910638c7aebe65a500f16f3ee", + URL: "https://static1.e621.net/data/36/e2/36e229e910638c7aebe65a500f16f3ee.jpg", + }, + Preview: model.Preview{}, + Sample: model.Sample{}, + Score: model.Score{}, + Tags: model.Tags{}, + LockedTags: nil, + ChangeSeq: 0, + Flags: model.Flags{}, + Rating: "", + FavCount: 0, + Sources: nil, + Pools: nil, + Relationships: model.Relationships{}, + ApproverID: nil, + UploaderID: 0, + Description: "", + CommentCount: 0, + IsFavorited: false, + HasNotes: false, + Duration: nil, + }, + { + ID: 5468564521685, + CreatedAt: "2023-10-15T03:58:27.272-04:00", + UpdatedAt: "2023-10-19T02:47:33.597-04:00", + File: model.File{ + Width: 759, + Height: 980, + EXT: "jpg", + Size: 640942, + Md5: "36e229e910638c7aebe65a500f16f3ee", + URL: "https://static1.e621.net/data/36/e2/36e229e910638c7aebe65a500f16f3ee.jpg", + }, + Preview: model.Preview{}, + Sample: model.Sample{}, + Score: model.Score{}, + Tags: model.Tags{}, + LockedTags: nil, + ChangeSeq: 0, + Flags: model.Flags{}, + Rating: "", + FavCount: 0, + Sources: nil, + Pools: nil, + Relationships: model.Relationships{}, + ApproverID: nil, + UploaderID: 0, + Description: "", + CommentCount: 0, + IsFavorited: false, + HasNotes: false, + Duration: nil, + }, + { + ID: 68546984, + CreatedAt: "2023-10-15T03:58:27.272-04:00", + UpdatedAt: "2023-10-19T02:47:33.597-04:00", + File: model.File{ + Width: 759, + Height: 980, + EXT: "jpg", + Size: 640942, + Md5: "36e229e910638c7aebe65a500f16f3ee", + URL: "https://static1.e621.net/data/36/e2/36e229e910638c7aebe65a500f16f3ee.jpg", + }, + Preview: model.Preview{}, + Sample: model.Sample{}, + Score: model.Score{}, + Tags: model.Tags{}, + LockedTags: nil, + ChangeSeq: 0, + Flags: model.Flags{}, + Rating: "", + FavCount: 0, + Sources: nil, + Pools: nil, + Relationships: model.Relationships{}, + ApproverID: nil, + UploaderID: 0, + Description: "", + CommentCount: 0, + IsFavorited: false, + HasNotes: false, + Duration: nil, + }, + } + + postResponse := model.PostResponse{ + Posts: postList, + Post: model.Post{}, + } + + jsonResponser, err := httpmock.NewJsonResponder(200, postResponse) + if err != nil { + t.Error(err) + return + } + httpmock.RegisterResponder("GET", "https://e621.net/posts.json", jsonResponser) + + requestContext := model.RequestContext{ + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: "memo", + APIKey: "123456", + } + + client := http.Client{} + post, err := GetPosts(client, requestContext, map[string]string{}) + if err != nil { + t.Error(err) + return + } + + log.Println(len(post)) + log.Println(len(postResponse.Posts)) + + if len(post) == len(postResponse.Posts) && post[0].ID == postResponse.Posts[0].ID && post[1].ID == postResponse.Posts[1].ID && post[2].File.URL == postResponse.Posts[2].File.URL { + return + } + + t.Errorf("Respons did not match mock data:\nOriginal: %v\nMock: %v", post, postResponse) + +} diff --git a/pkg/e621/model/post.go b/pkg/e621/model/post.go new file mode 100644 index 0000000..76ddad3 --- /dev/null +++ b/pkg/e621/model/post.go @@ -0,0 +1,91 @@ +package model + +type PostResponse struct { + Posts []Post `json:"posts"` + Post Post `json:"post"` +} + +type Post struct { + ID int64 `json:"id"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + File File `json:"file"` + Preview Preview `json:"preview"` + Sample Sample `json:"sample"` + Score Score `json:"score"` + Tags Tags `json:"tags"` + LockedTags []interface{} `json:"locked_tags"` + ChangeSeq int64 `json:"change_seq"` + Flags Flags `json:"flags"` + Rating string `json:"rating"` + FavCount int64 `json:"fav_count"` + Sources []interface{} `json:"sources"` + Pools []interface{} `json:"pools"` + Relationships Relationships `json:"relationships"` + ApproverID interface{} `json:"approver_id"` + UploaderID int64 `json:"uploader_id"` + Description string `json:"description"` + CommentCount int64 `json:"comment_count"` + IsFavorited bool `json:"is_favorited"` + HasNotes bool `json:"has_notes"` + Duration interface{} `json:"duration"` +} + +type File struct { + Width int64 `json:"width"` + Height int64 `json:"height"` + EXT string `json:"ext"` + Size int64 `json:"size"` + Md5 string `json:"md5"` + URL string `json:"url"` +} + +type Flags struct { + Pending bool `json:"pending"` + Flagged bool `json:"flagged"` + NoteLocked bool `json:"note_locked"` + StatusLocked bool `json:"status_locked"` + RatingLocked bool `json:"rating_locked"` + Deleted bool `json:"deleted"` +} + +type Preview struct { + Width int64 `json:"width"` + Height int64 `json:"height"` + URL string `json:"url"` +} + +type Relationships struct { + ParentID interface{} `json:"parent_id"` + HasChildren bool `json:"has_children"` + HasActiveChildren bool `json:"has_active_children"` + Children []interface{} `json:"children"` +} + +type Sample struct { + Has bool `json:"has"` + Height int64 `json:"height"` + Width int64 `json:"width"` + URL string `json:"url"` + Alternates Alternates `json:"alternates"` +} + +type Alternates struct { +} + +type Score struct { + Up int64 `json:"up"` + Down int64 `json:"down"` + Total int64 `json:"total"` +} + +type Tags struct { + General []string `json:"general"` + Artist []string `json:"artist"` + Copyright []interface{} `json:"copyright"` + Character []interface{} `json:"character"` + Species []string `json:"species"` + Invalid []interface{} `json:"invalid"` + Meta []string `json:"meta"` + Lore []interface{} `json:"lore"` +}