instagram-private-api 0.1.5.2 → 0.2.0
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.
- checksums.yaml +5 -5
- data/.gitignore +2 -0
- data/.rubocop.yml +4 -0
- data/Instagram-API.gemspec +36 -36
- data/README.md +11 -9
- data/lib/Instagram.rb +11 -7
- data/lib/Instagram/API/version.rb +5 -5
- data/lib/Instagram/account.rb +101 -69
- data/lib/Instagram/api.rb +103 -66
- data/lib/Instagram/configuration.rb +5 -0
- data/lib/Instagram/constants.rb +21 -19
- data/lib/Instagram/device.rb +11851 -11851
- data/lib/Instagram/feed.rb +78 -18
- data/lib/Instagram/relationship.rb +26 -0
- data/lib/Instagram/user.rb +118 -84
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2207b129dd5717404254fe01477f4d7187dcea4a8296e4f8ba5f7b0088dd7624
|
4
|
+
data.tar.gz: 10eb79723993e4bb1d246717d05df3f77a3cbcecd5b976931d3683bd932caf48
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 840be2d6f859320faf70cde2271952fb419f46d8e0054e1f8c014469b945476c37b6a920268696881c2c5a29a79286ab5e12e0d28f47e9b00979b91629c11d47
|
7
|
+
data.tar.gz: 6291205d99b5739a8e02e687df22475eea851be492b1ea76451d0523ddc42aaf51305c65a2c31d204c965292424d934f7e8945befc39e8d12e00f06aaf6320c7
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
data/Instagram-API.gemspec
CHANGED
@@ -1,36 +1,36 @@
|
|
1
|
-
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
3
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require 'Instagram/API/version'
|
5
|
-
|
6
|
-
Gem::Specification.new do |spec|
|
7
|
-
spec.name = 'instagram-private-api'
|
8
|
-
spec.version = Instagram::API::VERSION
|
9
|
-
spec.authors = ['vicoerv']
|
10
|
-
spec.email = ['vicoerv@gmail.com']
|
11
|
-
|
12
|
-
spec.summary = 'Instagram private api, implemented from huttarichard/instagram-private-api as best NODE-JS Instagram api'
|
13
|
-
spec.description = spec.summary
|
14
|
-
spec.homepage = 'http://www.vicoervanda.com'
|
15
|
-
spec.license = 'MIT'
|
16
|
-
|
17
|
-
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
18
|
-
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
19
|
-
if spec.respond_to?(:metadata)
|
20
|
-
spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
21
|
-
else
|
22
|
-
raise 'RubyGems 2.0 or newer is required to protect against ' \
|
23
|
-
'public gem pushes.'
|
24
|
-
end
|
25
|
-
|
26
|
-
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
27
|
-
f.match(%r{^(test|spec|features)/})
|
28
|
-
end
|
29
|
-
spec.bindir = 'exe'
|
30
|
-
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
31
|
-
spec.require_paths = ['lib']
|
32
|
-
|
33
|
-
spec.add_development_dependency 'bundler', '~> 1.16'
|
34
|
-
spec.add_development_dependency 'rake', '~> 10.0'
|
35
|
-
spec.add_development_dependency 'rspec', '~> 3.0'
|
36
|
-
end
|
1
|
+
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'Instagram/API/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'instagram-private-api'
|
8
|
+
spec.version = Instagram::API::VERSION
|
9
|
+
spec.authors = ['vicoerv']
|
10
|
+
spec.email = ['vicoerv@gmail.com']
|
11
|
+
|
12
|
+
spec.summary = 'Instagram private api, implemented from huttarichard/instagram-private-api as best NODE-JS Instagram api'
|
13
|
+
spec.description = spec.summary
|
14
|
+
spec.homepage = 'http://www.vicoervanda.com'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
18
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
19
|
+
if spec.respond_to?(:metadata)
|
20
|
+
spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
21
|
+
else
|
22
|
+
raise 'RubyGems 2.0 or newer is required to protect against ' \
|
23
|
+
'public gem pushes.'
|
24
|
+
end
|
25
|
+
|
26
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
f.match(%r{^(test|spec|features)/})
|
28
|
+
end
|
29
|
+
spec.bindir = 'exe'
|
30
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
31
|
+
spec.require_paths = ['lib']
|
32
|
+
|
33
|
+
spec.add_development_dependency 'bundler', '~> 1.16'
|
34
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
35
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
36
|
+
end
|
data/README.md
CHANGED
@@ -20,8 +20,7 @@ Or install it yourself as:
|
|
20
20
|
## Usage
|
21
21
|
- Login _for clearly new user_
|
22
22
|
```ruby
|
23
|
-
user = Instagram::
|
24
|
-
p Instagram::API::Accounts.login user
|
23
|
+
user = Instagram::Account.login 'username', 'password'
|
25
24
|
```
|
26
25
|
|
27
26
|
- Initiate existing user
|
@@ -38,23 +37,23 @@ Or install it yourself as:
|
|
38
37
|
session = logged_in_user.session
|
39
38
|
|
40
39
|
user = Instagram::User.new 'username', nil, data, session #password isn't mandatory, already have session
|
41
|
-
p user.search_for_user '
|
40
|
+
p user.search_for_user 'instagram' #then you can use it for any purpose
|
42
41
|
```
|
43
42
|
|
44
43
|
- Search for user
|
45
44
|
```ruby
|
46
|
-
p user.search_for_user '
|
45
|
+
p user.search_for_user 'instagram'
|
47
46
|
```
|
48
47
|
|
49
48
|
- User feed
|
50
49
|
```ruby
|
51
|
-
p user.
|
50
|
+
p user.media #your feed
|
52
51
|
|
53
|
-
user_target = user.search_for_user '
|
54
|
-
p user.
|
55
|
-
media = user_target.
|
52
|
+
user_target = user.search_for_user 'instagram'
|
53
|
+
p user.media user_id: user_target.data[:id] #instagram feed, or
|
54
|
+
media = user_target.media #instagram feed as shorthand
|
56
55
|
if media['next_available']
|
57
|
-
p user_target.
|
56
|
+
p user_target.media max_id: media['next_max_id'] #next page
|
58
57
|
end
|
59
58
|
```
|
60
59
|
## Development
|
@@ -63,6 +62,9 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
63
62
|
|
64
63
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
65
64
|
|
65
|
+
## Testing
|
66
|
+
testcase is using real instagram account, you can safely store your credential in environment variables.
|
67
|
+
|
66
68
|
## Contributing
|
67
69
|
|
68
70
|
Bug reports and pull requests are welcome on GitHub at [https://github.com/vicoerv/Instagram-API](https://github.com/vicoerv/instagram-private-api). This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
data/lib/Instagram.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
require 'Instagram/
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Root module
|
4
|
+
module Instagram
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'Instagram/API'
|
8
|
+
require 'Instagram/CONSTANTS'
|
9
|
+
require 'Instagram/Device'
|
10
|
+
require 'Instagram/User'
|
11
|
+
require 'Instagram/Relationship'
|
@@ -1,5 +1,5 @@
|
|
1
|
-
module Instagram
|
2
|
-
module API
|
3
|
-
VERSION = "0.
|
4
|
-
end
|
5
|
-
end
|
1
|
+
module Instagram
|
2
|
+
module API
|
3
|
+
VERSION = "0.2.0"
|
4
|
+
end
|
5
|
+
end
|
data/lib/Instagram/account.rb
CHANGED
@@ -1,69 +1,101 @@
|
|
1
|
-
module Instagram
|
2
|
-
|
3
|
-
def
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
1
|
+
module Instagram
|
2
|
+
class Account
|
3
|
+
def initialized
|
4
|
+
@api = nil
|
5
|
+
end
|
6
|
+
|
7
|
+
def api
|
8
|
+
@api = Instagram::V1.new if @api.nil?
|
9
|
+
|
10
|
+
@api
|
11
|
+
end
|
12
|
+
|
13
|
+
def login(username, password, config = Instagram::Configuration.new)
|
14
|
+
user = User.new username, password
|
15
|
+
|
16
|
+
request = api.post(
|
17
|
+
CONSTANTS::URL + 'accounts/login/',
|
18
|
+
format(
|
19
|
+
'ig_sig_key_version=4&signed_body=%s',
|
20
|
+
Instagram::V1.generate_signature(
|
21
|
+
device_id: user.device_id,
|
22
|
+
login_attempt_user: 0, password: user.password, username: user.username,
|
23
|
+
_csrftoken: 'missing', _uuid: Instagram::V1.generate_uuid
|
24
|
+
)
|
25
|
+
)
|
26
|
+
).with(ua: user.useragent).exec
|
27
|
+
|
28
|
+
json_body = JSON.parse request.body
|
29
|
+
logged_in_user = json_body['logged_in_user']
|
30
|
+
user.data = {
|
31
|
+
id: logged_in_user['pk'],
|
32
|
+
full_name: logged_in_user['full_name'],
|
33
|
+
is_private: logged_in_user['is_private'],
|
34
|
+
profile_pic_url: logged_in_user['profile_pic_url'],
|
35
|
+
profile_pic_id: logged_in_user['profile_pic_id'],
|
36
|
+
is_verified: logged_in_user['is_verified'],
|
37
|
+
is_business: logged_in_user['is_business']
|
38
|
+
}
|
39
|
+
cookies_array = []
|
40
|
+
all_cookies = request.get_fields('set-cookie')
|
41
|
+
all_cookies.each do |cookie|
|
42
|
+
cookies_array.push(cookie.split('; ')[0])
|
43
|
+
end
|
44
|
+
cookies = cookies_array.join('; ')
|
45
|
+
user.config = config
|
46
|
+
user.session = cookies
|
47
|
+
|
48
|
+
user
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.search_for_user_graphql(user, username)
|
52
|
+
endpoint = "https://www.instagram.com/#{username}/?__a=1"
|
53
|
+
result = Instagram::API.http(
|
54
|
+
url: endpoint,
|
55
|
+
method: 'GET',
|
56
|
+
user: user
|
57
|
+
)
|
58
|
+
response = JSON.parse result.body, symbolize_names: true
|
59
|
+
return nil unless response[:user].any?
|
60
|
+
{
|
61
|
+
profile_id: response[:user][:id],
|
62
|
+
external_url: response[:user][:external_url],
|
63
|
+
followers: response[:user][:followed_by][:count],
|
64
|
+
following: response[:user][:follows][:count],
|
65
|
+
full_name: response[:user][:full_name],
|
66
|
+
avatar_url: response[:user][:profile_pic_url],
|
67
|
+
avatar_url_hd: response[:user][:profile_pic_url_hd],
|
68
|
+
username: response[:user][:username],
|
69
|
+
biography: response[:user][:biography],
|
70
|
+
verified: response[:user][:is_verified],
|
71
|
+
medias_count: response[:user][:media][:count],
|
72
|
+
is_private: response[:user][:is_private]
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
def search_for_user(user, username)
|
77
|
+
rank_token = Instagram::V1.generate_rank_token user.session.scan(/ds_user_id=([\d]+);/)[0][0]
|
78
|
+
endpoint = 'https://i.instagram.com/api/v1/users/search/'
|
79
|
+
param = format('?is_typehead=true&q=%s&rank_token=%s', username, rank_token)
|
80
|
+
result = api.get(endpoint + param)
|
81
|
+
.with(session: user.session, ua: user.useragent).exec
|
82
|
+
|
83
|
+
json_result = JSON.parse result.body
|
84
|
+
if json_result['num_results'] > 0
|
85
|
+
user_result = json_result['users'][0]
|
86
|
+
user_object = Instagram::User.new username, nil
|
87
|
+
user_object.data = {
|
88
|
+
id: user_result['pk'],
|
89
|
+
full_name: user_result['full_name'],
|
90
|
+
is_private: user_result['is_prive'],
|
91
|
+
profile_pic_url: user_result['profile_pic_url'],
|
92
|
+
profile_pic_id: user_result['profile_pic_id'],
|
93
|
+
is_verified: user_result['is_verified'],
|
94
|
+
is_business: user_result['is_business']
|
95
|
+
}
|
96
|
+
user_object.session = user.session
|
97
|
+
user_object
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/lib/Instagram/api.rb
CHANGED
@@ -1,66 +1,103 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require '
|
4
|
-
require '
|
5
|
-
require '
|
6
|
-
require '
|
7
|
-
require '
|
8
|
-
require '
|
9
|
-
require 'Instagram/
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
end
|
33
|
-
|
34
|
-
def self.
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
end
|
65
|
-
|
66
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'Instagram/API/version'
|
4
|
+
require 'openssl'
|
5
|
+
require 'Base64'
|
6
|
+
require 'digest/md5'
|
7
|
+
require 'net/http'
|
8
|
+
require 'json'
|
9
|
+
require 'Instagram/User'
|
10
|
+
require 'Instagram/account'
|
11
|
+
require 'Instagram/feed'
|
12
|
+
require 'Instagram/Configuration'
|
13
|
+
|
14
|
+
module Instagram
|
15
|
+
class V1
|
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 = V1.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: Instagram::CONSTANTS::HEADER[:accept],
|
90
|
+
'Accept-Encoding': Instagram::CONSTANTS::HEADER[:encoding],
|
91
|
+
'Accept-Language': args.dig(:user)&.language,
|
92
|
+
'X-IG-Capabilities': Instagram::CONSTANTS::HEADER[:capabilities],
|
93
|
+
'X-IG-Connection-Type': Instagram::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, Instagram::V1.generate_uuid)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|