instagram_native 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/instagram_native.gemspec +15 -0
- data/lib/instagram/invalid_credentials.rb +4 -0
- data/lib/instagram/it_was_me_required.rb +4 -0
- data/lib/instagram/native_client.rb +352 -0
- data/lib/instagram/register_exception.rb +17 -0
- data/lib/instagram_native.rb +4 -0
- metadata +78 -0
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,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
|
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:
|