-
Notifications
You must be signed in to change notification settings - Fork 58
/
broadcasts.go
330 lines (300 loc) · 10.3 KB
/
broadcasts.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
package goinsta
import (
"encoding/json"
"fmt"
"strconv"
"sync"
)
// Broadcast struct represents live video streams.
type Broadcast struct {
insta *Instagram
// Mutex to keep track of BroadcastStatus
mu *sync.RWMutex
LastLikeTs int64
LastCommentTs int64
LastCommentFetchTs int64
LastCommentTotal int
ID int64 `json:"id"`
MediaID string `json:"media_id"`
LivePostID int64 `json:"live_post_id"`
// BroadcastStatus is either "active", "interrupted", "stopped"
BroadcastStatus string `json:"broadcast_status"`
DashPlaybackURL string `json:"dash_playback_url"`
DashAbrPlaybackURL string `json:"dash_abr_playback_url"`
DashManifest string `json:"dash_manifest"`
ExpireAt int64 `json:"expire_at"`
EncodingTag string `json:"encoding_tag"`
InternalOnly bool `json:"internal_only"`
NumberOfQualities int `json:"number_of_qualities"`
CoverFrameURL string `json:"cover_frame_url"`
User User `json:"broadcast_owner"`
Cobroadcasters []*User `json:"cobroadcasters"`
PublishedTime int64 `json:"published_time"`
Message string `json:"broadcast_message"`
OrganicTrackingToken string `json:"organic_tracking_token"`
IsPlayerLiveTrace int `json:"is_player_live_trace_enabled"`
IsGamingContent bool `json:"is_gaming_content"`
IsViewerCommentAllowed bool `json:"is_viewer_comment_allowed"`
IsPolicyViolation bool `json:"is_policy_violation"`
PolicyViolationReason string `json:"policy_violation_reason"`
LiveCommentMentionEnabled bool `json:"is_live_comment_mention_enabled"`
LiveCommentRepliesEnabled bool `json:"is_live_comment_replies_enabled"`
HideFromFeedUnit bool `json:"hide_from_feed_unit"`
VideoDuration float64 `json:"video_duration"`
Visibility int `json:"visibility"`
ViewerCount float64 `json:"viewer_count"`
ResponseTs int64 `json:"response_timestamp"`
Status string `json:"status"`
Dimensions struct {
Width int `json:"width"`
Height int `json:"height"`
} `json:"dimensions"`
Experiments map[string]interface{} `json:"broadcast_experiments"`
PayViewerConfig struct {
PayConfig struct {
ConsumptionSheetConfig struct {
Description string `json:"description"`
PrivacyDisclaimer string `json:"privacy_disclaimer"`
PrivacyDisclaimerLink string `json:"privacy_disclaimer_link"`
PrivacyDisclaimerLinkText string `json:"privacy_disclaimer_link_text"`
} `json:"consumption_sheet_config"`
DigitalNonConsumableProductID int64 `json:"digital_non_consumable_product_id"`
DigitalProductID int64 `json:"digital_product_id"`
PayeeID int64 `json:"payee_id"`
PinnedRowConfig struct {
ButtonTitle string `json:"button_title"`
Description string `json:"description"`
} `json:"pinned_row_config"`
TierInfos []struct {
DigitalProductID int64 `json:"digital_product_id"`
Sku string `json:"sku"`
SupportTier string `json:"support_tier"`
} `json:"tier_infos"`
} `json:"pay_config"`
} `json:"user_pay_viewer_config"`
}
type BroadcastComments struct {
CommentLikesEnabled bool `json:"comment_likes_enabled"`
Comments []*Comment `json:"comments"`
PinnedComment *Comment `json:"pinned_comment"`
CommentCount int `json:"comment_count"`
Caption *Caption `json:"caption"`
CaptionIsEdited bool `json:"caption_is_edited"`
HasMoreComments bool `json:"has_more_comments"`
HasMoreHeadloadComments bool `json:"has_more_headload_comments"`
MediaHeaderDisplay string `json:"media_header_display"`
CanViewMorePreviewComments bool `json:"can_view_more_preview_comments"`
LiveSecondsPerComment int `json:"live_seconds_per_comment"`
IsFirstFetch string `json:"is_first_fetch"`
SystemComments []*Comment `json:"system_comments"`
CommentMuted int `json:"comment_muted"`
IsViewerCommentAllowed bool `json:"is_viewer_comment_allowed"`
UserPaySupportersInfo struct {
SupportersInComments map[string]interface{} `json:"supporters_in_comments"`
SupportersInCommentsV2 map[string]interface{} `json:"supporters_in_comments_v2"`
// SupportersInCommentsV2 map[string]struct {
// SupportTier string `json:"support_tier"`
// BadgesCount int `json:"badges_count"`
// } `json:"supporters_in_comments_v2"`
NewSupportersNextMinID int64 `json:"new_supporters_next_min_id"`
NewSupporters []NewSupporter `json:"new_supporters"`
} `json:"user_pay_supporter_info"`
Status string `json:"status"`
}
type NewSupporter struct {
RepeatedSupporter bool `json:"is_repeat_supporter"`
SupportTier string `json:"support_tier"`
Timestamp float64 `json:"ts_secs"`
User struct {
ID int64 `json:"pk"`
Username string `json:"username"`
FullName string `json:"full_name"`
IsPrivate bool `json:"is_private"`
IsVerified bool `json:"is_verified"`
}
}
type BroadcastLikes struct {
Likes int `json:"likes"`
BurstLikes int `json:"burst_likes"`
Likers []struct {
UserID int64 `json:"user_id"`
ProfilePicUrl string `json:"profile_pic_url"`
Count string `json:"count"`
} `json:"likers"`
LikeTs int64 `json:"like_ts"`
Status string `json:"status"`
PaySupporterInfo struct {
LikeCountByTier []struct {
BurstLikes int `json:"burst_likes"`
Likers []interface{} `json:"likers"`
Likes int `json:"likes"`
SupportTier string `json:"support_tier"`
} `json:"like_count_by_support_tier"`
BurstLikes int `json:"supporter_tier_burst_likes"`
Likes int `json:"supporter_tier_likes"`
} `json:"user_pay_supporter_info"`
}
type BroadcastHeartbeat struct {
ViewerCount float64 `json:"viewer_count"`
BroadcastStatus string `json:"broadcast_status"`
CobroadcasterIds []string `json:"cobroadcaster_ids"`
OffsetVideoStart float64 `json:"offset_to_video_start"`
RequestToJoinEnabled int `json:"request_to_join_enabled"`
UserPayMaxAmountReached bool `json:"user_pay_max_amount_reached"`
Status string `json:"status"`
}
// Discover wraps Instagram.IGTV.Live
func (br *Broadcast) Discover() (*IGTVChannel, error) {
return br.insta.IGTV.Live()
}
// NewUser returns prepared user to be used with his functions.
func (insta *Instagram) NewBroadcast(id int64) *Broadcast {
return &Broadcast{insta: insta, ID: id, mu: &sync.RWMutex{}}
}
// GetInfo will fetch the information about a broadcast
func (br *Broadcast) GetInfo() error {
body, _, err := br.insta.sendRequest(
&reqOptions{
Endpoint: fmt.Sprintf(urlLiveInfo, br.ID),
Query: map[string]string{
"view_expired_broadcast": "false",
},
})
if err != nil {
return err
}
br.mu.RLock()
err = json.Unmarshal(body, br)
br.mu.RUnlock()
return err
}
// Call every 2 seconds
func (br *Broadcast) GetComments() (*BroadcastComments, error) {
br.mu.RLock()
if br.BroadcastStatus == "stopped" {
return nil, ErrMediaDeleted
}
br.mu.RUnlock()
body, _, err := br.insta.sendRequest(
&reqOptions{
Endpoint: fmt.Sprintf(urlLiveComments, br.ID),
Query: map[string]string{
"last_comment_ts": strconv.FormatInt(br.LastCommentTs, 10),
"join_request_last_seen_ts": "0",
"join_request_last_fetch_ts": "0",
"join_request_last_total_count": "0",
},
})
if err != nil {
return nil, err
}
c := &BroadcastComments{}
err = json.Unmarshal(body, c)
if err != nil {
return nil, err
}
if c.CommentCount > 0 {
br.LastCommentTs = c.Comments[0].CreatedAt
}
return c, nil
}
// Call every 6 seconds
func (br *Broadcast) GetLikes() (*BroadcastLikes, error) {
br.mu.RLock()
if br.BroadcastStatus == "stopped" {
return nil, ErrMediaDeleted
}
br.mu.RUnlock()
body, _, err := br.insta.sendRequest(
&reqOptions{
Endpoint: fmt.Sprintf(urlLiveLikeCount, br.ID),
Query: map[string]string{
"like_ts": strconv.FormatInt(br.LastLikeTs, 10),
},
})
if err != nil {
return nil, err
}
c := &BroadcastLikes{}
err = json.Unmarshal(body, c)
if err != nil {
return nil, err
}
br.LastLikeTs = c.LikeTs
return c, nil
}
// Call every 3 seconds
func (br *Broadcast) GetHeartbeat() (*BroadcastHeartbeat, error) {
br.mu.RLock()
if br.BroadcastStatus == "stopped" {
return nil, ErrMediaDeleted
}
br.mu.RUnlock()
body, _, err := br.insta.sendRequest(
&reqOptions{
Endpoint: fmt.Sprintf(urlLiveHeartbeat, br.ID),
IsPost: true,
Query: map[string]string{
"_uuid": br.insta.uuid,
"live_with_eligibility": "2", // What is this?
},
})
if err != nil {
return nil, err
}
c := &BroadcastHeartbeat{}
err = json.Unmarshal(body, c)
if err != nil {
return nil, err
}
br.mu.RLock()
br.BroadcastStatus = c.BroadcastStatus
br.mu.RUnlock()
return c, nil
}
// GetLiveChaining traditionally gets called after the live stream has ended, and provides
// recommendations of other current live streams, as well as past live streams.
func (br *Broadcast) GetLiveChaining() ([]*Broadcast, error) {
insta := br.insta
body, _, err := insta.sendRequest(
&reqOptions{
Endpoint: urlLiveChaining,
Query: map[string]string{
"include_post_lives": "true",
},
},
)
if err != nil {
return nil, err
}
var resp struct {
Broadcasts []*Broadcast `json:"broadcasts"`
Status string `json:"string"`
}
err = json.Unmarshal(body, &resp)
if err != nil {
return nil, err
}
for _, br := range resp.Broadcasts {
br.setValues(insta)
}
return resp.Broadcasts, nil
}
func (br *Broadcast) DownloadCoverFrame() ([]byte, error) {
if br.CoverFrameURL == "" {
return nil, ErrNoMedia
}
b, err := br.insta.download(br.CoverFrameURL)
if err != nil {
return nil, err
}
return b, nil
}
func (br *Broadcast) setValues(insta *Instagram) {
br.insta = insta
br.User.insta = insta
br.mu = &sync.RWMutex{}
for _, cb := range br.Cobroadcasters {
cb.insta = insta
}
}