Module: SpotifyWeb::Schema

Defined in:
lib/spotify_web/schema.rb,
lib/spotify_web/schema/core.pb.rb,
lib/spotify_web/schema/radio.pb.rb,
lib/spotify_web/schema/mercury.pb.rb,
lib/spotify_web/schema/metadata.pb.rb,
lib/spotify_web/schema/playlist4.pb.rb,
lib/spotify_web/schema/socialgraph.pb.rb

Defined Under Namespace

Modules: Mercury, Metadata, Playlist4, Radio, Socialgraph Classes: DecorationData, Toplist

Constant Summary collapse

SERVICES =

The services that are used

%w(
  mercury
  metadata
  playlist4changes
  playlist4content
  playlist4issues
  playlist4meta
  playlist4ops
  radio
  social
  socialgraph
  toplist
)

Class Method Summary collapse

Class Method Details

.build_allObject

Rebuilds all of the Beecake::Message schema definitions in Spotify. Note that this schema is not always kept up-to-date in Spotify – and can sometimes include parser errors. As a result, there may be some manual changes that need to be made once the build is complete.



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/spotify_web/schema.rb', line 27

def build_all
  # Prepare target directories
  proto_dir = File.join(File.dirname(__FILE__), '../../proto')
  schema_dir = File.join(File.dirname(__FILE__), 'schema')
  Dir.mkdir(proto_dir) unless Dir.exists?(proto_dir)

  # Build the proto files
  packages.each do |name, package|
    File.open("#{proto_dir}/#{name}.proto", 'w') {|f| f << package[:content]}
  end

  # Convert each proto file to a Beefcake message
  packages.each do |name, package|
    system(
      {'BEEFCAKE_NAMESPACE' => package[:namespace]},
      "protoc --beefcake_out #{schema_dir} -I #{proto_dir} #{proto_dir}/#{name}.proto"
    )
  end
end

.data_urlObject

Looks up the url representing the current schema for all Spotify services



105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/spotify_web/schema.rb', line 105

def data_url
  # Grab the login init options
  request = Net::HTTP::Get.new('https://play.spotify.com')
  request['User-Agent'] = SpotifyWeb::USER_AGENT
  response = Net::HTTP.start('play.spotify.com', 443, :use_ssl => true) do |http|
    http.request(request)
  end

  json = response.body.match(/Spotify\.Web\.Login\(document, (\{.+\}),[^\}]+\);/)[1]
  options = JSON.parse(json)

  "#{options['corejs']['protoSchemasLocation']}data.xml"
end

.packagesObject

Generates the Protocol Buffer packages based on the current list of Spotify services. This will merge certain services together under the same package if they have the same namespace.



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/spotify_web/schema.rb', line 50

def packages
  packages = {}

  services.values.each do |service|
    namespace = 'SpotifyWeb::Schema'
    if match = service[:content].match(/package spotify\.(.+)\.proto;/)
      name = match[1]
      namespace << '::' + name.split('.').map {|part| part.capitalize} * '::'
    else
      name = 'core'
    end

    if package = packages[name]
      # Package already exists: just append the message definitions
      content = service[:content]
      content = content[content.index('message')..-1]
      package[:content] += "\n#{content}"
    else
      # Create a new package with the entire definition
      packages[name] = {:name => name, :namespace => namespace, :content => service[:content]}
    end
  end

  packages
end

.servicesObject

Gets the collection of services defined in Spotify and the resource definitions associated with them



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/spotify_web/schema.rb', line 78

def services
  services = {}

  # Get the current schema
  request = Net::HTTP::Get.new(data_url)
  request['User-Agent'] = SpotifyWeb::USER_AGENT
  uri = URI(data_url)
  response = Net::HTTP.start(uri.host, uri.port, :use_ssl => true) do |http|
    http.request(request)
  end

  # Parse each service definition
  doc = REXML::Document.new(response.body)
  doc.elements.each('services') do |root|
    root.elements.each do |service|
      name = service.name
      if SERVICES.include?(name)
        content = service.text.strip
        services[name] = {:name => name, :content => content}
      end
    end
  end

  services
end