diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..4a66216f
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,8 @@
+# Contribution
+
+Welcome programmer!
+
+If you want to contribute to Goinsta API you must follow a simple instructions.
+
+- **Test your code after making pull request**. The title says it all.
+- **Include jokes if you can**. This instruction is optional.
diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md
deleted file mode 100644
index 9cd0cb61..00000000
--- a/CONTRIBUTION.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# Contribution
-
-Welcome programmer!
-
-If you want to contribute to Goinsta API you must follow a simple instructions.
-
-- **Do not push into master branch**. You can use alpha branch to push all your code. When your pull request will be tested by maintainers it will be moved into master branch. (And you will be able to boast of your Goinsta contribution with your friends.)
-
-- **Test your code after making pull request**. The title says it all.
-
-- **Include jokes if you can**. This is instruction is optional.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..a7931bdf
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Ahmadreza Zibaei
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index a8418d09..3016b324 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,11 @@
-# GoInsta. Make Goinsta Great Again!
-
+### Go + Instgaram API
+
> Unofficial Instagram API for Golang
-[![Build Status](https://travis-ci.org/ahmdrz/goinsta.svg?branch=master)](https://travis-ci.org/ahmdrz/goinsta) [![GoDoc](https://godoc.org/github.com/ahmdrz/goinsta?status.svg)](https://godoc.org/github.com/ahmdrz/goinsta) [![Go Report Card](https://goreportcard.com/badge/github.com/ahmdrz/goinsta)](https://goreportcard.com/report/github.com/ahmdrz/goinsta) [![Coverage Status](https://coveralls.io/repos/github/ahmdrz/goinsta/badge.svg?branch=master)](https://coveralls.io/github/ahmdrz/goinsta?branch=master)
+[![GoDoc](https://godoc.org/github.com/ahmdrz/goinsta?status.svg)](https://godoc.org/github.com/ahmdrz/goinsta) [![Go Report Card](https://goreportcard.com/badge/github.com/ahmdrz/goinsta)](https://goreportcard.com/report/github.com/ahmdrz/goinsta)
-## Features
+### Features
* **HTTP2 by default. Goinsta uses HTTP2 client enhancing performance.**
* **Object independency. Can handle multiple instagram accounts.**
@@ -13,26 +13,20 @@
* **Simple**. Goinsta is made by lazy programmers!
* **Backup methods**. You can use `Export` and `Import` functions.
* **Security**. Your password is only required to login. After login your password is deleted.
-* **No External Dependencies**. Goinsta will not use any Go packages outside of the standard library.
+* **No External Dependencies**. GoInsta will not use any Go packages outside of the standard library.
-## New Version !
-
-We are working on `alpha` branch. Try it and tell us your suggestions!
-
-The newer versions will be exported into v2 branch when new features will be well tested.
-
-## Package installation
+### Package installation
`go get -u -v gopkg.in/ahmdrz/goinsta.v2`
-## CLI installation
+### CLI installation
```
go get -u -v gopkg.in/ahmdrz/goinsta.v2
go install gopkg.in/ahmdrz/goinsta.v2/goinsta
```
-## Example
+### Example
```go
package main
@@ -40,31 +34,66 @@ package main
import (
"fmt"
- "github.com/ahmdrz/goinsta"
+ "gopkg.in/ahmdrz/goinsta.v2"
)
func main() {
- insta := goinsta.New("USERNAME", "PASSWORD")
+ //insta, err := goinsta.Import("~/.goinsta")
+ insta := goinsta.New("USERNAME", "PASSWORD")
- if err := insta.Login(); err != nil {
- fmt.Println(err)
- return
- }
- defer insta.Logout()
+ // also you can use New function from gopkg.in/ahmdrz/goinsta.v2/utils
- ...
+ // insta.SetProxy("http://localhost:8080", true) // true for insecure connections
+ if err := insta.Login(); err != nil {
+ fmt.Println(err)
+ return
+ }
+ // export your configuration
+ // after exporting you can use Import function instead of New function.
+ insta.Export("~/.goinsta")
+
+ ...
}
```
-In the next examples you can use an optional argument to use cache config.
+* [**More Examples**](https://github.com/ahmdrz/goinsta/tree/master/examples)
+
+### Projects using `goinsta`
-* [**More Examples**](https://github.com/ahmdrz/goinsta/tree/v2/examples)
+- [instagraph](https://github.com/ahmdrz/instagraph)
+- [icrawler](https://github.com/themester/icrawler)
+- [go-instabot](https://github.com/tducasse/go-instabot)
+- [ermes](https://github.com/borteo/ermes)
+- [nick\_bot](https://github.com/icholy/nick_bot)
+- [goinstadownload](https://github.com/alejoloaiza/goinstadownload)
+- [instafeed](https://github.com/falzm/instafeed)
+- [keepig](https://github.com/seankhliao/keepig)
+- ...
-## Legal
+### Legal
This code is in no way affiliated with, authorized, maintained, sponsored or endorsed by Instagram or any of its affiliates or subsidiaries. This is an independent and unofficial API. Use at your own risk.
-## Donate
+### Versioning
+
+Goinsta used gopkg.in as versioning control. Stable new API is the version v2.0. You can get it using:
+```bash
+go get -u -v gopkg.in/ahmdrz/goinsta.v2
+```
+
+### New version !
+
+We are working on a new object-oriented API. Try it and tell us your suggestions. See https://github.com/ahmdrz/goinsta/blob/master/CONTRIBUTING.md
+
+If you want to use the old version you can found it in v1 branch or using gopkg.in/ahmdrz/goinsta.v1/
+
+Sorry for breaking dependences :(. You can use this command in your project folder to update old master branch to v1.
+
+```bash
+for i in `grep -r ahmdrz ./ | awk '{split($0, a, ":"); print a[1]}'`; do sed -i 's/github\.com\/ahmdrz\/goinsta/gopkg\.in\/ahmdrz\/goinsta\.v1/g' $i; done
+```
+
+### Donate
**Ahmdrz**
@@ -75,38 +104,5 @@ This code is in no way affiliated with, authorized, maintained, sponsored or end
![btc](https://raw.githubusercontent.com/reek/anti-adblock-killer/gh-pages/images/bitcoin.png) Bitcoin: `37aogDJYBFkdSJTWG7TgcpgNweGHPCy1Ks`
-
[![Analytics](https://ga-beacon.appspot.com/UA-107698067-1/readme-page)](https://github.com/igrigorik/ga-beacon)
-
-## Schema
-
-Instagram
-- Account: Personal information and account interactions.
- - Followers
- - Following
- - Feed
- - Stories
- - Liked
- - Saved
- - Tags
-- Profiles: User interaction.
- - Blocked
- - Get user using ID
- - Get user using Username
-- Media:
- - Comments
- - Likes
- - Likers
-- Search:
- - Location
- - Username
- - Tags
- - Location **Deprecated**
- - Facebook
-- Activity:
- - Following
- - Recent
-- Hashtag: Hashtag allows user to search using hashtags.
- - Stories
- - Media
diff --git a/account.go b/account.go
index 5a9af479..cb7ed746 100644
--- a/account.go
+++ b/account.go
@@ -54,7 +54,7 @@ type Account struct {
SocialContext string `json:"social_context,omitempty"`
SearchSocialContext string `json:"search_social_context,omitempty"`
MutualFollowersCount float64 `json:"mutual_followers_count"`
- LatestReelMedia int `json:"latest_reel_media,omitempty"`
+ LatestReelMedia int64 `json:"latest_reel_media,omitempty"`
CityID int64 `json:"city_id"`
CityName string `json:"city_name"`
AddressStreet string `json:"address_street"`
@@ -77,14 +77,11 @@ func (account *Account) Sync() error {
if err != nil {
return err
}
-
- body, err := insta.sendRequest(
- &reqOptions{
- Endpoint: urlSyncProfile,
- Query: generateSignature(data),
- IsPost: true,
- },
- )
+ body, err := insta.sendRequest(&reqOptions{
+ Endpoint: urlCurrentUser,
+ Query: generateSignature(data),
+ IsPost: true,
+ })
if err == nil {
resp := profResp{}
err = json.Unmarshal(body, &resp)
@@ -246,17 +243,27 @@ func (account *Account) Following() *Users {
// Feed returns current account feed
//
+// params can be:
+// string: timestamp of the minimum media timestamp.
+//
// minTime is the minimum timestamp of media.
//
// For pagination use FeedMedia.Next()
-func (account *Account) Feed(minTime []byte) *FeedMedia {
+func (account *Account) Feed(params ...interface{}) *FeedMedia {
insta := account.inst
media := &FeedMedia{}
media.inst = insta
- media.timestamp = string(minTime)
media.endpoint = urlUserFeed
media.uid = account.ID
+
+ for _, param := range params {
+ switch s := param.(type) {
+ case string:
+ media.timestamp = s
+ }
+ }
+
return media
}
@@ -312,10 +319,36 @@ func (account *Account) Saved() (*SavedMedia, error) {
return nil, err
}
+type editResp struct {
+ Status string `json:"status"`
+ Account Account `json:"user"`
+}
+
+func (account *Account) edit() {
+ insta := account.inst
+ acResp := editResp{}
+ body, err := insta.sendRequest(
+ &reqOptions{
+ Endpoint: urlCurrentUser,
+ Query: map[string]string{
+ "edit": "true",
+ },
+ },
+ )
+ if err == nil {
+ err = json.Unmarshal(body, &acResp)
+ if err == nil {
+ acResp.Account.inst = insta
+ *account = acResp.Account
+ }
+ }
+}
+
// SetBiography changes your Instagram's biography.
//
// This function updates current Account information.
func (account *Account) SetBiography(bio string) error {
+ account.edit() // preparing to edit
insta := account.inst
data, err := insta.prepareData(
map[string]interface{}{
diff --git a/account_test.go b/account_test.go
deleted file mode 100644
index 503f0f38..00000000
--- a/account_test.go
+++ /dev/null
@@ -1,115 +0,0 @@
-package goinsta
-
-func ExampleAccount_ChangePass() {
- // See more: example/account/changepass.go
- fmt.Print("Password: ")
- pass, err := gopass.GetPasswd()
- if err != nil {
- panic(err)
- }
-
- fmt.Print("New password: ")
- pass2, err := gopass.GetPasswd()
- if err != nil {
- panic(err)
- }
-
- inst := goinsta.New(os.Args[1], string(pass))
-
- err = inst.Login()
- checkErr(err)
- fmt.Printf("Hello %s!\n", inst.Account.Username)
-
- err = inst.Account.ChangePassword(string(pass), string(pass2))
- if err == nil {
- fmt.Printf("Password have been changed\n")
- } else {
- fmt.Printf("Password cannot be changed: %s\n", err)
- }
-
- err = inst.Logout()
- checkErr(err)
-}
-
-func ExampleAccount_Followers() {
- inst, err := e.InitGoinsta("")
- e.CheckErr(err)
-
- users := inst.Account.Followers()
-
- for users.Next() {
- fmt.Println("Next:", users.NextID)
- for _, user := range users.Users {
- fmt.Printf(" - %s\n", user.Username)
- }
- }
-}
-
-func ExampleAccount_Following() {
- inst, err := e.InitGoinsta("")
- e.CheckErr(err)
-
- users := inst.Account.Following()
- e.CheckErr(err)
-
- for users.Next() {
- fmt.Println("Next:", users.NextID)
- for _, user := range users.Users {
- fmt.Printf(" - %s\n", user.Username)
- }
- }
-}
-
-func ExampleAccount_RemoveProfilePic() {
- inst, err := e.InitGoinsta("")
- e.CheckErr(err)
-
- fmt.Printf("Profile picture URL: %s\n", inst.Account.ProfilePicURL)
-
- err = inst.Account.RemoveProfilePic()
- e.CheckErr(err)
- fmt.Printf("After calling func: Profile picture URL: %s\n", inst.Account.ProfilePicURL)
-}
-
-func ExampleAccount_SetPrivate() {
- inst, err := e.InitGoinsta("")
- e.CheckErr(err)
-
- fmt.Printf("Is private: %v\n", inst.Account.IsPrivate)
-
- err = inst.Account.SetPrivate()
- e.CheckErr(err)
- fmt.Printf("After calling func: Is private: %v\n", inst.Account.IsPrivate)
-}
-
-func ExampleAccount_SetPublic() {
- inst, err := e.InitGoinsta("")
- e.CheckErr(err)
-
- fmt.Printf("Is private: %v\n", inst.Account.IsPrivate)
-
- err = inst.Account.SetPublic()
- e.CheckErr(err)
- fmt.Printf("After calling func: Is private: %v\n", inst.Account.IsPrivate)
-}
-
-func ExampleAccount_Stories() {
- inst, err := e.InitGoinsta("")
- e.CheckErr(err)
-
- stories := inst.Account.Stories()
- e.CheckErr(err)
-
- for stories.Next() {
- // getting images URL
- for _, item := range stories.Items {
- if len(item.Images.Versions) > 0 {
- fmt.Printf(" Image - %s\n", item.Images.Versions[0].URL)
- }
- if len(item.Videos) > 0 {
- fmt.Printf(" Video - %s\n", item.Videos[0].URL)
- }
- }
- }
- fmt.Println(stories.Error())
-}
diff --git a/activity.go b/activity.go
index 7ae788a5..57b390b5 100644
--- a/activity.go
+++ b/activity.go
@@ -12,6 +12,7 @@ type Activity struct {
inst *Instagram
}
+//FollowingActivity is the latest activity of the people you are following
type FollowingActivity struct {
inst *Instagram
err error
@@ -31,16 +32,16 @@ type FollowingActivity struct {
Type string `json:"type"`
ID string `json:"id"`
} `json:"links"`
- ProfileID int `json:"profile_id"`
+ ProfileID int64 `json:"profile_id"`
ProfileImage string `json:"profile_image"`
- SecondProfileID int `json:"second_profile_id"`
+ SecondProfileID int64 `json:"second_profile_id"`
SecondProfileImage string `json:"second_profile_image"`
ProfileImageDestination string `json:"profile_image_destination"`
Media []struct {
ID string `json:"id"`
Image string `json:"image"`
} `json:"media"`
- Timestamp int `json:"timestamp"`
+ Timestamp int64 `json:"timestamp"`
Tuuid string `json:"tuuid"`
} `json:"args"`
Counts struct {
@@ -133,19 +134,19 @@ type MineActivity struct {
OutgoingRequest bool `json:"outgoing_request"`
} `json:"inline_follow"`
Actions []string `json:"actions"`
- ProfileID int `json:"profile_id"`
+ ProfileID int64 `json:"profile_id"`
ProfileImage string `json:"profile_image"`
Timestamp float64 `json:"timestamp"`
Tuuid string `json:"tuuid"`
Clicked bool `json:"clicked"`
ProfileName string `json:"profile_name"`
- LatestReelMedia int `json:"latest_reel_media"`
+ LatestReelMedia int64 `json:"latest_reel_media"`
} `json:"args"`
Counts struct {
} `json:"counts"`
Pk string `json:"pk"`
} `json:"old_stories"`
- ContinuationToken int `json:"continuation_token"`
+ ContinuationToken int64 `json:"continuation_token"`
Subscription interface{} `json:"subscription"`
NextID int64 `json:"next_max_id"`
Status string `json:"status"`
diff --git a/comments.go b/comments.go
index 0fb4a337..219b7458 100644
--- a/comments.go
+++ b/comments.go
@@ -3,6 +3,7 @@ package goinsta
import (
"encoding/json"
"fmt"
+ "strconv"
)
// Comments allows user to interact with media (item) comments.
@@ -12,19 +13,29 @@ type Comments struct {
endpoint string
err error
- Items []Comment `json:"comments"`
- 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"`
- DisplayRealtimeTypingIndicator bool `json:"display_realtime_typing_indicator"`
- NextID string `json:"next_max_id"`
- LastID string `json:"next_min_id"`
- Status string `json:"status"`
+ Items []Comment `json:"comments"`
+ CommentCount int64 `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"`
+ ThreadingEnabled bool `json:"threading_enabled"`
+ MediaHeaderDisplay string `json:"media_header_display"`
+ InitiateAtTop bool `json:"initiate_at_top"`
+ InsertNewCommentToTop bool `json:"insert_new_comment_to_top"`
+ PreviewComments []Comment `json:"preview_comments"`
+ NextMaxID json.RawMessage `json:"next_max_id,omitempty"`
+ NextMinID json.RawMessage `json:"next_min_id,omitempty"`
+ CommentLikesEnabled bool `json:"comment_likes_enabled"`
+ DisplayRealtimeTypingIndicator bool `json:"display_realtime_typing_indicator"`
+ Status string `json:"status"`
+ //PreviewComments []Comment `json:"preview_comments"`
+}
- //PreviewComments []Comment `json:"preview_comments"`
+func (comments *Comments) setValues() {
+ for i := range comments.Items {
+ comments.Items[i].setValues(comments.item.media.instagram())
+ }
}
func newComments(item *Item) *Comments {
@@ -109,26 +120,24 @@ func (comments *Comments) Next() bool {
}
item := comments.item
- insta := comments.item.media.instagram()
- data, err := insta.prepareData(
- map[string]interface{}{
- "can_support_threading": true,
- "max_id": comments.NextID,
- "min_id": comments.LastID,
- },
- )
- if err != nil {
- comments.err = err
- return false
- }
-
+ insta := item.media.instagram()
endpoint := comments.endpoint
+ query := map[string]string{
+ // "can_support_threading": "true",
+ }
+ if comments.NextMaxID != nil {
+ next, _ := strconv.Unquote(string(comments.NextMaxID))
+ query["max_id"] = next
+ } else if comments.NextMinID != nil {
+ next, _ := strconv.Unquote(string(comments.NextMinID))
+ query["min_id"] = next
+ }
body, err := insta.sendRequest(
&reqOptions{
- Endpoint: endpoint,
- Query: generateSignature(data),
- IsPost: true,
+ Endpoint: endpoint,
+ Connection: "keep-alive",
+ Query: query,
},
)
if err == nil {
@@ -138,9 +147,11 @@ func (comments *Comments) Next() bool {
*comments = c
comments.endpoint = endpoint
comments.item = item
- if !comments.HasMoreComments || comments.NextID == "" {
+ if (!comments.HasMoreComments || comments.NextMaxID == nil) &&
+ (!comments.HasMoreHeadloadComments || comments.NextMinID == nil) {
comments.err = ErrNoMore
}
+ comments.setValues()
return true
}
}
@@ -165,44 +176,53 @@ func (comments *Comments) Sync() {
//
// See example: examples/media/commentsAdd.go
func (comments *Comments) Add(text string) (err error) {
- var url, data string
+ var opt *reqOptions
item := comments.item
insta := item.media.instagram()
switch item.media.(type) {
- case *StoryMedia: // TODO: story causes error
- url = urlReplyStory
- data, err = insta.prepareData(
+ case *StoryMedia:
+ to, err := prepareRecipients(item)
+ if err != nil {
+ return err
+ }
+
+ query := insta.prepareDataQuery(
map[string]interface{}{
- "recipient_users": fmt.Sprintf("[[%d]]", item.User.ID),
+ "recipient_users": to,
"action": "send_item",
- "client_context": insta.dID,
"media_id": item.ID,
+ "client_context": generateUUID(),
"text": text,
"entry": "reel",
"reel_id": item.User.ID,
},
)
+ opt = &reqOptions{
+ Connection: "keep-alive",
+ Endpoint: fmt.Sprintf("%s?media_type=%s", urlReplyStory, item.MediaToString()),
+ Query: query,
+ IsPost: true,
+ }
case *FeedMedia: // normal media
- url = fmt.Sprintf(urlCommentAdd, item.Pk)
+ var data string
data, err = insta.prepareData(
map[string]interface{}{
"comment_text": text,
},
)
+ opt = &reqOptions{
+ Endpoint: fmt.Sprintf(urlCommentAdd, item.Pk),
+ Query: generateSignature(data),
+ IsPost: true,
+ }
}
if err != nil {
return err
}
// ignoring response
- _, err = insta.sendRequest(
- &reqOptions{
- Endpoint: url,
- Query: generateSignature(data),
- IsPost: true,
- },
- )
+ _, err = insta.sendRequest(opt)
return err
}
@@ -263,3 +283,88 @@ floop:
}
return nil
}
+
+// Comment is a type of Media retrieved by the Comments methods
+type Comment struct {
+ inst *Instagram
+ idstr string
+
+ ID int64 `json:"pk"`
+ Text string `json:"text"`
+ Type int `json:"type"`
+ User User `json:"user"`
+ UserID int64 `json:"user_id"`
+ BitFlags int `json:"bit_flags"`
+ ChildCommentCount int `json:"child_comment_count"`
+ CommentIndex int `json:"comment_index"`
+ CommentLikeCount int `json:"comment_like_count"`
+ ContentType string `json:"content_type"`
+ CreatedAt int64 `json:"created_at"`
+ CreatedAtUtc int64 `json:"created_at_utc"`
+ DidReportAsSpam bool `json:"did_report_as_spam"`
+ HasLikedComment bool `json:"has_liked_comment"`
+ InlineComposerDisplayCondition string `json:"inline_composer_display_condition"`
+ OtherPreviewUsers []User `json:"other_preview_users"`
+ PreviewChildComments []Comment `json:"preview_child_comments"`
+ NextMaxChildCursor string `json:"next_max_child_cursor,omitempty"`
+ HasMoreTailChildComments bool `json:"has_more_tail_child_comments,omitempty"`
+ NextMinChildCursor string `json:"next_min_child_cursor,omitempty"`
+ HasMoreHeadChildComments bool `json:"has_more_head_child_comments,omitempty"`
+ NumTailChildComments int `json:"num_tail_child_comments,omitempty"`
+ NumHeadChildComments int `json:"num_head_child_comments,omitempty"`
+ Status string `json:"status"`
+}
+
+func (c *Comment) setValues(inst *Instagram) {
+ c.User.inst = inst
+ for i := range c.OtherPreviewUsers {
+ c.OtherPreviewUsers[i].inst = inst
+ }
+ for i := range c.PreviewChildComments {
+ c.PreviewChildComments[i].setValues(inst)
+ }
+}
+
+func (c Comment) getid() string {
+ switch {
+ case c.ID == 0:
+ return c.idstr
+ case c.idstr == "":
+ return strconv.FormatInt(c.ID, 10)
+ }
+ return ""
+}
+
+// Like likes comment.
+func (c *Comment) Like() error {
+ data, err := c.inst.prepareData()
+ if err != nil {
+ return err
+ }
+
+ _, err = c.inst.sendRequest(
+ &reqOptions{
+ Endpoint: fmt.Sprintf(urlCommentLike, c.getid()),
+ Query: generateSignature(data),
+ IsPost: true,
+ },
+ )
+ return err
+}
+
+// Unlike unlikes comment.
+func (c *Comment) Unlike() error {
+ data, err := c.inst.prepareData()
+ if err != nil {
+ return err
+ }
+
+ _, err = c.inst.sendRequest(
+ &reqOptions{
+ Endpoint: fmt.Sprintf(urlCommentUnlike, c.getid()),
+ Query: generateSignature(data),
+ IsPost: true,
+ },
+ )
+ return err
+}
diff --git a/const.go b/const.go
index 8642a635..188e123b 100644
--- a/const.go
+++ b/const.go
@@ -2,31 +2,48 @@ package goinsta
const (
goInstaAPIUrl = "https://i.instagram.com/api/v1/"
- goInstaUserAgent = "Instagram 10.26.0 Android (18/4.3; 320dpi; 720x1280; Xiaomi; HM 1SW; armani; qcom; en_US)"
- goInstaIGSigKey = "4f8732eb9ba7d1c8e8897a75d6474d4eb3f5279137431b2aafb71fafe2abe178"
- goInstaExperiments = "ig_promote_reach_objective_fix_universe,ig_android_universe_video_production,ig_search_client_h1_2017_holdout,ig_android_live_follow_from_comments_universe,ig_android_carousel_non_square_creation,ig_android_live_analytics,ig_android_follow_all_dialog_confirmation_copy,ig_android_stories_server_coverframe,ig_android_video_captions_universe,ig_android_offline_location_feed,ig_android_direct_inbox_retry_seen_state,ig_android_ontact_invite_universe,ig_android_live_broadcast_blacklist,ig_android_insta_video_reconnect_viewers,ig_android_ad_async_ads_universe,ig_android_search_clear_layout_universe,ig_android_shopping_reporting,ig_android_stories_surface_universe,ig_android_verified_comments_universe,ig_android_preload_media_ahead_in_current_reel,android_instagram_prefetch_suggestions_universe,ig_android_reel_viewer_fetch_missing_reels_universe,ig_android_direct_search_share_sheet_universe,ig_android_business_promote_tooltip,ig_android_direct_blue_tab,ig_android_async_network_tweak_universe,ig_android_elevate_main_thread_priority_universe,ig_android_stories_gallery_nux,ig_android_instavideo_remove_nux_comments,ig_video_copyright_whitelist,ig_react_native_inline_insights_with_relay,ig_android_direct_thread_message_animation,ig_android_draw_rainbow_client_universe,ig_android_direct_link_style,ig_android_live_heart_enhancements_universe,ig_android_rtc_reshare,ig_android_preload_item_count_in_reel_viewer_buffer,ig_android_users_bootstrap_service,ig_android_auto_retry_post_mode,ig_android_shopping,ig_android_main_feed_seen_state_dont_send_info_on_tail_load,ig_fbns_preload_default,ig_android_gesture_dismiss_reel_viewer,ig_android_tool_tip,ig_android_ad_logger_funnel_logging_universe,ig_android_gallery_grid_column_count_universe,ig_android_business_new_ads_payment_universe,ig_android_direct_links,ig_android_audience_control,ig_android_live_encore_consumption_settings_universe,ig_perf_android_holdout,ig_android_cache_contact_import_list,ig_android_links_receivers,ig_android_ad_impression_backtest,ig_android_list_redesign,ig_android_stories_separate_overlay_creation,ig_android_stop_video_recording_fix_universe,ig_android_render_video_segmentation,ig_android_live_encore_reel_chaining_universe,ig_android_sync_on_background_enhanced_10_25,ig_android_immersive_viewer,ig_android_mqtt_skywalker,ig_fbns_push,ig_android_ad_watchmore_overlay_universe,ig_android_react_native_universe,ig_android_profile_tabs_redesign_universe,ig_android_live_consumption_abr,ig_android_story_viewer_social_context,ig_android_hide_post_in_feed,ig_android_video_loopcount_int,ig_android_enable_main_feed_reel_tray_preloading,ig_android_camera_upsell_dialog,ig_android_ad_watchbrowse_universe,ig_android_internal_research_settings,ig_android_search_people_tag_universe,ig_android_react_native_ota,ig_android_enable_concurrent_request,ig_android_react_native_stories_grid_view,ig_android_business_stories_inline_insights,ig_android_log_mediacodec_info,ig_android_direct_expiring_media_loading_errors,ig_video_use_sve_universe,ig_android_cold_start_feed_request,ig_android_enable_zero_rating,ig_android_reverse_audio,ig_android_branded_content_three_line_ui_universe,ig_android_live_encore_production_universe,ig_stories_music_sticker,ig_android_stories_teach_gallery_location,ig_android_http_stack_experiment_2017,ig_android_stories_device_tilt,ig_android_pending_request_search_bar,ig_android_fb_topsearch_sgp_fork_request,ig_android_seen_state_with_view_info,ig_android_animation_perf_reporter_timeout,ig_android_new_block_flow,ig_android_story_tray_title_play_all_v2,ig_android_direct_address_links,ig_android_stories_archive_universe,ig_android_save_collections_cover_photo,ig_android_live_webrtc_livewith_production,ig_android_sign_video_url,ig_android_stories_video_prefetch_kb,ig_android_stories_create_flow_favorites_tooltip,ig_android_live_stop_broadcast_on_404,ig_android_live_viewer_invite_universe,ig_android_promotion_feedback_channel,ig_android_render_iframe_interval,ig_android_accessibility_logging_universe,ig_android_camera_shortcut_universe,ig_android_use_one_cookie_store_per_user_override,ig_profile_holdout_2017_universe,ig_android_stories_server_brushes,ig_android_ad_media_url_logging_universe,ig_android_shopping_tag_nux_text_universe,ig_android_comments_single_reply_universe,ig_android_stories_video_loading_spinner_improvements,ig_android_collections_cache,ig_android_comment_api_spam_universe,ig_android_facebook_twitter_profile_photos,ig_android_shopping_tag_creation_universe,ig_story_camera_reverse_video_experiment,ig_android_direct_bump_selected_recipients,ig_android_ad_cta_haptic_feedback_universe,ig_android_vertical_share_sheet_experiment,ig_android_family_bridge_share,ig_android_search,ig_android_insta_video_consumption_titles,ig_android_stories_gallery_preview_button,ig_android_fb_auth_education,ig_android_camera_universe,ig_android_me_only_universe,ig_android_instavideo_audio_only_mode,ig_android_user_profile_chaining_icon,ig_android_live_video_reactions_consumption_universe,ig_android_stories_hashtag_text,ig_android_post_live_badge_universe,ig_android_swipe_fragment_container,ig_android_search_users_universe,ig_android_live_save_to_camera_roll_universe,ig_creation_growth_holdout,ig_android_sticker_region_tracking,ig_android_unified_inbox,ig_android_live_new_watch_time,ig_android_offline_main_feed_10_11,ig_import_biz_contact_to_page,ig_android_live_encore_consumption_universe,ig_android_experimental_filters,ig_android_search_client_matching_2,ig_android_react_native_inline_insights_v2,ig_android_business_conversion_value_prop_v2,ig_android_redirect_to_low_latency_universe,ig_android_ad_show_new_awr_universe,ig_family_bridges_holdout_universe,ig_android_background_explore_fetch,ig_android_following_follower_social_context,ig_android_video_keep_screen_on,ig_android_ad_leadgen_relay_modern,ig_android_profile_photo_as_media,ig_android_insta_video_consumption_infra,ig_android_ad_watchlead_universe,ig_android_direct_prefetch_direct_story_json,ig_android_shopping_react_native,ig_android_top_live_profile_pics_universe,ig_android_direct_phone_number_links,ig_android_stories_weblink_creation,ig_android_direct_search_new_thread_universe,ig_android_histogram_reporter,ig_android_direct_on_profile_universe,ig_android_network_cancellation,ig_android_background_reel_fetch,ig_android_react_native_insights,ig_android_insta_video_audio_encoder,ig_android_family_bridge_bookmarks,ig_android_data_usage_network_layer,ig_android_universal_instagram_deep_links,ig_android_dash_for_vod_universe,ig_android_modular_tab_discover_people_redesign,ig_android_mas_sticker_upsell_dialog_universe,ig_android_ad_add_per_event_counter_to_logging_event,ig_android_sticky_header_top_chrome_optimization,ig_android_rtl,ig_android_biz_conversion_page_pre_select,ig_android_promote_from_profile_button,ig_android_live_broadcaster_invite_universe,ig_android_share_spinner,ig_android_text_action,ig_android_own_reel_title_universe,ig_promotions_unit_in_insights_landing_page,ig_android_business_settings_header_univ,ig_android_save_longpress_tooltip,ig_android_constrain_image_size_universe,ig_android_business_new_graphql_endpoint_universe,ig_ranking_following,ig_android_stories_profile_camera_entry_point,ig_android_universe_reel_video_production,ig_android_power_metrics,ig_android_sfplt,ig_android_offline_hashtag_feed,ig_android_live_skin_smooth,ig_android_direct_inbox_search,ig_android_stories_posting_offline_ui,ig_android_sidecar_video_upload_universe,ig_android_promotion_manager_entry_point_universe,ig_android_direct_reply_audience_upgrade,ig_android_swipe_navigation_x_angle_universe,ig_android_offline_mode_holdout,ig_android_live_send_user_location,ig_android_direct_fetch_before_push_notif,ig_android_non_square_first,ig_android_insta_video_drawing,ig_android_swipeablefilters_universe,ig_android_live_notification_control_universe,ig_android_analytics_logger_running_background_universe,ig_android_save_all,ig_android_reel_viewer_data_buffer_size,ig_direct_quality_holdout_universe,ig_android_family_bridge_discover,ig_android_react_native_restart_after_error_universe,ig_android_startup_manager,ig_story_tray_peek_content_universe,ig_android_profile,ig_android_high_res_upload_2,ig_android_http_service_same_thread,ig_android_scroll_to_dismiss_keyboard,ig_android_remove_followers_universe,ig_android_skip_video_render,ig_android_story_timestamps,ig_android_live_viewer_comment_prompt_universe,ig_profile_holdout_universe,ig_android_react_native_insights_grid_view,ig_stories_selfie_sticker,ig_android_stories_reply_composer_redesign,ig_android_streamline_page_creation,ig_explore_netego,ig_android_ig4b_connect_fb_button_universe,ig_android_feed_util_rect_optimization,ig_android_rendering_controls,ig_android_os_version_blocking,ig_android_encoder_width_safe_multiple_16,ig_search_new_bootstrap_holdout_universe,ig_android_snippets_profile_nux,ig_android_e2e_optimization_universe,ig_android_comments_logging_universe,ig_shopping_insights,ig_android_save_collections,ig_android_live_see_fewer_videos_like_this_universe,ig_android_show_new_contact_import_dialog,ig_android_live_view_profile_from_comments_universe,ig_fbns_blocked,ig_formats_and_feedbacks_holdout_universe,ig_android_reduce_view_pager_buffer,ig_android_instavideo_periodic_notif,ig_search_user_auto_complete_cache_sync_ttl,ig_android_marauder_update_frequency,ig_android_suggest_password_reset_on_oneclick_login,ig_android_promotion_entry_from_ads_manager_universe,ig_android_live_special_codec_size_list,ig_android_enable_share_to_messenger,ig_android_background_main_feed_fetch,ig_android_live_video_reactions_creation_universe,ig_android_channels_home,ig_android_sidecar_gallery_universe,ig_android_upload_reliability_universe,ig_migrate_mediav2_universe,ig_android_insta_video_broadcaster_infra_perf,ig_android_business_conversion_social_context,android_ig_fbns_kill_switch,ig_android_live_webrtc_livewith_consumption,ig_android_destroy_swipe_fragment,ig_android_react_native_universe_kill_switch,ig_android_stories_book_universe,ig_android_all_videoplayback_persisting_sound,ig_android_draw_eraser_universe,ig_direct_search_new_bootstrap_holdout_universe,ig_android_cache_layer_bytes_threshold,ig_android_search_hash_tag_and_username_universe,ig_android_business_promotion,ig_android_direct_search_recipients_controller_universe,ig_android_ad_show_full_name_universe,ig_android_anrwatchdog,ig_android_qp_kill_switch,ig_android_2fac,ig_direct_bypass_group_size_limit_universe,ig_android_promote_simplified_flow,ig_android_share_to_whatsapp,ig_android_hide_bottom_nav_bar_on_discover_people,ig_fbns_dump_ids,ig_android_hands_free_before_reverse,ig_android_skywalker_live_event_start_end,ig_android_live_join_comment_ui_change,ig_android_direct_search_story_recipients_universe,ig_android_direct_full_size_gallery_upload,ig_android_ad_browser_gesture_control,ig_channel_server_experiments,ig_android_video_cover_frame_from_original_as_fallback,ig_android_ad_watchinstall_universe,ig_android_ad_viewability_logging_universe,ig_android_new_optic,ig_android_direct_visual_replies,ig_android_stories_search_reel_mentions_universe,ig_android_threaded_comments_universe,ig_android_mark_reel_seen_on_Swipe_forward,ig_internal_ui_for_lazy_loaded_modules_experiment,ig_fbns_shared,ig_android_capture_slowmo_mode,ig_android_live_viewers_list_search_bar,ig_android_video_single_surface,ig_android_offline_reel_feed,ig_android_video_download_logging,ig_android_last_edits,ig_android_exoplayer_4142,ig_android_post_live_viewer_count_privacy_universe,ig_android_activity_feed_click_state,ig_android_snippets_haptic_feedback,ig_android_gl_drawing_marks_after_undo_backing,ig_android_mark_seen_state_on_viewed_impression,ig_android_live_backgrounded_reminder_universe,ig_android_live_hide_viewer_nux_universe,ig_android_live_monotonic_pts,ig_android_search_top_search_surface_universe,ig_android_user_detail_endpoint,ig_android_location_media_count_exp_ig,ig_android_comment_tweaks_universe,ig_android_ad_watchmore_entry_point_universe,ig_android_top_live_notification_universe,ig_android_add_to_last_post,ig_save_insights,ig_android_live_enhanced_end_screen_universe,ig_android_ad_add_counter_to_logging_event,ig_android_blue_token_conversion_universe,ig_android_exoplayer_settings,ig_android_progressive_jpeg,ig_android_offline_story_stickers,ig_android_gqls_typing_indicator,ig_android_chaining_button_tooltip,ig_android_video_prefetch_for_connectivity_type,ig_android_use_exo_cache_for_progressive,ig_android_samsung_app_badging,ig_android_ad_holdout_watchandmore_universe,ig_android_offline_commenting,ig_direct_stories_recipient_picker_button,ig_insights_feedback_channel_universe,ig_android_insta_video_abr_resize,ig_android_insta_video_sound_always_on"
+ goInstaAPIUrlv2 = "https://i.instagram.com/api/v2/"
+ goInstaUserAgent = "Instagram 27.0.0.7.97 Android (24/7.0; 380dpi; 1080x1920; OnePlus; ONEPLUS A3010; OnePlus3T; qcom; en_US)"
+ goInstaIGSigKey = "109513c04303341a7daf27bb41b268e633b30dcc65a3fe14503f743176113869"
+ fbAnalytics = "567067343352427"
+ igCapabilities = "3brTBw=="
+ connType = "WIFI"
+ goInstaExperiments = "ig_promote_reach_objective_fix_universe,ig_android_universe_video_production,ig_search_client_h1_2017_holdout,ig_android_live_follow_from_comments_universe,ig_android_carousel_non_square_creation,ig_android_live_analytics,ig_android_follow_all_dialog_confirmation_copy,ig_android_stories_server_coverframe,ig_android_video_captions_universe,ig_android_offline_location_feed,ig_android_direct_inbox_retry_seen_state,ig_android_ontact_invite_universe,ig_android_live_broadcast_blacklist,ig_android_insta_video_reconnect_viewers,ig_android_ad_async_ads_universe,ig_android_search_clear_layout_universe,ig_android_shopping_reporting,ig_android_stories_surface_universe,ig_android_verified_comments_universe,ig_android_preload_media_ahead_in_current_reel,android_instagram_prefetch_suggestions_universe,ig_android_reel_viewer_fetch_missing_reels_universe,ig_android_direct_search_share_sheet_universe,ig_android_business_promote_tooltip,ig_android_direct_blue_tab,ig_android_async_network_tweak_universe,ig_android_elevate_main_thread_priority_universe,ig_android_stories_gallery_nux,ig_android_instavideo_remove_nux_comments,ig_video_copyright_whitelist,ig_react_native_inline_insights_with_relay,ig_android_direct_thread_message_animation,ig_android_draw_rainbow_client_universe,ig_android_direct_link_style,ig_android_live_heart_enhancements_universe,ig_android_rtc_reshare,ig_android_preload_item_count_in_reel_viewer_buffer,ig_android_users_bootstrap_service,ig_android_auto_retry_post_mode,ig_android_shopping,ig_android_main_feed_seen_state_dont_send_info_on_tail_load,ig_fbns_preload_default,ig_android_gesture_dismiss_reel_viewer,ig_android_tool_tip,ig_android_ad_logger_funnel_logging_universe,ig_android_gallery_grid_column_count_universe,ig_android_business_new_ads_payment_universe,ig_android_direct_links,ig_android_audience_control,ig_android_live_encore_consumption_settings_universe,ig_perf_android_holdout,ig_android_cache_contact_import_list,ig_android_links_receivers,ig_android_ad_impression_backtest,ig_android_list_redesign,ig_android_stories_separate_overlay_creation,ig_android_stop_video_recording_fix_universe,ig_android_render_video_segmentation,ig_android_live_encore_reel_chaining_universe,ig_android_sync_on_background_enhanced_10_25,ig_android_immersive_viewer,ig_android_mqtt_skywalker,ig_fbns_push,ig_android_ad_watchmore_overlay_universe,ig_android_react_native_universe,ig_android_profile_tabs_redesign_universe,ig_android_live_consumption_abr,ig_android_story_viewer_social_context,ig_android_hide_post_in_feed,ig_android_video_loopcount_int,ig_android_enable_main_feed_reel_tray_preloading,ig_android_camera_upsell_dialog,ig_android_ad_watchbrowse_universe,ig_android_internal_research_settings,ig_android_search_people_tag_universe,ig_android_react_native_ota,ig_android_enable_concurrent_request,ig_android_react_native_stories_grid_view,ig_android_business_stories_inline_insights,ig_android_log_mediacodec_info,ig_android_direct_expiring_media_loading_errors,ig_video_use_sve_universe,ig_android_cold_start_feed_request,ig_android_enable_zero_rating,ig_android_reverse_audio,ig_android_branded_content_three_line_ui_universe,ig_android_live_encore_production_universe,ig_stories_music_sticker,ig_android_stories_teach_gallery_location,ig_android_http_stack_experiment_2017,ig_android_stories_device_tilt,ig_android_pending_request_search_bar,ig_android_fb_topsearch_sgp_fork_request,ig_android_seen_state_with_view_info,ig_android_animation_perf_reporter_timeout,ig_android_new_block_flow,ig_android_story_tray_title_play_all_v2,ig_android_direct_address_links,ig_android_stories_archive_universe,ig_android_save_collections_cover_photo,ig_android_live_webrtc_livewith_production,ig_android_sign_video_url,ig_android_stories_video_prefetch_kb,ig_android_stories_create_flow_favorites_tooltip,ig_android_live_stop_broadcast_on_404,ig_android_live_viewer_invite_universe,ig_android_promotion_feedback_channel,ig_android_render_iframe_interval,ig_android_accessibility_logging_universe,ig_android_camera_shortcut_universe,ig_android_use_one_cookie_store_per_user_override,ig_profile_holdout_2017_universe,ig_android_stories_server_brushes,ig_android_ad_media_url_logging_universe,ig_android_shopping_tag_nux_text_universe,ig_android_comments_single_reply_universe,ig_android_stories_video_loading_spinner_improvements,ig_android_collections_cache,ig_android_comment_api_spam_universe,ig_android_facebook_twitter_profile_photos,ig_android_shopping_tag_creation_universe,ig_story_camera_reverse_video_experiment,ig_android_direct_bump_selected_recipients,ig_android_ad_cta_haptic_feedback_universe,ig_android_vertical_share_sheet_experiment,ig_android_family_bridge_share,ig_android_search,ig_android_insta_video_consumption_titles,ig_android_stories_gallery_preview_button,ig_android_fb_auth_education,ig_android_camera_universe,ig_android_me_only_universe,ig_android_instavideo_audio_only_mode,ig_android_user_profile_chaining_icon,ig_android_live_video_reactions_consumption_universe,ig_android_stories_hashtag_text,ig_android_post_live_badge_universe,ig_android_swipe_fragment_container,ig_android_search_users_universe,ig_android_live_save_to_camera_roll_universe,ig_creation_growth_holdout,ig_android_sticker_region_tracking,ig_android_unified_inbox,ig_android_live_new_watch_time,ig_android_offline_main_feed_10_11,ig_import_biz_contact_to_page,ig_android_live_encore_consumption_universe,ig_android_experimental_filters,ig_android_search_client_matching_2,ig_android_react_native_inline_insights_v2,ig_android_business_conversion_value_prop_v2,ig_android_redirect_to_low_latency_universe,ig_android_ad_show_new_awr_universe,ig_family_bridges_holdout_universe,ig_android_background_explore_fetch,ig_android_following_follower_social_context,ig_android_video_keep_screen_on,ig_android_ad_leadgen_relay_modern,ig_android_profile_photo_as_media,ig_android_insta_video_consumption_infra,ig_android_ad_watchlead_universe,ig_android_direct_prefetch_direct_story_json,ig_android_shopping_react_native,ig_android_top_live_profile_pics_universe,ig_android_direct_phone_number_links,ig_android_stories_weblink_creation,ig_android_direct_search_new_thread_universe,ig_android_histogram_reporter,ig_android_direct_on_profile_universe,ig_android_network_cancellation,ig_android_background_reel_fetch,ig_android_react_native_insights,ig_android_insta_video_audio_encoder,ig_android_family_bridge_bookmarks,ig_android_data_usage_network_layer,ig_android_universal_instagram_deep_links,ig_android_dash_for_vod_universe,ig_android_modular_tab_discover_people_redesign,ig_android_mas_sticker_upsell_dialog_universe,ig_android_ad_add_per_event_counter_to_logging_event,ig_android_sticky_header_top_chrome_optimization,ig_android_rtl,ig_android_biz_conversion_page_pre_select,ig_android_promote_from_profile_button,ig_android_live_broadcaster_invite_universe,ig_android_share_spinner,ig_android_text_action,ig_android_own_reel_title_universe,ig_promotions_unit_in_insights_landing_page,ig_android_business_settings_header_univ,ig_android_save_longpress_tooltip,ig_android_constrain_image_size_universe,ig_android_business_new_graphql_endpoint_universe,ig_ranking_following,ig_android_stories_profile_camera_entry_point,ig_android_universe_reel_video_production,ig_android_power_metrics,ig_android_sfplt,ig_android_offline_hashtag_feed,ig_android_live_skin_smooth,ig_android_direct_inbox_search,ig_android_stories_posting_offline_ui,ig_android_sidecar_video_upload_universe,ig_android_promotion_manager_entry_point_universe,ig_android_direct_reply_audience_upgrade,ig_android_swipe_navigation_x_angle_universe,ig_android_offline_mode_holdout,ig_android_live_send_user_location,ig_android_direct_fetch_before_push_notif,ig_android_non_square_first,ig_android_insta_video_drawing,ig_android_swipeablefilters_universe,ig_android_live_notification_control_universe,ig_android_analytics_logger_running_background_universe,ig_android_save_all,ig_android_reel_viewer_data_buffer_size,ig_direct_quality_holdout_universe,ig_android_family_bridge_discover,ig_android_react_native_restart_after_error_universe,ig_android_startup_manager,ig_story_tray_peek_content_universe,ig_android_profile,ig_android_high_res_upload_2,ig_android_http_service_same_thread,ig_android_scroll_to_dismiss_keyboard,ig_android_remove_followers_universe,ig_android_skip_video_render,ig_android_story_timestamps,ig_android_live_viewer_comment_prompt_universe,ig_profile_holdout_universe,ig_android_react_native_insights_grid_view,ig_stories_selfie_sticker,ig_android_stories_reply_composer_redesign,ig_android_streamline_page_creation,ig_explore_netego,ig_android_ig4b_connect_fb_button_universe,ig_android_feed_util_rect_optimization,ig_android_rendering_controls,ig_android_os_version_blocking,ig_android_encoder_width_safe_multiple_16,ig_search_new_bootstrap_holdout_universe,ig_android_snippets_profile_nux,ig_android_e2e_optimization_universe,ig_android_comments_logging_universe,ig_shopping_insights,ig_android_save_collections,ig_android_live_see_fewer_videos_like_this_universe,ig_android_show_new_contact_import_dialog,ig_android_live_view_profile_from_comments_universe,ig_fbns_blocked,ig_formats_and_feedbacks_holdout_universe,ig_android_reduce_view_pager_buffer,ig_android_instavideo_periodic_notif,ig_search_user_auto_complete_cache_sync_ttl,ig_android_marauder_update_frequency,ig_android_suggest_password_reset_on_oneclick_login,ig_android_promotion_entry_from_ads_manager_universe,ig_android_live_special_codec_size_list,ig_android_enable_share_to_messenger,ig_android_background_main_feed_fetch,ig_android_live_video_reactions_creation_universe,ig_android_channels_home,ig_android_sidecar_gallery_universe,ig_android_upload_reliability_universe,ig_migrate_mediav2_universe,ig_android_insta_video_broadcaster_infra_perf,ig_android_business_conversion_social_context,android_ig_fbns_kill_switch,ig_android_live_webrtc_livewith_consumption,ig_android_destroy_swipe_fragment,ig_android_react_native_universe_kill_switch,ig_android_stories_book_universe,ig_android_all_videoplayback_persisting_sound,ig_android_draw_eraser_universe,ig_direct_search_new_bootstrap_holdout_universe,ig_android_cache_layer_bytes_threshold,ig_android_search_hash_tag_and_username_universe,ig_android_business_promotion,ig_android_direct_search_recipients_controller_universe,ig_android_ad_show_full_name_universe,ig_android_anrwatchdog,ig_android_qp_kill_switch,ig_android_2fac,ig_direct_bypass_group_size_limit_universe,ig_android_promote_simplified_flow,ig_android_share_to_whatsapp,ig_android_hide_bottom_nav_bar_on_discover_people,ig_fbns_dump_ids,ig_android_hands_free_before_reverse,ig_android_skywalker_live_event_start_end,ig_android_live_join_comment_ui_change,ig_android_direct_search_story_recipients_universe,ig_android_direct_full_size_gallery_upload,ig_android_ad_browser_gesture_control,ig_channel_server_experiments,ig_android_video_cover_frame_from_original_as_fallback,ig_android_ad_watchinstall_universe,ig_android_ad_viewability_logging_universe,ig_android_new_optic,ig_android_direct_visual_replies,ig_android_stories_search_reel_mentions_universe,ig_android_threaded_comments_universe,ig_android_mark_reel_seen_on_Swipe_forward,ig_internal_ui_for_lazy_loaded_modules_experiment,ig_fbns_shared,ig_android_capture_slowmo_mode,ig_android_live_viewers_list_search_bar,ig_android_video_single_surface,ig_android_offline_reel_feed,ig_android_video_download_logging,ig_android_last_editAs,ig_android_exoplayer_4142,ig_android_post_live_viewer_count_privacy_universe,ig_android_activity_feed_click_state,ig_android_snippets_haptic_feedback,ig_android_gl_drawing_marks_after_undo_backing,ig_android_mark_seen_state_on_viewed_impression,ig_android_live_backgrounded_reminder_universe,ig_android_live_hide_viewer_nux_universe,ig_android_live_monotonic_pts,ig_android_search_top_search_surface_universe,ig_android_user_detail_endpoint,ig_android_location_media_count_exp_ig,ig_android_comment_tweaks_universe,ig_android_ad_watchmore_entry_point_universe,ig_android_top_live_notification_universe,ig_android_add_to_last_post,ig_save_insights,ig_android_live_enhanced_end_screen_universe,ig_android_ad_add_counter_to_logging_event,ig_android_blue_token_conversion_universe,ig_android_exoplayer_settings,ig_android_progressive_jpeg,ig_android_offline_story_stickers,ig_android_gqls_typing_indicator,ig_android_chaining_button_tooltip,ig_android_video_prefetch_for_connectivity_type,ig_android_use_exo_cache_for_progressive,ig_android_samsung_app_badging,ig_android_ad_holdout_watchandmore_universe,ig_android_offline_commenting,ig_direct_stories_recipient_picker_button,ig_insights_feedback_channel_universe,ig_android_insta_video_abr_resize,ig_android_insta_video_sound_always_on"
goInstaSigKeyVersion = "4"
)
+var (
+ goInstaDeviceSettings = map[string]interface{}{
+ "manufacturer": "OnePlus",
+ "model": "A3010",
+ "android_version": 24,
+ "android_release": "7.0",
+ }
+)
+
// Endpoints (with format vars)
const (
// login
- urlFetchHeaders = "si/fetch_headers/"
- urlLogin = "accounts/login/"
- urlLogout = "accounts/logout/"
- urlAutoComplete = "friendships/autocomplete_user_list/"
- urlSync = "qe/sync/"
- urlMegaphoneLog = "megaphone/log/"
- urlExpose = "qe/expose/"
+ urlMsisdnHeader = "accounts/read_msisdn_header/"
+ urlContactPrefill = "accounts/contact_point_prefill/"
+ urlZrToken = "zr/token/result/"
+ urlLogin = "accounts/login/"
+ urlLogout = "accounts/logout/"
+ urlAutoComplete = "friendships/autocomplete_user_list/"
+ urlQeSync = "qe/sync/"
+ urlLogAttribution = "attribution/log_attribution/"
+ urlMegaphoneLog = "megaphone/log/"
+ urlExpose = "qe/expose/"
// account
+ urlCurrentUser = "accounts/current_user/"
urlChangePass = "accounts/change_password/"
urlSetPrivate = "accounts/set_private/"
urlSetPublic = "accounts/set_public/"
urlRemoveProfPic = "accounts/remove_profile_picture/"
- urlSyncProfile = "accounts/current_user/"
urlFeedSaved = "feed/saved/"
urlSetBiography = "accounts/set_biography/"
+ urlEditProfile = "accounts/edit_profile"
urlFeedLiked = "feed/liked/"
// account and profile
@@ -34,29 +51,34 @@ const (
urlFollowing = "friendships/%d/following/"
// users
- urlUserByName = "users/%s/usernameinfo/"
- urlUserById = "users/%d/info/"
- urlUserBlock = "friendships/block/%d/"
- urlUserUnblock = "friendships/unblock/%d/"
- urlUserFollow = "friendships/create/%d/"
- urlUserUnfollow = "friendships/destroy/%d/"
- urlUserFeed = "feed/user/%d/"
- urlFriendship = "friendships/show/%d/"
- urlUserStories = "feed/user/%d/reel_media/"
- urlUserTags = "usertags/%d/feed/"
- urlBlockedList = "users/blocked_list/"
- urlUserInfo = "users/%d/info/"
+ urlUserByName = "users/%s/usernameinfo/"
+ urlUserByID = "users/%d/info/"
+ urlUserBlock = "friendships/block/%d/"
+ urlUserUnblock = "friendships/unblock/%d/"
+ urlUserFollow = "friendships/create/%d/"
+ urlUserUnfollow = "friendships/destroy/%d/"
+ urlUserFeed = "feed/user/%d/"
+ urlFriendship = "friendships/show/%d/"
+ urlUserStories = "feed/user/%d/reel_media/"
+ urlUserTags = "usertags/%d/feed/"
+ urlBlockedList = "users/blocked_list/"
+ urlUserInfo = "users/%d/info/"
+ urlUserHighlights = "highlights/%d/highlights_tray/"
// timeline
- urlTimeline = "feed/timeline/"
- urlStories = "feed/reels_tray/"
+ urlTimeline = "feed/timeline/"
+ urlStories = "feed/reels_tray/"
+ urlReelMedia = "feed/reels_media/"
// search
urlSearchUser = "users/search/"
urlSearchTag = "tags/search/"
urlSearchLocation = "location_search/"
urlSearchFacebook = "fbsearch/topsearch/"
- urlSearchFeedTag = "feed/tag/%s/"
+
+ // feeds
+ urlFeedLocationID = "feed/location/%d/"
+ urlFeedTag = "feed/tag/%s/"
// media
urlMediaInfo = "media/%s/info/"
@@ -64,14 +86,17 @@ const (
urlMediaLike = "media/%s/like/"
urlMediaUnlike = "media/%s/unlike/"
urlMediaSave = "media/%s/save/"
+ urlMediaSeen = "media/seen/"
+ urlMediaLikers = "media/%s/likers/"
// comments
urlCommentAdd = "media/%d/comment/"
- urlCommentDelete = "media/%d/comment/%s/delete/"
- urlCommentSync = "media/%d/comments/"
- urlCommentDisable = "media/%d/disable_comments/"
- urlCommentEnable = "media/%d/enable_comments/"
- urlCommentLike = "media/%d/comment_like"
+ urlCommentDelete = "media/%s/comment/%s/delete/"
+ urlCommentSync = "media/%s/comments/"
+ urlCommentDisable = "media/%s/disable_comments/"
+ urlCommentEnable = "media/%s/enable_comments/"
+ urlCommentLike = "media/%s/comment_like/"
+ urlCommentUnlike = "media/%s/comment_unlike/"
// activity
urlActivityFollowing = "news/"
@@ -79,6 +104,7 @@ const (
// inbox
urlInbox = "direct_v2/inbox/"
+ urlInboxPending = "direct_v2/pending_inbox/"
urlInboxSend = "direct_v2/threads/broadcast/text/"
urlInboxSendLike = "direct_v2/threads/broadcast/like/"
urlReplyStory = "direct_v2/threads/broadcast/reel_share/"
@@ -90,4 +116,7 @@ const (
urlTagSync = "tags/%s/info/"
urlTagStories = "tags/%s/story/"
urlTagContent = "tags/%s/ranked_sections/"
+
+ // upload
+ urlUploadStory = "https://i.instagram.com/rupload_igphoto/103079408575885_0_-1340379573"
)
diff --git a/contacts.go b/contacts.go
new file mode 100644
index 00000000..30ea1003
--- /dev/null
+++ b/contacts.go
@@ -0,0 +1,104 @@
+package goinsta
+
+import (
+ "encoding/json"
+ "strconv"
+)
+
+type Contacts struct {
+ inst *Instagram
+}
+
+type Contact struct {
+ Numbers []string `json:"phone_numbers"`
+ Emails []string `json:"email_addresses"`
+ Name string `json:"first_name"`
+}
+
+type SyncAnswer struct {
+ Users []struct {
+ Pk int64 `json:"pk"`
+ Username string `json:"username"`
+ FullName string `json:"full_name"`
+ IsPrivate bool `json:"is_private"`
+ ProfilePicURL string `json:"profile_pic_url"`
+ ProfilePicID string `json:"profile_pic_id"`
+ IsVerified bool `json:"is_verified"`
+ HasAnonymousProfilePicture bool `json:"has_anonymous_profile_picture"`
+ ReelAutoArchive string `json:"reel_auto_archive"`
+ AddressbookName string `json:"addressbook_name"`
+ } `json:"users"`
+ Warning string `json:"warning"`
+ Status string `json:"status"`
+}
+
+func newContacts(inst *Instagram) *Contacts {
+ return &Contacts{inst: inst}
+}
+
+func (c *Contacts) SyncContacts(contacts *[]Contact) (*SyncAnswer, error) {
+ acquireContacts := &reqOptions{
+ Endpoint: "address_book/acquire_owner_contacts/",
+ IsPost: true,
+ Login: true,
+ UseV2: false,
+ Query: map[string]string{
+ "phone_id": c.inst.pid,
+ "me": `{"phone_numbers":[],"email_addresses":[]}`,
+ },
+ }
+ body, err := c.inst.sendRequest(acquireContacts)
+ if err != nil {
+ return nil, err
+ }
+
+ byteContacts, err := json.Marshal(contacts)
+ if err != nil {
+ return nil, err
+ }
+
+ syncContacts := &reqOptions{
+ Endpoint: `address_book/link/`,
+ IsPost: true,
+ Login: true,
+ UseV2: false,
+ Query: map[string]string{
+ "_uuid": c.inst.uuid,
+ "_csrftoken": c.inst.token,
+ "contacts": string(byteContacts),
+ },
+ }
+
+ body, err = c.inst.sendRequest(syncContacts)
+ if err != nil {
+ return nil, err
+ }
+
+ answ := &SyncAnswer{}
+ json.Unmarshal(body, answ)
+ return answ, nil
+}
+
+func (c *Contacts) UnlinkContacts() error {
+ toSign := map[string]string{
+ "_csrftoken": c.inst.token,
+ "_uid": strconv.Itoa(int(c.inst.Account.ID)),
+ "_uuid": c.inst.uuid,
+ }
+
+ bytesS, _ := json.Marshal(toSign)
+
+ unlinkBody := &reqOptions{
+ Endpoint: "address_book/unlink/",
+ IsPost: true,
+ Login: true,
+ UseV2: false,
+ Query: generateSignature(string(bytesS)),
+ }
+
+ _, err := c.inst.sendRequest(unlinkBody)
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/doc.go b/doc.go
index 3e618403..8524df92 100644
--- a/doc.go
+++ b/doc.go
@@ -1,13 +1,17 @@
-package goinsta // gopkg.in/ahmdrz/goinsta.v2
-
/*
- Goinsta is unofficial API of Instagram for Golang.
- This library provides private API methods.
+Package goinsta implements Goinsta private API in Golang.
+We changed the API for the comfort of the user.
+The new API is object-oriented making the programming bot task less repetitive.
- We changed the API for the comfort of the user.
- The new API is object-oriented making the programming bot task less repetitive.
+Probably you will find this API a bit confusing.
+This API is a little representation of Instagram engineers work.
+We are trying to humanize it. Do not care.
+If you have any question you can open an issue.
- Currently the API is very complete (we did it in a week xd).
- If you see that Goinsta needs any method or any new feature you are free to open
- an issue in github.com/ahmdrz/goinsta we will work to solve your issue as soon as possible. Also you can make your own contributions. See github.com/ahmdrz/goinsta/blob/master/CONTRIBUTION.md
+Currently the API is uncomplete (we did it in a week xd).
+If you see that Goinsta needs any method or any new feature you are free to open
+an issue in github.com/ahmdrz/goinsta we will work to solve your issue as soon
+as possible. Also you can make your own contributions.
+See https://github.com/ahmdrz/goinsta/blob/master/CONTRIBUTION.md
*/
+package goinsta
diff --git a/examples/account/changePass.go b/examples/account/changePass.go
index 1989f5c7..ed8164f7 100644
--- a/examples/account/changePass.go
+++ b/examples/account/changePass.go
@@ -6,7 +6,7 @@ import (
"fmt"
"os"
- "gopkg.in/ahmdrz/goinsta.v2"
+ "github.com/ahmdrz/goinsta"
"github.com/howeyc/gopass"
)
diff --git a/examples/account/followers.go b/examples/account/followers.go
index 9aa847e5..f9b5fbc4 100644
--- a/examples/account/followers.go
+++ b/examples/account/followers.go
@@ -5,7 +5,7 @@ package main
import (
"fmt"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
diff --git a/examples/account/following.go b/examples/account/following.go
index 50e5fd7c..31b321eb 100644
--- a/examples/account/following.go
+++ b/examples/account/following.go
@@ -5,7 +5,7 @@ package main
import (
"fmt"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
diff --git a/examples/account/liked.go b/examples/account/liked.go
new file mode 100644
index 00000000..f6ade8ae
--- /dev/null
+++ b/examples/account/liked.go
@@ -0,0 +1,27 @@
+// +build ignore
+
+package main
+
+import (
+ "fmt"
+
+ e "github.com/ahmdrz/goinsta/examples"
+)
+
+func main() {
+ inst, err := e.InitGoinsta("")
+ e.CheckErr(err)
+
+ media := inst.Account.Liked()
+
+ if media.Next() {
+ for _, item := range media.Items {
+ fmt.Printf("You liked the media %v of user: %s with total likes of %v\n", item.ID, item.User.Username, item.Likes)
+ }
+ }
+
+ if !e.UsingSession {
+ err = inst.Logout()
+ e.CheckErr(err)
+ }
+}
diff --git a/examples/account/removeProfilePic.go b/examples/account/removeProfilePic.go
index 1f1e0f89..2ed109f6 100644
--- a/examples/account/removeProfilePic.go
+++ b/examples/account/removeProfilePic.go
@@ -5,7 +5,7 @@ package main
import (
"fmt"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
diff --git a/examples/account/setPrivate.go b/examples/account/setPrivate.go
index 89813f94..5be88292 100644
--- a/examples/account/setPrivate.go
+++ b/examples/account/setPrivate.go
@@ -5,7 +5,7 @@ package main
import (
"fmt"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
diff --git a/examples/account/setPublic.go b/examples/account/setPublic.go
index 740665a2..1a783f41 100644
--- a/examples/account/setPublic.go
+++ b/examples/account/setPublic.go
@@ -5,7 +5,7 @@ package main
import (
"fmt"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
diff --git a/examples/account/setbiography.go b/examples/account/setbiography.go
new file mode 100644
index 00000000..5edd0498
--- /dev/null
+++ b/examples/account/setbiography.go
@@ -0,0 +1,31 @@
+// +build ignore
+
+package main
+
+import (
+ "fmt"
+ "os"
+ "strings"
+
+ e "github.com/ahmdrz/goinsta/examples"
+)
+
+func main() {
+ inst, err := e.InitGoinsta("")
+ e.CheckErr(err)
+
+ newBiography := strings.Join(os.Args, " ")
+
+ fmt.Printf("Your current biography: %s\n", inst.Account.Biography)
+ fmt.Printf("Setting biography to: %s\n", newBiography)
+
+ err = inst.Account.SetBiography(newBiography)
+ e.CheckErr(err)
+
+ fmt.Printf("Your current biography (after update): %s\n", inst.Account.Biography)
+
+ if !e.UsingSession {
+ err = inst.Logout()
+ e.CheckErr(err)
+ }
+}
diff --git a/examples/account/stories.go b/examples/account/stories.go
index aa72c688..2b9de4e1 100644
--- a/examples/account/stories.go
+++ b/examples/account/stories.go
@@ -5,7 +5,7 @@ package main
import (
"fmt"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
diff --git a/examples/activity/following.go b/examples/activity/following.go
index 7dc3e86f..bcf8d055 100644
--- a/examples/activity/following.go
+++ b/examples/activity/following.go
@@ -5,7 +5,7 @@ package main
import (
"fmt"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
diff --git a/examples/activity/recent.go b/examples/activity/recent.go
index 4b41c7dc..9d6ccdba 100644
--- a/examples/activity/recent.go
+++ b/examples/activity/recent.go
@@ -5,7 +5,7 @@ package main
import (
"fmt"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
diff --git a/examples/contacts/sync.go b/examples/contacts/sync.go
new file mode 100644
index 00000000..9a89a686
--- /dev/null
+++ b/examples/contacts/sync.go
@@ -0,0 +1,46 @@
+package main
+
+import (
+ "fmt"
+ "github.com/ahmdrz/goinsta"
+)
+
+func main() {
+ //insta, _ := goinsta.Import("session_dump")
+ insta := goinsta.New("lnsta_login", "insta_passwordd")
+
+ // also you can use New function from gopkg.in/ahmdrz/goinsta.v2/utils
+
+ // insta.SetProxy("http://localhost:8080", true) // true for insecure connections
+ if err := insta.Login(); err != nil {
+ fmt.Println(err)
+ return
+ }
+ // export your configuration
+ // after exporting you can use Import function instead of New function.
+ insta.Export("session_dump")
+
+ empty := make([]string, 0)
+ contacts := []goinsta.Contact{
+ {
+ Name: "To Search 1",
+ Numbers: []string{"+11111111111"},
+ Emails: empty,
+ },
+ {
+ Name: "To Search 2",
+ Numbers: []string{"+22222222222"},
+ Emails: empty,
+ },
+ {
+ Name: "To Search 3",
+ Numbers: empty,
+ Emails: []string{"test@mail.ex"},
+ },
+ }
+ answer, err := insta.Contacts.SyncContacts(&contacts)
+ if err != nil {
+ fmt.Println(err)
+ }
+ fmt.Println(answer)
+}
diff --git a/examples/export.go b/examples/export.go
index 90bfb383..1d106aa1 100644
--- a/examples/export.go
+++ b/examples/export.go
@@ -6,7 +6,7 @@ import (
"fmt"
"os"
- "gopkg.in/ahmdrz/goinsta.v2"
+ "github.com/ahmdrz/goinsta"
"github.com/howeyc/gopass"
)
diff --git a/examples/goinstaExamples.go b/examples/goinstaExamples.go
index 4ce06487..3f3d4f12 100644
--- a/examples/goinstaExamples.go
+++ b/examples/goinstaExamples.go
@@ -5,12 +5,14 @@ import (
"os"
"strings"
- "gopkg.in/ahmdrz/goinsta.v2"
+ "github.com/ahmdrz/goinsta"
"github.com/howeyc/gopass"
)
+// UsingSession is used inside InitGoinsta to control if there is already a session created.
var UsingSession bool
+// CheckErr is a generic function to validate all errors.
func CheckErr(err error) {
if err != nil {
fmt.Printf("error: %s\n", err)
@@ -32,7 +34,7 @@ func InitGoinsta(msg string) (*goinsta.Instagram, error) {
case nargs < min:
fmt.Printf("%s %s\n", os.Args[0], msg)
os.Exit(0)
- case nargs == min:
+ default:
user = os.Args[1]
}
@@ -50,13 +52,16 @@ func InitGoinsta(msg string) (*goinsta.Instagram, error) {
return nil, err
}
- inst = goinsta.New(os.Args[1], string(pass))
+ inst = goinsta.New(user, string(pass))
err = inst.Login()
if err != nil {
return inst, err
}
}
+ if min > 0 {
+ os.Args = os.Args[2:]
+ }
fmt.Printf("Hello %s!\n", inst.Account.Username)
return inst, nil
diff --git a/examples/inbox/conversation.go b/examples/inbox/conversation.go
index 5630497b..b54c5712 100644
--- a/examples/inbox/conversation.go
+++ b/examples/inbox/conversation.go
@@ -5,7 +5,7 @@ package main
import (
"fmt"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
diff --git a/examples/inbox/newconversation.go b/examples/inbox/newconversation.go
new file mode 100644
index 00000000..5605adb5
--- /dev/null
+++ b/examples/inbox/newconversation.go
@@ -0,0 +1,29 @@
+// +build ignore
+
+package main
+
+import (
+ "os"
+ "strings"
+
+ e "github.com/ahmdrz/goinsta/examples"
+)
+
+func main() {
+ inst, err := e.InitGoinsta(" ")
+ e.CheckErr(err)
+
+ user, err := inst.Profiles.ByName(os.Args[0])
+ e.CheckErr(err)
+
+ err = inst.Inbox.Sync()
+ e.CheckErr(err)
+
+ err = inst.Inbox.New(user, strings.Join(os.Args[1:], " "))
+ e.CheckErr(err)
+
+ if !e.UsingSession {
+ err = inst.Logout()
+ e.CheckErr(err)
+ }
+}
diff --git a/examples/inbox/next.go b/examples/inbox/next.go
new file mode 100644
index 00000000..63672825
--- /dev/null
+++ b/examples/inbox/next.go
@@ -0,0 +1,31 @@
+// +build ignore
+
+package main
+
+import (
+ "fmt"
+
+ e "github.com/ahmdrz/goinsta/examples"
+)
+
+func main() {
+ inst, err := e.InitGoinsta("")
+ e.CheckErr(err)
+
+ err = inst.Inbox.Sync()
+ e.CheckErr(err)
+
+ i := 1
+ fmt.Printf("Page %d has %d conversations\n", i, len(inst.Inbox.Conversations))
+
+ for inst.Inbox.Next() {
+ i++
+ fmt.Printf("Page %d has %d conversations\n", i, len(inst.Inbox.Conversations))
+ }
+ //inst.Inbox.Reset()
+
+ if !e.UsingSession {
+ err = inst.Logout()
+ e.CheckErr(err)
+ }
+}
diff --git a/examples/inbox/sms.go b/examples/inbox/sms.go
new file mode 100644
index 00000000..04ecaf7d
--- /dev/null
+++ b/examples/inbox/sms.go
@@ -0,0 +1,25 @@
+// +build ignore
+
+package main
+
+import (
+ e "github.com/ahmdrz/goinsta/examples"
+)
+
+func main() {
+ inst, err := e.InitGoinsta("")
+ e.CheckErr(err)
+
+ err = inst.Inbox.Sync()
+ e.CheckErr(err)
+
+ if len(inst.Inbox.Conversations) != 0 {
+ err = inst.Inbox.Conversations[0].Send("dfghj")
+ e.CheckErr(err)
+ }
+
+ if !e.UsingSession {
+ err = inst.Logout()
+ e.CheckErr(err)
+ }
+}
diff --git a/examples/inbox/sync.go b/examples/inbox/sync.go
index 88a0a90a..ba32e4b3 100644
--- a/examples/inbox/sync.go
+++ b/examples/inbox/sync.go
@@ -5,7 +5,7 @@ package main
import (
"fmt"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
@@ -15,7 +15,11 @@ func main() {
err = inst.Inbox.Sync()
e.CheckErr(err)
- fmt.Printf("You have %d opened conversations\n", len(inst.Inbox.Conversations))
+ i := len(inst.Inbox.Conversations)
+ for inst.Inbox.Next() {
+ i += len(inst.Inbox.Conversations)
+ }
+ fmt.Printf("You have %d opened conversations\n", i)
if !e.UsingSession {
err = inst.Logout()
diff --git a/examples/media/commentsAdd.go b/examples/media/commentsAdd.go
index 6d23306d..f844994f 100644
--- a/examples/media/commentsAdd.go
+++ b/examples/media/commentsAdd.go
@@ -7,16 +7,15 @@ import (
"os"
"time"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
inst, err := e.InitGoinsta("")
e.CheckErr(err)
- media := inst.AcquireFeed()
- media.SetID(os.Args[2])
- media.Sync()
+ media, err := inst.GetMedia(os.Args[0])
+ e.CheckErr(err)
fmt.Printf("Comments: %d\n", media.Items[0].CommentCount)
err = media.Items[0].Comments.Add("Awesome pic!")
@@ -32,17 +31,14 @@ func main() {
media.Sync()
fmt.Printf("After calling: Comments: %d\n", media.Items[0].CommentCount)
- /*
- tray, err := inst.Timeline.Stories()
- e.CheckErr(err)
+ tray, err := inst.Timeline.Stories()
+ e.CheckErr(err)
- story := tray.Stories[0]
- // commenting your first timeline story xddxdxd
- fmt.Printf("Sending reply to %s\n", story.Items[0].ID)
- err = story.Items[0].Comments.Add("xd")
- e.CheckErr(err)
- TODO Causes: Media ID is missing
- */
+ story := tray.Stories[1]
+ // commenting your first timeline story xddxdxd
+ fmt.Printf("Sending reply to %s %s\n", story.Items[0].Images.GetBest(), story.Items[0].MediaToString())
+ err = story.Items[0].Comments.Add("xasfdsaf")
+ e.CheckErr(err)
if !e.UsingSession {
err = inst.Logout()
diff --git a/examples/media/commentsDelByID.go b/examples/media/commentsDelByID.go
index 0caa9664..fc2b0a30 100644
--- a/examples/media/commentsDelByID.go
+++ b/examples/media/commentsDelByID.go
@@ -7,19 +7,18 @@ import (
"os"
"time"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
inst, err := e.InitGoinsta(" ")
e.CheckErr(err)
- media := inst.AcquireFeed()
- media.SetID(os.Args[2])
- media.Sync()
+ media, err := inst.GetMedia(os.Args[0])
+ e.CheckErr(err)
fmt.Printf("Comments: %d\n", media.Items[0].CommentCount)
- err = media.Items[0].Comments.DelByID(os.Args[3])
+ err = media.Items[0].Comments.DelByID(os.Args[1])
e.CheckErr(err)
fmt.Println("wait 5 seconds...")
diff --git a/examples/media/commentsDelMine.go b/examples/media/commentsDelMine.go
index 58741676..e66b5dea 100644
--- a/examples/media/commentsDelMine.go
+++ b/examples/media/commentsDelMine.go
@@ -7,16 +7,15 @@ import (
"os"
"time"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
inst, err := e.InitGoinsta("")
e.CheckErr(err)
- media := inst.AcquireFeed()
- media.SetID(os.Args[2])
- media.Sync()
+ media, err := inst.GetMedia(os.Args[0])
+ e.CheckErr(err)
fmt.Printf("Comments: %d\n", media.Items[0].CommentCount)
err = media.Items[0].Comments.DelMine(0)
diff --git a/examples/media/commentsDisable.go b/examples/media/commentsDisable.go
index ac03280e..43ee46f4 100644
--- a/examples/media/commentsDisable.go
+++ b/examples/media/commentsDisable.go
@@ -6,16 +6,15 @@ import (
"fmt"
"os"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
inst, err := e.InitGoinsta("")
e.CheckErr(err)
- media := inst.AcquireFeed()
- media.SetID(os.Args[2])
- media.Sync()
+ media, err := inst.GetMedia(os.Args[0])
+ e.CheckErr(err)
fmt.Printf("Comments disabled: %v\n", media.Items[0].CommentsDisabled)
err = media.Items[0].Comments.Disable()
diff --git a/examples/media/commentsEnable.go b/examples/media/commentsEnable.go
index d4abfa9d..a827df48 100644
--- a/examples/media/commentsEnable.go
+++ b/examples/media/commentsEnable.go
@@ -6,16 +6,15 @@ import (
"fmt"
"os"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
inst, err := e.InitGoinsta("")
e.CheckErr(err)
- media := inst.AcquireFeed()
- media.SetID(os.Args[2])
- media.Sync()
+ media, err := inst.GetMedia(os.Args[0])
+ e.CheckErr(err)
fmt.Printf("Comments disabled: %v\n", media.Items[0].CommentsDisabled)
err = media.Items[0].Comments.Enable()
diff --git a/examples/media/commentsSync.go b/examples/media/commentsSync.go
index b001b0d4..b469ea7b 100644
--- a/examples/media/commentsSync.go
+++ b/examples/media/commentsSync.go
@@ -6,16 +6,15 @@ import (
"fmt"
"os"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
inst, err := e.InitGoinsta("")
e.CheckErr(err)
- media := inst.AcquireFeed()
- media.SetID(os.Args[2])
- media.Sync()
+ media, err := inst.GetMedia(os.Args[0])
+ e.CheckErr(err)
fmt.Printf("Comments %d:\n", media.Items[0].CommentCount)
// getting items (images or videos)
diff --git a/examples/media/deleteStories.go b/examples/media/deleteStories.go
new file mode 100644
index 00000000..4eef0bc0
--- /dev/null
+++ b/examples/media/deleteStories.go
@@ -0,0 +1,28 @@
+// +build ignore
+
+package main
+
+import (
+ e "github.com/ahmdrz/goinsta/examples"
+)
+
+func main() {
+ inst, err := e.InitGoinsta("")
+ e.CheckErr(err)
+
+ stories := inst.Account.Stories()
+
+ // you can download item per item or
+ // using stories.Delete()
+ for stories.Next() {
+ for _, item := range stories.Items {
+ err = item.Delete()
+ e.CheckErr(err)
+ }
+ }
+
+ if !e.UsingSession {
+ err = inst.Logout()
+ e.CheckErr(err)
+ }
+}
diff --git a/examples/media/hashtagMedia.go b/examples/media/hashtagMedia.go
index eaf2552d..fd660560 100644
--- a/examples/media/hashtagMedia.go
+++ b/examples/media/hashtagMedia.go
@@ -5,7 +5,7 @@ package main
import (
"fmt"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
diff --git a/examples/media/hashtagStories.go b/examples/media/hashtagStories.go
index 1a3af397..f605c06b 100644
--- a/examples/media/hashtagStories.go
+++ b/examples/media/hashtagStories.go
@@ -5,7 +5,7 @@ package main
import (
"fmt"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
diff --git a/examples/media/hashtags.go b/examples/media/hashtags.go
index 6b1451d0..967c5829 100644
--- a/examples/media/hashtags.go
+++ b/examples/media/hashtags.go
@@ -6,16 +6,15 @@ import (
"fmt"
"os"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
inst, err := e.InitGoinsta("")
e.CheckErr(err)
- media := inst.AcquireFeed()
- media.SetID(os.Args[2])
- media.Sync()
+ media, err := inst.GetMedia(os.Args[0])
+ e.CheckErr(err)
fmt.Printf("Hashtags:\n")
for _, h := range media.Items[0].Hashtags() {
diff --git a/examples/media/itemDownload.go b/examples/media/itemDownload.go
index 182c01ec..2cbe5dd8 100644
--- a/examples/media/itemDownload.go
+++ b/examples/media/itemDownload.go
@@ -6,24 +6,24 @@ import (
"fmt"
"os"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
inst, err := e.InitGoinsta("")
e.CheckErr(err)
- user, err := inst.Profiles.ByName(os.Args[2])
+ user, err := inst.Profiles.ByName(os.Args[0])
e.CheckErr(err)
- // You can downlaod Stories or Feed images.
- // media := user.Feed(nil)
+ // You can download Stories or Feed images.
+ // media := user.Feed()
media := user.Stories()
e.CheckErr(err)
for media.Next() {
for _, item := range media.Items {
- err = item.Download("./files/", "")
+ _, _, err = item.Download("./files/", "")
if err != nil {
fmt.Println(err)
return
diff --git a/examples/media/like.go b/examples/media/like.go
index 79960ae7..457d4b7c 100644
--- a/examples/media/like.go
+++ b/examples/media/like.go
@@ -6,16 +6,15 @@ import (
"fmt"
"os"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
inst, err := e.InitGoinsta("")
e.CheckErr(err)
- media := inst.AcquireFeed()
- media.SetID(os.Args[2])
- media.Sync()
+ media, err := inst.GetMedia(os.Args[0])
+ e.CheckErr(err)
fmt.Printf("Liked: %v\n", media.Items[0].HasLiked)
media.Items[0].Like()
diff --git a/examples/media/likeAll.go b/examples/media/likeAll.go
new file mode 100644
index 00000000..137ff5af
--- /dev/null
+++ b/examples/media/likeAll.go
@@ -0,0 +1,23 @@
+// +build ignore
+
+package main
+
+import (
+ e "github.com/ahmdrz/goinsta/examples"
+)
+
+func main() {
+ inst, err := e.InitGoinsta("")
+ e.CheckErr(err)
+
+ for inst.Inbox.Next() {
+ for _, c := range inst.Inbox.Conversations {
+ c.Like()
+ }
+ }
+
+ if !e.UsingSession {
+ err = inst.Logout()
+ e.CheckErr(err)
+ }
+}
diff --git a/examples/media/mediaDelete.go b/examples/media/mediaDelete.go
index beab6156..5ff314ae 100644
--- a/examples/media/mediaDelete.go
+++ b/examples/media/mediaDelete.go
@@ -6,18 +6,17 @@ import (
"fmt"
"os"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
inst, err := e.InitGoinsta("")
e.CheckErr(err)
- media := inst.AcquireFeed()
- media.SetID(os.Args[2])
- media.Sync()
+ media, err := inst.GetMedia(os.Args[0])
+ e.CheckErr(err)
- fmt.Println("Deleting", os.Args[2])
+ fmt.Println("Deleting", os.Args[0])
err = media.Items[0].Delete()
e.CheckErr(err)
diff --git a/examples/media/unlike.go b/examples/media/unlike.go
index 40998750..2d4d8b16 100644
--- a/examples/media/unlike.go
+++ b/examples/media/unlike.go
@@ -6,16 +6,15 @@ import (
"fmt"
"os"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
inst, err := e.InitGoinsta("")
e.CheckErr(err)
- media := inst.AcquireFeed()
- media.SetID(os.Args[2])
- media.Sync()
+ media, err := inst.GetMedia(os.Args[0])
+ e.CheckErr(err)
fmt.Printf("Liked: %v\n", media.Items[0].HasLiked)
media.Items[0].Unlike()
diff --git a/examples/search/searchFacebook.go b/examples/search/searchFacebook.go
index 0d4f01b7..1b944fdb 100644
--- a/examples/search/searchFacebook.go
+++ b/examples/search/searchFacebook.go
@@ -6,14 +6,14 @@ import (
"fmt"
"os"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
inst, err := e.InitGoinsta("")
e.CheckErr(err)
- res, err := inst.Search.Facebook(os.Args[2])
+ res, err := inst.Search.Facebook(os.Args[0])
e.CheckErr(err)
for _, user := range res.Users {
diff --git a/examples/search/searchFeedTags.go b/examples/search/searchFeedTags.go
deleted file mode 100644
index d18d0f65..00000000
--- a/examples/search/searchFeedTags.go
+++ /dev/null
@@ -1,28 +0,0 @@
-// +build ignore
-
-package main
-
-import (
- "fmt"
- "os"
-
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
-)
-
-func main() {
- inst, err := e.InitGoinsta("")
- e.CheckErr(err)
-
- fmt.Printf("Hello %s!\n", inst.Account.Username)
-
- // I don't want to make an example of this. Not today.
- tags, err := inst.Search.FeedTags(os.Args[2])
- e.CheckErr(err)
-
- // TODO
-
- if !e.UsingSession {
- err = inst.Logout()
- e.CheckErr(err)
- }
-}
diff --git a/examples/search/searchLocation.go b/examples/search/searchLocation.go
index a638ed4c..d60e550c 100644
--- a/examples/search/searchLocation.go
+++ b/examples/search/searchLocation.go
@@ -6,7 +6,7 @@ import (
"fmt"
"os"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
@@ -14,7 +14,7 @@ func main() {
e.CheckErr(err)
res, err := inst.Search.Location(
- os.Args[2], os.Args[3], os.Args[4],
+ os.Args[0], os.Args[1], os.Args[2],
)
e.CheckErr(err)
diff --git a/examples/search/searchTag.go b/examples/search/searchTag.go
index 5ff64ee6..85fb47b3 100644
--- a/examples/search/searchTag.go
+++ b/examples/search/searchTag.go
@@ -6,14 +6,14 @@ import (
"fmt"
"os"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
inst, err := e.InitGoinsta("")
e.CheckErr(err)
- res, err := inst.Search.Tags(os.Args[2])
+ res, err := inst.Search.Tags(os.Args[0])
e.CheckErr(err)
for _, tag := range res.Tags {
diff --git a/examples/search/searchUser.go b/examples/search/searchUser.go
index 7593c242..be5c9285 100644
--- a/examples/search/searchUser.go
+++ b/examples/search/searchUser.go
@@ -6,14 +6,14 @@ import (
"fmt"
"os"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
inst, err := e.InitGoinsta("")
e.CheckErr(err)
- res, err := inst.Search.User(os.Args[2])
+ res, err := inst.Search.User(os.Args[0])
e.CheckErr(err)
for _, user := range res.Users {
diff --git a/examples/timeline/stories.go b/examples/timeline/stories.go
index 6bfb1038..4fd59207 100644
--- a/examples/timeline/stories.go
+++ b/examples/timeline/stories.go
@@ -5,7 +5,7 @@ package main
import (
"fmt"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
diff --git a/examples/timeline/timeline.go b/examples/timeline/timeline.go
index 39d5ddbe..39958eee 100644
--- a/examples/timeline/timeline.go
+++ b/examples/timeline/timeline.go
@@ -5,7 +5,7 @@ package main
import (
"fmt"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
diff --git a/examples/user/block.go b/examples/user/block.go
index d61b4bc4..dd491d3a 100644
--- a/examples/user/block.go
+++ b/examples/user/block.go
@@ -5,14 +5,14 @@ package main
import (
"os"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
inst, err := e.InitGoinsta("")
e.CheckErr(err)
- user, err := inst.Profiles.ByName(os.Args[2])
+ user, err := inst.Profiles.ByName(os.Args[0])
e.CheckErr(err)
err = user.Block()
diff --git a/examples/user/feed.go b/examples/user/feed.go
index 8dc846bc..f0d202e5 100644
--- a/examples/user/feed.go
+++ b/examples/user/feed.go
@@ -6,17 +6,17 @@ import (
"fmt"
"os"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
inst, err := e.InitGoinsta("")
e.CheckErr(err)
- user, err := inst.Profiles.ByName(os.Args[2])
+ user, err := inst.Profiles.ByName(os.Args[0])
e.CheckErr(err)
- media := user.Feed(nil)
+ media := user.Feed()
for media.Next() {
fmt.Printf("Printing %d items\n", len(media.Items))
diff --git a/examples/user/feedlikers.go b/examples/user/feedlikers.go
new file mode 100644
index 00000000..701fa6f3
--- /dev/null
+++ b/examples/user/feedlikers.go
@@ -0,0 +1,35 @@
+// +build ignore
+
+package main
+
+import (
+ "fmt"
+ "os"
+
+ e "github.com/ahmdrz/goinsta/examples"
+)
+
+func main() {
+ inst, err := e.InitGoinsta("")
+ e.CheckErr(err)
+
+ user, err := inst.Profiles.ByName(os.Args[0])
+ e.CheckErr(err)
+
+ media := user.Feed()
+
+ for media.Next() {
+ fmt.Printf("Printing %d items\n", len(media.Items))
+ for _, item := range media.Items {
+ for _, liker := range item.Likers {
+ fmt.Printf("%s with username: %s Likes this \n", liker.FullName, liker.Username)
+ }
+ }
+ }
+ fmt.Println(media.Error())
+
+ if !e.UsingSession {
+ err = inst.Logout()
+ e.CheckErr(err)
+ }
+}
diff --git a/examples/user/follow.go b/examples/user/follow.go
index 0718f82e..37db502f 100644
--- a/examples/user/follow.go
+++ b/examples/user/follow.go
@@ -6,14 +6,14 @@ import (
"fmt"
"os"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
inst, err := e.InitGoinsta("")
e.CheckErr(err)
- user, err := inst.Profiles.ByName(os.Args[2])
+ user, err := inst.Profiles.ByName(os.Args[0])
e.CheckErr(err)
fmt.Printf("Following: %v\n", user.Friendship.Following)
diff --git a/examples/user/followers.go b/examples/user/followers.go
index 3247a9a1..6f4daff9 100644
--- a/examples/user/followers.go
+++ b/examples/user/followers.go
@@ -6,14 +6,14 @@ import (
"fmt"
"os"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
inst, err := e.InitGoinsta("")
e.CheckErr(err)
- user, err := inst.Profiles.ByName(os.Args[2])
+ user, err := inst.Profiles.ByName(os.Args[0])
e.CheckErr(err)
users := user.Followers()
diff --git a/examples/user/following.go b/examples/user/following.go
index 198790ad..db2bcfd2 100644
--- a/examples/user/following.go
+++ b/examples/user/following.go
@@ -6,14 +6,14 @@ import (
"fmt"
"os"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
inst, err := e.InitGoinsta("")
e.CheckErr(err)
- user, err := inst.Profiles.ByName(os.Args[2])
+ user, err := inst.Profiles.ByName(os.Args[0])
e.CheckErr(err)
users := user.Following()
diff --git a/examples/user/friendship.go b/examples/user/friendship.go
new file mode 100644
index 00000000..41de5ea9
--- /dev/null
+++ b/examples/user/friendship.go
@@ -0,0 +1,31 @@
+// +build ignore
+
+package main
+
+import (
+ "fmt"
+ "os"
+
+ e "github.com/ahmdrz/goinsta/examples"
+)
+
+func main() {
+ inst, err := e.InitGoinsta("")
+ e.CheckErr(err)
+
+ user, err := inst.Profiles.ByName(os.Args[0])
+ e.CheckErr(err)
+
+ // At this context you can use:
+ // user.FriendShip()
+ // user.Sync(true)
+ err = user.Sync(true)
+ e.CheckErr(err)
+
+ fmt.Println("Following:", user.Friendship.Following)
+
+ if !e.UsingSession {
+ err = inst.Logout()
+ e.CheckErr(err)
+ }
+}
diff --git a/examples/user/highlights.go b/examples/user/highlights.go
new file mode 100644
index 00000000..2b2dcf55
--- /dev/null
+++ b/examples/user/highlights.go
@@ -0,0 +1,40 @@
+// +build ignore
+
+package main
+
+import (
+ "fmt"
+ "os"
+
+ e "github.com/ahmdrz/goinsta/examples"
+)
+
+func main() {
+ inst, err := e.InitGoinsta("")
+ e.CheckErr(err)
+
+ user, err := inst.Profiles.ByName(os.Args[0])
+ e.CheckErr(err)
+ user.Sync()
+
+ hlts, err := user.Highlights()
+ e.CheckErr(err)
+
+ for _, h := range hlts {
+ // getting images URL
+ for _, item := range h.Items {
+ if len(item.Images.Versions) > 0 {
+ fmt.Printf(" Image - %s\n", item.Images.Versions[0].URL)
+ }
+ if len(item.Videos) > 0 {
+ fmt.Printf(" Video - %s\n", item.Videos[0].URL)
+ }
+ }
+
+ }
+
+ if !e.UsingSession {
+ err = inst.Logout()
+ e.CheckErr(err)
+ }
+}
diff --git a/examples/user/seeunfollowers.go b/examples/user/seeunfollowers.go
new file mode 100644
index 00000000..2237f49d
--- /dev/null
+++ b/examples/user/seeunfollowers.go
@@ -0,0 +1,70 @@
+// +build ignore
+
+package main
+
+import (
+ "fmt"
+ "os"
+ "strings"
+
+ e "github.com/ahmdrz/goinsta/examples"
+)
+
+func main() {
+ var followers []string
+ var followings []string
+ inst, err := e.InitGoinsta("")
+ e.CheckErr(err)
+
+ user, err := inst.Profiles.ByName(os.Args[0])
+ e.CheckErr(err)
+
+ users_following := user.Following()
+ e.CheckErr(err)
+
+ users_follower := user.Followers()
+ e.CheckErr(err)
+
+ // collect usernames from Instagram following section
+ following_counter := 1
+ for users_following.Next() {
+ for _, user := range users_following.Users {
+ following_counter++
+ followings = append(followings, user.Username)
+ }
+ }
+
+ // collect usernames from Instagram followers section
+ follower_counter := 1
+ for users_follower.Next() {
+ for _, user := range users_follower.Users {
+ follower_counter++
+ followers = append(followers, user.Username)
+ }
+ }
+
+ fmt.Println("\nTOTAL FOLLOWING:", following_counter)
+ fmt.Println("TOTAL FOLLOWER:", follower_counter)
+ fmt.Println("TOTAL DOESN'T FOLLOW YOU BACK:", len(differ(followings, followers)))
+ fmt.Println("WHO DOESN'T FOLLOW YOU BACK:\n", strings.Trim(fmt.Sprint(differ(followings, followers)), "[]"))
+
+ if !e.UsingSession {
+ err = inst.Logout()
+ e.CheckErr(err)
+ }
+}
+
+func differ(a, b []string) (diff []string) {
+ m := make(map[string]bool)
+
+ for _, item := range b {
+ m[item] = true
+ }
+
+ for _, item := range a {
+ if _, ok := m[item]; !ok {
+ diff = append(diff, item)
+ }
+ }
+ return
+}
diff --git a/examples/user/stories.go b/examples/user/stories.go
index 7c6ac2dd..e8c72c22 100644
--- a/examples/user/stories.go
+++ b/examples/user/stories.go
@@ -6,14 +6,14 @@ import (
"fmt"
"os"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
inst, err := e.InitGoinsta("")
e.CheckErr(err)
- user, err := inst.Profiles.ByName(os.Args[2])
+ user, err := inst.Profiles.ByName(os.Args[0])
e.CheckErr(err)
stories := user.Stories()
diff --git a/examples/user/tags.go b/examples/user/tags.go
index bba93677..db819c55 100644
--- a/examples/user/tags.go
+++ b/examples/user/tags.go
@@ -6,14 +6,14 @@ import (
"fmt"
"os"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
inst, err := e.InitGoinsta("")
e.CheckErr(err)
- user, err := inst.Profiles.ByName(os.Args[2])
+ user, err := inst.Profiles.ByName(os.Args[0])
e.CheckErr(err)
media, err := user.Tags(nil)
diff --git a/examples/user/unblock.go b/examples/user/unblock.go
index 92d4e668..4380e38d 100644
--- a/examples/user/unblock.go
+++ b/examples/user/unblock.go
@@ -5,7 +5,7 @@ package main
import (
"os"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
@@ -13,7 +13,7 @@ func main() {
e.CheckErr(err)
// if you have someone blocked probably you cannot found it with this method
- user, err := inst.Profiles.ByName(os.Args[2])
+ user, err := inst.Profiles.ByName(os.Args[0])
e.CheckErr(err)
err = user.Unblock()
diff --git a/examples/user/unfollow.go b/examples/user/unfollow.go
index 4b5ae484..89b47ee7 100644
--- a/examples/user/unfollow.go
+++ b/examples/user/unfollow.go
@@ -6,14 +6,14 @@ import (
"fmt"
"os"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
inst, err := e.InitGoinsta("")
e.CheckErr(err)
- user, err := inst.Profiles.ByName(os.Args[2])
+ user, err := inst.Profiles.ByName(os.Args[0])
e.CheckErr(err)
fmt.Printf("Unfollowing: %v\n", user.Friendship.Following)
diff --git a/examples/user/unfolloweveryone.go b/examples/user/unfolloweveryone.go
new file mode 100644
index 00000000..2ad9c48d
--- /dev/null
+++ b/examples/user/unfolloweveryone.go
@@ -0,0 +1,38 @@
+// +build ignore
+
+package main
+
+import (
+ "os"
+ e "github.com/ahmdrz/goinsta/examples"
+ "fmt"
+)
+
+func main() {
+ inst, err := e.InitGoinsta("")
+ e.CheckErr(err)
+
+ user, err := inst.Profiles.ByName(os.Args[0])
+ e.CheckErr(err)
+
+ users_following := user.Following()
+ e.CheckErr(err)
+
+ //check if there are users
+ if (len(users_following.Users) > 0) {
+ for users_following.Next() {
+ for _, user := range users_following.Users {
+ err = user.Unfollow()
+ e.CheckErr(err)
+ fmt.Printf("username %s unfollowed\n", user.Username)
+ }
+ }
+ } else {
+ fmt.Printf("following list is empty\n")
+ }
+
+ if !e.UsingSession {
+ err = inst.Logout()
+ e.CheckErr(err)
+ }
+}
diff --git a/examples/user/userById.go b/examples/user/userById.go
index 98105fbf..cded960b 100644
--- a/examples/user/userById.go
+++ b/examples/user/userById.go
@@ -7,14 +7,14 @@ import (
"os"
"strconv"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
inst, err := e.InitGoinsta("")
e.CheckErr(err)
- id, err := strconv.Atoi(os.Args[2])
+ id, err := strconv.Atoi(os.Args[0])
e.CheckErr(err)
user, err := inst.Profiles.ByID(int64(id))
diff --git a/examples/user/userByName.go b/examples/user/userByName.go
index be05a86a..0ec8c1d0 100644
--- a/examples/user/userByName.go
+++ b/examples/user/userByName.go
@@ -6,14 +6,14 @@ import (
"fmt"
"os"
- e "gopkg.in/ahmdrz/goinsta.v2/examples"
+ e "github.com/ahmdrz/goinsta/examples"
)
func main() {
inst, err := e.InitGoinsta("")
e.CheckErr(err)
- user, err := inst.Profiles.ByName(os.Args[2])
+ user, err := inst.Profiles.ByName(os.Args[0])
e.CheckErr(err)
fmt.Printf("Target username is %s with the id: %d\n", user.Username, user.ID)
diff --git a/feeds.go b/feeds.go
new file mode 100644
index 00000000..371afaad
--- /dev/null
+++ b/feeds.go
@@ -0,0 +1,86 @@
+package goinsta
+
+import (
+ "encoding/json"
+ "fmt"
+)
+
+// Feed is the object for all feed endpoints.
+type Feed struct {
+ inst *Instagram
+}
+
+// newFeed creates new Feed structure
+func newFeed(inst *Instagram) *Feed {
+ return &Feed{
+ inst: inst,
+ }
+}
+
+// Feed search by locationID
+func (feed *Feed) LocationID(locationID int64) (*FeedLocation, error) {
+ insta := feed.inst
+ body, err := insta.sendRequest(
+ &reqOptions{
+ Endpoint: fmt.Sprintf(urlFeedLocationID, locationID),
+ Query: map[string]string{
+ "rank_token": insta.rankToken,
+ "ranked_content": "true",
+ },
+ },
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ res := &FeedLocation{}
+ err = json.Unmarshal(body, res)
+ return res, err
+}
+
+// FeedLocation is the struct that fits the structure returned by instagram on LocationID search.
+type FeedLocation struct {
+ RankedItems []Item `json:"ranked_items"`
+ Items []Item `json:"items"`
+ NumResults int `json:"num_results"`
+ NextID string `json:"next_max_id"`
+ MoreAvailable bool `json:"more_available"`
+ AutoLoadMoreEnabled bool `json:"auto_load_more_enabled"`
+ MediaCount int `json:"media_count"`
+ Location Location `json:"location"`
+ Status string `json:"status"`
+}
+
+// Tags search by Tag in user Feed
+//
+// (sorry for returning FeedTag. See #FeedTag)
+func (feed *Feed) Tags(tag string) (*FeedTag, error) {
+ insta := feed.inst
+ body, err := insta.sendRequest(
+ &reqOptions{
+ Endpoint: fmt.Sprintf(urlFeedTag, tag),
+ Query: map[string]string{
+ "rank_token": insta.rankToken,
+ "ranked_content": "true",
+ },
+ },
+ )
+ if err != nil {
+ return nil, err
+ }
+ res := &FeedTag{}
+ err = json.Unmarshal(body, res)
+ return res, err
+}
+
+// FeedTag is the struct that fits the structure returned by instagram on TagSearch.
+type FeedTag struct {
+ RankedItems []Item `json:"ranked_items"`
+ Images []Item `json:"items"`
+ NumResults int `json:"num_results"`
+ NextID string `json:"next_max_id"`
+ MoreAvailable bool `json:"more_available"`
+ AutoLoadMoreEnabled bool `json:"auto_load_more_enabled"`
+ Story StoryMedia `json:"story"`
+ Status string `json:"status"`
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 00000000..19b47934
--- /dev/null
+++ b/go.mod
@@ -0,0 +1 @@
+module github.com/ahmdrz/goinsta/v2
diff --git a/goinsta.go b/goinsta.go
index 0d067e0d..d4e89a5a 100644
--- a/goinsta.go
+++ b/goinsta.go
@@ -1,12 +1,15 @@
package goinsta
import (
+ "crypto/tls"
"encoding/json"
- "fmt"
+ "io"
"io/ioutil"
"net/http"
"net/http/cookiejar"
neturl "net/url"
+ "os"
+ "path/filepath"
"strconv"
"time"
)
@@ -29,9 +32,9 @@ import (
type Instagram struct {
user string
pass string
- // device id
+ // device id: android-1923fjnma8123
dID string
- // uuid
+ // uuid: 8493-1233-4312312-5123
uuid string
// rankToken
rankToken string
@@ -39,6 +42,8 @@ type Instagram struct {
token string
// phone id
pid string
+ // ads id
+ adid string
// Instagram objects
@@ -54,27 +59,33 @@ type Instagram struct {
Activity *Activity
// Inbox are instagram message/chat system.
Inbox *Inbox
+ // Feed for search over feeds
+ Feed *Feed
+ // User contacts from mobile address book
+ Contacts *Contacts
c *http.Client
}
// SetDeviceID sets device id
-func (i *Instagram) SetDeviceID(id string) {
- i.dID = id
+func (inst *Instagram) SetDeviceID(id string) {
+ inst.dID = id
}
// SetUUID sets uuid
-func (i *Instagram) SetUUID(uuid string) {
- i.uuid = uuid
+func (inst *Instagram) SetUUID(uuid string) {
+ inst.uuid = uuid
}
// SetPhoneID sets phone id
-func (i *Instagram) SetPhoneID(id string) {
- i.pid = id
+func (inst *Instagram) SetPhoneID(id string) {
+ inst.pid = id
}
// New creates Instagram structure
func New(username, password string) *Instagram {
+ // this call never returns error
+ jar, _ := cookiejar.New(nil)
inst := &Instagram{
user: username,
pass: password,
@@ -87,6 +98,7 @@ func New(username, password string) *Instagram {
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
},
+ Jar: jar,
},
}
inst.init()
@@ -100,14 +112,19 @@ func (inst *Instagram) init() {
inst.Timeline = newTimeline(inst)
inst.Search = newSearch(inst)
inst.Inbox = newInbox(inst)
+ inst.Feed = newFeed(inst)
+ inst.Contacts = newContacts(inst)
}
// SetProxy sets proxy for connection.
-func (inst *Instagram) SetProxy(url string) error {
+func (inst *Instagram) SetProxy(url string, insecure bool) error {
uri, err := neturl.Parse(url)
if err == nil {
inst.c.Transport = &http.Transport{
Proxy: http.ProxyURL(uri),
+ TLSClientConfig: &tls.Config{
+ InsecureSkipVerify: insecure,
+ },
}
}
return err
@@ -118,6 +135,15 @@ func (inst *Instagram) UnsetProxy() {
inst.c.Transport = nil
}
+// Save exports config to ~/.goinsta
+func (inst *Instagram) Save() error {
+ home := os.Getenv("HOME")
+ if home == "" {
+ home = os.Getenv("home") // for plan9
+ }
+ return inst.Export(filepath.Join(home, ".goinsta"))
+}
+
// Export exports *Instagram object options
func (inst *Instagram) Export(path string) error {
url, err := neturl.Parse(goInstaAPIUrl)
@@ -126,6 +152,7 @@ func (inst *Instagram) Export(path string) error {
}
config := ConfigFile{
+ ID: inst.Account.ID,
User: inst.user,
DeviceID: inst.dID,
UUID: inst.uuid,
@@ -142,22 +169,46 @@ func (inst *Instagram) Export(path string) error {
return ioutil.WriteFile(path, bytes, 0644)
}
-// Import imports instagram configuration
+// Export exports selected *Instagram object options to an io.Writer
+func Export(inst *Instagram, writer io.Writer) error {
+ url, err := neturl.Parse(goInstaAPIUrl)
+ if err != nil {
+ return err
+ }
+
+ config := ConfigFile{
+ ID: inst.Account.ID,
+ User: inst.user,
+ DeviceID: inst.dID,
+ UUID: inst.uuid,
+ RankToken: inst.rankToken,
+ Token: inst.token,
+ PhoneID: inst.pid,
+ Cookies: inst.c.Jar.Cookies(url),
+ }
+ bytes, err := json.Marshal(config)
+ if err != nil {
+ return err
+ }
+ _, err = writer.Write(bytes)
+ return err
+}
+
+// ImportReader imports instagram configuration from io.Reader
//
// This function does not set proxy automatically. Use SetProxy after this call.
-func Import(path string) (*Instagram, error) {
+func ImportReader(r io.Reader) (*Instagram, error) {
url, err := neturl.Parse(goInstaAPIUrl)
if err != nil {
return nil, err
}
- bytes, err := ioutil.ReadFile(path)
+ bytes, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
config := ConfigFile{}
-
err = json.Unmarshal(bytes, &config)
if err != nil {
return nil, err
@@ -182,33 +233,130 @@ func Import(path string) (*Instagram, error) {
inst.c.Jar.SetCookies(url, config.Cookies)
inst.init()
- inst.Account = &Account{inst: inst}
+ inst.Account = &Account{inst: inst, ID: config.ID}
inst.Account.Sync()
return inst, nil
}
-// Login performs instagram login.
+// Import imports instagram configuration
//
-// Password will be deleted after login
-func (inst *Instagram) Login() error {
- jar, err := cookiejar.New(nil)
+// This function does not set proxy automatically. Use SetProxy after this call.
+func Import(path string) (*Instagram, error) {
+ f, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ return ImportReader(f)
+}
+
+func (inst *Instagram) readMsisdnHeader() error {
+ data, err := json.Marshal(
+ map[string]string{
+ "device_id": inst.uuid,
+ },
+ )
if err != nil {
return err
}
- inst.c.Jar = jar
+ _, err = inst.sendRequest(
+ &reqOptions{
+ Endpoint: urlMsisdnHeader,
+ IsPost: true,
+ Connection: "keep-alive",
+ Query: generateSignature(b2s(data)),
+ },
+ )
+ return err
+}
- body, err := inst.sendRequest(
+func (inst *Instagram) contactPrefill() error {
+ data, err := json.Marshal(
+ map[string]string{
+ "phone_id": inst.pid,
+ "_csrftoken": inst.token,
+ "usage": "prefill",
+ },
+ )
+ if err != nil {
+ return err
+ }
+ _, err = inst.sendRequest(
+ &reqOptions{
+ Endpoint: urlContactPrefill,
+ IsPost: true,
+ Connection: "keep-alive",
+ Query: generateSignature(b2s(data)),
+ },
+ )
+ return err
+}
+
+func (inst *Instagram) zrToken() error {
+ _, err := inst.sendRequest(
&reqOptions{
- Endpoint: urlFetchHeaders,
+ Endpoint: urlZrToken,
+ IsPost: false,
+ Connection: "keep-alive",
Query: map[string]string{
- "challenge_type": "signup",
- "guid": inst.uuid,
+ "device_id": inst.dID,
+ "token_hash": "",
+ "custom_device_id": inst.uuid,
+ "fetch_reason": "token_expired",
},
},
)
+ return err
+}
+
+func (inst *Instagram) sendAdID() error {
+ data, err := inst.prepareData(
+ map[string]interface{}{
+ "adid": inst.adid,
+ },
+ )
+ if err != nil {
+ return err
+ }
+ _, err = inst.sendRequest(
+ &reqOptions{
+ Endpoint: urlLogAttribution,
+ IsPost: true,
+ Connection: "keep-alive",
+ Query: generateSignature(data),
+ },
+ )
+ return err
+}
+
+// Login performs instagram login.
+//
+// Password will be deleted after login
+func (inst *Instagram) Login() error {
+ err := inst.readMsisdnHeader()
+ if err != nil {
+ return err
+ }
+
+ err = inst.syncFeatures()
+ if err != nil {
+ return err
+ }
+
+ err = inst.zrToken()
+ if err != nil {
+ return err
+ }
+
+ err = inst.sendAdID()
+ if err != nil {
+ return err
+ }
+
+ err = inst.contactPrefill()
if err != nil {
- return fmt.Errorf("login failed for %s: %s", inst.user, err.Error())
+ return err
}
result, err := json.Marshal(
@@ -217,46 +365,41 @@ func (inst *Instagram) Login() error {
"login_attempt_count": 0,
"_csrftoken": inst.token,
"device_id": inst.dID,
+ "adid": inst.adid,
"phone_id": inst.pid,
"username": inst.user,
"password": inst.pass,
+ "google_tokens": "[]",
},
)
- if err == nil {
- body, err = inst.sendRequest(
- &reqOptions{
- Endpoint: urlLogin,
- Query: generateSignature(b2s(result)),
- IsPost: true,
- },
- )
- if err != nil {
- goto end
- }
- inst.pass = ""
-
- // getting account data
- res := accountResp{}
-
- err = json.Unmarshal(body, &res)
- if err != nil {
- ierr := instaError{}
- err = json.Unmarshal(body, &ierr)
- if err != nil {
- err = instaToErr(ierr)
- }
- goto end
- }
- inst.Account = &res.Account
- inst.Account.inst = inst
-
- inst.rankToken = strconv.FormatInt(inst.Account.ID, 10) + "_" + inst.uuid
+ if err != nil {
+ return err
+ }
+ body, err := inst.sendRequest(
+ &reqOptions{
+ Endpoint: urlLogin,
+ Query: generateSignature(b2s(result)),
+ IsPost: true,
+ Login: true,
+ },
+ )
+ if err != nil {
+ return err
+ }
+ inst.pass = ""
- inst.syncFeatures()
- inst.megaphoneLog()
+ // getting account data
+ res := accountResp{}
+ err = json.Unmarshal(body, &res)
+ if err != nil {
+ return err
}
-end:
+ inst.Account = &res.Account
+ inst.Account.inst = inst
+ inst.rankToken = strconv.FormatInt(inst.Account.ID, 10) + "_" + inst.uuid
+ inst.zrToken()
+
return err
}
@@ -271,7 +414,7 @@ func (inst *Instagram) Logout() error {
func (inst *Instagram) syncFeatures() error {
data, err := inst.prepareData(
map[string]interface{}{
- "id": inst.Account.ID,
+ "id": inst.uuid,
"experiments": goInstaExperiments,
},
)
@@ -281,21 +424,10 @@ func (inst *Instagram) syncFeatures() error {
_, err = inst.sendRequest(
&reqOptions{
- Endpoint: urlSync,
+ Endpoint: urlQeSync,
Query: generateSignature(data),
IsPost: true,
- },
- )
- if err != nil {
- return err
- }
-
- _, err = inst.sendRequest(
- &reqOptions{
- Endpoint: urlAutoComplete,
- Query: map[string]string{
- "version": "2",
- },
+ Login: true,
},
)
return err
@@ -320,6 +452,7 @@ func (inst *Instagram) megaphoneLog() error {
Endpoint: urlMegaphoneLog,
Query: generateSignature(data),
IsPost: true,
+ Login: true,
},
)
return err
@@ -347,9 +480,15 @@ func (inst *Instagram) expose() error {
return err
}
-// AcquireFeed returns initilised FeedMedia
+// GetMedia returns media specified by id.
//
-// Use FeedMedia.Sync() to update FeedMedia information. Do not forget to set id (you can use FeedMedia.SetID)
-func (inst *Instagram) AcquireFeed() *FeedMedia {
- return &FeedMedia{inst: inst}
+// The argument can be int64 or string
+//
+// See example: examples/media/like.go
+func (inst *Instagram) GetMedia(o interface{}) (*FeedMedia, error) {
+ media := &FeedMedia{
+ inst: inst,
+ NextID: o,
+ }
+ return media, media.Sync()
}
diff --git a/goinsta/cmd/account/feed/media.go b/goinsta/cmd/account/feed/media.go
index ca842e09..c029d4c5 100644
--- a/goinsta/cmd/account/feed/media.go
+++ b/goinsta/cmd/account/feed/media.go
@@ -23,9 +23,9 @@ package feed
import (
"fmt"
+ "github.com/ahmdrz/goinsta/utils"
"github.com/cheggaaa/pb"
"github.com/spf13/cobra"
- "gopkg.in/ahmdrz/goinsta.v2/utils"
)
// mediaCmd represents the media command
@@ -49,7 +49,7 @@ var mediaCmd = &cobra.Command{
for media.Next() {
pgb := pb.StartNew(len(media.Items))
for _, item := range media.Items {
- err := item.Download(output, "")
+ _, _, err := item.Download(output, "")
if err != nil {
fmt.Println(err)
}
diff --git a/goinsta/cmd/account/feed/root.go b/goinsta/cmd/account/feed/root.go
index ae87ee54..c103c760 100644
--- a/goinsta/cmd/account/feed/root.go
+++ b/goinsta/cmd/account/feed/root.go
@@ -24,6 +24,7 @@ import (
"github.com/spf13/cobra"
)
+//RootCmd is used as a command line interaction with Instagram Feeds.
var RootCmd = &cobra.Command{
Use: "feed",
Short: "Get downloads all user feed",
diff --git a/goinsta/cmd/account/followers/root.go b/goinsta/cmd/account/followers/root.go
index 9827076d..4a674ff3 100644
--- a/goinsta/cmd/account/followers/root.go
+++ b/goinsta/cmd/account/followers/root.go
@@ -23,10 +23,11 @@ package followers
import (
"fmt"
+ "github.com/ahmdrz/goinsta/utils"
"github.com/spf13/cobra"
- "gopkg.in/ahmdrz/goinsta.v2/utils"
)
+//RootCmd is used as a command line interaction with Instagram Followers.
var RootCmd = &cobra.Command{
Use: "followers",
Short: "Get account followers",
diff --git a/goinsta/cmd/account/following/root.go b/goinsta/cmd/account/following/root.go
index bcf38190..05e098a6 100644
--- a/goinsta/cmd/account/following/root.go
+++ b/goinsta/cmd/account/following/root.go
@@ -23,10 +23,11 @@ package following
import (
"fmt"
+ "github.com/ahmdrz/goinsta/utils"
"github.com/spf13/cobra"
- "gopkg.in/ahmdrz/goinsta.v2/utils"
)
+//RootCmd is used as a command line interaction with Instagram Following contacts.
var RootCmd = &cobra.Command{
Use: "following",
Short: "Get account following",
diff --git a/goinsta/cmd/account/info/root.go b/goinsta/cmd/account/info/root.go
index 789b624f..d58d7eed 100644
--- a/goinsta/cmd/account/info/root.go
+++ b/goinsta/cmd/account/info/root.go
@@ -23,10 +23,11 @@ package info
import (
"fmt"
+ "github.com/ahmdrz/goinsta/utils"
"github.com/spf13/cobra"
- "gopkg.in/ahmdrz/goinsta.v2/utils"
)
+//RootCmd is used as a command line interaction with Instagram Profile info.
var RootCmd = &cobra.Command{
Use: "info",
Short: "Get partial info about your account",
diff --git a/goinsta/cmd/account/root.go b/goinsta/cmd/account/root.go
index ca0df7d6..30d63851 100644
--- a/goinsta/cmd/account/root.go
+++ b/goinsta/cmd/account/root.go
@@ -24,12 +24,12 @@ import (
"fmt"
"os"
+ "github.com/ahmdrz/goinsta/goinsta/cmd/account/feed"
+ "github.com/ahmdrz/goinsta/goinsta/cmd/account/followers"
+ "github.com/ahmdrz/goinsta/goinsta/cmd/account/following"
+ "github.com/ahmdrz/goinsta/goinsta/cmd/account/info"
+ "github.com/ahmdrz/goinsta/goinsta/cmd/account/stories"
"github.com/spf13/cobra"
- "gopkg.in/ahmdrz/goinsta.v2/goinsta/cmd/account/feed"
- "gopkg.in/ahmdrz/goinsta.v2/goinsta/cmd/account/followers"
- "gopkg.in/ahmdrz/goinsta.v2/goinsta/cmd/account/following"
- "gopkg.in/ahmdrz/goinsta.v2/goinsta/cmd/account/info"
- "gopkg.in/ahmdrz/goinsta.v2/goinsta/cmd/account/stories"
)
func init() {
@@ -40,11 +40,13 @@ func init() {
RootCmd.AddCommand(following.RootCmd)
}
+//RootCmd is used as a command line interaction with Instagram Account related methods.
var RootCmd = &cobra.Command{
Use: "account",
Short: "Interact with your account",
}
+//Execute the method to to start the command execution of Instagram Account related methods.
func Execute() {
if err := RootCmd.Execute(); err != nil {
fmt.Println(err)
diff --git a/goinsta/cmd/account/stories/root.go b/goinsta/cmd/account/stories/root.go
index f2adaa1e..4e9271e6 100644
--- a/goinsta/cmd/account/stories/root.go
+++ b/goinsta/cmd/account/stories/root.go
@@ -23,11 +23,12 @@ package story
import (
"fmt"
+ "github.com/ahmdrz/goinsta/utils"
"github.com/cheggaaa/pb"
"github.com/spf13/cobra"
- "gopkg.in/ahmdrz/goinsta.v2/utils"
)
+//RootCmd is used as a command line interaction with Instagram Stories.
var RootCmd = &cobra.Command{
Use: "stories",
Short: "Get stories of your account",
@@ -48,7 +49,7 @@ var RootCmd = &cobra.Command{
for media.Next() {
pgb := pb.StartNew(len(media.Items))
for _, item := range media.Items {
- err := item.Download(output, "")
+ _, _, err := item.Download(output, "")
if err != nil {
fmt.Println(err)
}
diff --git a/goinsta/cmd/logout/root.go b/goinsta/cmd/logout/root.go
index 2627f146..f57c7538 100644
--- a/goinsta/cmd/logout/root.go
+++ b/goinsta/cmd/logout/root.go
@@ -23,10 +23,11 @@ package logout
import (
"fmt"
+ "github.com/ahmdrz/goinsta/utils"
"github.com/spf13/cobra"
- "gopkg.in/ahmdrz/goinsta.v2/utils"
)
+//RootCmd is used as a command line interaction with Instagram Logout.
var RootCmd = &cobra.Command{
Use: "logout",
Short: "Logout from your account",
diff --git a/goinsta/cmd/root.go b/goinsta/cmd/root.go
index b5a4b4aa..3b89746b 100644
--- a/goinsta/cmd/root.go
+++ b/goinsta/cmd/root.go
@@ -24,12 +24,12 @@ import (
"fmt"
"os"
+ "github.com/ahmdrz/goinsta/goinsta/cmd/account"
+ "github.com/ahmdrz/goinsta/goinsta/cmd/logout"
+ "github.com/ahmdrz/goinsta/goinsta/cmd/search"
+ "github.com/ahmdrz/goinsta/goinsta/cmd/timeline"
+ "github.com/ahmdrz/goinsta/goinsta/cmd/user"
"github.com/spf13/cobra"
- "gopkg.in/ahmdrz/goinsta.v2/goinsta/cmd/account"
- "gopkg.in/ahmdrz/goinsta.v2/goinsta/cmd/logout"
- "gopkg.in/ahmdrz/goinsta.v2/goinsta/cmd/search"
- "gopkg.in/ahmdrz/goinsta.v2/goinsta/cmd/timeline"
- "gopkg.in/ahmdrz/goinsta.v2/goinsta/cmd/user"
)
func init() {
@@ -49,6 +49,7 @@ var rootCmd = &cobra.Command{
Short: "Command line tool for Instagram",
}
+// Execute is called from main as an entrance to the command line interaction tool.
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
diff --git a/goinsta/cmd/search/facebook/root.go b/goinsta/cmd/search/facebook/root.go
index 0a40d122..2076ec90 100644
--- a/goinsta/cmd/search/facebook/root.go
+++ b/goinsta/cmd/search/facebook/root.go
@@ -24,10 +24,11 @@ import (
"fmt"
"strings"
+ "github.com/ahmdrz/goinsta/utils"
"github.com/spf13/cobra"
- "gopkg.in/ahmdrz/goinsta.v2/utils"
)
+//RootCmd is used as a command line interaction with Instagram Facebook Search.
var RootCmd = &cobra.Command{
Use: "facebook",
Short: "Search facebook users on Instagram",
diff --git a/goinsta/cmd/search/root.go b/goinsta/cmd/search/root.go
index ae1167e9..f2244127 100644
--- a/goinsta/cmd/search/root.go
+++ b/goinsta/cmd/search/root.go
@@ -24,10 +24,10 @@ import (
"fmt"
"os"
+ "github.com/ahmdrz/goinsta/goinsta/cmd/search/facebook"
+ "github.com/ahmdrz/goinsta/goinsta/cmd/search/tags"
+ "github.com/ahmdrz/goinsta/goinsta/cmd/search/user"
"github.com/spf13/cobra"
- "gopkg.in/ahmdrz/goinsta.v2/goinsta/cmd/search/facebook"
- "gopkg.in/ahmdrz/goinsta.v2/goinsta/cmd/search/tags"
- "gopkg.in/ahmdrz/goinsta.v2/goinsta/cmd/search/user"
)
func init() {
@@ -36,11 +36,13 @@ func init() {
RootCmd.AddCommand(tags.RootCmd)
}
+//RootCmd is used as a command line interaction with all Instagram Searches.
var RootCmd = &cobra.Command{
Use: "search",
Short: "Search on Instagram.",
}
+//Execute the method to to start the command execution of Instagram Search related methods.
func Execute() {
if err := RootCmd.Execute(); err != nil {
fmt.Println(err)
diff --git a/goinsta/cmd/search/tags/root.go b/goinsta/cmd/search/tags/root.go
index 37e751ed..0a3a96c3 100644
--- a/goinsta/cmd/search/tags/root.go
+++ b/goinsta/cmd/search/tags/root.go
@@ -24,10 +24,11 @@ import (
"fmt"
"strings"
+ "github.com/ahmdrz/goinsta/utils"
"github.com/spf13/cobra"
- "gopkg.in/ahmdrz/goinsta.v2/utils"
)
+//RootCmd is used as a command line interaction with Instagram Tag search.
var RootCmd = &cobra.Command{
Use: "tags",
Short: "Search tags on Instagram",
diff --git a/goinsta/cmd/search/user/root.go b/goinsta/cmd/search/user/root.go
index 11c1fdc9..b39fd765 100644
--- a/goinsta/cmd/search/user/root.go
+++ b/goinsta/cmd/search/user/root.go
@@ -24,10 +24,11 @@ import (
"fmt"
"strings"
+ "github.com/ahmdrz/goinsta/utils"
"github.com/spf13/cobra"
- "gopkg.in/ahmdrz/goinsta.v2/utils"
)
+//RootCmd is used as a command line interaction with Instagram User Search.
var RootCmd = &cobra.Command{
Use: "user",
Short: "Search users on Instagram",
diff --git a/goinsta/cmd/timeline/feed/root.go b/goinsta/cmd/timeline/feed/root.go
index 0928acfc..26a54b67 100644
--- a/goinsta/cmd/timeline/feed/root.go
+++ b/goinsta/cmd/timeline/feed/root.go
@@ -23,11 +23,12 @@ package feed
import (
"fmt"
+ "github.com/ahmdrz/goinsta/utils"
"github.com/cheggaaa/pb"
"github.com/spf13/cobra"
- "gopkg.in/ahmdrz/goinsta.v2/utils"
)
+//RootCmd is used as a command line interaction with Instagram Timeline Feed.
var RootCmd = &cobra.Command{
Use: "feed",
Short: "Download feed media",
@@ -53,7 +54,7 @@ var RootCmd = &cobra.Command{
pgb := pb.StartNew(len(media.Items))
for _, item := range media.Items {
- err := item.Download(output, "")
+ _, _, err := item.Download(output, "")
if err != nil {
fmt.Println(err)
}
diff --git a/goinsta/cmd/timeline/root.go b/goinsta/cmd/timeline/root.go
index 4611a387..53098fcd 100644
--- a/goinsta/cmd/timeline/root.go
+++ b/goinsta/cmd/timeline/root.go
@@ -24,9 +24,9 @@ import (
"fmt"
"os"
+ "github.com/ahmdrz/goinsta/goinsta/cmd/timeline/feed"
+ "github.com/ahmdrz/goinsta/goinsta/cmd/timeline/stories"
"github.com/spf13/cobra"
- "gopkg.in/ahmdrz/goinsta.v2/goinsta/cmd/timeline/feed"
- "gopkg.in/ahmdrz/goinsta.v2/goinsta/cmd/timeline/stories"
)
func init() {
@@ -34,11 +34,13 @@ func init() {
RootCmd.AddCommand(stories.RootCmd)
}
+//RootCmd is used as a command line interaction with Instagram Timeline related methods.
var RootCmd = &cobra.Command{
Use: "timeline",
Short: "Get timeline feed or stories",
}
+//Execute the method to to start the command execution of Instagram Timeline related methods.
func Execute() {
if err := RootCmd.Execute(); err != nil {
fmt.Println(err)
diff --git a/goinsta/cmd/timeline/stories/root.go b/goinsta/cmd/timeline/stories/root.go
index 04a9d7d1..5e65c3c9 100644
--- a/goinsta/cmd/timeline/stories/root.go
+++ b/goinsta/cmd/timeline/stories/root.go
@@ -23,11 +23,12 @@ package stories
import (
"fmt"
+ "github.com/ahmdrz/goinsta/utils"
"github.com/cheggaaa/pb"
"github.com/spf13/cobra"
- "gopkg.in/ahmdrz/goinsta.v2/utils"
)
+//RootCmd is used as a command line interaction with Instagram Timeline Stories.
var RootCmd = &cobra.Command{
Use: "stories",
Short: "Get stories of a user",
@@ -52,7 +53,7 @@ var RootCmd = &cobra.Command{
out := fmt.Sprintf("%s/%s/", output, media.User.Username)
pgb := pb.StartNew(len(media.Items))
for _, item := range media.Items {
- err := item.Download(out, "")
+ _, _, err := item.Download(out, "")
if err != nil {
fmt.Println(err)
}
diff --git a/goinsta/cmd/user/block/root.go b/goinsta/cmd/user/block/root.go
index ed8712bf..9fcfdb40 100644
--- a/goinsta/cmd/user/block/root.go
+++ b/goinsta/cmd/user/block/root.go
@@ -24,10 +24,11 @@ import (
"fmt"
"strconv"
+ "github.com/ahmdrz/goinsta/utils"
"github.com/spf13/cobra"
- "gopkg.in/ahmdrz/goinsta.v2/utils"
)
+//RootCmd is used as a command line interaction with Instagram Block user method.
var RootCmd = &cobra.Command{
Use: "block",
Short: "Blocks a user",
diff --git a/goinsta/cmd/user/feed/media.go b/goinsta/cmd/user/feed/media.go
index f475159a..257d2f2f 100644
--- a/goinsta/cmd/user/feed/media.go
+++ b/goinsta/cmd/user/feed/media.go
@@ -25,9 +25,9 @@ import (
"os"
"strconv"
+ "github.com/ahmdrz/goinsta/utils"
"github.com/cheggaaa/pb"
"github.com/spf13/cobra"
- "gopkg.in/ahmdrz/goinsta.v2/utils"
)
// mediaCmd represents the media command
@@ -44,7 +44,7 @@ var mediaCmd = &cobra.Command{
output, err := cmd.Flags().GetString("output")
if err != nil || output == "" {
- output = "./" + args[0] + "/"
+ output = "./" + args[0] + "/feed/"
}
inst := utils.New()
@@ -65,7 +65,7 @@ var mediaCmd = &cobra.Command{
for media.Next() {
pgb := pb.StartNew(len(media.Items))
for _, item := range media.Items {
- err := item.Download(output, "")
+ _, _, err := item.Download(output, "")
if err != nil {
fmt.Println(err)
}
diff --git a/goinsta/cmd/user/feed/root.go b/goinsta/cmd/user/feed/root.go
index ae87ee54..8c1b8483 100644
--- a/goinsta/cmd/user/feed/root.go
+++ b/goinsta/cmd/user/feed/root.go
@@ -24,6 +24,7 @@ import (
"github.com/spf13/cobra"
)
+//RootCmd is used as a command line interaction with Instagram user Feed method.
var RootCmd = &cobra.Command{
Use: "feed",
Short: "Get downloads all user feed",
diff --git a/goinsta/cmd/user/follow/root.go b/goinsta/cmd/user/follow/root.go
index da889178..a967a563 100644
--- a/goinsta/cmd/user/follow/root.go
+++ b/goinsta/cmd/user/follow/root.go
@@ -24,10 +24,11 @@ import (
"fmt"
"strconv"
+ "github.com/ahmdrz/goinsta/utils"
"github.com/spf13/cobra"
- "gopkg.in/ahmdrz/goinsta.v2/utils"
)
+//RootCmd is used as a command line interaction with Instagram Follow user method.
var RootCmd = &cobra.Command{
Use: "follow",
Short: "Start following a user",
diff --git a/goinsta/cmd/user/followers/root.go b/goinsta/cmd/user/followers/root.go
index ed61fe2d..d080117b 100644
--- a/goinsta/cmd/user/followers/root.go
+++ b/goinsta/cmd/user/followers/root.go
@@ -24,10 +24,11 @@ import (
"fmt"
"strconv"
+ "github.com/ahmdrz/goinsta/utils"
"github.com/spf13/cobra"
- "gopkg.in/ahmdrz/goinsta.v2/utils"
)
+//RootCmd is used as a command line interaction with Instagram get followers method.
var RootCmd = &cobra.Command{
Use: "followers",
Short: "Get user followers",
diff --git a/goinsta/cmd/user/following/root.go b/goinsta/cmd/user/following/root.go
index 858e485b..637fdbaf 100644
--- a/goinsta/cmd/user/following/root.go
+++ b/goinsta/cmd/user/following/root.go
@@ -24,10 +24,11 @@ import (
"fmt"
"strconv"
+ "github.com/ahmdrz/goinsta/utils"
"github.com/spf13/cobra"
- "gopkg.in/ahmdrz/goinsta.v2/utils"
)
+//RootCmd is used as a command line interaction with Instagram get user following method.
var RootCmd = &cobra.Command{
Use: "following",
Short: "Get user following",
diff --git a/goinsta/cmd/user/highlights/root.go b/goinsta/cmd/user/highlights/root.go
new file mode 100644
index 00000000..f623a0cd
--- /dev/null
+++ b/goinsta/cmd/user/highlights/root.go
@@ -0,0 +1,80 @@
+// Copyright © 2018 Mester
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package highlights
+
+import (
+ "fmt"
+ "os"
+ "strconv"
+
+ "github.com/ahmdrz/goinsta/utils"
+ "github.com/cheggaaa/pb"
+ "github.com/spf13/cobra"
+)
+
+//RootCmd is used as a command line interaction with Instagram get user highlight methods.
+var RootCmd = &cobra.Command{
+ Use: "highlights",
+ Short: "Get highlights of a user",
+ Example: "goinsta user highlights robpike",
+ Run: func(cmd *cobra.Command, args []string) {
+ if len(args) == 0 {
+ fmt.Println("Missing arguments. See example.")
+ return
+ }
+ cmd = cmd.Root()
+
+ output, err := cmd.Flags().GetString("output")
+ if err != nil || output == "" {
+ output = "./" + args[0] + "/highlights/"
+ }
+ inst := utils.New()
+
+ user, err := inst.Profiles.ByName(args[0])
+ if err != nil {
+ id, _ := strconv.ParseInt(args[0], 10, 64)
+ user, err = inst.Profiles.ByID(id)
+ if err != nil {
+ fmt.Printf("Invalid username or id: %s\n", args[0])
+ os.Exit(1)
+ }
+ }
+
+ hlgts, err := user.Highlights()
+ if err != nil {
+ fmt.Printf("error getting highlights: %s\n", err)
+ os.Exit(1)
+ }
+
+ fmt.Println("Downloading highlights of", user.Username)
+ for _, h := range hlgts {
+ pgb := pb.StartNew(len(h.Items))
+ for _, item := range h.Items {
+ _, _, err := item.Download(output, "")
+ if err != nil {
+ fmt.Println(err)
+ }
+ pgb.Add(1)
+ }
+ pgb.Finish()
+ }
+ },
+}
diff --git a/goinsta/cmd/user/info/root.go b/goinsta/cmd/user/info/root.go
index 0466f131..ed59d9fa 100644
--- a/goinsta/cmd/user/info/root.go
+++ b/goinsta/cmd/user/info/root.go
@@ -24,10 +24,11 @@ import (
"fmt"
"strconv"
+ "github.com/ahmdrz/goinsta/utils"
"github.com/spf13/cobra"
- "gopkg.in/ahmdrz/goinsta.v2/utils"
)
+//RootCmd is used as a command line interaction with Instagram get user info method.
var RootCmd = &cobra.Command{
Use: "info",
Short: "Get partial info about user",
@@ -56,13 +57,13 @@ Fullname: %s
ID: %d
ProfilePicURL: %s
Email: %s
-Gender: %d
+Phone number: %s
Biography: %s
Followers: %d
Following: %d
You follow him/her: %v
`, user.Username, user.FullName, user.ID, user.ProfilePicURL,
- user.PublicEmail, user.Gender, user.Biography, user.FollowerCount,
+ user.PublicEmail, user.PublicPhoneNumber, user.Biography, user.FollowerCount,
user.FollowingCount, user.Friendship.Following)
},
}
diff --git a/goinsta/cmd/user/root.go b/goinsta/cmd/user/root.go
index 5ac16e5c..00552121 100644
--- a/goinsta/cmd/user/root.go
+++ b/goinsta/cmd/user/root.go
@@ -24,19 +24,21 @@ import (
"fmt"
"os"
+ "github.com/ahmdrz/goinsta/goinsta/cmd/user/block"
+ "github.com/ahmdrz/goinsta/goinsta/cmd/user/feed"
+ "github.com/ahmdrz/goinsta/goinsta/cmd/user/follow"
+ "github.com/ahmdrz/goinsta/goinsta/cmd/user/followers"
+ "github.com/ahmdrz/goinsta/goinsta/cmd/user/following"
+ "github.com/ahmdrz/goinsta/goinsta/cmd/user/highlights"
+ "github.com/ahmdrz/goinsta/goinsta/cmd/user/info"
+ "github.com/ahmdrz/goinsta/goinsta/cmd/user/stories"
+ "github.com/ahmdrz/goinsta/goinsta/cmd/user/unblock"
+ "github.com/ahmdrz/goinsta/goinsta/cmd/user/unfollow"
"github.com/spf13/cobra"
- "gopkg.in/ahmdrz/goinsta.v2/goinsta/cmd/user/block"
- "gopkg.in/ahmdrz/goinsta.v2/goinsta/cmd/user/feed"
- "gopkg.in/ahmdrz/goinsta.v2/goinsta/cmd/user/follow"
- "gopkg.in/ahmdrz/goinsta.v2/goinsta/cmd/user/followers"
- "gopkg.in/ahmdrz/goinsta.v2/goinsta/cmd/user/following"
- "gopkg.in/ahmdrz/goinsta.v2/goinsta/cmd/user/info"
- "gopkg.in/ahmdrz/goinsta.v2/goinsta/cmd/user/stories"
- "gopkg.in/ahmdrz/goinsta.v2/goinsta/cmd/user/unblock"
- "gopkg.in/ahmdrz/goinsta.v2/goinsta/cmd/user/unfollow"
)
func init() {
+ RootCmd.AddCommand(highlights.RootCmd)
RootCmd.AddCommand(feed.RootCmd)
RootCmd.AddCommand(story.RootCmd)
RootCmd.AddCommand(info.RootCmd)
@@ -48,11 +50,13 @@ func init() {
RootCmd.AddCommand(unblock.RootCmd)
}
+//RootCmd is used as a command line interaction with Instagram User related methods.
var RootCmd = &cobra.Command{
Use: "user",
Short: "Get downloads specified Instagram user's object.",
}
+//Execute the method to to start the command execution of Instagram User related methods.
func Execute() {
if err := RootCmd.Execute(); err != nil {
fmt.Println(err)
diff --git a/goinsta/cmd/user/stories/root.go b/goinsta/cmd/user/stories/root.go
index 345de9c4..17d1a533 100644
--- a/goinsta/cmd/user/stories/root.go
+++ b/goinsta/cmd/user/stories/root.go
@@ -25,11 +25,12 @@ import (
"os"
"strconv"
+ "github.com/ahmdrz/goinsta/utils"
"github.com/cheggaaa/pb"
"github.com/spf13/cobra"
- "gopkg.in/ahmdrz/goinsta.v2/utils"
)
+//RootCmd is used as a command line interaction with Instagram get user stories method.
var RootCmd = &cobra.Command{
Use: "stories",
Short: "Get stories of a user",
@@ -43,7 +44,7 @@ var RootCmd = &cobra.Command{
output, err := cmd.Flags().GetString("output")
if err != nil || output == "" {
- output = "./" + args[0] + "/"
+ output = "./" + args[0] + "/stories/"
}
inst := utils.New()
@@ -63,7 +64,7 @@ var RootCmd = &cobra.Command{
for media.Next() {
pgb := pb.StartNew(len(media.Items))
for _, item := range media.Items {
- err := item.Download(output, "")
+ _, _, err := item.Download(output, "")
if err != nil {
fmt.Println(err)
}
diff --git a/goinsta/cmd/user/unblock/root.go b/goinsta/cmd/user/unblock/root.go
index 9b002585..0319e8b7 100644
--- a/goinsta/cmd/user/unblock/root.go
+++ b/goinsta/cmd/user/unblock/root.go
@@ -24,10 +24,11 @@ import (
"fmt"
"strconv"
+ "github.com/ahmdrz/goinsta/utils"
"github.com/spf13/cobra"
- "gopkg.in/ahmdrz/goinsta.v2/utils"
)
+//RootCmd is used as a command line interaction with Instagram unblock user method.
var RootCmd = &cobra.Command{
Use: "unblock",
Short: "Unblocks a user",
diff --git a/goinsta/cmd/user/unfollow/root.go b/goinsta/cmd/user/unfollow/root.go
index 5cc6124c..e81b60cd 100644
--- a/goinsta/cmd/user/unfollow/root.go
+++ b/goinsta/cmd/user/unfollow/root.go
@@ -24,10 +24,11 @@ import (
"fmt"
"strconv"
+ "github.com/ahmdrz/goinsta/utils"
"github.com/spf13/cobra"
- "gopkg.in/ahmdrz/goinsta.v2/utils"
)
+//RootCmd is used as a command line interaction with Instagram unfollow user method.
var RootCmd = &cobra.Command{
Use: "unfollow",
Short: "Start unfollowing a user",
diff --git a/goinsta/main.go b/goinsta/main.go
index 9c2d9346..06621c7a 100644
--- a/goinsta/main.go
+++ b/goinsta/main.go
@@ -20,7 +20,7 @@
package main
-import "gopkg.in/ahmdrz/goinsta.v2/goinsta/cmd"
+import "github.com/ahmdrz/goinsta/goinsta/cmd"
func main() {
cmd.Execute()
diff --git a/hashtags.go b/hashtags.go
index b993fcd2..7f42dc6d 100644
--- a/hashtags.go
+++ b/hashtags.go
@@ -5,6 +5,7 @@ import (
"fmt"
)
+// Hashtag is used for getting the media that matches a hashtag on instagram.
type Hashtag struct {
inst *Instagram
err error
@@ -39,6 +40,17 @@ type Hashtag struct {
Status string `json:"status"`
}
+func (h *Hashtag) setValues() {
+ for i := range h.Sections {
+ for j := range h.Sections[i].LayoutContent.Medias {
+ m := &FeedMedia{
+ inst: h.inst,
+ }
+ setToItem(&h.Sections[i].LayoutContent.Medias[j].Item, m)
+ }
+ }
+}
+
// NewHashtag returns initialised hashtag structure
// Name parameter is hashtag name
func (inst *Instagram) NewHashtag(name string) *Hashtag {
@@ -64,6 +76,7 @@ func (h *Hashtag) Sync() error {
h.Name = resp.Name
h.ID = resp.ID
h.MediaCount = resp.MediaCount
+ h.setValues()
}
}
return err
@@ -97,6 +110,7 @@ func (h *Hashtag) Next() bool {
if !h.MoreAvailable {
h.err = ErrNoMore
}
+ h.setValues()
return true
}
}
diff --git a/inbox.go b/inbox.go
index ace4cff1..7f600cc0 100644
--- a/inbox.go
+++ b/inbox.go
@@ -44,23 +44,25 @@ type InboxItem struct {
// InboxItems are the message of the chat.
type Inbox struct {
inst *Instagram
+ err error
Conversations []Conversation `json:"threads"`
- HasNewer bool `json:"has_newer"` // TODO
- HasOlder bool `json:"has_older"`
- UnseenCount int `json:"unseen_count"`
- UnseenCountTs int64 `json:"unseen_count_ts"`
- BlendedInboxEnabled bool `json:"blended_inbox_enabled"`
+ HasNewer bool `json:"has_newer"` // TODO
+ HasOlder bool `json:"has_older"`
+ Cursor string `json:"oldest_cursor"`
+ UnseenCount int `json:"unseen_count"`
+ UnseenCountTs int64 `json:"unseen_count_ts"`
+ BlendedInboxEnabled bool `json:"blended_inbox_enabled"`
// this fields are copied from response
- SeqID int `json:"seq_id"`
+ SeqID int64 `json:"seq_id"`
PendingRequestsTotal int `json:"pending_requests_total"`
SnapshotAtMs int64 `json:"snapshot_at_ms"`
}
type inboxResp struct {
Inbox Inbox `json:"inbox"`
- SeqID int `json:"seq_id"`
+ SeqID int64 `json:"seq_id"`
PendingRequestsTotal int `json:"pending_requests_total"`
SnapshotAtMs int64 `json:"snapshot_at_ms"`
Status string `json:"status"`
@@ -70,22 +72,20 @@ func newInbox(inst *Instagram) *Inbox {
return &Inbox{inst: inst}
}
-// Sync updates inbox messages.
-//
-// See example: examples/inbox/sync.go
-func (inbox *Inbox) Sync() error {
- // TODO: Next for pagination
+func (inbox *Inbox) sync(pending bool, params map[string]string) error {
+ endpoint := urlInbox
+ if pending {
+ endpoint = urlInboxPending
+ }
+
insta := inbox.inst
body, err := insta.sendRequest(
&reqOptions{
- Endpoint: urlInbox,
- Query: map[string]string{
- "persistentBadging": "true",
- "use_unified_inbox": "true",
- "limit": "0",
- },
+ Endpoint: endpoint,
+ Query: params,
},
)
+
if err == nil {
resp := inboxResp{}
err = json.Unmarshal(body, &resp)
@@ -104,13 +104,124 @@ func (inbox *Inbox) Sync() error {
return err
}
+func (inbox *Inbox) next(pending bool, params map[string]string) bool {
+ endpoint := urlInbox
+ if pending {
+ endpoint = urlInboxPending
+ }
+ if inbox.err != nil {
+ return false
+ }
+ insta := inbox.inst
+ body, err := insta.sendRequest(
+ &reqOptions{
+ Endpoint: endpoint,
+ Query: params,
+ },
+ )
+ if err == nil {
+ resp := inboxResp{}
+ err = json.Unmarshal(body, &resp)
+ if err == nil {
+ *inbox = resp.Inbox
+ inbox.inst = insta
+ inbox.SeqID = resp.Inbox.SeqID
+ inbox.PendingRequestsTotal = resp.Inbox.PendingRequestsTotal
+ inbox.SnapshotAtMs = resp.Inbox.SnapshotAtMs
+ for i := range inbox.Conversations {
+ inbox.Conversations[i].inst = insta
+ inbox.Conversations[i].firstRun = true
+ }
+ if inbox.Cursor == "" || !inbox.HasOlder {
+ inbox.err = ErrNoMore
+ }
+ return true
+ }
+ }
+ inbox.err = err
+ return false
+}
+
+// Sync updates inbox messages.
+//
+// See example: examples/inbox/sync.go
+func (inbox *Inbox) Sync() error {
+ return inbox.sync(false, map[string]string{
+ "persistentBadging": "true",
+ "use_unified_inbox": "true",
+ })
+}
+
+// SyncPending updates inbox pending messages.
+//
+// See example: examples/inbox/sync.go
+func (inbox *Inbox) SyncPending() error {
+ return inbox.sync(true, map[string]string{})
+}
+
+// New initialises a new conversation with a user, for further messages you should use Conversation.Send
+//
+// See example: examples/inbox/newconversation.go
+func (inbox *Inbox) New(user *User, text string) error {
+ insta := inbox.inst
+ to, err := prepareRecipients(user.ID)
+ if err != nil {
+ return err
+ }
+
+ data := insta.prepareDataQuery(
+ map[string]interface{}{
+ "recipient_users": to,
+ "client_context": generateUUID(),
+ "thread_ids": `["0"]`,
+ "action": "send_item",
+ "text": text,
+ },
+ )
+ _, err = insta.sendRequest(
+ &reqOptions{
+ Connection: "keep-alive",
+ Endpoint: urlInboxSend,
+ Query: data,
+ IsPost: true,
+ },
+ )
+ return err
+}
+
+// Reset sets inbox cursor at the beginning.
+func (inbox *Inbox) Reset() {
+ inbox.Cursor = ""
+}
+
+// Next allows pagination over messages.
+//
+// See example: examples/inbox/next.go
+func (inbox *Inbox) Next() bool {
+ return inbox.next(false, map[string]string{
+ "persistentBadging": "true",
+ "use_unified_inbox": "true",
+ "cursor": inbox.Cursor,
+ })
+}
+
+// NextPending allows pagination over pending messages.
+//
+// See example: examples/inbox/next.go
+func (inbox *Inbox) NextPending() bool {
+ return inbox.next(true, map[string]string{
+ "cursor": inbox.Cursor,
+ })
+}
+
+// Conversation is the representation of an instagram already established conversation through direct messages.
type Conversation struct {
inst *Instagram
err error
firstRun bool
ID string `json:"thread_id"`
- V2ID int64 `json:"thread_v2_id"`
+ V2ID string `json:"thread_v2_id"`
// Items can be of many types.
Items []InboxItem `json:"items"`
Title string `json:"thread_title"`
@@ -132,11 +243,9 @@ type Conversation struct {
Inviter User `json:"inviter"`
HasOlder bool `json:"has_older"`
HasNewer bool `json:"has_newer"`
- LastSeenAt struct {
- Num7629421016 struct {
- Timestamp string `json:"timestamp"`
- ItemID string `json:"item_id"`
- } `json:"7629421016"`
+ LastSeenAt map[string]struct {
+ Timestamp string `json:"timestamp"`
+ ItemID string `json:"item_id"`
} `json:"last_seen_at"`
NewestCursor string `json:"newest_cursor"`
OldestCursor string `json:"oldest_cursor"`
@@ -156,27 +265,74 @@ func (c Conversation) lastItemID() string {
return c.Items[n-1].ID
}
+// Like sends heart to the conversation
+//
+// See example: examples/media/likeAll.go
+func (c *Conversation) Like() error {
+ insta := c.inst
+ to, err := prepareRecipients(c)
+ if err != nil {
+ return err
+ }
+
+ thread, err := json.Marshal([]string{c.ID})
+ if err != nil {
+ return err
+ }
+
+ data := insta.prepareDataQuery(
+ map[string]interface{}{
+ "recipient_users": to,
+ "client_context": generateUUID(),
+ "thread_ids": b2s(thread),
+ "action": "send_item",
+ },
+ )
+ _, err = insta.sendRequest(
+ &reqOptions{
+ Connection: "keep-alive",
+ Endpoint: urlInboxSendLike,
+ Query: data,
+ IsPost: true,
+ },
+ )
+ return err
+}
+
// Send sends message in conversation
+//
+// See example: examples/inbox/sms.go
func (c *Conversation) Send(text string) error {
insta := c.inst
- data, err := insta.prepareData(
+ // I DON'T KNOW WHY BUT INSTAGRAM WANTS A DOUBLE SLICE OF INTS FOR ONE ID.
+ to, err := prepareRecipients(c)
+ if err != nil {
+ return err
+ }
+
+ // I DONT KNOW WHY BUT INSTAGRAM WANTS SLICE OF STRINGS FOR ONE ID
+ thread, err := json.Marshal([]string{c.ID})
+ if err != nil {
+ return err
+ }
+
+ data := insta.prepareDataQuery(
map[string]interface{}{
- "recipient_users": c.Inviter.ID,
+ "recipient_users": to,
+ "client_context": generateUUID(),
+ "thread_ids": b2s(thread),
"action": "send_item",
"text": text,
},
)
- body, err := insta.sendRequest(
+ _, err = insta.sendRequest(
&reqOptions{
- Endpoint: urlInboxSend,
- Query: generateSignature(data),
- IsPost: true,
+ Connection: "keep-alive",
+ Endpoint: urlInboxSend,
+ Query: data,
+ IsPost: true,
},
)
- if err == nil {
- // TODO
- _ = body
- }
return err
}
diff --git a/media.go b/media.go
index 76ff016a..71e383f6 100644
--- a/media.go
+++ b/media.go
@@ -1,14 +1,20 @@
package goinsta
import (
+ "bytes"
"encoding/json"
"fmt"
"io"
+ "io/ioutil"
+ "mime/multipart"
+ "net/http"
neturl "net/url"
"os"
"path"
+ "regexp"
"strconv"
"strings"
+ "time"
)
// Item represents media items
@@ -16,10 +22,10 @@ import (
// All Item has Images or Videos objects which contains the url(s).
// You can use Download function to get the best quality Image or Video from Item.
type Item struct {
- media Media `json:"-"`
+ media Media
Comments *Comments `json:"-"`
- TakenAt float64 `json:"taken_at"`
+ TakenAt int64 `json:"taken_at"`
Pk int64 `json:"pk"`
ID string `json:"id"`
CommentsDisabled bool `json:"comments_disabled"`
@@ -36,19 +42,19 @@ type Item struct {
CaptionIsEdited bool `json:"caption_is_edited"`
Likes int `json:"like_count"`
HasLiked bool `json:"has_liked"`
- // _TopLikers can be `string` or `[]string`.
+ // Toplikers can be `string` or `[]string`.
// Use TopLikers function instead of getting it directly.
- _TopLikers interface{} `json:"top_likers"`
+ Toplikers interface{} `json:"top_likers"`
Likers []User `json:"likers"`
CommentLikesEnabled bool `json:"comment_likes_enabled"`
CommentThreadingEnabled bool `json:"comment_threading_enabled"`
HasMoreComments bool `json:"has_more_comments"`
MaxNumVisiblePreviewComments int `json:"max_num_visible_preview_comments"`
- // _PreviewComments can be `string` or `[]string` or `[]Comment`.
+ // Previewcomments can be `string` or `[]string` or `[]Comment`.
// Use PreviewComments function instead of getting it directly.
- _PreviewComments interface{} `json:"preview_comments,omitempty"`
- CommentCount int `json:"comment_count"`
- PhotoOfYou bool `json:"photo_of_you"`
+ Previewcomments interface{} `json:"preview_comments,omitempty"`
+ CommentCount int `json:"comment_count"`
+ PhotoOfYou bool `json:"photo_of_you"`
// Tags are tagged people in photo
Tags struct {
In []Tag `json:"in"`
@@ -61,7 +67,7 @@ type Item struct {
Images Images `json:"image_versions2,omitempty"`
OriginalWidth int `json:"original_width,omitempty"`
OriginalHeight int `json:"original_height,omitempty"`
- ImportedTakenAt int `json:"imported_taken_at,omitempty"`
+ ImportedTakenAt int64 `json:"imported_taken_at,omitempty"`
Location Location `json:"location,omitempty"`
Lat float64 `json:"lat,omitempty"`
Lng float64 `json:"lng,omitempty"`
@@ -88,13 +94,29 @@ type Item struct {
StoryProductItems []interface{} `json:"story_product_items"`
SupportsReelReactions bool `json:"supports_reel_reactions"`
ShowOneTapFbShareTooltip bool `json:"show_one_tap_fb_share_tooltip"`
- HasSharedToFb int `json:"has_shared_to_fb"`
+ HasSharedToFb int64 `json:"has_shared_to_fb"`
Mentions []Mentions
}
+// MediaToString returns Item.MediaType as string.
+func (item *Item) MediaToString() string {
+ switch item.MediaType {
+ case 1:
+ return "photo"
+ case 2:
+ return "video"
+ }
+ return ""
+}
+
func setToItem(item *Item, media Media) {
item.media = media
+ item.User.inst = media.instagram()
item.Comments = newComments(item)
+ for i := range item.CarouselMedia {
+ item.CarouselMedia[i].User = item.User
+ setToItem(&item.CarouselMedia[i], media)
+ }
}
func getname(name string) string {
@@ -116,20 +138,20 @@ func getname(name string) string {
return name
}
-func download(inst *Instagram, url, dst string) error {
+func download(inst *Instagram, url, dst string) (string, error) {
file, err := os.Create(dst)
if err != nil {
- return err
+ return "", err
}
defer file.Close()
resp, err := inst.c.Get(url)
if err != nil {
- return err
+ return "", err
}
_, err = io.Copy(file, resp.Body)
- return err
+ return dst, err
}
type bestMedia struct {
@@ -137,91 +159,61 @@ type bestMedia struct {
url string
}
-func getBest(obj interface{}) []string {
- m := make(map[string]bestMedia)
+// GetBest returns best quality image or video.
+//
+// Arguments can be []Video or []Candidate
+func GetBest(obj interface{}) string {
+ m := bestMedia{}
switch t := obj.(type) {
// getting best video
case []Video:
for _, video := range t {
- v, ok := m[video.ID]
- if !ok {
- m[video.ID] = bestMedia{
- w: video.Width,
- h: video.Height,
- url: video.URL,
- }
- } else {
- if v.w < video.Width && video.Height > v.h {
- m[video.ID] = bestMedia{
- w: video.Width,
- h: video.Height,
- url: video.URL,
- }
- }
+ if m.w < video.Width && video.Height > m.h && video.URL != "" {
+ m.w = video.Width
+ m.h = video.Height
+ m.url = video.URL
}
}
// getting best image
case []Candidate:
for _, image := range t {
- url, err := neturl.Parse(image.URL)
- if err != nil {
- continue
- }
-
- base := path.Base(url.Path)
- i, ok := m[base]
- if !ok {
- m[base] = bestMedia{
- w: image.Width,
- h: image.Height,
- url: image.URL,
- }
- } else {
- if i.w < image.Width && image.Height > i.h {
- m[base] = bestMedia{
- w: image.Width,
- h: image.Height,
- url: image.URL,
- }
- }
+ if m.w < image.Width && image.Height > m.h && image.URL != "" {
+ m.w = image.Width
+ m.h = image.Height
+ m.url = image.URL
}
}
}
- s := []string{}
- // getting best to return in string slice
- for _, v := range m {
- s = append(s, v.url)
- }
- m = nil
- return s
+ return m.url
}
-// Hastags returns caption hashtags.
+var rxpTags = regexp.MustCompile(`#\w+`)
+
+// Hashtags returns caption hashtags.
//
// Item media parent must be FeedMedia.
//
// See example: examples/media/hashtags.go
func (item *Item) Hashtags() []Hashtag {
- hsh := make([]Hashtag, 0)
- capt := item.Caption.Text
- for {
- i := strings.IndexByte(capt, '#')
- if i < 0 {
- break
- }
- n := strings.IndexByte(capt[i:], ' ')
- if n < 0 { // last hashtag
- hsh = append(hsh, Hashtag{Name: capt[i+1:]})
- break
- }
+ tags := rxpTags.FindAllString(item.Caption.Text, -1)
- // avoiding '#' character
- hsh = append(hsh, Hashtag{Name: capt[i+1 : i+n]})
+ hsh := make([]Hashtag, len(tags))
- // snipping caption
- capt = capt[n+i:]
+ i := 0
+ for _, tag := range tags {
+ hsh[i].Name = tag[1:]
+ i++
}
+
+ for _, comment := range item.PreviewComments() {
+ tags := rxpTags.FindAllString(comment.Text, -1)
+
+ for _, tag := range tags {
+ hsh = append(hsh, Hashtag{Name: tag[1:]})
+ }
+ }
+
return hsh
}
@@ -229,30 +221,41 @@ func (item *Item) Hashtags() []Hashtag {
//
// See example: examples/media/mediaDelete.go
func (item *Item) Delete() error {
- switch m := item.media.(type) {
- case *FeedMedia:
- insta := item.media.instagram()
- data, err := insta.prepareData(
- map[string]interface{}{
- "media_id": item.ID,
- },
- )
- if err != nil {
- return err
- }
+ insta := item.media.instagram()
+ data, err := insta.prepareData(
+ map[string]interface{}{
+ "media_id": item.ID,
+ },
+ )
+ if err != nil {
+ return err
+ }
- _, err = insta.sendRequest(
- &reqOptions{
- Endpoint: fmt.Sprintf(urlMediaDelete, item.ID),
- Query: generateSignature(data),
- IsPost: true,
- },
- )
+ _, err = insta.sendRequest(
+ &reqOptions{
+ Endpoint: fmt.Sprintf(urlMediaDelete, item.ID),
+ Query: generateSignature(data),
+ IsPost: true,
+ },
+ )
+ return err
+}
+
+// SyncLikers fetch new likers of a media
+//
+// This function updates Item.Likers value
+func (item *Item) SyncLikers() error {
+ resp := respLikers{}
+ insta := item.media.instagram()
+ body, err := insta.sendSimpleRequest(urlMediaLikers, item.ID)
+ if err != nil {
return err
- case *StoryMedia:
- return m.Delete()
}
- return nil
+ err = json.Unmarshal(body, &resp)
+ if err == nil {
+ item.Likers = resp.Users
+ }
+ return err
}
// Unlike mark media item as unliked.
@@ -333,63 +336,68 @@ func (item *Item) Save() error {
// the default value name.
//
// If file exists it will be saved
+// This function makes folder automatically
+//
+// This function returns an slice of location of downloaded items
+// The returned values are the output path of images and videos.
+//
+// This function does not download CarouselMedia.
//
// See example: examples/media/itemDownload.go
-func (item *Item) Download(folder, name string) error {
- imgFolder := fmt.Sprintf("%s%cimages%c", folder, os.PathSeparator, os.PathSeparator)
- vidFolder := fmt.Sprintf("%s%cvideos%c", folder, os.PathSeparator, os.PathSeparator)
+func (item *Item) Download(folder, name string) (imgs, vds string, err error) {
+ var u *neturl.URL
+ var nname string
+ imgFolder := path.Join(folder, "images")
+ vidFolder := path.Join(folder, "videos")
inst := item.media.instagram()
os.MkdirAll(folder, 0777)
os.MkdirAll(imgFolder, 0777)
os.MkdirAll(vidFolder, 0777)
- for _, url := range getBest(item.Images.Versions) {
- var nname string
+ vds = GetBest(item.Videos)
+ if vds != "" {
if name == "" {
- u, err := neturl.Parse(url)
+ u, err = neturl.Parse(vds)
if err != nil {
- return err
+ return
}
- nname = fmt.Sprintf("%s%c%s", imgFolder, os.PathSeparator, path.Base(u.Path))
+ nname = path.Join(vidFolder, path.Base(u.Path))
} else {
- nname = fmt.Sprintf("%s%c%s", imgFolder, os.PathSeparator, nname)
+ nname = path.Join(vidFolder, name)
}
nname = getname(nname)
- err := download(inst, url, nname)
- if err != nil {
- return err
- }
+ vds, err = download(inst, vds, nname)
+ return "", vds, err
}
- for _, url := range getBest(item.Videos) {
- var nname string
+ imgs = GetBest(item.Images.Versions)
+ if imgs != "" {
if name == "" {
- u, err := neturl.Parse(url)
+ u, err = neturl.Parse(imgs)
if err != nil {
- return err
+ return
}
- nname = fmt.Sprintf("%s%c%s", vidFolder, os.PathSeparator, path.Base(u.Path))
+ nname = path.Join(imgFolder, path.Base(u.Path))
} else {
- nname = fmt.Sprintf("%s%c%s", vidFolder, os.PathSeparator, nname)
+ nname = path.Join(imgFolder, name)
}
nname = getname(nname)
- err := download(inst, url, nname)
- if err != nil {
- return err
- }
+ imgs, err = download(inst, imgs, nname)
+ return imgs, "", err
}
- return nil
+
+ return imgs, vds, fmt.Errorf("cannot find any image or video")
}
// TopLikers returns string slice or single string (inside string slice)
// Depending on TopLikers parameter.
func (item *Item) TopLikers() []string {
- switch s := item._TopLikers.(type) {
+ switch s := item.Toplikers.(type) {
case string:
return []string{s}
case []string:
@@ -402,20 +410,41 @@ func (item *Item) TopLikers() []string {
// Depending on PreviewComments parameter.
// If PreviewComments are string or []string only the Text field will be filled.
func (item *Item) PreviewComments() []Comment {
- switch s := item._PreviewComments.(type) {
- case []Comment:
- return s
- case []string:
- comments := make([]Comment, 0)
- for i := range s {
- comments = append(comments, Comment{
- Text: s[i],
- })
+ switch s := item.Previewcomments.(type) {
+ case []interface{}:
+ if len(s) == 0 {
+ return nil
+ }
+
+ switch s[0].(type) {
+ case interface{}:
+ comments := make([]Comment, 0)
+ for i := range s {
+ if buf, err := json.Marshal(s[i]); err != nil {
+ return nil
+ } else {
+ comment := &Comment{}
+
+ if err = json.Unmarshal(buf, comment); err != nil {
+ return nil
+ } else {
+ comments = append(comments, *comment)
+ }
+ }
+ }
+ return comments
+ case string:
+ comments := make([]Comment, 0)
+ for i := range s {
+ comments = append(comments, Comment{
+ Text: s[i].(string),
+ })
+ }
+ return comments
}
- return comments
case string:
comments := []Comment{
- Comment{
+ {
Text: s,
},
}
@@ -424,9 +453,10 @@ func (item *Item) PreviewComments() []Comment {
return nil
}
+//Media interface defines methods for both StoryMedia and FeedMedia.
type Media interface {
// Next allows pagination
- Next() bool
+ Next(...interface{}) bool
// Error returns error (in case it have been occurred)
Error() error
// ID returns media id
@@ -437,6 +467,7 @@ type Media interface {
instagram() *Instagram
}
+//StoryMedia is the struct that handles the information from the methods to get info about Stories.
type StoryMedia struct {
inst *Instagram
endpoint string
@@ -445,10 +476,11 @@ type StoryMedia struct {
err error
Pk interface{} `json:"id"`
- LatestReelMedia int `json:"latest_reel_media"`
+ LatestReelMedia int64 `json:"latest_reel_media"`
ExpiringAt float64 `json:"expiring_at"`
HaveBeenSeen float64 `json:"seen"`
CanReply bool `json:"can_reply"`
+ Title string `json:"title"`
CanReshare bool `json:"can_reshare"`
ReelType string `json:"reel_type"`
User User `json:"user"`
@@ -465,9 +497,25 @@ type StoryMedia struct {
}
// Delete removes instragram story.
-// TODO
+//
+// See example: examples/media/deleteStories.go
func (media *StoryMedia) Delete() error {
- return nil
+ insta := media.inst
+ data, err := insta.prepareData(
+ map[string]interface{}{
+ "media_id": media.ID(),
+ },
+ )
+ if err == nil {
+ _, err = insta.sendRequest(
+ &reqOptions{
+ Endpoint: fmt.Sprintf(urlMediaDelete, media.ID()),
+ Query: generateSignature(data),
+ IsPost: true,
+ },
+ )
+ }
+ return err
}
// ID returns Story id
@@ -491,23 +539,105 @@ func (media *StoryMedia) setValues() {
}
}
-// Error returns error happend any error
+// Error returns error happened any error
func (media StoryMedia) Error() error {
return media.err
}
// Seen marks story as seen.
-// TODO
+/*
func (media *StoryMedia) Seen() error {
- return nil
+ insta := media.inst
+ data, err := insta.prepareData(
+ map[string]interface{}{
+ "container_module": "feed_timeline",
+ "live_vods_skipped": "",
+ "nuxes_skipped": "",
+ "nuxes": "",
+ "reels": "", // TODO xd
+ "live_vods": "",
+ "reel_media_skipped": "",
+ },
+ )
+ if err == nil {
+ _, err = insta.sendRequest(
+ &reqOptions{
+ Endpoint: urlMediaSeen, // reel=1&live_vod=0
+ Query: generateSignature(data),
+ IsPost: true,
+ UseV2: true,
+ },
+ )
+ }
+ return err
+}
+*/
+
+type trayRequest struct {
+ Name string `json:"name"`
+ Value string `json:"value"`
+}
+
+// Sync function is used when Highlight must be sync.
+// Highlight must be sync when User.Highlights does not return any object inside StoryMedia slice.
+//
+// This function does NOT update Stories items.
+//
+// This function updates StoryMedia.Items
+func (media *StoryMedia) Sync() error {
+ insta := media.inst
+ query := []trayRequest{
+ {"SUPPORTED_SDK_VERSIONS", "9.0,10.0,11.0,12.0,13.0,14.0,15.0,16.0,17.0,18.0,19.0,20.0,21.0,22.0,23.0,24.0"},
+ {"FACE_TRACKER_VERSION", "10"},
+ {"segmentation", "segmentation_enabled"},
+ {"COMPRESSION", "ETC2_COMPRESSION"},
+ }
+ qjson, err := json.Marshal(query)
+ if err != nil {
+ return err
+ }
+
+ id := media.Pk.(string)
+ data, err := insta.prepareData(
+ map[string]interface{}{
+ "user_ids": []string{id},
+ "supported_capabilities_new": b2s(qjson),
+ },
+ )
+ if err != nil {
+ return err
+ }
+
+ body, err := insta.sendRequest(
+ &reqOptions{
+ Endpoint: urlReelMedia,
+ Query: generateSignature(data),
+ IsPost: true,
+ },
+ )
+ if err == nil {
+ resp := trayResp{}
+ err = json.Unmarshal(body, &resp)
+ if err == nil {
+ m, ok := resp.Reels[id]
+ if ok {
+ media.Items = m.Items
+ media.setValues()
+ return nil
+ }
+ err = fmt.Errorf("cannot find %s structure in response", id)
+ }
+ }
+ return err
}
// Next allows pagination after calling:
// User.Stories
//
+//
// returns false when list reach the end
// if StoryMedia.Error() is ErrNoMore no problem have been occurred.
-func (media *StoryMedia) Next() bool {
+func (media *StoryMedia) Next(params ...interface{}) bool {
if media.err != nil {
return false
}
@@ -527,7 +657,7 @@ func (media *StoryMedia) Next() bool {
*media = m
media.inst = insta
media.endpoint = endpoint
- media.err = ErrNoMore
+ media.err = ErrNoMore // TODO: See if stories has pagination
media.setValues()
return true
}
@@ -552,7 +682,7 @@ type FeedMedia struct {
AutoLoadMoreEnabled bool `json:"auto_load_more_enabled"`
Status string `json:"status"`
// Can be int64 and string
- // this is why we recomend Next() usage :')
+ // this is why we recommend Next() usage :')
NextID interface{} `json:"next_max_id"`
}
@@ -570,6 +700,11 @@ func (media *FeedMedia) instagram() *Instagram {
return media.inst
}
+// SetInstagram set instagram
+func (media *FeedMedia) SetInstagram(inst *Instagram) {
+ media.inst = inst
+}
+
// SetID sets media ID
// this value can be int64 or string
func (media *FeedMedia) SetID(id interface{}) {
@@ -594,7 +729,7 @@ func (media *FeedMedia) Sync() error {
&reqOptions{
Endpoint: fmt.Sprintf(urlMediaInfo, id),
Query: generateSignature(data),
- IsPost: true,
+ IsPost: false,
},
)
if err != nil {
@@ -628,16 +763,18 @@ func (media *FeedMedia) ID() string {
return s
case int64:
return strconv.FormatInt(s, 10)
+ case json.Number:
+ return string(s)
}
return ""
}
// Next allows pagination after calling:
// User.Feed
-//
+// Params: ranked_content is set to "true" by default, you can set it to false by either passing "false" or false as parameter.
// returns false when list reach the end.
// if FeedMedia.Error() is ErrNoMore no problem have been occurred.
-func (media *FeedMedia) Next() bool {
+func (media *FeedMedia) Next(params ...interface{}) bool {
if media.err != nil {
return false
}
@@ -645,11 +782,24 @@ func (media *FeedMedia) Next() bool {
insta := media.inst
endpoint := media.endpoint
next := media.ID()
+ ranked := "true"
if media.uid != 0 {
endpoint = fmt.Sprintf(endpoint, media.uid)
}
+ for _, param := range params {
+ switch s := param.(type) {
+ case string:
+ if _, err := strconv.ParseBool(s); err == nil {
+ ranked = s
+ }
+ case bool:
+ if !s {
+ ranked = "false"
+ }
+ }
+ }
body, err := insta.sendRequest(
&reqOptions{
Endpoint: endpoint,
@@ -657,13 +807,15 @@ func (media *FeedMedia) Next() bool {
"max_id": next,
"rank_token": insta.rankToken,
"min_timestamp": media.timestamp,
- "ranked_content": "true",
+ "ranked_content": ranked,
},
},
)
if err == nil {
m := FeedMedia{}
- err = json.Unmarshal(body, &m)
+ d := json.NewDecoder(bytes.NewReader(body))
+ d.UseNumber()
+ err = d.Decode(&m)
if err == nil {
*media = m
media.inst = insta
@@ -677,3 +829,131 @@ func (media *FeedMedia) Next() bool {
}
return false
}
+
+// UploadPhoto post image from io.Reader to instagram.
+func (insta *Instagram) UploadPhoto(photo io.Reader, photoCaption string, quality int, filterType int) (Item, error) {
+ out := Item{}
+
+ uploadID := time.Now().Unix()
+
+ photoName := fmt.Sprintf("pending_media_%d.jpg", uploadID)
+ var b bytes.Buffer
+ w := multipart.NewWriter(&b)
+
+ w.WriteField("upload_id", strconv.FormatInt(uploadID, 10))
+ w.WriteField("_uuid", insta.uuid)
+ w.WriteField("_csrftoken", insta.token)
+
+ var compression = map[string]interface{}{
+ "lib_name": "jt",
+ "lib_version": "1.3.0",
+ "quality": quality,
+ }
+ cBytes, _ := json.Marshal(compression)
+ w.WriteField("image_compression", toString(cBytes))
+
+ fw, err := w.CreateFormFile("photo", photoName)
+ if err != nil {
+ return out, err
+ }
+
+ var buf bytes.Buffer
+ rdr := io.TeeReader(photo, &buf)
+ if _, err = io.Copy(fw, rdr); err != nil {
+ return out, err
+ }
+ if err := w.Close(); err != nil {
+ return out, err
+ }
+ req, err := http.NewRequest("POST", goInstaAPIUrl+"upload/photo/", &b)
+ if err != nil {
+ return out, err
+ }
+ req.Header.Set("X-IG-Capabilities", "3Q4=")
+ req.Header.Set("X-IG-Connection-Type", "WIFI")
+ req.Header.Set("Cookie2", "$Version=1")
+ req.Header.Set("Accept-Language", "en-US")
+ req.Header.Set("Accept-Encoding", "gzip, deflate")
+ req.Header.Set("Content-type", w.FormDataContentType())
+ req.Header.Set("Connection", "close")
+ req.Header.Set("User-Agent", goInstaUserAgent)
+
+ resp, err := insta.c.Do(req)
+ if err != nil {
+ return out, err
+ }
+ defer resp.Body.Close()
+
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return out, err
+ }
+ if resp.StatusCode != 200 {
+ return out, fmt.Errorf("invalid status code, result: %s", resp.Status)
+ }
+
+ var result struct {
+ UploadID string `json:"upload_id"`
+ XsharingNonces interface{} `json:"xsharing_nonces"`
+ Status string `json:"status"`
+ }
+ err = json.Unmarshal(body, &result)
+ if err != nil {
+ return out, err
+ }
+
+ if result.Status != "ok" {
+ return out, fmt.Errorf("unknown error, status: %s", result.Status)
+ }
+
+ width, height, err := getImageDimensionFromReader(&buf)
+ if err != nil {
+ return out, err
+ }
+
+ config := map[string]interface{}{
+ "media_folder": "Instagram",
+ "source_type": 4,
+ "caption": photoCaption,
+ "upload_id": strconv.FormatInt(uploadID, 10),
+ "device": goInstaDeviceSettings,
+ "edits": map[string]interface{}{
+ "crop_original_size": []int{width * 1.0, height * 1.0},
+ "crop_center": []float32{0.0, 0.0},
+ "crop_zoom": 1.0,
+ "filter_type": filterType,
+ },
+ "extra": map[string]interface{}{
+ "source_width": width,
+ "source_height": height,
+ },
+ }
+ data, err := insta.prepareData(config)
+ if err != nil {
+ return out, err
+ }
+
+ body, err = insta.sendRequest(&reqOptions{
+ Endpoint: "media/configure/?",
+ Query: generateSignature(data),
+ IsPost: true,
+ })
+ if err != nil {
+ return out, err
+ }
+ var uploadResult struct {
+ Media Item `json:"media"`
+ UploadID string `json:"upload_id"`
+ Status string `json:"status"`
+ }
+ err = json.Unmarshal(body, &uploadResult)
+ if err != nil {
+ return out, err
+ }
+
+ if uploadResult.Status != "ok" {
+ return out, fmt.Errorf("invalid status, result: %s", resp.Status)
+ }
+
+ return uploadResult.Media, nil
+}
diff --git a/profiles.go b/profiles.go
index 84de3db3..53f823d0 100644
--- a/profiles.go
+++ b/profiles.go
@@ -41,7 +41,7 @@ func (prof *Profiles) ByID(id int64) (*User, error) {
body, err := prof.inst.sendRequest(
&reqOptions{
- Endpoint: fmt.Sprintf(urlUserById, id),
+ Endpoint: fmt.Sprintf(urlUserByID, id),
Query: generateSignature(data),
},
)
diff --git a/request.go b/request.go
index 4db0e40f..e9b19bcf 100644
--- a/request.go
+++ b/request.go
@@ -5,20 +5,32 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
+ "math/rand"
"net/http"
"net/url"
+ "strconv"
"strings"
+ "time"
)
type reqOptions struct {
+ // Connection is connection header. Default is "close".
+ Connection string
+
+ // Login process
+ Login bool
+
// Endpoint is the request path of instagram api
Endpoint string
- // IsPost setted to true will send request with POST method.
+ // IsPost set to true will send request with POST method.
//
// By default this option is false.
IsPost bool
+ // UseV2 is set when API endpoint uses v2 url.
+ UseV2 bool
+
// Query is the parameters of the request
//
// This parameters are independents of the request method (POST|GET)
@@ -33,28 +45,40 @@ func (insta *Instagram) sendSimpleRequest(uri string, a ...interface{}) (body []
)
}
-func (inst *Instagram) sendRequest(o *reqOptions) (body []byte, err error) {
+func (insta *Instagram) sendRequest(o *reqOptions) (body []byte, err error) {
method := "GET"
if o.IsPost {
method = "POST"
}
+ if o.Connection == "" {
+ o.Connection = "keep-alive"
+ }
- u, err := url.Parse(goInstaAPIUrl + o.Endpoint)
+ nu := goInstaAPIUrl
+ if o.UseV2 {
+ nu = goInstaAPIUrlv2
+ }
+
+ u, err := url.Parse(nu + o.Endpoint)
if err != nil {
return nil, err
}
+ vs := url.Values{}
bf := bytes.NewBuffer([]byte{})
- q := u.Query()
for k, v := range o.Query {
- q.Add(k, v)
+ vs.Add(k, v)
}
if o.IsPost {
- bf.WriteString(q.Encode())
+ bf.WriteString(vs.Encode())
} else {
- u.RawQuery = q.Encode()
+ for k, v := range u.Query() {
+ vs.Add(k, strings.Join(v, " "))
+ }
+
+ u.RawQuery = vs.Encode()
}
var req *http.Request
@@ -63,51 +87,72 @@ func (inst *Instagram) sendRequest(o *reqOptions) (body []byte, err error) {
return
}
- req.Header.Set("Connection", "close")
- req.Header.Set("Accept", "*/*")
- req.Header.Set("Content-type", "application/x-www-form-urlencoded; charset=UTF-8")
- req.Header.Set("Cookie2", "$Version=1")
+ req.Header.Set("Connection", o.Connection)
+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
req.Header.Set("Accept-Language", "en-US")
req.Header.Set("User-Agent", goInstaUserAgent)
-
- resp, err := inst.c.Do(req)
+ req.Header.Set("X-IG-App-ID", fbAnalytics)
+ req.Header.Set("X-IG-Capabilities", igCapabilities)
+ req.Header.Set("X-IG-Connection-Type", connType)
+ req.Header.Set("X-IG-Connection-Speed", fmt.Sprintf("%dkbps", acquireRand(1000, 3700)))
+ req.Header.Set("X-IG-Bandwidth-Speed-KBPS", "-1.000")
+ req.Header.Set("X-IG-Bandwidth-TotalBytes-B", "0")
+ req.Header.Set("X-IG-Bandwidth-TotalTime-MS", "0")
+
+ resp, err := insta.c.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
u, _ = url.Parse(goInstaAPIUrl)
- for _, value := range inst.c.Jar.Cookies(u) {
+ for _, value := range insta.c.Jar.Cookies(u) {
if strings.Contains(value.Name, "csrftoken") {
- inst.token = value.Value
+ insta.token = value.Value
}
}
body, err = ioutil.ReadAll(resp.Body)
- if err != nil {
- return
+ if err == nil {
+ err = isError(resp.StatusCode, body)
}
+ return body, err
+}
- switch resp.StatusCode {
+func isError(code int, body []byte) (err error) {
+ switch code {
case 200:
+ case 503:
+ return Error503{
+ Message: "Instagram API error. Try it later.",
+ }
+ case 400:
+ ierr := Error400{}
+ err = json.Unmarshal(body, &ierr)
+ if err == nil && ierr.Payload.Message != "" {
+ return ierr
+ }
+ fallthrough
default:
- ierr := instaError{}
+ ierr := ErrorN{}
err = json.Unmarshal(body, &ierr)
if err != nil {
- return nil, fmt.Errorf("Invalid status code: %d", resp.StatusCode)
+ return fmt.Errorf("Invalid status code: %d: %s", code, body)
}
- return nil, instaToErr(ierr)
+ return ierr
}
-
- return body, err
+ return nil
}
func (insta *Instagram) prepareData(other ...map[string]interface{}) (string, error) {
data := map[string]interface{}{
"_uuid": insta.uuid,
- "_uid": insta.Account.ID,
"_csrftoken": insta.token,
}
+ if insta.Account != nil && insta.Account.ID != 0 {
+ data["_uid"] = strconv.FormatInt(insta.Account.ID, 10)
+ }
+
for i := range other {
for key, value := range other[i] {
data[key] = value
@@ -119,3 +164,21 @@ func (insta *Instagram) prepareData(other ...map[string]interface{}) (string, er
}
return "", err
}
+
+func (insta *Instagram) prepareDataQuery(other ...map[string]interface{}) map[string]string {
+ data := map[string]string{
+ "_uuid": insta.uuid,
+ "_csrftoken": insta.token,
+ }
+ for i := range other {
+ for key, value := range other[i] {
+ data[key] = toString(value)
+ }
+ }
+ return data
+}
+
+func acquireRand(min, max int) int {
+ rand.Seed(time.Now().Unix())
+ return rand.Intn(max-min) + min
+}
diff --git a/search.go b/search.go
index 56c77785..2203a9ba 100644
--- a/search.go
+++ b/search.go
@@ -2,20 +2,21 @@ package goinsta
import (
"encoding/json"
- "fmt"
"strconv"
"time"
)
+// Search is the object for all searches like Facebook, Location or Tag search.
type Search struct {
inst *Instagram
}
+// SearchResult handles the data for the results given by each type of Search.
type SearchResult struct {
HasMore bool `json:"has_more"`
RankToken string `json:"rank_token"`
Status string `json:"status"`
- NumResults int `json:"num_results"`
+ NumResults int64 `json:"num_results"`
// User search results
Users []User `json:"users"`
@@ -165,39 +166,3 @@ func (search *Search) Facebook(user string) (*SearchResult, error) {
err = json.Unmarshal(body, res)
return res, err
}
-
-// FeedTags search by Tag in user Feed
-//
-// (sorry for returning FeedTag. See #FeedTag)
-func (search *Search) FeedTags(tag string) (*FeedTag, error) {
- insta := search.inst
- body, err := insta.sendRequest(
- &reqOptions{
- Endpoint: fmt.Sprintf(urlSearchFeedTag, tag),
- Query: map[string]string{
- "rank_token": insta.rankToken,
- "ranked_content": "true",
- },
- },
- )
- if err != nil {
- return nil, err
- }
- res := &FeedTag{}
- err = json.Unmarshal(body, res)
- return res, err
-}
-
-// Instagram's database is f*cking shit.
-// We all hate nodejs (seems that they uses nodejs and mongoldb)
-// I don't know why FeedTags returns this aberration structure.
-type FeedTag struct {
- RankedItems []Item `json:"ranked_items"`
- Images []Item `json:"items"`
- NumResults int `json:"num_results"`
- NextID string `json:"next_max_id"`
- MoreAvailable bool `json:"more_available"`
- AutoLoadMoreEnabled bool `json:"auto_load_more_enabled"`
- Story StoryMedia `json:"story"`
- Status string `json:"status"`
-}
diff --git a/timeline.go b/timeline.go
index 36a3cc80..e2ecc012 100644
--- a/timeline.go
+++ b/timeline.go
@@ -4,6 +4,7 @@ import (
"encoding/json"
)
+// Timeline is the object to represent the main feed on instagram, the first page that shows the latest feeds of my following contacts.
type Timeline struct {
inst *Instagram
}
diff --git a/types.go b/types.go
index 5a03cd1a..d731b508 100644
--- a/types.go
+++ b/types.go
@@ -1,12 +1,14 @@
package goinsta
import (
+ "encoding/json"
"fmt"
"net/http"
- "strconv"
)
+// ConfigFile is a structure to store the session information so that can be exported or imported.
type ConfigFile struct {
+ ID int64 `json:"id"`
User string `json:"username"`
DeviceID string `json:"device_id"`
UUID string `json:"uuid"`
@@ -27,21 +29,47 @@ type PicURLInfo struct {
Width int `json:"width"`
}
-type instaError struct {
+// ErrorN is general instagram error
+type ErrorN struct {
Message string `json:"message"`
Status string `json:"status"`
ErrorType string `json:"error_type"`
}
-func instaToErr(ierr instaError) error {
- return fmt.Errorf("%s: %s (%s)", ierr.Status, ierr.Message, ierr.ErrorType)
+// Error503 is instagram API error
+type Error503 struct {
+ Message string
}
+func (e Error503) Error() string {
+ return e.Message
+}
+
+func (e ErrorN) Error() string {
+ return fmt.Sprintf("%s: %s (%s)", e.Status, e.Message, e.ErrorType)
+}
+
+// Error400 is error returned by HTTP 400 status code.
+type Error400 struct {
+ Action string `json:"action"`
+ StatusCode string `json:"status_code"`
+ Payload struct {
+ ClientContext string `json:"client_context"`
+ Message string `json:"message"`
+ } `json:"payload"`
+ Status string `json:"status"`
+}
+
+func (e Error400) Error() string {
+ return fmt.Sprintf("%s: %s", e.Status, e.Payload.Message)
+}
+
+// Nametag is part of the account information.
type Nametag struct {
- Mode int `json:"mode"`
- Gradient int `json:"gradient"`
- Emoji string `json:"emoji"`
- SelfieSticker int `json:"selfie_sticker"`
+ Mode int64 `json:"mode"`
+ Gradient json.Number `json:"gradient,Number"`
+ Emoji string `json:"emoji"`
+ SelfieSticker json.Number `json:"selfie_sticker,Number"`
}
type friendResp struct {
@@ -49,8 +77,9 @@ type friendResp struct {
Friendship Friendship `json:"friendship_status"`
}
+// Location stores media location information.
type Location struct {
- Pk int `json:"pk"`
+ Pk int64 `json:"pk"`
Name string `json:"name"`
Address string `json:"address"`
City string `json:"city"`
@@ -61,6 +90,7 @@ type Location struct {
FacebookPlacesID int64 `json:"facebook_places_id"`
}
+// SuggestedUsers stores the information about user suggestions.
type SuggestedUsers struct {
Type int `json:"type"`
Suggestions []struct {
@@ -87,6 +117,7 @@ type SuggestedUsers struct {
TrackingToken string `json:"tracking_token"`
}
+// Friendship stores the details of the relationship between two users.
type Friendship struct {
IncomingRequest bool `json:"incoming_request"`
FollowedBy bool `json:"followed_by"`
@@ -98,6 +129,7 @@ type Friendship struct {
IsMutingReel bool `json:"is_muting_reel"`
}
+// SavedMedia stores the information about media being saved before in my account.
type SavedMedia struct {
Items []struct {
Media Item `json:"media"`
@@ -113,44 +145,27 @@ type Images struct {
Versions []Candidate `json:"candidates"`
}
+// GetBest returns the URL of the image with the best quality.
+func (img Images) GetBest() string {
+ best := ""
+ var mh, mw int
+ for _, v := range img.Versions {
+ if v.Width > mw || v.Height > mh {
+ best = v.URL
+ mh, mw = v.Height, v.Width
+ }
+ }
+ return best
+}
+
+// Candidate is something that I really have no idea what it is.
type Candidate struct {
Width int `json:"width"`
Height int `json:"height"`
URL string `json:"url"`
}
-type Comment struct {
- ID int64 `json:"pk"`
- idstr string `json:"-"`
- Text string `json:"text"`
- Type int `json:"type"`
- User User `json:"user"`
- UserID int64 `json:"user_id"`
- BitFlags int `json:"bit_flags"`
- ChildCommentCount int `json:"child_comment_count"`
- CommentIndex int `json:"comment_index"`
- CommentLikeCount int `json:"comment_like_count"`
- ContentType string `json:"content_type"`
- CreatedAt int `json:"created_at"`
- CreatedAtUtc int `json:"created_at_utc"`
- DidReportAsSpam bool `json:"did_report_as_spam"`
- HasLikedComment bool `json:"has_liked_comment"`
- InlineComposerDisplayCondition string `json:"inline_composer_display_condition"`
- OtherPreviewUsers []User `json:"other_preview_users"`
- PreviewChildComments []Comment `json:"preview_child_comments"`
- Status string `json:"status"`
-}
-
-func (c Comment) getid() string {
- switch {
- case c.ID == 0:
- return c.idstr
- case c.idstr == "":
- return strconv.FormatInt(c.ID, 10)
- }
- return ""
-}
-
+// Tag is the information of an user being tagged on any media.
type Tag struct {
In []struct {
User User `json:"user"`
@@ -163,11 +178,11 @@ type Tag struct {
// Caption is media caption
type Caption struct {
ID int64 `json:"pk"`
- UserID int `json:"user_id"`
+ UserID int64 `json:"user_id"`
Text string `json:"text"`
Type int `json:"type"`
- CreatedAt int `json:"created_at"`
- CreatedAtUtc int `json:"created_at_utc"`
+ CreatedAt int64 `json:"created_at"`
+ CreatedAtUtc int64 `json:"created_at_utc"`
ContentType string `json:"content_type"`
Status string `json:"status"`
BitFlags int `json:"bit_flags"`
@@ -177,10 +192,11 @@ type Caption struct {
HasTranslation bool `json:"has_translation"`
}
+// Mentions is a user being mentioned on media.
type Mentions struct {
X float64 `json:"x"`
Y float64 `json:"y"`
- Z int `json:"z"`
+ Z int64 `json:"z"`
Width float64 `json:"width"`
Height float64 `json:"height"`
Rotation float64 `json:"rotation"`
@@ -202,6 +218,11 @@ type timeStoryResp struct {
Media []StoryMedia `json:"tray"`
}
+type trayResp struct {
+ Reels map[string]StoryMedia `json:"reels"`
+ Status string `json:"status"`
+}
+
// Tray is a set of story media received from timeline calls.
type Tray struct {
Stories []StoryMedia `json:"tray"`
@@ -237,9 +258,9 @@ type LiveItems struct {
ID string `json:"pk"`
User User `json:"user"`
Broadcasts []Broadcast `json:"broadcasts"`
- LastSeenBroadcastTs int `json:"last_seen_broadcast_ts"`
- RankedPosition int `json:"ranked_position"`
- SeenRankedPosition int `json:"seen_ranked_position"`
+ LastSeenBroadcastTs float64 `json:"last_seen_broadcast_ts"`
+ RankedPosition int64 `json:"ranked_position"`
+ SeenRankedPosition int64 `json:"seen_ranked_position"`
Muted bool `json:"muted"`
CanReply bool `json:"can_reply"`
CanReshare bool `json:"can_reshare"`
@@ -250,24 +271,26 @@ type Broadcast struct {
ID int64 `json:"id"`
BroadcastStatus string `json:"broadcast_status"`
DashManifest string `json:"dash_manifest"`
- ExpireAt int `json:"expire_at"`
+ 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"`
BroadcastOwner User `json:"broadcast_owner"`
- PublishedTime int `json:"published_time"`
+ PublishedTime int64 `json:"published_time"`
MediaID string `json:"media_id"`
BroadcastMessage string `json:"broadcast_message"`
OrganicTrackingToken string `json:"organic_tracking_token"`
}
+// BlockedUser stores information about a used that has been blocked before.
type BlockedUser struct {
+ // TODO: Convert to user
UserID int64 `json:"user_id"`
Username string `json:"username"`
FullName string `json:"full_name"`
ProfilePicURL string `json:"profile_pic_url"`
- BlockAt int `json:"block_at"`
+ BlockAt int64 `json:"block_at"`
}
// Unblock unblocks blocked user.
@@ -293,7 +316,7 @@ type InboxItemMedia struct {
ItemID string `json:"item_id"`
ItemType string `json:"item_type"`
RavenMedia struct {
- MediaType int `json:"media_type"`
+ MediaType int64 `json:"media_type"`
} `json:"raven_media"`
ReplyChainCount int `json:"reply_chain_count"`
SeenUserIds []interface{} `json:"seen_user_ids"`
@@ -302,6 +325,7 @@ type InboxItemMedia struct {
ViewMode string `json:"view_mode"`
}
+//InboxItemLike is the heart sent during a conversation.
type InboxItemLike struct {
ItemID string `json:"item_id"`
ItemType string `json:"item_type"`
@@ -309,6 +333,12 @@ type InboxItemLike struct {
UserID int64 `json:"user_id"`
}
+type respLikers struct {
+ Users []User `json:"users"`
+ UserCount int64 `json:"user_count"`
+ Status string `json:"status"`
+}
+
type threadResp struct {
Conversation Conversation `json:"thread"`
Status string `json:"status"`
diff --git a/users.go b/users.go
index 9e60dd13..580aec8f 100644
--- a/users.go
+++ b/users.go
@@ -6,6 +6,7 @@ import (
"fmt"
)
+// Users is a struct that stores many user's returned by many different methods.
type Users struct {
inst *Instagram
@@ -34,6 +35,7 @@ func (users *Users) SetInstagram(inst *Instagram) {
users.inst = inst
}
+// ErrNoMore is an error that comes when there is no more elements available on the list.
var ErrNoMore = errors.New("List end have been reached")
// Next allows to paginate after calling:
@@ -78,6 +80,11 @@ func (users *Users) Next() bool {
return false
}
+// Error returns users error
+func (users *Users) Error() error {
+ return users.err
+}
+
func (users *Users) setValues() {
for i := range users.Users {
users.Users[i].inst = users.inst
@@ -93,29 +100,31 @@ type userResp struct {
type User struct {
inst *Instagram
- ID int64 `json:"pk"`
- Username string `json:"username"`
- FullName string `json:"full_name"`
- Biography string `json:"biography"`
- ProfilePicURL string `json:"profile_pic_url"`
- Email string `json:"email"`
- PhoneNumber string `json:"phone_number"`
- IsBusiness bool `json:"is_business"`
- Gender int `json:"gender"`
- ProfilePicID string `json:"profile_pic_id"`
- HasAnonymousProfilePicture bool `json:"has_anonymous_profile_picture"`
- IsPrivate bool `json:"is_private"`
- IsUnpublished bool `json:"is_unpublished"`
- AllowedCommenterType string `json:"allowed_commenter_type"`
- IsVerified bool `json:"is_verified"`
- MediaCount int `json:"media_count"`
- FollowerCount int `json:"follower_count"`
- FollowingCount int `json:"following_count"`
- FollowingTagCount int `json:"following_tag_count"`
- GeoMediaCount int `json:"geo_media_count"`
- ExternalURL string `json:"external_url"`
- HasBiographyTranslation bool `json:"has_biography_translation"`
- ExternalLynxURL string `json:"external_lynx_url"`
+ ID int64 `json:"pk"`
+ Username string `json:"username"`
+ FullName string `json:"full_name"`
+ Biography string `json:"biography"`
+ ProfilePicURL string `json:"profile_pic_url"`
+ Email string `json:"email"`
+ PhoneNumber string `json:"phone_number"`
+ IsBusiness bool `json:"is_business"`
+ Gender int `json:"gender"`
+ ProfilePicID string `json:"profile_pic_id"`
+ HasAnonymousProfilePicture bool `json:"has_anonymous_profile_picture"`
+ IsPrivate bool `json:"is_private"`
+ IsUnpublished bool `json:"is_unpublished"`
+ AllowedCommenterType string `json:"allowed_commenter_type"`
+ IsVerified bool `json:"is_verified"`
+ MediaCount int `json:"media_count"`
+ FollowerCount int `json:"follower_count"`
+ FollowingCount int `json:"following_count"`
+ FollowingTagCount int `json:"following_tag_count"`
+ MutualFollowersID []int64 `json:"profile_context_mutual_follow_ids"`
+ ProfileContext string `json:"profile_context"`
+ GeoMediaCount int `json:"geo_media_count"`
+ ExternalURL string `json:"external_url"`
+ HasBiographyTranslation bool `json:"has_biography_translation"`
+ ExternalLynxURL string `json:"external_lynx_url"`
BiographyWithEntities struct {
RawText string `json:"raw_text"`
Entities []interface{} `json:"entities"`
@@ -150,15 +159,30 @@ type User struct {
SocialContext string `json:"social_context,omitempty"`
SearchSocialContext string `json:"search_social_context,omitempty"`
MutualFollowersCount float64 `json:"mutual_followers_count"`
- LatestReelMedia int `json:"latest_reel_media,omitempty"`
+ LatestReelMedia int64 `json:"latest_reel_media,omitempty"`
IsCallToActionEnabled bool `json:"is_call_to_action_enabled"`
FbPageCallToActionID string `json:"fb_page_call_to_action_id"`
Zip string `json:"zip"`
Friendship Friendship `json:"friendship_status"`
}
+// SetInstagram will update instagram instance for selected User.
+func (user *User) SetInstagram(insta *Instagram) {
+ user.inst = insta
+}
+
+// NewUser returns prepared user to be used with his functions.
+func (inst *Instagram) NewUser() *User {
+ return &User{inst: inst}
+}
+
// Sync updates user info
-func (user *User) Sync() error {
+//
+// params can be:
+// bool: must be true if you want to include FriendShip call. See goinsta.FriendShip
+//
+// See example: examples/user/friendship.go
+func (user *User) Sync(params ...interface{}) error {
insta := user.inst
body, err := insta.sendSimpleRequest(urlUserInfo, user.ID)
if err == nil {
@@ -167,6 +191,14 @@ func (user *User) Sync() error {
if err == nil {
*user = resp.User
user.inst = insta
+ for _, param := range params {
+ switch b := param.(type) {
+ case bool:
+ if b {
+ err = user.FriendShip()
+ }
+ }
+ }
}
}
return err
@@ -208,21 +240,27 @@ func (user *User) Block() error {
"user_id": user.ID,
},
)
- if err == nil {
- body, err := insta.sendRequest(
- &reqOptions{
- Endpoint: fmt.Sprintf(urlUserBlock, user.ID),
- Query: generateSignature(data),
- IsPost: true,
- },
- )
- if err == nil {
- resp := friendResp{}
- err = json.Unmarshal(body, &resp)
- user.Friendship = resp.Friendship
- }
+ if err != nil {
+ return err
}
- return err
+ body, err := insta.sendRequest(
+ &reqOptions{
+ Endpoint: fmt.Sprintf(urlUserBlock, user.ID),
+ Query: generateSignature(data),
+ IsPost: true,
+ },
+ )
+ if err != nil {
+ return err
+ }
+ resp := friendResp{}
+ err = json.Unmarshal(body, &resp)
+ user.Friendship = resp.Friendship
+ if err != nil {
+ return err
+ }
+
+ return nil
}
// Unblock unblocks user
@@ -237,21 +275,27 @@ func (user *User) Unblock() error {
"user_id": user.ID,
},
)
- if err == nil {
- body, err := insta.sendRequest(
- &reqOptions{
- Endpoint: fmt.Sprintf(urlUserUnblock, user.ID),
- Query: generateSignature(data),
- IsPost: true,
- },
- )
- if err == nil {
- resp := friendResp{}
- err = json.Unmarshal(body, &resp)
- user.Friendship = resp.Friendship
- }
+ if err != nil {
+ return err
}
- return err
+ body, err := insta.sendRequest(
+ &reqOptions{
+ Endpoint: fmt.Sprintf(urlUserUnblock, user.ID),
+ Query: generateSignature(data),
+ IsPost: true,
+ },
+ )
+ if err != nil {
+ return err
+ }
+ resp := friendResp{}
+ err = json.Unmarshal(body, &resp)
+ user.Friendship = resp.Friendship
+ if err != nil {
+ return err
+ }
+
+ return nil
}
// Follow started following some user
@@ -269,21 +313,27 @@ func (user *User) Follow() error {
"user_id": user.ID,
},
)
- if err == nil {
- body, err := insta.sendRequest(
- &reqOptions{
- Endpoint: fmt.Sprintf(urlUserFollow, user.ID),
- Query: generateSignature(data),
- IsPost: true,
- },
- )
- if err == nil {
- resp := friendResp{}
- err = json.Unmarshal(body, &resp)
- user.Friendship = resp.Friendship
- }
+ if err != nil {
+ return err
}
- return err
+ body, err := insta.sendRequest(
+ &reqOptions{
+ Endpoint: fmt.Sprintf(urlUserFollow, user.ID),
+ Query: generateSignature(data),
+ IsPost: true,
+ },
+ )
+ if err != nil {
+ return err
+ }
+ resp := friendResp{}
+ err = json.Unmarshal(body, &resp)
+ user.Friendship = resp.Friendship
+ if err != nil {
+ return err
+ }
+
+ return nil
}
// Unfollow unfollows user
@@ -298,21 +348,27 @@ func (user *User) Unfollow() error {
"user_id": user.ID,
},
)
- if err == nil {
- body, err := insta.sendRequest(
- &reqOptions{
- Endpoint: fmt.Sprintf(urlUserUnfollow, user.ID),
- Query: generateSignature(data),
- IsPost: true,
- },
- )
- if err == nil {
- resp := friendResp{}
- err = json.Unmarshal(body, &resp)
- user.Friendship = resp.Friendship
- }
+ if err != nil {
+ return err
}
- return err
+ body, err := insta.sendRequest(
+ &reqOptions{
+ Endpoint: fmt.Sprintf(urlUserUnfollow, user.ID),
+ Query: generateSignature(data),
+ IsPost: true,
+ },
+ )
+ if err != nil {
+ return err
+ }
+ resp := friendResp{}
+ err = json.Unmarshal(body, &resp)
+ user.Friendship = resp.Friendship
+ if err != nil {
+ return err
+ }
+
+ return nil
}
// FriendShip allows user to get friend relationship.
@@ -344,20 +400,27 @@ func (user *User) FriendShip() error {
// Feed returns user feeds (media)
//
-// minTime is the minimum timestamp of media.
+// params can be:
+// string: timestamp of the minimum media timestamp.
//
// For pagination use FeedMedia.Next()
//
// See example: examples/user/feed.go
-func (user *User) Feed(minTime []byte) *FeedMedia {
+func (user *User) Feed(params ...interface{}) *FeedMedia {
insta := user.inst
- timestamp := b2s(minTime)
media := &FeedMedia{}
- media.timestamp = timestamp
media.inst = insta
media.endpoint = urlUserFeed
media.uid = user.ID
+
+ for _, param := range params {
+ switch s := param.(type) {
+ case string:
+ media.timestamp = s
+ }
+ }
+
return media
}
@@ -374,6 +437,45 @@ func (user *User) Stories() *StoryMedia {
return media
}
+// Highlights represents saved stories.
+//
+// See example: examples/user/highlights.go
+func (user *User) Highlights() ([]StoryMedia, error) {
+ query := []trayRequest{
+ {"SUPPORTED_SDK_VERSIONS", "9.0,10.0,11.0,12.0,13.0,14.0,15.0,16.0,17.0,18.0,19.0,20.0,21.0,22.0,23.0,24.0"},
+ {"FACE_TRACKER_VERSION", "10"},
+ {"segmentation", "segmentation_enabled"},
+ {"COMPRESSION", "ETC2_COMPRESSION"},
+ }
+ data, err := json.Marshal(query)
+ if err != nil {
+ return nil, err
+ }
+ body, err := user.inst.sendRequest(
+ &reqOptions{
+ Endpoint: fmt.Sprintf(urlUserHighlights, user.ID),
+ Query: generateSignature(b2s(data)),
+ },
+ )
+ if err == nil {
+ tray := &Tray{}
+ err = json.Unmarshal(body, &tray)
+ if err == nil {
+ tray.set(user.inst, "")
+ for i := range tray.Stories {
+ if len(tray.Stories[i].Items) == 0 {
+ err = tray.Stories[i].Sync()
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+ return tray.Stories, nil
+ }
+ }
+ return nil, err
+}
+
// Tags returns media where user is tagged in
//
// For pagination use FeedMedia.Next()
diff --git a/utils.go b/utils.go
index b6e150b9..8ae39942 100644
--- a/utils.go
+++ b/utils.go
@@ -1,9 +1,81 @@
package goinsta
import (
+ "encoding/json"
+ "image"
+ // Required for getImageDimensionFromReader in jpg and png format
+ _ "image/jpeg"
+ _ "image/png"
+ "io"
+ "strconv"
"unsafe"
)
func b2s(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
+
+func toString(i interface{}) string {
+ switch s := i.(type) {
+ case string:
+ return s
+ case bool:
+ return strconv.FormatBool(s)
+ case float64:
+ return strconv.FormatFloat(s, 'f', -1, 64)
+ case float32:
+ return strconv.FormatFloat(float64(s), 'f', -1, 32)
+ case int:
+ return strconv.Itoa(s)
+ case int64:
+ return strconv.FormatInt(s, 10)
+ case int32:
+ return strconv.Itoa(int(s))
+ case int16:
+ return strconv.FormatInt(int64(s), 10)
+ case int8:
+ return strconv.FormatInt(int64(s), 10)
+ case uint:
+ return strconv.FormatInt(int64(s), 10)
+ case uint64:
+ return strconv.FormatInt(int64(s), 10)
+ case uint32:
+ return strconv.FormatInt(int64(s), 10)
+ case uint16:
+ return strconv.FormatInt(int64(s), 10)
+ case uint8:
+ return strconv.FormatInt(int64(s), 10)
+ case []byte:
+ return b2s(s)
+ case error:
+ return s.Error()
+ }
+ return ""
+}
+
+func prepareRecipients(cc interface{}) (bb string, err error) {
+ var b []byte
+ ids := make([][]int64, 0)
+ switch c := cc.(type) {
+ case *Conversation:
+ for i := range c.Users {
+ ids = append(ids, []int64{c.Users[i].ID})
+ }
+ case *Item:
+ ids = append(ids, []int64{c.User.ID})
+ case int64:
+ ids = append(ids, []int64{c})
+ }
+ b, err = json.Marshal(ids)
+ bb = b2s(b)
+ return
+}
+
+// getImageDimensionFromReader return image dimension , types is .jpg and .png
+func getImageDimensionFromReader(rdr io.Reader) (int, int, error) {
+ image, _, err := image.DecodeConfig(rdr)
+ if err != nil {
+ return 0, 0, err
+ }
+ return image.Width, image.Height, nil
+}
diff --git a/utils/util.go b/utils/util.go
index 1f170a54..b2b9de13 100644
--- a/utils/util.go
+++ b/utils/util.go
@@ -5,9 +5,9 @@ import (
"fmt"
"os"
+ "github.com/ahmdrz/goinsta"
"github.com/howeyc/gopass"
homedir "github.com/mitchellh/go-homedir"
- "gopkg.in/ahmdrz/goinsta.v2"
)
// New function returns Instagram object.