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( constants.strings('download_in_progress', item=item.format_title()))
def check_update(self): self.log.debug("update: checking for updates...") new_version = util.new_release_version() if version.parse(new_version) > version.parse(self.version): changelog = util.fetch_changelog() self.log.info( "update: new version %s found, current installed version %s." % (new_version, self.version)) self.log.info("update: changelog: " + changelog) changelog = changelog.replace("\n", "<br>") self.send_channel_msg( constants.strings('new_version_found', new_version=new_version, changelog=changelog)) else: self.log.debug("update: no new version found.")
def check_update(self): self.log.debug("update: checking for updates...") new_version = util.new_release_version( var.config.get('bot', 'target_version')) if version.parse(new_version) > version.parse( self.version) and var.config.get('bot', 'target_version') == "stable": changelog = util.fetch_changelog() self.log.info( f"update: new version {new_version} found, current installed version {self.version}." ) self.log.info(f"update: changelog: {changelog}") changelog = changelog.replace("\n", "<br>") self.send_channel_msg( constants.strings('new_version_found', new_version=new_version, changelog=changelog)) else: self.log.debug("update: no new version found.")
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": self.send_channel_msg(constants.strings("auto_paused")) elif len(own_channel.get_users()) == 1: # 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.clear() else: self.pause()
def loop(self): raw_music = "" while not self.exit and self.mumble.is_alive(): while self.is_ffmpeg_running( ) and self.mumble.sound_output.get_buffer_size( ) > 0.5 and not self.exit: # If the buffer isn't empty, I cannot send new music part, so I wait self._loop_status = f'Wait for buffer {self.mumble.sound_output.get_buffer_size():.3f}' time.sleep(0.01) if self.is_ffmpeg_running(): # I get raw from ffmpeg thread # move playhead forward self._loop_status = 'Reading raw' if self.song_start_at == -1: self.song_start_at = time.time() - self.playhead self.playhead = time.time() - self.song_start_at raw_music = self.thread.stdout.read(self.pcm_buffer_size) self.read_pcm_size += self.pcm_buffer_size if self.redirect_ffmpeg_log: try: self.last_ffmpeg_err = self.thread_stderr.readline() if self.last_ffmpeg_err: self.log.debug("ffmpeg: " + self.last_ffmpeg_err.strip("\n")) except: pass if raw_music: # Adjust the volume and send it to mumble self.volume_cycle() if not self.on_interrupting: self.mumble.sound_output.add_sound( audioop.mul(raw_music, 2, self.volume_helper.real_volume)) else: self.mumble.sound_output.add_sound( audioop.mul(self._fadeout(raw_music, self.stereo), 2, self.volume_helper.real_volume)) self.thread.kill() time.sleep(0.1) self.on_interrupting = False else: time.sleep(0.1) else: time.sleep(0.1) if not self.is_pause and not self.is_ffmpeg_running(): # bot is not paused, but ffmpeg thread has gone. # indicate that last song has finished, or the bot just resumed from pause, or something is wrong. if self.read_pcm_size < self.pcm_buffer_size and len(var.playlist) > 0 \ and var.playlist.current_index != -1 \ and self.last_ffmpeg_err: current = var.playlist.current_item() self.log.error("bot: cannot play music %s", current.format_debug_string()) self.log.error("bot: with ffmpeg error: %s", self.last_ffmpeg_err) self.last_ffmpeg_err = "" self.send_channel_msg( constants.strings('unable_play', item=current.format_title())) var.playlist.remove_by_id(current.id) var.cache.free_and_delete(current.id) # move to the next song. if not self.wait_for_ready: # if wait_for_ready flag is not true, move to the next song. if var.playlist.next(): current = var.playlist.current_item() self.log.debug( f"bot: next into the song: {current.format_debug_string()}" ) try: self.validate_and_start_download(current) self.wait_for_ready = True self.song_start_at = -1 self.playhead = 0 except ValidationFailedError as e: self.send_channel_msg(e.msg) var.playlist.remove_by_id(current.id) var.cache.free_and_delete(current.id) else: self._loop_status = 'Empty queue' else: # if wait_for_ready flag is true, means the pointer is already # pointing to target song. start playing current = var.playlist.current_item() if current: if current.is_ready(): self.wait_for_ready = False self.read_pcm_size = 0 self.launch_music(current, self.playhead) self.last_volume_cycle_time = time.time() self.async_download_next() elif current.is_failed(): var.playlist.remove_by_id(current.id) self.wait_for_ready = False else: self._loop_status = 'Wait for the next item to be ready' else: self.wait_for_ready = False while self.mumble.sound_output.get_buffer_size() > 0: # Empty the buffer before exit time.sleep(0.01) time.sleep(0.5) if self.exit: self._loop_status = "exited" if var.config.getboolean('bot', 'save_playlist', fallback=True) \ and var.config.get("bot", "save_music_library", fallback=True): self.log.info("bot: save playlist into database") var.playlist.save()
def __init__(self, args): self.log = logging.getLogger("bot") self.log.info(f"bot: botamusique version {self.version}, starting...") signal.signal(signal.SIGINT, self.ctrl_caught) self.cmd_handle = {} self.stereo = var.config.getboolean('bot', 'stereo', fallback=True) if args.channel: self.channel = args.channel else: self.channel = var.config.get("server", "channel", fallback=None) var.user = args.user var.is_proxified = var.config.getboolean("webinterface", "is_web_proxified") # Flags to indicate the bot is exiting (Ctrl-C, or !kill) self.exit = False self.nb_exit = 0 # Related to ffmpeg thread self.thread = None self.thread_stderr = None self.read_pcm_size = 0 self.pcm_buffer_size = 0 self.last_ffmpeg_err = "" # Play/pause status self.is_pause = False self.pause_at_id = "" self.playhead = -1 # current position in a song. self.song_start_at = -1 self.wait_for_ready = False # flag for the loop are waiting for download to complete in the other thread # self.on_interrupting = False if args.host: host = args.host else: host = var.config.get("server", "host") if args.port: port = args.port else: port = var.config.getint("server", "port") if args.password: password = args.password else: password = var.config.get("server", "password") if args.channel: self.channel = args.channel else: self.channel = var.config.get("server", "channel") if args.certificate: certificate = args.certificate else: certificate = util.solve_filepath( var.config.get("server", "certificate")) if args.tokens: tokens = args.tokens else: tokens = var.config.get("server", "tokens") tokens = tokens.split(',') if args.user: self.username = args.user else: self.username = var.config.get("bot", "username") self.mumble = pymumble.Mumble(host, user=self.username, port=port, password=password, tokens=tokens, stereo=self.stereo, debug=var.config.getboolean( 'debug', 'mumbleConnection'), certfile=certificate) self.mumble.callbacks.set_callback( pymumble.constants.PYMUMBLE_CLBK_TEXTMESSAGERECEIVED, self.message_received) self.mumble.set_codec_profile("audio") self.mumble.start() # start the mumble thread self.mumble.is_ready() # wait for the connection if self.mumble.connected >= pymumble_py3.constants.PYMUMBLE_CONN_STATE_FAILED: exit() self.set_comment() self.mumble.users.myself.unmute() # by sure the user is not muted self.join_channel() self.mumble.set_bandwidth(200000) # ====== Volume ====== self.volume_helper = util.VolumeHelper() _volume = var.config.getfloat('bot', 'volume', fallback=0.1) if var.db.has_option('bot', 'volume'): _volume = var.db.getfloat('bot', 'volume') self.volume_helper.set_volume(_volume) self.is_ducking = False self.on_ducking = False self.ducking_release = time.time() self.last_volume_cycle_time = time.time() self._ducking_volume = 0 _ducking_volume = var.config.getfloat("bot", "ducking_volume", fallback=0.05) _ducking_volume = var.db.getfloat("bot", "ducking_volume", fallback=_ducking_volume) self.volume_helper.set_ducking_volume(_ducking_volume) self.ducking_threshold = var.config.getfloat("bot", "ducking_threshold", fallback=5000) self.ducking_threshold = var.db.getfloat( "bot", "ducking_threshold", fallback=self.ducking_threshold) if not var.db.has_option("bot", "ducking") and var.config.getboolean("bot", "ducking", fallback=False) \ or var.config.getboolean("bot", "ducking"): self.is_ducking = True self.mumble.callbacks.set_callback( pymumble.constants.PYMUMBLE_CLBK_SOUNDRECEIVED, self.ducking_sound_received) self.mumble.set_receive_sound(True) assert var.config.get("bot", "when_nobody_in_channel") in ['pause', 'pause_resume', 'stop', 'nothing', ''], \ "Unknown action for when_nobody_in_channel" if var.config.get("bot", "when_nobody_in_channel", fallback='') in ['pause', 'pause_resume', 'stop']: user_change_callback = \ lambda user, action: threading.Thread(target=self.users_changed, args=(user, action), daemon=True).start() self.mumble.callbacks.set_callback( pymumble.constants.PYMUMBLE_CLBK_USERREMOVED, user_change_callback) self.mumble.callbacks.set_callback( pymumble.constants.PYMUMBLE_CLBK_USERUPDATED, user_change_callback) # Debug use self._loop_status = 'Idle' self._display_rms = False self._max_rms = 0 self.redirect_ffmpeg_log = var.config.getboolean('debug', 'redirect_ffmpeg_log', fallback=True) if var.config.getboolean("bot", "auto_check_update"): th = threading.Thread(target=self.check_update, name="UpdateThread") th.daemon = True th.start() last_startup_version = var.db.get("bot", "version", fallback=None) if not last_startup_version or version.parse( last_startup_version) < version.parse(self.version): var.db.set("bot", "version", self.version) changelog = util.fetch_changelog().replace("\n", "<br>") self.send_channel_msg( constants.strings("update_successful", version=self.version, changelog=changelog))
def message_received(self, text): message = text.message.strip() user = self.mumble.users[text.actor]['name'] if var.config.getboolean('commands', 'split_username_at_space'): # in can you use https://github.com/Natenom/mumblemoderator-module-collection/tree/master/os-suffixes , # you want to split the username user = user.split()[0] if message[0] in var.config.get('commands', 'command_symbol'): # remove the symbol from the message message = message[1:].split(' ', 1) # use the first word as a command, the others one as parameters if len(message) > 0: command = message[0].lower() parameter = '' if len(message) > 1: parameter = message[1].rstrip() else: return self.log.info('bot: received command ' + command + ' - ' + parameter + ' by ' + user) # Anti stupid guy function if not self.is_admin(user) and not var.config.getboolean( 'bot', 'allow_private_message') and text.session: self.mumble.users[text.actor].send_text_message( constants.strings('pm_not_allowed')) return for i in var.db.items("user_ban"): if user.lower() == i[0]: self.mumble.users[text.actor].send_text_message( constants.strings('user_ban')) return if not self.is_admin(user) and parameter: input_url = util.get_url_from_input(parameter) if input_url: for i in var.db.items("url_ban"): if input_url == i[0]: self.mumble.users[text.actor].send_text_message( constants.strings('url_ban')) return command_exc = "" try: if command in self.cmd_handle: command_exc = command else: # try partial match cmds = self.cmd_handle.keys() matches = [] for cmd in cmds: if cmd.startswith(command) and self.cmd_handle[cmd][ 'partial_match']: matches.append(cmd) if len(matches) == 1: self.log.info("bot: {:s} matches {:s}".format( command, matches[0])) command_exc = matches[0] elif len(matches) > 1: self.mumble.users[text.actor].send_text_message( constants.strings('which_command', commands="<br>".join(matches))) return else: self.mumble.users[text.actor].send_text_message( constants.strings('bad_command', command=command)) return if self.cmd_handle[command_exc]['admin'] and not self.is_admin( user): self.mumble.users[text.actor].send_text_message( constants.strings('not_admin')) return if not self.cmd_handle[command_exc]['access_outside_channel'] \ and not self.is_admin(user) \ and not var.config.getboolean('bot', 'allow_other_channel_message') \ and self.mumble.users[text.actor]['channel_id'] != self.mumble.users.myself['channel_id']: self.mumble.users[text.actor].send_text_message( constants.strings('not_in_my_channel')) return self.cmd_handle[command_exc]['handle'](self, user, text, command_exc, parameter) except: error_traceback = traceback.format_exc() error = error_traceback.rstrip().split("\n")[-1] self.log.error( f"bot: command {command_exc} failed with error: {error_traceback}\n" ) self.send_msg( constants.strings('error_executing_command', command=command_exc, error=error), text)