Initial commit of Bragi fork.

This commit is contained in:
Storm Dragon
2025-06-13 18:52:37 -04:00
parent 2760a14f01
commit b1e21af243
30 changed files with 1 additions and 16426 deletions

5
.gitignore vendored
View File

@ -1,4 +1,5 @@
# Created by .ignore support plugin (hsz.mobi)
CLAUDE.md
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
@ -58,9 +59,6 @@ coverage.xml
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
@ -116,7 +114,6 @@ tmp/
*.db
templates/*.html
# Pycharm
.idea/

View File

@ -1,769 +0,0 @@
#!/usr/bin/python3
import sqlite3
from functools import wraps
from flask import Flask, render_template, request, redirect, send_file, Response, jsonify, abort, session
from werkzeug.utils import secure_filename
import variables as var
import util
import math
import os
import os.path
import errno
from typing import Type
import media
import json
from media.item import dicts_to_items, dict_to_item, BaseItem
from media.file import FileItem
from media.url import URLItem
from media.url_from_playlist import PlaylistURLItem
from media.radio import RadioItem
from media.cache import get_cached_wrapper_from_scrap, get_cached_wrapper_by_id, get_cached_wrappers_by_tags, \
get_cached_wrapper
from database import MusicDatabase, Condition
import logging
import time
class ReverseProxied(object):
"""Wrap the application in this middleware and configure the
front-end server to add these headers, to let you quietly bind
this to a URL other than / and to an HTTP scheme that is
different than what is used locally.
In nginx:
location /myprefix {
proxy_pass http://192.168.0.1:5001;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Script-Name /myprefix;
}
:param app: the WSGI application
"""
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
script_name = environ.get('HTTP_X_SCRIPT_NAME', '')
if script_name:
environ['SCRIPT_NAME'] = script_name
path_info = environ['PATH_INFO']
if path_info.startswith(script_name):
environ['PATH_INFO'] = path_info[len(script_name):]
scheme = environ.get('HTTP_X_SCHEME', '')
if scheme:
environ['wsgi.url_scheme'] = scheme
real_ip = environ.get('HTTP_X_REAL_IP', '')
if real_ip:
environ['REMOTE_ADDR'] = real_ip
return self.app(environ, start_response)
root_dir = os.path.dirname(__file__)
web = Flask(__name__, template_folder=os.path.join(root_dir, "web/templates"))
#web.config['TEMPLATES_AUTO_RELOAD'] = True
log = logging.getLogger("bot")
user = 'Remote Control'
def init_proxy():
global web
if var.is_proxified:
web.wsgi_app = ReverseProxied(web.wsgi_app)
# https://stackoverflow.com/questions/29725217/password-protect-one-webpage-in-flask-app
def check_auth(username, password):
"""This function is called to check if a username /
password combination is valid.
"""
if username == var.config.get("webinterface", "user") and password == var.config.get("webinterface", "password"):
return True
web_users = json.loads(var.db.get("privilege", "web_access", fallback='[]'))
if username in web_users:
user_dict = json.loads(var.db.get("user", username, fallback='{}'))
if 'password' in user_dict and 'salt' in user_dict and \
util.verify_password(password, user_dict['password'], user_dict['salt']):
return True
return False
def authenticate():
"""Sends a 401 response that enables basic auth"""
global log
return Response('Could not verify your access level for that URL.\n'
'You have to login with proper credentials', 401,
{'WWW-Authenticate': 'Basic realm="Login Required"'})
bad_access_count = {}
banned_ip = []
def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
global log, user, bad_access_count, banned_ip
if request.remote_addr in banned_ip:
abort(403)
auth_method = var.config.get("webinterface", "auth_method")
if auth_method == 'password':
auth = request.authorization
if auth:
user = auth.username
if not check_auth(auth.username, auth.password):
if request.remote_addr in bad_access_count:
bad_access_count[request.remote_addr] += 1
log.info(f"web: failed login attempt, user: {auth.username}, from ip {request.remote_addr}."
f"{bad_access_count[request.remote_addr]} attempts.")
if bad_access_count[request.remote_addr] > var.config.getint("webinterface", "max_attempts",
fallback=10):
banned_ip.append(request.remote_addr)
log.info(f"web: access banned for {request.remote_addr}")
else:
bad_access_count[request.remote_addr] = 1
log.info(f"web: failed login attempt, user: {auth.username}, from ip {request.remote_addr}.")
return authenticate()
else:
return authenticate()
if auth_method == 'token':
if 'user' in session and 'token' not in request.args:
user = session['user']
return f(*args, **kwargs)
elif 'token' in request.args:
token = request.args.get('token')
token_user = var.db.get("web_token", token, fallback=None)
if token_user is not None:
user = token_user
user_info = var.db.get("user", user, fallback=None)
user_dict = json.loads(user_info)
user_dict['IP'] = request.remote_addr
var.db.set("user", user, json.dumps(user_dict))
log.debug(
f"web: new user access, token validated for the user: {token_user}, from ip {request.remote_addr}.")
session['token'] = token
session['user'] = token_user
return f(*args, **kwargs)
if request.remote_addr in bad_access_count:
bad_access_count[request.remote_addr] += 1
log.info(f"web: bad token from ip {request.remote_addr}, "
f"{bad_access_count[request.remote_addr]} attempts.")
if bad_access_count[request.remote_addr] > var.config.getint("webinterface", "max_attempts"):
banned_ip.append(request.remote_addr)
log.info(f"web: access banned for {request.remote_addr}")
else:
bad_access_count[request.remote_addr] = 1
log.info(f"web: bad token from ip {request.remote_addr}.")
return render_template(f'need_token.{var.language}.html',
name=var.config.get('bot', 'username'),
command=f"{var.config.get('commands', 'command_symbol')[0]}"
f"{var.config.get('commands', 'requests_webinterface_access')}")
return f(*args, **kwargs)
return decorated
def tag_color(tag):
num = hash(tag) % 8
if num == 0:
return "primary"
elif num == 1:
return "secondary"
elif num == 2:
return "success"
elif num == 3:
return "danger"
elif num == 4:
return "warning"
elif num == 5:
return "info"
elif num == 6:
return "light"
elif num == 7:
return "dark"
def build_tags_color_lookup():
color_lookup = {}
for tag in var.music_db.query_all_tags():
color_lookup[tag] = tag_color(tag)
return color_lookup
def get_all_dirs():
dirs = ["."]
paths = var.music_db.query_all_paths()
for path in paths:
pos = 0
while True:
pos = path.find("/", pos + 1)
if pos == -1:
break
folder = path[:pos]
if folder not in dirs:
dirs.append(folder)
return dirs
@web.route("/", methods=['GET'])
@requires_auth
def index():
return open(os.path.join(root_dir, f"web/templates/index.{var.language}.html"), "r").read()
@web.route("/playlist", methods=['GET'])
@requires_auth
def playlist():
if len(var.playlist) == 0:
return jsonify({
'items': [],
'current_index': -1,
'length': 0,
'start_from': 0
})
DEFAULT_DISPLAY_COUNT = 11
_from = 0
_to = 10
if 'range_from' in request.args and 'range_to' in request.args:
_from = int(request.args['range_from'])
_to = int(request.args['range_to'])
else:
if var.playlist.current_index - int(DEFAULT_DISPLAY_COUNT / 2) > 0:
_from = var.playlist.current_index - int(DEFAULT_DISPLAY_COUNT / 2)
_to = _from - 1 + DEFAULT_DISPLAY_COUNT
tags_color_lookup = build_tags_color_lookup() # TODO: cached this?
items = []
for index, item_wrapper in enumerate(var.playlist[_from: _to + 1]):
tag_tuples = []
for tag in item_wrapper.item().tags:
tag_tuples.append([tag, tags_color_lookup[tag]])
item: Type[BaseItem] = item_wrapper.item()
title = item.format_title()
artist = "??"
path = ""
duration = 0
if isinstance(item, FileItem):
path = item.path
if item.artist:
artist = item.artist
duration = item.duration
elif isinstance(item, URLItem):
path = f" <a href=\"{item.url}\"><i>{item.url}</i></a>"
duration = item.duration
elif isinstance(item, PlaylistURLItem):
path = f" <a href=\"{item.url}\"><i>{item.url}</i></a>"
artist = f" <a href=\"{item.playlist_url}\"><i>{item.playlist_title}</i></a>"
duration = item.duration
elif isinstance(item, RadioItem):
path = f" <a href=\"{item.url}\"><i>{item.url}</i></a>"
thumb = ""
if item.type != 'radio' and item.thumbnail:
thumb = f"data:image/PNG;base64,{item.thumbnail}"
else:
thumb = "static/image/unknown-album.png"
items.append({
'index': _from + index,
'id': item.id,
'type': item.display_type(),
'path': path,
'title': title,
'artist': artist,
'thumbnail': thumb,
'tags': tag_tuples,
'duration': duration
})
return jsonify({
'items': items,
'current_index': var.playlist.current_index,
'length': len(var.playlist),
'start_from': _from
})
def status():
if len(var.playlist) > 0:
return jsonify({'ver': var.playlist.version,
'current_index': var.playlist.current_index,
'empty': False,
'play': not var.bot.is_pause,
'mode': var.playlist.mode,
'volume': var.bot.volume_helper.plain_volume_set,
'playhead': var.bot.playhead
})
else:
return jsonify({'ver': var.playlist.version,
'current_index': var.playlist.current_index,
'empty': True,
'play': not var.bot.is_pause,
'mode': var.playlist.mode,
'volume': var.bot.volume_helper.plain_volume_set,
'playhead': 0
})
@web.route("/post", methods=['POST'])
@requires_auth
def post():
global log
payload = request.get_json() if request.is_json else request.form
if payload:
log.debug("web: Post request from %s: %s" % (request.remote_addr, str(payload)))
if 'add_item_at_once' in payload:
music_wrapper = get_cached_wrapper_by_id(payload['add_item_at_once'], user)
if music_wrapper:
var.playlist.insert(var.playlist.current_index + 1, music_wrapper)
log.info('web: add to playlist(next): ' + music_wrapper.format_debug_string())
if not var.bot.is_pause:
var.bot.interrupt()
else:
var.bot.is_pause = False
else:
abort(404)
if 'add_item_bottom' in payload:
music_wrapper = get_cached_wrapper_by_id(payload['add_item_bottom'], user)
if music_wrapper:
var.playlist.append(music_wrapper)
log.info('web: add to playlist(bottom): ' + music_wrapper.format_debug_string())
else:
abort(404)
elif 'add_item_next' in payload:
music_wrapper = get_cached_wrapper_by_id(payload['add_item_next'], user)
if music_wrapper:
var.playlist.insert(var.playlist.current_index + 1, music_wrapper)
log.info('web: add to playlist(next): ' + music_wrapper.format_debug_string())
else:
abort(404)
elif 'add_url' in payload:
music_wrapper = get_cached_wrapper_from_scrap(type='url', url=payload['add_url'], user=user)
var.playlist.append(music_wrapper)
log.info("web: add to playlist: " + music_wrapper.format_debug_string())
if len(var.playlist) == 2:
# If I am the second item on the playlist. (I am the next one!)
var.bot.async_download_next()
elif 'add_radio' in payload:
url = payload['add_radio']
music_wrapper = get_cached_wrapper_from_scrap(type='radio', url=url, user=user)
var.playlist.append(music_wrapper)
log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
elif 'delete_music' in payload:
music_wrapper = var.playlist[int(payload['delete_music'])]
log.info("web: delete from playlist: " + music_wrapper.format_debug_string())
if len(var.playlist) >= int(payload['delete_music']):
index = int(payload['delete_music'])
if index == var.playlist.current_index:
var.playlist.remove(index)
if index < len(var.playlist):
if not var.bot.is_pause:
var.bot.interrupt()
var.playlist.current_index -= 1
# then the bot will move to next item
else: # if item deleted is the last item of the queue
var.playlist.current_index -= 1
if not var.bot.is_pause:
var.bot.interrupt()
else:
var.playlist.remove(index)
elif 'play_music' in payload:
music_wrapper = var.playlist[int(payload['play_music'])]
log.info("web: jump to: " + music_wrapper.format_debug_string())
if len(var.playlist) >= int(payload['play_music']):
var.bot.play(int(payload['play_music']))
time.sleep(0.1)
elif 'move_playhead' in payload:
if float(payload['move_playhead']) < var.playlist.current_item().item().duration:
log.info(f"web: move playhead to {float(payload['move_playhead'])} s.")
var.bot.play(var.playlist.current_index, float(payload['move_playhead']))
elif 'delete_item_from_library' in payload:
_id = payload['delete_item_from_library']
var.playlist.remove_by_id(_id)
item = var.cache.get_item_by_id(_id)
if os.path.isfile(item.uri()):
log.info("web: delete file " + item.uri())
os.remove(item.uri())
var.cache.free_and_delete(_id)
time.sleep(0.1)
elif 'add_tag' in payload:
music_wrappers = get_cached_wrappers_by_tags([payload['add_tag']], user)
for music_wrapper in music_wrappers:
log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
var.playlist.extend(music_wrappers)
elif 'action' in payload:
action = payload['action']
if action == "random":
if var.playlist.mode != "random":
var.playlist = media.playlist.get_playlist("random", var.playlist)
else:
var.playlist.randomize()
var.bot.interrupt()
var.db.set('playlist', 'playback_mode', "random")
log.info("web: playback mode changed to random.")
if action == "one-shot":
var.playlist = media.playlist.get_playlist("one-shot", var.playlist)
var.db.set('playlist', 'playback_mode', "one-shot")
log.info("web: playback mode changed to one-shot.")
if action == "repeat":
var.playlist = media.playlist.get_playlist("repeat", var.playlist)
var.db.set('playlist', 'playback_mode', "repeat")
log.info("web: playback mode changed to repeat.")
if action == "autoplay":
var.playlist = media.playlist.get_playlist("autoplay", var.playlist)
var.db.set('playlist', 'playback_mode', "autoplay")
log.info("web: playback mode changed to autoplay.")
if action == "rescan":
var.cache.build_dir_cache()
var.music_db.manage_special_tags()
log.info("web: Local file cache refreshed.")
elif action == "stop":
if var.config.getboolean("bot", "clear_when_stop_in_oneshot") \
and var.playlist.mode == 'one-shot':
var.bot.clear()
else:
var.bot.stop()
elif action == "next":
if not var.bot.is_pause:
var.bot.interrupt()
else:
var.playlist.next()
var.bot.wait_for_ready = True
elif action == "pause":
var.bot.pause()
elif action == "resume":
var.bot.resume()
elif action == "clear":
var.bot.clear()
elif action == "volume_up":
if var.bot.volume_helper.plain_volume_set + 0.03 < 1.0:
var.bot.volume_helper.set_volume(var.bot.volume_helper.plain_volume_set + 0.03)
else:
var.bot.volume_helper.set_volume(1.0)
var.db.set('bot', 'volume', str(var.bot.volume_helper.plain_volume_set))
log.info("web: volume up to %d" % (var.bot.volume_helper.plain_volume_set * 100))
elif action == "volume_down":
if var.bot.volume_helper.plain_volume_set - 0.03 > 0:
var.bot.volume_helper.set_volume(var.bot.unconverted_volume - 0.03)
else:
var.bot.volume_helper.set_volume(1.0)
var.db.set('bot', 'volume', str(var.bot.volume_helper.plain_volume_set))
log.info("web: volume down to %d" % (var.bot.volume_helper.plain_volume_set * 100))
elif action == "volume_set_value":
if 'new_volume' in payload:
if float(payload['new_volume']) > 1:
var.bot.volume_helper.set_volume(1.0)
elif float(payload['new_volume']) < 0:
var.bot.volume_helper.set_volume(0)
else:
# value for new volume is between 0 and 1, round to two decimal digits
var.bot.volume_helper.set_volume(round(float(payload['new_volume']), 2))
var.db.set('bot', 'volume', str(var.bot.volume_helper.plain_volume_set))
log.info("web: volume set to %d" % (var.bot.volume_helper.plain_volume_set * 100))
return status()
def build_library_query_condition(form):
try:
condition = Condition()
types = form['type'].split(",")
sub_cond = Condition()
for type in types:
sub_cond.or_equal("type", type)
condition.and_sub_condition(sub_cond)
if form['type'] == 'file':
folder = form['dir']
if folder == ".":
folder = ""
if not folder.endswith('/') and folder:
folder += '/'
condition.and_like('path', folder + '%')
tags = form['tags'].split(",")
for tag in tags:
if tag:
condition.and_like("tags", f"%{tag},%", case_sensitive=False)
_keywords = form['keywords'].split(" ")
keywords = []
for kw in _keywords:
if kw:
keywords.append(kw)
for keyword in keywords:
condition.and_like("keywords", f"%{keyword}%", case_sensitive=False)
condition.order_by('create_at', desc=True)
return condition
except KeyError:
abort(400)
@web.route("/library/info", methods=['GET'])
@requires_auth
def library_info():
global log
while var.cache.dir_lock.locked():
time.sleep(0.1)
tags = var.music_db.query_all_tags()
max_upload_file_size = util.parse_file_size(var.config.get("webinterface", "max_upload_file_size"))
return jsonify(dict(
dirs=get_all_dirs(),
upload_enabled=var.config.getboolean("webinterface", "upload_enabled") or var.bot.is_admin(user),
delete_allowed=var.config.getboolean("bot", "delete_allowed") or var.bot.is_admin(user),
tags=tags,
max_upload_file_size=max_upload_file_size
))
@web.route("/library", methods=['POST'])
@requires_auth
def library():
global log
ITEM_PER_PAGE = 10
payload = request.form if request.form else request.json
if payload:
log.debug("web: Post request from %s: %s" % (request.remote_addr, str(payload)))
if payload['action'] in ['add', 'query', 'delete']:
condition = build_library_query_condition(payload)
total_count = 0
try:
total_count = var.music_db.query_music_count(condition)
except sqlite3.OperationalError:
pass
if not total_count:
return jsonify({
'items': [],
'total_pages': 0,
'active_page': 0
})
if payload['action'] == 'add':
items = dicts_to_items(var.music_db.query_music(condition))
music_wrappers = []
for item in items:
music_wrapper = get_cached_wrapper(item, user)
music_wrappers.append(music_wrapper)
log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
var.playlist.extend(music_wrappers)
return redirect("./", code=302)
elif payload['action'] == 'delete':
if var.config.getboolean("bot", "delete_allowed"):
items = dicts_to_items(var.music_db.query_music(condition))
for item in items:
var.playlist.remove_by_id(item.id)
item = var.cache.get_item_by_id(item.id)
if os.path.isfile(item.uri()):
log.info("web: delete file " + item.uri())
os.remove(item.uri())
var.cache.free_and_delete(item.id)
if len(os.listdir(var.music_folder + payload['dir'])) == 0:
os.rmdir(var.music_folder + payload['dir'])
time.sleep(0.1)
return redirect("./", code=302)
else:
abort(403)
else:
page_count = math.ceil(total_count / ITEM_PER_PAGE)
current_page = int(payload['page']) if 'page' in payload else 1
if current_page <= page_count:
condition.offset((current_page - 1) * ITEM_PER_PAGE)
else:
current_page = 1
condition.limit(ITEM_PER_PAGE)
items = dicts_to_items(var.music_db.query_music(condition))
results = []
for item in items:
result = {'id': item.id, 'title': item.title, 'type': item.display_type(),
'tags': [(tag, tag_color(tag)) for tag in item.tags]}
if item.type != 'radio' and item.thumbnail:
result['thumb'] = f"data:image/PNG;base64,{item.thumbnail}"
else:
result['thumb'] = "static/image/unknown-album.png"
if item.type == 'file':
result['path'] = item.path
result['artist'] = item.artist
else:
result['path'] = item.url
result['artist'] = "??"
results.append(result)
return jsonify({
'items': results,
'total_pages': page_count,
'active_page': current_page
})
elif payload['action'] == 'edit_tags':
tags = list(dict.fromkeys(payload['tags'].split(","))) # remove duplicated items
if payload['id'] in var.cache:
music_wrapper = get_cached_wrapper_by_id(payload['id'], user)
music_wrapper.clear_tags()
music_wrapper.add_tags(tags)
var.playlist.version += 1
else:
item = var.music_db.query_music_by_id(payload['id'])
item['tags'] = tags
var.music_db.insert_music(item)
return redirect("./", code=302)
else:
abort(400)
@web.route('/upload', methods=["POST"])
@requires_auth
def upload():
global log
if not var.config.getboolean("webinterface", "upload_enabled"):
abort(403)
file = request.files['file']
if not file:
abort(400)
filename = file.filename
if filename == '':
abort(400)
targetdir = request.form['targetdir'].strip()
if targetdir == '':
targetdir = 'uploads/'
elif '../' in targetdir:
abort(403)
log.info('web: Uploading file from %s:' % request.remote_addr)
log.info('web: - filename: ' + filename)
log.info('web: - targetdir: ' + targetdir)
log.info('web: - mimetype: ' + file.mimetype)
if "audio" in file.mimetype or "video" in file.mimetype:
storagepath = os.path.abspath(os.path.join(var.music_folder, targetdir))
if not storagepath.startswith(os.path.abspath(var.music_folder)):
abort(403)
try:
os.makedirs(storagepath)
except OSError as ee:
if ee.errno != errno.EEXIST:
log.error(f'web: failed to create directory {storagepath}')
abort(500)
filepath = os.path.join(storagepath, filename)
log.info('web: - file saved at: ' + filepath)
if os.path.exists(filepath):
return 'File existed!', 409
file.save(filepath)
else:
log.error(f'web: unsupported file type {file.mimetype}! File was not saved.')
return 'Unsupported media type!', 415
return '', 200
@web.route('/download', methods=["GET"])
@requires_auth
def download():
global log
if 'id' in request.args and request.args['id']:
item = dicts_to_items(var.music_db.query_music(
Condition().and_equal('id', request.args['id'])))[0]
requested_file = item.uri()
log.info('web: Download of file %s requested from %s:' % (requested_file, request.remote_addr))
try:
return send_file(requested_file, as_attachment=True)
except Exception as e:
log.exception(e)
abort(404)
else:
condition = build_library_query_condition(request.args)
items = dicts_to_items(var.music_db.query_music(condition))
zipfile = util.zipdir([item.uri() for item in items])
try:
return send_file(zipfile, as_attachment=True)
except Exception as e:
log.exception(e)
abort(404)
return abort(400)
if __name__ == '__main__':
web.run(port=8181, host="127.0.0.1")

View File

@ -1,173 +0,0 @@
{
"cli": {
"added_tags": "<b>{song}</b> wurde mit <i>{tags}</i> verschlagwortet.",
"added_tags_to_all": "Alle Lieder in der Playlist wurden mit <i>{tags}</i> verschlagwortet.",
"admin_help": "<h3>Adminbefehle</h3>\n<b>Bot</b>\n<ul>\n<li><b>!<u>k</u>ill </b> - Bot stoppen</li>\n<li><b>!update </b> - Bot update</li>\n<li><b>!userban </b> {user} - Nutzer bannen</li>\n<li><b>!userunban </b> {user} - Nutzer entbannen</li>\n<li><b>!urlbanlist </b> - Zeige alle gebannten URLs an</li>\n<li><b>!urlban </b> [{url}] - Banne {url} (oder das aktuelle Lied, wenn leer) and lösche diese URL aus der Bibliothek.</li>\n<li><b>!urlunban </b> {url} - Entbanne {url}</li>\n<li><b>!rescan </b> {url} - Erneuere den lokalen Cache der Musikdateien</li>\n<li><b>!dropdatabase</b> - Lösche die aktuelle Datenbank. Dadurch gehen alle Einstellungen und die Bibliothek verloren.</li>\n</ul>\n<b>Webinterface</b>\n<ul>\n<li><b>!<u>webuserlist</u></b> - Zeige alle Nutzer, die auf das Webinterface zugreifen dürfen (wenn die Authentifizierung auf 'password' gestellt ist).</li>\n<li><b>!<u>webuseradd</u> {nick name}</b> - Erlaube {nick name} den Zugriff auf das Webinterface (wenn die Authentifizierung auf 'password' gestellt ist).</li>\n<li><b>!<u>webuserdel</u> {nick name}</b> - Lösche den Zugriff von {nick name} auf das Webinterface (wenn die Authentifizierung auf 'password' gestellt ist).</li>\n</ul>",
"auto_paused": "Sende <i>!play</i>, um die Wiedergabe fortzusetzen!",
"bad_command": "<i>{command}</i>: Befehl nicht verfügbar. Sende <i>!help</i>, um dir alle möglichen Befehle anzuzeigen.",
"bad_parameter": "<i>{command}</i>: Ungültiges Argument.",
"bad_url": "URL nicht verfügbar.",
"cache_refreshed": "Cache erneuert!",
"change_ducking_volume": "Lautstärkeabsenkung wurde von {user} auf {volume} gesetzt.",
"change_max_volume": "",
"change_mode": "Wiedergabemodus wurde von {user} auf <i>{mode}</i> gesetzt.",
"change_volume": "Lautstärke wurde von {user} auf {volume} gesetzt.",
"cleared": "Playlist wurde geleert.",
"cleared_tags": "Alle Tags wurden von <b>{song}</b> entfernt.",
"cleared_tags_from_all": "Alle Tags wurden von allen Songs in der Playlist entfernt.",
"command_disabled": "{command}: Befehl deaktiviert!",
"current_ducking_volume": "Aktuelle Lautstärkeabsenkung: {volume}.",
"current_max_volume": "",
"current_mode": "Aktueller Wiedergabemodus: <i>{mode}</i>",
"current_volume": "Aktuelle Lautstärke: {volume}.",
"database_dropped": "Datenbank gelöscht. Alle Einträge wurde gelöscht.",
"download_in_progress": "<b>{item}</b> wird heruntergeladen ...",
"error_executing_command": "{command}: Befehl fehlgeschlagen: {error}.",
"file": "Datei",
"file_added": "{item} wurde hinzugefügt.",
"file_deleted": "{item} wurde aus der Bibliothek gelöscht.",
"file_item": "<b>{artist} - {title}</b> <i>wurde von</i> {user} <i>hinzugefügt.</i> ",
"file_missed": "Datei {file} nicht gefunden. Das Element wurde aus der Playlist entfernt.",
"help": "",
"invalid_index": "<i>{index}</i> ist ein ungültiger Index. Sende <i>!queue</i>, um die aktuelle Playlist anzuzeigen.",
"last_song_on_the_queue": "Letztes Lied in der Wiedergabeliste.",
"max_volume": "",
"multiple_file_added": "Mehrere Elemente wurden hinzugefügt:",
"multiple_file_deleted": "Mehrere Elemente wurden aus der Bibliothek gelöscht:",
"multiple_file_found": "Gefunden:",
"multiple_matches": "Datei wurde nicht gefunden! Meintest du:",
"new_version_found": "<h2>Update verfügbar!</h2> Version {new_version} von botamusique ist verfügbar! <hr />\n<h3>Changelog</h3>\n{changelog} <hr /> Sende <i>!update</i>, um das Update zu starten!",
"next_to_play": "Nächster Song.",
"no_file": "Datei nicht gefunden.",
"not_admin": "Du bist kein Administrator!",
"not_in_my_channel": "Du bist nicht in meinem Kanal!",
"not_playing": "Aktuell läuft keine Wiedergabe.",
"now_playing": "{item} wird wiedergegeben.",
"page_instruction": "Seite {current}/{total}. Nutze <i>!{command} {{page}}</i>, um zu navigieren.",
"paused": "Wiedergabe pausiert.",
"playlist_fetching_failed": "Playlist konnte nicht geladen werden!",
"pm_not_allowed": "Private Nachrichten sind nicht erlaubt.",
"position_in_the_queue": "Aktuelle Position der Wiedergabeliste: {position}",
"preconfigurated_radio": "Folgende Radiosender wurden vorkonfiguriert und sind verfügbar:",
"queue_contents": "Elemente in der Wiedergabeliste:",
"queue_empty": "Wiedergabeliste ist leer!",
"radio": "Radiosender",
"radio_item": "",
"rb_play_empty": "",
"rb_query_result": "",
"records_omitted": "",
"removed_tags": "",
"removed_tags_from_all": "",
"removing_item": "",
"repeat": "",
"report_version": "",
"shortlist_instruction": "Sende <i>!sl {indexes}</i>, um das gewünscht Element abzuspielen.",
"start_updating": "",
"stopped": "",
"too_long": "",
"unable_download": "",
"unable_play": "",
"unknown_mode": "",
"update_successful": "",
"url": "",
"url_ban": "",
"url_ban_list": "",
"url_ban_success": "",
"url_from_playlist": "",
"url_from_playlist_item": "",
"url_item": "",
"url_unban_success": "",
"url_unwhitelist_success": "",
"url_whitelist_list": "",
"url_whitelist_success": "",
"user_ban": "",
"user_ban_list": "",
"user_ban_success": "",
"user_password_set": "",
"user_unban_success": "",
"web_user_list": "",
"webpage_address": "",
"which_command": "",
"wrong_pattern": "",
"yt_no_more": "",
"yt_query_error": "",
"yt_result": "Ergebnis der YouTube-Suche:\n{result_table}\nSende <i>!sl {{indexes}}</i>, um das gewünscht Element abzuspielen.\n<i>!ytquery -n</i>, um die nächste Seite aufzurufen."
},
"web": {
"action": "",
"add": "",
"add_all": "",
"add_radio": "",
"add_radio_url": "",
"add_to_bottom": "",
"add_to_bottom_of_current_playlist": "",
"add_to_playlist_next": "",
"add_url": "",
"add_youtube_or_soundcloud_url": "",
"are_you_really_sure": "",
"aria_botamusique_logo": "",
"aria_default_cover": "",
"aria_empty_box": "",
"aria_remove_this_song": "",
"aria_skip_current_song": "",
"aria_skip_to_next_track": "",
"aria_spinner": "",
"aria_warning_of_deletion": "",
"autoplay": "",
"browse_music_file": "",
"cancel": "",
"cancel_upload_warning": "",
"change_playback_mode": "",
"choose_file": "",
"clear_playlist": "",
"close": "",
"delete_all": "",
"delete_all_files": "",
"delete_file_warning": "",
"directory": "",
"download_all": "",
"download_song_from_library": "",
"edit_submit": "",
"edit_tags_for": "",
"expand_playlist": "",
"file": "",
"filters": "",
"index": "#",
"keywords": "",
"keywords_placeholder": "",
"mini_player_title": "",
"music_library": "",
"next_to_play": "",
"no_tag": "",
"oneshot": "",
"open_volume_controls": "",
"page_title": "",
"pause": "",
"play": "",
"playlist_controls": "",
"radio": "",
"radio_url_placeholder": "",
"random": "",
"remove_song_from_library": "",
"repeat": "",
"rescan_files": "",
"skip_track": "",
"submit": "",
"tags": "",
"tags_to_add": "",
"title": "",
"token": "",
"token_required": "",
"token_required_message": "",
"type": "",
"upload_file": "",
"upload_submit": "",
"upload_to": "",
"uploaded_finished": "",
"uploading_files": "",
"url": "",
"url_path": "",
"url_placeholder": "",
"volume_slider": ""
}
}

View File

@ -1,173 +0,0 @@
{
"cli": {
"added_tags": "Etiquetas <i>{tags}</i> fueron añadidas a <b>{song}</b>.",
"added_tags_to_all": "Etiquetas <i>{tags}</i> fueron añadidas a las canciones en la lista de reproducción.",
"admin_help": "<h3>Comandos de administrador</h3>\n<b>Bot</b>\n<ul>\n<li><b>!<u>k</u>ill </b> - matar al bot</li>\n<li><b>!update </b> - actualizar al bot</li>\n<li><b>!userban </b> {user} - banear a un usuario</li>\n<li><b>!userunban </b> {user} - desbanear a un usuario</li>\n<li><b>!urlbanlist </b> - listar url baneadas</li>\n<li><b>!urlban </b> [{url}] - banear {url} (o por defecto, la url del ítem actual) y eliminar esta url de la biblioteca.</li>\n<li><b>!urlunban </b> {url} - desbanear {url}</li>\n<li><b>!rescan </b> {url} - reconstruir caché local de ficheros de música</li>\n<li><b>!dropdatabase</b> - borrar toda la base de datos. Esto eliminará toda su configuración y su biblioteca musical.</li>\n</ul>\n<b>Interfaz Web</b>\n<ul>\n<li><b>!<u>webuserlist</u></b> - lista todos los usuarios que tienen permiso de acceder a la interfaz web, si el modo de autenticación es 'contraseña'.</li>\n<li><b>!<u>webuseradd</u> {nickname}</b> - otorga al usuario con {nickname} acceso a la interfaz web, si el modo de autenticación es 'contraseña'.</li>\n<li><b>!<u>webuserdel</u> {nickname}</b> - revoca el acceso a la interfaz web para {nickname}, si el modo de autenticación es 'contraseña'.</li>\n</ul>",
"auto_paused": "Usa <i>!play</i> para continuar la reproducción!",
"bad_command": "<i>{command}</i>: comando no encontrado.",
"bad_parameter": "<i>{command}</i>: parámetro inválido.",
"bad_url": "Se solicitó una URL mal formada. ",
"cache_refreshed": "Caché fue actualizada!",
"change_ducking_volume": "Volumen en agache ajustado a {volume} por {user}.",
"change_max_volume": "",
"change_mode": "Modo de reproducción ajustado a <i>{mode}</i> por {user}.",
"change_volume": "Volumen ajustado a {volume} por {user}.",
"cleared": "Lista de reproducción ha sido vaciada.",
"cleared_tags": "Eliminadas todas las etiquetas de <b>{song}</b>.",
"cleared_tags_from_all": "Eliminadas todas las etiquetas de las canciones en la lista de reproducción.",
"command_disabled": "{command}: comando desactivado!",
"current_ducking_volume": "Volumen en agache: {volume}.",
"current_max_volume": "",
"current_mode": "Modo actual de reproducción es <i>{mode}</i>.",
"current_volume": "Volumen actual: {volume}.",
"database_dropped": "Base de datos descartada. Todos los registros se han ido.",
"download_in_progress": "Descarga de <b>{item}</b> en progreso...",
"error_executing_command": "{command}: Comando falló, con el siguiente error: {error}.",
"file": "Fichero",
"file_added": "Añadido {item}.",
"file_deleted": "{item} fue eliminado de la biblioteca.",
"file_item": "<b>{artist} - {title}</b> <i>añadido por</i> {user}",
"file_missed": "Fichero de música '{file}' no encontrado! Este ítem ha sido eliminado de la lista de reproducción.",
"help": "<h3>Comandos</h3>\n<b>Control</b>\n<ul>\n<li> <b>!<u>w</u>eb</b> - obtener la URL de la interfaz web, en caso de estar activada. </li>\n<li> <b>!play </b> (or <b>!p</b>) [{n}] [{empezar_desde}] - continuar desde pausa / empezar a reproducir (desde la n-ésima canción, si n es introducido) </li>\n<li> <b>!<u>pa</u>use </b> - pausar </li>\n<li> <b>!<u>st</u>op </b> - parar la reproducción </li>\n<li> <b>!<u>sk</u>ip </b> - saltar a la siguiente canción </li>\n<li> <b>!<u>la</u>st </b> - saltar a la última canción </li>\n<li> <b>!<u>v</u>olume </b> {volumen} - obtener o cambiar el volumen (de 0 a 100) </li>\n<li> <b>!<u>m</u>ode </b> [{modo}] - obtener o ajustar el modo de reproducción. {modo} debiera ser o bien <i>one-shot</i> (eliminar el ítem de la lista una vez reproducido), <i>repeat</i> (repetir la lista de reproducción una vez terminada), <i>random</i> (aleatorizar la reproducción), o <i>autoplay</i> (reproducir una muestra aleatoria de canciones de la biblioteca musical).</li>\n<li> <b>!duck </b> on/off - activar o desactivar funcionalidad de agache </li>\n<li> <b>!duckv </b> - ajustar el volumen del bot para cuando se está en modo de agache </li>\n<li> <b>!<u>duckt</u>hres </b> - ajustar el nivel de volumen de habla que activa el agache (3000 por defecto) </li>\n<li> <b>!<u>o</u>ust </b> - parar la reproducción e ir al canal por defecto del bot </li>\n</ul>\n<b>Lista de Reproducción</b>\n<ul>\n<li> <b>!<u>n</u>ow </b> (o <b>!np</b>) - mostrar la canción actual </li>\n<li> <b>!<u>q</u>ueue </b> - mostrar ítems actualmente en la lista de reproducción </li>\n<li> <b>!<u>t</u>ag </b> {etiquetas} - añadir todos los ítems con etiquetas {etiquetas}. Éstas deben ir separadas por coma (\",\"). </li>\n<li> <b>!file </b>(or <b>!f</b>) {ruta/carpeta/palabra clave} - añadir un único fichero a la lista de reproducción a partir de su ruta o una palabra clave en su ruta. </li>\n<li> <b>!<u>filem</u>atch </b>(o <b>!fm</b>) {patrón} - añade todos los ficheros que calzan con la expresión regular {patrón}. </li>\n<li> <b>!<u>ur</u>l </b> {url} - añade música de Youtube o de SoundCloud </li>\n<li> <b>!<u>playl</u>ist </b> {url} [{offset}] - añade todos los ítems en una lista de reproducción de Youtube o de Soundcloud, y empieza desde el primer ítem después del {offset} entregado </li>\n<li> <b>!<u>rad</u>io </b> {url} - agrega una radio {url} a la lista de reproducción </li>\n<li> <b>!<u>rbq</u>uery </b> {palabra clave} - envía una query a http://www.radio-browser.info para una estación de radio </li>\n<li> <b>!<u>rbp</u>lay </b> {id} - reproduce una estación de radio con {id} (por ejemplo, !rbplay 96746) </li>\n<li> <b>!<u>ys</u>earch </b> {palabras clave} - busca en youtube. Use <i>!ysearch -n</i> para avanzar la página. </li>\n<li> <b>!<u>yp</u>lay </b> {palabras clave} - añade el primer resultado de la búsqueda de {palabras clave} en Youtube a la lista de reproducción.</li>\n<li> <b>!<u>sh</u>ortlist </b> (o <b>!sl</b>) {n/*} - añade el {n}-ésimo elemento (o todos los elementos si se entrega *) en la lista corta. </li>\n<li> <b>!rm </b> {n} - elimina la n-ésima canción en la lista de reproducción </li>\n<li> <b>!<u>rep</u>eat </b> [{n}] - repite la canción actual {n} veces (1 por defecto).</li>\n<li> <b>!<u>ran</u>dom </b> - baraja la lista de reproducción.</li>\n</ul>\n<b>Biblioteca Musical</b>\n<ul>\n<li> <b>!<u>se</u>arch </b> {palabras clave} - encuentra elemento con {palabras clave} en la biblioteca musical. Palabras clave separadas por espacios</li>\n<li> <b>!<u>li</u>stfile </b> [{patrón}] - muestra la lista de ficheros disponibles (cuyas rutas calzan con la expresión regular {patrón}, si éste es entregado) </li>\n<li> <b>!<u>addt</u>ag </b> [{n}] {etiquetas} - añade {etiquetas} a la {n}-ésima canción (canción actual si {n} es omitida) en la lista de reproducción. Etiquetas separadas por comas (\",\"). </li>\n<li> <b>!<u>addt</u>ag </b> * {etiquetas} - añade {etiquetas} a todos los elementos en la lista de reproducción. </li>\n<li> <b>!<u>un</u>tag </b> [{n/*}] {etiquetas}/* - elimina {etiquetas}/todas las etiquetas de la {n}-ésima canción (canción actual si {n} es omitida) en la lista de reproducción. </li>\n<li> <b>!<u>fin</u>dtagged </b> (o <b>!ft</b>) {etiquetas} - encuentra elemento con {etiquetas} en la biblioteca musical. </li>\n<li> <b>!<u>del</u>ete </b> {n} - elimina {n}-ésimo elemento en la lista corta, de la biblioteca musical. </li>\n</ul>\n<b>Otros</b>\n<ul>\n<li> <b>!<u>j</u>oinme {token} </b> - unirse a tu propio canal con {token}.</li>\n<li> <b>!<u>password</u> {contraseña} </b> - cambia la contraseña que usa para acceder a la interfaz web.</li>\n</ul>",
"invalid_index": "Índice <i>{index}</i> inválido. Use '!queue' para ver la lista de reproducción.",
"last_song_on_the_queue": "Última en la cola.",
"max_volume": "",
"multiple_file_added": "Múltiples elementos añadidos:",
"multiple_file_deleted": "Múltiples elementos fueron eliminados de la biblioteca:",
"multiple_file_found": "Encontrado:",
"multiple_matches": "Fichero no encontrado! Posibles candidatos:",
"new_version_found": "<h2>Actualización disponible!</h2> La versión {new_version} de botamusique está disponible! <hr />\n<h3>Lista de cambios:</h3> {changelog} <hr /> Envía <i>!update</i> para actualizar este bot!",
"next_to_play": "Siguiente canción.",
"no_file": "Fichero no encontrado.",
"not_admin": "Usted no es un administrador!",
"not_in_my_channel": "Tú no estás en mi canal!",
"not_playing": "Nada se está reproduciendo ahora mismo.",
"now_playing": "Reproduciendo {item}",
"page_instruction": "Página {current}/{total}. Use <i>!{command} {{page}}</i> para navegar.",
"paused": "Música pausada.",
"playlist_fetching_failed": "No fue posible obtener la lista de reproducción!",
"pm_not_allowed": "Mensajes privados no están permitidos.",
"position_in_the_queue": "Posición: {position}",
"preconfigurated_radio": "Radio pre-configurada disponible:",
"queue_contents": "Elementos en la lista de reproducción:",
"queue_empty": "Lista de reproducción está vacía!",
"radio": "Radio",
"radio_item": "<a href=\"{url}\"><b>{title}</b></a> <i>de</i> {name} <i>añadido por</i> {user}",
"rb_play_empty": "Por favor especifique el ID de una estación de radio!",
"rb_query_result": "Este es el resultado de su consulta, envíe <i> !rbplay {ID} </i> para reproducir una estación:",
"records_omitted": "...",
"removed_tags": "Eliminadas las etiquetas <i>{tags}</i> de <b>{song}</b>.",
"removed_tags_from_all": "Eliminadas las etiquetas <i>{tags}</i> de las canciones en la lista de reproducción.",
"removing_item": "Eliminado {item} de la lista de reproducción.",
"repeat": "Repetir {song} {n} veces.",
"report_version": "La versión actual de botamusique es <b>{version}</b>.",
"shortlist_instruction": "Use <i>!sl {índices}</i> para reproducir los elementos que usted desea.",
"start_updating": "Empezando la actualización...",
"stopped": "Música fue detenida.",
"too_long": "<b>{song}</b> es muy larga ({duration} > {max_duration}). Eliminada de la lista de reproducción!",
"unable_download": "No fue posible descargar <b>{item}</b>. Eliminado de la biblioteca.",
"unable_play": "No fue posible reproducir <b>{item}</b>. Eliminado de la biblioteca.",
"unknown_mode": "Modo de reproducción '{mode}' desconocido. Debiera ser o bien <i>one-shot</i>, <i>repeat</i> o <i>random</i>.",
"update_successful": "<h2>botamusique v{version} instalado!</h2><hr />\n<h3>Lista de cambios</h3> {changelog} <hr /> Visite <a href=\"https://github.com/azlux/botamusique\">nuestro repositorio en Github</a> para más detalles!",
"url": "URL",
"url_ban": "URL {url} está baneada! Eliminada de la lista de reproducción!",
"url_ban_list": "",
"url_ban_success": "",
"url_from_playlist": "URL",
"url_from_playlist_item": "<a href=\"{url}\"><b>{title}</b></a> <i>de lista de reproducción</i> <a href=\"{playlist_url}\">{playlist}</a> <i>añadido por</i> {user}",
"url_item": "<a href=\"{url}\"><b>{title}</b></a> <i>añadido por</i> {user}",
"url_unban_success": "",
"url_unwhitelist_success": "",
"url_whitelist_list": "",
"url_whitelist_success": "",
"user_ban": "Tú estás baneado. No tienes permitido hacer eso!",
"user_ban_list": "",
"user_ban_success": "",
"user_password_set": "Su contraseña ha sido actualizada.",
"user_unban_success": "",
"web_user_list": "Los siguientes usuarios tienen el privilegio de acceder a la interfaz web: <br /> {users}",
"webpage_address": "Tu dirección web para acceder a la interfaz es <a href=\"{address}\">{address}</a>",
"which_command": "Quieres decir <br /> {commands}",
"wrong_pattern": "Expresión regular inválida: {error}",
"yt_no_more": "No hay más resultados!",
"yt_query_error": "Fue imposible consultar a youtube!",
"yt_result": "Resultado de la consulta a youtube: {result_table} Use <i>!sl {{índices}}</i> para reproducir el elemento que usted desea. <br />\n<i>!ytquery -n</i> para la siguiente página."
},
"web": {
"action": "Acción",
"add": "Añadir",
"add_all": "Añadir todas",
"add_radio": "Añadir Radio",
"add_radio_url": "Añadir URL de radio",
"add_to_bottom": "Añadir al final",
"add_to_bottom_of_current_playlist": "Añadir al final de la lista de reproducción actual",
"add_to_playlist_next": "Añadir a la lista de reproducción justo después de la canción actual",
"add_url": "Añadir URL",
"add_youtube_or_soundcloud_url": "Añadir URL de Youtube o de Soundcloud",
"are_you_really_sure": "¿Está usted realmente seguro?",
"aria_botamusique_logo": "El logo de Botamusique: un zorro con dos audífonos, disfrutando de la música",
"aria_default_cover": "Un cuadrado negro, con dos corcheas unidas entre sí.",
"aria_empty_box": "El dibujo de una caja vacía.",
"aria_remove_this_song": "Sacar esta canción de la lista de reproducción actual",
"aria_skip_current_song": "Saltar la canción actual y reproducir esta canción ahora mismo",
"aria_skip_to_next_track": "Saltar a la siguiente canción",
"aria_spinner": "Una curva siguiendo la forma de un círculo, para indicar que el elemento está cargándose todavía.",
"aria_warning_of_deletion": "Advertencia acerca de la eliminación de ficheros.",
"autoplay": "Reproducción automática",
"browse_music_file": "Explorar fichero de música",
"cancel": "Cancelar",
"cancel_upload_warning": "<strong>¿Está realmente seguro?</strong> <br /> Haga click de nuevo para abortar la subida.",
"change_playback_mode": "Cambiar Modo de Reproducción.",
"choose_file": "Elija un fichero",
"clear_playlist": "Vaciar la lista de reproducción",
"close": "Cerrar",
"delete_all": "Borrar todo",
"delete_all_files": "Eliminar todos los ficheros listados",
"delete_file_warning": "Todos los archivos listados aquí, incluyendo ficheros en otras páginas, serán eliminados de su disco duro.\n ¿Es eso lo que usted desea?",
"directory": "Directorio",
"download_all": "Descargar todo",
"download_song_from_library": "Descargar canción desde la biblioteca",
"edit_submit": "Editar!",
"edit_tags_for": "Editar etiquetas para",
"expand_playlist": "Ver elemento <span\n class=\"playlist-expand-item-range\"></span> en la lista de reproducción.",
"file": "Fichero",
"filters": "Filtros",
"index": "#",
"keywords": "Palabras clave",
"keywords_placeholder": "Palabras clave...",
"mini_player_title": "Ahora reproduciendo...",
"music_library": "Biblioteca musical",
"next_to_play": "Siguiente canción a reproducir",
"no_tag": "Sin etiquetas",
"oneshot": "One-shot",
"open_volume_controls": "Abrir controles de volumen",
"page_title": "Interfaz web de botamusique",
"pause": "Pausar",
"play": "Reanudar",
"playlist_controls": "Controles de la lista de reproducción",
"radio": "Radio",
"radio_url_placeholder": "URL de radio...",
"random": "Aleatorio",
"remove_song_from_library": "Eliminar canción de la biblioteca",
"repeat": "Repetir",
"rescan_files": "Volver a escanear ficheros",
"skip_track": "Saltar canción",
"submit": "Enviar",
"tags": "Etiquetas",
"tags_to_add": "Etiquetas a añadir",
"title": "Título",
"token": "Token",
"token_required": "Se requiere una token",
"token_required_message": "Tú estás accediendo a la interfaz web de {{ name }}.\nUna token es necesaria para otorgarte acceso.<br />\nPor favor, envíe \"{{ command }}\" al bot en mumble para obtener una.",
"type": "Tipo",
"upload_file": "Subir Fichero",
"upload_submit": "Subir!",
"upload_to": "Subir a",
"uploaded_finished": "Subida terminada!",
"uploading_files": "Subiendo ficheros...",
"url": "URL",
"url_path": "Url/Ruta",
"url_placeholder": "URL...",
"volume_slider": "Control deslizante de volumen"
}
}

View File

@ -1,173 +0,0 @@
{
"cli": {
"added_tags": "Tags <i>{tags}</i> ajoutés à <b>{song}</b>.",
"added_tags_to_all": "Tags <i>{tags}</i> ajoutés aux musiques de la playlist.",
"admin_help": "<h3>Commandes Admin</h3>\n<b>Bot</b>\n<ul>\n<li><b>!<u>k</u>ill </b> - tuer le bot</li>\n<li><b>!update </b> - update the bot</li>\n<li><b>!userban </b> {user} - bannir un utilisateur</li>\n<li><b>!userunban </b> {user} - unban a user</li>\n<li><b>!urlbanlist </b> - liste url interdite</li>\n<li><b>!urlban </b> [{url}] - interdire {url} (ou l'url de l'élément courant par défaut) et supprimer cette url de la bibliothèque.</li>\n<li><b>!urlunban </b> {url} - unban {url}</li>\n<li><b>!rescan </b> {url} - reconstruction du cache des fichiers musicaux locaux</li>\n<li><b>!dropdatabase</b> - effacez toute la base de données, vous perdrez tous les paramètres et la bibliothèque musicale.</li>\n</ul>\n<b>Interface Web</b>\n<ul>\n<li><b>!<u>webuserlist</u></b> - liste de tous les utilisateurs qui ont la permission d'accéder à l'interface web, si le mode d'authentification est 'password'.</li>\n<li><b>!<u>webuseradd</u> {nick name}</b> - accorder à l'utilisateur avec {nick name} l'accès à l'interface web, si le mode d'authentification est 'password'.</li>\n<li><b>!<u>webuserdel</u> {nick name}</b> - révoquer l'accès à l'interface web de {nick name}, si le mode d'authentification est 'password'.</li>\n</ul>",
"auto_paused": "<i>!play</i> pour reprendre la lecture!",
"bad_command": "{{command}}: commande non trouvé.",
"bad_parameter": "{command}: commande invalide.",
"bad_url": "Mauvaise URL demandé",
"cache_refreshed": "Cache actualisé!",
"change_ducking_volume": "Volume sur le ducking réglé sur {volume} par {user}.",
"change_max_volume": "Volume max configuré à {max} par {user}",
"change_mode": "Mode de lecture réglé sur <i>{mode}</i> par {user}.",
"change_volume": "Volume réglé sur {volume} par {user}.",
"cleared": "Playlist vidée.",
"cleared_tags": "Suppression de tous les tag de <b>{song}</b>.",
"cleared_tags_from_all": "Suppression de tous les tags des chansons de la playlist.",
"command_disabled": "{command} : commande désactivée !",
"current_ducking_volume": "Volume de ducking: {volume}.",
"current_max_volume": "Volume max actuel : {max}",
"current_mode": "Le mode de lecture actuel est <i>{mode}</i>.",
"current_volume": "Volume actuel : {volume}.",
"database_dropped": "La base de données a été supprimée. Tous les enregistrements ont disparu.",
"download_in_progress": "Téléchargement de <b>{item}</b> en cours...",
"error_executing_command": "{command} : La commande a échoué avec l'erreur : {error}.",
"file": "Fichier",
"file_added": "{item} ajouté.",
"file_deleted": "{item} supprimé de la bibliothèque.",
"file_item": "<b>{artist} - {title}</b> <i>ajouté par</i> {user}",
"file_missed": "Fichier audio '{file}' introuvable! Cet élément a été supprimé de la playlist.",
"help": "<h3>Commandes</h3>\n<b>Control</b>\n<ul>\n<li> <b>!<u>w</u>eb</b> - obtenir l'URL de l'interface web, si elle est activée. </li>\n<li> <b>!play </b> (ou <b>!p</b>) [{num}] [{start_from}] - reprise de la pause / début de la lecture (à partir de la n° X s'il est donné) </li>\n<li> <b>!<u>pa</u>use</b> - pause </li>\n<li> <b>!<u>st</u>op</b> - arrêtez de jouer </li>\n<li> <b>!<u>sk</u>ip</b> - passer à la chanson suivante </li>\n<li> <b>!<u>la</u>st</b> - passer à la dernière chanson </li>\n<li> <b>!<u>v</u>olume</b> {volume} - obtenir ou modifier le volume (de 0 à 100) </li>\n<li> <b>!<u>m</u>ode</b> [{mode}] - obtenir ou définir le mode de lecture, {mode} doit être l'un de <i>one-shot</i> (supprimer l'élément une fois joué), <i>repeat</i> (boucle de la liste de lecture), <i>ramdom</i> (liste de lecture aléatoire),\n<i>autoplay</i> (prendre au hasard dans la bibliothèque musicale).</li>\n<li> <b>!duck</b> on/off - activer ou désactiver la fonction d'esquive </li>\n<li> <b>!duckv</b> {volume} - définit le volume du bot lorsque le ducking est activé </li>\n<li> <b>!<u>duckt</u>hres</b> - définir le seuil de volume pour activer le ducking (3000 par défaut) </li>\n<li> <b>!<u>o</u>ust</b> - arrêtez de jouer et passez sur le canal par défaut </li>\n</ul>\n<b>Playist</b>\n<ul>\n<li> <b>!<u>n</u>ow </b> (ou <b>!np</b>) - afficher la chanson actuelle </li>\n<li> <b>!<u>q</u>ueue </b> - afficher les éléments de la playlist </li>\n<li> <b>!<u>t</u>ag </b> {balises} - ajouter tous les éléments avec les tags {tags}, les balises séparées par \",\". </li>\n<li> <b>!file</b> (ou <b>!f</b>) {chemin/dossier/mot-clé} - ajoute un seul fichier à la playlist par son chemin ou un mot-clé. </li>\n<li> <b>!<u>filem</u>atch </b>(ou <b>!fm</b>) {pattern} - ajouter tous les fichiers qui correspondent à la regex {pattern} </li>\n<li> <b>!<u>ur</u>l </b> {url} - ajouter de la musique Youtube ou SoundCloud </li>\n<li> <b>!<u>playl</u>ist </b> {url} [{offset}] - ajouter tous les éléments d'une liste de lecture Youtube ou SoundCloud, et commencer par le {offset}-ième élément </li>\n<li> <b>!<u>rad</u>io </b> {url} - ajouter une radio {url} à la playlist </li>\n<li> <b>!<u>rbq</u>uery </b> {keyword} - interroger http://www.radio-browser.info pour une station de radio </li>\n<li> <b>!<u>rbp</u>lay </b> {id} - jouer une station de radio avec {id} (ex. !rbplay 96746) </li>\n<li> <b>!<u>ys</u>earch </b> {keywords} - requête youtube. Utilisez <i>!ysearch -n</i> pour aller à la page d'après. </li>\n<li> <b>!<u>yp</u>lay </b> {keywords} - ajouter le premier résultat de recherche de {keyword} dans la playlist.</li>\n<li> <b>!<u>sh</u>ortlist </b> (ou <b>!sl</b>) {index/*} - ajouter {index}-ième élément (ou tous les éléments si * est donné) de la liste. </li>\n<li> <b>!rm </b> {num} - supprimer le num-ième morceau de la playlist </li>\n<li> <b>!<u>rep</u>eat </b> [{num}] - répéter la chanson actuelle {num} (1 par défaut) times.</li>\n<li> <b>!<u>ran</u>dom </b> - randomiser la playlist.</li>\n</ul>\n<b>Bibliothèque musicale</b>\n<ul>\n<li> <b>!<u>se</u>arch </b> {keywords} - trouver un élément avec {mots-clés} dans la bibliothèque musicale, mots-clés séparés par un espace.</li>\n<li> <b>!<u>li</u>stfile </b> [{pattern}] - affiche la liste des fichiers disponibles (dont les chemins correspondent au motif de regex si {pattern} est donné) </li>\n<li> <b>!<u>addt</u>ag </b> [{index}] {tags} - ajouter {tags} à {index} (current song if {index} n'existe pas) de la playliste, tags séparer par \",\". </li>\n<li> <b>!<u>addt</u>ag </b> * {tags} - ajouter des {tags} à tous les éléments de la playlist. </li>\n<li> <b>!<u>un</u>tag </b> [{index/*}] {tags}/* - supprimer {tags}/toutes les tags de {index}-th(current song if {index} is oitted) item on the playlist. </li>\n<li> <b>!<u>fin</u>dtagged </b> (ou <b>!ft</b>) {tags} - trouver un élément avec des {balises} dans la bibliothèque. </li>\n<li> <b>!<u>del</u>ete </b> {index} - supprimer le {index}-ième élément de la liste de la bibliothèque. </li>\n</ul>\n<b>Autre</b>\n<ul>\n<li> <b>!<u>j</u>oinme {token} </b> - rejoins votre propre channel mumble avec {token}.</li>\n<li> <b>!<u>password</u> {password} </b> - changer votre mot de passe, utilisé pour accéder à l'interface web.</li>\n</ul>",
"invalid_index": "Index non valide <i>{index}</i>. Utilisez '!queue' pour voir la playlist.",
"last_song_on_the_queue": "Dernier de la file d'attente.",
"max_volume": "Le volume dépasse le maximum {max}. Réglage du volume sur le max.",
"multiple_file_added": "Ajout de plusieurs éléments :",
"multiple_file_deleted": "Plusieurs éléments ont été supprimés de la bibliothèque :",
"multiple_file_found": "Trouvé :",
"multiple_matches": "Fichier non trouvé ! Candidats possibles :",
"new_version_found": "<h2>Mise à jour disponible!</h2> La version {new_version} de botamusique est disponible ! <hr />\n<h3>Changelog</h3> {changelog} <hr /> Envoyer <i>!update</i> pour mettre à jour !",
"next_to_play": "Chanson suivante.",
"no_file": "Fichier non trouvé.",
"not_admin": "Vous n'êtes pas un admin !",
"not_in_my_channel": "Vous n'êtes pas dans mon canal, commande refusé !",
"not_playing": "Rien n'est joué en ce moment.",
"now_playing": "En cours de lecture {item}",
"page_instruction": "Page {current}/{total}. Utilisez <i>!{command} {{page}}</i> pour naviguer.",
"paused": "Music en pause.",
"playlist_fetching_failed": "Impossible d'obtenir la playlist !",
"pm_not_allowed": "Les messages privés ne sont pas autorisés.",
"position_in_the_queue": "Position: {position}",
"preconfigurated_radio": "Radio préconfigurées disponible :",
"queue_contents": "Éléments de la playlist :",
"queue_empty": "La playlist est vide !",
"radio": "Radio",
"radio_item": "<a href=\"{url}\"><b>{title}</b></a> <i>from</i> {name} <i>ajouté par</i> {user}",
"rb_play_empty": "Veuillez préciser l'ID de la station de radio !",
"rb_query_result": "Résultat de votre requête, envoyez !rbplay 'ID' pour jouer une station :",
"records_omitted": "...",
"removed_tags": "Suppression des tags <i>{tags}</i> de <b>{song}</b>.",
"removed_tags_from_all": "Suppression des tags <i>{tags}</i> des chansons de la playlist.",
"removing_item": "Entrée {item} suprimée de la playlist.",
"repeat": "Répète {song} {n} fois.",
"report_version": "La version actuelle de botamusique est <b>{version}{/b}.",
"shortlist_instruction": "Utilisez <i>!sl {indexes}</i> pour jouer l'élément que vous voulez.",
"start_updating": "Début de la mise à jour...",
"stopped": "Musique arrêté.",
"too_long": "<b>{song}</b> est trop long ({duration} > {max_duration}), supprimé de la playlist !",
"unable_download": "Impossible de télécharger <b>{item}</b>. Retiré de la bibliothèque.",
"unable_play": "Impossible de jouer <b>{item}</b>. Retiré de la bibliothèque.",
"unknown_mode": "Mode de lecture \"{mode}\" inconnu. Il devrait s'agir d'un des modes suivants : <i>one-shot</i>, <i>repeat</i>, <i>random</i>.",
"update_successful": "<h2>botamusique v{version} Installé ! </h2><hr />\n<h3>Changelog</h3> {changelog} <hr /> Visitez <a href=\"https://github.com/azlux/botamusique\">notre repo github</a> pour plus de détails !",
"url": "URL",
"url_ban": "URL {url} est interdite !",
"url_ban_list": "Liste des URL bannies:<br>{list=",
"url_ban_success": "L'URL suivante est interdite: {url}",
"url_from_playlist": "URL",
"url_from_playlist_item": "<a href=\"{url}\"><b>{title}</b></a> <i>depuis la playlist</i> <a href=\"{playlist_url}\">{playlist}</a> <i>ajouté par</i> {user}",
"url_item": "<a href=\"{url}\"><b>{title}</b></a> <i>ajouté par</i> {user}",
"url_unban_success": "L'URL suivante est débloquée : {url}.",
"url_unwhitelist_success": "L'URL suivante n'est pas sur liste blanche : {url}.",
"url_whitelist_list": "Liste des URL sur liste blanche: <br>{list}",
"url_whitelist_success": "L'URL suivante est sur la liste blanche : {url}.",
"user_ban": "Vous êtes banni, vous n'avez donc pas le droit de faire cela !",
"user_ban_list": "Liste des utilisateurs bannis:<br>{list}",
"user_ban_success": "L'utilisateur {user} est banni.",
"user_password_set": "Votre mot de passe a été mis à jour.",
"user_unban_success": "L'utilisateur {user} n'est plus banni.",
"web_user_list": "Les utilisateurs suivants ont l'autorisation d'accéder à l'interface web : <br /> {users}",
"webpage_address": "Votre propre adresse pour accéder à l'interface web est <a href=\"{address}\">{address}</a>",
"which_command": "Voulez-vous dire <br /> {commands}",
"wrong_pattern": "regex invalide: {error}.",
"yt_no_more": "Plus de résultats !",
"yt_query_error": "Impossible d'interroger youtube !",
"yt_result": "Résultat de la requête Youtube : {result_table} Utilisez <i>!sl {{indexes}}</i> pour jouer l'entrée que vous voulez. <br />\n<i>!ytquery -n</i> pour la page suivante."
},
"web": {
"action": "Action",
"add": "Ajouter",
"add_all": "Ajouter tout",
"add_radio": "Ajouter une Radio",
"add_radio_url": "Ajouter l'URL d'une Radio",
"add_to_bottom": "Ajouter à la fin",
"add_to_bottom_of_current_playlist": "Ajouter à la fin de la playlist actuelle",
"add_to_playlist_next": "Ajouter à la playlist juste après la chanson en cours",
"add_url": "Ajouter l'URL",
"add_youtube_or_soundcloud_url": "Ajouter une URL Youtube ou Soundcloud",
"are_you_really_sure": "En êtes-vous vraiment sûr ?",
"aria_botamusique_logo": "Logo Botamusique : un renard avec deux écouteurs, appréciant la musique",
"aria_default_cover": "Un carré noir avec deux croches qui se rejoignent.",
"aria_empty_box": "Un dessin d'une boîte vide.",
"aria_remove_this_song": "Supprimer cette chanson de la playlist actuelle",
"aria_skip_current_song": "Passer la chanson actuelle et jouer cette chanson maintenant",
"aria_skip_to_next_track": "Passer à la piste suivante",
"aria_spinner": "Une roue de chargement",
"aria_warning_of_deletion": "Avertissement concernant la suppression de fichiers.",
"autoplay": "Autoplay",
"browse_music_file": "Parcourir le dossier de musique",
"cancel": "Annuler",
"cancel_upload_warning": "<strong>Etes-vous vraiment sûr ?</strong> <br />Cliquez à nouveau pour interrompre le téléchargement.",
"change_playback_mode": "Changer de mode de lecture",
"choose_file": "Choisissez un fichier",
"clear_playlist": "Vider la playlist",
"close": "Fermer",
"delete_all": "Supprimer tous",
"delete_all_files": "Supprimer tous les fichiers répertoriés",
"delete_file_warning": "Tous les fichiers énumérés ici, y compris les fichiers des autres pages, seront supprimés de votre disque dur.\n C'est ce que vous voulez ?",
"directory": "Répertoire",
"download_all": "Télécharger tout",
"download_song_from_library": "Télécharger une chanson de la bibliothèque",
"edit_submit": "Editer !",
"edit_tags_for": "Modifier les tags pour",
"expand_playlist": "Voir le point <span\n class=\"playlist-expand-item-range\"></span> sur la playlist.",
"file": "Dossier",
"filters": "Filtres",
"index": "#",
"keywords": "Mots-clés",
"keywords_placeholder": "Mots-clés...",
"mini_player_title": "En train de jouer...",
"music_library": "Bibliothèque musicale",
"next_to_play": "Suivant à jouer",
"no_tag": "Pas de tag",
"oneshot": "One-shot",
"open_volume_controls": "Ouvrir le contrôle de volume",
"page_title": "Interface Web botamusique",
"pause": "Pause",
"play": "Jouer",
"playlist_controls": "Contrôle des playlists",
"radio": "Radio",
"radio_url_placeholder": "URL de la radio...",
"random": "Aléatoire",
"remove_song_from_library": "Retirer une chanson de la bibliothèque",
"repeat": "Répéter",
"rescan_files": "Re-scanner les fichiers",
"skip_track": "Passer la piste",
"submit": "Envoyer",
"tags": "Tags",
"tags_to_add": "Tags à ajouter",
"title": "Titre",
"token": "Token",
"token_required": "Token requis",
"token_required_message": "Vous accédez à l'interface web de {{ name }}.\nUn jeton est nécessaire pour vous permettre d'y accéder.<br />\nVeuillez envoyer \"{{ command }}\" au bot sur mumble pour en acquérir un.",
"type": "Type",
"upload_file": "Télécharger un fichier",
"upload_submit": "Téléchargez !",
"upload_to": "Télécharger vers",
"uploaded_finished": "Téléchargement terminé !",
"uploading_files": "Téléchargement de fichiers...",
"url": "URL",
"url_path": "Url/Path",
"url_placeholder": "URL...",
"volume_slider": "Curseur de volume"
}
}

View File

@ -1,173 +0,0 @@
{
"cli": {
"added_tags": "Tag <i>{tags}</i> aggiunti a <b>{song}</b>.",
"added_tags_to_all": "I tag <i>{tags}</i> sono stati aggiunti ai brani nella playlist.",
"admin_help": "<h3>Comandi amministratore</h3>\n<b>Bot</b>\n<ul>\n<li><b>!<u>k</u>ill </b> - Termina il bot.</li>\n<li><b>!update </b> - Aggiorna il bot.</li>\n<li><b>!userban </b> {user} - Banna utente.</li>\n<li><b>!userunban </b> {user} - Sbanna utente.</li>\n<li><b>!urlbanlist </b> - Elenco URL vietati.</li>\n<li><b>!urlban </b> [{url}] - Banna {url} (o URL dell'elemento corrente come impostazione predefinita) e rimuovi questo URL dalla libreria.</li>\n<li><b>!urlunban </b> {url} - Sbanna {url}.</li>\n<li><b>!rescan </b> {url} - Ricostruisce la cache dei file musicali locali.</li>\n<li><b>!dropdatabase</b> - Cancella l'intero database, perderai tutte le impostazioni e la libreria musicale.</li>\n</ul>\n<b>Interfaccia Web</b>\n<ul>\n<li><b>!<u>webuserlist</u></b> - Elenca tutti gli utenti che hanno il permesso di accedere all'interfaccia web, se la modalità di autenticazione è 'password'.</li>\n<li><b>!<u>webuseradd</u> {nick name}</b> - Concedi all'utente con {nick name} l'accesso all'interfaccia web, se la modalità di autenticazione è 'password'.</li>\n<li><b>!<u>webuserdel</u> {nick name}</b> - Revoca l'accesso all'interfaccia web di {nick name}, se la modalità di autenticazione è 'password'.</li>\n</ul>\"",
"auto_paused": "Usa <i>!play</i> per riprendere la musica!",
"bad_command": "<i>{command}</i>: comando non trovato.",
"bad_parameter": "<i>{command}</i>: parametro non valido.",
"bad_url": "È stato richiesto un URL non valido.",
"cache_refreshed": "Cache aggiornata!",
"change_ducking_volume": "Volume del ducking impostato a {volume} da {user}.",
"change_max_volume": "",
"change_mode": "Modalità di riproduzione impostata su <i>{mode}</i> da {user}.",
"change_volume": "Volume impostato a {volume} da {user}.",
"cleared": "Playlist svuotata.",
"cleared_tags": "Rimossi tutti i tag da <b>{song}</b>.",
"cleared_tags_from_all": "Rimossi tutti i tag dai brani nella playlist.",
"command_disabled": "{command}: comando disabilitato!",
"current_ducking_volume": "Volume ducking attuale: {volume}.",
"current_max_volume": "",
"current_mode": "Modalità di riproduzione corrente: <i>{mode}</i>.",
"current_volume": "Volume attuale: {volume}.",
"database_dropped": "Database eliminato. Tutti i dati sono andati.",
"download_in_progress": "Scaricamento di <b>{item}</b> in corso...",
"error_executing_command": "{command}: Comando non riuscito con errore: {error}.",
"file": "File",
"file_added": "{item} aggiunto.",
"file_deleted": "{item} eliminato dalla libreria.",
"file_item": "<b>{artist} - {title}</b> <i>aggiunto da</i> {user}",
"file_missed": "File musicale \"{file}\" mancante! Questo elemento è stato rimosso dalla playlist.",
"help": "<h3>Comandi</h3>\n<b>Controllo</b>\n<ul>\n<li><b>!<u>w</u>eb</b> - ottenere l'URL dell'interfaccia web, se abilitata.</li>\n<li><b>!play</b> (or <b>!p</b>) [{num}] [{start_from}] - Riprende dalla pausa / avvia la riproduzione (dal numero {num} se fornito).</li>\n<li><b>!<u>pa</u>use</b> - Pausa.</li>\n<li><b>!<u>st</u>op</b> - Arresta riproduzione.</li>\n<li><b>!<u>sk</u>ip</b> - Passa al brano successivo.</li>\n<li><b>!<u>la</u>st</b> - Passa all'ultimo brano.</li>\n<li><b>!<u>v</u>olume</b> {volume} - Ottenere o modificare il volume (da 0 a 100).</li>\n<li><b>!<u>m</u>ode</b> [{mode}] - Ottenere o impostare la modalità di riproduzione, {mode} dovrebbe essere <i>one-shot</i> (rimuove l'elemento una volta riprodotto), <i>repeat</i> (ripete la playlist dopo il completamento), <i>random</i> (riproduzione casuale della playlist), <i>autoplay</i> (riproduce brani casuali dalla libreria musicale).</li>\n<li><b>!duck</b> on/off - Abilitare o disabilitare la funzione ducking.</li>\n<li><b>!duckv</b> {volume} - Imposta il volume del bot quando il ducking è attivato.</li>\n<li><b>!<u>duckt</u>hres </b> - Imposta la soglia del volume per attivare il ducking (3000 per impostazione predefinita).</li>\n<li><b>!<u>o</u>ust</b> - Interrompe la riproduzione e vai al canale predefinito.</li>\n</ul>\n<b>Playlist</b>\n<ul>\n<li><b>!<u>n</u>ow</b> (or <b>!np</b>) - Visualizza il brano corrente.</li>\n<li><b>!<u>q</u>ueue</b> - Visualizza gli elementi nella playlist.</li>\n<li><b>!<u>t</u>ag</b> {tags} - Aggiungi tutti gli elementi con i tag {tags}, tag separati da \",\".</li>\n<li><b>!file </b>(or <b>!f</b>) {path/folder/keyword} - Aggiungi un singolo file alla playlist tramite il percorso o la parola chiave nel percorso.</li>\n<li><b>!<u>filem</u>atch </b>(or <b>!fm</b>) {pattern} - Aggiungi tutti i file che corrispondono all'espressione regolare {pattern}.</li>\n<li><b>!<u>ur</u>l</b> {url} - Aggiungi musica da YouTube o SoundCloud.</li>\n<li><b>!<u>playl</u>ist</b> {url} [{offset}] - Aggiungi tutti gli elementi da una playlist di YouTube o SoundCloud e inizia con l'elemento {offset}.</li>\n<li><b>!<u>rad</u>io</b> {url} - Aggiungi una radio {url} alla playlist.</li>\n<li><b>!<u>rbq</u>uery</b> {keyword} - Interroga http://www.radio-browser.info per una stazione radio.</li>\n<li><b>!<u>rbp</u>lay</b> {id} - Riproduce una stazione radio con {id} (es. !rbplay 96746).</li>\n<li><b>!<u>ys</u>earch</b> {keywords} - Interroga YouTube. Usa <i>!ysearch -n</i> per andare alla pagina successiva.</li>\n<li><b>!<u>yp</u>lay</b> {keywords} - Aggiungi il primo risultato di ricerca per {keyword} alla playlist.</li>\n<li><b>!<u>sh</u>ortlist</b> (or <b>!sl</b>) {indexes/*} - Aggiungi {index}-esimo elemento (o tutti gli elementi se * è dato) alla lista.</li>\n<li><b>!rm</b> {num} - Rimuove il brano {num} dalla playlist.</li>\n<li><b>!<u>rep</u>eat</b> [{num}] - Ripete il brano corrente {num} volte (1 per impostazione predefinita).</li>\n<li><b>!<u>ran</u>dom</b> - Playlist in riproduzione casuale.</li>\n</ul>\n<b>Libreria Musicale</b>\n<ul>\n<li><b>!<u>se</u>arch</b> {keywords} - Trova l'elemento con {keywords} nella libreria musicale, parole chiave separate da spazio.</li>\n<li><b>!<u>li</u>stfile</b> [{pattern}] - Mostra l'elenco dei file disponibili (i cui percorsi corrispondono all'espressione regolare {pattern}, se fornito).</li>\n<li><b>!<u>addt</u>ag</b> [{index}] {tags} - Aggiunge {tag} a {index} (brano corrente se {index} è omesso) della playlist, tag separati da \",\".</li>\n<li><b>!<u>addt</u>ag</b> * {tags} - Aggiunge {tags} a tutti gli elementi sulla playlist.</li>\n<li><b>!<u>un</u>tag</b> [{index/*}] {tags}/* - Rimuove {tags}/tutti i tag dall'elemento {index} (brano corrente se {index} è omesso) nella playlist.</li>\n<li><b>!<u>fin</u>dtagged </b> (or <b>!ft</b>) {tags} - Trova l'elemento con {tags} nella libreria musicale.</li>\n<li><b>!<u>del</u>ete</b> {index} - Rimuove {index} elemento dall'elenco della libreria musicale.</li>\n</ul>\n<b>Altro</b>\n<ul>\n<li><b>!<u>j</u>oinme {token}</b> - Unisciti al tuo canale Mumble con {token}.</li>\n<li><b>!<u>password</u> {password}</b> - Cambia la password, utilizzata per accedere all'interfaccia web.</li>\n</ul>\",",
"invalid_index": "Indice <i>{index}</i> non valido. Usa <i>!queue</i> per vedere la playlist.",
"last_song_on_the_queue": "Ultimo in coda.",
"max_volume": "",
"multiple_file_added": "Più elementi aggiunti:",
"multiple_file_deleted": "Più elementi eliminati dalla libreria:",
"multiple_file_found": "Trovati:",
"multiple_matches": "File non trovato! Possibili candidati:",
"new_version_found": "<h2>Aggiornamento disponibile!</h2> Versione {new_version} di botamusique trovata! <hr />\\n<h3>Changelog</h3> {changelog} <hr /> Invia <i>!update</i> per aggiornare!",
"next_to_play": "Brano successivo.",
"no_file": "File non trovato.",
"not_admin": "Non sei un amministratore!",
"not_in_my_channel": "Non sei nel mio canale!",
"not_playing": "Niente in riproduzione in questo momento.",
"now_playing": "{item} in riproduzione",
"page_instruction": "Pagina {corrente}/{totale}. Usa <i>!{command} {{page}}</i> per navigare.",
"paused": "Musica in pausa.",
"playlist_fetching_failed": "Impossibile recuperare la playlist!",
"pm_not_allowed": "Messaggi privati non consentiti.",
"position_in_the_queue": "Posizione: {position}",
"preconfigurated_radio": "Radio preconfigurate disponibili:",
"queue_contents": "Elementi nella playlist:",
"queue_empty": "La playlist è vuota!",
"radio": "Radio",
"radio_item": "<a href=\"{url}\"><b>{title}</b></a> <i>di</i> {name} <i>aggiunto da</i> {user}",
"rb_play_empty": "Si prega di specificare l'ID di una stazione radio!",
"rb_query_result": "Questo è il risultato della tua ricerca, invia <i>!rbplay {ID}</i> per riprodurre una stazione:",
"records_omitted": "...",
"removed_tags": "Tag <i>{tags}</i> rimossi da <b>{song}</b>.",
"removed_tags_from_all": "Tag <i>{tags}</i> rimossi dai brani nella playlist.",
"removing_item": "Voce {item} rimossa dalla playlist.",
"repeat": "Ripeti {song} per {n} volte.",
"report_version": "La versione attuale di Botamusique è <b>{version}</b>.",
"shortlist_instruction": "Usa <i>!sl {indexes}</i> per riprodurre l'elemento desiderato.",
"start_updating": "Inizio aggiornamento...",
"stopped": "Riproduzione interrotta.",
"too_long": "<b>{song}</b> è troppo lunga ({duration} > {max_duration}), rimossa dalla playlist!",
"unable_download": "Impossibile scaricare <b>{item}</b>. Rimosso dalla libreria.",
"unable_play": "Impossibile riprodurre <b>{item}</b>. Rimosso dalla libreria.",
"unknown_mode": "Modalità di riproduzione '{mode}' sconosciuta. Dovrebbe essere <i>one-shot</i>, <i>ripeti</i>, <i>casuale</i>.",
"update_successful": "<h2>botamusique v{version} installato!</h2><hr />\n<h3>Changelog</h3> {changelog} <hr /> Visita <a href=\"https://github.com/azlux/botamusique\">la nostra repository GitHub</a> per ulteriori dettagli!",
"url": "URL",
"url_ban": "URL {url} è vietato!",
"url_ban_list": "",
"url_ban_success": "",
"url_from_playlist": "URL",
"url_from_playlist_item": "<a href=\"{url}\"><b>{title}</b></a> <i>dalla playlist</i> <a href=\"{playlist_url}\">{playlist}</a> <i>aggiunto da</i> {user}",
"url_item": "<a href=\"{url}\"><b>{title}</b></a> <i>aggiunto da</i> {user}",
"url_unban_success": "",
"url_unwhitelist_success": "",
"url_whitelist_list": "",
"url_whitelist_success": "",
"user_ban": "Sei bannato, non ti è permesso farlo!",
"user_ban_list": "",
"user_ban_success": "",
"user_password_set": "La tua password è stata aggiornata.",
"user_unban_success": "",
"web_user_list": "I seguenti utenti hanno il privilegio di accedere all'interfaccia web: <br /> {users}",
"webpage_address": "Il tuo indirizzo per accedere all'interfaccia web è <a href=\"{address}\">{address}</a>",
"which_command": "Intendi <br /> {commands}",
"wrong_pattern": "Espressione regolare non valida: {error}.",
"yt_no_more": "Nessun altro risultato!",
"yt_query_error": "Impossibile consultare YouTube!",
"yt_result": "Risultato ricerca YouTube: {result_table} Usa <i>!sl {{indexes}}</i> per riprodurre l'elemento desiderato. <br />\\n<i>!ytquery -n</i> per la pagina successiva."
},
"web": {
"action": "Azione",
"add": "Aggiungi",
"add_all": "Aggiungi tutto",
"add_radio": "Aggiungi Radio",
"add_radio_url": "Aggiungi URL Radio",
"add_to_bottom": "Aggiungi in fondo",
"add_to_bottom_of_current_playlist": "Aggiungi in fondo alla playlist corrente",
"add_to_playlist_next": "Aggiungi alla playlist subito dopo il brano corrente",
"add_url": "Aggiungi URL",
"add_youtube_or_soundcloud_url": "Aggiungi URL di YouTube o SoundCloud",
"are_you_really_sure": "Sei davvero sicuro?",
"aria_botamusique_logo": "Botamusique Logo: una volpe con due cuffie, che si gode la musica",
"aria_default_cover": "Un quadrato nero con due ottave unite insieme.",
"aria_empty_box": "Il disegno di una scatola vuota.",
"aria_remove_this_song": "Rimuovi questo brano dalla playlist corrente",
"aria_skip_current_song": "Salta il brano corrente e riproduci ora questo brano",
"aria_skip_to_next_track": "Passa alla traccia successiva",
"aria_spinner": "Una ruota di caricamento",
"aria_warning_of_deletion": "Avviso sulla cancellazione dei file.",
"autoplay": "Riproduzione automatica",
"browse_music_file": "Sfoglia file musicali",
"cancel": "Annulla",
"cancel_upload_warning": "<strong>Sei davvero sicuro?</strong> <br /> Fare di nuovo clic per interrompere il caricamento.",
"change_playback_mode": "Cambia modalità di riproduzione",
"choose_file": "Scegli il file",
"clear_playlist": "Cancella playlist",
"close": "Chiudi",
"delete_all": "Cancella tutto",
"delete_all_files": "Elimina tutti i file elencati",
"delete_file_warning": "Tutti i file elencati qui, inclusi i file in altre pagine, verranno eliminati dal disco rigido.\n È questo che vuoi?",
"directory": "Directory",
"download_all": "Scarica tutto",
"download_song_from_library": "Scarica il brano dalla libreria",
"edit_submit": "Modifica!",
"edit_tags_for": "Modifica tag per",
"expand_playlist": "Vedi elemento <span\n class=\\\"playlist-expand-item-range\\\"></span> nella playlist.",
"file": "File",
"filters": "Filtri",
"index": "#",
"keywords": "Parole chiave",
"keywords_placeholder": "Parole chiave...",
"mini_player_title": "In riproduzione...",
"music_library": "Libreria musicale",
"next_to_play": "Brano seguente",
"no_tag": "Nessun tag",
"oneshot": "One-shot",
"open_volume_controls": "Apri i controlli del volume",
"page_title": "Interfaccia Web di botamusique",
"pause": "Pausa",
"play": "Play",
"playlist_controls": "Controlli playlist",
"radio": "Radio",
"radio_url_placeholder": "URL Radio...",
"random": "Casuale",
"remove_song_from_library": "Rimuovi brano dalla libreria",
"repeat": "Ripeti",
"rescan_files": "Riesegui la scansione dei file",
"skip_track": "Salta traccia",
"submit": "Invia",
"tags": "Tag",
"tags_to_add": "Tag da aggiungere",
"title": "Titolo",
"token": "Token",
"token_required": "Token richiesto",
"token_required_message": "Stai accedendo all'interfaccia web di {{ name }}.\nÈ necessario un token per concederti l'accesso.<br />\nPer favore invia \\\"{{ command }}\\\" al bot in mumble per acquisirne uno.",
"type": "Genere",
"upload_file": "Carica file",
"upload_submit": "Carica!",
"upload_to": "Carica in",
"uploaded_finished": "Caricamento terminato!",
"uploading_files": "Caricamento file...",
"url": "URL",
"url_path": "Url/Percorso",
"url_placeholder": "URL...",
"volume_slider": "Cursore del volume"
}
}

View File

@ -1,173 +0,0 @@
{
"cli": {
"added_tags": "<b>{song}</b>に<i>{tags}</i>というタグを追加しました。",
"added_tags_to_all": "再生リストの曲に<i>{tags}</i>というタグを追加しました。",
"admin_help": "<h3>管理者コマンド</h3>\n<b>Bot</b>\n<ul>\n<li><b>!<u>k</u>ill </b> - botを終了する。</li>\n<li><b>!update </b> - 自動更新する。</li>\n<li><b>!userban </b> {user} - このユーザーを禁止する。</li>\n<li><b>!userunban </b> {user} - このユーザーの禁止を解除する。</li>\n<li><b>!urlbanlist </b> - 禁止さらたユーザーリスト</li>\n<li><b>!urlban </b> [{url}] - {url} デフォルトは今の曲のURLを禁止する。ライブラリに削除する。</li>\n<li><b>!urlunban </b> {url} - このURLの禁止を解除する。</li>\n<li><b>!rescan </b> {url} - 本機の音楽フォルダをスキャン直す。</li>\n<li><b>!dropdatabase</b> - 全部設定とライブラリを消去する。</li>\n</ul>\n<b>Web Interface</b>\n<ul>\n<li><b>!<u>webuserlist</u></b> - list all users that have the permission of accessing the web interface, if auth mode is 'password'.</li>\n<li><b>!<u>webuseradd</u> {nick name}</b> - grant the user with {nick name} the access to the web interface, if auth mode is 'password'.</li>\n<li><b>!<u>webuserdel</u> {nick name}</b> - revoke the access to the web interface of {nick name}, if auth mode is 'password'.</li>\n</ul>",
"auto_paused": "音楽を再開するには、<i>!play</i> を送信してください。",
"bad_command": "<i>{command}</i>: コマンドが見つかりません。",
"bad_parameter": "<i>{command}</i>: パラメータが不正です。",
"bad_url": "URLが不正です。",
"cache_refreshed": "キャッシュが更新されました。",
"change_ducking_volume": "{user}は「ダッキング」が触発する時の音量を{volume}に設定しました。",
"change_max_volume": "",
"change_mode": "{user}がプレイモードを<i>{mode}</i>に設定しました。",
"change_volume": "{user}が音量を{volume}に設定しました。",
"cleared": "再生リストがクリアされました。",
"cleared_tags": "<b>{song}</b>のタグが全部クリアされました。",
"cleared_tags_from_all": "再生リスト内の全ての曲のタグがクリアされました。",
"command_disabled": "{command}: この命令は利用できません。",
"current_ducking_volume": "「ダッキング」が触発する時の音量:{volume}。",
"current_max_volume": "",
"current_mode": "現在のプレイモードは<i>{mode}</i>です。",
"current_volume": "現在の音量は{volume}です。",
"database_dropped": "データベースがクリアされました。",
"download_in_progress": "今は<b>{item}</b>をダウンロード中…",
"error_executing_command": "{command}: コマンドが失敗しまいました,エラーは {error}。",
"file": "ファイル",
"file_added": "新しい曲が追加しました:{item}。",
"file_deleted": "{item}がライブラリから削除されました。",
"file_item": "<b>{artist} - {title}</b><i>{user}</i>によって追加しました。",
"file_missed": "'{file}' が見つかりません!プレイリストから削除します。",
"help": "<h3>コマンドの使い方</h3> <br>\n\n<b>botを操縦する</b>\n\n<ul>\n<li> <b>!<u>w</u>eb</b> - ウェブインターフェースのアドレスを取得する。 </li>\n<li> <b>!play </b> = <b>!p</b> [{num}] [{start_from}] - 再生を再開する・第{num}番目を再生する。 </li>\n<li> <b>!<u>pa</u>use </b> - 一時停止。 </li>\n<li> <b>!<u>st</u>op </b> - 再生停止。 </li>\n<li> <b>!<u>sk</u>ip </b> - 次の曲にスキップする。 </li>\n<li> <b>!<u>la</u>st </b> - 最後の曲にスキップする。 </li>\n<li> <b>!<u>v</u>olume </b> {volume} - 音量を取得・設定する0〜100。 </li>\n<li> <b>!<u>m</u>ode </b> [{mode}] - 再生モードを設定する。 {mode} は<i>one-shot</i> 、 <i>repeat</i>、 <i>random</i>、 \n<i>autoplay</i> 四つ中の一つです。</li>\n<li> <b>!duck </b> on/off - 「ダッキング」を起動する(人が喋る時自動的に音量を下げる)。 </li>\n<li> <b>!duckv </b> {volume} - 「ダッキング」の音量を取得・設定する0〜100。 </li>\n<li> <b>!<u>duckt</u>hres </b> - 「ダッキング」を触発ために必要なオーディオ信号の閾値を設定するデフォルトは3000。 </li>\n<li> <b>!<u>o</u>ust </b> - 再生を停止する、そして最初のチャネルに戻る。 </li>\n</ul> <br>\n\n<b>再生リスト</b> <br>\n\n<ul>\n<li> <b>!<u>n</u>ow </b> ( <b>!np</b>) - 今放送中の曲のインフォを取得する。 </li>\n<li> <b>!<u>q</u>ueue </b> - 再生リストを表示する。 </li>\n<li> <b>!<u>t</u>ag </b> {tags} - ライブラリの中にタグ「{tags}」がある曲を再生リストに追加する。 </li>\n<li> <b>!file </b>( <b>!f</b>) {path/folder/keyword} - 本機にある音楽フェイル・フォルダを追加する。 </li>\n<li> <b>!<u>filem</u>atch </b>(or <b>!fm</b>) {pattern} - ファイルパスが正規表現パターン「{pattern}」にマッチされる曲を追加する。 </li>\n<li> <b>!<u>ur</u>l </b> {url} - Youtube/SoundCloudリンクを追加する。 </li>\n<li> <b>!<u>playl</u>ist </b> {url} [{offset}] - Youtube/SoundCloud再生リストを追加する。 </li>\n<li> <b>!<u>rad</u>io </b> {url} - アドレス「{url}」のウェブラジオを追加する。 </li>\n<li> <b>!<u>rbq</u>uery </b> {keyword} - http://www.radio-browser.infoからウェブラジオを検索する。</li>\n<li> <b>!<u>rbp</u>lay </b> {id} - ID「{id}」のウェブラジオを追加する (例: !rbplay 96746。 </li>\n<li> <b>!<u>ys</u>earch </b> {keywords} - Youtubeを検索する。 ペイジをめぐるため <i>!ysearch -n</i> を使ってください。 </li>\n<li> <b>!<u>yp</u>lay </b> {keywords} - Youtubeを検索する。第一番目の曲を直接に再生リストに追加する。</li>\n<li> <b>!<u>sh</u>ortlist </b> (or <b>!sl</b>) {indexes/*} - 候補リストの第{indexes}番目の曲を追加する(もし「*」を使ったら、候補リストにある全ての曲を追加する)。 </li>\n<li> <b>!rm </b> {num} - 再生リストにある第{num}番目の曲を削除する。 </li>\n<li> <b>!<u>rep</u>eat </b> [{num}] - 今の曲を{num}回リピートする(デフォルトは一回リピートする)。</li>\n<li> <b>!<u>ran</u>dom </b> - 再生リストの順序をランダム化にする。</li>\n</ul> <br>\n\n<b>ライブリ</b> <br>\n\n<ul>\n<li> <b>!<u>se</u>arch </b> {keywords} - ライブリの中に「{keywords}」が出る曲を検索する。</li>\n<li> <b>!<u>li</u>stfile </b> [{pattern}] - ファイルパスが正規表現パターン「{pattern}」にマッチされる曲を表示する。 </li>\n<li> <b>!<u>addt</u>ag </b> [{index}] {tags} - タグ「{tags}」を第{index}番目の曲に追加する(もし{index}が提供されなかったら、今の曲に追加する)。複数のタグが「,」で区切る。 </li>\n<li> <b>!<u>addt</u>ag </b> * {tags} - タグ「{tags}」を再生リストにある全部曲に追加する。 </li>\n<li> <b>!<u>un</u>tag </b> [{index/*}] {tags}/* - 第{index}番目の曲(全ての曲、もし「*」を使ったら)からタグ「{tags}」を削除する(全部のタグ、もし「*」を使ったら)。 </li>\n<li> <b>!<u>fin</u>dtagged </b> (or <b>!ft</b>) {tags} - ライブリに{tags}が含む曲を検索する。 </li>\n<li> <b>!<u>del</u>ete </b> {index} - ライブリ(ハードドライブ)に候補リストの第{index}番目曲を削除する。 </li>\n</ul> <br>\n\n<b>他のコマンド</b> <br>\n\n<ul>\n<li> <b>!<u>j</u>oinme [{token}] </b> - あなたがいるチャネルに入る。</li>\n<li> <b>!<u>password</u> {password} </b> - あなたのウェブインタフェーイスのパスワードを変更する。</li>\n</ul>",
"invalid_index": "インデックス<i>{index}</i>が不正です。再生リストを見るために、<i>!queue</i>を送信してください。",
"last_song_on_the_queue": "最後の曲。",
"max_volume": "",
"multiple_file_added": "以下の曲が追加しました:",
"multiple_file_deleted": "以下の曲がライブラリから削除されました:",
"multiple_file_found": "以下の曲が見つかりました:",
"multiple_matches": "ファイルが見つかりませんでした。もしかして:",
"new_version_found": "<h2>新バージョン発見!</h2> botamusique {new_version} 可用! <hr />\n<h3>更新履歴</h3> {changelog} <hr /> <i>!update</i>を送信してこのバージョンにアップデートします。",
"next_to_play": "次の曲。",
"no_file": "ファイルが見つかりません。",
"not_admin": "あなたは管理員ではありません。",
"not_in_my_channel": "あなたは私のチャネルにいません。",
"not_playing": "何も再生していません。",
"now_playing": "再生中:{item}",
"page_instruction": "第{current}/{total}頁。 <i>!{command} {{page}}</i>を送信してページをめぐります。",
"paused": "音楽は一時停止しました。",
"playlist_fetching_failed": "再生リストを取得できません。",
"pm_not_allowed": "プライベートメッセージが受け取りません。",
"position_in_the_queue": "位置:",
"preconfigurated_radio": "デフォルトのウェブラジオは:",
"queue_contents": "再生リストにある曲は:",
"queue_empty": "再生リストは空です。",
"radio": "ラジオ",
"radio_item": "<a href=\"{url}\"><b>{title}</b></a> <i>{name}</i>から)。<i>{user}</i>に追加されました。",
"rb_play_empty": "ラジオIDを提供してください。",
"rb_query_result": "検索の結果(<i> !rbplay {ID} </i>を送信して再生する)",
"records_omitted": "…",
"removed_tags": "<b>{song}</b>からタグ「 <i>{tags}</i>」を削除しました。",
"removed_tags_from_all": "再生リストの全ての曲にタグ「<i>{tags}</i> 」を削除しました。",
"removing_item": "再生リストに「{item}」を削除しました。",
"repeat": "「{song}」を{n}回リピートするになります。",
"report_version": "現在のbotamusiqueバージョンは<b>{version}</b>です。",
"shortlist_instruction": "<i>!sl {indexes}</i>を使ってこのリストの曲を再生する。",
"start_updating": "更新しています…",
"stopped": "再生停止。",
"too_long": "「{song}」が長さ制限を超えました({duration} > {max_duration})。削除されました。",
"unable_download": "「{item}」がダウンロードできません。削除されました。",
"unable_play": "「{item}」が再生できません。削除されました。",
"unknown_mode": "不正な再生モード「{mode}」。 <i>one-shot</i>, <i>repeat</i>, <i>random</i>, <i>autoplay</i>の中の一つを使ってください。",
"update_successful": "<h2>botamusique v{version} インストール完成!</h2><hr />\n<h3>更新履歴</h3> {changelog} <hr /> このプロジェクトの <a href=\"https://github.com/azlux/botamusique\">githubページ</a> をご覧ください!",
"url": "URL",
"url_ban": "URL {url} が禁止されています。",
"url_ban_list": "",
"url_ban_success": "",
"url_from_playlist": "URL",
"url_from_playlist_item": "<a href=\"{url}\"><b>{title}</b></a>、(<a href=\"{playlist_url}\">{playlist}</a>から)、 <i>{user} </i>に追加されました。",
"url_item": "<a href=\"{url}\"><b>{title}</b></a><i> {user} </i>に追加されました。",
"url_unban_success": "",
"url_unwhitelist_success": "",
"url_whitelist_list": "",
"url_whitelist_success": "",
"user_ban": "あなたはブラックリストに載っています。命令が拒否されました。",
"user_ban_list": "",
"user_ban_success": "",
"user_password_set": "パスワードが更新されました。",
"user_unban_success": "",
"web_user_list": "以下のユーザーはウェブインターフェースを訪問する権利を持っています:<br> {users}",
"webpage_address": "ウェブインターフェースのアドレスは<a href=\"{address}\">{address}</a>。",
"which_command": "もしかして <br /> {commands}",
"wrong_pattern": "不正な正規表現パターン:{error}。",
"yt_no_more": "これ以上のエントリがありません。",
"yt_query_error": "Youtubeを訪問できません",
"yt_result": "Youtube検索結果 {result_table} <i>!sl {{indexes}}</i>を使って再生します。 <br />\n<i>!ytquery -n</i>を使ってページをめぐります。"
},
"web": {
"action": "動作",
"add": "追加する",
"add_all": "全部追加",
"add_radio": "ラジオを追加する",
"add_radio_url": "ラジオURL",
"add_to_bottom": "最後尾に追加する。",
"add_to_bottom_of_current_playlist": "再施リストの最後尾に追加する。",
"add_to_playlist_next": "次の曲に追加する。",
"add_url": "URLを追加する",
"add_youtube_or_soundcloud_url": "Youtube・Soundcloud URLを追加する",
"are_you_really_sure": "本当ですが?",
"aria_botamusique_logo": "BotamusiqueのLogo",
"aria_default_cover": "デフォルトアルバムカバー。",
"aria_empty_box": "空。",
"aria_remove_this_song": "再生リストからこの曲を削除する。",
"aria_skip_current_song": "いますぐこの曲を再生する。",
"aria_skip_to_next_track": "次の曲を再生する。",
"aria_spinner": "ローディング中",
"aria_warning_of_deletion": "ファイル削除警告",
"autoplay": "自動再生",
"browse_music_file": "音楽ファイルを閲覧する",
"cancel": "キャンセル",
"cancel_upload_warning": "<strong>本当ですが?</strong> <br /> もしアップ本当にロードをキャンセルすることに決まったら、もう一度このバトンを押してください。",
"change_playback_mode": "再生モードを変更する",
"choose_file": "ファイルを選ぶ",
"clear_playlist": "クリアする",
"close": "閉める",
"delete_all": "全部削除",
"delete_all_files": "以上のファイルを全て削除する",
"delete_file_warning": "続行すると、以上表示されたファイル(他のページにあるファイルも含む)をハードドライブから消去されます。本当にそうしますか?",
"directory": "フォルダ",
"download_all": "全部ダウンロード",
"download_song_from_library": "ライブラリから音楽ファイルをダウンロードする",
"edit_submit": "変更",
"edit_tags_for": "タグを編集する",
"expand_playlist": "第 <span class=\"playlist-expand-item-range\"></span> 番目の曲を表示する。",
"file": "ファイル",
"filters": "検索",
"index": "",
"keywords": "キーワード",
"keywords_placeholder": "キーワード…",
"mini_player_title": "放送中…",
"music_library": "ライブラリ",
"next_to_play": "次の曲に追加する",
"no_tag": "空",
"oneshot": "順番に再生",
"open_volume_controls": "音量スライダーを表示する",
"page_title": "botamusiqueウェブインタフェイス",
"pause": "一時停止",
"play": "再生する",
"playlist_controls": "再生管理",
"radio": "ラジオ",
"radio_url_placeholder": "ラジオURL…",
"random": "シャッフル再生",
"remove_song_from_library": "ライブラリから削除する",
"repeat": "全曲リピート",
"rescan_files": "フォルダをスキャン直す",
"skip_track": "今の曲をスッキプする",
"submit": "送信",
"tags": "タグ",
"tags_to_add": "追加するタグ",
"title": "タイトル",
"token": "トークン",
"token_required": "トークンが必要です",
"token_required_message": "このページは{{ name }}のウェブインタフェイスです。\n設定によって、ログオンするにはトークンが必要になります。<br />\n \"{{ command }}\" を送信してトークンを取得してください。",
"type": "種類",
"upload_file": "アップロード",
"upload_submit": "アップロード",
"upload_to": "フォルダ",
"uploaded_finished": "アップロード完了",
"uploading_files": "アップロード中…",
"url": "URL",
"url_path": "URL・パス",
"url_placeholder": "URL…",
"volume_slider": "音量スライダー"
}
}

View File

@ -1,173 +0,0 @@
{
"cli": {
"added_tags": "",
"added_tags_to_all": "",
"admin_help": "",
"auto_paused": "",
"bad_command": "",
"bad_parameter": "",
"bad_url": "",
"cache_refreshed": "",
"change_ducking_volume": "",
"change_max_volume": "",
"change_mode": "",
"change_volume": "",
"cleared": "",
"cleared_tags": "",
"cleared_tags_from_all": "",
"command_disabled": "",
"current_ducking_volume": "",
"current_max_volume": "",
"current_mode": "",
"current_volume": "Huidig volume: {volume}.",
"database_dropped": "",
"download_in_progress": "",
"error_executing_command": "",
"file": "Bestand",
"file_added": "Toegevoegd {item}.",
"file_deleted": "",
"file_item": "",
"file_missed": "",
"help": "",
"invalid_index": "",
"last_song_on_the_queue": "",
"max_volume": "",
"multiple_file_added": "",
"multiple_file_deleted": "",
"multiple_file_found": "",
"multiple_matches": "",
"new_version_found": "",
"next_to_play": "",
"no_file": "",
"not_admin": "",
"not_in_my_channel": "",
"not_playing": "",
"now_playing": "",
"page_instruction": "",
"paused": "",
"playlist_fetching_failed": "",
"pm_not_allowed": "",
"position_in_the_queue": "",
"preconfigurated_radio": "",
"queue_contents": "",
"queue_empty": "",
"radio": "",
"radio_item": "",
"rb_play_empty": "",
"rb_query_result": "",
"records_omitted": "",
"removed_tags": "",
"removed_tags_from_all": "",
"removing_item": "",
"repeat": "",
"report_version": "",
"shortlist_instruction": "",
"start_updating": "",
"stopped": "",
"too_long": "",
"unable_download": "",
"unable_play": "",
"unknown_mode": "",
"update_successful": "",
"url": "",
"url_ban": "",
"url_ban_list": "",
"url_ban_success": "",
"url_from_playlist": "",
"url_from_playlist_item": "",
"url_item": "",
"url_unban_success": "",
"url_unwhitelist_success": "",
"url_whitelist_list": "",
"url_whitelist_success": "",
"user_ban": "",
"user_ban_list": "",
"user_ban_success": "",
"user_password_set": "",
"user_unban_success": "",
"web_user_list": "",
"webpage_address": "",
"which_command": "",
"wrong_pattern": "",
"yt_no_more": "",
"yt_query_error": "",
"yt_result": ""
},
"web": {
"action": "",
"add": "",
"add_all": "",
"add_radio": "",
"add_radio_url": "",
"add_to_bottom": "",
"add_to_bottom_of_current_playlist": "",
"add_to_playlist_next": "",
"add_url": "",
"add_youtube_or_soundcloud_url": "",
"are_you_really_sure": "",
"aria_botamusique_logo": "",
"aria_default_cover": "",
"aria_empty_box": "",
"aria_remove_this_song": "",
"aria_skip_current_song": "",
"aria_skip_to_next_track": "",
"aria_spinner": "",
"aria_warning_of_deletion": "",
"autoplay": "",
"browse_music_file": "",
"cancel": "",
"cancel_upload_warning": "",
"change_playback_mode": "",
"choose_file": "",
"clear_playlist": "",
"close": "",
"delete_all": "",
"delete_all_files": "",
"delete_file_warning": "",
"directory": "",
"download_all": "",
"download_song_from_library": "",
"edit_submit": "",
"edit_tags_for": "",
"expand_playlist": "",
"file": "",
"filters": "",
"index": "",
"keywords": "",
"keywords_placeholder": "",
"mini_player_title": "",
"music_library": "",
"next_to_play": "",
"no_tag": "",
"oneshot": "",
"open_volume_controls": "",
"page_title": "",
"pause": "",
"play": "",
"playlist_controls": "",
"radio": "",
"radio_url_placeholder": "",
"random": "",
"remove_song_from_library": "",
"repeat": "",
"rescan_files": "",
"skip_track": "",
"submit": "",
"tags": "",
"tags_to_add": "",
"title": "",
"token": "",
"token_required": "",
"token_required_message": "",
"type": "",
"upload_file": "",
"upload_submit": "",
"upload_to": "",
"uploaded_finished": "",
"uploading_files": "",
"url": "",
"url_path": "",
"url_placeholder": "",
"volume_slider": ""
}
}

View File

@ -1,173 +0,0 @@
{
"cli": {
"added_tags": "As etiquetas <i>{tags}</i> foram adicionadas em <b>{song}</b>.",
"added_tags_to_all": "As etiquetas <i>{tags}</i> foram adicionadas nas músicas da lista de reprodução.",
"admin_help": "<h3>Comandos de administrador</h3>\n<b>Robô</b>\n<ul>\n<li><b>!<u>k</u>ill </b> - matar o robô</li>\n<li><b>!update </b> - atualizar o robô</li>\n<li><b>!userban </b> {usuário} - banir um usuário</li>\n<li><b>!userunban </b> {usuário} - remover usuário da lista de usuários banidos</li>\n<li><b>!urlbanlist </b> - exibir lista de endereços banidos</li>\n<li><b>!urlban </b> [{endereço}] - banir {endereço} (ou o endereço do item atual, por padrão) e remover este endereço da biblioteca.</li>\n<li><b>!urlunban </b> {endereço - remover {endereço} da lista de endereços banidos</li>\n<li><b>!rescan </b> {endereço} - reconstruir cache de arquivos de música local</li>\n<li><b>!dropdatabase</b> - limpar o banco de dados inteiro, você perderá todas as configurações e a biblioteca de música.</li>\n</ul>\n<b>Interface web</b>\n<ul>\n<li><b>!<u>webuserlist</u></b> - exibir lista de todos os usuários que têm permissão para acessar a interface web, se o modo de autenticação for 'password'.</li>\n<li><b>!<u>webuseradd</u> {apelido}</b> - dar acesso à interface web para {apelido}, se o modo de autenticação for 'password'.</li>\n<li><b>!<u>webuserdel</u> {apelido}</b> - revogar o acesso à interface web de {apelido}, caso o modo de autenticação for 'password'.</li>\n</ul>",
"auto_paused": "Use <i>!play</i> para retomar a reprodução de música!",
"bad_command": "<i>{command}</i>: comando não encontrado.",
"bad_parameter": "<i>{command}</i>: parâmetro inválido.",
"bad_url": "Um endereço malformado foi pedido.",
"cache_refreshed": "Cache atualizado!",
"change_ducking_volume": "O volume de atenuação foi definido para {volume} por {user}.",
"change_max_volume": "O volume máximo foi definido para {max} por {user}.",
"change_mode": "O modo de reprodução foi definido para <i>{mode}</i> por {user}.",
"change_volume": "O volume foi definido para {volume} por {user}.",
"cleared": "A lista de reprodução foi esvaziada.",
"cleared_tags": "Todas as etiquetas foram removidas de <b>{song}</b>.",
"cleared_tags_from_all": "Todas as etiquetas das músicas na lista de reprodução foram removidas.",
"command_disabled": "{command}: comando desabilitado!",
"current_ducking_volume": "Volume de atenuação: {volume}.",
"current_max_volume": "Volume máximo atual: {max}.",
"current_mode": "O modo de reprodução é <i>{mode}</i>.",
"current_volume": "Volume atual: {volume}.",
"database_dropped": "O banco de dados foi esvaziado.",
"download_in_progress": "A descarga de <b>{item}</b> está em progresso...",
"error_executing_command": "{command}: O comando falhou com um erro: {error}.",
"file": "Arquivo",
"file_added": "{item} adicionado.",
"file_deleted": "{item} foi apagado da biblioteca.",
"file_item": "<b>{artist} - {title}</b> <i>adicionado por</i> {user}",
"file_missed": "O arquivo de música '{file}' foi perdido! Este item foi removido da lista de reprodução.",
"help": "<h3>Comandos</h3>\n<b>Controle</b>\n<ul>\n<li> <b>!<u>w</u>eb</b> - exibe o endereço da interface web, caso habilitado. </li>\n<li> <b>!play </b> (ou <b>!p</b>) [{num}] [{iniciar_de}] - resume/inicia a reprodução (a partir da música na posição {num}, caso especificado) </li>\n<li> <b>!<u>pa</u>use </b> - pausa </li>\n<li> <b>!<u>st</u>op </b> - interrompe a reprodução </li>\n<li> <b>!<u>sk</u>ip </b> - pula para a próxima música </li>\n<li> <b>!<u>la</u>st </b> - pula para a última música </li>\n<li> <b>!<u>v</u>olume </b> {volume} - exibe ou altera o volume (de 0 a 100) </li>\n<li> <b>!<u>m</u>ode </b> [{modo}] - exibe ou define o modo de reprodução, {modo} deve ser um dos seguintes: <i>one-shot</i> (remover o item assim que ele for reproduzido, <i>repeat</i> (repetir a lista de reprodução), ou <i>random</i> (tornar a lista de reprodução em ordem aleatória),\n<i>autoplay</i> (escolher algo da biblioteca de música aleatoriamente).</li>\n<li> <b>!duck </b> on/off - habilita ou desabilita a função de atenuação </li>\n<li> <b>!duckv </b> {volume} - define o volume do robô quando a atenuação está ativada </li>\n<li> <b>!<u>duckt</u>hres </b> - define o nível de volume que ativa a atenuação (3000 por padrão)</li>\n<li> <b>!<u>o</u>ust </b> - interrompe a reprodução e vai para o canal padrão </li>\n</ul>\n<b>Lista de reprodução</b>\n<ul>\n<li> <b>!<u>n</u>ow </b> (ou <b>!np</b>) - exibe a música atual </li>\n<li> <b>!<u>q</u>ueue </b> - exibe os itens na lista de reprodução </li>\n<li> <b>!<u>t</u>ag </b> {etiquetas} - adiciona todos os itens com as etiquetas {etiquetas}, etiquetas separadas com \",\". </li>\n<li> <b>!file </b>(ou <b>!f</b>) {caminho/pasta/palavra-chave} - adiciona um único arquivo à lista de reprodução pelo seu caminho ou palavra-chave em seu caminho. </li>\n<li> <b>!<u>filem</u>atch </b>(ou <b>!fm</b>) {padrão} - adiciona todos os arquivos que combinarem com a expressão regular {padrão} </li>\n<li> <b>!<u>ur</u>l </b> {url} - adicionar música do YouTube ou SoundCloud </li>\n<li> <b>!<u>playl</u>ist </b> {endereço} [{deslocamento}] - adiciona todos os itens em uma lista de reprodução do YouTube ou SoundCloud, a partir do item na posição {deslocamento} </li>\n<li> <b>!<u>rad</u>io </b> {endereço} - adiciona a rádio {endereço} no final da lista de reprodução </li>\n<li> <b>!<u>rbq</u>uery </b> {palavra_chave} - busca por uma estação de rádio em http://www.radio-browser.info </li>\n<li> <b>!<u>rbp</u>lay </b> {id} - reproduz uma estação de rádio com {id} (por ex.: !rbplay 96746) </li>\n<li> <b>!<u>ys</u>earch </b> {palavras_chave} - busca no YouTube. Use <i>!ysearch -n</i> para trocar de página. </li>\n<li> <b>!<u>yp</u>lay </b> {palavras_chave} - adiciona o primeiro resultado da busca de {palavras_chave} na lista de reprodução.</li>\n<li> <b>!<u>sh</u>ortlist </b> (ou <b>!sl</b>) {índices/*} - adiciona o item na posição {índices} (ou todos caso * seja especificado) na lista curta. </li>\n<li> <b>!rm </b> {num} - remove a música na posição {num} da lista de reprodução </li>\n<li> <b>!<u>rep</u>eat </b> [{num}] - repete a música atual {num} (1 por padrão) vezes.</li>\n<li> <b>!<u>ran</u>dom </b> - torna a lista de reprodução em ordem aleatória.</li>\n</ul>\n<b>Biblioteca de música</b>\n<ul>\n<li> <b>!<u>se</u>arch </b> {palavras_chave} - busca pelo item com {palavras_chave} na biblioteca de música, palavras-chave separadas por espaço.</li>\n<li> <b>!<u>li</u>stfile </b> [{padrão}] - exibe a lista de arquivos disponíveis (os quais caminhos combinam com o padrão de expressão regular caso {padrão} seja especificado) </li>\n<li> <b>!<u>addt</u>ag </b> [{índice}] {etiquetas} - adiciona {etiquetas} para a música da lista de reprodução na posição {índice} (ou a música atual caso {índice} seja omitido), etiquetas separadas por \",\". </li>\n<li> <b>!<u>addt</u>ag </b> * {etiquetas} - adiciona {etiquetas} para todos os itens na lista de reprodução. </li>\n<li> <b>!<u>un</u>tag </b> [{índice/*}] {etiquetas}/* - remove {etiquetas}/todas as etiquetas da música da lista de reprodução na posição {índice} (ou a música atual caso {índice} seja omitido). </li>\n<li> <b>!<u>fin</u>dtagged </b> (ou <b>!ft</b>) {etiquetas} - busca por um item com {etiquetas} na biblioteca de música. </li>\n<li> <b>!<u>del</u>ete </b> {índice} - apaga da biblioteca de música o item da lista curta na posição {índice}. </li>\n</ul>\n<b>Outro</b>\n<ul>\n<li> <b>!<u>j</u>oinme {token} </b> - entra no seu próprio canal com {token}.</li>\n<li> <b>!<u>password</u> {senha} </b> - altera sua senha, usada para acessar a interface web.</li>\n</ul>",
"invalid_index": "O índice <i>{index}</i> é inválido. Use <i>!queue</i> para visualizar a lista de reprodução.",
"last_song_on_the_queue": "Último na fila.",
"max_volume": "O volume excede o volume máximo {max}. O volume foi definido para o máximo.",
"multiple_file_added": "Múltiplos itens adicionados:",
"multiple_file_deleted": "Múltiplos itens foram apagados da biblioteca:",
"multiple_file_found": "Encontrado:",
"multiple_matches": "Arquivo não encontrado! Possíveis resultados:",
"new_version_found": "<h2>Atualização disponível!</h2> A versão {new_version} do botamusique está disponível! <hr />\n<h3>Registro de mudanças</h3> {changelog} <hr /> Envie <i>!update</i> para atualizar!",
"next_to_play": "Próxima música.",
"no_file": "Arquivo não encontrado.",
"not_admin": "Você não é um administrador!",
"not_in_my_channel": "Você não está no meu canal!",
"not_playing": "Nada está sendo reproduzido neste momento.",
"now_playing": "Reproduzindo {item}",
"page_instruction": "Página {current}/{total}. Use <i>!{command} {{page}}</i> para navegar.",
"paused": "Música pausada.",
"playlist_fetching_failed": "Não foi possível receber a lista de reprodução!",
"pm_not_allowed": "Mensagens privadas não são permitidas.",
"position_in_the_queue": "Posição: {position}",
"preconfigurated_radio": "Estações de rádio pré-configuradas disponíveis:",
"queue_contents": "Itens na lista de reprodução:",
"queue_empty": "A lista de reprodução está vazia!",
"radio": "Rádio",
"radio_item": "<a href=\"{url}\"><b>{title}</b></a> <i>de</i> {name} <i>foi adicionado por</i> {user}",
"rb_play_empty": "Por favor especifique a identificação de uma estação de rádio!",
"rb_query_result": "Este é o resultado da sua busca, envie <i>!rbplay {ID}</i> para reproduzir uma estação:",
"records_omitted": "…",
"removed_tags": "As etiquetas <i>{tags}</i> foram removidas de <b>{song}</b>.",
"removed_tags_from_all": "As etiquetas <i>{tags}</i> foram removidas das músicas na lista de reprodução.",
"removing_item": "O item {item} na lista de reprodução foi removido.",
"repeat": "Repetir {song} {n} vezes.",
"report_version": "A versão atual do botamusique é <b>{version}</b>.",
"shortlist_instruction": "Use <i>!sl {índices}</i> para reproduzir o item que você deseja.",
"start_updating": "Iniciando a atualização...",
"stopped": "Música parada.",
"too_long": "<b>{song}</b> é muito longo ({duration} > {max_duration}). Removido da lista de reprodução!",
"unable_download": "Falha ao baixar <b>{item}</b>. Removido da biblioteca.",
"unable_play": "Falha ao reproduzir <b>{item}</b>. Removido da biblioteca.",
"unknown_mode": "O modo de reprodução '{mode}' é desconhecido. Ele deve ser um dos seguintes: <i>one-shot</i>, <i>repeat</i>, <i>random</i>.",
"update_successful": "<h2>botamusique v{version} instalado!</h2><hr />\n<h3>Registro de mudanças</h3> {changelog} <hr /> Visite <a href=\"https://github.com/azlux/botamusique\">nosso repositório no GitHub</a> para mais detalhes!",
"url": "Endereço",
"url_ban": "O endereço {url} está banido! Removido da lista de reprodução!",
"url_ban_list": "Lista de endereços banidos: <br>{list}",
"url_ban_success": "O seguinte endereço está banido: {url}.",
"url_from_playlist": "Endereço",
"url_from_playlist_item": "<a href=\"{url}\"><b>{title}</b></a> <i>da lista de reprodução</i> <a href=\"{playlist_url}\">{playlist}</a> <i>adicionado por</i> {user}",
"url_item": "<a href=\"{url}\"><b>{title}</b></a> <i>adicionado por</i> {user}",
"url_unban_success": "O seguinte endereço foi removido da lista de endereços banidos: {url}.",
"url_unwhitelist_success": "O seguinte endereço foi removido da lista branca: {url}.",
"url_whitelist_list": "Lista de endereços na lista branca: <br>{list}",
"url_whitelist_success": "O seguinte endereço foi adicionado à lista branca: {url}.",
"user_ban": "Você está banido. Você não tem permissão para fazer isto!",
"user_ban_list": "Lista de usuários banidos: <br>{list}",
"user_ban_success": "O usuário {user} foi banido.",
"user_password_set": "A sua senha foi atualizada.",
"user_unban_success": "O usuário {user} foi removido da lista de usuários banidos.",
"web_user_list": "Os seguintes usuários possuem privilégio para acessar a interface web: <br /> {users}",
"webpage_address": "O seu próprio endereço para acessar a interface web é <a href=\"{address}\">{address}</a>",
"which_command": "Você quis dizer <br /> {commands}",
"wrong_pattern": "Expressão regular inválida: {error}.",
"yt_no_more": "Não há mais resultados!",
"yt_query_error": "Não foi possível buscar no YouTube!",
"yt_result": "Resultado da busca no YouTube: {result_table} Use <i>!sl {{índices}}</i> para reproduzir o item que você deseja. <br />\n<i>!ytquery -n</i> para exibir a próxima página."
},
"web": {
"action": "Ação",
"add": "Adicionar",
"add_all": "Adicionar todos",
"add_radio": "Adicionar rádio",
"add_radio_url": "Adicionar endereço de rádio",
"add_to_bottom": "Adicionar no fim",
"add_to_bottom_of_current_playlist": "Adicionar no fim da lista de reprodução atual",
"add_to_playlist_next": "Adicionar para a lista de reprodução após a música atual",
"add_url": "Adicionar lista de reprodução",
"add_youtube_or_soundcloud_url": "Adicionar endereço do YouTube ou SoundCloud",
"are_you_really_sure": "Você realmente tem certeza?",
"aria_botamusique_logo": "Logo do botamusique: uma raposa escutando música com fones de ouvido",
"aria_default_cover": "Um quadrado preto com duas oitavas disparadas juntas",
"aria_empty_box": "Um desenho de uma caixa vazia.",
"aria_remove_this_song": "Remover esta música da lista de reprodução atual",
"aria_skip_current_song": "Pular música atual e reproduzir esta música agora",
"aria_skip_to_next_track": "Pular para a próxima trilha",
"aria_spinner": "Um ícone de carregamento girando",
"aria_warning_of_deletion": "Aviso sobre a remoção de arquivos.",
"autoplay": "Reproduzir automaticamente",
"browse_music_file": "Procurar arquivo de música",
"cancel": "Cancelar",
"cancel_upload_warning": "<strong>Você realmente tem certeza?</strong><br />Clique novamente para abortar o envio.",
"change_playback_mode": "Alterar modo de reprodução",
"choose_file": "Escolher arquivo",
"clear_playlist": "Limpar lista de reprodução",
"close": "Fechar",
"delete_all": "Apagar todos",
"delete_all_files": "Apagar todos os arquivos listados",
"delete_file_warning": "Todos os arquivos listados aqui, incluindo os arquivos em outras páginas, serão apagados do seu disco.\n É isso o que você quer?",
"directory": "Diretório",
"download_all": "Baixar todos",
"download_song_from_library": "Baixar música da biblioteca",
"edit_submit": "Editar!",
"edit_tags_for": "Editar etiquetas de",
"expand_playlist": "Ver o item <span\n class=\"playlist-expand-item-range\"></span> na lista de reprodução.",
"file": "Arquivo",
"filters": "Filtros",
"index": "Nº",
"keywords": "Palavras-chave",
"keywords_placeholder": "Palavras-chave...",
"mini_player_title": "Reproduzindo...",
"music_library": "Biblioteca de música",
"next_to_play": "Próximo a reproduzir",
"no_tag": "Nenhuma etiqueta",
"oneshot": "Reprodução única",
"open_volume_controls": "Abrir controles de volume",
"page_title": "Interface web botamusique",
"pause": "Pausar",
"play": "Reproduzir",
"playlist_controls": "Controles de lista de reprodução",
"radio": "Rádio",
"radio_url_placeholder": "Endereço de rádio...",
"random": "Aleatório",
"remove_song_from_library": "Remover música da biblioteca",
"repeat": "Repetir",
"rescan_files": "Escanear arquivos novamente",
"skip_track": "Pular trilha",
"submit": "Enviar",
"tags": "Etiquetas",
"tags_to_add": "Etiquetas para adicionar",
"title": "Título",
"token": "Token",
"token_required": "Token necessário",
"token_required_message": "Você está acessando a interface web de {{ name }}.\nUm token é necessário para autorizar o seu acesso.<br />\nPor favor, envie \"{{ command }}\" para o robô no Mumble para recebê-lo.",
"type": "Tipo",
"upload_file": "Enviar arquivo",
"upload_submit": "Enviar!",
"upload_to": "Enviar para",
"uploaded_finished": "Envio concluído!",
"uploading_files": "Enviando arquivos...",
"url": "Endereço",
"url_path": "Endereço/Caminho",
"url_placeholder": "Endereço...",
"volume_slider": "Controle de volume"
}
}

View File

@ -1,173 +0,0 @@
{
"cli": {
"added_tags": "已将标签 <i>{tags}</i> 添加到 <b>{song}</b>。",
"added_tags_to_all": "已将标签 <i>{tags}</i> 添加到播放列表的所有曲目中。",
"admin_help": "<h3>管理员命令</h3>\n<b>机器人管理</b>\n<ul>\n<li><b>!<u>k</u>ill </b> - 退出。</li>\n<li><b>!update </b> - 自动更新至新版本。</li>\n<li><b>!userban </b> {user} - 封禁用户。</li>\n<li><b>!userunban </b> {user} - 解除封禁。</li>\n<li><b>!urlbanlist </b> - 列出全部封禁的用户。</li>\n<li><b>!urlban </b> [{url}] - 封禁链接 {url} 若未指定则默认为当前播放曲目的URL 并将它从数据库中移除。</li>\n<li><b>!urlunban </b> {url} - 解除封禁链接 {url}。</li>\n<li><b>!rescan </b> {url} - 更新本地音乐库。</li>\n<li><b>!dropdatabase</b> - 清除数据库(包括设置和音乐库)。本操作不可逆,请务必事先考虑清楚。</li>\n</ul>\n<b>网络控制界面</b>\n<ul>\n<li><b>!<u>webuserlist</u></b> - (若当前认证模式为 'password')列出所有具有网络控制界面访问权限的用户。</li>\n<li><b>!<u>webuseradd</u> {name}</b> - (若当前认证模式为 'password')授权名为 {name} 的用户访问网络控制界面。</li>\n<li><b>!<u>webuserdel</u> {name}</b> - (若当前认证模式为 'password')撤销名为 {name} 的用户的访问权限。</li>\n</ul>",
"auto_paused": "已暂停。若要继续播放,请发送 <i>!play</i> ",
"bad_command": "{{command}}: 未知命令。请发送<i>!help</i>获取命令列表。",
"bad_parameter": "{command}: 无效参数!",
"bad_url": "URL地址无效",
"cache_refreshed": "缓存已刷新。",
"change_ducking_volume": "{user}将“闪避”时的音量设置为 {volume}。",
"change_max_volume": "",
"change_mode": "{user}将播放列表模式被设置为<i>{mode}</i> 。",
"change_volume": "{user}将音量设置为{volume}。",
"cleared": "播放列表已清空。",
"cleared_tags": "已移除<b>{song}</b>上的所有标签。",
"cleared_tags_from_all": "已移除播放列表内所有曲目的标签。",
"command_disabled": "{command}: 该命令不可用!",
"current_ducking_volume": "“闪避”时的音量为:{volume}。",
"current_max_volume": "",
"current_mode": "当前的播放模式为<i>{mode}</i>。",
"current_volume": "当前音量为{volume}。",
"database_dropped": "数据库已经清空。",
"download_in_progress": "正在下载<b>{item}</b>……",
"error_executing_command": "{command}: 命令失败,错误为 {error}。",
"file": "文件",
"file_added": "新曲目被添加:{item}。",
"file_deleted": "{item}已从库中移除。",
"file_item": "<b>{artist} - {title}</b>,由<i>{user}</i>添加。",
"file_missed": "文件 '{file}' 丢失!已将其移出播放列表。",
"help": "<h3>命令帮助</h3>\n\n<b>播放控制</b>\n\n<ul>\n<li> <b>!<u>w</u>eb</b> - 获取网页控制界面的地址(如果启用了的话)。 </li>\n<li> <b>!play </b> (或 <b>!p</b> [{num}] [{start_from}] - 继续播放/开始播放第{num}首曲目。 </li>\n<li> <b>!<u>pa</u>use </b> - 暂停播放。 </li>\n<li> <b>!<u>st</u>op </b> - 停止播放。 </li>\n<li> <b>!<u>sk</u>ip </b> - 跳到下一首曲目。 </li>\n<li> <b>!<u>la</u>st </b> - 跳到播放列表上的最后一首曲目。 </li>\n<li> <b>!<u>v</u>olume </b> {volume} - 获取或设置音量从0到100。 </li>\n<li> <b>!<u>m</u>ode </b> [{mode}] - 设置播放模式。 {mode} 可以使 <i>one-shot</i> (顺序播放), <i>repeat</i> (循环播放), <i>random</i> (随机播放)或\n<i>autoplay</i> (自动播放)四种之一.</li>\n<li> <b>!duck </b> on/off - 开启或关闭“闪避”功能。开启后,在别人说话时,音乐的音量会自动减小。 </li>\n<li> <b>!duckv </b> {volume} - 获取或设置“闪避”时的音量。 </li>\n<li> <b>!<u>duckt</u>hres </b> - 设置“闪避”被激活所需音频信号强度的阈值默认是3000。 </li>\n<li> <b>!<u>o</u>ust </b> - 停止播放,并回到默认频道。 </li>\n</ul>\n<b>播放列表</b>\n\n<ul>\n<li> <b>!<u>n</u>ow </b> (或 <b>!np</b>) - 显示当前曲目信息。 </li>\n<li> <b>!<u>q</u>ueue </b> - 显示播放列表。 </li>\n<li> <b>!<u>t</u>ag </b> {tags} - 将音乐库中所有包含{tags}标签的曲目添加到播放列表中。 </li>\n<li> <b>!file </b>(或 <b>!f</b>) {path/folder/keyword} - 添加某一本地音频文件或某个目录中的全部文件到播放列表中。 </li>\n<li> <b>!<u>filem</u>atch </b>(or <b>!fm</b>) {pattern} - 将文件名满足正则表达式{pattern}的全部文件添加到播放列表中。 </li>\n<li> <b>!<u>ur</u>l </b> {url} - 添加Youtube或SoundCloud链接。 </li>\n<li> <b>!<u>playl</u>ist </b> {url} [{offset}] - 添加Youtube或SoundCloud播放列表。 </li>\n<li> <b>!<u>rad</u>io </b> {url} - 将地址为{url}的电台加入播放列表。 </li>\n<li> <b>!<u>rbq</u>uery </b> {keyword} - 从http://www.radio-browser.info中搜索某一电台。</li>\n<li> <b>!<u>rbp</u>lay </b> {id} - 播放ID为{id}的电台 (如 !rbplay 96746。 </li>\n<li> <b>!<u>ys</u>earch </b> {keywords} - 搜索Youtube。 使用 <i>!ysearch -n</i> 翻页. </li>\n<li> <b>!<u>yp</u>lay </b> {keywords} - 搜索Youtube将第一条搜索结果直接加入播放列表。</li>\n<li> <b>!<u>sh</u>ortlist </b> (or <b>!sl</b>) {indexes/*} - 添加候选列表中的第{indexes}条曲目(或者是全部曲目,如果该参数为“*”)到播放列表中。 </li>\n<li> <b>!rm </b> {num} - 删除播放列表上的第{num}首曲目。 </li>\n<li> <b>!<u>rep</u>eat </b> [{num}] - 重复当前曲目{num}遍(默认重复一遍)。</li>\n<li> <b>!<u>ran</u>dom </b> - 随机打乱播放列表顺序。</li>\n</ul>\n\n<b>音乐库</b>\n\n<ul>\n<li> <b>!<u>se</u>arch </b> {keywords} - 在音乐库中搜索包含关键词{keywords}的曲目,关键词以空格分割。</li>\n<li> <b>!<u>li</u>stfile </b> [{pattern}] - 列出路径符合正则表达式{pattern}的文件。 </li>\n<li> <b>!<u>addt</u>ag </b> [{index}] {tags} - 将标签{tags}添加到第{index}首曲目(如果{index}被省略则默认为当前曲目)。多个标签以“,”分割。 </li>\n<li> <b>!<u>addt</u>ag </b> * {tags} - 将标签{tags}添加到播放列表上的所有曲目。 </li>\n<li> <b>!<u>un</u>tag </b> [{index/*}] {tags}/* - 从第{index}首曲目(或当前曲目,若{index}被省略;或全部曲目,若该参数为“*”)上删除标签{tags}(或全部标签)。 </li>\n<li> <b>!<u>fin</u>dtagged </b> (or <b>!ft</b>) {tags} - 在音乐库中查找包含标签{tags}的曲目。 </li>\n<li> <b>!<u>del</u>ete </b> {index} - 从音乐库中删除候选列表上的第{index}首曲目。 </li>\n</ul>\n\n<b>其他</b>\n\n<ul>\n<li> <b>!<u>j</u>oinme [{token}] </b> - 加入你所在的频道。</li>\n<li> <b>!<u>password</u> {password} </b> - 更改你用于访问网页控制界面的密码。</li>\n</ul>",
"invalid_index": "无效的序号 <i>{index}</i>。 使用 '!queue' 查看播放列表。",
"last_song_on_the_queue": "最后一首。",
"max_volume": "",
"multiple_file_added": "以下曲目已被添加:",
"multiple_file_deleted": "以下曲目已被移出库:",
"multiple_file_found": "搜索到:",
"multiple_matches": "文件未找到!你是不是指:",
"new_version_found": "<h2>发现新版本!</h2> botamusique {new_version} 可用! <hr />\n<h3>更新日志</h3> {changelog} <hr /> 使用 <i>!update</i>自动更新至该版本。",
"next_to_play": "下一首。",
"no_file": "文件未找到。",
"not_admin": "你不是管理员!",
"not_in_my_channel": "你不在我的频道里!",
"not_playing": "无播放中的曲目。",
"now_playing": "正在播放:{item}",
"page_instruction": "第{current}/{total}页。发送<i>!{command} {{page}}</i>翻页。",
"paused": "暂停播放。",
"playlist_fetching_failed": "无法获取播放列表!",
"pm_not_allowed": "不接受私信。",
"position_in_the_queue": "位置:",
"preconfigurated_radio": "预设的电台如下:",
"queue_contents": "播放列表中的曲目:",
"queue_empty": "播放列表为空!",
"radio": "电台",
"radio_item": "<a href=\"{url}\"><b>{title}</b></a><i>来自</i> {name}。 <i>由</i> {user} <i>添加</i>。",
"rb_play_empty": "请指定一个电台ID",
"rb_query_result": "搜索结果如下。发送<i> !rbplay {ID} </i>播放。",
"records_omitted": "……",
"removed_tags": "已将标签 <i>{tags}</i> 从 <b>{song}</b>上移除。",
"removed_tags_from_all": "已将标签 <i>{tags}</i> 从播放列表的曲目中移除。",
"removing_item": "已将 {item} 从播放列表中移除。",
"repeat": "重复{song} {n}次。",
"report_version": "当前的botamusique版本为<b>{version}</b>。",
"shortlist_instruction": "使用<i>!sl {indexes}</i>播放列表中的曲目。",
"start_updating": "开始更新……",
"stopped": "音乐停止。",
"too_long": "<b>{song}</b>超出长度限制({duration} > {max_duration})!已被移出播放列表。",
"unable_download": "无法下载<b>{item}</b>。已移出播放列表。",
"unable_play": "无法播放<b>{item}</b>。已移出播放列表。",
"unknown_mode": "未知播放模式\"{mode}\"。播放模式应为 <i>one-shot</i>, <i>repeat</i>, <i>random</i>中的一个。",
"update_successful": "<h2>botamusique v{version} 安装完毕!</h2><hr />\n<h3>更新日志</h3> {changelog} <hr /> 请访问我们的 <a href=\"https://github.com/azlux/botamusique\">github页面</a> 获取更多信息!",
"url": "URL",
"url_ban": "链接{url}被列入黑名单了!",
"url_ban_list": "",
"url_ban_success": "",
"url_from_playlist": "URL",
"url_from_playlist_item": "<a href=\"{url}\"><b>{title}</b></a>,来自播放列表 <a href=\"{playlist_url}\">{playlist}</a>,由<i> {user} </i>添加。",
"url_item": "<a href=\"{url}\"><b>{title}</b></a><i>由</i> {user} <i>添加</i>。",
"url_unban_success": "",
"url_unwhitelist_success": "",
"url_whitelist_list": "",
"url_whitelist_success": "",
"user_ban": "你被列入黑名单了!无法操作!",
"user_ban_list": "",
"user_ban_success": "",
"user_password_set": "密码已经被更新。",
"user_unban_success": "",
"web_user_list": "下列用户具有访问网络控制界面的权限:<br /> {users}",
"webpage_address": "网页控制界面的地址是<a href=\"{address}\">{address}</a>。",
"which_command": "你是不是指 <br /> {commands}",
"wrong_pattern": "错误的正则表达式:{error}.",
"yt_no_more": "没有更多条目了!",
"yt_query_error": "无法访问Youtube",
"yt_result": "Youtube查询结果 {result_table} 使用 <i>!sl {{indexes}}</i> 播放列表中的曲目。 <br />\n使用<i>!ytquery -n</i>翻页。"
},
"web": {
"action": "操作",
"add": "添加",
"add_all": "添加全部",
"add_radio": "添加电台",
"add_radio_url": "电台URL",
"add_to_bottom": "添加到最后",
"add_to_bottom_of_current_playlist": "添加到播放列表的末尾",
"add_to_playlist_next": "添加到当前曲目的下一首",
"add_url": "添加URL",
"add_youtube_or_soundcloud_url": "添加Youtube或Soundcloud URL",
"are_you_really_sure": "你真的确定吗?",
"aria_botamusique_logo": "Botamusique的Logo",
"aria_default_cover": "默认专辑封面图片:黑色背景上的两个音符。",
"aria_empty_box": "空。",
"aria_remove_this_song": "从当前播放列表中移除该曲目。",
"aria_skip_current_song": "立刻播放该曲目。",
"aria_skip_to_next_track": "播放下一曲目。",
"aria_spinner": "加载中",
"aria_warning_of_deletion": "删除文件警告",
"autoplay": "自动播放",
"browse_music_file": "浏览音乐文件",
"cancel": "取消",
"cancel_upload_warning": "<strong>你真的确定吗?</strong> <br /> 若要取消上传,请再次点击该按钮。",
"change_playback_mode": "更改播放模式",
"choose_file": "选择文件",
"clear_playlist": "清空播放列表",
"close": "关闭",
"delete_all": "删除全部",
"delete_all_files": "删除全部列出的文件",
"delete_file_warning": "全部列出的文件(包括其他页面上的文件)将被从硬盘上删除。你确定要这样做吗?",
"directory": "目录",
"download_all": "下载全部",
"download_song_from_library": "从库中下载曲目文件",
"edit_submit": "编辑!",
"edit_tags_for": "修改标签:",
"expand_playlist": "查看第 <span class=\"playlist-expand-item-range\"></span> 首曲目。",
"file": "文件",
"filters": "筛选",
"index": "#",
"keywords": "关键词",
"keywords_placeholder": "关键词……",
"mini_player_title": "正在播放……",
"music_library": "音乐库",
"next_to_play": "添加到当前曲目后",
"no_tag": "无标签",
"oneshot": "顺序播放",
"open_volume_controls": "打开音量控制条",
"page_title": "botamusique控制面板",
"pause": "暂停",
"play": "播放",
"playlist_controls": "播放控制",
"radio": "电台",
"radio_url_placeholder": "电台URL……",
"random": "随机播放",
"remove_song_from_library": "从库中移除曲目",
"repeat": "列表循环",
"rescan_files": "重新扫描目录",
"skip_track": "跳过当前曲目",
"submit": "提交",
"tags": "标签",
"tags_to_add": "欲添加的标签",
"title": "标题",
"token": "令牌",
"token_required": "需要登录令牌",
"token_required_message": "你现在在访问{{ name }}的网络控制面板。\n根据设置你需要一个令牌才能登录。<br />\n请发送 \"{{ command }}\" 以获取你的登录令牌。",
"type": "类型",
"upload_file": "上传音乐文件",
"upload_submit": "上传!",
"upload_to": "上传到",
"uploaded_finished": "上传完毕!",
"uploading_files": "上传中……",
"url": "URL",
"url_path": "URL/路径",
"url_placeholder": "URL……",
"volume_slider": "音量控制条"
}
}

View File

@ -1,10 +0,0 @@
[*]
charset = utf-8
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
quote_type = single
[*.json]
quote_type = double

View File

@ -1,40 +0,0 @@
{
"parser": "@babel/eslint-parser",
"env": {
"browser": true,
"es6": true,
"es2017": true,
"es2020": true,
"es2021": true,
"jquery": true
},
"plugins": [
"@babel",
"import",
"jsdoc",
"jquery"
],
"extends": [
"eslint:recommended",
"plugin:import/errors",
"plugin:import/warnings",
"plugin:jsdoc/recommended",
"plugin:jquery/deprecated"
],
"rules": {
"max-len": ["warn", {
"code": 120
}],
"linebreak-style": "off",
"jsdoc/require-jsdoc": "off",
"import/unambiguous": "error",
"import/no-commonjs": "error",
"import/no-amd": "error",
"import/no-nodejs-modules": "error",
"import/no-deprecated": "error",
"import/extensions": ["error", "always"],
"import/no-unresolved": ["error", {
"commonjs": true
}]
}
}

1
web/.gitattributes vendored
View File

@ -1 +0,0 @@
package-lock.json text eol=lf

2
web/.gitignore vendored
View File

@ -1,2 +0,0 @@
!*
node_modules/

View File

@ -1,5 +0,0 @@
{
"plugins": [
"@babel/plugin-proposal-class-properties"
]
}

View File

@ -1,33 +0,0 @@
import {library, dom} from '@fortawesome/fontawesome-svg-core/index.es.js';
import {
faTimesCircle, faPlus, faCheck, faUpload, faTimes, faTrash, faPlay, faPause, faFastForward, faPlayCircle, faLightbulb,
faTrashAlt, faDownload, faSyncAlt, faEdit, faVolumeUp, faVolumeDown, faRobot, faRedo, faRandom, faTasks
} from '@fortawesome/free-solid-svg-icons/index.es.js';
import {faFileAlt} from '@fortawesome/free-regular-svg-icons/index.es.js';
library.add(
// Solid
faTimesCircle, faPlus, faCheck, faUpload, faTimes, faTrash, faPlay, faPause, faFastForward, faPlayCircle, faLightbulb,
faTrashAlt, faDownload, faSyncAlt, faEdit, faVolumeUp, faVolumeDown, faRobot, faRedo, faRandom, faTasks,
// Regular
faFileAlt
);
// Old application code
import './main.mjs';
// New application code
import Theme from './lib/theme.mjs';
document.addEventListener('DOMContentLoaded', () => {
Theme.init();
// Replace any existing <i> tags with <svg> and set up a MutationObserver to
// continue doing this as the DOM changes.
dom.watch();
document.getElementById('theme-switch-btn').addEventListener('click', () => {
Theme.swap();
});
});

View File

@ -1,42 +0,0 @@
import {validateString, validateNumber} from './type.mjs';
/**
* Truncate string length by characters.
*
* @param {string} text String to format.
* @param {number} limit Maximum number of characters in resulting string.
* @param {string} ending Ending to use if string is trucated.
*
* @returns {string} Formatted string.
*/
export function limitChars(text, limit = 50, ending = '...') {
validateString(text);
validateNumber(limit);
validateString(ending);
// Check if string is already below limit
if (text.length <= limit) {
return text;
}
// Limit string length by characters
return text.substring(0, limit - ending.length) + ending;
}
/**
* Truncate string length by words.
*
* @param {string} text String to format.
* @param {number} limit Maximum number of words in resulting string.
* @param {string} ending Ending to use if string is trucated.
*
* @returns {string} Formatted string.
*/
export function limitWords(text, limit = 10, ending = '...') {
validateString(text);
validateNumber(limit);
validateString(ending);
// Limit string length by words
return text.split(' ').splice(0, limit).join(' ') + ending;
}

View File

@ -1,42 +0,0 @@
export default class {
/**
* @property {boolean} dark Interal state for dark theme activation.
* @private
*/
static #dark = false;
/**
* Inialize the theme class.
*/
static init() {
// Check LocalStorage for dark theme selection
if (localStorage.getItem('darkTheme') === 'true') {
// Update page theme
this.set(true);
}
}
/**
* Set page theme and update local storage variable.
*
* @param {boolean} dark Whether to activate dark theme.
*/
static set(dark = false) {
// Swap CSS to selected theme
document.getElementById('pagestyle')
.setAttribute('href', 'static/css/' + (dark ? 'dark' : 'main') + '.css');
// Update local storage
localStorage.setItem('darkTheme', dark);
// Update internal state
this.#dark = dark;
}
/**
* Swap page theme.
*/
static swap() {
this.set(!this.#dark);
}
}

View File

@ -1,65 +0,0 @@
/**
* Checks if `value` is the type `Object` excluding `Function` and `null`
*
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an object, otherwise `false`.
*/
export function isObject(value) {
return (Object.prototype.toString.call(value) === '[object Object]');
}
/**
* Checks if `value` is the type `string`
*
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a string, otherwise `false`.
*/
export function isString(value) {
return (typeof value === 'string');
}
/**
* Checks if `value` is the type `number`
*
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a number, otherwise `false`.
*/
export function isNumber(value) {
return (typeof value === 'number');
}
/**
* Validate parameter is of type object.
*
* @param {string} value Variable to validate.
* @throws Error if not an object.
*/
export function validateObject(value) {
if (!isObject(value)) {
throw new TypeError('Parameter "value" must be of type object.');
}
}
/**
* Validate parameter is of type string.
*
* @param {string} value Variable to validate.
* @throws Error if not an string.
*/
export function validateString(value) {
if (!isString(value)) {
throw new TypeError('Parameter "value" must be of type string.');
}
}
/**
* Validate parameter is of type number.
*
* @param {number} value Variable to validate.
* @throws Error if not an number.
*/
export function validateNumber(value) {
if (!isNumber(value)) {
throw new TypeError('Parameter "value" must be of type number.');
}
}

View File

@ -1,55 +0,0 @@
export function isOverflown(element) {
return element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth;
}
export function hash(string) {
if (typeof string != 'string') return 0;
let hash = 0;
if (string.length === 0) {
return hash;
}
for (let i = 0; i < string.length; i++) {
const char = string.charCodeAt(i);
hash = ((hash<<5)-hash)+char;
hash = hash & hash; // Convert to 32bit integer
}
return hash;
}
export function getColor(string) {
const num = hash(string) % 8;
switch (num) {
case 0:
return 'primary';
case 1:
return 'secondary';
case 2:
return 'success';
case 3:
return 'danger';
case 4:
return 'warning';
case 5:
return 'info';
case 6:
return 'light';
case 7:
return 'dark';
}
}
export function setProgressBar(bar, progress, text = '') {
const progPos = (-1 * (1 - progress) * bar.scrollWidth).toString();
const progStr = (progress * 100).toString();
bar.setAttribute('aria-valuenow', progStr);
bar.style.transform = 'translateX(' + progPos + 'px)';
bar.textContent = text;
}
export function secondsToStr(seconds) {
seconds = Math.floor(seconds);
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return ('00' + mins).slice(-2) + ':' + ('00' + secs).slice(-2);
}

File diff suppressed because it is too large Load Diff

11676
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,53 +0,0 @@
{
"name": "botamusique",
"private": true,
"type": "module",
"scripts": {
"lint": "eslint --config .eslintrc.json js/ --ext .mjs",
"build": "webpack --config webpack.config.cjs --progress",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/azlux/botamusique.git"
},
"author": "azlux",
"license": "MIT",
"bugs": {
"url": "https://github.com/azlux/botamusique/issues"
},
"homepage": "https://github.com/azlux/botamusique#readme",
"devDependencies": {
"@babel/core": "^7.12.9",
"@babel/eslint-parser": "^7.12.1",
"@babel/eslint-plugin": "^7.12.1",
"@babel/plugin-proposal-class-properties": "^7.12.1",
"@babel/preset-env": "^7.12.7",
"autoprefixer": "^10.0.2",
"babel-loader": "^8.2.1",
"core-js": "^3.7.0",
"css-loader": "^5.0.1",
"eslint": "^7.14.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jquery": "^1.5.1",
"eslint-plugin-jsdoc": "^30.7.8",
"html-webpack-plugin": "^4.5.0",
"mini-css-extract-plugin": "^1.3.1",
"postcss-loader": "^7.2.4",
"regenerator-runtime": "^0.13.7",
"sass": "^1.29.0",
"sass-loader": "^10.1.0",
"webpack": "^5.6.0",
"webpack-cli": "^4.2.0"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.32",
"@fortawesome/free-regular-svg-icons": "^5.15.1",
"@fortawesome/free-solid-svg-icons": "^5.15.1",
"bootstrap": "^4.5.3",
"bootswatch": "^4.5.3",
"jquery": "^3.5.1",
"jquery-migrate": "^3.3.2",
"popper.js": "^1.16.1"
}
}

View File

@ -1,5 +0,0 @@
@import '~bootswatch/dist/darkly/variables';
@import '~bootstrap/scss/bootstrap';
@import '~bootswatch/dist/darkly/bootswatch';
@import './main';

View File

@ -1,3 +0,0 @@
@import '~bootstrap/scss/bootstrap';
@import './main';

View File

@ -1,265 +0,0 @@
.btn-space {
margin-right: 5px;
}
/* Playlist */
.playlist-item {
transition: all 0.2s ease-in-out;
}
.playlist-artwork {
float: left;
margin-left: 10px;
white-space: nowrap;
overflow: hidden;
}
.tag-space {
margin-right: 3px;
}
.tag-click {
cursor: pointer;
transition: 400ms;
}
.tag-unclicked {
opacity: 0.6;
}
.tag-clicked {
box-shadow: 2px 4px 10px #777777;
transform: scale(1.2);
opacity: 1;
margin: 5px;
}
.library-item {
display: flex;
padding: .5rem .5rem .5rem 0;
height: 72px;
transition: ease-in-out 200ms;
}
.library-thumb-img {
width: 70px;
height: 70px;
border-radius: 5px;
}
.library-thumb-col {
position: relative;
padding-left: 0;
overflow: hidden;
margin: -0.5rem 1rem -0.5rem 0;
}
.library-thumb-grp {
position: absolute;
top: 0;
left: -95px;
width: 70px;
margin-left: 15px;
transition: left 300ms;
border-radius: 5px;
opacity: 0.7;
font-weight: 300;
}
.library-thumb-grp-hover {
left: -15px;
}
.library-thumb-btn-up {
position: absolute !important;
top: 0;
height: 70px;
font-size: 2em;
padding-top: 10px;
}
.library-btn-svg {
width: 1rem;
fill: currentColor;
}
.library-info-col {
margin-right: 1rem;
padding: 3px 0;
display: flex;
flex-direction: column;
justify-content: center;
white-space: nowrap;
overflow: hidden;
}
.library-info-col .small {
font-weight: 300;
}
.library-action {
margin-left: auto;
}
.library-info-col .path {
font-style: italic !important;
font-weight: 300;
}
/* Theme changer and player button */
.floating-button {
width: 50px;
height: 50px;
background-color: #aaaaaa40;
border-radius: 50%;
box-shadow: 0 6px 10px 0 #66666647;
transition: all 0.1s ease-in-out;
font-size: 25px;
color: #9896967a;
text-align: center;
line-height: 52px;
position: fixed;
right: 50px;
}
.floating-button:hover {
background-color: hsl(0, 0%, 43%);
color: white;
}
#volume-slider {
margin-top: 4px;
margin-right: 5px;
}
.dropdown {
display: inline-block;
}
#volume-popover {
position: relative;
background: #333;
color: white;
font-weight: bold;
padding: 4px 8px;
font-size: 20px;
border-radius: 4px;
display: none;
}
#volume-popover[data-show] {
display: flex;
}
#volume-popover a {
cursor: pointer;
}
#volume-popover-arrow,
#volume-popover-arrow::before {
position: absolute;
width: 10px;
height: 10px;
z-index: -1;
top: 16px;
left: 46px;
}
#volume-popover-arrow::before {
content: '';
transform: rotate(45deg);
background: #333;
}
#volume-popover[data-popper-placement^='top']>#volume-popover-arrow {
bottom: -4px;
}
#playerToast {
position: fixed;
right: 20px;
top: 20px;
max-width: 800px;
}
#playerContainer {
display: flex;
height: 105px;
}
#playerArtwork {
width: 80px;
height: 80px;
border-radius: 5px;
}
#playerArtworkIdle {
width: 80px;
height: 80px;
border-radius: 5px;
margin: auto;
padding: 15px;
}
#playerInfo {
position: relative;
padding-top: 6px;
margin-left: 10px;
height: 80px;
font-size: 15px;
}
#playerTitle {
display: block;
white-space: nowrap;
}
#playerArtist {
display: block;
white-space: nowrap;
min-height: 20px;
}
#playerActionBox {
margin-top: 5px;
display: flex;
float: right;
}
#playerBarBox {
margin-top: 5px;
height: 15px;
width: 400px;
cursor: pointer;
}
.scrolling {
animation: scrolling 8s linear infinite;
}
@keyframes scrolling {
0% {
transform: translateX(100%);
opacity: 1;
}
95% {
transform: translateX(-90%);
opacity: 1;
}
100% {
transform: translateX(-100%);
opacity: 0;
}
}
// Allows us to have H3 with the size of an H5
h3 {
font-size: 1.25rem;
}
// Makes legend match the size of other labels
legend {
font-size: 1rem;
}

View File

@ -1,541 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta charset="UTF-8">
<title>{{ tr('page_title') }}</title>
<link rel="icon" href="static/image/favicon.ico" />
<link id="pagestyle" rel="stylesheet" href="static/css/main.css">
</head>
<body>
<header class="container page-header mb-5" id="banner">
<div class="row">
<div class="col-auto">
<img src="static/image/logo.png" height="200px"
alt="{{ tr('aria_botamusique_logo') }}">
</div>
<div class="col my-auto">
<h1>{{ tr('page_title') }}</h1>
</div>
</div>
</header>
<main id="playlist" class="container mb-5">
<div class="btn-toolbar mb-2" role="toolbar" aria-label="{{ tr('playlist_controls') }}">
<button type="button" id="play-pause-btn" class="btn btn-info mb-2 btn-space" aria-label="{{ tr('play') }}">
<i class="fas fa-play"></i>
</button>
<button type="button" id="fast-forward-btn" class="btn btn-info mb-2" aria-label="{{ tr('skip_track') }}">
<i class="fas fa-fast-forward"></i>
</button>
<div class="ml-auto">
<div class="dropdown mr-2">
<button class="btn btn-secondary dropdown-toggle" type="button" id="play-mode"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
aria-label="{{ tr('change_playback_mode') }}">
<i class="fas fa-tasks mr-2" aria-hidden="true" id="modeIndicator"></i>
</button>
<div class="dropdown-menu" aria-labelledby="play-mode">
<a class="dropdown-item" href="#" id="one-shot-mode-btn">
<i class="fas fa-tasks mr-2" aria-hidden="true"></i> {{ tr('oneshot') }}
</a>
<a class="dropdown-item" href="#" id="random-mode-btn">
<i class="fas fa-random mr-2" aria-hidden="true"></i> {{ tr('random') }}
</a>
<a class="dropdown-item" href="#" id="repeat-mode-btn">
<i class="fas fa-redo mr-2" aria-hidden="true"></i> {{ tr('repeat') }}
</a>
<a class="dropdown-item" href="#" id="autoplay-mode-btn">
<i class="fas fa-robot mr-2" aria-hidden="true"></i> {{ tr('autoplay') }}
</a>
</div>
</div>
<button type="button" id="volume-popover-btn" class="btn btn-warning ml-1"
aria-label="{{ tr('open_volume_controls') }}">
<i class="fa fa-volume-up" aria-hidden="true"></i>
</button>
<div id="volume-popover">
<a id="volume-down-btn">
<i class="fa fa-volume-down" aria-hidden="true"></i>
</a>
<input type="range" class="custom-range ml-1" id="volume-slider" min="0" max="1" step="0.01"
value="0.5" aria-label="{{ tr('volume_slider') }}" />
<a id="volume-up-btn">
<i class="fa fa-volume-up" aria-hidden="true"></i>
</a>
<div id="volume-popover-arrow"></div>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th scope="col" class="d-none d-md-table-cell">{{ tr('index') }}</th>
<th scope="col" class="w-50">{{ tr('title') }}</th>
<th scope="col" class="d-none d-md-table-cell">{{ tr('url_path') }}</th>
<th scope="col">{{ tr('action') }}</th>
</tr>
</thead>
<tbody id="playlist-table" class="playlist-table">
<tr id="playlist-loading">
<td colspan="4" class="text-center">
<img style="margin: auto; width: 35px;" src="static/image/loading.svg"
alt="{{ tr('aria_spinner') }}" />
</td>
</tr>
<tr id="playlist-empty" class="d-none">
<td colspan="4" class="text-center">
<img style="margin: auto; width: 35px;" src="static/image/empty_box.svg"
alt="{{ tr('aria_empty_box') }}" />
</td>
</tr>
<tr class="playlist-expand table-dark d-none">
<td colspan="4" class="text-center">
<a class="text-muted" href="javascript:">{{ tr('expand_playlist') }}</a>
</td>
</tr>
<tr class="playlist-item-template d-none" aria-hidden="true">
<th scope="row" class="playlist-item-index d-none d-md-table-cell">1</th>
<td>
<input hidden type="hidden" class="playlist-item-id" value="" />
<div class="float-left">
<img width="80" class="playlist-item-thumbnail" src="static/image/unknown-album.png"
alt="{{ tr('aria_default_cover') }}" />
</div>
<div class="playlist-artwork">
<b class="playlist-item-title"></b>
<span class="playlist-item-type badge badge-secondary"></span>
<br />
<span class="playlist-item-artist"></span>
<br />
<div class="playlist-item-tags">
<a class="playlist-item-edit tag-space tag-click">
<i class="fas fa-edit" style="color: #AAAAAA"></i>
</a>
</div>
</div>
</td>
<td class="d-none d-md-table-cell">
<small class="playlist-item-path"></small>
</td>
<td>
<div class="btn-group">
<button type="button" class="playlist-item-play btn btn-info btn-sm"
aria-label="{{ tr('aria_skip_current_song') }}">
<i class="fas fa-play" aria-hidden="true"></i>
</button>
<button type="button" class="playlist-item-trash btn btn-danger btn-sm ml-1"
aria-label="{{ tr('aria_remove_this_song') }}">
<i class="fas fa-trash-alt" aria-hidden="true"></i>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="btn-group">
<button type="button" id="clear-playlist-btn" class="btn btn-danger mr-1">
<i class="fas fa-trash-alt" aria-hidden="true"></i> {{ tr('clear_playlist') }}</button>
</div>
</main>
<div class="container mb-3">
<h2 id="forms">{{ tr('music_library') }}</h2>
<div class="card mb-3">
<div class="card-header">
<h3 class="card-title">{{ tr('filters') }}</h3>
<hr>
<div class="row">
<div class="col">
<fieldset id="filter-type" class="mb-2">
<legend>{{ tr('type') }}</legend>
<div class="btn-group btn-group-sm btn-group-toggle">
<label id="filter-type-file" class="btn btn-secondary">
<input type="checkbox" name="options">{{ tr('file') }}
</label>
<label id="filter-type-url" class="btn btn-secondary">
<input type="checkbox" name="options">{{ tr('url') }}
</label>
<label id="filter-type-radio" class="btn btn-secondary">
<input type="checkbox" name="options">{{ tr('radio') }}
</label>
</div>
</fieldset>
<label for="filter-dir">{{ tr('directory') }}</label>
<div id="filter-path" class="input-group mb-2">
<select class="form-control form-control-sm" id="filter-dir" disabled>
</select>
</div>
<label for="filter-keywords">{{ tr('keywords') }}</label>
<div id="filter-path" class="input-group mb-2">
<input class="form-control form-control-sm" id="filter-keywords" name="keywords"
placeholder="{{ tr('keywords_placeholder') }}" style="margin-top:5px;" />
</div>
</div>
<div class="col">
<fieldset id="filter-tags">
<legend>{{ tr('tags') }}</legend>
<span class="filter-tag tag-unclicked tag-click badge"></span>
</fieldset>
</div>
</div>
</div>
<div class="card-body">
<div id="library-group" class="list-group library-group" style="overflow: auto;">
<input type="hidden" id="deleteAllowed" value="true" />
<div id="library-item-loading" class="list-group-item library-item">
<img style="margin: auto; width: 35px;" src="static/image/loading.svg"
alt="{{ tr('aria_spinner') }}" />
</div>
<div id="library-item-empty" style="display: none" class="list-group-item library-item">
<img style="margin: auto; width: 35px;" src="static/image/empty_box.svg"
alt="{{ tr('aria_empty_box') }}" />
</div>
<div id="library-item" style="display: none;" class="list-group-item library-item">
<input hidden type="hidden" class="library-item-id" value="" />
<div class="library-thumb-col">
<div class="library-thumb-img">
<img class="library-item-thumb library-thumb-img" src="static/image/unknown-album.png"
alt="{{ tr('aria_default_cover') }}" />
</div>
<div class="btn-group-vertical library-thumb-grp">
<div class="library-item-play btn btn-secondary library-thumb-btn-up" title="{{ tr('play') }}">
<i class="fas fa-play" aria-hidden="true"></i>
</div>
</div>
</div>
<div class="library-info-col library-info-title col-5" style="padding: 12px 0;">
<div>
<span class="library-item-type lead text-muted btn-space">[File]</span>
<span class="library-item-title lead btn-space">This is my title</span>
<span class="library-item-artist text-muted"> - Artist</span>
</div>
</div>
<div class="library-info-col col-4 d-none d-md-flex" style="padding: 3px;">
<span class="library-item-path text-muted path">Path/to/the/file</span>
<div class="library-item-tags">
<a class="tag-space tag-click library-item-edit"><i class="fas fa-edit"
style="color: #AAAAAA"></i></a>
<span class="library-item-notag badge badge-light text-muted font-italic">{{ tr('no_tag') }}</span>
<span class="library-item-tag tag-space badge">Tag</span>
</div>
</div>
<div class="btn-group library-action">
<button class="library-item-add-next btn btn-info btn-sm btn-space" type="button"
title="{{ tr('next_to_play') }}" aria-label="{{ tr('add_to_playlist_next') }}">
<svg class="library-btn-svg" style="width: 1rem; fill: currentColor;"
viewBox="5 5 17 17">
<path d="m5.700245,3.92964l0,14.150376l11.451127,-7.075188l-11.451127,-7.075188z">
</path>
<path
d="m20.942859,18.221072l-3.323292,0l0,3.323292l-1.107764,0l0,-3.323292l-3.323292,0l0,-1.107764l3.323292,0l0,-3.323292l1.107764,0l0,3.323292l3.323292,0l0,1.107764z">
</path>
</svg>
</button>
<button class="library-item-add-bottom library-btn btn btn-info btn-sm btn-space"
type="button" title="{{ tr('add_to_bottom') }}" aria-label="{{ tr('add_to_bottom_of_current_playlist') }}">
<svg class="library-btn-svg" style="width: 1rem; fill: currentColor;"
viewBox="2 2 20 20">
<path
d="M2,16H10V14H2M18,14V10H16V14H12V16H16V20H18V16H22V14M14,6H2V8H14M14,10H2V12H14V10Z">
</path>
</svg>
</button>
<button class="library-item-download btn btn-primary btn-sm btn-space" type="button"
aria-label="{{ tr('download_song_from_library') }}">
<i class="fas fa-download" aria-hidden="true"></i>
</button>
<button class="library-item-trash btn btn-danger btn-sm btn-space library-delete" type="button"
aria-label="{{ tr('remove_song_from_library') }}">
<i class="fas fa-trash-alt" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
<div class="list-group">
<div id="library-pagination" style="margin-left: auto; margin-top: 10px;">
<ul id="library-page-ul" class="pagination pagination">
<li class="library-page-li page-item ctive">
<a class="library-page-no page-link">1</a>
</li>
</ul>
</div>
</div>
<div class="btn-group mb-2" role="group">
<button id="add-to-playlist-btn" type="button" class="btn btn-secondary mr-1">
<i class="fas fa-plus" aria-hidden="true"></i>{{ tr('add_all') }}
</button>
<button id="library-rescan-btn" type="button" class="btn btn-secondary mr-1">
<i class="fas fa-sync-alt" aria-hidden="true"></i>{{ tr('rescan_files') }}
</button>
<button id="library-download-btn" type="button" class="btn btn-secondary mr-1">
<i class="fas fa-download" aria-hidden="true"></i>{{ tr('download_all') }}
</button>
<button type="button" class="btn btn-danger mr-1 library-delete" data-toggle="modal"
data-target="#deleteWarningModal">
<i class="fas fa-trash-alt" aria-hidden="true"></i>{{ tr('delete_all') }}
</button>
</div>
<div class="modal fade" id="deleteWarningModal" tabindex="-1" role="dialog"
aria-label="{{ tr('aria_warning_of_deletion') }}" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title" id="deleteWarningModalLabel">{{ tr('are_you_really_sure') }}</h3>
<button type="button" class="close" data-dismiss="modal" aria-label="{{ tr('close') }}">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
{{ tr('delete_file_warning') }}</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ tr('close') }}</button>
<button id="library-delete-btn" type="button" class="btn btn-danger"
data-dismiss="modal">{{ tr('delete_all_files') }}</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="upload" class="container mb-3">
<input type="hidden" id="uploadDisabled" value="false" />
<div class="card">
<div class="card-header">
<h3 class="card-title">{{ tr('upload_file') }}</h3>
</div>
<div class="card-body">
<form action="./upload" method="post" enctype="multipart/form-data">
<div class="row">
<div id="uploadBox" class="col-lg-7">
<div class="input-group mb-3">
<div id="uploadField" style="display: flex; width: 100%">
<div class="custom-file">
<input type="file" name="file[]" class="custom-file-input" id="uploadSelectFile" aria-describedby="uploadSubmit" value="{{ tr('browse_music_file') }}" multiple />
<label class="custom-file-label" for="uploadSelectFile">{{ tr('choose_file') }}</label>
</div>
</div>
</div>
</div>
<div class="col-lg-5">
<div class="row">
<div class="col">
<div class="input-group mb-3">
<div class="input-group-prepend">
<label for="uploadTargetDir" class="input-group-text">{{ tr('upload_to') }}</label>
</div>
<input class="form-control" list="upload-target-dirs" id="uploadTargetDir" name="upload-target-dirs" placeholder="uploads" />
<datalist id="upload-target-dirs">
</datalist>
</div>
</div>
<div class="col-auto">
<button class="btn btn-primary" type="button" id="uploadSubmit"><i class="fas fa-upload mr-1"></i>{{ tr('upload_submit') }}</button>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="container mb-5">
<div class="card-deck">
<div id="add-music-url" class="card">
<div class="card-header">
<h3 class="card-title">{{ tr('add_url') }}</h3>
</div>
<div class="card-body">
<label for="music-url-input">{{ tr('add_youtube_or_soundcloud_url') }}</label>
<div class="input-group mb-2">
<input class="form-control" type="text" id="music-url-input" placeholder="{{ tr('url_placeholder') }}">
</div>
<button type="submit" class="btn btn-primary">
{{ tr('add_url') }}
</button>
</div>
</div>
<div id="add-radio-url" class="card">
<div class="card-header">
<h3 class="card-title">{{ tr('add_radio') }}</h3>
</div>
<div class="card-body">
<label for="radio-url-input">{{ tr('add_radio_url') }}</label>
<div class="input-group mb-2">
<input id="radio-url-input" class="form-control" type="text" placeholder="{{ tr('radio_url_placeholder') }}">
</div>
<button type="submit" class="btn btn-primary">
{{ tr('add_radio') }}
</button>
</div>
</div>
</div>
</div>
<div id="player-toast" class="floating-button" style="bottom: 120px;">
<i class="fas fa-play" aria-hidden="true"></i>
</div>
<div id="theme-switch-btn" class="floating-button" style="bottom: 50px;">
<i class="fas fa-lightbulb" aria-hidden="true"></i>
</div>
<div id="playerToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<i class="fas fa-play-circle mr-2 text-primary"></i>
<strong class="mr-auto">{{ tr('mini_player_title') }}</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body" id="playerContainer">
<img id="playerArtworkIdle" src="static/image/empty_box.svg" alt="{{ tr('aria_empty_box') }}" />
<img id="playerArtwork" src="static/image/unknown-album.png" style="display: none;"
alt="{{ tr('aria_default_cover') }}" />
<div id="playerInfo">
<div id="playerActionBox">
<button id="playerPlayBtn" class="btn btn-primary btn-sm btn-space" style="display: none"
aria-label="{{ tr('play') }}">
<i class="fas fa-play"></i>
</button>
<button id="playerPauseBtn" class="btn btn-primary btn-sm btn-space" style="display: none"
aria-label="{{ tr('pause') }}">
<i class="fas fa-pause"></i>
</button>
<button id="playerSkipBtn" class="btn btn-primary btn-sm" aria-label="{{ tr('aria_skip_to_next_track') }}">
<i class="fas fa-fast-forward"></i>
</button>
</div>
<div style="overflow: hidden; max-width: 320px;">
<strong id="playerTitle">Song Title</strong>
</div>
<span id="playerArtist">Artist</span>
<div id="playerBarBox" class="progress">
<div id="playerBar" class="progress-bar pr-2" role="progressbar" aria-valuenow="50"
aria-valuemin="0" aria-valuemax="100"
style="width: 100%; text-align: right; transform: translateX(-100%);"></div>
</div>
</div>
</div>
</div>
<div id="footer" style="height:50px; width: 100%; margin-top: 100px;"></div>
<form id="download-form" action="download" method="GET" target="_blank">
<input hidden type="hidden" name="id" value="">
<input hidden type="hidden" name="type" value="">
<input hidden type="hidden" name="dir" value="">
<input hidden type="hidden" name="tags" value="">
<input hidden type="hidden" name="keywords" value="">
</form>
<!-- Add tags modal -->
<div class="modal fade" id="addTagModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">{{ tr('edit_tags_for') }} <span id="addTagModalTitle">?</span></h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div id="addTagModalBody" class="modal-body">
<input hidden type="hidden" id="addTagModalItemId" name="id" value="">
<div class="modal-tag" style="display: none; width: 100%;">
<span class="modal-tag-text tag-space badge badge-pill badge-dark">Tag</span>
<a class="modal-tag-remove tag-click small"><i
class="fas fa-times-circle btn-outline-danger"></i></a>
</div>
<div id="addTagModalTags" style="margin-left: 5px; margin-bottom: 10px;">
</div>
<div class="input-group">
<input class="form-control form-control-sm btn-space" type="text" id="addTagModalInput"
placeholder="tag1,tag2,..." aria-label="{{ tr('tags_to_add') }}">
<button id="addTagModalAddBtn" type="button" class="btn btn-primary btn-sm">
<i class="fas fa-plus" aria-hidden="true"></i>
{{ tr('add') }}
</button>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ tr('close') }}</button>
<button id="addTagModalSubmit" type="button" class="btn btn-success"
data-dismiss="modal">{{ tr('edit_submit') }}</button>
</div>
</div>
</div>
</div>
<!-- Upload files modal -->
<div class="modal fade" id="uploadModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title" id="uploadTitle"><i class="fas fa-upload mr-1"></i>{{ tr('uploading_files') }}</h3>
</div>
<div id="uploadModalBody" class="modal-body">
<div id="uploadSuccessAlert" class="alert alert-success" role="alert" style="display: none">
<i class="fas fa-check mr-1"></i>
{{ tr('uploaded_finished') }}
</div>
<div id="uploadModalList" style="margin-left: 5px; margin-bottom: 10px;">
<div class="uploadItem" style="display: none; width: 100%; padding-bottom: 8px;">
<i class="far fa-file-alt mr-1"></i>
<span class="uploadItemTitle mr-3"></span>
<span class="uploadItemError text-danger"></span>
<div class="progress" style="margin-top: 5px; height: 10px;">
<div class="uploadProgress progress-bar pr-2" role="progressbar" aria-valuenow="0"
aria-valuemin="0" aria-valuemax="100"
style="width: 100%; text-align: right; transform: translateX(-100%);"></div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" id="uploadClose" class="btn btn-success" data-dismiss="modal">
<i class="fas fa-times mr-1"></i> {{ tr('close') }}</button>
<button type="button" id="uploadCancel" class="btn btn-danger" data-toggle="tooltip"
data-html="true"
title="{{ tr('cancel_upload_warning') }}">
<i class="fas fa-trash-alt mr-1" aria-hidden="true"></i> {{ tr('cancel') }}</button>
</div>
</div>
</div>
</div>
<input type="hidden" id="maxUploadFileSize" value="" />
<script src="static/js/main.js"></script>
</body>
</html>

View File

@ -1,41 +0,0 @@
<!DOCTYPE html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta charset="UTF-8">
<title>{{ tr('page_title') }}</title>
<link rel="icon" href="static/image/favicon.ico" />
<link rel="stylesheet" href="static/css/main.css">
</head>
<body>
<div class="container" style="max-width: 800px">
<div class="col-8" style="margin: auto; padding-top: 50px;">
<div class="card">
<div class="card-header">
{{ tr('token_required') }}
</div>
<div class="card-body">
<h3>{{ tr('token_required') }}</h3>
{{ tr('token_required_message') }}
<form action="." method="get">
<div class="form-group mt-3">
<label for="token_input">{{ tr('token') }}</label>
<div class="input-group">
<input type="password" class="form-control btn-space" id="token_input" name="token" placeholder="xxxxxxx">
<button type="submit" class="btn btn-primary">{{ tr('submit') }}</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<script src="static/js/main.js"></script>
</body>
</html>

View File

@ -1,7 +0,0 @@
{
"parserOptions": {
"babelOptions": {
"configFile": "./web/babel.config.json"
}
}
}

View File

@ -1,83 +0,0 @@
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'production',
devtool: 'source-map',
entry: {
main: [
'./js/app.mjs',
'./sass/app.scss',
],
dark: [
'./sass/app-dark.scss',
],
},
output: {
filename: 'static/js/[name].js',
path: path.resolve(__dirname, '../'),
},
plugins: [
new MiniCssExtractPlugin({
filename: 'static/css/[name].css',
}),
new HtmlWebpackPlugin({
filename: 'templates/index.template.html',
template: './templates/index.template.html',
inject: false,
}),
new HtmlWebpackPlugin({
filename: 'templates/need_token.template.html',
template: './templates/need_token.template.html',
inject: false,
}),
],
module: {
rules: [{
test: /\.s[ac]ss$/i,
use: [
MiniCssExtractPlugin.loader,
'css-loader', // translates CSS into CommonJS modules
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
[
'autoprefixer',
{
// Options
},
],
],
},
},
},
'sass-loader', // compiles Sass to CSS
],
},
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
resolve: {
fullySpecified: false,
},
use: {
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
'corejs': '3.6',
'useBuiltIns': 'usage',
},
],
],
},
},
},
],
},
};