-
Notifications
You must be signed in to change notification settings - Fork 56
/
igtv.go
338 lines (298 loc) · 8.14 KB
/
igtv.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
package goinsta
import (
"bytes"
"encoding/json"
"fmt"
)
// Do i need to extract the rank token?
// Methods to create:
// Broadcasts.Discover
// All items with interface{} I have only seen a null response
type IGTV struct {
insta *Instagram
err error
// Shared between the endpoints
DestinationClientConfigs interface{} `json:"destination_client_configs"`
MaxID string `json:"max_id"`
MoreAvailable bool `json:"more_available"`
SeenState interface{} `json:"seen_state"`
NumResults int `json:"num_results"`
Status string `json:"status"`
// Specific to igtv/discover
Badging interface{} `json:"badging"`
BannerToken interface{} `json:"banner_token"`
BrowseItems interface{} `json:"browser_items"`
Channels []IGTVChannel `json:"channels"`
Composer interface{} `json:"composer"`
Items []*Item `json:"items"`
DestinationItems []IGTVItem `json:"destination_items"`
MyChannel struct{} `json:"my_channel"`
// Specific to igtv/suggested_searches
RankToken int `json:"rank_token"`
}
// IGTVItem is a media item that can be found inside the IGTV struct, from the
// IGTV Discover endpoint.
type IGTVItem struct {
Title string `json:"title"`
Type string `json:"type"`
Channel IGTVChannel `json:"channel"`
Item *Item `json:"item"`
LogingInfo struct {
SourceChannelType string `json:"source_channel_type"`
} `json:"logging_info"`
// Specific to igtv/suggested_searches
Hashtag interface{} `json:"hashtag"`
Keyword interface{} `json:"keyword"`
User User `json:"user"`
}
// IGTVChannel can represent a single user's collection of IGTV posts, or it can
// e.g. represent a user's IGTV series.
//
// It's called a channel, however the Items inside the Channel struct can, but
// don't have to, belong to the same account, depending on the request. It's a bit dubious
//
type IGTVChannel struct {
insta *Instagram
id string // user id parameter
err error
ApproxTotalVideos interface{} `json:"approx_total_videos"`
ApproxVideosFormatted interface{} `json:"approx_videos_formatted"`
CoverPhotoUrl string `json:"cover_photo_url"`
Description string `json:"description"`
ID string `json:"id"`
Items []*Item `json:"items"`
NumResults int `json:"num_results"`
Broadcasts []*Broadcast `json:"live_items"`
Title string `json:"title"`
Type string `json:"type"`
User *User `json:"user_dict"`
DestinationClientConfigs interface{} `json:"destination_client_configs"`
NextID interface{} `json:"max_id"`
MoreAvailable bool `json:"more_available"`
SeenState interface{} `json:"seen_state"`
}
func newIGTV(insta *Instagram) *IGTV {
return &IGTV{insta: insta}
}
// IGTV returns the IGTV items of a user
//
// Use IGTVChannel.Next for pagination.
//
func (user *User) IGTV() (*IGTVChannel, error) {
insta := user.insta
igtv := &IGTVChannel{
insta: insta,
id: fmt.Sprintf("user_%d", user.ID),
}
if !igtv.Next() {
return igtv, igtv.Error()
}
return igtv, nil
}
// IGTVSeries will fetch the igtv series of a user. Usually the slice length
// of the return value is 1, as there is one channel, which contains multiple
// series.
//
func (user *User) IGTVSeries() ([]*IGTVChannel, error) {
if !user.HasIGTVSeries {
return nil, ErrIGTVNoSeries
}
insta := user.insta
body, _, err := insta.sendRequest(
&reqOptions{
Endpoint: fmt.Sprintf(urlIGTVSeries, user.ID),
Query: generateSignature("{}"),
},
)
if err != nil {
return nil, err
}
var res struct {
Channels []*IGTVChannel `json:"channels"`
Status string `json:"status"`
}
err = json.Unmarshal(body, &res)
if err != nil {
return nil, err
}
for _, chann := range res.Channels {
chann.setValues()
}
return res.Channels, nil
}
// Live will return a list of current broadcasts
func (igtv *IGTV) Live() (*IGTVChannel, error) {
return igtv.insta.callIGTVChannel("live", "")
}
// Live test method to see if Live can paginate
func (igtv *IGTVChannel) Live() (*IGTVChannel, error) {
return igtv.insta.callIGTVChannel("live", igtv.GetNextID())
}
// GetNexID returns the max id used for pagination.
func (igtv *IGTVChannel) GetNextID() string {
return formatID(igtv.NextID)
}
// Next allows you to paginate the IGTV feed of a channel.
// returns false when list reach the end.
// if FeedMedia.Error() is ErrNoMore no problems have occurred.
func (igtv *IGTVChannel) Next(params ...interface{}) bool {
if igtv.err != nil {
return false
}
insta := igtv.insta
query := map[string]string{
"id": igtv.id,
"_uuid": igtv.insta.uuid,
"count": "10",
}
if len(params)%2 == 0 {
for i := 0; i < len(params); i = i + 2 {
query[params[i].(string)] = params[i+1].(string)
}
}
if next := igtv.GetNextID(); next != "" {
query["max_id"] = next
}
body, _, err := insta.sendRequest(
&reqOptions{
Endpoint: urlIGTVChannel,
IsPost: true,
Query: query,
})
if err != nil {
igtv.err = err
return false
}
oldItems := igtv.Items
d := json.NewDecoder(bytes.NewReader(body))
d.UseNumber()
err = d.Decode(igtv)
if err != nil {
igtv.err = err
return false
}
if !igtv.MoreAvailable {
igtv.err = ErrNoMore
}
igtv.setValues()
igtv.Items = append(oldItems, igtv.Items...)
return true
}
// Latest will return the results from the latest fetch
func (igtv *IGTVChannel) Latest() []*Item {
return igtv.Items[len(igtv.Items)-igtv.NumResults:]
}
func (insta *Instagram) callIGTVChannel(id, nextID string) (*IGTVChannel, error) {
query := map[string]string{
"id": id,
"_uuid": insta.uuid,
"count": "10",
}
if nextID != "" {
query["max_id"] = nextID
}
body, _, err := insta.sendRequest(&reqOptions{
Endpoint: urlIGTVChannel,
IsPost: true,
Query: query,
})
if err != nil {
return nil, err
}
igtv := &IGTVChannel{insta: insta, id: id}
d := json.NewDecoder(bytes.NewReader(body))
d.UseNumber()
err = d.Decode(igtv)
igtv.setValues()
return igtv, err
}
func (igtv *IGTVChannel) Delete() error {
return nil
}
func (igtv *IGTVChannel) Error() error {
return igtv.err
}
func (media *IGTVChannel) getInsta() *Instagram {
return media.insta
}
func (igtv *IGTVChannel) setValues() {
insta := igtv.insta
if igtv.User != nil {
igtv.User.insta = insta
}
for _, i := range igtv.Items {
setToItem(i, igtv)
}
for _, br := range igtv.Broadcasts {
br.setValues(insta)
}
}
// Next allows you to paginate the IGTV Discover page.
func (igtv *IGTV) Next(params ...interface{}) bool {
if igtv.err != nil {
return false
}
insta := igtv.insta
query := map[string]string{}
if igtv.MaxID != "" {
query["max_id"] = igtv.MaxID
}
body, _, err := insta.sendRequest(
&reqOptions{
Endpoint: urlIGTVDiscover,
Query: query,
},
)
if err != nil {
igtv.err = err
return false
}
err = json.Unmarshal(body, igtv)
if err != nil {
igtv.err = err
return false
}
igtv.setValues()
count := 0
for _, item := range igtv.DestinationItems {
if item.Item != nil {
igtv.Items = append(igtv.Items, item.Item)
count++
}
}
igtv.NumResults = count
if !igtv.MoreAvailable {
igtv.err = ErrNoMore
return false
}
return true
}
func (igtv *IGTV) setValues() {
for _, item := range igtv.DestinationItems {
if item.Item != nil {
setToItem(item.Item, igtv)
}
}
for _, chann := range igtv.Channels {
chann.setValues()
}
}
// Delete does nothing, is only a place holder
func (igtv *IGTV) Delete() error {
return nil
}
// Error return the error of IGTV, if one has occured
func (igtv *IGTV) Error() error {
return igtv.err
}
func (igtv *IGTV) getInsta() *Instagram {
return igtv.insta
}
// Error return the error of IGTV, if one has occured
func (igtv *IGTV) GetNextID() string {
return igtv.MaxID
}
// Latest returns the last fetched items, by slicing IGTV.Items with IGTV.NumResults
func (igtv *IGTV) Latest() []*Item {
return igtv.Items[len(igtv.Items)-igtv.NumResults:]
}