]> git.0d.be Git - botaradio.git/blob - command.py
feat: add ytquery and ytplay
[botaradio.git] / command.py
1 # coding=utf-8
2 import logging
3 import os.path
4 import pymumble.pymumble_py3 as pymumble
5 import re
6
7 import constants
8 import media.file
9 import media.playlist
10 import media.radio
11 import media.system
12 import media.url
13 import util
14 import variables as var
15 from librb import radiobrowser
16 from database import Database
17
18 log = logging.getLogger("bot")
19
20 def register_all_commands(bot):
21     bot.register_command(constants.commands('joinme'), cmd_joinme)
22     bot.register_command(constants.commands('user_ban'), cmd_user_ban)
23     bot.register_command(constants.commands('user_unban'), cmd_user_unban)
24     bot.register_command(constants.commands('url_ban'), cmd_url_ban)
25     bot.register_command(constants.commands('url_unban'), cmd_url_unban)
26     bot.register_command(constants.commands('play'), cmd_play)
27     bot.register_command(constants.commands('pause'), cmd_pause)
28     bot.register_command(constants.commands('play_file'), cmd_play_file)
29     bot.register_command(constants.commands('play_file_match'), cmd_play_file_match)
30     bot.register_command(constants.commands('play_url'), cmd_play_url)
31     bot.register_command(constants.commands('play_playlist'), cmd_play_playlist)
32     bot.register_command(constants.commands('play_radio'), cmd_play_radio)
33     bot.register_command(constants.commands('rb_query'), cmd_rb_query)
34     bot.register_command(constants.commands('rb_play'), cmd_rb_play)
35     bot.register_command(constants.commands('yt_query'), cmd_yt_query)
36     bot.register_command(constants.commands('yt_play'), cmd_yt_play)
37     bot.register_command(constants.commands('help'), cmd_help)
38     bot.register_command(constants.commands('stop'), cmd_stop)
39     bot.register_command(constants.commands('clear'), cmd_clear)
40     bot.register_command(constants.commands('kill'), cmd_kill)
41     bot.register_command(constants.commands('update'), cmd_update)
42     bot.register_command(constants.commands('stop_and_getout'), cmd_stop_and_getout)
43     bot.register_command(constants.commands('volume'), cmd_volume)
44     bot.register_command(constants.commands('ducking'), cmd_ducking)
45     bot.register_command(constants.commands('ducking_threshold'), cmd_ducking_threshold)
46     bot.register_command(constants.commands('ducking_volume'), cmd_ducking_volume)
47     bot.register_command(constants.commands('current_music'), cmd_current_music)
48     bot.register_command(constants.commands('skip'), cmd_skip)
49     bot.register_command(constants.commands('remove'), cmd_remove)
50     bot.register_command(constants.commands('list_file'), cmd_list_file)
51     bot.register_command(constants.commands('queue'), cmd_queue)
52     bot.register_command(constants.commands('random'), cmd_random)
53     bot.register_command(constants.commands('repeat'), cmd_repeat)
54     bot.register_command(constants.commands('mode'), cmd_mode)
55     bot.register_command(constants.commands('drop_database'), cmd_drop_database)
56
57 def send_multi_lines(bot, lines, text):
58     global log
59
60     msg = ""
61     br = ""
62     for newline in lines:
63         msg += br
64         br = "<br>"
65         if len(msg) + len(newline) > 5000:
66             bot.send_msg(msg, text)
67             msg = ""
68         msg += newline
69
70     bot.send_msg(msg, text)
71
72 # ---------------- Commands ------------------
73
74
75 def cmd_joinme(bot, user, text, command, parameter):
76     global log
77
78     bot.mumble.users.myself.move_in(
79         bot.mumble.users[text.actor]['channel_id'], token=parameter)
80
81
82 def cmd_user_ban(bot, user, text, command, parameter):
83     global log
84
85     if bot.is_admin(user):
86         if parameter:
87             bot.mumble.users[text.actor].send_text_message(util.user_ban(parameter))
88         else:
89             bot.mumble.users[text.actor].send_text_message(util.get_user_ban())
90     else:
91         bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin'))
92     return
93
94
95 def cmd_user_unban(bot, user, text, command, parameter):
96     global log
97
98     if bot.is_admin(user):
99         if parameter:
100             bot.mumble.users[text.actor].send_text_message(util.user_unban(parameter))
101     else:
102         bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin'))
103     return
104
105
106 def cmd_url_ban(bot, user, text, command, parameter):
107     global log
108
109     if bot.is_admin(user):
110         if parameter:
111             bot.mumble.users[text.actor].send_text_message(util.url_ban(util.get_url_from_input(parameter)))
112         else:
113             bot.mumble.users[text.actor].send_text_message(util.get_url_ban())
114     else:
115         bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin'))
116     return
117
118
119 def cmd_url_unban(bot, user, text, command, parameter):
120     global log
121
122     if bot.is_admin(user):
123         if parameter:
124             bot.mumble.users[text.actor].send_text_message(util.url_unban(util.get_url_from_input(parameter)))
125     else:
126         bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin'))
127     return
128
129
130 def cmd_play(bot, user, text, command, parameter):
131     global log
132
133     if var.playlist.length() > 0:
134         if parameter:
135             if parameter.isdigit() and int(parameter) > 0 and int(parameter) <= len(var.playlist):
136                 bot.interrupt_playing()
137                 bot.launch_music(int(parameter) - 1)
138             else:
139                 bot.send_msg(constants.strings('invalid_index', index=parameter), text)
140
141         elif bot.is_pause:
142             bot.resume()
143         else:
144             bot.send_msg(util.format_current_playing(), text)
145     else:
146         bot.is_pause = False
147         bot.send_msg(constants.strings('queue_empty'), text)
148
149
150 def cmd_pause(bot, user, text, command, parameter):
151     global log
152
153     bot.pause()
154     bot.send_msg(constants.strings('paused'))
155
156
157 def cmd_play_file(bot, user, text, command, parameter):
158     global log
159
160     # if parameter is {index}
161     if parameter.isdigit():
162         files = util.get_recursive_file_list_sorted(var.music_folder)
163         if int(parameter) < len(files):
164             filename = files[int(parameter)].replace(var.music_folder, '')
165             music = {'type': 'file',
166                      'path': filename,
167                      'user': user}
168             music = var.playlist.append(music)
169             log.info("cmd: add to playlist: " + util.format_debug_song_string(music))
170             bot.send_msg(constants.strings('file_added', item=util.format_song_string(music)), text)
171
172     # if parameter is {path}
173     else:
174         # sanitize "../" and so on
175         path = os.path.abspath(os.path.join(var.music_folder, parameter))
176         if not path.startswith(os.path.abspath(var.music_folder)):
177             bot.send_msg(constants.strings('no_file'), text)
178             return
179
180         if os.path.isfile(path):
181             music = {'type': 'file',
182                      'path': parameter,
183                      'user': user}
184             music = var.playlist.append(music)
185             log.info("cmd: add to playlist: " + util.format_debug_song_string(music))
186             bot.send_msg(constants.strings('file_added', item=util.format_song_string(music)), text)
187             return
188
189         # if parameter is {folder}
190         elif os.path.isdir(path):
191             if parameter != '.' and parameter != './':
192                 if not parameter.endswith("/"):
193                     parameter += "/"
194             else:
195                 parameter = ""
196
197             files = util.get_recursive_file_list_sorted(var.music_folder)
198             music_library = util.Dir(var.music_folder)
199             for file in files:
200                 music_library.add_file(file)
201
202             files = music_library.get_files(parameter)
203             msgs = [constants.strings('multiple_file_added')]
204             count = 0
205
206             for file in files:
207                 count += 1
208                 music = {'type': 'file',
209                          'path': file,
210                          'user': user}
211                 music = var.playlist.append(music)
212                 log.info("cmd: add to playlist: " + util.format_debug_song_string(music))
213                 msgs.append("{} ({})".format(music['title'], music['path']))
214
215             if count != 0:
216                 send_multi_lines(bot, msgs, text)
217             else:
218                 bot.send_msg(constants.strings('no_file'), text)
219
220         else:
221             # try to do a partial match
222             files = util.get_recursive_file_list_sorted(var.music_folder)
223             matches = [(index, file) for index, file in enumerate(files) if parameter.lower() in file.lower()]
224             if len(matches) == 0:
225                 bot.send_msg(constants.strings('no_file'), text)
226             elif len(matches) == 1:
227                 music = {'type': 'file',
228                          'path': matches[0][1],
229                          'user': user}
230                 music = var.playlist.append(music)
231                 log.info("cmd: add to playlist: " + util.format_debug_song_string(music))
232                 bot.send_msg(constants.strings('file_added', item=util.format_song_string(music)), text)
233             else:
234                 msgs = [ constants.strings('multiple_matches')]
235                 for match in matches:
236                     msgs.append("<b>{:0>3d}</b> - {:s}".format(match[0], match[1]))
237                 send_multi_lines(bot, msgs, text)
238
239
240 def cmd_play_file_match(bot, user, text, command, parameter):
241     global log
242
243     music_folder = var.music_folder
244     if parameter:
245         files = util.get_recursive_file_list_sorted(music_folder)
246         msgs = [ constants.strings('multiple_file_added')]
247         count = 0
248         try:
249             for file in files:
250                 match = re.search(parameter, file)
251                 if match:
252                     count += 1
253                     music = {'type': 'file',
254                              'path': file,
255                              'user': user}
256                     music = var.playlist.append(music)
257                     log.info("cmd: add to playlist: " + util.format_debug_song_string(music))
258
259                     msgs.append("{} ({})".format(music['title'], music['path']))
260
261             if count != 0:
262                 send_multi_lines(bot, msgs, text)
263             else:
264                 bot.send_msg(constants.strings('no_file'), text)
265
266         except re.error as e:
267             msg = constants.strings('wrong_pattern', error=str(e))
268             bot.send_msg(msg, text)
269     else:
270         bot.send_msg(constants.strings('bad_parameter', command))
271
272
273 def cmd_play_url(bot, user, text, command, parameter):
274     global log
275
276     music = {'type': 'url',
277              # grab the real URL
278              'url': util.get_url_from_input(parameter),
279              'user': user,
280              'ready': 'validation'}
281
282     music = bot.validate_music(music)
283     if music:
284         music = var.playlist.append(music)
285         log.info("cmd: add to playlist: " + util.format_debug_song_string(music))
286         bot.send_msg(constants.strings('file_added', item=util.format_song_string(music)), text)
287         if var.playlist.length() == 2:
288             # If I am the second item on the playlist. (I am the next one!)
289             bot.async_download_next()
290     else:
291         bot.send_msg(constants.strings('unable_download'), text)
292
293
294 def cmd_play_playlist(bot, user, text, command, parameter):
295     global log
296
297     offset = 0  # if you want to start the playlist at a specific index
298     try:
299         offset = int(parameter.split(" ")[-1])
300     except ValueError:
301         pass
302
303     url = util.get_url_from_input(parameter)
304     log.debug("cmd: fetching media info from playlist url %s" % url)
305     items = media.playlist.get_playlist_info(url=url, start_index=offset, user=user)
306     if len(items) > 0:
307         var.playlist.extend(items)
308         for music in items:
309             log.info("cmd: add to playlist: " + util.format_debug_song_string(music))
310
311
312 def cmd_play_radio(bot, user, text, command, parameter):
313     global log
314
315     if not parameter:
316         all_radio = var.config.items('radio')
317         msg = constants.strings('preconfigurated_radio')
318         for i in all_radio:
319             comment = ""
320             if len(i[1].split(maxsplit=1)) == 2:
321                 comment = " - " + i[1].split(maxsplit=1)[1]
322             msg += "<br />" + i[0] + comment
323         bot.send_msg(msg, text)
324     else:
325         if var.config.has_option('radio', parameter):
326             parameter = var.config.get('radio', parameter)
327             parameter = parameter.split()[0]
328         url = util.get_url_from_input(parameter)
329         if url:
330             music = {'type': 'radio',
331                      'url': url,
332                      'user': user}
333             var.playlist.append(music)
334             log.info("cmd: add to playlist: " + util.format_debug_song_string(music))
335             bot.async_download_next()
336         else:
337             bot.send_msg(constants.strings('bad_url'))
338
339
340 def cmd_rb_query(bot, user, text, command, parameter):
341     global log
342
343     log.info('cmd: Querying radio stations')
344     if not parameter:
345         log.debug('rbquery without parameter')
346         msg = constants.strings('rb_query_empty')
347         bot.send_msg(msg, text)
348     else:
349         log.debug('cmd: Found query parameter: ' + parameter)
350         # bot.send_msg('Searching for stations - this may take some seconds...', text)
351         rb_stations = radiobrowser.getstations_byname(parameter)
352         msg = constants.strings('rb_query_result')
353         msg += '\n<table><tr><th>!rbplay ID</th><th>Station Name</th><th>Genre</th><th>Codec/Bitrate</th><th>Country</th></tr>'
354         if not rb_stations:
355             log.debug('cmd: No matches found for rbquery ' + parameter)
356             bot.send_msg('Radio-Browser found no matches for ' + parameter, text)
357         else:
358             for s in rb_stations:
359                 stationid = s['id']
360                 stationname = s['stationname']
361                 country = s['country']
362                 codec = s['codec']
363                 bitrate = s['bitrate']
364                 genre = s['genre']
365                 # msg += f'<tr><td>{stationid}</td><td>{stationname}</td><td>{genre}</td><td>{codec}/{bitrate}</td><td>{country}</td></tr>'
366                 msg += '<tr><td>%s</td><td>%s</td><td>%s</td><td>%s/%s</td><td>%s</td></tr>' % (
367                     stationid, stationname, genre, codec, bitrate, country)
368             msg += '</table>'
369             # Full message as html table
370             if len(msg) <= 5000:
371                 bot.send_msg(msg, text)
372             # Shorten message if message too long (stage I)
373             else:
374                 log.debug('Result too long stage I')
375                 msg = constants.strings('rb_query_result') + ' (shortened L1)'
376                 msg += '\n<table><tr><th>!rbplay ID</th><th>Station Name</th></tr>'
377                 for s in rb_stations:
378                     stationid = s['id']
379                     stationname = s['stationname']
380                     # msg += f'<tr><td>{stationid}</td><td>{stationname}</td>'
381                     msg += '<tr><td>%s</td><td>%s</td>' % (stationid, stationname)
382                 msg += '</table>'
383                 if len(msg) <= 5000:
384                     bot.send_msg(msg, text)
385                 # Shorten message if message too long (stage II)
386                 else:
387                     log.debug('Result too long stage II')
388                     msg = constants.strings('rb_query_result') + ' (shortened L2)'
389                     msg += '!rbplay ID - Station Name'
390                     for s in rb_stations:
391                         stationid = s['id']
392                         stationname = s['stationname'][:12]
393                         # msg += f'{stationid} - {stationname}'
394                         msg += '%s - %s' % (stationid, stationname)
395                     if len(msg) <= 5000:
396                         bot.send_msg(msg, text)
397                     # Message still too long
398                     else:
399                         bot.send_msg('Query result too long to post (> 5000 characters), please try another query.',
400                                      text)
401
402
403 def cmd_rb_play(bot, user, text, command, parameter):
404     global log
405
406     log.debug('cmd: Play a station by ID')
407     if not parameter:
408         log.debug('rbplay without parameter')
409         msg = constants.strings('rb_play_empty')
410         bot.send_msg(msg, text)
411     else:
412         log.debug('cmd: Retreiving url for station ID ' + parameter)
413         rstation = radiobrowser.getstationname_byid(parameter)
414         stationname = rstation[0]['name']
415         country = rstation[0]['country']
416         codec = rstation[0]['codec']
417         bitrate = rstation[0]['bitrate']
418         genre = rstation[0]['tags']
419         homepage = rstation[0]['homepage']
420         msg = 'Radio station added to playlist:'
421         # msg += '<table><tr><th>ID</th><th>Station Name</th><th>Genre</th><th>Codec/Bitrate</th><th>Country</th><th>Homepage</th></tr>' + \
422         #       f'<tr><td>{parameter}</td><td>{stationname}</td><td>{genre}</td><td>{codec}/{bitrate}</td><td>{country}</td><td>{homepage}</td></tr></table>'
423         msg += '<table><tr><th>ID</th><th>Station Name</th><th>Genre</th><th>Codec/Bitrate</th><th>Country</th><th>Homepage</th></tr>' + \
424                '<tr><td>%s</td><td>%s</td><td>%s</td><td>%s/%s</td><td>%s</td><td>%s</td></tr></table>' \
425                % (parameter, stationname, genre, codec, bitrate, country, homepage)
426         log.debug('cmd: Added station to playlist %s' % stationname)
427         bot.send_msg(msg, text)
428         url = radiobrowser.geturl_byid(parameter)
429         if url != "-1":
430             log.info('cmd: Found url: ' + url)
431             music = {'type': 'radio',
432                      'title': stationname,
433                      'artist': homepage,
434                      'url': url,
435                      'user': user}
436             var.playlist.append(music)
437             log.info("cmd: add to playlist: " + util.format_debug_song_string(music))
438             bot.async_download_next()
439         else:
440             log.info('cmd: No playable url found.')
441             msg += "No playable url found for this station, please try another station."
442             bot.send_msg(msg, text)
443
444 yt_last_result = None
445 yt_last_page = 0 # TODO: if we keep adding global variables, we need to consider sealing all commands up into classes.
446
447 def cmd_yt_query(bot, user, text, command, parameter):
448     global log, yt_last_result, yt_last_page
449     item_per_page = 5
450
451     if parameter:
452         # if next page
453         if parameter.startswith("-n"):
454             yt_last_page += 1
455             if len(yt_last_result) > yt_last_page * item_per_page:
456                 msg = _yt_format_result(yt_last_result, yt_last_page * item_per_page, item_per_page)
457                 bot.send_msg(constants.strings('yt_result', result_table=msg), text)
458             else:
459                 bot.send_msg(constants.strings('yt_no_more'))
460
461         # if query
462         else:
463             results = util.youtube_search(parameter)
464             if results:
465                 yt_last_result = results
466                 yt_last_page = 0
467                 msg = _yt_format_result(results, 0, item_per_page)
468                 bot.send_msg(constants.strings('yt_result', result_table=msg), text)
469             else:
470                 bot.send_msg(constants.strings('yt_query_error'))
471     else:
472         bot.send_msg(constants.strings('bad_parameter', command=command), text)
473
474 def _yt_format_result(results, start, count):
475     msg = '<table><tr><th width="10%">Index</th><th>Title</th><th width="20%">Uploader</th></tr>'
476     for index, item in enumerate(results[start:start+count]):
477         msg += '<tr><td>{index:d}</td><td>{title}</td><td>{uploader}</td></tr>'.format(
478             index=index + 1 + start, title=item[1], uploader=item[2])
479     msg += '</table>'
480
481     return msg
482
483
484 def cmd_yt_play(bot, user, text, command, parameter):
485     global log, yt_last_result
486
487     if parameter and parameter.isdigit() and 0 < int(parameter) - 1 < len(yt_last_result):
488         url = "https://www.youtube.com/watch?v=" + yt_last_result[int(parameter) - 1][0]
489         cmd_play_url(bot, user, text, command, url)
490     else:
491         bot.send_msg(constants.strings('bad_parameter', command=command), text)
492
493
494 def cmd_help(bot, user, text, command, parameter):
495     global log
496
497     bot.send_msg(constants.strings('help'), text)
498     if bot.is_admin(user):
499         bot.send_msg(constants.strings('admin_help'), text)
500
501
502 def cmd_stop(bot, user, text, command, parameter):
503     global log
504
505     bot.stop()
506     bot.send_msg(constants.strings('stopped'), text)
507
508
509 def cmd_clear(bot, user, text, command, parameter):
510     global log
511
512     bot.clear()
513     bot.send_msg(constants.strings('cleared'), text)
514
515
516 def cmd_kill(bot, user, text, command, parameter):
517     global log
518
519     if bot.is_admin(user):
520         bot.pause()
521         bot.exit = True
522     else:
523         bot.mumble.users[text.actor].send_text_message(
524             constants.strings('not_admin'))
525
526
527 def cmd_update(bot, user, text, command, parameter):
528     global log
529
530     if bot.is_admin(user):
531         bot.mumble.users[text.actor].send_text_message(
532             constants.strings('start_updating'))
533         msg = util.update(bot.version)
534         bot.mumble.users[text.actor].send_text_message(msg)
535     else:
536         bot.mumble.users[text.actor].send_text_message(
537             constants.strings('not_admin'))
538
539
540 def cmd_stop_and_getout(bot, user, text, command, parameter):
541     global log
542
543     bot.stop()
544     if bot.channel:
545         bot.mumble.channels.find_by_name(bot.channel).move_in()
546
547
548 def cmd_volume(bot, user, text, command, parameter):
549     global log
550
551     # The volume is a percentage
552     if parameter and parameter.isdigit() and 0 <= int(parameter) <= 100:
553         bot.volume_set = float(float(parameter) / 100)
554         bot.send_msg(constants.strings('change_volume',
555             volume=int(bot.volume_set * 100), user=bot.mumble.users[text.actor]['name']), text)
556         var.db.set('bot', 'volume', str(bot.volume_set))
557         log.info('cmd: volume set to %d' % (bot.volume_set * 100))
558     else:
559         bot.send_msg(constants.strings('current_volume', volume=int(bot.volume_set * 100)), text)
560
561
562 def cmd_ducking(bot, user, text, command, parameter):
563     global log
564
565     if parameter == "" or parameter == "on":
566         bot.is_ducking = True
567         var.db.set('bot', 'ducking', True)
568         bot.ducking_volume = var.config.getfloat("bot", "ducking_volume", fallback=0.05)
569         bot.ducking_threshold = var.config.getint("bot", "ducking_threshold", fallback=5000)
570         bot.mumble.callbacks.set_callback(pymumble.constants.PYMUMBLE_CLBK_SOUNDRECEIVED,
571                                           bot.ducking_sound_received)
572         bot.mumble.set_receive_sound(True)
573         log.info('cmd: ducking is on')
574         msg = "Ducking on."
575         bot.send_msg(msg, text)
576     elif parameter == "off":
577         bot.is_ducking = False
578         bot.mumble.set_receive_sound(False)
579         var.db.set('bot', 'ducking', False)
580         msg = "Ducking off."
581         log.info('cmd: ducking is off')
582         bot.send_msg(msg, text)
583
584
585 def cmd_ducking_threshold(bot, user, text, command, parameter):
586     global log
587
588     if parameter and parameter.isdigit():
589         bot.ducking_threshold = int(parameter)
590         var.db.set('bot', 'ducking_threshold', str(bot.ducking_threshold))
591         msg = "Ducking threshold set to %d." % bot.ducking_threshold
592         bot.send_msg(msg, text)
593     else:
594         msg = "Current ducking threshold is %d." % bot.ducking_threshold
595         bot.send_msg(msg, text)
596
597
598 def cmd_ducking_volume(bot, user, text, command, parameter):
599     global log
600
601     # The volume is a percentage
602     if parameter and parameter.isdigit() and 0 <= int(parameter) <= 100:
603         bot.ducking_volume = float(float(parameter) / 100)
604         bot.send_msg(constants.strings('change_ducking_volume',
605             volume=int(bot.ducking_volume * 100), user=bot.mumble.users[text.actor]['name']), text)
606         # var.db.set('bot', 'volume', str(bot.volume_set))
607         var.db.set('bot', 'ducking_volume', str(bot.ducking_volume))
608         log.info('cmd: volume on ducking set to %d' % (bot.ducking_volume * 100))
609     else:
610         bot.send_msg(constants.strings('current_ducking_volume', volume=int(bot.ducking_volume * 100)), text)
611
612
613 def cmd_current_music(bot, user, text, command, parameter):
614     global log
615
616     reply = ""
617     if var.playlist.length() > 0:
618         bot.send_msg(util.format_current_playing())
619     else:
620         reply = constants.strings('not_playing')
621     bot.send_msg(reply, text)
622
623
624 def cmd_skip(bot, user, text, command, parameter):
625     global log
626
627     if var.playlist.length() > 0:
628         bot.stop()
629         bot.launch_music()
630         bot.async_download_next()
631     else:
632         bot.send_msg(constants.strings('queue_empty'), text)
633
634
635 def cmd_remove(bot, user, text, command, parameter):
636     global log
637
638     # Allow to remove specific music into the queue with a number
639     if parameter and parameter.isdigit() and int(parameter) > 0 \
640             and int(parameter) <= var.playlist.length():
641
642         index = int(parameter) - 1
643
644         removed = None
645         if index == var.playlist.current_index:
646             removed = var.playlist.remove(index)
647
648             if index < len(var.playlist):
649                 if not bot.is_pause:
650                     bot.interrupt_playing()
651                     var.playlist.current_index -= 1
652                     # then the bot will move to next item
653
654             else: # if item deleted is the last item of the queue
655                 var.playlist.current_index -= 1
656                 if not bot.is_pause:
657                     bot.interrupt_playing()
658         else:
659             removed = var.playlist.remove(index)
660
661         # the Title isn't here if the music wasn't downloaded
662         bot.send_msg(constants.strings('removing_item',
663             item=removed['title'] if 'title' in removed else removed['url']), text)
664
665         log.info("cmd: delete from playlist: " + str(removed['path'] if 'path' in removed else removed['url']))
666     else:
667         bot.send_msg(constants.strings('bad_parameter', command=command))
668
669
670 def cmd_list_file(bot, user, text, command, parameter):
671     global log
672
673     folder_path = var.music_folder
674
675     files = util.get_recursive_file_list_sorted(folder_path)
676     msgs = [ "<br> <b>Files available:</b>" if not parameter else "<br> <b>Matched files:</b>" ]
677     try:
678         count = 0
679         for index, file in enumerate(files):
680             if parameter:
681                 match = re.search(parameter, file)
682                 if not match:
683                     continue
684
685             count += 1
686             msgs.append("<b>{:0>3d}</b> - {:s}".format(index, file))
687
688         if count != 0:
689             send_multi_lines(bot, msgs, text)
690         else:
691             bot.send_msg(constants.strings('no_file'), text)
692
693     except re.error as e:
694         msg = constants.strings('wrong_pattern', error=str(e))
695         bot.send_msg(msg, text)
696
697
698 def cmd_queue(bot, user, text, command, parameter):
699     global log
700
701     if len(var.playlist) == 0:
702         msg = constants.strings('queue_empty')
703         bot.send_msg(msg, text)
704     else:
705         msgs = [ constants.strings('queue_contents')]
706         for i, value in enumerate(var.playlist):
707             newline = ''
708             if i == var.playlist.current_index:
709                 newline = '<b>{} ▶ ({}) {} ◀</b>'.format(i + 1, value['type'],
710                                                            value['title'] if 'title' in value else value['url'])
711             else:
712                 newline = '<b>{}</b> ({}) {}'.format(i + 1, value['type'],
713                                                      value['title'] if 'title' in value else value['url'])
714
715             msgs.append(newline)
716
717         send_multi_lines(bot, msgs, text)
718
719 def cmd_random(bot, user, text, command, parameter):
720     global log
721
722     bot.interrupt_playing()
723     var.playlist.randomize()
724
725 def cmd_repeat(bot, user, text, command, parameter):
726     global log
727
728     repeat = 1
729     if parameter and parameter.isdigit():
730         repeat = int(parameter)
731
732     music = var.playlist.current_item()
733     for _ in range(repeat):
734         var.playlist.insert(
735             var.playlist.current_index + 1,
736             music
737         )
738         log.info("bot: add to playlist: " + util.format_debug_song_string(music))
739
740     bot.send_msg(constants.strings("repeat", song=util.format_song_string(music), n=str(repeat)), text)
741
742 def cmd_mode(bot, user, text, command, parameter):
743     global log
744
745     if not parameter:
746         bot.send_msg(constants.strings("current_mode", mode=var.playlist.mode), text)
747         return
748     if not parameter in ["one-shot", "repeat", "random"]:
749         bot.send_msg(constants.strings('unknown_mode', mode=parameter), text)
750     else:
751         var.db.set('playlist', 'playback_mode', parameter)
752         var.playlist.set_mode(parameter)
753         log.info("command: playback mode changed to %s." % parameter)
754         bot.send_msg(constants.strings("change_mode", mode=var.playlist.mode,
755                                        user=bot.mumble.users[text.actor]['name']), text)
756         if parameter == "random":
757             bot.stop()
758             var.playlist.randomize()
759             bot.launch_music(0)
760
761
762 def cmd_drop_database(bot, user, text, command, parameter):
763     global log
764
765     var.db.drop_table()
766     var.db = Database(var.dbfile)
767     bot.send_msg(constants.strings('database_dropped'), text)