vk_music 2.2.1 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +6 -4
- data/Gemfile.lock +66 -60
- data/README.md +87 -81
- data/bin/console +10 -14
- data/lib/vk_music.rb +18 -18
- data/lib/vk_music/audio.rb +68 -115
- data/lib/vk_music/client.rb +357 -497
- data/lib/vk_music/constants.rb +3 -20
- data/lib/vk_music/exceptions.rb +1 -12
- data/lib/vk_music/link_decoder.rb +2 -8
- data/lib/vk_music/playlist.rb +30 -41
- data/lib/vk_music/utility.rb +5 -63
- data/lib/vk_music/utility/log.rb +51 -0
- data/lib/vk_music/version.rb +5 -7
- metadata +3 -2
data/lib/vk_music/constants.rb
CHANGED
@@ -1,17 +1,14 @@
|
|
1
1
|
module VkMusic
|
2
|
-
|
3
2
|
##
|
4
3
|
# Constants.
|
5
4
|
module Constants
|
6
|
-
|
7
5
|
##
|
8
|
-
#
|
9
|
-
DEFAULT_USER_AGENT = "
|
6
|
+
# Default web user agent.
|
7
|
+
DEFAULT_USER_AGENT = "" # Using empty user agent confuses VK and it returnes MP3
|
10
8
|
|
11
9
|
##
|
12
10
|
# Different URLs
|
13
11
|
module URL
|
14
|
-
|
15
12
|
##
|
16
13
|
# Hash with URLs to VK pages which are used by library.
|
17
14
|
VK = {
|
@@ -27,55 +24,43 @@ module VkMusic
|
|
27
24
|
audio_unavailable: "https://m.vk.com/mp3/audio_api_unavailable.mp3",
|
28
25
|
profile_audios: "https://m.vk.com/audios",
|
29
26
|
}
|
30
|
-
|
31
27
|
end
|
32
28
|
|
33
29
|
##
|
34
30
|
# Regular expressions
|
35
31
|
module Regex
|
36
|
-
|
37
32
|
##
|
38
33
|
# VK user or group ID.
|
39
34
|
VK_ID_STR = /^-?\d+$/
|
40
|
-
|
41
35
|
##
|
42
36
|
# VK user or group ID.
|
43
37
|
VK_ID = /-?\d+/
|
44
|
-
|
45
38
|
##
|
46
39
|
# VK prefixed user or group ID.
|
47
40
|
VK_PREFIXED_ID_STR = /^(?:id|club|group|public|event)(\d+)$/
|
48
|
-
|
49
41
|
##
|
50
42
|
# VK custom ID regular expression.
|
51
43
|
VK_CUSTOM_ID = /^\w+$/
|
52
|
-
|
53
44
|
##
|
54
45
|
# VK URL regular expression.
|
55
46
|
VK_URL = /(?:https?:\/\/)?(?:m\.|www\.)?vk\.com\/([\w\-]+)/
|
56
|
-
|
57
47
|
##
|
58
48
|
# +href+ attribute with VK ID regular expression.
|
59
49
|
VK_HREF_ID_CONTAINING = /(?:audios|photo|write|owner_id=|friends\?id=)(-?\d+)/
|
60
|
-
|
61
50
|
##
|
62
51
|
# VK audios page regular expression.
|
63
52
|
VK_AUDIOS_URL_POSTFIX = /^audios(-?\d+)$/
|
64
|
-
|
65
53
|
##
|
66
54
|
# Playlist URL regular expression.
|
67
55
|
VK_PLAYLIST_URL_POSTFIX = /.*(?:audio_playlist|album\/)(-?\d+)_(\d+)(?:(?:(?:.*(?=&access_hash=)&access_hash=)|\/|%2F|_)([\da-z]+))?/
|
68
|
-
|
69
56
|
##
|
70
57
|
# Post URL regular expression #1.
|
71
58
|
VK_POST_URL_POSTFIX = /.*post(-?\d+)_(\d+)/
|
72
|
-
|
73
59
|
##
|
74
60
|
# Post URL regular expression #2.
|
75
61
|
VK_WALL_URL_POSTFIX = /.*wall(-?\d+)_(\d+)/
|
76
|
-
|
77
62
|
end
|
78
|
-
|
63
|
+
|
79
64
|
##
|
80
65
|
# Names used in VK login form.
|
81
66
|
VK_LOGIN_FORM_NAMES = {
|
@@ -86,7 +71,5 @@ module VkMusic
|
|
86
71
|
##
|
87
72
|
# Maximum amount of audios in VK playlist.
|
88
73
|
MAXIMUM_PLAYLIST_SIZE = 10000
|
89
|
-
|
90
74
|
end
|
91
|
-
|
92
75
|
end
|
data/lib/vk_music/exceptions.rb
CHANGED
@@ -1,32 +1,21 @@
|
|
1
1
|
module VkMusic
|
2
|
-
|
3
2
|
##
|
4
3
|
# Exceptions.
|
5
4
|
module Exceptions
|
6
|
-
|
7
5
|
##
|
8
6
|
# General class for all the errors.
|
9
7
|
class VkMusicError < RuntimeError; end
|
10
|
-
|
11
8
|
##
|
12
9
|
# Failed to login.
|
13
10
|
class LoginError < VkMusicError; end
|
14
|
-
|
15
11
|
##
|
16
|
-
# Failed to get request. _Only_
|
12
|
+
# Failed to get request. _Only_ thrown when Mechanize failed to load page
|
17
13
|
class RequestError < VkMusicError; end
|
18
|
-
|
19
14
|
##
|
20
15
|
# Parse error. Request is OK, but something went wrong while parsing reply.
|
21
16
|
# It might be missing playlist/post as well.
|
22
17
|
class ParseError < VkMusicError; end
|
23
|
-
|
24
|
-
##
|
25
|
-
# Unable to login.
|
26
|
-
class LoginError < VkMusicError; end
|
27
|
-
|
28
18
|
end
|
29
19
|
|
30
20
|
include Exceptions
|
31
|
-
|
32
21
|
end
|
@@ -1,9 +1,7 @@
|
|
1
1
|
module VkMusic
|
2
|
-
|
3
2
|
##
|
4
3
|
# Module containing link decoding utilities.
|
5
4
|
module LinkDecoder
|
6
|
-
|
7
5
|
##
|
8
6
|
# JS code which creates function to unmask audio URL.
|
9
7
|
js_code = <<~HEREDOC
|
@@ -90,19 +88,15 @@ module VkMusic
|
|
90
88
|
##
|
91
89
|
# JS context with unmasking link.
|
92
90
|
@@js_context = ExecJS.compile(js_code)
|
93
|
-
|
91
|
+
|
94
92
|
##
|
95
93
|
# Unmask audio download URL.
|
96
|
-
#
|
97
94
|
# @param link [String] encoded link to audio. Usually looks like "https://m.vk.com/mp3/audio_api_unavailable.mp3?extra=...".
|
98
95
|
# @param client_id [Integer] ID of user which got this link. ID is required for decoding.
|
99
|
-
#
|
100
96
|
# @return [String] audio download URL, which can be used only from current IP.
|
101
97
|
def self.unmask_link(link, client_id)
|
102
|
-
|
98
|
+
VkMusic.debug("Unmasking link.")
|
103
99
|
@@js_context.call("vk_unmask_link", link.to_s, client_id.to_i)
|
104
100
|
end
|
105
|
-
|
106
101
|
end
|
107
|
-
|
108
102
|
end
|
data/lib/vk_music/playlist.rb
CHANGED
@@ -1,34 +1,32 @@
|
|
1
1
|
module VkMusic
|
2
|
-
|
3
2
|
##
|
4
3
|
# VK playlist.
|
5
4
|
class Playlist
|
6
5
|
include Enumerable
|
7
|
-
|
6
|
+
|
8
7
|
##
|
9
8
|
# @return [Integer, nil] playlist ID.
|
10
9
|
attr_reader :id
|
11
|
-
|
12
10
|
##
|
13
11
|
# @return [Integer, nil] owner of playlist ID.
|
14
12
|
attr_reader :owner_id
|
15
|
-
|
16
13
|
##
|
17
14
|
# @return [String, nil] access hash which should be part of link for some playlists.
|
18
15
|
attr_reader :access_hash
|
19
|
-
|
20
16
|
##
|
21
17
|
# @return [String] playlist title.
|
22
18
|
attr_reader :title
|
23
|
-
|
24
19
|
##
|
25
20
|
# @return [String, nil] playlist subtitle. May be empty.
|
26
21
|
attr_reader :subtitle
|
27
|
-
|
28
22
|
##
|
29
23
|
# @return [Integer, nil] real size of playlist or +nil+ if unknown.
|
30
24
|
attr_reader :real_size
|
31
|
-
|
25
|
+
|
26
|
+
##
|
27
|
+
# @!visibility private
|
28
|
+
attr_reader :list
|
29
|
+
|
32
30
|
##
|
33
31
|
# @return [String] playlist description in Russian.
|
34
32
|
def to_s
|
@@ -36,7 +34,6 @@ module VkMusic
|
|
36
34
|
@title +
|
37
35
|
(@real_size ? "(#{self.length} из #{@real_size} аудиозаписей загружено)" : " (#{self.length} аудиозаписей)")
|
38
36
|
end
|
39
|
-
|
40
37
|
##
|
41
38
|
# @return [String] Same to {#to_s}, but also outputs list of audios.
|
42
39
|
def pp
|
@@ -48,61 +45,53 @@ module VkMusic
|
|
48
45
|
def to_a
|
49
46
|
@list.dup
|
50
47
|
end
|
51
|
-
|
48
|
+
|
52
49
|
##
|
53
|
-
#
|
50
|
+
# @!visibility private
|
54
51
|
def each(&block)
|
55
52
|
@list.each(&block)
|
56
53
|
end
|
57
|
-
|
58
54
|
##
|
59
|
-
# @
|
55
|
+
# @return [Integer] amount of audios. This can be less than real size as not all audios might be loaded.
|
60
56
|
def length
|
61
57
|
@list.length
|
62
58
|
end
|
63
59
|
alias size length
|
64
|
-
|
65
|
-
##
|
66
|
-
# @see Array#empty?
|
67
|
-
def empty?
|
68
|
-
@list.empty?
|
69
|
-
end
|
70
|
-
|
71
60
|
##
|
72
61
|
# Access audios from playlist.
|
73
|
-
#
|
74
62
|
# @param index [Integer] index of audio (starting from 0).
|
75
|
-
#
|
76
63
|
# @return [Audio, nil] audio or +nil+ if out of range.
|
77
64
|
def [](index)
|
78
65
|
@list[index]
|
79
66
|
end
|
80
|
-
|
67
|
+
##
|
68
|
+
# @return [Boolean] whether no audios loaded for this playlist.
|
69
|
+
def empty?
|
70
|
+
@list.empty?
|
71
|
+
end
|
72
|
+
|
81
73
|
##
|
82
74
|
# Initialize new playlist.
|
83
75
|
#
|
84
76
|
# @param list [Array] list of audios in playlist.
|
85
|
-
#
|
86
|
-
# @
|
87
|
-
# @
|
88
|
-
# @
|
89
|
-
# @
|
90
|
-
# @
|
91
|
-
|
92
|
-
|
93
|
-
raise ArgumentError, "Bad arguments", caller unless list.class == Array
|
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)
|
94
85
|
# Saving list
|
95
86
|
@list = list.dup
|
96
|
-
|
87
|
+
|
97
88
|
# Setting up attributes
|
98
|
-
@id
|
99
|
-
@owner_id
|
100
|
-
@access_hash =
|
101
|
-
@title
|
102
|
-
@subtitle
|
103
|
-
@real_size
|
89
|
+
@id = id
|
90
|
+
@owner_id = owner_id
|
91
|
+
@access_hash = access_hash
|
92
|
+
@title = title
|
93
|
+
@subtitle = subtitle
|
94
|
+
@real_size = real_size
|
104
95
|
end
|
105
|
-
|
106
96
|
end
|
107
|
-
|
108
97
|
end
|
data/lib/vk_music/utility.rb
CHANGED
@@ -1,20 +1,18 @@
|
|
1
|
-
|
1
|
+
require_relative "utility/log"
|
2
2
|
|
3
|
+
module VkMusic
|
3
4
|
##
|
4
5
|
# Utility methods.
|
5
6
|
module Utility
|
6
|
-
|
7
7
|
##
|
8
8
|
# Turn amount of seconds into string.
|
9
|
-
#
|
10
9
|
# @param s [Integer] amount of seconds.
|
11
|
-
#
|
12
10
|
# @return [String] formatted string.
|
13
11
|
def self.format_seconds(s)
|
14
|
-
s = s.to_i # Require integer
|
12
|
+
s = s.to_i # Require integer
|
15
13
|
"#{(s / 60).to_s.rjust(2, "0")}:#{(s % 60).to_s.rjust(2, "0")}";
|
16
14
|
end
|
17
|
-
|
15
|
+
|
18
16
|
##
|
19
17
|
# Guess type of request by from string.
|
20
18
|
#
|
@@ -23,12 +21,9 @@ module VkMusic
|
|
23
21
|
# * +:post+ - if string match post URL.
|
24
22
|
# * +:audios+ - if string match user or group URL.
|
25
23
|
# * +:find+ - in rest of cases.
|
26
|
-
#
|
27
24
|
# @param str [String] request from user for some audios.
|
28
|
-
#
|
29
25
|
# @return [Symbol]
|
30
26
|
def self.guess_request_type(str)
|
31
|
-
# Guess what type of request is this. Returns Symbol: :find, :playlist, :audios
|
32
27
|
case str
|
33
28
|
when Constants::Regex::VK_PLAYLIST_URL_POSTFIX
|
34
29
|
:playlist
|
@@ -43,9 +38,7 @@ module VkMusic
|
|
43
38
|
|
44
39
|
##
|
45
40
|
# Turn hash into URL query string.
|
46
|
-
#
|
47
41
|
# @param hash [Hash]
|
48
|
-
#
|
49
42
|
# @return [String]
|
50
43
|
def self.hash_to_params(hash = {})
|
51
44
|
qs = ""
|
@@ -61,56 +54,9 @@ module VkMusic
|
|
61
54
|
qs
|
62
55
|
end
|
63
56
|
|
64
|
-
##
|
65
|
-
# Utility loggers
|
66
|
-
@@loggers = {
|
67
|
-
debug: Logger.new(STDOUT),
|
68
|
-
warn: Logger.new(STDERR)
|
69
|
-
}
|
70
|
-
@@loggers[:debug].level = Logger::DEBUG
|
71
|
-
@@loggers[:warn].level = Logger::WARN
|
72
|
-
|
73
|
-
##
|
74
|
-
# Send warning.
|
75
|
-
def self.warn(*args)
|
76
|
-
@@loggers[:warn].warn(args.join("\n"))
|
77
|
-
end
|
78
|
-
|
79
|
-
##
|
80
|
-
# Send debug message.
|
81
|
-
def self.debug(*args)
|
82
|
-
@@loggers[:debug].debug(args.join("\n")) if $DEBUG
|
83
|
-
end
|
84
|
-
|
85
|
-
##
|
86
|
-
# Function to turn values into given class unless nil provided
|
87
|
-
#
|
88
|
-
# Supported types:
|
89
|
-
# * +String+
|
90
|
-
# * +Integer+
|
91
|
-
#
|
92
|
-
# @param new_class [Class] class to transform to.
|
93
|
-
# @param obj [Object] object to check.
|
94
|
-
#
|
95
|
-
# @return object transformed to given class or +nil+ if object was +nil+ already.
|
96
|
-
def self.unless_nil_to(new_class, obj)
|
97
|
-
case
|
98
|
-
when obj.nil?
|
99
|
-
nil
|
100
|
-
when String <= new_class
|
101
|
-
obj.to_s
|
102
|
-
when Integer <= new_class
|
103
|
-
obj.to_i
|
104
|
-
else
|
105
|
-
raise ArgumentError, "Bad arguments", caller
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
57
|
##
|
110
58
|
# Get content of text children of provided Node.
|
111
|
-
#
|
112
59
|
# @param node [Nokogiri::Xml::Node]
|
113
|
-
#
|
114
60
|
# @return [String]
|
115
61
|
def self.plain_text(node)
|
116
62
|
node.children.select(&:text?).map(&:text).join ""
|
@@ -118,9 +64,7 @@ module VkMusic
|
|
118
64
|
|
119
65
|
##
|
120
66
|
# Turn human readable track length to its size in seconds.
|
121
|
-
#
|
122
|
-
# @param str [String] string in format "(HH:MM:SS)" or something alike.
|
123
|
-
#
|
67
|
+
# @param str [String] string in format "HH:MM:SS" or something alike (+/d++ Regex selector is used).
|
124
68
|
# @return [Integer] amount of seconds.
|
125
69
|
def self.parse_duration(str)
|
126
70
|
str.scan(/\d+/)
|
@@ -128,7 +72,5 @@ module VkMusic
|
|
128
72
|
.reverse
|
129
73
|
.each_with_index.reduce(0) { |m, arr| m + arr[0] * 60**arr[1] }
|
130
74
|
end
|
131
|
-
|
132
75
|
end
|
133
|
-
|
134
76
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module VkMusic
|
2
|
+
##
|
3
|
+
# @!group Logger
|
4
|
+
|
5
|
+
##
|
6
|
+
# Default logger
|
7
|
+
@@logger = Logger.new(defined?($LOG_FILE) ? $LOGFILE : STDOUT,
|
8
|
+
formatter: Proc.new do |severity, datetime, progname, msg|
|
9
|
+
"[#{datetime}] #{severity}#{progname ? " - #{progname}" : ""}:\t #{msg}\n"
|
10
|
+
end
|
11
|
+
)
|
12
|
+
@@logger.level = $DEBUG ? Logger::DEBUG : Logger::INFO
|
13
|
+
|
14
|
+
##
|
15
|
+
# Setup new logger.
|
16
|
+
# @param new_logger [Logger, nil]
|
17
|
+
def self.logger=(new_logger)
|
18
|
+
@@logger = new_logger
|
19
|
+
end
|
20
|
+
##
|
21
|
+
# Access current logger.
|
22
|
+
# @return [Logger, nil]
|
23
|
+
def self.logger
|
24
|
+
@@logger
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Log message.
|
29
|
+
def self.log(severity, message = nil, progname = nil)
|
30
|
+
@@logger.log(severity, message, progname) if @@logger
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# Log warn message.
|
35
|
+
def self.warn(message = nil, progname = nil)
|
36
|
+
@@logger.log(Logger::WARN, message, progname)
|
37
|
+
end
|
38
|
+
##
|
39
|
+
# Log info message.
|
40
|
+
def self.info(message = nil, progname = nil)
|
41
|
+
@@logger.log(Logger::INFO, message, progname)
|
42
|
+
end
|
43
|
+
##
|
44
|
+
# Log debug message.
|
45
|
+
def self.debug(message = nil, progname = nil)
|
46
|
+
@@logger.log(Logger::DEBUG, message, progname)
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# @!endgroup
|
51
|
+
end
|