diff --git a/Dockerfile b/Dockerfile
index 950be92b..b8260679 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -70,6 +70,7 @@ RUN sed -i 's/^user www\-data\;$/user root\;/' /etc/nginx/nginx.conf
COPY ./backend /app
COPY ./docker_assets/run.sh /app
COPY ./docker_assets/backend_start.py /app
+COPY ./docker_assets/beat_auto_spawn.sh /app
COPY --from=node-builder ./frontend/dist /app/static
diff --git a/README.md b/README.md
index b63fbd68..7d3b2a96 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-
+
[*more screenshots and video*](SHOWCASE.MD)
@@ -23,13 +23,13 @@
------------------------
## Core functionality
-Once your YouTube video collection grows, it becomes hard to search and find a specific video. That's where Tube Archivist comes in: By indexing your video collection with metadata from YouTube, you can organize, search and enjoy your archived YouTube videos without hassle offline through a convenient web interface. This includes:
+Once your YouTube video collection grows, it becomes hard to search and find a specific video. That's where Tube Archivist comes in: By indexing your video collection with metadata from YouTube, you can organize, search and enjoy your archived YouTube videos without hassle offline through a convenient web interface. This includes:
* Subscribe to your favorite YouTube channels
* Download Videos using **[yt-dlp](https://github.com/yt-dlp/yt-dlp)**
* Index and make videos searchable
* Play videos
* Keep track of viewed and unviewed videos
-
+
## Resources
- [Discord](https://www.tubearchivist.com/discord): Connect with us on our Discord server.
- [r/TubeArchivist](https://www.reddit.com/r/TubeArchivist/): Join our Subreddit.
@@ -38,8 +38,8 @@ Once your YouTube video collection grows, it becomes hard to search and find a s
- [Plex Plugin](https://github.com/tubearchivist/tubearchivist-plex): Add your videos to Plex
## Installing
-For minimal system requirements, the Tube Archivist stack needs around 2GB of available memory for a small testing setup and around 4GB of available memory for a mid to large sized installation. Minimal with dual core with 4 threads, better quad core plus.
-This project requires docker. Ensure it is installed and running on your system.
+For minimal system requirements, the Tube Archivist stack needs around 2GB of available memory for a small testing setup and around 4GB of available memory for a mid to large sized installation. Minimal with dual core with 4 threads, better quad core plus.
+This project requires docker. Ensure it is installed and running on your system.
The documentation has additional user provided instructions for [Unraid](https://docs.tubearchivist.com/installation/unraid/), [Synology](https://docs.tubearchivist.com/installation/synology/) and [Podman](https://docs.tubearchivist.com/installation/podman/).
@@ -49,7 +49,7 @@ Take a look at the example [docker-compose.yml](https://github.com/tubearchivist
All environment variables are explained in detail in the docs [here](https://docs.tubearchivist.com/installation/env-vars/).
-**TubeArchivist**:
+**TubeArchivist**:
| Environment Var | Value | |
| ----------- | ----------- | ----------- |
| TA_HOST | Server IP or hostname `http://tubearchivist.local:8000` | Required |
@@ -72,8 +72,17 @@ All environment variables are explained in detail in the docs [here](https://doc
| TA_LDAP | Configure TA to use LDAP Authentication | [Read more](https://docs.tubearchivist.com/configuration/ldap/) |
| DISABLE_STATIC_AUTH | Remove authentication from media files, (Google Cast...) | [Read more](https://docs.tubearchivist.com/installation/env-vars/#disable_static_auth) |
| DJANGO_DEBUG | Return additional error messages, for debug only | Optional |
+| TA_LOGIN_AUTH_MODE | Configure the order of login authentication backends (Default: single) | Optional |
-**ElasticSearch**
+| TA_LOGIN_AUTH_MODE value | Description |
+| ------------------------ | ----------- |
+| single | Only use a single backend (default, or LDAP, or Forward auth, selected by TA_LDAP or TA_ENABLE_AUTH_PROXY) |
+| local | Use local password database only |
+| ldap | Use LDAP backend only |
+| forwardauth | Use reverse proxy headers only |
+| ldap_local | Use LDAP backend in addition to the local password database |
+
+**ElasticSearch**
| Environment Var | Value | State |
| ----------- | ----------- | ----------- |
| ELASTIC_PASSWORD | Matching password `ELASTIC_PASSWORD` from TubeArchivist | Required |
@@ -81,58 +90,58 @@ All environment variables are explained in detail in the docs [here](https://doc
## Update
-Always use the *latest* (the default) or a named semantic version tag for the docker images. The *unstable* tags are only for your testing environment, there might not be an update path for these testing builds.
+Always use the *latest* (the default) or a named semantic version tag for the docker images. The *unstable* tags are only for your testing environment, there might not be an update path for these testing builds.
-You will see the current version number of **Tube Archivist** in the footer of the interface. There is a daily version check task querying tubearchivist.com, notifying you of any new releases in the footer. To update, you need to update the docker images, the method for which will depend on your platform. For example, if you're using `docker-compose`, run `docker-compose pull` and then restart with `docker-compose up -d`. After updating, check the footer to verify you are running the expected version.
+You will see the current version number of **Tube Archivist** in the footer of the interface. There is a daily version check task querying tubearchivist.com, notifying you of any new releases in the footer. To update, you need to update the docker images, the method for which will depend on your platform. For example, if you're using `docker-compose`, run `docker-compose pull` and then restart with `docker-compose up -d`. After updating, check the footer to verify you are running the expected version.
- - This project is tested for updates between one or two releases maximum. Further updates back may or may not be supported and you might have to reset your index and configurations to update. Ideally apply new updates at least once per month.
- - There can be breaking changes between updates, particularly as the application grows, new environment variables or settings might be required for you to set in the your docker-compose file. *Always* check the **release notes**: Any breaking changes will be marked there.
- - All testing and development is done with the Elasticsearch version number as mentioned in the provided *docker-compose.yml* file. This will be updated when a new release of Elasticsearch is available. Running an older version of Elasticsearch is most likely not going to result in any issues, but it's still recommended to run the same version as mentioned. Use `bbilly1/tubearchivist-es` to automatically get the recommended version.
+ - This project is tested for updates between one or two releases maximum. Further updates back may or may not be supported and you might have to reset your index and configurations to update. Ideally apply new updates at least once per month.
+ - There can be breaking changes between updates, particularly as the application grows, new environment variables or settings might be required for you to set in the your docker-compose file. *Always* check the **release notes**: Any breaking changes will be marked there.
+ - All testing and development is done with the Elasticsearch version number as mentioned in the provided *docker-compose.yml* file. This will be updated when a new release of Elasticsearch is available. Running an older version of Elasticsearch is most likely not going to result in any issues, but it's still recommended to run the same version as mentioned. Use `bbilly1/tubearchivist-es` to automatically get the recommended version.
## Getting Started
1. Go through the **settings** page and look at the available options. Particularly set *Download Format* to your desired video quality before downloading. **Tube Archivist** downloads the best available quality by default. To support iOS or MacOS and some other browsers a compatible format must be specified. For example:
```
bestvideo[vcodec*=avc1]+bestaudio[acodec*=mp4a]/mp4
```
-2. Subscribe to some of your favorite YouTube channels on the **channels** page.
+2. Subscribe to some of your favorite YouTube channels on the **channels** page.
3. On the **downloads** page, click on *Rescan subscriptions* to add videos from the subscribed channels to your Download queue or click on *Add to download queue* to manually add Video IDs, links, channels or playlists.
-4. Click on *Start download* and let **Tube Archivist** to it's thing.
+4. Click on *Start download* and let **Tube Archivist** to it's thing.
5. Enjoy your archived collection!
-### Port Collisions
-If you have a collision on port `8000`, best solution is to use dockers *HOST_PORT* and *CONTAINER_PORT* distinction: To for example change the interface to port 9000 use `9000:8000` in your docker-compose file.
+### Port Collisions
+If you have a collision on port `8000`, best solution is to use dockers *HOST_PORT* and *CONTAINER_PORT* distinction: To for example change the interface to port 9000 use `9000:8000` in your docker-compose file.
For more information on port collisions, check the docs.
-## Common Errors
-Here is a list of common errors and their solutions.
+## Common Errors
+Here is a list of common errors and their solutions.
### `vm.max_map_count`
**Elastic Search** in Docker requires the kernel setting of the host machine `vm.max_map_count` to be set to at least 262144.
-To temporary set the value run:
+To temporary set the value run:
```
sudo sysctl -w vm.max_map_count=262144
-```
-To apply the change permanently depends on your host operating system:
+```
+To apply the change permanently depends on your host operating system:
- For example on Ubuntu Server add `vm.max_map_count = 262144` to the file `/etc/sysctl.conf`.
- - On Arch based systems create a file `/etc/sysctl.d/max_map_count.conf` with the content `vm.max_map_count = 262144`.
- - On any other platform look up in the documentation on how to pass kernel parameters.
+ - On Arch based systems create a file `/etc/sysctl.d/max_map_count.conf` with the content `vm.max_map_count = 262144`.
+ - On any other platform look up in the documentation on how to pass kernel parameters.
### Permissions for elasticsearch
-If you see a message similar to `Unable to access 'path.repo' (/usr/share/elasticsearch/data/snapshot)` or `failed to obtain node locks, tried [/usr/share/elasticsearch/data]` and `maybe these locations are not writable` when initially starting elasticsearch, that probably means the container is not allowed to write files to the volume.
+If you see a message similar to `Unable to access 'path.repo' (/usr/share/elasticsearch/data/snapshot)` or `failed to obtain node locks, tried [/usr/share/elasticsearch/data]` and `maybe these locations are not writable` when initially starting elasticsearch, that probably means the container is not allowed to write files to the volume.
To fix that issue, shutdown the container and on your host machine run:
```
chown 1000:0 -R /path/to/mount/point
```
-This will match the permissions with the **UID** and **GID** of elasticsearch process within the container and should fix the issue.
+This will match the permissions with the **UID** and **GID** of elasticsearch process within the container and should fix the issue.
### Disk usage
-The Elasticsearch index will turn to ***read only*** if the disk usage of the container goes above 95% until the usage drops below 90% again, you will see error messages like `disk usage exceeded flood-stage watermark`.
+The Elasticsearch index will turn to ***read only*** if the disk usage of the container goes above 95% until the usage drops below 90% again, you will see error messages like `disk usage exceeded flood-stage watermark`.
Similar to that, TubeArchivist will become all sorts of messed up when running out of disk space. There are some error messages in the logs when that happens, but it's best to make sure to have enough disk space before starting to download.
@@ -142,7 +151,7 @@ If you are seeing errors like `failed to create shim: OCI runtime create failed`
This can happen if you have nested virtualizations, e.g. LXC running Docker in Proxmox.
## Known limitations
-- Video files created by Tube Archivist need to be playable in your browser of choice. Not every codec is compatible with every browser and might require some testing with format selection.
+- Video files created by Tube Archivist need to be playable in your browser of choice. Not every codec is compatible with every browser and might require some testing with format selection.
- Every limitation of **yt-dlp** will also be present in Tube Archivist. If **yt-dlp** can't download or extract a video for any reason, Tube Archivist won't be able to either.
- There is no flexibility in naming of the media files.
@@ -191,7 +200,7 @@ Implemented:
- [X] Scan your file system to index already downloaded videos [2021-09-14]
## User Scripts
-This is a list of useful user scripts, generously created from folks like you to extend this project and its functionality. Make sure to check the respective repository links for detailed license information.
+This is a list of useful user scripts, generously created from folks like you to extend this project and its functionality. Make sure to check the respective repository links for detailed license information.
This is your time to shine, [read this](https://github.com/tubearchivist/tubearchivist/blob/master/CONTRIBUTING.md#user-scripts) then open a PR to add your script here.
@@ -200,13 +209,13 @@ This is your time to shine, [read this](https://github.com/tubearchivist/tubearc
- [DarkFighterLuke/ta_base_url_nginx](https://gist.github.com/DarkFighterLuke/4561b6bfbf83720493dc59171c58ac36): Set base URL with Nginx when you can't use subdomains.
- [lamusmaser/ta_migration_helper](https://github.com/lamusmaser/ta_migration_helper): Advanced helper script for migration issues to TubeArchivist v0.4.4 or later.
- [lamusmaser/create_info_json](https://gist.github.com/lamusmaser/837fb58f73ea0cad784a33497932e0dd): Script to generate `.info.json` files using `ffmpeg` collecting information from downloaded videos.
-- [lamusmaser/ta_fix_for_video_redirection](https://github.com/lamusmaser/ta_fix_for_video_redirection): Script to fix videos that were incorrectly indexed by YouTube's "Video is Unavailable" response.
+- [lamusmaser/ta_fix_for_video_redirection](https://github.com/lamusmaser/ta_fix_for_video_redirection): Script to fix videos that were incorrectly indexed by YouTube's "Video is Unavailable" response.
- [RoninTech/ta-helper](https://github.com/RoninTech/ta-helper): Helper script to provide a symlink association to reference TubeArchivist videos with their original titles.
- [tangyjoust/Tautulli-Notify-TubeArchivist-of-Plex-Watched-State](https://github.com/tangyjoust/Tautulli-Notify-TubeArchivist-of-Plex-Watched-State) Mark videos watched in Plex (through streaming not manually) through Tautulli back to TubeArchivist
- [Dhs92/delete_shorts](https://github.com/Dhs92/delete_shorts): A script to delete ALL YouTube Shorts from TubeArchivist
## Donate
-The best donation to **Tube Archivist** is your time, take a look at the [contribution page](CONTRIBUTING.md) to get started.
+The best donation to **Tube Archivist** is your time, take a look at the [contribution page](CONTRIBUTING.md) to get started.
Second best way to support the development is to provide for caffeinated beverages:
* [GitHub Sponsor](https://github.com/sponsors/bbilly1) become a sponsor here on GitHub
* [Paypal.me](https://paypal.me/bbilly1) for a one time coffee
diff --git a/backend/appsettings/src/index_setup.py b/backend/appsettings/src/index_setup.py
index d22f07d3..657199ed 100644
--- a/backend/appsettings/src/index_setup.py
+++ b/backend/appsettings/src/index_setup.py
@@ -201,7 +201,15 @@ class ElasitIndexWrap:
if self.backup_run:
return
- config = AppConfig().config
+ try:
+ config = AppConfig().config
+ except ValueError:
+ # create defaults in ES if config not found
+ print("AppConfig not found, creating defaults...")
+ handler = AppConfig.__new__(AppConfig)
+ handler.sync_defaults()
+ config = AppConfig.CONFIG_DEFAULTS
+
if config["application"]["enable_snapshot"]:
# take snapshot if enabled
ElasticSnapshot().take_snapshot_now(wait=True)
diff --git a/backend/channel/serializers.py b/backend/channel/serializers.py
index 568e4eed..7b2a9ec8 100644
--- a/backend/channel/serializers.py
+++ b/backend/channel/serializers.py
@@ -90,6 +90,7 @@ class ChannelNavSerializer(serializers.Serializer):
"""serialize channel navigation"""
has_pending = serializers.BooleanField()
+ has_ignored = serializers.BooleanField()
has_playlists = serializers.BooleanField()
has_videos = serializers.BooleanField()
has_streams = serializers.BooleanField()
diff --git a/backend/channel/src/index.py b/backend/channel/src/index.py
index 929aa5c9..65503ef7 100644
--- a/backend/channel/src/index.py
+++ b/backend/channel/src/index.py
@@ -14,7 +14,6 @@ from common.src.helper import rand_sleep
from common.src.index_generic import YouTubeItem
from download.src.thumbnails import ThumbManager
from download.src.yt_dlp_base import YtWrap
-from playlist.src.index import YoutubePlaylist
class YoutubeChannel(YouTubeItem):
@@ -215,6 +214,8 @@ class YoutubeChannel(YouTubeItem):
def delete_playlists(self):
"""delete all indexed playlist from es"""
+ from playlist.src.index import YoutubePlaylist
+
all_playlists = self.get_indexed_playlists()
for playlist in all_playlists:
YoutubePlaylist(playlist["playlist_id"]).delete_metadata()
@@ -277,6 +278,8 @@ class YoutubeChannel(YouTubeItem):
@staticmethod
def _index_single_playlist(playlist):
"""add single playlist if needed"""
+ from playlist.src.index import YoutubePlaylist
+
playlist = YoutubePlaylist(playlist[0])
playlist.update_playlist(skip_on_empty=True)
diff --git a/backend/channel/src/nav.py b/backend/channel/src/nav.py
index 6ff3e047..44b15f8b 100644
--- a/backend/channel/src/nav.py
+++ b/backend/channel/src/nav.py
@@ -13,6 +13,7 @@ class ChannelNav:
"""build nav items"""
nav = {
"has_pending": self._get_has_pending(),
+ "has_ignored": self._get_has_ignored(),
"has_playlists": self._get_has_playlists(),
}
nav.update(self._get_vid_types())
@@ -63,6 +64,24 @@ class ChannelNav:
return bool(response["hits"]["hits"])
+ def _get_has_ignored(self):
+ """Check if there are ignored videos in the download queue"""
+ data = {
+ "size": 1,
+ "query": {
+ "bool": {
+ "must": [
+ {"term": {"status": {"value": "ignore"}}},
+ {"term": {"channel_id": {"value": self.channel_id}}},
+ ]
+ }
+ },
+ "_source": False,
+ }
+ response, _ = ElasticWrap("ta_download/_search").get(data=data)
+
+ return bool(response["hits"]["hits"])
+
def _get_has_playlists(self):
"""check if channel has playlists"""
path = "ta_playlist/_search"
diff --git a/backend/common/src/helper.py b/backend/common/src/helper.py
index 263018db..fcf75dfe 100644
--- a/backend/common/src/helper.py
+++ b/backend/common/src/helper.py
@@ -8,7 +8,7 @@ import os
import random
import string
import subprocess
-from datetime import datetime
+from datetime import datetime, timezone
from time import sleep
from typing import Any
from urllib.parse import urlparse
@@ -106,13 +106,14 @@ def requests_headers() -> dict[str, str]:
def date_parser(timestamp: int | str) -> str:
"""return formatted date string"""
if isinstance(timestamp, int):
- date_obj = datetime.fromtimestamp(timestamp)
+ date_obj = datetime.fromtimestamp(timestamp, tz=timezone.utc)
elif isinstance(timestamp, str):
date_obj = datetime.strptime(timestamp, "%Y-%m-%d")
+ date_obj = date_obj.replace(tzinfo=timezone.utc)
else:
raise TypeError(f"invalid timestamp: {timestamp}")
- return date_obj.date().isoformat()
+ return date_obj.isoformat()
def time_parser(timestamp: str) -> float:
diff --git a/backend/common/tests/test_src/test_helper.py b/backend/common/tests/test_src/test_helper.py
index e99808cd..013e774b 100644
--- a/backend/common/tests/test_src/test_helper.py
+++ b/backend/common/tests/test_src/test_helper.py
@@ -22,14 +22,14 @@ def test_randomizor_with_positive_length():
def test_date_parser_with_int():
"""unix timestamp"""
timestamp = 1621539600
- expected_date = "2021-05-20"
+ expected_date = "2021-05-20T19:40:00+00:00"
assert date_parser(timestamp) == expected_date
def test_date_parser_with_str():
"""iso timestamp"""
date_str = "2021-05-21"
- expected_date = "2021-05-21"
+ expected_date = "2021-05-21T00:00:00+00:00"
assert date_parser(date_str) == expected_date
diff --git a/backend/config/management/commands/ta_connection.py b/backend/config/management/commands/ta_connection.py
index 3edb42db..4ad511d1 100644
--- a/backend/config/management/commands/ta_connection.py
+++ b/backend/config/management/commands/ta_connection.py
@@ -99,7 +99,12 @@ class Command(BaseCommand):
continue
if status_code and status_code == 200:
- path = "_cluster/health?wait_for_status=yellow&timeout=60s"
+ path = (
+ "_cluster/health?"
+ "wait_for_status=yellow&"
+ "timeout=60s&"
+ "wait_for_active_shards=1"
+ )
_, _ = ElasticWrap(path).get(timeout=60)
self.stdout.write(
self.style.SUCCESS(" ✓ ES connection established")
diff --git a/backend/config/management/commands/ta_startup.py b/backend/config/management/commands/ta_startup.py
index e80af6b7..980354b1 100644
--- a/backend/config/management/commands/ta_startup.py
+++ b/backend/config/management/commands/ta_startup.py
@@ -263,7 +263,7 @@ class Command(BaseCommand):
def _init_app_config(self) -> None:
"""init default app config to ES"""
self.stdout.write("[10] Check AppConfig")
- _, status_code = ElasticWrap("ta_config/_doc/appsettings").get()
+ response, status_code = ElasticWrap("ta_config/_doc/appsettings").get()
if status_code in [200, 201]:
self.stdout.write(
self.style.SUCCESS(" skip completed appsettings init")
@@ -276,6 +276,13 @@ class Command(BaseCommand):
return
+ if status_code != 404:
+ message = " 🗙 ta_config index lookup failed"
+ self.stdout.write(self.style.ERROR(message))
+ self.stdout.write(response)
+ sleep(60)
+ raise CommandError(message)
+
handler = AppConfig.__new__(AppConfig)
_, status_code = handler.sync_defaults()
self.stdout.write(
diff --git a/backend/config/settings.py b/backend/config/settings.py
index 5b51647e..b2d9318f 100644
--- a/backend/config/settings.py
+++ b/backend/config/settings.py
@@ -195,7 +195,6 @@ if bool(environ.get("TA_LDAP")):
ldap.OPT_X_TLS_REQUIRE_CERT: ldap.OPT_X_TLS_NEVER,
}
- AUTHENTICATION_BACKENDS = ("django_auth_ldap.backend.LDAPBackend",)
# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
@@ -239,10 +238,31 @@ if bool(environ.get("TA_ENABLE_AUTH_PROXY")):
MIDDLEWARE.append("user.src.remote_user_auth.HttpRemoteUserMiddleware")
+
+# Configure Authentication Backend Combinations
+_login_auth_mode = (environ.get("TA_LOGIN_AUTH_MODE") or "single").casefold()
+if _login_auth_mode == "local":
+ AUTHENTICATION_BACKENDS = ("django.contrib.auth.backends.ModelBackend",)
+elif _login_auth_mode == "ldap":
+ AUTHENTICATION_BACKENDS = ("django_auth_ldap.backend.LDAPBackend",)
+elif _login_auth_mode == "forwardauth":
AUTHENTICATION_BACKENDS = (
"django.contrib.auth.backends.RemoteUserBackend",
)
-
+elif _login_auth_mode == "ldap_local":
+ AUTHENTICATION_BACKENDS = (
+ "django_auth_ldap.backend.LDAPBackend",
+ "django.contrib.auth.backends.ModelBackend",
+ )
+else:
+ # If none of these cases match, AUTHENTICATION_BACKENDS is unset, which
+ # means the ModelBackend should be used by default
+ if bool(environ.get("TA_LDAP")):
+ AUTHENTICATION_BACKENDS = ("django_auth_ldap.backend.LDAPBackend",)
+ if bool(environ.get("TA_ENABLE_AUTH_PROXY")):
+ AUTHENTICATION_BACKENDS = (
+ "django.contrib.auth.backends.RemoteUserBackend",
+ )
# Internationalization
# https://docs.djangoproject.com/en/3.2/topics/i18n/
diff --git a/backend/download/src/queue.py b/backend/download/src/queue.py
index eae697d5..7110ceaf 100644
--- a/backend/download/src/queue.py
+++ b/backend/download/src/queue.py
@@ -167,14 +167,14 @@ class PendingList(PendingIndex):
self.to_skip = False
self.missing_videos = False
- def parse_url_list(self):
+ def parse_url_list(self, auto_start=False):
"""extract youtube ids from list"""
self.missing_videos = []
self.get_download()
self.get_indexed()
total = len(self.youtube_ids)
for idx, entry in enumerate(self.youtube_ids):
- self._process_entry(entry)
+ self._process_entry(entry, auto_start=auto_start)
if not self.task:
continue
@@ -183,11 +183,11 @@ class PendingList(PendingIndex):
progress=(idx + 1) / total,
)
- def _process_entry(self, entry):
+ def _process_entry(self, entry, auto_start=False):
"""process single entry from url list"""
vid_type = self._get_vid_type(entry)
if entry["type"] == "video":
- self._add_video(entry["url"], vid_type)
+ self._add_video(entry["url"], vid_type, auto_start=auto_start)
elif entry["type"] == "channel":
self._parse_channel(entry["url"], vid_type)
elif entry["type"] == "playlist":
@@ -204,8 +204,14 @@ class PendingList(PendingIndex):
return VideoTypeEnum(vid_type_str)
- def _add_video(self, url, vid_type):
+ def _add_video(self, url, vid_type, auto_start=False):
"""add video to list"""
+ if auto_start and url in set(
+ i["youtube_id"] for i in self.all_pending
+ ):
+ PendingInteract(youtube_id=url, status="priority").update_status()
+ return
+
if url not in self.missing_videos and url not in self.to_skip:
self.missing_videos.append((url, vid_type))
else:
@@ -330,9 +336,6 @@ class PendingList(PendingIndex):
def _parse_youtube_details(self, vid, vid_type=VideoTypeEnum.VIDEOS):
"""parse response"""
vid_id = vid.get("id")
- published = datetime.strptime(vid["upload_date"], "%Y%m%d").strftime(
- "%Y-%m-%d"
- )
# build dict
youtube_details = {
@@ -342,10 +345,23 @@ class PendingList(PendingIndex):
"title": vid["title"],
"channel_id": vid["channel_id"],
"duration": get_duration_str(vid["duration"]),
- "published": published,
+ "published": self._build_published(vid),
"timestamp": int(datetime.now().timestamp()),
"vid_type": vid_type.value,
"channel_indexed": vid["channel_id"] in self.all_channels,
}
return youtube_details
+
+ @staticmethod
+ def _build_published(vid):
+ """build published date or timestamp"""
+ timestamp = vid["timestamp"]
+ if timestamp:
+ return timestamp
+
+ upload_date = vid["upload_date"]
+ upload_date_time = datetime.strptime(upload_date, "%Y%m%d")
+ published = upload_date_time.strftime("%Y-%m-%d")
+
+ return published
diff --git a/backend/download/src/yt_dlp_base.py b/backend/download/src/yt_dlp_base.py
index 05043123..c80f9d7d 100644
--- a/backend/download/src/yt_dlp_base.py
+++ b/backend/download/src/yt_dlp_base.py
@@ -20,7 +20,6 @@ class YtWrap:
OBS_BASE = {
"default_search": "ytsearch",
"quiet": True,
- "check_formats": "selected",
"socket_timeout": 10,
"extractor_retries": 3,
"retries": 10,
@@ -66,6 +65,7 @@ class YtWrap:
def download(self, url):
"""make download request"""
+ self.obs.update({"check_formats": "selected"})
with yt_dlp.YoutubeDL(self.obs) as ydl:
try:
ydl.download([url])
diff --git a/backend/playlist/src/index.py b/backend/playlist/src/index.py
index 584342b3..3533205a 100644
--- a/backend/playlist/src/index.py
+++ b/backend/playlist/src/index.py
@@ -7,7 +7,6 @@ functionality:
import json
from datetime import datetime
-from channel.src import index as channel
from common.src.env_settings import EnvironmentSettings
from common.src.es_connect import ElasticWrap, IndexPaginate
from common.src.index_generic import YouTubeItem
@@ -74,8 +73,10 @@ class YoutubePlaylist(YouTubeItem):
def _ensure_channel(self):
"""make sure channel is indexed"""
+ from channel.src.index import YoutubeChannel
+
channel_id = self.json_data["playlist_channel_id"]
- channel_handler = channel.YoutubeChannel(channel_id)
+ channel_handler = YoutubeChannel(channel_id)
channel_handler.build_json(upload=True)
def get_local_vids(self) -> list[str]:
diff --git a/backend/task/tasks.py b/backend/task/tasks.py
index 31d7eb83..525938b4 100644
--- a/backend/task/tasks.py
+++ b/backend/task/tasks.py
@@ -156,7 +156,7 @@ def extrac_dl(self, youtube_ids, auto_start=False, status="pending"):
to_add = youtube_ids
pending_handler = PendingList(youtube_ids=to_add, task=self)
- pending_handler.parse_url_list()
+ pending_handler.parse_url_list(auto_start=auto_start)
videos_added = pending_handler.add_to_pending(
status=status, auto_start=auto_start
)
diff --git a/backend/video/serializers.py b/backend/video/serializers.py
index 5e0be9f8..afd8b0b4 100644
--- a/backend/video/serializers.py
+++ b/backend/video/serializers.py
@@ -12,6 +12,7 @@ class PlayerSerializer(serializers.Serializer):
"""serialize player"""
watched = serializers.BooleanField()
+ watched_date = serializers.IntegerField(required=False)
duration = serializers.IntegerField()
duration_str = serializers.CharField()
progress = serializers.FloatField(required=False)
diff --git a/backend/video/src/index.py b/backend/video/src/index.py
index 42ce02ac..01b8435b 100644
--- a/backend/video/src/index.py
+++ b/backend/video/src/index.py
@@ -173,9 +173,6 @@ class YoutubeVideo(YouTubeItem, YoutubeSubtitle):
self._validate_id()
# extract
self.channel_id = self.youtube_meta["channel_id"]
- upload_date = self.youtube_meta["upload_date"]
- upload_date_time = datetime.strptime(upload_date, "%Y%m%d")
- published = upload_date_time.strftime("%Y-%m-%d")
last_refresh = int(datetime.now().timestamp())
# base64_blur = ThumbManager().get_base64_blur(self.youtube_id)
base64_blur = False
@@ -187,7 +184,7 @@ class YoutubeVideo(YouTubeItem, YoutubeSubtitle):
"vid_thumb_url": self.youtube_meta["thumbnail"],
"vid_thumb_base64": base64_blur,
"tags": self.youtube_meta.get("tags", []),
- "published": published,
+ "published": self._build_published(),
"vid_last_refresh": last_refresh,
"date_downloaded": last_refresh,
"youtube_id": self.youtube_id,
@@ -196,6 +193,18 @@ class YoutubeVideo(YouTubeItem, YoutubeSubtitle):
"active": True,
}
+ def _build_published(self):
+ """build published date or timestamp"""
+ timestamp = self.youtube_meta["timestamp"]
+ if timestamp:
+ return timestamp
+
+ upload_date = self.youtube_meta["upload_date"]
+ upload_date_time = datetime.strptime(upload_date, "%Y%m%d")
+ published = upload_date_time.strftime("%Y-%m-%d")
+
+ return published
+
def _validate_id(self):
"""validate expected video ID, raise value error on mismatch"""
remote_id = self.youtube_meta["id"]
diff --git a/backend/video/src/query_building.py b/backend/video/src/query_building.py
index 248a0e44..fe4bc0b7 100644
--- a/backend/video/src/query_building.py
+++ b/backend/video/src/query_building.py
@@ -1,6 +1,7 @@
"""build query for video fetching"""
from common.src.ta_redis import RedisArchivist
+from playlist.src.index import YoutubePlaylist
from video.src.constants import OrderEnum, SortEnum, VideoTypeEnum
@@ -84,6 +85,11 @@ class QueryBuilder:
def parse_sort(self) -> dict | None:
"""build sort key"""
+ playlist = self.request_params.get("playlist")
+ if playlist:
+ # overwrite sort based on idx in playlist
+ return self._get_playlist_sort(playlist_id=playlist)
+
sort = self.request_params.get("sort")
if not sort:
return None
@@ -100,3 +106,39 @@ class QueryBuilder:
order_by = getattr(OrderEnum, order.upper()).value
return {"sort": [{sort_field: {"order": order_by}}]}
+
+ def _get_playlist_sort(self, playlist_id: str):
+ """get sort for playlist"""
+ playlist = YoutubePlaylist(playlist_id)
+ playlist.get_from_es()
+ if not playlist.json_data:
+ raise ValueError(f"playlist {playlist_id} not found")
+
+ sort_score = {
+ i["youtube_id"]: i["idx"]
+ for i in playlist.json_data["playlist_entries"]
+ if i["downloaded"]
+ }
+ script = (
+ "if(params.scores.containsKey(doc['youtube_id'].value)) "
+ + "{return params.scores[doc['youtube_id'].value];} "
+ + "return 100000;"
+ )
+
+ sort = {
+ "sort": [
+ {
+ "_script": {
+ "type": "number",
+ "script": {
+ "lang": "painless",
+ "source": script,
+ "params": {"scores": sort_score},
+ },
+ "order": "asc",
+ }
+ }
+ ],
+ }
+
+ return sort
diff --git a/backend/video/tests/test_src/test_query_building.py b/backend/video/tests/test_src/test_query_building.py
index 606d07c1..8dcbf50e 100644
--- a/backend/video/tests/test_src/test_query_building.py
+++ b/backend/video/tests/test_src/test_query_building.py
@@ -16,7 +16,6 @@ def test_build_data():
qb = QueryBuilder(
user_id=1,
channel="test_channel",
- playlist="test_playlist",
watch="watched",
type="videos",
sort="published",
diff --git a/docker_assets/beat_auto_spawn.sh b/docker_assets/beat_auto_spawn.sh
new file mode 100755
index 00000000..16002afc
--- /dev/null
+++ b/docker_assets/beat_auto_spawn.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+# auto restart beat scheduler
+# https://github.com/celery/django-celery-beat/issues/894
+
+if [[ -n "$DJANGO_DEBUG" ]]; then
+ LOGLEVEL="DEBUG"
+else
+ LOGLEVEL="INFO"
+fi
+
+COMMAND="celery -A task beat --loglevel=$LOGLEVEL --scheduler django_celery_beat.schedulers:DatabaseScheduler"
+TIMEOUT=3600
+
+while true; do
+ echo "Starting process beat scheduler"
+
+ $COMMAND &
+ PID=$!
+
+ sleep $TIMEOUT
+
+ # Kill the process if still running
+ if kill -0 $PID 2>/dev/null; then
+ echo "Killing beat process after $TIMEOUT seconds"
+ kill $PID
+ # Wait a bit to allow graceful shutdown, then force kill if needed
+ sleep 10
+ kill -9 $PID 2>/dev/null
+ fi
+
+ echo "Restarting beat..."
+done
diff --git a/docker_assets/run.sh b/docker_assets/run.sh
index cabc6afb..9f065d7b 100644
--- a/docker_assets/run.sh
+++ b/docker_assets/run.sh
@@ -3,6 +3,12 @@
set -e
+if [[ -n "$DJANGO_DEBUG" ]]; then
+ LOGLEVEL="DEBUG"
+else
+ LOGLEVEL="INFO"
+fi
+
# stop on pending manual migration
python manage.py ta_stop_on_error
@@ -18,10 +24,11 @@ python manage.py ta_startup
# start all tasks
nginx &
celery -A task.celery worker \
- --loglevel=INFO \
+ --loglevel=$LOGLEVEL \
--concurrency 4 \
--max-tasks-per-child 5 \
--max-memory-per-child 150000 &
-celery -A task beat --loglevel=INFO \
- --scheduler django_celery_beat.schedulers:DatabaseScheduler &
+
+./beat_auto_spawn.sh &
+
python backend_start.py
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 51ec571f..a5be2f31 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -8,26 +8,26 @@
"name": "tubearchivist-frontend",
"version": "0.5.1",
"dependencies": {
- "dompurify": "^3.2.5",
+ "dompurify": "^3.2.6",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-router-dom": "^7.6.0",
- "zustand": "^5.0.4"
+ "zustand": "^5.0.5"
},
"devDependencies": {
- "@types/react": "^19.1.3",
- "@types/react-dom": "^19.1.3",
- "@typescript-eslint/eslint-plugin": "^8.32.0",
- "@typescript-eslint/parser": "^8.32.0",
- "@vitejs/plugin-react-swc": "^3.9.0",
- "eslint": "^9.26.0",
+ "@types/react": "^19.1.5",
+ "@types/react-dom": "^19.1.5",
+ "@typescript-eslint/eslint-plugin": "^8.32.1",
+ "@typescript-eslint/parser": "^8.32.1",
+ "@vitejs/plugin-react-swc": "^3.10.0",
+ "eslint": "^9.27.0",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20",
"globals": "^16.1.0",
"prettier": "3.5.3",
"typescript": "^5.8.3",
- "typescript-eslint": "^8.32.0",
+ "typescript-eslint": "^8.32.1",
"vite": ">=6.3.5",
"vite-plugin-checker": "^0.9.3"
}
@@ -561,9 +561,9 @@
}
},
"node_modules/@eslint/core": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz",
- "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==",
+ "version": "0.14.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz",
+ "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -635,13 +635,16 @@
}
},
"node_modules/@eslint/js": {
- "version": "9.26.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.26.0.tgz",
- "integrity": "sha512-I9XlJawFdSMvWjDt6wksMCrgns5ggLNfFwFvnShsleWruvXM514Qxk8V246efTw+eo9JABvVz+u3q2RiAowKxQ==",
+ "version": "9.27.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz",
+ "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
}
},
"node_modules/@eslint/object-schema": {
@@ -655,13 +658,13 @@
}
},
"node_modules/@eslint/plugin-kit": {
- "version": "0.2.8",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz",
- "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==",
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz",
+ "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@eslint/core": "^0.13.0",
+ "@eslint/core": "^0.14.0",
"levn": "^0.4.1"
},
"engines": {
@@ -734,28 +737,6 @@
"url": "https://github.com/sponsors/nzakas"
}
},
- "node_modules/@modelcontextprotocol/sdk": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.11.1.tgz",
- "integrity": "sha512-9LfmxKTb1v+vUS1/emSk1f5ePmTLkb9Le9AxOB5T0XM59EUumwcS45z05h7aiZx3GI0Bl7mjb3FMEglYj+acuQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "content-type": "^1.0.5",
- "cors": "^2.8.5",
- "cross-spawn": "^7.0.3",
- "eventsource": "^3.0.2",
- "express": "^5.0.1",
- "express-rate-limit": "^7.5.0",
- "pkce-challenge": "^5.0.0",
- "raw-body": "^3.0.0",
- "zod": "^3.23.8",
- "zod-to-json-schema": "^3.24.1"
- },
- "engines": {
- "node": ">=18"
- }
- },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -794,6 +775,13 @@
"node": ">= 8"
}
},
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.9.tgz",
+ "integrity": "sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.39.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.39.0.tgz",
@@ -1075,9 +1063,9 @@
]
},
"node_modules/@swc/core": {
- "version": "1.11.21",
- "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.11.21.tgz",
- "integrity": "sha512-/Y3BJLcwd40pExmdar8MH2UGGvCBrqNN7hauOMckrEX2Ivcbv3IMhrbGX4od1dnF880Ed8y/E9aStZCIQi0EGw==",
+ "version": "1.11.29",
+ "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.11.29.tgz",
+ "integrity": "sha512-g4mThMIpWbNhV8G2rWp5a5/Igv8/2UFRJx2yImrLGMgrDDYZIopqZ/z0jZxDgqNA1QDx93rpwNF7jGsxVWcMlA==",
"dev": true,
"hasInstallScript": true,
"license": "Apache-2.0",
@@ -1093,16 +1081,16 @@
"url": "https://opencollective.com/swc"
},
"optionalDependencies": {
- "@swc/core-darwin-arm64": "1.11.21",
- "@swc/core-darwin-x64": "1.11.21",
- "@swc/core-linux-arm-gnueabihf": "1.11.21",
- "@swc/core-linux-arm64-gnu": "1.11.21",
- "@swc/core-linux-arm64-musl": "1.11.21",
- "@swc/core-linux-x64-gnu": "1.11.21",
- "@swc/core-linux-x64-musl": "1.11.21",
- "@swc/core-win32-arm64-msvc": "1.11.21",
- "@swc/core-win32-ia32-msvc": "1.11.21",
- "@swc/core-win32-x64-msvc": "1.11.21"
+ "@swc/core-darwin-arm64": "1.11.29",
+ "@swc/core-darwin-x64": "1.11.29",
+ "@swc/core-linux-arm-gnueabihf": "1.11.29",
+ "@swc/core-linux-arm64-gnu": "1.11.29",
+ "@swc/core-linux-arm64-musl": "1.11.29",
+ "@swc/core-linux-x64-gnu": "1.11.29",
+ "@swc/core-linux-x64-musl": "1.11.29",
+ "@swc/core-win32-arm64-msvc": "1.11.29",
+ "@swc/core-win32-ia32-msvc": "1.11.29",
+ "@swc/core-win32-x64-msvc": "1.11.29"
},
"peerDependencies": {
"@swc/helpers": ">=0.5.17"
@@ -1114,9 +1102,9 @@
}
},
"node_modules/@swc/core-darwin-arm64": {
- "version": "1.11.21",
- "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.11.21.tgz",
- "integrity": "sha512-v6gjw9YFWvKulCw3ZA1dY+LGMafYzJksm1mD4UZFZ9b36CyHFowYVYug1ajYRIRqEvvfIhHUNV660zTLoVFR8g==",
+ "version": "1.11.29",
+ "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.11.29.tgz",
+ "integrity": "sha512-whsCX7URzbuS5aET58c75Dloby3Gtj/ITk2vc4WW6pSDQKSPDuONsIcZ7B2ng8oz0K6ttbi4p3H/PNPQLJ4maQ==",
"cpu": [
"arm64"
],
@@ -1131,9 +1119,9 @@
}
},
"node_modules/@swc/core-darwin-x64": {
- "version": "1.11.21",
- "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.11.21.tgz",
- "integrity": "sha512-CUiTiqKlzskwswrx9Ve5NhNoab30L1/ScOfQwr1duvNlFvarC8fvQSgdtpw2Zh3MfnfNPpyLZnYg7ah4kbT9JQ==",
+ "version": "1.11.29",
+ "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.11.29.tgz",
+ "integrity": "sha512-S3eTo/KYFk+76cWJRgX30hylN5XkSmjYtCBnM4jPLYn7L6zWYEPajsFLmruQEiTEDUg0gBEWLMNyUeghtswouw==",
"cpu": [
"x64"
],
@@ -1148,9 +1136,9 @@
}
},
"node_modules/@swc/core-linux-arm-gnueabihf": {
- "version": "1.11.21",
- "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.11.21.tgz",
- "integrity": "sha512-YyBTAFM/QPqt1PscD8hDmCLnqPGKmUZpqeE25HXY8OLjl2MUs8+O4KjwPZZ+OGxpdTbwuWFyMoxjcLy80JODvg==",
+ "version": "1.11.29",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.11.29.tgz",
+ "integrity": "sha512-o9gdshbzkUMG6azldHdmKklcfrcMx+a23d/2qHQHPDLUPAN+Trd+sDQUYArK5Fcm7TlpG4sczz95ghN0DMkM7g==",
"cpu": [
"arm"
],
@@ -1165,9 +1153,9 @@
}
},
"node_modules/@swc/core-linux-arm64-gnu": {
- "version": "1.11.21",
- "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.11.21.tgz",
- "integrity": "sha512-DQD+ooJmwpNsh4acrftdkuwl5LNxxg8U4+C/RJNDd7m5FP9Wo4c0URi5U0a9Vk/6sQNh9aSGcYChDpqCDWEcBw==",
+ "version": "1.11.29",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.11.29.tgz",
+ "integrity": "sha512-sLoaciOgUKQF1KX9T6hPGzvhOQaJn+3DHy4LOHeXhQqvBgr+7QcZ+hl4uixPKTzxk6hy6Hb0QOvQEdBAAR1gXw==",
"cpu": [
"arm64"
],
@@ -1182,9 +1170,9 @@
}
},
"node_modules/@swc/core-linux-arm64-musl": {
- "version": "1.11.21",
- "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.11.21.tgz",
- "integrity": "sha512-y1L49+snt1a1gLTYPY641slqy55QotPdtRK9Y6jMi4JBQyZwxC8swWYlQWb+MyILwxA614fi62SCNZNznB3XSA==",
+ "version": "1.11.29",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.11.29.tgz",
+ "integrity": "sha512-PwjB10BC0N+Ce7RU/L23eYch6lXFHz7r3NFavIcwDNa/AAqywfxyxh13OeRy+P0cg7NDpWEETWspXeI4Ek8otw==",
"cpu": [
"arm64"
],
@@ -1199,9 +1187,9 @@
}
},
"node_modules/@swc/core-linux-x64-gnu": {
- "version": "1.11.21",
- "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.21.tgz",
- "integrity": "sha512-NesdBXv4CvVEaFUlqKj+GA4jJMNUzK2NtKOrUNEtTbXaVyNiXjFCSaDajMTedEB0jTAd9ybB0aBvwhgkJUWkWA==",
+ "version": "1.11.29",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.29.tgz",
+ "integrity": "sha512-i62vBVoPaVe9A3mc6gJG07n0/e7FVeAvdD9uzZTtGLiuIfVfIBta8EMquzvf+POLycSk79Z6lRhGPZPJPYiQaA==",
"cpu": [
"x64"
],
@@ -1216,9 +1204,9 @@
}
},
"node_modules/@swc/core-linux-x64-musl": {
- "version": "1.11.21",
- "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.11.21.tgz",
- "integrity": "sha512-qFV60pwpKVOdmX67wqQzgtSrUGWX9Cibnp1CXyqZ9Mmt8UyYGvmGu7p6PMbTyX7vdpVUvWVRf8DzrW2//wmVHg==",
+ "version": "1.11.29",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.11.29.tgz",
+ "integrity": "sha512-YER0XU1xqFdK0hKkfSVX1YIyCvMDI7K07GIpefPvcfyNGs38AXKhb2byySDjbVxkdl4dycaxxhRyhQ2gKSlsFQ==",
"cpu": [
"x64"
],
@@ -1233,9 +1221,9 @@
}
},
"node_modules/@swc/core-win32-arm64-msvc": {
- "version": "1.11.21",
- "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.11.21.tgz",
- "integrity": "sha512-DJJe9k6gXR/15ZZVLv1SKhXkFst8lYCeZRNHH99SlBodvu4slhh/MKQ6YCixINRhCwliHrpXPym8/5fOq8b7Ig==",
+ "version": "1.11.29",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.11.29.tgz",
+ "integrity": "sha512-po+WHw+k9g6FAg5IJ+sMwtA/fIUL3zPQ4m/uJgONBATCVnDDkyW6dBA49uHNVtSEvjvhuD8DVWdFP847YTcITw==",
"cpu": [
"arm64"
],
@@ -1250,9 +1238,9 @@
}
},
"node_modules/@swc/core-win32-ia32-msvc": {
- "version": "1.11.21",
- "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.11.21.tgz",
- "integrity": "sha512-TqEXuy6wedId7bMwLIr9byds+mKsaXVHctTN88R1UIBPwJA92Pdk0uxDgip0pEFzHB/ugU27g6d8cwUH3h2eIw==",
+ "version": "1.11.29",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.11.29.tgz",
+ "integrity": "sha512-h+NjOrbqdRBYr5ItmStmQt6x3tnhqgwbj9YxdGPepbTDamFv7vFnhZR0YfB3jz3UKJ8H3uGJ65Zw1VsC+xpFkg==",
"cpu": [
"ia32"
],
@@ -1267,9 +1255,9 @@
}
},
"node_modules/@swc/core-win32-x64-msvc": {
- "version": "1.11.21",
- "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.11.21.tgz",
- "integrity": "sha512-BT9BNNbMxdpUM1PPAkYtviaV0A8QcXttjs2MDtOeSqqvSJaPtyM+Fof2/+xSwQDmDEFzbGCcn75M5+xy3lGqpA==",
+ "version": "1.11.29",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.11.29.tgz",
+ "integrity": "sha512-Q8cs2BDV9wqDvqobkXOYdC+pLUSEpX/KvI0Dgfun1F+LzuLotRFuDhrvkU9ETJA6OnD2+Fn/ieHgloiKA/Mn/g==",
"cpu": [
"x64"
],
@@ -1315,9 +1303,9 @@
"license": "MIT"
},
"node_modules/@types/react": {
- "version": "19.1.3",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.3.tgz",
- "integrity": "sha512-dLWQ+Z0CkIvK1J8+wrDPwGxEYFA4RAyHoZPxHVGspYmFVnwGSNT24cGIhFJrtfRnWVuW8X7NO52gCXmhkVUWGQ==",
+ "version": "19.1.5",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.5.tgz",
+ "integrity": "sha512-piErsCVVbpMMT2r7wbawdZsq4xMvIAhQuac2gedQHysu1TZYEigE6pnFfgZT+/jQnrRuF5r+SHzuehFjfRjr4g==",
"devOptional": true,
"license": "MIT",
"dependencies": {
@@ -1325,9 +1313,9 @@
}
},
"node_modules/@types/react-dom": {
- "version": "19.1.3",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.3.tgz",
- "integrity": "sha512-rJXC08OG0h3W6wDMFxQrZF00Kq6qQvw0djHRdzl3U5DnIERz0MRce3WVc7IS6JYBwtaP/DwYtRRjVlvivNveKg==",
+ "version": "19.1.5",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.5.tgz",
+ "integrity": "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -1342,19 +1330,19 @@
"optional": true
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.32.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.0.tgz",
- "integrity": "sha512-/jU9ettcntkBFmWUzzGgsClEi2ZFiikMX5eEQsmxIAWMOn4H3D4rvHssstmAHGVvrYnaMqdWWWg0b5M6IN/MTQ==",
+ "version": "8.32.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.1.tgz",
+ "integrity": "sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.32.0",
- "@typescript-eslint/type-utils": "8.32.0",
- "@typescript-eslint/utils": "8.32.0",
- "@typescript-eslint/visitor-keys": "8.32.0",
+ "@typescript-eslint/scope-manager": "8.32.1",
+ "@typescript-eslint/type-utils": "8.32.1",
+ "@typescript-eslint/utils": "8.32.1",
+ "@typescript-eslint/visitor-keys": "8.32.1",
"graphemer": "^1.4.0",
- "ignore": "^5.3.1",
+ "ignore": "^7.0.0",
"natural-compare": "^1.4.0",
"ts-api-utils": "^2.1.0"
},
@@ -1371,17 +1359,27 @@
"typescript": ">=4.8.4 <5.9.0"
}
},
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz",
+ "integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
"node_modules/@typescript-eslint/parser": {
- "version": "8.32.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.32.0.tgz",
- "integrity": "sha512-B2MdzyWxCE2+SqiZHAjPphft+/2x2FlO9YBx7eKE1BCb+rqBlQdhtAEhzIEdozHd55DXPmxBdpMygFJjfjjA9A==",
+ "version": "8.32.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.32.1.tgz",
+ "integrity": "sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/scope-manager": "8.32.0",
- "@typescript-eslint/types": "8.32.0",
- "@typescript-eslint/typescript-estree": "8.32.0",
- "@typescript-eslint/visitor-keys": "8.32.0",
+ "@typescript-eslint/scope-manager": "8.32.1",
+ "@typescript-eslint/types": "8.32.1",
+ "@typescript-eslint/typescript-estree": "8.32.1",
+ "@typescript-eslint/visitor-keys": "8.32.1",
"debug": "^4.3.4"
},
"engines": {
@@ -1397,14 +1395,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.32.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.32.0.tgz",
- "integrity": "sha512-jc/4IxGNedXkmG4mx4nJTILb6TMjL66D41vyeaPWvDUmeYQzF3lKtN15WsAeTr65ce4mPxwopPSo1yUUAWw0hQ==",
+ "version": "8.32.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.32.1.tgz",
+ "integrity": "sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.32.0",
- "@typescript-eslint/visitor-keys": "8.32.0"
+ "@typescript-eslint/types": "8.32.1",
+ "@typescript-eslint/visitor-keys": "8.32.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1415,14 +1413,14 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "8.32.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.32.0.tgz",
- "integrity": "sha512-t2vouuYQKEKSLtJaa5bB4jHeha2HJczQ6E5IXPDPgIty9EqcJxpr1QHQ86YyIPwDwxvUmLfP2YADQ5ZY4qddZg==",
+ "version": "8.32.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.32.1.tgz",
+ "integrity": "sha512-mv9YpQGA8iIsl5KyUPi+FGLm7+bA4fgXaeRcFKRDRwDMu4iwrSHeDPipwueNXhdIIZltwCJv+NkxftECbIZWfA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/typescript-estree": "8.32.0",
- "@typescript-eslint/utils": "8.32.0",
+ "@typescript-eslint/typescript-estree": "8.32.1",
+ "@typescript-eslint/utils": "8.32.1",
"debug": "^4.3.4",
"ts-api-utils": "^2.1.0"
},
@@ -1439,9 +1437,9 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.32.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.32.0.tgz",
- "integrity": "sha512-O5Id6tGadAZEMThM6L9HmVf5hQUXNSxLVKeGJYWNhhVseps/0LddMkp7//VDkzwJ69lPL0UmZdcZwggj9akJaA==",
+ "version": "8.32.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.32.1.tgz",
+ "integrity": "sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1453,14 +1451,14 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.32.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.32.0.tgz",
- "integrity": "sha512-pU9VD7anSCOIoBFnhTGfOzlVFQIA1XXiQpH/CezqOBaDppRwTglJzCC6fUQGpfwey4T183NKhF1/mfatYmjRqQ==",
+ "version": "8.32.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.32.1.tgz",
+ "integrity": "sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.32.0",
- "@typescript-eslint/visitor-keys": "8.32.0",
+ "@typescript-eslint/types": "8.32.1",
+ "@typescript-eslint/visitor-keys": "8.32.1",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@@ -1480,16 +1478,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "8.32.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.32.0.tgz",
- "integrity": "sha512-8S9hXau6nQ/sYVtC3D6ISIDoJzS1NsCK+gluVhLN2YkBPX+/1wkwyUiDKnxRh15579WoOIyVWnoyIf3yGI9REw==",
+ "version": "8.32.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.32.1.tgz",
+ "integrity": "sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.7.0",
- "@typescript-eslint/scope-manager": "8.32.0",
- "@typescript-eslint/types": "8.32.0",
- "@typescript-eslint/typescript-estree": "8.32.0"
+ "@typescript-eslint/scope-manager": "8.32.1",
+ "@typescript-eslint/types": "8.32.1",
+ "@typescript-eslint/typescript-estree": "8.32.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1504,13 +1502,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.32.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.32.0.tgz",
- "integrity": "sha512-1rYQTCLFFzOI5Nl0c8LUpJT8HxpwVRn9E4CkMsYfuN6ctmQqExjSTzzSk0Tz2apmXy7WU6/6fyaZVVA/thPN+w==",
+ "version": "8.32.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.32.1.tgz",
+ "integrity": "sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.32.0",
+ "@typescript-eslint/types": "8.32.1",
"eslint-visitor-keys": "^4.2.0"
},
"engines": {
@@ -1535,32 +1533,19 @@
}
},
"node_modules/@vitejs/plugin-react-swc": {
- "version": "3.9.0",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.9.0.tgz",
- "integrity": "sha512-jYFUSXhwMCYsh/aQTgSGLIN3Foz5wMbH9ahb0Zva//UzwZYbMiZd7oT3AU9jHT9DLswYDswsRwPU9jVF3yA48Q==",
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.10.0.tgz",
+ "integrity": "sha512-ZmkdHw3wo/o/Rk05YsXZs/DJAfY2CdQ5DUAjoWji+PEr+hYADdGMCGgEAILbiKj+CjspBTuTACBcWDrmC8AUfw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@swc/core": "^1.11.21"
+ "@rolldown/pluginutils": "1.0.0-beta.9",
+ "@swc/core": "^1.11.22"
},
"peerDependencies": {
"vite": "^4 || ^5 || ^6"
}
},
- "node_modules/accepts": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
- "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "mime-types": "^3.0.0",
- "negotiator": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/acorn": {
"version": "8.14.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
@@ -1644,27 +1629,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/body-parser": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
- "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "bytes": "^3.1.2",
- "content-type": "^1.0.5",
- "debug": "^4.4.0",
- "http-errors": "^2.0.0",
- "iconv-lite": "^0.6.3",
- "on-finished": "^2.4.1",
- "qs": "^6.14.0",
- "raw-body": "^3.0.0",
- "type-is": "^2.0.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
"node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
@@ -1688,47 +1652,6 @@
"node": ">=8"
}
},
- "node_modules/bytes": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
- "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/call-bind-apply-helpers": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
- "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/call-bound": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
- "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "get-intrinsic": "^1.3.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -1799,63 +1722,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/content-disposition": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
- "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "5.2.1"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/content-type": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
- "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/cookie": {
- "version": "0.7.2",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
- "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/cookie-signature": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
- "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.6.0"
- }
- },
- "node_modules/cors": {
- "version": "2.8.5",
- "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
- "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "object-assign": "^4",
- "vary": "^1"
- },
- "engines": {
- "node": ">= 0.10"
- }
- },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -1903,90 +1769,15 @@
"dev": true,
"license": "MIT"
},
- "node_modules/depd": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
- "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
"node_modules/dompurify": {
- "version": "3.2.5",
- "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.5.tgz",
- "integrity": "sha512-mLPd29uoRe9HpvwP2TxClGQBzGXeEC/we/q+bFlmPPmj2p2Ugl3r6ATu/UU1v77DXNcehiBg9zsr1dREyA/dJQ==",
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz",
+ "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
}
},
- "node_modules/dunder-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
- "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.1",
- "es-errors": "^1.3.0",
- "gopd": "^1.2.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/ee-first": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
- "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/encodeurl": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
- "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/es-define-property": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
- "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-errors": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
- "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-object-atoms": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
- "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
"node_modules/esbuild": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz",
@@ -2028,13 +1819,6 @@
"@esbuild/win32-x64": "0.25.2"
}
},
- "node_modules/escape-html": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
- "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -2049,9 +1833,9 @@
}
},
"node_modules/eslint": {
- "version": "9.26.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.26.0.tgz",
- "integrity": "sha512-Hx0MOjPh6uK9oq9nVsATZKE/Wlbai7KFjfCuw9UHaguDW3x+HF0O5nIi3ud39TWgrTjTO5nHxmL3R1eANinWHQ==",
+ "version": "9.27.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz",
+ "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2059,14 +1843,13 @@
"@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.20.0",
"@eslint/config-helpers": "^0.2.1",
- "@eslint/core": "^0.13.0",
+ "@eslint/core": "^0.14.0",
"@eslint/eslintrc": "^3.3.1",
- "@eslint/js": "9.26.0",
- "@eslint/plugin-kit": "^0.2.8",
+ "@eslint/js": "9.27.0",
+ "@eslint/plugin-kit": "^0.3.1",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2",
- "@modelcontextprotocol/sdk": "^1.8.0",
"@types/estree": "^1.0.6",
"@types/json-schema": "^7.0.15",
"ajv": "^6.12.4",
@@ -2090,8 +1873,7 @@
"lodash.merge": "^4.6.2",
"minimatch": "^3.1.2",
"natural-compare": "^1.4.0",
- "optionator": "^0.9.3",
- "zod": "^3.24.2"
+ "optionator": "^0.9.3"
},
"bin": {
"eslint": "bin/eslint.js"
@@ -2294,98 +2076,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/etag": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
- "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/eventsource": {
- "version": "3.0.7",
- "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
- "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eventsource-parser": "^3.0.1"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/eventsource-parser": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.1.tgz",
- "integrity": "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/express": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
- "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "accepts": "^2.0.0",
- "body-parser": "^2.2.0",
- "content-disposition": "^1.0.0",
- "content-type": "^1.0.5",
- "cookie": "^0.7.1",
- "cookie-signature": "^1.2.1",
- "debug": "^4.4.0",
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "etag": "^1.8.1",
- "finalhandler": "^2.1.0",
- "fresh": "^2.0.0",
- "http-errors": "^2.0.0",
- "merge-descriptors": "^2.0.0",
- "mime-types": "^3.0.0",
- "on-finished": "^2.4.1",
- "once": "^1.4.0",
- "parseurl": "^1.3.3",
- "proxy-addr": "^2.0.7",
- "qs": "^6.14.0",
- "range-parser": "^1.2.1",
- "router": "^2.2.0",
- "send": "^1.1.0",
- "serve-static": "^2.2.0",
- "statuses": "^2.0.1",
- "type-is": "^2.0.1",
- "vary": "^1.1.2"
- },
- "engines": {
- "node": ">= 18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/express-rate-limit": {
- "version": "7.5.0",
- "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz",
- "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 16"
- },
- "funding": {
- "url": "https://github.com/sponsors/express-rate-limit"
- },
- "peerDependencies": {
- "express": "^4.11 || 5 || ^5.0.0-beta.1"
- }
- },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -2473,24 +2163,6 @@
"node": ">=8"
}
},
- "node_modules/finalhandler": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
- "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "debug": "^4.4.0",
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "on-finished": "^2.4.1",
- "parseurl": "^1.3.3",
- "statuses": "^2.0.1"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
"node_modules/find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@@ -2529,26 +2201,6 @@
"dev": true,
"license": "ISC"
},
- "node_modules/forwarded": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
- "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/fresh": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
- "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -2564,55 +2216,6 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
- "node_modules/function-bind": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
- "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/get-intrinsic": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
- "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "es-define-property": "^1.0.1",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.1.1",
- "function-bind": "^1.1.2",
- "get-proto": "^1.0.1",
- "gopd": "^1.2.0",
- "has-symbols": "^1.1.0",
- "hasown": "^2.0.2",
- "math-intrinsics": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/get-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
- "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "dunder-proto": "^1.0.1",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
"node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -2639,19 +2242,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/gopd": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
- "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/graphemer": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
@@ -2669,62 +2259,6 @@
"node": ">=8"
}
},
- "node_modules/has-symbols": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
- "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/hasown": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/http-errors": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
- "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "depd": "2.0.0",
- "inherits": "2.0.4",
- "setprototypeof": "1.2.0",
- "statuses": "2.0.1",
- "toidentifier": "1.0.1"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/iconv-lite": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
- "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "safer-buffer": ">= 2.1.2 < 3.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -2762,23 +2296,6 @@
"node": ">=0.8.19"
}
},
- "node_modules/inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/ipaddr.js": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
- "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.10"
- }
- },
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -2812,13 +2329,6 @@
"node": ">=0.12.0"
}
},
- "node_modules/is-promise": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
- "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -2914,39 +2424,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/math-intrinsics": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
- "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/media-typer": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
- "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/merge-descriptors": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
- "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -2971,29 +2448,6 @@
"node": ">=8.6"
}
},
- "node_modules/mime-db": {
- "version": "1.54.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
- "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/mime-types": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
- "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "mime-db": "^1.54.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
@@ -3043,16 +2497,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/negotiator": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
- "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/npm-run-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz",
@@ -3083,52 +2527,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/object-assign": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
- "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/object-inspect": {
- "version": "1.13.4",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
- "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/on-finished": {
- "version": "2.4.1",
- "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
- "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ee-first": "1.1.1"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "wrappy": "1"
- }
- },
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -3192,16 +2590,6 @@
"node": ">=6"
}
},
- "node_modules/parseurl": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
- "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -3222,16 +2610,6 @@
"node": ">=8"
}
},
- "node_modules/path-to-regexp": {
- "version": "8.2.0",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
- "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=16"
- }
- },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -3252,16 +2630,6 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
- "node_modules/pkce-challenge": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz",
- "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=16.20.0"
- }
- },
"node_modules/postcss": {
"version": "8.5.3",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
@@ -3317,20 +2685,6 @@
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
- "node_modules/proxy-addr": {
- "version": "2.0.7",
- "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
- "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "forwarded": "0.2.0",
- "ipaddr.js": "1.9.1"
- },
- "engines": {
- "node": ">= 0.10"
- }
- },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -3341,22 +2695,6 @@
"node": ">=6"
}
},
- "node_modules/qs": {
- "version": "6.14.0",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
- "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "side-channel": "^1.1.0"
- },
- "engines": {
- "node": ">=0.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -3378,32 +2716,6 @@
],
"license": "MIT"
},
- "node_modules/range-parser": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
- "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/raw-body": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
- "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "bytes": "3.1.2",
- "http-errors": "2.0.0",
- "iconv-lite": "0.6.3",
- "unpipe": "1.0.0"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
"node_modules/react": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
@@ -3547,23 +2859,6 @@
"fsevents": "~2.3.2"
}
},
- "node_modules/router": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
- "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "debug": "^4.4.0",
- "depd": "^2.0.0",
- "is-promise": "^4.0.0",
- "parseurl": "^1.3.3",
- "path-to-regexp": "^8.0.0"
- },
- "engines": {
- "node": ">= 18"
- }
- },
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -3588,34 +2883,6 @@
"queue-microtask": "^1.2.2"
}
},
- "node_modules/safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/safer-buffer": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
- "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/scheduler": {
"version": "0.26.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
@@ -3623,9 +2890,9 @@
"license": "MIT"
},
"node_modules/semver": {
- "version": "7.7.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
- "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"dev": true,
"license": "ISC",
"bin": {
@@ -3635,58 +2902,12 @@
"node": ">=10"
}
},
- "node_modules/send": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
- "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "debug": "^4.3.5",
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "etag": "^1.8.1",
- "fresh": "^2.0.0",
- "http-errors": "^2.0.0",
- "mime-types": "^3.0.1",
- "ms": "^2.1.3",
- "on-finished": "^2.4.1",
- "range-parser": "^1.2.1",
- "statuses": "^2.0.1"
- },
- "engines": {
- "node": ">= 18"
- }
- },
- "node_modules/serve-static": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
- "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "parseurl": "^1.3.3",
- "send": "^1.2.0"
- },
- "engines": {
- "node": ">= 18"
- }
- },
"node_modules/set-cookie-parser": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
"license": "MIT"
},
- "node_modules/setprototypeof": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
- "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -3710,82 +2931,6 @@
"node": ">=8"
}
},
- "node_modules/side-channel": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
- "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.3",
- "side-channel-list": "^1.0.0",
- "side-channel-map": "^1.0.1",
- "side-channel-weakmap": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-list": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
- "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-map": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
- "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-weakmap": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
- "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3",
- "side-channel-map": "^1.0.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -3796,16 +2941,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/statuses": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
- "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
"node_modules/strip-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
@@ -3913,16 +3048,6 @@
"node": ">=8.0"
}
},
- "node_modules/toidentifier": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
- "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.6"
- }
- },
"node_modules/ts-api-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
@@ -3949,21 +3074,6 @@
"node": ">= 0.8.0"
}
},
- "node_modules/type-is": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
- "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "content-type": "^1.0.5",
- "media-typer": "^1.1.0",
- "mime-types": "^3.0.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/typescript": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
@@ -3979,15 +3089,15 @@
}
},
"node_modules/typescript-eslint": {
- "version": "8.32.0",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.32.0.tgz",
- "integrity": "sha512-UMq2kxdXCzinFFPsXc9o2ozIpYCCOiEC46MG3yEh5Vipq6BO27otTtEBZA1fQ66DulEUgE97ucQ/3YY66CPg0A==",
+ "version": "8.32.1",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.32.1.tgz",
+ "integrity": "sha512-D7el+eaDHAmXvrZBy1zpzSNIRqnCOrkwTgZxTu3MUqRWk8k0q9m9Ho4+vPf7iHtgUfrK/o8IZaEApsxPlHTFCg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/eslint-plugin": "8.32.0",
- "@typescript-eslint/parser": "8.32.0",
- "@typescript-eslint/utils": "8.32.0"
+ "@typescript-eslint/eslint-plugin": "8.32.1",
+ "@typescript-eslint/parser": "8.32.1",
+ "@typescript-eslint/utils": "8.32.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -4014,16 +3124,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/unpipe": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
- "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@@ -4034,16 +3134,6 @@
"punycode": "^2.1.0"
}
},
- "node_modules/vary": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
- "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
"node_modules/vite": {
"version": "6.3.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
@@ -4255,13 +3345,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
@@ -4275,30 +3358,10 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/zod": {
- "version": "3.24.4",
- "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.4.tgz",
- "integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/colinhacks"
- }
- },
- "node_modules/zod-to-json-schema": {
- "version": "3.24.5",
- "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz",
- "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==",
- "dev": true,
- "license": "ISC",
- "peerDependencies": {
- "zod": "^3.24.1"
- }
- },
"node_modules/zustand": {
- "version": "5.0.4",
- "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.4.tgz",
- "integrity": "sha512-39VFTN5InDtMd28ZhjLyuTnlytDr9HfwO512Ai4I8ZABCoyAj4F1+sr7sD1jP/+p7k77Iko0Pb5NhgBFDCX0kQ==",
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.5.tgz",
+ "integrity": "sha512-mILtRfKW9xM47hqxGIxCv12gXusoY/xTSHBYApXozR0HmQv299whhBeeAcRy+KrPPybzosvJBCOmVjq6x12fCg==",
"license": "MIT",
"engines": {
"node": ">=12.20.0"
diff --git a/frontend/package.json b/frontend/package.json
index bf89f32b..6d205445 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -11,26 +11,26 @@
"preview": "vite preview"
},
"dependencies": {
- "dompurify": "^3.2.5",
+ "dompurify": "^3.2.6",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-router-dom": "^7.6.0",
- "zustand": "^5.0.4"
+ "zustand": "^5.0.5"
},
"devDependencies": {
- "@types/react": "^19.1.3",
- "@types/react-dom": "^19.1.3",
- "@typescript-eslint/eslint-plugin": "^8.32.0",
- "@typescript-eslint/parser": "^8.32.0",
- "@vitejs/plugin-react-swc": "^3.9.0",
- "eslint": "^9.26.0",
+ "@types/react": "^19.1.5",
+ "@types/react-dom": "^19.1.5",
+ "@typescript-eslint/eslint-plugin": "^8.32.1",
+ "@typescript-eslint/parser": "^8.32.1",
+ "@vitejs/plugin-react-swc": "^3.10.0",
+ "eslint": "^9.27.0",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20",
"globals": "^16.1.0",
"prettier": "3.5.3",
"typescript": "^5.8.3",
- "typescript-eslint": "^8.32.0",
+ "typescript-eslint": "^8.32.1",
"vite": ">=6.3.5",
"vite-plugin-checker": "^0.9.3"
}
diff --git a/frontend/src/api/actions/updateUserConfig.ts b/frontend/src/api/actions/updateUserConfig.ts
index 84c5ce3d..ac806e20 100644
--- a/frontend/src/api/actions/updateUserConfig.ts
+++ b/frontend/src/api/actions/updateUserConfig.ts
@@ -8,6 +8,14 @@ export type ColourVariants =
| 'midnight.css'
| 'custom.css';
+export const ColourConstant = {
+ Dark: 'dark.css',
+ Light: 'light.css',
+ Matrix: 'matrix.css',
+ Midnight: 'midnight.css',
+ Custom: 'custom.css',
+};
+
export const FileSizeUnits = {
Binary: 'binary',
Metric: 'metric',
diff --git a/frontend/src/api/loader/loadChannelNav.ts b/frontend/src/api/loader/loadChannelNav.ts
index 26c97c10..361e0d38 100644
--- a/frontend/src/api/loader/loadChannelNav.ts
+++ b/frontend/src/api/loader/loadChannelNav.ts
@@ -5,6 +5,7 @@ export type ChannelNavResponseType = {
has_shorts: boolean;
has_playlists: boolean;
has_pending: boolean;
+ has_ignored: boolean;
};
const loadChannelNav = async (youtubeChannelId: string) => {
diff --git a/frontend/src/components/VideoPlayer.tsx b/frontend/src/components/VideoPlayer.tsx
index 4778ba4b..7684e24a 100644
--- a/frontend/src/components/VideoPlayer.tsx
+++ b/frontend/src/components/VideoPlayer.tsx
@@ -150,6 +150,7 @@ const VideoPlayer = ({
const resetPlaybackSpeedPressed = useKeyPress('=');
const arrowRightPressed = useKeyPress('ArrowRight');
const arrowLeftPressed = useKeyPress('ArrowLeft');
+ const pPausedPressed = useKeyPress('p');
const videoId = video.youtube_id;
const videoUrl = video.media_url;
@@ -214,6 +215,16 @@ const VideoPlayer = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [mutePressed]);
+ useEffect(() => {
+ if (pPausedPressed) {
+ if (videoRef.current?.paused) {
+ videoRef.current.play();
+ } else {
+ videoRef.current?.pause();
+ }
+ }
+ }, [pPausedPressed]);
+
useEffect(() => {
if (increasePlaybackSpeedPressed) {
const newSpeed = playbackSpeedIndex + 1;
@@ -301,16 +312,25 @@ const VideoPlayer = ({
}, [subtitlesPressed]);
useEffect(() => {
- if (arrowLeftPressed) {
- infoDialog('- 5 seconds');
- }
- }, [arrowLeftPressed]);
+ if (arrowLeftPressed || arrowRightPressed) {
+ let timeStep = 5;
- useEffect(() => {
- if (arrowRightPressed) {
- infoDialog('+ 5 seconds');
+ if (arrowLeftPressed) {
+ infoDialog('- 5 seconds');
+ timeStep *= -1;
+ }
+
+ if (arrowRightPressed) {
+ infoDialog('+ 5 seconds');
+ }
+
+ const currentCurrentTime = videoRef.current?.currentTime;
+
+ if (currentCurrentTime !== undefined && videoRef.current) {
+ videoRef.current.currentTime = currentCurrentTime + timeStep;
+ }
}
- }, [arrowRightPressed]);
+ }, [arrowLeftPressed, arrowRightPressed]);
useEffect(() => {
if (questionmarkPressed) {
@@ -361,6 +381,11 @@ const VideoPlayer = ({
});
}}
onEnded={handleVideoEnd(videoId, watched)}
+ onKeyDown={e => {
+ if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
+ e.preventDefault();
+ }
+ }}
autoPlay={autoplay}
controls
width="100%"
@@ -386,6 +411,10 @@ const VideoPlayer = ({
Show help |
? |
+
+ | Toggle pause play |
+ p |
+
| Toggle mute |
m |
diff --git a/frontend/src/configuration/colours/Colours.tsx b/frontend/src/configuration/colours/Colours.tsx
index c4654f32..312be577 100644
--- a/frontend/src/configuration/colours/Colours.tsx
+++ b/frontend/src/configuration/colours/Colours.tsx
@@ -1,14 +1,40 @@
+import { ColourConstant, ColourVariants } from '../../api/actions/updateUserConfig';
import { useUserConfigStore } from '../../stores/UserConfigStore';
-import { ColourConstant } from './colourConstant';
import CustomStylesheet from './components/Custom';
import DarkStylesheet from './components/Dark';
import LightStylesheet from './components/Light';
import MatrixStylesheet from './components/Matrix';
import MidnightStylesheet from './components/Midnight';
+function getThemeFromLocalStorage(stylesheet: string): ColourVariants {
+ // Check when localStorage when its possibly the default theme. ( e.g. login page )
+ if (stylesheet === ColourConstant.Dark) {
+ const fromLocalStorage = localStorage.getItem('stylesheet');
+
+ if (!fromLocalStorage) {
+ localStorage.setItem('stylesheet', stylesheet);
+ }
+
+ if (fromLocalStorage) {
+ stylesheet = fromLocalStorage;
+ }
+ } else {
+ const fromLocalStorage = localStorage.getItem('stylesheet');
+
+ // Re-sync when localStorage is not the same as in userConfig
+ if (stylesheet !== fromLocalStorage) {
+ localStorage.setItem('stylesheet', stylesheet);
+ }
+ }
+
+ return stylesheet as ColourVariants;
+}
+
const Colours = () => {
const { userConfig } = useUserConfigStore();
- const stylesheet = userConfig?.stylesheet;
+ let stylesheet = userConfig?.stylesheet;
+
+ stylesheet = getThemeFromLocalStorage(stylesheet);
switch (stylesheet) {
case ColourConstant.Dark:
diff --git a/frontend/src/configuration/colours/colourConstant.ts b/frontend/src/configuration/colours/colourConstant.ts
deleted file mode 100644
index b910fe9a..00000000
--- a/frontend/src/configuration/colours/colourConstant.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export const ColourConstant = {
- Dark: 'dark.css',
- Light: 'light.css',
- Matrix: 'matrix.css',
- Midnight: 'midnight.css',
- Custom: 'custom.css',
-};
diff --git a/frontend/src/configuration/routes/RouteList.ts b/frontend/src/configuration/routes/RouteList.ts
index 41f36558..df1513a5 100644
--- a/frontend/src/configuration/routes/RouteList.ts
+++ b/frontend/src/configuration/routes/RouteList.ts
@@ -10,7 +10,8 @@ const Routes = {
Playlists: '/playlist/',
Playlist: (id: string) => `/playlist/${id}`,
Downloads: '/downloads/',
- DownloadsByChannelId: (channelId: string) => `/downloads/?channel=${channelId}`,
+ DownloadsByChannelId: (channelId: string) => `/downloads/?channel=${channelId}&ignored=false`,
+ IgnoredByChannelId: (channelId: string) => `/downloads/?channel=${channelId}&ignored=true`,
Search: '/search/',
SettingsDashboard: '/settings/',
SettingsUser: '/settings/user/',
diff --git a/frontend/src/pages/ChannelBase.tsx b/frontend/src/pages/ChannelBase.tsx
index 05bf21af..aba6e579 100644
--- a/frontend/src/pages/ChannelBase.tsx
+++ b/frontend/src/pages/ChannelBase.tsx
@@ -27,7 +27,7 @@ const ChannelBase = () => {
const { data: channelNavData } = channelNav ?? {};
const channel = channelResponseData;
- const { has_streams, has_shorts, has_playlists, has_pending } = channelNavData || {};
+ const { has_streams, has_shorts, has_playlists, has_pending, has_ignored } = channelNavData || {};
useEffect(() => {
(async () => {
@@ -84,6 +84,11 @@ const ChannelBase = () => {
Downloads
)}
+ {has_ignored && isAdmin && (
+
+ Ignored
+
+ )}
{
const { currentPage, setCurrentPage } = useOutletContext() as OutletContextType;
const channelFilterFromUrl = searchParams.get('channel');
+ const ignoredOnlyParam = searchParams.get('ignored');
const [refresh, setRefresh] = useState(false);
const [showHiddenForm, setShowHiddenForm] = useState(false);
@@ -82,7 +83,8 @@ const Download = () => {
const view = userConfig.view_style_downloads;
const gridItems = userConfig.grid_items;
- const showIgnored = userConfig.show_ignored_only;
+ const showIgnored =
+ ignoredOnlyParam !== null ? ignoredOnlyParam === 'true' : userConfig.show_ignored_only;
const isGridView = view === ViewStyles.grid;
const gridView = isGridView ? `boxed-${gridItems}` : '';
const gridViewGrid = isGridView ? `grid-${gridItems}` : '';
@@ -234,6 +236,9 @@ const Download = () => {
id="showIgnored"
onChange={() => {
handleUserConfigUpdate({ show_ignored_only: !showIgnored });
+ const newParams = new URLSearchParams(searchParams.toString());
+ newParams.set('ignored', String(!showIgnored));
+ setSearchParams(newParams);
setRefresh(true);
}}
type="checkbox"
diff --git a/frontend/src/pages/SettingsActions.tsx b/frontend/src/pages/SettingsActions.tsx
index f56383d3..577c0262 100644
--- a/frontend/src/pages/SettingsActions.tsx
+++ b/frontend/src/pages/SettingsActions.tsx
@@ -204,8 +204,8 @@ const SettingsActions = () => {
deleted videos from the filesystem.
- Rescan your media folder looking for missing videos and clean up index. More infos on
- the Github{' '}
+ Rescan your media folder looking for missing videos and clean up index. More info on the
+ Github{' '}
{
const { userConfig, setUserConfig } = useUserConfigStore();
@@ -38,6 +38,9 @@ const SettingsUser = () => {
const handleStyleSheetChange = async (selectedStyleSheet: ColourVariants) => {
handleUserConfigUpdate({ stylesheet: selectedStyleSheet });
setStyleSheet(selectedStyleSheet);
+
+ // Store in local storage for pages like login, without a userConfig
+ localStorage.setItem('stylesheet', selectedStyleSheet);
};
const handlePageSizeChange = async () => {