3 from functools import wraps
4 from flask import Flask, render_template, request, redirect, send_file, Response, jsonify, abort
5 import variables as var
7 from datetime import datetime
12 from werkzeug.utils import secure_filename
20 class ReverseProxied(object):
21 '''Wrap the application in this middleware and configure the
22 front-end server to add these headers, to let you quietly bind
23 this to a URL other than / and to an HTTP scheme that is
24 different than what is used locally.
28 proxy_pass http://192.168.0.1:5001;
29 proxy_set_header Host $host;
30 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
31 proxy_set_header X-Scheme $scheme;
32 proxy_set_header X-Script-Name /myprefix;
35 :param app: the WSGI application
38 def __init__(self, app):
41 def __call__(self, environ, start_response):
42 script_name = environ.get('HTTP_X_SCRIPT_NAME', '')
44 environ['SCRIPT_NAME'] = script_name
45 path_info = environ['PATH_INFO']
46 if path_info.startswith(script_name):
47 environ['PATH_INFO'] = path_info[len(script_name):]
49 scheme = environ.get('HTTP_X_SCHEME', '')
51 environ['wsgi.url_scheme'] = scheme
52 real_ip = environ.get('HTTP_X_REAL_IP', '')
54 environ['REMOTE_ADDR'] = real_ip
55 return self.app(environ, start_response)
59 log = logging.getLogger("bot")
64 web.wsgi_app = ReverseProxied(web.wsgi_app)
66 # https://stackoverflow.com/questions/29725217/password-protect-one-webpage-in-flask-app
68 def check_auth(username, password):
69 """This function is called to check if a username /
70 password combination is valid.
72 return username == var.config.get("webinterface", "user") and password == var.config.get("webinterface", "password")
75 """Sends a 401 response that enables basic auth"""
78 'Could not verify your access level for that URL.\n'
79 'You have to login with proper credentials', 401,
80 {'WWW-Authenticate': 'Basic realm="Login Required"'})
84 def decorated(*args, **kwargs):
86 auth = request.authorization
87 if var.config.getboolean("webinterface", "require_auth") and (not auth or not check_auth(auth.username, auth.password)):
89 log.info("web: Failed login attempt, user: %s" % auth.username)
91 return f(*args, **kwargs)
95 @web.route("/", methods=['GET'])
98 folder_path = var.music_folder
99 files = util.get_recursive_file_list_sorted(var.music_folder)
100 music_library = util.Dir(folder_path)
102 music_library.add_file(file)
105 return render_template('index.html',
107 music_library=music_library,
109 playlist=var.playlist,
111 paused=var.botamusique.is_pause
114 @web.route("/playlist", methods=['GET'])
117 if var.playlist.length() == 0:
118 return jsonify({'items': [render_template('playlist.html',
126 for index, item in enumerate(var.playlist):
127 items.append(render_template('playlist.html',
130 playlist=var.playlist
134 return jsonify({ 'items': items })
137 if (var.playlist.length() > 0):
138 return jsonify({'ver': var.playlist.version,
140 'play': not var.botamusique.is_pause,
141 'mode': var.playlist.mode})
143 return jsonify({'ver': var.playlist.version,
146 'mode': var.playlist.mode})
149 @web.route("/post", methods=['POST'])
154 folder_path = var.music_folder
155 if request.method == 'POST':
157 log.debug("web: Post request from %s: %s" % ( request.remote_addr, str(request.form)))
158 if 'add_file_bottom' in request.form and ".." not in request.form['add_file_bottom']:
159 path = var.music_folder + request.form['add_file_bottom']
160 if os.path.isfile(path):
161 item = {'type': 'file',
162 'path' : request.form['add_file_bottom'],
164 'user' : 'Remote Control'}
165 item = var.playlist.append(util.get_music_tag_info(item))
166 log.info('web: add to playlist(bottom): ' + util.format_debug_song_string(item))
168 elif 'add_file_next' in request.form and ".." not in request.form['add_file_next']:
169 path = var.music_folder + request.form['add_file_next']
170 if os.path.isfile(path):
171 item = {'type': 'file',
172 'path' : request.form['add_file_next'],
174 'user' : 'Remote Control'}
175 item = var.playlist.insert(
176 var.playlist.current_index + 1,
179 log.info('web: add to playlist(next): ' + util.format_debug_song_string(item))
181 elif ('add_folder' in request.form and ".." not in request.form['add_folder']) or ('add_folder_recursively' in request.form and ".." not in request.form['add_folder_recursively']):
183 folder = request.form['add_folder']
185 folder = request.form['add_folder_recursively']
187 if not folder.endswith('/'):
190 print('folder:', folder)
192 if os.path.isdir(var.music_folder + folder):
194 files = util.get_recursive_file_list_sorted(var.music_folder)
195 music_library = util.Dir(folder_path)
197 music_library.add_file(file)
199 if 'add_folder_recursively' in request.form:
200 files = music_library.get_files_recursively(folder)
202 files = music_library.get_files(folder)
204 files = list(map(lambda file:
206 'path': os.path.join(folder, file),
207 'user':'Remote Control'}, files))
209 files = var.playlist.extend(files)
212 log.info("web: add to playlist: %s" % util.format_debug_song_string(file))
215 elif 'add_url' in request.form:
216 music = {'type':'url',
217 'url': request.form['add_url'],
218 'user': 'Remote Control',
219 'ready': 'validation'}
220 music = var.botamusique.validate_music(music)
222 var.playlist.append(music)
223 log.info("web: add to playlist: " + util.format_debug_song_string(music))
224 if var.playlist.length() == 2:
225 # If I am the second item on the playlist. (I am the next one!)
226 var.botamusique.async_download_next()
228 elif 'add_radio' in request.form:
229 music = var.playlist.append({'type': 'radio',
230 'path': request.form['add_radio'],
231 'user': "Remote Control"})
232 log.info("web: add to playlist: " + util.format_debug_song_string(music))
234 elif 'delete_music' in request.form:
235 music = var.playlist[int(request.form['delete_music'])]
236 log.info("web: delete from playlist: " + util.format_debug_song_string(music))
238 if var.playlist.length() >= int(request.form['delete_music']):
239 index = int(request.form['delete_music'])
241 if index == var.playlist.current_index:
242 var.playlist.remove(index)
244 if index < len(var.playlist):
245 if not var.botamusique.is_pause:
246 var.botamusique.interrupt_playing()
247 var.playlist.current_index -= 1
248 # then the bot will move to next item
250 else: # if item deleted is the last item of the queue
251 var.playlist.current_index -= 1
252 if not var.botamusique.is_pause:
253 var.botamusique.interrupt_playing()
255 var.playlist.remove(index)
258 elif 'play_music' in request.form:
259 music = var.playlist[int(request.form['play_music'])]
260 log.info("web: jump to: " + util.format_debug_song_string(music))
262 if len(var.playlist) >= int(request.form['play_music']):
263 var.botamusique.interrupt_playing()
264 var.botamusique.launch_music(int(request.form['play_music']))
266 elif 'delete_music_file' in request.form and ".." not in request.form['delete_music_file']:
267 path = var.music_folder + request.form['delete_music_file']
268 if os.path.isfile(path):
269 log.info("web: delete file " + path)
272 elif 'delete_folder' in request.form and ".." not in request.form['delete_folder']:
273 path = var.music_folder + request.form['delete_folder']
274 if os.path.isdir(path):
275 log.info("web: delete folder " + path)
279 elif 'action' in request.form:
280 action = request.form['action']
281 if action == "randomize":
282 var.botamusique.interrupt_playing()
283 var.playlist.set_mode("random")
284 var.db.set('playlist', 'playback_mode', "random")
285 log.info("web: playback mode changed to random.")
286 if action == "one-shot":
287 var.playlist.set_mode("one-shot")
288 var.db.set('playlist', 'playback_mode', "one-shot")
289 log.info("web: playback mode changed to one-shot.")
290 if action == "repeat":
291 var.playlist.set_mode("repeat")
292 var.db.set('playlist', 'playback_mode', "repeat")
293 log.info("web: playback mode changed to repeat.")
294 elif action == "stop":
295 var.botamusique.stop()
296 elif action == "pause":
297 var.botamusique.pause()
298 elif action == "resume":
299 var.botamusique.resume()
300 elif action == "clear":
301 var.botamusique.clear()
302 elif action == "volume_up":
303 if var.botamusique.volume_set + 0.03 < 1.0:
304 var.botamusique.volume_set = var.botamusique.volume_set + 0.03
306 var.botamusique.volume_set = 1.0
307 var.db.set('bot', 'volume', str(var.botamusique.volume_set))
308 log.info("web: volume up to %d" % (var.botamusique.volume_set * 100))
309 elif action == "volume_down":
310 if var.botamusique.volume_set - 0.03 > 0:
311 var.botamusique.volume_set = var.botamusique.volume_set - 0.03
313 var.botamusique.volume_set = 0
314 var.db.set('bot', 'volume', str(var.botamusique.volume_set))
315 log.info("web: volume up to %d" % (var.botamusique.volume_set * 100))
319 @web.route('/upload', methods=["POST"])
323 files = request.files.getlist("file[]")
325 return redirect("./", code=406)
327 #filename = secure_filename(file.filename).strip()
329 filename = file.filename
331 return redirect("./", code=406)
333 targetdir = request.form['targetdir'].strip()
335 targetdir = 'uploads/'
336 elif '../' in targetdir:
337 return redirect("./", code=406)
339 log.info('web: Uploading file from %s:' % request.remote_addr)
340 log.info('web: - filename: ' + filename)
341 log.info('web: - targetdir: ' + targetdir)
342 log.info('web: - mimetype: ' + file.mimetype)
344 if "audio" in file.mimetype:
345 storagepath = os.path.abspath(os.path.join(var.music_folder, targetdir))
346 print('storagepath:',storagepath)
347 if not storagepath.startswith(os.path.abspath(var.music_folder)):
348 return redirect("./", code=406)
351 os.makedirs(storagepath)
352 except OSError as ee:
353 if ee.errno != errno.EEXIST:
354 return redirect("./", code=500)
356 filepath = os.path.join(storagepath, filename)
357 log.info(' - filepath: ' + filepath)
358 if os.path.exists(filepath):
359 return redirect("./", code=406)
363 return redirect("./", code=409)
365 return redirect("./", code=302)
368 @web.route('/download', methods=["GET"])
372 if 'file' in request.args:
373 requested_file = request.args['file']
374 log.info('web: Download of file %s requested from %s:' % (requested_file, request.remote_addr))
375 if '../' not in requested_file:
376 folder_path = var.music_folder
377 files = util.get_recursive_file_list_sorted(var.music_folder)
379 if requested_file in files:
380 filepath = os.path.join(folder_path, requested_file)
382 return send_file(filepath, as_attachment=True)
383 except Exception as e:
386 elif 'directory' in request.args:
387 requested_dir = request.args['directory']
388 folder_path = var.music_folder
389 requested_dir_fullpath = os.path.abspath(os.path.join(folder_path, requested_dir)) + '/'
390 if requested_dir_fullpath.startswith(folder_path):
391 if os.path.samefile(requested_dir_fullpath, folder_path):
394 prefix = secure_filename(os.path.relpath(requested_dir_fullpath, folder_path))
395 zipfile = util.zipdir(requested_dir_fullpath, prefix)
397 return send_file(zipfile, as_attachment=True)
398 except Exception as e:
402 return redirect("./", code=400)
405 if __name__ == '__main__':
406 web.run(port=8181, host="127.0.0.1")