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.
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 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
- result = @api.get(endpoint + "?rank_token=#{rank_token}")
22
- .with(session: @user[:session], ua: @user[:ua]).exec
23
-
24
- JSON.parse result.body
25
- end
26
-
27
- def self.user_followers(user, data, limit)
28
- has_next_page = true
29
- followers = []
30
- user_id = (!data[:id].nil? ? data[:id] : user.data[:id])
31
- data[:rank_token] = IgApi::API.generate_rank_token user.session.scan(/ds_user_id=([\d]+);/)[0][0]
32
- while has_next_page && limit > followers.size
33
- response = user_followers_next_page(user, user_id, data)
34
- has_next_page = !response['next_max_id'].nil?
35
- data[:max_id] = response['next_max_id']
36
- followers += response['users']
37
- end
38
- limit.infinite? ? followers : followers[0...limit]
39
- end
40
-
41
- def self.user_followers_graphql(user, data, limit)
42
- has_next_page = true
43
- followers = []
44
- user_id = (!data[:id].nil? ? data[:id] : user.data[:id])
45
- while has_next_page && limit > followers.size
46
- response = user_followers_graphql_next_page(user, user_id, data)
47
- has_next_page = response['data']['user']['edge_followed_by']['page_info']['has_next_page']
48
- data[:end_cursor] = response['data']['user']['edge_followed_by']['page_info']['end_cursor']
49
- followers += response['data']['user']['edge_followed_by']['edges']
50
- end
51
- limit.infinite? ? followers : followers[0...limit]
52
- end
53
-
54
- def self.user_followers_graphql_next_page(user, user_id, data)
55
- endpoint = "https://www.instagram.com/graphql/query/?query_id=17851374694183129&id=#{user_id}&first=5000"
56
- param = (!data[:end_cursor].nil? ? "&after=#{data[:end_cursor]}" : '')
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
- def self.user_followers_next_page(user, user_id, data)
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
- require 'net/http/post/multipart'
12
-
13
- module IgApi
14
- class Http
15
- def self.compute_hash(data)
16
- OpenSSL::HMAC.hexdigest OpenSSL::Digest.new('sha256'), Constants::PRIVATE_KEY[:SIG_KEY], data
17
- end
18
-
19
- def self.__obj=value
20
- @@obj = value
21
- end
22
-
23
- def self.__obj
24
- @@obj
25
- end
26
-
27
- def self.singleton
28
- @@obj = Http.new unless defined? @@obj
29
-
30
- @@obj
31
- end
32
-
33
- def self.generate_uuid
34
- 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.gsub(/[xy]/) do |c|
35
- r = (Random.rand * 16).round | 0
36
- v = c == 'x' ? r : (r & 0x3 | 0x8)
37
- c.gsub(c, v.to_s(16))
38
- end.downcase
39
- end
40
-
41
- def self.create_md5(data)
42
- Digest::MD5.hexdigest(data).to_s
43
- end
44
-
45
- def self.generate_device_id
46
- timestamp = Time.now.to_i.to_s
47
- 'android-' + create_md5(timestamp)[0..16]
48
- end
49
-
50
- def self.generate_signature(data)
51
- data = data.to_json
52
- compute_hash(data) + '.' + data
53
- end
54
-
55
- def post(url, body = nil)
56
- @data = { method: 'POST', url: url, body: body }
57
- self
58
- end
59
-
60
- def multipart(url, body = nil)
61
- @data = { method: 'MULTIPART', url: url, body: body }
62
- self
63
- end
64
-
65
- def with(data)
66
- data.each { |k, v| @data[k] = v }
67
- self
68
- end
69
-
70
- def exec
71
- http @data
72
- end
73
-
74
- def get(url)
75
- @data = {method: 'GET', url: url}
76
- self
77
- end
78
-
79
- def http(args)
80
- args[:url] = URI.parse(args[:url])
81
- http = Net::HTTP.new(args[:url].host, args[:url].port,
82
- ENV['INSTAGRAM_PROXY_HOST'], ENV['INSTAGRAM_PROXY_PORT'])
83
- http.use_ssl = true
84
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
85
- request = nil
86
- if args[:method] == 'POST'
87
- request = Net::HTTP::Post.new(args[:url].path)
88
- elsif args[:method] == 'GET'
89
- request = Net::HTTP::Get.new(args[:url].path + (!args[:url].nil? ? '?' + args[:url].query : ''))
90
- elsif args[:method] == 'MULTIPART'
91
- request = Net::HTTP::Post::Multipart.new args[:url].path, args[:body],
92
- 'User-Agent': args[:ua],
93
- Accept: IgApi::Constants::HEADER[:accept],
94
- 'Accept-Encoding': IgApi::Constants::HEADER[:encoding],
95
- 'Accept-Language': 'en-US',
96
- 'X-IG-Capabilities': IgApi::Constants::HEADER[:capabilities],
97
- 'X-IG-Connection-Type': IgApi::Constants::HEADER[:type],
98
- Cookie: args[:session] || ''
99
- end
100
-
101
- unless args[:method] == 'MULTIPART'
102
- request.initialize_http_header('User-Agent': args[:ua],
103
- Accept: IgApi::Constants::HEADER[:accept],
104
- 'Accept-Encoding': IgApi::Constants::HEADER[:encoding],
105
- 'Accept-Language': 'en-US',
106
- 'X-IG-Capabilities': IgApi::Constants::HEADER[:capabilities],
107
- 'X-IG-Connection-Type': IgApi::Constants::HEADER[:type],
108
- Cookie: args[:session] || '')
109
-
110
- request.body = args.key?(:body) ? args[:body] : nil
111
- end
112
-
113
- http.request(request)
114
- end
115
-
116
- def self.generate_rank_token(pk)
117
- format('%s_%s', pk, IgApi::Http.generate_uuid)
118
- end
119
- end
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
- response = @api.multipart(Constants::URL + 'direct_v2/threads/broadcast/text/',
24
- body)
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