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