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('drop_database'), cmd_drop_database)
52 def send_multi_lines(bot, lines, text):
58 if len(msg) + len(newline) > 5000:
59 bot.send_msg(msg, text)
63 bot.send_msg(msg, text)
65 # ---------------- Commands ------------------
68 def cmd_joinme(bot, user, text, command, parameter):
69 channel_id = bot.mumble.users[text.actor]['channel_id']
70 bot.mumble.channels[channel_id].move_in()
73 def cmd_user_ban(bot, user, text, command, parameter):
74 if bot.is_admin(user):
76 bot.mumble.users[text.actor].send_text_message(util.user_ban(parameter))
78 bot.mumble.users[text.actor].send_text_message(util.get_user_ban())
80 bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin'))
84 def cmd_user_unban(bot, user, text, command, parameter):
85 if bot.is_admin(user):
87 bot.mumble.users[text.actor].send_text_message(util.user_unban(parameter))
89 bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin'))
93 def cmd_url_ban(bot, user, text, command, parameter):
94 if bot.is_admin(user):
96 bot.mumble.users[text.actor].send_text_message(util.url_ban(util.get_url_from_input(parameter)))
98 bot.mumble.users[text.actor].send_text_message(util.get_url_ban())
100 bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin'))
104 def cmd_url_unban(bot, user, text, command, parameter):
105 if bot.is_admin(user):
107 bot.mumble.users[text.actor].send_text_message(util.url_unban(util.get_url_from_input(parameter)))
109 bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin'))
113 def cmd_play(bot, user, text, command, parameter):
114 if var.playlist.length() > 0:
115 if parameter is not None and parameter.isdigit() and int(parameter) > 0 \
116 and int(parameter) <= len(var.playlist.playlist):
118 bot.launch_music(int(parameter) - 1)
122 bot.send_msg(util.format_current_playing(), text)
124 bot.send_msg(constants.strings('queue_empty'), text)
127 def cmd_pause(bot, user, text, command, parameter):
129 bot.send_msg(constants.strings('paused'))
132 def cmd_play_file(bot, user, text, command, parameter):
133 music_folder = var.config.get('bot', 'music_folder')
134 # if parameter is {index}
135 if parameter.isdigit():
136 files = util.get_recursive_filelist_sorted(music_folder)
137 if int(parameter) < len(files):
138 filename = files[int(parameter)].replace(music_folder, '')
139 music = {'type': 'file',
142 logging.info("cmd: add to playlist: " + filename)
143 music = var.playlist.append(music)
144 bot.send_msg(constants.strings('file_added', item=util.format_song_string(music)), text)
146 # if parameter is {path}
148 # sanitize "../" and so on
149 path = os.path.abspath(os.path.join(music_folder, parameter))
150 if not path.startswith(os.path.abspath(music_folder)):
151 bot.send_msg(constants.strings('no_file'), text)
154 if os.path.isfile(path):
155 music = {'type': 'file',
158 logging.info("cmd: add to playlist: " + parameter)
159 music = var.playlist.append(music)
160 bot.send_msg(constants.strings('file_added', item=util.format_song_string(music)), text)
163 # if parameter is {folder}
164 elif os.path.isdir(path):
165 if parameter != '.' and parameter != './':
166 if not parameter.endswith("/"):
171 files = util.get_recursive_filelist_sorted(music_folder)
172 music_library = util.Dir(music_folder)
174 music_library.add_file(file)
176 files = music_library.get_files(parameter)
177 msgs = [constants.strings('multiple_file_added')]
182 music = {'type': 'file',
185 logging.info("cmd: add to playlist: " + file)
186 music = var.playlist.append(music)
188 msgs.append("{} ({})".format(music['title'], music['path']))
191 send_multi_lines(bot, msgs, text)
193 bot.send_msg(constants.strings('no_file'), text)
196 # try to do a partial match
197 files = util.get_recursive_filelist_sorted(music_folder)
198 matches = [(index, file) for index, file in enumerate(files) if parameter.lower() in file.lower()]
199 if len(matches) == 0:
200 bot.send_msg(constants.strings('no_file'), text)
201 elif len(matches) == 1:
202 music = {'type': 'file',
203 'path': matches[0][1],
205 logging.info("cmd: add to playlist: " + matches[0][1])
206 music = var.playlist.append(music)
207 bot.send_msg(constants.strings('file_added', item=util.format_song_string(music)), text)
209 msgs = [ constants.strings('multiple_matches')]
210 for match in matches:
211 msgs.append("<b>{:0>3d}</b> - {:s}".format(match[0], match[1]))
212 send_multi_lines(bot, msgs, text)
215 def cmd_play_file_match(bot, user, text, command, parameter):
216 music_folder = var.config.get('bot', 'music_folder')
217 if parameter is not None:
218 files = util.get_recursive_filelist_sorted(music_folder)
219 msgs = [ constants.strings('file_added')]
223 match = re.search(parameter, file)
226 music = {'type': 'file',
229 logging.info("cmd: add to playlist: " + file)
230 music = var.playlist.append(music)
232 msgs.append("{} ({})".format(music['title'], music['path']))
235 send_multi_lines(bot, msgs, text)
237 bot.send_msg(constants.strings('no_file'), text)
239 except re.error as e:
240 msg = constants.strings('wrong_pattern', error=str(e))
241 bot.send_msg(msg, text)
243 bot.send_msg(constants.strings('bad_parameter', command))
246 def cmd_play_url(bot, user, text, command, parameter):
247 music = {'type': 'url',
249 'url': util.get_url_from_input(parameter),
251 'ready': 'validation'}
253 if media.url.get_url_info(music):
254 if music['duration'] > var.config.getint('bot', 'max_track_duration'):
255 bot.send_msg(constants.strings('too_long'), text)
257 music['ready'] = "no"
258 var.playlist.append(music)
259 logging.info("cmd: add to playlist: " + music['url'])
260 bot.async_download_next()
262 bot.send_msg(constants.strings('unable_download'), text)
265 def cmd_play_playlist(bot, user, text, command, parameter):
266 offset = 0 # if you want to start the playlist at a specific index
268 offset = int(parameter.split(" ")[-1])
272 url = util.get_url_from_input(parameter)
273 logging.debug("cmd: fetching media info from playlist url %s" % url)
274 items = media.playlist.get_playlist_info(url=url, start_index=offset, user=user)
276 var.playlist.extend(items)
278 logging.info("cmd: add to playlist: " + util.format_debug_song_string(music))
281 def cmd_play_radio(bot, user, text, command, parameter):
283 all_radio = var.config.items('radio')
284 msg = constants.strings('preconfigurated_radio')
287 if len(i[1].split(maxsplit=1)) == 2:
288 comment = " - " + i[1].split(maxsplit=1)[1]
289 msg += "<br />" + i[0] + comment
290 bot.send_msg(msg, text)
292 if var.config.has_option('radio', command, parameter):
293 parameter = var.config.get('radio', parameter)
294 parameter = parameter.split()[0]
295 url = util.get_url_from_input(parameter)
297 music = {'type': 'radio',
300 var.playlist.append(music)
301 logging.info("cmd: add to playlist: " + music['url'])
302 bot.async_download_next()
304 bot.send_msg(constants.strings('bad_url'))
307 def cmd_rb_query(bot, user, text, command, parameter):
308 logging.info('cmd: Querying radio stations')
310 logging.debug('rbquery without parameter')
311 msg = constants.strings('rb_query_empty')
312 bot.send_msg(msg, text)
314 logging.debug('cmd: Found query parameter: ' + parameter)
315 # bot.send_msg('Searching for stations - this may take some seconds...', text)
316 rb_stations = radiobrowser.getstations_byname(parameter)
317 msg = constants.strings('rb_query_result')
318 msg += '\n<table><tr><th>!rbplay ID</th><th>Station Name</th><th>Genre</th><th>Codec/Bitrate</th><th>Country</th></tr>'
320 logging.debug('cmd: No matches found for rbquery ' + parameter)
321 bot.send_msg('Radio-Browser found no matches for ' + parameter, text)
323 for s in rb_stations:
325 stationname = s['stationname']
326 country = s['country']
328 bitrate = s['bitrate']
330 # msg += f'<tr><td>{stationid}</td><td>{stationname}</td><td>{genre}</td><td>{codec}/{bitrate}</td><td>{country}</td></tr>'
331 msg += '<tr><td>%s</td><td>%s</td><td>%s</td><td>%s/%s</td><td>%s</td></tr>' % (
332 stationid, stationname, genre, codec, bitrate, country)
334 # Full message as html table
336 bot.send_msg(msg, text)
337 # Shorten message if message too long (stage I)
339 logging.debug('Result too long stage I')
340 msg = constants.strings('rb_query_result') + ' (shortened L1)'
341 msg += '\n<table><tr><th>!rbplay ID</th><th>Station Name</th></tr>'
342 for s in rb_stations:
344 stationname = s['stationname']
345 # msg += f'<tr><td>{stationid}</td><td>{stationname}</td>'
346 msg += '<tr><td>%s</td><td>%s</td>' % (stationid, stationname)
349 bot.send_msg(msg, text)
350 # Shorten message if message too long (stage II)
352 logging.debug('Result too long stage II')
353 msg = constants.strings('rb_query_result') + ' (shortened L2)'
354 msg += '!rbplay ID - Station Name'
355 for s in rb_stations:
357 stationname = s['stationname'][:12]
358 # msg += f'{stationid} - {stationname}'
359 msg += '%s - %s' % (stationid, stationname)
361 bot.send_msg(msg, text)
362 # Message still too long
364 bot.send_msg('Query result too long to post (> 5000 characters), please try another query.',
368 def cmd_rb_play(bot, user, text, command, parameter):
369 logging.debug('cmd: Play a station by ID')
371 logging.debug('rbplay without parameter')
372 msg = constants.strings('rb_play_empty')
373 bot.send_msg(msg, text)
375 logging.debug('cmd: Retreiving url for station ID ' + parameter)
376 rstation = radiobrowser.getstationname_byid(parameter)
377 stationname = rstation[0]['name']
378 country = rstation[0]['country']
379 codec = rstation[0]['codec']
380 bitrate = rstation[0]['bitrate']
381 genre = rstation[0]['tags']
382 homepage = rstation[0]['homepage']
383 msg = 'Radio station added to playlist:'
384 # msg += '<table><tr><th>ID</th><th>Station Name</th><th>Genre</th><th>Codec/Bitrate</th><th>Country</th><th>Homepage</th></tr>' + \
385 # f'<tr><td>{parameter}</td><td>{stationname}</td><td>{genre}</td><td>{codec}/{bitrate}</td><td>{country}</td><td>{homepage}</td></tr></table>'
386 msg += '<table><tr><th>ID</th><th>Station Name</th><th>Genre</th><th>Codec/Bitrate</th><th>Country</th><th>Homepage</th></tr>' + \
387 '<tr><td>%s</td><td>%s</td><td>%s</td><td>%s/%s</td><td>%s</td><td>%s</td></tr></table>' \
388 % (parameter, stationname, genre, codec, bitrate, country, homepage)
389 logging.debug('cmd: Added station to playlist %s' % stationname)
390 bot.send_msg(msg, text)
391 url = radiobrowser.geturl_byid(parameter)
393 logging.info('cmd: Found url: ' + url)
394 music = {'type': 'radio',
395 'title': stationname,
399 var.playlist.append(music)
400 logging.info("cmd: add to playlist: " + music['url'])
401 bot.async_download_next()
403 logging.info('cmd: No playable url found.')
404 msg += "No playable url found for this station, please try another station."
405 bot.send_msg(msg, text)
408 def cmd_help(bot, user, text, command, parameter):
409 bot.send_msg(constants.strings('help'), text)
410 if bot.is_admin(user):
411 bot.send_msg(constants.strings('admin_help'), text)
414 def cmd_stop(bot, user, text, command, parameter):
416 bot.send_msg(constants.strings('stopped'), text)
419 def cmd_clear(bot, user, text, command, parameter):
421 bot.send_msg(constants.strings('cleared'), text)
424 def cmd_kill(bot, user, text, command, parameter):
425 if bot.is_admin(user):
429 bot.mumble.users[text.actor].send_text_message(
430 constants.strings('not_admin'))
433 def cmd_update(bot, user, text, command, parameter):
434 if bot.is_admin(user):
435 bot.mumble.users[text.actor].send_text_message(
436 constants.strings('start_updating'))
437 msg = util.update(bot.version)
438 bot.mumble.users[text.actor].send_text_message(msg)
440 bot.mumble.users[text.actor].send_text_message(
441 constants.strings('not_admin'))
444 def cmd_stop_and_getout(bot, user, text, command, parameter):
447 bot.mumble.channels.find_by_name(bot.channel).move_in()
450 def cmd_volume(bot, user, text, command, parameter):
451 # The volume is a percentage
452 if parameter is not None and parameter.isdigit() and 0 <= int(parameter) <= 100:
453 bot.volume_set = float(float(parameter) / 100)
454 bot.send_msg(constants.strings('change_volume',
455 volume=int(bot.volume_set * 100), user=bot.mumble.users[text.actor]['name']), text)
456 var.db.set('bot', 'volume', str(bot.volume_set))
457 logging.info('cmd: volume set to %d' % (bot.volume_set * 100))
459 bot.send_msg(constants.strings('current_volume', volume=int(bot.volume_set * 100)), text)
462 def cmd_ducking(bot, user, text, command, parameter):
463 if parameter == "" or parameter == "on":
464 bot.is_ducking = True
465 var.db.set('bot', 'ducking', True)
466 bot.ducking_volume = var.config.getfloat("bot", "ducking_volume", fallback=0.05)
467 bot.ducking_threshold = var.config.getint("bot", "ducking_threshold", fallback=5000)
468 bot.mumble.callbacks.set_callback(pymumble.constants.PYMUMBLE_CLBK_SOUNDRECEIVED,
469 bot.ducking_sound_received)
470 bot.mumble.set_receive_sound(True)
471 logging.info('cmd: ducking is on')
473 bot.send_msg(msg, text)
474 elif parameter == "off":
475 bot.is_ducking = False
476 bot.mumble.set_receive_sound(False)
477 var.db.set('bot', 'ducking', False)
479 logging.info('cmd: ducking is off')
480 bot.send_msg(msg, text)
483 def cmd_ducking_threshold(bot, user, text, command, parameter):
484 if parameter is not None and parameter.isdigit():
485 bot.ducking_threshold = int(parameter)
486 var.db.set('bot', 'ducking_threshold', str(bot.ducking_threshold))
487 msg = "Ducking threshold set to %d." % bot.ducking_threshold
488 bot.send_msg(msg, text)
490 msg = "Current ducking threshold is %d." % bot.ducking_threshold
491 bot.send_msg(msg, text)
494 def cmd_ducking_volume(bot, user, text, command, parameter):
495 # The volume is a percentage
496 if parameter is not None and parameter.isdigit() and 0 <= int(parameter) <= 100:
497 bot.ducking_volume = float(float(parameter) / 100)
498 bot.send_msg(constants.strings('change_ducking_volume',
499 volume=int(bot.ducking_volume * 100), user=bot.mumble.users[text.actor]['name']), text)
500 # var.db.set('bot', 'volume', str(bot.volume_set))
501 var.db.set('bot', 'ducking_volume', str(bot.ducking_volume))
502 logging.info('cmd: volume on ducking set to %d' % (bot.ducking_volume * 100))
504 bot.send_msg(constants.strings('current_ducking_volume', volume=int(bot.ducking_volume * 100)), text)
507 def cmd_current_music(bot, user, text, command, parameter):
509 if var.playlist.length() > 0:
510 bot.send_msg(util.format_current_playing())
512 reply = constants.strings('not_playing')
513 bot.send_msg(reply, text)
516 def cmd_skip(bot, user, text, command, parameter):
517 if bot.next(): # Is no number send, just skip the current music
519 bot.async_download_next()
521 bot.send_msg(constants.strings('queue_empty'), text)
524 def cmd_remove(bot, user, text, command, parameter):
525 # Allow to remove specific music into the queue with a number
526 if parameter is not None and parameter.isdigit() and int(parameter) > 0 \
527 and int(parameter) <= var.playlist.length():
529 index = int(parameter) - 1
532 if index == var.playlist.current_index:
533 removed = var.playlist.remove(index)
534 var.botamusique.stop()
535 var.botamusique.launch_music(index)
537 removed = var.playlist.remove(index)
539 # the Title isn't here if the music wasn't downloaded
540 bot.send_msg(constants.strings('removing_item',
541 item=removed['title'] if 'title' in removed else removed['url']), text)
543 logging.info("cmd: delete from playlist: " + str(removed['path'] if 'path' in removed else removed['url']))
545 bot.send_msg(constants.strings('bad_parameter', command=command))
548 def cmd_list_file(bot, user, text, command, parameter):
549 folder_path = var.config.get('bot', 'music_folder')
551 files = util.get_recursive_filelist_sorted(folder_path)
552 msgs = [ "<br> <b>Files available:</b>" if not parameter else "<br> <b>Matched files:</b>" ]
555 for index, file in enumerate(files):
557 match = re.search(parameter, file)
562 msgs.append("<b>{:0>3d}</b> - {:s}".format(index, file))
565 send_multi_lines(bot, msgs, text)
567 bot.send_msg(constants.strings('no_file'), text)
569 except re.error as e:
570 msg = constants.strings('wrong_pattern', error=str(e))
571 bot.send_msg(msg, text)
574 def cmd_queue(bot, user, text, command, parameter):
575 if len(var.playlist.playlist) == 0:
576 msg = constants.strings('queue_empty')
577 bot.send_msg(msg, text)
579 msgs = [ constants.strings('queue_contents')]
580 for i, value in enumerate(var.playlist.playlist):
582 if i == var.playlist.current_index:
583 newline = '<b>{} ▶ ({}) {} ◀</b>'.format(i + 1, value['type'],
584 value['title'] if 'title' in value else value['url'])
586 newline = '<b>{}</b> ({}) {}'.format(i + 1, value['type'],
587 value['title'] if 'title' in value else value['url'])
591 send_multi_lines(bot, msgs, text)
594 def cmd_random(bot, user, text, command, parameter):
596 var.playlist.randomize()
599 def cmd_drop_database(bot, user, text, command, parameter):
601 var.db = Database(var.dbfile)
602 bot.send_msg(constants.strings('database_dropped'), text)