Merge upstream changes
This commit is contained in:
commit
be8ee41e2a
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal 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.
|
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal 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.
|
13
README.md
13
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)
|
||||
|
||||
======
|
||||
|
@ -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
|
||||
|
173
interface.py
173
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")
|
||||
|
206
mumbleBot.py
206
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') + '<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)
|
||||
|
@ -1,3 +1,5 @@
|
||||
opuslib==2.0.0
|
||||
protobuf==3.4.0
|
||||
flask
|
||||
flask
|
||||
youtube-dl
|
||||
python-magic
|
||||
|
@ -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;
|
||||
}
|
@ -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 }}/ </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">
|
||||
{{ 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
143
util.py
Normal 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))
|
@ -3,3 +3,4 @@ playlist = []
|
||||
user = ""
|
||||
music_folder = ""
|
||||
is_proxified = False
|
||||
config = None
|
||||
|
Loading…
x
Reference in New Issue
Block a user