vk_music 1.1.1 → 2.0.0
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/README.md +9 -7
- data/Rakefile +5 -0
- data/lib/vk_music.rb +3 -3
- data/lib/vk_music/audio.rb +143 -79
- data/lib/vk_music/client.rb +543 -250
- data/lib/vk_music/constants.rb +78 -33
- data/lib/vk_music/exceptions.rb +14 -17
- data/lib/vk_music/link_decoder.rb +9 -5
- data/lib/vk_music/playlist.rb +47 -36
- data/lib/vk_music/utility.rb +57 -19
- data/test/test_attached_audios_amount.rb +82 -0
- data/test/test_audios.rb +92 -0
- data/test/test_find.rb +43 -0
- data/test/test_from_id.rb +61 -0
- data/test/test_last_post_id.rb +46 -0
- data/test/test_login.rb +1 -1
- data/test/test_page_id.rb +113 -0
- data/test/test_playlist.rb +39 -10
- data/test/test_post.rb +21 -19
- data/test/test_wall.rb +56 -0
- data/vk_music.gemspec +4 -2
- metadata +31 -11
- data/test/test_audios_by_id.rb +0 -49
- data/test/test_get_id.rb +0 -113
- data/test/test_search.rb +0 -30
- data/test/test_user_or_group_audios.rb +0 -77
data/lib/vk_music/constants.rb
CHANGED
@@ -1,46 +1,91 @@
|
|
1
1
|
module VkMusic
|
2
2
|
|
3
|
-
|
3
|
+
##
|
4
|
+
# Constants.
|
4
5
|
module Constants
|
5
|
-
|
6
|
+
|
7
|
+
##
|
8
|
+
# Web user agent.
|
6
9
|
# DEFAULT_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1636.0 Safari/537.36"
|
7
10
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
11
|
+
##
|
12
|
+
# Different URLs
|
13
|
+
module URL
|
14
|
+
|
15
|
+
##
|
16
|
+
# Hash with URLs to VK pages which are used by library.
|
17
|
+
VK = {
|
18
|
+
scheme: "https",
|
19
|
+
host: "m.vk.com",
|
20
|
+
home: "https://m.vk.com",
|
21
|
+
profile: "https://m.vk.com/id0",
|
22
|
+
feed: "https://m.vk.com/feed",
|
23
|
+
audios: "https://m.vk.com/audio",
|
24
|
+
login: "https://m.vk.com/login",
|
25
|
+
login_action: "https://login.vk.com",
|
26
|
+
wall: "https://m.vk.com/wall",
|
27
|
+
audio_unavailable: "https://m.vk.com/mp3/audio_api_unavailable.mp3"
|
28
|
+
}
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Regular expressions
|
34
|
+
module Regex
|
35
|
+
|
36
|
+
##
|
37
|
+
# VK user or group ID.
|
38
|
+
VK_ID_STR = /^-?\d+$/
|
39
|
+
|
40
|
+
##
|
41
|
+
# VK user or group ID.
|
42
|
+
VK_ID = /-?\d+/
|
43
|
+
|
44
|
+
##
|
45
|
+
# VK prefixed user or group ID.
|
46
|
+
VK_PREFIXED_ID_STR = /^(?:id|club|group|public|event)(\d+)$/
|
47
|
+
|
48
|
+
##
|
49
|
+
# VK custom ID regular expression.
|
50
|
+
VK_CUSTOM_ID = /^\w+$/
|
51
|
+
|
52
|
+
##
|
53
|
+
# VK URL regular expression.
|
54
|
+
VK_URL = /(?:https?:\/\/)?(?:m\.|www\.)?vk\.com\/([\w\-]+)/
|
55
|
+
|
56
|
+
##
|
57
|
+
# +href+ attribute with VK ID regular expression.
|
58
|
+
VK_HREF_ID_CONTAINING = /(?:audios|photo|write|owner_id=|friends\?id=)(-?\d+)/
|
59
|
+
|
60
|
+
##
|
61
|
+
# VK audios page regular expression.
|
62
|
+
VK_AUDIOS_URL_POSTFIX = /^audios(-?\d+)$/
|
63
|
+
|
64
|
+
##
|
65
|
+
# Playlist URL regular expression.
|
66
|
+
VK_PLAYLIST_URL_POSTFIX = /.*audio_playlist(-?\d+)_(\d+)(?:(?:(?:&access_hash=)|\/|%2F)([\da-z]+))?/
|
67
|
+
|
68
|
+
##
|
69
|
+
# Post URL regular expression #1.
|
70
|
+
VK_POST_URL_POSTFIX = /.*post(-?\d+)_(\d+)/
|
71
|
+
|
72
|
+
##
|
73
|
+
# Post URL regular expression #2.
|
74
|
+
VK_WALL_URL_POSTFIX = /.*wall(-?\d+)_(\d+)/
|
75
|
+
|
76
|
+
end
|
19
77
|
|
78
|
+
##
|
79
|
+
# Names used in VK login form.
|
20
80
|
VK_LOGIN_FORM_NAMES = {
|
21
|
-
:
|
22
|
-
:
|
81
|
+
username: "email",
|
82
|
+
password: "pass"
|
23
83
|
}
|
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
84
|
|
37
|
-
|
38
|
-
|
85
|
+
##
|
86
|
+
# Maximum amount of audios in VK playlist.
|
87
|
+
MAXIMUM_PLAYLIST_SIZE = 10000
|
39
88
|
|
40
|
-
|
41
|
-
# QUESTION: Should I move ALL the constants (string, regex etc) here? It would make code more flexible, but seems like overkill
|
42
89
|
end
|
43
90
|
|
44
|
-
include Constants
|
45
|
-
|
46
91
|
end
|
data/lib/vk_music/exceptions.rb
CHANGED
@@ -1,33 +1,30 @@
|
|
1
1
|
module VkMusic
|
2
2
|
|
3
|
+
##
|
3
4
|
# Exceptions.
|
4
5
|
module Exceptions
|
6
|
+
|
7
|
+
##
|
5
8
|
# General class for all the errors.
|
6
9
|
class VkMusicError < RuntimeError; end
|
7
10
|
|
11
|
+
##
|
8
12
|
# Failed to login.
|
9
13
|
class LoginError < VkMusicError; end
|
10
|
-
|
11
|
-
# Unable to parse audios from somewhere.
|
12
|
-
class AudiosParseError < VkMusicError; end
|
13
14
|
|
14
|
-
|
15
|
-
|
15
|
+
##
|
16
|
+
# Failed to get request. _Only_ thron when Mechanize failed to load page
|
17
|
+
class RequestError < VkMusicError; end
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
+
##
|
20
|
+
# Parse error. Request is OK, but something went wrong while parsing reply.
|
21
|
+
# It might be missing playlist/post as well.
|
22
|
+
class ParseError < VkMusicError; end
|
19
23
|
|
20
|
-
|
21
|
-
|
24
|
+
##
|
25
|
+
# Unable to login.
|
26
|
+
class LoginError < VkMusicError; end
|
22
27
|
|
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
28
|
end
|
32
29
|
|
33
30
|
include Exceptions
|
@@ -2,9 +2,11 @@ require "execjs"
|
|
2
2
|
|
3
3
|
module VkMusic
|
4
4
|
|
5
|
+
##
|
5
6
|
# Module containing link decoding utilities.
|
6
7
|
module LinkDecoder
|
7
8
|
|
9
|
+
##
|
8
10
|
# JS code which creates function to unmask audio URL.
|
9
11
|
js_code = <<~HEREDOC
|
10
12
|
function vk_unmask_link(link, vk_id) {
|
@@ -87,17 +89,19 @@ module VkMusic
|
|
87
89
|
}
|
88
90
|
HEREDOC
|
89
91
|
|
92
|
+
##
|
93
|
+
# JS context with unmasking link.
|
90
94
|
@@js_context = ExecJS.compile(js_code)
|
91
95
|
|
96
|
+
##
|
92
97
|
# Unmask audio download URL.
|
93
98
|
#
|
94
|
-
#
|
95
|
-
#
|
96
|
-
# * [+client_id+] (+Integer+) - ID of user which got this link. ID is required for decoding.
|
99
|
+
# @param link [String] encoded link to audio. Usually looks like "https://m.vk.com/mp3/audio_api_unavailable.mp3?extra=...".
|
100
|
+
# @param client_id [Integer] ID of user which got this link. ID is required for decoding.
|
97
101
|
#
|
98
|
-
#
|
99
|
-
# * (+String+) - audio download URL, which can be used from current IP.
|
102
|
+
# @return [String] audio download URL, which can be used only from current IP.
|
100
103
|
def self.unmask_link(link, client_id)
|
104
|
+
Utility.debug("Unmasking link.")
|
101
105
|
@@js_context.call("vk_unmask_link", link.to_s, client_id.to_i)
|
102
106
|
end
|
103
107
|
|
data/lib/vk_music/playlist.rb
CHANGED
@@ -1,87 +1,98 @@
|
|
1
1
|
module VkMusic
|
2
2
|
|
3
|
-
|
3
|
+
##
|
4
|
+
# VK playlist.
|
4
5
|
class Playlist
|
5
6
|
include Enumerable
|
6
7
|
|
7
|
-
|
8
|
+
##
|
9
|
+
# @return [Integer, nil] playlist ID.
|
8
10
|
attr_reader :id
|
9
|
-
|
11
|
+
|
12
|
+
##
|
13
|
+
# @return [Integer, nil] owner of playlist ID.
|
10
14
|
attr_reader :owner_id
|
11
|
-
|
15
|
+
|
16
|
+
##
|
17
|
+
# @return [String, nil] access hash which should be part of link for some playlists.
|
12
18
|
attr_reader :access_hash
|
13
|
-
|
19
|
+
|
20
|
+
##
|
21
|
+
# @return [String] playlist title.
|
14
22
|
attr_reader :title
|
15
|
-
|
23
|
+
|
24
|
+
##
|
25
|
+
# @return [String, nil] playlist subtitle. May be empty.
|
16
26
|
attr_reader :subtitle
|
17
27
|
|
18
|
-
|
28
|
+
##
|
29
|
+
# @return [String] playlist description in Russian.
|
19
30
|
def to_s
|
20
|
-
(@subtitle
|
31
|
+
(@subtitle ? "#{@subtitle} - " : "") + "#{@title} (#{self.length} аудиозаписей)"
|
21
32
|
end
|
22
33
|
|
23
|
-
|
34
|
+
##
|
35
|
+
# @return [String] Same to {#to_s}, but also outputs list of audios.
|
24
36
|
def pp
|
25
|
-
"#{to_s}:\n#{@list.map(&:
|
37
|
+
"#{to_s}:\n#{@list.map(&:pp).join("\n")}"
|
26
38
|
end
|
27
39
|
|
28
|
-
|
40
|
+
##
|
41
|
+
# @return [Array<Audio>] Returns duplicate of array of playlist audios.
|
29
42
|
def to_a
|
30
43
|
@list.dup
|
31
44
|
end
|
32
45
|
|
33
|
-
|
46
|
+
##
|
47
|
+
# @see Array#each
|
34
48
|
def each(&block)
|
35
49
|
@list.each(&block)
|
36
50
|
end
|
37
51
|
|
38
|
-
|
52
|
+
##
|
53
|
+
# @see Array#length
|
39
54
|
def length
|
40
55
|
@list.length
|
41
56
|
end
|
42
57
|
alias size length
|
43
58
|
|
59
|
+
##
|
60
|
+
# @see Array#empty?
|
44
61
|
def empty?
|
45
62
|
@list.empty?
|
46
63
|
end
|
47
|
-
# :startdoc:
|
48
64
|
|
49
|
-
|
50
|
-
#
|
51
|
-
# ===== Parameters:
|
52
|
-
# * [+index+] (+Integer+) - index of audio (starting from 0).
|
65
|
+
##
|
66
|
+
# Access audios from playlist.
|
53
67
|
#
|
54
|
-
#
|
55
|
-
#
|
68
|
+
# @param index [Integer] index of audio (starting from 0).
|
69
|
+
#
|
70
|
+
# @return [Audio, nil] audio or +nil+ if out of range.
|
56
71
|
def [](index)
|
57
72
|
@list[index]
|
58
73
|
end
|
59
74
|
|
75
|
+
##
|
60
76
|
# Initialize new playlist.
|
61
77
|
#
|
62
|
-
#
|
63
|
-
# * [+list+] (+Array+) - list of audios in album.
|
64
|
-
# * [+options+] (+Hash+)
|
78
|
+
# @param list [Array] list of audios in playlist.
|
65
79
|
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
# * [+:subtitle+]
|
80
|
+
# @option options [Integer] :id
|
81
|
+
# @option options [Integer] :owner_id
|
82
|
+
# @option options [String] :access_hash
|
83
|
+
# @option options [String] :title
|
84
|
+
# @option options [String] :subtitle
|
72
85
|
def initialize(list, options = {})
|
73
|
-
|
74
|
-
raise ArgumentError, "array of audios must be provided", caller unless list.class == Array
|
75
|
-
|
86
|
+
raise ArgumentError, "Bad arguments", caller unless list.class == Array
|
76
87
|
# Saving list
|
77
88
|
@list = list.dup
|
78
89
|
|
79
90
|
# Setting up attributes
|
80
|
-
@id = options[:id]
|
81
|
-
@owner_id = options[:owner_id]
|
82
|
-
@access_hash = options[:access_hash]
|
91
|
+
@id = Utility.unless_nil_to Integer, options[:id]
|
92
|
+
@owner_id = Utility.unless_nil_to Integer, options[:owner_id]
|
93
|
+
@access_hash = Utility.unless_nil_to String, options[:access_hash]
|
83
94
|
@title = options[:title].to_s
|
84
|
-
@subtitle = options[:subtitle]
|
95
|
+
@subtitle = Utility.unless_nil_to String, options[:subtitle]
|
85
96
|
end
|
86
97
|
|
87
98
|
end
|
data/lib/vk_music/utility.rb
CHANGED
@@ -1,53 +1,55 @@
|
|
1
1
|
require "cgi"
|
2
|
+
require "logger"
|
2
3
|
|
3
4
|
module VkMusic
|
4
5
|
|
6
|
+
##
|
5
7
|
# Utility methods.
|
6
8
|
module Utility
|
7
9
|
|
10
|
+
##
|
8
11
|
# Turn amount of seconds into string.
|
9
12
|
#
|
10
|
-
#
|
11
|
-
# * [+s+] (+Integer+) - amount of seconds.
|
13
|
+
# @param s [Integer] amount of seconds.
|
12
14
|
#
|
13
|
-
#
|
14
|
-
# * (+String+) - formatted string.
|
15
|
+
# @return [String] formatted string.
|
15
16
|
def self.format_seconds(s)
|
16
17
|
s = s.to_i # Require integer
|
17
18
|
"#{(s / 60).to_s.rjust(2, "0")}:#{(s % 60).to_s.rjust(2, "0")}";
|
18
19
|
end
|
19
20
|
|
21
|
+
##
|
20
22
|
# Guess type of request by from string.
|
21
23
|
#
|
22
|
-
#
|
23
|
-
# * [+str+] (+String+) - request from user for some audios.
|
24
|
-
#
|
25
|
-
# ===== Returns:
|
24
|
+
# Possible results:
|
26
25
|
# * +:playlist+ - if string match playlist URL.
|
27
26
|
# * +:post+ - if string match post URL.
|
28
27
|
# * +:audios+ - if string match user or group URL.
|
29
28
|
# * +:find+ - in rest of cases.
|
29
|
+
#
|
30
|
+
# @param str [String] request from user for some audios.
|
31
|
+
#
|
32
|
+
# @return [Symbol]
|
30
33
|
def self.guess_request_type(str)
|
31
34
|
# Guess what type of request is this. Returns Symbol: :find, :playlist, :audios
|
32
35
|
case str
|
33
|
-
when Constants::
|
36
|
+
when Constants::Regex::VK_PLAYLIST_URL_POSTFIX
|
34
37
|
:playlist
|
35
|
-
when Constants::
|
38
|
+
when Constants::Regex::VK_WALL_URL_POSTFIX, Constants::Regex::VK_POST_URL_POSTFIX
|
36
39
|
:post
|
37
|
-
when Constants::
|
40
|
+
when Constants::Regex::VK_URL
|
38
41
|
:audios
|
39
42
|
else
|
40
43
|
:find
|
41
44
|
end
|
42
45
|
end
|
43
46
|
|
47
|
+
##
|
44
48
|
# Turn hash into URL query string.
|
45
49
|
#
|
46
|
-
#
|
47
|
-
# * [+hash+] (+Hash+)
|
50
|
+
# @param hash [Hash]
|
48
51
|
#
|
49
|
-
#
|
50
|
-
# * (+String+)
|
52
|
+
# @return [String]
|
51
53
|
def self.hash_to_params(hash = {})
|
52
54
|
qs = ""
|
53
55
|
hash.each_key do |key|
|
@@ -62,12 +64,48 @@ module VkMusic
|
|
62
64
|
qs
|
63
65
|
end
|
64
66
|
|
67
|
+
##
|
68
|
+
# Utility loggers
|
69
|
+
@@loggers = {
|
70
|
+
debug: Logger.new(STDOUT),
|
71
|
+
warn: Logger.new(STDERR)
|
72
|
+
}
|
73
|
+
@@loggers[:debug].level = Logger::DEBUG
|
74
|
+
@@loggers[:warn].level = Logger::WARN
|
75
|
+
|
76
|
+
##
|
65
77
|
# Send warning.
|
66
78
|
def self.warn(*args)
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
79
|
+
@@loggers[:warn].warn(args.join("\n"))
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# Send debug message.
|
84
|
+
def self.debug(*args)
|
85
|
+
@@loggers[:debug].debug(args.join("\n")) if $DEBUG
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# Function to turn values into given class unless nil provided
|
90
|
+
#
|
91
|
+
# Supported types:
|
92
|
+
# * +String+
|
93
|
+
# * +Integer+
|
94
|
+
#
|
95
|
+
# @param new_class [Class] class to transform to.
|
96
|
+
# @param obj [Object] object to check.
|
97
|
+
#
|
98
|
+
# @return object transformed to given class or +nil+ if object was +nil+ already.
|
99
|
+
def self.unless_nil_to(new_class, obj)
|
100
|
+
case
|
101
|
+
when obj.nil?
|
102
|
+
nil
|
103
|
+
when String <= new_class
|
104
|
+
obj.to_s
|
105
|
+
when Integer <= new_class
|
106
|
+
obj.to_i
|
107
|
+
else
|
108
|
+
raise ArgumentError, "Bad arguments", caller
|
71
109
|
end
|
72
110
|
end
|
73
111
|
|