vk_music 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/vk_music/audio.rb +59 -4
- data/lib/vk_music/client.rb +104 -40
- data/lib/vk_music/constants.rb +41 -36
- data/lib/vk_music/exceptions.rb +26 -21
- data/lib/vk_music/link_decoder.rb +81 -68
- data/lib/vk_music/playlist.rb +45 -10
- data/lib/vk_music/utility.rb +29 -3
- data/vk_music.gemspec +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0c6e2d05ccf44bc32534cc66a85c2495f799e247876495df962bd312b3cef644
|
|
4
|
+
data.tar.gz: acc2fab3d0f196536f195cecea4f5bd0c997a6e14566a9a8a574b444db98bf4e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9f31a0049dc14a0ef0603f503733593a5bd8ffc2a26cae4ce4c6bb4ace98d175f86745ead97e2293d9157a2bf62b323c2f61254c01940b37505715c35454fdbc
|
|
7
|
+
data.tar.gz: 20fa21e9ee919f544664c20c149089ce594967bafa9809e7ab28ed68f53ab68d0147f5451fac66d83fe4453d1e3c388614fc543958f024d911ff24439cf94bb5
|
data/lib/vk_music/audio.rb
CHANGED
|
@@ -2,10 +2,38 @@ require "cgi"
|
|
|
2
2
|
|
|
3
3
|
module VkMusic
|
|
4
4
|
|
|
5
|
+
# VK audio.
|
|
5
6
|
class Audio
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
# Id of audio.
|
|
9
|
+
attr_reader :id
|
|
10
|
+
# Id of audio owner.
|
|
11
|
+
attr_reader :owner_id
|
|
12
|
+
# Parts of secret hash which used when using +act=reload_audio+
|
|
13
|
+
attr_reader :secret_1, :secret_2
|
|
14
|
+
# Artist.
|
|
15
|
+
attr_reader :artist
|
|
16
|
+
# Title.
|
|
17
|
+
attr_reader :title
|
|
18
|
+
# Duration.
|
|
19
|
+
attr_reader :duration
|
|
20
|
+
# Download URL.
|
|
21
|
+
attr_reader :url
|
|
22
|
+
# Encoded URL.
|
|
23
|
+
attr_reader :url_encoded
|
|
8
24
|
|
|
25
|
+
# Update audio URLs.
|
|
26
|
+
#
|
|
27
|
+
# If +:url+ is provided - just save it.
|
|
28
|
+
# If +:url_encoded+ and +:client_id+ provided - unmask link first.
|
|
29
|
+
#
|
|
30
|
+
# ===== Parameters:
|
|
31
|
+
# * [+options+] (+Hash+)
|
|
32
|
+
#
|
|
33
|
+
# ===== Options:
|
|
34
|
+
# * +:url+
|
|
35
|
+
# * +:url_encoded+
|
|
36
|
+
# * +:client_id+
|
|
9
37
|
def update_url(options)
|
|
10
38
|
raise ArgumentError, "options hash must be provided", caller unless options.class == Hash
|
|
11
39
|
if !options[:url].to_s.empty?
|
|
@@ -13,20 +41,37 @@ module VkMusic
|
|
|
13
41
|
@url = options[:url].to_s
|
|
14
42
|
elsif !options[:url].to_s.empty? && options[:client_id]
|
|
15
43
|
@url_encoded = options[:url_encoded].to_s
|
|
16
|
-
@url = VkMusic.unmask_link(options[:url_encoded], options[:client_id])
|
|
44
|
+
@url = VkMusic::LinkDecoder.unmask_link(options[:url_encoded], options[:client_id])
|
|
17
45
|
else
|
|
18
46
|
raise ArgumentError, "You should either provide :url or :url_encoded and :client_id", caller
|
|
19
47
|
end
|
|
20
48
|
end
|
|
21
49
|
|
|
50
|
+
# Returns string with information about audio.
|
|
22
51
|
def to_s
|
|
23
52
|
"#{@artist} - #{@title} [#{Utility.format_seconds(@duration)}]"
|
|
24
53
|
end
|
|
25
54
|
|
|
55
|
+
# Returns extended information about audio.
|
|
26
56
|
def pp
|
|
27
57
|
"#{to_s} (Got decoded URL: #{@url ? "yes" : "no"}, able to get URL from VK: #{@id && @owner_id && @secret_1 && @secret_2 ? "yes" : "no"})"
|
|
28
58
|
end
|
|
29
59
|
|
|
60
|
+
# Initialize new audio.
|
|
61
|
+
#
|
|
62
|
+
# ===== Parameters:
|
|
63
|
+
# * [+options+] (+Hash+)
|
|
64
|
+
#
|
|
65
|
+
# ===== Options:
|
|
66
|
+
# * +:id+
|
|
67
|
+
# * +:owner_id+
|
|
68
|
+
# * +:secret_1+
|
|
69
|
+
# * +:secret_2+
|
|
70
|
+
# * +:artist+
|
|
71
|
+
# * +:title+
|
|
72
|
+
# * +:duration+
|
|
73
|
+
# * +:url_encoded+
|
|
74
|
+
# * +:url+
|
|
30
75
|
def initialize(options)
|
|
31
76
|
# Arguments check
|
|
32
77
|
raise ArgumentError, "options hash must be provided", caller unless options.class == Hash
|
|
@@ -46,6 +91,11 @@ module VkMusic
|
|
|
46
91
|
@url = options[:url].to_s
|
|
47
92
|
end
|
|
48
93
|
|
|
94
|
+
# Initialize new audio from Nokogiri HTML node.
|
|
95
|
+
#
|
|
96
|
+
# ===== Parameters:
|
|
97
|
+
# * [+node+] (+Nokogiri::XML::Node+)
|
|
98
|
+
# * [+client_id+] (+Integer+)
|
|
49
99
|
def self.from_node(node, client_id)
|
|
50
100
|
url_encoded = node.at_css("input").attribute("value").to_s
|
|
51
101
|
url_encoded = nil if url_encoded == "https://m.vk.com/mp3/audio_api_unavailable.mp3"
|
|
@@ -58,10 +108,15 @@ module VkMusic
|
|
|
58
108
|
:title => node.at_css(".ai_title").text.strip,
|
|
59
109
|
:duration => node.at_css(".ai_dur").attribute("data-dur").to_s.to_i,
|
|
60
110
|
:url_encoded => url_encoded,
|
|
61
|
-
:url => url_encoded ? VkMusic.unmask_link(url_encoded, client_id) : nil,
|
|
111
|
+
:url => url_encoded ? VkMusic::LinkDecoder.unmask_link(url_encoded, client_id) : nil,
|
|
62
112
|
})
|
|
63
113
|
end
|
|
64
114
|
|
|
115
|
+
# Initialize new audio from data array.
|
|
116
|
+
#
|
|
117
|
+
# ===== Parameters:
|
|
118
|
+
# * [+data+] (+Array+)
|
|
119
|
+
# * [+client_id+] (+Integer+)
|
|
65
120
|
def self.from_data_array(data, client_id)
|
|
66
121
|
url_encoded = data[2]
|
|
67
122
|
url_encoded = nil if url_encoded == ""
|
|
@@ -77,7 +132,7 @@ module VkMusic
|
|
|
77
132
|
:title => CGI.unescapeHTML(data[3]),
|
|
78
133
|
:duration => data[5],
|
|
79
134
|
:url_encoded => url_encoded,
|
|
80
|
-
:url => url_encoded ? VkMusic.unmask_link(url_encoded, client_id) : nil,
|
|
135
|
+
:url => url_encoded ? VkMusic::LinkDecoder.unmask_link(url_encoded, client_id) : nil,
|
|
81
136
|
})
|
|
82
137
|
end
|
|
83
138
|
|
data/lib/vk_music/client.rb
CHANGED
|
@@ -3,9 +3,13 @@ require "json"
|
|
|
3
3
|
|
|
4
4
|
module VkMusic
|
|
5
5
|
|
|
6
|
+
# Main class with all the interface.
|
|
6
7
|
class Client
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
# ID of user
|
|
10
|
+
attr_reader :id
|
|
11
|
+
# Name of user
|
|
12
|
+
attr_reader :name
|
|
9
13
|
|
|
10
14
|
# Mechanize agent
|
|
11
15
|
@agent = nil
|
|
@@ -21,16 +25,31 @@ module VkMusic
|
|
|
21
25
|
login(options[:username], options[:password])
|
|
22
26
|
end
|
|
23
27
|
|
|
28
|
+
# Find Audio.
|
|
29
|
+
#
|
|
30
|
+
# ===== Parameters:
|
|
31
|
+
# * [+query+] (+String+) - string to search for.
|
|
32
|
+
#
|
|
33
|
+
# ===== Returns:
|
|
34
|
+
# * (+Array+) - array of Audio.
|
|
24
35
|
def find_audio(query)
|
|
25
|
-
uri = URI(VK_URL[:audios])
|
|
36
|
+
uri = URI(Constants::VK_URL[:audios])
|
|
26
37
|
uri.query = Utility.hash_to_params({ "act" => "search", "q" => query.to_s })
|
|
27
38
|
load_audios_from_page(uri)
|
|
28
39
|
end
|
|
29
40
|
|
|
41
|
+
# Get Playlist.
|
|
42
|
+
#
|
|
43
|
+
# ===== Parameters:
|
|
44
|
+
# * [+url+] (+String+) - url to playlist.
|
|
45
|
+
# * [+up_to+] (+Integer+) - maximum amount of Audio to load.
|
|
46
|
+
#
|
|
47
|
+
# ===== Returns:
|
|
48
|
+
# * (+Playlist+)
|
|
30
49
|
def get_playlist(url, up_to = nil)
|
|
31
50
|
# NOTICE: it is possible to use same type of requests as in get_audios method
|
|
32
51
|
begin
|
|
33
|
-
url, owner_id, id, access_hash = url.to_s.match(PLAYLIST_URL_REGEX).to_a
|
|
52
|
+
url, owner_id, id, access_hash = url.to_s.match(Constants::PLAYLIST_URL_REGEX).to_a
|
|
34
53
|
|
|
35
54
|
# Load first page and get info
|
|
36
55
|
first_page = load_playlist_page(owner_id: owner_id, id: id, access_hash: access_hash, offset: 0)
|
|
@@ -47,7 +66,7 @@ module VkMusic
|
|
|
47
66
|
playlist_size = 0
|
|
48
67
|
end
|
|
49
68
|
rescue Exception => error
|
|
50
|
-
raise PlaylistParseError, "unable to parse playlist page. Error: #{error.message}", caller
|
|
69
|
+
raise Exceptions::PlaylistParseError, "unable to parse playlist page. Error: #{error.message}", caller
|
|
51
70
|
end
|
|
52
71
|
# Now we can be sure we are on correct page
|
|
53
72
|
|
|
@@ -75,7 +94,15 @@ module VkMusic
|
|
|
75
94
|
})
|
|
76
95
|
end
|
|
77
96
|
|
|
78
|
-
|
|
97
|
+
# Get user or group audios.
|
|
98
|
+
#
|
|
99
|
+
# ===== Parameters:
|
|
100
|
+
# * [+url+] (+String+) - URL to user or group.
|
|
101
|
+
# * [+up_to+] (+Integer+) - maximum amount of Audio to load.
|
|
102
|
+
#
|
|
103
|
+
# ===== Returns:
|
|
104
|
+
# * (+Playlist+)
|
|
105
|
+
def get_audios(url, up_to = nil)
|
|
79
106
|
if up_to && up_to > 100
|
|
80
107
|
Utility.warn("Current implementation of method VkMusic::Client#get_audios is only able to load first 100 audios from user page.")
|
|
81
108
|
end
|
|
@@ -84,7 +111,7 @@ module VkMusic
|
|
|
84
111
|
# NOTICE: it is possible to load up to 2000 audios **without url** if offset is negative
|
|
85
112
|
|
|
86
113
|
# Firstly, we need to get numeric id
|
|
87
|
-
id = get_id(
|
|
114
|
+
id = get_id(url.to_s)
|
|
88
115
|
|
|
89
116
|
# Trying to parse out audios
|
|
90
117
|
begin
|
|
@@ -92,7 +119,7 @@ module VkMusic
|
|
|
92
119
|
first_data = first_json["data"][0]
|
|
93
120
|
first_data_audios = load_audios_from_data(first_data["list"])
|
|
94
121
|
rescue Exception => error
|
|
95
|
-
raise AudiosSectionParseError, "unable to load or parse audios section: #{error.message}", caller
|
|
122
|
+
raise Exceptions::AudiosSectionParseError, "unable to load or parse audios section: #{error.message}", caller
|
|
96
123
|
end
|
|
97
124
|
|
|
98
125
|
#total_count = first_data["totalCount"] # NOTICE: not used due to restrictions described above
|
|
@@ -113,6 +140,13 @@ module VkMusic
|
|
|
113
140
|
})
|
|
114
141
|
end
|
|
115
142
|
|
|
143
|
+
# Get audios by their ids and secrets.
|
|
144
|
+
#
|
|
145
|
+
# ===== Parameters:
|
|
146
|
+
# * [+arr+] (+Array+) - Array of objects, which can have different types: Audio or Array[owner_id, id, secret_1, secret_2].
|
|
147
|
+
#
|
|
148
|
+
# ===== Returns:
|
|
149
|
+
# * (+Array+) - array of audios with decoded URLs.
|
|
116
150
|
def get_audios_by_id(*arr)
|
|
117
151
|
if arr.size > 10
|
|
118
152
|
Utility.warn("Current implementation of method VkMusic::Client#get_audios_by_id is only able to handle first 10 audios.")
|
|
@@ -131,18 +165,27 @@ module VkMusic
|
|
|
131
165
|
end
|
|
132
166
|
json = load_audios_json_by_id(arr)
|
|
133
167
|
result = load_audios_from_data(json["data"][0].to_a)
|
|
134
|
-
raise ReloadAudiosParseError, "Result size don't match: excepected #{arr.size}, got #{result.size}", caller if result.size != arr.size
|
|
168
|
+
raise Exceptions::ReloadAudiosParseError, "Result size don't match: excepected #{arr.size}, got #{result.size}", caller if result.size != arr.size
|
|
135
169
|
|
|
136
170
|
result
|
|
137
171
|
end
|
|
138
172
|
|
|
173
|
+
# Get audios on wall of user or group starting with given post.
|
|
174
|
+
#
|
|
175
|
+
# ===== Parameters:
|
|
176
|
+
# * [+owner_id+] (+Integer+)
|
|
177
|
+
# * [+post_id+] (+Integer+)
|
|
178
|
+
# * [+up_to+] (+Integer+) - maximum amount of Audio to load.
|
|
179
|
+
#
|
|
180
|
+
# ===== Returns:
|
|
181
|
+
# * (+Array+) - array of audios with URLs.
|
|
139
182
|
def get_audios_from_wall(owner_id, post_id, up_to = nil)
|
|
140
183
|
begin
|
|
141
184
|
json = load_audios_json_from_wall(owner_id, post_id)
|
|
142
185
|
data = json["data"][0]
|
|
143
186
|
no_url_audios = load_audios_from_data(data["list"])
|
|
144
187
|
rescue Exception => error
|
|
145
|
-
raise WallParseError, "Failed to parse wall from #{@owner_id}_#{post_id}. Error: #{error.message}", caller
|
|
188
|
+
raise Exceptions::WallParseError, "Failed to parse wall from #{@owner_id}_#{post_id}. Error: #{error.message}", caller
|
|
146
189
|
end
|
|
147
190
|
|
|
148
191
|
up_to = no_url_audios.size if (up_to.nil? || up_to < 0 || up_to > no_url_audios.size)
|
|
@@ -159,54 +202,75 @@ module VkMusic
|
|
|
159
202
|
})
|
|
160
203
|
end
|
|
161
204
|
|
|
205
|
+
# Get audios attached to post.
|
|
206
|
+
#
|
|
207
|
+
# ===== Parameters:
|
|
208
|
+
# * [+url+] (+String+)
|
|
209
|
+
#
|
|
210
|
+
# ===== Returns:
|
|
211
|
+
# * (+Array+) - array of audios with URLs.
|
|
162
212
|
def get_audios_from_post(url)
|
|
163
|
-
url, owner_id, post_id = url.match(POST_URL_REGEX).to_a
|
|
213
|
+
url, owner_id, post_id = url.match(Constants::POST_URL_REGEX).to_a
|
|
164
214
|
|
|
165
215
|
amount = get_amount_of_audios_in_post(owner_id, post_id)
|
|
166
216
|
get_audios_from_wall(owner_id, post_id, amount).to_a
|
|
167
217
|
end
|
|
168
218
|
|
|
169
|
-
|
|
219
|
+
# Get user or group id.
|
|
220
|
+
#
|
|
221
|
+
# ===== Parameters:
|
|
222
|
+
# * [+str+] (+String+) - link, id with prefix or custom id.
|
|
223
|
+
#
|
|
224
|
+
# ===== Returns:
|
|
225
|
+
# * (+Integer+)
|
|
170
226
|
def get_id(str)
|
|
171
227
|
case str
|
|
172
|
-
when VK_URL_REGEX
|
|
173
|
-
path = str.match(VK_URL_REGEX)[1]
|
|
228
|
+
when Constants::VK_URL_REGEX
|
|
229
|
+
path = str.match(Constants::VK_URL_REGEX)[1]
|
|
174
230
|
get_id(path) # Recursive call
|
|
175
|
-
when VK_ID_REGEX
|
|
231
|
+
when Constants::VK_ID_REGEX
|
|
176
232
|
str
|
|
177
|
-
when VK_AUDIOS_REGEX
|
|
233
|
+
when Constants::VK_AUDIOS_REGEX
|
|
178
234
|
str.match(/-?\d+/).to_s # Numbers with sigh
|
|
179
|
-
when VK_PREFIXED_ID_REGEX
|
|
235
|
+
when Constants::VK_PREFIXED_ID_REGEX
|
|
180
236
|
id = str.match(/\d+/).to_s # Just numbers. Sign needed
|
|
181
237
|
id = "-#{id}" unless str.start_with?("id")
|
|
182
238
|
id
|
|
183
|
-
when VK_CUSTOM_ID_REGEX
|
|
239
|
+
when Constants::VK_CUSTOM_ID_REGEX
|
|
184
240
|
begin
|
|
185
|
-
page = load_page("#{VK_URL[:home]}/#{str}")
|
|
241
|
+
page = load_page("#{Constants::VK_URL[:home]}/#{str}")
|
|
186
242
|
rescue Exception => error
|
|
187
|
-
raise IdParseError, "unable to load page by id \"#{str}\". Error: #{error.message}"
|
|
243
|
+
raise Exceptions::IdParseError, "unable to load page by id \"#{str}\". Error: #{error.message}"
|
|
188
244
|
end
|
|
189
245
|
|
|
190
246
|
unless page.at_css(".PageBlock .owner_panel")
|
|
191
247
|
# Ensure this isn't some random vk page
|
|
192
|
-
raise IdParseError, "page #{str} doesn't seem to be a group or user page"
|
|
248
|
+
raise Exceptions::IdParseError, "page #{str} doesn't seem to be a group or user page"
|
|
193
249
|
end
|
|
194
250
|
|
|
195
|
-
id = page.link_with(href: VK_HREF_ID_CONTAINING_REGEX).href.slice(/-?\d+/) # Numbers with sign
|
|
251
|
+
id = page.link_with(href: Constants::VK_HREF_ID_CONTAINING_REGEX).href.slice(/-?\d+/) # Numbers with sign
|
|
196
252
|
id
|
|
197
253
|
else
|
|
198
|
-
raise IdParseError, "unable to convert \"#{str}\" into id"
|
|
254
|
+
raise Exceptions::IdParseError, "unable to convert \"#{str}\" into id"
|
|
199
255
|
end
|
|
200
256
|
end
|
|
201
257
|
|
|
258
|
+
# Get amount of audios attached to specified post.
|
|
259
|
+
#
|
|
260
|
+
# ===== Parameters:
|
|
261
|
+
# * [+owner_id+] (+Integer+)
|
|
262
|
+
# * [+post_id+] (+Integer+)
|
|
263
|
+
#
|
|
264
|
+
# ===== Returns:
|
|
265
|
+
# * (+Integer+)
|
|
202
266
|
def get_amount_of_audios_in_post(owner_id, post_id)
|
|
203
267
|
begin
|
|
204
|
-
page = load_page("#{VK_URL[:wall]}#{owner_id}_#{post_id}")
|
|
268
|
+
page = load_page("#{Constants::VK_URL[:wall]}#{owner_id}_#{post_id}")
|
|
205
269
|
result = page.css(".wi_body > .pi_medias .medias_audio").size
|
|
206
270
|
rescue Exception => error
|
|
207
|
-
raise PostParseError, "Unable to get amount of audios in post #{owner_id}_#{post_id}. Error: #{error.message}", caller
|
|
271
|
+
raise Exceptions::PostParseError, "Unable to get amount of audios in post #{owner_id}_#{post_id}. Error: #{error.message}", caller
|
|
208
272
|
end
|
|
209
|
-
raise PostParseError, "Post not found: #{owner_id}_#{post_id}", caller if result == 0 && !page.css(".service_msg_error").empty?
|
|
273
|
+
raise Exceptions::PostParseError, "Post not found: #{owner_id}_#{post_id}", caller if result == 0 && !page.css(".service_msg_error").empty?
|
|
210
274
|
result
|
|
211
275
|
end
|
|
212
276
|
|
|
@@ -224,7 +288,7 @@ module VkMusic
|
|
|
224
288
|
end
|
|
225
289
|
|
|
226
290
|
def load_playlist_page(options)
|
|
227
|
-
uri = URI(VK_URL[:audios])
|
|
291
|
+
uri = URI(Constants::VK_URL[:audios])
|
|
228
292
|
uri.query = Utility.hash_to_params({
|
|
229
293
|
"act" => "audio_playlist#{options[:owner_id]}_#{options[:id]}",
|
|
230
294
|
"access_hash" => options[:access_hash].to_s,
|
|
@@ -233,7 +297,7 @@ module VkMusic
|
|
|
233
297
|
load_page(uri)
|
|
234
298
|
end
|
|
235
299
|
def load_playlist_json_section(owner_id, playlist_id, offset = 0)
|
|
236
|
-
uri = URI(VK_URL[:audios])
|
|
300
|
+
uri = URI(Constants::VK_URL[:audios])
|
|
237
301
|
uri.query = Utility.hash_to_params({
|
|
238
302
|
"act" => "load_section",
|
|
239
303
|
"owner_id" => owner_id,
|
|
@@ -245,12 +309,12 @@ module VkMusic
|
|
|
245
309
|
begin
|
|
246
310
|
load_json(uri)
|
|
247
311
|
rescue Exception => error
|
|
248
|
-
raise AudiosSectionParseError, "unable to load or parse audios section: #{error.message}", caller
|
|
312
|
+
raise Exceptions::AudiosSectionParseError, "unable to load or parse audios section: #{error.message}", caller
|
|
249
313
|
end
|
|
250
314
|
end
|
|
251
315
|
|
|
252
316
|
def load_audios_json_by_id(ids)
|
|
253
|
-
uri = URI(VK_URL[:audios])
|
|
317
|
+
uri = URI(Constants::VK_URL[:audios])
|
|
254
318
|
uri.query = Utility.hash_to_params({
|
|
255
319
|
"act" => "reload_audio",
|
|
256
320
|
"ids" => ids,
|
|
@@ -259,12 +323,12 @@ module VkMusic
|
|
|
259
323
|
begin
|
|
260
324
|
load_json(uri)
|
|
261
325
|
rescue Exception => error
|
|
262
|
-
raise AudiosSectionParseError, "unable to load or parse audios section: #{error.message}", caller
|
|
326
|
+
raise Exceptions::AudiosSectionParseError, "unable to load or parse audios section: #{error.message}", caller
|
|
263
327
|
end
|
|
264
328
|
end
|
|
265
329
|
|
|
266
330
|
def load_audios_json_from_wall(owner_id, post_id)
|
|
267
|
-
uri = URI(VK_URL[:audios])
|
|
331
|
+
uri = URI(Constants::VK_URL[:audios])
|
|
268
332
|
uri.query = Utility.hash_to_params({
|
|
269
333
|
"act" => "load_section",
|
|
270
334
|
"owner_id" => owner_id,
|
|
@@ -276,7 +340,7 @@ module VkMusic
|
|
|
276
340
|
begin
|
|
277
341
|
load_json(uri)
|
|
278
342
|
rescue Exception => error
|
|
279
|
-
raise AudiosSectionParseError, "unable to load or parse audios section: #{error.message}", caller
|
|
343
|
+
raise Exceptions::AudiosSectionParseError, "unable to load or parse audios section: #{error.message}", caller
|
|
280
344
|
end
|
|
281
345
|
end
|
|
282
346
|
|
|
@@ -294,24 +358,24 @@ module VkMusic
|
|
|
294
358
|
# Login
|
|
295
359
|
def login(username, password)
|
|
296
360
|
# Loading login page
|
|
297
|
-
homepage = load_page(VK_URL[:home])
|
|
361
|
+
homepage = load_page(Constants::VK_URL[:home])
|
|
298
362
|
# Submitting login form
|
|
299
|
-
login_form = homepage.forms.find { |form| form.action.start_with?(VK_URL[:login_action]) }
|
|
300
|
-
login_form[VK_LOGIN_FORM_NAMES[:username]] = username.to_s
|
|
301
|
-
login_form[VK_LOGIN_FORM_NAMES[:password]] = password.to_s
|
|
363
|
+
login_form = homepage.forms.find { |form| form.action.start_with?(Constants::VK_URL[:login_action]) }
|
|
364
|
+
login_form[Constants::VK_LOGIN_FORM_NAMES[:username]] = username.to_s
|
|
365
|
+
login_form[Constants::VK_LOGIN_FORM_NAMES[:password]] = password.to_s
|
|
302
366
|
after_login = @agent.submit(login_form)
|
|
303
367
|
|
|
304
368
|
# Checking whether logged in
|
|
305
|
-
raise LoginError, "unable to login. Redirected to #{after_login.uri.to_s}", caller unless after_login.uri.to_s == VK_URL[:feed]
|
|
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]
|
|
306
370
|
|
|
307
371
|
# Parsing information about this profile
|
|
308
|
-
profile = load_page(VK_URL[:profile])
|
|
372
|
+
profile = load_page(Constants::VK_URL[:profile])
|
|
309
373
|
@name = profile.title
|
|
310
|
-
@id = profile.link_with(href: VK_HREF_ID_CONTAINING_REGEX).href.slice(/\d+/)
|
|
374
|
+
@id = profile.link_with(href: Constants::VK_HREF_ID_CONTAINING_REGEX).href.slice(/\d+/)
|
|
311
375
|
end
|
|
312
376
|
|
|
313
377
|
def unmask_link(link)
|
|
314
|
-
VkMusic.unmask_link(link, @id)
|
|
378
|
+
VkMusic::LinkDecoder.unmask_link(link, @id)
|
|
315
379
|
end
|
|
316
380
|
|
|
317
381
|
end
|
data/lib/vk_music/constants.rb
CHANGED
|
@@ -1,41 +1,46 @@
|
|
|
1
1
|
module VkMusic
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
|
|
3
|
+
# Constants
|
|
4
|
+
module Constants
|
|
5
|
+
# Web
|
|
6
|
+
# DEFAULT_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1636.0 Safari/537.36"
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
8
|
+
VK_URL = {
|
|
9
|
+
:scheme => "https",
|
|
10
|
+
:host => "m.vk.com",
|
|
11
|
+
:home => "https://m.vk.com",
|
|
12
|
+
:profile => "https://m.vk.com/id0",
|
|
13
|
+
:feed => "https://m.vk.com/feed",
|
|
14
|
+
:audios => "https://m.vk.com/audio",
|
|
15
|
+
:login => "https://m.vk.com/login",
|
|
16
|
+
:login_action => "https://login.vk.com",
|
|
17
|
+
:wall => "https://m.vk.com/wall"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
VK_LOGIN_FORM_NAMES = {
|
|
21
|
+
:username => "email",
|
|
22
|
+
:password => "pass",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
VK_ID_REGEX = /^-?\d+$/
|
|
27
|
+
VK_AUDIOS_REGEX = /^audios-?\d+$/
|
|
28
|
+
VK_PREFIXED_ID_REGEX = /^(?:id|club|group|public|event)\d+$/ # TODO: Rework. This one is REALLY dirty. Not quite sure every page can return correct id with this regex
|
|
29
|
+
VK_CUSTOM_ID_REGEX = /^\w+$/
|
|
30
|
+
VK_URL_REGEX = /(?:https?:\/\/)?(?:m\.|www\.)?vk\.com\/([\w\-]+)/
|
|
31
|
+
|
|
32
|
+
VK_HREF_ID_CONTAINING_REGEX = /(?:audios|photo|write|owner_id=|friends\?id=)-?\d+/
|
|
33
|
+
|
|
34
|
+
# Playlist
|
|
35
|
+
PLAYLIST_URL_REGEX = /.*audio_playlist(-?\d+)_(\d+)(?:(?:(?:&access_hash=)|\/|%2F)([\da-z]+))?/
|
|
36
|
+
|
|
37
|
+
# Post
|
|
38
|
+
POST_URL_REGEX = /.*wall(-?\d+)_(\d+)/
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# QUESTION: Should I move ALL the constants (string, regex etc) here? It would make code more flexible, but seems like overkill
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
include Constants
|
|
34
45
|
|
|
35
|
-
# Post
|
|
36
|
-
POST_URL_REGEX = /.*wall(-?\d+)_(\d+)/
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
# QUESTION: Should I move ALL the constants (string, regex etc) here? It would make code more flexible, but seems like overkill
|
|
40
|
-
|
|
41
46
|
end
|
data/lib/vk_music/exceptions.rb
CHANGED
|
@@ -1,30 +1,35 @@
|
|
|
1
1
|
module VkMusic
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
|
|
3
|
+
# Exceptions.
|
|
4
|
+
module Exceptions
|
|
5
|
+
# General class for all the errors.
|
|
6
|
+
class VkMusicError < RuntimeError; end
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
# Unable to find playlist or got permission error
|
|
13
|
-
class PlaylistParseError < AudiosParseError; end
|
|
8
|
+
# Failed to login.
|
|
9
|
+
class LoginError < VkMusicError; end
|
|
10
|
+
|
|
11
|
+
# Unable to parse audios from somewhere.
|
|
12
|
+
class AudiosParseError < VkMusicError; end
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
# Unable to find playlist or got permission error.
|
|
15
|
+
class PlaylistParseError < AudiosParseError; end
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
# Unable to convert string to id
|
|
22
|
-
class IdParseError < AudiosParseError; end
|
|
17
|
+
# Unable to load or parse audios section from json.
|
|
18
|
+
class AudiosSectionParseError < AudiosParseError; end
|
|
23
19
|
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
# Unable to load or parse all of audios by ids.
|
|
21
|
+
class ReloadAudiosParseError < AudiosParseError; end
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
# Unable to convert string to id.
|
|
24
|
+
class IdParseError < AudiosParseError; end
|
|
25
|
+
|
|
26
|
+
# Unable to parse audios from wall.
|
|
27
|
+
class WallParseError < AudiosParseError; end
|
|
28
|
+
|
|
29
|
+
# Unable to parse audios from post.
|
|
30
|
+
class PostParseError < AudiosParseError; end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
include Exceptions
|
|
29
34
|
|
|
30
35
|
end
|
|
@@ -2,92 +2,105 @@ require "execjs"
|
|
|
2
2
|
|
|
3
3
|
module VkMusic
|
|
4
4
|
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
function vk_unmask_link(link, vk_id) {
|
|
5
|
+
# Module containing link decoding utilities.
|
|
6
|
+
module LinkDecoder
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
# JS code which creates function to unmask audio URL.
|
|
9
|
+
js_code = <<~HEREDOC
|
|
10
|
+
function vk_unmask_link(link, vk_id) {
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
if (encrypted.indexOf('audio_api_unavailable') != -1) {
|
|
13
|
-
var parts = encrypted.split('?extra=')[1].split('#');
|
|
12
|
+
// Utility functions to unmask
|
|
14
13
|
|
|
15
|
-
|
|
14
|
+
var audioUnmaskSource = function (encrypted) {
|
|
15
|
+
if (encrypted.indexOf('audio_api_unavailable') != -1) {
|
|
16
|
+
var parts = encrypted.split('?extra=')[1].split('#');
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
var handled_anchor = '' === parts[1] ? '' : handler(parts[1]);
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
// if (typeof handled_anchor != 'string') console.warn('Handled_anchor type: ' + typeof handled_anchor);
|
|
21
|
-
// if (!handled_part) console.warn('Handled_part: ' + handled_part);
|
|
22
|
-
return encrypted;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
handled_anchor = handled_anchor ? handled_anchor.split(String.fromCharCode(9)) : [];
|
|
20
|
+
var handled_part = handler(parts[0]);
|
|
26
21
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (!utility_object[func_key]) {
|
|
31
|
-
// console.warn('Was unable to find key: ' + func_key);
|
|
22
|
+
if (typeof handled_anchor != 'string' || !handled_part) {
|
|
23
|
+
// if (typeof handled_anchor != 'string') console.warn('Handled_anchor type: ' + typeof handled_anchor);
|
|
24
|
+
// if (!handled_part) console.warn('Handled_part: ' + handled_part);
|
|
32
25
|
return encrypted;
|
|
33
26
|
}
|
|
34
|
-
handled_part = utility_object[func_key].apply(null, splited_anchor)
|
|
35
|
-
}
|
|
36
27
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
28
|
+
handled_anchor = handled_anchor ? handled_anchor.split(String.fromCharCode(9)) : [];
|
|
29
|
+
|
|
30
|
+
for (var func_key, splited_anchor, l = handled_anchor.length; l--;) {
|
|
31
|
+
splited_anchor = handled_anchor[l].split(String.fromCharCode(11));
|
|
32
|
+
func_key = splited_anchor.splice(0, 1, handled_part)[0];
|
|
33
|
+
if (!utility_object[func_key]) {
|
|
34
|
+
// console.warn('Was unable to find key: ' + func_key);
|
|
35
|
+
return encrypted;
|
|
36
|
+
}
|
|
37
|
+
handled_part = utility_object[func_key].apply(null, splited_anchor)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (handled_part && 'http' === handled_part.substr(0, 4)) return handled_part;
|
|
41
|
+
// else console.warn('Failed unmasking: ' + handled_part);
|
|
42
|
+
} else {
|
|
43
|
+
// console.warn('Bad link: ' + encrypted);
|
|
44
|
+
}
|
|
45
|
+
return encrypted;
|
|
41
46
|
}
|
|
42
|
-
return encrypted;
|
|
43
|
-
}
|
|
44
47
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
var handler = function (part) {
|
|
49
|
+
if (!part || part.length % 4 == 1) return !1;
|
|
50
|
+
for (var t, i, o = 0, s = 0, a = ''; i = part.charAt(s++);) {
|
|
51
|
+
i = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN0PQRSTUVWXYZO123456789+/='.indexOf(i)
|
|
52
|
+
~i && (t = o % 4 ? 64 * t + i : i, o++ % 4) && (a += String.fromCharCode(255 & t >> (-2 * o & 6)));
|
|
53
|
+
}
|
|
54
|
+
return a;
|
|
50
55
|
}
|
|
51
|
-
return a;
|
|
52
|
-
}
|
|
53
56
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
var utility_object = {
|
|
58
|
+
i: function(e, t) {
|
|
59
|
+
return utility_object.s(e, t ^ vk_id);
|
|
60
|
+
},
|
|
61
|
+
s: function(e, t) {
|
|
62
|
+
var n = e.length;
|
|
63
|
+
if (n) {
|
|
64
|
+
var i = r_func(e, t),
|
|
65
|
+
o = 0;
|
|
66
|
+
for (e = e.split(''); ++o < n;)
|
|
67
|
+
e[o] = e.splice(i[n - 1 - o], 1, e[o])[0];
|
|
68
|
+
e = e.join('')
|
|
69
|
+
}
|
|
70
|
+
return e;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
var r_func = function (e, t) {
|
|
75
|
+
var n = e.length,
|
|
76
|
+
i = [];
|
|
60
77
|
if (n) {
|
|
61
|
-
var
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
e = e.join('')
|
|
78
|
+
var o = n;
|
|
79
|
+
for (t = Math.abs(t); o--;)
|
|
80
|
+
t = (n * (o + 1) ^ t + o) % n,
|
|
81
|
+
i[o] = t;
|
|
66
82
|
}
|
|
67
|
-
return
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
var r_func = function (e, t) {
|
|
72
|
-
var n = e.length,
|
|
73
|
-
i = [];
|
|
74
|
-
if (n) {
|
|
75
|
-
var o = n;
|
|
76
|
-
for (t = Math.abs(t); o--;)
|
|
77
|
-
t = (n * (o + 1) ^ t + o) % n,
|
|
78
|
-
i[o] = t;
|
|
83
|
+
return i;
|
|
79
84
|
}
|
|
80
|
-
|
|
85
|
+
|
|
86
|
+
return audioUnmaskSource(link);
|
|
81
87
|
}
|
|
88
|
+
HEREDOC
|
|
82
89
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
90
|
+
@@js_context = ExecJS.compile(js_code)
|
|
91
|
+
|
|
92
|
+
# Unmask audio download URL.
|
|
93
|
+
#
|
|
94
|
+
# ===== Parameters:
|
|
95
|
+
# * [+link+] (+String+) - encoded link to audio. Usually looks like "https://m.vk.com/mp3/audio_api_unavailable.mp3?extra=...".
|
|
96
|
+
# * [+client_id+] (+Integer+) - ID of user which got this link. ID is required for decoding.
|
|
97
|
+
#
|
|
98
|
+
# ===== Returns:
|
|
99
|
+
# * (+String+) - audio download URL, which can be used from current IP.
|
|
100
|
+
def self.unmask_link(link, client_id)
|
|
101
|
+
@@js_context.call("vk_unmask_link", link.to_s, client_id.to_i)
|
|
102
|
+
end
|
|
86
103
|
|
|
87
|
-
@@js_context = ExecJS.compile(js_code)
|
|
88
|
-
|
|
89
|
-
def self.unmask_link(link, client_id)
|
|
90
|
-
@@js_context.call("vk_unmask_link", link.to_s, client_id.to_i)
|
|
91
104
|
end
|
|
92
105
|
|
|
93
|
-
end
|
|
106
|
+
end
|
data/lib/vk_music/playlist.rb
CHANGED
|
@@ -1,39 +1,74 @@
|
|
|
1
1
|
module VkMusic
|
|
2
2
|
|
|
3
|
+
# VK playlist. Extended with Enumerable.
|
|
3
4
|
class Playlist
|
|
4
5
|
include Enumerable
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
# Playlist id.
|
|
8
|
+
attr_reader :id
|
|
9
|
+
# Owner of playlist.
|
|
10
|
+
attr_reader :owner_id
|
|
11
|
+
# Access hash which should be part of link for some playlists.
|
|
12
|
+
attr_reader :access_hash
|
|
13
|
+
# Playlist title.
|
|
14
|
+
attr_reader :title
|
|
15
|
+
# Playlist subtitle. May be empty.
|
|
16
|
+
attr_reader :subtitle
|
|
12
17
|
|
|
18
|
+
# Return string describing playlist in Russian.
|
|
13
19
|
def to_s
|
|
14
20
|
(@subtitle.empty? ? "" : "#{@subtitle} - ") + "#{@title} (#{self.length} аудиозаписей)"
|
|
15
21
|
end
|
|
16
22
|
|
|
23
|
+
# Same to +to_s+, but also outputs list of audios.
|
|
17
24
|
def pp
|
|
18
25
|
"#{to_s}:\n#{@list.map(&:to_s).join("\n")}"
|
|
19
26
|
end
|
|
20
27
|
|
|
28
|
+
# Returns audios array.
|
|
21
29
|
def to_a
|
|
22
30
|
@list.dup
|
|
23
31
|
end
|
|
24
32
|
|
|
33
|
+
# :nodoc:
|
|
25
34
|
def each(&block)
|
|
26
35
|
@list.each(&block)
|
|
27
36
|
end
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
37
|
+
|
|
38
|
+
# :stopdoc:
|
|
39
|
+
def length
|
|
40
|
+
@list.length
|
|
31
41
|
end
|
|
32
|
-
|
|
42
|
+
alias size length
|
|
43
|
+
|
|
33
44
|
def empty?
|
|
34
45
|
@list.empty?
|
|
35
46
|
end
|
|
47
|
+
# :startdoc:
|
|
48
|
+
|
|
49
|
+
# Access to audios from playlist.
|
|
50
|
+
#
|
|
51
|
+
# ===== Parameters:
|
|
52
|
+
# * [+index+] (+Integer+) - index of audio (starting from 0).
|
|
53
|
+
#
|
|
54
|
+
# ===== Returns:
|
|
55
|
+
# * (+Audio+, +nil+) - audio or +nil+ if out of range.
|
|
56
|
+
def [](index)
|
|
57
|
+
@list[index]
|
|
58
|
+
end
|
|
36
59
|
|
|
60
|
+
# Initialize new playlist.
|
|
61
|
+
#
|
|
62
|
+
# ===== Parameters:
|
|
63
|
+
# * [+list+] (+Array+) - list of audios in album.
|
|
64
|
+
# * [+options+] (+Hash+)
|
|
65
|
+
#
|
|
66
|
+
# ===== Options:
|
|
67
|
+
# * [+:id+]
|
|
68
|
+
# * [+:owner_id+]
|
|
69
|
+
# * [+:access_hash+]
|
|
70
|
+
# * [+:title+]
|
|
71
|
+
# * [+:subtitle+]
|
|
37
72
|
def initialize(list, options = {})
|
|
38
73
|
# Arguments check
|
|
39
74
|
raise ArgumentError, "array of audios must be provided", caller unless list.class == Array
|
data/lib/vk_music/utility.rb
CHANGED
|
@@ -2,27 +2,52 @@ require "cgi"
|
|
|
2
2
|
|
|
3
3
|
module VkMusic
|
|
4
4
|
|
|
5
|
+
# Utility methods.
|
|
5
6
|
module Utility
|
|
6
7
|
|
|
8
|
+
# Turn amount of seconds into string.
|
|
9
|
+
#
|
|
10
|
+
# ===== Parameters:
|
|
11
|
+
# * [+s+] (+Integer+) - amount of seconds.
|
|
12
|
+
#
|
|
13
|
+
# ===== Returns:
|
|
14
|
+
# * (+String+) - formatted string.
|
|
7
15
|
def self.format_seconds(s)
|
|
8
16
|
s = s.to_i # Require integer
|
|
9
17
|
"#{(s / 60).to_s.rjust(2, "0")}:#{(s % 60).to_s.rjust(2, "0")}";
|
|
10
18
|
end
|
|
11
19
|
|
|
20
|
+
# Guess type of request by from string.
|
|
21
|
+
#
|
|
22
|
+
# ===== Parameters:
|
|
23
|
+
# * [+str+] (+String+) - request from user for some audios.
|
|
24
|
+
#
|
|
25
|
+
# ===== Returns:
|
|
26
|
+
# * +:playlist+ - if string match playlist URL.
|
|
27
|
+
# * +:post+ - if string match post URL.
|
|
28
|
+
# * +:audios+ - if string match user or group URL.
|
|
29
|
+
# * +:find+ - in rest of cases.
|
|
12
30
|
def self.guess_request_type(str)
|
|
13
31
|
# Guess what type of request is this. Returns Symbol: :find, :playlist, :audios
|
|
14
32
|
case str
|
|
15
|
-
when PLAYLIST_URL_REGEX
|
|
33
|
+
when Constants::PLAYLIST_URL_REGEX
|
|
16
34
|
:playlist
|
|
17
|
-
when POST_URL_REGEX
|
|
35
|
+
when Constants::POST_URL_REGEX
|
|
18
36
|
:post
|
|
19
|
-
when VK_URL_REGEX
|
|
37
|
+
when Constants::VK_URL_REGEX
|
|
20
38
|
:audios
|
|
21
39
|
else
|
|
22
40
|
:find
|
|
23
41
|
end
|
|
24
42
|
end
|
|
25
43
|
|
|
44
|
+
# Turn hash into URL query string.
|
|
45
|
+
#
|
|
46
|
+
# ===== Parameters:
|
|
47
|
+
# * [+hash+] (+Hash+)
|
|
48
|
+
#
|
|
49
|
+
# ===== Returns:
|
|
50
|
+
# * (+String+)
|
|
26
51
|
def self.hash_to_params(hash = {})
|
|
27
52
|
qs = ""
|
|
28
53
|
hash.each_key do |key|
|
|
@@ -37,6 +62,7 @@ module VkMusic
|
|
|
37
62
|
qs
|
|
38
63
|
end
|
|
39
64
|
|
|
65
|
+
# Send warning.
|
|
40
66
|
def self.warn(*args)
|
|
41
67
|
if defined?(Warning.warn)
|
|
42
68
|
Warning.warn args.join("\n")
|
data/vk_music.gemspec
CHANGED
|
@@ -2,7 +2,7 @@ Gem::Specification.new do |s|
|
|
|
2
2
|
s.name = "vk_music"
|
|
3
3
|
s.summary = "Provides interface to work with VK music via HTTP requests"
|
|
4
4
|
s.description = "Library to work with audios on popular Russian social network vk.com. VK disabled their public API for audios, so it is now necessary to use parsers instead."
|
|
5
|
-
s.version = "1.1.
|
|
5
|
+
s.version = "1.1.1"
|
|
6
6
|
s.author = "Kuznetsov Vladislav"
|
|
7
7
|
s.email = "fizvlad@mail.ru"
|
|
8
8
|
s.homepage = "https://github.com/fizvlad/vk-music-rb"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: vk_music
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.1.
|
|
4
|
+
version: 1.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kuznetsov Vladislav
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2019-
|
|
11
|
+
date: 2019-08-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: mechanize
|