-
Notifications
You must be signed in to change notification settings - Fork 2
/
spotithin.rb
329 lines (289 loc) · 10.8 KB
/
spotithin.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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
75
76
77
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
#! /usr/bin/env ruby
# -*- coding: utf-8 -*-
# This is a webserver that is suppose to handle incomming requests for Spotiserv
# Requirements
require "thin"
require "active_support/core_ext"
# Web-server that sends requests to the player-server, and sends info the users
class SpotiThin
# Starting server, takes: ip, port and player-server (SpotiPlay)
def initialize (ip, port, sp, command_privileges, user_hash)
puts "Starting thin, webserber on: http://" + ip + ":" + port.to_s
char_map = [(0..9), ('a'..'z'), ('A'..'Z')].map { |i| i.to_a }.flatten
# Registrates the resources for the web-server
# All commands will be responded with an xml-file with information if they were a success or not.
Thin::Server.start(ip, port) do
#use Rack::CommonLogger
#set :logging, false
# Request: /addtrack, to add a song to the playlist
# /addtrack/<uid>/<spotify-uri>
# e.g. http://music.example.com/HXaIBXjI/addtrack/spotify:track:2OcieDHRpksQQpIuQCOwPs
# Response: ok/err/auth/priv
map "/addtrack" do
run AddTrack.new sp, user_hash, command_privileges
end
# ! NOT IMPLEMENTED YET !
# Request: /add_album, to add an entire album to the playlist
# /addalbum/<uid>/<spotify-uri>
# e.g. http://music.example.com/jimmQEEp/addalbum/spotify:album:4UjcMSiyv8QCiZ4O8gpzXS
# Response: ok/err/auth/priv
map "/addalbum" do
run AddAlbum.new sp, user_hash, command_privileges
end
# ! DOES NOT WORK 100% !
# Request: /playlist, set a playlist to play when there is no tracks in the local playlist,
# if no uri is sent, server will remove the current playlist
# /playlist/<uid>/[spotify-uri]
# e.g. http://music.example.com/pqMoOjUe/playlist/spotify:user:badgersweden:playlist:4B95CO3FINr1jH2okewLAY
# e.g. http://music.example.com/pqMoOjUe/playlist
# Response: ok/err/auth/priv
map "/playlist" do
run Playlist.new sp, user_hash, command_privileges
end
# ! NOT IMPLEMENTED YET !
# Request: /remove, remove a song from the playlist if you queued the track or if you are admin
# /remove/<uid>/<index>
# e.g. http://music.example.com/GprIrgRs/remove/23
# Respone: ok/err/auth/priv
map "/remove" do
run Remove.new sp, user_hash, command_privileges
end
# Request: /queue.xml, the ajax request from the browser
# e.g http://music.example.com/queue.xml
# See syntax.txt for xml-syntax
map "/queue.xml" do
run GetQueue.new sp, user_hash, command_privileges
end
# ! DOES NOT WORK !
# /next, skips the current song and starts the next instead.
map "/nexttrack" do
run NextTrack.new sp, user_hash, command_privileges
end
# /phone, sends a html5-page depending on your phone and resolution, the page pulls the xml-file with javascript.
map "/phone" do
run Phone.new
end
# /get, sends files back to the agent, such as css/javascript/images
# e.g. http://music.example.com/get/css/main.css
map "/getfile" do
run GetFile.new
end
# Request: /register, registers the client to the server
# /register/<username>/<android|ios|pc|other>/[key]
# e.g. http://music.example.com/register/thorn/android/muusic
# Response: command:register, response:ok|err, uid:<random-id>, role:<client|admin>
map "/register" do
run Register.new user_hash, command_privileges, char_map
end
# Request: /isreg, checks if the client is regstrated or not
# /isreg/<uid>
# e.g. http://music.example.com/isreg/0jA5Rwg1gNQ7h8Pc
# Response: command:isreg, response:ok|auth|err
map "/isreg" do
run IsReg.new user_hash
end
# Dark theme for fullhd monitors
map "/dark" do
run Dark.new
end
# Light theme for fullhd monitors
map "/light" do
run Light.new
end
# / and /index.html, page with js that loads the xml-file.
map "/" do
run Dark.new
end
end
end
# Superclass to all other webrequests, contains a logger, just run 'log(env)' to print a log-message with timestamp to the terminal.
class WebLog
def log(env)
if env.class == Hash
rm = env.fetch("REQUEST_METHOD")
rp = env.fetch("REQUEST_PATH")
addr = env.fetch("REMOTE_ADDR")
puts "[LOG] #{Time.new.strftime "[%d/%b/%Y %H:%M:%S.%L]"} I #{self.class.to_s} [#{addr} #{rm} #{rp}]"
elsif env.class == String
puts "[LOG] #{Time.new.strftime "[%d/%b/%Y %H:%M:%S.%L]"} I #{self.class.to_s} env"
end
end
end
# Superclass to most other webrequests, contains userchecks and initializer.
class WebRequest < WebLog
def initialize (sp, user_hash, command_privileges)
@sp = sp
@user_hash = user_hash
@command_privileges = command_privileges
end
def can_call? (user_id, user_hash, command, command_privileges)
if user_hash[user_id].nil?
return false
else
user_hash[user_id][:time] = Time.new
return true
end
end
end
# Resource that reads a user and a sporify-uri, and adds this to the playlist of the play-server.
class AddTrack < WebRequest
def call(env)
rp = env["PATH_INFO"]
url = "https://embed.spotify.com/oembed/?url="
puts "\nAddTrack"
puts "Request: #{rp}"
user_id, track_uri = rp.match(/^\/(\w*)\/(.*)/)[1..2]
puts "UID: " + user_id
puts "Track-URI: " + track_uri
if can_call? user_id, @user_hash, self.class.to_s.split(":").last, @command_privileges
track = Hallon::Track.new(track_uri).load
puts "Can call\n"
@sp.add_to_playlist ({ :track => track, :user_id => user_id, :username => @user_hash[user_id][:username],
:art => JSON.parse(Net::HTTP.get_response(URI.parse(url+track_uri)).body)["thumbnail_url"]})
puts track.inspect
xml = {:command => "add", :result => "ok", :track=>track.name, :artist=>track.artist.name,
:album=>track.album.name}.to_xml(:root => "response")
else
puts "Can't call due to auth"
xml = {:command => "add", :response => "auth"}.to_xml(:root => "response")
end
[200, {'Content-Type'=>'text/xml'}, [xml]]
end
end
class NextTrack < WebRequest
def call(env)
@sp.new_next
xml = {:command=>"next"}.to_xml(:root => "response")
[200, {'Content-Type'=>'text/xml'}, [xml]]
end
end
class AddAlbum < WebRequest
def call(env)
rp = env["PATH_INFO"]
puts env["HTTP_USER_AGENT"]
puts "SptiThin.Thin.Add_album, rp: #{rp}"
user, album_uri = rp.match(/^\/(\w*)\/(.*)/)[1..2]
puts "User: " + user
puts "Album: " + album_uri
album_browse = Hallon::Album.new(album_uri).browse.load
for track in album_browse.tracks
@sp.add_to_playlist ({:track => track, :user => user})
end
xml = {:command=>"add_album", :track=>track.name, :artist=>track.artist.name,
:album=>track.album.name, :user=>user}.to_xml(:root => "response")
[200, {'Content-Type'=>'text/xml'}, [xml]]
end
end
class Playlist < WebRequest
def call(env)
puts "SpotiThin.Thin.Playlist"
rp = env["PATH_INFO"]
puts "rp: #{rp}"
playlist_uri = rp.match(/^\/(.*)/)[1]
puts "Playlist: " + playlist_uri
playlist = Hallon::Playlist.new(playlist_uri).load
@sp.set_playlist(playlist)
xml = {:command=>"playlist", :playlist=>playlist.name}.to_xml(:root => "response")
[200, {'Content-Type'=>'text/xml'}, [xml]]
end
end
class Remove < WebRequest
def call(env)
puts "SpotiThin.Thin.Remove"
rp = env["PATH_INFO"]
puts "rp: #{rp}"
index = rp.match(/^\/(.*)/)[1]
puts "Index: " + index
@playlist[index.to_i][:status] = "removed"
xml = {:command=>"remove", :index=>index}.to_xml(:root => "response")
[200, {'Content-Type'=>'text/xml'}, [xml]]
end
end
class GetQueue < WebRequest
def call(env)
xml_array = []
@trunk = 3
if (@sp.index < @trunk)
@trunk = @sp.index
end
list = @sp.playlist.drop(@sp.index-@trunk).take(20)
list.take(20).each do |item|
xml_array.push({ :artist => item[:track].artist.name, :song => item[:track].name,
:album => item[:track].album.name, :status => item[:status],
:username => item[:username], :user_id => item[:user_id], :art => item[:art]})
end
xml = xml_array.to_xml(:root => "root").gsub(" <root", " <item").gsub(" </root", " </item")
[200, {"Content-Type"=>"text/xml"}, [xml]]
end
end
class Phone
def call(env)
agent = env["HTTP_USER_AGENT"]
html = File.open("web/clients/iphone5.html").read
[200, {"Content-Type"=>"text/html"}, [html]]
end
end
class GetFile
def call(env)
rp = env["PATH_INFO"]
file = File.open("web/" + rp).read
[200, {"Content-Type"=>"text/html"}, [file]]
end
end
class Dark
def call(env)
html = File.open("web/clients/dark-fullhd.html").read
[200, {"Content-Type"=>"text/html"}, [html]]
end
end
class Light
def call(env)
html = File.open("web/clients/light-fullhd.html").read
[200, {"Content-Type"=>"text/html"}, [html]]
end
end
class Register < WebLog
def initialize (user_hash, priv_hash, char_map)
@user_hash = user_hash
@priv_hash = priv_hash
@char_map = char_map
end
def call(env)
log(env)
rp = env["PATH_INFO"]
username, device, key = rp.split("/")[1..-1]
xml = {:command => self.class.to_s.split(":").last}
if @priv_hash["clientkey"].empty? or @priv_hash["clientkey"] == key
xml[:user_id] = (0...16).map{@char_map[rand(@char_map.length)]}.join
xml[:role] = "client"
xml[:result] = "ok"
end
if @priv_hash["adminkey"] == key
xml[:user_id] = (0...16).map{@char_map[rand(@char_map.length)]}.join
xml[:role] = "admin"
xml[:result] = "ok"
elsif xml[:result] != "ok"
xml[:result] = "auth"
end
@user_hash[xml[:user_id]] = {username: username, device: device, role: xml[:role], time: Time.new} if xml[:result] == "ok"
[200, {"Content-Type"=>"text/xml"}, [xml.to_xml(:root => "response")]]
end
end
class IsReg
def initialize (user_hash)
@user_hash = user_hash
end
def call(env)
rp = env["PATH_INFO"]
uid = rp.split("/")[1..-1].first
xml = {:command => self.class.to_s.split(":").last}
if @user_hash[uid].nil?
xml[:result] = "auth"
else
@user_hash[uid][:time] = Time.new
xml[:result] = "ok"
end
[200, {"Content-Type"=>"text/xml"}, [xml.to_xml(:root => "response")]]
end
end
end