require 'mechanize' require 'selenium-webdriver' require 'json' require 'instagram_user/version' require 'pry' module InstagramUser class Client BASE_URL = 'https://www.instagram.com/graphql/query/?query_hash=%s&variables=%s'.freeze LOGIN_URL = 'https://www.instagram.com/accounts/login/ajax/'.freeze USER_INFO_URL = 'https://www.instagram.com/%s/?__a=1'.freeze DEFAULT_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36'.freeze DEFAULT_MAX_NUM_USERS = 3000 DEFAULT_REFERER = 'https://www.instagram.com/'.freeze USER_MAP = { follow: { query_hash: '58712303d941c6855d4e888c5f0cd22f', edge: 'edge_follow' }, follower: { query_hash: '37479f2b8209594dde7facb0d904896a', edge: 'edge_followed_by' } }.freeze def initialize(options = {}) @user_name = (ENV['INSTAGRAM_USER_NAME'] || options[:user_name]) @password = (ENV['INSTAGRAM_PASSWORD'] || options[:password]) @user_agent = (ENV['INSTAGRAM_USER_AGENT'] || options[:user_agent] || DEFAULT_USER_AGENT) @referer = (ENV['INSTAGRAM_REFERER'] || options[:referer] || DEFAULT_REFERER) @num_users = (ENV['INSTAGRAM_NUM_USERS'] || options[:num_users] || DEFAULT_MAX_NUM_USERS) @session = Mechanize.new @user_ids = {} logined_session selenium_setting end def get_follows(user_name) user_id = @user_ids[user_name].nil? ? get_user_id(user_name) : @user_ids[user_name] fetch_all_user_names(user_id, USER_MAP[:follow]) end def get_followers(user_name) user_id = @user_ids[user_name].nil? ? get_user_id(user_name) : @user_ids[user_name] fetch_all_user_names(user_id, USER_MAP[:follower]) end def create_follow(user_name) @driver.get "https://www.instagram.com/#{user_name}" begin @wait.until { !@driver.find_elements(:xpath, '//article//button').empty? } rescue => e return false end color = @driver.find_element(:xpath, '//article//button').css_value("color") return false if color != "rgba(255, 255, 255, 1)" @driver.find_element(:xpath, '//article//button').click sleep(2) @driver.get "https://www.instagram.com/#{user_name}" @wait.until { !@driver.find_elements(:xpath, '//article//button').empty? } color = @driver.find_element(:xpath, '//article//button').css_value("color") (color == "rgba(255, 255, 255, 1)") ? false : true end def delete_follow(user_name) @driver.get "https://www.instagram.com/#{user_name}" begin @wait.until { !@driver.find_elements(:xpath, '//article//button').empty? } rescue => e return false end color = @driver.find_element(:xpath, '//article//button').css_value("color") return false if color == "rgba(255, 255, 255, 1)" @driver.find_element(:xpath, '//article//button').click sleep(2) @driver.get "https://www.instagram.com/#{user_name}" @wait.until { !@driver.find_elements(:xpath, '//article//button').empty? } color = @driver.find_element(:xpath, '//article//button').css_value("color") (color != "rgba(255, 255, 255, 1)") ? false : true end def get_cookies_d @driver.manage.all_cookies end def get_cookies_s @session.cookie_jar.cookies end private def selenium_setting options = Selenium::WebDriver::Chrome::Options.new options.add_argument("--user-agent=#{@user_agent}") options.add_argument('--headless') @driver = Selenium::WebDriver.for :chrome, options: options @driver.get 'https://www.instagram.com/' @session.cookie_jar.cookies.each do |c| cookie_hash = { name: c.name, value: c.value, path: c.path, secure: c.secure, expires: c.expires, domain: c.domain, for_domain: c.for_domain, httponly: c.httponly, max_age: c.max_age, created_at: c.created_at, accessed_at: c.accessed_at, origin: c.origin.to_s } @driver.manage.add_cookie(cookie_hash) end @wait = Selenium::WebDriver::Wait.new(timeout: 60) end def get_user_id(user_name) url = format USER_INFO_URL, user_name page = @session.get(url) json = JSON.parse(page.body) @user_ids[user_name] = json["graphql"]["user"]["id"] @user_ids[user_name] end def logined_session @session.request_headers = login_http_headers @session.post(LOGIN_URL, user_info) end def login_http_headers default_http_headers.update( "x-csrftoken" => "null", "cookie" => "sessionid=null; csrftoken=null" ) end def default_http_headers { "user-agent" => @user_agent, "referer" => @referer } end def user_info { username: @user_name, password: @password } end def fetch_all_user_names(user_id, request_params) after = nil user_names = [] loop do res = fetch_user_names(user_id, request_params, after) user_names += res[:user_names] break unless res[:has_next] after = res[:after] end user_names end def fetch_user_names(user_id, request_params, after = nil) variables = { id: user_id, first: @num_users } variables[:after] = after unless after.nil? url = format BASE_URL, request_params[:query_hash], JSON.generate(variables) @session.request_headers = default_http_headers page = @session.get(url) json = JSON.parse(page.body) edge = json["data"]["user"][request_params[:edge]] { after: edge["page_info"]["end_cursor"], has_next: edge["page_info"]["has_next_page"], user_names: edge["edges"].map{ |f| f["node"]["username"] } } end end end