Merge pull request #187 from azlux/translate

i18n support, with traduora
This commit is contained in:
Terry Geng
2020-07-12 18:22:50 +08:00
committed by GitHub
22 changed files with 738 additions and 519 deletions
+31 -7
View File
@@ -3,18 +3,22 @@ type: docker
name: default name: default
steps: steps:
- name: build - name: build-web
image: node image: node
commands: commands:
- cd web - (cd web && npm install && npm run build)
- npm install
- npm run build - name: translate-web
- cd .. image: python:3
commands:
- pip3 install jinja2
- (cd templates/ && ./translate.py)
- name: deploy-testing - name: deploy-testing
image: debian image: debian
commands: commands:
- apt-get -qq update && apt-get -qq install git > /dev/null - apt-get -qq update && apt-get -qq install git python3-requests > /dev/null
- (cd lang && ./sync_translation.py --client $TRADUORA_W_CLIENT --secret $TRADUORA_W_SECRET --push)
- sed -i 's/target_version = git/target_version = testing/' configuration.default.ini - sed -i 's/target_version = git/target_version = testing/' configuration.default.ini
- version=$(git rev-parse HEAD) - version=$(git rev-parse HEAD)
- echo "current git commit is $version" - echo "current git commit is $version"
@@ -53,7 +57,8 @@ steps:
- name: deploy-stable - name: deploy-stable
image: debian image: debian
commands: commands:
- apt-get -qq update && apt-get -qq install jq curl git pandoc > /dev/null - apt-get -qq update && apt-get -qq install jq curl git pandoc python3-requests > /dev/null
- (cd lang && ./sync_translation.py --client $TRADUORA_W_CLIENT --secret $TRADUORA_W_SECRET --push)
- sed -i 's/target_version = git/target_version = stable/' configuration.default.ini - sed -i 's/target_version = git/target_version = stable/' configuration.default.ini
- git fetch --tags - git fetch --tags
- version=$(git describe --abbrev=0 --tags) - version=$(git describe --abbrev=0 --tags)
@@ -86,6 +91,25 @@ steps:
event: event:
- tag - tag
- name: fetch-translate
image: debian
commands:
- apt update && apt install -y git python3-requests hub
- git branch bot-traduora && git checkout bot-traduora
- (cd lang/ && ./sync_translation.py --client $TRADUORA_R_CLIENT --secret $TRADUORA_R_SECRET --fetch)
- git add lang/* && git status
- git config --global user.email "github@azlux.fr" && git config --global user.name "azlux"
- "git commit -a -m 'Bot: update strings'"
- git remote set-url origin https://azlux:$GITHUB_API@github.com/azlux/botamusique/
- git push --set-upstream origin new-translations && git push
- "GITHUB_USER=\"azlux\" GITHUB_TOKEN=\"$GITHUB_API\" hub pull-request -m \"Bot: TRADUORA Update\""
when:
event:
- cron
cron:
- auto-fetch-lang
volumes: volumes:
- name: repo - name: repo
host: host:
+1 -1
View File
@@ -116,7 +116,7 @@ tmp/
*.db *.db
templates/* templates/*.html
# Pycharm # Pycharm
.idea/ .idea/
+2 -3
View File
@@ -91,9 +91,8 @@ cd botamusique
python3 -m venv venv python3 -m venv venv
venv/bin/pip install wheel venv/bin/pip install wheel
venv/bin/pip install -r requirements.txt venv/bin/pip install -r requirements.txt
cd web (cd web && npm install && npm run build)
npm install (cd templates/ && ./translate.py)
npm run build
``` ```
</details> </details>
+163 -162
View File
@@ -6,7 +6,8 @@ import json
import re import re
import pymumble_py3 as pymumble import pymumble_py3 as pymumble
import constants from constants import tr_cli as tr
from constants import commands
import interface import interface
import media.system import media.system
import util import util
@@ -22,56 +23,56 @@ log = logging.getLogger("bot")
def register_all_commands(bot): def register_all_commands(bot):
bot.register_command(constants.commands('joinme'), cmd_joinme, access_outside_channel=True) bot.register_command(commands('joinme'), cmd_joinme, access_outside_channel=True)
bot.register_command(constants.commands('user_ban'), cmd_user_ban, no_partial_match=True, admin=True) bot.register_command(commands('user_ban'), cmd_user_ban, no_partial_match=True, admin=True)
bot.register_command(constants.commands('user_unban'), cmd_user_unban, no_partial_match=True, admin=True) bot.register_command(commands('user_unban'), cmd_user_unban, no_partial_match=True, admin=True)
bot.register_command(constants.commands('url_ban_list'), cmd_url_ban_list, no_partial_match=True, admin=True) bot.register_command(commands('url_ban_list'), cmd_url_ban_list, no_partial_match=True, admin=True)
bot.register_command(constants.commands('url_ban'), cmd_url_ban, no_partial_match=True, admin=True) bot.register_command(commands('url_ban'), cmd_url_ban, no_partial_match=True, admin=True)
bot.register_command(constants.commands('url_unban'), cmd_url_unban, no_partial_match=True, admin=True) bot.register_command(commands('url_unban'), cmd_url_unban, no_partial_match=True, admin=True)
bot.register_command(constants.commands('play'), cmd_play) bot.register_command(commands('play'), cmd_play)
bot.register_command(constants.commands('pause'), cmd_pause) bot.register_command(commands('pause'), cmd_pause)
bot.register_command(constants.commands('play_file'), cmd_play_file) bot.register_command(commands('play_file'), cmd_play_file)
bot.register_command(constants.commands('play_file_match'), cmd_play_file_match) bot.register_command(commands('play_file_match'), cmd_play_file_match)
bot.register_command(constants.commands('play_url'), cmd_play_url) bot.register_command(commands('play_url'), cmd_play_url)
bot.register_command(constants.commands('play_playlist'), cmd_play_playlist) bot.register_command(commands('play_playlist'), cmd_play_playlist)
bot.register_command(constants.commands('play_radio'), cmd_play_radio) bot.register_command(commands('play_radio'), cmd_play_radio)
bot.register_command(constants.commands('play_tag'), cmd_play_tags) bot.register_command(commands('play_tag'), cmd_play_tags)
bot.register_command(constants.commands('rb_query'), cmd_rb_query) bot.register_command(commands('rb_query'), cmd_rb_query)
bot.register_command(constants.commands('rb_play'), cmd_rb_play) bot.register_command(commands('rb_play'), cmd_rb_play)
bot.register_command(constants.commands('yt_search'), cmd_yt_search) bot.register_command(commands('yt_search'), cmd_yt_search)
bot.register_command(constants.commands('yt_play'), cmd_yt_play) bot.register_command(commands('yt_play'), cmd_yt_play)
bot.register_command(constants.commands('help'), cmd_help, no_partial_match=False, access_outside_channel=True) bot.register_command(commands('help'), cmd_help, no_partial_match=False, access_outside_channel=True)
bot.register_command(constants.commands('stop'), cmd_stop) bot.register_command(commands('stop'), cmd_stop)
bot.register_command(constants.commands('clear'), cmd_clear) bot.register_command(commands('clear'), cmd_clear)
bot.register_command(constants.commands('kill'), cmd_kill, admin=True) bot.register_command(commands('kill'), cmd_kill, admin=True)
bot.register_command(constants.commands('update'), cmd_update, no_partial_match=True, admin=True) bot.register_command(commands('update'), cmd_update, no_partial_match=True, admin=True)
bot.register_command(constants.commands('stop_and_getout'), cmd_stop_and_getout) bot.register_command(commands('stop_and_getout'), cmd_stop_and_getout)
bot.register_command(constants.commands('volume'), cmd_volume) bot.register_command(commands('volume'), cmd_volume)
bot.register_command(constants.commands('ducking'), cmd_ducking) bot.register_command(commands('ducking'), cmd_ducking)
bot.register_command(constants.commands('ducking_threshold'), cmd_ducking_threshold) bot.register_command(commands('ducking_threshold'), cmd_ducking_threshold)
bot.register_command(constants.commands('ducking_volume'), cmd_ducking_volume) bot.register_command(commands('ducking_volume'), cmd_ducking_volume)
bot.register_command(constants.commands('current_music'), cmd_current_music) bot.register_command(commands('current_music'), cmd_current_music)
bot.register_command(constants.commands('skip'), cmd_skip) bot.register_command(commands('skip'), cmd_skip)
bot.register_command(constants.commands('last'), cmd_last) bot.register_command(commands('last'), cmd_last)
bot.register_command(constants.commands('remove'), cmd_remove) bot.register_command(commands('remove'), cmd_remove)
bot.register_command(constants.commands('list_file'), cmd_list_file) bot.register_command(commands('list_file'), cmd_list_file)
bot.register_command(constants.commands('queue'), cmd_queue) bot.register_command(commands('queue'), cmd_queue)
bot.register_command(constants.commands('random'), cmd_random) bot.register_command(commands('random'), cmd_random)
bot.register_command(constants.commands('repeat'), cmd_repeat) bot.register_command(commands('repeat'), cmd_repeat)
bot.register_command(constants.commands('mode'), cmd_mode) bot.register_command(commands('mode'), cmd_mode)
bot.register_command(constants.commands('add_tag'), cmd_add_tag) bot.register_command(commands('add_tag'), cmd_add_tag)
bot.register_command(constants.commands('remove_tag'), cmd_remove_tag) bot.register_command(commands('remove_tag'), cmd_remove_tag)
bot.register_command(constants.commands('find_tagged'), cmd_find_tagged) bot.register_command(commands('find_tagged'), cmd_find_tagged)
bot.register_command(constants.commands('search'), cmd_search_library) bot.register_command(commands('search'), cmd_search_library)
bot.register_command(constants.commands('add_from_shortlist'), cmd_shortlist) bot.register_command(commands('add_from_shortlist'), cmd_shortlist)
bot.register_command(constants.commands('delete_from_library'), cmd_delete_from_library) bot.register_command(commands('delete_from_library'), cmd_delete_from_library)
bot.register_command(constants.commands('drop_database'), cmd_drop_database, no_partial_match=True, admin=True) bot.register_command(commands('drop_database'), cmd_drop_database, no_partial_match=True, admin=True)
bot.register_command(constants.commands('rescan'), cmd_refresh_cache, no_partial_match=True) bot.register_command(commands('rescan'), cmd_refresh_cache, no_partial_match=True)
bot.register_command(constants.commands('requests_webinterface_access'), cmd_web_access) bot.register_command(commands('requests_webinterface_access'), cmd_web_access)
bot.register_command(constants.commands('add_webinterface_user'), cmd_web_user_add, admin=True) bot.register_command(commands('add_webinterface_user'), cmd_web_user_add, admin=True)
bot.register_command(constants.commands('remove_webinterface_user'), cmd_web_user_remove, admin=True) bot.register_command(commands('remove_webinterface_user'), cmd_web_user_remove, admin=True)
bot.register_command(constants.commands('list_webinterface_user'), cmd_web_user_list, admin=True) bot.register_command(commands('list_webinterface_user'), cmd_web_user_list, admin=True)
bot.register_command(constants.commands('change_user_password'), cmd_user_password, no_partial_match=True) bot.register_command(commands('change_user_password'), cmd_user_password, no_partial_match=True)
# Just for debug use # Just for debug use
bot.register_command('rtrms', cmd_real_time_rms, True) bot.register_command('rtrms', cmd_real_time_rms, True)
# bot.register_command('loop', cmd_loop_state, True) # bot.register_command('loop', cmd_loop_state, True)
@@ -160,7 +161,7 @@ def cmd_url_ban(bot, user, text, command, parameter):
var.cache.free_and_delete(item.id) var.cache.free_and_delete(item.id)
var.playlist.remove_by_id(item.id) var.playlist.remove_by_id(item.id)
else: else:
bot.send_msg(constants.strings('bad_parameter', command=command), text) bot.send_msg(tr('bad_parameter', command=command), text)
def cmd_url_ban_list(bot, user, text, command, parameter): def cmd_url_ban_list(bot, user, text, command, parameter):
@@ -184,14 +185,14 @@ def cmd_play(bot, user, text, command, parameter):
if params[0].isdigit() and 1 <= int(params[0]) <= len(var.playlist): if params[0].isdigit() and 1 <= int(params[0]) <= len(var.playlist):
index = int(params[0]) index = int(params[0])
else: else:
bot.send_msg(constants.strings('invalid_index', index=parameter), text) bot.send_msg(tr('invalid_index', index=parameter), text)
return return
if len(params) > 1: if len(params) > 1:
try: try:
start_at = util.parse_time(params[1]) start_at = util.parse_time(params[1])
except ValueError: except ValueError:
bot.send_msg(constants.strings('bad_parameter', command=command), text) bot.send_msg(tr('bad_parameter', command=command), text)
return return
if len(var.playlist) > 0: if len(var.playlist) > 0:
@@ -204,14 +205,14 @@ def cmd_play(bot, user, text, command, parameter):
bot.send_msg(var.playlist.current_item().format_current_playing(), text) bot.send_msg(var.playlist.current_item().format_current_playing(), text)
else: else:
bot.is_pause = False bot.is_pause = False
bot.send_msg(constants.strings('queue_empty'), text) bot.send_msg(tr('queue_empty'), text)
def cmd_pause(bot, user, text, command, parameter): def cmd_pause(bot, user, text, command, parameter):
global log global log
bot.pause() bot.pause()
bot.send_channel_msg(constants.strings('paused')) bot.send_channel_msg(tr('paused'))
def cmd_play_file(bot, user, text, command, parameter, do_not_refresh_cache=False): def cmd_play_file(bot, user, text, command, parameter, do_not_refresh_cache=False):
@@ -222,7 +223,7 @@ def cmd_play_file(bot, user, text, command, parameter, do_not_refresh_cache=Fals
if music_wrappers: if music_wrappers:
var.playlist.append(music_wrappers[0]) var.playlist.append(music_wrappers[0])
log.info("cmd: add to playlist: " + music_wrappers[0].format_debug_string()) log.info("cmd: add to playlist: " + music_wrappers[0].format_debug_string())
bot.send_msg(constants.strings('file_added', item=music_wrappers[0].format_song_string()), text) bot.send_msg(tr('file_added', item=music_wrappers[0].format_song_string()), text)
return return
# assume parameter is a folder # assume parameter is a folder
@@ -230,7 +231,7 @@ def cmd_play_file(bot, user, text, command, parameter, do_not_refresh_cache=Fals
.and_equal('type', 'file') .and_equal('type', 'file')
.and_like('path', parameter + '%')), user) .and_like('path', parameter + '%')), user)
if music_wrappers: if music_wrappers:
msgs = [constants.strings('multiple_file_added')] msgs = [tr('multiple_file_added')]
for music_wrapper in music_wrappers: for music_wrapper in music_wrappers:
log.info("cmd: add to playlist: " + music_wrapper.format_debug_string()) log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
@@ -249,20 +250,20 @@ def cmd_play_file(bot, user, text, command, parameter, do_not_refresh_cache=Fals
music_wrapper = get_cached_wrapper_from_dict(matches[0], user) music_wrapper = get_cached_wrapper_from_dict(matches[0], user)
var.playlist.append(music_wrapper) var.playlist.append(music_wrapper)
log.info("cmd: add to playlist: " + music_wrapper.format_debug_string()) log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
bot.send_msg(constants.strings('file_added', item=music_wrapper.format_song_string()), text) bot.send_msg(tr('file_added', item=music_wrapper.format_song_string()), text)
return return
elif len(matches) > 1: elif len(matches) > 1:
song_shortlist = matches song_shortlist = matches
msgs = [constants.strings('multiple_matches')] msgs = [tr('multiple_matches')]
for index, match in enumerate(matches): for index, match in enumerate(matches):
msgs.append("<b>{:d}</b> - <b>{:s}</b> ({:s})".format( msgs.append("<b>{:d}</b> - <b>{:s}</b> ({:s})".format(
index + 1, match['title'], match['path'])) index + 1, match['title'], match['path']))
msgs.append(constants.strings("shortlist_instruction")) msgs.append(tr("shortlist_instruction"))
send_multi_lines(bot, msgs, text) send_multi_lines(bot, msgs, text)
return return
if do_not_refresh_cache: if do_not_refresh_cache:
bot.send_msg(constants.strings("no_file"), text) bot.send_msg(tr("no_file"), text)
else: else:
var.cache.build_dir_cache() var.cache.build_dir_cache()
cmd_play_file(bot, user, text, command, parameter, do_not_refresh_cache=True) cmd_play_file(bot, user, text, command, parameter, do_not_refresh_cache=True)
@@ -273,7 +274,7 @@ def cmd_play_file_match(bot, user, text, command, parameter, do_not_refresh_cach
if parameter: if parameter:
file_dicts = var.music_db.query_music(Condition().and_equal('type', 'file')) file_dicts = var.music_db.query_music(Condition().and_equal('type', 'file'))
msgs = [constants.strings('multiple_file_added') + "<ul>"] msgs = [tr('multiple_file_added') + "<ul>"]
try: try:
count = 0 count = 0
music_wrappers = [] music_wrappers = []
@@ -299,16 +300,16 @@ def cmd_play_file_match(bot, user, text, command, parameter, do_not_refresh_cach
send_multi_lines_in_channel(bot, msgs, "") send_multi_lines_in_channel(bot, msgs, "")
else: else:
if do_not_refresh_cache: if do_not_refresh_cache:
bot.send_msg(constants.strings("no_file"), text) bot.send_msg(tr("no_file"), text)
else: else:
var.cache.build_dir_cache() var.cache.build_dir_cache()
cmd_play_file_match(bot, user, text, command, parameter, do_not_refresh_cache=True) cmd_play_file_match(bot, user, text, command, parameter, do_not_refresh_cache=True)
except re.error as e: except re.error as e:
msg = constants.strings('wrong_pattern', error=str(e)) msg = tr('wrong_pattern', error=str(e))
bot.send_msg(msg, text) bot.send_msg(msg, text)
else: else:
bot.send_msg(constants.strings('bad_parameter', command=command), text) bot.send_msg(tr('bad_parameter', command=command), text)
def cmd_play_url(bot, user, text, command, parameter): def cmd_play_url(bot, user, text, command, parameter):
@@ -320,12 +321,12 @@ def cmd_play_url(bot, user, text, command, parameter):
var.playlist.append(music_wrapper) var.playlist.append(music_wrapper)
log.info("cmd: add to playlist: " + music_wrapper.format_debug_string()) log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
bot.send_msg(constants.strings('file_added', item=music_wrapper.format_song_string()), text) bot.send_msg(tr('file_added', item=music_wrapper.format_song_string()), text)
if len(var.playlist) == 2: if len(var.playlist) == 2:
# If I am the second item on the playlist. (I am the next one!) # If I am the second item on the playlist. (I am the next one!)
bot.async_download_next() bot.async_download_next()
else: else:
bot.send_msg(constants.strings('bad_parameter', command=command), text) bot.send_msg(tr('bad_parameter', command=command), text)
def cmd_play_playlist(bot, user, text, command, parameter): def cmd_play_playlist(bot, user, text, command, parameter):
@@ -345,7 +346,7 @@ def cmd_play_playlist(bot, user, text, command, parameter):
for music in items: for music in items:
log.info("cmd: add to playlist: " + music.format_debug_string()) log.info("cmd: add to playlist: " + music.format_debug_string())
else: else:
bot.send_msg(constants.strings("playlist_fetching_failed"), text) bot.send_msg(tr("playlist_fetching_failed"), text)
def cmd_play_radio(bot, user, text, command, parameter): def cmd_play_radio(bot, user, text, command, parameter):
@@ -353,7 +354,7 @@ def cmd_play_radio(bot, user, text, command, parameter):
if not parameter: if not parameter:
all_radio = var.config.items('radio') all_radio = var.config.items('radio')
msg = constants.strings('preconfigurated_radio') msg = tr('preconfigurated_radio')
for i in all_radio: for i in all_radio:
comment = "" comment = ""
if len(i[1].split(maxsplit=1)) == 2: if len(i[1].split(maxsplit=1)) == 2:
@@ -370,9 +371,9 @@ def cmd_play_radio(bot, user, text, command, parameter):
var.playlist.append(music_wrapper) var.playlist.append(music_wrapper)
log.info("cmd: add to playlist: " + music_wrapper.format_debug_string()) log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
bot.send_msg(constants.strings('file_added', item=music_wrapper.format_song_string()), text) bot.send_msg(tr('file_added', item=music_wrapper.format_song_string()), text)
else: else:
bot.send_msg(constants.strings('bad_url'), text) bot.send_msg(tr('bad_url'), text)
def cmd_rb_query(bot, user, text, command, parameter): def cmd_rb_query(bot, user, text, command, parameter):
@@ -381,13 +382,13 @@ def cmd_rb_query(bot, user, text, command, parameter):
log.info('cmd: Querying radio stations') log.info('cmd: Querying radio stations')
if not parameter: if not parameter:
log.debug('rbquery without parameter') log.debug('rbquery without parameter')
msg = constants.strings('rb_query_empty') msg = tr('rb_query_empty')
bot.send_msg(msg, text) bot.send_msg(msg, text)
else: else:
log.debug('cmd: Found query parameter: ' + parameter) log.debug('cmd: Found query parameter: ' + parameter)
rb = RadioBrowser() rb = RadioBrowser()
rb_stations = rb.search(name=parameter, name_exact=False) rb_stations = rb.search(name=parameter, name_exact=False)
msg = constants.strings('rb_query_result') msg = tr('rb_query_result')
msg += '\n<table><tr><th>!rbplay ID</th><th>Station Name</th><th>Genre</th><th>Codec/Bitrate</th><th>Country</th></tr>' msg += '\n<table><tr><th>!rbplay ID</th><th>Station Name</th><th>Genre</th><th>Codec/Bitrate</th><th>Country</th></tr>'
if not rb_stations: if not rb_stations:
log.debug(f"cmd: No matches found for rbquery {parameter}") log.debug(f"cmd: No matches found for rbquery {parameter}")
@@ -408,7 +409,7 @@ def cmd_rb_query(bot, user, text, command, parameter):
# Shorten message if message too long (stage I) # Shorten message if message too long (stage I)
else: else:
log.debug('Result too long stage I') log.debug('Result too long stage I')
msg = constants.strings('rb_query_result') + ' (shortened L1)' msg = tr('rb_query_result') + ' (shortened L1)'
msg += '\n<table><tr><th>!rbplay ID</th><th>Station Name</th></tr>' msg += '\n<table><tr><th>!rbplay ID</th><th>Station Name</th></tr>'
for s in rb_stations: for s in rb_stations:
station_id = s['stationuuid'] station_id = s['stationuuid']
@@ -420,7 +421,7 @@ def cmd_rb_query(bot, user, text, command, parameter):
# Shorten message if message too long (stage II) # Shorten message if message too long (stage II)
else: else:
log.debug('Result too long stage II') log.debug('Result too long stage II')
msg = constants.strings('rb_query_result') + ' (shortened L2)' msg = tr('rb_query_result') + ' (shortened L2)'
msg += '!rbplay ID - Station Name' msg += '!rbplay ID - Station Name'
for s in rb_stations: for s in rb_stations:
station_id = s['stationuuid'] station_id = s['stationuuid']
@@ -439,7 +440,7 @@ def cmd_rb_play(bot, user, text, command, parameter):
log.debug('cmd: Play a station by ID') log.debug('cmd: Play a station by ID')
if not parameter: if not parameter:
log.debug('rbplay without parameter') log.debug('rbplay without parameter')
msg = constants.strings('rb_play_empty') msg = tr('rb_play_empty')
bot.send_msg(msg, text) bot.send_msg(msg, text)
else: else:
log.debug('cmd: Retreiving url for station ID ' + parameter) log.debug('cmd: Retreiving url for station ID ' + parameter)
@@ -488,9 +489,9 @@ def cmd_yt_search(bot, user, text, command, parameter):
'title': result[1] 'title': result[1]
} for result in yt_last_result[yt_last_page * item_per_page: item_per_page]] } for result in yt_last_result[yt_last_page * item_per_page: item_per_page]]
msg = _yt_format_result(yt_last_result, yt_last_page * item_per_page, item_per_page) msg = _yt_format_result(yt_last_result, yt_last_page * item_per_page, item_per_page)
bot.send_msg(constants.strings('yt_result', result_table=msg), text) bot.send_msg(tr('yt_result', result_table=msg), text)
else: else:
bot.send_msg(constants.strings('yt_no_more'), text) bot.send_msg(tr('yt_no_more'), text)
# if query # if query
else: else:
@@ -501,11 +502,11 @@ def cmd_yt_search(bot, user, text, command, parameter):
song_shortlist = [{'type': 'url', 'url': "https://www.youtube.com/watch?v=" + result[0]} song_shortlist = [{'type': 'url', 'url': "https://www.youtube.com/watch?v=" + result[0]}
for result in results[0: item_per_page]] for result in results[0: item_per_page]]
msg = _yt_format_result(results, 0, item_per_page) msg = _yt_format_result(results, 0, item_per_page)
bot.send_msg(constants.strings('yt_result', result_table=msg), text) bot.send_msg(tr('yt_result', result_table=msg), text)
else: else:
bot.send_msg(constants.strings('yt_query_error'), text) bot.send_msg(tr('yt_query_error'), text)
else: else:
bot.send_msg(constants.strings('bad_parameter', command=command), text) bot.send_msg(tr('bad_parameter', command=command), text)
def _yt_format_result(results, start, count): def _yt_format_result(results, start, count):
@@ -529,16 +530,16 @@ def cmd_yt_play(bot, user, text, command, parameter):
url = "https://www.youtube.com/watch?v=" + yt_last_result[0][0] url = "https://www.youtube.com/watch?v=" + yt_last_result[0][0]
cmd_play_url(bot, user, text, command, url) cmd_play_url(bot, user, text, command, url)
else: else:
bot.send_msg(constants.strings('yt_query_error'), text) bot.send_msg(tr('yt_query_error'), text)
else: else:
bot.send_msg(constants.strings('bad_parameter', command=command), text) bot.send_msg(tr('bad_parameter', command=command), text)
def cmd_help(bot, user, text, command, parameter): def cmd_help(bot, user, text, command, parameter):
global log global log
bot.send_msg(constants.strings('help'), text) bot.send_msg(tr('help'), text)
if bot.is_admin(user): if bot.is_admin(user):
bot.send_msg(constants.strings('admin_help'), text) bot.send_msg(tr('admin_help'), text)
def cmd_stop(bot, user, text, command, parameter): def cmd_stop(bot, user, text, command, parameter):
@@ -549,14 +550,14 @@ def cmd_stop(bot, user, text, command, parameter):
cmd_clear(bot, user, text, command, parameter) cmd_clear(bot, user, text, command, parameter)
else: else:
bot.stop() bot.stop()
bot.send_msg(constants.strings('stopped'), text) bot.send_msg(tr('stopped'), text)
def cmd_clear(bot, user, text, command, parameter): def cmd_clear(bot, user, text, command, parameter):
global log global log
bot.clear() bot.clear()
bot.send_msg(constants.strings('cleared'), text) bot.send_msg(tr('cleared'), text)
def cmd_kill(bot, user, text, command, parameter): def cmd_kill(bot, user, text, command, parameter):
@@ -571,12 +572,12 @@ def cmd_update(bot, user, text, command, parameter):
if bot.is_admin(user): if bot.is_admin(user):
bot.mumble.users[text.actor].send_text_message( bot.mumble.users[text.actor].send_text_message(
constants.strings('start_updating')) tr('start_updating'))
msg = util.update(bot.version) msg = util.update(bot.version)
bot.mumble.users[text.actor].send_text_message(msg) bot.mumble.users[text.actor].send_text_message(msg)
else: else:
bot.mumble.users[text.actor].send_text_message( bot.mumble.users[text.actor].send_text_message(
constants.strings('not_admin')) tr('not_admin'))
def cmd_stop_and_getout(bot, user, text, command, parameter): def cmd_stop_and_getout(bot, user, text, command, parameter):
@@ -595,11 +596,11 @@ def cmd_volume(bot, user, text, command, parameter):
# The volume is a percentage # The volume is a percentage
if parameter and parameter.isdigit() and 0 <= int(parameter) <= 100: if parameter and parameter.isdigit() and 0 <= int(parameter) <= 100:
bot.volume_helper.set_volume(float(parameter) / 100.0) bot.volume_helper.set_volume(float(parameter) / 100.0)
bot.send_msg(constants.strings('change_volume', volume=parameter, user=bot.mumble.users[text.actor]['name']), text) bot.send_msg(tr('change_volume', volume=parameter, user=bot.mumble.users[text.actor]['name']), text)
var.db.set('bot', 'volume', str(float(parameter) / 100.0)) var.db.set('bot', 'volume', str(float(parameter) / 100.0))
log.info(f'cmd: volume set to {float(parameter) / 100.0}') log.info(f'cmd: volume set to {float(parameter) / 100.0}')
else: else:
bot.send_msg(constants.strings('current_volume', volume=int(bot.volume_helper.plain_volume_set * 100)), text) bot.send_msg(tr('current_volume', volume=int(bot.volume_helper.plain_volume_set * 100)), text)
def cmd_ducking(bot, user, text, command, parameter): def cmd_ducking(bot, user, text, command, parameter):
@@ -608,7 +609,7 @@ def cmd_ducking(bot, user, text, command, parameter):
if parameter == "" or parameter == "on": if parameter == "" or parameter == "on":
bot.is_ducking = True bot.is_ducking = True
var.db.set('bot', 'ducking', True) var.db.set('bot', 'ducking', True)
bot.mumble.callbacks.set_callback(pymumble.constants.PYMUMBLE_CLBK_SOUNDRECEIVED, bot.ducking_sound_received) bot.mumble.callbacks.set_callback(pymumble.c.PYMUMBLE_CLBK_SOUNDRECEIVED, bot.ducking_sound_received)
bot.mumble.set_receive_sound(True) bot.mumble.set_receive_sound(True)
log.info('cmd: ducking is on') log.info('cmd: ducking is on')
msg = "Ducking on." msg = "Ducking on."
@@ -641,11 +642,11 @@ def cmd_ducking_volume(bot, user, text, command, parameter):
# The volume is a percentage # The volume is a percentage
if parameter and parameter.isdigit() and 0 <= int(parameter) <= 100: if parameter and parameter.isdigit() and 0 <= int(parameter) <= 100:
bot.volume_helper.set_ducking_volume(float(parameter) / 100.0) bot.volume_helper.set_ducking_volume(float(parameter) / 100.0)
bot.send_msg(constants.strings('change_ducking_volume', volume=parameter, user=bot.mumble.users[text.actor]['name']), text) bot.send_msg(tr('change_ducking_volume', volume=parameter, user=bot.mumble.users[text.actor]['name']), text)
var.db.set('bot', 'ducking_volume', float(parameter) / 100.0) var.db.set('bot', 'ducking_volume', float(parameter) / 100.0)
log.info(f'cmd: volume on ducking set to {parameter}') log.info(f'cmd: volume on ducking set to {parameter}')
else: else:
bot.send_msg(constants.strings('current_ducking_volume', volume=int(bot.volume_helper.plain_ducking_volume_set * 100)), text) bot.send_msg(tr('current_ducking_volume', volume=int(bot.volume_helper.plain_ducking_volume_set * 100)), text)
def cmd_current_music(bot, user, text, command, parameter): def cmd_current_music(bot, user, text, command, parameter):
@@ -654,7 +655,7 @@ def cmd_current_music(bot, user, text, command, parameter):
if len(var.playlist) > 0: if len(var.playlist) > 0:
bot.send_msg(var.playlist.current_item().format_current_playing(), text) bot.send_msg(var.playlist.current_item().format_current_playing(), text)
else: else:
bot.send_msg(constants.strings('not_playing'), text) bot.send_msg(tr('not_playing'), text)
def cmd_skip(bot, user, text, command, parameter): def cmd_skip(bot, user, text, command, parameter):
@@ -667,7 +668,7 @@ def cmd_skip(bot, user, text, command, parameter):
bot.wait_for_ready = True bot.wait_for_ready = True
if len(var.playlist) == 0: if len(var.playlist) == 0:
bot.send_msg(constants.strings('queue_empty'), text) bot.send_msg(tr('queue_empty'), text)
def cmd_last(bot, user, text, command, parameter): def cmd_last(bot, user, text, command, parameter):
@@ -677,7 +678,7 @@ def cmd_last(bot, user, text, command, parameter):
bot.interrupt() bot.interrupt()
var.playlist.point_to(len(var.playlist) - 1 - 1) var.playlist.point_to(len(var.playlist) - 1 - 1)
else: else:
bot.send_msg(constants.strings('queue_empty'), text) bot.send_msg(tr('queue_empty'), text)
def cmd_remove(bot, user, text, command, parameter): def cmd_remove(bot, user, text, command, parameter):
@@ -690,7 +691,7 @@ def cmd_remove(bot, user, text, command, parameter):
if index == var.playlist.current_index: if index == var.playlist.current_index:
removed = var.playlist[index] removed = var.playlist[index]
bot.send_msg(constants.strings('removing_item', bot.send_msg(tr('removing_item',
item=removed.format_title()), text) item=removed.format_title()), text)
log.info("cmd: delete from playlist: " + removed.format_debug_string()) log.info("cmd: delete from playlist: " + removed.format_debug_string())
@@ -710,7 +711,7 @@ def cmd_remove(bot, user, text, command, parameter):
var.playlist.remove(index) var.playlist.remove(index)
else: else:
bot.send_msg(constants.strings('bad_parameter', command=command), text) bot.send_msg(tr('bad_parameter', command=command), text)
def cmd_list_file(bot, user, text, command, parameter): def cmd_list_file(bot, user, text, command, parameter):
@@ -722,7 +723,7 @@ def cmd_list_file(bot, user, text, command, parameter):
song_shortlist = files song_shortlist = files
msgs = [constants.strings("multiple_file_found") + "<ul>"] msgs = [tr("multiple_file_found") + "<ul>"]
try: try:
count = 0 count = 0
for index, file in enumerate(files): for index, file in enumerate(files):
@@ -739,14 +740,14 @@ def cmd_list_file(bot, user, text, command, parameter):
if count != 0: if count != 0:
msgs.append("</ul>") msgs.append("</ul>")
if count > ITEMS_PER_PAGE: if count > ITEMS_PER_PAGE:
msgs.append(constants.strings("records_omitted")) msgs.append(tr("records_omitted"))
msgs.append(constants.strings("shortlist_instruction")) msgs.append(tr("shortlist_instruction"))
send_multi_lines(bot, msgs, text, "") send_multi_lines(bot, msgs, text, "")
else: else:
bot.send_msg(constants.strings("no_file"), text) bot.send_msg(tr("no_file"), text)
except re.error as e: except re.error as e:
msg = constants.strings('wrong_pattern', error=str(e)) msg = tr('wrong_pattern', error=str(e))
bot.send_msg(msg, text) bot.send_msg(msg, text)
@@ -754,10 +755,10 @@ def cmd_queue(bot, user, text, command, parameter):
global log global log
if len(var.playlist) == 0: if len(var.playlist) == 0:
msg = constants.strings('queue_empty') msg = tr('queue_empty')
bot.send_msg(msg, text) bot.send_msg(msg, text)
else: else:
msgs = [constants.strings('queue_contents')] msgs = [tr('queue_contents')]
for i, music in enumerate(var.playlist): for i, music in enumerate(var.playlist):
tags = '' tags = ''
if len(music.item().tags) > 0: if len(music.item().tags) > 0:
@@ -797,24 +798,24 @@ def cmd_repeat(bot, user, text, command, parameter):
) )
log.info("bot: add to playlist: " + music.format_debug_string()) log.info("bot: add to playlist: " + music.format_debug_string())
bot.send_channel_msg(constants.strings("repeat", song=music.format_song_string(), n=str(repeat))) bot.send_channel_msg(tr("repeat", song=music.format_song_string(), n=str(repeat)))
else: else:
bot.send_msg(constants.strings("queue_empty"), text) bot.send_msg(tr("queue_empty"), text)
def cmd_mode(bot, user, text, command, parameter): def cmd_mode(bot, user, text, command, parameter):
global log global log
if not parameter: if not parameter:
bot.send_msg(constants.strings("current_mode", mode=var.playlist.mode), text) bot.send_msg(tr("current_mode", mode=var.playlist.mode), text)
return return
if parameter not in ["one-shot", "repeat", "random", "autoplay"]: if parameter not in ["one-shot", "repeat", "random", "autoplay"]:
bot.send_msg(constants.strings('unknown_mode', mode=parameter), text) bot.send_msg(tr('unknown_mode', mode=parameter), text)
else: else:
var.db.set('playlist', 'playback_mode', parameter) var.db.set('playlist', 'playback_mode', parameter)
var.playlist = media.playlist.get_playlist(parameter, var.playlist) var.playlist = media.playlist.get_playlist(parameter, var.playlist)
log.info(f"command: playback mode changed to {parameter}.") log.info(f"command: playback mode changed to {parameter}.")
bot.send_msg(constants.strings("change_mode", mode=var.playlist.mode, bot.send_msg(tr("change_mode", mode=var.playlist.mode,
user=bot.mumble.users[text.actor]['name']), text) user=bot.mumble.users[text.actor]['name']), text)
if parameter == "random": if parameter == "random":
bot.interrupt() bot.interrupt()
@@ -823,10 +824,10 @@ def cmd_mode(bot, user, text, command, parameter):
def cmd_play_tags(bot, user, text, command, parameter): def cmd_play_tags(bot, user, text, command, parameter):
if not parameter: if not parameter:
bot.send_msg(constants.strings('bad_parameter', command=command), text) bot.send_msg(tr('bad_parameter', command=command), text)
return return
msgs = [constants.strings('multiple_file_added') + "<ul>"] msgs = [tr('multiple_file_added') + "<ul>"]
count = 0 count = 0
tags = parameter.split(",") tags = parameter.split(",")
@@ -842,7 +843,7 @@ def cmd_play_tags(bot, user, text, command, parameter):
var.playlist.extend(music_wrappers) var.playlist.extend(music_wrappers)
send_multi_lines_in_channel(bot, msgs, "") send_multi_lines_in_channel(bot, msgs, "")
else: else:
bot.send_msg(constants.strings("no_file"), text) bot.send_msg(tr("no_file"), text)
def cmd_add_tag(bot, user, text, command, parameter): def cmd_add_tag(bot, user, text, command, parameter):
@@ -866,7 +867,7 @@ def cmd_add_tag(bot, user, text, command, parameter):
if index.isdigit() and 1 <= int(index) <= len(var.playlist): if index.isdigit() and 1 <= int(index) <= len(var.playlist):
var.playlist[int(index) - 1].add_tags(tags) var.playlist[int(index) - 1].add_tags(tags)
log.info(f"cmd: add tags {', '.join(tags)} to song {var.playlist[int(index) - 1].format_debug_string()}") log.info(f"cmd: add tags {', '.join(tags)} to song {var.playlist[int(index) - 1].format_debug_string()}")
bot.send_msg(constants.strings("added_tags", bot.send_msg(tr("added_tags",
tags=", ".join(tags), tags=", ".join(tags),
song=var.playlist[int(index) - 1].format_title()), text) song=var.playlist[int(index) - 1].format_title()), text)
return return
@@ -875,10 +876,10 @@ def cmd_add_tag(bot, user, text, command, parameter):
for item in var.playlist: for item in var.playlist:
item.add_tags(tags) item.add_tags(tags)
log.info(f"cmd: add tags {', '.join(tags)} to song {item.format_debug_string()}") log.info(f"cmd: add tags {', '.join(tags)} to song {item.format_debug_string()}")
bot.send_msg(constants.strings("added_tags_to_all", tags=", ".join(tags)), text) bot.send_msg(tr("added_tags_to_all", tags=", ".join(tags)), text)
return return
bot.send_msg(constants.strings('bad_parameter', command=command), text) bot.send_msg(tr('bad_parameter', command=command), text)
def cmd_remove_tag(bot, user, text, command, parameter): def cmd_remove_tag(bot, user, text, command, parameter):
@@ -903,14 +904,14 @@ def cmd_remove_tag(bot, user, text, command, parameter):
if tags[0] != "*": if tags[0] != "*":
var.playlist[int(index) - 1].remove_tags(tags) var.playlist[int(index) - 1].remove_tags(tags)
log.info(f"cmd: remove tags {', '.join(tags)} from song {var.playlist[int(index) - 1].format_debug_string()}") log.info(f"cmd: remove tags {', '.join(tags)} from song {var.playlist[int(index) - 1].format_debug_string()}")
bot.send_msg(constants.strings("removed_tags", bot.send_msg(tr("removed_tags",
tags=", ".join(tags), tags=", ".join(tags),
song=var.playlist[int(index) - 1].format_title()), text) song=var.playlist[int(index) - 1].format_title()), text)
return return
else: else:
var.playlist[int(index) - 1].clear_tags() var.playlist[int(index) - 1].clear_tags()
log.info(f"cmd: clear tags from song {var.playlist[int(index) - 1].format_debug_string()}") log.info(f"cmd: clear tags from song {var.playlist[int(index) - 1].format_debug_string()}")
bot.send_msg(constants.strings("cleared_tags", bot.send_msg(tr("cleared_tags",
song=var.playlist[int(index) - 1].format_title()), text) song=var.playlist[int(index) - 1].format_title()), text)
return return
@@ -919,26 +920,26 @@ def cmd_remove_tag(bot, user, text, command, parameter):
for item in var.playlist: for item in var.playlist:
item.remove_tags(tags) item.remove_tags(tags)
log.info(f"cmd: remove tags {', '.join(tags)} from song {item.format_debug_string()}") log.info(f"cmd: remove tags {', '.join(tags)} from song {item.format_debug_string()}")
bot.send_msg(constants.strings("removed_tags_from_all", tags=", ".join(tags)), text) bot.send_msg(tr("removed_tags_from_all", tags=", ".join(tags)), text)
return return
else: else:
for item in var.playlist: for item in var.playlist:
item.clear_tags() item.clear_tags()
log.info(f"cmd: clear tags from song {item.format_debug_string()}") log.info(f"cmd: clear tags from song {item.format_debug_string()}")
bot.send_msg(constants.strings("cleared_tags_from_all"), text) bot.send_msg(tr("cleared_tags_from_all"), text)
return return
bot.send_msg(constants.strings('bad_parameter', command=command), text) bot.send_msg(tr('bad_parameter', command=command), text)
def cmd_find_tagged(bot, user, text, command, parameter): def cmd_find_tagged(bot, user, text, command, parameter):
global song_shortlist global song_shortlist
if not parameter: if not parameter:
bot.send_msg(constants.strings('bad_parameter', command=command), text) bot.send_msg(tr('bad_parameter', command=command), text)
return return
msgs = [constants.strings('multiple_file_found') + "<ul>"] msgs = [tr('multiple_file_found') + "<ul>"]
count = 0 count = 0
tags = parameter.split(",") tags = parameter.split(",")
@@ -957,20 +958,20 @@ def cmd_find_tagged(bot, user, text, command, parameter):
if count != 0: if count != 0:
msgs.append("</ul>") msgs.append("</ul>")
if count > ITEMS_PER_PAGE: if count > ITEMS_PER_PAGE:
msgs.append(constants.strings("records_omitted")) msgs.append(tr("records_omitted"))
msgs.append(constants.strings("shortlist_instruction")) msgs.append(tr("shortlist_instruction"))
send_multi_lines(bot, msgs, text, "") send_multi_lines(bot, msgs, text, "")
else: else:
bot.send_msg(constants.strings("no_file"), text) bot.send_msg(tr("no_file"), text)
def cmd_search_library(bot, user, text, command, parameter): def cmd_search_library(bot, user, text, command, parameter):
global song_shortlist global song_shortlist
if not parameter: if not parameter:
bot.send_msg(constants.strings('bad_parameter', command=command), text) bot.send_msg(tr('bad_parameter', command=command), text)
return return
msgs = [constants.strings('multiple_file_found') + "<ul>"] msgs = [tr('multiple_file_found') + "<ul>"]
count = 0 count = 0
_keywords = parameter.split(" ") _keywords = parameter.split(" ")
@@ -988,7 +989,7 @@ def cmd_search_library(bot, user, text, command, parameter):
music_wrapper = get_cached_wrapper(items[0], user) music_wrapper = get_cached_wrapper(items[0], user)
var.playlist.append(music_wrapper) var.playlist.append(music_wrapper)
log.info("cmd: add to playlist: " + music_wrapper.format_debug_string()) log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
bot.send_channel_msg(constants.strings('file_added', item=music_wrapper.format_song_string())) bot.send_channel_msg(tr('file_added', item=music_wrapper.format_song_string()))
else: else:
for item in items: for item in items:
count += 1 count += 1
@@ -1002,19 +1003,19 @@ def cmd_search_library(bot, user, text, command, parameter):
if count != 0: if count != 0:
msgs.append("</ul>") msgs.append("</ul>")
if count > ITEMS_PER_PAGE: if count > ITEMS_PER_PAGE:
msgs.append(constants.strings("records_omitted")) msgs.append(tr("records_omitted"))
msgs.append(constants.strings("shortlist_instruction")) msgs.append(tr("shortlist_instruction"))
send_multi_lines(bot, msgs, text, "") send_multi_lines(bot, msgs, text, "")
else: else:
bot.send_msg(constants.strings("no_file"), text) bot.send_msg(tr("no_file"), text)
else: else:
bot.send_msg(constants.strings("no_file"), text) bot.send_msg(tr("no_file"), text)
def cmd_shortlist(bot, user, text, command, parameter): def cmd_shortlist(bot, user, text, command, parameter):
global song_shortlist, log global song_shortlist, log
if parameter.strip() == "*": if parameter.strip() == "*":
msgs = [constants.strings('multiple_file_added') + "<ul>"] msgs = [tr('multiple_file_added') + "<ul>"]
music_wrappers = [] music_wrappers = []
for kwargs in song_shortlist: for kwargs in song_shortlist:
kwargs['user'] = user kwargs['user'] = user
@@ -1032,11 +1033,11 @@ def cmd_shortlist(bot, user, text, command, parameter):
try: try:
indexes = [int(i) for i in parameter.split(" ")] indexes = [int(i) for i in parameter.split(" ")]
except ValueError: except ValueError:
bot.send_msg(constants.strings('bad_parameter', command=command), text) bot.send_msg(tr('bad_parameter', command=command), text)
return return
if len(indexes) > 1: if len(indexes) > 1:
msgs = [constants.strings('multiple_file_added') + "<ul>"] msgs = [tr('multiple_file_added') + "<ul>"]
music_wrappers = [] music_wrappers = []
for index in indexes: for index in indexes:
if 1 <= index <= len(song_shortlist): if 1 <= index <= len(song_shortlist):
@@ -1048,7 +1049,7 @@ def cmd_shortlist(bot, user, text, command, parameter):
msgs.append("<li>[{}] <b>{}</b></li>".format(music_wrapper.item().type, music_wrapper.item().title)) msgs.append("<li>[{}] <b>{}</b></li>".format(music_wrapper.item().type, music_wrapper.item().title))
else: else:
var.playlist.extend(music_wrappers) var.playlist.extend(music_wrappers)
bot.send_msg(constants.strings('bad_parameter', command=command), text) bot.send_msg(tr('bad_parameter', command=command), text)
return return
var.playlist.extend(music_wrappers) var.playlist.extend(music_wrappers)
@@ -1064,10 +1065,10 @@ def cmd_shortlist(bot, user, text, command, parameter):
music_wrapper = get_cached_wrapper_from_scrap(**kwargs) music_wrapper = get_cached_wrapper_from_scrap(**kwargs)
var.playlist.append(music_wrapper) var.playlist.append(music_wrapper)
log.info("cmd: add to playlist: " + music_wrapper.format_debug_string()) log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
bot.send_channel_msg(constants.strings('file_added', item=music_wrapper.format_song_string())) bot.send_channel_msg(tr('file_added', item=music_wrapper.format_song_string()))
return return
bot.send_msg(constants.strings('bad_parameter', command=command), text) bot.send_msg(tr('bad_parameter', command=command), text)
def cmd_delete_from_library(bot, user, text, command, parameter): def cmd_delete_from_library(bot, user, text, command, parameter):
@@ -1075,11 +1076,11 @@ def cmd_delete_from_library(bot, user, text, command, parameter):
try: try:
indexes = [int(i) for i in parameter.split(" ")] indexes = [int(i) for i in parameter.split(" ")]
except ValueError: except ValueError:
bot.send_msg(constants.strings('bad_parameter', command=command), text) bot.send_msg(tr('bad_parameter', command=command), text)
return return
if len(indexes) > 1: if len(indexes) > 1:
msgs = [constants.strings('multiple_file_added') + "<ul>"] msgs = [tr('multiple_file_added') + "<ul>"]
count = 0 count = 0
for index in indexes: for index in indexes:
if 1 <= index <= len(song_shortlist): if 1 <= index <= len(song_shortlist):
@@ -1092,11 +1093,11 @@ def cmd_delete_from_library(bot, user, text, command, parameter):
var.cache.free_and_delete(music_dict['id']) var.cache.free_and_delete(music_dict['id'])
count += 1 count += 1
else: else:
bot.send_msg(constants.strings('bad_parameter', command=command), text) bot.send_msg(tr('bad_parameter', command=command), text)
return return
if count == 0: if count == 0:
bot.send_msg(constants.strings('bad_parameter', command=command), text) bot.send_msg(tr('bad_parameter', command=command), text)
return return
msgs.append("</ul>") msgs.append("</ul>")
@@ -1108,13 +1109,13 @@ def cmd_delete_from_library(bot, user, text, command, parameter):
music_dict = song_shortlist[index - 1] music_dict = song_shortlist[index - 1]
if 'id' in music_dict: if 'id' in music_dict:
music_wrapper = get_cached_wrapper_by_id(music_dict['id'], user) music_wrapper = get_cached_wrapper_by_id(music_dict['id'], user)
bot.send_msg(constants.strings('file_deleted', item=music_wrapper.format_song_string()), text) bot.send_msg(tr('file_deleted', item=music_wrapper.format_song_string()), text)
log.info("cmd: remove from library: " + music_wrapper.format_debug_string()) log.info("cmd: remove from library: " + music_wrapper.format_debug_string())
var.playlist.remove_by_id(music_dict['id']) var.playlist.remove_by_id(music_dict['id'])
var.cache.free_and_delete(music_dict['id']) var.cache.free_and_delete(music_dict['id'])
return return
bot.send_msg(constants.strings('bad_parameter', command=command), text) bot.send_msg(tr('bad_parameter', command=command), text)
def cmd_drop_database(bot, user, text, command, parameter): def cmd_drop_database(bot, user, text, command, parameter):
@@ -1126,9 +1127,9 @@ def cmd_drop_database(bot, user, text, command, parameter):
var.music_db.drop_table() var.music_db.drop_table()
var.music_db = MusicDatabase(var.settings_db_path) var.music_db = MusicDatabase(var.settings_db_path)
log.info("command: database dropped.") log.info("command: database dropped.")
bot.send_msg(constants.strings('database_dropped'), text) bot.send_msg(tr('database_dropped'), text)
else: else:
bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin')) bot.mumble.users[text.actor].send_text_message(tr('not_admin'))
def cmd_refresh_cache(bot, user, text, command, parameter): def cmd_refresh_cache(bot, user, text, command, parameter):
@@ -1136,9 +1137,9 @@ def cmd_refresh_cache(bot, user, text, command, parameter):
if bot.is_admin(user): if bot.is_admin(user):
var.cache.build_dir_cache() var.cache.build_dir_cache()
log.info("command: Local file cache refreshed.") log.info("command: Local file cache refreshed.")
bot.send_msg(constants.strings('cache_refreshed'), text) bot.send_msg(tr('cache_refreshed'), text)
else: else:
bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin')) bot.mumble.users[text.actor].send_text_message(tr('not_admin'))
def cmd_web_access(bot, user, text, command, parameter): def cmd_web_access(bot, user, text, command, parameter):
@@ -1164,12 +1165,12 @@ def cmd_web_access(bot, user, text, command, parameter):
else: else:
access_address = var.config.get("webinterface", "access_address") access_address = var.config.get("webinterface", "access_address")
bot.send_msg(constants.strings('webpage_address', address=access_address), text) bot.send_msg(tr('webpage_address', address=access_address), text)
def cmd_user_password(bot, user, text, command, parameter): def cmd_user_password(bot, user, text, command, parameter):
if not parameter: if not parameter:
bot.send_msg(constants.strings('bad_parameter', command=command), text) bot.send_msg(tr('bad_parameter', command=command), text)
return return
user_info = var.db.get("user", user, fallback='{}') user_info = var.db.get("user", user, fallback='{}')
@@ -1178,12 +1179,12 @@ def cmd_user_password(bot, user, text, command, parameter):
var.db.set("user", user, json.dumps(user_dict)) var.db.set("user", user, json.dumps(user_dict))
bot.send_msg(constants.strings('user_password_set'), text) bot.send_msg(tr('user_password_set'), text)
def cmd_web_user_add(bot, user, text, command, parameter): def cmd_web_user_add(bot, user, text, command, parameter):
if not parameter: if not parameter:
bot.send_msg(constants.strings('bad_parameter', command=command), text) bot.send_msg(tr('bad_parameter', command=command), text)
return return
auth_method = var.config.get("webinterface", "auth_method") auth_method = var.config.get("webinterface", "auth_method")
@@ -1193,14 +1194,14 @@ def cmd_web_user_add(bot, user, text, command, parameter):
if parameter not in web_users: if parameter not in web_users:
web_users.append(parameter) web_users.append(parameter)
var.db.set("privilege", "web_access", json.dumps(web_users)) var.db.set("privilege", "web_access", json.dumps(web_users))
bot.send_msg(constants.strings('web_user_list', users=", ".join(web_users)), text) bot.send_msg(tr('web_user_list', users=", ".join(web_users)), text)
else: else:
bot.send_msg(constants.strings('command_disabled', command=command), text) bot.send_msg(tr('command_disabled', command=command), text)
def cmd_web_user_remove(bot, user, text, command, parameter): def cmd_web_user_remove(bot, user, text, command, parameter):
if not parameter: if not parameter:
bot.send_msg(constants.strings('bad_parameter', command=command), text) bot.send_msg(tr('bad_parameter', command=command), text)
return return
auth_method = var.config.get("webinterface", "auth_method") auth_method = var.config.get("webinterface", "auth_method")
@@ -1210,9 +1211,9 @@ def cmd_web_user_remove(bot, user, text, command, parameter):
if parameter in web_users: if parameter in web_users:
web_users.remove(parameter) web_users.remove(parameter)
var.db.set("privilege", "web_access", json.dumps(web_users)) var.db.set("privilege", "web_access", json.dumps(web_users))
bot.send_msg(constants.strings('web_user_list', users=", ".join(web_users)), text) bot.send_msg(tr('web_user_list', users=", ".join(web_users)), text)
else: else:
bot.send_msg(constants.strings('command_disabled', command=command), text) bot.send_msg(tr('command_disabled', command=command), text)
def cmd_web_user_list(bot, user, text, command, parameter): def cmd_web_user_list(bot, user, text, command, parameter):
@@ -1220,9 +1221,9 @@ def cmd_web_user_list(bot, user, text, command, parameter):
if auth_method == 'password': if auth_method == 'password':
web_users = json.loads(var.db.get("privilege", "web_access", fallback='[]')) web_users = json.loads(var.db.get("privilege", "web_access", fallback='[]'))
bot.send_msg(constants.strings('web_user_list', users=", ".join(web_users)), text) bot.send_msg(tr('web_user_list', users=", ".join(web_users)), text)
else: else:
bot.send_msg(constants.strings('command_disabled', command=command), text) bot.send_msg(tr('command_disabled', command=command), text)
# Just for debug use # Just for debug use
-156
View File
@@ -195,159 +195,3 @@ list_webinterface_user = webuserlist
add_webinterface_user = webuseradd add_webinterface_user = webuseradd
remove_webinterface_user = webuserdel remove_webinterface_user = webuserdel
change_user_password = password change_user_password = password
[strings]
current_volume = Current volume: {volume}.
current_ducking_volume = Volume on ducking: {volume}.
change_volume = Volume set to {volume} by {user}.
change_ducking_volume = Volume on ducking set to {volume} by {user}.
bad_command = {command}: command not found.
bad_parameter = {command}: invalid parameter.
error_executing_command = {command}: Command failed with error: {error}.
not_admin = You are not an admin!
not_playing = Nothing is playing right now.
no_file = File not found.
wrong_pattern = Invalid regex: {error}.
file_added = Added {item}.
file_deleted = Deleted {item} from the library.
multiple_file_added = Multiple items added:
multiple_file_deleted = Multiple items deleted from the library:
multiple_file_found = Found:
page_instruction = Page {current}/{total}. Use <i>!{command} {{page}}</i> to navigate.
records_omitted = ...
bad_url = Bad URL requested.
preconfigurated_radio = Preconfigurated Radio available:
unable_download = Unable to download <b>{item}</b>. Removed from the library.
unable_play = Unable to play <b>{item}</b>. Removed from the library.
which_command = Do you mean <br /> {commands}
multiple_matches = File not found! Possible candidates:
queue_contents = Items on the playlist:
queue_empty = Playlist is empty!
invalid_index = Invalid index <i>{index}</i>. Use '!queue' to see your playlist.
now_playing = Playing {item}
radio = Radio
file = File
url_from_playlist = URL
url = URL
radio_item = <a href="{url}"><b>{title}</b></a> <i>from</i> {name} <i>added by</i> {user}
file_item = <b>{artist} - {title}</b> <i>added by</i> {user}
url_from_playlist_item = <a href="{url}"><b>{title}</b></a> <i>from playlist</i> <a href="{playlist_url}">{playlist}</a> <i>added by</i> {user}
url_item = <a href="{url}"><b>{title}</b></a> <i>added by</i> {user}
not_in_my_channel = You're not in my channel, command refused!
pm_not_allowed = Private message aren't allowed.
too_long = <b>{song}</b> is too long, removed from playlist!
download_in_progress = Download of <b>{item}</b> in progress...
removing_item = Removed entry {item} from playlist.
user_ban = You are banned, not allowed to do that!
url_ban = This url is banned!
rb_query_result = This is the result of your query, send !rbplay 'ID' to play a station:
rb_play_empty = Please specify a radio station ID!
paused = Music paused.
stopped = Music stopped.
cleared = Playlist emptied.
database_dropped = Database dropped. All records have gone.
new_version_found = <h2>Update Available!</h2> Version {new_version} of botamusique is available! <hr />
<h3>Changelog</h3> {changelog} <hr /> Send <i>!update</i> to update!
update_successful = <h2>botamusique v{version} Installed!</h2><hr />
<h3>Changelog</h3> {changelog} <hr /> Visit <a href="https://github.com/azlux/botamusique">our github repo</a> for more details!
start_updating = Start updating...
file_missed = Music file '{file}' missed! This item has been removed from the playlist.
unknown_mode = Unknown playback mode '{mode}'. It should be one of <i>one-shot</i>, <i>repeat</i>, <i>random</i>.
current_mode = Current playback mode is <i>{mode}</i>.
change_mode = Playback mode set to <i>{mode}</i> by {user}.
repeat = Repeat {song} for {n} times.
yt_result = Youtube query result: {result_table} Use <i>!sl {{indexes}}</i> to play the item you want. <br />
<i>!ytquery -n</i> for the next page.
yt_no_more = No more results!
yt_query_error = Unable to query youtube!
playlist_fetching_failed = Unable to fetch the playlist!
cache_refreshed = Cache refreshed!
added_tags = Added tags <i>{tags}</i> to <b>{song}</b>.
added_tags_to_all = Added tags <i>{tags}</i> to songs on the playlist.
removed_tags = Removed tags <i>{tags}</i> from <b>{song}</b>.
removed_tags_from_all = Removed tags <i>{tags}</i> from songs on the playlist.
cleared_tags = Removed all tags from <b>{song}</b>.
cleared_tags_from_all = Removed all tags from songs on the playlist.
shortlist_instruction = Use <i>!sl {indexes}</i> to play the item you want.
auto_paused = Use <i>!play</i> to resume music!
webpage_address= Your own address to access the web interface is <a href="{address}">{address}</a>
web_user_list = Following users has the privilege to access the web interface: <br /> {users}
user_password_set = Your password has been updated.
command_disabled = {command}: command disabled!
help = <h3>Commands</h3>
<b>Control</b>
<ul>
<li> <b>!<u>w</u>eb</b> - get the URL of the web interface, if enabled. </li>
<li> <b>!play </b> (or <b>!p</b>) [{num}] [{start_from}] - resume from pausing / start to play (the num-th song is num if given) </li>
<li> <b>!<u>pa</u>use </b> - pause </li>
<li> <b>!<u>st</u>op </b> - stop playing </li>
<li> <b>!<u>sk</u>ip </b> - jump to the next song </li>
<li> <b>!<u>la</u>st </b> - jump to the last song </li>
<li> <b>!<u>v</u>olume </b> {volume} - get or change the volume (from 0 to 100) </li>
<li> <b>!<u>m</u>ode </b> [{mode}] - get or set the playback mode, {mode} should be one of <i>one-shot</i> (remove
item once played), <i>repeat</i> (looping through the playlist), <i>random</i> (randomize the playlist),
<i>autoplay</i> (randomly grab something from the music library).</li>
<li> <b>!duck </b> on/off - enable or disable ducking function </li>
<li> <b>!duckv </b> - set the volume of the bot when ducking is activated </li>
<li> <b>!<u>duckt</u>hres </b> - set the threshold of volume to activate ducking (3000 by default) </li>
<li> <b>!<u>o</u>ust </b> - stop playing and go to default channel </li>
</ul>
<b>Playlist</b>
<ul>
<li> <b>!<u>n</u>ow </b> (or <b>!np</b>) - display the current song </li>
<li> <b>!<u>q</u>ueue </b> - display items in the playlist </li>
<li> <b>!<u>t</u>ag </b> {tags} - add all items with tags {tags}, tags separated by ",". </li>
<li> <b>!file </b>(or <b>!f</b>) {path/folder/keyword} - add a single file to the playlist by its path or keyword in its path. </li>
<li> <b>!<u>filem</u>atch </b>(or <b>!fm</b>) {pattern} - add all files that match regex {pattern} </li>
<li> <b>!<u>ur</u>l </b> {url} - add Youtube or SoundCloud music </li>
<li> <b>!<u>playl</u>ist </b> {url} [{offset}] - add all items in a Youtube or SoundCloud playlist, and start with the {offset}-th item </li>
<li> <b>!<u>rad</u>io </b> {url} - append a radio {url} to the playlist </li>
<li> <b>!<u>rbq</u>uery </b> {keyword} - query http://www.radio-browser.info for a radio station </li>
<li> <b>!<u>rbp</u>lay </b> {id} - play a radio station with {id} (eg. !rbplay 96746) </li>
<li> <b>!<u>ys</u>earch </b> {keywords} - query youtube. Use <i>!ysearch -n</i> to turn the page. </li>
<li> <b>!<u>yp</u>lay </b> {keywords} - add the first search result of {keywords} into the playlist.</li>
<li> <b>!<u>sh</u>ortlist </b> (or <b>!sl</b>) {indexes/*} - add {indexes}-th item (or all items if * is given) on the shortlist. </li>
<li> <b>!rm </b> {num} - remove the num-th song on the playlist </li>
<li> <b>!<u>rep</u>eat </b> [{num}] - repeat current song {num} (1 by default) times.</li>
<li> <b>!<u>ran</u>dom </b> - randomize the playlist.</li>
</ul>
<b>Music Library</b>
<ul>
<li> <b>!<u>se</u>arch </b> {keywords} - find item with {keywords} in the music library, keywords separated by space.</li>
<li> <b>!<u>li</u>stfile </b> [{pattern}] - display list of available files (whose paths match the regex pattern if {pattern} is given) </li>
<li> <b>!<u>addt</u>ag </b> [{index}] {tags} - add {tags} to {index}-th(current song if {index} is omitted) item on the playlist, tags separated by ",". </li>
<li> <b>!<u>addt</u>ag </b> * {tags} - add {tags} to all items on the playlist. </li>
<li> <b>!<u>un</u>tag </b> [{index/*}] {tags}/* - remove {tags}/all tags from {index}-th(current song if {index} is omitted) item on the playlist. </li>
<li> <b>!<u>fin</u>dtagged </b> (or <b>!ft</b>) {tags} - find item with {tags} in the music library. </li>
<li> <b>!<u>del</u>ete </b> {index} - delete {index}-th item on the shortlist from the music library. </li>
</ul>
<b>Other</b>
<ul>
<li> <b>!<u>j</u>oinme {token} </b> - join your own channel with {token}.</li>
<li> <b>!<u>password</u> {password} </b> - change your password, used to access the web interface.</li>
</ul>
admin_help = <h3>Admin command</h3>
<b>Bot</b>
<ul>
<li><b>!<u>k</u>ill </b> - kill the bot</li>
<li><b>!update </b> - update the bot</li>
<li><b>!userban </b> {user} - ban a user</li>
<li><b>!userunban </b> {user} - unban a user</li>
<li><b>!urlbanlist </b> - list banned url</li>
<li><b>!urlban </b> [{url}] - ban {url} (or current item's url by default) and remove this url from the library.</li>
<li><b>!urlunban </b> {url} - unban {url}</li>
<li><b>!rescan </b> {url} - rebuild local music file cache</li>
<li><b>!dropdatabase</b> - clear the entire database, you will lose all settings and music library.</li>
</ul>
<b>Web Interface</b>
<ul>
<li><b>!<u>webuserlist</u></b> - list all users that have the permission of accessing the web interface, if auth mode is 'password'.</li>
<li><b>!<u>webuseradd</u> {nick name}</b> - grant the user with {nick name} the access to the web interface, if auth mode is 'password'.</li>
<li><b>!<u>webuserdel</u> {nick name}</b> - revoke the access to the web interface of {nick name}, if auth mode is 'password'.</li>
<li><b>!update </b> - update the bot</li>
<li><b>!userban </b> {user} - ban a user</li>
</ul>
-15
View File
@@ -177,18 +177,3 @@ port = 64738
# see configuration.default.ini. Copy options you want to edit into this file. # see configuration.default.ini. Copy options you want to edit into this file.
#play_file = file, f #play_file = file, f
#play_file_match = filematch, fm #play_file_match = filematch, fm
# [strings] is used to compose what the bot says. You can customize them to fit in
# the style of your channel, or translate into your own language.
# For a full list of strings, please see configuration.default.ini.
# Copy options you want to edit into this file.
# Note: please keep those {placeholder} of each string in your new string.
[strings]
# Some examples are:
#current_volume = Current volume: {volume}
#current_volume = 当前音量为{volume}
#current_volume = よく聞いてね!今の音量は{volume}!<br />ちゃんと覚える:大音量で耳が悪くなる!
#
#bad_command = {command}: command not found
#bad_command = {command}: 未知命令,键入'!help'以获取可用命令列表
#bad_command = {command}がなに?食べれる?おいしいでしか?
+39 -10
View File
@@ -1,24 +1,54 @@
import json
import variables as var import variables as var
default_lang_dict = {}
lang_dict = {}
def strings(option, *argv, **kwargs):
def load_lang(lang):
global lang_dict, default_lang_dict
with open("lang/en_US.json", "r") as f:
default_lang_dict = json.load(f)
with open(f"lang/{lang}.json", "r") as f:
lang_dict = json.load(f)
def tr_cli(option, *argv, **kwargs):
try: try:
string = var.config.get("strings", option) if option in lang_dict['cli'] and lang_dict['cli'][option]:
string = lang_dict['cli'][option]
else:
string = default_lang_dict['cli'][option]
except KeyError: except KeyError:
raise KeyError("Missed strings in configuration file: '{string}'. ".format(string=option) raise KeyError("Missed strings in language file: '{string}'. ".format(string=option))
+ "Please restore you configuration file back to default if necessary.") return _tr(string, *argv, **kwargs)
def tr_web(option, *argv, **kwargs):
try:
if option in lang_dict['web'] and lang_dict['web'][option]:
string = lang_dict['web'][option]
else:
string = default_lang_dict['web'][option]
except KeyError:
raise KeyError("Missed strings in language file: '{string}'. ".format(string=option))
return _tr(string, *argv, **kwargs)
def _tr(string, *argv, **kwargs):
if argv or kwargs: if argv or kwargs:
try: try:
formatted = string.format(*argv, **kwargs) formatted = string.format(*argv, **kwargs)
return formatted return formatted
except KeyError as e: except KeyError as e:
raise KeyError( raise KeyError(
"Missed/Unexpected placeholder {{{placeholder}}} in string '{string}'. ".format(placeholder=str(e).strip("'"), string=option) "Missed/Unexpected placeholder {{{placeholder}}} in string "
+ "Please restore you configuration file back to default if necessary.") "'{string}'. ".format(placeholder=str(e).strip("'"),
string=string))
except TypeError: except TypeError:
raise KeyError( raise KeyError(
"Missed placeholder in string '{string}'. ".format(string=option) "Missed placeholder in string '{string}'. ".format(string=string))
+ "Please restore you configuration file back to default if necessary.")
else: else:
return string return string
@@ -28,5 +58,4 @@ def commands(command):
string = var.config.get("commands", command) string = var.config.get("commands", command)
return string return string
except KeyError: except KeyError:
raise KeyError("Missed command in configuration file: '{string}'. ".format(string=command) raise KeyError("Missed command in configuration file: '{string}'. ".format(string=command))
+ "Please restore you configuration file back to default if necessary.")
+2 -3
View File
@@ -169,7 +169,7 @@ def requires_auth(f):
bad_access_count[request.remote_addr] = 1 bad_access_count[request.remote_addr] = 1
log.info(f"web: bad token from ip {request.remote_addr}.") log.info(f"web: bad token from ip {request.remote_addr}.")
return render_template('need_token.html', return render_template(f'need_token.{var.language}.html',
name=var.config.get('bot', 'username'), name=var.config.get('bot', 'username'),
command=f"{var.config.get('commands', 'command_symbol')[0]}{var.config.get('commands', 'requests_webinterface_access')}") command=f"{var.config.get('commands', 'command_symbol')[0]}{var.config.get('commands', 'requests_webinterface_access')}")
@@ -225,7 +225,7 @@ def get_all_dirs():
@web.route("/", methods=['GET']) @web.route("/", methods=['GET'])
@requires_auth @requires_auth
def index(): def index():
return open('templates/index.html', "r").read() return open(f"templates/index.{var.language}.html", "r").read()
@web.route("/playlist", methods=['GET']) @web.route("/playlist", methods=['GET'])
@@ -554,7 +554,6 @@ def library_info():
tags = var.music_db.query_all_tags() tags = var.music_db.query_all_tags()
max_upload_file_size = util.parse_file_size(var.config.get("webinterface", "max_upload_file_size", fallback="30MB")) max_upload_file_size = util.parse_file_size(var.config.get("webinterface", "max_upload_file_size", fallback="30MB"))
print(get_all_dirs())
return jsonify(dict( return jsonify(dict(
dirs=get_all_dirs(), dirs=get_all_dirs(),
upload_enabled=var.config.getboolean("webinterface", "upload_enabled", fallback=True), upload_enabled=var.config.getboolean("webinterface", "upload_enabled", fallback=True),
+157
View File
@@ -0,0 +1,157 @@
{
"cli": {
"added_tags": "Added tags <i>{tags}</i> to <b>{song}</b>.",
"added_tags_to_all": "Added tags <i>{tags}</i> to songs on the playlist.",
"admin_help": "<h3>Admin command</h3>\n<b>Bot</b>\n<ul>\n<li><b>!<u>k</u>ill </b> - kill the bot</li>\n<li><b>!update </b> - update the bot</li>\n<li><b>!userban </b> {user} - ban a user</li>\n<li><b>!userunban </b> {user} - unban a user</li>\n<li><b>!urlbanlist </b> - list banned url</li>\n<li><b>!urlban </b> [{url}] - ban {url} (or current item's url by default) and remove this url from the library.</li>\n<li><b>!urlunban </b> {url} - unban {url}</li>\n<li><b>!rescan </b> {url} - rebuild local music file cache</li>\n<li><b>!dropdatabase</b> - clear the entire database, you will lose all settings and music library.</li>\n</ul>\n<b>Web Interface</b>\n<ul>\n<li><b>!<u>webuserlist</u></b> - list all users that have the permission of accessing the web interface, if auth mode is 'password'.</li>\n<li><b>!<u>webuseradd</u> {nick name}</b> - grant the user with {nick name} the access to the web interface, if auth mode is 'password'.</li>\n<li><b>!<u>webuserdel</u> {nick name}</b> - revoke the access to the web interface of {nick name}, if auth mode is 'password'.</li>\n<li><b>!update </b> - update the bot</li>\n<li><b>!userban </b> {user} - ban a user</li>\n</ul>",
"auto_paused": "Use <i>!play</i> to resume music!",
"bad_command": "<i>{command}</i>: command not found.",
"bad_parameter": "<i>{command}</i>: invalid parameter.",
"bad_url": "Bad URL requested.",
"cache_refreshed": "Cache refreshed!",
"change_ducking_volume": "Volume on ducking set to {volume} by {user}.",
"change_mode": "Playback mode set to <i>{mode}</i> by {user}.",
"change_volume": "Volume set to {volume} by {user}.",
"cleared": "Playlist emptied.",
"cleared_tags": "Removed all tags from <b>{song}</b>.",
"cleared_tags_from_all": "Removed all tags from songs on the playlist.",
"command_disabled": "{command}: command disabled!",
"current_ducking_volume": "Volume on ducking: {volume}.",
"current_mode": "Current playback mode is <i>{mode}</i>.",
"current_volume": "Current volume: {volume}.",
"database_dropped": "Database dropped. All records have gone.",
"download_in_progress": "Download of <b>{item}</b> in progress...",
"error_executing_command": "{command}: Command failed with error: {error}.",
"file": "File",
"file_added": "Added {item}.",
"file_deleted": "Deleted {item} from the library.",
"file_item": "<b>{artist} - {title}</b> <i>added by</i> {user}",
"file_missed": "Music file '{file}' missed! This item has been removed from the playlist.",
"help": "<h3>Commands</h3>\n<b>Control</b>\n<ul>\n<li> <b>!<u>w</u>eb</b> - get the URL of the web interface, if enabled. </li>\n<li> <b>!play </b> (or <b>!p</b>) [{num}] [{start_from}] - resume from pausing / start to play (the num-th song is num if given) </li>\n<li> <b>!<u>pa</u>use </b> - pause </li>\n<li> <b>!<u>st</u>op </b> - stop playing </li>\n<li> <b>!<u>sk</u>ip </b> - jump to the next song </li>\n<li> <b>!<u>la</u>st </b> - jump to the last song </li>\n<li> <b>!<u>v</u>olume </b> {volume} - get or change the volume (from 0 to 100) </li>\n<li> <b>!<u>m</u>ode </b> [{mode}] - get or set the playback mode, {mode} should be one of <i>one-shot</i> (remove\nitem once played), <i>repeat</i> (looping through the playlist), <i>random</i> (randomize the playlist),\n<i>autoplay</i> (randomly grab something from the music library).</li>\n<li> <b>!duck </b> on/off - enable or disable ducking function </li>\n<li> <b>!duckv </b> - set the volume of the bot when ducking is activated </li>\n<li> <b>!<u>duckt</u>hres </b> - set the threshold of volume to activate ducking (3000 by default) </li>\n<li> <b>!<u>o</u>ust </b> - stop playing and go to default channel </li>\n</ul>\n<b>Playlist</b>\n<ul>\n<li> <b>!<u>n</u>ow </b> (or <b>!np</b>) - display the current song </li>\n<li> <b>!<u>q</u>ueue </b> - display items in the playlist </li>\n<li> <b>!<u>t</u>ag </b> {tags} - add all items with tags {tags}, tags separated by \",\". </li>\n<li> <b>!file </b>(or <b>!f</b>) {path/folder/keyword} - add a single file to the playlist by its path or keyword in its path. </li>\n<li> <b>!<u>filem</u>atch </b>(or <b>!fm</b>) {pattern} - add all files that match regex {pattern} </li>\n<li> <b>!<u>ur</u>l </b> {url} - add Youtube or SoundCloud music </li>\n<li> <b>!<u>playl</u>ist </b> {url} [{offset}] - add all items in a Youtube or SoundCloud playlist, and start with the {offset}-th item </li>\n<li> <b>!<u>rad</u>io </b> {url} - append a radio {url} to the playlist </li>\n<li> <b>!<u>rbq</u>uery </b> {keyword} - query http://www.radio-browser.info for a radio station </li>\n<li> <b>!<u>rbp</u>lay </b> {id} - play a radio station with {id} (eg. !rbplay 96746) </li>\n<li> <b>!<u>ys</u>earch </b> {keywords} - query youtube. Use <i>!ysearch -n</i> to turn the page. </li>\n<li> <b>!<u>yp</u>lay </b> {keywords} - add the first search result of {keywords} into the playlist.</li>\n<li> <b>!<u>sh</u>ortlist </b> (or <b>!sl</b>) {indexes/*} - add {indexes}-th item (or all items if * is given) on the shortlist. </li>\n<li> <b>!rm </b> {num} - remove the num-th song on the playlist </li>\n<li> <b>!<u>rep</u>eat </b> [{num}] - repeat current song {num} (1 by default) times.</li>\n<li> <b>!<u>ran</u>dom </b> - randomize the playlist.</li>\n</ul>\n<b>Music Library</b>\n<ul>\n<li> <b>!<u>se</u>arch </b> {keywords} - find item with {keywords} in the music library, keywords separated by space.</li>\n<li> <b>!<u>li</u>stfile </b> [{pattern}] - display list of available files (whose paths match the regex pattern if {pattern} is given) </li>\n<li> <b>!<u>addt</u>ag </b> [{index}] {tags} - add {tags} to {index}-th(current song if {index} is omitted) item on the playlist, tags separated by \",\". </li>\n<li> <b>!<u>addt</u>ag </b> * {tags} - add {tags} to all items on the playlist. </li>\n<li> <b>!<u>un</u>tag </b> [{index/*}] {tags}/* - remove {tags}/all tags from {index}-th(current song if {index} is omitted) item on the playlist. </li>\n<li> <b>!<u>fin</u>dtagged </b> (or <b>!ft</b>) {tags} - find item with {tags} in the music library. </li>\n<li> <b>!<u>del</u>ete </b> {index} - delete {index}-th item on the shortlist from the music library. </li>\n</ul>\n<b>Other</b>\n<ul>\n<li> <b>!<u>j</u>oinme {token} </b> - join your own channel with {token}.</li>\n<li> <b>!<u>password</u> {password} </b> - change your password, used to access the web interface.</li>\n</ul>",
"invalid_index": "Invalid index <i>{index}</i>. Use '!queue' to see the playlist.",
"multiple_file_added": "Multiple items added:",
"multiple_file_deleted": "Multiple items deleted from the library:",
"multiple_file_found": "Found:",
"multiple_matches": "File not found! Possible candidates:",
"new_version_found": "<h2>Update Available!</h2> Version {new_version} of botamusique is available! <hr />\n<h3>Changelog</h3> {changelog} <hr /> Send <i>!update</i> to update!",
"no_file": "File not found.",
"not_admin": "You are not an admin!",
"not_in_my_channel": "You're not in my channel!",
"not_playing": "Nothing is playing right now.",
"now_playing": "Playing {item}",
"page_instruction": "Page {current}/{total}. Use <i>!{command} {{page}}</i> to navigate.",
"paused": "Music paused.",
"playlist_fetching_failed": "Unable to fetch the playlist!",
"pm_not_allowed": "Private message aren't allowed.",
"preconfigurated_radio": "Preconfigurated Radio available:",
"queue_contents": "Items on the playlist:",
"queue_empty": "Playlist is empty!",
"radio": "Radio",
"radio_item": "<a href=\"{url}\"><b>{title}</b></a> <i>from</i> {name} <i>added by</i> {user}",
"rb_play_empty": "Please specify a radio station ID!",
"rb_query_result": "This is the result of your query, send <i> !rbplay {ID} </i> to play a station:",
"records_omitted": "...",
"removed_tags": "Removed tags <i>{tags}</i> from <b>{song}</b>.",
"removed_tags_from_all": "Removed tags <i>{tags}</i> from songs on the playlist.",
"removing_item": "Removed entry {item} from playlist.",
"repeat": "Repeat {song} for {n} times.",
"shortlist_instruction": "Use <i>!sl {indexes}</i> to play the item you want.",
"start_updating": "Start updating...",
"stopped": "Music stopped.",
"too_long": "<b>{song}</b> is too long, removed from playlist!",
"unable_download": "Unable to download <b>{item}</b>. Removed from the library.",
"unable_play": "Unable to play <b>{item}</b>. Removed from the library.",
"unknown_mode": "Unknown playback mode '{mode}'. It should be one of <i>one-shot</i>, <i>repeat</i>, <i>random</i>.",
"update_successful": "<h2>botamusique v{version} Installed!</h2><hr />\n<h3>Changelog</h3> {changelog} <hr /> Visit <a href=\"https://github.com/azlux/botamusique\">our github repo</a> for more details!",
"url": "URL",
"url_ban": "This URL is banned!",
"url_from_playlist": "URL",
"url_from_playlist_item": "<a href=\"{url}\"><b>{title}</b></a> <i>from playlist</i> <a href=\"{playlist_url}\">{playlist}</a> <i>added by</i> {user}",
"url_item": "<a href=\"{url}\"><b>{title}</b></a> <i>added by</i> {user}",
"user_ban": "You are banned, not allowed to do that!",
"user_password_set": "Your password has been updated.",
"web_user_list": "Following users has the privilege to access the web interface: <br /> {users}",
"webpage_address": "Your own address to access the web interface is <a href=\"{address}\">{address}</a>",
"which_command": "Do you mean <br /> {commands}",
"wrong_pattern": "Invalid regex: {error}.",
"yt_no_more": "No more results!",
"yt_query_error": "Unable to query youtube!",
"yt_result": "Youtube query result: {result_table} Use <i>!sl {{indexes}}</i> to play the item you want. <br />\n<i>!ytquery -n</i> for the next page."
},
"web": {
"action": "Action",
"add": "Add",
"add_all": "Add All",
"add_radio": "Add Radio",
"add_radio_url": "Add Radio URL",
"add_to_bottom": "Add to bottom",
"add_to_bottom_of_current_playlist": "Add to bottom of current playlist",
"add_to_playlist_next": "Add to playlist right after current song",
"add_url": "Add URL",
"add_youtube_or_soundcloud_url": "Add Youtube or Soundcloud URL",
"are_you_really_sure": "Are you really sure?",
"aria_botamusique_logo": "Botamusique Logo: a person with two headphones, enjoying the music",
"aria_default_cover": "A black square with two eight notes beamed together.",
"aria_empty_box": "A drawing of an empty box.",
"aria_remove_this_song": "Remove this song from the current playlist",
"aria_skip_current_song": "Skip current song and play this song right now",
"aria_skip_to_next_track": "Skip to next track",
"aria_spinner": "A loading spinner",
"aria_warning_of_deletion": "Warning about deletion of files.",
"autoplay": "Autoplay",
"browse_music_file": "Browse Music file",
"cancel": "Cancel",
"cancel_upload_warning": "<strong>Are you really sure?</strong> <br /> Click again to abort uploading.",
"change_playback_mode": "Change Playback Mode",
"choose_file": "Choose file",
"clear_playlist": "Clear Playlist",
"close": "Close",
"delete_all": "Delete All",
"delete_all_files": "Delete All Listed Files",
"delete_file_warning": "All files listed here, include files on other pages, will be deleted from your hard-drive.\n Is that what you want?",
"directory": "Directory",
"download_all": "Download All",
"download_song_from_library": "Download song from library",
"edit_submit": "Edit!",
"edit_tags_for": "Edit tags for",
"expand_playlist": "See item <span\n class=\"playlist-expand-item-range\"></span> on the playlist.",
"file": "File",
"filters": "Filters",
"index": "#",
"keywords": "Keywords",
"keywords_placeholder": "Keywords...",
"mini_player_title": "Now Playing...",
"music_library": "Music Library",
"next_to_play": "Next to play",
"no_tag": "No tag",
"oneshot": "One-shot",
"open_volume_controls": "Open Volume Controls",
"page_title": "botamusique Web Interface",
"pause": "Pause",
"play": "Play",
"playlist_controls": "Playlist controls",
"radio": "Radio",
"radio_url_placeholder": "Radio URL...",
"random": "Random",
"remove_song_from_library": "Remove song from library",
"repeat": "Repeat",
"rescan_files": "Rescan Files",
"skip_track": "Skip Track",
"submit": "Submit",
"tags": "Tags",
"tags_to_add": "Tags to add",
"title": "Title",
"token": "Token",
"token_required": "Token Required",
"token_required_message": "You are accessing the web interface of {{ name }}.\nA token is needed to grant you access.<br />\nPlease send \"{{ command }}\" to the bot in mumble to acquire one.",
"type": "Type",
"upload_file": "Upload File",
"upload_submit": "Upload!",
"upload_to": "Upload To",
"uploaded_finished": "Uploaded finished!",
"uploading_files": "Uploading files...",
"url": "URL",
"url_path": "Url/Path",
"url_placeholder": "URL...",
"volume_slider": "Volume Slider"
}
}
+95
View File
@@ -0,0 +1,95 @@
#!/usr/bin/env python3
import os
import re
import argparse
import requests
base_url = "https://translate.azlux.fr/api/v1"
project_id = "4aafb197-3282-47b3-a197-0ca870cf6ab2"
def get_access_header(client, secret):
data = {"grant_type": "client_credentials",
"client_id": client,
"client_secret": secret}
r = requests.post(f"{base_url}/auth/token", json=data)
if r.status_code != 200:
print("Access denied! Please check your client ID or secret.")
exit(1)
token = r.json()["access_token"]
headers = {"Authorization": "Bearer " + token,
"Accept": "application/json, text/plain, */*"}
return headers
def fetch_translation(r_client, r_secret):
headers = get_access_header(r_client, r_secret)
r = requests.get(f"{base_url}/projects/{project_id}/translations", headers=headers)
translations = r.json()['data']
for translation in translations:
lang_code = translation['locale']['code']
print(f" - Fetching {lang_code}")
params = {'locale': lang_code,
'format': 'jsonnested'}
r = requests.get(f"{base_url}/projects/{project_id}/exports", params=params, headers=headers)
with open(lang_code + ".json", "wb") as f:
f.write(r.content)
def push_strings(w_client, w_secret):
print("Pushing local translation files into the remote host...")
headers = get_access_header(w_client, w_secret)
lang_files = os.listdir('.')
lang_list = []
for lang_file in lang_files:
match = re.search("([a-z]{2}_[A-Z]{2})\.json", lang_file)
if match:
lang_list.append(match[1])
for lang in lang_list:
print(f" - Pushing {lang}")
params = {'locale': lang,
'format': 'jsonnested'}
files = {'file': open(lang + ".json", 'r')}
r = requests.post(f"{base_url}/projects/{project_id}/imports", params=params, headers=headers, files=files)
assert r.status_code == 200, f"Unable to push {lang} into remote host. {r.status_code}"
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Sync translation files with azlux's traduora server.")
parser.add_argument("--client", dest="client",
type=str, help="Client ID used to access the server.")
parser.add_argument("--secret", dest="secret",
type=str, help="Secret used to access the server.")
parser.add_argument("--fetch", dest='fetch', action="store_true",
help='Fetch translation files from the server.')
parser.add_argument("--push", dest='push', action="store_true",
help='Push local translation files into the server.')
args = parser.parse_args()
if not args.client or not args.secret:
print("Client ID and secret need to be provided!")
exit(1)
if args.push:
push_strings(args.client, args.secret)
if args.fetch:
fetch_translation(args.client, args.secret)
print("Done.")
+5 -5
View File
@@ -9,7 +9,7 @@ from PIL import Image
import util import util
import variables as var import variables as var
from media.item import BaseItem, item_builders, item_loaders, item_id_generators, ValidationFailedError from media.item import BaseItem, item_builders, item_loaders, item_id_generators, ValidationFailedError
import constants from constants import tr_cli as tr
''' '''
type : file type : file
@@ -75,7 +75,7 @@ class FileItem(BaseItem):
if not os.path.exists(self.uri()): if not os.path.exists(self.uri()):
self.log.info( self.log.info(
"file: music file missed for %s" % self.format_debug_string()) "file: music file missed for %s" % self.format_debug_string())
raise ValidationFailedError(constants.strings('file_missed', file=self.path)) raise ValidationFailedError(tr('file_missed', file=self.path))
if self.duration == 0: if self.duration == 0:
self.duration = util.get_media_duration(self.uri()) self.duration = util.get_media_duration(self.uri())
@@ -185,14 +185,14 @@ class FileItem(BaseItem):
) )
def format_song_string(self, user): def format_song_string(self, user):
return constants.strings("file_item", return tr("file_item",
title=self.title, title=self.title,
artist=self.artist if self.artist else '??', artist=self.artist if self.artist else '??',
user=user user=user
) )
def format_current_playing(self, user): def format_current_playing(self, user):
display = constants.strings("now_playing", item=self.format_song_string(user)) display = tr("now_playing", item=self.format_song_string(user))
if self.thumbnail: if self.thumbnail:
thumbnail_html = '<img width="80" src="data:image/jpge;base64,' + \ thumbnail_html = '<img width="80" src="data:image/jpge;base64,' + \
self.thumbnail + '"/>' self.thumbnail + '"/>'
@@ -208,4 +208,4 @@ class FileItem(BaseItem):
return title return title
def display_type(self): def display_type(self):
return constants.strings("file") return tr("file")
+4 -4
View File
@@ -7,7 +7,7 @@ import hashlib
from media.item import BaseItem from media.item import BaseItem
from media.item import item_builders, item_loaders, item_id_generators from media.item import item_builders, item_loaders, item_id_generators
import constants from constants import tr_cli as tr
log = logging.getLogger("bot") log = logging.getLogger("bot")
@@ -147,7 +147,7 @@ class RadioItem(BaseItem):
) )
def format_song_string(self, user): def format_song_string(self, user):
return constants.strings("radio_item", return tr("radio_item",
url=self.url, url=self.url,
title=get_radio_title(self.url), # the title of current song title=get_radio_title(self.url), # the title of current song
name=self.title, # the title of radio station name=self.title, # the title of radio station
@@ -155,10 +155,10 @@ class RadioItem(BaseItem):
) )
def format_current_playing(self, user): def format_current_playing(self, user):
return constants.strings("now_playing", item=self.format_song_string(user)) return tr("now_playing", item=self.format_song_string(user))
def format_title(self): def format_title(self):
return self.title if self.title else self.url return self.title if self.title else self.url
def display_type(self): def display_type(self):
return constants.strings("radio") return tr("radio")
+7 -7
View File
@@ -9,7 +9,7 @@ import glob
from io import BytesIO from io import BytesIO
import base64 import base64
import constants from constants import tr_cli as tr
import media import media
import variables as var import variables as var
from media.item import BaseItem, item_builders, item_loaders, item_id_generators, ValidationFailedError, \ from media.item import BaseItem, item_builders, item_loaders, item_id_generators, ValidationFailedError, \
@@ -99,7 +99,7 @@ class URLItem(BaseItem):
# Check the length, useful in case of playlist, it wasn't checked before) # Check the length, useful in case of playlist, it wasn't checked before)
log.info( log.info(
"url: " + self.url + " has a duration of " + str(self.duration / 60) + " min -- too long") "url: " + self.url + " has a duration of " + str(self.duration / 60) + " min -- too long")
raise ValidationFailedError(constants.strings('too_long', song=self.title)) raise ValidationFailedError(tr('too_long', song=self.title))
else: else:
self.ready = "validated" self.ready = "validated"
self.version += 1 # notify wrapper to save me self.version += 1 # notify wrapper to save me
@@ -138,7 +138,7 @@ class URLItem(BaseItem):
if not succeed: if not succeed:
self.ready = 'failed' self.ready = 'failed'
self.log.error("url: error while fetching info from the URL") self.log.error("url: error while fetching info from the URL")
raise ValidationFailedError(constants.strings('unable_download', item=self.format_title())) raise ValidationFailedError(tr('unable_download', item=self.format_title()))
def _download(self): def _download(self):
media.system.clear_tmp_folder(var.tmp_folder, var.config.getint('bot', 'tmp_folder_max_size')) media.system.clear_tmp_folder(var.tmp_folder, var.config.getint('bot', 'tmp_folder_max_size'))
@@ -193,7 +193,7 @@ class URLItem(BaseItem):
os.remove(f) os.remove(f)
self.ready = "failed" self.ready = "failed"
self.downloading = False self.downloading = False
raise PreparationFailedError(constants.strings('unable_download', item=self.format_title())) raise PreparationFailedError(tr('unable_download', item=self.format_title()))
def _read_thumbnail_from_file(self, path_thumbnail): def _read_thumbnail_from_file(self, path_thumbnail):
if os.path.isfile(path_thumbnail): if os.path.isfile(path_thumbnail):
@@ -226,14 +226,14 @@ class URLItem(BaseItem):
def format_song_string(self, user): def format_song_string(self, user):
if self.ready in ['validated', 'yes']: if self.ready in ['validated', 'yes']:
return constants.strings("url_item", return tr("url_item",
title=self.title if self.title else "??", title=self.title if self.title else "??",
url=self.url, url=self.url,
user=user) user=user)
return self.url return self.url
def format_current_playing(self, user): def format_current_playing(self, user):
display = constants.strings("now_playing", item=self.format_song_string(user)) display = tr("now_playing", item=self.format_song_string(user))
if self.thumbnail: if self.thumbnail:
thumbnail_html = '<img width="80" src="data:image/jpge;base64,' + \ thumbnail_html = '<img width="80" src="data:image/jpge;base64,' + \
@@ -246,4 +246,4 @@ class URLItem(BaseItem):
return self.title if self.title.strip() else self.url return self.title if self.title.strip() else self.url
def display_type(self): def display_type(self):
return constants.strings("url") return tr("url")
+4 -4
View File
@@ -1,5 +1,5 @@
import youtube_dl import youtube_dl
import constants from constants import tr_cli as tr
import variables as var import variables as var
from media.item import item_builders, item_loaders, item_id_generators from media.item import item_builders, item_loaders, item_id_generators
from media.url import URLItem, url_item_id_generator from media.url import URLItem, url_item_id_generator
@@ -96,7 +96,7 @@ class PlaylistURLItem(URLItem):
) )
def format_song_string(self, user): def format_song_string(self, user):
return constants.strings("url_from_playlist_item", return tr("url_from_playlist_item",
title=self.title, title=self.title,
url=self.url, url=self.url,
playlist_url=self.playlist_url, playlist_url=self.playlist_url,
@@ -104,7 +104,7 @@ class PlaylistURLItem(URLItem):
user=user) user=user)
def format_current_playing(self, user): def format_current_playing(self, user):
display = constants.strings("now_playing", item=self.format_song_string(user)) display = tr("now_playing", item=self.format_song_string(user))
if self.thumbnail: if self.thumbnail:
thumbnail_html = '<img width="80" src="data:image/jpge;base64,' + \ thumbnail_html = '<img width="80" src="data:image/jpge;base64,' + \
@@ -114,4 +114,4 @@ class PlaylistURLItem(URLItem):
return display return display
def display_type(self): def display_type(self):
return constants.strings("url_from_playlist") return tr("url_from_playlist")
+40 -16
View File
@@ -24,6 +24,7 @@ from packaging import version
import util import util
import command import command
import constants import constants
from constants import tr_cli as tr
from database import SettingsDatabase, MusicDatabase, DatabaseMigration from database import SettingsDatabase, MusicDatabase, DatabaseMigration
import media.system import media.system
from media.item import ValidationFailedError, PreparationFailedError from media.item import ValidationFailedError, PreparationFailedError
@@ -118,7 +119,7 @@ class MumbleBot:
self.mumble.start() # start the mumble thread self.mumble.start() # start the mumble thread
self.mumble.is_ready() # wait for the connection self.mumble.is_ready() # wait for the connection
if self.mumble.connected >= pymumble_py3.constants.PYMUMBLE_CONN_STATE_FAILED: if self.mumble.connected >= pymumble.constants.PYMUMBLE_CONN_STATE_FAILED:
exit() exit()
self.set_comment() self.set_comment()
@@ -179,7 +180,7 @@ class MumbleBot:
if not last_startup_version or version.parse(last_startup_version) < version.parse(self.version): if not last_startup_version or version.parse(last_startup_version) < version.parse(self.version):
var.db.set("bot", "version", self.version) var.db.set("bot", "version", self.version)
changelog = util.fetch_changelog() changelog = util.fetch_changelog()
self.send_channel_msg(constants.strings("update_successful", version=self.version, changelog=changelog)) self.send_channel_msg(tr("update_successful", version=self.version, changelog=changelog))
# Set the CTRL+C shortcut # Set the CTRL+C shortcut
def ctrl_caught(self, signal, frame): def ctrl_caught(self, signal, frame):
@@ -206,7 +207,7 @@ class MumbleBot:
self.log.info(f"update: new version {new_version} found, current installed version {self.version}.") self.log.info(f"update: new version {new_version} found, current installed version {self.version}.")
self.log.info(f"update: changelog: {changelog}") self.log.info(f"update: changelog: {changelog}")
changelog = changelog.replace("\n", "<br>") changelog = changelog.replace("\n", "<br>")
self.send_channel_msg(constants.strings('new_version_found', new_version=new_version, changelog=changelog)) self.send_channel_msg(tr('new_version_found', new_version=new_version, changelog=changelog))
else: else:
self.log.debug("update: no new version found.") self.log.debug("update: no new version found.")
@@ -263,13 +264,13 @@ class MumbleBot:
# Anti stupid guy function # Anti stupid guy function
if not self.is_admin(user) and not var.config.getboolean('bot', 'allow_private_message') and text.session: if not self.is_admin(user) and not var.config.getboolean('bot', 'allow_private_message') and text.session:
self.mumble.users[text.actor].send_text_message( self.mumble.users[text.actor].send_text_message(
constants.strings('pm_not_allowed')) tr('pm_not_allowed'))
return return
for i in var.db.items("user_ban"): for i in var.db.items("user_ban"):
if user.lower() == i[0]: if user.lower() == i[0]:
self.mumble.users[text.actor].send_text_message( self.mumble.users[text.actor].send_text_message(
constants.strings('user_ban')) tr('user_ban'))
return return
if not self.is_admin(user) and parameter: if not self.is_admin(user) and parameter:
@@ -278,7 +279,7 @@ class MumbleBot:
for i in var.db.items("url_ban"): for i in var.db.items("url_ban"):
if input_url == i[0]: if input_url == i[0]:
self.mumble.users[text.actor].send_text_message( self.mumble.users[text.actor].send_text_message(
constants.strings('url_ban')) tr('url_ban'))
return return
command_exc = "" command_exc = ""
@@ -299,15 +300,15 @@ class MumbleBot:
elif len(matches) > 1: elif len(matches) > 1:
self.mumble.users[text.actor].send_text_message( self.mumble.users[text.actor].send_text_message(
constants.strings('which_command', commands="<br>".join(matches))) tr('which_command', commands="<br>".join(matches)))
return return
else: else:
self.mumble.users[text.actor].send_text_message( self.mumble.users[text.actor].send_text_message(
constants.strings('bad_command', command=command)) tr('bad_command', command=command))
return return
if self.cmd_handle[command_exc]['admin'] and not self.is_admin(user): if self.cmd_handle[command_exc]['admin'] and not self.is_admin(user):
self.mumble.users[text.actor].send_text_message(constants.strings('not_admin')) self.mumble.users[text.actor].send_text_message(tr('not_admin'))
return return
if not self.cmd_handle[command_exc]['access_outside_channel'] \ if not self.cmd_handle[command_exc]['access_outside_channel'] \
@@ -315,7 +316,7 @@ class MumbleBot:
and not var.config.getboolean('bot', 'allow_other_channel_message') \ and not var.config.getboolean('bot', 'allow_other_channel_message') \
and self.mumble.users[text.actor]['channel_id'] != self.mumble.users.myself['channel_id']: and self.mumble.users[text.actor]['channel_id'] != self.mumble.users.myself['channel_id']:
self.mumble.users[text.actor].send_text_message( self.mumble.users[text.actor].send_text_message(
constants.strings('not_in_my_channel')) tr('not_in_my_channel'))
return return
self.cmd_handle[command_exc]['handle'](self, user, text, command_exc, parameter) self.cmd_handle[command_exc]['handle'](self, user, text, command_exc, parameter)
@@ -323,7 +324,7 @@ class MumbleBot:
error_traceback = traceback.format_exc() error_traceback = traceback.format_exc()
error = error_traceback.rstrip().split("\n")[-1] error = error_traceback.rstrip().split("\n")[-1]
self.log.error(f"bot: command {command_exc} failed with error: {error_traceback}\n") self.log.error(f"bot: command {command_exc} failed with error: {error_traceback}\n")
self.send_msg(constants.strings('error_executing_command', command=command_exc, error=error), text) self.send_msg(tr('error_executing_command', command=command_exc, error=error), text)
def send_msg(self, msg, text): def send_msg(self, msg, text):
msg = msg.encode('utf-8', 'ignore').decode('utf-8') msg = msg.encode('utf-8', 'ignore').decode('utf-8')
@@ -355,7 +356,7 @@ class MumbleBot:
if var.config.get("bot", "when_nobody_in_channel") == "pause_resume": if var.config.get("bot", "when_nobody_in_channel") == "pause_resume":
self.resume() self.resume()
elif var.config.get("bot", "when_nobody_in_channel") == "pause": elif var.config.get("bot", "when_nobody_in_channel") == "pause":
self.send_channel_msg(constants.strings("auto_paused")) self.send_channel_msg(tr("auto_paused"))
elif len(own_channel.get_users()) == 1: elif len(own_channel.get_users()) == 1:
# if the bot is the only user left in the channel # if the bot is the only user left in the channel
@@ -436,7 +437,7 @@ class MumbleBot:
self.log.info("bot: current music isn't ready, start downloading.") self.log.info("bot: current music isn't ready, start downloading.")
self.async_download(item) self.async_download(item)
self.send_channel_msg( self.send_channel_msg(
constants.strings('download_in_progress', item=item.format_title())) tr('download_in_progress', item=item.format_title()))
def _download(self, item): def _download(self, item):
ver = item.version ver = item.version
@@ -514,7 +515,7 @@ class MumbleBot:
self.log.error("bot: with ffmpeg error: %s", self.last_ffmpeg_err) self.log.error("bot: with ffmpeg error: %s", self.last_ffmpeg_err)
self.last_ffmpeg_err = "" self.last_ffmpeg_err = ""
self.send_channel_msg(constants.strings('unable_play', item=current.format_title())) self.send_channel_msg(tr('unable_play', item=current.format_title()))
var.playlist.remove_by_id(current.id) var.playlist.remove_by_id(current.id)
var.cache.free_and_delete(current.id) var.cache.free_and_delete(current.id)
@@ -713,6 +714,8 @@ def start_web_interface(addr, port):
if __name__ == '__main__': if __name__ == '__main__':
supported_languages = util.get_supported_language()
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description='Bot for playing music on Mumble') description='Bot for playing music on Mumble')
@@ -720,9 +723,11 @@ if __name__ == '__main__':
parser.add_argument("--config", dest='config', type=str, default='configuration.ini', parser.add_argument("--config", dest='config', type=str, default='configuration.ini',
help='Load configuration from this file. Default: configuration.ini') help='Load configuration from this file. Default: configuration.ini')
parser.add_argument("--db", dest='db', type=str, parser.add_argument("--db", dest='db', type=str,
default=None, help='settings database file. Default: settings-{username_of_the_bot}.db') default=None, help='Settings database file')
parser.add_argument("--music-db", dest='music_db', type=str, parser.add_argument("--music-db", dest='music_db', type=str,
default=None, help='music library database file. Default: music.db') default=None, help='Music library database file')
parser.add_argument("--lang", dest='lang', type=str, default=None,
help='Preferred language. Support ' + ", ".join(supported_languages))
parser.add_argument("-q", "--quiet", dest="quiet", parser.add_argument("-q", "--quiet", dest="quiet",
action="store_true", help="Only Error logs") action="store_true", help="Only Error logs")
@@ -814,6 +819,25 @@ if __name__ == '__main__':
var.music_folder = util.solve_filepath(var.config.get('bot', 'music_folder')) var.music_folder = util.solve_filepath(var.config.get('bot', 'music_folder'))
var.tmp_folder = util.solve_filepath(var.config.get('bot', 'tmp_folder')) var.tmp_folder = util.solve_filepath(var.config.get('bot', 'tmp_folder'))
# ======================
# Translation
# ======================
lang = ""
if args.lang:
lang = args.lang
else:
lang = var.config.get('bot', 'language', fallback='en_US')
if lang not in supported_languages:
raise KeyError(f"Unsupported language {lang}")
var.language = lang
constants.load_lang(lang)
# ======================
# Prepare Cache
# ======================
var.cache = MusicCache(var.music_db) var.cache = MusicCache(var.music_db)
if var.config.getboolean("bot", "refresh_cache_on_startup", fallback=True): if var.config.getboolean("bot", "refresh_cache_on_startup", fallback=True):
+58
View File
@@ -0,0 +1,58 @@
#!/usr/bin/env python3
import os
import json
import re
import jinja2
default_lang_dict = {}
lang_dict = {}
def load_lang(lang):
with open(f"../lang/{lang}.json", "r") as f:
return json.load(f)
def tr(option):
try:
if option in lang_dict['web'] and lang_dict['web'][option]:
string = lang_dict['web'][option]
else:
string = default_lang_dict['web'][option]
return string
except KeyError:
raise KeyError("Missed strings in language file: '{string}'. ".format(string=option))
if __name__ == "__main__":
html_files = os.listdir('.')
for html_file in html_files:
match = re.search("(.+)\.template\.html", html_file)
if match is None:
continue
print(f"Populating {html_file} with translations...")
basename = match[1]
with open(html_file, "r") as f:
html = f.read()
lang_files = os.listdir('../lang')
lang_list = []
default_lang_dict = load_lang("en_US")
for lang_file in lang_files:
match = re.search("([a-z]{2}_[A-Z]{2})\.json", lang_file)
if match:
lang_list.append(match[1])
template = jinja2.Template(html)
for lang in lang_list:
print(f" - Populating {lang}...")
lang_dict = load_lang(lang)
with open(f"{basename}.{lang}.html", "w") as f:
f.write(template.render(tr=tr))
print("Done.")
+11 -1
View File
@@ -8,7 +8,6 @@ import io
import sys import sys
import variables as var import variables as var
import zipfile import zipfile
import requests
import re import re
import subprocess as sp import subprocess as sp
import logging import logging
@@ -416,6 +415,17 @@ def verify_password(password, salted_hash, salt):
return False return False
def get_supported_language():
lang_files = os.listdir('lang')
lang_list = []
for lang_file in lang_files:
match = re.search("([a-z]{2}_[A-Z]{2})\.json", lang_file)
if match:
lang_list.append(match[1])
return lang_list
class LoggerIOWrapper(io.TextIOWrapper): class LoggerIOWrapper(io.TextIOWrapper):
def __init__(self, logger: logging.Logger, logging_level, fallback_io_buffer): def __init__(self, logger: logging.Logger, logging_level, fallback_io_buffer):
super().__init__(fallback_io_buffer, write_through=True) super().__init__(fallback_io_buffer, write_through=True)
+2
View File
@@ -23,3 +23,5 @@ bot_logger = None
music_folder = "" music_folder = ""
tmp_folder = "" tmp_folder = ""
language = ""
+1 -1
View File
@@ -816,7 +816,7 @@ const modal_tag = $('.modal-tag');
const modal_tag_text = $('.modal-tag-text'); const modal_tag_text = $('.modal-tag-text');
function addTagModalShow(_id, _title, _tag_tuples) { function addTagModalShow(_id, _title, _tag_tuples) {
add_tag_modal_title.html('Edit tags for ' + _title); add_tag_modal_title.html(_title);
add_tag_modal_item_id.val(_id); add_tag_modal_item_id.val(_id);
add_tag_modal_tags.empty(); add_tag_modal_tags.empty();
_tag_tuples.forEach(function (tag_tuple) { _tag_tuples.forEach(function (tag_tuple) {
@@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>botamusique web interface</title> <title>{{ tr('page_title') }}</title>
<link rel="icon" href="static/image/favicon.ico" /> <link rel="icon" href="static/image/favicon.ico" />
@@ -18,46 +18,46 @@
<div class="row"> <div class="row">
<div class="col-auto"> <div class="col-auto">
<img src="static/image/logo.png" height="200px" <img src="static/image/logo.png" height="200px"
alt="Botamusique Logo: a person with two headphones, enjoying the music"> alt="{{ tr('aria_botamusique_logo') }}">
</div> </div>
<div class="col my-auto"> <div class="col my-auto">
<h1>botamusique Web Interface</h1> <h1>{{ tr('page_title') }}</h1>
</div> </div>
</div> </div>
</header> </header>
<main id="playlist" class="container mb-5"> <main id="playlist" class="container mb-5">
<div class="btn-toolbar mb-2" role="toolbar" aria-label="Playlist controls"> <div class="btn-toolbar mb-2" role="toolbar" aria-label="{{ tr('playlist_controls') }}">
<button type="button" id="play-pause-btn" class="btn btn-info mb-2 btn-space" aria-label="Play"> <button type="button" id="play-pause-btn" class="btn btn-info mb-2 btn-space" aria-label="{{ tr('play') }}">
<i class="fas fa-play"></i> <i class="fas fa-play"></i>
</button> </button>
<button type="button" id="fast-forward-btn" class="btn btn-info mb-2" aria-label="Skip Track"> <button type="button" id="fast-forward-btn" class="btn btn-info mb-2" aria-label="{{ tr('skip_track') }}">
<i class="fas fa-fast-forward"></i> <i class="fas fa-fast-forward"></i>
</button> </button>
<div class="ml-auto"> <div class="ml-auto">
<div class="dropdown mr-2"> <div class="dropdown mr-2">
<button class="btn btn-secondary dropdown-toggle" type="button" id="play-mode" <button class="btn btn-secondary dropdown-toggle" type="button" id="play-mode"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
aria-label="Change Playback Mode"> aria-label="{{ tr('change_playback_mode') }}">
<i class="fas fa-tasks mr-2" aria-hidden="true" id="modeIndicator"></i> <i class="fas fa-tasks mr-2" aria-hidden="true" id="modeIndicator"></i>
</button> </button>
<div class="dropdown-menu" aria-labelledby="play-mode"> <div class="dropdown-menu" aria-labelledby="play-mode">
<a class="dropdown-item" href="#" id="one-shot-mode-btn"> <a class="dropdown-item" href="#" id="one-shot-mode-btn">
<i class="fas fa-tasks mr-2" aria-hidden="true"></i>One-shot <i class="fas fa-tasks mr-2" aria-hidden="true"></i> {{ tr('oneshot') }}
</a> </a>
<a class="dropdown-item" href="#" id="random-mode-btn"> <a class="dropdown-item" href="#" id="random-mode-btn">
<i class="fas fa-random mr-2" aria-hidden="true"></i>Random <i class="fas fa-random mr-2" aria-hidden="true"></i> {{ tr('random') }}
</a> </a>
<a class="dropdown-item" href="#" id="repeat-mode-btn"> <a class="dropdown-item" href="#" id="repeat-mode-btn">
<i class="fas fa-redo mr-2" aria-hidden="true"></i>Repeat <i class="fas fa-redo mr-2" aria-hidden="true"></i> {{ tr('repeat') }}
</a> </a>
<a class="dropdown-item" href="#" id="autoplay-mode-btn"> <a class="dropdown-item" href="#" id="autoplay-mode-btn">
<i class="fas fa-robot mr-2" aria-hidden="true"></i>Autoplay <i class="fas fa-robot mr-2" aria-hidden="true"></i> {{ tr('autoplay') }}
</a> </a>
</div> </div>
</div> </div>
<button type="button" id="volume-popover-btn" class="btn btn-warning ml-1" <button type="button" id="volume-popover-btn" class="btn btn-warning ml-1"
aria-label="Open Volume Controls"> aria-label="{{ tr('open_volume_controls') }}">
<i class="fa fa-volume-up" aria-hidden="true"></i> <i class="fa fa-volume-up" aria-hidden="true"></i>
</button> </button>
@@ -67,7 +67,7 @@
</a> </a>
<input type="range" class="custom-range ml-1" id="volume-slider" min="0" max="1" step="0.01" <input type="range" class="custom-range ml-1" id="volume-slider" min="0" max="1" step="0.01"
value="0.5" aria-label="Volume Slider" /> value="0.5" aria-label="{{ tr('volume_slider') }}" />
<a id="volume-up-btn"> <a id="volume-up-btn">
<i class="fa fa-volume-up" aria-hidden="true"></i> <i class="fa fa-volume-up" aria-hidden="true"></i>
@@ -81,29 +81,28 @@
<table class="table table-striped table-hover"> <table class="table table-striped table-hover">
<thead> <thead>
<tr> <tr>
<th scope="col" class="d-none d-md-table-cell">#</th> <th scope="col" class="d-none d-md-table-cell">{{ tr('index') }}</th>
<th scope="col" class="w-50">Title</th> <th scope="col" class="w-50">{{ tr('title') }}</th>
<th scope="col" class="d-none d-md-table-cell">Url/Path</th> <th scope="col" class="d-none d-md-table-cell">{{ tr('url_path') }}</th>
<th scope="col">Action</th> <th scope="col">{{ tr('action') }}</th>
</tr> </tr>
</thead> </thead>
<tbody id="playlist-table" class="playlist-table"> <tbody id="playlist-table" class="playlist-table">
<tr id="playlist-loading"> <tr id="playlist-loading">
<td colspan="4" class="text-center"> <td colspan="4" class="text-center">
<img style="margin: auto; width: 35px;" src="static/image/loading.svg" <img style="margin: auto; width: 35px;" src="static/image/loading.svg"
alt="A loading spinner" /> alt="{{ tr('aria_spinner') }}" />
</td> </td>
</tr> </tr>
<tr id="playlist-empty" class="d-none"> <tr id="playlist-empty" class="d-none">
<td colspan="4" class="text-center"> <td colspan="4" class="text-center">
<img style="margin: auto; width: 35px;" src="static/image/empty_box.svg" <img style="margin: auto; width: 35px;" src="static/image/empty_box.svg"
alt="A drawing of an empty box." /> alt="{{ tr('aria_empty_box') }}" />
</td> </td>
</tr> </tr>
<tr class="playlist-expand table-dark d-none"> <tr class="playlist-expand table-dark d-none">
<td colspan="4" class="text-center"> <td colspan="4" class="text-center">
<a class="text-muted" href="javascript:">See item <span <a class="text-muted" href="javascript:">{{ tr('expand_playlist') }}</a>
class="playlist-expand-item-range"></span> on the playlist.</a>
</td> </td>
</tr> </tr>
<tr class="playlist-item-template d-none" aria-hidden="true"> <tr class="playlist-item-template d-none" aria-hidden="true">
@@ -112,7 +111,7 @@
<input hidden type="hidden" class="playlist-item-id" value="" /> <input hidden type="hidden" class="playlist-item-id" value="" />
<div class="float-left"> <div class="float-left">
<img width="80" class="playlist-item-thumbnail" src="static/image/unknown-album.png" <img width="80" class="playlist-item-thumbnail" src="static/image/unknown-album.png"
alt="A black square with two eight notes beamed together." /> alt="{{ tr('aria_default_cover') }}" />
</div> </div>
<div class="playlist-artwork"> <div class="playlist-artwork">
<b class="playlist-item-title"></b> <b class="playlist-item-title"></b>
@@ -134,11 +133,11 @@
<td> <td>
<div class="btn-group"> <div class="btn-group">
<button type="button" class="playlist-item-play btn btn-info btn-sm" <button type="button" class="playlist-item-play btn btn-info btn-sm"
aria-label="Skip current song and play this song right now"> aria-label="{{ tr('aria_skip_current_song') }}">
<i class="fas fa-play" aria-hidden="true"></i> <i class="fas fa-play" aria-hidden="true"></i>
</button> </button>
<button type="button" class="playlist-item-trash btn btn-danger btn-sm ml-1" <button type="button" class="playlist-item-trash btn btn-danger btn-sm ml-1"
aria-label="Remove this song from the current playlist"> aria-label="{{ tr('aria_remove_this_song') }}">
<i class="fas fa-trash-alt" aria-hidden="true"></i> <i class="fas fa-trash-alt" aria-hidden="true"></i>
</button> </button>
</div> </div>
@@ -150,52 +149,51 @@
<div class="btn-group"> <div class="btn-group">
<button type="button" id="clear-playlist-btn" class="btn btn-danger mr-1"> <button type="button" id="clear-playlist-btn" class="btn btn-danger mr-1">
<i class="fas fa-trash-alt" aria-hidden="true"></i> Clear Playlist <i class="fas fa-trash-alt" aria-hidden="true"></i> {{ tr('clear_playlist') }}</button>
</button>
</div> </div>
</main> </main>
<div class="container mb-3"> <div class="container mb-3">
<h2 id="forms">Music Library</h2> <h2 id="forms">{{ tr('music_library') }}</h2>
<div class="card mb-3"> <div class="card mb-3">
<div class="card-header"> <div class="card-header">
<h3 class="card-title">Filters</h3> <h3 class="card-title">{{ tr('filters') }}</h3>
<hr> <hr>
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<fieldset id="filter-type" class="mb-2"> <fieldset id="filter-type" class="mb-2">
<legend>Type</legend> <legend>{{ tr('type') }}</legend>
<div class="btn-group btn-group-sm btn-group-toggle"> <div class="btn-group btn-group-sm btn-group-toggle">
<label id="filter-type-file" class="btn btn-secondary"> <label id="filter-type-file" class="btn btn-secondary">
<input type="checkbox" name="options"> File <input type="checkbox" name="options">{{ tr('file') }}
</label> </label>
<label id="filter-type-url" class="btn btn-secondary"> <label id="filter-type-url" class="btn btn-secondary">
<input type="checkbox" name="options"> URL <input type="checkbox" name="options">{{ tr('url') }}
</label> </label>
<label id="filter-type-radio" class="btn btn-secondary"> <label id="filter-type-radio" class="btn btn-secondary">
<input type="checkbox" name="options"> Radio <input type="checkbox" name="options">{{ tr('radio') }}
</label> </label>
</div> </div>
</fieldset> </fieldset>
<label for="filter-dir">Directory</label> <label for="filter-dir">{{ tr('directory') }}</label>
<div id="filter-path" class="input-group mb-2"> <div id="filter-path" class="input-group mb-2">
<select class="form-control form-control-sm" id="filter-dir" disabled> <select class="form-control form-control-sm" id="filter-dir" disabled>
<option value="">.</option> <option value="">.</option>
</select> </select>
</div> </div>
<label for="filter-keywords">Keywords</label> <label for="filter-keywords">{{ tr('keywords') }}</label>
<div id="filter-path" class="input-group mb-2"> <div id="filter-path" class="input-group mb-2">
<input class="form-control form-control-sm" id="filter-keywords" name="keywords" <input class="form-control form-control-sm" id="filter-keywords" name="keywords"
placeholder="Keywords..." style="margin-top:5px;" /> placeholder="{{ tr('keywords_placeholder') }}" style="margin-top:5px;" />
</div> </div>
</div> </div>
<div class="col"> <div class="col">
<fieldset id="filter-tags"> <fieldset id="filter-tags">
<legend>Tags</legend> <legend>{{ tr('tags') }}</legend>
<span class="filter-tag tag-unclicked tag-click badge"></span> <span class="filter-tag tag-unclicked tag-click badge"></span>
</fieldset> </fieldset>
</div> </div>
@@ -205,11 +203,11 @@
<div id="library-group" class="list-group library-group" style="overflow: auto;"> <div id="library-group" class="list-group library-group" style="overflow: auto;">
<div id="library-item-loading" class="list-group-item library-item"> <div id="library-item-loading" class="list-group-item library-item">
<img style="margin: auto; width: 35px;" src="static/image/loading.svg" <img style="margin: auto; width: 35px;" src="static/image/loading.svg"
alt="A loading spinner" /> alt="{{ tr('aria_spinner') }}" />
</div> </div>
<div id="library-item-empty" style="display: none" class="list-group-item library-item"> <div id="library-item-empty" style="display: none" class="list-group-item library-item">
<img style="margin: auto; width: 35px;" src="static/image/empty_box.svg" <img style="margin: auto; width: 35px;" src="static/image/empty_box.svg"
alt="A drawing of an empty box." /> alt="{{ tr('aria_empty_box') }}" />
</div> </div>
<div id="library-item" style="display: none;" class="list-group-item library-item"> <div id="library-item" style="display: none;" class="list-group-item library-item">
<input hidden type="hidden" class="library-item-id" value="" /> <input hidden type="hidden" class="library-item-id" value="" />
@@ -217,10 +215,10 @@
<div class="library-thumb-col"> <div class="library-thumb-col">
<div class="library-thumb-img"> <div class="library-thumb-img">
<img class="library-item-thumb library-thumb-img" src="static/image/unknown-album.png" <img class="library-item-thumb library-thumb-img" src="static/image/unknown-album.png"
alt="A black square with two eight notes beamed together." /> alt="{{ tr('aria_default_cover') }}" />
</div> </div>
<div class="btn-group-vertical library-thumb-grp"> <div class="btn-group-vertical library-thumb-grp">
<div class="library-item-play btn btn-secondary library-thumb-btn-up" title="Play"> <div class="library-item-play btn btn-secondary library-thumb-btn-up" title="{{ tr('play') }}">
<i class="fas fa-play" aria-hidden="true"></i> <i class="fas fa-play" aria-hidden="true"></i>
</div> </div>
</div> </div>
@@ -239,14 +237,14 @@
<div class="library-item-tags"> <div class="library-item-tags">
<a class="tag-space tag-click library-item-edit"><i class="fas fa-edit" <a class="tag-space tag-click library-item-edit"><i class="fas fa-edit"
style="color: #AAAAAA"></i></a> style="color: #AAAAAA"></i></a>
<span class="library-item-notag badge badge-light text-muted font-italic">No tag</span> <span class="library-item-notag badge badge-light text-muted font-italic">{{ tr('no_tag') }}</span>
<span class="library-item-tag tag-space badge">Tag</span> <span class="library-item-tag tag-space badge">Tag</span>
</div> </div>
</div> </div>
<div class="btn-group library-action"> <div class="btn-group library-action">
<button class="library-item-add-next btn btn-info btn-sm btn-space" type="button" <button class="library-item-add-next btn btn-info btn-sm btn-space" type="button"
title="Next to play" aria-label="Add to playlist right after current song"> title="{{ tr('next_to_play') }}" aria-label="{{ tr('add_to_playlist_next') }}">
<svg class="library-btn-svg" style="width: 1rem; fill: currentColor;" <svg class="library-btn-svg" style="width: 1rem; fill: currentColor;"
viewBox="5 5 17 17"> viewBox="5 5 17 17">
<path d="m5.700245,3.92964l0,14.150376l11.451127,-7.075188l-11.451127,-7.075188z"> <path d="m5.700245,3.92964l0,14.150376l11.451127,-7.075188l-11.451127,-7.075188z">
@@ -257,7 +255,7 @@
</svg> </svg>
</button> </button>
<button class="library-item-add-bottom library-btn btn btn-info btn-sm btn-space" <button class="library-item-add-bottom library-btn btn btn-info btn-sm btn-space"
type="button" title="Add to bottom" aria-label="Add to bottom of current playlist"> type="button" title="{{ tr('add_to_bottom') }}" aria-label="{{ tr('add_to_bottom_of_current_playlist') }}">
<svg class="library-btn-svg" style="width: 1rem; fill: currentColor;" <svg class="library-btn-svg" style="width: 1rem; fill: currentColor;"
viewBox="2 2 20 20"> viewBox="2 2 20 20">
<path <path
@@ -266,11 +264,11 @@
</svg> </svg>
</button> </button>
<button class="library-item-download btn btn-primary btn-sm btn-space" type="button" <button class="library-item-download btn btn-primary btn-sm btn-space" type="button"
aria-label="Download song from library"> aria-label="{{ tr('download_song_from_library') }}">
<i class="fas fa-download" aria-hidden="true"></i> <i class="fas fa-download" aria-hidden="true"></i>
</button> </button>
<button class="library-item-trash btn btn-danger btn-sm btn-space" type="button" <button class="library-item-trash btn btn-danger btn-sm btn-space" type="button"
aria-label="Remove song from library"> aria-label="{{ tr('remove_song_from_library') }}">
<i class="fas fa-trash-alt"></i> <i class="fas fa-trash-alt"></i>
</button> </button>
</div> </div>
@@ -290,40 +288,36 @@
<div class="btn-group mb-2" role="group"> <div class="btn-group mb-2" role="group">
<button id="add-to-playlist-btn" type="button" class="btn btn-secondary mr-1"> <button id="add-to-playlist-btn" type="button" class="btn btn-secondary mr-1">
<i class="fas fa-plus" aria-hidden="true"></i> Add All <i class="fas fa-plus" aria-hidden="true"></i>{{ tr('add_all') }}
</button> </button>
<button id="library-rescan-btn" type="button" class="btn btn-secondary mr-1"> <button id="library-rescan-btn" type="button" class="btn btn-secondary mr-1">
<i class="fas fa-sync-alt" aria-hidden="true"></i> Rescan Files <i class="fas fa-sync-alt" aria-hidden="true"></i>{{ tr('rescan_files') }}
</button> </button>
<button id="library-download-btn" type="button" class="btn btn-secondary mr-1"> <button id="library-download-btn" type="button" class="btn btn-secondary mr-1">
<i class="fas fa-download" aria-hidden="true"></i> Download All <i class="fas fa-download" aria-hidden="true"></i>{{ tr('download_all') }}
</button> </button>
<button type="button" class="btn btn-danger mr-1" data-toggle="modal" <button type="button" class="btn btn-danger mr-1" data-toggle="modal"
data-target="#deleteWarningModal"> data-target="#deleteWarningModal">
<i class="fas fa-trash-alt" aria-hidden="true"></i> Delete All <i class="fas fa-trash-alt" aria-hidden="true"></i>{{ tr('delete_all') }}
</button> </button>
</div> </div>
<!-- QUESTION: should this div have aria-hidden as true?? -->
<div class="modal fade" id="deleteWarningModal" tabindex="-1" role="dialog" <div class="modal fade" id="deleteWarningModal" tabindex="-1" role="dialog"
aria-label="Modal Window for warning about deletion of files." aria-hidden="true"> aria-label="{{ tr('aria_warning_of_deletion') }}" aria-hidden="true">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h3 class="modal-title" id="deleteWarningModalLabel">Are you really sure?</h3> <h3 class="modal-title" id="deleteWarningModalLabel">{{ tr('are_you_really_sure') }}</h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="{{ tr('close') }}">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
All files listed here, include files on other pages, will be deleted from your {{ tr('delete_file_warning') }}</div>
hard-drive.
Is that what you want?
</div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">{{ tr('close') }}</button>
<button id="library-delete-btn" type="button" class="btn btn-danger" <button id="library-delete-btn" type="button" class="btn btn-danger"
data-dismiss="modal">Delete All Listed Files</button> data-dismiss="modal">{{ tr('delete_all_files') }}</button>
</div> </div>
</div> </div>
</div> </div>
@@ -336,7 +330,7 @@
<input type="hidden" id="uploadDisabled" value="false" /> <input type="hidden" id="uploadDisabled" value="false" />
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h3 class="card-title">Upload File</h3> <h3 class="card-title">{{ tr('upload_file') }}</h3>
</div> </div>
<div class="card-body"> <div class="card-body">
<form action="./upload" method="post" enctype="multipart/form-data"> <form action="./upload" method="post" enctype="multipart/form-data">
@@ -345,8 +339,8 @@
<div class="input-group mb-3"> <div class="input-group mb-3">
<div id="uploadField" style="display: flex; width: 100%"> <div id="uploadField" style="display: flex; width: 100%">
<div class="custom-file"> <div class="custom-file">
<input type="file" name="file[]" class="custom-file-input" id="uploadSelectFile" aria-describedby="uploadSubmit" value="Browse Music file" multiple /> <input type="file" name="file[]" class="custom-file-input" id="uploadSelectFile" aria-describedby="uploadSubmit" value="{{ tr('browse_music_file') }}" multiple />
<label class="custom-file-label" for="uploadSelectFile">Choose file</label> <label class="custom-file-label" for="uploadSelectFile">{{ tr('choose_file') }}</label>
</div> </div>
</div> </div>
</div> </div>
@@ -356,7 +350,7 @@
<div class="col"> <div class="col">
<div class="input-group mb-3"> <div class="input-group mb-3">
<div class="input-group-prepend"> <div class="input-group-prepend">
<label for="uploadTargetDir" class="input-group-text">Upload To</label> <label for="uploadTargetDir" class="input-group-text">{{ tr('upload_to') }}</label>
</div> </div>
<input class="form-control" list="upload-target-dirs" id="uploadTargetDir" name="upload-target-dirs" placeholder="uploads" /> <input class="form-control" list="upload-target-dirs" id="uploadTargetDir" name="upload-target-dirs" placeholder="uploads" />
<datalist id="upload-target-dirs"> <datalist id="upload-target-dirs">
@@ -364,7 +358,7 @@
</div> </div>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<button class="btn btn-primary" type="button" id="uploadSubmit"><i class="fas fa-upload mr-1"></i>Upload!</button> <button class="btn btn-primary" type="button" id="uploadSubmit"><i class="fas fa-upload mr-1"></i>{{ tr('upload_submit') }}</button>
</div> </div>
</div> </div>
</div> </div>
@@ -378,29 +372,29 @@
<div class="card-deck"> <div class="card-deck">
<div id="add-music-url" class="card"> <div id="add-music-url" class="card">
<div class="card-header"> <div class="card-header">
<h3 class="card-title">Add URL</h3> <h3 class="card-title">{{ tr('add_url') }}</h3>
</div> </div>
<div class="card-body"> <div class="card-body">
<label for="music-url-input">Add Youtube or Soundcloud URL</label> <label for="music-url-input">{{ tr('add_youtube_or_soundcloud_url') }}</label>
<div class="input-group mb-2"> <div class="input-group mb-2">
<input class="form-control" type="text" id="music-url-input" placeholder="URL..."> <input class="form-control" type="text" id="music-url-input" placeholder="{{ tr('url_placeholder') }}">
</div> </div>
<button type="submit" class="btn btn-primary"> <button type="submit" class="btn btn-primary">
Add URL {{ tr('add_url') }}
</button> </button>
</div> </div>
</div> </div>
<div id="add-radio-url" class="card"> <div id="add-radio-url" class="card">
<div class="card-header"> <div class="card-header">
<h3 class="card-title">Add Radio</h3> <h3 class="card-title">{{ tr('add_radio') }}</h3>
</div> </div>
<div class="card-body"> <div class="card-body">
<label for="radio-url-input">Add Radio URL</label> <label for="radio-url-input">{{ tr('add_radio_url') }}</label>
<div class="input-group mb-2"> <div class="input-group mb-2">
<input id="radio-url-input" class="form-control" type="text" placeholder="Radio Address..."> <input id="radio-url-input" class="form-control" type="text" placeholder="{{ tr('radio_url_placeholder') }}">
</div> </div>
<button type="submit" class="btn btn-primary"> <button type="submit" class="btn btn-primary">
Add Radio {{ tr('add_radio') }}
</button> </button>
</div> </div>
</div> </div>
@@ -418,26 +412,26 @@
<div id="playerToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false"> <div id="playerToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header"> <div class="toast-header">
<i class="fas fa-play-circle mr-2 text-primary"></i> <i class="fas fa-play-circle mr-2 text-primary"></i>
<strong class="mr-auto">Now Playing...</strong> <strong class="mr-auto">{{ tr('mini_player_title') }}</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close"> <button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="toast-body" id="playerContainer"> <div class="toast-body" id="playerContainer">
<img id="playerArtworkIdle" src="static/image/empty_box.svg" alt="A drawing of an empty box." /> <img id="playerArtworkIdle" src="static/image/empty_box.svg" alt="{{ tr('aria_empty_box') }}" />
<img id="playerArtwork" src="static/image/unknown-album.png" style="display: none;" <img id="playerArtwork" src="static/image/unknown-album.png" style="display: none;"
alt="A black square with two eight notes beamed together." /> alt="{{ tr('aria_default_cover') }}" />
<div id="playerInfo"> <div id="playerInfo">
<div id="playerActionBox"> <div id="playerActionBox">
<button id="playerPlayBtn" class="btn btn-primary btn-sm btn-space" style="display: none" <button id="playerPlayBtn" class="btn btn-primary btn-sm btn-space" style="display: none"
aria-label="Play"> aria-label="{{ tr('play') }}">
<i class="fas fa-play"></i> <i class="fas fa-play"></i>
</button> </button>
<button id="playerPauseBtn" class="btn btn-primary btn-sm btn-space" style="display: none" <button id="playerPauseBtn" class="btn btn-primary btn-sm btn-space" style="display: none"
aria-label="Pause"> aria-label="{{ tr('pause') }}">
<i class="fas fa-pause"></i> <i class="fas fa-pause"></i>
</button> </button>
<button id="playerSkipBtn" class="btn btn-primary btn-sm" aria-label="Skip to next track"> <button id="playerSkipBtn" class="btn btn-primary btn-sm" aria-label="{{ tr('aria_skip_to_next_track') }}">
<i class="fas fa-fast-forward"></i> <i class="fas fa-fast-forward"></i>
</button> </button>
</div> </div>
@@ -470,7 +464,7 @@
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h3 class="modal-title" id="addTagModalTitle">Edit tags for ?</h3> <h3 class="modal-title">{{ tr('edit_tags_for') }} <span id="addTagModalTitle">?</span></h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
@@ -486,17 +480,17 @@
</div> </div>
<div class="input-group"> <div class="input-group">
<input class="form-control form-control-sm btn-space" type="text" id="addTagModalInput" <input class="form-control form-control-sm btn-space" type="text" id="addTagModalInput"
placeholder="tag1,tag2,..." aria-label="Tags to add"> placeholder="tag1,tag2,..." aria-label="{{ tr('tags_to_add') }}">
<button id="addTagModalAddBtn" type="button" class="btn btn-primary btn-sm"> <button id="addTagModalAddBtn" type="button" class="btn btn-primary btn-sm">
<i class="fas fa-plus" aria-hidden="true"></i> <i class="fas fa-plus" aria-hidden="true"></i>
Add {{ tr('add') }}
</button> </button>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">{{ tr('close') }}</button>
<button id="addTagModalSubmit" type="button" class="btn btn-success" <button id="addTagModalSubmit" type="button" class="btn btn-success"
data-dismiss="modal">Edit!</button> data-dismiss="modal">{{ tr('edit_submit') }}</button>
</div> </div>
</div> </div>
</div> </div>
@@ -507,12 +501,12 @@
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h3 class="modal-title" id="uploadTitle"><i class="fas fa-upload mr-1"></i>Uploading files...</h3> <h3 class="modal-title" id="uploadTitle"><i class="fas fa-upload mr-1"></i>{{ tr('uploading_files') }}</h3>
</div> </div>
<div id="uploadModalBody" class="modal-body"> <div id="uploadModalBody" class="modal-body">
<div id="uploadSuccessAlert" class="alert alert-success" role="alert" style="display: none"> <div id="uploadSuccessAlert" class="alert alert-success" role="alert" style="display: none">
<i class="fas fa-check mr-1"></i> <i class="fas fa-check mr-1"></i>
Uploaded finished! {{ tr('uploaded_finished') }}
</div> </div>
<div id="uploadModalList" style="margin-left: 5px; margin-bottom: 10px;"> <div id="uploadModalList" style="margin-left: 5px; margin-bottom: 10px;">
<div class="uploadItem" style="display: none; width: 100%; padding-bottom: 8px;"> <div class="uploadItem" style="display: none; width: 100%; padding-bottom: 8px;">
@@ -529,11 +523,11 @@
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" id="uploadClose" class="btn btn-success" data-dismiss="modal"> <button type="button" id="uploadClose" class="btn btn-success" data-dismiss="modal">
<i class="fas fa-times mr-1"></i> Close</button> <i class="fas fa-times mr-1"></i> {{ tr('close') }}</button>
<button type="button" id="uploadCancel" class="btn btn-danger" data-toggle="tooltip" <button type="button" id="uploadCancel" class="btn btn-danger" data-toggle="tooltip"
data-html="true" data-html="true"
title="<strong>Are you really sure?</strong> <br /> Click again to abort uploading."> title="{{ tr('cancel_upload_warning') }}">
<i class="fas fa-trash-alt mr-1" aria-hidden="true"></i> Cancel</button> <i class="fas fa-trash-alt mr-1" aria-hidden="true"></i> {{ tr('cancel') }}</button>
</div> </div>
</div> </div>
</div> </div>
@@ -4,7 +4,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>botamusique web interface</title> <title>{{ tr('page_title') }}</title>
<link rel="icon" href="static/image/favicon.ico" /> <link rel="icon" href="static/image/favicon.ico" />
@@ -16,19 +16,17 @@
<div class="col-8" style="margin: auto; padding-top: 50px;"> <div class="col-8" style="margin: auto; padding-top: 50px;">
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
Token Required {{ tr('token_required') }}
</div> </div>
<div class="card-body"> <div class="card-body">
<h3>Token Required!</h3> <h3>{{ tr('token_required') }}</h3>
You are accessing the web interface of {{ name }}. {{ tr('token_required_message') }}
A token is needed to grant you access.<br />
Please send "{{ command }}" to the bot in mumble to acquire one.
<form action="." method="get"> <form action="." method="get">
<div class="form-group mt-3"> <div class="form-group mt-3">
<label for="token_input">Token</label> <label for="token_input">{{ tr('token') }}</label>
<div class="input-group"> <div class="input-group">
<input type="password" class="form-control btn-space" id="token_input" name="token" placeholder="xxxxxxx"> <input type="password" class="form-control btn-space" id="token_input" name="token" placeholder="xxxxxxx">
<button type="submit" class="btn btn-primary">Submit</button> <button type="submit" class="btn btn-primary">{{ tr('submit') }}</button>
</div> </div>
</div> </div>
</form> </form>
+4 -4
View File
@@ -29,13 +29,13 @@ module.exports = {
filename: 'static/css/[name].css', filename: 'static/css/[name].css',
}), }),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
filename: 'templates/index.html', filename: 'templates/index.template.html',
template: './templates/index.html', template: './templates/index.template.html',
inject: false, inject: false,
}), }),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
filename: 'templates/need_token.html', filename: 'templates/need_token.template.html',
template: './templates/need_token.html', template: './templates/need_token.template.html',
inject: false, inject: false,
}), }),
], ],