]> git.0d.be Git - botaradio.git/blob - interface.py
chore: update help
[botaradio.git] / interface.py
1 #!/usr/bin/python3
2
3 from functools import wraps
4 from flask import Flask, render_template, request, redirect, send_file, Response, jsonify, abort
5 import variables as var
6 import util
7 from datetime import datetime
8 import os
9 import os.path
10 import shutil
11 import random
12 from werkzeug.utils import secure_filename
13 import errno
14 import media
15 import logging
16 import time
17 import constants
18
19
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.
25
26     In nginx:
27     location /myprefix {
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;
33         }
34
35     :param app: the WSGI application
36     '''
37
38     def __init__(self, app):
39         self.app = app
40
41     def __call__(self, environ, start_response):
42         script_name = environ.get('HTTP_X_SCRIPT_NAME', '')
43         if 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):]
48
49         scheme = environ.get('HTTP_X_SCHEME', '')
50         if scheme:
51             environ['wsgi.url_scheme'] = scheme
52         real_ip = environ.get('HTTP_X_REAL_IP', '')
53         if real_ip:
54             environ['REMOTE_ADDR'] = real_ip
55         return self.app(environ, start_response)
56
57
58 web = Flask(__name__)
59 log = logging.getLogger("bot")
60
61 def init_proxy():
62     global web
63     if var.is_proxified:
64         web.wsgi_app = ReverseProxied(web.wsgi_app)
65
66 # https://stackoverflow.com/questions/29725217/password-protect-one-webpage-in-flask-app
67
68 def check_auth(username, password):
69     """This function is called to check if a username /
70     password combination is valid.
71     """
72     return username == var.config.get("webinterface", "user") and password == var.config.get("webinterface", "password")
73
74 def authenticate():
75     """Sends a 401 response that enables basic auth"""
76     global log
77     return Response(
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"'})
81
82 def requires_auth(f):
83     @wraps(f)
84     def decorated(*args, **kwargs):
85         global log
86         auth = request.authorization
87         if var.config.getboolean("webinterface", "require_auth") and (not auth or not check_auth(auth.username, auth.password)):
88             if auth:
89                 log.info("web: Failed login attempt, user: %s" % auth.username)
90             return authenticate()
91         return f(*args, **kwargs)
92     return decorated
93
94
95 @web.route("/", methods=['GET'])
96 @requires_auth
97 def index():
98     folder_path = var.music_folder
99     files = util.get_recursive_file_list_sorted(var.music_folder)
100     music_library = util.Dir(folder_path)
101     for file in files:
102         music_library.add_file(file)
103
104
105     return render_template('index.html',
106                            all_files=files,
107                            music_library=music_library,
108                            os=os,
109                            playlist=var.playlist,
110                            user=var.user,
111                            paused=var.botamusique.is_pause
112                            )
113
114 @web.route("/playlist", methods=['GET'])
115 @requires_auth
116 def playlist():
117     if var.playlist.length() == 0:
118         return jsonify({'items': [render_template('playlist.html',
119                                m=False,
120                                index=-1
121                                )]
122                         })
123
124     items = []
125
126     for index, item in enumerate(var.playlist):
127          items.append(render_template('playlist.html',
128                                      index=index,
129                                      m=item,
130                                      playlist=var.playlist
131                                      )
132                      )
133
134     return jsonify({ 'items': items })
135
136 def status():
137     if (var.playlist.length() > 0):
138         return jsonify({'ver': var.playlist.version,
139                         'empty': False,
140                         'play': not var.botamusique.is_pause,
141                         'mode': var.playlist.mode})
142     else:
143         return jsonify({'ver': var.playlist.version,
144                         'empty': True,
145                         'play': False,
146                         'mode': var.playlist.mode})
147
148
149 @web.route("/post", methods=['POST'])
150 @requires_auth
151 def post():
152     global log
153
154     folder_path = var.music_folder
155     if request.method == 'POST':
156         if request.form:
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'],
163                         'title' : '',
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))
167
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'],
173                         'title' : '',
174                         'user' : 'Remote Control'}
175                 item = var.playlist.insert(
176                     var.playlist.current_index + 1,
177                     item
178                 )
179                 log.info('web: add to playlist(next): ' + util.format_debug_song_string(item))
180
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']):
182             try:
183                 folder = request.form['add_folder']
184             except:
185                 folder = request.form['add_folder_recursively']
186
187             if not folder.endswith('/'):
188                 folder += '/'
189
190             print('folder:', folder)
191
192             if os.path.isdir(var.music_folder + folder):
193
194                 files = util.get_recursive_file_list_sorted(var.music_folder)
195                 music_library = util.Dir(folder_path)
196                 for file in files:
197                     music_library.add_file(file)
198
199                 if 'add_folder_recursively' in request.form:
200                     files = music_library.get_files_recursively(folder)
201                 else:
202                     files = music_library.get_files(folder)
203
204                 files = list(map(lambda file:
205                     {'type':'file',
206                      'path': os.path.join(folder, file),
207                      'user':'Remote Control'}, files))
208
209                 files = var.playlist.extend(files)
210
211                 for file in files:
212                     log.info("web: add to playlist: %s" %  util.format_debug_song_string(file))
213
214
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)
221             if 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()
227
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))
233
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))
237
238             if var.playlist.length() >= int(request.form['delete_music']):
239                 index = int(request.form['delete_music'])
240
241                 if index == var.playlist.current_index:
242                     var.playlist.remove(index)
243
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
249
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()
254                 else:
255                     var.playlist.remove(index)
256
257
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))
261
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']))
265
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)
270                 os.remove(path)
271
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)
276                 shutil.rmtree(path)
277                 time.sleep(0.1)
278
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
305                 else:
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
312                 else:
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))
316
317     return status()
318
319 @web.route('/upload', methods=["POST"])
320 def upload():
321     global log
322
323     files = request.files.getlist("file[]")
324     if not files:
325         return redirect("./", code=406)
326
327     #filename = secure_filename(file.filename).strip()
328     for file in files:
329         filename = file.filename
330         if filename == '':
331             return redirect("./", code=406)
332
333         targetdir = request.form['targetdir'].strip()
334         if targetdir == '':
335             targetdir = 'uploads/'
336         elif '../' in targetdir:
337             return redirect("./", code=406)
338
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)
343
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)
349
350             try:
351                 os.makedirs(storagepath)
352             except OSError as ee:
353                 if ee.errno != errno.EEXIST:
354                     return redirect("./", code=500)
355
356             filepath = os.path.join(storagepath, filename)
357             log.info(' - filepath: ' + filepath)
358             if os.path.exists(filepath):
359                 return redirect("./", code=406)
360
361             file.save(filepath)
362         else:
363             return redirect("./", code=409)
364
365     return redirect("./", code=302)
366
367
368 @web.route('/download', methods=["GET"])
369 def download():
370     global log
371
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)
378
379             if requested_file in files:
380                 filepath = os.path.join(folder_path, requested_file)
381                 try:
382                     return send_file(filepath, as_attachment=True)
383                 except Exception as e:
384                     log.exception(e)
385                     abort(404)
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):
392                 prefix = 'all'
393             else:
394                 prefix = secure_filename(os.path.relpath(requested_dir_fullpath, folder_path))
395             zipfile = util.zipdir(requested_dir_fullpath, prefix)
396             try:
397                 return send_file(zipfile, as_attachment=True)
398             except Exception as e:
399                 log.exception(e)
400                 abort(404)
401
402     return redirect("./", code=400)
403
404
405 if __name__ == '__main__':
406     web.run(port=8181, host="127.0.0.1")