]> git.0d.be Git - botaradio.git/blob - interface.py
feat: beautified current song string, fix ytplay index problem
[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 media.radio
16 import logging
17 import time
18 import constants
19
20
21 class ReverseProxied(object):
22     '''Wrap the application in this middleware and configure the
23     front-end server to add these headers, to let you quietly bind
24     this to a URL other than / and to an HTTP scheme that is
25     different than what is used locally.
26
27     In nginx:
28     location /myprefix {
29         proxy_pass http://192.168.0.1:5001;
30         proxy_set_header Host $host;
31         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
32         proxy_set_header X-Scheme $scheme;
33         proxy_set_header X-Script-Name /myprefix;
34         }
35
36     :param app: the WSGI application
37     '''
38
39     def __init__(self, app):
40         self.app = app
41
42     def __call__(self, environ, start_response):
43         script_name = environ.get('HTTP_X_SCRIPT_NAME', '')
44         if script_name:
45             environ['SCRIPT_NAME'] = script_name
46             path_info = environ['PATH_INFO']
47             if path_info.startswith(script_name):
48                 environ['PATH_INFO'] = path_info[len(script_name):]
49
50         scheme = environ.get('HTTP_X_SCHEME', '')
51         if scheme:
52             environ['wsgi.url_scheme'] = scheme
53         real_ip = environ.get('HTTP_X_REAL_IP', '')
54         if real_ip:
55             environ['REMOTE_ADDR'] = real_ip
56         return self.app(environ, start_response)
57
58
59 web = Flask(__name__)
60 log = logging.getLogger("bot")
61
62 def init_proxy():
63     global web
64     if var.is_proxified:
65         web.wsgi_app = ReverseProxied(web.wsgi_app)
66
67 # https://stackoverflow.com/questions/29725217/password-protect-one-webpage-in-flask-app
68
69 def check_auth(username, password):
70     """This function is called to check if a username /
71     password combination is valid.
72     """
73     return username == var.config.get("webinterface", "user") and password == var.config.get("webinterface", "password")
74
75 def authenticate():
76     """Sends a 401 response that enables basic auth"""
77     global log
78     return Response(
79     'Could not verify your access level for that URL.\n'
80     'You have to login with proper credentials', 401,
81     {'WWW-Authenticate': 'Basic realm="Login Required"'})
82
83 def requires_auth(f):
84     @wraps(f)
85     def decorated(*args, **kwargs):
86         global log
87         auth = request.authorization
88         if var.config.getboolean("webinterface", "require_auth") and (not auth or not check_auth(auth.username, auth.password)):
89             if auth:
90                 log.info("web: Failed login attempt, user: %s" % auth.username)
91             return authenticate()
92         return f(*args, **kwargs)
93     return decorated
94
95
96 @web.route("/", methods=['GET'])
97 @requires_auth
98 def index():
99     folder_path = var.music_folder
100     files = util.get_recursive_file_list_sorted(var.music_folder)
101     music_library = util.Dir(folder_path)
102     for file in files:
103         music_library.add_file(file)
104
105
106     return render_template('index.html',
107                            all_files=files,
108                            music_library=music_library,
109                            os=os,
110                            playlist=var.playlist,
111                            user=var.user,
112                            paused=var.botamusique.is_pause
113                            )
114
115 @web.route("/playlist", methods=['GET'])
116 @requires_auth
117 def playlist():
118     if var.playlist.length() == 0:
119         return jsonify({'items': [render_template('playlist.html',
120                                m=False,
121                                index=-1
122                                )]
123                         })
124
125     items = []
126
127     for index, item in enumerate(var.playlist):
128          items.append(render_template('playlist.html',
129                                      index=index,
130                                      m=item,
131                                      playlist=var.playlist
132                                      )
133                      )
134
135     return jsonify({ 'items': items })
136
137 def status():
138     if (var.playlist.length() > 0):
139         return jsonify({'ver': var.playlist.version,
140                         'empty': False,
141                         'play': not var.botamusique.is_pause,
142                         'mode': var.playlist.mode})
143     else:
144         return jsonify({'ver': var.playlist.version,
145                         'empty': True,
146                         'play': False,
147                         'mode': var.playlist.mode})
148
149
150 @web.route("/post", methods=['POST'])
151 @requires_auth
152 def post():
153     global log
154
155     folder_path = var.music_folder
156     if request.method == 'POST':
157         if request.form:
158             log.debug("web: Post request from %s: %s" % ( request.remote_addr, str(request.form)))
159         if 'add_file_bottom' in request.form and ".." not in request.form['add_file_bottom']:
160             path = var.music_folder + request.form['add_file_bottom']
161             if os.path.isfile(path):
162                 item = {'type': 'file',
163                         'path' : request.form['add_file_bottom'],
164                         'title' : '',
165                         'user' : 'Remote Control'}
166                 item = var.playlist.append(util.get_music_tag_info(item))
167                 log.info('web: add to playlist(bottom): ' + util.format_debug_song_string(item))
168
169         elif 'add_file_next' in request.form and ".." not in request.form['add_file_next']:
170             path = var.music_folder + request.form['add_file_next']
171             if os.path.isfile(path):
172                 item = {'type': 'file',
173                         'path' : request.form['add_file_next'],
174                         'title' : '',
175                         'user' : 'Remote Control'}
176                 item = var.playlist.insert(
177                     var.playlist.current_index + 1,
178                     item
179                 )
180                 log.info('web: add to playlist(next): ' + util.format_debug_song_string(item))
181
182         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             try:
184                 folder = request.form['add_folder']
185             except:
186                 folder = request.form['add_folder_recursively']
187
188             if not folder.endswith('/'):
189                 folder += '/'
190
191             print('folder:', folder)
192
193             if os.path.isdir(var.music_folder + folder):
194
195                 files = util.get_recursive_file_list_sorted(var.music_folder)
196                 music_library = util.Dir(folder_path)
197                 for file in files:
198                     music_library.add_file(file)
199
200                 if 'add_folder_recursively' in request.form:
201                     files = music_library.get_files_recursively(folder)
202                 else:
203                     files = music_library.get_files(folder)
204
205                 files = list(map(lambda file:
206                     {'type':'file',
207                      'path': os.path.join(folder, file),
208                      'user':'Remote Control'}, files))
209
210                 files = var.playlist.extend(files)
211
212                 for file in files:
213                     log.info("web: add to playlist: %s" %  util.format_debug_song_string(file))
214
215
216         elif 'add_url' in request.form:
217             music = {'type':'url',
218                                  'url': request.form['add_url'],
219                                  'user': 'Remote Control',
220                                  'ready': 'validation'}
221             music = var.botamusique.validate_music(music)
222             if music:
223                 var.playlist.append(music)
224                 log.info("web: add to playlist: " + util.format_debug_song_string(music))
225                 if var.playlist.length() == 2:
226                     # If I am the second item on the playlist. (I am the next one!)
227                     var.botamusique.async_download_next()
228
229         elif 'add_radio' in request.form:
230             url = request.form['add_radio']
231             music = var.playlist.append({'type': 'radio',
232                                  'url': url,
233                                  'user': "Remote Control"})
234             log.info("web: fetching radio server description")
235             music["name"] = media.radio.get_radio_server_description(url)
236             log.info("web: add to playlist: " + util.format_debug_song_string(music))
237
238         elif 'delete_music' in request.form:
239             music = var.playlist[int(request.form['delete_music'])]
240             log.info("web: delete from playlist: " + util.format_debug_song_string(music))
241
242             if var.playlist.length() >= int(request.form['delete_music']):
243                 index = int(request.form['delete_music'])
244
245                 if index == var.playlist.current_index:
246                     var.playlist.remove(index)
247
248                     if index < len(var.playlist):
249                         if not var.botamusique.is_pause:
250                             var.botamusique.interrupt_playing()
251                             var.playlist.current_index -= 1
252                             # then the bot will move to next item
253
254                     else:  # if item deleted is the last item of the queue
255                         var.playlist.current_index -= 1
256                         if not var.botamusique.is_pause:
257                             var.botamusique.interrupt_playing()
258                 else:
259                     var.playlist.remove(index)
260
261
262         elif 'play_music' in request.form:
263             music = var.playlist[int(request.form['play_music'])]
264             log.info("web: jump to: " + util.format_debug_song_string(music))
265
266             if len(var.playlist) >= int(request.form['play_music']):
267                 var.botamusique.interrupt_playing()
268                 var.botamusique.launch_music(int(request.form['play_music']))
269
270         elif 'delete_music_file' in request.form and ".." not in request.form['delete_music_file']:
271             path = var.music_folder + request.form['delete_music_file']
272             if os.path.isfile(path):
273                 log.info("web: delete file " + path)
274                 os.remove(path)
275
276         elif 'delete_folder' in request.form and ".." not in request.form['delete_folder']:
277             path = var.music_folder + request.form['delete_folder']
278             if os.path.isdir(path):
279                 log.info("web: delete folder " + path)
280                 shutil.rmtree(path)
281                 time.sleep(0.1)
282
283         elif 'action' in request.form:
284             action = request.form['action']
285             if action == "randomize":
286                 var.botamusique.interrupt_playing()
287                 var.playlist.set_mode("random")
288                 var.db.set('playlist', 'playback_mode', "random")
289                 log.info("web: playback mode changed to random.")
290             if action == "one-shot":
291                 var.playlist.set_mode("one-shot")
292                 var.db.set('playlist', 'playback_mode', "one-shot")
293                 log.info("web: playback mode changed to one-shot.")
294             if action == "repeat":
295                 var.playlist.set_mode("repeat")
296                 var.db.set('playlist', 'playback_mode', "repeat")
297                 log.info("web: playback mode changed to repeat.")
298             elif action == "stop":
299                 var.botamusique.stop()
300             elif action == "pause":
301                 var.botamusique.pause()
302             elif action == "resume":
303                 var.botamusique.resume()
304             elif action == "clear":
305                 var.botamusique.clear()
306             elif action == "volume_up":
307                 if var.botamusique.volume_set + 0.03 < 1.0:
308                     var.botamusique.volume_set = var.botamusique.volume_set + 0.03
309                 else:
310                     var.botamusique.volume_set = 1.0
311                 var.db.set('bot', 'volume', str(var.botamusique.volume_set))
312                 log.info("web: volume up to %d" % (var.botamusique.volume_set * 100))
313             elif action == "volume_down":
314                 if var.botamusique.volume_set - 0.03 > 0:
315                     var.botamusique.volume_set = var.botamusique.volume_set - 0.03
316                 else:
317                     var.botamusique.volume_set = 0
318                 var.db.set('bot', 'volume', str(var.botamusique.volume_set))
319                 log.info("web: volume up to %d" % (var.botamusique.volume_set * 100))
320
321     return status()
322
323 @web.route('/upload', methods=["POST"])
324 def upload():
325     global log
326
327     files = request.files.getlist("file[]")
328     if not files:
329         return redirect("./", code=406)
330
331     #filename = secure_filename(file.filename).strip()
332     for file in files:
333         filename = file.filename
334         if filename == '':
335             return redirect("./", code=406)
336
337         targetdir = request.form['targetdir'].strip()
338         if targetdir == '':
339             targetdir = 'uploads/'
340         elif '../' in targetdir:
341             return redirect("./", code=406)
342
343         log.info('web: Uploading file from %s:' % request.remote_addr)
344         log.info('web: - filename: ' + filename)
345         log.info('web: - targetdir: ' + targetdir)
346         log.info('web:  - mimetype: ' + file.mimetype)
347
348         if "audio" in file.mimetype:
349             storagepath = os.path.abspath(os.path.join(var.music_folder, targetdir))
350             print('storagepath:',storagepath)
351             if not storagepath.startswith(os.path.abspath(var.music_folder)):
352                 return redirect("./", code=406)
353
354             try:
355                 os.makedirs(storagepath)
356             except OSError as ee:
357                 if ee.errno != errno.EEXIST:
358                     return redirect("./", code=500)
359
360             filepath = os.path.join(storagepath, filename)
361             log.info(' - filepath: ' + filepath)
362             if os.path.exists(filepath):
363                 return redirect("./", code=406)
364
365             file.save(filepath)
366         else:
367             return redirect("./", code=409)
368
369     return redirect("./", code=302)
370
371
372 @web.route('/download', methods=["GET"])
373 def download():
374     global log
375
376     if 'file' in request.args:
377         requested_file = request.args['file']
378         log.info('web: Download of file %s requested from %s:' % (requested_file, request.remote_addr))
379         if '../' not in requested_file:
380             folder_path = var.music_folder
381             files = util.get_recursive_file_list_sorted(var.music_folder)
382
383             if requested_file in files:
384                 filepath = os.path.join(folder_path, requested_file)
385                 try:
386                     return send_file(filepath, as_attachment=True)
387                 except Exception as e:
388                     log.exception(e)
389                     abort(404)
390     elif 'directory' in request.args:
391         requested_dir = request.args['directory']
392         folder_path = var.music_folder
393         requested_dir_fullpath = os.path.abspath(os.path.join(folder_path, requested_dir)) + '/'
394         if requested_dir_fullpath.startswith(folder_path):
395             if os.path.samefile(requested_dir_fullpath, folder_path):
396                 prefix = 'all'
397             else:
398                 prefix = secure_filename(os.path.relpath(requested_dir_fullpath, folder_path))
399             zipfile = util.zipdir(requested_dir_fullpath, prefix)
400             try:
401                 return send_file(zipfile, as_attachment=True)
402             except Exception as e:
403                 log.exception(e)
404                 abort(404)
405
406     return redirect("./", code=400)
407
408
409 if __name__ == '__main__':
410     web.run(port=8181, host="127.0.0.1")