|
1 /* |
|
2 Copyright 2017-2018 Mikael Berthe |
|
3 |
|
4 Licensed under the MIT license. Please see the LICENSE file is this directory. |
|
5 */ |
|
6 |
|
7 package madon |
|
8 |
|
9 import ( |
|
10 "fmt" |
|
11 "strconv" |
|
12 |
|
13 "github.com/pkg/errors" |
|
14 "github.com/sendgrid/rest" |
|
15 ) |
|
16 |
|
17 // PostStatusParams contains option fields for the PostStatus command |
|
18 type PostStatusParams struct { |
|
19 Text string |
|
20 InReplyTo int64 |
|
21 MediaIDs []int64 |
|
22 Sensitive bool |
|
23 SpoilerText string |
|
24 Visibility string |
|
25 } |
|
26 |
|
27 // updateStatusOptions contains option fields for POST and DELETE API calls |
|
28 type updateStatusOptions struct { |
|
29 // The ID is used for most commands |
|
30 ID int64 |
|
31 |
|
32 // The following fields are used for posting a new status |
|
33 Status string |
|
34 InReplyToID int64 |
|
35 MediaIDs []int64 |
|
36 Sensitive bool |
|
37 SpoilerText string |
|
38 Visibility string // "direct", "private", "unlisted" or "public" |
|
39 } |
|
40 |
|
41 // getMultipleStatuses returns a list of status entities |
|
42 // If lopt.All is true, several requests will be made until the API server |
|
43 // has nothing to return. |
|
44 func (mc *Client) getMultipleStatuses(endPoint string, params apiCallParams, lopt *LimitParams) ([]Status, error) { |
|
45 var statuses []Status |
|
46 var links apiLinks |
|
47 if err := mc.apiCall("v1/"+endPoint, rest.Get, params, lopt, &links, &statuses); err != nil { |
|
48 return nil, err |
|
49 } |
|
50 if lopt != nil { // Fetch more pages to reach our limit |
|
51 var statusSlice []Status |
|
52 for (lopt.All || lopt.Limit > len(statuses)) && links.next != nil { |
|
53 newlopt := links.next |
|
54 links = apiLinks{} |
|
55 if err := mc.apiCall("v1/"+endPoint, rest.Get, params, newlopt, &links, &statusSlice); err != nil { |
|
56 return nil, err |
|
57 } |
|
58 statuses = append(statuses, statusSlice...) |
|
59 statusSlice = statusSlice[:0] // Clear struct |
|
60 } |
|
61 } |
|
62 return statuses, nil |
|
63 } |
|
64 |
|
65 // queryStatusData queries the statuses API |
|
66 // The operation 'op' can be empty or "status" (the status itself), "context", |
|
67 // "card", "reblogged_by", "favourited_by". |
|
68 // The data argument will receive the object(s) returned by the API server. |
|
69 func (mc *Client) queryStatusData(statusID int64, op string, data interface{}) error { |
|
70 if statusID < 1 { |
|
71 return ErrInvalidID |
|
72 } |
|
73 |
|
74 endPoint := "statuses/" + strconv.FormatInt(statusID, 10) |
|
75 |
|
76 if op != "" && op != "status" { |
|
77 switch op { |
|
78 case "context", "card", "reblogged_by", "favourited_by": |
|
79 default: |
|
80 return ErrInvalidParameter |
|
81 } |
|
82 |
|
83 endPoint += "/" + op |
|
84 } |
|
85 |
|
86 return mc.apiCall("v1/"+endPoint, rest.Get, nil, nil, nil, data) |
|
87 } |
|
88 |
|
89 // updateStatusData updates the statuses |
|
90 // The operation 'op' can be empty or "status" (to post a status), "delete" |
|
91 // (for deleting a status), "reblog"/"unreblog", "favourite"/"unfavourite", |
|
92 // "mute"/"unmute" (for conversations) or "pin"/"unpin". |
|
93 // The data argument will receive the object(s) returned by the API server. |
|
94 func (mc *Client) updateStatusData(op string, opts updateStatusOptions, data interface{}) error { |
|
95 method := rest.Post |
|
96 endPoint := "statuses" |
|
97 params := make(apiCallParams) |
|
98 |
|
99 switch op { |
|
100 case "", "status": |
|
101 op = "status" |
|
102 if opts.Status == "" { |
|
103 return ErrInvalidParameter |
|
104 } |
|
105 switch opts.Visibility { |
|
106 case "", "direct", "private", "unlisted", "public": |
|
107 // Okay |
|
108 default: |
|
109 return ErrInvalidParameter |
|
110 } |
|
111 if len(opts.MediaIDs) > 4 { |
|
112 return errors.New("too many (>4) media IDs") |
|
113 } |
|
114 case "delete": |
|
115 method = rest.Delete |
|
116 if opts.ID < 1 { |
|
117 return ErrInvalidID |
|
118 } |
|
119 endPoint += "/" + strconv.FormatInt(opts.ID, 10) |
|
120 case "reblog", "unreblog", "favourite", "unfavourite": |
|
121 if opts.ID < 1 { |
|
122 return ErrInvalidID |
|
123 } |
|
124 endPoint += "/" + strconv.FormatInt(opts.ID, 10) + "/" + op |
|
125 case "mute", "unmute", "pin", "unpin": |
|
126 if opts.ID < 1 { |
|
127 return ErrInvalidID |
|
128 } |
|
129 endPoint += "/" + strconv.FormatInt(opts.ID, 10) + "/" + op |
|
130 default: |
|
131 return ErrInvalidParameter |
|
132 } |
|
133 |
|
134 // Form items for a new toot |
|
135 if op == "status" { |
|
136 params["status"] = opts.Status |
|
137 if opts.InReplyToID > 0 { |
|
138 params["in_reply_to_id"] = strconv.FormatInt(opts.InReplyToID, 10) |
|
139 } |
|
140 for i, id := range opts.MediaIDs { |
|
141 if id < 1 { |
|
142 return ErrInvalidID |
|
143 } |
|
144 qID := fmt.Sprintf("[%d]media_ids", i) |
|
145 params[qID] = strconv.FormatInt(id, 10) |
|
146 } |
|
147 if opts.Sensitive { |
|
148 params["sensitive"] = "true" |
|
149 } |
|
150 if opts.SpoilerText != "" { |
|
151 params["spoiler_text"] = opts.SpoilerText |
|
152 } |
|
153 if opts.Visibility != "" { |
|
154 params["visibility"] = opts.Visibility |
|
155 } |
|
156 } |
|
157 |
|
158 return mc.apiCall("v1/"+endPoint, method, params, nil, nil, data) |
|
159 } |
|
160 |
|
161 // GetStatus returns a status |
|
162 // The returned status can be nil if there is an error or if the |
|
163 // requested ID does not exist. |
|
164 func (mc *Client) GetStatus(statusID int64) (*Status, error) { |
|
165 var status Status |
|
166 |
|
167 if err := mc.queryStatusData(statusID, "status", &status); err != nil { |
|
168 return nil, err |
|
169 } |
|
170 if status.ID == 0 { |
|
171 return nil, ErrEntityNotFound |
|
172 } |
|
173 return &status, nil |
|
174 } |
|
175 |
|
176 // GetStatusContext returns a status context |
|
177 func (mc *Client) GetStatusContext(statusID int64) (*Context, error) { |
|
178 var context Context |
|
179 if err := mc.queryStatusData(statusID, "context", &context); err != nil { |
|
180 return nil, err |
|
181 } |
|
182 return &context, nil |
|
183 } |
|
184 |
|
185 // GetStatusCard returns a status card |
|
186 func (mc *Client) GetStatusCard(statusID int64) (*Card, error) { |
|
187 var card Card |
|
188 if err := mc.queryStatusData(statusID, "card", &card); err != nil { |
|
189 return nil, err |
|
190 } |
|
191 return &card, nil |
|
192 } |
|
193 |
|
194 // GetStatusRebloggedBy returns a list of the accounts who reblogged a status |
|
195 func (mc *Client) GetStatusRebloggedBy(statusID int64, lopt *LimitParams) ([]Account, error) { |
|
196 o := &getAccountsOptions{ID: statusID, Limit: lopt} |
|
197 return mc.getMultipleAccountsHelper("reblogged_by", o) |
|
198 } |
|
199 |
|
200 // GetStatusFavouritedBy returns a list of the accounts who favourited a status |
|
201 func (mc *Client) GetStatusFavouritedBy(statusID int64, lopt *LimitParams) ([]Account, error) { |
|
202 o := &getAccountsOptions{ID: statusID, Limit: lopt} |
|
203 return mc.getMultipleAccountsHelper("favourited_by", o) |
|
204 } |
|
205 |
|
206 // PostStatus posts a new "toot" |
|
207 // All parameters but "text" can be empty. |
|
208 // Visibility must be empty, or one of "direct", "private", "unlisted" and "public". |
|
209 func (mc *Client) PostStatus(cmdParams PostStatusParams) (*Status, error) { |
|
210 var status Status |
|
211 o := updateStatusOptions{ |
|
212 Status: cmdParams.Text, |
|
213 InReplyToID: cmdParams.InReplyTo, |
|
214 MediaIDs: cmdParams.MediaIDs, |
|
215 Sensitive: cmdParams.Sensitive, |
|
216 SpoilerText: cmdParams.SpoilerText, |
|
217 Visibility: cmdParams.Visibility, |
|
218 } |
|
219 |
|
220 err := mc.updateStatusData("status", o, &status) |
|
221 if err != nil { |
|
222 return nil, err |
|
223 } |
|
224 if status.ID == 0 { |
|
225 return nil, ErrEntityNotFound // TODO Change error message |
|
226 } |
|
227 return &status, err |
|
228 } |
|
229 |
|
230 // DeleteStatus deletes a status |
|
231 func (mc *Client) DeleteStatus(statusID int64) error { |
|
232 var status Status |
|
233 o := updateStatusOptions{ID: statusID} |
|
234 err := mc.updateStatusData("delete", o, &status) |
|
235 return err |
|
236 } |
|
237 |
|
238 // ReblogStatus reblogs a status |
|
239 func (mc *Client) ReblogStatus(statusID int64) error { |
|
240 var status Status |
|
241 o := updateStatusOptions{ID: statusID} |
|
242 err := mc.updateStatusData("reblog", o, &status) |
|
243 return err |
|
244 } |
|
245 |
|
246 // UnreblogStatus unreblogs a status |
|
247 func (mc *Client) UnreblogStatus(statusID int64) error { |
|
248 var status Status |
|
249 o := updateStatusOptions{ID: statusID} |
|
250 err := mc.updateStatusData("unreblog", o, &status) |
|
251 return err |
|
252 } |
|
253 |
|
254 // FavouriteStatus favourites a status |
|
255 func (mc *Client) FavouriteStatus(statusID int64) error { |
|
256 var status Status |
|
257 o := updateStatusOptions{ID: statusID} |
|
258 err := mc.updateStatusData("favourite", o, &status) |
|
259 return err |
|
260 } |
|
261 |
|
262 // UnfavouriteStatus unfavourites a status |
|
263 func (mc *Client) UnfavouriteStatus(statusID int64) error { |
|
264 var status Status |
|
265 o := updateStatusOptions{ID: statusID} |
|
266 err := mc.updateStatusData("unfavourite", o, &status) |
|
267 return err |
|
268 } |
|
269 |
|
270 // PinStatus pins a status |
|
271 func (mc *Client) PinStatus(statusID int64) error { |
|
272 var status Status |
|
273 o := updateStatusOptions{ID: statusID} |
|
274 err := mc.updateStatusData("pin", o, &status) |
|
275 return err |
|
276 } |
|
277 |
|
278 // UnpinStatus unpins a status |
|
279 func (mc *Client) UnpinStatus(statusID int64) error { |
|
280 var status Status |
|
281 o := updateStatusOptions{ID: statusID} |
|
282 err := mc.updateStatusData("unpin", o, &status) |
|
283 return err |
|
284 } |
|
285 |
|
286 // MuteConversation mutes the conversation containing a status |
|
287 func (mc *Client) MuteConversation(statusID int64) (*Status, error) { |
|
288 var status Status |
|
289 o := updateStatusOptions{ID: statusID} |
|
290 err := mc.updateStatusData("mute", o, &status) |
|
291 return &status, err |
|
292 } |
|
293 |
|
294 // UnmuteConversation unmutes the conversation containing a status |
|
295 func (mc *Client) UnmuteConversation(statusID int64) (*Status, error) { |
|
296 var status Status |
|
297 o := updateStatusOptions{ID: statusID} |
|
298 err := mc.updateStatusData("unmute", o, &status) |
|
299 return &status, err |
|
300 } |
|
301 |
|
302 // GetFavourites returns the list of the user's favourites |
|
303 // If lopt.All is true, several requests will be made until the API server |
|
304 // has nothing to return. |
|
305 // If lopt.Limit is set (and not All), several queries can be made until the |
|
306 // limit is reached. |
|
307 func (mc *Client) GetFavourites(lopt *LimitParams) ([]Status, error) { |
|
308 return mc.getMultipleStatuses("favourites", nil, lopt) |
|
309 } |