vk_music 1.1.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,46 +1,91 @@
1
1
  module VkMusic
2
2
 
3
- # Constants
3
+ ##
4
+ # Constants.
4
5
  module Constants
5
- # Web
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
- 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
- }
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
- :username => "email",
22
- :password => "pass",
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
- # Post
38
- POST_URL_REGEX = /.*wall(-?\d+)_(\d+)/
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
@@ -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
- # Unable to find playlist or got permission error.
15
- class PlaylistParseError < AudiosParseError; end
15
+ ##
16
+ # Failed to get request. _Only_ thron when Mechanize failed to load page
17
+ class RequestError < VkMusicError; end
16
18
 
17
- # Unable to load or parse audios section from json.
18
- class AudiosSectionParseError < AudiosParseError; end
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
- # Unable to load or parse all of audios by ids.
21
- class ReloadAudiosParseError < AudiosParseError; end
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
- # ===== 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.
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
- # ===== Returns:
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
 
@@ -1,87 +1,98 @@
1
1
  module VkMusic
2
2
 
3
- # VK playlist. Extended with Enumerable.
3
+ ##
4
+ # VK playlist.
4
5
  class Playlist
5
6
  include Enumerable
6
7
 
7
- # Playlist id.
8
+ ##
9
+ # @return [Integer, nil] playlist ID.
8
10
  attr_reader :id
9
- # Owner of playlist.
11
+
12
+ ##
13
+ # @return [Integer, nil] owner of playlist ID.
10
14
  attr_reader :owner_id
11
- # Access hash which should be part of link for some playlists.
15
+
16
+ ##
17
+ # @return [String, nil] access hash which should be part of link for some playlists.
12
18
  attr_reader :access_hash
13
- # Playlist title.
19
+
20
+ ##
21
+ # @return [String] playlist title.
14
22
  attr_reader :title
15
- # Playlist subtitle. May be empty.
23
+
24
+ ##
25
+ # @return [String, nil] playlist subtitle. May be empty.
16
26
  attr_reader :subtitle
17
27
 
18
- # Return string describing playlist in Russian.
28
+ ##
29
+ # @return [String] playlist description in Russian.
19
30
  def to_s
20
- (@subtitle.empty? ? "" : "#{@subtitle} - ") + "#{@title} (#{self.length} аудиозаписей)"
31
+ (@subtitle ? "#{@subtitle} - " : "") + "#{@title} (#{self.length} аудиозаписей)"
21
32
  end
22
33
 
23
- # Same to +to_s+, but also outputs list of audios.
34
+ ##
35
+ # @return [String] Same to {#to_s}, but also outputs list of audios.
24
36
  def pp
25
- "#{to_s}:\n#{@list.map(&:to_s).join("\n")}"
37
+ "#{to_s}:\n#{@list.map(&:pp).join("\n")}"
26
38
  end
27
39
 
28
- # Returns audios array.
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
- # :nodoc:
46
+ ##
47
+ # @see Array#each
34
48
  def each(&block)
35
49
  @list.each(&block)
36
50
  end
37
51
 
38
- # :stopdoc:
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
- # Access to audios from playlist.
50
- #
51
- # ===== Parameters:
52
- # * [+index+] (+Integer+) - index of audio (starting from 0).
65
+ ##
66
+ # Access audios from playlist.
53
67
  #
54
- # ===== Returns:
55
- # * (+Audio+, +nil+) - audio or +nil+ if out of range.
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
- # ===== Parameters:
63
- # * [+list+] (+Array+) - list of audios in album.
64
- # * [+options+] (+Hash+)
78
+ # @param list [Array] list of audios in playlist.
65
79
  #
66
- # ===== Options:
67
- # * [+:id+]
68
- # * [+:owner_id+]
69
- # * [+:access_hash+]
70
- # * [+:title+]
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
- # Arguments check
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].to_s
81
- @owner_id = options[:owner_id].to_s
82
- @access_hash = options[:access_hash].to_s
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].to_s
95
+ @subtitle = Utility.unless_nil_to String, options[:subtitle]
85
96
  end
86
97
 
87
98
  end
@@ -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
- # ===== Parameters:
11
- # * [+s+] (+Integer+) - amount of seconds.
13
+ # @param s [Integer] amount of seconds.
12
14
  #
13
- # ===== Returns:
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
- # ===== Parameters:
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::PLAYLIST_URL_REGEX
36
+ when Constants::Regex::VK_PLAYLIST_URL_POSTFIX
34
37
  :playlist
35
- when Constants::POST_URL_REGEX
38
+ when Constants::Regex::VK_WALL_URL_POSTFIX, Constants::Regex::VK_POST_URL_POSTFIX
36
39
  :post
37
- when Constants::VK_URL_REGEX
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
- # ===== Parameters:
47
- # * [+hash+] (+Hash+)
50
+ # @param hash [Hash]
48
51
  #
49
- # ===== Returns:
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
- if defined?(Warning.warn)
68
- Warning.warn args.join("\n")
69
- else
70
- STDERR.puts "Warning:", *args
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