def cmd_remove(bot, user, text, command, parameter): global log # Allow to remove specific music into the queue with a number if parameter and parameter.isdigit() and 0 < int(parameter) <= len( var.playlist): index = int(parameter) - 1 if index == var.playlist.current_index: removed = var.playlist[index] bot.send_msg(tr('removing_item', item=removed.format_title()), text) log.info("cmd: delete from playlist: " + removed.format_debug_string()) var.playlist.remove(index) if index < len(var.playlist): if not bot.is_pause: bot.interrupt() var.playlist.current_index -= 1 # then the bot will move to next item else: # if item deleted is the last item of the queue var.playlist.current_index -= 1 if not bot.is_pause: bot.interrupt() else: var.playlist.remove(index) else: bot.send_msg(tr('bad_parameter', command=command), text)
def cmd_find_tagged(bot, user, text, command, parameter): global song_shortlist if not parameter: bot.send_msg(tr('bad_parameter', command=command), text) return msgs = [tr('multiple_file_found') + "<ul>"] count = 0 tags = parameter.split(",") tags = list(map(lambda t: t.strip(), tags)) music_dicts = var.music_db.query_music_by_tags(tags) song_shortlist = music_dicts for i, music_dict in enumerate(music_dicts): item = dict_to_item(music_dict) count += 1 if count > ITEMS_PER_PAGE: break msgs.append("<li><b>{:d}</b> - <b>{}</b> (<i>{}</i>)</li>".format( i + 1, item.title, ", ".join(item.tags))) if count != 0: msgs.append("</ul>") if count > ITEMS_PER_PAGE: msgs.append(tr("records_omitted")) msgs.append(tr("shortlist_instruction")) send_multi_lines(bot, msgs, text, "") else: bot.send_msg(tr("no_file"), text)
def cmd_play(bot, user, text, command, parameter): global log params = parameter.split() index = -1 start_at = 0 if len(params) > 0: if params[0].isdigit() and 1 <= int(params[0]) <= len(var.playlist): index = int(params[0]) else: bot.send_msg(tr('invalid_index', index=parameter), text) return if len(params) > 1: try: start_at = util.parse_time(params[1]) except ValueError: bot.send_msg(tr('bad_parameter', command=command), text) return if len(var.playlist) > 0: if index != -1: bot.play(int(index) - 1, start_at) elif bot.is_pause: bot.resume() else: bot.send_msg(var.playlist.current_item().format_current_playing(), text) else: bot.is_pause = False bot.send_msg(tr('queue_empty'), text)
def cmd_add_tag(bot, user, text, command, parameter): global log params = parameter.split(" ", 1) index = 0 tags = [] if len(params) == 2 and params[0].isdigit(): index = params[0] tags = list(map(lambda t: t.strip(), params[1].split(","))) elif len(params) == 2 and params[0] == "*": index = "*" tags = list(map(lambda t: t.strip(), params[1].split(","))) else: index = str(var.playlist.current_index + 1) tags = list(map(lambda t: t.strip(), parameter.split(","))) if tags[0]: if index.isdigit() and 1 <= int(index) <= len(var.playlist): var.playlist[int(index) - 1].add_tags(tags) log.info(f"cmd: add tags {', '.join(tags)} to song {var.playlist[int(index) - 1].format_debug_string()}") bot.send_msg(tr("added_tags", tags=", ".join(tags), song=var.playlist[int(index) - 1].format_title()), text) return elif index == "*": for item in var.playlist: item.add_tags(tags) log.info(f"cmd: add tags {', '.join(tags)} to song {item.format_debug_string()}") bot.send_msg(tr("added_tags_to_all", tags=", ".join(tags)), text) return bot.send_msg(tr('bad_parameter', command=command), text)
def cmd_yt_search(bot, user, text, command, parameter): global log, yt_last_result, yt_last_page, song_shortlist item_per_page = 5 if parameter: # if next page if parameter.startswith("-n"): yt_last_page += 1 if len(yt_last_result) > yt_last_page * item_per_page: song_shortlist = [{'type': 'url', 'url': "https://www.youtube.com/watch?v=" + result[0], 'title': result[1] } for result in yt_last_result[yt_last_page * item_per_page: item_per_page]] msg = _yt_format_result(yt_last_result, yt_last_page * item_per_page, item_per_page) bot.send_msg(tr('yt_result', result_table=msg), text) else: bot.send_msg(tr('yt_no_more'), text) # if query else: results = util.youtube_search(parameter) if results: yt_last_result = results yt_last_page = 0 song_shortlist = [{'type': 'url', 'url': "https://www.youtube.com/watch?v=" + result[0]} for result in results[0: item_per_page]] msg = _yt_format_result(results, 0, item_per_page) bot.send_msg(tr('yt_result', result_table=msg), text) else: bot.send_msg(tr('yt_query_error'), text) else: bot.send_msg(tr('bad_parameter', command=command), text)
def cmd_play_radio(bot, user, text, command, parameter): global log if not parameter: all_radio = var.config.items('radio') msg = tr('preconfigurated_radio') for i in all_radio: comment = "" if len(i[1].split(maxsplit=1)) == 2: comment = " - " + i[1].split(maxsplit=1)[1] msg += "<br />" + i[0] + comment bot.send_msg(msg, text) else: if var.config.has_option('radio', parameter): parameter = var.config.get('radio', parameter) parameter = parameter.split()[0] url = util.get_url_from_input(parameter) if url: music_wrapper = get_cached_wrapper_from_scrap(type='radio', url=url, user=user) var.playlist.append(music_wrapper) log.info("cmd: add to playlist: " + music_wrapper.format_debug_string()) send_item_added_message(bot, music_wrapper, len(var.playlist) - 1, text) else: bot.send_msg(tr('bad_url'), text)
def cmd_list_file(bot, user, text, command, parameter): global song_shortlist files = var.music_db.query_music(Condition().and_equal( 'type', 'file').order_by('path')) song_shortlist = files msgs = [tr("multiple_file_found") + "<ul>"] try: count = 0 for index, file in enumerate(files): if parameter: match = re.search(parameter, file['path']) if not match: continue count += 1 if count > ITEMS_PER_PAGE: break msgs.append("<li><b>{:d}</b> - <b>{:s}</b> ({:s})</li>".format( index + 1, file['title'], file['path'])) if count != 0: msgs.append("</ul>") if count > ITEMS_PER_PAGE: msgs.append(tr("records_omitted")) msgs.append(tr("shortlist_instruction")) send_multi_lines(bot, msgs, text, "") else: bot.send_msg(tr("no_file"), text) except re.error as e: msg = tr('wrong_pattern', error=str(e)) bot.send_msg(msg, text)
def cmd_rb_query(bot, user, text, command, parameter): global log log.info('cmd: Querying radio stations') if not parameter: log.debug('rbquery without parameter') msg = tr('rb_query_empty') bot.send_msg(msg, text) else: log.debug('cmd: Found query parameter: ' + parameter) rb = RadioBrowser() rb_stations = rb.search(name=parameter, name_exact=False) msg = tr('rb_query_result') msg += '\n<table><tr><th>!rbplay ID</th><th>Station Name</th><th>Genre</th><th>Codec/Bitrate</th><th>Country</th></tr>' if not rb_stations: log.debug(f"cmd: No matches found for rbquery {parameter}") bot.send_msg(f"Radio-Browser found no matches for {parameter}", text) else: for s in rb_stations: station_id = s['stationuuid'] station_name = s['name'] country = s['countrycode'] codec = s['codec'] bitrate = s['bitrate'] genre = s['tags'] msg += f"<tr><td>{station_id}</td><td>{station_name}</td><td>{genre}</td><td>{codec}/{bitrate}</td><td>{country}</td></tr>" msg += '</table>' # Full message as html table if len(msg) <= 5000: bot.send_msg(msg, text) # Shorten message if message too long (stage I) else: log.debug('Result too long stage I') msg = tr('rb_query_result') + ' (shortened L1)' msg += '\n<table><tr><th>!rbplay ID</th><th>Station Name</th></tr>' for s in rb_stations: station_id = s['stationuuid'] station_name = s['name'] msg += f'<tr><td>{station_id}</td><td>{station_name}</td>' msg += '</table>' if len(msg) <= 5000: bot.send_msg(msg, text) # Shorten message if message too long (stage II) else: log.debug('Result too long stage II') msg = tr('rb_query_result') + ' (shortened L2)' msg += '!rbplay ID - Station Name' for s in rb_stations: station_id = s['stationuuid'] station_name = s['name'][:12] msg += f'{station_id} - {station_name}' if len(msg) <= 5000: bot.send_msg(msg, text) # Message still too long else: bot.send_msg( 'Query result too long to post (> 5000 characters), please try another query.', text)
def cmd_refresh_cache(bot, user, text, command, parameter): global log if bot.is_admin(user): var.cache.build_dir_cache() log.info("command: Local file cache refreshed.") bot.send_msg(tr('cache_refreshed'), text) else: bot.mumble.users[text.actor].send_text_message(tr('not_admin'))
def cmd_web_user_list(bot, user, text, command, parameter): auth_method = var.config.get("webinterface", "auth_method") if auth_method == 'password': web_users = json.loads(var.db.get("privilege", "web_access", fallback='[]')) bot.send_msg(tr('web_user_list', users=", ".join(web_users)), text) else: bot.send_msg(tr('command_disabled', command=command), text)
def cmd_shortlist(bot, user, text, command, parameter): global song_shortlist, log if parameter.strip() == "*": msgs = [tr('multiple_file_added') + "<ul>"] music_wrappers = [] for kwargs in song_shortlist: kwargs['user'] = user music_wrapper = get_cached_wrapper_from_scrap(**kwargs) music_wrappers.append(music_wrapper) log.info("cmd: add to playlist: " + music_wrapper.format_debug_string()) msgs.append("<li>[{}] <b>{}</b></li>".format(music_wrapper.item().type, music_wrapper.item().title)) var.playlist.extend(music_wrappers) msgs.append("</ul>") send_multi_lines_in_channel(bot, msgs, "") return try: indexes = [int(i) for i in parameter.split(" ")] except ValueError: bot.send_msg(tr('bad_parameter', command=command), text) return if len(indexes) > 1: msgs = [tr('multiple_file_added') + "<ul>"] music_wrappers = [] for index in indexes: if 1 <= index <= len(song_shortlist): kwargs = song_shortlist[index - 1] kwargs['user'] = user music_wrapper = get_cached_wrapper_from_scrap(**kwargs) music_wrappers.append(music_wrapper) log.info("cmd: add to playlist: " + music_wrapper.format_debug_string()) msgs.append("<li>[{}] <b>{}</b></li>".format(music_wrapper.item().type, music_wrapper.item().title)) else: var.playlist.extend(music_wrappers) bot.send_msg(tr('bad_parameter', command=command), text) return var.playlist.extend(music_wrappers) msgs.append("</ul>") send_multi_lines_in_channel(bot, msgs, "") return elif len(indexes) == 1: index = indexes[0] if 1 <= index <= len(song_shortlist): kwargs = song_shortlist[index - 1] kwargs['user'] = user music_wrapper = get_cached_wrapper_from_scrap(**kwargs) var.playlist.append(music_wrapper) log.info("cmd: add to playlist: " + music_wrapper.format_debug_string()) send_item_added_message(bot, music_wrapper, len(var.playlist) - 1, text) return bot.send_msg(tr('bad_parameter', command=command), text)
def cmd_update(bot, user, text, command, parameter): global log if bot.is_admin(user): bot.mumble.users[text.actor].send_text_message(tr('start_updating')) msg = util.update(bot.version) bot.mumble.users[text.actor].send_text_message(msg) else: bot.mumble.users[text.actor].send_text_message(tr('not_admin'))
def cmd_ducking_volume(bot, user, text, command, parameter): global log # The volume is a percentage if parameter and parameter.isdigit() and 0 <= int(parameter) <= 100: bot.volume_helper.set_ducking_volume(float(parameter) / 100.0) bot.send_msg(tr('change_ducking_volume', volume=parameter, user=bot.mumble.users[text.actor]['name']), text) var.db.set('bot', 'ducking_volume', float(parameter) / 100.0) log.info(f'cmd: volume on ducking set to {parameter}') else: bot.send_msg(tr('current_ducking_volume', volume=int(bot.volume_helper.plain_ducking_volume_set * 100)), text)
def cmd_user_password(bot, user, text, command, parameter): if not parameter: bot.send_msg(tr('bad_parameter', command=command), text) return user_info = var.db.get("user", user, fallback='{}') user_dict = json.loads(user_info) user_dict['password'], user_dict['salt'] = util.get_salted_password_hash(parameter) var.db.set("user", user, json.dumps(user_dict)) bot.send_msg(tr('user_password_set'), text)
def cmd_drop_database(bot, user, text, command, parameter): global log if bot.is_admin(user): var.db.drop_table() var.db = SettingsDatabase(var.settings_db_path) var.music_db.drop_table() var.music_db = MusicDatabase(var.settings_db_path) log.info("command: database dropped.") bot.send_msg(tr('database_dropped'), text) else: bot.mumble.users[text.actor].send_text_message(tr('not_admin'))
def cmd_play_file(bot, user, text, command, parameter, do_not_refresh_cache=False): global log, song_shortlist # assume parameter is a path music_wrappers = get_cached_wrappers_from_dicts(var.music_db.query_music(Condition().and_equal('path', parameter)), user) if music_wrappers: var.playlist.append(music_wrappers[0]) log.info("cmd: add to playlist: " + music_wrappers[0].format_debug_string()) send_item_added_message(bot, music_wrappers[0], len(var.playlist) - 1, text) return # assume parameter is a folder music_wrappers = get_cached_wrappers_from_dicts(var.music_db.query_music(Condition() .and_equal('type', 'file') .and_like('path', parameter + '%')), user) if music_wrappers: msgs = [tr('multiple_file_added')] for music_wrapper in music_wrappers: log.info("cmd: add to playlist: " + music_wrapper.format_debug_string()) msgs.append("<b>{:s}</b> ({:s})".format(music_wrapper.item().title, music_wrapper.item().path)) var.playlist.extend(music_wrappers) send_multi_lines_in_channel(bot, msgs) return # try to do a partial match matches = var.music_db.query_music(Condition() .and_equal('type', 'file') .and_like('path', '%' + parameter + '%', case_sensitive=False)) if len(matches) == 1: music_wrapper = get_cached_wrapper_from_dict(matches[0], user) var.playlist.append(music_wrapper) log.info("cmd: add to playlist: " + music_wrapper.format_debug_string()) send_item_added_message(bot, music_wrapper, len(var.playlist) - 1, text) return elif len(matches) > 1: song_shortlist = matches msgs = [tr('multiple_matches')] for index, match in enumerate(matches): msgs.append("<b>{:d}</b> - <b>{:s}</b> ({:s})".format( index + 1, match['title'], match['path'])) msgs.append(tr("shortlist_instruction")) send_multi_lines(bot, msgs, text) return if do_not_refresh_cache: bot.send_msg(tr("no_file"), text) else: var.cache.build_dir_cache() cmd_play_file(bot, user, text, command, parameter, do_not_refresh_cache=True)
def cmd_play_file_match(bot, user, text, command, parameter, do_not_refresh_cache=False): global log if parameter: file_dicts = var.music_db.query_music(Condition().and_equal( 'type', 'file')) msgs = [tr('multiple_file_added') + "<ul>"] try: count = 0 music_wrappers = [] for file_dict in file_dicts: file = file_dict['title'] match = re.search(parameter, file) if match and match[0]: count += 1 music_wrapper = get_cached_wrapper(dict_to_item(file_dict), user) music_wrappers.append(music_wrapper) log.info("cmd: add to playlist: " + music_wrapper.format_debug_string()) msgs.append("<li><b>{}</b> ({})</li>".format( music_wrapper.item().title, file[:match.span()[0]] + "<b style='color:pink'>" + file[match.span()[0]:match.span()[1]] + "</b>" + file[match.span()[1]:])) if count != 0: msgs.append("</ul>") var.playlist.extend(music_wrappers) send_multi_lines_in_channel(bot, msgs, "") else: if do_not_refresh_cache: bot.send_msg(tr("no_file"), text) else: var.cache.build_dir_cache() cmd_play_file_match(bot, user, text, command, parameter, do_not_refresh_cache=True) except re.error as e: msg = tr('wrong_pattern', error=str(e)) bot.send_msg(msg, text) else: bot.send_msg(tr('bad_parameter', command=command), text)
def cmd_yt_play(bot, user, text, command, parameter): global log, yt_last_result, yt_last_page if parameter: results = util.youtube_search(parameter) if results: yt_last_result = results yt_last_page = 0 url = "https://www.youtube.com/watch?v=" + yt_last_result[0][0] cmd_play_url(bot, user, text, command, url) else: bot.send_msg(tr('yt_query_error'), text) else: bot.send_msg(tr('bad_parameter', command=command), text)
def cmd_web_user_remove(bot, user, text, command, parameter): if not parameter: bot.send_msg(tr('bad_parameter', command=command), text) return auth_method = var.config.get("webinterface", "auth_method") if auth_method == 'password': web_users = json.loads(var.db.get("privilege", "web_access", fallback='[]')) if parameter in web_users: web_users.remove(parameter) var.db.set("privilege", "web_access", json.dumps(web_users)) bot.send_msg(tr('web_user_list', users=", ".join(web_users)), text) else: bot.send_msg(tr('command_disabled', command=command), text)
def cmd_rb_play(bot, user, text, command, parameter): global log log.debug('cmd: Play a station by ID') if not parameter: log.debug('rbplay without parameter') msg = tr('rb_play_empty') bot.send_msg(msg, text) else: log.debug('cmd: Retreiving url for station ID ' + parameter) rb = RadioBrowser() rstation = rb.station_by_uuid(parameter) stationname = rstation[0]['name'] country = rstation[0]['countrycode'] codec = rstation[0]['codec'] bitrate = rstation[0]['bitrate'] genre = rstation[0]['tags'] homepage = rstation[0]['homepage'] url = rstation[0]['url'] msg = 'Radio station added to playlist:' msg += '<table><tr><th>ID</th><th>Station Name</th><th>Genre</th><th>Codec/Bitrate</th><th>Country</th><th>Homepage</th></tr>' + \ f"<tr><td>{parameter}</td><td>{stationname}</td><td>{genre}</td><td>{codec}/{bitrate}</td><td>{country}</td><td>{homepage}</td></tr></table>" log.debug(f'cmd: Added station to playlist {stationname}') bot.send_msg(msg, text) if url != "-1": log.info('cmd: Found url: ' + url) music_wrapper = get_cached_wrapper_from_scrap(type='radio', url=url, name=stationname, user=user) var.playlist.append(music_wrapper) log.info("cmd: add to playlist: " + music_wrapper.format_debug_string()) bot.async_download_next() else: log.info('cmd: No playable url found.') msg += "No playable url found for this station, please try another station." bot.send_msg(msg, text)
def cmd_current_music(bot, user, text, command, parameter): global log if len(var.playlist) > 0: bot.send_msg(var.playlist.current_item().format_current_playing(), text) else: bot.send_msg(tr('not_playing'), text)
def cmd_web_access(bot, user, text, command, parameter): auth_method = var.config.get("webinterface", "auth_method") if auth_method == 'token': interface.banned_ip = [] interface.bad_access_count = {} user_info = var.db.get("user", user, fallback='{}') user_dict = json.loads(user_info) if 'token' in user_dict: var.db.remove_option("web_token", user_dict['token']) token = secrets.token_urlsafe(5) user_dict['token'] = token user_dict['token_created'] = str(datetime.datetime.now()) user_dict['last_ip'] = '' var.db.set("web_token", token, user) var.db.set("user", user, json.dumps(user_dict)) access_address = var.config.get("webinterface", "access_address") + "/?token=" + token else: access_address = var.config.get("webinterface", "access_address") bot.send_msg(tr('webpage_address', address=access_address), text)
def format_song_string(self, user): return tr("url_from_playlist_item", title=self.title, url=self.url, playlist_url=self.playlist_url, playlist=self.playlist_title, user=user)
def users_changed(self, user, message): own_channel = self.mumble.channels[ self.mumble.users.myself['channel_id']] # only check if there is one more user currently in the channel # else when the music is paused and somebody joins, music would start playing again if len(own_channel.get_users()) == 2: if var.config.get("bot", "when_nobody_in_channel") == "pause_resume": self.resume() elif var.config.get( "bot", "when_nobody_in_channel") == "pause" and self.is_pause: self.send_channel_msg(tr("auto_paused")) self.timeout_cancelled.set() elif len(own_channel.get_users()) == 1 and len(var.playlist) != 0: # if the bot is the only user left in the channel and the playlist isn't empty self.log.info( 'bot: Other users in the channel left. Stopping music now.') if var.config.get("bot", "when_nobody_in_channel") == "stop": self.clear() else: self.pause() if self.timeout_cancelled.is_set(): self.timeout_cancelled.clear() threading.Thread(target=self.leave_on_timeout, daemon=True).start() self.log.info("Timer started. Leaving in 120 seconds")
def users_changed(self, join, user, message_=None): own_channel = self.mumble.channels[ self.mumble.users.myself['channel_id']] users = {i.get_property("name") for i in own_channel.get_users()} users -= {"IRC", "mumsi", "Music"} # only check if there is one more user currently in the channel # else when the music is paused and somebody joins, music would start playing again if len(users) == 1 and join: if var.config.get("bot", "when_nobody_in_channel") == "pause_resume": self.resume() elif var.config.get("bot", "when_nobody_in_channel") == "pause": self.send_channel_msg(tr("auto_paused")) elif len(users) == 0: # if the bot is the only user left in the channel self.log.info( 'bot: Other users in the channel left. Stopping music now.') if var.config.get("bot", "when_nobody_in_channel") == "stop": self.send_channel_msg("Playing stopped.") self.clear() else: self.send_channel_msg("Playing paused.") self.pause()
def format_song_string(self, user): if self.ready in ['validated', 'yes']: return tr("url_item", title=self.title if self.title else "??", url=self.url, user=user) return self.url
def _get_info_from_url(self): self.log.info("url: fetching metadata of url %s " % self.url) ydl_opts = {'noplaylist': True} succeed = False with youtube_dl.YoutubeDL(ydl_opts) as ydl: attempts = var.config.getint('bot', 'download_attempts', fallback=2) for i in range(attempts): try: info = ydl.extract_info(self.url, download=False) self.duration = info['duration'] self.title = info['title'] self.keywords = info['title'] succeed = True return True except youtube_dl.utils.DownloadError: pass except KeyError: # info has no 'duration' break if not succeed: self.ready = 'failed' self.log.error("url: error while fetching info from the URL") raise ValidationFailedError( tr('unable_download', item=self.format_title()))
def validate_and_start_download(self, item): item.validate() if not item.is_ready(): self.log.info("bot: current music isn't ready, start downloading.") self.async_download(item) self.send_channel_msg( tr('download_in_progress', item=item.format_title()))
def validate(self): self.validating_lock.acquire() if self.ready in ['yes', 'validated']: self.validating_lock.release() return True # if self.ready == 'failed': # self.validating_lock.release() # return False # if os.path.exists(self.path): self.validating_lock.release() self.ready = "yes" return True # avoid multiple process validating in the meantime info = self._get_info_from_url() self.validating_lock.release() if not info: return False if self.duration > var.config.getint('bot', 'max_track_duration') * 60 != 0: # Check the length, useful in case of playlist, it wasn't checked before) log.info("url: " + self.url + " has a duration of " + str(self.duration / 60) + " min -- too long") raise ValidationFailedError(tr('too_long', song=self.title)) else: self.ready = "validated" self.version += 1 # notify wrapper to save me return True
def format_song_string(self, user): return tr( "radio_item", url=self.url, title=get_radio_title(self.url), # the title of current song name=self.title, # the title of radio station user=user)