Initial commit of Bragi fork.
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@ -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/
|
||||
|
769
interface.py
769
interface.py
@ -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")
|
173
lang/de_DE.json
173
lang/de_DE.json
@ -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": ""
|
||||
}
|
||||
}
|
173
lang/es_ES.json
173
lang/es_ES.json
@ -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"
|
||||
}
|
||||
}
|
173
lang/fr_FR.json
173
lang/fr_FR.json
@ -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"
|
||||
}
|
||||
}
|
173
lang/it_IT.json
173
lang/it_IT.json
@ -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"
|
||||
}
|
||||
}
|
173
lang/ja_JP.json
173
lang/ja_JP.json
@ -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": "音量スライダー"
|
||||
}
|
||||
}
|
173
lang/nl_NL.json
173
lang/nl_NL.json
@ -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": ""
|
||||
}
|
||||
}
|
173
lang/pt_BR.json
173
lang/pt_BR.json
@ -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"
|
||||
}
|
||||
}
|
173
lang/zh_CN.json
173
lang/zh_CN.json
@ -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": "音量控制条"
|
||||
}
|
||||
}
|
@ -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
|
@ -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
1
web/.gitattributes
vendored
@ -1 +0,0 @@
|
||||
package-lock.json text eol=lf
|
2
web/.gitignore
vendored
2
web/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
!*
|
||||
node_modules/
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"plugins": [
|
||||
"@babel/plugin-proposal-class-properties"
|
||||
]
|
||||
}
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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.');
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
1300
web/js/main.mjs
1300
web/js/main.mjs
File diff suppressed because it is too large
Load Diff
11676
web/package-lock.json
generated
11676
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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"
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
@import '~bootswatch/dist/darkly/variables';
|
||||
@import '~bootstrap/scss/bootstrap';
|
||||
@import '~bootswatch/dist/darkly/bootswatch';
|
||||
|
||||
@import './main';
|
@ -1,3 +0,0 @@
|
||||
@import '~bootstrap/scss/bootstrap';
|
||||
|
||||
@import './main';
|
@ -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;
|
||||
}
|
@ -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">×</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">×</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">×</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>
|
@ -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>
|
@ -1,7 +0,0 @@
|
||||
{
|
||||
"parserOptions": {
|
||||
"babelOptions": {
|
||||
"configFile": "./web/babel.config.json"
|
||||
}
|
||||
}
|
||||
}
|
@ -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',
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
Reference in New Issue
Block a user