From b1e21af243a0e2706a61bedf15afd7b4746c3333 Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Fri, 13 Jun 2025 18:52:37 -0400 Subject: [PATCH] Initial commit of Bragi fork. --- .gitignore | 5 +- interface.py | 769 -- lang/de_DE.json | 173 - lang/es_ES.json | 173 - lang/fr_FR.json | 173 - lang/it_IT.json | 173 - lang/ja_JP.json | 173 - lang/nl_NL.json | 173 - lang/pt_BR.json | 173 - lang/zh_CN.json | 173 - web/.editorconfig | 10 - web/.eslintrc.json | 40 - web/.gitattributes | 1 - web/.gitignore | 2 - web/babel.config.json | 5 - web/js/app.mjs | 33 - web/js/lib/text.mjs | 42 - web/js/lib/theme.mjs | 42 - web/js/lib/type.mjs | 65 - web/js/lib/util.mjs | 55 - web/js/main.mjs | 1300 --- web/package-lock.json | 11676 ----------------------- web/package.json | 53 - web/sass/app-dark.scss | 5 - web/sass/app.scss | 3 - web/sass/main.scss | 265 - web/templates/index.template.html | 541 -- web/templates/need_token.template.html | 41 - web/vscode.eslintrc.json | 7 - web/webpack.config.cjs | 83 - 30 files changed, 1 insertion(+), 16426 deletions(-) delete mode 100644 interface.py delete mode 100644 lang/de_DE.json delete mode 100644 lang/es_ES.json delete mode 100644 lang/fr_FR.json delete mode 100644 lang/it_IT.json delete mode 100644 lang/ja_JP.json delete mode 100644 lang/nl_NL.json delete mode 100644 lang/pt_BR.json delete mode 100644 lang/zh_CN.json delete mode 100644 web/.editorconfig delete mode 100644 web/.eslintrc.json delete mode 100644 web/.gitattributes delete mode 100644 web/.gitignore delete mode 100644 web/babel.config.json delete mode 100644 web/js/app.mjs delete mode 100644 web/js/lib/text.mjs delete mode 100644 web/js/lib/theme.mjs delete mode 100644 web/js/lib/type.mjs delete mode 100644 web/js/lib/util.mjs delete mode 100644 web/js/main.mjs delete mode 100644 web/package-lock.json delete mode 100644 web/package.json delete mode 100644 web/sass/app-dark.scss delete mode 100644 web/sass/app.scss delete mode 100644 web/sass/main.scss delete mode 100644 web/templates/index.template.html delete mode 100644 web/templates/need_token.template.html delete mode 100644 web/vscode.eslintrc.json delete mode 100644 web/webpack.config.cjs diff --git a/.gitignore b/.gitignore index 54ac0c2..000398b 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/interface.py b/interface.py deleted file mode 100644 index 0dec6a4..0000000 --- a/interface.py +++ /dev/null @@ -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" {item.url}" - duration = item.duration - elif isinstance(item, PlaylistURLItem): - path = f" {item.url}" - artist = f" {item.playlist_title}" - duration = item.duration - elif isinstance(item, RadioItem): - path = f" {item.url}" - - 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") diff --git a/lang/de_DE.json b/lang/de_DE.json deleted file mode 100644 index c5560a4..0000000 --- a/lang/de_DE.json +++ /dev/null @@ -1,173 +0,0 @@ -{ - "cli": { - "added_tags": "{song} wurde mit {tags} verschlagwortet.", - "added_tags_to_all": "Alle Lieder in der Playlist wurden mit {tags} verschlagwortet.", - "admin_help": "

Adminbefehle

\nBot\n\nWebinterface\n", - "auto_paused": "Sende !play, um die Wiedergabe fortzusetzen!", - "bad_command": "{command}: Befehl nicht verfügbar. Sende !help, um dir alle möglichen Befehle anzuzeigen.", - "bad_parameter": "{command}: 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 {mode} gesetzt.", - "change_volume": "Lautstärke wurde von {user} auf {volume} gesetzt.", - "cleared": "Playlist wurde geleert.", - "cleared_tags": "Alle Tags wurden von {song} 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: {mode}", - "current_volume": "Aktuelle Lautstärke: {volume}.", - "database_dropped": "Datenbank gelöscht. Alle Einträge wurde gelöscht.", - "download_in_progress": "{item} 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": "{artist} - {title} wurde von {user} hinzugefügt. ", - "file_missed": "Datei {file} nicht gefunden. Das Element wurde aus der Playlist entfernt.", - "help": "", - "invalid_index": "{index} ist ein ungültiger Index. Sende !queue, 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": "

Update verfügbar!

Version {new_version} von botamusique ist verfügbar!
\n

Changelog

\n{changelog}
Sende !update, 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 !{command} {{page}}, 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 !sl {indexes}, 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 !sl {{indexes}}, um das gewünscht Element abzuspielen.\n!ytquery -n, 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": "" - } -} \ No newline at end of file diff --git a/lang/es_ES.json b/lang/es_ES.json deleted file mode 100644 index 2ca1a46..0000000 --- a/lang/es_ES.json +++ /dev/null @@ -1,173 +0,0 @@ -{ - "cli": { - "added_tags": "Etiquetas {tags} fueron añadidas a {song}.", - "added_tags_to_all": "Etiquetas {tags} fueron añadidas a las canciones en la lista de reproducción.", - "admin_help": "

Comandos de administrador

\nBot\n\nInterfaz Web\n", - "auto_paused": "Usa !play para continuar la reproducción!", - "bad_command": "{command}: comando no encontrado.", - "bad_parameter": "{command}: 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 {mode} 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 {song}.", - "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 {mode}.", - "current_volume": "Volumen actual: {volume}.", - "database_dropped": "Base de datos descartada. Todos los registros se han ido.", - "download_in_progress": "Descarga de {item} 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": "{artist} - {title} añadido por {user}", - "file_missed": "Fichero de música '{file}' no encontrado! Este ítem ha sido eliminado de la lista de reproducción.", - "help": "

Comandos

\nControl\n\nLista de Reproducción\n\nBiblioteca Musical\n\nOtros\n", - "invalid_index": "Índice {index} 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": "

Actualización disponible!

La versión {new_version} de botamusique está disponible!
\n

Lista de cambios:

{changelog}
Envía !update 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 !{command} {{page}} 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": "{title} de {name} añadido por {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 !rbplay {ID} para reproducir una estación:", - "records_omitted": "...", - "removed_tags": "Eliminadas las etiquetas {tags} de {song}.", - "removed_tags_from_all": "Eliminadas las etiquetas {tags} 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 {version}.", - "shortlist_instruction": "Use !sl {índices} para reproducir los elementos que usted desea.", - "start_updating": "Empezando la actualización...", - "stopped": "Música fue detenida.", - "too_long": "{song} es muy larga ({duration} > {max_duration}). Eliminada de la lista de reproducción!", - "unable_download": "No fue posible descargar {item}. Eliminado de la biblioteca.", - "unable_play": "No fue posible reproducir {item}. Eliminado de la biblioteca.", - "unknown_mode": "Modo de reproducción '{mode}' desconocido. Debiera ser o bien one-shot, repeat o random.", - "update_successful": "

botamusique v{version} instalado!


\n

Lista de cambios

{changelog}
Visite nuestro repositorio en Github 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": "{title} de lista de reproducción {playlist} añadido por {user}", - "url_item": "{title} añadido por {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:
{users}", - "webpage_address": "Tu dirección web para acceder a la interfaz es {address}", - "which_command": "Quieres decir
{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 !sl {{índices}} para reproducir el elemento que usted desea.
\n!ytquery -n 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": "¿Está realmente seguro?
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 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.
\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" - } -} \ No newline at end of file diff --git a/lang/fr_FR.json b/lang/fr_FR.json deleted file mode 100644 index e13ce26..0000000 --- a/lang/fr_FR.json +++ /dev/null @@ -1,173 +0,0 @@ -{ - "cli": { - "added_tags": "Tags {tags} ajoutés à {song}.", - "added_tags_to_all": "Tags {tags} ajoutés aux musiques de la playlist.", - "admin_help": "

Commandes Admin

\nBot\n\nInterface Web\n", - "auto_paused": "!play 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 {mode} par {user}.", - "change_volume": "Volume réglé sur {volume} par {user}.", - "cleared": "Playlist vidée.", - "cleared_tags": "Suppression de tous les tag de {song}.", - "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 {mode}.", - "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 {item} 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": "{artist} - {title} ajouté par {user}", - "file_missed": "Fichier audio '{file}' introuvable! Cet élément a été supprimé de la playlist.", - "help": "

Commandes

\nControl\n\nPlayist\n\nBibliothèque musicale\n\nAutre\n", - "invalid_index": "Index non valide {index}. 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": "

Mise à jour disponible!

La version {new_version} de botamusique est disponible !
\n

Changelog

{changelog}
Envoyer !update 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 !{command} {{page}} 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": "{title} from {name} ajouté par {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 {tags} de {song}.", - "removed_tags_from_all": "Suppression des tags {tags} 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 {version}{/b}.", - "shortlist_instruction": "Utilisez !sl {indexes} pour jouer l'élément que vous voulez.", - "start_updating": "Début de la mise à jour...", - "stopped": "Musique arrêté.", - "too_long": "{song} est trop long ({duration} > {max_duration}), supprimé de la playlist !", - "unable_download": "Impossible de télécharger {item}. Retiré de la bibliothèque.", - "unable_play": "Impossible de jouer {item}. Retiré de la bibliothèque.", - "unknown_mode": "Mode de lecture \"{mode}\" inconnu. Il devrait s'agir d'un des modes suivants : one-shot, repeat, random.", - "update_successful": "

botamusique v{version} Installé !


\n

Changelog

{changelog}
Visitez notre repo github pour plus de détails !", - "url": "URL", - "url_ban": "URL {url} est interdite !", - "url_ban_list": "Liste des URL bannies:
{list=", - "url_ban_success": "L'URL suivante est interdite: {url}", - "url_from_playlist": "URL", - "url_from_playlist_item": "{title} depuis la playlist {playlist} ajouté par {user}", - "url_item": "{title} ajouté par {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:
{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:
{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 :
{users}", - "webpage_address": "Votre propre adresse pour accéder à l'interface web est {address}", - "which_command": "Voulez-vous dire
{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 !sl {{indexes}} pour jouer l'entrée que vous voulez.
\n!ytquery -n 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": "Etes-vous vraiment sûr ?
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 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.
\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" - } -} \ No newline at end of file diff --git a/lang/it_IT.json b/lang/it_IT.json deleted file mode 100644 index b9d767c..0000000 --- a/lang/it_IT.json +++ /dev/null @@ -1,173 +0,0 @@ -{ - "cli": { - "added_tags": "Tag {tags} aggiunti a {song}.", - "added_tags_to_all": "I tag {tags} sono stati aggiunti ai brani nella playlist.", - "admin_help": "

Comandi amministratore

\nBot\n
    \n
  • !kill - Termina il bot.
  • \n
  • !update - Aggiorna il bot.
  • \n
  • !userban {user} - Banna utente.
  • \n
  • !userunban {user} - Sbanna utente.
  • \n
  • !urlbanlist - Elenco URL vietati.
  • \n
  • !urlban [{url}] - Banna {url} (o URL dell'elemento corrente come impostazione predefinita) e rimuovi questo URL dalla libreria.
  • \n
  • !urlunban {url} - Sbanna {url}.
  • \n
  • !rescan {url} - Ricostruisce la cache dei file musicali locali.
  • \n
  • !dropdatabase - Cancella l'intero database, perderai tutte le impostazioni e la libreria musicale.
  • \n
\nInterfaccia Web\n
    \n
  • !webuserlist - Elenca tutti gli utenti che hanno il permesso di accedere all'interfaccia web, se la modalità di autenticazione è 'password'.
  • \n
  • !webuseradd {nick name} - Concedi all'utente con {nick name} l'accesso all'interfaccia web, se la modalità di autenticazione è 'password'.
  • \n
  • !webuserdel {nick name} - Revoca l'accesso all'interfaccia web di {nick name}, se la modalità di autenticazione è 'password'.
  • \n
\"", - "auto_paused": "Usa !play per riprendere la musica!", - "bad_command": "{command}: comando non trovato.", - "bad_parameter": "{command}: 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 {mode} da {user}.", - "change_volume": "Volume impostato a {volume} da {user}.", - "cleared": "Playlist svuotata.", - "cleared_tags": "Rimossi tutti i tag da {song}.", - "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: {mode}.", - "current_volume": "Volume attuale: {volume}.", - "database_dropped": "Database eliminato. Tutti i dati sono andati.", - "download_in_progress": "Scaricamento di {item} 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": "{artist} - {title} aggiunto da {user}", - "file_missed": "File musicale \"{file}\" mancante! Questo elemento è stato rimosso dalla playlist.", - "help": "

Comandi

\nControllo\n
    \n
  • !web - ottenere l'URL dell'interfaccia web, se abilitata.
  • \n
  • !play (or !p) [{num}] [{start_from}] - Riprende dalla pausa / avvia la riproduzione (dal numero {num} se fornito).
  • \n
  • !pause - Pausa.
  • \n
  • !stop - Arresta riproduzione.
  • \n
  • !skip - Passa al brano successivo.
  • \n
  • !last - Passa all'ultimo brano.
  • \n
  • !volume {volume} - Ottenere o modificare il volume (da 0 a 100).
  • \n
  • !mode [{mode}] - Ottenere o impostare la modalità di riproduzione, {mode} dovrebbe essere one-shot (rimuove l'elemento una volta riprodotto), repeat (ripete la playlist dopo il completamento), random (riproduzione casuale della playlist), autoplay (riproduce brani casuali dalla libreria musicale).
  • \n
  • !duck on/off - Abilitare o disabilitare la funzione ducking.
  • \n
  • !duckv {volume} - Imposta il volume del bot quando il ducking è attivato.
  • \n
  • !duckthres - Imposta la soglia del volume per attivare il ducking (3000 per impostazione predefinita).
  • \n
  • !oust - Interrompe la riproduzione e vai al canale predefinito.
  • \n
\nPlaylist\n
    \n
  • !now (or !np) - Visualizza il brano corrente.
  • \n
  • !queue - Visualizza gli elementi nella playlist.
  • \n
  • !tag {tags} - Aggiungi tutti gli elementi con i tag {tags}, tag separati da \",\".
  • \n
  • !file (or !f) {path/folder/keyword} - Aggiungi un singolo file alla playlist tramite il percorso o la parola chiave nel percorso.
  • \n
  • !filematch (or !fm) {pattern} - Aggiungi tutti i file che corrispondono all'espressione regolare {pattern}.
  • \n
  • !url {url} - Aggiungi musica da YouTube o SoundCloud.
  • \n
  • !playlist {url} [{offset}] - Aggiungi tutti gli elementi da una playlist di YouTube o SoundCloud e inizia con l'elemento {offset}.
  • \n
  • !radio {url} - Aggiungi una radio {url} alla playlist.
  • \n
  • !rbquery {keyword} - Interroga http://www.radio-browser.info per una stazione radio.
  • \n
  • !rbplay {id} - Riproduce una stazione radio con {id} (es. !rbplay 96746).
  • \n
  • !ysearch {keywords} - Interroga YouTube. Usa !ysearch -n per andare alla pagina successiva.
  • \n
  • !yplay {keywords} - Aggiungi il primo risultato di ricerca per {keyword} alla playlist.
  • \n
  • !shortlist (or !sl) {indexes/*} - Aggiungi {index}-esimo elemento (o tutti gli elementi se * è dato) alla lista.
  • \n
  • !rm {num} - Rimuove il brano {num} dalla playlist.
  • \n
  • !repeat [{num}] - Ripete il brano corrente {num} volte (1 per impostazione predefinita).
  • \n
  • !random - Playlist in riproduzione casuale.
  • \n
\nLibreria Musicale\n
    \n
  • !search {keywords} - Trova l'elemento con {keywords} nella libreria musicale, parole chiave separate da spazio.
  • \n
  • !listfile [{pattern}] - Mostra l'elenco dei file disponibili (i cui percorsi corrispondono all'espressione regolare {pattern}, se fornito).
  • \n
  • !addtag [{index}] {tags} - Aggiunge {tag} a {index} (brano corrente se {index} è omesso) della playlist, tag separati da \",\".
  • \n
  • !addtag * {tags} - Aggiunge {tags} a tutti gli elementi sulla playlist.
  • \n
  • !untag [{index/*}] {tags}/* - Rimuove {tags}/tutti i tag dall'elemento {index} (brano corrente se {index} è omesso) nella playlist.
  • \n
  • !findtagged (or !ft) {tags} - Trova l'elemento con {tags} nella libreria musicale.
  • \n
  • !delete {index} - Rimuove {index} elemento dall'elenco della libreria musicale.
  • \n
\nAltro\n
    \n
  • !joinme {token} - Unisciti al tuo canale Mumble con {token}.
  • \n
  • !password {password} - Cambia la password, utilizzata per accedere all'interfaccia web.
  • \n
\",", - "invalid_index": "Indice {index} non valido. Usa !queue 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": "

Aggiornamento disponibile!

Versione {new_version} di botamusique trovata!
\\n

Changelog

{changelog}
Invia !update 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 !{command} {{page}} 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": "{title} di {name} aggiunto da {user}", - "rb_play_empty": "Si prega di specificare l'ID di una stazione radio!", - "rb_query_result": "Questo è il risultato della tua ricerca, invia !rbplay {ID} per riprodurre una stazione:", - "records_omitted": "...", - "removed_tags": "Tag {tags} rimossi da {song}.", - "removed_tags_from_all": "Tag {tags} 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 è {version}.", - "shortlist_instruction": "Usa !sl {indexes} per riprodurre l'elemento desiderato.", - "start_updating": "Inizio aggiornamento...", - "stopped": "Riproduzione interrotta.", - "too_long": "{song} è troppo lunga ({duration} > {max_duration}), rimossa dalla playlist!", - "unable_download": "Impossibile scaricare {item}. Rimosso dalla libreria.", - "unable_play": "Impossibile riprodurre {item}. Rimosso dalla libreria.", - "unknown_mode": "Modalità di riproduzione '{mode}' sconosciuta. Dovrebbe essere one-shot, ripeti, casuale.", - "update_successful": "

botamusique v{version} installato!


\n

Changelog

{changelog}
Visita la nostra repository GitHub per ulteriori dettagli!", - "url": "URL", - "url_ban": "URL {url} è vietato!", - "url_ban_list": "", - "url_ban_success": "", - "url_from_playlist": "URL", - "url_from_playlist_item": "{title} dalla playlist {playlist} aggiunto da {user}", - "url_item": "{title} aggiunto da {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:
{users}", - "webpage_address": "Il tuo indirizzo per accedere all'interfaccia web è {address}", - "which_command": "Intendi
{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 !sl {{indexes}} per riprodurre l'elemento desiderato.
\\n!ytquery -n 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": "Sei davvero sicuro?
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 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.
\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" - } -} \ No newline at end of file diff --git a/lang/ja_JP.json b/lang/ja_JP.json deleted file mode 100644 index 44a457f..0000000 --- a/lang/ja_JP.json +++ /dev/null @@ -1,173 +0,0 @@ -{ - "cli": { - "added_tags": "{song}{tags}というタグを追加しました。", - "added_tags_to_all": "再生リストの曲に{tags}というタグを追加しました。", - "admin_help": "

管理者コマンド

\nBot\n
    \n
  • !kill - botを終了する。
  • \n
  • !update - 自動更新する。
  • \n
  • !userban {user} - このユーザーを禁止する。
  • \n
  • !userunban {user} - このユーザーの禁止を解除する。
  • \n
  • !urlbanlist - 禁止さらたユーザーリスト
  • \n
  • !urlban [{url}] - {url} (デフォルトは今の曲のURL)を禁止する。ライブラリに削除する。
  • \n
  • !urlunban {url} - このURLの禁止を解除する。
  • \n
  • !rescan {url} - 本機の音楽フォルダをスキャン直す。
  • \n
  • !dropdatabase - 全部設定とライブラリを消去する。
  • \n
\nWeb Interface\n
    \n
  • !webuserlist - list all users that have the permission of accessing the web interface, if auth mode is 'password'.
  • \n
  • !webuseradd {nick name} - grant the user with {nick name} the access to the web interface, if auth mode is 'password'.
  • \n
  • !webuserdel {nick name} - revoke the access to the web interface of {nick name}, if auth mode is 'password'.
  • \n
", - "auto_paused": "音楽を再開するには、!play を送信してください。", - "bad_command": "{command}: コマンドが見つかりません。", - "bad_parameter": "{command}: パラメータが不正です。", - "bad_url": "URLが不正です。", - "cache_refreshed": "キャッシュが更新されました。", - "change_ducking_volume": "{user}は「ダッキング」が触発する時の音量を{volume}に設定しました。", - "change_max_volume": "", - "change_mode": "{user}がプレイモードを{mode}に設定しました。", - "change_volume": "{user}が音量を{volume}に設定しました。", - "cleared": "再生リストがクリアされました。", - "cleared_tags": "{song}のタグが全部クリアされました。", - "cleared_tags_from_all": "再生リスト内の全ての曲のタグがクリアされました。", - "command_disabled": "{command}: この命令は利用できません。", - "current_ducking_volume": "「ダッキング」が触発する時の音量:{volume}。", - "current_max_volume": "", - "current_mode": "現在のプレイモードは{mode}です。", - "current_volume": "現在の音量は{volume}です。", - "database_dropped": "データベースがクリアされました。", - "download_in_progress": "今は{item}をダウンロード中…", - "error_executing_command": "{command}: コマンドが失敗しまいました,エラーは {error}。", - "file": "ファイル", - "file_added": "新しい曲が追加しました:{item}。", - "file_deleted": "{item}がライブラリから削除されました。", - "file_item": "{artist} - {title}{user}によって追加しました。", - "file_missed": "'{file}' が見つかりません!プレイリストから削除します。", - "help": "

コマンドの使い方


\n\nbotを操縦する\n\n
    \n
  • !web - ウェブインターフェースのアドレスを取得する。
  • \n
  • !play (= !p) [{num}] [{start_from}] - 再生を再開する・第{num}番目を再生する。
  • \n
  • !pause - 一時停止。
  • \n
  • !stop - 再生停止。
  • \n
  • !skip - 次の曲にスキップする。
  • \n
  • !last - 最後の曲にスキップする。
  • \n
  • !volume {volume} - 音量を取得・設定する(0〜100)。
  • \n
  • !mode [{mode}] - 再生モードを設定する。 {mode} はone-shotrepeatrandom、 \nautoplay 四つ中の一つです。
  • \n
  • !duck on/off - 「ダッキング」を起動する(人が喋る時自動的に音量を下げる)。
  • \n
  • !duckv {volume} - 「ダッキング」の音量を取得・設定する(0〜100)。
  • \n
  • !duckthres - 「ダッキング」を触発ために必要なオーディオ信号の閾値を設定する(デフォルトは3000)。
  • \n
  • !oust - 再生を停止する、そして最初のチャネルに戻る。
  • \n

\n\n再生リスト
\n\n
    \n
  • !now (= !np) - 今放送中の曲のインフォを取得する。
  • \n
  • !queue - 再生リストを表示する。
  • \n
  • !tag {tags} - ライブラリの中にタグ「{tags}」がある曲を再生リストに追加する。
  • \n
  • !file (= !f) {path/folder/keyword} - 本機にある音楽フェイル・フォルダを追加する。
  • \n
  • !filematch (or !fm) {pattern} - ファイルパスが正規表現パターン「{pattern}」にマッチされる曲を追加する。
  • \n
  • !url {url} - Youtube/SoundCloudリンクを追加する。
  • \n
  • !playlist {url} [{offset}] - Youtube/SoundCloud再生リストを追加する。
  • \n
  • !radio {url} - アドレス「{url}」のウェブラジオを追加する。
  • \n
  • !rbquery {keyword} - http://www.radio-browser.infoからウェブラジオを検索する。
  • \n
  • !rbplay {id} - ID「{id}」のウェブラジオを追加する (例: !rbplay 96746)。
  • \n
  • !ysearch {keywords} - Youtubeを検索する。 ペイジをめぐるため !ysearch -n を使ってください。
  • \n
  • !yplay {keywords} - Youtubeを検索する。第一番目の曲を直接に再生リストに追加する。
  • \n
  • !shortlist (or !sl) {indexes/*} - 候補リストの第{indexes}番目の曲を追加する(もし「*」を使ったら、候補リストにある全ての曲を追加する)。
  • \n
  • !rm {num} - 再生リストにある第{num}番目の曲を削除する。
  • \n
  • !repeat [{num}] - 今の曲を{num}回リピートする(デフォルトは一回リピートする)。
  • \n
  • !random - 再生リストの順序をランダム化にする。
  • \n

\n\nライブリ
\n\n
    \n
  • !search {keywords} - ライブリの中に「{keywords}」が出る曲を検索する。
  • \n
  • !listfile [{pattern}] - ファイルパスが正規表現パターン「{pattern}」にマッチされる曲を表示する。
  • \n
  • !addtag [{index}] {tags} - タグ「{tags}」を第{index}番目の曲に追加する(もし{index}が提供されなかったら、今の曲に追加する)。複数のタグが「,」で区切る。
  • \n
  • !addtag * {tags} - タグ「{tags}」を再生リストにある全部曲に追加する。
  • \n
  • !untag [{index/*}] {tags}/* - 第{index}番目の曲(全ての曲、もし「*」を使ったら)からタグ「{tags}」を削除する(全部のタグ、もし「*」を使ったら)。
  • \n
  • !findtagged (or !ft) {tags} - ライブリに{tags}が含む曲を検索する。
  • \n
  • !delete {index} - ライブリ(ハードドライブ)に候補リストの第{index}番目曲を削除する。
  • \n

\n\n他のコマンド
\n\n
    \n
  • !joinme [{token}] - あなたがいるチャネルに入る。
  • \n
  • !password {password} - あなたのウェブインタフェーイスのパスワードを変更する。
  • \n
", - "invalid_index": "インデックス{index}が不正です。再生リストを見るために、!queueを送信してください。", - "last_song_on_the_queue": "最後の曲。", - "max_volume": "", - "multiple_file_added": "以下の曲が追加しました:", - "multiple_file_deleted": "以下の曲がライブラリから削除されました:", - "multiple_file_found": "以下の曲が見つかりました:", - "multiple_matches": "ファイルが見つかりませんでした。もしかして:", - "new_version_found": "

新バージョン発見!

botamusique {new_version} 可用!
\n

更新履歴

{changelog}
!updateを送信してこのバージョンにアップデートします。", - "next_to_play": "次の曲。", - "no_file": "ファイルが見つかりません。", - "not_admin": "あなたは管理員ではありません。", - "not_in_my_channel": "あなたは私のチャネルにいません。", - "not_playing": "何も再生していません。", - "now_playing": "再生中:{item}", - "page_instruction": "第{current}/{total}頁。 !{command} {{page}}を送信してページをめぐります。", - "paused": "音楽は一時停止しました。", - "playlist_fetching_failed": "再生リストを取得できません。", - "pm_not_allowed": "プライベートメッセージが受け取りません。", - "position_in_the_queue": "位置:", - "preconfigurated_radio": "デフォルトのウェブラジオは:", - "queue_contents": "再生リストにある曲は:", - "queue_empty": "再生リストは空です。", - "radio": "ラジオ", - "radio_item": "{title}{name}から)。{user}に追加されました。", - "rb_play_empty": "ラジオIDを提供してください。", - "rb_query_result": "検索の結果( !rbplay {ID} を送信して再生する)", - "records_omitted": "…", - "removed_tags": "{song}からタグ「 {tags}」を削除しました。", - "removed_tags_from_all": "再生リストの全ての曲にタグ「{tags} 」を削除しました。", - "removing_item": "再生リストに「{item}」を削除しました。", - "repeat": "「{song}」を{n}回リピートするになります。", - "report_version": "現在のbotamusiqueバージョンは{version}です。", - "shortlist_instruction": "!sl {indexes}を使ってこのリストの曲を再生する。", - "start_updating": "更新しています…", - "stopped": "再生停止。", - "too_long": "「{song}」が長さ制限を超えました({duration} > {max_duration})。削除されました。", - "unable_download": "「{item}」がダウンロードできません。削除されました。", - "unable_play": "「{item}」が再生できません。削除されました。", - "unknown_mode": "不正な再生モード「{mode}」。 one-shot, repeat, random, autoplayの中の一つを使ってください。", - "update_successful": "

botamusique v{version} インストール完成!


\n

更新履歴

{changelog}
このプロジェクトの githubページ をご覧ください!", - "url": "URL", - "url_ban": "URL {url} が禁止されています。", - "url_ban_list": "", - "url_ban_success": "", - "url_from_playlist": "URL", - "url_from_playlist_item": "{title}、({playlist}から)、 {user} に追加されました。", - "url_item": "{title} {user} に追加されました。", - "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": "以下のユーザーはウェブインターフェースを訪問する権利を持っています:
{users}", - "webpage_address": "ウェブインターフェースのアドレスは{address}。", - "which_command": "もしかして 
{commands}", - "wrong_pattern": "不正な正規表現パターン:{error}。", - "yt_no_more": "これ以上のエントリがありません。", - "yt_query_error": "Youtubeを訪問できません!", - "yt_result": "Youtube検索結果: {result_table} !sl {{indexes}}を使って再生します。
\n!ytquery -nを使ってページをめぐります。" - }, - "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": "本当ですが?
もしアップ本当にロードをキャンセルすることに決まったら、もう一度このバトンを押してください。", - "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": "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設定によって、ログオンするにはトークンが必要になります。
\n \"{{ command }}\" を送信してトークンを取得してください。", - "type": "種類", - "upload_file": "アップロード", - "upload_submit": "アップロード", - "upload_to": "フォルダ", - "uploaded_finished": "アップロード完了", - "uploading_files": "アップロード中…", - "url": "URL", - "url_path": "URL・パス", - "url_placeholder": "URL…", - "volume_slider": "音量スライダー" - } -} \ No newline at end of file diff --git a/lang/nl_NL.json b/lang/nl_NL.json deleted file mode 100644 index 8e6f570..0000000 --- a/lang/nl_NL.json +++ /dev/null @@ -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": "" - } -} \ No newline at end of file diff --git a/lang/pt_BR.json b/lang/pt_BR.json deleted file mode 100644 index 5a69f42..0000000 --- a/lang/pt_BR.json +++ /dev/null @@ -1,173 +0,0 @@ -{ - "cli": { - "added_tags": "As etiquetas {tags} foram adicionadas em {song}.", - "added_tags_to_all": "As etiquetas {tags} foram adicionadas nas músicas da lista de reprodução.", - "admin_help": "

Comandos de administrador

\nRobô\n
    \n
  • !kill - matar o robô
  • \n
  • !update - atualizar o robô
  • \n
  • !userban {usuário} - banir um usuário
  • \n
  • !userunban {usuário} - remover usuário da lista de usuários banidos
  • \n
  • !urlbanlist - exibir lista de endereços banidos
  • \n
  • !urlban [{endereço}] - banir {endereço} (ou o endereço do item atual, por padrão) e remover este endereço da biblioteca.
  • \n
  • !urlunban {endereço - remover {endereço} da lista de endereços banidos
  • \n
  • !rescan {endereço} - reconstruir cache de arquivos de música local
  • \n
  • !dropdatabase - limpar o banco de dados inteiro, você perderá todas as configurações e a biblioteca de música.
  • \n
\nInterface web\n
    \n
  • !webuserlist - 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'.
  • \n
  • !webuseradd {apelido} - dar acesso à interface web para {apelido}, se o modo de autenticação for 'password'.
  • \n
  • !webuserdel {apelido} - revogar o acesso à interface web de {apelido}, caso o modo de autenticação for 'password'.
  • \n
", - "auto_paused": "Use !play para retomar a reprodução de música!", - "bad_command": "{command}: comando não encontrado.", - "bad_parameter": "{command}: 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 {mode} 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 {song}.", - "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 é {mode}.", - "current_volume": "Volume atual: {volume}.", - "database_dropped": "O banco de dados foi esvaziado.", - "download_in_progress": "A descarga de {item} 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": "{artist} - {title} adicionado por {user}", - "file_missed": "O arquivo de música '{file}' foi perdido! Este item foi removido da lista de reprodução.", - "help": "

Comandos

\nControle\n
    \n
  • !web - exibe o endereço da interface web, caso habilitado.
  • \n
  • !play (ou !p) [{num}] [{iniciar_de}] - resume/inicia a reprodução (a partir da música na posição {num}, caso especificado)
  • \n
  • !pause - pausa
  • \n
  • !stop - interrompe a reprodução
  • \n
  • !skip - pula para a próxima música
  • \n
  • !last - pula para a última música
  • \n
  • !volume {volume} - exibe ou altera o volume (de 0 a 100)
  • \n
  • !mode [{modo}] - exibe ou define o modo de reprodução, {modo} deve ser um dos seguintes: one-shot (remover o item assim que ele for reproduzido, repeat (repetir a lista de reprodução), ou random (tornar a lista de reprodução em ordem aleatória),\nautoplay (escolher algo da biblioteca de música aleatoriamente).
  • \n
  • !duck on/off - habilita ou desabilita a função de atenuação
  • \n
  • !duckv {volume} - define o volume do robô quando a atenuação está ativada
  • \n
  • !duckthres - define o nível de volume que ativa a atenuação (3000 por padrão)
  • \n
  • !oust - interrompe a reprodução e vai para o canal padrão
  • \n
\nLista de reprodução\n
    \n
  • !now (ou !np) - exibe a música atual
  • \n
  • !queue - exibe os itens na lista de reprodução
  • \n
  • !tag {etiquetas} - adiciona todos os itens com as etiquetas {etiquetas}, etiquetas separadas com \",\".
  • \n
  • !file (ou !f) {caminho/pasta/palavra-chave} - adiciona um único arquivo à lista de reprodução pelo seu caminho ou palavra-chave em seu caminho.
  • \n
  • !filematch (ou !fm) {padrão} - adiciona todos os arquivos que combinarem com a expressão regular {padrão}
  • \n
  • !url {url} - adicionar música do YouTube ou SoundCloud
  • \n
  • !playlist {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}
  • \n
  • !radio {endereço} - adiciona a rádio {endereço} no final da lista de reprodução
  • \n
  • !rbquery {palavra_chave} - busca por uma estação de rádio em http://www.radio-browser.info
  • \n
  • !rbplay {id} - reproduz uma estação de rádio com {id} (por ex.: !rbplay 96746)
  • \n
  • !ysearch {palavras_chave} - busca no YouTube. Use !ysearch -n para trocar de página.
  • \n
  • !yplay {palavras_chave} - adiciona o primeiro resultado da busca de {palavras_chave} na lista de reprodução.
  • \n
  • !shortlist (ou !sl) {índices/*} - adiciona o item na posição {índices} (ou todos caso * seja especificado) na lista curta.
  • \n
  • !rm {num} - remove a música na posição {num} da lista de reprodução
  • \n
  • !repeat [{num}] - repete a música atual {num} (1 por padrão) vezes.
  • \n
  • !random - torna a lista de reprodução em ordem aleatória.
  • \n
\nBiblioteca de música\n
    \n
  • !search {palavras_chave} - busca pelo item com {palavras_chave} na biblioteca de música, palavras-chave separadas por espaço.
  • \n
  • !listfile [{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)
  • \n
  • !addtag [{í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 \",\".
  • \n
  • !addtag * {etiquetas} - adiciona {etiquetas} para todos os itens na lista de reprodução.
  • \n
  • !untag [{í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).
  • \n
  • !findtagged (ou !ft) {etiquetas} - busca por um item com {etiquetas} na biblioteca de música.
  • \n
  • !delete {índice} - apaga da biblioteca de música o item da lista curta na posição {índice}.
  • \n
\nOutro\n
    \n
  • !joinme {token} - entra no seu próprio canal com {token}.
  • \n
  • !password {senha} - altera sua senha, usada para acessar a interface web.
  • \n
", - "invalid_index": "O índice {index} é inválido. Use !queue 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": "

Atualização disponível!

A versão {new_version} do botamusique está disponível!
\n

Registro de mudanças

{changelog}
Envie !update 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 !{command} {{page}} 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": "{title} de {name} foi adicionado por {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 !rbplay {ID} para reproduzir uma estação:", - "records_omitted": "…", - "removed_tags": "As etiquetas {tags} foram removidas de {song}.", - "removed_tags_from_all": "As etiquetas {tags} 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 é {version}.", - "shortlist_instruction": "Use !sl {índices} para reproduzir o item que você deseja.", - "start_updating": "Iniciando a atualização...", - "stopped": "Música parada.", - "too_long": "{song} é muito longo ({duration} > {max_duration}). Removido da lista de reprodução!", - "unable_download": "Falha ao baixar {item}. Removido da biblioteca.", - "unable_play": "Falha ao reproduzir {item}. Removido da biblioteca.", - "unknown_mode": "O modo de reprodução '{mode}' é desconhecido. Ele deve ser um dos seguintes: one-shot, repeat, random.", - "update_successful": "

botamusique v{version} instalado!


\n

Registro de mudanças

{changelog}
Visite nosso repositório no GitHub 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:
{list}", - "url_ban_success": "O seguinte endereço está banido: {url}.", - "url_from_playlist": "Endereço", - "url_from_playlist_item": "{title} da lista de reprodução {playlist} adicionado por {user}", - "url_item": "{title} adicionado por {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:
{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:
{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:
{users}", - "webpage_address": "O seu próprio endereço para acessar a interface web é {address}", - "which_command": "Você quis dizer
{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 !sl {{índices}} para reproduzir o item que você deseja.
\n!ytquery -n 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": "Você realmente tem certeza?
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 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.
\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" - } -} \ No newline at end of file diff --git a/lang/zh_CN.json b/lang/zh_CN.json deleted file mode 100644 index 8c3cd67..0000000 --- a/lang/zh_CN.json +++ /dev/null @@ -1,173 +0,0 @@ -{ - "cli": { - "added_tags": "已将标签 {tags} 添加到 {song}。", - "added_tags_to_all": "已将标签 {tags} 添加到播放列表的所有曲目中。", - "admin_help": "

管理员命令

\n机器人管理\n
    \n
  • !kill - 退出。
  • \n
  • !update - 自动更新至新版本。
  • \n
  • !userban {user} - 封禁用户。
  • \n
  • !userunban {user} - 解除封禁。
  • \n
  • !urlbanlist - 列出全部封禁的用户。
  • \n
  • !urlban [{url}] - 封禁链接 {url} (若未指定,则默认为当前播放曲目的URL) 并将它从数据库中移除。
  • \n
  • !urlunban {url} - 解除封禁链接 {url}。
  • \n
  • !rescan {url} - 更新本地音乐库。
  • \n
  • !dropdatabase - 清除数据库(包括设置和音乐库)。本操作不可逆,请务必事先考虑清楚。
  • \n
\n网络控制界面\n
    \n
  • !webuserlist - (若当前认证模式为 'password')列出所有具有网络控制界面访问权限的用户。
  • \n
  • !webuseradd {name} - (若当前认证模式为 'password')授权名为 {name} 的用户访问网络控制界面。
  • \n
  • !webuserdel {name} - (若当前认证模式为 'password')撤销名为 {name} 的用户的访问权限。
  • \n
", - "auto_paused": "已暂停。若要继续播放,请发送 !play !", - "bad_command": "{{command}}: 未知命令。请发送!help获取命令列表。", - "bad_parameter": "{command}: 无效参数!", - "bad_url": "URL地址无效!", - "cache_refreshed": "缓存已刷新。", - "change_ducking_volume": "{user}将“闪避”时的音量设置为 {volume}。", - "change_max_volume": "", - "change_mode": "{user}将播放列表模式被设置为{mode} 。", - "change_volume": "{user}将音量设置为{volume}。", - "cleared": "播放列表已清空。", - "cleared_tags": "已移除{song}上的所有标签。", - "cleared_tags_from_all": "已移除播放列表内所有曲目的标签。", - "command_disabled": "{command}: 该命令不可用!", - "current_ducking_volume": "“闪避”时的音量为:{volume}。", - "current_max_volume": "", - "current_mode": "当前的播放模式为{mode}。", - "current_volume": "当前音量为{volume}。", - "database_dropped": "数据库已经清空。", - "download_in_progress": "正在下载{item}……", - "error_executing_command": "{command}: 命令失败,错误为 {error}。", - "file": "文件", - "file_added": "新曲目被添加:{item}。", - "file_deleted": "{item}已从库中移除。", - "file_item": "{artist} - {title},由{user}添加。", - "file_missed": "文件 '{file}' 丢失!已将其移出播放列表。", - "help": "

命令帮助

\n\n播放控制\n\n
    \n
  • !web - 获取网页控制界面的地址(如果启用了的话)。
  • \n
  • !play (或 !p) [{num}] [{start_from}] - 继续播放/开始播放第{num}首曲目。
  • \n
  • !pause - 暂停播放。
  • \n
  • !stop - 停止播放。
  • \n
  • !skip - 跳到下一首曲目。
  • \n
  • !last - 跳到播放列表上的最后一首曲目。
  • \n
  • !volume {volume} - 获取或设置音量(从0到100)。
  • \n
  • !mode [{mode}] - 设置播放模式。 {mode} 可以使 one-shot (顺序播放), repeat (循环播放), random (随机播放)或\nautoplay (自动播放)四种之一.
  • \n
  • !duck on/off - 开启或关闭“闪避”功能。开启后,在别人说话时,音乐的音量会自动减小。
  • \n
  • !duckv {volume} - 获取或设置“闪避”时的音量。
  • \n
  • !duckthres - 设置“闪避”被激活所需音频信号强度的阈值(默认是3000)。
  • \n
  • !oust - 停止播放,并回到默认频道。
  • \n
\n播放列表\n\n
    \n
  • !now (或 !np) - 显示当前曲目信息。
  • \n
  • !queue - 显示播放列表。
  • \n
  • !tag {tags} - 将音乐库中所有包含{tags}标签的曲目添加到播放列表中。
  • \n
  • !file (或 !f) {path/folder/keyword} - 添加某一本地音频文件或某个目录中的全部文件到播放列表中。
  • \n
  • !filematch (or !fm) {pattern} - 将文件名满足正则表达式{pattern}的全部文件添加到播放列表中。
  • \n
  • !url {url} - 添加Youtube或SoundCloud链接。
  • \n
  • !playlist {url} [{offset}] - 添加Youtube或SoundCloud播放列表。
  • \n
  • !radio {url} - 将地址为{url}的电台加入播放列表。
  • \n
  • !rbquery {keyword} - 从http://www.radio-browser.info中搜索某一电台。
  • \n
  • !rbplay {id} - 播放ID为{id}的电台 (如 !rbplay 96746)。
  • \n
  • !ysearch {keywords} - 搜索Youtube。 使用 !ysearch -n 翻页.
  • \n
  • !yplay {keywords} - 搜索Youtube,将第一条搜索结果直接加入播放列表。
  • \n
  • !shortlist (or !sl) {indexes/*} - 添加候选列表中的第{indexes}条曲目(或者是全部曲目,如果该参数为“*”)到播放列表中。
  • \n
  • !rm {num} - 删除播放列表上的第{num}首曲目。
  • \n
  • !repeat [{num}] - 重复当前曲目{num}遍(默认重复一遍)。
  • \n
  • !random - 随机打乱播放列表顺序。
  • \n
\n\n音乐库\n\n
    \n
  • !search {keywords} - 在音乐库中搜索包含关键词{keywords}的曲目,关键词以空格分割。
  • \n
  • !listfile [{pattern}] - 列出路径符合正则表达式{pattern}的文件。
  • \n
  • !addtag [{index}] {tags} - 将标签{tags}添加到第{index}首曲目(如果{index}被省略则默认为当前曲目)。多个标签以“,”分割。
  • \n
  • !addtag * {tags} - 将标签{tags}添加到播放列表上的所有曲目。
  • \n
  • !untag [{index/*}] {tags}/* - 从第{index}首曲目(或当前曲目,若{index}被省略;或全部曲目,若该参数为“*”)上删除标签{tags}(或全部标签)。
  • \n
  • !findtagged (or !ft) {tags} - 在音乐库中查找包含标签{tags}的曲目。
  • \n
  • !delete {index} - 从音乐库中删除候选列表上的第{index}首曲目。
  • \n
\n\n其他\n\n
    \n
  • !joinme [{token}] - 加入你所在的频道。
  • \n
  • !password {password} - 更改你用于访问网页控制界面的密码。
  • \n
", - "invalid_index": "无效的序号 {index}。 使用 '!queue' 查看播放列表。", - "last_song_on_the_queue": "最后一首。", - "max_volume": "", - "multiple_file_added": "以下曲目已被添加:", - "multiple_file_deleted": "以下曲目已被移出库:", - "multiple_file_found": "搜索到:", - "multiple_matches": "文件未找到!你是不是指:", - "new_version_found": "

发现新版本!

botamusique {new_version} 可用!
\n

更新日志

{changelog}
使用 !update自动更新至该版本。", - "next_to_play": "下一首。", - "no_file": "文件未找到。", - "not_admin": "你不是管理员!", - "not_in_my_channel": "你不在我的频道里!", - "not_playing": "无播放中的曲目。", - "now_playing": "正在播放:{item}", - "page_instruction": "第{current}/{total}页。发送!{command} {{page}}翻页。", - "paused": "暂停播放。", - "playlist_fetching_failed": "无法获取播放列表!", - "pm_not_allowed": "不接受私信。", - "position_in_the_queue": "位置:", - "preconfigurated_radio": "预设的电台如下:", - "queue_contents": "播放列表中的曲目:", - "queue_empty": "播放列表为空!", - "radio": "电台", - "radio_item": "{title}来自 {name}。 {user} 添加。", - "rb_play_empty": "请指定一个电台ID!", - "rb_query_result": "搜索结果如下。发送 !rbplay {ID} 播放。", - "records_omitted": "……", - "removed_tags": "已将标签 {tags}{song}上移除。", - "removed_tags_from_all": "已将标签 {tags} 从播放列表的曲目中移除。", - "removing_item": "已将 {item} 从播放列表中移除。", - "repeat": "重复{song} {n}次。", - "report_version": "当前的botamusique版本为{version}。", - "shortlist_instruction": "使用!sl {indexes}播放列表中的曲目。", - "start_updating": "开始更新……", - "stopped": "音乐停止。", - "too_long": "{song}超出长度限制({duration} > {max_duration})!已被移出播放列表。", - "unable_download": "无法下载{item}。已移出播放列表。", - "unable_play": "无法播放{item}。已移出播放列表。", - "unknown_mode": "未知播放模式\"{mode}\"。播放模式应为 one-shot, repeat, random中的一个。", - "update_successful": "

botamusique v{version} 安装完毕!


\n

更新日志

{changelog}
请访问我们的 github页面 获取更多信息!", - "url": "URL", - "url_ban": "链接{url}被列入黑名单了!", - "url_ban_list": "", - "url_ban_success": "", - "url_from_playlist": "URL", - "url_from_playlist_item": "{title},来自播放列表 {playlist},由 {user} 添加。", - "url_item": "{title} {user} 添加。", - "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": "下列用户具有访问网络控制界面的权限:
{users}", - "webpage_address": "网页控制界面的地址是{address}。", - "which_command": "你是不是指
{commands}", - "wrong_pattern": "错误的正则表达式:{error}.", - "yt_no_more": "没有更多条目了!", - "yt_query_error": "无法访问Youtube!", - "yt_result": "Youtube查询结果: {result_table} 使用 !sl {{indexes}} 播放列表中的曲目。
\n使用!ytquery -n翻页。" - }, - "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": "你真的确定吗?
若要取消上传,请再次点击该按钮。", - "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": "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根据设置,你需要一个令牌才能登录。
\n请发送 \"{{ command }}\" 以获取你的登录令牌。", - "type": "类型", - "upload_file": "上传音乐文件", - "upload_submit": "上传!", - "upload_to": "上传到", - "uploaded_finished": "上传完毕!", - "uploading_files": "上传中……", - "url": "URL", - "url_path": "URL/路径", - "url_placeholder": "URL……", - "volume_slider": "音量控制条" - } -} \ No newline at end of file diff --git a/web/.editorconfig b/web/.editorconfig deleted file mode 100644 index db4f9c8..0000000 --- a/web/.editorconfig +++ /dev/null @@ -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 diff --git a/web/.eslintrc.json b/web/.eslintrc.json deleted file mode 100644 index 2d298ca..0000000 --- a/web/.eslintrc.json +++ /dev/null @@ -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 - }] - } -} diff --git a/web/.gitattributes b/web/.gitattributes deleted file mode 100644 index f8a7cde..0000000 --- a/web/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -package-lock.json text eol=lf diff --git a/web/.gitignore b/web/.gitignore deleted file mode 100644 index 1436c17..0000000 --- a/web/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -!* -node_modules/ diff --git a/web/babel.config.json b/web/babel.config.json deleted file mode 100644 index db84814..0000000 --- a/web/babel.config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "plugins": [ - "@babel/plugin-proposal-class-properties" - ] -} diff --git a/web/js/app.mjs b/web/js/app.mjs deleted file mode 100644 index 4506030..0000000 --- a/web/js/app.mjs +++ /dev/null @@ -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 tags with and set up a MutationObserver to - // continue doing this as the DOM changes. - dom.watch(); - - document.getElementById('theme-switch-btn').addEventListener('click', () => { - Theme.swap(); - }); -}); - diff --git a/web/js/lib/text.mjs b/web/js/lib/text.mjs deleted file mode 100644 index 6ee3f9b..0000000 --- a/web/js/lib/text.mjs +++ /dev/null @@ -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; -} diff --git a/web/js/lib/theme.mjs b/web/js/lib/theme.mjs deleted file mode 100644 index 70294d6..0000000 --- a/web/js/lib/theme.mjs +++ /dev/null @@ -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); - } -} diff --git a/web/js/lib/type.mjs b/web/js/lib/type.mjs deleted file mode 100644 index 6429dd8..0000000 --- a/web/js/lib/type.mjs +++ /dev/null @@ -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.'); - } -} diff --git a/web/js/lib/util.mjs b/web/js/lib/util.mjs deleted file mode 100644 index 4bd7221..0000000 --- a/web/js/lib/util.mjs +++ /dev/null @@ -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); -} diff --git a/web/js/main.mjs b/web/js/main.mjs deleted file mode 100644 index b5af6cd..0000000 --- a/web/js/main.mjs +++ /dev/null @@ -1,1300 +0,0 @@ -import 'jquery/src/jquery.js'; -import 'jquery-migrate/src/migrate.js'; -import Popper from 'popper.js/dist/esm/popper.js'; -import { - Modal, - Toast, - Tooltip, -} from 'bootstrap/js/src/index.js'; -import { - getColor, - isOverflown, - setProgressBar, - secondsToStr, -} from './lib/util.mjs'; -import {limitChars} from './lib/text.mjs'; - -$('#uploadSelectFile').on('change', function() { - // get the file name - const fileName = $(this).val().replace('C:\\fakepath\\', ' '); - // replace the "Choose a file" label - $(this).next('.custom-file-label').html(fileName); -}); - - -// ---------------------- -// ------ Playlist ------ -// ---------------------- - -const pl_item_template = $('.playlist-item-template'); -const pl_id_element = $('.playlist-item-id'); -const pl_index_element = $('.playlist-item-index'); -const pl_title_element = $('.playlist-item-title'); -const pl_artist_element = $('.playlist-item-artist'); -const pl_thumb_element = $('.playlist-item-thumbnail'); -const pl_type_element = $('.playlist-item-type'); -const pl_path_element = $('.playlist-item-path'); - -const pl_tag_edit_element = $('.playlist-item-edit'); - -const notag_element = $('.library-item-notag'); // these elements are shared with library -const tag_element = $('.library-item-tag'); - -const addTagModal = new Modal(document.getElementById('addTagModal')); - -const playlist_loading = $('#playlist-loading'); -const playlist_table = $('#playlist-table'); -const playlist_empty = $('#playlist-empty'); -const playlist_expand = $('.playlist-expand'); - -let playlist_items = null; - -let playlist_ver = 0; -let playlist_current_index = 0; - -let playlist_range_from = 0; -let playlist_range_to = 0; - -let last_volume = 0; - -let playing = false; - -const playPauseBtn = $('#play-pause-btn'); -const fastForwardBtn = $('#fast-forward-btn'); -const volumeSlider = document.getElementById('volume-slider'); - -const playModeBtns = { - 'one-shot': $('#one-shot-mode-btn'), - 'random': $('#random-mode-btn'), - 'repeat': $('#repeat-mode-btn'), - 'autoplay': $('#autoplay-mode-btn'), -}; -const playModeIcon = { - 'one-shot': 'fa-tasks', - 'random': 'fa-random', - 'repeat': 'fa-redo', - 'autoplay': 'fa-robot', -}; - -playPauseBtn.on('click', togglePlayPause); - -fastForwardBtn.on('click', () => { - request('post', { - action: 'next', - }); -}); - -document.getElementById('clear-playlist-btn').addEventListener('click', () => { - request('post', {action: 'clear'}); -}); - -// eslint-disable-next-line guard-for-in -for (const playMode in playModeBtns) { - playModeBtns[playMode].on('click', () => { - changePlayMode(playMode); - }); -} - -function request(_url, _data, refresh = false) { - console.log(_data); - $.ajax({ - type: 'POST', - url: _url, - data: _data, - statusCode: { - 200: function(data) { - if (data.ver !== playlist_ver) { - checkForPlaylistUpdate(); - } - updateControls(data.empty, data.play, data.mode, data.volume); - updatePlayerPlayhead(data.playhead); - }, - 403: function() { - location.reload(true); - }, - }, - }); - if (refresh) { - location.reload(true); - } -} - -function addPlaylistItem(item) { - pl_id_element.val(item.id); - pl_index_element.html(item.index + 1); - pl_title_element.html(item.title); - pl_artist_element.html(item.artist); - pl_thumb_element.attr('src', item.thumbnail); - pl_thumb_element.attr('alt', limitChars(item.title)); - pl_type_element.html(item.type); - pl_path_element.html(item.path); - - const item_copy = pl_item_template.clone(); - item_copy.attr('id', 'playlist-item-' + item.index); - item_copy.addClass('playlist-item').removeClass('d-none'); - - const tags = item_copy.find('.playlist-item-tags'); - tags.empty(); - - const tag_edit_copy = pl_tag_edit_element.clone(); - tag_edit_copy.click(function() { - addTagModalShow(item.id, item.title, item.tags); - }); - tag_edit_copy.appendTo(tags); - - if (item.tags.length > 0) { - item.tags.forEach(function(tag_tuple) { - const tag_copy = tag_element.clone(); - tag_copy.html(tag_tuple[0]); - tag_copy.addClass('badge-' + tag_tuple[1]); - tag_copy.appendTo(tags); - }); - } else { - const tag_copy = notag_element.clone(); - tag_copy.appendTo(tags); - } - - item_copy.appendTo(playlist_table); -} - -function displayPlaylist(data) { - playlist_table.animate({ - opacity: 0, - }, 200, function() { - playlist_loading.hide(); - $('.playlist-item').remove(); - const items = data.items; - const length = data.length; - if (items.length === 0) { - playlist_empty.removeClass('d-none'); - playlist_table.animate({opacity: 1}, 200); - return; - } - playlist_items = {}; - for (const i in items) { - playlist_items[items[i].index] = items[i]; - } - const start_from = data.start_from; - playlist_range_from = start_from; - playlist_range_to = start_from + items.length - 1; - - if (items.length < length && start_from > 0) { - let _from = start_from - 5; - _from = _from > 0 ? _from : 0; - const _to = start_from - 1; - if (_to > 0) { - insertExpandPrompt(_from, start_from + length - 1, _from, _to, length); - } - } - - items.forEach( - function(item) { - addPlaylistItem(item); - }, - ); - - if (items.length < length && start_from + items.length < length) { - const _from = start_from + items.length; - let _to = start_from + items.length - 1 + 10; - _to = _to < length - 1 ? _to : length - 1; - if (start_from + items.length < _to) { - insertExpandPrompt(start_from, _to, _from, _to, length); - } - } - - displayActiveItem(data.current_index); - updatePlayerInfo(playlist_items[data.current_index]); - bindPlaylistEvent(); - playlist_table.animate({opacity: 1}, 200); - }); -} - -function displayActiveItem(current_index) { - $('.playlist-item').removeClass('table-active'); - $('#playlist-item-' + current_index).addClass('table-active'); -} - -function insertExpandPrompt(real_from, real_to, display_from, display_to, total_length) { - const expand_copy = playlist_expand.clone(); - expand_copy.addClass('playlist-item'); - expand_copy.removeClass('d-none'); - if (display_from !== display_to) { - expand_copy.find('.playlist-expand-item-range').html((display_from + 1) + '~' + (display_to + 1) + - ' of ' + (total_length) + ' items'); - } else { - expand_copy.find('.playlist-expand-item-range').html(display_from + ' of ' + (total_length) + ' items'); - } - - expand_copy.addClass('playlist-item'); - expand_copy.appendTo(playlist_table); - expand_copy.click(function() { - playlist_range_from = real_from; - playlist_range_to = real_to; - updatePlaylist(); - }); -} - -function updatePlaylist() { - playlist_table.animate({ - opacity: 0, - }, 200, function() { - playlist_empty.addClass('d-none'); - playlist_loading.show(); - playlist_table.find('.playlist-item').css('opacity', 0); - let data = {}; - if (!(playlist_range_from === 0 && playlist_range_to === 0)) { - data = { - range_from: playlist_range_from, - range_to: playlist_range_to, - }; - } - $.ajax({ - type: 'GET', - url: 'playlist', - data: data, - statusCode: { - 200: displayPlaylist, - }, - }); - playlist_table.animate({ - opacity: 1, - }, 200); - }); -} - -function checkForPlaylistUpdate() { - $.ajax({ - type: 'POST', - url: 'post', - statusCode: { - 200: function(data) { - if (data.ver !== playlist_ver) { - playlist_ver = data.ver; - playlist_range_from = 0; - playlist_range_to = 0; - updatePlaylist(); - } - if (data.current_index !== playlist_current_index) { - if (data.current_index !== -1) { - if ((data.current_index > playlist_range_to || data.current_index < playlist_range_from)) { - playlist_range_from = 0; - playlist_range_to = 0; - updatePlaylist(); - } else { - playlist_current_index = data.current_index; - updatePlayerInfo(playlist_items[data.current_index]); - displayActiveItem(data.current_index); - } - } - } - updateControls(data.empty, data.play, data.mode, data.volume); - if (!data.empty) { - updatePlayerPlayhead(data.playhead); - } - }, - }, - }); -} - -function bindPlaylistEvent() { - $('.playlist-item-play').unbind().click( - function(e) { - request('post', { - 'play_music': ($(e.currentTarget).parent().parent().parent().find('.playlist-item-index').html() - 1), - }); - }, - ); - $('.playlist-item-trash').unbind().click( - function(e) { - request('post', { - 'delete_music': ($(e.currentTarget).parent().parent().parent().find('.playlist-item-index').html() - 1), - }); - }, - ); -} - -function updateControls(empty, play, mode, volume) { - updatePlayerControls(play, empty); - if (empty) { - playPauseBtn.prop('disabled', true); - fastForwardBtn.prop('disabled', true); - } else { - playPauseBtn.prop('disabled', false); - fastForwardBtn.prop('disabled', false); - if (play) { - playing = true; - playPauseBtn.find('[data-fa-i2svg]').removeClass('fa-play').addClass('fa-pause'); - // PR #180: Since this button changes behavior dynamically, we change its - // ARIA labels in JS instead of only adding them statically in the HTML - playPauseBtn.attr('aria-label', 'Pause'); - } else { - playing = false; - playPauseBtn.find('[data-fa-i2svg]').removeClass('fa-pause').addClass('fa-play'); - // PR #180: Since this button changes behavior dynamically, we change its - // ARIA labels in JS instead of only adding them statically in the HTML - playPauseBtn.attr('aria-label', 'Play'); - } - } - - for (const otherMode of Object.values(playModeBtns)) { - otherMode.removeClass('active'); - } - playModeBtns[mode].addClass('active'); - - const playModeIndicator = $('#modeIndicator'); - for (const icon_class of Object.values(playModeIcon)) { - playModeIndicator.removeClass(icon_class); - } - playModeIndicator.addClass(playModeIcon[mode]); - - if (volume !== last_volume) { - last_volume = volume; - if (volume > 1) { - volumeSlider.value = 1; - } else if (volume < 0) { - volumeSlider.value = 0; - } else { - volumeSlider.value = volume; - } - } -} - -function togglePlayPause() { - if (playing) { - request('post', { - action: 'pause', - }); - } else { - request('post', { - action: 'resume', - }); - } -} - -function changePlayMode(mode) { - request('post', { - action: mode, - }); -} - - -// --------------------- -// ------ Browser ------ -// --------------------- - -const filters = { - file: $('#filter-type-file'), - url: $('#filter-type-url'), - radio: $('#filter-type-radio'), -}; -const filter_dir = $('#filter-dir'); -const filter_keywords = $('#filter-keywords'); - -// eslint-disable-next-line guard-for-in -for (const filter in filters) { - filters[filter].on('click', (e) => { - setFilterType(e, filter); - }); -} - -function setFilterType(event, type) { - event.preventDefault(); - - if (filters[type].hasClass('active')) { - filters[type].removeClass('active btn-primary').addClass('btn-secondary'); - filters[type].find('input[type=radio]').removeAttr('checked'); - } else { - filters[type].removeClass('btn-secondary').addClass('active btn-primary'); - filters[type].find('input[type=radio]').attr('checked', 'checked'); - } - - if (type === 'file') { - filter_dir.prop('disabled', !filters['file'].hasClass('active')); - } - - updateResults(); -} - - -filter_dir.change(function() { - updateResults(); -}); -filter_keywords.change(function() { - updateResults(); -}); - -const item_template = $('#library-item'); - -function bindLibraryResultEvent() { - $('.library-thumb-col').unbind().hover( - function(e) { - $(e.currentTarget).find('.library-thumb-grp').addClass('library-thumb-grp-hover'); - }, - function(e) { - $(e.currentTarget).find('.library-thumb-grp').removeClass('library-thumb-grp-hover'); - }, - ); - - $('.library-info-title').unbind().hover( - function(e) { - $(e.currentTarget).parent().find('.library-thumb-grp').addClass('library-thumb-grp-hover'); - }, - function(e) { - $(e.currentTarget).parent().find('.library-thumb-grp').removeClass('library-thumb-grp-hover'); - }, - ); - - $('.library-item-play').unbind().click( - function(e) { - request('post', { - 'add_item_at_once': $(e.currentTarget).parent().parent().parent().find('.library-item-id').val(), - }); - }, - ); - - $('.library-item-trash').unbind().click( - function(e) { - request('post', { - 'delete_item_from_library': $(e.currentTarget).parent().parent().find('.library-item-id').val(), - }); - updateResults(active_page); - }, - ); - - $('.library-item-download').unbind().click( - function(e) { - const id = $(e.currentTarget).parent().parent().find('.library-item-id').val(); - // window.open('/download?id=' + id); - downloadId(id); - }, - ); - - $('.library-item-add-next').unbind().click( - function(e) { - const id = $(e.currentTarget).parent().parent().find('.library-item-id').val(); - request('post', { - 'add_item_next': id, - }); - }, - ); - - $('.library-item-add-bottom').unbind().click( - function(e) { - const id = $(e.currentTarget).parent().parent().find('.library-item-id').val(); - request('post', { - 'add_item_bottom': id, - }); - }, - ); -} - -const lib_filter_tag_group = $('#filter-tags'); -const lib_filter_tag_element = $('.filter-tag'); - -const lib_group = $('#library-group'); -const id_element = $('.library-item-id'); -const title_element = $('.library-item-title'); -const artist_element = $('.library-item-artist'); -const thumb_element = $('.library-item-thumb'); -const type_element = $('.library-item-type'); -const path_element = $('.library-item-path'); - -const tag_edit_element = $('.library-item-edit'); -// var notag_element = $(".library-item-notag"); -// var tag_element = $(".library-item-tag"); - -const library_tags = []; - -function updateLibraryControls() { - $.ajax({ - type: 'GET', - url: 'library/info', - statusCode: { - 200: displayLibraryControls, - 403: function() { - location.reload(true); - }, - }, - }); -} - -function displayLibraryControls(data) { - $('#maxUploadFileSize').val(data.max_upload_file_size); - if (data.upload_enabled) { - $('#uploadDisabled').val('false'); - $('#upload').show(); - } else { - $('#uploadDisabled').val('true'); - $('#upload').hide(); - } - - if (data.delete_allowed) { - $('#deleteAllowed').val('true'); - } else { - $('#deleteAllowed').val('false'); - $('.library-delete').remove(); - } - - const dataList = $('#upload-target-dirs'); - const dirs = []; - filter_dir.find('option').each(function(i, dir_element){ - dirs.push(dir_element.value); - }); - if (data.dirs.length > 0) { - console.log(data.dirs); - data.dirs.forEach(function(dir) { - if(!dirs.includes(dir)) { - $('').appendTo(filter_dir); - $(' - - - - {{ tr('page_title') }} - - - - - - - - - -
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
{{ tr('index') }}{{ tr('title') }}{{ tr('url_path') }}{{ tr('action') }}
- {{ tr('aria_spinner') }} -
- {{ tr('aria_empty_box') }} -
- {{ tr('expand_playlist') }} -
-
- -
- -
-
- -
-

{{ tr('music_library') }}

- -
-
-

{{ tr('filters') }}

-
-
-
-
- {{ tr('type') }} -
- - - -
-
- - -
- -
- - -
- -
-
- -
-
- {{ tr('tags') }} - -
-
-
-
-
-
- -
- {{ tr('aria_spinner') }} -
- - - -
- -
-
-
    -
  • - 1 -
  • -
-
-
- -
- - - - -
- - -
-
-
- -
- -
-
-

{{ tr('upload_file') }}

-
-
-
-
-
-
-
-
- - -
-
-
-
-
-
-
-
-
- -
- - - -
-
-
- -
-
-
-
-
-
-
-
- -
-
-
-
-

{{ tr('add_url') }}

-
-
- -
- -
- -
-
-
-
-

{{ tr('add_radio') }}

-
-
- -
- -
- -
-
-
-
- -
- -
- -
- -
- - - - - -
- - - - - -
- - - - - - - - - - - - - diff --git a/web/templates/need_token.template.html b/web/templates/need_token.template.html deleted file mode 100644 index 6d07510..0000000 --- a/web/templates/need_token.template.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - {{ tr('page_title') }} - - - - - - - -
-
-
-
- {{ tr('token_required') }} -
-
-

{{ tr('token_required') }}

- {{ tr('token_required_message') }} -
-
- -
- - -
-
-
-
-
-
-
- - - - - diff --git a/web/vscode.eslintrc.json b/web/vscode.eslintrc.json deleted file mode 100644 index 2b3ffc7..0000000 --- a/web/vscode.eslintrc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "parserOptions": { - "babelOptions": { - "configFile": "./web/babel.config.json" - } - } -} diff --git a/web/webpack.config.cjs b/web/webpack.config.cjs deleted file mode 100644 index 41aa4ca..0000000 --- a/web/webpack.config.cjs +++ /dev/null @@ -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', - }, - ], - ], - }, - }, - }, - ], - }, -};