Home Reference Source

src/index.js

import { createReadStream } from 'fs';
import { v1 as uuidV1 } from 'uuid';
import { extend } from 'lodash';
import { default as request } from './utils/request_util';
import { timestamp, generateUserAgent, generateSignature } from './utils/auth_util';

const jar = request.jar;

/**
 * Class InstagramApi contains all the methods needed to interact
 * with instagram.
 * @class InstagramApi
 */
export default class InstagramApi {
  constructor(config = { debug: false }) {
    const {
      username,
      password
    } = config;

    if (!username) {
      const errMsgKey = 'Username missing.';
      throw new Error(errMsgKey);
    }

    if (!password) {
      const errMsgPass = 'Password missing.';
      throw new Error(errMsgPass);
    }

    extend(this, config);

    this.uid = uuidV1();
    this.userAgent = generateUserAgent();
    this.deviceId = 'android-4bca48fddbe5477d';
    this.endpointUrl = 'https://i.instagram.com/api/v1';
    this.jar = jar();

    // Provide a default logger via console
    if (!this.log && this.debug) {
      this.log = function log(val) {
        console.log(val); // eslint-disable-line no-console
      };
      return this;
    }

    this.log = function noop() {
    };
    return this;
  }

  /**
   * Method to generate the session to Instagram.
   * @method login
   * @return {Object} Whether or not the promise is fulfilled.
   */
  login() {
    this.log('login');
    const conf = JSON.stringify({
      device_id: this.deviceId,
      _uuid: this.uid,
      username: this.username,
      password: this.password,
      _csrftoken: 'missing',
      login_attempt_count: 0
    });

    const signature = generateSignature(conf);

    return request.postAsync({
      uri: `${this.endpointUrl}/accounts/login/`,
      headers: {
        'USER-AGENT': this.userAgent
      },
      jar: this.jar,
      form: {
        signed_body: `${signature}.${conf}`,
        ig_sig_key_version: 4
      }
    }).then(function parseBodyLogin(resp) {
      this.log('parseBodyLogin');

      const body = JSON.parse(resp.body);
      if (body.status !== 'ok') {
        throw new Error(body);
      }
      return body;
    }.bind(this));
  }

  /**
   * Method to create a photo in instagram from a local filepath.
   * @method uploadPhoto
   * @return {Object} Whether or not the promise is fulfilled.
   */
  uploadPhoto(source, captionText, isFilePath) {
    this.log('uploadPhoto');
    const stamp = timestamp();
    const formData = {
      _csrftoken: 'missing',
      upload_id: stamp,
      device_id: this.deviceId,
      _uuid: this.uid,
      image_compression: '{"lib_name":"jt","lib_version":"1.3.0","quality":"70"}',
      filename: `pending_media_${stamp}.jpg`,
      photo: (isFilePath ? createReadStream(source) : request.get({ uri: source }))
    };
    return request.postAsync({
      headers: {
        'USER-AGENT': this.userAgent
      },
      uri: `${this.endpointUrl}/upload/photo/`,
      jar: this.jar,
      formData
    }).then(function parseBodyPhotoId(resp) {
      this.log('parseBodyPhotoId');
      this.log(resp.body);

      const body = JSON.parse(resp.body);
      if (!body.upload_id) {
        throw new Error(resp.body);
      }
      const uploadId = body.upload_id;
      return { uploadId, captionText };
    }.bind(this))
      .then(this.configurePhoto.bind(this));
  }

  configurePhoto({
    uploadId,
    captionText
  }) {
    this.log('configurePhoto');
    const conf = JSON.stringify({
      device_id: this.deviceId,
      _uuid: this.uid,
      _csrftoken: 'missing',
      media_id: uploadId,
      caption: captionText,
      device_timestamp: timestamp(),
      source_type: '5',
      filter_type: '0',
      extra: '{}',
      'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
    });
    const signature = generateSignature(conf);
    return request.postAsync({
      uri: `${this.endpointUrl}/media/configure/`,
      headers: {
        'USER-AGENT': this.userAgent
      },
      jar: this.jar,
      form: {
        signed_body: `${signature}.${conf}`,
        ig_sig_key_version: 4
      }
    }).then(function parseBodyConfigure(resp) {
      this.log('parseBodyConfigure');
      this.log(resp.body);
      const body = JSON.parse(resp.body);
      if (body.status !== 'ok') {
        throw new Error(resp.body);
      }
      return body;
    }.bind(this));
  }

  postFromUrl(url, caption) {
    this.log('postFromUrl');
    return this.login()
      .then(function uploadFromUrl() {
        this.log('uploadFromUrl');
        return this.uploadPhoto(url, caption);
      }.bind(this));
  }

  postFromFilepath(filepath, caption) {
    return this.login()
      .then(function uploadFromFilePath() {
        return this.uploadPhoto(filepath, caption, true);
      }.bind(this));
  }
}