/** * Copyright (C) 2016 Bruno Candido Volpato da Cunha ([email protected]) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.brunocvcunha.instagram4j; import java.io.IOException; import java.io.Serializable; import java.util.Optional; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.CookieStore; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.cookie.Cookie; import org.apache.http.impl.client.BasicCookieStore; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.brunocvcunha.instagram4j.requests.InstagramAutoCompleteUserListRequest; import org.brunocvcunha.instagram4j.requests.InstagramGetInboxRequest; import org.brunocvcunha.instagram4j.requests.InstagramGetRecentActivityRequest; import org.brunocvcunha.instagram4j.requests.InstagramLoginRequest; import org.brunocvcunha.instagram4j.requests.InstagramLoginTwoFactorRequest; import org.brunocvcunha.instagram4j.requests.InstagramRequest; import org.brunocvcunha.instagram4j.requests.InstagramTimelineFeedRequest; import org.brunocvcunha.instagram4j.requests.internal.InstagramLogAttributionRequest; import org.brunocvcunha.instagram4j.requests.internal.InstagramReadMsisdnHeaderRequest; import org.brunocvcunha.instagram4j.requests.internal.InstagramSyncFeaturesRequest; import org.brunocvcunha.instagram4j.requests.internal.InstagramZeroRatingTokenRequest; import org.brunocvcunha.instagram4j.requests.payload.InstagramLoginPayload; import org.brunocvcunha.instagram4j.requests.payload.InstagramLoginResult; import org.brunocvcunha.instagram4j.requests.payload.InstagramLoginTwoFactorPayload; import org.brunocvcunha.instagram4j.util.InstagramGenericUtil; import org.brunocvcunha.instagram4j.util.InstagramHashUtil; import org.brunocvcunha.inutils4j.MyNumberUtils; import lombok.Builder; import lombok.Getter; import lombok.Setter; import lombok.SneakyThrows; import lombok.extern.log4j.Log4j; /** * * Instagram4j API * * @author Bruno Candido Volpato da Cunha * */ @Log4j public class Instagram4j implements Serializable { @Getter private String deviceId; @Getter private String uuid; @Getter private String advertisingId; @Getter @Setter private String username; @Getter @Setter private String password; @Getter private CredentialsProvider credentialsProvider; @Getter @Setter private HttpHost proxy; @Getter @Setter private long userId; @Getter @Setter private String rankToken; @Getter private boolean isLoggedIn; @Getter private HttpResponse lastResponse; @Getter @Setter private boolean debug; @Getter private CookieStore cookieStore; private CloseableHttpClient client; @Getter @Setter private String AUTH_VALUE = "0"; @Getter @Setter private String X_MID = "0"; @Getter @Setter private String WWW_CLAIM = "0"; private String identifier; /** * @param username Username * @param password Password */ public Instagram4j(String username, String password) { super(); this.username = username; this.password = password; } /** * @param username Username * @param password Password * @param userId UserId * @param uuid UUID * @param cookieStore Cookie Store * @param proxy proxy * @param credentialsProvider proxy credential */ @Builder public Instagram4j(String username, String password, long userId, String uuid, CookieStore cookieStore, HttpHost proxy, CredentialsProvider credentialsProvider) { super(); this.username = username; this.password = password; this.userId = userId; this.uuid = uuid; this.cookieStore = cookieStore; this.proxy = proxy; this.credentialsProvider = credentialsProvider; this.isLoggedIn = true; } /** * Setup some variables */ public void setup() { log.info("Setup..."); if (StringUtils.isEmpty(this.username)) { throw new IllegalArgumentException("Username is mandatory."); } if (StringUtils.isEmpty(this.password)) { throw new IllegalArgumentException("Password is mandatory."); } this.deviceId = InstagramHashUtil.generateDeviceId(this.username, this.password); if (StringUtils.isEmpty(this.uuid)) { this.uuid = InstagramGenericUtil.generateUuid(true); } if (StringUtils.isEmpty(this.advertisingId)) { this.advertisingId = InstagramGenericUtil.generateUuid(true); } if (this.cookieStore == null) { this.cookieStore = new BasicCookieStore(); } log.info("Device ID is: " + this.deviceId + ", random id: " + this.uuid); HttpClientBuilder builder = HttpClientBuilder.create(); if (proxy != null) { builder.setProxy(proxy); } if (credentialsProvider != null) builder.setDefaultCredentialsProvider(credentialsProvider); builder.setDefaultCookieStore(this.cookieStore); this.client = builder.build(); } /** * @return * @throws IOException * @throws ClientProtocolException */ public InstagramLoginResult login() throws ClientProtocolException, IOException { log.info("Logging with user " + username + " and password " + password.replaceAll("[a-zA-Z0-9]", "*")); InstagramLoginPayload loginRequest = InstagramLoginPayload.builder().username(username).password(password) .guid(uuid).device_id(deviceId).phone_id(InstagramGenericUtil.generateUuid(true)) .login_attempt_account(0)._csrftoken("missing").build(); InstagramLoginRequest req = new InstagramLoginRequest(loginRequest); InstagramLoginResult loginResult = this.sendRequest(req); if(loginResult.getStatus().equals("ok")) { this.isLoggedIn = true; } if (loginResult.getTwo_factor_info() != null) { identifier = loginResult.getTwo_factor_info().getTwo_factor_identifier(); } else if (loginResult.getChallenge() != null) { // logic for challenge log.info("Challenge required: " + loginResult.getChallenge()); } return loginResult; } public InstagramLoginResult login(String verificationCode) throws ClientProtocolException, IOException { if (identifier == null) { login(); } InstagramLoginTwoFactorPayload loginRequest = InstagramLoginTwoFactorPayload.builder().username(username) .verification_code(verificationCode).two_factor_identifier(identifier).password(password).guid(uuid) .device_id(deviceId).phone_id(InstagramGenericUtil.generateUuid(true)).login_attempt_account(0) ._csrftoken(getOrFetchCsrf()).build(); InstagramLoginTwoFactorRequest req = new InstagramLoginTwoFactorRequest(loginRequest); InstagramLoginResult loginResult = this.sendRequest(req); if(loginResult.getStatus().equals("ok")) { this.isLoggedIn = true; } return loginResult; } /** * @return * @throws ClientProtocolException * @throws IOException */ public String getOrFetchCsrf() throws ClientProtocolException, IOException { Optional<Cookie> checkCookie = getCsrfCookie(); if (!checkCookie.isPresent()) { return "0"; } String csrfToken = checkCookie.get().getValue(); return csrfToken; } public Optional<Cookie> getCsrfCookie() { return cookieStore.getCookies().stream().filter(cookie -> cookie.getName().equalsIgnoreCase("csrftoken")) .findFirst(); } /** * Send request to endpoint * * @param request Request object * @return success flag * @throws IOException * @throws ClientProtocolException */ public <T> T sendRequest(InstagramRequest<T> request) throws ClientProtocolException, IOException { log.info("Sending request: " + request.getClass().getName()); if (!this.isLoggedIn && request.requiresLogin()) { throw new IllegalStateException("Need to login first!"); } // wait to simulate real human interaction randomWait(); request.setApi(this); T response = request.execute(); this.setHeaders(); log.debug("Result for " + request.getClass().getName() + ": " + response); return response; } @SneakyThrows public HttpResponse executeHttpRequest(HttpUriRequest req) { lastResponse = client.execute(req); return lastResponse; } private void setHeaders() { InstagramGenericUtil.getFirstHeaderValue(getLastResponse(), "ig-set-authorization") .ifPresent(this::setAUTH_VALUE); InstagramGenericUtil.getFirstHeaderValue(getLastResponse(), "x-ig-set-www-claim").ifPresent(this::setWWW_CLAIM); InstagramGenericUtil.getFirstHeaderValue(getLastResponse(), "ig-set-x-mid").ifPresent(this::setX_MID); } public void emulatePreLoginFlow() throws ClientProtocolException, IOException { this.sendRequest(new InstagramReadMsisdnHeaderRequest()); this.sendRequest(new InstagramSyncFeaturesRequest(true)); this.sendRequest(new InstagramZeroRatingTokenRequest()); this.sendRequest(new InstagramLogAttributionRequest()); } public void emulateUserLoggedIn(InstagramLoginResult loginResult) throws IOException { if (loginResult.getStatus().equalsIgnoreCase("ok")) { this.userId = loginResult.getLogged_in_user().getPk(); this.rankToken = this.userId + "_" + this.uuid; this.sendRequest(new InstagramSyncFeaturesRequest(false)); this.sendRequest(new InstagramAutoCompleteUserListRequest()); this.sendRequest(new InstagramTimelineFeedRequest()); this.sendRequest(new InstagramGetInboxRequest()); this.sendRequest(new InstagramGetRecentActivityRequest()); } } @SneakyThrows private void randomWait() { Thread.sleep(MyNumberUtils.randomLongBetween(100, 250)); } }