Add more controls (#71)
* add more ducking command * fix current music command * provide more controls, like pause, resume, clear. * add more controls in the web interface * refactored and improved: 1. move get_music_tag_info to util, and 2. refined logic related to it. 3. now playlist will check for tag info thus update_music_tag_info is useless and was removed 4. add "add folder" feature to !file asked in #65, 5. fix bugs related to !file * truncate file list if too long * fixed several tiny bugs * fixed several tiny bugs continue * fixed several tiny bugs continue continue * fixed several tiny bugs continue**3 * fixed several tiny bugs continue**4 * added !filematch command to add files that match a regex pattern. * truncate long message * fix web interface delete file issue * refresh after delete file * add regex to listfile command * refactored command part, added partial match support for commands * organized * added random command * typo * typo * Fixed many bugs. * Added workaround for azlux/pymumble#44 to fix the memory leak. * changed logging style. * fixed bugs related to random and resume * fix now playing * fixed issue related to download * fixed issue related to download 2 * fixed thumbnail issue * fixed add url problem in web interface * REFACTORED, turned db.ini into sqlite3 database. * fixed remove song problem * fixed radio get title problem. auto download if tmp file is deleted * fixed current index not loaded from database * fixed: order of songs loaded from the database * fixed: some obscure bugs. beautified error of commands * added a workaround for TerryGeng/botamusique#1. * beautified * fixed: channel not loaded in the config * fixed: auto checked for updates * fixed: mysterious bug: sometimes "now playing" string cannot be properly displayed. The real reason is: do use <br />, do not use <br>. I tried hours to find out this. * chore: unified debug messages that refer to music items * feav: fetch ffmpeg stderr mentioned in #72, reformatted logs. * fix: async download not working * fix: async download not working, still * fix: async download not working, finished * feat: queue command: ▶current playing item◀ * feat: support more than one command prefix * chore: added some WARNINGs into default config file to avoid people to touch it. * refactor: packed all string contants into constants.py, just to avoid people messing it around. * refactor: required by azlux. Added a configuration.example.ini to keep people away from configuration.default.ini
This commit is contained in:
parent
951934602e
commit
97f2326d9b
4
.gitignore
vendored
4
.gitignore
vendored
@ -110,4 +110,6 @@ configuration.ini
|
||||
2019-07-27 22_09_08-radiobrowser.py - botamusique - Visual Studio Code.png
|
||||
|
||||
music_folder/
|
||||
tmp/
|
||||
tmp/
|
||||
|
||||
database.db
|
602
command.py
Normal file
602
command.py
Normal file
@ -0,0 +1,602 @@
|
||||
# coding=utf-8
|
||||
import logging
|
||||
import os.path
|
||||
import pymumble.pymumble_py3 as pymumble
|
||||
import re
|
||||
|
||||
import constants
|
||||
import media.file
|
||||
import media.playlist
|
||||
import media.radio
|
||||
import media.system
|
||||
import media.url
|
||||
import util
|
||||
import variables as var
|
||||
from librb import radiobrowser
|
||||
from database import Database
|
||||
|
||||
|
||||
def register_all_commands(bot):
|
||||
bot.register_command(constants.commands('joinme'), cmd_joinme)
|
||||
bot.register_command(constants.commands('user_ban'), cmd_user_ban)
|
||||
bot.register_command(constants.commands('user_unban'), cmd_user_unban)
|
||||
bot.register_command(constants.commands('url_ban'), cmd_url_ban)
|
||||
bot.register_command(constants.commands('url_unban'), cmd_url_unban)
|
||||
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)
|
||||
bot.register_command(constants.commands('play_file_match'), cmd_play_file_match)
|
||||
bot.register_command(constants.commands('play_url'), cmd_play_url)
|
||||
bot.register_command(constants.commands('play_playlist'), cmd_play_playlist)
|
||||
bot.register_command(constants.commands('play_radio'), cmd_play_radio)
|
||||
bot.register_command(constants.commands('rb_query'), cmd_rb_query)
|
||||
bot.register_command(constants.commands('rb_play'), cmd_rb_play)
|
||||
bot.register_command(constants.commands('help'), cmd_help)
|
||||
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)
|
||||
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)
|
||||
bot.register_command(constants.commands('ducking_threshold'), cmd_ducking_threshold)
|
||||
bot.register_command(constants.commands('ducking_volume'), cmd_ducking_volume)
|
||||
bot.register_command(constants.commands('current_music'), cmd_current_music)
|
||||
bot.register_command(constants.commands('skip'), cmd_skip)
|
||||
bot.register_command(constants.commands('remove'), cmd_remove)
|
||||
bot.register_command(constants.commands('list_file'), cmd_list_file)
|
||||
bot.register_command(constants.commands('queue'), cmd_queue)
|
||||
bot.register_command(constants.commands('random'), cmd_random)
|
||||
bot.register_command(constants.commands('drop_database'), cmd_drop_database)
|
||||
|
||||
def send_multi_lines(bot, lines, text):
|
||||
msg = ""
|
||||
br = ""
|
||||
for newline in lines:
|
||||
msg += br
|
||||
br = "<br>"
|
||||
if len(msg) + len(newline) > 5000:
|
||||
bot.send_msg(msg, text)
|
||||
msg = ""
|
||||
msg += newline
|
||||
|
||||
bot.send_msg(msg, text)
|
||||
|
||||
# ---------------- Commands ------------------
|
||||
|
||||
|
||||
def cmd_joinme(bot, user, text, command, parameter):
|
||||
channel_id = bot.mumble.users[text.actor]['channel_id']
|
||||
bot.mumble.channels[channel_id].move_in()
|
||||
|
||||
|
||||
def cmd_user_ban(bot, user, text, command, parameter):
|
||||
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())
|
||||
else:
|
||||
bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin'))
|
||||
return
|
||||
|
||||
|
||||
def cmd_user_unban(bot, user, text, command, parameter):
|
||||
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
|
||||
|
||||
|
||||
def cmd_url_ban(bot, user, text, command, parameter):
|
||||
if bot.is_admin(user):
|
||||
if parameter:
|
||||
bot.mumble.users[text.actor].send_text_message(util.url_ban(util.get_url_from_input(parameter)))
|
||||
else:
|
||||
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
|
||||
|
||||
|
||||
def cmd_url_unban(bot, user, text, command, parameter):
|
||||
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
|
||||
|
||||
|
||||
def cmd_play(bot, user, text, command, parameter):
|
||||
if var.playlist.length() > 0:
|
||||
if parameter is not None and parameter.isdigit() and int(parameter) > 0 \
|
||||
and int(parameter) <= len(var.playlist.playlist):
|
||||
bot.stop()
|
||||
bot.launch_music(int(parameter) - 1)
|
||||
elif bot.is_pause:
|
||||
bot.resume()
|
||||
else:
|
||||
bot.send_msg(util.format_current_playing(), text)
|
||||
else:
|
||||
bot.send_msg(constants.strings('queue_empty'), text)
|
||||
|
||||
|
||||
def cmd_pause(bot, user, text, command, parameter):
|
||||
bot.pause()
|
||||
bot.send_msg(constants.strings('paused'))
|
||||
|
||||
|
||||
def cmd_play_file(bot, user, text, command, parameter):
|
||||
music_folder = var.config.get('bot', 'music_folder')
|
||||
# if parameter is {index}
|
||||
if parameter.isdigit():
|
||||
files = util.get_recursive_filelist_sorted(music_folder)
|
||||
if int(parameter) < len(files):
|
||||
filename = files[int(parameter)].replace(music_folder, '')
|
||||
music = {'type': 'file',
|
||||
'path': filename,
|
||||
'user': user}
|
||||
logging.info("cmd: add to playlist: " + filename)
|
||||
music = var.playlist.append(music)
|
||||
bot.send_msg(constants.strings('file_added', item=util.format_song_string(music)), text)
|
||||
|
||||
# if parameter is {path}
|
||||
else:
|
||||
# sanitize "../" and so on
|
||||
path = os.path.abspath(os.path.join(music_folder, parameter))
|
||||
if not path.startswith(os.path.abspath(music_folder)):
|
||||
bot.send_msg(constants.strings('no_file'), text)
|
||||
return
|
||||
|
||||
if os.path.isfile(path):
|
||||
music = {'type': 'file',
|
||||
'path': parameter,
|
||||
'user': user}
|
||||
logging.info("cmd: add to playlist: " + parameter)
|
||||
music = var.playlist.append(music)
|
||||
bot.send_msg(constants.strings('file_added', item=util.format_song_string(music)), text)
|
||||
return
|
||||
|
||||
# if parameter is {folder}
|
||||
elif os.path.isdir(path):
|
||||
if parameter != '.' and parameter != './':
|
||||
if not parameter.endswith("/"):
|
||||
parameter += "/"
|
||||
else:
|
||||
parameter = ""
|
||||
|
||||
files = util.get_recursive_filelist_sorted(music_folder)
|
||||
music_library = util.Dir(music_folder)
|
||||
for file in files:
|
||||
music_library.add_file(file)
|
||||
|
||||
files = music_library.get_files(parameter)
|
||||
msgs = [constants.strings('multiple_file_added')]
|
||||
count = 0
|
||||
|
||||
for file in files:
|
||||
count += 1
|
||||
music = {'type': 'file',
|
||||
'path': file,
|
||||
'user': user}
|
||||
logging.info("cmd: add to playlist: " + file)
|
||||
music = var.playlist.append(music)
|
||||
|
||||
msgs.append("{} ({})".format(music['title'], music['path']))
|
||||
|
||||
if count != 0:
|
||||
send_multi_lines(bot, msgs, text)
|
||||
else:
|
||||
bot.send_msg(constants.strings('no_file'), text)
|
||||
|
||||
else:
|
||||
# try to do a partial match
|
||||
files = util.get_recursive_filelist_sorted(music_folder)
|
||||
matches = [(index, file) for index, file in enumerate(files) if parameter.lower() in file.lower()]
|
||||
if len(matches) == 0:
|
||||
bot.send_msg(constants.strings('no_file'), text)
|
||||
elif len(matches) == 1:
|
||||
music = {'type': 'file',
|
||||
'path': matches[0][1],
|
||||
'user': user}
|
||||
logging.info("cmd: add to playlist: " + matches[0][1])
|
||||
music = var.playlist.append(music)
|
||||
bot.send_msg(constants.strings('file_added', item=util.format_song_string(music)), text)
|
||||
else:
|
||||
msgs = [ constants.strings('multiple_matches')]
|
||||
for match in matches:
|
||||
msgs.append("<b>{:0>3d}</b> - {:s}".format(match[0], match[1]))
|
||||
send_multi_lines(bot, msgs, text)
|
||||
|
||||
|
||||
def cmd_play_file_match(bot, user, text, command, parameter):
|
||||
music_folder = var.config.get('bot', 'music_folder')
|
||||
if parameter is not None:
|
||||
files = util.get_recursive_filelist_sorted(music_folder)
|
||||
msgs = [ constants.strings('file_added')]
|
||||
count = 0
|
||||
try:
|
||||
for file in files:
|
||||
match = re.search(parameter, file)
|
||||
if match:
|
||||
count += 1
|
||||
music = {'type': 'file',
|
||||
'path': file,
|
||||
'user': user}
|
||||
logging.info("cmd: add to playlist: " + file)
|
||||
music = var.playlist.append(music)
|
||||
|
||||
msgs.append("{} ({})".format(music['title'], music['path']))
|
||||
|
||||
if count != 0:
|
||||
send_multi_lines(bot, msgs, text)
|
||||
else:
|
||||
bot.send_msg(constants.strings('no_file'), text)
|
||||
|
||||
except re.error as e:
|
||||
msg = constants.strings('wrong_pattern', error=str(e))
|
||||
bot.send_msg(msg, text)
|
||||
else:
|
||||
bot.send_msg(constants.strings('bad_parameter', command))
|
||||
|
||||
|
||||
def cmd_play_url(bot, user, text, command, parameter):
|
||||
music = {'type': 'url',
|
||||
# grab the real URL
|
||||
'url': util.get_url_from_input(parameter),
|
||||
'user': user,
|
||||
'ready': 'validation'}
|
||||
|
||||
if media.url.get_url_info(music):
|
||||
if music['duration'] > var.config.getint('bot', 'max_track_duration'):
|
||||
bot.send_msg(constants.strings('too_long'), text)
|
||||
else:
|
||||
music['ready'] = "no"
|
||||
var.playlist.append(music)
|
||||
logging.info("cmd: add to playlist: " + music['url'])
|
||||
bot.async_download_next()
|
||||
else:
|
||||
bot.send_msg(constants.strings('unable_download'), text)
|
||||
|
||||
|
||||
def cmd_play_playlist(bot, user, text, command, parameter):
|
||||
offset = 0 # if you want to start the playlist at a specific index
|
||||
try:
|
||||
offset = int(parameter.split(" ")[-1])
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
url = util.get_url_from_input(parameter)
|
||||
logging.debug("cmd: fetching media info from playlist url %s" % url)
|
||||
items = media.playlist.get_playlist_info(url=url, start_index=offset, user=user)
|
||||
if len(items) > 0:
|
||||
var.playlist.extend(items)
|
||||
for music in items:
|
||||
logging.info("cmd: add to playlist: " + util.format_debug_song_string(music))
|
||||
|
||||
|
||||
def cmd_play_radio(bot, user, text, command, parameter):
|
||||
if not parameter:
|
||||
all_radio = var.config.items('radio')
|
||||
msg = constants.strings('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', command, parameter):
|
||||
parameter = var.config.get('radio', parameter)
|
||||
parameter = parameter.split()[0]
|
||||
url = util.get_url_from_input(parameter)
|
||||
if url:
|
||||
music = {'type': 'radio',
|
||||
'url': url,
|
||||
'user': user}
|
||||
var.playlist.append(music)
|
||||
logging.info("cmd: add to playlist: " + music['url'])
|
||||
bot.async_download_next()
|
||||
else:
|
||||
bot.send_msg(constants.strings('bad_url'))
|
||||
|
||||
|
||||
def cmd_rb_query(bot, user, text, command, parameter):
|
||||
logging.info('cmd: Querying radio stations')
|
||||
if not parameter:
|
||||
logging.debug('rbquery without parameter')
|
||||
msg = constants.strings('rb_query_empty')
|
||||
bot.send_msg(msg, text)
|
||||
else:
|
||||
logging.debug('cmd: Found query parameter: ' + parameter)
|
||||
# bot.send_msg('Searching for stations - this may take some seconds...', text)
|
||||
rb_stations = radiobrowser.getstations_byname(parameter)
|
||||
msg = constants.strings('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:
|
||||
logging.debug('cmd: No matches found for rbquery ' + parameter)
|
||||
bot.send_msg('Radio-Browser found no matches for ' + parameter, text)
|
||||
else:
|
||||
for s in rb_stations:
|
||||
stationid = s['id']
|
||||
stationname = s['stationname']
|
||||
country = s['country']
|
||||
codec = s['codec']
|
||||
bitrate = s['bitrate']
|
||||
genre = s['genre']
|
||||
# msg += f'<tr><td>{stationid}</td><td>{stationname}</td><td>{genre}</td><td>{codec}/{bitrate}</td><td>{country}</td></tr>'
|
||||
msg += '<tr><td>%s</td><td>%s</td><td>%s</td><td>%s/%s</td><td>%s</td></tr>' % (
|
||||
stationid, stationname, genre, codec, bitrate, country)
|
||||
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:
|
||||
logging.debug('Result too long stage I')
|
||||
msg = constants.strings('rb_query_result') + ' (shortened L1)'
|
||||
msg += '\n<table><tr><th>!rbplay ID</th><th>Station Name</th></tr>'
|
||||
for s in rb_stations:
|
||||
stationid = s['id']
|
||||
stationname = s['stationname']
|
||||
# msg += f'<tr><td>{stationid}</td><td>{stationname}</td>'
|
||||
msg += '<tr><td>%s</td><td>%s</td>' % (stationid, stationname)
|
||||
msg += '</table>'
|
||||
if len(msg) <= 5000:
|
||||
bot.send_msg(msg, text)
|
||||
# Shorten message if message too long (stage II)
|
||||
else:
|
||||
logging.debug('Result too long stage II')
|
||||
msg = constants.strings('rb_query_result') + ' (shortened L2)'
|
||||
msg += '!rbplay ID - Station Name'
|
||||
for s in rb_stations:
|
||||
stationid = s['id']
|
||||
stationname = s['stationname'][:12]
|
||||
# msg += f'{stationid} - {stationname}'
|
||||
msg += '%s - %s' % (stationid, stationname)
|
||||
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_rb_play(bot, user, text, command, parameter):
|
||||
logging.debug('cmd: Play a station by ID')
|
||||
if not parameter:
|
||||
logging.debug('rbplay without parameter')
|
||||
msg = constants.strings('rb_play_empty')
|
||||
bot.send_msg(msg, text)
|
||||
else:
|
||||
logging.debug('cmd: Retreiving url for station ID ' + parameter)
|
||||
rstation = radiobrowser.getstationname_byid(parameter)
|
||||
stationname = rstation[0]['name']
|
||||
country = rstation[0]['country']
|
||||
codec = rstation[0]['codec']
|
||||
bitrate = rstation[0]['bitrate']
|
||||
genre = rstation[0]['tags']
|
||||
homepage = rstation[0]['homepage']
|
||||
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>'
|
||||
msg += '<table><tr><th>ID</th><th>Station Name</th><th>Genre</th><th>Codec/Bitrate</th><th>Country</th><th>Homepage</th></tr>' + \
|
||||
'<tr><td>%s</td><td>%s</td><td>%s</td><td>%s/%s</td><td>%s</td><td>%s</td></tr></table>' \
|
||||
% (parameter, stationname, genre, codec, bitrate, country, homepage)
|
||||
logging.debug('cmd: Added station to playlist %s' % stationname)
|
||||
bot.send_msg(msg, text)
|
||||
url = radiobrowser.geturl_byid(parameter)
|
||||
if url != "-1":
|
||||
logging.info('cmd: Found url: ' + url)
|
||||
music = {'type': 'radio',
|
||||
'title': stationname,
|
||||
'artist': homepage,
|
||||
'url': url,
|
||||
'user': user}
|
||||
var.playlist.append(music)
|
||||
logging.info("cmd: add to playlist: " + music['url'])
|
||||
bot.async_download_next()
|
||||
else:
|
||||
logging.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_help(bot, user, text, command, parameter):
|
||||
bot.send_msg(constants.strings('help'), text)
|
||||
if bot.is_admin(user):
|
||||
bot.send_msg(constants.strings('admin_help'), text)
|
||||
|
||||
|
||||
def cmd_stop(bot, user, text, command, parameter):
|
||||
bot.stop()
|
||||
bot.send_msg(constants.strings('stopped'), text)
|
||||
|
||||
|
||||
def cmd_clear(bot, user, text, command, parameter):
|
||||
bot.clear()
|
||||
bot.send_msg(constants.strings('cleared'), text)
|
||||
|
||||
|
||||
def cmd_kill(bot, user, text, command, parameter):
|
||||
if bot.is_admin(user):
|
||||
bot.pause()
|
||||
bot.exit = True
|
||||
else:
|
||||
bot.mumble.users[text.actor].send_text_message(
|
||||
constants.strings('not_admin'))
|
||||
|
||||
|
||||
def cmd_update(bot, user, text, command, parameter):
|
||||
if bot.is_admin(user):
|
||||
bot.mumble.users[text.actor].send_text_message(
|
||||
constants.strings('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(
|
||||
constants.strings('not_admin'))
|
||||
|
||||
|
||||
def cmd_stop_and_getout(bot, user, text, command, parameter):
|
||||
bot.stop()
|
||||
if bot.channel:
|
||||
bot.mumble.channels.find_by_name(bot.channel).move_in()
|
||||
|
||||
|
||||
def cmd_volume(bot, user, text, command, parameter):
|
||||
# The volume is a percentage
|
||||
if parameter is not None and parameter.isdigit() and 0 <= int(parameter) <= 100:
|
||||
bot.volume_set = float(float(parameter) / 100)
|
||||
bot.send_msg(constants.strings('change_volume',
|
||||
volume=int(bot.volume_set * 100), user=bot.mumble.users[text.actor]['name']), text)
|
||||
var.db.set('bot', 'volume', str(bot.volume_set))
|
||||
logging.info('cmd: volume set to %d' % (bot.volume_set * 100))
|
||||
else:
|
||||
bot.send_msg(constants.strings('current_volume', volume=int(bot.volume_set * 100)), text)
|
||||
|
||||
|
||||
def cmd_ducking(bot, user, text, command, parameter):
|
||||
if parameter == "" or parameter == "on":
|
||||
bot.is_ducking = True
|
||||
var.db.set('bot', 'ducking', True)
|
||||
bot.ducking_volume = var.config.getfloat("bot", "ducking_volume", fallback=0.05)
|
||||
bot.ducking_threshold = var.config.getint("bot", "ducking_threshold", fallback=5000)
|
||||
bot.mumble.callbacks.set_callback(pymumble.constants.PYMUMBLE_CLBK_SOUNDRECEIVED,
|
||||
bot.ducking_sound_received)
|
||||
bot.mumble.set_receive_sound(True)
|
||||
logging.info('cmd: ducking is on')
|
||||
msg = "Ducking on."
|
||||
bot.send_msg(msg, text)
|
||||
elif parameter == "off":
|
||||
bot.is_ducking = False
|
||||
bot.mumble.set_receive_sound(False)
|
||||
var.db.set('bot', 'ducking', False)
|
||||
msg = "Ducking off."
|
||||
logging.info('cmd: ducking is off')
|
||||
bot.send_msg(msg, text)
|
||||
|
||||
|
||||
def cmd_ducking_threshold(bot, user, text, command, parameter):
|
||||
if parameter is not None and parameter.isdigit():
|
||||
bot.ducking_threshold = int(parameter)
|
||||
var.db.set('bot', 'ducking_threshold', str(bot.ducking_threshold))
|
||||
msg = "Ducking threshold set to %d." % bot.ducking_threshold
|
||||
bot.send_msg(msg, text)
|
||||
else:
|
||||
msg = "Current ducking threshold is %d." % bot.ducking_threshold
|
||||
bot.send_msg(msg, text)
|
||||
|
||||
|
||||
def cmd_ducking_volume(bot, user, text, command, parameter):
|
||||
# The volume is a percentage
|
||||
if parameter is not None and parameter.isdigit() and 0 <= int(parameter) <= 100:
|
||||
bot.ducking_volume = float(float(parameter) / 100)
|
||||
bot.send_msg(constants.strings('change_ducking_volume',
|
||||
volume=int(bot.ducking_volume * 100), user=bot.mumble.users[text.actor]['name']), text)
|
||||
# var.db.set('bot', 'volume', str(bot.volume_set))
|
||||
var.db.set('bot', 'ducking_volume', str(bot.ducking_volume))
|
||||
logging.info('cmd: volume on ducking set to %d' % (bot.ducking_volume * 100))
|
||||
else:
|
||||
bot.send_msg(constants.strings('current_ducking_volume', volume=int(bot.ducking_volume * 100)), text)
|
||||
|
||||
|
||||
def cmd_current_music(bot, user, text, command, parameter):
|
||||
reply = ""
|
||||
if var.playlist.length() > 0:
|
||||
bot.send_msg(util.format_current_playing())
|
||||
else:
|
||||
reply = constants.strings('not_playing')
|
||||
bot.send_msg(reply, text)
|
||||
|
||||
|
||||
def cmd_skip(bot, user, text, command, parameter):
|
||||
if bot.next(): # Is no number send, just skip the current music
|
||||
bot.launch_music()
|
||||
bot.async_download_next()
|
||||
else:
|
||||
bot.send_msg(constants.strings('queue_empty'), text)
|
||||
|
||||
|
||||
def cmd_remove(bot, user, text, command, parameter):
|
||||
# Allow to remove specific music into the queue with a number
|
||||
if parameter is not None and parameter.isdigit() and int(parameter) > 0 \
|
||||
and int(parameter) <= var.playlist.length():
|
||||
|
||||
index = int(parameter) - 1
|
||||
|
||||
removed = None
|
||||
if index == var.playlist.current_index:
|
||||
removed = var.playlist.remove(index)
|
||||
var.botamusique.stop()
|
||||
var.botamusique.launch_music(index)
|
||||
else:
|
||||
removed = var.playlist.remove(index)
|
||||
|
||||
# the Title isn't here if the music wasn't downloaded
|
||||
bot.send_msg(constants.strings('removing_item',
|
||||
item=removed['title'] if 'title' in removed else removed['url']), text)
|
||||
|
||||
logging.info("cmd: delete from playlist: " + str(removed['path'] if 'path' in removed else removed['url']))
|
||||
else:
|
||||
bot.send_msg(constants.strings('bad_parameter', command=command))
|
||||
|
||||
|
||||
def cmd_list_file(bot, user, text, command, parameter):
|
||||
folder_path = var.config.get('bot', 'music_folder')
|
||||
|
||||
files = util.get_recursive_filelist_sorted(folder_path)
|
||||
msgs = [ "<br> <b>Files available:</b>" if not parameter else "<br> <b>Matched files:</b>" ]
|
||||
try:
|
||||
count = 0
|
||||
for index, file in enumerate(files):
|
||||
if parameter:
|
||||
match = re.search(parameter, file)
|
||||
if not match:
|
||||
continue
|
||||
|
||||
count += 1
|
||||
msgs.append("<b>{:0>3d}</b> - {:s}".format(index, file))
|
||||
|
||||
if count != 0:
|
||||
send_multi_lines(bot, msgs, text)
|
||||
else:
|
||||
bot.send_msg(constants.strings('no_file'), text)
|
||||
|
||||
except re.error as e:
|
||||
msg = constants.strings('wrong_pattern', error=str(e))
|
||||
bot.send_msg(msg, text)
|
||||
|
||||
|
||||
def cmd_queue(bot, user, text, command, parameter):
|
||||
if len(var.playlist.playlist) == 0:
|
||||
msg = constants.strings('queue_empty')
|
||||
bot.send_msg(msg, text)
|
||||
else:
|
||||
msgs = [ constants.strings('queue_contents')]
|
||||
for i, value in enumerate(var.playlist.playlist):
|
||||
newline = ''
|
||||
if i == var.playlist.current_index:
|
||||
newline = '<b>{} ▶ ({}) {} ◀</b>'.format(i + 1, value['type'],
|
||||
value['title'] if 'title' in value else value['url'])
|
||||
else:
|
||||
newline = '<b>{}</b> ({}) {}'.format(i + 1, value['type'],
|
||||
value['title'] if 'title' in value else value['url'])
|
||||
|
||||
msgs.append(newline)
|
||||
|
||||
send_multi_lines(bot, msgs, text)
|
||||
|
||||
|
||||
def cmd_random(bot, user, text, command, parameter):
|
||||
bot.stop()
|
||||
var.playlist.randomize()
|
||||
bot.launch_music(0)
|
||||
|
||||
def cmd_drop_database(bot, user, text, command, parameter):
|
||||
var.db.drop_table()
|
||||
var.db = Database(var.dbfile)
|
||||
bot.send_msg(constants.strings('database_dropped'), text)
|
@ -1,20 +1,45 @@
|
||||
# ========================================================
|
||||
# botamusique Default Configuration File
|
||||
# Version 6
|
||||
# ========================================================
|
||||
# WARNING:
|
||||
# ******************************
|
||||
# **DO NOT MODIFIED THIS FILE.**
|
||||
# ******************************
|
||||
#
|
||||
# Please create your own configuration file, and
|
||||
# ONLY ADD ITEMS YOU WANT TO MODIFY into it. Other
|
||||
# items will be loaded from this file automatically.
|
||||
# DO NOT DIRECTLY COPY THIS FILE.
|
||||
#
|
||||
# That is because this file will be overridden
|
||||
# during updates. New options will be added and
|
||||
# old options (like [strings]) will be updated.
|
||||
# ========================================================
|
||||
|
||||
[server]
|
||||
host = 127.0.0.1
|
||||
port = 64738
|
||||
password =
|
||||
channel =
|
||||
tokens = # example: token1,token2
|
||||
# example: token1,token2
|
||||
tokens =
|
||||
certificate =
|
||||
|
||||
[bot]
|
||||
username = botamusique
|
||||
comment = Hi, I'm here to play radio, local music or youtube/soundcloud music. Have fun !
|
||||
comment = Hi, I'm here to play radio, local music or youtube/soundcloud music. Have fun!
|
||||
# default volume from 0 to 1.
|
||||
volume = 0.1
|
||||
admin = User1;User2; # Allow user to kill the bot
|
||||
|
||||
# Users allowed to kill the bot, or ban URLs.
|
||||
admin = User1;User2;
|
||||
# Folder that stores your local songs.
|
||||
music_folder = music_folder/
|
||||
# Folder that stores the downloaded music.
|
||||
tmp_folder = /tmp/
|
||||
pip3_path = venv/bin/pip
|
||||
auto_update = True
|
||||
auto_check_update = True
|
||||
logfile =
|
||||
|
||||
# in MB, 0 for no cache, -1 for unlimited size
|
||||
@ -27,6 +52,10 @@ announce_current_music = True
|
||||
allow_other_channel_message = False
|
||||
allow_private_message = True
|
||||
|
||||
# If save_playlist is set True, the bot will save current
|
||||
# playlist before quitting and reload it the next time it start.
|
||||
save_playlist = True
|
||||
|
||||
# Maximum track played when a playlist is added.
|
||||
max_track_playlist = 20
|
||||
|
||||
@ -39,6 +68,7 @@ ducking_volume = 0.05
|
||||
ducking_threshold = 3000
|
||||
|
||||
[webinterface]
|
||||
# Set enabled to True if you'd like to use the web interface to manage your playlist, upload files, etc.
|
||||
enabled = False
|
||||
is_web_proxified = True
|
||||
listening_addr = 127.0.0.1
|
||||
@ -47,16 +77,38 @@ listening_port = 8181
|
||||
# Set this option to True to enable password protection for the web interface
|
||||
require_auth = False
|
||||
user =
|
||||
password =
|
||||
password =
|
||||
|
||||
[command]
|
||||
#This it the char (only on letter) the bot will recognize as a command
|
||||
command_symbol = !
|
||||
#this option split username, in case you use such kind of mumo plugins https://wiki.mumble.info/wiki/Mumo#Set_Status
|
||||
[debug]
|
||||
# Set ffmpeg to True if you want to display DEBUG level log of ffmpeg.
|
||||
ffmpeg = False
|
||||
mumbleConnection = False
|
||||
|
||||
# This is a list of default radio stations.
|
||||
[radio]
|
||||
ponyville = http://192.99.131.205:8000/stream.mp3 "Here a command of !radio comment"
|
||||
luna = http://radio.ponyvillelive.com:8002/stream "calm and orchestra music"
|
||||
radiobrony = http://62.210.138.34:8000/live "Borny music of a friend"
|
||||
celestiaradio = http://celestia.aiverse.org:8000/mp3_256
|
||||
jazz = http://jazz-wr04.ice.infomaniak.ch/jazz-wr04-128.mp3 "Jazz Yeah !"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# ========================================================
|
||||
# WARNING: WE DO NOT SUGGEST YOU MODIFY THE FOLLOWING
|
||||
# PARTS, EXCEPT YOU KNOW WHAT YOU ARE DOING.
|
||||
# ========================================================
|
||||
[commands]
|
||||
# This is a list of characters the bot recognizes as command prefix.
|
||||
command_symbol = !:!
|
||||
# This option split username, in case you use such kind of mumo plugins https://wiki.mumble.info/wiki/Mumo#Set_Status
|
||||
split_username_at_space = False
|
||||
|
||||
|
||||
play_file = file
|
||||
play_file = file, f
|
||||
play_file_match = filematch, fm
|
||||
play_url = url
|
||||
play_radio = radio
|
||||
play_playlist = playlist
|
||||
@ -65,17 +117,22 @@ rb_query = rbquery
|
||||
rb_play = rbplay
|
||||
|
||||
help = help
|
||||
pause = pause
|
||||
play = p, play
|
||||
stop = stop
|
||||
list = list
|
||||
remove = rm
|
||||
clear = clear
|
||||
skip = skip
|
||||
current_music = np
|
||||
volume = v
|
||||
current_music = np, now
|
||||
volume = volume
|
||||
kill = kill
|
||||
stop_and_getout = oust
|
||||
joinme = joinme
|
||||
queue = queue
|
||||
repeat = repeat
|
||||
random = random
|
||||
update = update
|
||||
list_file = listfile
|
||||
|
||||
user_ban = userban
|
||||
user_unban = userunban
|
||||
@ -84,72 +141,91 @@ url_unban = urlunban
|
||||
|
||||
ducking = duck
|
||||
ducking_threshold = duckthres
|
||||
ducking_volume = duckv
|
||||
|
||||
#command to reload the ban list
|
||||
reload = reload
|
||||
|
||||
[radio]
|
||||
ponyville = http://192.99.131.205:8000/stream.mp3 "Here a command of !radio comment"
|
||||
luna = http://radio.ponyvillelive.com:8002/stream "calm and orchestra music"
|
||||
radiobrony = http://62.210.138.34:8000/live "Borny music of a friend"
|
||||
celestiaradio = http://celestia.aiverse.org:8000/mp3_256
|
||||
jazz = http://jazz-wr04.ice.infomaniak.ch/jazz-wr04-128.mp3 "Jazz Yeah !"
|
||||
drop_database = dropdatabase
|
||||
|
||||
[strings]
|
||||
current_volume = volume : %d%%
|
||||
change_volume = volume : %d%% by %s
|
||||
bad_command = Incorrect command
|
||||
not_admin = You are not an admin !
|
||||
not_playing = No music right now
|
||||
bad_file = Bad file requested
|
||||
current_volume = Current volume: {volume}
|
||||
current_ducking_volume = Volume on ducking: {volume} by {user}
|
||||
change_volume = Volume set to {volume} by {user}
|
||||
change_ducking_volume = Volume on ducking set to {volume} by {user}
|
||||
bad_command = {command}: command not found
|
||||
bad_parameter = {command}: invalid parameter
|
||||
error_executing_command = {command}: Command failed with error: {error}.
|
||||
not_admin = You are not an admin!
|
||||
not_playing = Nothing is playing right now
|
||||
no_file = File not found
|
||||
wrong_pattern = Invalid regex: {error}
|
||||
file_added = Added: {item}
|
||||
multiple_file_added = Multiple files added:
|
||||
bad_url = Bad URL requested
|
||||
preconfigurated_radio = Preconfigurated Radio available
|
||||
unable_download = Error while downloading the music...
|
||||
preconfigurated_radio = Preconfigurated Radio available:
|
||||
unable_download = Error while downloading music...
|
||||
which_command = Do you mean <br /> {commands}
|
||||
multiple_matches = Track not found! Possible candidates:
|
||||
queue_contents = The next items in the queue are:
|
||||
queue_empty = No more music in the playlist!
|
||||
now_playing = Now playing %s<br />%s
|
||||
not_in_my_channel = You're not in my channel, command refused !
|
||||
queue_contents = Items on the playlist:
|
||||
queue_empty = Playlist is empty!
|
||||
now_playing = Now playing {item}<br />{thumb}
|
||||
not_in_my_channel = You're not in my channel, command refused!
|
||||
pm_not_allowed = Private message aren't allowed.
|
||||
too_long = This music is too long, skipping !
|
||||
download_in_progress = Download of %s in progress
|
||||
no_possible = it's not possible to do that
|
||||
removing_item = Removing entry %s from queue
|
||||
user_ban = You are ban, not allowed to do that !
|
||||
url_ban = This url isn't allowed !
|
||||
rb_query_result = This is the result of your query, send !rbplay 'ID' to play a station
|
||||
rb_query_empty = You have to add a query text to search for a matching radio stations.
|
||||
rb_play_empty = Please enter a station ID from rbquery. Example: !rbplay 96748
|
||||
too_long = This music is too long, skip!
|
||||
download_in_progress = Download of {item} in progress
|
||||
removing_item = Removed entry {item} from playlist
|
||||
user_ban = You are banned, not allowed to do that!
|
||||
url_ban = This url is banned!
|
||||
rb_query_result = This is the result of your query, send !rbplay 'ID' to play a station:
|
||||
rb_play_empty = Please specify a radio station ID!
|
||||
paused = Music paused.
|
||||
stopped = Music stopped.
|
||||
cleared = Playlist emptied.
|
||||
database_dropped = Database dropped. All records have gone.
|
||||
new_version_found = <h3>Update Available!</h3> New version of botamusique is available, send <i>!update</i> to update!
|
||||
start_updating = Start updating...
|
||||
|
||||
help = Command available:
|
||||
<br />!file [path]
|
||||
<br />!url [url] - youtube or soundcloud
|
||||
<br />!playlist [url] [offset] - youtube or soundcloud playlist (the offset is the track number the bot will start to play - 1 by default)
|
||||
<br />!radio [url] - url of a stream
|
||||
<br />!rbquery - Search http://www.radio-browser.info for a radio station
|
||||
<br />!rbplay - Play a radio station from !rbquery search results (eg. !rbplay 96746)
|
||||
<br />!list - display list of available tracks
|
||||
<br />!queue - display items in queue
|
||||
<br />!np - display the current music
|
||||
<br />!skip - jump to the next music of the playlist (of remove the X items if you add a number)
|
||||
<br />!stop - stop and clear the playlist
|
||||
<br />!oust - stop + Go to default channel
|
||||
<br />!v - get or change the volume (in %)
|
||||
<br />!joinme - join your own channel
|
||||
<br />!duck [on/off] - enable or disable ducking function
|
||||
<br />!duckthres - set the threshold of volume to activate ducking (3000 by default)
|
||||
help = <h3>Commands</h3>
|
||||
<b>Control</b>
|
||||
<ul>
|
||||
<li> <b>!play </b> (or <b>!p</b>) [{num}] - resume from pausing / start to play (the num-th song is num is given) </li>
|
||||
<li> <b>!<u>pa</u>use </b> - pause </li>
|
||||
<li> <b>!<u>st</u>op </b> - stop playing </li>
|
||||
<li> <b>!<u>sk</u>ip </b> - jump to the next song </li>
|
||||
<li> <b>!<u>v</u>olume </b> {volume} - get or change the volume (from 0 to 100) </li>
|
||||
<li> <b>!duck </b> on/off - enable or disable ducking function </li>
|
||||
<li> <b>!duckv </b> - set the volume of the bot when ducking is activated </li>
|
||||
<li> <b>!<u>duckt</u>hres </b> - set the threshold of volume to activate ducking (3000 by default) </li>
|
||||
</ul>
|
||||
<b>Playlist</b>
|
||||
<ul>
|
||||
<li> <b>!<u>n</u>ow </b> (or <b>!np</b>) - display the current song </li>
|
||||
<li> <b>!<u>q</u>ueue </b> - display items in the playlist </li>
|
||||
<li> <b>!file </b>(or <b>!f</b>) {path/folder/index/keyword} - append file to the playlist by its path or index returned by !listfile </li>
|
||||
<li> <b>!<u>filem</u>atch </b>(or <b>!fm</b>) {pattern} - add all files that match regex {pattern} </li>
|
||||
<li> <b>!<u>ur</u>l </b> {url} - append youtube or soundcloud music to the playlist </li>
|
||||
<li> <b>!<u>playl</u>ist </b> {url} [{offset}] - append items in a youtube or soundcloud playlist, and start with the {offset}-th item </li>
|
||||
<li> <b>!rm </b> {num} - remove the num-th song on the playlist </li>
|
||||
<li> <b>!<u>rep</u>eat </b> [{num}] - repeat current song {num} (1 by default) times.</li>
|
||||
<li> <b>!<u>ran</u>dom </b> - randomize the playlist.</li>
|
||||
<li> <b>!<u>rad</u>io </b> {url} - append a radio {url} to the playlist </li>
|
||||
<li> <b>!<u>rbq</u>uery </b> {keyword} - query http://www.radio-browser.info for a radio station </li>
|
||||
<li> <b>!<u>rbp</u>lay </b> {id} - play a radio station with {id} (eg. !rbplay 96746) </li>
|
||||
<li> <b>!<u>l</u>istfile </b> [{pattern}] - display list of available files (that match the regex pattern if {pattern} is given) </li>
|
||||
<li> <b>!<u>o</u>ust </b> - stop playing and go to default channel </li>
|
||||
</ul>
|
||||
<b>Other</b>
|
||||
<ul>
|
||||
<li> <b>!<u>j</u>oinme </b> - join your own channel </li>
|
||||
</ul>
|
||||
|
||||
admin_help = Admin command:
|
||||
<br />!kill (kill the bot)
|
||||
<br />!update (update the bot)
|
||||
<br />!userban [user] (ban a user)
|
||||
<br />!userunban [user] (unban a user)
|
||||
<br />!urlban [url] (ban an url)
|
||||
<br />!urlunban [url] (unban an url)
|
||||
<br />!reload (reload the ban config)
|
||||
admin_help = <h3>Admin command</h3>
|
||||
<ul>
|
||||
<li><b>!<u>k</u>ill </b> - kill the bot</li>
|
||||
<li><b>!<u>up</u>date </b> - update the bot</li>
|
||||
<li><b>!<u>userb</u>an </b> {user} - ban a user</li>
|
||||
<li><b>!<u>useru</u>nban </b> {user} - unban a user</li>
|
||||
<li><b>!<u>urlb</u>an </b> {url} - ban an url</li>
|
||||
<li><b>!<u>urlu</u>nban </b> {url} - unban an url</li>
|
||||
<li><b>!dropdatabase</b> - clear the entire database, YOU SHOULD KNOW WHAT YOU ARE DOING.</li>
|
||||
</ul>
|
||||
|
||||
[debug]
|
||||
ffmpeg = False
|
||||
mumbleConnection = False
|
||||
|
||||
|
126
configuration.example.ini
Normal file
126
configuration.example.ini
Normal file
@ -0,0 +1,126 @@
|
||||
# ========================================================
|
||||
# botamusique Example Configuration File
|
||||
# Version 6
|
||||
# ========================================================
|
||||
# Rename this file into configuration.ini after editing.
|
||||
# Uncomment lines you needed, and carefully follow the
|
||||
# instructions.
|
||||
# ========================================================
|
||||
|
||||
# [server] section tells the bot how to connect to your murmur server.
|
||||
[server]
|
||||
host = 127.0.0.1
|
||||
port = 64738
|
||||
#password =
|
||||
#channel =
|
||||
#tokens = token1,token2
|
||||
#certificate =
|
||||
|
||||
# [bot] section stores some basic settings of the behavior of the bot.
|
||||
[bot]
|
||||
# 'username' is the user name of the bot.
|
||||
# 'comment' is the comment displayed by the bot.
|
||||
#username = botamusique
|
||||
#comment = Hi, I'm here to play radio, local music or youtube/soundcloud music. Have fun!
|
||||
|
||||
# 'volume' is default volume from 0 to 1.
|
||||
#volume = 0.1
|
||||
|
||||
# 'admin': Users allowed to kill the bot, or ban URLs. Separated by ';'
|
||||
#admin = User1;User2;
|
||||
|
||||
# 'music_folder': Folder that stores your local songs.
|
||||
#music_folder = music_folder/
|
||||
|
||||
# 'tmp_folder': Folder that stores the downloaded music.
|
||||
# 'tmp_folder_max_size': in MB, 0 for no cache, -1 for unlimited size
|
||||
# 'ignored_folders', 'ignored_files': files and folders that would be ignored during scanning.
|
||||
#tmp_folder = /tmp/
|
||||
#tmp_folder_max_size = 10
|
||||
#ignored_folders = tmp
|
||||
#ignored_files = Thumbs.db
|
||||
|
||||
# 'auto_check_update': check for updates every time the bot starts
|
||||
#auto_check_update = True
|
||||
#pip3_path = venv/bin/pip
|
||||
|
||||
# 'logfile': write logs into this file.
|
||||
#logfile =
|
||||
|
||||
#announce_current_music = True
|
||||
#allow_other_channel_message = False
|
||||
#allow_private_message = True
|
||||
|
||||
# 'save_playlist': If save_playlist is set True, the bot will save current
|
||||
# playlist before quitting and reload it the next time it start.
|
||||
#save_playlist = True
|
||||
|
||||
# 'max_track_playlist': Maximum track played when a playlist is added.
|
||||
#max_track_playlist = 20
|
||||
|
||||
# 'max_track_duration': Maximum music duration (minutes)
|
||||
#max_track_duration = 60
|
||||
|
||||
# 'ducking': If ducking is enabled, the bot will automatically attenuate its
|
||||
# volume when someone is talking.
|
||||
#ducking = False
|
||||
#ducking_volume = 0.05
|
||||
#ducking_threshold = 3000
|
||||
|
||||
# [webinterface] stores settings related to the web interface.
|
||||
[webinterface]
|
||||
# 'enable': Set 'enabled' to True if you'd like to use the web interface to manage
|
||||
# your playlist, upload files, etc.
|
||||
# The web interface is disable by default for security and performance reason.
|
||||
#enabled = False
|
||||
#listening_addr = 127.0.0.1
|
||||
#listening_port = 8181
|
||||
#is_web_proxified = True
|
||||
|
||||
# 'required_auth': Set this to True to enable password protection for the web interface.
|
||||
#require_auth = False
|
||||
#user =
|
||||
#password =
|
||||
|
||||
# [debug] stores some debug settings.
|
||||
[debug]
|
||||
# 'ffmpeg': Set ffmpeg to True if you want to display DEBUG level log of ffmpeg.
|
||||
#ffmpeg = False
|
||||
#mumbleConnection = False
|
||||
|
||||
# [radio] is a list of default radio stations.
|
||||
[radio]
|
||||
#ponyville = http://192.99.131.205:8000/stream.mp3 "Here a command of !radio comment"
|
||||
#luna = http://radio.ponyvillelive.com:8002/stream "calm and orchestra music"
|
||||
#radiobrony = http://62.210.138.34:8000/live "Borny music of a friend"
|
||||
#celestiaradio = http://celestia.aiverse.org:8000/mp3_256
|
||||
#jazz = http://jazz-wr04.ice.infomaniak.ch/jazz-wr04-128.mp3 "Jazz Yeah !"
|
||||
|
||||
|
||||
# [commands] is settings related to user command sent via mumble message.
|
||||
[commands]
|
||||
# 'command_symbol' is a list of characters the bot recognizes as command prefix.
|
||||
#command_symbol = !:!
|
||||
# 'split_username_at_space': This option split username, in case you use such kind of mumo plugins https://wiki.mumble.info/wiki/Mumo#Set_Status
|
||||
#split_username_at_space = False
|
||||
|
||||
|
||||
# You may also customize commands recognized by the bot. For a full list of commands,
|
||||
# see configuration.default.ini. Copy options you want to edit into this file.
|
||||
#play_file = file, f
|
||||
#play_file_match = filematch, fm
|
||||
|
||||
# [strings] is used to compose what the bot says. You can customize them to fit in
|
||||
# the style of your channel, or translate into your own language.
|
||||
# For a full list of strings, please see configuration.default.ini.
|
||||
# Copy options you want to edit into this file.
|
||||
# Note: please keep those {placeholder} of each string in your new string.
|
||||
[strings]
|
||||
# Some examples are:
|
||||
#current_volume = Current volume: {volume}
|
||||
#current_volume = 当前音量为{volume}
|
||||
#current_volume = よく聞いてね!今の音量は{volume}!<br />ちゃんと覚える:大音量で耳が悪くなる!
|
||||
#
|
||||
#bad_command = {command}: command not found
|
||||
#bad_command = {command}: 未知命令,键入'!help'以获取可用命令列表
|
||||
#bad_command = {command}がなに?食べれる?おいしいでしか?
|
32
constants.py
Normal file
32
constants.py
Normal file
@ -0,0 +1,32 @@
|
||||
import variables as var
|
||||
|
||||
def strings(option, *argv, **kwargs):
|
||||
string = ""
|
||||
try:
|
||||
string = var.config.get("strings", option)
|
||||
except KeyError as e:
|
||||
raise KeyError("Missed strings in configuration file: '{string}'. ".format(string=option) +
|
||||
"Please restore you configuration file back to default if necessary.")
|
||||
if argv or kwargs:
|
||||
try:
|
||||
formatted = string.format(*argv, **kwargs)
|
||||
return formatted
|
||||
except KeyError as e:
|
||||
raise KeyError(
|
||||
"Missed placeholder {{{placeholder}}} in string '{string}'. ".format(placeholder=str(e).strip("'"), string=option) +
|
||||
"Please restore you configuration file back to default if necessary.")
|
||||
except TypeError as e:
|
||||
raise KeyError(
|
||||
"Missed placeholder in string '{string}'. ".format(string=option) +
|
||||
"Please restore you configuration file back to default if necessary.")
|
||||
else:
|
||||
return string
|
||||
|
||||
def commands(command):
|
||||
string = ""
|
||||
try:
|
||||
string = var.config.get("commands", command)
|
||||
return string
|
||||
except KeyError as e:
|
||||
raise KeyError("Missed command in configuration file: '{string}'. ".format(string=command) +
|
||||
"Please restore you configuration file back to default if necessary.")
|
93
database.py
Normal file
93
database.py
Normal file
@ -0,0 +1,93 @@
|
||||
import sqlite3
|
||||
|
||||
class DatabaseError(Exception):
|
||||
pass
|
||||
|
||||
class Database:
|
||||
def __init__(self, db_path):
|
||||
self.db_path = db_path
|
||||
|
||||
# connect
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# check if table exists, or create one
|
||||
tables = cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='botamusique';").fetchall()
|
||||
if len(tables) == 0:
|
||||
cursor.execute("CREATE TABLE botamusique (section text, option text, value text, UNIQUE(section, option))")
|
||||
conn.commit()
|
||||
|
||||
conn.close()
|
||||
|
||||
def get(self, section, option, **kwargs):
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
result = cursor.execute("SELECT value FROM botamusique WHERE section=? AND option=?", (section, option)).fetchall()
|
||||
conn.close()
|
||||
|
||||
if len(result) > 0:
|
||||
return result[0][0]
|
||||
else:
|
||||
if 'fallback' in kwargs:
|
||||
return kwargs['fallback']
|
||||
else:
|
||||
raise DatabaseError("Item not found")
|
||||
|
||||
def getboolean(self, section, option, **kwargs):
|
||||
return bool(int(self.get(section, option, **kwargs)))
|
||||
|
||||
def getfloat(self, section, option, **kwargs):
|
||||
return float(self.get(section, option, **kwargs))
|
||||
|
||||
def getint(self, section, option, **kwargs):
|
||||
return int(self.get(section, option, **kwargs))
|
||||
|
||||
def set(self, section, option, value):
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
INSERT OR REPLACE INTO botamusique (section, option, value)
|
||||
VALUES (?, ?, ?)
|
||||
''', (section, option, value))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def has_option(self, section, option):
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
result = cursor.execute("SELECT value FROM botamusique WHERE section=? AND option=?", (section, option)).fetchall()
|
||||
conn.close()
|
||||
if len(result) > 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def remove_option(self, section, option):
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("DELETE FROM botamusique WHERE section=? AND option=?", (section, option))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def remove_section(self, section):
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("DELETE FROM botamusique WHERE section=?", (section, ))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def items(self, section):
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
results = cursor.execute("SELECT option, value FROM botamusique WHERE section=?", (section, )).fetchall()
|
||||
conn.close()
|
||||
|
||||
return map(lambda v: (v[0], v[1]), results)
|
||||
|
||||
def drop_table(self):
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("DROP TABLE botamusique")
|
||||
conn.close()
|
||||
|
||||
|
97
interface.py
97
interface.py
@ -14,6 +14,7 @@ import errno
|
||||
import media
|
||||
import logging
|
||||
import time
|
||||
import constants
|
||||
|
||||
|
||||
class ReverseProxied(object):
|
||||
@ -105,41 +106,39 @@ def index():
|
||||
music_library=music_library,
|
||||
os=os,
|
||||
playlist=var.playlist,
|
||||
user=var.user
|
||||
user=var.user,
|
||||
paused=var.botamusique.is_pause
|
||||
)
|
||||
|
||||
@web.route("/playlist", methods=['GET'])
|
||||
@requires_auth
|
||||
def playlist():
|
||||
if var.playlist.length() == 0:
|
||||
return jsonify([render_template('playlist.html',
|
||||
return jsonify({'items': [render_template('playlist.html',
|
||||
m=False,
|
||||
index=-1
|
||||
)]
|
||||
)
|
||||
})
|
||||
|
||||
data = []
|
||||
items = []
|
||||
|
||||
for index, item in enumerate(var.playlist.playlist):
|
||||
data.append(render_template('playlist.html',
|
||||
items.append(render_template('playlist.html',
|
||||
index=index,
|
||||
m=item,
|
||||
playlist=var.playlist
|
||||
)
|
||||
)
|
||||
|
||||
return jsonify(data)
|
||||
return jsonify({ 'items': items })
|
||||
|
||||
@web.route("/post", methods=['POST'])
|
||||
@requires_auth
|
||||
def post():
|
||||
folder_path = var.music_folder
|
||||
files = util.get_recursive_filelist_sorted(var.music_folder)
|
||||
music_library = util.Dir(folder_path)
|
||||
for file in files:
|
||||
music_library.add_file(file)
|
||||
if request.method == 'POST':
|
||||
logging.debug("Post request: "+ str(request.form))
|
||||
if request.form:
|
||||
logging.debug("Post request: "+ str(request.form))
|
||||
if 'add_file_bottom' in request.form and ".." not in request.form['add_file_bottom']:
|
||||
path = var.config.get('bot', 'music_folder') + request.form['add_file_bottom']
|
||||
if os.path.isfile(path):
|
||||
@ -147,8 +146,8 @@ def post():
|
||||
'path' : request.form['add_file_bottom'],
|
||||
'title' : '',
|
||||
'user' : 'Web'}
|
||||
var.playlist.append(var.botamusique.get_music_tag_info(item, path))
|
||||
logging.info('web: add to playlist(bottom): ' + item['path'])
|
||||
item = var.playlist.append(util.get_music_tag_info(item))
|
||||
logging.info('web: add to playlist(bottom): ' + util.format_debug_song_string(item))
|
||||
|
||||
elif 'add_file_next' in request.form and ".." not in request.form['add_file_next']:
|
||||
path = var.config.get('bot', 'music_folder') + request.form['add_file_next']
|
||||
@ -157,11 +156,11 @@ def post():
|
||||
'path' : request.form['add_file_next'],
|
||||
'title' : '',
|
||||
'user' : 'Web'}
|
||||
var.playlist.insert(
|
||||
item = var.playlist.insert(
|
||||
var.playlist.current_index + 1,
|
||||
var.botamusique.get_music_tag_info(item, var.config.get('bot', 'music_folder') + item['path'])
|
||||
item
|
||||
)
|
||||
logging.info('web: add to playlist(next): ' + item['path'])
|
||||
logging.info('web: add to playlist(next): ' + util.format_debug_song_string(item))
|
||||
|
||||
elif ('add_folder' in request.form and ".." not in request.form['add_folder']) or ('add_folder_recursively' in request.form and ".." not in request.form['add_folder_recursively']):
|
||||
try:
|
||||
@ -175,50 +174,63 @@ def post():
|
||||
print('folder:', folder)
|
||||
|
||||
if os.path.isdir(var.config.get('bot', 'music_folder') + folder):
|
||||
|
||||
files = util.get_recursive_filelist_sorted(var.music_folder)
|
||||
music_library = util.Dir(folder_path)
|
||||
for file in files:
|
||||
music_library.add_file(file)
|
||||
|
||||
if 'add_folder_recursively' in request.form:
|
||||
files = music_library.get_files_recursively(folder)
|
||||
else:
|
||||
files = music_library.get_files(folder)
|
||||
|
||||
files = list(map(lambda file: var.botamusique.get_music_tag_info({'type':'file','path': os.path.join(folder, file), 'user':'Web'}, \
|
||||
var.config.get('bot', 'music_folder') + os.path.join(folder, file)), files))
|
||||
files = list(map(lambda file:
|
||||
{'type':'file',
|
||||
'path': os.path.join(folder, file),
|
||||
'user':'Web'}, files))
|
||||
|
||||
files = var.playlist.extend(files)
|
||||
|
||||
for file in files:
|
||||
logging.info("web: add to playlist: %s" % util.format_debug_song_string(file))
|
||||
|
||||
logging.info("web: add to playlist: " + " ,".join([file['path'] for file in files]))
|
||||
var.playlist.extend(files)
|
||||
|
||||
elif 'add_url' in request.form:
|
||||
var.playlist.append({'type':'url',
|
||||
music = {'type':'url',
|
||||
'url': request.form['add_url'],
|
||||
'user': 'Web',
|
||||
'ready': 'validation'})
|
||||
logging.info("web: add to playlist: " + request.form['add_url'])
|
||||
media.url.get_url_info()
|
||||
'ready': 'validation'}
|
||||
media.url.get_url_info(music)
|
||||
music = var.playlist.append(music)
|
||||
logging.info("web: add to playlist: " + util.format_debug_song_string(music))
|
||||
var.playlist.playlist[-1]['ready'] = "no"
|
||||
|
||||
elif 'add_radio' in request.form:
|
||||
var.playlist.append({'type': 'radio',
|
||||
music = var.playlist.append({'type': 'radio',
|
||||
'path': request.form['add_radio'],
|
||||
'user': "Web"})
|
||||
logging.info("web: add to playlist: " + request.form['add_radio'])
|
||||
logging.info("web: add to playlist: " + util.format_debug_song_string(music))
|
||||
|
||||
elif 'delete_music' in request.form:
|
||||
music = var.playlist.playlist[int(request.form['delete_music'])]
|
||||
logging.info("web: delete from playlist: " + str(music['path'] if 'path' in music else music['url']))
|
||||
logging.info("web: delete from playlist: " + util.format_debug_song_string(music))
|
||||
|
||||
if len(var.playlist.playlist) >= int(request.form['delete_music']):
|
||||
if var.playlist.current_index == int(request.form['delete_music']):
|
||||
var.botamusique.pause()
|
||||
if var.playlist.length() >= int(request.form['delete_music']):
|
||||
if int(request.form['delete_music']) == var.playlist.current_index:
|
||||
var.playlist.remove(int(request.form['delete_music']))
|
||||
var.botamusique.launch_music()
|
||||
var.botamusique.stop()
|
||||
var.botamusique.launch_music(int(request.form['delete_music']))
|
||||
else:
|
||||
var.playlist.remove(int(request.form['delete_music']))
|
||||
|
||||
|
||||
elif 'play_music' in request.form:
|
||||
music = var.playlist.playlist[int(request.form['play_music'])]
|
||||
logging.info("web: jump to: " + str(music['path'] if 'path' in music else music['url']))
|
||||
logging.info("web: jump to: " + util.format_debug_song_string(music))
|
||||
|
||||
if len(var.playlist.playlist) >= int(request.form['play_music']):
|
||||
var.botamusique.pause()
|
||||
var.botamusique.stop()
|
||||
var.botamusique.launch_music(int(request.form['play_music']))
|
||||
|
||||
elif 'delete_music_file' in request.form and ".." not in request.form['delete_music_file']:
|
||||
@ -237,11 +249,17 @@ def post():
|
||||
elif 'action' in request.form:
|
||||
action = request.form['action']
|
||||
if action == "randomize":
|
||||
var.playlist.randomize()
|
||||
elif action == "stop":
|
||||
var.botamusique.pause()
|
||||
elif action == "clear":
|
||||
var.botamusique.stop()
|
||||
var.playlist.randomize()
|
||||
var.botamusique.resume()
|
||||
elif action == "stop":
|
||||
var.botamusique.stop()
|
||||
elif action == "pause":
|
||||
var.botamusique.pause()
|
||||
elif action == "resume":
|
||||
var.botamusique.resume()
|
||||
elif action == "clear":
|
||||
var.botamusique.clear()
|
||||
elif action == "volume_up":
|
||||
if var.botamusique.volume_set + 0.03 < 1.0:
|
||||
var.botamusique.volume_set = var.botamusique.volume_set + 0.03
|
||||
@ -255,7 +273,10 @@ def post():
|
||||
var.botamusique.volume_set = 0
|
||||
logging.info("web: volume up to %d" % (var.botamusique.volume_set * 100))
|
||||
|
||||
return jsonify({'ver': var.playlist.version})
|
||||
if(var.playlist.length() > 0):
|
||||
return jsonify({'ver': var.playlist.version, 'empty': False, 'play': not var.botamusique.is_pause})
|
||||
else:
|
||||
return jsonify({'ver': var.playlist.version, 'empty': True, 'play': False})
|
||||
|
||||
@web.route('/upload', methods=["POST"])
|
||||
def upload():
|
||||
|
@ -1,6 +1,8 @@
|
||||
import youtube_dl
|
||||
import variables as var
|
||||
import util
|
||||
import random
|
||||
import json
|
||||
|
||||
class PlayList:
|
||||
playlist = []
|
||||
@ -9,25 +11,35 @@ class PlayList:
|
||||
|
||||
def append(self, item):
|
||||
self.version += 1
|
||||
item = util.get_music_tag_info(item)
|
||||
self.playlist.append(item)
|
||||
|
||||
return item
|
||||
|
||||
def insert(self, index, item):
|
||||
self.version += 1
|
||||
|
||||
if index == -1:
|
||||
index = self.current_index
|
||||
|
||||
item = util.get_music_tag_info(item)
|
||||
self.playlist.insert(index, item)
|
||||
|
||||
if index <= self.current_index:
|
||||
self.current_index += 1
|
||||
|
||||
return item
|
||||
|
||||
def length(self):
|
||||
return len(self.playlist)
|
||||
|
||||
def extend(self, items):
|
||||
self.version += 1
|
||||
items = list(map(
|
||||
lambda item: util.get_music_tag_info(item),
|
||||
items))
|
||||
self.playlist.extend(items)
|
||||
return items
|
||||
|
||||
def next(self):
|
||||
self.version += 1
|
||||
@ -51,10 +63,14 @@ class PlayList:
|
||||
|
||||
if index == -1:
|
||||
index = self.current_index
|
||||
|
||||
removed = self.playlist[index]
|
||||
del self.playlist[index]
|
||||
|
||||
if self.current_index <= index:
|
||||
self.next()
|
||||
if self.current_index > index:
|
||||
self.current_index -= 1
|
||||
|
||||
return removed
|
||||
|
||||
def current_item(self):
|
||||
return self.playlist[self.current_index]
|
||||
@ -81,12 +97,12 @@ class PlayList:
|
||||
|
||||
def randomize(self):
|
||||
# current_index will lose track after shuffling, thus we take current music out before shuffling
|
||||
current = self.current_item()
|
||||
del self.playlist[self.current_index]
|
||||
#current = self.current_item()
|
||||
#del self.playlist[self.current_index]
|
||||
|
||||
random.shuffle(self.playlist)
|
||||
|
||||
self.playlist.insert(0, current)
|
||||
#self.playlist.insert(0, current)
|
||||
self.current_index = 0
|
||||
self.version += 1
|
||||
|
||||
@ -95,8 +111,25 @@ class PlayList:
|
||||
self.playlist = []
|
||||
self.current_index = 0
|
||||
|
||||
def save(self):
|
||||
var.db.remove_section("playlist_item")
|
||||
var.db.set("playlist", "current_index", self.current_index)
|
||||
for index, item in enumerate(self.playlist):
|
||||
var.db.set("playlist_item", str(index), json.dumps(item))
|
||||
|
||||
def load(self):
|
||||
current_index = var.db.getint("playlist", "current_index", fallback=-1)
|
||||
if current_index == -1:
|
||||
return
|
||||
|
||||
items = list(var.db.items("playlist_item"))
|
||||
items.sort(key=lambda v: int(v[0]))
|
||||
self.playlist = list(map(lambda v: json.loads(v[1]), items))
|
||||
self.current_index = current_index
|
||||
|
||||
|
||||
def get_playlist_info(url, start_index=0, user=""):
|
||||
items = []
|
||||
ydl_opts = {
|
||||
'extract_flat': 'in_playlist'
|
||||
}
|
||||
@ -104,6 +137,16 @@ def get_playlist_info(url, start_index=0, user=""):
|
||||
for i in range(2):
|
||||
try:
|
||||
info = ydl.extract_info(url, download=False)
|
||||
# # if url is not a playlist but a video
|
||||
# if 'entries' not in info and 'webpage_url' in info:
|
||||
# music = {'type': 'url',
|
||||
# 'title': info['title'],
|
||||
# 'url': info['webpage_url'],
|
||||
# 'user': user,
|
||||
# 'ready': 'validation'}
|
||||
# items.append(music)
|
||||
# return items
|
||||
|
||||
playlist_title = info['title']
|
||||
for j in range(start_index, min(len(info['entries']), start_index + var.config.getint('bot', 'max_track_playlist'))):
|
||||
# Unknow String if No title into the json
|
||||
@ -111,7 +154,6 @@ def get_playlist_info(url, start_index=0, user=""):
|
||||
# Add youtube url if the url in the json isn't a full url
|
||||
url = info['entries'][j]['url'] if info['entries'][j]['url'][0:4] == 'http' else "https://www.youtube.com/watch?v=" + info['entries'][j]['url']
|
||||
|
||||
# append the music to a list of futur music to play
|
||||
music = {'type': 'url',
|
||||
'title': title,
|
||||
'url': url,
|
||||
@ -120,32 +162,30 @@ def get_playlist_info(url, start_index=0, user=""):
|
||||
'playlist_title': playlist_title,
|
||||
'playlist_url': url,
|
||||
'ready': 'validation'}
|
||||
var.playlist.append(music)
|
||||
except youtube_dl.utils.DownloadError:
|
||||
items.append(music)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
return True
|
||||
return False
|
||||
|
||||
return items
|
||||
|
||||
def get_music_info(index=0):
|
||||
ydl_opts = {
|
||||
'playlist_items': str(index)
|
||||
}
|
||||
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
|
||||
for i in range(2):
|
||||
try:
|
||||
info = ydl.extract_info(var.playlist.playlist[index]['url'], download=False)
|
||||
# Check if the Duration is longer than the config
|
||||
if var.playlist[index]['current_index'] == index:
|
||||
var.playlist[index]['current_duration'] = info['entries'][0]['duration'] / 60
|
||||
var.playlist[index]['current_title'] = info['entries'][0]['title']
|
||||
# Check if the Duration of the next music is longer than the config (async download)
|
||||
elif var.playlist[index]['current_index'] == index - 1:
|
||||
var.playlist[index]['next_duration'] = info['entries'][0]['duration'] / 60
|
||||
var.playlist[index]['next_title'] = info['entries'][0]['title']
|
||||
except youtube_dl.utils.DownloadError:
|
||||
pass
|
||||
else:
|
||||
return True
|
||||
return False
|
||||
# def get_music_info(index=0):
|
||||
# ydl_opts = {
|
||||
# 'playlist_items': str(index)
|
||||
# }
|
||||
# with youtube_dl.YoutubeDL(ydl_opts) as ydl:
|
||||
# for i in range(2):
|
||||
# try:
|
||||
# info = ydl.extract_info(var.playlist.playlist[index]['url'], download=False)
|
||||
# # Check if the Duration is longer than the config
|
||||
# if var.playlist[index]['current_index'] == index:
|
||||
# var.playlist[index]['current_duration'] = info['entries'][0]['duration'] / 60
|
||||
# var.playlist[index]['current_title'] = info['entries'][0]['title']
|
||||
# # Check if the Duration of the next music is longer than the config (async download)
|
||||
# elif var.playlist[index]['current_index'] == index - 1:
|
||||
# var.playlist[index]['next_duration'] = info['entries'][0]['duration'] / 60
|
||||
# var.playlist[index]['next_title'] = info['entries'][0]['title']
|
||||
# except youtube_dl.utils.DownloadError:
|
||||
# pass
|
||||
# else:
|
||||
# return True
|
||||
# return False
|
||||
|
@ -68,6 +68,6 @@ def get_radio_title(url):
|
||||
title = m.group(1)
|
||||
if title:
|
||||
return title.decode()
|
||||
except (urllib.error.URLError, urllib.error.HTTPError):
|
||||
except (urllib.error.URLError, urllib.error.HTTPError, http.client.BadStatusLine):
|
||||
pass
|
||||
return 'Unable to get the music title'
|
||||
return 'Unknown title'
|
||||
|
897
mumbleBot.py
897
mumbleBot.py
File diff suppressed because it is too large
Load Diff
@ -37,10 +37,10 @@
|
||||
<input type="text" value="{{ subdirpath }}" name="directory" hidden>
|
||||
<button type="submit" class="btn btn-primary btn-sm btn-space"><i class="fa fa-download" aria-hidden="true"></i></button>
|
||||
</form>
|
||||
<form method="post">
|
||||
<input type="text" value="{{ subdirpath }}" name="delete_folder" hidden>
|
||||
<button type="submit" class="btn btn-danger btn-sm btn-space"><i class="fas fa-trash-alt"></i></button>
|
||||
</form>
|
||||
<button type="submit" class="btn btn-danger btn-sm btn-space"
|
||||
onclick="request('/post', {delete_folder : '{{ subdirpath }}'}, true)">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
<div class="collapse multi-collapse" id="multiCollapse-{{ subdirid }}">
|
||||
@ -81,10 +81,10 @@
|
||||
<input type="text" value="{{ filepath }}" name="file" hidden>
|
||||
<button type="submit" class="btn btn-primary btn-sm btn-space"><i class="fa fa-download" aria-hidden="true"></i></button>
|
||||
</form>
|
||||
<form method="post">
|
||||
<input type="text" value="{{ filepath }}" name="delete_music_file" hidden>
|
||||
<button type="submit" class="btn btn-danger btn-sm btn-space"><i class="fas fa-trash-alt"></i></button>
|
||||
</form>
|
||||
<button type="submit" class="btn btn-danger btn-sm btn-space"
|
||||
onclick="request('/post', {delete_music_file : '{{ filepath }}'}, true)">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
@ -124,8 +124,19 @@
|
||||
onclick="request('/post', {action : 'randomize'})">
|
||||
<i class="fas fa-random" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger btn-space"
|
||||
onclick="request('/post', {action : 'stop'})">
|
||||
|
||||
<button type="button" id="play-btn" class="btn btn-info btn-space"
|
||||
onclick="request('/post', {action : 'resume'})" disabled>
|
||||
<i class="fas fa-play" aria-hidden="true"></i>
|
||||
</button>
|
||||
|
||||
<button type="button" id="pause-btn" class="btn btn-warning btn-space"
|
||||
onclick="request('/post', {action : 'pause'})" disabled>
|
||||
<i class="fas fa-pause" aria-hidden="true"></i>
|
||||
</button>
|
||||
|
||||
<button type="button" id="stop-btn" class="btn btn-danger btn-space"
|
||||
onclick="request('/post', {action : 'stop'})" disabled>
|
||||
<i class="fas fa-stop" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
@ -208,7 +219,7 @@
|
||||
<div class="card-body">
|
||||
<form action="./upload" method="post" enctype="multipart/form-data">
|
||||
<div class="row" style="margin-bottom: 5px;">
|
||||
<div id="uploadBox" class="col-lg-8 input-group">
|
||||
<div id="uploadBox" class="col-lg-7 input-group">
|
||||
<div id="uploadField" style="display: flex; width: 100%">
|
||||
<div class="custom-file btn-space">
|
||||
<input type="file" name="file[]" class="custom-file-input" id="uploadSelectFile"
|
||||
@ -227,10 +238,9 @@
|
||||
<option value="{{ dir }}">
|
||||
{% endfor %}
|
||||
</datalist>
|
||||
|
||||
<button class="btn btn-outline-secondary" type="submit"
|
||||
id="uploadSubmit">Upload</button>
|
||||
</div>
|
||||
<button class="btn btn-primary btn-space" type="submit"
|
||||
id="uploadSubmit" style="margin-left: -5px;">Upload!</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@ -239,7 +249,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bs-docs-section">
|
||||
<div class="bs-docs-section" style="margin-bottom: 150px;">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
@ -247,13 +257,12 @@
|
||||
<h5 class="card-title">Add URL</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post">
|
||||
<label>Add Youtube/Soundcloud URL</label>
|
||||
<div class="input-group">
|
||||
<input class="form-control btn-space" type="text" name="add_url">
|
||||
<button type="submit" class="btn btn-primary">Add URL</button>
|
||||
</div>
|
||||
</form>
|
||||
<label>Add Youtube/Soundcloud URL</label>
|
||||
<div class="input-group">
|
||||
<input class="form-control btn-space" type="text" id="add_url_input" placeholder="URL...">
|
||||
<button type="submit" class="btn btn-primary"
|
||||
onclick="request('/post', {add_url : $('#add_url_input')[0].value })">Add URL</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -263,13 +272,12 @@
|
||||
<h5 class="card-title">Add Radio</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post">
|
||||
<label>Add Radio URL</label>
|
||||
<div class="input-group">
|
||||
<input class="form-control btn-space" type="text" name="add_radio">
|
||||
<button type="submit" class="btn btn-primary">Add Radio</button>
|
||||
</div>
|
||||
</form>
|
||||
<label>Add Radio URL</label>
|
||||
<div class="input-group">
|
||||
<input class="form-control btn-space" type="text" id="add_radio_input" placeholder="Radio Address...">
|
||||
<button type="submit" class="btn btn-primary"
|
||||
onclick="request('/post', {add_radio : $('#add_radio_input')[0].value })">Add Radio</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -294,27 +302,35 @@
|
||||
|
||||
var playlist_ver = 0;
|
||||
|
||||
function request(url, _data){
|
||||
function request(url, _data, refresh=false){
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/post',
|
||||
data : _data,
|
||||
statusCode : {
|
||||
200 : function(data) {
|
||||
if (data.ver > playlist_ver) {
|
||||
if (data.ver !== playlist_ver) {
|
||||
updatePlaylist();
|
||||
playlist_ver = data.ver;
|
||||
}
|
||||
updateControls(data.empty, data.play);
|
||||
}
|
||||
}
|
||||
});
|
||||
if(refresh){
|
||||
location.reload()
|
||||
}
|
||||
}
|
||||
|
||||
function displayPlaylist(data){
|
||||
// console.info(data);
|
||||
$("#playlist-table tr").remove();
|
||||
$.each(data, function(index, item){
|
||||
|
||||
var items = data.items;
|
||||
$.each(items, function(index, item){
|
||||
$("#playlist-table").append(item);
|
||||
})
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function updatePlaylist(){
|
||||
@ -327,6 +343,23 @@
|
||||
});
|
||||
}
|
||||
|
||||
function updateControls(empty, play){
|
||||
if(empty){
|
||||
$("#play-btn").prop("disabled", true);
|
||||
$("#pause-btn").prop("disabled", true);
|
||||
$("#stop-btn").prop("disabled", true);
|
||||
}else{
|
||||
if(play){
|
||||
$("#play-btn").prop("disabled", true);
|
||||
$("#pause-btn").prop("disabled", false);
|
||||
$("#stop-btn").prop("disabled", false);
|
||||
}else{
|
||||
$("#play-btn").prop("disabled", false);
|
||||
$("#pause-btn").prop("disabled", true);
|
||||
$("#stop-btn").prop("disabled", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check the version of playlist to see if update is needed.
|
||||
setInterval(function(){
|
||||
@ -335,10 +368,11 @@
|
||||
url : '/post',
|
||||
statusCode : {
|
||||
200 : function(data){
|
||||
if(data.ver > playlist_ver){
|
||||
if(data.ver !== playlist_ver){
|
||||
updatePlaylist();
|
||||
playlist_ver = data.ver;
|
||||
}
|
||||
updateControls(data.empty, data.play);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -41,7 +41,7 @@
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-success btn-sm btn-space"
|
||||
<button type="button" class="btn btn-info btn-sm btn-space"
|
||||
onclick="request('/post', {play_music : '{{ index }}'})">
|
||||
<i class="fa fa-play" aria-hidden="true"></i>
|
||||
</button>
|
||||
|
238
util.py
238
util.py
@ -1,15 +1,26 @@
|
||||
#!/usr/bin/python3
|
||||
# coding=utf-8
|
||||
|
||||
import hashlib
|
||||
import magic
|
||||
import os
|
||||
import sys
|
||||
import variables as var
|
||||
import constants
|
||||
import zipfile
|
||||
import urllib.request
|
||||
import mutagen
|
||||
import re
|
||||
import subprocess as sp
|
||||
import logging
|
||||
import youtube_dl
|
||||
from importlib import reload
|
||||
from PIL import Image
|
||||
from io import BytesIO
|
||||
from sys import platform
|
||||
import base64
|
||||
import media
|
||||
import media.radio
|
||||
|
||||
def get_recursive_filelist_sorted(path):
|
||||
filelist = []
|
||||
@ -35,6 +46,156 @@ def get_recursive_filelist_sorted(path):
|
||||
return filelist
|
||||
|
||||
|
||||
def get_music_tag_info(music, uri = ""):
|
||||
|
||||
if "path" in music:
|
||||
if not uri:
|
||||
uri = var.config.get('bot', 'music_folder') + music["path"]
|
||||
|
||||
if os.path.isfile(uri):
|
||||
match = re.search("(.+)\.(.+)", uri)
|
||||
if match is None:
|
||||
return music
|
||||
|
||||
file_no_ext = match[1]
|
||||
ext = match[2]
|
||||
|
||||
try:
|
||||
im = None
|
||||
path_thumbnail = file_no_ext + ".jpg"
|
||||
if os.path.isfile(path_thumbnail):
|
||||
im = Image.open(path_thumbnail)
|
||||
|
||||
if ext == "mp3":
|
||||
# title: TIT2
|
||||
# artist: TPE1, TPE2
|
||||
# album: TALB
|
||||
# cover artwork: APIC:
|
||||
tags = mutagen.File(uri)
|
||||
if 'TIT2' in tags:
|
||||
music['title'] = tags['TIT2'].text[0]
|
||||
if 'TPE1' in tags: # artist
|
||||
music['artist'] = tags['TPE1'].text[0]
|
||||
|
||||
if im is None:
|
||||
if "APIC:" in tags:
|
||||
im = Image.open(BytesIO(tags["APIC:"].data))
|
||||
|
||||
elif ext == "m4a" or ext == "m4b" or ext == "mp4" or ext == "m4p":
|
||||
# title: ©nam (\xa9nam)
|
||||
# artist: ©ART
|
||||
# album: ©alb
|
||||
# cover artwork: covr
|
||||
tags = mutagen.File(uri)
|
||||
if '©nam' in tags:
|
||||
music['title'] = tags['©nam'][0]
|
||||
if '©ART' in tags: # artist
|
||||
music['artist'] = tags['©ART'][0]
|
||||
|
||||
if im is None:
|
||||
if "covr" in tags:
|
||||
im = Image.open(BytesIO(tags["covr"][0]))
|
||||
|
||||
if im:
|
||||
im.thumbnail((100, 100), Image.ANTIALIAS)
|
||||
buffer = BytesIO()
|
||||
im = im.convert('RGB')
|
||||
im.save(buffer, format="JPEG")
|
||||
music['thumbnail'] = base64.b64encode(buffer.getvalue()).decode('utf-8')
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
uri = music['url']
|
||||
|
||||
# if nothing found
|
||||
if 'title' not in music:
|
||||
match = re.search("([^\.]+)\.?.*", os.path.basename(uri))
|
||||
music['title'] = match[1]
|
||||
|
||||
return music
|
||||
|
||||
def format_song_string(music):
|
||||
display = ''
|
||||
source = music["type"]
|
||||
title = music["title"] if "title" in music else "Unknown title"
|
||||
artist = music["artist"] if "artist" in music else "Unknown artist"
|
||||
|
||||
if source == "radio":
|
||||
display = "[radio] {title} from {url} by {user}".format(
|
||||
title=media.radio.get_radio_title(music["url"]),
|
||||
url=music["url"],
|
||||
user=music["user"]
|
||||
)
|
||||
elif source == "url" and 'from_playlist' in music:
|
||||
display = "[url] {title} (from playlist <a href=\"{url}\">{playlist}</a>) by {user}".format(
|
||||
title=title,
|
||||
url=music["playlist_url"],
|
||||
playlist=music["playlist_title"],
|
||||
user=music["user"]
|
||||
)
|
||||
elif source == "url":
|
||||
display = "[url] <a href=\"{url}\">{title}</a> by {user}".format(
|
||||
title=title,
|
||||
url=music["url"],
|
||||
user=music["user"]
|
||||
)
|
||||
elif source == "file":
|
||||
display = "[file] {artist} - {title} by {user}".format(
|
||||
title=title,
|
||||
artist=artist,
|
||||
user=music["user"]
|
||||
)
|
||||
|
||||
return display
|
||||
|
||||
def format_debug_song_string(music):
|
||||
display = ''
|
||||
source = music["type"]
|
||||
title = music["title"] if "title" in music else "??"
|
||||
artist = music["artist"] if "artist" in music else "??"
|
||||
|
||||
if source == "radio":
|
||||
display = "[radio] {url} by {user}".format(
|
||||
url=music["url"],
|
||||
user=music["user"]
|
||||
)
|
||||
elif source == "url" and 'from_playlist' in music:
|
||||
display = "[url] {title} ({url}) from playlist {playlist} by {user}".format(
|
||||
title=title,
|
||||
url=music["url"],
|
||||
playlist=music["playlist_title"],
|
||||
user=music["user"]
|
||||
)
|
||||
elif source == "url":
|
||||
display = "[url] {title} ({url}) by {user}".format(
|
||||
title=title,
|
||||
url=music["url"],
|
||||
user=music["user"]
|
||||
)
|
||||
elif source == "file":
|
||||
display = "[file] {artist} - {title} ({path}) by {user}".format(
|
||||
title=title,
|
||||
artist=artist,
|
||||
path=music["path"],
|
||||
user=music["user"]
|
||||
)
|
||||
|
||||
return display
|
||||
|
||||
def format_current_playing():
|
||||
music = var.playlist.current_item()
|
||||
display = format_song_string(music)
|
||||
|
||||
thumbnail_html = ''
|
||||
if 'thumbnail' in music:
|
||||
thumbnail_html = '<img width="80" src="data:image/jpge;base64,' + \
|
||||
music['thumbnail'] + '"/>'
|
||||
|
||||
display = (constants.strings('now_playing', item=display, thumb=thumbnail_html))
|
||||
|
||||
return display
|
||||
|
||||
|
||||
# - zips all files of the given zippath (must be a directory)
|
||||
# - returns the absolute path of the created zip file
|
||||
# - zip file will be in the applications tmp folder (according to configuration)
|
||||
@ -70,52 +231,47 @@ def zipdir(zippath, zipname_prefix=None):
|
||||
return zipname
|
||||
|
||||
|
||||
def write_db():
|
||||
with open(var.dbfile, 'w') as f:
|
||||
var.db.write(f)
|
||||
|
||||
|
||||
def get_user_ban():
|
||||
res = "List of ban hash"
|
||||
for i in var.db.items("user_ban"):
|
||||
res += "<br/>" + i[0]
|
||||
return res
|
||||
|
||||
def new_release_version():
|
||||
v = int(urllib.request.urlopen(urllib.request.Request("https://azlux.fr/botamusique/version")).read())
|
||||
return v
|
||||
|
||||
def update(version):
|
||||
v = int(urllib.request.urlopen(urllib.request.Request("https://azlux.fr/botamusique/version")).read())
|
||||
v = new_release_version()
|
||||
if v > version:
|
||||
logging.info('New version, starting update')
|
||||
logging.info('update: new version, start updating...')
|
||||
tp = sp.check_output(['/usr/bin/env', 'bash', 'update.sh']).decode()
|
||||
logging.debug(tp)
|
||||
logging.info('Update pip librairies dependancies')
|
||||
logging.info('update: update pip librairies dependancies')
|
||||
tp = sp.check_output([var.config.get('bot', 'pip3_path'), 'install', '--upgrade', '-r', 'requirements.txt']).decode()
|
||||
msg = "New version installed"
|
||||
msg = "New version installed, please restart the bot."
|
||||
|
||||
else:
|
||||
logging.info('Starting update youtube-dl via pip3')
|
||||
logging.info('update: starting update youtube-dl via pip3')
|
||||
tp = sp.check_output([var.config.get('bot', 'pip3_path'), 'install', '--upgrade', 'youtube-dl']).decode()
|
||||
msg = ""
|
||||
if "Requirement already up-to-date" in tp:
|
||||
msg += "Youtube-dl is up-to-date"
|
||||
else:
|
||||
msg += "Update done : " + tp.split('Successfully installed')[1]
|
||||
msg += "Update done: " + tp.split('Successfully installed')[1]
|
||||
reload(youtube_dl)
|
||||
msg += "<br/> Youtube-dl reloaded"
|
||||
return msg
|
||||
|
||||
|
||||
def user_ban(user):
|
||||
var.db.set("user_ban", user, None)
|
||||
res = "User " + user + " banned"
|
||||
write_db()
|
||||
return res
|
||||
|
||||
|
||||
def user_unban(user):
|
||||
var.db.remove_option("user_ban", user)
|
||||
res = "Done"
|
||||
write_db()
|
||||
return res
|
||||
|
||||
|
||||
@ -129,16 +285,53 @@ def get_url_ban():
|
||||
def url_ban(url):
|
||||
var.db.set("url_ban", url, None)
|
||||
res = "url " + url + " banned"
|
||||
write_db()
|
||||
return res
|
||||
|
||||
|
||||
def url_unban(url):
|
||||
var.db.remove_option("url_ban", url)
|
||||
res = "Done"
|
||||
write_db()
|
||||
return res
|
||||
|
||||
def pipe_no_wait(pipefd):
|
||||
''' Used to fetch the STDERR of ffmpeg. pipefd is the file descriptor returned from os.pipe()'''
|
||||
if platform == "linux" or platform == "linux2" or platform == "darwin":
|
||||
import fcntl
|
||||
import os
|
||||
try:
|
||||
fl = fcntl.fcntl(pipefd, fcntl.F_GETFL)
|
||||
fcntl.fcntl(pipefd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
|
||||
except:
|
||||
print(sys.exc_info()[1])
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
elif platform == "win32":
|
||||
# https://stackoverflow.com/questions/34504970/non-blocking-read-on-os-pipe-on-windows
|
||||
import msvcrt
|
||||
import os
|
||||
|
||||
from ctypes import windll, byref, wintypes, GetLastError, WinError
|
||||
from ctypes.wintypes import HANDLE, DWORD, POINTER, BOOL
|
||||
|
||||
LPDWORD = POINTER(DWORD)
|
||||
PIPE_NOWAIT = wintypes.DWORD(0x00000001)
|
||||
ERROR_NO_DATA = 232
|
||||
|
||||
SetNamedPipeHandleState = windll.kernel32.SetNamedPipeHandleState
|
||||
SetNamedPipeHandleState.argtypes = [HANDLE, LPDWORD, LPDWORD, LPDWORD]
|
||||
SetNamedPipeHandleState.restype = BOOL
|
||||
|
||||
h = msvcrt.get_osfhandle(pipefd)
|
||||
|
||||
res = windll.kernel32.SetNamedPipeHandleState(h, byref(PIPE_NOWAIT), None, None)
|
||||
if res == 0:
|
||||
print(WinError())
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
|
||||
class Dir(object):
|
||||
def __init__(self, path):
|
||||
@ -225,3 +418,16 @@ class Dir(object):
|
||||
val.render_text(ident + 1)
|
||||
for file in self.files:
|
||||
print('{}{}'.format(' ' * (ident + 1) * 4, file))
|
||||
|
||||
|
||||
# Parse the html from the message to get the URL
|
||||
|
||||
def get_url_from_input(string):
|
||||
if string.startswith('http'):
|
||||
return string
|
||||
p = re.compile('href="(.+?)"', re.IGNORECASE)
|
||||
res = re.search(p, string)
|
||||
if res:
|
||||
return res.group(1)
|
||||
else:
|
||||
return False
|
Loading…
x
Reference in New Issue
Block a user