vk_music 2.2.1 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,298 +1,139 @@
1
- ##
2
- # @!macro [new] options_hash_param
3
- # @param options [Hash] hash with options.
4
- #
5
- # @!macro [new] playlist_return
6
- # @return [Playlist] playlist with audios. Possibly empty.
7
- # Possibly contains audios without download URL.
8
-
9
- ##
10
- # Main module.
11
1
  module VkMusic
12
-
13
2
  ##
14
3
  # Main class with all the interface.
15
- # To start working with VK audios firstly create new client with +Client.new+.
16
4
  class Client
17
-
18
5
  ##
19
6
  # @return [Integer] ID of client.
20
7
  attr_reader :id
21
-
22
8
  ##
23
9
  # @return [String] name of client.
24
10
  attr_reader :name
25
-
26
- @agent = nil # Mechanize agent
11
+ ##
12
+ # @return [Mechanize] client used to access web pages.
13
+ attr_reader :agent
27
14
 
28
15
  ##
29
16
  # Create new client and login.
30
- #
31
- # @macro options_hash_param
32
- #
33
- # @option options [String] :username usually telephone number or email.
34
- # @option options [String] :password
35
- # @option options [String] :user_agent (Constants::DEFAULT_USER_AGENT)
36
- def initialize(options = {})
37
- # Arguments check
38
- raise ArgumentError, "Options hash must be provided", caller unless options.class == Hash
39
- raise ArgumentError, "Username is not provided", caller unless options.has_key?(:username)
40
- raise ArgumentError, "Password is not provided", caller unless options.has_key?(:password)
41
-
17
+ # @param username [String] usually telephone number or email.
18
+ # @param password [String]
19
+ # @param user_agent [String]
20
+ def initialize(username: "", password: "", user_agent: Constants::DEFAULT_USER_AGENT)
21
+ raise ArgumentError if username.empty? || password.empty?
42
22
  # Setting up client
43
23
  @agent = Mechanize.new
44
- @agent.user_agent = options[:user_agent] || Constants::DEFAULT_USER_AGENT
45
- login(options[:username], options[:password])
24
+ @agent.user_agent = user_agent
25
+ login(username, password)
46
26
  end
47
27
 
48
28
  ##
49
29
  #@!group Loading audios
50
-
30
+
51
31
  ##
52
- # @!macro [new] find__options
53
- # @option options [Symbol] :type (:audio) what to search for (you can find available values for this option above).
54
- #
55
32
  # Search for audio or playlist.
56
- #
57
- # @note some audios and playlists might be removed from search.
58
- #
59
- # @todo search in group audios.
60
- #
61
33
  # Possible values of +type+ option:
62
34
  # * +:audio+ - search for audios. Returns up to 50 audios.
63
- # * +:playlist+ - search for playlists. Returns up to 6 playlists *without* audios (Loaded with +up_to: 0+ option).
35
+ # * +:playlist+ - search for playlists. Returns up to 6 playlists *without* audios (Loaded with +up_to: 0+ option).
64
36
  # You can get all the audios of selected playlist calling {Client#playlist} method with gained info.
65
- #
66
- # @overload find(query, options)
67
- # @param query [String] string to search for.
68
- # @macro options_hash_param
69
- # @macro find__options
70
- #
71
- # @overload find(options)
72
- # @macro options_hash_param
73
- # @option options [String] :query string to search for.
74
- # @macro find__options
75
- #
76
- # @return [Array<Audio>, Array<Playlist>] array with audios or playlists matching given string.
77
- # Possibly empty. Possibly contains audios or playlists without download URL.
78
- def find(*args)
79
- begin
80
- case
81
- when (args.size == 1 && String === args[0]) ||
82
- (args.size == 2 && String === args[0] && Hash === args[1])
83
- options = args[1] || {}
84
- query = args[0]
85
- when args.size == 1 && Hash === args[0]
86
- options = args[0]
87
- query = options[:query].to_s
88
- else
89
- raise
90
- end
91
- rescue
92
- raise ArgumentError, "Bad arguments", caller
93
- end
94
-
95
- options[:type] ||= :audio
96
-
37
+ # @note some audios and playlists might be removed from search.
38
+ # @todo search in group audios.
39
+ # @param query [String] search query.
40
+ # @param type [Symbol] what to search for.
41
+ # @return [Array<Audio>, Array<Playlist>] array with audios or playlists
42
+ # matching given string.
43
+ def find(query = "", type: :audio)
44
+ raise ArgumentError if query.empty?
97
45
  uri = URI(Constants::URL::VK[:audios])
98
-
99
- case options[:type]
100
- when :audio
101
- uri.query = Utility.hash_to_params({ "act" => "search", "q" => query })
102
- audios__from_page(uri)
103
- when :playlist
104
- uri.query = Utility.hash_to_params({ "q" => query, "tab" => "global" })
105
- urls = playlist_urls__from_page(uri)
106
- urls.map { |url| playlist(url, up_to: 0, with_url: false) }
107
- else
108
- raise ArgumentError, "Bad :type option", caller
46
+ case type
47
+ when :audio
48
+ uri.query = Utility.hash_to_params({ "act" => "search", "q" => query })
49
+ audios_from_page(uri)
50
+ when :playlist
51
+ uri.query = Utility.hash_to_params({ "q" => query, "tab" => "global" })
52
+ urls = playlist_urls_from_page(uri)
53
+ urls.map { |url| playlist(url: url, up_to: 0, use_web: false) }
54
+ else
55
+ raise ArgumentError
109
56
  end
110
57
  end
111
- alias search find
112
-
58
+ alias_method :search, :find
59
+
113
60
  ##
114
- # @!macro [new] pl__options
115
- # @option options [Integer] :up_to (MAXIMUM_PLAYLIST_SIZE) maximum amount of audios to load.
116
- # If 0, no audios would be loaded (Just information about playlist).
117
- # If less than 0, will load whole playlist.
118
- # @option options [Boolean] :with_url (true) makes all the audios have download URLs,
119
- # but every 100 audios will cost one more request. You can reduce amount of requests using option +up_to+.
120
- # Otherwise audio download URL would be accessable only with {Client#from_id}.
121
- # Main advantage of disabling URLs is the fact that 2000 audios will be loaded per request,
122
- # which is 20 times more effecient.
123
- #
124
61
  # Get VK playlist.
125
- #
126
- # @overload playlist(url, options)
127
- # @param url [String] URL to playlist.
128
- # @macro options_hash_param
129
- # @macro pl__options
130
- #
131
- # @overload playlist(options)
132
- # Use options +owner_id+, +playlist_id+ and +access_hash+ instead of URL.
133
- # @macro options_hash_param
134
- # @option options [Integer] :owner_id playlist owner ID.
135
- # @option options [Integer] :playlist_id ID of the playlist.
136
- # @option options [String] :access_hash access hash to playlist. Might not exist.
137
- # @macro pl__options
138
- #
139
- # @macro playlist_return
140
- def playlist(*args)
62
+ # Specify either +url+ or +(owner_id,playlist_id,access_hash)+.
63
+ # @note since updating URLs can take a lot of time in this case, you have to
64
+ # do it manually with {Client#update_urls}.
65
+ # @param url [String, nil] playlist URL.
66
+ # @param owner_id [Integer, nil] playlist owner ID.
67
+ # @param playlist_id [Integer, nil] ID of the playlist.
68
+ # @param access_hash [String, nil] access hash to playlist. Might not exist.
69
+ # @param up_to [Integer] maximum amount of audios to load.
70
+ # If 0, no audios would be loaded (Just information about playlist).
71
+ # If less than 0, will load whole playlist.
72
+ # @param use_web [Boolean, nil] if +true+ web version of pages sill be used, if +false+
73
+ # JSON will be used (latter is faster, but using web allow to get URLs instantly).
74
+ # If +nil+ mixed algorithm will be used: if provided +up_to+ value is less than 200
75
+ # web will be used.
76
+ # @return [Playlist]
77
+ def playlist(url: nil, owner_id: nil, playlist_id: nil, access_hash: nil, up_to: Constants::MAXIMUM_PLAYLIST_SIZE, use_web: nil)
141
78
  begin
142
- case
143
- when (args.size == 1 && String === args[0]) ||
144
- (args.size == 2 && String === args[0] && Hash === args[1])
145
- options = args[1] || {}
146
- owner_id, playlist_id, access_hash = args[0].to_s.match(Constants::Regex::VK_PLAYLIST_URL_POSTFIX).captures
147
- when args.size == 1 && Hash === args[0]
148
- options = args[0]
149
- owner_id, playlist_id, access_hash = options[:owner_id].to_i, options[:playlist_id].to_i, options[:access_hash].to_s
150
- else
151
- raise
152
- end
79
+ owner_id, playlist_id, access_hash = url.match(Constants::Regex::VK_PLAYLIST_URL_POSTFIX).captures if url
153
80
  rescue
154
- raise ArgumentError, "Bad arguments", caller
81
+ raise Exceptions::ParseError
155
82
  end
156
-
157
- options[:up_to] ||= Constants::MAXIMUM_PLAYLIST_SIZE
158
- options[:with_url] = true if options[:with_url].nil?
159
-
160
- if options[:with_url]
161
- playlist__web(owner_id, playlist_id, access_hash, options)
83
+ raise ArgumentError unless owner_id && playlist_id
84
+ use_web = up_to > 200 if use_web.nil?
85
+ if use_web
86
+ playlist_web(owner_id, playlist_id, access_hash, up_to: up_to)
162
87
  else
163
- playlist__json(owner_id, playlist_id, access_hash, options)
88
+ playlist_json(owner_id, playlist_id, access_hash, up_to: up_to)
164
89
  end
165
90
  end
166
-
91
+
167
92
  ##
168
- # @!macro [new] ua__options
169
- # @option options [Integer] :up_to (MAXIMUM_PLAYLIST_SIZE) maximum amount of audios to load.
170
- # If 0, no audios would be loaded (Just information about playlist).
171
- # If less than 0, will load whole playlist.
172
- #
173
93
  # Get user or group audios.
174
- #
175
- # @note currently this method is only able to load 100 audios with download URL.
176
- #
177
- # @overload audios(url, options)
178
- # @param url [String] URL to user/group page or audios.
179
- # @macro options_hash_param
180
- # @macro ua__options
181
- #
182
- # @overload audios(options)
183
- # @macro options_hash_param
184
- # @option options [Integer] :owner_id numerical ID of owner.
185
- # @macro ua__options
186
- #
187
- # @macro playlist_return
188
- def audios(*args)
189
- begin
190
- case
191
- when (args.size == 1 && String === args[0] ) ||
192
- (args.size == 2 && String === args[0] && Hash === args[1])
193
- owner_id = page_id(args[0].to_s)
194
- options = args[1] || {}
195
- when args.size == 1 && Hash === args[0]
196
- owner_id = args[0][:owner_id].to_i
197
- options = args[0]
198
- else
199
- raise
200
- end
201
- rescue
202
- raise ArgumentError, "Bad arguments", caller
203
- end
204
-
205
- options[:up_to] ||= Constants::MAXIMUM_PLAYLIST_SIZE
206
-
207
- playlist__json(owner_id, -1, nil, options)
94
+ # Specify either +url+ or +owner_id+.
95
+ # @note since updating URLs can take a lot of time in this case, you have to
96
+ # do it manually with {Client#update_urls}.
97
+ # @param url [String, nil]
98
+ # @param owner_id [Integer, nil] numerical ID of owner.
99
+ # @param up_to [Integer] maximum amount of audios to load.
100
+ # If 0, no audios would be loaded (Just information about playlist).
101
+ # If less than 0, will load whole playlist.
102
+ # @return [Playlist]
103
+ def audios(url: nil, owner_id: nil, up_to: Constants::MAXIMUM_PLAYLIST_SIZE)
104
+ owner_id = page_id(url) if url
105
+ playlist_json(owner_id, -1, nil, up_to: up_to)
208
106
  end
209
107
 
210
108
  ##
211
- # @!macro [new] wall__up_to_option
212
- # @option up_to [Integer] :up_to (50) maximum amount of audios to load from wall.
213
- #
214
- # @!macro [new] wall__with_url_option
215
- # @option options [Boolean] :with_url (true) automatically use {Client#from_id} to get download URLs.
216
- #
217
109
  # Get audios on wall of user or group starting with given post.
218
- #
110
+ # Specify either +url+ or +(owner_id,post_id)+.
219
111
  # @note this method is only able to load up to 91 audios from wall.
220
- #
221
- # @todo this method breaks when club got fixed post with attached audios.
222
- #
223
- # @overload wall(url, options)
224
- # Load last audios from wall.
225
- # @param url [String] URL to user/group page.
226
- # @macro options_hash_param
227
- # @macro wall__up_to_option
228
- # @macro wall__with_url_option
229
- #
230
- # @overload wall(options)
231
- # Load audios starting from some exact post.
232
- # @macro options_hash_param
233
- # @option options [Integer] :owner_id numerical ID of wall owner.
234
- # @option options [Integer] :post_id numerical ID of post.
235
- # @macro wall__up_to_option
236
- # @macro wall__with_url_option
237
- #
238
- # @return [Array<Audio>] array of audios from wall. Possibly empty.
239
- def wall(*args)
240
- begin
241
- case
242
- when (args.size == 1 && args[0].class == String) ||
243
- (args.size == 2 && args[0].class == String && args[1].class == Hash)
244
- url = args[0].to_s
245
- owner_id = page_id(url)
246
- post_id = last_post_id(owner_id)
247
- options = args[1] || {}
248
- return [] if post_id.nil?
249
- when args.length == 1 && Hash === args[0]
250
- options = args[0]
251
- owner_id = options[:owner_id].to_i
252
- post_id = options[:post_id].to_i
253
- else
254
- raise
255
- end
256
- rescue Exceptions::ParseError => error
257
- raise Exceptions::ParseError, "Unable to get last post id. Error: #{error.message}", caller
258
- rescue
259
- raise ArgumentError, "Bad arguments", caller
112
+ # @param url [String] URL to post.
113
+ # @param owner_id [Integer] numerical ID of wall owner.
114
+ # @param post_id [Integer] numerical ID of post.
115
+ # @return [Array<Audio>] array of audios from wall.
116
+ def wall(url: nil, owner_id: nil, post_id: nil, up_to: 91, with_url: false)
117
+ if url
118
+ owner_id = page_id(url)
119
+ post_id = last_post_id(owner_id: owner_id)
260
120
  end
261
-
262
- options[:up_to] ||= 50
263
- options[:with_url] = true if options[:with_url].nil?
264
-
265
- wall__json(owner_id, post_id, options)
121
+ wall_json(owner_id, post_id, up_to: up_to, with_url: with_url)
266
122
  end
267
123
 
268
124
  ##
269
125
  # Get audios attached to post.
270
- #
271
- # @note currently this method works incorrectly with reposts.
272
- #
273
- # @overload post(url)
274
- # @param url [String] URL to post.
275
- #
276
- # @overload post(options)
277
- # @macro options_hash_param
278
- # @option options [Integer] :owner_id numerical ID of wall owner.
279
- # @option options [Integer] :post_id numerical ID of post.
280
- #
281
- # @return [Array<Audio>] array of audios. Possibly without download URL.
282
- def post(arg)
126
+ # Specify either +url+ or +(owner_id,post_id)+.
127
+ # @param url [String] URL to post.
128
+ # @param owner_id [Integer] numerical ID of wall owner.
129
+ # @param post_id [Integer] numerical ID of post.
130
+ # @return [Array<Audio>] array of audios attached to post. Most of audios will
131
+ # already have download URLs, but there might be audios which can't be resolved.
132
+ def post(url: nil, owner_id: nil, post_id: nil)
283
133
  begin
284
- case arg
285
- when String
286
- owner_id, post_id = arg.match(Constants::Regex::VK_WALL_URL_POSTFIX).captures
287
- when Hash
288
- options = arg
289
- owner_id = options[:owner_id].to_i
290
- post_id = options[:post_id].to_i
291
- else
292
- raise
293
- end
134
+ owner_id, post_id = url.match(Constants::Regex::VK_WALL_URL_POSTFIX).captures if url
294
135
  rescue
295
- raise ArgumentError, "Bad arguments", caller
136
+ raise Exceptions::ParseError
296
137
  end
297
138
 
298
139
  attached = attached_audios(owner_id: owner_id, post_id: post_id)
@@ -303,59 +144,68 @@ module VkMusic
303
144
  wall.find { |a| a.artist == a_empty.artist && a.title == a_empty.title } || a_empty
304
145
  end
305
146
  loaded_audios = from_id(no_link)
306
-
147
+
307
148
  loaded_audios.map.with_index { |el, i| el || no_link[i] }
308
149
  end
309
150
 
310
151
  ##
311
152
  # Get audios with download URLs by their IDs and secrets.
312
- #
313
153
  # @param args [Array<Audio, Array<(owner_id, audio_id, secret_1, secret_2)>, "#{owner_id}_#{id}_#{secret_1}_#{secret_2}">]
314
- #
315
154
  # @return [Array<Audio, nil>] array of: audio with download URLs or audio
316
- # audio without URL if wasn't able to get it for audio or +nil+ if
155
+ # without URL if wasn't able to get it for audio or +nil+ if
317
156
  # matching element can't be retrieved for array or string.
318
- def from_id(args)
319
- begin
320
- args_formatted = args.map do |el|
321
- case el
322
- when Array
323
- el.join("_")
324
- when Audio
325
- el.full_id
326
- when String
327
- el # Do not change
328
- else
329
- raise
330
- end
157
+ def get_urls(args)
158
+ args_formatted = args.map do |el|
159
+ case el
160
+ when Array
161
+ el.join("_")
162
+ when Audio
163
+ el.full_id
164
+ when String
165
+ el # Do not change
166
+ else
167
+ raise ArgumentError
331
168
  end
332
- rescue
333
- raise ArgumentError, "Bad arguments", caller
334
169
  end
335
170
  args_formatted.compact.uniq # Not dealing with nil or doubled IDs
336
-
171
+
337
172
  audios = []
338
- args_formatted.each_slice(10) do |subarray|
339
- json = load__json__audios_by_id(subarray)
340
- subresult = audios__from_data(json["data"][0].to_a)
341
- audios.concat(subresult)
173
+ begin
174
+ args_formatted.each_slice(10) do |subarray|
175
+ json = load_json_audios_by_id(subarray)
176
+ subresult = audios_from_data(json["data"][0].to_a)
177
+ audios.concat(subresult)
178
+ end
179
+ rescue
180
+ raise Exceptions::ParseError
342
181
  end
343
- Utility.debug("Loaded audios from ids: #{audios.map(&:pp).join(", ")}")
182
+ VkMusic.debug("Loaded audios from ids: #{audios.map(&:pp).join(", ")}")
344
183
 
345
184
  args.map do |el|
346
185
  case el
347
- when Array
348
- audios.find { |audio| audio.owner_id == el[0].to_i && audio.id == el[1].to_i }
349
- when Audio
350
- next el if el.full_id.nil? # Audio was skipped
351
- audios.find { |audio| audio.owner_id == el.owner_id && audio.id == el.id }
352
- when String
353
- audios.find { |audio| [audio.owner_id, audio.id] == el.split("_").first(2).map(&:to_i) }
354
- else
355
- nil # This shouldn't happen actually
186
+ when Array
187
+ audios.find { |audio| audio.owner_id == el[0].to_i && audio.id == el[1].to_i }
188
+ when Audio
189
+ next el if el.full_id.nil? # Audio was skipped
190
+ audios.find { |audio| audio.owner_id == el.owner_id && audio.id == el.id }
191
+ when String
192
+ audios.find { |audio| [audio.owner_id, audio.id] == el.split("_").first(2).map(&:to_i) }
193
+ else
194
+ nil # This shouldn't happen actually
356
195
  end
357
196
  end
358
197
  end
198
+ alias_method :from_id, :get_urls
199
+ ##
200
+ # Update download URLs of audios.
201
+ # @param audios [Array<Audio>]
202
+ def update_urls(audios)
203
+ audios_with_urls = get_urls(audios)
204
+ audios.each.with_index do |a, i|
205
+ a_u = audios_with_urls[i]
206
+ a.update(from: a_u) unless a_u.nil?
207
+ end
208
+ end
359
209
 
360
210
  ##
361
211
  # @!endgroup
@@ -364,140 +214,103 @@ module VkMusic
364
214
  # @!group Other
365
215
 
366
216
  ##
367
- # Get user or group ID. Sends one request if custom ID provided
368
- #
217
+ # Get user or group ID. Sends one request if custom ID provided.
369
218
  # @param str [String] link, ID with/without prefix or custom ID.
370
- #
371
219
  # @return [Integer] page ID.
372
220
  def page_id(str)
373
- raise ArgumentError, "Bad arguments", caller unless str.class == String
374
-
375
221
  case str
376
- when Constants::Regex::VK_URL
377
- path = str.match(Constants::Regex::VK_URL)[1]
378
- id = page_id(path) # Recursive call
379
- when Constants::Regex::VK_ID_STR
380
- id = str.to_i
381
- when Constants::Regex::VK_AUDIOS_URL_POSTFIX
382
- id = str.match(/-?\d+/).to_s.to_i # Numbers with sign
383
- when Constants::Regex::VK_PREFIXED_ID_STR
384
- id = str.match(/\d+/).to_s.to_i # Just numbers. Sign needed
385
- id *= -1 unless str.start_with?("id")
386
- when Constants::Regex::VK_CUSTOM_ID
387
- url = "#{Constants::URL::VK[:home]}/#{str}"
388
- begin
389
- page = load__page(url)
390
- rescue Exceptions::RequestError => error
391
- raise Exceptions::ParseError, "Failed request: #{error.message}", caller
392
- end
393
-
394
- raise Exceptions::ParseError, "Page #{str} doesn't seem to be a group or user page", caller unless page.at_css(".PageBlock .owner_panel")
395
-
396
- begin
397
- id = page.link_with(href: Constants::Regex::VK_HREF_ID_CONTAINING).href.slice(Constants::Regex::VK_ID).to_i # Numbers with sign
398
- rescue Exception => error
399
- raise Exceptions::ParseError, "Unable to get user or group ID. Custom ID: #{str}. Error: #{error.message}", caller
400
- end
401
- else
402
- raise Exceptions::ParseError, "Unable to convert \"#{str}\" into ID", caller
222
+ when Constants::Regex::VK_URL
223
+ path = str.match(Constants::Regex::VK_URL)[1]
224
+ page_id(path) # Recursive call
225
+ when Constants::Regex::VK_ID_STR
226
+ str.to_i
227
+ when Constants::Regex::VK_AUDIOS_URL_POSTFIX
228
+ str.match(/-?\d+/).to_s.to_i # Numbers with sign
229
+ when Constants::Regex::VK_PREFIXED_ID_STR
230
+ id = str.match(/\d+/).to_s.to_i # Just numbers. Sign needed
231
+ id *= -1 unless str.start_with?("id")
232
+ id
233
+ when Constants::Regex::VK_CUSTOM_ID
234
+ url = "#{Constants::URL::VK[:home]}/#{str}"
235
+ begin
236
+ page = load_page(url)
237
+ rescue Exceptions::RequestError
238
+ raise Exceptions::ParseError
239
+ end
240
+
241
+ raise Exceptions::ParseError unless page.at_css(".PageBlock .owner_panel")
242
+
243
+ begin
244
+ page.link_with(href: Constants::Regex::VK_HREF_ID_CONTAINING).href.slice(Constants::Regex::VK_ID).to_i # Numbers with sign
245
+ rescue
246
+ raise Exceptions::ParseError
247
+ end
248
+ else
249
+ raise Exceptions::ParseError
403
250
  end
404
- id
405
251
  end
406
252
 
407
253
  ##
408
254
  # Get ID of last post.
409
- #
255
+ # Specify either +url+ or +owner_id+.
410
256
  # @note requesting for "vk.com/id0" will raise ArgumentError.
411
257
  # Use +client.last_post_id(owner_id: client.id)+ to get last post of client.
412
- #
413
- # @overload last_post_id(url)
414
- # @param url [String] URL to wall owner.
415
- #
416
- # @overload last_post_id(owner_id)
417
- # @param owner_id [Integer] numerical ID of wall owner.
418
- #
258
+ # @param url [String] URL to wall owner.
259
+ # @param owner_id [Integer] numerical ID of wall owner.
419
260
  # @return [Integer, nil] ID of last post or +nil+ if there are no posts.
420
- def last_post_id(arg)
421
- begin
422
- case arg
423
- when String
424
- path = arg.match(Constants::Regex::VK_URL)[1]
425
- when Integer
426
- owner_id = arg
427
- path = "#{owner_id < 0 ? "club" : "id"}#{owner_id.abs}"
428
- else
429
- raise
430
- end
431
- rescue
432
- raise ArgumentError, "Bad arguments", caller
261
+ def last_post_id(url: nil, owner_id: nil)
262
+ path = if url
263
+ url.match(Constants::Regex::VK_URL)[1]
264
+ else
265
+ path = "#{owner_id < 0 ? "club" : "id"}#{owner_id.abs}"
433
266
  end
434
267
  raise ArgumentError, "Requesting this method for id0 is forbidden", caller if path == "id0"
435
268
 
436
269
  url = "#{Constants::URL::VK[:home]}/#{path}"
437
-
438
- begin
439
- page = load__page(url)
440
- rescue Exceptions::RequestError => error
441
- raise Exceptions::ParseError, "Failed request: #{error.message}", caller
442
- end
270
+ page = load_page(url)
443
271
 
444
272
  # Ensure this isn't some random vk page
445
- raise Exceptions::ParseError, "Page at #{url} doesn't seem to be a group or user page", caller unless page.at_css(".PageBlock .owner_panel")
273
+ raise Exceptions::ParseError unless page.at_css(".PageBlock .owner_panel")
446
274
 
447
275
  begin
448
- posts = page.css(".wall_posts > .wall_item .post__anchor")
276
+ posts = page.css(".wall_posts > .wall_item .anchor")
449
277
  posts_ids = posts.map do |post|
450
278
  post ? post.attribute("name").to_s.match(Constants::Regex::VK_POST_URL_POSTFIX)[2].to_i : 0
451
279
  end
452
280
  # To avoid checking id of pinned post need to take maximum id.
453
281
  return posts_ids.max
454
- rescue Exception => error
455
- raise Exceptions::ParseError, "Unable to get last post on #{url}. Error: #{error.message}", caller
282
+ rescue
283
+ raise Exceptions::ParseError
456
284
  end
457
285
  end
458
286
 
459
287
  ##
460
288
  # Get audios attached to specified post.
461
- #
462
- # @overload attached_audios(url)
463
- # @param url [String] URL to post.
464
- #
465
- # @overload attached_audios(options)
466
- # @macro options_hash_param
467
- # @option options [Integer] :owner_id numerical ID of wall owner.
468
- # @option options [Integer] :post_id numerical ID of post.
469
- #
289
+ # Specify either +url+ or +(owner_id,post_id)+.
290
+ # @param url [String] URL to post.
291
+ # @param owner_id [Integer] numerical ID of wall owner.
292
+ # @param post_id [Integer] numerical ID of post.
470
293
  # @return [Array<Audio>] audios with only artist, title and duration.
471
- def attached_audios(arg)
294
+ def attached_audios(url: nil, owner_id: nil, post_id: nil)
472
295
  begin
473
- case arg
474
- when String
475
- owner_id, post_id = arg.match(Constants::Regex::VK_WALL_URL_POSTFIX).captures
476
- when Hash
477
- options = arg
478
- owner_id = options[:owner_id].to_i
479
- post_id = options[:post_id].to_i
480
- else
481
- raise
482
- end
296
+ owner_id, post_id = url.match(Constants::Regex::VK_WALL_URL_POSTFIX).captures if url
483
297
  rescue
484
- raise ArgumentError, "Bad arguments", caller
298
+ raise Exceptions::ParseError
485
299
  end
486
300
 
487
301
  url = "#{Constants::URL::VK[:wall]}#{owner_id}_#{post_id}"
488
302
  begin
489
- page = load__page(url)
490
- rescue Exceptions::RequestError => error
491
- raise Exceptions::ParseError, "Failed request: #{error.message}", caller
303
+ page = load_page(url)
304
+ rescue Exceptions::RequestError
305
+ raise Exceptions::ParseError
492
306
  end
493
307
 
494
- raise Exceptions::ParseError, "Post not found: #{owner_id}_#{post_id}", caller unless page.css(".service_msg_error").empty?
308
+ raise Exceptions::ParseError unless page.css(".service_msg_error").empty?
495
309
  begin
496
- result = page.css(".wi_body > .pi_medias .medias_audio").map { |e| Audio.from_node(e, @id) }
497
- rescue Exception => error
498
- raise Exceptions::ParseError, "Unable to get amount of audios in post #{owner_id}_#{post_id}. Error: #{error.message}", caller
310
+ page.css(".wi_body > .pi_medias .medias_audio").map { |e| Audio.from_node(e, @id) }
311
+ rescue
312
+ raise Exceptions::ParseError
499
313
  end
500
- result
501
314
  end
502
315
 
503
316
  ##
@@ -505,20 +318,25 @@ module VkMusic
505
318
 
506
319
  private
507
320
 
508
- # Load page by URL. And return Mechanize::Page.
509
- def load__page(url)
321
+ ##
322
+ # Load page web page.
323
+ # @param url [String, URI]
324
+ # @return [Mechanize::Page]
325
+ def load_page(url)
510
326
  uri = URI(url) if url.class != URI
511
- Utility.debug("Loading #{uri}")
327
+ VkMusic.debug("Loading #{uri}")
512
328
  begin
513
329
  @agent.get(uri)
514
- rescue Exception => error
515
- raise Exceptions::RequestError, error.message, caller
330
+ rescue
331
+ raise Exceptions::RequestError
516
332
  end
517
333
  end
518
-
519
- # Load JSON by URL. And return JSON object.
520
- def load__json(url)
521
- page = load__page(url)
334
+ ##
335
+ # Load JSON from web page.
336
+ # @param url [String, URI]
337
+ # @return [Hash]
338
+ def load_json(url)
339
+ page = load_page(url)
522
340
  begin
523
341
  JSON.parse(page.body.strip)
524
342
  rescue Exception => error
@@ -526,90 +344,113 @@ module VkMusic
526
344
  end
527
345
  end
528
346
 
529
-
347
+ ##
530
348
  # Load playlist web page.
531
- def load__page__playlist(owner_id, playlist_id, access_hash, options)
349
+ # @param owner_id [Integer]
350
+ # @param playlist_id [Integer]
351
+ # @param access_hash [String, nil]
352
+ # @param offset [Integer]
353
+ # @return [Mechanize::Page]
354
+ def load_page_playlist(owner_id, playlist_id, access_hash = nil, offset: 0)
532
355
  uri = URI(Constants::URL::VK[:audios])
533
356
  uri.query = Utility.hash_to_params({
534
- "act" => "audio_playlist#{owner_id.to_i}_#{playlist_id.to_i}",
535
- "access_hash" => access_hash.to_s,
536
- "offset" => options[:offset].to_i
357
+ act: "audio_playlist#{owner_id}_#{playlist_id}",
358
+ access_hash: access_hash.to_s,
359
+ offset: offset
537
360
  })
538
- load__page(uri)
361
+ load_page(uri)
539
362
  end
540
-
541
- # Load JSON playlist part with +load_section+ request.
542
- def load__json__playlist_section(owner_id, playlist_id, access_hash, options)
363
+ ##
364
+ # Load JSON playlist section with +load_section+ request.
365
+ # @param owner_id [Integer]
366
+ # @param playlist_id [Integer]
367
+ # @param access_hash [String, nil]
368
+ # @param offset [Integer]
369
+ # @return [Hash]
370
+ def load_json_playlist_section(owner_id, playlist_id, access_hash = nil, offset: 0)
543
371
  uri = URI(Constants::URL::VK[:audios])
544
372
  uri.query = Utility.hash_to_params({
545
- "act" => "load_section",
546
- "owner_id" => owner_id.to_i,
547
- "playlist_id" => playlist_id.to_i,
548
- "access_hash" => access_hash.to_s,
549
- "type" => "playlist",
550
- "offset" => options[:offset].to_i,
551
- "utf8" => true
373
+ act: "load_section",
374
+ owner_id: owner_id,
375
+ playlist_id: playlist_id,
376
+ access_hash: access_hash.to_s,
377
+ type: "playlist",
378
+ offset: offset,
379
+ utf8: true
552
380
  })
553
- load__json(uri)
381
+ load_json(uri)
554
382
  end
555
383
 
556
-
384
+ ##
557
385
  # Load JSON audios with +reload_audio+ request.
558
- def load__json__audios_by_id(ids)
386
+ # @param ids [Array<String>]
387
+ # @return [Hash]
388
+ def load_json_audios_by_id(ids)
559
389
  uri = URI(Constants::URL::VK[:audios])
560
390
  uri.query = Utility.hash_to_params({
561
- "act" => "reload_audio",
562
- "ids" => ids.to_a,
563
- "utf8" => true
391
+ act: "reload_audio",
392
+ ids: ids,
393
+ utf8: true
564
394
  })
565
- load__json(uri)
395
+ load_json(uri)
566
396
  end
567
-
397
+ ##
568
398
  # Load JSON audios with +load_section+ from wall.
569
- def load__json__audios_wall(owner_id, post_id)
399
+ # @param owner_id [Integer]
400
+ # @param post_id [Integer]
401
+ # @return [Hash]
402
+ def load_json_audios_wall(owner_id, post_id)
570
403
  uri = URI(Constants::URL::VK[:audios])
571
404
  uri.query = Utility.hash_to_params({
572
- "act" => "load_section",
573
- "owner_id" => owner_id,
574
- "post_id" => post_id,
575
- "type" => "wall",
576
- "wall_type" => "own",
577
- "utf8" => true
405
+ act: "load_section",
406
+ owner_id: owner_id,
407
+ post_id: post_id,
408
+ type: "wall",
409
+ wall_type: "own",
410
+ utf8: true
578
411
  })
579
- load__json(uri)
412
+ load_json(uri)
580
413
  end
581
414
 
582
-
583
- # Loading audios from web page.
584
- def audios__from_page(obj)
585
- page = obj.class == Mechanize::Page ? obj : load__page(obj)
415
+ ##
416
+ # Load audios from web page.
417
+ # @param obj [Mechanize::Page, String, URI]
418
+ # @return [Array<Audio>]
419
+ def audios_from_page(obj)
420
+ page = obj.is_a?(Mechanize::Page) ? obj : load_page(obj)
586
421
  begin
587
422
  page.css(".audio_item.ai_has_btn").map { |elem| Audio.from_node(elem, @id) }
588
- rescue Exception => error
589
- raise Exceptions::ParseError, error.message, caller
423
+ rescue
424
+ raise Exceptions::ParseError
590
425
  end
591
- end
592
-
426
+ end
427
+ ##
593
428
  # Load audios from JSON data.
594
- def audios__from_data(data)
429
+ # @param data [Hash]
430
+ # @return [Array<Audio>]
431
+ def audios_from_data(data)
595
432
  begin
596
433
  data.map { |audio_data| Audio.from_data(audio_data, @id) }
597
- rescue Exception => error
598
- raise Exceptions::ParseError, error.message, caller
434
+ rescue
435
+ raise Exceptions::ParseError
599
436
  end
600
437
  end
601
438
 
602
-
439
+ ##
603
440
  # Load playlist through web page requests.
604
- def playlist__web(owner_id, playlist_id, access_hash, options)
441
+ # @param owner_id [Integer]
442
+ # @param playlist_id [Integer]
443
+ # @param access_hash [String, nil]
444
+ # @param up_to [Integer] if less than 0, all audios will be loaded.
445
+ # @return [Playlist]
446
+ def playlist_web(owner_id, playlist_id, access_hash = nil, up_to: -1)
447
+ # Load first page and get info
448
+ first_page = load_page_playlist(owner_id, playlist_id, access_hash, offset: 0)
605
449
  begin
606
- # Load first page and get info
607
- first_page = load__page__playlist(owner_id, playlist_id, access_hash, offset: 0)
608
-
609
450
  # Parse out essential data
610
451
  title = first_page.at_css(".audioPlaylist__title").text.strip
611
452
  subtitle = first_page.at_css(".audioPlaylist__subtitle").text.strip
612
-
453
+
613
454
  footer_node = first_page.at_css(".audioPlaylist__footer")
614
455
  if footer_node
615
456
  footer_match = footer_node.text.strip.match(/^\d+/)
@@ -617,96 +458,117 @@ module VkMusic
617
458
  else
618
459
  real_size = 0
619
460
  end
620
- rescue Exception => error
621
- raise Exceptions::ParseError, error.message, caller
461
+ rescue
462
+ raise Exceptions::ParseError
622
463
  end
623
- # Now we can be sure we are on correct page
624
-
625
- first_page_audios = audios__from_page(first_page)
626
-
464
+ # Now we can be sure we are on correct page and have essential data.
465
+
466
+ first_page_audios = audios_from_page(first_page)
467
+
627
468
  # Check whether need to make additional requests
628
- options[:up_to] = real_size if (options[:up_to] < 0 || options[:up_to] > real_size)
629
- list = first_page_audios[0, options[:up_to]]
630
- while list.length < options[:up_to] do
631
- playlist_page = load__page__playlist(owner_id, playlist_id, access_hash, offset: list.length)
632
- list.concat(audios__from_page(playlist_page)[0, options[:up_to] - list.length])
469
+ up_to = real_size if (up_to < 0 || up_to > real_size)
470
+ list = first_page_audios.first(up_to)
471
+ while list.length < up_to do
472
+ playlist_page = load_page_playlist(owner_id, playlist_id, access_hash, offset: list.length)
473
+ list.concat(audios_from_page(playlist_page).first(up_to - list.length))
633
474
  end
634
-
635
- Playlist.new(list, {
636
- :id => id,
637
- :owner_id => owner_id,
638
- :access_hash => access_hash,
639
- :title => title,
640
- :subtitle => subtitle,
641
- :real_size => real_size
642
- })
475
+
476
+ Playlist.new(list,
477
+ id: id,
478
+ owner_id: owner_id,
479
+ access_hash: access_hash,
480
+ title: title,
481
+ subtitle: subtitle,
482
+ real_size: real_size
483
+ )
643
484
  end
644
485
 
486
+ ##
645
487
  # Load playlist through JSON requests.
646
- def playlist__json(owner_id, playlist_id, access_hash, options)
488
+ # @param owner_id [Integer]
489
+ # @param playlist_id [Integer]
490
+ # @param access_hash [String, nil]
491
+ # @param up_to [Integer] if less than 0, all audios will be loaded.
492
+ # @return [Playlist]
493
+ def playlist_json(owner_id, playlist_id, access_hash, up_to: -1)
647
494
  # Trying to parse out audios
495
+ first_json = load_json_playlist_section(owner_id, playlist_id, access_hash, offset: 0)
648
496
  begin
649
- first_json = load__json__playlist_section(owner_id, playlist_id, access_hash, offset: 0)
650
497
  first_data = first_json["data"][0]
651
- first_data_audios = audios__from_data(first_data["list"])
652
- rescue Exception => error
653
- raise Exceptions::ParseError, error.message, caller
498
+ first_data_audios = audios_from_data(first_data["list"])
499
+ rescue
500
+ raise Exceptions::ParseError
654
501
  end
655
-
502
+
656
503
  real_size = first_data["totalCount"]
657
- options[:up_to] = real_size if (options[:up_to] < 0 || options[:up_to] > real_size)
658
- list = first_data_audios[0, options[:up_to]]
659
- while list.length < options[:up_to] do
660
- json = load__json__playlist_section(owner_id, playlist_id, access_hash,
661
- offset: list.length
504
+ up_to = real_size if (up_to < 0 || up_to > real_size)
505
+ list = first_data_audios.first(up_to)
506
+ while list.length < up_to do
507
+ json = load_json_playlist_section(owner_id, playlist_id, access_hash, offset: list.length)
508
+ audios = begin
509
+ audios_from_data(json["data"][0]["list"])
510
+ rescue
511
+ raise Exceptions::ParseError
512
+ end
513
+ list.concat(audios.first(up_to - list.length))
514
+ end
515
+
516
+ begin
517
+ Playlist.new(list,
518
+ id: first_data["id"],
519
+ owner_id: first_data["owner_id"],
520
+ access_hash: first_data["access_hash"],
521
+ title: CGI.unescapeHTML(first_data["title"].to_s),
522
+ subtitle: CGI.unescapeHTML(first_data["subtitle"].to_s),
523
+ real_size: real_size
662
524
  )
663
- audios = audios__from_data(json["data"][0]["list"])
664
- list.concat(audios[0, options[:up_to] - list.length])
525
+ rescue
526
+ raise Exceptions::ParseError
665
527
  end
666
-
667
- Playlist.new(list, {
668
- :id => first_data["id"],
669
- :owner_id => first_data["owner_id"],
670
- :access_hash => first_data["access_hash"],
671
- :title => CGI.unescapeHTML(first_data["title"].to_s),
672
- :subtitle => CGI.unescapeHTML(first_data["subtitle"].to_s),
673
- :real_size => real_size
674
- })
675
528
  end
676
529
 
677
- # Found playlist on *global* search page
678
- def playlist_urls__from_page(obj)
679
- page = obj.class == Mechanize::Page ? obj : load__page(obj)
530
+ ##
531
+ # Found playlist URLs on *global* search page.
532
+ # @param obj [Mechanize::Page, String, URI]
533
+ # @return [Array<String>]
534
+ def playlist_urls_from_page(obj)
535
+ page = obj.is_a?(Mechanize::Page) ? obj : load_page(obj)
680
536
  begin
681
- page.css(".AudioSerp__foundGlobal .AudioPlaylistSlider .al_playlist").map { |elem| elem.attribute("href").to_s }
682
- rescue Exception => error
683
- raise Exceptions::ParseError, error.message, caller
537
+ page.css(".AudioShowcase__block_playlists .AudioPlaylistSlider .al_playlist").map { |elem| elem.attribute("href").to_s }
538
+ rescue
539
+ raise Exceptions::ParseError
684
540
  end
685
541
  end
686
542
 
543
+ ##
687
544
  # Load audios from wall using JSON request.
688
- def wall__json(owner_id, post_id, options)
689
- if options[:up_to] < 0 || options[:up_to] > 91
690
- options[:up_to] = 91
691
- Utility.warn("Current implementation of this method is not able to return more than 91 audios from wall.")
545
+ # @param owner_id [Integer]
546
+ # @param post_id [Intger]
547
+ # @param up_to [Integer]
548
+ # @param with_url [Boolean] whether to retrieve URLs with {Client#from_id} method
549
+ # @return [Array<Audio>]
550
+ def wall_json(owner_id, post_id, up_to: 91, with_url: false)
551
+ if up_to < 0 || up_to > 91
552
+ up_to = 91
553
+ VkMusic.warn("Current implementation of this method is not able to return more than 91 audios from wall.")
692
554
  end
693
555
 
556
+ json = load_json_audios_wall(owner_id, post_id)
694
557
  begin
695
- json = load__json__audios_wall(owner_id, post_id)
696
558
  data = json["data"][0]
697
- audios = audios__from_data(data["list"])[0, options[:up_to]]
698
- rescue Exception => error
699
- raise Exceptions::ParseError, error.message, caller
559
+ audios = audios_from_data(data["list"]).first(up_to)
560
+ rescue
561
+ raise Exceptions::ParseError
700
562
  end
701
- options[:with_url] ? from_id(audios) : audios
563
+ with_url ? from_id(audios) : audios
702
564
  end
703
565
 
704
-
705
- # Login
566
+ ##
567
+ # Login to VK.
706
568
  def login(username, password)
707
- Utility.debug("Logging in.")
569
+ VkMusic.debug("Logging in.")
708
570
  # Loading login page
709
- homepage = load__page(Constants::URL::VK[:login])
571
+ homepage = load_page(Constants::URL::VK[:login])
710
572
  # Submitting login form
711
573
  login_form = homepage.forms.find { |form| form.action.start_with?(Constants::URL::VK[:login_action]) }
712
574
  login_form[Constants::VK_LOGIN_FORM_NAMES[:username]] = username.to_s
@@ -715,18 +577,16 @@ module VkMusic
715
577
 
716
578
  # Checking whether logged in
717
579
  raise Exceptions::LoginError, "Unable to login. Redirected to #{after_login.uri.to_s}", caller unless after_login.uri.to_s == Constants::URL::VK[:feed]
718
-
580
+
719
581
  # Parsing information about this profile
720
- profile = load__page(Constants::URL::VK[:profile])
582
+ profile = load_page(Constants::URL::VK[:profile])
721
583
  @name = profile.title.to_s
722
584
  @id = profile.link_with(href: Constants::Regex::VK_HREF_ID_CONTAINING).href.slice(/\d+/).to_i
723
585
  end
724
-
586
+
725
587
  # Shortcut
726
588
  def unmask_link(link)
727
589
  VkMusic::LinkDecoder.unmask_link(link, @id)
728
590
  end
729
-
730
591
  end
731
-
732
592
  end