ig_api 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []