Use links from headers
Keep querying the API until the requested limit is reached, using the
headers. If no limit is set, a single query is made.
--- a/account.go Sat Apr 29 10:51:45 2017 +0200
+++ b/account.go Sat Apr 29 12:16:16 2017 +0200
@@ -59,7 +59,7 @@
}
var account Account
- if err := mc.apiCall(endPoint, method, nil, nil, &account); err != nil {
+ if err := mc.apiCall(endPoint, method, nil, nil, nil, &account); err != nil {
return nil, err
}
return &account, nil
@@ -90,6 +90,11 @@
return []Account{}, ErrInvalidParameter
}
endPoint = "accounts/" + op
+ case "reblogged_by", "favourited_by":
+ if opts == nil || opts.ID < 1 {
+ return []Account{}, ErrInvalidID
+ }
+ endPoint = "statuses/" + strconv.Itoa(opts.ID) + "/" + op
default:
return nil, ErrInvalidParameter
}
@@ -101,9 +106,22 @@
}
var accounts []Account
- if err := mc.apiCall(endPoint, rest.Get, params, lopt, &accounts); err != nil {
+ var links apiLinks
+ if err := mc.apiCall(endPoint, rest.Get, params, lopt, &links, &accounts); err != nil {
return nil, err
}
+ if lopt != nil { // Fetch more pages to reach our limit
+ var accountSlice []Account
+ for lopt.Limit > len(accounts) && links.next != nil {
+ newlopt := links.next
+ links = apiLinks{}
+ if err := mc.apiCall(endPoint, rest.Get, params, newlopt, &links, &accountSlice); err != nil {
+ return nil, err
+ }
+ accounts = append(accounts, accountSlice...)
+ accountSlice = accountSlice[:0] // Clear struct
+ }
+ }
return accounts, nil
}
@@ -180,7 +198,7 @@
params["uri"] = uri
var account Account
- if err := mc.apiCall("follows", rest.Post, params, nil, &account); err != nil {
+ if err := mc.apiCall("follows", rest.Post, params, nil, nil, &account); err != nil {
return nil, err
}
if account.ID == 0 {
@@ -281,7 +299,7 @@
}
var rl []Relationship
- if err := mc.apiCall("accounts/relationships", rest.Get, params, nil, &rl); err != nil {
+ if err := mc.apiCall("accounts/relationships", rest.Get, params, nil, nil, &rl); err != nil {
return nil, err
}
return rl, nil
@@ -305,9 +323,22 @@
}
var sl []Status
- if err := mc.apiCall(endPoint, rest.Get, params, lopt, &sl); err != nil {
+ var links apiLinks
+ if err := mc.apiCall(endPoint, rest.Get, params, lopt, &links, &sl); err != nil {
return nil, err
}
+ if lopt != nil { // Fetch more pages to reach our limit
+ var statusSlice []Status
+ for lopt.Limit > len(sl) && links.next != nil {
+ newlopt := links.next
+ links = apiLinks{}
+ if err := mc.apiCall(endPoint, rest.Get, params, newlopt, &links, &statusSlice); err != nil {
+ return nil, err
+ }
+ sl = append(sl, statusSlice...)
+ statusSlice = statusSlice[:0] // Clear struct
+ }
+ }
return sl, nil
}
--- a/api.go Sat Apr 29 10:51:45 2017 +0200
+++ b/api.go Sat Apr 29 12:16:16 2017 +0200
@@ -13,12 +13,72 @@
"fmt"
"net/http"
"net/url"
+ "regexp"
"strconv"
"strings"
"github.com/sendgrid/rest"
)
+type apiLinks struct {
+ next, prev *LimitParams
+}
+
+func parseLink(links []string) (*apiLinks, error) {
+ if len(links) == 0 {
+ return nil, nil
+ }
+
+ al := new(apiLinks)
+ linkRegex := regexp.MustCompile(`<([^>]+)>; rel="([^"]+)`)
+ for _, l := range links {
+ m := linkRegex.FindAllStringSubmatch(l, -1)
+ for _, submatch := range m {
+ if len(submatch) != 3 {
+ continue
+ }
+ // Parse URL
+ u, err := url.Parse(submatch[1])
+ if err != nil {
+ return al, err
+ }
+ var lp *LimitParams
+ since := u.Query().Get("since_id")
+ max := u.Query().Get("max_id")
+ lim := u.Query().Get("limit")
+ if since == "" && max == "" {
+ continue
+ }
+ lp = new(LimitParams)
+ if since != "" {
+ lp.SinceID, err = strconv.Atoi(since)
+ if err != nil {
+ return al, err
+ }
+ }
+ if max != "" {
+ lp.MaxID, err = strconv.Atoi(max)
+ if err != nil {
+ return al, err
+ }
+ }
+ if lim != "" {
+ lp.Limit, err = strconv.Atoi(lim)
+ if err != nil {
+ return al, err
+ }
+ }
+ switch submatch[2] {
+ case "prev":
+ al.prev = lp
+ case "next":
+ al.next = lp
+ }
+ }
+ }
+ return al, nil
+}
+
// restAPI actually does the HTTP query
// It is a copy of rest.API with better handling of parameters with multiple values
func restAPI(request rest.Request) (*rest.Response, error) {
@@ -103,7 +163,9 @@
}
// apiCall makes a call to the Mastodon API server
-func (mc *Client) apiCall(endPoint string, method rest.Method, params apiCallParams, limitOptions *LimitParams, data interface{}) error {
+// If links is not nil, the prev/next links from the API response headers
+// will be set (if they exist) in the structure.
+func (mc *Client) apiCall(endPoint string, method rest.Method, params apiCallParams, limitOptions *LimitParams, links *apiLinks, data interface{}) error {
if mc == nil {
return fmt.Errorf("use of uninitialized madon client")
}
@@ -135,6 +197,16 @@
return fmt.Errorf("API query (%s) failed: %s", endPoint, err.Error())
}
+ if links != nil {
+ pLinks, err := parseLink(r.Headers["Link"])
+ if err != nil {
+ return fmt.Errorf("cannot decode header links (%s): %s", method, err.Error())
+ }
+ if pLinks != nil {
+ *links = *pLinks
+ }
+ }
+
// Check for error reply
var errorResult Error
if err := json.Unmarshal([]byte(r.Body), &errorResult); err == nil {
--- a/app.go Sat Apr 29 10:51:45 2017 +0200
+++ b/app.go Sat Apr 29 12:16:16 2017 +0200
@@ -68,7 +68,7 @@
}
var app registerApp
- if err := mc.apiCall("apps", rest.Post, params, nil, &app); err != nil {
+ if err := mc.apiCall("apps", rest.Post, params, nil, nil, &app); err != nil {
return nil, err
}
--- a/favourites.go Sat Apr 29 10:51:45 2017 +0200
+++ b/favourites.go Sat Apr 29 12:16:16 2017 +0200
@@ -13,9 +13,22 @@
// GetFavourites returns the list of the user's favourites
func (mc *Client) GetFavourites(lopt *LimitParams) ([]Status, error) {
var faves []Status
- err := mc.apiCall("favourites", rest.Get, nil, lopt, &faves)
+ var links apiLinks
+ err := mc.apiCall("favourites", rest.Get, nil, lopt, &links, &faves)
if err != nil {
return nil, err
}
+ if lopt != nil { // Fetch more pages to reach our limit
+ var faveSlice []Status
+ for lopt.Limit > len(faves) && links.next != nil {
+ newlopt := links.next
+ links = apiLinks{}
+ if err := mc.apiCall("favourites", rest.Get, nil, newlopt, &links, &faveSlice); err != nil {
+ return nil, err
+ }
+ faves = append(faves, faveSlice...)
+ faveSlice = faveSlice[:0] // Clear struct
+ }
+ }
return faves, nil
}
--- a/instance.go Sat Apr 29 10:51:45 2017 +0200
+++ b/instance.go Sat Apr 29 12:16:16 2017 +0200
@@ -13,7 +13,7 @@
// GetCurrentInstance returns current instance information
func (mc *Client) GetCurrentInstance() (*Instance, error) {
var i Instance
- if err := mc.apiCall("instance", rest.Get, nil, nil, &i); err != nil {
+ if err := mc.apiCall("instance", rest.Get, nil, nil, nil, &i); err != nil {
return nil, err
}
return &i, nil
--- a/notifications.go Sat Apr 29 10:51:45 2017 +0200
+++ b/notifications.go Sat Apr 29 12:16:16 2017 +0200
@@ -15,9 +15,22 @@
// GetNotifications returns the list of the user's notifications
func (mc *Client) GetNotifications(lopt *LimitParams) ([]Notification, error) {
var notifications []Notification
- if err := mc.apiCall("notifications", rest.Get, nil, lopt, ¬ifications); err != nil {
+ var links apiLinks
+ if err := mc.apiCall("notifications", rest.Get, nil, lopt, &links, ¬ifications); err != nil {
return nil, err
}
+ if lopt != nil { // Fetch more pages to reach our limit
+ var notifSlice []Notification
+ for lopt.Limit > len(notifications) && links.next != nil {
+ newlopt := links.next
+ links = apiLinks{}
+ if err := mc.apiCall("notifications", rest.Get, nil, newlopt, &links, ¬ifSlice); err != nil {
+ return nil, err
+ }
+ notifications = append(notifications, notifSlice...)
+ notifSlice = notifSlice[:0] // Clear struct
+ }
+ }
return notifications, nil
}
@@ -31,7 +44,7 @@
var endPoint = "notifications/" + strconv.Itoa(notificationID)
var notification Notification
- if err := mc.apiCall(endPoint, rest.Get, nil, nil, ¬ification); err != nil {
+ if err := mc.apiCall(endPoint, rest.Get, nil, nil, nil, ¬ification); err != nil {
return nil, err
}
if notification.ID == 0 {
@@ -48,11 +61,13 @@
endPoint := "notifications/dismiss"
params := apiCallParams{"id": strconv.Itoa(notificationID)}
- return mc.apiCall(endPoint, rest.Post, params, nil, &Notification{})
+ err := mc.apiCall(endPoint, rest.Post, params, nil, nil, &Notification{})
+ return err
}
// ClearNotifications deletes all notifications from the Mastodon server for
// the authenticated user
func (mc *Client) ClearNotifications() error {
- return mc.apiCall("notifications/clear", rest.Post, nil, nil, &Notification{})
+ err := mc.apiCall("notifications/clear", rest.Post, nil, nil, nil, &Notification{})
+ return err
}
--- a/report.go Sat Apr 29 10:51:45 2017 +0200
+++ b/report.go Sat Apr 29 12:16:16 2017 +0200
@@ -16,7 +16,7 @@
// GetReports returns the current user's reports
func (mc *Client) GetReports(lopt *LimitParams) ([]Report, error) {
var reports []Report
- if err := mc.apiCall("reports", rest.Get, nil, lopt, &reports); err != nil {
+ if err := mc.apiCall("reports", rest.Get, nil, lopt, nil, &reports); err != nil {
return nil, err
}
return reports, nil
@@ -40,7 +40,7 @@
}
var report Report
- if err := mc.apiCall("reports", rest.Post, params, nil, &report); err != nil {
+ if err := mc.apiCall("reports", rest.Post, params, nil, nil, &report); err != nil {
return nil, err
}
return &report, nil
--- a/search.go Sat Apr 29 10:51:45 2017 +0200
+++ b/search.go Sat Apr 29 12:16:16 2017 +0200
@@ -23,7 +23,7 @@
}
var results Results
- if err := mc.apiCall("search", rest.Get, params, nil, &results); err != nil {
+ if err := mc.apiCall("search", rest.Get, params, nil, nil, &results); err != nil {
return nil, err
}
return &results, nil
--- a/status.go Sat Apr 29 10:51:45 2017 +0200
+++ b/status.go Sat Apr 29 12:16:16 2017 +0200
@@ -48,7 +48,7 @@
endPoint += "/" + op
}
- return mc.apiCall(endPoint, rest.Get, nil, nil, data)
+ return mc.apiCall(endPoint, rest.Get, nil, nil, nil, data)
}
// updateStatusData updates the statuses
@@ -114,7 +114,7 @@
}
}
- return mc.apiCall(endPoint, method, params, nil, data)
+ return mc.apiCall(endPoint, method, params, nil, nil, data)
}
// GetStatus returns a status
@@ -152,16 +152,14 @@
// GetStatusRebloggedBy returns a list of the accounts who reblogged a status
func (mc *Client) GetStatusRebloggedBy(statusID int, lopt *LimitParams) ([]Account, error) {
- var accounts []Account
- err := mc.queryStatusData(statusID, "reblogged_by", &accounts)
- return accounts, err
+ o := &getAccountsOptions{ID: statusID, Limit: lopt}
+ return mc.getMultipleAccounts("reblogged_by", o)
}
// GetStatusFavouritedBy returns a list of the accounts who favourited a status
func (mc *Client) GetStatusFavouritedBy(statusID int, lopt *LimitParams) ([]Account, error) {
- var accounts []Account
- err := mc.queryStatusData(statusID, "favourited_by", &accounts)
- return accounts, err
+ o := &getAccountsOptions{ID: statusID, Limit: lopt}
+ return mc.getMultipleAccounts("favourited_by", o)
}
// PostStatus posts a new "toot"
--- a/timelines.go Sat Apr 29 10:51:45 2017 +0200
+++ b/timelines.go Sat Apr 29 12:16:16 2017 +0200
@@ -39,8 +39,21 @@
}
var tl []Status
- if err := mc.apiCall(endPoint, rest.Get, params, lopt, &tl); err != nil {
+ var links apiLinks
+ if err := mc.apiCall(endPoint, rest.Get, params, lopt, &links, &tl); err != nil {
return nil, err
}
+ if lopt != nil { // Fetch more pages to reach our limit
+ var statusSlice []Status
+ for lopt.Limit > len(tl) && links.next != nil {
+ newlopt := links.next
+ links = apiLinks{}
+ if err := mc.apiCall(endPoint, rest.Get, params, newlopt, &links, &statusSlice); err != nil {
+ return nil, err
+ }
+ tl = append(tl, statusSlice...)
+ statusSlice = statusSlice[:0] // Clear struct
+ }
+ }
return tl, nil
}