ig_api 0.0.15 → 0.0.16
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +3 -3
- data/.rubocop.yml +3 -3
- data/.travis.yml +5 -5
- data/CODE_OF_CONDUCT.md +74 -74
- data/Gemfile +5 -5
- data/Gemfile.lock +38 -38
- data/LICENSE.txt +21 -21
- data/README.md +56 -56
- data/Rakefile +6 -6
- data/bin/console +14 -14
- data/bin/setup +8 -8
- data/ig_api.gemspec +38 -38
- data/lib/ig_api/account.rb +118 -115
- data/lib/ig_api/configuration.rb +4 -4
- data/lib/ig_api/constants.rb +22 -22
- data/lib/ig_api/device.rb +11851 -11851
- data/lib/ig_api/feed.rb +65 -77
- data/lib/ig_api/http.rb +119 -120
- data/lib/ig_api/thread.rb +56 -3
- data/lib/ig_api/user.rb +128 -120
- data/lib/ig_api/version.rb +3 -3
- data/lib/ig_api.rb +11 -11
- metadata +2 -2
data/lib/ig_api/feed.rb
CHANGED
@@ -1,77 +1,65 @@
|
|
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
|
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
|
-
result = IgApi::API.http(
|
58
|
-
url: endpoint + param,
|
59
|
-
method: 'GET',
|
60
|
-
user: user
|
61
|
-
)
|
62
|
-
JSON.parse result.body
|
63
|
-
end
|
64
|
-
|
65
|
-
|
66
|
-
endpoint = "https://i.instagram.com/api/v1/friendships/#{user_id}/followers/"
|
67
|
-
param = "?rank_token=#{data[:rank_token]}" +
|
68
|
-
(!data[:max_id].nil? ? '&max_id=' + data[:max_id] : '')
|
69
|
-
result = IgApi::API.http(
|
70
|
-
url: endpoint + param,
|
71
|
-
method: 'GET',
|
72
|
-
user: user
|
73
|
-
)
|
74
|
-
JSON.parse result.body
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
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 story(ids)
|
17
|
+
signature = IgApi::Http.generate_signature(
|
18
|
+
user_ids: ids.map(&:to_s)
|
19
|
+
)
|
20
|
+
response = @api.post(Constants::URL + 'feed/reels_media/',
|
21
|
+
"ig_sig_key_version=4&signed_body=#{signature}")
|
22
|
+
.with(session: @user[:session], ua: @user[:ua])
|
23
|
+
.exec
|
24
|
+
|
25
|
+
response.body
|
26
|
+
end
|
27
|
+
|
28
|
+
def timeline_media
|
29
|
+
user_id = @user[:id]
|
30
|
+
|
31
|
+
rank_token = IgApi::Http.generate_rank_token @user[:id]
|
32
|
+
endpoint = "https://i.instagram.com/api/v1/feed/user/#{user_id}/"
|
33
|
+
result = @api.get(endpoint + "?rank_token=#{rank_token}")
|
34
|
+
.with(session: @user[:session], ua: @user[:ua]).exec
|
35
|
+
|
36
|
+
JSON.parse result.body
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.user_followers(user, data, limit)
|
40
|
+
has_next_page = true
|
41
|
+
followers = []
|
42
|
+
user_id = (!data[:id].nil? ? data[:id] : user.data[:id])
|
43
|
+
data[:rank_token] = IgApi::API.generate_rank_token user.session.scan(/ds_user_id=([\d]+);/)[0][0]
|
44
|
+
while has_next_page && limit > followers.size
|
45
|
+
response = user_followers_next_page(user, user_id, data)
|
46
|
+
has_next_page = !response['next_max_id'].nil?
|
47
|
+
data[:max_id] = response['next_max_id']
|
48
|
+
followers += response['users']
|
49
|
+
end
|
50
|
+
limit.infinite? ? followers : followers[0...limit]
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.user_followers_next_page(user, user_id, data)
|
54
|
+
endpoint = "https://i.instagram.com/api/v1/friendships/#{user_id}/followers/"
|
55
|
+
param = "?rank_token=#{data[:rank_token]}" +
|
56
|
+
(!data[:max_id].nil? ? '&max_id=' + data[:max_id] : '')
|
57
|
+
result = IgApi::API.http(
|
58
|
+
url: endpoint + param,
|
59
|
+
method: 'GET',
|
60
|
+
user: user
|
61
|
+
)
|
62
|
+
JSON.parse result.body
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/ig_api/http.rb
CHANGED
@@ -1,120 +1,119 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'ig_api/version'
|
4
|
-
require 'openssl'
|
5
|
-
require 'net/http'
|
6
|
-
require 'json'
|
7
|
-
require 'ig_api/user'
|
8
|
-
require 'ig_api/account'
|
9
|
-
require 'ig_api/feed'
|
10
|
-
require 'ig_api/configuration'
|
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
|
-
data
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
args[:url]
|
81
|
-
|
82
|
-
|
83
|
-
http.
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
Accept: IgApi::Constants::HEADER[:
|
104
|
-
'Accept-
|
105
|
-
'
|
106
|
-
'X-IG-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ig_api/version'
|
4
|
+
require 'openssl'
|
5
|
+
require 'net/http'
|
6
|
+
require 'json'
|
7
|
+
require 'ig_api/user'
|
8
|
+
require 'ig_api/account'
|
9
|
+
require 'ig_api/feed'
|
10
|
+
require 'ig_api/configuration'
|
11
|
+
|
12
|
+
module IgApi
|
13
|
+
class Http
|
14
|
+
def self.compute_hash(data)
|
15
|
+
OpenSSL::HMAC.hexdigest OpenSSL::Digest.new('sha256'), Constants::PRIVATE_KEY[:SIG_KEY], data
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.__obj=(value)
|
19
|
+
@@obj = value
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.__obj
|
23
|
+
@@obj
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.singleton
|
27
|
+
@@obj = Http.new unless defined? @@obj
|
28
|
+
|
29
|
+
@@obj
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.generate_uuid
|
33
|
+
'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.gsub(/[xy]/) do |c|
|
34
|
+
r = (Random.rand * 16).round | 0
|
35
|
+
v = c == 'x' ? r : (r & 0x3 | 0x8)
|
36
|
+
c.gsub(c, v.to_s(16))
|
37
|
+
end.downcase
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.create_md5(data)
|
41
|
+
Digest::MD5.hexdigest(data).to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.generate_device_id
|
45
|
+
timestamp = Time.now.to_i.to_s
|
46
|
+
'android-' + create_md5(timestamp)[0..16]
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.generate_signature(data)
|
50
|
+
data = data.to_json
|
51
|
+
compute_hash(data) + '.' + data
|
52
|
+
end
|
53
|
+
|
54
|
+
def post(url, body = nil)
|
55
|
+
@data = { method: 'POST', url: url, body: body }
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
def multipart(url, body = nil)
|
60
|
+
@data = { method: 'MULTIPART', url: url, body: body }
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
def with(data)
|
65
|
+
data.each { |k, v| @data[k] = v }
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
def exec
|
70
|
+
http @data
|
71
|
+
end
|
72
|
+
|
73
|
+
def get(url)
|
74
|
+
@data = {method: 'GET', url: url}
|
75
|
+
self
|
76
|
+
end
|
77
|
+
|
78
|
+
def http(args)
|
79
|
+
args[:url] = URI.parse(args[:url])
|
80
|
+
http = Net::HTTP.new(args[:url].host, args[:url].port,
|
81
|
+
ENV['INSTAGRAM_PROXY_HOST'], ENV['INSTAGRAM_PROXY_PORT'])
|
82
|
+
http.use_ssl = true
|
83
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
84
|
+
request = nil
|
85
|
+
if args[:method] == 'POST'
|
86
|
+
request = Net::HTTP::Post.new(args[:url].path)
|
87
|
+
elsif args[:method] == 'GET'
|
88
|
+
request = Net::HTTP::Get.new(args[:url].path + (!args[:url].nil? ? '?' + args[:url].query : ''))
|
89
|
+
elsif args[:method] == 'MULTIPART'
|
90
|
+
request = Net::HTTP::Post::Multipart.new args[:url].path, args[:body],
|
91
|
+
'User-Agent': args[:ua],
|
92
|
+
Accept: IgApi::Constants::HEADER[:accept],
|
93
|
+
'Accept-Encoding': IgApi::Constants::HEADER[:encoding],
|
94
|
+
'Accept-Language': 'en-US',
|
95
|
+
'X-IG-Capabilities': IgApi::Constants::HEADER[:capabilities],
|
96
|
+
'X-IG-Connection-Type': IgApi::Constants::HEADER[:type],
|
97
|
+
Cookie: args[:session] || ''
|
98
|
+
end
|
99
|
+
|
100
|
+
unless args[:method] == 'MULTIPART'
|
101
|
+
request.initialize_http_header('User-Agent': args[:ua],
|
102
|
+
Accept: IgApi::Constants::HEADER[:accept],
|
103
|
+
'Accept-Encoding': IgApi::Constants::HEADER[:encoding],
|
104
|
+
'Accept-Language': 'en-US',
|
105
|
+
'X-IG-Capabilities': IgApi::Constants::HEADER[:capabilities],
|
106
|
+
'X-IG-Connection-Type': IgApi::Constants::HEADER[:type],
|
107
|
+
Cookie: args[:session] || '')
|
108
|
+
|
109
|
+
request.body = args.key?(:body) ? args[:body] : nil
|
110
|
+
end
|
111
|
+
|
112
|
+
http.request(request)
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.generate_rank_token(pk)
|
116
|
+
format('%s_%s', pk, IgApi::Http.generate_uuid)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
data/lib/ig_api/thread.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
1
3
|
module IgApi
|
2
4
|
class Thread
|
3
5
|
def initialize
|
@@ -6,6 +8,7 @@ module IgApi
|
|
6
8
|
|
7
9
|
def using(user)
|
8
10
|
@user = {
|
11
|
+
id: user.data[:id],
|
9
12
|
session: user.session,
|
10
13
|
ua: user.useragent
|
11
14
|
}
|
@@ -14,14 +17,64 @@ module IgApi
|
|
14
17
|
end
|
15
18
|
|
16
19
|
def configure_text(users, text)
|
20
|
+
uris = URI.extract(text, %w[http https])
|
21
|
+
broadcast = 'text'
|
22
|
+
|
17
23
|
body = {
|
18
24
|
recipient_users: [users].to_json,
|
19
25
|
client_context: Http.generate_uuid,
|
20
|
-
text: text
|
21
26
|
}
|
22
27
|
|
23
|
-
|
24
|
-
|
28
|
+
if uris.empty?
|
29
|
+
body[:text] = text
|
30
|
+
else
|
31
|
+
broadcast = 'link'
|
32
|
+
body[:link_text] = text
|
33
|
+
body[:link_urls] = uris.to_json
|
34
|
+
end
|
35
|
+
|
36
|
+
response = @api.multipart(Constants::URL +
|
37
|
+
"direct_v2/threads/broadcast/#{broadcast}/",
|
38
|
+
body)
|
39
|
+
.with(ua: @user[:ua], session: @user[:session])
|
40
|
+
.exec
|
41
|
+
|
42
|
+
response.body
|
43
|
+
end
|
44
|
+
|
45
|
+
def configure_media(users, media_id, text)
|
46
|
+
payload = {
|
47
|
+
recipient_users: [users].to_json,
|
48
|
+
client_context: IgApi::Http.generate_uuid,
|
49
|
+
media_id: media_id
|
50
|
+
}
|
51
|
+
|
52
|
+
payload[:text] = text unless text.empty?
|
53
|
+
response = @api.multipart(Constants::URL + 'direct_v2/threads/broadcast/media_share/?media_type=photo',
|
54
|
+
payload)
|
55
|
+
.with(session: @user[:session], ua: @user[:ua])
|
56
|
+
.exec
|
57
|
+
|
58
|
+
response.body
|
59
|
+
end
|
60
|
+
|
61
|
+
def configure_story(users, media_id, text)
|
62
|
+
payload = {
|
63
|
+
action: 'send_item',
|
64
|
+
_uuid: IgApi::Http.generate_uuid,
|
65
|
+
client_context: IgApi::Http.generate_uuid,
|
66
|
+
recipient_users: [users].to_json,
|
67
|
+
story_media_id: media_id,
|
68
|
+
reel_id: media_id.split('_')[1],
|
69
|
+
text: text
|
70
|
+
}
|
71
|
+
|
72
|
+
signature = Http.generate_signature payload
|
73
|
+
|
74
|
+
response = @api.post(
|
75
|
+
Constants::URL + 'direct_v2/threads/broadcast/story_share/',
|
76
|
+
"ig_sig_key_version=4&signed_body=#{signature}"
|
77
|
+
)
|
25
78
|
.with(ua: @user[:ua], session: @user[:session])
|
26
79
|
.exec
|
27
80
|
|