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