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