Merge upstream changes

This commit is contained in:
Fabian Würfl 2018-06-01 17:02:12 +02:00
commit be8ee41e2a
11 changed files with 632 additions and 129 deletions

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -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.

View File

@ -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.

View File

@ -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)
======

View File

@ -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:
<br />!play_file <path>
<br />!play_url <url> - youtube or soundcloud
<br />!play_radio <url> - url of a stream
<br />!list - display list of available tracks
<br />!queue - display items in queue
<br />!next - jump to the next music of the playlist
<br />!stop - stop and clear the playlist
<br />!play_file - stop + Go to default channel
<br />!oust - stop + Go to default channel
<br />!v - get or change the volume (in %)
<br />!joinme
[debug]
ffmpeg = False
mumbleConnection = False
mumbleConnection = False

View File

@ -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")

View File

@ -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') + '<br />'
msg += '<br />'.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} (<a href=\"{url}\">{url}</a>)".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] + "<br />" + 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('<br>'.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') + '<br />'
for (type, path) in var.playlist:
msg += '({}) {}<br />'.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)

View File

@ -1,3 +1,5 @@
opuslib==2.0.0
protobuf==3.4.0
flask
flask
youtube-dl
python-magic

View File

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

View File

@ -1,3 +1,44 @@
{% macro dirlisting(dir, path='') -%}
<ul>
{% for subdirname, subdirobj in dir.get_subdirs().items() %}
{%- set subdirpath = os.path.relpath(subdirobj.fullpath, music_library.fullpath) %}
<li class="directory">
<span>{{ subdirname }}/&nbsp;</span>
<form method="post" class="directory form1">
<input type="text" value="{{ subdirpath }}" name="add_folder" hidden>
<input type="submit" value="Add all tracks from this folder">
</form>
<form method="post" class="directory form2">
<input type="text" value="{{ subdirpath }}" name="add_folder_recursively" hidden>
<input type="submit" value="Add all tracks from this folder (recursively)">
</form>
<form action="./download" method="get" class="directory form3">
<input type="text" value="{{ subdirpath }}" name="directory" hidden>
<input type="submit" value="Download entire directory">
</form>
</li>
{{- 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) %}
<li class="file">
<form method="post" class="file file_add">
<input type="text" value="{{ filepath }}" name="add_file" hidden>
<input type="submit" value="Add">
</form>
<form action="./download" method="get" class="file file_download">
<input type="text" value="{{ filepath }}" name="file" hidden>
<input type="submit" value="Download">
&nbsp;{{ file }}
</form>
</li>
{% endfor %}
{%- endif %}
</ul>
{%- endmacro %}
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
@ -9,17 +50,22 @@
<body>
<a href="."><h5>Refresh</h5></a>
<br>
<div id="download">
<form action="./download" method="post" enctype="multipart/form-data">
<input type="file" name="music_file" value="Browse Music file"/>
<select name="directory">
{% for dir in all_files %}
<option value={{ dir }}>{{ dir }}</option>
<div id="upload">
<form action="./upload" method="post" enctype="multipart/form-data">
<input type="file" name="file" value="Browse Music file"/>
Upload into
<input list="targetdirs" id="targetdir" name="targetdir" placeholder="uploads" />
<datalist id="targetdirs">
<option value="uploads">
{% for dir in music_library.get_subdirs_recursively() %}
<option value="{{ dir }}">
{% endfor %}
</select>
</datalist>
<input type="submit" value="Upload"/>
</form>
</div>
<div id="url">
Add Youtube/Soundcloud URL :
<form method="post">
@ -34,37 +80,39 @@
<input type="submit" value="Add Radio">
</form>
</div>
<div id="playlist">
Current Playing :
Currently Playing :
{% if current_music %}
{{ current_music[0] }} > {{ current_music[2] }}
{{ current_music[0] }} {{ current_music[1] }}
{% if current_music[2] %}
(<a href="{{ current_music[2] }}">{{ current_music[2] }}</a>)
{% endif %}
{% else %}
No music
{% endif %}
<br>
<br />
Playlist :
<form method="post"><input type=text value="randomize" name="action" hidden><input type="submit" value="Randomize playlist"></form>
<form method="post"><input type="text" value="randomize" name="action" hidden><input type="submit" value="Randomize playlist"></form>
<ul>
{% for m in playlist %}
<li>{{ m[0] }} - {{ m[1] }}
<form method="post"><input type=text value="{{ m }}" name="delete_music" type="file" hidden><input type="submit" value="X"></form>
<form method="post"><input type="text" value="{{ m }}" name="delete_music" hidden><input type="submit" value="X"></form>
</li>
{% endfor %}
</ul>
<br>
{% for dir in all_files %}
{{ dir }}
<form method="post"><input type=text value={{ dir }} name="add_folder" hidden><input type="submit" value="add all folder"></form>
<br>
<ul>
{% for m in all_files[dir] %}
<li>
<form method="post"><input type=text value="{{ dir }}/{{ m }}" name="add_music" hidden><input type="submit" value="{{ m }}"></form>
</li>
{% endfor %}
</ul>
{% endfor %}
<h2>Music library:</h2>
<form action="./download" method="get" class="directory form1">
<input type="text" value="./" name="directory" hidden>
<input type="submit" value="Download entire music library">
</form>
<form method="post" class="directory form3">
<input type="text" value="./" name="add_folder_recursively" hidden>
<input type="submit" value="Add all tracks from music library (recursively)">
</form>
<br />
{{ dirlisting(music_library) }}
</div>
@ -75,4 +123,4 @@
</div>
</body>
</html>
</html>

143
util.py Normal file
View File

@ -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))

View File

@ -3,3 +3,4 @@ playlist = []
user = ""
music_folder = ""
is_proxified = False
config = None