spotify_web 0.0.1
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 +7 -0
- data/.gitignore +8 -0
- data/.rspec +2 -0
- data/.yardopts +7 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +3 -0
- data/LICENSE +20 -0
- data/README.md +309 -0
- data/Rakefile +13 -0
- data/examples/playlists.rb +14 -0
- data/lib/spotify_web.rb +105 -0
- data/lib/spotify_web/album.rb +58 -0
- data/lib/spotify_web/artist.rb +54 -0
- data/lib/spotify_web/assertions.rb +36 -0
- data/lib/spotify_web/authorized_user.rb +119 -0
- data/lib/spotify_web/client.rb +369 -0
- data/lib/spotify_web/connection.rb +162 -0
- data/lib/spotify_web/error.rb +13 -0
- data/lib/spotify_web/event.rb +95 -0
- data/lib/spotify_web/handler.rb +74 -0
- data/lib/spotify_web/loggable.rb +13 -0
- data/lib/spotify_web/playlist.rb +48 -0
- data/lib/spotify_web/resource.rb +309 -0
- data/lib/spotify_web/resource_collection.rb +99 -0
- data/lib/spotify_web/restriction.rb +32 -0
- data/lib/spotify_web/schema.rb +120 -0
- data/lib/spotify_web/schema/core.pb.rb +31 -0
- data/lib/spotify_web/schema/mercury.pb.rb +66 -0
- data/lib/spotify_web/schema/metadata.pb.rb +257 -0
- data/lib/spotify_web/schema/playlist4.pb.rb +461 -0
- data/lib/spotify_web/schema/radio.pb.rb +110 -0
- data/lib/spotify_web/schema/socialgraph.pb.rb +106 -0
- data/lib/spotify_web/song.rb +58 -0
- data/lib/spotify_web/user.rb +7 -0
- data/lib/spotify_web/version.rb +9 -0
- data/lib/tasks/spotify_web.rake +1 -0
- data/lib/tasks/spotify_web.rb +9 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/spotify_web_spec.rb +4 -0
- data/spotify_web.gemspec +27 -0
- metadata +216 -0
@@ -0,0 +1,99 @@
|
|
1
|
+
module SpotifyWeb
|
2
|
+
# Represents a collection of related resources (of the same type) on
|
3
|
+
# Spotify
|
4
|
+
class ResourceCollection < Array
|
5
|
+
# Initializes this collection with the given resources. This will continue
|
6
|
+
# to call the superclass's constructor with any additional arguments that
|
7
|
+
# get specified.
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
def initialize(client, *args)
|
11
|
+
@client = client
|
12
|
+
@loaded = false
|
13
|
+
super(*args)
|
14
|
+
|
15
|
+
# Load all resources if attempted for a single one
|
16
|
+
each do |resource|
|
17
|
+
resource.metadata = lambda { load unless loaded? }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Loads the attributes for these resources from Spotify. By default this is
|
22
|
+
# a no-op and just marks the resource as loaded.
|
23
|
+
#
|
24
|
+
# @return [true]
|
25
|
+
def load
|
26
|
+
if count == 1
|
27
|
+
# Remove the metadata loader / load directly from the resource
|
28
|
+
first.metadata = nil
|
29
|
+
first.load
|
30
|
+
else
|
31
|
+
# Load each resource's metadata
|
32
|
+
metadata.each_with_index do |result, index|
|
33
|
+
self[index].metadata = result
|
34
|
+
end
|
35
|
+
|
36
|
+
true
|
37
|
+
end
|
38
|
+
|
39
|
+
@loaded = true
|
40
|
+
end
|
41
|
+
alias :reload :load
|
42
|
+
|
43
|
+
# Determines whether the current collection has been loaded from Spotify.
|
44
|
+
#
|
45
|
+
# @return [Boolean] +true+ if the collection has been loaded, otherwise +false+
|
46
|
+
def loaded?
|
47
|
+
@loaded
|
48
|
+
end
|
49
|
+
|
50
|
+
# Looks up the metadata associated with all of the resources in this
|
51
|
+
# collection.
|
52
|
+
#
|
53
|
+
# @api private
|
54
|
+
# @return [Array] The resulting metadata for each resource
|
55
|
+
def metadata
|
56
|
+
if any? && metadata_schema
|
57
|
+
response = api('request',
|
58
|
+
:uri => "hm://metadata/#{resource_name}s",
|
59
|
+
:batch => true,
|
60
|
+
:payload => map {|resource| {:uri => resource.metadata_uri}},
|
61
|
+
:response_schema => metadata_schema
|
62
|
+
)
|
63
|
+
response['result']
|
64
|
+
else
|
65
|
+
[]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
# The types of resources being stored in this collection. This should only
|
71
|
+
# be called when there are actually resources available.
|
72
|
+
def resource_class
|
73
|
+
if any?
|
74
|
+
first.class
|
75
|
+
else
|
76
|
+
raise ArgumentError, 'Cannot determine resource class on empty collection'
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# The Spotify name for the resource type
|
81
|
+
def resource_name
|
82
|
+
resource_class.resource_name
|
83
|
+
end
|
84
|
+
|
85
|
+
# The response schema used for looking up metadata associated with the
|
86
|
+
# resources
|
87
|
+
def metadata_schema
|
88
|
+
resource_class.metadata_schema
|
89
|
+
end
|
90
|
+
|
91
|
+
# The client that all APIs filter through
|
92
|
+
attr_reader :client
|
93
|
+
|
94
|
+
# Runs the given API command on the client.
|
95
|
+
def api(command, options = {})
|
96
|
+
client.api(command, options)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'spotify_web/resource'
|
2
|
+
|
3
|
+
module SpotifyWeb
|
4
|
+
# Represents a country-based restriction on Spotify data
|
5
|
+
class Restriction < Resource
|
6
|
+
# The countries allowed to access the data
|
7
|
+
# @return [String]
|
8
|
+
attribute :countries_allowed do |countries|
|
9
|
+
countries.scan(/.{2}/)
|
10
|
+
end
|
11
|
+
|
12
|
+
# The countries forbidden to access the data
|
13
|
+
# @return [String]
|
14
|
+
attribute :countries_forbidden do |countries|
|
15
|
+
countries.scan(/.{2}/)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Whether the user is permitted to access data based on this restriction
|
19
|
+
# @return [Boolean]
|
20
|
+
def permitted?
|
21
|
+
country = client.user.settings['country']
|
22
|
+
|
23
|
+
if countries_allowed
|
24
|
+
countries_allowed.include?(country)
|
25
|
+
elsif countries_forbidden
|
26
|
+
!countries_forbidden.include?(country)
|
27
|
+
else
|
28
|
+
true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'json'
|
3
|
+
require 'rexml/document'
|
4
|
+
|
5
|
+
module SpotifyWeb
|
6
|
+
module Schema
|
7
|
+
# The services that are used
|
8
|
+
SERVICES = %w(
|
9
|
+
mercury
|
10
|
+
metadata
|
11
|
+
playlist4changes
|
12
|
+
playlist4content
|
13
|
+
playlist4issues
|
14
|
+
playlist4meta
|
15
|
+
playlist4ops
|
16
|
+
radio
|
17
|
+
social
|
18
|
+
socialgraph
|
19
|
+
toplist
|
20
|
+
)
|
21
|
+
|
22
|
+
class << self
|
23
|
+
# Rebuilds all of the Beecake::Message schema definitions in Spotify.
|
24
|
+
# Note that this schema is not always kept up-to-date in Spotify --
|
25
|
+
# and can sometimes include parser errors. As a result, there may be
|
26
|
+
# some manual changes that need to be made once the build is complete.
|
27
|
+
def build_all
|
28
|
+
# Prepare target directories
|
29
|
+
proto_dir = File.join(File.dirname(__FILE__), '../../proto')
|
30
|
+
schema_dir = File.join(File.dirname(__FILE__), 'schema')
|
31
|
+
Dir.mkdir(proto_dir) unless Dir.exists?(proto_dir)
|
32
|
+
|
33
|
+
# Build the proto files
|
34
|
+
packages.each do |name, package|
|
35
|
+
File.open("#{proto_dir}/#{name}.proto", 'w') {|f| f << package[:content]}
|
36
|
+
end
|
37
|
+
|
38
|
+
# Convert each proto file to a Beefcake message
|
39
|
+
packages.each do |name, package|
|
40
|
+
system(
|
41
|
+
{'BEEFCAKE_NAMESPACE' => package[:namespace]},
|
42
|
+
"protoc --beefcake_out #{schema_dir} -I #{proto_dir} #{proto_dir}/#{name}.proto"
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Generates the Protocol Buffer packages based on the current list of
|
48
|
+
# Spotify services. This will merge certain services together under
|
49
|
+
# the same package if they have the same namespace.
|
50
|
+
def packages
|
51
|
+
packages = {}
|
52
|
+
|
53
|
+
services.values.each do |service|
|
54
|
+
namespace = 'SpotifyWeb::Schema'
|
55
|
+
if match = service[:content].match(/package spotify\.(.+)\.proto;/)
|
56
|
+
name = match[1]
|
57
|
+
namespace << '::' + name.split('.').map {|part| part.capitalize} * '::'
|
58
|
+
else
|
59
|
+
name = 'core'
|
60
|
+
end
|
61
|
+
|
62
|
+
if package = packages[name]
|
63
|
+
# Package already exists: just append the message definitions
|
64
|
+
content = service[:content]
|
65
|
+
content = content[content.index('message')..-1]
|
66
|
+
package[:content] += "\n#{content}"
|
67
|
+
else
|
68
|
+
# Create a new package with the entire definition
|
69
|
+
packages[name] = {:name => name, :namespace => namespace, :content => service[:content]}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
packages
|
74
|
+
end
|
75
|
+
|
76
|
+
# Gets the collection of services defined in Spotify and the resource
|
77
|
+
# definitions associated with them
|
78
|
+
def services
|
79
|
+
services = {}
|
80
|
+
|
81
|
+
# Get the current schema
|
82
|
+
request = Net::HTTP::Get.new(data_url)
|
83
|
+
request['User-Agent'] = SpotifyWeb::USER_AGENT
|
84
|
+
uri = URI(data_url)
|
85
|
+
response = Net::HTTP.start(uri.host, uri.port, :use_ssl => true) do |http|
|
86
|
+
http.request(request)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Parse each service definition
|
90
|
+
doc = REXML::Document.new(response.body)
|
91
|
+
doc.elements.each('services') do |root|
|
92
|
+
root.elements.each do |service|
|
93
|
+
name = service.name
|
94
|
+
if SERVICES.include?(name)
|
95
|
+
content = service.text.strip
|
96
|
+
services[name] = {:name => name, :content => content}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
services
|
102
|
+
end
|
103
|
+
|
104
|
+
# Looks up the url representing the current schema for all Spotify services
|
105
|
+
def data_url
|
106
|
+
# Grab the login init options
|
107
|
+
request = Net::HTTP::Get.new('https://play.spotify.com')
|
108
|
+
request['User-Agent'] = SpotifyWeb::USER_AGENT
|
109
|
+
response = Net::HTTP.start('play.spotify.com', 443, :use_ssl => true) do |http|
|
110
|
+
http.request(request)
|
111
|
+
end
|
112
|
+
|
113
|
+
json = response.body.match(/Spotify\.Web\.Login\(document, (\{.+\}),[^\}]+\);/)[1]
|
114
|
+
options = JSON.parse(json)
|
115
|
+
|
116
|
+
"#{options['corejs']['protoSchemasLocation']}data.xml"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
## Generated from core.proto for
|
2
|
+
require "beefcake"
|
3
|
+
|
4
|
+
module SpotifyWeb
|
5
|
+
module Schema
|
6
|
+
|
7
|
+
class Toplist
|
8
|
+
include Beefcake::Message
|
9
|
+
end
|
10
|
+
|
11
|
+
class DecorationData
|
12
|
+
include Beefcake::Message
|
13
|
+
end
|
14
|
+
|
15
|
+
class Toplist
|
16
|
+
repeated :items, :string, 1
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
class DecorationData
|
21
|
+
optional :username, :string, 1
|
22
|
+
optional :full_name, :string, 2
|
23
|
+
optional :image_url, :string, 3
|
24
|
+
optional :large_image_url, :string, 5
|
25
|
+
optional :first_name, :string, 6
|
26
|
+
optional :last_name, :string, 7
|
27
|
+
optional :facebook_uid, :string, 8
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
## Generated from mercury.proto for spotify.mercury.proto
|
2
|
+
require "beefcake"
|
3
|
+
|
4
|
+
module SpotifyWeb
|
5
|
+
module Schema
|
6
|
+
module Mercury
|
7
|
+
|
8
|
+
class UserField
|
9
|
+
include Beefcake::Message
|
10
|
+
end
|
11
|
+
|
12
|
+
class MercuryMultiGetRequest
|
13
|
+
include Beefcake::Message
|
14
|
+
end
|
15
|
+
|
16
|
+
class MercuryMultiGetReply
|
17
|
+
include Beefcake::Message
|
18
|
+
end
|
19
|
+
|
20
|
+
class MercuryRequest
|
21
|
+
include Beefcake::Message
|
22
|
+
end
|
23
|
+
|
24
|
+
class MercuryReply
|
25
|
+
include Beefcake::Message
|
26
|
+
|
27
|
+
module CachePolicy
|
28
|
+
CACHE_NO = 1
|
29
|
+
CACHE_PRIVATE = 2
|
30
|
+
CACHE_PUBLIC = 3
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class MercuryMultiGetRequest
|
35
|
+
repeated :request, MercuryRequest, 1
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
class MercuryMultiGetReply
|
40
|
+
repeated :reply, MercuryReply, 1
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
class MercuryRequest
|
45
|
+
optional :uri, :string, 1
|
46
|
+
optional :content_type, :string, 2
|
47
|
+
optional :method, :bytes, 3
|
48
|
+
optional :status_code, :sint32, 4
|
49
|
+
optional :source, :string, 5
|
50
|
+
repeated :user_fields, UserField, 6
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
class MercuryReply
|
55
|
+
optional :status_code, :sint32, 1
|
56
|
+
optional :status_message, :string, 2
|
57
|
+
optional :cache_policy, MercuryReply::CachePolicy, 3
|
58
|
+
optional :ttl, :sint32, 4
|
59
|
+
optional :etag, :bytes, 5
|
60
|
+
optional :content_type, :string, 6
|
61
|
+
optional :body, :bytes, 7
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,257 @@
|
|
1
|
+
## Generated from metadata.proto for spotify.metadata.proto
|
2
|
+
require "beefcake"
|
3
|
+
|
4
|
+
module SpotifyWeb
|
5
|
+
module Schema
|
6
|
+
module Metadata
|
7
|
+
|
8
|
+
class TopTracks
|
9
|
+
include Beefcake::Message
|
10
|
+
end
|
11
|
+
|
12
|
+
class ActivityPeriod
|
13
|
+
include Beefcake::Message
|
14
|
+
end
|
15
|
+
|
16
|
+
class Artist
|
17
|
+
include Beefcake::Message
|
18
|
+
end
|
19
|
+
|
20
|
+
class AlbumGroup
|
21
|
+
include Beefcake::Message
|
22
|
+
end
|
23
|
+
|
24
|
+
class Date
|
25
|
+
include Beefcake::Message
|
26
|
+
end
|
27
|
+
|
28
|
+
class Album
|
29
|
+
include Beefcake::Message
|
30
|
+
|
31
|
+
module Type
|
32
|
+
ALBUM = 1
|
33
|
+
SINGLE = 2
|
34
|
+
COMPILATION = 3
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Track
|
39
|
+
include Beefcake::Message
|
40
|
+
end
|
41
|
+
|
42
|
+
class Image
|
43
|
+
include Beefcake::Message
|
44
|
+
|
45
|
+
module Size
|
46
|
+
DEFAULT = 0
|
47
|
+
SMALL = 1
|
48
|
+
LARGE = 2
|
49
|
+
XLARGE = 3
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class ImageGroup
|
54
|
+
include Beefcake::Message
|
55
|
+
end
|
56
|
+
|
57
|
+
class Biography
|
58
|
+
include Beefcake::Message
|
59
|
+
end
|
60
|
+
|
61
|
+
class Disc
|
62
|
+
include Beefcake::Message
|
63
|
+
end
|
64
|
+
|
65
|
+
class Copyright
|
66
|
+
include Beefcake::Message
|
67
|
+
|
68
|
+
module Type
|
69
|
+
P = 0
|
70
|
+
C = 1
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class Restriction
|
75
|
+
include Beefcake::Message
|
76
|
+
|
77
|
+
module Catalogue
|
78
|
+
AD = 0
|
79
|
+
SUBSCRIPTION = 1
|
80
|
+
SHUFFLE = 3
|
81
|
+
end
|
82
|
+
|
83
|
+
module Type
|
84
|
+
STREAMING = 0
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class SalePeriod
|
89
|
+
include Beefcake::Message
|
90
|
+
end
|
91
|
+
|
92
|
+
class ExternalId
|
93
|
+
include Beefcake::Message
|
94
|
+
end
|
95
|
+
|
96
|
+
class AudioFile
|
97
|
+
include Beefcake::Message
|
98
|
+
|
99
|
+
module Format
|
100
|
+
OGG_VORBIS_96 = 0
|
101
|
+
OGG_VORBIS_160 = 1
|
102
|
+
OGG_VORBIS_320 = 2
|
103
|
+
MP3_256 = 3
|
104
|
+
MP3_320 = 4
|
105
|
+
MP3_160 = 5
|
106
|
+
MP3_96 = 6
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
class TopTracks
|
111
|
+
optional :country, :string, 1
|
112
|
+
repeated :track, Track, 2
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
class ActivityPeriod
|
117
|
+
optional :start_year, :sint32, 1
|
118
|
+
optional :end_year, :sint32, 2
|
119
|
+
optional :decade, :sint32, 3
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
class Artist
|
124
|
+
optional :gid, :bytes, 1
|
125
|
+
optional :name, :string, 2
|
126
|
+
optional :popularity, :sint32, 3
|
127
|
+
repeated :top_track, TopTracks, 4
|
128
|
+
repeated :album_group, AlbumGroup, 5
|
129
|
+
repeated :single_group, AlbumGroup, 6
|
130
|
+
repeated :compilation_group, AlbumGroup, 7
|
131
|
+
repeated :appears_on_group, AlbumGroup, 8
|
132
|
+
repeated :genre, :string, 9
|
133
|
+
repeated :external_id, ExternalId, 10
|
134
|
+
repeated :portrait, Image, 11
|
135
|
+
repeated :biography, Biography, 12
|
136
|
+
repeated :activity_period, ActivityPeriod, 13
|
137
|
+
repeated :restriction, Restriction, 14
|
138
|
+
repeated :related, Artist, 15
|
139
|
+
optional :is_portrait_album_cover, :bool, 16
|
140
|
+
optional :portrait_group, ImageGroup, 17
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
class AlbumGroup
|
145
|
+
repeated :album, Album, 1
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
class Date
|
150
|
+
optional :year, :sint32, 1
|
151
|
+
optional :month, :sint32, 2
|
152
|
+
optional :day, :sint32, 3
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
class Album
|
157
|
+
optional :gid, :bytes, 1
|
158
|
+
optional :name, :string, 2
|
159
|
+
repeated :artist, Artist, 3
|
160
|
+
optional :type, Album::Type, 4
|
161
|
+
optional :label, :string, 5
|
162
|
+
optional :date, Date, 6
|
163
|
+
optional :popularity, :sint32, 7
|
164
|
+
repeated :genre, :string, 8
|
165
|
+
repeated :cover, Image, 9
|
166
|
+
repeated :external_id, ExternalId, 10
|
167
|
+
repeated :disc, Disc, 11
|
168
|
+
repeated :review, :string, 12
|
169
|
+
repeated :copyright, Copyright, 13
|
170
|
+
repeated :restriction, Restriction, 14
|
171
|
+
repeated :related, Album, 15
|
172
|
+
repeated :sale_period, SalePeriod, 16
|
173
|
+
optional :cover_group, ImageGroup, 17
|
174
|
+
end
|
175
|
+
|
176
|
+
|
177
|
+
class Track
|
178
|
+
optional :gid, :bytes, 1
|
179
|
+
optional :name, :string, 2
|
180
|
+
optional :album, Album, 3
|
181
|
+
repeated :artist, Artist, 4
|
182
|
+
optional :number, :sint32, 5
|
183
|
+
optional :disc_number, :sint32, 6
|
184
|
+
optional :duration, :sint32, 7
|
185
|
+
optional :popularity, :sint32, 8
|
186
|
+
optional :explicit, :bool, 9
|
187
|
+
repeated :external_id, ExternalId, 10
|
188
|
+
repeated :restriction, Restriction, 11
|
189
|
+
repeated :file, AudioFile, 12
|
190
|
+
repeated :alternative, Track, 13
|
191
|
+
repeated :sale_period, SalePeriod, 14
|
192
|
+
repeated :preview, AudioFile, 15
|
193
|
+
end
|
194
|
+
|
195
|
+
|
196
|
+
class Image
|
197
|
+
optional :file_id, :bytes, 1
|
198
|
+
optional :size, Image::Size, 2
|
199
|
+
optional :width, :sint32, 3
|
200
|
+
optional :height, :sint32, 4
|
201
|
+
end
|
202
|
+
|
203
|
+
|
204
|
+
class ImageGroup
|
205
|
+
repeated :image, Image, 1
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
class Biography
|
210
|
+
optional :text, :string, 1
|
211
|
+
repeated :portrait, Image, 2
|
212
|
+
repeated :portrait_group, ImageGroup, 3
|
213
|
+
end
|
214
|
+
|
215
|
+
|
216
|
+
class Disc
|
217
|
+
optional :number, :sint32, 1
|
218
|
+
optional :name, :string, 2
|
219
|
+
repeated :track, Track, 3
|
220
|
+
end
|
221
|
+
|
222
|
+
|
223
|
+
class Copyright
|
224
|
+
optional :type, Copyright::Type, 1
|
225
|
+
optional :text, :string, 2
|
226
|
+
end
|
227
|
+
|
228
|
+
|
229
|
+
class Restriction
|
230
|
+
repeated :catalogue, Restriction::Catalogue, 1
|
231
|
+
optional :countries_allowed, :string, 2
|
232
|
+
optional :countries_forbidden, :string, 3
|
233
|
+
optional :type, Restriction::Type, 4
|
234
|
+
end
|
235
|
+
|
236
|
+
|
237
|
+
class SalePeriod
|
238
|
+
repeated :restriction, Restriction, 1
|
239
|
+
optional :start, Date, 2
|
240
|
+
optional :end, Date, 3
|
241
|
+
end
|
242
|
+
|
243
|
+
|
244
|
+
class ExternalId
|
245
|
+
optional :type, :string, 1
|
246
|
+
optional :id, :string, 2
|
247
|
+
end
|
248
|
+
|
249
|
+
|
250
|
+
class AudioFile
|
251
|
+
optional :file_id, :bytes, 1
|
252
|
+
optional :format, AudioFile::Format, 2
|
253
|
+
end
|
254
|
+
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|