Merge pull request #4 from BafDyce/uif

Need your version to not create conflit
This commit is contained in:
azlux 2018-06-03 22:30:32 +02:00 committed by GitHub
commit d91178dc68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 636 additions and 136 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 # 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) [Version Française ici](README.fr.md)
====== ======

View File

@ -4,8 +4,14 @@ volume = 0.1
admin = Azlux;AzMobile admin = Azlux;AzMobile
music_folder = /home/dmichel/botamusique/music/ music_folder = /home/dmichel/botamusique/music/
tmp_folder = /tmp/ tmp_folder = /tmp/
web_interface = False ignored_folders = tmp
ignored_files = Thumbs.db
[webinterface]
enabled = False
is_web_proxified = True is_web_proxified = True
listening_addr = 127.0.0.1
listening_port = 8181
[command] [command]
play_file = file play_file = file
@ -21,6 +27,7 @@ volume = v
kill = kill kill = kill
stop_and_getout = oust stop_and_getout = oust
joinme = joinme joinme = joinme
queue = queue
[radio] [radio]
ponyville = http://192.99.131.205:8000/stream.mp3 ponyville = http://192.99.131.205:8000/stream.mp3
@ -38,15 +45,19 @@ not_playing = Aucun stream en lecture
bad_file = Bad file asked bad_file = Bad file asked
no_file = Not file here no_file = Not file here
bad_url = Bad URL asked 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: help = Command available:
<br />!play_file <path> <br />!play_file <path>
<br />!play_url <url> - youtube or soundcloud <br />!play_url <url> - youtube or soundcloud
<br />!play_radio <url> - url of a stream <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 />!next - jump to the next music of the playlist
<br />!stop - stop and clear 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 />!v - get or change the volume (in %)
<br />!joinme <br />!joinme

View File

@ -1,12 +1,15 @@
#!/usr/bin/python3 #!/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 variables as var
import util
from datetime import datetime
import os.path import os.path
from os import listdir from os import listdir
import random import random
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
import errno
import media
class ReverseProxied(object): class ReverseProxied(object):
'''Wrap the application in this middleware and configure the '''Wrap the application in this middleware and configure the
@ -58,62 +61,165 @@ def init_proxy():
@web.route("/", methods=['GET', 'POST']) @web.route("/", methods=['GET', 'POST'])
def index(): def index():
folder_path = var.music_folder folder_path = var.music_folder
files = {} files = util.get_recursive_filelist_sorted(var.music_folder)
dirs = [f for f in listdir(folder_path) if os.path.isdir(os.path.join(folder_path, f))] music_library = util.Dir(folder_path)
for director in dirs: for file in files:
files[director] = [f for f in listdir(folder_path + director) if os.path.isfile(os.path.join(folder_path + director, f))] music_library.add_file(file)
if request.method == 'POST': if request.method == 'POST':
if 'add_music' in request.form and ".." not in request.form['add_music']: print(request.form)
var.playlist.append(['file', request.form['add_music']]) if 'add_file' in request.form and ".." not in request.form['add_file']:
item = ('file', request.form['add_file'], datetime.now().timestamp())
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), datetime.now().timestamp()), files))
print('Adding to playlist: ', files)
var.playlist.extend(files)
elif 'add_url' in request.form :
var.playlist.append(['url', request.form['add_url']]) 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']]) 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: elif 'delete_music' in request.form:
try: for item in var.playlist:
var.playlist.remove(["file", request.form['delete_music']]) if str(item[2]) == request.form['delete_music']:
except ValueError: var.playlist.remove(item)
pass break
elif 'action' in request.form: elif 'action' in request.form:
action = request.form['action'] action = request.form['action']
if action == "randomize": if action == "randomize":
random.shuffle(var.playlist) 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', return render_template('index.html',
current_music=var.current_music, all_files=files,
user=var.user, current_music=current_music,
playlist=var.playlist, music_library=music_library,
all_files=files) os=os,
playlist=var.playlist,
user=var.user)
@web.route('/download', methods=["POST"]) @web.route('/upload', methods=["POST"])
def download(): def upload():
print(request.form) file = request.files['file']
file = request.files['music_file']
if not file: if not file:
return redirect("./", code=406) return redirect("./", code=406)
elif file.filename == '':
return redirect("./", code=406) filename = secure_filename(file.filename).strip()
elif '..' in request.form['directory']: if filename == '':
return redirect("./", code=406) return redirect("./", code=406)
if file.name == "music_file" and "audio" in file.headers.to_list()[1][1]: targetdir = request.form['targetdir'].strip()
web.config['UPLOAD_FOLDER'] = var.music_folder + request.form['directory'] if targetdir == '':
filename = secure_filename(file.filename) targetdir = 'uploads/'
print(filename) elif '../' in targetdir:
file.save(os.path.join(web.config['UPLOAD_FOLDER'], filename)) 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) return redirect("./", code=302)
else: else:
return redirect("./", code=409) 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__': 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 youtube_dl
import media import media
import logging import logging
import util
class MumbleBot: class MumbleBot:
def __init__(self): def __init__(self, args):
signal.signal(signal.SIGINT, self.ctrl_caught) signal.signal(signal.SIGINT, self.ctrl_caught)
self.config = configparser.ConfigParser(interpolation=None) self.volume = var.config.getfloat('bot', 'volume')
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.channel = args.channel self.channel = args.channel
var.current_music = None var.current_music = None
@ -66,21 +53,22 @@ class MumbleBot:
var.playlist = [] var.playlist = []
var.user = args.user var.user = args.user
var.music_folder = self.config.get('bot', 'music_folder') var.music_folder = var.config.get('bot', 'music_folder')
var.is_proxified = self.config.getboolean("bot", "is_web_proxified") var.is_proxified = var.config.getboolean("webinterface", "is_web_proxified")
self.exit = False self.exit = False
self.nb_exit = 0 self.nb_exit = 0
self.thread = None self.thread = None
if self.config.getboolean("bot", "web_interface"): if var.config.getboolean("webinterface", "enabled"):
wi_addr = var.config.get("webinterface", "listening_addr")
wi_port = var.config.getint("webinterface", "listening_port")
interface.init_proxy() interface.init_proxy()
t = threading.Thread(target=start_web_interface) tt = threading.Thread(target=start_web_interface, args=(wi_addr, wi_port))
t.daemon = True tt.daemon = True
t.start() tt.start()
self.mumble = pymumble.Mumble(args.host, user=args.user, port=args.port, password=args.password, 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.callbacks.set_callback("text_received", self.message_received)
self.mumble.set_codec_profile("audio") self.mumble.set_codec_profile("audio")
@ -104,7 +92,7 @@ class MumbleBot:
self.nb_exit += 1 self.nb_exit += 1
def message_received(self, text): def message_received(self, text):
message = text.message message = text.message.strip()
if message[0] == '!': if message[0] == '!':
message = message[1:].split(' ', 1) message = message[1:].split(' ', 1)
if len(message) > 0: if len(message) > 0:
@ -117,76 +105,134 @@ class MumbleBot:
logging.info(command + ' - ' + parameter + ' by ' + self.mumble.users[text.actor]['name']) logging.info(command + ' - ' + parameter + ' by ' + self.mumble.users[text.actor]['name'])
if command == self.config.get('command', 'play_file') and parameter: if command == var.config.get('command', 'play_file') and parameter:
path = self.config.get('bot', 'music_folder') + parameter music_folder = var.config.get('bot', 'music_folder')
if "/" in parameter: # sanitize "../" and so on
self.mumble.users[text.actor].send_message(self.config.get('strings', 'bad_file')) path = os.path.abspath(os.path.join(music_folder, parameter))
elif os.path.isfile(path): if path.startswith(music_folder):
var.playlist.append(["file", path]) 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: 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]) var.playlist.append(["url", parameter])
elif command == self.config.get('command', 'play_radio') and parameter: elif command == var.config.get('command', 'play_radio') and parameter:
if self.config.has_option('radio', parameter): if var.config.has_option('radio', parameter):
parameter = self.config.get('radio', parameter) parameter = var.config.get('radio', parameter)
var.playlist.append(["radio", parameter]) var.playlist.append(["radio", parameter])
elif command == self.config.get('command', 'help'): elif command == var.config.get('command', 'help'):
self.send_msg_channel(self.config.get('strings', '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() self.stop()
elif command == self.config.get('command', 'kill'): elif command == var.config.get('command', 'kill'):
if self.is_admin(text.actor): if self.is_admin(text.actor):
self.stop() self.stop()
self.exit = True self.exit = True
else: 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() self.stop()
if self.channel: if self.channel:
self.mumble.channels.find_by_name(self.channel).move_in() 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']) 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: if parameter is not None and parameter.isdigit() and 0 <= int(parameter) <= 100:
self.volume = float(float(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'])) int(self.volume * 100), self.mumble.users[text.actor]['name']))
else: 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'): elif command == var.config.get('command', 'current_music'):
if var.current_music is not None: if var.current_music:
if var.current_music[0] == "radio": source = var.current_music[0]
self.send_msg_channel(media.get_radio_title(var.current_music[1]) + " sur " + var.current_music[2]) 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: 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: 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: 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) var.playlist.pop(0)
self.launch_next() self.launch_next()
else: 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() 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: 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): def is_admin(self, user):
username = self.mumble.users[user]['name'] 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: if username in list_admin:
return True return True
else: else:
@ -203,7 +249,7 @@ class MumbleBot:
var.current_music[1] = url var.current_music[1] = url
elif var.current_music[0] == "file": 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] title = var.current_music[1]
elif var.current_music[0] == "radio": elif var.current_music[0] == "radio":
@ -214,19 +260,19 @@ class MumbleBot:
path = url path = url
title = media.get_radio_server_description(url) title = media.get_radio_server_description(url)
if self.config.getboolean('debug', 'ffmpeg'): if var.config.getboolean('debug', 'ffmpeg'):
ffmpeg_debug = "debug" ffmpeg_debug = "debug"
else: else:
ffmpeg_debug = "warning" 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) self.thread = sp.Popen(command, stdout=sp.PIPE, bufsize=480)
var.current_music.append(title) var.current_music[2] = title
var.current_music.append(path) var.current_music[3] = path
def download_music(self, url): def download_music(self, url):
url_hash = hashlib.md5(url.encode()).hexdigest() 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 = { ydl_opts = {
'format': 'bestaudio/best', 'format': 'bestaudio/best',
'outtmpl': path, 'outtmpl': path,
@ -266,10 +312,10 @@ class MumbleBot:
if self.thread is None or not raw_music: if self.thread is None or not raw_music:
if len(var.playlist) != 0: 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) var.playlist.pop(0)
self.launch_next() self.launch_next()
elif len(var.playlist) == 0 and var.current_music: else:
var.current_music = None var.current_music = None
while self.mumble.sound_output.get_buffer_size() > 0: while self.mumble.sound_output.get_buffer_size() > 0:
@ -284,7 +330,7 @@ class MumbleBot:
var.playlist = [] var.playlist = []
def set_comment(self): 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): def send_msg_channel(self, msg, channel=None):
if not channel: if not channel:
@ -292,9 +338,31 @@ class MumbleBot:
channel.send_text_message(msg) channel.send_text_message(msg)
def start_web_interface(): def start_web_interface(addr, port):
interface.web.run(port=8181, host="0.0.0.0") print('Starting web interface on {}:{}'.format(addr, port))
interface.web.run(port=port, host=addr)
if __name__ == '__main__': 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")
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 opuslib==2.0.0
protobuf==3.4.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> <!DOCTYPE html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
@ -9,17 +50,22 @@
<body> <body>
<a href="."><h5>Refresh</h5></a> <a href="."><h5>Refresh</h5></a>
<br> <br>
<div id="download">
<form action="./download" method="post" enctype="multipart/form-data"> <div id="upload">
<input type="file" name="music_file" value="Browse Music file"/> <form action="./upload" method="post" enctype="multipart/form-data">
<select name="directory"> <input type="file" name="file" value="Browse Music file"/>
{% for dir in all_files %} Upload into
<option value={{ dir }}>{{ dir }}</option> <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 %} {% endfor %}
</select> </datalist>
<input type="submit" value="Upload"/> <input type="submit" value="Upload"/>
</form> </form>
</div> </div>
<div id="url"> <div id="url">
Add Youtube/Soundcloud URL : Add Youtube/Soundcloud URL :
<form method="post"> <form method="post">
@ -34,37 +80,39 @@
<input type="submit" value="Add Radio"> <input type="submit" value="Add Radio">
</form> </form>
</div> </div>
<div id="playlist"> <div id="playlist">
Current Playing : Currently Playing :
{% if current_music %} {% 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 %} {% else %}
No music No music
{% endif %} {% endif %}
<br> <br />
Playlist : 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> <ul>
{% for m in playlist %} {% for m in playlist %}
<li>{{ m[0] }} - {{ m[1] }} <li>{{ 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[2] }}" name="delete_music" hidden><input type="submit" value="X"></form>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
<br> <h2>Music library:</h2>
{% for dir in all_files %} <form action="./download" method="get" class="directory form1">
{{ dir }} <input type="text" value="./" name="directory" hidden>
<form method="post"><input type=text value={{ dir }} name="add_folder" hidden><input type="submit" value="add all folder"></form> <input type="submit" value="Download entire music library">
<br> </form>
<ul> <form method="post" class="directory form3">
{% for m in all_files[dir] %} <input type="text" value="./" name="add_folder_recursively" hidden>
<li> <input type="submit" value="Add all tracks from music library (recursively)">
<form method="post"><input type=text value="{{ dir }}/{{ m }}" name="add_music" hidden><input type="submit" value="{{ m }}"></form> </form>
</li> <br />
{% endfor %} {{ dirlisting(music_library) }}
</ul>
{% endfor %}
</div> </div>

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 = "" user = ""
music_folder = "" music_folder = ""
is_proxified = False is_proxified = False
config = None