diff --git a/command.py b/command.py index 704f908..f868eb6 100644 --- a/command.py +++ b/command.py @@ -14,7 +14,7 @@ import util import variables as var from pyradios import RadioBrowser from database import SettingsDatabase, MusicDatabase, Condition -from media.item import item_id_generators, dict_to_item, dicts_to_items +from media.item import item_id_generators, dict_to_item, dicts_to_items, ValidationFailedError from media.cache import get_cached_wrapper_from_scrap, get_cached_wrapper_by_id, get_cached_wrappers_by_tags, \ get_cached_wrapper, get_cached_wrappers, get_cached_wrapper_from_dict, get_cached_wrappers_from_dicts from media.url_from_playlist import get_playlist_info @@ -74,6 +74,9 @@ def register_all_commands(bot): bot.register_command(commands('url_ban'), cmd_url_ban, 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(commands('url_unban'), cmd_url_unban, no_partial_match=True, admin=True) + bot.register_command(commands('url_unwhitelist'), cmd_url_unwhitelist, no_partial_match=True, admin=True) + bot.register_command(commands('url_whitelist'), cmd_url_whitelist, no_partial_match=True, admin=True) + bot.register_command(commands('url_whitelist_list'), cmd_url_whitelist_list, no_partial_match=True, admin=True) bot.register_command(commands('user_ban'), cmd_user_ban, no_partial_match=True, admin=True) bot.register_command(commands('user_unban'), cmd_user_unban, no_partial_match=True, admin=True) @@ -149,46 +152,102 @@ def cmd_user_ban(bot, user, text, command, parameter): global log if parameter: - bot.mumble.users[text.actor].send_text_message(util.user_ban(parameter)) + var.db.set("user_ban", parameter, None) + bot.send_msg(tr("user_ban_success", user=parameter), text) else: - bot.mumble.users[text.actor].send_text_message(util.get_user_ban()) + ban_list = "" + bot.send_msg(tr("user_ban_list", list=ban_list), text) def cmd_user_unban(bot, user, text, command, parameter): global log - if parameter: - bot.mumble.users[text.actor].send_text_message(util.user_unban(parameter)) + if parameter and var.db.has_option("user_ban", parameter): + var.db.remove_option("user_ban", parameter) + bot.send_msg(tr("user_unban_success", user=parameter), text) def cmd_url_ban(bot, user, text, command, parameter): global log - if parameter: - bot.mumble.users[text.actor].send_text_message(util.url_ban(util.get_url_from_input(parameter))) - - id = item_id_generators['url'](url=parameter) - var.cache.free_and_delete(id) - var.playlist.remove_by_id(id) + url = util.get_url_from_input(parameter) + if url: + _id = item_id_generators['url'](url=url) + var.cache.free_and_delete(_id) + var.playlist.remove_by_id(_id) else: if var.playlist.current_item() and var.playlist.current_item().type == 'url': item = var.playlist.current_item().item() - bot.mumble.users[text.actor].send_text_message(util.url_ban(util.get_url_from_input(item.url))) + url = item.url var.cache.free_and_delete(item.id) var.playlist.remove_by_id(item.id) else: bot.send_msg(tr('bad_parameter', command=command), text) + return + + # Remove from the whitelist first + if var.db.has_option('url_whitelist', url): + var.db.remove_option("url_whitelist", url) + bot.send_msg(tr("url_unwhitelist_success", url=url), text) + + if not var.db.has_option('url_ban', url): + var.db.set("url_ban", url, None) + bot.send_msg(tr("url_ban_success", url=url), text) def cmd_url_ban_list(bot, user, text, command, parameter): - bot.mumble.users[text.actor].send_text_message(util.get_url_ban()) + ban_list = "" + + bot.send_msg(tr("url_ban_list", list=ban_list), text) def cmd_url_unban(bot, user, text, command, parameter): - global log + url = util.get_url_from_input(parameter) + if url: + var.db.remove_option("url_ban", url) + bot.send_msg(tr("url_unban_success", url=url), text) + else: + bot.send_msg(tr('bad_parameter', command=command), text) - if parameter: - bot.mumble.users[text.actor].send_text_message(util.url_unban(util.get_url_from_input(parameter))) + +def cmd_url_whitelist(bot, user, text, command, parameter): + url = util.get_url_from_input(parameter) + if url: + # Unban first + if var.db.has_option('url_ban', url): + var.db.remove_option("url_ban", url) + bot.send_msg(tr("url_unban_success"), text) + + # Then add to whitelist + if not var.db.has_option('url_whitelist', url): + var.db.set("url_whitelist", url, None) + bot.send_msg(tr("url_whitelist_success", url=url), text) + else: + bot.send_msg(tr('bad_parameter', command=command), text) + + +def cmd_url_whitelist_list(bot, user, text, command, parameter): + ban_list = "" + + bot.send_msg(tr("url_whitelist_list", list=ban_list), text) + + +def cmd_url_unwhitelist(bot, user, text, command, parameter): + url = util.get_url_from_input(parameter) + if url: + var.db.remove_option("url_whitelist", url) + bot.send_msg(tr("url_unwhitelist_success"), text) + else: + bot.send_msg(tr('bad_parameter', command=command), text) def cmd_play(bot, user, text, command, parameter): @@ -338,6 +397,7 @@ def cmd_play_url(bot, user, text, command, parameter): log.info("cmd: add to playlist: " + music_wrapper.format_debug_string()) send_item_added_message(bot, music_wrapper, len(var.playlist) - 1, text) + if len(var.playlist) == 2: # If I am the second item on the playlist. (I am the next one!) bot.async_download_next() diff --git a/configuration.default.ini b/configuration.default.ini index 4ead2bf..35e818d 100644 --- a/configuration.default.ini +++ b/configuration.default.ini @@ -183,6 +183,9 @@ user_unban = userunban url_ban = urlban url_ban_list = urlbanlist url_unban = urlunban +url_whitelist = urlwhitelist, urlw +url_unwhitelist = urlunwhitelist, urlunw +url_whitelist_list = urlwhitelistlist, urlwls ducking = duck ducking_threshold = duckthres diff --git a/lang/en_US.json b/lang/en_US.json index 10770ff..cc69062 100644 --- a/lang/en_US.json +++ b/lang/en_US.json @@ -61,17 +61,26 @@ "shortlist_instruction": "Use !sl {indexes} to play the item you want.", "start_updating": "Start updating...", "stopped": "Music stopped.", - "too_long": "{song} is too long, removed from playlist!", + "too_long": "{song} is too long ({duration} > {max_duration}), removed from playlist!", "unable_download": "Unable to download {item}. Removed from the library.", "unable_play": "Unable to play {item}. Removed from the library.", "unknown_mode": "Unknown playback mode '{mode}'. It should be one of one-shot, repeat, random.", "update_successful": "

botamusique v{version} Installed!


\n

Changelog

{changelog}
Visit our github repo for more details!", "url": "URL", - "url_ban": "This URL is banned!", + "url_ban": "The URL {url} is banned! Removed from playlist!", + "url_ban_success": "The following URL is banned: {url}.", + "url_ban_list": "List of banned URL:
{list}", "url_from_playlist": "URL", "url_from_playlist_item": "{title} from playlist {playlist} added by {user}", "url_item": "{title} added by {user}", + "url_unban_success": "The following URL is unbanned: {url}.", + "url_unwhitelist_success": "The following URL is un-whitelisted: {url}.", + "url_whitelist_success": "The following URL is whitelisted: {url}.", + "url_whitelist_list": "List of whitelisted URL:
{list}", "user_ban": "You are banned, not allowed to do that!", + "user_ban_success": "User {user} is banned.", + "user_ban_list": "List of banned user:
{list}", + "user_unban_success": "User {user} is unbanned.", "user_password_set": "Your password has been updated.", "web_user_list": "Following users have the privilege to access the web interface:
{users}", "webpage_address": "Your own address to access the web interface is {address}", diff --git a/lang/es_ES.json b/lang/es_ES.json index 91127f2..d5028ca 100644 --- a/lang/es_ES.json +++ b/lang/es_ES.json @@ -61,13 +61,13 @@ "shortlist_instruction": "Use !sl {índices} para reproducir los elementos que usted desea.", "start_updating": "Empezando la actualización...", "stopped": "Música fue detenida.", - "too_long": "{song} es muy larga. Eliminada de la lista de reproducción!", + "too_long": "{song} es muy larga ({duration} > {max_duration}). Eliminada de la lista de reproducción!", "unable_download": "No fue posible descargar {item}. Eliminado de la biblioteca.", "unable_play": "No fue posible reproducir {item}. Eliminado de la biblioteca.", "unknown_mode": "Modo de reproducción '{mode}' desconocido. Debiera ser o bien one-shot, repeat o random.", "update_successful": "

botamusique v{version} instalado!


\n

Lista de cambios

{changelog}
Visite nuestro repositorio en Github para más detalles!", "url": "URL", - "url_ban": "Esta URL está baneada!", + "url_ban": "URL {url} está baneada! Eliminada de la lista de reproducción!", "url_from_playlist": "URL", "url_from_playlist_item": "{title} de lista de reproducción {playlist} añadido por {user}", "url_item": "{title} añadido por {user}", diff --git a/lang/fr_FR.json b/lang/fr_FR.json index 15286b1..5c58230 100644 --- a/lang/fr_FR.json +++ b/lang/fr_FR.json @@ -61,13 +61,13 @@ "shortlist_instruction": "Utilisez !sl {indexes} pour jouer l'élément que vous voulez.", "start_updating": "Début de la mise à jour...", "stopped": "Musique arrêté.", - "too_long": "{song} est trop long, supprimé de la playlist !", + "too_long": "{song} est trop long ({duration} > {max_duration}), supprimé de la playlist !", "unable_download": "Impossible de télécharger {item}. Retiré de la bibliothèque.", "unable_play": "Impossible de jouer {item}. Retiré de la bibliothèque.", "unknown_mode": "Mode de lecture \"{mode}\" inconnu. Il devrait s'agir d'un des modes suivants : one-shot, repeat, random.", "update_successful": "

botamusique v{version} Installé !


\n

Changelog

{changelog}
Visitez notre repo github pour plus de détails !", "url": "URL", - "url_ban": "Cette URL est interdite !", + "url_ban": "URL {url} est interdite !", "url_from_playlist": "URL", "url_from_playlist_item": "{title} depuis la playlist {playlist} ajouté par {user}", "url_item": "{title} ajouté par {user}", diff --git a/lang/it_IT.json b/lang/it_IT.json index c724b11..f94a26f 100644 --- a/lang/it_IT.json +++ b/lang/it_IT.json @@ -61,13 +61,13 @@ "shortlist_instruction": "Usa !sl {indexes} per riprodurre l'elemento desiderato.", "start_updating": "Inizio aggiornamento...", "stopped": "Riproduzione interrotta.", - "too_long": "{song} è troppo lunga, rimossa dalla playlist!", + "too_long": "{song} è troppo lunga ({duration} > {max_duration}), rimossa dalla playlist!", "unable_download": "Impossibile scaricare {item}. Rimosso dalla libreria.", "unable_play": "Impossibile riprodurre {item}. Rimosso dalla libreria.", "unknown_mode": "Modalità di riproduzione '{mode}' sconosciuta. Dovrebbe essere one-shot, ripeti, casuale.", "update_successful": "

botamusique v{version} installato!


\n

Changelog

{changelog}
Visita la nostra repository GitHub per ulteriori dettagli!", "url": "URL", - "url_ban": "Questo URL è vietato!", + "url_ban": "URL {url} è vietato!", "url_from_playlist": "URL", "url_from_playlist_item": "{title} dalla playlist {playlist} aggiunto da {user}", "url_item": "{title} aggiunto da {user}", diff --git a/lang/ja_JP.json b/lang/ja_JP.json index 940afae..34b35e1 100644 --- a/lang/ja_JP.json +++ b/lang/ja_JP.json @@ -53,21 +53,21 @@ "rb_play_empty": "ラジオIDを提供してください。", "rb_query_result": "検索の結果( !rbplay {ID} を送信して再生する)", "records_omitted": "…", - "removed_tags": "もう{song}からタグ「 {tags}」を削除しました。", - "removed_tags_from_all": "もう再生リストの全ての曲にタグ「{tags} 」を削除しました。", - "removing_item": "もう再生リストに「{item}」を削除しました。", + "removed_tags": "{song}からタグ「 {tags}」を削除しました。", + "removed_tags_from_all": "再生リストの全ての曲にタグ「{tags} 」を削除しました。", + "removing_item": "再生リストに「{item}」を削除しました。", "repeat": "「{song}」を{n}回リピートするになります。", "report_version": "現在のbotamusiqueバージョンは{version}です。", "shortlist_instruction": "!sl {indexes}を使ってこのリストの曲を再生する。", "start_updating": "更新しています…", "stopped": "再生停止。", - "too_long": "「{song}」が長さ制限を超えました。削除されました。", + "too_long": "「{song}」が長さ制限を超えました({duration} > {max_duration})。削除されました。", "unable_download": "「{item}」がダウンロードできません。削除されました。", "unable_play": "「{item}」が再生できません。削除されました。", "unknown_mode": "不正な再生モード「{mode}」。 one-shot, repeat, random, autoplayの中の一つを使ってください。", "update_successful": "

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


\n

更新履歴

{changelog}
このプロジェクトの githubページ をご覧ください!", "url": "URL", - "url_ban": "このURLが禁止されています。", + "url_ban": "URL {url} が禁止されています。", "url_from_playlist": "URL", "url_from_playlist_item": "{title}、({playlist}から)、 {user} に追加されました。", "url_item": "{title} {user} に追加されました。", diff --git a/lang/zh_CN.json b/lang/zh_CN.json index 6eda882..0242805 100644 --- a/lang/zh_CN.json +++ b/lang/zh_CN.json @@ -61,13 +61,13 @@ "shortlist_instruction": "使用!sl {indexes}播放列表中的曲目。", "start_updating": "开始更新……", "stopped": "音乐停止。", - "too_long": "{song}超出长度限制!已被移出播放列表。", + "too_long": "{song}超出长度限制({duration} > {max_duration})!已被移出播放列表。", "unable_download": "无法下载{item}。已移出播放列表。", "unable_play": "无法播放{item}。已移出播放列表。", "unknown_mode": "未知播放模式\"{mode}\"。播放模式应为 one-shot, repeat, random中的一个。", "update_successful": "

botamusique v{version} 安装完毕!


\n

更新日志

{changelog}
请访问我们的 github页面 获取更多信息!", "url": "URL", - "url_ban": "该链接被列入黑名单了!", + "url_ban": "链接{url}被列入黑名单了!", "url_from_playlist": "URL", "url_from_playlist_item": "{title},来自播放列表 {playlist},由 {user} 添加。", "url_item": "{title} {user} 添加。", diff --git a/media/url.py b/media/url.py index 8276b87..3d31375 100644 --- a/media/url.py +++ b/media/url.py @@ -15,6 +15,7 @@ import variables as var from media.item import BaseItem, item_builders, item_loaders, item_id_generators, ValidationFailedError, \ PreparationFailedError import media.system +from util import format_time log = logging.getLogger("bot") @@ -74,36 +75,45 @@ class URLItem(BaseItem): return True def validate(self): - self.validating_lock.acquire() - if self.ready in ['yes', 'validated']: + try: + self.validating_lock.acquire() + if self.ready in ['yes', 'validated']: + return True + + # if self.ready == 'failed': + # self.validating_lock.release() + # return False + # + if os.path.exists(self.path): + self.ready = "yes" + return True + + # Check if this url is banned + if var.db.has_option('url_ban', self.url): + raise ValidationFailedError(tr('url_ban', url=self.url)) + + # avoid multiple process validating in the meantime + info = self._get_info_from_url() + + if not info: + return False + + # Check if the song is too long and is not whitelisted + max_duration = var.config.getint('bot', 'max_track_duration') * 60 + if max_duration and \ + not var.db.has_option('url_whitelist', self.url) and \ + self.duration > max_duration: + log.info( + "url: " + self.url + " has a duration of " + str(self.duration / 60) + " min -- too long") + raise ValidationFailedError(tr('too_long', song=self.format_title(), + duration=format_time(self.duration), + max_duration=format_time(max_duration))) + else: + self.ready = "validated" + self.version += 1 # notify wrapper to save me + return True + finally: self.validating_lock.release() - return True - - # if self.ready == 'failed': - # self.validating_lock.release() - # return False - # - if os.path.exists(self.path): - self.validating_lock.release() - self.ready = "yes" - return True - - # avoid multiple process validating in the meantime - info = self._get_info_from_url() - self.validating_lock.release() - - if not info: - return False - - if self.duration > var.config.getint('bot', 'max_track_duration') * 60 != 0: - # Check the length, useful in case of playlist, it wasn't checked before) - log.info( - "url: " + self.url + " has a duration of " + str(self.duration / 60) + " min -- too long") - raise ValidationFailedError(tr('too_long', song=self.title)) - else: - self.ready = "validated" - self.version += 1 # notify wrapper to save me - return True # Run in a other thread def prepare(self): @@ -126,8 +136,8 @@ class URLItem(BaseItem): try: info = ydl.extract_info(self.url, download=False) self.duration = info['duration'] - self.title = info['title'] - self.keywords = info['title'] + self.title = info['title'].strip() + self.keywords = self.title succeed = True return True except youtube_dl.utils.DownloadError: @@ -237,7 +247,7 @@ class URLItem(BaseItem): return display def format_title(self): - return self.title if self.title.strip() else self.url + return self.title if self.title else self.url def display_type(self): return tr("url") diff --git a/mumbleBot.py b/mumbleBot.py index 099b580..6072dad 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -276,12 +276,10 @@ class MumbleBot: if not self.is_admin(user) and parameter: input_url = util.get_url_from_input(parameter) - if input_url: - for i in var.db.items("url_ban"): - if input_url == i[0]: - self.mumble.users[text.actor].send_text_message( - tr('url_ban')) - return + if input_url and var.db.has_option('url_ban', input_url): + self.mumble.users[text.actor].send_text_message( + tr('url_ban')) + return command_exc = "" try: @@ -408,13 +406,12 @@ class MumbleBot: # Function start if the next music isn't ready # Do nothing in case the next music is already downloaded self.log.debug("bot: Async download next asked ") - while var.playlist.next_item() and var.playlist.next_item().type in ['url', 'url_from_playlist']: + while var.playlist.next_item(): # usually, all validation will be done when adding to the list. # however, for performance consideration, youtube playlist won't be validate when added. # the validation has to be done here. next = var.playlist.next_item() try: - next.validate() if not next.is_ready(): self.async_download(next) @@ -432,8 +429,7 @@ class MumbleBot: th.start() return th - def validate_and_start_download(self, item): - item.validate() + def start_download(self, item): if not item.is_ready(): self.log.info("bot: current music isn't ready, start downloading.") self.async_download(item) @@ -442,15 +438,25 @@ class MumbleBot: def _download(self, item): ver = item.version + try: + item.validate() + if item.is_ready(): + return True + except ValidationFailedError as e: + self.send_channel_msg(e.msg) + var.playlist.remove_by_id(item.id) + var.cache.free_and_delete(item.id) + return False + try: item.prepare() + if item.version > ver: + var.playlist.version += 1 + return True except PreparationFailedError as e: self.send_channel_msg(e.msg) return False - if item.version > ver: - var.playlist.version += 1 - # ======================= # Loop # ======================= @@ -528,7 +534,7 @@ class MumbleBot: current = var.playlist.current_item() self.log.debug(f"bot: next into the song: {current.format_debug_string()}") try: - self.validate_and_start_download(current) + self.start_download(current) self.wait_for_ready = True self.song_start_at = -1 @@ -640,7 +646,7 @@ class MumbleBot: current = var.playlist.current_item() - self.validate_and_start_download(current) + self.start_download(current) self.is_pause = False self.wait_for_ready = True self.song_start_at = -1 diff --git a/util.py b/util.py index 6415775..e6dffac 100644 --- a/util.py +++ b/util.py @@ -159,37 +159,6 @@ def update(current_version): return msg -def user_ban(user): - var.db.set("user_ban", user, None) - res = "User " + user + " banned" - return res - - -def user_unban(user): - var.db.remove_option("user_ban", user) - res = "Done" - return res - - -def get_url_ban(): - res = "List of ban:" - for i in var.db.items("url_ban"): - res += "
" + i[0] - return res - - -def url_ban(url): - var.db.set("url_ban", url, None) - res = "url " + url + " banned" - return res - - -def url_unban(url): - var.db.remove_option("url_ban", url) - res = "Done" - return res - - def pipe_no_wait(): """ Generate a non-block pipe used to fetch the STDERR of ffmpeg. """ @@ -337,14 +306,14 @@ def get_url_from_input(string): if res: string = res.group(1) else: - return False + return "" match = re.search("(http|https)://(\S*)?/(\S*)", string, flags=re.IGNORECASE) if match: url = match[1].lower() + "://" + match[2].lower() + "/" + match[3] return url else: - return False + return "" def youtube_search(query): @@ -412,6 +381,14 @@ def parse_time(human): raise ValueError("Invalid time string given.") +def format_time(seconds): + hours = seconds // 3600 + seconds = seconds % 3600 + minutes = seconds // 60 + seconds = seconds % 60 + return f"{hours:d}:{minutes:02d}:{seconds:02d}" + + def parse_file_size(human): units = {"B": 1, "KB": 1024, "MB": 1024 * 1024, "GB": 1024 * 1024 * 1024, "TB": 1024 * 1024 * 1024 * 1024, "K": 1024, "M": 1024 * 1024, "G": 1024 * 1024 * 1024, "T": 1024 * 1024 * 1024 * 1024}