vk_music 0.0.2 → 0.1.1

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: 630c70ebe99cfb32de3d536247332448eba5100ed50e22b625942dd0bfae1ad1
4
- data.tar.gz: 449db24fadc1762ebe302a7cd67ac302495b2d05987df3bcbaf105bf6a242ce4
3
+ metadata.gz: 75c5a7995475caabd8779990209db2cca18f9fb17227680e4f5925cd0fb93480
4
+ data.tar.gz: afb4818c76ea686851bb191154d46a09458655d14b9253659a93edbb4e83ebba
5
5
  SHA512:
6
- metadata.gz: aa5706ff7955649611171b4b7fa6fdfabaad1b58c64b8fb2d4a2f594e83b5472d57f989377a4814517f489238e04fe7c2801a4b941e01edd945967256041b424
7
- data.tar.gz: 39efc44662fb545306547b83e234b7804fc48d9079c84d0eca6d4276fa4a705ef83f2a6c348a42055c70c4c1ea42d789562d9f8e182bbee6bee092d099246f03
6
+ metadata.gz: f1c85be3faff79c6a5c6a00604c6108f76d5121384282783c4451334dd0653b9e0d9cd2e9690ed76e259c4646299283385406281e0a3c3b87e94506365ef1b9b
7
+ data.tar.gz: f3caed90799b89082654ddbdb048fdbf9df791add1c7b88710a48e96a69e9ee8e6096bd53d5a631da4498738b3a6c006149af3f43aff98c73b7840998722693b
data/README.md CHANGED
@@ -12,45 +12,56 @@
12
12
  ## Installation
13
13
 
14
14
  Simpliest way to install gem:
15
- ``
15
+ ```
16
16
  gem install vk_music
17
- ``
17
+ ```
18
18
 
19
19
  Alternatively, you can build gem from sources with following command:
20
- ``
20
+ ```
21
21
  gem build vk_music.gemspec
22
- ``
22
+ ```
23
23
 
24
24
  ... and install it:
25
- ``
26
- gem install vk_music-0.0.1.gem
27
- ``
25
+ ```
26
+ gem install vk_music-*.gem
27
+ ```
28
28
 
29
29
 
30
30
  ## Usage
31
31
 
32
32
  ### Logging in
33
33
  Firstly, it is required to create new *VkMusic::Client* and provide login credentials:
34
- ```
34
+ ```ruby
35
35
  client = VkMusic::Client.new(username: "+71234567890", password: "password")
36
36
  ```
37
37
 
38
38
  ### Searching for audios
39
39
  You can search audios by name with following method:
40
- ```
40
+ ```ruby
41
41
  audios = client.find_audio("Acid Spit - Mega Drive")
42
42
  puts audios[0] # Basic information about audio
43
- puts audios[0].url # Download this audio using its URL
43
+ puts audios[0].url # URL to access audio. Notice that it is only accessible from your IP
44
44
  ```
45
45
 
46
46
  ### Parsing playlists
47
47
  You can load all the audios from playlist using following method:
48
- ```
48
+ ```ruby
49
49
  playlist = client.get_playlist("https://vk.com/audio?z=audio_playlist-37661843_1/0e420c32c8b69e6637")
50
50
  ```
51
51
  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
52
  For example, following method will perform only one HTML request:
53
- ```
53
+ ```ruby
54
54
  playlist = client.get_playlist("https://vk.com/audio?z=audio_playlist121570739_7", 100)
55
55
  urls = playlist.map(&:url) # URLs for every audio
56
56
  ```
57
+
58
+ ### User or group audios
59
+ 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
+ ```ruby
61
+ user_playlist = client.get_audios("8024985")
62
+ group_playlist = client.get_audios("-4790861") # Group and public id starts with '-'
63
+ ```
64
+ You can set how many audios you actually need as well:
65
+ ```ruby
66
+ user_playlist = client.get_audios("8024985", 10)
67
+ ```
data/Rakefile CHANGED
@@ -1,6 +1,23 @@
1
+ require "io/console"
2
+
1
3
  desc "Build gem file"
2
4
  task :build do
3
- `gem build vk_music.gemfile`
5
+ puts `gem build vk_music.gemspec`
4
6
  end
5
7
 
6
- # TODO: Test task
8
+ desc "Run tests"
9
+ task :test do
10
+ puts "Running tests require login credetionals"
11
+
12
+ print "Login: "
13
+ username = STDIN.gets.chomp
14
+
15
+ print "Password: "
16
+ password = STDIN.noecho(&:gets).chomp
17
+ puts
18
+
19
+ Dir[ "test/test*.rb" ].each do |file|
20
+ puts "\n\nRunning #{file}:"
21
+ puts `ruby -w #{file} #{username} #{password}`
22
+ end
23
+ end
@@ -37,7 +37,22 @@ module VkMusic
37
37
  :title => node.at_css(".ai_title").text.strip,
38
38
  :duration => node.at_css(".ai_dur").attribute("data-dur").to_s.to_i,
39
39
  :url_encoded => url_encoded,
40
- :url => url_encoded ? VkMusic.unmask_link(url_encoded, client_id) : "",
40
+ :url => url_encoded ? VkMusic.unmask_link(url_encoded, client_id) : nil,
41
+ })
42
+ end
43
+
44
+ def self.from_data_array(data, client_id)
45
+ url_encoded = data[2]
46
+ url_encoded = nil if url_encoded == ""
47
+
48
+ new({
49
+ :id => data[0],
50
+ :owner_id => data[1],
51
+ :artist => data[4],
52
+ :title => data[3],
53
+ :duration => data[5],
54
+ :url_encoded => url_encoded,
55
+ :url => url_encoded ? VkMusic.unmask_link(url_encoded, client_id) : nil,
41
56
  })
42
57
  end
43
58
 
@@ -1,4 +1,5 @@
1
1
  require "mechanize"
2
+ require "json"
2
3
 
3
4
  module VkMusic
4
5
 
@@ -22,8 +23,8 @@ module VkMusic
22
23
 
23
24
  def find_audio(query)
24
25
  uri = URI(VK_URL[:audios])
25
- uri.query = URI.encode_www_form("act" => "search", "q" => query.to_s)
26
- load_audios_from(uri)
26
+ uri.query = URI.encode_www_form({ "act" => "search", "q" => query.to_s })
27
+ load_audios_from_page(uri)
27
28
  end
28
29
 
29
30
  def get_playlist(url, up_to = nil)
@@ -31,18 +32,25 @@ module VkMusic
31
32
 
32
33
  # Load first page and get info
33
34
  first_page = load_playlist_page(owner_id: owner_id, id: id, access_hash: access_hash, offset: 0)
34
- title = first_page.at_css(".audioPlaylist__title").text.strip
35
- subtitle = first_page.at_css(".audioPlaylist__subtitle").text.strip
36
35
 
37
- footer_node = first_page.at_css(".audioPlaylist__footer")
38
- if footer_node
39
- footer_match = footer_node.text.strip.match(/^\d+/)
40
- playlist_size = footer_match ? footer_match[0].to_i : 0
41
- else
42
- playlist_size = 0
36
+ # Trying to parse out essential data
37
+ begin
38
+ title = first_page.at_css(".audioPlaylist__title").text.strip
39
+ subtitle = first_page.at_css(".audioPlaylist__subtitle").text.strip
40
+
41
+ footer_node = first_page.at_css(".audioPlaylist__footer")
42
+ if footer_node
43
+ footer_match = footer_node.text.strip.match(/^\d+/)
44
+ playlist_size = footer_match ? footer_match[0].to_i : 0
45
+ else
46
+ playlist_size = 0
47
+ end
48
+ rescue Exception => error
49
+ raise PlaylistParseError, "unable to parse playlist page. Redirected to #{first_page.uri.to_s}. Error: #{error.message}", caller
43
50
  end
51
+ # Now we can be sure we are on correct page
44
52
 
45
- first_page_audios = load_audios_from(first_page)
53
+ first_page_audios = load_audios_from_page(first_page)
46
54
 
47
55
  # Check whether need to make additional requests
48
56
  up_to = playlist_size if (up_to.nil? || up_to < 0 || up_to > playlist_size)
@@ -52,7 +60,7 @@ module VkMusic
52
60
  list = first_page_audios
53
61
  loop do
54
62
  playlist_page = load_playlist_page(owner_id: owner_id, id: id, access_hash: access_hash, offset: list.length)
55
- list.concat(load_audios_from(playlist_page)[0, up_to - list.length])
63
+ list.concat(load_audios_from_page(playlist_page)[0, up_to - list.length])
56
64
  break if list.length == up_to
57
65
  end
58
66
  end
@@ -66,24 +74,80 @@ module VkMusic
66
74
  })
67
75
  end
68
76
 
77
+ def get_audios(id, up_to = nil)
78
+ Warning.warn("Current implementation of method VkMusic::Client#get_audios is only able to load first 100 audios from user page.\n") if (up_to && up_to > 100)
79
+ # NOTICE: this method is only able to load first 100 audios
80
+
81
+ # Trying to parse out audios
82
+ begin
83
+ first_json = load_playlist_json_section(id: id.to_s, playlist_id: -1, offset: 0)
84
+ first_data = first_json["data"][0]
85
+ first_data_audios = load_audios_from_data(first_data)
86
+ rescue Exception => error
87
+ raise AudiosSectionParseError, "unable to load or parse audios section: #{error.message}", caller
88
+ end
89
+
90
+ #total_count = first_data["totalCount"] # NOTICE: not used due to restrictions dexcribed above
91
+ total_count = first_data_audios.length
92
+ up_to = total_count if (up_to.nil? || up_to < 0 || up_to > total_count)
93
+ list = first_data_audios[0, up_to]
94
+
95
+ # It turns out user audios are just playlist with id -1
96
+ Playlist.new(list, {
97
+ :id => first_data["id"],
98
+ :owner_id => first_data["owner_id"],
99
+ :access_hash => first_data["access_hash"],
100
+ :title => first_data["title"],
101
+ :subtitle => first_data["subtitle"],
102
+ })
103
+ end
104
+
69
105
  private
70
106
  # Loading pages
71
107
  def load_page(url)
72
108
  uri = URI(url) if url.class != URI
73
109
  @agent.get(uri)
74
110
  end
111
+ def load_json(url)
112
+ page = load_page(url)
113
+ JSON.parse(page.body.strip)
114
+ end
115
+
75
116
  def load_playlist_page(options)
76
117
  uri = URI(VK_URL[:audios])
77
- uri.query = URI.encode_www_form("act" => "audio_playlist#{options[:owner_id]}_#{options[:id]}", "access_hash" => options[:access_hash].to_s, "offset" => options[:offset].to_i)
118
+ uri.query = URI.encode_www_form({
119
+ "act" => "audio_playlist#{options[:owner_id]}_#{options[:id]}",
120
+ "access_hash" => options[:access_hash].to_s,
121
+ "offset" => options[:offset].to_i
122
+ })
78
123
  load_page(uri)
79
124
  end
125
+ def load_playlist_json_section(options)
126
+ uri = URI(VK_URL[:audios])
127
+ uri.query = URI.encode_www_form({
128
+ "act" => "load_section",
129
+ "owner_id" => options[:id],
130
+ "playlist_id" => options[:playlist_id],
131
+ "type" => "playlist",
132
+ "offset" => options[:offset].to_i,
133
+ "utf8" => true
134
+ })
135
+ begin
136
+ load_json(uri)
137
+ rescue Exception => error
138
+ raise AudiosSectionParseError, "unable to load or parse audios section: #{error.message}", caller
139
+ end
140
+ end
80
141
 
81
142
 
82
143
  # Loading audios
83
- def load_audios_from(obj)
144
+ def load_audios_from_page(obj)
84
145
  page = obj.class == Mechanize::Page ? obj : load_page(obj)
85
146
  page.css(".audio_item.ai_has_btn").map { |elem| Audio.from_node(elem, @id) }
86
147
  end
148
+ def load_audios_from_data(data)
149
+ data["list"].map { |audio_data| Audio.from_data_array(audio_data, @id) }
150
+ end
87
151
 
88
152
 
89
153
  def login(username, password)
@@ -4,4 +4,16 @@ module VkMusic
4
4
  # Unable to login
5
5
  end
6
6
 
7
+ class PlaylistParseError < RuntimeError
8
+ # Unable to find playlist or got permission error
9
+ end
10
+
11
+ class AudiosParseError < RuntimeError
12
+ # Unable to find user/group or got permission error
13
+ end
14
+
15
+ class AudiosSectionParseError < AudiosParseError
16
+ # Unable to load or parse audios section
17
+ end
18
+
7
19
  end
@@ -21,6 +21,10 @@ module VkMusic
21
21
  def [](index)
22
22
  @list[index]
23
23
  end
24
+
25
+ def empty?
26
+ @list.empty?
27
+ end
24
28
 
25
29
  def initialize(list, options = {})
26
30
  # Arguments check
@@ -1,12 +1,29 @@
1
1
  require "minitest/autorun"
2
2
  require_relative "../lib/vk_music.rb"
3
3
 
4
- class Example < MiniTest::Test
4
+ class TestVkMusic < MiniTest::Test
5
+
5
6
  def test_bad_data
6
- assert_raises(VkMusic::LoginError) {
7
- client = VkMusic::Client.new(username: "login", password: "password")
8
- }
7
+ assert_raises(VkMusic::LoginError) do
8
+ VkMusic::Client.new(username: "login", password: "password")
9
+ end
10
+ end
11
+
12
+ def test_empty_data
13
+ assert_raises(VkMusic::LoginError) do
14
+ VkMusic::Client.new(username: "", password: "")
15
+ end
16
+ end
17
+
18
+ def test_good_data
19
+ begin
20
+ client = VkMusic::Client.new(username: ARGV[0], password: ARGV[1])
21
+ rescue VkMusic::LoginError
22
+ puts "Unable to login! Please check provided credetionals"
23
+ end
24
+ refute_nil(client, "Client not defined")
25
+ refute_nil(client.name, "User name undefined")
26
+ refute_nil(client.id, "User id undefined")
9
27
  end
10
28
 
11
- # TODO: any way to test correct login?
12
29
  end
@@ -0,0 +1,52 @@
1
+ require "minitest/autorun"
2
+ require_relative "../lib/vk_music.rb"
3
+
4
+ begin
5
+ CLIENT = VkMusic::Client.new(username: ARGV[0], password: ARGV[1])
6
+ rescue VkMusic::LoginError
7
+ puts "Unable to login! Please check provided credetionals"
8
+ exit
9
+ end
10
+
11
+ class TestVkMusic < MiniTest::Test
12
+
13
+ def test_playlist_small
14
+ pl = CLIENT.get_playlist("https://vk.com/audio?z=audio_playlist-37661843_1/0e420c32c8b69e6637")
15
+ refute_empty(pl, "This playlist must not be empty")
16
+ assert_instance_of(VkMusic::Audio, pl[0], "Playlist members must be of class Audio")
17
+ refute_empty(pl[0].url, "Audio must have download url")
18
+ refute_empty(pl[-1].url, "Audio must have download url")
19
+ end
20
+
21
+ def test_playlist_large
22
+ pl = CLIENT.get_playlist("https://vk.com/audio?z=audio_playlist121570739_7")
23
+ refute_empty(pl, "This playlist must not be empty")
24
+ refute_empty(pl[0].url, "Audio must have download url")
25
+ refute_empty(pl[-1].url, "Audio must have download url")
26
+ end
27
+
28
+ def test_playlist_empty
29
+ pl = CLIENT.get_playlist("https://vk.com/audios437727675?section=playlists&z=audio_playlist437727675_2")
30
+ assert_empty(pl, "This playlist must be empty")
31
+ end
32
+
33
+ def test_playlist_dont_exist
34
+ assert_raises(VkMusic::PlaylistParseError) do
35
+ CLIENT.get_playlist("https://m.vk.com/audio?act=audio_playlist437727675_300")
36
+ end
37
+ end
38
+
39
+ def test_playlist_no_access
40
+ assert_raises(VkMusic::PlaylistParseError) do
41
+ CLIENT.get_playlist("https://m.vk.com/audio?act=audio_playlist1_1")
42
+ end
43
+ end
44
+
45
+ def test_playlist_with_upper_limit
46
+ pl = CLIENT.get_playlist("https://vk.com/audio?z=audio_playlist121570739_7", 113)
47
+ assert_equal(113, pl.length, "Size of result must match given limit") # This playlist got more audios
48
+ refute_empty(pl[0].url, "Audio must have download url")
49
+ refute_empty(pl[-1].url, "Audio must have download url")
50
+ end
51
+
52
+ end
@@ -0,0 +1,30 @@
1
+ require "minitest/autorun"
2
+ require_relative "../lib/vk_music.rb"
3
+
4
+ begin
5
+ CLIENT = VkMusic::Client.new(username: ARGV[0], password: ARGV[1])
6
+ rescue VkMusic::LoginError
7
+ puts "Unable to login! Please check provided credetionals"
8
+ exit
9
+ end
10
+
11
+ class TestVkMusic < MiniTest::Test
12
+
13
+ def test_search
14
+ results = CLIENT.find_audio("Rick Astley")
15
+ refute_empty(results, "There must be some music of Rick Astley")
16
+ assert_instance_of(VkMusic::Audio, results[0], "Results of search must be of class Audio")
17
+ refute_empty(results[0].url, "Audio must have download url")
18
+ end
19
+
20
+ def test_search_no_query
21
+ results = CLIENT.find_audio("")
22
+ assert_empty(results, "There must be no results for empty query")
23
+ end
24
+
25
+ def test_search_no_results
26
+ results = CLIENT.find_audio("I'm pretty sure no one ever would name a song like this 282E8EE")
27
+ assert_empty(results, "There must be no results for such query")
28
+ end
29
+
30
+ end
@@ -0,0 +1,62 @@
1
+ require "minitest/autorun"
2
+ require_relative "../lib/vk_music.rb"
3
+
4
+ begin
5
+ CLIENT = VkMusic::Client.new(username: ARGV[0], password: ARGV[1])
6
+ rescue VkMusic::LoginError
7
+ puts "Unable to login! Please check provided credetionals"
8
+ exit
9
+ end
10
+
11
+ class TestVkMusic < MiniTest::Test
12
+
13
+ def test_user
14
+ pl = CLIENT.get_audios("8024985")
15
+ assert_instance_of(VkMusic::Playlist, pl, "User audios must be returned as a playlist (cause it is actually is playlist)")
16
+ refute_empty(pl, "There must be some music")
17
+ assert_instance_of(VkMusic::Audio, pl[0], "Results must be of class Audio")
18
+ refute_empty(pl[0].url, "Audio must have download url")
19
+ end
20
+
21
+ def test_incorrect_id
22
+ assert_raises(VkMusic::AudiosParseError) do
23
+ CLIENT.get_audios("42424242424242424242424242")
24
+ end
25
+ end
26
+
27
+ def test_user_with_locked_audios
28
+ assert_raises(VkMusic::AudiosParseError) do
29
+ CLIENT.get_audios("152719703")
30
+ end
31
+ end
32
+
33
+ def test_user_with_empty_audios
34
+ pl = CLIENT.get_audios("437727675")
35
+ assert_empty(pl, "This user got no audios")
36
+ end
37
+
38
+ def test_group
39
+ pl = CLIENT.get_audios("-4790861")
40
+ refute_empty(pl, "There must be some music")
41
+ refute_empty(pl[0].url, "Audio must have download url")
42
+ end
43
+
44
+ def test_group_with_upper_limit_1
45
+ pl = CLIENT.get_audios("-72589944", 10)
46
+ assert_equal(10, pl.length, "Size of result must match given limit") # This group got more audios
47
+ refute_empty(pl[0].url, "Audio must have download url")
48
+ end
49
+
50
+ def test_group_with_upper_limit_2
51
+ pl = CLIENT.get_audios("-72589944", 200)
52
+ assert(pl.size <= 200, "Size of result must match given limit")
53
+ refute_empty(pl[0].url, "Audio must have download url")
54
+ refute_empty(pl[-1].url, "Audio must have download url")
55
+ end
56
+
57
+ def test_group_with_no_audios
58
+ pl = CLIENT.get_audios("-52298374")
59
+ assert_empty(pl, "This group got no audios")
60
+ end
61
+
62
+ end
@@ -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 = "0.0.2"
5
+ s.version = "0.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"
@@ -15,4 +15,5 @@ Gem::Specification.new do |s|
15
15
 
16
16
  s.add_runtime_dependency "mechanize", "~>2.7"
17
17
  s.add_runtime_dependency "duktape", "~>2.3"
18
+ s.add_runtime_dependency "json", "~>2.0"
18
19
  end
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: 0.0.2
4
+ version: 0.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-06-17 00:00:00.000000000 Z
11
+ date: 2019-06-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mechanize
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '2.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: json
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
41
55
  description: Library to work with audios on popular Russian social network vk.com.
42
56
  VK disabled their public API for audios, so it is now necessary to use parsers instead.
43
57
  email: fizvlad@mail.ru
@@ -57,6 +71,9 @@ files:
57
71
  - lib/vk_music/playlist.rb
58
72
  - lib/vk_music/utility.rb
59
73
  - test/test_login.rb
74
+ - test/test_playlist.rb
75
+ - test/test_search.rb
76
+ - test/test_user_or_group_audios.rb
60
77
  - vk_music.gemspec
61
78
  homepage: https://github.com/fizvlad/vk-music-rb
62
79
  licenses:
@@ -84,3 +101,6 @@ specification_version: 4
84
101
  summary: Provides interface to work with VK music via HTTP requests
85
102
  test_files:
86
103
  - test/test_login.rb
104
+ - test/test_playlist.rb
105
+ - test/test_search.rb
106
+ - test/test_user_or_group_audios.rb