diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..80c49db --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**Affected version** +The exact version you're using (git commit id). You should **always** only report bugs which you can reproduce on the latest version (`uif` branch), however **always** state the current commit id here (in case there are new commits between your report and us looking at it) + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..066b2d9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature request +about: Suggest an idea for this project + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/README.md b/README.md index a6bbec2..f0f49a5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,17 @@ # botamusique + +**Info:** + +> This is a fork of the official repository (located [here](https://github.com/azlux/botamusique)). +We are working on various features for our own version of the bot. However, we will +regularly merge upstream changes and will also create pull requests to merge back our +features to the upstream repo at some point. + +The remainder of this readme is from the original repo. +Note that the ToDo list at the end of the Readme is **outdated** and **not applicable** for this repository. + +--- + [Version Française ici](README.fr.md) ====== diff --git a/configuration.ini b/configuration.ini index d926ee2..b2d5dfe 100644 --- a/configuration.ini +++ b/configuration.ini @@ -6,6 +6,8 @@ music_folder = /home/dmichel/botamusique/music/ tmp_folder = /tmp/ web_interface = False is_web_proxified = True +ignored_folders = tmp +ignored_files = Thumbs.db [command] play_file = file @@ -21,6 +23,7 @@ volume = v kill = kill stop_and_getout = oust joinme = joinme +queue = queue [radio] ponyville = http://192.99.131.205:8000/stream.mp3 @@ -38,18 +41,22 @@ not_playing = Aucun stream en lecture bad_file = Bad file asked no_file = Not file here bad_url = Bad URL asked -empty_playlist = No more music into the playlist +multiple_matches = Track not found! Possible candidates: +queue_contents = The next items in the queue are: +queue_empty = No more music in the playlist! help = Command available:
!play_file
!play_url - youtube or soundcloud
!play_radio - url of a stream +
!list - display list of available tracks +
!queue - display items in queue
!next - jump to the next music of the playlist
!stop - stop and clear the playlist -
!play_file - stop + Go to default channel +
!oust - stop + Go to default channel
!v - get or change the volume (in %)
!joinme [debug] ffmpeg = False -mumbleConnection = False \ No newline at end of file +mumbleConnection = False diff --git a/interface.py b/interface.py index 05f1af0..72f8495 100644 --- a/interface.py +++ b/interface.py @@ -1,12 +1,14 @@ #!/usr/bin/python3 -from flask import Flask, render_template, request, redirect +from flask import Flask, render_template, request, redirect, send_file import variables as var +import util import os.path from os import listdir import random from werkzeug.utils import secure_filename - +import errno +import media class ReverseProxied(object): '''Wrap the application in this middleware and configure the @@ -58,24 +60,47 @@ def init_proxy(): @web.route("/", methods=['GET', 'POST']) def index(): folder_path = var.music_folder - files = {} - dirs = [f for f in listdir(folder_path) if os.path.isdir(os.path.join(folder_path, f))] - for director in dirs: - files[director] = [f for f in listdir(folder_path + director) if os.path.isfile(os.path.join(folder_path + director, f))] + 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': - if 'add_music' in request.form and ".." not in request.form['add_music']: - var.playlist.append(['file', request.form['add_music']]) + print(request.form) + if 'add_file' in request.form and ".." not in request.form['add_file']: + item = ('file', request.form['add_file']) + var.playlist.append(item) - if 'add_url' in request.form : + 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: + folder = request.form['add_folder'] + except: + folder = request.form['add_folder_recursively'] + + if not folder.endswith('/'): + folder += '/' + + print('folder:', folder) + 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: ( + 'file', + os.path.join(folder, file) + ), + files + )) + print('Adding to playlist: ', files) + var.playlist.extend(files) + + elif 'add_url' in request.form : var.playlist.append(['url', request.form['add_url']]) - if 'add_radio' in request.form: + elif 'add_radio' in request.form: var.playlist.append(['radio', request.form['add_radio']]) - if 'add_folder' in request.form and ".." not in request.form['add_folder']: - dir_files = [["file", request.form['add_folder'] + '/' + i] for i in files[request.form['add_folder']]] - var.playlist.extend(dir_files) elif 'delete_music' in request.form: try: var.playlist.remove(["file", request.form['delete_music']]) @@ -86,34 +111,120 @@ def index(): if action == "randomize": random.shuffle(var.playlist) + if var.current_music: + source = var.current_music[0] + # format for current_music below: + # (sourcetype, title, url or None) + if source == "radio": + current_music = ( + "[radio]", + media.get_radio_title(var.current_music[1]), + var.current_music[2] + ) + elif source == "url": + current_music = ( + "[url]", + var.current_music[2], + var.current_music[1] + ) + elif source == "file": + current_music = ( + "[file]", + var.current_music[2], + None + ) + else: + current_music = ( + "(??)[" + var.current_music[0] + "]", + var.current_music[1], + var.current_music[2], + ) + else: + current_music = None + return render_template('index.html', - current_music=var.current_music, - user=var.user, - playlist=var.playlist, - all_files=files) + all_files=files, + current_music=current_music, + music_library=music_library, + os=os, + playlist=var.playlist, + user=var.user) -@web.route('/download', methods=["POST"]) -def download(): - print(request.form) - - file = request.files['music_file'] +@web.route('/upload', methods=["POST"]) +def upload(): + file = request.files['file'] if not file: return redirect("./", code=406) - elif file.filename == '': - return redirect("./", code=406) - elif '..' in request.form['directory']: + + filename = secure_filename(file.filename).strip() + if filename == '': return redirect("./", code=406) - if file.name == "music_file" and "audio" in file.headers.to_list()[1][1]: - web.config['UPLOAD_FOLDER'] = var.music_folder + request.form['directory'] - filename = secure_filename(file.filename) - print(filename) - file.save(os.path.join(web.config['UPLOAD_FOLDER'], filename)) + targetdir = request.form['targetdir'].strip() + if targetdir == '': + targetdir = 'uploads/' + elif '../' in targetdir: + return redirect("./", code=406) + + #print('Uploading file:') + #print('filename:', filename) + #print('targetdir:', targetdir) + #print('mimetype:', file.mimetype) + + if "audio" in file.mimetype: + storagepath = os.path.abspath(os.path.join(var.music_folder, targetdir)) + if not storagepath.startswith(var.music_folder): + return redirect("./", code=406) + + try: + os.makedirs(storagepath) + except OSError as ee: + if ee.errno != errno.EEXIST: + return redirect("./", code=500) + + filepath = os.path.join(storagepath, filename) + if os.path.exists(filepath): + return redirect("./", code=406) + + file.save(filepath) return redirect("./", code=302) else: return redirect("./", code=409) +@web.route('/download', methods=["GET"]) +def download(): + if 'file' in request.args: + requested_file = request.args['file'] + if '../' not in requested_file: + folder_path = var.music_folder + files = util.get_recursive_filelist_sorted(var.music_folder) + + if requested_file in files: + filepath = os.path.join(folder_path, requested_file) + try: + return send_file(filepath, as_attachment=True) + except Exception as e: + self.log.exception(e) + self.Error(400) + elif 'directory' in request.args: + requested_dir = request.args['directory'] + folder_path = var.music_folder + requested_dir_fullpath = os.path.abspath(os.path.join(folder_path, requested_dir)) + '/' + if requested_dir_fullpath.startswith(folder_path): + if os.path.samefile(requested_dir_fullpath, folder_path): + prefix = 'all' + else: + prefix = secure_filename(os.path.relpath(requested_dir_fullpath, folder_path)) + zipfile = util.zipdir(requested_dir_fullpath, prefix) + try: + return send_file(zipfile, as_attachment=True) + except Exception as e: + self.log.exception(e) + self.Error(400) + + return redirect("./", code=400) + if __name__ == '__main__': - web.run(port=8181, host="0.0.0.0") + web.run(port=8181, host="127.0.0.1") diff --git a/mumbleBot.py b/mumbleBot.py index 28c9edf..fb2b889 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -19,26 +19,13 @@ import hashlib import youtube_dl import media import logging - +import util class MumbleBot: - def __init__(self): + def __init__(self, args): signal.signal(signal.SIGINT, self.ctrl_caught) - self.config = configparser.ConfigParser(interpolation=None) - self.config.read("configuration.ini", encoding='latin-1') - - parser = argparse.ArgumentParser(description='Bot for playing radio stream on Mumble') - parser.add_argument("-s", "--server", dest="host", type=str, required=True, help="The server's hostame of a mumble server") - parser.add_argument("-u", "--user", dest="user", type=str, required=True, help="Username you wish, Default=abot") - parser.add_argument("-P", "--password", dest="password", type=str, default="", help="Password if server requires one") - parser.add_argument("-p", "--port", dest="port", type=int, default=64738, help="Port for the mumble server") - parser.add_argument("-c", "--channel", dest="channel", type=str, default="", help="Default chanel for the bot") - parser.add_argument("-q", "--quiet", dest="quiet", action="store_true", help="Only Error logs") - - args = parser.parse_args() - self.volume = self.config.getfloat('bot', 'volume') - + self.volume = var.config.getfloat('bot', 'volume') self.channel = args.channel var.current_music = None @@ -66,21 +53,20 @@ class MumbleBot: var.playlist = [] var.user = args.user - var.music_folder = self.config.get('bot', 'music_folder') - var.is_proxified = self.config.getboolean("bot", "is_web_proxified") - + var.music_folder = var.config.get('bot', 'music_folder') + var.is_proxified = var.config.getboolean("bot", "is_web_proxified") self.exit = False self.nb_exit = 0 self.thread = None - if self.config.getboolean("bot", "web_interface"): + if var.config.getboolean("bot", "web_interface"): interface.init_proxy() - t = threading.Thread(target=start_web_interface) - t.daemon = True - t.start() + tt = threading.Thread(target=start_web_interface, args=(args.wi_addr, args.wi_port)) + tt.daemon = True + tt.start() self.mumble = pymumble.Mumble(args.host, user=args.user, port=args.port, password=args.password, - debug=self.config.getboolean('debug', 'mumbleConnection')) + debug=var.config.getboolean('debug', 'mumbleConnection')) self.mumble.callbacks.set_callback("text_received", self.message_received) self.mumble.set_codec_profile("audio") @@ -104,7 +90,7 @@ class MumbleBot: self.nb_exit += 1 def message_received(self, text): - message = text.message + message = text.message.strip() if message[0] == '!': message = message[1:].split(' ', 1) if len(message) > 0: @@ -117,76 +103,134 @@ class MumbleBot: logging.info(command + ' - ' + parameter + ' by ' + self.mumble.users[text.actor]['name']) - if command == self.config.get('command', 'play_file') and parameter: - path = self.config.get('bot', 'music_folder') + parameter - if "/" in parameter: - self.mumble.users[text.actor].send_message(self.config.get('strings', 'bad_file')) - elif os.path.isfile(path): - var.playlist.append(["file", path]) + if command == var.config.get('command', 'play_file') and parameter: + music_folder = var.config.get('bot', 'music_folder') + # sanitize "../" and so on + path = os.path.abspath(os.path.join(music_folder, parameter)) + if path.startswith(music_folder): + if os.path.isfile(path): + filename = path.replace(music_folder, '') + var.playlist.append(["file", filename]) + else: + # try to do a partial match + matches = [file for file in util.get_recursive_filelist_sorted(music_folder) if parameter.lower() in file.lower()] + if len(matches) == 0: + self.mumble.users[text.actor].send_message(var.config.get('strings', 'no_file')) + elif len(matches) == 1: + var.playlist.append(["file", matches[0]]) + else: + msg = var.config.get('strings', 'multiple_matches') + '
' + msg += '
'.join(matches) + self.mumble.users[text.actor].send_message(msg) else: - self.mumble.users[text.actor].send_message(self.config.get('strings', 'bad_file')) + self.mumble.users[text.actor].send_message(var.config.get('strings', 'bad_file')) - elif command == self.config.get('command', 'play_url') and parameter: + elif command == var.config.get('command', 'play_url') and parameter: var.playlist.append(["url", parameter]) - elif command == self.config.get('command', 'play_radio') and parameter: - if self.config.has_option('radio', parameter): - parameter = self.config.get('radio', parameter) + elif command == var.config.get('command', 'play_radio') and parameter: + if var.config.has_option('radio', parameter): + parameter = var.config.get('radio', parameter) var.playlist.append(["radio", parameter]) - elif command == self.config.get('command', 'help'): - self.send_msg_channel(self.config.get('strings', 'help')) + elif command == var.config.get('command', 'help'): + self.send_msg_channel(var.config.get('strings', 'help')) - elif command == self.config.get('command', 'stop'): + elif command == var.config.get('command', 'stop'): self.stop() - elif command == self.config.get('command', 'kill'): + elif command == var.config.get('command', 'kill'): if self.is_admin(text.actor): self.stop() self.exit = True else: - self.mumble.users[text.actor].send_message(self.config.get('strings', 'not_admin')) + self.mumble.users[text.actor].send_message(var.config.get('strings', 'not_admin')) - elif command == self.config.get('command', 'stop_and_getout'): + elif command == var.config.get('command', 'stop_and_getout'): self.stop() if self.channel: self.mumble.channels.find_by_name(self.channel).move_in() - elif command == self.config.get('command', 'joinme'): + elif command == var.config.get('command', 'joinme'): self.mumble.users.myself.move_in(self.mumble.users[text.actor]['channel_id']) - elif command == self.config.get('command', 'volume'): + elif command == var.config.get('command', 'volume'): if parameter is not None and parameter.isdigit() and 0 <= int(parameter) <= 100: self.volume = float(float(parameter) / 100) - self.send_msg_channel(self.config.get('strings', 'change_volume') % ( + self.send_msg_channel(var.config.get('strings', 'change_volume') % ( int(self.volume * 100), self.mumble.users[text.actor]['name'])) else: - self.send_msg_channel(self.config.get('strings', 'current_volume') % int(self.volume * 100)) + self.send_msg_channel(var.config.get('strings', 'current_volume') % int(self.volume * 100)) - elif command == self.config.get('command', 'current_music'): - if var.current_music is not None: - if var.current_music[0] == "radio": - self.send_msg_channel(media.get_radio_title(var.current_music[1]) + " sur " + var.current_music[2]) + elif command == var.config.get('command', 'current_music'): + if var.current_music: + source = var.current_music[0] + if source == "radio": + reply = "[radio] {title} sur {url}".format( + title=media.get_radio_title(var.current_music[1]), + url=var.current_music[2] + ) + elif source == "url": + reply = "[url] {title} ({url})".format( + title=var.current_music[2], + url=var.current_music[1] + ) + elif source == "file": + reply = "[file] {title}".format(title=var.current_music[2]) else: - self.send_msg_channel(var.current_music[2] + "
" + var.current_music[1]) + reply = "(?)[{}] {} {}".format( + var.current_music[0], + var.current_music[1], + var.current_music[2], + ) else: - self.mumble.users[text.actor].send_message(self.config.get('strings', 'not_playing')) + reply = var.config.get('strings', 'not_playing') - elif command == self.config.get('command', 'next'): + self.mumble.users[text.actor].send_message(reply) + + elif command == var.config.get('command', 'next'): if var.playlist: - var.current_music = var.playlist[0] + var.current_music = [var.playlist[0][0], var.playlist[0][1], None, None] var.playlist.pop(0) self.launch_next() else: - self.mumble.users[text.actor].send_message(self.config.get('strings', 'empty_playlist')) + self.mumble.users[text.actor].send_message(var.config.get('strings', 'queue_empty')) self.stop() + elif command == var.config.get('command', 'list'): + folder_path = var.config.get('bot', 'music_folder') + + files = util.get_recursive_filelist_sorted(folder_path) + if files : + self.mumble.users[text.actor].send_message('
'.join(files)) + else : + self.mumble.users[text.actor].send_message(var.config.get('strings', 'no_file')) + + elif command == var.config.get('command', 'queue'): + if len(var.playlist) == 0: + msg = var.config.get('strings', 'queue_empty') + else: + msg = var.config.get('strings', 'queue_contents') + '
' + for (type, path) in var.playlist: + msg += '({}) {}
'.format(type, path) + + self.send_msg_channel(msg) else: - self.mumble.users[text.actor].send_message(self.config.get('strings', 'bad_command')) + self.mumble.users[text.actor].send_message(var.config.get('strings', 'bad_command')) + + def launch_play_file(self, path): + self.stop() + if var.config.getboolean('debug', 'ffmpeg'): + ffmpeg_debug = "debug" + else: + ffmpeg_debug = "warning" + command = ["ffmpeg", '-v', ffmpeg_debug, '-nostdin', '-i', path, '-ac', '1', '-f', 's16le', '-ar', '48000', '-'] + self.thread = sp.Popen(command, stdout=sp.PIPE, bufsize=480) + self.playing = True def is_admin(self, user): username = self.mumble.users[user]['name'] - list_admin = self.config.get('bot', 'admin').split(';') + list_admin = var.config.get('bot', 'admin').split(';') if username in list_admin: return True else: @@ -203,7 +247,7 @@ class MumbleBot: var.current_music[1] = url elif var.current_music[0] == "file": - path = self.config.get('bot', 'music_folder') + var.current_music[1] + path = var.config.get('bot', 'music_folder') + var.current_music[1] title = var.current_music[1] elif var.current_music[0] == "radio": @@ -214,19 +258,19 @@ class MumbleBot: path = url title = media.get_radio_server_description(url) - if self.config.getboolean('debug', 'ffmpeg'): + if var.config.getboolean('debug', 'ffmpeg'): ffmpeg_debug = "debug" else: ffmpeg_debug = "warning" - command = ["ffmpeg", '-v', ffmpeg_debug, '-nostdin', '-i', path, '-ac', '1', '-f', 's16le', '-ar', '48000', '-'] + command = ["/usr/bin/ffmpeg", '-v', ffmpeg_debug, '-nostdin', '-i', path, '-ac', '1', '-f', 's16le', '-ar', '48000', '-'] self.thread = sp.Popen(command, stdout=sp.PIPE, bufsize=480) - var.current_music.append(title) - var.current_music.append(path) + var.current_music[2] = title + var.current_music[3] = path def download_music(self, url): url_hash = hashlib.md5(url.encode()).hexdigest() - path = self.config.get('bot', 'tmp_folder') + url_hash + ".mp3" + path = var.config.get('bot', 'tmp_folder') + url_hash + ".mp3" ydl_opts = { 'format': 'bestaudio/best', 'outtmpl': path, @@ -266,7 +310,7 @@ class MumbleBot: if self.thread is None or not raw_music: if len(var.playlist) != 0: - var.current_music = var.playlist[0] + var.current_music = [var.playlist[0][0], var.playlist[0][1], None, None] var.playlist.pop(0) self.launch_next() elif len(var.playlist) == 0 and var.current_music: @@ -284,7 +328,7 @@ class MumbleBot: var.playlist = [] def set_comment(self): - self.mumble.users.myself.comment(self.config.get('bot', 'comment')) + self.mumble.users.myself.comment(var.config.get('bot', 'comment')) def send_msg_channel(self, msg, channel=None): if not channel: @@ -292,9 +336,35 @@ class MumbleBot: channel.send_text_message(msg) -def start_web_interface(): - interface.web.run(port=8181, host="0.0.0.0") +def start_web_interface(addr, port): + print('Starting web interface on {}:{}'.format(addr, port)) + interface.web.run(port=port, host=addr) if __name__ == '__main__': - botamusique = MumbleBot() + parser = argparse.ArgumentParser(description='Bot for playing music on Mumble') + + # General arguments + parser.add_argument("--config", dest='config', type=str, default='configuration.ini', help='Load configuration from this file. Default: configuration.ini') + parser.add_argument("-q", "--quiet", dest="quiet", action="store_true", help="Only Error logs") + + # Mumble arguments + parser.add_argument("-s", "--server", dest="host", type=str, required=True, help="The server's hostame of a mumble server") + parser.add_argument("-u", "--user", dest="user", type=str, required=True, help="Username you wish, Default=abot") + parser.add_argument("-P", "--password", dest="password", type=str, default="", help="Password if server requires one") + parser.add_argument("-p", "--port", dest="port", type=int, default=64738, help="Port for the mumble server") + parser.add_argument("-c", "--channel", dest="channel", type=str, default="", help="Default chanel for the bot") + + # web interface arguments + parser.add_argument('--wi-port', dest='wi_port', type=int, default=8181, help='Listening port of the web interface') + parser.add_argument('--wi-addr', dest='wi_addr', type=str, default=None, help='Listening address of the web interface') + + args = parser.parse_args() + config = configparser.ConfigParser(interpolation=None) + parsed_configs = config.read(args.config, encoding='latin-1') + if len(parsed_configs) == 0: + print('Could not read configuration from file \"{}\"'.format(args.config), file=sys.stderr) + sys.exit() + + var.config = config + botamusique = MumbleBot(args) diff --git a/requirements.txt b/requirements.txt index e6b1ef3..0b1e08d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ opuslib==2.0.0 protobuf==3.4.0 -flask \ No newline at end of file +flask +youtube-dl +python-magic diff --git a/static/index.css b/static/index.css index e69de29..011d00c 100644 --- a/static/index.css +++ b/static/index.css @@ -0,0 +1,53 @@ +/* necessary to place both forms/buttons next to each other */ +li.file { + clear: both; + list-style-position: outside; +} + +/* necessary to place both forms/buttons next to each other */ +form.file { + /* Float both forms to the left */ + float: left; +} + +form.file.file_add { + margin-left: 5px; + margin-right: 5px; +} + +/* necessary to place both forms/buttons next to each other */ +form.file.file_download { + clear: right; + /* with some space to the left of the second form */ + margin-right: 20px; +} + +/* necessary to place all forms/buttons of the directory entries next to each other */ +li.directory { + clear: both; + list-style-position: outside; + margin-top: 15px; + margin-bottom: 15px; +} + +li.directory span { + float: left; +} + +form.directory { + float: left; +} + +form.directory.form1 { + margin-left: 5px; + margin-right: 5px; +} + +form.directory.form2 { + margin-right: 5px; +} + +form.directory.form3 { + clear: right; + margin-right: 5px; +} diff --git a/templates/index.html b/templates/index.html index 07f1513..760368f 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,3 +1,44 @@ +{% macro dirlisting(dir, path='') -%} +
    + {% for subdirname, subdirobj in dir.get_subdirs().items() %} + {%- set subdirpath = os.path.relpath(subdirobj.fullpath, music_library.fullpath) %} +
  • + {{ subdirname }}/  +
    + + +
    +
    + + +
    +
    + + +
    +
  • + {{- dirlisting(subdirobj, subdirpath) -}} + {% endfor %} + {%- set files = dir.get_files() %} + {%- if files %} + {% for file in files %} + {% set filepath = os.path.relpath(os.path.join(dir.fullpath, file), music_library.fullpath) %} +
  • +
    + + +
    +
    + + +  {{ file }} +
    +
  • + {% endfor %} + {%- endif %} +
+{%- endmacro %} + @@ -9,17 +50,22 @@
Refresh

-
-
- - + Upload into + + +
+
Add Youtube/Soundcloud URL :
@@ -34,37 +80,39 @@
+
- Current Playing : + Currently Playing : {% if current_music %} - {{ current_music[0] }} > {{ current_music[2] }} + {{ current_music[0] }} {{ current_music[1] }} + {% if current_music[2] %} + ({{ current_music[2] }}) + {% endif %} {% else %} No music {% endif %} -
+
Playlist : -
+
    {% for m in playlist %}
  • {{ m[0] }} - {{ m[1] }} -
    +
  • {% endfor %}
-
- {% for dir in all_files %} - {{ dir }} -
-
-
    - {% for m in all_files[dir] %} -
  • -
    -
  • - {% endfor %} -
- {% endfor %} +

Music library:

+
+ + +
+
+ + +
+
+ {{ dirlisting(music_library) }}
@@ -75,4 +123,4 @@ - \ No newline at end of file + diff --git a/util.py b/util.py new file mode 100644 index 0000000..1c4d4ed --- /dev/null +++ b/util.py @@ -0,0 +1,143 @@ +#!/usr/bin/python3 + +import hashlib +import magic +import os +import variables as var +import zipfile + +def get_recursive_filelist_sorted(path): + filelist = [] + for root, dirs, files in os.walk(path): + relroot = root.replace(path, '', 1) + if relroot != '' and relroot in var.config.get('bot', 'ignored_folders'): + continue + if len(relroot): + relroot += '/' + for file in files: + if file in var.config.get('bot', 'ignored_files'): + continue + + fullpath = os.path.join(path, relroot, file) + mime = magic.from_file(fullpath, mime=True) + if 'audio' in mime or 'audio' in magic.from_file(fullpath).lower() or 'video' in mime: + filelist.append(relroot + file) + + filelist.sort() + return filelist + +# - 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) +# - format of the filename itself = prefix_hash.zip +# - prefix can be controlled by the caller +# - hash is a sha1 of the string representation of the directories' contents (which are +# zipped) +def zipdir(zippath, zipname_prefix=None): + zipname = var.config.get('bot', 'tmp_folder') + if zipname_prefix and '../' not in zipname_prefix: + zipname += zipname_prefix.strip().replace('/', '_') + '_' + + files = get_recursive_filelist_sorted(zippath) + hash = hashlib.sha1((str(files).encode())).hexdigest() + zipname += hash + '.zip' + + if os.path.exists(zipname): + return zipname + + zipf = zipfile.ZipFile(zipname, 'w', zipfile.ZIP_DEFLATED) + + for file in files: + filepath = os.path.dirname(file) + file_to_add = os.path.join(zippath, file) + add_file_as = os.path.relpath(os.path.join(zippath, file), os.path.join(zippath, '..')) + zipf.write(file_to_add, add_file_as) + + zipf.close() + return zipname + +class Dir(object): + def __init__(self, path): + self.name = os.path.basename(path.strip('/')) + self.fullpath = path + self.subdirs = {} + self.files = [] + + def add_file(self, file): + if file.startswith(self.name + '/'): + file = file.replace(self.name + '/', '', 1) + + if '/' in file: + # This file is in a subdir + subdir = file.split('/')[0] + if subdir in self.subdirs: + self.subdirs[subdir].add_file(file) + else: + self.subdirs[subdir] = Dir(os.path.join(self.fullpath, subdir)) + self.subdirs[subdir].add_file(file) + else: + self.files.append(file) + return True + + def get_subdirs(self, path=None): + subdirs = [] + if path and path != '' and path != './': + subdir = path.split('/')[0] + if subdir in self.subdirs: + searchpath = '/'.join(path.split('/')[1::]) + subdirs = self.subdirs[subdir].get_subdirs(searchpath) + subdirs = list(map(lambda subsubdir: os.path.join(subdir, subsubdir), subdirs)) + else: + subdirs = self.subdirs + + return subdirs + + def get_subdirs_recursively(self, path=None): + subdirs = [] + if path and path != '' and path != './': + subdir = path.split('/')[0] + if subdir in self.subdirs: + searchpath = '/'.join(path.split('/')[1::]) + subdirs = self.subdirs[subdir].get_subdirs_recursively(searchpath) + else: + subdirs = list(self.subdirs.keys()) + + for key, val in self.subdirs.items(): + subdirs.extend(map(lambda subdir: key + '/' + subdir,val.get_subdirs_recursively())) + + subdirs.sort() + return subdirs + + def get_files(self, path=None): + files = [] + if path and path != '' and path != './': + subdir = path.split('/')[0] + if subdir in self.subdirs: + searchpath = '/'.join(path.split('/')[1::]) + files = self.subdirs[subdir].get_files(searchpath) + else: + files = self.files + + return files + + def get_files_recursively(self, path=None): + files = [] + if path and path != '' and path != './': + subdir = path.split('/')[0] + if subdir in self.subdirs: + searchpath = '/'.join(path.split('/')[1::]) + files = self.subdirs[subdir].get_files_recursively(searchpath) + else: + files = self.files + + for key, val in self.subdirs.items(): + files.extend(map(lambda file: key + '/' + file,val.get_files_recursively())) + + return files + + def render_text(self, ident=0): + print('{}{}/'.format(' ' * (ident * 4), self.name)) + for key, val in self.subdirs.items(): + val.render_text(ident+1) + for file in self.files: + print('{}{}'.format(' ' * ((ident + 1)) * 4, file)) diff --git a/variables.py b/variables.py index f7eb8ce..ef3e859 100644 --- a/variables.py +++ b/variables.py @@ -3,3 +3,4 @@ playlist = [] user = "" music_folder = "" is_proxified = False +config = None