From 09f0a2783377e366a6a75d60ff57775abe244596 Mon Sep 17 00:00:00 2001 From: Mike Dallas Date: Wed, 3 May 2023 13:51:01 +0100 Subject: [PATCH 01/57] Fixed SyntaxError under Python 3.7 --- bazarr/subtitles/manual.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bazarr/subtitles/manual.py b/bazarr/subtitles/manual.py index 868918889..45ff4e12b 100644 --- a/bazarr/subtitles/manual.py +++ b/bazarr/subtitles/manual.py @@ -87,8 +87,7 @@ def manual_search(path, profile_id, providers, sceneName, title, media_type): logging.debug(f"BAZARR Skipping {s}, because it doesn't match our series/episode") except TypeError: logging.debug("BAZARR Ignoring invalid subtitles") - finally: - continue + continue initial_hi = None initial_hi_match = False From e83f37d42ec3a8141ca6b5ca487d0ab200e59cb5 Mon Sep 17 00:00:00 2001 From: Vitiko Date: Thu, 4 May 2023 19:15:35 -0400 Subject: [PATCH 02/57] SuperSubtitles provider: fix hungarian subtitles downloads --- libs/subliminal_patch/providers/utils.py | 45 ++++++++++++------- tests/subliminal_patch/test_supersubtitles.py | 24 ++++++++++ 2 files changed, 52 insertions(+), 17 deletions(-) diff --git a/libs/subliminal_patch/providers/utils.py b/libs/subliminal_patch/providers/utils.py index ea8e411e6..f06f75e34 100644 --- a/libs/subliminal_patch/providers/utils.py +++ b/libs/subliminal_patch/providers/utils.py @@ -121,6 +121,33 @@ def is_episode(content): return "episode" in guessit(content, {"type": "episode"}) +_ENCS = ("utf-8", "ascii", "iso-8859-1", "iso-8859-2", "iso-8859-5", "cp1252") + + +def _zip_from_subtitle_file(content): + with tempfile.NamedTemporaryFile(prefix="spsub", suffix=".srt") as tmp_f: + tmp_f.write(content) + sub = None + for enc in _ENCS: + try: + logger.debug("Trying %s encoding", enc) + sub = pysubs2.load(tmp_f.name, encoding=enc) + except Exception as error: + logger.debug("%s: %s", type(error).__name__, error) + continue + else: + break + + if sub is not None: + logger.debug("Identified subtitle file: %s", sub) + zip_obj = zipfile.ZipFile(io.BytesIO(), mode="x") + zip_obj.write(tmp_f.name, os.path.basename(tmp_f.name)) + return zip_obj + + logger.debug("Couldn't load subtitle file") + return None + + def get_archive_from_bytes(content: bytes): """Get RarFile/ZipFile object from bytes. A ZipFile instance will be returned if a subtitle-like stream is found. Return None if something else is found.""" @@ -134,23 +161,7 @@ def get_archive_from_bytes(content: bytes): return zipfile.ZipFile(archive_stream) logger.debug("No compression format found. Trying with subtitle-like files") - - # If the file is a subtitle-like file - with tempfile.NamedTemporaryFile(prefix="spsub", suffix=".srt") as tmp_f: - try: - tmp_f.write(content) - sub = pysubs2.load(tmp_f.name) - except Exception as error: - logger.debug("Couldn't load file: '%s'", error) - else: - if sub is not None: - logger.debug("Identified subtitle file: %s", sub) - zip_obj = zipfile.ZipFile(io.BytesIO(), mode="x") - zip_obj.write(tmp_f.name, os.path.basename(tmp_f.name)) - return zip_obj - - logger.debug("Nothing found") - return None + return _zip_from_subtitle_file(content) def update_matches( diff --git a/tests/subliminal_patch/test_supersubtitles.py b/tests/subliminal_patch/test_supersubtitles.py index d02c017fe..ce483caad 100644 --- a/tests/subliminal_patch/test_supersubtitles.py +++ b/tests/subliminal_patch/test_supersubtitles.py @@ -107,6 +107,30 @@ def test_download_movie_subtitle(movies): assert subtitle.is_valid() +def test_download_movie_subtitle_hungarian(movies): + movie = movies["dune"] + + subtitle = SuperSubtitlesSubtitle( + Language.fromalpha2("hu"), + "https://www.feliratok.eu//index.php?action=letolt&felirat=1681841063", + 1634579718, + "Foo", + 0, + 0, + "", + ["Foo"], + "", + "", + "", + asked_for_episode=None, + ) + assert subtitle.get_matches(movie) + + with SuperSubtitlesProvider() as provider: + provider.download_subtitle(subtitle) + assert subtitle.is_valid() + + def test_subtitle_reprs(movies): subtitle = SuperSubtitlesSubtitle( Language.fromalpha2("en"), From 0907269377401b64a52589a54431e4ab75a2587a Mon Sep 17 00:00:00 2001 From: Kai Yang Date: Sun, 7 May 2023 20:16:20 +0800 Subject: [PATCH 03/57] Fixed zimuku.org parsing error --- libs/subliminal_patch/providers/zimuku.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/subliminal_patch/providers/zimuku.py b/libs/subliminal_patch/providers/zimuku.py index 1940f0f1c..99bfb3d6c 100644 --- a/libs/subliminal_patch/providers/zimuku.py +++ b/libs/subliminal_patch/providers/zimuku.py @@ -143,7 +143,7 @@ class ZimukuProvider(Provider): self.session.cookies.set("srcurl", string_to_hex(r.url)) if tr: verify_resp = self.session.get( - self.server_url + tr[0] + string_to_hex(self.code), allow_redirects=False) + urljoin(self.server_url, tr[0] + string_to_hex(self.code)), allow_redirects=False) if verify_resp.status_code == 302 \ and self.session.cookies.get("security_session_verify") is not None: pass @@ -164,7 +164,7 @@ class ZimukuProvider(Provider): bs_obj = ParserBeautifulSoup( r.content.decode("utf-8", "ignore"), ["html.parser"] ) - subs_body = bs_obj.find("div", class_="subs box clearfix").find("tbody") + subs_body = bs_obj.find("tbody") subs = [] for sub in subs_body.find_all("tr"): a = sub.find("a") @@ -208,7 +208,7 @@ class ZimukuProvider(Provider): logger.debug("Searching subtitles %r", params) subtitles = [] - search_link = self.server_url + text_type(self.search_url).format(params) + search_link = urljoin(self.server_url, text_type(self.search_url).format(params)) r = self.yunsuo_bypass(search_link, timeout=30) r.raise_for_status() @@ -254,7 +254,7 @@ class ZimukuProvider(Provider): season_cn2 = num_to_cn(str(season)) if season_cn1 != season_cn2: continue - episode_link = self.server_url + title_a.attrs["href"] + episode_link = urljoin(self.server_url, title_a.attrs["href"]) new_subs = self._parse_episode_page(episode_link, subs_year) subtitles += new_subs From a6ecbb43154d6f57bcab4ea3be17467b487eea3f Mon Sep 17 00:00:00 2001 From: morpheus65535 Date: Tue, 9 May 2023 12:59:48 -0400 Subject: [PATCH 04/57] Fixed yify provider MissingSchema exception on download. #2139 --- libs/subliminal_patch/providers/yifysubtitles.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/libs/subliminal_patch/providers/yifysubtitles.py b/libs/subliminal_patch/providers/yifysubtitles.py index 37715f314..35c1317b1 100644 --- a/libs/subliminal_patch/providers/yifysubtitles.py +++ b/libs/subliminal_patch/providers/yifysubtitles.py @@ -18,6 +18,11 @@ from subliminal_patch.subtitle import Subtitle, guess_matches from subzero.language import Language from .utils import FIRST_THOUSAND_OR_SO_USER_AGENTS as AGENT_LIST +try: + from urlparse import urljoin +except ImportError: + from urllib.parse import urljoin + logger = logging.getLogger(__name__) @@ -137,7 +142,7 @@ class YifySubtitlesProvider(Provider): subtitles = [] logger.info('Searching subtitle %r', imdb_id) - response = self.session.get(self.server_url + '/movie-imdb/' + imdb_id, + response = self.session.get(urljoin(self.server_url, f'/movie-imdb/{imdb_id}'), allow_redirects=False, timeout=10, headers={'Referer': self.server_url}) @@ -171,7 +176,7 @@ class YifySubtitlesProvider(Provider): cache_key = sha1(subtitle.page_link.encode("utf-8")).digest() request = region.get(cache_key) if request is NO_VALUE: - request = self.session.get(subtitle.page_link, headers={ + request = self.session.get(urljoin(self.server_url, subtitle.page_link), headers={ 'Referer': subtitle.page_link }) request.raise_for_status() @@ -182,9 +187,7 @@ class YifySubtitlesProvider(Provider): soup = BeautifulSoup(request.content, 'lxml') download_button = soup.find('a', {'class': 'download-subtitle'}) if download_button: - download_link = self.server_url + download_button['href'] - - request = self.session.get(download_link, headers={ + request = self.session.get(urljoin(self.server_url, download_button['href']), headers={ 'Referer': subtitle.page_link }) request.raise_for_status() From 5f9418b1f3eae5b1acb5f8c4ecbd835726c6f63f Mon Sep 17 00:00:00 2001 From: Vitiko Date: Tue, 9 May 2023 17:08:22 -0400 Subject: [PATCH 05/57] Subdivx Provider: improve series matching --- libs/subliminal_patch/providers/subdivx.py | 11 +++++++++++ tests/subliminal_patch/test_subdivx.py | 13 +++++++++++++ 2 files changed, 24 insertions(+) diff --git a/libs/subliminal_patch/providers/subdivx.py b/libs/subliminal_patch/providers/subdivx.py index b20523cbf..6bdc58eb0 100644 --- a/libs/subliminal_patch/providers/subdivx.py +++ b/libs/subliminal_patch/providers/subdivx.py @@ -136,6 +136,9 @@ class SubdivxSubtitlesProvider(Provider): if len(subtitles) <= 5: subtitles += self._handle_multi_page_search(video.series, video, 1) + # Try with episode title as last resort + if not subtitles and video.title != video.series: + subtitles += self._handle_multi_page_search(video.title, video, 1) else: for query in (video.title, f"{video.title} ({video.year})"): subtitles += self._handle_multi_page_search(query, video) @@ -309,6 +312,14 @@ def _check_episode(video, title): logger.debug("Series year doesn't match: %s", title) return False + # Include matches where the episode title is present + if ( + video.series.lower() in title.lower() + and (video.title or "").lower() in title.lower() + ): + logger.debug("Episode title found in title: %s ~ %s", video.title, title) + return True + if season_num is None: logger.debug("Not a season/episode: %s", title) return False diff --git a/tests/subliminal_patch/test_subdivx.py b/tests/subliminal_patch/test_subdivx.py index 96342075c..33f938766 100644 --- a/tests/subliminal_patch/test_subdivx.py +++ b/tests/subliminal_patch/test_subdivx.py @@ -74,6 +74,19 @@ def test_list_subtitles_episode_with_title_only_fallback(episodes): subtitles = provider.list_subtitles(item, {Language("spa", "MX")}) assert len(subtitles) > 2 +def test_list_subtitles_episode_with_episode_title_fallback(episodes): + item = list(episodes.values())[0] + item.series = "30 for 30" + item.title = "The Two Escobars" + item.season = 1 + item.episode = 16 + + with SubdivxSubtitlesProvider() as provider: + sub = provider.list_subtitles(item, {Language("spa", "MX")})[0] + assert sub.get_matches(item) + provider.download_subtitle(sub) + assert sub.is_valid() + def test_download_subtitle(movies): subtitle = SubdivxSubtitle( From 2b5cd2b72e19469b545e4d35e2564d5219a36e7c Mon Sep 17 00:00:00 2001 From: morpheus65535 Date: Tue, 9 May 2023 21:25:54 -0400 Subject: [PATCH 06/57] Implemented gzip compression of http requests made to Bazarr --- bazarr/app/app.py | 3 + libs/flask_compress/__init__.py | 9 + libs/flask_compress/flask_compress.py | 239 ++++++++++++++++++++++++++ libs/version.txt | 1 + 4 files changed, 252 insertions(+) create mode 100644 libs/flask_compress/__init__.py create mode 100644 libs/flask_compress/flask_compress.py diff --git a/bazarr/app/app.py b/bazarr/app/app.py index 8445df0e0..2fa01231c 100644 --- a/bazarr/app/app.py +++ b/bazarr/app/app.py @@ -2,6 +2,7 @@ from flask import Flask, redirect +from flask_compress import Compress from flask_cors import CORS from flask_socketio import SocketIO @@ -15,6 +16,8 @@ socketio = SocketIO() def create_app(): # Flask Setup app = Flask(__name__) + app.config['COMPRESS_ALGORITHM'] = 'gzip' + Compress(app) app.wsgi_app = ReverseProxied(app.wsgi_app) app.config["SECRET_KEY"] = settings.general.flask_secret_key diff --git a/libs/flask_compress/__init__.py b/libs/flask_compress/__init__.py new file mode 100644 index 000000000..0a643ec4e --- /dev/null +++ b/libs/flask_compress/__init__.py @@ -0,0 +1,9 @@ +from .flask_compress import Compress + +# _version.py is generated by setuptools_scm when building the package, it is not versioned. +# If missing, this means that the imported code was most likely the git repository, that was +# installed without the "editable" mode. +try: + from ._version import __version__ +except ImportError: + __version__ = "0" diff --git a/libs/flask_compress/flask_compress.py b/libs/flask_compress/flask_compress.py new file mode 100644 index 000000000..a990a7fb2 --- /dev/null +++ b/libs/flask_compress/flask_compress.py @@ -0,0 +1,239 @@ + +# Authors: William Fagan +# Copyright (c) 2013-2017 William Fagan +# License: The MIT License (MIT) + +import sys +import functools +from gzip import GzipFile +import zlib +from io import BytesIO + +from collections import defaultdict + +from flask import request, after_this_request, current_app + + +if sys.version_info[:2] == (2, 6): + class GzipFile(GzipFile): + """ Backport of context manager support for python 2.6""" + def __enter__(self): + if self.fileobj is None: + raise ValueError("I/O operation on closed GzipFile object") + return self + + def __exit__(self, *args): + self.close() + + +class DictCache(object): + + def __init__(self): + self.data = {} + + def get(self, key): + return self.data.get(key) + + def set(self, key, value): + self.data[key] = value + + +class Compress(object): + """ + The Compress object allows your application to use Flask-Compress. + + When initialising a Compress object you may optionally provide your + :class:`flask.Flask` application object if it is ready. Otherwise, + you may provide it later by using the :meth:`init_app` method. + + :param app: optional :class:`flask.Flask` application object + :type app: :class:`flask.Flask` or None + """ + def __init__(self, app=None): + """ + An alternative way to pass your :class:`flask.Flask` application + object to Flask-Compress. :meth:`init_app` also takes care of some + default `settings`_. + + :param app: the :class:`flask.Flask` application object. + """ + self.app = app + if app is not None: + self.init_app(app) + + def init_app(self, app): + defaults = [ + ('COMPRESS_MIMETYPES', ['text/html', 'text/css', 'text/xml', + 'application/json', + 'application/javascript']), + ('COMPRESS_LEVEL', 6), + ('COMPRESS_BR_LEVEL', 4), + ('COMPRESS_BR_MODE', 0), + ('COMPRESS_BR_WINDOW', 22), + ('COMPRESS_BR_BLOCK', 0), + ('COMPRESS_DEFLATE_LEVEL', -1), + ('COMPRESS_MIN_SIZE', 500), + ('COMPRESS_CACHE_KEY', None), + ('COMPRESS_CACHE_BACKEND', None), + ('COMPRESS_REGISTER', True), + ('COMPRESS_STREAMS', True), + ('COMPRESS_ALGORITHM', ['br', 'gzip', 'deflate']), + ] + + for k, v in defaults: + app.config.setdefault(k, v) + + backend = app.config['COMPRESS_CACHE_BACKEND'] + self.cache = backend() if backend else None + self.cache_key = app.config['COMPRESS_CACHE_KEY'] + + algo = app.config['COMPRESS_ALGORITHM'] + if isinstance(algo, str): + self.enabled_algorithms = [i.strip() for i in algo.split(',')] + else: + self.enabled_algorithms = list(algo) + + if (app.config['COMPRESS_REGISTER'] and + app.config['COMPRESS_MIMETYPES']): + app.after_request(self.after_request) + + def _choose_compress_algorithm(self, accept_encoding_header): + """ + Determine which compression algorithm we're going to use based on the + client request. The `Accept-Encoding` header may list one or more desired + algorithms, together with a "quality factor" for each one (higher quality + means the client prefers that algorithm more). + + :param accept_encoding_header: Content of the `Accept-Encoding` header + :return: name of a compression algorithm (`gzip`, `deflate`, `br`) or `None` if + the client and server don't agree on any. + """ + # A flag denoting that client requested using any (`*`) algorithm, + # in case a specific one is not supported by the server + fallback_to_any = False + + # Map quality factors to requested algorithm names. + algos_by_quality = defaultdict(set) + + # Set of supported algorithms + server_algos_set = set(self.enabled_algorithms) + + for part in accept_encoding_header.lower().split(','): + part = part.strip() + if ';q=' in part: + # If the client associated a quality factor with an algorithm, + # try to parse it. We could do the matching using a regex, but + # the format is so simple that it would be overkill. + algo = part.split(';')[0].strip() + try: + quality = float(part.split('=')[1].strip()) + except ValueError: + quality = 1.0 + else: + # Otherwise, use the default quality + algo = part + quality = 1.0 + + if algo == '*': + if quality > 0: + fallback_to_any = True + elif algo == 'identity': # identity means 'no compression asked' + algos_by_quality[quality].add(None) + elif algo in server_algos_set: + algos_by_quality[quality].add(algo) + + # Choose the algorithm with the highest quality factor that the server supports. + # + # If there are multiple equally good options, choose the first supported algorithm + # from server configuration. + # + # If the server doesn't support any algorithm that the client requested but + # there's a special wildcard algorithm request (`*`), choose the first supported + # algorithm. + for _, viable_algos in sorted(algos_by_quality.items(), reverse=True): + if len(viable_algos) == 1: + return viable_algos.pop() + elif len(viable_algos) > 1: + for server_algo in self.enabled_algorithms: + if server_algo in viable_algos: + return server_algo + + if fallback_to_any: + return self.enabled_algorithms[0] + return None + + def after_request(self, response): + app = self.app or current_app + + vary = response.headers.get('Vary') + if not vary: + response.headers['Vary'] = 'Accept-Encoding' + elif 'accept-encoding' not in vary.lower(): + response.headers['Vary'] = '{}, Accept-Encoding'.format(vary) + + accept_encoding = request.headers.get('Accept-Encoding', '') + chosen_algorithm = self._choose_compress_algorithm(accept_encoding) + + if (chosen_algorithm is None or + response.mimetype not in app.config["COMPRESS_MIMETYPES"] or + response.status_code < 200 or + response.status_code >= 300 or + (response.is_streamed and app.config["COMPRESS_STREAMS"] is False)or + "Content-Encoding" in response.headers or + (response.content_length is not None and + response.content_length < app.config["COMPRESS_MIN_SIZE"])): + return response + + response.direct_passthrough = False + + if self.cache is not None: + key = self.cache_key(request) + compressed_content = self.cache.get(key) + if compressed_content is None: + compressed_content = self.compress(app, response, chosen_algorithm) + self.cache.set(key, compressed_content) + else: + compressed_content = self.compress(app, response, chosen_algorithm) + + response.set_data(compressed_content) + + response.headers['Content-Encoding'] = chosen_algorithm + response.headers['Content-Length'] = response.content_length + + # "123456789" => "123456789:gzip" - A strong ETag validator + # W/"123456789" => W/"123456789:gzip" - A weak ETag validator + etag = response.headers.get('ETag') + if etag: + response.headers['ETag'] = '{0}:{1}"'.format(etag[:-1], chosen_algorithm) + + return response + + def compressed(self): + def decorator(f): + @functools.wraps(f) + def decorated_function(*args, **kwargs): + @after_this_request + def compressor(response): + return self.after_request(response) + return f(*args, **kwargs) + return decorated_function + return decorator + + def compress(self, app, response, algorithm): + if algorithm == 'gzip': + gzip_buffer = BytesIO() + with GzipFile(mode='wb', + compresslevel=app.config['COMPRESS_LEVEL'], + fileobj=gzip_buffer) as gzip_file: + gzip_file.write(response.get_data()) + return gzip_buffer.getvalue() + elif algorithm == 'deflate': + return zlib.compress(response.get_data(), + app.config['COMPRESS_DEFLATE_LEVEL']) + elif algorithm == 'br': + import brotli + return brotli.compress(response.get_data(), + mode=app.config['COMPRESS_BR_MODE'], + quality=app.config['COMPRESS_BR_LEVEL'], + lgwin=app.config['COMPRESS_BR_WINDOW'], + lgblock=app.config['COMPRESS_BR_BLOCK']) diff --git a/libs/version.txt b/libs/version.txt index 88552b535..1280cb4ab 100644 --- a/libs/version.txt +++ b/libs/version.txt @@ -9,6 +9,7 @@ deep-translator==1.9.1 dogpile.cache==1.1.8 fese==0.1.2 ffsubsync==0.4.20 +flask-compress==1.1.3 flask-cors==3.0.10 flask-restx==1.0.3 Flask-SocketIO==5.3.1 From c06dd620b759d6dd37125caf19a22711fc426f37 Mon Sep 17 00:00:00 2001 From: Vitiko Date: Wed, 17 May 2023 01:45:29 -0400 Subject: [PATCH 07/57] Add mediainfo support for custom languages --- bazarr/languages/custom_lang.py | 13 + bazarr/utilities/video_analyzer.py | 12 +- tests/bazarr/test_utilities_video_analyzer.py | 229 ++++++++++++++++++ 3 files changed, 251 insertions(+), 3 deletions(-) create mode 100644 tests/bazarr/test_utilities_video_analyzer.py diff --git a/bazarr/languages/custom_lang.py b/bazarr/languages/custom_lang.py index 81beca99a..2b19d0a5f 100644 --- a/bazarr/languages/custom_lang.py +++ b/bazarr/languages/custom_lang.py @@ -4,6 +4,7 @@ import logging import os from subzero.language import Language +from babelfish.script import Script logger = logging.getLogger(__name__) @@ -18,6 +19,7 @@ class CustomLanguage: official_alpha3 = "por" name = "Brazilian Portuguese" iso = "BR" + _scripts = [] _possible_matches = ("pt-br", "pob", "pb", "brazilian", "brasil", "brazil") _extensions = (".pt-br", ".pob", ".pb") _extensions_forced = (".pt-br.forced", ".pob.forced", ".pb.forced") @@ -86,6 +88,15 @@ class CustomLanguage: return any(ext in name for ext in self._possible_matches) + def language_found(self, language: Language): + if str(language.country) == self.iso: + return True + + if language.script and language.script in self._scripts: + return True + + return False + class BrazilianPortuguese(CustomLanguage): # Same attributes as base class @@ -100,6 +111,7 @@ class ChineseTraditional(CustomLanguage): official_alpha3 = "zho" name = "Chinese Traditional" iso = "TW" + _scripts = (Script("Hant"),) _extensions = ( ".cht", ".tc", @@ -211,6 +223,7 @@ class LatinAmericanSpanish(CustomLanguage): official_alpha3 = "spa" name = "Latin American Spanish" iso = "MX" # Not fair, but ok + _scripts = (Script("419"),) _possible_matches = ( "es-la", "spa-la", diff --git a/bazarr/utilities/video_analyzer.py b/bazarr/utilities/video_analyzer.py index 2454c5149..89bc48ff0 100644 --- a/bazarr/utilities/video_analyzer.py +++ b/bazarr/utilities/video_analyzer.py @@ -16,13 +16,19 @@ def _handle_alpha3(detected_language: dict): alpha3 = detected_language["language"].alpha3 custom = CustomLanguage.from_value(alpha3, "official_alpha3") - if custom and custom.ffprobe_found(detected_language): + if not custom: + return alpha3 + + found = custom.language_found(detected_language["language"]) + if not found: + found = custom.ffprobe_found(detected_language) + + if found: logging.debug("Custom embedded language found: %s", custom.name) return custom.alpha3 return alpha3 - def embedded_subs_reader(file, file_size, episode_file_id=None, movie_file_id=None, use_cache=True): data = parse_video_metadata(file, file_size, episode_file_id, movie_file_id, use_cache=use_cache) und_default_language = alpha3_from_alpha2(settings.general.default_und_embedded_subtitles_lang) @@ -33,7 +39,7 @@ def embedded_subs_reader(file, file_size, episode_file_id=None, movie_file_id=No return subtitles_list cache_provider = None - if data["ffprobe"] and "subtitle" in data["ffprobe"]: + if "ffprobe" in data and data["ffprobe"] and "subtitle" in data["ffprobe"]: cache_provider = 'ffprobe' elif 'mediainfo' in data and data["mediainfo"] and "subtitle" in data["mediainfo"]: cache_provider = 'mediainfo' diff --git a/tests/bazarr/test_utilities_video_analyzer.py b/tests/bazarr/test_utilities_video_analyzer.py new file mode 100644 index 000000000..893415e13 --- /dev/null +++ b/tests/bazarr/test_utilities_video_analyzer.py @@ -0,0 +1,229 @@ +import logging + +import pytest + +from bazarr.utilities import video_analyzer + +logging.getLogger("knowit").setLevel(logging.WARNING) + +M_INFO = { + "creatingLibrary": { + "name": "MediaInfoLib", + "version": "23.03", + "url": "https://mediaarea.net/MediaInfo", + }, + "media": { + "@ref": "/mnt/media/Hocus.Pocus.1993.1080p.DSNP.WEB-DL.DDP5.1.H.264.DUAL-PD.mkv", + "track": [ + { + "@type": "General", + "UniqueID": "177986280948425821736023466260510750529", + "VideoCount": "1", + "AudioCount": "2", + "TextCount": "31", + "FileExtension": "mkv", + "Format": "Matroska", + "Format_Version": "4", + "FileSize": "6468058376", + "Duration": "5766.219", + "OverallBitRate_Mode": "VBR", + "OverallBitRate": "8973726", + "FrameRate": "23.976", + "FrameCount": "138251", + "StreamSize": "2607619", + "IsStreamable": "Yes", + "Encoded_Date": "2023-04-22 16:46:57 UTC", + "File_Modified_Date": "2023-05-17 01:49:55 UTC", + "File_Modified_Date_Local": "2023-05-16 21:49:55", + "Encoded_Application": "mkvmerge v75.0.0 ('Goliath') 64-bit", + "Encoded_Library": "libebml v1.4.4 + libmatroska v1.7.1", + }, + { + "@type": "Video", + "StreamOrder": "0", + "ID": "1", + "UniqueID": "9393509843335289949", + "Format": "AVC", + "Format_Profile": "High", + "Format_Level": "4", + "Format_Settings_CABAC": "Yes", + "Format_Settings_RefFrames": "4", + "CodecID": "V_MPEG4/ISO/AVC", + "Duration": "5766.219000000", + "BitRate_Mode": "VBR", + "BitRate": "8458733", + "BitRate_Maximum": "12749952", + "Width": "1920", + "Height": "1080", + "Stored_Height": "1088", + "Sampled_Width": "1920", + "Sampled_Height": "1080", + "PixelAspectRatio": "1.000", + "DisplayAspectRatio": "1.778", + "FrameRate_Mode": "CFR", + "FrameRate": "23.976", + "FrameCount": "138251", + "ColorSpace": "YUV", + "ChromaSubsampling": "4:2:0", + "BitDepth": "8", + "ScanType": "Progressive", + "Delay": "0.000", + "Delay_Source": "Container", + "StreamSize": "6096863666", + "Default": "Yes", + "Forced": "No", + "BufferSize": "17000000", + "colour_description_present": "Yes", + "colour_description_present_Source": "Container / Stream", + "colour_range": "Limited", + "colour_range_Source": "Stream", + "colour_primaries": "BT.709", + "colour_primaries_Source": "Container / Stream", + "transfer_characteristics": "BT.709", + "transfer_characteristics_Source": "Container / Stream", + "matrix_coefficients": "BT.709", + "matrix_coefficients_Source": "Container / Stream", + }, + { + "@type": "Text", + "@typeorder": "7", + "StreamOrder": "9", + "ID": "10", + "UniqueID": "2233390560797234737", + "Format": "UTF-8", + "CodecID": "S_TEXT/UTF8", + "Duration": "5480.360000000", + "BitRate": "45", + "FrameRate": "0.206", + "FrameCount": "1129", + "ElementCount": "1129", + "StreamSize": "31194", + "Language": "es-419", + "Default": "No", + "Forced": "No", + }, + { + "@type": "Text", + "@typeorder": "9", + "StreamOrder": "11", + "ID": "12", + "UniqueID": "1345374948683222936", + "Format": "UTF-8", + "CodecID": "S_TEXT/UTF8", + "Duration": "5561.600000000", + "BitRate": "46", + "FrameRate": "0.164", + "FrameCount": "914", + "ElementCount": "914", + "StreamSize": "32145", + "Language": "es-ES", + "Default": "No", + "Forced": "No", + }, + { + "@type": "Text", + "@typeorder": "11", + "StreamOrder": "13", + "ID": "14", + "UniqueID": "17039172451186467602", + "Format": "UTF-8", + "CodecID": "S_TEXT/UTF8", + "Duration": "4966.120000000", + "BitRate": "1", + "FrameRate": "0.007", + "FrameCount": "35", + "ElementCount": "35", + "StreamSize": "1011", + "Language": "fr-CA", + "Default": "No", + "Forced": "No", + }, + { + "@type": "Text", + "@typeorder": "24", + "StreamOrder": "26", + "ID": "27", + "UniqueID": "16221047442617815320", + "Format": "UTF-8", + "CodecID": "S_TEXT/UTF8", + "Duration": "4961.520000000", + "BitRate": "0", + "FrameRate": "0.002", + "FrameCount": "11", + "ElementCount": "11", + "StreamSize": "379", + "Language": "pt-BR", + "Default": "No", + "Forced": "No", + }, + { + "@type": "Text", + "@typeorder": "30", + "StreamOrder": "32", + "ID": "33", + "UniqueID": "4259582444071016270", + "Format": "UTF-8", + "CodecID": "S_TEXT/UTF8", + "Duration": "5507.508000000", + "BitRate": "50", + "FrameRate": "0.253", + "FrameCount": "1392", + "ElementCount": "1392", + "StreamSize": "34539", + "Language": "zh-Hans", + "Default": "No", + "Forced": "No", + }, + { + "@type": "Text", + "@typeorder": "31", + "StreamOrder": "33", + "ID": "34", + "UniqueID": "4890027048965677919", + "Format": "UTF-8", + "CodecID": "S_TEXT/UTF8", + "Duration": "5730.725000000", + "BitRate": "43", + "FrameRate": "0.207", + "FrameCount": "1186", + "ElementCount": "1186", + "StreamSize": "31154", + "Language": "zh-Hant", + "Default": "No", + "Forced": "No", + }, + ], + }, +} + + +@pytest.fixture +def video_file(): + return "tests/subliminal_patch/data/file_1.mkv" + + +@pytest.fixture +def mediainfo_data(mocker, video_file): + mocker.patch( + "knowit.providers.mediainfo.MediaInfoCTypesExecutor._execute", + return_value=M_INFO, + ) + data = video_analyzer.know( + video_path=video_file, + context={"provider": "mediainfo"}, + ) + yield data + + +def test_embedded_subs_reader(mocker, mediainfo_data, video_file): + mocker.patch( + "bazarr.utilities.video_analyzer.parse_video_metadata", + return_value={"mediainfo": mediainfo_data}, + ) + mocker.patch( + "bazarr.utilities.video_analyzer.alpha3_from_alpha2", return_value=None + ) + result = video_analyzer.embedded_subs_reader(1e6, video_file) + assert ["spl", False, False, "SubRip"] in result + assert ["pob", False, False, "SubRip"] in result + assert ["zht", False, False, "SubRip"] in result From 015beaf769f7d10077ad590a6ed0363c4407f129 Mon Sep 17 00:00:00 2001 From: morpheus65535 Date: Wed, 17 May 2023 06:41:37 -0400 Subject: [PATCH 08/57] Emergency fix for custom languages issue --- bazarr/languages/custom_lang.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bazarr/languages/custom_lang.py b/bazarr/languages/custom_lang.py index 2b19d0a5f..654647497 100644 --- a/bazarr/languages/custom_lang.py +++ b/bazarr/languages/custom_lang.py @@ -223,7 +223,7 @@ class LatinAmericanSpanish(CustomLanguage): official_alpha3 = "spa" name = "Latin American Spanish" iso = "MX" # Not fair, but ok - _scripts = (Script("419"),) + #_scripts = (Script("419"),) _possible_matches = ( "es-la", "spa-la", From bdf4ee85af7bd9f194da82420f66649e964650a1 Mon Sep 17 00:00:00 2001 From: morpheus65535 Date: Wed, 17 May 2023 06:42:27 -0400 Subject: [PATCH 09/57] Fixed AI and machine translated subtitles being incorrectly returned by Opensubtitles.com --- libs/subliminal_patch/providers/opensubtitlescom.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/libs/subliminal_patch/providers/opensubtitlescom.py b/libs/subliminal_patch/providers/opensubtitlescom.py index a2b66fbcc..97c4f4ffe 100644 --- a/libs/subliminal_patch/providers/opensubtitlescom.py +++ b/libs/subliminal_patch/providers/opensubtitlescom.py @@ -56,6 +56,7 @@ class OpenSubtitlesComSubtitle(Subtitle): def __init__(self, language, forced, hearing_impaired, page_link, file_id, releases, uploader, title, year, hash_matched, file_hash=None, season=None, episode=None, imdb_match=False): + super().__init__(language, hearing_impaired, page_link) language = Language.rebuild(language, hi=hearing_impaired, forced=forced) self.title = title @@ -334,6 +335,16 @@ class OpenSubtitlesComProvider(ProviderRetryMixin, Provider): if len(result['data']): for item in result['data']: + # ignore AI translated subtitles + if 'ai_translated' in item['attributes'] and item['attributes']['ai_translated']: + logging.debug("Skipping AI translated subtitles") + continue + + # ignore machine translated subtitles + if 'machine_translated' in item['attributes'] and item['attributes']['machine_translated']: + logging.debug("Skipping machine translated subtitles") + continue + if 'season_number' in item['attributes']['feature_details']: season_number = item['attributes']['feature_details']['season_number'] else: From ea7b9487ab145a39d83188e92bf8cc9e96064b80 Mon Sep 17 00:00:00 2001 From: Vitiko Date: Wed, 17 May 2023 22:50:15 -0400 Subject: [PATCH 10/57] no log: Add tests header warning --- dev-requirements.txt | 1 + tests/conftest.py | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/dev-requirements.txt b/dev-requirements.txt index 550ab0d0e..5241aefd7 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -7,3 +7,4 @@ pytest-cov pytest-vcr pytest-mock requests-mock +setuptools diff --git a/tests/conftest.py b/tests/conftest.py index 311264122..46d8fc4a0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,32 @@ # -*- coding: utf-8 -*- import os +import pkgutil import sys +import pkg_resources + sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../libs/")) sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../bazarr/")) + + +def pytest_report_header(config): + conflicting_packages = _get_conflicting("libs") + if conflicting_packages: + return f"Conflicting packages detected:\n{conflicting_packages}" + + +def _get_conflicting(path): + libs_packages = [] + for _, package_name, _ in pkgutil.iter_modules([path]): + libs_packages.append(package_name) + + installed_packages = pkg_resources.working_set + package_names = [package.key for package in installed_packages] + unique_package_names = set(package_names) + + conflicting = [] + for installed in unique_package_names: + if installed in libs_packages: + conflicting.append(installed) + + return conflicting From 13f965d7255d99a23633ee1f04dc0adfb3b8dd4d Mon Sep 17 00:00:00 2001 From: Vitiko Date: Thu, 18 May 2023 18:55:33 -0400 Subject: [PATCH 11/57] Use literals instead of scripts for custom languages --- bazarr/languages/custom_lang.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bazarr/languages/custom_lang.py b/bazarr/languages/custom_lang.py index 654647497..9ff40d5d2 100644 --- a/bazarr/languages/custom_lang.py +++ b/bazarr/languages/custom_lang.py @@ -4,7 +4,6 @@ import logging import os from subzero.language import Language -from babelfish.script import Script logger = logging.getLogger(__name__) @@ -92,7 +91,7 @@ class CustomLanguage: if str(language.country) == self.iso: return True - if language.script and language.script in self._scripts: + if language.script and str(language.script) in self._scripts: return True return False @@ -111,7 +110,9 @@ class ChineseTraditional(CustomLanguage): official_alpha3 = "zho" name = "Chinese Traditional" iso = "TW" - _scripts = (Script("Hant"),) + # _scripts = (Script("Hant"),) + # We'll use literals for now + _scripts = ("Hant",) _extensions = ( ".cht", ".tc", @@ -223,7 +224,7 @@ class LatinAmericanSpanish(CustomLanguage): official_alpha3 = "spa" name = "Latin American Spanish" iso = "MX" # Not fair, but ok - #_scripts = (Script("419"),) + _scripts = ("419",) _possible_matches = ( "es-la", "spa-la", From 585c70c39d8ae1e3b9b82fae8800417eba5f6444 Mon Sep 17 00:00:00 2001 From: Vitiko Date: Fri, 19 May 2023 02:03:17 -0400 Subject: [PATCH 12/57] Add support for custom languages in audio tracks --- bazarr/utilities/video_analyzer.py | 5 +- tests/bazarr/test_utilities_video_analyzer.py | 72 ++++++++++++------- 2 files changed, 48 insertions(+), 29 deletions(-) diff --git a/bazarr/utilities/video_analyzer.py b/bazarr/utilities/video_analyzer.py index 89bc48ff0..2b221fde1 100644 --- a/bazarr/utilities/video_analyzer.py +++ b/bazarr/utilities/video_analyzer.py @@ -81,7 +81,7 @@ def embedded_audio_reader(file, file_size, episode_file_id=None, movie_file_id=N return audio_list cache_provider = None - if data["ffprobe"] and "audio" in data["ffprobe"]: + if "ffprobe" in data and data["ffprobe"] and "audio" in data["ffprobe"]: cache_provider = 'ffprobe' elif 'mediainfo' in data and data["mediainfo"] and "audio" in data["mediainfo"]: cache_provider = 'mediainfo' @@ -92,7 +92,8 @@ def embedded_audio_reader(file, file_size, episode_file_id=None, movie_file_id=N audio_list.append(None) continue - language = language_from_alpha3(detected_language["language"].alpha3) + alpha3 = _handle_alpha3(detected_language) + language = language_from_alpha3(alpha3) if language not in audio_list: audio_list.append(language) diff --git a/tests/bazarr/test_utilities_video_analyzer.py b/tests/bazarr/test_utilities_video_analyzer.py index 893415e13..9a6eb21f0 100644 --- a/tests/bazarr/test_utilities_video_analyzer.py +++ b/tests/bazarr/test_utilities_video_analyzer.py @@ -55,34 +55,40 @@ M_INFO = { "BitRate_Maximum": "12749952", "Width": "1920", "Height": "1080", - "Stored_Height": "1088", - "Sampled_Width": "1920", - "Sampled_Height": "1080", - "PixelAspectRatio": "1.000", - "DisplayAspectRatio": "1.778", - "FrameRate_Mode": "CFR", - "FrameRate": "23.976", - "FrameCount": "138251", - "ColorSpace": "YUV", - "ChromaSubsampling": "4:2:0", - "BitDepth": "8", - "ScanType": "Progressive", - "Delay": "0.000", - "Delay_Source": "Container", - "StreamSize": "6096863666", - "Default": "Yes", + }, + { + "@type": "Audio", + "@typeorder": "1", + "StreamOrder": "1", + "ID": "2", + "UniqueID": "12329215851643269509", + "Format": "E-AC-3", + "Format_Commercial_IfAny": "Dolby Digital Plus", + "Format_Settings_Endianness": "Big", + "CodecID": "A_EAC3", + "Duration": "5766.112000000", + "BitRate_Mode": "CBR", + "BitRate": "256000", + "Language": "pt-BR", + "Default": "No", + "Forced": "No", + }, + { + "@type": "Audio", + "@typeorder": "2", + "StreamOrder": "2", + "ID": "3", + "UniqueID": "1232921585164326950923", + "Format": "E-AC-3", + "Format_Commercial_IfAny": "Dolby Digital Plus", + "Format_Settings_Endianness": "Big", + "CodecID": "A_EAC3", + "Duration": "5766.112000000", + "BitRate_Mode": "CBR", + "BitRate": "256000", + "Language": "pt", + "Default": "No", "Forced": "No", - "BufferSize": "17000000", - "colour_description_present": "Yes", - "colour_description_present_Source": "Container / Stream", - "colour_range": "Limited", - "colour_range_Source": "Stream", - "colour_primaries": "BT.709", - "colour_primaries_Source": "Container / Stream", - "transfer_characteristics": "BT.709", - "transfer_characteristics_Source": "Container / Stream", - "matrix_coefficients": "BT.709", - "matrix_coefficients_Source": "Container / Stream", }, { "@type": "Text", @@ -227,3 +233,15 @@ def test_embedded_subs_reader(mocker, mediainfo_data, video_file): assert ["spl", False, False, "SubRip"] in result assert ["pob", False, False, "SubRip"] in result assert ["zht", False, False, "SubRip"] in result + + +def test_embedded_audio_reader(mocker, mediainfo_data, video_file): + mocker.patch( + "bazarr.utilities.video_analyzer.parse_video_metadata", + return_value={"mediainfo": mediainfo_data}, + ) + mocker.patch( + "bazarr.utilities.video_analyzer.language_from_alpha3", lambda alpha3: alpha3 + ) + result = video_analyzer.embedded_audio_reader(1e6, video_file) + assert {"pob", "por"} == set(result) From 9c92dd493bea1c8848fd3d7d289a01d339f73427 Mon Sep 17 00:00:00 2001 From: Alex Yancey Date: Sat, 20 May 2023 18:02:48 -0700 Subject: [PATCH 13/57] Specify ffmpeg path in case it's not in PATH envvar (#2147) --- bazarr/app/get_providers.py | 3 ++- libs/subliminal_patch/providers/whisperai.py | 14 +++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/bazarr/app/get_providers.py b/bazarr/app/get_providers.py index 2ea396a1d..8b51a4e2d 100644 --- a/bazarr/app/get_providers.py +++ b/bazarr/app/get_providers.py @@ -254,7 +254,8 @@ def get_providers_auth(): }, 'whisperai': { 'endpoint': settings.whisperai.endpoint, - 'timeout': settings.whisperai.timeout + 'timeout': settings.whisperai.timeout, + 'ffmpeg_path': _FFMPEG_BINARY, } } diff --git a/libs/subliminal_patch/providers/whisperai.py b/libs/subliminal_patch/providers/whisperai.py index 0c816bb46..8aef88bc0 100644 --- a/libs/subliminal_patch/providers/whisperai.py +++ b/libs/subliminal_patch/providers/whisperai.py @@ -124,7 +124,7 @@ logger = logging.getLogger(__name__) @functools.lru_cache(2) -def encode_audio_stream(path, audio_stream_language=None): +def encode_audio_stream(path, ffmpeg_path, audio_stream_language=None): logger.debug("Encoding audio stream to WAV with ffmpeg") try: @@ -135,7 +135,7 @@ def encode_audio_stream(path, audio_stream_language=None): inp = inp[f'a:m:language:{audio_stream_language}'] out, _ = inp.output("-", format="s16le", acodec="pcm_s16le", ac=1, ar=16000) \ - .run(cmd=["ffmpeg", "-nostdin"], capture_stdout=True, capture_stderr=True) + .run(cmd=[ffmpeg_path, "-nostdin"], capture_stdout=True, capture_stderr=True) except ffmpeg.Error as e: raise RuntimeError(f"Failed to load audio: {e.stderr.decode()}") from e @@ -203,16 +203,20 @@ class WhisperAIProvider(Provider): video_types = (Episode, Movie) - def __init__(self, endpoint=None, timeout=None): + def __init__(self, endpoint=None, timeout=None, ffmpeg_path=None): if not endpoint: raise ConfigurationError('Whisper Web Service Endpoint must be provided') if not timeout: raise ConfigurationError('Whisper Web Service Timeout must be provided') + if not ffmpeg_path: + raise ConfigurationError("ffmpeg path must be provided") + self.endpoint = endpoint self.timeout = int(timeout) self.session = None + self.ffmpeg_path = ffmpeg_path def initialize(self): self.session = Session() @@ -224,7 +228,7 @@ class WhisperAIProvider(Provider): @functools.lru_cache(2048) def detect_language(self, path) -> Language: - out = encode_audio_stream(path) + out = encode_audio_stream(path, self.ffmpeg_path) r = self.session.post(f"{self.endpoint}/detect-language", params={'encode': 'false'}, @@ -281,7 +285,7 @@ class WhisperAIProvider(Provider): # Invoke Whisper through the API. This may take a long time depending on the file. # TODO: This loads the entire file into memory, find a good way to stream the file in chunks - out = encode_audio_stream(subtitle.video.original_path, subtitle.force_audio_stream) + out = encode_audio_stream(subtitle.video.original_path, self.ffmpeg_path, subtitle.force_audio_stream) r = self.session.post(f"{self.endpoint}/asr", params={'task': subtitle.task, 'language': whisper_get_language_reverse(subtitle.audio_language), 'output': 'srt', 'encode': 'false'}, From 80414c08abfac7d214fd233397b1b83f48faf006 Mon Sep 17 00:00:00 2001 From: morpheus65535 Date: Sun, 21 May 2023 12:02:09 -0400 Subject: [PATCH 14/57] no log: fixed static folder path in dev environment --- bazarr/app/ui.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/bazarr/app/ui.py b/bazarr/app/ui.py index c5bd41736..7d8162430 100644 --- a/bazarr/app/ui.py +++ b/bazarr/app/ui.py @@ -24,9 +24,15 @@ ui_bp = Blueprint('ui', __name__, 'build', 'assets'), static_url_path='/assets') +if os.path.exists(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))),'frontend', 'build', + 'images')): + static_directory = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'frontend', 'build', + 'images') +else: + static_directory = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'frontend', 'public', + 'images') static_bp = Blueprint('images', __name__, - static_folder=os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), - 'frontend', 'build', 'images'), static_url_path='/images') + static_folder=static_directory, static_url_path='/images') ui_bp.register_blueprint(static_bp) From 205bceb38db5eb35c7123177b3bc45574e2c1242 Mon Sep 17 00:00:00 2001 From: morpheus65535 Date: Sun, 21 May 2023 12:08:30 -0400 Subject: [PATCH 15/57] no log: make it cleaner --- bazarr/app/ui.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bazarr/app/ui.py b/bazarr/app/ui.py index 7d8162430..ae8bfba96 100644 --- a/bazarr/app/ui.py +++ b/bazarr/app/ui.py @@ -24,15 +24,14 @@ ui_bp = Blueprint('ui', __name__, 'build', 'assets'), static_url_path='/assets') -if os.path.exists(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))),'frontend', 'build', +if os.path.exists(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'frontend', 'build', 'images')): static_directory = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'frontend', 'build', 'images') else: static_directory = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'frontend', 'public', 'images') -static_bp = Blueprint('images', __name__, - static_folder=static_directory, static_url_path='/images') +static_bp = Blueprint('images', __name__, static_folder=static_directory, static_url_path='/images') ui_bp.register_blueprint(static_bp) From 6f92e35ba0576136306ecf49e46c9c4ee020278c Mon Sep 17 00:00:00 2001 From: morpheus65535 Date: Mon, 22 May 2023 18:32:21 -0400 Subject: [PATCH 16/57] no log: fix type in version.txt --- libs/version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/version.txt b/libs/version.txt index 1280cb4ab..ad74cae19 100644 --- a/libs/version.txt +++ b/libs/version.txt @@ -9,7 +9,7 @@ deep-translator==1.9.1 dogpile.cache==1.1.8 fese==0.1.2 ffsubsync==0.4.20 -flask-compress==1.1.3 +flask-compress==1.13 flask-cors==3.0.10 flask-restx==1.0.3 Flask-SocketIO==5.3.1 From baf7a7300d7d3556642dfab48b37c2a64743afeb Mon Sep 17 00:00:00 2001 From: morpheus65535 Date: Wed, 24 May 2023 19:14:23 -0400 Subject: [PATCH 17/57] Fixed type in opensubtitles.com provider that could cause AuthenticationError for new users. #2152 --- libs/subliminal_patch/providers/opensubtitlescom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/subliminal_patch/providers/opensubtitlescom.py b/libs/subliminal_patch/providers/opensubtitlescom.py index 97c4f4ffe..7f2a9a1dc 100644 --- a/libs/subliminal_patch/providers/opensubtitlescom.py +++ b/libs/subliminal_patch/providers/opensubtitlescom.py @@ -400,7 +400,7 @@ class OpenSubtitlesComProvider(ProviderRetryMixin, Provider): logger.info('Downloading subtitle %r', subtitle) headers = {'Accept': 'application/json', 'Content-Type': 'application/json', - 'Authorization': 'Beaker ' + self.token} + 'Authorization': 'Bearer ' + self.token} res = self.retry( lambda: checked( lambda: self.session.post(self.server_url + 'download', From 787a9ad531b6d54325e3d41da9bf848e7b15d2a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 May 2023 23:23:15 +0800 Subject: [PATCH 18/57] no log: Bump @vitest/coverage-c8 from 0.25.8 to 0.28.5 in /frontend (#2130) Bumps [@vitest/coverage-c8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-c8) from 0.25.8 to 0.28.5. - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v0.28.5/packages/coverage-c8) --- updated-dependencies: - dependency-name: "@vitest/coverage-c8" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- frontend/package-lock.json | 409 ++++++++++++++++++++++++++++++++++++- frontend/package.json | 2 +- 2 files changed, 405 insertions(+), 6 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 098d2eb36..4dcadd4ca 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -39,7 +39,7 @@ "@types/react-dom": "^17.0.0", "@types/react-table": "^7.7.0", "@vitejs/plugin-react": "^2.2.0", - "@vitest/coverage-c8": "^0.25.0", + "@vitest/coverage-c8": "^0.28.5", "@vitest/ui": "^0.29.1", "clsx": "^1.2.0", "eslint": "^8.26.0", @@ -3578,18 +3578,152 @@ } }, "node_modules/@vitest/coverage-c8": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@vitest/coverage-c8/-/coverage-c8-0.25.8.tgz", - "integrity": "sha512-fWgzQoK2KNzTTNnDcLCyibfO9/pbcpPOMtZ9Yvq/Eggpi2X8lewx/OcKZkO5ba5q9dl6+BBn6d5hTcS1709rZw==", + "version": "0.28.5", + "resolved": "https://registry.npmjs.org/@vitest/coverage-c8/-/coverage-c8-0.28.5.tgz", + "integrity": "sha512-zCNyurjudoG0BAqAgknvlBhkV2V9ZwyYLWOAGtHSDhL/St49MJT+V2p1G0yPaoqBbKOTATVnP5H2p1XL15H75g==", "dev": true, "dependencies": { "c8": "^7.12.0", - "vitest": "0.25.8" + "picocolors": "^1.0.0", + "std-env": "^3.3.1", + "vitest": "0.28.5" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, + "node_modules/@vitest/coverage-c8/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@vitest/coverage-c8/node_modules/vitest": { + "version": "0.28.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.28.5.tgz", + "integrity": "sha512-pyCQ+wcAOX7mKMcBNkzDwEHRGqQvHUl0XnoHR+3Pb1hytAHISgSxv9h0gUiSiYtISXUU3rMrKiKzFYDrI6ZIHA==", + "dev": true, + "dependencies": { + "@types/chai": "^4.3.4", + "@types/chai-subset": "^1.3.3", + "@types/node": "*", + "@vitest/expect": "0.28.5", + "@vitest/runner": "0.28.5", + "@vitest/spy": "0.28.5", + "@vitest/utils": "0.28.5", + "acorn": "^8.8.1", + "acorn-walk": "^8.2.0", + "cac": "^6.7.14", + "chai": "^4.3.7", + "debug": "^4.3.4", + "local-pkg": "^0.4.2", + "pathe": "^1.1.0", + "picocolors": "^1.0.0", + "source-map": "^0.6.1", + "std-env": "^3.3.1", + "strip-literal": "^1.0.0", + "tinybench": "^2.3.1", + "tinypool": "^0.3.1", + "tinyspy": "^1.0.2", + "vite": "^3.0.0 || ^4.0.0", + "vite-node": "0.28.5", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": ">=v14.16.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@vitest/browser": "*", + "@vitest/ui": "*", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "0.28.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.28.5.tgz", + "integrity": "sha512-gqTZwoUTwepwGIatnw4UKpQfnoyV0Z9Czn9+Lo2/jLIt4/AXLTn+oVZxlQ7Ng8bzcNkR+3DqLJ08kNr8jRmdNQ==", + "dev": true, + "dependencies": { + "@vitest/spy": "0.28.5", + "@vitest/utils": "0.28.5", + "chai": "^4.3.7" + } + }, + "node_modules/@vitest/runner": { + "version": "0.28.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.28.5.tgz", + "integrity": "sha512-NKkHtLB+FGjpp5KmneQjTcPLWPTDfB7ie+MmF1PnUBf/tGe2OjGxWyB62ySYZ25EYp9krR5Bw0YPLS/VWh1QiA==", + "dev": true, + "dependencies": { + "@vitest/utils": "0.28.5", + "p-limit": "^4.0.0", + "pathe": "^1.1.0" + } + }, + "node_modules/@vitest/runner/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/runner/node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/spy": { + "version": "0.28.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.28.5.tgz", + "integrity": "sha512-7if6rsHQr9zbmvxN7h+gGh2L9eIIErgf8nSKYDlg07HHimCxp4H6I/X/DPXktVPPLQfiZ1Cw2cbDIx9fSqDjGw==", + "dev": true, + "dependencies": { + "tinyspy": "^1.0.2" + } + }, "node_modules/@vitest/ui": { "version": "0.29.8", "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-0.29.8.tgz", @@ -3603,6 +3737,19 @@ "sirv": "^2.0.2" } }, + "node_modules/@vitest/utils": { + "version": "0.28.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.28.5.tgz", + "integrity": "sha512-UyZdYwdULlOa4LTUSwZ+Paz7nBHGTT72jKwdFSV4IjHF1xsokp+CabMdhjvVhYwkLfO88ylJT46YMilnkSARZA==", + "dev": true, + "dependencies": { + "cli-truncate": "^3.1.0", + "diff": "^5.1.0", + "loupe": "^2.3.6", + "picocolors": "^1.0.0", + "pretty-format": "^27.5.1" + } + }, "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", @@ -4099,6 +4246,12 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, "node_modules/c8": { "version": "7.13.0", "resolved": "https://registry.npmjs.org/c8/-/c8-7.13.0.tgz", @@ -4125,6 +4278,15 @@ "node": ">=10.12.0" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -4269,6 +4431,66 @@ "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==", "dev": true }, + "node_modules/cli-truncate": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -4625,6 +4847,15 @@ "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" }, + "node_modules/diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/diff-sequences": { "version": "29.4.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", @@ -4685,6 +4916,12 @@ "node": ">=12" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "node_modules/electron-to-chromium": { "version": "1.4.368", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.368.tgz", @@ -7213,6 +7450,12 @@ "node": ">=6" } }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -7503,6 +7746,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mlly": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.2.0.tgz", + "integrity": "sha512-+c7A3CV0KGdKcylsI6khWyts/CYrGTrRVo4R/I7u/cUsy0Conxa6LUhiEzVKIw14lc2L5aiO4+SeVe4TeGRKww==", + "dev": true, + "dependencies": { + "acorn": "^8.8.2", + "pathe": "^1.1.0", + "pkg-types": "^1.0.2", + "ufo": "^1.1.1" + } + }, "node_modules/moment": { "version": "2.29.4", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", @@ -7939,6 +8194,17 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkg-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.2.tgz", + "integrity": "sha512-hM58GKXOcj8WTqUXnsQyJYXdeAPbythQgEF3nTcEo+nkD49chjQ9IKm/QJy9xf6JakXptz86h7ecP2024rrLaQ==", + "dev": true, + "dependencies": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.1.1", + "pathe": "^1.1.0" + } + }, "node_modules/postcss": { "version": "8.4.22", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.22.tgz", @@ -8790,6 +9056,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -8819,6 +9091,46 @@ "node": ">=8" } }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/socket.io-client": { "version": "4.6.1", "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.6.1.tgz", @@ -8863,6 +9175,25 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sourcemap-codec": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", @@ -8891,6 +9222,18 @@ "node": ">=8" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, + "node_modules/std-env": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.3.2.tgz", + "integrity": "sha512-uUZI65yrV2Qva5gqE0+A7uVAvO40iPo6jGhs7s8keRfHCmtg+uB2X6EiLGCI9IgL1J17xGhvoOqSz79lzICPTA==", + "dev": true + }, "node_modules/stop-iteration-iterator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", @@ -9315,6 +9658,12 @@ "node": ">=4.2.0" } }, + "node_modules/ufo": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.1.1.tgz", + "integrity": "sha512-MvlCc4GHrmZdAllBc0iUDowff36Q9Ndw/UzqmEKyrfSzokTd9ZCy1i+IIk5hrYKkjoYVQyNbrw7/F8XJ2rEwTg==", + "dev": true + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -9537,6 +9886,40 @@ } } }, + "node_modules/vite-node": { + "version": "0.28.5", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.28.5.tgz", + "integrity": "sha512-LmXb9saMGlrMZbXTvOveJKwMTBTNUH66c8rJnQ0ZPNX+myPEol64+szRzXtV5ORb0Hb/91yq+/D3oERoyAt6LA==", + "dev": true, + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "mlly": "^1.1.0", + "pathe": "^1.1.0", + "picocolors": "^1.0.0", + "source-map": "^0.6.1", + "source-map-support": "^0.5.21", + "vite": "^3.0.0 || ^4.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": ">=v14.16.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/vite-node/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/vite-plugin-checker": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/vite-plugin-checker/-/vite-plugin-checker-0.5.6.tgz", @@ -9882,6 +10265,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", + "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "dev": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index c8c8c201c..0b8bded7b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -43,7 +43,7 @@ "@types/react-dom": "^17.0.0", "@types/react-table": "^7.7.0", "@vitejs/plugin-react": "^2.2.0", - "@vitest/coverage-c8": "^0.25.0", + "@vitest/coverage-c8": "^0.28.5", "@vitest/ui": "^0.29.1", "clsx": "^1.2.0", "eslint": "^8.26.0", From 70d1fd9049c45b05851ca7a10f8e70a608bab61c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 May 2023 23:22:56 +0800 Subject: [PATCH 19/57] no log: Bump socket.io-parser from 4.2.2 to 4.2.3 in /frontend (#2150) Bumps [socket.io-parser](https://github.com/socketio/socket.io-parser) from 4.2.2 to 4.2.3. - [Release notes](https://github.com/socketio/socket.io-parser/releases) - [Changelog](https://github.com/socketio/socket.io-parser/blob/main/CHANGELOG.md) - [Commits](https://github.com/socketio/socket.io-parser/compare/4.2.2...4.2.3) --- updated-dependencies: - dependency-name: socket.io-parser dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- frontend/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4dcadd4ca..edfa33e2f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9146,9 +9146,9 @@ } }, "node_modules/socket.io-parser": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz", - "integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.3.tgz", + "integrity": "sha512-JMafRntWVO2DCJimKsRTh/wnqVvO4hrfwOqtO7f+uzwsQMuxO6VwImtYxaQ+ieoyshWOTJyV0fA21lccEXRPpQ==", "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" From 547f8c428df856d97bf9d258e723e39a7609b635 Mon Sep 17 00:00:00 2001 From: Vitiko <59455966+vitiko98@users.noreply.github.com> Date: Sat, 27 May 2023 09:38:55 -0400 Subject: [PATCH 20/57] Added feature to treat couples of languages as equal when searching for subtitles * Add 'Language-equals' support This feature will treat couples of languages as equal for list-subtitles operations. It's optional; its methods won't do anything if an empy list is set. See more info at docstrings from 'subliminal_patch.core'. For example, let's say I only want to have "Spanish (es.srt)" subtitles and I don't care about the differences between Spain and LATAM spanish. This feature will allow me to always get European Spanish even from LATAM Spanish providers like Argenteam and Subdivx. Example for config.ini: language_equals = ['spa-MX:spa'] (Which means all Latam Spanish subtitles from every provider will be converted to European Spanish) * Add PT and ZH language tests * Add HI and Forced parsing for language pairs Format example: ["en@HI:en", "es-MX@forced:es-MX"] * Update languages.py * Update API definition to reflect the previous change * Add language equals table to the UI (test only) * Add global language selector and get language from code3 utilities * Add unit tests for language equal feature * Add encode function to language equal feature * Add CRUD methods to the language equals panel * Add equals description * Add parsing support for alpha3 custom languages * no log: add more tests * Add forced and hi support to the language equal target --------- Co-authored-by: morpheus65535 Co-authored-by: LASER-Yi --- bazarr/api/system/languages.py | 4 +- bazarr/app/config.py | 6 +- bazarr/app/get_providers.py | 44 +++ bazarr/subtitles/pool.py | 18 +- .../components/bazarr/LanguageSelector.tsx | 34 ++ .../pages/Settings/Languages/equals.test.ts | 196 ++++++++++ .../src/pages/Settings/Languages/equals.tsx | 365 ++++++++++++++++++ .../src/pages/Settings/Languages/index.tsx | 9 +- frontend/src/pages/Settings/keys.ts | 2 + frontend/src/types/api.d.ts | 1 + frontend/src/types/settings.d.ts | 1 + frontend/src/utilities/languages.ts | 9 + libs/subliminal_patch/core.py | 56 ++- tests/bazarr/app/test_get_providers.py | 45 --- tests/bazarr/conftest.py | 1 + tests/bazarr/test_app_get_providers.py | 115 ++++++ tests/bazarr/test_subtitles_pool.py | 10 + tests/subliminal_patch/test_core.py | 96 +++++ 18 files changed, 956 insertions(+), 56 deletions(-) create mode 100644 frontend/src/components/bazarr/LanguageSelector.tsx create mode 100644 frontend/src/pages/Settings/Languages/equals.test.ts create mode 100644 frontend/src/pages/Settings/Languages/equals.tsx delete mode 100644 tests/bazarr/app/test_get_providers.py create mode 100644 tests/bazarr/test_app_get_providers.py create mode 100644 tests/bazarr/test_subtitles_pool.py diff --git a/bazarr/api/system/languages.py b/bazarr/api/system/languages.py index 757e84a32..75a680f36 100644 --- a/bazarr/api/system/languages.py +++ b/bazarr/api/system/languages.py @@ -4,7 +4,7 @@ from flask_restx import Resource, Namespace, reqparse from operator import itemgetter from app.database import TableHistory, TableHistoryMovie, TableSettingsLanguages -from languages.get_languages import alpha2_from_alpha3, language_from_alpha2 +from languages.get_languages import alpha2_from_alpha3, language_from_alpha2, alpha3_from_alpha2 from ..utils import authenticate, False_Keys @@ -46,6 +46,7 @@ class Languages(Resource): try: languages_dicts.append({ 'code2': code2, + 'code3': alpha3_from_alpha2(code2), 'name': language_from_alpha2(code2), # Compatibility: Use false temporarily 'enabled': False @@ -55,6 +56,7 @@ class Languages(Resource): else: languages_dicts = TableSettingsLanguages.select(TableSettingsLanguages.name, TableSettingsLanguages.code2, + TableSettingsLanguages.code3, TableSettingsLanguages.enabled)\ .order_by(TableSettingsLanguages.name).dicts() languages_dicts = list(languages_dicts) diff --git a/bazarr/app/config.py b/bazarr/app/config.py index 306c46539..b3840e0c4 100644 --- a/bazarr/app/config.py +++ b/bazarr/app/config.py @@ -83,7 +83,8 @@ defaults = { 'default_und_audio_lang': '', 'default_und_embedded_subtitles_lang': '', 'parse_embedded_audio_track': 'False', - 'skip_hashing': 'False' + 'skip_hashing': 'False', + 'language_equals': '[]', }, 'auth': { 'type': 'None', @@ -300,7 +301,8 @@ array_keys = ['excluded_tags', 'excluded_series_types', 'enabled_providers', 'path_mappings', - 'path_mappings_movie'] + 'path_mappings_movie', + 'language_equals'] str_keys = ['chmod'] diff --git a/bazarr/app/get_providers.py b/bazarr/app/get_providers.py index 8b51a4e2d..04593ea15 100644 --- a/bazarr/app/get_providers.py +++ b/bazarr/app/get_providers.py @@ -20,6 +20,7 @@ from subliminal_patch.extensions import provider_registry from app.get_args import args from app.config import settings, get_array_from +from languages.get_languages import CustomLanguage from app.event_handler import event_stream from utilities.binaries import get_binary from radarr.blacklist import blacklist_log_movie @@ -115,6 +116,49 @@ def provider_pool(): return subliminal_patch.core.SZProviderPool +def _lang_from_str(content: str): + " Formats: es-MX en@hi es-MX@forced " + extra_info = content.split("@") + if len(extra_info) > 1: + kwargs = {extra_info[-1]: True} + else: + kwargs = {} + + content = extra_info[0] + + try: + code, country = content.split("-") + except ValueError: + lang = CustomLanguage.from_value(content) + if lang is not None: + lang = lang.subzero_language() + return lang.rebuild(lang, **kwargs) + + code, country = content, None + + return subliminal_patch.core.Language(code, country, **kwargs) + + +def get_language_equals(settings_=None): + settings_ = settings_ or settings + + equals = get_array_from(settings_.general.language_equals) + if not equals: + return [] + + items = [] + for equal in equals: + try: + from_, to_ = equal.split(":") + from_, to_ = _lang_from_str(from_), _lang_from_str(to_) + except Exception as error: + logging.info("Invalid equal value: '%s' [%s]", equal, error) + else: + items.append((from_, to_)) + + return items + + def get_providers(): providers_list = [] existing_providers = provider_registry.names() diff --git a/bazarr/subtitles/pool.py b/bazarr/subtitles/pool.py index c1c771807..c70e8f98c 100644 --- a/bazarr/subtitles/pool.py +++ b/bazarr/subtitles/pool.py @@ -8,7 +8,7 @@ from inspect import getfullargspec from radarr.blacklist import get_blacklist_movie from sonarr.blacklist import get_blacklist -from app.get_providers import get_providers, get_providers_auth, provider_throttle, provider_pool +from app.get_providers import get_providers, get_providers_auth, provider_throttle, provider_pool, get_language_equals from .utils import get_ban_list @@ -19,10 +19,11 @@ def _init_pool(media_type, profile_id=None, providers=None): return pool( providers=providers or get_providers(), provider_configs=get_providers_auth(), - blacklist=get_blacklist() if media_type == 'series' else get_blacklist_movie(), + blacklist=get_blacklist() if media_type == "series" else get_blacklist_movie(), throttle_callback=provider_throttle, ban_list=get_ban_list(profile_id), language_hook=None, + language_equals=get_language_equals(), ) @@ -54,8 +55,19 @@ def _update_pool(media_type, profile_id=None): return pool.update( get_providers(), get_providers_auth(), - get_blacklist() if media_type == 'series' else get_blacklist_movie(), + get_blacklist() if media_type == "series" else get_blacklist_movie(), get_ban_list(profile_id), + get_language_equals(), + ) + + +def _pool_update(pool, media_type, profile_id=None): + return pool.update( + get_providers(), + get_providers_auth(), + get_blacklist() if media_type == "series" else get_blacklist_movie(), + get_ban_list(profile_id), + get_language_equals(), ) diff --git a/frontend/src/components/bazarr/LanguageSelector.tsx b/frontend/src/components/bazarr/LanguageSelector.tsx new file mode 100644 index 000000000..84ce363d5 --- /dev/null +++ b/frontend/src/components/bazarr/LanguageSelector.tsx @@ -0,0 +1,34 @@ +import { useLanguages } from "@/apis/hooks"; +import { Selector, SelectorProps } from "@/components/inputs"; +import { useSelectorOptions } from "@/utilities"; +import { FunctionComponent, useMemo } from "react"; + +interface LanguageSelectorProps + extends Omit, "options" | "getkey"> { + enabled?: boolean; +} + +const LanguageSelector: FunctionComponent = ({ + enabled = false, + ...selector +}) => { + const { data } = useLanguages(); + + const filteredData = useMemo(() => { + if (enabled) { + return data?.filter((value) => value.enabled); + } else { + return data; + } + }, [data, enabled]); + + const options = useSelectorOptions( + filteredData ?? [], + (value) => value.name, + (value) => value.code3 + ); + + return ; +}; + +export default LanguageSelector; diff --git a/frontend/src/pages/Settings/Languages/equals.test.ts b/frontend/src/pages/Settings/Languages/equals.test.ts new file mode 100644 index 000000000..19c641fcb --- /dev/null +++ b/frontend/src/pages/Settings/Languages/equals.test.ts @@ -0,0 +1,196 @@ +import { + decodeEqualData, + encodeEqualData, + LanguageEqualData, + LanguageEqualImmediateData, +} from "@/pages/Settings/Languages/equals"; +import { describe, expect, it } from "vitest"; + +describe("Equals Parser", () => { + it("should parse from string correctly", () => { + interface TestData { + text: string; + expected: LanguageEqualImmediateData; + } + + function testParsedResult( + text: string, + expected: LanguageEqualImmediateData + ) { + const result = decodeEqualData(text); + + if (result === undefined) { + expect(false, `Cannot parse '${text}' as language equal data`); + return; + } + + expect( + result, + `${text} does not match with the expected equal data` + ).toStrictEqual(expected); + } + + const testValues: TestData[] = [ + { + text: "spa-MX:spa", + expected: { + source: { + content: "spa-MX", + hi: false, + forced: false, + }, + target: { + content: "spa", + hi: false, + forced: false, + }, + }, + }, + { + text: "zho@hi:zht", + expected: { + source: { + content: "zho", + hi: true, + forced: false, + }, + target: { + content: "zht", + hi: false, + forced: false, + }, + }, + }, + { + text: "es-MX@forced:es-MX", + expected: { + source: { + content: "es-MX", + hi: false, + forced: true, + }, + target: { + content: "es-MX", + hi: false, + forced: false, + }, + }, + }, + { + text: "en:en@hi", + expected: { + source: { + content: "en", + hi: false, + forced: false, + }, + target: { + content: "en", + hi: true, + forced: false, + }, + }, + }, + ]; + + testValues.forEach((data) => { + testParsedResult(data.text, data.expected); + }); + }); + + it("should encode to string correctly", () => { + interface TestData { + source: LanguageEqualData; + expected: string; + } + + const testValues: TestData[] = [ + { + source: { + source: { + content: { + name: "Abkhazian", + code2: "ab", + code3: "abk", + enabled: false, + }, + hi: false, + forced: false, + }, + target: { + content: { + name: "Aragonese", + code2: "an", + code3: "arg", + enabled: false, + }, + hi: false, + forced: false, + }, + }, + expected: "abk:arg", + }, + { + source: { + source: { + content: { + name: "Abkhazian", + code2: "ab", + code3: "abk", + enabled: false, + }, + hi: true, + forced: false, + }, + target: { + content: { + name: "Aragonese", + code2: "an", + code3: "arg", + enabled: false, + }, + hi: false, + forced: false, + }, + }, + expected: "abk@hi:arg", + }, + { + source: { + source: { + content: { + name: "Abkhazian", + code2: "ab", + code3: "abk", + enabled: false, + }, + hi: false, + forced: true, + }, + target: { + content: { + name: "Aragonese", + code2: "an", + code3: "arg", + enabled: false, + }, + hi: false, + forced: false, + }, + }, + expected: "abk@forced:arg", + }, + ]; + + function testEncodeResult({ source, expected }: TestData) { + const encoded = encodeEqualData(source); + + expect( + encoded, + `Encoded result '${encoded}' is not matched to '${expected}'` + ).toEqual(expected); + } + + testValues.forEach(testEncodeResult); + }); +}); diff --git a/frontend/src/pages/Settings/Languages/equals.tsx b/frontend/src/pages/Settings/Languages/equals.tsx new file mode 100644 index 000000000..a958237df --- /dev/null +++ b/frontend/src/pages/Settings/Languages/equals.tsx @@ -0,0 +1,365 @@ +import { useLanguages } from "@/apis/hooks"; +import { Action, SimpleTable } from "@/components"; +import LanguageSelector from "@/components/bazarr/LanguageSelector"; +import { languageEqualsKey } from "@/pages/Settings/keys"; +import { useFormActions } from "@/pages/Settings/utilities/FormValues"; +import { useSettingValue } from "@/pages/Settings/utilities/hooks"; +import { LOG } from "@/utilities/console"; +import { faEquals, faTrash } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Button, Checkbox } from "@mantine/core"; +import { FunctionComponent, useCallback, useMemo } from "react"; +import { Column } from "react-table"; + +interface GenericEqualTarget { + content: T; + hi: boolean; + forced: boolean; +} + +interface LanguageEqualGenericData { + source: GenericEqualTarget; + target: GenericEqualTarget; +} + +export type LanguageEqualImmediateData = + LanguageEqualGenericData; + +export type LanguageEqualData = LanguageEqualGenericData; + +function decodeEqualTarget( + text: string +): GenericEqualTarget | undefined { + const [code, decoration] = text.split("@"); + + if (code.length === 0) { + return undefined; + } + + const forced = decoration === "forced"; + const hi = decoration === "hi"; + + return { + content: code, + forced, + hi, + }; +} + +export function decodeEqualData( + text: string +): LanguageEqualImmediateData | undefined { + const [first, second] = text.split(":"); + + const source = decodeEqualTarget(first); + const target = decodeEqualTarget(second); + + if (source === undefined || target === undefined) { + return undefined; + } + + return { + source, + target, + }; +} + +function encodeEqualTarget(data: GenericEqualTarget): string { + let text = data.content.code3; + if (data.hi) { + text += "@hi"; + } else if (data.forced) { + text += "@forced"; + } + + return text; +} + +export function encodeEqualData(data: LanguageEqualData): string { + const source = encodeEqualTarget(data.source); + const target = encodeEqualTarget(data.target); + + return `${source}:${target}`; +} + +export function useLatestLanguageEquals(): LanguageEqualData[] { + const { data } = useLanguages(); + + const latest = useSettingValue(languageEqualsKey); + + return useMemo( + () => + latest + ?.map(decodeEqualData) + .map((parsed) => { + if (parsed === undefined) { + return undefined; + } + + const source = data?.find( + (value) => value.code3 === parsed.source.content + ); + const target = data?.find( + (value) => value.code3 === parsed.target.content + ); + + if (source === undefined || target === undefined) { + return undefined; + } + + return { + source: { ...parsed.source, content: source }, + target: { ...parsed.target, content: target }, + }; + }) + .filter((v): v is LanguageEqualData => v !== undefined) ?? [], + [data, latest] + ); +} + +interface EqualsTableProps {} + +const EqualsTable: FunctionComponent = () => { + const { data: languages } = useLanguages(); + const canAdd = languages !== undefined; + + const equals = useLatestLanguageEquals(); + + const { setValue } = useFormActions(); + + const setEquals = useCallback( + (values: LanguageEqualData[]) => { + const encodedValues = values.map(encodeEqualData); + + LOG("info", "updating language equals data", values); + setValue(encodedValues, languageEqualsKey); + }, + [setValue] + ); + + const add = useCallback(() => { + if (languages === undefined) { + return; + } + + const enabled = languages.find((value) => value.enabled); + + if (enabled === undefined) { + return; + } + + const newValue: LanguageEqualData[] = [ + ...equals, + { + source: { + content: enabled, + hi: false, + forced: false, + }, + target: { + content: enabled, + hi: false, + forced: false, + }, + }, + ]; + + setEquals(newValue); + }, [equals, languages, setEquals]); + + const update = useCallback( + (index: number, value: LanguageEqualData) => { + if (index < 0 || index >= equals.length) { + return; + } + + const newValue: LanguageEqualData[] = [...equals]; + + newValue[index] = { ...value }; + setEquals(newValue); + }, + [equals, setEquals] + ); + + const remove = useCallback( + (index: number) => { + if (index < 0 || index >= equals.length) { + return; + } + + const newValue: LanguageEqualData[] = [...equals]; + + newValue.splice(index, 1); + + setEquals(newValue); + }, + [equals, setEquals] + ); + + const columns = useMemo[]>( + () => [ + { + Header: "Source", + id: "source-lang", + accessor: "source", + Cell: ({ value: { content }, row }) => { + return ( + { + if (result !== null) { + update(row.index, { + ...row.original, + source: { ...row.original.source, content: result }, + }); + } + }} + > + ); + }, + }, + { + id: "source-hi", + accessor: "source", + Cell: ({ value: { hi }, row }) => { + return ( + { + update(row.index, { + ...row.original, + source: { + ...row.original.source, + hi: checked, + forced: checked ? false : row.original.source.forced, + }, + }); + }} + > + ); + }, + }, + { + id: "source-forced", + accessor: "source", + Cell: ({ value: { forced }, row }) => { + return ( + { + update(row.index, { + ...row.original, + source: { + ...row.original.source, + forced: checked, + hi: checked ? false : row.original.source.hi, + }, + }); + }} + > + ); + }, + }, + { + id: "equal-icon", + Cell: () => { + return ; + }, + }, + { + Header: "Target", + id: "target-lang", + accessor: "target", + Cell: ({ value: { content }, row }) => { + return ( + { + if (result !== null) { + update(row.index, { + ...row.original, + target: { ...row.original.target, content: result }, + }); + } + }} + > + ); + }, + }, + { + id: "target-hi", + accessor: "target", + Cell: ({ value: { hi }, row }) => { + return ( + { + update(row.index, { + ...row.original, + target: { + ...row.original.target, + hi: checked, + forced: checked ? false : row.original.target.forced, + }, + }); + }} + > + ); + }, + }, + { + id: "target-forced", + accessor: "target", + Cell: ({ value: { forced }, row }) => { + return ( + { + update(row.index, { + ...row.original, + target: { + ...row.original.target, + forced: checked, + hi: checked ? false : row.original.target.hi, + }, + }); + }} + > + ); + }, + }, + { + id: "action", + accessor: "target", + Cell: ({ row }) => { + return ( + remove(row.index)} + > + ); + }, + }, + ], + [remove, update] + ); + + return ( + <> + + + + ); +}; + +export default EqualsTable; diff --git a/frontend/src/pages/Settings/Languages/index.tsx b/frontend/src/pages/Settings/Languages/index.tsx index 993820478..762174133 100644 --- a/frontend/src/pages/Settings/Languages/index.tsx +++ b/frontend/src/pages/Settings/Languages/index.tsx @@ -17,6 +17,7 @@ import { } from "../keys"; import { useSettingValue } from "../utilities/hooks"; import { LanguageSelector, ProfileSelector } from "./components"; +import EqualsTable from "./equals"; import Table from "./table"; export function useLatestEnabledLanguages() { @@ -69,6 +70,13 @@ const SettingsLanguagesView: FunctionComponent = () => { > +
+ + Treat the following languages as equal across all providers. + + +
+
{ }} > - data?.find((value) => value.code3 === code3), + [data, code3] + ); +} diff --git a/libs/subliminal_patch/core.py b/libs/subliminal_patch/core.py index c31d5ecd0..b649e4288 100644 --- a/libs/subliminal_patch/core.py +++ b/libs/subliminal_patch/core.py @@ -153,9 +153,52 @@ class _Blacklist(list): return not blacklisted +class _LanguageEquals(list): + """ An optional config field for the pool. It will treat a couple of languages as equal for + list-subtitles operations. It's optional; its methods won't do anything if an empy list + is set. + + Example usage: [(language_instance, language_instance), ...]""" + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + for item in self: + if len(item) != 2 or not any(isinstance(i, Language) for i in item): + raise ValueError(f"Not a valid equal tuple: {item}") + + def check_set(self, items: set): + """ Check a set of languages. For example, if the set is {Language('es')} and one of the + equals of the instance is (Language('es'), Language('es', 'MX')), the set will now have + to {Language('es'), Language('es', 'MX')}. + + It will return a copy of the original set to avoid messing up outside its scope. + + Note that hearing_impaired and forced language attributes are not yet tested. + """ + to_add = [] + for equals in self: + from_, to_ = equals + if from_ in items: + logger.debug("Adding %s to %s", to_, items) + to_add.append(to_) + + new_items = items.copy() + new_items.update(to_add) + logger.debug("New set: %s", new_items) + return new_items + + def update_subtitle(self, subtitle): + for equals in self: + from_, to_ = equals + if from_ == subtitle.language: + logger.debug("Updating language for %s (to %s)", subtitle, to_) + subtitle.language = to_ + break + + class SZProviderPool(ProviderPool): def __init__(self, providers=None, provider_configs=None, blacklist=None, ban_list=None, throttle_callback=None, - pre_download_hook=None, post_download_hook=None, language_hook=None): + pre_download_hook=None, post_download_hook=None, language_hook=None, language_equals=None): #: Name of providers to use self.providers = set(providers or []) @@ -170,6 +213,8 @@ class SZProviderPool(ProviderPool): #: Should be a dict of 2 lists of strings self.ban_list = _Banlist(**(ban_list or {'must_contain': [], 'must_not_contain': []})) + self.lang_equals = _LanguageEquals(language_equals or []) + self.throttle_callback = throttle_callback self.pre_download_hook = pre_download_hook @@ -185,7 +230,7 @@ class SZProviderPool(ProviderPool): self.provider_configs = _ProviderConfigs(self) self.provider_configs.update(provider_configs or {}) - def update(self, providers, provider_configs, blacklist, ban_list): + def update(self, providers, provider_configs, blacklist, ban_list, language_equals=None): # Check if the pool was initialized enough hours ago self._check_lifetime() @@ -222,6 +267,7 @@ class SZProviderPool(ProviderPool): self.blacklist = _Blacklist(blacklist or []) self.ban_list = _Banlist(**ban_list or {'must_contain': [], 'must_not_contain': []}) + self.lang_equals = _LanguageEquals(language_equals or []) return updated @@ -299,7 +345,7 @@ class SZProviderPool(ProviderPool): return [] # check supported languages - provider_languages = provider_registry[provider].languages & use_languages + provider_languages = self.lang_equals.check_set(set(provider_registry[provider].languages)) & use_languages if not provider_languages: logger.info('Skipping provider %r: no language to search for', provider) return [] @@ -312,6 +358,8 @@ class SZProviderPool(ProviderPool): seen = [] out = [] for s in results: + self.lang_equals.update_subtitle(s) + if not self.blacklist.is_valid(provider, s): continue @@ -569,7 +617,7 @@ class SZProviderPool(ProviderPool): continue # add the languages for this provider - languages.append({'provider': name, 'languages': provider_languages}) + languages.append({'provider': name, 'languages': self.lang_equals.check_set(set(provider_languages))}) return languages diff --git a/tests/bazarr/app/test_get_providers.py b/tests/bazarr/app/test_get_providers.py deleted file mode 100644 index caafa17d9..000000000 --- a/tests/bazarr/app/test_get_providers.py +++ /dev/null @@ -1,45 +0,0 @@ -import pytest - -import inspect - -from bazarr.app import get_providers - - -def test_get_providers_auth(): - for val in get_providers.get_providers_auth().values(): - assert isinstance(val, dict) - - -def test_get_providers_auth_with_provider_registry(): - """Make sure all providers will be properly initialized with bazarr - configs""" - from subliminal_patch.extensions import provider_registry - - auths = get_providers.get_providers_auth() - for key, val in auths.items(): - provider = provider_registry[key] - sign = inspect.signature(provider.__init__) - for sub_key in val.keys(): - if sub_key not in sign.parameters: - raise ValueError(f"'{sub_key}' parameter not present in {provider}") - - assert sign.parameters[sub_key] is not None - - -def test_get_providers_auth_embeddedsubtitles(): - item = get_providers.get_providers_auth()["embeddedsubtitles"] - assert isinstance(item["included_codecs"], list) - assert isinstance(item["hi_fallback"], bool) - assert isinstance(item["cache_dir"], str) - assert isinstance(item["ffprobe_path"], str) - assert isinstance(item["ffmpeg_path"], str) - assert isinstance(item["timeout"], str) - assert isinstance(item["unknown_as_english"], bool) - - -def test_get_providers_auth_karagarga(): - item = get_providers.get_providers_auth()["karagarga"] - assert item["username"] is not None - assert item["password"] is not None - assert item["f_username"] is not None - assert item["f_password"] is not None diff --git a/tests/bazarr/conftest.py b/tests/bazarr/conftest.py index 865b92767..d4bc23974 100644 --- a/tests/bazarr/conftest.py +++ b/tests/bazarr/conftest.py @@ -3,5 +3,6 @@ import logging os.environ["NO_CLI"] = "true" os.environ["SZ_USER_AGENT"] = "test" +os.environ["BAZARR_VERSION"] = "test" # fixme logging.getLogger("rebulk").setLevel(logging.WARNING) diff --git a/tests/bazarr/test_app_get_providers.py b/tests/bazarr/test_app_get_providers.py new file mode 100644 index 000000000..e6a31a39f --- /dev/null +++ b/tests/bazarr/test_app_get_providers.py @@ -0,0 +1,115 @@ +import inspect + +import pytest +from subliminal_patch.core import Language + +from bazarr.app import get_providers + + +def test_get_providers_auth(): + for val in get_providers.get_providers_auth().values(): + assert isinstance(val, dict) + + +def test_get_providers_auth_with_provider_registry(): + """Make sure all providers will be properly initialized with bazarr + configs""" + from subliminal_patch.extensions import provider_registry + + auths = get_providers.get_providers_auth() + for key, val in auths.items(): + provider = provider_registry[key] + sign = inspect.signature(provider.__init__) + for sub_key in val.keys(): + if sub_key not in sign.parameters: + raise ValueError(f"'{sub_key}' parameter not present in {provider}") + + assert sign.parameters[sub_key] is not None + + +def test_get_providers_auth_embeddedsubtitles(): + item = get_providers.get_providers_auth()["embeddedsubtitles"] + assert isinstance(item["included_codecs"], list) + assert isinstance(item["hi_fallback"], bool) + assert isinstance(item["cache_dir"], str) + assert isinstance(item["ffprobe_path"], str) + assert isinstance(item["ffmpeg_path"], str) + assert isinstance(item["timeout"], str) + assert isinstance(item["unknown_as_english"], bool) + + +def test_get_providers_auth_karagarga(): + item = get_providers.get_providers_auth()["karagarga"] + assert item["username"] is not None + assert item["password"] is not None + assert item["f_username"] is not None + assert item["f_password"] is not None + + +def test_get_language_equals_default_settings(): + assert isinstance(get_providers.get_language_equals(), list) + + +def test_get_language_equals_injected_settings_invalid(): + config = get_providers.settings + config.set("general", "language_equals", '["invalid"]') + assert not get_providers.get_language_equals(config) + + +def test_get_language_equals_injected_settings_valid(): + config = get_providers.settings + config.set("general", "language_equals", '["spa:spa-MX"]') + + result = get_providers.get_language_equals(config) + assert result == [(Language("spa"), Language("spa", "MX"))] + + +@pytest.mark.parametrize( + "config_value,expected", + [ + ('["spa:spl"]', (Language("spa"), Language("spa", "MX"))), + ('["por:pob"]', (Language("por"), Language("por", "BR"))), + ('["zho:zht"]', (Language("zho"), Language("zho", "TW"))), + ], +) +def test_get_language_equals_injected_settings_custom_lang_alpha3( + config_value, expected +): + config = get_providers.settings + + config.set("general", "language_equals", config_value) + + result = get_providers.get_language_equals(config) + assert result == [expected] + + +def test_get_language_equals_injected_settings_multiple(): + config = get_providers.settings + + config.set( + "general", + "language_equals", + "['eng@hi:eng', 'spa:spl', 'spa@hi:spl', 'spl@hi:spl']", + ) + + result = get_providers.get_language_equals(config) + assert len(result) == 4 + + +def test_get_language_equals_injected_settings_valid_multiple(): + config = get_providers.settings + config.set("general", "language_equals", '["spa:spa-MX", "spa-MX:spa"]') + + result = get_providers.get_language_equals(config) + assert result == [ + (Language("spa"), Language("spa", "MX")), + (Language("spa", "MX"), Language("spa")), + ] + + +def test_get_language_equals_injected_settings_hi(): + config = get_providers.settings + config.set("general", "language_equals", '["eng@hi:eng"]') + + result = get_providers.get_language_equals(config) + assert result == [(Language("eng", hi=True), Language("eng"))] diff --git a/tests/bazarr/test_subtitles_pool.py b/tests/bazarr/test_subtitles_pool.py new file mode 100644 index 000000000..862c6d493 --- /dev/null +++ b/tests/bazarr/test_subtitles_pool.py @@ -0,0 +1,10 @@ +from bazarr.subtitles import pool + + +def test_init_pool(): + assert pool._init_pool("movie") + + +def test_pool_update(): + pool_ = pool._init_pool("movie") + assert pool._pool_update(pool_, "movie") diff --git a/tests/subliminal_patch/test_core.py b/tests/subliminal_patch/test_core.py index d6481ee16..fadf4e493 100644 --- a/tests/subliminal_patch/test_core.py +++ b/tests/subliminal_patch/test_core.py @@ -70,3 +70,99 @@ def test_pool_update_discarded_providers_2(pool_instance): # Provider should not disappear from discarded providers assert pool_instance.discarded_providers == {"argenteam"} + + +def test_language_equals_init(): + assert core._LanguageEquals([(core.Language("spa"), core.Language("spa", "MX"))]) + + +def test_language_equals_init_invalid(): + with pytest.raises(ValueError): + assert core._LanguageEquals([(core.Language("spa", "MX"),)]) + + +def test_language_equals_init_empty_list_gracefully(): + assert core._LanguageEquals([]) == [] + + +@pytest.mark.parametrize( + "langs", + [ + [(core.Language("spa"), core.Language("spa", "MX"))], + [(core.Language("por"), core.Language("por", "BR"))], + [(core.Language("zho"), core.Language("zho", "TW"))], + ], +) +def test_language_equals_check_set(langs): + equals = core._LanguageEquals(langs) + lang_set = {langs[0]} + assert equals.check_set(lang_set) == set(langs) + + +def test_language_equals_check_set_do_nothing(): + equals = core._LanguageEquals([(core.Language("eng"), core.Language("spa"))]) + lang_set = {core.Language("spa")} + assert equals.check_set(lang_set) == {core.Language("spa")} + + +def test_language_equals_check_set_do_nothing_w_forced(): + equals = core._LanguageEquals( + [(core.Language("spa", forced=True), core.Language("spa", "MX"))] + ) + lang_set = {core.Language("spa")} + assert equals.check_set(lang_set) == {core.Language("spa")} + + +@pytest.fixture +def language_equals_pool_intance(): + equals = [(core.Language("spa"), core.Language("spa", "MX"))] + yield core.SZProviderPool({"subdivx"}, language_equals=equals) + + +def test_language_equals_pool_intance_list_subtitles( + language_equals_pool_intance, movies +): + subs = language_equals_pool_intance.list_subtitles( + movies["dune"], {core.Language("spa")} + ) + assert subs + assert all(sub.language == core.Language("spa", "MX") for sub in subs) + + +def test_language_equals_pool_intance_list_subtitles_reversed(movies): + equals = [(core.Language("spa", "MX"), core.Language("spa"))] + language_equals_pool_intance = core.SZProviderPool( + {"subdivx"}, language_equals=equals + ) + subs = language_equals_pool_intance.list_subtitles( + movies["dune"], {core.Language("spa")} + ) + assert subs + assert all(sub.language == core.Language("spa") for sub in subs) + + +def test_language_equals_pool_intance_list_subtitles_empty_lang_equals(movies): + language_equals_pool_intance = core.SZProviderPool( + {"subdivx"}, language_equals=None + ) + subs = language_equals_pool_intance.list_subtitles( + movies["dune"], {core.Language("spa")} + ) + assert subs + assert not all(sub.language == core.Language("spa", "MX") for sub in subs) + + +def test_language_equals_pool_intance_list_subtitles_return_nothing(movies): + equals = [ + (core.Language("spa", "MX"), core.Language("eng")), + (core.Language("spa"), core.Language("eng")), + ] + language_equals_pool_intance = core.SZProviderPool( + {"subdivx"}, language_equals=equals + ) + subs = language_equals_pool_intance.list_subtitles( + movies["dune"], {core.Language("spa")} + ) + assert not language_equals_pool_intance.download_best_subtitles( + subs, movies["dune"], {core.Language("spa")} + ) From d90d1cbfcc57ff07ad2cff136481260dbb1ef7e3 Mon Sep 17 00:00:00 2001 From: morpheus65535 Date: Sun, 28 May 2023 09:16:29 -0400 Subject: [PATCH 21/57] Fixed external subtitles indexation with accented characters that resulted in download in loop. #1961 --- libs/subliminal_patch/core.py | 79 ++++++++++++++--------------------- 1 file changed, 31 insertions(+), 48 deletions(-) diff --git a/libs/subliminal_patch/core.py b/libs/subliminal_patch/core.py index c31d5ecd0..948b25eb6 100644 --- a/libs/subliminal_patch/core.py +++ b/libs/subliminal_patch/core.py @@ -1,6 +1,5 @@ # coding=utf-8 -from __future__ import absolute_import -import codecs +import six import json import re import os @@ -11,37 +10,27 @@ import traceback import time import operator import unicodedata - import itertools -from six.moves.http_client import ResponseNotReady - import rarfile import requests +from os import scandir from collections import defaultdict from bs4 import UnicodeDammit from babelfish import LanguageReverseError from guessit.jsonutils import GuessitEncoder -from subliminal import ProviderError, refiner_manager +from subliminal import refiner_manager from concurrent.futures import as_completed from .extensions import provider_registry from .exceptions import MustGetBlacklisted from .score import compute_score as default_compute_score -from subliminal.exceptions import ServiceUnavailable, DownloadLimitExceeded from subliminal.utils import hash_napiprojekt, hash_opensubtitles, hash_shooter, hash_thesubdb from subliminal.video import VIDEO_EXTENSIONS, Video, Episode, Movie from subliminal.core import guessit, ProviderPool, io, is_windows_special_path, \ ThreadPoolExecutor, check_video -from subliminal_patch.exceptions import TooManyRequests, APIThrottled from subzero.language import Language, ENDSWITH_LANGUAGECODE_RE, FULL_LANGUAGE_LIST -try: - from os import scandir - _scandir_generic = scandir -except ImportError: - from scandir import scandir, scandir_generic as _scandir_generic -import six logger = logging.getLogger(__name__) @@ -106,7 +95,7 @@ class _ProviderConfigs(dict): logger.debug("Config changed. Restarting provider: %s", key) try: - provider = provider_registry[key](**registered_val) # type: ignore + provider = provider_registry[key](**registered_val) # type: ignore provider.initialize() except Exception as error: self._pool.throttle_callback(key, error) @@ -269,7 +258,7 @@ class SZProviderPool(ProviderPool): """List subtitles with a single provider. The video and languages are checked against the provider. - + patch: add traceback info :param str provider: name of the provider. @@ -333,7 +322,7 @@ class SZProviderPool(ProviderPool): def list_subtitles(self, video, languages): """List subtitles. - + patch: handle LanguageReverseError :param video: video to list subtitles for. @@ -372,9 +361,9 @@ class SZProviderPool(ProviderPool): def download_subtitle(self, subtitle): """Download `subtitle`'s :attr:`~subliminal.subtitle.Subtitle.content`. - + patch: add retry functionality - + :param subtitle: subtitle to download. :type subtitle: :class:`~subliminal.subtitle.Subtitle` :return: `True` if the subtitle has been successfully downloaded, `False` otherwise. @@ -442,8 +431,8 @@ class SZProviderPool(ProviderPool): def download_best_subtitles(self, subtitles, video, languages, min_score=0, hearing_impaired=False, only_one=False, compute_score=None): """Download the best matching subtitles. - - patch: + + patch: - hearing_impaired is now string - add .score to subtitle - move all languages check further to the top (still necessary?) @@ -513,7 +502,7 @@ class SZProviderPool(ProviderPool): # bail out if hearing_impaired was wrong if subtitle.hearing_impaired_verifiable and "hearing_impaired" not in matches and \ - hearing_impaired in ("force HI", "force non-HI"): + hearing_impaired in ("force HI", "force non-HI"): logger.debug('%r: Skipping subtitle with score %d because hearing-impaired set to %s', subtitle, score, hearing_impaired) continue @@ -525,7 +514,7 @@ class SZProviderPool(ProviderPool): matches_series = False if {"season", "episode"}.issubset(orig_matches) and \ - ("series" in orig_matches or "imdb_id" in orig_matches): + ("series" in orig_matches or "imdb_id" in orig_matches): matches_series = True if can_verify_series and not matches_series: @@ -534,8 +523,8 @@ class SZProviderPool(ProviderPool): continue # download - logger.debug("%r: Trying to download subtitle with matches %s, score: %s; release(s): %s", subtitle, matches, - score, subtitle.release_info) + logger.debug("%r: Trying to download subtitle with matches %s, score: %s; release(s): %s", subtitle, + matches, score, subtitle.release_info) if self.download_subtitle(subtitle): subtitle.score = score downloaded_subtitles.append(subtitle) @@ -613,6 +602,7 @@ class SZAsyncProviderPool(SZProviderPool): to the number of :attr:`~ProviderPool.providers`. """ + def __init__(self, max_workers=None, *args, **kwargs): super(SZAsyncProviderPool, self).__init__(*args, **kwargs) @@ -761,7 +751,7 @@ def scan_video(path, dont_use_actual_file=False, hints=None, providers=None, ski logger.debug('GuessIt found: %s', json.dumps(guessed_result, cls=GuessitEncoder, indent=4, ensure_ascii=False)) video = Video.fromguess(path, guessed_result) - video.hints = hints # ? + video.hints = hints # ? if dont_use_actual_file and not hash_from: return video @@ -810,18 +800,14 @@ def scan_video(path, dont_use_actual_file=False, hints=None, providers=None, ski return video -def _search_external_subtitles(path, languages=None, only_one=False, scandir_generic=False, match_strictness="strict"): +def _search_external_subtitles(path, languages=None, only_one=False, match_strictness="strict"): dirpath, filename = os.path.split(path) dirpath = dirpath or '.' fn_no_ext, fileext = os.path.splitext(filename) - fn_no_ext_lower = fn_no_ext.lower() + fn_no_ext_lower = unicodedata.normalize('NFC', fn_no_ext.lower()) subtitles = {} - _scandir = _scandir_generic if scandir_generic else scandir - for entry in _scandir(dirpath): - if (not entry.name or entry.name in ('\x0c', '$', ',', '\x7f')) and not scandir_generic: - logger.debug('Could not determine the name of the file, retrying with scandir_generic') - return _search_external_subtitles(path, languages, only_one, True) + for entry in scandir(dirpath): if not entry.is_file(follow_symlinks=False): continue @@ -860,9 +846,11 @@ def _search_external_subtitles(path, languages=None, only_one=False, scandir_gen hi_tag = ["hi", "cc", "sdh"] hi = any(i for i in hi_tag if i in adv_tag) - #add simplified/traditional chinese detection - simplified_chinese = ["chs", "sc", "zhs", "hans","zh-hans", "gb", "简", "简中", "简体", "简体中文", "中英双语", "中日双语","中法双语","简体&英文"] - traditional_chinese = ["cht", "tc", "zht", "hant","zh-hant", "big5", "繁", "繁中", "繁体", "繁體","繁体中文", "繁體中文", "正體中文", "中英雙語", "中日雙語","中法雙語","繁体&英文"] + # add simplified/traditional chinese detection + simplified_chinese = ["chs", "sc", "zhs", "hans", "zh-hans", "gb", "简", "简中", "简体", "简体中文", "中英双语", + "中日双语", "中法双语", "简体&英文"] + traditional_chinese = ["cht", "tc", "zht", "hant", "zh-hant", "big5", "繁", "繁中", "繁体", "繁體", "繁体中文", + "繁體中文", "正體中文", "中英雙語", "中日雙語", "中法雙語", "繁体&英文"] p_root = p_root.replace('zh-TW', 'zht') # remove possible language code for matching @@ -884,11 +872,11 @@ def _search_external_subtitles(path, languages=None, only_one=False, scandir_gen try: language_code = p_root.rsplit(".", 1)[1].replace('_', '-') try: - language = Language.fromietf(language_code) + language = Language.fromietf(language_code) language.forced = forced language.hi = hi except (ValueError, LanguageReverseError): - #add simplified/traditional chinese detection + # add simplified/traditional chinese detection if any(ext in str(language_code) for ext in simplified_chinese): language = Language.fromietf('zh') language.forced = forced @@ -901,7 +889,7 @@ def _search_external_subtitles(path, languages=None, only_one=False, scandir_gen logger.error('Cannot parse language code %r', language_code) language_code = None except IndexError: - language_code = None + language_code = None if not language and not language_code and only_one: language = Language.rebuild(list(languages)[0], forced=forced, hi=hi) @@ -932,20 +920,15 @@ def search_external_subtitles(path, languages=None, only_one=False, match_strict logger.debug("external subs: scanning path %s", abspath) if os.path.isdir(os.path.dirname(abspath)): - try: - subtitles.update(_search_external_subtitles(abspath, languages=languages, - only_one=only_one, match_strictness=match_strictness)) - except OSError: - subtitles.update(_search_external_subtitles(abspath, languages=languages, - only_one=only_one, match_strictness=match_strictness, - scandir_generic=True)) + subtitles.update(_search_external_subtitles(abspath, languages=languages, only_one=only_one, + match_strictness=match_strictness)) logger.debug("external subs: found %s", subtitles) return subtitles def list_all_subtitles(videos, languages, **kwargs): """List all available subtitles. - + patch: remove video check, it has been done before The `videos` must pass the `languages` check of :func:`check_video`. @@ -1177,7 +1160,7 @@ def save_subtitles(file_path, subtitles, single=False, directory=None, chmod=Non def refine(video, episode_refiners=None, movie_refiners=None, **kwargs): """Refine a video using :ref:`refiners`. - + patch: add traceback logging .. note:: From 77531090b2c2c98d7cbb7f562222d04fcfc66c89 Mon Sep 17 00:00:00 2001 From: Vitiko Date: Sun, 28 May 2023 21:05:21 -0400 Subject: [PATCH 22/57] no log: reduce debug call verbosity --- libs/subliminal_patch/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/subliminal_patch/core.py b/libs/subliminal_patch/core.py index 1e293455b..aa3ec3051 100644 --- a/libs/subliminal_patch/core.py +++ b/libs/subliminal_patch/core.py @@ -168,12 +168,12 @@ class _LanguageEquals(list): for equals in self: from_, to_ = equals if from_ in items: - logger.debug("Adding %s to %s", to_, items) + logger.debug("Adding %s to %s item(s) set", to_, len(items)) to_add.append(to_) new_items = items.copy() new_items.update(to_add) - logger.debug("New set: %s", new_items) + logger.debug("New set: %s items", len(new_items)) return new_items def update_subtitle(self, subtitle): From 4725496313a53e47cda01a092e6829a282413e0b Mon Sep 17 00:00:00 2001 From: Vitiko Date: Sun, 28 May 2023 22:06:43 -0400 Subject: [PATCH 23/57] Language-equals: add compatibility for more providers Some providers directly uses language codes within their query implementations. This 'translator' method will take care of that. --- libs/subliminal_patch/core.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/libs/subliminal_patch/core.py b/libs/subliminal_patch/core.py index aa3ec3051..70e1004dc 100644 --- a/libs/subliminal_patch/core.py +++ b/libs/subliminal_patch/core.py @@ -155,6 +155,19 @@ class _LanguageEquals(list): if len(item) != 2 or not any(isinstance(i, Language) for i in item): raise ValueError(f"Not a valid equal tuple: {item}") + def translate(self, items: set): + translated = set() + + for equals in self: + from_, to_ = equals + if to_ in items: + logger.debug("Translating %s -> %s", to_, from_) + translated.add(from_) + else: + translated.add(to_) + + return translated or items + def check_set(self, items: set): """ Check a set of languages. For example, if the set is {Language('es')} and one of the equals of the instance is (Language('es'), Language('es', 'MX')), the set will now have @@ -343,7 +356,7 @@ class SZProviderPool(ProviderPool): logger.info('Listing subtitles with provider %r and languages %r', provider, provider_languages) results = [] try: - results = self[provider].list_subtitles(video, provider_languages) + results = self[provider].list_subtitles(video, self.lang_equals.translate(provider_languages)) seen = [] out = [] for s in results: From 2346f3ed580ac8c05d3971315f239694201364ed Mon Sep 17 00:00:00 2001 From: morpheus65535 Date: Mon, 29 May 2023 06:38:35 -0400 Subject: [PATCH 24/57] Added some exceptions that shouldn't be retried to retry function. #2153 --- libs/subliminal_patch/providers/mixins.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/subliminal_patch/providers/mixins.py b/libs/subliminal_patch/providers/mixins.py index 6fa47513a..b5885ada2 100644 --- a/libs/subliminal_patch/providers/mixins.py +++ b/libs/subliminal_patch/providers/mixins.py @@ -10,8 +10,7 @@ import os from six.moves.http_client import ResponseNotReady from guessit import guessit -from subliminal import ProviderError -from subliminal.exceptions import ServiceUnavailable, DownloadLimitExceeded +from subliminal.exceptions import ServiceUnavailable, DownloadLimitExceeded, ConfigurationError, AuthenticationError from subliminal.providers.opensubtitles import Unauthorized from subliminal.subtitle import fix_line_ending from subliminal_patch.exceptions import TooManyRequests @@ -45,7 +44,8 @@ class ProviderRetryMixin(object): while i <= amount: try: return f() - except (Unauthorized, ServiceUnavailable, TooManyRequests, DownloadLimitExceeded, ResponseNotReady): + except (Unauthorized, ServiceUnavailable, TooManyRequests, DownloadLimitExceeded, ResponseNotReady, + ConfigurationError, AuthenticationError): raise except exc: formatted_exc = traceback.format_exc() From f1cbfd4be537148985b580b29accc6a935a2c72b Mon Sep 17 00:00:00 2001 From: Vitiko Date: Tue, 30 May 2023 19:25:37 -0400 Subject: [PATCH 25/57] no log: add debug call to lang-equals --- libs/subliminal_patch/core.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/libs/subliminal_patch/core.py b/libs/subliminal_patch/core.py index 70e1004dc..8d6f54ff5 100644 --- a/libs/subliminal_patch/core.py +++ b/libs/subliminal_patch/core.py @@ -156,15 +156,16 @@ class _LanguageEquals(list): raise ValueError(f"Not a valid equal tuple: {item}") def translate(self, items: set): - translated = set() + translated = items.copy() for equals in self: from_, to_ = equals if to_ in items: logger.debug("Translating %s -> %s", to_, from_) translated.add(from_) - else: - translated.add(to_) + + if translated == items: + logger.debug("Nothing to translate found") return translated or items From ead8a3892793351fb504a1ec8fd33fa54301d8f6 Mon Sep 17 00:00:00 2001 From: morpheus65535 Date: Fri, 2 Jun 2023 20:47:55 -0400 Subject: [PATCH 26/57] Added support for Portuguese and Simplified Chinese to opensubtitles.com. #2159 --- .../providers/opensubtitlescom.py | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/libs/subliminal_patch/providers/opensubtitlescom.py b/libs/subliminal_patch/providers/opensubtitlescom.py index 7f2a9a1dc..2d735746c 100644 --- a/libs/subliminal_patch/providers/opensubtitlescom.py +++ b/libs/subliminal_patch/providers/opensubtitlescom.py @@ -50,6 +50,27 @@ def fix_movie_naming(title): }, True) +custom_languages = { + 'pt': 'pt-PT', + 'zh': 'zh-CN', +} + + +def to_opensubtitlescom(lang): + if lang in custom_languages.keys(): + return custom_languages[lang] + else: + return lang + + +def from_opensubtitlescom(lang): + from_custom_languages = {v: k for k, v in custom_languages.items()} + if lang in from_custom_languages.keys(): + return from_custom_languages[lang] + else: + return lang + + class OpenSubtitlesComSubtitle(Subtitle): provider_name = 'opensubtitlescom' hash_verifiable = False @@ -279,7 +300,7 @@ class OpenSubtitlesComProvider(ProviderRetryMixin, Provider): if not title_id: return [] - lang_strings = [str(lang.basename) for lang in languages] + lang_strings = [to_opensubtitlescom(lang.basename) for lang in languages] langs = ','.join(lang_strings) logging.debug(f'Searching for this languages: {lang_strings}') @@ -367,7 +388,7 @@ class OpenSubtitlesComProvider(ProviderRetryMixin, Provider): if len(item['attributes']['files']): subtitle = OpenSubtitlesComSubtitle( - language=Language.fromietf(item['attributes']['language']), + language=Language.fromietf(from_opensubtitlescom(item['attributes']['language'])), forced=item['attributes']['foreign_parts_only'], hearing_impaired=item['attributes']['hearing_impaired'], page_link=item['attributes']['url'], From 07f6666d4660b87d420eb1d8dbc2bb71cb5dcced Mon Sep 17 00:00:00 2001 From: LASER-Yi Date: Sat, 3 Jun 2023 22:01:34 +0800 Subject: [PATCH 27/57] no log: Update type definition of general.language_equals in frontend --- frontend/src/types/settings.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/types/settings.d.ts b/frontend/src/types/settings.d.ts index 8d94794d3..127386118 100644 --- a/frontend/src/types/settings.d.ts +++ b/frontend/src/types/settings.d.ts @@ -25,7 +25,7 @@ interface Settings { titlovi: Settings.Titlovi; ktuvit: Settings.Ktuvit; notifications: Settings.Notifications; - language_equals: string[][]; + language_equals: string[]; } declare namespace Settings { From 933a456b0375b62d65455537a2446c722663bda8 Mon Sep 17 00:00:00 2001 From: Liang Yi Date: Sun, 4 Jun 2023 23:03:59 +0800 Subject: [PATCH 28/57] Update all UI dependencies and update React version to 18 (#2127) --- frontend/package-lock.json | 2161 +++++++++-------- frontend/package.json | 53 +- frontend/src/App/Header.tsx | 2 +- frontend/src/App/Navbar.tsx | 8 +- frontend/src/App/index.tsx | 2 +- frontend/src/App/theme.tsx | 10 +- frontend/src/Router/index.tsx | 18 +- frontend/src/apis/queries/hooks.ts | 2 +- frontend/src/components/ErrorBoundary.tsx | 4 +- frontend/src/components/async/Lazy.tsx | 4 +- .../src/components/forms/MovieUploadForm.tsx | 4 +- frontend/src/components/index.tsx | 2 +- .../src/components/inputs/DropContent.tsx | 2 +- .../src/components/modals/HistoryModal.tsx | 2 +- .../components/modals/ManualSearchModal.tsx | 2 +- .../components/modals/SubtitleToolsModal.tsx | 6 +- .../src/components/tables/PageControl.tsx | 2 +- frontend/src/components/tables/PageTable.tsx | 2 +- .../tables/plugins/useCustomSelection.tsx | 2 +- frontend/src/components/toolbox/index.tsx | 4 +- frontend/src/dom.tsx | 31 +- .../src/modules/modals/ModalsProvider.tsx | 10 +- frontend/src/modules/modals/hooks.ts | 7 +- frontend/src/modules/modals/index.ts | 2 +- frontend/src/modules/socketio/index.ts | 2 +- frontend/src/pages/Episodes/index.tsx | 4 +- frontend/src/pages/Episodes/table.tsx | 4 +- .../src/pages/History/Statistics/index.tsx | 2 +- frontend/src/pages/Movies/Details/index.tsx | 4 +- frontend/src/pages/Movies/index.tsx | 22 +- .../src/pages/Settings/Languages/table.tsx | 42 +- .../pages/Settings/Providers/components.tsx | 6 +- .../src/pages/Settings/components/Message.tsx | 13 +- .../src/pages/Settings/components/Section.tsx | 6 +- .../pages/Settings/components/collapse.tsx | 6 +- .../pages/Settings/components/forms.test.tsx | 4 +- .../src/pages/Settings/components/forms.tsx | 17 +- .../src/pages/Settings/components/index.tsx | 8 +- .../Settings/utilities/SettingsProvider.tsx | 11 +- frontend/src/pages/System/Status/index.tsx | 19 +- frontend/src/pages/Wanted/Movies/index.tsx | 2 +- frontend/src/pages/Wanted/Series/index.tsx | 2 +- frontend/src/pages/views/ItemOverview.tsx | 6 +- frontend/src/providers.tsx | 24 +- frontend/src/tests/index.tsx | 28 +- frontend/src/utilities/routers.ts | 36 +- 46 files changed, 1364 insertions(+), 1246 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index edfa33e2f..e722ba67a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,17 +9,17 @@ "version": "1.0.0", "license": "GPL-3", "dependencies": { - "@mantine/core": "^5.6.0", - "@mantine/dropzone": "^5.6.0", - "@mantine/form": "^5.6.0", - "@mantine/hooks": "^5.6.0", - "@mantine/modals": "^5.6.0", - "@mantine/notifications": "^5.6.0", - "axios": "^0.27.2", - "react": "^17.0.2", - "react-dom": "^17.0.2", + "@mantine/core": "^6.0.0", + "@mantine/dropzone": "^6.0.0", + "@mantine/form": "^6.0.0", + "@mantine/hooks": "^6.0.0", + "@mantine/modals": "^6.0.0", + "@mantine/notifications": "^6.0.0", + "axios": "^1.3.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", "react-query": "^3.39.2", - "react-router-dom": "~6.3.0", + "react-router-dom": "~6.10.0", "socket.io-client": "^4.5.3" }, "devDependencies": { @@ -30,36 +30,35 @@ "@fortawesome/free-solid-svg-icons": "^6.2.0", "@fortawesome/react-fontawesome": "^0.2.0", "@testing-library/jest-dom": "^5.16.5", - "@testing-library/react": "^12.1.0", - "@testing-library/react-hooks": "^8.0.1", + "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.4.3", "@types/lodash": "^4.14.0", - "@types/node": "^18.11.7", - "@types/react": "^17.0.0", - "@types/react-dom": "^17.0.0", + "@types/node": "^18.16.0", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", "@types/react-table": "^7.7.0", - "@vitejs/plugin-react": "^2.2.0", - "@vitest/coverage-c8": "^0.28.5", - "@vitest/ui": "^0.29.1", + "@vitejs/plugin-react": "^4.0.0", + "@vitest/coverage-c8": "^0.30.0", + "@vitest/ui": "^0.30.0", "clsx": "^1.2.0", - "eslint": "^8.26.0", + "eslint": "^8.39.0", "eslint-config-react-app": "^7.0.1", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-testing-library": "^5.9.0", "husky": "^8.0.2", - "jsdom": "^20.0.1", + "jsdom": "^21.0.0", "lodash": "^4.17.0", "moment": "^2.29", - "prettier": "^2.7.0", + "prettier": "^2.8.0", "prettier-plugin-organize-imports": "^3.1.0", "pretty-quick": "^3.1.0", "react-table": "^7.8.0", - "recharts": "~2.1.0", - "sass": "^1.55.0", - "typescript": "^4", - "vite": "^3.2.1", + "recharts": "~2.5.0", + "sass": "^1.62.0", + "typescript": "^5", + "vite": "^4.3.0", "vite-plugin-checker": "^0.5.5", - "vitest": "^0.25.0" + "vitest": "^0.30.1" } }, "node_modules/@adobe/css-tools": { @@ -2133,9 +2132,9 @@ "peer": true }, "node_modules/@esbuild/android-arm": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", - "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.18.tgz", + "integrity": "sha512-EmwL+vUBZJ7mhFCs5lA4ZimpUH3WMAoqvOIYhVQwdIgSpHC8ImHdsRyhHAVxpDYUSm0lWvd63z0XH1IlImS2Qw==", "cpu": [ "arm" ], @@ -2148,10 +2147,154 @@ "node": ">=12" } }, + "node_modules/@esbuild/android-arm64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.18.tgz", + "integrity": "sha512-/iq0aK0eeHgSC3z55ucMAHO05OIqmQehiGay8eP5l/5l+iEr4EIbh4/MI8xD9qRFjqzgkc0JkX0LculNC9mXBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.18.tgz", + "integrity": "sha512-x+0efYNBF3NPW2Xc5bFOSFW7tTXdAcpfEg2nXmxegm4mJuVeS+i109m/7HMiOQ6M12aVGGFlqJX3RhNdYM2lWg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.18.tgz", + "integrity": "sha512-6tY+djEAdF48M1ONWnQb1C+6LiXrKjmqjzPNPWXhu/GzOHTHX2nh8Mo2ZAmBFg0kIodHhciEgUBtcYCAIjGbjQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.18.tgz", + "integrity": "sha512-Qq84ykvLvya3dO49wVC9FFCNUfSrQJLbxhoQk/TE1r6MjHo3sFF2tlJCwMjhkBVq3/ahUisj7+EpRSz0/+8+9A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.18.tgz", + "integrity": "sha512-fw/ZfxfAzuHfaQeMDhbzxp9mc+mHn1Y94VDHFHjGvt2Uxl10mT4CDavHm+/L9KG441t1QdABqkVYwakMUeyLRA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.18.tgz", + "integrity": "sha512-FQFbRtTaEi8ZBi/A6kxOC0V0E9B/97vPdYjY9NdawyLd4Qk5VD5g2pbWN2VR1c0xhzcJm74HWpObPszWC+qTew==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.18.tgz", + "integrity": "sha512-jW+UCM40LzHcouIaqv3e/oRs0JM76JfhHjCavPxMUti7VAPh8CaGSlS7cmyrdpzSk7A+8f0hiedHqr/LMnfijg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.18.tgz", + "integrity": "sha512-R7pZvQZFOY2sxUG8P6A21eq6q+eBv7JPQYIybHVf1XkQYC+lT7nDBdC7wWKTrbvMXKRaGudp/dzZCwL/863mZQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.18.tgz", + "integrity": "sha512-ygIMc3I7wxgXIxk6j3V00VlABIjq260i967Cp9BNAk5pOOpIXmd1RFQJQX9Io7KRsthDrQYrtcx7QCof4o3ZoQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/linux-loong64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", - "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.18.tgz", + "integrity": "sha512-bvPG+MyFs5ZlwYclCG1D744oHk1Pv7j8psF5TfYx7otCVmcJsEXgFEhQkbhNW8otDHL1a2KDINW20cfCgnzgMQ==", "cpu": [ "loong64" ], @@ -2164,6 +2307,182 @@ "node": ">=12" } }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.18.tgz", + "integrity": "sha512-oVqckATOAGuiUOa6wr8TXaVPSa+6IwVJrGidmNZS1cZVx0HqkTMkqFGD2HIx9H1RvOwFeWYdaYbdY6B89KUMxA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.18.tgz", + "integrity": "sha512-3dLlQO+b/LnQNxgH4l9rqa2/IwRJVN9u/bK63FhOPB4xqiRqlQAU0qDU3JJuf0BmaH0yytTBdoSBHrb2jqc5qQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.18.tgz", + "integrity": "sha512-/x7leOyDPjZV3TcsdfrSI107zItVnsX1q2nho7hbbQoKnmoeUWjs+08rKKt4AUXju7+3aRZSsKrJtaRmsdL1xA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.18.tgz", + "integrity": "sha512-cX0I8Q9xQkL/6F5zWdYmVf5JSQt+ZfZD2bJudZrWD+4mnUvoZ3TDDXtDX2mUaq6upMFv9FlfIh4Gfun0tbGzuw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.18.tgz", + "integrity": "sha512-66RmRsPlYy4jFl0vG80GcNRdirx4nVWAzJmXkevgphP1qf4dsLQCpSKGM3DUQCojwU1hnepI63gNZdrr02wHUA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.18.tgz", + "integrity": "sha512-95IRY7mI2yrkLlTLb1gpDxdC5WLC5mZDi+kA9dmM5XAGxCME0F8i4bYH4jZreaJ6lIZ0B8hTrweqG1fUyW7jbg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.18.tgz", + "integrity": "sha512-WevVOgcng+8hSZ4Q3BKL3n1xTv5H6Nb53cBrtzzEjDbbnOmucEVcZeGCsCOi9bAOcDYEeBZbD2SJNBxlfP3qiA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.18.tgz", + "integrity": "sha512-Rzf4QfQagnwhQXVBS3BYUlxmEbcV7MY+BH5vfDZekU5eYpcffHSyjU8T0xucKVuOcdCsMo+Ur5wmgQJH2GfNrg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.18.tgz", + "integrity": "sha512-Kb3Ko/KKaWhjeAm2YoT/cNZaHaD1Yk/pa3FTsmqo9uFh1D1Rfco7BBLIPdDOozrObj2sahslFuAQGvWbgWldAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.18.tgz", + "integrity": "sha512-0/xUMIdkVHwkvxfbd5+lfG7mHOf2FRrxNbPiKWg9C4fFrB8H0guClmaM3BFiRUYrznVoyxTIyC/Ou2B7QQSwmw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.18.tgz", + "integrity": "sha512-qU25Ma1I3NqTSHJUOKi9sAH1/Mzuvlke0ioMJRthLXKm7JiSKVwFghlGbDLOO2sARECGhja4xYfRAZNPAkooYg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -2239,9 +2558,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.38.0.tgz", - "integrity": "sha512-IoD2MfUnOV58ghIHCiil01PcohxjbYR/qCxsoC+xNgUwh1EY8jOOrYmu3d3a71+tJJ23uscEV4X2HJWMsPJu4g==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz", + "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2505,41 +2824,42 @@ "dev": true }, "node_modules/@mantine/core": { - "version": "5.10.5", - "resolved": "https://registry.npmjs.org/@mantine/core/-/core-5.10.5.tgz", - "integrity": "sha512-F4tqHSEVM9D6/iSqHfPda+Xl5XgSEPHAAkT01Zwzj4Jnbd10qGrlqr/SFUop2CIcuKYnmra9XltUahUPXBC2BQ==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@mantine/core/-/core-6.0.9.tgz", + "integrity": "sha512-v0g59ajqFOcSlXO3bNpRaZg3VSX3hVAJ/MyaAvzdMYtTiN0ogaBJH3RXQscxr7Us2ega4psG1rW7ybUP7WrZKg==", "dependencies": { "@floating-ui/react": "^0.19.1", - "@mantine/styles": "5.10.5", - "@mantine/utils": "5.10.5", + "@mantine/styles": "6.0.9", + "@mantine/utils": "6.0.9", "@radix-ui/react-scroll-area": "1.0.2", + "react-remove-scroll": "^2.5.5", "react-textarea-autosize": "8.3.4" }, "peerDependencies": { - "@mantine/hooks": "5.10.5", + "@mantine/hooks": "6.0.9", "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "node_modules/@mantine/dropzone": { - "version": "5.10.5", - "resolved": "https://registry.npmjs.org/@mantine/dropzone/-/dropzone-5.10.5.tgz", - "integrity": "sha512-tBPBuQvlvesEApECTfmYFQXbS26sAQo8VaYIebTqBy9VIUoVAM9VCKHBLqa3KMKtq+/HjKCJpaa8+Cjn9riqqQ==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@mantine/dropzone/-/dropzone-6.0.9.tgz", + "integrity": "sha512-hJxoFSDUvhMI8Bma4b0DLL7Y+aYzxZNioOni8+cSOg9+5FGU0UxM6JZ6JUXP6dTKvpdAzwhZdT7no5EeWQo26Q==", "dependencies": { - "@mantine/utils": "5.10.5", + "@mantine/utils": "6.0.9", "react-dropzone": "14.2.3" }, "peerDependencies": { - "@mantine/core": "5.10.5", - "@mantine/hooks": "5.10.5", + "@mantine/core": "6.0.9", + "@mantine/hooks": "6.0.9", "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "node_modules/@mantine/form": { - "version": "5.10.5", - "resolved": "https://registry.npmjs.org/@mantine/form/-/form-5.10.5.tgz", - "integrity": "sha512-0ENh7W/mwbOnIjJlRUoUTZjOtEi7/qoF86k67CVjKqagsA+Wy6cuBF1spPcllMzujepprtqKACb3rVLR+Pxj7Q==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@mantine/form/-/form-6.0.9.tgz", + "integrity": "sha512-JB6zzSPDNUH9yKIORplqUxP063y2jUfLUzH3kty7UaEt2YHMLDU8ttAF77uF3namTdp0kDsiyp/VYJtUidaPyQ==", "dependencies": { "fast-deep-equal": "^3.1.3", "klona": "^2.0.5" @@ -2549,46 +2869,46 @@ } }, "node_modules/@mantine/hooks": { - "version": "5.10.5", - "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-5.10.5.tgz", - "integrity": "sha512-hFQp71QZDfivPzfIUOQZfMKLiOL/Cn2EnzacRlbUr55myteTfzYN8YMt+nzniE/6c4IRopFHEAdbKEtfyQc6kg==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-6.0.9.tgz", + "integrity": "sha512-01FvXnXed0Hni4WvmtTs0HMulyd7sibEG52uS+oy4ziYumKb8eMMTnvs4Ov7meKAvtA9/pmW+Yif1pm2GB4bnw==", "peerDependencies": { "react": ">=16.8.0" } }, "node_modules/@mantine/modals": { - "version": "5.10.5", - "resolved": "https://registry.npmjs.org/@mantine/modals/-/modals-5.10.5.tgz", - "integrity": "sha512-q3BCqAxulcIZCL48vUrwSaXDhxDng/2daVky8K1mTPYNlcm9iN1mqVTUC4uFWhn4b2UmPu4UdbNePEgLuhK4Mw==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@mantine/modals/-/modals-6.0.9.tgz", + "integrity": "sha512-azdb++XlXheM1Ai4XPBk5d9fJOGv/PnLg4KciZZripkPZ5+xXrBwBxmCXlMogNYNfp6o+K5Q8BaiO16UbDvCQQ==", "dependencies": { - "@mantine/utils": "5.10.5" + "@mantine/utils": "6.0.9" }, "peerDependencies": { - "@mantine/core": "5.10.5", - "@mantine/hooks": "5.10.5", + "@mantine/core": "6.0.9", + "@mantine/hooks": "6.0.9", "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "node_modules/@mantine/notifications": { - "version": "5.10.5", - "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-5.10.5.tgz", - "integrity": "sha512-IzTAXA7Zb9DcI94Mv5O2OinhLmI7fvs/VutDw9uCpp6OHtLuF/XN1d262jrsGhMZT0c4nuUsotSLFZF3GWZwXg==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-6.0.9.tgz", + "integrity": "sha512-X0Nyv9Fe9/1wrjxkyGTCE5VlOfzCbQjVWJ9uCWshqGkDSs8HTaWWZ9FM8fUeIXtI/LVB65OwPBegKw4/POgZQQ==", "dependencies": { - "@mantine/utils": "5.10.5", + "@mantine/utils": "6.0.9", "react-transition-group": "4.4.2" }, "peerDependencies": { - "@mantine/core": "5.10.5", - "@mantine/hooks": "5.10.5", + "@mantine/core": "6.0.9", + "@mantine/hooks": "6.0.9", "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "node_modules/@mantine/styles": { - "version": "5.10.5", - "resolved": "https://registry.npmjs.org/@mantine/styles/-/styles-5.10.5.tgz", - "integrity": "sha512-0NXk8c/XGzuTUkZc6KceF2NaTCMEu5mHR4ru0x+ttb9DGnLpHuGWduTHjSfr4hl6eAJgedD0zauO+VAhDzO9zA==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@mantine/styles/-/styles-6.0.9.tgz", + "integrity": "sha512-jTtSicZalxw5fHD4p2c5RJdsb1DlBTKFwMmPWSTt5IgQ3ADucQtQWl2sVtVCpR/b5w9/Ihzgju84bPmOlAWh0g==", "dependencies": { "clsx": "1.1.1", "csstype": "3.0.9" @@ -2613,9 +2933,9 @@ "integrity": "sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==" }, "node_modules/@mantine/utils": { - "version": "5.10.5", - "resolved": "https://registry.npmjs.org/@mantine/utils/-/utils-5.10.5.tgz", - "integrity": "sha512-FGMq4dGs5HhDAtI0z46uzxzKKPmZ3h5uKUyKg1ZHoFR1mBtcUMbB6FylFmHqKFRWlJ5IXqX9dwmiVrLYUOfTmA==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@mantine/utils/-/utils-6.0.9.tgz", + "integrity": "sha512-zEDZixO8dw2ZSNZiQ1g7S+Bq2q4E8ps6bvpyZsxpiXBdh3g2H0WOEdJdDpBTiTRPWotSlU1kaKN3tOplCRDQ2Q==", "peerDependencies": { "react": ">=16.8.0" } @@ -2823,6 +3143,14 @@ "react": "^16.8 || ^17.0 || ^18.0" } }, + "node_modules/@remix-run/router": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.5.0.tgz", + "integrity": "sha512-bkUDCp8o1MvFO+qxkODcbhSqRa6P2GXgrGZVpt0dCXNW2HCSCqYI0ZoAqEOSAjRWmmlKcYgFvN4B4S+zo/f8kg==", + "engines": { + "node": ">=14" + } + }, "node_modules/@rushstack/eslint-patch": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz", @@ -2845,7 +3173,6 @@ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.2.0.tgz", "integrity": "sha512-xTEnpUKiV/bMyEsE5bT4oYA0x0Z/colMtxzUY8bKyPXBNLn/e0V4ZjBZkEhms0xE4pv9QsPfSRu9AWS4y5wGvA==", "dev": true, - "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -2896,70 +3223,21 @@ } }, "node_modules/@testing-library/react": { - "version": "12.1.5", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-12.1.5.tgz", - "integrity": "sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.0.0.tgz", + "integrity": "sha512-S04gSNJbYE30TlIMLTzv6QCTzt9AqIF5y6s6SzVFILNcNvbV/jU96GeiTPillGQo+Ny64M/5PV7klNYYgv5Dfg==", "dev": true, "dependencies": { "@babel/runtime": "^7.12.5", - "@testing-library/dom": "^8.0.0", - "@types/react-dom": "<18.0.0" + "@testing-library/dom": "^9.0.0", + "@types/react-dom": "^18.0.0" }, "engines": { - "node": ">=12" + "node": ">=14" }, "peerDependencies": { - "react": "<18.0.0", - "react-dom": "<18.0.0" - } - }, - "node_modules/@testing-library/react-hooks": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz", - "integrity": "sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.12.5", - "react-error-boundary": "^3.1.0" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "@types/react": "^16.9.0 || ^17.0.0", - "react": "^16.9.0 || ^17.0.0", - "react-dom": "^16.9.0 || ^17.0.0", - "react-test-renderer": "^16.9.0 || ^17.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "react-test-renderer": { - "optional": true - } - } - }, - "node_modules/@testing-library/react/node_modules/@testing-library/dom": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.0.tgz", - "integrity": "sha512-d9ULIT+a4EXLX3UU8FBjauG9NnsZHkHztXoIcTsOKoOw030fyjheN9svkTULjJxtYag9DZz5Jz5qkWZDPxTFwA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "^5.0.0", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.4.4", - "pretty-format": "^27.0.2" - }, - "engines": { - "node": ">=12" + "react": "^18.0.0", + "react-dom": "^18.0.0" } }, "node_modules/@testing-library/user-event": { @@ -3005,49 +3283,67 @@ "@types/chai": "*" } }, + "node_modules/@types/d3-array": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.4.tgz", + "integrity": "sha512-nwvEkG9vYOc0Ic7G7kwgviY4AQlTfYGIZ0fqB7CQHXGyYM6nO7kJh5EguSNA3jfh4rq7Sb7eMVq8isuvg2/miQ==", + "dev": true + }, "node_modules/@types/d3-color": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-2.0.3.tgz", - "integrity": "sha512-+0EtEjBfKEDtH9Rk3u3kLOUXM5F+iZK+WvASPb0MhIZl8J8NUvGeZRwKCXl+P3HkYx5TdU4YtcibpqHkSR9n7w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==", + "dev": true + }, + "node_modules/@types/d3-ease": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.0.tgz", + "integrity": "sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==", "dev": true }, "node_modules/@types/d3-interpolate": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-2.0.2.tgz", - "integrity": "sha512-lElyqlUfIPyWG/cD475vl6msPL4aMU7eJvx1//Q177L8mdXoVPFl1djIESF2FKnc0NyaHvQlJpWwKJYwAhUoCw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==", "dev": true, "dependencies": { - "@types/d3-color": "^2" + "@types/d3-color": "*" } }, "node_modules/@types/d3-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-2.0.2.tgz", - "integrity": "sha512-3YHpvDw9LzONaJzejXLOwZ3LqwwkoXb9LI2YN7Hbd6pkGo5nIlJ09ul4bQhBN4hQZJKmUpX8HkVqbzgUKY48cg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.0.tgz", + "integrity": "sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==", "dev": true }, "node_modules/@types/d3-scale": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-3.3.2.tgz", - "integrity": "sha512-gGqr7x1ost9px3FvIfUMi5XA/F/yAf4UkUDtdQhpH92XCT0Oa7zkkRzY61gPVJq+DxpHn/btouw5ohWkbBsCzQ==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.3.tgz", + "integrity": "sha512-PATBiMCpvHJSMtZAMEhc2WyL+hnzarKzI6wAHYjhsonjWJYGq5BXTzQjv4l8m2jO183/4wZ90rKvSeT7o72xNQ==", "dev": true, "dependencies": { - "@types/d3-time": "^2" + "@types/d3-time": "*" } }, "node_modules/@types/d3-shape": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-2.1.3.tgz", - "integrity": "sha512-HAhCel3wP93kh4/rq+7atLdybcESZ5bRHDEZUojClyZWsRuEMo3A52NGYJSh48SxfxEU6RZIVbZL2YFZ2OAlzQ==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.1.tgz", + "integrity": "sha512-6Uh86YFF7LGg4PQkuO2oG6EMBRLuW9cbavUW46zkIO5kuS2PfTqo2o9SkgtQzguBHbLgNnU90UNsITpsX1My+A==", "dev": true, "dependencies": { - "@types/d3-path": "^2" + "@types/d3-path": "*" } }, "node_modules/@types/d3-time": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-2.1.1.tgz", - "integrity": "sha512-9MVYlmIgmRR31C5b4FVSWtuMmBHh2mOWQYfl7XAYOa8dsnb7iEmUmRSWSFgXFtkjxO65d7hTUHQC+RhR/9IWFg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz", + "integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==", + "dev": true + }, + "node_modules/@types/d3-timer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.0.tgz", + "integrity": "sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==", "dev": true }, "node_modules/@types/istanbul-lib-coverage": { @@ -3075,9 +3371,9 @@ } }, "node_modules/@types/jest": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.0.tgz", - "integrity": "sha512-3Emr5VOl/aoBwnWcH/EFQvlSAmjV+XtV9GGu5mwdYew5vhQh0IUZx/60x0TzHDu09Bi7HMx10t/namdJw5QIcg==", + "version": "29.5.1", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.1.tgz", + "integrity": "sha512-tEuVcHrpaixS36w7hpsfLBLpjtMRJUE09/MHXn923LOVojDwyC14cWcfc0rDs0VEfUyYmt/+iX1kxxp+gZMcaQ==", "dev": true, "dependencies": { "expect": "^29.0.0", @@ -3141,9 +3437,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.15.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz", - "integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==", + "version": "18.16.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.1.tgz", + "integrity": "sha512-DZxSZWXxFfOlx7k7Rv4LAyiMroaxa3Ly/7OOzZO8cBNho0YzAi4qlbrx8W27JGqG57IgR/6J7r+nOJWw6kcvZA==", "dev": true }, "node_modules/@types/parse-json": { @@ -3155,13 +3451,13 @@ "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", - "dev": true + "devOptional": true }, "node_modules/@types/react": { - "version": "17.0.58", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.58.tgz", - "integrity": "sha512-c1GzVY97P0fGxwGxhYq989j4XwlcHQoto6wQISOC2v6wm3h0PORRWJFHlkRjfGsiG3y1609WdQ+J+tKxvrEd6A==", - "dev": true, + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.0.tgz", + "integrity": "sha512-0FLj93y5USLHdnhIhABk83rm8XEGA7kH3cr+YUlvxoUGp1xNt/DINUMvqPxLyOQMzLmZe8i4RTHbvb8MC7NmrA==", + "devOptional": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -3169,12 +3465,12 @@ } }, "node_modules/@types/react-dom": { - "version": "17.0.19", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.19.tgz", - "integrity": "sha512-PiYG40pnQRdPHnlf7tZnp0aQ6q9tspYr72vD61saO6zFCybLfMqwUCN0va1/P+86DXn18ZWeW30Bk7xlC5eEAQ==", + "version": "18.2.1", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.1.tgz", + "integrity": "sha512-8QZEV9+Kwy7tXFmjJrp3XUKQSs9LTnE0KnoUb0YCguWBiNW0Yfb2iBMYZ08WPg35IR6P3Z0s00B15SwZnO26+w==", "dev": true, "dependencies": { - "@types/react": "^17" + "@types/react": "*" } }, "node_modules/@types/react-table": { @@ -3190,7 +3486,7 @@ "version": "0.16.3", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==", - "dev": true + "devOptional": true }, "node_modules/@types/semver": { "version": "7.3.13", @@ -3229,15 +3525,15 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.0.tgz", - "integrity": "sha512-p0QgrEyrxAWBecR56gyn3wkG15TJdI//eetInP3zYRewDh0XS+DhB3VUAd3QqvziFsfaQIoIuZMxZRB7vXYaYw==", + "version": "5.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.1.tgz", + "integrity": "sha512-AVi0uazY5quFB9hlp2Xv+ogpfpk77xzsgsIEWyVS7uK/c7MZ5tw7ZPbapa0SbfkqE0fsAMkz5UwtgMLVk2BQAg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.59.0", - "@typescript-eslint/type-utils": "5.59.0", - "@typescript-eslint/utils": "5.59.0", + "@typescript-eslint/scope-manager": "5.59.1", + "@typescript-eslint/type-utils": "5.59.1", + "@typescript-eslint/utils": "5.59.1", "debug": "^4.3.4", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", @@ -3296,12 +3592,12 @@ "dev": true }, "node_modules/@typescript-eslint/experimental-utils": { - "version": "5.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.59.0.tgz", - "integrity": "sha512-evvdzcPrUv9+Hj+KX6fa3WMrtTZ7onnGHL3NfT/zN9q2FQhb2yvNJDa+w/ND0TpdRCbulwag0dxwMUt2MJB2Vg==", + "version": "5.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.59.1.tgz", + "integrity": "sha512-KVtKcHEizCIRx//LC9tBi6xp94ULKbU5StVHBVWURJQOVa2qw6HP28Hu7LmHrQM3p9I3q5Y2VR4wKllCJ3IWrw==", "dev": true, "dependencies": { - "@typescript-eslint/utils": "5.59.0" + "@typescript-eslint/utils": "5.59.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3315,14 +3611,14 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.0.tgz", - "integrity": "sha512-qK9TZ70eJtjojSUMrrEwA9ZDQ4N0e/AuoOIgXuNBorXYcBDk397D2r5MIe1B3cok/oCtdNC5j+lUUpVB+Dpb+w==", + "version": "5.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.1.tgz", + "integrity": "sha512-nzjFAN8WEu6yPRDizIFyzAfgK7nybPodMNFGNH0M9tei2gYnYszRDqVA0xlnRjkl7Hkx2vYrEdb6fP2a21cG1g==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.59.0", - "@typescript-eslint/types": "5.59.0", - "@typescript-eslint/typescript-estree": "5.59.0", + "@typescript-eslint/scope-manager": "5.59.1", + "@typescript-eslint/types": "5.59.1", + "@typescript-eslint/typescript-estree": "5.59.1", "debug": "^4.3.4" }, "engines": { @@ -3342,13 +3638,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.0.tgz", - "integrity": "sha512-tsoldKaMh7izN6BvkK6zRMINj4Z2d6gGhO2UsI8zGZY3XhLq1DndP3Ycjhi1JwdwPRwtLMW4EFPgpuKhbCGOvQ==", + "version": "5.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.1.tgz", + "integrity": "sha512-mau0waO5frJctPuAzcxiNWqJR5Z8V0190FTSqRw1Q4Euop6+zTwHAf8YIXNwDOT29tyUDrQ65jSg9aTU/H0omA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.0", - "@typescript-eslint/visitor-keys": "5.59.0" + "@typescript-eslint/types": "5.59.1", + "@typescript-eslint/visitor-keys": "5.59.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3359,13 +3655,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.0.tgz", - "integrity": "sha512-d/B6VSWnZwu70kcKQSCqjcXpVH+7ABKH8P1KNn4K7j5PXXuycZTPXF44Nui0TEm6rbWGi8kc78xRgOC4n7xFgA==", + "version": "5.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.1.tgz", + "integrity": "sha512-ZMWQ+Oh82jWqWzvM3xU+9y5U7MEMVv6GLioM3R5NJk6uvP47kZ7YvlgSHJ7ERD6bOY7Q4uxWm25c76HKEwIjZw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.59.0", - "@typescript-eslint/utils": "5.59.0", + "@typescript-eslint/typescript-estree": "5.59.1", + "@typescript-eslint/utils": "5.59.1", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -3386,9 +3682,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.0.tgz", - "integrity": "sha512-yR2h1NotF23xFFYKHZs17QJnB51J/s+ud4PYU4MqdZbzeNxpgUr05+dNeCN/bb6raslHvGdd6BFCkVhpPk/ZeA==", + "version": "5.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.1.tgz", + "integrity": "sha512-dg0ICB+RZwHlysIy/Dh1SP+gnXNzwd/KS0JprD3Lmgmdq+dJAJnUPe1gNG34p0U19HvRlGX733d/KqscrGC1Pg==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3399,13 +3695,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.0.tgz", - "integrity": "sha512-sUNnktjmI8DyGzPdZ8dRwW741zopGxltGs/SAPgGL/AAgDpiLsCFLcMNSpbfXfmnNeHmK9h3wGmCkGRGAoUZAg==", + "version": "5.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.1.tgz", + "integrity": "sha512-lYLBBOCsFltFy7XVqzX0Ju+Lh3WPIAWxYpmH/Q7ZoqzbscLiCW00LeYCdsUnnfnj29/s1WovXKh2gwCoinHNGA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.0", - "@typescript-eslint/visitor-keys": "5.59.0", + "@typescript-eslint/types": "5.59.1", + "@typescript-eslint/visitor-keys": "5.59.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -3459,17 +3755,17 @@ "dev": true }, "node_modules/@typescript-eslint/utils": { - "version": "5.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.0.tgz", - "integrity": "sha512-GGLFd+86drlHSvPgN/el6dRQNYYGOvRSDVydsUaQluwIW3HvbXuxyuD5JETvBt/9qGYe+lOrDk6gRrWOHb/FvA==", + "version": "5.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.1.tgz", + "integrity": "sha512-MkTe7FE+K1/GxZkP5gRj3rCztg45bEhsd8HYjczBuYm+qFHP5vtZmjx3B0yUCDotceQ4sHgTyz60Ycl225njmA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.59.0", - "@typescript-eslint/types": "5.59.0", - "@typescript-eslint/typescript-estree": "5.59.0", + "@typescript-eslint/scope-manager": "5.59.1", + "@typescript-eslint/types": "5.59.1", + "@typescript-eslint/typescript-estree": "5.59.1", "eslint-scope": "^5.1.1", "semver": "^7.3.7" }, @@ -3540,12 +3836,12 @@ "dev": true }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.0.tgz", - "integrity": "sha512-qZ3iXxQhanchCeaExlKPV3gDQFxMUmU35xfd5eCXB6+kUw1TUAbIy2n7QIrwz9s98DQLzNWyHp61fY0da4ZcbA==", + "version": "5.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.1.tgz", + "integrity": "sha512-6waEYwBTCWryx0VJmP7JaM4FpipLsFl9CvYf2foAE8Qh/Y0s+bxWysciwOs0LTBED4JCaNxTZ5rGadB14M6dwA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.0", + "@typescript-eslint/types": "5.59.1", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -3557,133 +3853,59 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-2.2.0.tgz", - "integrity": "sha512-FFpefhvExd1toVRlokZgxgy2JtnBOdp4ZDsq7ldCWaqGSGn9UhWMAVm/1lxPL14JfNS5yGz+s9yFrQY6shoStA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.0.0.tgz", + "integrity": "sha512-HX0XzMjL3hhOYm+0s95pb0Z7F8O81G7joUHgfDd/9J/ZZf5k4xX6QAMFkKsHFxaHlf6X7GD7+XuaZ66ULiJuhQ==", "dev": true, "dependencies": { - "@babel/core": "^7.19.6", - "@babel/plugin-transform-react-jsx": "^7.19.0", - "@babel/plugin-transform-react-jsx-development": "^7.18.6", - "@babel/plugin-transform-react-jsx-self": "^7.18.6", + "@babel/core": "^7.21.4", + "@babel/plugin-transform-react-jsx-self": "^7.21.0", "@babel/plugin-transform-react-jsx-source": "^7.19.6", - "magic-string": "^0.26.7", "react-refresh": "^0.14.0" }, "engines": { "node": "^14.18.0 || >=16.0.0" }, "peerDependencies": { - "vite": "^3.0.0" + "vite": "^4.2.0" } }, "node_modules/@vitest/coverage-c8": { - "version": "0.28.5", - "resolved": "https://registry.npmjs.org/@vitest/coverage-c8/-/coverage-c8-0.28.5.tgz", - "integrity": "sha512-zCNyurjudoG0BAqAgknvlBhkV2V9ZwyYLWOAGtHSDhL/St49MJT+V2p1G0yPaoqBbKOTATVnP5H2p1XL15H75g==", + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@vitest/coverage-c8/-/coverage-c8-0.30.1.tgz", + "integrity": "sha512-/Wa3dtSuckpdngAmiCwowaEXXgJkqPrtfvrs9HTB9QoEfNbZWPu4E4cjEn4lJZb4qcGf4fxFtUA2f9DnDNAzBA==", "dev": true, "dependencies": { - "c8": "^7.12.0", + "c8": "^7.13.0", "picocolors": "^1.0.0", - "std-env": "^3.3.1", - "vitest": "0.28.5" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vitest/coverage-c8/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@vitest/coverage-c8/node_modules/vitest": { - "version": "0.28.5", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.28.5.tgz", - "integrity": "sha512-pyCQ+wcAOX7mKMcBNkzDwEHRGqQvHUl0XnoHR+3Pb1hytAHISgSxv9h0gUiSiYtISXUU3rMrKiKzFYDrI6ZIHA==", - "dev": true, - "dependencies": { - "@types/chai": "^4.3.4", - "@types/chai-subset": "^1.3.3", - "@types/node": "*", - "@vitest/expect": "0.28.5", - "@vitest/runner": "0.28.5", - "@vitest/spy": "0.28.5", - "@vitest/utils": "0.28.5", - "acorn": "^8.8.1", - "acorn-walk": "^8.2.0", - "cac": "^6.7.14", - "chai": "^4.3.7", - "debug": "^4.3.4", - "local-pkg": "^0.4.2", - "pathe": "^1.1.0", - "picocolors": "^1.0.0", - "source-map": "^0.6.1", - "std-env": "^3.3.1", - "strip-literal": "^1.0.0", - "tinybench": "^2.3.1", - "tinypool": "^0.3.1", - "tinyspy": "^1.0.2", - "vite": "^3.0.0 || ^4.0.0", - "vite-node": "0.28.5", - "why-is-node-running": "^2.2.2" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": ">=v14.16.0" + "std-env": "^3.3.2" }, "funding": { "url": "https://github.com/sponsors/antfu" }, "peerDependencies": { - "@edge-runtime/vm": "*", - "@vitest/browser": "*", - "@vitest/ui": "*", - "happy-dom": "*", - "jsdom": "*" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@vitest/browser": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - } + "vitest": ">=0.30.0 <1" } }, "node_modules/@vitest/expect": { - "version": "0.28.5", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.28.5.tgz", - "integrity": "sha512-gqTZwoUTwepwGIatnw4UKpQfnoyV0Z9Czn9+Lo2/jLIt4/AXLTn+oVZxlQ7Ng8bzcNkR+3DqLJ08kNr8jRmdNQ==", + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.30.1.tgz", + "integrity": "sha512-c3kbEtN8XXJSeN81iDGq29bUzSjQhjES2WR3aColsS4lPGbivwLtas4DNUe0jD9gg/FYGIteqOenfU95EFituw==", "dev": true, "dependencies": { - "@vitest/spy": "0.28.5", - "@vitest/utils": "0.28.5", + "@vitest/spy": "0.30.1", + "@vitest/utils": "0.30.1", "chai": "^4.3.7" } }, "node_modules/@vitest/runner": { - "version": "0.28.5", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.28.5.tgz", - "integrity": "sha512-NKkHtLB+FGjpp5KmneQjTcPLWPTDfB7ie+MmF1PnUBf/tGe2OjGxWyB62ySYZ25EYp9krR5Bw0YPLS/VWh1QiA==", + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.30.1.tgz", + "integrity": "sha512-W62kT/8i0TF1UBCNMRtRMOBWJKRnNyv9RrjIgdUryEe0wNpGZvvwPDLuzYdxvgSckzjp54DSpv1xUbv4BQ0qVA==", "dev": true, "dependencies": { - "@vitest/utils": "0.28.5", + "@vitest/utils": "0.30.1", + "concordance": "^5.0.4", "p-limit": "^4.0.0", "pathe": "^1.1.0" } @@ -3715,22 +3937,35 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@vitest/spy": { - "version": "0.28.5", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.28.5.tgz", - "integrity": "sha512-7if6rsHQr9zbmvxN7h+gGh2L9eIIErgf8nSKYDlg07HHimCxp4H6I/X/DPXktVPPLQfiZ1Cw2cbDIx9fSqDjGw==", + "node_modules/@vitest/snapshot": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.30.1.tgz", + "integrity": "sha512-fJZqKrE99zo27uoZA/azgWyWbFvM1rw2APS05yB0JaLwUIg9aUtvvnBf4q7JWhEcAHmSwbrxKFgyBUga6tq9Tw==", "dev": true, "dependencies": { - "tinyspy": "^1.0.2" + "magic-string": "^0.30.0", + "pathe": "^1.1.0", + "pretty-format": "^27.5.1" + } + }, + "node_modules/@vitest/spy": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.30.1.tgz", + "integrity": "sha512-YfJeIf37GvTZe04ZKxzJfnNNuNSmTEGnla2OdL60C8od16f3zOfv9q9K0nNii0NfjDJRt/CVN/POuY5/zTS+BA==", + "dev": true, + "dependencies": { + "tinyspy": "^2.1.0" } }, "node_modules/@vitest/ui": { - "version": "0.29.8", - "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-0.29.8.tgz", - "integrity": "sha512-+vbLd+c1R/XUWfzJsWeyjeiw13fwJ95I5tguxaqXRg61y9iYUKesVljg7Pttp2uo7VK+kAjvY91J41NZ1Vx3vg==", + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-0.30.1.tgz", + "integrity": "sha512-Izz4ElDmdvX02KImSC2nCJI6CsGo9aETbKqxli55M0rbbPPAMtF0zDcJIqgEP5V6Y+4Ysf6wvsjLbLCTnaBvKw==", "dev": true, "dependencies": { + "@vitest/utils": "0.30.1", "fast-glob": "^3.2.12", + "fflate": "^0.7.4", "flatted": "^3.2.7", "pathe": "^1.1.0", "picocolors": "^1.0.0", @@ -3738,15 +3973,13 @@ } }, "node_modules/@vitest/utils": { - "version": "0.28.5", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.28.5.tgz", - "integrity": "sha512-UyZdYwdULlOa4LTUSwZ+Paz7nBHGTT72jKwdFSV4IjHF1xsokp+CabMdhjvVhYwkLfO88ylJT46YMilnkSARZA==", + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.30.1.tgz", + "integrity": "sha512-/c8Xv2zUVc+rnNt84QF0Y0zkfxnaGhp87K2dYJMLtLOIckPzuxLVzAtFCicGFdB4NeBHNzTRr1tNn7rCtQcWFA==", "dev": true, "dependencies": { - "cli-truncate": "^3.1.0", - "diff": "^5.1.0", + "concordance": "^5.0.4", "loupe": "^2.3.6", - "picocolors": "^1.0.0", "pretty-format": "^27.5.1" } }, @@ -4060,12 +4293,13 @@ } }, "node_modules/axios": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", - "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.6.tgz", + "integrity": "sha512-PEcdkk7JcdPiMDkvM4K6ZBRYq9keuVJsToxm2zQIM70Qqo2WHTdJZMXcG9X+RmRp2VPNUQC8W1RAGbgt6b1yMg==", "dependencies": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "node_modules/axobject-query": { @@ -4182,6 +4416,12 @@ "node": ">=8" } }, + "node_modules/blueimp-md5": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz", + "integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==", + "dev": true + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -4246,12 +4486,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, "node_modules/c8": { "version": "7.13.0", "resolved": "https://registry.npmjs.org/c8/-/c8-7.13.0.tgz", @@ -4309,9 +4543,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001480", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001480.tgz", - "integrity": "sha512-q7cpoPPvZYgtyC4VaBSN0Bt+PJ4c4EYRf0DrduInOz2SkFpHD5p3LnvEpqBp7UnJn+8x1Ogl1s38saUxe+ihQQ==", + "version": "1.0.30001481", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001481.tgz", + "integrity": "sha512-KCqHwRnaa1InZBtqXzP98LPg0ajCVujMKjqKDhZEthIpAsJl/YEIa3YvXjGXPVqzZVguccuu7ga9KOE1J9rKPQ==", "dev": true, "funding": [ { @@ -4431,66 +4665,6 @@ "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==", "dev": true }, - "node_modules/cli-truncate": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", - "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", - "dev": true, - "dependencies": { - "slice-ansi": "^5.0.0", - "string-width": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-truncate/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/cli-truncate/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-truncate/node_modules/strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -4554,6 +4728,58 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "node_modules/concordance": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/concordance/-/concordance-5.0.4.tgz", + "integrity": "sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==", + "dev": true, + "dependencies": { + "date-time": "^3.1.0", + "esutils": "^2.0.3", + "fast-diff": "^1.2.0", + "js-string-escape": "^1.0.1", + "lodash": "^4.17.15", + "md5-hex": "^3.0.1", + "semver": "^7.3.2", + "well-known-symbols": "^2.0.0" + }, + "engines": { + "node": ">=10.18.0 <11 || >=12.14.0 <13 || >=14" + } + }, + "node_modules/concordance/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/concordance/node_modules/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/concordance/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/confusing-browser-globals": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", @@ -4619,109 +4845,142 @@ "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", "dev": true }, - "node_modules/cssom": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", - "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", - "dev": true - }, "node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz", + "integrity": "sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==", "dev": true, "dependencies": { - "cssom": "~0.3.6" + "rrweb-cssom": "^0.6.0" }, "engines": { - "node": ">=8" + "node": ">=14" } }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - }, "node_modules/csstype": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, "node_modules/d3-array": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", - "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.3.tgz", + "integrity": "sha512-JRHwbQQ84XuAESWhvIPaUV4/1UYTBOLiOPGWqgFDHZS1D5QN9c57FbH3QpEnQMYiOXNzKUQyGTZf+EVO7RT5TQ==", "dev": true, "dependencies": { - "internmap": "^1.0.0" + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-2.0.0.tgz", - "integrity": "sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==", - "dev": true + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "dev": true, + "engines": { + "node": ">=12" + } }, "node_modules/d3-format": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-2.0.0.tgz", - "integrity": "sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA==", - "dev": true + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "dev": true, + "engines": { + "node": ">=12" + } }, "node_modules/d3-interpolate": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-2.0.1.tgz", - "integrity": "sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", "dev": true, "dependencies": { - "d3-color": "1 - 2" + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-2.0.0.tgz", - "integrity": "sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA==", - "dev": true + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "dev": true, + "engines": { + "node": ">=12" + } }, "node_modules/d3-scale": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", - "integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", "dev": true, "dependencies": { - "d3-array": "^2.3.0", - "d3-format": "1 - 2", - "d3-interpolate": "1.2.0 - 2", - "d3-time": "^2.1.1", - "d3-time-format": "2 - 3" + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-shape": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-2.1.0.tgz", - "integrity": "sha512-PnjUqfM2PpskbSLTJvAzp2Wv4CZsnAgTfcVRTwW03QR3MkXF8Uo7B1y/lWkAsmbKwuecto++4NlsYcvYpXpTHA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", "dev": true, "dependencies": { - "d3-path": "1 - 2" + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-time": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz", - "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", "dev": true, "dependencies": { - "d3-array": "2" + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-time-format": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz", - "integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", "dev": true, "dependencies": { - "d3-time": "1 - 2" + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "dev": true, + "engines": { + "node": ">=12" } }, "node_modules/damerau-levenshtein": { @@ -4731,17 +4990,29 @@ "dev": true }, "node_modules/data-urls": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", - "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz", + "integrity": "sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==", "dev": true, "dependencies": { "abab": "^2.0.6", "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0" + "whatwg-url": "^12.0.0" }, "engines": { - "node": ">=12" + "node": ">=14" + } + }, + "node_modules/date-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/date-time/-/date-time-3.1.0.tgz", + "integrity": "sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==", + "dev": true, + "dependencies": { + "time-zone": "^1.0.0" + }, + "engines": { + "node": ">=6" } }, "node_modules/debug": { @@ -4847,14 +5118,10 @@ "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" }, - "node_modules/diff": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", - "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" }, "node_modules/diff-sequences": { "version": "29.4.3", @@ -4916,16 +5183,10 @@ "node": ">=12" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true - }, "node_modules/electron-to-chromium": { - "version": "1.4.368", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.368.tgz", - "integrity": "sha512-e2aeCAixCj9M7nJxdB/wDjO6mbYX+lJJxSJCXDzlr5YPGYVofuJwGN9nKg2o6wWInjX6XmxRinn3AeJMK81ltw==", + "version": "1.4.372", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.372.tgz", + "integrity": "sha512-MrlFq/j+TYHOjeWsWGYfzevc25HNeJdsF6qaLFrqBTRWZQtWkb1myq/Q2veLWezVaa5OcSZ99CFwTT4aF4Mung==", "dev": true }, "node_modules/emoji-regex": { @@ -5112,9 +5373,9 @@ } }, "node_modules/esbuild": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", - "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.18.tgz", + "integrity": "sha512-z1lix43jBs6UKjcZVKOw2xx69ffE2aG0PygLL5qJ9OS/gy0Ewd1gW/PUQIOIQGXBHWNywSc0floSKoMFF8aK2w==", "dev": true, "hasInstallScript": true, "bin": { @@ -5124,348 +5385,28 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.15.18", - "@esbuild/linux-loong64": "0.15.18", - "esbuild-android-64": "0.15.18", - "esbuild-android-arm64": "0.15.18", - "esbuild-darwin-64": "0.15.18", - "esbuild-darwin-arm64": "0.15.18", - "esbuild-freebsd-64": "0.15.18", - "esbuild-freebsd-arm64": "0.15.18", - "esbuild-linux-32": "0.15.18", - "esbuild-linux-64": "0.15.18", - "esbuild-linux-arm": "0.15.18", - "esbuild-linux-arm64": "0.15.18", - "esbuild-linux-mips64le": "0.15.18", - "esbuild-linux-ppc64le": "0.15.18", - "esbuild-linux-riscv64": "0.15.18", - "esbuild-linux-s390x": "0.15.18", - "esbuild-netbsd-64": "0.15.18", - "esbuild-openbsd-64": "0.15.18", - "esbuild-sunos-64": "0.15.18", - "esbuild-windows-32": "0.15.18", - "esbuild-windows-64": "0.15.18", - "esbuild-windows-arm64": "0.15.18" - } - }, - "node_modules/esbuild-android-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz", - "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-android-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz", - "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-darwin-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz", - "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-darwin-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz", - "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-freebsd-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz", - "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-freebsd-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz", - "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-32": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz", - "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz", - "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-arm": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz", - "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz", - "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-mips64le": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz", - "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-ppc64le": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz", - "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-riscv64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz", - "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-s390x": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz", - "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-netbsd-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz", - "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-openbsd-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz", - "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-sunos-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", - "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-32": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz", - "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", - "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz", - "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "@esbuild/android-arm": "0.17.18", + "@esbuild/android-arm64": "0.17.18", + "@esbuild/android-x64": "0.17.18", + "@esbuild/darwin-arm64": "0.17.18", + "@esbuild/darwin-x64": "0.17.18", + "@esbuild/freebsd-arm64": "0.17.18", + "@esbuild/freebsd-x64": "0.17.18", + "@esbuild/linux-arm": "0.17.18", + "@esbuild/linux-arm64": "0.17.18", + "@esbuild/linux-ia32": "0.17.18", + "@esbuild/linux-loong64": "0.17.18", + "@esbuild/linux-mips64el": "0.17.18", + "@esbuild/linux-ppc64": "0.17.18", + "@esbuild/linux-riscv64": "0.17.18", + "@esbuild/linux-s390x": "0.17.18", + "@esbuild/linux-x64": "0.17.18", + "@esbuild/netbsd-x64": "0.17.18", + "@esbuild/openbsd-x64": "0.17.18", + "@esbuild/sunos-x64": "0.17.18", + "@esbuild/win32-arm64": "0.17.18", + "@esbuild/win32-ia32": "0.17.18", + "@esbuild/win32-x64": "0.17.18" } }, "node_modules/escalade": { @@ -5572,15 +5513,15 @@ } }, "node_modules/eslint": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.38.0.tgz", - "integrity": "sha512-pIdsD2jwlUGf/U38Jv97t8lq6HpaU/G9NKbYmpWpZGw3LdTNhZLbJePqxOXGB5+JEKfOPU/XLxYxFh03nr1KTg==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.39.0.tgz", + "integrity": "sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.38.0", + "@eslint/js": "8.39.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -5590,7 +5531,7 @@ "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", + "eslint-scope": "^7.2.0", "eslint-visitor-keys": "^3.4.0", "espree": "^9.5.1", "esquery": "^1.4.2", @@ -6087,6 +6028,12 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "node_modules/fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, "node_modules/fast-equals": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-4.0.3.tgz", @@ -6142,6 +6089,12 @@ "reusify": "^1.0.4" } }, + "node_modules/fflate": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz", + "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==", + "dev": true + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -6387,6 +6340,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", @@ -6597,14 +6558,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/history": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", - "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==", - "dependencies": { - "@babel/runtime": "^7.7.6" - } - }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -6772,10 +6725,21 @@ } }, "node_modules/internmap": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", - "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", - "dev": true + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } }, "node_modules/is-arguments": { "version": "1.1.1", @@ -7347,6 +7311,15 @@ "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" }, + "node_modules/js-string-escape": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", + "integrity": "sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -7365,18 +7338,17 @@ } }, "node_modules/jsdom": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", - "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-21.1.1.tgz", + "integrity": "sha512-Jjgdmw48RKcdAIQyUD1UdBh2ecH7VqwaXPN3ehoZN6MqgVbMn+lRm1aAT1AsdJRAJpwfa4IpwgzySn61h2qu3w==", "dev": true, "dependencies": { "abab": "^2.0.6", - "acorn": "^8.8.1", + "acorn": "^8.8.2", "acorn-globals": "^7.0.0", - "cssom": "^0.5.0", - "cssstyle": "^2.3.0", - "data-urls": "^3.0.2", - "decimal.js": "^10.4.2", + "cssstyle": "^3.0.0", + "data-urls": "^4.0.0", + "decimal.js": "^10.4.3", "domexception": "^4.0.0", "escodegen": "^2.0.0", "form-data": "^4.0.0", @@ -7385,7 +7357,8 @@ "https-proxy-agent": "^5.0.1", "is-potential-custom-element-name": "^1.0.1", "nwsapi": "^2.2.2", - "parse5": "^7.1.1", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.6.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^4.1.2", @@ -7393,8 +7366,8 @@ "webidl-conversions": "^7.0.0", "whatwg-encoding": "^2.0.0", "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0", - "ws": "^8.11.0", + "whatwg-url": "^12.0.1", + "ws": "^8.13.0", "xml-name-validator": "^4.0.0" }, "engines": { @@ -7621,12 +7594,12 @@ } }, "node_modules/magic-string": { - "version": "0.26.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.7.tgz", - "integrity": "sha512-hX9XH3ziStPoPhJxLq1syWuZMxbDvGNbVchfrdCtanC7D13888bMFow61x8axrx+GfHLtVeAx2kxL7tTGRl+Ow==", + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz", + "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==", "dev": true, "dependencies": { - "sourcemap-codec": "^1.4.8" + "@jridgewell/sourcemap-codec": "^1.4.13" }, "engines": { "node": ">=12" @@ -7656,6 +7629,18 @@ "remove-accents": "0.4.2" } }, + "node_modules/md5-hex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-3.0.1.tgz", + "integrity": "sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==", + "dev": true, + "dependencies": { + "blueimp-md5": "^2.10.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -8206,9 +8191,9 @@ } }, "node_modules/postcss": { - "version": "8.4.22", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.22.tgz", - "integrity": "sha512-XseknLAfRHzVWjCEtdviapiBtfLdgyzExD50Rg2ePaucEesyh8Wv4VPdW0nbyDa1ydbrAxV19jvMT4+LFmcNUA==", + "version": "8.4.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.23.tgz", + "integrity": "sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==", "dev": true, "funding": [ { @@ -8249,9 +8234,9 @@ } }, "node_modules/prettier": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz", - "integrity": "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==", + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, "bin": { "prettier": "bin-prettier.js" @@ -8413,6 +8398,11 @@ "react-is": "^16.13.1" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -8465,28 +8455,26 @@ ] }, "node_modules/react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "loose-envify": "^1.1.0" }, "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", - "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", "dependencies": { "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" + "scheduler": "^0.23.0" }, "peerDependencies": { - "react": "17.0.2" + "react": "^18.2.0" } }, "node_modules/react-dropzone": { @@ -8505,22 +8493,6 @@ "react": ">= 16.8 || 18.0.0" } }, - "node_modules/react-error-boundary": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz", - "integrity": "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.12.5" - }, - "engines": { - "node": ">=10", - "npm": ">=6" - }, - "peerDependencies": { - "react": ">=16.13.1" - } - }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -8566,10 +8538,55 @@ "node": ">=0.10.0" } }, + "node_modules/react-remove-scroll": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", + "integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.3", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz", + "integrity": "sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==", + "dependencies": { + "react-style-singleton": "^2.2.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-resize-detector": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-7.1.2.tgz", - "integrity": "sha512-zXnPJ2m8+6oq9Nn8zsep/orts9vQv3elrpA+R8XTcW7DVVUJ9vwDwMXaBtykAYjMnkCIaOoK9vObyR7ZgFNlOw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-8.1.0.tgz", + "integrity": "sha512-S7szxlaIuiy5UqLhLL1KY3aoyGHbZzsTpYal9eYMwCyKqoqoVLCmIgAgNyIM1FhnP2KyBygASJxdhejrzjMb+w==", "dev": true, "dependencies": { "lodash": "^4.17.21" @@ -8580,23 +8597,29 @@ } }, "node_modules/react-router": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz", - "integrity": "sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.10.0.tgz", + "integrity": "sha512-Nrg0BWpQqrC3ZFFkyewrflCud9dio9ME3ojHCF/WLsprJVzkq3q3UeEhMCAW1dobjeGbWgjNn/PVF6m46ANxXQ==", "dependencies": { - "history": "^5.2.0" + "@remix-run/router": "1.5.0" + }, + "engines": { + "node": ">=14" }, "peerDependencies": { "react": ">=16.8" } }, "node_modules/react-router-dom": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.3.0.tgz", - "integrity": "sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.10.0.tgz", + "integrity": "sha512-E5dfxRPuXKJqzwSe/qGcqdwa18QiWC6f3H3cWXM24qj4N0/beCIf/CWTipop2xm7mR0RCS99NnaqPNjHtrAzCg==", "dependencies": { - "history": "^5.2.0", - "react-router": "6.3.0" + "@remix-run/router": "1.5.0", + "react-router": "6.10.0" + }, + "engines": { + "node": ">=14" }, "peerDependencies": { "react": ">=16.8", @@ -8643,6 +8666,28 @@ "react-dom": ">=15.0.0" } }, + "node_modules/react-style-singleton": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", + "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", + "dependencies": { + "get-nonce": "^1.0.0", + "invariant": "^2.2.4", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-table": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/react-table/-/react-table-7.8.0.tgz", @@ -8700,25 +8745,20 @@ } }, "node_modules/recharts": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.1.16.tgz", - "integrity": "sha512-aYn1plTjYzRCo3UGxtWsduslwYd+Cuww3h/YAAEoRdGe0LRnBgYgaXSlVrNFkWOOSXrBavpmnli9h7pvRuk5wg==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.5.0.tgz", + "integrity": "sha512-0EQYz3iA18r1Uq8VqGZ4dABW52AKBnio37kJgnztIqprELJXpOEsa0SzkqU1vjAhpCXCv52Dx1hiL9119xsqsQ==", "dev": true, "dependencies": { - "@types/d3-interpolate": "^2.0.0", - "@types/d3-scale": "^3.0.0", - "@types/d3-shape": "^2.0.0", "classnames": "^2.2.5", - "d3-interpolate": "^2.0.0", - "d3-scale": "^3.0.0", - "d3-shape": "^2.0.0", "eventemitter3": "^4.0.1", "lodash": "^4.17.19", "react-is": "^16.10.2", - "react-resize-detector": "^7.1.2", - "react-smooth": "^2.0.1", + "react-resize-detector": "^8.0.4", + "react-smooth": "^2.0.2", "recharts-scale": "^0.4.4", - "reduce-css-calc": "^2.1.8" + "reduce-css-calc": "^2.1.8", + "victory-vendor": "^36.6.8" }, "engines": { "node": ">=12" @@ -8917,20 +8957,27 @@ } }, "node_modules/rollup": { - "version": "2.79.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", - "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.21.0.tgz", + "integrity": "sha512-ANPhVcyeHvYdQMUyCbczy33nbLzI7RzrBje4uvNiTDJGIMtlKoOStmympwr9OtS1LZxiDmE2wvxHyVhoLtf1KQ==", "dev": true, "bin": { "rollup": "dist/bin/rollup" }, "engines": { - "node": ">=10.0.0" + "node": ">=14.18.0", + "npm": ">=8.0.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, + "node_modules/rrweb-cssom": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", + "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==", + "dev": true + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -8975,9 +9022,9 @@ "dev": true }, "node_modules/sass": { - "version": "1.62.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.62.0.tgz", - "integrity": "sha512-Q4USplo4pLYgCi+XlipZCWUQz5pkg/ruSSgJ0WRDSb/+3z9tXUOkQ7QPYn4XrhZKYAK4HlpaQecRwKLJX6+DBg==", + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.62.1.tgz", + "integrity": "sha512-NHpxIzN29MXvWiuswfc1W3I0N8SXBd8UR26WntmDlRYf0bSADnwnOjsyMZ3lMezSlArD33Vs3YFhp7dWvL770A==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -9004,12 +9051,11 @@ } }, "node_modules/scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "loose-envify": "^1.1.0" } }, "node_modules/semver": { @@ -9069,9 +9115,9 @@ "dev": true }, "node_modules/sirv": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.2.tgz", - "integrity": "sha512-4Qog6aE29nIjAOKe/wowFTxOdmbEZKb+3tsLljaBRzJwtqto0BChD2zzH0LhgCSXiI+V7X+Y45v14wBZQ1TK3w==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.3.tgz", + "integrity": "sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==", "dev": true, "dependencies": { "@polka/url": "^1.0.0-next.20", @@ -9091,46 +9137,6 @@ "node": ">=8" } }, - "node_modules/slice-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", - "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^6.0.0", - "is-fullwidth-code-point": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/socket.io-client": { "version": "4.6.1", "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.6.1.tgz", @@ -9175,32 +9181,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "deprecated": "Please use @jridgewell/sourcemap-codec instead", - "dev": true - }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -9462,6 +9442,15 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/time-zone": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/time-zone/-/time-zone-1.0.0.tgz", + "integrity": "sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/tiny-invariant": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", @@ -9475,18 +9464,18 @@ "dev": true }, "node_modules/tinypool": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.3.1.tgz", - "integrity": "sha512-zLA1ZXlstbU2rlpA4CIeVaqvWq41MTWqLY3FfsAXgC8+f7Pk7zroaJQxDgxn1xNudKW6Kmj4808rPFShUlIRmQ==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.4.0.tgz", + "integrity": "sha512-2ksntHOKf893wSAH4z/+JbPpi92esw8Gn9N2deXX+B0EO92hexAVI9GIZZPx7P5aYo5KULfeOSt3kMOmSOy6uA==", "dev": true, "engines": { "node": ">=14.0.0" } }, "node_modules/tinyspy": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-1.1.1.tgz", - "integrity": "sha512-UVq5AXt/gQlti7oxoIg5oi/9r0WpF7DGEVwXgqWSMmyN16+e3tl5lIvTaOpJ3TAtu5xFzWccFRM4R5NaWHF+4g==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.1.0.tgz", + "integrity": "sha512-7eORpyqImoOvkQJCSkL0d0mB4NHHIFAy4b1u8PHdDa7SjGS2njzl6/lyGoZLm+eyYEtlUmFGE0rFj66SWxZgQQ==", "dev": true, "engines": { "node": ">=14.0.0" @@ -9537,15 +9526,15 @@ } }, "node_modules/tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", "dev": true, "dependencies": { - "punycode": "^2.1.1" + "punycode": "^2.3.0" }, "engines": { - "node": ">=12" + "node": ">=14" } }, "node_modules/tsconfig-paths": { @@ -9646,16 +9635,16 @@ } }, "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", + "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=12.20" } }, "node_modules/ufo": { @@ -9786,6 +9775,26 @@ "requires-port": "^1.0.0" } }, + "node_modules/use-callback-ref": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.0.tgz", + "integrity": "sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/use-composed-ref": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.3.0.tgz", @@ -9823,6 +9832,27 @@ } } }, + "node_modules/use-sidecar": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", + "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/v8-to-istanbul": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", @@ -9837,16 +9867,37 @@ "node": ">=10.12.0" } }, - "node_modules/vite": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.6.tgz", - "integrity": "sha512-nTXTxYVvaQNLoW5BQ8PNNQ3lPia57gzsQU/Khv+JvzKPku8kNZL6NMUR/qwXhMG6E+g1idqEPanomJ+VZgixEg==", + "node_modules/victory-vendor": { + "version": "36.6.8", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.6.8.tgz", + "integrity": "sha512-H3kyQ+2zgjMPvbPqAl7Vwm2FD5dU7/4bCTQakFQnpIsfDljeOMDojRsrmJfwh4oAlNnWhpAf+mbAoLh8u7dwyQ==", "dev": true, "dependencies": { - "esbuild": "^0.15.9", - "postcss": "^8.4.18", - "resolve": "^1.22.1", - "rollup": "^2.79.1" + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/vite": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.2.tgz", + "integrity": "sha512-9R53Mf+TBoXCYejcL+qFbZde+eZveQLDYd9XgULILLC1a5ZwPaqgmdVpL8/uvw2BM/1TzetWjglwm+3RO+xTyw==", + "dev": true, + "dependencies": { + "esbuild": "^0.17.5", + "postcss": "^8.4.21", + "rollup": "^3.21.0" }, "bin": { "vite": "bin/vite.js" @@ -9887,39 +9938,28 @@ } }, "node_modules/vite-node": { - "version": "0.28.5", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.28.5.tgz", - "integrity": "sha512-LmXb9saMGlrMZbXTvOveJKwMTBTNUH66c8rJnQ0ZPNX+myPEol64+szRzXtV5ORb0Hb/91yq+/D3oERoyAt6LA==", + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.30.1.tgz", + "integrity": "sha512-vTikpU/J7e6LU/8iM3dzBo8ZhEiKZEKRznEMm+mJh95XhWaPrJQraT/QsT2NWmuEf+zgAoMe64PKT7hfZ1Njmg==", "dev": true, "dependencies": { "cac": "^6.7.14", "debug": "^4.3.4", - "mlly": "^1.1.0", + "mlly": "^1.2.0", "pathe": "^1.1.0", "picocolors": "^1.0.0", - "source-map": "^0.6.1", - "source-map-support": "^0.5.21", "vite": "^3.0.0 || ^4.0.0" }, "bin": { "vite-node": "vite-node.mjs" }, "engines": { - "node": ">=v14.16.0" + "node": ">=v14.18.0" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, - "node_modules/vite-node/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/vite-plugin-checker": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/vite-plugin-checker/-/vite-plugin-checker-0.5.6.tgz", @@ -9985,31 +10025,43 @@ } }, "node_modules/vitest": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.25.8.tgz", - "integrity": "sha512-X75TApG2wZTJn299E/TIYevr4E9/nBo1sUtZzn0Ci5oK8qnpZAZyhwg0qCeMSakGIWtc6oRwcQFyFfW14aOFWg==", + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.30.1.tgz", + "integrity": "sha512-y35WTrSTlTxfMLttgQk4rHcaDkbHQwDP++SNwPb+7H8yb13Q3cu2EixrtHzF27iZ8v0XCciSsLg00RkPAzB/aA==", "dev": true, "dependencies": { "@types/chai": "^4.3.4", "@types/chai-subset": "^1.3.3", "@types/node": "*", - "acorn": "^8.8.1", + "@vitest/expect": "0.30.1", + "@vitest/runner": "0.30.1", + "@vitest/snapshot": "0.30.1", + "@vitest/spy": "0.30.1", + "@vitest/utils": "0.30.1", + "acorn": "^8.8.2", "acorn-walk": "^8.2.0", + "cac": "^6.7.14", "chai": "^4.3.7", + "concordance": "^5.0.4", "debug": "^4.3.4", - "local-pkg": "^0.4.2", + "local-pkg": "^0.4.3", + "magic-string": "^0.30.0", + "pathe": "^1.1.0", + "picocolors": "^1.0.0", "source-map": "^0.6.1", - "strip-literal": "^1.0.0", - "tinybench": "^2.3.1", - "tinypool": "^0.3.0", - "tinyspy": "^1.0.2", - "vite": "^3.0.0 || ^4.0.0" + "std-env": "^3.3.2", + "strip-literal": "^1.0.1", + "tinybench": "^2.4.0", + "tinypool": "^0.4.0", + "vite": "^3.0.0 || ^4.0.0", + "vite-node": "0.30.1", + "why-is-node-running": "^2.2.2" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": ">=v14.16.0" + "node": ">=v14.18.0" }, "funding": { "url": "https://github.com/sponsors/antfu" @@ -10019,7 +10071,10 @@ "@vitest/browser": "*", "@vitest/ui": "*", "happy-dom": "*", - "jsdom": "*" + "jsdom": "*", + "playwright": "*", + "safaridriver": "*", + "webdriverio": "*" }, "peerDependenciesMeta": { "@edge-runtime/vm": { @@ -10036,6 +10091,15 @@ }, "jsdom": { "optional": true + }, + "playwright": { + "optional": true + }, + "safaridriver": { + "optional": true + }, + "webdriverio": { + "optional": true } } }, @@ -10165,6 +10229,15 @@ "node": ">=12" } }, + "node_modules/well-known-symbols": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/well-known-symbols/-/well-known-symbols-2.0.0.tgz", + "integrity": "sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/whatwg-encoding": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", @@ -10187,16 +10260,16 @@ } }, "node_modules/whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-12.0.1.tgz", + "integrity": "sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==", "dev": true, "dependencies": { - "tr46": "^3.0.0", + "tr46": "^4.1.1", "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=12" + "node": ">=14" } }, "node_modules/which": { diff --git a/frontend/package.json b/frontend/package.json index 0b8bded7b..2ef06b97d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,17 +13,17 @@ }, "private": true, "dependencies": { - "@mantine/core": "^5.6.0", - "@mantine/dropzone": "^5.6.0", - "@mantine/form": "^5.6.0", - "@mantine/hooks": "^5.6.0", - "@mantine/modals": "^5.6.0", - "@mantine/notifications": "^5.6.0", - "axios": "^0.27.2", - "react": "^17.0.2", - "react-dom": "^17.0.2", + "@mantine/core": "^6.0.0", + "@mantine/dropzone": "^6.0.0", + "@mantine/form": "^6.0.0", + "@mantine/hooks": "^6.0.0", + "@mantine/modals": "^6.0.0", + "@mantine/notifications": "^6.0.0", + "axios": "^1.3.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", "react-query": "^3.39.2", - "react-router-dom": "~6.3.0", + "react-router-dom": "~6.10.0", "socket.io-client": "^4.5.3" }, "devDependencies": { @@ -34,36 +34,35 @@ "@fortawesome/free-solid-svg-icons": "^6.2.0", "@fortawesome/react-fontawesome": "^0.2.0", "@testing-library/jest-dom": "^5.16.5", - "@testing-library/react": "^12.1.0", - "@testing-library/react-hooks": "^8.0.1", + "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.4.3", "@types/lodash": "^4.14.0", - "@types/node": "^18.11.7", - "@types/react": "^17.0.0", - "@types/react-dom": "^17.0.0", + "@types/node": "^18.16.0", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", "@types/react-table": "^7.7.0", - "@vitejs/plugin-react": "^2.2.0", - "@vitest/coverage-c8": "^0.28.5", - "@vitest/ui": "^0.29.1", + "@vitejs/plugin-react": "^4.0.0", + "vitest": "^0.30.1", + "@vitest/coverage-c8": "^0.30.0", + "@vitest/ui": "^0.30.0", "clsx": "^1.2.0", - "eslint": "^8.26.0", + "eslint": "^8.39.0", "eslint-config-react-app": "^7.0.1", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-testing-library": "^5.9.0", "husky": "^8.0.2", - "jsdom": "^20.0.1", + "jsdom": "^21.0.0", "lodash": "^4.17.0", "moment": "^2.29", - "prettier": "^2.7.0", + "prettier": "^2.8.0", "prettier-plugin-organize-imports": "^3.1.0", "pretty-quick": "^3.1.0", "react-table": "^7.8.0", - "recharts": "~2.1.0", - "sass": "^1.55.0", - "typescript": "^4", - "vite": "^3.2.1", - "vite-plugin-checker": "^0.5.5", - "vitest": "^0.25.0" + "recharts": "~2.5.0", + "sass": "^1.62.0", + "typescript": "^5", + "vite": "^4.3.0", + "vite-plugin-checker": "^0.5.5" }, "scripts": { "start": "vite", diff --git a/frontend/src/App/Header.tsx b/frontend/src/App/Header.tsx index f1e4f1799..c15071045 100644 --- a/frontend/src/App/Header.tsx +++ b/frontend/src/App/Header.tsx @@ -15,12 +15,12 @@ import { Avatar, Badge, Burger, - createStyles, Divider, Group, Header, MediaQuery, Menu, + createStyles, } from "@mantine/core"; import { FunctionComponent } from "react"; diff --git a/frontend/src/App/Navbar.tsx b/frontend/src/App/Navbar.tsx index abde1e82b..af64a6b35 100644 --- a/frontend/src/App/Navbar.tsx +++ b/frontend/src/App/Navbar.tsx @@ -357,9 +357,11 @@ const NavbarItem: FunctionComponent = ({ > )} {name} - + {shouldHideBadge === false && ( + + {badge} + + )} ); diff --git a/frontend/src/App/index.tsx b/frontend/src/App/index.tsx index 374505be1..87236d6c6 100644 --- a/frontend/src/App/index.tsx +++ b/frontend/src/App/index.tsx @@ -1,11 +1,11 @@ import AppNavbar from "@/App/Navbar"; +import { RouterNames } from "@/Router/RouterNames"; import ErrorBoundary from "@/components/ErrorBoundary"; import { Layout } from "@/constants"; import NavbarProvider from "@/contexts/Navbar"; import OnlineProvider from "@/contexts/Online"; import { notification } from "@/modules/task"; import CriticalError from "@/pages/errors/CriticalError"; -import { RouterNames } from "@/Router/RouterNames"; import { Environment } from "@/utilities"; import { AppShell } from "@mantine/core"; import { useWindowEvent } from "@mantine/hooks"; diff --git a/frontend/src/App/theme.tsx b/frontend/src/App/theme.tsx index e50fc2f33..0fe2be9d5 100644 --- a/frontend/src/App/theme.tsx +++ b/frontend/src/App/theme.tsx @@ -6,7 +6,13 @@ import { MantineThemeOverride, } from "@mantine/core"; import { useColorScheme } from "@mantine/hooks"; -import { FunctionComponent, useCallback, useEffect, useState } from "react"; +import { + FunctionComponent, + PropsWithChildren, + useCallback, + useEffect, + useState, +} from "react"; const theme: MantineThemeOverride = { fontFamily: "Roboto, open sans, Helvetica Neue, Helvetica, Arial, sans-serif", @@ -45,7 +51,7 @@ function useAutoColorScheme() { const emotionCache = createEmotionCache({ key: "bazarr" }); -const ThemeProvider: FunctionComponent = ({ children }) => { +const ThemeProvider: FunctionComponent = ({ children }) => { const { colorScheme, toggleColorScheme } = useAutoColorScheme(); return ( diff --git a/frontend/src/Router/index.tsx b/frontend/src/Router/index.tsx index d13ea1417..8d925e524 100644 --- a/frontend/src/Router/index.tsx +++ b/frontend/src/Router/index.tsx @@ -1,12 +1,11 @@ +import App from "@/App"; import { useBadges } from "@/apis/hooks"; import { useEnabledStatus } from "@/apis/hooks/site"; -import App from "@/App"; import { Lazy } from "@/components/async"; import Authentication from "@/pages/Authentication"; import BlacklistMoviesView from "@/pages/Blacklist/Movies"; import BlacklistSeriesView from "@/pages/Blacklist/Series"; import Episodes from "@/pages/Episodes"; -import NotFound from "@/pages/errors/NotFound"; import MoviesHistoryView from "@/pages/History/Movies"; import SeriesHistoryView from "@/pages/History/Series"; import MovieView from "@/pages/Movies"; @@ -31,6 +30,7 @@ import SystemReleasesView from "@/pages/System/Releases"; import SystemTasksView from "@/pages/System/Tasks"; import WantedMoviesView from "@/pages/Wanted/Movies"; import WantedSeriesView from "@/pages/Wanted/Series"; +import NotFound from "@/pages/errors/NotFound"; import { Environment } from "@/utilities"; import { faClock, @@ -42,13 +42,13 @@ import { faPlay, } from "@fortawesome/free-solid-svg-icons"; import { - createContext, FunctionComponent, + createContext, lazy, useContext, useMemo, } from "react"; -import { BrowserRouter } from "react-router-dom"; +import { RouterProvider, createBrowserRouter } from "react-router-dom"; import Redirector from "./Redirector"; import { RouterNames } from "./RouterNames"; import { CustomRouteObject } from "./type"; @@ -315,12 +315,18 @@ function useRoutes(): CustomRouteObject[] { const RouterItemContext = createContext([]); -export const Router: FunctionComponent = ({ children }) => { +export const Router: FunctionComponent = () => { const routes = useRoutes(); + // TODO: Move this outside the function component scope + const router = useMemo( + () => createBrowserRouter(routes, { basename: Environment.baseUrl }), + [routes] + ); + return ( - {children} + ); }; diff --git a/frontend/src/apis/queries/hooks.ts b/frontend/src/apis/queries/hooks.ts index 4f8467221..507cc2120 100644 --- a/frontend/src/apis/queries/hooks.ts +++ b/frontend/src/apis/queries/hooks.ts @@ -3,9 +3,9 @@ import { usePageSize } from "@/utilities/storage"; import { useCallback, useEffect, useState } from "react"; import { QueryKey, + UseQueryResult, useQuery, useQueryClient, - UseQueryResult, } from "react-query"; import { QueryKeys } from "./keys"; diff --git a/frontend/src/components/ErrorBoundary.tsx b/frontend/src/components/ErrorBoundary.tsx index 9f6228e34..a29200e47 100644 --- a/frontend/src/components/ErrorBoundary.tsx +++ b/frontend/src/components/ErrorBoundary.tsx @@ -1,11 +1,11 @@ import UIError from "@/pages/errors/UIError"; -import { Component } from "react"; +import { Component, PropsWithChildren } from "react"; interface State { error: Error | null; } -class ErrorBoundary extends Component { +class ErrorBoundary extends Component { constructor(props: object) { super(props); this.state = { error: null }; diff --git a/frontend/src/components/async/Lazy.tsx b/frontend/src/components/async/Lazy.tsx index 5edb55c5b..2a0496223 100644 --- a/frontend/src/components/async/Lazy.tsx +++ b/frontend/src/components/async/Lazy.tsx @@ -1,7 +1,7 @@ import { LoadingOverlay } from "@mantine/core"; -import { FunctionComponent, Suspense } from "react"; +import { FunctionComponent, PropsWithChildren, Suspense } from "react"; -const Lazy: FunctionComponent = ({ children }) => { +const Lazy: FunctionComponent = ({ children }) => { return }>{children}; }; diff --git a/frontend/src/components/forms/MovieUploadForm.tsx b/frontend/src/components/forms/MovieUploadForm.tsx index 4506baa83..46b592081 100644 --- a/frontend/src/components/forms/MovieUploadForm.tsx +++ b/frontend/src/components/forms/MovieUploadForm.tsx @@ -1,6 +1,6 @@ import { useMovieSubtitleModification } from "@/apis/hooks"; import { useModals, withModal } from "@/modules/modals"; -import { task, TaskGroup } from "@/modules/task"; +import { TaskGroup, task } from "@/modules/task"; import { useTableStyles } from "@/styles"; import { useArrayAction, useSelectorOptions } from "@/utilities"; import FormUtils from "@/utilities/form"; @@ -28,9 +28,9 @@ import { useForm } from "@mantine/form"; import { isString } from "lodash"; import { FunctionComponent, useEffect, useMemo } from "react"; import { Column } from "react-table"; +import TextPopover from "../TextPopover"; import { Action, Selector } from "../inputs"; import { SimpleTable } from "../tables"; -import TextPopover from "../TextPopover"; type SubtitleFile = { file: File; diff --git a/frontend/src/components/index.tsx b/frontend/src/components/index.tsx index f9ebf40d1..c3d7b4763 100644 --- a/frontend/src/components/index.tsx +++ b/frontend/src/components/index.tsx @@ -1,4 +1,4 @@ -export * from "./inputs"; export { default as Search } from "./Search"; +export * from "./inputs"; export * from "./tables"; export { default as Toolbox } from "./toolbox"; diff --git a/frontend/src/components/inputs/DropContent.tsx b/frontend/src/components/inputs/DropContent.tsx index 5e25a3e18..38556220d 100644 --- a/frontend/src/components/inputs/DropContent.tsx +++ b/frontend/src/components/inputs/DropContent.tsx @@ -4,7 +4,7 @@ import { faXmark, } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { createStyles, Group, Stack, Text } from "@mantine/core"; +import { Group, Stack, Text, createStyles } from "@mantine/core"; import { Dropzone } from "@mantine/dropzone"; import { FunctionComponent } from "react"; diff --git a/frontend/src/components/modals/HistoryModal.tsx b/frontend/src/components/modals/HistoryModal.tsx index 566a54611..a3563d2ac 100644 --- a/frontend/src/components/modals/HistoryModal.tsx +++ b/frontend/src/components/modals/HistoryModal.tsx @@ -12,11 +12,11 @@ import { Badge, Center, Text } from "@mantine/core"; import { FunctionComponent, useMemo } from "react"; import { Column } from "react-table"; import { PageTable } from ".."; +import TextPopover from "../TextPopover"; import MutateAction from "../async/MutateAction"; import QueryOverlay from "../async/QueryOverlay"; import { HistoryIcon } from "../bazarr"; import Language from "../bazarr/Language"; -import TextPopover from "../TextPopover"; interface MovieHistoryViewProps { movie: Item.Movie; diff --git a/frontend/src/components/modals/ManualSearchModal.tsx b/frontend/src/components/modals/ManualSearchModal.tsx index 411c93620..a152c8823 100644 --- a/frontend/src/components/modals/ManualSearchModal.tsx +++ b/frontend/src/components/modals/ManualSearchModal.tsx @@ -105,7 +105,7 @@ function ManualSearchView(props: Props) { ); } else { - return value; + return {value}; } }, }, diff --git a/frontend/src/components/modals/SubtitleToolsModal.tsx b/frontend/src/components/modals/SubtitleToolsModal.tsx index c92ff6a18..d780504c3 100644 --- a/frontend/src/components/modals/SubtitleToolsModal.tsx +++ b/frontend/src/components/modals/SubtitleToolsModal.tsx @@ -4,7 +4,7 @@ import { SimpleTable } from "@/components/tables"; import { useCustomSelection } from "@/components/tables/plugins"; import { withModal } from "@/modules/modals"; import { isMovie } from "@/utilities"; -import { Badge, Button, Divider, Group, Stack } from "@mantine/core"; +import { Badge, Button, Divider, Group, Stack, Text } from "@mantine/core"; import { FunctionComponent, useMemo, useState } from "react"; import { Column, useRowSelect } from "react-table"; @@ -60,9 +60,9 @@ const SubtitleToolView: FunctionComponent = ({ } if (idx !== -1) { - return path.slice(idx + 1); + return {path.slice(idx + 1)}; } else { - return path; + return {path}; } }, }, diff --git a/frontend/src/components/tables/PageControl.tsx b/frontend/src/components/tables/PageControl.tsx index 5c7bb41ba..d00edcef6 100644 --- a/frontend/src/components/tables/PageControl.tsx +++ b/frontend/src/components/tables/PageControl.tsx @@ -30,7 +30,7 @@ const PageControl: FunctionComponent = ({ goto(page - 1)} hidden={count <= 1} total={count} diff --git a/frontend/src/components/tables/PageTable.tsx b/frontend/src/components/tables/PageTable.tsx index bc43b25e0..fa958b28f 100644 --- a/frontend/src/components/tables/PageTable.tsx +++ b/frontend/src/components/tables/PageTable.tsx @@ -3,8 +3,8 @@ import { useEffect } from "react"; import { usePagination, useTable } from "react-table"; import BaseTable from "./BaseTable"; import PageControl from "./PageControl"; -import { useDefaultSettings } from "./plugins"; import { SimpleTableProps } from "./SimpleTable"; +import { useDefaultSettings } from "./plugins"; type Props = SimpleTableProps & { autoScroll?: boolean; diff --git a/frontend/src/components/tables/plugins/useCustomSelection.tsx b/frontend/src/components/tables/plugins/useCustomSelection.tsx index 9099da677..f3d357378 100644 --- a/frontend/src/components/tables/plugins/useCustomSelection.tsx +++ b/frontend/src/components/tables/plugins/useCustomSelection.tsx @@ -4,12 +4,12 @@ import { CellProps, Column, ColumnInstance, - ensurePluginOrder, HeaderProps, Hooks, MetaBase, TableInstance, TableToggleCommonProps, + ensurePluginOrder, } from "react-table"; const pluginName = "useCustomSelection"; diff --git a/frontend/src/components/toolbox/index.tsx b/frontend/src/components/toolbox/index.tsx index 56f3463de..6995e111d 100644 --- a/frontend/src/components/toolbox/index.tsx +++ b/frontend/src/components/toolbox/index.tsx @@ -1,5 +1,5 @@ import { createStyles, Group } from "@mantine/core"; -import { FunctionComponent } from "react"; +import { FunctionComponent, PropsWithChildren } from "react"; import ToolboxButton, { ToolboxMutateButton } from "./Button"; const useStyles = createStyles((theme) => ({ @@ -11,7 +11,7 @@ const useStyles = createStyles((theme) => ({ }, })); -declare type ToolboxComp = FunctionComponent & { +declare type ToolboxComp = FunctionComponent & { Button: typeof ToolboxButton; MutateButton: typeof ToolboxMutateButton; }; diff --git a/frontend/src/dom.tsx b/frontend/src/dom.tsx index f8028af36..e00b3e089 100644 --- a/frontend/src/dom.tsx +++ b/frontend/src/dom.tsx @@ -1,20 +1,19 @@ import { StrictMode } from "react"; -import ReactDOM from "react-dom"; -import { useRoutes } from "react-router-dom"; +import { createRoot } from "react-dom/client"; +import { Router } from "./Router"; import { AllProviders } from "./providers"; -import { useRouteItems } from "./Router"; -const RouteApp = () => { - const items = useRouteItems(); +const container = document.getElementById("root"); - return useRoutes(items); -}; - -ReactDOM.render( - - - - - , - document.getElementById("root") -); +if (container === null) { + Error("Cannot initialize app, root not found"); +} else { + const root = createRoot(container); + root.render( + + + + + + ); +} diff --git a/frontend/src/modules/modals/ModalsProvider.tsx b/frontend/src/modules/modals/ModalsProvider.tsx index d7724542c..d76aa763f 100644 --- a/frontend/src/modules/modals/ModalsProvider.tsx +++ b/frontend/src/modules/modals/ModalsProvider.tsx @@ -2,19 +2,19 @@ import { ModalsProvider as MantineModalsProvider, ModalsProviderProps as MantineModalsProviderProps, } from "@mantine/modals"; -import { FunctionComponent, useMemo } from "react"; +import { FunctionComponent, PropsWithChildren, useMemo } from "react"; import { ModalComponent, StaticModals } from "./WithModal"; const DefaultModalProps: MantineModalsProviderProps["modalProps"] = { centered: true, styles: { - modal: { - maxWidth: "100%", - }, + // modals: { + // maxWidth: "100%", + // }, }, }; -const ModalsProvider: FunctionComponent = ({ children }) => { +const ModalsProvider: FunctionComponent = ({ children }) => { const modals = useMemo( () => StaticModals.reduce>((prev, curr) => { diff --git a/frontend/src/modules/modals/hooks.ts b/frontend/src/modules/modals/hooks.ts index 3ebdbc0a8..25399f414 100644 --- a/frontend/src/modules/modals/hooks.ts +++ b/frontend/src/modules/modals/hooks.ts @@ -5,8 +5,11 @@ import { useCallback, useContext, useMemo } from "react"; import { ModalComponent, ModalIdContext } from "./WithModal"; export function useModals() { - const { openContextModal: openMantineContextModal, ...rest } = - useMantineModals(); + const { + openContextModal: openMantineContextModal, + closeContextModal: closeContextModalRaw, + ...rest + } = useMantineModals(); const openContextModal = useCallback( ( diff --git a/frontend/src/modules/modals/index.ts b/frontend/src/modules/modals/index.ts index 852458acd..05aed103f 100644 --- a/frontend/src/modules/modals/index.ts +++ b/frontend/src/modules/modals/index.ts @@ -1,3 +1,3 @@ -export * from "./hooks"; export { default as ModalsProvider } from "./ModalsProvider"; export { default as withModal } from "./WithModal"; +export * from "./hooks"; diff --git a/frontend/src/modules/socketio/index.ts b/frontend/src/modules/socketio/index.ts index ac75c4b9b..bf8df7ea8 100644 --- a/frontend/src/modules/socketio/index.ts +++ b/frontend/src/modules/socketio/index.ts @@ -1,6 +1,6 @@ import { debounce, forIn, remove, uniq } from "lodash"; import { onlineManager } from "react-query"; -import { io, Socket } from "socket.io-client"; +import { Socket, io } from "socket.io-client"; import { Environment, isDevEnv, isTestEnv } from "../../utilities"; import { ENSURE, GROUP, LOG } from "../../utilities/console"; import { createDefaultReducer } from "./reducer"; diff --git a/frontend/src/pages/Episodes/index.tsx b/frontend/src/pages/Episodes/index.tsx index a4bcf3a94..3aaee0729 100644 --- a/frontend/src/pages/Episodes/index.tsx +++ b/frontend/src/pages/Episodes/index.tsx @@ -1,3 +1,4 @@ +import { RouterNames } from "@/Router/RouterNames"; import { useEpisodesBySeriesId, useIsAnyActionRunning, @@ -11,9 +12,8 @@ import { ItemEditModal } from "@/components/forms/ItemEditForm"; import { SeriesUploadModal } from "@/components/forms/SeriesUploadForm"; import { SubtitleToolsModal } from "@/components/modals"; import { useModals } from "@/modules/modals"; -import { notification, task, TaskGroup } from "@/modules/task"; +import { TaskGroup, notification, task } from "@/modules/task"; import ItemOverview from "@/pages/views/ItemOverview"; -import { RouterNames } from "@/Router/RouterNames"; import { useLanguageProfileBy } from "@/utilities/languages"; import { faAdjust, diff --git a/frontend/src/pages/Episodes/table.tsx b/frontend/src/pages/Episodes/table.tsx index 3fa0a89ba..1347869b0 100644 --- a/frontend/src/pages/Episodes/table.tsx +++ b/frontend/src/pages/Episodes/table.tsx @@ -1,10 +1,10 @@ import { useDownloadEpisodeSubtitles, useEpisodesProvider } from "@/apis/hooks"; import { useShowOnlyDesired } from "@/apis/hooks/site"; import { Action, GroupTable } from "@/components"; +import TextPopover from "@/components/TextPopover"; import { AudioList } from "@/components/bazarr"; import { EpisodeHistoryModal } from "@/components/modals"; import { EpisodeSearchModal } from "@/components/modals/ManualSearchModal"; -import TextPopover from "@/components/TextPopover"; import { useModals } from "@/modules/modals"; import { useTableStyles } from "@/styles"; import { BuildKey, filterSubtitleBy } from "@/utilities"; @@ -84,7 +84,7 @@ const Table: FunctionComponent = ({ episodes, profile, disabled }) => { { accessor: "season", Cell: (row) => { - return `Season ${row.value}`; + return Season {row.value}; }, }, { diff --git a/frontend/src/pages/History/Statistics/index.tsx b/frontend/src/pages/History/Statistics/index.tsx index f37d7b931..df03a0a65 100644 --- a/frontend/src/pages/History/Statistics/index.tsx +++ b/frontend/src/pages/History/Statistics/index.tsx @@ -11,8 +11,8 @@ import { useSelectorOptions } from "@/utilities"; import { Box, Container, - createStyles, SimpleGrid, + createStyles, useMantineTheme, } from "@mantine/core"; import { useDocumentTitle } from "@mantine/hooks"; diff --git a/frontend/src/pages/Movies/Details/index.tsx b/frontend/src/pages/Movies/Details/index.tsx index d8eaafb22..488850cb4 100644 --- a/frontend/src/pages/Movies/Details/index.tsx +++ b/frontend/src/pages/Movies/Details/index.tsx @@ -1,3 +1,4 @@ +import { RouterNames } from "@/Router/RouterNames"; import { useDownloadMovieSubtitles, useIsMovieActionRunning, @@ -15,9 +16,8 @@ import { MovieUploadModal } from "@/components/forms/MovieUploadForm"; import { MovieHistoryModal, SubtitleToolsModal } from "@/components/modals"; import { MovieSearchModal } from "@/components/modals/ManualSearchModal"; import { useModals } from "@/modules/modals"; -import { notification, task, TaskGroup } from "@/modules/task"; +import { TaskGroup, notification, task } from "@/modules/task"; import ItemOverview from "@/pages/views/ItemOverview"; -import { RouterNames } from "@/Router/RouterNames"; import { useLanguageProfileBy } from "@/utilities/languages"; import { faCloudUploadAlt, diff --git a/frontend/src/pages/Movies/index.tsx b/frontend/src/pages/Movies/index.tsx index d8431f1aa..479ed50ee 100644 --- a/frontend/src/pages/Movies/index.tsx +++ b/frontend/src/pages/Movies/index.tsx @@ -65,15 +65,19 @@ const MovieView: FunctionComponent = () => { accessor: "missing_subtitles", Cell: (row) => { const missing = row.value; - return missing.map((v) => ( - - - - )); + return ( + <> + {missing.map((v) => ( + + + + ))} + + ); }, }, { diff --git a/frontend/src/pages/Settings/Languages/table.tsx b/frontend/src/pages/Settings/Languages/table.tsx index 1e1c089e6..ac8da306c 100644 --- a/frontend/src/pages/Settings/Languages/table.tsx +++ b/frontend/src/pages/Settings/Languages/table.tsx @@ -1,7 +1,7 @@ import { Action, SimpleTable } from "@/components"; import { - anyCutoff, ProfileEditModal, + anyCutoff, } from "@/components/forms/ProfileEditForm"; import { useModals } from "@/modules/modals"; import { BuildKey, useArrayAction } from "@/utilities"; @@ -87,15 +87,19 @@ const Table: FunctionComponent = () => { Cell: (row) => { const items = row.value; if (!items) { - return false; + return null; } - return items.map((v, idx) => { - return ( - - {v} - - ); - }); + return ( + <> + {items.map((v, idx) => { + return ( + + {v} + + ); + })} + + ); }, }, { @@ -104,15 +108,19 @@ const Table: FunctionComponent = () => { Cell: (row) => { const items = row.value; if (!items) { - return false; + return null; } - return items.map((v, idx) => { - return ( - - {v} - - ); - }); + return ( + <> + {items.map((v, idx) => { + return ( + + {v} + + ); + })} + + ); }, }, { diff --git a/frontend/src/pages/Settings/Providers/components.tsx b/frontend/src/pages/Settings/Providers/components.tsx index 357a3f94a..16654c8b5 100644 --- a/frontend/src/pages/Settings/Providers/components.tsx +++ b/frontend/src/pages/Settings/Providers/components.tsx @@ -6,15 +6,15 @@ import { Button, Divider, Group, + Text as MantineText, SimpleGrid, Stack, - Text as MantineText, } from "@mantine/core"; import { useForm } from "@mantine/form"; import { capitalize } from "lodash"; import { - forwardRef, FunctionComponent, + forwardRef, useCallback, useMemo, useRef, @@ -28,8 +28,8 @@ import { useFormActions, useStagedValues, } from "../utilities/FormValues"; -import { useSettingValue } from "../utilities/hooks"; import { SettingsProvider, useSettings } from "../utilities/SettingsProvider"; +import { useSettingValue } from "../utilities/hooks"; import { ProviderInfo, ProviderList } from "./list"; const ProviderKey = "settings-general-enabled_providers"; diff --git a/frontend/src/pages/Settings/components/Message.tsx b/frontend/src/pages/Settings/components/Message.tsx index 2f334b793..301df7bab 100644 --- a/frontend/src/pages/Settings/components/Message.tsx +++ b/frontend/src/pages/Settings/components/Message.tsx @@ -1,9 +1,16 @@ import { Text } from "@mantine/core"; -import { FunctionComponent } from "react"; +import { FunctionComponent, PropsWithChildren } from "react"; -export const Message: FunctionComponent<{ +interface MessageProps { type?: "warning" | "info"; -}> = ({ type = "info", children }) => { +} + +type Props = PropsWithChildren; + +export const Message: FunctionComponent = ({ + type = "info", + children, +}) => { return ( {children} diff --git a/frontend/src/pages/Settings/components/Section.tsx b/frontend/src/pages/Settings/components/Section.tsx index 70226b633..36f56ff8d 100644 --- a/frontend/src/pages/Settings/components/Section.tsx +++ b/frontend/src/pages/Settings/components/Section.tsx @@ -1,12 +1,14 @@ import { Divider, Stack, Title } from "@mantine/core"; -import { FunctionComponent } from "react"; +import { FunctionComponent, PropsWithChildren } from "react"; interface SectionProps { header: string; hidden?: boolean; } -export const Section: FunctionComponent = ({ +type Props = PropsWithChildren; + +export const Section: FunctionComponent = ({ header, hidden, children, diff --git a/frontend/src/pages/Settings/components/collapse.tsx b/frontend/src/pages/Settings/components/collapse.tsx index 8f224f109..1dcffbd97 100644 --- a/frontend/src/pages/Settings/components/collapse.tsx +++ b/frontend/src/pages/Settings/components/collapse.tsx @@ -1,5 +1,5 @@ import { Collapse, Stack } from "@mantine/core"; -import { FunctionComponent, useMemo, useRef } from "react"; +import { FunctionComponent, PropsWithChildren, useMemo, useRef } from "react"; import { useSettingValue } from "../utilities/hooks"; interface ContentProps { @@ -8,7 +8,9 @@ interface ContentProps { indent?: boolean; } -const CollapseBox: FunctionComponent = ({ +type Props = PropsWithChildren; + +const CollapseBox: FunctionComponent = ({ on, indent, children, diff --git a/frontend/src/pages/Settings/components/forms.test.tsx b/frontend/src/pages/Settings/components/forms.test.tsx index 711c764a9..78ec5341e 100644 --- a/frontend/src/pages/Settings/components/forms.test.tsx +++ b/frontend/src/pages/Settings/components/forms.test.tsx @@ -1,11 +1,11 @@ import { rawRender, RenderOptions, screen } from "@/tests"; import { useForm } from "@mantine/form"; -import { FunctionComponent, ReactElement } from "react"; +import { FunctionComponent, PropsWithChildren, ReactElement } from "react"; import { describe, it } from "vitest"; import { FormContext, FormValues } from "../utilities/FormValues"; import { Number, Text } from "./forms"; -const FormSupport: FunctionComponent = ({ children }) => { +const FormSupport: FunctionComponent = ({ children }) => { const form = useForm({ initialValues: { settings: {}, diff --git a/frontend/src/pages/Settings/components/forms.tsx b/frontend/src/pages/Settings/components/forms.tsx index 03d2cb614..9457abd41 100644 --- a/frontend/src/pages/Settings/components/forms.tsx +++ b/frontend/src/pages/Settings/components/forms.tsx @@ -1,7 +1,7 @@ import { - Action as GlobalAction, FileBrowser, FileBrowserProps, + Action as GlobalAction, MultiSelector as GlobalMultiSelector, MultiSelectorProps as GlobalMultiSelectorProps, Selector as GlobalSelector, @@ -12,17 +12,17 @@ import ChipInput, { ChipInputProps } from "@/components/inputs/ChipInput"; import { useSliderMarks } from "@/utilities"; import { Input, + Slider as MantineSlider, + SliderProps as MantineSliderProps, NumberInput, NumberInputProps, PasswordInput, PasswordInputProps, - Slider as MantineSlider, - SliderProps as MantineSliderProps, Switch, TextInput, TextInputProps, } from "@mantine/core"; -import { FunctionComponent, ReactText } from "react"; +import { FunctionComponent, ReactNode, ReactText } from "react"; import { BaseInput, useBaseInput } from "../utilities/hooks"; export type NumberProps = BaseInput & NumberInputProps; @@ -34,7 +34,10 @@ export const Number: FunctionComponent = (props) => { { + onChange={(val) => { + if (val === "") { + val = 0; + } update(val); }} > @@ -126,7 +129,9 @@ export function MultiSelector( } type SliderProps = BaseInput & - Omit; + Omit & { + label?: ReactNode; + }; export const Slider: FunctionComponent = (props) => { const { value, update, rest } = useBaseInput(props); diff --git a/frontend/src/pages/Settings/components/index.tsx b/frontend/src/pages/Settings/components/index.tsx index 80c77f584..15b60db29 100644 --- a/frontend/src/pages/Settings/components/index.tsx +++ b/frontend/src/pages/Settings/components/index.tsx @@ -63,11 +63,11 @@ export const URLTestButton: FunctionComponent<{ }; export * from "./Card"; -export * from "./collapse"; -export { default as CollapseBox } from "./collapse"; -export * from "./forms"; export * from "./Layout"; export { default as Layout } from "./Layout"; export * from "./Message"; -export * from "./pathMapper"; export * from "./Section"; +export * from "./collapse"; +export { default as CollapseBox } from "./collapse"; +export * from "./forms"; +export * from "./pathMapper"; diff --git a/frontend/src/pages/Settings/utilities/SettingsProvider.tsx b/frontend/src/pages/Settings/utilities/SettingsProvider.tsx index 16bd86131..a15878c4a 100644 --- a/frontend/src/pages/Settings/utilities/SettingsProvider.tsx +++ b/frontend/src/pages/Settings/utilities/SettingsProvider.tsx @@ -1,4 +1,9 @@ -import { createContext, FunctionComponent, useContext } from "react"; +import { + createContext, + FunctionComponent, + PropsWithChildren, + useContext, +} from "react"; const SettingsContext = createContext(null); @@ -12,7 +17,9 @@ type SettingsProviderProps = { value: Settings | null; }; -export const SettingsProvider: FunctionComponent = ({ +type Props = PropsWithChildren; + +export const SettingsProvider: FunctionComponent = ({ value, children, }) => { diff --git a/frontend/src/pages/System/Status/index.tsx b/frontend/src/pages/System/Status/index.tsx index d6843c0dc..2fca8d26d 100644 --- a/frontend/src/pages/System/Status/index.tsx +++ b/frontend/src/pages/System/Status/index.tsx @@ -13,7 +13,13 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Anchor, Container, Divider, Grid, Stack, Text } from "@mantine/core"; import { useDocumentTitle } from "@mantine/hooks"; import moment from "moment"; -import { FunctionComponent, ReactNode, useCallback, useState } from "react"; +import { + FunctionComponent, + PropsWithChildren, + ReactNode, + useCallback, + useState, +} from "react"; import Table from "./table"; interface InfoProps { @@ -53,10 +59,13 @@ function Label(props: IconProps): JSX.Element { ); } -const InfoContainer: FunctionComponent<{ title: string }> = ({ - title, - children, -}) => { +interface InfoContainerProps { + title: string; +} + +const InfoContainer: FunctionComponent< + PropsWithChildren +> = ({ title, children }) => { return (

{title}

diff --git a/frontend/src/pages/Wanted/Movies/index.tsx b/frontend/src/pages/Wanted/Movies/index.tsx index d4334c9cd..102a41139 100644 --- a/frontend/src/pages/Wanted/Movies/index.tsx +++ b/frontend/src/pages/Wanted/Movies/index.tsx @@ -4,7 +4,7 @@ import { useMovieWantedPagination, } from "@/apis/hooks"; import Language from "@/components/bazarr/Language"; -import { task, TaskGroup } from "@/modules/task"; +import { TaskGroup, task } from "@/modules/task"; import WantedView from "@/pages/views/WantedView"; import { BuildKey } from "@/utilities"; import { faSearch } from "@fortawesome/free-solid-svg-icons"; diff --git a/frontend/src/pages/Wanted/Series/index.tsx b/frontend/src/pages/Wanted/Series/index.tsx index e04f91dca..5496a8530 100644 --- a/frontend/src/pages/Wanted/Series/index.tsx +++ b/frontend/src/pages/Wanted/Series/index.tsx @@ -4,7 +4,7 @@ import { useSeriesAction, } from "@/apis/hooks"; import Language from "@/components/bazarr/Language"; -import { task, TaskGroup } from "@/modules/task"; +import { TaskGroup, task } from "@/modules/task"; import WantedView from "@/pages/views/WantedView"; import { useTableStyles } from "@/styles"; import { BuildKey } from "@/utilities"; diff --git a/frontend/src/pages/views/ItemOverview.tsx b/frontend/src/pages/views/ItemOverview.tsx index 02a847878..8b537f475 100644 --- a/frontend/src/pages/views/ItemOverview.tsx +++ b/frontend/src/pages/views/ItemOverview.tsx @@ -5,17 +5,17 @@ import { useProfileItemsToLanguages, } from "@/utilities/languages"; import { - faBookmark as farBookmark, faFolder, + faBookmark as farBookmark, } from "@fortawesome/free-regular-svg-icons"; import { + IconDefinition, faBookmark, faClone, faLanguage, faMusic, faStream, faTags, - IconDefinition, } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { @@ -23,7 +23,6 @@ import { Badge, BadgeProps, Box, - createStyles, Grid, Group, HoverCard, @@ -33,6 +32,7 @@ import { Stack, Text, Title, + createStyles, } from "@mantine/core"; import { FunctionComponent, useMemo } from "react"; diff --git a/frontend/src/providers.tsx b/frontend/src/providers.tsx index 0d9350295..97189a23e 100644 --- a/frontend/src/providers.tsx +++ b/frontend/src/providers.tsx @@ -1,28 +1,24 @@ -import queryClient from "@/apis/queries"; import ThemeProvider from "@/App/theme"; +import queryClient from "@/apis/queries"; import { ModalsProvider } from "@/modules/modals"; import "@fontsource/roboto/300.css"; -import { NotificationsProvider } from "@mantine/notifications"; -import { FunctionComponent } from "react"; +import { Notifications } from "@mantine/notifications"; +import { FunctionComponent, PropsWithChildren } from "react"; import { QueryClientProvider } from "react-query"; import { ReactQueryDevtools } from "react-query/devtools"; -import { Router } from "./Router"; import { Environment } from "./utilities"; -export const AllProviders: FunctionComponent = ({ children }) => { +export const AllProviders: FunctionComponent = ({ + children, +}) => { return ( - - - {/* c8 ignore next 3 */} - {Environment.queryDev && ( - - )} - {children} - - + + {/* c8 ignore next 3 */} + {Environment.queryDev && } + {children} diff --git a/frontend/src/tests/index.tsx b/frontend/src/tests/index.tsx index 9053be909..b627626fd 100644 --- a/frontend/src/tests/index.tsx +++ b/frontend/src/tests/index.tsx @@ -1,11 +1,33 @@ import { AllProviders } from "@/providers"; import { render, RenderOptions } from "@testing-library/react"; -import { FunctionComponent, ReactElement, StrictMode } from "react"; +import { + FunctionComponent, + PropsWithChildren, + ReactElement, + StrictMode, +} from "react"; +import { + createBrowserRouter, + RouteObject, + RouterProvider, +} from "react-router-dom"; + +const AllProvidersWithStrictMode: FunctionComponent = ({ + children, +}) => { + const route: RouteObject = { + path: "/", + element: children, + }; + + // TODO: Update router system + const router = createBrowserRouter([route]); -const AllProvidersWithStrictMode: FunctionComponent = ({ children }) => { return ( - {children} + + + ); }; diff --git a/frontend/src/utilities/routers.ts b/frontend/src/utilities/routers.ts index 6dd988be7..f3f82c8cd 100644 --- a/frontend/src/utilities/routers.ts +++ b/frontend/src/utilities/routers.ts @@ -1,41 +1,9 @@ // A workaround of built-in hooks in React-Router v6 // https://gist.github.com/rmorse/426ffcc579922a82749934826fa9f743 -import type { Blocker, History, Transition } from "history"; -import { useContext, useEffect } from "react"; -// eslint-disable-next-line camelcase -import { UNSAFE_NavigationContext } from "react-router-dom"; - -export function useBlocker(blocker: Blocker, when = true) { - const navigator = useContext(UNSAFE_NavigationContext).navigator as History; - - useEffect(() => { - if (!when) return; - - const unblock = navigator.block((tx: Transition) => { - const autoUnblockingTx = { - ...tx, - retry() { - // Automatically unblock the transition so it can play all the way - // through before retrying it. TODO: Figure out how to re-enable - // this block if the transition is cancelled for some reason. - unblock(); - tx.retry(); - }, - }; - - blocker(autoUnblockingTx); - }); - - return unblock; - }, [navigator, blocker, when]); -} +import { unstable_usePrompt as useUnstablePrompt } from "react-router-dom"; // TODO: Replace with Mantine's confirmation modal export function usePrompt(when: boolean, message: string) { - useBlocker((tx) => { - if (window.confirm(message)) { - tx.retry(); - } - }, when); + useUnstablePrompt({ when, message }); } From 6a9f875cbd588a43864a7772c3ac5ed8596c5404 Mon Sep 17 00:00:00 2001 From: LASER-Yi Date: Mon, 5 Jun 2023 09:32:30 +0800 Subject: [PATCH 29/57] Downgrade Axios to fix a settings saving issue. --- frontend/package-lock.json | 18 ++++++------------ frontend/package.json | 2 +- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e722ba67a..f4fd62275 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15,7 +15,7 @@ "@mantine/hooks": "^6.0.0", "@mantine/modals": "^6.0.0", "@mantine/notifications": "^6.0.0", - "axios": "^1.3.0", + "axios": "^0.27.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-query": "^3.39.2", @@ -4293,13 +4293,12 @@ } }, "node_modules/axios": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.6.tgz", - "integrity": "sha512-PEcdkk7JcdPiMDkvM4K6ZBRYq9keuVJsToxm2zQIM70Qqo2WHTdJZMXcG9X+RmRp2VPNUQC8W1RAGbgt6b1yMg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", "dependencies": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" } }, "node_modules/axobject-query": { @@ -8398,11 +8397,6 @@ "react-is": "^16.13.1" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 2ef06b97d..8b0c7c6df 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,7 +19,7 @@ "@mantine/hooks": "^6.0.0", "@mantine/modals": "^6.0.0", "@mantine/notifications": "^6.0.0", - "axios": "^1.3.0", + "axios": "^0.27.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-query": "^3.39.2", From 0956d401bc11854a2abaeef100ce6e5cf75aeb20 Mon Sep 17 00:00:00 2001 From: morpheus65535 Date: Wed, 7 Jun 2023 08:07:47 -0400 Subject: [PATCH 30/57] Added debug logging for request/response to opensubtitles.com provider. #2153 --- .../providers/opensubtitlescom.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/libs/subliminal_patch/providers/opensubtitlescom.py b/libs/subliminal_patch/providers/opensubtitlescom.py index 2d735746c..2ea6b29de 100644 --- a/libs/subliminal_patch/providers/opensubtitlescom.py +++ b/libs/subliminal_patch/providers/opensubtitlescom.py @@ -494,8 +494,10 @@ def checked(fn, raise_api_limit=False, validate_token=False, validate_json=False if validate_token: return 401 else: + log_request_response(response) raise AuthenticationError(f'Login failed: {response.reason}') elif status_code == 403: + log_request_response(response) raise ProviderError("Bazarr API key seems to be in problem") elif status_code == 406: try: @@ -506,12 +508,15 @@ def checked(fn, raise_api_limit=False, validate_token=False, validate_json=False except JSONDecodeError: raise ProviderError('Invalid JSON returned by provider') else: + log_request_response(response) raise DownloadLimitExceeded(f"Daily download limit reached. {download_count} subtitles have been " f"downloaded and {remaining_download} remaining subtitles can be " f"downloaded. Quota will be reset in {quota_reset_time}.") elif status_code == 410: + log_request_response(response) raise ProviderError("Download as expired") elif status_code == 429: + log_request_response(response) raise TooManyRequests() elif status_code == 502: # this one should deal with Bad Gateway issue on their side. @@ -520,6 +525,7 @@ def checked(fn, raise_api_limit=False, validate_token=False, validate_json=False raise ProviderError(response.reason) if status_code != 200: + log_request_response(response) raise ProviderError(f'Bad status code: {response.status_code}') if validate_json: @@ -540,3 +546,13 @@ def checked(fn, raise_api_limit=False, validate_token=False, validate_json=False return False return response + + +def log_request_response(response): + logging.debug("opensubtitlescom returned a non standard response. Logging request/response for debugging purpose.") + logging.debug(f"Request URL: {response.request.url}") + logging.debug(f"Request Headers: {response.request.headers}") + logging.debug(f"Request Body: {response.request.body}") + logging.debug(f"Response Status Code: {response.status_code}") + logging.debug(f"Response Headers: {response.headers}") + logging.debug(f"Response Body: {response.text}") From 07f601f407ef5b9e6fe0b0db842f3bec8c9916b0 Mon Sep 17 00:00:00 2001 From: morpheus65535 Date: Wed, 7 Jun 2023 14:05:42 -0400 Subject: [PATCH 31/57] Updated apprise module to improve notification system. #2163 --- libs/apprise/Apprise.py | 284 +++-- libs/apprise/AppriseAsset.py | 43 +- libs/apprise/AppriseAttachment.py | 54 +- libs/apprise/AppriseConfig.py | 43 +- libs/apprise/AppriseLocale.py | 45 +- libs/apprise/URLBase.py | 77 +- libs/apprise/__init__.py | 49 +- libs/apprise/attachment/AttachBase.py | 45 +- libs/apprise/attachment/AttachFile.py | 45 +- libs/apprise/attachment/AttachHTTP.py | 47 +- libs/apprise/attachment/__init__.py | 44 +- libs/apprise/cli.py | 93 +- libs/apprise/common.py | 44 +- libs/apprise/common.pyi | 9 +- libs/apprise/config/ConfigBase.py | 47 +- libs/apprise/config/ConfigFile.py | 45 +- libs/apprise/config/ConfigHTTP.py | 45 +- libs/apprise/config/ConfigMemory.py | 45 +- libs/apprise/config/__init__.py | 43 +- libs/apprise/conversion.py | 45 +- libs/apprise/decorators/CustomNotifyPlugin.py | 46 +- libs/apprise/decorators/__init__.py | 44 +- libs/apprise/decorators/notify.py | 44 +- libs/apprise/logger.py | 44 +- libs/apprise/plugins/NotifyAppriseAPI.py | 170 ++- libs/apprise/plugins/NotifyBark.py | 54 +- libs/apprise/plugins/NotifyBase.py | 133 ++- libs/apprise/plugins/NotifyBoxcar.py | 92 +- libs/apprise/plugins/NotifyBulkSMS.py | 64 +- libs/apprise/plugins/NotifyClickSend.py | 60 +- libs/apprise/plugins/NotifyD7Networks.py | 281 +++-- libs/apprise/plugins/NotifyDBus.py | 70 +- libs/apprise/plugins/NotifyDapnet.py | 60 +- libs/apprise/plugins/NotifyDingTalk.py | 52 +- libs/apprise/plugins/NotifyDiscord.py | 56 +- libs/apprise/plugins/NotifyEmail.py | 398 ++++--- libs/apprise/plugins/NotifyEmby.py | 48 +- libs/apprise/plugins/NotifyEnigma2.py | 45 +- libs/apprise/plugins/NotifyFCM/__init__.py | 51 +- libs/apprise/plugins/NotifyFCM/color.py | 43 +- libs/apprise/plugins/NotifyFCM/common.py | 44 +- libs/apprise/plugins/NotifyFCM/oauth.py | 43 +- libs/apprise/plugins/NotifyFCM/priority.py | 43 +- libs/apprise/plugins/NotifyFaast.py | 44 +- libs/apprise/plugins/NotifyFlock.py | 52 +- libs/apprise/plugins/NotifyForm.py | 215 +++- libs/apprise/plugins/NotifyGitter.py | 51 +- libs/apprise/plugins/NotifyGnome.py | 62 +- libs/apprise/plugins/NotifyGoogleChat.py | 104 +- libs/apprise/plugins/NotifyGotify.py | 48 +- libs/apprise/plugins/NotifyGrowl.py | 45 +- libs/apprise/plugins/NotifyGuilded.py | 43 +- libs/apprise/plugins/NotifyHomeAssistant.py | 45 +- libs/apprise/plugins/NotifyIFTTT.py | 52 +- libs/apprise/plugins/NotifyJSON.py | 137 ++- libs/apprise/plugins/NotifyJoin.py | 51 +- libs/apprise/plugins/NotifyKavenegar.py | 51 +- libs/apprise/plugins/NotifyKumulos.py | 45 +- libs/apprise/plugins/NotifyLametric.py | 45 +- libs/apprise/plugins/NotifyLine.py | 52 +- libs/apprise/plugins/NotifyMQTT.py | 111 +- libs/apprise/plugins/NotifyMSG91.py | 51 +- libs/apprise/plugins/NotifyMSTeams.py | 45 +- libs/apprise/plugins/NotifyMacOSX.py | 79 +- libs/apprise/plugins/NotifyMailgun.py | 123 ++- libs/apprise/plugins/NotifyMastodon.py | 990 ++++++++++++++++++ libs/apprise/plugins/NotifyMatrix.py | 61 +- libs/apprise/plugins/NotifyMatterMost.py | 45 +- libs/apprise/plugins/NotifyMessageBird.py | 52 +- libs/apprise/plugins/NotifyMisskey.py | 310 ++++++ libs/apprise/plugins/NotifyNextcloud.py | 50 +- libs/apprise/plugins/NotifyNextcloudTalk.py | 57 +- libs/apprise/plugins/NotifyNotica.py | 44 +- libs/apprise/plugins/NotifyNotifico.py | 45 +- libs/apprise/plugins/NotifyNtfy.py | 227 +++- libs/apprise/plugins/NotifyOffice365.py | 51 +- libs/apprise/plugins/NotifyOneSignal.py | 73 +- libs/apprise/plugins/NotifyOpsgenie.py | 65 +- libs/apprise/plugins/NotifyPagerDuty.py | 86 +- libs/apprise/plugins/NotifyPagerTree.py | 424 ++++++++ libs/apprise/plugins/NotifyParsePlatform.py | 45 +- libs/apprise/plugins/NotifyPopcornNotify.py | 60 +- libs/apprise/plugins/NotifyProwl.py | 45 +- libs/apprise/plugins/NotifyPushBullet.py | 51 +- libs/apprise/plugins/NotifyPushSafer.py | 51 +- libs/apprise/plugins/NotifyPushed.py | 52 +- libs/apprise/plugins/NotifyPushjet.py | 45 +- libs/apprise/plugins/NotifyPushover.py | 53 +- libs/apprise/plugins/NotifyReddit.py | 52 +- libs/apprise/plugins/NotifyRocketChat.py | 61 +- libs/apprise/plugins/NotifyRyver.py | 45 +- libs/apprise/plugins/NotifySES.py | 52 +- libs/apprise/plugins/NotifySMSEagle.py | 94 +- libs/apprise/plugins/NotifySMTP2Go.py | 60 +- libs/apprise/plugins/NotifySNS.py | 51 +- libs/apprise/plugins/NotifySendGrid.py | 52 +- libs/apprise/plugins/NotifyServerChan.py | 45 +- libs/apprise/plugins/NotifySignalAPI.py | 60 +- libs/apprise/plugins/NotifySimplePush.py | 46 +- libs/apprise/plugins/NotifySinch.py | 52 +- libs/apprise/plugins/NotifySlack.py | 98 +- libs/apprise/plugins/NotifySparkPost.py | 71 +- libs/apprise/plugins/NotifySpontit.py | 52 +- libs/apprise/plugins/NotifyStreamlabs.py | 45 +- libs/apprise/plugins/NotifySyslog.py | 46 +- libs/apprise/plugins/NotifyTechulusPush.py | 45 +- libs/apprise/plugins/NotifyTelegram.py | 82 +- libs/apprise/plugins/NotifyTwilio.py | 52 +- libs/apprise/plugins/NotifyTwist.py | 52 +- libs/apprise/plugins/NotifyTwitter.py | 163 ++- libs/apprise/plugins/NotifyVoipms.py | 379 +++++++ libs/apprise/plugins/NotifyVonage.py | 52 +- libs/apprise/plugins/NotifyWebexTeams.py | 56 +- libs/apprise/plugins/NotifyWindows.py | 47 +- libs/apprise/plugins/NotifyXBMC.py | 45 +- libs/apprise/plugins/NotifyXML.py | 145 ++- libs/apprise/plugins/NotifyZulip.py | 51 +- libs/apprise/plugins/__init__.py | 56 +- libs/apprise/py3compat/__init__.py | 0 libs/apprise/py3compat/asyncio.py | 140 --- libs/apprise/utils.py | 85 +- libs/version.txt | 2 +- 122 files changed, 7156 insertions(+), 2988 deletions(-) create mode 100644 libs/apprise/plugins/NotifyMastodon.py create mode 100644 libs/apprise/plugins/NotifyMisskey.py create mode 100644 libs/apprise/plugins/NotifyPagerTree.py create mode 100644 libs/apprise/plugins/NotifyVoipms.py delete mode 100644 libs/apprise/py3compat/__init__.py delete mode 100644 libs/apprise/py3compat/asyncio.py diff --git a/libs/apprise/Apprise.py b/libs/apprise/Apprise.py index 39a1ff0aa..8c2cf5330 100644 --- a/libs/apprise/Apprise.py +++ b/libs/apprise/Apprise.py @@ -1,28 +1,37 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +import asyncio +import concurrent.futures as cf import os from itertools import chain from . import common @@ -43,11 +52,6 @@ from .plugins.NotifyBase import NotifyBase from . import plugins from . import __version__ -# Python v3+ support code made importable, so it can remain backwards -# compatible with Python v2 -# TODO: Review after dropping support for Python 2. -from . import py3compat - class Apprise: """ @@ -369,91 +373,83 @@ class Apprise: such as turning a \n into an actual new line, etc. """ - return py3compat.asyncio.tosync( - self.async_notify( + try: + # Process arguments and build synchronous and asynchronous calls + # (this step can throw internal errors). + sequential_calls, parallel_calls = self._create_notify_calls( body, title, notify_type=notify_type, body_format=body_format, tag=tag, match_always=match_always, attach=attach, - interpret_escapes=interpret_escapes, - ), - debug=self.debug - ) + interpret_escapes=interpret_escapes + ) - def async_notify(self, *args, **kwargs): + except TypeError: + # No notifications sent, and there was an internal error. + return False + + if not sequential_calls and not parallel_calls: + # Nothing to send + return None + + sequential_result = Apprise._notify_sequential(*sequential_calls) + parallel_result = Apprise._notify_parallel_threadpool(*parallel_calls) + return sequential_result and parallel_result + + async def async_notify(self, *args, **kwargs): """ Send a notification to all the plugins previously loaded, for - asynchronous callers. This method is an async method that should be - awaited on, even if it is missing the async keyword in its signature. - (This is omitted to preserve syntax compatibility with Python 2.) + asynchronous callers. The arguments are identical to those of Apprise.notify(). """ try: - coroutines = list( - self._notifyall( - Apprise._notifyhandlerasync, *args, **kwargs)) + # Process arguments and build synchronous and asynchronous calls + # (this step can throw internal errors). + sequential_calls, parallel_calls = self._create_notify_calls( + *args, **kwargs) except TypeError: # No notifications sent, and there was an internal error. - return py3compat.asyncio.toasyncwrapvalue(False) - - else: - if len(coroutines) > 0: - # All notifications sent, return False if any failed. - return py3compat.asyncio.notify(coroutines) - - else: - # No notifications sent. - return py3compat.asyncio.toasyncwrapvalue(None) - - @staticmethod - def _notifyhandler(server, **kwargs): - """ - The synchronous notification sender. Returns True if the notification - sent successfully. - """ - - try: - # Send notification - return server.notify(**kwargs) - - except TypeError: - # These our our internally thrown notifications return False - except Exception: - # A catch all so we don't have to abort early - # just because one of our plugins has a bug in it. - logger.exception("Unhandled Notification Exception") - return False + if not sequential_calls and not parallel_calls: + # Nothing to send + return None - @staticmethod - def _notifyhandlerasync(server, **kwargs): - """ - The asynchronous notification sender. Returns a coroutine that yields - True if the notification sent successfully. - """ + sequential_result = Apprise._notify_sequential(*sequential_calls) + parallel_result = \ + await Apprise._notify_parallel_asyncio(*parallel_calls) + return sequential_result and parallel_result - if server.asset.async_mode: - return server.async_notify(**kwargs) - - else: - # Send the notification immediately, and wrap the result in a - # coroutine. - status = Apprise._notifyhandler(server, **kwargs) - return py3compat.asyncio.toasyncwrapvalue(status) - - def _notifyall(self, handler, body, title='', - notify_type=common.NotifyType.INFO, body_format=None, - tag=common.MATCH_ALL_TAG, match_always=True, attach=None, - interpret_escapes=None): + def _create_notify_calls(self, *args, **kwargs): """ Creates notifications for all the plugins loaded. - Returns a generator that calls handler for each notification. The first - and only argument supplied to handler is the server, and the keyword - arguments are exactly as they would be passed to server.notify(). + Returns a list of (server, notify() kwargs) tuples for plugins with + parallelism disabled and another list for plugins with parallelism + enabled. + """ + + all_calls = list(self._create_notify_gen(*args, **kwargs)) + + # Split into sequential and parallel notify() calls. + sequential, parallel = [], [] + for (server, notify_kwargs) in all_calls: + if server.asset.async_mode: + parallel.append((server, notify_kwargs)) + else: + sequential.append((server, notify_kwargs)) + + return sequential, parallel + + def _create_notify_gen(self, body, title='', + notify_type=common.NotifyType.INFO, + body_format=None, tag=common.MATCH_ALL_TAG, + match_always=True, attach=None, + interpret_escapes=None): + """ + Internal generator function for _create_notify_calls(). """ if len(self) == 0: @@ -546,14 +542,121 @@ class Apprise: logger.error(msg) raise TypeError(msg) - yield handler( - server, + kwargs = dict( body=conversion_body_map[server.notify_format], title=conversion_title_map[server.notify_format], notify_type=notify_type, attach=attach, - body_format=body_format, + body_format=body_format ) + yield (server, kwargs) + + @staticmethod + def _notify_sequential(*servers_kwargs): + """ + Process a list of notify() calls sequentially and synchronously. + """ + + success = True + + for (server, kwargs) in servers_kwargs: + try: + # Send notification + result = server.notify(**kwargs) + success = success and result + + except TypeError: + # These are our internally thrown notifications. + success = False + + except Exception: + # A catch all so we don't have to abort early + # just because one of our plugins has a bug in it. + logger.exception("Unhandled Notification Exception") + success = False + + return success + + @staticmethod + def _notify_parallel_threadpool(*servers_kwargs): + """ + Process a list of notify() calls in parallel and synchronously. + """ + + n_calls = len(servers_kwargs) + + # 0-length case + if n_calls == 0: + return True + + # There's no need to use a thread pool for just a single notification + if n_calls == 1: + return Apprise._notify_sequential(servers_kwargs[0]) + + # Create log entry + logger.info( + 'Notifying %d service(s) with threads.', len(servers_kwargs)) + + with cf.ThreadPoolExecutor() as executor: + success = True + futures = [executor.submit(server.notify, **kwargs) + for (server, kwargs) in servers_kwargs] + + for future in cf.as_completed(futures): + try: + result = future.result() + success = success and result + + except TypeError: + # These are our internally thrown notifications. + success = False + + except Exception: + # A catch all so we don't have to abort early + # just because one of our plugins has a bug in it. + logger.exception("Unhandled Notification Exception") + success = False + + return success + + @staticmethod + async def _notify_parallel_asyncio(*servers_kwargs): + """ + Process a list of async_notify() calls in parallel and asynchronously. + """ + + n_calls = len(servers_kwargs) + + # 0-length case + if n_calls == 0: + return True + + # (Unlike with the thread pool, we don't optimize for the single- + # notification case because asyncio can do useful work while waiting + # for that thread to complete) + + # Create log entry + logger.info( + 'Notifying %d service(s) asynchronously.', len(servers_kwargs)) + + async def do_call(server, kwargs): + return await server.async_notify(**kwargs) + + cors = (do_call(server, kwargs) for (server, kwargs) in servers_kwargs) + results = await asyncio.gather(*cors, return_exceptions=True) + + if any(isinstance(status, Exception) + and not isinstance(status, TypeError) for status in results): + # A catch all so we don't have to abort early just because + # one of our plugins has a bug in it. + logger.exception("Unhandled Notification Exception") + return False + + if any(isinstance(status, TypeError) for status in results): + # These are our internally thrown notifications. + return False + + return all(results) def details(self, lang=None, show_requirements=False, show_disabled=False): """ @@ -581,6 +684,7 @@ class Apprise: 'setup_url': getattr(plugin, 'setup_url', None), # Placeholder - populated below 'details': None, + # Differentiat between what is a custom loaded plugin and # which is native. 'category': getattr(plugin, 'category', None) diff --git a/libs/apprise/AppriseAsset.py b/libs/apprise/AppriseAsset.py index 80bd0656c..34821e278 100644 --- a/libs/apprise/AppriseAsset.py +++ b/libs/apprise/AppriseAsset.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. import re from uuid import uuid4 diff --git a/libs/apprise/AppriseAttachment.py b/libs/apprise/AppriseAttachment.py index b808cfaee..0a3913ed0 100644 --- a/libs/apprise/AppriseAttachment.py +++ b/libs/apprise/AppriseAttachment.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. from . import attachment from . import URLBase @@ -170,6 +177,11 @@ class AppriseAttachment: return_status = False continue + elif isinstance(_attachment, AppriseAttachment): + # We were provided a list of Apprise Attachments + # append our content together + instance = _attachment.attachments + elif not isinstance(_attachment, attachment.AttachBase): logger.warning( "An invalid attachment (type={}) was specified.".format( @@ -196,7 +208,11 @@ class AppriseAttachment: continue # Add our initialized plugin to our server listings - self.attachments.append(instance) + if isinstance(instance, list): + self.attachments.extend(instance) + + else: + self.attachments.append(instance) # Return our status return return_status diff --git a/libs/apprise/AppriseConfig.py b/libs/apprise/AppriseConfig.py index f92d31d18..8f2857776 100644 --- a/libs/apprise/AppriseConfig.py +++ b/libs/apprise/AppriseConfig.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. from . import config from . import ConfigBase diff --git a/libs/apprise/AppriseLocale.py b/libs/apprise/AppriseLocale.py index 9da8467b7..ce61d0c9b 100644 --- a/libs/apprise/AppriseLocale.py +++ b/libs/apprise/AppriseLocale.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. import ctypes import locale @@ -67,7 +74,7 @@ class LazyTranslation: """ self.text = text - super(LazyTranslation, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def __str__(self): return gettext.gettext(self.text) diff --git a/libs/apprise/URLBase.py b/libs/apprise/URLBase.py index eb4a379e4..4b33920ea 100644 --- a/libs/apprise/URLBase.py +++ b/libs/apprise/URLBase.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. import re from .logger import logger @@ -194,7 +201,7 @@ class URLBase: asset if isinstance(asset, AppriseAsset) else AppriseAsset() # Certificate Verification (for SSL calls); default to being enabled - self.verify_certificate = kwargs.get('verify', True) + self.verify_certificate = parse_bool(kwargs.get('verify', True)) # Secure Mode self.secure = kwargs.get('secure', False) @@ -222,24 +229,22 @@ class URLBase: self.password = URLBase.unquote(self.password) # Store our Timeout Variables - if 'socket_read_timeout' in kwargs: + if 'rto' in kwargs: try: - self.socket_read_timeout = \ - float(kwargs.get('socket_read_timeout')) + self.socket_read_timeout = float(kwargs.get('rto')) except (TypeError, ValueError): self.logger.warning( 'Invalid socket read timeout (rto) was specified {}' - .format(kwargs.get('socket_read_timeout'))) + .format(kwargs.get('rto'))) - if 'socket_connect_timeout' in kwargs: + if 'cto' in kwargs: try: - self.socket_connect_timeout = \ - float(kwargs.get('socket_connect_timeout')) + self.socket_connect_timeout = float(kwargs.get('cto')) except (TypeError, ValueError): self.logger.warning( 'Invalid socket connect timeout (cto) was specified {}' - .format(kwargs.get('socket_connect_timeout'))) + .format(kwargs.get('cto'))) if 'tag' in kwargs: # We want to associate some tags with our notification service. @@ -598,7 +603,7 @@ class URLBase: } @staticmethod - def parse_url(url, verify_host=True): + def parse_url(url, verify_host=True, plus_to_space=False): """Parses the URL and returns it broken apart into a dictionary. This is very specific and customized for Apprise. @@ -618,7 +623,8 @@ class URLBase: """ results = parse_url( - url, default_schema='unknown', verify_host=verify_host) + url, default_schema='unknown', verify_host=verify_host, + plus_to_space=plus_to_space) if not results: # We're done; we failed to parse our url @@ -646,11 +652,11 @@ class URLBase: # Store our socket read timeout if specified if 'rto' in results['qsd']: - results['socket_read_timeout'] = results['qsd']['rto'] + results['rto'] = results['qsd']['rto'] # Store our socket connect timeout if specified if 'cto' in results['qsd']: - results['socket_connect_timeout'] = results['qsd']['cto'] + results['cto'] = results['qsd']['cto'] if 'port' in results['qsd']: results['port'] = results['qsd']['port'] @@ -679,6 +685,15 @@ class URLBase: return response + def __len__(self): + """ + Should be over-ridden and allows the tracking of how many targets + are associated with each URLBase object. + + Default is always 1 + """ + return 1 + def schemas(self): """A simple function that returns a set of all schemas associated with this object based on the object.protocol and diff --git a/libs/apprise/__init__.py b/libs/apprise/__init__.py index 04ae0982d..3a9136e96 100644 --- a/libs/apprise/__init__.py +++ b/libs/apprise/__init__.py @@ -1,33 +1,40 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2022 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. __title__ = 'Apprise' -__version__ = '1.1.0' +__version__ = '1.4.0' __author__ = 'Chris Caron' -__license__ = 'MIT' -__copywrite__ = 'Copyright (C) 2022 Chris Caron ' +__license__ = 'BSD' +__copywrite__ = 'Copyright (C) 2023 Chris Caron ' __email__ = 'lead2gold@gmail.com' __status__ = 'Production' diff --git a/libs/apprise/attachment/AttachBase.py b/libs/apprise/attachment/AttachBase.py index 16f6c6429..2b05c8497 100644 --- a/libs/apprise/attachment/AttachBase.py +++ b/libs/apprise/attachment/AttachBase.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. import os import time @@ -120,7 +127,7 @@ class AttachBase(URLBase): should be considered expired. """ - super(AttachBase, self).__init__(**kwargs) + super().__init__(**kwargs) if not mimetypes.inited: # Ensure mimetypes has been initialized diff --git a/libs/apprise/attachment/AttachFile.py b/libs/apprise/attachment/AttachFile.py index 20ee15199..f89b915eb 100644 --- a/libs/apprise/attachment/AttachFile.py +++ b/libs/apprise/attachment/AttachFile.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. import re import os @@ -50,7 +57,7 @@ class AttachFile(AttachBase): Initialize Local File Attachment Object """ - super(AttachFile, self).__init__(**kwargs) + super().__init__(**kwargs) # Store path but mark it dirty since we have not performed any # verification at this point. diff --git a/libs/apprise/attachment/AttachHTTP.py b/libs/apprise/attachment/AttachHTTP.py index da1d698e8..d8b46ff28 100644 --- a/libs/apprise/attachment/AttachHTTP.py +++ b/libs/apprise/attachment/AttachHTTP.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. import re import os @@ -61,7 +68,7 @@ class AttachHTTP(AttachBase): additionally include as part of the server headers to post with """ - super(AttachHTTP, self).__init__(**kwargs) + super().__init__(**kwargs) self.schema = 'https' if self.secure else 'http' @@ -254,7 +261,7 @@ class AttachHTTP(AttachBase): self._temp_file.close() self._temp_file = None - super(AttachHTTP, self).invalidate() + super().invalidate() def url(self, privacy=False, *args, **kwargs): """ diff --git a/libs/apprise/attachment/__init__.py b/libs/apprise/attachment/__init__.py index 7f83769a8..1b0e1bfe6 100644 --- a/libs/apprise/attachment/__init__.py +++ b/libs/apprise/attachment/__init__.py @@ -1,30 +1,36 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. import re - from os import listdir from os.path import dirname from os.path import abspath diff --git a/libs/apprise/cli.py b/libs/apprise/cli.py index 0e60e5cd2..a3335bbb5 100644 --- a/libs/apprise/cli.py +++ b/libs/apprise/cli.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- - -# Copyright (C) 2019 Chris Caron -# All rights reserved. +# BSD 3-Clause License # -# This code is licensed under the MIT License. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. import click import logging @@ -73,28 +80,64 @@ DEFAULT_CONFIG_PATHS = ( '~/.apprise/apprise.yml', '~/.config/apprise/apprise', '~/.config/apprise/apprise.yml', + + # Global Configuration Support + '/etc/apprise', + '/etc/apprise.yml', + '/etc/apprise/apprise', + '/etc/apprise/apprise.yml', ) # Define our paths to search for plugins DEFAULT_PLUGIN_PATHS = ( '~/.apprise/plugins', '~/.config/apprise/plugins', + + # Global Plugin Support + '/var/lib/apprise/plugins', ) # Detect Windows if platform.system() == 'Windows': # Default Config Search Path for Windows Users DEFAULT_CONFIG_PATHS = ( - expandvars('%APPDATA%/Apprise/apprise'), - expandvars('%APPDATA%/Apprise/apprise.yml'), - expandvars('%LOCALAPPDATA%/Apprise/apprise'), - expandvars('%LOCALAPPDATA%/Apprise/apprise.yml'), + expandvars('%APPDATA%\\Apprise\\apprise'), + expandvars('%APPDATA%\\Apprise\\apprise.yml'), + expandvars('%LOCALAPPDATA%\\Apprise\\apprise'), + expandvars('%LOCALAPPDATA%\\Apprise\\apprise.yml'), + + # + # Global Support + # + + # C:\ProgramData\Apprise\ + expandvars('%ALLUSERSPROFILE%\\Apprise\\apprise'), + expandvars('%ALLUSERSPROFILE%\\Apprise\\apprise.yml'), + + # C:\Program Files\Apprise + expandvars('%PROGRAMFILES%\\Apprise\\apprise'), + expandvars('%PROGRAMFILES%\\Apprise\\apprise.yml'), + + # C:\Program Files\Common Files + expandvars('%COMMONPROGRAMFILES%\\Apprise\\apprise'), + expandvars('%COMMONPROGRAMFILES%\\Apprise\\apprise.yml'), ) # Default Plugin Search Path for Windows Users DEFAULT_PLUGIN_PATHS = ( - expandvars('%APPDATA%/Apprise/plugins'), - expandvars('%LOCALAPPDATA%/Apprise/plugins'), + expandvars('%APPDATA%\\Apprise\\plugins'), + expandvars('%LOCALAPPDATA%\\Apprise\\plugins'), + + # + # Global Support + # + + # C:\ProgramData\Apprise\plugins + expandvars('%ALLUSERSPROFILE%\\Apprise\\plugins'), + # C:\Program Files\Apprise\plugins + expandvars('%PROGRAMFILES%\\Apprise\\plugins'), + # C:\Program Files\Common Files + expandvars('%COMMONPROGRAMFILES%\\Apprise\\plugins'), ) diff --git a/libs/apprise/common.py b/libs/apprise/common.py index 958bd22ee..8380c405e 100644 --- a/libs/apprise/common.py +++ b/libs/apprise/common.py @@ -1,28 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. # we mirror our base purely for the ability to reset everything; this # is generally only used in testing and should not be used by developers diff --git a/libs/apprise/common.pyi b/libs/apprise/common.pyi index 769573185..862fc4f27 100644 --- a/libs/apprise/common.pyi +++ b/libs/apprise/common.pyi @@ -1,3 +1,7 @@ +import types +import typing as t + + class NotifyType: INFO: NotifyType SUCCESS: NotifyType @@ -12,4 +16,7 @@ class NotifyFormat: class ContentLocation: LOCAL: ContentLocation HOSTED: ContentLocation - INACCESSIBLE: ContentLocation \ No newline at end of file + INACCESSIBLE: ContentLocation + + +NOTIFY_MODULE_MAP: t.Dict[str, t.Dict[str, t.Union[t.Type["NotifyBase"], types.ModuleType]]] diff --git a/libs/apprise/config/ConfigBase.py b/libs/apprise/config/ConfigBase.py index d504a98dd..5eb73ebcb 100644 --- a/libs/apprise/config/ConfigBase.py +++ b/libs/apprise/config/ConfigBase.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2020 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. import os import re @@ -113,7 +120,7 @@ class ConfigBase(URLBase): these 'include' entries to be honored, this value must be set to True. """ - super(ConfigBase, self).__init__(**kwargs) + super().__init__(**kwargs) # Tracks the time the content was last retrieved on. This place a role # for cases where we are not caching our response and are required to @@ -548,7 +555,7 @@ class ConfigBase(URLBase): # Define what a valid line should look like valid_line_re = re.compile( r'^\s*(?P([;#]+(?P.*))|' - r'(\s*(?P[^=]+)=|=)?\s*' + r'(\s*(?P[a-z0-9, \t_-]+)\s*=|=)?\s*' r'(?P[a-z0-9]{2,9}://.*)|' r'include\s+(?P.+))?\s*$', re.I) diff --git a/libs/apprise/config/ConfigFile.py b/libs/apprise/config/ConfigFile.py index 10f0a463c..b2c211766 100644 --- a/libs/apprise/config/ConfigFile.py +++ b/libs/apprise/config/ConfigFile.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. import re import os @@ -53,7 +60,7 @@ class ConfigFile(ConfigBase): additionally include as part of the server headers to post with """ - super(ConfigFile, self).__init__(**kwargs) + super().__init__(**kwargs) # Store our file path as it was set self.path = os.path.abspath(os.path.expanduser(path)) diff --git a/libs/apprise/config/ConfigHTTP.py b/libs/apprise/config/ConfigHTTP.py index 795c6fac8..5813b90a9 100644 --- a/libs/apprise/config/ConfigHTTP.py +++ b/libs/apprise/config/ConfigHTTP.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. import re import requests @@ -75,7 +82,7 @@ class ConfigHTTP(ConfigBase): additionally include as part of the server headers to post with """ - super(ConfigHTTP, self).__init__(**kwargs) + super().__init__(**kwargs) self.schema = 'https' if self.secure else 'http' diff --git a/libs/apprise/config/ConfigMemory.py b/libs/apprise/config/ConfigMemory.py index c8d49a141..ec44e9b4f 100644 --- a/libs/apprise/config/ConfigMemory.py +++ b/libs/apprise/config/ConfigMemory.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2020 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. from .ConfigBase import ConfigBase from ..AppriseLocale import gettext_lazy as _ @@ -46,7 +53,7 @@ class ConfigMemory(ConfigBase): Memory objects just store the raw configuration in memory. There is no external reference point. It's always considered cached. """ - super(ConfigMemory, self).__init__(**kwargs) + super().__init__(**kwargs) # Store our raw config into memory self.content = content diff --git a/libs/apprise/config/__init__.py b/libs/apprise/config/__init__.py index 783118903..7d03a34a8 100644 --- a/libs/apprise/config/__init__.py +++ b/libs/apprise/config/__init__.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. import re from os import listdir diff --git a/libs/apprise/conversion.py b/libs/apprise/conversion.py index 3b692aa60..77c9aa5e5 100644 --- a/libs/apprise/conversion.py +++ b/libs/apprise/conversion.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2022 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. import re from markdown import markdown @@ -99,7 +106,7 @@ class HTMLConverter(HTMLParser, object): BLOCK_END = {} def __init__(self, **kwargs): - super(HTMLConverter, self).__init__(**kwargs) + super().__init__(**kwargs) # Shoudl we store the text content or not? self._do_store = True diff --git a/libs/apprise/decorators/CustomNotifyPlugin.py b/libs/apprise/decorators/CustomNotifyPlugin.py index 39fb51a9e..9c8e7cb1d 100644 --- a/libs/apprise/decorators/CustomNotifyPlugin.py +++ b/libs/apprise/decorators/CustomNotifyPlugin.py @@ -1,27 +1,35 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2022 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + from ..plugins.NotifyBase import NotifyBase from ..utils import URL_DETAILS_RE from ..utils import parse_url @@ -134,7 +142,7 @@ class CustomNotifyPlugin(NotifyBase): """ # init parent - super(CustomNotifyPluginWrapper, self).__init__(**kwargs) + super().__init__(**kwargs) self._default_args = {} diff --git a/libs/apprise/decorators/__init__.py b/libs/apprise/decorators/__init__.py index a6ef9662a..699fd0da4 100644 --- a/libs/apprise/decorators/__init__.py +++ b/libs/apprise/decorators/__init__.py @@ -1,27 +1,35 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2022 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + from .notify import notify diff --git a/libs/apprise/decorators/notify.py b/libs/apprise/decorators/notify.py index 3705e8708..36842b419 100644 --- a/libs/apprise/decorators/notify.py +++ b/libs/apprise/decorators/notify.py @@ -1,27 +1,35 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2022 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + from .CustomNotifyPlugin import CustomNotifyPlugin diff --git a/libs/apprise/logger.py b/libs/apprise/logger.py index 4510e1b60..005a3e0d7 100644 --- a/libs/apprise/logger.py +++ b/libs/apprise/logger.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. import os import logging @@ -181,6 +188,7 @@ class LogCapture: if self.__path: # Close our file pointer self.__buffer_ptr.close() + self.__handler.close() if self.__delete: try: # Always remove file afterwards diff --git a/libs/apprise/plugins/NotifyAppriseAPI.py b/libs/apprise/plugins/NotifyAppriseAPI.py index 10d52d5ba..b8765496f 100644 --- a/libs/apprise/plugins/NotifyAppriseAPI.py +++ b/libs/apprise/plugins/NotifyAppriseAPI.py @@ -1,31 +1,39 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2021 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. import re import requests from json import dumps +import base64 from .NotifyBase import NotifyBase from ..URLBase import PrivacyMode @@ -35,6 +43,20 @@ from ..utils import validate_regex from ..AppriseLocale import gettext_lazy as _ +class AppriseAPIMethod: + """ + Defines the method to post data tot he remote server + """ + JSON = 'json' + FORM = 'form' + + +APPRISE_API_METHODS = ( + AppriseAPIMethod.FORM, + AppriseAPIMethod.JSON, +) + + class NotifyAppriseAPI(NotifyBase): """ A wrapper for Apprise (Persistent) API Notifications @@ -57,7 +79,7 @@ class NotifyAppriseAPI(NotifyBase): # Depending on the number of transactions/notifications taking place, this # could take a while. 30 seconds should be enough to perform the task - socket_connect_timeout = 30.0 + socket_read_timeout = 30.0 # Disable throttle rate for Apprise API requests since they are normally # local anyway @@ -112,6 +134,12 @@ class NotifyAppriseAPI(NotifyBase): 'name': _('Tags'), 'type': 'string', }, + 'method': { + 'name': _('Query Method'), + 'type': 'choice:string', + 'values': APPRISE_API_METHODS, + 'default': APPRISE_API_METHODS[0], + }, 'to': { 'alias_of': 'token', }, @@ -125,7 +153,8 @@ class NotifyAppriseAPI(NotifyBase): }, } - def __init__(self, token=None, tags=None, headers=None, **kwargs): + def __init__(self, token=None, tags=None, method=None, headers=None, + **kwargs): """ Initialize Apprise API Object @@ -133,7 +162,7 @@ class NotifyAppriseAPI(NotifyBase): additionally include as part of the server headers to post with """ - super(NotifyAppriseAPI, self).__init__(**kwargs) + super().__init__(**kwargs) self.fullpath = kwargs.get('fullpath') if not isinstance(self.fullpath, str): @@ -147,6 +176,14 @@ class NotifyAppriseAPI(NotifyBase): self.logger.warning(msg) raise TypeError(msg) + self.method = self.template_args['method']['default'] \ + if not isinstance(method, str) else method.lower() + + if self.method not in APPRISE_API_METHODS: + msg = 'The method specified ({}) is invalid.'.format(method) + self.logger.warning(msg) + raise TypeError(msg) + # Build list of tags self.__tags = parse_list(tags) @@ -162,8 +199,13 @@ class NotifyAppriseAPI(NotifyBase): Returns the URL built dynamically based on specified arguments. """ - # Our URL parameters - params = self.url_parameters(privacy=privacy, *args, **kwargs) + # Define any URL parameters + params = { + 'method': self.method, + } + + # Extend our parameters + params.update(self.url_parameters(privacy=privacy, *args, **kwargs)) # Append our headers into our parameters params.update({'+{}'.format(k): v for k, v in self.headers.items()}) @@ -202,15 +244,61 @@ class NotifyAppriseAPI(NotifyBase): token=self.pprint(self.token, privacy, safe=''), params=NotifyAppriseAPI.urlencode(params)) - def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs): + def send(self, body, title='', notify_type=NotifyType.INFO, attach=None, + **kwargs): """ Perform Apprise API Notification """ - headers = {} + # Prepare HTTP Headers + headers = { + 'User-Agent': self.app_id, + } + # Apply any/all header over-rides defined headers.update(self.headers) + attachments = [] + files = [] + if attach: + for no, attachment in enumerate(attach, start=1): + # Perform some simple error checking + if not attachment: + # We could not access the attachment + self.logger.error( + 'Could not access attachment {}.'.format( + attachment.url(privacy=True))) + return False + + try: + if self.method == AppriseAPIMethod.JSON: + with open(attachment.path, 'rb') as f: + # Output must be in a DataURL format (that's what + # PushSafer calls it): + attachments.append({ + 'filename': attachment.name, + 'base64': base64.b64encode(f.read()) + .decode('utf-8'), + 'mimetype': attachment.mimetype, + }) + + else: # AppriseAPIMethod.FORM + files.append(( + 'file{:02d}'.format(no), + ( + attachment.name, + open(attachment.path, 'rb'), + attachment.mimetype, + ) + )) + + except (OSError, IOError) as e: + self.logger.warning( + 'An I/O error occurred while reading {}.'.format( + attachment.name if attachment else 'attachment')) + self.logger.debug('I/O Exception: %s' % str(e)) + return False + # prepare Apprise API Object payload = { # Apprise API Payload @@ -220,6 +308,11 @@ class NotifyAppriseAPI(NotifyBase): 'format': self.notify_format, } + if self.method == AppriseAPIMethod.JSON: + headers['Content-Type'] = 'application/json' + payload['attachments'] = attachments + payload = dumps(payload) + if self.__tags: payload['tag'] = self.__tags @@ -240,8 +333,8 @@ class NotifyAppriseAPI(NotifyBase): # Some entries can not be over-ridden headers.update({ - 'User-Agent': self.app_id, - 'Content-Type': 'application/json', + # Our response to be in JSON format always + 'Accept': 'application/json', # Pass our Source UUID4 Identifier 'X-Apprise-ID': self.asset._uid, # Pass our current recursion count to our upstream server @@ -259,9 +352,10 @@ class NotifyAppriseAPI(NotifyBase): try: r = requests.post( url, - data=dumps(payload), + data=payload, headers=headers, auth=auth, + files=files if files else None, verify=self.verify_certificate, timeout=self.request_timeout, ) @@ -283,7 +377,8 @@ class NotifyAppriseAPI(NotifyBase): return False else: - self.logger.info('Sent Apprise API notification.') + self.logger.info( + 'Sent Apprise API notification; method=%s.', self.method) except requests.RequestException as e: self.logger.warning( @@ -294,6 +389,18 @@ class NotifyAppriseAPI(NotifyBase): # Return; we're done return False + except (OSError, IOError) as e: + self.logger.warning( + 'An I/O error occurred while reading one of the ' + 'attached files.') + self.logger.debug('I/O Exception: %s' % str(e)) + return False + + finally: + for file in files: + # Ensure all files are closed + file[1][1].close() + return True @staticmethod @@ -370,4 +477,9 @@ class NotifyAppriseAPI(NotifyBase): # re-assemble our full path results['fullpath'] = '/'.join(entries) + # Set method if specified + if 'method' in results['qsd'] and len(results['qsd']['method']): + results['method'] = \ + NotifyAppriseAPI.unquote(results['qsd']['method']) + return results diff --git a/libs/apprise/plugins/NotifyBark.py b/libs/apprise/plugins/NotifyBark.py index d6283c022..f1c6d7bf9 100644 --- a/libs/apprise/plugins/NotifyBark.py +++ b/libs/apprise/plugins/NotifyBark.py @@ -1,27 +1,35 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2022 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + # # API: https://github.com/Finb/bark-server/blob/master/docs/API_V2.md#python # @@ -204,7 +212,7 @@ class NotifyBark(NotifyBase): """ Initialize Notify Bark Object """ - super(NotifyBark, self).__init__(**kwargs) + super().__init__(**kwargs) # Prepare our URL self.notify_url = '%s://%s%s/push' % ( @@ -272,7 +280,7 @@ class NotifyBark(NotifyBase): # error tracking (used for function return) has_error = False - if not len(self.targets): + if not self.targets: # We have nothing to notify; we're done self.logger.warning('There are no Bark devices to notify') return False @@ -448,6 +456,12 @@ class NotifyBark(NotifyBase): params=NotifyBark.urlencode(params), ) + def __len__(self): + """ + Returns the number of targets associated with this notification + """ + return len(self.targets) + @staticmethod def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyBase.py b/libs/apprise/plugins/NotifyBase.py index 4bb937795..1b07baa71 100644 --- a/libs/apprise/plugins/NotifyBase.py +++ b/libs/apprise/plugins/NotifyBase.py @@ -1,29 +1,38 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +import asyncio import re +from functools import partial from ..URLBase import URLBase from ..common import NotifyType @@ -36,12 +45,7 @@ from ..AppriseLocale import gettext_lazy as _ from ..AppriseAttachment import AppriseAttachment -# Wrap our base with the asyncio wrapper -from ..py3compat.asyncio import AsyncNotifyBase -BASE_OBJECT = AsyncNotifyBase - - -class NotifyBase(BASE_OBJECT): +class NotifyBase(URLBase): """ This is the base class for all notification services """ @@ -180,7 +184,7 @@ class NotifyBase(BASE_OBJECT): """ - super(NotifyBase, self).__init__(**kwargs) + super().__init__(**kwargs) if 'format' in kwargs: # Store the specified format if specified @@ -267,19 +271,64 @@ class NotifyBase(BASE_OBJECT): color_type=color_type, ) - def notify(self, body, title=None, notify_type=NotifyType.INFO, - overflow=None, attach=None, body_format=None, **kwargs): + def notify(self, *args, **kwargs): """ Performs notification + """ + try: + # Build a list of dictionaries that can be used to call send(). + send_calls = list(self._build_send_calls(*args, **kwargs)) + except TypeError: + # Internal error + return False + + else: + # Loop through each call, one at a time. (Use a list rather than a + # generator to call all the partials, even in case of a failure.) + the_calls = [self.send(**kwargs2) for kwargs2 in send_calls] + return all(the_calls) + + async def async_notify(self, *args, **kwargs): + """ + Performs notification for asynchronous callers + """ + try: + # Build a list of dictionaries that can be used to call send(). + send_calls = list(self._build_send_calls(*args, **kwargs)) + + except TypeError: + # Internal error + return False + + else: + loop = asyncio.get_event_loop() + + # Wrap each call in a coroutine that uses the default executor. + # TODO: In the future, allow plugins to supply a native + # async_send() method. + async def do_send(**kwargs2): + send = partial(self.send, **kwargs2) + result = await loop.run_in_executor(None, send) + return result + + # gather() all calls in parallel. + the_cors = (do_send(**kwargs2) for kwargs2 in send_calls) + return all(await asyncio.gather(*the_cors)) + + def _build_send_calls(self, body, title=None, + notify_type=NotifyType.INFO, overflow=None, + attach=None, body_format=None, **kwargs): + """ + Get a list of dictionaries that can be used to call send() or + (in the future) async_send(). """ if not self.enabled: # Deny notifications issued to services that are disabled - self.logger.warning( - "{} is currently disabled on this system.".format( - self.service_name)) - return False + msg = f"{self.service_name} is currently disabled on this system." + self.logger.warning(msg) + raise TypeError(msg) # Prepare attachments if required if attach is not None and not isinstance(attach, AppriseAttachment): @@ -288,7 +337,7 @@ class NotifyBase(BASE_OBJECT): except TypeError: # bad attachments - return False + raise # Handle situations where the title is None title = '' if not title else title @@ -299,14 +348,11 @@ class NotifyBase(BASE_OBJECT): body_format=body_format): # Send notification - if not self.send(body=chunk['body'], title=chunk['title'], - notify_type=notify_type, attach=attach, - body_format=body_format): - - # Toggle our return status flag - return False - - return True + yield dict( + body=chunk['body'], title=chunk['title'], + notify_type=notify_type, attach=attach, + body_format=body_format + ) def _apply_overflow(self, body, title=None, overflow=None, body_format=None): @@ -423,13 +469,13 @@ class NotifyBase(BASE_OBJECT): 'overflow': self.overflow_mode, } - params.update(super(NotifyBase, self).url_parameters(*args, **kwargs)) + params.update(super().url_parameters(*args, **kwargs)) # return default parameters return params @staticmethod - def parse_url(url, verify_host=True): + def parse_url(url, verify_host=True, plus_to_space=False): """Parses the URL and returns it broken apart into a dictionary. This is very specific and customized for Apprise. @@ -447,7 +493,8 @@ class NotifyBase(BASE_OBJECT): A dictionary is returned containing the URL fully parsed if successful, otherwise None is returned. """ - results = URLBase.parse_url(url, verify_host=verify_host) + results = URLBase.parse_url( + url, verify_host=verify_host, plus_to_space=plus_to_space) if not results: # We're done; we failed to parse our url diff --git a/libs/apprise/plugins/NotifyBoxcar.py b/libs/apprise/plugins/NotifyBoxcar.py index b40b71cd9..8e7045c7b 100644 --- a/libs/apprise/plugins/NotifyBoxcar.py +++ b/libs/apprise/plugins/NotifyBoxcar.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. import re import requests @@ -39,6 +46,7 @@ except ImportError: from .NotifyBase import NotifyBase from ..URLBase import PrivacyMode from ..utils import parse_bool +from ..utils import parse_list from ..utils import validate_regex from ..common import NotifyType from ..common import NotifyImageSize @@ -51,7 +59,7 @@ DEFAULT_TAG = '@all' # list of tagged devices that the notification need to be send to, and a # boolean operator (‘and’ / ‘or’) that defines the criteria to match devices # against those tags. -IS_TAG = re.compile(r'^[@](?P[A-Z0-9]{1,63})$', re.I) +IS_TAG = re.compile(r'^[@]?(?P[A-Z0-9]{1,63})$', re.I) # Device tokens are only referenced when developing. # It's not likely you'll send a message directly to a device, but if you do; @@ -150,10 +158,10 @@ class NotifyBoxcar(NotifyBase): """ Initialize Boxcar Object """ - super(NotifyBoxcar, self).__init__(**kwargs) + super().__init__(**kwargs) # Initialize tag list - self.tags = list() + self._tags = list() # Initialize device_token list self.device_tokens = list() @@ -177,29 +185,27 @@ class NotifyBoxcar(NotifyBase): raise TypeError(msg) if not targets: - self.tags.append(DEFAULT_TAG) + self._tags.append(DEFAULT_TAG) targets = [] - elif isinstance(targets, str): - targets = [x for x in filter(bool, TAGS_LIST_DELIM.split( - targets, - ))] - # Validate targets and drop bad ones: - for target in targets: - if IS_TAG.match(target): + for target in parse_list(targets): + result = IS_TAG.match(target) + if result: # store valid tag/alias - self.tags.append(IS_TAG.match(target).group('name')) + self._tags.append(result.group('name')) + continue - elif IS_DEVICETOKEN.match(target): + result = IS_DEVICETOKEN.match(target) + if result: # store valid device self.device_tokens.append(target) + continue - else: - self.logger.warning( - 'Dropped invalid tag/alias/device_token ' - '({}) specified.'.format(target), - ) + self.logger.warning( + 'Dropped invalid tag/alias/device_token ' + '({}) specified.'.format(target), + ) # Track whether or not we want to send an image with our notification # or not. @@ -231,8 +237,8 @@ class NotifyBoxcar(NotifyBase): if body: payload['aps']['alert'] = body - if self.tags: - payload['tags'] = {'or': self.tags} + if self._tags: + payload['tags'] = {'or': self._tags} if self.device_tokens: payload['device_tokens'] = self.device_tokens @@ -334,10 +340,18 @@ class NotifyBoxcar(NotifyBase): self.secret, privacy, mode=PrivacyMode.Secret, safe=''), targets='/'.join([ NotifyBoxcar.quote(x, safe='') for x in chain( - self.tags, self.device_tokens) if x != DEFAULT_TAG]), + self._tags, self.device_tokens) if x != DEFAULT_TAG]), params=NotifyBoxcar.urlencode(params), ) + def __len__(self): + """ + Returns the number of targets associated with this notification + """ + targets = len(self._tags) + len(self.device_tokens) + # DEFAULT_TAG is set if no tokens/tags are otherwise set + return targets if targets > 0 else 1 + @staticmethod def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyBulkSMS.py b/libs/apprise/plugins/NotifyBulkSMS.py index 8fa546421..814badaef 100644 --- a/libs/apprise/plugins/NotifyBulkSMS.py +++ b/libs/apprise/plugins/NotifyBulkSMS.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2022 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. # To use this service you will need a BulkSMS account # You will need credits (new accounts start with a few) @@ -30,7 +37,6 @@ # API is documented here: # - https://www.bulksms.com/developer/json/v1/#tag/Message import re -import six import requests import json from itertools import chain @@ -192,7 +198,7 @@ class NotifyBulkSMS(NotifyBase): # Setup our route self.route = self.template_args['route']['default'] \ - if not isinstance(route, six.string_types) else route.upper() + if not isinstance(route, str) else route.upper() if self.route not in BULKSMS_ROUTING_GROUPS: msg = 'The route specified ({}) is invalid.'.format(route) self.logger.warning(msg) @@ -408,6 +414,24 @@ class NotifyBulkSMS(NotifyBase): for x in self.groups])), params=NotifyBulkSMS.urlencode(params)) + def __len__(self): + """ + Returns the number of targets associated with this notification + """ + + # + # Factor batch into calculation + # + # Note: Groups always require a separate request (and can not be + # included in batch calculations) + batch_size = 1 if not self.batch else self.default_batch_size + targets = len(self.targets) + if batch_size > 1: + targets = int(targets / batch_size) + \ + (1 if targets % batch_size else 0) + + return targets + len(self.groups) + @staticmethod def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyClickSend.py b/libs/apprise/plugins/NotifyClickSend.py index 9054c6f01..ed6e462fc 100644 --- a/libs/apprise/plugins/NotifyClickSend.py +++ b/libs/apprise/plugins/NotifyClickSend.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. # To use this plugin, simply signup with clicksend: # https://www.clicksend.com/ @@ -132,7 +139,7 @@ class NotifyClickSend(NotifyBase): """ Initialize ClickSend Object """ - super(NotifyClickSend, self).__init__(**kwargs) + super().__init__(**kwargs) # Prepare Batch Mode Flag self.batch = batch @@ -281,6 +288,21 @@ class NotifyClickSend(NotifyBase): params=NotifyClickSend.urlencode(params), ) + def __len__(self): + """ + Returns the number of targets associated with this notification + """ + # + # Factor batch into calculation + # + batch_size = 1 if not self.batch else self.default_batch_size + targets = len(self.targets) + if batch_size > 1: + targets = int(targets / batch_size) + \ + (1 if targets % batch_size else 0) + + return targets + @staticmethod def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyD7Networks.py b/libs/apprise/plugins/NotifyD7Networks.py index b7575d92e..3d0ee8aa4 100644 --- a/libs/apprise/plugins/NotifyD7Networks.py +++ b/libs/apprise/plugins/NotifyD7Networks.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. # To use this service you will need a D7 Networks account from their website # at https://d7networks.com/ @@ -29,17 +36,18 @@ # After you've established your account you can get your api login credentials # (both user and password) from the API Details section from within your # account profile area: https://d7networks.com/accounts/profile/ +# +# API Reference: https://d7networks.com/docs/Messages/Send_Message/ import requests -import base64 from json import dumps from json import loads from .NotifyBase import NotifyBase -from ..URLBase import PrivacyMode from ..common import NotifyType from ..utils import is_phone_no from ..utils import parse_phone_no +from ..utils import validate_regex from ..utils import parse_bool from ..AppriseLocale import gettext_lazy as _ @@ -52,25 +60,6 @@ D7NETWORKS_HTTP_ERROR_MAP = { } -# Priorities -class D7SMSPriority: - """ - D7 Networks SMS Message Priority - """ - LOW = 0 - MODERATE = 1 - NORMAL = 2 - HIGH = 3 - - -D7NETWORK_SMS_PRIORITIES = ( - D7SMSPriority.LOW, - D7SMSPriority.MODERATE, - D7SMSPriority.NORMAL, - D7SMSPriority.HIGH, -) - - class NotifyD7Networks(NotifyBase): """ A wrapper for D7 Networks Notifications @@ -92,11 +81,8 @@ class NotifyD7Networks(NotifyBase): # A URL that takes you to the setup/help of the specific protocol setup_url = 'https://github.com/caronc/apprise/wiki/Notify_d7networks' - # D7 Networks batch notification URL - notify_batch_url = 'http://rest-api.d7networks.com/secure/sendbatch' - # D7 Networks single notification URL - notify_url = 'http://rest-api.d7networks.com/secure/send' + notify_url = 'https://api.d7networks.com/messages/v1/send' # The maximum length of the body body_maxlen = 160 @@ -107,21 +93,16 @@ class NotifyD7Networks(NotifyBase): # Define object templates templates = ( - '{schema}://{user}:{password}@{targets}', + '{schema}://{token}@{targets}', ) # Define our template tokens template_tokens = dict(NotifyBase.template_tokens, **{ - 'user': { - 'name': _('Username'), + 'token': { + 'name': _('API Access Token'), 'type': 'string', 'required': True, - }, - 'password': { - 'name': _('Password'), - 'type': 'string', 'private': True, - 'required': True, }, 'target_phone': { 'name': _('Target Phone No'), @@ -138,16 +119,11 @@ class NotifyD7Networks(NotifyBase): # Define our template arguments template_args = dict(NotifyBase.template_args, **{ - 'priority': { - 'name': _('Priority'), - 'type': 'choice:int', - 'min': D7SMSPriority.LOW, - 'max': D7SMSPriority.HIGH, - 'values': D7NETWORK_SMS_PRIORITIES, - - # The website identifies that the default priority is low; so - # this plugin will honor that same default - 'default': D7SMSPriority.LOW, + 'unicode': { + # Unicode characters (default is 'auto') + 'name': _('Unicode Characters'), + 'type': 'bool', + 'default': False, }, 'batch': { 'name': _('Batch Mode'), @@ -172,19 +148,12 @@ class NotifyD7Networks(NotifyBase): }, }) - def __init__(self, targets=None, priority=None, source=None, batch=False, - **kwargs): + def __init__(self, token=None, targets=None, source=None, + batch=False, unicode=None, **kwargs): """ Initialize D7 Networks Object """ - super(NotifyD7Networks, self).__init__(**kwargs) - - # The Priority of the message - if priority not in D7NETWORK_SMS_PRIORITIES: - self.priority = self.template_args['priority']['default'] - - else: - self.priority = priority + super().__init__(**kwargs) # Prepare Batch Mode Flag self.batch = batch @@ -193,8 +162,15 @@ class NotifyD7Networks(NotifyBase): self.source = None \ if not isinstance(source, str) else source.strip() - if not (self.user and self.password): - msg = 'A D7 Networks user/pass was not provided.' + # Define whether or not we should set the unicode flag + self.unicode = self.template_args['unicode']['default'] \ + if unicode is None else bool(unicode) + + # The token associated with the account + self.token = validate_regex(token) + if not self.token: + msg = 'The D7 Networks token specified ({}) is invalid.'\ + .format(token) self.logger.warning(msg) raise TypeError(msg) @@ -229,40 +205,41 @@ class NotifyD7Networks(NotifyBase): # error tracking (used for function return) has_error = False - auth = '{user}:{password}'.format( - user=self.user, password=self.password) - - # Python 3's versio of b64encode() expects a byte array and not - # a string. To accommodate this, we encode the content here - auth = auth.encode('utf-8') - # Prepare our headers headers = { 'User-Agent': self.app_id, + 'Content-Type': 'application/json', 'Accept': 'application/json', - 'Authorization': 'Basic {}'.format(base64.b64encode(auth)) + 'Authorization': f'Bearer {self.token}', } - # Our URL varies depending if we're doing a batch mode or not - url = self.notify_batch_url if self.batch else self.notify_url + payload = { + 'message_globals': { + 'channel': 'sms', + }, + 'messages': [{ + # Populated later on + 'recipients': None, + 'content': body, + 'data_coding': + # auto is a better substitute over 'text' as text is easier to + # detect from a post than `unicode` is. + 'auto' if not self.unicode else 'unicode', + }], + } # use the list directly targets = list(self.targets) + if self.source: + payload['message_globals']['originator'] = self.source + + target = None while len(targets): if self.batch: # Prepare our payload - payload = { - 'globals': { - 'priority': self.priority, - 'from': self.source if self.source else self.app_id, - }, - 'messages': [{ - 'to': self.targets, - 'content': body, - }], - } + payload['messages'][0]['recipients'] = self.targets # Reset our targets so we don't keep going. This is required # because we're in batch mode; we only need to loop once. @@ -274,24 +251,19 @@ class NotifyD7Networks(NotifyBase): target = targets.pop(0) # Prepare our payload - payload = { - 'priority': self.priority, - 'content': body, - 'to': target, - 'from': self.source if self.source else self.app_id, - } + payload['messages'][0]['recipients'] = [target] # Some Debug Logging self.logger.debug( 'D7 Networks POST URL: {} (cert_verify={})'.format( - url, self.verify_certificate)) + self.notify_url, self.verify_certificate)) self.logger.debug('D7 Networks Payload: {}' .format(payload)) # Always call throttle before any remote server i/o is made self.throttle() try: r = requests.post( - url, + self.notify_url, data=dumps(payload), headers=headers, verify=self.verify_certificate, @@ -337,29 +309,9 @@ class NotifyD7Networks(NotifyBase): else: if self.batch: - count = len(self.targets) - try: - # Get our message delivery count if we can - json_response = loads(r.content) - count = int(json_response.get( - 'data', {}).get('messageCount', -1)) - - except (AttributeError, TypeError, ValueError): - # ValueError = r.content is Unparsable - # TypeError = r.content is None - # AttributeError = r is None - - # We could not parse JSON response. Assume that - # our delivery is okay for now. - pass - - if count != len(self.targets): - has_error = True - self.logger.info( 'Sent D7 Networks batch SMS notification to ' - '{} of {} target(s).'.format( - count, len(self.targets))) + '{} target(s).'.format(len(self.targets))) else: self.logger.info( @@ -389,26 +341,31 @@ class NotifyD7Networks(NotifyBase): # Define any URL parameters params = { 'batch': 'yes' if self.batch else 'no', + 'unicode': 'yes' if self.unicode else 'no', } - # Extend our parameters - params.update(self.url_parameters(privacy=privacy, *args, **kwargs)) - - if self.priority != self.template_args['priority']['default']: - params['priority'] = str(self.priority) - if self.source: params['from'] = self.source - return '{schema}://{user}:{password}@{targets}/?{params}'.format( + # Extend our parameters + params.update(self.url_parameters(privacy=privacy, *args, **kwargs)) + + return '{schema}://{token}@{targets}/?{params}'.format( schema=self.secure_protocol, - user=NotifyD7Networks.quote(self.user, safe=''), - password=self.pprint( - self.password, privacy, mode=PrivacyMode.Secret, safe=''), + token=self.pprint(self.token, privacy, safe=''), targets='/'.join( [NotifyD7Networks.quote(x, safe='') for x in self.targets]), params=NotifyD7Networks.urlencode(params)) + def __len__(self): + """ + Returns the number of targets associated with this notification + """ + # + # Factor batch into calculation + # + return len(self.targets) if not self.batch else 1 + @staticmethod def parse_url(url): """ @@ -421,6 +378,23 @@ class NotifyD7Networks(NotifyBase): # We're done early as we couldn't load the results return results + if 'token' in results['qsd'] and len(results['qsd']['token']): + results['token'] = \ + NotifyD7Networks.unquote(results['qsd']['token']) + + elif results['user']: + results['token'] = NotifyD7Networks.unquote(results['user']) + + if results['password']: + # Support token containing a colon (:) + results['token'] += \ + ':' + NotifyD7Networks.unquote(results['password']) + + elif results['password']: + # Support token starting with a colon (:) + results['token'] = \ + ':' + NotifyD7Networks.unquote(results['password']) + # Initialize our targets results['targets'] = list() @@ -432,44 +406,27 @@ class NotifyD7Networks(NotifyBase): results['targets'].extend( NotifyD7Networks.split_path(results['fullpath'])) - # Set our priority - if 'priority' in results['qsd'] and len(results['qsd']['priority']): - _map = { - 'l': D7SMSPriority.LOW, - '0': D7SMSPriority.LOW, - 'm': D7SMSPriority.MODERATE, - '1': D7SMSPriority.MODERATE, - 'n': D7SMSPriority.NORMAL, - '2': D7SMSPriority.NORMAL, - 'h': D7SMSPriority.HIGH, - '3': D7SMSPriority.HIGH, - } - try: - results['priority'] = \ - _map[results['qsd']['priority'][0].lower()] - - except KeyError: - # No priority was set - pass - - # Support the 'from' and 'source' variable so that we can support - # targets this way too. - # The 'from' makes it easier to use yaml configuration - if 'from' in results['qsd'] and len(results['qsd']['from']): - results['source'] = \ - NotifyD7Networks.unquote(results['qsd']['from']) - if 'source' in results['qsd'] and len(results['qsd']['source']): - results['source'] = \ - NotifyD7Networks.unquote(results['qsd']['source']) - # Get Batch Mode Flag results['batch'] = \ parse_bool(results['qsd'].get('batch', False)) + # Get Unicode Flag + results['unicode'] = \ + parse_bool(results['qsd'].get('unicode', False)) + # Support the 'to' variable so that we can support targets this way too # The 'to' makes it easier to use yaml configuration if 'to' in results['qsd'] and len(results['qsd']['to']): results['targets'] += \ NotifyD7Networks.parse_phone_no(results['qsd']['to']) + # Support the 'from' and source variable + if 'from' in results['qsd'] and len(results['qsd']['from']): + results['source'] = \ + NotifyD7Networks.unquote(results['qsd']['from']) + + elif 'source' in results['qsd'] and len(results['qsd']['source']): + results['source'] = \ + NotifyD7Networks.unquote(results['qsd']['source']) + return results diff --git a/libs/apprise/plugins/NotifyDBus.py b/libs/apprise/plugins/NotifyDBus.py index b568dfe73..62a1093c8 100644 --- a/libs/apprise/plugins/NotifyDBus.py +++ b/libs/apprise/plugins/NotifyDBus.py @@ -1,31 +1,39 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. from __future__ import absolute_import from __future__ import print_function +import sys from .NotifyBase import NotifyBase from ..common import NotifyImageSize from ..common import NotifyType @@ -77,6 +85,13 @@ try: NOTIFY_DBUS_SUPPORT_ENABLED = ( LOOP_GLIB is not None or LOOP_QT is not None) + # ImportError: When using gi.repository you must not import static modules + # like "gobject". Please change all occurrences of "import gobject" to + # "from gi.repository import GObject". + # See: https://bugzilla.gnome.org/show_bug.cgi?id=709183 + if "gobject" in sys.modules: # pragma: no cover + del sys.modules["gobject"] + try: # The following is required for Image/Icon loading only import gi @@ -233,7 +248,7 @@ class NotifyDBus(NotifyBase): Initialize DBus Object """ - super(NotifyDBus, self).__init__(**kwargs) + super().__init__(**kwargs) # Track our notifications self.registry = {} @@ -272,12 +287,9 @@ class NotifyDBus(NotifyBase): self.x_axis = None self.y_axis = None - # Track whether or not we want to send an image with our notification - # or not. + # Track whether we want to add an image to the notification. self.include_image = include_image - return - def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs): """ Perform DBus Notification @@ -286,10 +298,10 @@ class NotifyDBus(NotifyBase): try: session = SessionBus(mainloop=MAINLOOP_MAP[self.schema]) - except DBusException: + except DBusException as e: # Handle exception self.logger.warning('Failed to send DBus notification.') - self.logger.exception('DBus Exception') + self.logger.debug(f'DBus Exception: {e}') return False # If there is no title, but there is a body, swap the two to get rid @@ -342,8 +354,8 @@ class NotifyDBus(NotifyBase): except Exception as e: self.logger.warning( - "Could not load Gnome notification icon ({}): {}" - .format(icon_path, e)) + "Could not load notification icon (%s).", icon_path) + self.logger.debug(f'DBus Exception: {e}') try: # Always call throttle() before any remote execution is made @@ -370,9 +382,9 @@ class NotifyDBus(NotifyBase): self.logger.info('Sent DBus notification.') - except Exception: + except Exception as e: self.logger.warning('Failed to send DBus notification.') - self.logger.exception('DBus Exception') + self.logger.debug(f'DBus Exception: {e}') return False return True diff --git a/libs/apprise/plugins/NotifyDapnet.py b/libs/apprise/plugins/NotifyDapnet.py index 27ea65cd3..1b718286a 100644 --- a/libs/apprise/plugins/NotifyDapnet.py +++ b/libs/apprise/plugins/NotifyDapnet.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. # To use this plugin, sign up with Hampager (you need to be a licensed # ham radio operator @@ -179,7 +186,7 @@ class NotifyDapnet(NotifyBase): """ Initialize Dapnet Object """ - super(NotifyDapnet, self).__init__(**kwargs) + super().__init__(**kwargs) # Parse our targets self.targets = list() @@ -343,6 +350,21 @@ class NotifyDapnet(NotifyBase): params=NotifyDapnet.urlencode(params), ) + def __len__(self): + """ + Returns the number of targets associated with this notification + """ + # + # Factor batch into calculation + # + batch_size = 1 if not self.batch else self.default_batch_size + targets = len(self.targets) + if batch_size > 1: + targets = int(targets / batch_size) + \ + (1 if targets % batch_size else 0) + + return targets + @staticmethod def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyDingTalk.py b/libs/apprise/plugins/NotifyDingTalk.py index 68c069479..ae2a9b499 100644 --- a/libs/apprise/plugins/NotifyDingTalk.py +++ b/libs/apprise/plugins/NotifyDingTalk.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2020 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. import re import time @@ -124,7 +131,7 @@ class NotifyDingTalk(NotifyBase): """ Initialize DingTalk Object """ - super(NotifyDingTalk, self).__init__(**kwargs) + super().__init__(**kwargs) # Secret Key (associated with project) self.token = validate_regex( @@ -302,6 +309,13 @@ class NotifyDingTalk(NotifyBase): [NotifyDingTalk.quote(x, safe='') for x in self.targets]), args=NotifyDingTalk.urlencode(args)) + def __len__(self): + """ + Returns the number of targets associated with this notification + """ + targets = len(self.targets) + return targets if targets > 0 else 1 + @staticmethod def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyDiscord.py b/libs/apprise/plugins/NotifyDiscord.py index 48e2c2836..fff76eef2 100644 --- a/libs/apprise/plugins/NotifyDiscord.py +++ b/libs/apprise/plugins/NotifyDiscord.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. # For this to work correctly you need to create a webhook. To do this just # click on the little gear icon next to the channel you're part of. From @@ -164,7 +171,7 @@ class NotifyDiscord(NotifyBase): Initialize Discord Object """ - super(NotifyDiscord, self).__init__(**kwargs) + super().__init__(**kwargs) # Webhook ID (associated with project) self.webhook_id = validate_regex(webhook_id) @@ -283,9 +290,6 @@ class NotifyDiscord(NotifyBase): payload['content'] = \ body if not title else "{}\r\n{}".format(title, body) - if self.thread_id: - payload['thread_id'] = self.thread_id - if self.avatar and (image_url or self.avatar_url): payload['avatar_url'] = \ self.avatar_url if self.avatar_url else image_url @@ -294,7 +298,8 @@ class NotifyDiscord(NotifyBase): # Optionally override the default username of the webhook payload['username'] = self.user - if not self._send(payload): + params = {'thread_id': self.thread_id} if self.thread_id else None + if not self._send(payload, params=params): # We failed to post our message return False @@ -338,7 +343,7 @@ class NotifyDiscord(NotifyBase): # Otherwise return return True - def _send(self, payload, attach=None, **kwargs): + def _send(self, payload, attach=None, params=None, **kwargs): """ Wrapper to the requests (post) object """ @@ -389,6 +394,7 @@ class NotifyDiscord(NotifyBase): r = requests.post( notify_url, + params=params, data=payload if files else dumps(payload), headers=headers, files=files, @@ -580,7 +586,7 @@ class NotifyDiscord(NotifyBase): if description: # Strip description from our string since it has been handled # now. - markdown = re.sub(description, '', markdown, count=1) + markdown = re.sub(re.escape(description), '', markdown, count=1) regex = re.compile( r'\s*#[# \t\v]*(?P[^\n]+)(\n|\s*$)' diff --git a/libs/apprise/plugins/NotifyEmail.py b/libs/apprise/plugins/NotifyEmail.py index 5858f0906..e55de7314 100644 --- a/libs/apprise/plugins/NotifyEmail.py +++ b/libs/apprise/plugins/NotifyEmail.py @@ -1,30 +1,39 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +import dataclasses import re import smtplib +import typing as t from email.mime.text import MIMEText from email.mime.application import MIMEApplication from email.mime.multipart import MIMEMultipart @@ -41,6 +50,7 @@ from ..common import NotifyFormat, NotifyType from ..conversion import convert_between from ..utils import is_email, parse_emails from ..AppriseLocale import gettext_lazy as _ +from ..logger import logger # Globally Default encoding mode set to Quoted Printable. charset.add_charset('utf-8', charset.QP, charset.QP, 'utf-8') @@ -60,15 +70,23 @@ class WebBaseLogin: # Secure Email Modes class SecureMailMode: + INSECURE = "insecure" SSL = "ssl" STARTTLS = "starttls" # Define all of the secure modes (used during validation) -SECURE_MODES = ( - SecureMailMode.SSL, - SecureMailMode.STARTTLS, -) +SECURE_MODES = { + SecureMailMode.STARTTLS: { + 'default_port': 587, + }, + SecureMailMode.SSL: { + 'default_port': 465, + }, + SecureMailMode.INSECURE: { + 'default_port': 25, + }, +} # To attempt to make this script stupid proof, if we detect an email address # that is part of the this table, we can pre-use a lot more defaults if they @@ -109,7 +127,7 @@ EMAIL_TEMPLATES = ( 'Microsoft Hotmail', re.compile( r'^((?P