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