commit acdf544b0860c870f45725364bb1b7e613474471 Author: Spasol Date: Tue Sep 30 12:54:29 2025 +0800 initialˆ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..ce286c2 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..ffd8d51 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,18 @@ + + + + + mongo.4 + true + true + com.dbschema.MongoJdbcDriver + mongodb://100.113.74.91:27017/reijm + + + + + + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..076b1c2 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..699c781 --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..4c5f367 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..2c4d9e7 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..8aa36bd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Reijm.iml b/Reijm.iml new file mode 100644 index 0000000..e7d3a15 --- /dev/null +++ b/Reijm.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/jm/.idea/jm.iml b/jm/.idea/jm.iml new file mode 100644 index 0000000..5362588 --- /dev/null +++ b/jm/.idea/jm.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/jm/src/findmaimaibotJM/__init__.py b/jm/src/findmaimaibotJM/__init__.py new file mode 100755 index 0000000..6b66489 --- /dev/null +++ b/jm/src/findmaimaibotJM/__init__.py @@ -0,0 +1,289 @@ +from fastapi import FastAPI, HTTPException +from jmcomic import * +from typing import List, Union +import redis +from datetime import timedelta +from functools import wraps +import json + + +def cache_result(expire_time: timedelta): + def decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + # 生成缓存键 + cache_key = f"{func.__name__}:{json.dumps(kwargs)}" + + # 尝试从缓存中获取数据 + cached_data = redis_client.get(cache_key) + if cached_data: + return json.loads(cached_data) + + # 如果缓存中没有数据,则调用函数获取数据 + result = func(*args, **kwargs) + + # 将结果存入缓存 + redis_client.setex(cache_key, int(expire_time.total_seconds()), json.dumps(result)) + + return result + + return wrapper + + return decorator + + + +# 配置Redis连接 +redis_client = redis.StrictRedis(host='localhost', port=6379, db=0, decode_responses=True) + +app = FastAPI() + +# 初始化客户端 +option = JmOption.default() +client = option.new_jm_client() + + +@app.post("/login/") +def login(username: str, password: str): + try: + client.login(username, password) + return {"message": "Login successful"} + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) + + +from datetime import timedelta + +# 设置缓存时间为3天 +cache_time = timedelta(days=3) + +@app.get("/search/") +@cache_result(cache_time) +def search_site(search_query: str, page: int = 1): + try: + page = client.search_site(search_query=search_query, page=page) + results = [{"album_id": album_id, "title": title} for album_id, title in page] + return results + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) + +@app.get("/album/{album_id}/") +@cache_result(cache_time) +def get_album_details(album_id: int): + try: + page = client.search_site(search_query=str(album_id)) + album = page.single_album + # 存储所有图片的URL + image_urls = [] + nums = [] + # 遍历每个章节 + for photo in album: + # 章节实体类 + photo_detail = client.get_photo_detail(photo.photo_id, False) + + # 遍历每个图片 + for image in photo_detail: + # 图片实体类 + image_urls.append(image.img_url) + nums.append(JmImageTool.get_num_by_url(image.scramble_id, image.img_url)) + + return { + "album_id": album.album_id, + "scramble_id": album.scramble_id, + "name": album.name, + "page_count": album.page_count, + "pub_date": album.pub_date, + "update_date": album.update_date, + "likes": album.likes, + "views": album.views, + "comment_count": album.comment_count, + "works": album.works, + "actors": album.actors, + "authors": album.authors, + "tags": album.tags, + "related_list": album.related_list, + "episode_list": album.episode_list, + "image_urls": image_urls, + "nums": nums + } + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) + + +@app.get("/album/{album_id}/chapters/") +@cache_result(cache_time) +def get_album_chapters_paginated(album_id: int, page: int = 1, per_page: int = 5): + """ + 分页获取专辑章节列表 + :param album_id: 专辑ID + :param page: 页码(从1开始) + :param per_page: 每页章节数 + """ + try: + page_result = client.search_site(search_query=str(album_id)) + album = page_result.single_album + + # 计算分页信息 + total_chapters = len(album.photos) + total_pages = (total_chapters + per_page - 1) // per_page # 向上取整 + + if page < 1 or page > total_pages: + raise HTTPException(status_code=404, detail="Page out of range") + + # 计算当前页的章节范围 + start_index = (page - 1) * per_page + end_index = min(start_index + per_page, total_chapters) + + # 获取当前页的章节信息 + chapters = [] + for i in range(start_index, end_index): + photo = album.photos[i] + chapters.append({ + "chapter_index": i, + "chapter_id": photo.photo_id, + "title": photo.name, + "page_count": photo.page_count, + "pub_date": photo.pub_date + }) + + return { + "album_id": album.album_id, + "album_name": album.name, + "current_page": page, + "per_page": per_page, + "total_chapters": total_chapters, + "total_pages": total_pages, + "chapters": chapters + } + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) + + +@app.get("/fast/{album_id}/") +# @cache_result(cache_time) +def get_album_details(album_id: int): + try: + page = client.search_site(search_query=str(album_id)) + album = page.single_album + # 存储所有图片的URL + image_urls = [] + nums = [] + # 遍历每个章节 + cut = 0 + cut2 = 0 + for photo in album: + if cut == 1: + break + cut = cut + 1 + # 章节实体类 + photo_detail = client.get_photo_detail(photo.photo_id, False) + + # 遍历每个图片 + for image in photo_detail: + # 图片实体类 + if cut2 == 1: + break + cut2 = cut2 + 1 + image_urls.append(image.img_url) + nums.append(JmImageTool.get_num_by_url(image.scramble_id, image.img_url)) + + return { + "album_id": album.album_id, + "scramble_id": album.scramble_id, + "name": album.name, + "page_count": album.page_count, + "pub_date": album.pub_date, + "update_date": album.update_date, + "likes": album.likes, + "views": album.views, + "comment_count": album.comment_count, + "works": album.works, + "actors": album.actors, + "authors": album.authors, + "tags": album.tags, + "related_list": album.related_list, + "episode_list": album.episode_list, + "image_urls": image_urls, + "nums": nums + } + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) +@app.get("/favorites/") +@cache_result(cache_time) +def get_favorites(username: str): + try: + favorites = [] + for page in client.favorite_folder_gen(username=username): + for aid, atitle in page: + favorites.append({"album_id": aid, "title": atitle}) + return favorites + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) + +@app.get("/categories/") +@cache_result(cache_time) +def get_categories(page: int = 1, time: str = JmMagicConstants.TIME_ALL, category: str = JmMagicConstants.CATEGORY_ALL, + order_by: str = JmMagicConstants.ORDER_BY_LATEST): + try: + page = client.categories_filter(page=page, time=time, category=category, order_by=order_by) + results = [{"album_id": aid, "title": atitle} for aid, atitle in page] + return results + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) + +@app.get("/rankings/year") +@cache_result(cache_time) +def get_rankings_year(page: int): + try: + op = JmOption.default() + cl = op.new_jm_client() + page: JmCategoryPage = cl.categories_filter( + page=page, + time=JmMagicConstants.TIME_ALL, + category=JmMagicConstants.CATEGORY_ALL, + order_by=JmMagicConstants.ORDER_BY_LATEST, + ) + results = [{"album_id": aid, "title": atitle} for aid, atitle in page] + return results + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) + +@app.get("/rankings/mouth") +@cache_result(cache_time) +def get_rankings_mouth(page: int): + try: + op = JmOption.default() + cl = op.new_jm_client() + page2: JmCategoryPage = cl.categories_filter( + page=page, + time=JmMagicConstants.TIME_MONTH, + category=JmMagicConstants.CATEGORY_ALL, + order_by=JmMagicConstants.ORDER_BY_LATEST, + ) + results = [{"album_id": aid, "title": atitle} for aid, atitle in page2] + return results + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) + +@app.get("/rankings/week") +@cache_result(cache_time) +def get_rankings_week(page: int): + try: + op = JmOption.default() + cl = op.new_jm_client() + page2: JmCategoryPage = cl.categories_filter( + page=page, + time=JmMagicConstants.TIME_WEEK, + category=JmMagicConstants.CATEGORY_ALL, + order_by=JmMagicConstants.ORDER_BY_LATEST, + ) + results = [{"album_id": aid, "title": atitle} for aid, atitle in page2] + return results + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) + + +# 启动API +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8982) diff --git a/jm/src/jmcomic/__init__.py b/jm/src/jmcomic/__init__.py new file mode 100755 index 0000000..df2750c --- /dev/null +++ b/jm/src/jmcomic/__init__.py @@ -0,0 +1,29 @@ +# 模块依赖关系如下: +# 被依赖方 <--- 使用方 +# config <--- entity <--- toolkit <--- client <--- option <--- downloader + +__version__ = '2.5.35' + +from .api import * +from .jm_plugin import * + +# 下面进行注册组件(客户端、插件) +gb = dict(filter(lambda pair: isinstance(pair[1], type), globals().items())) + + +def register_jmcomic_component(variables: Dict[str, Any], method, valid_interface: type): + for v in variables.values(): + if v != valid_interface and issubclass(v, valid_interface): + method(v) + + +# 注册客户端 +register_jmcomic_component(gb, + JmModuleConfig.register_client, + JmcomicClient, + ) +# 注册插件 +register_jmcomic_component(gb, + JmModuleConfig.register_plugin, + JmOptionPlugin, + ) diff --git a/jm/src/jmcomic/api.py b/jm/src/jmcomic/api.py new file mode 100755 index 0000000..4714145 --- /dev/null +++ b/jm/src/jmcomic/api.py @@ -0,0 +1,131 @@ +from .jm_downloader import * + +__DOWNLOAD_API_RET = Tuple[JmAlbumDetail, JmDownloader] + + +def download_batch(download_api, + jm_id_iter: Union[Iterable, Generator], + option=None, + downloader=None, + ) -> Set[__DOWNLOAD_API_RET]: + """ + 批量下载 album / photo + + 一个album/photo,对应一个线程,对应一个option + + :param download_api: 下载api + :param jm_id_iter: jmid (album_id, photo_id) 的迭代器 + :param option: 下载选项,所有的jmid共用一个option + :param downloader: 下载器类 + """ + from common import multi_thread_launcher + + if option is None: + option = JmModuleConfig.option_class().default() + + result = set() + + def callback(*ret): + result.add(ret) + + multi_thread_launcher( + iter_objs=set( + JmcomicText.parse_to_jm_id(jmid) + for jmid in jm_id_iter + ), + apply_each_obj_func=lambda aid: download_api(aid, + option, + downloader, + callback=callback, + ), + wait_finish=True + ) + + return result + + +def download_album(jm_album_id, + option=None, + downloader=None, + callback=None, + check_exception=True, + ) -> Union[__DOWNLOAD_API_RET, Set[__DOWNLOAD_API_RET]]: + """ + 下载一个本子(album),包含其所有的章节(photo) + + 当jm_album_id不是str或int时,视为批量下载,相当于调用 download_batch(download_album, jm_album_id, option, downloader) + + :param jm_album_id: 本子的禁漫车号 + :param option: 下载选项 + :param downloader: 下载器类 + :param callback: 返回值回调函数,可以拿到 album 和 downloader + :param check_exception: 是否检查异常, 如果为True,会检查downloader是否有下载异常,并上抛PartialDownloadFailedException + :return: 对于的本子实体类,下载器(如果是上述的批量情况,返回值为download_batch的返回值) + """ + + if not isinstance(jm_album_id, (str, int)): + return download_batch(download_album, jm_album_id, option, downloader) + + with new_downloader(option, downloader) as dler: + album = dler.download_album(jm_album_id) + + if callback is not None: + callback(album, dler) + if check_exception: + dler.raise_if_has_exception() + return album, dler + + +def download_photo(jm_photo_id, + option=None, + downloader=None, + callback=None, + check_exception=True, + ): + """ + 下载一个章节(photo),参数同 download_album + """ + if not isinstance(jm_photo_id, (str, int)): + return download_batch(download_photo, jm_photo_id, option) + + with new_downloader(option, downloader) as dler: + photo = dler.download_photo(jm_photo_id) + + if callback is not None: + callback(photo, dler) + if check_exception: + dler.raise_if_has_exception() + return photo, dler + + +def new_downloader(option=None, downloader=None) -> JmDownloader: + if option is None: + option = JmModuleConfig.option_class().default() + + if downloader is None: + downloader = JmModuleConfig.downloader_class() + + return downloader(option) + + +def create_option_by_file(filepath): + return JmModuleConfig.option_class().from_file(filepath) + + +def create_option_by_env(env_name='JM_OPTION_PATH'): + from .cl import get_env + + filepath = get_env(env_name, None) + ExceptionTool.require_true(filepath is not None, + f'未配置环境变量: {env_name},请配置为option的文件路径') + return create_option_by_file(filepath) + + +def create_option_by_str(text: str, mode=None): + if mode is None: + mode = PackerUtil.mode_yml + data = PackerUtil.unpack_by_str(text, mode)[0] + return JmModuleConfig.option_class().construct(data) + + +create_option = create_option_by_file diff --git a/jm/src/jmcomic/cl.py b/jm/src/jmcomic/cl.py new file mode 100755 index 0000000..a3e2add --- /dev/null +++ b/jm/src/jmcomic/cl.py @@ -0,0 +1,121 @@ +""" +command-line usage + +for example, download album 123 456, photo 333: + +$ jmcomic 123 456 p333 --option="D:/option.yml" + + +""" +import os.path +from typing import List, Optional + + +def get_env(name, default): + import os + value = os.getenv(name, None) + if value is None or value == '': + return default + + return value + + +class JmcomicUI: + + def __init__(self) -> None: + self.option_path: Optional[str] = None + self.raw_id_list: List[str] = [] + self.album_id_list: List[str] = [] + self.photo_id_list: List[str] = [] + + def parse_arg(self): + import argparse + parser = argparse.ArgumentParser(prog='python -m jmcomic', description='JMComic Command Line Downloader') + parser.add_argument( + 'id_list', + nargs='*', + help='input all album/photo ids that you want to download, separating them by spaces. ' + 'Need add a "p" prefix to indicate a photo id, such as `123 456 p333`.', + default=[], + ) + + parser.add_argument( + '--option', + help='path to the option file, you can also specify it by env `JM_OPTION_PATH`', + type=str, + default=get_env('JM_OPTION_PATH', ''), + ) + + args = parser.parse_args() + option = args.option + if len(option) == 0 or option == "''": + self.option_path = None + else: + self.option_path = os.path.abspath(option) + + self.raw_id_list = args.id_list + self.parse_raw_id() + + def parse_raw_id(self): + + def parse(text): + from .jm_toolkit import JmcomicText + + try: + return JmcomicText.parse_to_jm_id(text) + except Exception as e: + print(e.args[0]) + exit(1) + + for raw_id in self.raw_id_list: + if raw_id.startswith('p'): + self.photo_id_list.append(parse(raw_id[1:])) + elif raw_id.startswith('a'): + self.album_id_list.append(parse(raw_id[1:])) + else: + self.album_id_list.append(parse(raw_id)) + + def main(self): + self.parse_arg() + from .api import jm_log + jm_log('command_line', + f'start downloading...\n' + f'- using option: [{self.option_path or "default"}]\n' + f'to be downloaded: \n' + f'- album: {self.album_id_list}\n' + f'- photo: {self.photo_id_list}') + + from .api import create_option, JmOption + if self.option_path is not None: + option = create_option(self.option_path) + else: + option = JmOption.default() + + self.run(option) + + def run(self, option): + from .api import download_album, download_photo + from common import MultiTaskLauncher + + if len(self.album_id_list) == 0: + download_photo(self.photo_id_list, option) + elif len(self.photo_id_list) == 0: + download_album(self.album_id_list, option) + else: + # 同时下载album和photo + launcher = MultiTaskLauncher() + + launcher.create_task( + target=download_album, + args=(self.album_id_list, option) + ) + launcher.create_task( + target=download_photo, + args=(self.photo_id_list, option) + ) + + launcher.wait_finish() + + +def main(): + JmcomicUI().main() diff --git a/jm/src/jmcomic/jm_client_impl.py b/jm/src/jmcomic/jm_client_impl.py new file mode 100755 index 0000000..a9ac346 --- /dev/null +++ b/jm/src/jmcomic/jm_client_impl.py @@ -0,0 +1,1176 @@ +from threading import Lock + +from .jm_client_interface import * + + +# 抽象基类,实现了域名管理,发请求,重试机制,log,缓存等功能 +class AbstractJmClient( + JmcomicClient, + PostmanProxy, +): + client_key = '__just_for_placeholder_do_not_use_me__' + func_to_cache = [] + + def __init__(self, + postman: Postman, + domain_list: List[str], + retry_times=0, + ): + """ + 创建JM客户端 + + :param postman: 负责实现HTTP请求的对象,持有cookies、headers、proxies等信息 + :param domain_list: 禁漫域名 + :param retry_times: 重试次数 + """ + super().__init__(postman) + self.retry_times = retry_times + self.domain_list = domain_list + self.CLIENT_CACHE = None + self._username = None # help for favorite_folder method + self.enable_cache() + self.after_init() + + def after_init(self): + pass + + def get(self, url, **kwargs): + return self.request_with_retry(self.postman.get, url, **kwargs) + + def post(self, url, **kwargs): + return self.request_with_retry(self.postman.post, url, **kwargs) + + def of_api_url(self, api_path, domain): + return JmcomicText.format_url(api_path, domain) + + def get_jm_image(self, img_url) -> JmImageResp: + + def callback(resp): + """ + 使用此方法包装 self.get,使得图片数据为空时,判定为请求失败时,走重试逻辑 + """ + resp = JmImageResp(resp) + resp.require_success() + return resp + + return self.get(img_url, callback=callback, headers=JmModuleConfig.new_html_headers()) + + def request_with_retry(self, + request, + url, + domain_index=0, + retry_count=0, + callback=None, + **kwargs, + ): + """ + 支持重试和切换域名的机制 + + 如果url包含了指定域名,则不会切换域名,例如图片URL。 + + 如果需要拿到域名进行回调处理,可以重写 self.update_request_with_specify_domain 方法,例如更新headers + + :param request: 请求方法 + :param url: 图片url / path (/album/xxx) + :param domain_index: 域名下标 + :param retry_count: 重试次数 + :param callback: 回调,可以接收resp返回新的resp,也可以抛出异常强制重试 + :param kwargs: 请求方法的kwargs + """ + if domain_index >= len(self.domain_list): + return self.fallback(request, url, domain_index, retry_count, **kwargs) + + url_backup = url + + if url.startswith('/'): + # path → url + domain = self.domain_list[domain_index] + url = self.of_api_url(url, domain) + + self.update_request_with_specify_domain(kwargs, domain) + + jm_log(self.log_topic(), self.decode(url)) + else: + # 图片url + self.update_request_with_specify_domain(kwargs, None, True) + + if domain_index != 0 or retry_count != 0: + jm_log(f'req.retry', + ', '.join([ + f'次数: [{retry_count}/{self.retry_times}]', + f'域名: [{domain_index} of {self.domain_list}]', + f'路径: [{url}]', + f'参数: [{kwargs if "login" not in url else "#login_form#"}]' + ]) + ) + + try: + resp = request(url, **kwargs) + + # 回调,可以接收resp返回新的resp,也可以抛出异常强制重试 + if callback is not None: + resp = callback(resp) + + # 依然是回调,在最后返回之前,还可以判断resp是否重试 + resp = self.raise_if_resp_should_retry(resp) + + return resp + except Exception as e: + if self.retry_times == 0: + raise e + + self.before_retry(e, kwargs, retry_count, url) + + if retry_count < self.retry_times: + return self.request_with_retry(request, url_backup, domain_index, retry_count + 1, callback, **kwargs) + else: + return self.request_with_retry(request, url_backup, domain_index + 1, 0, callback, **kwargs) + + # noinspection PyMethodMayBeStatic + def raise_if_resp_should_retry(self, resp): + """ + 依然是回调,在最后返回之前,还可以判断resp是否重试 + """ + return resp + + def update_request_with_specify_domain(self, kwargs: dict, domain: Optional[str], is_image: bool = False): + """ + 域名自动切换时,用于更新请求参数的回调 + """ + pass + + # noinspection PyMethodMayBeStatic + def log_topic(self): + return self.client_key + + # noinspection PyMethodMayBeStatic, PyUnusedLocal + def before_retry(self, e, kwargs, retry_count, url): + jm_log('req.error', str(e)) + + def enable_cache(self): + # noinspection PyDefaultArgument,PyShadowingBuiltins + def make_key(args, kwds, typed, + kwd_mark=(object(),), + fasttypes={int, str}, + tuple=tuple, type=type, len=len): + key = args + if kwds: + key += kwd_mark + for item in kwds.items(): + key += item + if typed: + key += tuple(type(v) for v in args) + if kwds: + key += tuple(type(v) for v in kwds.values()) + elif len(key) == 1 and type(key[0]) in fasttypes: + return key[0] + return hash(key) + + def wrap_func_with_cache(func_name, cache_field_name): + if hasattr(self, cache_field_name): + return + + func = getattr(self, func_name) + + def cache_wrapper(*args, **kwargs): + cache = self.CLIENT_CACHE + + # Equivalent to not enable cache + if cache is None: + return func(*args, **kwargs) + + key = make_key(args, kwargs, False) + sentinel = object() # unique object used to signal cache misses + + result = cache.get(key, sentinel) + if result is not sentinel: + return result + + result = func(*args, **kwargs) + cache[key] = result + return result + + setattr(self, func_name, cache_wrapper) + + for func_name in self.func_to_cache: + wrap_func_with_cache(func_name, f'__{func_name}.cache.dict__') + + def set_cache_dict(self, cache_dict: Optional[Dict]): + self.CLIENT_CACHE = cache_dict + + def get_cache_dict(self): + return self.CLIENT_CACHE + + def get_domain_list(self): + return self.domain_list + + def set_domain_list(self, domain_list: List[str]): + self.domain_list = domain_list + + # noinspection PyUnusedLocal + def fallback(self, request, url, domain_index, retry_count, **kwargs): + msg = f"请求重试全部失败: [{url}], {self.domain_list}" + jm_log('req.fallback', msg) + ExceptionTool.raises(msg, {}, RequestRetryAllFailException) + + # noinspection PyMethodMayBeStatic + def append_params_to_url(self, url, params): + from urllib.parse import urlencode + + # 将参数字典编码为查询字符串 + query_string = urlencode(params) + url = f"{url}?{query_string}" + return url + + # noinspection PyMethodMayBeStatic + def decode(self, url: str): + if not JmModuleConfig.FLAG_DECODE_URL_WHEN_LOGGING or '/search/' not in url: + return url + + from urllib.parse import unquote + return unquote(url.replace('+', ' ')) + + +# 基于网页实现的JmClient +class JmHtmlClient(AbstractJmClient): + client_key = 'html' + + func_to_cache = ['search', 'fetch_detail_entity'] + + API_SEARCH = '/search/photos' + API_CATEGORY = '/albums' + + def add_favorite_album(self, + album_id, + folder_id='0', + ): + data = { + 'album_id': album_id, + 'fid': folder_id, + } + + resp = self.get_jm_html( + '/ajax/favorite_album', + data=data, + ) + + res = resp.json() + + if res['status'] != 1: + msg = parse_unicode_escape_text(res['msg']) + error_msg = PatternTool.match_or_default(msg, JmcomicText.pattern_ajax_favorite_msg, msg) + # 此圖片已經在您最喜愛的清單! + + self.raise_request_error( + resp, + error_msg + ) + + return resp + + def get_album_detail(self, album_id) -> JmAlbumDetail: + return self.fetch_detail_entity(album_id, 'album') + + def get_photo_detail(self, + photo_id, + fetch_album=True, + fetch_scramble_id=True, + ) -> JmPhotoDetail: + photo = self.fetch_detail_entity(photo_id, 'photo') + + # 一并获取该章节的所处本子 + # todo: 可优化,获取章节所在本子,其实不需要等待章节获取完毕后。 + # 可以直接调用 self.get_album_detail(photo_id),会重定向返回本子的HTML + # (had polished by FutureClientProxy) + if fetch_album is True: + photo.from_album = self.get_album_detail(photo.album_id) + + return photo + + def fetch_detail_entity(self, jmid, prefix): + # 参数校验 + jmid = JmcomicText.parse_to_jm_id(jmid) + + # 请求 + resp = self.get_jm_html(f"/{prefix}/{jmid}") + + # 用 JmcomicText 解析 html,返回实体类 + if prefix == 'album': + return JmcomicText.analyse_jm_album_html(resp.text) + + if prefix == 'photo': + return JmcomicText.analyse_jm_photo_html(resp.text) + + def search(self, + search_query: str, + page: int, + main_tag: int, + order_by: str, + time: str, + category: str, + sub_category: Optional[str], + ) -> JmSearchPage: + """ + 网页搜索API + """ + params = { + 'main_tag': main_tag, + 'search_query': search_query, + 'page': page, + 'o': order_by, + 't': time, + } + + url = self.build_search_url(self.API_SEARCH, category, sub_category) + + resp = self.get_jm_html( + self.append_params_to_url(url, params), + allow_redirects=True, + ) + + # 检查是否发生了重定向 + # 因为如果搜索的是禁漫车号,会直接跳转到本子详情页面 + if resp.redirect_count != 0 and '/album/' in resp.url: + album = JmcomicText.analyse_jm_album_html(resp.text) + return JmSearchPage.wrap_single_album(album) + else: + return JmPageTool.parse_html_to_search_page(resp.text) + + @classmethod + def build_search_url(cls, base: str, category: str, sub_category: Optional[str]): + """ + 构建网页搜索/分类的URL + + 示例: + :param base: "/search/photos" + :param category CATEGORY_DOUJIN + :param sub_category SUB_DOUJIN_CG + :return "/search/photos/doujin/sub/CG" + """ + if category == JmMagicConstants.CATEGORY_ALL: + return base + + if sub_category is None: + return f'{base}/{category}' + else: + return f'{base}/{category}/sub/{sub_category}' + + def categories_filter(self, + page: int, + time: str, + category: str, + order_by: str, + sub_category: Optional[str] = None, + ) -> JmCategoryPage: + params = { + 'page': page, + 'o': order_by, + 't': time, + } + + url = self.build_search_url(self.API_CATEGORY, category, sub_category) + + resp = self.get_jm_html( + self.append_params_to_url(url, params), + allow_redirects=True, + ) + + return JmPageTool.parse_html_to_category_page(resp.text) + + # -- 帐号管理 -- + + def login(self, + username, + password, + id_remember='on', + login_remember='on', + ): + """ + 返回response响应对象 + """ + + data = { + 'username': username, + 'password': password, + 'id_remember': id_remember, + 'login_remember': login_remember, + 'submit_login': '', + } + + resp = self.post('/login', + data=data, + allow_redirects=False, + ) + + if resp.status_code != 200: + ExceptionTool.raises_resp(f'登录失败,状态码为{resp.status_code}', resp) + + orig_cookies = self.get_meta_data('cookies') or {} + new_cookies = dict(resp.cookies) + # 重复登录下存在bug,AVS会丢失 + if 'AVS' in orig_cookies and 'AVS' not in new_cookies: + return resp + + self['cookies'] = new_cookies + self._username = username + + return resp + + def favorite_folder(self, + page=1, + order_by=JmMagicConstants.ORDER_BY_LATEST, + folder_id='0', + username='', + ) -> JmFavoritePage: + if username == '': + ExceptionTool.require_true(self._username is not None, 'favorite_folder方法需要传username参数') + username = self._username + + resp = self.get_jm_html( + f'/user/{username}/favorite/albums', + params={ + 'page': page, + 'o': order_by, + 'folder_id': folder_id, + } + ) + + return JmPageTool.parse_html_to_favorite_page(resp.text) + + # noinspection PyTypeChecker + def get_username_from_cookies(self) -> str: + # cookies = self.get_meta_data('cookies', None) + # if not cookies: + # ExceptionTool.raises('未登录,无法获取到对应的用户名,请给favorite方法传入username参数') + # 解析cookies,可能需要用到 phpserialize,比较麻烦,暂不实现 + pass + + def get_jm_html(self, url, require_200=True, **kwargs): + """ + 请求禁漫网页的入口 + """ + resp = self.get(url, **kwargs) + + if require_200 is True and resp.status_code != 200: + # 检查是否是特殊的状态码(JmModuleConfig.JM_ERROR_STATUS_CODE) + # 如果是,直接抛出异常 + self.check_special_http_code(resp) + # 运行到这里说明上一步没有抛异常,说明是未知状态码,抛异常兜底处理 + self.raise_request_error(resp) + + # 检查请求是否成功 + self.require_resp_success_else_raise(resp, url) + + return resp + + def update_request_with_specify_domain(self, kwargs: dict, domain: Optional[str], is_image=False): + if is_image: + return + + latest_headers = kwargs.get('headers', None) + base_headers = self.get_meta_data('headers', None) or JmModuleConfig.new_html_headers(domain) + base_headers.update(latest_headers or {}) + kwargs['headers'] = base_headers + + @classmethod + def raise_request_error(cls, resp, msg: Optional[str] = None): + """ + 请求如果失败,统一由该方法抛出异常 + """ + if msg is None: + msg = f"请求失败," \ + f"响应状态码为{resp.status_code}," \ + f"URL=[{resp.url}]," \ + + (f"响应文本=[{resp.text}]" if len(resp.text) < 200 else + f'响应文本过长(len={len(resp.text)}),不打印' + ) + + ExceptionTool.raises_resp(msg, resp) + + def album_comment(self, + video_id, + comment, + originator='', + status='true', + comment_id=None, + **kwargs, + ) -> JmAlbumCommentResp: + data = { + 'video_id': video_id, + 'comment': comment, + 'originator': originator, + 'status': status, + } + + # 处理回复评论 + if comment_id is not None: + data.pop('status') + data['comment_id'] = comment_id + data['is_reply'] = 1 + data['forum_subject'] = 1 + + jm_log('album.comment', + f'{video_id}: [{comment}]' + + (f' to ({comment_id})' if comment_id is not None else '') + ) + + resp = self.post('/ajax/album_comment', data=data) + + ret = JmAlbumCommentResp(resp) + jm_log('album.comment', f'{video_id}: [{comment}] ← ({ret.model().cid})') + + return ret + + @classmethod + def require_resp_success_else_raise(cls, resp, url: str): + """ + :param resp: 响应对象 + :param url: /photo/12412312 + """ + resp_url: str = resp.url + + # 1. 是否是特殊的内容 + cls.check_special_text(resp) + + # 2. 检查响应发送重定向,重定向url是否表示错误网页,即 /error/xxx + if resp.redirect_count == 0 or '/error/' not in resp_url: + return + + # 3. 检查错误类型 + def match_case(error_path): + return resp_url.endswith(error_path) and not url.endswith(error_path) + + # 3.1 album_missing + if match_case('/error/album_missing'): + ExceptionTool.raise_missing(resp, JmcomicText.parse_to_jm_id(url)) + + # 3.2 user_missing + if match_case('/error/user_missing'): + ExceptionTool.raises_resp('此用戶名稱不存在,或者你没有登录,請再次確認使用名稱', resp) + + # 3.3 invalid_module + if match_case('/error/invalid_module'): + ExceptionTool.raises_resp('發生了無法預期的錯誤。若問題持續發生,請聯繫客服支援', resp) + + @classmethod + def check_special_text(cls, resp): + html = resp.text + url = resp.url + + if len(html) > 500: + return + + for content, reason in JmModuleConfig.JM_ERROR_RESPONSE_TEXT.items(): + if content not in html: + continue + + cls.raise_request_error( + resp, + f'{reason}({content})' + + (f': {url}' if url is not None else '') + ) + + @classmethod + def check_special_http_code(cls, resp): + code = resp.status_code + url = resp.url + + error_msg = JmModuleConfig.JM_ERROR_STATUS_CODE.get(int(code), None) + if error_msg is None: + return + + cls.raise_request_error( + resp, + f"请求失败," + f"响应状态码为{code}," + f'原因为: [{error_msg}], ' + + (f'URL=[{url}]' if url is not None else '') + ) + + +# 基于禁漫移动端(APP)实现的JmClient +class JmApiClient(AbstractJmClient): + client_key = 'api' + func_to_cache = ['search', 'fetch_detail_entity'] + + API_SEARCH = '/search' + API_CATEGORIES_FILTER = '/categories/filter' + API_ALBUM = '/album' + API_CHAPTER = '/chapter' + API_SCRAMBLE = '/chapter_view_template' + API_FAVORITE = '/favorite' + + def search(self, + search_query: str, + page: int, + main_tag: int, + order_by: str, + time: str, + category: str, + sub_category: Optional[str], + ) -> JmSearchPage: + """ + 移动端暂不支持 category和sub_category + """ + params = { + 'main_tag': main_tag, + 'search_query': search_query, + 'page': page, + 'o': order_by, + 't': time, + } + + resp = self.req_api(self.append_params_to_url(self.API_SEARCH, params)) + + # 直接搜索禁漫车号,发生重定向的响应数据 resp.model_data + # { + # "search_query": "310311", + # "total": 1, + # "redirect_aid": "310311", + # "content": [] + # } + data = resp.model_data + if data.get('redirect_aid', None) is not None: + aid = data.redirect_aid + return JmSearchPage.wrap_single_album(self.get_album_detail(aid)) + + return JmPageTool.parse_api_to_search_page(data) + + def categories_filter(self, + page: int, + time: str, + category: str, + order_by: str, + sub_category: Optional[str] = None, + ): + """ + 移动端不支持 sub_category + """ + # o: mv, mv_m, mv_w, mv_t + o = f'{order_by}_{time}' if time != JmMagicConstants.TIME_ALL else order_by + + params = { + 'page': page, + 'order': '', # 该参数为空 + 'c': category, + 'o': o, + } + + resp = self.req_api(self.append_params_to_url(self.API_CATEGORIES_FILTER, params)) + + return JmPageTool.parse_api_to_search_page(resp.model_data) + + def get_album_detail(self, album_id) -> JmAlbumDetail: + return self.fetch_detail_entity(album_id, + JmModuleConfig.album_class(), + ) + + def get_photo_detail(self, + photo_id, + fetch_album=True, + fetch_scramble_id=True, + ) -> JmPhotoDetail: + photo: JmPhotoDetail = self.fetch_detail_entity(photo_id, + JmModuleConfig.photo_class(), + ) + if fetch_album or fetch_scramble_id: + self.fetch_photo_additional_field(photo, fetch_album, fetch_scramble_id) + + return photo + + def get_scramble_id(self, photo_id, album_id=None): + """ + 带有缓存的fetch_scramble_id,缓存位于 JmModuleConfig.SCRAMBLE_CACHE + """ + cache = JmModuleConfig.SCRAMBLE_CACHE + if photo_id in cache: + return cache[photo_id] + + if album_id is not None and album_id in cache: + return cache[album_id] + + scramble_id = self.fetch_scramble_id(photo_id) + cache[photo_id] = scramble_id + if album_id is not None: + cache[album_id] = scramble_id + + return scramble_id + + def fetch_detail_entity(self, jmid, clazz): + """ + 请求实体类 + """ + jmid = JmcomicText.parse_to_jm_id(jmid) + url = self.API_ALBUM if issubclass(clazz, JmAlbumDetail) else self.API_CHAPTER + resp = self.req_api(self.append_params_to_url( + url, + { + 'id': jmid + }) + ) + + if resp.res_data.get('name') is None: + ExceptionTool.raise_missing(resp, jmid) + + return JmApiAdaptTool.parse_entity(resp.res_data, clazz) + + def fetch_scramble_id(self, photo_id): + """ + 请求scramble_id + """ + photo_id: str = JmcomicText.parse_to_jm_id(photo_id) + resp = self.req_api( + self.API_SCRAMBLE, + params={ + 'id': photo_id, + 'mode': 'vertical', + 'page': '0', + 'app_img_shunt': '1', + 'express': 'off', + 'v': time_stamp(), + }, + require_success=False, + ) + + scramble_id = PatternTool.match_or_default(resp.text, + JmcomicText.pattern_html_album_scramble_id, + None, + ) + if scramble_id is None: + jm_log('api.scramble', f'未匹配到scramble_id,响应文本:{resp.text}') + scramble_id = str(JmMagicConstants.SCRAMBLE_220980) + + return scramble_id + + def fetch_photo_additional_field(self, photo: JmPhotoDetail, fetch_album: bool, fetch_scramble_id: bool): + """ + 获取章节的额外信息 + 1. scramble_id + 2. album + 如果都需要获取,会排队,效率低 + + todo: 改进实现 (had polished by FutureClientProxy) + 1. 直接开两个线程跑 + 2. 开两个线程,但是开之前检查重复性 + 3. 线程池,也要检查重复性 + 23做法要改不止一处地方 + """ + if fetch_album: + photo.from_album = self.get_album_detail(photo.album_id) + + if fetch_scramble_id: + # 同album的scramble_id相同 + photo.scramble_id = self.get_scramble_id(photo.photo_id, photo.album_id) + + def setting(self) -> JmApiResp: + """ + 禁漫app的setting请求,返回如下内容(resp.res_data) + { + "logo_path": "https://cdn-msp.jmapiproxy1.monster/media/logo/new_logo.png", + "main_web_host": "18-comic.work", + "img_host": "https://cdn-msp.jmapiproxy1.monster", + "base_url": "https://www.jmapinode.biz", + "is_cn": 0, + "cn_base_url": "https://www.jmapinode.biz", + "version": "1.6.0", + "test_version": "1.6.1", + "store_link": "https://play.google.com/store/apps/details?id=com.jiaohua_browser", + "ios_version": "1.6.0", + "ios_test_version": "1.6.1", + "ios_store_link": "https://18comic.vip/stray/", + "ad_cache_version": 1698140798, + "bundle_url": "https://18-comic.work/static/apk/patches1.6.0.zip", + "is_hot_update": true, + "api_banner_path": "https://cdn-msp.jmapiproxy1.monster/media/logo/channel_log.png?v=", + "version_info": "\nAPP & IOS更新\nV1.6.0\n#禁漫 APK 更新拉!!\n更新調整以下項目\n1. 系統優化\n\nV1.5.9\n1. 跳錯誤新增 重試 網頁 按鈕\n2. 圖片讀取優化\n3. + 線路調整優化\n\n無法順利更新或是系統題是有風險請使用下方\n下載點2\n有問題可以到DC群反饋\nhttps://discord.gg/V74p7HM\n", + "app_shunts": [ + { + "title": "圖源1", + "key": 1 + }, + { + "title": "圖源2", + "key": 2 + }, + { + "title": "圖源3", + "key": 3 + }, + { + "title": "圖源4", + "key": 4 + } + ], + "download_url": "https://18-comic.work/static/apk/1.6.0.apk", + "app_landing_page": "https://jm365.work/pXYbfA", + "float_ad": true + } + """ + resp = self.req_api('/setting') + + # 检查禁漫最新的版本号 + setting_ver = str(resp.model_data.version) + # 禁漫接口的版本 > jmcomic库内置版本 + if setting_ver > JmMagicConstants.APP_VERSION and JmModuleConfig.FLAG_USE_VERSION_NEWER_IF_BEHIND: + jm_log('api.setting', f'change APP_VERSION from [{JmMagicConstants.APP_VERSION}] to [{setting_ver}]') + JmMagicConstants.APP_VERSION = setting_ver + + return resp + + def login(self, + username, + password, + ) -> JmApiResp: + """ + { + "uid": "123", + "username": "x", + "email": "x", + "emailverified": "yes", + "photo": "x", + "fname": "", + "gender": "x", + "message": "Welcome x!", + "coin": 123, + "album_favorites": 123, + "s": "x", + "level_name": "x", + "level": 1, + "nextLevelExp": 123, + "exp": "123", + "expPercent": 123, + "badges": [], + "album_favorites_max": 123 + } + + """ + resp = self.req_api('/login', False, data={ + 'username': username, + 'password': password, + }) + + cookies = dict(resp.resp.cookies) + cookies.update({'AVS': resp.res_data['s']}) + self['cookies'] = cookies + + return resp + + def favorite_folder(self, + page=1, + order_by=JmMagicConstants.ORDER_BY_LATEST, + folder_id='0', + username='', + ) -> JmFavoritePage: + resp = self.req_api( + self.API_FAVORITE, + params={ + 'page': page, + 'folder_id': folder_id, + 'o': order_by, + } + ) + + return JmPageTool.parse_api_to_favorite_page(resp.model_data) + + def add_favorite_album(self, + album_id, + folder_id='0', + ): + """ + 移动端没有提供folder_id参数 + """ + resp = self.req_api( + '/favorite', + data={ + 'aid': album_id, + }, + ) + + self.require_resp_status_ok(resp) + + return resp + + # noinspection PyMethodMayBeStatic + def require_resp_status_ok(self, resp: JmApiResp): + """ + 检查返回数据中的status字段是否为ok + """ + data = resp.model_data + if data.status == 'ok': + ExceptionTool.raises_resp(data.msg, resp) + + def req_api(self, url, get=True, require_success=True, **kwargs) -> JmApiResp: + ts = self.decide_headers_and_ts(kwargs, url) + + if get: + resp = self.get(url, **kwargs) + else: + resp = self.post(url, **kwargs) + + resp = JmApiResp(resp, ts) + + if require_success: + self.require_resp_success(resp, url) + + return resp + + def update_request_with_specify_domain(self, kwargs: dict, domain: Optional[str], is_image=False): + if is_image: + # 设置APP端的图片请求headers + kwargs['headers'] = {**JmModuleConfig.APP_HEADERS_TEMPLATE, **JmModuleConfig.APP_HEADERS_IMAGE} + + # noinspection PyMethodMayBeStatic + def decide_headers_and_ts(self, kwargs, url): + # 获取时间戳 + if url == self.API_SCRAMBLE: + # /chapter_view_template + # 这个接口很特殊,用的密钥 18comicAPPContent 而不是 18comicAPP + # 如果用后者,则会返回403信息 + ts = time_stamp() + token, tokenparam = JmCryptoTool.token_and_tokenparam(ts, secret=JmMagicConstants.APP_TOKEN_SECRET_2) + + elif JmModuleConfig.FLAG_USE_FIX_TIMESTAMP: + ts, token, tokenparam = JmModuleConfig.get_fix_ts_token_tokenparam() + + else: + ts = time_stamp() + token, tokenparam = JmCryptoTool.token_and_tokenparam(ts) + + # 设置headers + headers = kwargs.get('headers', None) or JmModuleConfig.APP_HEADERS_TEMPLATE.copy() + headers.update({ + 'token': token, + 'tokenparam': tokenparam, + }) + kwargs['headers'] = headers + + return ts + + @classmethod + def require_resp_success(cls, resp: JmApiResp, url: Optional[str] = None): + """ + + :param resp: 响应对象 + :param url: 请求路径,例如 /setting + """ + resp.require_success() + + # 1. 检查是否 album_missing + # json: {'code': 200, 'data': []} + data = resp.model().data + if isinstance(data, list) and len(data) == 0: + ExceptionTool.raise_missing(resp, JmcomicText.parse_to_jm_id(url)) + + # 2. 是否是特殊的内容 + # 暂无 + + def raise_if_resp_should_retry(self, resp): + """ + 该方法会判断resp返回值是否是json格式, + 如果不是,大概率是禁漫内部异常,需要进行重试 + + 由于完整的json格式校验会有性能开销,所以只做简单的检查, + 只校验第一个有效字符是不是 '{',如果不是,就认为异常数据,需要重试 + + :param resp: 响应对象 + :return: resp + """ + if isinstance(resp, JmResp): + # 不对包装过的resp对象做校验,包装者自行校验 + # 例如图片请求 + return resp + + code = resp.status_code + if code >= 500: + msg = JmModuleConfig.JM_ERROR_STATUS_CODE.get(code, f'HTTP状态码: {code}') + ExceptionTool.raises_resp(f"禁漫API异常响应, {msg}", resp) + + url = resp.request.url + + if self.API_SCRAMBLE in url: + # /chapter_view_template 这个接口不是返回json数据,不做检查 + return resp + + text = resp.text + for char in text: + if char not in (' ', '\n', '\t'): + # 找到第一个有效字符 + ExceptionTool.require_true( + char == '{', + f'请求不是json格式,强制重试!响应文本: [{resp.text}]' + ) + return resp + + ExceptionTool.raises_resp(f'响应无数据!request_url=[{url}]', resp) + + def after_init(self): + # 保证拥有cookies,因为移动端要求必须携带cookies,否则会直接跳转同一本子【禁漫娘】 + if JmModuleConfig.FLAG_API_CLIENT_REQUIRE_COOKIES: + self.ensure_have_cookies() + + client_init_cookies_lock = Lock() + + def ensure_have_cookies(self): + if self.get_meta_data('cookies'): + return + + with self.client_init_cookies_lock: + if self.get_meta_data('cookies'): + return + + self['cookies'] = self.get_cookies() + + @field_cache("APP_COOKIES", obj=JmModuleConfig) + def get_cookies(self): + resp = self.setting() + cookies = dict(resp.resp.cookies) + return cookies + + +class PhotoConcurrentFetcherProxy(JmcomicClient): + """ + 为了解决 JmApiClient.get_photo_detail 方法的排队调用问题, + 即在访问完photo的接口后,需要另外排队访问获取album和scramble_id的接口。 + + 这三个接口可以并发请求,这样可以提高效率。 + + 此Proxy代理了get_photo_detail,实现了并发请求这三个接口,然后组装返回值返回photo。 + + 可通过插件 ClientProxyPlugin 启用本类,配置如下: + ```yml + plugins: + after_init: + - plugin: client_proxy + kwargs: + proxy_client_key: photo_concurrent_fetcher_proxy + ``` + """ + client_key = 'photo_concurrent_fetcher_proxy' + + class FutureWrapper: + def __init__(self, future, after_done_callback): + from concurrent.futures import Future + future: Future + self.future = future + self.done = False + self._result = None + self.after_done_callback = after_done_callback + + def result(self): + if not self.done: + result = self.future.result() + self._result = result + self.done = True + self.future = None # help gc + self.after_done_callback() + + return self._result + + def __init__(self, + client: JmcomicClient, + max_workers=None, + executors=None, + ): + self.client = client + self.route_notimpl_method_to_internal_client(client) + + if executors is None: + from concurrent.futures import ThreadPoolExecutor + executors = ThreadPoolExecutor(max_workers) + + self.executors = executors + self.future_dict: Dict[str, PhotoConcurrentFetcherProxy.FutureWrapper] = {} + from threading import Lock + self.lock = Lock() + + def route_notimpl_method_to_internal_client(self, client): + + proxy_methods = str_to_set(''' + get_album_detail + get_photo_detail + ''') + + # 获取对象的所有属性和方法的名称列表 + attributes_and_methods = dir(client) + # 遍历属性和方法列表,并访问每个方法 + for method in attributes_and_methods: + # 判断是否为方法(可调用对象) + if (not method.startswith('_') + and callable(getattr(client, method)) + and method not in proxy_methods + ): + setattr(self, method, getattr(client, method)) + + def get_album_detail(self, album_id) -> JmAlbumDetail: + album_id = JmcomicText.parse_to_jm_id(album_id) + cache_key = f'album_{album_id}' + future = self.get_future(cache_key, task=lambda: self.client.get_album_detail(album_id)) + return future.result() + + def get_future(self, cache_key, task): + if cache_key in self.future_dict: + # cache hit, means that a same task is running + return self.future_dict[cache_key] + + with self.lock: + if cache_key in self.future_dict: + return self.future_dict[cache_key] + + # after future done, remove it from future_dict. + # cache depends on self.client instead of self.future_dict + future = self.FutureWrapper(self.executors.submit(task), + after_done_callback=lambda: self.future_dict.pop(cache_key, None) + ) + + self.future_dict[cache_key] = future + return future + + def get_photo_detail(self, photo_id, fetch_album=True, fetch_scramble_id=True) -> JmPhotoDetail: + photo_id = JmcomicText.parse_to_jm_id(photo_id) + client: JmcomicClient = self.client + futures = [None, None, None] + results = [None, None, None] + + # photo_detail + photo_future = self.get_future(f'photo_{photo_id}', + lambda: client.get_photo_detail(photo_id, + False, + False) + ) + futures[0] = photo_future + + # fetch_album + if fetch_album: + album_future = self.get_future(f'album_{photo_id}', + lambda: client.get_album_detail(photo_id)) + futures[1] = album_future + else: + results[1] = None + + # fetch_scramble_id + if fetch_scramble_id and isinstance(client, JmApiClient): + client: JmApiClient + scramble_future = self.get_future(f'scramble_id_{photo_id}', + lambda: client.get_scramble_id(photo_id)) + futures[2] = scramble_future + else: + results[2] = '' + + # wait finish + for i, f in enumerate(futures): + if f is None: + continue + results[i] = f.result() + + # compose + photo: JmPhotoDetail = results[0] + album = results[1] + scramble_id = results[2] + + if album is not None: + photo.from_album = album + if scramble_id != '': + photo.scramble_id = scramble_id + + return photo diff --git a/jm/src/jmcomic/jm_client_interface.py b/jm/src/jmcomic/jm_client_interface.py new file mode 100755 index 0000000..5ec98ea --- /dev/null +++ b/jm/src/jmcomic/jm_client_interface.py @@ -0,0 +1,606 @@ +from .jm_toolkit import * + +""" + +Response Entity + +""" + + +class JmResp: + + def __init__(self, resp): + ExceptionTool.require_true(not isinstance(resp, JmResp), f'重复包装: {resp}') + self.resp = resp + + @property + def is_success(self) -> bool: + return self.http_code == 200 and len(self.content) != 0 + + @property + def is_not_success(self) -> bool: + return not self.is_success + + @property + def content(self): + return self.resp.content + + @property + def http_code(self): + return self.resp.status_code + + @property + def text(self) -> str: + return self.resp.text + + @property + def url(self) -> str: + return self.resp.url + + def require_success(self): + if self.is_not_success: + ExceptionTool.raises_resp(self.error_msg(), self) + + def error_msg(self): + return self.text + + +class JmImageResp(JmResp): + + def error_msg(self): + msg = f'禁漫图片获取失败: [{self.url}]' + if self.http_code != 200: + msg += f',http状态码={self.http_code}' + if len(self.content) == 0: + msg += f',响应数据为空' + return msg + + def transfer_to(self, + path, + scramble_id, + decode_image=True, + img_url=None, + ): + img_url = img_url or self.url + + if decode_image is False or scramble_id is None: + # 不解密图片,直接保存文件 + JmImageTool.save_resp_img( + self, + path, + need_convert=suffix_not_equal(img_url[:img_url.find("?")], path), + ) + else: + # 解密图片并保存文件 + JmImageTool.decode_and_save( + JmImageTool.get_num_by_url(scramble_id, img_url), + JmImageTool.open_image(self.content), + path, + ) + + +class JmJsonResp(JmResp): + + @field_cache() + def json(self) -> Dict: + try: + return self.resp.json() + except Exception as e: + ExceptionTool.raises_resp(f'json解析失败: {e}', self, JsonResolveFailException) + + def model(self) -> AdvancedDict: + return AdvancedDict(self.json()) + + +class JmApiResp(JmJsonResp): + + def __init__(self, resp, ts: str): + super().__init__(resp) + self.ts = ts + + @property + def is_success(self) -> bool: + return super().is_success and self.json()['code'] == 200 + + @property + @field_cache() + def decoded_data(self) -> str: + return JmCryptoTool.decode_resp_data(self.encoded_data, self.ts) + + @property + def encoded_data(self) -> str: + return self.json()['data'] + + @property + def res_data(self) -> Any: + self.require_success() + from json import loads + return loads(self.decoded_data) + + @property + def model_data(self) -> AdvancedDict: + self.require_success() + return AdvancedDict(self.res_data) + + +# album-comment +class JmAlbumCommentResp(JmJsonResp): + + def is_success(self) -> bool: + return super().is_success and self.json()['err'] is False + + +""" + +Client Interface + +""" + + +class JmDetailClient: + + def get_album_detail(self, album_id) -> JmAlbumDetail: + raise NotImplementedError + + def get_photo_detail(self, + photo_id, + fetch_album=True, + fetch_scramble_id=True, + ) -> JmPhotoDetail: + raise NotImplementedError + + def check_photo(self, photo: JmPhotoDetail): + """ + photo来源有两种: + 1. album[?] + 2. client.get_photo_detail(?) + + 其中,只有[2]是可以包含下载图片的url信息的。 + 本方法会检查photo是不是[1], + 如果是[1],通过请求获取[2],然后把2中的一些重要字段更新到1中 + + :param photo: 被检查的JmPhotoDetail对象 + """ + # 检查 from_album + if photo.from_album is None: + photo.from_album = self.get_album_detail(photo.album_id) + + # 检查 page_arr 和 data_original_domain + if photo.page_arr is None or photo.data_original_domain is None: + new = self.get_photo_detail(photo.photo_id, False) + new.from_album = photo.from_album + photo.__dict__.update(new.__dict__) + + +class JmUserClient: + + def login(self, + username: str, + password: str, + ): + """ + 1. 返回response响应对象 + 2. 保证当前client拥有登录cookies + """ + raise NotImplementedError + + def album_comment(self, + video_id, + comment, + originator='', + status='true', + comment_id=None, + **kwargs, + ) -> JmAlbumCommentResp: + """ + 评论漫画/评论回复 + :param video_id: album_id/photo_id + :param comment: 评论内容 + :param status: 是否 "有劇透" + :param comment_id: 被回复评论的id + :param originator: + :returns: JmAcResp 对象 + """ + raise NotImplementedError + + def favorite_folder(self, + page=1, + order_by=JmMagicConstants.ORDER_BY_LATEST, + folder_id='0', + username='', + ) -> JmFavoritePage: + """ + 获取收藏了的漫画,文件夹默认是全部 + :param folder_id: 文件夹id + :param page: 分页 + :param order_by: 排序 + :param username: 用户名 + """ + raise NotImplementedError + + def add_favorite_album(self, + album_id, + folder_id='0', + ): + """ + 把漫画加入收藏夹 + """ + raise NotImplementedError + + +class JmImageClient: + + # -- 下载图片 -- + + def download_image(self, + img_url: str, + img_save_path: str, + scramble_id: Optional[int] = None, + decode_image=True, + ): + """ + 下载JM的图片 + :param img_url: 图片url + :param img_save_path: 图片保存位置 + :param scramble_id: 图片所在photo的scramble_id + :param decode_image: 要保存的是解密后的图还是原图 + """ + # 请求图片 + resp = self.get_jm_image(img_url) + + resp.require_success() + + return self.save_image_resp(decode_image, img_save_path, img_url, resp, scramble_id) + + # noinspection PyMethodMayBeStatic + def save_image_resp(self, decode_image, img_save_path, img_url, resp, scramble_id): + resp.transfer_to(img_save_path, scramble_id, decode_image, img_url) + + def download_by_image_detail(self, + image: JmImageDetail, + img_save_path, + decode_image=True, + ): + return self.download_image( + image.download_url, + img_save_path, + int(image.scramble_id), + decode_image=decode_image, + ) + + def get_jm_image(self, img_url) -> JmImageResp: + raise NotImplementedError + + @classmethod + def img_is_not_need_to_decode(cls, data_original: str, _resp) -> bool: + # https://cdn-msp2.18comic.vip/media/photos/498976/00027.gif?v=1697541064 + query_params_index = data_original.find('?') + + if query_params_index != -1: + data_original = data_original[:query_params_index] + + # https://cdn-msp2.18comic.vip/media/photos/498976/00027.gif + return data_original.endswith('.gif') + + +class JmSearchAlbumClient: + """ + 搜尋的最佳姿勢? + 【包含搜尋】 + 搜尋[+]全彩[空格][+]人妻,僅顯示全彩且是人妻的本本 + 範例:+全彩 +人妻 + + 【排除搜尋】 + 搜尋全彩[空格][-]人妻,顯示全彩並排除人妻的本本 + 範例:全彩 -人妻 + + 【我都要搜尋】 + 搜尋全彩[空格]人妻,會顯示所有包含全彩及人妻的本本 + 範例:全彩 人妻 + """ + + def search(self, + search_query: str, + page: int, + main_tag: int, + order_by: str, + time: str, + category: str, + sub_category: Optional[str], + ) -> JmSearchPage: + """ + 搜索【成人A漫】 + 网页端与移动端的搜索有差别: + + - 移动端不支持 category, sub_category参数,网页端支持全部参数 + """ + raise NotImplementedError + + def search_site(self, + search_query: str, + page: int = 1, + order_by: str = JmMagicConstants.ORDER_BY_LATEST, + time: str = JmMagicConstants.TIME_ALL, + category: str = JmMagicConstants.CATEGORY_ALL, + sub_category: Optional[str] = None, + ): + """ + 对应禁漫的站内搜索 + """ + return self.search(search_query, page, 0, order_by, time, category, sub_category) + + def search_work(self, + search_query: str, + page: int = 1, + order_by: str = JmMagicConstants.ORDER_BY_LATEST, + time: str = JmMagicConstants.TIME_ALL, + category: str = JmMagicConstants.CATEGORY_ALL, + sub_category: Optional[str] = None, + ): + """ + 搜索album的作品 work + """ + return self.search(search_query, page, 1, order_by, time, category, sub_category) + + def search_author(self, + search_query: str, + page: int = 1, + order_by: str = JmMagicConstants.ORDER_BY_LATEST, + time: str = JmMagicConstants.TIME_ALL, + category: str = JmMagicConstants.CATEGORY_ALL, + sub_category: Optional[str] = None, + ): + """ + 搜索album的作者 author + """ + return self.search(search_query, page, 2, order_by, time, category, sub_category) + + def search_tag(self, + search_query: str, + page: int = 1, + order_by: str = JmMagicConstants.ORDER_BY_LATEST, + time: str = JmMagicConstants.TIME_ALL, + category: str = JmMagicConstants.CATEGORY_ALL, + sub_category: Optional[str] = None, + ): + """ + 搜索album的标签 tag + """ + return self.search(search_query, page, 3, order_by, time, category, sub_category) + + def search_actor(self, + search_query: str, + page: int = 1, + order_by: str = JmMagicConstants.ORDER_BY_LATEST, + time: str = JmMagicConstants.TIME_ALL, + category: str = JmMagicConstants.CATEGORY_ALL, + sub_category: Optional[str] = None, + ): + """ + 搜索album的登场角色 actor + """ + return self.search(search_query, page, 4, order_by, time, category, sub_category) + + +class JmCategoryClient: + """ + 该接口可以看作是对全体禁漫本子的排行,热门排行的功能也派生于此 + + 月排行 = 分类【时间=月,排序=观看】 + 周排行 = 分类【时间=周,排序=观看】 + 日排行 = 分类【时间=周,排序=观看】 + """ + + def categories_filter(self, + page: int, + time: str, + category: str, + order_by: str, + sub_category: Optional[str] = None, + ) -> JmCategoryPage: + """ + 分类 + + :param page: 页码 + :param time: 时间范围,默认是全部时间 + :param category: 类别,默认是最新,即显示最新的禁漫本子 + :param sub_category: 副分类,仅网页端有这功能 + :param order_by: 排序方式,默认是观看数 + """ + raise NotImplementedError + + def month_ranking(self, + page: int, + category: str = JmMagicConstants.CATEGORY_ALL, + ): + """ + 月排行 = 分类【时间=月,排序=观看】 + """ + return self.categories_filter(page, + JmMagicConstants.TIME_MONTH, + category, + JmMagicConstants.ORDER_BY_VIEW, + ) + + def week_ranking(self, + page: int, + category: str = JmMagicConstants.CATEGORY_ALL, + ): + """ + 周排行 = 分类【时间=周,排序=观看】 + """ + return self.categories_filter(page, + JmMagicConstants.TIME_WEEK, + category, + JmMagicConstants.ORDER_BY_VIEW, + ) + + def day_ranking(self, + page: int, + category: str = JmMagicConstants.CATEGORY_ALL, + ): + """ + 日排行 = 分类【时间=日,排序=观看】 + """ + return self.categories_filter(page, + JmMagicConstants.TIME_TODAY, + category, + JmMagicConstants.ORDER_BY_VIEW, + ) + + +# noinspection PyAbstractClass +class JmcomicClient( + JmImageClient, + JmDetailClient, + JmUserClient, + JmSearchAlbumClient, + JmCategoryClient, + Postman, +): + client_key: None + + def get_domain_list(self) -> List[str]: + """ + 获取当前client的域名配置 + """ + raise NotImplementedError + + def set_domain_list(self, domain_list: List[str]): + """ + 设置当前client的域名配置 + """ + raise NotImplementedError + + def set_cache_dict(self, cache_dict: Optional[Dict]): + raise NotImplementedError + + def get_cache_dict(self) -> Optional[Dict]: + raise NotImplementedError + + def of_api_url(self, api_path, domain): + raise NotImplementedError + + def get_html_domain(self): + return JmModuleConfig.get_html_domain(self.get_root_postman()) + + def get_html_domain_all(self): + return JmModuleConfig.get_html_domain_all(self.get_root_postman()) + + def get_html_domain_all_via_github(self): + return JmModuleConfig.get_html_domain_all_via_github(self.get_root_postman()) + + # noinspection PyMethodMayBeStatic + def do_page_iter(self, params: dict, page: int, get_page_method): + from math import inf + + def update(value: Optional[Dict], page: int, page_content: JmPageContent): + if value is None: + return page + 1, page_content.page_count + + ExceptionTool.require_true(isinstance(value, dict), 'require dict params') + + # 根据外界传递的参数,更新params和page + page = value.get('page', page) + params.update(value) + + return page, inf + + total = inf + while page <= total: + params['page'] = page + page_content = get_page_method(**params) + value = yield page_content + page, total = update(value, page, page_content) + + def favorite_folder_gen(self, + page=1, + order_by=JmMagicConstants.ORDER_BY_LATEST, + folder_id='0', + username='', + ) -> Generator[JmFavoritePage, Dict, None]: + """ + 见 search_gen + """ + params = { + 'order_by': order_by, + 'folder_id': folder_id, + 'username': username, + } + + yield from self.do_page_iter(params, page, self.favorite_folder) + + def search_gen(self, + search_query: str, + main_tag=0, + page: int = 1, + order_by: str = JmMagicConstants.ORDER_BY_LATEST, + time: str = JmMagicConstants.TIME_ALL, + category: str = JmMagicConstants.CATEGORY_ALL, + sub_category: Optional[str] = None, + ) -> Generator[JmSearchPage, Dict, None]: + """ + 搜索结果的生成器,支持下面这种调用方式: + + ``` + for page in self.search_gen('无修正'): + # 每次循环,page为新页的结果 + pass + ``` + + 同时支持外界send参数,可以改变搜索的设定,例如: + + ``` + gen = client.search_gen('MANA') + for i, page in enumerate(gen): + print(page.page_count) + page = gen.send({ + 'search_query': '+MANA +无修正', + 'page': 1 + }) + print(page.page_count) + break + ``` + + """ + params = { + 'search_query': search_query, + 'main_tag': main_tag, + 'order_by': order_by, + 'time': time, + 'category': category, + 'sub_category': sub_category, + } + + yield from self.do_page_iter(params, page, self.search) + + def categories_filter_gen(self, + page: int = 1, + time: str = JmMagicConstants.TIME_ALL, + category: str = JmMagicConstants.CATEGORY_ALL, + order_by: str = JmMagicConstants.ORDER_BY_LATEST, + sub_category: Optional[str] = None, + ) -> Generator[JmCategoryPage, Dict, None]: + """ + 见 search_gen + """ + params = { + 'time': time, + 'category': category, + 'order_by': order_by, + 'sub_category': sub_category, + } + + yield from self.do_page_iter(params, page, self.categories_filter) + + def is_given_type(self, ctype: Type['JmcomicClient']) -> bool: + """ + Client代理的此方法会被路由到内部client的方法 + 即:ClientProxy(AClient()).is_given_type(AClient) is True + 但是: ClientProxy(AClient()).client_key != AClient.client_key + """ + if isinstance(self, ctype): + return True + if self.client_key == ctype.client_key: + return True + return False diff --git a/jm/src/jmcomic/jm_config.py b/jm/src/jmcomic/jm_config.py new file mode 100755 index 0000000..b920a18 --- /dev/null +++ b/jm/src/jmcomic/jm_config.py @@ -0,0 +1,497 @@ +from common import time_stamp, field_cache, ProxyBuilder + + +def shuffled(lines): + from random import shuffle + from common import str_to_list + ls = str_to_list(lines) + shuffle(ls) + return ls + + +def default_jm_logging(topic: str, msg: str): + from common import format_ts, current_thread + print('[{}] [{}]:【{}】{}'.format(format_ts(), current_thread().name, topic, msg)) + + +# 禁漫常量 +class JmMagicConstants: + # 搜索参数-排序 + ORDER_BY_LATEST = 'mr' + ORDER_BY_VIEW = 'mv' + ORDER_BY_PICTURE = 'mp' + ORDER_BY_LIKE = 'tf' + + ORDER_MONTH_RANKING = 'mv_m' + ORDER_WEEK_RANKING = 'mv_w' + ORDER_DAY_RANKING = 'mv_t' + + # 搜索参数-时间段 + TIME_TODAY = 't' + TIME_WEEK = 'w' + TIME_MONTH = 'm' + TIME_ALL = 'a' + + # 分类参数API接口的category + CATEGORY_ALL = '0' # 全部 + CATEGORY_DOUJIN = 'doujin' # 同人 + CATEGORY_SINGLE = 'single' # 单本 + CATEGORY_SHORT = 'short' # 短篇 + CATEGORY_ANOTHER = 'another' # 其他 + CATEGORY_HANMAN = 'hanman' # 韩漫 + CATEGORY_MEIMAN = 'meiman' # 美漫 + CATEGORY_DOUJIN_COSPLAY = 'doujin_cosplay' # cosplay + CATEGORY_3D = '3D' # 3D + CATEGORY_ENGLISH_SITE = 'english_site' # 英文站 + + # 副分类 + SUB_CHINESE = 'chinese' # 汉化,通用副分类 + SUB_JAPANESE = 'japanese' # 日语,通用副分类 + + # 其他类(CATEGORY_ANOTHER)的副分类 + SUB_ANOTHER_OTHER = 'other' # 其他漫画 + SUB_ANOTHER_3D = '3d' # 3D + SUB_ANOTHER_COSPLAY = 'cosplay' # cosplay + + # 同人(SUB_CHINESE)的副分类 + SUB_DOUJIN_CG = 'CG' # CG + SUB_DOUJIN_CHINESE = SUB_CHINESE + SUB_DOUJIN_JAPANESE = SUB_JAPANESE + + # 短篇(CATEGORY_SHORT)的副分类 + SUB_SHORT_CHINESE = SUB_CHINESE + SUB_SHORT_JAPANESE = SUB_JAPANESE + + # 单本(CATEGORY_SINGLE)的副分类 + SUB_SINGLE_CHINESE = SUB_CHINESE + SUB_SINGLE_JAPANESE = SUB_JAPANESE + SUB_SINGLE_YOUTH = 'youth' + + # 图片分割参数 + SCRAMBLE_220980 = 220980 + SCRAMBLE_268850 = 268850 + SCRAMBLE_421926 = 421926 # 2023-02-08后改了图片切割算法 + + # 移动端API密钥 + APP_TOKEN_SECRET = '18comicAPP' + APP_TOKEN_SECRET_2 = '18comicAPPContent' + APP_DATA_SECRET = '185Hcomic3PAPP7R' + APP_VERSION = '1.7.9' + + +# 模块级别共用配置 +class JmModuleConfig: + # 网站相关 + PROT = "https://" + JM_REDIRECT_URL = f'{PROT}jm365.work/3YeBdF' # 永久網域,怕走失的小伙伴收藏起来 + JM_PUB_URL = f'{PROT}jmcomic-fb.vip' + JM_CDN_IMAGE_URL_TEMPLATE = PROT + 'cdn-msp.{domain}/media/photos/{photo_id}/{index:05}{suffix}' # index 从1开始 + JM_IMAGE_SUFFIX = ['.jpg', '.webp', '.png', '.gif'] + + # JM的异常网页内容 + JM_ERROR_RESPONSE_TEXT = { + "Could not connect to mysql! Please check your database settings!": "禁漫服务器内部报错", + "Restricted Access!": "禁漫拒绝你所在ip地区的访问,你可以选择: 换域名/换代理", + } + + # JM的异常网页code + JM_ERROR_STATUS_CODE = { + 403: 'ip地区禁止访问/爬虫被识别', + 500: '500: 禁漫服务器内部异常(可能是服务器过载,可以切换ip或稍后重试)', + 520: '520: Web server is returning an unknown error (禁漫服务器内部报错)', + 524: '524: The origin web server timed out responding to this request. (禁漫服务器处理超时)', + } + + # 分页大小 + PAGE_SIZE_SEARCH = 80 + PAGE_SIZE_FAVORITE = 20 + + # 图片分隔相关 + SCRAMBLE_CACHE = {} + + # 当本子没有作者名字时,顶替作者名字 + DEFAULT_AUTHOR = 'default_author' + + # cookies,目前只在移动端使用,因为移动端请求接口须携带,但不会校验cookies的内容。 + APP_COOKIES = None + + # 移动端图片域名 + DOMAIN_IMAGE_LIST = shuffled(''' + cdn-msp.jmapiproxy1.cc + cdn-msp.jmapiproxy2.cc + cdn-msp2.jmapiproxy2.cc + cdn-msp3.jmapiproxy2.cc + cdn-msp.jmapinodeudzn.net + cdn-msp3.jmapinodeudzn.net + ''') + + # 移动端API域名 + DOMAIN_API_LIST = shuffled(''' + www.cdnmhwscc.vip + www.cdnblackmyth.club + www.cdnmhws.cc + www.cdnuc.vip + ''') + + APP_HEADERS_TEMPLATE = { + 'Accept-Encoding': 'gzip, deflate', + 'user-agent': 'Mozilla/5.0 (Linux; Android 9; V1938CT Build/PQ3A.190705.11211812; wv) AppleWebKit/537.36 (KHTML, ' + 'like Gecko) Version/4.0 Chrome/91.0.4472.114 Safari/537.36', + } + + APP_HEADERS_IMAGE = { + 'Accept': 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8', + 'X-Requested-With': 'com.jiaohua_browser', + 'Referer': PROT + DOMAIN_API_LIST[0], + 'Accept-Language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7', + } + + # 网页端headers + HTML_HEADERS_TEMPLATE = { + 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,' + 'application/signed-exchange;v=b3;q=0.7', + 'accept-language': 'zh-CN,zh;q=0.9', + 'cache-control': 'no-cache', + 'dnt': '1', + 'pragma': 'no-cache', + 'priority': 'u=0, i', + 'referer': 'https://18comic.vip/', + 'sec-ch-ua': '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"Windows"', + 'sec-fetch-dest': 'document', + 'sec-fetch-mode': 'navigate', + 'sec-fetch-site': 'none', + 'sec-fetch-user': '?1', + 'upgrade-insecure-requests': '1', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 ' + 'Safari/537.36', + } + + # 网页端域名配置 + # 无需配置,默认为None,需要的时候会发起请求获得 + # 使用优先级: + # 1. DOMAIN_HTML_LIST + # 2. [DOMAIN_HTML] + DOMAIN_HTML = None + DOMAIN_HTML_LIST = None + + # 模块级别的可重写类配置 + CLASS_DOWNLOADER = None + CLASS_OPTION = None + CLASS_ALBUM = None + CLASS_PHOTO = None + CLASS_IMAGE = None + + # 客户端注册表 + REGISTRY_CLIENT = {} + # 插件注册表 + REGISTRY_PLUGIN = {} + # 异常监听器 + # key: 异常类 + # value: 函数,参数只有异常对象,无需返回值 + # 这个异常类(或者这个异常的子类)的实例将要被raise前,你的listener方法会被调用 + REGISTRY_EXCEPTION_LISTENER = {} + + # 执行log的函数 + EXECUTOR_LOG = default_jm_logging + + # 使用固定时间戳 + FLAG_USE_FIX_TIMESTAMP = True + # 移动端Client初始化cookies + FLAG_API_CLIENT_REQUIRE_COOKIES = True + # log开关标记 + FLAG_ENABLE_JM_LOG = True + # log时解码url + FLAG_DECODE_URL_WHEN_LOGGING = True + # 当内置的版本号落后时,使用最新的禁漫app版本号 + FLAG_USE_VERSION_NEWER_IF_BEHIND = True + + # 关联dir_rule的自定义字段与对应的处理函数 + # 例如: + # Amyname -> JmModuleConfig.AFIELD_ADVICE['myname'] = lambda album: "自定义名称" + AFIELD_ADVICE = dict() + PFIELD_ADVICE = dict() + + # 当发生 oserror: [Errno 36] File name too long 时, + # 把文件名限制在指定个字符以内 + VAR_FILE_NAME_LENGTH_LIMIT = 100 + + @classmethod + def downloader_class(cls): + if cls.CLASS_DOWNLOADER is not None: + return cls.CLASS_DOWNLOADER + + from .jm_downloader import JmDownloader + return JmDownloader + + @classmethod + def option_class(cls): + if cls.CLASS_OPTION is not None: + return cls.CLASS_OPTION + + from .jm_option import JmOption + return JmOption + + @classmethod + def album_class(cls): + if cls.CLASS_ALBUM is not None: + return cls.CLASS_ALBUM + + from .jm_entity import JmAlbumDetail + return JmAlbumDetail + + @classmethod + def photo_class(cls): + if cls.CLASS_PHOTO is not None: + return cls.CLASS_PHOTO + + from .jm_entity import JmPhotoDetail + return JmPhotoDetail + + @classmethod + def image_class(cls): + if cls.CLASS_IMAGE is not None: + return cls.CLASS_IMAGE + + from .jm_entity import JmImageDetail + return JmImageDetail + + @classmethod + def client_impl_class(cls, client_key: str): + clazz_dict = cls.REGISTRY_CLIENT + + clazz = clazz_dict.get(client_key, None) + if clazz is None: + from .jm_toolkit import ExceptionTool + ExceptionTool.raises(f'not found client impl class for key: "{client_key}"') + + return clazz + + @classmethod + @field_cache("DOMAIN_HTML") + def get_html_domain(cls, postman=None): + """ + 由于禁漫的域名经常变化,调用此方法可以获取一个当前可用的最新的域名 domain, + 并且设置把 domain 设置为禁漫模块的默认域名。 + 这样一来,配置文件也不用配置域名了,一切都在运行时动态获取。 + """ + from .jm_toolkit import JmcomicText + return JmcomicText.parse_to_jm_domain(cls.get_html_url(postman)) + + @classmethod + def get_html_url(cls, postman=None): + """ + 访问禁漫的永久网域,从而得到一个可用的禁漫网址 + :returns: https://jm-comic2.cc + """ + postman = postman or cls.new_postman(session=True) + + url = postman.with_redirect_catching().get(cls.JM_REDIRECT_URL) + cls.jm_log('module.html_url', f'获取禁漫网页URL: [{cls.JM_REDIRECT_URL}] → [{url}]') + return url + + @classmethod + @field_cache("DOMAIN_HTML_LIST") + def get_html_domain_all(cls, postman=None): + """ + 访问禁漫发布页,得到所有的禁漫网页域名 + + :returns: ['18comic.vip', ..., 'jm365.xyz/ZNPJam'], 最后一个是【APP軟件下載】 + """ + postman = postman or cls.new_postman(session=True) + + resp = postman.get(cls.JM_PUB_URL) + if resp.status_code != 200: + from .jm_toolkit import ExceptionTool + ExceptionTool.raises_resp(f'请求失败,访问禁漫发布页获取所有域名,HTTP状态码为: {resp.status_code}', resp) + + from .jm_toolkit import JmcomicText + domain_list = JmcomicText.analyse_jm_pub_html(resp.text) + + cls.jm_log('module.html_domain_all', f'获取禁漫网页全部域名: [{resp.url}] → {domain_list}') + return domain_list + + @classmethod + def get_html_domain_all_via_github(cls, + postman=None, + template='https://jmcmomic.github.io/go/{}.html', + index_range=(300, 309) + ): + """ + 通过禁漫官方的github号的repo获取最新的禁漫域名 + https://github.com/jmcmomic/jmcmomic.github.io + """ + postman = postman or cls.new_postman(headers={ + 'authority': 'github.com', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 ' + 'Safari/537.36' + }) + domain_set = set() + + def fetch_domain(url): + resp = postman.get(url, allow_redirects=False) + text = resp.text + from .jm_toolkit import JmcomicText + for domain in JmcomicText.analyse_jm_pub_html(text): + if domain.startswith('jm365'): + continue + domain_set.add(domain) + + from common import multi_thread_launcher + + multi_thread_launcher( + iter_objs=[template.format(i) for i in range(*index_range)], + apply_each_obj_func=fetch_domain, + ) + + return domain_set + + @classmethod + def new_html_headers(cls, domain='18comic.vip'): + """ + 网页端的headers + """ + headers = cls.HTML_HEADERS_TEMPLATE.copy() + headers.update({ + 'authority': domain, + 'origin': f'https://{domain}', + 'referer': f'https://{domain}', + }) + return headers + + @classmethod + @field_cache() + def get_fix_ts_token_tokenparam(cls): + ts = time_stamp() + from .jm_toolkit import JmCryptoTool + token, tokenparam = JmCryptoTool.token_and_tokenparam(ts) + return ts, token, tokenparam + + # noinspection PyUnusedLocal + @classmethod + def jm_log(cls, topic: str, msg: str): + if cls.FLAG_ENABLE_JM_LOG is True: + cls.EXECUTOR_LOG(topic, msg) + + @classmethod + def disable_jm_log(cls): + cls.FLAG_ENABLE_JM_LOG = False + + @classmethod + def new_postman(cls, session=False, **kwargs): + kwargs.setdefault('impersonate', 'chrome110') + kwargs.setdefault('headers', JmModuleConfig.new_html_headers()) + kwargs.setdefault('proxies', JmModuleConfig.DEFAULT_PROXIES) + + from common import Postmans + + if session is True: + return Postmans.new_session(**kwargs) + + return Postmans.new_postman(**kwargs) + + # option 相关的默认配置 + # 一般情况下,建议使用option配置文件来定制配置 + # 而如果只想修改几个简单常用的配置,也可以下方的DEFAULT_XXX属性 + JM_OPTION_VER = '2.1' + DEFAULT_CLIENT_IMPL = 'api' # 默认Client实现类型为网页端 + DEFAULT_CLIENT_CACHE = None # 默认关闭Client缓存。缓存的配置详见 CacheRegistry + DEFAULT_PROXIES = ProxyBuilder.system_proxy() # 默认使用系统代理 + + DEFAULT_OPTION_DICT: dict = { + 'log': None, + 'dir_rule': {'rule': 'Bd_Pname', 'base_dir': None}, + 'download': { + 'cache': True, + 'image': {'decode': True, 'suffix': None}, + 'threading': { + 'image': 30, + 'photo': None, + }, + }, + 'client': { + 'cache': None, # see CacheRegistry + 'domain': [], + 'postman': { + 'type': 'cffi', + 'meta_data': { + 'impersonate': 'chrome110', + 'headers': None, + 'proxies': None, + } + }, + 'impl': None, + 'retry_times': 5, + }, + 'plugins': { + # 如果插件抛出参数校验异常,只log。(全局配置,可以被插件的局部配置覆盖) + # 可选值:ignore(忽略),log(打印日志),raise(抛异常)。 + 'valid': 'log', + }, + } + + @classmethod + def option_default_dict(cls) -> dict: + """ + 返回JmOption.default()的默认配置字典。 + 这样做是为了支持外界自行覆盖option默认配置字典 + """ + from copy import deepcopy + + option_dict = deepcopy(cls.DEFAULT_OPTION_DICT) + + # log + if option_dict['log'] is None: + option_dict['log'] = cls.FLAG_ENABLE_JM_LOG + + # dir_rule.base_dir + dir_rule = option_dict['dir_rule'] + if dir_rule['base_dir'] is None: + import os + dir_rule['base_dir'] = os.getcwd() + + # client cache + client = option_dict['client'] + if client['cache'] is None: + client['cache'] = cls.DEFAULT_CLIENT_CACHE + + # client impl + if client['impl'] is None: + client['impl'] = cls.DEFAULT_CLIENT_IMPL + + # postman proxies + meta_data = client['postman']['meta_data'] + if meta_data['proxies'] is None: + # use system proxy by default + meta_data['proxies'] = cls.DEFAULT_PROXIES + + # threading photo + dt = option_dict['download']['threading'] + if dt['photo'] is None: + import os + dt['photo'] = os.cpu_count() + + return option_dict + + @classmethod + def register_plugin(cls, plugin_class): + from .jm_toolkit import ExceptionTool + ExceptionTool.require_true(getattr(plugin_class, 'plugin_key', None) is not None, + f'未配置plugin_key, class: {plugin_class}') + cls.REGISTRY_PLUGIN[plugin_class.plugin_key] = plugin_class + + @classmethod + def register_client(cls, client_class): + from .jm_toolkit import ExceptionTool + ExceptionTool.require_true(getattr(client_class, 'client_key', None) is not None, + f'未配置client_key, class: {client_class}') + cls.REGISTRY_CLIENT[client_class.client_key] = client_class + + @classmethod + def register_exception_listener(cls, etype, listener): + cls.REGISTRY_EXCEPTION_LISTENER[etype] = listener + + +jm_log = JmModuleConfig.jm_log +disable_jm_log = JmModuleConfig.disable_jm_log diff --git a/jm/src/jmcomic/jm_downloader.py b/jm/src/jmcomic/jm_downloader.py new file mode 100755 index 0000000..1518454 --- /dev/null +++ b/jm/src/jmcomic/jm_downloader.py @@ -0,0 +1,350 @@ +from .jm_option import * + + +def catch_exception(func): + from functools import wraps + + @wraps(func) + def wrapper(self, *args, **kwargs): + self: JmDownloader + try: + return func(self, *args, **kwargs) + except Exception as e: + detail: JmBaseEntity = args[0] + if detail.is_image(): + detail: JmImageDetail + jm_log('image.failed', f'图片下载失败: [{detail.download_url}], 异常: [{e}]') + self.download_failed_image.append((detail, e)) + + elif detail.is_photo(): + detail: JmPhotoDetail + jm_log('photo.failed', f'章节下载失败: [{detail.id}], 异常: [{e}]') + self.download_failed_photo.append((detail, e)) + + raise e + + return wrapper + + +# noinspection PyMethodMayBeStatic +class DownloadCallback: + + def before_album(self, album: JmAlbumDetail): + jm_log('album.before', + f'本子获取成功: [{album.id}], ' + f'作者: [{album.author}], ' + f'章节数: [{len(album)}], ' + f'总页数: [{album.page_count}], ' + f'标题: [{album.name}], ' + f'关键词: {album.tags}' + ) + + def after_album(self, album: JmAlbumDetail): + jm_log('album.after', f'本子下载完成: [{album.id}]') + + def before_photo(self, photo: JmPhotoDetail): + jm_log('photo.before', + f'开始下载章节: {photo.id} ({photo.album_id}[{photo.index}/{len(photo.from_album)}]), ' + f'标题: [{photo.name}], ' + f'图片数为[{len(photo)}]' + ) + + def after_photo(self, photo: JmPhotoDetail): + jm_log('photo.after', + f'章节下载完成: [{photo.id}] ({photo.album_id}[{photo.index}/{len(photo.from_album)}])') + + def before_image(self, image: JmImageDetail, img_save_path): + if image.exists: + jm_log('image.before', + f'图片已存在: {image.tag} ← [{img_save_path}]' + ) + else: + jm_log('image.before', + f'图片准备下载: {image.tag}, [{image.img_url}] → [{img_save_path}]' + ) + + def after_image(self, image: JmImageDetail, img_save_path): + jm_log('image.after', + f'图片下载完成: {image.tag}, [{image.img_url}] → [{img_save_path}]') + + +class JmDownloader(DownloadCallback): + """ + JmDownloader = JmOption + 调度逻辑 + """ + + def __init__(self, option: JmOption) -> None: + self.option = option + self.client = option.build_jm_client() + # 下载成功的记录dict + self.download_success_dict: Dict[JmAlbumDetail, Dict[JmPhotoDetail, List[Tuple[str, JmImageDetail]]]] = {} + # 下载失败的记录list + self.download_failed_image: List[Tuple[JmImageDetail, BaseException]] = [] + self.download_failed_photo: List[Tuple[JmPhotoDetail, BaseException]] = [] + + def download_album(self, album_id): + album = self.client.get_album_detail(album_id) + self.download_by_album_detail(album) + return album + + def download_by_album_detail(self, album: JmAlbumDetail): + self.before_album(album) + if album.skip: + return + self.execute_on_condition( + iter_objs=album, + apply=self.download_by_photo_detail, + count_batch=self.option.decide_photo_batch_count(album) + ) + self.after_album(album) + + def download_photo(self, photo_id): + photo = self.client.get_photo_detail(photo_id) + self.download_by_photo_detail(photo) + return photo + + @catch_exception + def download_by_photo_detail(self, photo: JmPhotoDetail): + self.client.check_photo(photo) + + self.before_photo(photo) + if photo.skip: + return + self.execute_on_condition( + iter_objs=photo, + apply=self.download_by_image_detail, + count_batch=self.option.decide_image_batch_count(photo) + ) + self.after_photo(photo) + + @catch_exception + def download_by_image_detail(self, image: JmImageDetail): + img_save_path = self.option.decide_image_filepath(image) + + image.save_path = img_save_path + image.exists = file_exists(img_save_path) + + self.before_image(image, img_save_path) + + if image.skip: + return + + # let option decide use_cache and decode_image + use_cache = self.option.decide_download_cache(image) + decode_image = self.option.decide_download_image_decode(image) + + # skip download + if use_cache is True and image.exists: + return + + self.client.download_by_image_detail( + image, + img_save_path, + decode_image=decode_image, + ) + + self.after_image(image, img_save_path) + + def execute_on_condition(self, + iter_objs: DetailEntity, + apply: Callable, + count_batch: int, + ): + """ + 调度本子/章节的下载 + """ + iter_objs = self.do_filter(iter_objs) + count_real = len(iter_objs) + + if count_real == 0: + return + + if count_batch >= count_real: + # 一个图/章节 对应 一个线程 + multi_thread_launcher( + iter_objs=iter_objs, + apply_each_obj_func=apply, + ) + else: + # 创建batch个线程的线程池 + thread_pool_executor( + iter_objs=iter_objs, + apply_each_obj_func=apply, + max_workers=count_batch, + ) + + # noinspection PyMethodMayBeStatic + def do_filter(self, detail: DetailEntity): + """ + 该方法可用于过滤本子/章节,默认不会做过滤。 + 例如: + 只想下载 本子的最新一章,返回 [album[-1]] + 只想下载 章节的前10张图片,返回 [photo[:10]] + + :param detail: 可能是本子或者章节,需要自行使用 isinstance / detail.is_xxx 判断 + :returns: 只想要下载的 本子的章节 或 章节的图片 + """ + return detail + + @property + def all_success(self) -> bool: + """ + 是否成功下载了全部图片 + + 该属性需要等到downloader的全部download_xxx方法完成后才有意义。 + + 注意!如果使用了filter机制,例如通过filter只下载3张图片,那么all_success也会为False + """ + if self.has_download_failures: + return False + + for album, photo_dict in self.download_success_dict.items(): + if len(album) != len(photo_dict): + return False + + for photo, image_list in photo_dict.items(): + if len(photo) != len(image_list): + return False + + return True + + @property + def has_download_failures(self): + return len(self.download_failed_image) != 0 or len(self.download_failed_photo) != 0 + + # 下面是回调方法 + + def before_album(self, album: JmAlbumDetail): + super().before_album(album) + self.download_success_dict.setdefault(album, {}) + self.option.call_all_plugin( + 'before_album', + album=album, + downloader=self, + ) + + def after_album(self, album: JmAlbumDetail): + super().after_album(album) + self.option.call_all_plugin( + 'after_album', + album=album, + downloader=self, + ) + + def before_photo(self, photo: JmPhotoDetail): + super().before_photo(photo) + self.download_success_dict.setdefault(photo.from_album, {}) + self.download_success_dict[photo.from_album].setdefault(photo, []) + self.option.call_all_plugin( + 'before_photo', + photo=photo, + downloader=self, + ) + + def after_photo(self, photo: JmPhotoDetail): + super().after_photo(photo) + self.option.call_all_plugin( + 'after_photo', + photo=photo, + downloader=self, + ) + + def before_image(self, image: JmImageDetail, img_save_path): + super().before_image(image, img_save_path) + self.option.call_all_plugin( + 'before_image', + image=image, + downloader=self, + ) + + def after_image(self, image: JmImageDetail, img_save_path): + super().after_image(image, img_save_path) + photo = image.from_photo + album = photo.from_album + + self.download_success_dict.get(album).get(photo).append((img_save_path, image)) + self.option.call_all_plugin( + 'after_image', + image=image, + downloader=self, + ) + + def raise_if_has_exception(self): + if not self.has_download_failures: + return + msg_ls = ['部分下载失败', '', ''] + + if len(self.download_failed_photo) != 0: + msg_ls[1] = f'共{len(self.download_failed_photo)}个章节下载失败: {self.download_failed_photo}' + + if len(self.download_failed_image) != 0: + msg_ls[2] = f'共{len(self.download_failed_image)}个图片下载失败: {self.download_failed_image}' + + ExceptionTool.raises( + '\n'.join(msg_ls), + {'downloader': self}, + PartialDownloadFailedException, + ) + + # 下面是对with语法的支持 + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + if exc_type is not None: + jm_log('dler.exception', + f'{self.__class__.__name__} Exit with exception: {exc_type, exc_val}' + ) + + @classmethod + def use(cls, *args, **kwargs): + """ + 让本类替换JmModuleConfig.CLASS_DOWNLOADER + """ + JmModuleConfig.CLASS_DOWNLOADER = cls + + +class DoNotDownloadImage(JmDownloader): + """ + 不会下载任何图片的Downloader,用作测试 + """ + + def download_by_image_detail(self, image: JmImageDetail): + # ensure make dir + self.option.decide_image_filepath(image) + + +class JustDownloadSpecificCountImage(JmDownloader): + """ + 只下载特定数量图片的Downloader,用作测试 + """ + from threading import Lock + + count_lock = Lock() + count = 0 + + @catch_exception + def download_by_image_detail(self, image: JmImageDetail): + # ensure make dir + self.option.decide_image_filepath(image) + + if self.try_countdown(): + return super().download_by_image_detail(image) + + def try_countdown(self): + if self.count < 0: + return False + + with self.count_lock: + if self.count < 0: + return False + + self.count -= 1 + + return self.count >= 0 + + @classmethod + def use(cls, count): + cls.count = count + super().use() diff --git a/jm/src/jmcomic/jm_entity.py b/jm/src/jmcomic/jm_entity.py new file mode 100755 index 0000000..d262702 --- /dev/null +++ b/jm/src/jmcomic/jm_entity.py @@ -0,0 +1,680 @@ +from functools import lru_cache + +from common import * + +from .jm_config import * + + +class Downloadable: + + def __init__(self): + self.save_path: str = '' + self.exists: bool = False + self.skip = False + + +class JmBaseEntity: + + def to_file(self, filepath): + from common import PackerUtil + PackerUtil.pack(self, filepath) + + @classmethod + def is_image(cls): + return False + + @classmethod + def is_photo(cls): + return False + + @classmethod + def is_album(cls): + return False + + @classmethod + def is_page(cls): + return False + + +class IndexedEntity: + def getindex(self, index: int): + raise NotImplementedError + + def __len__(self): + raise NotImplementedError + + def __getitem__(self, item) -> Any: + if isinstance(item, slice): + start = item.start or 0 + stop = item.stop or len(self) + step = item.step or 1 + return [self.getindex(index) for index in range(start, stop, step)] + + elif isinstance(item, int): + return self.getindex(item) + + else: + raise TypeError(f"Invalid item type for {self.__class__}") + + def __iter__(self): + for index in range(len(self)): + yield self.getindex(index) + + +class DetailEntity(JmBaseEntity, IndexedEntity): + + @property + def id(self) -> str: + raise NotImplementedError + + @property + def title(self) -> str: + return getattr(self, 'name') + + @property + def author(self): + raise NotImplementedError + + @property + def oname(self) -> str: + """ + oname = original name + + 示例: + + title:"喂我吃吧 老師! [欶瀾漢化組] [BLVEFO9] たべさせて、せんせい! (ブルーアーカイブ) [中國翻譯] [無修正]" + + oname:"喂我吃吧 老師!" + + :return: 返回本子的原始名称 + """ + from .jm_toolkit import JmcomicText + oname = JmcomicText.parse_orig_album_name(self.title) + if oname is not None: + return oname + + jm_log('entity', f'无法提取出原album名字: {self.title}') + return self.title + + @property + def authoroname(self): + """ + authoroname = author + oname + + 个人认为识别度比较高的本子名称,一眼看去就能获取到本子的关键信息 + + 具体格式: '【author】oname' + + 示例: + + Pname:喂我吃吧 老師! [欶瀾漢化組] [BLVEFO9] たべさせて、せんせい! (ブルーアーカイブ) [中國翻譯] [無修正] + + Pauthoroname:【BLVEFO9】喂我吃吧 老師! + + :return: 返回作者名+本子原始名称,格式为: '【author】oname' + """ + return f'【{self.author}】{self.oname}' + + @property + def idoname(self): + """ + 类似 authoroname + + :return: '[id] oname' + """ + return f'[{self.id}] {self.oname}' + + def __str__(self): + return f'''{self.__class__.__name__}({self.__alias__()}-{self.id}: "{self.title}")''' + + __repr__ = __str__ + + @classmethod + def __alias__(cls): + # "JmAlbumDetail" -> "album" (本子) + # "JmPhotoDetail" -> "photo" (章节) + cls_name = cls.__name__ + return cls_name[cls_name.index("m") + 1: cls_name.rfind("Detail")].lower() + + @classmethod + def get_dirname(cls, detail: 'DetailEntity', ref: str) -> str: + """ + 该方法被 DirDule 调用,用于生成特定层次的文件夹 + + 通常调用方式如下: + Atitle -> ref = 'title' -> DetailEntity.get_dirname(album, 'title') + 该方法需要返回 ref 对应的文件夹名,默认实现直接返回 getattr(detail, 'title') + + 用户可重写此方法,来实现自定义文件夹名 + + v2.4.5: 此方法支持优先从 JmModuleConfig.XFIELD_ADVICE 中获取自定义函数并调用返回结果 + + :param detail: 本子/章节 实例 + :param ref: 字段名 + :returns: 文件夹名 + """ + + advice_func = (JmModuleConfig.AFIELD_ADVICE + if isinstance(detail, JmAlbumDetail) + else JmModuleConfig.PFIELD_ADVICE + ).get(ref, None) + + if advice_func is not None: + return advice_func(detail) + + return getattr(detail, ref) + + +class JmImageDetail(JmBaseEntity, Downloadable): + + def __init__(self, + aid, + scramble_id, + img_url, + img_file_name, + img_file_suffix, + from_photo=None, + query_params=None, + index=-1, + ): + super().__init__() + if scramble_id is None or (isinstance(scramble_id, str) and scramble_id == ''): + from .jm_toolkit import ExceptionTool + ExceptionTool.raises(f'图片的scramble_id不能为空') + + self.aid: str = str(aid) + self.scramble_id: str = str(scramble_id) + self.img_url: str = img_url + self.img_file_name: str = img_file_name # without suffix + self.img_file_suffix: str = img_file_suffix + + self.from_photo: Optional[JmPhotoDetail] = from_photo + self.query_params: Optional[str] = query_params + self.index = index # 从1开始 + + @property + def filename_without_suffix(self): + return self.img_file_name + + @property + def filename(self): + return self.img_file_name + self.img_file_suffix + + @property + def is_gif(self): + return self.img_file_suffix == '.gif' + + @property + def download_url(self) -> str: + """ + 图片的下载路径 + 与 self.img_url 的唯一不同是,在最后会带上 ?{self.query_params} + :returns: 图片的下载路径 + """ + if self.query_params is None: + return self.img_url + + return f'{self.img_url}?{self.query_params}' + + @classmethod + def of(cls, + photo_id: str, + scramble_id: str, + data_original: str, + from_photo=None, + query_params=None, + index=-1, + ) -> 'JmImageDetail': + """ + 该方法用于创建 JmImageDetail 对象 + """ + + # /xxx.yyy + # ↑ ↑ + # x y + x = data_original.rfind('/') + y = data_original.rfind('.') + + return JmImageDetail( + aid=photo_id, + scramble_id=scramble_id, + img_url=data_original, + img_file_name=data_original[x + 1:y], + img_file_suffix=data_original[y:], + from_photo=from_photo, + query_params=query_params, + index=index, + ) + + @property + def tag(self) -> str: + """ + this tag is used to print pretty info when logging + """ + return f'{self.aid}/{self.img_file_name}{self.img_file_suffix} [{self.index}/{len(self.from_photo)}]' + + @classmethod + def is_image(cls): + return True + + def __str__(self): + return f'''{self.__class__.__name__}(image-[{self.download_url}])''' + + __repr__ = __str__ + + +class JmPhotoDetail(DetailEntity, Downloadable): + + def __init__(self, + photo_id, + name, + series_id, + sort, + tags='', + scramble_id='', + page_arr=None, + data_original_domain=None, + data_original_0=None, + author=None, + from_album=None, + ): + super().__init__() + self.photo_id: str = str(photo_id) + self.scramble_id: str = str(scramble_id) + self.name: str = str(name).strip() + self.sort: int = int(sort) + self._tags: str = tags + self._series_id: int = int(series_id) + + self._author: Optional[str] = author + self.from_album: Optional[JmAlbumDetail] = from_album + self.index = self.album_index + + # 下面的属性和图片url有关 + if isinstance(page_arr, str): + import json + page_arr = json.loads(page_arr) + + # page_arr存放了该photo的所有图片文件名 img_name + self.page_arr: List[str] = page_arr + # 图片的cdn域名 + self.data_original_domain: Optional[str] = data_original_domain + # 第一张图的URL + self.data_original_0 = data_original_0 + + # 2023-07-14 + # 禁漫的图片url加上了一个参数v,如果没有带上这个参数v,图片会返回空数据 + # 参数v的特点: + # 1. 值似乎是该photo的更新时间的时间戳,因此所有图片都使用同一个值 + # 2. 值目前在网页端只在photo页面的图片标签的data-original属性出现 + # 这里的模拟思路是,获取到第一个图片标签的data-original, + # 取出其query参数 → self.data_original_query_params, 该值未来会传递给 JmImageDetail + # self.data_original_query_params = self.get_data_original_query_params(data_original_0) + self.data_original_query_params = None + + @property + def is_single_album(self) -> bool: + return self._series_id == 0 + + @property + def tags(self) -> List[str]: + if self.from_album is not None: + return self.from_album.tags + + tag_str = self._tags + if ',' in tag_str: + # html + return tag_str.split(',') + else: + # api + return tag_str.split() + + @property + def indextitle(self): + return f'第{self.album_index}話 {self.name}' + + @property + def album_id(self) -> str: + return self.photo_id if self.is_single_album else str(self._series_id) + + @property + def album_index(self) -> int: + """ + 返回这个章节在本子中的序号,从1开始 + """ + + # 如果是单章本子,JM给的sort为2。 + # 这里返回1比较符合语义定义 + if self.is_single_album and self.sort == 2: + return 1 + + return self.sort + + @property + def author(self) -> str: + # 优先使用 from_album + if self.from_album is not None: + return self.from_album.author + + if self._author is not None and self._author != '': + return self._author.strip() + + # 使用默认 + return JmModuleConfig.DEFAULT_AUTHOR + + def create_image_detail(self, index) -> JmImageDetail: + # 校验参数 + length = len(self.page_arr) + if index >= length: + raise IndexError(f'image index out of range for photo-{self.photo_id}: {index} >= {length}') + + data_original = self.get_img_data_original(self.page_arr[index]) + + return JmModuleConfig.image_class().of( + self.photo_id, + self.scramble_id, + data_original, + from_photo=self, + query_params=self.data_original_query_params, + index=index + 1, + ) + + def get_img_data_original(self, img_name: str) -> str: + """ + 根据图片名,生成图片的完整请求路径 URL + 例如:img_name = 01111.webp + 返回:https://cdn-msp2.18comic.org/media/photos/147643/01111.webp + """ + domain = self.data_original_domain + + from .jm_toolkit import ExceptionTool + ExceptionTool.require_true(domain is not None, f'图片域名为空: {domain}') + + return f'{JmModuleConfig.PROT}{domain}/media/photos/{self.photo_id}/{img_name}' + + # noinspection PyMethodMayBeStatic + def get_data_original_query_params(self, data_original_0: Optional[str]) -> str: + if data_original_0 is None: + return f'v={time_stamp()}' + + index = data_original_0.rfind('?') + if index == -1: + return f'v={time_stamp()}' + + return data_original_0[index + 1:] + + @property + def id(self): + return self.photo_id + + @lru_cache(None) + def getindex(self, index) -> JmImageDetail: + return self.create_image_detail(index) + + def __getitem__(self, item) -> Union[JmImageDetail, List[JmImageDetail]]: + return super().__getitem__(item) + + def __len__(self): + return len(self.page_arr) + + def __iter__(self) -> Generator[JmImageDetail, None, None]: + return super().__iter__() + + @classmethod + def is_photo(cls): + return True + + +class JmAlbumDetail(DetailEntity, Downloadable): + + def __init__(self, + album_id, + scramble_id, + name, + episode_list, + page_count, + pub_date, + update_date, + likes, + views, + comment_count, + works, + actors, + authors, + tags, + related_list=None, + ): + super().__init__() + self.album_id: str = str(album_id) + self.scramble_id: str = str(scramble_id) + self.name: str = str(name).strip() + self.page_count: int = int(page_count) # 总页数 + self.pub_date: str = pub_date # 发布日期 + self.update_date: str = update_date # 更新日期 + + self.likes: str = likes # [1K] 點擊喜歡 + self.views: str = views # [40K] 次觀看 + self.comment_count: int = int(comment_count) # 评论数 + self.works: List[str] = works # 作品 + self.actors: List[str] = actors # 登場人物 + self.tags: List[str] = tags # 標籤 + self.authors: List[str] = authors # 作者 + + # 有的 album 没有章节,则自成一章。 + episode_list: List[Tuple[str, str, str]] + if len(episode_list) == 0: + # photo_id, photo_index, photo_title, photo_pub_date + episode_list = [(album_id, "1", name)] + else: + episode_list = self.distinct_episode(episode_list) + + self.episode_list = episode_list + self.related_list = related_list + + @property + def author(self): + """ + 作者 + 禁漫本子的作者标签可能有多个,全部作者请使用字段 self.author_list + """ + if len(self.authors) >= 1: + return self.authors[0] + + return JmModuleConfig.DEFAULT_AUTHOR + + @property + def id(self): + return self.album_id + + @staticmethod + def distinct_episode(episode_list: list): + """ + 去重章节 + photo_id, photo_index, photo_title, photo_pub_date + """ + episode_list.sort(key=lambda e: int(e[1])) # 按照photo_index排序 + ret = [episode_list[0]] + + for i in range(1, len(episode_list)): + if ret[-1][1] != episode_list[i][1]: + ret.append(episode_list[i]) + + return ret + + def create_photo_detail(self, index) -> JmPhotoDetail: + # 校验参数 + length = len(self.episode_list) + + if index >= length: + raise IndexError(f'photo index out of range for album-{self.album_id}: {index} >= {length}') + + # ('212214', '81', '94 突然打來', '2020-08-29') + pid, pindex, pname = self.episode_list[index] + + photo = JmModuleConfig.photo_class()( + photo_id=pid, + scramble_id=self.scramble_id, + name=pname, + series_id=self.album_id, + sort=pindex, + from_album=self, + ) + + return photo + + @lru_cache(None) + def getindex(self, item) -> JmPhotoDetail: + return self.create_photo_detail(item) + + def __getitem__(self, item) -> Union[JmPhotoDetail, List[JmPhotoDetail]]: + return super().__getitem__(item) + + def __len__(self): + return len(self.episode_list) + + def __iter__(self) -> Generator[JmPhotoDetail, None, None]: + return super().__iter__() + + @classmethod + def is_album(cls): + return True + + +class JmPageContent(JmBaseEntity, IndexedEntity): + ContentItem = Tuple[str, Dict[str, Any]] + + def __init__(self, content: List[ContentItem], total: int): + + """ + content: + [ + album_id, {title, tags, ...} + ] + :param content: 分页数据 + :param total: 总结果数 + """ + self.content = content + self.total = total + + @property + def page_count(self) -> int: + """ + 页数 + """ + page_size = self.page_size + import math + return math.ceil(int(self.total) / page_size) + + @property + def page_size(self) -> int: + """ + 页大小 + """ + raise NotImplementedError + + def iter_id(self) -> Generator[str, None, None]: + """ + 返回 album_id 的迭代器 + """ + for aid, ainfo in self.content: + yield aid + + def iter_id_title(self) -> Generator[Tuple[str, str], None, None]: + """ + 返回 album_id, album_title 的迭代器 + """ + for aid, ainfo in self.content: + yield aid, ainfo['name'] + + def iter_id_title_tag(self) -> Generator[Tuple[str, str, List[str]], None, None]: + """ + 返回 album_id, album_title, album_tags 的迭代器 + """ + for aid, ainfo in self.content: + ainfo.setdefault('tags', []) + yield aid, ainfo['name'], ainfo['tags'] + + # 下面的方法实现方便的元素访问 + + def __len__(self): + return len(self.content) + + def __iter__(self): + return self.iter_id_title() + + def __getitem__(self, item) -> Union[ContentItem, List[ContentItem]]: + return super().__getitem__(item) + + def getindex(self, index: int): + return self.content[index] + + @classmethod + def is_page(cls): + return True + + +class JmSearchPage(JmPageContent): + + @property + def page_size(self) -> int: + return JmModuleConfig.PAGE_SIZE_SEARCH + + # 下面的方法是对单个album的包装 + + @property + def is_single_album(self): + return hasattr(self, 'album') + + @property + def single_album(self) -> JmAlbumDetail: + return getattr(self, 'album') + + @classmethod + def wrap_single_album(cls, album: JmAlbumDetail) -> 'JmSearchPage': + page = JmSearchPage([( + album.album_id, { + 'name': album.name, + 'tags': album.tags, + 'scramble_id': album.scramble_id, + 'page_count': album.page_count, + 'pub_date': album.pub_date, + 'update_date': album.update_date, + 'likes': album.likes, + 'views': album.views, + 'comment_count': album.comment_count, + 'works': album.works, + 'actors': album.actors, + 'authors': album.authors, + 'related_list': album.related_list, + } + )], 1) + setattr(page, 'album', album) + return page + + +JmCategoryPage = JmSearchPage + + +class JmFavoritePage(JmPageContent): + + def __init__(self, content, folder_list, total): + """ + + :param content: 收藏夹一页数据 + :param folder_list: 所有的收藏夹的信息 + :param total: 收藏夹的收藏总数 + """ + super().__init__(content, total) + self.folder_list = folder_list + + @property + def page_size(self) -> int: + return JmModuleConfig.PAGE_SIZE_FAVORITE + + def iter_folder_id_name(self) -> Generator[Tuple[str, str], None, None]: + """ + 用户文件夹的迭代器 + """ + for folder_info in self.folder_list: + fid, fname = folder_info['FID'], folder_info['name'] + yield fid, fname diff --git a/jm/src/jmcomic/jm_exception.py b/jm/src/jmcomic/jm_exception.py new file mode 100755 index 0000000..8305277 --- /dev/null +++ b/jm/src/jmcomic/jm_exception.py @@ -0,0 +1,191 @@ +# 该文件存放jmcomic的异常机制设计和实现 +from .jm_entity import * + + +class JmcomicException(Exception): + description = 'jmcomic 模块异常' + + def __init__(self, msg: str, context: dict): + self.msg = msg + self.context = context + + def from_context(self, key): + return self.context[key] + + def __str__(self): + return self.msg + + +class ResponseUnexpectedException(JmcomicException): + description = '响应不符合预期异常' + + @property + def resp(self): + return self.from_context(ExceptionTool.CONTEXT_KEY_RESP) + + +class RegularNotMatchException(JmcomicException): + description = '正则表达式不匹配异常' + + @property + def resp(self): + """ + 可能为None + """ + return self.context.get(ExceptionTool.CONTEXT_KEY_RESP, None) + + @property + def error_text(self): + return self.from_context(ExceptionTool.CONTEXT_KEY_HTML) + + @property + def pattern(self): + return self.from_context(ExceptionTool.CONTEXT_KEY_RE_PATTERN) + + +class JsonResolveFailException(ResponseUnexpectedException): + description = 'Json解析异常' + + +class MissingAlbumPhotoException(ResponseUnexpectedException): + description = '不存在本子或章节异常' + + @property + def error_jmid(self) -> str: + return self.from_context(ExceptionTool.CONTEXT_KEY_MISSING_JM_ID) + + +class RequestRetryAllFailException(JmcomicException): + description = '请求重试全部失败异常' + + +class PartialDownloadFailedException(JmcomicException): + description = '部分章节或图片下载失败异常' + + @property + def downloader(self): + return self.from_context(ExceptionTool.CONTEXT_KEY_DOWNLOADER) + +class ExceptionTool: + """ + 抛异常的工具 + 1: 能简化 if-raise 语句的编写 + 2: 有更好的上下文信息传递方式 + """ + + CONTEXT_KEY_RESP = 'resp' + CONTEXT_KEY_HTML = 'html' + CONTEXT_KEY_RE_PATTERN = 'pattern' + CONTEXT_KEY_MISSING_JM_ID = 'missing_jm_id' + CONTEXT_KEY_DOWNLOADER = 'downloader' + + @classmethod + def raises(cls, + msg: str, + context: dict = None, + etype: Optional[Type[Exception]] = None, + ): + """ + 抛出异常 + + :param msg: 异常消息 + :param context: 异常上下文数据 + :param etype: 异常类型,默认使用 JmcomicException + """ + if context is None: + context = {} + + if etype is None: + etype = JmcomicException + + # 异常对象 + e = etype(msg, context) + + # 异常处理建议 + cls.notify_all_listeners(e) + + raise e + + @classmethod + def raises_regex(cls, + msg: str, + html: str, + pattern: Pattern, + ): + cls.raises( + msg, + { + cls.CONTEXT_KEY_HTML: html, + cls.CONTEXT_KEY_RE_PATTERN: pattern, + }, + RegularNotMatchException, + ) + + @classmethod + def raises_resp(cls, + msg: str, + resp, + etype=ResponseUnexpectedException + ): + cls.raises( + msg, { + cls.CONTEXT_KEY_RESP: resp + }, + etype, + ) + + @classmethod + def raise_missing(cls, + resp, + jmid: str, + ): + """ + 抛出本子/章节的异常 + :param resp: 响应对象 + :param jmid: 禁漫本子/章节id + """ + from .jm_toolkit import JmcomicText + url = JmcomicText.format_album_url(jmid) + + req_type = "本子" if "album" in url else "章节" + cls.raises( + ( + f'请求的{req_type}不存在!({url})\n' + '原因可能为:\n' + f'1. id有误,检查你的{req_type}id\n' + '2. 该漫画只对登录用户可见,请配置你的cookies,或者使用移动端Client(api)\n' + ), + { + cls.CONTEXT_KEY_RESP: resp, + cls.CONTEXT_KEY_MISSING_JM_ID: jmid, + }, + MissingAlbumPhotoException, + ) + + @classmethod + def require_true(cls, case: bool, msg: str): + if case: + return + + cls.raises(msg) + + @classmethod + def replace_old_exception_executor(cls, raises: Callable[[Callable, str, dict], None]): + old = cls.raises + + def new(msg, context=None, _etype=None): + if context is None: + context = {} + raises(old, msg, context) + + cls.raises = new + + @classmethod + def notify_all_listeners(cls, e): + registry: Dict[Type, Callable[Type]] = JmModuleConfig.REGISTRY_EXCEPTION_LISTENER + if not registry: + return None + + for accept_type, listener in registry.items(): + if isinstance(e, accept_type): + listener(e) diff --git a/jm/src/jmcomic/jm_option.py b/jm/src/jmcomic/jm_option.py new file mode 100755 index 0000000..79168c3 --- /dev/null +++ b/jm/src/jmcomic/jm_option.py @@ -0,0 +1,670 @@ +from .jm_client_impl import * + + +class CacheRegistry: + REGISTRY = {} + + @classmethod + def level_option(cls, option, _client): + registry = cls.REGISTRY + registry.setdefault(option, {}) + return registry[option] + + @classmethod + def level_client(cls, _option, client): + registry = cls.REGISTRY + registry.setdefault(client, {}) + return registry[client] + + @classmethod + def enable_client_cache_on_condition(cls, + option: 'JmOption', + client: JmcomicClient, + cache: Union[None, bool, str, Callable], + ): + """ + cache parameter + + if None: no cache + + if bool: + true: level_option + + false: no cache + + if str: + (invoke corresponding Cache class method) + + :param option: JmOption + :param client: JmcomicClient + :param cache: config dsl + """ + if cache is None: + return + + elif isinstance(cache, bool): + if cache is False: + return + else: + cache = cls.level_option + + elif isinstance(cache, str): + func = getattr(cls, cache, None) + ExceptionTool.require_true(func is not None, f'未实现的cache配置名: {cache}') + cache = func + + cache: Callable + client.set_cache_dict(cache(option, client)) + + +class DirRule: + rule_sample = [ + # 根目录 / Album-id / Photo-序号 / + 'Bd_Aid_Pindex', # 禁漫网站的默认下载方式 + # 根目录 / Album-作者 / Album-标题 / Photo-序号 / + 'Bd_Aauthor_Atitle_Pindex', + # 根目录 / Photo-序号&标题 / + 'Bd_Pindextitle', + # 根目录 / Photo-自定义类属性 / + 'Bd_Aauthor_Atitle_Pcustomfield', + # 需要替换JmModuleConfig.CLASS_ALBUM / CLASS_PHOTO才能让自定义属性生效 + ] + + Detail = Union[JmAlbumDetail, JmPhotoDetail, None] + RuleFunc = Callable[[Detail], str] + RuleSolver = Tuple[str, RuleFunc, str] + RuleSolverList = List[RuleSolver] + + def __init__(self, rule: str, base_dir=None): + base_dir = JmcomicText.parse_to_abspath(base_dir) + self.base_dir = base_dir + self.rule_dsl = rule + self.solver_list = self.get_role_solver_list(rule, base_dir) + + def decide_image_save_dir(self, + album: JmAlbumDetail, + photo: JmPhotoDetail, + ) -> str: + path_ls = [] + for solver in self.solver_list: + try: + ret = self.apply_rule_solver(album, photo, solver) + except BaseException as e: + # noinspection PyUnboundLocalVariable + jm_log('dir_rule', f'路径规则"{solver[2]}"的解析出错: {e}, album={album}, photo={photo}') + raise e + + path_ls.append(str(ret)) + + return fix_filepath('/'.join(path_ls), is_dir=True) + + def decide_album_root_dir(self, album: JmAlbumDetail) -> str: + path_ls = [] + for solver in self.solver_list: + key, _, rule = solver + + if key != 'Bd' and key != 'A': + continue + + try: + ret = self.apply_rule_solver(album, None, solver) + except BaseException as e: + # noinspection PyUnboundLocalVariable + jm_log('dir_rule', f'路径规则"{rule}"的解析出错: {e}, album={album}') + raise e + + path_ls.append(str(ret)) + + return fix_filepath('/'.join(path_ls), is_dir=True) + + def get_role_solver_list(self, rule_dsl: str, base_dir: str) -> RuleSolverList: + """ + 解析下载路径dsl,得到一个路径规则解析列表 + """ + + rule_list = self.split_rule_dsl(rule_dsl) + solver_ls: List[DirRule.RuleSolver] = [] + + for rule in rule_list: + rule = rule.strip() + if rule == 'Bd': + solver_ls.append(('Bd', lambda _: base_dir, 'Bd')) + continue + + rule_solver = self.get_rule_solver(rule) + if rule_solver is None: + ExceptionTool.raises(f'不支持的dsl: "{rule}" in "{rule_dsl}"') + + solver_ls.append(rule_solver) + + return solver_ls + + # noinspection PyMethodMayBeStatic + def split_rule_dsl(self, rule_dsl: str) -> List[str]: + if rule_dsl == 'Bd': + return [rule_dsl] + + if '/' in rule_dsl: + return rule_dsl.split('/') + + if '_' in rule_dsl: + return rule_dsl.split('_') + + ExceptionTool.raises(f'不支持的rule配置: "{rule_dsl}"') + + @classmethod + def get_rule_solver(cls, rule: str) -> Optional[RuleSolver]: + # 检查dsl + if not rule.startswith(('A', 'P')): + return None + + def solve_func(detail): + return fix_windir_name(str(DetailEntity.get_dirname(detail, rule[1:]))).strip() + + return rule[0], solve_func, rule + + @classmethod + def apply_rule_solver(cls, album, photo, rule_solver: RuleSolver) -> str: + """ + 应用规则解析器(RuleSolver) + + :param album: JmAlbumDetail + :param photo: JmPhotoDetail + :param rule_solver: Ptitle + :returns: photo.title + """ + + def choose_detail(key): + if key == 'Bd': + return None + if key == 'A': + return album + if key == 'P': + return photo + + key, func, _ = rule_solver + detail = choose_detail(key) + return func(detail) + + @classmethod + def apply_rule_directly(cls, album, photo, rule: str) -> str: + return cls.apply_rule_solver(album, photo, cls.get_rule_solver(rule)) + + +class JmOption: + + def __init__(self, + dir_rule: Dict, + download: Dict, + client: Dict, + plugins: Dict, + filepath=None, + call_after_init_plugin=True, + ): + # 路径规则配置 + self.dir_rule = DirRule(**dir_rule) + # 客户端配置 + self.client = AdvancedDict(client) + # 下载配置 + self.download = AdvancedDict(download) + # 插件配置 + self.plugins = AdvancedDict(plugins) + # 其他配置 + self.filepath = filepath + + # 需要主线程等待完成的插件 + self.need_wait_plugins = [] + + if call_after_init_plugin: + self.call_all_plugin('after_init', safe=True) + + def copy_option(self): + return self.__class__( + dir_rule={ + 'rule': self.dir_rule.rule_dsl, + 'base_dir': self.dir_rule.base_dir, + }, + download=self.download.src_dict, + client=self.client.src_dict, + plugins=self.plugins.src_dict, + filepath=self.filepath, + call_after_init_plugin=False + ) + + """ + 下面是decide系列方法,为了支持重写和增加程序动态性。 + """ + + # noinspection PyUnusedLocal + def decide_image_batch_count(self, photo: JmPhotoDetail): + return self.download.threading.image + + # noinspection PyMethodMayBeStatic,PyUnusedLocal + def decide_photo_batch_count(self, album: JmAlbumDetail): + return self.download.threading.photo + + # noinspection PyMethodMayBeStatic + def decide_image_filename(self, image: JmImageDetail) -> str: + """ + 返回图片的文件名,不包含后缀 + 默认返回禁漫的图片文件名,例如:00001 (.jpg) + """ + return image.filename_without_suffix + + def decide_image_suffix(self, image: JmImageDetail) -> str: + """ + 返回图片的后缀,如果返回的后缀和原后缀不一致,则会进行图片格式转换 + """ + # 动图则使用原后缀 + if image.is_gif: + return image.img_file_suffix + + # 非动图,以配置为先 + return self.download.image.suffix or image.img_file_suffix + + def decide_image_save_dir(self, photo, ensure_exists=True) -> str: + # 使用 self.dir_rule 决定 save_dir + save_dir = self.dir_rule.decide_image_save_dir( + photo.from_album, + photo + ) + + if ensure_exists: + save_dir = JmcomicText.try_mkdir(save_dir) + + return save_dir + + def decide_image_filepath(self, image: JmImageDetail, consider_custom_suffix=True) -> str: + # 以此决定保存文件夹、后缀、不包含后缀的文件名 + save_dir = self.decide_image_save_dir(image.from_photo) + suffix = self.decide_image_suffix(image) if consider_custom_suffix else image.img_file_suffix + return os.path.join(save_dir, fix_windir_name(self.decide_image_filename(image)) + suffix) + + def decide_download_cache(self, _image: JmImageDetail) -> bool: + return self.download.cache + + def decide_download_image_decode(self, image: JmImageDetail) -> bool: + # .gif file needn't be decoded + if image.is_gif: + return False + + return self.download.image.decode + + """ + 下面是创建对象相关方法 + """ + + @classmethod + def default_dict(cls) -> Dict: + return JmModuleConfig.option_default_dict() + + @classmethod + def default(cls) -> 'JmOption': + """ + 使用默认的 JmOption + """ + return cls.construct({}) + + @classmethod + def construct(cls, origdic: Dict, cover_default=True) -> 'JmOption': + dic = cls.merge_default_dict(origdic) if cover_default else origdic + + # log + log = dic.pop('log', True) + if log is False: + disable_jm_log() + + # version + version = dic.pop('version', None) + # noinspection PyTypeChecker + if version is not None and float(version) >= float(JmModuleConfig.JM_OPTION_VER): + # 版本号更高,跳过兼容代码 + return cls(**dic) + + # 旧版本option,做兼容 + cls.compatible_with_old_versions(dic) + + return cls(**dic) + + @classmethod + def compatible_with_old_versions(cls, dic): + """ + 兼容旧的option版本 + """ + # 1: 并发配置项 + dt: dict = dic['download']['threading'] + if 'batch_count' in dt: + batch_count = dt.pop('batch_count') + dt['image'] = batch_count + + # 2: 插件配置项 plugin -> plugins + if 'plugin' in dic: + dic['plugins'] = dic.pop('plugin') + + def deconstruct(self) -> Dict: + return { + 'version': JmModuleConfig.JM_OPTION_VER, + 'log': JmModuleConfig.FLAG_ENABLE_JM_LOG, + 'dir_rule': { + 'rule': self.dir_rule.rule_dsl, + 'base_dir': self.dir_rule.base_dir, + }, + 'download': self.download.src_dict, + 'client': self.client.src_dict, + 'plugins': self.plugins.src_dict + } + + """ + 下面是文件IO方法 + """ + + @classmethod + def from_file(cls, filepath: str) -> 'JmOption': + dic: dict = PackerUtil.unpack(filepath)[0] + dic.setdefault('filepath', filepath) + return cls.construct(dic) + + def to_file(self, filepath=None): + if filepath is None: + filepath = self.filepath + + ExceptionTool.require_true(filepath is not None, "未指定JmOption的保存路径") + + PackerUtil.pack(self.deconstruct(), filepath) + + """ + 下面是创建客户端的相关方法 + """ + + @field_cache() + def build_jm_client(self, **kwargs): + """ + 该方法会首次调用会创建JmcomicClient对象, + 然后保存在self中, + 多次调用`不会`创建新的JmcomicClient对象 + """ + return self.new_jm_client(**kwargs) + + def new_jm_client(self, domain_list=None, impl=None, cache=None, **kwargs) -> Union[JmHtmlClient, JmApiClient]: + """ + 创建新的Client(客户端),不同Client之间的元数据不共享 + """ + from copy import deepcopy + + # 所有需要用到的 self.client 配置项如下 + postman_conf: dict = deepcopy(self.client.postman.src_dict) # postman dsl 配置 + + meta_data: dict = postman_conf['meta_data'] # 元数据 + + retry_times: int = self.client.retry_times # 重试次数 + + cache: str = cache if cache is not None else self.client.cache # 启用缓存 + + impl: str = impl or self.client.impl # client_key + + if isinstance(impl, type): + # eg: impl = JmHtmlClient + # noinspection PyUnresolvedReferences + impl = impl.client_key + + # start construct client + + # domain + def decide_domain_list(): + nonlocal domain_list + + if domain_list is None: + domain_list = self.client.domain + + if not isinstance(domain_list, (list, str)): + # dict + domain_list = domain_list.get(impl, []) + + if isinstance(domain_list, str): + # multi-lines text + domain_list = str_to_list(domain_list) + + # list or str + if len(domain_list) == 0: + domain_list = self.decide_client_domain(impl) + + return domain_list + + # support kwargs overwrite meta_data + if len(kwargs) != 0: + meta_data.update(kwargs) + + # postman + postman = Postmans.create(data=postman_conf) + + # client + clazz = JmModuleConfig.client_impl_class(impl) + if clazz == AbstractJmClient or not issubclass(clazz, AbstractJmClient): + raise NotImplementedError(clazz) + + client: AbstractJmClient = clazz( + postman=postman, + domain_list=decide_domain_list(), + retry_times=retry_times, + ) + + # enable cache + CacheRegistry.enable_client_cache_on_condition(self, client, cache) + + # noinspection PyTypeChecker + return client + + def update_cookies(self, cookies: dict): + metadata: dict = self.client.postman.meta_data.src_dict + orig_cookies: Optional[Dict] = metadata.get('cookies', None) + if orig_cookies is None: + metadata['cookies'] = cookies + else: + orig_cookies.update(cookies) + metadata['cookies'] = orig_cookies + + # noinspection PyMethodMayBeStatic + def decide_client_domain(self, client_key: str) -> List[str]: + def is_client_type(ctype) -> bool: + return self.client_key_is_given_type(client_key, ctype) + + if is_client_type(JmApiClient): + # 移动端 + return JmModuleConfig.DOMAIN_API_LIST + + if is_client_type(JmHtmlClient): + # 网页端 + domain_list = JmModuleConfig.DOMAIN_HTML_LIST + if domain_list is not None: + return domain_list + return [JmModuleConfig.get_html_domain()] + + ExceptionTool.raises(f'没有配置域名,且是无法识别的client类型: {client_key}') + + @classmethod + def client_key_is_given_type(cls, client_key, ctype: Type[JmcomicClient]): + if client_key == ctype.client_key: + return True + + clazz = JmModuleConfig.client_impl_class(client_key) + if issubclass(clazz, ctype): + return True + + return False + + @classmethod + def merge_default_dict(cls, user_dict, default_dict=None): + """ + 深度合并两个字典 + """ + if default_dict is None: + default_dict = cls.default_dict() + + for key, value in user_dict.items(): + if isinstance(value, dict) and isinstance(default_dict.get(key), dict): + default_dict[key] = cls.merge_default_dict(value, default_dict[key]) + else: + default_dict[key] = value + return default_dict + + # 下面的方法提供面向对象的调用风格 + + def download_album(self, + album_id, + downloader=None, + callback=None, + ): + from .api import download_album + download_album(album_id, self, downloader, callback) + + def download_photo(self, + photo_id, + downloader=None, + callback=None + ): + from .api import download_photo + download_photo(photo_id, self, downloader, callback) + + # 下面的方法为调用插件提供支持 + + def call_all_plugin(self, group: str, safe=True, **extra): + plugin_list: List[dict] = self.plugins.get(group, []) + if plugin_list is None or len(plugin_list) == 0: + return + + # 保证 jm_plugin.py 被加载 + from .jm_plugin import JmOptionPlugin + + plugin_registry = JmModuleConfig.REGISTRY_PLUGIN + for pinfo in plugin_list: + key, kwargs = pinfo['plugin'], pinfo.get('kwargs', None) # kwargs为None + pclass: Optional[Type[JmOptionPlugin]] = plugin_registry.get(key, None) + + ExceptionTool.require_true(pclass is not None, f'[{group}] 未注册的plugin: {key}') + + try: + self.invoke_plugin(pclass, kwargs, extra, pinfo) + except BaseException as e: + if safe is True: + traceback_print_exec() + else: + raise e + + def invoke_plugin(self, pclass, kwargs: Optional[Dict], extra: dict, pinfo: dict): + # 检查插件的参数类型 + kwargs = self.fix_kwargs(kwargs) + # 把插件的配置数据kwargs和附加数据extra合并,extra会覆盖kwargs + if len(extra) != 0: + kwargs.update(extra) + + # 保证 jm_plugin.py 被加载 + from .jm_plugin import JmOptionPlugin, PluginValidationException + + pclass: Type[JmOptionPlugin] + plugin: Optional[JmOptionPlugin] = None + + try: + # 构建插件对象 + plugin: JmOptionPlugin = pclass.build(self) + + # 设置日志开关 + if pinfo.get('log', True) is not True: + plugin.log_enable = False + + jm_log('plugin.invoke', f'调用插件: [{pclass.plugin_key}]') + + # 调用插件功能 + plugin.invoke(**kwargs) + + except PluginValidationException as e: + # 插件抛出的参数校验异常 + self.handle_plugin_valid_exception(e, pinfo, kwargs, plugin, pclass) + + except JmcomicException as e: + # 模块内部异常,通过不是插件抛出的,而是插件调用了例如Client,Client请求失败抛出的 + self.handle_plugin_jmcomic_exception(e, pinfo, kwargs, plugin, pclass) + + except BaseException as e: + # 为插件兜底,捕获其他所有异常 + self.handle_plugin_unexpected_error(e, pinfo, kwargs, plugin, pclass) + + # noinspection PyMethodMayBeStatic,PyUnusedLocal + def handle_plugin_valid_exception(self, e, pinfo: dict, kwargs: dict, _plugin, _pclass): + from .jm_plugin import PluginValidationException + e: PluginValidationException + + mode = pinfo.get('valid', self.plugins.valid) + + if mode == 'ignore': + # ignore + return + + if mode == 'log': + # log + jm_log('plugin.validation', + f'插件 [{e.plugin.plugin_key}] 参数校验异常:{e.msg}' + ) + return + + if mode == 'raise': + # raise + raise e + + # 其他的mode可以通过继承+方法重写来扩展 + + # noinspection PyMethodMayBeStatic,PyUnusedLocal + def handle_plugin_unexpected_error(self, e, pinfo: dict, kwargs: dict, _plugin, pclass): + msg = str(e) + jm_log('plugin.error', f'插件 [{pclass.plugin_key}],运行遇到未捕获异常,异常信息: [{msg}]') + raise e + + # noinspection PyMethodMayBeStatic,PyUnusedLocal + def handle_plugin_jmcomic_exception(self, e, pinfo: dict, kwargs: dict, _plugin, pclass): + msg = str(e) + jm_log('plugin.exception', f'插件 [{pclass.plugin_key}] 调用失败,异常信息: [{msg}]') + raise e + + # noinspection PyMethodMayBeStatic + def fix_kwargs(self, kwargs: Optional[Dict]) -> Dict[str, Any]: + """ + kwargs将来要传给方法参数,这要求kwargs的key是str类型, + 该方法检查kwargs的key的类型,如果不是str,尝试转为str,不行则抛异常。 + """ + if kwargs is None: + kwargs = {} + else: + ExceptionTool.require_true( + isinstance(kwargs, dict), + f'插件的kwargs参数必须为dict类型,而不能是类型: {type(kwargs)}' + ) + + kwargs: dict + new_kwargs: Dict[str, Any] = {} + + for k, v in kwargs.items(): + if isinstance(v, str): + newv = JmcomicText.parse_dsl_text(v) + v = newv + + if isinstance(k, str): + new_kwargs[k] = v + continue + + if isinstance(k, (int, float)): + newk = str(k) + jm_log('plugin.kwargs', f'插件参数类型转换: {k} ({type(k)}) -> {newk} ({type(newk)})') + new_kwargs[newk] = v + continue + + ExceptionTool.raises( + f'插件kwargs参数类型有误,' + f'字段: {k},预期类型为str,实际类型为{type(k)}' + ) + + return new_kwargs + + def wait_all_plugins_finish(self): + from .jm_plugin import JmOptionPlugin + for plugin in self.need_wait_plugins: + plugin: JmOptionPlugin + plugin.wait_until_finish() diff --git a/jm/src/jmcomic/jm_plugin.py b/jm/src/jmcomic/jm_plugin.py new file mode 100755 index 0000000..44ef6ff --- /dev/null +++ b/jm/src/jmcomic/jm_plugin.py @@ -0,0 +1,1222 @@ +""" +该文件存放的是option插件 +""" +import os.path + +from .jm_option import * + + +class PluginValidationException(Exception): + + def __init__(self, plugin: 'JmOptionPlugin', msg: str): + self.plugin = plugin + self.msg = msg + + +class JmOptionPlugin: + plugin_key: str + + def __init__(self, option: JmOption): + self.option = option + self.log_enable = True + self.delete_original_file = False + + def invoke(self, **kwargs) -> None: + """ + 执行插件的功能 + :param kwargs: 给插件的参数 + """ + raise NotImplementedError + + @classmethod + def build(cls, option: JmOption) -> 'JmOptionPlugin': + """ + 创建插件实例 + :param option: JmOption对象 + """ + return cls(option) + + def log(self, msg, topic=None): + if self.log_enable is not True: + return + + jm_log( + topic=f'plugin.{self.plugin_key}' + (f'.{topic}' if topic is not None else ''), + msg=msg + ) + + def require_param(self, case: Any, msg: str): + """ + 专门用于校验参数的方法,会抛出特定异常,由option拦截根据策略进行处理 + + :param case: 条件 + :param msg: 报错信息 + """ + if case: + return + + raise PluginValidationException(self, msg) + + def warning_lib_not_install(self, lib: str): + msg = (f'插件`{self.plugin_key}`依赖库: {lib},请先安装{lib}再使用。' + f'安装命令: [pip install {lib}]') + import warnings + warnings.warn(msg) + + def execute_deletion(self, paths: List[str]): + """ + 删除文件和文件夹 + :param paths: 路径列表 + """ + if self.delete_original_file is not True: + return + + for p in paths: + if file_not_exists(p): + continue + + if os.path.isdir(p): + os.rmdir(p) + self.log(f'删除文件夹: {p}', 'remove') + else: + os.remove(p) + self.log(f'删除原文件: {p}', 'remove') + + # noinspection PyMethodMayBeStatic + def execute_cmd(self, cmd): + """ + 执行shell命令,这里采用简单的实现 + :param cmd: shell命令 + """ + return os.system(cmd) + + # noinspection PyMethodMayBeStatic + def execute_multi_line_cmd(self, cmd: str): + import subprocess + subprocess.run(cmd, shell=True, check=True) + + def enter_wait_list(self): + self.option.need_wait_plugins.append(self) + + def leave_wait_list(self): + self.option.need_wait_plugins.remove(self) + + def wait_until_finish(self): + pass + + +class JmLoginPlugin(JmOptionPlugin): + """ + 功能:登录禁漫,并保存登录后的cookies,让所有client都带上此cookies + """ + plugin_key = 'login' + + def invoke(self, + username: str, + password: str, + impl=None, + ) -> None: + self.require_param(username, '用户名不能为空') + self.require_param(password, '密码不能为空') + + client = self.option.build_jm_client(impl=impl) + client.login(username, password) + + cookies = dict(client['cookies']) + self.option.update_cookies(cookies) + JmModuleConfig.APP_COOKIES = cookies + + self.log('登录成功') + + +class UsageLogPlugin(JmOptionPlugin): + plugin_key = 'usage_log' + + def invoke(self, **kwargs) -> None: + import threading + t = threading.Thread( + target=self.monitor_resource_usage, + kwargs=kwargs, + daemon=True, + ) + t.start() + + self.set_thread_as_option_attr(t) + + def set_thread_as_option_attr(self, t): + """ + 线程留痕 + """ + name = f'thread_{self.plugin_key}' + + thread_ls: Optional[list] = getattr(self.option, name, None) + if thread_ls is None: + setattr(self.option, name, [t]) + else: + thread_ls.append(t) + + def monitor_resource_usage( + self, + interval=1, + enable_warning=True, + warning_cpu_percent=70, + warning_mem_percent=70, + warning_thread_count=100, + ): + try: + import psutil + except ImportError: + self.warning_lib_not_install('psutil') + return + + from time import sleep + from threading import active_count + # 获取当前进程 + process = psutil.Process() + + cpu_percent = None + # noinspection PyUnusedLocal + thread_count = None + # noinspection PyUnusedLocal + mem_usage = None + + def warning(): + warning_msg_list = [] + if cpu_percent >= warning_cpu_percent: + warning_msg_list.append(f'进程占用cpu过高 ({cpu_percent}% >= {warning_cpu_percent}%)') + + mem_percent = psutil.virtual_memory().percent + if mem_percent >= warning_mem_percent: + warning_msg_list.append(f'系统内存占用过高 ({mem_percent}% >= {warning_mem_percent}%)') + + if thread_count >= warning_thread_count: + warning_msg_list.append(f'线程数过多 ({thread_count} >= {warning_thread_count})') + + if len(warning_msg_list) != 0: + warning_msg_list.insert(0, '硬件占用告警,占用过高可能导致系统卡死!') + warning_msg_list.append('') + self.log('\n'.join(warning_msg_list), topic='warning') + + while True: + # 获取CPU占用率(0~100) + cpu_percent = process.cpu_percent() + # 获取内存占用(MB) + mem_usage = round(process.memory_info().rss / 1024 / 1024, 2) + thread_count = active_count() + # 获取网络占用情况 + # network_info = psutil.net_io_counters() + # network_bytes_sent = network_info.bytes_sent + # network_bytes_received = network_info.bytes_recv + + # 打印信息 + msg = ', '.join([ + f'线程数: {thread_count}', + f'CPU占用: {cpu_percent}%', + f'内存占用: {mem_usage}MB', + # f"发送的字节数: {network_bytes_sent}", + # f"接收的字节数: {network_bytes_received}", + ]) + self.log(msg, topic='log') + + if enable_warning is True: + # 警告 + warning() + + # 等待一段时间 + sleep(interval) + + +class FindUpdatePlugin(JmOptionPlugin): + """ + 参考: https://github.com/hect0x7/JMComic-Crawler-Python/issues/95 + """ + plugin_key = 'find_update' + + def invoke(self, **kwargs) -> None: + self.download_album_with_find_update(kwargs or {}) + + def download_album_with_find_update(self, dic: Dict[str, int]): + from .api import download_album + from .jm_downloader import JmDownloader + + # 带入漫画id, 章节id(第x章),寻找该漫画下第x章节後的所有章节Id + def find_update(album: JmAlbumDetail): + if album.album_id not in dic: + return album + + photo_ls = [] + photo_begin = int(dic[album.album_id]) + is_new_photo = False + + for photo in album: + if is_new_photo: + photo_ls.append(photo) + + if int(photo.photo_id) == photo_begin: + is_new_photo = True + + return photo_ls + + class FindUpdateDownloader(JmDownloader): + def do_filter(self, detail): + if not detail.is_album(): + return detail + + detail: JmAlbumDetail + return find_update(detail) + + # 调用下载api,指定option和downloader + download_album( + jm_album_id=dic.keys(), + option=self.option, + downloader=FindUpdateDownloader, + ) + + +class ZipPlugin(JmOptionPlugin): + plugin_key = 'zip' + + # noinspection PyAttributeOutsideInit + def invoke(self, + downloader, + album: JmAlbumDetail = None, + photo: JmPhotoDetail = None, + delete_original_file=False, + level='photo', + filename_rule='Ptitle', + suffix='zip', + zip_dir='./' + ) -> None: + + from .jm_downloader import JmDownloader + downloader: JmDownloader + self.downloader = downloader + self.level = level + self.delete_original_file = delete_original_file + + # 确保压缩文件所在文件夹存在 + zip_dir = JmcomicText.parse_to_abspath(zip_dir) + mkdir_if_not_exists(zip_dir) + + path_to_delete = [] + photo_dict = self.get_downloaded_photo(downloader, album, photo) + + if level == 'album': + zip_path = self.get_zip_path(album, None, filename_rule, suffix, zip_dir) + self.zip_album(album, photo_dict, zip_path, path_to_delete) + + elif level == 'photo': + for photo, image_list in photo_dict.items(): + zip_path = self.get_zip_path(None, photo, filename_rule, suffix, zip_dir) + self.zip_photo(photo, image_list, zip_path, path_to_delete) + + else: + ExceptionTool.raises(f'Not Implemented Zip Level: {level}') + + self.after_zip(path_to_delete) + + def get_downloaded_photo(self, downloader, album, photo): + return ( + downloader.download_success_dict[album] + if album is not None # after_album + else downloader.download_success_dict[photo.from_album] # after_photo + ) + + def zip_photo(self, photo, image_list: list, zip_path: str, path_to_delete): + """ + 压缩photo文件夹 + """ + photo_dir = self.option.decide_image_save_dir(photo) \ + if len(image_list) == 0 \ + else os.path.dirname(image_list[0][0]) + + from common import backup_dir_to_zip + backup_dir_to_zip(photo_dir, zip_path) + + self.log(f'压缩章节[{photo.photo_id}]成功 → {zip_path}', 'finish') + path_to_delete.append(self.unified_path(photo_dir)) + + @staticmethod + def unified_path(f): + return fix_filepath(f, os.path.isdir(f)) + + def zip_album(self, album, photo_dict: dict, zip_path, path_to_delete): + """ + 压缩album文件夹 + """ + + album_dir = self.option.dir_rule.decide_album_root_dir(album) + import zipfile + with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as f: + for photo in photo_dict.keys(): + # 定位到章节所在文件夹 + photo_dir = self.unified_path(self.option.decide_image_save_dir(photo)) + # 章节文件夹标记为删除 + path_to_delete.append(photo_dir) + for file in files_of_dir(photo_dir): + abspath = os.path.join(photo_dir, file) + relpath = os.path.relpath(abspath, album_dir) + f.write(abspath, relpath) + self.log(f'压缩本子[{album.album_id}]成功 → {zip_path}', 'finish') + + def after_zip(self, path_to_delete: List[str]): + # 删除所有原文件 + dirs = sorted(path_to_delete, reverse=True) + image_paths = [ + path + for photo_dict in self.downloader.download_success_dict.values() + for image_list in photo_dict.values() + for path, image in image_list + ] + self.execute_deletion(image_paths) + self.execute_deletion(dirs) + + # noinspection PyMethodMayBeStatic + def get_zip_path(self, album, photo, filename_rule, suffix, zip_dir): + """ + 计算zip文件的路径 + """ + filename = DirRule.apply_rule_directly(album, photo, filename_rule) + from os.path import join + return join( + zip_dir, + filename + fix_suffix(suffix), + ) + + +class ClientProxyPlugin(JmOptionPlugin): + plugin_key = 'client_proxy' + + def invoke(self, + proxy_client_key, + whitelist=None, + **clazz_init_kwargs, + ) -> None: + if whitelist is not None: + whitelist = set(whitelist) + + proxy_clazz = JmModuleConfig.client_impl_class(proxy_client_key) + new_jm_client: Callable = self.option.new_jm_client + + def hook_new_jm_client(*args, **kwargs): + client = new_jm_client(*args, **kwargs) + if whitelist is not None and client.client_key not in whitelist: + return client + + self.log(f'proxy client {client} with {proxy_clazz}') + return proxy_clazz(client, **clazz_init_kwargs) + + self.option.new_jm_client = hook_new_jm_client + + +class ImageSuffixFilterPlugin(JmOptionPlugin): + plugin_key = 'image_suffix_filter' + + def invoke(self, + allowed_orig_suffix=None, + ) -> None: + if allowed_orig_suffix is None: + return + + allowed_suffix_set = set(fix_suffix(suffix) for suffix in allowed_orig_suffix) + + option_decide_cache = self.option.decide_download_cache + + def apply_filter_then_decide_cache(image: JmImageDetail): + if image.img_file_suffix not in allowed_suffix_set: + self.log(f'跳过下载图片: {image.tag},' + f'因为其后缀\'{image.img_file_suffix}\'不在允许的后缀集合{allowed_suffix_set}内') + image.skip = True + + # let option decide + return option_decide_cache(image) + + self.option.decide_download_cache = apply_filter_then_decide_cache + + +class SendQQEmailPlugin(JmOptionPlugin): + plugin_key = 'send_qq_email' + + def invoke(self, + msg_from, + msg_to, + password, + title, + content, + album=None, + downloader=None, + ) -> None: + self.require_param(msg_from and msg_to and password, '发件人、收件人、授权码都不能为空') + + from common import EmailConfig + econfig = EmailConfig(msg_from, msg_to, password) + epostman = econfig.create_email_postman() + epostman.send(content, title) + + self.log('Email sent successfully') + + +class LogTopicFilterPlugin(JmOptionPlugin): + plugin_key = 'log_topic_filter' + + def invoke(self, whitelist) -> None: + if whitelist is not None: + whitelist = set(whitelist) + + old_jm_log = JmModuleConfig.EXECUTOR_LOG + + def new_jm_log(topic, msg): + if whitelist is not None and topic not in whitelist: + return + + old_jm_log(topic, msg) + + JmModuleConfig.EXECUTOR_LOG = new_jm_log + + +class AutoSetBrowserCookiesPlugin(JmOptionPlugin): + plugin_key = 'auto_set_browser_cookies' + + accepted_cookies_keys = str_to_set(''' + yuo1 + remember_id + remember + ''') + + def invoke(self, + browser: str, + domain: str, + ) -> None: + """ + 坑点预警:由于禁漫需要校验同一设备,使用该插件需要配置自己浏览器的headers,例如 + + ```yml + client: + postman: + meta_data: + headers: { + # 浏览器headers + } + + # 插件配置如下: + plugins: + after_init: + - plugin: auto_set_browser_cookies + kwargs: + browser: chrome + domain: 18comic.vip + ``` + + :param browser: chrome/edge/... + :param domain: 18comic.vip/... + :return: cookies + """ + cookies, e = get_browser_cookies(browser, domain, safe=True) + + if cookies is None: + if isinstance(e, ImportError): + self.warning_lib_not_install('browser_cookie3') + else: + self.log('获取浏览器cookies失败,请关闭浏览器重试') + return + + self.option.update_cookies( + {k: v for k, v in cookies.items() if k in self.accepted_cookies_keys} + ) + self.log('获取浏览器cookies成功') + + +# noinspection PyMethodMayBeStatic +class FavoriteFolderExportPlugin(JmOptionPlugin): + plugin_key = 'favorite_folder_export' + + # noinspection PyAttributeOutsideInit + def invoke(self, + save_dir=None, + zip_enable=False, + zip_filepath=None, + zip_password=None, + delete_original_file=False, + ): + self.save_dir = os.path.abspath(save_dir if save_dir is not None else (os.getcwd() + '/export/')) + self.zip_enable = zip_enable + self.zip_filepath = os.path.abspath(zip_filepath) + self.zip_password = zip_password + self.delete_original_file = delete_original_file + self.files = [] + + mkdir_if_not_exists(self.save_dir) + mkdir_if_not_exists(of_dir_path(self.zip_filepath)) + + self.main() + + def main(self): + cl = self.option.build_jm_client() + # noinspection PyAttributeOutsideInit + self.cl = cl + page = cl.favorite_folder() + + # 获取所有的收藏夹 + folders = {fid: fname for fid, fname in page.iter_folder_id_name()} + # 加上特殊收藏栏【全部】 + folders.setdefault('0', '全部') + + # 一个收藏夹一个线程,导出收藏夹数据到文件 + multi_thread_launcher( + iter_objs=folders.items(), + apply_each_obj_func=self.handle_folder, + ) + + if not self.zip_enable: + return + + # 压缩导出的文件 + self.require_param(self.zip_filepath, '如果开启zip,请指定zip_filepath参数(压缩文件保存路径)') + + if self.zip_password is None: + self.zip_folder_without_password(self.files, self.zip_filepath) + else: + self.zip_with_password() + + self.execute_deletion(self.files) + + def handle_folder(self, fid: str, fname: str): + self.log(f'【收藏夹: {fname}, fid: {fid}】开始获取数据') + + # 获取收藏夹数据 + page_data = self.fetch_folder_page_data(fid) + + # 序列化到文件 + filepath = self.save_folder_page_data_to_file(page_data, fid, fname) + + if filepath is None: + self.log(f'【收藏夹: {fname}, fid: {fid}】收藏夹无数据') + return + + self.log(f'【收藏夹: {fname}, fid: {fid}】保存文件成功 → [{filepath}]') + self.files.append(filepath) + + def fetch_folder_page_data(self, fid): + # 一页一页获取,不使用并行 + page_data = list(self.cl.favorite_folder_gen(folder_id=fid)) + return page_data + + def save_folder_page_data_to_file(self, page_data: List[JmFavoritePage], fid: str, fname: str): + from os import path + filepath = path.abspath(path.join(self.save_dir, fix_windir_name(f'【{fid}】{fname}.csv'))) + + data = [] + for page in page_data: + for aid, extra in page.content: + data.append( + (aid, extra.get('author', '') or JmModuleConfig.DEFAULT_AUTHOR, extra['name']) + ) + + if len(data) == 0: + return + + with open(filepath, 'w', encoding='utf-8') as f: + f.write('id,author,name\n') + for item in data: + f.write(','.join(item) + '\n') + + return filepath + + def zip_folder_without_password(self, files, zip_path): + """ + 压缩文件夹中的文件并设置密码 + + :param files: 要压缩的文件的绝对路径的列表 + :param zip_path: 压缩文件的保存路径 + """ + import zipfile + + with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf: + # 获取文件夹中的文件列表并将其添加到 ZIP 文件中 + for file in files: + zipf.write(file, arcname=of_file_name(file)) + + def zip_with_password(self): + # 构造shell命令 + cmd_list = f''' + cd {self.save_dir} + 7z a "{self.zip_filepath}" "./" -p{self.zip_password} -mhe=on > "../7z_output.txt" + + ''' + self.log(f'运行命令: {cmd_list}') + + # 执行 + self.execute_multi_line_cmd(cmd_list) + + +class ConvertJpgToPdfPlugin(JmOptionPlugin): + plugin_key = 'j2p' + + def check_image_suffix_is_valid(self, std_suffix): + """ + 检查option配置的图片后缀转换,目前限制使用Magick时只能搭配jpg + 暂不探究Magick是否支持更多图片格式 + """ + cur_suffix: Optional[str] = self.option.download.image.suffix + + ExceptionTool.require_true( + cur_suffix is not None and cur_suffix.endswith(std_suffix), + '请把图片的后缀转换配置为jpg,不然无法使用Magick!' + f'(当前配置是[{cur_suffix}])\n' + f'配置模板如下: \n' + f'```\n' + f'download:\n' + f' image:\n' + f' suffix: {std_suffix} # 当前配置是{cur_suffix}\n' + f'```' + ) + + def invoke(self, + photo: JmPhotoDetail, + downloader=None, + pdf_dir=None, + filename_rule='Pid', + quality=100, + delete_original_file=False, + override_cmd=None, + override_jpg=None, + **kwargs, + ): + self.delete_original_file = delete_original_file + + # 检查图片后缀配置 + suffix = override_jpg or '.jpg' + self.check_image_suffix_is_valid(suffix) + + # 处理文件夹配置 + filename = DirRule.apply_rule_directly(None, photo, filename_rule) + photo_dir = self.option.decide_image_save_dir(photo) + + # 处理生成的pdf文件的路径 + if pdf_dir is None: + pdf_dir = photo_dir + else: + pdf_dir = fix_filepath(pdf_dir, True) + mkdir_if_not_exists(pdf_dir) + + pdf_filepath = os.path.join(pdf_dir, f'{filename}.pdf') + + # 生成命令 + def generate_cmd(): + return ( + override_cmd or + 'magick convert -quality {quality} "{photo_dir}*{suffix}" "{pdf_filepath}"' + ).format( + quality=quality, + photo_dir=photo_dir, + suffix=suffix, + pdf_filepath=pdf_filepath, + ) + + cmd = generate_cmd() + self.log(f'Execute Command: [{cmd}]') + code = self.execute_cmd(cmd) + + ExceptionTool.require_true( + code == 0, + 'jpg图片合并为pdf失败!' + '请确认你是否安装了magick,安装网站: [https://www.imagemagick.org/]', + ) + + self.log(f'Convert Successfully: JM{photo.id} → {pdf_filepath}') + + if downloader is not None: + from .jm_downloader import JmDownloader + downloader: JmDownloader + + paths = [ + path + for path, image in downloader.download_success_dict[photo.from_album][photo] + ] + + paths.append(self.option.decide_image_save_dir(photo, ensure_exists=False)) + self.execute_deletion(paths) + + +class Img2pdfPlugin(JmOptionPlugin): + plugin_key = 'img2pdf' + + def invoke(self, + photo: JmPhotoDetail = None, + album: JmAlbumDetail = None, + downloader=None, + pdf_dir=None, + filename_rule='Pid', + delete_original_file=False, + **kwargs, + ): + if photo is None and album is None: + jm_log('wrong_usage', 'img2pdf必须运行在after_photo或after_album时') + + try: + import img2pdf + except ImportError: + self.warning_lib_not_install('img2pdf') + return + + self.delete_original_file = delete_original_file + + # 处理生成的pdf文件的路径 + pdf_dir = self.ensure_make_pdf_dir(pdf_dir) + + # 处理pdf文件名 + filename = DirRule.apply_rule_directly(album, photo, filename_rule) + + # pdf路径 + pdf_filepath = os.path.join(pdf_dir, f'{filename}.pdf') + + # 调用 img2pdf 把 photo_dir 下的所有图片转为pdf + img_path_ls, img_dir_ls = self.write_img_2_pdf(pdf_filepath, album, photo) + self.log(f'Convert Successfully: JM{album or photo} → {pdf_filepath}') + + # 执行删除 + img_path_ls += img_dir_ls + self.execute_deletion(img_path_ls) + + def write_img_2_pdf(self, pdf_filepath, album: JmAlbumDetail, photo: JmPhotoDetail): + import img2pdf + + if album is None: + img_dir_ls = [self.option.decide_image_save_dir(photo)] + else: + img_dir_ls = [self.option.decide_image_save_dir(photo) for photo in album] + + img_path_ls = [] + + for img_dir in img_dir_ls: + imgs = files_of_dir(img_dir) + if not imgs: + continue + img_path_ls += imgs + + with open(pdf_filepath, 'wb') as f: + f.write(img2pdf.convert(img_path_ls)) + + return img_path_ls, img_dir_ls + + @staticmethod + def ensure_make_pdf_dir(pdf_dir: str): + pdf_dir = pdf_dir or os.getcwd() + pdf_dir = fix_filepath(pdf_dir, True) + mkdir_if_not_exists(pdf_dir) + return pdf_dir + + +class LongImgPlugin(JmOptionPlugin): + plugin_key = 'long_img' + + def invoke(self, + photo: JmPhotoDetail = None, + album: JmAlbumDetail = None, + downloader=None, + img_dir=None, + filename_rule='Pid', + delete_original_file=False, + **kwargs, + ): + if photo is None and album is None: + jm_log('wrong_usage', 'long_img必须运行在after_photo或after_album时') + + try: + from PIL import Image + except ImportError: + self.warning_lib_not_install('PIL') + return + + self.delete_original_file = delete_original_file + + # 处理文件夹配置 + img_dir = self.get_img_dir(img_dir) + + # 处理生成的长图文件的路径 + filename = DirRule.apply_rule_directly(album, photo, filename_rule) + + # 长图路径 + long_img_path = os.path.join(img_dir, f'{filename}.png') + + # 调用 PIL 把 photo_dir 下的所有图片合并为长图 + img_path_ls = self.write_img_2_long_img(long_img_path, album, photo) + self.log(f'Convert Successfully: JM{album or photo} → {long_img_path}') + + # 执行删除 + self.execute_deletion(img_path_ls) + + def write_img_2_long_img(self, long_img_path, album: JmAlbumDetail, photo: JmPhotoDetail) -> List[str]: + import itertools + from PIL import Image + + if album is None: + img_dir_items = [self.option.decide_image_save_dir(photo)] + else: + img_dir_items = [self.option.decide_image_save_dir(photo) for photo in album] + + img_paths = itertools.chain(*map(files_of_dir, img_dir_items)) + img_paths = filter(lambda x: not x.startswith('.'), img_paths) # 过滤系统文件 + + images = self.open_images(img_paths) + + try: + resample_method = Image.Resampling.LANCZOS + except AttributeError: + resample_method = Image.LANCZOS + + min_img_width = min(img.width for img in images) + total_height = 0 + for i, img in enumerate(images): + if img.width > min_img_width: + images[i] = img.resize((min_img_width, int(img.height * min_img_width / img.width)), + resample=resample_method) + total_height += images[i].height + + long_img = Image.new('RGB', (min_img_width, total_height)) + y_offset = 0 + for img in images: + long_img.paste(img, (0, y_offset)) + y_offset += img.height + + long_img.save(long_img_path) + for img in images: + img.close() + + return img_paths + + def open_images(self, img_paths: List[str]): + images = [] + for img_path in img_paths: + try: + img = Image.open(img_path) + images.append(img) + except IOError as e: + self.log(f"Failed to open image {img_path}: {e}", 'error') + return images + + @staticmethod + def get_img_dir(img_dir: Optional[str]) -> str: + img_dir = fix_filepath(img_dir or os.getcwd()) + mkdir_if_not_exists(img_dir) + return img_dir + + +class JmServerPlugin(JmOptionPlugin): + plugin_key = 'jm_server' + + default_run_kwargs = { + 'host': '0.0.0.0', + 'port': '80', + 'debug': False, + } + + from threading import Lock + single_instance_lock = Lock() + + def __init__(self, option: JmOption): + super().__init__(option) + self.run_server_lock = Lock() + self.running = False + self.server_thread: Optional[Thread] = None + + def invoke(self, + password='', + base_dir=None, + album=None, + photo=None, + downloader=None, + run=None, + **kwargs + ): + """ + + :param password: 密码 + :param base_dir: 初始访问服务器的根路径 + :param album: 为了支持 after_album 这种调用时机 + :param photo: 为了支持 after_album 这种调用时机 + :param downloader: 为了支持 after_album 这种调用时机 + :param run: 用于启动服务器: app.run(**run_kwargs) + :param kwargs: 用于JmServer构造函数: JmServer(base_dir, password, **kwargs) + """ + + if base_dir is None: + base_dir = self.option.dir_rule.base_dir + + if run is None: + run = self.default_run_kwargs + else: + base_run_kwargs = self.default_run_kwargs.copy() + base_run_kwargs.update(run) + run = base_run_kwargs + + if self.running is True: + return + + with self.run_server_lock: + if self.running is True: + return + + # 服务器的代码位于一个独立库:plugin_jm_server,需要独立安装 + # 源代码仓库:https://github.com/hect0x7/plugin-jm-server + try: + import plugin_jm_server + self.log(f'当前使用plugin_jm_server版本: {plugin_jm_server.__version__}') + except ImportError: + self.warning_lib_not_install('plugin_jm_server') + return + + # 核心函数,启动服务器,会阻塞当前线程 + def blocking_run_server(): + self.server_thread = current_thread() + self.enter_wait_list() + server = plugin_jm_server.JmServer(base_dir, password, **kwargs) + # run方法会阻塞当前线程直到flask退出 + server.run(**run) + + # 对于debug模式,特殊处理 + if run['debug'] is True: + run.setdefault('use_reloader', False) + + # debug模式只能在主线程启动,判断当前线程是不是主线程 + if current_thread() is not threading.main_thread(): + # 不是主线程,return + return self.warning_wrong_usage_of_debug() + else: + self.running = True + # 是主线程,启动服务器 + blocking_run_server() + + else: + # 非debug模式,开新线程启动 + threading.Thread(target=blocking_run_server, daemon=True).start() + atexit_register(self.wait_server_stop) + self.running = True + + def warning_wrong_usage_of_debug(self): + self.log('注意!当配置debug=True时,请确保当前插件是在主线程中被调用。\n' + '因为如果本插件配置在 [after_album/after_photo] 这种时机调用,\n' + '会使得flask框架不在主线程debug运行,\n' + '导致报错(ValueError: signal only works in main thread of the main interpreter)。\n', + '【基于上述原因,当前线程非主线程,不启动服务器】' + 'warning' + ) + + def wait_server_stop(self, proactive=False): + st = self.server_thread + if ( + st is None + or st == current_thread() + or not st.is_alive() + ): + return + + if proactive: + msg = f'[{self.plugin_key}]的服务器线程仍运行中,可按下ctrl+c结束程序' + else: + msg = f'主线程执行完毕,但插件[{self.plugin_key}]的服务器线程仍运行中,可按下ctrl+c结束程序' + + self.log(msg, 'wait') + + while st.is_alive(): + try: + st.join(timeout=0.5) + except KeyboardInterrupt: + self.log('收到ctrl+c,结束程序', 'wait') + return + + def wait_until_finish(self): + self.wait_server_stop(proactive=True) + + @classmethod + def build(cls, option: JmOption) -> 'JmOptionPlugin': + """ + 单例模式 + """ + field_name = 'single_instance' + + instance = getattr(cls, field_name, None) + if instance is not None: + return instance + + with cls.single_instance_lock: + instance = getattr(cls, field_name, None) + if instance is not None: + return instance + instance = JmServerPlugin(option) + setattr(cls, field_name, instance) + return instance + + +class SubscribeAlbumUpdatePlugin(JmOptionPlugin): + plugin_key = 'subscribe_album_update' + + def invoke(self, + album_photo_dict=None, + email_notify=None, + download_if_has_update=True, + auto_update_after_download=True, + ) -> None: + if album_photo_dict is None: + return + + album_photo_dict: Dict + for album_id, photo_id in album_photo_dict.copy().items(): + # check update + try: + has_update, photo_new_list = self.check_photo_update(album_id, photo_id) + except JmcomicException as e: + self.log('Exception happened: ' + str(e), 'check_update.error') + continue + + if has_update is False: + continue + + self.log(f'album={album_id},发现新章节: {photo_new_list},准备开始下载') + + # send email + try: + if email_notify: + SendQQEmailPlugin.build(self.option).invoke(**email_notify) + except PluginValidationException: + # ignore + pass + + # download new photo + if has_update and download_if_has_update: + self.option.download_photo(photo_new_list) + + if auto_update_after_download: + album_photo_dict[album_id] = photo_new_list[-1] + self.option.to_file() + + def check_photo_update(self, album_id: str, photo_id: str): + client = self.option.new_jm_client() + album = client.get_album_detail(album_id) + + photo_new_list = [] + is_new_photo = False + sentinel = int(photo_id) + + for photo in album: + if is_new_photo: + photo_new_list.append(photo.photo_id) + + if int(photo.photo_id) == sentinel: + is_new_photo = True + + return len(photo_new_list) != 0, photo_new_list + + +class SkipPhotoWithFewImagesPlugin(JmOptionPlugin): + plugin_key = 'skip_photo_with_few_images' + + def invoke(self, + at_least_image_count: int, + photo: Optional[JmPhotoDetail] = None, + image: Optional[JmImageDetail] = None, + album: Optional[JmAlbumDetail] = None, + **kwargs + ): + self.try_mark_photo_skip_and_log(photo, at_least_image_count) + if image is not None: + self.try_mark_photo_skip_and_log(image.from_photo, at_least_image_count) + + def try_mark_photo_skip_and_log(self, photo: JmPhotoDetail, at_least_image_count: int): + if photo is None: + return + + if len(photo) >= at_least_image_count: + return + + self.log(f'跳过下载章节: {photo.id} ({photo.album_id}[{photo.index}/{len(photo.from_album)}]),' + f'因为其图片数: {len(photo)} < {at_least_image_count} (at_least_image_count)') + photo.skip = True + + @classmethod + @field_cache() # 单例 + def build(cls, option: JmOption) -> 'JmOptionPlugin': + return super().build(option) + + +class DeleteDuplicatedFilesPlugin(JmOptionPlugin): + """ + https://github.com/hect0x7/JMComic-Crawler-Python/issues/244 + """ + plugin_key = 'delete_duplicated_files' + + @classmethod + def calculate_md5(cls, file_path): + import hashlib + + """计算文件的MD5哈希值""" + hash_md5 = hashlib.md5() + with open(file_path, "rb") as f: + for chunk in iter(lambda: f.read(4096), b""): + hash_md5.update(chunk) + return hash_md5.hexdigest() + + @classmethod + def find_duplicate_files(cls, root_folder): + """递归读取文件夹下所有文件并计算MD5出现次数""" + import os + from collections import defaultdict + md5_dict = defaultdict(list) + + for root, _, files in os.walk(root_folder): + for file in files: + file_path = os.path.join(root, file) + file_md5 = cls.calculate_md5(file_path) + md5_dict[file_md5].append(file_path) + + return md5_dict + + def invoke(self, + limit, + album=None, + downloader=None, + delete_original_file=True, + **kwargs, + ) -> None: + if album is None: + return + + self.delete_original_file = delete_original_file + # 获取到下载本子所在根目录 + root_folder = self.option.dir_rule.decide_album_root_dir(album) + self.find_duplicated_files_and_delete(limit, root_folder, album) + + def find_duplicated_files_and_delete(self, limit: int, root_folder: str, album: Optional[JmAlbumDetail] = None): + md5_dict = self.find_duplicate_files(root_folder) + # 打印MD5出现次数大于等于limit的文件 + for md5, paths in md5_dict.items(): + if len(paths) >= limit: + prefix = '' if album is None else f'({album.album_id}) ' + message = [prefix + f'MD5: {md5} 出现次数: {len(paths)}'] + \ + [f' {path}' for path in paths] + self.log('\n'.join(message)) + self.execute_deletion(paths) + + +class ReplacePathStringPlugin(JmOptionPlugin): + plugin_key = 'replace_path_string' + + def invoke(self, + replace: Dict[str, str], + ): + if not replace: + return + + old_decide_dir = self.option.decide_image_save_dir + + def new_decide_dir(photo, ensure_exists=True) -> str: + original_path: str = old_decide_dir(photo, False) + for k, v in replace.items(): + original_path = original_path.replace(k, v) + + if ensure_exists: + JmcomicText.try_mkdir(original_path) + + return original_path + + self.option.decide_image_save_dir = new_decide_dir diff --git a/jm/src/jmcomic/jm_toolkit.py b/jm/src/jmcomic/jm_toolkit.py new file mode 100755 index 0000000..0c53014 --- /dev/null +++ b/jm/src/jmcomic/jm_toolkit.py @@ -0,0 +1,927 @@ +from PIL import Image + +from .jm_exception import * + + +class JmcomicText: + pattern_jm_domain = compile(r'https://([\w.-]+)') + pattern_jm_pa_id = [ + (compile(r'(photos?|albums?)/(\d+)'), 2), + (compile(r'id=(\d+)'), 1), + ] + pattern_html_jm_pub_domain = compile(r'[\w-]+\.\w+/?\w+') + + pattern_html_photo_photo_id = compile(r'') + pattern_html_photo_scramble_id = compile(r'var scramble_id = (\d+);') + pattern_html_photo_name = compile(r'([\s\S]*?)\|.*') + # pattern_html_photo_data_original_list = compile(r'data-original="(.*?)" id="album_photo_.+?"') + pattern_html_photo_data_original_domain = compile(r'src="https://(.*?)/media/albums/blank') + pattern_html_photo_data_original_0 = compile(r'data-original="(.*?)"[^>]*?id="album_photo[^>]*?data-page="0"') + pattern_html_photo_tags = compile(r'.*?:JM(\d+)') + pattern_html_album_scramble_id = compile(r'var scramble_id = (\d+);') + pattern_html_album_name = compile(r'

([\s\S]*?)

') + pattern_html_album_episode_list = compile(r'data-album="(\d+)"[^>]*>\s*?\s*?第(\d+)[话話]([\s\S]*?)<[\s\S]*?>') + pattern_html_album_page_count = compile(r'.*?:(\d+)') + pattern_html_album_pub_date = compile(r'>上架日期 : (.*?)') + pattern_html_album_update_date = compile(r'>更新日期 : (.*?)') + pattern_html_tag_a = compile(r']*?>\s*(\S*)\s*') + # 作品 + pattern_html_album_works = [ + compile(r''), + pattern_html_tag_a, + ] + # 登場人物 + pattern_html_album_actors = [ + compile(r''), + pattern_html_tag_a, + ] + # 标签 + pattern_html_album_tags = [ + compile(r'([\s\S]*?)'), + pattern_html_tag_a, + ] + # 作者 + pattern_html_album_authors = [ + compile(r'作者: *'), + pattern_html_tag_a, + ] + # 點擊喜歡 + pattern_html_album_likes = compile(r'(.*?)') + # 觀看 + pattern_html_album_views = compile(r'(.*?)\n *(次觀看|观看次数|次观看次数)') + # 評論(div) + pattern_html_album_comment_count = compile(r'
]*?id="total_video_comments">(\d+)
'), 0 + + # 提取接口返回值信息 + pattern_ajax_favorite_msg = compile(r'(.*?)') + + @classmethod + def parse_to_jm_domain(cls, text: str): + if text.startswith(JmModuleConfig.PROT): + return cls.pattern_jm_domain.search(text)[1] + + return text + + @classmethod + def parse_to_jm_id(cls, text) -> str: + if isinstance(text, int): + return str(text) + + ExceptionTool.require_true(isinstance(text, str), f"无法解析jm车号, 参数类型为: {type(text)}") + + # 43210 + if text.isdigit(): + return text + + # Jm43210 + ExceptionTool.require_true(len(text) >= 2, f"无法解析jm车号, 文本太短: {text}") + + # text: JM12341 + c0 = text[0] + c1 = text[1] + if (c0 == 'J' or c0 == 'j') and (c1 == 'M' or c1 == 'm'): + # JM123456 + return text[2:] + else: + # https://xxx/photo/412038 + # https://xxx/album/?id=412038 + for p, i in cls.pattern_jm_pa_id: + match = p.search(text) + if match is not None: + return match[i] + + ExceptionTool.raises(f"无法解析jm车号, 文本为: {text}") + + @classmethod + def analyse_jm_pub_html(cls, html: str, domain_keyword=('jm', 'comic')) -> List[str]: + domain_ls = cls.pattern_html_jm_pub_domain.findall(html) + + return list(filter( + lambda domain: any(kw in domain for kw in domain_keyword), + domain_ls + )) + + @classmethod + def analyse_jm_photo_html(cls, html: str) -> JmPhotoDetail: + return cls.reflect_new_instance( + html, + "pattern_html_photo_", + JmModuleConfig.photo_class() + ) + + @classmethod + def analyse_jm_album_html(cls, html: str) -> JmAlbumDetail: + return cls.reflect_new_instance( + html, + "pattern_html_album_", + JmModuleConfig.album_class() + ) + + @classmethod + def reflect_new_instance(cls, html: str, cls_field_prefix: str, clazz: type): + + def match_field(field_name: str, pattern: Union[Pattern, List[Pattern]], text): + + if isinstance(pattern, list): + # 如果是 pattern 是 List[re.Pattern], + # 取最后一个 pattern 用于 match field, + # 其他的 pattern 用来给文本缩小范围(相当于多次正则匹配) + last_pattern = pattern[len(pattern) - 1] + # 缩小文本 + for i in range(0, len(pattern) - 1): + match: Match = pattern[i].search(text) + if match is None: + return None + text = match[0] + + return last_pattern.findall(text) + + if field_name.endswith("_list"): + return pattern.findall(text) + else: + match = pattern.search(text) + if match is not None: + return match[1] + return None + + field_dict = {} + pattern_name: str + for pattern_name, pattern in cls.__dict__.items(): + if not pattern_name.startswith(cls_field_prefix): + continue + + # 支持如果不匹配,使用默认值 + if isinstance(pattern, tuple): + pattern, default = pattern + else: + default = None + + # 获取字段名和值 + field_name = pattern_name[pattern_name.index(cls_field_prefix) + len(cls_field_prefix):] + field_value = match_field(field_name, pattern, html) + + if field_value is None: + if default is None: + ExceptionTool.raises_regex( + f"文本没有匹配上字段:字段名为'{field_name}',pattern: [{pattern}]" + + (f"\n响应文本=[{html}]" if len(html) < 200 else + f'响应文本过长(len={len(html)}),不打印' + ), + html=html, + pattern=pattern, + ) + else: + field_value = default + + # 保存字段 + field_dict[field_name] = field_value + + return clazz(**field_dict) + + @classmethod + def format_url(cls, path, domain): + ExceptionTool.require_true(isinstance(domain, str) and len(domain) != 0, '域名为空') + + if domain.startswith(JmModuleConfig.PROT): + return f'{domain}{path}' + + return f'{JmModuleConfig.PROT}{domain}{path}' + + @classmethod + def format_album_url(cls, aid, domain='18comic.vip'): + """ + 把album_id变为可访问的URL,方便print打印后用浏览器访问 + """ + return cls.format_url(f'/album/{aid}/', domain) + + class DSLReplacer: + + def __init__(self): + self.dsl_dict: Dict[Pattern, Callable[[Match], str]] = {} + + def parse_dsl_text(self, text) -> str: + for pattern, replacer in self.dsl_dict.items(): + text = pattern.sub(replacer, text) + return text + + def add_dsl_and_replacer(self, dsl: str, replacer: Callable[[Match], str]): + pattern = compile(dsl) + self.dsl_dict[pattern] = replacer + + @classmethod + def match_os_env(cls, match: Match) -> str: + name = match[1] + value = os.getenv(name, None) + ExceptionTool.require_true(value is not None, f'未配置环境变量: {name}') + return value + + dsl_replacer = DSLReplacer() + + @classmethod + def parse_to_abspath(cls, dsl_text: str) -> str: + return os.path.abspath(cls.parse_dsl_text(dsl_text)) + + @classmethod + def parse_dsl_text(cls, dsl_text: str) -> str: + return cls.dsl_replacer.parse_dsl_text(dsl_text) + + bracket_map = {'(': ')', + '[': ']', + '【': '】', + '(': ')', + } + + @classmethod + def parse_orig_album_name(cls, name: str, default=None): + word_list = cls.tokenize(name) + + for word in word_list: + if word[0] in cls.bracket_map: + continue + + return word + + return default + + @classmethod + def tokenize(cls, title: str) -> List[str]: + """ + 繞道#2 [暴碧漢化組] [えーすけ(123)] よりみち#2 (COMIC 快樂天 2024年1月號) [中國翻譯] [DL版] + :return: ['繞道#2', '[暴碧漢化組]', '[えーすけ(123)]', 'よりみち#2', '(COMIC 快樂天 2024年1月號)', '[中國翻譯]', '[DL版]'] + """ + title = title.strip() + ret = [] + bracket_map = cls.bracket_map + + char_list = [] + i = 0 + length = len(title) + + def add(w=None): + if w is None: + w = ''.join(char_list).strip() + + if w == '': + return + + ret.append(w) + char_list.clear() + + def find_right_pair(left_pair, i): + stack = [left_pair] + j = i + 1 + + while j < length and len(stack) != 0: + c = title[j] + if c in bracket_map: + stack.append(c) + elif c == bracket_map[stack[-1]]: + stack.pop() + + j += 1 + + if len(stack) == 0: + return j + else: + return -1 + + while i < length: + c = title[i] + + if c in bracket_map: + # 上一个单词结束 + add() + # 定位右括号 + j = find_right_pair(c, i) + if j == -1: + # 括号未闭合 + char_list.append(c) + i += 1 + continue + # 整个括号的单词结束 + add(title[i:j]) + # 移动指针 + i = j + else: + char_list.append(c) + i += 1 + + add() + return ret + + @classmethod + def to_zh_cn(cls, s): + import zhconv + return zhconv.convert(s, 'zh-cn') + + @classmethod + def try_mkdir(cls, save_dir: str): + try: + mkdir_if_not_exists(save_dir) + except OSError as e: + if e.errno == 36: + # 目录名过长 + limit = JmModuleConfig.VAR_FILE_NAME_LENGTH_LIMIT + jm_log('error', f'目录名过长,无法创建目录,强制缩短到{limit}个字符并重试') + save_dir = save_dir[0:limit] + return cls.try_mkdir(save_dir) + raise e + return save_dir + + +# 支持dsl: #{???} -> os.getenv(???) +JmcomicText.dsl_replacer.add_dsl_and_replacer(r'\$\{(.*?)\}', JmcomicText.match_os_env) + + +class PatternTool: + + @classmethod + def match_or_default(cls, html: str, pattern: Pattern, default): + match = pattern.search(html) + return default if match is None else match[1] + + @classmethod + def require_match(cls, html: str, pattern: Pattern, msg, rindex=1): + match = pattern.search(html) + if match is not None: + return match[rindex] if rindex is not None else match + + ExceptionTool.raises_regex( + msg, + html=html, + pattern=pattern, + ) + + @classmethod + def require_not_match(cls, html: str, pattern: Pattern, *, msg_func): + match = pattern.search(html) + if match is None: + return + + ExceptionTool.raises_regex( + msg_func(match), + html=html, + pattern=pattern, + ) + + +class JmPageTool: + # 用来缩减html的长度 + pattern_html_search_shorten_for = compile(r'
([\s\S]*)
') + + # 用来提取搜索页面的album的信息 + pattern_html_search_album_info_list = compile( + r']*>[^>]*?' + r'title="(.*?)"[^>]*>[ \n]*[ \n]*' + r'
([\s\S]*?)' + r'
' + ) + + # 用来查找tag列表 + pattern_html_search_tags = compile(r']*?>(.*?)') + + # 查找错误,例如 [错误,關鍵字過短,請至少輸入兩個字以上。] + pattern_html_search_error = compile(r'
\n(.*?)\n
\n(.*?)\n
\n
') + + pattern_html_search_total = compile(r'class="text-white">(\d+) A漫.'), 0 + + # 收藏页面的本子结果 + pattern_html_favorite_content = compile( + r'
[\s\S]*?' + r'
([^<]*?)' + r'
' + ) + + # 收藏夹的收藏总数 + pattern_html_favorite_total = compile(r' : (\d+)[^/]*/\D*(\d+)') + + # 所有的收藏夹 + pattern_html_favorite_folder_list = [ + compile(r''), + compile(r'') + ] + + @classmethod + def parse_html_to_search_page(cls, html: str) -> JmSearchPage: + # 1. 检查是否失败 + PatternTool.require_not_match( + html, + cls.pattern_html_search_error, + msg_func=lambda match: '{}: {}'.format(match[1], match[2]) + ) + + # 2. 缩小文本范围 + html = PatternTool.require_match( + html, + cls.pattern_html_search_shorten_for, + msg='未匹配到搜索结果', + ) + + # 3. 提取结果 + content = [] # content这个名字来源于api版搜索返回值 + total = int(PatternTool.match_or_default(html, *cls.pattern_html_search_total)) # 总结果数 + + album_info_list = cls.pattern_html_search_album_info_list.findall(html) + + for (album_id, title, _label_category_text, tag_text) in album_info_list: + # 从label_category_text中可以解析出label-category和label-sub + # 这里不作解析,因为没什么用... + tags = cls.pattern_html_search_tags.findall(tag_text) + content.append(( + album_id, { + 'name': title, # 改成name是为了兼容 parse_api_resp_to_page + 'tags': tags + } + )) + + return JmSearchPage(content, total) + + @classmethod + def parse_html_to_category_page(cls, html: str) -> JmSearchPage: + content = [] + total = int(PatternTool.match_or_default(html, *cls.pattern_html_search_total)) + + album_info_list = cls.pattern_html_category_album_info_list.findall(html) + + for (album_id, title, tag_text) in album_info_list: + tags = cls.pattern_html_search_tags.findall(tag_text) + content.append(( + album_id, { + 'name': title, # 改成name是为了兼容 parse_api_resp_to_page + 'tags': tags + } + )) + + return JmSearchPage(content, total) + + @classmethod + def parse_html_to_favorite_page(cls, html: str) -> JmFavoritePage: + total = int(PatternTool.require_match( + html, + cls.pattern_html_favorite_total, + '未匹配到收藏夹的本子总数', + )) + + # 收藏夹的本子结果 + content = cls.pattern_html_favorite_content.findall(html) + content = [ + (aid, {'name': atitle}) + for aid, atitle in content + ] + + # 匹配收藏夹列表 + p1, p2 = cls.pattern_html_favorite_folder_list + folder_list_text = PatternTool.require_match(html, p1, '未匹配到收藏夹列表') + folder_list_raw = p2.findall(folder_list_text) + folder_list = [{'name': fname, 'FID': fid} for fid, fname in folder_list_raw] + + return JmFavoritePage(content, folder_list, total) + + @classmethod + def parse_api_to_search_page(cls, data: AdvancedDict) -> JmSearchPage: + """ + model_data: { + "search_query": "MANA", + "total": "177", + "content": [ + { + "id": "441923", + "author": "MANA", + "description": "", + "name": "[MANA] 神里绫华5", + "image": "", + "category": { + "id": "1", + "title": "同人" + }, + "category_sub": { + "id": "1", + "title": "同人" + } + } + ] + } + """ + total: int = int(data.total or 0) # 2024.1.5 data.total可能为None + content = cls.adapt_content(data.content) + return JmSearchPage(content, total) + + @classmethod + def parse_api_to_favorite_page(cls, data: AdvancedDict) -> JmFavoritePage: + """ + { + "list": [ + { + "id": "363859", + "author": "紺菓", + "description": "", + "name": "[無邪氣漢化組] (C99) [紺色果實 (紺菓)] サレンの樂しい夢 (プリンセスコネクト!Re:Dive) [中國翻譯]", + "latest_ep": null, + "latest_ep_aid": null, + "image": "", + "category": { + "id": "1", + "title": "同人" + }, + "category_sub": { + "id": "1", + "title": "同人" + } + } + ], + "folder_list": [ + { + "0": "123", + "FID": "123", + "1": "456", + "UID": "456", + "2": "收藏夹名", + "name": "收藏夹名" + } + ], + "total": "87", + "count": 20 + } + """ + total: int = int(data.total) + # count: int = int(data.count) + content = cls.adapt_content(data.list) + folder_list = data.get('folder_list', []) + + return JmFavoritePage(content, folder_list, total) + + @classmethod + def adapt_content(cls, content): + def adapt_item(item: AdvancedDict): + item: dict = item.src_dict + item.setdefault('tags', []) + return item + + content = [ + (item.id, adapt_item(item)) for item in content + ] + return content + + +class JmApiAdaptTool: + """ + 本类负责把移动端的api返回值,适配为标准的实体类 + + # album + { + "id": 123, + "name": "[狗野叉漢化]", + "author": [ + "AREA188" + ], + "images": [ + "00004.webp" + ], + "description": null, + "total_views": "41314", + "likes": "918", + "series": [], + "series_id": "0", + "comment_total": "5", + "tags": [ + "全彩", + "中文" + ], + "works": [], + "actors": [], + "related_list": [ + { + "id": "333718", + "author": "been", + "description": "", + "name": "[been]The illusion of lies(1)[中國語][無修正][全彩]", + "image": "" + } + ], + "liked": false, + "is_favorite": false + } + + # photo + { + "id": 413446, + "series": [ + { + "id": "487043", + "name": "第48話", + "sort": "48" + } + ], + "tags": "慾望 調教 NTL 地鐵 戲劇", + "name": "癡漢成癮-第2話", + "images": [ + "00047.webp" + ], + "series_id": "400222", + "is_favorite": false, + "liked": false + } + """ + field_adapter = { + JmAlbumDetail: [ + 'likes', + 'tags', + 'works', + 'actors', + 'related_list', + 'name', + ('id', 'album_id'), + ('author', 'authors'), + ('total_views', 'views'), + ('comment_total', 'comment_count'), + ], + JmPhotoDetail: [ + 'name', + 'series_id', + 'tags', + ('id', 'photo_id'), + ('images', 'page_arr'), + + ] + } + + @classmethod + def parse_entity(cls, data: dict, clazz: type): + adapter = cls.get_adapter(clazz) + + fields = {} + for k in adapter: + if isinstance(k, str): + v = data[k] + fields[k] = v + elif isinstance(k, tuple): + k, rename_k = k + v = data[k] + fields[rename_k] = v + + if issubclass(clazz, JmAlbumDetail): + cls.post_adapt_album(data, clazz, fields) + else: + cls.post_adapt_photo(data, clazz, fields) + + return clazz(**fields) + + @classmethod + def get_adapter(cls, clazz: type): + for k, v in cls.field_adapter.items(): + if issubclass(clazz, k): + return v + + ExceptionTool.raises(f'不支持的类型: {clazz}') + + @classmethod + def post_adapt_album(cls, data: dict, _clazz: type, fields: dict): + series = data['series'] + episode_list = [] + for chapter in series: + chapter = AdvancedDict(chapter) + # photo_id, photo_index, photo_title, photo_pub_date + episode_list.append( + (chapter.id, chapter.sort, chapter.name) + ) + fields['episode_list'] = episode_list + for it in 'scramble_id', 'page_count', 'pub_date', 'update_date': + fields[it] = '0' + + @classmethod + def post_adapt_photo(cls, data: dict, _clazz: type, fields: dict): + # 1. 获取sort字段,如果data['series']中没有,使用默认值1 + sort = 1 + series: list = data['series'] # series中的sort从1开始 + for chapter in series: + chapter = AdvancedDict(chapter) + if int(chapter.id) == int(data['id']): + sort = chapter.sort + break + + fields['sort'] = sort + import random + fields['data_original_domain'] = random.choice(JmModuleConfig.DOMAIN_IMAGE_LIST) + + +class JmImageTool: + + @classmethod + def save_resp_img(cls, resp: Any, filepath: str, need_convert=True): + """ + 接收HTTP响应对象,将其保存到图片文件. + 如果需要改变图片的文件格式,比如 .jpg → .png,则需要指定参数 neet_convert=True. + 如果不需要改变图片的文件格式,使用 need_convert=False,可以跳过PIL解析图片,效率更高. + + :param resp: JmImageResp + :param filepath: 图片文件路径 + :param need_convert: 是否转换图片 + """ + if need_convert is False: + cls.save_directly(resp, filepath) + else: + cls.save_image(cls.open_image(resp.content), filepath) + + @classmethod + def save_image(cls, image: Image, filepath: str): + """ + 保存图片 + + :param image: PIL.Image对象 + :param filepath: 保存文件路径 + """ + image.save(filepath) + + @classmethod + def save_directly(cls, resp, filepath): + from common import save_resp_content + save_resp_content(resp, filepath) + + @classmethod + def decode_and_save(cls, + num: int, + img_src: Image, + decoded_save_path: str + ) -> None: + """ + 解密图片并保存 + :param num: 分割数,可以用 cls.calculate_segmentation_num 计算 + :param img_src: 原始图片 + :param decoded_save_path: 解密图片的保存路径 + """ + + # 无需解密,直接保存 + if num == 0: + cls.save_image(img_src, decoded_save_path) + return + + import math + w, h = img_src.size + + # 创建新的解密图片 + img_decode = Image.new("RGB", (w, h)) + over = h % num + for i in range(num): + move = math.floor(h / num) + y_src = h - (move * (i + 1)) - over + y_dst = move * i + + if i == 0: + move += over + else: + y_dst += over + + img_decode.paste( + img_src.crop(( + 0, y_src, + w, y_src + move + )), + ( + 0, y_dst, + w, y_dst + move + ) + ) + + # save every step result + # cls.save_image(img_decode, change_file_name( + # decoded_save_path, + # f'{of_file_name(decoded_save_path, trim_suffix=True)}_{i}{of_file_suffix(decoded_save_path)}' + # )) + + # 保存到新的解密文件 + cls.save_image(img_decode, decoded_save_path) + + @classmethod + def open_image(cls, fp: Union[str, bytes]): + from io import BytesIO + fp = fp if isinstance(fp, str) else BytesIO(fp) + return Image.open(fp) + + @classmethod + def get_num(cls, scramble_id, aid, filename: str) -> int: + """ + 获得图片分割数 + """ + + scramble_id = int(scramble_id) + aid = int(aid) + + if aid < scramble_id: + return 0 + elif aid < JmMagicConstants.SCRAMBLE_268850: + return 10 + else: + import hashlib + x = 10 if aid < JmMagicConstants.SCRAMBLE_421926 else 8 + s = f"{aid}{filename}" # 拼接 + s = s.encode() + s = hashlib.md5(s).hexdigest() + num = ord(s[-1]) + num %= x + num = num * 2 + 2 + return num + + @classmethod + def get_num_by_url(cls, scramble_id, url) -> int: + """ + 获得图片分割数 + """ + return cls.get_num( + scramble_id, + aid=JmcomicText.parse_to_jm_id(url), + filename=of_file_name(url, True), + ) + + @classmethod + def get_num_by_detail(cls, detail: JmImageDetail) -> int: + """ + 获得图片分割数 + """ + return cls.get_num(detail.scramble_id, detail.aid, detail.img_file_name) + + +class JmCryptoTool: + """ + 禁漫加解密相关逻辑 + """ + + @classmethod + def token_and_tokenparam(cls, + ts, + ver=None, + secret=None, + ): + """ + 计算禁漫接口的请求headers的token和tokenparam + + :param ts: 时间戳 + :param ver: app版本 + :param secret: 密钥 + :return (token, tokenparam) + """ + + if ver is None: + ver = JmMagicConstants.APP_VERSION + + if secret is None: + secret = JmMagicConstants.APP_TOKEN_SECRET + + # tokenparam: 1700566805,1.6.3 + tokenparam = '{},{}'.format(ts, ver) + + # token: 81498a20feea7fbb7149c637e49702e3 + token = cls.md5hex(f'{ts}{secret}') + + return token, tokenparam + + @classmethod + def decode_resp_data(cls, + data: str, + ts, + secret=None, + ) -> str: + """ + 解密接口返回值 + + :param data: resp.json()['data'] + :param ts: 时间戳 + :param secret: 密钥 + :return: json格式的字符串 + """ + if secret is None: + secret = JmMagicConstants.APP_DATA_SECRET + + # 1. base64解码 + import base64 + data_b64 = base64.b64decode(data) + + # 2. AES-ECB解密 + key = cls.md5hex(f'{ts}{secret}').encode('utf-8') + from Crypto.Cipher import AES + data_aes = AES.new(key, AES.MODE_ECB).decrypt(data_b64) + + # 3. 移除末尾的padding + data = data_aes[:-data_aes[-1]] + + # 4. 解码为字符串 (json) + res = data.decode('utf-8') + + return res + + @classmethod + def md5hex(cls, key: str): + ExceptionTool.require_true(isinstance(key, str), 'key参数需为字符串') + + from hashlib import md5 + return md5(key.encode("utf-8")).hexdigest() diff --git a/jm/src/pixiv/__init__.py b/jm/src/pixiv/__init__.py new file mode 100755 index 0000000..bf22f6a --- /dev/null +++ b/jm/src/pixiv/__init__.py @@ -0,0 +1,8 @@ +from pixivpy3 import AppPixivAPI +api = AppPixivAPI() + +# 作品推荐 +json_result = api.illust_recommended() +print(json_result) +illust = json_result.illusts[0] +print(f">>> {illust.title}, origin url: {illust.image_urls.large}") \ No newline at end of file diff --git a/reijm-read/.editorconfig b/reijm-read/.editorconfig new file mode 100644 index 0000000..4bd3bd8 --- /dev/null +++ b/reijm-read/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/reijm-read/.gitignore b/reijm-read/.gitignore new file mode 100644 index 0000000..cf62607 --- /dev/null +++ b/reijm-read/.gitignore @@ -0,0 +1,35 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +/cypress/videos/ +/cypress/screenshots/ + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +!.vscode/settings.json +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo + +# 环境变量文件 +.env* +!.env.example +.env diff --git a/reijm-read/.idea/.gitignore b/reijm-read/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/reijm-read/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/reijm-read/.idea/inspectionProfiles/Project_Default.xml b/reijm-read/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..03d9549 --- /dev/null +++ b/reijm-read/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/reijm-read/.idea/modules.xml b/reijm-read/.idea/modules.xml new file mode 100644 index 0000000..bf02edb --- /dev/null +++ b/reijm-read/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/reijm-read/.idea/unionvue.iml b/reijm-read/.idea/unionvue.iml new file mode 100644 index 0000000..24643cc --- /dev/null +++ b/reijm-read/.idea/unionvue.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/reijm-read/.idea/vcs.xml b/reijm-read/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/reijm-read/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/reijm-read/LICENSE b/reijm-read/LICENSE new file mode 100644 index 0000000..1fa915b --- /dev/null +++ b/reijm-read/LICENSE @@ -0,0 +1,27 @@ +MIT License + +Copyright (c) 2024 Reisa + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +Additional Terms: + +1. The footer copyright notice and author attribution must be preserved. +2. Any modifications to the footer must maintain the original author's credit. +3. Commercial use requires explicit permission from the author. diff --git a/reijm-read/README.md b/reijm-read/README.md new file mode 100644 index 0000000..1e5f9d4 --- /dev/null +++ b/reijm-read/README.md @@ -0,0 +1,120 @@ +# ReisaWork0604 + +一个使用 Vue 3 + TypeScript + Vite 构建的现代化个人主页,具有博客文章展示、项目展示、联系表单等功能。 +- Reisa 改编 + +## 特性 + +- 🚀 使用 Vue 3 + TypeScript + Vite 构建 +- 🎨 支持深色模式 +- 📱 响应式设计,支持移动端 +- ⚡️ 快速加载和页面切换 +- 🔍 SEO 友好 +- 🌐 支持多语言 +- 📝 Markdown 博客支持 +- 📦 组件自动导入 +- 🎯 TypeScript 类型安全 +- 🔧 可配置的主题 + +## 技术栈 + +- Vue 3 +- TypeScript +- Vite +- Vue Router +- TailwindCSS +- PostCSS +- ESLint + Prettier +- Husky + lint-staged + +## 开发 + +```bash +# 克隆项目 +git clone https://github.com/Spaso1/ReisaPage.git + +# 安装依赖 +pnpm install + +# 启动开发服务器 +pnpm dev + +# 构建生产版本 +pnpm build + +# 预览生产构建 +pnpm preview + +# 代码格式化 +pnpm format + +# 代码检查 +pnpm lint +``` + +## 项目结构 + +``` +├── public/ # 静态资源 +├── src/ +│ ├── assets/ # 项目资源 +│ ├── components/ # 组件 +│ ├── config/ # 配置文件 +│ ├── layouts/ # 布局组件 +│ ├── pages/ # 页面 +│ ├── router/ # 路由配置 +│ ├── styles/ # 样式文件 +│ ├── types/ # TypeScript 类型 +│ ├── utils/ # 工具函数 +│ ├── App.vue # 根组件 +│ └── main.ts # 入口文件 +├── .env # 环境变量 +├── index.html # HTML 模板 +├── package.json # 项目配置 +├── tsconfig.json # TypeScript 配置 +├── vite.config.ts # Vite 配置 +└── README.md # 项目说明 +``` + +## 配置 + +### 站点配置 + +在 `src/config/site.ts` 中配置站点基本信息: + +```typescript +export const siteConfig = { + name: "Your Site Name", + description: "Your site description", + // ...其他配置 +}; +``` + +### 主题配置 + +在 `src/config/theme.ts` 中配置主题相关选项: + +```typescript +export const themeConfig = { + colors: { + primary: "#2196f3", + // ...其他颜色 + }, + // ...其他主题配置 +}; +``` + +## 部署 + +项目可以部署到任何静态网站托管服务: + +```bash +# 构建项目 +pnpm build + +# 部署 dist 目录 +``` + +## 许可证 + +[MIT](./LICENSE) diff --git a/reijm-read/dist.zip b/reijm-read/dist.zip new file mode 100644 index 0000000..190bcc7 Binary files /dev/null and b/reijm-read/dist.zip differ diff --git a/reijm-read/favicon.ico b/reijm-read/favicon.ico new file mode 100644 index 0000000..ed6ee61 Binary files /dev/null and b/reijm-read/favicon.ico differ diff --git a/reijm-read/index.html b/reijm-read/index.html new file mode 100644 index 0000000..8446fa6 --- /dev/null +++ b/reijm-read/index.html @@ -0,0 +1,58 @@ + + + + + + + + + Powered by Reisa + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + diff --git a/reijm-read/package.json b/reijm-read/package.json new file mode 100644 index 0000000..2bb33b5 --- /dev/null +++ b/reijm-read/package.json @@ -0,0 +1,71 @@ +{ + "name": "home-for-vue", + "version": "1.0.0", + "license": "MIT", + "author": { + "name": "Reisa Spasol", + "url": "https://www.godserver.cn/" + }, + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "run-p type-check \"build-only {@}\" --", + "build-only": "vite build", + "type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false", + "preview": "vite preview", + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", + "format": "prettier --write src/", + "serve": "vite run" + }, + "dependencies": { + "@emailjs/browser": "^4.4.1", + "axios": "^1.8.4", + "crypto-js": "^4.2.0", + "element-plus": "^2.9.9", + "jsqr": "^1.4.0", + "marked": "^15.0.10", + "mitt": "^3.0.1", + "otplib": "^12.0.1", + "pinia": "^2.1.7", + "qrcode.vue": "^3.6.0", + "rss-parser": "^3.13.0", + "uuid": "^11.1.0", + "vue": "^3.4.3", + "vue-router": "^4.2.5", + "vuetify": "^3.8.0-beta.0" + }, + "devDependencies": { + "@rushstack/eslint-patch": "^1.3.3", + "@tsconfig/node18": "^18.2.2", + "@types/node": "^20.17.10", + "@vitejs/plugin-vue": "^4.5.2", + "@vue/eslint-config-prettier": "^8.0.0", + "@vue/eslint-config-typescript": "^12.0.0", + "@vue/tsconfig": "^0.5.0", + "autoprefixer": "^10.4.16", + "cesium": "^1.129.0", + "eslint": "^8.49.0", + "eslint-plugin-vue": "^9.17.0", + "imagemin": "^9.0.0", + "imagemin-gifsicle": "^7.0.0", + "imagemin-mozjpeg": "^10.0.0", + "imagemin-optipng": "^8.0.0", + "imagemin-pngquant": "^10.0.0", + "imagemin-svgo": "^11.0.1", + "noise": "~0.0.0", + "npm-run-all": "^4.1.5", + "postcss": "^8.4.32", + "prettier": "^3.0.3", + "sharp": "^0.33.5", + "tailwindcss": "^3.4.0", + "terser": "^5.37.0", + "typescript": "~5.3.0", + "vite": "^5.4.19", + "vite-plugin-cesium": "^1.2.23", + "vite-plugin-compression": "^0.5.1", + "vite-plugin-image-optimizer": "^1.1.8", + "vite-plugin-imagemin": "^0.6.1", + "vue-tsc": "^1.8.25" + } +} diff --git a/reijm-read/pnpm-lock.yaml b/reijm-read/pnpm-lock.yaml new file mode 100644 index 0000000..e109515 --- /dev/null +++ b/reijm-read/pnpm-lock.yaml @@ -0,0 +1,8137 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@emailjs/browser': + specifier: ^4.4.1 + version: 4.4.1 + axios: + specifier: ^1.8.4 + version: 1.10.0 + chart: + specifier: ^0.1.2 + version: 0.1.2 + chart.js: + specifier: ^4.4.9 + version: 4.5.0 + crypto-js: + specifier: ^4.2.0 + version: 4.2.0 + element-plus: + specifier: ^2.9.9 + version: 2.10.4(vue@3.5.13(typescript@5.3.3)) + jsqr: + specifier: ^1.4.0 + version: 1.4.0 + marked: + specifier: ^15.0.10 + version: 15.0.12 + mitt: + specifier: ^3.0.1 + version: 3.0.1 + otplib: + specifier: ^12.0.1 + version: 12.0.1 + pinia: + specifier: ^2.1.7 + version: 2.3.0(typescript@5.3.3)(vue@3.5.13(typescript@5.3.3)) + qrcode.vue: + specifier: ^3.6.0 + version: 3.6.0(vue@3.5.13(typescript@5.3.3)) + rss-parser: + specifier: ^3.13.0 + version: 3.13.0 + uuid: + specifier: ^11.1.0 + version: 11.1.0 + vue: + specifier: ^3.4.3 + version: 3.5.13(typescript@5.3.3) + vue-router: + specifier: ^4.2.5 + version: 4.5.0(vue@3.5.13(typescript@5.3.3)) + vuetify: + specifier: ^3.8.0-beta.0 + version: 3.9.0(typescript@5.3.3)(vue@3.5.13(typescript@5.3.3)) + devDependencies: + '@rushstack/eslint-patch': + specifier: ^1.3.3 + version: 1.10.4 + '@tsconfig/node18': + specifier: ^18.2.2 + version: 18.2.4 + '@types/node': + specifier: ^20.17.10 + version: 20.17.10 + '@vitejs/plugin-vue': + specifier: ^4.5.2 + version: 4.6.2(vite@5.4.19(@types/node@20.17.10)(terser@5.37.0))(vue@3.5.13(typescript@5.3.3)) + '@vue/eslint-config-prettier': + specifier: ^8.0.0 + version: 8.0.0(eslint@8.57.1)(prettier@3.4.2) + '@vue/eslint-config-typescript': + specifier: ^12.0.0 + version: 12.0.0(eslint-plugin-vue@9.32.0(eslint@8.57.1))(eslint@8.57.1)(typescript@5.3.3) + '@vue/tsconfig': + specifier: ^0.5.0 + version: 0.5.1 + autoprefixer: + specifier: ^10.4.16 + version: 10.4.20(postcss@8.4.49) + cesium: + specifier: ^1.129.0 + version: 1.131.0 + eslint: + specifier: ^8.49.0 + version: 8.57.1 + eslint-plugin-vue: + specifier: ^9.17.0 + version: 9.32.0(eslint@8.57.1) + imagemin: + specifier: ^9.0.0 + version: 9.0.0 + imagemin-gifsicle: + specifier: ^7.0.0 + version: 7.0.0 + imagemin-mozjpeg: + specifier: ^10.0.0 + version: 10.0.0 + imagemin-optipng: + specifier: ^8.0.0 + version: 8.0.0 + imagemin-pngquant: + specifier: ^10.0.0 + version: 10.0.0 + imagemin-svgo: + specifier: ^11.0.1 + version: 11.0.1 + noise: + specifier: ~0.0.0 + version: 0.0.0 + npm-run-all: + specifier: ^4.1.5 + version: 4.1.5 + postcss: + specifier: ^8.4.32 + version: 8.4.49 + prettier: + specifier: ^3.0.3 + version: 3.4.2 + sharp: + specifier: ^0.33.5 + version: 0.33.5 + tailwindcss: + specifier: ^3.4.0 + version: 3.4.17 + terser: + specifier: ^5.37.0 + version: 5.37.0 + typescript: + specifier: ~5.3.0 + version: 5.3.3 + vite: + specifier: ^5.4.19 + version: 5.4.19(@types/node@20.17.10)(terser@5.37.0) + vite-plugin-cesium: + specifier: ^1.2.23 + version: 1.2.23(cesium@1.131.0)(rollup@4.29.1)(vite@5.4.19(@types/node@20.17.10)(terser@5.37.0)) + vite-plugin-compression: + specifier: ^0.5.1 + version: 0.5.1(vite@5.4.19(@types/node@20.17.10)(terser@5.37.0)) + vite-plugin-image-optimizer: + specifier: ^1.1.8 + version: 1.1.8(vite@5.4.19(@types/node@20.17.10)(terser@5.37.0)) + vite-plugin-imagemin: + specifier: ^0.6.1 + version: 0.6.1(vite@5.4.19(@types/node@20.17.10)(terser@5.37.0)) + vue-tsc: + specifier: ^1.8.25 + version: 1.8.27(typescript@5.3.3) + +packages: + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.26.3': + resolution: {integrity: sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.26.3': + resolution: {integrity: sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==} + engines: {node: '>=6.9.0'} + + '@cesium/engine@18.3.0': + resolution: {integrity: sha512-ILNA1a+RX2gNsQGyF2zq27Ucaj5UMj19paYoJbKxX/U0QtxLuFUhJ2yg6wr2eBtC3X3kd475o7kAx0LpfCZxpg==} + engines: {node: '>=20.19.0'} + + '@cesium/wasm-splats@0.1.0-alpha.2': + resolution: {integrity: sha512-t9pMkknv31hhIbLpMa8yPvmqfpvs5UkUjgqlQv9SeO8VerCXOYnyP8/486BDaFrztM0A7FMbRjsXtNeKvqQghA==} + + '@cesium/widgets@12.3.0': + resolution: {integrity: sha512-5Aa54lCrdywogY1ypOwRmRrb72LVrPvrOzOrcYeQ50DDZSs5hCxVXWecYYsq3qoeK0buA9wP/5TBWE7RlsV7OA==} + engines: {node: '>=20.19.0'} + + '@ctrl/tinycolor@3.6.1': + resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==} + engines: {node: '>=10'} + + '@element-plus/icons-vue@2.3.1': + resolution: {integrity: sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==} + peerDependencies: + vue: ^3.2.0 + + '@emailjs/browser@4.4.1': + resolution: {integrity: sha512-DGSlP9sPvyFba3to2A50kDtZ+pXVp/0rhmqs2LmbMS3I5J8FSOgLwzY2Xb4qfKlOVHh29EAutLYwe5yuEZmEFg==} + engines: {node: '>=14.0.0'} + + '@emnapi/runtime@1.3.1': + resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.14.54': + resolution: {integrity: sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.4.1': + resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/eslintrc@2.1.4': + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/js@8.57.1': + resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@floating-ui/core@1.7.2': + resolution: {integrity: sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==} + + '@floating-ui/dom@1.7.2': + resolution: {integrity: sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==} + + '@floating-ui/utils@0.2.10': + resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + + '@humanwhocodes/config-array@0.13.0': + resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/object-schema@2.0.3': + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead + + '@img/sharp-darwin-arm64@0.33.5': + resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.33.5': + resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.0.4': + resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.0.4': + resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.0.4': + resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.0.5': + resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.0.4': + resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.0.4': + resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.33.5': + resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.33.5': + resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.33.5': + resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.33.5': + resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.33.5': + resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.33.5': + resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.33.5': + resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-ia32@0.33.5': + resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.33.5': + resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.6': + resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@kurkle/color@0.3.4': + resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@otplib/core@12.0.1': + resolution: {integrity: sha512-4sGntwbA/AC+SbPhbsziRiD+jNDdIzsZ3JUyfZwjtKyc/wufl1pnSIaG4Uqx8ymPagujub0o92kgBnB89cuAMA==} + + '@otplib/plugin-crypto@12.0.1': + resolution: {integrity: sha512-qPuhN3QrT7ZZLcLCyKOSNhuijUi9G5guMRVrxq63r9YNOxxQjPm59gVxLM+7xGnHnM6cimY57tuKsjK7y9LM1g==} + + '@otplib/plugin-thirty-two@12.0.1': + resolution: {integrity: sha512-MtT+uqRso909UkbrrYpJ6XFjj9D+x2Py7KjTO9JDPhL0bJUYVu5kFP4TFZW4NFAywrAtFRxOVY261u0qwb93gA==} + + '@otplib/preset-default@12.0.1': + resolution: {integrity: sha512-xf1v9oOJRyXfluBhMdpOkr+bsE+Irt+0D5uHtvg6x1eosfmHCsCC6ej/m7FXiWqdo0+ZUI6xSKDhJwc8yfiOPQ==} + + '@otplib/preset-v11@12.0.1': + resolution: {integrity: sha512-9hSetMI7ECqbFiKICrNa4w70deTUfArtwXykPUvSHWOdzOlfa9ajglu7mNCntlvxycTiOAXkQGwjQCzzDEMRMg==} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@pkgr/core@0.1.1': + resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.4': + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + + '@protobufjs/eventemitter@1.1.0': + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + + '@protobufjs/fetch@1.1.0': + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/inquire@1.1.0': + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.0': + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + + '@rollup/pluginutils@4.2.1': + resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} + engines: {node: '>= 8.0.0'} + + '@rollup/rollup-android-arm-eabi@4.29.1': + resolution: {integrity: sha512-ssKhA8RNltTZLpG6/QNkCSge+7mBQGUqJRisZ2MDQcEGaK93QESEgWK2iOpIDZ7k9zPVkG5AS3ksvD5ZWxmItw==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.29.1': + resolution: {integrity: sha512-CaRfrV0cd+NIIcVVN/jx+hVLN+VRqnuzLRmfmlzpOzB87ajixsN/+9L5xNmkaUUvEbI5BmIKS+XTwXsHEb65Ew==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.29.1': + resolution: {integrity: sha512-2ORr7T31Y0Mnk6qNuwtyNmy14MunTAMx06VAPI6/Ju52W10zk1i7i5U3vlDRWjhOI5quBcrvhkCHyF76bI7kEw==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.29.1': + resolution: {integrity: sha512-j/Ej1oanzPjmN0tirRd5K2/nncAhS9W6ICzgxV+9Y5ZsP0hiGhHJXZ2JQ53iSSjj8m6cRY6oB1GMzNn2EUt6Ng==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.29.1': + resolution: {integrity: sha512-91C//G6Dm/cv724tpt7nTyP+JdN12iqeXGFM1SqnljCmi5yTXriH7B1r8AD9dAZByHpKAumqP1Qy2vVNIdLZqw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.29.1': + resolution: {integrity: sha512-hEioiEQ9Dec2nIRoeHUP6hr1PSkXzQaCUyqBDQ9I9ik4gCXQZjJMIVzoNLBRGet+hIUb3CISMh9KXuCcWVW/8w==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.29.1': + resolution: {integrity: sha512-Py5vFd5HWYN9zxBv3WMrLAXY3yYJ6Q/aVERoeUFwiDGiMOWsMs7FokXihSOaT/PMWUty/Pj60XDQndK3eAfE6A==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.29.1': + resolution: {integrity: sha512-RiWpGgbayf7LUcuSNIbahr0ys2YnEERD4gYdISA06wa0i8RALrnzflh9Wxii7zQJEB2/Eh74dX4y/sHKLWp5uQ==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.29.1': + resolution: {integrity: sha512-Z80O+taYxTQITWMjm/YqNoe9d10OX6kDh8X5/rFCMuPqsKsSyDilvfg+vd3iXIqtfmp+cnfL1UrYirkaF8SBZA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.29.1': + resolution: {integrity: sha512-fOHRtF9gahwJk3QVp01a/GqS4hBEZCV1oKglVVq13kcK3NeVlS4BwIFzOHDbmKzt3i0OuHG4zfRP0YoG5OF/rA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loongarch64-gnu@4.29.1': + resolution: {integrity: sha512-5a7q3tnlbcg0OodyxcAdrrCxFi0DgXJSoOuidFUzHZ2GixZXQs6Tc3CHmlvqKAmOs5eRde+JJxeIf9DonkmYkw==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-powerpc64le-gnu@4.29.1': + resolution: {integrity: sha512-9b4Mg5Yfz6mRnlSPIdROcfw1BU22FQxmfjlp/CShWwO3LilKQuMISMTtAu/bxmmrE6A902W2cZJuzx8+gJ8e9w==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-gnu@4.29.1': + resolution: {integrity: sha512-G5pn0NChlbRM8OJWpJFMX4/i8OEU538uiSv0P6roZcbpe/WfhEO+AT8SHVKfp8qhDQzaz7Q+1/ixMy7hBRidnQ==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-s390x-gnu@4.29.1': + resolution: {integrity: sha512-WM9lIkNdkhVwiArmLxFXpWndFGuOka4oJOZh8EP3Vb8q5lzdSCBuhjavJsw68Q9AKDGeOOIHYzYm4ZFvmWez5g==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.29.1': + resolution: {integrity: sha512-87xYCwb0cPGZFoGiErT1eDcssByaLX4fc0z2nRM6eMtV9njAfEE6OW3UniAoDhX4Iq5xQVpE6qO9aJbCFumKYQ==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.29.1': + resolution: {integrity: sha512-xufkSNppNOdVRCEC4WKvlR1FBDyqCSCpQeMMgv9ZyXqqtKBfkw1yfGMTUTs9Qsl6WQbJnsGboWCp7pJGkeMhKA==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-win32-arm64-msvc@4.29.1': + resolution: {integrity: sha512-F2OiJ42m77lSkizZQLuC+jiZ2cgueWQL5YC9tjo3AgaEw+KJmVxHGSyQfDUoYR9cci0lAywv2Clmckzulcq6ig==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.29.1': + resolution: {integrity: sha512-rYRe5S0FcjlOBZQHgbTKNrqxCBUmgDJem/VQTCcTnA2KCabYSWQDrytOzX7avb79cAAweNmMUb/Zw18RNd4mng==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.29.1': + resolution: {integrity: sha512-+10CMg9vt1MoHj6x1pxyjPSMjHTIlqs8/tBztXvPAx24SKs9jwVnKqHJumlH/IzhaPUaj3T6T6wfZr8okdXaIg==} + cpu: [x64] + os: [win32] + + '@rushstack/eslint-patch@1.10.4': + resolution: {integrity: sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==} + + '@sec-ant/readable-stream@0.4.1': + resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + + '@sindresorhus/is@0.7.0': + resolution: {integrity: sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==} + engines: {node: '>=4'} + + '@sindresorhus/is@6.3.1': + resolution: {integrity: sha512-FX4MfcifwJyFOI2lPoX7PQxCqx8BG1HCho7WdiXwpEQx1Ycij0JxkfYtGK7yqNScrZGSlt6RE6sw8QYoH7eKnQ==} + engines: {node: '>=16'} + + '@sindresorhus/merge-streams@2.3.0': + resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} + engines: {node: '>=18'} + + '@spz-loader/core@0.1.0': + resolution: {integrity: sha512-atqn/DLy0xNkqRgz3l/5CD12y1M44JdjTmAFQYDKvzf0pIyj+NlJ/PeCRlSJQfXmZ2JndNOCpcGOFVldANf/EA==} + engines: {node: '>=16', pnpm: '>=8'} + + '@sxzz/popperjs-es@2.11.7': + resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==} + + '@tokenizer/token@0.3.0': + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + + '@trysound/sax@0.2.0': + resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} + engines: {node: '>=10.13.0'} + + '@tsconfig/node18@18.2.4': + resolution: {integrity: sha512-5xxU8vVs9/FNcvm3gE07fPbn9tl6tqGGWA9tSlwsUEkBxtRnTsNmwrV8gasZ9F/EobaSv9+nu8AxUKccw77JpQ==} + + '@tweenjs/tween.js@25.0.0': + resolution: {integrity: sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==} + + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + + '@types/glob@7.2.0': + resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} + + '@types/imagemin-gifsicle@7.0.4': + resolution: {integrity: sha512-ZghMBd/Jgqg5utTJNPmvf6DkuHzMhscJ8vgf/7MUGCpO+G+cLrhYltL+5d+h3A1B4W73S2SrmJZ1jS5LACpX+A==} + + '@types/imagemin-jpegtran@5.0.4': + resolution: {integrity: sha512-PSMxOeJa8q94Y+qx8Yriw+qj1+vH5xWpvar63o6SGO0Xi5RlKuwHHfJmN2GRUngPrlhe394jOUmpVq8jQlVmFA==} + + '@types/imagemin-mozjpeg@8.0.4': + resolution: {integrity: sha512-ZCAxV8SYJB8ehwHpnbRpHjg5Wc4HcyuAMiDhXbkgC7gujDoOTyHO3dhDkUtZ1oK1DLBRZapqG9etdLVhUml7yQ==} + + '@types/imagemin-optipng@5.2.4': + resolution: {integrity: sha512-mvKnDMC8eCYZetAQudjs1DbgpR84WhsTx1wgvdiXnpuUEti3oJ+MaMYBRWPY0JlQ4+y4TXKOfa7+LOuT8daegQ==} + + '@types/imagemin-svgo@10.0.5': + resolution: {integrity: sha512-9U2Rf7vWBHeqJvzmWNP3vYAKqR0208QqQ9Mkrq9OLIL5AeoF/dRVRou6iUYCufBSim57BpBpCJhZLrTgfS3k1g==} + + '@types/imagemin-webp@7.0.3': + resolution: {integrity: sha512-C2/EMohS4bzsvY5VJvdzHFdcfmnZoui54DmM/9bFtK57/CgGmKkc+p6n49euPGmMFDDvwm4yVl60nwxcZOmH5A==} + + '@types/imagemin@7.0.1': + resolution: {integrity: sha512-xEn5+M3lDBtI3JxLy6eU3ksoVurygnlG7OYhTqJfGGP4PcvYnfn+IABCmMve7ziM/SneHDm5xgJFKC8hCYPicw==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/keyv@3.1.4': + resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} + + '@types/lodash-es@4.17.12': + resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} + + '@types/lodash@4.17.20': + resolution: {integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==} + + '@types/minimatch@5.1.2': + resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} + + '@types/node@20.17.10': + resolution: {integrity: sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==} + + '@types/responselike@1.0.3': + resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} + + '@types/semver@7.5.8': + resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} + + '@types/svgo@2.6.4': + resolution: {integrity: sha512-l4cmyPEckf8moNYHdJ+4wkHvFxjyW6ulm9l4YGaOxeyBWPhBOT0gvni1InpFPdzx1dKf/2s62qGITwxNWnPQng==} + + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + + '@types/web-bluetooth@0.0.16': + resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==} + + '@typescript-eslint/eslint-plugin@6.21.0': + resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@6.21.0': + resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@6.21.0': + resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@typescript-eslint/type-utils@6.21.0': + resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@6.21.0': + resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@typescript-eslint/typescript-estree@6.21.0': + resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@6.21.0': + resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + + '@typescript-eslint/visitor-keys@6.21.0': + resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@ungap/structured-clone@1.2.1': + resolution: {integrity: sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==} + + '@vitejs/plugin-vue@4.6.2': + resolution: {integrity: sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.0.0 || ^5.0.0 + vue: ^3.2.25 + + '@volar/language-core@1.11.1': + resolution: {integrity: sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==} + + '@volar/source-map@1.11.1': + resolution: {integrity: sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==} + + '@volar/typescript@1.11.1': + resolution: {integrity: sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==} + + '@vue/compiler-core@3.5.13': + resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==} + + '@vue/compiler-dom@3.5.13': + resolution: {integrity: sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==} + + '@vue/compiler-sfc@3.5.13': + resolution: {integrity: sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==} + + '@vue/compiler-ssr@3.5.13': + resolution: {integrity: sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==} + + '@vue/devtools-api@6.6.4': + resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} + + '@vue/eslint-config-prettier@8.0.0': + resolution: {integrity: sha512-55dPqtC4PM/yBjhAr+yEw6+7KzzdkBuLmnhBrDfp4I48+wy+Giqqj9yUr5T2uD/BkBROjjmqnLZmXRdOx/VtQg==} + peerDependencies: + eslint: '>= 8.0.0' + prettier: '>= 3.0.0' + + '@vue/eslint-config-typescript@12.0.0': + resolution: {integrity: sha512-StxLFet2Qe97T8+7L8pGlhYBBr8Eg05LPuTDVopQV6il+SK6qqom59BA/rcFipUef2jD8P2X44Vd8tMFytfvlg==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 + eslint-plugin-vue: ^9.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@vue/language-core@1.8.27': + resolution: {integrity: sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@vue/reactivity@3.5.13': + resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==} + + '@vue/runtime-core@3.5.13': + resolution: {integrity: sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==} + + '@vue/runtime-dom@3.5.13': + resolution: {integrity: sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==} + + '@vue/server-renderer@3.5.13': + resolution: {integrity: sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==} + peerDependencies: + vue: 3.5.13 + + '@vue/shared@3.5.13': + resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==} + + '@vue/tsconfig@0.5.1': + resolution: {integrity: sha512-VcZK7MvpjuTPx2w6blwnwZAu5/LgBUtejFOi3pPGQFXQN5Ela03FUtd2Qtg4yWGGissVL0dr6Ro1LfOFh+PCuQ==} + + '@vueuse/core@9.13.0': + resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==} + + '@vueuse/metadata@9.13.0': + resolution: {integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==} + + '@vueuse/shared@9.13.0': + resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==} + + '@zip.js/zip.js@2.7.63': + resolution: {integrity: sha512-B02i6QDMUQ4c+5F9LmliBGA+jFsiEHIlF0eLQ6rWLaQOD3YwI6vyWwGkVCNJnVVguE2xYyr9fAwSD/3valm1/Q==} + engines: {bun: '>=0.7.0', deno: '>=1.0.0', node: '>=16.5.0'} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-regex@2.1.1: + resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==} + engines: {node: '>=0.10.0'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + + ansi-styles@2.2.1: + resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==} + engines: {node: '>=0.10.0'} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arch@2.2.0: + resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==} + + archive-type@4.0.0: + resolution: {integrity: sha512-zV4Ky0v1F8dBrdYElwTvQhweQ0P7Kwc1aluqJsYtOBP01jXcWCyW2IEfI1YiqsG+Iy7ZR+o5LF1N+PGECBxHWA==} + engines: {node: '>=4'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-find-index@1.0.2: + resolution: {integrity: sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==} + engines: {node: '>=0.10.0'} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + async-validator@4.2.5: + resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + at-least-node@1.0.0: + resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} + engines: {node: '>= 4.0.0'} + + autolinker@4.1.5: + resolution: {integrity: sha512-vEfYZPmvVOIuE567XBVCsx8SBgOYtjB2+S1iAaJ+HgH+DNjAcrHem2hmAeC9yaNGWayicv4yR+9UaJlkF3pvtw==} + engines: {pnpm: '>=10.10.0'} + + autoprefixer@10.4.20: + resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + axios@1.10.0: + resolution: {integrity: sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + bin-build@3.0.0: + resolution: {integrity: sha512-jcUOof71/TNAI2uM5uoUaDq2ePcVBQ3R/qhxAz1rX7UfvduAL/RXD3jXzvn8cVcDJdGVkiR1shal3OH0ImpuhA==} + engines: {node: '>=4'} + + bin-check@4.1.0: + resolution: {integrity: sha512-b6weQyEUKsDGFlACWSIOfveEnImkJyK/FGW6FAG42loyoquvjdtOIqO6yBFzHyqyVVhNgNkQxxx09SFLK28YnA==} + engines: {node: '>=4'} + + bin-version-check@4.0.0: + resolution: {integrity: sha512-sR631OrhC+1f8Cvs8WyVWOA33Y8tgwjETNPyyD/myRBXLkfS/vl74FmH/lFcRl9KY3zwGh7jFhvyk9vV3/3ilQ==} + engines: {node: '>=6'} + + bin-version@3.1.0: + resolution: {integrity: sha512-Mkfm4iE1VFt4xd4vH+gx+0/71esbfus2LsnCGe8Pi4mndSPyT+NGES/Eg99jx8/lUGWfu3z2yuB/bt5UB+iVbQ==} + engines: {node: '>=6'} + + bin-wrapper@4.1.0: + resolution: {integrity: sha512-hfRmo7hWIXPkbpi0ZltboCMVrU+0ClXR/JgbCKKjlDjQf6igXa7OwdqNcFWQZPZTgiY7ZpzE3+LjjkLiTN2T7Q==} + engines: {node: '>=6'} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + bitmap-sdf@1.0.4: + resolution: {integrity: sha512-1G3U4n5JE6RAiALMxu0p1XmeZkTeCwGKykzsLTCqVzfSDaN6S7fKnkIkfejogz+iwqBWc0UYAIKnKHNN7pSfDg==} + + bl@1.2.3: + resolution: {integrity: sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.24.3: + resolution: {integrity: sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer-alloc-unsafe@1.1.0: + resolution: {integrity: sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==} + + buffer-alloc@1.2.0: + resolution: {integrity: sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==} + + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + + buffer-fill@1.0.0: + resolution: {integrity: sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + cacheable-request@2.1.4: + resolution: {integrity: sha512-vag0O2LKZ/najSoUwDbVlnlCFvhBE/7mGTY2B5FgCBDcRD+oVV1HYTOwM6JZfMg/hIcM6IwnTZ1uQQL5/X3xIQ==} + + call-bind-apply-helpers@1.0.1: + resolution: {integrity: sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==} + engines: {node: '>= 0.4'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.3: + resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + callsites@4.2.0: + resolution: {integrity: sha512-kfzR4zzQtAE9PC7CzZsjl3aBNbXWuXiSeOCdLcPpBfGW8YuCqQHcRPFDbr/BPVmd3EEPVpuFzLyuT/cUhPr4OQ==} + engines: {node: '>=12.20'} + + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + camelcase-keys@2.1.0: + resolution: {integrity: sha512-bA/Z/DERHKqoEOrp+qeGKw1QlvEQkGZSc0XaY6VnTxZr+Kv1G5zFwttpjv8qxZ/sBPT4nthwZaAcsAZTJlSKXQ==} + engines: {node: '>=0.10.0'} + + camelcase@2.1.1: + resolution: {integrity: sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==} + engines: {node: '>=0.10.0'} + + caniuse-lite@1.0.30001690: + resolution: {integrity: sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==} + + caw@2.0.1: + resolution: {integrity: sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==} + engines: {node: '>=4'} + + cesium@1.131.0: + resolution: {integrity: sha512-4OVlpds2TR6elsHLfVAp5FOqxjNbvKA7Ff5n3yuwDBFKgYgc6JErbpBSjY31pBI2ZJSWhKLknCNjrTR8FlVTFg==} + engines: {node: '>=20.19.0'} + + chalk@1.1.3: + resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==} + engines: {node: '>=0.10.0'} + + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + change-file-extension@0.1.1: + resolution: {integrity: sha512-lB0j9teu8JtDPDHRfU8pNH33w4wMu5bOaKoT4PxH+AKugBrIfpiJMTTKIm0TErNeJPkeQEgvH31YpccTwOKPRg==} + engines: {node: '>=18'} + + chart.js@4.5.0: + resolution: {integrity: sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==} + engines: {pnpm: '>=8'} + + chart@0.1.2: + resolution: {integrity: sha512-MSiVzAd3qUEXv54k9KGe1oIoC7WG32W9wtjpovlTGlzo2ue/fRiHf7kJAK1zmD736jH/0fVWNCQLh41btfAEZQ==} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + clone-response@1.0.2: + resolution: {integrity: sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q==} + + color-convert@0.2.1: + resolution: {integrity: sha512-FWbwpCgyRV41Vml0iKU9UmL0dVTKORnm7ZC8h8cdfvutk2bU7ZcMLtSleggScK/IpUVXILg9Pw86LhPUQyTaVg==} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + computeds@0.0.1: + resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + config-chain@1.1.13: + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + + console-stream@0.1.1: + resolution: {integrity: sha512-QC/8l9e6ofi6nqZ5PawlDgzmMw3OxIXtvolBzap/F4UDBJlDaZRSNbL/lb41C29FcbSJncBFlJFj2WJoNyZRfQ==} + + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + + convert-hrtime@5.0.0: + resolution: {integrity: sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==} + engines: {node: '>=12'} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + cross-spawn@5.1.0: + resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} + + cross-spawn@6.0.6: + resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==} + engines: {node: '>=4.8'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + crypto-js@4.2.0: + resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + + css-select@4.3.0: + resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} + + css-select@5.1.0: + resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} + + css-tree@1.1.3: + resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} + engines: {node: '>=8.0.0'} + + css-tree@2.2.1: + resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + + css-tree@2.3.1: + resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css-what@6.1.0: + resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} + engines: {node: '>= 6'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csso@4.2.0: + resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==} + engines: {node: '>=8.0.0'} + + csso@5.0.5: + resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + currently-unhandled@0.4.1: + resolution: {integrity: sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==} + engines: {node: '>=0.10.0'} + + cwebp-bin@6.1.2: + resolution: {integrity: sha512-NLEZ/BVAl9g426hwUX/qrQ7b/EfQH7BS1tr+CzPo2EgDQbcdzmUVE+fIfsi64lsL638lWgzTEViMAL4pxV1GOg==} + engines: {node: '>=10'} + hasBin: true + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + dayjs@1.11.13: + resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + + de-indent@1.0.2: + resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + + decode-uri-component@0.2.2: + resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} + engines: {node: '>=0.10'} + + decompress-response@3.3.0: + resolution: {integrity: sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==} + engines: {node: '>=4'} + + decompress-tar@4.1.1: + resolution: {integrity: sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==} + engines: {node: '>=4'} + + decompress-tarbz2@4.1.1: + resolution: {integrity: sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==} + engines: {node: '>=4'} + + decompress-targz@4.1.1: + resolution: {integrity: sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==} + engines: {node: '>=4'} + + decompress-unzip@4.0.1: + resolution: {integrity: sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==} + engines: {node: '>=4'} + + decompress@4.2.1: + resolution: {integrity: sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==} + engines: {node: '>=4'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + engines: {node: '>=8'} + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + dom-serializer@1.4.1: + resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} + + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@4.3.1: + resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} + engines: {node: '>= 4'} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + dompurify@3.2.6: + resolution: {integrity: sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==} + + domutils@2.8.0: + resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} + + domutils@3.1.0: + resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} + + dot-prop@8.0.2: + resolution: {integrity: sha512-xaBe6ZT4DHPkg0k4Ytbvn5xoxgpG0jOS1dYxSOwAHPuNLjP3/OzN0gH55SrLqpx8cBfSaVt91lXYkApjb+nYdQ==} + engines: {node: '>=16'} + + download@6.2.5: + resolution: {integrity: sha512-DpO9K1sXAST8Cpzb7kmEhogJxymyVUd5qz/vCOSyvwtp2Klj2XcDt5YUuasgxka44SxF0q5RriKIwJmQHG2AuA==} + engines: {node: '>=4'} + + download@7.1.0: + resolution: {integrity: sha512-xqnBTVd/E+GxJVrX5/eUJiLYjCGPwMpdL+jGhGU57BvtcA7wwhtHVbXBeUk51kOpW3S7Jn3BQbN9Q1R1Km2qDQ==} + engines: {node: '>=6'} + + draco3d@1.5.7: + resolution: {integrity: sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + duplexer3@0.1.5: + resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==} + + earcut@3.0.2: + resolution: {integrity: sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + electron-to-chromium@1.5.75: + resolution: {integrity: sha512-Lf3++DumRE/QmweGjU+ZcKqQ+3bKkU/qjaKYhIJKEOhgIO9Xs6IiAQFkfFoj+RhgDk4LUeNsLo6plExHqSyu6Q==} + + element-plus@2.10.4: + resolution: {integrity: sha512-UD4elWHrCnp1xlPhbXmVcaKFLCRaRAY6WWRwemGfGW3ceIjXm9fSYc9RNH3AiOEA6Ds1p9ZvhCs76CR9J8Vd+A==} + peerDependencies: + vue: ^3.2.0 + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + + entities@2.2.0: + resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + + es-abstract@1.23.7: + resolution: {integrity: sha512-OygGC8kIcDhXX+6yAZRGLqwi2CmEXCbLQixeGUgYeR+Qwlppqmo7DIDr8XibtEBZp+fJcoYpoatp5qwLMEdcqQ==} + engines: {node: '>= 0.4'} + + es-abstract@1.24.0: + resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.0.0: + resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.0.3: + resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + + esbuild-android-64@0.14.54: + resolution: {integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + esbuild-android-arm64@0.14.54: + resolution: {integrity: sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + esbuild-darwin-64@0.14.54: + resolution: {integrity: sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + esbuild-darwin-arm64@0.14.54: + resolution: {integrity: sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + esbuild-freebsd-64@0.14.54: + resolution: {integrity: sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + esbuild-freebsd-arm64@0.14.54: + resolution: {integrity: sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + esbuild-linux-32@0.14.54: + resolution: {integrity: sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + esbuild-linux-64@0.14.54: + resolution: {integrity: sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + esbuild-linux-arm64@0.14.54: + resolution: {integrity: sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + esbuild-linux-arm@0.14.54: + resolution: {integrity: sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + esbuild-linux-mips64le@0.14.54: + resolution: {integrity: sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + esbuild-linux-ppc64le@0.14.54: + resolution: {integrity: sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + esbuild-linux-riscv64@0.14.54: + resolution: {integrity: sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + esbuild-linux-s390x@0.14.54: + resolution: {integrity: sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + esbuild-netbsd-64@0.14.54: + resolution: {integrity: sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + esbuild-openbsd-64@0.14.54: + resolution: {integrity: sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + esbuild-sunos-64@0.14.54: + resolution: {integrity: sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + esbuild-windows-32@0.14.54: + resolution: {integrity: sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + esbuild-windows-64@0.14.54: + resolution: {integrity: sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + esbuild-windows-arm64@0.14.54: + resolution: {integrity: sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + esbuild@0.14.54: + resolution: {integrity: sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-prettier@8.10.0: + resolution: {integrity: sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-plugin-prettier@5.2.1: + resolution: {integrity: sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '*' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + + eslint-plugin-vue@9.32.0: + resolution: {integrity: sha512-b/Y05HYmnB/32wqVcjxjHZzNpwxj1onBOvqW89W+V+XNG1dRuaFbNd3vT9CLbr2LXjEoq+3vn8DanWf7XU22Ug==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint@8.57.1: + resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + hasBin: true + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + exec-buffer@3.2.0: + resolution: {integrity: sha512-wsiD+2Tp6BWHoVv3B+5Dcx6E7u5zky+hUwOHjuH2hKSLR3dvRmX8fk8UD8uqQixHs4Wk6eDmiegVrMPjKj7wpA==} + engines: {node: '>=4'} + + execa@0.7.0: + resolution: {integrity: sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw==} + engines: {node: '>=4'} + + execa@1.0.0: + resolution: {integrity: sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==} + engines: {node: '>=6'} + + execa@4.1.0: + resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} + engines: {node: '>=10'} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + execa@6.1.0: + resolution: {integrity: sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + + executable@4.1.1: + resolution: {integrity: sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==} + engines: {node: '>=4'} + + ext-list@2.2.2: + resolution: {integrity: sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==} + engines: {node: '>=0.10.0'} + + ext-name@5.0.0: + resolution: {integrity: sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==} + engines: {node: '>=4'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + + fast-equals@5.0.1: + resolution: {integrity: sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==} + engines: {node: '>=6.0.0'} + + fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-xml-parser@4.5.1: + resolution: {integrity: sha512-y655CeyUQ+jj7KBbYMc4FG01V8ZQqjN+gDYGJ50RtfsUB8iG9AmwmwoAgeKLJdmueKKMrH1RJ7yXHTSoczdv5w==} + hasBin: true + + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + + fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + + figures@1.7.0: + resolution: {integrity: sha512-UxKlfCRuCBxSXU4C6t9scbDyWZ4VlaFFdojKtzJuSkuOBQ5CNFum+zZXFwHjo+CxBC1t6zlYPgHIgFjL8ggoEQ==} + engines: {node: '>=0.10.0'} + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + + file-type@10.11.0: + resolution: {integrity: sha512-uzk64HRpUZyTGZtVuvrjP0FYxzQrBf4rojot6J65YMEbwBLB0CWm0CLojVpwpmFmxcE/lkvYICgfcGozbBq6rw==} + engines: {node: '>=6'} + + file-type@12.4.2: + resolution: {integrity: sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg==} + engines: {node: '>=8'} + + file-type@19.6.0: + resolution: {integrity: sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ==} + engines: {node: '>=18'} + + file-type@3.9.0: + resolution: {integrity: sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==} + engines: {node: '>=0.10.0'} + + file-type@4.4.0: + resolution: {integrity: sha512-f2UbFQEk7LXgWpi5ntcO86OeA/cC80fuDDDaX/fZ2ZGel+AF7leRQqBBW1eJNiiQkrZlAoM6P+VYP5P6bOlDEQ==} + engines: {node: '>=4'} + + file-type@5.2.0: + resolution: {integrity: sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==} + engines: {node: '>=4'} + + file-type@6.2.0: + resolution: {integrity: sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==} + engines: {node: '>=4'} + + file-type@8.1.0: + resolution: {integrity: sha512-qyQ0pzAy78gVoJsmYeNgl8uH8yKhr1lVhW7JbzJmnlRi0I4R2eEDEJZVKG8agpDnLpacwNbDhLNG/LMdxHD2YQ==} + engines: {node: '>=6'} + + filename-reserved-regex@2.0.0: + resolution: {integrity: sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==} + engines: {node: '>=4'} + + filenamify@2.1.0: + resolution: {integrity: sha512-ICw7NTT6RsDp2rnYKVd8Fu4cr6ITzGy3+u4vUujPkabyaz+03F24NWEX7fs5fp+kBonlaqPH8fAO2NM+SXt/JA==} + engines: {node: '>=4'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@1.1.2: + resolution: {integrity: sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==} + engines: {node: '>=0.10.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + find-versions@3.2.0: + resolution: {integrity: sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww==} + engines: {node: '>=6'} + + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + + flatted@3.3.2: + resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} + + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + for-each@0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + foreground-child@3.3.0: + resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} + engines: {node: '>=14'} + + form-data@4.0.3: + resolution: {integrity: sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==} + engines: {node: '>= 6'} + + fraction.js@4.3.7: + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + from2@2.3.0: + resolution: {integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==} + + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + + fs-extra@9.1.0: + resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} + engines: {node: '>=10'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function-timeout@1.0.2: + resolution: {integrity: sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==} + engines: {node: '>=18'} + + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + get-intrinsic@1.2.6: + resolution: {integrity: sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==} + engines: {node: '>= 0.4'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-proxy@2.1.0: + resolution: {integrity: sha512-zmZIaQTWnNQb4R4fJUEp/FC51eZsc6EkErspy3xtIYStaq8EB/hDIWipxsal+E8rz0qD7f2sL/NA9Xee4RInJw==} + engines: {node: '>=4'} + + get-stdin@4.0.1: + resolution: {integrity: sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==} + engines: {node: '>=0.10.0'} + + get-stream@2.3.1: + resolution: {integrity: sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==} + engines: {node: '>=0.10.0'} + + get-stream@3.0.0: + resolution: {integrity: sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==} + engines: {node: '>=4'} + + get-stream@4.1.0: + resolution: {integrity: sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==} + engines: {node: '>=6'} + + get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + + get-stream@9.0.1: + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + engines: {node: '>=18'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + gifsicle@5.2.0: + resolution: {integrity: sha512-vOIS3j0XoTCxq9pkGj43gEix82RkI5FveNgaFZutjbaui/HH+4fR8Y56dwXDuxYo8hR4xOo6/j2h1WHoQW6XLw==} + engines: {node: '>=10'} + hasBin: true + + gifsicle@5.3.0: + resolution: {integrity: sha512-FJTpgdj1Ow/FITB7SVza5HlzXa+/lqEY0tHQazAJbuAdvyJtkH4wIdsR2K414oaTwRXHFLLF+tYbipj+OpYg+Q==} + engines: {node: '>=10'} + hasBin: true + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + globby@10.0.2: + resolution: {integrity: sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==} + engines: {node: '>=8'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + globby@14.0.2: + resolution: {integrity: sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==} + engines: {node: '>=18'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + got@7.1.0: + resolution: {integrity: sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==} + engines: {node: '>=4'} + + got@8.3.2: + resolution: {integrity: sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==} + engines: {node: '>=4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + grapheme-splitter@1.0.4: + resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-ansi@2.0.0: + resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==} + engines: {node: '>=0.10.0'} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbol-support-x@1.4.2: + resolution: {integrity: sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-to-string-tag-x@1.4.1: + resolution: {integrity: sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hashish@0.0.4: + resolution: {integrity: sha512-xyD4XgslstNAs72ENaoFvgMwtv8xhiDtC2AtzCG+8yF7W/Knxxm9BX+e2s25mm+HxMKh0rBmXVOEGF3zNImXvA==} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hat@0.0.3: + resolution: {integrity: sha512-zpImx2GoKXy42fVDSEad2BPKuSQdLcqsCYa48K3zHSzM/ugWuYjLDr8IXxpVuL7uCLHw56eaiLxCRthhOzf5ug==} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + hosted-git-info@2.8.9: + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + + http-cache-semantics@3.8.1: + resolution: {integrity: sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==} + + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + + human-signals@1.1.1: + resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} + engines: {node: '>=8.12.0'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + human-signals@3.0.1: + resolution: {integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==} + engines: {node: '>=12.20.0'} + + human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + + identifier-regex@1.0.0: + resolution: {integrity: sha512-Rcy5cjBOM9iTR+Vwy0Llyip9u0cA99T1yiWOhDW/+PDaTQhyski0tMovsipQ/FRNDkudjLWusJ/IMVIlG5WZnQ==} + engines: {node: '>=18'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + imagemin-gifsicle@7.0.0: + resolution: {integrity: sha512-LaP38xhxAwS3W8PFh4y5iQ6feoTSF+dTAXFRUEYQWYst6Xd+9L/iPk34QGgK/VO/objmIlmq9TStGfVY2IcHIA==} + engines: {node: '>=10'} + + imagemin-jpegtran@7.0.0: + resolution: {integrity: sha512-MJoyTCW8YjMJf56NorFE41SR/WkaGA3IYk4JgvMlRwguJEEd3PnP9UxA8Y2UWjquz8d+On3Ds/03ZfiiLS8xTQ==} + engines: {node: '>=10'} + + imagemin-mozjpeg@10.0.0: + resolution: {integrity: sha512-DK85QNOjS3/GzWYfNB3CACMZD10sIQgFDv1+WTOnZljgltQTEyATjdyUVyjKu5q4sCESQdwvwq7WEZzJ5fFjlg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + imagemin-mozjpeg@9.0.0: + resolution: {integrity: sha512-TwOjTzYqCFRgROTWpVSt5UTT0JeCuzF1jswPLKALDd89+PmrJ2PdMMYeDLYZ1fs9cTovI9GJd68mRSnuVt691w==} + engines: {node: '>=10'} + + imagemin-optipng@8.0.0: + resolution: {integrity: sha512-CUGfhfwqlPjAC0rm8Fy+R2DJDBGjzy2SkfyT09L8rasnF9jSoHFqJ1xxSZWK6HVPZBMhGPMxCTL70OgTHlLF5A==} + engines: {node: '>=10'} + + imagemin-pngquant@10.0.0: + resolution: {integrity: sha512-kt0LFxyv7sBxUbZyvt+JXoU0HvSnmTJkEW32rZPQ9d7AQJPVh0vkz9mGkvbX0yntY2zW/3N20Yw69PBSt1UQzw==} + engines: {node: '>=18'} + + imagemin-pngquant@9.0.2: + resolution: {integrity: sha512-cj//bKo8+Frd/DM8l6Pg9pws1pnDUjgb7ae++sUX1kUVdv2nrngPykhiUOgFeE0LGY/LmUbCf4egCHC4YUcZSg==} + engines: {node: '>=10'} + + imagemin-svgo@11.0.1: + resolution: {integrity: sha512-sUZdlXFXZkPt61nzVXbt7EzYjjevPkNuZmAw0VjEm085PRrK5AHSF+1myF0NKtE+ZhY2Vf6W3CEoRqAgksruWQ==} + engines: {node: '>=18'} + + imagemin-svgo@9.0.0: + resolution: {integrity: sha512-uNgXpKHd99C0WODkrJ8OO/3zW3qjgS4pW7hcuII0RcHN3tnKxDjJWcitdVC/TZyfIqSricU8WfrHn26bdSW62g==} + engines: {node: '>=10'} + + imagemin-webp@6.1.0: + resolution: {integrity: sha512-i8ZluZV1pfQX9aVzmZ/VZh9KBSdPwUlp5VruAa9c30GZnX/nMl5n7h+oUMnI7Mg7+SUpu9mYBsw2nsYGUEllWQ==} + engines: {node: '>=10'} + + imagemin@7.0.1: + resolution: {integrity: sha512-33AmZ+xjZhg2JMCe+vDf6a9mzWukE7l+wAtesjE7KyteqqKjzxv7aVQeWnul1Ve26mWvEQqyPwl0OctNBfSR9w==} + engines: {node: '>=8'} + + imagemin@9.0.0: + resolution: {integrity: sha512-oFlmioXTIrDCNYiKUVPjzUzm8M/7X74WEO6v8NFjn3ZtxjArdVJiRRdbPpq/OG4BdwaHMUz8ej9Fp4AcaDzMnA==} + engines: {node: '>=18'} + + import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + + import-lazy@3.1.0: + resolution: {integrity: sha512-8/gvXvX2JMn0F+CDlSC4l6kOmVaLOO3XLkksI7CI3Ud95KDYJuYur2b9P/PUt/i/pDAMd/DulQsNbbbmRRsDIQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@2.1.0: + resolution: {integrity: sha512-aqwDFWSgSgfRaEwao5lg5KEcVd/2a+D1rvoG7NdilmYz0NwRk6StWpWdz/Hpk34MKPpx7s8XxUqimfcQK6gGlg==} + engines: {node: '>=0.10.0'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + into-stream@3.1.0: + resolution: {integrity: sha512-TcdjPibTksa1NQximqep2r17ISRiNE9fwlfbg3F8ANdvP5/yrFTew86VcO//jk4QTaMlbjypPBq76HN2zaKfZQ==} + engines: {node: '>=4'} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + + is-async-function@2.0.0: + resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-boolean-object@1.2.1: + resolution: {integrity: sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==} + engines: {node: '>= 0.4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-cwebp-readable@3.0.0: + resolution: {integrity: sha512-bpELc7/Q1/U5MWHn4NdHI44R3jxk0h9ew9ljzabiRl70/UIjL/ZAqRMb52F5+eke/VC8yTiv4Ewryo1fPWidvA==} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-finite@1.1.0: + resolution: {integrity: sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-function@1.0.10: + resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} + engines: {node: '>= 0.4'} + + is-gif@3.0.0: + resolution: {integrity: sha512-IqJ/jlbw5WJSNfwQ/lHEDXF8rxhRgF6ythk2oiEvhpG29F704eX9NO6TvPfMiq9DrbwgcEDnETYNcZDPewQoVw==} + engines: {node: '>=6'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-identifier@1.0.1: + resolution: {integrity: sha512-HQ5v4rEJ7REUV54bCd2l5FaD299SGDEn2UPoVXaTHAyGviLq2menVUD2udi3trQ32uvB6LdAh/0ck2EuizrtpA==} + engines: {node: '>=18'} + + is-jpg@2.0.0: + resolution: {integrity: sha512-ODlO0ruzhkzD3sdynIainVP5eoOFNN85rxA1+cwwnPe4dKyX0r5+hxNO5XpCrxlHcmb9vkOit9mhRD2JVuimHg==} + engines: {node: '>=6'} + + is-jpg@3.0.0: + resolution: {integrity: sha512-Vcd67KWHZblEKEBrtP25qLZ8wN9ICoAhl1pKUqD7SM7hf2qtuRl7loDgP5Zigh2oN/+7uj+KVyC0eRJvgOEFeQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-natural-number@4.0.1: + resolution: {integrity: sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-object@1.0.2: + resolution: {integrity: sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-plain-obj@1.1.0: + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} + + is-png@2.0.0: + resolution: {integrity: sha512-4KPGizaVGj2LK7xwJIz8o5B2ubu1D/vcQsgOGFEDlpcvgZHto4gBnyd0ig7Ws+67ixmwKoNmu0hYnpo6AaKb5g==} + engines: {node: '>=8'} + + is-png@3.0.1: + resolution: {integrity: sha512-8TqC8+bdsm3YkpI2aECCDycFDl1hTB0HMVRnP3xRRa3Tqx2oVE7sBi1G6CuO9IqEyWSzbBZr1mGqdb3it9h/pg==} + engines: {node: '>=12'} + + is-reference@1.2.1: + resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-retry-allowed@1.2.0: + resolution: {integrity: sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==} + engines: {node: '>=0.10.0'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-stream@1.1.0: + resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} + engines: {node: '>=0.10.0'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + is-stream@4.0.1: + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-svg@4.4.0: + resolution: {integrity: sha512-v+AgVwiK5DsGtT9ng+m4mClp6zDAmwrW8nZi6Gg15qzvBnRWWdfWA1TGaXyCDnWq5g5asofIgMVl3PjKxvk1ug==} + engines: {node: '>=6'} + + is-svg@5.1.0: + resolution: {integrity: sha512-uVg5yifaTxHoefNf5Jcx+i9RZe2OBYd/UStp1umx+EERa4xGRa3LLGXjoEph43qUORC0qkafUgrXZ6zzK89yGA==} + engines: {node: '>=14.16'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-utf8@0.2.1: + resolution: {integrity: sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.0: + resolution: {integrity: sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isurl@1.0.0: + resolution: {integrity: sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==} + engines: {node: '>= 4'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + hasBin: true + + jpegtran-bin@5.0.2: + resolution: {integrity: sha512-4FSmgIcr8d5+V6T1+dHbPZjaFH0ogVyP4UVsE+zri7S9YLO4qAT2our4IN3sW3STVgNTbqPermdIgt2XuAJ4EA==} + engines: {node: '>=10'} + hasBin: true + + jpegtran-bin@6.0.1: + resolution: {integrity: sha512-WohhhHhqe22de7PU8hXs6Sr5d4BAvkrfA93NR5tGlHyPnFLgvEW/bH+q7fv65JgoiQDsd7SBwwQ/OGRBivU3Mw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsep@1.4.0: + resolution: {integrity: sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==} + engines: {node: '>= 10.16.0'} + + json-buffer@3.0.0: + resolution: {integrity: sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==} + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-better-errors@1.0.2: + resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + + jsqr@1.4.0: + resolution: {integrity: sha512-dxLob7q65Xg2DvstYkRpkYtmKm2sPJ9oFhrhmudT1dZvNFFTlroai3AWSpLey/w5vMcLBXRgOJsbXpdN9HzU/A==} + + junk@3.1.0: + resolution: {integrity: sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==} + engines: {node: '>=8'} + + junk@4.0.1: + resolution: {integrity: sha512-Qush0uP+G8ZScpGMZvHUiRfI0YBWuB3gVBYlI0v0vvOJt5FLicco+IkP0a50LqTTQhmts/m6tP5SWE+USyIvcQ==} + engines: {node: '>=12.20'} + + kdbush@4.0.2: + resolution: {integrity: sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==} + + keyv@3.0.0: + resolution: {integrity: sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + ktx-parse@1.0.1: + resolution: {integrity: sha512-djwUWv/82Xc8LOVinJU4EBrVqYkO8OsUDSPEtY/OOVY8BSe3DMU7D7PlIAZ0pI7ZZtErj7mqpJcgffUTABvgaA==} + + lerc@2.0.0: + resolution: {integrity: sha512-7qo1Mq8ZNmaR4USHHm615nEW2lPeeWJ3bTyoqFbd35DLx0LUH7C6ptt5FDCTAlbIzs3+WKrk5SkJvw8AFDE2hg==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + load-json-file@1.1.0: + resolution: {integrity: sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==} + engines: {node: '>=0.10.0'} + + load-json-file@4.0.0: + resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} + engines: {node: '>=4'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + + lodash-unified@1.0.3: + resolution: {integrity: sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==} + peerDependencies: + '@types/lodash-es': '*' + lodash: '*' + lodash-es: '*' + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + logalot@2.1.0: + resolution: {integrity: sha512-Ah4CgdSRfeCJagxQhcVNMi9BfGYyEKLa6d7OA6xSbld/Hg3Cf2QiOa1mDpmG7Ve8LOH6DN3mdttzjQAvWTyVkw==} + engines: {node: '>=0.10.0'} + + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + + longest@1.0.1: + resolution: {integrity: sha512-k+yt5n3l48JU4k8ftnKG6V7u32wyH2NfKzeMto9F/QRE0amxy/LayxwlvjjkZEIzqR+19IrtFO8p5kB9QaYUFg==} + engines: {node: '>=0.10.0'} + + loud-rejection@1.6.0: + resolution: {integrity: sha512-RPNliZOFkqFumDhvYqOaNY4Uz9oJM2K9tC6JWsJJsNdhuONW4LQHRBpb0qf4pJApVffI5N39SwzWZJuEhfd7eQ==} + engines: {node: '>=0.10.0'} + + lowercase-keys@1.0.0: + resolution: {integrity: sha512-RPlX0+PHuvxVDZ7xX+EBVAp4RsVxP/TdDSN2mJYdiq1Lc4Hz7EUSjUI7RZrKKlmrIzVhf6Jo2stj7++gVarS0A==} + engines: {node: '>=0.10.0'} + + lowercase-keys@1.0.1: + resolution: {integrity: sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==} + engines: {node: '>=0.10.0'} + + lpad-align@1.1.2: + resolution: {integrity: sha512-MMIcFmmR9zlGZtBcFOows6c2COMekHCIFJz3ew/rRpKZ1wR4mXDPzvcVqLarux8M33X4TPSq2Jdw8WJj0q0KbQ==} + engines: {node: '>=0.10.0'} + hasBin: true + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@4.1.5: + resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} + + magic-string@0.25.9: + resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + make-dir@1.3.0: + resolution: {integrity: sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==} + engines: {node: '>=4'} + + make-dir@3.1.0: + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} + engines: {node: '>=8'} + + map-obj@1.0.1: + resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} + engines: {node: '>=0.10.0'} + + marked@15.0.12: + resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==} + engines: {node: '>= 18'} + hasBin: true + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mdn-data@2.0.14: + resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} + + mdn-data@2.0.28: + resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} + + mdn-data@2.0.30: + resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + + memoize-one@6.0.0: + resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} + + memorystream@0.3.1: + resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} + engines: {node: '>= 0.10.0'} + + meow@3.7.0: + resolution: {integrity: sha512-TNdwZs0skRlpPpCUK25StC4VH+tP5GgeY1HQOOGP+lQ2xtdkN2VtT/5tiX9k3IWpkBPV9b3LsAWXn4GGi/PrSA==} + engines: {node: '>=0.10.0'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + mersenne-twister@1.1.0: + resolution: {integrity: sha512-mUYWsMKNrm4lfygPkL3OfGzOPTR2DBlTkBNHM//F6hGp8cLThY897crAlk3/Jo17LEOOjQUrNAx6DvgO77QJkA==} + + meshoptimizer@0.24.0: + resolution: {integrity: sha512-Iq/8cM5cK2B0P0QdKHexr30zEJn5sQxjXKtQJGp6LO7r1VUPwhgxau18pvybVDnEDsKE49GdlJYwqPX9A/P9Sw==} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-db@1.53.0: + resolution: {integrity: sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + + mimic-response@1.0.1: + resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} + engines: {node: '>=4'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + + mozjpeg@7.1.1: + resolution: {integrity: sha512-iIDxWvzhWvLC9mcRJ1uSkiKaj4drF58oCqK2bITm5c2Jt6cJ8qQjSSru2PCaysG+hLIinryj8mgz5ZJzOYTv1A==} + engines: {node: '>=10'} + hasBin: true + + mozjpeg@8.0.0: + resolution: {integrity: sha512-Ca2Yhah9hG0Iutgsn8MOrAl37P9ThnKsJatjXoWdUO+8X8GeG/6ahvHZrTyqvbs6leMww1SauWUCao/L9qBuFQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + + mrcolor@https://codeload.github.com/rook2pawn/mrcolor/tar.gz/refs/heads/master: + resolution: {tarball: https://codeload.github.com/rook2pawn/mrcolor/tar.gz/refs/heads/master} + version: 0.0.1 + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + muggle-string@0.3.1: + resolution: {integrity: sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + nice-try@1.0.5: + resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} + + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + + noise@0.0.0: + resolution: {integrity: sha512-CRkKL2xsXQZ+/TroyP8PjDhIqBanIIlAFrPOUCQMAW4a7qyv+TZtPe4eBW96+QxXnkC7cOZV/0FqIHmtrS23nA==} + + normalize-package-data@2.5.0: + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + + normalize-url@2.0.1: + resolution: {integrity: sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==} + engines: {node: '>=4'} + + normalize-wheel-es@1.2.0: + resolution: {integrity: sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==} + + nosleep.js@0.12.0: + resolution: {integrity: sha512-9d1HbpKLh3sdWlhXMhU6MMH+wQzKkrgfRkYV0EBdvt99YJfj0ilCJrWRDYG2130Tm4GXbEoTCx5b34JSaP+HhA==} + + npm-conf@1.1.3: + resolution: {integrity: sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==} + engines: {node: '>=4'} + + npm-run-all@4.1.5: + resolution: {integrity: sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==} + engines: {node: '>= 4'} + hasBin: true + + npm-run-path@2.0.2: + resolution: {integrity: sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==} + engines: {node: '>=4'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + object-inspect@1.13.3: + resolution: {integrity: sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==} + engines: {node: '>= 0.4'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + optipng-bin@7.0.1: + resolution: {integrity: sha512-W99mpdW7Nt2PpFiaO+74pkht7KEqkXkeRomdWXfEz3SALZ6hns81y/pm1dsGZ6ItUIfchiNIP6ORDr1zETU1jA==} + engines: {node: '>=10'} + hasBin: true + + os-filter-obj@2.0.0: + resolution: {integrity: sha512-uksVLsqG3pVdzzPvmAHpBK0wKxYItuzZr7SziusRPoz67tGV8rL1szZ6IdeUrbqLjGDwApBtN29eEE3IqGHOjg==} + engines: {node: '>=4'} + + otplib@12.0.1: + resolution: {integrity: sha512-xDGvUOQjop7RDgxTQ+o4pOol0/3xSZzawTiPKRrHnQWAy0WjhNs/5HdIDJCrqC4MBynmjXgULc6YfioaxZeFgg==} + + ow@0.17.0: + resolution: {integrity: sha512-i3keDzDQP5lWIe4oODyDFey1qVrq2hXKTuTH2VpqwpYtzPiKZt2ziRI4NBQmgW40AnV5Euz17OyWweCb+bNEQA==} + engines: {node: '>=10'} + + ow@2.0.0: + resolution: {integrity: sha512-ESUigmGrdhUZ2nQSFNkeKSl6ZRPupXzprMs3yF9DYlNVpJ8XAjM/fI9RUZxA7PI1K9HQDCCvBo1jr/GEIo9joQ==} + engines: {node: '>=18'} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + + p-cancelable@0.3.0: + resolution: {integrity: sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==} + engines: {node: '>=4'} + + p-cancelable@0.4.1: + resolution: {integrity: sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==} + engines: {node: '>=4'} + + p-event@1.3.0: + resolution: {integrity: sha512-hV1zbA7gwqPVFcapfeATaNjQ3J0NuzorHPyG8GPL9g/Y/TplWVBVoCKCXL6Ej2zscrCEv195QNWJXuBH6XZuzA==} + engines: {node: '>=4'} + + p-event@2.3.1: + resolution: {integrity: sha512-NQCqOFhbpVTMX4qMe8PF8lbGtzZ+LCiN7pcNrb/413Na7+TRoe1xkKUzuWa/YEJdGQ0FvKtj35EEbDoVPO2kbA==} + engines: {node: '>=6'} + + p-finally@1.0.0: + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=4'} + + p-is-promise@1.1.0: + resolution: {integrity: sha512-zL7VE4JVS2IFSkR2GQKDSPEVxkoH43/p7oEnwpdCndKYJO0HVeRB7fA8TJwuLOTBREtK0ea8eHaxdwcpob5dmg==} + engines: {node: '>=4'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-map-series@1.0.0: + resolution: {integrity: sha512-4k9LlvY6Bo/1FcIdV33wqZQES0Py+iKISU9Uc8p8AjWoZPnFKMpVIVD3s0EYn4jzLh1I+WeUZkJ0Yoa4Qfw3Kg==} + engines: {node: '>=4'} + + p-pipe@3.1.0: + resolution: {integrity: sha512-08pj8ATpzMR0Y80x50yJHn37NF6vjrqHutASaX5LiH5npS9XPvrUmscd9MF5R4fuYRHOxQR1FfMIlF7AzwoPqw==} + engines: {node: '>=8'} + + p-pipe@4.0.0: + resolution: {integrity: sha512-HkPfFklpZQPUKBFXzKFB6ihLriIHxnmuQdK9WmLDwe4hf2PdhhfWT/FJa+pc3bA1ywvKXtedxIRmd4Y7BTXE4w==} + engines: {node: '>=12'} + + p-reduce@1.0.0: + resolution: {integrity: sha512-3Tx1T3oM1xO/Y8Gj0sWyE78EIJZ+t+aEmXUdvQgvGmSMri7aPTHoovbXEreWKkL5j21Er60XAWLTzKbAKYOujQ==} + engines: {node: '>=4'} + + p-timeout@1.2.1: + resolution: {integrity: sha512-gb0ryzr+K2qFqFv6qi3khoeqMZF/+ajxQipEF6NteZVnvz9tzdsfAVj3lYtn1gAXvH5lfLwfxEII799gt/mRIA==} + engines: {node: '>=4'} + + p-timeout@2.0.1: + resolution: {integrity: sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==} + engines: {node: '>=4'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + pako@2.1.0: + resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@2.2.0: + resolution: {integrity: sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==} + engines: {node: '>=0.10.0'} + + parse-json@4.0.0: + resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} + engines: {node: '>=4'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + path-exists@2.1.0: + resolution: {integrity: sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==} + engines: {node: '>=0.10.0'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@2.0.1: + resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} + engines: {node: '>=4'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-type@1.1.0: + resolution: {integrity: sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==} + engines: {node: '>=0.10.0'} + + path-type@3.0.0: + resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} + engines: {node: '>=4'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + path-type@5.0.0: + resolution: {integrity: sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==} + engines: {node: '>=12'} + + pathe@0.2.0: + resolution: {integrity: sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==} + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + peek-readable@5.3.1: + resolution: {integrity: sha512-GVlENSDW6KHaXcd9zkZltB7tCLosKB/4Hg0fqBJkAoBgYG2Tn1xtMgXtSUuMU9AK/gCm/tTdT8mgAeF4YNeeqw==} + engines: {node: '>=14.16'} + + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + pidtree@0.3.1: + resolution: {integrity: sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==} + engines: {node: '>=0.10'} + hasBin: true + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pify@3.0.0: + resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} + engines: {node: '>=4'} + + pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + + pinia@2.3.0: + resolution: {integrity: sha512-ohZj3jla0LL0OH5PlLTDMzqKiVw2XARmC1XYLdLWIPBMdhDW/123ZWr4zVAhtJm+aoSkFa13pYXskAvAscIkhQ==} + peerDependencies: + typescript: '>=4.4.4' + vue: ^2.7.0 || ^3.5.11 + peerDependenciesMeta: + typescript: + optional: true + + pinkie-promise@2.0.1: + resolution: {integrity: sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==} + engines: {node: '>=0.10.0'} + + pinkie@2.0.4: + resolution: {integrity: sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==} + engines: {node: '>=0.10.0'} + + pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + + pngquant-bin@6.0.1: + resolution: {integrity: sha512-Q3PUyolfktf+hYio6wsg3SanQzEU/v8aICg/WpzxXcuCMRb7H2Q81okfpcEztbMvw25ILjd3a87doj2N9kvbpQ==} + engines: {node: '>=10'} + hasBin: true + + pngquant-bin@9.0.0: + resolution: {integrity: sha512-jlOKfIQBTNJwQn2JKK5xLmwrsi/NwVTmHRvbrknCjdWxfX1/c/+yP4Jmp9jRZWedft/vnhh+rGbvl/kUmesurg==} + engines: {node: '>=18'} + hasBin: true + + possible-typed-array-names@1.0.0: + resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} + engines: {node: '>= 0.4'} + + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.0.1: + resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@4.0.2: + resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} + engines: {node: '>= 14'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.4.49: + resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prepend-http@1.0.4: + resolution: {integrity: sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==} + engines: {node: '>=0.10.0'} + + prepend-http@2.0.0: + resolution: {integrity: sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==} + engines: {node: '>=4'} + + prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + + prettier@3.4.2: + resolution: {integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==} + engines: {node: '>=14'} + hasBin: true + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + proto-list@1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + + protobufjs@7.5.3: + resolution: {integrity: sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw==} + engines: {node: '>=12.0.0'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + pseudomap@1.0.2: + resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} + + pump@3.0.2: + resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + qrcode.vue@3.6.0: + resolution: {integrity: sha512-vQcl2fyHYHMjDO1GguCldJxepq2izQjBkDEEu9NENgfVKP6mv/e2SU62WbqYHGwTgWXLhxZ1NCD1dAZKHQq1fg==} + peerDependencies: + vue: ^3.0.0 + + query-string@5.1.1: + resolution: {integrity: sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==} + engines: {node: '>=0.10.0'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + quickselect@3.0.0: + resolution: {integrity: sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + rbush@4.0.1: + resolution: {integrity: sha512-IP0UpfeWQujYC8Jg162rMNc01Rf0gWMMAb2Uxus/Q0qOFw4lCcq6ZnQEZwUoJqWyUGJ9th7JjwI4yIWo+uvoAQ==} + + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + + read-pkg-up@1.0.1: + resolution: {integrity: sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==} + engines: {node: '>=0.10.0'} + + read-pkg@1.1.0: + resolution: {integrity: sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==} + engines: {node: '>=0.10.0'} + + read-pkg@3.0.0: + resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} + engines: {node: '>=4'} + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + redent@1.0.0: + resolution: {integrity: sha512-qtW5hKzGQZqKoh6JNSD+4lfitfPKGz42e6QwiRmPM5mmKtR0N41AbJRYu0xJi7nhOJ4WDgRkKvAk6tw4WIwR4g==} + engines: {node: '>=0.10.0'} + + reflect.getprototypeof@1.0.9: + resolution: {integrity: sha512-r0Ay04Snci87djAsI4U+WNRcSw5S4pOH7qFjd/veA5gC7TbqESR3tcj28ia95L/fYUDw11JKP7uqUKUAfVvV5Q==} + engines: {node: '>= 0.4'} + + regexp.prototype.flags@1.5.3: + resolution: {integrity: sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==} + engines: {node: '>= 0.4'} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + repeating@2.0.1: + resolution: {integrity: sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A==} + engines: {node: '>=0.10.0'} + + replace-ext@1.0.1: + resolution: {integrity: sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==} + engines: {node: '>= 0.10'} + + reserved-identifiers@1.0.0: + resolution: {integrity: sha512-h0bP2Katmvf3hv4Z3WtDl4+6xt/OglQ2Xa6TnhZ/Rm9/7IH1crXQqMwD4J2ngKBonVv+fB55zfGgNDAmsevLVQ==} + engines: {node: '>=18'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} + hasBin: true + + responselike@1.0.2: + resolution: {integrity: sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==} + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@2.7.1: + resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rollup-plugin-external-globals@0.6.1: + resolution: {integrity: sha512-mlp3KNa5sE4Sp9UUR2rjBrxjG79OyZAh/QC18RHIjM+iYkbBwNXSo8DHRMZWtzJTrH8GxQ+SJvCTN3i14uMXIA==} + peerDependencies: + rollup: ^2.25.0 + + rollup@4.29.1: + resolution: {integrity: sha512-RaJ45M/kmJUzSWDs1Nnd5DdV4eerC98idtUOVr6FfKcgxqvjwHmxc5upLF9qZU9EpsVzzhleFahrT3shLuJzIw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + rss-parser@3.13.0: + resolution: {integrity: sha512-7jWUBV5yGN3rqMMj7CZufl/291QAhvrrGpDNE4k/02ZchL0npisiYYqULF71jCEKoIiHvK/Q2e6IkDwPziT7+w==} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + sax@1.4.1: + resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} + + seek-bzip@1.0.6: + resolution: {integrity: sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==} + hasBin: true + + semver-regex@2.0.0: + resolution: {integrity: sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==} + engines: {node: '>=6'} + + semver-truncate@1.1.2: + resolution: {integrity: sha512-V1fGg9i4CL3qesB6U0L6XAm4xOJiHmt4QAacazumuasc03BvtFGIMCduv01JWQ69Nv+JST9TqhSCiJoxoY031w==} + engines: {node: '>=0.10.0'} + + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + + send@0.19.0: + resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} + engines: {node: '>= 0.8.0'} + + serve-static@1.16.2: + resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} + engines: {node: '>= 0.8.0'} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + sharp@0.33.5: + resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + shebang-command@1.2.0: + resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} + engines: {node: '>=0.10.0'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@1.0.0: + resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} + engines: {node: '>=0.10.0'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shell-quote@1.8.2: + resolution: {integrity: sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==} + engines: {node: '>= 0.4'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slash@5.1.0: + resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} + engines: {node: '>=14.16'} + + sort-keys-length@1.0.1: + resolution: {integrity: sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==} + engines: {node: '>=0.10.0'} + + sort-keys@1.1.2: + resolution: {integrity: sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==} + engines: {node: '>=0.10.0'} + + sort-keys@2.0.0: + resolution: {integrity: sha512-/dPCrG1s3ePpWm6yBbxZq5Be1dXGLyLn9Z791chDC3NFrpkVbWGzkBwPN1knaciexFXgRJ7hzdnwZ4stHSDmjg==} + engines: {node: '>=4'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + sourcemap-codec@1.4.8: + resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} + deprecated: Please use @jridgewell/sourcemap-codec instead + + spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + + spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + + spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + + spdx-license-ids@3.0.20: + resolution: {integrity: sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==} + + squeak@1.3.0: + resolution: {integrity: sha512-YQL1ulInM+ev8nXX7vfXsCsDh6IqXlrremc1hzi77776BtpWgYJUMto3UM05GSAaGzJgWekszjoKDrVNB5XG+A==} + engines: {node: '>=0.10.0'} + + stable@0.1.8: + resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} + deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' + + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + + strict-uri-encode@1.1.0: + resolution: {integrity: sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==} + engines: {node: '>=0.10.0'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string.prototype.padend@3.1.6: + resolution: {integrity: sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==} + engines: {node: '>= 0.4'} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + strip-ansi@3.0.1: + resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==} + engines: {node: '>=0.10.0'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-bom@2.0.0: + resolution: {integrity: sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==} + engines: {node: '>=0.10.0'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-dirs@2.1.0: + resolution: {integrity: sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==} + + strip-eof@1.0.0: + resolution: {integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==} + engines: {node: '>=0.10.0'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + + strip-indent@1.0.1: + resolution: {integrity: sha512-I5iQq6aFMM62fBEAIB/hXzwJD6EEZ0xEGCX2t7oXqaKPIRgt4WruAQ285BISgdkP+HLGWyeGmNJcpIwFeRYRUA==} + engines: {node: '>=0.10.0'} + hasBin: true + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + strip-outer@1.0.1: + resolution: {integrity: sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==} + engines: {node: '>=0.10.0'} + + strnum@1.0.5: + resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} + + strtok3@9.1.1: + resolution: {integrity: sha512-FhwotcEqjr241ZbjFzjlIYg6c5/L/s4yBGWSMvJ9UoExiSqL+FnFA/CaeZx17WGaZMS/4SOZp8wH18jSS4R4lw==} + engines: {node: '>=16'} + + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + super-regex@1.0.0: + resolution: {integrity: sha512-CY8u7DtbvucKuquCmOFEKhr9Besln7n9uN8eFbwcoGYWXOMW07u2o8njWaiXt11ylS3qoGF55pILjRmPlbodyg==} + engines: {node: '>=18'} + + supports-color@2.0.0: + resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} + engines: {node: '>=0.8.0'} + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + svgo@2.8.0: + resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==} + engines: {node: '>=10.13.0'} + hasBin: true + + svgo@3.3.2: + resolution: {integrity: sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==} + engines: {node: '>=14.0.0'} + hasBin: true + + synckit@0.9.2: + resolution: {integrity: sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==} + engines: {node: ^14.18.0 || >=16.0.0} + + tailwindcss@3.4.17: + resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==} + engines: {node: '>=14.0.0'} + hasBin: true + + tar-stream@1.6.2: + resolution: {integrity: sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==} + engines: {node: '>= 0.8.0'} + + temp-dir@1.0.0: + resolution: {integrity: sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ==} + engines: {node: '>=4'} + + tempfile@2.0.0: + resolution: {integrity: sha512-ZOn6nJUgvgC09+doCEF3oB+r3ag7kUvlsXEGX069QRD60p+P3uP7XG9N2/at+EyIRGSN//ZY3LyEotA1YpmjuA==} + engines: {node: '>=4'} + + terser@5.37.0: + resolution: {integrity: sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==} + engines: {node: '>=10'} + hasBin: true + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + thirty-two@1.0.2: + resolution: {integrity: sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA==} + engines: {node: '>=0.2.6'} + + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + + time-span@5.1.0: + resolution: {integrity: sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==} + engines: {node: '>=12'} + + timed-out@4.0.1: + resolution: {integrity: sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==} + engines: {node: '>=0.10.0'} + + to-buffer@1.1.1: + resolution: {integrity: sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + token-types@6.0.0: + resolution: {integrity: sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA==} + engines: {node: '>=14.16'} + + topojson-client@3.1.0: + resolution: {integrity: sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==} + hasBin: true + + traverse@0.6.11: + resolution: {integrity: sha512-vxXDZg8/+p3gblxB6BhhG5yWVn1kGRlaL8O78UDXc3wRnPizB5g83dcvWV1jpDMIPnjZjOFuxlMmE82XJ4407w==} + engines: {node: '>= 0.4'} + + trim-newlines@1.0.0: + resolution: {integrity: sha512-Nm4cF79FhSTzrLKGDMi3I4utBtFv8qKy4sq1enftf2gMdpqI8oVQTAfySkTz5r49giVzDj88SVZXP4CeYQwjaw==} + engines: {node: '>=0.10.0'} + + trim-repeated@1.0.0: + resolution: {integrity: sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==} + engines: {node: '>=0.10.0'} + + ts-api-utils@1.4.3: + resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@0.11.0: + resolution: {integrity: sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==} + engines: {node: '>=8'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + type-fest@3.13.1: + resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} + engines: {node: '>=14.16'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} + + typedarray.prototype.slice@1.0.5: + resolution: {integrity: sha512-q7QNVDGTdl702bVFiI5eY4l/HkgCM6at9KhcFbgUAzezHFbOVy4+0O/lCjsABEQwbZPravVfBIiBVGo89yzHFg==} + engines: {node: '>= 0.4'} + + typescript@5.3.3: + resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} + engines: {node: '>=14.17'} + hasBin: true + + uint8array-extras@1.4.0: + resolution: {integrity: sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==} + engines: {node: '>=18'} + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + + unbzip2-stream@1.4.3: + resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} + + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + + unicorn-magic@0.1.0: + resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} + engines: {node: '>=18'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + update-browserslist-db@1.1.1: + resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + urijs@1.19.11: + resolution: {integrity: sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==} + + url-parse-lax@1.0.0: + resolution: {integrity: sha512-BVA4lR5PIviy2PMseNd2jbFQ+jwSwQGdJejf5ctd1rEXt0Ypd7yanUK9+lYechVlN5VaTJGsu2U/3MDDu6KgBA==} + engines: {node: '>=0.10.0'} + + url-parse-lax@3.0.0: + resolution: {integrity: sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==} + engines: {node: '>=4'} + + url-to-options@1.0.1: + resolution: {integrity: sha512-0kQLIzG4fdk/G5NONku64rSH/x32NOA39LVQqlK8Le6lvTF6GGRJpqaQFGgU+CLwySIqBSMdwYM0sYcW9f6P4A==} + engines: {node: '>= 4'} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + uuid@11.1.0: + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} + hasBin: true + + uuid@3.4.0: + resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} + deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. + hasBin: true + + validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + + vite-plugin-cesium@1.2.23: + resolution: {integrity: sha512-x9A8ZCEoegceXg/E+LnxKr0XBsI9CR4cgYWQ2Dd3cUEYwKcTnHQ3kBfpol7BUcGtgQnQos/mtVrRmuVQBXFjHw==} + peerDependencies: + cesium: ^1.95.0 + vite: '>=2.7.1' + + vite-plugin-compression@0.5.1: + resolution: {integrity: sha512-5QJKBDc+gNYVqL/skgFAP81Yuzo9R+EAf19d+EtsMF/i8kFUpNi3J/H01QD3Oo8zBQn+NzoCIFkpPLynoOzaJg==} + peerDependencies: + vite: '>=2.0.0' + + vite-plugin-image-optimizer@1.1.8: + resolution: {integrity: sha512-40bYRDHQLUOrIwJIJQqyKJHrfgVshqzDLtMy8SEgf+fB7PnppslSTTkY7PJFrBGqgbCdOdN9KkqsvccXmnEa5Q==} + engines: {node: '>=14'} + peerDependencies: + vite: '>=3' + + vite-plugin-imagemin@0.6.1: + resolution: {integrity: sha512-cP7LDn8euPrji7WYtDoNQpJEB9nkMxJHm/A+QZnvMrrCSuyo/clpMy/T1v7suDXPBavsDiDdFdVQB5p7VGD2cg==} + peerDependencies: + vite: '>=2.0.0' + + vite@5.4.19: + resolution: {integrity: sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vue-demi@0.14.10: + resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} + engines: {node: '>=12'} + hasBin: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + + vue-eslint-parser@9.4.3: + resolution: {integrity: sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + + vue-router@4.5.0: + resolution: {integrity: sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==} + peerDependencies: + vue: ^3.2.0 + + vue-template-compiler@2.7.16: + resolution: {integrity: sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==} + + vue-tsc@1.8.27: + resolution: {integrity: sha512-WesKCAZCRAbmmhuGl3+VrdWItEvfoFIPXOvUJkjULi+x+6G/Dy69yO3TBRJDr9eUlmsNAwVmxsNZxvHKzbkKdg==} + hasBin: true + peerDependencies: + typescript: '*' + + vue@3.5.13: + resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + vuetify@3.9.0: + resolution: {integrity: sha512-vjqyHP5gBFH4x0BAjdRAcS3FXY5OfHaKnC6Hhgln8tePZtKc3AUhF7BEJtcrD3l6XwL8gaYx/wMt+UP7X5EZJw==} + engines: {node: ^12.20 || >=14.13} + peerDependencies: + typescript: '>=4.7' + vite-plugin-vuetify: '>=2.1.0' + vue: ^3.5.0 + webpack-plugin-vuetify: '>=3.1.0' + peerDependenciesMeta: + typescript: + optional: true + vite-plugin-vuetify: + optional: true + webpack-plugin-vuetify: + optional: true + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.18: + resolution: {integrity: sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.19: + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: '>= 0.4'} + + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + xml-name-validator@4.0.0: + resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} + engines: {node: '>=12'} + + xml2js@0.5.0: + resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==} + engines: {node: '>=4.0.0'} + + xmlbuilder@11.0.1: + resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} + engines: {node: '>=4.0'} + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + yallist@2.1.2: + resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} + + yaml@2.6.1: + resolution: {integrity: sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==} + engines: {node: '>= 14'} + hasBin: true + + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@babel/helper-string-parser@7.25.9': {} + + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/parser@7.26.3': + dependencies: + '@babel/types': 7.26.3 + + '@babel/types@7.26.3': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + + '@cesium/engine@18.3.0': + dependencies: + '@cesium/wasm-splats': 0.1.0-alpha.2 + '@spz-loader/core': 0.1.0 + '@tweenjs/tween.js': 25.0.0 + '@zip.js/zip.js': 2.7.63 + autolinker: 4.1.5 + bitmap-sdf: 1.0.4 + dompurify: 3.2.6 + draco3d: 1.5.7 + earcut: 3.0.2 + grapheme-splitter: 1.0.4 + jsep: 1.4.0 + kdbush: 4.0.2 + ktx-parse: 1.0.1 + lerc: 2.0.0 + mersenne-twister: 1.1.0 + meshoptimizer: 0.24.0 + pako: 2.1.0 + protobufjs: 7.5.3 + rbush: 4.0.1 + topojson-client: 3.1.0 + urijs: 1.19.11 + + '@cesium/wasm-splats@0.1.0-alpha.2': {} + + '@cesium/widgets@12.3.0': + dependencies: + '@cesium/engine': 18.3.0 + nosleep.js: 0.12.0 + + '@ctrl/tinycolor@3.6.1': {} + + '@element-plus/icons-vue@2.3.1(vue@3.5.13(typescript@5.3.3))': + dependencies: + vue: 3.5.13(typescript@5.3.3) + + '@emailjs/browser@4.4.1': {} + + '@emnapi/runtime@1.3.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.14.54': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@eslint-community/eslint-utils@4.4.1(eslint@8.57.1)': + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/eslintrc@2.1.4': + dependencies: + ajv: 6.12.6 + debug: 4.4.0 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.2 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@8.57.1': {} + + '@floating-ui/core@1.7.2': + dependencies: + '@floating-ui/utils': 0.2.10 + + '@floating-ui/dom@1.7.2': + dependencies: + '@floating-ui/core': 1.7.2 + '@floating-ui/utils': 0.2.10 + + '@floating-ui/utils@0.2.10': {} + + '@humanwhocodes/config-array@0.13.0': + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.4.0 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/object-schema@2.0.3': {} + + '@img/sharp-darwin-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.0.4 + optional: true + + '@img/sharp-darwin-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.0.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.0.5': + optional: true + + '@img/sharp-libvips-linux-s390x@1.0.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + optional: true + + '@img/sharp-linux-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.0.4 + optional: true + + '@img/sharp-linux-arm@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.0.5 + optional: true + + '@img/sharp-linux-s390x@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.0.4 + optional: true + + '@img/sharp-linux-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + optional: true + + '@img/sharp-wasm32@0.33.5': + dependencies: + '@emnapi/runtime': 1.3.1 + optional: true + + '@img/sharp-win32-ia32@0.33.5': + optional: true + + '@img/sharp-win32-x64@0.33.5': + optional: true + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.8': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/source-map@0.3.6': + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@kurkle/color@0.3.4': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + + '@otplib/core@12.0.1': {} + + '@otplib/plugin-crypto@12.0.1': + dependencies: + '@otplib/core': 12.0.1 + + '@otplib/plugin-thirty-two@12.0.1': + dependencies: + '@otplib/core': 12.0.1 + thirty-two: 1.0.2 + + '@otplib/preset-default@12.0.1': + dependencies: + '@otplib/core': 12.0.1 + '@otplib/plugin-crypto': 12.0.1 + '@otplib/plugin-thirty-two': 12.0.1 + + '@otplib/preset-v11@12.0.1': + dependencies: + '@otplib/core': 12.0.1 + '@otplib/plugin-crypto': 12.0.1 + '@otplib/plugin-thirty-two': 12.0.1 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@pkgr/core@0.1.1': {} + + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.4': {} + + '@protobufjs/eventemitter@1.1.0': {} + + '@protobufjs/fetch@1.1.0': + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/inquire@1.1.0': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.0': {} + + '@rollup/pluginutils@4.2.1': + dependencies: + estree-walker: 2.0.2 + picomatch: 2.3.1 + + '@rollup/rollup-android-arm-eabi@4.29.1': + optional: true + + '@rollup/rollup-android-arm64@4.29.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.29.1': + optional: true + + '@rollup/rollup-darwin-x64@4.29.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.29.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.29.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.29.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.29.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.29.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.29.1': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.29.1': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.29.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.29.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.29.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.29.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.29.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.29.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.29.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.29.1': + optional: true + + '@rushstack/eslint-patch@1.10.4': {} + + '@sec-ant/readable-stream@0.4.1': {} + + '@sindresorhus/is@0.7.0': {} + + '@sindresorhus/is@6.3.1': {} + + '@sindresorhus/merge-streams@2.3.0': {} + + '@spz-loader/core@0.1.0': {} + + '@sxzz/popperjs-es@2.11.7': {} + + '@tokenizer/token@0.3.0': {} + + '@trysound/sax@0.2.0': {} + + '@tsconfig/node18@18.2.4': {} + + '@tweenjs/tween.js@25.0.0': {} + + '@types/estree@1.0.6': {} + + '@types/glob@7.2.0': + dependencies: + '@types/minimatch': 5.1.2 + '@types/node': 20.17.10 + + '@types/imagemin-gifsicle@7.0.4': + dependencies: + '@types/imagemin': 7.0.1 + + '@types/imagemin-jpegtran@5.0.4': + dependencies: + '@types/imagemin': 7.0.1 + + '@types/imagemin-mozjpeg@8.0.4': + dependencies: + '@types/imagemin': 7.0.1 + + '@types/imagemin-optipng@5.2.4': + dependencies: + '@types/imagemin': 7.0.1 + + '@types/imagemin-svgo@10.0.5': + dependencies: + '@types/imagemin': 7.0.1 + '@types/svgo': 2.6.4 + + '@types/imagemin-webp@7.0.3': + dependencies: + '@types/imagemin': 7.0.1 + + '@types/imagemin@7.0.1': + dependencies: + '@types/node': 20.17.10 + + '@types/json-schema@7.0.15': {} + + '@types/keyv@3.1.4': + dependencies: + '@types/node': 20.17.10 + + '@types/lodash-es@4.17.12': + dependencies: + '@types/lodash': 4.17.20 + + '@types/lodash@4.17.20': {} + + '@types/minimatch@5.1.2': {} + + '@types/node@20.17.10': + dependencies: + undici-types: 6.19.8 + + '@types/responselike@1.0.3': + dependencies: + '@types/node': 20.17.10 + + '@types/semver@7.5.8': {} + + '@types/svgo@2.6.4': + dependencies: + '@types/node': 20.17.10 + + '@types/trusted-types@2.0.7': + optional: true + + '@types/web-bluetooth@0.0.16': {} + + '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.3.3))(eslint@8.57.1)(typescript@5.3.3)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.3.3) + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.1)(typescript@5.3.3) + '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.3.3) + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.0 + eslint: 8.57.1 + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + semver: 7.6.3 + ts-api-utils: 1.4.3(typescript@5.3.3) + optionalDependencies: + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.3.3)': + dependencies: + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3) + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.0 + eslint: 8.57.1 + optionalDependencies: + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@6.21.0': + dependencies: + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 + + '@typescript-eslint/type-utils@6.21.0(eslint@8.57.1)(typescript@5.3.3)': + dependencies: + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3) + '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.3.3) + debug: 4.4.0 + eslint: 8.57.1 + ts-api-utils: 1.4.3(typescript@5.3.3) + optionalDependencies: + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@6.21.0': {} + + '@typescript-eslint/typescript-estree@6.21.0(typescript@5.3.3)': + dependencies: + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.0 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.3 + semver: 7.6.3 + ts-api-utils: 1.4.3(typescript@5.3.3) + optionalDependencies: + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@6.21.0(eslint@8.57.1)(typescript@5.3.3)': + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) + '@types/json-schema': 7.0.15 + '@types/semver': 7.5.8 + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3) + eslint: 8.57.1 + semver: 7.6.3 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/visitor-keys@6.21.0': + dependencies: + '@typescript-eslint/types': 6.21.0 + eslint-visitor-keys: 3.4.3 + + '@ungap/structured-clone@1.2.1': {} + + '@vitejs/plugin-vue@4.6.2(vite@5.4.19(@types/node@20.17.10)(terser@5.37.0))(vue@3.5.13(typescript@5.3.3))': + dependencies: + vite: 5.4.19(@types/node@20.17.10)(terser@5.37.0) + vue: 3.5.13(typescript@5.3.3) + + '@volar/language-core@1.11.1': + dependencies: + '@volar/source-map': 1.11.1 + + '@volar/source-map@1.11.1': + dependencies: + muggle-string: 0.3.1 + + '@volar/typescript@1.11.1': + dependencies: + '@volar/language-core': 1.11.1 + path-browserify: 1.0.1 + + '@vue/compiler-core@3.5.13': + dependencies: + '@babel/parser': 7.26.3 + '@vue/shared': 3.5.13 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.13': + dependencies: + '@vue/compiler-core': 3.5.13 + '@vue/shared': 3.5.13 + + '@vue/compiler-sfc@3.5.13': + dependencies: + '@babel/parser': 7.26.3 + '@vue/compiler-core': 3.5.13 + '@vue/compiler-dom': 3.5.13 + '@vue/compiler-ssr': 3.5.13 + '@vue/shared': 3.5.13 + estree-walker: 2.0.2 + magic-string: 0.30.17 + postcss: 8.4.49 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.13': + dependencies: + '@vue/compiler-dom': 3.5.13 + '@vue/shared': 3.5.13 + + '@vue/devtools-api@6.6.4': {} + + '@vue/eslint-config-prettier@8.0.0(eslint@8.57.1)(prettier@3.4.2)': + dependencies: + eslint: 8.57.1 + eslint-config-prettier: 8.10.0(eslint@8.57.1) + eslint-plugin-prettier: 5.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@3.4.2) + prettier: 3.4.2 + transitivePeerDependencies: + - '@types/eslint' + + '@vue/eslint-config-typescript@12.0.0(eslint-plugin-vue@9.32.0(eslint@8.57.1))(eslint@8.57.1)(typescript@5.3.3)': + dependencies: + '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.3.3))(eslint@8.57.1)(typescript@5.3.3) + '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.3.3) + eslint: 8.57.1 + eslint-plugin-vue: 9.32.0(eslint@8.57.1) + vue-eslint-parser: 9.4.3(eslint@8.57.1) + optionalDependencies: + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + + '@vue/language-core@1.8.27(typescript@5.3.3)': + dependencies: + '@volar/language-core': 1.11.1 + '@volar/source-map': 1.11.1 + '@vue/compiler-dom': 3.5.13 + '@vue/shared': 3.5.13 + computeds: 0.0.1 + minimatch: 9.0.5 + muggle-string: 0.3.1 + path-browserify: 1.0.1 + vue-template-compiler: 2.7.16 + optionalDependencies: + typescript: 5.3.3 + + '@vue/reactivity@3.5.13': + dependencies: + '@vue/shared': 3.5.13 + + '@vue/runtime-core@3.5.13': + dependencies: + '@vue/reactivity': 3.5.13 + '@vue/shared': 3.5.13 + + '@vue/runtime-dom@3.5.13': + dependencies: + '@vue/reactivity': 3.5.13 + '@vue/runtime-core': 3.5.13 + '@vue/shared': 3.5.13 + csstype: 3.1.3 + + '@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.3.3))': + dependencies: + '@vue/compiler-ssr': 3.5.13 + '@vue/shared': 3.5.13 + vue: 3.5.13(typescript@5.3.3) + + '@vue/shared@3.5.13': {} + + '@vue/tsconfig@0.5.1': {} + + '@vueuse/core@9.13.0(vue@3.5.13(typescript@5.3.3))': + dependencies: + '@types/web-bluetooth': 0.0.16 + '@vueuse/metadata': 9.13.0 + '@vueuse/shared': 9.13.0(vue@3.5.13(typescript@5.3.3)) + vue-demi: 0.14.10(vue@3.5.13(typescript@5.3.3)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@vueuse/metadata@9.13.0': {} + + '@vueuse/shared@9.13.0(vue@3.5.13(typescript@5.3.3))': + dependencies: + vue-demi: 0.14.10(vue@3.5.13(typescript@5.3.3)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@zip.js/zip.js@2.7.63': {} + + acorn-jsx@5.3.2(acorn@8.14.0): + dependencies: + acorn: 8.14.0 + + acorn@8.14.0: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-colors@4.1.3: {} + + ansi-regex@2.1.1: {} + + ansi-regex@5.0.1: {} + + ansi-regex@6.1.0: {} + + ansi-styles@2.2.1: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + arch@2.2.0: {} + + archive-type@4.0.0: + dependencies: + file-type: 4.4.0 + + arg@5.0.2: {} + + argparse@2.0.1: {} + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.3 + is-array-buffer: 3.0.5 + + array-find-index@1.0.2: {} + + array-union@2.1.0: {} + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.6 + is-array-buffer: 3.0.5 + + async-validator@4.2.5: {} + + asynckit@0.4.0: {} + + at-least-node@1.0.0: {} + + autolinker@4.1.5: + dependencies: + tslib: 2.8.1 + + autoprefixer@10.4.20(postcss@8.4.49): + dependencies: + browserslist: 4.24.3 + caniuse-lite: 1.0.30001690 + fraction.js: 4.3.7 + normalize-range: 0.1.2 + picocolors: 1.1.1 + postcss: 8.4.49 + postcss-value-parser: 4.2.0 + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.0.0 + + axios@1.10.0: + dependencies: + follow-redirects: 1.15.9 + form-data: 4.0.3 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + balanced-match@1.0.2: {} + + base64-js@1.5.1: {} + + bin-build@3.0.0: + dependencies: + decompress: 4.2.1 + download: 6.2.5 + execa: 0.7.0 + p-map-series: 1.0.0 + tempfile: 2.0.0 + + bin-check@4.1.0: + dependencies: + execa: 0.7.0 + executable: 4.1.1 + + bin-version-check@4.0.0: + dependencies: + bin-version: 3.1.0 + semver: 5.7.2 + semver-truncate: 1.1.2 + + bin-version@3.1.0: + dependencies: + execa: 1.0.0 + find-versions: 3.2.0 + + bin-wrapper@4.1.0: + dependencies: + bin-check: 4.1.0 + bin-version-check: 4.0.0 + download: 7.1.0 + import-lazy: 3.1.0 + os-filter-obj: 2.0.0 + pify: 4.0.1 + + binary-extensions@2.3.0: {} + + bitmap-sdf@1.0.4: {} + + bl@1.2.3: + dependencies: + readable-stream: 2.3.8 + safe-buffer: 5.2.1 + + boolbase@1.0.0: {} + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.24.3: + dependencies: + caniuse-lite: 1.0.30001690 + electron-to-chromium: 1.5.75 + node-releases: 2.0.19 + update-browserslist-db: 1.1.1(browserslist@4.24.3) + + buffer-alloc-unsafe@1.1.0: {} + + buffer-alloc@1.2.0: + dependencies: + buffer-alloc-unsafe: 1.1.0 + buffer-fill: 1.0.0 + + buffer-crc32@0.2.13: {} + + buffer-fill@1.0.0: {} + + buffer-from@1.1.2: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + cacheable-request@2.1.4: + dependencies: + clone-response: 1.0.2 + get-stream: 3.0.0 + http-cache-semantics: 3.8.1 + keyv: 3.0.0 + lowercase-keys: 1.0.0 + normalize-url: 2.0.1 + responselike: 1.0.2 + + call-bind-apply-helpers@1.0.1: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.1 + es-define-property: 1.0.1 + get-intrinsic: 1.2.6 + set-function-length: 1.2.2 + + call-bound@1.0.3: + dependencies: + call-bind-apply-helpers: 1.0.1 + get-intrinsic: 1.2.6 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + callsites@4.2.0: {} + + camelcase-css@2.0.1: {} + + camelcase-keys@2.1.0: + dependencies: + camelcase: 2.1.1 + map-obj: 1.0.1 + + camelcase@2.1.1: {} + + caniuse-lite@1.0.30001690: {} + + caw@2.0.1: + dependencies: + get-proxy: 2.1.0 + isurl: 1.0.0 + tunnel-agent: 0.6.0 + url-to-options: 1.0.1 + + cesium@1.131.0: + dependencies: + '@cesium/engine': 18.3.0 + '@cesium/widgets': 12.3.0 + + chalk@1.1.3: + dependencies: + ansi-styles: 2.2.1 + escape-string-regexp: 1.0.5 + has-ansi: 2.0.0 + strip-ansi: 3.0.1 + supports-color: 2.0.0 + + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + change-file-extension@0.1.1: {} + + chart.js@4.5.0: + dependencies: + '@kurkle/color': 0.3.4 + + chart@0.1.2: + dependencies: + hashish: 0.0.4 + hat: 0.0.3 + mrcolor: https://codeload.github.com/rook2pawn/mrcolor/tar.gz/refs/heads/master + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + clone-response@1.0.2: + dependencies: + mimic-response: 1.0.1 + + color-convert@0.2.1: {} + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@2.20.3: {} + + commander@4.1.1: {} + + commander@7.2.0: {} + + computeds@0.0.1: {} + + concat-map@0.0.1: {} + + config-chain@1.1.13: + dependencies: + ini: 1.3.8 + proto-list: 1.2.4 + + console-stream@0.1.1: {} + + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + + convert-hrtime@5.0.0: {} + + core-util-is@1.0.3: {} + + cross-spawn@5.1.0: + dependencies: + lru-cache: 4.1.5 + shebang-command: 1.2.0 + which: 1.3.1 + + cross-spawn@6.0.6: + dependencies: + nice-try: 1.0.5 + path-key: 2.0.1 + semver: 5.7.2 + shebang-command: 1.2.0 + which: 1.3.1 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + crypto-js@4.2.0: {} + + css-select@4.3.0: + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 4.3.1 + domutils: 2.8.0 + nth-check: 2.1.1 + + css-select@5.1.0: + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 5.0.3 + domutils: 3.1.0 + nth-check: 2.1.1 + + css-tree@1.1.3: + dependencies: + mdn-data: 2.0.14 + source-map: 0.6.1 + + css-tree@2.2.1: + dependencies: + mdn-data: 2.0.28 + source-map-js: 1.2.1 + + css-tree@2.3.1: + dependencies: + mdn-data: 2.0.30 + source-map-js: 1.2.1 + + css-what@6.1.0: {} + + cssesc@3.0.0: {} + + csso@4.2.0: + dependencies: + css-tree: 1.1.3 + + csso@5.0.5: + dependencies: + css-tree: 2.2.1 + + csstype@3.1.3: {} + + currently-unhandled@0.4.1: + dependencies: + array-find-index: 1.0.2 + + cwebp-bin@6.1.2: + dependencies: + bin-build: 3.0.0 + bin-wrapper: 4.1.0 + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + dayjs@1.11.13: {} + + de-indent@1.0.2: {} + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@4.4.0: + dependencies: + ms: 2.1.3 + + decamelize@1.2.0: {} + + decode-uri-component@0.2.2: {} + + decompress-response@3.3.0: + dependencies: + mimic-response: 1.0.1 + + decompress-tar@4.1.1: + dependencies: + file-type: 5.2.0 + is-stream: 1.1.0 + tar-stream: 1.6.2 + + decompress-tarbz2@4.1.1: + dependencies: + decompress-tar: 4.1.1 + file-type: 6.2.0 + is-stream: 1.1.0 + seek-bzip: 1.0.6 + unbzip2-stream: 1.4.3 + + decompress-targz@4.1.1: + dependencies: + decompress-tar: 4.1.1 + file-type: 5.2.0 + is-stream: 1.1.0 + + decompress-unzip@4.0.1: + dependencies: + file-type: 3.9.0 + get-stream: 2.3.1 + pify: 2.3.0 + yauzl: 2.10.0 + + decompress@4.2.1: + dependencies: + decompress-tar: 4.1.1 + decompress-tarbz2: 4.1.1 + decompress-targz: 4.1.1 + decompress-unzip: 4.0.1 + graceful-fs: 4.2.11 + make-dir: 1.3.0 + pify: 2.3.0 + strip-dirs: 2.1.0 + + deep-is@0.1.4: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + delayed-stream@1.0.0: {} + + depd@2.0.0: {} + + destroy@1.2.0: {} + + detect-libc@2.0.3: {} + + didyoumean@1.2.2: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + dlv@1.1.3: {} + + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + dom-serializer@1.4.1: + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + entities: 2.2.0 + + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@4.3.1: + dependencies: + domelementtype: 2.3.0 + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + dompurify@3.2.6: + optionalDependencies: + '@types/trusted-types': 2.0.7 + + domutils@2.8.0: + dependencies: + dom-serializer: 1.4.1 + domelementtype: 2.3.0 + domhandler: 4.3.1 + + domutils@3.1.0: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + dot-prop@8.0.2: + dependencies: + type-fest: 3.13.1 + + download@6.2.5: + dependencies: + caw: 2.0.1 + content-disposition: 0.5.4 + decompress: 4.2.1 + ext-name: 5.0.0 + file-type: 5.2.0 + filenamify: 2.1.0 + get-stream: 3.0.0 + got: 7.1.0 + make-dir: 1.3.0 + p-event: 1.3.0 + pify: 3.0.0 + + download@7.1.0: + dependencies: + archive-type: 4.0.0 + caw: 2.0.1 + content-disposition: 0.5.4 + decompress: 4.2.1 + ext-name: 5.0.0 + file-type: 8.1.0 + filenamify: 2.1.0 + get-stream: 3.0.0 + got: 8.3.2 + make-dir: 1.3.0 + p-event: 2.3.1 + pify: 3.0.0 + + draco3d@1.5.7: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + duplexer3@0.1.5: {} + + earcut@3.0.2: {} + + eastasianwidth@0.2.0: {} + + ee-first@1.1.1: {} + + electron-to-chromium@1.5.75: {} + + element-plus@2.10.4(vue@3.5.13(typescript@5.3.3)): + dependencies: + '@ctrl/tinycolor': 3.6.1 + '@element-plus/icons-vue': 2.3.1(vue@3.5.13(typescript@5.3.3)) + '@floating-ui/dom': 1.7.2 + '@popperjs/core': '@sxzz/popperjs-es@2.11.7' + '@types/lodash': 4.17.20 + '@types/lodash-es': 4.17.12 + '@vueuse/core': 9.13.0(vue@3.5.13(typescript@5.3.3)) + async-validator: 4.2.5 + dayjs: 1.11.13 + escape-html: 1.0.3 + lodash: 4.17.21 + lodash-es: 4.17.21 + lodash-unified: 1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.21)(lodash@4.17.21) + memoize-one: 6.0.0 + normalize-wheel-es: 1.2.0 + vue: 3.5.13(typescript@5.3.3) + transitivePeerDependencies: + - '@vue/composition-api' + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + encodeurl@1.0.2: {} + + encodeurl@2.0.0: {} + + end-of-stream@1.4.4: + dependencies: + once: 1.4.0 + + entities@2.2.0: {} + + entities@4.5.0: {} + + environment@1.1.0: {} + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + + es-abstract@1.23.7: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.3 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + es-set-tostringtag: 2.0.3 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.2.6 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-regex: 1.2.1 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.0 + math-intrinsics: 1.1.0 + object-inspect: 1.13.3 + object-keys: 1.1.1 + object.assign: 4.1.7 + regexp.prototype.flags: 1.5.3 + safe-array-concat: 1.1.3 + safe-regex-test: 1.1.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.18 + + es-abstract@1.24.0: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.19 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.0.0: + dependencies: + es-errors: 1.3.0 + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.0.3: + dependencies: + get-intrinsic: 1.2.6 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.2.6 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + esbuild-android-64@0.14.54: + optional: true + + esbuild-android-arm64@0.14.54: + optional: true + + esbuild-darwin-64@0.14.54: + optional: true + + esbuild-darwin-arm64@0.14.54: + optional: true + + esbuild-freebsd-64@0.14.54: + optional: true + + esbuild-freebsd-arm64@0.14.54: + optional: true + + esbuild-linux-32@0.14.54: + optional: true + + esbuild-linux-64@0.14.54: + optional: true + + esbuild-linux-arm64@0.14.54: + optional: true + + esbuild-linux-arm@0.14.54: + optional: true + + esbuild-linux-mips64le@0.14.54: + optional: true + + esbuild-linux-ppc64le@0.14.54: + optional: true + + esbuild-linux-riscv64@0.14.54: + optional: true + + esbuild-linux-s390x@0.14.54: + optional: true + + esbuild-netbsd-64@0.14.54: + optional: true + + esbuild-openbsd-64@0.14.54: + optional: true + + esbuild-sunos-64@0.14.54: + optional: true + + esbuild-windows-32@0.14.54: + optional: true + + esbuild-windows-64@0.14.54: + optional: true + + esbuild-windows-arm64@0.14.54: + optional: true + + esbuild@0.14.54: + optionalDependencies: + '@esbuild/linux-loong64': 0.14.54 + esbuild-android-64: 0.14.54 + esbuild-android-arm64: 0.14.54 + esbuild-darwin-64: 0.14.54 + esbuild-darwin-arm64: 0.14.54 + esbuild-freebsd-64: 0.14.54 + esbuild-freebsd-arm64: 0.14.54 + esbuild-linux-32: 0.14.54 + esbuild-linux-64: 0.14.54 + esbuild-linux-arm: 0.14.54 + esbuild-linux-arm64: 0.14.54 + esbuild-linux-mips64le: 0.14.54 + esbuild-linux-ppc64le: 0.14.54 + esbuild-linux-riscv64: 0.14.54 + esbuild-linux-s390x: 0.14.54 + esbuild-netbsd-64: 0.14.54 + esbuild-openbsd-64: 0.14.54 + esbuild-sunos-64: 0.14.54 + esbuild-windows-32: 0.14.54 + esbuild-windows-64: 0.14.54 + esbuild-windows-arm64: 0.14.54 + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@4.0.0: {} + + eslint-config-prettier@8.10.0(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + + eslint-plugin-prettier@5.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@3.4.2): + dependencies: + eslint: 8.57.1 + prettier: 3.4.2 + prettier-linter-helpers: 1.0.0 + synckit: 0.9.2 + optionalDependencies: + eslint-config-prettier: 8.10.0(eslint@8.57.1) + + eslint-plugin-vue@9.32.0(eslint@8.57.1): + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) + eslint: 8.57.1 + globals: 13.24.0 + natural-compare: 1.4.0 + nth-check: 2.1.1 + postcss-selector-parser: 6.1.2 + semver: 7.6.3 + vue-eslint-parser: 9.4.3(eslint@8.57.1) + xml-name-validator: 4.0.0 + transitivePeerDependencies: + - supports-color + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint@8.57.1: + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) + '@eslint-community/regexpp': 4.12.1 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.1 + '@humanwhocodes/config-array': 0.13.0 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.2.1 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.0 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.24.0 + graphemer: 1.4.0 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + espree@9.6.1: + dependencies: + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) + eslint-visitor-keys: 3.4.3 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@2.0.2: {} + + esutils@2.0.3: {} + + etag@1.8.1: {} + + exec-buffer@3.2.0: + dependencies: + execa: 0.7.0 + p-finally: 1.0.0 + pify: 3.0.0 + rimraf: 2.7.1 + tempfile: 2.0.0 + + execa@0.7.0: + dependencies: + cross-spawn: 5.1.0 + get-stream: 3.0.0 + is-stream: 1.1.0 + npm-run-path: 2.0.2 + p-finally: 1.0.0 + signal-exit: 3.0.7 + strip-eof: 1.0.0 + + execa@1.0.0: + dependencies: + cross-spawn: 6.0.6 + get-stream: 4.1.0 + is-stream: 1.1.0 + npm-run-path: 2.0.2 + p-finally: 1.0.0 + signal-exit: 3.0.7 + strip-eof: 1.0.0 + + execa@4.1.0: + dependencies: + cross-spawn: 7.0.6 + get-stream: 5.2.0 + human-signals: 1.1.1 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + execa@6.1.0: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 3.0.1 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 3.0.7 + strip-final-newline: 3.0.0 + + execa@8.0.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + + executable@4.1.1: + dependencies: + pify: 2.3.0 + + ext-list@2.2.2: + dependencies: + mime-db: 1.53.0 + + ext-name@5.0.0: + dependencies: + ext-list: 2.2.2 + sort-keys-length: 1.0.1 + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-equals@5.0.1: {} + + fast-glob@3.3.2: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-xml-parser@4.5.1: + dependencies: + strnum: 1.0.5 + + fastq@1.17.1: + dependencies: + reusify: 1.0.4 + + fd-slicer@1.1.0: + dependencies: + pend: 1.2.0 + + figures@1.7.0: + dependencies: + escape-string-regexp: 1.0.5 + object-assign: 4.1.1 + + file-entry-cache@6.0.1: + dependencies: + flat-cache: 3.2.0 + + file-type@10.11.0: {} + + file-type@12.4.2: {} + + file-type@19.6.0: + dependencies: + get-stream: 9.0.1 + strtok3: 9.1.1 + token-types: 6.0.0 + uint8array-extras: 1.4.0 + + file-type@3.9.0: {} + + file-type@4.4.0: {} + + file-type@5.2.0: {} + + file-type@6.2.0: {} + + file-type@8.1.0: {} + + filename-reserved-regex@2.0.0: {} + + filenamify@2.1.0: + dependencies: + filename-reserved-regex: 2.0.0 + strip-outer: 1.0.1 + trim-repeated: 1.0.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@1.1.2: + dependencies: + path-exists: 2.1.0 + pinkie-promise: 2.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + find-versions@3.2.0: + dependencies: + semver-regex: 2.0.0 + + flat-cache@3.2.0: + dependencies: + flatted: 3.3.2 + keyv: 4.5.4 + rimraf: 3.0.2 + + flatted@3.3.2: {} + + follow-redirects@1.15.9: {} + + for-each@0.3.3: + dependencies: + is-callable: 1.2.7 + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + foreground-child@3.3.0: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + form-data@4.0.3: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + fraction.js@4.3.7: {} + + fresh@0.5.2: {} + + from2@2.3.0: + dependencies: + inherits: 2.0.4 + readable-stream: 2.3.8 + + fs-constants@1.0.0: {} + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + + fs-extra@9.1.0: + dependencies: + at-least-node: 1.0.0 + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function-timeout@1.0.2: {} + + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.3 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + + get-intrinsic@1.2.6: + dependencies: + call-bind-apply-helpers: 1.0.1 + dunder-proto: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + function-bind: 1.1.2 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-intrinsic@1.3.0: + 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 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.0.0 + + get-proxy@2.1.0: + dependencies: + npm-conf: 1.1.3 + + get-stdin@4.0.1: {} + + get-stream@2.3.1: + dependencies: + object-assign: 4.1.1 + pinkie-promise: 2.0.1 + + get-stream@3.0.0: {} + + get-stream@4.1.0: + dependencies: + pump: 3.0.2 + + get-stream@5.2.0: + dependencies: + pump: 3.0.2 + + get-stream@6.0.1: {} + + get-stream@8.0.1: {} + + get-stream@9.0.1: + dependencies: + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + get-intrinsic: 1.2.6 + + gifsicle@5.2.0: + dependencies: + bin-build: 3.0.0 + bin-wrapper: 4.1.0 + execa: 5.1.1 + logalot: 2.1.0 + + gifsicle@5.3.0: + dependencies: + bin-build: 3.0.0 + bin-wrapper: 4.1.0 + execa: 5.1.1 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.4.5: + dependencies: + foreground-child: 3.3.0 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + globby@10.0.2: + dependencies: + '@types/glob': 7.2.0 + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + glob: 7.2.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + globby@14.0.2: + dependencies: + '@sindresorhus/merge-streams': 2.3.0 + fast-glob: 3.3.2 + ignore: 5.3.2 + path-type: 5.0.0 + slash: 5.1.0 + unicorn-magic: 0.1.0 + + gopd@1.2.0: {} + + got@7.1.0: + dependencies: + '@types/keyv': 3.1.4 + '@types/responselike': 1.0.3 + decompress-response: 3.3.0 + duplexer3: 0.1.5 + get-stream: 3.0.0 + is-plain-obj: 1.1.0 + is-retry-allowed: 1.2.0 + is-stream: 1.1.0 + isurl: 1.0.0 + lowercase-keys: 1.0.1 + p-cancelable: 0.3.0 + p-timeout: 1.2.1 + safe-buffer: 5.2.1 + timed-out: 4.0.1 + url-parse-lax: 1.0.0 + url-to-options: 1.0.1 + + got@8.3.2: + dependencies: + '@sindresorhus/is': 0.7.0 + '@types/keyv': 3.1.4 + '@types/responselike': 1.0.3 + cacheable-request: 2.1.4 + decompress-response: 3.3.0 + duplexer3: 0.1.5 + get-stream: 3.0.0 + into-stream: 3.1.0 + is-retry-allowed: 1.2.0 + isurl: 1.0.0 + lowercase-keys: 1.0.1 + mimic-response: 1.0.1 + p-cancelable: 0.4.1 + p-timeout: 2.0.1 + pify: 3.0.0 + safe-buffer: 5.2.1 + timed-out: 4.0.1 + url-parse-lax: 3.0.0 + url-to-options: 1.0.1 + + graceful-fs@4.2.11: {} + + grapheme-splitter@1.0.4: {} + + graphemer@1.4.0: {} + + has-ansi@2.0.0: + dependencies: + ansi-regex: 2.1.1 + + has-bigints@1.1.0: {} + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbol-support-x@1.4.2: {} + + has-symbols@1.1.0: {} + + has-to-string-tag-x@1.4.1: + dependencies: + has-symbol-support-x: 1.4.2 + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hashish@0.0.4: + dependencies: + traverse: 0.6.11 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hat@0.0.3: {} + + he@1.2.0: {} + + hosted-git-info@2.8.9: {} + + http-cache-semantics@3.8.1: {} + + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + + human-signals@1.1.1: {} + + human-signals@2.1.0: {} + + human-signals@3.0.1: {} + + human-signals@5.0.0: {} + + identifier-regex@1.0.0: + dependencies: + reserved-identifiers: 1.0.0 + + ieee754@1.2.1: {} + + ignore@5.3.2: {} + + imagemin-gifsicle@7.0.0: + dependencies: + execa: 1.0.0 + gifsicle: 5.3.0 + is-gif: 3.0.0 + + imagemin-jpegtran@7.0.0: + dependencies: + exec-buffer: 3.2.0 + is-jpg: 2.0.0 + jpegtran-bin: 5.0.2 + + imagemin-mozjpeg@10.0.0: + dependencies: + execa: 6.1.0 + is-jpg: 3.0.0 + mozjpeg: 8.0.0 + + imagemin-mozjpeg@9.0.0: + dependencies: + execa: 4.1.0 + is-jpg: 2.0.0 + mozjpeg: 7.1.1 + + imagemin-optipng@8.0.0: + dependencies: + exec-buffer: 3.2.0 + is-png: 2.0.0 + optipng-bin: 7.0.1 + + imagemin-pngquant@10.0.0: + dependencies: + environment: 1.1.0 + execa: 8.0.1 + is-png: 3.0.1 + ow: 2.0.0 + pngquant-bin: 9.0.0 + uint8array-extras: 1.4.0 + + imagemin-pngquant@9.0.2: + dependencies: + execa: 4.1.0 + is-png: 2.0.0 + is-stream: 2.0.1 + ow: 0.17.0 + pngquant-bin: 6.0.1 + + imagemin-svgo@11.0.1: + dependencies: + is-svg: 5.1.0 + svgo: 3.3.2 + + imagemin-svgo@9.0.0: + dependencies: + is-svg: 4.4.0 + svgo: 2.8.0 + + imagemin-webp@6.1.0: + dependencies: + cwebp-bin: 6.1.2 + exec-buffer: 3.2.0 + is-cwebp-readable: 3.0.0 + + imagemin@7.0.1: + dependencies: + file-type: 12.4.2 + globby: 10.0.2 + graceful-fs: 4.2.11 + junk: 3.1.0 + make-dir: 3.1.0 + p-pipe: 3.1.0 + replace-ext: 1.0.1 + + imagemin@9.0.0: + dependencies: + change-file-extension: 0.1.1 + environment: 1.1.0 + file-type: 19.6.0 + globby: 14.0.2 + junk: 4.0.1 + ow: 2.0.0 + p-pipe: 4.0.0 + slash: 5.1.0 + uint8array-extras: 1.4.0 + + import-fresh@3.3.0: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-lazy@3.1.0: {} + + imurmurhash@0.1.4: {} + + indent-string@2.1.0: + dependencies: + repeating: 2.0.1 + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + ini@1.3.8: {} + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + into-stream@3.1.0: + dependencies: + from2: 2.3.0 + p-is-promise: 1.1.0 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.3 + get-intrinsic: 1.2.6 + + is-arrayish@0.2.1: {} + + is-arrayish@0.3.2: {} + + is-async-function@2.0.0: + dependencies: + has-tostringtag: 1.0.2 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-boolean-object@1.2.1: + dependencies: + call-bound: 1.0.3 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-cwebp-readable@3.0.0: + dependencies: + file-type: 10.11.0 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.3 + get-intrinsic: 1.2.6 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.3 + has-tostringtag: 1.0.2 + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.3 + + is-finite@1.1.0: {} + + is-fullwidth-code-point@3.0.0: {} + + is-generator-function@1.0.10: + dependencies: + has-tostringtag: 1.0.2 + + is-gif@3.0.0: + dependencies: + file-type: 10.11.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-identifier@1.0.1: + dependencies: + identifier-regex: 1.0.0 + super-regex: 1.0.0 + + is-jpg@2.0.0: {} + + is-jpg@3.0.0: {} + + is-map@2.0.3: {} + + is-natural-number@4.0.1: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.3 + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-object@1.0.2: {} + + is-path-inside@3.0.3: {} + + is-plain-obj@1.1.0: {} + + is-png@2.0.0: {} + + is-png@3.0.1: {} + + is-reference@1.2.1: + dependencies: + '@types/estree': 1.0.6 + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.3 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-retry-allowed@1.2.0: {} + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.3 + + is-stream@1.1.0: {} + + is-stream@2.0.1: {} + + is-stream@3.0.0: {} + + is-stream@4.0.1: {} + + is-string@1.1.1: + dependencies: + call-bound: 1.0.3 + has-tostringtag: 1.0.2 + + is-svg@4.4.0: + dependencies: + fast-xml-parser: 4.5.1 + + is-svg@5.1.0: + dependencies: + fast-xml-parser: 4.5.1 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.3 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.18 + + is-utf8@0.2.1: {} + + is-weakmap@2.0.2: {} + + is-weakref@1.1.0: + dependencies: + call-bound: 1.0.3 + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.3 + get-intrinsic: 1.2.6 + + isarray@1.0.0: {} + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + isurl@1.0.0: + dependencies: + has-to-string-tag-x: 1.4.1 + is-object: 1.0.2 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jiti@1.21.7: {} + + jpegtran-bin@5.0.2: + dependencies: + bin-build: 3.0.0 + bin-wrapper: 4.1.0 + logalot: 2.1.0 + + jpegtran-bin@6.0.1: + dependencies: + bin-build: 3.0.0 + bin-wrapper: 4.1.0 + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsep@1.4.0: {} + + json-buffer@3.0.0: {} + + json-buffer@3.0.1: {} + + json-parse-better-errors@1.0.2: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + jsonfile@6.1.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + jsqr@1.4.0: {} + + junk@3.1.0: {} + + junk@4.0.1: {} + + kdbush@4.0.2: {} + + keyv@3.0.0: + dependencies: + json-buffer: 3.0.0 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + ktx-parse@1.0.1: {} + + lerc@2.0.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + load-json-file@1.1.0: + dependencies: + graceful-fs: 4.2.11 + parse-json: 2.2.0 + pify: 2.3.0 + pinkie-promise: 2.0.1 + strip-bom: 2.0.0 + + load-json-file@4.0.0: + dependencies: + graceful-fs: 4.2.11 + parse-json: 4.0.0 + pify: 3.0.0 + strip-bom: 3.0.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash-es@4.17.21: {} + + lodash-unified@1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.21)(lodash@4.17.21): + dependencies: + '@types/lodash-es': 4.17.12 + lodash: 4.17.21 + lodash-es: 4.17.21 + + lodash.merge@4.6.2: {} + + lodash@4.17.21: {} + + logalot@2.1.0: + dependencies: + figures: 1.7.0 + squeak: 1.3.0 + + long@5.3.2: {} + + longest@1.0.1: {} + + loud-rejection@1.6.0: + dependencies: + currently-unhandled: 0.4.1 + signal-exit: 3.0.7 + + lowercase-keys@1.0.0: {} + + lowercase-keys@1.0.1: {} + + lpad-align@1.1.2: + dependencies: + get-stdin: 4.0.1 + indent-string: 2.1.0 + longest: 1.0.1 + meow: 3.7.0 + + lru-cache@10.4.3: {} + + lru-cache@4.1.5: + dependencies: + pseudomap: 1.0.2 + yallist: 2.1.2 + + magic-string@0.25.9: + dependencies: + sourcemap-codec: 1.4.8 + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + make-dir@1.3.0: + dependencies: + pify: 3.0.0 + + make-dir@3.1.0: + dependencies: + semver: 6.3.1 + + map-obj@1.0.1: {} + + marked@15.0.12: {} + + math-intrinsics@1.1.0: {} + + mdn-data@2.0.14: {} + + mdn-data@2.0.28: {} + + mdn-data@2.0.30: {} + + memoize-one@6.0.0: {} + + memorystream@0.3.1: {} + + meow@3.7.0: + dependencies: + camelcase-keys: 2.1.0 + decamelize: 1.2.0 + loud-rejection: 1.6.0 + map-obj: 1.0.1 + minimist: 1.2.8 + normalize-package-data: 2.5.0 + object-assign: 4.1.1 + read-pkg-up: 1.0.1 + redent: 1.0.0 + trim-newlines: 1.0.0 + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + mersenne-twister@1.1.0: {} + + meshoptimizer@0.24.0: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-db@1.53.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@1.6.0: {} + + mimic-fn@2.1.0: {} + + mimic-fn@4.0.0: {} + + mimic-response@1.0.1: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimatch@9.0.3: + dependencies: + brace-expansion: 2.0.1 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + minimist@1.2.8: {} + + minipass@7.1.2: {} + + mitt@3.0.1: {} + + mozjpeg@7.1.1: + dependencies: + bin-build: 3.0.0 + bin-wrapper: 4.1.0 + + mozjpeg@8.0.0: + dependencies: + bin-build: 3.0.0 + bin-wrapper: 4.1.0 + + mrcolor@https://codeload.github.com/rook2pawn/mrcolor/tar.gz/refs/heads/master: + dependencies: + color-convert: 0.2.1 + + ms@2.0.0: {} + + ms@2.1.3: {} + + muggle-string@0.3.1: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.8: {} + + natural-compare@1.4.0: {} + + nice-try@1.0.5: {} + + node-releases@2.0.19: {} + + noise@0.0.0: {} + + normalize-package-data@2.5.0: + dependencies: + hosted-git-info: 2.8.9 + resolve: 1.22.10 + semver: 5.7.2 + validate-npm-package-license: 3.0.4 + + normalize-path@3.0.0: {} + + normalize-range@0.1.2: {} + + normalize-url@2.0.1: + dependencies: + prepend-http: 2.0.0 + query-string: 5.1.1 + sort-keys: 2.0.0 + + normalize-wheel-es@1.2.0: {} + + nosleep.js@0.12.0: {} + + npm-conf@1.1.3: + dependencies: + config-chain: 1.1.13 + pify: 3.0.0 + + npm-run-all@4.1.5: + dependencies: + ansi-styles: 3.2.1 + chalk: 2.4.2 + cross-spawn: 6.0.6 + memorystream: 0.3.1 + minimatch: 3.1.2 + pidtree: 0.3.1 + read-pkg: 3.0.0 + shell-quote: 1.8.2 + string.prototype.padend: 3.1.6 + + npm-run-path@2.0.2: + dependencies: + path-key: 2.0.1 + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + object-inspect@1.13.3: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.3 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + optipng-bin@7.0.1: + dependencies: + bin-build: 3.0.0 + bin-wrapper: 4.1.0 + + os-filter-obj@2.0.0: + dependencies: + arch: 2.2.0 + + otplib@12.0.1: + dependencies: + '@otplib/core': 12.0.1 + '@otplib/preset-default': 12.0.1 + '@otplib/preset-v11': 12.0.1 + + ow@0.17.0: + dependencies: + type-fest: 0.11.0 + + ow@2.0.0: + dependencies: + '@sindresorhus/is': 6.3.1 + callsites: 4.2.0 + dot-prop: 8.0.2 + environment: 1.1.0 + fast-equals: 5.0.1 + is-identifier: 1.0.1 + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-cancelable@0.3.0: {} + + p-cancelable@0.4.1: {} + + p-event@1.3.0: + dependencies: + p-timeout: 1.2.1 + + p-event@2.3.1: + dependencies: + p-timeout: 2.0.1 + + p-finally@1.0.0: {} + + p-is-promise@1.1.0: {} + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-map-series@1.0.0: + dependencies: + p-reduce: 1.0.0 + + p-pipe@3.1.0: {} + + p-pipe@4.0.0: {} + + p-reduce@1.0.0: {} + + p-timeout@1.2.1: + dependencies: + p-finally: 1.0.0 + + p-timeout@2.0.1: + dependencies: + p-finally: 1.0.0 + + package-json-from-dist@1.0.1: {} + + pako@2.1.0: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@2.2.0: + dependencies: + error-ex: 1.3.2 + + parse-json@4.0.0: + dependencies: + error-ex: 1.3.2 + json-parse-better-errors: 1.0.2 + + parseurl@1.3.3: {} + + path-browserify@1.0.1: {} + + path-exists@2.1.0: + dependencies: + pinkie-promise: 2.0.1 + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@2.0.1: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + path-type@1.1.0: + dependencies: + graceful-fs: 4.2.11 + pify: 2.3.0 + pinkie-promise: 2.0.1 + + path-type@3.0.0: + dependencies: + pify: 3.0.0 + + path-type@4.0.0: {} + + path-type@5.0.0: {} + + pathe@0.2.0: {} + + pathe@1.1.2: {} + + peek-readable@5.3.1: {} + + pend@1.2.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + pidtree@0.3.1: {} + + pify@2.3.0: {} + + pify@3.0.0: {} + + pify@4.0.1: {} + + pinia@2.3.0(typescript@5.3.3)(vue@3.5.13(typescript@5.3.3)): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.13(typescript@5.3.3) + vue-demi: 0.14.10(vue@3.5.13(typescript@5.3.3)) + optionalDependencies: + typescript: 5.3.3 + transitivePeerDependencies: + - '@vue/composition-api' + + pinkie-promise@2.0.1: + dependencies: + pinkie: 2.0.4 + + pinkie@2.0.4: {} + + pirates@4.0.6: {} + + pngquant-bin@6.0.1: + dependencies: + bin-build: 3.0.0 + bin-wrapper: 4.1.0 + execa: 4.1.0 + + pngquant-bin@9.0.0: + dependencies: + bin-build: 3.0.0 + bin-wrapper: 4.1.0 + execa: 8.0.1 + + possible-typed-array-names@1.0.0: {} + + postcss-import@15.1.0(postcss@8.4.49): + dependencies: + postcss: 8.4.49 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.10 + + postcss-js@4.0.1(postcss@8.4.49): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.4.49 + + postcss-load-config@4.0.2(postcss@8.4.49): + dependencies: + lilconfig: 3.1.3 + yaml: 2.6.1 + optionalDependencies: + postcss: 8.4.49 + + postcss-nested@6.2.0(postcss@8.4.49): + dependencies: + postcss: 8.4.49 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.4.49: + dependencies: + nanoid: 3.3.8 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prepend-http@1.0.4: {} + + prepend-http@2.0.0: {} + + prettier-linter-helpers@1.0.0: + dependencies: + fast-diff: 1.3.0 + + prettier@3.4.2: {} + + process-nextick-args@2.0.1: {} + + proto-list@1.2.4: {} + + protobufjs@7.5.3: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 20.17.10 + long: 5.3.2 + + proxy-from-env@1.1.0: {} + + pseudomap@1.0.2: {} + + pump@3.0.2: + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + + punycode@2.3.1: {} + + qrcode.vue@3.6.0(vue@3.5.13(typescript@5.3.3)): + dependencies: + vue: 3.5.13(typescript@5.3.3) + + query-string@5.1.1: + dependencies: + decode-uri-component: 0.2.2 + object-assign: 4.1.1 + strict-uri-encode: 1.1.0 + + queue-microtask@1.2.3: {} + + quickselect@3.0.0: {} + + range-parser@1.2.1: {} + + rbush@4.0.1: + dependencies: + quickselect: 3.0.0 + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + read-pkg-up@1.0.1: + dependencies: + find-up: 1.1.2 + read-pkg: 1.1.0 + + read-pkg@1.1.0: + dependencies: + load-json-file: 1.1.0 + normalize-package-data: 2.5.0 + path-type: 1.1.0 + + read-pkg@3.0.0: + dependencies: + load-json-file: 4.0.0 + normalize-package-data: 2.5.0 + path-type: 3.0.0 + + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + redent@1.0.0: + dependencies: + indent-string: 2.1.0 + strip-indent: 1.0.1 + + reflect.getprototypeof@1.0.9: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + dunder-proto: 1.0.1 + es-abstract: 1.23.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.6 + gopd: 1.2.0 + which-builtin-type: 1.2.1 + + regexp.prototype.flags@1.5.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + set-function-name: 2.0.2 + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + repeating@2.0.1: + dependencies: + is-finite: 1.1.0 + + replace-ext@1.0.1: {} + + reserved-identifiers@1.0.0: {} + + resolve-from@4.0.0: {} + + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + responselike@1.0.2: + dependencies: + lowercase-keys: 1.0.1 + + reusify@1.0.4: {} + + rimraf@2.7.1: + dependencies: + glob: 7.2.3 + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + rollup-plugin-external-globals@0.6.1(rollup@4.29.1): + dependencies: + '@rollup/pluginutils': 4.2.1 + estree-walker: 2.0.2 + is-reference: 1.2.1 + magic-string: 0.25.9 + rollup: 4.29.1 + + rollup@4.29.1: + dependencies: + '@types/estree': 1.0.6 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.29.1 + '@rollup/rollup-android-arm64': 4.29.1 + '@rollup/rollup-darwin-arm64': 4.29.1 + '@rollup/rollup-darwin-x64': 4.29.1 + '@rollup/rollup-freebsd-arm64': 4.29.1 + '@rollup/rollup-freebsd-x64': 4.29.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.29.1 + '@rollup/rollup-linux-arm-musleabihf': 4.29.1 + '@rollup/rollup-linux-arm64-gnu': 4.29.1 + '@rollup/rollup-linux-arm64-musl': 4.29.1 + '@rollup/rollup-linux-loongarch64-gnu': 4.29.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.29.1 + '@rollup/rollup-linux-riscv64-gnu': 4.29.1 + '@rollup/rollup-linux-s390x-gnu': 4.29.1 + '@rollup/rollup-linux-x64-gnu': 4.29.1 + '@rollup/rollup-linux-x64-musl': 4.29.1 + '@rollup/rollup-win32-arm64-msvc': 4.29.1 + '@rollup/rollup-win32-ia32-msvc': 4.29.1 + '@rollup/rollup-win32-x64-msvc': 4.29.1 + fsevents: 2.3.3 + + rss-parser@3.13.0: + dependencies: + entities: 2.2.0 + xml2js: 0.5.0 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.3 + get-intrinsic: 1.2.6 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + is-regex: 1.2.1 + + sax@1.4.1: {} + + seek-bzip@1.0.6: + dependencies: + commander: 2.20.3 + + semver-regex@2.0.0: {} + + semver-truncate@1.1.2: + dependencies: + semver: 5.7.2 + + semver@5.7.2: {} + + semver@6.3.1: {} + + semver@7.6.3: {} + + send@0.19.0: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + + serve-static@1.16.2: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.0 + transitivePeerDependencies: + - supports-color + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.6 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + + setprototypeof@1.2.0: {} + + sharp@0.33.5: + dependencies: + color: 4.2.3 + detect-libc: 2.0.3 + semver: 7.6.3 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.33.5 + '@img/sharp-darwin-x64': 0.33.5 + '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-linux-arm': 1.0.5 + '@img/sharp-libvips-linux-arm64': 1.0.4 + '@img/sharp-libvips-linux-s390x': 1.0.4 + '@img/sharp-libvips-linux-x64': 1.0.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@img/sharp-linux-arm': 0.33.5 + '@img/sharp-linux-arm64': 0.33.5 + '@img/sharp-linux-s390x': 0.33.5 + '@img/sharp-linux-x64': 0.33.5 + '@img/sharp-linuxmusl-arm64': 0.33.5 + '@img/sharp-linuxmusl-x64': 0.33.5 + '@img/sharp-wasm32': 0.33.5 + '@img/sharp-win32-ia32': 0.33.5 + '@img/sharp-win32-x64': 0.33.5 + + shebang-command@1.2.0: + dependencies: + shebang-regex: 1.0.0 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@1.0.0: {} + + shebang-regex@3.0.0: {} + + shell-quote@1.8.2: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.3 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + get-intrinsic: 1.2.6 + object-inspect: 1.13.3 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + get-intrinsic: 1.2.6 + object-inspect: 1.13.3 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + 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 + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + simple-swizzle@0.2.2: + dependencies: + is-arrayish: 0.3.2 + + slash@3.0.0: {} + + slash@5.1.0: {} + + sort-keys-length@1.0.1: + dependencies: + sort-keys: 1.1.2 + + sort-keys@1.1.2: + dependencies: + is-plain-obj: 1.1.0 + + sort-keys@2.0.0: + dependencies: + is-plain-obj: 1.1.0 + + source-map-js@1.2.1: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + sourcemap-codec@1.4.8: {} + + spdx-correct@3.2.0: + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.20 + + spdx-exceptions@2.5.0: {} + + spdx-expression-parse@3.0.1: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.20 + + spdx-license-ids@3.0.20: {} + + squeak@1.3.0: + dependencies: + chalk: 1.1.3 + console-stream: 0.1.1 + lpad-align: 1.1.2 + + stable@0.1.8: {} + + statuses@2.0.1: {} + + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + + strict-uri-encode@1.1.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + string.prototype.padend@3.1.6: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.7 + es-object-atoms: 1.0.0 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.3 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.23.7 + es-object-atoms: 1.0.0 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.3 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + strip-ansi@3.0.1: + dependencies: + ansi-regex: 2.1.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.1.0 + + strip-bom@2.0.0: + dependencies: + is-utf8: 0.2.1 + + strip-bom@3.0.0: {} + + strip-dirs@2.1.0: + dependencies: + is-natural-number: 4.0.1 + + strip-eof@1.0.0: {} + + strip-final-newline@2.0.0: {} + + strip-final-newline@3.0.0: {} + + strip-indent@1.0.1: + dependencies: + get-stdin: 4.0.1 + + strip-json-comments@3.1.1: {} + + strip-outer@1.0.1: + dependencies: + escape-string-regexp: 1.0.5 + + strnum@1.0.5: {} + + strtok3@9.1.1: + dependencies: + '@tokenizer/token': 0.3.0 + peek-readable: 5.3.1 + + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + + super-regex@1.0.0: + dependencies: + function-timeout: 1.0.2 + time-span: 5.1.0 + + supports-color@2.0.0: {} + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + svgo@2.8.0: + dependencies: + '@trysound/sax': 0.2.0 + commander: 7.2.0 + css-select: 4.3.0 + css-tree: 1.1.3 + csso: 4.2.0 + picocolors: 1.1.1 + stable: 0.1.8 + + svgo@3.3.2: + dependencies: + '@trysound/sax': 0.2.0 + commander: 7.2.0 + css-select: 5.1.0 + css-tree: 2.3.1 + css-what: 6.1.0 + csso: 5.0.5 + picocolors: 1.1.1 + + synckit@0.9.2: + dependencies: + '@pkgr/core': 0.1.1 + tslib: 2.8.1 + + tailwindcss@3.4.17: + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.2 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 3.1.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.4.49 + postcss-import: 15.1.0(postcss@8.4.49) + postcss-js: 4.0.1(postcss@8.4.49) + postcss-load-config: 4.0.2(postcss@8.4.49) + postcss-nested: 6.2.0(postcss@8.4.49) + postcss-selector-parser: 6.1.2 + resolve: 1.22.10 + sucrase: 3.35.0 + transitivePeerDependencies: + - ts-node + + tar-stream@1.6.2: + dependencies: + bl: 1.2.3 + buffer-alloc: 1.2.0 + end-of-stream: 1.4.4 + fs-constants: 1.0.0 + readable-stream: 2.3.8 + to-buffer: 1.1.1 + xtend: 4.0.2 + + temp-dir@1.0.0: {} + + tempfile@2.0.0: + dependencies: + temp-dir: 1.0.0 + uuid: 3.4.0 + + terser@5.37.0: + dependencies: + '@jridgewell/source-map': 0.3.6 + acorn: 8.14.0 + commander: 2.20.3 + source-map-support: 0.5.21 + + text-table@0.2.0: {} + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + thirty-two@1.0.2: {} + + through@2.3.8: {} + + time-span@5.1.0: + dependencies: + convert-hrtime: 5.0.0 + + timed-out@4.0.1: {} + + to-buffer@1.1.1: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + token-types@6.0.0: + dependencies: + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 + + topojson-client@3.1.0: + dependencies: + commander: 2.20.3 + + traverse@0.6.11: + dependencies: + gopd: 1.2.0 + typedarray.prototype.slice: 1.0.5 + which-typed-array: 1.1.18 + + trim-newlines@1.0.0: {} + + trim-repeated@1.0.0: + dependencies: + escape-string-regexp: 1.0.5 + + ts-api-utils@1.4.3(typescript@5.3.3): + dependencies: + typescript: 5.3.3 + + ts-interface-checker@0.1.13: {} + + tslib@2.8.1: {} + + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@0.11.0: {} + + type-fest@0.20.2: {} + + type-fest@3.13.1: {} + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.3 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.3 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.9 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.3 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.0.0 + reflect.getprototypeof: 1.0.9 + + typedarray.prototype.slice@1.0.5: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + get-proto: 1.0.1 + math-intrinsics: 1.1.0 + typed-array-buffer: 1.0.3 + typed-array-byte-offset: 1.0.4 + + typescript@5.3.3: {} + + uint8array-extras@1.4.0: {} + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.3 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + unbzip2-stream@1.4.3: + dependencies: + buffer: 5.7.1 + through: 2.3.8 + + undici-types@6.19.8: {} + + unicorn-magic@0.1.0: {} + + universalify@2.0.1: {} + + update-browserslist-db@1.1.1(browserslist@4.24.3): + dependencies: + browserslist: 4.24.3 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + urijs@1.19.11: {} + + url-parse-lax@1.0.0: + dependencies: + prepend-http: 1.0.4 + + url-parse-lax@3.0.0: + dependencies: + prepend-http: 2.0.0 + + url-to-options@1.0.1: {} + + util-deprecate@1.0.2: {} + + uuid@11.1.0: {} + + uuid@3.4.0: {} + + validate-npm-package-license@3.0.4: + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 + + vite-plugin-cesium@1.2.23(cesium@1.131.0)(rollup@4.29.1)(vite@5.4.19(@types/node@20.17.10)(terser@5.37.0)): + dependencies: + cesium: 1.131.0 + fs-extra: 9.1.0 + rollup-plugin-external-globals: 0.6.1(rollup@4.29.1) + serve-static: 1.16.2 + vite: 5.4.19(@types/node@20.17.10)(terser@5.37.0) + transitivePeerDependencies: + - rollup + - supports-color + + vite-plugin-compression@0.5.1(vite@5.4.19(@types/node@20.17.10)(terser@5.37.0)): + dependencies: + chalk: 4.1.2 + debug: 4.4.0 + fs-extra: 10.1.0 + vite: 5.4.19(@types/node@20.17.10)(terser@5.37.0) + transitivePeerDependencies: + - supports-color + + vite-plugin-image-optimizer@1.1.8(vite@5.4.19(@types/node@20.17.10)(terser@5.37.0)): + dependencies: + ansi-colors: 4.1.3 + pathe: 1.1.2 + vite: 5.4.19(@types/node@20.17.10)(terser@5.37.0) + + vite-plugin-imagemin@0.6.1(vite@5.4.19(@types/node@20.17.10)(terser@5.37.0)): + dependencies: + '@types/imagemin': 7.0.1 + '@types/imagemin-gifsicle': 7.0.4 + '@types/imagemin-jpegtran': 5.0.4 + '@types/imagemin-mozjpeg': 8.0.4 + '@types/imagemin-optipng': 5.2.4 + '@types/imagemin-svgo': 10.0.5 + '@types/imagemin-webp': 7.0.3 + '@types/svgo': 2.6.4 + chalk: 4.1.2 + debug: 4.4.0 + esbuild: 0.14.54 + fs-extra: 10.1.0 + gifsicle: 5.2.0 + imagemin: 7.0.1 + imagemin-gifsicle: 7.0.0 + imagemin-jpegtran: 7.0.0 + imagemin-mozjpeg: 9.0.0 + imagemin-optipng: 8.0.0 + imagemin-pngquant: 9.0.2 + imagemin-svgo: 9.0.0 + imagemin-webp: 6.1.0 + jpegtran-bin: 6.0.1 + pathe: 0.2.0 + vite: 5.4.19(@types/node@20.17.10)(terser@5.37.0) + transitivePeerDependencies: + - supports-color + + vite@5.4.19(@types/node@20.17.10)(terser@5.37.0): + dependencies: + esbuild: 0.21.5 + postcss: 8.4.49 + rollup: 4.29.1 + optionalDependencies: + '@types/node': 20.17.10 + fsevents: 2.3.3 + terser: 5.37.0 + + vue-demi@0.14.10(vue@3.5.13(typescript@5.3.3)): + dependencies: + vue: 3.5.13(typescript@5.3.3) + + vue-eslint-parser@9.4.3(eslint@8.57.1): + dependencies: + debug: 4.4.0 + eslint: 8.57.1 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.6.0 + lodash: 4.17.21 + semver: 7.6.3 + transitivePeerDependencies: + - supports-color + + vue-router@4.5.0(vue@3.5.13(typescript@5.3.3)): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.13(typescript@5.3.3) + + vue-template-compiler@2.7.16: + dependencies: + de-indent: 1.0.2 + he: 1.2.0 + + vue-tsc@1.8.27(typescript@5.3.3): + dependencies: + '@volar/typescript': 1.11.1 + '@vue/language-core': 1.8.27(typescript@5.3.3) + semver: 7.6.3 + typescript: 5.3.3 + + vue@3.5.13(typescript@5.3.3): + dependencies: + '@vue/compiler-dom': 3.5.13 + '@vue/compiler-sfc': 3.5.13 + '@vue/runtime-dom': 3.5.13 + '@vue/server-renderer': 3.5.13(vue@3.5.13(typescript@5.3.3)) + '@vue/shared': 3.5.13 + optionalDependencies: + typescript: 5.3.3 + + vuetify@3.9.0(typescript@5.3.3)(vue@3.5.13(typescript@5.3.3)): + dependencies: + vue: 3.5.13(typescript@5.3.3) + optionalDependencies: + typescript: 5.3.3 + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.1 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.3 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.0.0 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.0.10 + is-regex: 1.2.1 + is-weakref: 1.1.0 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.18 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.18: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.3 + for-each: 0.3.3 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which-typed-array@1.1.19: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@1.3.1: + dependencies: + isexe: 2.0.0 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + wrappy@1.0.2: {} + + xml-name-validator@4.0.0: {} + + xml2js@0.5.0: + dependencies: + sax: 1.4.1 + xmlbuilder: 11.0.1 + + xmlbuilder@11.0.1: {} + + xtend@4.0.2: {} + + yallist@2.1.2: {} + + yaml@2.6.1: {} + + yauzl@2.10.0: + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + + yocto-queue@0.1.0: {} diff --git a/reijm-read/postcss.config.js b/reijm-read/postcss.config.js new file mode 100644 index 0000000..2aa7205 --- /dev/null +++ b/reijm-read/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/reijm-read/public/manifest.json b/reijm-read/public/manifest.json new file mode 100644 index 0000000..7f53f80 --- /dev/null +++ b/reijm-read/public/manifest.json @@ -0,0 +1,22 @@ +{ + "name": "ReisaSpasol | MaimaiDX", + "short_name": "ReisaSpasol", + "description": "专注于Java、Spring Boot、微服务等后端技术开发的个人作品集网站", + "start_url": "/", + "display": "standalone", + "background_color": "#ffffff", + "theme_color": "#4F46E5", + + "icons": [ + { + "src": "./assets/icon.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "./assets/icon.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} diff --git a/reijm-read/public/robots.txt b/reijm-read/public/robots.txt new file mode 100644 index 0000000..771e8ba --- /dev/null +++ b/reijm-read/public/robots.txt @@ -0,0 +1,3 @@ +User-agent: * +Allow: / +Sitemap: <%= config.siteUrl %>/sitemap.xml \ No newline at end of file diff --git a/reijm-read/public/sitemap.xml b/reijm-read/public/sitemap.xml new file mode 100644 index 0000000..a265cb5 --- /dev/null +++ b/reijm-read/public/sitemap.xml @@ -0,0 +1,27 @@ + + + + <%= config.siteUrl %>/ + 2024-03-21 + weekly + 1.0 + + + <%= config.siteUrl %>/blog + 2024-03-21 + weekly + 0.8 + + + <%= config.siteUrl %>/skills + 2024-03-21 + monthly + 0.8 + + + <%= config.siteUrl %>/contact + 2024-03-21 + monthly + 0.7 + + \ No newline at end of file diff --git a/reijm-read/public/vite.svg b/reijm-read/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/reijm-read/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/reijm-read/src/App.vue b/reijm-read/src/App.vue new file mode 100644 index 0000000..f27619b --- /dev/null +++ b/reijm-read/src/App.vue @@ -0,0 +1,148 @@ + + + + + + diff --git a/reijm-read/src/assets/1photogrid.png b/reijm-read/src/assets/1photogrid.png new file mode 100644 index 0000000..8538805 Binary files /dev/null and b/reijm-read/src/assets/1photogrid.png differ diff --git a/reijm-read/src/assets/20180621142015_5FmGZ.jpeg b/reijm-read/src/assets/20180621142015_5FmGZ.jpeg new file mode 100644 index 0000000..c681910 Binary files /dev/null and b/reijm-read/src/assets/20180621142015_5FmGZ.jpeg differ diff --git a/reijm-read/src/assets/a.jpg b/reijm-read/src/assets/a.jpg new file mode 100644 index 0000000..6f44fe5 Binary files /dev/null and b/reijm-read/src/assets/a.jpg differ diff --git a/reijm-read/src/assets/icon.png b/reijm-read/src/assets/icon.png new file mode 100644 index 0000000..87f9839 Binary files /dev/null and b/reijm-read/src/assets/icon.png differ diff --git a/reijm-read/src/assets/icon.psd b/reijm-read/src/assets/icon.psd new file mode 100644 index 0000000..0c44468 Binary files /dev/null and b/reijm-read/src/assets/icon.psd differ diff --git a/reijm-read/src/assets/mmexport3ce9739b640a7e4ce6464c7392896ad6_1743239672087.png b/reijm-read/src/assets/mmexport3ce9739b640a7e4ce6464c7392896ad6_1743239672087.png new file mode 100644 index 0000000..c9f56c7 Binary files /dev/null and b/reijm-read/src/assets/mmexport3ce9739b640a7e4ce6464c7392896ad6_1743239672087.png differ diff --git a/reijm-read/src/assets/page/3f562d98148099080ddd72e7899c3379.png b/reijm-read/src/assets/page/3f562d98148099080ddd72e7899c3379.png new file mode 100644 index 0000000..fe49400 Binary files /dev/null and b/reijm-read/src/assets/page/3f562d98148099080ddd72e7899c3379.png differ diff --git a/reijm-read/src/assets/page/ad387886e0895e4eff57e5813ced97b1.png b/reijm-read/src/assets/page/ad387886e0895e4eff57e5813ced97b1.png new file mode 100644 index 0000000..51a175f Binary files /dev/null and b/reijm-read/src/assets/page/ad387886e0895e4eff57e5813ced97b1.png differ diff --git a/reijm-read/src/assets/page/b40bc5ad04d9d3f34fff37a1e31687b6.png b/reijm-read/src/assets/page/b40bc5ad04d9d3f34fff37a1e31687b6.png new file mode 100644 index 0000000..b78dfcf Binary files /dev/null and b/reijm-read/src/assets/page/b40bc5ad04d9d3f34fff37a1e31687b6.png differ diff --git a/reijm-read/src/assets/page/faa5939484a2004b958f4fa501633e03.png b/reijm-read/src/assets/page/faa5939484a2004b958f4fa501633e03.png new file mode 100644 index 0000000..e7f3d10 Binary files /dev/null and b/reijm-read/src/assets/page/faa5939484a2004b958f4fa501633e03.png differ diff --git a/reijm-read/src/assets/page/img.png b/reijm-read/src/assets/page/img.png new file mode 100644 index 0000000..2445007 Binary files /dev/null and b/reijm-read/src/assets/page/img.png differ diff --git a/reijm-read/src/assets/styles/colors.css b/reijm-read/src/assets/styles/colors.css new file mode 100644 index 0000000..7c9b612 --- /dev/null +++ b/reijm-read/src/assets/styles/colors.css @@ -0,0 +1,48 @@ +:root { + /* 主色调 */ + --color-primary: #3b82f6; + --color-primary-dark: #2563eb; + --color-primary-light: #60a5fa; + --color-primary-10: rgba(59, 130, 246, 0.1); + + /* 背景色 */ + --color-bg-main: #ffffff; + --color-bg-secondary: #f9fafb; + --color-bg-tertiary: #f3f4f6; + + /* 文本颜色 */ + --color-text-primary: #111827; + --color-text-secondary: #4b5563; + --color-text-tertiary: #6b7280; + + /* 边框颜色 */ + --color-border: #e5e7eb; + --color-border-light: #f3f4f6; + + /* 卡片阴影 */ + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); +} + +/* 深色模式 */ +:root[class~="dark"] { + /* 背景色 */ + --color-bg-main: #111827; + --color-bg-secondary: #1f2937; + --color-bg-tertiary: #374151; + + /* 文本颜色 */ + --color-text-primary: #f9fafb; + --color-text-secondary: #e5e7eb; + --color-text-tertiary: #d1d5db; + + /* 边框颜色 */ + --color-border: #374151; + --color-border-light: #1f2937; + + /* 卡片阴影 */ + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3); + --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.3), 0 1px 2px -1px rgb(0 0 0 / 0.3); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.3), 0 2px 4px -2px rgb(0 0 0 / 0.3); +} diff --git a/reijm-read/src/assets/styles/main.css b/reijm-read/src/assets/styles/main.css new file mode 100644 index 0000000..bc74997 --- /dev/null +++ b/reijm-read/src/assets/styles/main.css @@ -0,0 +1,72 @@ +@import "./colors.css"; +@import "./variables.css"; +@import "tailwindcss/base"; +@import "tailwindcss/components"; +@import "tailwindcss/utilities"; + +/* 添加字体定义 */ +@font-face { + font-family: "LXWK"; + font-weight: 100 900; + font-display: swap; + font-style: normal; + src: url("https://cdn.jsdmirror.com/gh/acanyo/mmm.sd@master/assets/font/lxwk.woff2") + format("woff2"); +} + +@layer base { + html { + font-family: + "LXWK", + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + Roboto, + "Helvetica Neue", + Arial, + sans-serif; + scroll-behavior: smooth; + -webkit-tap-highlight-color: transparent; + } + + body { + @apply bg-main text-primary antialiased; + } + + /* 移动端优化 */ + @media (max-width: 768px) { + html { + font-size: 14px; + } + } + + /* 移动端点击态优化 */ + @media (hover: none) { + .hover\:scale-105:active { + transform: scale(1.02); + } + } +} + +@layer components { + .btn-primary { + @apply inline-block px-6 py-3 bg-primary text-white rounded-lg + hover:bg-primary-dark transition-colors duration-300; + } + + .btn-secondary { + @apply inline-block px-6 py-3 border-2 border-primary text-primary rounded-lg + hover:bg-primary hover:text-white transition-colors duration-300; + } + + .card { + @apply bg-main border border-light rounded-2xl shadow-sm + hover:shadow-md transition-all duration-300; + } +} + +/* 移动端滚动优化 */ +.smooth-scroll { + -webkit-overflow-scrolling: touch; + scroll-behavior: smooth; +} diff --git a/reijm-read/src/assets/styles/variables.css b/reijm-read/src/assets/styles/variables.css new file mode 100644 index 0000000..5d7c9c6 --- /dev/null +++ b/reijm-read/src/assets/styles/variables.css @@ -0,0 +1,16 @@ +:root { + --font-family-custom: "LXWK"; + --font-family-system: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + "Helvetica Neue", Arial, sans-serif; + --font-family: var(--font-family-custom), var(--font-family-system); +} + +/* 添加字体定义 */ +@font-face { + font-family: "LXWK"; + font-weight: 100 900; + font-display: swap; + font-style: normal; + src: url("https://cdn.jsdmirror.com/gh/acanyo/mmm.sd@master/assets/font/lxwk.woff2") + format("woff2"); +} diff --git a/reijm-read/src/assets/styles/图层 1_PhotoGrid.png b/reijm-read/src/assets/styles/图层 1_PhotoGrid.png new file mode 100644 index 0000000..8538805 Binary files /dev/null and b/reijm-read/src/assets/styles/图层 1_PhotoGrid.png differ diff --git a/reijm-read/src/assets/useTs1.png b/reijm-read/src/assets/useTs1.png new file mode 100644 index 0000000..b943270 Binary files /dev/null and b/reijm-read/src/assets/useTs1.png differ diff --git a/reijm-read/src/assets/vue.svg b/reijm-read/src/assets/vue.svg new file mode 100644 index 0000000..770e9d3 --- /dev/null +++ b/reijm-read/src/assets/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/reijm-read/src/components/PageTransition.vue b/reijm-read/src/components/PageTransition.vue new file mode 100644 index 0000000..70c078c --- /dev/null +++ b/reijm-read/src/components/PageTransition.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/reijm-read/src/components/ProgressBar.vue b/reijm-read/src/components/ProgressBar.vue new file mode 100644 index 0000000..8d625c5 --- /dev/null +++ b/reijm-read/src/components/ProgressBar.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/reijm-read/src/components/ThemeToggle.vue b/reijm-read/src/components/ThemeToggle.vue new file mode 100644 index 0000000..661235c --- /dev/null +++ b/reijm-read/src/components/ThemeToggle.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/reijm-read/src/components/effects/Fireworks.vue b/reijm-read/src/components/effects/Fireworks.vue new file mode 100644 index 0000000..d201133 --- /dev/null +++ b/reijm-read/src/components/effects/Fireworks.vue @@ -0,0 +1,159 @@ + + + diff --git a/reijm-read/src/components/layout/TheFooter.vue b/reijm-read/src/components/layout/TheFooter.vue new file mode 100644 index 0000000..f8f46ad --- /dev/null +++ b/reijm-read/src/components/layout/TheFooter.vue @@ -0,0 +1,82 @@ + + + diff --git a/reijm-read/src/components/layout/TheHeader.vue b/reijm-read/src/components/layout/TheHeader.vue new file mode 100644 index 0000000..07561c3 --- /dev/null +++ b/reijm-read/src/components/layout/TheHeader.vue @@ -0,0 +1,205 @@ + + + + + + + diff --git a/reijm-read/src/components/layout/ToolLayout.vue b/reijm-read/src/components/layout/ToolLayout.vue new file mode 100644 index 0000000..6d75243 --- /dev/null +++ b/reijm-read/src/components/layout/ToolLayout.vue @@ -0,0 +1,27 @@ + + + diff --git a/reijm-read/src/components/ui/LazyImage.vue b/reijm-read/src/components/ui/LazyImage.vue new file mode 100644 index 0000000..a32b078 --- /dev/null +++ b/reijm-read/src/components/ui/LazyImage.vue @@ -0,0 +1,48 @@ + + + diff --git a/reijm-read/src/components/ui/Modal.vue b/reijm-read/src/components/ui/Modal.vue new file mode 100644 index 0000000..3d60986 --- /dev/null +++ b/reijm-read/src/components/ui/Modal.vue @@ -0,0 +1,149 @@ + + + + + diff --git a/reijm-read/src/components/ui/Tabs.vue b/reijm-read/src/components/ui/Tabs.vue new file mode 100644 index 0000000..e837612 --- /dev/null +++ b/reijm-read/src/components/ui/Tabs.vue @@ -0,0 +1,44 @@ + + + diff --git a/reijm-read/src/components/ui/Toast.vue b/reijm-read/src/components/ui/Toast.vue new file mode 100644 index 0000000..7291c70 --- /dev/null +++ b/reijm-read/src/components/ui/Toast.vue @@ -0,0 +1,61 @@ + + + diff --git a/reijm-read/src/components/ui/WarningDialog.vue b/reijm-read/src/components/ui/WarningDialog.vue new file mode 100644 index 0000000..4272cef --- /dev/null +++ b/reijm-read/src/components/ui/WarningDialog.vue @@ -0,0 +1,89 @@ + + + + + diff --git a/reijm-read/src/config/blog.ts b/reijm-read/src/config/blog.ts new file mode 100644 index 0000000..fb054e2 --- /dev/null +++ b/reijm-read/src/config/blog.ts @@ -0,0 +1,42 @@ + + +export interface BlogPost { + title: string; + category: string; + date: Date; + link: string; + data: string; + summary: string; // 添加简介字段 +} + +export const blogPosts: BlogPost[] = [ + { + title: "FindMaimaiDX", + data: ` + # FindMaimai + `, + summary: "FindMaimai是一个基于Java开发全平台客户端", + link: "https://example.com/vue3-features", + date: new Date("2023-10-01"), + category: "Maimai", + }, + { + title: "TypeScript 在大型项目中的应用", + data: `## TypeScript 在大型项目中的应用 + +探讨如何在大型项目中使用 TypeScript 提高代码质量和开发效率。 + +### 类型检查 + +TypeScript 提供了强大的类型检查功能,帮助开发者减少运行时错误。 + +### 代码重构 + +使用 TypeScript 可以更容易地进行代码重构,提高代码的可维护性。`, + link: "https://example.com/typescript-large-projects", + summary: "TypeScript 在大型项目中的应用", + date: new Date("2023-09-15"), + category: "TypeScript", + }, + // 添加更多博客文章... +]; diff --git a/reijm-read/src/config/email.ts b/reijm-read/src/config/email.ts new file mode 100644 index 0000000..e0bfb73 --- /dev/null +++ b/reijm-read/src/config/email.ts @@ -0,0 +1,25 @@ +import emailjs from "@emailjs/browser"; + +interface EmailConfig { + serviceId: string; + templateId: string; + publicKey: string; +} + +export const emailConfig: EmailConfig = { + serviceId: import.meta.env.VITE_EMAILJS_SERVICE_ID, + templateId: import.meta.env.VITE_EMAILJS_TEMPLATE_ID, + publicKey: import.meta.env.VITE_EMAILJS_PUBLIC_KEY, +}; + +export const initEmailJS = () => { + emailjs.init(emailConfig.publicKey); +}; + +if ( + !emailConfig.serviceId || + !emailConfig.templateId || + !emailConfig.publicKey +) { + throw new Error("Missing required EmailJS configuration"); +} diff --git a/reijm-read/src/config/font.ts b/reijm-read/src/config/font.ts new file mode 100644 index 0000000..5a4a3b1 --- /dev/null +++ b/reijm-read/src/config/font.ts @@ -0,0 +1,17 @@ +interface FontConfig { + enabled: boolean; + name: string; + url: string; + preload?: boolean; + display?: "auto" | "block" | "swap" | "fallback" | "optional"; + weights?: string; +} + +export const fontConfig: FontConfig = { + enabled: true, + name: "LXWK", + url: "https://cdn.jsdmirror.com/gh/acanyo/mmm.sd@master/assets/font/lxwk.woff2", + preload: true, + display: "swap", + weights: "100 900", +}; diff --git a/reijm-read/src/config/footer.ts b/reijm-read/src/config/footer.ts new file mode 100644 index 0000000..7be7e79 --- /dev/null +++ b/reijm-read/src/config/footer.ts @@ -0,0 +1,39 @@ +interface FooterLink { + text: string; + to?: string; // 内部路由 + href?: string; // 外部链接 + target?: string; +} + +interface FooterConfig { + links: FooterLink[]; + provider: { + name: string; + link: string; + logo: string; + text: string; + }; +} + +export const footerConfig: FooterConfig = { + links: [ + + + { + text: "博客", + href: "https://www.godserver.cn", + target: "_blank", + }, + { + text: "GitHub", + href: "https://github.com/Spaso1", + target: "_blank", + }, + ], + provider: { + name: "Aliyun", + link: "https://www.aliyun.com/", + logo: "https://avatars.githubusercontent.com/u/172407636?v=4", + text: "提供 CDN 加速 / 云存储服务", + }, +}; diff --git a/reijm-read/src/config/index.ts b/reijm-read/src/config/index.ts new file mode 100644 index 0000000..cb18476 --- /dev/null +++ b/reijm-read/src/config/index.ts @@ -0,0 +1,46 @@ +import { siteConfig } from "./site"; + +// 导出所有配置 +export { siteConfig }; + +// 合并基础配置 +export const config = { + ...siteConfig, + siteUrl: "https://www.godserver.cn", // 默认值 +}; + +// 生成 sitemap.xml 内容 +export const generateSitemap = ( + siteUrl: string, +) => ` + + + ${siteUrl}/ + 2024-03-21 + weekly + 1.0 + + + ${siteUrl}/blog + 2024-03-21 + weekly + 0.8 + + + ${siteUrl}/skills + 2024-03-21 + monthly + 0.8 + + + ${siteUrl}/contact + 2024-03-21 + monthly + 0.7 + +`; + +// 生成 robots.txt 内容 +export const generateRobots = (siteUrl: string) => `User-agent: * +Allow: / +Sitemap: ${siteUrl}/sitemap.xml`; diff --git a/reijm-read/src/config/navigation.ts b/reijm-read/src/config/navigation.ts new file mode 100644 index 0000000..b5ce81e --- /dev/null +++ b/reijm-read/src/config/navigation.ts @@ -0,0 +1,11 @@ +export interface Tab { + id: string; + label: string; + icon: string; +} + +export const tabs: Tab[] = [ + { id: "projects", label: "项目展示", icon: "🎨" }, + { id: "tools", label: "在线工具", icon: "🛠" }, + { id: "bookmarks", label: "网址导航", icon: "🔖" }, +]; diff --git a/reijm-read/src/config/notice.ts b/reijm-read/src/config/notice.ts new file mode 100644 index 0000000..e6b19d7 --- /dev/null +++ b/reijm-read/src/config/notice.ts @@ -0,0 +1,53 @@ +import type { NoticeButton, NoticeConfig } from "../types/notice"; + +interface ExtendedNoticeButton extends NoticeButton { + type: "primary" | "secondary" | "danger"; +} + +interface ExtendedNoticeConfig extends NoticeConfig { + enabled: boolean; + showFireworks: boolean; + defaultShowAfter?: number | "refresh" | null; + buttons: ExtendedNoticeButton[]; +} + +export const noticeConfig: ExtendedNoticeConfig = { + id: "site_notice_v1", + enabled: true, + showFireworks: true, + title: "网站公告", + content: ` +
+

+ 🎉 网站改版升级公告 +

+
+

网站已完成改版升级,新增以下功能:

+
    +
  • 全新的深色模式支持
  • +
  • 性能优化与体验提升
  • +
  • 更多实用工具正在开发中
  • +
+
+
+ `, + width: "500px", + maskClosable: true, + showClose: true, + defaultShowAfter: null, + buttons: [ + { + text: "稍后查看", + type: "secondary", + action: "close", + showAfter: "refresh", + }, + { + text: "立即体验", + type: "primary", + action: "navigate", + to: "/projects", + showAfter: 3 * 60 * 60 * 1000, + }, + ], +}; diff --git a/reijm-read/src/config/projects.ts b/reijm-read/src/config/projects.ts new file mode 100644 index 0000000..1534dd5 --- /dev/null +++ b/reijm-read/src/config/projects.ts @@ -0,0 +1,41 @@ +export interface Project { + id: number; + title: string; + description: string; + tags: string[]; + image: string; + link?: string; + status: "completed" | "developing" | "planning"; +} + +export const projects: Project[] = [ + { + id: 2, + title: "FindMaimaiUltra", + description: + "全新重构版本 Powered By Reisa", + tags: ["技术分享", "Blog", "Markdown","舞萌DX","中二节奏","B50","查分器","旅行"], + image: "https://picsum.photos/800/600?random=3", + link: "https://github.com/Spaso1/FindMaimaiDX_Phone", + status: "completed", + }, + { + id: 3, + title: "EasyTop", + description: "服务状态监控页面,实时监控各项服务的运行状态。", + tags: ["监控", "服务状态", "实时数据"], + image: "https://picsum.photos/800/600?random=4", + link: "https://github.com/Spaso1/EasyTop", + status: "completed", + }, + { + id: 4, + title: "AsTrip", + description: + "旅行规划软件", + tags: ["数据分析", "统计", "开源","旅行"], + image: "https://picsum.photos/800/600?random=5", + link: "https://github.com/Spaso1/Astrip", + status: "completed", + }, +]; diff --git a/reijm-read/src/config/rss.ts b/reijm-read/src/config/rss.ts new file mode 100644 index 0000000..f93fc86 --- /dev/null +++ b/reijm-read/src/config/rss.ts @@ -0,0 +1,7 @@ +interface RssConfig { + url: string; +} + +export const rssConfig: RssConfig = { + url: "https://www.godserver.cn/rss.xml", // 直接使用完整 URL +}; diff --git a/reijm-read/src/config/site-info.ts b/reijm-read/src/config/site-info.ts new file mode 100644 index 0000000..4425e46 --- /dev/null +++ b/reijm-read/src/config/site-info.ts @@ -0,0 +1,20 @@ +interface SiteInfo { + enabled: boolean; + text: string; + link: string; + position?: "top" | "bottom"; + theme?: "dark" | "light"; + style?: string; + linkStyle?: string; + version?: string; +} + +export const siteInfo: SiteInfo = { + enabled: true, + text: "一个使用 Vue 3 + TypeScript + Vite 构建的现代化个人主页,具有博客文章展示、项目展示、联系表单等功能。", + version: "V.2.3", + link: "https://github.com/Spaso1/ReisaPage", + position: "bottom", + theme: "dark", + style: "position: fixed; bottom: 0; left: 0; width: 100%; z-index: 1000;", +}; diff --git a/reijm-read/src/config/site.ts b/reijm-read/src/config/site.ts new file mode 100644 index 0000000..fc250d7 --- /dev/null +++ b/reijm-read/src/config/site.ts @@ -0,0 +1,39 @@ +export const siteConfig = { + // 基本信息 + name: "Powered by Reisa", // 作者名称 + title: "FindMaimaiDX开发者 学生", // 职位头衔 + siteName: "ReisaSpasol | MaimaiDX", // 网站标题 + siteDescription: + "专注于Java、Spring Boot、微服务等后端技术开发的个人作品集网站", // 网站描述 + author: "ReisaSpasol", // 作者信息 + + // 图片资源配置 + images: { + logo: "./assets/icon.png", // 网站Logo + icon: "./assets/icon.png", // 网站图标 + avatar: "./assets/icon.png", // 个人头像 + ogImage: "./assets/icon.png", // 社交分享图片 + }, + + // 个性化配置 + slogan: "Use FindMaimai!", // 个性签名 + skills: ["Java", "Spring Boot", "MySQL", "Vue", "Docker", "Git","FindMaimai"], // 技能标签 + + // SEO 相关配置 + language: "zh-CN", // 网站语言 + themeColor: "#4F46E5", // 主题色 + twitterHandle: "@Spasolmodlic", // Twitter账号 + githubHandle: "Spaso1", // GitHub账号 + + // Schema.org 结构化数据 + organization: { + name: "Reisa", // 组织名称 + logo: "./assets/icon.png", // 组织Logo + }, + + // 社交媒体链接 + social: { + github: "https://github.com/acanyo", // GitHub主页 + email: "astralpath@163.com", // 联系邮箱 + }, +}; diff --git a/reijm-read/src/config/tools.ts b/reijm-read/src/config/tools.ts new file mode 100644 index 0000000..1e4a351 --- /dev/null +++ b/reijm-read/src/config/tools.ts @@ -0,0 +1,33 @@ +import JsonFormatterView from "@/views/tools/JsonFormatterView.vue"; +import TimestampView from "@/views/tools/TimestampView.vue"; + +export interface Tool { + id: number; + title: string; + description: string; + tags: string[]; + image: string; + component: any; + status: "completed" | "developing" | "planning"; +} + +export const tools: Tool[] = [ + { + id: 1, + title: "JSON 格式化工具", + description: "在线 JSON 格式化工具,支持压缩、美化、验证和转换等功能", + tags: ["JSON", "格式化", "在线工具"], + image: "https://picsum.photos/800/600?random=1", + component: JsonFormatterView, + status: "completed", + }, + { + id: 2, + title: "时间戳转换器", + description: "时间戳与日期格式互转工具,支持多种格式和时区设置", + tags: ["时间戳", "日期转换", "时区"], + image: "https://picsum.photos/800/600?random=2", + component: TimestampView, + status: "completed", + }, +]; diff --git a/reijm-read/src/env.d.ts b/reijm-read/src/env.d.ts new file mode 100644 index 0000000..c951361 --- /dev/null +++ b/reijm-read/src/env.d.ts @@ -0,0 +1,167 @@ +/// + +declare global { + interface ImportMetaEnv { + readonly VITE_APP_TITLE: string; + readonly VITE_APP_DESCRIPTION: string; + readonly VITE_APP_KEYWORDS: string; + readonly VITE_APP_AUTHOR: string; + readonly VITE_APP_URL: string; + readonly VITE_APP_LOGO: string; + readonly VITE_APP_GITHUB: string; + readonly VITE_APP_TWITTER: string; + readonly VITE_APP_TWITTER_URL: string; + readonly VITE_APP_THEME_COLOR: string; + readonly VITE_EMAILJS_SERVICE_ID: string; + readonly VITE_EMAILJS_TEMPLATE_ID: string; + readonly VITE_EMAILJS_PUBLIC_KEY: string; + readonly VITE_SITE_URL: string; + readonly DEV: boolean; + readonly PROD: boolean; + readonly MODE: string; + } + + interface ImportMeta { + readonly env: ImportMetaEnv; + readonly hot?: { + readonly data: any; + accept(): void; + accept(cb: (mod: any) => void): void; + accept(dep: string, cb: (mod: any) => void): void; + accept(deps: string[], cb: (mods: any[]) => void): void; + prune(cb: () => void): void; + dispose(cb: (data: any) => void): void; + decline(): void; + invalidate(): void; + on(event: string, cb: (...args: any[]) => void): void; + }; + readonly glob: (glob: string) => Record Promise>; + } +} + +// Vue 组件类型声明 +declare module "*.vue" { + import type { DefineComponent } from "vue"; + const component: DefineComponent<{}, {}, any>; + export default component; +} + +// Vue 宏命令类型声明 +declare module "vue" { + import type { DefineComponent, Ref } from "vue"; + + // 生命周期钩子 + export declare const onMounted: (cb: () => void) => void; + export declare const onBeforeMount: (cb: () => void) => void; + export declare const onBeforeUnmount: (cb: () => void) => void; + export declare const onUnmounted: (cb: () => void) => void; + export declare const onActivated: (cb: () => void) => void; + export declare const onDeactivated: (cb: () => void) => void; + export declare const onBeforeUpdate: (cb: () => void) => void; + export declare const onUpdated: (cb: () => void) => void; + export declare const onErrorCaptured: (cb: (err: unknown) => void) => void; + + // 组合式 API + export declare const createApp: any; + export declare const ref: (value: T) => Ref; + export declare const computed: (getter: () => T) => Ref; + export declare const watch: typeof import("vue").watch; + export declare const watchEffect: (effect: () => void) => void; + export declare const reactive: (target: T) => T; + export declare const readonly: (target: T) => Readonly; + + // 组件相关 + export declare const defineProps: { + >(): Readonly; + >(props: T): Readonly; + }; + + export declare const defineEmits: { + >(): T; + >(emits: T): T; + }; + + export declare const defineExpose: (exposed?: Record) => void; + export declare const withDefaults: < + Props, + Defaults extends { [K in keyof Props]?: Props[K] }, + >( + props: Props, + defaults: Defaults, + ) => { + [K in keyof Props]: K extends keyof Defaults ? Defaults[K] : Props[K]; + }; +} + +// 第三方模块声明 +declare module "vite" { + import type { UserConfig, Plugin } from "vite"; + + export interface ViteConfig extends UserConfig { + plugins?: Plugin[]; + } + + export const defineConfig: (config: T) => T; +} + +declare module "vue-router" { + import type { Component } from "vue"; + + export interface RouteMeta { + title?: string; + description?: string; + keywords?: string; + transition?: string; + requiresAuth?: boolean; + [key: string]: any; + } + + export interface RouteRecordRaw { + path: string; + name?: string; + component?: Component | (() => Promise); + components?: { [key: string]: Component }; + redirect?: string | { name: string }; + meta?: RouteMeta; + children?: RouteRecordRaw[]; + } + + export interface Router { + push(to: string | { name: string; params?: any }): Promise; + } + + export interface Route { + meta: RouteMeta; + params: Record; + query: Record; + hash: string; + path: string; + fullPath: string; + matched: RouteRecordRaw[]; + } + + // 添加 RouterLink 组件类型 + export interface RouterLinkProps { + to: string | { name: string; params?: Record }; + replace?: boolean; + activeClass?: string; + exactActiveClass?: string; + custom?: boolean; + ariaCurrentValue?: string; + } + + export const RouterLink: Component; + export const RouterView: Component; + export const createRouter: any; + export const createWebHistory: any; + export const useRoute: () => Route; + export const useRouter: () => Router; +} + +declare module "@emailjs/browser" { + const emailjs: any; + export default emailjs; +} + +declare module "vite-plugin-compression"; +declare module "vite-plugin-image-optimizer"; diff --git a/reijm-read/src/eventBus.ts b/reijm-read/src/eventBus.ts new file mode 100644 index 0000000..65d4820 --- /dev/null +++ b/reijm-read/src/eventBus.ts @@ -0,0 +1,9 @@ +import mitt from 'mitt'; + +type Events = { + 'refresh-user-info': void; +}; + +const eventBus = mitt(); + +export default eventBus; diff --git a/reijm-read/src/main.ts b/reijm-read/src/main.ts new file mode 100644 index 0000000..f07e33d --- /dev/null +++ b/reijm-read/src/main.ts @@ -0,0 +1,14 @@ +import { createApp } from "vue"; +import App from "./App.vue"; +import router from "./router"; +import "./assets/styles/main.css"; +import { initFontLoading } from "./utils/font"; + +const app = createApp(App); +app.use(router); +app.mount("#app"); + +// 初始化字体加载 +initFontLoading().then(() => { + console.log("Font initialization complete"); +}); diff --git a/reijm-read/src/router/index.ts b/reijm-read/src/router/index.ts new file mode 100644 index 0000000..a994cb3 --- /dev/null +++ b/reijm-read/src/router/index.ts @@ -0,0 +1,49 @@ +import { createRouter, createWebHistory } from "vue-router"; +import type { RouteRecordRaw } from "vue-router"; + +const routes: RouteRecordRaw[] = [ + { + path: "/", + name: "home", + component: () => import("@/views/HomeView.vue"), + meta: { + title: "ReiJM", + }, + }, + { + path: "/user", + name: "user", + component: () => import("@/views/User.vue"), + meta: { + title: "user", + } + }, + { + path: "/manga/:id", + name: "manga", + component: () => import("@/views/Manga.vue"), + meta: { + title: "manga", + hideHeader: true // 添加此元信息 + } + } +]; + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes, + scrollBehavior(to, from, savedPosition) { + if (savedPosition) { + return savedPosition; + } + return { top: 0 }; + }, +}); + +// 路由标题 +router.beforeEach((to, from, next) => { + document.title = `${to.meta.title || "首页"} | Reisa`; + next(); +}); + +export default router; diff --git a/reijm-read/src/services/rss.ts b/reijm-read/src/services/rss.ts new file mode 100644 index 0000000..78b8a31 --- /dev/null +++ b/reijm-read/src/services/rss.ts @@ -0,0 +1,51 @@ +export interface BlogPost { + title: string; + link: string; + content: string; + creator: string; + pubDate: string; + categories?: string[]; + description?: string; +} + +export async function fetchBlogPosts(rssUrl: string): Promise { + try { + const response = await fetch(rssUrl, { + headers: { + Accept: "application/xml, text/xml, */*", + }, + }); + const xmlText = await response.text(); + const parser = new DOMParser(); + const xmlDoc = parser.parseFromString(xmlText, "text/xml"); + + const items = xmlDoc.querySelectorAll("item"); + + return Array.from(items).map((item) => { + const getElementText = (tagName: string) => + item.querySelector(tagName)?.textContent?.trim() || ""; + + const getCleanContent = (content: string) => { + return content.replace("", ""); + }; + + const description = getElementText("description"); + const content = description.includes("CDATA") + ? getCleanContent(description) + : description; + + return { + title: getCleanContent(getElementText("title")), + link: getElementText("link"), + content: content, + creator: "Reisa", + pubDate: getElementText("pubDate"), + categories: [getElementText("category")].filter(Boolean), + description: content, + }; + }); + } catch (error) { + console.error("获取博客文章失败:", error); + return []; + } +} diff --git a/reijm-read/src/style.css b/reijm-read/src/style.css new file mode 100644 index 0000000..f691315 --- /dev/null +++ b/reijm-read/src/style.css @@ -0,0 +1,79 @@ +:root { + font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +.card { + padding: 2em; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/reijm-read/src/types/blog.ts b/reijm-read/src/types/blog.ts new file mode 100644 index 0000000..2124d89 --- /dev/null +++ b/reijm-read/src/types/blog.ts @@ -0,0 +1,8 @@ +export interface BlogPost { + title: string; + link: string; + date: Date; + description: string; + category?: string; + image?: string; +} diff --git a/reijm-read/src/types/global.d.ts b/reijm-read/src/types/global.d.ts new file mode 100644 index 0000000..f83922e --- /dev/null +++ b/reijm-read/src/types/global.d.ts @@ -0,0 +1,17 @@ +export {}; // 确保文件被视为模块 + +interface Window { + $toast: { + show: (text: string, type?: "success" | "error" | "info") => void; + success: (text: string) => void; + error: (text: string) => void; + info: (text: string) => void; + }; + liquidGlass?: { + destroy: () => void; + }; +} +declare module '*.js' { + const content: any; + export default content; +} diff --git a/reijm-read/src/types/notice.ts b/reijm-read/src/types/notice.ts new file mode 100644 index 0000000..e5c0526 --- /dev/null +++ b/reijm-read/src/types/notice.ts @@ -0,0 +1,19 @@ +export interface NoticeButton { + text: string; + type?: string; + action: "close" | "navigate" | "link" | "custom"; + to?: string; + href?: string; + handler?: () => void; + showAfter?: number | "refresh" | null; +} + +export interface NoticeConfig { + id: string; + title: string; + content: string; + width?: string; + maskClosable?: boolean; + showClose?: boolean; + buttons: NoticeButton[]; +} diff --git a/reijm-read/src/utils/console.ts b/reijm-read/src/utils/console.ts new file mode 100644 index 0000000..edaa25c --- /dev/null +++ b/reijm-read/src/utils/console.ts @@ -0,0 +1,77 @@ +interface ConsoleInfo { + text: string; + version: string | undefined; + link: string; + style?: string; +} + +export const printConsoleInfo = (info: ConsoleInfo) => { + // 标题样式 + const titleStyle = [ + "background: linear-gradient(45deg, #2193b0, #6dd5ed)", + "color: white", + "padding: 12px 20px", + "border-radius: 4px 0 0 4px", + "font-weight: bold", + "font-size: 13px", + "text-shadow: 0 1px 1px rgba(0,0,0,0.2)", + "box-shadow: inset 0 -3px 0 rgba(0,0,0,0.1)", + ].join(";"); + + // 版本样式 + const versionStyle = [ + "background: linear-gradient(45deg, #6dd5ed, #2193b0)", + "color: white", + "padding: 12px 20px", + "font-weight: bold", + "font-size: 13px", + "text-shadow: 0 1px 1px rgba(0,0,0,0.2)", + "box-shadow: inset 0 -3px 0 rgba(0,0,0,0.1)", + ].join(";"); + + // 链接样式 + const linkStyle = [ + "background: linear-gradient(45deg, #2193b0, #6dd5ed)", + "color: white", + "padding: 12px 20px", + "border-radius: 0 4px 4px 0", + "font-weight: bold", + "font-size: 13px", + "text-shadow: 0 1px 1px rgba(0,0,0,0.2)", + "box-shadow: inset 0 -3px 0 rgba(0,0,0,0.1)", + ].join(";"); + + // 主信息 + console.log( + `%c ${info.text} %c ${info.version || ""} %c ${info.link} `, + titleStyle, + versionStyle, + linkStyle, + ); + + // 欢迎信息 + const welcomeStyle = [ + "color: #2193b0", + "font-size: 14px", + "font-weight: bold", + "padding: 12px 20px", + "margin: 20px 0", + "border: 2px solid #2193b0", + "border-radius: 4px", + "background: rgba(33,147,176,0.1)", + "text-shadow: 0 1px 1px rgba(255,255,255,0.8)", + ].join(";"); + + console.log("%c欢迎访问我的个人主页!", welcomeStyle); + + // 装饰线 + const lineStyle = [ + "font-size: 1px", + "padding: 0", + "margin: 4px 0", + "line-height: 1px", + "background: linear-gradient(to right, #2193b0, #6dd5ed)", + ].join(";"); + + console.log("%c ", `${lineStyle}; padding: 2px 125px;`); +}; diff --git a/reijm-read/src/utils/copyright.ts b/reijm-read/src/utils/copyright.ts new file mode 100644 index 0000000..8700c68 --- /dev/null +++ b/reijm-read/src/utils/copyright.ts @@ -0,0 +1,42 @@ +/** + * @license + * Copyright (c) 2024 Reisa + * + * This file is part of the project and must retain the author's credit. + * Modifications to this file must maintain original attribution. + * Commercial use requires explicit permission. + */ + +// 使用一个自执行函数来增加混淆难度 +export const createCopyrightGuard = (() => { + const key = btoa("Reisa" + new Date().getFullYear()); + + return (element: HTMLElement | null) => { + if (!element) return false; + + // 随机检查函数 + const checks = [ + () => element.textContent?.includes("©"), + () => element.textContent?.includes("Reisa"), + () => element.textContent?.includes("All rights"), + () => element.querySelector("a")?.href.includes("godserver.cn"), + () => !element.textContent?.includes("Modified"), + () => element.children.length >= 3, + ]; + + // 随机打乱检查顺序 + const shuffledChecks = checks.sort(() => Math.random() - 0.5); + + // 执行所有检查 + const isValid = shuffledChecks.every((check) => { + try { + return check(); + } catch { + return false; + } + }); + + + return isValid; + }; +})(); diff --git a/reijm-read/src/utils/encryptionUtil.d.ts b/reijm-read/src/utils/encryptionUtil.d.ts new file mode 100644 index 0000000..bea3287 --- /dev/null +++ b/reijm-read/src/utils/encryptionUtil.d.ts @@ -0,0 +1,10 @@ +declare module '@/utils/encryptionUtil' { + const encryptionUtil: { + methods: { + encryptAndCompress(data: string): string; + decompressAndDecrypt(encryptedData: string, key?: any, iv?: any): string; + removePadding(byteArray: Uint8Array): Uint8Array; + }; + }; + export default encryptionUtil; +} diff --git a/reijm-read/src/utils/encryptionUtil.js b/reijm-read/src/utils/encryptionUtil.js new file mode 100644 index 0000000..2cd20f3 --- /dev/null +++ b/reijm-read/src/utils/encryptionUtil.js @@ -0,0 +1,121 @@ +import CryptoJS from 'crypto-js'; +import pako from 'pako'; + +const key2 = CryptoJS.enc.Utf8.parse(',Lscj312.;[]sc`1dsajcjc;;wislacx'); // 32字节的密钥 +const iv2 = CryptoJS.enc.Utf8.parse(',>ew:[7890;,wd[2'); // 16字节的IV + +// 新增PKCS5Padding移除函数(更健壮) +function removePadding(data) { + if (data.length === 0) return data; + + const paddingLength = data[data.length - 1]; + + + if (data.length < paddingLength) { + console.warn("Padding length greater than data length"); + return data; + } + + return data.slice(0, data.length - paddingLength); +} + +// 辅助函数:打印ArrayBuffer内容(调试用) +function arrayBufferToString(buffer) { + return [...new Uint8Array(buffer)].map(b => b.toString(16).padStart(2, '0')).join(' '); +} + +export default { + methods: { + encryptAndCompress(data) { + try { + // 压缩数据 + const dataArray = new TextEncoder().encode(data); + const compressed = pako.gzip(dataArray); + + // 加密数据 + const wordArray = CryptoJS.lib.WordArray.create(compressed); + const encrypted = CryptoJS.AES.encrypt(wordArray, key2, { + iv: iv2, + mode: CryptoJS.mode.CBC, + padding: CryptoJS.pad.Pkcs7 + }); + + return encrypted.toString(); + } catch (error) { + console.error("Encryption error:", error); + throw error; + } + }, + decompressAndDecrypt(encryptedData, key = key2, iv = iv2) { + try { + if (!encryptedData || encryptedData.length < 16) { + throw new Error("Invalid or empty encrypted data"); + } + + // 确保 Base64 格式正确 + const properB64Str = encryptedData.trim().replace(/\s+/g, ''); + if (!/^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$/.test(properB64Str)) { + throw new Error("Invalid Base64 string"); + } + // 解密数据 + const cipherParams = CryptoJS.lib.CipherParams.create({ ciphertext: CryptoJS.enc.Base64.parse(properB64Str) }); + const decrypted = CryptoJS.AES.decrypt(cipherParams, key, { + iv: iv, + padding: CryptoJS.pad.Pkcs7 + }); + if (!decrypted || !decrypted.words) { + throw new Error("Decryption failed or returned invalid data"); + } + function wordArrayToUint8Array(wordArray) { + const byteArray = []; + const words = wordArray.words; + const sigBytes = wordArray.sigBytes; + + for (let i = 0; i < sigBytes; i++) { + byteArray.push((words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff); + } + + return new Uint8Array(byteArray); + } + const compressedArray = wordArrayToUint8Array(decrypted); + const debugHex = [...compressedArray.slice(0, 32)].map(b => b.toString(16).padStart(2, '0')).join(' '); + // 移除PKCS7 Padding + const decryptedWithoutPadding = removePadding(compressedArray); + // 手动验证是否是合法的GZIP格式 + if (decryptedWithoutPadding.length < 10 || + (decryptedWithoutPadding[0] !== 0x1f || decryptedWithoutPadding[1] !== 0x8b)) { + // 调试输出前32字节 + const debugHexPostPadding = [...decryptedWithoutPadding.slice(0, 32)].map(b => b.toString(16).padStart(2, '0')).join(''); + return ''; + } + + // 解压数据 + let decompressed; + try { + decompressed = pako.ungzip(decryptedWithoutPadding); + } catch (e) { + console.error("GZIP decompression failed", e); + throw new Error("Failed to decompress data - invalid gzip format"); + } + + if (!decompressed || decompressed.length === 0) { + throw new Error("Empty decompressed data"); + } + + // 解压成功,返回解压后的文本数据 + return new TextDecoder().decode(decompressed); + } catch (error) { + console.error("Decryption error:", error); + throw error; + } + }, + +// 移除PKCS7 Padding的函数 + removePadding(byteArray) { + // 假设是PKCS7 padding,最后一个字节的值即为填充的字节数 + const paddingLength = byteArray[byteArray.length - 1]; + return byteArray.slice(0, byteArray.length - paddingLength); + } + + } +}; diff --git a/reijm-read/src/utils/font.ts b/reijm-read/src/utils/font.ts new file mode 100644 index 0000000..2705313 --- /dev/null +++ b/reijm-read/src/utils/font.ts @@ -0,0 +1,21 @@ +export const initFontLoading = async () => { + try { + // 等待字体加载 + await document.fonts.load('1em "LXWK"'); + + // 检查字体是否加载成功 + const isLoaded = document.fonts.check('1em "LXWK"'); + console.log("Font loaded:", isLoaded); + + if (!isLoaded) { + // 如果字体加载失败,使用系统字体 + document.documentElement.style.fontFamily = + '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'; + } + } catch (error) { + console.error("Font loading error:", error); + // 出错时使用系统字体 + document.documentElement.style.fontFamily = + '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'; + } +}; diff --git a/reijm-read/src/utils/liquid_glass.js b/reijm-read/src/utils/liquid_glass.js new file mode 100644 index 0000000..af464e9 --- /dev/null +++ b/reijm-read/src/utils/liquid_glass.js @@ -0,0 +1,292 @@ +// Vanilla JS Liquid Glass Effect - Paste into browser console +// Created by Shu Ding (https://github.com/shuding/liquid-glass) in 2025. + +(function() { + 'use strict'; + + // Check if liquid glass already exists and destroy it + if (window.liquidGlass) { + window.liquidGlass.destroy(); + console.log('Previous liquid glass effect removed.'); + } + + // Utility functions + function smoothStep(a, b, t) { + t = Math.max(0, Math.min(1, (t - a) / (b - a))); + return t * t * (3 - 2 * t); + } + + function length(x, y) { + return Math.sqrt(x * x + y * y); + } + + function roundedRectSDF(x, y, width, height, radius) { + const qx = Math.abs(x) - width + radius; + const qy = Math.abs(y) - height + radius; + return Math.min(Math.max(qx, qy), 0) + length(Math.max(qx, 0), Math.max(qy, 0)) - radius; + } + + function texture(x, y) { + return { type: 't', x, y }; + } + + // Generate unique ID + function generateId() { + return 'liquid-glass-' + Math.random().toString(36).substr(2, 9); + } + + // Main Shader class + class Shader { + constructor(options = {}) { + this.width = options.width || 100; + this.height = options.height || 100; + this.fragment = options.fragment || ((uv) => texture(uv.x, uv.y)); + this.canvasDPI = 1; + this.id = generateId(); + this.offset = 10; // Viewport boundary offset + + this.mouse = { x: 0, y: 0 }; + this.mouseUsed = false; + + this.createElement(); + this.setupEventListeners(); + this.updateShader(); + } + + createElement() { + // Create container + this.container = document.createElement('div'); + this.container.style.cssText = ` + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: ${this.width}px; + height: ${this.height}px; + overflow: hidden; + border-radius: 150px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.25), 0 -10px 25px inset rgba(0, 0, 0, 0.15); + cursor: grab; + backdrop-filter: url(#${this.id}_filter) blur(0.25px) contrast(1.2) brightness(1.05) saturate(1.1); + z-index: 9999; + pointer-events: auto; + `; + + // Create SVG filter + this.svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + this.svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); + this.svg.setAttribute('width', '0'); + this.svg.setAttribute('height', '0'); + this.svg.style.cssText = ` + position: fixed; + top: 0; + left: 0; + pointer-events: none; + z-index: 9998; + `; + + const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs'); + const filter = document.createElementNS('http://www.w3.org/2000/svg', 'filter'); + filter.setAttribute('id', `${this.id}_filter`); + filter.setAttribute('filterUnits', 'userSpaceOnUse'); + filter.setAttribute('colorInterpolationFilters', 'sRGB'); + filter.setAttribute('x', '0'); + filter.setAttribute('y', '0'); + filter.setAttribute('width', this.width.toString()); + filter.setAttribute('height', this.height.toString()); + + this.feImage = document.createElementNS('http://www.w3.org/2000/svg', 'feImage'); + this.feImage.setAttribute('id', `${this.id}_map`); + this.feImage.setAttribute('width', this.width.toString()); + this.feImage.setAttribute('height', this.height.toString()); + + this.feDisplacementMap = document.createElementNS('http://www.w3.org/2000/svg', 'feDisplacementMap'); + this.feDisplacementMap.setAttribute('in', 'SourceGraphic'); + this.feDisplacementMap.setAttribute('in2', `${this.id}_map`); + this.feDisplacementMap.setAttribute('xChannelSelector', 'R'); + this.feDisplacementMap.setAttribute('yChannelSelector', 'G'); + + filter.appendChild(this.feImage); + filter.appendChild(this.feDisplacementMap); + defs.appendChild(filter); + this.svg.appendChild(defs); + + // Create canvas for displacement map (hidden) + this.canvas = document.createElement('canvas'); + this.canvas.width = this.width * this.canvasDPI; + this.canvas.height = this.height * this.canvasDPI; + this.canvas.style.display = 'none'; + + this.context = this.canvas.getContext('2d'); + } + + constrainPosition(x, y) { + const viewportWidth = window.innerWidth; + const viewportHeight = window.innerHeight; + + // Calculate boundaries with offset + const minX = this.offset; + const maxX = viewportWidth - this.width - this.offset; + const minY = this.offset; + const maxY = viewportHeight - this.height - this.offset; + + // Constrain position + const constrainedX = Math.max(minX, Math.min(maxX, x)); + const constrainedY = Math.max(minY, Math.min(maxY, y)); + + return { x: constrainedX, y: constrainedY }; + } + + setupEventListeners() { + let isDragging = false; + let startX, startY, initialX, initialY; + + this.container.addEventListener('mousedown', (e) => { + isDragging = true; + this.container.style.cursor = 'grabbing'; + startX = e.clientX; + startY = e.clientY; + const rect = this.container.getBoundingClientRect(); + initialX = rect.left; + initialY = rect.top; + e.preventDefault(); + }); + + document.addEventListener('mousemove', (e) => { + if (isDragging) { + const deltaX = e.clientX - startX; + const deltaY = e.clientY - startY; + + // Calculate new position + const newX = initialX + deltaX; + const newY = initialY + deltaY; + + // Constrain position within viewport bounds + const constrained = this.constrainPosition(newX, newY); + + this.container.style.left = constrained.x + 'px'; + this.container.style.top = constrained.y + 'px'; + this.container.style.transform = 'none'; + } + + // Update mouse position for shader + const rect = this.container.getBoundingClientRect(); + this.mouse.x = (e.clientX - rect.left) / rect.width; + this.mouse.y = (e.clientY - rect.top) / rect.height; + + if (this.mouseUsed) { + this.updateShader(); + } + }); + + document.addEventListener('mouseup', () => { + isDragging = false; + this.container.style.cursor = 'grab'; + }); + + // Handle window resize to maintain constraints + window.addEventListener('resize', () => { + const rect = this.container.getBoundingClientRect(); + const constrained = this.constrainPosition(rect.left, rect.top); + + if (rect.left !== constrained.x || rect.top !== constrained.y) { + this.container.style.left = constrained.x + 'px'; + this.container.style.top = constrained.y + 'px'; + this.container.style.transform = 'none'; + } + }); + } + + updateShader() { + const mouseProxy = new Proxy(this.mouse, { + get: (target, prop) => { + this.mouseUsed = true; + return target[prop]; + } + }); + + this.mouseUsed = false; + + const w = this.width * this.canvasDPI; + const h = this.height * this.canvasDPI; + const data = new Uint8ClampedArray(w * h * 4); + + let maxScale = 0; + const rawValues = []; + + for (let i = 0; i < data.length; i += 4) { + const x = (i / 4) % w; + const y = Math.floor(i / 4 / w); + const pos = this.fragment( + { x: x / w, y: y / h }, + mouseProxy + ); + const dx = pos.x * w - x; + const dy = pos.y * h - y; + maxScale = Math.max(maxScale, Math.abs(dx), Math.abs(dy)); + rawValues.push(dx, dy); + } + + maxScale *= 0.5; + + let index = 0; + for (let i = 0; i < data.length; i += 4) { + const r = rawValues[index++] / maxScale + 0.5; + const g = rawValues[index++] / maxScale + 0.5; + data[i] = r * 255; + data[i + 1] = g * 255; + data[i + 2] = 0; + data[i + 3] = 255; + } + + this.context.putImageData(new ImageData(data, w, h), 0, 0); + this.feImage.setAttributeNS('http://www.w3.org/1999/xlink', 'href', this.canvas.toDataURL()); + this.feDisplacementMap.setAttribute('scale', (maxScale / this.canvasDPI).toString()); + } + + appendTo(parent) { + parent.appendChild(this.svg); + parent.appendChild(this.container); + } + + destroy() { + this.svg.remove(); + this.container.remove(); + this.canvas.remove(); + } + } + + // Create the liquid glass effect + function createLiquidGlass() { + // Create shader + const shader = new Shader({ + width: 300, + height: 200, + fragment: (uv, mouse) => { + const ix = uv.x - 0.5; + const iy = uv.y - 0.5; + const distanceToEdge = roundedRectSDF( + ix, + iy, + 0.3, + 0.2, + 0.6 + ); + const displacement = smoothStep(0.8, 0, distanceToEdge - 0.15); + const scaled = smoothStep(0, 1, displacement); + return texture(ix * scaled + 0.5, iy * scaled + 0.5); + } + }); + + // Add to page + shader.appendTo(document.body); + + console.log('Liquid Glass effect created! Drag the glass around the page.'); + + // Return shader instance so it can be removed if needed + window.liquidGlass = shader; + } + + // Initialize + createLiquidGlass(); +})(); diff --git a/reijm-read/src/utils/rss.ts b/reijm-read/src/utils/rss.ts new file mode 100644 index 0000000..3867c26 --- /dev/null +++ b/reijm-read/src/utils/rss.ts @@ -0,0 +1,43 @@ +import type { BlogPost } from "../types/blog"; +import { rssConfig } from "@/config/rss"; + +// 使用 RSS2JSON API 转换 RSS 为 JSON +export const fetchBlogPosts = async (): Promise => { + try { + if (!rssConfig.url) { + throw new Error("RSS URL is not defined"); + } + + // 使用 RSS2JSON 服务 + const apiUrl = `https://api.rss2json.com/v1/api.json?rss_url=${encodeURIComponent(rssConfig.url)}`; + const response = await fetch(apiUrl, { + headers: { + "Cache-Control": "no-cache", + "Pragma": "no-cache" + } + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + + if (data.status !== "ok") { + throw new Error("Failed to fetch RSS feed"); + } + + return data.items.map( + (item: any): BlogPost => ({ + title: item.title, + link: item.link, + date: new Date(item.pubDate), + description: item.content || item.description, + category: item.category || "默认分类", + }), + ); + } catch (error) { + console.error("获取博客文章失败:", error); + return []; + } +}; diff --git a/reijm-read/src/utils/security.ts b/reijm-read/src/utils/security.ts new file mode 100644 index 0000000..d6dc1c9 --- /dev/null +++ b/reijm-read/src/utils/security.ts @@ -0,0 +1,70 @@ +// 禁用开发者工具和快捷键 +export const initSecurityMeasures = () => { + let warningShown = false; + let warningTimeout: number | null = null; + + // 显示友好提示 + const showWarning = () => { + if (!warningShown) { + warningShown = true; + const warningDialog = document.querySelector("#warning-dialog"); + if (warningDialog) { + warningDialog.open(); + // 清除之前的定时器 + if (warningTimeout) { + clearTimeout(warningTimeout); + } + // 3秒后自动关闭 + warningTimeout = window.setTimeout(() => { + warningDialog.close(); + warningShown = false; + warningTimeout = null; + }, 3000); + } + } + }; + + // 监听右键菜单 + document.addEventListener( + "contextmenu", + (e: MouseEvent) => { + e.preventDefault(); + showWarning(); + }, + true, + ); + + // 监听开发者工具快捷键 + document.addEventListener( + "keydown", + (e: KeyboardEvent) => { + const isMacCmd = e.metaKey && !e.ctrlKey; + const isWinCtrl = e.ctrlKey && !e.metaKey; + + if ( + e.key === "F12" || + ((isMacCmd || isWinCtrl) && + e.shiftKey && + ["I", "J", "C", "U"].includes(e.key.toUpperCase())) + ) { + e.preventDefault(); + showWarning(); + } + }, + true, + ); + + // 检测开发者工具状态 + const checkDevTools = () => { + const threshold = 160; + const widthThreshold = window.outerWidth - window.innerWidth > threshold; + const heightThreshold = window.outerHeight - window.innerHeight > threshold; + + if (widthThreshold || heightThreshold) { + showWarning(); + } + }; + + // 每 1000ms 检查一次 + setInterval(checkDevTools, 1000); +}; diff --git a/reijm-read/src/views/GithubLoginButton.vue b/reijm-read/src/views/GithubLoginButton.vue new file mode 100644 index 0000000..0c3a135 --- /dev/null +++ b/reijm-read/src/views/GithubLoginButton.vue @@ -0,0 +1,42 @@ + + + + + diff --git a/reijm-read/src/views/HomeView.vue b/reijm-read/src/views/HomeView.vue new file mode 100644 index 0000000..a658331 --- /dev/null +++ b/reijm-read/src/views/HomeView.vue @@ -0,0 +1,17 @@ + + + + diff --git a/reijm-read/src/views/Manga.vue b/reijm-read/src/views/Manga.vue new file mode 100644 index 0000000..765d3e1 --- /dev/null +++ b/reijm-read/src/views/Manga.vue @@ -0,0 +1,894 @@ + + + + + + diff --git a/reijm-read/src/views/User.vue b/reijm-read/src/views/User.vue new file mode 100644 index 0000000..f3734f4 --- /dev/null +++ b/reijm-read/src/views/User.vue @@ -0,0 +1,227 @@ + + + + + + diff --git a/reijm-read/src/views/tools/BookmarksView.vue b/reijm-read/src/views/tools/BookmarksView.vue new file mode 100644 index 0000000..5a37450 --- /dev/null +++ b/reijm-read/src/views/tools/BookmarksView.vue @@ -0,0 +1,200 @@ + + + diff --git a/reijm-read/src/views/tools/JsonFormatterView.vue b/reijm-read/src/views/tools/JsonFormatterView.vue new file mode 100644 index 0000000..05c15b8 --- /dev/null +++ b/reijm-read/src/views/tools/JsonFormatterView.vue @@ -0,0 +1,126 @@ + + + diff --git a/reijm-read/src/views/tools/TimestampView.vue b/reijm-read/src/views/tools/TimestampView.vue new file mode 100644 index 0000000..e69de29 diff --git a/reijm-read/src/vite-env.d.ts b/reijm-read/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/reijm-read/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/reijm-read/src/workers/blockDetector.worker.js b/reijm-read/src/workers/blockDetector.worker.js new file mode 100644 index 0000000..20e4941 --- /dev/null +++ b/reijm-read/src/workers/blockDetector.worker.js @@ -0,0 +1,16 @@ +let lastHeartbeat = Date.now(); + +self.onmessage = function(e) { + if (e.data.type === 'HEARTBEAT') { + const now = Date.now(); + const delta = now - lastHeartbeat; + + if (delta > 500) { + self.postMessage({ type: 'UI_BLOCKED' }); + } else { + self.postMessage({ type: 'UI_RECOVERED' }); + } + + lastHeartbeat = now; + } +}; diff --git a/reijm-read/src/workers/flightProcessor.worker.js b/reijm-read/src/workers/flightProcessor.worker.js new file mode 100644 index 0000000..cdbfa2f --- /dev/null +++ b/reijm-read/src/workers/flightProcessor.worker.js @@ -0,0 +1,52 @@ +// 完全保持原有数据处理逻辑,仅在Worker中执行 +class FlightProcessor { + constructor() { + this.previousPositions = new Map(); + } + + processFlights(flightData) { + const updates = []; + const currentFlights = new Set(); + + flightData.forEach(flight => { + if (!flight || flight.length < 7) return; + + const icao24 = flight[0]; + currentFlights.add(icao24); + + const prevPosition = this.previousPositions.get(icao24); + const longitude = flight[5]; + const latitude = flight[6]; + const altitude = flight[7] || 0; + + updates.push({ + icao24, + flight, + prevPosition: prevPosition || { longitude, latitude, altitude } + }); + + this.previousPositions.set(icao24, { longitude, latitude, altitude }); + }); + + // 清理不再存在的航班 + this.previousPositions.forEach((_, icao24) => { + if (!currentFlights.has(icao24)) { + this.previousPositions.delete(icao24); + } + }); + + return updates; + } +} + +const processor = new FlightProcessor(); + +self.onmessage = function(e) { + if (e.data.type === 'PROCESS_FLIGHTS') { + const updates = processor.processFlights(e.data.flightData); + self.postMessage({ + type: 'UPDATE_ENTITIES', + data: updates + }); + } +}; diff --git a/reijm-read/tailwind.config.js b/reijm-read/tailwind.config.js new file mode 100644 index 0000000..562714f --- /dev/null +++ b/reijm-read/tailwind.config.js @@ -0,0 +1,52 @@ +import { fontFamily } from "tailwindcss/defaultTheme"; + +/** @type {import('tailwindcss').Config} */ +export default { + darkMode: "class", + content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"], + theme: { + extend: { + colors: { + primary: { + DEFAULT: "var(--color-primary)", + dark: "var(--color-primary-dark)", + light: "var(--color-primary-light)", + 10: "var(--color-primary-10)", + 50: '#e6f7ff', + 100: '#ccf0ff', + 200: '#99e6ff', + 300: '#66dbff', + 400: '#33cfff', + 500: '#00b3ff', + 600: '#0099e6', + 700: '#007fbf', + 800: '#006699', + 900: '#004d73', + }, + }, + backgroundColor: { + main: "var(--color-bg-main)", + secondary: "var(--color-bg-secondary)", + tertiary: "var(--color-bg-tertiary)", + }, + textColor: { + primary: "var(--color-text-primary)", + secondary: "var(--color-text-secondary)", + tertiary: "var(--color-text-tertiary)", + }, + borderColor: { + DEFAULT: "var(--color-border)", + light: "var(--color-border-light)", + }, + boxShadow: { + sm: "var(--shadow-sm)", + DEFAULT: "var(--shadow)", + md: "var(--shadow-md)", + }, + fontFamily: { + sans: ["Inter var", ...fontFamily.sans], + }, + }, + }, + plugins: [], +}; diff --git a/reijm-read/tsconfig.app.json b/reijm-read/tsconfig.app.json new file mode 100644 index 0000000..4399651 --- /dev/null +++ b/reijm-read/tsconfig.app.json @@ -0,0 +1,18 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": [ + "env.d.ts", + "src/**/*", + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.vue" + ], + "exclude": ["src/**/__tests__/*"], + "compilerOptions": { + "composite": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/reijm-read/tsconfig.json b/reijm-read/tsconfig.json new file mode 100644 index 0000000..8edc1e0 --- /dev/null +++ b/reijm-read/tsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "jsx": "preserve", + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ESNext", "DOM"], + "skipLibCheck": true, + "noEmit": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + }, + "types": [ + "node", + "vite/client", + "@types/node", + "vite-plugin-obfuscator" + ] + }, + "include": [ + "src/**/*.ts", + "src/**/*.d.ts", + "src/**/*.tsx", + "src/**/*.vue" + ], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/reijm-read/tsconfig.node.json b/reijm-read/tsconfig.node.json new file mode 100644 index 0000000..1d8ee98 --- /dev/null +++ b/reijm-read/tsconfig.node.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true, + "types": ["vite/client"] + }, + "include": ["vite.config.ts", "src/config/**/*", "src/types/**/*"] +} diff --git a/reijm-read/vite.config.ts b/reijm-read/vite.config.ts new file mode 100644 index 0000000..39c456a --- /dev/null +++ b/reijm-read/vite.config.ts @@ -0,0 +1,141 @@ +import { defineConfig } from "vite"; +import vue from "@vitejs/plugin-vue"; +import path from "path"; +import viteCompression from "vite-plugin-compression"; +import { ViteImageOptimizer } from "vite-plugin-image-optimizer"; +import { fontConfig } from "./src/config/font"; + +import cesium from 'vite-plugin-cesium' + +export default defineConfig({ + base: "/", + + build: { + outDir: "dist", + assetsDir: "assets", + minify: "terser", + sourcemap: false, + chunkSizeWarningLimit: 1500, + terserOptions: { + compress: { + drop_console: true, + drop_debugger: true, + pure_funcs: ["console.log"], + }, + format: { + comments: /@license/i, + }, + }, + rollupOptions: { + output: { + manualChunks: { + vendor: ["vue", "vue-router"], + }, + chunkFileNames: "assets/js/[name]-[hash].js", + entryFileNames: "assets/js/[name]-[hash].js", + assetFileNames: "assets/[ext]/[name]-[hash].[ext]", + }, + }, + cssCodeSplit: true, + cssMinify: true, + }, + plugins: [ + cesium(), + vue(), + viteCompression({ + verbose: true, + disable: false, + threshold: 10240, + algorithm: "gzip", + ext: ".gz", + }), + ViteImageOptimizer({ + test: /\.(jpe?g|png|gif|svg)$/i, + exclude: undefined, + include: undefined, + includePublic: true, + logStats: true, + ansiColors: true, + svg: { + multipass: true, + plugins: [ + { + name: "preset-default", + params: { + overrides: { + removeViewBox: false, + removeEmptyAttrs: false, + }, + }, + }, + ], + }, + png: { + quality: 80, + }, + jpeg: { + quality: 80, + }, + jpg: { + quality: 80, + }, + tiff: { + quality: 80, + }, + gif: undefined, + webp: { + quality: 80, + }, + avif: { + quality: 80, + }, + }), + ], + resolve: { + alias: { + "@": path.resolve(__dirname, "src"), + }, + }, + server: { + host: "0.0.0.0", + port: 5173, + allowedHosts: ["w.godserver.cn",'godserver.cn','www.godserver.cn','rbq.college','mai.godserver.cn'], + proxy: { + "/api": { + target: "http://127.0.0.1:8981/api", + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, ''), + headers: { + "User-Agent": "Mozilla/5.0", + "Cache-Control": "no-cache", + "Pragma": "no-cache" + // 移除 Accept 头部,避免覆盖客户端发送的 Accept: text/event-stream + }, + configure: (proxy, options) => { + proxy.on('proxyReq', (proxyReq, req, res) => { + // 确保不覆盖客户端的 Accept 头部 + if (req.headers.accept) { + proxyReq.setHeader('Accept', req.headers.accept); + } + }); + + proxy.on('proxyRes', (proxyRes, req, res) => { + // 确保流式响应的头部设置正确 + proxyRes.headers['Content-Type'] = 'text/event-stream'; + proxyRes.headers['Cache-Control'] = 'no-cache'; + proxyRes.headers['Connection'] = 'keep-alive'; + proxyRes.headers['X-Accel-Buffering'] = 'no'; + }); + } + }, + }, + }, + define: { + __VUE_OPTIONS_API__: true, + __VUE_PROD_DEVTOOLS__: false, + 'process.env.VITE_API_BASE_URL': JSON.stringify(process.env.VITE_API_BASE_URL), + "process.env.VITE_FONT_URL": JSON.stringify(fontConfig.url), + "process.env.VITE_FONT_ENABLED": JSON.stringify(fontConfig.enabled), + "process.env.VITE_FONT_PRELOAD": JSON.stringify(fontConfig.preload), + }, +});