diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 507bf3eb4..a95c835a2 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,6 +8,19 @@ updates: prefix: "[bot]" open-pull-requests-limit: 1 target-branch: "development" + groups: + fortawesome: + patterns: + - "@fortawesome*" + mantine: + patterns: + - "@mantine*" + react: + patterns: + - "react" + - "react-dom" + - "@types/react" + - "@types/react-dom" - package-ecosystem: 'github-actions' directory: '/' schedule: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7f8c15659..79b1fc149 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: restore-keys: ${{ runner.os }}-modules- - name: Setup NodeJS - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: "lts/*" @@ -76,7 +76,7 @@ jobs: uses: actions/checkout@v4 - name: Set up Python 3.8 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.8" diff --git a/.github/workflows/release_beta_to_dev.yaml b/.github/workflows/release_beta_to_dev.yaml index 994f511bc..f7155984c 100644 --- a/.github/workflows/release_beta_to_dev.yaml +++ b/.github/workflows/release_beta_to_dev.yaml @@ -36,7 +36,7 @@ jobs: restore-keys: ${{ runner.os }}-modules- - name: Setup NodeJS - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: "lts/*" diff --git a/.github/workflows/release_dev_to_master.yaml b/.github/workflows/release_dev_to_master.yaml index 37d0e7eba..dddf603b0 100644 --- a/.github/workflows/release_dev_to_master.yaml +++ b/.github/workflows/release_dev_to_master.yaml @@ -38,7 +38,7 @@ jobs: restore-keys: ${{ runner.os }}-modules- - name: Setup NodeJS - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: "lts/*" diff --git a/.github/workflows/schedule.yaml b/.github/workflows/schedule.yaml index 69eddf032..93a1a1ecd 100644 --- a/.github/workflows/schedule.yaml +++ b/.github/workflows/schedule.yaml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Execute - uses: benc-uk/workflow-dispatch@v121 + uses: benc-uk/workflow-dispatch@v1.2.3 with: workflow: "release_beta_to_dev" token: ${{ secrets.WF_GITHUB_TOKEN }} diff --git a/.github/workflows/test_bazarr_execution.yml b/.github/workflows/test_bazarr_execution.yml index da31638c0..034eb782a 100644 --- a/.github/workflows/test_bazarr_execution.yml +++ b/.github/workflows/test_bazarr_execution.yml @@ -22,7 +22,7 @@ jobs: ref: development - name: Setup NodeJS - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: "lts/*" @@ -35,7 +35,7 @@ jobs: working-directory: ${{ env.UI_DIRECTORY }} - name: Set up Python 3.8 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.8" diff --git a/README.md b/README.md index c244d7b47..2520aa9f9 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,9 @@ If you need something that is not already part of Bazarr, feel free to create a ## Supported subtitles providers: - Addic7ed +- Animetosho (requires AniDb HTTP API client described [here](https://wiki.anidb.net/HTTP_API_Definition)) - Assrt +- AvistaZ, CinemaZ (Get session cookies using method described [here](https://github.com/morpheus65535/bazarr/pull/2375#issuecomment-2057010996)) - BetaSeries - BSplayer - Embedded Subtitles diff --git a/bazarr.py b/bazarr.py index dedf1aea1..7e75272b0 100644 --- a/bazarr.py +++ b/bazarr.py @@ -8,12 +8,14 @@ import sys import time from bazarr.app.get_args import args -from bazarr.literals import * +from bazarr.literals import EXIT_PYTHON_UPGRADE_NEEDED, EXIT_NORMAL, FILE_RESTART, FILE_STOP, ENV_RESTARTFILE, ENV_STOPFILE, EXIT_INTERRUPT + def exit_program(status_code): print(f'Bazarr exited with status code {status_code}.') raise SystemExit(status_code) + def check_python_version(): python_version = platform.python_version_tuple() minimum_py3_tuple = (3, 8, 0) @@ -52,12 +54,13 @@ check_python_version() dir_name = os.path.dirname(__file__) + def start_bazarr(): script = [get_python_path(), "-u", os.path.normcase(os.path.join(dir_name, 'bazarr', 'main.py'))] + sys.argv[1:] - ep = subprocess.Popen(script, stdout=None, stderr=None, stdin=subprocess.DEVNULL) + ep = subprocess.Popen(script, stdout=None, stderr=None, stdin=subprocess.DEVNULL, env=os.environ) print(f"Bazarr starting child process with PID {ep.pid}...") return ep - + def terminate_child(): print(f"Terminating child process with PID {child_process.pid}") @@ -66,7 +69,7 @@ def terminate_child(): def get_stop_status_code(input_file): try: - with open(input_file,'r') as file: + with open(input_file, 'r') as file: # read status code from file, if it exists line = file.readline() try: @@ -74,33 +77,33 @@ def get_stop_status_code(input_file): except (ValueError, TypeError): status_code = EXIT_NORMAL file.close() - except: + except Exception: status_code = EXIT_NORMAL return status_code def check_status(): global child_process - if os.path.exists(stopfile): - status_code = get_stop_status_code(stopfile) + if os.path.exists(stop_file): + status_code = get_stop_status_code(stop_file) try: - print(f"Deleting stop file...") - os.remove(stopfile) - except Exception as e: + print("Deleting stop file...") + os.remove(stop_file) + except Exception: print('Unable to delete stop file.') finally: terminate_child() exit_program(status_code) - if os.path.exists(restartfile): + if os.path.exists(restart_file): try: - print(f"Deleting restart file...") - os.remove(restartfile) + print("Deleting restart file...") + os.remove(restart_file) except Exception: print('Unable to delete restart file.') finally: terminate_child() - print(f"Bazarr is restarting...") + print("Bazarr is restarting...") child_process = start_bazarr() @@ -113,25 +116,25 @@ def interrupt_handler(signum, frame): interrupted = True print('Handling keyboard interrupt...') else: - print(f"Stop doing that! I heard you the first time!") + print("Stop doing that! I heard you the first time!") if __name__ == '__main__': interrupted = False signal.signal(signal.SIGINT, interrupt_handler) - restartfile = os.path.join(args.config_dir, FILE_RESTART) - stopfile = os.path.join(args.config_dir, FILE_STOP) - os.environ[ENV_STOPFILE] = stopfile - os.environ[ENV_RESTARTFILE] = restartfile + restart_file = os.path.join(args.config_dir, FILE_RESTART) + stop_file = os.path.join(args.config_dir, FILE_STOP) + os.environ[ENV_STOPFILE] = stop_file + os.environ[ENV_RESTARTFILE] = restart_file # Cleanup leftover files try: - os.remove(restartfile) + os.remove(restart_file) except FileNotFoundError: pass try: - os.remove(stopfile) + os.remove(stop_file) except FileNotFoundError: pass @@ -145,5 +148,5 @@ if __name__ == '__main__': time.sleep(5) except (KeyboardInterrupt, SystemExit, ChildProcessError): # this code should never be reached, if signal handling is working properly - print(f'Bazarr exited main script file via keyboard interrupt.') + print('Bazarr exited main script file via keyboard interrupt.') exit_program(EXIT_INTERRUPT) diff --git a/bazarr/api/system/logs.py b/bazarr/api/system/logs.py index 7293f44c2..23daa98b4 100644 --- a/bazarr/api/system/logs.py +++ b/bazarr/api/system/logs.py @@ -7,7 +7,6 @@ from flask_restx import Resource, Namespace, fields, marshal from app.config import settings from app.logger import empty_log -from app.get_args import args from utilities.central import get_log_file_path from ..utils import authenticate diff --git a/bazarr/app/app.py b/bazarr/app/app.py index 982cea34b..918d877e1 100644 --- a/bazarr/app/app.py +++ b/bazarr/app/app.py @@ -1,6 +1,6 @@ # coding=utf-8 -from flask import Flask, redirect +from flask import Flask, redirect, Request from flask_compress import Compress from flask_cors import CORS @@ -13,9 +13,17 @@ from .config import settings, base_url socketio = SocketIO() +class CustomRequest(Request): + def __init__(self, *args, **kwargs): + super(CustomRequest, self).__init__(*args, **kwargs) + # required to increase form-data size before returning a 413 + self.max_form_parts = 10000 + + def create_app(): # Flask Setup app = Flask(__name__) + app.request_class = CustomRequest app.config['COMPRESS_ALGORITHM'] = 'gzip' Compress(app) app.wsgi_app = ReverseProxied(app.wsgi_app) diff --git a/bazarr/app/check_update.py b/bazarr/app/check_update.py index 327294324..4a8f64868 100644 --- a/bazarr/app/check_update.py +++ b/bazarr/app/check_update.py @@ -25,7 +25,7 @@ def check_releases(): url_releases = 'https://api.github.com/repos/morpheus65535/Bazarr/releases?per_page=100' try: logging.debug(f'BAZARR getting releases from Github: {url_releases}') - r = requests.get(url_releases, allow_redirects=True) + r = requests.get(url_releases, allow_redirects=True, timeout=15) r.raise_for_status() except requests.exceptions.HTTPError: logging.exception("Error trying to get releases from Github. Http error.") @@ -160,8 +160,7 @@ def apply_update(): 'BAZARR was unable to delete the previous build directory during upgrade process.') for file in archive.namelist(): - if file.startswith(zip_root_directory) and file != zip_root_directory and not \ - file.endswith('bazarr.py'): + if file.startswith(zip_root_directory) and file != zip_root_directory: file_path = os.path.join(bazarr_dir, file[len(zip_root_directory):]) parent_dir = os.path.dirname(file_path) os.makedirs(parent_dir, exist_ok=True) diff --git a/bazarr/app/config.py b/bazarr/app/config.py index 5e9d74bbc..2af614909 100644 --- a/bazarr/app/config.py +++ b/bazarr/app/config.py @@ -7,6 +7,7 @@ import logging import re from urllib.parse import quote_plus +from utilities.binaries import BinaryNotFound, get_binary from literals import EXIT_VALIDATION_ERROR from utilities.central import stop_bazarr from subliminal.cache import region @@ -54,6 +55,14 @@ class Validator(OriginalValidator): ) +def check_parser_binary(value): + try: + get_binary(value) + except BinaryNotFound: + raise ValidationError(f"Executable '{value}' not found in search path. Please install before making this selection.") + return True + + validators = [ # general section Validator('general.flask_secret_key', must_exist=True, default=hexlify(os.urandom(16)).decode(), @@ -100,6 +109,7 @@ validators = [ Validator('general.adaptive_searching_delta', must_exist=True, default='1w', is_type_of=str, is_in=['3d', '1w', '2w', '3w', '4w']), Validator('general.enabled_providers', must_exist=True, default=[], is_type_of=list), + Validator('general.enabled_integrations', must_exist=True, default=[], is_type_of=list), Validator('general.multithreading', must_exist=True, default=True, is_type_of=bool), Validator('general.chmod_enabled', must_exist=True, default=False, is_type_of=bool), Validator('general.chmod', must_exist=True, default='0640', is_type_of=str), @@ -119,7 +129,7 @@ validators = [ Validator('general.dont_notify_manual_actions', must_exist=True, default=False, is_type_of=bool), Validator('general.hi_extension', must_exist=True, default='hi', is_type_of=str, is_in=['hi', 'cc', 'sdh']), Validator('general.embedded_subtitles_parser', must_exist=True, default='ffprobe', is_type_of=str, - is_in=['ffprobe', 'mediainfo']), + is_in=['ffprobe', 'mediainfo'], condition=check_parser_binary), Validator('general.default_und_audio_lang', must_exist=True, default='', is_type_of=str), Validator('general.default_und_embedded_subtitles_lang', must_exist=True, default='', is_type_of=str), Validator('general.parse_embedded_audio_track', must_exist=True, default=False, is_type_of=bool), @@ -225,6 +235,11 @@ validators = [ Validator('addic7ed.user_agent', must_exist=True, default='', is_type_of=str), Validator('addic7ed.vip', must_exist=True, default=False, is_type_of=bool), + # animetosho section + Validator('animetosho.search_threshold', must_exist=True, default=6, is_type_of=int, gte=1, lte=15), + Validator('animetosho.anidb_api_client', must_exist=True, default='', is_type_of=str, cast=str), + Validator('animetosho.anidb_api_client_ver', must_exist=True, default=1, is_type_of=int, gte=1, lte=9), + # avistaz section Validator('avistaz.cookies', must_exist=True, default='', is_type_of=str), Validator('avistaz.user_agent', must_exist=True, default='', is_type_of=str), @@ -278,10 +293,6 @@ validators = [ Validator('napisy24.username', must_exist=True, default='', is_type_of=str, cast=str), Validator('napisy24.password', must_exist=True, default='', is_type_of=str, cast=str), - # subscene section - Validator('subscene.username', must_exist=True, default='', is_type_of=str, cast=str), - Validator('subscene.password', must_exist=True, default='', is_type_of=str, cast=str), - # betaseries section Validator('betaseries.token', must_exist=True, default='', is_type_of=str, cast=str), @@ -360,6 +371,10 @@ validators = [ Validator('postgresql.database', must_exist=True, default='', is_type_of=str), Validator('postgresql.username', must_exist=True, default='', is_type_of=str, cast=str), Validator('postgresql.password', must_exist=True, default='', is_type_of=str, cast=str), + + # anidb section + Validator('anidb.api_client', must_exist=True, default='', is_type_of=str), + Validator('anidb.api_client_ver', must_exist=True, default=1, is_type_of=int), ] @@ -433,6 +448,7 @@ array_keys = ['excluded_tags', 'subzero_mods', 'excluded_series_types', 'enabled_providers', + 'enabled_integrations', 'path_mappings', 'path_mappings_movie', 'language_equals', @@ -666,15 +682,6 @@ def save_settings(settings_items): reset_providers = True region.delete('oscom_token') - if key == 'settings-subscene-username': - if key != settings.subscene.username: - reset_providers = True - region.delete('subscene_cookies2') - elif key == 'settings-subscene-password': - if key != settings.subscene.password: - reset_providers = True - region.delete('subscene_cookies2') - if key == 'settings-titlovi-username': if key != settings.titlovi.username: reset_providers = True diff --git a/bazarr/app/get_providers.py b/bazarr/app/get_providers.py index bb78f29d9..d7a61326f 100644 --- a/bazarr/app/get_providers.py +++ b/bazarr/app/get_providers.py @@ -125,7 +125,7 @@ def provider_throttle_map(): PROVIDERS_FORCED_OFF = ["addic7ed", "tvsubtitles", "legendasdivx", "napiprojekt", "shooter", - "hosszupuska", "supersubtitles", "titlovi", "assrt", "subscene"] + "hosszupuska", "supersubtitles", "titlovi", "assrt"] throttle_count = {} @@ -259,11 +259,6 @@ def get_providers_auth(): 'also_foreign': False, # fixme 'verify_ssl': settings.podnapisi.verify_ssl }, - 'subscene': { - 'username': settings.subscene.username, - 'password': settings.subscene.password, - 'only_foreign': False, # fixme - }, 'legendasdivx': { 'username': settings.legendasdivx.username, 'password': settings.legendasdivx.password, @@ -324,6 +319,9 @@ def get_providers_auth(): 'timeout': settings.whisperai.timeout, 'ffmpeg_path': _FFMPEG_BINARY, 'loglevel': settings.whisperai.loglevel, + }, + "animetosho": { + 'search_threshold': settings.animetosho.search_threshold, } } diff --git a/bazarr/app/logger.py b/bazarr/app/logger.py index 8470944d5..a47acf3dc 100644 --- a/bazarr/app/logger.py +++ b/bazarr/app/logger.py @@ -11,7 +11,6 @@ from logging.handlers import TimedRotatingFileHandler from utilities.central import get_log_file_path from pytz_deprecation_shim import PytzUsageWarning -from .get_args import args from .config import settings @@ -62,18 +61,18 @@ class UnwantedWaitressMessageFilter(logging.Filter): if settings.general.debug: # no filtering in debug mode return True - - unwantedMessages = [ - "Exception while serving /api/socket.io/", - ['Session is disconnected', 'Session not found' ], - - "Exception while serving /api/socket.io/", - ["'Session is disconnected'", "'Session not found'" ], - - "Exception while serving /api/socket.io/", - ['"Session is disconnected"', '"Session not found"' ], - - "Exception when servicing %r", + + unwantedMessages = [ + "Exception while serving /api/socket.io/", + ['Session is disconnected', 'Session not found'], + + "Exception while serving /api/socket.io/", + ["'Session is disconnected'", "'Session not found'"], + + "Exception while serving /api/socket.io/", + ['"Session is disconnected"', '"Session not found"'], + + "Exception when servicing %r", [], ] diff --git a/bazarr/app/scheduler.py b/bazarr/app/scheduler.py index f0e73637f..39cbe88b3 100644 --- a/bazarr/app/scheduler.py +++ b/bazarr/app/scheduler.py @@ -10,7 +10,6 @@ from apscheduler.triggers.date import DateTrigger from apscheduler.events import EVENT_JOB_SUBMITTED, EVENT_JOB_EXECUTED, EVENT_JOB_ERROR from datetime import datetime, timedelta from calendar import day_name -from math import floor from random import randrange from tzlocal import get_localzone try: @@ -47,6 +46,10 @@ ONE_YEAR_IN_SECONDS = 60 * 60 * 24 * 365 def a_long_time_from_now(job): + # job isn't scheduled at all + if job.next_run_time is None: + return True + # currently defined as more than a year from now delta = job.next_run_time - datetime.now(job.next_run_time.tzinfo) return delta.total_seconds() > ONE_YEAR_IN_SECONDS @@ -321,8 +324,8 @@ class Scheduler: self.aps_scheduler.modify_job(job.id, next_run_time=datetime.now(tz=self.timezone) + timedelta(seconds=randrange( - job.trigger.interval.total_seconds() * 0.75, - job.trigger.interval.total_seconds()))) + int(job.trigger.interval.total_seconds() * 0.75), + int(job.trigger.interval.total_seconds())))) def __no_task(self): for job in self.aps_scheduler.get_jobs(): diff --git a/bazarr/app/server.py b/bazarr/app/server.py index d56e1205b..1def54dab 100644 --- a/bazarr/app/server.py +++ b/bazarr/app/server.py @@ -4,7 +4,7 @@ import signal import warnings import logging import errno -from literals import EXIT_INTERRUPT, EXIT_NORMAL +from literals import EXIT_INTERRUPT, EXIT_NORMAL, EXIT_PORT_ALREADY_IN_USE_ERROR from utilities.central import restart_bazarr, stop_bazarr from waitress.server import create_server @@ -18,10 +18,7 @@ from .database import close_database from .app import create_app app = create_app() -ui_bp.register_blueprint(api_bp, url_prefix='/api') -# Mute UserWarning with flask-restx and Flask >= 2.2.0. Will be raised as an exception in 2.3.0 -# https://github.com/python-restx/flask-restx/issues/485 -warnings.filterwarnings('ignore', message='The setup method ') +app.register_blueprint(api_bp, url_prefix=base_url.rstrip('/') + '/api') app.register_blueprint(ui_bp, url_prefix=base_url.rstrip('/')) @@ -56,10 +53,17 @@ class Server: logging.exception("BAZARR cannot bind to specified IP, trying with default (0.0.0.0)") self.address = '0.0.0.0' self.connected = False + super(Server, self).__init__() elif error.errno == errno.EADDRINUSE: - logging.exception("BAZARR cannot bind to specified TCP port, trying with default (6767)") - self.port = '6767' - self.connected = False + if self.port != '6767': + logging.exception("BAZARR cannot bind to specified TCP port, trying with default (6767)") + self.port = '6767' + self.connected = False + super(Server, self).__init__() + else: + logging.exception("BAZARR cannot bind to default TCP port (6767) because it's already in use, " + "exiting...") + self.shutdown(EXIT_PORT_ALREADY_IN_USE_ERROR) else: logging.exception("BAZARR cannot start because of unhandled exception.") self.shutdown() @@ -83,9 +87,9 @@ class Server: pass def close_all(self): - print(f"Closing database...") + print("Closing database...") close_database() - print(f"Closing webserver...") + print("Closing webserver...") self.server.close() def shutdown(self, status=EXIT_NORMAL): diff --git a/bazarr/app/signalr_client.py b/bazarr/app/signalr_client.py index b731e09e8..9f930e215 100644 --- a/bazarr/app/signalr_client.py +++ b/bazarr/app/signalr_client.py @@ -12,7 +12,7 @@ from signalrcore.hub_connection_builder import HubConnectionBuilder from collections import deque from time import sleep -from constants import headers +from constants import HEADERS from app.event_handler import event_stream from sonarr.sync.episodes import sync_episodes, sync_one_episode from sonarr.sync.series import update_series, update_one_series @@ -39,7 +39,7 @@ class SonarrSignalrClientLegacy: self.session = Session() self.session.timeout = 60 self.session.verify = False - self.session.headers = headers + self.session.headers = HEADERS self.connection = None self.connected = False @@ -162,7 +162,7 @@ class SonarrSignalrClient: .with_url(f"{url_sonarr()}/signalr/messages?access_token={self.apikey_sonarr}", options={ "verify_ssl": False, - "headers": headers + "headers": HEADERS }) \ .with_automatic_reconnect({ "type": "raw", @@ -229,7 +229,7 @@ class RadarrSignalrClient: .with_url(f"{url_radarr()}/signalr/messages?access_token={self.apikey_radarr}", options={ "verify_ssl": False, - "headers": headers + "headers": HEADERS }) \ .with_automatic_reconnect({ "type": "raw", diff --git a/bazarr/app/ui.py b/bazarr/app/ui.py index 5ca597be7..df43f7b0c 100644 --- a/bazarr/app/ui.py +++ b/bazarr/app/ui.py @@ -4,11 +4,12 @@ import os import requests import mimetypes -from flask import request, abort, render_template, Response, session, send_file, stream_with_context, Blueprint +from flask import (request, abort, render_template, Response, session, send_file, stream_with_context, Blueprint, + redirect) from functools import wraps from urllib.parse import unquote -from constants import headers +from constants import HEADERS from literals import FILE_LOG from sonarr.info import url_api_sonarr from radarr.info import url_api_radarr @@ -65,6 +66,10 @@ def check_login(actual_method): @ui_bp.route('/', defaults={'path': ''}) @ui_bp.route('/') def catch_all(path): + if path.startswith('login') and settings.auth.type not in ['basic', 'form']: + # login page has been accessed when no authentication is enabled + return redirect(base_url or "/", code=302) + auth = True if settings.auth.type == 'basic': auth = request.authorization @@ -113,7 +118,7 @@ def series_images(url): baseUrl = settings.sonarr.base_url url_image = f'{url_api_sonarr()}{url.lstrip(baseUrl)}?apikey={apikey}'.replace('poster-250', 'poster-500') try: - req = requests.get(url_image, stream=True, timeout=15, verify=False, headers=headers) + req = requests.get(url_image, stream=True, timeout=15, verify=False, headers=HEADERS) except Exception: return '', 404 else: @@ -127,7 +132,7 @@ def movies_images(url): baseUrl = settings.radarr.base_url url_image = f'{url_api_radarr()}{url.lstrip(baseUrl)}?apikey={apikey}' try: - req = requests.get(url_image, stream=True, timeout=15, verify=False, headers=headers) + req = requests.get(url_image, stream=True, timeout=15, verify=False, headers=HEADERS) except Exception: return '', 404 else: @@ -168,7 +173,7 @@ def proxy(protocol, url): url = f'{protocol}://{unquote(url)}' params = request.args try: - result = requests.get(url, params, allow_redirects=False, verify=False, timeout=5, headers=headers) + result = requests.get(url, params, allow_redirects=False, verify=False, timeout=5, headers=HEADERS) except Exception as e: return dict(status=False, error=repr(e)) else: diff --git a/bazarr/constants.py b/bazarr/constants.py index 4f8af9614..b96236625 100644 --- a/bazarr/constants.py +++ b/bazarr/constants.py @@ -1,13 +1,12 @@ # coding=utf-8 import os -import re # set Bazarr user-agent used to make requests -headers = {"User-Agent": os.environ["SZ_USER_AGENT"]} - -# hearing-impaired detection regex -hi_regex = re.compile(r'[*¶♫♪].{3,}[*¶♫♪]|[\[\(\{].{3,}[\]\)\}](? 1 * 1024 * 1024: + if os.path.getsize(subtitle_path) > MAXIMUM_SUBTITLE_SIZE: logging.debug(f"BAZARR subtitles file is too large to be text based. Skipping this file: " f"{subtitle_path}") continue @@ -119,7 +119,7 @@ def guess_external_subtitles(dest_folder, subtitles, media_type, previously_inde # check if file exist: if os.path.exists(subtitle_path) and os.path.splitext(subtitle_path)[1] in core.SUBTITLE_EXTENSIONS: # to improve performance, skip detection of files larger that 1M - if os.path.getsize(subtitle_path) > 1 * 1024 * 1024: + if os.path.getsize(subtitle_path) > MAXIMUM_SUBTITLE_SIZE: logging.debug(f"BAZARR subtitles file is too large to be text based. Skipping this file: " f"{subtitle_path}") continue @@ -136,6 +136,6 @@ def guess_external_subtitles(dest_folder, subtitles, media_type, previously_inde continue text = text.decode(encoding) - if bool(re.search(hi_regex, text)): + if bool(re.search(core.HI_REGEX, text)): subtitles[subtitle] = Language.rebuild(subtitles[subtitle], forced=False, hi=True) return subtitles diff --git a/bazarr/subtitles/manual.py b/bazarr/subtitles/manual.py index b94a51df6..ca8dd42b4 100644 --- a/bazarr/subtitles/manual.py +++ b/bazarr/subtitles/manual.py @@ -18,7 +18,7 @@ from app.config import get_scores, settings, get_array_from from utilities.helper import get_target_folder, force_unicode from app.database import get_profiles_list -from .pool import update_pools, _get_pool, _init_pool +from .pool import update_pools, _get_pool from .utils import get_video, _get_lang_obj, _get_scores, _set_forced_providers from .processing import process_subtitle @@ -46,21 +46,7 @@ def manual_search(path, profile_id, providers, sceneName, title, media_type): try: if providers: subtitles = list_all_subtitles([video], language_set, pool) - - if 'subscene' in providers: - s_pool = _init_pool("movie", profile_id, {"subscene"}) - - subscene_language_set = set() - for language in language_set: - if language.forced: - subscene_language_set.add(language) - if len(subscene_language_set): - s_pool.provider_configs.update({"subscene": {"only_foreign": True}}) - subtitles_subscene = list_all_subtitles([video], subscene_language_set, s_pool) - s_pool.provider_configs.update({"subscene": {"only_foreign": False}}) - subtitles[video] += subtitles_subscene[video] else: - subtitles = [] logging.info("BAZARR All providers are throttled") return 'All providers are throttled' except Exception: diff --git a/bazarr/subtitles/refiners/__init__.py b/bazarr/subtitles/refiners/__init__.py index 750fa4f18..ff1e715a0 100644 --- a/bazarr/subtitles/refiners/__init__.py +++ b/bazarr/subtitles/refiners/__init__.py @@ -3,9 +3,11 @@ from .ffprobe import refine_from_ffprobe from .database import refine_from_db from .arr_history import refine_from_arr_history +from .anidb import refine_from_anidb registered = { "database": refine_from_db, "ffprobe": refine_from_ffprobe, "arr_history": refine_from_arr_history, + "anidb": refine_from_anidb, } diff --git a/bazarr/subtitles/refiners/anidb.py b/bazarr/subtitles/refiners/anidb.py new file mode 100644 index 000000000..88ee55ecb --- /dev/null +++ b/bazarr/subtitles/refiners/anidb.py @@ -0,0 +1,140 @@ +# coding=utf-8 +# fmt: off + +import logging +import requests +from collections import namedtuple +from datetime import timedelta +from requests.exceptions import HTTPError + +from app.config import settings +from subliminal import Episode, region + +try: + from lxml import etree +except ImportError: + try: + import xml.etree.cElementTree as etree + except ImportError: + import xml.etree.ElementTree as etree + +refined_providers = {'animetosho'} + +api_url = 'http://api.anidb.net:9001/httpapi' + + +class AniDBClient(object): + def __init__(self, api_client_key=None, api_client_ver=1, session=None): + self.session = session or requests.Session() + self.api_client_key = api_client_key + self.api_client_ver = api_client_ver + + AnimeInfo = namedtuple('AnimeInfo', ['anime', 'episode_offset']) + + @region.cache_on_arguments(expiration_time=timedelta(days=1).total_seconds()) + def get_series_mappings(self): + r = self.session.get( + 'https://raw.githubusercontent.com/Anime-Lists/anime-lists/master/anime-list.xml', + timeout=10 + ) + + r.raise_for_status() + + return r.content + + @region.cache_on_arguments(expiration_time=timedelta(days=1).total_seconds()) + def get_series_id(self, mappings, tvdb_series_season, tvdb_series_id, episode): + # Enrich the collection of anime with the episode offset + animes = [ + self.AnimeInfo(anime, int(anime.attrib.get('episodeoffset', 0))) + for anime in mappings.findall( + f".//anime[@tvdbid='{tvdb_series_id}'][@defaulttvdbseason='{tvdb_series_season}']" + ) + ] + + if not animes: + return None, None + + # Sort the anime by offset in ascending order + animes.sort(key=lambda a: a.episode_offset) + + # Different from Tvdb, Anidb have different ids for the Parts of a season + anidb_id = None + offset = 0 + + for index, anime_info in enumerate(animes): + anime, episode_offset = anime_info + anidb_id = int(anime.attrib.get('anidbid')) + if episode > episode_offset: + anidb_id = anidb_id + offset = episode_offset + + return anidb_id, episode - offset + + @region.cache_on_arguments(expiration_time=timedelta(days=1).total_seconds()) + def get_series_episodes_ids(self, tvdb_series_id, season, episode): + mappings = etree.fromstring(self.get_series_mappings()) + + series_id, episode_no = self.get_series_id(mappings, season, tvdb_series_id, episode) + + if not series_id: + return None, None + + episodes = etree.fromstring(self.get_episodes(series_id)) + + return series_id, int(episodes.find(f".//episode[epno='{episode_no}']").attrib.get('id')) + + @region.cache_on_arguments(expiration_time=timedelta(days=1).total_seconds()) + def get_episodes(self, series_id): + r = self.session.get( + api_url, + params={ + 'request': 'anime', + 'client': self.api_client_key, + 'clientver': self.api_client_ver, + 'protover': 1, + 'aid': series_id + }, + timeout=10) + r.raise_for_status() + + xml_root = etree.fromstring(r.content) + + response_code = xml_root.attrib.get('code') + if response_code == '500': + raise HTTPError('AniDB API Abuse detected. Banned status.') + elif response_code == '302': + raise HTTPError('AniDB API Client error. Client is disabled or does not exists.') + + episode_elements = xml_root.find('episodes') + + if not episode_elements: + raise ValueError + + return etree.tostring(episode_elements, encoding='utf8', method='xml') + + +def refine_from_anidb(path, video): + if not isinstance(video, Episode) or not video.series_tvdb_id: + logging.debug(f'Video is not an Anime TV series, skipping refinement for {video}') + + return + + if refined_providers.intersection(settings.general.enabled_providers) and video.series_anidb_id is None: + refine_anidb_ids(video) + + +def refine_anidb_ids(video): + anidb_client = AniDBClient(settings.anidb.api_client, settings.anidb.api_client_ver) + + season = video.season if video.season else 0 + + anidb_series_id, anidb_episode_id = anidb_client.get_series_episodes_ids(video.series_tvdb_id, season, video.episode) + + if not anidb_episode_id: + logging.error(f'Could not find anime series {video.series}') + + return video + + video.series_anidb_id = anidb_series_id + video.series_anidb_episode_id = anidb_episode_id diff --git a/bazarr/subtitles/sync.py b/bazarr/subtitles/sync.py index dffc24fcb..f7a3a69c4 100644 --- a/bazarr/subtitles/sync.py +++ b/bazarr/subtitles/sync.py @@ -33,9 +33,9 @@ def sync_subtitles(video_path, srt_path, srt_lang, forced, percent_score, sonarr 'max_offset_seconds': str(settings.subsync.max_offset_seconds), 'no_fix_framerate': settings.subsync.no_fix_framerate, 'gss': settings.subsync.gss, - 'reference': None, # means choose automatically within video file - 'sonarr_series_id': sonarr_series_id, - 'sonarr_episode_id': sonarr_episode_id, + 'reference': None, # means choose automatically within video file + 'sonarr_series_id': sonarr_series_id, + 'sonarr_episode_id': sonarr_episode_id, 'radarr_id': radarr_id, } subsync.sync(**sync_kwargs) diff --git a/bazarr/subtitles/tools/subsyncer.py b/bazarr/subtitles/tools/subsyncer.py index 72364aa5e..a71424516 100644 --- a/bazarr/subtitles/tools/subsyncer.py +++ b/bazarr/subtitles/tools/subsyncer.py @@ -30,8 +30,8 @@ class SubSyncer: self.vad = 'subs_then_webrtc' self.log_dir_path = os.path.join(args.config_dir, 'log') - def sync(self, video_path, srt_path, srt_lang, - max_offset_seconds, no_fix_framerate, gss, reference=None, + def sync(self, video_path, srt_path, srt_lang, + max_offset_seconds, no_fix_framerate, gss, reference=None, sonarr_series_id=None, sonarr_episode_id=None, radarr_id=None): self.reference = video_path self.srtin = srt_path diff --git a/bazarr/subtitles/upgrade.py b/bazarr/subtitles/upgrade.py index eb072b500..4593c71bc 100644 --- a/bazarr/subtitles/upgrade.py +++ b/bazarr/subtitles/upgrade.py @@ -110,7 +110,9 @@ def upgrade_subtitles(): episode['seriesTitle'], 'series', forced_minimum_score=int(episode['score']), - is_upgrade=True)) + is_upgrade=True, + previous_subtitles_to_delete=path_mappings.path_replace( + episode['subtitles_path']))) if result: if isinstance(result, list) and len(result): @@ -195,7 +197,9 @@ def upgrade_subtitles(): movie['title'], 'movie', forced_minimum_score=int(movie['score']), - is_upgrade=True)) + is_upgrade=True, + previous_subtitles_to_delete=path_mappings.path_replace_movie( + movie['subtitles_path']))) if result: if isinstance(result, list) and len(result): result = result[0] diff --git a/bazarr/subtitles/utils.py b/bazarr/subtitles/utils.py index 4fa0a8d27..436bc7b52 100644 --- a/bazarr/subtitles/utils.py +++ b/bazarr/subtitles/utils.py @@ -97,7 +97,6 @@ def _set_forced_providers(pool, also_forced=False, forced_required=False): pool.provider_configs.update( { "podnapisi": {'also_foreign': also_forced, "only_foreign": forced_required}, - "subscene": {"only_foreign": forced_required}, "opensubtitles": {'also_foreign': also_forced, "only_foreign": forced_required} } ) diff --git a/bazarr/utilities/backup.py b/bazarr/utilities/backup.py index 136a959b1..a28eadef5 100644 --- a/bazarr/utilities/backup.py +++ b/bazarr/utilities/backup.py @@ -33,7 +33,7 @@ def get_restore_path(): def get_backup_files(fullpath=True): backup_file_pattern = os.path.join(get_backup_path(), 'bazarr_backup_v*.zip') file_list = glob(backup_file_pattern) - file_list.sort(key=os.path.getmtime) + file_list.sort(key=os.path.getmtime, reverse=True) if fullpath: return file_list else: diff --git a/bazarr/utilities/central.py b/bazarr/utilities/central.py index 3a0ed8378..009c42e1d 100644 --- a/bazarr/utilities/central.py +++ b/bazarr/utilities/central.py @@ -6,30 +6,37 @@ import logging import os from pathlib import Path -from literals import * + +from literals import ENV_BAZARR_ROOT_DIR, DIR_LOG, ENV_STOPFILE, ENV_RESTARTFILE, EXIT_NORMAL, FILE_LOG + def get_bazarr_dir(sub_dir): path = os.path.join(os.environ[ENV_BAZARR_ROOT_DIR], sub_dir) return path + def make_bazarr_dir(sub_dir): path = get_bazarr_dir(sub_dir) if not os.path.exists(path): os.mkdir(path) + def get_log_file_path(): path = os.path.join(get_bazarr_dir(DIR_LOG), FILE_LOG) return path + def get_stop_file_path(): return os.environ[ENV_STOPFILE] - + + def get_restart_file_path(): return os.environ[ENV_RESTARTFILE] + def stop_bazarr(status_code=EXIT_NORMAL, exit_main=True): try: - with open(get_stop_file_path(),'w', encoding='UTF-8') as file: + with open(get_stop_file_path(), 'w', encoding='UTF-8') as file: # write out status code for final exit file.write(f'{status_code}\n') file.close() @@ -39,6 +46,7 @@ def stop_bazarr(status_code=EXIT_NORMAL, exit_main=True): if exit_main: raise SystemExit(status_code) + def restart_bazarr(): try: Path(get_restart_file_path()).touch() @@ -46,4 +54,3 @@ def restart_bazarr(): logging.error(f'BAZARR Cannot create restart file: {repr(e)}') logging.info('Bazarr is being restarted...') raise SystemExit(EXIT_NORMAL) - \ No newline at end of file diff --git a/custom_libs/custom_version.txt b/custom_libs/custom_version.txt index 687b8e37a..52f3519d0 100644 --- a/custom_libs/custom_version.txt +++ b/custom_libs/custom_version.txt @@ -15,5 +15,4 @@ deathbycaptcha # unknown version, only found on gist git+https://github.com/pannal/libfilebot#egg=libfilebot git+https://github.com/RobinDavid/pyADS.git@28a2f6dbfb357f85b2c2f49add770b336e88840d#egg=pyads py7zr==0.7.0 # modified to prevent importing of modules that can't be vendored -subscene-api==1.0.0 # modified specificaly for Bazarr subliminal==2.1.0 # modified specifically for Bazarr diff --git a/custom_libs/libfilebot/main.py b/custom_libs/libfilebot/main.py index 3e1333e0b..e93822bb3 100644 --- a/custom_libs/libfilebot/main.py +++ b/custom_libs/libfilebot/main.py @@ -50,7 +50,7 @@ def default_xattr(fn): XATTR_MAP = { "default": ( default_xattr, - lambda result: re.search('(?um)(net\.filebot\.filename(?=="|: )[=:" ]+|Attribute.+:\s)([^"\n\r\0]+)', + lambda result: re.search(r'(?um)(net\.filebot\.filename(?=="|: )[=:" ]+|Attribute.+:\s)([^"\n\r\0]+)', result).group(2) ), # "darwin": ( @@ -60,7 +60,7 @@ XATTR_MAP = { # ), "darwin": ( lambda fn: ["filebot", "-script", "fn:xattr", fn], - lambda result: re.search('(?um)(net\.filebot\.filename(?=="|: )[=:" ]+|Attribute.+:\s)([^"\n\r\0]+)', + lambda result: re.search(r'(?um)(net\.filebot\.filename(?=="|: )[=:" ]+|Attribute.+:\s)([^"\n\r\0]+)', result).group(2) ), "win32": ( diff --git a/custom_libs/subliminal/core.py b/custom_libs/subliminal/core.py index cf26f333a..142d50f22 100644 --- a/custom_libs/subliminal/core.py +++ b/custom_libs/subliminal/core.py @@ -591,7 +591,7 @@ def scan_videos(path, age=None, archives=True): def refine(video, episode_refiners=None, movie_refiners=None, **kwargs): - """Refine a video using :ref:`refiners`. + r"""Refine a video using :ref:`refiners`. .. note:: @@ -619,7 +619,7 @@ def refine(video, episode_refiners=None, movie_refiners=None, **kwargs): def list_subtitles(videos, languages, pool_class=ProviderPool, **kwargs): - """List subtitles. + r"""List subtitles. The `videos` must pass the `languages` check of :func:`check_video`. @@ -660,7 +660,7 @@ def list_subtitles(videos, languages, pool_class=ProviderPool, **kwargs): def download_subtitles(subtitles, pool_class=ProviderPool, **kwargs): - """Download :attr:`~subliminal.subtitle.Subtitle.content` of `subtitles`. + r"""Download :attr:`~subliminal.subtitle.Subtitle.content` of `subtitles`. :param subtitles: subtitles to download. :type subtitles: list of :class:`~subliminal.subtitle.Subtitle` @@ -677,7 +677,7 @@ def download_subtitles(subtitles, pool_class=ProviderPool, **kwargs): def download_best_subtitles(videos, languages, min_score=0, hearing_impaired=False, only_one=False, compute_score=None, pool_class=ProviderPool, **kwargs): - """List and download the best matching subtitles. + r"""List and download the best matching subtitles. The `videos` must pass the `languages` and `undefined` (`only_one`) checks of :func:`check_video`. diff --git a/custom_libs/subliminal/extensions.py b/custom_libs/subliminal/extensions.py index 327087a02..547abe98a 100644 --- a/custom_libs/subliminal/extensions.py +++ b/custom_libs/subliminal/extensions.py @@ -6,7 +6,7 @@ from stevedore import ExtensionManager class RegistrableExtensionManager(ExtensionManager): - """:class:~stevedore.extensions.ExtensionManager` with support for registration. + r""":class:~stevedore.extensions.ExtensionManager` with support for registration. It allows loading of internal extensions without setup and registering/unregistering additional extensions. diff --git a/custom_libs/subliminal/refiners/__init__.py b/custom_libs/subliminal/refiners/__init__.py index bbb8d3ef8..4f3e45418 100644 --- a/custom_libs/subliminal/refiners/__init__.py +++ b/custom_libs/subliminal/refiners/__init__.py @@ -1,4 +1,4 @@ -""" +r""" Refiners enrich a :class:`~subliminal.video.Video` object by adding information to it. A refiner is a simple function: diff --git a/custom_libs/subliminal/video.py b/custom_libs/subliminal/video.py index 1264d0b84..2168d91a9 100644 --- a/custom_libs/subliminal/video.py +++ b/custom_libs/subliminal/video.py @@ -115,7 +115,7 @@ class Video(object): class Episode(Video): - """Episode :class:`Video`. + r"""Episode :class:`Video`. :param str series: series of the episode. :param int season: season number of the episode. @@ -129,7 +129,8 @@ class Episode(Video): """ def __init__(self, name, series, season, episode, title=None, year=None, original_series=True, tvdb_id=None, - series_tvdb_id=None, series_imdb_id=None, alternative_series=None, **kwargs): + series_tvdb_id=None, series_imdb_id=None, alternative_series=None, series_anidb_id=None, + series_anidb_episode_id=None, **kwargs): super(Episode, self).__init__(name, **kwargs) #: Series of the episode @@ -162,6 +163,9 @@ class Episode(Video): #: Alternative names of the series self.alternative_series = alternative_series or [] + self.series_anidb_episode_id = series_anidb_episode_id + self.series_anidb_id = series_anidb_id + @classmethod def fromguess(cls, name, guess): if guess['type'] != 'episode': @@ -198,7 +202,7 @@ class Episode(Video): class Movie(Video): - """Movie :class:`Video`. + r"""Movie :class:`Video`. :param str title: title of the movie. :param int year: year of the movie. diff --git a/custom_libs/subliminal_patch/converters/subscene.py b/custom_libs/subliminal_patch/converters/subscene.py deleted file mode 100644 index 1d1727c2e..000000000 --- a/custom_libs/subliminal_patch/converters/subscene.py +++ /dev/null @@ -1,92 +0,0 @@ -# coding=utf-8 - -from __future__ import absolute_import -from babelfish import LanguageReverseConverter -from subliminal.exceptions import ConfigurationError -from subzero.language import Language - - -# alpha3 codes extracted from `https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes` -# Subscene language list extracted from it's upload form -from_subscene = { - 'Farsi/Persian': 'fas', 'Greek': 'ell', 'Greenlandic': 'kal', - 'Malay': 'msa', 'Pashto': 'pus', 'Punjabi': 'pan', 'Swahili': 'swa' -} - -from_subscene_with_country = { - 'Brazillian Portuguese': ('por', 'BR') -} - -to_subscene_with_country = {val: key for key, val in from_subscene_with_country.items()} - - -to_subscene = {v: k for k, v in from_subscene.items()} - -exact_languages_alpha3 = [ - 'ara', 'aze', 'bel', 'ben', 'bos', 'bul', 'cat', 'ces', 'dan', 'deu', - 'eng', 'epo', 'est', 'eus', 'fin', 'fra', 'heb', 'hin', 'hrv', 'hun', - 'hye', 'ind', 'isl', 'ita', 'jpn', 'kat', 'kor', 'kur', 'lav', 'lit', - 'mal', 'mkd', 'mni', 'mon', 'mya', 'nld', 'nor', 'pol', 'por', 'ron', - 'rus', 'sin', 'slk', 'slv', 'som', 'spa', 'sqi', 'srp', 'sun', 'swe', - 'tam', 'tel', 'tgl', 'tha', 'tur', 'ukr', 'urd', 'vie', 'yor' -] - -language_ids = { - 'ara': 2, 'dan': 10, 'nld': 11, 'eng': 13, 'fas': 46, 'fin': 17, - 'fra': 18, 'heb': 22, 'ind': 44, 'ita': 26, 'msa': 50, 'nor': 30, - 'ron': 33, 'spa': 38, 'swe': 39, 'vie': 45, 'sqi': 1, 'hye': 73, - 'aze': 55, 'eus': 74, 'bel': 68, 'ben': 54, 'bos': 60, 'bul': 5, - 'mya': 61, 'cat': 49, 'hrv': 8, 'ces': 9, 'epo': 47, 'est': 16, - 'kat': 62, 'deu': 19, 'ell': 21, 'kal': 57, 'hin': 51, 'hun': 23, - 'isl': 25, 'jpn': 27, 'kor': 28, 'kur': 52, 'lav': 29, 'lit': 43, - 'mkd': 48, 'mal': 64, 'mni': 65, 'mon': 72, 'pus': 67, 'pol': 31, - 'por': 32, 'pan': 66, 'rus': 34, 'srp': 35, 'sin': 58, 'slk': 36, - 'slv': 37, 'som': 70, 'tgl': 53, 'tam': 59, 'tel': 63, 'tha': 40, - 'tur': 41, 'ukr': 56, 'urd': 42, 'yor': 71, 'pt-BR': 4 -} - -# TODO: specify codes for unspecified_languages -unspecified_languages = [ - 'Big 5 code', 'Bulgarian/ English', - 'Chinese BG code', 'Dutch/ English', 'English/ German', - 'Hungarian/ English', 'Rohingya' -] - -supported_languages = {Language(l) for l in exact_languages_alpha3} - -alpha3_of_code = {l.name: l.alpha3 for l in supported_languages} - -supported_languages.update({Language(l) for l in to_subscene}) - -supported_languages.update({Language(lang, cr) for lang, cr in to_subscene_with_country}) - - -class SubsceneConverter(LanguageReverseConverter): - codes = {l.name for l in supported_languages} - - def convert(self, alpha3, country=None, script=None): - if alpha3 in exact_languages_alpha3: - return Language(alpha3).name - - if alpha3 in to_subscene: - return to_subscene[alpha3] - - if (alpha3, country) in to_subscene_with_country: - return to_subscene_with_country[(alpha3, country)] - - raise ConfigurationError('Unsupported language for subscene: %s, %s, %s' % (alpha3, country, script)) - - def reverse(self, code): - if code in from_subscene_with_country: - return from_subscene_with_country[code] - - if code in from_subscene: - return (from_subscene[code],) - - if code in alpha3_of_code: - return (alpha3_of_code[code],) - - if code in unspecified_languages: - raise NotImplementedError("currently this language is unspecified: %s" % code) - - raise ConfigurationError('Unsupported language code for subscene: %s' % code) \ No newline at end of file diff --git a/custom_libs/subliminal_patch/core.py b/custom_libs/subliminal_patch/core.py index 3c7e00479..cb21f4581 100644 --- a/custom_libs/subliminal_patch/core.py +++ b/custom_libs/subliminal_patch/core.py @@ -49,6 +49,8 @@ SUBTITLE_EXTENSIONS = ('.srt', '.sub', '.smi', '.txt', '.ssa', '.ass', '.mpl', ' _POOL_LIFETIME = datetime.timedelta(hours=12) +HI_REGEX = re.compile(r'[*¶♫♪].{3,}[*¶♫♪]|[\[\(\{].{3,}[\]\)\}](? 0 else 0 + logger.info('%r: Score %d is below min_score: %d out of %d (or %r%%)', + subtitle, score, min_score, max_score, min_score_in_percent) break # stop when all languages are downloaded @@ -1054,7 +1059,7 @@ def list_supported_video_types(pool_class, **kwargs): def download_subtitles(subtitles, pool_class=ProviderPool, **kwargs): - """Download :attr:`~subliminal.subtitle.Subtitle.content` of `subtitles`. + r"""Download :attr:`~subliminal.subtitle.Subtitle.content` of `subtitles`. :param subtitles: subtitles to download. :type subtitles: list of :class:`~subliminal.subtitle.Subtitle` @@ -1071,7 +1076,7 @@ def download_subtitles(subtitles, pool_class=ProviderPool, **kwargs): def download_best_subtitles(videos, languages, min_score=0, hearing_impaired=False, only_one=False, compute_score=None, pool_class=ProviderPool, throttle_time=0, **kwargs): - """List and download the best matching subtitles. + r"""List and download the best matching subtitles. The `videos` must pass the `languages` and `undefined` (`only_one`) checks of :func:`check_video`. @@ -1198,6 +1203,8 @@ def save_subtitles(file_path, subtitles, single=False, directory=None, chmod=Non continue # create subtitle path + if bool(re.search(HI_REGEX, subtitle.text)): + subtitle.language.hi = True subtitle_path = get_subtitle_path(file_path, None if single else subtitle.language, forced_tag=subtitle.language.forced, hi_tag=False if must_remove_hi else subtitle.language.hi, tags=tags) @@ -1242,7 +1249,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`. + r"""Refine a video using :ref:`refiners`. patch: add traceback logging diff --git a/custom_libs/subliminal_patch/extensions.py b/custom_libs/subliminal_patch/extensions.py index eeb2ee4cb..800a1d547 100644 --- a/custom_libs/subliminal_patch/extensions.py +++ b/custom_libs/subliminal_patch/extensions.py @@ -64,4 +64,3 @@ subliminal.refiner_manager.register('drone = subliminal_patch.refiners.drone:ref subliminal.refiner_manager.register('filebot = subliminal_patch.refiners.filebot:refine') subliminal.refiner_manager.register('file_info_file = subliminal_patch.refiners.file_info_file:refine') subliminal.refiner_manager.register('symlinks = subliminal_patch.refiners.symlinks:refine') - diff --git a/custom_libs/subliminal_patch/providers/animetosho.py b/custom_libs/subliminal_patch/providers/animetosho.py new file mode 100644 index 000000000..1fb791e86 --- /dev/null +++ b/custom_libs/subliminal_patch/providers/animetosho.py @@ -0,0 +1,186 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import + +import logging +import lzma + +from guessit import guessit +from requests import Session +from subzero.language import Language + + +from subliminal.exceptions import ConfigurationError, ProviderError +from subliminal_patch.providers import Provider +from subliminal_patch.providers.mixins import ProviderSubtitleArchiveMixin +from subliminal_patch.subtitle import Subtitle, guess_matches +from subliminal.video import Episode + +try: + from lxml import etree +except ImportError: + try: + import xml.etree.cElementTree as etree + except ImportError: + import xml.etree.ElementTree as etree + +logger = logging.getLogger(__name__) + +supported_languages = [ + "ara", # Arabic + "eng", # English + "fin", # Finnish + "fra", # French + "heb", # Hebrew + "ita", # Italian + "jpn", # Japanese + "por", # Portuguese + "pol", # Polish + "spa", # Spanish + "swe", # Swedish + "tha", # Thai + "tur", # Turkish +] + + +class AnimeToshoSubtitle(Subtitle): + """AnimeTosho.org Subtitle.""" + provider_name = 'animetosho' + + def __init__(self, language, download_link, meta, release_info): + super(AnimeToshoSubtitle, self).__init__(language, page_link=download_link) + self.meta = meta + self.download_link = download_link + self.release_info = release_info + + @property + def id(self): + return self.download_link + + def get_matches(self, video): + matches = set() + matches |= guess_matches(video, guessit(self.meta['filename'])) + + # Add these data are explicit extracted from the API and they always have to match otherwise they wouldn't + # arrive at this point and would stop on list_subtitles. + matches.update(['title', 'series', 'tvdb_id', 'season', 'episode']) + + return matches + + +class AnimeToshoProvider(Provider, ProviderSubtitleArchiveMixin): + """AnimeTosho.org Provider.""" + subtitle_class = AnimeToshoSubtitle + languages = {Language('por', 'BR')} | {Language(sl) for sl in supported_languages} + video_types = Episode + + def __init__(self, search_threshold=None): + self.session = None + + if not all([search_threshold]): + raise ConfigurationError("Search threshold, Api Client and Version must be specified!") + + self.search_threshold = search_threshold + + def initialize(self): + self.session = Session() + + def terminate(self): + self.session.close() + + def list_subtitles(self, video, languages): + if not video.series_anidb_episode_id: + logger.debug('Skipping video %r. It is not an anime or the anidb_episode_id could not be identified', video) + + return [] + + return [s for s in self._get_series(video.series_anidb_episode_id) if s.language in languages] + + def download_subtitle(self, subtitle): + logger.info('Downloading subtitle %r', subtitle) + + r = self.session.get(subtitle.page_link, timeout=10) + r.raise_for_status() + + # Check if the bytes content starts with the xz magic number of the xz archives + if not self._is_xz_file(r.content): + raise ProviderError('Unidentified archive type') + + subtitle.content = lzma.decompress(r.content) + + return subtitle + + @staticmethod + def _is_xz_file(content): + return content.startswith(b'\xFD\x37\x7A\x58\x5A\x00') + + def _get_series(self, episode_id): + storage_download_url = 'https://animetosho.org/storage/attach/' + feed_api_url = 'https://feed.animetosho.org/json' + + subtitles = [] + + entries = self._get_series_entries(episode_id) + + for entry in entries: + r = self.session.get( + feed_api_url, + params={ + 'show': 'torrent', + 'id': entry['id'], + }, + timeout=10 + ) + r.raise_for_status() + + for file in r.json()['files']: + if 'attachments' not in file: + continue + + subtitle_files = list(filter(lambda f: f['type'] == 'subtitle', file['attachments'])) + + for subtitle_file in subtitle_files: + hex_id = format(subtitle_file['id'], '08x') + + lang = Language.fromalpha3b(subtitle_file['info']['lang']) + + # For Portuguese and Portuguese Brazilian they both share the same code, the name is the only + # identifier AnimeTosho provides. Also, some subtitles does not have name, in this case it could + # be a false negative but there is nothing we can use to guarantee it is PT-BR, we rather skip it. + if lang.alpha3 == 'por' and subtitle_file['info'].get('name', '').lower().find('brazil'): + lang = Language('por', 'BR') + + subtitle = self.subtitle_class( + lang, + storage_download_url + '{}/{}.xz'.format(hex_id, subtitle_file['id']), + meta=file, + release_info=entry.get('title'), + ) + + logger.debug('Found subtitle %r', subtitle) + + subtitles.append(subtitle) + + return subtitles + + def _get_series_entries(self, episode_id): + api_url = 'https://feed.animetosho.org/json' + + r = self.session.get( + api_url, + params={ + 'eid': episode_id, + }, + timeout=10 + ) + + r.raise_for_status() + + j = r.json() + + # Ignore records that are not yet ready or has been abandoned by AnimeTosho. + entries = list(filter(lambda t: t['status'] == 'complete', j))[:self.search_threshold] + + # Return the latest entries that have been added as it is used to cutoff via the user configuration threshold + entries.sort(key=lambda t: t['timestamp'], reverse=True) + + return entries diff --git a/custom_libs/subliminal_patch/providers/betaseries.py b/custom_libs/subliminal_patch/providers/betaseries.py index 4cd66401c..dfc92ccdd 100644 --- a/custom_libs/subliminal_patch/providers/betaseries.py +++ b/custom_libs/subliminal_patch/providers/betaseries.py @@ -83,6 +83,14 @@ class BetaSeriesProvider(Provider): logger.debug('Searching subtitles %r', params) res = self.session.get( server_url + 'episodes/display', params=params, timeout=10) + try: + if res.status_code == 400 and res.json()['errors'][0]['code'] == 4001: + # this is to catch no series found + return [] + elif res.status_code == 400 and res.json()['errors'][0]['code'] == 1001: + raise AuthenticationError("Invalid token provided") + except Exception: + pass res.raise_for_status() result = res.json() matches.add('tvdb_id') @@ -96,8 +104,14 @@ class BetaSeriesProvider(Provider): logger.debug('Searching subtitles %r', params) res = self.session.get( server_url + 'shows/episodes', params=params, timeout=10) - if res.status_code == 400: - raise AuthenticationError("Invalid token provided") + try: + if res.status_code == 400 and res.json()['errors'][0]['code'] == 4001: + # this is to catch no series found + return [] + elif res.status_code == 400 and res.json()['errors'][0]['code'] == 1001: + raise AuthenticationError("Invalid token provided") + except Exception: + pass res.raise_for_status() result = res.json() matches.add('series_tvdb_id') diff --git a/custom_libs/subliminal_patch/providers/embeddedsubtitles.py b/custom_libs/subliminal_patch/providers/embeddedsubtitles.py index 7fbf1bef8..002f439b7 100644 --- a/custom_libs/subliminal_patch/providers/embeddedsubtitles.py +++ b/custom_libs/subliminal_patch/providers/embeddedsubtitles.py @@ -208,8 +208,11 @@ class EmbeddedSubtitlesProvider(Provider): except Exception as error: logger.debug("'%s' raised running modifier", error) - with open(path, "rb") as sub: - subtitle.content = sub.read() + if os.path.exists(path): + with open(path, "rb") as sub: + subtitle.content = sub.read() + else: + logger.error("%s not found in filesystem", path) def _get_subtitle_path(self, subtitle: EmbeddedSubtitle): container = subtitle.container @@ -379,7 +382,7 @@ def _clean_ass_subtitles(path, output_path): logger.debug("Cleaned lines: %d", abs(len(lines) - len(clean_lines))) - with open(output_path, "w") as f: + with open(output_path, "w", encoding="utf-8", errors="ignore") as f: f.writelines(clean_lines) logger.debug("Lines written to output path: %s", output_path) diff --git a/custom_libs/subliminal_patch/providers/hdbits.py b/custom_libs/subliminal_patch/providers/hdbits.py index 10793b9ea..19196aecd 100644 --- a/custom_libs/subliminal_patch/providers/hdbits.py +++ b/custom_libs/subliminal_patch/providers/hdbits.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- import functools -from json import JSONDecodeError +from requests.exceptions import JSONDecodeError import logging import re import time diff --git a/custom_libs/subliminal_patch/providers/ktuvit.py b/custom_libs/subliminal_patch/providers/ktuvit.py index 9bb746547..699349601 100644 --- a/custom_libs/subliminal_patch/providers/ktuvit.py +++ b/custom_libs/subliminal_patch/providers/ktuvit.py @@ -3,6 +3,7 @@ import io import logging import os import json +from requests.exceptions import JSONDecodeError from subzero.language import Language from guessit import guessit @@ -144,7 +145,7 @@ class KtuvitProvider(Provider): self.session.headers["Pragma"] = "no-cache" self.session.headers["Cache-Control"] = "no-cache" self.session.headers["Content-Type"] = "application/json" - self.session.headers["User-Agent"]: os.environ.get( + self.session.headers["User-Agent"] = os.environ.get( "SZ_USER_AGENT", "Sub-Zero/2" ) @@ -161,13 +162,13 @@ class KtuvitProvider(Provider): is_success = self.parse_d_response( r, "IsSuccess", False, "Authentication to the provider" ) - except json.decoder.JSONDecodeError: + except JSONDecodeError: logger.info("Failed to Login to Ktuvit") if not is_success: error_message = '' try: error_message = self.parse_d_response(r, "ErrorMessage", "[None]") - except json.decode.JSONDecoderError: + except JSONDecodeError: raise AuthenticationError( "Error Logging in to Ktuvit Provider: " + str(r.content) ) @@ -473,8 +474,8 @@ class KtuvitProvider(Provider): try: response_content = response.json() - except json.decoder.JSONDecodeError as ex: - raise json.decoder.JSONDecodeError( + except JSONDecodeError as ex: + raise JSONDecodeError( "Unable to parse JSON returned while getting " + message, ex.doc, ex.pos ) else: @@ -486,11 +487,11 @@ class KtuvitProvider(Provider): value = response_content.get(field, default_value) if not value and value != default_value: - raise json.decoder.JSONDecodeError( + raise JSONDecodeError( "Missing " + message, str(response_content), 0 ) else: - raise json.decoder.JSONDecodeError( + raise JSONDecodeError( "Incomplete JSON returned while getting " + message, str(response_content), 0 diff --git a/custom_libs/subliminal_patch/providers/legendasdivx.py b/custom_libs/subliminal_patch/providers/legendasdivx.py index 46a52ffd1..612693bb9 100644 --- a/custom_libs/subliminal_patch/providers/legendasdivx.py +++ b/custom_libs/subliminal_patch/providers/legendasdivx.py @@ -324,7 +324,7 @@ class LegendasdivxProvider(Provider): # for series, if no results found, try again just with series and season (subtitle packs) if isinstance(video, Episode): logger.debug("Legendasdivx.pt :: trying again with just series and season on query.") - querytext = re.sub("(e|E)(\d{2})", "", querytext) + querytext = re.sub(r"(e|E)(\d{2})", "", querytext) # sleep for a 1 second before another request sleep(1) res = self.session.get(_searchurl.format(query=querytext), allow_redirects=False) diff --git a/custom_libs/subliminal_patch/providers/subdivx.py b/custom_libs/subliminal_patch/providers/subdivx.py index 4b250a2a2..ae8625f47 100644 --- a/custom_libs/subliminal_patch/providers/subdivx.py +++ b/custom_libs/subliminal_patch/providers/subdivx.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import -from json import JSONDecodeError +from requests.exceptions import JSONDecodeError import logging import random import re diff --git a/custom_libs/subliminal_patch/providers/subscene.py b/custom_libs/subliminal_patch/providers/subscene.py deleted file mode 100644 index e11ab0b02..000000000 --- a/custom_libs/subliminal_patch/providers/subscene.py +++ /dev/null @@ -1,366 +0,0 @@ -# coding=utf-8 - -import io -import logging -import os -import time -import traceback -from urllib import parse - -import requests - -import inflect -import re -import json - -import html - -import zipfile -import rarfile -from babelfish import language_converters -from guessit import guessit -from dogpile.cache.api import NO_VALUE -from requests.exceptions import RequestException -from subliminal import Episode, ProviderError -from subliminal.video import Episode, Movie -from subliminal.exceptions import ConfigurationError, ServiceUnavailable -from subliminal.utils import sanitize_release_group -from subliminal.cache import region -from subliminal_patch.http import RetryingCFSession -from subliminal_patch.providers import Provider, reinitialize_on_error -from subliminal_patch.providers.mixins import ProviderSubtitleArchiveMixin -from subliminal_patch.subtitle import Subtitle, guess_matches -from subliminal_patch.converters.subscene import language_ids, supported_languages -from subscene_api.subscene import search, SearchTypes, Subtitle as APISubtitle, SITE_DOMAIN -from subzero.language import Language - -p = inflect.engine() - -language_converters.register('subscene = subliminal_patch.converters.subscene:SubsceneConverter') -logger = logging.getLogger(__name__) - - -class SubsceneSubtitle(Subtitle): - provider_name = 'subscene' - hearing_impaired_verifiable = True - is_pack = False - page_link = None - season = None - episode = None - releases = None - - def __init__(self, language, release_info, hearing_impaired=False, page_link=None, encoding=None, mods=None, - asked_for_release_group=None, asked_for_episode=None): - super(SubsceneSubtitle, self).__init__(language, hearing_impaired=hearing_impaired, page_link=page_link, - encoding=encoding, mods=mods) - self.release_info = self.releases = release_info - self.asked_for_episode = asked_for_episode - self.asked_for_release_group = asked_for_release_group - self.season = None - self.episode = None - - @classmethod - def from_api(cls, s): - return cls(Language.fromsubscene(s.language.strip()), s.title, hearing_impaired=s.hearing_impaired, - page_link=s.url) - - @property - def id(self): - return self.page_link - - @property - def numeric_id(self): - return self.page_link.split("/")[-1] - - def get_matches(self, video): - matches = set() - - if self.release_info.strip() == get_video_filename(video): - logger.debug("Using hash match as the release name is the same") - matches |= {"hash"} - - # episode - if isinstance(video, Episode): - guess = guessit(self.release_info, {'type': 'episode'}) - self.season = guess.get("season") - self.episode = guess.get("episode") - - matches |= guess_matches(video, guess) - if "season" in matches and "episode" not in guess: - # pack - matches.add("episode") - logger.debug("%r is a pack", self) - self.is_pack = True - - if "title" in guess and "year" in matches: - if video.series in guess['title']: - matches.add("series") - - # movie - else: - guess = guessit(self.release_info, {'type': 'movie'}) - matches |= guess_matches(video, guess) - - if video.release_group and "release_group" not in matches and "release_group" in guess: - if sanitize_release_group(video.release_group) in sanitize_release_group(guess["release_group"]): - matches.add("release_group") - - self.matches = matches - - return matches - - def get_download_link(self, session): - return APISubtitle.get_zipped_url(self.page_link, session) - - -def get_video_filename(video): - return os.path.splitext(os.path.basename(video.original_name))[0] - - -class SubsceneProvider(Provider, ProviderSubtitleArchiveMixin): - """ - This currently only searches for the filename on SubScene. It doesn't open every found subtitle page to avoid - massive hammering, thus it can't determine whether a subtitle is only-foreign or not. - """ - subtitle_class = SubsceneSubtitle - languages = supported_languages - languages.update(set(Language.rebuild(l, forced=True) for l in languages)) - languages.update(set(Language.rebuild(l, hi=True) for l in languages)) - video_types = (Episode, Movie) - session = None - skip_wrong_fps = False - hearing_impaired_verifiable = True - only_foreign = False - username = None - password = None - - search_throttle = 8 # seconds - - def __init__(self, only_foreign=False, username=None, password=None): - if not all((username, password)): - raise ConfigurationError('Username and password must be specified') - - self.only_foreign = only_foreign - self.username = username - self.password = password - - def initialize(self): - logger.info("Creating session") - self.session = RetryingCFSession() - - prev_cookies = region.get("subscene_cookies2") - if prev_cookies != NO_VALUE: - logger.debug("Re-using old subscene cookies: %r", prev_cookies) - self.session.cookies.update(prev_cookies) - - else: - logger.debug("Logging in") - self.login() - - def login(self): - r = self.session.get("https://subscene.com/account/login") - if "Server Error" in r.text: - logger.error("Login unavailable; Maintenance?") - raise ServiceUnavailable("Login unavailable; Maintenance?") - - match = re.search(r"", r.text) - - if match: - h = html - data = json.loads(h.unescape(match.group(1))) - login_url = parse.urljoin(data["siteUrl"], data["loginUrl"]) - time.sleep(1.0) - - r = self.session.post(login_url, - { - "username": self.username, - "password": self.password, - data["antiForgery"]["name"]: data["antiForgery"]["value"] - }) - pep_content = re.search(r"
" - r".+name=\"id_token\".+?value=\"(?P.+?)\".*?" - r"access_token\".+?value=\"(?P.+?)\".+?" - r"token_type.+?value=\"(?P.+?)\".+?" - r"expires_in.+?value=\"(?P.+?)\".+?" - r"scope.+?value=\"(?P.+?)\".+?" - r"state.+?value=\"(?P.+?)\".+?" - r"session_state.+?value=\"(?P.+?)\"", - r.text, re.MULTILINE | re.DOTALL) - - if pep_content: - r = self.session.post(SITE_DOMAIN, pep_content.groupdict()) - try: - r.raise_for_status() - except Exception: - raise ProviderError("Something went wrong when trying to log in: %s", traceback.format_exc()) - else: - cj = self.session.cookies.copy() - store_cks = ("scene", "idsrv", "idsrv.xsrf", "idsvr.clients", "idsvr.session", "idsvr.username") - for cn in self.session.cookies.keys(): - if cn not in store_cks: - del cj[cn] - - logger.debug("Storing cookies: %r", cj) - region.set("subscene_cookies2", cj) - return - raise ProviderError("Something went wrong when trying to log in #1") - - def terminate(self): - logger.info("Closing session") - self.session.close() - - def _create_filters(self, languages): - self.filters = dict(HearingImpaired="2") - acc_filters = self.filters.copy() - if self.only_foreign: - self.filters["ForeignOnly"] = "True" - acc_filters["ForeignOnly"] = self.filters["ForeignOnly"].lower() - logger.info("Only searching for foreign/forced subtitles") - - selected_ids = [] - for l in languages: - lid = language_ids.get(l.basename, language_ids.get(l.alpha3, None)) - if lid: - selected_ids.append(str(lid)) - - acc_filters["SelectedIds"] = selected_ids - self.filters["LanguageFilter"] = ",".join(acc_filters["SelectedIds"]) - - last_filters = region.get("subscene_filters") - if last_filters != acc_filters: - region.set("subscene_filters", acc_filters) - logger.debug("Setting account filters to %r", acc_filters) - self.session.post("https://u.subscene.com/filter", acc_filters, allow_redirects=False) - - logger.debug("Filter created: '%s'" % self.filters) - - def _enable_filters(self): - self.session.cookies.update(self.filters) - logger.debug("Filters applied") - - def list_subtitles(self, video, languages): - if not video.original_name: - logger.info("Skipping search because we don't know the original release name") - return [] - - self._create_filters(languages) - self._enable_filters() - - if isinstance(video, Episode): - international_titles = list(set([video.series] + video.alternative_series[:1])) - subtitles = [s for s in self.query(video, international_titles) if s.language in languages] - if not len(subtitles): - us_titles = [x + ' (US)' for x in international_titles] - subtitles = [s for s in self.query(video, us_titles) if s.language in languages] - return subtitles - else: - titles = list(set([video.title] + video.alternative_titles[:1])) - return [s for s in self.query(video, titles) if s.language in languages] - - def download_subtitle(self, subtitle): - if subtitle.pack_data: - logger.info("Using previously downloaded pack data") - if rarfile.is_rarfile(io.BytesIO(subtitle.pack_data)): - logger.debug('Identified rar archive') - archive = rarfile.RarFile(io.BytesIO(subtitle.pack_data)) - elif zipfile.is_zipfile(io.BytesIO(subtitle.pack_data)): - logger.debug('Identified zip archive') - archive = zipfile.ZipFile(io.BytesIO(subtitle.pack_data)) - else: - logger.error('Unsupported compressed format') - return - subtitle.pack_data = None - - try: - subtitle.content = self.get_subtitle_from_archive(subtitle, archive) - return - except ProviderError: - pass - - # open the archive - r = self.session.get(subtitle.get_download_link(self.session), timeout=10) - r.raise_for_status() - archive_stream = io.BytesIO(r.content) - - if rarfile.is_rarfile(archive_stream): - logger.debug('Identified rar archive') - archive = rarfile.RarFile(archive_stream) - elif zipfile.is_zipfile(archive_stream): - logger.debug('Identified zip archive') - archive = zipfile.ZipFile(archive_stream) - else: - logger.error('Unsupported compressed format') - return - - subtitle.content = self.get_subtitle_from_archive(subtitle, archive) - - # store archive as pack_data for later caching - subtitle.pack_data = r.content - - def parse_results(self, video, film): - subtitles = [] - for s in film.subtitles: - try: - subtitle = SubsceneSubtitle.from_api(s) - except NotImplementedError as e: - logger.info(e) - continue - subtitle.asked_for_release_group = video.release_group - if isinstance(video, Episode): - subtitle.asked_for_episode = video.episode - - if self.only_foreign: - subtitle.language = Language.rebuild(subtitle.language, forced=True) - - # set subtitle language to hi if it's hearing_impaired - if subtitle.hearing_impaired: - subtitle.language = Language.rebuild(subtitle.language, hi=True) - - subtitles.append(subtitle) - logger.debug('Found subtitle %r', subtitle) - - return subtitles - - def do_search(self, *args, **kwargs): - try: - return search(*args, **kwargs) - except requests.HTTPError: - region.delete("subscene_cookies2") - raise - - @reinitialize_on_error((RequestException,), attempts=1) - def query(self, video, titles): - subtitles = [] - if isinstance(video, Episode): - more_than_one = len(titles) > 1 - for series in titles: - term = u"%s - %s Season" % (series, p.number_to_words("%sth" % video.season).capitalize()) - logger.debug('Searching with series and season: %s', term) - film = self.do_search(term, session=self.session, release=False, throttle=self.search_throttle, - limit_to=SearchTypes.TvSerie) - if not film and video.season == 1: - logger.debug('Searching with series name: %s', series) - film = self.do_search(series, session=self.session, release=False, throttle=self.search_throttle, - limit_to=SearchTypes.TvSerie) - - if film and film.subtitles: - logger.debug('Searching found: %s', len(film.subtitles)) - subtitles += self.parse_results(video, film) - else: - logger.debug('No results found') - - if more_than_one: - time.sleep(self.search_throttle) - else: - more_than_one = len(titles) > 1 - for title in titles: - logger.debug('Searching for movie results: %r', title) - film = self.do_search(title, year=video.year, session=self.session, limit_to=None, release=False, - throttle=self.search_throttle) - if film and film.subtitles: - subtitles += self.parse_results(video, film) - if more_than_one: - time.sleep(self.search_throttle) - - logger.info("%s subtitles found" % len(subtitles)) - return subtitles diff --git a/custom_libs/subliminal_patch/providers/subscene_cloudscraper.py b/custom_libs/subliminal_patch/providers/subscene_cloudscraper.py deleted file mode 100644 index f9eead046..000000000 --- a/custom_libs/subliminal_patch/providers/subscene_cloudscraper.py +++ /dev/null @@ -1,410 +0,0 @@ -# -*- coding: utf-8 -*- - -from difflib import SequenceMatcher -import functools -import logging -import re -import time -import urllib.parse - -from bs4 import BeautifulSoup as bso -import cloudscraper -from guessit import guessit -from requests import Session -from requests.exceptions import HTTPError -from subliminal.exceptions import ProviderError -from subliminal_patch.core import Episode -from subliminal_patch.core import Movie -from subliminal_patch.exceptions import APIThrottled -from subliminal_patch.providers import Provider -from subliminal_patch.providers.utils import get_archive_from_bytes -from subliminal_patch.providers.utils import get_subtitle_from_archive -from subliminal_patch.providers.utils import update_matches -from subliminal_patch.subtitle import Subtitle -from subzero.language import Language - -logger = logging.getLogger(__name__) - - -class SubsceneSubtitle(Subtitle): - provider_name = "subscene_cloudscraper" - hash_verifiable = False - - def __init__(self, language, page_link, release_info, episode_number=None): - super().__init__(language, page_link=page_link) - - self.release_info = release_info - self.episode_number = episode_number - self.episode_title = None - - self._matches = set( - ("title", "year") - if episode_number is None - else ("title", "series", "year", "season", "episode") - ) - - def get_matches(self, video): - update_matches(self._matches, video, self.release_info) - - return self._matches - - @property - def id(self): - return self.page_link - - -_BASE_URL = "https://subscene.com" - -# TODO: add more seasons and languages - -_SEASONS = ( - "First", - "Second", - "Third", - "Fourth", - "Fifth", - "Sixth", - "Seventh", - "Eighth", - "Ninth", - "Tenth", - "Eleventh", - "Twelfth", - "Thirdteenth", - "Fourthteenth", - "Fifteenth", - "Sixteenth", - "Seventeenth", - "Eightheenth", - "Nineteenth", - "Tweentieth", -) - -_LANGUAGE_MAP = { - "english": "eng", - "farsi_persian": "per", - "arabic": "ara", - "spanish": "spa", - "portuguese": "por", - "italian": "ita", - "dutch": "dut", - "hebrew": "heb", - "indonesian": "ind", - "danish": "dan", - "norwegian": "nor", - "bengali": "ben", - "bulgarian": "bul", - "croatian": "hrv", - "swedish": "swe", - "vietnamese": "vie", - "czech": "cze", - "finnish": "fin", - "french": "fre", - "german": "ger", - "greek": "gre", - "hungarian": "hun", - "icelandic": "ice", - "japanese": "jpn", - "macedonian": "mac", - "malay": "may", - "polish": "pol", - "romanian": "rum", - "russian": "rus", - "serbian": "srp", - "thai": "tha", - "turkish": "tur", -} - - -class SubsceneProvider(Provider): - provider_name = "subscene_cloudscraper" - - _movie_title_regex = re.compile(r"^(.+?)( \((\d{4})\))?$") - _tv_show_title_regex = re.compile( - r"^(.+?) [-\(]\s?(.*?) (season|series)\)?( \((\d{4})\))?$" - ) - _supported_languages = {} - _supported_languages["brazillian-portuguese"] = Language("por", "BR") - - for key, val in _LANGUAGE_MAP.items(): - _supported_languages[key] = Language.fromalpha3b(val) - - _supported_languages_reversed = { - val: key for key, val in _supported_languages.items() - } - - languages = set(_supported_languages.values()) - - video_types = (Episode, Movie) - subtitle_class = SubsceneSubtitle - - def initialize(self): - pass - - def terminate(self): - pass - - def _scraper_call(self, url, retry=7, method="GET", sleep=5, **kwargs): - last_exc = None - - for n in range(retry): - # Creating an instance for every try in order to avoid dropped connections. - - # This could probably be improved! - scraper = cloudscraper.create_scraper() - if method == "GET": - req = scraper.get(url, **kwargs) - elif method == "POST": - req = scraper.post(url, **kwargs) - else: - raise NotImplementedError(f"{method} not allowed") - - try: - req.raise_for_status() - except HTTPError as error: - logger.debug( - "'%s' returned. Trying again [%d] in %s", error, n + 1, sleep - ) - last_exc = error - time.sleep(sleep) - else: - return req - - raise ProviderError("403 Retry count exceeded") from last_exc - - def _gen_results(self, query): - url = ( - f"{_BASE_URL}/subtitles/searchbytitle?query={urllib.parse.quote(query)}&l=" - ) - - result = self._scraper_call(url, method="POST") - soup = bso(result.content, "html.parser") - - for title in soup.select("li div[class='title'] a"): - yield title - - def _search_movie(self, title, year): - title = title.lower() - year = str(year) - - found_movie = None - - results = [] - for result in self._gen_results(title): - text = result.text.lower() - match = self._movie_title_regex.match(text) - if not match: - continue - match_title = match.group(1) - match_year = match.group(3) - if year == match_year: - results.append( - { - "href": result.get("href"), - "similarity": SequenceMatcher(None, title, match_title).ratio(), - } - ) - - if results: - results.sort(key=lambda x: x["similarity"], reverse=True) - found_movie = results[0]["href"] - logger.debug("Movie found: %s", results[0]) - return found_movie - - def _search_tv_show_season(self, title, season, year=None): - try: - season_str = _SEASONS[season - 1].lower() - except IndexError: - logger.debug("Season number not supported: %s", season) - return None - - found_tv_show_season = None - - results = [] - for result in self._gen_results(title): - text = result.text.lower() - - match = self._tv_show_title_regex.match(text) - if not match: - logger.debug("Series title not matched: %s", text) - continue - else: - logger.debug("Series title matched: %s", text) - - match_title = match.group(1) - match_season = match.group(2) - - # Match "complete series" titles as they usually contain season packs - if season_str == match_season or "complete" in match_season: - plus = 0.1 if year and str(year) in text else 0 - results.append( - { - "href": result.get("href"), - "similarity": SequenceMatcher(None, title, match_title).ratio() - + plus, - } - ) - - if results: - results.sort(key=lambda x: x["similarity"], reverse=True) - found_tv_show_season = results[0]["href"] - logger.debug("TV Show season found: %s", results[0]) - - return found_tv_show_season - - def _find_movie_subtitles(self, path, language): - soup = self._get_subtitle_page_soup(path, language) - - subtitles = [] - for item in soup.select("tr"): - subtitle = _get_subtitle_from_item(item, language) - if subtitle is None: - continue - - logger.debug("Found subtitle: %s", subtitle) - subtitles.append(subtitle) - - return subtitles - - def _find_episode_subtitles( - self, path, season, episode, language, episode_title=None - ): - soup = self._get_subtitle_page_soup(path, language) - - subtitles = [] - - for item in soup.select("tr"): - valid_item = None - clean_text = " ".join(item.text.split()) - - if not clean_text: - continue - - # It will return list values - guess = _memoized_episode_guess(clean_text) - - if "season" not in guess: - if "complete series" in clean_text.lower(): - logger.debug("Complete series pack found: %s", clean_text) - guess["season"] = [season] - else: - logger.debug("Nothing guessed from release: %s", clean_text) - continue - - if season in guess["season"] and episode in guess.get("episode", []): - logger.debug("Episode match found: %s - %s", guess, clean_text) - valid_item = item - - elif season in guess["season"] and not "episode" in guess: - logger.debug("Season pack found: %s", clean_text) - valid_item = item - - if valid_item is None: - continue - - subtitle = _get_subtitle_from_item(item, language, episode) - - if subtitle is None: - continue - - subtitle.episode_title = episode_title - - logger.debug("Found subtitle: %s", subtitle) - subtitles.append(subtitle) - - return subtitles - - def _get_subtitle_page_soup(self, path, language): - language_path = self._supported_languages_reversed[language] - result = self._scraper_call(f"{_BASE_URL}{path}/{language_path}") - return bso(result.content, "html.parser") - - def list_subtitles(self, video, languages): - is_episode = isinstance(video, Episode) - - if is_episode: - result = self._search_tv_show_season(video.series, video.season, video.year) - else: - result = self._search_movie(video.title, video.year) - - if result is None: - logger.debug("No results") - return [] - - subtitles = [] - - for language in languages: - if is_episode: - subtitles.extend( - self._find_episode_subtitles( - result, video.season, video.episode, language, video.title - ) - ) - else: - subtitles.extend(self._find_movie_subtitles(result, language)) - - return subtitles - - def download_subtitle(self, subtitle): - # TODO: add MustGetBlacklisted support - - result = self._scraper_call(subtitle.page_link) - soup = bso(result.content, "html.parser") - try: - download_url = _BASE_URL + str( - soup.select_one("a[id='downloadButton']")["href"] # type: ignore - ) - except (AttributeError, KeyError, TypeError): - raise APIThrottled(f"Couldn't get download url from {subtitle.page_link}") - - downloaded = self._scraper_call(download_url) - archive = get_archive_from_bytes(downloaded.content) - - if archive is None: - raise APIThrottled(f"Invalid archive: {subtitle.page_link}") - - subtitle.content = get_subtitle_from_archive( - archive, - episode=subtitle.episode_number, - episode_title=subtitle.episode_title, - ) - - -@functools.lru_cache(2048) -def _memoized_episode_guess(content): - # Use include to save time from unnecessary checks - return guessit( - content, - { - "type": "episode", - # Add codec keys to avoid matching x264, 5.1, etc as episode info - "includes": ["season", "episode", "video_codec", "audio_codec"], - "enforce_list": True, - }, - ) - - -def _get_subtitle_from_item(item, language, episode_number=None): - release_infos = [] - - try: - release_infos.append(item.find("td", {"class": "a6"}).text.strip()) - except (AttributeError, KeyError): - pass - - try: - release_infos.append( - item.find("td", {"class": "a1"}).find_all("span")[-1].text.strip() - ) - except (AttributeError, KeyError): - pass - - release_info = "".join(r_info for r_info in release_infos if r_info) - - try: - path = item.find("td", {"class": "a1"}).find("a")["href"] - except (AttributeError, KeyError): - logger.debug("Couldn't get path: %s", item) - return None - - return SubsceneSubtitle(language, _BASE_URL + path, release_info, episode_number) diff --git a/custom_libs/subliminal_patch/providers/subssabbz.py b/custom_libs/subliminal_patch/providers/subssabbz.py index 18c9ffbef..f0386e889 100644 --- a/custom_libs/subliminal_patch/providers/subssabbz.py +++ b/custom_libs/subliminal_patch/providers/subssabbz.py @@ -110,7 +110,7 @@ class SubsSabBzSubtitle(Subtitle): guess_filename = guessit(self.filename, video.hints) matches |= guess_matches(video, guess_filename) - if isinstance(video, Movie) and (self.num_cds > 1 or 'cd' in guess_filename): + if isinstance(video, Movie) and ((isinstance(self.num_cds, int) and self.num_cds > 1) or 'cd' in guess_filename): # reduce score of subtitles for multi-disc movie releases return set() diff --git a/custom_libs/subliminal_patch/providers/subsunacs.py b/custom_libs/subliminal_patch/providers/subsunacs.py index df969bcc8..366a42546 100644 --- a/custom_libs/subliminal_patch/providers/subsunacs.py +++ b/custom_libs/subliminal_patch/providers/subsunacs.py @@ -108,7 +108,7 @@ class SubsUnacsSubtitle(Subtitle): guess_filename = guessit(self.filename, video.hints) matches |= guess_matches(video, guess_filename) - if isinstance(video, Movie) and (self.num_cds > 1 or 'cd' in guess_filename): + if isinstance(video, Movie) and ((isinstance(self.num_cds, int) and self.num_cds > 1) or 'cd' in guess_filename): # reduce score of subtitles for multi-disc movie releases return set() diff --git a/custom_libs/subliminal_patch/providers/whisperai.py b/custom_libs/subliminal_patch/providers/whisperai.py index dfd733da3..d427f8ad2 100644 --- a/custom_libs/subliminal_patch/providers/whisperai.py +++ b/custom_libs/subliminal_patch/providers/whisperai.py @@ -169,7 +169,7 @@ def whisper_get_language_reverse(alpha3): lan = whisper_get_language(wl, whisper_languages[wl]) if lan.alpha3 == alpha3: return wl - raise ValueError + return None def language_from_alpha3(lang): name = Language(lang).name @@ -317,7 +317,7 @@ class WhisperAIProvider(Provider): if out == None: logger.info(f"Whisper cannot process {subtitle.video.original_path} because of missing/bad audio track") subtitle.content = None - return + return logger.debug(f'Audio stream length (in WAV format) is {len(out):,} bytes') @@ -326,11 +326,23 @@ class WhisperAIProvider(Provider): else: output_language = "eng" + input_language = whisper_get_language_reverse(subtitle.audio_language) + if input_language is None: + if output_language == "eng": + # guess that audio track is mislabelled English and let whisper try to transcribe it + input_language = "en" + subtitle.task = "transcribe" + logger.info(f"Whisper treating unsupported audio track language: '{subtitle.audio_language}' as English") + else: + logger.info(f"Whisper cannot process {subtitle.video.original_path} because of unsupported audio track language: '{subtitle.audio_language}'") + subtitle.content = None + return + logger.info(f'Starting WhisperAI {subtitle.task} to {language_from_alpha3(output_language)} for {subtitle.video.original_path}') startTime = time.time() r = self.session.post(f"{self.endpoint}/asr", - params={'task': subtitle.task, 'language': whisper_get_language_reverse(subtitle.audio_language), 'output': 'srt', 'encode': 'false'}, + params={'task': subtitle.task, 'language': input_language, 'output': 'srt', 'encode': 'false'}, files={'audio_file': out}, timeout=(self.response, self.timeout)) diff --git a/custom_libs/subliminal_patch/video.py b/custom_libs/subliminal_patch/video.py index 0d613bb94..f5df0c92e 100644 --- a/custom_libs/subliminal_patch/video.py +++ b/custom_libs/subliminal_patch/video.py @@ -33,6 +33,8 @@ class Video(Video_): edition=None, other=None, info_url=None, + series_anidb_id=None, + series_anidb_episode_id=None, **kwargs ): super(Video, self).__init__( @@ -57,3 +59,5 @@ class Video(Video_): self.original_path = name self.other = other self.info_url = info_url + self.series_anidb_series_id = series_anidb_id, + self.series_anidb_episode_id = series_anidb_episode_id, diff --git a/custom_libs/subscene_api/subscene.py b/custom_libs/subscene_api/subscene.py deleted file mode 100644 index e91f6fdef..000000000 --- a/custom_libs/subscene_api/subscene.py +++ /dev/null @@ -1,299 +0,0 @@ -# -*- coding: utf-8 -*- -# vim: fenc=utf-8 ts=4 et sw=4 sts=4 - -# This file is part of Subscene-API. -# -# Subscene-API is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Subscene-API is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -""" -Python wrapper for Subscene subtitle database. - -since Subscene doesn't provide an official API, I wrote -this script that does the job by parsing the website"s pages. -""" - -# imports -import re -import enum -import sys -import requests -import time -import logging - -is_PY2 = sys.version_info[0] < 3 -if is_PY2: - from contextlib2 import suppress - from urllib2 import Request, urlopen -else: - from contextlib import suppress - from urllib.request import Request, urlopen - -from dogpile.cache.api import NO_VALUE -from subliminal.cache import region -from bs4 import BeautifulSoup, NavigableString - - -logger = logging.getLogger(__name__) - -# constants -HEADERS = { -} -SITE_DOMAIN = "https://subscene.com" - -DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWeb"\ - "Kit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47 Safari/537.36" - - -ENDPOINT_RE = re.compile(r'(?uis).*?=6.9.0" } }, - "node_modules/@babel/code-frame/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/code-frame/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/compat-data": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", - "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", + "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz", - "integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.4.tgz", + "integrity": "sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.4", "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.24.0", - "@babel/parser": "^7.24.0", + "@babel/helpers": "^7.24.4", + "@babel/parser": "^7.24.4", "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.0", + "@babel/traverse": "^7.24.1", "@babel/types": "^7.24.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -213,9 +148,9 @@ "dev": true }, "node_modules/@babel/eslint-parser": { - "version": "7.23.10", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.23.10.tgz", - "integrity": "sha512-3wSYDPZVnhseRnxRJH6ZVTNknBz76AEnyC+AYYhasjP3Yy23qz0ERR7Fcd2SHmYuSFJ2kY9gaaDd3vyqU09eSw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.24.1.tgz", + "integrity": "sha512-d5guuzMlPeDfZIbpQ8+g1NaCNuAGBBGNECh0HVqz1sjOeVLh2CEaifuOysCH18URW6R7pqXINvf5PaR/dC6jLQ==", "dev": true, "dependencies": { "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", @@ -240,14 +175,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.4.tgz", + "integrity": "sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==", "dev": true, "dependencies": { - "@babel/types": "^7.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.24.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { @@ -295,9 +230,9 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.0.tgz", - "integrity": "sha512-QAH+vfvts51BCsNZ2PhY6HAggnlS6omLLFTsIpeqZk/MmJ6cW7tgz5yRv0fMJThcr6FmbMrENh1RgrWPTYA76g==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.4.tgz", + "integrity": "sha512-lG75yeuUSVu0pIcbhiYMXBXANHrpUPaOfu7ryAzskCgKUHuAxRQI5ssrtmF0X9UXldPlvT0XM/A4F44OXRt6iQ==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", @@ -305,7 +240,7 @@ "@babel/helper-function-name": "^7.23.0", "@babel/helper-member-expression-to-functions": "^7.23.0", "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-replace-supers": "^7.24.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", "semver": "^6.3.1" @@ -397,11 +332,11 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", + "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", "dependencies": { - "@babel/types": "^7.22.15" + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -465,13 +400,13 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", - "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz", + "integrity": "sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-member-expression-to-functions": "^7.23.0", "@babel/helper-optimise-call-expression": "^7.22.5" }, "engines": { @@ -518,9 +453,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", "engines": { "node": ">=6.9.0" } @@ -557,13 +492,13 @@ } }, "node_modules/@babel/helpers": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.0.tgz", - "integrity": "sha512-ulDZdc0Aj5uLc5nETsa7EPx2L7rM0YJM8r7ck7U73AXi7qOV44IHHRAYZHY6iU1rr3C5N4NtTmMRUJP6kwCWeA==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.4.tgz", + "integrity": "sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==", "dev": true, "dependencies": { "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.0", + "@babel/traverse": "^7.24.1", "@babel/types": "^7.24.0" }, "engines": { @@ -571,13 +506,14 @@ } }, "node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", + "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -648,9 +584,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz", - "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz", + "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -659,13 +595,29 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.4.tgz", + "integrity": "sha512-qpl6vOOEEzTLLcsuqYYo8yDtrTocmu2xkGvgNebvPjT9DTtfFYGmgDqY+rBYXNlqL4s9qLDn6xkrJv4RxAPiTA==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz", - "integrity": "sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz", + "integrity": "sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -675,14 +627,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz", - "integrity": "sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.1.tgz", + "integrity": "sha512-Hj791Ii4ci8HqnaKHAlLNs+zaLXb0EzSDhiAWp5VNlyvCNymYfacs64pxTxbH1znW/NcArSmwpmG9IKE/TUVVQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.23.3" + "@babel/plugin-transform-optional-chaining": "^7.24.1" }, "engines": { "node": ">=6.9.0" @@ -692,13 +644,13 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.7.tgz", - "integrity": "sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.1.tgz", + "integrity": "sha512-m9m/fXsXLiHfwdgydIFnpk+7jlVbnvlK5B2EKiPdLUb6WX654ZaaEWJUjk8TftRbZpK0XibovlLWX4KIZhV6jw==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -725,14 +677,14 @@ } }, "node_modules/@babel/plugin-proposal-decorators": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.24.0.tgz", - "integrity": "sha512-LiT1RqZWeij7X+wGxCoYh3/3b8nVOX6/7BZ9wiQgAIyjoeQWdROaodJCgT+dwtbjHaz0r7bEbHJzjSbVfcOyjQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.24.1.tgz", + "integrity": "sha512-zPEvzFijn+hRvJuX2Vu3KbEBN39LN3f7tW3MQO2LsIs57B26KU+kUc82BdAktS1VCM6libzh45eKGI65lg0cpA==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.0", + "@babel/helper-create-class-features-plugin": "^7.24.1", "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-decorators": "^7.24.0" + "@babel/plugin-syntax-decorators": "^7.24.1" }, "engines": { "node": ">=6.9.0" @@ -862,9 +814,9 @@ } }, "node_modules/@babel/plugin-syntax-decorators": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.24.0.tgz", - "integrity": "sha512-MXW3pQCu9gUiVGzqkGqsgiINDVYXoAnrY8FYF/rmb+OfufNF0zHMpHPN4ulRrinxYT8Vk/aZJxYqOKsDECjKAw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.24.1.tgz", + "integrity": "sha512-05RJdO/cCrtVWuAaSn1tS3bH8jbsJa/Y1uD186u6J4C/1mnHFxseeuWpsqr9anvo7TUulev7tm7GDwRV+VuhDw==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.0" @@ -901,12 +853,12 @@ } }, "node_modules/@babel/plugin-syntax-flow": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.23.3.tgz", - "integrity": "sha512-YZiAIpkJAwQXBJLIQbRFayR5c+gJ35Vcz3bg954k7cd73zqjvhacJuL9RbrzPz8qPmZdgqP6EUKwy0PCNhaaPA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.24.1.tgz", + "integrity": "sha512-sxi2kLTI5DeW5vDtMUsk4mTPwvlUDbjOnoWayhynCwrw4QXRld4QEYwqzY8JmQXaJUtgUuCIurtSRH5sn4c7mA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -916,12 +868,12 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz", - "integrity": "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz", + "integrity": "sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -931,12 +883,12 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz", - "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.1.tgz", + "integrity": "sha512-zhQTMH0X2nVLnb04tz+s7AMuasX8U0FnpE+nHTOhSOINjWMnopoZTxtIKsd45n4GQ/HIZLyfIpoul8e2m0DnRA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -970,12 +922,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", - "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz", + "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1087,12 +1039,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", - "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz", + "integrity": "sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1118,12 +1070,12 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz", - "integrity": "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.1.tgz", + "integrity": "sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1133,13 +1085,13 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.9.tgz", - "integrity": "sha512-8Q3veQEDGe14dTYuwagbRtwxQDnytyg1JFu4/HwEMETeofocrB0U0ejBJIXoeG/t2oXZ8kzCyI0ZZfbT80VFNQ==", + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.3.tgz", + "integrity": "sha512-Qe26CMYVjpQxJ8zxM1340JFNjZaF+ISWpr1Kt/jGo+ZTUzKkfw/pphEWbRCb+lmSM6k/TOgfYLvmbHkUQ0asIg==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-remap-async-to-generator": "^7.22.20", "@babel/plugin-syntax-async-generators": "^7.8.4" }, @@ -1151,13 +1103,13 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", - "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.1.tgz", + "integrity": "sha512-AawPptitRXp1y0n4ilKcGbRYWfbbzFWz2NqNu7dacYDtFtz0CMjG64b3LQsb3KIgnf4/obcUL78hfaOS7iCUfw==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-module-imports": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-remap-async-to-generator": "^7.22.20" }, "engines": { @@ -1168,12 +1120,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz", - "integrity": "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.1.tgz", + "integrity": "sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1183,12 +1135,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz", - "integrity": "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.4.tgz", + "integrity": "sha512-nIFUZIpGKDf9O9ttyRXpHFpKC+X3Y5mtshZONuEUYBomAKoM4y029Jr+uB1bHGPhNmK8YXHevDtKDOLmtRrp6g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1198,13 +1150,13 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz", - "integrity": "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.1.tgz", + "integrity": "sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-class-features-plugin": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1214,13 +1166,13 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz", - "integrity": "sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.4.tgz", + "integrity": "sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.24.4", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-class-static-block": "^7.14.5" }, "engines": { @@ -1231,17 +1183,17 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.23.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.8.tgz", - "integrity": "sha512-yAYslGsY1bX6Knmg46RjiCiNSwJKv2IUC8qOdYKqMMr0491SXFhcHqOdRDeCRohOOIzwN/90C6mQ9qAKgrP7dg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.1.tgz", + "integrity": "sha512-ZTIe3W7UejJd3/3R4p7ScyyOoafetUShSf4kCqV0O7F/RiHxVj/wRaRnQlrGwflvcehNA8M42HkAiEDYZu2F1Q==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-replace-supers": "^7.24.1", "@babel/helper-split-export-declaration": "^7.22.6", "globals": "^11.1.0" }, @@ -1253,13 +1205,13 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz", - "integrity": "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz", + "integrity": "sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/template": "^7.22.15" + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/template": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1269,12 +1221,12 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz", - "integrity": "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.1.tgz", + "integrity": "sha512-ow8jciWqNxR3RYbSNVuF4U2Jx130nwnBnhRw6N6h1bOejNkABmcI5X5oz29K4alWX7vf1C+o6gtKXikzRKkVdw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1284,13 +1236,13 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz", - "integrity": "sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.1.tgz", + "integrity": "sha512-p7uUxgSoZwZ2lPNMzUkqCts3xlp8n+o05ikjy7gbtFJSt9gdU88jAmtfmOxHM14noQXBxfgzf2yRWECiNVhTCw==", "dev": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1300,12 +1252,12 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz", - "integrity": "sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.1.tgz", + "integrity": "sha512-msyzuUnvsjsaSaocV6L7ErfNsa5nDWL1XKNnDePLgmz+WdU4w/J8+AxBMrWfi9m4IxfL5sZQKUPQKDQeeAT6lA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1315,12 +1267,12 @@ } }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz", - "integrity": "sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.1.tgz", + "integrity": "sha512-av2gdSTyXcJVdI+8aFZsCAtR29xJt0S5tas+Ef8NvBNmD1a+N/3ecMLeMBgfcK+xzsjdLDT6oHt+DFPyeqUbDA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-dynamic-import": "^7.8.3" }, "engines": { @@ -1331,13 +1283,13 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz", - "integrity": "sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.1.tgz", + "integrity": "sha512-U1yX13dVBSwS23DEAqU+Z/PkwE9/m7QQy8Y9/+Tdb8UWYaGNDYwTLi19wqIAiROr8sXVum9A/rtiH5H0boUcTw==", "dev": true, "dependencies": { "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1347,12 +1299,12 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz", - "integrity": "sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.1.tgz", + "integrity": "sha512-Ft38m/KFOyzKw2UaJFkWG9QnHPG/Q/2SkOrRk4pNBPg5IPZ+dOxcmkK5IyuBcxiNPyyYowPGUReyBvrvZs7IlQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" }, "engines": { @@ -1363,13 +1315,13 @@ } }, "node_modules/@babel/plugin-transform-flow-strip-types": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.23.3.tgz", - "integrity": "sha512-26/pQTf9nQSNVJCrLB1IkHUKyPxR+lMrH2QDPG89+Znu9rAMbtrybdbWeE9bb7gzjmE5iXHEY+e0HUwM6Co93Q==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.24.1.tgz", + "integrity": "sha512-iIYPIWt3dUmUKKE10s3W+jsQ3icFkw0JyRVyY1B7G4yK/nngAOHLVx8xlhA6b/Jzl/Y0nis8gjqhqKtRDQqHWQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-flow": "^7.23.3" + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-flow": "^7.24.1" }, "engines": { "node": ">=6.9.0" @@ -1379,12 +1331,12 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz", - "integrity": "sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.1.tgz", + "integrity": "sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" }, "engines": { @@ -1395,14 +1347,14 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz", - "integrity": "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.1.tgz", + "integrity": "sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA==", "dev": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1412,12 +1364,12 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz", - "integrity": "sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.1.tgz", + "integrity": "sha512-U7RMFmRvoasscrIFy5xA4gIp8iWnWubnKkKuUGJjsuOH7GfbMkB+XZzeslx2kLdEGdOJDamEmCqOks6e8nv8DQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-json-strings": "^7.8.3" }, "engines": { @@ -1428,12 +1380,12 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz", - "integrity": "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.1.tgz", + "integrity": "sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1443,12 +1395,12 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz", - "integrity": "sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.1.tgz", + "integrity": "sha512-OhN6J4Bpz+hIBqItTeWJujDOfNP+unqv/NJgyhlpSqgBTPm37KkMmZV6SYcOj+pnDbdcl1qRGV/ZiIjX9Iy34w==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" }, "engines": { @@ -1459,12 +1411,12 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz", - "integrity": "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.1.tgz", + "integrity": "sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1474,13 +1426,13 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz", - "integrity": "sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.1.tgz", + "integrity": "sha512-lAxNHi4HVtjnHd5Rxg3D5t99Xm6H7b04hUS7EHIXcUl2EV4yl1gWdqZrNzXnSrHveL9qMdbODlLF55mvgjAfaQ==", "dev": true, "dependencies": { "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1490,13 +1442,13 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz", - "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz", + "integrity": "sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==", "dev": true, "dependencies": { "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-simple-access": "^7.22.5" }, "engines": { @@ -1507,14 +1459,14 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.9.tgz", - "integrity": "sha512-KDlPRM6sLo4o1FkiSlXoAa8edLXFsKKIda779fbLrvmeuc3itnjCtaO6RrtoaANsIJANj+Vk1zqbZIMhkCAHVw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.1.tgz", + "integrity": "sha512-mqQ3Zh9vFO1Tpmlt8QPnbwGHzNz3lpNEMxQb1kAemn/erstyqw1r9KeOlOfo3y6xAnFEcOv2tSyrXfmMk+/YZA==", "dev": true, "dependencies": { "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-validator-identifier": "^7.22.20" }, "engines": { @@ -1525,13 +1477,13 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz", - "integrity": "sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.1.tgz", + "integrity": "sha512-tuA3lpPj+5ITfcCluy6nWonSL7RvaG0AOTeAuvXqEKS34lnLzXpDb0dcP6K8jD0zWZFNDVly90AGFJPnm4fOYg==", "dev": true, "dependencies": { "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1557,12 +1509,12 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz", - "integrity": "sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.1.tgz", + "integrity": "sha512-/rurytBM34hYy0HKZQyA0nHbQgQNFm4Q/BOc9Hflxi2X3twRof7NaE5W46j4kQitm7SvACVRXsa6N/tSZxvPug==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1572,12 +1524,12 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz", - "integrity": "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.1.tgz", + "integrity": "sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" }, "engines": { @@ -1588,12 +1540,12 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz", - "integrity": "sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.1.tgz", + "integrity": "sha512-7GAsGlK4cNL2OExJH1DzmDeKnRv/LXq0eLUSvudrehVA5Rgg4bIrqEUW29FbKMBRT0ztSqisv7kjP+XIC4ZMNw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-numeric-separator": "^7.10.4" }, "engines": { @@ -1604,16 +1556,15 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.0.tgz", - "integrity": "sha512-y/yKMm7buHpFFXfxVFS4Vk1ToRJDilIa6fKRioB9Vjichv58TDGXTvqV0dN7plobAmTW5eSEGXDngE+Mm+uO+w==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.1.tgz", + "integrity": "sha512-XjD5f0YqOtebto4HGISLNfiNMTTs6tbkFf2TOqJlYKYmbo+mN9Dnpl4SRoofiziuOWMIyq3sZEUqLo3hLITFEA==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.23.5", "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.23.3" + "@babel/plugin-transform-parameters": "^7.24.1" }, "engines": { "node": ">=6.9.0" @@ -1623,13 +1574,13 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz", - "integrity": "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.1.tgz", + "integrity": "sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20" + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-replace-supers": "^7.24.1" }, "engines": { "node": ">=6.9.0" @@ -1639,12 +1590,12 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz", - "integrity": "sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.1.tgz", + "integrity": "sha512-oBTH7oURV4Y+3EUrf6cWn1OHio3qG/PVwO5J03iSJmBg6m2EhKjkAu/xuaXaYwWW9miYtvbWv4LNf0AmR43LUA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" }, "engines": { @@ -1655,12 +1606,12 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz", - "integrity": "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.1.tgz", + "integrity": "sha512-n03wmDt+987qXwAgcBlnUUivrZBPZ8z1plL0YvgQalLm+ZE5BMhGm94jhxXtA1wzv1Cu2aaOv1BM9vbVttrzSg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", "@babel/plugin-syntax-optional-chaining": "^7.8.3" }, @@ -1672,12 +1623,12 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz", - "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.1.tgz", + "integrity": "sha512-8Jl6V24g+Uw5OGPeWNKrKqXPDw2YDjLc53ojwfMcKwlEoETKU9rU0mHUtcg9JntWI/QYzGAXNWEcVHZ+fR+XXg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1687,13 +1638,13 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz", - "integrity": "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.1.tgz", + "integrity": "sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-class-features-plugin": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1703,14 +1654,14 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz", - "integrity": "sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.1.tgz", + "integrity": "sha512-pTHxDVa0BpUbvAgX3Gat+7cSciXqUcY9j2VZKTbSB6+VQGpNgNO9ailxTGHSXlqOnX1Hcx1Enme2+yv7VqP9bg==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" }, "engines": { @@ -1721,12 +1672,12 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz", - "integrity": "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.1.tgz", + "integrity": "sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1736,12 +1687,12 @@ } }, "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.23.3.tgz", - "integrity": "sha512-GnvhtVfA2OAtzdX58FJxU19rhoGeQzyVndw3GgtdECQvQFXPEZIOVULHVZGAYmOgmqjXpVpfocAbSjh99V/Fqw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.1.tgz", + "integrity": "sha512-mvoQg2f9p2qlpDQRBC7M3c3XTr0k7cp/0+kFKKO/7Gtu0LSw16eKB+Fabe2bDT/UpsyasTBBkAnbdsLrkD5XMw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1785,12 +1736,12 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz", - "integrity": "sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.1.tgz", + "integrity": "sha512-kDJgnPujTmAZ/9q2CN4m2/lRsUUPDvsG3+tSHWUJIzMGTt5U/b/fwWd3RO3n+5mjLrsBrVa5eKFRVSQbi3dF1w==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1800,12 +1751,12 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.23.3.tgz", - "integrity": "sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.1.tgz", + "integrity": "sha512-1v202n7aUq4uXAieRTKcwPzNyphlCuqHHDcdSNc+vdhoTEZcFMh+L5yZuCmGaIO7bs1nJUNfHB89TZyoL48xNA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1815,13 +1766,13 @@ } }, "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.23.3.tgz", - "integrity": "sha512-qMFdSS+TUhB7Q/3HVPnEdYJDQIk57jkntAwSuz9xfSE4n+3I+vHYCli3HoHawN1Z3RfCz/y1zXA/JXjG6cVImQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.24.1.tgz", + "integrity": "sha512-+pWEAaDJvSm9aFvJNpLiM2+ktl2Sn2U5DdyiWdZBxmLc6+xGt88dvFqsHiAiDS+8WqUwbDfkKz9jRxK3M0k+kA==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1831,12 +1782,12 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz", - "integrity": "sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.1.tgz", + "integrity": "sha512-sJwZBCzIBE4t+5Q4IGLaaun5ExVMRY0lYwos/jNecjMrVCygCdph3IKv0tkP5Fc87e/1+bebAmEAGBfnRD+cnw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "regenerator-transform": "^0.15.2" }, "engines": { @@ -1847,12 +1798,12 @@ } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz", - "integrity": "sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.1.tgz", + "integrity": "sha512-JAclqStUfIwKN15HrsQADFgeZt+wexNQ0uLhuqvqAUFoqPMjEcFCYZBhq0LUdz6dZK/mD+rErhW71fbx8RYElg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1862,16 +1813,16 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.0.tgz", - "integrity": "sha512-zc0GA5IitLKJrSfXlXmp8KDqLrnGECK7YRfQBmEKg1NmBOQ7e+KuclBEKJgzifQeUYLdNiAw4B4bjyvzWVLiSA==", + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.3.tgz", + "integrity": "sha512-J0BuRPNlNqlMTRJ72eVptpt9VcInbxO6iP3jaxr+1NPhC0UkKL+6oeX6VXMEYdADnuqmMmsBspt4d5w8Y/TCbQ==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-module-imports": "^7.24.3", "@babel/helper-plugin-utils": "^7.24.0", - "babel-plugin-polyfill-corejs2": "^0.4.8", - "babel-plugin-polyfill-corejs3": "^0.9.0", - "babel-plugin-polyfill-regenerator": "^0.5.5", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.1", + "babel-plugin-polyfill-regenerator": "^0.6.1", "semver": "^6.3.1" }, "engines": { @@ -1882,12 +1833,12 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz", - "integrity": "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz", + "integrity": "sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1897,12 +1848,12 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz", - "integrity": "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.1.tgz", + "integrity": "sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" }, "engines": { @@ -1913,12 +1864,12 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz", - "integrity": "sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.1.tgz", + "integrity": "sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1928,12 +1879,12 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz", - "integrity": "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.1.tgz", + "integrity": "sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1943,12 +1894,12 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz", - "integrity": "sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.1.tgz", + "integrity": "sha512-CBfU4l/A+KruSUoW+vTQthwcAdwuqbpRNB8HQKlZABwHRhsdHZ9fezp4Sn18PeAlYxTNiLMlx4xUBV3AWfg1BA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1958,15 +1909,15 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.6.tgz", - "integrity": "sha512-6cBG5mBvUu4VUD04OHKnYzbuHNP8huDsD3EDqqpIpsswTDoqHCjLoHb6+QgsV1WsT2nipRqCPgxD3LXnEO7XfA==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.4.tgz", + "integrity": "sha512-79t3CQ8+oBGk/80SQ8MN3Bs3obf83zJ0YZjDmDaEZN8MqhMI760apl5z6a20kFeMXBwJX99VpKT8CKxEBp5H1g==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.23.6", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-typescript": "^7.23.3" + "@babel/helper-create-class-features-plugin": "^7.24.4", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-typescript": "^7.24.1" }, "engines": { "node": ">=6.9.0" @@ -1976,12 +1927,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz", - "integrity": "sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.1.tgz", + "integrity": "sha512-RlkVIcWT4TLI96zM660S877E7beKlQw7Ig+wqkKBiWfj0zH5Q4h50q6er4wzZKRNSYpfo6ILJ+hrJAGSX2qcNw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1991,13 +1942,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz", - "integrity": "sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.1.tgz", + "integrity": "sha512-Ss4VvlfYV5huWApFsF8/Sq0oXnGO+jB+rijFEFugTd3cwSObUSnUi88djgR5528Csl0uKlrI331kRqe56Ov2Ng==", "dev": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -2007,13 +1958,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz", - "integrity": "sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.1.tgz", + "integrity": "sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g==", "dev": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -2023,13 +1974,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz", - "integrity": "sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.1.tgz", + "integrity": "sha512-fqj4WuzzS+ukpgerpAoOnMfQXwUHFxXUZUE84oL2Kao2N8uSlvcpnAidKASgsNgzZHBsHWvcm8s9FPWUhAb8fA==", "dev": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -2039,26 +1990,27 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.0.tgz", - "integrity": "sha512-ZxPEzV9IgvGn73iK0E6VB9/95Nd7aMFpbE0l8KQFDG70cOV9IxRP7Y2FUPmlK0v6ImlLqYX50iuZ3ZTVhOF2lA==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.4.tgz", + "integrity": "sha512-7Kl6cSmYkak0FK/FXjSEnLJ1N9T/WA2RkMhu17gZ/dsxKJUuTYNIylahPTzqpLyJN4WhDif8X0XK1R8Wsguo/A==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.23.5", + "@babel/compat-data": "^7.24.4", "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-validator-option": "^7.23.5", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.7", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.4", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.1", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.23.3", - "@babel/plugin-syntax-import-attributes": "^7.23.3", + "@babel/plugin-syntax-import-assertions": "^7.24.1", + "@babel/plugin-syntax-import-attributes": "^7.24.1", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", @@ -2070,58 +2022,58 @@ "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.23.3", - "@babel/plugin-transform-async-generator-functions": "^7.23.9", - "@babel/plugin-transform-async-to-generator": "^7.23.3", - "@babel/plugin-transform-block-scoped-functions": "^7.23.3", - "@babel/plugin-transform-block-scoping": "^7.23.4", - "@babel/plugin-transform-class-properties": "^7.23.3", - "@babel/plugin-transform-class-static-block": "^7.23.4", - "@babel/plugin-transform-classes": "^7.23.8", - "@babel/plugin-transform-computed-properties": "^7.23.3", - "@babel/plugin-transform-destructuring": "^7.23.3", - "@babel/plugin-transform-dotall-regex": "^7.23.3", - "@babel/plugin-transform-duplicate-keys": "^7.23.3", - "@babel/plugin-transform-dynamic-import": "^7.23.4", - "@babel/plugin-transform-exponentiation-operator": "^7.23.3", - "@babel/plugin-transform-export-namespace-from": "^7.23.4", - "@babel/plugin-transform-for-of": "^7.23.6", - "@babel/plugin-transform-function-name": "^7.23.3", - "@babel/plugin-transform-json-strings": "^7.23.4", - "@babel/plugin-transform-literals": "^7.23.3", - "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", - "@babel/plugin-transform-member-expression-literals": "^7.23.3", - "@babel/plugin-transform-modules-amd": "^7.23.3", - "@babel/plugin-transform-modules-commonjs": "^7.23.3", - "@babel/plugin-transform-modules-systemjs": "^7.23.9", - "@babel/plugin-transform-modules-umd": "^7.23.3", + "@babel/plugin-transform-arrow-functions": "^7.24.1", + "@babel/plugin-transform-async-generator-functions": "^7.24.3", + "@babel/plugin-transform-async-to-generator": "^7.24.1", + "@babel/plugin-transform-block-scoped-functions": "^7.24.1", + "@babel/plugin-transform-block-scoping": "^7.24.4", + "@babel/plugin-transform-class-properties": "^7.24.1", + "@babel/plugin-transform-class-static-block": "^7.24.4", + "@babel/plugin-transform-classes": "^7.24.1", + "@babel/plugin-transform-computed-properties": "^7.24.1", + "@babel/plugin-transform-destructuring": "^7.24.1", + "@babel/plugin-transform-dotall-regex": "^7.24.1", + "@babel/plugin-transform-duplicate-keys": "^7.24.1", + "@babel/plugin-transform-dynamic-import": "^7.24.1", + "@babel/plugin-transform-exponentiation-operator": "^7.24.1", + "@babel/plugin-transform-export-namespace-from": "^7.24.1", + "@babel/plugin-transform-for-of": "^7.24.1", + "@babel/plugin-transform-function-name": "^7.24.1", + "@babel/plugin-transform-json-strings": "^7.24.1", + "@babel/plugin-transform-literals": "^7.24.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.1", + "@babel/plugin-transform-member-expression-literals": "^7.24.1", + "@babel/plugin-transform-modules-amd": "^7.24.1", + "@babel/plugin-transform-modules-commonjs": "^7.24.1", + "@babel/plugin-transform-modules-systemjs": "^7.24.1", + "@babel/plugin-transform-modules-umd": "^7.24.1", "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.23.3", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", - "@babel/plugin-transform-numeric-separator": "^7.23.4", - "@babel/plugin-transform-object-rest-spread": "^7.24.0", - "@babel/plugin-transform-object-super": "^7.23.3", - "@babel/plugin-transform-optional-catch-binding": "^7.23.4", - "@babel/plugin-transform-optional-chaining": "^7.23.4", - "@babel/plugin-transform-parameters": "^7.23.3", - "@babel/plugin-transform-private-methods": "^7.23.3", - "@babel/plugin-transform-private-property-in-object": "^7.23.4", - "@babel/plugin-transform-property-literals": "^7.23.3", - "@babel/plugin-transform-regenerator": "^7.23.3", - "@babel/plugin-transform-reserved-words": "^7.23.3", - "@babel/plugin-transform-shorthand-properties": "^7.23.3", - "@babel/plugin-transform-spread": "^7.23.3", - "@babel/plugin-transform-sticky-regex": "^7.23.3", - "@babel/plugin-transform-template-literals": "^7.23.3", - "@babel/plugin-transform-typeof-symbol": "^7.23.3", - "@babel/plugin-transform-unicode-escapes": "^7.23.3", - "@babel/plugin-transform-unicode-property-regex": "^7.23.3", - "@babel/plugin-transform-unicode-regex": "^7.23.3", - "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", + "@babel/plugin-transform-new-target": "^7.24.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.1", + "@babel/plugin-transform-numeric-separator": "^7.24.1", + "@babel/plugin-transform-object-rest-spread": "^7.24.1", + "@babel/plugin-transform-object-super": "^7.24.1", + "@babel/plugin-transform-optional-catch-binding": "^7.24.1", + "@babel/plugin-transform-optional-chaining": "^7.24.1", + "@babel/plugin-transform-parameters": "^7.24.1", + "@babel/plugin-transform-private-methods": "^7.24.1", + "@babel/plugin-transform-private-property-in-object": "^7.24.1", + "@babel/plugin-transform-property-literals": "^7.24.1", + "@babel/plugin-transform-regenerator": "^7.24.1", + "@babel/plugin-transform-reserved-words": "^7.24.1", + "@babel/plugin-transform-shorthand-properties": "^7.24.1", + "@babel/plugin-transform-spread": "^7.24.1", + "@babel/plugin-transform-sticky-regex": "^7.24.1", + "@babel/plugin-transform-template-literals": "^7.24.1", + "@babel/plugin-transform-typeof-symbol": "^7.24.1", + "@babel/plugin-transform-unicode-escapes": "^7.24.1", + "@babel/plugin-transform-unicode-property-regex": "^7.24.1", + "@babel/plugin-transform-unicode-regex": "^7.24.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.24.1", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.8", - "babel-plugin-polyfill-corejs3": "^0.9.0", - "babel-plugin-polyfill-regenerator": "^0.5.5", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-regenerator": "^0.6.1", "core-js-compat": "^3.31.0", "semver": "^6.3.1" }, @@ -2147,17 +2099,17 @@ } }, "node_modules/@babel/preset-react": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.23.3.tgz", - "integrity": "sha512-tbkHOS9axH6Ysf2OUEqoSZ6T3Fa2SrNH6WTWSPBboxKzdxNc9qOICeLXkNG0ZEwbQ1HY8liwOce4aN/Ceyuq6w==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.24.1.tgz", + "integrity": "sha512-eFa8up2/8cZXLIpkafhaADTXSnl7IsUFCYenRWrARBz0/qZwcT0RBXpys0LJU4+WfPoF2ZG6ew6s2V6izMCwRA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.15", - "@babel/plugin-transform-react-display-name": "^7.23.3", - "@babel/plugin-transform-react-jsx": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-validator-option": "^7.23.5", + "@babel/plugin-transform-react-display-name": "^7.24.1", + "@babel/plugin-transform-react-jsx": "^7.23.4", "@babel/plugin-transform-react-jsx-development": "^7.22.5", - "@babel/plugin-transform-react-pure-annotations": "^7.23.3" + "@babel/plugin-transform-react-pure-annotations": "^7.24.1" }, "engines": { "node": ">=6.9.0" @@ -2167,16 +2119,16 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.23.3.tgz", - "integrity": "sha512-17oIGVlqz6CchO9RFYn5U6ZpWRZIngayYCtrPRSgANSwC2V1Jb+iP74nVxzzXJte8b8BYxrL1yY96xfhTBrNNQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.24.1.tgz", + "integrity": "sha512-1DBaMmRDpuYQBPWD8Pf/WEwCrtgRHxsZnP4mIy9G/X+hFfbI47Q2G4t1Paakld84+qsk2fSsUPMKg71jkoOOaQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.15", - "@babel/plugin-syntax-jsx": "^7.23.3", - "@babel/plugin-transform-modules-commonjs": "^7.23.3", - "@babel/plugin-transform-typescript": "^7.23.3" + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-validator-option": "^7.23.5", + "@babel/plugin-syntax-jsx": "^7.24.1", + "@babel/plugin-transform-modules-commonjs": "^7.24.1", + "@babel/plugin-transform-typescript": "^7.24.1" }, "engines": { "node": ">=6.9.0" @@ -2192,9 +2144,9 @@ "dev": true }, "node_modules/@babel/runtime": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz", - "integrity": "sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", + "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -2217,18 +2169,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.0.tgz", - "integrity": "sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz", + "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", + "@babel/code-frame": "^7.24.1", + "@babel/generator": "^7.24.1", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.24.0", + "@babel/parser": "^7.24.1", "@babel/types": "^7.24.0", "debug": "^4.3.1", "globals": "^11.1.0" @@ -2325,9 +2277,9 @@ } }, "node_modules/@emotion/serialize": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.3.tgz", - "integrity": "sha512-iD4D6QVZFDhcbH0RAG1uVu1CwVLMWUkCvAqqlewO/rxf8+87yIBAlt4+AxMiiKPLs5hFc0owNk/sLLAOROw3cA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.4.tgz", + "integrity": "sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ==", "peer": true, "dependencies": { "@emotion/hash": "^0.9.1", @@ -2371,9 +2323,9 @@ "peer": true }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", - "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", "cpu": [ "ppc64" ], @@ -2387,9 +2339,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", - "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", "cpu": [ "arm" ], @@ -2403,9 +2355,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", - "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", "cpu": [ "arm64" ], @@ -2419,9 +2371,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", - "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", "cpu": [ "x64" ], @@ -2435,9 +2387,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", - "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", "cpu": [ "arm64" ], @@ -2451,9 +2403,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", - "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", "cpu": [ "x64" ], @@ -2467,9 +2419,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", - "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", "cpu": [ "arm64" ], @@ -2483,9 +2435,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", - "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", "cpu": [ "x64" ], @@ -2499,9 +2451,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", - "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", "cpu": [ "arm" ], @@ -2515,9 +2467,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", - "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", "cpu": [ "arm64" ], @@ -2531,9 +2483,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", - "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", "cpu": [ "ia32" ], @@ -2547,9 +2499,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", - "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", "cpu": [ "loong64" ], @@ -2563,9 +2515,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", - "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", "cpu": [ "mips64el" ], @@ -2579,9 +2531,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", - "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", "cpu": [ "ppc64" ], @@ -2595,9 +2547,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", - "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", "cpu": [ "riscv64" ], @@ -2611,9 +2563,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", - "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", "cpu": [ "s390x" ], @@ -2627,9 +2579,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", - "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", "cpu": [ "x64" ], @@ -2643,9 +2595,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", - "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", "cpu": [ "x64" ], @@ -2659,9 +2611,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", - "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", "cpu": [ "x64" ], @@ -2675,9 +2627,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", - "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", "cpu": [ "x64" ], @@ -2691,9 +2643,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", - "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", "cpu": [ "arm64" ], @@ -2707,9 +2659,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", - "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", "cpu": [ "ia32" ], @@ -2723,9 +2675,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", - "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", "cpu": [ "x64" ], @@ -2876,9 +2828,9 @@ "dev": true }, "node_modules/@fortawesome/fontawesome-common-types": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.1.tgz", - "integrity": "sha512-GkWzv+L6d2bI5f/Vk6ikJ9xtl7dfXtoRu3YGE6nq0p/FFqA1ebMOAWg3XgRyb0I6LYyYkiAo+3/KrwuBp8xG7A==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.2.tgz", + "integrity": "sha512-gBxPg3aVO6J0kpfHNILc+NMhXnqHumFxOmjYCFfOiLZfwhnnfhtsdA2hfJlDnj+8PjAs6kKQPenOTKj3Rf7zHw==", "dev": true, "hasInstallScript": true, "engines": { @@ -2886,52 +2838,52 @@ } }, "node_modules/@fortawesome/fontawesome-svg-core": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.1.tgz", - "integrity": "sha512-MfRCYlQPXoLlpem+egxjfkEuP9UQswTrlCOsknus/NcMoblTH2g0jPrapbcIb04KGA7E2GZxbAccGZfWoYgsrQ==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.2.tgz", + "integrity": "sha512-5CdaCBGl8Rh9ohNdxeeTMxIj8oc3KNBgIeLMvJosBMdslK/UnEB8rzyDRrbKdL1kDweqBPo4GT9wvnakHWucZw==", "dev": true, "hasInstallScript": true, "dependencies": { - "@fortawesome/fontawesome-common-types": "6.5.1" + "@fortawesome/fontawesome-common-types": "6.5.2" }, "engines": { "node": ">=6" } }, "node_modules/@fortawesome/free-brands-svg-icons": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.5.1.tgz", - "integrity": "sha512-093l7DAkx0aEtBq66Sf19MgoZewv1zeY9/4C7vSKPO4qMwEsW/2VYTUTpBtLwfb9T2R73tXaRDPmE4UqLCYHfg==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.5.2.tgz", + "integrity": "sha512-zi5FNYdmKLnEc0jc0uuHH17kz/hfYTg4Uei0wMGzcoCL/4d3WM3u1VMc0iGGa31HuhV5i7ZK8ZlTCQrHqRHSGQ==", "dev": true, "hasInstallScript": true, "dependencies": { - "@fortawesome/fontawesome-common-types": "6.5.1" + "@fortawesome/fontawesome-common-types": "6.5.2" }, "engines": { "node": ">=6" } }, "node_modules/@fortawesome/free-regular-svg-icons": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.5.1.tgz", - "integrity": "sha512-m6ShXn+wvqEU69wSP84coxLbNl7sGVZb+Ca+XZq6k30SzuP3X4TfPqtycgUh9ASwlNh5OfQCd8pDIWxl+O+LlQ==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.5.2.tgz", + "integrity": "sha512-iabw/f5f8Uy2nTRtJ13XZTS1O5+t+anvlamJ3zJGLEVE2pKsAWhPv2lq01uQlfgCX7VaveT3EVs515cCN9jRbw==", "dev": true, "hasInstallScript": true, "dependencies": { - "@fortawesome/fontawesome-common-types": "6.5.1" + "@fortawesome/fontawesome-common-types": "6.5.2" }, "engines": { "node": ">=6" } }, "node_modules/@fortawesome/free-solid-svg-icons": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.1.tgz", - "integrity": "sha512-S1PPfU3mIJa59biTtXJz1oI0+KAXW6bkAb31XKhxdxtuXDiUIFsih4JR1v5BbxY7hVHsD1RKq+jRkVRaf773NQ==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.2.tgz", + "integrity": "sha512-QWFZYXFE7O1Gr1dTIp+D6UcFUF0qElOnZptpi7PBUMylJh+vFmIedVe1Ir6RM1t2tEQLLSV1k7bR4o92M+uqlw==", "dev": true, "hasInstallScript": true, "dependencies": { - "@fortawesome/fontawesome-common-types": "6.5.1" + "@fortawesome/fontawesome-common-types": "6.5.2" }, "engines": { "node": ">=6" @@ -2978,9 +2930,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", - "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "dev": true }, "node_modules/@istanbuljs/schema": { @@ -3410,9 +3362,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz", - "integrity": "sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.1.tgz", + "integrity": "sha512-fH8/o8nSUek8ceQnT7K4EQbSiV7jgkHq81m9lWZFIXjJ7lJzpWXbQFpT/Zh6OZYnpFykvzC3fbEvEAFZu03dPA==", "cpu": [ "arm" ], @@ -3423,9 +3375,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz", - "integrity": "sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.1.tgz", + "integrity": "sha512-Y/9OHLjzkunF+KGEoJr3heiD5X9OLa8sbT1lm0NYeKyaM3oMhhQFvPB0bNZYJwlq93j8Z6wSxh9+cyKQaxS7PQ==", "cpu": [ "arm64" ], @@ -3436,9 +3388,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz", - "integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.1.tgz", + "integrity": "sha512-+kecg3FY84WadgcuSVm6llrABOdQAEbNdnpi5X3UwWiFVhZIZvKgGrF7kmLguvxHNQy+UuRV66cLVl3S+Rkt+Q==", "cpu": [ "arm64" ], @@ -3449,9 +3401,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz", - "integrity": "sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.1.tgz", + "integrity": "sha512-2pYRzEjVqq2TB/UNv47BV/8vQiXkFGVmPFwJb+1E0IFFZbIX8/jo1olxqqMbo6xCXf8kabANhp5bzCij2tFLUA==", "cpu": [ "x64" ], @@ -3462,9 +3414,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz", - "integrity": "sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.1.tgz", + "integrity": "sha512-mS6wQ6Do6/wmrF9aTFVpIJ3/IDXhg1EZcQFYHZLHqw6AzMBjTHWnCG35HxSqUNphh0EHqSM6wRTT8HsL1C0x5g==", "cpu": [ "arm" ], @@ -3475,9 +3427,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz", - "integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.1.tgz", + "integrity": "sha512-p9rGKYkHdFMzhckOTFubfxgyIO1vw//7IIjBBRVzyZebWlzRLeNhqxuSaZ7kCEKVkm/kuC9fVRW9HkC/zNRG2w==", "cpu": [ "arm64" ], @@ -3488,9 +3440,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz", - "integrity": "sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.1.tgz", + "integrity": "sha512-nDY6Yz5xS/Y4M2i9JLQd3Rofh5OR8Bn8qe3Mv/qCVpHFlwtZSBYSPaU4mrGazWkXrdQ98GB//H0BirGR/SKFSw==", "cpu": [ "arm64" ], @@ -3500,10 +3452,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.1.tgz", + "integrity": "sha512-im7HE4VBL+aDswvcmfx88Mp1soqL9OBsdDBU8NqDEYtkri0qV0THhQsvZtZeNNlLeCUQ16PZyv7cqutjDF35qw==", + "cpu": [ + "ppc64le" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz", - "integrity": "sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.1.tgz", + "integrity": "sha512-RWdiHuAxWmzPJgaHJdpvUUlDz8sdQz4P2uv367T2JocdDa98iRw2UjIJ4QxSyt077mXZT2X6pKfT2iYtVEvOFw==", "cpu": [ "riscv64" ], @@ -3513,10 +3478,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.1.tgz", + "integrity": "sha512-VMgaGQ5zRX6ZqV/fas65/sUGc9cPmsntq2FiGmayW9KMNfWVG/j0BAqImvU4KTeOOgYSf1F+k6at1UfNONuNjA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz", - "integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.1.tgz", + "integrity": "sha512-9Q7DGjZN+hTdJomaQ3Iub4m6VPu1r94bmK2z3UeWP3dGUecRC54tmVu9vKHTm1bOt3ASoYtEz6JSRLFzrysKlA==", "cpu": [ "x64" ], @@ -3527,9 +3505,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz", - "integrity": "sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.1.tgz", + "integrity": "sha512-JNEG/Ti55413SsreTguSx0LOVKX902OfXIKVg+TCXO6Gjans/k9O6ww9q3oLGjNDaTLxM+IHFMeXy/0RXL5R/g==", "cpu": [ "x64" ], @@ -3540,9 +3518,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz", - "integrity": "sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.1.tgz", + "integrity": "sha512-ryS22I9y0mumlLNwDFYZRDFLwWh3aKaC72CWjFcFvxK0U6v/mOkM5Up1bTbCRAhv3kEIwW2ajROegCIQViUCeA==", "cpu": [ "arm64" ], @@ -3553,9 +3531,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz", - "integrity": "sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.1.tgz", + "integrity": "sha512-TdloItiGk+T0mTxKx7Hp279xy30LspMso+GzQvV2maYePMAWdmrzqSNZhUpPj3CGw12aGj57I026PgLCTu8CGg==", "cpu": [ "ia32" ], @@ -3566,9 +3544,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz", - "integrity": "sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.1.tgz", + "integrity": "sha512-wQGI+LY/Py20zdUPq+XCem7JcPOyzIJBm3dli+56DJsQOHbnXZFEwgmnC6el1TPAfC8lBT3m+z69RmLykNUbew==", "cpu": [ "x64" ], @@ -3579,9 +3557,9 @@ ] }, "node_modules/@rushstack/eslint-patch": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.7.2.tgz", - "integrity": "sha512-RbhOOTCNoCrbfkRyoXODZp75MlpiHMgbE5MEBZAnnnLyQNgrigEj4p0lzsMDyc1zVsJDLrivB58tgg3emX0eEA==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.1.tgz", + "integrity": "sha512-S3Kq8e7LqxkA9s7HKLqXGTGck1uwis5vAXan3FnU5yw1Ec5hsSGnq4s/UCaSqABPOnOTg7zASLyst7+ohgWexg==", "dev": true }, "node_modules/@sinclair/typebox": { @@ -3596,22 +3574,22 @@ "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" }, "node_modules/@testing-library/dom": { - "version": "9.3.4", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz", - "integrity": "sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.0.0.tgz", + "integrity": "sha512-PmJPnogldqoVFf+EwbHvbBJ98MmqASV8kLrBYgsDNxQcFMeIS7JFL48sfyXvuMtgmWO/wMhh25odr+8VhDmn4g==", "dev": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", - "aria-query": "5.1.3", + "aria-query": "5.3.0", "chalk": "^4.1.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "pretty-format": "^27.0.2" }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/@testing-library/jest-dom": { @@ -3679,17 +3657,17 @@ "dev": true }, "node_modules/@testing-library/react": { - "version": "14.2.1", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.2.1.tgz", - "integrity": "sha512-sGdjws32ai5TLerhvzThYFbpnF9XtL65Cjf+gB0Dhr29BGqK+mAeN7SURSdu+eqgET4ANcWoC7FQpkaiGvBr+A==", + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-15.0.5.tgz", + "integrity": "sha512-ttodVWYA2i2w4hRa6krKrmS1vKxAEkwDz34y+CwbcrbZUxFzUYN3a5xZyFKo+K6LBseCRCUkwcjATpaNn/UsIA==", "dev": true, "dependencies": { "@babel/runtime": "^7.12.5", - "@testing-library/dom": "^9.0.0", + "@testing-library/dom": "^10.0.0", "@types/react-dom": "^18.0.0" }, "engines": { - "node": ">=14" + "node": ">=18" }, "peerDependencies": { "react": "^18.0.0", @@ -3910,9 +3888,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.11.26", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.26.tgz", - "integrity": "sha512-YwOMmyhNnAWijOBQweOJnQPl068Oqd4K3OFbTc6AHJwzweUwwWG3GIFY74OKks2PJUDkQPeddOQES9mLn1CTEQ==", + "version": "20.12.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.6.tgz", + "integrity": "sha512-3KurE8taB8GCvZBPngVbp0lk5CKi8M9f9k1rsADh0Evdz5SzJ+Q+Hx9uHoFGsLnLnd1xmkDQr2hVhlA0Mn0lKQ==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -3924,46 +3902,39 @@ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" }, "node_modules/@types/prop-types": { - "version": "15.7.11", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", - "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==", + "version": "15.7.12", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", "devOptional": true }, "node_modules/@types/react": { - "version": "18.2.65", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.65.tgz", - "integrity": "sha512-98TsY0aW4jqx/3RqsUXwMDZSWR1Z4CUlJNue8ueS2/wcxZOsz4xmW1X8ieaWVRHcmmQM3R8xVA4XWB3dJnWwDQ==", + "version": "18.2.75", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.75.tgz", + "integrity": "sha512-+DNnF7yc5y0bHkBTiLKqXFe+L4B3nvOphiMY3tuA5X10esmjqk7smyBZzbGTy2vsiy/Bnzj8yFIBL8xhRacoOg==", "devOptional": true, "dependencies": { "@types/prop-types": "*", - "@types/scheduler": "*", "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "18.2.21", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.21.tgz", - "integrity": "sha512-gnvBA/21SA4xxqNXEwNiVcP0xSGHh/gi1VhWv9Bl46a0ItbTT5nFY+G9VSQpaG/8N/qdJpJ+vftQ4zflTtnjLw==", + "version": "18.2.24", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.24.tgz", + "integrity": "sha512-cN6upcKd8zkGy4HU9F1+/s98Hrp6D4MOcippK4PoE8OZRngohHZpbJn1GsaDLz87MqvHNoT13nHvNqM9ocRHZg==", "dev": true, "dependencies": { "@types/react": "*" } }, "node_modules/@types/react-table": { - "version": "7.7.19", - "resolved": "https://registry.npmjs.org/@types/react-table/-/react-table-7.7.19.tgz", - "integrity": "sha512-47jMa1Pai7ily6BXJCW33IL5ghqmCWs2VM9s+h1D4mCaK5P4uNkZOW3RMMg8MCXBvAJ0v9+sPqKjhid0PaJPQA==", + "version": "7.7.20", + "resolved": "https://registry.npmjs.org/@types/react-table/-/react-table-7.7.20.tgz", + "integrity": "sha512-ahMp4pmjVlnExxNwxyaDrFgmKxSbPwU23sGQw2gJK4EhCvnvmib2s/O/+y1dfV57dXOwpr2plfyBol+vEHbi2w==", "dev": true, "dependencies": { "@types/react": "*" } }, - "node_modules/@types/scheduler": { - "version": "0.16.8", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", - "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", - "devOptional": true - }, "node_modules/@types/semver": { "version": "7.5.8", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", @@ -4345,9 +4316,9 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.3.1.tgz", - "integrity": "sha512-UuBnkSJUNE9rdHjDCPyJ4fYuMkoMtnghes1XohYa4At0MS3OQSAo97FrbwSLRshYsXThMZy1+ybD/byK5llyIg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.4.0.tgz", + "integrity": "sha512-4hDGyH1SvKpgZnIByr9LhGgCEuF9DKM34IBLCC/fVfy24Z3+PZ+Ii9hsVBsHvY1umM1aGPEjceRkzxCfcQ10wg==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.1", @@ -4355,12 +4326,13 @@ "debug": "^4.3.4", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^4.0.1", + "istanbul-lib-source-maps": "^5.0.4", "istanbul-reports": "^3.1.6", "magic-string": "^0.30.5", "magicast": "^0.3.3", "picocolors": "^1.0.0", "std-env": "^3.5.0", + "strip-literal": "^2.0.0", "test-exclude": "^6.0.0", "v8-to-istanbul": "^9.2.0" }, @@ -4368,17 +4340,17 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "1.3.1" + "vitest": "1.4.0" } }, "node_modules/@vitest/expect": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.3.1.tgz", - "integrity": "sha512-xofQFwIzfdmLLlHa6ag0dPV8YsnKOCP1KdAeVVh34vSjN2dcUiXYCD9htu/9eM7t8Xln4v03U9HLxLpPlsXdZw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.4.0.tgz", + "integrity": "sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA==", "dev": true, "dependencies": { - "@vitest/spy": "1.3.1", - "@vitest/utils": "1.3.1", + "@vitest/spy": "1.4.0", + "@vitest/utils": "1.4.0", "chai": "^4.3.10" }, "funding": { @@ -4386,12 +4358,12 @@ } }, "node_modules/@vitest/runner": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.3.1.tgz", - "integrity": "sha512-5FzF9c3jG/z5bgCnjr8j9LNq/9OxV2uEBAITOXfoe3rdZJTdO7jzThth7FXv/6b+kdY65tpRQB7WaKhNZwX+Kg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.4.0.tgz", + "integrity": "sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg==", "dev": true, "dependencies": { - "@vitest/utils": "1.3.1", + "@vitest/utils": "1.4.0", "p-limit": "^5.0.0", "pathe": "^1.1.1" }, @@ -4427,9 +4399,9 @@ } }, "node_modules/@vitest/snapshot": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.3.1.tgz", - "integrity": "sha512-EF++BZbt6RZmOlE3SuTPu/NfwBF6q4ABS37HHXzs2LUVPBLx2QoY/K0fKpRChSo8eLiuxcbCVfqKgx/dplCDuQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.4.0.tgz", + "integrity": "sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==", "dev": true, "dependencies": { "magic-string": "^0.30.5", @@ -4473,9 +4445,9 @@ "dev": true }, "node_modules/@vitest/spy": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.3.1.tgz", - "integrity": "sha512-xAcW+S099ylC9VLU7eZfdT9myV67Nor9w9zhf0mGCYJSO+zM2839tOeROTdikOi/8Qeusffvxb/MyBSOja1Uig==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.4.0.tgz", + "integrity": "sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==", "dev": true, "dependencies": { "tinyspy": "^2.2.0" @@ -4485,12 +4457,12 @@ } }, "node_modules/@vitest/ui": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-1.3.1.tgz", - "integrity": "sha512-2UrFLJ62c/eJGPHcclstMKlAR7E1WB1ITe1isuowEPJJHi3HfqofvsUqQ1cGrEF7kitG1DJuwURUA3HLDtQkXA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-1.4.0.tgz", + "integrity": "sha512-XC6CMhN1gzYcGbpn6/Oanj4Au2EXwQEX6vpcOeLlZv8dy7g11Ukx8zwtYQbwxs9duK2s9j2o5rbQiCP5DPAcmw==", "dev": true, "dependencies": { - "@vitest/utils": "1.3.1", + "@vitest/utils": "1.4.0", "fast-glob": "^3.3.2", "fflate": "^0.8.1", "flatted": "^3.2.9", @@ -4502,13 +4474,13 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "1.3.1" + "vitest": "1.4.0" } }, "node_modules/@vitest/utils": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.3.1.tgz", - "integrity": "sha512-d3Waie/299qqRyHTm2DjADeTaNdNSVsnwHPWrs20JMpjh6eiVq7ggggweO8rc4arhf6rRkWuHKwvxGvejUXZZQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.4.0.tgz", + "integrity": "sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==", "dev": true, "dependencies": { "diff-sequences": "^29.6.3", @@ -4583,9 +4555,9 @@ } }, "node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "dependencies": { "debug": "^4.3.4" @@ -4669,9 +4641,9 @@ "dev": true }, "node_modules/aria-hidden": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.3.tgz", - "integrity": "sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz", + "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==", "dependencies": { "tslib": "^2.0.0" }, @@ -4680,12 +4652,12 @@ } }, "node_modules/aria-query": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", - "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, "dependencies": { - "deep-equal": "^2.0.5" + "dequal": "^2.0.3" } }, "node_modules/array-buffer-byte-length": { @@ -4705,15 +4677,16 @@ } }, "node_modules/array-includes": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", - "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", "is-string": "^1.0.7" }, "engines": { @@ -4732,35 +4705,17 @@ "node": ">=8" } }, - "node_modules/array.prototype.filter": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.3.tgz", - "integrity": "sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-array-method-boxes-properly": "^1.0.0", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/array.prototype.findlast": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.4.tgz", - "integrity": "sha512-BMtLxpV+8BD+6ZPFIWmnUBpQoy+A+ujcg4rhp2iwCRJYA7PEh2MS4NL3lz8EiDlLrJPp2hg9qWihr5pd//jcGw==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.5", + "call-bind": "^1.0.7", "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", + "es-abstract": "^1.23.2", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "es-shim-unscopables": "^1.0.2" }, "engines": { @@ -4771,15 +4726,16 @@ } }, "node_modules/array.prototype.findlastindex": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.4.tgz", - "integrity": "sha512-hzvSHUshSpCflDR1QMUBLHGHP1VIEBegT4pix9H/Z92Xw3ySoy6c2qh7lJWTJnRJ8JCZ9bJNCgTyYaJGcJu6xQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.5", + "call-bind": "^1.0.7", "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", + "es-abstract": "^1.23.2", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "es-shim-unscopables": "^1.0.2" }, "engines": { @@ -4887,15 +4843,6 @@ "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", "dev": true }, - "node_modules/asynciterator.prototype": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz", - "integrity": "sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.3" - } - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -4934,11 +4881,11 @@ } }, "node_modules/axios": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", - "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", "dependencies": { - "follow-redirects": "^1.15.4", + "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } @@ -4980,58 +4927,26 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.9.0.tgz", - "integrity": "sha512-7nZPG1uzK2Ymhy/NbaOWTg3uibM2BmGASS4vHS4szRZAIR8R6GwA/xAujpdrXU5iyklrimWnLWU+BLF9suPTqg==", - "dev": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.5.0", - "core-js-compat": "^3.34.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz", - "integrity": "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==", - "dev": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.5.tgz", - "integrity": "sha512-OJGYZlhLqBh2DDHeqAxWB1XIvr49CxiJ2gIt61/PU55CQK4Z58OzMqjDe1zwQdQk+rBYsRc+1rJmdajM3gimHg==", + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", + "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", "dev": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.5.0" + "@babel/helper-define-polyfill-provider": "^0.6.1", + "core-js-compat": "^3.36.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/babel-plugin-polyfill-regenerator/node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz", - "integrity": "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==", + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.1.tgz", + "integrity": "sha512-JfTApdE++cgcTWjsiCQlLyFBMbTUft9ja17saCc93lgV33h4tuCVj7tlvu//qpLwaG+3yEz7/KhahGrUMkVq9g==", "dev": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" + "@babel/helper-define-polyfill-provider": "^0.6.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -5081,12 +4996,15 @@ } }, "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/brace-expansion": { @@ -5194,9 +5112,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001597", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001597.tgz", - "integrity": "sha512-7LjJvmQU6Sj7bL0j5b5WY/3n7utXUJvAe1lxhsHDbLmwX9mdL86Yjtr+5SRCyf8qME4M7pU2hswj0FpyBVCv9w==", + "version": "1.0.30001607", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001607.tgz", + "integrity": "sha512-WcvhVRjXLKFB/kmOFVwELtMxyhq3iM/MvmXcyCe2PNf166c39mptscOc/45TTS96n2gpNV2z7+NakArTWZCQ3w==", "dev": true, "funding": [ { @@ -5375,12 +5293,12 @@ "peer": true }, "node_modules/core-js-compat": { - "version": "3.36.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.0.tgz", - "integrity": "sha512-iV9Pd/PsgjNWBXeq8XRtWVSgz2tKAfhfvBs7qxYty+RlRd+OCksaWmOnc4JKrTc1cToXL1N0s3l/vwlxPtdElw==", + "version": "3.36.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.1.tgz", + "integrity": "sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA==", "dev": true, "dependencies": { - "browserslist": "^4.22.3" + "browserslist": "^4.23.0" }, "funding": { "type": "opencollective", @@ -5587,6 +5505,57 @@ "node": ">=18" } }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -5627,38 +5596,6 @@ "node": ">=6" } }, - "node_modules/deep-equal": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", - "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.5", - "es-get-iterator": "^1.1.3", - "get-intrinsic": "^1.2.2", - "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.2", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "isarray": "^2.0.5", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -5775,9 +5712,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.702", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.702.tgz", - "integrity": "sha512-LYLXyEUsZ3nNSwiOWjI88N1PJUAMU2QphQSgGLVkFnb3FxZxNui2Vzi2PaKPgPWbsWbZstZnh6BMf/VQJamjiQ==", + "version": "1.4.730", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.730.tgz", + "integrity": "sha512-oJRPo82XEqtQAobHpJIR3zW5YO3sSRRkPz2an4yxi1UvqhsGm54vR/wzTFV74a3soDOJ8CKW7ajOOX5ESzddwg==", "dev": true }, "node_modules/emoji-regex": { @@ -5847,17 +5784,21 @@ } }, "node_modules/es-abstract": { - "version": "1.22.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.5.tgz", - "integrity": "sha512-oW69R+4q2wG+Hc3KZePPZxOiisRIqfKBVo/HLx94QcJeWGU/8sZhCvc829rd1kS366vlJbzBfXf9yWwf0+Ko7w==", + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.1", "arraybuffer.prototype.slice": "^1.0.3", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", "es-define-property": "^1.0.0", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.0.3", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.6", @@ -5868,10 +5809,11 @@ "has-property-descriptors": "^1.0.2", "has-proto": "^1.0.3", "has-symbols": "^1.0.3", - "hasown": "^2.0.1", + "hasown": "^2.0.2", "internal-slot": "^1.0.7", "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", "is-negative-zero": "^2.0.3", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.3", @@ -5882,17 +5824,17 @@ "object-keys": "^1.1.1", "object.assign": "^4.1.5", "regexp.prototype.flags": "^1.5.2", - "safe-array-concat": "^1.1.0", + "safe-array-concat": "^1.1.2", "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.8", - "string.prototype.trimend": "^1.0.7", - "string.prototype.trimstart": "^1.0.7", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.2", "typed-array-byte-length": "^1.0.1", "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.5", + "typed-array-length": "^1.0.6", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.14" + "which-typed-array": "^1.1.15" }, "engines": { "node": ">= 0.4" @@ -5901,12 +5843,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-array-method-boxes-properly": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", - "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", - "dev": true - }, "node_modules/es-define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", @@ -5928,47 +5864,38 @@ "node": ">= 0.4" } }, - "node_modules/es-get-iterator": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", - "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "is-arguments": "^1.1.1", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.7", - "isarray": "^2.0.5", - "stop-iteration-iterator": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/es-iterator-helpers": { - "version": "1.0.17", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.17.tgz", - "integrity": "sha512-lh7BsUqelv4KUbR5a/ZTaGGIMLCjPGPqJ6q+Oq24YP0RdyptX1uzm4vvaqzk7Zx3bpl/76YLTTDj9L7uYQ92oQ==", + "version": "1.0.18", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.18.tgz", + "integrity": "sha512-scxAJaewsahbqTYrGKJihhViaM6DDZDDoucfvzNbK0pOren1g/daDQ3IAhzn+1G14rBG7w+i5N+qul60++zlKA==", "dev": true, "dependencies": { - "asynciterator.prototype": "^1.0.0", "call-bind": "^1.0.7", "define-properties": "^1.2.1", - "es-abstract": "^1.22.4", + "es-abstract": "^1.23.0", "es-errors": "^1.3.0", - "es-set-tostringtag": "^2.0.2", + "es-set-tostringtag": "^2.0.3", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "globalthis": "^1.0.3", "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.1", + "has-proto": "^1.0.3", "has-symbols": "^1.0.3", "internal-slot": "^1.0.7", "iterator.prototype": "^1.1.2", - "safe-array-concat": "^1.1.0" + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" }, "engines": { "node": ">= 0.4" @@ -6015,9 +5942,9 @@ } }, "node_modules/esbuild": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", - "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", "dev": true, "hasInstallScript": true, "bin": { @@ -6027,29 +5954,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.12", - "@esbuild/android-arm": "0.19.12", - "@esbuild/android-arm64": "0.19.12", - "@esbuild/android-x64": "0.19.12", - "@esbuild/darwin-arm64": "0.19.12", - "@esbuild/darwin-x64": "0.19.12", - "@esbuild/freebsd-arm64": "0.19.12", - "@esbuild/freebsd-x64": "0.19.12", - "@esbuild/linux-arm": "0.19.12", - "@esbuild/linux-arm64": "0.19.12", - "@esbuild/linux-ia32": "0.19.12", - "@esbuild/linux-loong64": "0.19.12", - "@esbuild/linux-mips64el": "0.19.12", - "@esbuild/linux-ppc64": "0.19.12", - "@esbuild/linux-riscv64": "0.19.12", - "@esbuild/linux-s390x": "0.19.12", - "@esbuild/linux-x64": "0.19.12", - "@esbuild/netbsd-x64": "0.19.12", - "@esbuild/openbsd-x64": "0.19.12", - "@esbuild/sunos-x64": "0.19.12", - "@esbuild/win32-arm64": "0.19.12", - "@esbuild/win32-ia32": "0.19.12", - "@esbuild/win32-x64": "0.19.12" + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" } }, "node_modules/escalade": { @@ -6341,19 +6268,10 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" } }, - "node_modules/eslint-plugin-jsx-a11y/node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "dev": true, - "dependencies": { - "dequal": "^2.0.3" - } - }, "node_modules/eslint-plugin-react": { - "version": "7.34.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.0.tgz", - "integrity": "sha512-MeVXdReleBTdkz/bvcQMSnCXGi+c9kvy51IpinjnJgutl3YTHWsDdke7Z1ufZpGfDG8xduBDKyjtB9JH1eBKIQ==", + "version": "7.34.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.1.tgz", + "integrity": "sha512-N97CxlouPT1AHt8Jn0mhhN2RrADlUAsk1/atcT2KyA/l9Q/E6ll7OIGwNumFmWfZ9skV3XXccYS19h80rHtgkw==", "dev": true, "dependencies": { "array-includes": "^3.1.7", @@ -6754,9 +6672,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", @@ -7306,22 +7224,6 @@ "loose-envify": "^1.0.0" } }, - "node_modules/is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-array-buffer": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", @@ -7421,6 +7323,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-date-object": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", @@ -7723,28 +7640,19 @@ } }, "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.4.tgz", + "integrity": "sha512-wHOoEsNJTVltaJp8eVkm8w+GVkVNHT2YDYo53YdzQEL2gWm1hBX5cGFR9hQJtuGLebidVX7et3+dmDZrmclduw==", "dev": true, "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" + "istanbul-lib-coverage": "^3.0.0" }, "engines": { "node": ">=10" } }, - "node_modules/istanbul-lib-source-maps/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/istanbul-reports": { "version": "3.1.7", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", @@ -8235,9 +8143,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.8", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", - "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", + "version": "0.30.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.9.tgz", + "integrity": "sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw==", "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" @@ -8416,15 +8324,6 @@ "ufo": "^1.3.2" } }, - "node_modules/moment": { - "version": "2.30.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", - "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -8536,22 +8435,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object-is": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", - "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -8580,28 +8463,29 @@ } }, "node_modules/object.entries": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", - "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" } }, "node_modules/object.fromentries": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", - "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -8611,40 +8495,45 @@ } }, "node_modules/object.groupby": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.2.tgz", - "integrity": "sha512-bzBq58S+x+uo0VjurFT0UktpKHOZmv4/xePiOA1nbB9pMqpGK7rUPNgf+1YC+7mE+0HzhTMqNUuCqvKhj6FnBw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", "dev": true, "dependencies": { - "array.prototype.filter": "^1.0.3", - "call-bind": "^1.0.5", + "call-bind": "^1.0.7", "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.0.0" + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" } }, "node_modules/object.hasown": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.3.tgz", - "integrity": "sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.4.tgz", + "integrity": "sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg==", "dev": true, "dependencies": { - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/object.values": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", - "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -8825,8 +8714,7 @@ "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -8861,9 +8749,9 @@ } }, "node_modules/postcss": { - "version": "8.4.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", - "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "dev": true, "funding": [ { @@ -8882,7 +8770,7 @@ "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" @@ -9135,11 +9023,11 @@ } }, "node_modules/react-remove-scroll": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz", - "integrity": "sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==", + "version": "2.5.9", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.9.tgz", + "integrity": "sha512-bvHCLBrFfM2OgcrpPY2YW84sPdS2o2HKWJUf1xGyGLnSoEnOTOBpahIarjRuYtN0ryahCeP242yf+5TrBX/pZA==", "dependencies": { - "react-remove-scroll-bar": "^2.3.4", + "react-remove-scroll-bar": "^2.3.6", "react-style-singleton": "^2.2.1", "tslib": "^2.1.0", "use-callback-ref": "^1.3.0", @@ -9159,9 +9047,9 @@ } }, "node_modules/react-remove-scroll-bar": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.5.tgz", - "integrity": "sha512-3cqjOqg6s0XbOjWvmasmqHch+RLxIEk2r/70rzGXuz3iIGQsQheEQyqYCBb5EECoD01Vo2SIbDqW4paLeLTASw==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz", + "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==", "dependencies": { "react-style-singleton": "^2.2.1", "tslib": "^2.0.0" @@ -9210,9 +9098,9 @@ } }, "node_modules/react-smooth": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.0.tgz", - "integrity": "sha512-2NMXOBY1uVUQx1jBeENGA497HK20y6CPGYL1ZnJLeoQ8rrc3UfmOM82sRxtzpcoCkUMy4CS0RGylfuVhuFjBgg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.1.tgz", + "integrity": "sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w==", "dev": true, "dependencies": { "fast-equals": "^5.0.1", @@ -9319,9 +9207,9 @@ } }, "node_modules/recharts": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.12.2.tgz", - "integrity": "sha512-9bpxjXSF5g81YsKkTSlaX7mM4b6oYI1mIYck6YkUcWuL3tomADccI51/6thY4LmvhYuRTwpfrOvE80Zc3oBRfQ==", + "version": "2.12.6", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.12.6.tgz", + "integrity": "sha512-D+7j9WI+D0NHauah3fKHuNNcRK8bOypPW7os1DERinogGBGaHI7i6tQKJ0aUF3JXyBZ63dyfKIW2WTOPJDxJ8w==", "dev": true, "dependencies": { "clsx": "^2.0.0", @@ -9364,16 +9252,16 @@ } }, "node_modules/reflect.getprototypeof": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.5.tgz", - "integrity": "sha512-62wgfC8dJWrmxv44CA36pLDnP6KKl3Vhxb7PL+8+qrrFMMoJij4vgiMP8zV4O8+CBMXY1mHxI5fITGHXFHVmQQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", + "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==", "dev": true, "dependencies": { - "call-bind": "^1.0.5", + "call-bind": "^1.0.7", "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.0.0", - "get-intrinsic": "^1.2.3", + "es-abstract": "^1.23.1", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", "globalthis": "^1.0.3", "which-builtin-type": "^1.1.3" }, @@ -9532,9 +9420,9 @@ } }, "node_modules/rollup": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz", - "integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.1.tgz", + "integrity": "sha512-4LnHSdd3QK2pa1J6dFbfm1HN0D7vSK/ZuZTsdyUAlA6Rr1yTouUTL13HaDOGJVgby461AhrNGBS7sCGXXtT+SA==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -9547,19 +9435,21 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.13.0", - "@rollup/rollup-android-arm64": "4.13.0", - "@rollup/rollup-darwin-arm64": "4.13.0", - "@rollup/rollup-darwin-x64": "4.13.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.13.0", - "@rollup/rollup-linux-arm64-gnu": "4.13.0", - "@rollup/rollup-linux-arm64-musl": "4.13.0", - "@rollup/rollup-linux-riscv64-gnu": "4.13.0", - "@rollup/rollup-linux-x64-gnu": "4.13.0", - "@rollup/rollup-linux-x64-musl": "4.13.0", - "@rollup/rollup-win32-arm64-msvc": "4.13.0", - "@rollup/rollup-win32-ia32-msvc": "4.13.0", - "@rollup/rollup-win32-x64-msvc": "4.13.0", + "@rollup/rollup-android-arm-eabi": "4.14.1", + "@rollup/rollup-android-arm64": "4.14.1", + "@rollup/rollup-darwin-arm64": "4.14.1", + "@rollup/rollup-darwin-x64": "4.14.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.14.1", + "@rollup/rollup-linux-arm64-gnu": "4.14.1", + "@rollup/rollup-linux-arm64-musl": "4.14.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.14.1", + "@rollup/rollup-linux-riscv64-gnu": "4.14.1", + "@rollup/rollup-linux-s390x-gnu": "4.14.1", + "@rollup/rollup-linux-x64-gnu": "4.14.1", + "@rollup/rollup-linux-x64-musl": "4.14.1", + "@rollup/rollup-win32-arm64-msvc": "4.14.1", + "@rollup/rollup-win32-ia32-msvc": "4.14.1", + "@rollup/rollup-win32-x64-msvc": "4.14.1", "fsevents": "~2.3.2" } }, @@ -9634,9 +9524,9 @@ "dev": true }, "node_modules/sass": { - "version": "1.71.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz", - "integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==", + "version": "1.74.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.74.1.tgz", + "integrity": "sha512-w0Z9p/rWZWelb88ISOLyvqTWGmtmu2QJICqDBGyNnfG4OUnPX9BBjjYIXUpXCMOOg5MQWNpqzt876la1fsTvUA==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -9786,9 +9676,9 @@ } }, "node_modules/socket.io-client": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.4.tgz", - "integrity": "sha512-wh+OkeF0rAVCrABWQBaEjLfb7DVPotMbu0cgWgyR0v6eA4EoVnAwcIeIbcdTE3GT/H3kbdLl7OoH2+asoDRIIg==", + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz", + "integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==", "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", @@ -9821,9 +9711,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "dev": true, "engines": { "node": ">=0.10.0" @@ -9862,18 +9752,6 @@ "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", "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", - "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", - "dev": true, - "dependencies": { - "internal-slot": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/string-natural-compare": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz", @@ -9881,34 +9759,41 @@ "dev": true }, "node_modules/string.prototype.matchall": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz", - "integrity": "sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==", + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", + "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", - "regexp.prototype.flags": "^1.5.0", - "set-function-name": "^2.0.0", - "side-channel": "^1.0.4" + "internal-slot": "^1.0.7", + "regexp.prototype.flags": "^1.5.2", + "set-function-name": "^2.0.2", + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trim": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", - "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -9918,28 +9803,31 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", - "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimstart": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", - "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -10000,21 +9888,21 @@ } }, "node_modules/strip-literal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.0.0.tgz", - "integrity": "sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.0.tgz", + "integrity": "sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==", "dev": true, "dependencies": { - "js-tokens": "^8.0.2" + "js-tokens": "^9.0.0" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/strip-literal/node_modules/js-tokens": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-8.0.3.tgz", - "integrity": "sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.0.tgz", + "integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==", "dev": true }, "node_modules/stylis": { @@ -10090,9 +9978,9 @@ "dev": true }, "node_modules/tinypool": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.2.tgz", - "integrity": "sha512-SUszKYe5wgsxnNOVlBYO6IC+8VGWdVGZWAqUxp3UErNBtptZvWbwyUOyzNL59zigz2rCA92QiL3wvG+JDSdJdQ==", + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.3.tgz", + "integrity": "sha512-Ud7uepAklqRH1bvwy22ynrliC7Dljz7Tm8M/0RBUW+YRa4YHhZ6e4PpgE+fu1zr/WqB1kbeuVrdfeuyIBpy4tw==", "dev": true, "engines": { "node": ">=14.0.0" @@ -10300,9 +10188,9 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.5.tgz", - "integrity": "sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", "dev": true, "dependencies": { "call-bind": "^1.0.7", @@ -10320,9 +10208,9 @@ } }, "node_modules/typescript": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", - "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.4.tgz", + "integrity": "sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -10333,9 +10221,9 @@ } }, "node_modules/ufo": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.4.0.tgz", - "integrity": "sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz", + "integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==", "dev": true }, "node_modules/unbox-primitive": { @@ -10467,9 +10355,9 @@ } }, "node_modules/use-callback-ref": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.1.tgz", - "integrity": "sha512-Lg4Vx1XZQauB42Hw3kK7JM6yjVjgFmFC5/Ab797s79aARomD2nEErc4mCgM8EZrARLmmbWpi5DGCadmK50DcAQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz", + "integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==", "dependencies": { "tslib": "^2.0.0" }, @@ -10565,9 +10453,9 @@ "dev": true }, "node_modules/victory-vendor": { - "version": "36.9.1", - "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.1.tgz", - "integrity": "sha512-+pZIP+U3pEJdDCeFmsXwHzV7vNHQC/eIbHklfe2ZCZqayYRH7lQbHcVgsJ0XOOv27hWs4jH4MONgXxHMObTMSA==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", "dev": true, "dependencies": { "@types/d3-array": "^3.0.3", @@ -10587,14 +10475,14 @@ } }, "node_modules/vite": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.6.tgz", - "integrity": "sha512-yYIAZs9nVfRJ/AiOLCA91zzhjsHUgMjB+EigzFb6W2XTLO8JixBCKCjvhKZaye+NKYHCrkv3Oh50dH9EdLU2RA==", + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.8.tgz", + "integrity": "sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==", "dev": true, "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.35", - "rollup": "^4.2.0" + "esbuild": "^0.20.1", + "postcss": "^8.4.38", + "rollup": "^4.13.0" }, "bin": { "vite": "bin/vite.js" @@ -10642,9 +10530,9 @@ } }, "node_modules/vite-node": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.3.1.tgz", - "integrity": "sha512-azbRrqRxlWTJEVbzInZCTchx0X69M/XPTCz4H+TLvlTcR/xH/3hkRqhOakT41fMJCMzXTu4UvegkZiEoJAWvng==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.4.0.tgz", + "integrity": "sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==", "dev": true, "dependencies": { "cac": "^6.7.14", @@ -10760,16 +10648,16 @@ "dev": true }, "node_modules/vitest": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.3.1.tgz", - "integrity": "sha512-/1QJqXs8YbCrfv/GPQ05wAZf2eakUPLPa18vkJAKE7RXOKfVHqMZZ1WlTjiwl6Gcn65M5vpNUB6EFLnEdRdEXQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.4.0.tgz", + "integrity": "sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==", "dev": true, "dependencies": { - "@vitest/expect": "1.3.1", - "@vitest/runner": "1.3.1", - "@vitest/snapshot": "1.3.1", - "@vitest/spy": "1.3.1", - "@vitest/utils": "1.3.1", + "@vitest/expect": "1.4.0", + "@vitest/runner": "1.4.0", + "@vitest/snapshot": "1.4.0", + "@vitest/spy": "1.4.0", + "@vitest/utils": "1.4.0", "acorn-walk": "^8.3.2", "chai": "^4.3.10", "debug": "^4.3.4", @@ -10783,7 +10671,7 @@ "tinybench": "^2.5.1", "tinypool": "^0.8.2", "vite": "^5.0.0", - "vite-node": "1.3.1", + "vite-node": "1.4.0", "why-is-node-running": "^2.2.2" }, "bin": { @@ -10798,8 +10686,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "1.3.1", - "@vitest/ui": "1.3.1", + "@vitest/browser": "1.4.0", + "@vitest/ui": "1.4.0", "happy-dom": "*", "jsdom": "*" }, diff --git a/frontend/package.json b/frontend/package.json index 05f5e07e0..df5b6918a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,31 +19,31 @@ "@mantine/hooks": "^6.0.21", "@mantine/modals": "^6.0.21", "@mantine/notifications": "^6.0.21", - "axios": "^1.6.7", + "axios": "^1.6.8", "react": "^18.2.0", "react-dom": "^18.2.0", "react-query": "^3.39.3", "react-router-dom": "^6.22.3", - "socket.io-client": "^4.7.4" + "socket.io-client": "^4.7.5" }, "devDependencies": { "@fontsource/roboto": "^5.0.12", - "@fortawesome/fontawesome-svg-core": "^6.5.1", - "@fortawesome/free-brands-svg-icons": "^6.5.1", - "@fortawesome/free-regular-svg-icons": "^6.5.1", - "@fortawesome/free-solid-svg-icons": "^6.5.1", + "@fortawesome/fontawesome-svg-core": "^6.5.2", + "@fortawesome/free-brands-svg-icons": "^6.5.2", + "@fortawesome/free-regular-svg-icons": "^6.5.2", + "@fortawesome/free-solid-svg-icons": "^6.5.2", "@fortawesome/react-fontawesome": "^0.2.0", "@testing-library/jest-dom": "^6.4.2", - "@testing-library/react": "^14.2.1", + "@testing-library/react": "^15.0.5", "@testing-library/user-event": "^14.5.2", "@types/jest": "^29.5.12", "@types/lodash": "^4.17.0", - "@types/node": "^20.11.26", - "@types/react": "^18.2.65", - "@types/react-dom": "^18.2.21", - "@types/react-table": "^7.7.19", + "@types/node": "^20.12.6", + "@types/react": "^18.2.75", + "@types/react-dom": "^18.2.24", + "@types/react-table": "^7.7.20", "@vitejs/plugin-react": "^4.2.1", - "@vitest/coverage-v8": "^1.3.1", + "@vitest/coverage-v8": "^1.4.0", "@vitest/ui": "^1.2.2", "clsx": "^2.1.0", "eslint": "^8.57.0", @@ -53,15 +53,14 @@ "husky": "^9.0.11", "jsdom": "^24.0.0", "lodash": "^4.17.21", - "moment": "^2.30.1", "prettier": "^3.2.5", "prettier-plugin-organize-imports": "^3.2.4", "pretty-quick": "^4.0.0", "react-table": "^7.8.0", - "recharts": "^2.12.2", - "sass": "^1.71.1", - "typescript": "^5.4.2", - "vite": "^5.1.6", + "recharts": "^2.12.6", + "sass": "^1.74.1", + "typescript": "^5.4.4", + "vite": "^5.2.8", "vite-plugin-checker": "^0.6.4", "vitest": "^1.2.2", "yaml": "^2.4.1" @@ -77,7 +76,7 @@ "test:ui": "vitest --ui", "coverage": "vitest run --coverage", "format": "prettier -w .", - "prepare": "cd .. && husky install frontend/.husky" + "prepare": "cd .. && husky frontend/.husky" }, "browserslist": { "production": [ diff --git a/frontend/src/components/SubtitleToolsMenu.tsx b/frontend/src/components/SubtitleToolsMenu.tsx index 50509c3c2..e36a1e9e1 100644 --- a/frontend/src/components/SubtitleToolsMenu.tsx +++ b/frontend/src/components/SubtitleToolsMenu.tsx @@ -140,7 +140,7 @@ const SubtitleToolsMenu: FunctionComponent = ({ const disabledTools = selections.length === 0; return ( - + {children} Tools diff --git a/frontend/src/components/forms/MovieUploadForm.tsx b/frontend/src/components/forms/MovieUploadForm.tsx index fbcb0b04f..b51614770 100644 --- a/frontend/src/components/forms/MovieUploadForm.tsx +++ b/frontend/src/components/forms/MovieUploadForm.tsx @@ -19,6 +19,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Button, Checkbox, + createStyles, Divider, MantineColor, Stack, @@ -78,12 +79,21 @@ interface Props { onComplete?: () => void; } +const useStyles = createStyles((theme) => { + return { + wrapper: { + overflowWrap: "anywhere", + }, + }; +}); + const MovieUploadForm: FunctionComponent = ({ files, movie, onComplete, }) => { const modals = useModals(); + const { classes } = useStyles(); const profile = useLanguageProfileBy(movie.profileId); @@ -279,7 +289,7 @@ const MovieUploadForm: FunctionComponent = ({ modals.closeSelf(); })} > - + diff --git a/frontend/src/components/forms/ProfileEditForm.tsx b/frontend/src/components/forms/ProfileEditForm.tsx index 291e9660d..bfb5f2b13 100644 --- a/frontend/src/components/forms/ProfileEditForm.tsx +++ b/frontend/src/components/forms/ProfileEditForm.tsx @@ -86,7 +86,12 @@ const ProfileEditForm: FunctionComponent = ({ const itemCutoffOptions = useSelectorOptions( form.values.items, - (v) => v.language, + (v) => { + const suffix = + v.hi === "True" ? ":hi" : v.forced === "True" ? ":forced" : ""; + + return v.language + suffix; + }, (v) => String(v.id), ); diff --git a/frontend/src/components/forms/SeriesUploadForm.tsx b/frontend/src/components/forms/SeriesUploadForm.tsx index 784baf8a5..5ce9c821a 100644 --- a/frontend/src/components/forms/SeriesUploadForm.tsx +++ b/frontend/src/components/forms/SeriesUploadForm.tsx @@ -23,6 +23,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Button, Checkbox, + createStyles, Divider, MantineColor, Stack, @@ -85,12 +86,21 @@ interface Props { onComplete?: VoidFunction; } +const useStyles = createStyles((theme) => { + return { + wrapper: { + overflowWrap: "anywhere", + }, + }; +}); + const SeriesUploadForm: FunctionComponent = ({ series, files, onComplete, }) => { const modals = useModals(); + const { classes } = useStyles(); const episodes = useEpisodesBySeriesId(series.sonarrSeriesId); const episodeOptions = useSelectorOptions( episodes.data ?? [], @@ -358,7 +368,7 @@ const SeriesUploadForm: FunctionComponent = ({ modals.closeSelf(); })} > - + diff --git a/frontend/src/pages/Settings/Providers/components.tsx b/frontend/src/pages/Settings/Providers/components.tsx index 87abe7571..91076d7de 100644 --- a/frontend/src/pages/Settings/Providers/components.tsx +++ b/frontend/src/pages/Settings/Providers/components.tsx @@ -39,14 +39,24 @@ import { } from "../utilities/FormValues"; import { SettingsProvider, useSettings } from "../utilities/SettingsProvider"; import { useSettingValue } from "../utilities/hooks"; -import { ProviderInfo, ProviderList } from "./list"; +import { ProviderInfo } from "./list"; -const ProviderKey = "settings-general-enabled_providers"; +type SettingsKey = + | "settings-general-enabled_providers" + | "settings-general-enabled_integrations"; -export const ProviderView: FunctionComponent = () => { +interface ProviderViewProps { + availableOptions: Readonly; + settingsKey: SettingsKey; +} + +export const ProviderView: FunctionComponent = ({ + availableOptions, + settingsKey, +}) => { const settings = useSettings(); const staged = useStagedValues(); - const providers = useSettingValue(ProviderKey); + const providers = useSettingValue(settingsKey); const { update } = useFormActions(); @@ -61,17 +71,27 @@ export const ProviderView: FunctionComponent = () => { staged, settings, onChange: update, + availableOptions: availableOptions, + settingsKey: settingsKey, }); } }, - [modals, providers, settings, staged, update], + [ + modals, + providers, + settings, + staged, + update, + availableOptions, + settingsKey, + ], ); const cards = useMemo(() => { if (providers) { return providers .flatMap((v) => { - const item = ProviderList.find((inn) => inn.key === v); + const item = availableOptions.find((inn) => inn.key === v); if (item) { return item; } else { @@ -89,7 +109,7 @@ export const ProviderView: FunctionComponent = () => { } else { return []; } - }, [providers, select]); + }, [providers, select, availableOptions]); return ( @@ -106,6 +126,8 @@ interface ProviderToolProps { staged: LooseObject; settings: Settings; onChange: (v: LooseObject) => void; + availableOptions: Readonly; + settingsKey: Readonly; } const SelectItem = forwardRef< @@ -126,6 +148,8 @@ const ProviderTool: FunctionComponent = ({ staged, settings, onChange, + availableOptions, + settingsKey, }) => { const modals = useModals(); @@ -147,11 +171,11 @@ const ProviderTool: FunctionComponent = ({ if (idx !== -1) { const newProviders = [...enabledProviders]; newProviders.splice(idx, 1); - onChangeRef.current({ [ProviderKey]: newProviders }); + onChangeRef.current({ [settingsKey]: newProviders }); modals.closeAll(); } } - }, [payload, enabledProviders, modals]); + }, [payload, enabledProviders, modals, settingsKey]); const submit = useCallback( (values: FormValues) => { @@ -161,8 +185,7 @@ const ProviderTool: FunctionComponent = ({ // Add this provider if not exist if (enabledProviders.find((v) => v === info.key) === undefined) { - const newProviders = [...enabledProviders, info.key]; - changes[ProviderKey] = newProviders; + changes[settingsKey] = [...enabledProviders, info.key]; } // Apply submit hooks @@ -172,7 +195,7 @@ const ProviderTool: FunctionComponent = ({ modals.closeAll(); } }, - [info, enabledProviders, modals], + [info, enabledProviders, modals, settingsKey], ); const canSave = info !== null; @@ -188,18 +211,18 @@ const ProviderTool: FunctionComponent = ({ } }, []); - const availableOptions = useMemo( + const options = useMemo( () => - ProviderList.filter( + availableOptions.filter( (v) => enabledProviders?.find((p) => p === v.key && p !== info?.key) === undefined, ), - [info?.key, enabledProviders], + [info?.key, enabledProviders, availableOptions], ); - const options = useSelectorOptions( - availableOptions, + const selectorOptions = useSelectorOptions( + options, (v) => v.name ?? capitalize(v.key), ); @@ -289,7 +312,7 @@ const ProviderTool: FunctionComponent = ({ placeholder="Click to Select a Provider" itemComponent={SelectItem} disabled={payload !== null} - {...options} + {...selectorOptions} value={info} onChange={onSelect} > diff --git a/frontend/src/pages/Settings/Providers/index.tsx b/frontend/src/pages/Settings/Providers/index.tsx index bd8b648ff..ce855c4ee 100644 --- a/frontend/src/pages/Settings/Providers/index.tsx +++ b/frontend/src/pages/Settings/Providers/index.tsx @@ -11,12 +11,16 @@ import { Text, } from "../components"; import { ProviderView } from "./components"; +import { IntegrationList, ProviderList } from "./list"; const SettingsProvidersView: FunctionComponent = () => { return (
- +
{ Link to subscribe
+
+ +
); }; diff --git a/frontend/src/pages/Settings/Providers/list.ts b/frontend/src/pages/Settings/Providers/list.ts index 967526187..bb3f4e5a5 100644 --- a/frontend/src/pages/Settings/Providers/list.ts +++ b/frontend/src/pages/Settings/Providers/list.ts @@ -64,6 +64,21 @@ export const ProviderList: Readonly = [ }, ], }, + { + key: "animetosho", + name: "Anime Tosho", + description: + "Anime Tosho is a free, completely automated service which mirrors most torrents posted on TokyoTosho's anime category, Nyaa.si's English translated anime category and AniDex's anime category.", + inputs: [ + { + type: "text", + key: "search_threshold", + defaultValue: 6, + name: "Search Threshold. Increase if you often cannot find subtitles for your Anime. Note that increasing the value will decrease the performance of the search for each Episode.", + }, + ], + message: "Requires AniDB Integration.", + }, { key: "argenteam_dump", name: "Argenteam Dump", @@ -359,7 +374,6 @@ export const ProviderList: Readonly = [ { key: "subf2m", name: "subf2m.co", - description: "Subscene Alternative Provider", inputs: [ { type: "switch", @@ -391,20 +405,6 @@ export const ProviderList: Readonly = [ description: "Greek Subtitles Provider.\nRequires anti-captcha provider to solve captchas for each download.", }, - { - key: "subscene", - inputs: [ - { - type: "text", - key: "username", - }, - { - type: "password", - key: "password", - }, - ], - description: "Broken, may not work for some. Use subf2m instead.", - }, { key: "subscenter", description: "Hebrew Subtitles Provider" }, { key: "subsunacs", @@ -538,3 +538,24 @@ export const ProviderList: Readonly = [ description: "Chinese Subtitles Provider. Anti-captcha required.", }, ]; + +export const IntegrationList: Readonly = [ + { + key: "anidb", + name: "AniDB", + description: + "AniDB is non-profit database of anime information that is freely open to the public.", + inputs: [ + { + type: "text", + key: "api_client", + name: "API Client", + }, + { + type: "text", + key: "api_client_ver", + name: "API Client Version", + }, + ], + }, +]; diff --git a/frontend/src/pages/Settings/Subtitles/index.tsx b/frontend/src/pages/Settings/Subtitles/index.tsx index f6e0cae37..f2dec69ac 100644 --- a/frontend/src/pages/Settings/Subtitles/index.tsx +++ b/frontend/src/pages/Settings/Subtitles/index.tsx @@ -409,8 +409,7 @@ const SettingsSubtitlesView: FunctionComponent = () => { settingKey="settings-subsync-use_subsync" > - Enable automatic subtitles synchronization after downloading a - subtitle. + Enable automatic synchronization after downloading subtitles. { const update = useCallback(() => { const startTime = status?.start_time; if (startTime) { - const duration = moment.duration( - moment().utc().unix() - startTime, - "seconds", - ), - days = duration.days(), - hours = duration.hours().toString().padStart(2, "0"), - minutes = duration.minutes().toString().padStart(2, "0"), - seconds = duration.seconds().toString().padStart(2, "0"); - setUptime(days + "d " + hours + ":" + minutes + ":" + seconds); + // Current time in seconds + const currentTime = Math.floor(Date.now() / 1000); + + const uptimeInSeconds = currentTime - startTime; + + const uptime: string = formatTime(uptimeInSeconds, [ + { unit: "d", divisor: divisorDay }, + { unit: "h", divisor: divisorHour }, + { unit: "m", divisor: divisorMinute }, + { unit: "s", divisor: divisorSecond }, + ]); + + setUptime(uptime); } }, [status?.start_time]); diff --git a/frontend/src/types/settings.d.ts b/frontend/src/types/settings.d.ts index d46a9734e..9ae6d8454 100644 --- a/frontend/src/types/settings.d.ts +++ b/frontend/src/types/settings.d.ts @@ -20,7 +20,6 @@ interface Settings { xsubs: Settings.XSubs; assrt: Settings.Assrt; napisy24: Settings.Napisy24; - subscene: Settings.Subscene; betaseries: Settings.Betaseries; titlovi: Settings.Titlovi; ktuvit: Settings.Ktuvit; @@ -211,8 +210,6 @@ declare namespace Settings { interface Napisy24 extends BaseProvider {} - interface Subscene extends BaseProvider {} - interface Titlovi extends BaseProvider {} interface Ktuvit { diff --git a/frontend/src/utilities/time.test.ts b/frontend/src/utilities/time.test.ts new file mode 100644 index 000000000..a0e936a25 --- /dev/null +++ b/frontend/src/utilities/time.test.ts @@ -0,0 +1,60 @@ +import { + divisorDay, + divisorHour, + divisorMinute, + divisorSecond, + formatTime, +} from "./time"; + +describe("formatTime", () => { + it("should format day hour minute and second", () => { + const uptimeInSeconds = 3661; + + const formattedTime = formatTime(uptimeInSeconds, [ + { unit: "d", divisor: divisorDay }, + { unit: "h", divisor: divisorHour }, + { unit: "m", divisor: divisorMinute }, + { unit: "s", divisor: divisorSecond }, + ]); + + expect(formattedTime).toBe("0d 01:01:01"); + }); + + it("should format multiple digits of days", () => { + const uptimeInSeconds = 50203661; + + const formattedTime = formatTime(uptimeInSeconds, [ + { unit: "d", divisor: divisorDay }, + { unit: "h", divisor: divisorHour }, + { unit: "m", divisor: divisorMinute }, + { unit: "s", divisor: divisorSecond }, + ]); + + expect(formattedTime).toBe("581d 25:27:41"); + }); + + it("should format time day hour minute", () => { + const uptimeInSeconds = 3661; + + const formattedTime = formatTime(uptimeInSeconds, [ + { unit: "d", divisor: divisorDay }, + { unit: "h", divisor: divisorHour }, + { unit: "m", divisor: divisorMinute }, + ]); + + expect(formattedTime).toBe("0d 01:01"); + }); + + it("should format zero uptime", () => { + const uptimeInSeconds = 0; + + const formattedTime = formatTime(uptimeInSeconds, [ + { unit: "d", divisor: divisorDay }, + { unit: "h", divisor: divisorHour }, + { unit: "m", divisor: divisorMinute }, + { unit: "s", divisor: divisorSecond }, + ]); + + expect(formattedTime).toBe("0d 00:00:00"); + }); +}); diff --git a/frontend/src/utilities/time.ts b/frontend/src/utilities/time.ts new file mode 100644 index 000000000..54f93289f --- /dev/null +++ b/frontend/src/utilities/time.ts @@ -0,0 +1,29 @@ +interface TimeFormat { + unit: string; + divisor: number; +} + +export const divisorDay = 24 * 60 * 60; +export const divisorHour = 60 * 60; +export const divisorMinute = 60; +export const divisorSecond = 1; + +export const formatTime = ( + timeInSeconds: number, + formats: TimeFormat[], +): string => + formats.reduce( + (formattedTime: string, { unit, divisor }: TimeFormat, index: number) => { + const timeValue: number = + index === 0 + ? Math.floor(timeInSeconds / divisor) + : Math.floor(timeInSeconds / divisor) % 60; + return ( + formattedTime + + (index === 0 + ? `${timeValue}${unit} ` + : `${timeValue.toString().padStart(2, "0")}${index < formats.length - 1 ? ":" : ""}`) + ); + }, + "", + ); diff --git a/libs/apprise-1.7.4.dist-info/INSTALLER b/libs/apprise-1.7.6.dist-info/INSTALLER similarity index 100% rename from libs/apprise-1.7.4.dist-info/INSTALLER rename to libs/apprise-1.7.6.dist-info/INSTALLER diff --git a/libs/apprise-1.7.4.dist-info/LICENSE b/libs/apprise-1.7.6.dist-info/LICENSE similarity index 100% rename from libs/apprise-1.7.4.dist-info/LICENSE rename to libs/apprise-1.7.6.dist-info/LICENSE diff --git a/libs/apprise-1.7.4.dist-info/METADATA b/libs/apprise-1.7.6.dist-info/METADATA similarity index 97% rename from libs/apprise-1.7.4.dist-info/METADATA rename to libs/apprise-1.7.6.dist-info/METADATA index 1fbaf2a08..ac7cb9aac 100644 --- a/libs/apprise-1.7.4.dist-info/METADATA +++ b/libs/apprise-1.7.6.dist-info/METADATA @@ -1,12 +1,12 @@ Metadata-Version: 2.1 Name: apprise -Version: 1.7.4 +Version: 1.7.6 Summary: Push Notifications that work with just about every platform! Home-page: https://github.com/caronc/apprise Author: Chris Caron Author-email: lead2gold@gmail.com License: BSD -Keywords: Alerts Apprise API Automated Packet Reporting System AWS Boxcar BulkSMS BulkVS Burst SMS Chat CLI ClickSend D7Networks Dapnet DBus DingTalk Discord Email Emby Enigma2 Faast FCM Flock Form Gnome Google Chat Gotify Growl Guilded Home Assistant httpSMS IFTTT Join JSON Kavenegar KODI Kumulos LaMetric Line LunaSea MacOSX Mailgun Mastodon Matrix Mattermost MessageBird Microsoft Misskey MQTT MSG91 MSTeams Nextcloud NextcloudTalk Notica Notifiarr Notifico Ntfy Office365 OneSignal Opsgenie PagerDuty PagerTree ParsePlatform PopcornNotify Prowl PushBullet Pushed Pushjet PushMe Push Notifications Pushover PushSafer Pushy PushDeer Reddit Revolt Rocket.Chat RSyslog Ryver SendGrid ServerChan SES Signal SimplePush Sinch Slack SMSEagle SMS Manager SMTP2Go SNS SparkPost Streamlabs Stride Synology Chat Syslog Techulus Telegram Threema Gateway Twilio Twist Twitter Voipms Vonage Webex WeCom Bot WhatsApp Windows XBMC XML Zulip +Keywords: Alerts Apprise API Automated Packet Reporting System AWS Boxcar BulkSMS BulkVS Burst SMS Chantify Chat CLI ClickSend D7Networks Dapnet DBus DingTalk Discord Email Emby Enigma2 FCM Feishu Flock Form Free Mobile Gnome Google Chat Gotify Growl Guilded Home Assistant httpSMS IFTTT Join JSON Kavenegar KODI Kumulos LaMetric Line LunaSea MacOSX Mailgun Mastodon Matrix Mattermost MessageBird Microsoft Misskey MQTT MSG91 MSTeams Nextcloud NextcloudTalk Notica Notifiarr Notifico Ntfy Office365 OneSignal Opsgenie PagerDuty PagerTree ParsePlatform PopcornNotify Prowl PushBullet Pushed Pushjet PushMe Push Notifications Pushover PushSafer Pushy PushDeer Reddit Revolt Rocket.Chat RSyslog Ryver SendGrid ServerChan SES Signal SimplePush Sinch Slack SMSEagle SMS Manager SMTP2Go SNS SparkPost Streamlabs Stride Synology Chat Syslog Techulus Telegram Threema Gateway Twilio Twist Twitter Voipms Vonage Webex WeCom Bot WhatsApp Windows XBMC XML Zulip Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators @@ -20,6 +20,7 @@ Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: License :: OSI Approved :: BSD License @@ -98,11 +99,12 @@ The table below identifies the services this tool supports and some example serv | [AWS SES](https://github.com/caronc/apprise/wiki/Notify_ses) | ses:// | (TCP) 443 | ses://user@domain/AccessKeyID/AccessSecretKey/RegionName
ses://user@domain/AccessKeyID/AccessSecretKey/RegionName/email1/email2/emailN | [Bark](https://github.com/caronc/apprise/wiki/Notify_bark) | bark:// | (TCP) 80 or 443 | bark://hostname
bark://hostname/device_key
bark://hostname/device_key1/device_key2/device_keyN
barks://hostname
barks://hostname/device_key
barks://hostname/device_key1/device_key2/device_keyN | [Boxcar](https://github.com/caronc/apprise/wiki/Notify_boxcar) | boxcar:// | (TCP) 443 | boxcar://hostname
boxcar://hostname/@tag
boxcar://hostname/device_token
boxcar://hostname/device_token1/device_token2/device_tokenN
boxcar://hostname/@tag/@tag2/device_token +| [Chantify](https://github.com/caronc/apprise/wiki/Notify_chantify) | chantify:// | (TCP) 443 | chantify://token | [Discord](https://github.com/caronc/apprise/wiki/Notify_discord) | discord:// | (TCP) 443 | discord://webhook_id/webhook_token
discord://avatar@webhook_id/webhook_token | [Emby](https://github.com/caronc/apprise/wiki/Notify_emby) | emby:// or embys:// | (TCP) 8096 | emby://user@hostname/
emby://user:password@hostname | [Enigma2](https://github.com/caronc/apprise/wiki/Notify_enigma2) | enigma2:// or enigma2s:// | (TCP) 80 or 443 | enigma2://hostname -| [Faast](https://github.com/caronc/apprise/wiki/Notify_faast) | faast:// | (TCP) 443 | faast://authorizationtoken | [FCM](https://github.com/caronc/apprise/wiki/Notify_fcm) | fcm:// | (TCP) 443 | fcm://project@apikey/DEVICE_ID
fcm://project@apikey/#TOPIC
fcm://project@apikey/DEVICE_ID1/#topic1/#topic2/DEVICE_ID2/ +| [Feishu](https://github.com/caronc/apprise/wiki/Notify_feishu) | feishu:// | (TCP) 443 | feishu://token | [Flock](https://github.com/caronc/apprise/wiki/Notify_flock) | flock:// | (TCP) 443 | flock://token
flock://botname@token
flock://app_token/u:userid
flock://app_token/g:channel_id
flock://app_token/u:userid/g:channel_id | [Google Chat](https://github.com/caronc/apprise/wiki/Notify_googlechat) | gchat:// | (TCP) 443 | gchat://workspace/key/token | [Gotify](https://github.com/caronc/apprise/wiki/Notify_gotify) | gotify:// or gotifys:// | (TCP) 80 or 443 | gotify://hostname/token
gotifys://hostname/token?priority=high @@ -184,6 +186,7 @@ The table below identifies the services this tool supports and some example serv | [DAPNET](https://github.com/caronc/apprise/wiki/Notify_dapnet) | dapnet:// | (TCP) 80 | dapnet://user:pass@callsign
dapnet://user:pass@callsign1/callsign2/callsignN | [D7 Networks](https://github.com/caronc/apprise/wiki/Notify_d7networks) | d7sms:// | (TCP) 443 | d7sms://token@PhoneNo
d7sms://token@ToPhoneNo1/ToPhoneNo2/ToPhoneNoN | [DingTalk](https://github.com/caronc/apprise/wiki/Notify_dingtalk) | dingtalk:// | (TCP) 443 | dingtalk://token/
dingtalk://token/ToPhoneNo
dingtalk://token/ToPhoneNo1/ToPhoneNo2/ToPhoneNo1/ +| [Free-Mobile](https://github.com/caronc/apprise/wiki/Notify_freemobile) | freemobile:// | (TCP) 443 | freemobile://user@password/ [httpSMS](https://github.com/caronc/apprise/wiki/Notify_httpsms) | httpsms:// | (TCP) 443 | httpsms://ApiKey@FromPhoneNo
httpsms://ApiKey@FromPhoneNo/ToPhoneNo
httpsms://ApiKey@FromPhoneNo/ToPhoneNo1/ToPhoneNo2/ToPhoneNoN/ | [Kavenegar](https://github.com/caronc/apprise/wiki/Notify_kavenegar) | kavenegar:// | (TCP) 443 | kavenegar://ApiKey/ToPhoneNo
kavenegar://FromPhoneNo@ApiKey/ToPhoneNo
kavenegar://ApiKey/ToPhoneNo1/ToPhoneNo2/ToPhoneNoN | [MessageBird](https://github.com/caronc/apprise/wiki/Notify_messagebird) | msgbird:// | (TCP) 443 | msgbird://ApiKey/FromPhoneNo
msgbird://ApiKey/FromPhoneNo/ToPhoneNo
msgbird://ApiKey/FromPhoneNo/ToPhoneNo1/ToPhoneNo2/ToPhoneNoN/ diff --git a/libs/apprise-1.7.4.dist-info/RECORD b/libs/apprise-1.7.6.dist-info/RECORD similarity index 86% rename from libs/apprise-1.7.4.dist-info/RECORD rename to libs/apprise-1.7.6.dist-info/RECORD index ca5dcd003..250648105 100644 --- a/libs/apprise-1.7.4.dist-info/RECORD +++ b/libs/apprise-1.7.6.dist-info/RECORD @@ -1,12 +1,12 @@ ../../bin/apprise,sha256=ZJ-e4qqxNLtdW_DAvpuPPX5iROIiQd8I6nvg7vtAv-g,233 -apprise-1.7.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -apprise-1.7.4.dist-info/LICENSE,sha256=gt7qKBxRhVcdmXCYVtrWP6DtYjD0DzONet600dkU994,1343 -apprise-1.7.4.dist-info/METADATA,sha256=Lc66iPsSCFv0zmoQX8NFuc_V5CqFYN5Yrx_gqeN8OF8,44502 -apprise-1.7.4.dist-info/RECORD,, -apprise-1.7.4.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -apprise-1.7.4.dist-info/WHEEL,sha256=Xo9-1PvkuimrydujYJAjF7pCkriuXBpUPEjma1nZyJ0,92 -apprise-1.7.4.dist-info/entry_points.txt,sha256=71YypBuNdjAKiaLsiMG40HEfLHxkU4Mi7o_S0s0d8wI,45 -apprise-1.7.4.dist-info/top_level.txt,sha256=JrCRn-_rXw5LMKXkIgMSE4E0t1Ks9TYrBH54Pflwjkk,8 +apprise-1.7.6.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +apprise-1.7.6.dist-info/LICENSE,sha256=gt7qKBxRhVcdmXCYVtrWP6DtYjD0DzONet600dkU994,1343 +apprise-1.7.6.dist-info/METADATA,sha256=z_gaX2IdNJqw4T9q7AYQri9jcIs-OTGCo3t2EgEY-mw,44823 +apprise-1.7.6.dist-info/RECORD,, +apprise-1.7.6.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +apprise-1.7.6.dist-info/WHEEL,sha256=Xo9-1PvkuimrydujYJAjF7pCkriuXBpUPEjma1nZyJ0,92 +apprise-1.7.6.dist-info/entry_points.txt,sha256=71YypBuNdjAKiaLsiMG40HEfLHxkU4Mi7o_S0s0d8wI,45 +apprise-1.7.6.dist-info/top_level.txt,sha256=JrCRn-_rXw5LMKXkIgMSE4E0t1Ks9TYrBH54Pflwjkk,8 apprise/Apprise.py,sha256=Stm2NhJprWRaMwQfTiIQG_nR1bLpHi_zcdwEcsCpa-A,32865 apprise/Apprise.pyi,sha256=_4TBKvT-QVj3s6PuTh3YX-BbQMeJTdBGdVpubLMY4_k,2203 apprise/AppriseAsset.py,sha256=jRW8Y1EcAvjVA9h_mINmsjO4DM3S0aDl6INIFVMcUCs,11647 @@ -15,13 +15,13 @@ apprise/AppriseAttachment.py,sha256=vhrktSrp8GLr32aK4KqV6BX83IpI1lxZe-pGo1wiSFM, apprise/AppriseAttachment.pyi,sha256=R9-0dVqWpeaFrVpcREwPhGy3qHWztG5jEjYIOsbE5dM,1145 apprise/AppriseConfig.py,sha256=wfuR6Mb3ZLHvjvqWdFp9lVmjjDRWs65unY9qa92RkCg,16909 apprise/AppriseConfig.pyi,sha256=_mUlCnncqAq8sL01WxQTgZjnb2ic9kZXvtqZmVl-fc8,1568 -apprise/AppriseLocale.py,sha256=ISth7xC7M1WhsSNXdGZFouaA4bi07KP35m9RX-ExG48,8852 +apprise/AppriseLocale.py,sha256=4uSr4Nj_rz6ISMMAfRVRk58wZVLKOofJgk2x0_E8NkQ,8994 apprise/AttachmentManager.py,sha256=EwlnjuKn3fv_pioWcmMCkyDTsO178t6vkEOD8AjAPsw,2053 apprise/ConfigurationManager.py,sha256=MUmGajxjgnr6FGN7xb3q0nD0VVgdTdvapBBR7CsI-rc,2058 apprise/NotificationManager.py,sha256=ZJgkiCgcJ7Bz_6bwQ47flrcxvLMbA4Vbw0HG_yTsGdE,2041 -apprise/URLBase.py,sha256=ZWjHz69790EfVNDIBzWzRZzjw-gwC3db_t3_3an6cWI,28388 +apprise/URLBase.py,sha256=xRP0-blocp9UudYh04Hb3fIEmTZWJaTv_tzjrqaB9fg,29423 apprise/URLBase.pyi,sha256=WLaRREH7FzZ5x3-qkDkupojWGFC4uFwJ1EDt02lVs8c,520 -apprise/__init__.py,sha256=oBHq9Zbcwz9DTkurqnEhbu9Q79a0TdVAZrWFIhlk__8,3368 +apprise/__init__.py,sha256=ArtvoarAMnBcSfXF7L_hzq5CUJ9TUnHopiC7xafCe3c,3368 apprise/assets/NotifyXML-1.0.xsd,sha256=292qQ_IUl5EWDhPyzm9UTT0C2rVvJkyGar8jiODkJs8,986 apprise/assets/NotifyXML-1.1.xsd,sha256=bjR3CGG4AEXoJjYkGCbDttKHSkPP1FlIWO02E7G59g4,1758 apprise/assets/themes/default/apprise-failure-128x128.ico,sha256=Mt0ptfHJaN3Wsv5UCNDn9_3lyEDHxVDv1JdaDEI_xCA,67646 @@ -45,22 +45,22 @@ apprise/assets/themes/default/apprise-warning-128x128.png,sha256=pf5c4Ph7jWH7gf3 apprise/assets/themes/default/apprise-warning-256x256.png,sha256=SY-xlaiXaj420iEYKC2_fJxU-yj2SuaQg6xfPNi83bw,43708 apprise/assets/themes/default/apprise-warning-32x32.png,sha256=97R2ywNvcwczhBoWEIgajVtWjgT8fLs4FCCz4wu0dwc,2472 apprise/assets/themes/default/apprise-warning-72x72.png,sha256=L8moEInkO_OLxoOcuvN7rmrGZo64iJeH20o-24MQghE,7913 -apprise/attachment/AttachBase.py,sha256=ik3hRFnr8Z9bXt69P9Ej1VST4gQbnE0C_9WQvEE-72A,13592 +apprise/attachment/AttachBase.py,sha256=T3WreGrTsqqGplXJO36jm-N14X7ymSc9xt7XdTYuXVE,13656 apprise/attachment/AttachBase.pyi,sha256=w0XG_QKauiMLJ7eQ4S57IiLIURZHm_Snw7l6-ih9GP8,961 apprise/attachment/AttachFile.py,sha256=MbHY_av0GeM_AIBKV02Hq7SHiZ9eCr1yTfvDMUgi2I4,4765 -apprise/attachment/AttachHTTP.py,sha256=dyDy3U47cI28ENhaw1r5nQlGh8FWHZlHI8n9__k8wcY,11995 +apprise/attachment/AttachHTTP.py,sha256=_CMPp4QGLATfGO2-Nw57sxsQyed9z3ywgoB0vpK3KZk,13779 apprise/attachment/__init__.py,sha256=xabgXpvV05X-YRuqIt3uGYMXwYNXjHyF6Dwd8HfZCFE,1658 apprise/cli.py,sha256=h-pWSQPqQficH6J-OEp3MTGydWyt6vMYnDZvHCeAt4Y,20697 apprise/common.py,sha256=I6wfrndggCL7l7KAl7Cm4uwAX9n0l3SN4-BVvTE0L0M,5593 apprise/common.pyi,sha256=luF3QRiClDCk8Z23rI6FCGYsVmodOt_JYfYyzGogdNM,447 -apprise/config/ConfigBase.py,sha256=A4p_N9vSxOK37x9kuYeZFzHhAeEt-TCe2oweNi2KGg4,53062 +apprise/config/ConfigBase.py,sha256=d1efIuQFCJr66WgpudV2DWtxY3-tuZAyMAhHXBzJ8p0,53194 apprise/config/ConfigBase.pyi,sha256=cngfobwH6v2vxYbQrObDi5Z-t5wcquWF-wR0kBCr3Eg,54 apprise/config/ConfigFile.py,sha256=u_SDaN3OHMyaAq2X7k_T4_PRKkVsDwleqBz9YIN5lbA,6138 apprise/config/ConfigHTTP.py,sha256=Iy6Ji8_nX3xDjFgJGLrz4ftrMlMiyKiFGzYGJ7rMSMQ,9457 apprise/config/ConfigMemory.py,sha256=epEAgNy-eJVWoQaUOvjivMWxXTofy6wAQ-NbCqYmuyE,2829 apprise/config/__init__.py,sha256=lbsxrUpB1IYM2q7kjYhsXQGgPF-yZXJrKFE361tdIPY,1663 -apprise/conversion.py,sha256=bvTu-3TU2CPEhdroLRtd_XpDzzXqe_wyUql089IpYxs,6197 -apprise/decorators/CustomNotifyPlugin.py,sha256=F49vOM2EVy43Pn3j8z7tgTacweMUxGhw0UX-1n2Y3c8,7836 +apprise/conversion.py,sha256=0VZ0eCZfksN-97Vl0TjVjwnCTgus3XTRioceSFnP-gc,6277 +apprise/decorators/CustomNotifyPlugin.py,sha256=i4D-sgOsBWsxO5auWCN2bgXLLPuADaaLlJ1gUKLj2bU,7972 apprise/decorators/__init__.py,sha256=e_PDAm0kQNzwDPx-NJZLPfLMd2VAABvNZtxx_iDviRM,1487 apprise/decorators/notify.py,sha256=a2WupErNw1_SMAld7jPC273bskiChMpYy95BOog5A9w,5111 apprise/emojis.py,sha256=ONF0t8dY9f2XlEkLUG79-ybKVAj2GqbPj2-Be97vAoI,87738 @@ -69,21 +69,22 @@ apprise/i18n/en/LC_MESSAGES/apprise.mo,sha256=oUTuHREmLEYN07oqYqRMJ_kU71-o5o37Ns apprise/logger.py,sha256=131hqhed8cUj9x_mfXDEvwA2YbcYDFAYiWVK1HgxRVY,6921 apprise/manager.py,sha256=R9w8jxQRNy6Z_XDcobkt4JYbrC4jtj2OwRw9Zrib3CA,26857 apprise/plugins/NotifyAppriseAPI.py,sha256=ISBE0brD3eQdyw3XrGXd4Uc4kSYvIuI3SSUVCt-bkdo,16654 -apprise/plugins/NotifyAprs.py,sha256=IS1uxIl391L3i2LOK6x8xmlOG1W58k4o793Oq2W5Wao,24220 +apprise/plugins/NotifyAprs.py,sha256=xdL_aIVgb4ggxRFeCdkZAbgHYZ8DWLw9pRpLZQ0rHoE,25523 apprise/plugins/NotifyBark.py,sha256=bsDvKooRy4k1Gg7tvBjv3DIx7-WZiV_mbTrkTwMtd9Q,15698 -apprise/plugins/NotifyBase.py,sha256=9MB2uv4Rv8BnoXjU52k5Mv4YQppkNPv4Y_iPwauKxKQ,29716 +apprise/plugins/NotifyBase.py,sha256=G3xkF_a2BWqNSxsrnOW7NUgHjOqBCYC5zihCifWemo8,30360 apprise/plugins/NotifyBase.pyi,sha256=aKlZXRYUgG8lz_ZgGkYYJ_GKhuf18youTmMU-FlG7z8,21 apprise/plugins/NotifyBoxcar.py,sha256=vR00-WggHa1nHYWyb-f5P2V-G4f683fU_-GBlIeJvD0,12867 apprise/plugins/NotifyBulkSMS.py,sha256=stPWAFCfhBP617zYK9Dgk6pNJBN_WcyJtODzo0jR1QQ,16005 apprise/plugins/NotifyBulkVS.py,sha256=viLGeyUDiirRRM7CgRqqElHSLYFnMugDtWE6Ytjqfaw,13290 apprise/plugins/NotifyBurstSMS.py,sha256=cN2kRETKIK5LhwpQEA8C68LKv8KEUPmXYe-nTSegGls,15550 +apprise/plugins/NotifyChantify.py,sha256=GJJOAtSnVoIfKbJF_W1DTu7WsvS_zHdjO4T1XTKT87g,6673 apprise/plugins/NotifyClickSend.py,sha256=UfOJqsas6WLjQskojuJE7I_-lrb5QrkMiBZv-po_Q9c,11229 apprise/plugins/NotifyD7Networks.py,sha256=4E6Fh0kQoDlMMwgZJDOXky7c7KrdMMvqprcfm29scWU,15043 apprise/plugins/NotifyDBus.py,sha256=1eVJHIL3XkFjDePMqfcll35Ie1vxggJ1iBsVFAIaF00,14379 apprise/plugins/NotifyDapnet.py,sha256=KuXjBU0ZrIYtoDei85NeLZ-IP810T4w5oFXH9sWiSh0,13624 apprise/plugins/NotifyDingTalk.py,sha256=NJyETgN6QjtRqtxQjfBLFVuFpURyWykRftm6WpQJVbY,12009 apprise/plugins/NotifyDiscord.py,sha256=M_qmTzB7NNL5_agjYDX38KBN1jRzDBp2EMSNwEF_9Tw,26072 -apprise/plugins/NotifyEmail.py,sha256=DhAzLFX4pzzuS07QQFcv0VUOYu2PzQE7TTjlPokJcPY,38883 +apprise/plugins/NotifyEmail.py,sha256=Y_ZOrdK6hTUKHLvogKpV5VqD8byzDyDSvwIVmfdsC2g,39789 apprise/plugins/NotifyEmby.py,sha256=OMVO8XsVl_XCBYNNNQi8ni2lS4voLfU8Puk1xJOAvHs,24039 apprise/plugins/NotifyEnigma2.py,sha256=Hj0Q9YOeljSwbfiuMKLqXTVX_1g_mjNUGEts7wfrwno,11498 apprise/plugins/NotifyFCM/__init__.py,sha256=mBFtIgIJuLIFnMB5ndx5Makjs9orVMc2oLoD7LaVT48,21669 @@ -91,9 +92,10 @@ apprise/plugins/NotifyFCM/color.py,sha256=8iqDtadloQh2TMxkFmIFwenHqKp1pHHn1bwyWO apprise/plugins/NotifyFCM/common.py,sha256=978uBUoNdtopCtylipGiKQdsQ8FTONxkFBp7uJMZHc8,1718 apprise/plugins/NotifyFCM/oauth.py,sha256=Vvbd0-rd5BPIjAneG3rILU153JIzfSZ0kaDov6hm96M,11197 apprise/plugins/NotifyFCM/priority.py,sha256=0WuRW1y1HVnybgjlTeCZPHzt7j8SwWnC7faNcjioAOc,8163 -apprise/plugins/NotifyFaast.py,sha256=_F1633tQhk8gCfaNpZZm808f2G0S6fP0OOEetSiv0h8,6972 +apprise/plugins/NotifyFeishu.py,sha256=IpcABdLZJ1vcQdZHlmASVbNOiOCIrmgKFhz1hbdskY4,7266 apprise/plugins/NotifyFlock.py,sha256=0rUIa9nToGsO8BTUgixh8Z_qdVixJeH479UNYjcE4EM,12748 apprise/plugins/NotifyForm.py,sha256=38nL-2m1cf4gEQFQ4NpvA4j9i5_nNUgelReWFSjyV5U,17905 +apprise/plugins/NotifyFreeMobile.py,sha256=XCkgZLc3KKGlx_9UdeoMJVcHpeQrOml9T93S-DGf4bs,6644 apprise/plugins/NotifyGnome.py,sha256=8MXTa8gZg1wTgNJfLlmq7_fl3WaYK-SX6VR91u308C4,9059 apprise/plugins/NotifyGoogleChat.py,sha256=lnoN17m6lZANaXcElDTP8lcuVWjIZEK8C6_iqJNAnw4,12622 apprise/plugins/NotifyGotify.py,sha256=DNlOIHyuYitO5use9oa_REPm2Fant7y9QSaatrZFNI0,10551 @@ -109,7 +111,7 @@ apprise/plugins/NotifyKumulos.py,sha256=eCEW2ZverZqETOLHVWMC4E8Ll6rEhhEWOSD73RD8 apprise/plugins/NotifyLametric.py,sha256=h8vZoX-Ll5NBZRprBlxTO2H9w0lOiMxglGvUgJtK4_8,37534 apprise/plugins/NotifyLine.py,sha256=OVI0ozMJcq_-dI8dodVX52dzUzgENlAbOik-Kw4l-rI,10676 apprise/plugins/NotifyLunaSea.py,sha256=woN8XdkwAjhgxAXp7Zj4XsWLybNL80l4W3Dx5BvobZg,14459 -apprise/plugins/NotifyMQTT.py,sha256=PFLwESgR8dMZvVFHxmOZ8xfy-YqyX5b2kl_e8Z1lo-0,19537 +apprise/plugins/NotifyMQTT.py,sha256=cnuG4f3bYYNPhEj9qDX8SLmnxLVT9G1b8J5w6-mQGKY,19545 apprise/plugins/NotifyMSG91.py,sha256=P7JPyT1xmucnaEeCZPf_6aJfe1gS_STYYwEM7hJ7QBw,12677 apprise/plugins/NotifyMSTeams.py,sha256=dFH575hoLL3zRddbBKfozlYjxvPJGbj3BKvfJSIkvD0,22976 apprise/plugins/NotifyMacOSX.py,sha256=y2fGpSZXomFiNwKbWImrXQUMVM4JR4uPCnsWpnxQrFA,8271 @@ -124,7 +126,7 @@ apprise/plugins/NotifyNextcloudTalk.py,sha256=dLl_g7Knq5PVcadbzDuQsxbGHTZlC4r-pQ apprise/plugins/NotifyNotica.py,sha256=yHmk8HiNFjzoI4Gewo_nBRrx9liEmhT95k1d10wqhYg,12990 apprise/plugins/NotifyNotifiarr.py,sha256=ADwLJO9eenfLkNa09tXMGSBTM4c3zTY0SEePvyB8WYA,15857 apprise/plugins/NotifyNotifico.py,sha256=Qe9jMN_M3GL4XlYIWkAf-w_Hf65g9Hde4bVuytGhUW4,12035 -apprise/plugins/NotifyNtfy.py,sha256=TkDs6jOc30XQn2O2BJ14-nE_cohPdJiSS8DpYXc9hoE,27953 +apprise/plugins/NotifyNtfy.py,sha256=AtJt2zH35mMQTwRDxKia93NPy6-4rtixplP53zIYV2M,27979 apprise/plugins/NotifyOffice365.py,sha256=8TxsVsdbUghmNj0kceMlmoZzTOKQTgn3priI8JuRuHE,25190 apprise/plugins/NotifyOneSignal.py,sha256=gsw7ckW7xLiJDRUb7eJHNe_4bvdBXmt6_YsB1u_ghjw,18153 apprise/plugins/NotifyOpsgenie.py,sha256=zJWpknjoHq35Iv9w88ucR62odaeIN3nrGFPtYnhDdjA,20515 @@ -144,7 +146,7 @@ apprise/plugins/NotifyPushy.py,sha256=mmWcnu905Fvc8ihYXvZ7lVYErGZH5Q-GbBNS20v5r4 apprise/plugins/NotifyRSyslog.py,sha256=W42LT90X65-pNoU7KdhdX1PBcmsz9RyV376CDa_H3CI,11982 apprise/plugins/NotifyReddit.py,sha256=E78OSyDQfUalBEcg71sdMsNBOwdj7cVBnELrhrZEAXY,25785 apprise/plugins/NotifyRevolt.py,sha256=DRA9Xylwl6leVjVFuJcP4L1cG49CIBtnQdxh4BKnAZ4,14500 -apprise/plugins/NotifyRocketChat.py,sha256=GTEfT-upQ56tJgE0kuc59l4uQGySj_d15wjdcARR9Ko,24624 +apprise/plugins/NotifyRocketChat.py,sha256=Cb_nasX0-G3FoPMYvNk55RJ-tHuXUCTLUn2wTSi4IcI,25738 apprise/plugins/NotifyRyver.py,sha256=yhHPMLGeJtcHwBKSPPk0OBfp59DgTvXio1R59JhrJu4,11823 apprise/plugins/NotifySES.py,sha256=wtRmpAZkS5mQma6sdiaPT6U1xcgoj77CB9mNFvSEAw8,33545 apprise/plugins/NotifySMSEagle.py,sha256=voFNqOewD9OC1eRctD0YdUB_ZSWsb06rjUwBfCcxPYA,24161 @@ -162,7 +164,7 @@ apprise/plugins/NotifyStreamlabs.py,sha256=lx3N8T2ufUWFYIZ-kU_rOv50YyGWBqLSCKk7x apprise/plugins/NotifySynology.py,sha256=_jTqfgWeOuSi_I8geMOraHBVFtDkvm9mempzymrmeAo,11105 apprise/plugins/NotifySyslog.py,sha256=J9Kain2bb-PDNiG5Ydb0q678cYjNE_NjZFqMG9oEXM0,10617 apprise/plugins/NotifyTechulusPush.py,sha256=m43_Qj1scPcgCRX5Dr2Ul7nxMbaiVxNzm_HRuNmfgoA,7253 -apprise/plugins/NotifyTelegram.py,sha256=Bim4mmPcefHNpvbNSy3pmLuCXRw5IVVWUNUB1SkIhDM,35624 +apprise/plugins/NotifyTelegram.py,sha256=XE7PC9LRzcrfE2bpLKyor5lO_7B9LS4Xw1UlUmA4a2A,37187 apprise/plugins/NotifyThreema.py,sha256=C_C3j0fJWgeF2uB7ceJFXOdC6Lt0TFBInFMs5Xlg04M,11885 apprise/plugins/NotifyTwilio.py,sha256=WCo8eTI9OF1rtg3ueHHRDXt4Lp45eZ6h3IdTZVf5HM8,15976 apprise/plugins/NotifyTwist.py,sha256=nZA73CYVe-p0tkVMy5q3vFRyflLM4yjUo9LECvkUwgc,28841 @@ -175,7 +177,7 @@ apprise/plugins/NotifyWhatsApp.py,sha256=PtzW0ue3d2wZ8Pva_LG29jUcpRRP03TFxO5SME_ apprise/plugins/NotifyWindows.py,sha256=QgWJfJF8AE6RWr-L81YYVZNWrnImK9Qr3B991HWanqU,8563 apprise/plugins/NotifyXBMC.py,sha256=5hDuOTP3Kwtp4NEMaokNjWyEKEkQcN_fSx-cUPJvhaU,12096 apprise/plugins/NotifyXML.py,sha256=WJnmdvXseuTRgioVMRqpR8a09cDfTpPTfuFlTnT_TfI,16973 -apprise/plugins/NotifyZulip.py,sha256=mbZoPiQXFbcaJ5UYDbkX4HJPAvRzPEAB-rsOlF9SD4o,13755 +apprise/plugins/NotifyZulip.py,sha256=M8cSL7nZvtBYyTX6045g34tyn2vyybltgD1CoI4Xa7A,13968 apprise/plugins/__init__.py,sha256=jTfLmW47kZC_Wf5eFFta2NoD2J-7_E7JaPrrVMIECkU,18725 apprise/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 apprise/utils.py,sha256=SjRU2tb1UsVnTCTXPUyXVz3WpRbDWwAHH-d3ll38EHY,53185 diff --git a/libs/apprise-1.7.6.dist-info/REQUESTED b/libs/apprise-1.7.6.dist-info/REQUESTED new file mode 100644 index 000000000..e69de29bb diff --git a/libs/apprise-1.7.4.dist-info/WHEEL b/libs/apprise-1.7.6.dist-info/WHEEL similarity index 100% rename from libs/apprise-1.7.4.dist-info/WHEEL rename to libs/apprise-1.7.6.dist-info/WHEEL diff --git a/libs/apprise-1.7.4.dist-info/entry_points.txt b/libs/apprise-1.7.6.dist-info/entry_points.txt similarity index 100% rename from libs/apprise-1.7.4.dist-info/entry_points.txt rename to libs/apprise-1.7.6.dist-info/entry_points.txt diff --git a/libs/apprise-1.7.4.dist-info/top_level.txt b/libs/apprise-1.7.6.dist-info/top_level.txt similarity index 100% rename from libs/apprise-1.7.4.dist-info/top_level.txt rename to libs/apprise-1.7.6.dist-info/top_level.txt diff --git a/libs/apprise/AppriseLocale.py b/libs/apprise/AppriseLocale.py index cb9512e5e..e900ce5bc 100644 --- a/libs/apprise/AppriseLocale.py +++ b/libs/apprise/AppriseLocale.py @@ -219,6 +219,9 @@ class AppriseLocale: try: # Acquire our locale lang = locale.getlocale()[0] + # Compatibility for Python >= 3.12 + if lang == 'C': + lang = AppriseLocale._default_language except (ValueError, TypeError) as e: # This occurs when an invalid locale was parsed from the diff --git a/libs/apprise/URLBase.py b/libs/apprise/URLBase.py index 2467a4c1e..90ea85c66 100644 --- a/libs/apprise/URLBase.py +++ b/libs/apprise/URLBase.py @@ -669,6 +669,79 @@ class URLBase: 'verify': 'yes' if self.verify_certificate else 'no', } + @staticmethod + def post_process_parse_url_results(results): + """ + After parsing the URL, this function applies a bit of extra logic to + support extra entries like `pass` becoming `password`, etc + + This function assumes that parse_url() was called previously setting + up the basics to be checked + """ + + # if our URL ends with an 's', then assume our secure flag is set. + results['secure'] = (results['schema'][-1] == 's') + + # QSD Checking (over-rides all) + qsd_exists = True if isinstance(results.get('qsd'), dict) else False + + if qsd_exists and 'verify' in results['qsd']: + # Pulled from URL String + results['verify'] = parse_bool( + results['qsd'].get('verify', True)) + + elif 'verify' in results: + # Pulled from YAML Configuratoin + results['verify'] = parse_bool(results.get('verify', True)) + + else: + # Support SSL Certificate 'verify' keyword. Default to being + # enabled + results['verify'] = True + + # Password overrides + if 'pass' in results: + results['password'] = results['pass'] + del results['pass'] + + if qsd_exists: + if 'password' in results['qsd']: + results['password'] = results['qsd']['password'] + if 'pass' in results['qsd']: + results['password'] = results['qsd']['pass'] + + # User overrides + if 'user' in results['qsd']: + results['user'] = results['qsd']['user'] + + # parse_url() always creates a 'password' and 'user' entry in the + # results returned. Entries are set to None if they weren't + # specified + if results['password'] is None and 'user' in results['qsd']: + # Handle cases where the user= provided in 2 locations, we want + # the original to fall back as a being a password (if one + # wasn't otherwise defined) e.g. + # mailtos://PASSWORD@hostname?user=admin@mail-domain.com + # - in the above, the PASSWORD gets lost in the parse url() + # since a user= over-ride is specified. + presults = parse_url(results['url']) + if presults: + # Store our Password + results['password'] = presults['user'] + + # Store our socket read timeout if specified + if 'rto' in results['qsd']: + results['rto'] = results['qsd']['rto'] + + # Store our socket connect timeout if specified + if 'cto' in results['qsd']: + results['cto'] = results['qsd']['cto'] + + if 'port' in results['qsd']: + results['port'] = results['qsd']['port'] + + return results + @staticmethod def parse_url(url, verify_host=True, plus_to_space=False, strict_port=False): @@ -698,53 +771,7 @@ class URLBase: # We're done; we failed to parse our url return results - # if our URL ends with an 's', then assume our secure flag is set. - results['secure'] = (results['schema'][-1] == 's') - - # Support SSL Certificate 'verify' keyword. Default to being enabled - results['verify'] = True - - if 'verify' in results['qsd']: - results['verify'] = parse_bool( - results['qsd'].get('verify', True)) - - # Password overrides - if 'password' in results['qsd']: - results['password'] = results['qsd']['password'] - if 'pass' in results['qsd']: - results['password'] = results['qsd']['pass'] - - # User overrides - if 'user' in results['qsd']: - results['user'] = results['qsd']['user'] - - # parse_url() always creates a 'password' and 'user' entry in the - # results returned. Entries are set to None if they weren't specified - if results['password'] is None and 'user' in results['qsd']: - # Handle cases where the user= provided in 2 locations, we want - # the original to fall back as a being a password (if one wasn't - # otherwise defined) - # e.g. - # mailtos://PASSWORD@hostname?user=admin@mail-domain.com - # - the PASSWORD gets lost in the parse url() since a user= - # over-ride is specified. - presults = parse_url(results['url']) - if presults: - # Store our Password - results['password'] = presults['user'] - - # Store our socket read timeout if specified - if 'rto' in results['qsd']: - results['rto'] = results['qsd']['rto'] - - # Store our socket connect timeout if specified - if 'cto' in results['qsd']: - results['cto'] = results['qsd']['cto'] - - if 'port' in results['qsd']: - results['port'] = results['qsd']['port'] - - return results + return URLBase.post_process_parse_url_results(results) @staticmethod def http_response_code_lookup(code, response_mask=None): diff --git a/libs/apprise/__init__.py b/libs/apprise/__init__.py index bb18eaec8..81373c75b 100644 --- a/libs/apprise/__init__.py +++ b/libs/apprise/__init__.py @@ -27,7 +27,7 @@ # POSSIBILITY OF SUCH DAMAGE. __title__ = 'Apprise' -__version__ = '1.7.4' +__version__ = '1.7.6' __author__ = 'Chris Caron' __license__ = 'BSD' __copywrite__ = 'Copyright (C) 2024 Chris Caron ' diff --git a/libs/apprise/attachment/AttachBase.py b/libs/apprise/attachment/AttachBase.py index 062e553d7..8cb6bd5cb 100644 --- a/libs/apprise/attachment/AttachBase.py +++ b/libs/apprise/attachment/AttachBase.py @@ -253,7 +253,7 @@ class AttachBase(URLBase): return self.detected_mimetype \ if self.detected_mimetype else self.unknown_mimetype - def exists(self): + def exists(self, retrieve_if_missing=True): """ Simply returns true if the object has downloaded and stored the attachment AND the attachment has not expired. @@ -282,7 +282,7 @@ class AttachBase(URLBase): # The file is not present pass - return self.download() + return False if not retrieve_if_missing else self.download() def invalidate(self): """ diff --git a/libs/apprise/attachment/AttachHTTP.py b/libs/apprise/attachment/AttachHTTP.py index 719ebc625..5a3af9467 100644 --- a/libs/apprise/attachment/AttachHTTP.py +++ b/libs/apprise/attachment/AttachHTTP.py @@ -29,6 +29,7 @@ import re import os import requests +import threading from tempfile import NamedTemporaryFile from .AttachBase import AttachBase from ..common import ContentLocation @@ -56,6 +57,9 @@ class AttachHTTP(AttachBase): # Web based requests are remote/external to our current location location = ContentLocation.HOSTED + # thread safe loading + _lock = threading.Lock() + def __init__(self, headers=None, **kwargs): """ Initialize HTTP Object @@ -96,9 +100,6 @@ class AttachHTTP(AttachBase): # our content is inaccessible return False - # Ensure any existing content set has been invalidated - self.invalidate() - # prepare header headers = { 'User-Agent': self.app_id, @@ -117,134 +118,154 @@ class AttachHTTP(AttachBase): url += self.fullpath - self.logger.debug('HTTP POST URL: %s (cert_verify=%r)' % ( - url, self.verify_certificate, - )) - # Where our request object will temporarily live. r = None # Always call throttle before any remote server i/o is made self.throttle() - try: - # Make our request - with requests.get( - url, - headers=headers, - auth=auth, - params=self.qsd, - verify=self.verify_certificate, - timeout=self.request_timeout, - stream=True) as r: - - # Handle Errors - r.raise_for_status() - - # Get our file-size (if known) - try: - file_size = int(r.headers.get('Content-Length', '0')) - except (TypeError, ValueError): - # Handle edge case where Content-Length is a bad value - file_size = 0 - - # Perform a little Q/A on file limitations and restrictions - if self.max_file_size > 0 and file_size > self.max_file_size: - - # The content retrieved is to large - self.logger.error( - 'HTTP response exceeds allowable maximum file length ' - '({}KB): {}'.format( - int(self.max_file_size / 1024), - self.url(privacy=True))) - - # Return False (signifying a failure) - return False - - # Detect config format based on mime if the format isn't - # already enforced - self.detected_mimetype = r.headers.get('Content-Type') - - d = r.headers.get('Content-Disposition', '') - result = re.search( - "filename=['\"]?(?P[^'\"]+)['\"]?", d, re.I) - if result: - self.detected_name = result.group('name').strip() - - # Create a temporary file to work with - self._temp_file = NamedTemporaryFile() - - # Get our chunk size - chunk_size = self.chunk_size - - # Track all bytes written to disk - bytes_written = 0 - - # If we get here, we can now safely write our content to disk - for chunk in r.iter_content(chunk_size=chunk_size): - # filter out keep-alive chunks - if chunk: - self._temp_file.write(chunk) - bytes_written = self._temp_file.tell() - - # Prevent a case where Content-Length isn't provided - # we don't want to fetch beyond our limits - if self.max_file_size > 0: - if bytes_written > self.max_file_size: - # The content retrieved is to large - self.logger.error( - 'HTTP response exceeds allowable maximum ' - 'file length ({}KB): {}'.format( - int(self.max_file_size / 1024), - self.url(privacy=True))) - - # Invalidate any variables previously set - self.invalidate() - - # Return False (signifying a failure) - return False - - elif bytes_written + chunk_size \ - > self.max_file_size: - # Adjust out next read to accomodate up to our - # limit +1. This will prevent us from readig - # to much into our memory buffer - self.max_file_size - bytes_written + 1 - - # Ensure our content is flushed to disk for post-processing - self._temp_file.flush() - - # Set our minimum requirements for a successful download() call - self.download_path = self._temp_file.name - if not self.detected_name: - self.detected_name = os.path.basename(self.fullpath) - - except requests.RequestException as e: - self.logger.error( - 'A Connection error occurred retrieving HTTP ' - 'configuration from %s.' % self.host) - self.logger.debug('Socket Exception: %s' % str(e)) - - # Invalidate any variables previously set - self.invalidate() - - # Return False (signifying a failure) - return False - - except (IOError, OSError): - # IOError is present for backwards compatibility with Python - # versions older then 3.3. >= 3.3 throw OSError now. - - # Could not open and/or write the temporary file - self.logger.error( - 'Could not write attachment to disk: {}'.format( - self.url(privacy=True))) + with self._lock: + if self.exists(retrieve_if_missing=False): + # Due to locking; it's possible a concurrent thread already + # handled the retrieval in which case we can safely move on + self.logger.trace( + 'HTTP Attachment %s already retrieved', + self._temp_file.name) + return True - # Invalidate any variables previously set + # Ensure any existing content set has been invalidated self.invalidate() - # Return False (signifying a failure) - return False + self.logger.debug( + 'HTTP Attachment Fetch URL: %s (cert_verify=%r)' % ( + url, self.verify_certificate)) + + try: + # Make our request + with requests.get( + url, + headers=headers, + auth=auth, + params=self.qsd, + verify=self.verify_certificate, + timeout=self.request_timeout, + stream=True) as r: + + # Handle Errors + r.raise_for_status() + + # Get our file-size (if known) + try: + file_size = int(r.headers.get('Content-Length', '0')) + except (TypeError, ValueError): + # Handle edge case where Content-Length is a bad value + file_size = 0 + + # Perform a little Q/A on file limitations and restrictions + if self.max_file_size > 0 and \ + file_size > self.max_file_size: + + # The content retrieved is to large + self.logger.error( + 'HTTP response exceeds allowable maximum file ' + 'length ({}KB): {}'.format( + int(self.max_file_size / 1024), + self.url(privacy=True))) + + # Return False (signifying a failure) + return False + + # Detect config format based on mime if the format isn't + # already enforced + self.detected_mimetype = r.headers.get('Content-Type') + + d = r.headers.get('Content-Disposition', '') + result = re.search( + "filename=['\"]?(?P[^'\"]+)['\"]?", d, re.I) + if result: + self.detected_name = result.group('name').strip() + + # Create a temporary file to work with; delete must be set + # to False or it isn't compatible with Microsoft Windows + # instances. In lieu of this, __del__ will clean up the + # file for us. + self._temp_file = NamedTemporaryFile(delete=False) + + # Get our chunk size + chunk_size = self.chunk_size + + # Track all bytes written to disk + bytes_written = 0 + + # If we get here, we can now safely write our content to + # disk + for chunk in r.iter_content(chunk_size=chunk_size): + # filter out keep-alive chunks + if chunk: + self._temp_file.write(chunk) + bytes_written = self._temp_file.tell() + + # Prevent a case where Content-Length isn't + # provided. In this case we don't want to fetch + # beyond our limits + if self.max_file_size > 0: + if bytes_written > self.max_file_size: + # The content retrieved is to large + self.logger.error( + 'HTTP response exceeds allowable ' + 'maximum file length ' + '({}KB): {}'.format( + int(self.max_file_size / 1024), + self.url(privacy=True))) + + # Invalidate any variables previously set + self.invalidate() + + # Return False (signifying a failure) + return False + + elif bytes_written + chunk_size \ + > self.max_file_size: + # Adjust out next read to accomodate up to + # our limit +1. This will prevent us from + # reading to much into our memory buffer + self.max_file_size - bytes_written + 1 + + # Ensure our content is flushed to disk for post-processing + self._temp_file.flush() + + # Set our minimum requirements for a successful download() + # call + self.download_path = self._temp_file.name + if not self.detected_name: + self.detected_name = os.path.basename(self.fullpath) + + except requests.RequestException as e: + self.logger.error( + 'A Connection error occurred retrieving HTTP ' + 'configuration from %s.' % self.host) + self.logger.debug('Socket Exception: %s' % str(e)) + + # Invalidate any variables previously set + self.invalidate() + + # Return False (signifying a failure) + return False + + except (IOError, OSError): + # IOError is present for backwards compatibility with Python + # versions older then 3.3. >= 3.3 throw OSError now. + + # Could not open and/or write the temporary file + self.logger.error( + 'Could not write attachment to disk: {}'.format( + self.url(privacy=True))) + + # Invalidate any variables previously set + self.invalidate() + + # Return False (signifying a failure) + return False # Return our success return True @@ -254,11 +275,30 @@ class AttachHTTP(AttachBase): Close our temporary file """ if self._temp_file: + self.logger.trace( + 'Attachment cleanup of %s', self._temp_file.name) self._temp_file.close() + + try: + # Ensure our file is removed (if it exists) + os.unlink(self._temp_file.name) + + except OSError: + pass + + # Reset our temporary file to prevent from entering + # this block again self._temp_file = None super().invalidate() + def __del__(self): + """ + Tidy memory if open + """ + with self._lock: + self.invalidate() + def url(self, privacy=False, *args, **kwargs): """ Returns the URL built dynamically based on specified arguments. diff --git a/libs/apprise/config/ConfigBase.py b/libs/apprise/config/ConfigBase.py index 731945256..32e1bde34 100644 --- a/libs/apprise/config/ConfigBase.py +++ b/libs/apprise/config/ConfigBase.py @@ -1184,6 +1184,9 @@ class ConfigBase(URLBase): # Prepare our Asset Object _results['asset'] = asset + # Handle post processing of result set + _results = URLBase.post_process_parse_url_results(_results) + # Store our preloaded entries preloaded.append({ 'results': _results, diff --git a/libs/apprise/conversion.py b/libs/apprise/conversion.py index 86e967bc4..4d5632f59 100644 --- a/libs/apprise/conversion.py +++ b/libs/apprise/conversion.py @@ -58,8 +58,8 @@ def markdown_to_html(content): """ Converts specified content from markdown to HTML. """ - - return markdown(content) + return markdown(content, extensions=[ + 'markdown.extensions.nl2br', 'markdown.extensions.tables']) def text_to_html(content): diff --git a/libs/apprise/decorators/CustomNotifyPlugin.py b/libs/apprise/decorators/CustomNotifyPlugin.py index 4c93ef1bd..eb5f17b78 100644 --- a/libs/apprise/decorators/CustomNotifyPlugin.py +++ b/libs/apprise/decorators/CustomNotifyPlugin.py @@ -147,6 +147,10 @@ class CustomNotifyPlugin(NotifyBase): self._default_args = {} + # Some variables do not need to be set + if 'secure' in kwargs: + del kwargs['secure'] + # Apply our updates based on what was parsed dict_full_update(self._default_args, self._base_args) dict_full_update(self._default_args, kwargs) diff --git a/libs/apprise/plugins/NotifyAprs.py b/libs/apprise/plugins/NotifyAprs.py index c56982a70..5d8c3c100 100644 --- a/libs/apprise/plugins/NotifyAprs.py +++ b/libs/apprise/plugins/NotifyAprs.py @@ -203,6 +203,13 @@ class NotifyAprs(NotifyBase): "type": "string", "map_to": "targets", }, + "delay": { + "name": _("Resend Delay"), + "type": "float", + "min": 0.0, + "max": 5.0, + "default": 0.0, + }, "locale": { "name": _("Locale"), "type": "choice:string", @@ -212,7 +219,7 @@ class NotifyAprs(NotifyBase): } ) - def __init__(self, targets=None, locale=None, **kwargs): + def __init__(self, targets=None, locale=None, delay=None, **kwargs): """ Initialize APRS Object """ @@ -272,6 +279,28 @@ class NotifyAprs(NotifyBase): self.logger.warning(msg) raise TypeError(msg) + # Update our delay + if delay is None: + self.delay = NotifyAprs.template_args["delay"]["default"] + + else: + try: + self.delay = float(delay) + if self.delay < NotifyAprs.template_args["delay"]["min"]: + raise ValueError() + + elif self.delay >= NotifyAprs.template_args["delay"]["max"]: + raise ValueError() + + except (TypeError, ValueError): + msg = "Unsupported APRS-IS delay ({}) specified. ".format( + delay) + self.logger.warning(msg) + raise TypeError(msg) + + # Bump up our request_rate + self.request_rate_per_sec += self.delay + # Set the transmitter group self.locale = \ NotifyAprs.template_args["locale"]["default"] \ @@ -674,6 +703,10 @@ class NotifyAprs(NotifyBase): # Store our locale if not default params['locale'] = self.locale + if self.delay != NotifyAprs.template_args["delay"]["default"]: + # Store our locale if not default + params['delay'] = "{:.2f}".format(self.delay) + # Extend our parameters params.update(self.url_parameters(privacy=privacy, *args, **kwargs)) @@ -727,6 +760,10 @@ class NotifyAprs(NotifyBase): # All entries after the hostname are additional targets results["targets"].extend(NotifyAprs.split_path(results["fullpath"])) + # Get Delay (if set) + if 'delay' in results['qsd'] and len(results['qsd']['delay']): + results['delay'] = NotifyAprs.unquote(results['qsd']['delay']) + # Support the 'to' variable so that we can support rooms this way too # The 'to' makes it easier to use yaml configuration if "to" in results["qsd"] and len(results["qsd"]["to"]): diff --git a/libs/apprise/plugins/NotifyBase.py b/libs/apprise/plugins/NotifyBase.py index 6daa8aa1d..c29417c60 100644 --- a/libs/apprise/plugins/NotifyBase.py +++ b/libs/apprise/plugins/NotifyBase.py @@ -457,6 +457,19 @@ class NotifyBase(URLBase): # Handle situations where the title is None title = '' if not title else title + # Truncate flag set with attachments ensures that only 1 + # attachment passes through. In the event there could be many + # services specified, we only want to do this logic once. + # The logic is only applicable if ther was more then 1 attachment + # specified + overflow = self.overflow_mode if overflow is None else overflow + if attach and len(attach) > 1 and overflow == OverflowMode.TRUNCATE: + # Save first attachment + _attach = AppriseAttachment(attach[0], asset=self.asset) + else: + # reference same attachment + _attach = attach + # Apply our overflow (if defined) for chunk in self._apply_overflow( body=body, title=title, overflow=overflow, @@ -465,7 +478,7 @@ class NotifyBase(URLBase): # Send notification yield dict( body=chunk['body'], title=chunk['title'], - notify_type=notify_type, attach=attach, + notify_type=notify_type, attach=_attach, body_format=body_format ) @@ -485,7 +498,7 @@ class NotifyBase(URLBase): }, { title: 'the title goes here', - body: 'the message body goes here', + body: 'the continued message body goes here', }, ] diff --git a/libs/apprise/plugins/NotifyFaast.py b/libs/apprise/plugins/NotifyChantify.py similarity index 57% rename from libs/apprise/plugins/NotifyFaast.py rename to libs/apprise/plugins/NotifyChantify.py index f82c44020..d912bd257 100644 --- a/libs/apprise/plugins/NotifyFaast.py +++ b/libs/apprise/plugins/NotifyChantify.py @@ -26,118 +26,111 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. +# Chantify +# 1. Visit https://chanify.net/ + +# The API URL will look something like this: +# https://api.chanify.net/v1/sender/token +# + import requests from .NotifyBase import NotifyBase -from ..common import NotifyImageSize from ..common import NotifyType -from ..utils import parse_bool -from ..AppriseLocale import gettext_lazy as _ from ..utils import validate_regex +from ..AppriseLocale import gettext_lazy as _ -class NotifyFaast(NotifyBase): +class NotifyChantify(NotifyBase): """ - A wrapper for Faast Notifications + A wrapper for Chantify Notifications """ # The default descriptive name associated with the Notification - service_name = 'Faast' + service_name = _('Chantify') # The services URL - service_url = 'http://www.faast.io/' + service_url = 'https://chanify.net/' - # The default protocol (this is secure for faast) - protocol = 'faast' + # The default secure protocol + secure_protocol = 'chantify' # A URL that takes you to the setup/help of the specific protocol - setup_url = 'https://github.com/caronc/apprise/wiki/Notify_faast' - - # Faast uses the http protocol with JSON requests - notify_url = 'https://www.appnotifications.com/account/notifications.json' + setup_url = 'https://github.com/caronc/apprise/wiki/Notify_chantify' - # Allows the user to specify the NotifyImageSize object - image_size = NotifyImageSize.XY_72 + # Notification URL + notify_url = 'https://api.chanify.net/v1/sender/{token}/' # Define object templates templates = ( - '{schema}://{authtoken}', + '{schema}://{token}', ) - # Define our template tokens + # The title is not used + title_maxlen = 0 + + # Define our tokens; these are the minimum tokens required required to + # be passed into this function (as arguments). The syntax appends any + # previously defined in the base package and builds onto them template_tokens = dict(NotifyBase.template_tokens, **{ - 'authtoken': { - 'name': _('Authorization Token'), + 'token': { + 'name': _('Token'), 'type': 'string', 'private': True, 'required': True, + 'regex': (r'^[A-Z0-9_-]+$', 'i'), }, }) # Define our template arguments template_args = dict(NotifyBase.template_args, **{ - 'image': { - 'name': _('Include Image'), - 'type': 'bool', - 'default': True, - 'map_to': 'include_image', + 'token': { + 'alias_of': 'token', }, }) - def __init__(self, authtoken, include_image=True, **kwargs): + def __init__(self, token, **kwargs): """ - Initialize Faast Object + Initialize Chantify Object """ super().__init__(**kwargs) - # Store the Authentication Token - self.authtoken = validate_regex(authtoken) - if not self.authtoken: - msg = 'An invalid Faast Authentication Token ' \ - '({}) was specified.'.format(authtoken) + self.token = validate_regex( + token, *self.template_tokens['token']['regex']) + if not self.token: + msg = 'The Chantify token specified ({}) is invalid.'\ + .format(token) self.logger.warning(msg) raise TypeError(msg) - # Associate an image with our post - self.include_image = include_image - return def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs): """ - Perform Faast Notification + Send our notification """ + # prepare our headers headers = { 'User-Agent': self.app_id, - 'Content-Type': 'multipart/form-data' + 'Content-Type': 'application/x-www-form-urlencoded', } - # prepare JSON Object + # Our Message payload = { - 'user_credentials': self.authtoken, - 'title': title, - 'message': body, + 'text': body } - # Acquire our image if we're configured to do so - image_url = None if not self.include_image \ - else self.image_url(notify_type) - - if image_url: - payload['icon_url'] = image_url - - self.logger.debug('Faast POST URL: %s (cert_verify=%r)' % ( - self.notify_url, self.verify_certificate, - )) - self.logger.debug('Faast Payload: %s' % str(payload)) + self.logger.debug('Chantify GET URL: %s (cert_verify=%r)' % ( + self.notify_url, self.verify_certificate)) + self.logger.debug('Chantify Payload: %s' % str(payload)) # Always call throttle before any remote server i/o is made self.throttle() try: r = requests.post( - self.notify_url, + self.notify_url.format(token=self.token), data=payload, headers=headers, verify=self.verify_certificate, @@ -146,10 +139,10 @@ class NotifyFaast(NotifyBase): if r.status_code != requests.codes.ok: # We had a problem status_str = \ - NotifyFaast.http_response_code_lookup(r.status_code) + NotifyChantify.http_response_code_lookup(r.status_code) self.logger.warning( - 'Failed to send Faast notification:' + 'Failed to send Chantify notification: ' '{}{}error={}.'.format( status_str, ', ' if status_str else '', @@ -161,12 +154,12 @@ class NotifyFaast(NotifyBase): return False else: - self.logger.info('Sent Faast notification.') + self.logger.info('Sent Chantify notification.') except requests.RequestException as e: self.logger.warning( - 'A Connection error occurred sending Faast notification.', - ) + 'A Connection error occurred sending Chantify ' + 'notification.') self.logger.debug('Socket Exception: %s' % str(e)) # Return; we're done @@ -179,18 +172,13 @@ class NotifyFaast(NotifyBase): Returns the URL built dynamically based on specified arguments. """ - # Define any URL parameters - params = { - 'image': 'yes' if self.include_image else 'no', - } - - # Extend our parameters - params.update(self.url_parameters(privacy=privacy, *args, **kwargs)) + # Prepare our parameters + params = self.url_parameters(privacy=privacy, *args, **kwargs) - return '{schema}://{authtoken}/?{params}'.format( - schema=self.protocol, - authtoken=self.pprint(self.authtoken, privacy, safe=''), - params=NotifyFaast.urlencode(params), + return '{schema}://{token}/?{params}'.format( + schema=self.secure_protocol, + token=self.pprint(self.token, privacy, safe=''), + params=NotifyChantify.urlencode(params), ) @staticmethod @@ -200,16 +188,19 @@ class NotifyFaast(NotifyBase): us to re-instantiate this object. """ + + # parse_url already handles getting the `user` and `password` fields + # populated. results = NotifyBase.parse_url(url, verify_host=False) if not results: # We're done early as we couldn't load the results return results - # Store our authtoken using the host - results['authtoken'] = NotifyFaast.unquote(results['host']) + # Allow over-ride + if 'token' in results['qsd'] and len(results['qsd']['token']): + results['token'] = NotifyChantify.unquote(results['qsd']['token']) - # Include image with our post - results['include_image'] = \ - parse_bool(results['qsd'].get('image', True)) + else: + results['token'] = NotifyChantify.unquote(results['host']) return results diff --git a/libs/apprise/plugins/NotifyEmail.py b/libs/apprise/plugins/NotifyEmail.py index e3ecde3f6..80f88bf61 100644 --- a/libs/apprise/plugins/NotifyEmail.py +++ b/libs/apprise/plugins/NotifyEmail.py @@ -45,7 +45,7 @@ from .NotifyBase import NotifyBase from ..URLBase import PrivacyMode from ..common import NotifyFormat, NotifyType from ..conversion import convert_between -from ..utils import is_email, parse_emails +from ..utils import is_email, parse_emails, is_hostname from ..AppriseLocale import gettext_lazy as _ from ..logger import logger @@ -566,12 +566,20 @@ class NotifyEmail(NotifyBase): # Apply any defaults based on certain known configurations self.NotifyEmailDefaults(secure_mode=secure_mode, **kwargs) - if self.user and self.host: - # Prepare the bases of our email - self.from_addr = [self.app_id, '{}@{}'.format( - re.split(r'[\s@]+', self.user)[0], - self.host, - )] + if self.user: + if self.host: + # Prepare the bases of our email + self.from_addr = [self.app_id, '{}@{}'.format( + re.split(r'[\s@]+', self.user)[0], + self.host, + )] + + else: + result = is_email(self.user) + if result: + # Prepare the bases of our email and include domain + self.host = result['domain'] + self.from_addr = [self.app_id, self.user] if from_addr: result = is_email(from_addr) @@ -1037,11 +1045,25 @@ class NotifyEmail(NotifyBase): us to re-instantiate this object. """ - results = NotifyBase.parse_url(url) + results = NotifyBase.parse_url(url, verify_host=False) if not results: # We're done early as we couldn't load the results return results + # Prepare our target lists + results['targets'] = [] + + if not is_hostname(results['host'], ipv4=False, ipv6=False, + underscore=False): + + if is_email(NotifyEmail.unquote(results['host'])): + # Don't lose defined email addresses + results['targets'].append(NotifyEmail.unquote(results['host'])) + + # Detect if we have a valid hostname or not; be sure to reset it's + # value if invalid; we'll attempt to figure this out later on + results['host'] = '' + # The From address is a must; either through the use of templates # from= entry and/or merging the user and hostname together, this # must be calculated or parse_url will fail. @@ -1052,7 +1074,7 @@ class NotifyEmail(NotifyBase): # Get our potential email targets; if none our found we'll just # add one to ourselves - results['targets'] = NotifyEmail.split_path(results['fullpath']) + results['targets'] += NotifyEmail.split_path(results['fullpath']) # Attempt to detect 'to' email address if 'to' in results['qsd'] and len(results['qsd']['to']): diff --git a/libs/apprise/plugins/NotifyFeishu.py b/libs/apprise/plugins/NotifyFeishu.py new file mode 100644 index 000000000..e6988333c --- /dev/null +++ b/libs/apprise/plugins/NotifyFeishu.py @@ -0,0 +1,231 @@ +# -*- coding: utf-8 -*- +# BSD 2-Clause License +# +# Apprise - Push Notification Library. +# Copyright (c) 2024, Chris Caron +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 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. +# +# 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. + +# Feishu +# 1. Visit https://open.feishu.cn + +# Custom Bot Setup +# https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot +# + +import requests +from json import dumps + +from .NotifyBase import NotifyBase +from ..common import NotifyType +from ..utils import validate_regex +from ..AppriseLocale import gettext_lazy as _ + + +class NotifyFeishu(NotifyBase): + """ + A wrapper for Feishu Notifications + """ + + # The default descriptive name associated with the Notification + service_name = _('Feishu') + + # The services URL + service_url = 'https://open.feishu.cn/' + + # The default secure protocol + secure_protocol = 'feishu' + + # A URL that takes you to the setup/help of the specific protocol + setup_url = 'https://github.com/caronc/apprise/wiki/Notify_feishu' + + # Notification URL + notify_url = 'https://open.feishu.cn/open-apis/bot/v2/hook/{token}/' + + # Define object templates + templates = ( + '{schema}://{token}', + ) + + # The title is not used + title_maxlen = 0 + + # Limit is documented to be 20K message sizes. This number safely + # allows padding around that size. + body_maxlen = 19985 + + # Define our tokens; these are the minimum tokens required required to + # be passed into this function (as arguments). The syntax appends any + # previously defined in the base package and builds onto them + template_tokens = dict(NotifyBase.template_tokens, **{ + 'token': { + 'name': _('Token'), + 'type': 'string', + 'private': True, + 'required': True, + 'regex': (r'^[A-Z0-9_-]+$', 'i'), + }, + }) + + # Define our template arguments + template_args = dict(NotifyBase.template_args, **{ + 'token': { + 'alias_of': 'token', + }, + }) + + def __init__(self, token, **kwargs): + """ + Initialize Feishu Object + """ + super().__init__(**kwargs) + + self.token = validate_regex( + token, *self.template_tokens['token']['regex']) + if not self.token: + msg = 'The Feishu token specified ({}) is invalid.'\ + .format(token) + self.logger.warning(msg) + raise TypeError(msg) + + return + + def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs): + """ + Send our notification + """ + + # prepare our headers + headers = { + 'User-Agent': self.app_id, + 'Content-Type': "application/json", + } + + # Our Message + payload = { + 'msg_type': 'text', + "content": { + "text": body, + } + } + + self.logger.debug('Feishu GET URL: %s (cert_verify=%r)' % ( + self.notify_url, self.verify_certificate)) + self.logger.debug('Feishu Payload: %s' % str(payload)) + + # Always call throttle before any remote server i/o is made + self.throttle() + + try: + r = requests.post( + self.notify_url.format(token=self.token), + data=dumps(payload).encode('utf-8'), + headers=headers, + verify=self.verify_certificate, + timeout=self.request_timeout, + ) + + # + # Sample Responses + # + # Valid: + # { + # "code": 0, + # "data": {}, + # "msg": "success" + # } + + # Invalid (non 200 response): + # { + # "code": 9499, + # "msg": "Bad Request", + # "data": {} + # } + if r.status_code != requests.codes.ok: + # We had a problem + status_str = \ + NotifyFeishu.http_response_code_lookup(r.status_code) + + self.logger.warning( + 'Failed to send Feishu notification: ' + '{}{}error={}.'.format( + status_str, + ', ' if status_str else '', + r.status_code)) + + self.logger.debug('Response Details:\r\n{}'.format(r.content)) + + # Return; we're done + return False + + else: + self.logger.info('Sent Feishu notification.') + + except requests.RequestException as e: + self.logger.warning( + 'A Connection error occurred sending Feishu ' + 'notification.') + self.logger.debug('Socket Exception: %s' % str(e)) + + # Return; we're done + return False + + return True + + def url(self, privacy=False, *args, **kwargs): + """ + Returns the URL built dynamically based on specified arguments. + """ + + # Prepare our parameters + params = self.url_parameters(privacy=privacy, *args, **kwargs) + + return '{schema}://{token}/?{params}'.format( + schema=self.secure_protocol, + token=self.pprint(self.token, privacy, safe=''), + params=NotifyFeishu.urlencode(params), + ) + + @staticmethod + def parse_url(url): + """ + Parses the URL and returns enough arguments that can allow + us to re-instantiate this object. + + """ + + # parse_url already handles getting the `user` and `password` fields + # populated. + results = NotifyBase.parse_url(url, verify_host=False) + if not results: + # We're done early as we couldn't load the results + return results + + # Allow over-ride + if 'token' in results['qsd'] and len(results['qsd']['token']): + results['token'] = NotifyFeishu.unquote(results['qsd']['token']) + + else: + results['token'] = NotifyFeishu.unquote(results['host']) + + return results diff --git a/libs/apprise/plugins/NotifyFreeMobile.py b/libs/apprise/plugins/NotifyFreeMobile.py new file mode 100644 index 000000000..4aad8db3d --- /dev/null +++ b/libs/apprise/plugins/NotifyFreeMobile.py @@ -0,0 +1,204 @@ +# -*- coding: utf-8 -*- +# BSD 2-Clause License +# +# Apprise - Push Notification Library. +# Copyright (c) 2024, Chris Caron +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 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. +# +# 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. + +# Free Mobile +# 1. Visit https://mobile.free.fr/ + +# the URL will look something like this: +# https://smsapi.free-mobile.fr/sendmsg +# + +import requests +from json import dumps + +from .NotifyBase import NotifyBase +from ..common import NotifyType +from ..AppriseLocale import gettext_lazy as _ + + +class NotifyFreeMobile(NotifyBase): + """ + A wrapper for Free-Mobile Notifications + """ + + # The default descriptive name associated with the Notification + service_name = _('Free-Mobile') + + # The services URL + service_url = 'https://mobile.free.fr/' + + # The default secure protocol + secure_protocol = 'freemobile' + + # A URL that takes you to the setup/help of the specific protocol + setup_url = 'https://github.com/caronc/apprise/wiki/Notify_freemobile' + + # Plain Text Notification URL + notify_url = 'https://smsapi.free-mobile.fr/sendmsg' + + # Define object templates + templates = ( + '{schema}://{user}@{password}', + ) + + # The title is not used + title_maxlen = 0 + + # SMS Messages are restricted in size + body_maxlen = 160 + + # Define our tokens; these are the minimum tokens required required to + # be passed into this function (as arguments). The syntax appends any + # previously defined in the base package and builds onto them + template_tokens = dict(NotifyBase.template_tokens, **{ + 'user': { + 'name': _('Username'), + 'type': 'string', + 'required': True, + }, + 'password': { + 'name': _('Password'), + 'type': 'string', + 'private': True, + 'required': True, + }, + }) + + def __init__(self, **kwargs): + """ + Initialize Free Mobile Object + """ + super().__init__(**kwargs) + + if not (self.user and self.password): + msg = 'A FreeMobile user and password ' \ + 'combination was not provided.' + self.logger.warning(msg) + raise TypeError(msg) + + return + + def url(self, privacy=False, *args, **kwargs): + """ + Returns the URL built dynamically based on specified arguments. + """ + + # Prepare our parameters + params = self.url_parameters(privacy=privacy, *args, **kwargs) + + return '{schema}://{user}@{password}/?{params}'.format( + schema=self.secure_protocol, + user=self.user, + password=self.pprint(self.password, privacy, safe=''), + params=NotifyFreeMobile.urlencode(params), + ) + + def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs): + """ + Send our notification + """ + + # prepare our headers + headers = { + 'User-Agent': self.app_id, + } + + # Prepare our payload + payload = { + 'user': self.user, + 'pass': self.password, + 'msg': body + } + + self.logger.debug('Free Mobile GET URL: %s (cert_verify=%r)' % ( + self.notify_url, self.verify_certificate)) + self.logger.debug('Free Mobile Payload: %s' % str(payload)) + + # Always call throttle before any remote server i/o is made + self.throttle() + + try: + r = requests.post( + self.notify_url, + data=dumps(payload).encode('utf-8'), + headers=headers, + verify=self.verify_certificate, + timeout=self.request_timeout, + ) + if r.status_code != requests.codes.ok: + # We had a problem + status_str = \ + NotifyFreeMobile.http_response_code_lookup(r.status_code) + + self.logger.warning( + 'Failed to send Free Mobile notification: ' + '{}{}error={}.'.format( + status_str, + ', ' if status_str else '', + r.status_code)) + + self.logger.debug('Response Details:\r\n{}'.format(r.content)) + + # Return; we're done + return False + + else: + self.logger.info('Sent Free Mobile notification.') + + except requests.RequestException as e: + self.logger.warning( + 'A Connection error occurred sending Free Mobile ' + 'notification.') + self.logger.debug('Socket Exception: %s' % str(e)) + + # Return; we're done + return False + + return True + + @staticmethod + def parse_url(url): + """ + Parses the URL and returns enough arguments that can allow + us to re-instantiate this object. + + """ + + # parse_url already handles getting the `user` and `password` fields + # populated. + results = NotifyBase.parse_url(url, verify_host=False) + if not results: + # We're done early as we couldn't load the results + return results + + # The hostname can act as the password if specified and a password + # was otherwise not (specified): + if not results.get('password'): + results['password'] = NotifyFreeMobile.unquote(results['host']) + + return results diff --git a/libs/apprise/plugins/NotifyMQTT.py b/libs/apprise/plugins/NotifyMQTT.py index 4e159b662..49380d330 100644 --- a/libs/apprise/plugins/NotifyMQTT.py +++ b/libs/apprise/plugins/NotifyMQTT.py @@ -89,7 +89,7 @@ class NotifyMQTT(NotifyBase): requirements = { # Define our required packaging in order to work - 'packages_required': 'paho-mqtt' + 'packages_required': 'paho-mqtt < 2.0.0' } # The default descriptive name associated with the Notification diff --git a/libs/apprise/plugins/NotifyNtfy.py b/libs/apprise/plugins/NotifyNtfy.py index 138c3fca7..40834eece 100644 --- a/libs/apprise/plugins/NotifyNtfy.py +++ b/libs/apprise/plugins/NotifyNtfy.py @@ -698,7 +698,7 @@ class NotifyNtfy(NotifyBase): """ Returns the number of targets associated with this notification """ - return len(self.topics) + return 1 if not self.topics else len(self.topics) @staticmethod def parse_url(url): diff --git a/libs/apprise/plugins/NotifyRocketChat.py b/libs/apprise/plugins/NotifyRocketChat.py index 8e6c0751c..9011a5e71 100644 --- a/libs/apprise/plugins/NotifyRocketChat.py +++ b/libs/apprise/plugins/NotifyRocketChat.py @@ -59,6 +59,9 @@ class RocketChatAuthMode: # providing a webhook WEBHOOK = "webhook" + # Support token submission + TOKEN = "token" + # Providing a username and password (default) BASIC = "basic" @@ -66,6 +69,7 @@ class RocketChatAuthMode: # Define our authentication modes ROCKETCHAT_AUTH_MODES = ( RocketChatAuthMode.WEBHOOK, + RocketChatAuthMode.TOKEN, RocketChatAuthMode.BASIC, ) @@ -107,6 +111,8 @@ class NotifyRocketChat(NotifyBase): templates = ( '{schema}://{user}:{password}@{host}:{port}/{targets}', '{schema}://{user}:{password}@{host}/{targets}', + '{schema}://{user}:{token}@{host}:{port}/{targets}', + '{schema}://{user}:{token}@{host}/{targets}', '{schema}://{webhook}@{host}', '{schema}://{webhook}@{host}:{port}', '{schema}://{webhook}@{host}/{targets}', @@ -135,6 +141,11 @@ class NotifyRocketChat(NotifyBase): 'type': 'string', 'private': True, }, + 'token': { + 'name': _('API Token'), + 'map_to': 'password', + 'private': True, + }, 'webhook': { 'name': _('Webhook'), 'type': 'string', @@ -230,13 +241,20 @@ class NotifyRocketChat(NotifyBase): if self.webhook is not None: # Just a username was specified, we treat this as a webhook self.mode = RocketChatAuthMode.WEBHOOK + elif self.password and len(self.password) > 32: + self.mode = RocketChatAuthMode.TOKEN else: self.mode = RocketChatAuthMode.BASIC - if self.mode == RocketChatAuthMode.BASIC \ + self.logger.debug( + "Auto-Detected Rocketchat Auth Mode: %s", self.mode) + + if self.mode in (RocketChatAuthMode.BASIC, RocketChatAuthMode.TOKEN) \ and not (self.user and self.password): # Username & Password is required for Rocket Chat to work - msg = 'No Rocket.Chat user/pass combo was specified.' + msg = 'No Rocket.Chat {} was specified.'.format( + 'user/pass combo' if self.mode == RocketChatAuthMode.BASIC else + 'user/apikey') self.logger.warning(msg) raise TypeError(msg) @@ -245,6 +263,13 @@ class NotifyRocketChat(NotifyBase): self.logger.warning(msg) raise TypeError(msg) + if self.mode == RocketChatAuthMode.TOKEN: + # Set our headers for further communication + self.headers.update({ + 'X-User-Id': self.user, + 'X-Auth-Token': self.password, + }) + # Validate recipients and drop bad ones: for recipient in parse_list(targets): result = IS_CHANNEL.match(recipient) @@ -309,12 +334,13 @@ class NotifyRocketChat(NotifyBase): params.update(self.url_parameters(privacy=privacy, *args, **kwargs)) # Determine Authentication - if self.mode == RocketChatAuthMode.BASIC: + if self.mode in (RocketChatAuthMode.BASIC, RocketChatAuthMode.TOKEN): auth = '{user}:{password}@'.format( user=NotifyRocketChat.quote(self.user, safe=''), password=self.pprint( self.password, privacy, mode=PrivacyMode.Secret, safe=''), ) + else: auth = '{user}{webhook}@'.format( user='{}:'.format(NotifyRocketChat.quote(self.user, safe='')) @@ -359,8 +385,11 @@ class NotifyRocketChat(NotifyBase): # Call the _send_ function applicable to whatever mode we're in # - calls _send_webhook_notification if the mode variable is set # - calls _send_basic_notification if the mode variable is not set - return getattr(self, '_send_{}_notification'.format(self.mode))( - body=body, title=title, notify_type=notify_type, **kwargs) + return getattr(self, '_send_{}_notification'.format( + RocketChatAuthMode.WEBHOOK + if self.mode == RocketChatAuthMode.WEBHOOK + else RocketChatAuthMode.BASIC))( + body=body, title=title, notify_type=notify_type, **kwargs) def _send_webhook_notification(self, body, title='', notify_type=NotifyType.INFO, **kwargs): @@ -412,7 +441,7 @@ class NotifyRocketChat(NotifyBase): """ # Track whether we authenticated okay - if not self.login(): + if self.mode == RocketChatAuthMode.BASIC and not self.login(): return False # prepare JSON Object @@ -432,9 +461,7 @@ class NotifyRocketChat(NotifyBase): channel = channels.pop(0) payload['channel'] = channel - if not self._send( - payload, notify_type=notify_type, headers=self.headers, - **kwargs): + if not self._send(payload, notify_type=notify_type, **kwargs): # toggle flag has_error = True @@ -447,15 +474,14 @@ class NotifyRocketChat(NotifyBase): room = rooms.pop(0) payload['roomId'] = room - if not self._send( - payload, notify_type=notify_type, headers=self.headers, - **kwargs): + if not self._send(payload, notify_type=notify_type, **kwargs): # toggle flag has_error = True - # logout - self.logout() + if self.mode == RocketChatAuthMode.BASIC: + # logout + self.logout() return not has_error @@ -476,7 +502,7 @@ class NotifyRocketChat(NotifyBase): return payload def _send(self, payload, notify_type, path='api/v1/chat.postMessage', - headers={}, **kwargs): + **kwargs): """ Perform Notify Rocket.Chat Notification """ @@ -487,6 +513,9 @@ class NotifyRocketChat(NotifyBase): api_url, self.verify_certificate)) self.logger.debug('Rocket.Chat Payload: %s' % str(payload)) + # Copy our existing headers + headers = self.headers.copy() + # Apply minimum headers headers.update({ 'User-Agent': self.app_id, diff --git a/libs/apprise/plugins/NotifyTelegram.py b/libs/apprise/plugins/NotifyTelegram.py index dbea79b1a..cce8af625 100644 --- a/libs/apprise/plugins/NotifyTelegram.py +++ b/libs/apprise/plugins/NotifyTelegram.py @@ -82,6 +82,34 @@ IS_CHAT_ID_RE = re.compile( ) +class TelegramMarkdownVersion: + """ + Telegram Markdown Version + """ + # Classic (Original Telegram Markdown) + ONE = 'MARKDOWN' + + # Supports strikethrough and many other items + TWO = 'MarkdownV2' + + +TELEGRAM_MARKDOWN_VERSION_MAP = { + # v1 + "v1": TelegramMarkdownVersion.ONE, + "1": TelegramMarkdownVersion.ONE, + # v2 + "v2": TelegramMarkdownVersion.TWO, + "2": TelegramMarkdownVersion.TWO, + "default": TelegramMarkdownVersion.TWO, +} + +TELEGRAM_MARKDOWN_VERSIONS = { + # Note: This also acts as a reverse lookup mapping + TelegramMarkdownVersion.ONE: 'v1', + TelegramMarkdownVersion.TWO: 'v2', +} + + class TelegramContentPlacement: """ The Telegram Content Placement @@ -333,6 +361,12 @@ class NotifyTelegram(NotifyBase): 'name': _('Topic Thread ID'), 'type': 'int', }, + 'mdv': { + 'name': _('Markdown Version'), + 'type': 'choice:string', + 'values': ('v1', 'v2'), + 'default': 'v2', + }, 'to': { 'alias_of': 'targets', }, @@ -346,7 +380,7 @@ class NotifyTelegram(NotifyBase): def __init__(self, bot_token, targets, detect_owner=True, include_image=False, silent=None, preview=None, topic=None, - content=None, **kwargs): + content=None, mdv=None, **kwargs): """ Initialize Telegram Object """ @@ -361,6 +395,17 @@ class NotifyTelegram(NotifyBase): self.logger.warning(err) raise TypeError(err) + # Get our Markdown Version + self.markdown_ver = \ + TELEGRAM_MARKDOWN_VERSION_MAP[NotifyTelegram. + template_args['mdv']['default']] \ + if mdv is None else \ + next(( + v for k, v in TELEGRAM_MARKDOWN_VERSION_MAP.items() + if str(mdv).lower().startswith(k)), + TELEGRAM_MARKDOWN_VERSION_MAP[NotifyTelegram. + template_args['mdv']['default']]) + # Define whether or not we should make audible alarms self.silent = self.template_args['silent']['default'] \ if silent is None else bool(silent) @@ -717,8 +762,7 @@ class NotifyTelegram(NotifyBase): # Prepare Message Body if self.notify_format == NotifyFormat.MARKDOWN: - _payload['parse_mode'] = 'MARKDOWN' - + _payload['parse_mode'] = self.markdown_ver _payload['text'] = body else: # HTML @@ -886,6 +930,7 @@ class NotifyTelegram(NotifyBase): 'silent': 'yes' if self.silent else 'no', 'preview': 'yes' if self.preview else 'no', 'content': self.content, + 'mdv': TELEGRAM_MARKDOWN_VERSIONS[self.markdown_ver], } if self.topic: @@ -990,6 +1035,10 @@ class NotifyTelegram(NotifyBase): # Store our bot token results['bot_token'] = bot_token + # Support Markdown Version + if 'mdv' in results['qsd'] and len(results['qsd']['mdv']): + results['mdv'] = results['qsd']['mdv'] + # Support Thread Topic if 'topic' in results['qsd'] and len(results['qsd']['topic']): results['topic'] = results['qsd']['topic'] diff --git a/libs/apprise/plugins/NotifyZulip.py b/libs/apprise/plugins/NotifyZulip.py index 66ffb9d1d..54fe2d062 100644 --- a/libs/apprise/plugins/NotifyZulip.py +++ b/libs/apprise/plugins/NotifyZulip.py @@ -163,6 +163,9 @@ class NotifyZulip(NotifyBase): 'to': { 'alias_of': 'targets', }, + 'token': { + 'alias_of': 'token', + }, }) # The default hostname to append to a defined organization @@ -377,21 +380,24 @@ class NotifyZulip(NotifyBase): # The botname results['botname'] = NotifyZulip.unquote(results['user']) - # The first token is stored in the hostname + # The organization is stored in the hostname results['organization'] = NotifyZulip.unquote(results['host']) - # Now fetch the remaining tokens - try: - results['token'] = \ - NotifyZulip.split_path(results['fullpath'])[0] + # Store our targets + results['targets'] = NotifyZulip.split_path(results['fullpath']) - except IndexError: + if 'token' in results['qsd'] and len(results['qsd']['token']): + # Store our token if specified + results['token'] = NotifyZulip.unquote(results['qsd']['token']) + + elif results['targets']: + # First item is the token + results['token'] = results['targets'].pop(0) + + else: # no token results['token'] = None - # Get unquoted entries - results['targets'] = NotifyZulip.split_path(results['fullpath'])[1:] - # Support the 'to' variable so that we can support rooms this way too # The 'to' makes it easier to use yaml configuration if 'to' in results['qsd'] and len(results['qsd']['to']): diff --git a/libs/version.txt b/libs/version.txt index bfd019444..ccabf65cc 100644 --- a/libs/version.txt +++ b/libs/version.txt @@ -2,7 +2,7 @@ alembic==1.13.1 aniso8601==9.0.1 argparse==1.4.0 -apprise==1.7.4 +apprise==1.7.6 apscheduler<=3.10.4 attrs==23.2.0 blinker==1.7.0 diff --git a/migrations/env.py b/migrations/env.py index d706218a2..beddf9710 100644 --- a/migrations/env.py +++ b/migrations/env.py @@ -1,6 +1,7 @@ from flask import current_app from alembic import context +from sqlalchemy import text import logging @@ -95,8 +96,22 @@ def run_migrations_online(): ) with context.begin_transaction(): + bind = context.get_bind() + + if bind.engine.name == 'sqlite': + bind.execute(text("PRAGMA foreign_keys=OFF;")) + elif bind.engine.name == 'postgresql': + bind.execute(text("SET CONSTRAINTS ALL DEFERRED;")) + context.run_migrations() + if bind.engine.name == 'sqlite': + bind.execute(text("PRAGMA foreign_keys=ON;")) + elif bind.engine.name == 'postgresql': + bind.execute(text("SET CONSTRAINTS ALL IMMEDIATE;")) + + bind.close() + if context.is_offline_mode(): run_migrations_offline() diff --git a/migrations/versions/452dd0f0b578_.py b/migrations/versions/452dd0f0b578_.py new file mode 100644 index 000000000..13e623988 --- /dev/null +++ b/migrations/versions/452dd0f0b578_.py @@ -0,0 +1,46 @@ +"""empty message + +Revision ID: 452dd0f0b578 +Revises: 30f37e2e15e1 +Create Date: 2024-05-06 20:27:15.618027 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '452dd0f0b578' +down_revision = '30f37e2e15e1' +branch_labels = None +depends_on = None + +bind = op.get_context().bind +insp = sa.inspect(bind) + + +def column_exists(table_name, column_name): + columns = insp.get_columns(table_name) + return any(c["name"] == column_name for c in columns) + + +def upgrade(): + if column_exists('table_shows', 'alternativeTitle'): + with op.batch_alter_table('table_shows', schema=None) as batch_op: + batch_op.drop_column('alternativeTitle') + + if not column_exists('table_languages_profiles', 'originalFormat'): + with op.batch_alter_table('table_languages_profiles', schema=None) as batch_op: + batch_op.add_column(sa.Column('originalFormat', sa.Integer(), server_default='0')) + + if not column_exists('table_languages_profiles', 'mustContain'): + with op.batch_alter_table('table_languages_profiles', schema=None) as batch_op: + batch_op.add_column(sa.Column('mustContain', sa.Text(), server_default='[]')) + + if not column_exists('table_languages_profiles', 'mustNotContain'): + with op.batch_alter_table('table_languages_profiles', schema=None) as batch_op: + batch_op.add_column(sa.Column('mustNotContain', sa.Text(), server_default='[]')) + + +def downgrade(): + pass diff --git a/tests/conftest.py b/tests/conftest.py index 46d8fc4a0..c7fbdea07 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,7 +7,7 @@ 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/")) - +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../custom_libs/")) def pytest_report_header(config): conflicting_packages = _get_conflicting("libs") diff --git a/tests/subliminal_patch/data/animetosho_episode_response.json b/tests/subliminal_patch/data/animetosho_episode_response.json new file mode 100644 index 000000000..3f7a898d3 --- /dev/null +++ b/tests/subliminal_patch/data/animetosho_episode_response.json @@ -0,0 +1,756 @@ +[ + { + "id": 608526, + "title": "[EMBER] Ore dake Level Up na Ken S01E12 [1080p] [HEVC WEBRip] (Solo Leveling)", + "link": "https://animetosho.org/view/ember-ore-dake-level-up-na-ken-s01e12.n1796835", + "timestamp": 1711853493, + "status": "complete", + "tosho_id": null, + "nyaa_id": 1796835, + "nyaa_subdom": null, + "anidex_id": null, + "torrent_url": "https://animetosho.org/storage/torrent/f9670720a5142ec31aa8e3715745f1392e3ffd5b/%5BEMBER%5D%20Solo%20Leveling%20-%2012.torrent", + "torrent_name": "[EMBER] Solo Leveling - 12.mkv", + "info_hash": "f9670720a5142ec31aa8e3715745f1392e3ffd5b", + "info_hash_v2": null, + "magnet_uri": "magnet:?xt=urn:btih:7FTQOIFFCQXMGGVI4NYVORPRHEXD77K3&tr=http%3A%2F%2Fnyaa.tracker.wf%3A7777%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Fexodus.desync.com%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.torrent.eu.org%3A451%2Fannounce&dn=%5BEMBER%5D%20Ore%20dake%20Level%20Up%20na%20Ken%20S01E12%20%5B1080p%5D%20%5BHEVC%20WEBRip%5D%20%28Solo%20Leveling%29", + "seeders": 316, + "leechers": 24, + "torrent_downloaded_count": 1164, + "tracker_updated": 1711872554, + "nzb_url": "https://animetosho.org/storage/nzbs/0009490e/%5BEMBER%5D%20Solo%20Leveling%20-%2012.nzb", + "total_size": 369572311, + "num_files": 1, + "anidb_aid": 17495, + "anidb_eid": 277518, + "anidb_fid": null, + "article_url": null, + "article_title": null, + "website_url": null + }, + { + "id": 608516, + "title": "[Anime Time] Solo Leveling - 12 [1080p][HEVC 10bit x265][AAC][Multi Sub] [Weekly] Ore dake Level Up na Ken", + "link": "https://animetosho.org/view/anime-time-solo-leveling-12-1080p-hevc-10bit.n1796824", + "timestamp": 1711851382, + "status": "complete", + "tosho_id": null, + "nyaa_id": 1796824, + "nyaa_subdom": null, + "anidex_id": null, + "torrent_url": "https://animetosho.org/storage/torrent/db16234b7aba70d2d901bde30fb0aa899589da47/%5BAnime%20Time%5D%20Solo%20Leveling%20-%2012%20%5B1080p%5D%5BHEVC%2010bit%20x265%5D%5BAAC%5D%5BMulti%20Sub%5D.torrent", + "torrent_name": "[Anime Time] Solo Leveling - 12 [1080p][HEVC 10bit x265][AAC][Multi Sub].mkv", + "info_hash": "db16234b7aba70d2d901bde30fb0aa899589da47", + "info_hash_v2": null, + "magnet_uri": "magnet:?xt=urn:btih:3MLCGS32XJYNFWIBXXRQ7MFKRGKYTWSH&tr=http%3A%2F%2Fnyaa.tracker.wf%3A7777%2Fannounce&tr=http%3A%2F%2Fanidex.moe%3A6969%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Fexodus.desync.com%3A6969%2Fannounce&dn=%5BAnime%20Time%5D%20Solo%20Leveling%20-%2012%20%5B1080p%5D%5BHEVC%2010bit%20x265%5D%5BAAC%5D%5BMulti%20Sub%5D%20%5BWeekly%5D%20Ore%20dake%20Level%20Up%20na%20Ken", + "seeders": 85, + "leechers": 11, + "torrent_downloaded_count": 436, + "tracker_updated": 1711892085, + "nzb_url": "https://animetosho.org/storage/nzbs/00094904/%5BAnime%20Time%5D%20Solo%20Leveling%20-%2012%20%5B1080p%5D%5BHEVC%2010bit%20x265%5D%5BAAC%5D%5BMulti%20Sub%5D.nzb", + "total_size": 588022366, + "num_files": 1, + "anidb_aid": 17495, + "anidb_eid": 277518, + "anidb_fid": null, + "article_url": null, + "article_title": null, + "website_url": "http://animetime.cc/" + }, + { + "id": 608491, + "title": "Solo Leveling - 12 (WEB 4K | 2160p AV1 AAC).mkv", + "link": "https://animetosho.org/view/solo-leveling-12-web-4k-2160p-av1-aac-mkv.d595164", + "timestamp": 1711845852, + "status": "complete", + "tosho_id": null, + "nyaa_id": null, + "nyaa_subdom": null, + "anidex_id": 595164, + "torrent_url": "https://animetosho.org/storage/torrent/cfc631026a1837ef70ff6993aa9c1b1304e0d75b/Solo%20Leveling%20-%2012%20%28WEB%204K%20AV1%20AAC%29.torrent", + "torrent_name": "Solo Leveling - 12 (WEB 4K AV1 AAC).mkv", + "info_hash": "cfc631026a1837ef70ff6993aa9c1b1304e0d75b", + "info_hash_v2": null, + "magnet_uri": "magnet:?xt=urn:btih:Z7DDCATKDA3664H7NGJ2VHA3CMCOBV23&tr=http%3A%2F%2Fanidex.moe%3A6969%2Fannounce&tr=http%3A%2F%2Fnyaa.tracker.wf%3A7777%2Fannounce&tr=http%3A%2F%2Ftracker.anirena.com%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker-udp.anirena.com%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&dn=Solo%20Leveling%20-%2012%20%28WEB%204K%20%7C%202160p%20AV1%20AAC%29.mkv", + "seeders": 11, + "leechers": 0, + "torrent_downloaded_count": 118, + "tracker_updated": 1712027991, + "nzb_url": "https://animetosho.org/storage/nzbs/000948eb/Solo%20Leveling%20-%2012%20%28WEB%204K%20AV1%20AAC%29.nzb", + "total_size": 586875995, + "num_files": 1, + "anidb_aid": 17495, + "anidb_eid": 277518, + "anidb_fid": null, + "article_url": null, + "article_title": null, + "website_url": null + }, + { + "id": 608439, + "title": "[SanKyuu] Ore dake Level Up na Ken (Solo Leveling) - 12 [WEB 1080p][AV1][AAC E-AC3][Multi-Sub]", + "link": "https://animetosho.org/view/sankyuu-ore-dake-level-up-na-ken-solo.n1796722", + "timestamp": 1711836670, + "status": "complete", + "tosho_id": null, + "nyaa_id": 1796722, + "nyaa_subdom": null, + "anidex_id": null, + "torrent_url": "https://animetosho.org/storage/torrent/fb8193f9da38b06a444e2a50966c3fbefdb7dcde/%5BSanKyuu%5D%20Ore%20dake%20Level%20Up%20na%20Ken%20%28Solo%20Leveling%29%20-%2012%20%5BWEB%201080p%20AV1%20AAC%20E-AC3%5D%20%5B325A372A%5D.torrent", + "torrent_name": "[SanKyuu] Ore dake Level Up na Ken (Solo Leveling) - 12 [WEB 1080p AV1 AAC E-AC3] [325A372A].mkv", + "info_hash": "fb8193f9da38b06a444e2a50966c3fbefdb7dcde", + "info_hash_v2": null, + "magnet_uri": "magnet:?xt=urn:btih:7OAZH6O2HCYGURCOFJIJM3B7X363PXG6&tr=http%3A%2F%2Fnyaa.tracker.wf%3A7777%2Fannounce&tr=http%3A%2F%2Fanidex.moe%3A6969%2Fannounce&tr=http%3A%2F%2Ftracker.anirena.com%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker-udp.anirena.com%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&dn=%5BSanKyuu%5D%20Ore%20dake%20Level%20Up%20na%20Ken%20%28Solo%20Leveling%29%20-%2012%20%5BWEB%201080p%5D%5BAV1%5D%5BAAC%20E-AC3%5D%5BMulti-Sub%5D", + "seeders": 51, + "leechers": 2, + "torrent_downloaded_count": 522, + "tracker_updated": 1711939563, + "nzb_url": "https://animetosho.org/storage/nzbs/000948b7/%5BSanKyuu%5D%20Ore%20dake%20Level%20Up%20na%20Ken%20%28Solo%20Leveling%29%20-%2012%20%5BWEB%201080p%20AV1%20AAC%20E-AC3%5D%20%5B325A372A%5D.nzb", + "total_size": 477160034, + "num_files": 1, + "anidb_aid": 17495, + "anidb_eid": 277518, + "anidb_fid": null, + "article_url": null, + "article_title": null, + "website_url": null + }, + { + "id": 608407, + "title": "[NeoLX] Solo Leveling - S01E12 [END][1080p x264 10bits AAC][Multiple Subtitles].mkv", + "link": "https://animetosho.org/view/neolx-solo-leveling-s01e12-end-1080p-x264-10bits.1859288", + "timestamp": 1711831500, + "status": "complete", + "tosho_id": 1859288, + "nyaa_id": null, + "nyaa_subdom": null, + "anidex_id": null, + "torrent_url": "https://animetosho.org/storage/torrent/4a053ad27c1ca38db68b77f1a81860948bec93cb/%5BNeoLX%5D%20Solo%20Leveling%20-%20S01E12%20%5BEND%5D%5B1080p%20x264%2010bits%20AAC%5D%5BMultiple%20Subtitles%5D.torrent", + "torrent_name": "[NeoLX] Solo Leveling - S01E12 [END][1080p x264 10bits AAC][Multiple Subtitles].mkv", + "info_hash": "4a053ad27c1ca38db68b77f1a81860948bec93cb", + "info_hash_v2": null, + "magnet_uri": "magnet:?xt=urn:btih:JICTVUT4DSRY3NULO7Y2QGDASSF6ZE6L&tr=http%3A%2F%2Fanidex.moe%3A6969%2Fannounce&tr=http%3A%2F%2Ftracker.anirena.com%3A80%2Fannounce&tr=http%3A%2F%2Ftracker.acgnx.se%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.tiny-vps.com%3A6969%2Fannounce&dn=%5BNeoLX%5D%20Solo%20Leveling%20-%20S01E12%20%5BEND%5D%5B1080p%20x264%2010bits%20AAC%5D%5BMultiple%20Subtitles%5D.mkv", + "seeders": 22, + "leechers": 0, + "torrent_downloaded_count": 444, + "tracker_updated": 1712027991, + "nzb_url": "https://animetosho.org/storage/nzbs/00094897/%5BNeoLX%5D%20Solo%20Leveling%20-%20S01E12%20%5BEND%5D%5B1080p%20x264%2010bits%20AAC%5D%5BMultiple%20Subtitles%5D.nzb", + "total_size": 1775949490, + "num_files": 1, + "anidb_aid": 17495, + "anidb_eid": 277518, + "anidb_fid": null, + "article_url": null, + "article_title": null, + "website_url": null + }, + { + "id": 608385, + "title": "[CameEsp] Solo Leveling - 12 [1080p][ESP-ENG][mkv].mkv", + "link": "https://animetosho.org/view/cameesp-solo-leveling-12-1080p-esp-eng-mkv-mkv.1859281", + "timestamp": 1711829220, + "status": "skipped", + "tosho_id": 1859281, + "nyaa_id": 1796635, + "nyaa_subdom": null, + "anidex_id": null, + "torrent_url": "https://animetosho.org/storage/torrent/7e683310c0a03a8d94ad4a1aaac0b7f97d98e70e/%5BCameEsp%5D%20Solo%20Leveling%20-%2012%20%5B1080p%5D%5BESP-ENG%5D%5Bmkv%5D.torrent", + "torrent_name": "", + "info_hash": "7e683310c0a03a8d94ad4a1aaac0b7f97d98e70e", + "info_hash_v2": null, + "magnet_uri": "magnet:?xt=urn:btih:PZUDGEGAUA5I3FFNJINKVQFX7F6ZRZYO&tr=http%3A%2F%2Fnyaa.tracker.wf%3A7777%2Fannounce&tr=http%3A%2F%2Fanidex.moe%3A6969%2Fannounce&tr=http%3A%2F%2Ftracker.anirena.com%3A80%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&dn=%5BCameEsp%5D%20Solo%20Leveling%20-%2012%20%5B1080p%5D%5BESP-ENG%5D%5Bmkv%5D.mkv", + "seeders": 178, + "leechers": 21, + "torrent_downloaded_count": 541, + "tracker_updated": null, + "nzb_url": null, + "total_size": 1448914325, + "num_files": 1, + "anidb_aid": 17495, + "anidb_eid": 277518, + "anidb_fid": null, + "article_url": null, + "article_title": null, + "website_url": "https://linkr.bio/CAME_CameEsp" + }, + { + "id": 608386, + "title": "[CameEsp] Solo Leveling - 12 [720p][ESP-ENG][mkv].mkv", + "link": "https://animetosho.org/view/cameesp-solo-leveling-12-720p-esp-eng-mkv-mkv.1859282", + "timestamp": 1711829220, + "status": "skipped", + "tosho_id": 1859282, + "nyaa_id": 1796636, + "nyaa_subdom": null, + "anidex_id": null, + "torrent_url": "https://animetosho.org/storage/torrent/3b35bdf9880489b38f7a318cca8fe02ff31e430c/%5BCameEsp%5D%20Solo%20Leveling%20-%2012%20%5B720p%5D%5BESP-ENG%5D%5Bmkv%5D.torrent", + "torrent_name": "", + "info_hash": "3b35bdf9880489b38f7a318cca8fe02ff31e430c", + "info_hash_v2": null, + "magnet_uri": "magnet:?xt=urn:btih:HM2336MIASE3HD32GGGMVD7AF7ZR4QYM&tr=http%3A%2F%2Fnyaa.tracker.wf%3A7777%2Fannounce&tr=http%3A%2F%2Fanidex.moe%3A6969%2Fannounce&tr=http%3A%2F%2Ftracker.anirena.com%3A80%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&dn=%5BCameEsp%5D%20Solo%20Leveling%20-%2012%20%5B720p%5D%5BESP-ENG%5D%5Bmkv%5D.mkv", + "seeders": 35, + "leechers": 10, + "torrent_downloaded_count": 160, + "tracker_updated": null, + "nzb_url": null, + "total_size": 737663624, + "num_files": 1, + "anidb_aid": 17495, + "anidb_eid": 277518, + "anidb_fid": null, + "article_url": null, + "article_title": null, + "website_url": "https://linkr.bio/CAME_CameEsp" + }, + { + "id": 608370, + "title": "[Valenciano] Ore dake Level Up na Ken - 12 [1080p][AV1 10bit][AAC][Multi-Sub] (Weekly).mkv", + "link": "https://animetosho.org/view/valenciano-ore-dake-level-up-na-ken-12.n1796530", + "timestamp": 1711826614, + "status": "complete", + "tosho_id": null, + "nyaa_id": 1796530, + "nyaa_subdom": null, + "anidex_id": null, + "torrent_url": "https://animetosho.org/storage/torrent/f89a9ffa23f5fc3321f3e2c476dbccbf0435fc98/%5BValenciano%5D%20Ore%20dake%20Level%20Up%20na%20Ken%20-%2012%20%5B1080p%5D%5BAV1%2010bit%5D%5BAAC%5D%5BMulti-Sub%5D%20%28Weekly%29.torrent", + "torrent_name": "[Valenciano] Ore dake Level Up na Ken - 12 [1080p][AV1 10bit][AAC][Multi-Sub] (Weekly).mkv", + "info_hash": "f89a9ffa23f5fc3321f3e2c476dbccbf0435fc98", + "info_hash_v2": null, + "magnet_uri": "magnet:?xt=urn:btih:7CNJ76RD6X6DGIPT4LCHNW6MX4CDL7EY&tr=http%3A%2F%2Fnyaa.tracker.wf%3A7777%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Fexodus.desync.com%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.torrent.eu.org%3A451%2Fannounce&dn=%5BValenciano%5D%20Ore%20dake%20Level%20Up%20na%20Ken%20-%2012%20%5B1080p%5D%5BAV1%2010bit%5D%5BAAC%5D%5BMulti-Sub%5D%20%28Weekly%29.mkv", + "seeders": 22, + "leechers": 2, + "torrent_downloaded_count": 240, + "tracker_updated": 1711940503, + "nzb_url": "https://animetosho.org/storage/nzbs/00094872/%5BValenciano%5D%20Ore%20dake%20Level%20Up%20na%20Ken%20-%2012%20%5B1080p%5D%5BAV1%2010bit%5D%5BAAC%5D%5BMulti-Sub%5D%20%28Weekly%29.nzb", + "total_size": 427930434, + "num_files": 1, + "anidb_aid": 17495, + "anidb_eid": 277518, + "anidb_fid": null, + "article_url": null, + "article_title": null, + "website_url": "https://discord.gg/83dRFDFDp7" + }, + { + "id": 608368, + "title": "[Erai-raws] Ore dake Level Up na Ken - 12 [1080p][HEVC][Multiple Subtitle] [ENG][POR-BR][SPA-LA][SPA][ARA][FRE][GER][ITA][RUS]", + "link": "https://animetosho.org/view/erai-raws-ore-dake-level-up-na-ken.n1796524", + "timestamp": 1711825169, + "status": "complete", + "tosho_id": null, + "nyaa_id": 1796524, + "nyaa_subdom": null, + "anidex_id": null, + "torrent_url": "https://animetosho.org/storage/torrent/8009ee0e9b20732f44df9bf5eaef926df0814d68/%5BErai-raws%5D%20Ore%20dake%20Level%20Up%20na%20Ken%20-%2012%20%5B1080p%5D%5BHEVC%5D%5BMultiple%20Subtitle%5D%5BB46F650F%5D.torrent", + "torrent_name": "[Erai-raws] Ore dake Level Up na Ken - 12 [1080p][HEVC][Multiple Subtitle][B46F650F].mkv", + "info_hash": "8009ee0e9b20732f44df9bf5eaef926df0814d68", + "info_hash_v2": null, + "magnet_uri": "magnet:?xt=urn:btih:QAE64DU3EBZS6RG7TP26V34SNXYICTLI&tr=http%3A%2F%2Fnyaa.tracker.wf%3A7777%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=http%3A%2F%2Fopen.acgnxtracker.com%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.torrent.eu.org%3A451%2Fannounce&dn=%5BErai-raws%5D%20Ore%20dake%20Level%20Up%20na%20Ken%20-%2012%20%5B1080p%5D%5BHEVC%5D%5BMultiple%20Subtitle%5D%20%5BENG%5D%5BPOR-BR%5D%5BSPA-LA%5D%5BSPA%5D%5BARA%5D%5BFRE%5D%5BGER%5D%5BITA%5D%5BRUS%5D", + "seeders": 298, + "leechers": 12, + "torrent_downloaded_count": 2398, + "tracker_updated": 1711933623, + "nzb_url": "https://animetosho.org/storage/nzbs/00094870/%5BErai-raws%5D%20Ore%20dake%20Level%20Up%20na%20Ken%20-%2012%20%5B1080p%5D%5BHEVC%5D%5BMultiple%20Subtitle%5D%5BB46F650F%5D.nzb", + "total_size": 1073028408, + "num_files": 1, + "anidb_aid": 17495, + "anidb_eid": 277518, + "anidb_fid": 3399105, + "article_url": null, + "article_title": null, + "website_url": "https://www.erai-raws.info/anime-list/ore-dake-level-up-na-ken/" + }, + { + "id": 608349, + "title": "[DKB] Solo Leveling - S01E12 [1080p][END][HEVC x265 10bit][Multi-Subs][weekly]", + "link": "https://animetosho.org/view/dkb-solo-leveling-s01e12-1080p-end-hevc-x265.n1796502", + "timestamp": 1711823055, + "status": "complete", + "tosho_id": null, + "nyaa_id": 1796502, + "nyaa_subdom": null, + "anidex_id": null, + "torrent_url": "https://animetosho.org/storage/torrent/d89e1a3b415c0b569481b06dbbe58d936f2e6f0a/%5BDKB%5D%20Solo%20Leveling%20-%20S01E12%20%5B1080p%5D%5BEND%5D%5BHEVC%20x265%2010bit%5D%5BMulti-Subs%5D.torrent", + "torrent_name": "[DKB] Solo Leveling - S01E12 [1080p][END][HEVC x265 10bit][Multi-Subs].mkv", + "info_hash": "d89e1a3b415c0b569481b06dbbe58d936f2e6f0a", + "info_hash_v2": null, + "magnet_uri": "magnet:?xt=urn:btih:3CPBUO2BLQFVNFEBWBW3XZMNSNXS43YK&tr=http%3A%2F%2Fnyaa.tracker.wf%3A7777%2Fannounce&tr=http%3A%2F%2Fanidex.moe%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.internetwarriors.net%3A1337%2Fannounce&dn=%5BDKB%5D%20Solo%20Leveling%20-%20S01E12%20%5B1080p%5D%5BEND%5D%5BHEVC%20x265%2010bit%5D%5BMulti-Subs%5D%5Bweekly%5D", + "seeders": 153, + "leechers": 9, + "torrent_downloaded_count": 1010, + "tracker_updated": 1711922054, + "nzb_url": "https://animetosho.org/storage/nzbs/0009485d/%5BDKB%5D%20Solo%20Leveling%20-%20S01E12%20%5B1080p%5D%5BEND%5D%5BHEVC%20x265%2010bit%5D%5BMulti-Subs%5D.nzb", + "total_size": 665460303, + "num_files": 1, + "anidb_aid": 17495, + "anidb_eid": 277518, + "anidb_fid": 3399329, + "article_url": null, + "article_title": null, + "website_url": "https://discord.gg/ZBQCQ7u" + }, + { + "id": 608347, + "title": "[ASW] Solo Leveling - 12 [1080p HEVC x265 10Bit][AAC]", + "link": "https://animetosho.org/view/asw-solo-leveling-12-1080p-hevc-x265-10bit-aac.n1796496", + "timestamp": 1711822239, + "status": "complete", + "tosho_id": null, + "nyaa_id": 1796496, + "nyaa_subdom": null, + "anidex_id": null, + "torrent_url": "https://animetosho.org/storage/torrent/78d41db42271123d520f971b71a67d970cfdb64c/%5BASW%5D%20Solo%20Leveling%20-%2012%20%5B1080p%20HEVC%5D%5BE1A01A3E%5D.torrent", + "torrent_name": "[ASW] Solo Leveling - 12 [1080p HEVC][E1A01A3E].mkv", + "info_hash": "78d41db42271123d520f971b71a67d970cfdb64c", + "info_hash_v2": null, + "magnet_uri": "magnet:?xt=urn:btih:PDKB3NBCOEJD2UQPS4NXDJT5S4GP3NSM&tr=http%3A%2F%2Fnyaa.tracker.wf%3A7777%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Fexodus.desync.com%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.torrent.eu.org%3A451%2Fannounce&dn=%5BASW%5D%20Solo%20Leveling%20-%2012%20%5B1080p%20HEVC%20x265%2010Bit%5D%5BAAC%5D", + "seeders": 689, + "leechers": 212, + "torrent_downloaded_count": 1960, + "tracker_updated": null, + "nzb_url": "https://animetosho.org/storage/nzbs/0009485b/%5BASW%5D%20Solo%20Leveling%20-%2012%20%5B1080p%20HEVC%5D%5BE1A01A3E%5D.nzb", + "total_size": 455351405, + "num_files": 1, + "anidb_aid": 17495, + "anidb_eid": 277518, + "anidb_fid": 3399037, + "article_url": null, + "article_title": null, + "website_url": "https://discord.gg/6XnYdWP" + }, + { + "id": 608346, + "title": "[Tenrai-Sensei] Solo Leveling - S01E12 - Arise [Web][1080p][HEVC 10bit x265] Ore dake Level Up na Ken", + "link": "https://animetosho.org/view/tenrai-sensei-solo-leveling-s01e12-arise-web-1080p.n1796495", + "timestamp": 1711822029, + "status": "complete", + "tosho_id": null, + "nyaa_id": 1796495, + "nyaa_subdom": null, + "anidex_id": null, + "torrent_url": "https://animetosho.org/storage/torrent/8b22acf6c18e9ac56f0af00a6061918712d8d992/Solo%20Leveling%20-%20S01E12%20-%20Arise.torrent", + "torrent_name": "Solo Leveling - S01E12 - Arise.mkv", + "info_hash": "8b22acf6c18e9ac56f0af00a6061918712d8d992", + "info_hash_v2": null, + "magnet_uri": "magnet:?xt=urn:btih:RMRKZ5WBR2NMK3YK6AFGAYMRQ4JNRWMS&tr=http%3A%2F%2Fnyaa.tracker.wf%3A7777%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=udp%3A%2F%2Fexodus.desync.com%3A6969%2Fannounce&tr=udp%3A%2F%2Fopentracker.i2p.rocks%3A6969%2Fannounce&dn=%5BTenrai-Sensei%5D%20Solo%20Leveling%20-%20S01E12%20-%20Arise%20%5BWeb%5D%5B1080p%5D%5BHEVC%2010bit%20x265%5D%20Ore%20dake%20Level%20Up%20na%20Ken", + "seeders": 1000000000, + "leechers": 1000000000, + "torrent_downloaded_count": 3028, + "tracker_updated": null, + "nzb_url": "https://animetosho.org/storage/nzbs/0009485a/Solo%20Leveling%20-%20S01E12%20-%20Arise.nzb", + "total_size": 574082022, + "num_files": 1, + "anidb_aid": 17495, + "anidb_eid": 277518, + "anidb_fid": 3399753, + "article_url": null, + "article_title": null, + "website_url": "https://discord.gg/WucPeE5ume" + }, + { + "id": 608339, + "title": "[Judas] Ore dake Level Up na Ken (Solo Leveling) - S01E12 [1080p][HEVC x265 10bit][Multi-Subs] (Weekly)", + "link": "https://animetosho.org/view/judas-ore-dake-level-up-na-ken-solo.n1796483", + "timestamp": 1711821551, + "status": "complete", + "tosho_id": null, + "nyaa_id": 1796483, + "nyaa_subdom": null, + "anidex_id": null, + "torrent_url": "https://animetosho.org/storage/torrent/aa0f1690f5bff99891883dbbde61e4c59920aa5e/%5BJudas%5D%20Solo%20Leveling%20-%20S01E12.torrent", + "torrent_name": "[Judas] Solo Leveling - S01E12.mkv", + "info_hash": "aa0f1690f5bff99891883dbbde61e4c59920aa5e", + "info_hash_v2": null, + "magnet_uri": "magnet:?xt=urn:btih:VIHRNEHVX74ZREMIHW554YPEYWMSBKS6&tr=http%3A%2F%2Fnyaa.tracker.wf%3A7777%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Fexodus.desync.com%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.torrent.eu.org%3A451%2Fannounce&dn=%5BJudas%5D%20Ore%20dake%20Level%20Up%20na%20Ken%20%28Solo%20Leveling%29%20-%20S01E12%20%5B1080p%5D%5BHEVC%20x265%2010bit%5D%5BMulti-Subs%5D%20%28Weekly%29", + "seeders": 422, + "leechers": 20, + "torrent_downloaded_count": 3434, + "tracker_updated": 1711874024, + "nzb_url": "https://animetosho.org/storage/nzbs/00094853/%5BJudas%5D%20Solo%20Leveling%20-%20S01E12.nzb", + "total_size": 485334036, + "num_files": 1, + "anidb_aid": 17495, + "anidb_eid": 277518, + "anidb_fid": 3399248, + "article_url": null, + "article_title": null, + "website_url": "https://discord.gg/vbJ7RTn" + }, + { + "id": 608338, + "title": "[Raze] Solo Leveling (Ore dake Level Up na Ken) - 12 x265 10bit 1080p 143.8561fps.mkv", + "link": "https://animetosho.org/view/raze-solo-leveling-ore-dake-level-up-na.n1796482", + "timestamp": 1711821474, + "status": "complete", + "tosho_id": null, + "nyaa_id": 1796482, + "nyaa_subdom": null, + "anidex_id": null, + "torrent_url": "https://animetosho.org/storage/torrent/bcebcb468c1a2557404afa18f4948cb4fded5c17/%5BRaze%5D%20Solo%20Leveling%20%28Ore%20dake%20Level%20Up%20na%20Ken%29%20-%2012%20x265%2010bit%201080p%20143.8561fps.torrent", + "torrent_name": "[Raze] Solo Leveling (Ore dake Level Up na Ken) - 12 x265 10bit 1080p 143.8561fps.mkv", + "info_hash": "bcebcb468c1a2557404afa18f4948cb4fded5c17", + "info_hash_v2": null, + "magnet_uri": "magnet:?xt=urn:btih:XTV4WRUMDISVOQCK7IMPJFEMWT662XAX&tr=http%3A%2F%2Fnyaa.tracker.wf%3A7777%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Fexodus.desync.com%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.torrent.eu.org%3A451%2Fannounce&dn=%5BRaze%5D%20Solo%20Leveling%20%28Ore%20dake%20Level%20Up%20na%20Ken%29%20-%2012%20x265%2010bit%201080p%20143.8561fps.mkv", + "seeders": 15, + "leechers": 1, + "torrent_downloaded_count": 216, + "tracker_updated": 1711932770, + "nzb_url": "https://animetosho.org/storage/nzbs/00094852/%5BRaze%5D%20Solo%20Leveling%20%28Ore%20dake%20Level%20Up%20na%20Ken%29%20-%2012%20x265%2010bit%201080p%20143.8561fps.nzb", + "total_size": 987255128, + "num_files": 1, + "anidb_aid": 17495, + "anidb_eid": 277518, + "anidb_fid": null, + "article_url": null, + "article_title": null, + "website_url": "https://discord.gg/72ZKssY" + }, + { + "id": 608324, + "title": "Ore dake Level Up na Ken - S01E12 - 720p WEB x264 -NanDesuKa (CR).mkv", + "link": "https://animetosho.org/view/ore-dake-level-up-na-ken-s01e12-720p.n1796469", + "timestamp": 1711817371, + "status": "complete", + "tosho_id": null, + "nyaa_id": 1796469, + "nyaa_subdom": null, + "anidex_id": null, + "torrent_url": "https://animetosho.org/storage/torrent/514d25bbb699f0cd50ba82663f4d9972e42aec7e/Ore%20dake%20Level%20Up%20na%20Ken%20-%20S01E12%20-%20720p%20WEB%20x264%20-NanDesuKa%20%28CR%29.torrent", + "torrent_name": "Ore dake Level Up na Ken - S01E12 - 720p WEB x264 -NanDesuKa (CR).mkv", + "info_hash": "514d25bbb699f0cd50ba82663f4d9972e42aec7e", + "info_hash_v2": null, + "magnet_uri": "magnet:?xt=urn:btih:KFGSLO5WTHYM2UF2QJTD6TMZOLSCV3D6&tr=http%3A%2F%2Fnyaa.tracker.wf%3A7777%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.internetwarriors.net%3A1337%2Fannounce&tr=udp%3A%2F%2Fexodus.desync.com%3A6969%2Fannounce&dn=Ore%20dake%20Level%20Up%20na%20Ken%20-%20S01E12%20-%20720p%20WEB%20x264%20-NanDesuKa%20%28CR%29.mkv", + "seeders": 24, + "leechers": 1, + "torrent_downloaded_count": 147, + "tracker_updated": 1711941873, + "nzb_url": "https://animetosho.org/storage/nzbs/00094844/Ore%20dake%20Level%20Up%20na%20Ken%20-%20S01E12%20-%20720p%20WEB%20x264%20-NanDesuKa%20%28CR%29.nzb", + "total_size": 733563952, + "num_files": 1, + "anidb_aid": 17495, + "anidb_eid": 277518, + "anidb_fid": null, + "article_url": null, + "article_title": null, + "website_url": null + }, + { + "id": 608323, + "title": "Ore dake Level Up na Ken - S01E12 - 1080p WEB x264 -NanDesuKa (CR).mkv", + "link": "https://animetosho.org/view/ore-dake-level-up-na-ken-s01e12-1080p.n1796468", + "timestamp": 1711817278, + "status": "complete", + "tosho_id": null, + "nyaa_id": 1796468, + "nyaa_subdom": null, + "anidex_id": null, + "torrent_url": "https://animetosho.org/storage/torrent/2368c8599cb9dfcd15c4133c195a026c0c061452/Ore%20dake%20Level%20Up%20na%20Ken%20-%20S01E12%20-%201080p%20WEB%20x264%20-NanDesuKa%20%28CR%29.torrent", + "torrent_name": "Ore dake Level Up na Ken - S01E12 - 1080p WEB x264 -NanDesuKa (CR).mkv", + "info_hash": "2368c8599cb9dfcd15c4133c195a026c0c061452", + "info_hash_v2": null, + "magnet_uri": "magnet:?xt=urn:btih:ENUMQWM4XHP42FOECM6BSWQCNQGAMFCS&tr=http%3A%2F%2Fnyaa.tracker.wf%3A7777%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.internetwarriors.net%3A1337%2Fannounce&tr=udp%3A%2F%2Fexodus.desync.com%3A6969%2Fannounce&dn=Ore%20dake%20Level%20Up%20na%20Ken%20-%20S01E12%20-%201080p%20WEB%20x264%20-NanDesuKa%20%28CR%29.mkv", + "seeders": 43, + "leechers": 1, + "torrent_downloaded_count": 325, + "tracker_updated": 1711950739, + "nzb_url": "https://animetosho.org/storage/nzbs/00094843/Ore%20dake%20Level%20Up%20na%20Ken%20-%20S01E12%20-%201080p%20WEB%20x264%20-NanDesuKa%20%28CR%29.nzb", + "total_size": 1444814636, + "num_files": 1, + "anidb_aid": 17495, + "anidb_eid": 277518, + "anidb_fid": null, + "article_url": null, + "article_title": null, + "website_url": null + }, + { + "id": 608322, + "title": "Ore dake Level Up na Ken - S01E12 - 480p WEB x264 -NanDesuKa (CR).mkv", + "link": "https://animetosho.org/view/ore-dake-level-up-na-ken-s01e12-480p.n1796467", + "timestamp": 1711817275, + "status": "complete", + "tosho_id": null, + "nyaa_id": 1796467, + "nyaa_subdom": null, + "anidex_id": null, + "torrent_url": "https://animetosho.org/storage/torrent/65c34046223f78409badf5138035708875dae046/Ore%20dake%20Level%20Up%20na%20Ken%20-%20S01E12%20-%20480p%20WEB%20x264%20-NanDesuKa%20%28CR%29.torrent", + "torrent_name": "Ore dake Level Up na Ken - S01E12 - 480p WEB x264 -NanDesuKa (CR).mkv", + "info_hash": "65c34046223f78409badf5138035708875dae046", + "info_hash_v2": null, + "magnet_uri": "magnet:?xt=urn:btih:MXBUARRCH54EBG5N6UJYANLQRB25VYCG&tr=http%3A%2F%2Fnyaa.tracker.wf%3A7777%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.internetwarriors.net%3A1337%2Fannounce&tr=udp%3A%2F%2Fexodus.desync.com%3A6969%2Fannounce&dn=Ore%20dake%20Level%20Up%20na%20Ken%20-%20S01E12%20-%20480p%20WEB%20x264%20-NanDesuKa%20%28CR%29.mkv", + "seeders": 2, + "leechers": 1, + "torrent_downloaded_count": 47, + "tracker_updated": 1711939543, + "nzb_url": "https://animetosho.org/storage/nzbs/00094842/Ore%20dake%20Level%20Up%20na%20Ken%20-%20S01E12%20-%20480p%20WEB%20x264%20-NanDesuKa%20%28CR%29.nzb", + "total_size": 379888841, + "num_files": 1, + "anidb_aid": 17495, + "anidb_eid": 277518, + "anidb_fid": null, + "article_url": null, + "article_title": null, + "website_url": null + }, + { + "id": 608321, + "title": "[ToonsHub] Solo Leveling E12 Arise 2160p B-Global WEB-DL x264 (Multi-Subs)", + "link": "https://animetosho.org/view/toonshub-solo-leveling-e12-arise-2160p-b-global.n1796464", + "timestamp": 1711816495, + "status": "complete", + "tosho_id": null, + "nyaa_id": 1796464, + "nyaa_subdom": null, + "anidex_id": null, + "torrent_url": "https://animetosho.org/storage/torrent/953e7663ac91c946ee46efd1f00c544a81ac5210/Solo%20Leveling%20E12%20Arise%202160p%20B-Global%20WEB-DL%20x264%20%5BJapanese%5D%20%28AAC%202.0%29%20MSubs_ToonsHub_.torrent", + "torrent_name": "Solo Leveling E12 Arise 2160p B-Global WEB-DL x264 [Japanese] (AAC 2.0) MSubs_ToonsHub_.mkv", + "info_hash": "953e7663ac91c946ee46efd1f00c544a81ac5210", + "info_hash_v2": null, + "magnet_uri": "magnet:?xt=urn:btih:SU7HMY5MSHEUN3SG57I7ADCUJKA2YUQQ&tr=http%3A%2F%2Fnyaa.tracker.wf%3A7777%2Fannounce&tr=http%3A%2F%2Fanidex.moe%3A6969%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Fexodus.desync.com%3A6969%2Fannounce&dn=%5BToonsHub%5D%20Solo%20Leveling%20E12%20Arise%202160p%20B-Global%20WEB-DL%20x264%20%28Multi-Subs%29", + "seeders": 283, + "leechers": 6, + "torrent_downloaded_count": 2103, + "tracker_updated": 1711940653, + "nzb_url": "https://animetosho.org/storage/nzbs/00094841/Solo%20Leveling%20E12%20Arise%202160p%20B-Global%20WEB-DL%20x264%20%5BJapanese%5D%20%28AAC%202.0%29%20MSubs_ToonsHub_.nzb", + "total_size": 1725930387, + "num_files": 1, + "anidb_aid": 17495, + "anidb_eid": 277518, + "anidb_fid": null, + "article_url": null, + "article_title": null, + "website_url": "https://discord.gg/2mPFKykW4j" + }, + { + "id": 608320, + "title": "Solo Leveling S01E12 Arise 1080p CR WEB-DL AAC2.0 H 264-VARYG (Ore dake Level Up na Ken, Multi-Subs)", + "link": "https://animetosho.org/view/solo-leveling-s01e12-arise-1080p-cr-web-dl.n1796463", + "timestamp": 1711816461, + "status": "complete", + "tosho_id": null, + "nyaa_id": 1796463, + "nyaa_subdom": null, + "anidex_id": null, + "torrent_url": "https://animetosho.org/storage/torrent/e8c25942e297bfd38515d785ca82e0dac7a09688/Solo.Leveling.S01E12.Arise.1080p.CR.WEB-DL.AAC2.0.H.264-VARYG.torrent", + "torrent_name": "Solo.Leveling.S01E12.Arise.1080p.CR.WEB-DL.AAC2.0.H.264-VARYG.mkv", + "info_hash": "e8c25942e297bfd38515d785ca82e0dac7a09688", + "info_hash_v2": null, + "magnet_uri": "magnet:?xt=urn:btih:5DBFSQXCS675HBIV26C4VAXA3LD2BFUI&tr=http%3A%2F%2Fnyaa.tracker.wf%3A7777%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Fexodus.desync.com%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.torrent.eu.org%3A451%2Fannounce&dn=Solo%20Leveling%20S01E12%20Arise%201080p%20CR%20WEB-DL%20AAC2.0%20H%20264-VARYG%20%28Ore%20dake%20Level%20Up%20na%20Ken%2C%20Multi-Subs%29", + "seeders": 476, + "leechers": 78, + "torrent_downloaded_count": 2233, + "tracker_updated": 1711948863, + "nzb_url": "https://animetosho.org/storage/nzbs/00094840/Solo.Leveling.S01E12.Arise.1080p.CR.WEB-DL.AAC2.0.H.264-VARYG.nzb", + "total_size": 1447070929, + "num_files": 1, + "anidb_aid": 17495, + "anidb_eid": 277518, + "anidb_fid": null, + "article_url": null, + "article_title": null, + "website_url": "https://myanimelist.net/anime/52299/" + }, + { + "id": 608319, + "title": "[ToonsHub] Solo Leveling E12 Arise 1080p B-Global WEB-DL x264 (Multi-Subs)", + "link": "https://animetosho.org/view/toonshub-solo-leveling-e12-arise-1080p-b-global.n1796462", + "timestamp": 1711816458, + "status": "complete", + "tosho_id": null, + "nyaa_id": 1796462, + "nyaa_subdom": null, + "anidex_id": null, + "torrent_url": "https://animetosho.org/storage/torrent/adf123922973e4c0f7979ab6d3874d2e2e0e5207/Solo%20Leveling%20E12%20Arise%201080p%20B-Global%20WEB-DL%20x264%20%5BJapanese%5D%20%28AAC%202.0%29%20MSubs_ToonsHub_.torrent", + "torrent_name": "Solo Leveling E12 Arise 1080p B-Global WEB-DL x264 [Japanese] (AAC 2.0) MSubs_ToonsHub_.mkv", + "info_hash": "adf123922973e4c0f7979ab6d3874d2e2e0e5207", + "info_hash_v2": null, + "magnet_uri": "magnet:?xt=urn:btih:VXYSHERJOPSMB54XTK3NHB2NFYXA4UQH&tr=http%3A%2F%2Fnyaa.tracker.wf%3A7777%2Fannounce&tr=http%3A%2F%2Fanidex.moe%3A6969%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Fexodus.desync.com%3A6969%2Fannounce&dn=%5BToonsHub%5D%20Solo%20Leveling%20E12%20Arise%201080p%20B-Global%20WEB-DL%20x264%20%28Multi-Subs%29", + "seeders": 112, + "leechers": 2, + "torrent_downloaded_count": 955, + "tracker_updated": 1711941514, + "nzb_url": "https://animetosho.org/storage/nzbs/0009483f/Solo%20Leveling%20E12%20Arise%201080p%20B-Global%20WEB-DL%20x264%20%5BJapanese%5D%20%28AAC%202.0%29%20MSubs_ToonsHub_.nzb", + "total_size": 424813220, + "num_files": 1, + "anidb_aid": 17495, + "anidb_eid": 277518, + "anidb_fid": null, + "article_url": null, + "article_title": null, + "website_url": "https://discord.gg/2mPFKykW4j" + }, + { + "id": 608318, + "title": "[Erai-raws] Ore dake Level Up na Ken - 12 [480p][Multiple Subtitle] [ENG][POR-BR][SPA-LA][SPA][ARA][FRE][GER][ITA][RUS]", + "link": "https://animetosho.org/view/erai-raws-ore-dake-level-up-na-ken.n1796461", + "timestamp": 1711816354, + "status": "complete", + "tosho_id": null, + "nyaa_id": 1796461, + "nyaa_subdom": null, + "anidex_id": null, + "torrent_url": "https://animetosho.org/storage/torrent/5b4a6ec6c71d1033f336bdf027fc3f56dbb6544b/%5BErai-raws%5D%20Ore%20dake%20Level%20Up%20na%20Ken%20-%2012%20%5B480p%5D%5BMultiple%20Subtitle%5D%5B348538A9%5D.torrent", + "torrent_name": "[Erai-raws] Ore dake Level Up na Ken - 12 [480p][Multiple Subtitle][348538A9].mkv", + "info_hash": "5b4a6ec6c71d1033f336bdf027fc3f56dbb6544b", + "info_hash_v2": null, + "magnet_uri": "magnet:?xt=urn:btih:LNFG5RWHDUIDH4ZWXXYCP7B7K3N3MVCL&tr=http%3A%2F%2Fnyaa.tracker.wf%3A7777%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=http%3A%2F%2Fopen.acgnxtracker.com%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.torrent.eu.org%3A451%2Fannounce&dn=%5BErai-raws%5D%20Ore%20dake%20Level%20Up%20na%20Ken%20-%2012%20%5B480p%5D%5BMultiple%20Subtitle%5D%20%5BENG%5D%5BPOR-BR%5D%5BSPA-LA%5D%5BSPA%5D%5BARA%5D%5BFRE%5D%5BGER%5D%5BITA%5D%5BRUS%5D", + "seeders": 57, + "leechers": 1, + "torrent_downloaded_count": 593, + "tracker_updated": 1711935570, + "nzb_url": "https://animetosho.org/storage/nzbs/0009483e/%5BErai-raws%5D%20Ore%20dake%20Level%20Up%20na%20Ken%20-%2012%20%5B480p%5D%5BMultiple%20Subtitle%5D%5B348538A9%5D.nzb", + "total_size": 390242548, + "num_files": 1, + "anidb_aid": 17495, + "anidb_eid": 277518, + "anidb_fid": 3399156, + "article_url": null, + "article_title": null, + "website_url": "https://www.erai-raws.info/anime-list/ore-dake-level-up-na-ken/" + }, + { + "id": 608317, + "title": "[Erai-raws] Ore dake Level Up na Ken - 12 [720p][Multiple Subtitle] [ENG][POR-BR][SPA-LA][SPA][ARA][FRE][GER][ITA][RUS]", + "link": "https://animetosho.org/view/erai-raws-ore-dake-level-up-na-ken.n1796459", + "timestamp": 1711816335, + "status": "complete", + "tosho_id": null, + "nyaa_id": 1796459, + "nyaa_subdom": null, + "anidex_id": null, + "torrent_url": "https://animetosho.org/storage/torrent/371911c245694044bb84dfa1cb162c5922e46090/%5BErai-raws%5D%20Ore%20dake%20Level%20Up%20na%20Ken%20-%2012%20%5B720p%5D%5BMultiple%20Subtitle%5D%5BFF79133E%5D.torrent", + "torrent_name": "[Erai-raws] Ore dake Level Up na Ken - 12 [720p][Multiple Subtitle][FF79133E].mkv", + "info_hash": "371911c245694044bb84dfa1cb162c5922e46090", + "info_hash_v2": null, + "magnet_uri": "magnet:?xt=urn:btih:G4MRDQSFNFAEJO4E36Q4WFRMLEROIYEQ&tr=http%3A%2F%2Fnyaa.tracker.wf%3A7777%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=http%3A%2F%2Fopen.acgnxtracker.com%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.torrent.eu.org%3A451%2Fannounce&dn=%5BErai-raws%5D%20Ore%20dake%20Level%20Up%20na%20Ken%20-%2012%20%5B720p%5D%5BMultiple%20Subtitle%5D%20%5BENG%5D%5BPOR-BR%5D%5BSPA-LA%5D%5BSPA%5D%5BARA%5D%5BFRE%5D%5BGER%5D%5BITA%5D%5BRUS%5D", + "seeders": 244, + "leechers": 8, + "torrent_downloaded_count": 1819, + "tracker_updated": 1711891219, + "nzb_url": "https://animetosho.org/storage/nzbs/0009483d/%5BErai-raws%5D%20Ore%20dake%20Level%20Up%20na%20Ken%20-%2012%20%5B720p%5D%5BMultiple%20Subtitle%5D%5BFF79133E%5D.nzb", + "total_size": 743917601, + "num_files": 1, + "anidb_aid": 17495, + "anidb_eid": 277518, + "anidb_fid": 3399115, + "article_url": null, + "article_title": null, + "website_url": "https://www.erai-raws.info/anime-list/ore-dake-level-up-na-ken/" + }, + { + "id": 608316, + "title": "[Erai-raws] Ore dake Level Up na Ken - 12 [1080p][Multiple Subtitle] [ENG][POR-BR][SPA-LA][SPA][ARA][FRE][GER][ITA][RUS]", + "link": "https://animetosho.org/view/erai-raws-ore-dake-level-up-na-ken.n1796456", + "timestamp": 1711816309, + "status": "complete", + "tosho_id": null, + "nyaa_id": 1796456, + "nyaa_subdom": null, + "anidex_id": null, + "torrent_url": "https://animetosho.org/storage/torrent/927f32f78956475287b80b7a3b8069d775b3bb74/%5BErai-raws%5D%20Ore%20dake%20Level%20Up%20na%20Ken%20-%2012%20%5B1080p%5D%5BMultiple%20Subtitle%5D%5BC8116259%5D.torrent", + "torrent_name": "[Erai-raws] Ore dake Level Up na Ken - 12 [1080p][Multiple Subtitle][C8116259].mkv", + "info_hash": "927f32f78956475287b80b7a3b8069d775b3bb74", + "info_hash_v2": null, + "magnet_uri": "magnet:?xt=urn:btih:SJ7TF54JKZDVFB5YBN5DXADJ2523HO3U&tr=http%3A%2F%2Fnyaa.tracker.wf%3A7777%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=http%3A%2F%2Fopen.acgnxtracker.com%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.torrent.eu.org%3A451%2Fannounce&dn=%5BErai-raws%5D%20Ore%20dake%20Level%20Up%20na%20Ken%20-%2012%20%5B1080p%5D%5BMultiple%20Subtitle%5D%20%5BENG%5D%5BPOR-BR%5D%5BSPA-LA%5D%5BSPA%5D%5BARA%5D%5BFRE%5D%5BGER%5D%5BITA%5D%5BRUS%5D", + "seeders": 1054, + "leechers": 30, + "torrent_downloaded_count": 7259, + "tracker_updated": 1711904421, + "nzb_url": "https://animetosho.org/storage/nzbs/0009483c/%5BErai-raws%5D%20Ore%20dake%20Level%20Up%20na%20Ken%20-%2012%20%5B1080p%5D%5BMultiple%20Subtitle%5D%5BC8116259%5D.nzb", + "total_size": 1455168235, + "num_files": 1, + "anidb_aid": 17495, + "anidb_eid": 277518, + "anidb_fid": 3399072, + "article_url": null, + "article_title": null, + "website_url": "https://www.erai-raws.info/anime-list/ore-dake-level-up-na-ken/" + }, + { + "id": 608315, + "title": "[SubsPlease] Solo Leveling - 12 (1080p) [5B47BF7E].mkv", + "link": "https://animetosho.org/view/subsplease-solo-leveling-12-1080p-5b47bf7e-mkv.1859208", + "timestamp": 1711816291, + "status": "complete", + "tosho_id": 1859208, + "nyaa_id": 1796455, + "nyaa_subdom": null, + "anidex_id": null, + "torrent_url": "https://animetosho.org/storage/torrent/ac03972f83b13773c7d6c0387969d3873c98fe6e/%5BSubsPlease%5D%20Solo%20Leveling%20-%2012%20%281080p%29%20%5B5B47BF7E%5D.torrent", + "torrent_name": "[SubsPlease] Solo Leveling - 12 (1080p) [5B47BF7E].mkv", + "info_hash": "ac03972f83b13773c7d6c0387969d3873c98fe6e", + "info_hash_v2": null, + "magnet_uri": "magnet:?xt=urn:btih:VQBZOL4DWE3XHR6WYA4HS2OTQ46JR7TO&tr=http%3A%2F%2Fnyaa.tracker.wf%3A7777%2Fannounce&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2F9.rarbg.to%3A2710%2Fannounce&tr=udp%3A%2F%2F9.rarbg.me%3A2710%2Fannounce&dn=%5BSubsPlease%5D%20Solo%20Leveling%20-%2012%20%281080p%29%20%5B5B47BF7E%5D.mkv", + "seeders": 4800, + "leechers": 126, + "torrent_downloaded_count": 32750, + "tracker_updated": 1711902497, + "nzb_url": "https://animetosho.org/storage/nzbs/0009483b/%5BSubsPlease%5D%20Solo%20Leveling%20-%2012%20%281080p%29%20%5B5B47BF7E%5D.nzb", + "total_size": 1448782755, + "num_files": 1, + "anidb_aid": 17495, + "anidb_eid": 277518, + "anidb_fid": 3398902, + "article_url": null, + "article_title": null, + "website_url": "https://subsplease.org" + }, + { + "id": 608314, + "title": "[SubsPlease] Solo Leveling - 12 (720p) [83538700].mkv", + "link": "https://animetosho.org/view/subsplease-solo-leveling-12-720p-83538700-mkv.1859207", + "timestamp": 1711816270, + "status": "complete", + "tosho_id": 1859207, + "nyaa_id": 1796454, + "nyaa_subdom": null, + "anidex_id": null, + "torrent_url": "https://animetosho.org/storage/torrent/5a65737737ea4a6c24c2ef17fb87906f250f2b2b/%5BSubsPlease%5D%20Solo%20Leveling%20-%2012%20%28720p%29%20%5B83538700%5D.torrent", + "torrent_name": "[SubsPlease] Solo Leveling - 12 (720p) [83538700].mkv", + "info_hash": "5a65737737ea4a6c24c2ef17fb87906f250f2b2b", + "info_hash_v2": null, + "magnet_uri": "magnet:?xt=urn:btih:LJSXG5ZX5JFGYJGC54L7XB4QN4SQ6KZL&tr=http%3A%2F%2Fnyaa.tracker.wf%3A7777%2Fannounce&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2F9.rarbg.to%3A2710%2Fannounce&tr=udp%3A%2F%2F9.rarbg.me%3A2710%2Fannounce&dn=%5BSubsPlease%5D%20Solo%20Leveling%20-%2012%20%28720p%29%20%5B83538700%5D.mkv", + "seeders": 1155, + "leechers": 51, + "torrent_downloaded_count": 8614, + "tracker_updated": 1711883436, + "nzb_url": "https://animetosho.org/storage/nzbs/0009483a/%5BSubsPlease%5D%20Solo%20Leveling%20-%2012%20%28720p%29%20%5B83538700%5D.nzb", + "total_size": 737532022, + "num_files": 1, + "anidb_aid": 17495, + "anidb_eid": 277518, + "anidb_fid": 3398956, + "article_url": null, + "article_title": null, + "website_url": "https://subsplease.org" + }, + { + "id": 608313, + "title": "[SubsPlease] Solo Leveling - 12 (480p) [9FBA731B].mkv", + "link": "https://animetosho.org/view/subsplease-solo-leveling-12-480p-9fba731b-mkv.1859206", + "timestamp": 1711816267, + "status": "complete", + "tosho_id": 1859206, + "nyaa_id": 1796453, + "nyaa_subdom": null, + "anidex_id": null, + "torrent_url": "https://animetosho.org/storage/torrent/071cfac21f433b07e81e43f33f9a869098af4f64/%5BSubsPlease%5D%20Solo%20Leveling%20-%2012%20%28480p%29%20%5B9FBA731B%5D.torrent", + "torrent_name": "[SubsPlease] Solo Leveling - 12 (480p) [9FBA731B].mkv", + "info_hash": "071cfac21f433b07e81e43f33f9a869098af4f64", + "info_hash_v2": null, + "magnet_uri": "magnet:?xt=urn:btih:A4OPVQQ7IM5QP2A6IPZT7GUGSCMK6T3E&tr=http%3A%2F%2Fnyaa.tracker.wf%3A7777%2Fannounce&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2F9.rarbg.to%3A2710%2Fannounce&tr=udp%3A%2F%2F9.rarbg.me%3A2710%2Fannounce&dn=%5BSubsPlease%5D%20Solo%20Leveling%20-%2012%20%28480p%29%20%5B9FBA731B%5D.mkv", + "seeders": 385, + "leechers": 143, + "torrent_downloaded_count": 2467, + "tracker_updated": null, + "nzb_url": "https://animetosho.org/storage/nzbs/00094839/%5BSubsPlease%5D%20Solo%20Leveling%20-%2012%20%28480p%29%20%5B9FBA731B%5D.nzb", + "total_size": 383857166, + "num_files": 1, + "anidb_aid": 17495, + "anidb_eid": 277518, + "anidb_fid": 3399016, + "article_url": null, + "article_title": null, + "website_url": "https://subsplease.org" + } +] \ No newline at end of file diff --git a/tests/subliminal_patch/data/animetosho_series_response.json b/tests/subliminal_patch/data/animetosho_series_response.json new file mode 100644 index 000000000..290a8f7a8 --- /dev/null +++ b/tests/subliminal_patch/data/animetosho_series_response.json @@ -0,0 +1,232 @@ +{ + "title": "[EMBER] Ore dake Level Up na Ken S01E12 [1080p] [HEVC WEBRip] (Solo Leveling)", + "timestamp": 1711853493, + "status": "complete", + "tosho_id": null, + "nyaa_id": 1796835, + "nyaa_subdom": null, + "anidex_id": null, + "comment": "", + "is_dupe": false, + "deleted": false, + "torrent_url": "https://animetosho.org/storage/torrent/f9670720a5142ec31aa8e3715745f1392e3ffd5b/%5BEMBER%5D%20Solo%20Leveling%20-%2012.torrent", + "torrent_name": "[EMBER] Solo Leveling - 12.mkv", + "info_hash": "f9670720a5142ec31aa8e3715745f1392e3ffd5b", + "info_hash_v2": null, + "magnet_uri": "magnet:?xt=urn:btih:7FTQOIFFCQXMGGVI4NYVORPRHEXD77K3&tr=http%3A%2F%2Fnyaa.tracker.wf%3A7777%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Fexodus.desync.com%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.torrent.eu.org%3A451%2Fannounce&dn=%5BEMBER%5D%20Ore%20dake%20Level%20Up%20na%20Ken%20S01E12%20%5B1080p%5D%20%5BHEVC%20WEBRip%5D%20%28Solo%20Leveling%29", + "nzb_url": "https://animetosho.org/storage/nzbs/0009490e/%5BEMBER%5D%20Solo%20Leveling%20-%2012.nzb", + "anidb_aid": 17495, + "anidb_eid": 277518, + "anidb_fid": null, + "article_url": null, + "article_title": null, + "website_url": null, + "total_size": 369572311, + "num_files": 1, + "primary_file_id": 1154190, + "files": [ + { + "id": 1154190, + "is_archive": false, + "filename": "[EMBER] Solo Leveling - 12.mkv", + "size": 369572311, + "processed": true, + "crc32": "cc8c36bc", + "md5": "07306f34a7cc9d25c2ec8a3b7d0b573a", + "sha1": "d79665531066b24439b0ef421074e6445d8b25a6", + "sha256": "0df3ec5424233bdb75ff33077b196e515455152a76ab5d4eab7a63cdcbb8c917", + "ed2k": "c0359507e7da148750279ff229cb9fbf", + "bt2": "b4526e074a41981e73cdcd13b9142a4a774bd15ff641b5cf114ace527268d213", + "vidframe_timestamps": [ + 15000, + 354000, + 693000, + 1032000, + 1371000 + ], + "file_info_type": "mediainfo", + "file_info": "General\nUnique ID : 196382376391031659169980002506916611524 (0x93BDDEB35938CEE294C45B12E5A5C5C4)\nComplete name : [EMBER] Solo Leveling - 12.mkv\nFormat : Matroska\nFormat version : Version 4\nFile size : 352 MiB\nDuration : 23 min 40 s\nOverall bit rate : 2 082 kb/s\nFrame rate : 23.976 FPS\nMovie name : S01E12-Arise\nEncoded date : 2024-03-31 02:15:23 UTC\nWriting application : mkvmerge v69.0.0 ('Day And Age') 64-bit\nWriting library : libebml v1.4.2 + libmatroska v1.6.4\nCover : Yes\nAttachments : cover.jpg / Roboto-Medium.ttf / Roboto-MediumItalic.ttf / arial.ttf / arialbd.ttf / comic.ttf / comicbd.ttf / times.ttf / timesbd.ttf / trebuc.ttf / trebucbd.ttf / verdana.ttf / verdanab.ttf / CONSOLA.TTF / CONSOLAB.TTF\n\nVideo\nID : 1\nFormat : HEVC\nFormat/Info : High Efficiency Video Coding\nFormat profile : Main 10@L4@High\nCodec ID : V_MPEGH/ISO/HEVC\nDuration : 23 min 40 s\nBit rate : 1 914 kb/s\nWidth : 1 920 pixels\nHeight : 1 080 pixels\nDisplay aspect ratio : 16:9\nFrame rate mode : Constant\nFrame rate : 23.976 (24000/1001) FPS\nColor space : YUV\nChroma subsampling : 4:2:0\nBit depth : 10 bits\nBits/(Pixel*Frame) : 0.038\nStream size : 324 MiB (92%)\nTitle : Presented By EMBER\nWriting library : x265 2.4+14-bc0e9bd7c08f5ddc:[Windows][GCC 6.3.0][64 bit] 10bit: KG7x [x265.ru]\nEncoding settings : cpuid=1173503 / frame-threads=3 / numa-pools=8 / wpp / no-pmode / no-pme / no-psnr / no-ssim / log-level=2 / input-csp=1 / input-res=1920x1080 / interlace=0 / total-frames=0 / level-idc=40 / high-tier=1 / uhd-bd=0 / ref=4 / no-allow-non-conformance / no-repeat-headers / annexb / no-aud / no-hrd / info / hash=0 / no-temporal-layers / open-gop / min-keyint=23 / keyint=250 / bframes=8 / b-adapt=2 / b-pyramid / bframe-bias=0 / rc-lookahead=70 / lookahead-slices=4 / scenecut=40 / no-intra-refresh / ctu=64 / min-cu-size=8 / rect / no-amp / max-tu-size=32 / tu-inter-depth=2 / tu-intra-depth=2 / limit-tu=4 / rdoq-level=2 / dynamic-rd=0.00 / no-ssim-rd / signhide / no-tskip / nr-intra=0 / nr-inter=0 / no-constrained-intra / no-strong-intra-smoothing / max-merge=3 / limit-refs=3 / limit-modes / me=3 / subme=3 / merange=57 / temporal-mvp / weightp / no-weightb / no-analyze-src-pics / deblock=1:1 / no-sao / no-sao-non-deblock / rd=4 / no-early-skip / rskip / no-fast-intra / no-tskip-fast / no-cu-lossless / no-b-intra / rdpenalty=0 / psy-rd=1.20 / psy-rdoq=2.00 / no-rd-refine / analysis-mode=0 / no-lossless / cbqpoffs=0 / crqpoffs=0 / rc=crf / crf=23.0 / qcomp=0.60 / qpstep=4 / stats-write=0 / stats-read=0 / vbv-maxrate=30000 / vbv-bufsize=30000 / vbv-init=0.9 / crf-max=0.0 / crf-min=0.0 / ipratio=1.40 / pbratio=1.30 / aq-mode=3 / aq-strength=0.70 / cutree / zone-count=0 / no-strict-cbr / qg-size=32 / no-rc-grain / qpmax=69 / qpmin=0 / sar=0 / overscan=0 / videoformat=5 / range=0 / colorprim=1 / transfer=1 / colormatrix=1 / chromaloc=0 / display-window=0 / max-cll=0,0 / min-luma=0 / max-luma=1023 / log2-max-poc-lsb=8 / vui-timing-info / vui-hrd-info / slices=1 / opt-qp-pps / opt-ref-list-length-pps / no-multi-pass-opt-rps / scenecut-bias=0.05 / no-opt-cu-delta-qp / no-aq-motion / no-hdr / no-hdr-opt / no-dhdr10-opt / refine-level=5 / no-limit-sao / ctu-info=0\nDefault : Yes\nForced : No\nColor range : Limited\nColor primaries : BT.709\nTransfer characteristics : BT.709\nMatrix coefficients : BT.709\n\nAudio\nID : 2\nFormat : AAC LC\nFormat/Info : Advanced Audio Codec Low Complexity\nCodec ID : A_AAC-2\nDuration : 23 min 40 s\nBit rate : 128 kb/s\nChannel(s) : 2 channels\nChannel layout : L R\nSampling rate : 44.1 kHz\nFrame rate : 43.066 FPS (1024 SPF)\nCompression mode : Lossy\nStream size : 21.7 MiB (6%)\nLanguage : Japanese\nDefault : Yes\nForced : No\n\nText\nID : 3\nFormat : ASS\nCodec ID : S_TEXT/ASS\nCodec ID/Info : Advanced Sub Station Alpha\nDuration : 23 min 20 s\nBit rate : 98 b/s\nFrame rate : 0.168 FPS\nCount of elements : 235\nCompression mode : Lossless\nStream size : 16.8 KiB (0%)\nLanguage : English\nDefault : Yes\nForced : No\n\nMenu\n00:00:00.000 : en:Intro\n00:02:00.000 : en:OP\n00:03:30.000 : en:Main EP\n00:21:42.000 : en:ED\n00:23:12.000 : en:Extra", + "attachments": [ + { + "id": 268579, + "filename": "arial.ttf", + "type": "other", + "info": { + "mime": "application/x-truetype-font" + }, + "size": 892680 + }, + { + "id": 624527, + "filename": "arialbd.ttf", + "type": "other", + "info": { + "mime": "application/x-truetype-font" + }, + "size": 854584 + }, + { + "id": 1962029, + "filename": "chapters.xml", + "type": "xmlmeta", + "info": { + "meta": "chapters" + }, + "size": 1874 + }, + { + "id": 9973, + "filename": "comic.ttf", + "type": "other", + "info": { + "mime": "application/x-truetype-font" + }, + "size": 240588 + }, + { + "id": 13424, + "filename": "comicbd.ttf", + "type": "other", + "info": { + "mime": "application/x-truetype-font" + }, + "size": 224404 + }, + { + "id": 727768, + "filename": "CONSOLA.TTF", + "type": "other", + "info": { + "mime": "application/x-truetype-font" + }, + "size": 358460 + }, + { + "id": 183919, + "filename": "CONSOLAB.TTF", + "type": "other", + "info": { + "mime": "application/x-truetype-font" + }, + "size": 368720 + }, + { + "id": 1900106, + "filename": "cover.jpg", + "type": "other", + "info": { + "mime": "image/jpeg" + }, + "size": 178243 + }, + { + "id": 1066790, + "filename": "Roboto-Medium.ttf", + "type": "other", + "info": { + "mime": "application/x-truetype-font" + }, + "size": 351704 + }, + { + "id": 1066791, + "filename": "Roboto-MediumItalic.ttf", + "type": "other", + "info": { + "mime": "application/x-truetype-font" + }, + "size": 378412 + }, + { + "id": 1962028, + "filename": "tags.xml", + "type": "xmlmeta", + "info": { + "meta": "tags" + }, + "size": 2698 + }, + { + "id": 653220, + "filename": "times.ttf", + "type": "other", + "info": { + "mime": "application/x-truetype-font" + }, + "size": 975228 + }, + { + "id": 653221, + "filename": "timesbd.ttf", + "type": "other", + "info": { + "mime": "application/x-truetype-font" + }, + "size": 980120 + }, + { + "id": 1961547, + "filename": "track3.eng.ass", + "type": "subtitle", + "info": { + "number": 3, + "codec": "ASS", + "lang": "eng", + "default": 1, + "enabled": 1, + "forced": 0, + "trackid": 2, + "tracknum": 3 + }, + "size": 25988 + }, + { + "id": 71778, + "filename": "trebuc.ttf", + "type": "other", + "info": { + "mime": "application/x-truetype-font" + }, + "size": 218432 + }, + { + "id": 16207, + "filename": "trebucbd.ttf", + "type": "other", + "info": { + "mime": "application/x-truetype-font" + }, + "size": 206776 + }, + { + "id": 1149, + "filename": "verdana.ttf", + "type": "other", + "info": { + "mime": "application/x-truetype-font" + }, + "size": 240456 + }, + { + "id": 8678, + "filename": "verdanab.ttf", + "type": "other", + "info": { + "mime": "application/x-truetype-font" + }, + "size": 208456 + } + ], + "links": { + "ClickNUpload": "https://clicknupload.red/ydok51qdzjr7", + "DailyUploads": "https://dailyuploads.net/nksc253gopcq", + "DownloadGG": "https://download.gg/file-18642810_87ce8be6abbb74f6", + "GoFile": "https://gofile.io/d/XgPwnN", + "KrakenFiles": "https://krakenfiles.com/view/9lJPP2MvAt/file.html", + "MdiaLoad": "https://down.mdiaload.com/t9w39f21ad9o", + "MultiUp": "https://multiup.io/download/b7958d691eb0b38477acdb63db00cca6/%5BEMBER%5D%20Solo%20Leveling%20-%2012.mkv", + "Uppit": "http://uppit.com/g58333gdg3ei" + } + } + ] +} \ No newline at end of file diff --git a/tests/subliminal_patch/test_animetosho.py b/tests/subliminal_patch/test_animetosho.py new file mode 100644 index 000000000..acb62781c --- /dev/null +++ b/tests/subliminal_patch/test_animetosho.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 + +import os +import pytest + +from subliminal_patch.core import Episode +from subliminal_patch.providers.animetosho import AnimeToshoProvider +from subzero.language import Language + +@pytest.fixture(scope="session") +def anime_episodes(): + return { + "frieren_s01e01": Episode( + "Frieren - Beyond Journey's End S01E28 1080p WEB x264 AAC -Tsundere-Raws (CR) (Sousou no Frieren).mkv", + "Frieren: Beyond Journey's End", + 1, + 28, + source="Web", + series_anidb_id=17617, + series_anidb_episode_id=271418, + series_tvdb_id=424536, + series_imdb_id="tt22248376", + release_group="Tsundere-Raws", + resolution="1080p", + video_codec="H.264", + ), + "solo_leveling_s01e10": Episode( + "[New-raws] Ore Dake Level Up na Ken - 12 END [1080p] [AMZN].mkv", + "Solo Leveling", + 1, + 12, + source="Web", + series_anidb_id=17495, + series_anidb_episode_id=277518, + series_tvdb_id=389597, + series_imdb_id="tt21209876", + release_group="New-raws", + resolution="1080p", + video_codec="H.264", + ), + } + + +def test_list_subtitles(anime_episodes, requests_mock, data): + language = Language("eng") + item = anime_episodes["solo_leveling_s01e10"] + + with open(os.path.join(data, 'animetosho_episode_response.json'), "rb") as f: + requests_mock.get(' https://feed.animetosho.org/json?eid=277518', content=f.read()) + + with open(os.path.join(data, 'animetosho_series_response.json'), "rb") as f: + response = f.read() + requests_mock.get('https://feed.animetosho.org/json?show=torrent&id=608516', content=response) + requests_mock.get('https://feed.animetosho.org/json?show=torrent&id=608526', content=response) + + with AnimeToshoProvider(2) as provider: + subtitles = provider.list_subtitles(item, languages={language}) + + assert len(subtitles) == 2