diff --git a/README.md b/README.md index d93a23a..661b65a 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,6 @@ tar -xzf botamusique.tar.gz cd botamusique python3 -m venv venv venv/bin/pip install wheel -venv/bin/pip install -r pymumble/requirements.txt venv/bin/pip install -r requirements.txt ``` diff --git a/command.py b/command.py index 1791d6f..7918616 100644 --- a/command.py +++ b/command.py @@ -1,9 +1,10 @@ # coding=utf-8 import logging -import math - -import pymumble_py3 as pymumble +import secrets +import datetime +import json import re +import pymumble_py3 as pymumble import constants import interface @@ -21,12 +22,12 @@ log = logging.getLogger("bot") def register_all_commands(bot): - bot.register_command(constants.commands('joinme'), cmd_joinme, no_partial_match=False, access_outside_channel=True) - bot.register_command(constants.commands('user_ban'), cmd_user_ban, no_partial_match=True) - bot.register_command(constants.commands('user_unban'), cmd_user_unban, no_partial_match=True) - bot.register_command(constants.commands('url_ban_list'), cmd_url_ban_list, no_partial_match=True) - bot.register_command(constants.commands('url_ban'), cmd_url_ban, no_partial_match=True) - bot.register_command(constants.commands('url_unban'), cmd_url_unban, no_partial_match=True) + bot.register_command(constants.commands('joinme'), cmd_joinme, access_outside_channel=True) + bot.register_command(constants.commands('user_ban'), cmd_user_ban, no_partial_match=True, admin=True) + bot.register_command(constants.commands('user_unban'), cmd_user_unban, no_partial_match=True, admin=True) + bot.register_command(constants.commands('url_ban_list'), cmd_url_ban_list, no_partial_match=True, admin=True) + bot.register_command(constants.commands('url_ban'), cmd_url_ban, no_partial_match=True, admin=True) + bot.register_command(constants.commands('url_unban'), cmd_url_unban, no_partial_match=True, admin=True) bot.register_command(constants.commands('play'), cmd_play) bot.register_command(constants.commands('pause'), cmd_pause) bot.register_command(constants.commands('play_file'), cmd_play_file) @@ -42,8 +43,8 @@ def register_all_commands(bot): bot.register_command(constants.commands('help'), cmd_help, no_partial_match=False, access_outside_channel=True) bot.register_command(constants.commands('stop'), cmd_stop) bot.register_command(constants.commands('clear'), cmd_clear) - bot.register_command(constants.commands('kill'), cmd_kill) - bot.register_command(constants.commands('update'), cmd_update, no_partial_match=True) + bot.register_command(constants.commands('kill'), cmd_kill, admin=True) + bot.register_command(constants.commands('update'), cmd_update, no_partial_match=True, admin=True) bot.register_command(constants.commands('stop_and_getout'), cmd_stop_and_getout) bot.register_command(constants.commands('volume'), cmd_volume) bot.register_command(constants.commands('ducking'), cmd_ducking) @@ -64,9 +65,13 @@ def register_all_commands(bot): bot.register_command(constants.commands('search'), cmd_search_library) bot.register_command(constants.commands('add_from_shortlist'), cmd_shortlist) bot.register_command(constants.commands('delete_from_library'), cmd_delete_from_library) - bot.register_command(constants.commands('drop_database'), cmd_drop_database, no_partial_match=True) + bot.register_command(constants.commands('drop_database'), cmd_drop_database, no_partial_match=True, admin=True) bot.register_command(constants.commands('rescan'), cmd_refresh_cache, no_partial_match=True) bot.register_command(constants.commands('requests_webinterface_access'), cmd_web_access) + bot.register_command(constants.commands('add_webinterface_user'), cmd_web_user_add, admin=True) + bot.register_command(constants.commands('remove_webinterface_user'), cmd_web_user_remove, admin=True) + bot.register_command(constants.commands('list_webinterface_user'), cmd_web_user_list, admin=True) + bot.register_command(constants.commands('change_user_password'), cmd_user_password, no_partial_match=True) # Just for debug use bot.register_command('rtrms', cmd_real_time_rms, True) #bot.register_command('loop', cmd_loop_state, True) @@ -126,67 +131,47 @@ def cmd_joinme(bot, user, text, command, parameter): def cmd_user_ban(bot, user, text, command, parameter): global log - if bot.is_admin(user): - if parameter: - bot.mumble.users[text.actor].send_text_message(util.user_ban(parameter)) - else: - bot.mumble.users[text.actor].send_text_message(util.get_user_ban()) + if parameter: + bot.mumble.users[text.actor].send_text_message(util.user_ban(parameter)) else: - bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin')) - return + bot.mumble.users[text.actor].send_text_message(util.get_user_ban()) def cmd_user_unban(bot, user, text, command, parameter): global log - if bot.is_admin(user): - if parameter: - bot.mumble.users[text.actor].send_text_message(util.user_unban(parameter)) - else: - bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin')) - return + if parameter: + bot.mumble.users[text.actor].send_text_message(util.user_unban(parameter)) def cmd_url_ban(bot, user, text, command, parameter): global log - if bot.is_admin(user): - if parameter: - bot.mumble.users[text.actor].send_text_message(util.url_ban(util.get_url_from_input(parameter))) + if parameter: + bot.mumble.users[text.actor].send_text_message(util.url_ban(util.get_url_from_input(parameter))) - id = item_id_generators['url'](url=parameter) - var.cache.free_and_delete(id) - var.playlist.remove_by_id(id) - else: - if var.playlist.current_item() and var.playlist.current_item().type == 'url': - item = var.playlist.current_item().item() - bot.mumble.users[text.actor].send_text_message(util.url_ban(util.get_url_from_input(item.url))) - var.cache.free_and_delete(item.id) - var.playlist.remove_by_id(item.id) - else: - bot.send_msg(constants.strings('bad_parameter', command=command), text) + id = item_id_generators['url'](url=parameter) + var.cache.free_and_delete(id) + var.playlist.remove_by_id(id) else: - bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin')) - return + if var.playlist.current_item() and var.playlist.current_item().type == 'url': + item = var.playlist.current_item().item() + bot.mumble.users[text.actor].send_text_message(util.url_ban(util.get_url_from_input(item.url))) + var.cache.free_and_delete(item.id) + var.playlist.remove_by_id(item.id) + else: + bot.send_msg(constants.strings('bad_parameter', command=command), text) def cmd_url_ban_list(bot, user, text, command, parameter): - if bot.is_admin(user): - bot.mumble.users[text.actor].send_text_message(util.get_url_ban()) - else: - bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin')) - return + bot.mumble.users[text.actor].send_text_message(util.get_url_ban()) def cmd_url_unban(bot, user, text, command, parameter): global log - if bot.is_admin(user): - if parameter: - bot.mumble.users[text.actor].send_text_message(util.url_unban(util.get_url_from_input(parameter))) - else: - bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin')) - return + if parameter: + bot.mumble.users[text.actor].send_text_message(util.url_unban(util.get_url_from_input(parameter))) def cmd_play(bot, user, text, command, parameter): @@ -584,12 +569,8 @@ def cmd_clear(bot, user, text, command, parameter): def cmd_kill(bot, user, text, command, parameter): global log - if bot.is_admin(user): - bot.pause() - bot.exit = True - else: - bot.mumble.users[text.actor].send_text_message( - constants.strings('not_admin')) + bot.pause() + bot.exit = True def cmd_update(bot, user, text, command, parameter): @@ -1178,25 +1159,23 @@ def cmd_refresh_cache(bot, user, text, command, parameter): def cmd_web_access(bot, user, text, command, parameter): - import secrets - import datetime - import json - 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=None) - if user_info is not None: - user_dict = json.loads(user_info) - token = user_dict['token'] - else: - token = secrets.token_urlsafe(5) - var.db.set("web_token", token, user) + 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']) - var.db.set("user", user, json.dumps({'token': token, 'datetime': str(datetime.datetime.now()), 'IP': ''})) + 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: @@ -1205,6 +1184,64 @@ def cmd_web_access(bot, user, text, command, parameter): bot.send_msg(constants.strings('webpage_address', address=access_address), text) +def cmd_user_password(bot, user, text, command, parameter): + if not parameter: + bot.send_msg(constants.strings('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(constants.strings('user_password_set'), text) + + +def cmd_web_user_add(bot, user, text, command, parameter): + if not parameter: + bot.send_msg(constants.strings('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 not in web_users: + web_users.append(parameter) + var.db.set("privilege", "web_access", json.dumps(web_users)) + bot.send_msg(constants.strings('web_user_list', users=", ". join(web_users)), text) + else: + bot.send_msg(constants.strings('command_disabled', command=command), text) + + +def cmd_web_user_remove(bot, user, text, command, parameter): + if not parameter: + bot.send_msg(constants.strings('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(constants.strings('web_user_list', users=", ". join(web_users)), text) + else: + bot.send_msg(constants.strings('command_disabled', command=command), text) + + +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(constants.strings('web_user_list', users=", ". join(web_users)), text) + else: + bot.send_msg(constants.strings('command_disabled', command=command), text) + + # Just for debug use def cmd_real_time_rms(bot, user, text, command, parameter): bot._display_rms = not bot._display_rms diff --git a/configuration.default.ini b/configuration.default.ini index e0c2ac7..7d134e3 100644 --- a/configuration.default.ini +++ b/configuration.default.ini @@ -96,7 +96,7 @@ listening_addr = 127.0.0.1 listening_port = 8181 web_logfile = -auth_method = 'none' +auth_method = none user = password = max_attempts = 10 @@ -190,6 +190,10 @@ drop_database = dropdatabase rescan = rescan requests_webinterface_access = web +list_webinterface_user = webuserlist +add_webinterface_user = webuseradd +remove_webinterface_user = webuserdel +change_user_password = password [strings] current_volume = Current volume: {volume}. @@ -241,7 +245,10 @@ paused = Music paused. stopped = Music stopped. cleared = Playlist emptied. database_dropped = Database dropped. All records have gone. -new_version_found =

Update Available!

New version of botamusique is available, send !update to update! +new_version_found =

Update Available!

Version {new_version} of botamusique is available!
+

Changelog

{changelog}
Send !update to update! +update_successful =

botamusique v{version} Installed!


+

Changelog

{changelog}
Visit our github repo for more details! start_updating = Start updating... file_missed = Music file '{file}' missed! This item has been removed from the playlist. unknown_mode = Unknown playback mode '{mode}'. It should be one of one-shot, repeat, random. @@ -263,6 +270,9 @@ cleared_tags_from_all = Removed all tags from songs on the playlist. shortlist_instruction = Use !sl {indexes} to play the item you want. auto_paused = Use !play to resume music! webpage_address= Your own address to access the web interface is {address} +web_user_list = Following users has the privilege to access the web interface:
{users} +user_password_set = Your password has been updated. +command_disabled = {command}: command disabled! help =

Commands

Control diff --git a/configuration.example.ini b/configuration.example.ini index 27eaa22..43176fc 100644 --- a/configuration.example.ini +++ b/configuration.example.ini @@ -135,8 +135,8 @@ port = 64738 #auth_method = token #max_attempts = 10 -# 'user', 'password': If auth_method set to 'password', you need to set the username and -# password. +# 'user', 'password': If auth_method set to 'password', you need to set the default +# username and password. You can add more users by '!webadduser' #user = botamusique #password = mumble diff --git a/interface.py b/interface.py index 90c59e4..964c4e6 100644 --- a/interface.py +++ b/interface.py @@ -66,7 +66,7 @@ class ReverseProxied(object): web = Flask(__name__) web.config['TEMPLATES_AUTO_RELOAD'] = True log = logging.getLogger("bot") -user = 'webuser' +user = 'Remote Control' def init_proxy(): @@ -82,7 +82,18 @@ def check_auth(username, password): """This function is called to check if a username / password combination is valid. """ - return username == var.config.get("webinterface", "user") and password == var.config.get("webinterface", "password") + + if username == var.config.get("webinterface", "user") and password == var.config.get("webinterface", "password"): + return True + + web_users = json.loads(var.db.get("privilege", "web_access", fallback='[]')) + if username in web_users: + user_dict = json.loads(var.db.get("user", username, fallback='{}')) + if 'password' in user_dict and 'salt' in user_dict and \ + util.verify_password(password, user_dict['password'], user_dict['salt']): + return True + + return False def authenticate(): @@ -109,8 +120,9 @@ def requires_auth(f): if auth_method == 'password': auth = request.authorization - if not auth or not check_auth(auth.username, auth.password): - if auth: + if auth: + user = auth.username + if not check_auth(auth.username, auth.password): if request.remote_addr in bad_access_count: bad_access_count[request.remote_addr] += 1 log.info(f"web: failed login attempt, user: {auth.username}, from ip {request.remote_addr}." @@ -122,6 +134,8 @@ def requires_auth(f): else: bad_access_count[request.remote_addr] = 1 log.info(f"web: failed login attempt, user: {auth.username}, from ip {request.remote_addr}.") + return authenticate() + else: return authenticate() if auth_method == 'token': if 'user' in session and 'token' not in request.args: diff --git a/media/item.py b/media/item.py index 5c35079..1d6a61a 100644 --- a/media/item.py +++ b/media/item.py @@ -82,7 +82,7 @@ class BaseItem: def add_tags(self, tags): for tag in tags: - if tag not in self.tags: + if tag and tag not in self.tags: self.tags.append(tag) self.version += 1 diff --git a/media/playlist.py b/media/playlist.py index 84f38ef..75c7fb7 100644 --- a/media/playlist.py +++ b/media/playlist.py @@ -10,8 +10,9 @@ from database import Condition from media.item import ValidationFailedError, PreparationFailedError -def get_playlist(mode, _list=None, index=None): - if _list and index is None: +def get_playlist(mode, _list=None, _index=None): + index = -1 + if _list and _index is None: index = _list.current_index if _list is None: diff --git a/mumbleBot.py b/mumbleBot.py index 3b749bd..b3da2df 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -66,11 +66,8 @@ class MumbleBot: self.read_pcm_size = 0 # self.download_threads = [] self.wait_for_ready = False # flag for the loop are waiting for download to complete in the other thread - - if var.config.getboolean("bot", "auto_check_update"): - th = threading.Thread(target=self.check_update, name="UpdateThread") - th.daemon = True - th.start() + self.on_killing = threading.Lock() # lock to acquire when killing ffmpeg thread is asked but ffmpeg is not + # killed yet if args.host: host = args.host @@ -129,7 +126,7 @@ class MumbleBot: self.ducking_release = time.time() self.last_volume_cycle_time = time.time() - if not var.db.has_option("bot", "ducking") and var.config.getboolean("bot", "ducking", fallback=False)\ + 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.ducking_volume = var.config.getfloat("bot", "ducking_volume", fallback=0.05) @@ -144,8 +141,10 @@ class MumbleBot: "Unknown action for when_nobody_in_channel" if var.config.get("bot", "when_nobody_in_channel", fallback='') in ['pause', 'pause_resume', 'stop']: - self.mumble.callbacks.set_callback(pymumble.constants.PYMUMBLE_CLBK_USERREMOVED, self.users_changed) - self.mumble.callbacks.set_callback(pymumble.constants.PYMUMBLE_CLBK_USERUPDATED, self.users_changed) + 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' @@ -154,40 +153,55 @@ class MumbleBot: 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", "
") + self.send_channel_msg(constants.strings("update_successful", version=self.version, changelog=changelog)) + # Set the CTRL+C shortcut def ctrl_caught(self, signal, frame): - self.log.info( "\nSIGINT caught, quitting, {} more to kill".format(2 - self.nb_exit)) - self.exit = True - self.pause() - if self.nb_exit > 1: - self.log.info("Forced Quit") - sys.exit(0) - self.nb_exit += 1 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() + if self.nb_exit > 1: + self.log.info("Forced Quit") + sys.exit(0) + self.nb_exit += 1 + + self.exit = True + 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.send_channel_msg(constants.strings('new_version_found')) + self.log.info("update: changelog: " + changelog) + changelog = changelog.replace("\n", "
") + 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 register_command(self, cmd, handle, no_partial_match=False, access_outside_channel=False): + def register_command(self, cmd, handle, no_partial_match=False, access_outside_channel=False, admin=False): cmds = cmd.split(",") for command in cmds: command = command.strip() if command: self.cmd_handle[command] = {'handle': handle, 'partial_match': not no_partial_match, - 'access_outside_channel': access_outside_channel} + 'access_outside_channel': access_outside_channel, + 'admin': admin} self.log.debug("bot: command added: " + command) def set_comment(self): @@ -275,6 +289,10 @@ class MumbleBot: 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') \ @@ -336,6 +354,9 @@ class MumbleBot: # ======================= def launch_music(self, music_wrapper, start_from=0): + self.on_killing.acquire() + self.on_killing.release() + assert music_wrapper.is_ready() uri = music_wrapper.uri() @@ -469,7 +490,7 @@ class MumbleBot: 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 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() try: @@ -527,20 +548,22 @@ class MumbleBot: if delta > 0.001: if self.is_ducking and self.on_ducking: self.volume = (self.volume - self.ducking_volume) * math.exp(- delta / 0.2) + self.ducking_volume + elif self.on_killing.locked(): + self.volume = self.volume_set - (self.volume_set - self.volume) * math.exp(- delta / 0.05) else: self.volume = self.volume_set - (self.volume_set - self.volume) * math.exp(- delta / 0.5) - self.last_volume_cycle_time = time.time() + self.last_volume_cycle_time = time.time() def ducking_sound_received(self, user, sound): rms = audioop.rms(sound.pcm, 2) self._max_rms = max(rms, self._max_rms) if self._display_rms: if rms < self.ducking_threshold: - print('%6d/%6d ' % (rms, self._max_rms) + '-'*int(rms/200), end='\r') + print('%6d/%6d ' % (rms, self._max_rms) + '-' * int(rms / 200), end='\r') else: - print('%6d/%6d ' % (rms, self._max_rms) + '-'*int(self.ducking_threshold/200) - + '+'*int((rms - self.ducking_threshold)/200), end='\r') + print('%6d/%6d ' % (rms, self._max_rms) + '-' * int(self.ducking_threshold / 200) + + '+' * int((rms - self.ducking_threshold) / 200), end='\r') if rms > self.ducking_threshold: if self.on_ducking is False: @@ -582,22 +605,31 @@ class MumbleBot: def interrupt(self): # Kill the ffmpeg thread - if self.thread: - self.thread.kill() - self.thread = None - self.song_start_at = -1 - self.read_pcm_size = 0 - self.playhead = 0 + if not self.on_killing.locked(): + self.on_killing.acquire() + if self.thread: + volume_set = self.volume_set + self.volume_set = 0 + + while self.volume > 0.01 and self.thread: # Waiting for volume_cycle to gradually tune volume to 0. + time.sleep(0.01) + + self.thread.kill() + self.thread = None + self.volume_set = volume_set + self.on_killing.release() + + self.song_start_at = -1 + self.read_pcm_size = 0 def pause(self): # Kill the ffmpeg thread - if self.thread: - self.pause_at_id = var.playlist.current_item().id - self.thread.kill() - self.thread = None + self.interrupt() self.is_pause = True self.song_start_at = -1 - self.log.info("bot: music paused at %.2f seconds." % self.playhead) + if len(var.playlist) > 0: + self.pause_at_id = var.playlist.current_item().id + self.log.info("bot: music paused at %.2f seconds." % self.playhead) def resume(self): self.is_pause = False @@ -698,7 +730,7 @@ if __name__ == '__main__': bot_logger.setLevel(logging.ERROR) bot_logger.error("Starting in ERROR loglevel") - logfile = util.solve_filepath(var.config.get('bot', 'logfile')) + logfile = util.solve_filepath(var.config.get('bot', 'logfile').strip()) handler = None if logfile: print(f"Redirecting stdout and stderr to log file: {logfile}") @@ -780,4 +812,3 @@ if __name__ == '__main__': # Start the main loop. var.bot.loop() - diff --git a/util.py b/util.py index 73fa741..44333a5 100644 --- a/util.py +++ b/util.py @@ -16,7 +16,7 @@ import youtube_dl from importlib import reload from sys import platform import traceback -import urllib.request +import requests from packaging import version log = logging.getLogger("bot") @@ -101,8 +101,15 @@ def get_user_ban(): def new_release_version(): - v = urllib.request.urlopen(urllib.request.Request("https://packages.azlux.fr/botamusique/version")).read() - return v.rstrip().decode() + r = requests.get("https://packages.azlux.fr/botamusique/version") + v = r.text + return v.rstrip() + + +def fetch_changelog(): + r = requests.get("https://packages.azlux.fr/botamusique/changelog") + c = r.text + return c def update(current_version): @@ -379,6 +386,20 @@ def parse_file_size(human): raise ValueError("Invalid file size given.") +def get_salted_password_hash(password): + salt = os.urandom(10) + hashed = hashlib.pbkdf2_hmac('sha1', password.encode("utf-8"), salt, 100000) + + return hashed.hex(), salt.hex() + + +def verify_password(password, salted_hash, salt): + hashed = hashlib.pbkdf2_hmac('sha1', password.encode("utf-8"), bytearray.fromhex(salt), 100000) + if hashed.hex() == salted_hash: + return True + return False + + class LoggerIOWrapper(io.TextIOWrapper): def __init__(self, logger: logging.Logger, logging_level, fallback_io_buffer): super().__init__(fallback_io_buffer, write_through=True) diff --git a/web/src/js/main.mjs b/web/src/js/main.mjs index 1cde4cf..d54b5d4 100644 --- a/web/src/js/main.mjs +++ b/web/src/js/main.mjs @@ -125,7 +125,10 @@ function displayPlaylist(data) { playlist_loading.hide(); $(".playlist-item").remove(); let items = data.items; - playlist_items = items; + playlist_items = {}; + for (let i in items){ + playlist_items[items[i].index] = items[i] + } let length = data.length; let start_from = data.start_from; playlist_range_from = start_from; @@ -156,7 +159,7 @@ function displayPlaylist(data) { } displayActiveItem(data.current_index); - updatePlayerInfo(items[data.current_index - data.start_from]); + updatePlayerInfo(playlist_items[data.current_index]); bindPlaylistEvent(); playlist_table.animate({ opacity: 1 }, 200); }); @@ -236,7 +239,7 @@ function checkForPlaylistUpdate() { updatePlaylist(); } else { playlist_current_index = data.current_index; - updatePlayerInfo(playlist_items[data.current_index - data.start_from]); + updatePlayerInfo(playlist_items[data.current_index]); displayActiveItem(data.current_index); } }