ig_api 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/IgApi/feed.rb ADDED
@@ -0,0 +1,78 @@
1
+ module IgApi
2
+ class Feed
3
+ def initialize
4
+ @api = Http.singleton
5
+ end
6
+
7
+ def using user
8
+ @user = {
9
+ id: user.data[:id],
10
+ session: user.session,
11
+ ua: user.useragent
12
+ }
13
+ self
14
+ end
15
+
16
+ def timeline_media
17
+ user_id = @user[:id]
18
+
19
+ rank_token = IgApi::Http.generate_rank_token @user[:id]
20
+ endpoint = "https://i.instagram.com/api/v1/feed/user/#{user_id}/"
21
+ param = "?rank_token=#{rank_token}"
22
+ result = @api.get(endpoint + param)
23
+ .with(session: @user[:session], ua: @user[:ua]).exec
24
+
25
+ JSON.parse result.body
26
+ end
27
+
28
+ def self.user_followers(user, data, limit)
29
+ has_next_page = true
30
+ followers = []
31
+ user_id = (!data[:id].nil? ? data[:id] : user.data[:id])
32
+ data[:rank_token] = IgApi::API.generate_rank_token user.session.scan(/ds_user_id=([\d]+);/)[0][0]
33
+ while has_next_page && limit > followers.size
34
+ response = user_followers_next_page(user, user_id, data)
35
+ has_next_page = !response['next_max_id'].nil?
36
+ data[:max_id] = response['next_max_id']
37
+ followers += response['users']
38
+ end
39
+ limit.infinite? ? followers : followers[0...limit]
40
+ end
41
+
42
+ def self.user_followers_graphql(user, data, limit)
43
+ has_next_page = true
44
+ followers = []
45
+ user_id = (!data[:id].nil? ? data[:id] : user.data[:id])
46
+ while has_next_page && limit > followers.size
47
+ response = user_followers_graphql_next_page(user, user_id, data)
48
+ has_next_page = response['data']['user']['edge_followed_by']['page_info']['has_next_page']
49
+ data[:end_cursor] = response['data']['user']['edge_followed_by']['page_info']['end_cursor']
50
+ followers += response['data']['user']['edge_followed_by']['edges']
51
+ end
52
+ limit.infinite? ? followers : followers[0...limit]
53
+ end
54
+
55
+ def self.user_followers_graphql_next_page(user, user_id, data)
56
+ endpoint = "https://www.instagram.com/graphql/query/?query_id=17851374694183129&id=#{user_id}&first=5000"
57
+ param = (!data[:end_cursor].nil? ? "&after=#{data[:end_cursor]}" : '')
58
+ result = IgApi::API.http(
59
+ url: endpoint + param,
60
+ method: 'GET',
61
+ user: user
62
+ )
63
+ JSON.parse result.body
64
+ end
65
+
66
+ def self.user_followers_next_page(user, user_id, data)
67
+ endpoint = "https://i.instagram.com/api/v1/friendships/#{user_id}/followers/"
68
+ param = "?rank_token=#{data[:rank_token]}" +
69
+ (!data[:max_id].nil? ? '&max_id=' + data[:max_id] : '')
70
+ result = IgApi::API.http(
71
+ url: endpoint + param,
72
+ method: 'GET',
73
+ user: user
74
+ )
75
+ JSON.parse result.body
76
+ end
77
+ end
78
+ end
data/lib/IgApi/http.rb ADDED
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'IgApi/version'
4
+ require 'openssl'
5
+ require 'Base64'
6
+ require 'digest/md5'
7
+ require 'net/http'
8
+ require 'json'
9
+ require 'IgApi/User'
10
+ require 'IgApi/account'
11
+ require 'IgApi/feed'
12
+ require 'IgApi/Configuration'
13
+
14
+ module IgApi
15
+ class Http
16
+ def self.compute_hash(data)
17
+ OpenSSL::HMAC.hexdigest OpenSSL::Digest.new('sha256'), Constants::PRIVATE_KEY[:SIG_KEY], data
18
+ end
19
+
20
+ def self.__obj=value
21
+ @@obj = value
22
+ end
23
+
24
+ def self.__obj
25
+ @@obj
26
+ end
27
+
28
+ def self.singleton
29
+ @@obj = Http.new unless defined? @@obj
30
+
31
+ @@obj
32
+ end
33
+
34
+ def self.generate_uuid
35
+ 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.gsub(/[xy]/) do |c|
36
+ r = (Random.rand * 16).round | 0
37
+ v = c == 'x' ? r : (r & 0x3 | 0x8)
38
+ c.gsub(c, v.to_s(16))
39
+ end.downcase
40
+ end
41
+
42
+ def self.create_md5(data)
43
+ Digest::MD5.hexdigest(data).to_s
44
+ end
45
+
46
+ def self.generate_device_id
47
+ timestamp = Time.now.to_i.to_s
48
+ 'android-' + create_md5(timestamp)[0..16]
49
+ end
50
+
51
+ def self.generate_signature(data)
52
+ data = data.to_json
53
+ compute_hash(data) + '.' + data
54
+ end
55
+
56
+ def post(url, body = nil)
57
+ @data = { method: 'POST', url: url, body: body }
58
+ self
59
+ end
60
+
61
+ def with(data)
62
+ data.each { |k, v| @data[k] = v }
63
+ self
64
+ end
65
+
66
+ def exec
67
+ http @data
68
+ end
69
+
70
+ def get(url)
71
+ @data = {method: 'GET', url: url}
72
+ self
73
+ end
74
+
75
+ def http(args)
76
+ args[:url] = URI.parse(args[:url])
77
+ http = Net::HTTP.new(args[:url].host, args[:url].port,
78
+ ENV['INSTAGRAM_PROXY_HOST'], ENV['INSTAGRAM_PROXY_PORT'])
79
+ http.use_ssl = true
80
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
81
+ request = nil
82
+ if args[:method] == 'POST'
83
+ request = Net::HTTP::Post.new(args[:url].path)
84
+ elsif args[:method] == 'GET'
85
+ request = Net::HTTP::Get.new(args[:url].path + (!args[:url].nil? ? '?' + args[:url].query : ''))
86
+ end
87
+
88
+ request.initialize_http_header('User-Agent': args[:ua],
89
+ Accept: IgApi::Constants::HEADER[:accept],
90
+ 'Accept-Encoding': IgApi::Constants::HEADER[:encoding],
91
+ 'Accept-Language': args.dig(:user)&.language,
92
+ 'X-IG-Capabilities': IgApi::Constants::HEADER[:capabilities],
93
+ 'X-IG-Connection-Type': IgApi::Constants::HEADER[:type],
94
+ Cookie: args[:session])
95
+ request.body = args.key?(:body) ? args[:body] : nil
96
+ http.request(request)
97
+ end
98
+
99
+ def self.generate_rank_token(pk)
100
+ format('%s_%s', pk, IgApi::Http.generate_uuid)
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IgApi
4
+ class Relationship
5
+ def initialize user
6
+ @user = user
7
+ @api = nil
8
+ end
9
+
10
+ def create(id)
11
+ JSON.parse api.post("https://i.instagram.com/api/v1/friendships/create/#{id}/",
12
+ format(
13
+ 'ig_sig_key_version=4&signed_body=%s',
14
+ Http.generate_signature(
15
+ user_id: id
16
+ )
17
+ )).with(session: @user.session, ua: @user.useragent)
18
+ .exec.body
19
+ end
20
+
21
+ def destroy(id)
22
+ JSON.parse api.post("https://i.instagram.com/api/v1/friendships/destroy/#{id}/",
23
+ format(
24
+ 'ig_sig_key_version=4&signed_body=%s',
25
+ Http.generate_signature(
26
+ user_id: id
27
+ )
28
+ )).with(session: @user.session, ua: @user.useragent)
29
+ .exec.body
30
+ end
31
+
32
+ def api
33
+ @api = Http.new if @api.nil?
34
+
35
+ @api
36
+ end
37
+ end
38
+ end
data/lib/IgApi/user.rb ADDED
@@ -0,0 +1,118 @@
1
+ require 'digest/md5'
2
+ require 'IgApi/Device'
3
+ require 'IgApi/CONSTANTS'
4
+
5
+ module IgApi
6
+ class User
7
+ attr_reader :username
8
+ attr_reader :password
9
+ attr_reader :language
10
+ attr_reader :data
11
+ attr_writer :data
12
+ attr_reader :session
13
+ attr_writer :session
14
+ attr_reader :config
15
+ attr_writer :config
16
+
17
+ def initialize(username, password, session = nil, data = nil, config = nil)
18
+ @username = username
19
+ @password = password
20
+ @language = 'en_US'
21
+ @session = session
22
+ @data = data
23
+ @config = config
24
+ @account = nil
25
+ @feed = nil
26
+ end
27
+
28
+ def search_for_user(username)
29
+ account.search_for_user(self, username)
30
+ end
31
+
32
+ def search_for_user_graphql(username)
33
+ account.search_for_graphql(self, username)
34
+ end
35
+
36
+ def followers(limit = Float::INFINITY, data = {})
37
+ IgApi::Feed.user_followers(self, data, limit)
38
+ end
39
+
40
+ def user_followers_graphql(limit = Float::INFINITY, data = {})
41
+ IgApi::Feed.user_followers_graphql(self, data, limit)
42
+ end
43
+
44
+ def relationship
45
+ unless instance_variable_defined? :@relationship
46
+ @relationship = Relationship.new self
47
+ end
48
+
49
+ @relationship
50
+ end
51
+
52
+ def account
53
+ @account = IgApi::Account.new if @account.nil?
54
+
55
+ @account
56
+ end
57
+
58
+ def feed
59
+ @feed = IgApi::Feed.new if @feed.nil?
60
+
61
+ @feed.using(self)
62
+ end
63
+
64
+ def md5
65
+ Digest::MD5.hexdigest @username
66
+ end
67
+
68
+ def md5int
69
+ (md5.to_i(32) / 10e32).round
70
+ end
71
+
72
+ def api
73
+ (18 + (md5int % 5)).to_s
74
+ end
75
+
76
+ # @return [string]
77
+ def release
78
+ %w[4.0.4 4.3.1 4.4.4 5.1.1 6.0.1][md5int % 5]
79
+ end
80
+
81
+ def dpi
82
+ %w[801 577 576 538 515 424 401 373][md5int % 8]
83
+ end
84
+
85
+ def resolution
86
+ %w[3840x2160 1440x2560 2560x1440 1440x2560
87
+ 2560x1440 1080x1920 1080x1920 1080x1920][md5int % 8]
88
+ end
89
+
90
+ def info
91
+ line = Device.devices[md5int % Device.devices.count]
92
+ {
93
+ manufacturer: line[0],
94
+ device: line[1],
95
+ model: line[2]
96
+ }
97
+ end
98
+
99
+ def useragent_hash
100
+ agent = [api + '/' + release, dpi + 'dpi',
101
+ resolution, info[:manufacturer],
102
+ info[:model], info[:device], @language]
103
+
104
+ {
105
+ agent: agent.join('; '),
106
+ version: Constants::PRIVATE_KEY[:APP_VERSION]
107
+ }
108
+ end
109
+
110
+ def useragent
111
+ format('Instagram %s Android(%s)', useragent_hash[:version], useragent_hash[:agent].rstrip)
112
+ end
113
+
114
+ def device_id
115
+ 'android-' + md5[0..15]
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,3 @@
1
+ module IgApi
2
+ VERSION = "0.0.1"
3
+ end
data/lib/IgApi.rb ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Root module
4
+ module IgApi
5
+ end
6
+
7
+ require 'IgApi/http'
8
+ require 'IgApi/Constants'
9
+ require 'IgApi/Device'
10
+ require 'IgApi/User'
11
+ require 'IgApi/Relationship'
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ig_api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - vicoerv
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-07-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description: IgApi private api, implemented from huttarichard/instagram-private-api
56
+ email:
57
+ - vicoerv@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".rspec"
64
+ - ".rubocop.yml"
65
+ - ".travis.yml"
66
+ - CODE_OF_CONDUCT.md
67
+ - Gemfile
68
+ - Gemfile.lock
69
+ - IgApi.gemspec
70
+ - LICENSE.txt
71
+ - README.md
72
+ - Rakefile
73
+ - bin/console
74
+ - bin/setup
75
+ - lib/IgApi.rb
76
+ - lib/IgApi/account.rb
77
+ - lib/IgApi/configuration.rb
78
+ - lib/IgApi/constants.rb
79
+ - lib/IgApi/device.rb
80
+ - lib/IgApi/feed.rb
81
+ - lib/IgApi/http.rb
82
+ - lib/IgApi/relationship.rb
83
+ - lib/IgApi/user.rb
84
+ - lib/IgApi/version.rb
85
+ homepage: http://www.vicoervanda.com
86
+ licenses:
87
+ - MIT
88
+ metadata:
89
+ allowed_push_host: https://rubygems.org
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements: []
105
+ rubyforge_project:
106
+ rubygems_version: 2.7.6
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: IgApi private api, implemented from huttarichard/instagram-private-api
110
+ test_files: []