-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathstory.go
More file actions
123 lines (116 loc) · 3.12 KB
/
Copy pathstory.go
File metadata and controls
123 lines (116 loc) · 3.12 KB
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
package ig
import (
"encoding/json"
"errors"
"fmt"
"net/url"
"strconv"
"strings"
igcfg "github.com/felipeinf/instago/config"
"github.com/felipeinf/instago/igerrors"
)
const userReelsGQLHash = "303a4ae99711322310f25250d988f3b7"
// UserStoriesV1 loads stories from the private feed/user/{id}/story/ endpoint.
func (c *Client) UserStoriesV1(userID int64, amount int) ([]Story, error) {
capJSON, _ := json.Marshal(igcfg.SupportedCapabilities)
params := url.Values{}
params.Set("supported_capabilities_new", string(capJSON))
res, err := c.PrivateRequest(PrivateRequestOpts{
Endpoint: fmt.Sprintf("feed/user/%d/story/", userID),
Params: params,
WithSignature: false,
})
if err != nil {
return nil, err
}
reel, _ := res["reel"].(map[string]any)
if reel == nil {
return nil, nil
}
items, _ := reel["items"].([]any)
out := make([]Story, 0, len(items))
for _, x := range items {
m, ok := x.(map[string]any)
if !ok {
continue
}
out = append(out, extractStoryV1(m))
}
if amount > 0 && len(out) > amount {
out = out[:amount]
}
return out, nil
}
func (c *Client) userStoriesGQLSingle(userID int64, amount int) ([]Story, error) {
c.injectSessionIDToPublic()
data, err := c.PublicGraphqlRequest(map[string]any{
"reel_ids": []any{userID},
"precomposed_overlay": false,
}, userReelsGQLHash)
if err != nil {
return nil, err
}
reels, _ := data["reels_media"].([]any)
var items []any
for _, x := range reels {
rm, ok := x.(map[string]any)
if !ok {
continue
}
owner, _ := rm["owner"].(map[string]any)
if toInt64(owner["id"]) != userID && toInt64(owner["pk"]) != userID {
continue
}
items, _ = rm["items"].([]any)
break
}
out := make([]Story, 0, len(items))
for _, it := range items {
m, ok := it.(map[string]any)
if !ok {
continue
}
out = append(out, extractStoryGql(m))
}
if amount > 0 && len(out) > amount {
out = out[:amount]
}
return out, nil
}
// UserStories prefers public GraphQL reels_media; on login_required it may inject sessionid and retry, then falls back to UserStoriesV1.
func (c *Client) UserStories(userID int64, amount int) ([]Story, error) {
gql, err := c.userStoriesGQLSingle(userID, amount)
if err == nil {
return gql, nil
}
var lr *igerrors.LoginRequired
if errors.As(err, &lr) && c.injectSessionIDToPublic() {
gql, err = c.userStoriesGQLSingle(userID, amount)
if err == nil {
return gql, nil
}
}
return c.UserStoriesV1(userID, amount)
}
// StoryInfo parses a story primary key of form "{mediaPK}_{userID}", loads that user's stories, and returns the matching item.
func (c *Client) StoryInfo(storyPK string) (Story, error) {
idx := strings.LastIndex(storyPK, "_")
if idx <= 0 || idx >= len(storyPK)-1 {
return Story{}, fmt.Errorf("ig: invalid story pk")
}
targetPK := storyPK[:idx]
uid, err := strconv.ParseInt(storyPK[idx+1:], 10, 64)
if err != nil {
return Story{}, err
}
stories, err := c.UserStories(uid, 0)
if err != nil {
return Story{}, err
}
for _, s := range stories {
if s.PK == targetPK || strings.HasPrefix(s.ID, targetPK+"_") {
return s, nil
}
}
return Story{}, fmt.Errorf("ig: story not found")
}