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);
}
}