ig_api 0.0.15 → 0.0.16

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/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