vk_music 3.1.4 → 4.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +3 -0
  3. data/.github/workflows/ruby.yml +35 -0
  4. data/.gitignore +6 -0
  5. data/.rspec +1 -0
  6. data/.rubocop.yml +56 -0
  7. data/Gemfile +38 -10
  8. data/Gemfile.lock +76 -25
  9. data/LICENSE.txt +0 -0
  10. data/README.md +113 -94
  11. data/Rakefile +15 -22
  12. data/bin/console +18 -24
  13. data/lib/vk_music.rb +32 -18
  14. data/lib/vk_music/audio.rb +111 -187
  15. data/lib/vk_music/client.rb +187 -645
  16. data/lib/vk_music/playlist.rb +44 -97
  17. data/lib/vk_music/request.rb +13 -0
  18. data/lib/vk_music/request/audios.rb +29 -0
  19. data/lib/vk_music/request/base.rb +75 -0
  20. data/lib/vk_music/request/login.rb +35 -0
  21. data/lib/vk_music/request/my_page.rb +21 -0
  22. data/lib/vk_music/request/playlist.rb +31 -0
  23. data/lib/vk_music/request/playlist_section.rb +35 -0
  24. data/lib/vk_music/request/post.rb +22 -0
  25. data/lib/vk_music/request/profile.rb +24 -0
  26. data/lib/vk_music/request/search.rb +34 -0
  27. data/lib/vk_music/request/wall_section.rb +34 -0
  28. data/lib/vk_music/utility.rb +8 -78
  29. data/lib/vk_music/utility/audio_data_parser.rb +37 -0
  30. data/lib/vk_music/utility/audio_items_parser.rb +18 -0
  31. data/lib/vk_music/utility/audio_node_parser.rb +59 -0
  32. data/lib/vk_music/utility/audios_from_ids_loader.rb +21 -0
  33. data/lib/vk_music/utility/audios_ids_getter.rb +25 -0
  34. data/lib/vk_music/utility/audios_loader.rb +37 -0
  35. data/lib/vk_music/utility/data_type_guesser.rb +43 -0
  36. data/lib/vk_music/utility/duration_parser.rb +17 -0
  37. data/lib/vk_music/utility/last_profile_post_loader.rb +26 -0
  38. data/lib/vk_music/utility/link_decoder.rb +106 -0
  39. data/lib/vk_music/utility/node_text_children_reader.rb +14 -0
  40. data/lib/vk_music/utility/playlist_loader.rb +30 -0
  41. data/lib/vk_music/utility/playlist_node_parser.rb +21 -0
  42. data/lib/vk_music/utility/playlist_section_loader.rb +29 -0
  43. data/lib/vk_music/utility/playlist_url_parser.rb +32 -0
  44. data/lib/vk_music/utility/post_loader.rb +23 -0
  45. data/lib/vk_music/utility/post_url_parser.rb +24 -0
  46. data/lib/vk_music/utility/profile_id_resolver.rb +58 -0
  47. data/lib/vk_music/utility/wall_loader.rb +25 -0
  48. data/lib/vk_music/version.rb +7 -5
  49. data/lib/vk_music/web_parser.rb +9 -0
  50. data/lib/vk_music/web_parser/audios.rb +20 -0
  51. data/lib/vk_music/web_parser/base.rb +27 -0
  52. data/lib/vk_music/web_parser/login.rb +13 -0
  53. data/lib/vk_music/web_parser/my_page.rb +19 -0
  54. data/lib/vk_music/web_parser/playlist.rb +33 -0
  55. data/lib/vk_music/web_parser/playlist_section.rb +53 -0
  56. data/lib/vk_music/web_parser/post.rb +15 -0
  57. data/lib/vk_music/web_parser/profile.rb +33 -0
  58. data/lib/vk_music/web_parser/search.rb +56 -0
  59. data/lib/vk_music/web_parser/wall_section.rb +53 -0
  60. data/vk_music.gemspec +36 -40
  61. metadata +59 -77
  62. data/.travis.yml +0 -7
  63. data/bin/setup +0 -8
  64. data/lib/vk_music/constants.rb +0 -78
  65. data/lib/vk_music/exceptions.rb +0 -21
  66. data/lib/vk_music/link_decoder.rb +0 -102
  67. data/lib/vk_music/utility/log.rb +0 -51
@@ -1,97 +1,44 @@
1
- module VkMusic
2
- ##
3
- # VK playlist.
4
- class Playlist
5
- include Enumerable
6
-
7
- ##
8
- # @return [Integer, nil] playlist ID.
9
- attr_reader :id
10
- ##
11
- # @return [Integer, nil] owner of playlist ID.
12
- attr_reader :owner_id
13
- ##
14
- # @return [String, nil] access hash which should be part of link for some playlists.
15
- attr_reader :access_hash
16
- ##
17
- # @return [String] playlist title.
18
- attr_reader :title
19
- ##
20
- # @return [String, nil] playlist subtitle. May be empty.
21
- attr_reader :subtitle
22
- ##
23
- # @return [Integer, nil] real size of playlist or +nil+ if unknown.
24
- attr_reader :real_size
25
-
26
- ##
27
- # @!visibility private
28
- attr_reader :list
29
-
30
- ##
31
- # @return [String] playlist description in Russian.
32
- def to_s
33
- (@subtitle && !@subtitle.empty? ? "#{@subtitle} - " : "") +
34
- @title +
35
- (@real_size ? "(#{self.length} из #{@real_size} аудиозаписей загружено)" : " (#{self.length} аудиозаписей)")
36
- end
37
- ##
38
- # @return [String] Same to {#to_s}, but also outputs list of audios.
39
- def pp
40
- "#{to_s}:\n#{@list.map(&:pp).join("\n")}"
41
- end
42
-
43
- ##
44
- # @return [Array<Audio>] Returns duplicate of array of playlist audios.
45
- def to_a
46
- @list.dup
47
- end
48
-
49
- ##
50
- # @!visibility private
51
- def each(&block)
52
- @list.each(&block)
53
- end
54
- ##
55
- # @return [Integer] amount of audios. This can be less than real size as not all audios might be loaded.
56
- def length
57
- @list.length
58
- end
59
- alias size length
60
- ##
61
- # Access audios from playlist.
62
- # @param index [Integer] index of audio (starting from 0).
63
- # @return [Audio, nil] audio or +nil+ if out of range.
64
- def [](index)
65
- @list[index]
66
- end
67
- ##
68
- # @return [Boolean] whether no audios loaded for this playlist.
69
- def empty?
70
- @list.empty?
71
- end
72
-
73
- ##
74
- # Initialize new playlist.
75
- #
76
- # @param list [Array] list of audios in playlist.
77
- # @param id [Integer, nil]
78
- # @param owner_id [Integer, nil]
79
- # @param access_hash [String, nil]
80
- # @param title [String]
81
- # @param subtitle [String, nil]
82
- # @param real_size [Integer, nil]
83
- def initialize(list, id: nil, owner_id: nil, access_hash: nil, title: "", subtitle: nil, real_size: nil)
84
- raise ArgumentError unless list.is_a?(Array)
85
- # Saving list
86
- @list = list.dup
87
-
88
- # Setting up attributes
89
- @id = id
90
- @owner_id = owner_id
91
- @access_hash = access_hash
92
- @title = title
93
- @subtitle = subtitle
94
- @real_size = real_size
95
- end
96
- end
97
- end
1
+ # frozen_string_literal: true
2
+
3
+ module VkMusic
4
+ # Class representing VK playlist
5
+ class Playlist
6
+ extend Forwardable
7
+ include Enumerable
8
+
9
+ # @return [Integer, nil] playlist ID
10
+ attr_reader :id
11
+ # @return [Integer, nil] playlist owner ID
12
+ attr_reader :owner_id
13
+ # @return [String, nil] access hash which should be part of link for some playlists
14
+ attr_reader :access_hash
15
+ # @return [String] playlist title
16
+ attr_reader :title
17
+ # @return [String, nil] playlist subtitle. May be empty
18
+ attr_reader :subtitle
19
+ # @return [Integer, nil] real size of playlist or +nil+ if unknown
20
+ attr_reader :real_size
21
+ # @return [Array] audios array
22
+ attr_reader :audios
23
+
24
+ # Initialize new playlist
25
+ # @param audios [Array] list of audios in playlist
26
+ # @param id [Integer, nil]
27
+ # @param owner_id [Integer, nil]
28
+ # @param access_hash [String, nil]
29
+ # @param title [String]
30
+ # @param subtitle [String, nil]
31
+ # @param real_size [Integer, nil]
32
+ def initialize(audios, id: nil, owner_id: nil, access_hash: nil, title: '', subtitle: nil, real_size: nil)
33
+ @audios = audios.dup
34
+ @id = id
35
+ @owner_id = owner_id
36
+ @access_hash = access_hash
37
+ @title = title.to_s.strip
38
+ @subtitle = subtitle
39
+ @real_size = real_size
40
+ end
41
+
42
+ def_delegators :@audios, :each, :length, :size, :[], :empty?
43
+ end
44
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VkMusic
4
+ # Bunch of different web requests
5
+ module Request
6
+ # VK root URL
7
+ VK_ROOT = 'https://m.vk.com'
8
+ public_constant :VK_ROOT
9
+ end
10
+ end
11
+
12
+ require_relative 'request/base'
13
+ Dir[File.join(__dir__, 'request', '*.rb')].each { |file| require_relative file }
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VkMusic
4
+ module Request
5
+ # Audios reload
6
+ class AudiosReload < Base
7
+ # Initialize new request
8
+ # @param ids [Array<String>]
9
+ # @param client_id [Integer]
10
+ def initialize(ids, client_id)
11
+ @client_id = client_id
12
+ super(
13
+ "#{VK_ROOT}/audio",
14
+ { act: 'reload_audio', ids: ids.join(','), utf8: true },
15
+ 'GET',
16
+ {}
17
+ )
18
+ end
19
+
20
+ def_delegators :@parser, :audios
21
+
22
+ private
23
+
24
+ def after_call
25
+ @parser = WebParser::AudiosReload.new(@response.body, client_id: @client_id)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VkMusic
4
+ module Request
5
+ # Base class for most of requests
6
+ class Base
7
+ extend Forwardable
8
+
9
+ # @return [String]
10
+ attr_reader :path
11
+ # @return [Hash]
12
+ attr_reader :data
13
+ # @return [String]
14
+ attr_reader :method
15
+ # @return [Hash]
16
+ attr_reader :headers
17
+ # @return [Mechanize::File?]
18
+ attr_reader :response
19
+
20
+ # Initialize new request
21
+ # @param path [String]
22
+ # @param data [Hash]
23
+ # @param method [String]
24
+ # @param headers [Hash]
25
+ def initialize(path, data = {}, method = 'GET', headers = {})
26
+ @path = path
27
+ @data = data
28
+ @method = method.upcase
29
+ @headers = headers
30
+
31
+ @response = nil
32
+ end
33
+
34
+ # @param agent [Mechanize]
35
+ # @return [self]
36
+ def call(agent)
37
+ before_call
38
+
39
+ log
40
+ @response = case method
41
+ when 'GET' then get(agent)
42
+ when 'POST' then post(agent)
43
+ else raise(ArgumentError, "unsupported method #{method}")
44
+ end
45
+
46
+ after_call
47
+
48
+ self
49
+ end
50
+
51
+ private
52
+
53
+ def log
54
+ VkMusic.log.debug('request') do
55
+ "#{method} to '#{path}', with data: #{data}, headers: #{headers}"
56
+ end
57
+ end
58
+
59
+ def get(agent)
60
+ uri = URI(path)
61
+ uri.query = URI.encode_www_form(data)
62
+ agent.get(uri, [], nil, headers)
63
+ end
64
+
65
+ def post(agent)
66
+ uri = URI(path)
67
+ agent.post(uri, data, headers)
68
+ end
69
+
70
+ def before_call; end
71
+
72
+ def after_call; end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VkMusic
4
+ module Request
5
+ # Logging in request
6
+ class Login < Base
7
+ # Initialize new request
8
+ def initialize
9
+ super("#{VK_ROOT}/login", {}, 'GET', {})
10
+ @success = false
11
+ end
12
+
13
+ # @return [Boolean]
14
+ def success?
15
+ !!@success
16
+ end
17
+
18
+ # Send login form
19
+ def send_form(login, password, agent)
20
+ form = @parser.login_form
21
+ form['email'] = login
22
+ form['pass'] = password
23
+ page = agent.submit(form)
24
+
25
+ @success = (page.uri.to_s == 'https://m.vk.com/feed')
26
+ end
27
+
28
+ private
29
+
30
+ def after_call
31
+ @parser = WebParser::Login.new(@response)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VkMusic
4
+ module Request
5
+ # Logging in request
6
+ class MyPage < Base
7
+ # Initialize new request
8
+ def initialize
9
+ super("#{VK_ROOT}/id0", {}, 'GET', {})
10
+ end
11
+
12
+ def_delegators :@parser, :id, :name
13
+
14
+ private
15
+
16
+ def after_call
17
+ @parser = WebParser::MyPage.new(@response)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VkMusic
4
+ module Request
5
+ # Playlist in web-mobile request
6
+ class Playlist < Base
7
+ # Initialize new request
8
+ # @param owner_id [Integer]
9
+ # @param playlist_id [Integer]
10
+ # @param access_hash [String, nil]
11
+ # @param client_id [Integer]
12
+ def initialize(owner_id, playlist_id, access_hash, client_id)
13
+ @client_id = client_id
14
+ super(
15
+ "#{VK_ROOT}/audio",
16
+ { act: "audio_playlist#{owner_id}_#{playlist_id}", access_hash: access_hash },
17
+ 'GET',
18
+ {}
19
+ )
20
+ end
21
+
22
+ def_delegators :@parser, :audios, :title, :subtitle, :real_size
23
+
24
+ private
25
+
26
+ def after_call
27
+ @parser = WebParser::Playlist.new(@response.body)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VkMusic
4
+ module Request
5
+ # Playlist in web-mobile request
6
+ class PlaylistSection < Base
7
+ # Initialize new request
8
+ # @param owner_id [Integer]
9
+ # @param playlist_id [Integer]
10
+ # @param access_hash [String, nil]
11
+ # @param offset [Integer]
12
+ # @param client_id [Integer]
13
+ def initialize(owner_id, playlist_id, access_hash, offset, client_id)
14
+ @client_id = client_id
15
+ super(
16
+ "#{VK_ROOT}/audio",
17
+ {
18
+ act: 'load_section', type: 'playlist', offset: offset, utf8: true,
19
+ owner_id: owner_id, playlist_id: playlist_id, access_hash: access_hash.to_s
20
+ },
21
+ 'GET',
22
+ {}
23
+ )
24
+ end
25
+
26
+ def_delegators :@parser, :audios, :title, :subtitle, :real_size, :more?
27
+
28
+ private
29
+
30
+ def after_call
31
+ @parser = WebParser::PlaylistSection.new(@response, client_id: @client_id)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VkMusic
4
+ module Request
5
+ # Post page
6
+ class Post < Base
7
+ # Initialize new request
8
+ def initialize(owner_id, post_id, client_id)
9
+ @client_id = client_id
10
+ super("#{VK_ROOT}/wall#{owner_id}_#{post_id}", {}, 'GET', {})
11
+ end
12
+
13
+ def_delegators :@parser, :audios
14
+
15
+ private
16
+
17
+ def after_call
18
+ @parser = WebParser::Post.new(@response, client_id: @client_id)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VkMusic
4
+ module Request
5
+ # User or group page
6
+ class Profile < Base
7
+ # Initialize new request
8
+ def initialize(profile_id: nil, profile_custom_path: nil)
9
+ profile_path =
10
+ profile_custom_path ||
11
+ "#{profile_id.negative? ? 'club' : 'id'}#{profile_id.abs}"
12
+ super("#{VK_ROOT}/#{profile_path}", {}, 'GET', {})
13
+ end
14
+
15
+ def_delegators :@parser, :id, :last_post_id
16
+
17
+ private
18
+
19
+ def after_call
20
+ @parser = WebParser::Profile.new(@response)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VkMusic
4
+ module Request
5
+ # Logging in request
6
+ class Search < Base
7
+ # Initialize new request
8
+ # @param query [String]
9
+ # @param client_id [Integer]
10
+ def initialize(query, client_id)
11
+ @client_id = client_id
12
+ super(
13
+ "#{VK_ROOT}/audio",
14
+ { q: query, _ajax: 1 },
15
+ 'POST',
16
+ { 'content-type' => 'application/x-www-form-urlencoded', 'x-requested-with' => 'XMLHttpRequest' }
17
+ )
18
+ end
19
+
20
+ def_delegators :@parser, :audios, :audios_all_path, :playlists, :playlists_all_path
21
+
22
+ private
23
+
24
+ def after_call
25
+ json = JSON.parse(@response.body.strip)
26
+ raise 'Captcha requested' if json['key'] == 'captcha_key'
27
+
28
+ inner = json['data'][2]
29
+ html = Nokogiri::HTML.fragment(CGI.unescapeElement(inner))
30
+ @parser = WebParser::Search.new(html)
31
+ end
32
+ end
33
+ end
34
+ end