Skip to content
This repository has been archived by the owner on Aug 25, 2023. It is now read-only.

Commit

Permalink
Verify API Support (#198)
Browse files Browse the repository at this point in the history
* Add verify API support (#198)

* Remove unreachable code
  • Loading branch information
Nyarum authored and ahmdrz committed Oct 16, 2019
1 parent 5fa7ca5 commit 7f4892d
Show file tree
Hide file tree
Showing 5 changed files with 250 additions and 3 deletions.
158 changes: 158 additions & 0 deletions challenge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package goinsta

import (
"encoding/json"
"strings"
)

type ChallengeStepData struct {
Choice string `json:"choice"`
FbAccessToken string `json:"fb_access_token"`
BigBlueToken string `json:"big_blue_token"`
GoogleOauthToken string `json:"google_oauth_token"`
Email string `json:"email"`
SecurityCode string `json:"security_code"`
ResendDelay interface{} `json:"resend_delay"`
ContactPoint string `json:"contact_point"`
FormType string `json:"form_type"`
}
type Challenge struct {
insta *Instagram
StepName string `json:"step_name"`
StepData ChallengeStepData `json:"step_data"`
LoggedInUser *Account `json:"logged_in_user,omitempty"`
UserID int64 `json:"user_id"`
NonceCode string `json:"nonce_code"`
Action string `json:"action"`
Status string `json:"status"`
}

type challengeResp struct {
*Challenge
}

func newChallenge(insta *Instagram) *Challenge {
time := &Challenge{
insta: insta,
}
return time
}

// updateState updates current data from challenge url
func (challenge *Challenge) updateState() error {
insta := challenge.insta

data, err := insta.prepareData(map[string]interface{}{
"guid": insta.uuid,
"device_id": insta.dID,
})
if err != nil {
return err
}

body, err := insta.sendRequest(
&reqOptions{
Endpoint: challenge.insta.challengeURL,
Query: generateSignature(data),
},
)
if err == nil {
resp := challengeResp{}
err = json.Unmarshal(body, &resp)
if err == nil {
*challenge = *resp.Challenge
challenge.insta = insta
}
}
return err
}

// selectVerifyMethod selects a way and verify it (Phone number = 0, email = 1)
func (challenge *Challenge) selectVerifyMethod(choice string, isReplay ...bool) error {
insta := challenge.insta

url := challenge.insta.challengeURL
if len(isReplay) > 0 && isReplay[0] {
url = strings.Replace(url, "/challenge/", "/challenge/replay/", -1)
}

data, err := insta.prepareData(map[string]interface{}{
"choice": choice,
"guid": insta.uuid,
"device_id": insta.dID,
})
if err != nil {
return err
}

body, err := insta.sendRequest(
&reqOptions{
Endpoint: url,
Query: generateSignature(data),
IsPost: true,
},
)
if err == nil {
resp := challengeResp{}
err = json.Unmarshal(body, &resp)
if err == nil {
*challenge = *resp.Challenge
challenge.insta = insta
}
}
return err
}

// sendSecurityCode sends the code received in the message
func (challenge *Challenge) SendSecurityCode(code string) error {
insta := challenge.insta
url := challenge.insta.challengeURL

data, err := insta.prepareData(map[string]interface{}{
"security_code": code,
"guid": insta.uuid,
"device_id": insta.dID,
})
if err != nil {
return err
}

body, err := insta.sendRequest(
&reqOptions{
Endpoint: url,
Query: generateSignature(data),
IsPost: true,
},
)
if err == nil {
resp := challengeResp{}
err = json.Unmarshal(body, &resp)
if err == nil {
*challenge = *resp.Challenge
challenge.insta = insta
}
}
return err
}

// deltaLoginReview process with choice (It was me = 0, It wasn't me = 1)
func (challenge *Challenge) deltaLoginReview() error {
return challenge.selectVerifyMethod("0")
}

func (challenge *Challenge) Process(apiURL string) error {
challenge.insta.challengeURL = apiURL[1:]

if err := challenge.updateState(); err != nil {
return err
}

switch challenge.StepName {
case "select_verify_method":
return challenge.selectVerifyMethod(challenge.StepData.Choice)
case "delta_login_review":
return challenge.deltaLoginReview()
}

return ErrChallengeProcess{StepName: challenge.StepName}
}
48 changes: 48 additions & 0 deletions examples/verification/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package main

import (
"log"
"os"

"github.com/ahmdrz/goinsta"
"github.com/tcnksm/go-input"
)

func main() {
insta := goinsta.New(
os.Getenv("INSTAGRAM_USERNAME"),
os.Getenv("INSTAGRAM_PASSWORD"),
)
if err := insta.Login(); err != nil {
switch v := err.(type) {
case goinsta.ChallengeError:
err := insta.Challenge.Process(v.Challenge.APIPath)
if err != nil {
log.Fatalln(err)
}

ui := &input.UI{
Writer: os.Stdout,
Reader: os.Stdin,
}

query := "What is SMS code for instagram?"
code, err := ui.Ask(query, &input.Options{
Default: "000000",
Required: true,
Loop: true,
})
if err != nil {
log.Fatalln(err)
}

err = insta.Challenge.SendSecurityCode(code)
if err != nil {
log.Fatalln(err)
}
}

log.Fatalln(err)
}
defer insta.Logout()
}
5 changes: 5 additions & 0 deletions goinsta.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,13 @@ type Instagram struct {
pid string
// ads id
adid string
// challenge URL
challengeURL string

// Instagram objects

// Challenge controls security side of account (Like sms verify / It was me)
Challenge *Challenge
// Profiles is the user interaction
Profiles *Profiles
// Account stores all personal data of the user and his/her options.
Expand Down Expand Up @@ -137,6 +141,7 @@ func New(username, password string) *Instagram {
}

func (inst *Instagram) init() {
inst.Challenge = newChallenge(inst)
inst.Profiles = newProfiles(inst)
inst.Activity = newActivity(inst)
inst.Timeline = newTimeline(inst)
Expand Down
14 changes: 11 additions & 3 deletions request.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,15 +129,23 @@ func isError(code int, body []byte) (err error) {
case 400:
ierr := Error400{}
err = json.Unmarshal(body, &ierr)
if err == nil && ierr.Payload.Message != "" {
if err != nil {
return err
}

if ierr.Message == "challenge_required" {
return ierr.ChallengeError

}

if err == nil && ierr.Message != "" {
return ierr
}
fallthrough
default:
ierr := ErrorN{}
err = json.Unmarshal(body, &ierr)
if err != nil {
return fmt.Errorf("Invalid status code: %d: %s", code, body)
return err
}
return ierr
}
Expand Down
28 changes: 28 additions & 0 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func (e ErrorN) Error() string {

// Error400 is error returned by HTTP 400 status code.
type Error400 struct {
ChallengeError
Action string `json:"action"`
StatusCode string `json:"status_code"`
Payload struct {
Expand All @@ -64,6 +65,25 @@ func (e Error400) Error() string {
return fmt.Sprintf("%s: %s", e.Status, e.Payload.Message)
}

// ChallengeError is error returned by HTTP 400 status code.
type ChallengeError struct {
Message string `json:"message"`
Challenge struct {
URL string `json:"url"`
APIPath string `json:"api_path"`
HideWebviewHeader bool `json:"hide_webview_header"`
Lock bool `json:"lock"`
Logout bool `json:"logout"`
NativeFlow bool `json:"native_flow"`
} `json:"challenge"`
Status string `json:"status"`
ErrorType string `json:"error_type"`
}

func (e ChallengeError) Error() string {
return fmt.Sprintf("%s: %s", e.Status, e.Message)
}

// Nametag is part of the account information.
type Nametag struct {
Mode int64 `json:"mode"`
Expand Down Expand Up @@ -343,3 +363,11 @@ type threadResp struct {
Conversation Conversation `json:"thread"`
Status string `json:"status"`
}

type ErrChallengeProcess struct {
StepName string
}

func (ec ErrChallengeProcess) Error() string {
return ec.StepName
}

0 comments on commit 7f4892d

Please sign in to comment.