From 3fac0d046f78cd313347273ef8dbcb8330ecb65b Mon Sep 17 00:00:00 2001 From: Thomas Lynch Date: Thu, 17 Nov 2022 22:46:59 +1100 Subject: [PATCH] Allow passing embedded "ReturnHeaders" object to get response headers in handlers if necessary e.g. captcha cookie? Parse set-cookie header and store session cookie in client if set-cookie contains connect.sid cookie Remove some json definitions for option arguments to client methods --- app/auth.go | 40 ++++++++++++++++++++++++++++++++++++++++ app/boardlist.go | 20 ++++++++++---------- app/jschan.go | 42 +++++++++++++++++++++++++++++++++++++----- app/overboard.go | 14 +++++++------- example.go | 21 ++++++++++++++++----- 5 files changed, 110 insertions(+), 27 deletions(-) create mode 100644 app/auth.go diff --git a/app/auth.go b/app/auth.go new file mode 100644 index 0000000..5748671 --- /dev/null +++ b/app/auth.go @@ -0,0 +1,40 @@ +package jschan + +import ( + "context" + "fmt" + "net/http" + "net/url" + "strings" +) + +type PostLoginOptions struct { + Username string + Password string + Twofactor string +} + +func (c *Client) Login(ctx context.Context, options *PostLoginOptions) error { + + formData := url.Values{} + formData.Set("username", options.Username) + formData.Set("password", options.Password) + formData.Set("twofactor", options.Twofactor) + endodedBody := strings.NewReader(formData.Encode()) + + url := fmt.Sprintf("%s/forms/login", c.BaseURL) + + req, err := http.NewRequest(http.MethodPost, url, endodedBody) + if err != nil { + return err + } + + req = req.WithContext(ctx) + + if err := c.sendRequest(req, nil, nil); err != nil { + return err + } + + return nil + +} diff --git a/app/boardlist.go b/app/boardlist.go index e36d835..fa56d2a 100644 --- a/app/boardlist.go +++ b/app/boardlist.go @@ -15,12 +15,12 @@ type GetBoardsResponse struct { } type GetBoardsPublicOptions struct { - Search string `json:"search"` - Sort string `json:"sort"` - SortDirection string `json:"direction"` - Page int `json:"page"` - LocalFirst bool `json:"local_first"` - Sites []string `json:"sites"` + Search string + Sort string + SortDirection string + Page int + LocalFirst bool + Sites []string } func (c *Client) GetBoardsPublic(ctx context.Context, options *GetBoardsPublicOptions) (*GetBoardsResponse, error) { @@ -54,7 +54,7 @@ func (c *Client) GetBoardsPublic(ctx context.Context, options *GetBoardsPublicOp url := fmt.Sprintf("%s/boards.json?%s", c.BaseURL, query.Encode()) - req, err := http.NewRequest("GET", url, nil) + req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, err } @@ -62,7 +62,7 @@ func (c *Client) GetBoardsPublic(ctx context.Context, options *GetBoardsPublicOp req = req.WithContext(ctx) res := GetBoardsResponse{} - if err := c.sendRequest(req, &res); err != nil { + if err := c.sendRequest(req, &res, nil); err != nil { return nil, err } @@ -116,7 +116,7 @@ func (c *Client) GetBoardsGlobalmanage(ctx context.Context, options *GetBoardsGl url := fmt.Sprintf("%s/boards.json?%s", c.BaseURL, query.Encode()) - req, err := http.NewRequest("GET", url, nil) + req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, err @@ -125,7 +125,7 @@ func (c *Client) GetBoardsGlobalmanage(ctx context.Context, options *GetBoardsGl req = req.WithContext(ctx) res := GetBoardsResponse{} - if err := c.sendRequest(req, &res); err != nil { + if err := c.sendRequest(req, &res, nil); err != nil { return nil, err } diff --git a/app/jschan.go b/app/jschan.go index 920bfd8..bdf63e4 100644 --- a/app/jschan.go +++ b/app/jschan.go @@ -22,6 +22,9 @@ func NewClient(baseURL string) *Client { CsrfToken: "", HTTPClient: &http.Client{ Timeout: time.Minute, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, }, } } @@ -32,9 +35,27 @@ type DynamicResponse struct { Redirect string `json:"redirect,omitempty"` } -func (c *Client) sendRequest(req *http.Request, v interface{}) error { +type ReturnHeaders struct { + *http.Header +} + +func cookieHeader(rawCookies string) []*http.Cookie { + header := http.Header{} + header.Add("Cookie", rawCookies) + req := http.Request{Header: header} + return req.Cookies() +} + +func (c *Client) sendRequest(req *http.Request, v interface{}, h *ReturnHeaders) error { + + if req.Method == http.MethodGet { + req.Header.Set("Content-Type", "application/json; charset=utf-8") + } else { + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + } req.Header.Set("Accept", "application/json; charset=utf-8") - //req.Header.Set("Content-Type", "application/json; charset=utf-8") + req.Header.Set("X-Using-XHR", "true") + req.Header.Set("Referer", c.BaseURL) if c.SessionCookie != "" { req.Header.Set("Cookie", fmt.Sprintf("connect.sid=%s", c.SessionCookie)) } @@ -56,9 +77,20 @@ func (c *Client) sendRequest(req *http.Request, v interface{}) error { return fmt.Errorf("unknown error, status code: %d", res.StatusCode) } - fullResponse := v - if err = json.NewDecoder(res.Body).Decode(&fullResponse); err != nil { - return err + h = &ReturnHeaders{ + &res.Header, + } + setCookieValue := h.Get("Set-Cookie") + if setCookieValue != "" { + parsedSetCookie := cookieHeader(setCookieValue) + c.SessionCookie = parsedSetCookie[0].Value + } + + if v != nil { + fullResponse := v + if err = json.NewDecoder(res.Body).Decode(&fullResponse); err != nil { + return err + } } return nil diff --git a/app/overboard.go b/app/overboard.go index 8b63dcc..4be003d 100644 --- a/app/overboard.go +++ b/app/overboard.go @@ -10,9 +10,9 @@ import ( ) type GetOverboardOptions struct { - AddBoards []string `json:"search"` - RemoveBoards []string `json:"sort"` - IncludeDefault bool `json:"include_default"` + AddBoards []string + RemoveBoards []string + IncludeDefault bool } type GetOverboardResponse struct { @@ -45,7 +45,7 @@ func (c *Client) GetOverboardIndex(ctx context.Context, options *GetOverboardOpt url := fmt.Sprintf("%s/overboard.json?%s", c.BaseURL, query.Encode()) - req, err := http.NewRequest("GET", url, nil) + req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, err } @@ -53,7 +53,7 @@ func (c *Client) GetOverboardIndex(ctx context.Context, options *GetOverboardOpt req = req.WithContext(ctx) res := GetOverboardResponse{} - if err := c.sendRequest(req, &res); err != nil { + if err := c.sendRequest(req, &res, nil); err != nil { return nil, err } @@ -70,7 +70,7 @@ func (c *Client) GetOverboardCatalog(ctx context.Context, options *GetOverboardO url := fmt.Sprintf("%s/catalog.json?%s", c.BaseURL, query.Encode()) - req, err := http.NewRequest("GET", url, nil) + req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, err } @@ -78,7 +78,7 @@ func (c *Client) GetOverboardCatalog(ctx context.Context, options *GetOverboardO req = req.WithContext(ctx) res := GetOverboardResponse{} - if err := c.sendRequest(req, &res); err != nil { + if err := c.sendRequest(req, &res, nil); err != nil { return nil, err } diff --git a/example.go b/example.go index aab00ff..ba0f7c4 100644 --- a/example.go +++ b/example.go @@ -8,15 +8,26 @@ import ( func main() { - jschanClient := jschan.NewClient("https://ptchan.org") + jschanClient := jschan.NewClient("https://fatchan.org") ctx := context.Background() - options := &jschan.GetOverboardOptions{ - IncludeDefault: true, + + loginOptions := &jschan.PostLoginOptions{ + Username: "", + Password: "", + Twofactor: "", + } + err := jschanClient.Login(ctx, loginOptions) + if err != nil { + fmt.Println(err) + return } - res, err := jschanClient.GetOverboardCatalog(ctx, options) + overboardOptions := &jschan.GetOverboardOptions{ + IncludeDefault: true, + } + res, err := jschanClient.GetOverboardCatalog(ctx, overboardOptions) if err != nil { - fmt.Println("an error occurred") + fmt.Println(err) return }