instagram_native 0.0.7

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6e3aab885f5545a089907df02b9a16d99cd30581
4
+ data.tar.gz: 441a17386a8444516b3d27dbf643311fe7fbe584
5
+ SHA512:
6
+ metadata.gz: d0ab4dbac71165ed6d0e59a79ee90629318a469453fd9f4d1a818678db4dfa31025b7785e93c7c085dc8dfc6ab1e191d2e4a12a9c4750933ce08e0cb47bc60d6
7
+ data.tar.gz: 320b484e43e5be7877b7d020959659444c51f8be2034680532f1e57e3e74da6d607c83898cab1aebcae08ba4673b527170f0324aed5ed5c6d258e1b0d4119680
@@ -0,0 +1,15 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'instagram_native'
3
+ s.version = '0.0.7'
4
+ s.date = '2015-01-31'
5
+ s.summary = "Instagram native API"
6
+ s.description = "Instagram native API realization"
7
+ s.authors = ["Denis Kuznetsov"]
8
+ s.email = 'denis@f7.ru'
9
+ s.files = ["lib/instagram_native.rb", "lib/instagram/native_client.rb", "lib/instagram/invalid_credentials.rb", "lib/instagram/register_exception.rb", "lib/instagram/it_was_me_required.rb", "instagram_native.gemspec"]
10
+ s.license = 'MIT'
11
+
12
+
13
+ s.add_runtime_dependency "httpclient"
14
+ s.add_runtime_dependency "json"
15
+ end
@@ -0,0 +1,4 @@
1
+ module Instagram
2
+ class InvalidCredentials < StandardError
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Instagram
2
+ class ItWasMeRequired < StandardError
3
+ end
4
+ end
@@ -0,0 +1,352 @@
1
+ require 'cgi'
2
+
3
+ module Instagram
4
+ class NativeClient
5
+ attr_accessor :http, :private_key, :useragent, :ig_user_id, :ig_user_name, :csrftoken, :device_guid, :proxy, :device_id
6
+
7
+ def initialize(proxy, dg: '4da9753f-4663-4c89-a99a-7c1bc6d188fe', ua: 'Instagram 6.14.1 Android (16/4.1.2; 240dpi; 540x960; Sony/SEMC; LT22i; LT22i; st-ericsson; ru_RU)', device_id: nil)
8
+ @device_guid = generate_device_guid #dg
9
+ @device_id = device_id || generate_device_id
10
+ @useragent = ua
11
+ m = proxy.match(/(\w+):(\w+)@([\w\.]+):(\d+)/) if proxy
12
+ @proxy = proxy
13
+ if m
14
+ host_port = "http://#{m[3]}:#{m[4]}"
15
+ @http = HTTPClient.new(host_port, @useragent)
16
+ @http.set_proxy_auth(m[1], m[2])
17
+ else
18
+ if proxy
19
+ puts "proxy is: #{proxy}"
20
+ host_port = "http://#{proxy}"
21
+ @http = HTTPClient.new(host_port, @useragent)
22
+ else
23
+ @http = HTTPClient.new(nil, @useragent)
24
+ end
25
+ end
26
+ @http.ssl_config.options |= OpenSSL::SSL::OP_NO_SSLv3
27
+ end
28
+
29
+
30
+ def login(login, password)
31
+ login_info = {:_csrftoken => "missing",
32
+ :username => login,
33
+ :guid => @device_guid,
34
+ :password => password,
35
+ :device_id => @device_id
36
+ }
37
+
38
+ result = post_struct "https://i.instagram.com/api/v1/accounts/login/", login_info
39
+ puts "login result: #{result.inspect}"
40
+
41
+ if 'fail' == result['status']
42
+ if result['message'] == 'checkpoint_required'
43
+ raise ItWasMeRequired
44
+ else
45
+ raise result['message']
46
+ end
47
+ else
48
+ begin
49
+ @ig_user_id = result['logged_in_user']['pk']
50
+ @ig_user_name = result['logged_in_user']['username']
51
+ @csrftoken = @http.cookies.select {|c| c.name == 'csrftoken'}.first.value
52
+ rescue => ex
53
+ Rails.logger.error ex.message
54
+ Rails.logger.error "Server answer: #{result}"
55
+ raise ex
56
+ end
57
+ end
58
+ end
59
+
60
+ def upload_photo(file_path, description)
61
+ File.open(file_path) do |file|
62
+ photo_info = {
63
+ :_csrftoken => @csrftoken,
64
+ :upload_id => Time.now.to_i,
65
+ :photo => file
66
+ }
67
+
68
+ @http.cookies.each {|c| c.domain = 'i.instagram.com'}
69
+ res = @http.post("http://i.instagram.com/api/v1/upload/photo/", photo_info)
70
+
71
+ puts "Upload photo result: #{res.body}"
72
+
73
+ result = JSON.parse(res.body)
74
+ upload_id = result['upload_id']
75
+
76
+ caption_info = {:upload_id => upload_id,
77
+ :source_type => "3",
78
+ :filter_type => "0",
79
+ :device => {
80
+ :manufacturer => "Sony",
81
+ :model => "LT22i",
82
+ :android_version => 16,
83
+ :android_release => "4.1.2"
84
+ },
85
+ :_csrftoken => @csrftoken,
86
+ :_uuid => @device_guid,
87
+ :_uid => ig_user_id}
88
+ caption_info.merge!({:caption => description}) unless description.blank?
89
+
90
+ @http.cookies.each {|c| c.domain = 'i.instagram.com'}
91
+ parsed = post_struct "https://i.instagram.com/api/v1/media/configure/", caption_info
92
+
93
+ result_hash = { server_answer: parsed.to_json }
94
+
95
+ if parsed['status'] == 'ok'
96
+ result_hash[:social_shortcode] = parsed['media']['code']
97
+ result_hash[:social_id] = parsed['media']['id']
98
+ end
99
+ result_hash
100
+ end
101
+ end
102
+
103
+ def upload_video(file_path, description)
104
+ File.open(file_path) do |file|
105
+ upload_id = Time.now.to_i
106
+ video_info = {
107
+ :_csrftoken => @csrftoken,
108
+ :media_type => 2,
109
+ :upload_id => upload_id
110
+ }
111
+
112
+ @http.cookies.each {|c| c.domain = 'i.instagram.com'}
113
+ res = @http.post("http://i.instagram.com/api/v1/upload/video/", video_info)
114
+ result = JSON.parse(res.body)
115
+
116
+ chunks_number = result['video_upload_urls'].count
117
+ chunk_size = file.size / chunks_number
118
+ bytes_uploaded = 0
119
+
120
+ puts "File size: #{file.size}, chunks_number: #{chunks_number}, chunk_size: #{chunk_size}"
121
+
122
+ video_result = nil
123
+
124
+ puts result['video_upload_urls'].inspect
125
+
126
+ byebug
127
+
128
+ result['video_upload_urls'].each {|upload_struct|
129
+ bytes_rest = file.size - bytes_uploaded
130
+ is_last_chunk = bytes_rest <= chunk_size * 2
131
+
132
+ to_upload = is_last_chunk ? bytes_rest : chunk_size
133
+ file_part = file.read(to_upload)
134
+
135
+ puts "To upload: #{to_upload}, bytes_rest: #{bytes_rest}, bytes_uploaded: #{bytes_uploaded}"
136
+
137
+
138
+ #uri = URI.parse upload_struct['url']
139
+ #@http.cookies.each {|c| c.domain = uri.host}
140
+ result = @http.post(upload_struct['url'], file_part, { "job" => upload_struct['job'],
141
+ "Content-Range" => "bytes #{bytes_uploaded}-#{bytes_uploaded + to_upload - 1}/#{file.size}"
142
+ # "Content-Type" => "application/octet-stream"
143
+ }
144
+ )
145
+ byebug
146
+
147
+ video_result = JSON.parse(result)['result'] if is_last_chunk
148
+ bytes_uploaded += to_upload
149
+ }
150
+
151
+ end
152
+ end
153
+
154
+ def change_profile_picture(picture_path = 'http://lorempixel.com/150/150/')
155
+ filename = get_temp_filename
156
+ begin
157
+ file = File.new(filename, 'wb')
158
+ res = open(picture_path) { |f| f.read }
159
+ file.write(res.to_s)
160
+ file.close
161
+ file = File.open(filename)
162
+ picture_params = {
163
+ :_csrftoken => @csrftoken,
164
+ :profile_pic => file
165
+ }
166
+ @http.cookies.each {|c| c.domain = 'i.instagram.com'}
167
+ res = @http.post("http://i.instagram.com/api/v1/accounts/change_profile_picture/", picture_params)
168
+ ensure
169
+ file.close
170
+ File.unlink filename
171
+ end
172
+ end
173
+
174
+ def send_sms(phone)
175
+ struct = {
176
+ :_csrftoken => @csrftoken,
177
+ :_uid => @ig_user_id,
178
+ :phone_number => phone,
179
+ :_uuid => @device_guid
180
+ }
181
+ parsed = post_struct 'https://i.instagram.com/api/v1/accounts/send_sms_code/', struct
182
+ #puts "Parsed sms result: #{parsed.inspect}"
183
+ parsed['status'] == 'ok' && parsed['phone_number_valid']
184
+ end
185
+
186
+ def verify_account(phone, code)
187
+ struct = {
188
+ :verification_code => code,
189
+ :_uid => @ig_user_id,
190
+ :_csrftoken => @csrftoken,
191
+ :phone_number => phone,
192
+ :_uuid => @device_guid
193
+ }
194
+ parsed = post_struct 'https://i.instagram.com/api/v1/accounts/verify_sms_code/', struct
195
+ puts "Parsed sms result: #{parsed.inspect}"
196
+ parsed['status'] == 'ok' && parsed['verified']
197
+ end
198
+
199
+
200
+ def check_username(uname)
201
+ login_info = {
202
+ :_csrftoken => @csrftoken,
203
+ :username => uname
204
+ }
205
+ parsed = post_struct "https://instagram.com/api/v1/users/check_username/", login_info
206
+ parsed['status'] == 'ok' && parsed['available']
207
+ end
208
+
209
+ def create_account(uname, pass, email = nil, first_name = "")
210
+ email ||= generate_email(uname)
211
+ account_info = {
212
+ :_csrftoken => @csrftoken,
213
+ :first_name => first_name,
214
+ :username => uname,
215
+ :email => email,
216
+ :guid => @device_guid,
217
+ :password => pass,
218
+ :device_id => @device_id
219
+ }
220
+ parsed = post_struct "https://instagram.com/api/v1/accounts/create/", account_info
221
+ raise RegisterException.new(uname, pass, email, @proxy) unless parsed['status'] == 'ok'
222
+ end
223
+
224
+ def get_timeline
225
+ @http.cookies.each {|c| c.domain = 'i.instagram.com'}
226
+ res = @http.get("http://i.instagram.com/api/v1/feed/timeline/") #, "Content-Type" => "application/json"
227
+ end
228
+
229
+ def get_news
230
+ @http.cookies.each {|c| c.domain = 'i.instagram.com'}
231
+ res = @http.get("http://i.instagram.com/api/v1/news/inbox/") #, "Content-Type" => "application/json"
232
+ end
233
+
234
+ def direct_inbox
235
+ @http.cookies.each {|c| c.domain = 'i.instagram.com'}
236
+ res = @http.get("https://i.instagram.com/api/v1/direct_share/inbox/") #"Content-Type" => "application/json"
237
+ end
238
+
239
+ def follow_user(user_id)
240
+ post_struct = {
241
+ :_uid => @ig_user_id,
242
+ :_csrftoken => @csrftoken,
243
+ :user_id => user_id
244
+ }
245
+ parsed = post_struct "http://i.instagram.com/api/v1/friendships/create/#{user_id}/", post_struct
246
+ parsed['status'] == 'ok'
247
+ end
248
+
249
+ def unfollow_user(user_id)
250
+ post_struct = {
251
+ :_uid => @ig_user_id,
252
+ :_csrftoken => @csrftoken,
253
+ :user_id => user_id
254
+ }
255
+ parsed = post_struct "http://i.instagram.com/api/v1/friendships/destroy/#{user_id}/", post_struct
256
+ parsed['status'] == 'ok'
257
+ end
258
+
259
+ def like_media(media_id)
260
+ post_struct = {
261
+ :_uid => @ig_user_id,
262
+ :_csrftoken => @csrftoken,
263
+ :media_id => media_id
264
+ }
265
+
266
+ parsed = post_struct "http://i.instagram.com/api/v1/media/#{media_id}/like/", post_struct, {src: "timeline", d: 0}, "application/x-www-form-urlencoded; charset=UTF-8"
267
+ parsed['status'] == 'ok'
268
+ end
269
+
270
+ def search_user(query)
271
+ @http.get("http://i.instagram.com/api/v1/users/search/?is_typeahead=true&rank_token=#{@ig_user_id}_e9730a31-df06-4c97-aaef-56cdc2601c1a&q=#{query}")
272
+ end
273
+
274
+ def media_by_tag(tag)
275
+ @http.get("http://i.instagram.com/api/v1/feed/tag/#{tag}/?max_id=#{(Time.now.to_f * 1000).to_i}")
276
+ end
277
+
278
+ def user_follows(user_id = nil)
279
+ user_id ||= @ig_user_id
280
+ @http.get("http://i.instagram.com/api/v1/friendships/#{user_id}/following/")
281
+ end
282
+
283
+ def user_followed(user_id = nil)
284
+ user_id ||= @ig_user_id
285
+ @http.get("http://i.instagram.com/api/v1/friendships/#{user_id}/followers/")
286
+ end
287
+
288
+
289
+
290
+ private
291
+
292
+ def sign_structure(struct)
293
+ jsoned = struct.to_json
294
+ signature = get_signature(jsoned)
295
+ signed_body = "#{signature}.#{jsoned}"
296
+ {:signed_body => signed_body, :ig_sig_key_version => 4}
297
+ end
298
+
299
+ def get_signature(jsoned)
300
+ s = Time.now.to_f.to_s.last(10).gsub('.', '')
301
+ filename = "#{s}.txt"
302
+ local_path = "#{Rails.root}/tmp/signatures/#{filename}"
303
+ File.write(local_path, jsoned)
304
+
305
+ %x[#{ADB_PATH} push #{local_path} /data/byte/signatures/]
306
+
307
+ File.delete local_path
308
+
309
+ #e = CGI.escape(jsoned)
310
+ signature = %x[#{ADB_PATH} shell dalvikvm -Djava.library.path=/data/byte/ -cp /data/byte/app.jar App #{filename}]
311
+ puts "signature: #{signature}. for string: #{jsoned}"
312
+ signature
313
+ end
314
+
315
+ def generate_device_id
316
+ res = ""
317
+ arr = ['1', '2', "3", '4', '5', '6', '7', '8', '9', '0']
318
+ 16.times { res += arr.sample }
319
+ "android-#{res}"
320
+ end
321
+
322
+ def generate_device_guid
323
+ "#{hex_digits(8)}-#{hex_digits(4)}-#{hex_digits(4)}-#{hex_digits(4)}-#{hex_digits(12)}"
324
+ #4da9753f-4663-4c89-a99a-7c1bc6d188fe
325
+ end
326
+
327
+ def generate_email(login)
328
+ "#{login}@mail.ru"
329
+ end
330
+
331
+ def hex_digits(count)
332
+ hex = ['1', '2', "3", '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e', 'f']
333
+ res = ''
334
+ count.times { res += hex.sample }
335
+ res
336
+ end
337
+
338
+ def get_temp_filename
339
+ s = Time.now.to_f.to_s.last(10).gsub('.', '')
340
+ "#{s}.jpg"
341
+ end
342
+
343
+
344
+ def post_struct(url, structure, options = {}, content_type = "application/json")
345
+ #puts "Making POST to: #{url}, structure: #{structure.inspect}"
346
+ result = @http.post(url, sign_structure(structure).merge(options), "Content-Type" => content_type)
347
+ JSON.parse(result.body)
348
+ end
349
+
350
+ end
351
+
352
+ end
@@ -0,0 +1,17 @@
1
+ module Instagram
2
+ class RegisterException < StandardError
3
+ attr_accessor :login, :password, :email, :proxy
4
+
5
+ def initialize(login, password, email, proxy)
6
+ @login = login
7
+ @password = password
8
+ @email = email
9
+ @proxy = proxy
10
+ end
11
+
12
+ def message
13
+ "Error registering Instagram account: #{@login}:#{@password}, proxy: #{@proxy}"
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,4 @@
1
+ require 'instagram/native_client'
2
+ require 'instagram/register_exception'
3
+ require 'instagram/invalid_credentials'
4
+ require 'instagram/it_was_me_required'
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: instagram_native
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.7
5
+ platform: ruby
6
+ authors:
7
+ - Denis Kuznetsov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-01-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: httpclient
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Instagram native API realization
42
+ email: denis@f7.ru
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - instagram_native.gemspec
48
+ - lib/instagram/invalid_credentials.rb
49
+ - lib/instagram/it_was_me_required.rb
50
+ - lib/instagram/native_client.rb
51
+ - lib/instagram/register_exception.rb
52
+ - lib/instagram_native.rb
53
+ homepage:
54
+ licenses:
55
+ - MIT
56
+ metadata: {}
57
+ post_install_message:
58
+ rdoc_options: []
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ requirements: []
72
+ rubyforge_project:
73
+ rubygems_version: 2.4.5
74
+ signing_key:
75
+ specification_version: 4
76
+ summary: Instagram native API
77
+ test_files: []
78
+ has_rdoc: