diff --git a/README.md b/README.md index af5eb08..13bb507 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ _Low Level API_ | Area | Complete | Comment | |:-----------------|:------------------:|:--------| | Authentication | :x: | | -| Posts | :x: | | +| Posts | :heavy_check_mark: | | | Tags | :heavy_minus_sign: | | | Tag aliases | :x: | | | Tag implications | :x: | | diff --git a/example/lowlevel/post.go b/example/lowlevel/post.go new file mode 100644 index 0000000..c522e00 --- /dev/null +++ b/example/lowlevel/post.go @@ -0,0 +1,40 @@ +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 (@username)", + Username: "", + APIKey: "", + } + + 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", + } + posts, err := endpoints.GetPosts(client, requestContext, query) + + if err != nil { + log.Println(err) + } + log.Println(len(posts)) + log.Println("----------") + +} diff --git a/pkg/e621/endpoints/post.go b/pkg/e621/endpoints/post.go index a182af2..b0f9d30 100644 --- a/pkg/e621/endpoints/post.go +++ b/pkg/e621/endpoints/post.go @@ -1,4 +1,103 @@ 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" +) + +func GetPost(client http.Client, requestContext model.RequestContext, ID string) (model.Post, error) { + // Create a new HTTP GET request to fetch user information from the specified 'host' and 'username'. + 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 User 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 User 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 User struct to store the response data. + var PostResponse model.PostResponse + + // Decode the JSON response into the model.PostResponse{} struct. + err = json.NewDecoder(resp.Body).Decode(&PostResponse) + if err != nil { + // Log the error and return an empty User struct and the error. + log.Println(err) + return model.Post{}, err + } + + // Return the user information and no error (nil). + return PostResponse.Post, nil +} + +func GetPosts(client http.Client, requestContext model.RequestContext, query map[string]string) ([]model.Post, error) { + // Create a new HTTP GET request. + r, err := http.NewRequest("GET", fmt.Sprintf("%s/posts.json", requestContext.Host), nil) + if err != nil { + log.Print(err) + } + + // 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() + + 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.Print(err) + } + + // Send the request using the provided HTTP client. + resp, err = client.Do(r) + if err != nil { + log.Print(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 User struct to store the response data. + var postResponse model.PostResponse + + // Decode the JSON response into the user struct. + err = json.NewDecoder(resp.Body).Decode(&postResponse) + if err != nil { + // Log the error and return an empty User struct and the error. + log.Println(err) + return nil, err + } + + return postResponse.Posts, nil + +} diff --git a/pkg/e621/endpoints/post_test.go b/pkg/e621/endpoints/post_test.go new file mode 100644 index 0000000..2de749a --- /dev/null +++ b/pkg/e621/endpoints/post_test.go @@ -0,0 +1,212 @@ +package endpoints + +import ( + "git.dragse.it/anthrove/e621-to-graph/pkg/e621/model" + "github.com/jarcoal/httpmock" + "net/http" + "testing" +) + +func TestGetPost(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + postResponse := model.PostResponse{ + Post: model.Post{ + ID: 658415636580, + 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, + }, + Posts: nil, + } + + jsonResponser, err := httpmock.NewJsonResponder(200, postResponse) + if err != nil { + t.Error(err) + return + } + httpmock.RegisterResponder("GET", "https://e621.net/posts/658415636580.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, "658415636580") + if err != nil { + t.Error(err) + return + } + + if post.ID == postResponse.Post.ID && 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() + + postResponse := model.PostResponse{ + Post: model.Post{}, + Posts: []model.Post{ + { + ID: 658415636580, + 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: 698418695, + 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: 48976516894, + 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.json", jsonResponser) + + requestContext := model.RequestContext{ + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: "memo", + APIKey: "123456", + } + + client := http.Client{} + posts, err := GetPosts(client, requestContext, map[string]string{}) + if err != nil { + t.Error(err) + return + } + + if len(posts) == 3 && posts[0].ID == postResponse.Posts[0].ID && posts[1].File.Md5 == postResponse.Posts[1].File.Md5 && posts[2].File.EXT == postResponse.Posts[2].File.EXT { + return + } + + t.Errorf("Respons did not match mock data:\nOriginal: %v\nMock: %v", posts, postResponse) + +} diff --git a/pkg/e621/model/post.go b/pkg/e621/model/post.go new file mode 100644 index 0000000..fd221fa --- /dev/null +++ b/pkg/e621/model/post.go @@ -0,0 +1,91 @@ +package model + +type PostResponse struct { + Post Post `json:"post"` + Posts []Post `json:"posts"` +} + +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 []string `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 []string `json:"character"` + Species []string `json:"species"` + Invalid []interface{} `json:"invalid"` + Meta []string `json:"meta"` + Lore []interface{} `json:"lore"` +}