4 import pymumble.pymumble_py3 as pymumble
14 import variables as var
15 from librb import radiobrowser
16 from database import Database
19 def register_all_commands(bot):
20 bot.register_command(constants.commands('joinme'), cmd_joinme)
21 bot.register_command(constants.commands('user_ban'), cmd_user_ban)
22 bot.register_command(constants.commands('user_unban'), cmd_user_unban)
23 bot.register_command(constants.commands('url_ban'), cmd_url_ban)
24 bot.register_command(constants.commands('url_unban'), cmd_url_unban)
25 bot.register_command(constants.commands('play'), cmd_play)
26 bot.register_command(constants.commands('pause'), cmd_pause)
27 bot.register_command(constants.commands('play_file'), cmd_play_file)
28 bot.register_command(constants.commands('play_file_match'), cmd_play_file_match)
29 bot.register_command(constants.commands('play_url'), cmd_play_url)
30 bot.register_command(constants.commands('play_playlist'), cmd_play_playlist)
31 bot.register_command(constants.commands('play_radio'), cmd_play_radio)
32 bot.register_command(constants.commands('rb_query'), cmd_rb_query)
33 bot.register_command(constants.commands('rb_play'), cmd_rb_play)
34 bot.register_command(constants.commands('help'), cmd_help)
35 bot.register_command(constants.commands('stop'), cmd_stop)
36 bot.register_command(constants.commands('clear'), cmd_clear)
37 bot.register_command(constants.commands('kill'), cmd_kill)
38 bot.register_command(constants.commands('update'), cmd_update)
39 bot.register_command(constants.commands('stop_and_getout'), cmd_stop_and_getout)
40 bot.register_command(constants.commands('volume'), cmd_volume)
41 bot.register_command(constants.commands('ducking'), cmd_ducking)
42 bot.register_command(constants.commands('ducking_threshold'), cmd_ducking_threshold)
43 bot.register_command(constants.commands('ducking_volume'), cmd_ducking_volume)
44 bot.register_command(constants.commands('current_music'), cmd_current_music)
45 bot.register_command(constants.commands('skip'), cmd_skip)
46 bot.register_command(constants.commands('remove'), cmd_remove)
47 bot.register_command(constants.commands('list_file'), cmd_list_file)
48 bot.register_command(constants.commands('queue'), cmd_queue)
49 bot.register_command(constants.commands('random'), cmd_random)
50 bot.register_command(constants.commands('repeat'), cmd_repeat)
51 bot.register_command(constants.commands('mode'), cmd_mode)
52 bot.register_command(constants.commands('drop_database'), cmd_drop_database)
54 def send_multi_lines(bot, lines, text):
60 if len(msg) + len(newline) > 5000:
61 bot.send_msg(msg, text)
65 bot.send_msg(msg, text)
67 # ---------------- Commands ------------------
70 def cmd_joinme(bot, user, text, command, parameter):
71 bot.mumble.users.myself.move_in(
72 bot.mumble.users[text.actor]['channel_id'], token=parameter)
75 def cmd_user_ban(bot, user, text, command, parameter):
76 if bot.is_admin(user):
78 bot.mumble.users[text.actor].send_text_message(util.user_ban(parameter))
80 bot.mumble.users[text.actor].send_text_message(util.get_user_ban())
82 bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin'))
86 def cmd_user_unban(bot, user, text, command, parameter):
87 if bot.is_admin(user):
89 bot.mumble.users[text.actor].send_text_message(util.user_unban(parameter))
91 bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin'))
95 def cmd_url_ban(bot, user, text, command, parameter):
96 if bot.is_admin(user):
98 bot.mumble.users[text.actor].send_text_message(util.url_ban(util.get_url_from_input(parameter)))
100 bot.mumble.users[text.actor].send_text_message(util.get_url_ban())
102 bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin'))
106 def cmd_url_unban(bot, user, text, command, parameter):
107 if bot.is_admin(user):
109 bot.mumble.users[text.actor].send_text_message(util.url_unban(util.get_url_from_input(parameter)))
111 bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin'))
115 def cmd_play(bot, user, text, command, parameter):
116 if var.playlist.length() > 0:
117 if parameter is not None:
118 if parameter.isdigit() and int(parameter) > 0 and int(parameter) <= len(var.playlist):
119 bot.interrupt_playing()
120 bot.launch_music(int(parameter) - 1)
122 bot.send_msg(constants.strings('invalid_index', index=parameter), text)
127 bot.send_msg(util.format_current_playing(), text)
130 bot.send_msg(constants.strings('queue_empty'), text)
133 def cmd_pause(bot, user, text, command, parameter):
135 bot.send_msg(constants.strings('paused'))
138 def cmd_play_file(bot, user, text, command, parameter):
139 music_folder = var.config.get('bot', 'music_folder')
140 # if parameter is {index}
141 if parameter.isdigit():
142 files = util.get_recursive_filelist_sorted(music_folder)
143 if int(parameter) < len(files):
144 filename = files[int(parameter)].replace(music_folder, '')
145 music = {'type': 'file',
148 music = var.playlist.append(music)
149 logging.info("cmd: add to playlist: " + util.format_debug_song_string(music))
150 bot.send_msg(constants.strings('file_added', item=util.format_song_string(music)), text)
152 # if parameter is {path}
154 # sanitize "../" and so on
155 path = os.path.abspath(os.path.join(music_folder, parameter))
156 if not path.startswith(os.path.abspath(music_folder)):
157 bot.send_msg(constants.strings('no_file'), text)
160 if os.path.isfile(path):
161 music = {'type': 'file',
164 music = var.playlist.append(music)
165 logging.info("cmd: add to playlist: " + util.format_debug_song_string(music))
166 bot.send_msg(constants.strings('file_added', item=util.format_song_string(music)), text)
169 # if parameter is {folder}
170 elif os.path.isdir(path):
171 if parameter != '.' and parameter != './':
172 if not parameter.endswith("/"):
177 files = util.get_recursive_filelist_sorted(music_folder)
178 music_library = util.Dir(music_folder)
180 music_library.add_file(file)
182 files = music_library.get_files(parameter)
183 msgs = [constants.strings('multiple_file_added')]
188 music = {'type': 'file',
191 music = var.playlist.append(music)
192 logging.info("cmd: add to playlist: " + util.format_debug_song_string(music))
193 msgs.append("{} ({})".format(music['title'], music['path']))
196 send_multi_lines(bot, msgs, text)
198 bot.send_msg(constants.strings('no_file'), text)
201 # try to do a partial match
202 files = util.get_recursive_filelist_sorted(music_folder)
203 matches = [(index, file) for index, file in enumerate(files) if parameter.lower() in file.lower()]
204 if len(matches) == 0:
205 bot.send_msg(constants.strings('no_file'), text)
206 elif len(matches) == 1:
207 music = {'type': 'file',
208 'path': matches[0][1],
210 music = var.playlist.append(music)
211 logging.info("cmd: add to playlist: " + util.format_debug_song_string(music))
212 bot.send_msg(constants.strings('file_added', item=util.format_song_string(music)), text)
214 msgs = [ constants.strings('multiple_matches')]
215 for match in matches:
216 msgs.append("<b>{:0>3d}</b> - {:s}".format(match[0], match[1]))
217 send_multi_lines(bot, msgs, text)
220 def cmd_play_file_match(bot, user, text, command, parameter):
221 music_folder = var.config.get('bot', 'music_folder')
222 if parameter is not None:
223 files = util.get_recursive_filelist_sorted(music_folder)
224 msgs = [ constants.strings('multiple_file_added')]
228 match = re.search(parameter, file)
231 music = {'type': 'file',
234 music = var.playlist.append(music)
235 logging.info("cmd: add to playlist: " + util.format_debug_song_string(music))
237 msgs.append("{} ({})".format(music['title'], music['path']))
240 send_multi_lines(bot, msgs, text)
242 bot.send_msg(constants.strings('no_file'), text)
244 except re.error as e:
245 msg = constants.strings('wrong_pattern', error=str(e))
246 bot.send_msg(msg, text)
248 bot.send_msg(constants.strings('bad_parameter', command))
251 def cmd_play_url(bot, user, text, command, parameter):
252 music = {'type': 'url',
254 'url': util.get_url_from_input(parameter),
256 'ready': 'validation'}
258 music = bot.validate_music(music)
260 music = var.playlist.append(music)
261 logging.info("cmd: add to playlist: " + util.format_debug_song_string(music))
262 bot.send_msg(constants.strings('file_added', item=util.format_song_string(music)), text)
263 if var.playlist.length() == 2:
264 # If I am the second item on the playlist. (I am the next one!)
265 bot.async_download_next()
267 bot.send_msg(constants.strings('unable_download'), text)
270 def cmd_play_playlist(bot, user, text, command, parameter):
271 offset = 0 # if you want to start the playlist at a specific index
273 offset = int(parameter.split(" ")[-1])
277 url = util.get_url_from_input(parameter)
278 logging.debug("cmd: fetching media info from playlist url %s" % url)
279 items = media.playlist.get_playlist_info(url=url, start_index=offset, user=user)
281 var.playlist.extend(items)
283 logging.info("cmd: add to playlist: " + util.format_debug_song_string(music))
286 def cmd_play_radio(bot, user, text, command, parameter):
288 all_radio = var.config.items('radio')
289 msg = constants.strings('preconfigurated_radio')
292 if len(i[1].split(maxsplit=1)) == 2:
293 comment = " - " + i[1].split(maxsplit=1)[1]
294 msg += "<br />" + i[0] + comment
295 bot.send_msg(msg, text)
297 if var.config.has_option('radio', parameter):
298 parameter = var.config.get('radio', parameter)
299 parameter = parameter.split()[0]
300 url = util.get_url_from_input(parameter)
302 music = {'type': 'radio',
305 var.playlist.append(music)
306 logging.info("cmd: add to playlist: " + util.format_debug_song_string(music))
307 bot.async_download_next()
309 bot.send_msg(constants.strings('bad_url'))
312 def cmd_rb_query(bot, user, text, command, parameter):
313 logging.info('cmd: Querying radio stations')
315 logging.debug('rbquery without parameter')
316 msg = constants.strings('rb_query_empty')
317 bot.send_msg(msg, text)
319 logging.debug('cmd: Found query parameter: ' + parameter)
320 # bot.send_msg('Searching for stations - this may take some seconds...', text)
321 rb_stations = radiobrowser.getstations_byname(parameter)
322 msg = constants.strings('rb_query_result')
323 msg += '\n<table><tr><th>!rbplay ID</th><th>Station Name</th><th>Genre</th><th>Codec/Bitrate</th><th>Country</th></tr>'
325 logging.debug('cmd: No matches found for rbquery ' + parameter)
326 bot.send_msg('Radio-Browser found no matches for ' + parameter, text)
328 for s in rb_stations:
330 stationname = s['stationname']
331 country = s['country']
333 bitrate = s['bitrate']
335 # msg += f'<tr><td>{stationid}</td><td>{stationname}</td><td>{genre}</td><td>{codec}/{bitrate}</td><td>{country}</td></tr>'
336 msg += '<tr><td>%s</td><td>%s</td><td>%s</td><td>%s/%s</td><td>%s</td></tr>' % (
337 stationid, stationname, genre, codec, bitrate, country)
339 # Full message as html table
341 bot.send_msg(msg, text)
342 # Shorten message if message too long (stage I)
344 logging.debug('Result too long stage I')
345 msg = constants.strings('rb_query_result') + ' (shortened L1)'
346 msg += '\n<table><tr><th>!rbplay ID</th><th>Station Name</th></tr>'
347 for s in rb_stations:
349 stationname = s['stationname']
350 # msg += f'<tr><td>{stationid}</td><td>{stationname}</td>'
351 msg += '<tr><td>%s</td><td>%s</td>' % (stationid, stationname)
354 bot.send_msg(msg, text)
355 # Shorten message if message too long (stage II)
357 logging.debug('Result too long stage II')
358 msg = constants.strings('rb_query_result') + ' (shortened L2)'
359 msg += '!rbplay ID - Station Name'
360 for s in rb_stations:
362 stationname = s['stationname'][:12]
363 # msg += f'{stationid} - {stationname}'
364 msg += '%s - %s' % (stationid, stationname)
366 bot.send_msg(msg, text)
367 # Message still too long
369 bot.send_msg('Query result too long to post (> 5000 characters), please try another query.',
373 def cmd_rb_play(bot, user, text, command, parameter):
374 logging.debug('cmd: Play a station by ID')
376 logging.debug('rbplay without parameter')
377 msg = constants.strings('rb_play_empty')
378 bot.send_msg(msg, text)
380 logging.debug('cmd: Retreiving url for station ID ' + parameter)
381 rstation = radiobrowser.getstationname_byid(parameter)
382 stationname = rstation[0]['name']
383 country = rstation[0]['country']
384 codec = rstation[0]['codec']
385 bitrate = rstation[0]['bitrate']
386 genre = rstation[0]['tags']
387 homepage = rstation[0]['homepage']
388 msg = 'Radio station added to playlist:'
389 # msg += '<table><tr><th>ID</th><th>Station Name</th><th>Genre</th><th>Codec/Bitrate</th><th>Country</th><th>Homepage</th></tr>' + \
390 # f'<tr><td>{parameter}</td><td>{stationname}</td><td>{genre}</td><td>{codec}/{bitrate}</td><td>{country}</td><td>{homepage}</td></tr></table>'
391 msg += '<table><tr><th>ID</th><th>Station Name</th><th>Genre</th><th>Codec/Bitrate</th><th>Country</th><th>Homepage</th></tr>' + \
392 '<tr><td>%s</td><td>%s</td><td>%s</td><td>%s/%s</td><td>%s</td><td>%s</td></tr></table>' \
393 % (parameter, stationname, genre, codec, bitrate, country, homepage)
394 logging.debug('cmd: Added station to playlist %s' % stationname)
395 bot.send_msg(msg, text)
396 url = radiobrowser.geturl_byid(parameter)
398 logging.info('cmd: Found url: ' + url)
399 music = {'type': 'radio',
400 'title': stationname,
404 var.playlist.append(music)
405 logging.info("cmd: add to playlist: " + util.format_debug_song_string(music))
406 bot.async_download_next()
408 logging.info('cmd: No playable url found.')
409 msg += "No playable url found for this station, please try another station."
410 bot.send_msg(msg, text)
413 def cmd_help(bot, user, text, command, parameter):
414 bot.send_msg(constants.strings('help'), text)
415 if bot.is_admin(user):
416 bot.send_msg(constants.strings('admin_help'), text)
419 def cmd_stop(bot, user, text, command, parameter):
421 bot.send_msg(constants.strings('stopped'), text)
424 def cmd_clear(bot, user, text, command, parameter):
426 bot.send_msg(constants.strings('cleared'), text)
429 def cmd_kill(bot, user, text, command, parameter):
430 if bot.is_admin(user):
434 bot.mumble.users[text.actor].send_text_message(
435 constants.strings('not_admin'))
438 def cmd_update(bot, user, text, command, parameter):
439 if bot.is_admin(user):
440 bot.mumble.users[text.actor].send_text_message(
441 constants.strings('start_updating'))
442 msg = util.update(bot.version)
443 bot.mumble.users[text.actor].send_text_message(msg)
445 bot.mumble.users[text.actor].send_text_message(
446 constants.strings('not_admin'))
449 def cmd_stop_and_getout(bot, user, text, command, parameter):
452 bot.mumble.channels.find_by_name(bot.channel).move_in()
455 def cmd_volume(bot, user, text, command, parameter):
456 # The volume is a percentage
457 if parameter is not None and parameter.isdigit() and 0 <= int(parameter) <= 100:
458 bot.volume_set = float(float(parameter) / 100)
459 bot.send_msg(constants.strings('change_volume',
460 volume=int(bot.volume_set * 100), user=bot.mumble.users[text.actor]['name']), text)
461 var.db.set('bot', 'volume', str(bot.volume_set))
462 logging.info('cmd: volume set to %d' % (bot.volume_set * 100))
464 bot.send_msg(constants.strings('current_volume', volume=int(bot.volume_set * 100)), text)
467 def cmd_ducking(bot, user, text, command, parameter):
468 if parameter == "" or parameter == "on":
469 bot.is_ducking = True
470 var.db.set('bot', 'ducking', True)
471 bot.ducking_volume = var.config.getfloat("bot", "ducking_volume", fallback=0.05)
472 bot.ducking_threshold = var.config.getint("bot", "ducking_threshold", fallback=5000)
473 bot.mumble.callbacks.set_callback(pymumble.constants.PYMUMBLE_CLBK_SOUNDRECEIVED,
474 bot.ducking_sound_received)
475 bot.mumble.set_receive_sound(True)
476 logging.info('cmd: ducking is on')
478 bot.send_msg(msg, text)
479 elif parameter == "off":
480 bot.is_ducking = False
481 bot.mumble.set_receive_sound(False)
482 var.db.set('bot', 'ducking', False)
484 logging.info('cmd: ducking is off')
485 bot.send_msg(msg, text)
488 def cmd_ducking_threshold(bot, user, text, command, parameter):
489 if parameter is not None and parameter.isdigit():
490 bot.ducking_threshold = int(parameter)
491 var.db.set('bot', 'ducking_threshold', str(bot.ducking_threshold))
492 msg = "Ducking threshold set to %d." % bot.ducking_threshold
493 bot.send_msg(msg, text)
495 msg = "Current ducking threshold is %d." % bot.ducking_threshold
496 bot.send_msg(msg, text)
499 def cmd_ducking_volume(bot, user, text, command, parameter):
500 # The volume is a percentage
501 if parameter is not None and parameter.isdigit() and 0 <= int(parameter) <= 100:
502 bot.ducking_volume = float(float(parameter) / 100)
503 bot.send_msg(constants.strings('change_ducking_volume',
504 volume=int(bot.ducking_volume * 100), user=bot.mumble.users[text.actor]['name']), text)
505 # var.db.set('bot', 'volume', str(bot.volume_set))
506 var.db.set('bot', 'ducking_volume', str(bot.ducking_volume))
507 logging.info('cmd: volume on ducking set to %d' % (bot.ducking_volume * 100))
509 bot.send_msg(constants.strings('current_ducking_volume', volume=int(bot.ducking_volume * 100)), text)
512 def cmd_current_music(bot, user, text, command, parameter):
514 if var.playlist.length() > 0:
515 bot.send_msg(util.format_current_playing())
517 reply = constants.strings('not_playing')
518 bot.send_msg(reply, text)
521 def cmd_skip(bot, user, text, command, parameter):
522 if var.playlist.length() > 0:
525 bot.async_download_next()
527 bot.send_msg(constants.strings('queue_empty'), text)
530 def cmd_remove(bot, user, text, command, parameter):
531 # Allow to remove specific music into the queue with a number
532 if parameter is not None and parameter.isdigit() and int(parameter) > 0 \
533 and int(parameter) <= var.playlist.length():
535 index = int(parameter) - 1
538 if index == var.playlist.current_index:
539 removed = var.playlist.remove(index)
541 if index < len(var.playlist):
543 bot.interrupt_playing()
544 var.playlist.current_index -= 1
545 # then the bot will move to next item
547 else: # if item deleted is the last item of the queue
548 var.playlist.current_index -= 1
550 bot.interrupt_playing()
552 removed = var.playlist.remove(index)
554 # the Title isn't here if the music wasn't downloaded
555 bot.send_msg(constants.strings('removing_item',
556 item=removed['title'] if 'title' in removed else removed['url']), text)
558 logging.info("cmd: delete from playlist: " + str(removed['path'] if 'path' in removed else removed['url']))
560 bot.send_msg(constants.strings('bad_parameter', command=command))
563 def cmd_list_file(bot, user, text, command, parameter):
564 folder_path = var.config.get('bot', 'music_folder')
566 files = util.get_recursive_filelist_sorted(folder_path)
567 msgs = [ "<br> <b>Files available:</b>" if not parameter else "<br> <b>Matched files:</b>" ]
570 for index, file in enumerate(files):
572 match = re.search(parameter, file)
577 msgs.append("<b>{:0>3d}</b> - {:s}".format(index, file))
580 send_multi_lines(bot, msgs, text)
582 bot.send_msg(constants.strings('no_file'), text)
584 except re.error as e:
585 msg = constants.strings('wrong_pattern', error=str(e))
586 bot.send_msg(msg, text)
589 def cmd_queue(bot, user, text, command, parameter):
590 if len(var.playlist) == 0:
591 msg = constants.strings('queue_empty')
592 bot.send_msg(msg, text)
594 msgs = [ constants.strings('queue_contents')]
595 for i, value in enumerate(var.playlist):
597 if i == var.playlist.current_index:
598 newline = '<b>{} ▶ ({}) {} ◀</b>'.format(i + 1, value['type'],
599 value['title'] if 'title' in value else value['url'])
601 newline = '<b>{}</b> ({}) {}'.format(i + 1, value['type'],
602 value['title'] if 'title' in value else value['url'])
606 send_multi_lines(bot, msgs, text)
608 def cmd_random(bot, user, text, command, parameter):
609 bot.interrupt_playing()
610 var.playlist.randomize()
612 def cmd_repeat(bot, user, text, command, parameter):
614 if parameter is not None and parameter.isdigit():
615 repeat = int(parameter)
617 music = var.playlist.current_item()
618 for _ in range(repeat):
620 var.playlist.current_index + 1,
623 logging.info("bot: add to playlist: " + util.format_debug_song_string(music))
625 bot.send_msg(constants.strings("repeat", song=util.format_song_string(music), n=str(repeat)), text)
627 def cmd_mode(bot, user, text, command, parameter):
629 bot.send_msg(constants.strings("current_mode", mode=var.playlist.mode), text)
631 if not parameter in ["one-shot", "repeat", "random"]:
632 bot.send_msg(constants.strings('unknown_mode', mode=parameter), text)
634 var.db.set('playlist', 'playback_mode', parameter)
635 var.playlist.set_mode(parameter)
636 logging.info("command: playback mode changed to %s." % parameter)
637 bot.send_msg(constants.strings("change_mode", mode=var.playlist.mode,
638 user=bot.mumble.users[text.actor]['name']), text)
639 if parameter == "random":
641 var.playlist.randomize()
645 def cmd_drop_database(bot, user, text, command, parameter):
647 var.db = Database(var.dbfile)
648 bot.send_msg(constants.strings('database_dropped'), text)