notu 3.0.0 → 4.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/VERSION +1 -1
- data/lib/notu/api.rb +25 -0
- data/lib/notu/json_document.rb +11 -0
- data/lib/notu/library.rb +8 -37
- data/lib/notu/loved_tracks.rb +18 -10
- data/lib/notu/most_played_tracks.rb +21 -28
- data/lib/notu/recent_tracks.rb +34 -0
- data/lib/notu.rb +3 -5
- data/notu.gemspec +0 -1
- metadata +5 -26
- data/lib/notu/html_document.rb +0 -19
- data/lib/notu/listing.rb +0 -32
- data/lib/notu/played_tracks.rb +0 -26
- data/lib/notu/unknown_username_error.rb +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: efbddd2bb568278da60c542fd1046d39a61d200bf1ca1a20614e7135ac07e66f
|
4
|
+
data.tar.gz: 4c14831c8b1526a402cf47870e64d17fb5e3ffa4b04d0f2ecaefb30d493de4e3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 60c8869371be7e6b80a4b8f8b26ebfa2406f63cb576d7788cf6aeacb6d963c3f4d534455a5ae33bca7ea0a33236a0bb6ab529980fc3697ba302062e764f96d0c
|
7
|
+
data.tar.gz: 8343035fb3d9a5ce7e9bba05676f13b0265ace6f514e2e492aa5280eeae0ddc25990eb553adf39f698f66767f4da2d5a5d55523e6b04d2fef428cb47129ece3b
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
4.0.0
|
data/lib/notu/api.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
module Notu
|
2
|
+
|
3
|
+
class Api
|
4
|
+
|
5
|
+
DEFAULT_KEY = '91f5d6a201de58e0c0a0d858573dddf0'.freeze
|
6
|
+
FORMAT = 'json'.freeze
|
7
|
+
HOST = 'ws.audioscrobbler.com'.freeze
|
8
|
+
VERSION = '2.0'.freeze
|
9
|
+
|
10
|
+
attr_reader :key
|
11
|
+
|
12
|
+
def initialize(key: DEFAULT_KEY)
|
13
|
+
@key = key.try(:squish).presence || raise(Error.new('API key must be specified'))
|
14
|
+
end
|
15
|
+
|
16
|
+
def url(params = {})
|
17
|
+
params = (params || {}).symbolize_keys
|
18
|
+
params.merge!(api_key: key, format: FORMAT)
|
19
|
+
query_string = params.map { |name, value| "#{CGI.escape(name.to_s)}=#{CGI.escape(value.to_s)}" }.join('&')
|
20
|
+
"https://#{HOST}/#{VERSION}?#{query_string}"
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
data/lib/notu/library.rb
CHANGED
@@ -2,14 +2,11 @@ module Notu
|
|
2
2
|
|
3
3
|
class Library
|
4
4
|
|
5
|
-
|
5
|
+
attr_reader :api, :username
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
@semaphore = Mutex.new
|
11
|
-
options = options.symbolize_keys
|
12
|
-
self.username = options[:username]
|
7
|
+
def initialize(username:, api: Api.new)
|
8
|
+
@api = api.presence || raise(Error.new('API must be specified'))
|
9
|
+
@username = username.try(:squish).presence || raise(Error.new('Username must be specified'))
|
13
10
|
end
|
14
11
|
|
15
12
|
def loved_tracks
|
@@ -20,38 +17,12 @@ module Notu
|
|
20
17
|
MostPlayedTracks.new(self, options)
|
21
18
|
end
|
22
19
|
|
23
|
-
def
|
24
|
-
|
25
|
-
end
|
26
|
-
|
27
|
-
def url(options = {})
|
28
|
-
options = options.symbolize_keys
|
29
|
-
path = options[:path].presence
|
30
|
-
query = options[:query].presence
|
31
|
-
query = options[:query].map { |name, value| "#{CGI.escape(name.to_s)}=#{CGI.escape(value.to_s)}" }.join('&') if options[:query].is_a?(Hash)
|
32
|
-
"https://#{HOST}/user/#{username}".tap do |url|
|
33
|
-
if path.present?
|
34
|
-
url << '/' unless path.starts_with?('/')
|
35
|
-
url << path
|
36
|
-
end
|
37
|
-
if query.present?
|
38
|
-
url << '?' << query
|
39
|
-
end
|
40
|
-
end
|
20
|
+
def recent_tracks
|
21
|
+
RecentTracks.new(self)
|
41
22
|
end
|
42
23
|
|
43
|
-
|
44
|
-
|
45
|
-
def username=(value)
|
46
|
-
@semaphore.synchronize do
|
47
|
-
@username = value.to_s.strip.downcase
|
48
|
-
raise UnknownUsernameError.new(value) if username !~ /^[a-z0-9_]+$/
|
49
|
-
begin
|
50
|
-
HtmlDocument.get(url)
|
51
|
-
rescue
|
52
|
-
raise UnknownUsernameError.new(value)
|
53
|
-
end
|
54
|
-
end
|
24
|
+
def url(params = {})
|
25
|
+
api.url((params || {}).symbolize_keys.merge(user: username))
|
55
26
|
end
|
56
27
|
|
57
28
|
end
|
data/lib/notu/loved_tracks.rb
CHANGED
@@ -2,25 +2,33 @@ module Notu
|
|
2
2
|
|
3
3
|
class LovedTracks
|
4
4
|
|
5
|
-
include
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
attr_reader :library
|
8
|
+
|
9
|
+
def initialize(library)
|
10
|
+
raise ArgumentError.new("#{self.class}#library must be a library, #{library.inspect} given") unless library.is_a?(Library)
|
11
|
+
@library = library
|
12
|
+
end
|
6
13
|
|
7
14
|
def each
|
8
15
|
return unless block_given?
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
16
|
+
pages_count = nil
|
17
|
+
page = 1
|
18
|
+
loop do
|
19
|
+
json = JsonDocument.get(library.url(limit: 50, method: 'user.getLovedTracks', page:))
|
20
|
+
pages_count = json['lovedtracks']['@attr']['totalPages'].to_i
|
21
|
+
json['lovedtracks']['track'].each do |track_json|
|
22
|
+
artist = track_json['artist']['name'] || next
|
23
|
+
title = track_json['name'] || next
|
14
24
|
yield(Track.new(artist:, title:))
|
15
25
|
end
|
26
|
+
page += 1
|
27
|
+
break if page > pages_count
|
16
28
|
end
|
17
29
|
nil
|
18
30
|
end
|
19
31
|
|
20
|
-
def path
|
21
|
-
'loved'
|
22
|
-
end
|
23
|
-
|
24
32
|
end
|
25
33
|
|
26
34
|
end
|
@@ -2,51 +2,44 @@ module Notu
|
|
2
2
|
|
3
3
|
class MostPlayedTracks
|
4
4
|
|
5
|
-
include
|
5
|
+
include Enumerable
|
6
6
|
|
7
|
-
PERIODS =
|
8
|
-
'7 days' => 'LAST_7_DAYS',
|
9
|
-
'30 days' => 'LAST_30_DAYS',
|
10
|
-
'90 days' => 'LAST_90_DAYS',
|
11
|
-
'365 days' => 'LAST_365_DAYS',
|
12
|
-
'Overall' => '',
|
13
|
-
}.freeze
|
7
|
+
PERIODS = %w(overall 7day 1month 3month 6month 12month).freeze
|
14
8
|
|
15
|
-
attr_reader :period
|
9
|
+
attr_reader :library, :period
|
16
10
|
|
17
11
|
def initialize(library, options = {})
|
18
|
-
|
19
|
-
|
20
|
-
|
12
|
+
raise ArgumentError.new("#{self.class}#library must be a library, #{library.inspect} given") unless library.is_a?(Library)
|
13
|
+
@library = library
|
14
|
+
options = options.symbolize_keys.reverse_merge(period: PERIODS.first)
|
15
|
+
self.period = options[:period]
|
21
16
|
end
|
22
17
|
|
23
18
|
def each
|
24
19
|
return unless block_given?
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
20
|
+
pages_count = nil
|
21
|
+
page = 1
|
22
|
+
loop do
|
23
|
+
json = JsonDocument.get(library.url(limit: 50, method: 'user.getTopTracks', page:))
|
24
|
+
pages_count = json['toptracks']['@attr']['totalPages'].to_i
|
25
|
+
json['toptracks']['track'].each do |track_json|
|
26
|
+
artist = track_json['artist']['name'] || next
|
27
|
+
title = track_json['name'] || next
|
28
|
+
plays_count = track_json['playcount'] || next
|
31
29
|
yield(Track.new(artist:, plays_count:, title:))
|
32
30
|
end
|
31
|
+
page += 1
|
32
|
+
break if page > pages_count
|
33
33
|
end
|
34
34
|
nil
|
35
35
|
end
|
36
36
|
|
37
|
-
def params
|
38
|
-
{ 'date_preset' => PERIODS[period] }
|
39
|
-
end
|
40
|
-
|
41
|
-
def path
|
42
|
-
'library/tracks'
|
43
|
-
end
|
44
|
-
|
45
37
|
private
|
46
38
|
|
47
39
|
def period=(value)
|
48
|
-
|
49
|
-
|
40
|
+
string_value = value.to_s
|
41
|
+
raise ArgumentError.new("Notu::MostPlayedTracks#period is invalid: #{value.inspect}") unless PERIODS.include?(string_value)
|
42
|
+
@period = string_value
|
50
43
|
end
|
51
44
|
|
52
45
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Notu
|
2
|
+
|
3
|
+
class RecentTracks
|
4
|
+
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
attr_reader :library
|
8
|
+
|
9
|
+
def initialize(library)
|
10
|
+
raise ArgumentError.new("#{self.class}#library must be a library, #{library.inspect} given") unless library.is_a?(Library)
|
11
|
+
@library = library
|
12
|
+
end
|
13
|
+
|
14
|
+
def each
|
15
|
+
return unless block_given?
|
16
|
+
pages_count = nil
|
17
|
+
page = 1
|
18
|
+
loop do
|
19
|
+
json = JsonDocument.get(library.url(limit: 50, method: 'user.getRecentTracks', page:))
|
20
|
+
pages_count = json['recenttracks']['@attr']['totalPages'].to_i
|
21
|
+
json['recenttracks']['track'].each do |track_json|
|
22
|
+
artist = track_json['artist']['#text'] || next
|
23
|
+
title = track_json['name'] || next
|
24
|
+
yield(Track.new(artist:, title:))
|
25
|
+
end
|
26
|
+
page += 1
|
27
|
+
break if page > pages_count
|
28
|
+
end
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
data/lib/notu.rb
CHANGED
@@ -3,7 +3,6 @@ require 'active_support/core_ext'
|
|
3
3
|
require 'byebug' if ENV['DEBUGGER']
|
4
4
|
require 'cgi'
|
5
5
|
require 'net/https'
|
6
|
-
require 'nokogiri'
|
7
6
|
|
8
7
|
lib_path = "#{__dir__}/notu"
|
9
8
|
|
@@ -18,12 +17,11 @@ require "#{lib_path}/error"
|
|
18
17
|
require "#{lib_path}/network_error"
|
19
18
|
require "#{lib_path}/parse_error"
|
20
19
|
|
21
|
-
require "#{lib_path}/
|
20
|
+
require "#{lib_path}/api"
|
22
21
|
require "#{lib_path}/http_download"
|
22
|
+
require "#{lib_path}/json_document"
|
23
23
|
require "#{lib_path}/library"
|
24
|
-
require "#{lib_path}/listing"
|
25
24
|
require "#{lib_path}/loved_tracks"
|
26
25
|
require "#{lib_path}/most_played_tracks"
|
27
|
-
require "#{lib_path}/
|
26
|
+
require "#{lib_path}/recent_tracks"
|
28
27
|
require "#{lib_path}/track"
|
29
|
-
require "#{lib_path}/unknown_username_error"
|
data/notu.gemspec
CHANGED
@@ -16,7 +16,6 @@ Gem::Specification.new do |s|
|
|
16
16
|
s.required_ruby_version = '>= 3.1.0'
|
17
17
|
|
18
18
|
s.add_dependency 'activesupport', '>= 7.0.0', '< 8.0.0'
|
19
|
-
s.add_dependency 'nokogiri', '>= 1.6.0', '< 1.14.0'
|
20
19
|
|
21
20
|
s.add_development_dependency 'byebug', '>= 3.2.0', '< 12.0.0'
|
22
21
|
s.add_development_dependency 'rake', '>= 10.3.0', '< 14.0.0'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: notu
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexis Toulotte
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-01-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -30,26 +30,6 @@ dependencies:
|
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
32
|
version: 8.0.0
|
33
|
-
- !ruby/object:Gem::Dependency
|
34
|
-
name: nokogiri
|
35
|
-
requirement: !ruby/object:Gem::Requirement
|
36
|
-
requirements:
|
37
|
-
- - ">="
|
38
|
-
- !ruby/object:Gem::Version
|
39
|
-
version: 1.6.0
|
40
|
-
- - "<"
|
41
|
-
- !ruby/object:Gem::Version
|
42
|
-
version: 1.14.0
|
43
|
-
type: :runtime
|
44
|
-
prerelease: false
|
45
|
-
version_requirements: !ruby/object:Gem::Requirement
|
46
|
-
requirements:
|
47
|
-
- - ">="
|
48
|
-
- !ruby/object:Gem::Version
|
49
|
-
version: 1.6.0
|
50
|
-
- - "<"
|
51
|
-
- !ruby/object:Gem::Version
|
52
|
-
version: 1.14.0
|
53
33
|
- !ruby/object:Gem::Dependency
|
54
34
|
name: byebug
|
55
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -220,18 +200,17 @@ files:
|
|
220
200
|
- README.mdown
|
221
201
|
- VERSION
|
222
202
|
- lib/notu.rb
|
203
|
+
- lib/notu/api.rb
|
223
204
|
- lib/notu/error.rb
|
224
|
-
- lib/notu/html_document.rb
|
225
205
|
- lib/notu/http_download.rb
|
206
|
+
- lib/notu/json_document.rb
|
226
207
|
- lib/notu/library.rb
|
227
|
-
- lib/notu/listing.rb
|
228
208
|
- lib/notu/loved_tracks.rb
|
229
209
|
- lib/notu/most_played_tracks.rb
|
230
210
|
- lib/notu/network_error.rb
|
231
211
|
- lib/notu/parse_error.rb
|
232
|
-
- lib/notu/
|
212
|
+
- lib/notu/recent_tracks.rb
|
233
213
|
- lib/notu/track.rb
|
234
|
-
- lib/notu/unknown_username_error.rb
|
235
214
|
- notu.gemspec
|
236
215
|
homepage: https://github.com/alexistoulotte/notu
|
237
216
|
licenses:
|
data/lib/notu/html_document.rb
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
module Notu
|
2
|
-
|
3
|
-
module HtmlDocument
|
4
|
-
|
5
|
-
def self.get(url, options = {})
|
6
|
-
parse(HttpDownload.get(url, options))
|
7
|
-
end
|
8
|
-
|
9
|
-
def self.parse(data)
|
10
|
-
data = data.gsub(/ /i, ' ').gsub(/\s+/, ' ')
|
11
|
-
document = Nokogiri::HTML.parse(data, nil, 'UTF-8')
|
12
|
-
raise ParseError.new('Invalid HTML document') if (document / 'head').empty?
|
13
|
-
document
|
14
|
-
end
|
15
|
-
private_class_method :parse
|
16
|
-
|
17
|
-
end
|
18
|
-
|
19
|
-
end
|
data/lib/notu/listing.rb
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
module Notu
|
2
|
-
|
3
|
-
module Listing
|
4
|
-
|
5
|
-
include Enumerable
|
6
|
-
|
7
|
-
attr_reader :library
|
8
|
-
|
9
|
-
def initialize(library)
|
10
|
-
raise ArgumentError.new("#{self.class}#library must be a library, #{library.inspect} given") unless library.is_a?(Library)
|
11
|
-
@library = library
|
12
|
-
end
|
13
|
-
|
14
|
-
def page_urls
|
15
|
-
(1..pages_count).map do |index|
|
16
|
-
library.url(path:, query: params.merge('page' => index))
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def pages_count
|
21
|
-
document = HtmlDocument.get(library.url(path:, query: params))
|
22
|
-
[1, (document / 'ul.pagination-list li.pagination-page').text.split(/\s+/).map(&:to_i)].flatten.compact.max
|
23
|
-
end
|
24
|
-
|
25
|
-
def params
|
26
|
-
# to be overriden
|
27
|
-
{}
|
28
|
-
end
|
29
|
-
|
30
|
-
end
|
31
|
-
|
32
|
-
end
|
data/lib/notu/played_tracks.rb
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
module Notu
|
2
|
-
|
3
|
-
class PlayedTracks
|
4
|
-
|
5
|
-
include Listing
|
6
|
-
|
7
|
-
def each
|
8
|
-
return unless block_given?
|
9
|
-
page_urls.each do |url|
|
10
|
-
document = HtmlDocument.get(url)
|
11
|
-
(document / 'table.chartlist tbody tr').each do |element|
|
12
|
-
artist = (element / 'td.chartlist-artist a').first.try(:text) || next
|
13
|
-
title = (element / 'td.chartlist-name a').first.try(:text) || next
|
14
|
-
yield(Track.new(artist:, title:))
|
15
|
-
end
|
16
|
-
end
|
17
|
-
nil
|
18
|
-
end
|
19
|
-
|
20
|
-
def path
|
21
|
-
'library'
|
22
|
-
end
|
23
|
-
|
24
|
-
end
|
25
|
-
|
26
|
-
end
|