vk_music 1.1.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0c6e2d05ccf44bc32534cc66a85c2495f799e247876495df962bd312b3cef644
4
- data.tar.gz: acc2fab3d0f196536f195cecea4f5bd0c997a6e14566a9a8a574b444db98bf4e
3
+ metadata.gz: e42dd22de66cd8bb8d2d0b70686102d4d50078677736b036b7c383470ff31f2a
4
+ data.tar.gz: c6d3eb20221b599d48a1e4464b837ef294d926b4bc9fbeb81b348e9ae5c63f90
5
5
  SHA512:
6
- metadata.gz: 9f31a0049dc14a0ef0603f503733593a5bd8ffc2a26cae4ce4c6bb4ace98d175f86745ead97e2293d9157a2bf62b323c2f61254c01940b37505715c35454fdbc
7
- data.tar.gz: 20fa21e9ee919f544664c20c149089ce594967bafa9809e7ab28ed68f53ab68d0147f5451fac66d83fe4453d1e3c388614fc543958f024d911ff24439cf94bb5
6
+ metadata.gz: 515fbb7b67a8a53b0e2bc8ed0ef8f34a4cdda560ad94a4880ff8e9b36fb7d3c023386b6c2fca3d0df59eb19487d5a708d21684ddb3bf25bcbda4b2141506dd63
7
+ data.tar.gz: 40eac37ca4af3db6eb0d88a7e93277f923565c013207706e0d29e7b14a18354628d9bdb79b8eb71de753571a29a4ed93bbeb0e3adad697a4cdc6cfed3c9598a0
data/README.md CHANGED
@@ -29,6 +29,8 @@ gem install vk_music-*.gem
29
29
 
30
30
  ## Usage
31
31
 
32
+ You can take a look on documentation [here](https://www.rubydoc.info/gems/vk_music/).
33
+
32
34
  ### Logging in
33
35
  Firstly, it is required to create new *VkMusic::Client* and provide login credentials:
34
36
  ```ruby
@@ -38,7 +40,7 @@ client = VkMusic::Client.new(username: "+71234567890", password: "password")
38
40
  ### Searching for audios
39
41
  You can search audios by name with following method:
40
42
  ```ruby
41
- audios = client.find_audio("Acid Spit - Mega Drive")
43
+ audios = client.find("Acid Spit - Mega Drive")
42
44
  puts audios[0] # Basic information about audio
43
45
  puts audios[0].url # URL to access audio. Notice that it is only accessible from your IP
44
46
  ```
@@ -46,28 +48,28 @@ puts audios[0].url # URL to access audio. Notice that it is only accessible from
46
48
  ### Parsing playlists
47
49
  You can load all the audios from playlist using following method:
48
50
  ```ruby
49
- playlist = client.get_playlist("https://vk.com/audio?z=audio_playlist-37661843_1/0e420c32c8b69e6637")
51
+ playlist = client.playlist("https://vk.com/audio?z=audio_playlist-37661843_1/0e420c32c8b69e6637")
50
52
  ```
51
53
  It is only possible to load up to 100 audios from playlist per request, so you can reduce amount of requests by setting up how many audios from playlist you actually need.
52
54
  For example, following method will perform only one HTML request:
53
55
  ```ruby
54
- playlist = client.get_playlist("https://vk.com/audio?z=audio_playlist121570739_7", 100)
56
+ playlist = client.playlist("https://vk.com/audio?z=audio_playlist121570739_7", up_to: 100)
55
57
  urls = playlist.map(&:url) # URLs for every audio
56
58
  ```
57
59
 
58
60
  ### User or group audios
59
61
  You can load first 100 audios from user or group page. Those audios will be returned as playlist. To do it simply pass user or group id:
60
62
  ```ruby
61
- user_playlist = client.get_audios("8024985")
62
- group_playlist = client.get_audios("-4790861") # Group and public id starts with '-'
63
+ user_playlist = client.audios(owner_id: 8024985)
64
+ group_playlist = client.audios(owner_id: -4790861) # Group and public id starts with '-'
63
65
  ```
64
66
  You can set how many audios you actually need as well:
65
67
  ```ruby
66
- user_playlist = client.get_audios("8024985", 10)
68
+ user_playlist = client.audios(owner_id: 8024985, up_to: 10)
67
69
  ```
68
70
 
69
71
  ### Audios from post
70
72
  You can load up to 10 audios attached to some post. Those audios will be returned as array:
71
73
  ```ruby
72
- audios = client.get_audios_from_post("https://vk.com/wall-4790861_5453")
74
+ audios = client.post("https://vk.com/wall-4790861_5453")
73
75
  ```
data/Rakefile CHANGED
@@ -1,3 +1,8 @@
1
+ desc "Create documentation"
2
+ task :doc do
3
+ puts `yardoc`
4
+ end
5
+
1
6
  desc "Build gem file"
2
7
  task :build do
3
8
  puts `gem build vk_music.gemspec`
data/lib/vk_music.rb CHANGED
@@ -1,7 +1,7 @@
1
- require_relative "vk_music/utility.rb"
2
1
  require_relative "vk_music/constants.rb"
3
2
  require_relative "vk_music/exceptions.rb"
4
- require_relative "vk_music/audio.rb"
3
+ require_relative "vk_music/utility.rb"
5
4
  require_relative "vk_music/link_decoder.rb"
5
+ require_relative "vk_music/audio.rb"
6
6
  require_relative "vk_music/playlist.rb"
7
- require_relative "vk_music/client.rb"
7
+ require_relative "vk_music/client.rb"
@@ -1,138 +1,202 @@
1
1
  require "cgi"
2
2
 
3
+ ##
4
+ # @!macro [new] options_hash_param
5
+ # @param options [Hash] hash with options.
6
+
3
7
  module VkMusic
4
8
 
5
- # VK audio.
9
+ ##
10
+ # Class representing VK audio.
6
11
  class Audio
7
12
 
8
- # Id of audio.
13
+ ##
14
+ # @return [Integer, nil] ID of audio.
9
15
  attr_reader :id
10
- # Id of audio owner.
16
+
17
+ ##
18
+ # @return [Integer, nil] ID of audio owner.
11
19
  attr_reader :owner_id
12
- # Parts of secret hash which used when using +act=reload_audio+
20
+
21
+ ##
22
+ # @return [String, nil] part of secret hash which used when using +act=reload_audio+.
13
23
  attr_reader :secret_1, :secret_2
14
- # Artist.
24
+
25
+ ##
26
+ # @return [String] name of artist.
15
27
  attr_reader :artist
16
- # Title.
28
+
29
+ ##
30
+ # @return [String] title of song.
17
31
  attr_reader :title
18
- # Duration.
32
+
33
+ ##
34
+ # @return [Integer] duration of track in seconds.
19
35
  attr_reader :duration
20
- # Download URL.
21
- attr_reader :url
22
- # Encoded URL.
23
- attr_reader :url_encoded
24
36
 
25
- # Update audio URLs.
37
+ ##
38
+ # Access decoded download URL.
26
39
  #
27
- # If +:url+ is provided - just save it.
28
- # If +:url_encoded+ and +:client_id+ provided - unmask link first.
40
+ # If link was already decoded, returns cached value. Else decodes existing link.
41
+ # If no link can be provided, returns +nil+.
29
42
  #
30
- # ===== Parameters:
31
- # * [+options+] (+Hash+)
32
- #
33
- # ===== Options:
34
- # * +:url+
35
- # * +:url_encoded+
36
- # * +:client_id+
37
- def update_url(options)
38
- raise ArgumentError, "options hash must be provided", caller unless options.class == Hash
39
- if !options[:url].to_s.empty?
40
- @url_encoded = ""
41
- @url = options[:url].to_s
42
- elsif !options[:url].to_s.empty? && options[:client_id]
43
- @url_encoded = options[:url_encoded].to_s
44
- @url = VkMusic::LinkDecoder.unmask_link(options[:url_encoded], options[:client_id])
43
+ # @return [String, nil] decoded download URL or +nil+ if not available.
44
+ def url
45
+ if url_cached?
46
+ @url
47
+ elsif @url_encoded && @client_id
48
+ @url = VkMusic::LinkDecoder.unmask_link(@url_encoded, @client_id)
45
49
  else
46
- raise ArgumentError, "You should either provide :url or :url_encoded and :client_id", caller
50
+ @url # nil
47
51
  end
48
52
  end
53
+
54
+ ##
55
+ # @return [String, nil] encoded download URL.
56
+ attr_reader :url_encoded
57
+
58
+ ##
59
+ # @return [Boolean] whether decoded URL is already cached.
60
+ def url_cached?
61
+ !!(@url)
62
+ end
63
+
64
+ ##
65
+ # @return [Boolean] whether able to get download URL without web requests.
66
+ def url_available?
67
+ !!(url_cached? || (@url_encoded && @client_id))
68
+ end
69
+
70
+ ##
71
+ # @return [Boolean] whether it's possible to get download URL with {Client#from_id}.
72
+ def url_accessable?
73
+ !!(@id && @owner_id && @secret_1 && @secret_2)
74
+ end
49
75
 
50
- # Returns string with information about audio.
76
+ ##
77
+ # @return [String] information about audio.
51
78
  def to_s
52
79
  "#{@artist} - #{@title} [#{Utility.format_seconds(@duration)}]"
53
80
  end
54
81
 
55
- # Returns extended information about audio.
82
+ ##
83
+ # @return [String] extended information about audio.
56
84
  def pp
57
- "#{to_s} (Got decoded URL: #{@url ? "yes" : "no"}, able to get URL from VK: #{@id && @owner_id && @secret_1 && @secret_2 ? "yes" : "no"})"
85
+ "#{to_s} (Able to get decoded URL: #{url_available? ? "yes" : "no"}, able to get URL from VK: #{url_accessable? ? "yes" : "no"})"
86
+ end
87
+
88
+ ##
89
+ # Update audio URLs.
90
+ #
91
+ # @overload update_url(decoded_url)
92
+ # Simply save download URL.
93
+ # @param decoded_url [String]
94
+ #
95
+ # @overload update_url(audio)
96
+ # Copy URLs from this audio (no checks applied).
97
+ # @param audio [Audio]
98
+ #
99
+ # @overload update_url(options)
100
+ # @macro options_hash_param
101
+ # @option options [String, nil] :url decoded download URL.
102
+ # @option options [String, nil] :url_encoded decoded download URL.
103
+ # @option options [String, nil] :client_id decoded download URL.
104
+ #
105
+ # @return [self]
106
+ def update_url(arg)
107
+ case arg
108
+ when String
109
+ @url = arg
110
+ when Audio
111
+ # Only save URL if it's already decoded and cached
112
+ @url = arg.url if arg.url_cached?
113
+ @url_encoded = arg.url_encoded
114
+ @client_id = arg.client_id
115
+ when Hash
116
+ @url_encoded = Utility.unless_nil_to String, options[:url_encoded]
117
+ @url = Utility.unless_nil_to String, options[:url]
118
+ @client_id = Utility.unless_nil_to Integer, options[:client_id]
119
+ else
120
+ raise ArgumentError, "Bad arguments", caller
121
+ end
122
+ self
58
123
  end
59
124
 
125
+ ##
60
126
  # Initialize new audio.
61
127
  #
62
- # ===== Parameters:
63
- # * [+options+] (+Hash+)
128
+ # @macro options_hash_param
64
129
  #
65
- # ===== Options:
66
- # * +:id+
67
- # * +:owner_id+
68
- # * +:secret_1+
69
- # * +:secret_2+
70
- # * +:artist+
71
- # * +:title+
72
- # * +:duration+
73
- # * +:url_encoded+
74
- # * +:url+
75
- def initialize(options)
76
- # Arguments check
77
- raise ArgumentError, "options hash must be provided", caller unless options.class == Hash
78
- raise ArgumentError, "artist is not provided", caller unless options.has_key?(:artist)
79
- raise ArgumentError, "title is not provided", caller unless options.has_key?(:title)
80
- raise ArgumentError, "duration is not provided", caller unless options.has_key?(:duration)
81
-
82
- # Setting up attributes
83
- @id = options[:id].to_s
84
- @owner_id = options[:owner_id].to_s
85
- @secret_1 = options[:secret_1].to_s
86
- @secret_2 = options[:secret_2].to_s
130
+ # @option options [Integer, nil] :id
131
+ # @option options [Integer, nil] :owner_id
132
+ # @option options [String, nil] :secret_1
133
+ # @option options [String, nil] :secret_2
134
+ # @option options [String] :artist
135
+ # @option options [String] :title
136
+ # @option options [Integer] :duration
137
+ # @option options [String, nil] :url_encoded
138
+ # @option options [String, nil] :url
139
+ # @option options [Integer, nil] :client_id
140
+ def initialize(options = {})
141
+ @id = Utility.unless_nil_to Integer, options[:id]
142
+ @owner_id = Utility.unless_nil_to Integer, options[:owner_id]
143
+ @secret_1 = Utility.unless_nil_to String, options[:secret_1]
144
+ @secret_2 = Utility.unless_nil_to String, options[:secret_2]
87
145
  @artist = options[:artist].to_s
88
146
  @title = options[:title].to_s
89
147
  @duration = options[:duration].to_i
90
- @url_encoded = options[:url_encoded].to_s
91
- @url = options[:url].to_s
148
+ @url_encoded = Utility.unless_nil_to String, options[:url_encoded]
149
+ @url = Utility.unless_nil_to String, options[:url]
150
+ @client_id = Utility.unless_nil_to Integer, options[:client_id]
92
151
  end
93
152
 
153
+ ##
94
154
  # Initialize new audio from Nokogiri HTML node.
95
155
  #
96
- # ===== Parameters:
97
- # * [+node+] (+Nokogiri::XML::Node+)
98
- # * [+client_id+] (+Integer+)
156
+ # @param node [Nokogiri::XML::Node] node, which match following CSS selector: +.audio_item.ai_has_btn+
157
+ # @param client_id [Integer]
158
+ #
159
+ # @return [Audio]
99
160
  def self.from_node(node, client_id)
100
161
  url_encoded = node.at_css("input").attribute("value").to_s
101
- url_encoded = nil if url_encoded == "https://m.vk.com/mp3/audio_api_unavailable.mp3"
162
+ url_encoded = nil if url_encoded == Constants::URL::VK[:audio_unavailable]
102
163
  id_array = node.attribute("data-id").to_s.split("_")
103
164
 
104
165
  new({
105
- :id => id_array[1],
106
- :owner_id => id_array[0],
166
+ :id => id_array[1].to_i,
167
+ :owner_id => id_array[0].to_i,
107
168
  :artist => node.at_css(".ai_artist").text.strip,
108
169
  :title => node.at_css(".ai_title").text.strip,
109
170
  :duration => node.at_css(".ai_dur").attribute("data-dur").to_s.to_i,
110
- :url_encoded => url_encoded,
111
- :url => url_encoded ? VkMusic::LinkDecoder.unmask_link(url_encoded, client_id) : nil,
171
+ :url_encoded => url_encoded.to_s,
172
+ :url => nil,
173
+ :client_id => client_id
112
174
  })
113
175
  end
114
176
 
115
- # Initialize new audio from data array.
177
+ ##
178
+ # Initialize new audio from VK data array.
179
+ #
180
+ # @param data [Array]
181
+ # @param client_id [Integer]
116
182
  #
117
- # ===== Parameters:
118
- # * [+data+] (+Array+)
119
- # * [+client_id+] (+Integer+)
120
- def self.from_data_array(data, client_id)
121
- url_encoded = data[2]
122
- url_encoded = nil if url_encoded == ""
183
+ # @return [Audio]
184
+ def self.from_data(data, client_id)
185
+ url_encoded = data[2].to_s
123
186
 
124
- secrets = data[13].split("/")
187
+ secrets = data[13].to_s.split("/")
125
188
 
126
189
  new({
127
- :id => data[0],
128
- :owner_id => data[1],
190
+ :id => data[0].to_i,
191
+ :owner_id => data[1].to_i,
129
192
  :secret_1 => secrets[3],
130
193
  :secret_2 => secrets[5],
131
194
  :artist => CGI.unescapeHTML(data[4]),
132
195
  :title => CGI.unescapeHTML(data[3]),
133
- :duration => data[5],
196
+ :duration => data[5].to_i,
134
197
  :url_encoded => url_encoded,
135
- :url => url_encoded ? VkMusic::LinkDecoder.unmask_link(url_encoded, client_id) : nil,
198
+ :url => nil,
199
+ :client_id => client_id
136
200
  })
137
201
  end
138
202
 
@@ -1,334 +1,525 @@
1
1
  require "mechanize"
2
2
  require "json"
3
3
 
4
+ ##
5
+ # @!macro [new] options_hash_param
6
+ # @param options [Hash] hash with options.
7
+ #
8
+ # @!macro [new] playlist_return
9
+ # @return [Playlist] playlist with audios. Possibly empty.
10
+ # Possibly contains audios without download URL.
11
+
12
+ ##
13
+ # Main module.
4
14
  module VkMusic
5
15
 
16
+ ##
6
17
  # Main class with all the interface.
18
+ # To start working with VK audios firstly create new client with +Client.new+.
7
19
  class Client
8
20
 
9
- # ID of user
21
+ ##
22
+ # @return [Integer] ID of client.
10
23
  attr_reader :id
11
- # Name of user
24
+
25
+ ##
26
+ # @return [String] name of client.
12
27
  attr_reader :name
13
28
 
14
- # Mechanize agent
15
- @agent = nil
16
-
17
- def initialize(options)
29
+ @agent = nil # Mechanize agent
30
+
31
+ ##
32
+ # Create new client and login.
33
+ #
34
+ # @macro options_hash_param
35
+ #
36
+ # @option options [String] :username usually telephone number or email.
37
+ # @option options [String] :password
38
+ def initialize(options = {})
18
39
  # Arguments check
19
- raise ArgumentError, "options hash must be provided", caller unless options.class == Hash
20
- raise ArgumentError, "username is not provided", caller unless options.has_key?(:username)
21
- raise ArgumentError, "password is not provided", caller unless options.has_key?(:password)
40
+ raise ArgumentError, "Options hash must be provided", caller unless options.class == Hash
41
+ raise ArgumentError, "Username is not provided", caller unless options.has_key?(:username)
42
+ raise ArgumentError, "Password is not provided", caller unless options.has_key?(:password)
22
43
 
23
44
  # Setting up client
24
45
  @agent = Mechanize.new
25
46
  login(options[:username], options[:password])
26
47
  end
48
+
49
+ ##
50
+ #@!group Loading audios
27
51
 
28
- # Find Audio.
52
+ ##
53
+ # Search for audio.
54
+ #
55
+ # @note some audios might be removed from search.
29
56
  #
30
- # ===== Parameters:
31
- # * [+query+] (+String+) - string to search for.
57
+ # @todo search in group audios.
32
58
  #
33
- # ===== Returns:
34
- # * (+Array+) - array of Audio.
35
- def find_audio(query)
36
- uri = URI(Constants::VK_URL[:audios])
59
+ # @overload find(query)
60
+ # @param query [String] string to search for.
61
+ #
62
+ # @overload find(options)
63
+ # @macro options_hash_param
64
+ # @option options [String] :query string to search for.
65
+ #
66
+ # @return [Array<Audio>] array with audios matching given string. Possibly empty.
67
+ # Possibly contains audios without download URL.
68
+ def find(arg)
69
+ begin
70
+ case arg
71
+ when String
72
+ query = arg
73
+ when Hash
74
+ query = arg[:query].to_s
75
+ else
76
+ raise
77
+ end
78
+ rescue
79
+ raise ArgumentError, "Bad arguments", caller
80
+ end
81
+
82
+ uri = URI(Constants::URL::VK[:audios])
37
83
  uri.query = Utility.hash_to_params({ "act" => "search", "q" => query.to_s })
38
- load_audios_from_page(uri)
84
+
85
+ audios__from_page(uri)
39
86
  end
87
+ alias search find
40
88
 
41
- # Get Playlist.
89
+ ##
90
+ # @!macro [new] pl__options
91
+ # @option options [Integer] :up_to (MAXIMUM_PLAYLIST_SIZE) maximum amount of audios to load.
92
+ # If 0, no audios would be loaded (Just information about playlist).
93
+ # If less than 0, will load whole playlist.
94
+ # @option options [Boolean] :with_url (true) makes all the audios have download URLs,
95
+ # but every 100 audios will cost one more request. You can reduce amount of requests using option +up_to+.
96
+ # Otherwise audio download URL would be accessable only with {Client#from_id}.
97
+ # Main advantage of disabling URLs is the fact that 2000 audios will be loaded per request,
98
+ # which is 20 times more effecient.
99
+ #
100
+ # Get VK playlist.
42
101
  #
43
- # ===== Parameters:
44
- # * [+url+] (+String+) - url to playlist.
45
- # * [+up_to+] (+Integer+) - maximum amount of Audio to load.
102
+ # @overload playlist(url, options)
103
+ # @param url [String] URL to playlist.
104
+ # @macro options_hash_param
105
+ # @macro pl__options
46
106
  #
47
- # ===== Returns:
48
- # * (+Playlist+)
49
- def get_playlist(url, up_to = nil)
50
- # NOTICE: it is possible to use same type of requests as in get_audios method
107
+ # @overload playlist(options)
108
+ # Use options +owner_id+, +playlist_id+ and +access_hash+ instead of URL.
109
+ # @macro options_hash_param
110
+ # @option options [Integer] :owner_id playlist owner ID.
111
+ # @option options [Integer] :playlist_id ID of the playlist.
112
+ # @option options [String] :access_hash access hash to playlist. Might not exist.
113
+ # @macro pl__options
114
+ #
115
+ # @macro playlist_return
116
+ def playlist(*args)
51
117
  begin
52
- url, owner_id, id, access_hash = url.to_s.match(Constants::PLAYLIST_URL_REGEX).to_a
53
-
54
- # Load first page and get info
55
- first_page = load_playlist_page(owner_id: owner_id, id: id, access_hash: access_hash, offset: 0)
56
-
57
- # Parse out essential data
58
- title = first_page.at_css(".audioPlaylist__title").text.strip
59
- subtitle = first_page.at_css(".audioPlaylist__subtitle").text.strip
60
-
61
- footer_node = first_page.at_css(".audioPlaylist__footer")
62
- if footer_node
63
- footer_match = footer_node.text.strip.match(/^\d+/)
64
- playlist_size = footer_match ? footer_match[0].to_i : 0
65
- else
66
- playlist_size = 0
118
+ case
119
+ when (args.size == 1 && String === args[0]) ||
120
+ (args.size == 2 && String === args[0] && Hash === args[1])
121
+ options = args[1] || {}
122
+ owner_id, playlist_id, access_hash = args[0].to_s.match(Constants::Regex::VK_PLAYLIST_URL_POSTFIX).captures
123
+ when args.size == 1 && Hash === args[0]
124
+ options = args[0]
125
+ owner_id, playlist_id, access_hash = options[:owner_id].to_i, options[:playlist_id].to_i, options[:access_hash].to_s
126
+ else
127
+ raise
67
128
  end
68
- rescue Exception => error
69
- raise Exceptions::PlaylistParseError, "unable to parse playlist page. Error: #{error.message}", caller
129
+ rescue
130
+ raise ArgumentError, "Bad arguments", caller
70
131
  end
71
- # Now we can be sure we are on correct page
72
-
73
- first_page_audios = load_audios_from_page(first_page)
74
132
 
75
- # Check whether need to make additional requests
76
- up_to = playlist_size if (up_to.nil? || up_to < 0 || up_to > playlist_size)
77
- if first_page_audios.length >= up_to
78
- list = first_page_audios[0, up_to]
79
- else
80
- list = first_page_audios
81
- loop do
82
- playlist_page = load_playlist_page(owner_id: owner_id, id: id, access_hash: access_hash, offset: list.length)
83
- list.concat(load_audios_from_page(playlist_page)[0, up_to - list.length])
84
- break if list.length == up_to
85
- end
133
+ options[:up_to] ||= Constants::MAXIMUM_PLAYLIST_SIZE
134
+ options[:with_url] = true if options[:with_url].nil?
135
+
136
+ if options[:with_url]
137
+ playlist__web(owner_id, playlist_id, access_hash, options)
138
+ else
139
+ playlist__json(owner_id, playlist_id, access_hash, options)
86
140
  end
87
-
88
- Playlist.new(list, {
89
- :id => id,
90
- :owner_id => owner_id,
91
- :access_hash => access_hash,
92
- :title => title,
93
- :subtitle => subtitle,
94
- })
95
141
  end
96
142
 
143
+ ##
144
+ # @!macro [new] ua__options
145
+ # @option options [Integer] :up_to (MAXIMUM_PLAYLIST_SIZE) maximum amount of audios to load.
146
+ # If 0, no audios would be loaded (Just information about playlist).
147
+ # If less than 0, will load whole playlist.
148
+ #
97
149
  # Get user or group audios.
98
150
  #
99
- # ===== Parameters:
100
- # * [+url+] (+String+) - URL to user or group.
101
- # * [+up_to+] (+Integer+) - maximum amount of Audio to load.
151
+ # @note currently this method is only able to load 100 audios with download URL.
102
152
  #
103
- # ===== Returns:
104
- # * (+Playlist+)
105
- def get_audios(url, up_to = nil)
106
- if up_to && up_to > 100
107
- Utility.warn("Current implementation of method VkMusic::Client#get_audios is only able to load first 100 audios from user page.")
108
- end
109
- # NOTICE: this method is only able to load first 100 audios
110
- # NOTICE: it is possible to download 50 audios per request on "https://m.vk.com/audios#{owner_id}?offset=#{offset}", so it will cost A LOT to download all of audios (up to 200 requests).
111
- # NOTICE: it is possible to load up to 2000 audios **without url** if offset is negative
112
-
113
- # Firstly, we need to get numeric id
114
- id = get_id(url.to_s)
115
-
116
- # Trying to parse out audios
153
+ # @overload audios(url, options)
154
+ # @param url [String] URL to user/group page or audios.
155
+ # @macro options_hash_param
156
+ # @macro ua__options
157
+ #
158
+ # @overload audios(options)
159
+ # @macro options_hash_param
160
+ # @option options [Integer] :owner_id numerical ID of owner.
161
+ # @macro ua__options
162
+ #
163
+ # @macro playlist_return
164
+ def audios(*args)
117
165
  begin
118
- first_json = load_playlist_json_section(id.to_s, -1, 0)
119
- first_data = first_json["data"][0]
120
- first_data_audios = load_audios_from_data(first_data["list"])
121
- rescue Exception => error
122
- raise Exceptions::AudiosSectionParseError, "unable to load or parse audios section: #{error.message}", caller
166
+ case
167
+ when (args.size == 1 && String === args[0] ) ||
168
+ (args.size == 2 && String === args[0] && Hash === args[1])
169
+ owner_id = page_id(args[0].to_s)
170
+ options = args[1] || {}
171
+ when args.size == 1 && Hash === args[0]
172
+ owner_id = args[0][:owner_id].to_i
173
+ options = args[0]
174
+ else
175
+ raise
176
+ end
177
+ rescue
178
+ raise ArgumentError, "Bad arguments", caller
123
179
  end
124
-
125
- #total_count = first_data["totalCount"] # NOTICE: not used due to restrictions described above
126
- total_count = first_data_audios.length # Using this instead
127
180
 
128
- # TODO: Loading rest
181
+ options[:up_to] ||= Constants::MAXIMUM_PLAYLIST_SIZE
129
182
 
130
- up_to = total_count if (up_to.nil? || up_to < 0 || up_to > total_count)
131
- list = first_data_audios.first(up_to)
132
-
133
- # It turns out user audios are just playlist with id -1
134
- Playlist.new(list, {
135
- :id => first_data["id"],
136
- :owner_id => first_data["owner_id"],
137
- :access_hash => first_data["access_hash"],
138
- :title => CGI.unescapeHTML(first_data["title"].to_s),
139
- :subtitle => CGI.unescapeHTML(first_data["subtitle"].to_s),
140
- })
183
+ playlist__json(owner_id, -1, nil, options)
141
184
  end
142
185
 
143
- # Get audios by their ids and secrets.
186
+ ##
187
+ # @!macro [new] wall__up_to_option
188
+ # @option up_to [Integer] :up_to (10) maximum amount of audios to load from wall.
189
+ #
190
+ # @!macro [new] wall__with_url_option
191
+ # @option options [Boolean] :with_url (true) automatically use {Client#from_id} to get download URLs.
144
192
  #
145
- # ===== Parameters:
146
- # * [+arr+] (+Array+) - Array of objects, which can have different types: Audio or Array[owner_id, id, secret_1, secret_2].
193
+ # Get audios on wall of user or group starting with given post.
147
194
  #
148
- # ===== Returns:
149
- # * (+Array+) - array of audios with decoded URLs.
150
- def get_audios_by_id(*arr)
151
- if arr.size > 10
152
- Utility.warn("Current implementation of method VkMusic::Client#get_audios_by_id is only able to handle first 10 audios.")
153
- arr = arr.first(10)
154
- end
155
-
156
- arr.map! do |el|
157
- case el
158
- when Array
159
- el.join("_")
160
- when Audio
161
- "#{el.owner_id}_#{el.id}_#{el.secret_1}_#{el.secret_2}"
195
+ # @note this method is only able to load up to 91 audios from wall.
196
+ #
197
+ # @overload wall(url, options)
198
+ # Load last audios from wall.
199
+ # @param url [String] URL to user/group page.
200
+ # @macro options_hash_param
201
+ # @macro wall__up_to_option
202
+ # @macro wall__with_url_option
203
+ #
204
+ # @overload wall(options)
205
+ # Load audios starting from some exact post.
206
+ # @macro options_hash_param
207
+ # @option options [Integer] :owner_id numerical ID of wall owner.
208
+ # @option options [Integer] :post_id numerical ID of post.
209
+ # @macro wall__up_to_option
210
+ # @macro wall__with_url_option
211
+ #
212
+ # @return [Array<Audio>] array of audios from wall. Possibly empty.
213
+ def wall(*args)
214
+ begin
215
+ case
216
+ when (args.size == 1 && args[0].class == String) ||
217
+ (args.size == 2 && args[0].class == String && args[1].class == Hash)
218
+ url = args[0].to_s
219
+ owner_id = page_id(url)
220
+ post_id = last_post_id(owner_id)
221
+ options = args[1] || {}
222
+ return [] if post_id.nil?
223
+ when args.length == 1 && Hash === args[0]
224
+ options = args[0]
225
+ owner_id = options[:owner_id].to_i
226
+ post_id = options[:post_id].to_i
162
227
  else
163
- el.to_s
228
+ raise
164
229
  end
230
+ rescue Exceptions::ParseError => error
231
+ raise Exceptions::ParseError, "Unable to get last post id. Error: #{error.message}", caller
232
+ rescue
233
+ raise ArgumentError, "Bad arguments", caller
165
234
  end
166
- json = load_audios_json_by_id(arr)
167
- result = load_audios_from_data(json["data"][0].to_a)
168
- raise Exceptions::ReloadAudiosParseError, "Result size don't match: excepected #{arr.size}, got #{result.size}", caller if result.size != arr.size
169
235
 
170
- result
236
+ options[:up_to] ||= 10
237
+ options[:with_url] = true if options[:with_url].nil?
238
+
239
+ wall__json(owner_id, post_id, options)
171
240
  end
172
241
 
173
- # Get audios on wall of user or group starting with given post.
242
+ ##
243
+ # Get audios attached to post.
174
244
  #
175
- # ===== Parameters:
176
- # * [+owner_id+] (+Integer+)
177
- # * [+post_id+] (+Integer+)
178
- # * [+up_to+] (+Integer+) - maximum amount of Audio to load.
245
+ # @overload post(url)
246
+ # @param url [String] URL to post.
179
247
  #
180
- # ===== Returns:
181
- # * (+Array+) - array of audios with URLs.
182
- def get_audios_from_wall(owner_id, post_id, up_to = nil)
248
+ # @overload post(options)
249
+ # @macro options_hash_param
250
+ # @option options [Integer] :owner_id numerical ID of wall owner.
251
+ # @option options [Integer] :post_id numerical ID of post.
252
+ #
253
+ # @return [Array<Audio>] audios with download URLs.
254
+ def post(arg)
183
255
  begin
184
- json = load_audios_json_from_wall(owner_id, post_id)
185
- data = json["data"][0]
186
- no_url_audios = load_audios_from_data(data["list"])
187
- rescue Exception => error
188
- raise Exceptions::WallParseError, "Failed to parse wall from #{@owner_id}_#{post_id}. Error: #{error.message}", caller
256
+ case arg
257
+ when String
258
+ owner_id, post_id = arg.match(Constants::Regex::VK_WALL_URL_POSTFIX).captures
259
+ when Hash
260
+ options = arg
261
+ owner_id = options[:owner_id].to_i
262
+ post_id = options[:post_id].to_i
263
+ else
264
+ raise
265
+ end
266
+ rescue
267
+ raise ArgumentError, "Bad arguments", caller
189
268
  end
190
269
 
191
- up_to = no_url_audios.size if (up_to.nil? || up_to < 0 || up_to > no_url_audios.size)
192
- no_url_audios = no_url_audios.first(up_to)
193
-
194
- list = get_audios_by_id(*no_url_audios)
195
-
196
- Playlist.new(list, {
197
- :id => data["id"],
198
- :owner_id => data["owner_id"],
199
- :access_hash => data["access_hash"],
200
- :title => CGI.unescapeHTML(data["title"].to_s),
201
- :subtitle => CGI.unescapeHTML(data["subtitle"].to_s),
202
- })
270
+ amount = attached_audios_amount(owner_id: owner_id, post_id: post_id)
271
+ wall(owner_id: owner_id, post_id: post_id, up_to: amount)
203
272
  end
204
-
205
- # Get audios attached to post.
273
+
274
+ ##
275
+ # Get audios with download URLs by their IDs and secrets.
206
276
  #
207
- # ===== Parameters:
208
- # * [+url+] (+String+)
277
+ # @note warning: audios must not match.
209
278
  #
210
- # ===== Returns:
211
- # * (+Array+) - array of audios with URLs.
212
- def get_audios_from_post(url)
213
- url, owner_id, post_id = url.match(Constants::POST_URL_REGEX).to_a
214
-
215
- amount = get_amount_of_audios_in_post(owner_id, post_id)
216
- get_audios_from_wall(owner_id, post_id, amount).to_a
279
+ # @todo workaround for not unique audios in request
280
+ #
281
+ # @param args [Array<Audio, Array<(owner_id, audio_id, secret_1, secret_2)>, "#{owner_id}_#{id}_#{secret_1}_#{secret_2}">]
282
+ #
283
+ # @return [Array<Audio>] array of audios with download URLs.
284
+ def from_id(args)
285
+ begin
286
+ args.map! do |el|
287
+ case el
288
+ when Array
289
+ el.join("_")
290
+ when Audio
291
+ "#{el.owner_id}_#{el.id}_#{el.secret_1}_#{el.secret_2}"
292
+ when String
293
+ el # Do not change
294
+ else
295
+ raise
296
+ end
297
+ end
298
+ rescue
299
+ raise ArgumentError, "Bad arguments", caller
300
+ end
301
+
302
+ result = []
303
+ args.each_slice(10) do |subarray|
304
+ json = load__json__audios_by_id(subarray)
305
+ subresult = audios__from_data(json["data"][0].to_a)
306
+ raise Exceptions::ParseError, "Result size don't match: excepected #{subarray.size}, got #{subresult.size}", caller if subresult.size != subarray.size
307
+ result.concat(subresult)
308
+ end
309
+ result
217
310
  end
218
311
 
219
- # Get user or group id.
312
+ ##
313
+ # @!endgroup
314
+
315
+ ##
316
+ # @!group Other
317
+
318
+ ##
319
+ # Get user or group ID. Sends one request if custom ID provided
220
320
  #
221
- # ===== Parameters:
222
- # * [+str+] (+String+) - link, id with prefix or custom id.
321
+ # @param str [String] link, ID with/without prefix or custom ID.
223
322
  #
224
- # ===== Returns:
225
- # * (+Integer+)
226
- def get_id(str)
323
+ # @return [Integer] page ID.
324
+ def page_id(str)
325
+ raise ArgumentError, "Bad arguments", caller unless str.class == String
326
+
227
327
  case str
228
- when Constants::VK_URL_REGEX
229
- path = str.match(Constants::VK_URL_REGEX)[1]
230
- get_id(path) # Recursive call
231
- when Constants::VK_ID_REGEX
232
- str
233
- when Constants::VK_AUDIOS_REGEX
234
- str.match(/-?\d+/).to_s # Numbers with sigh
235
- when Constants::VK_PREFIXED_ID_REGEX
236
- id = str.match(/\d+/).to_s # Just numbers. Sign needed
237
- id = "-#{id}" unless str.start_with?("id")
238
- id
239
- when Constants::VK_CUSTOM_ID_REGEX
328
+ when Constants::Regex::VK_URL
329
+ path = str.match(Constants::Regex::VK_URL)[1]
330
+ id = page_id(path) # Recursive call
331
+ when Constants::Regex::VK_ID_STR
332
+ id = str.to_i
333
+ when Constants::Regex::VK_AUDIOS_URL_POSTFIX
334
+ id = str.match(/-?\d+/).to_s.to_i # Numbers with sign
335
+ when Constants::Regex::VK_PREFIXED_ID_STR
336
+ id = str.match(/\d+/).to_s.to_i # Just numbers. Sign needed
337
+ id *= -1 unless str.start_with?("id")
338
+ when Constants::Regex::VK_CUSTOM_ID
339
+ url = "#{Constants::URL::VK[:home]}/#{str}"
240
340
  begin
241
- page = load_page("#{Constants::VK_URL[:home]}/#{str}")
242
- rescue Exception => error
243
- raise Exceptions::IdParseError, "unable to load page by id \"#{str}\". Error: #{error.message}"
244
- end
341
+ page = load__page(url)
342
+ rescue Exceptions::RequestError => error
343
+ raise Exceptions::ParseError, "Failed request: #{error.message}", caller
344
+ end
245
345
 
246
- unless page.at_css(".PageBlock .owner_panel")
247
- # Ensure this isn't some random vk page
248
- raise Exceptions::IdParseError, "page #{str} doesn't seem to be a group or user page"
249
- end
346
+ raise Exceptions::ParseError, "Page #{str} doesn't seem to be a group or user page", caller unless page.at_css(".PageBlock .owner_panel")
250
347
 
251
- id = page.link_with(href: Constants::VK_HREF_ID_CONTAINING_REGEX).href.slice(/-?\d+/) # Numbers with sign
252
- id
348
+ begin
349
+ id = page.link_with(href: Constants::Regex::VK_HREF_ID_CONTAINING).href.slice(Constants::Regex::VK_ID).to_i # Numbers with sign
350
+ rescue Exception => error
351
+ raise Exceptions::ParseError, "Unable to get user or group ID. Custom ID: #{str}. Error: #{error.message}", caller
352
+ end
253
353
  else
254
- raise Exceptions::IdParseError, "unable to convert \"#{str}\" into id"
354
+ raise Exceptions::ParseError, "Unable to convert \"#{str}\" into ID", caller
255
355
  end
356
+ id
256
357
  end
257
358
 
359
+ ##
360
+ # Get ID of last post.
361
+ #
362
+ # @note requesting for "vk.com/id0" will raise ArgumentError.
363
+ # Use +client.last_post_id(owner_id: client.id)+ to get last post of client.
364
+ #
365
+ # @overload last_post_id(url)
366
+ # @param url [String] URL to wall owner.
367
+ #
368
+ # @overload last_post_id(owner_id)
369
+ # @param owner_id [Integer] numerical ID of wall owner.
370
+ #
371
+ # @return [Integer, nil] ID of last post or +nil+ if there are no posts.
372
+ def last_post_id(arg)
373
+ begin
374
+ case arg
375
+ when String
376
+ path = arg.match(Constants::Regex::VK_URL)[1]
377
+ when Integer
378
+ owner_id = arg
379
+ path = "#{owner_id < 0 ? "club" : "id"}#{owner_id.abs}"
380
+ else
381
+ raise
382
+ end
383
+ rescue
384
+ raise ArgumentError, "Bad arguments", caller
385
+ end
386
+ raise ArgumentError, "Requesting this method for id0 is forbidden", caller if path == "id0"
387
+
388
+ url = "#{Constants::URL::VK[:home]}/#{path}"
389
+
390
+ begin
391
+ page = load__page(url)
392
+ rescue Exceptions::RequestError => error
393
+ raise Exceptions::ParseError, "Failed request: #{error.message}", caller
394
+ end
395
+
396
+ # Ensure this isn't some random vk page
397
+ raise Exceptions::ParseError, "Page at #{url} doesn't seem to be a group or user page", caller unless page.at_css(".PageBlock .owner_panel")
398
+
399
+ begin
400
+ posts = page.css(".wall_posts > .wall_item .post__anchor")
401
+ posts_ids = posts.map do |post|
402
+ post ? post.attribute("name").to_s.match(Constants::Regex::VK_POST_URL_POSTFIX)[2].to_i : 0
403
+ end
404
+ # To avoid checking id of pinned post need to take maximum id.
405
+ return posts_ids.max
406
+ rescue Exception => error
407
+ raise Exceptions::ParseError, "Unable to get last post on #{url}. Error: #{error.message}", caller
408
+ end
409
+ end
410
+
411
+ ##
258
412
  # Get amount of audios attached to specified post.
259
413
  #
260
- # ===== Parameters:
261
- # * [+owner_id+] (+Integer+)
262
- # * [+post_id+] (+Integer+)
414
+ # @overload attached_audios_amount(url)
415
+ # @param url [String] URL to post.
263
416
  #
264
- # ===== Returns:
265
- # * (+Integer+)
266
- def get_amount_of_audios_in_post(owner_id, post_id)
417
+ # @overload attached_audios_amount(options)
418
+ # @macro options_hash_param
419
+ # @option options [Integer] :owner_id numerical ID of wall owner.
420
+ # @option options [Integer] :post_id numerical ID of post.
421
+ #
422
+ # @return [Integer] amount of audios.
423
+ def attached_audios_amount(arg)
424
+ begin
425
+ case arg
426
+ when String
427
+ owner_id, post_id = arg.match(Constants::Regex::VK_WALL_URL_POSTFIX).captures
428
+ when Hash
429
+ options = arg
430
+ owner_id = options[:owner_id].to_i
431
+ post_id = options[:post_id].to_i
432
+ else
433
+ raise
434
+ end
435
+ rescue
436
+ raise ArgumentError, "Bad arguments", caller
437
+ end
438
+
439
+ url = "#{Constants::URL::VK[:wall]}#{owner_id}_#{post_id}"
440
+ begin
441
+ page = load__page(url)
442
+ rescue Exceptions::RequestError => error
443
+ raise Exceptions::ParseError, "Failed request: #{error.message}", caller
444
+ end
445
+
446
+ raise Exceptions::ParseError, "Post not found: #{owner_id}_#{post_id}", caller unless page.css(".service_msg_error").empty?
267
447
  begin
268
- page = load_page("#{Constants::VK_URL[:wall]}#{owner_id}_#{post_id}")
269
448
  result = page.css(".wi_body > .pi_medias .medias_audio").size
270
449
  rescue Exception => error
271
- raise Exceptions::PostParseError, "Unable to get amount of audios in post #{owner_id}_#{post_id}. Error: #{error.message}", caller
450
+ raise Exceptions::ParseError, "Unable to get amount of audios in post #{owner_id}_#{post_id}. Error: #{error.message}", caller
272
451
  end
273
- raise Exceptions::PostParseError, "Post not found: #{owner_id}_#{post_id}", caller if result == 0 && !page.css(".service_msg_error").empty?
274
452
  result
275
453
  end
276
454
 
455
+ ##
456
+ # @!endgroup
277
457
 
278
458
  private
279
-
280
- # Loading pages
281
- def load_page(url)
282
- uri = URI(url) if url.class != URI
283
- @agent.get(uri)
459
+
460
+ # Load page by URL. And return Mechanize::Page.
461
+ def load__page(url)
462
+ uri = URI(url) if url.class != URI
463
+ Utility.debug("Loading #{uri}")
464
+ begin
465
+ @agent.get(uri)
466
+ rescue Exception => error
467
+ raise Exceptions::RequestError, error.message, caller
468
+ end
284
469
  end
285
- def load_json(url)
286
- page = load_page(url)
287
- JSON.parse(page.body.strip)
470
+
471
+ # Load JSON by URL. And return JSON object.
472
+ def load__json(url)
473
+ page = load__page(url)
474
+ begin
475
+ JSON.parse(page.body.strip)
476
+ rescue Exception => error
477
+ raise Exceptions::ParseError, error.message, caller
478
+ end
288
479
  end
289
-
290
- def load_playlist_page(options)
291
- uri = URI(Constants::VK_URL[:audios])
480
+
481
+
482
+ # Load playlist web page.
483
+ def load__page__playlist(owner_id, playlist_id, access_hash, options)
484
+ uri = URI(Constants::URL::VK[:audios])
292
485
  uri.query = Utility.hash_to_params({
293
- "act" => "audio_playlist#{options[:owner_id]}_#{options[:id]}",
294
- "access_hash" => options[:access_hash].to_s,
486
+ "act" => "audio_playlist#{owner_id.to_i}_#{playlist_id.to_i}",
487
+ "access_hash" => access_hash.to_s,
295
488
  "offset" => options[:offset].to_i
296
489
  })
297
- load_page(uri)
490
+ load__page(uri)
298
491
  end
299
- def load_playlist_json_section(owner_id, playlist_id, offset = 0)
300
- uri = URI(Constants::VK_URL[:audios])
492
+
493
+ # Load JSON playlist part with +load_section+ request.
494
+ def load__json__playlist_section(owner_id, playlist_id, access_hash, options)
495
+ uri = URI(Constants::URL::VK[:audios])
301
496
  uri.query = Utility.hash_to_params({
302
497
  "act" => "load_section",
303
- "owner_id" => owner_id,
304
- "playlist_id" => playlist_id,
498
+ "owner_id" => owner_id.to_i,
499
+ "playlist_id" => playlist_id.to_i,
500
+ "access_hash" => access_hash.to_s,
305
501
  "type" => "playlist",
306
- "offset" => offset,
502
+ "offset" => options[:offset].to_i,
307
503
  "utf8" => true
308
504
  })
309
- begin
310
- load_json(uri)
311
- rescue Exception => error
312
- raise Exceptions::AudiosSectionParseError, "unable to load or parse audios section: #{error.message}", caller
313
- end
505
+ load__json(uri)
314
506
  end
315
507
 
316
- def load_audios_json_by_id(ids)
317
- uri = URI(Constants::VK_URL[:audios])
508
+
509
+ # Load JSON audios with +reload_audio+ request.
510
+ def load__json__audios_by_id(ids)
511
+ uri = URI(Constants::URL::VK[:audios])
318
512
  uri.query = Utility.hash_to_params({
319
513
  "act" => "reload_audio",
320
- "ids" => ids,
514
+ "ids" => ids.to_a,
321
515
  "utf8" => true
322
516
  })
323
- begin
324
- load_json(uri)
325
- rescue Exception => error
326
- raise Exceptions::AudiosSectionParseError, "unable to load or parse audios section: #{error.message}", caller
327
- end
517
+ load__json(uri)
328
518
  end
329
519
 
330
- def load_audios_json_from_wall(owner_id, post_id)
331
- uri = URI(Constants::VK_URL[:audios])
520
+ # Load JSON audios with +load_section+ from wall.
521
+ def load__json__audios_wall(owner_id, post_id)
522
+ uri = URI(Constants::URL::VK[:audios])
332
523
  uri.query = Utility.hash_to_params({
333
524
  "act" => "load_section",
334
525
  "owner_id" => owner_id,
@@ -337,43 +528,145 @@ module VkMusic
337
528
  "wall_type" => "own",
338
529
  "utf8" => true
339
530
  })
531
+ load__json(uri)
532
+ end
533
+
534
+
535
+ # Loading audios from web page.
536
+ def audios__from_page(obj)
537
+ page = obj.class == Mechanize::Page ? obj : load__page(obj)
340
538
  begin
341
- load_json(uri)
539
+ page.css(".audio_item.ai_has_btn").map { |elem| Audio.from_node(elem, @id) }
342
540
  rescue Exception => error
343
- raise Exceptions::AudiosSectionParseError, "unable to load or parse audios section: #{error.message}", caller
541
+ raise Exceptions::ParseError, error.message, caller
344
542
  end
345
- end
346
-
347
-
348
- # Loading audios
349
- def load_audios_from_page(obj)
350
- page = obj.class == Mechanize::Page ? obj : load_page(obj)
351
- page.css(".audio_item.ai_has_btn").map { |elem| Audio.from_node(elem, @id) }
352
543
  end
353
- def load_audios_from_data(data)
354
- data.map { |audio_data| Audio.from_data_array(audio_data, @id) }
544
+
545
+ # Load audios from JSON data.
546
+ def audios__from_data(data)
547
+ begin
548
+ data.map { |audio_data| Audio.from_data(audio_data, @id) }
549
+ rescue Exception => error
550
+ raise Exceptions::ParseError, error.message, caller
551
+ end
355
552
  end
356
-
357
-
553
+
554
+
555
+ # Load playlist through web page requests.
556
+ def playlist__web(owner_id, playlist_id, access_hash, options)
557
+ begin
558
+ # Load first page and get info
559
+ first_page = load__page__playlist(owner_id, playlist_id, access_hash, offset: 0)
560
+
561
+ # Parse out essential data
562
+ title = first_page.at_css(".audioPlaylist__title").text.strip
563
+ subtitle = first_page.at_css(".audioPlaylist__subtitle").text.strip
564
+
565
+ footer_node = first_page.at_css(".audioPlaylist__footer")
566
+ if footer_node
567
+ footer_match = footer_node.text.strip.match(/^\d+/)
568
+ playlist_size = footer_match ? footer_match[0].to_i : 0
569
+ else
570
+ playlist_size = 0
571
+ end
572
+ rescue Exception => error
573
+ raise Exceptions::ParseError, error.message, caller
574
+ end
575
+ # Now we can be sure we are on correct page
576
+
577
+ first_page_audios = audios__from_page(first_page)
578
+
579
+ # Check whether need to make additional requests
580
+ options[:up_to] = playlist_size if (options[:up_to] < 0 || options[:up_to] > playlist_size)
581
+ list = first_page_audios[0, options[:up_to]]
582
+ while list.length < options[:up_to] do
583
+ playlist_page = load__page__playlist(owner_id, playlist_id, access_hash, offset: list.length)
584
+ list.concat(audios__from_page(playlist_page)[0, options[:up_to] - list.length])
585
+ end
586
+
587
+ Playlist.new(list, {
588
+ :id => id,
589
+ :owner_id => owner_id,
590
+ :access_hash => access_hash,
591
+ :title => title,
592
+ :subtitle => subtitle,
593
+ })
594
+ end
595
+
596
+ # Load playlist through JSON requests.
597
+ def playlist__json(owner_id, playlist_id, access_hash, options)
598
+ if options[:up_to] < 0 || options[:up_to] > 100
599
+ Utility.warn("Current implementation of this method is not able to return more than 100 first audios with URL.")
600
+ end
601
+
602
+ # Trying to parse out audios
603
+ begin
604
+ first_json = load__json__playlist_section(owner_id, playlist_id, access_hash, offset: 0)
605
+ first_data = first_json["data"][0]
606
+ first_data_audios = audios__from_data(first_data["list"])
607
+ rescue Exception => error
608
+ raise Exceptions::ParseError, error.message, caller
609
+ end
610
+
611
+ total_count = first_data["totalCount"]
612
+ options[:up_to] = total_count if (options[:up_to] < 0 || options[:up_to] > total_count)
613
+ list = first_data_audios[0, options[:up_to]]
614
+ while list.length < options[:up_to] do
615
+ json = load__json__playlist_section(owner_id, playlist_id, access_hash,
616
+ offset: list.length
617
+ )
618
+ audios = audios__from_data(json["data"][0]["list"])
619
+ list.concat(audios[0, options[:up_to] - list.length])
620
+ end
621
+
622
+ Playlist.new(list, {
623
+ :id => first_data["id"],
624
+ :owner_id => first_data["owner_id"],
625
+ :access_hash => first_data["access_hash"],
626
+ :title => CGI.unescapeHTML(first_data["title"].to_s),
627
+ :subtitle => CGI.unescapeHTML(first_data["subtitle"].to_s),
628
+ })
629
+ end
630
+
631
+ # Load audios from wall using JSON request.
632
+ def wall__json(owner_id, post_id, options)
633
+ if options[:up_to] < 0 || options[:up_to] > 91
634
+ options[:up_to] = 91
635
+ Utility.warn("Current implementation of this method is not able to return more than 91 audios from wall.")
636
+ end
637
+
638
+ begin
639
+ json = load__json__audios_wall(owner_id, post_id)
640
+ data = json["data"][0]
641
+ audios = audios__from_data(data["list"])[0, options[:up_to]]
642
+ rescue Exception => error
643
+ raise Exceptions::ParseError, error.message, caller
644
+ end
645
+ options[:with_url] ? from_id(audios) : audios
646
+ end
647
+
648
+
358
649
  # Login
359
650
  def login(username, password)
651
+ Utility.debug("Logging in.")
360
652
  # Loading login page
361
- homepage = load_page(Constants::VK_URL[:home])
653
+ homepage = load__page(Constants::URL::VK[:home])
362
654
  # Submitting login form
363
- login_form = homepage.forms.find { |form| form.action.start_with?(Constants::VK_URL[:login_action]) }
655
+ login_form = homepage.forms.find { |form| form.action.start_with?(Constants::URL::VK[:login_action]) }
364
656
  login_form[Constants::VK_LOGIN_FORM_NAMES[:username]] = username.to_s
365
657
  login_form[Constants::VK_LOGIN_FORM_NAMES[:password]] = password.to_s
366
658
  after_login = @agent.submit(login_form)
367
659
 
368
660
  # Checking whether logged in
369
- raise Exceptions::LoginError, "unable to login. Redirected to #{after_login.uri.to_s}", caller unless after_login.uri.to_s == Constants::VK_URL[:feed]
661
+ raise Exceptions::LoginError, "Unable to login. Redirected to #{after_login.uri.to_s}", caller unless after_login.uri.to_s == Constants::URL::VK[:feed]
370
662
 
371
663
  # Parsing information about this profile
372
- profile = load_page(Constants::VK_URL[:profile])
373
- @name = profile.title
374
- @id = profile.link_with(href: Constants::VK_HREF_ID_CONTAINING_REGEX).href.slice(/\d+/)
664
+ profile = load__page(Constants::URL::VK[:profile])
665
+ @name = profile.title.to_s
666
+ @id = profile.link_with(href: Constants::Regex::VK_HREF_ID_CONTAINING).href.slice(/\d+/).to_i
375
667
  end
376
668
 
669
+ # Shortcut
377
670
  def unmask_link(link)
378
671
  VkMusic::LinkDecoder.unmask_link(link, @id)
379
672
  end