From e89ab87372747dac1472877f9c0173a884453875 Mon Sep 17 00:00:00 2001 From: Mark Cornick Date: Mon, 27 Feb 2023 21:36:30 -0500 Subject: [PATCH 1/3] Add Pastebin support Signed-off-by: Mark Cornick --- README.md | 4 ++ omglol/models.go | 6 ++ omglol/paste.go | 166 +++++++++++++++++++++++++++++++++++++++++++ omglol/paste_test.go | 90 +++++++++++++++++++++++ 4 files changed, 266 insertions(+) create mode 100644 omglol/paste.go create mode 100644 omglol/paste_test.go diff --git a/README.md b/README.md index 5418f24..669ea77 100644 --- a/README.md +++ b/README.md @@ -57,5 +57,9 @@ This project is a work-in-progress, see the following table for supported featur ||Retrieve|GET|✔️| ||Create|POST|✔️| ||Delete|DELETE|✔️| +|Paste|List|GET|✔️| +||Retrieve|GET|✔️| +||Create|POST|✔️| +||Delete|DELETE|✔️| >**Note** Features marked with a * are additional to what the API provides diff --git a/omglol/models.go b/omglol/models.go index 49f44fb..7ccf682 100644 --- a/omglol/models.go +++ b/omglol/models.go @@ -145,6 +145,12 @@ type dnsChangeResponse struct { } `json:"response"` } +type Paste struct { + Title string `json:"title"` + Content string `json:"content"` + ModifiedOn int64 `json:"modified_on"` +} + type PersistentURL struct { Name string `json:"name"` URL string `json:"url"` diff --git a/omglol/paste.go b/omglol/paste.go new file mode 100644 index 0000000..ce98633 --- /dev/null +++ b/omglol/paste.go @@ -0,0 +1,166 @@ +package omglol + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "strings" +) + +// Create a Paste object +func NewPaste(Title, Content string, ModifiedOn int64) *Paste { + return &Paste{ + Title: Title, + Content: Content, + ModifiedOn: ModifiedOn, + } +} + +// Returns a string representaion of a Paste +func (p *Paste) String() string { + return fmt.Sprintf("Title: %s, Content: %s, ModifiedOn: %d", p.Title, p.Content, p.ModifiedOn) +} + +// Create a new Paste. See https://api.omg.lol/#token-post-pastes-create-a-new-paste +func (c *Client) CreatePaste(domain string, paste Paste) error { + type pasteRequest struct { + Title string `json:"title"` + Content string `json:"content"` + } + + p := pasteRequest{ + Title: paste.Title, + Content: paste.Content, + } + + jsonData, err := json.Marshal(p) + if err != nil { + return err + } + + req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/address/%s/pastebin", c.HostURL, domain), bytes.NewBuffer([]byte(jsonData))) + if err != nil { + return err + } + + body, err := c.doRequest(req) + if err != nil { + return fmt.Errorf("sent: %s, error: %w", jsonData, err) + } + + var r apiResponse + if err := json.Unmarshal(body, &r); err != nil { + fmt.Printf("error unmarshalling response: %v\n", err) + return err + } + + return nil +} + +// Get a specific paste. See https://api.omg.lol/#noauth-get-pastebin-retrieve-a-specific-paste +func (c *Client) GetPaste(domain string, pasteTitle string) (*Paste, error) { + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/address/%s/pastebin/%s", c.HostURL, domain, pasteTitle), nil) + if err != nil { + return nil, err + } + + body, err := c.doRequest(req) + if err != nil { + return nil, err + } + + type getpasteResponse struct { + Request struct { + StatusCode int64 `json:"status_code"` + Success bool `json:"success"` + } `json:"request"` + Response struct { + Message string `json:"message"` + Paste struct { + Title string `json:"title"` + Content string `json:"content"` + ModifiedOn int64 `json:"modified_on"` + } `json:"paste"` + } `json:"response"` + } + + var g getpasteResponse + if err := json.Unmarshal(body, &g); err != nil { + fmt.Printf("Error unmarshalling response: %v\n", err) + return nil, err + } + + return NewPaste(g.Response.Paste.Title, g.Response.Paste.Content, g.Response.Paste.ModifiedOn), nil +} + +// Retrieve a list of pastes associated with an address. See https://api.omg.lol/#token-get-pastebin-retrieve-an-entire-pastebin +func (c *Client) ListPastes(address string) (*[]Paste, error) { + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/address/%s/pastebin", c.HostURL, address), nil) + if err != nil { + return nil, err + } + + var p []Paste + + body, err := c.doRequest(req) + if err != nil { + if strings.Contains(err.Error(), "status: 404") { + return &p, nil + } + return nil, err + } + + type listpasteResponse struct { + Request struct { + StatusCode int64 `json:"status_code"` + Success bool `json:"success"` + } `json:"request"` + Response struct { + Message string `json:"message"` + Pastebin []struct { + Title string `json:"title"` + Content string `json:"content"` + ModifiedOn int64 `json:"modified_on"` + } `json:"pastebin"` + } `json:"response"` + } + + var r listpasteResponse + if err := json.Unmarshal(body, &r); err != nil { + fmt.Printf("Error unmarshalling response: %v\n", err) + return nil, err + } + + for _, paste := range r.Response.Pastebin { + var x Paste + + x.Title = paste.Title + x.Content = paste.Content + x.ModifiedOn = paste.ModifiedOn + p = append(p, x) + } + + return &p, nil +} + +// Permanently delete a paste. See https://api.omg.lol/#token-delete-pastebin-delete-a-paste-from-a-pastebin +func (c *Client) DeletePaste(domain string, pasteTitle string) error { + req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/address/%s/pastebin/%s", c.HostURL, domain, pasteTitle), nil) + if err != nil { + return err + } + + body, err := c.doRequest(req) + if err != nil { + return err + } + + var response apiResponse + if err := json.Unmarshal(body, &response); err != nil { + fmt.Printf("Error unmarshalling response: %v\n", err) + return err + } + + return nil +} diff --git a/omglol/paste_test.go b/omglol/paste_test.go new file mode 100644 index 0000000..2a2c0bc --- /dev/null +++ b/omglol/paste_test.go @@ -0,0 +1,90 @@ +package omglol + +import ( + "testing" +) + +func validatePaste(t *testing.T, p Paste) { + if len(p.Title) <= 0 { + t.Error("Paste Title is empty.") + } + if len(p.Content) <= 0 { + t.Error("Paste Content is empty.") + } +} + +func TestGetPaste(t *testing.T) { + sleep() + c, err := NewClient(testEmail, testKey, testHostURL) + if err != nil { + t.Errorf(err.Error()) + } + + p, err := c.GetPaste(testOwnedDomain, "testget") + if err != nil { + t.Errorf(err.Error()) + } + + if p != nil { + t.Logf(p.String()) + validatePaste(t, *p) + } else { + t.Error("GetPaste returned 'nil'.") + } +} + +func TestListPastes(t *testing.T) { + sleep() + c, err := NewClient(testEmail, testKey, testHostURL) + if err != nil { + t.Errorf(err.Error()) + } + + l, err := c.ListPastes(testOwnedDomain) + if err != nil { + t.Errorf(err.Error()) + } + + if l != nil { + for _, p := range *l { + t.Logf(p.String() + "\n") + validatePaste(t, p) + } + } else { + t.Error("ListPastes returned 'nil'.") + } +} + +func TestCreateAndDeletePaste(t *testing.T) { + sleep() + c, err := NewClient(testEmail, testKey, testHostURL) + if err != nil { + t.Errorf(err.Error()) + } + + title := "test" + RunUID + + paste := NewPaste(title, "example paste content", 0) + + err = c.CreatePaste(testOwnedDomain, *paste) + if err != nil { + t.Errorf(err.Error()) + } + + u, err := c.GetPaste(testOwnedDomain, title) + if err != nil { + t.Errorf(err.Error()) + } + + if u != nil { + t.Log(u.String()) + validatePaste(t, *u) + } else { + t.Error("GetPaste returned 'nil' when retrieving paste.") + } + + err = c.DeletePaste(testOwnedDomain, title) + if err != nil { + t.Errorf(err.Error()) + } +} From bd357fa18121e526d9290c30ce384dee440bfd65 Mon Sep 17 00:00:00 2001 From: Mark Cornick Date: Tue, 28 Feb 2023 16:05:25 -0500 Subject: [PATCH 2/3] Updates to handle Listed attribute and others Co-authored-by: Elliott Street Signed-off-by: Mark Cornick --- omglol/models.go | 3 ++- omglol/paste.go | 46 +++++++++++++++++++++++++++++++++---------- omglol/paste_test.go | 47 ++++++++++++++++++++++++++++++++++++-------- 3 files changed, 77 insertions(+), 19 deletions(-) diff --git a/omglol/models.go b/omglol/models.go index 7ccf682..24124f9 100644 --- a/omglol/models.go +++ b/omglol/models.go @@ -148,7 +148,8 @@ type dnsChangeResponse struct { type Paste struct { Title string `json:"title"` Content string `json:"content"` - ModifiedOn int64 `json:"modified_on"` + ModifiedOn *int64 `json:"modified_on"` + Listed bool `json:"listed"` } type PersistentURL struct { diff --git a/omglol/paste.go b/omglol/paste.go index ce98633..dba82e3 100644 --- a/omglol/paste.go +++ b/omglol/paste.go @@ -9,17 +9,24 @@ import ( ) // Create a Paste object -func NewPaste(Title, Content string, ModifiedOn int64) *Paste { +func NewPaste(Title, Content string, Listed bool, ModifiedOn ...int64) *Paste { + var modified *int64 + if len(ModifiedOn) > 0 { + m := ModifiedOn[0] + modified = &m + } + return &Paste{ Title: Title, Content: Content, - ModifiedOn: ModifiedOn, + Listed: Listed, + ModifiedOn: modified, } } // Returns a string representaion of a Paste func (p *Paste) String() string { - return fmt.Sprintf("Title: %s, Content: %s, ModifiedOn: %d", p.Title, p.Content, p.ModifiedOn) + return fmt.Sprintf("Title: %s, Content: %s, Listed: %t, ModifiedOn: %d", p.Title, p.Content, p.Listed, p.ModifiedOn) } // Create a new Paste. See https://api.omg.lol/#token-post-pastes-create-a-new-paste @@ -27,6 +34,7 @@ func (c *Client) CreatePaste(domain string, paste Paste) error { type pasteRequest struct { Title string `json:"title"` Content string `json:"content"` + Listed *bool `json:"listed"` } p := pasteRequest{ @@ -34,6 +42,13 @@ func (c *Client) CreatePaste(domain string, paste Paste) error { Content: paste.Content, } + if !paste.Listed { + p.Listed = nil + } else { + t := true + p.Listed = &t + } + jsonData, err := json.Marshal(p) if err != nil { return err @@ -81,6 +96,7 @@ func (c *Client) GetPaste(domain string, pasteTitle string) (*Paste, error) { Title string `json:"title"` Content string `json:"content"` ModifiedOn int64 `json:"modified_on"` + Listed *int64 `json:"listed"` } `json:"paste"` } `json:"response"` } @@ -91,7 +107,14 @@ func (c *Client) GetPaste(domain string, pasteTitle string) (*Paste, error) { return nil, err } - return NewPaste(g.Response.Paste.Title, g.Response.Paste.Content, g.Response.Paste.ModifiedOn), nil + var listed bool + if g.Response.Paste.Listed != nil { + listed = true + } else { + listed = false + } + + return NewPaste(g.Response.Paste.Title, g.Response.Paste.Content, listed, g.Response.Paste.ModifiedOn), nil } // Retrieve a list of pastes associated with an address. See https://api.omg.lol/#token-get-pastebin-retrieve-an-entire-pastebin @@ -105,6 +128,7 @@ func (c *Client) ListPastes(address string) (*[]Paste, error) { body, err := c.doRequest(req) if err != nil { + // Return an empty list instead of erroring if no pastes exist if strings.Contains(err.Error(), "status: 404") { return &p, nil } @@ -117,12 +141,8 @@ func (c *Client) ListPastes(address string) (*[]Paste, error) { Success bool `json:"success"` } `json:"request"` Response struct { - Message string `json:"message"` - Pastebin []struct { - Title string `json:"title"` - Content string `json:"content"` - ModifiedOn int64 `json:"modified_on"` - } `json:"pastebin"` + Message string `json:"message"` + Pastebin []Paste `json:"pastebin"` } `json:"response"` } @@ -138,6 +158,12 @@ func (c *Client) ListPastes(address string) (*[]Paste, error) { x.Title = paste.Title x.Content = paste.Content x.ModifiedOn = paste.ModifiedOn + if !paste.Listed { + x.Listed = false + } else { + x.Listed = true + } + p = append(p, x) } diff --git a/omglol/paste_test.go b/omglol/paste_test.go index 2a2c0bc..a0dd276 100644 --- a/omglol/paste_test.go +++ b/omglol/paste_test.go @@ -8,8 +8,8 @@ func validatePaste(t *testing.T, p Paste) { if len(p.Title) <= 0 { t.Error("Paste Title is empty.") } - if len(p.Content) <= 0 { - t.Error("Paste Content is empty.") + if *p.ModifiedOn <= 0 { + t.Error("ModifiedOn time is unset.") } } @@ -62,16 +62,16 @@ func TestCreateAndDeletePaste(t *testing.T) { t.Errorf(err.Error()) } - title := "test" + RunUID + unlistedTitle := "unlistedtest" + RunUID - paste := NewPaste(title, "example paste content", 0) + unlistedPaste := NewPaste(unlistedTitle, "example paste content", false) - err = c.CreatePaste(testOwnedDomain, *paste) + err = c.CreatePaste(testOwnedDomain, *unlistedPaste) if err != nil { t.Errorf(err.Error()) } - u, err := c.GetPaste(testOwnedDomain, title) + u, err := c.GetPaste(testOwnedDomain, unlistedTitle) if err != nil { t.Errorf(err.Error()) } @@ -79,11 +79,42 @@ func TestCreateAndDeletePaste(t *testing.T) { if u != nil { t.Log(u.String()) validatePaste(t, *u) + if u.Listed != false { + t.Error("Unlisted paste should have Listed value 'false'.") + } + } else { + t.Error("GetPaste returned 'nil' when retrieving unlisted paste.") + } + + err = c.DeletePaste(testOwnedDomain, unlistedTitle) + if err != nil { + t.Errorf(err.Error()) + } + sleep() + listedTitle := "listedtest" + RunUID + listedPaste := NewPaste(listedTitle, "example paste content", true) + + err = c.CreatePaste(testOwnedDomain, *listedPaste) + if err != nil { + t.Errorf(err.Error()) + } + + l, err := c.GetPaste(testOwnedDomain, listedTitle) + if err != nil { + t.Errorf(err.Error()) + } + + if l != nil { + t.Log(l.String()) + validatePaste(t, *l) + if l.Listed != true { + t.Error("Listed paste should have Listed value 'true'.") + } } else { - t.Error("GetPaste returned 'nil' when retrieving paste.") + t.Error("GetPaste returned 'nil' when retrieving listed paste.") } - err = c.DeletePaste(testOwnedDomain, title) + err = c.DeletePaste(testOwnedDomain, listedTitle) if err != nil { t.Errorf(err.Error()) } From 7b1794ab833bab0d2726b36aec8012c5dd02a564 Mon Sep 17 00:00:00 2001 From: Elliott Street Date: Tue, 28 Feb 2023 23:46:57 -0700 Subject: [PATCH 3/3] Fix ListPaste I realised from my local testing that the response couldn't use the Paste type, since the response returns an *int rather than a bool. I also found that using the loop assignment method meant entries were being replicated (reusing the same pointer). Using the NewPaste function in the loop fixes this --- omglol/paste.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/omglol/paste.go b/omglol/paste.go index dba82e3..48507ab 100644 --- a/omglol/paste.go +++ b/omglol/paste.go @@ -142,7 +142,12 @@ func (c *Client) ListPastes(address string) (*[]Paste, error) { } `json:"request"` Response struct { Message string `json:"message"` - Pastebin []Paste `json:"pastebin"` + Pastebin []struct { + Title string `json:"title"` + Content string `json:"content"` + ModifiedOn int64 `json:"modified_on"` + Listed *int64 `json:"listed"` + } `json:"pastebin"` } `json:"response"` } @@ -153,18 +158,15 @@ func (c *Client) ListPastes(address string) (*[]Paste, error) { } for _, paste := range r.Response.Pastebin { - var x Paste - x.Title = paste.Title - x.Content = paste.Content - x.ModifiedOn = paste.ModifiedOn - if !paste.Listed { - x.Listed = false + var listed bool + if paste.Listed != nil { + listed = true } else { - x.Listed = true + listed = false } - p = append(p, x) + p = append(p, *NewPaste(paste.Title, paste.Content, listed, paste.ModifiedOn)) } return &p, nil