diff --git a/.github/workflows/jvm-ci.yml b/.github/workflows/jvm-ci.yml index 770fc5576f..466e193e60 100644 --- a/.github/workflows/jvm-ci.yml +++ b/.github/workflows/jvm-ci.yml @@ -71,8 +71,41 @@ jobs: platforms: | linux/amd64 linux/arm64/v8 - linux/riscv64 - linux/ppc64le + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }}-jvm-universal + cache-from: type=gha + cache-to: type=gha,mode=max + Build_Docker_Standalone: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set Up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set Up Buildx + uses: docker/setup-buildx-action@v3 + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5.6.1 + with: + images: ghostchu/peerbanhelper-snapshot + tags: | + type=ref,event=branch + type=ref,event=tag + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,ci-jvm-universal + type=raw,ci + type=sha + - name: Build and push Docker image + uses: docker/build-push-action@v6.10.0 + with: + context: . + file: ./Dockerfile + push: false + platforms: | + linux/amd64 + linux/arm64/v8 tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }}-jvm-universal cache-from: type=gha diff --git a/.github/workflows/jvm-release.yml b/.github/workflows/jvm-release.yml index 5475c336a3..d7c4bb334c 100644 --- a/.github/workflows/jvm-release.yml +++ b/.github/workflows/jvm-release.yml @@ -167,8 +167,6 @@ jobs: platforms: | linux/amd64 linux/arm64/v8 - linux/riscv64 - linux/ppc64le tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }}-jvm-universal cache-from: type=gha @@ -205,8 +203,6 @@ jobs: platforms: | linux/amd64 linux/arm64/v8 - linux/riscv64 - linux/ppc64le tags: ${{ steps.meta-acr.outputs.tags }} labels: ${{ steps.meta-acr.outputs.labels }}-jvm-universal cache-from: type=gha diff --git a/.gitignore b/.gitignore index da5364e69e..66171cba55 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,4 @@ dependency-reduced-pom.xml PeerBanHelper.jar *.pkg +install4j/output diff --git a/Dockerfile b/Dockerfile index e9f6dc9ab1..3c742203f1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,15 +11,15 @@ RUN apk add --update npm curl git && \ mv webui/dist src/main/resources/static && \ mvn -B clean package --file pom.xml -T 1.5C -P thin-sqlite-packaging -FROM docker.io/eclipse-temurin:23-jre-noble +FROM docker.io/bellsoft/liberica-runtime-container:jre-23-slim-musl LABEL maintainer="https://github.com/PBH-BTN/PeerBanHelper" USER 0 EXPOSE 9898 ENV TZ=UTC -ENV JAVA_OPTS="-Dpbh.release=docker -Djava.awt.headless=true -Xmx512M -Xss512k -XX:+UseG1GC -XX:+UseStringDeduplication -XX:+ShrinkHeapInSteps" +ENV JAVA_OPTS="-Dpbh.release=docker -Djava.awt.headless=true -Xmx512M -Xms16M -Xss512k -XX:+UseG1GC -XX:+UseStringDeduplication -XX:+ShrinkHeapInSteps" WORKDIR /app VOLUME /tmp COPY --from=build build/target/libraries /app/libraries COPY --from=build build/target/PeerBanHelper.jar /app/PeerBanHelper.jar ENV PATH="${JAVA_HOME}/bin:${PATH}" -ENTRYPOINT ${JAVA_HOME}/bin/java ${JAVA_OPTS} -jar PeerBanHelper.jar \ No newline at end of file +ENTRYPOINT ["sh", "-c", "${JAVA_HOME}/bin/java ${JAVA_OPTS} -jar PeerBanHelper.jar"] \ No newline at end of file diff --git a/Dockerfile-Release b/Dockerfile-Release index dfa195d726..eee011ae8d 100644 --- a/Dockerfile-Release +++ b/Dockerfile-Release @@ -1,11 +1,11 @@ -FROM docker.io/eclipse-temurin:23-jre-noble +FROM docker.io/bellsoft/liberica-runtime-container:jre-23-slim-musl LABEL maintainer="https://github.com/PBH-BTN/PeerBanHelper" COPY target/libraries /app/libraries COPY target/PeerBanHelper.jar /app/PeerBanHelper.jar USER 0 EXPOSE 9898 ENV TZ=UTC -ENV JAVA_OPTS="-Dpbh.release=docker -Djava.awt.headless=true -Xmx512M -Xss512k -XX:+UseG1GC -XX:+UseStringDeduplication -XX:+ShrinkHeapInSteps" +ENV JAVA_OPTS="-Dpbh.release=docker -Djava.awt.headless=true -Xmx512M -Xms16M -Xss512k -XX:+UseG1GC -XX:+UseStringDeduplication -XX:+ShrinkHeapInSteps" WORKDIR /app VOLUME /tmp -ENTRYPOINT ${JAVA_HOME}/bin/java ${JAVA_OPTS} -jar PeerBanHelper.jar \ No newline at end of file +ENTRYPOINT ["sh", "-c", "${JAVA_HOME}/bin/java ${JAVA_OPTS} -jar PeerBanHelper.jar"] \ No newline at end of file diff --git a/README.EN.md b/README.EN.md index 691e546d5b..cbcbccfab7 100644 --- a/README.EN.md +++ b/README.EN.md @@ -6,7 +6,7 @@ Automatically block unwanted, leeches and abnormal BT peers with support for cus ![page-views](https://raw.githubusercontent.com/PBH-BTN/views-counter/refs/heads/master/svg/754169590/badge.svg) ## Introduction -Following function are provided by PeerBanHelper: +The following functions are provided by PeerBanHelper: - [PeerID Blacklist](https://docs.pbh-btn.com/en/docs/module/peer-id) - [Client Name Blacklist](https://docs.pbh-btn.com/en/docs/module/client-name) @@ -19,7 +19,7 @@ Following function are provided by PeerBanHelper: - [IP set subscribe](https://docs.pbh-btn.com/en/docs/module/ip-address-blocker-rules) - A modern WebUI -In addition, PeerBanHelper downloads the GeoIP library at startup, and supports the following functions once it successful loaded: +In addition, PeerBanHelper downloads the GeoIP library at startup and supports the following functions once it has been successfully loaded: - View IP address attribution, AS information (ASN, ISP, AS name, etc.), network type information (broadband, base station, IoT, data center, etc.) in the blocking list. - Based on GeoIP information, block IP addresses by country/region, city, network type, ASN and so on. - View GeoIP statistics @@ -34,7 +34,7 @@ In addition, PeerBanHelper downloads the GeoIP library at startup, and supports - BiglyBT([plugin](https://github.com/PBH-BTN/PBH-Adapter-BiglyBT) is required) - Deluge([plugin](https://github.com/PBH-BTN/PBH-Adapter-Deluge) is required) - Azureus(Vuze)([plugin](https://github.com/PBH-BTN/PBH-Adapter-Azureus) is required) -- Transmission **(deprecated;3.00-20 or higher)** +- Transmission **(deprecated; 3.00-20 or higher)** - BitComet **v2.10 Beta6 [20240928] or higher** (P2SP LTSeed mode is not supported) @@ -68,12 +68,22 @@ Any consequences caused by the user's use of this software are borne by the user [![Star History Chart](https://api.star-history.com/svg?repos=PBH-BTN/PeerBanHelper&type=Date)](https://star-history.com/#PBH-BTN/PeerBanHelper&Date) +## Tools + +In the development process of PeerBanHelper, we have used many excellent professional tools. Thanks to the following companies or projects for providing open-source licenses: + ### Install4j PeerBanHelper use [Install4j multi-platform installer builder](https://www.ej-technologies.com/products/install4j/overview.html) to build its multi-platform installer. Thanks the open-source license provided by ej-technolgies. Click the link or the image below to download install4j. [![Install4j](https://www.ej-technologies.com/images/product_banners/install4j_large.png)](https://www.ej-technologies.com/products/install4j/overview.html) +### JProfiler + +PeerBanHelper use [JProfiler all-in one Java profiler](https://www.ej-technologies.com/jprofiler) to analyze and optimize the program. Thanks the open-source license provided by ej-technolgies. Click the link or the image below to download JProfiler. + +[![JProfiler](https://www.ej-technologies.com/images/product_banners/jprofiler_large.png)](https://www.ej-technologies.com/jprofiler) + ## Credit ### Backend diff --git a/README.md b/README.md index 279e552fc0..b0f7d0674d 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,9 @@ PeerBanHelper 仅支持对传统 IPv4 或 IPv6 地址的反吸血,如遇 I2P [![Star History Chart](https://api.star-history.com/svg?repos=PBH-BTN/PeerBanHelper&type=Date)](https://star-history.com/#PBH-BTN/PeerBanHelper&Date) +## Tools + +在 PeerBanHelper 的开发过程中,我们使用到了许多优秀的专业工具。感谢下面的公司或项目慷慨的提供开源许可证: ### Install4j @@ -76,6 +79,12 @@ PeerBanHelper 使用 [Install4j multi-platform installer builder](https://www.ej [![Install4j](https://www.ej-technologies.com/images/product_banners/install4j_large.png)](https://www.ej-technologies.com/products/install4j/overview.html) +### JProfiler + +PeerBanHelper 使用 [JProfiler all-in one Java profiler](https://www.ej-technologies.com/jprofiler) 对程序进行性能分析与优化。感谢 ej-technolgies 的开放源代码许可证。点击链接或者下面的图片下载 JProfiler。 + +[![JProfiler](https://www.ej-technologies.com/images/product_banners/jprofiler_large.png)](https://www.ej-technologies.com/jprofiler) + ## Credit ### Backend diff --git a/install4j/project.install4j b/install4j/project.install4j index e37180e675..6b47cebe18 100644 --- a/install4j/project.install4j +++ b/install4j/project.install4j @@ -1,7 +1,7 @@ - + @@ -12,6 +12,8 @@ + + @@ -37,7 +39,7 @@ - + @@ -51,7 +53,7 @@ - + @@ -65,7 +67,7 @@ - + @@ -79,7 +81,7 @@ - + @@ -93,7 +95,7 @@ - + @@ -285,18 +287,6 @@ return true; - - - - - - libraries - - - - - - @@ -441,7 +431,23 @@ return console.askYesNo(message, true); + + + 70 + + + + + + + + libraries + + + + + diff --git a/install4j/project.install4j~ b/install4j/project.install4j~ new file mode 100644 index 0000000000..d327b09c7a --- /dev/null +++ b/install4j/project.install4j~ @@ -0,0 +1,1612 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${i18n:components.peerbanhelper.description} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + sys.installationDir + + + context.getBooleanVariable("sys.confirmedUpdateInstallation") + + + + + + ${form:welcomeMessage} + + !context.isConsole() + + + + + + String message = context.getMessage("ConsoleWelcomeLabel", context.getApplicationName()); +return console.askOkCancel(message, true); + + + + + + + + updateCheck + + + + + ${i18n:ClickNext} + + + + + + !context.getBooleanVariable("sys.confirmedUpdateInstallation") + + + + + sys.installationDir + + + context.getVariable("sys.responseFile") == null + + + + + + ${i18n:SelectDirLabel(${compiler:sys.fullName})} + + + + + + + + suggestAppDir + validateApplicationId + existingDirWarning + checkWritable + manualEntryAllowed + checkFreeSpace + showRequiredDiskSpace + showFreeDiskSpace + allowSpacesOnUnix + validationScript + standardValidation + + + + + + + + + ${i18n:SelectComponentsLabel2} + + !context.isConsole() + + + + + + + selectionChangedScript + + + + + + + + + ${form:confirmationMessage} + + !context.isConsole() + + + + ${i18n:CreateDesktopIcon} + createDesktopLinkAction + + + + + ${i18n:checkbox.followsystemstartup} + startupWhenLoggedIn + + + + + ${i18n:checkbox.registersystemservice} + registerSystemService + + + + + + context.getBooleanVariable("registerSystemService") + + + + ${i18n:InfoBeforeClickLabel} + + !context.isConsole() + + + + ${i18n:systemservice.note} + + + + + textSource + displayedText + displayedTextFile + variableName + + + + + + + console.waitForEnter(); +return true; + + + + + + + + + + + + + ${i18n:UninstallerMenuEntry(${compiler:sys.fullName})} + + !context.getBooleanVariable("sys.programGroupDisabled") + + + + + + + PeerBanHelper-GUI + + + ${compiler:sys.fullName} + + + . + + + + + ./icon.png + + + + + ./icon.ico + + + + context.getBooleanVariable("createDesktopLinkAction") + + + + + + PeerBanHelper-GUI-Silent + + + PeerBanHelper + + context.getBooleanVariable("startupWhenLoggedIn") + + + + ${compiler:sys.fullName} ${compiler:sys.version} + + + + + + 70 + com.ghostchu.peerbanhelper.PBHService + + + + + context.getBooleanVariable("registerSystemService") + + + + + 70 + + context.getBooleanVariable("registerSystemService") + + + + + + ${i18n:WizardPreparing} + + + + + + + + + 26 + + context.getBooleanVariable("executeLauncherAction") && (!context.isUnattended()) + + + + + + ${form:finishedMessage} + + + + + ${i18n:RunEntryExec("${compiler:sys.fullName}")} + + executeLauncherAction + + + + + + + + + ${i18n:UninstallerMenuEntry(${compiler:sys.fullName})} + + + + + + + + + + + + + + + + + + + + ${form:welcomeMessage} + + !context.isConsole() + + + + + + String message = context.getMessage("ConfirmUninstall", context.getApplicationName()); +return console.askYesNo(message, true); + + + + + + + + + + + + 70 + + + + + + + + + libraries + + + + + + + + + + ${i18n:UninstallerPreparing} + + + + + + + + + + ${form:successMessage} + + + + + + + + + + + + ${compiler:sys.install4jHome}/resource/updater_16.png + + + + + ${compiler:sys.install4jHome}/resource/updater_32.png + + + + + ${compiler:sys.install4jHome}/resource/updater_48.png + + + + + ${compiler:sys.install4jHome}/resource/updater_128.png + + + + + ${compiler:sys.install4jHome}/resource/updater_256.png + + + + bgupdater + + -Dapple.awt.UIElement=true + ${compiler:sys.fullName} + + + + + + + + + import java.nio.file.*; + +Path dir = context.getInstallationDirectory().toPath(); +// quit if the current installation is on a read only file system, for example a disk image on macOS +// or if the directory is not writeable on Linux/Unix. +// If there is no "Request privileges" action in the installer, the condition should also +// check Files.isWritable(dir) +return !Files.getFileStore(dir).isReadOnly() && ((Util.isWindows() && !Util.isArchive()) || Util.isMacOS() || (Util.isLinux() && !Util.isArchive())); + + + + + + + + + ${installer:updatesUrl?:${compiler:sys.updatesUrl}} + updateDescriptor + + + + + + + + UpdateDescriptorEntry entry = ((UpdateDescriptor)context.getVariable("updateDescriptor")).getPossibleUpdateEntry(); + +if (entry == null) { + return null; +} else if (entry.isArchive() && !entry.isSingleBundle()) { + // only installers and single bundle archives on macOS are supported + return null; +} else if (entry.isDownloaded()) { + // update has been downloaded already + return null; +} else { + return entry; +} + + + updateDescriptorEntry + + + + + + + context.getVariable("updateDescriptorEntry") != null + + + + + + + + + ((UpdateDescriptorEntry)context.getVariable("updateDescriptorEntry")).getNewVersion() + + + updaterNewVersion + + + + + + + ((UpdateDescriptorEntry)context.getVariable("updateDescriptorEntry")).getURL().toExternalForm() + + + updaterDownloadUrl + + + + + + + context.getVariable("sys.updateStorageDir") + File.separator + ((UpdateDescriptorEntry)context.getVariable("updateDescriptorEntry")).getFileName() + + + updaterDownloadFile + + + + + + + ${installer:updaterDownloadFile} + + + ${installer:updaterDownloadUrl} + + + + + + + !((UpdateDescriptorEntry)context.getVariable("updateDescriptorEntry")).isArchive() + + + + + + + + + + ${installer:updaterDownloadFile} + + + + 755 + + + + + + + ${installer:updaterDownloadFile} + + + ${installer:updaterNewVersion} + + + + + + + + + ((UpdateDescriptorEntry)context.getVariable("updateDescriptorEntry")).isArchive() + + + + + + + + + String dirName = context.getVariable("updaterDownloadFile") + "_dir"; +new File(dirName).mkdirs(); +return dirName; + + + updaterStagingDir + + + + + + + + + ${installer:updaterStagingDir} + + + + + + new File((String)context.getVariable("updaterStagingDir")).exists() + + + + + + ${installer:updaterDownloadFile} + + + + + ${installer:updaterStagingDir} + + + + + // only extract app bundle, no other top level files + +import com.install4j.api.unix.UnixFileSystem; + +File realFile = new File(dmgMountPoint, file.getPath()); + +return file.getParent() != null || (file.getName().endsWith(".app") && realFile.isDirectory() && !UnixFileSystem.getFileInformation(realFile).isLink()); + + + + ((String)context.getVariable("updaterDownloadFile")).endsWith(".dmg") + + + + + + ${installer:updaterDownloadFile} + + + + + ${installer:updaterStagingDir} + + + + + // only extract app bundle, no other top level files +file.getParent() != null || (file.getName().endsWith(".app") && directory) + + + + !((String)context.getVariable("updaterDownloadFile")).endsWith(".dmg") + + + + + + ${installer:updaterStagingDir} + + + ${installer:updaterNewVersion} + + + + + + + + + ${installer:updaterDownloadFile} + + + + + + + + + + + + + + + + + + + ${compiler:sys.install4jHome}/resource/updater_16.png + + + + + ${compiler:sys.install4jHome}/resource/updater_32.png + + + + + ${compiler:sys.install4jHome}/resource/updater_48.png + + + + + ${compiler:sys.install4jHome}/resource/updater_128.png + + + + + ${compiler:sys.install4jHome}/resource/updater_256.png + + + + updater + + ${i18n:updater.WindowTitle("${compiler:sys.fullName}")} + + + + + + + + + + + ${i18n:updater.WelcomeTitle("${compiler:sys.fullName}")} + + + + + + ${i18n:updater.WelcomeInfoText("${compiler:sys.fullName}")} + + !context.isConsole() + + labelText + + + + + + + ${i18n:updater.CheckForUpdateSubtitle} + ${i18n:updater.CheckForUpdateTitle} + + context.getWizardContext().setControlButtonVisible(ControlButtonType.NEXT, false); +context.getWizardContext().setControlButtonVisible(ControlButtonType.PREVIOUS, false); +context.goForward(1, true, true); + + + + + + + + + + ${installer:updatesUrl?:${compiler:sys.updatesUrl}} + updateDescriptor + + + + + + + + ((UpdateDescriptor)context.getVariable("updateDescriptor")).getPossibleUpdateEntry() + + + updateDescriptorEntry + + + + + + + context.getVariable("updateDescriptorEntry") != null + + + + + + + + + ((UpdateDescriptorEntry)context.getVariable("updateDescriptorEntry")).getNewVersion() + + + updaterNewVersion + + + + + + + ((UpdateDescriptorEntry)context.getVariable("updateDescriptorEntry")).getFileSizeVerbose() + + + updaterDownloadSize + + + + + + + ((UpdateDescriptorEntry)context.getVariable("updateDescriptorEntry")).getComment() + + + updaterComment + + + + + + + ((UpdateDescriptorEntry)context.getVariable("updateDescriptorEntry")).getURL().toExternalForm() + + + updaterDownloadUrl + + + + + + + ((UpdateDescriptorEntry)context.getVariable("updateDescriptorEntry")).isArchive() ? Boolean.TRUE : Boolean.FALSE + + + isArchive + + + + + + + ((UpdateDescriptorEntry)context.getVariable("updateDescriptorEntry")).getFileName().toLowerCase().endsWith(".dmg") + + + isDmg + + + + + + + + + ${i18n:updater.CheckForUpdateLabel} + + + statusVisible + initialStatusMessage + + + + + + + + + context.getVariable("updateDescriptorEntry") == null + + + + + + + ${i18n:updater.UpToDateTitle} + + + + + + ${i18n:updater.UpToDateInfoText("${compiler:sys.fullName}")} + + !context.isConsole() + + labelText + + + + + + + + + + + context.getVariable("updateDescriptorEntry") != null + + + + + + + ${i18n:updater.NewVersionAvailableSubtitle("${compiler:sys.fullName}")} + ${i18n:updater.NewVersionAvailableTitle} + + !context.getBooleanVariable("skipNewVersionAvailable") + + + + ${i18n:updater.CurrentVersionLabel} + + + 128 + 0 + 0 + 255 + + + + + ${installer:sys.version} + + + + + + + ${i18n:updater.NewVersionLabel} + + + 0 + 128 + 0 + 255 + + + + + ${installer:updaterNewVersion} + + + + + + + context.goForward(1, false, false); + + + ${i18n:updater.ShowComments} + + ((String)context.getVariable("updaterComment")).length() > 0 + + + + + + + ${i18n:updater.DownloadLocationLabel} + + + + + ${installer:sys.downloadsDir} + ${i18n:updater.DownloadToLabel} + + updaterDownloadLocation + + + + + ${i18n:updater.DownloadSizeLabel} + ${installer:updaterDownloadSize} + + + + + + + + ${i18n:updater.CommentsSubTitle} + ${i18n:updater.CommentsTitle} + + false // This screen is only shown if the user clicks the "Show comments" hyperlink label in the previous screen. + + if (context.isConsole()) { + context.goBackInHistory(1); +} +return true; + WizardContext wizardContext = context.getWizardContext(); +wizardContext.setControlButtonVisible(ControlButtonType.NEXT, false); +wizardContext.setControlButtonVisible(ControlButtonType.CANCEL, false); + + + + + ${i18n:updater.CommentsLabel} + + !context.isConsole() + + labelText + + + + + ${installer:updaterComment} + + + + + textSource + displayedText + displayedTextFile + variableName + + + + + + + console.waitForEnter(); +return true; + + + + + + + + + + ${i18n:updater.DownloadSubTitle} + ${i18n:updater.DownloadTitle} + + context.getWizardContext().setControlButtonVisible(ControlButtonType.NEXT, false); +context.getWizardContext().setControlButtonVisible(ControlButtonType.PREVIOUS, false); +context.goForward(1, true, true); + + + + + + + context.getVariable("updaterDownloadLocation") + File.separator + ((UpdateDescriptorEntry)context.getVariable("updateDescriptorEntry")).getFileName() + + + updaterDownloadFile + + + + + + + ${installer:updaterDownloadFile} + + + ${installer:updaterDownloadUrl} + + + + + + + + ${installer:updaterDownloadFile} + + + + 755 + + + + + + + statusVisible + initialStatusMessage + + + + + + + + + ${i18n:updater.FinishTitle} + + !(context.getBooleanVariable("isArchive") && context.getBooleanVariable("isDmg")) + + + + + + !context.getBooleanVariable("isArchive") && ((Integer)context.getVariable("updaterLaunchSelection")).intValue() == 0 + + + + + + + + + List<String> args = new ArrayList<String>(); +String installationDirectory = context.getInstallationDirectory().getPath(); +if (context.isUnattended()) { + args.add("-q"); + args.add("-wait"); + args.add("20"); + ProgressInterface progressInterface = context.getProgressInterface(); + if (progressInterface.isUnattendedProgressDialog()) { + if (progressInterface.isAlertsShown()) { + args.add("-alerts"); + } + args.add("-splash"); + args.add("Installing"); + } +} else if (context.isConsole()) { + args.add("-c"); +} + args.add("-dir"); + args.add(installationDirectory); + + return args.toArray(new String[args.size()]); + + + + installerArguments + + + + + + + ${installer:installerArguments} + + + + ${installer:updaterDownloadFile} + + + + + ${installer:updaterDownloadLocation} + + + + + + + + + + + + + !context.isConsole() + + labelText + + + + + + ${i18n:updater.FinishInfoText("${compiler:sys.fullName}")} + + !context.isConsole() + + labelText + + + + + ${i18n:updater.LaunchUpdaterQuestion} + + + + + + + + + + + ${i18n:updater.LaunchUpdaterLabel} + ${i18n:updater.DoNotLaunchUpdaterLabel} + + updaterLaunchSelection + + !context.getBooleanVariable("isArchive") + + + + + + Util.showPath((String)context.getVariable("updaterDownloadFile")); + + + ${i18n:updater.OpenContainingFolderLabel} + + !context.isConsole() + + + + + + + + + + + + ${i18n:updater.FinishTitle} + + context.getBooleanVariable("isArchive") && context.getBooleanVariable("isDmg") + + + + + + context.getBooleanVariable("updaterOpenDmg") + + + + + + + + + + Util.showPath((String)context.getVariable("updaterDownloadFile")); +return true; + + + + + + + + + + + + + !context.isConsole() + + labelText + + + + + + ${i18n:updater.FinishInfoText("${compiler:sys.fullName}")} + + !context.isConsole() + + labelText + + + + + ${i18n:updater.LaunchUpdaterQuestion} + + + + + + + + + + ${i18n:updater.OpenContainingFolderLabel} + + updaterOpenDmg + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pkg/deb/usr/lib/systemd/system/peerbanhelper.service b/pkg/deb/usr/lib/systemd/system/peerbanhelper.service index 15955044fb..e027c6367e 100644 --- a/pkg/deb/usr/lib/systemd/system/peerbanhelper.service +++ b/pkg/deb/usr/lib/systemd/system/peerbanhelper.service @@ -5,7 +5,7 @@ After=network.target [Service] User=peerbanhelper WorkingDirectory=/usr/lib/peerbanhelper -ExecStart=/usr/bin/java -Dpbh.release=debian -Dpbh.datadir=/var/lib/peerbanhelper -Dpbh.configdir=/etc/peerbanhelper -Dpbh.logsdir=/var/log/peerbanhelper -Dpbh.log.level=WARN -Xmx512M -XX:+UseG1GC -XX:+UseStringDeduplication -XX:+ShrinkHeapInSteps -jar PeerBanHelper.jar +ExecStart=/usr/bin/java -Dpbh.release=debian -Dpbh.datadir=/var/lib/peerbanhelper -Dpbh.configdir=/etc/peerbanhelper -Dpbh.logsdir=/var/log/peerbanhelper -Dpbh.log.level=WARN -Xmx512M -Xms16M -Xss512k -XX:+UseG1GC -XX:+UseStringDeduplication -XX:+ShrinkHeapInSteps -jar PeerBanHelper.jar SuccessExitStatus=143 AmbientCapabilities=CAP_NET_ADMIN Restart=on-failure diff --git a/pkg/pkg/work-dir/usr/local/etc/rc.d/peerbanhelper b/pkg/pkg/work-dir/usr/local/etc/rc.d/peerbanhelper index 595ed21aec..6b6a6e43b0 100755 --- a/pkg/pkg/work-dir/usr/local/etc/rc.d/peerbanhelper +++ b/pkg/pkg/work-dir/usr/local/etc/rc.d/peerbanhelper @@ -14,7 +14,7 @@ rcvar=peerbanhelper_enable : ${peerbanhelper_user:="root"} : ${peerbanhelper_dir:="/usr/local/var/db/peerbanhelper"} : ${peerbanhelper_classpath:="/usr/local/lib/peerbanhelper/PeerBanHelper.jar"} -: ${peerbanhelper_jvm_flags:="-Dpbh.release=freebsd -Dpbh.datadir=${peerbanhelper_dir} -Xmx512M -Xss512K -XX:+UseG1GC -XX:+UseStringDeduplication -XX:+ShrinkHeapInSteps"} +: ${peerbanhelper_jvm_flags:="-Dpbh.release=freebsd -Dpbh.datadir=${peerbanhelper_dir} -Xmx512M -XX:+UseG1GC -Xms16M -Xss512k -XX:+UseStringDeduplication -XX:+ShrinkHeapInSteps"} : ${peerbanhelper_command:="/usr/local/bin/java ${peerbanhelper_jvm_flags} -jar ${peerbanhelper_classpath}"} command="/usr/sbin/daemon" diff --git a/pom.xml b/pom.xml index b4bd60005c..8159cb7fc5 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.ghostchu.peerbanhelper peerbanhelper - 7.2.2 + 7.3.0 jar PeerBanHelper @@ -219,7 +219,7 @@ - C:\Program Files\install4j10 + C:\Program Files\install4j11 @@ -276,7 +276,7 @@ com.google.guava guava - 33.3.1-jre + 33.4.0-jre org.jetbrains @@ -313,13 +313,13 @@ com.github.mizosoft.methanol methanol - 1.7.0 + 1.8.0 ch.qos.logback logback-classic - 1.5.12 + 1.5.16 com.formdev @@ -330,7 +330,7 @@ org.junit.jupiter junit-jupiter-api - 5.11.3 + 5.11.4 test @@ -341,7 +341,7 @@ io.javalin javalin - 6.3.0 + 6.4.0 jetty-server @@ -358,13 +358,13 @@ org.json json - 20240303 + 20250107 org.springframework spring-context - 6.2.0 + 6.2.1 @@ -510,5 +510,10 @@ java-multiaddr v1.4.12 + + dnsjava + dnsjava + 3.6.2 + diff --git a/src/main/java/com/ghostchu/peerbanhelper/Main.java b/src/main/java/com/ghostchu/peerbanhelper/Main.java index d1003aedd0..69fc7af8ce 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/Main.java +++ b/src/main/java/com/ghostchu/peerbanhelper/Main.java @@ -8,18 +8,28 @@ import com.ghostchu.peerbanhelper.config.PBHConfigUpdater; import com.ghostchu.peerbanhelper.config.ProfileUpdateScript; import com.ghostchu.peerbanhelper.event.PBHShutdownEvent; +import com.ghostchu.peerbanhelper.exchange.ExchangeMap; import com.ghostchu.peerbanhelper.gui.PBHGuiManager; import com.ghostchu.peerbanhelper.gui.impl.console.ConsoleGuiImpl; import com.ghostchu.peerbanhelper.gui.impl.swing.SwingGuiImpl; import com.ghostchu.peerbanhelper.text.Lang; import com.ghostchu.peerbanhelper.text.TextManager; +import com.ghostchu.peerbanhelper.util.*; +import com.ghostchu.peerbanhelper.util.encrypt.RSAUtils; +import com.ghostchu.peerbanhelper.util.json.JsonUtil; +import com.ghostchu.peerbanhelper.util.paging.Pageable; import com.ghostchu.simplereloadlib.ReloadManager; import com.ghostchu.simplereloadlib.ReloadResult; import com.ghostchu.simplereloadlib.ReloadStatus; import com.google.common.eventbus.EventBus; +import com.googlecode.aviator.AviatorEvaluator; +import com.googlecode.aviator.EvalMode; +import com.googlecode.aviator.Options; +import com.googlecode.aviator.runtime.JavaMethodReflectionFunctionMissing; import lombok.Getter; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.bspfsystems.yamlconfiguration.configuration.InvalidConfigurationException; import org.bspfsystems.yamlconfiguration.file.YamlConfiguration; import org.jetbrains.annotations.Nullable; @@ -37,6 +47,7 @@ import java.io.IOException; import java.io.InputStream; import java.lang.management.ManagementFactory; +import java.math.MathContext; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.Arrays; @@ -111,6 +122,7 @@ public static void main(String[] args) { guiManager.createMainWindow(); pbhServerAddress = mainConfig.getString("server.prefix", "http://127.0.0.1:" + mainConfig.getInt("server.http")); setupProxySettings(); + setupScriptEngine(); try { log.info(TextManager.tlUI(Lang.SPRING_CONTEXT_LOADING)); applicationContext = new AnnotationConfigApplicationContext(); @@ -450,4 +462,55 @@ public static void unregisterBean(String beanName) { defaultListableBeanFactory.removeBeanDefinition(beanName); } + private static void setupScriptEngine() { + AviatorEvaluator.getInstance().setCachedExpressionByDefault(true); + // ASM 性能优先 + AviatorEvaluator.getInstance().setOption(Options.EVAL_MODE, EvalMode.ASM); + // EVAL 性能优先 + AviatorEvaluator.getInstance().setOption(Options.OPTIMIZE_LEVEL, AviatorEvaluator.EVAL); + // 降低浮点计算精度 + AviatorEvaluator.getInstance().setOption(Options.MATH_CONTEXT, MathContext.DECIMAL32); + // 启用变量语法糖 + AviatorEvaluator.getInstance().setOption(Options.ENABLE_PROPERTY_SYNTAX_SUGAR, true); +// // 表达式允许序列化和反序列化 +// AviatorEvaluator.getInstance().setOption(Options.SERIALIZABLE, true); + // 启用反射方法查找 + AviatorEvaluator.getInstance().setFunctionMissing(JavaMethodReflectionFunctionMissing.getInstance()); + // 注册反射调用 + registerFunctions(IPAddressUtil.class); + registerFunctions(HTTPUtil.class); + registerFunctions(JsonUtil.class); + registerFunctions(Lang.class); + registerFunctions(StrUtil.class); + registerFunctions(PeerBanHelperServer.class); + registerFunctions(InfoHashUtil.class); + registerFunctions(CommonUtil.class); + registerFunctions(ByteUtil.class); + registerFunctions(MiscUtil.class); + registerFunctions(MsgUtil.class); + registerFunctions(SharedObject.class); + registerFunctions(UrlEncoderDecoder.class); + registerFunctions(URLUtil.class); + registerFunctions(WebUtil.class); + registerFunctions(RSAUtils.class); + registerFunctions(Pageable.class); + registerFunctions(TextManager.class); + registerFunctions(ExchangeMap.class); + registerFunctions(Main.class); + } + + private static void registerFunctions(Class clazz) { + try { + AviatorEvaluator.addInstanceFunctions(StringUtils.uncapitalize(clazz.getSimpleName()), clazz); + } catch (IllegalAccessException | NoSuchMethodException e) { + log.error("Internal error: failed on register instance functions: {}", clazz.getName(), e); + } + try { + AviatorEvaluator.addStaticFunctions(StringUtils.capitalize(clazz.getSimpleName()), clazz); + } catch (IllegalAccessException | NoSuchMethodException e) { + log.error("Internal error: failed on register static functions: {}", clazz.getName(), e); + } + } + + } \ No newline at end of file diff --git a/src/main/java/com/ghostchu/peerbanhelper/PeerBanHelperServer.java b/src/main/java/com/ghostchu/peerbanhelper/PeerBanHelperServer.java index d3c0ff17e1..31188f6805 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/PeerBanHelperServer.java +++ b/src/main/java/com/ghostchu/peerbanhelper/PeerBanHelperServer.java @@ -23,6 +23,7 @@ import com.ghostchu.peerbanhelper.invoker.impl.IPFilterInvoker; import com.ghostchu.peerbanhelper.ipdb.IPDB; import com.ghostchu.peerbanhelper.ipdb.IPGeoData; +import com.ghostchu.peerbanhelper.lab.Experiments; import com.ghostchu.peerbanhelper.lab.Laboratory; import com.ghostchu.peerbanhelper.metric.BasicMetrics; import com.ghostchu.peerbanhelper.module.*; @@ -30,16 +31,12 @@ import com.ghostchu.peerbanhelper.module.impl.webapi.*; import com.ghostchu.peerbanhelper.peer.Peer; import com.ghostchu.peerbanhelper.text.Lang; -import com.ghostchu.peerbanhelper.text.TextManager; import com.ghostchu.peerbanhelper.text.TranslationComponent; import com.ghostchu.peerbanhelper.torrent.Torrent; import com.ghostchu.peerbanhelper.util.*; -import com.ghostchu.peerbanhelper.util.encrypt.RSAUtils; -import com.ghostchu.peerbanhelper.util.json.JsonUtil; -import com.ghostchu.peerbanhelper.util.paging.Pageable; +import com.ghostchu.peerbanhelper.util.dns.DNSLookup; import com.ghostchu.peerbanhelper.util.rule.ModuleMatchCache; import com.ghostchu.peerbanhelper.util.time.ExceptedTime; -import com.ghostchu.peerbanhelper.util.time.InfoHashUtil; import com.ghostchu.peerbanhelper.util.time.TimeoutProtect; import com.ghostchu.peerbanhelper.web.JavalinWebContainer; import com.ghostchu.peerbanhelper.wrapper.BanMetadata; @@ -51,16 +48,12 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.gson.JsonObject; -import com.googlecode.aviator.AviatorEvaluator; -import com.googlecode.aviator.EvalMode; -import com.googlecode.aviator.Options; -import com.googlecode.aviator.runtime.JavaMethodReflectionFunctionMissing; import inet.ipaddr.IPAddress; import inet.ipaddr.format.util.DualIPv4v6Tries; import io.javalin.util.JavalinBindException; import lombok.Getter; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.bspfsystems.yamlconfiguration.configuration.ConfigurationSection; import org.bspfsystems.yamlconfiguration.configuration.MemoryConfiguration; import org.jetbrains.annotations.NotNull; @@ -74,7 +67,6 @@ import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; -import java.math.MathContext; import java.sql.SQLException; import java.util.*; import java.util.concurrent.*; @@ -83,8 +75,6 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; -import static com.ghostchu.peerbanhelper.Main.DEF_LOCALE; -import static com.ghostchu.peerbanhelper.text.TextManager.tl; import static com.ghostchu.peerbanhelper.text.TextManager.tlUI; @Slf4j @@ -93,6 +83,7 @@ public class PeerBanHelperServer implements Reloadable { private static final long BANLIST_SAVE_INTERVAL = 60 * 60 * 1000; private final CheckResult NO_MATCHES_CHECK_RESULT = new CheckResult(getClass(), PeerAction.NO_ACTION, 0, new TranslationComponent("No Matches"), new TranslationComponent("No Matches")); private final Map BAN_LIST = new ConcurrentHashMap<>(); + @Getter private final AtomicBoolean needReApplyBanList = new AtomicBoolean(); private final Deque scheduledBanListOperations = new ConcurrentLinkedDeque<>(); private final List downloaders = new CopyOnWriteArrayList<>(); @@ -139,6 +130,10 @@ public class PeerBanHelperServer implements Reloadable { private BanListDao banListDao; @Autowired private Laboratory laboratory; + @Autowired + private DNSLookup dnsLookup; + @Getter + private boolean globalPaused = false; // @Autowired // private IPFSBanListShare share; @@ -170,7 +165,8 @@ public ReloadResult reloadModule() throws Exception { private void unbanWhitelistedPeers() { for (PeerAddress peerAddress : BAN_LIST.keySet()) { - if (ignoreAddresses.elementContains(peerAddress.getAddress())) { + var node = ignoreAddresses.elementsContaining(peerAddress.getAddress()); + if (node != null) { scheduleUnBanPeer(peerAddress); } } @@ -180,7 +176,6 @@ public void start() throws SQLException { log.info(tlUI(Lang.MOTD, Main.getMeta().getVersion())); loadDownloaders(); registerBanListInvokers(); - setupScriptEngine(); registerModules(); setupIPDB(); registerHttpServer(); @@ -197,59 +192,18 @@ public void start() throws SQLException { Main.getReloadManager().register(this); Main.getEventBus().post(new PBHServerStartedEvent(this)); sendSnapshotAlert(); - + runTestCode(); } - private void setupScriptEngine() { - AviatorEvaluator.getInstance().setCachedExpressionByDefault(true); - // ASM 性能优先 - AviatorEvaluator.getInstance().setOption(Options.EVAL_MODE, EvalMode.ASM); - // EVAL 性能优先 - AviatorEvaluator.getInstance().setOption(Options.OPTIMIZE_LEVEL, AviatorEvaluator.EVAL); - // 降低浮点计算精度 - AviatorEvaluator.getInstance().setOption(Options.MATH_CONTEXT, MathContext.DECIMAL32); - // 启用变量语法糖 - AviatorEvaluator.getInstance().setOption(Options.ENABLE_PROPERTY_SYNTAX_SUGAR, true); -// // 表达式允许序列化和反序列化 -// AviatorEvaluator.getInstance().setOption(Options.SERIALIZABLE, true); - // 启用反射方法查找 - AviatorEvaluator.getInstance().setFunctionMissing(JavaMethodReflectionFunctionMissing.getInstance()); - // 注册反射调用 - registerFunctions(IPAddressUtil.class); - registerFunctions(HTTPUtil.class); - registerFunctions(JsonUtil.class); - registerFunctions(Lang.class); - registerFunctions(StrUtil.class); - registerFunctions(PeerBanHelperServer.class); - registerFunctions(InfoHashUtil.class); - registerFunctions(CommonUtil.class); - registerFunctions(ByteUtil.class); - registerFunctions(MiscUtil.class); - registerFunctions(MsgUtil.class); - registerFunctions(SharedObject.class); - registerFunctions(UrlEncoderDecoder.class); - registerFunctions(URLUtil.class); - registerFunctions(WebUtil.class); - registerFunctions(RSAUtils.class); - registerFunctions(Pageable.class); - registerFunctions(TextManager.class); - registerFunctions(ExchangeMap.class); - registerFunctions(Main.class); - } - - private void registerFunctions(Class clazz) { - try { - AviatorEvaluator.addInstanceFunctions(StringUtils.uncapitalize(clazz.getSimpleName()), clazz); - } catch (IllegalAccessException | NoSuchMethodException e) { - log.error("Internal error: failed on register instance functions: {}", clazz.getName(), e); - } - try { - AviatorEvaluator.addStaticFunctions(StringUtils.capitalize(clazz.getSimpleName()), clazz); - } catch (IllegalAccessException | NoSuchMethodException e) { - log.error("Internal error: failed on register static functions: {}", clazz.getName(), e); + @SneakyThrows + private void runTestCode() { + if (!"LiveDebug".equalsIgnoreCase(System.getProperty("pbh.release"))) { + return; } + // run some junky test code here } + private void sendSnapshotAlert() { if (Main.getMeta().isSnapshotOrBeta()) { alertManager.publishAlert(false, AlertLevel.INFO, "unstable-alert", new TranslationComponent(Lang.ALERT_SNAPSHOT), new TranslationComponent(Lang.ALERT_SNAPSHOT_DESCRIPTION)); @@ -348,7 +302,7 @@ private void setupIPDB() { private void resetKnownDownloaders() { try { - for (Downloader downloader : downloaders) { + for (Downloader downloader : getDownloaders()) { var result = downloader.login(); if (result.success()) { downloader.setBanList(Collections.emptyList(), null, null, true); @@ -400,13 +354,13 @@ private void loadBanListToMemory() { Map data = banListDao.readBanList(); this.BAN_LIST.putAll(data); log.info(tlUI(Lang.LOAD_BANLIST_FROM_FILE, data.size())); - downloaders.forEach(downloader -> { + getDownloaders().forEach(downloader -> { if (downloader.login().success()) { downloader.setBanList(BAN_LIST.keySet(), null, null, true); } }); Collection relaunch = data.values().stream().map(BanMetadata::getTorrent).toList(); - downloaders.forEach(downloader -> downloader.relaunchTorrentIfNeededByTorrentWrapper(relaunch)); + getDownloaders().forEach(downloader -> downloader.relaunchTorrentIfNeededByTorrentWrapper(relaunch)); } catch (Exception e) { log.error(tlUI(Lang.ERR_UPDATE_BAN_LIST), e); } @@ -493,6 +447,16 @@ public void banWave() { if (!banWaveLock.tryLock(3, TimeUnit.SECONDS)) { return; } + if (isGlobalPaused()) { + if (needReApplyBanList.get()) { + getDownloaders().forEach(downloader -> { + if (downloader.login().success()) { + downloader.setBanList(BAN_LIST.keySet(), null, null, true); + } + }); + } + return; + } banWaveWatchDog.setLastOperation("Ban wave - start"); long startTimer = System.currentTimeMillis(); // 重置所有下载器状态为健康,这样后面失败就会对其降级 @@ -502,7 +466,7 @@ public void banWave() { Map> needRelaunched = new ConcurrentHashMap<>(); // 执行计划任务 banWaveWatchDog.setLastOperation("Run scheduled tasks"); - downloaders.forEach(Downloader::runScheduleTasks); + getDownloaders().forEach(Downloader::runScheduleTasks); // 被解除封禁的对等体列表 banWaveWatchDog.setLastOperation("Remove expired bans"); Collection unbannedPeers = removeExpiredBans(); @@ -520,7 +484,14 @@ public void banWave() { try (TimeoutProtect protect = new TimeoutProtect(ExceptedTime.CHECK_BANS.getTimeout(), (t) -> { log.error(tlUI(Lang.TIMING_CHECK_BANS)); })) { - downloaders.forEach(downloader -> protect.getService().submit(() -> downloaderBanDetailMap.put(downloader, checkBans(peers.get(downloader), downloader)))); + getDownloaders().forEach(downloader -> protect.getService().submit(() -> { + try { + downloaderBanDetailMap.put(downloader, checkBans(peers.get(downloader), downloader)); + } catch (Exception e) { + log.error("Unexpected fatal error occurred while checking bans!", e); + throw e; + } + })); } @@ -571,7 +542,15 @@ public void banWave() { relaunch.add(detail.torrent()); banPeer(banlistClone, banMetadata, detail.torrent(), detail.peer()); if (detail.result().action() != PeerAction.BAN_FOR_DISCONNECT) { - log.info(tlUI(Lang.BAN_PEER, detail.peer().getPeerAddress(), detail.peer().getPeerId(), detail.peer().getClientName(), detail.peer().getProgress(), detail.peer().getUploaded(), detail.peer().getDownloaded(), detail.torrent().getName(), tl(DEF_LOCALE, detail.result().reason()))); + log.info(tlUI(Lang.BAN_PEER, + detail.peer().getPeerAddress(), + detail.peer().getPeerId(), + detail.peer().getClientName(), + detail.peer().getProgress(), + detail.peer().getUploaded(), + detail.peer().getDownloaded(), + detail.torrent().getName(), + tlUI(detail.result().reason()))); } } } catch (Exception e) { @@ -592,13 +571,13 @@ public void banWave() { log.error(tlUI(Lang.TIMING_APPLY_BAN_LIST)); })) { if (!needReApplyBanList.get()) { - downloaders.forEach(downloader -> protect.getService().submit(() -> + getDownloaders().forEach(downloader -> protect.getService().submit(() -> updateDownloader(downloader, !bannedPeers.isEmpty() || !unbannedPeers.isEmpty(), needRelaunched.getOrDefault(downloader, Collections.emptyList()), bannedPeers, unbannedPeers, false))); } else { log.info(tlUI(Lang.APPLYING_FULL_BANLIST_TO_DOWNLOADER)); - downloaders.forEach(downloader -> protect.getService().submit(() -> { + getDownloaders().forEach(downloader -> protect.getService().submit(() -> { List torrents = downloader.getTorrents(); var list = BAN_LIST.values().stream().map(meta -> meta.getTorrent().getId()).toList(); torrents.removeIf(torrent -> !list.contains(torrent.getId())); @@ -608,7 +587,7 @@ public void banWave() { needReApplyBanList.set(false); } } - if (!hideFinishLogs && !downloaders.isEmpty()) { + if (!hideFinishLogs && !getDownloaders().isEmpty()) { long downloadersCount = peers.keySet().size(); long torrentsCount = peers.values().stream().mapToLong(e -> e.keySet().size()).sum(); long peersCount = peers.values().stream().flatMap(e -> e.values().stream()).mapToLong(List::size).sum(); @@ -635,8 +614,14 @@ private List checkBans(Map> provided, @NotNull Do List peers = provided.get(torrent); for (Peer peer : peers) { protect.getService().submit(() -> { - CheckResult checkResult = checkBan(torrent, peer, downloader); - details.add(new BanDetail(torrent, peer, checkResult, checkResult.duration())); + try { + CheckResult checkResult = checkBan(torrent, peer, downloader); + details.add(new BanDetail(torrent, peer, checkResult, checkResult.duration())); + + } catch (Exception e) { + log.error("Unexpected error occurred while checking bans", e); + throw e; + } }); } } @@ -675,8 +660,10 @@ public void updateDownloader(@NotNull Downloader downloader, boolean updateBanLi try { var loginResult = downloader.login(); if (!loginResult.success()) { - log.error(tlUI(Lang.ERR_CLIENT_LOGIN_FAILURE_SKIP, downloader.getName(), downloader.getEndpoint(), tlUI(loginResult.getMessage()))); - downloader.setLastStatus(DownloaderLastStatus.ERROR, loginResult.getMessage()); + if (loginResult.getStatus() != DownloaderLoginResult.Status.PAUSED) { + log.error(tlUI(Lang.ERR_CLIENT_LOGIN_FAILURE_SKIP, downloader.getName(), downloader.getEndpoint(), tlUI(loginResult.getMessage()))); + downloader.setLastStatus(DownloaderLastStatus.ERROR, loginResult.getMessage()); + } return; } else { downloader.setLastStatus(DownloaderLastStatus.HEALTHY, loginResult.getMessage()); @@ -727,6 +714,7 @@ private void registerModules() { moduleManager.register(BtnNetworkOnline.class); moduleManager.register(BlockListController.class); moduleManager.register(IPBlackRuleList.class); + moduleManager.register(PTRBlacklist.class); moduleManager.register(PBHMetricsController.class); moduleManager.register(PBHBanController.class); moduleManager.register(PBHMetadataController.class); @@ -746,12 +734,13 @@ private void registerModules() { moduleManager.register(PBHPushController.class); moduleManager.register(PBHLabController.class); moduleManager.register(PBHEasterEggController.class); + } public Map>> collectPeers() { Map>> peers = new HashMap<>(); try (var service = Executors.newVirtualThreadPerTaskExecutor()) { - downloaders.forEach(downloader -> service.submit(() -> { + getDownloaders().forEach(downloader -> service.submit(() -> { try { Map> p = collectPeers(downloader); peers.put(downloader, p); @@ -767,10 +756,12 @@ public Map> collectPeers(Downloader downloader) { Map> peers = new ConcurrentHashMap<>(); var loginResult = downloader.login(); if (!loginResult.success()) { - log.error(tlUI(Lang.ERR_CLIENT_LOGIN_FAILURE_SKIP, downloader.getName(), downloader.getEndpoint(), tlUI(loginResult.getMessage()))); - downloader.setLastStatus(DownloaderLastStatus.ERROR, loginResult.getMessage()); - if (loginResult.getStatus() == DownloaderLoginResult.Status.MISSING_COMPONENTS || loginResult.getStatus() == DownloaderLoginResult.Status.REQUIRE_TAKE_ACTIONS) { - downloader.setLastStatus(DownloaderLastStatus.NEED_TAKE_ACTION, loginResult.getMessage()); + if (loginResult.getStatus() != DownloaderLoginResult.Status.PAUSED) { + log.error(tlUI(Lang.ERR_CLIENT_LOGIN_FAILURE_SKIP, downloader.getName(), downloader.getEndpoint(), tlUI(loginResult.getMessage()))); + downloader.setLastStatus(DownloaderLastStatus.ERROR, loginResult.getMessage()); + if (loginResult.getStatus() == DownloaderLoginResult.Status.MISSING_COMPONENTS || loginResult.getStatus() == DownloaderLoginResult.Status.REQUIRE_TAKE_ACTIONS) { + downloader.setLastStatus(DownloaderLastStatus.NEED_TAKE_ACTION, loginResult.getMessage()); + } } return Collections.emptyMap(); } @@ -832,7 +823,8 @@ public CheckResult checkBan(@NotNull Torrent torrent, @NotNull Peer peer, @NotNu if (peer.getPeerAddress().getAddress().isAnyLocal()) { return new CheckResult(getClass(), PeerAction.SKIP, 0, new TranslationComponent("general-rule-local-address"), new TranslationComponent("general-reason-skip-local-peers")); } - if (ignoreAddresses.elementContains(peer.getPeerAddress().getAddress())) { + var node = ignoreAddresses.elementsContaining(peer.getPeerAddress().getAddress()); + if (node != null) { return new CheckResult(getClass(), PeerAction.SKIP, 0, new TranslationComponent("general-rule-ignored-address"), new TranslationComponent("general-reason-skip-ignored-peers")); } try { @@ -916,9 +908,19 @@ private void banPeer(@NotNull Collection compareWith, @NotNull BanM banMetadata.setReverseLookup("N/A"); if (Main.getMainConfig().getBoolean("lookup.dns-reverse-lookup")) { executor.submit(() -> { - String hostName = peer.getPeerAddress().getAddress().toInetAddress().getHostName(); - if (!peer.getPeerAddress().getIp().equals(hostName)) { - banMetadata.setReverseLookup(peer.getPeerAddress().getAddress().toInetAddress().getHostName()); + if (laboratory.isExperimentActivated(Experiments.DNSJAVA.getExperiment())) { + dnsLookup.ptr(peer.getPeerAddress().getAddress().toReverseDNSLookupString()).thenAccept(hostName -> { + if (hostName.isPresent()) { + if (!peer.getPeerAddress().getIp().equals(hostName.get())) { + banMetadata.setReverseLookup(hostName.get()); + } + } + }); + } else { + String hostName = peer.getPeerAddress().getAddress().toInetAddress().getHostName(); + if (!peer.getPeerAddress().getIp().equals(hostName)) { + banMetadata.setReverseLookup(peer.getPeerAddress().getAddress().toInetAddress().getHostName()); + } } }); } @@ -933,7 +935,9 @@ public void scheduleBanPeer(@NotNull BanMetadata banMetadata, @NotNull Torrent t downloader, new BanDetail(torrent, peer, - new CheckResult(getClass(), PeerAction.BAN, banDuration, new TranslationComponent(Lang.USER_MANUALLY_BAN_RULE), new TranslationComponent(Lang.USER_MANUALLY_BAN_REASON)) + new CheckResult(getClass(), PeerAction.BAN, banDuration, + new TranslationComponent(Lang.USER_MANUALLY_BAN_RULE), + new TranslationComponent(Lang.USER_MANUALLY_BAN_REASON)) , banDuration) ))); } @@ -972,6 +976,14 @@ public Map> getLivePeersSnapshot() { return LIVE_PEERS; } + public void setGlobalPaused(boolean globalPaused) { + this.globalPaused = globalPaused; + if (globalPaused) { + ExchangeMap.GUI_DISPLAY_FLAGS.add(new ExchangeMap.DisplayFlag("global-paused", 20, tlUI(Lang.STATUS_BAR_GLOBAL_PAUSED))); + } else { + ExchangeMap.GUI_DISPLAY_FLAGS.removeIf(f -> "global-paused".equals(f.getId())); + } + } /** * Use @Autowired if available diff --git a/src/main/java/com/ghostchu/peerbanhelper/btn/BtnExceptionRuleParsed.java b/src/main/java/com/ghostchu/peerbanhelper/btn/BtnExceptionRuleParsed.java index e7ad7c4265..0282f10de2 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/btn/BtnExceptionRuleParsed.java +++ b/src/main/java/com/ghostchu/peerbanhelper/btn/BtnExceptionRuleParsed.java @@ -3,12 +3,9 @@ import com.ghostchu.peerbanhelper.text.Lang; import com.ghostchu.peerbanhelper.text.TranslationComponent; import com.ghostchu.peerbanhelper.util.IPAddressUtil; -import com.ghostchu.peerbanhelper.util.rule.AbstractMatcher; -import com.ghostchu.peerbanhelper.util.rule.MatchResult; -import com.ghostchu.peerbanhelper.util.rule.Rule; -import com.ghostchu.peerbanhelper.util.rule.RuleParser; +import com.ghostchu.peerbanhelper.util.rule.*; import com.ghostchu.peerbanhelper.util.rule.matcher.IPMatcher; -import inet.ipaddr.format.util.DualIPv4v6Tries; +import inet.ipaddr.format.util.DualIPv4v6AssociativeTries; import lombok.Data; import org.jetbrains.annotations.NotNull; @@ -42,7 +39,7 @@ private Map> parsePortRule(Map> portRul @Override public @NotNull MatchResult match0(@NotNull String content) { boolean hit = Integer.parseInt(content) == s; - return hit ? MatchResult.TRUE : MatchResult.DEFAULT; + return hit ? new MatchResult(MatchResultEnum.TRUE, new TranslationComponent(Lang.MATCH_CONDITION_PORT_MATCH)) : new MatchResult(MatchResultEnum.DEFAULT, new TranslationComponent("Port seems OK")); } @Override @@ -69,7 +66,7 @@ public String matcherIdentifier() { public Map> parseIPRule(Map> raw) { Map> rules = new HashMap<>(); raw.forEach((k, v) -> { - DualIPv4v6Tries tries = new DualIPv4v6Tries(); + DualIPv4v6AssociativeTries tries = new DualIPv4v6AssociativeTries<>(); v.stream().map(IPAddressUtil::getIPAddress).forEach(tries::add); rules.put(k, List.of(new BtnRuleIpMatcher(version, k, k, List.of(tries)))); }); @@ -86,7 +83,7 @@ public static class BtnRuleIpMatcher extends IPMatcher { private final String version; - public BtnRuleIpMatcher(String version, String ruleId, String ruleName, List ruleData) { + public BtnRuleIpMatcher(String version, String ruleId, String ruleName, List> ruleData) { super(ruleId, ruleName, ruleData); this.version = version; } diff --git a/src/main/java/com/ghostchu/peerbanhelper/btn/BtnNetwork.java b/src/main/java/com/ghostchu/peerbanhelper/btn/BtnNetwork.java index 231d1d9081..df68417a0d 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/btn/BtnNetwork.java +++ b/src/main/java/com/ghostchu/peerbanhelper/btn/BtnNetwork.java @@ -6,6 +6,7 @@ import com.ghostchu.peerbanhelper.database.dao.impl.PeerRecordDao; import com.ghostchu.peerbanhelper.scriptengine.ScriptEngine; import com.ghostchu.peerbanhelper.text.Lang; +import com.ghostchu.peerbanhelper.text.TranslationComponent; import com.ghostchu.peerbanhelper.util.HTTPUtil; import com.ghostchu.peerbanhelper.util.rule.ModuleMatchCache; import com.ghostchu.simplereloadlib.ReloadResult; @@ -24,6 +25,7 @@ import java.net.http.HttpClient; import java.net.http.HttpResponse; import java.time.Duration; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Executors; @@ -39,9 +41,11 @@ // 特别注意:该类不允许静态初始化任何内容 public class BtnNetwork implements Reloadable { @Getter - private final Map, BtnAbility> abilities = new HashMap<>(); + private final Map, BtnAbility> abilities = Collections.synchronizedMap(new HashMap<>()); + private final ScriptEngine scriptEngine; private final AtomicBoolean configSuccess = new AtomicBoolean(false); + private TranslationComponent configResult; private boolean scriptExecute; @Getter private ScheduledExecutorService executeService = null; @@ -68,6 +72,7 @@ public BtnNetwork(PeerBanHelperServer server, ScriptEngine scriptEngine, ModuleM @Override public ReloadResult reloadModule() throws Exception { reloadConfig(); + log.info("BtnNetwork reloaded"); return Reloadable.super.reloadModule(); } @@ -78,6 +83,8 @@ public void reloadConfig() { this.appId = Main.getMainConfig().getString("btn.app-id"); this.appSecret = Main.getMainConfig().getString("btn.app-secret"); this.scriptExecute = Main.getMainConfig().getBoolean("btn.allow-script-execute"); + configSuccess.set(false); + configResult = null; resetAbilities(); setupHttpClient(); resetScheduler(); @@ -95,19 +102,20 @@ private void resetScheduler() { } if (enabled) { executeService = Executors.newScheduledThreadPool(2); - executeService.scheduleWithFixedDelay(this::checkIfNeedRetryConfig, 600, 600, TimeUnit.SECONDS); + executeService.scheduleWithFixedDelay(this::checkIfNeedRetryConfig, 0, 600, TimeUnit.SECONDS); } else { executeService = null; } } - public void configBtnNetwork() { + public synchronized void configBtnNetwork() { String response = ""; int statusCode = 0; try { HttpResponse resp = HTTPUtil.retryableSend(httpClient, MutableRequest.GET(configUrl), HttpResponse.BodyHandlers.ofString()).join(); if (resp.statusCode() != 200) { log.error(tlUI(Lang.BTN_CONFIG_FAILS, resp.statusCode() + " - " + resp.body(), 600)); + configResult = new TranslationComponent(Lang.BTN_CONFIG_STATUS_UNSUCCESSFUL_HTTP_REQUEST, configUrl, resp.statusCode(), resp.body()); return; } statusCode = resp.statusCode(); @@ -118,10 +126,12 @@ public void configBtnNetwork() { } int min_protocol_version = json.get("min_protocol_version").getAsInt(); if (Main.PBH_BTN_PROTOCOL_IMPL_VERSION < min_protocol_version) { - throw new IllegalStateException(tlUI(Lang.BTN_INCOMPATIBLE_SERVER)); + configResult = new TranslationComponent(Lang.BTN_CONFIG_STATUS_UNSUCCESSFUL_INCOMPATIBLE_BTN_PROTOCOL_VERSION_CLIENT, Main.PBH_BTN_PROTOCOL_IMPL_VERSION, min_protocol_version); + throw new IllegalStateException(tlUI(configResult)); } int max_protocol_version = json.get("max_protocol_version").getAsInt(); if (Main.PBH_BTN_PROTOCOL_IMPL_VERSION > max_protocol_version) { + configResult = new TranslationComponent(Lang.BTN_CONFIG_STATUS_UNSUCCESSFUL_INCOMPATIBLE_BTN_PROTOCOL_VERSION_SERVER, Main.PBH_BTN_PROTOCOL_IMPL_VERSION, max_protocol_version); throw new IllegalStateException(tlUI(Lang.BTN_INCOMPATIBLE_SERVER)); } resetScheduler(); @@ -157,8 +167,11 @@ public void configBtnNetwork() { } }); configSuccess.set(true); + configResult = new TranslationComponent(Lang.BTN_CONFIG_STATUS_SUCCESSFUL); } catch (Throwable e) { log.error(tlUI(Lang.BTN_CONFIG_FAILS, statusCode+" - "+response, 600), e); + configResult = new TranslationComponent(Lang.BTN_CONFIG_STATUS_EXCEPTION, e.getClass().getName(), e.getMessage()); + configSuccess.set(false); } } diff --git a/src/main/java/com/ghostchu/peerbanhelper/btn/BtnRuleParsed.java b/src/main/java/com/ghostchu/peerbanhelper/btn/BtnRuleParsed.java index 40e037f024..62ad5ca7e3 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/btn/BtnRuleParsed.java +++ b/src/main/java/com/ghostchu/peerbanhelper/btn/BtnRuleParsed.java @@ -5,12 +5,9 @@ import com.ghostchu.peerbanhelper.text.Lang; import com.ghostchu.peerbanhelper.text.TranslationComponent; import com.ghostchu.peerbanhelper.util.IPAddressUtil; -import com.ghostchu.peerbanhelper.util.rule.AbstractMatcher; -import com.ghostchu.peerbanhelper.util.rule.MatchResult; -import com.ghostchu.peerbanhelper.util.rule.Rule; -import com.ghostchu.peerbanhelper.util.rule.RuleParser; +import com.ghostchu.peerbanhelper.util.rule.*; import com.ghostchu.peerbanhelper.util.rule.matcher.IPMatcher; -import inet.ipaddr.format.util.DualIPv4v6Tries; +import inet.ipaddr.format.util.DualIPv4v6AssociativeTries; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; @@ -70,7 +67,7 @@ private Map> parsePortRule(Map> portRul @Override public @NotNull MatchResult match0(@NotNull String content) { boolean hit = Integer.parseInt(content) == s; - return hit ? MatchResult.TRUE : MatchResult.DEFAULT; + return hit ? new MatchResult(MatchResultEnum.TRUE, new TranslationComponent(Lang.MATCH_CONDITION_PORT_MATCH)) : new MatchResult(MatchResultEnum.DEFAULT, new TranslationComponent("Port seems OK")); } @Override @@ -97,7 +94,7 @@ public String matcherIdentifier() { public Map parseIPRule(Map> raw) { Map rules = new HashMap<>(); raw.forEach((k, v) -> { - DualIPv4v6Tries tries = new DualIPv4v6Tries(); + DualIPv4v6AssociativeTries tries = new DualIPv4v6AssociativeTries<>(); v.stream().map(IPAddressUtil::getIPAddress).forEach(tries::add); rules.put(k,new IPMatcher(version, k, List.of(tries))); }); diff --git a/src/main/java/com/ghostchu/peerbanhelper/btn/ability/BtnAbilityException.java b/src/main/java/com/ghostchu/peerbanhelper/btn/ability/BtnAbilityException.java index 188d5fd8a5..c7d4cf9dce 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/btn/ability/BtnAbilityException.java +++ b/src/main/java/com/ghostchu/peerbanhelper/btn/ability/BtnAbilityException.java @@ -34,7 +34,7 @@ import static com.ghostchu.peerbanhelper.text.TextManager.tlUI; @Slf4j -public class BtnAbilityException extends AbstractBtnAbility { +public final class BtnAbilityException extends AbstractBtnAbility { private final BtnNetwork btnNetwork; private final long interval; private final String endpoint; diff --git a/src/main/java/com/ghostchu/peerbanhelper/btn/ability/BtnAbilityReconfigure.java b/src/main/java/com/ghostchu/peerbanhelper/btn/ability/BtnAbilityReconfigure.java index 0b16188db2..0717f76de5 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/btn/ability/BtnAbilityReconfigure.java +++ b/src/main/java/com/ghostchu/peerbanhelper/btn/ability/BtnAbilityReconfigure.java @@ -16,7 +16,7 @@ import static com.ghostchu.peerbanhelper.text.TextManager.tlUI; @Slf4j -public class BtnAbilityReconfigure extends AbstractBtnAbility { +public final class BtnAbilityReconfigure extends AbstractBtnAbility { private final BtnNetwork btnNetwork; private final long interval; private final long randomInitialDelay; diff --git a/src/main/java/com/ghostchu/peerbanhelper/btn/ability/BtnAbilityRules.java b/src/main/java/com/ghostchu/peerbanhelper/btn/ability/BtnAbilityRules.java index d59121261d..f97d40da45 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/btn/ability/BtnAbilityRules.java +++ b/src/main/java/com/ghostchu/peerbanhelper/btn/ability/BtnAbilityRules.java @@ -31,7 +31,7 @@ import static com.ghostchu.peerbanhelper.text.TextManager.tlUI; @Slf4j -public class BtnAbilityRules extends AbstractBtnAbility { +public final class BtnAbilityRules extends AbstractBtnAbility { private final BtnNetwork btnNetwork; private final long interval; private final String endpoint; diff --git a/src/main/java/com/ghostchu/peerbanhelper/btn/ability/BtnAbilitySubmitBans.java b/src/main/java/com/ghostchu/peerbanhelper/btn/ability/BtnAbilitySubmitBans.java index 7ede9cd6f2..1410cf4442 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/btn/ability/BtnAbilitySubmitBans.java +++ b/src/main/java/com/ghostchu/peerbanhelper/btn/ability/BtnAbilitySubmitBans.java @@ -28,7 +28,7 @@ import static com.ghostchu.peerbanhelper.text.TextManager.tlUI; @Slf4j -public class BtnAbilitySubmitBans extends AbstractBtnAbility { +public final class BtnAbilitySubmitBans extends AbstractBtnAbility { private final BtnNetwork btnNetwork; private final long interval; private final String endpoint; diff --git a/src/main/java/com/ghostchu/peerbanhelper/btn/ability/BtnAbilitySubmitHistory.java b/src/main/java/com/ghostchu/peerbanhelper/btn/ability/BtnAbilitySubmitHistory.java index 6a11955ec3..2fdf59c17c 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/btn/ability/BtnAbilitySubmitHistory.java +++ b/src/main/java/com/ghostchu/peerbanhelper/btn/ability/BtnAbilitySubmitHistory.java @@ -24,7 +24,7 @@ import static com.ghostchu.peerbanhelper.text.TextManager.tlUI; @Slf4j -public class BtnAbilitySubmitHistory extends AbstractBtnAbility { +public final class BtnAbilitySubmitHistory extends AbstractBtnAbility { private final BtnNetwork btnNetwork; private final long interval; private final String endpoint; @@ -103,6 +103,7 @@ private List generatePing() { Pageable pageable = new Pageable(0, 10000); // 再多的话,担心爆内存 return btnNetwork.getPeerRecordDao().getPendingSubmitPeerRecords(pageable, new Timestamp(lastSubmitAt)).getResults().stream() + .filter(r -> !r.getDownloader().equals("")) .map(BtnPeerHistory::from).collect(Collectors.toList()); } diff --git a/src/main/java/com/ghostchu/peerbanhelper/btn/ability/BtnAbilitySubmitPeers.java b/src/main/java/com/ghostchu/peerbanhelper/btn/ability/BtnAbilitySubmitPeers.java index 486ef5edaf..dcd37b27ba 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/btn/ability/BtnAbilitySubmitPeers.java +++ b/src/main/java/com/ghostchu/peerbanhelper/btn/ability/BtnAbilitySubmitPeers.java @@ -21,7 +21,7 @@ import static com.ghostchu.peerbanhelper.text.TextManager.tlUI; @Slf4j -public class BtnAbilitySubmitPeers extends AbstractBtnAbility { +public final class BtnAbilitySubmitPeers extends AbstractBtnAbility { private final BtnNetwork btnNetwork; private final long interval; private final String endpoint; diff --git a/src/main/java/com/ghostchu/peerbanhelper/btn/ping/BtnBan.java b/src/main/java/com/ghostchu/peerbanhelper/btn/ping/BtnBan.java index 31291bc0f2..0ed7673449 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/btn/ping/BtnBan.java +++ b/src/main/java/com/ghostchu/peerbanhelper/btn/ping/BtnBan.java @@ -8,7 +8,7 @@ @Data @AllArgsConstructor @NoArgsConstructor -public class BtnBan { +public final class BtnBan { @SerializedName("btn_ban") private boolean btnBan; @SerializedName("ban_unique_id") diff --git a/src/main/java/com/ghostchu/peerbanhelper/btn/ping/BtnBanPing.java b/src/main/java/com/ghostchu/peerbanhelper/btn/ping/BtnBanPing.java index e4d6360639..bfa23158ef 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/btn/ping/BtnBanPing.java +++ b/src/main/java/com/ghostchu/peerbanhelper/btn/ping/BtnBanPing.java @@ -10,7 +10,7 @@ @Data @AllArgsConstructor @NoArgsConstructor -public class BtnBanPing { +public final class BtnBanPing { @SerializedName("populate_time") private long populateTime; @SerializedName("bans") diff --git a/src/main/java/com/ghostchu/peerbanhelper/btn/ping/BtnPeer.java b/src/main/java/com/ghostchu/peerbanhelper/btn/ping/BtnPeer.java index 1485ce2199..5176794877 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/btn/ping/BtnPeer.java +++ b/src/main/java/com/ghostchu/peerbanhelper/btn/ping/BtnPeer.java @@ -2,7 +2,7 @@ import com.ghostchu.peerbanhelper.peer.Peer; import com.ghostchu.peerbanhelper.torrent.Torrent; -import com.ghostchu.peerbanhelper.util.time.InfoHashUtil; +import com.ghostchu.peerbanhelper.util.InfoHashUtil; import com.ghostchu.peerbanhelper.wrapper.PeerWrapper; import com.ghostchu.peerbanhelper.wrapper.TorrentWrapper; import com.google.gson.annotations.SerializedName; @@ -13,7 +13,7 @@ @Data @AllArgsConstructor @NoArgsConstructor -public class BtnPeer { +public final class BtnPeer { @SerializedName("ip_address") private String ipAddress; @SerializedName("peer_port") diff --git a/src/main/java/com/ghostchu/peerbanhelper/btn/ping/BtnPeerHistory.java b/src/main/java/com/ghostchu/peerbanhelper/btn/ping/BtnPeerHistory.java index 760e1363c0..5e3d287c46 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/btn/ping/BtnPeerHistory.java +++ b/src/main/java/com/ghostchu/peerbanhelper/btn/ping/BtnPeerHistory.java @@ -1,7 +1,7 @@ package com.ghostchu.peerbanhelper.btn.ping; import com.ghostchu.peerbanhelper.database.table.PeerRecordEntity; -import com.ghostchu.peerbanhelper.util.time.InfoHashUtil; +import com.ghostchu.peerbanhelper.util.InfoHashUtil; import com.google.gson.annotations.SerializedName; import lombok.AllArgsConstructor; import lombok.Data; @@ -12,7 +12,7 @@ @Data @AllArgsConstructor @NoArgsConstructor -public class BtnPeerHistory { +public final class BtnPeerHistory { @SerializedName("ip_address") private String ipAddress; @SerializedName("peer_id") diff --git a/src/main/java/com/ghostchu/peerbanhelper/btn/ping/BtnPeerHistoryPing.java b/src/main/java/com/ghostchu/peerbanhelper/btn/ping/BtnPeerHistoryPing.java index 32fcc4b266..9312a7b332 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/btn/ping/BtnPeerHistoryPing.java +++ b/src/main/java/com/ghostchu/peerbanhelper/btn/ping/BtnPeerHistoryPing.java @@ -10,7 +10,7 @@ @Data @AllArgsConstructor @NoArgsConstructor -public class BtnPeerHistoryPing { +public final class BtnPeerHistoryPing { @SerializedName("populate_time") private long populateTime; @SerializedName("peers") diff --git a/src/main/java/com/ghostchu/peerbanhelper/btn/ping/BtnPeerPing.java b/src/main/java/com/ghostchu/peerbanhelper/btn/ping/BtnPeerPing.java index efa70e8ebc..4c06617788 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/btn/ping/BtnPeerPing.java +++ b/src/main/java/com/ghostchu/peerbanhelper/btn/ping/BtnPeerPing.java @@ -10,7 +10,7 @@ @Data @AllArgsConstructor @NoArgsConstructor -public class BtnPeerPing { +public final class BtnPeerPing { @SerializedName("populate_time") private long populateTime; @SerializedName("peers") diff --git a/src/main/java/com/ghostchu/peerbanhelper/config/MainConfigUpdateScript.java b/src/main/java/com/ghostchu/peerbanhelper/config/MainConfigUpdateScript.java index 06aa013282..89c3f79063 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/config/MainConfigUpdateScript.java +++ b/src/main/java/com/ghostchu/peerbanhelper/config/MainConfigUpdateScript.java @@ -30,6 +30,13 @@ private void validate() { } + @UpdateScript(version = 27) + public void updateVacuum() { + conf.set("persist.vacuum-interval-days", 60); + } + + + @UpdateScript(version = 26) public void pushProvidersSMTPStructUpgrade() { var pushNotification = conf.getConfigurationSection("push-notification"); @@ -71,6 +78,11 @@ public void pushProvidersCleanup() { single.set("sendkey", sendKey); single.set("send-key", null); } + var chatId = single.get("chat-id"); + if (chatId != null) { + single.set("chatid", chatId); + single.set("chat-id", null); + } pushNotification.set(key, single); } conf.set("push-notification", pushNotification); diff --git a/src/main/java/com/ghostchu/peerbanhelper/config/ProfileUpdateScript.java b/src/main/java/com/ghostchu/peerbanhelper/config/ProfileUpdateScript.java index dcb476f2d7..8a5c9283ad 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/config/ProfileUpdateScript.java +++ b/src/main/java/com/ghostchu/peerbanhelper/config/ProfileUpdateScript.java @@ -25,7 +25,13 @@ public ProfileUpdateScript(YamlConfiguration conf) { this.conf = conf; } - + @UpdateScript(version = 24) + public void ptrBlacklistAndUpdateTorExitNodeList(YamlConfiguration bundled) { + conf.set("module.ptr-blacklist", bundled.get("module.ptr-blacklist")); + if("https://cdn.jsdelivr.net/gh/platformcosmo/Tor-IP-Addresses/tor-exit-nodes.lst".equals(conf.getString("module.ip-address-blocker-rules.rules.tor-exit-nodes.url"))){ + conf.set("module.ip-address-blocker-rules.rules.tor-exit-nodes.url", "https://cdn.jsdelivr.net/gh/7c/torfilter/lists/txt/torfilter-1d-flat.txt"); + } + } @UpdateScript(version = 22) public void workaroundForBadWebUI() { diff --git a/src/main/java/com/ghostchu/peerbanhelper/database/Database.java b/src/main/java/com/ghostchu/peerbanhelper/database/Database.java index ed0fd68077..5b51c8ce89 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/database/Database.java +++ b/src/main/java/com/ghostchu/peerbanhelper/database/Database.java @@ -1,7 +1,11 @@ package com.ghostchu.peerbanhelper.database; import com.ghostchu.peerbanhelper.Main; +import com.ghostchu.peerbanhelper.lab.Experiments; +import com.ghostchu.peerbanhelper.lab.Laboratory; import com.ghostchu.peerbanhelper.text.Lang; +import com.ghostchu.peerbanhelper.util.MiscUtil; +import com.ghostchu.peerbanhelper.util.MsgUtil; import com.j256.ormlite.field.DataPersisterManager; import com.j256.ormlite.jdbc.JdbcSingleConnectionSource; import com.j256.ormlite.jdbc.db.SqliteDatabaseType; @@ -12,8 +16,13 @@ import org.springframework.stereotype.Component; import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; import java.sql.Connection; import java.sql.SQLException; +import java.time.Duration; import static com.ghostchu.peerbanhelper.text.TextManager.tlUI; @@ -21,31 +30,54 @@ @Slf4j @Component public class Database { + private final File sqliteDb; + private final File dbMaintenanceFile; + private final Laboratory laboratory; private JdbcSingleConnectionSource dataSource; private HikariDataSource hikari; private DatabaseHelper helper; - public Database() throws SQLException, ClassNotFoundException { + public Database(Laboratory laboratory) throws SQLException, ClassNotFoundException { + this.laboratory = laboratory; File databaseDirectory = new File(Main.getDataDirectory(), "persist"); if (!databaseDirectory.exists()) { databaseDirectory.mkdirs(); } - File sqliteDb = new File(databaseDirectory, "peerbanhelper.db"); + this.sqliteDb = new File(databaseDirectory, "peerbanhelper.db"); + this.dbMaintenanceFile = new File(databaseDirectory, "peerbanhelper.db.maintenance"); registerPersisters(); setupDatabase(sqliteDb); + } private void registerPersisters() { DataPersisterManager.registerDataPersisters(TranslationComponentPersistener.getSingleton()); } + private long getLastMaintenanceTime() { + if (!dbMaintenanceFile.exists()) { + return 0; + } + try { + return Long.parseLong(Files.readString(dbMaintenanceFile.toPath())); + } catch (Exception e) { + return 0; + } + } + + private void setLastMaintenanceTime(long time) { + try { + Files.writeString(dbMaintenanceFile.toPath(), String.valueOf(time)); + } catch (IOException e) { + } + } + public void setupDatabase(File file) throws SQLException { HikariConfig config = new HikariConfig(); config.setPoolName("PeerBanHelper SQLite Connection Pool"); config.setDriverClassName("org.sqlite.JDBC"); config.setJdbcUrl("jdbc:sqlite:" + file); config.setConnectionTestQuery("SELECT 1"); - config.setMaxLifetime(60000); // 60 Sec config.setMaximumPoolSize(1); // 50 Connections (including idle connections) this.hikari = new HikariDataSource(config); Connection rawConnection = this.hikari.getConnection(); @@ -53,6 +85,38 @@ public void setupDatabase(File file) throws SQLException { try (var stmt = rawConnection.createStatement()) { stmt.executeUpdate("PRAGMA synchronous = NORMAL"); stmt.executeUpdate("PRAGMA journal_mode = WAL"); + stmt.executeUpdate("PRAGMA auto_vacuum = INCREMENTAL"); + try { + if (System.currentTimeMillis() - getLastMaintenanceTime() >= Duration.ofDays(Main.getMainConfig().getInt("persist.vacuum-interval-days")).toMillis()) { + if (System.getProperty("pbh.disableSQLiteVacuum") == null) { + if (laboratory.isExperimentActivated(Experiments.SQLITE_VACUUM.getExperiment())) { + log.info(tlUI(Lang.SQLITE_VACUUM_BACKUP)); + // 防强关备份 + File outputBackup = new File(file.getParentFile(), file.getName() + ".bak.gz"); + log.info(tlUI(Lang.SQLITE_VACUUM_BACKUP_COMPLETED)); + for (int i = 0; i < 10; i++) { + log.info(tlUI(Lang.SQLITE_VACUUM_IN_PROGRESS)); + } + try { + backupDatabase(file, outputBackup); + long fileSize = file.length(); + stmt.executeUpdate("VACUUM;"); + long newFileSize = file.length(); + log.info(tlUI(Lang.SQLITE_VACUUM_SUCCESS, MsgUtil.humanReadableByteCountBin(fileSize), MsgUtil.humanReadableByteCountBin(newFileSize))); + } catch (IOException e) { + log.warn(tlUI(Lang.SQLITE_VACUUM_BACKUP_FAILED), e); + } finally { + // 太好了,我们没有被强关 + outputBackup.delete(); + } + } + } + } + } catch (Exception e) { + log.warn(tlUI(Lang.UNABLE_SET_SQLITE_OPTIMIZED_PRAGMA), e); + } finally { + setLastMaintenanceTime(System.currentTimeMillis()); + } } catch (Exception e) { log.warn(tlUI(Lang.UNABLE_SET_SQLITE_OPTIMIZED_PRAGMA), e); } @@ -62,6 +126,13 @@ public void setupDatabase(File file) throws SQLException { // this.dataSource = new DataSourceConnectionSource( new HikariDataSource(config), new SqliteDatabaseType()); } + private void backupDatabase(File input, File output) throws IOException { + try (var filein = new FileInputStream(input); + var fileout = new FileOutputStream(output)) { + MiscUtil.gzip(filein, fileout); + } + } + public void close() { this.dataSource.closeQuietly(); } diff --git a/src/main/java/com/ghostchu/peerbanhelper/database/table/RuleSubInfoEntity.java b/src/main/java/com/ghostchu/peerbanhelper/database/table/RuleSubInfoEntity.java index 36dd948012..f7528b87e5 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/database/table/RuleSubInfoEntity.java +++ b/src/main/java/com/ghostchu/peerbanhelper/database/table/RuleSubInfoEntity.java @@ -10,7 +10,7 @@ @NoArgsConstructor @Data @DatabaseTable(tableName = "rule_sub_info") -public class RuleSubInfoEntity { +public final class RuleSubInfoEntity { @DatabaseField(id = true, index = true) private String ruleId; @DatabaseField diff --git a/src/main/java/com/ghostchu/peerbanhelper/database/table/RuleSubLogEntity.java b/src/main/java/com/ghostchu/peerbanhelper/database/table/RuleSubLogEntity.java index d78eb0758c..c2d3cf86d4 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/database/table/RuleSubLogEntity.java +++ b/src/main/java/com/ghostchu/peerbanhelper/database/table/RuleSubLogEntity.java @@ -12,7 +12,7 @@ @NoArgsConstructor @Data @DatabaseTable(tableName = "rule_sub_log") -public class RuleSubLogEntity { +public final class RuleSubLogEntity { @DatabaseField(generatedId = true, index = true) private Long id; @DatabaseField(index = true) diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/AbstractDownloader.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/AbstractDownloader.java index 2834ed8932..7470ba04cb 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/AbstractDownloader.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/AbstractDownloader.java @@ -27,6 +27,11 @@ public AbstractDownloader(String name, AlertManager alertManager) { @Override public DownloaderLoginResult login() { + if(isPaused()){ + lastStatus = DownloaderLastStatus.PAUSED; + statusMessage = new TranslationComponent(Lang.STATUS_TEXT_PAUSED); + return new DownloaderLoginResult(DownloaderLoginResult.Status.PAUSED, new TranslationComponent(Lang.DOWNLOADER_PAUSED)); + } if (nextLoginTry >= System.currentTimeMillis()) { alertManager.publishAlert(true, AlertLevel.WARN, @@ -60,6 +65,17 @@ public DownloaderLoginResult login() { } } + @Override + public synchronized void setPaused(boolean paused) { + if (paused) { + lastStatus = DownloaderLastStatus.PAUSED; + statusMessage = new TranslationComponent(Lang.STATUS_TEXT_PAUSED); + } else { + lastStatus = DownloaderLastStatus.UNKNOWN; + statusMessage = null; + } + } + @Override public void relaunchTorrentIfNeeded(Collection torrents) { diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/Downloader.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/Downloader.java index ee368fb1d1..cba264c328 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/Downloader.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/Downloader.java @@ -52,6 +52,20 @@ public interface Downloader extends AutoCloseable { */ DownloaderLoginResult login(); + /** + * Check if the downloader is in paused state + * + * @return true if paused, false otherwise + */ + boolean isPaused(); + + /** + * Set the paused state of the downloader + * + * @param paused true to pause, false to resume + */ + void setPaused(boolean paused); + /** * 一个执行调度任务的窗口,该方法总是在 banWave 中调用 */ diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/DownloaderLastStatus.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/DownloaderLastStatus.java index 9f0daf2da3..db10bcedd8 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/DownloaderLastStatus.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/DownloaderLastStatus.java @@ -2,6 +2,7 @@ public enum DownloaderLastStatus { HEALTHY, + PAUSED, NEED_TAKE_ACTION, ERROR, UNKNOWN diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/DownloaderLoginResult.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/DownloaderLoginResult.java index b96133704f..5ede3710c0 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/DownloaderLoginResult.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/DownloaderLoginResult.java @@ -19,10 +19,11 @@ public boolean success() { public enum Status { SUCCESS, + PAUSED, INCORRECT_CREDENTIAL, MISSING_COMPONENTS, NETWORK_ERROR, EXCEPTION, - REQUIRE_TAKE_ACTIONS + REQUIRE_TAKE_ACTIONS, } } diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/biglybt/BiglyBT.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/biglybt/BiglyBT.java index 6b4334956e..0a1c65c601 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/biglybt/BiglyBT.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/biglybt/BiglyBT.java @@ -137,6 +137,17 @@ public String getType() { return "BiglyBT"; } + @Override + public boolean isPaused() { + return config.isPaused(); + } + + @Override + public void setPaused(boolean paused) { + super.setPaused(paused); + config.setPaused(paused); + } + @Override public void setBanList(@NotNull Collection fullList, @Nullable Collection added, @Nullable Collection removed, boolean applyFullList) { if (removed != null && removed.isEmpty() && added != null && config.isIncrementBan() && !applyFullList) { @@ -245,7 +256,8 @@ public List getPeers(Torrent torrent) { peer.getStats().getTotalSent(), peer.getPercentDoneInThousandNotation() / 1000d, null, - supportedMessages + supportedMessages, + peer.getState() != 30 && peer.getState() != 40 )); } return peersList; @@ -301,6 +313,7 @@ public static class Config { private boolean incrementBan; private boolean verifySsl; private boolean ignorePrivate; + private boolean paused; public static Config readFromYaml(ConfigurationSection section) { Config config = new Config(); @@ -314,6 +327,7 @@ public static Config readFromYaml(ConfigurationSection section) { config.setHttpVersion(section.getString("http-version", "HTTP_1_1")); config.setVerifySsl(section.getBoolean("verify-ssl", true)); config.setIgnorePrivate(section.getBoolean("ignore-private", false)); + config.setPaused(section.getBoolean("paused", false)); return config; } @@ -326,6 +340,7 @@ public YamlConfiguration saveToYaml() { section.set("increment-ban", incrementBan); section.set("ignore-private", ignorePrivate); section.set("verify-ssl", verifySsl); + section.set("paused", paused); return section; } } diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/biglybt/network/ConnectorData.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/biglybt/network/ConnectorData.java index 7e21b95412..fbef8b64b8 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/biglybt/network/ConnectorData.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/biglybt/network/ConnectorData.java @@ -7,7 +7,7 @@ @Data @AllArgsConstructor @NoArgsConstructor -public class ConnectorData { +public final class ConnectorData { private String software; private String version; private String abbrev; diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/biglybt/network/wrapper/StatisticsRecord.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/biglybt/network/wrapper/StatisticsRecord.java index 1ea98c0679..65b54c36ce 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/biglybt/network/wrapper/StatisticsRecord.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/biglybt/network/wrapper/StatisticsRecord.java @@ -7,7 +7,7 @@ @AllArgsConstructor @Data @NoArgsConstructor -public class StatisticsRecord { +public final class StatisticsRecord { private Long overallDataBytesReceived; private Long overallDataBytesSent; private Long sessionUptimeSeconds; diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/BitComet.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/BitComet.java index 0c49aa0cef..b297f3578e 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/BitComet.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/BitComet.java @@ -191,6 +191,17 @@ public String getType() { return "BitComet"; } + @Override + public boolean isPaused() { + return config.isPaused(); + } + + @Override + public void setPaused(boolean paused) { + super.setPaused(paused); + config.setPaused(paused); + } + public boolean isLoggedIn() { try { queryNeedReConfigureIpFilter(); @@ -345,7 +356,8 @@ public List getPeers(Torrent torrent) { peer.getDlSize() != null ? peer.getDlSize() : -1, // 兼容 2.10 peer.getUpRate(), peer.getUpSize() != null ? peer.getUpSize() : -1, // 兼容 2.10 - peer.getPermillage() / 1000.0d, null, Collections.emptyList()) + peer.getPermillage() / 1000.0d, null, Collections.emptyList(), + peer.getDlRate() <= 0 && peer.getUpRate() <= 0) ).collect(Collectors.toList()); } catch (Exception e) { throw new IllegalStateException(e); @@ -460,6 +472,7 @@ public static class Config { private String httpVersion; private boolean verifySsl; private boolean ignorePrivate; + private boolean paused; public static Config readFromYaml(ConfigurationSection section) { Config config = new Config(); @@ -474,6 +487,7 @@ public static Config readFromYaml(ConfigurationSection section) { config.setHttpVersion(section.getString("http-version", "HTTP_1_1")); config.setVerifySsl(section.getBoolean("verify-ssl", true)); config.setIgnorePrivate(section.getBoolean("ignore-private", false)); + config.setPaused(section.getBoolean("paused", false)); return config; } @@ -487,6 +501,7 @@ public YamlConfiguration saveToYaml() { section.set("http-version", httpVersion); section.set("verify-ssl", verifySsl); section.set("ignore-private", ignorePrivate); + section.set("paused", paused); return section; } } diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCConfigSetResponse.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCConfigSetResponse.java index 6d43ff9662..1dda369cce 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCConfigSetResponse.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCConfigSetResponse.java @@ -6,7 +6,7 @@ @NoArgsConstructor @Data -public class BCConfigSetResponse { +public final class BCConfigSetResponse { @SerializedName("error_code") private String errorCode; diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCDeviceTokenResult.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCDeviceTokenResult.java index d3d2566d19..e5c5f18b9a 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCDeviceTokenResult.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCDeviceTokenResult.java @@ -6,7 +6,7 @@ @NoArgsConstructor @Data -public class BCDeviceTokenResult { +public final class BCDeviceTokenResult { @SerializedName("device_token") private String deviceToken; diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCIpFilterResponse.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCIpFilterResponse.java index bb551b735d..4a79c54946 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCIpFilterResponse.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCIpFilterResponse.java @@ -6,7 +6,7 @@ @NoArgsConstructor @Data -public class BCIpFilterResponse { +public final class BCIpFilterResponse { @SerializedName("ip_filter_config") private IpFilterConfigDTO ipFilterConfig; diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCLoginResponse.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCLoginResponse.java index feb462f8f2..7d6b6a79f7 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCLoginResponse.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCLoginResponse.java @@ -6,7 +6,7 @@ @NoArgsConstructor @Data -public class BCLoginResponse { +public final class BCLoginResponse { @SerializedName("error_code") private String errorCode; diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCTaskListResponse.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCTaskListResponse.java index 4fbf1dcbf8..ef012275d7 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCTaskListResponse.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCTaskListResponse.java @@ -8,7 +8,7 @@ @NoArgsConstructor @Data -public class BCTaskListResponse { +public final class BCTaskListResponse { @SerializedName("tasks") private List tasks; diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCTaskPeersResponse.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCTaskPeersResponse.java index 85bd1e2916..9153867d3e 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCTaskPeersResponse.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCTaskPeersResponse.java @@ -8,7 +8,7 @@ @NoArgsConstructor @Data -public class BCTaskPeersResponse { +public final class BCTaskPeersResponse { @SerializedName("error_code") private String errorCode; diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCTaskTorrentResponse.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCTaskTorrentResponse.java index 1ddca99ad6..f3539cad89 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCTaskTorrentResponse.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCTaskTorrentResponse.java @@ -4,11 +4,9 @@ import lombok.Data; import lombok.NoArgsConstructor; -import java.util.List; - @NoArgsConstructor @Data -public class BCTaskTorrentResponse { +public final class BCTaskTorrentResponse { @SerializedName("error_code") private String errorCode; diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/deluge/Deluge.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/deluge/Deluge.java index 6224500456..39749da9af 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/deluge/Deluge.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/deluge/Deluge.java @@ -84,6 +84,17 @@ public String getType() { return "Deluge"; } + @Override + public boolean isPaused() { + return config.isPaused(); + } + + @Override + public void setPaused(boolean paused) { + super.setPaused(paused); + config.setPaused(paused); + } + @Override public DownloaderLoginResult login0() { try { @@ -244,6 +255,7 @@ public static class Config { private String rpcUrl; private boolean incrementBan; private boolean ignorePrivate; + private boolean paused; public static Config readFromYaml(ConfigurationSection section) { Config config = new Config(); @@ -258,6 +270,7 @@ public static Config readFromYaml(ConfigurationSection section) { config.setVerifySsl(section.getBoolean("verify-ssl", true)); config.setIncrementBan(section.getBoolean("increment-ban", true)); config.setIgnorePrivate(section.getBoolean("ignore-private", false)); + config.setPaused(section.getBoolean("paused", false)); return config; } @@ -271,6 +284,7 @@ public YamlConfiguration saveToYaml() { section.set("increment-ban", incrementBan); section.set("verify-ssl", verifySsl); section.set("ignore-private", ignorePrivate); + section.set("paused", paused); return section; } } diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/deluge/DelugePeer.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/deluge/DelugePeer.java index 126e904590..070ed120d2 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/deluge/DelugePeer.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/deluge/DelugePeer.java @@ -28,6 +28,11 @@ public String getRawIp() { return peerAddress.getIp(); } + @Override + public boolean isHandshaking() { + return downloadSpeed <= 0 && uploadSpeed <= 0; + } + @Override public List getSupportedMessages() { return Collections.emptyList(); diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/QBittorrentConfig.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/QBittorrentConfig.java index c81f93c3e0..6db8b85c76 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/QBittorrentConfig.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/QBittorrentConfig.java @@ -26,6 +26,10 @@ public interface QBittorrentConfig { boolean isIgnorePrivate(); + boolean isPaused(); + + void setPaused(boolean paused); + void setType(String type); void setEndpoint(String endpoint); diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/QBittorrent.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/QBittorrent.java index 57ecec8eec..c915428806 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/QBittorrent.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/QBittorrent.java @@ -15,6 +15,17 @@ public QBittorrent(String name, QBittorrentConfig config, AlertManager alertMana super(name, config, alertManager); } + @Override + public boolean isPaused() { + return config.isPaused(); + } + + @Override + public void setPaused(boolean paused) { + super.setPaused(paused); + config.setPaused(paused); + } + public static QBittorrent loadFromConfig(String name, JsonObject section, AlertManager alertManager) { QBittorrentConfigImpl config = JsonUtil.getGson().fromJson(section.toString(), QBittorrentConfigImpl.class); return new QBittorrent(name, config, alertManager); diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/QBittorrentConfigImpl.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/QBittorrentConfigImpl.java index 727d3258dd..48f91dc61c 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/QBittorrentConfigImpl.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/QBittorrentConfigImpl.java @@ -22,6 +22,7 @@ public class QBittorrentConfigImpl implements QBittorrentConfig { private boolean useShadowBan; private boolean verifySsl; private boolean ignorePrivate; + private boolean paused; public static QBittorrentConfigImpl readFromYaml(ConfigurationSection section) { QBittorrentConfigImpl config = new QBittorrentConfigImpl(); @@ -41,6 +42,7 @@ public static QBittorrentConfigImpl readFromYaml(ConfigurationSection section) { config.setUseShadowBan(section.getBoolean("use-shadow-ban", false)); config.setVerifySsl(section.getBoolean("verify-ssl", true)); config.setIgnorePrivate(section.getBoolean("ignore-private", false)); + config.setPaused(section.getBoolean("paused", false)); return config; } @@ -58,6 +60,7 @@ public YamlConfiguration saveToYaml() { section.set("use-shadow-ban", useShadowBan); section.set("verify-ssl", verifySsl); section.set("ignore-private", ignorePrivate); + section.set("paused", paused); return section; } } diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/QBittorrentPeer.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/QBittorrentPeer.java index 74f6ddb319..5db6f85542 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/QBittorrentPeer.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/QBittorrentPeer.java @@ -104,6 +104,11 @@ public PeerFlag getFlags() { return new PeerFlag(flags); } + @Override + public boolean isHandshaking() { + return upSpeed <= 0 && dlSpeed <= 0; + } + @Override public List getSupportedMessages() { return Collections.emptyList(); diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/enhanced/QBittorrentEE.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/enhanced/QBittorrentEE.java index 3fe959f68b..7b7949fac0 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/enhanced/QBittorrentEE.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/enhanced/QBittorrentEE.java @@ -41,6 +41,19 @@ public QBittorrentEE(String name, QBittorrentEEConfigImpl config, AlertManager a } } + @Override + public boolean isPaused() { + return config.isPaused(); + } + + @Override + public synchronized void setPaused(boolean paused) { + super.setPaused(paused); + if (config != null) { + config.setPaused(paused); + } + } + public static QBittorrentEE loadFromConfig(String name, JsonObject section, AlertManager alertManager) { QBittorrentEEConfigImpl config = JsonUtil.getGson().fromJson(section.toString(), QBittorrentEEConfigImpl.class); return new QBittorrentEE(name, config, alertManager); diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/enhanced/QBittorrentEEConfigImpl.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/enhanced/QBittorrentEEConfigImpl.java index d4e5b6c1ab..c0d99f091b 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/enhanced/QBittorrentEEConfigImpl.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/enhanced/QBittorrentEEConfigImpl.java @@ -22,6 +22,7 @@ public class QBittorrentEEConfigImpl implements QBittorrentConfig { private boolean verifySsl; private boolean useShadowBan; private boolean ignorePrivate; + private boolean paused; public static QBittorrentEEConfigImpl readFromYaml(ConfigurationSection section) { QBittorrentEEConfigImpl config = new QBittorrentEEConfigImpl(); @@ -41,6 +42,7 @@ public static QBittorrentEEConfigImpl readFromYaml(ConfigurationSection section) config.setUseShadowBan(section.getBoolean("use-shadow-ban", false)); config.setVerifySsl(section.getBoolean("verify-ssl", true)); config.setIgnorePrivate(section.getBoolean("ignore-private", false)); + config.setPaused(section.getBoolean("paused", false)); return config; } @@ -58,6 +60,7 @@ public YamlConfiguration saveToYaml() { section.set("use-shadow-ban", useShadowBan); section.set("verify-ssl", verifySsl); section.set("ignore-private", ignorePrivate); + section.set("paused", paused); return section; } } diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/enhanced/QBittorrentEEPeer.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/enhanced/QBittorrentEEPeer.java index a85d571356..1f64712c23 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/enhanced/QBittorrentEEPeer.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/enhanced/QBittorrentEEPeer.java @@ -106,6 +106,11 @@ public PeerFlag getFlags() { return new PeerFlag(flags); } + @Override + public boolean isHandshaking() { + return dlSpeed <= 0 && upSpeed <= 0; + } + @Override public List getSupportedMessages() { return Collections.emptyList(); diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/transmission/TRPeer.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/transmission/TRPeer.java index ffcf3c295b..95751d5598 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/transmission/TRPeer.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/transmission/TRPeer.java @@ -66,6 +66,11 @@ public PeerFlag getFlags() { return new PeerFlag(backend.getFlagStr()); } + @Override + public boolean isHandshaking() { + return getDownloadSpeed() <= 0 && getUploadSpeed() <= 0; + } + @Override public List getSupportedMessages() { return Collections.emptyList(); diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/transmission/Transmission.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/transmission/Transmission.java index 311945622b..c673ef62b2 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/transmission/Transmission.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/transmission/Transmission.java @@ -96,6 +96,17 @@ public String getType() { return "Transmission"; } + @Override + public boolean isPaused() { + return config.isPaused(); + } + + @Override + public void setPaused(boolean paused) { + super.setPaused(paused); + config.setPaused(paused); + } + @SneakyThrows(InterruptedException.class) @Override public DownloaderLoginResult login0() { @@ -280,6 +291,7 @@ public static class Config { private boolean verifySsl; private String rpcUrl; private boolean ignorePrivate; + private boolean paused; public static Transmission.Config readFromYaml(ConfigurationSection section) { Transmission.Config config = new Transmission.Config(); @@ -294,6 +306,7 @@ public static Transmission.Config readFromYaml(ConfigurationSection section) { config.setHttpVersion(section.getString("http-version", "HTTP_1_1")); config.setVerifySsl(section.getBoolean("verify-ssl", true)); config.setIgnorePrivate(section.getBoolean("ignore-private", false)); + config.setPaused(section.getBoolean("paused", false)); return config; } @@ -307,6 +320,7 @@ public YamlConfiguration saveToYaml() { section.set("http-version", httpVersion); section.set("verify-ssl", verifySsl); section.set("ignore-private", ignorePrivate); + section.set("paused", paused); return section; } } diff --git a/src/main/java/com/ghostchu/peerbanhelper/exchange/ExchangeMap.java b/src/main/java/com/ghostchu/peerbanhelper/exchange/ExchangeMap.java index 288546d238..01ccd72df5 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/exchange/ExchangeMap.java +++ b/src/main/java/com/ghostchu/peerbanhelper/exchange/ExchangeMap.java @@ -10,18 +10,20 @@ import java.util.TreeSet; public class ExchangeMap { - public static final Set GUI_DISPLAY_FLAGS = Collections.synchronizedSet(new TreeSet<>()); + public static volatile Set GUI_DISPLAY_FLAGS = Collections.synchronizedSet(new TreeSet<>()); @Data @AllArgsConstructor @NoArgsConstructor public static class DisplayFlag implements Comparable { + private String id; private int priority; private String content; @Override public int compareTo(@NotNull ExchangeMap.DisplayFlag o) { - return Integer.compare(priority, o.priority); + int priorityCompare = Integer.compare(priority, o.priority); + return priorityCompare != 0 ? priorityCompare : id.compareTo(o.id); } } } diff --git a/src/main/java/com/ghostchu/peerbanhelper/ipdb/IPDB.java b/src/main/java/com/ghostchu/peerbanhelper/ipdb/IPDB.java index 20cb115fff..ac7a24df54 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/ipdb/IPDB.java +++ b/src/main/java/com/ghostchu/peerbanhelper/ipdb/IPDB.java @@ -270,7 +270,7 @@ private void loadMMDB() throws IOException { private void updateMMDB(String databaseName, File target) throws IOException { log.info(tlUI(Lang.IPDB_UPDATING, databaseName)); - IPDBDownloadSource mirror1 = new IPDBDownloadSource("https://github.com/P3TERX/GeoLite.mmdb/releases/latest/download/", databaseName); + IPDBDownloadSource mirror1 = new IPDBDownloadSource("https://github.com/PBH-BTN/GeoLite.mmdb/releases/latest/download/", databaseName, true); //IPDBDownloadSource mirror2 = new IPDBDownloadSource("https://ghp.ci/https://github.com/P3TERX/GeoLite.mmdb/releases/latest/download/", databaseName); IPDBDownloadSource mirror3 = new IPDBDownloadSource("https://pbh-static.paulzzh.com/ipdb/", databaseName, true); IPDBDownloadSource mirror4 = new IPDBDownloadSource("https://pbh-static.ghostchu.com/ipdb/", databaseName, true); diff --git a/src/main/java/com/ghostchu/peerbanhelper/lab/Experiments.java b/src/main/java/com/ghostchu/peerbanhelper/lab/Experiments.java index 2734edd79c..470b353874 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/lab/Experiments.java +++ b/src/main/java/com/ghostchu/peerbanhelper/lab/Experiments.java @@ -6,7 +6,9 @@ import java.util.List; public enum Experiments { - IPFS(new Experiment("ipfs", List.of(0), new TranslationComponent(Lang.LAB_EXPERIMENT_IPFS_TITLE), new TranslationComponent(Lang.LAB_EXPERIMENT_IPFS_DESCRIPTION))); + IPFS(new Experiment("ipfs", List.of(0), new TranslationComponent(Lang.LAB_EXPERIMENT_IPFS_TITLE), new TranslationComponent(Lang.LAB_EXPERIMENT_IPFS_DESCRIPTION))), + DNSJAVA(new Experiment("dnsjava", List.of(0, 1, 2), new TranslationComponent(Lang.LAB_EXPERIMENT_DNSJAVA_TITLE), new TranslationComponent(Lang.LAB_EXPERIMENT_DNSJAVA_DESCRIPTION))), + SQLITE_VACUUM(new Experiment("sqlite_vacuum", List.of(0, 1, 3, 5), new TranslationComponent(Lang.LAB_EXPERIMENT_SQLITE_VACUUM_TITLE), new TranslationComponent(Lang.LAB_EXPERIMENT_SQLITE_VACUUM_DESCRIPTION))); private final Experiment experiment; @@ -17,4 +19,4 @@ public enum Experiments { public Experiment getExperiment() { return experiment; } -} + } diff --git a/src/main/java/com/ghostchu/peerbanhelper/lab/Laboratory.java b/src/main/java/com/ghostchu/peerbanhelper/lab/Laboratory.java index 7ad93d2991..3e822995f6 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/lab/Laboratory.java +++ b/src/main/java/com/ghostchu/peerbanhelper/lab/Laboratory.java @@ -37,7 +37,7 @@ public Laboratory() throws IOException { } public boolean isExperimentActivated(Experiment experiment) { - if(!isEnabled()){ + if (!isEnabled()) { return false; } var value = labConfig.getString(experiment.getId()); @@ -57,16 +57,16 @@ public boolean isExperimentActivated(Experiment experiment) { } } - public void setExperimentActivated(String id, Boolean activated) throws IllegalArgumentException{ + public void setExperimentActivated(String id, Boolean activated) throws IllegalArgumentException { for (Experiments value : Experiments.values()) { - if(!value.getExperiment().getId().equals(id)){ + if (!value.getExperiment().getId().equals(id)) { continue; } labConfig.set(value.getExperiment().getId(), activated == null ? "default" : activated); saveLabConfig(); return; } - throw new IllegalArgumentException("Invalid experiment id: "+id+", it's not exists in Experiments registry"); + throw new IllegalArgumentException("Invalid experiment id: " + id + ", it's not exists in Experiments registry"); } private void saveLabConfig() { diff --git a/src/main/java/com/ghostchu/peerbanhelper/metric/impl/persist/PersistMetrics.java b/src/main/java/com/ghostchu/peerbanhelper/metric/impl/persist/PersistMetrics.java index 45bf56e308..dc011a4e19 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/metric/impl/persist/PersistMetrics.java +++ b/src/main/java/com/ghostchu/peerbanhelper/metric/impl/persist/PersistMetrics.java @@ -114,7 +114,7 @@ public void recordPeerBan(PeerAddress address, BanMetadata metadata) { null, new Timestamp(metadata.getBanAt()), new Timestamp(metadata.getUnbanAt()), - address.getIp(), + address.getAddress().toNormalizedString(), address.getPort(), metadata.getPeer().getId(), metadata.getPeer().getClientName(), diff --git a/src/main/java/com/ghostchu/peerbanhelper/module/AbstractRuleFeatureModule.java b/src/main/java/com/ghostchu/peerbanhelper/module/AbstractRuleFeatureModule.java index bfa1fcd619..fccc4f9510 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/module/AbstractRuleFeatureModule.java +++ b/src/main/java/com/ghostchu/peerbanhelper/module/AbstractRuleFeatureModule.java @@ -23,7 +23,7 @@ public abstract class AbstractRuleFeatureModule extends AbstractFeatureModule im public boolean isHandShaking(Peer peer) { // 跳过此 Peer,速度都是0,可能是没有完成握手 - return peer.getDownloadSpeed() <= 0 && peer.getUploadSpeed() <= 0; + return peer.isHandshaking(); } /** diff --git a/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/BtnNetworkOnline.java b/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/BtnNetworkOnline.java index 8e412f2a79..309b3efc32 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/BtnNetworkOnline.java +++ b/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/BtnNetworkOnline.java @@ -21,10 +21,7 @@ import com.ghostchu.peerbanhelper.util.NullUtil; import com.ghostchu.peerbanhelper.util.SharedObject; import com.ghostchu.peerbanhelper.util.context.IgnoreScan; -import com.ghostchu.peerbanhelper.util.rule.MatchResult; -import com.ghostchu.peerbanhelper.util.rule.Rule; -import com.ghostchu.peerbanhelper.util.rule.RuleMatchResult; -import com.ghostchu.peerbanhelper.util.rule.RuleParser; +import com.ghostchu.peerbanhelper.util.rule.*; import com.ghostchu.peerbanhelper.web.JavalinWebContainer; import com.ghostchu.peerbanhelper.web.Role; import com.ghostchu.peerbanhelper.web.wrapper.StdResp; @@ -55,12 +52,10 @@ @IgnoreScan public class BtnNetworkOnline extends AbstractRuleFeatureModule implements Reloadable { private final CheckResult BTN_MANAGER_NOT_INITIALIZED = new CheckResult(getClass(), PeerAction.NO_ACTION, 0, new TranslationComponent(Lang.GENERAL_NA), new TranslationComponent("BtnManager not initialized")); - @Autowired(required = false) - private BtnNetwork manager; private long banDuration; @Autowired private JavalinWebContainer javalinWebContainer; - @Autowired + @Autowired(required = false) private BtnNetwork btnNetwork; @Autowired private ScriptEngine scriptEngine; @@ -105,6 +100,7 @@ private void status(Context context) { } info.put("configSuccess", btnNetwork.getConfigSuccess()); + info.put("configResult", btnNetwork.getConfigResult() == null ? null : tl(locale(context), btnNetwork.getConfigResult())); var abilities = new ArrayList<>(); for (Map.Entry, BtnAbility> entry : btnNetwork.getAbilities().entrySet()) { Map abilityStatus = new HashMap<>(); @@ -168,7 +164,7 @@ public boolean isThreadSafe() { @Override public @NotNull CheckResult shouldBanPeer(@NotNull Torrent torrent, @NotNull Peer peer, @NotNull Downloader downloader, @NotNull ExecutorService ruleExecuteExecutor) { - if (manager == null) { + if (btnNetwork == null) { return BTN_MANAGER_NOT_INITIALIZED; } // TODO: 需要重构 @@ -186,7 +182,7 @@ public boolean isThreadSafe() { } private @NotNull CheckResult checkScript(Torrent torrent, Peer peer, Downloader downloader, ExecutorService ruleExecuteExecutor) { - var abilityObject = manager.getAbilities().get(BtnAbilityRules.class); + var abilityObject = btnNetwork.getAbilities().get(BtnAbilityRules.class); if (abilityObject == null) { return pass(); } @@ -262,7 +258,7 @@ public boolean isThreadSafe() { } private @NotNull CheckResult checkShouldSkip(Torrent torrent, Peer peer, Downloader downloader, ExecutorService ruleExecuteExecutor) { - var abilityObject = manager.getAbilities().get(BtnAbilityException.class); + var abilityObject = btnNetwork.getAbilities().get(BtnAbilityException.class); if (abilityObject == null) { return pass(); } @@ -296,7 +292,7 @@ public boolean isThreadSafe() { } private @NotNull CheckResult checkShouldBan(@NotNull Torrent torrent, @NotNull Peer peer, @NotNull Downloader downloader, @NotNull ExecutorService ruleExecuteExecutor) { - var abilityObject = manager.getAbilities().get(BtnAbilityRules.class); + var abilityObject = btnNetwork.getAbilities().get(BtnAbilityRules.class); if (abilityObject == null) { return pass(); } @@ -340,7 +336,9 @@ private CheckResult checkPortRule(BtnRuleParsed rule, Torrent torrent, Peer peer for (String category : rule.getPortRules().keySet()) { RuleMatchResult matchResult = RuleParser.matchRule(rule.getPortRules().get(category), Integer.toString(peer.getPeerAddress().getPort())); if (matchResult.hit()) { - return new CheckResult(getClass(), PeerAction.BAN, banDuration, new TranslationComponent(Lang.BTN_BTN_RULE, category, matchResult.rule().matcherIdentifier()), new TranslationComponent(Lang.MODULE_BTN_BAN, "Port", category, matchResult.rule().toString())); + return new CheckResult(getClass(), PeerAction.BAN, banDuration, + new TranslationComponent(Lang.BTN_BTN_RULE, category, matchResult.rule().matcherName()), + new TranslationComponent(Lang.MODULE_BTN_BAN, "Port", category, matchResult.rule().matcherName())); } } return null; @@ -350,7 +348,9 @@ private CheckResult checkPortRuleException(BtnExceptionRuleParsed rule, Torrent for (String category : rule.getPortRules().keySet()) { RuleMatchResult matchResult = RuleParser.matchRule(rule.getPortRules().get(category), Integer.toString(peer.getPeerAddress().getPort())); if (matchResult.hit()) { - return new CheckResult(getClass(), PeerAction.SKIP, banDuration, new TranslationComponent(Lang.BTN_BTN_RULE, category, matchResult.rule().matcherIdentifier()), new TranslationComponent(Lang.MODULE_BTN_BAN, "Port", category, matchResult.rule().toString())); + return new CheckResult(getClass(), PeerAction.SKIP, banDuration, + new TranslationComponent(Lang.BTN_BTN_RULE, category, matchResult.rule().matcherName()), + new TranslationComponent(Lang.MODULE_BTN_BAN, "Port", category, matchResult.rule().matcherName())); } } return null; @@ -362,7 +362,9 @@ private CheckResult checkClientNameRule(BtnRuleParsed rule, Torrent torrent, Pee List rules = rule.getClientNameRules().get(category); RuleMatchResult matchResult = RuleParser.matchRule(rules, peer.getClientName()); if (matchResult.hit()) { - return new CheckResult(getClass(), PeerAction.BAN, banDuration, new TranslationComponent(Lang.BTN_BTN_RULE, category, matchResult.rule().matcherIdentifier()), new TranslationComponent(Lang.MODULE_BTN_BAN, "ClientName", category, matchResult.rule().toString())); + return new CheckResult(getClass(), PeerAction.BAN, banDuration, + new TranslationComponent(Lang.BTN_BTN_RULE, category, matchResult.rule().matcherName()), + new TranslationComponent(Lang.MODULE_BTN_BAN, "ClientName", category, matchResult.rule().matcherName())); } } return null; @@ -374,7 +376,9 @@ private CheckResult checkClientNameRuleException(BtnExceptionRuleParsed rule, To List rules = rule.getClientNameRules().get(category); RuleMatchResult matchResult = RuleParser.matchRule(rules, peer.getClientName()); if (matchResult.hit()) { - return new CheckResult(getClass(), PeerAction.SKIP, banDuration, new TranslationComponent(Lang.BTN_BTN_RULE, category, matchResult.rule().matcherIdentifier()), new TranslationComponent(Lang.MODULE_BTN_BAN, "ClientName", category, matchResult.rule().toString())); + return new CheckResult(getClass(), PeerAction.SKIP, banDuration, + new TranslationComponent(Lang.BTN_BTN_RULE, category, matchResult.rule().matcherName()), + new TranslationComponent(Lang.MODULE_BTN_BAN, "ClientName", category, matchResult.rule().matcherName())); } } return null; @@ -386,7 +390,9 @@ private CheckResult checkPeerIdRule(BtnRuleParsed rule, Torrent torrent, Peer pe List rules = rule.getPeerIdRules().get(category); RuleMatchResult matchResult = RuleParser.matchRule(rules, peer.getPeerId()); if (matchResult.hit()) { - return new CheckResult(getClass(), PeerAction.BAN, banDuration, new TranslationComponent(Lang.BTN_BTN_RULE, category, matchResult.rule().matcherIdentifier()), new TranslationComponent(Lang.MODULE_BTN_BAN, "PeerId", category, matchResult.rule().toString())); + return new CheckResult(getClass(), PeerAction.BAN, banDuration, + new TranslationComponent(Lang.BTN_BTN_RULE, category, matchResult.rule().matcherName()), + new TranslationComponent(Lang.MODULE_BTN_BAN, "PeerId", category, matchResult.rule().matcherName())); } } return null; @@ -398,7 +404,9 @@ private CheckResult checkPeerIdRuleException(BtnExceptionRuleParsed rule, Torren List rules = rule.getPeerIdRules().get(category); RuleMatchResult matchResult = RuleParser.matchRule(rules, peer.getPeerId()); if (matchResult.hit()) { - return new CheckResult(getClass(), PeerAction.SKIP, banDuration, new TranslationComponent(Lang.BTN_BTN_RULE, category, matchResult.rule().matcherIdentifier()), new TranslationComponent(Lang.MODULE_BTN_BAN, "PeerId", category, matchResult.rule().toString())); + return new CheckResult(getClass(), PeerAction.SKIP, banDuration, + new TranslationComponent(Lang.BTN_BTN_RULE, category, matchResult.rule().matcherName()), + new TranslationComponent(Lang.MODULE_BTN_BAN, "PeerId", category, matchResult.rule().matcherName())); } } return null; @@ -414,8 +422,10 @@ private CheckResult checkIpRule(BtnRuleParsed rule, @NotNull Torrent torrent, @N for (String category : rule.getIpRules().keySet()) { var ipMatcher = rule.getIpRules().get(category); MatchResult matchResult = ipMatcher.match(pa.toString()); - if (matchResult == MatchResult.TRUE) { - return new CheckResult(getClass(), PeerAction.BAN, banDuration, new TranslationComponent(Lang.BTN_BTN_RULE, category, category), new TranslationComponent(Lang.MODULE_BTN_BAN, "IP", category, pa.toString())); + if (matchResult.result() == MatchResultEnum.TRUE) { + return new CheckResult(getClass(), PeerAction.BAN, banDuration, + new TranslationComponent(Lang.BTN_BTN_RULE, category, category), + new TranslationComponent(Lang.MODULE_BTN_BAN, "IP", category, pa.toString())); } } return null; @@ -431,7 +441,9 @@ private CheckResult checkIpRuleException(BtnExceptionRuleParsed rule, @NotNull T for (String category : rule.getIpRules().keySet()) { RuleMatchResult matchResult = RuleParser.matchRule(rule.getIpRules().get(category), pa.toString()); if (matchResult.hit()) { - return new CheckResult(getClass(), PeerAction.SKIP, banDuration, new TranslationComponent(Lang.BTN_BTN_RULE, category, matchResult.rule().matcherIdentifier()), new TranslationComponent(Lang.MODULE_BTN_BAN, "IP", category, pa.toString())); + return new CheckResult(getClass(), PeerAction.SKIP, banDuration, + new TranslationComponent(Lang.BTN_BTN_RULE, category, matchResult.rule().matcherIdentifier()), + new TranslationComponent(Lang.MODULE_BTN_BAN, "IP", category, pa.toString())); } } return null; diff --git a/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/ClientNameBlacklist.java b/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/ClientNameBlacklist.java index 0250bd23d5..7345791c55 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/ClientNameBlacklist.java +++ b/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/ClientNameBlacklist.java @@ -20,6 +20,7 @@ import com.ghostchu.simplereloadlib.Reloadable; import io.javalin.http.Context; import lombok.Getter; +import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -28,6 +29,7 @@ import java.util.Map; import java.util.concurrent.ExecutorService; +@Slf4j @Getter @Component @IgnoreScan @@ -95,7 +97,10 @@ private void reloadConfig() { //return getCache().readCache(this, peer.getClientName(), () -> { RuleMatchResult matchResult = RuleParser.matchRule(bannedPeers, peer.getClientName()); if (matchResult.hit()) { - return new CheckResult(getClass(), PeerAction.BAN, banDuration, new TranslationComponent(matchResult.rule().toString()), new TranslationComponent(Lang.MODULE_CNB_MATCH_CLIENT_NAME, String.valueOf(matchResult.rule()))); + return new CheckResult(getClass(), PeerAction.BAN, banDuration, + matchResult.rule().matcherName(), + new TranslationComponent(Lang.MODULE_CNB_MATCH_CLIENT_NAME, + matchResult.comment())); } return pass(); //}, true); diff --git a/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/IPBlackRuleList.java b/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/IPBlackRuleList.java index 550c73c492..ed9e6ff2ac 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/IPBlackRuleList.java +++ b/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/IPBlackRuleList.java @@ -21,6 +21,7 @@ import com.ghostchu.peerbanhelper.util.paging.Page; import com.ghostchu.peerbanhelper.util.paging.Pageable; import com.ghostchu.peerbanhelper.util.rule.MatchResult; +import com.ghostchu.peerbanhelper.util.rule.MatchResultEnum; import com.ghostchu.peerbanhelper.util.rule.ModuleMatchCache; import com.ghostchu.peerbanhelper.util.rule.matcher.IPMatcher; import com.ghostchu.peerbanhelper.web.wrapper.StdResp; @@ -32,13 +33,15 @@ import com.google.common.io.Files; import com.j256.ormlite.stmt.SelectArg; import inet.ipaddr.IPAddress; -import inet.ipaddr.format.util.DualIPv4v6Tries; +import inet.ipaddr.format.util.DualIPv4v6AssociativeTries; import io.ipfs.cid.Cid; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; import org.bspfsystems.yamlconfiguration.configuration.ConfigurationSection; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.springframework.stereotype.Component; import java.io.File; @@ -49,9 +52,9 @@ import java.nio.charset.StandardCharsets; import java.sql.SQLException; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Optional; +import java.util.StringJoiner; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; @@ -122,28 +125,28 @@ public ReloadResult reloadModule() throws Exception { @Override public @NotNull CheckResult shouldBanPeer(@NotNull Torrent torrent, @NotNull Peer peer, @NotNull Downloader downloader, @NotNull ExecutorService ruleExecuteExecutor) { return getCache().readCacheButWritePassOnly(this, peer.getPeerAddress().getIp(), () -> { - long t1 = System.currentTimeMillis(); String ip = peer.getPeerAddress().getIp(); List results = new ArrayList<>(); ipBanMatchers.forEach(rule -> results.add(new IPBanResult(rule.getRuleName(), rule.match(ip)))); - AtomicReference matchRule = new AtomicReference<>(); - boolean mr = results.stream().anyMatch(ipBanResult -> { + for (IPBanResult ipBanResult : results) { try { - if (ipBanResult == null) return false; - boolean match = ipBanResult.matchResult() == MatchResult.TRUE; + if (ipBanResult == null) return pass(); + boolean match = ipBanResult.matchResult().result() == MatchResultEnum.TRUE; if (match) { - matchRule.set(ipBanResult); + return new CheckResult(getClass(), + PeerAction.BAN, + banDuration, + new TranslationComponent(ipBanResult.ruleName()), + new TranslationComponent(Lang.MODULE_IBL_MATCH_IP_RULE, + ipBanResult.ruleName(), + ip, + Optional.ofNullable(ipBanResult.matchResult().comment()).orElse(new TranslationComponent(Lang.MODULE_IBL_COMMENT_UNKNOWN)) + )); } - return match; } catch (Exception e) { log.error(tlUI(Lang.IP_BAN_RULE_MATCH_ERROR), e); - return false; + return pass(); } - }); - long t2 = System.currentTimeMillis(); - log.debug(tlUI(Lang.IP_BAN_RULE_MATCH_TIME, t2 - t1)); - if (mr) { - return new CheckResult(getClass(), PeerAction.BAN, banDuration, new TranslationComponent(matchRule.get().ruleName()), new TranslationComponent(Lang.MODULE_IBL_MATCH_IP_RULE, matchRule.get().ruleName(), ip)); } return pass(); }, true); @@ -203,7 +206,7 @@ public StdResp updateRule(String locale, @NotNull ConfigurationSection rule, IPB File dir = new File(Main.getDataDirectory(), "/sub"); dir.mkdirs(); File ruleFile = new File(dir, ruleFileName); - DualIPv4v6Tries ipAddresses = new DualIPv4v6Tries(); + DualIPv4v6AssociativeTries ipAddresses = new DualIPv4v6AssociativeTries<>(); getResource(url) .whenComplete((dataUpdateResult, throwable) -> { if (throwable != null) { @@ -300,15 +303,15 @@ private CompletableFuture getResource(String url) { } // IPNS if (uri.getScheme().equalsIgnoreCase("ipns")) { - var ipnsCid = uri.getHost(); + var ipnsCid = uri.getHost(); var ipfs = decentralizedManager.getIpfs(); if (ipfs == null) { throw new IllegalStateException(tlUI(Lang.MODULE_IBL_UPDATE_IPFS_NOT_AVAILABLE)); } try { - var cid = ipfs.name.resolve(Cid.decode(ipnsCid), true); - var data = ipfs.cat(Cid.decode(StringUtils.substringAfter(cid,"/ipfs/"))); - return new DataUpdateResult(200, "Data get from IPFS via IPNS", data); + var cid = ipfs.name.resolve(Cid.decode(ipnsCid), true); + var data = ipfs.cat(Cid.decode(StringUtils.substringAfter(cid, "/ipfs/"))); + return new DataUpdateResult(200, "Data get from IPFS via IPNS", data); } catch (IOException e) { throw new RuntimeException(e); } @@ -339,22 +342,29 @@ private CompletableFuture getResource(String url) { * @param ips ip列表 * @return 加载的行数 */ - private int fileToIPList(File ruleFile, DualIPv4v6Tries ips) throws IOException { + private int fileToIPList(File ruleFile, DualIPv4v6AssociativeTries ips) throws IOException { AtomicInteger count = new AtomicInteger(); - Files.readLines(ruleFile, StandardCharsets.UTF_8).stream().filter(s -> !s.isBlank()).forEach(ele -> { + StringJoiner sj = new StringJoiner("\n"); + var lines = Files.readLines(ruleFile, StandardCharsets.UTF_8); + for (String ele : lines) { + if (ele.isBlank()) continue; if (ele.startsWith("#")) { - return; // 注释 + // add into sj but without hashtag prefix + sj.add(ele.substring(1)); + continue; } try { - var parsedIp = parseRuleLine(ele); + var parsedIp = parseRuleLine(ele, sj.toString()); if (parsedIp != null) { count.getAndIncrement(); - ips.add(parsedIp); + ips.put(parsedIp.getLeft(), parsedIp.getRight()); } } catch (Exception e) { log.error("Unable parse rule: {}", ele, e); + } finally { + sj = new StringJoiner("\n"); } - }); + } return count.get(); } @@ -365,28 +375,32 @@ private int fileToIPList(File ruleFile, DualIPv4v6Tries ips) throws IOException * @param ips ip列表 * @return 加载的行数 */ - private int stringToIPList(String data, DualIPv4v6Tries ips) throws IOException { + private int stringToIPList(String data, DualIPv4v6AssociativeTries ips) throws IOException { AtomicInteger count = new AtomicInteger(); - Arrays.stream(data.split("\n")).filter(s -> !s.isBlank()).forEach(ele -> { + StringJoiner sj = new StringJoiner("\n"); + for (String ele : data.split("\n")) { + if (ele.isBlank()) continue; if (ele.startsWith("#")) { - return; // 注释 + // add into sj but without hashtag prefix + sj.add(ele.substring(1)); + continue; } try { - var parsedIp = parseRuleLine(ele); + var parsedIp = parseRuleLine(ele, sj.toString()); if (parsedIp != null) { count.getAndIncrement(); - ips.add(parsedIp); + ips.put(parsedIp.getLeft(), parsedIp.getRight()); } } catch (Exception e) { log.error("Unable parse rule: {}", ele, e); + } finally { + sj = new StringJoiner("\n"); } - }); + } return count.get(); } - private IPAddress parseRuleLine(String ele) { - // 注释? - if (ele.startsWith("#")) return null; + private Pair parseRuleLine(String ele, String preReadComment) { // 检查是否是 DAT/eMule 格式 // 016.000.000.000 , 016.255.255.255 , 200 , Yet another organization // 032.000.000.000 , 032.255.255.255 , 200 , And another @@ -398,11 +412,24 @@ private IPAddress parseRuleLine(String ele) { IPAddress start = IPAddressUtil.getIPAddress(spilted[0]); IPAddress end = IPAddressUtil.getIPAddress(spilted[1]); int level = Integer.parseInt(spilted[2]); + String comment = spilted.length > 3 ? spilted[3] : preReadComment; if (level >= 128) return null; if (start == null || end == null) return null; - return start.spanWithRange(end).coverWithPrefixBlock(); + return Pair.of(start.spanWithRange(end).coverWithPrefixBlock(), comment); + } else { + // ip #end-line-comment + String ip; + if (ele.contains("#")) { + ip = ele.substring(0, ele.indexOf("#")); + String comment = null; + if (ele.contains("#")) { + comment = ele.substring(ele.indexOf("#") + 1); + } + return Pair.of(IPAddressUtil.getIPAddress(ip), Optional.ofNullable(comment).orElse(preReadComment)); + } else { + return Pair.of(IPAddressUtil.getIPAddress(ele), preReadComment); + } } - return IPAddressUtil.getIPAddress(ele); } /** diff --git a/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/PTRBlacklist.java b/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/PTRBlacklist.java new file mode 100644 index 0000000000..68a278fef9 --- /dev/null +++ b/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/PTRBlacklist.java @@ -0,0 +1,125 @@ +package com.ghostchu.peerbanhelper.module.impl.rule; + +import com.ghostchu.peerbanhelper.Main; +import com.ghostchu.peerbanhelper.downloader.Downloader; +import com.ghostchu.peerbanhelper.lab.Experiments; +import com.ghostchu.peerbanhelper.lab.Laboratory; +import com.ghostchu.peerbanhelper.module.AbstractRuleFeatureModule; +import com.ghostchu.peerbanhelper.module.CheckResult; +import com.ghostchu.peerbanhelper.module.PeerAction; +import com.ghostchu.peerbanhelper.peer.Peer; +import com.ghostchu.peerbanhelper.text.Lang; +import com.ghostchu.peerbanhelper.text.TranslationComponent; +import com.ghostchu.peerbanhelper.torrent.Torrent; +import com.ghostchu.peerbanhelper.util.context.IgnoreScan; +import com.ghostchu.peerbanhelper.util.dns.DNSLookup; +import com.ghostchu.peerbanhelper.util.rule.Rule; +import com.ghostchu.peerbanhelper.util.rule.RuleMatchResult; +import com.ghostchu.peerbanhelper.util.rule.RuleParser; +import com.ghostchu.peerbanhelper.web.JavalinWebContainer; +import com.ghostchu.peerbanhelper.web.Role; +import com.ghostchu.peerbanhelper.web.wrapper.StdResp; +import com.ghostchu.simplereloadlib.ReloadResult; +import com.ghostchu.simplereloadlib.Reloadable; +import io.javalin.http.Context; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ExecutorService; + +@Component +@IgnoreScan +public class PTRBlacklist extends AbstractRuleFeatureModule implements Reloadable { + private List ptrRules; + @Autowired + private JavalinWebContainer webContainer; + @Autowired + private DNSLookup dnsLookup; + private long banDuration; + @Autowired + private Laboratory laboratory; + + @Override + public @NotNull String getName() { + return "PTR Blacklist"; + } + + @Override + public @NotNull String getConfigName() { + return "ptr-blacklist"; + } + + + @Override + public boolean isConfigurable() { + return true; + } + + + @Override + public void onEnable() { + reloadConfig(); + webContainer.javalin() + .get("/api/modules/" + getConfigName(), this::handleWebAPI, Role.USER_READ); + Main.getReloadManager().register(this); + } + + @Override + public boolean isThreadSafe() { + return true; + } + + private void handleWebAPI(Context ctx) { + String locale = locale(ctx); + ctx.json(new StdResp(true, null, Map.of("ptr-rules", ptrRules.stream().map(r -> r.toPrintableText(locale)).toList()))); + } + + @Override + public void onDisable() { + Main.getReloadManager().unregister(this); + } + + @Override + public ReloadResult reloadModule() throws Exception { + reloadConfig(); + return Reloadable.super.reloadModule(); + } + + public void reloadConfig() { + this.banDuration = getConfig().getLong("ban-duration", 0); + this.ptrRules = RuleParser.parse(getConfig().getStringList("ptr-rules")); + getCache().invalidateAll(); + } + + @Override + public @NotNull CheckResult shouldBanPeer(@NotNull Torrent torrent, @NotNull Peer peer, @NotNull Downloader downloader, @NotNull ExecutorService ruleExecuteExecutor) { + var reverseDnsLookupString = peer.getPeerAddress().getAddress().toReverseDNSLookupString(); + return getCache().readCache(this, reverseDnsLookupString, () -> { + Optional ptr; + if (laboratory.isExperimentActivated(Experiments.DNSJAVA.getExperiment())) { + ptr = dnsLookup.ptr(reverseDnsLookupString).join(); + } else { + try { + ptr = Optional.ofNullable(InetAddress.getByName(peer.getPeerAddress().getIp()).getHostName()); + } catch (UnknownHostException e) { + ptr = Optional.empty(); + } + } + if (ptr.isPresent()) { + RuleMatchResult matchResult = RuleParser.matchRule(ptrRules, ptr.get()); + if (matchResult.hit()) { + return new CheckResult(getClass(), PeerAction.BAN, banDuration, matchResult.rule().matcherName(), + new TranslationComponent(Lang.MODULE_PTR_MATCH_PTR_RULE, matchResult.rule().matcherName())); + } + } + return pass(); + }, true); + } + +} diff --git a/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/PeerIdBlacklist.java b/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/PeerIdBlacklist.java index c1860b7399..03a309420a 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/PeerIdBlacklist.java +++ b/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/PeerIdBlacklist.java @@ -96,7 +96,7 @@ public void reloadConfig() { //return getCache().readCache(this, peer.getPeerId(), () -> { RuleMatchResult matchResult = RuleParser.matchRule(bannedPeers, peer.getPeerId()); if (matchResult.hit()) { - return new CheckResult(getClass(), PeerAction.BAN, banDuration, new TranslationComponent(matchResult.rule().toString()), new TranslationComponent(Lang.MODULE_PID_MATCH_PEER_ID, matchResult.rule().toString())); + return new CheckResult(getClass(), PeerAction.BAN, banDuration, matchResult.rule().matcherName(), new TranslationComponent(Lang.MODULE_CNB_MATCH_CLIENT_NAME, matchResult.comment())); } return pass(); //}, true); diff --git a/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/ProgressCheatBlocker.java b/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/ProgressCheatBlocker.java index bdc841238f..4a9b9c3672 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/ProgressCheatBlocker.java +++ b/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/ProgressCheatBlocker.java @@ -4,6 +4,7 @@ import com.ghostchu.peerbanhelper.database.dao.impl.ProgressCheatBlockerPersistDao; import com.ghostchu.peerbanhelper.downloader.Downloader; import com.ghostchu.peerbanhelper.downloader.DownloaderFeatureFlag; +import com.ghostchu.peerbanhelper.event.PeerUnbanEvent; import com.ghostchu.peerbanhelper.module.AbstractRuleFeatureModule; import com.ghostchu.peerbanhelper.module.CheckResult; import com.ghostchu.peerbanhelper.module.PeerAction; @@ -23,6 +24,7 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.cache.Weigher; +import com.google.common.eventbus.Subscribe; import inet.ipaddr.IPAddress; import io.javalin.http.Context; import lombok.AllArgsConstructor; @@ -103,12 +105,45 @@ public void onEnable() { Main.getReloadManager().register(this); } + @Subscribe + public void onPeerUnBan(PeerUnbanEvent event) { + IPAddress peerPrefix; + IPAddress peerIp = event.getPeer().getAddress(); + if (peerIp.isIPv4()) { + peerPrefix = IPAddressUtil.toPrefixBlock(peerIp, ipv4PrefixLength); + } else { + peerPrefix = IPAddressUtil.toPrefixBlock(peerIp, ipv6PrefixLength); + } + String peerIpString = peerIp.toString(); + Client client = new Client(peerPrefix.toString(), event.getBanMetadata().getTorrent().getId()); + // 从缓存取数据 + List lastRecordedProgress = null; + try { + // 如果未命中缓存 只导入当前种子记录的ip + lastRecordedProgress = progressRecorder.get(client, () -> loadClientTasks(client)); + } catch (ExecutionException e) { + log.error("Unhandled exception during load cached record data", e); + } + if (lastRecordedProgress == null) lastRecordedProgress = new CopyOnWriteArrayList<>(); + ClientTask clientTask = lastRecordedProgress.stream().filter(task -> task.getPeerIp().equals(peerIpString)).findFirst().orElse(null); + if (clientTask != null) { + clientTask.setDownloader(""); + clientTask.setBanDelayWindowEndAt(0L); + clientTask.setLastReportProgress(0); + clientTask.setLastReportUploaded(0); + clientTask.setTrackingUploadedIncreaseTotal(0); + clientTask.setRewindCounter(0); + clientTask.setProgressDifferenceCounter(0); + clientTask.setFastPcbTestExecuteAt(0); + progressRecorder.put(client, lastRecordedProgress); + } + } + private void cleanDatabase() { try { progressCheatBlockerPersistDao.cleanupDatabase(new Timestamp(System.currentTimeMillis() - persistDuration)); } catch (Throwable e) { log.error("Unable to remove expired data from database", e); - ; } } @@ -330,7 +365,7 @@ private void reloadConfig() { } } - private boolean isUploadingToPeer(Peer peer){ + private boolean isUploadingToPeer(Peer peer) { return peer.getUploadSpeed() > 0 || peer.getUploaded() > 0; } diff --git a/src/main/java/com/ghostchu/peerbanhelper/module/impl/webapi/PBHBanController.java b/src/main/java/com/ghostchu/peerbanhelper/module/impl/webapi/PBHBanController.java index 3e7538fc6c..dca11d72a7 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/module/impl/webapi/PBHBanController.java +++ b/src/main/java/com/ghostchu/peerbanhelper/module/impl/webapi/PBHBanController.java @@ -71,9 +71,14 @@ public void onEnable() { private void handleBanDelete(Context context) { List request = Arrays.asList(context.bodyAsClass(String[].class)); List pendingRemovals = new ArrayList<>(); - for (PeerAddress address : getServer().getBannedPeers().keySet()) { - if (request.contains(address.getIp())) { - pendingRemovals.add(address); + if (request.contains("*")) { + pendingRemovals.addAll(getServer().getBannedPeers().keySet()); + getServer().getNeedReApplyBanList().set(true); + } else { + for (PeerAddress address : getServer().getBannedPeers().keySet()) { + if (request.contains(address.getIp())) { + pendingRemovals.add(address); + } } } pendingRemovals.forEach(pa -> getServer().scheduleUnBanPeer(pa)); @@ -101,7 +106,8 @@ private void handleBans(Context ctx) { long limit = Long.parseLong(Objects.requireNonNullElse(ctx.queryParam("limit"), "-1")); long lastBanTime = Long.parseLong(Objects.requireNonNullElse(ctx.queryParam("lastBanTime"), "-1")); boolean ignoreBanForDisconnect = Boolean.parseBoolean(Objects.requireNonNullElse(ctx.queryParam("ignoreBanForDisconnect"), "true")); - var banResponseList = getBanResponseStream(locale(ctx), lastBanTime, limit, ignoreBanForDisconnect); + var search = ctx.queryParam("search"); + var banResponseList = getBanResponseStream(locale(ctx), lastBanTime, limit, ignoreBanForDisconnect, search); ctx.json(new StdResp(true, null, banResponseList.toList())); } @@ -111,14 +117,16 @@ public void onDisable() { } - private @NotNull Stream getBanResponseStream(String locale, long lastBanTime, long limit, boolean ignoreBanForDisconnect) { + private @NotNull Stream getBanResponseStream(String locale, long lastBanTime, long limit, boolean ignoreBanForDisconnect, String search) { var banResponseList = getServer().getBannedPeers() .entrySet() .stream() .filter(b -> { - if(!ignoreBanForDisconnect) return true; + if (!ignoreBanForDisconnect) return true; return !b.getValue().isBanForDisconnect(); }) + .filter(b -> search == null || b.getKey().toString().toLowerCase(Locale.ROOT).contains(search.toLowerCase(Locale.ROOT)) + || b.getValue().toString().toLowerCase(Locale.ROOT).contains(search.toLowerCase(Locale.ROOT))) .map(entry -> new BanResponse(entry.getKey().getAddress().toString(), new BakedBanMetadata(locale, entry.getValue()))) .sorted((o1, o2) -> Long.compare(o2.getBanMetadata().getBanAt(), o1.getBanMetadata().getBanAt())); if (lastBanTime > 0) { diff --git a/src/main/java/com/ghostchu/peerbanhelper/module/impl/webapi/PBHDownloaderController.java b/src/main/java/com/ghostchu/peerbanhelper/module/impl/webapi/PBHDownloaderController.java index 2eb930348e..eaafb592b5 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/module/impl/webapi/PBHDownloaderController.java +++ b/src/main/java/com/ghostchu/peerbanhelper/module/impl/webapi/PBHDownloaderController.java @@ -1,14 +1,19 @@ package com.ghostchu.peerbanhelper.module.impl.webapi; +import com.ghostchu.peerbanhelper.Main; import com.ghostchu.peerbanhelper.PeerBanHelperServer; import com.ghostchu.peerbanhelper.downloader.Downloader; import com.ghostchu.peerbanhelper.downloader.DownloaderLastStatus; import com.ghostchu.peerbanhelper.ipdb.IPGeoData; +import com.ghostchu.peerbanhelper.lab.Experiments; +import com.ghostchu.peerbanhelper.lab.Laboratory; import com.ghostchu.peerbanhelper.module.AbstractFeatureModule; import com.ghostchu.peerbanhelper.module.impl.webapi.dto.PopulatedPeerDTO; import com.ghostchu.peerbanhelper.text.Lang; import com.ghostchu.peerbanhelper.text.TranslationComponent; +import com.ghostchu.peerbanhelper.util.IPAddressUtil; import com.ghostchu.peerbanhelper.util.context.IgnoreScan; +import com.ghostchu.peerbanhelper.util.dns.DNSLookup; import com.ghostchu.peerbanhelper.web.JavalinWebContainer; import com.ghostchu.peerbanhelper.web.Role; import com.ghostchu.peerbanhelper.web.wrapper.StdResp; @@ -24,6 +29,8 @@ import org.springframework.stereotype.Component; import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.Collection; import java.util.List; import java.util.Optional; @@ -36,6 +43,10 @@ public class PBHDownloaderController extends AbstractFeatureModule { @Autowired private JavalinWebContainer webContainer; + @Autowired + private Laboratory laboratory; + @Autowired + private DNSLookup dnsLookup; @Override public boolean isConfigurable() { @@ -71,6 +82,7 @@ private void handleDownloaderPut(Context ctx) { if (name.contains(".")) { throw new IllegalArgumentException("Illegal character (.) in name: " + name); } + boolean paused = draftDownloader.has("paused") && draftDownloader.get("paused").getAsBoolean(); JsonObject config = draftDownloader.get("config").getAsJsonObject(); Downloader downloader = getServer().createDownloader(name, config); if (downloader == null) { @@ -78,6 +90,7 @@ private void handleDownloaderPut(Context ctx) { ctx.json(new StdResp(false, tl(locale(ctx), Lang.DOWNLOADER_API_ADD_FAILURE), null)); return; } + downloader.setPaused(paused); if (getServer().registerDownloader(downloader)) { ctx.status(HttpStatus.CREATED); ctx.json(new StdResp(true, tl(locale(ctx), Lang.DOWNLOADER_API_CREATED), null)); @@ -100,6 +113,7 @@ private void handleDownloaderPatch(Context ctx, String downloaderName) { if (name.contains(".")) { throw new IllegalArgumentException("Illegal character (.) in name: " + name); } + boolean paused = draftDownloader.has("paused") && draftDownloader.get("paused").getAsBoolean(); JsonObject config = draftDownloader.get("config").getAsJsonObject(); Downloader downloader = getServer().createDownloader(name, config); if (downloader == null) { @@ -107,6 +121,7 @@ private void handleDownloaderPatch(Context ctx, String downloaderName) { ctx.json(new StdResp(false, tl(locale(ctx), Lang.DOWNLOADER_API_UPDATE_FAILURE), null)); return; } + downloader.setPaused(paused); // 可能重命名了? getServer().getDownloaders().stream() .filter(d -> d.getName().equals(downloaderName)) @@ -132,6 +147,7 @@ private void handleDownloaderTest(Context ctx) { if (name.contains(".")) { throw new IllegalArgumentException("Illegal character (.) in name: " + name); } + boolean paused = draftDownloader.has("paused") && draftDownloader.get("paused").getAsBoolean(); JsonObject config = draftDownloader.get("config").getAsJsonObject(); // if (getServer().getDownloaders().stream().anyMatch(d -> d.getName().equals(name))) { // ctx.status(HttpStatus.CONFLICT); @@ -144,14 +160,19 @@ private void handleDownloaderTest(Context ctx) { ctx.json(new StdResp(false, tl(locale(ctx), Lang.DOWNLOADER_API_ADD_FAILURE), null)); return; } + downloader.setPaused(paused); try { - var testResult = downloader.login(); - if (testResult.success()) { - ctx.json(new StdResp(testResult.success(), tl(locale(ctx), Lang.DOWNLOADER_API_TEST_OK), null)); + if (!paused) { + var testResult = downloader.login(); + if (testResult.success()) { + ctx.json(new StdResp(testResult.success(), tl(locale(ctx), Lang.DOWNLOADER_API_TEST_OK), null)); + } else { + ctx.json(new StdResp(testResult.success(), tl(locale(ctx), testResult.getMessage()), null)); + } + downloader.close(); } else { - ctx.json(new StdResp(testResult.success(), tl(locale(ctx), testResult.getMessage()), null)); + ctx.json(new StdResp(true, tl(locale(ctx), Lang.DOWNLOADER_API_TEST_BYPASS_PAUSED), null)); } - downloader.close(); } catch (Exception e) { log.error("Validate downloader failed", e); ctx.status(HttpStatus.INTERNAL_SERVER_ERROR); @@ -186,24 +207,37 @@ private void handlePeersInTorrentOnDownloader(Context ctx, String downloaderName return; } Downloader downloader = selected.get(); + boolean ptr = Main.getMainConfig().getBoolean("lookup.dns-reverse-lookup"); List peerWrappers = getServer().getLivePeersSnapshot().values() .stream() - .flatMap(Collection::stream) + .flatMap(Collection::parallelStream) .filter(p -> p.getDownloader().equals(downloader.getName())) .filter(p -> p.getTorrent().getId().equals(torrentId)) .sorted((o1, o2) -> Long.compare(o2.getPeer().getUploadSpeed(), o1.getPeer().getUploadSpeed())) - .map(this::populatePeerDTO) + .map(dat -> populatePeerDTO(dat, ptr)) .toList(); ctx.json(new StdResp(true, null, peerWrappers)); } - private PopulatedPeerDTO populatePeerDTO(PeerMetadata p) { - PopulatedPeerDTO dto = new PopulatedPeerDTO(p.getPeer(), null); + private PopulatedPeerDTO populatePeerDTO(PeerMetadata p, boolean resolvePTR) { + PopulatedPeerDTO dto = new PopulatedPeerDTO(p.getPeer(), null, null); PeerBanHelperServer.IPDBResponse response = getServer().queryIPDB(p.getPeer().toPeerAddress()); IPGeoData geoData = response.geoData().get(); if (geoData != null) { dto.setGeo(geoData); } + if (dto.getPtrRecord() == null && resolvePTR) { + if (laboratory.isExperimentActivated(Experiments.DNSJAVA.getExperiment())) { + dto.setPtrRecord(dnsLookup.ptr(IPAddressUtil.getIPAddress(p.getPeer().getAddress().getIp()).toReverseDNSLookupString()).join().orElse(null)); + } else { + try { + dto.setPtrRecord(InetAddress.getByName(p.getPeer().getAddress().getIp()).getCanonicalHostName()); + } catch (UnknownHostException e) { + dto.setPtrRecord(null); + } + } + } + return dto; } @@ -260,12 +294,12 @@ private void handleDownloaderStatus(@NotNull Context ctx, String downloaderName) .count(); JsonObject config = downloader.saveDownloaderJson(); - ctx.json(new StdResp(true, null, new DownloaderStatus(lastStatus, tl(locale, downloader.getLastStatusMessage() == null ? new TranslationComponent(Lang.STATUS_TEXT_UNKNOWN) : downloader.getLastStatusMessage()), activeTorrents, activePeers, config))); + ctx.json(new StdResp(true, null, new DownloaderStatus(lastStatus, tl(locale, downloader.getLastStatusMessage() == null ? new TranslationComponent(Lang.STATUS_TEXT_UNKNOWN) : downloader.getLastStatusMessage()), activeTorrents, activePeers, config, downloader.isPaused()))); } private void handleDownloaderList(@NotNull Context ctx) { List downloaders = getServer().getDownloaders() - .stream().map(d -> new DownloaderWrapper(d.getName(), d.getEndpoint(), d.getType().toLowerCase())) + .stream().map(d -> new DownloaderWrapper(d.getName(), d.getEndpoint(), d.getType().toLowerCase(), d.isPaused())) .toList(); ctx.json(new StdResp(true, null, downloaders)); } @@ -281,10 +315,10 @@ record DraftDownloader(String name, JsonObject config) { record DownloaderStatus(DownloaderLastStatus lastStatus, String lastStatusMessage, long activeTorrents, - long activePeers, JsonObject config) { + long activePeers, JsonObject config, boolean paused) { } - record DownloaderWrapper(String name, String endpoint, String type) { + record DownloaderWrapper(String name, String endpoint, String type, boolean paused) { } } diff --git a/src/main/java/com/ghostchu/peerbanhelper/module/impl/webapi/PBHEasterEggController.java b/src/main/java/com/ghostchu/peerbanhelper/module/impl/webapi/PBHEasterEggController.java index 8fd71f5067..1c53f3693c 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/module/impl/webapi/PBHEasterEggController.java +++ b/src/main/java/com/ghostchu/peerbanhelper/module/impl/webapi/PBHEasterEggController.java @@ -1,9 +1,12 @@ package com.ghostchu.peerbanhelper.module.impl.webapi; +import com.ghostchu.peerbanhelper.Main; import com.ghostchu.peerbanhelper.module.AbstractFeatureModule; import com.ghostchu.peerbanhelper.util.context.IgnoreScan; import com.ghostchu.peerbanhelper.web.JavalinWebContainer; +import io.javalin.http.ContentType; import io.javalin.http.Context; +import io.javalin.http.HttpStatus; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.springframework.stereotype.Component; @@ -63,7 +66,24 @@ public boolean isConfigurable() { @Override public void onEnable() { javalinWebContainer.javalin() - .get("/api/egg", this::handleEgg); + .get("/api/egg", this::handleEgg) + .get("/api/neuro", this::neuro) // AI VTuber made by Vedal on Twitch: https://www.twitch.tv/vedal987 + .get("/api/neurosama", this::neuro); + } + + + private void neuro(Context context) { + // Yeeeeet, Neuro! + var imageStream = Main.class.getResourceAsStream("/assets/other/neuro.png"); + if (imageStream == null) { + context.status(HttpStatus.NOT_FOUND); + context.result("You killed Neuro! How dare you!?"); + return; + } + context + .status(HttpStatus.ENHANCE_YOUR_CALM) + .contentType(ContentType.IMAGE_PNG) + .result(imageStream); } private void handleEgg(Context context) { diff --git a/src/main/java/com/ghostchu/peerbanhelper/module/impl/webapi/PBHGeneralController.java b/src/main/java/com/ghostchu/peerbanhelper/module/impl/webapi/PBHGeneralController.java index c31208aa70..ca1e6679dc 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/module/impl/webapi/PBHGeneralController.java +++ b/src/main/java/com/ghostchu/peerbanhelper/module/impl/webapi/PBHGeneralController.java @@ -1,6 +1,7 @@ package com.ghostchu.peerbanhelper.module.impl.webapi; import com.ghostchu.peerbanhelper.Main; +import com.ghostchu.peerbanhelper.PeerBanHelperServer; import com.ghostchu.peerbanhelper.module.AbstractFeatureModule; import com.ghostchu.peerbanhelper.module.FeatureModule; import com.ghostchu.peerbanhelper.module.ModuleManager; @@ -58,6 +59,8 @@ public class PBHGeneralController extends AbstractFeatureModule { private ModuleMatchCache moduleMatchCache; @Autowired private ModuleManager moduleManager; + @Autowired + private PeerBanHelperServer peerBanHelperServer; @Override public boolean isConfigurable() { @@ -81,10 +84,29 @@ public void onEnable() { .get("/api/general/checkModuleAvailable", this::handleModuleAvailable, Role.USER_READ) .get("/api/general/heapdump", this::handleHeapDump, Role.USER_WRITE) .post("/api/general/reload", this::handleReloading, Role.USER_WRITE) + .get("/api/general/global", this::handleGlobalConfigRead, Role.USER_READ) + .patch("/api/general/global", this::handleGlobalConfig, Role.USER_WRITE) .get("/api/general/{configName}", this::handleConfigGet, Role.USER_WRITE) .put("/api/general/{configName}", this::handleConfigPut, Role.USER_WRITE); } + private void handleGlobalConfigRead(Context context) { + Map data = new LinkedHashMap<>(); + data.put("globalPaused", peerBanHelperServer.isGlobalPaused()); + context.json(new StdResp(true, null, data)); + } + + private void handleGlobalConfig(Context context) { + var body = context.bodyAsClass(GlobalOptionPatch.class); + if (body == null) { + throw new IllegalArgumentException("Request body cannot be null"); + } + if (body.globalPaused() != null) { + peerBanHelperServer.setGlobalPaused(body.globalPaused()); + } + context.json(new StdResp(true, "OK!", null)); + } + private void handleModuleAvailable(Context context) { var moduleName = context.queryParam("module"); if (moduleName == null) { @@ -92,9 +114,9 @@ private void handleModuleAvailable(Context context) { } for (FeatureModule module : moduleManager.getModules()) { if (module.getName().equalsIgnoreCase(moduleName) - || module.getConfigName().equalsIgnoreCase(moduleName) - || module.getClass().getName().equalsIgnoreCase(moduleName) - || module.getClass().getSimpleName().equalsIgnoreCase(moduleName)) { + || module.getConfigName().equalsIgnoreCase(moduleName) + || module.getClass().getName().equalsIgnoreCase(moduleName) + || module.getClass().getSimpleName().equalsIgnoreCase(moduleName)) { if (module.isModuleEnabled()) { context.json(new StdResp(true, null, true)); return; @@ -276,6 +298,7 @@ private void handleConfigGet(Context context) throws IOException, InvalidConfigu yamlConfiguration.load(Main.getProfileConfigFile()); stringListToMapList(yamlConfiguration, "module.peer-id-blacklist", "banned-peer-id"); stringListToMapList(yamlConfiguration, "module.client-name-blacklist", "banned-client-name"); + stringListToMapList(yamlConfiguration, "module.ptr-blacklist", "ptr-rules"); } default -> { context.status(HttpStatus.NOT_FOUND); @@ -414,6 +437,15 @@ private static void mergeYaml(ConfigurationSection cfg, Map newM value = bannedList; } } + case "ptr-rules" -> { + if ("module.ptr-blacklist".equals(path)) { + List bannedList = ((List) value).stream() + .filter(Map.class::isInstance) + .map(GSON::toJson) + .toList(); + value = bannedList; + } + } } // 如果值是 Map,递归替换子 cfg 中的键 if (value instanceof Map map) { @@ -441,6 +473,12 @@ public void onDisable() { } + public record GlobalOptionPatch( + Boolean globalPaused + ) { + + } + public record ReloadEntry( String reloadable, String reloadResult diff --git a/src/main/java/com/ghostchu/peerbanhelper/module/impl/webapi/PBHPeerController.java b/src/main/java/com/ghostchu/peerbanhelper/module/impl/webapi/PBHPeerController.java index df39530d1e..c4b7649696 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/module/impl/webapi/PBHPeerController.java +++ b/src/main/java/com/ghostchu/peerbanhelper/module/impl/webapi/PBHPeerController.java @@ -4,11 +4,14 @@ import com.ghostchu.peerbanhelper.database.dao.impl.PeerRecordDao; import com.ghostchu.peerbanhelper.ipdb.IPDB; import com.ghostchu.peerbanhelper.ipdb.IPGeoData; +import com.ghostchu.peerbanhelper.lab.Experiments; +import com.ghostchu.peerbanhelper.lab.Laboratory; import com.ghostchu.peerbanhelper.module.AbstractFeatureModule; import com.ghostchu.peerbanhelper.module.impl.rule.ActiveMonitoringModule; import com.ghostchu.peerbanhelper.util.IPAddressUtil; import com.ghostchu.peerbanhelper.util.MsgUtil; import com.ghostchu.peerbanhelper.util.context.IgnoreScan; +import com.ghostchu.peerbanhelper.util.dns.DNSLookup; import com.ghostchu.peerbanhelper.util.paging.Page; import com.ghostchu.peerbanhelper.util.paging.Pageable; import com.ghostchu.peerbanhelper.web.JavalinWebContainer; @@ -23,6 +26,7 @@ import java.net.InetAddress; import java.sql.SQLException; import java.sql.Timestamp; +import java.util.concurrent.TimeUnit; @Component @Slf4j @@ -32,15 +36,20 @@ public class PBHPeerController extends AbstractFeatureModule { private final HistoryDao historyDao; private final PeerRecordDao peerRecordDao; private final ActiveMonitoringModule activeMonitoringModule; + private final Laboratory laboratory; + private final DNSLookup dnsLookup; public PBHPeerController(JavalinWebContainer javalinWebContainer, HistoryDao historyDao, PeerRecordDao peerRecordDao, - ActiveMonitoringModule activeMonitoringModule) { + ActiveMonitoringModule activeMonitoringModule, + Laboratory laboratory, DNSLookup dnsLookup) { super(); this.javalinWebContainer = javalinWebContainer; this.historyDao = historyDao; this.peerRecordDao = peerRecordDao; this.activeMonitoringModule = activeMonitoringModule; + this.laboratory = laboratory; + this.dnsLookup = dnsLookup; } @Override @@ -70,15 +79,14 @@ public void onEnable() { private void handleInfo(Context ctx) throws SQLException { // 转换 IP 格式到 PBH 统一内部格式 activeMonitoringModule.flush(); - @SuppressWarnings("DataFlowIssue") - String ip = IPAddressUtil.getIPAddress(ctx.pathParam("ip")).toString(); + String ip = IPAddressUtil.getIPAddress(ctx.pathParam("ip")).toNormalizedString(); long banCount = historyDao.queryBuilder() .where() .eq("ip", new SelectArg(ip)) .countOf(); long torrentAccessCount = peerRecordDao.queryBuilder() .where() - .eq("address",new SelectArg( ip)) + .eq("address", new SelectArg(ip)) .countOf(); long uploadedToPeer; long downloadedFromPeer; @@ -128,15 +136,23 @@ private void handleInfo(Context ctx) throws SQLException { } catch (Exception e) { log.warn("Unable to perform GeoIP query for ip {}", ip); } + String ptrLookup = null; + try { + if (laboratory.isExperimentActivated(Experiments.DNSJAVA.getExperiment())) { + ptrLookup = dnsLookup.ptr(ip).get(3, TimeUnit.SECONDS).orElse(null); + } else { + ptrLookup = InetAddress.getByName(ip).getCanonicalHostName(); + } + } catch (Exception ignored) { + } var info = new PeerInfo( upDownResult != null || banCount > 0 || torrentAccessCount > 0, - ip, firstTimeSeenTS, lastTimeSeenTS, banCount, torrentAccessCount, uploadedToPeer, downloadedFromPeer, geoIP); + ip, firstTimeSeenTS, lastTimeSeenTS, banCount, torrentAccessCount, uploadedToPeer, downloadedFromPeer, geoIP, ptrLookup); ctx.json(new StdResp(true, null, info)); } private void handleBanHistory(Context ctx) throws SQLException { - @SuppressWarnings("DataFlowIssue") String ip = IPAddressUtil.getIPAddress(ctx.pathParam("ip")).toString(); Pageable pageable = new Pageable(ctx); var builder = historyDao.queryBuilder() @@ -152,7 +168,6 @@ private void handleBanHistory(Context ctx) throws SQLException { private void handleAccessHistory(Context ctx) throws SQLException { activeMonitoringModule.flush(); - @SuppressWarnings("DataFlowIssue") String ip = IPAddressUtil.getIPAddress(ctx.pathParam("ip")).toString(); Pageable pageable = new Pageable(ctx); var builder = peerRecordDao.queryBuilder() @@ -179,7 +194,8 @@ public record PeerInfo( long torrentAccessCount, long uploadedToPeer, long downloadedFromPeer, - IPGeoData geo + IPGeoData geo, + String ptrLookup ) { } } diff --git a/src/main/java/com/ghostchu/peerbanhelper/module/impl/webapi/dto/PopulatedPeerDTO.java b/src/main/java/com/ghostchu/peerbanhelper/module/impl/webapi/dto/PopulatedPeerDTO.java index 04dd8088d1..4d6c64fcf6 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/module/impl/webapi/dto/PopulatedPeerDTO.java +++ b/src/main/java/com/ghostchu/peerbanhelper/module/impl/webapi/dto/PopulatedPeerDTO.java @@ -13,4 +13,5 @@ public final class PopulatedPeerDTO { private PeerWrapper peer; private IPGeoData geo; + private String ptrRecord; } diff --git a/src/main/java/com/ghostchu/peerbanhelper/pbhplus/ActivationKeyManager.java b/src/main/java/com/ghostchu/peerbanhelper/pbhplus/ActivationKeyManager.java index 7944ef5e58..bb2b437aaa 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/pbhplus/ActivationKeyManager.java +++ b/src/main/java/com/ghostchu/peerbanhelper/pbhplus/ActivationKeyManager.java @@ -30,7 +30,7 @@ @Component public class ActivationKeyManager { public static String OFFICIAL_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCHxgRTk+Zx/pkN8rpK+Lbr1/f1meapIRDJIgBiSfFy4xdbmDF8wE9PJhdM+3peThz9dJQlt6dkeduIVp65rGS9oZdj7gO5YKtUCDir4NgGQGe1p2C41Xv6RiOXObLmF+ubAJILsimwtDyJT8IysEh9hgaZWnvRXT8JX9wB0Ti2rwIDAQAB"; - private static String hardwareUUIDHash; + private static final String hardwareUUIDHash; static { SystemInfo systemInfo = new SystemInfo(); @@ -38,7 +38,7 @@ public class ActivationKeyManager { hardwareUUIDHash = Hashing.sha256().hashString(hardwareUUID, StandardCharsets.UTF_8).toString().substring(0, 10); } - private Map.Entry localKeyPair; + private final Map.Entry localKeyPair; public ActivationKeyManager() throws Exception { localKeyPair = loadLocalKeyPair(); diff --git a/src/main/java/com/ghostchu/peerbanhelper/peer/Peer.java b/src/main/java/com/ghostchu/peerbanhelper/peer/Peer.java index cea6ec9c55..2aac62b515 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/peer/Peer.java +++ b/src/main/java/com/ghostchu/peerbanhelper/peer/Peer.java @@ -73,6 +73,12 @@ public interface Peer extends Comparable { @Nullable PeerFlag getFlags(); + /** + * 对等体是否连接中或者握手中(总之就是还没准备好传输数据) + * @return 是否连接中或者握手中 + */ + boolean isHandshaking(); + /** * 获取此 Peer 支持的消息集合,需要下载器支持 * 不支持的下载器此处将返回空集合 diff --git a/src/main/java/com/ghostchu/peerbanhelper/peer/PeerImpl.java b/src/main/java/com/ghostchu/peerbanhelper/peer/PeerImpl.java index 260a216008..989ab9ceaf 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/peer/PeerImpl.java +++ b/src/main/java/com/ghostchu/peerbanhelper/peer/PeerImpl.java @@ -6,7 +6,7 @@ import java.util.List; @Setter -public class PeerImpl implements Peer { +public final class PeerImpl implements Peer { private PeerAddress peerAddress; private String rawIp; private String peerId; @@ -18,8 +18,9 @@ public class PeerImpl implements Peer { private double progress; private PeerFlag flags; private List supportedMessages; + private boolean handshaking; - public PeerImpl(PeerAddress peerAddress, String rawIp, String peerId, String clientName, long downloadSpeed, long downloaded, long uploadSpeed, long uploaded, double progress, PeerFlag flags, List supportedMessages) { + public PeerImpl(PeerAddress peerAddress, String rawIp, String peerId, String clientName, long downloadSpeed, long downloaded, long uploadSpeed, long uploaded, double progress, PeerFlag flags, List supportedMessages, boolean handshaking) { this.peerAddress = peerAddress; this.rawIp = rawIp; this.peerId = peerId; @@ -31,6 +32,7 @@ public PeerImpl(PeerAddress peerAddress, String rawIp, String peerId, String cli this.progress = progress; this.flags = flags; this.supportedMessages = supportedMessages; + this.handshaking = handshaking; } @Override @@ -78,6 +80,11 @@ public PeerFlag getFlags() { return flags; } + @Override + public boolean isHandshaking() { + return handshaking; + } + @Override public String getRawIp() { return rawIp; diff --git a/src/main/java/com/ghostchu/peerbanhelper/platform/WindowsEcoQosAPI.java b/src/main/java/com/ghostchu/peerbanhelper/platform/WindowsEcoQosAPI.java index b98a792961..482a9cee21 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/platform/WindowsEcoQosAPI.java +++ b/src/main/java/com/ghostchu/peerbanhelper/platform/WindowsEcoQosAPI.java @@ -31,7 +31,7 @@ private void installEcoQosApi() { if (os.startsWith("win")) { if (ecoMode.ecoMode(true)) { log.info(tlUI(Lang.IN_ECOMODE_DESCRIPTION)); - ExchangeMap.GUI_DISPLAY_FLAGS.add(new ExchangeMap.DisplayFlag(10, tlUI(Lang.IN_ECOMODE_SHORT))); + ExchangeMap.GUI_DISPLAY_FLAGS.add(new ExchangeMap.DisplayFlag("eco-mode", 10, tlUI(Lang.IN_ECOMODE_SHORT))); } } } diff --git a/src/main/java/com/ghostchu/peerbanhelper/push/impl/PushPlusPushProvider.java b/src/main/java/com/ghostchu/peerbanhelper/push/impl/PushPlusPushProvider.java index f58816f432..d48f52d990 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/push/impl/PushPlusPushProvider.java +++ b/src/main/java/com/ghostchu/peerbanhelper/push/impl/PushPlusPushProvider.java @@ -21,7 +21,7 @@ import java.util.Map; @Slf4j -public class PushPlusPushProvider extends AbstractPushProvider { +public final class PushPlusPushProvider extends AbstractPushProvider { private final Config config; private final String name; diff --git a/src/main/java/com/ghostchu/peerbanhelper/push/impl/ServerChanPushProvider.java b/src/main/java/com/ghostchu/peerbanhelper/push/impl/ServerChanPushProvider.java index a22d4e12a8..85e8ae225b 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/push/impl/ServerChanPushProvider.java +++ b/src/main/java/com/ghostchu/peerbanhelper/push/impl/ServerChanPushProvider.java @@ -18,7 +18,7 @@ import java.util.HashMap; import java.util.Map; -public class ServerChanPushProvider extends AbstractPushProvider { +public final class ServerChanPushProvider extends AbstractPushProvider { private final Config config; private final String name; diff --git a/src/main/java/com/ghostchu/peerbanhelper/push/impl/SmtpPushProvider.java b/src/main/java/com/ghostchu/peerbanhelper/push/impl/SmtpPushProvider.java index 65e6a7b5d8..7a0bbfa4f9 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/push/impl/SmtpPushProvider.java +++ b/src/main/java/com/ghostchu/peerbanhelper/push/impl/SmtpPushProvider.java @@ -19,7 +19,7 @@ import java.util.Objects; @Slf4j -public class SmtpPushProvider extends AbstractPushProvider { +public final class SmtpPushProvider extends AbstractPushProvider { private final Config config; private final String name; diff --git a/src/main/java/com/ghostchu/peerbanhelper/push/impl/TelegramPushProvider.java b/src/main/java/com/ghostchu/peerbanhelper/push/impl/TelegramPushProvider.java index 8b7ca15c13..f0e10ffbe0 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/push/impl/TelegramPushProvider.java +++ b/src/main/java/com/ghostchu/peerbanhelper/push/impl/TelegramPushProvider.java @@ -18,7 +18,7 @@ import java.util.HashMap; import java.util.Map; -public class TelegramPushProvider extends AbstractPushProvider { +public final class TelegramPushProvider extends AbstractPushProvider { private final Config config; private final String name; diff --git a/src/main/java/com/ghostchu/peerbanhelper/text/Lang.java b/src/main/java/com/ghostchu/peerbanhelper/text/Lang.java index d908011076..a4d04b308b 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/text/Lang.java +++ b/src/main/java/com/ghostchu/peerbanhelper/text/Lang.java @@ -439,12 +439,12 @@ public enum Lang { GUI_MENU_STATS_BANNED, GUI_MENU_STATS_DOWNLOADER, GUI_MENU_QUICK_OPERATIONS, - GUI_MENU_STATS, + GUI_MENU_STATS, KADEMLIA_STARTUP_ERROR, PORT_MAPPER_PORT_MAPPED, PORT_MAPPER_PORT_MAPPING_FAILED, DECENTRALIZED_PORT_FORWARDED, - LAB_EXPERIMENT_IPFS_TITLE, + LAB_EXPERIMENT_IPFS_TITLE, LAB_EXPERIMENT_IPFS_DESCRIPTION, PORT_MAPPER_PORT_MAPPED_BUT_INTERNAL_ADDRESS, PORT_MAPPER_PORT_MAPPING, @@ -467,7 +467,39 @@ public enum Lang { FREE_LICENSE_DESCRIPTION, FREE_LICENSE_SOURCE, FREE_LICENSE_LICENSE_TO, - FREE_LICENSE_RENEW_STILL_ACTIVE, PBH_PLUS_LICENSE_UPDATED, DOWNLOADER_QB_DISABLE_SAME_IP_MULTI_CONNECTION_FAILED; + FREE_LICENSE_RENEW_STILL_ACTIVE, + PBH_PLUS_LICENSE_UPDATED, + DOWNLOADER_QB_DISABLE_SAME_IP_MULTI_CONNECTION_FAILED, + DOWNLOADER_PAUSED, + LAB_EXPERIMENT_DNSJAVA_TITLE, + LAB_EXPERIMENT_DNSJAVA_DESCRIPTION, + LAB_EXPERIMENT_SQLITE_VACUUM_TITLE, + LAB_EXPERIMENT_SQLITE_VACUUM_DESCRIPTION, + SQLITE_VACUUM_BACKUP_FAILED, + SQLITE_VACUUM_BACKUP, + SQLITE_VACUUM_BACKUP_COMPLETED, + SQLITE_VACUUM_IN_PROGRESS, + SQLITE_VACUUM_SUCCESS, + MODULE_PTR_MATCH_PTR_RULE, + MATCH_CONDITION_PORT_MATCH, + MODULE_IBL_COMMENT_UNKNOWN, + JSON_MATCHER_NOT_MET, + MATCH_CONDITION_BOOLEAN, + MATCH_CONDITION_BOOLEAN_BY_INTEGER, + MATCH_CONDITION_BOOLEAN_BY_STRING, + MATCH_STRING_CONTAINS, + MATCH_STRING_ENDS_WITH, + MATCH_STRING_LENGTH, + MATCH_STRING_EQUALS, + MATCH_STRING_REGEX, + STATUS_TEXT_PAUSED, + DOWNLOADER_API_TEST_BYPASS_PAUSED, + STATUS_BAR_GLOBAL_PAUSED, + BTN_CONFIG_STATUS_UNSUCCESSFUL_HTTP_REQUEST, + BTN_CONFIG_STATUS_UNSUCCESSFUL_INCOMPATIBLE_BTN_PROTOCOL_VERSION_CLIENT, + BTN_CONFIG_STATUS_UNSUCCESSFUL_INCOMPATIBLE_BTN_PROTOCOL_VERSION_SERVER, + BTN_CONFIG_STATUS_SUCCESSFUL, + BTN_CONFIG_STATUS_EXCEPTION, MATCH_STRING_STARTS_WITH; public String getKey() { return name(); diff --git a/src/main/java/com/ghostchu/peerbanhelper/text/TextManager.java b/src/main/java/com/ghostchu/peerbanhelper/text/TextManager.java index b62d4aecc6..5a73d00311 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/text/TextManager.java +++ b/src/main/java/com/ghostchu/peerbanhelper/text/TextManager.java @@ -73,6 +73,12 @@ public static String tl(String locale, TranslationComponent translationComponent return "Unsupported locale " + locale; } } + if (translationComponent == null) { + return "null"; + } + if (translationComponent.getKey().isBlank()) { + return ""; + } String str = yamlConfiguration.getString(translationComponent.getKey()); if (str == null) { str = translationComponent.getKey(); @@ -113,6 +119,7 @@ public static String[] convert(String locale, @Nullable Object... args) { // continue; // } components[i] = obj.toString(); + } catch (Exception exception) { log.debug("Failed to process the object: {}", obj); components[i] = String.valueOf(obj); // null safe diff --git a/src/main/java/com/ghostchu/peerbanhelper/text/TranslationComponent.java b/src/main/java/com/ghostchu/peerbanhelper/text/TranslationComponent.java index 9fd7b4cc75..05b7abc63b 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/text/TranslationComponent.java +++ b/src/main/java/com/ghostchu/peerbanhelper/text/TranslationComponent.java @@ -7,7 +7,7 @@ import java.util.Arrays; @Getter -public class TranslationComponent implements Serializable { +public final class TranslationComponent implements Serializable { @Serial private static final long serialVersionUID = 1L; diff --git a/src/main/java/com/ghostchu/peerbanhelper/torrent/Torrent.java b/src/main/java/com/ghostchu/peerbanhelper/torrent/Torrent.java index 42ebabfde2..11e28d2bca 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/torrent/Torrent.java +++ b/src/main/java/com/ghostchu/peerbanhelper/torrent/Torrent.java @@ -1,6 +1,6 @@ package com.ghostchu.peerbanhelper.torrent; -import com.ghostchu.peerbanhelper.util.time.InfoHashUtil; +import com.ghostchu.peerbanhelper.util.InfoHashUtil; public interface Torrent { /** diff --git a/src/main/java/com/ghostchu/peerbanhelper/torrent/TorrentImpl.java b/src/main/java/com/ghostchu/peerbanhelper/torrent/TorrentImpl.java index 25ac615613..3d8ea4ba71 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/torrent/TorrentImpl.java +++ b/src/main/java/com/ghostchu/peerbanhelper/torrent/TorrentImpl.java @@ -3,7 +3,7 @@ import lombok.Setter; @Setter -public class TorrentImpl implements Torrent { +public final class TorrentImpl implements Torrent { private boolean privateTorrent; private double progress; private long rtUploadSpeed; diff --git a/src/main/java/com/ghostchu/peerbanhelper/util/IPAddressUtil.java b/src/main/java/com/ghostchu/peerbanhelper/util/IPAddressUtil.java index e07bb1d707..aeb9d145e7 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/util/IPAddressUtil.java +++ b/src/main/java/com/ghostchu/peerbanhelper/util/IPAddressUtil.java @@ -5,6 +5,7 @@ import inet.ipaddr.IPAddress; import inet.ipaddr.IPAddressString; import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -34,7 +35,7 @@ public class IPAddressUtil { * @param ip * @return */ - @Nullable + @Contract(value = "null -> null", pure = true) public static IPAddress getIPAddress(String ip) { if (ip == null) return null; if (ip.startsWith("[") && ip.endsWith("]")) { diff --git a/src/main/java/com/ghostchu/peerbanhelper/util/time/InfoHashUtil.java b/src/main/java/com/ghostchu/peerbanhelper/util/InfoHashUtil.java similarity index 93% rename from src/main/java/com/ghostchu/peerbanhelper/util/time/InfoHashUtil.java rename to src/main/java/com/ghostchu/peerbanhelper/util/InfoHashUtil.java index ae4762daa7..8ae929a4cf 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/util/time/InfoHashUtil.java +++ b/src/main/java/com/ghostchu/peerbanhelper/util/InfoHashUtil.java @@ -1,4 +1,4 @@ -package com.ghostchu.peerbanhelper.util.time; +package com.ghostchu.peerbanhelper.util; import com.google.common.hash.Hashing; diff --git a/src/main/java/com/ghostchu/peerbanhelper/util/dns/DNSLookup.java b/src/main/java/com/ghostchu/peerbanhelper/util/dns/DNSLookup.java new file mode 100644 index 0000000000..de12292385 --- /dev/null +++ b/src/main/java/com/ghostchu/peerbanhelper/util/dns/DNSLookup.java @@ -0,0 +1,91 @@ +package com.ghostchu.peerbanhelper.util.dns; + +import com.ghostchu.peerbanhelper.Main; +import com.ghostchu.simplereloadlib.ReloadResult; +import com.ghostchu.simplereloadlib.Reloadable; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.xbill.DNS.Record; +import org.xbill.DNS.*; +import oshi.SystemInfo; + +import java.net.UnknownHostException; +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +@Slf4j +@Component +public class DNSLookup implements Reloadable { + private volatile ExtendedResolver resolver = new ExtendedResolver(); + + public DNSLookup() { + reloadConfig(); + Main.getReloadManager().register(this); + } + + @Override + public ReloadResult reloadModule() throws Exception { + reloadConfig(); + return Reloadable.super.reloadModule(); + } + + private void reloadConfig() { + // get system dns via oshi + SystemInfo systemInfo = new SystemInfo(); + var dnsServers = systemInfo.getOperatingSystem().getNetworkParams().getDnsServers(); + List dns = Main.getMainConfig().getStringList("resolvers.servers"); + if (Main.getMainConfig().getBoolean("resolvers.use-system",true)) { + dns.addAll(Arrays.asList(dnsServers)); + } + applyDnsServers(dns); + } + + private void applyDnsServers(List servers) { + List resolvers = new ArrayList<>(); + for (String dns : servers) { + if (dns.startsWith("http")) { + resolvers.add(new DohResolver(dns)); + log.debug("Added DoH resolver: {}", dns); + } else { + try { + resolvers.add(new SimpleResolver(dns)); + log.debug("Added resolver: {}", dns); + } catch (UnknownHostException e) { + log.warn("Failed to add resolver: {}", dns, e); + } + } + } + var replace = new ExtendedResolver(resolvers.toArray(new Resolver[0])); + replace.setLoadBalance(true); + replace.setTimeout(Duration.of(3, ChronoUnit.SECONDS)); + resolver = replace; + } + + public CompletableFuture> ptr(String query) { + return CompletableFuture.supplyAsync(() -> { + try { + Lookup lookup = new Lookup(query, Type.PTR); + lookup.setResolver(resolver); + lookup.run(); + if (lookup.getResult() == Lookup.SUCCESSFUL) { + Record[] records = lookup.getAnswers(); + for (Record record : records) { + if (record instanceof PTRRecord ptr) { + return Optional.of(ptr.getTarget().toString()); + } + } + return Optional.empty(); + } + return Optional.empty(); + } catch (TextParseException ignored) { + return Optional.empty(); + } + }); + } + +} diff --git a/src/main/java/com/ghostchu/peerbanhelper/util/paging/Page.java b/src/main/java/com/ghostchu/peerbanhelper/util/paging/Page.java index 34942adaef..d1a4d04672 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/util/paging/Page.java +++ b/src/main/java/com/ghostchu/peerbanhelper/util/paging/Page.java @@ -9,7 +9,7 @@ @AllArgsConstructor @NoArgsConstructor @Data -public class Page { +public final class Page { private long page; private long size; private long total; diff --git a/src/main/java/com/ghostchu/peerbanhelper/util/paging/Pageable.java b/src/main/java/com/ghostchu/peerbanhelper/util/paging/Pageable.java index a660ae6c20..1389ceecff 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/util/paging/Pageable.java +++ b/src/main/java/com/ghostchu/peerbanhelper/util/paging/Pageable.java @@ -4,7 +4,7 @@ import lombok.Data; @Data -public class Pageable { +public final class Pageable { private long page; private long size; diff --git a/src/main/java/com/ghostchu/peerbanhelper/util/rule/AbstractJsonMatcher.java b/src/main/java/com/ghostchu/peerbanhelper/util/rule/AbstractJsonMatcher.java index d4f264d5ec..140702b277 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/util/rule/AbstractJsonMatcher.java +++ b/src/main/java/com/ghostchu/peerbanhelper/util/rule/AbstractJsonMatcher.java @@ -1,6 +1,8 @@ package com.ghostchu.peerbanhelper.util.rule; import com.ghostchu.peerbanhelper.Main; +import com.ghostchu.peerbanhelper.text.Lang; +import com.ghostchu.peerbanhelper.text.TranslationComponent; import com.google.gson.JsonObject; import lombok.EqualsAndHashCode; import lombok.ToString; @@ -24,8 +26,11 @@ public AbstractJsonMatcher(JsonObject rule) { content = ""; } if (condition != null) { - if (condition.match(content) == MatchResult.FALSE) { - return MatchResult.FALSE; + if (condition.match(content).result() == MatchResultEnum.FALSE) { + return new MatchResult(MatchResultEnum.FALSE, + new TranslationComponent(Lang.JSON_MATCHER_NOT_MET, + condition.toPrintableText(Main.DEF_LOCALE), + toPrintableText(Main.DEF_LOCALE))); } } return match0(content); diff --git a/src/main/java/com/ghostchu/peerbanhelper/util/rule/MatchResult.java b/src/main/java/com/ghostchu/peerbanhelper/util/rule/MatchResult.java index 13cb3ccc33..f98a23a535 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/util/rule/MatchResult.java +++ b/src/main/java/com/ghostchu/peerbanhelper/util/rule/MatchResult.java @@ -1,7 +1,8 @@ package com.ghostchu.peerbanhelper.util.rule; -public enum MatchResult { - DEFAULT, - TRUE, - FALSE +import com.ghostchu.peerbanhelper.text.TranslationComponent; +import org.jetbrains.annotations.Nullable; + +public record MatchResult(MatchResultEnum result, @Nullable TranslationComponent comment) { + } diff --git a/src/main/java/com/ghostchu/peerbanhelper/util/rule/MatchResultEnum.java b/src/main/java/com/ghostchu/peerbanhelper/util/rule/MatchResultEnum.java new file mode 100644 index 0000000000..9728fdf0f3 --- /dev/null +++ b/src/main/java/com/ghostchu/peerbanhelper/util/rule/MatchResultEnum.java @@ -0,0 +1,7 @@ +package com.ghostchu.peerbanhelper.util.rule; + +public enum MatchResultEnum { + DEFAULT, + TRUE, + FALSE +} diff --git a/src/main/java/com/ghostchu/peerbanhelper/util/rule/RuleMatchResult.java b/src/main/java/com/ghostchu/peerbanhelper/util/rule/RuleMatchResult.java index 5e5ad129ae..1e8b3c1d75 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/util/rule/RuleMatchResult.java +++ b/src/main/java/com/ghostchu/peerbanhelper/util/rule/RuleMatchResult.java @@ -1,4 +1,7 @@ package com.ghostchu.peerbanhelper.util.rule; -public record RuleMatchResult(boolean hit, Rule rule) { +import com.ghostchu.peerbanhelper.text.TranslationComponent; +import org.jetbrains.annotations.Nullable; + +public record RuleMatchResult(boolean hit, Rule rule, @Nullable TranslationComponent comment) { } diff --git a/src/main/java/com/ghostchu/peerbanhelper/util/rule/RuleParser.java b/src/main/java/com/ghostchu/peerbanhelper/util/rule/RuleParser.java index bbd13f8743..c69ce9d8f3 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/util/rule/RuleParser.java +++ b/src/main/java/com/ghostchu/peerbanhelper/util/rule/RuleParser.java @@ -1,5 +1,7 @@ package com.ghostchu.peerbanhelper.util.rule; +import com.ghostchu.peerbanhelper.text.Lang; +import com.ghostchu.peerbanhelper.text.TranslationComponent; import com.ghostchu.peerbanhelper.util.rule.matcher.*; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -9,10 +11,9 @@ import org.jetbrains.annotations.NotNull; import java.util.List; -import java.util.Map; @Slf4j -public class RuleParser { +public final class RuleParser { public static List parse(List string) { return string.stream() .map(JsonParser::parseString) @@ -21,15 +22,16 @@ public static List parse(List string) { } public static RuleMatchResult matchRule(List rules, String content) { - RuleMatchResult matchResult = new RuleMatchResult(false, null); + RuleMatchResult matchResult = new RuleMatchResult(false, null, null); for (Rule rule : rules) { MatchResult result = rule.match(content); - if (result == MatchResult.FALSE) { // 规则的优先级最高 - return new RuleMatchResult(false, rule); + if (result.result() == MatchResultEnum.FALSE) { // 规则的优先级最高 + return new RuleMatchResult(false, rule, result.comment()); } - if (result == MatchResult.TRUE) { // 其次,可被覆盖 - matchResult = new RuleMatchResult(true, rule); + if (result.result() == MatchResultEnum.TRUE) { // 其次,可被覆盖 + matchResult = new RuleMatchResult(true, rule, result.comment()); } + } return matchResult; @@ -41,7 +43,7 @@ public static Rule parse(JsonElement element) { return new Rule() { @Override public @NotNull MatchResult match(@NotNull String content) { - return MatchResult.TRUE; + return new MatchResult(MatchResultEnum.TRUE, null); } @Override @@ -59,7 +61,7 @@ public String matcherIdentifier() { return new Rule() { @Override public @NotNull MatchResult match(@NotNull String content) { - return MatchResult.DEFAULT; + return new MatchResult(MatchResultEnum.TRUE, null); } @Override @@ -79,7 +81,7 @@ public String matcherIdentifier() { return new Rule() { @Override public @NotNull MatchResult match(@NotNull String content) { - return primitive.getAsBoolean() ? MatchResult.TRUE : MatchResult.FALSE; + return primitive.getAsBoolean() ? new MatchResult(MatchResultEnum.TRUE, new TranslationComponent(Lang.MATCH_CONDITION_BOOLEAN)) : new MatchResult(MatchResultEnum.FALSE, new TranslationComponent(Lang.MATCH_CONDITION_BOOLEAN)); } @Override @@ -97,7 +99,7 @@ public String matcherIdentifier() { return new Rule() { @Override public @NotNull MatchResult match(@NotNull String content) { - return primitive.getAsInt() != 0 ? MatchResult.TRUE : MatchResult.FALSE; + return primitive.getAsInt() != 0 ? new MatchResult(MatchResultEnum.TRUE, new TranslationComponent(Lang.MATCH_CONDITION_BOOLEAN_BY_INTEGER)) : new MatchResult(MatchResultEnum.FALSE, new TranslationComponent(Lang.MATCH_CONDITION_BOOLEAN_BY_INTEGER)); } @Override @@ -116,7 +118,7 @@ public String matcherIdentifier() { @Override public @NotNull MatchResult match(@NotNull String content) { String str = primitive.getAsString(); - return Boolean.parseBoolean(str) ? MatchResult.TRUE : MatchResult.FALSE; + return Boolean.parseBoolean(str) ? new MatchResult(MatchResultEnum.TRUE, new TranslationComponent(Lang.MATCH_CONDITION_BOOLEAN_BY_STRING)) : new MatchResult(MatchResultEnum.FALSE, new TranslationComponent(Lang.MATCH_CONDITION_BOOLEAN_BY_STRING)); } @Override diff --git a/src/main/java/com/ghostchu/peerbanhelper/util/rule/matcher/IPMatcher.java b/src/main/java/com/ghostchu/peerbanhelper/util/rule/matcher/IPMatcher.java index f9f9e4126a..bbd0e0775d 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/util/rule/matcher/IPMatcher.java +++ b/src/main/java/com/ghostchu/peerbanhelper/util/rule/matcher/IPMatcher.java @@ -4,9 +4,10 @@ import com.ghostchu.peerbanhelper.text.TranslationComponent; import com.ghostchu.peerbanhelper.util.IPAddressUtil; import com.ghostchu.peerbanhelper.util.rule.MatchResult; +import com.ghostchu.peerbanhelper.util.rule.MatchResultEnum; import com.ghostchu.peerbanhelper.util.rule.RuleMatcher; import inet.ipaddr.IPAddress; -import inet.ipaddr.format.util.DualIPv4v6Tries; +import inet.ipaddr.format.util.DualIPv4v6AssociativeTries; import lombok.EqualsAndHashCode; import lombok.ToString; import lombok.extern.slf4j.Slf4j; @@ -17,10 +18,10 @@ @Slf4j @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) -public class IPMatcher extends RuleMatcher { - private DualIPv4v6Tries ips; +public class IPMatcher extends RuleMatcher> { + private DualIPv4v6AssociativeTries ips; - public IPMatcher(String ruleId, String ruleName, List ruleData) { + public IPMatcher(String ruleId, String ruleName, List> ruleData) { super(ruleId, ruleName, ruleData); this.ips = ruleData.getFirst(); } @@ -31,7 +32,7 @@ public IPMatcher(String ruleId, String ruleName, List ruleData) * @param ruleName 规则名 * @param ruleData 规则数据 */ - public void setData(String ruleName, List ruleData) { + public void setData(String ruleName, List> ruleData) { setRuleName(ruleName); this.ips = ruleData.getFirst(); } @@ -43,14 +44,15 @@ public long size() { @Override public @NotNull MatchResult match0(@NotNull String content) { final IPAddress ip = IPAddressUtil.getIPAddress(content); - if (ip == null) return MatchResult.DEFAULT; + if (ip == null) return new MatchResult(MatchResultEnum.DEFAULT, new TranslationComponent("IP is null")); if (ips == null) { - return MatchResult.DEFAULT; + return new MatchResult(MatchResultEnum.DEFAULT, new TranslationComponent("IPs set is null")); } - if (ips.elementContains(ip)) { - return MatchResult.TRUE; + var node = ips.elementsContaining(ip); + if (node != null) { + return new MatchResult(MatchResultEnum.TRUE, new TranslationComponent(node.getValue())); } - return MatchResult.DEFAULT; + return new MatchResult(MatchResultEnum.DEFAULT, new TranslationComponent("Given IP not in IPs set")); } @Override diff --git a/src/main/java/com/ghostchu/peerbanhelper/util/rule/matcher/StringContainsMatcher.java b/src/main/java/com/ghostchu/peerbanhelper/util/rule/matcher/StringContainsMatcher.java index cbd200ae32..889c79234b 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/util/rule/matcher/StringContainsMatcher.java +++ b/src/main/java/com/ghostchu/peerbanhelper/util/rule/matcher/StringContainsMatcher.java @@ -4,30 +4,32 @@ import com.ghostchu.peerbanhelper.text.TranslationComponent; import com.ghostchu.peerbanhelper.util.rule.AbstractJsonMatcher; import com.ghostchu.peerbanhelper.util.rule.MatchResult; +import com.ghostchu.peerbanhelper.util.rule.MatchResultEnum; import com.google.gson.JsonObject; import lombok.EqualsAndHashCode; import lombok.ToString; import org.jetbrains.annotations.NotNull; import java.util.Locale; -import java.util.Map; @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) -public class StringContainsMatcher extends AbstractJsonMatcher { +public final class StringContainsMatcher extends AbstractJsonMatcher { private static final TranslationComponent nameComponent = new TranslationComponent(Lang.RULE_MATCHER_STRING_CONTAINS); private final String rule; - private MatchResult hit = MatchResult.TRUE; - private MatchResult miss = MatchResult.DEFAULT; + private MatchResult hit; + private MatchResult miss; public StringContainsMatcher(JsonObject syntax) { super(syntax); this.rule = syntax.get("content").getAsString().toLowerCase(Locale.ROOT); + this.hit = new MatchResult(MatchResultEnum.TRUE, new TranslationComponent(Lang.MATCH_STRING_CONTAINS, rule)); + this.miss = new MatchResult(MatchResultEnum.DEFAULT, new TranslationComponent(Lang.MATCH_STRING_CONTAINS, rule)); if (syntax.has("hit")) { - this.hit = MatchResult.valueOf(syntax.get("hit").getAsString()); + this.hit = new MatchResult(MatchResultEnum.valueOf(syntax.get("hit").getAsString()), new TranslationComponent(Lang.MATCH_STRING_CONTAINS, "Hit-" + rule)); } if (syntax.has("miss")) { - this.miss = MatchResult.valueOf(syntax.get("miss").getAsString()); + this.miss = new MatchResult(MatchResultEnum.valueOf(syntax.get("miss").getAsString()), new TranslationComponent(Lang.MATCH_STRING_CONTAINS, "Miss-" + rule)); } } @@ -43,7 +45,7 @@ public StringContainsMatcher(JsonObject syntax) { @Override public TranslationComponent matcherName() { - return nameComponent; + return new TranslationComponent(Lang.MATCH_STRING_CONTAINS, rule); } @Override diff --git a/src/main/java/com/ghostchu/peerbanhelper/util/rule/matcher/StringEndsWithMatcher.java b/src/main/java/com/ghostchu/peerbanhelper/util/rule/matcher/StringEndsWithMatcher.java index a4337e3818..535647f620 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/util/rule/matcher/StringEndsWithMatcher.java +++ b/src/main/java/com/ghostchu/peerbanhelper/util/rule/matcher/StringEndsWithMatcher.java @@ -4,30 +4,32 @@ import com.ghostchu.peerbanhelper.text.TranslationComponent; import com.ghostchu.peerbanhelper.util.rule.AbstractJsonMatcher; import com.ghostchu.peerbanhelper.util.rule.MatchResult; +import com.ghostchu.peerbanhelper.util.rule.MatchResultEnum; import com.google.gson.JsonObject; import lombok.EqualsAndHashCode; import lombok.ToString; import org.jetbrains.annotations.NotNull; import java.util.Locale; -import java.util.Map; @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) -public class StringEndsWithMatcher extends AbstractJsonMatcher { +public final class StringEndsWithMatcher extends AbstractJsonMatcher { private static final TranslationComponent nameComponent = new TranslationComponent(Lang.RULE_MATCHER_STRING_ENDS_WITH); private final String rule; - private MatchResult hit = MatchResult.TRUE; - private MatchResult miss = MatchResult.DEFAULT; + private MatchResult hit; + private MatchResult miss; public StringEndsWithMatcher(JsonObject syntax) { super(syntax); this.rule = syntax.get("content").getAsString().toLowerCase(Locale.ROOT); + this.hit = new MatchResult(MatchResultEnum.TRUE, new TranslationComponent(Lang.MATCH_STRING_ENDS_WITH, rule)); + this.miss = new MatchResult(MatchResultEnum.DEFAULT, new TranslationComponent(Lang.MATCH_STRING_ENDS_WITH, rule)); if (syntax.has("hit")) { - this.hit = MatchResult.valueOf(syntax.get("hit").getAsString()); + this.hit = new MatchResult(MatchResultEnum.valueOf(syntax.get("hit").getAsString()), new TranslationComponent(Lang.MATCH_STRING_ENDS_WITH, "Hit-" + rule)); } if (syntax.has("miss")) { - this.miss = MatchResult.valueOf(syntax.get("miss").getAsString()); + this.miss = new MatchResult(MatchResultEnum.valueOf(syntax.get("miss").getAsString()), new TranslationComponent(Lang.MATCH_STRING_ENDS_WITH, "Miss-" + rule)); } } @@ -43,7 +45,7 @@ public StringEndsWithMatcher(JsonObject syntax) { @Override public TranslationComponent matcherName() { - return nameComponent; + return new TranslationComponent(Lang.MATCH_STRING_ENDS_WITH, rule); } @Override diff --git a/src/main/java/com/ghostchu/peerbanhelper/util/rule/matcher/StringEqualsMatcher.java b/src/main/java/com/ghostchu/peerbanhelper/util/rule/matcher/StringEqualsMatcher.java index d7306bdceb..786da0031f 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/util/rule/matcher/StringEqualsMatcher.java +++ b/src/main/java/com/ghostchu/peerbanhelper/util/rule/matcher/StringEqualsMatcher.java @@ -4,29 +4,30 @@ import com.ghostchu.peerbanhelper.text.TranslationComponent; import com.ghostchu.peerbanhelper.util.rule.AbstractJsonMatcher; import com.ghostchu.peerbanhelper.util.rule.MatchResult; +import com.ghostchu.peerbanhelper.util.rule.MatchResultEnum; import com.google.gson.JsonObject; import lombok.EqualsAndHashCode; import lombok.ToString; import org.jetbrains.annotations.NotNull; -import java.util.Map; - @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) -public class StringEqualsMatcher extends AbstractJsonMatcher { +public final class StringEqualsMatcher extends AbstractJsonMatcher { private static final TranslationComponent nameComponent = new TranslationComponent(Lang.RULE_MATCHER_STRING_EQUALS); private final String rule; - private MatchResult hit = MatchResult.TRUE; - private MatchResult miss = MatchResult.DEFAULT; + private MatchResult hit; + private MatchResult miss; public StringEqualsMatcher(JsonObject syntax) { super(syntax); this.rule = syntax.get("content").getAsString(); + this.hit = new MatchResult(MatchResultEnum.TRUE, new TranslationComponent(Lang.MATCH_STRING_EQUALS, rule)); + this.miss = new MatchResult(MatchResultEnum.DEFAULT, new TranslationComponent(Lang.MATCH_STRING_EQUALS, rule)); if (syntax.has("hit")) { - this.hit = MatchResult.valueOf(syntax.get("hit").getAsString()); + this.hit = new MatchResult(MatchResultEnum.valueOf(syntax.get("hit").getAsString()), new TranslationComponent(Lang.MATCH_STRING_EQUALS, "Hit-" + rule)); } if (syntax.has("miss")) { - this.miss = MatchResult.valueOf(syntax.get("miss").getAsString()); + this.miss = new MatchResult(MatchResultEnum.valueOf(syntax.get("miss").getAsString()), new TranslationComponent(Lang.MATCH_STRING_EQUALS, "Miss-" + rule)); } } @@ -41,7 +42,7 @@ public StringEqualsMatcher(JsonObject syntax) { @Override public TranslationComponent matcherName() { - return nameComponent; + return new TranslationComponent(Lang.MATCH_STRING_EQUALS, rule); } @Override diff --git a/src/main/java/com/ghostchu/peerbanhelper/util/rule/matcher/StringLengthMatcher.java b/src/main/java/com/ghostchu/peerbanhelper/util/rule/matcher/StringLengthMatcher.java index c740bb219e..52bb45929a 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/util/rule/matcher/StringLengthMatcher.java +++ b/src/main/java/com/ghostchu/peerbanhelper/util/rule/matcher/StringLengthMatcher.java @@ -4,31 +4,32 @@ import com.ghostchu.peerbanhelper.text.TranslationComponent; import com.ghostchu.peerbanhelper.util.rule.AbstractJsonMatcher; import com.ghostchu.peerbanhelper.util.rule.MatchResult; +import com.ghostchu.peerbanhelper.util.rule.MatchResultEnum; import com.google.gson.JsonObject; import lombok.EqualsAndHashCode; import lombok.ToString; import org.jetbrains.annotations.NotNull; -import java.util.Map; - @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) -public class StringLengthMatcher extends AbstractJsonMatcher { +public final class StringLengthMatcher extends AbstractJsonMatcher { private static final TranslationComponent nameComponent = new TranslationComponent(Lang.RULE_MATCHER_STRING_LENGTH); private final int min; private final int max; - private MatchResult hit = MatchResult.TRUE; - private MatchResult miss = MatchResult.DEFAULT; + private MatchResult hit; + private MatchResult miss; public StringLengthMatcher(JsonObject syntax) { super(syntax); this.min = syntax.get("min").getAsInt(); this.max = syntax.get("max").getAsInt(); + this.hit = new MatchResult(MatchResultEnum.TRUE, new TranslationComponent(Lang.MATCH_STRING_LENGTH, "Hit-Min-" + min + ", Max-" + max)); + this.miss = new MatchResult(MatchResultEnum.DEFAULT, new TranslationComponent(Lang.MATCH_STRING_LENGTH, "Hit-Min-" + min + ", Max-" + max)); if (syntax.has("hit")) { - this.hit = MatchResult.valueOf(syntax.get("hit").getAsString()); + this.hit = new MatchResult(MatchResultEnum.valueOf(syntax.get("hit").getAsString()), new TranslationComponent(Lang.MATCH_STRING_LENGTH, "Hit-Min-" + min + ", Max-" + max)); } if (syntax.has("miss")) { - this.miss = MatchResult.valueOf(syntax.get("miss").getAsString()); + this.miss = new MatchResult(MatchResultEnum.valueOf(syntax.get("miss").getAsString()), new TranslationComponent(Lang.MATCH_STRING_LENGTH, "Miss-Min-" + min + ", Max-" + max)); } } @@ -40,7 +41,7 @@ public StringLengthMatcher(JsonObject syntax) { @Override public TranslationComponent matcherName() { - return nameComponent; + return new TranslationComponent(Lang.MATCH_STRING_LENGTH, "Min-" + min + ", Max-" + max); } @Override diff --git a/src/main/java/com/ghostchu/peerbanhelper/util/rule/matcher/StringRegexMatcher.java b/src/main/java/com/ghostchu/peerbanhelper/util/rule/matcher/StringRegexMatcher.java index 116bc5f746..7f364010f6 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/util/rule/matcher/StringRegexMatcher.java +++ b/src/main/java/com/ghostchu/peerbanhelper/util/rule/matcher/StringRegexMatcher.java @@ -4,30 +4,32 @@ import com.ghostchu.peerbanhelper.text.TranslationComponent; import com.ghostchu.peerbanhelper.util.rule.AbstractJsonMatcher; import com.ghostchu.peerbanhelper.util.rule.MatchResult; +import com.ghostchu.peerbanhelper.util.rule.MatchResultEnum; import com.google.gson.JsonObject; import lombok.EqualsAndHashCode; import lombok.ToString; import org.jetbrains.annotations.NotNull; -import java.util.Map; import java.util.regex.Pattern; @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) -public class StringRegexMatcher extends AbstractJsonMatcher { +public final class StringRegexMatcher extends AbstractJsonMatcher { private static final TranslationComponent nameComponent = new TranslationComponent(Lang.RULE_MATCHER_STRING_REGEX); private final Pattern rule; - private MatchResult hit = MatchResult.TRUE; - private MatchResult miss = MatchResult.DEFAULT; + private MatchResult hit; + private MatchResult miss; public StringRegexMatcher(JsonObject syntax) { super(syntax); this.rule = Pattern.compile(syntax.get("content").getAsString()); + this.hit = new MatchResult(MatchResultEnum.TRUE, new TranslationComponent(Lang.MATCH_STRING_REGEX, rule)); + this.miss = new MatchResult(MatchResultEnum.DEFAULT, new TranslationComponent(Lang.MATCH_STRING_REGEX, rule)); if (syntax.has("hit")) { - this.hit = MatchResult.valueOf(syntax.get("hit").getAsString()); + this.hit = new MatchResult(MatchResultEnum.valueOf(syntax.get("hit").getAsString()), new TranslationComponent(Lang.MATCH_STRING_REGEX, "Hit=" + rule.pattern())); } if (syntax.has("miss")) { - this.miss = MatchResult.valueOf(syntax.get("miss").getAsString()); + this.miss = new MatchResult(MatchResultEnum.valueOf(syntax.get("miss").getAsString()), new TranslationComponent(Lang.MATCH_STRING_REGEX, "Miss=" + rule.pattern())); } } @@ -47,7 +49,7 @@ public String matcherIdentifier() { @Override public TranslationComponent matcherName() { - return nameComponent; + return new TranslationComponent(Lang.MATCH_STRING_REGEX, rule); } @Override diff --git a/src/main/java/com/ghostchu/peerbanhelper/util/rule/matcher/StringStartsWithMatcher.java b/src/main/java/com/ghostchu/peerbanhelper/util/rule/matcher/StringStartsWithMatcher.java index 82b179c15c..0622728fdb 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/util/rule/matcher/StringStartsWithMatcher.java +++ b/src/main/java/com/ghostchu/peerbanhelper/util/rule/matcher/StringStartsWithMatcher.java @@ -4,30 +4,32 @@ import com.ghostchu.peerbanhelper.text.TranslationComponent; import com.ghostchu.peerbanhelper.util.rule.AbstractJsonMatcher; import com.ghostchu.peerbanhelper.util.rule.MatchResult; +import com.ghostchu.peerbanhelper.util.rule.MatchResultEnum; import com.google.gson.JsonObject; import lombok.EqualsAndHashCode; import lombok.ToString; import org.jetbrains.annotations.NotNull; import java.util.Locale; -import java.util.Map; @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) -public class StringStartsWithMatcher extends AbstractJsonMatcher { +public final class StringStartsWithMatcher extends AbstractJsonMatcher { private static final TranslationComponent nameComponent = new TranslationComponent(Lang.RULE_MATCHER_STRING_STARTS_WITH); private final String rule; - private MatchResult hit = MatchResult.TRUE; - private MatchResult miss = MatchResult.DEFAULT; + private MatchResult hit; + private MatchResult miss; public StringStartsWithMatcher(JsonObject syntax) { super(syntax); this.rule = syntax.get("content").getAsString().toLowerCase(Locale.ROOT); + this.hit = new MatchResult(MatchResultEnum.TRUE, new TranslationComponent(Lang.MATCH_STRING_STARTS_WITH, rule)); + this.miss = new MatchResult(MatchResultEnum.DEFAULT, new TranslationComponent(Lang.MATCH_STRING_STARTS_WITH, rule)); if (syntax.has("hit")) { - this.hit = MatchResult.valueOf(syntax.get("hit").getAsString()); + this.hit = new MatchResult(MatchResultEnum.valueOf(syntax.get("hit").getAsString()), new TranslationComponent(Lang.MATCH_STRING_STARTS_WITH, "Hit-" + rule)); } if (syntax.has("miss")) { - this.miss = MatchResult.valueOf(syntax.get("miss").getAsString()); + this.miss = new MatchResult(MatchResultEnum.valueOf(syntax.get("miss").getAsString()), new TranslationComponent(Lang.MATCH_STRING_STARTS_WITH, "Hit-" + rule)); } } @@ -49,11 +51,20 @@ public String matcherIdentifier() { @Override public TranslationComponent matcherName() { - return nameComponent; + return new TranslationComponent(Lang.MATCH_STRING_STARTS_WITH, rule); } @Override public String metadata() { return rule; } + + @Override + public String toString() { + return "StringStartsWithMatcher{" + + "rule='" + rule + '\'' + + ", hit=" + hit + + ", miss=" + miss + + '}'; + } } diff --git a/src/main/java/com/ghostchu/peerbanhelper/util/time/TimeoutProtect.java b/src/main/java/com/ghostchu/peerbanhelper/util/time/TimeoutProtect.java index dc0e111af9..0b9f4f573e 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/util/time/TimeoutProtect.java +++ b/src/main/java/com/ghostchu/peerbanhelper/util/time/TimeoutProtect.java @@ -12,7 +12,7 @@ import static com.ghostchu.peerbanhelper.text.TextManager.tlUI; @Slf4j -public class TimeoutProtect implements AutoCloseable { +public final class TimeoutProtect implements AutoCloseable { @Getter private final ExecutorService service; @Getter diff --git a/src/main/java/com/ghostchu/peerbanhelper/web/exception/IPAddressBannedException.java b/src/main/java/com/ghostchu/peerbanhelper/web/exception/IPAddressBannedException.java index 1933545663..b261b31b7f 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/web/exception/IPAddressBannedException.java +++ b/src/main/java/com/ghostchu/peerbanhelper/web/exception/IPAddressBannedException.java @@ -1,4 +1,4 @@ package com.ghostchu.peerbanhelper.web.exception; -public class IPAddressBannedException extends Exception { +public final class IPAddressBannedException extends Exception { } diff --git a/src/main/java/com/ghostchu/peerbanhelper/web/exception/NotLoggedInException.java b/src/main/java/com/ghostchu/peerbanhelper/web/exception/NotLoggedInException.java index 4fe3e40fd4..47dafa7122 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/web/exception/NotLoggedInException.java +++ b/src/main/java/com/ghostchu/peerbanhelper/web/exception/NotLoggedInException.java @@ -1,5 +1,5 @@ package com.ghostchu.peerbanhelper.web.exception; -public class NotLoggedInException extends Exception { +public final class NotLoggedInException extends Exception { } diff --git a/src/main/java/com/ghostchu/peerbanhelper/wrapper/BanMetadata.java b/src/main/java/com/ghostchu/peerbanhelper/wrapper/BanMetadata.java index 7c5c40629f..d62b15b87f 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/wrapper/BanMetadata.java +++ b/src/main/java/com/ghostchu/peerbanhelper/wrapper/BanMetadata.java @@ -9,6 +9,8 @@ import java.io.Serializable; +import static com.ghostchu.peerbanhelper.text.TextManager.tlUI; + @EqualsAndHashCode(callSuper = true) @NoArgsConstructor @Data @@ -42,5 +44,15 @@ public BanMetadata(String context, String downloader, long banAt, long unbanAt, this.description = description; } - + @Override + public String toString() { + return "BanMetadata{" + + "context='" + context + '\'' + + ", banAt=" + banAt + + ", unbanAt=" + unbanAt + + ", banForDisconnect=" + banForDisconnect + + ", rule=" + tlUI(rule) + + ", description=" + tlUI(description) + + '}'; + } } diff --git a/src/main/resources/assets/other/Neuro.png b/src/main/resources/assets/other/Neuro.png new file mode 100644 index 0000000000..7d1b75a665 Binary files /dev/null and b/src/main/resources/assets/other/Neuro.png differ diff --git a/src/main/resources/assets/other/Neuro.txt b/src/main/resources/assets/other/Neuro.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 2bab58397c..039bfc7baf 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,4 +1,4 @@ -config-version: 26 +config-version: 27 # 设置程序语言 # Set the program language # default 跟随操作系统 (Follow the operating system) @@ -54,6 +54,13 @@ persist: # 您是否想要持久化保存封禁列表?如果不持久化,重启 PBH 将重置封禁列表。 # Do you want persist the banlist? If you disable it, PBH will reset the banlist when you restarting it. banlist: true + # 数据库维护操作间隔时间,单位:天 + # 定时维护数据库将可以提升性能,并释放未使用的空间;过于频繁的维护可能会导致嵌入式设备的闪存磨损 + # 此操作仅在 PeerBanHelper 启动时才会检查是否需要执行 + # Database maintenance interval, time unit: days + # Regular maintenance will improve performance and release unused space; too frequent maintenance may cause flash wear on embedded devices + # This operation only check and execute when PeerBanHelper starting + vacuum-interval-days: 60 # BitTorrent Threat Network 威胁防护网络(测试版) # BitTorrent Threat Network (BETA) btn: diff --git a/src/main/resources/lang/en_us/messages.yml b/src/main/resources/lang/en_us/messages.yml index ae51683f33..14d6c2c509 100644 --- a/src/main/resources/lang/en_us/messages.yml +++ b/src/main/resources/lang/en_us/messages.yml @@ -18,7 +18,7 @@ CHECK_COMPLETED: "[Completed] Checked {} downloaders with {} active torrents and ERR_INVALID_RULE_SYNTAX: "The expression of rule {} is invalid, please check for spelling errors" MODULE_CNB_MATCH_CLIENT_NAME: "Match ClientName (UserAgent): {}" MODULE_IBL_MATCH_IP: "Match IP rule: {}" -MODULE_IBL_MATCH_IP_RULE: "Match IP blacklist subscription rule: {}, IP address: {}" +MODULE_IBL_MATCH_IP_RULE: "Match IP blacklist subscription rule: {}, IP address: {}, Comment: {}" MODULE_IBL_MATCH_ASN: "Match ASN rule: {}" MODULE_IBL_MATCH_REGION: "Match country or city ISO code rule: {}" MODULE_IBL_MATCH_CITY: "Match city rule: {}" @@ -199,6 +199,7 @@ STATUS_TEXT_OK: "Currently working normally" STATUS_TEXT_LOGIN_FAILED: "Attempt to log in to downloader failed" STATUS_TEXT_EXCEPTION: "An exception occurred, please check PeerBanHelper console: {}" STATUS_TEXT_NEED_PRIVILEGE: "Insufficient privileges, request privilege escalation (run as administrator/root)" +STATUS_TEXT_PAUSED: "PeerBanHelper won't check paused downloader, please cancel the pausing" SUGGEST_FIREWALL_IPTABLES: "Native iptables are not recommended, may cause network performance degradation. Consider installing ipset instead of using iptables" SUGGEST_FIREWALL_FIREWALLD: "Native firewalld is not recommended, may cause network performance degradation. Consider installing ipset instead of using firewalld" SUGGEST_FIREWALL_WINDOWS_FIREWALL_DISABLED: "Windows Firewall is currently disabled, please enable Windows Firewall for 'Public Network' and 'Private Network' to ensure system firewall integration works" @@ -535,3 +536,34 @@ FREE_LICENSE_RENEW_STILL_ACTIVE: "The current license is still active and does n FREE_LICENSE_SOURCE: "Locally generated free license." FREE_LICENSE_LICENSE_TO: "{} for personal use." FREE_LICENSE_DESCRIPTION: "Local generated renewable free license, for personal use only." +DOWNLOADER_PAUSED: Downloader paused +LAB_EXPERIMENT_DNSJAVA_TITLE: "DNSJava DNS" +LAB_EXPERIMENT_DNSJAVA_DESCRIPTION: "Use [dnsjava/dnsjava](https://github.com/dnsjava/dnsjava) to replace the default JDK DNS resolver in some modules, providing more reliable hostname and reverse DNS lookup capabilities, and supporting custom DNS servers and load balancing. DNSJava only works for certain functions (such as reverse DNS lookup), while other functions will continue to use JDK DNS. \nAfter enabling this experiment, you can configure the DNS server addresses for DNSJava in the configuration file." +LAB_EXPERIMENT_SQLITE_VACUUM_TITLE: "SQLite VACUUM Optimization" +LAB_EXPERIMENT_SQLITE_VACUUM_DESCRIPTION: "PeerBanHelper will check the SQLite data file for vacuuming (VACUUM) when starting up, and will perform a vacuum operation every 60 days by default (you can modify `persist.vacuum-interval-days` to change the vacuum interval). The vacuum operation will optimize the database file, reduce the file size, and improve query performance at the same time." +SQLITE_VACUUM_BACKUP: "Backing up SQLite database file for vacuum operation..." +SQLITE_VACUUM_BACKUP_COMPLETED: "SQLite database file backup completed, preparing for vacuum..." +SQLITE_VACUUM_BACKUP_FAILED: "SQLite database file backup failed, unable to perform vacuum operation. This may be due to insufficient disk space, IO errors, or insufficient permissions. Please check the error message." +SQLITE_VACUUM_IN_PROGRESS: "Vacuuming SQLite database in progress, do not close PeerBanHelper as it may cause database corruption! [In Progress]...." +SQLITE_VACUUM_SUCCESS: "SQLite database vacuumed successfully, original file size: {} vacuumed file size: {}" +MODULE_PTR_MATCH_PTR_RULE: "Match PTR rule: {}" + +MATCH_CONDITION_PORT_MATCH: "Port Match" +MATCH_CONDITION_BOOLEAN: "Boolean" +MATCH_CONDITION_BOOLEAN_BY_INTEGER: "Boolean (by Integer)" +MATCH_CONDITION_BOOLEAN_BY_STRING: "Boolean (by String)" +MATCH_STRING_CONTAINS: "String Contains: {}" +MATCH_STRING_STARTS_WITH: "String StartsWith: {}" +MATCH_STRING_ENDS_WITH: "String EndsWith: {}" +MATCH_STRING_EQUALS: "String Equals: {}" +MATCH_STRING_LENGTH: "String Length: {}" +MATCH_STRING_REGEX: "String Regex: {}" +MODULE_IBL_COMMENT_UNKNOWN: "Not Provided" +JSON_MATCHER_NOT_MET: "Condition not meet: {} on {}" +DOWNLOADER_API_TEST_BYPASS_PAUSED: "Download configuration drafted, please enable later" +STATUS_BAR_GLOBAL_PAUSED: "⏸Global Paused" +BTN_CONFIG_STATUS_UNSUCCESSFUL_HTTP_REQUEST: "Failed to send HTTP request to URL {}, HTTP status code: {}, remote response: {}" +BTN_CONFIG_STATUS_UNSUCCESSFUL_INCOMPATIBLE_BTN_PROTOCOL_VERSION_CLIENT: "The current client BTN protocol implementation version is too old, local implementation version: {}, server requires at least: {}" +BTN_CONFIG_STATUS_UNSUCCESSFUL_INCOMPATIBLE_BTN_PROTOCOL_VERSION_SERVER: "The current server BTN protocol implementation version is too old, server implementation version: {}, local requires at least: {}" +BTN_CONFIG_STATUS_SUCCESSFUL: "Successfully connected to the BTN server" +BTN_CONFIG_STATUS_EXCEPTION: "An unexpected error occurred while connecting to the BTN server: {}" \ No newline at end of file diff --git a/src/main/resources/lang/messages_fallback.yml b/src/main/resources/lang/messages_fallback.yml index 1529036a53..0ce93412ab 100644 --- a/src/main/resources/lang/messages_fallback.yml +++ b/src/main/resources/lang/messages_fallback.yml @@ -18,7 +18,7 @@ CHECK_COMPLETED: "[完成] 已检查 {} 的 {} 个活跃 Torrent 和 {} 个对 ERR_INVALID_RULE_SYNTAX: "规则 {} 的表达式无效,请检查是否存在拼写错误" MODULE_CNB_MATCH_CLIENT_NAME: "匹配 ClientName (UserAgent): {}" MODULE_IBL_MATCH_IP: "匹配 IP 规则: {}" -MODULE_IBL_MATCH_IP_RULE: "匹配 IP黑名单订阅 规则: {}, IP 地址: {}" +MODULE_IBL_MATCH_IP_RULE: "匹配 IP黑名单订阅 规则: {}, IP 地址: {}, 备注: {}" MODULE_IBL_MATCH_ASN: "匹配 ASN 规则: {}" MODULE_IBL_MATCH_REGION: "匹配国家或地区 ISO 代码规则: {}" MODULE_IBL_EXCEPTION_GEOIP: "匹配 GeoIP 信息时出现异常,请反馈错误给开发者" @@ -199,6 +199,7 @@ STATUS_TEXT_OK: "当前工作正常" STATUS_TEXT_LOGIN_FAILED: "尝试登陆到下载器失败" STATUS_TEXT_EXCEPTION: "出现异常,请检查 PeerBanHelper 控制台: {}" STATUS_TEXT_NEED_PRIVILEGE: "权限不足,请求权限提升(以管理员/root身份运行)" +STATUS_TEXT_PAUSED: "PeerBanHelper 不会检查已暂停的下载器,请取消暂停" SUGGEST_FIREWALL_IPTABELS: "不推荐使用原生 iptables,可能引起网络性能下降。请考虑安装 ipset 代替使用 iptabels" SUGGEST_FIREWALL_FIREWALLD: "不推荐使用原生 firewalld,可能引起网络性能下降。请考虑安装 ipset 代替使用 firewalld" SUGGEST_FIREWALL_WINDOWS_FIREWALL_DISABLED: "Windows 防火墙目前处于禁用状态,请为 “公用网络” 和 “专用网络” 打开 Windows 防火墙,否则系统防火墙集成将不起作用" @@ -534,4 +535,35 @@ FREE_LICENSE_RENEW_STILL_ACTIVE: "当前许可证仍在有效期内,无需重 FREE_LICENSE_SOURCE: "本地生成的免费许可证" FREE_LICENSE_LICENSE_TO: "{} 用于个人用途" FREE_LICENSE_DESCRIPTION: "本地生成的循环免费许可证,仅用于个人用途" -DOWNLOADER_QB_DISABLE_SAME_IP_MULTI_CONNECTION_FAILED: "禁用 {} ({}) 的高级设置 “允许来自不同 IP 地址的多重连接” 失败:{} - {}!此功能必须关闭,否则将导致 PCB 封禁误判。" \ No newline at end of file +DOWNLOADER_QB_DISABLE_SAME_IP_MULTI_CONNECTION_FAILED: "禁用 {} ({}) 的高级设置 “允许来自不同 IP 地址的多重连接” 失败:{} - {}!此功能必须关闭,否则将导致 PCB 封禁误判。" +DOWNLOADER_PAUSED: "下载器已暂停" +LAB_EXPERIMENT_DNSJAVA_TITLE: "DNSJava DNS 解析" +LAB_EXPERIMENT_DNSJAVA_DESCRIPTION: "在部分功能模块中使用 [dnsjava/dnsjava](https://github.com/dnsjava/dnsjava) 替代 JDK 默认的 DNS 解析器,以提供更可靠的主机名和反向 DNS 查询能力,并支持自定义 DNS 服务器和负载均衡的能力。DNSJava 仅在部分功能上工作(如 DNS 反查),其它功能将继续使用 JDK DNS。 \n启用此实验后,您可以在配置文件中配置 DNSJava 的 DNS 服务器地址。" +LAB_EXPERIMENT_SQLITE_VACUUM_TITLE: "SQLite VACUUM 数据库优化" +LAB_EXPERIMENT_SQLITE_VACUUM_DESCRIPTION: "PeerBanHelper 将在启动时检查 SQLite 数据文件是否需要真空(VACUUM),默认每 60 天进行一次真空操作(可修改 `persist.vacuum-interval-days` 来改变真空间隔时间)。真空操作将优化数据库文件并减少文件大小,并同时提高查询性能。" +SQLITE_VACUUM_BACKUP: "正在备份 SQLite 数据库文件以便对数据库进行真空操作……" +SQLITE_VACUUM_BACKUP_COMPLETED: "SQLite 数据库文件备份完成,准备进行真空……" +SQLITE_VACUUM_BACKUP_FAILED: "SQLite 数据库文件备份失败,无法进行真空操作,这可能是由于磁盘空间不足,IO 错误或者权限不足导致的,请检查错误信息。" +SQLITE_VACUUM_IN_PROGRESS: "正在对 SQLite 数据库进行真空操作,请勿关闭 PeerBanHelper 否则将可能导致数据库损坏! 【进行中】...." +SQLITE_VACUUM_SUCCESS: "SQLite 数据库已成功真空,原文件大小: {} 真空后文件大小: {}" +MODULE_PTR_MATCH_PTR_RULE: "匹配 PTR 规则: {}" + +MATCH_CONDITION_PORT_MATCH: "端口匹配" +MATCH_CONDITION_BOOLEAN: "布尔值" +MATCH_CONDITION_BOOLEAN_BY_INTEGER: "布尔值 (整数转换)" +MATCH_CONDITION_BOOLEAN_BY_STRING: "布尔值 (字符串转换)" +MATCH_STRING_CONTAINS: "字符串包含: {}" +MATCH_STRING_STARTS_WITH: "字符串开头: {}" +MATCH_STRING_ENDS_WITH: "字符串结尾: {}" +MATCH_STRING_EQUALS: "字符串匹配: {}" +MATCH_STRING_LENGTH: "字符串长度: {}" +MATCH_STRING_REGEX: "字符串正则表达式: {}" +MODULE_IBL_COMMENT_UNKNOWN: "未提供" +JSON_MATCHER_NOT_MET: "表达式未满足: {} 于 {} 上" +DOWNLOADER_API_TEST_BYPASS_PAUSED: "下载器配置已暂存,请在稍后启用" +STATUS_BAR_GLOBAL_PAUSED: "⏸全局暂停" +BTN_CONFIG_STATUS_UNSUCCESSFUL_HTTP_REQUEST: "向 URL {} 发送 HTTP 请求失败,HTTP 状态码: {},远端响应:{}" +BTN_CONFIG_STATUS_UNSUCCESSFUL_INCOMPATIBLE_BTN_PROTOCOL_VERSION_CLIENT: "当前客户端 BTN 协议实现版本过旧,本地实现版本号: {},服务器要求最低: {}" +BTN_CONFIG_STATUS_UNSUCCESSFUL_INCOMPATIBLE_BTN_PROTOCOL_VERSION_SERVER: "当前服务器 BTN 协议实现版本过旧,服务器实现版本号: {},本地要求最低: {}" +BTN_CONFIG_STATUS_SUCCESSFUL: "已成功连接到 BTN 服务器" +BTN_CONFIG_STATUS_EXCEPTION: "配置 BTN 功能时发生意外错误:{}: {}" \ No newline at end of file diff --git a/src/main/resources/lang/zh_cn/messages.yml b/src/main/resources/lang/zh_cn/messages.yml index 624a20df67..a284c7a363 100644 --- a/src/main/resources/lang/zh_cn/messages.yml +++ b/src/main/resources/lang/zh_cn/messages.yml @@ -18,7 +18,7 @@ CHECK_COMPLETED: "[完成] 已检查 {} 的 {} 个活跃 Torrent 和 {} 个对 ERR_INVALID_RULE_SYNTAX: "规则 {} 的表达式无效,请检查是否存在拼写错误" MODULE_CNB_MATCH_CLIENT_NAME: "匹配 ClientName (UserAgent): {}" MODULE_IBL_MATCH_IP: "匹配 IP 规则: {}" -MODULE_IBL_MATCH_IP_RULE: "匹配 IP黑名单订阅 规则: {}, IP 地址: {}" +MODULE_IBL_MATCH_IP_RULE: "匹配 IP黑名单订阅 规则: {}, IP 地址: {}, 备注: {}" MODULE_IBL_MATCH_ASN: "匹配 ASN 规则: {}" MODULE_IBL_MATCH_REGION: "匹配国家或地区 ISO 代码规则: {}" MODULE_IBL_EXCEPTION_GEOIP: "匹配 GeoIP 信息时出现异常,请反馈错误给开发者" @@ -96,6 +96,7 @@ BTN_SHUTTING_DOWN: "[BTN 网络] 正在关闭 BTN 模块……" BTN_RECONFIGURING: "[BTN 网络] 发现服务器基本配置更新,正在重新配置 BTN 网络模块……" RULE_MATCHER_STRING_CONTAINS: "子串匹配" RULE_MATCHER_STRING_ENDS_WITH: "匹配结尾" +MATCH_STRING_STARTS_WITH: "字符串开头: {}" RULE_MATCHER_STRING_STARTS_WITH: "匹配开头" RULE_MATCHER_STRING_LENGTH: "匹配长度" RULE_MATCHER_STRING_REGEX: "匹配正则" @@ -305,6 +306,7 @@ NET_TYPE_DATACENTER: "数据中心" WEBUI_VALIDATION_DOWNLOAD_LOGIN_FAILED: "配置有效,但无法连接到下载器,请检查日志" STATUS_TEXT_UNKNOWN: "PeerBanHelper 暂时还未获取到此下载器的信息" +STATUS_TEXT_PAUSED: "PeerBanHelper 不会检查已暂停的下载器,请取消暂停" DOWNLOADER_LOGIN_EXCEPTION: "无法连接到下载器,登录时出现错误:{}" DOWNLOADER_LOGIN_IO_EXCEPTION: "无法连接到下载器,登录时出现网络错误:{}" DOWNLOADER_LOGIN_INCORRECT_CRED: "鉴权失败,错误的登录凭据。请检查用户名、密码或 Token 是否正确。" @@ -531,4 +533,34 @@ FREE_LICENSE_RENEW_SUCCESS: "新的许可证已生成并激活" FREE_LICENSE_RENEW_STILL_ACTIVE: "当前许可证仍在有效期内,无需重新生成" FREE_LICENSE_SOURCE: "本地生成的免费许可证" FREE_LICENSE_LICENSE_TO: "{} 用于个人用途" -FREE_LICENSE_DESCRIPTION: "本地生成的循环免费许可证,仅用于个人用途" \ No newline at end of file +FREE_LICENSE_DESCRIPTION: "本地生成的循环免费许可证,仅用于个人用途" +DOWNLOADER_PAUSED: "下载器已暂停" +LAB_EXPERIMENT_DNSJAVA_TITLE: "DNSJava DNS 解析" +LAB_EXPERIMENT_DNSJAVA_DESCRIPTION: "在部分功能模块中使用 [dnsjava/dnsjava](https://github.com/dnsjava/dnsjava) 替代 JDK 默认的 DNS 解析器,以提供更可靠的主机名和反向 DNS 查询能力,并支持自定义 DNS 服务器和负载均衡的能力。DNSJava 仅在部分功能上工作(如 DNS 反查),其它功能将继续使用 JDK DNS。 \n启用此实验后,您可以在配置文件中配置 DNSJava 的 DNS 服务器地址。" +LAB_EXPERIMENT_SQLITE_VACUUM_TITLE: "SQLite VACUUM 数据库优化" +LAB_EXPERIMENT_SQLITE_VACUUM_DESCRIPTION: "PeerBanHelper 将在启动时检查 SQLite 数据文件是否需要真空(VACUUM),默认每 60 天进行一次真空操作(可修改 `persist.vacuum-interval-days` 来改变真空间隔时间)。真空操作将优化数据库文件并减少文件大小,并同时提高查询性能。" +SQLITE_VACUUM_BACKUP: "正在备份 SQLite 数据库文件以便对数据库进行真空操作……" +SQLITE_VACUUM_BACKUP_COMPLETED: "SQLite 数据库文件备份完成,准备进行真空……" +SQLITE_VACUUM_BACKUP_FAILED: "SQLite 数据库文件备份失败,无法进行真空操作,这可能是由于磁盘空间不足,IO 错误或者权限不足导致的,请检查错误信息。" +SQLITE_VACUUM_IN_PROGRESS: "正在对 SQLite 数据库进行真空操作,请勿关闭 PeerBanHelper 否则将可能导致数据库损坏! 【进行中】...." +SQLITE_VACUUM_SUCCESS: "SQLite 数据库已成功真空,原文件大小: {} 真空后文件大小: {}" +MODULE_PTR_MATCH_PTR_RULE: "匹配 PTR 规则: {}" + +MATCH_CONDITION_PORT_MATCH: "端口匹配" +MATCH_CONDITION_BOOLEAN: "布尔值" +MATCH_CONDITION_BOOLEAN_BY_INTEGER: "布尔值 (整数转换)" +MATCH_CONDITION_BOOLEAN_BY_STRING: "布尔值 (字符串转换)" +MATCH_STRING_CONTAINS: "字符串包含: {}" +MATCH_STRING_ENDS_WITH: "字符串结尾: {}" +MATCH_STRING_EQUALS: "字符串匹配: {}" +MATCH_STRING_LENGTH: "字符串长度: {}" +MATCH_STRING_REGEX: "字符串正则表达式: {}" +MODULE_IBL_COMMENT_UNKNOWN: "未提供" +JSON_MATCHER_NOT_MET: "表达式未满足: {} 于 {} 上" +DOWNLOADER_API_TEST_BYPASS_PAUSED: "下载器配置已暂存,请在稍后启用" +STATUS_BAR_GLOBAL_PAUSED: "⏸全局暂停" +BTN_CONFIG_STATUS_UNSUCCESSFUL_HTTP_REQUEST: "向 URL {} 发送 HTTP 请求失败,HTTP 状态码: {},远端响应:{}" +BTN_CONFIG_STATUS_UNSUCCESSFUL_INCOMPATIBLE_BTN_PROTOCOL_VERSION_CLIENT: "当前客户端 BTN 协议实现版本过旧,本地实现版本号: {},服务器要求最低: {}" +BTN_CONFIG_STATUS_UNSUCCESSFUL_INCOMPATIBLE_BTN_PROTOCOL_VERSION_SERVER: "当前服务器 BTN 协议实现版本过旧,服务器实现版本号: {},本地要求最低: {}" +BTN_CONFIG_STATUS_SUCCESSFUL: "已成功连接到 BTN 服务器" +BTN_CONFIG_STATUS_EXCEPTION: "配置 BTN 功能时发生意外错误:{}: {}" \ No newline at end of file diff --git a/src/main/resources/profile.yml b/src/main/resources/profile.yml index cf48d18458..8b4d13a531 100644 --- a/src/main/resources/profile.yml +++ b/src/main/resources/profile.yml @@ -1,4 +1,4 @@ -config-version: 22 +config-version: 23 # Check interval (Timeunit: ms) # 检查频率(单位:毫秒) check-interval: 5000 @@ -92,7 +92,6 @@ module: - '{"method":"STARTS_WITH","content":"qbittorrent/3.3.15"}' - '{"method":"STARTS_WITH","content":"github.com/thank423/trafficconsume"}' - '{"method":"STARTS_WITH","content":"ޭ__"}' # 0xde-0xad-0xbe-0xef - - '{"method":"STARTS_WITH","content":"taipei-torrent"}' - '{"method":"STARTS_WITH","content":"-XL"}' # 进度作弊检查器:Progress Cheat Blocker # 注:有时这会错误的封禁部分启用“超级做种”的客户端。但在大多数情况下,此模块能够有效阻止循环下载的流量消耗器,建议启用。 @@ -379,7 +378,7 @@ module: tor-exit-nodes: enabled: false name: Tor Exit Nodes - url: https://cdn.jsdelivr.net/gh/platformcosmo/Tor-IP-Addresses/tor-exit-nodes.lst + url: https://cdn.jsdelivr.net/gh/7c/torfilter/lists/txt/torfilter-1d-flat.txt # 主动监测 - Active Monitoring # 此功能允许 PeerBanHelper 主动记录每次请求下载器时获取到的数据到本地 SQLite 数据库中 # Allow PBH records all data that fetched from downloader and save them into SQLite database @@ -413,4 +412,34 @@ module: traffic-monitoring: # 每日阈值 - 设置为 -1 以禁用,单位:bytes # Daily threshold, set to -1 to disable, Unit: bytes - daily: -1 \ No newline at end of file + daily: -1 + # PTR (反向解析记录) 封禁 + # 此模块将强制对 Peer IP 进行 PTR 查询,并试图解析其 IP 地址绑定的主机名。如果 IP 地址绑定了一个主机名且主机名匹配下列规则,则执行操作 + # PTR (Reverse DNS) Blocker + # This module will force to do PTR query on Peer IP, and try to resolve the hostname that bind with IP address. If the IP address bind with a hostname and the hostname match the rules below, then do the action + ptr-blacklist: + enabled: false + # 封禁时间,单位:毫秒,使用 default 则跟随全局设置 + # BanDuration, Timeunit: ms, use `default` to fallback to global settings + ban-duration: 259200000 + # method = 匹配方式 - Match Method + # + STARTS_WITH = 匹配开头 - Match the starts + # + ENDS_WITH = 匹配结尾 - Match the ends + # + LENGTH = 匹配字符串长度 - Match the string length + # + 支持的额外字段 - Other supported fields + # * min = 最小长度 - Min length + # * max = 最大长度 - Max length + # + CONTAINS = 匹配包含 - Match the contains + # + EQUALS = 匹配相同 - Match the equals + # + REGEX = 匹配正则表达式(大小写敏感) - Match the regex (case-sensitive) + # content = 匹配的内容(除正则外忽略大小写) - The content will be matched + # if = 表达式控制器,当 if 的表达式为 true 时,则检查此规则;否则此规则被忽略。 # if controller, `0` or `false` will skip this rule + # + if 表达式可以为 true/false, 1/0 或者一个嵌套的规则 # the return result can be `true` or `false` and `0` or `1` + # hit = 匹配成功返回的行为代码 # the behavior if matched + # + TRUE = 在 if 中代表 true,在规则中代表 BAN(封禁) # true in if controller, BAN in rule + # + FALSE = 在 if 中代表 false,在规则中代表 SKIP(排除) # false in if controller, SKIP in rule + # + DEFAULT = 在 if 中代表 true,在规则中代表 NO_ACTION(默认行为) # true in if controller, NO_ACTION in rule + # miss = 匹配失败返回的行为代码(与上相同) # the behavior if match failed, same as above + # 规则从上到下执行 + ptr-rules: + - '{"method":"EQUALS","content":"example.com"}' \ No newline at end of file diff --git a/webui/.gitignore b/webui/.gitignore index 8ee54e8d34..0dddfa739a 100644 --- a/webui/.gitignore +++ b/webui/.gitignore @@ -28,3 +28,4 @@ coverage *.sw? *.tsbuildinfo +vite.config.ts.timestamp* diff --git a/webui/package.json b/webui/package.json index 2618f3e239..fc3c1d0c82 100644 --- a/webui/package.json +++ b/webui/package.json @@ -1,6 +1,6 @@ { "name": "pbh-fe", - "version": "2.4.3", + "version": "2.4.4", "private": true, "type": "module", "scripts": { @@ -17,16 +17,16 @@ "dependencies": { "@arco-design/web-vue": "^2.56.3", "@dzangolab/flag-icon-css": "^3.4.5", - "@formatjs/intl-durationformat": "^0.6.4", + "@formatjs/intl-durationformat": "^0.7.2", "@guolao/vue-monaco-editor": "^1.5.4", - "@octokit/core": "^6.1.2", - "@octokit/request-error": "^6.1.5", - "@vueuse/core": "^12.0.0", + "@octokit/core": "^6.1.3", + "@octokit/request-error": "^6.1.6", + "@vueuse/core": "^12.3.0", "antlr4": "^4.13.2", "compare-versions": "^6.1.1", "copy-to-clipboard": "^3.3.3", "dayjs": "^1.11.13", - "echarts": "^5.5.1", + "echarts": "^5.6.0", "is-in-subnet": "^4.0.1", "is-ip": "^5.0.1", "lodash": "^4.17.21", @@ -35,41 +35,41 @@ "normalize.css": "^8.0.1", "pinia": "^2.3.0", "url-join": "^5.0.0", - "uuid": "^11.0.3", + "uuid": "^11.0.4", "vue": "^3.5.13", "vue-echarts": "^7.0.3", - "vue-i18n": "^10.0.5", + "vue-i18n": "^11.0.1", "vue-request": "^2.0.4", "vue-router": "^4.5.0" }, "devDependencies": { "@arco-plugins/vite-vue": "^1.4.5", - "@eslint/js": "^9.16.0", + "@eslint/js": "^9.17.0", "@rushstack/eslint-patch": "^1.10.4", "@tsconfig/node20": "^20.1.4", "@types/eslint__js": "^8.42.3", "@types/lodash": "^4.17.13", "@types/markdown-it": "^14.1.2", - "@types/node": "^22.10.1", + "@types/node": "^22.10.2", "@types/uuid": "^10.0.0", - "@typescript-eslint/parser": "^8.17.0", + "@typescript-eslint/parser": "^8.18.2", "@vitejs/plugin-vue": "^5.2.1", "@vue/tsconfig": "^0.7.0", - "eslint": "9.16.0", + "eslint": "9.17.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-vue": "^9.32.0", "husky": "^9.1.7", "less": "^4.2.1", - "monaco-editor": "^0.52.0", - "npm-run-all2": "^7.0.1", + "monaco-editor": "^0.52.2", + "npm-run-all2": "^7.0.2", "prettier": "^3.4.2", "typescript": "~5.6.3", - "typescript-eslint": "^8.17.0", - "vite": "^6.0.3", + "typescript-eslint": "^8.18.2", + "vite": "^6.0.6", "vite-bundle-analyzer": "^0.15.2", "vite-plugin-node-polyfills": "^0.22.0", "vite-plugin-remove-console": "^2.2.0", - "vite-plugin-vue-devtools": "^7.6.7", + "vite-plugin-vue-devtools": "^7.6.8", "vue-eslint-parser": "^9.4.3", "vue-tsc": "^2.1.10" }, diff --git a/webui/pnpm-lock.yaml b/webui/pnpm-lock.yaml index a8be45a7d1..fbf63227fe 100644 --- a/webui/pnpm-lock.yaml +++ b/webui/pnpm-lock.yaml @@ -20,20 +20,20 @@ importers: specifier: ^3.4.5 version: 3.4.5 '@formatjs/intl-durationformat': - specifier: ^0.6.4 - version: 0.6.4 + specifier: ^0.7.2 + version: 0.7.2 '@guolao/vue-monaco-editor': specifier: ^1.5.4 - version: 1.5.4(monaco-editor@0.52.0)(vue@3.5.13(typescript@5.6.3)) + version: 1.5.4(monaco-editor@0.52.2)(vue@3.5.13(typescript@5.6.3)) '@octokit/core': - specifier: ^6.1.2 - version: 6.1.2 + specifier: ^6.1.3 + version: 6.1.3 '@octokit/request-error': - specifier: ^6.1.5 - version: 6.1.5 + specifier: ^6.1.6 + version: 6.1.6 '@vueuse/core': - specifier: ^12.0.0 - version: 12.0.0(typescript@5.6.3) + specifier: ^12.3.0 + version: 12.3.0(typescript@5.6.3) antlr4: specifier: ^4.13.2 version: 4.13.2 @@ -47,8 +47,8 @@ importers: specifier: ^1.11.13 version: 1.11.13 echarts: - specifier: ^5.5.1 - version: 5.5.1 + specifier: ^5.6.0 + version: 5.6.0 is-in-subnet: specifier: ^4.0.1 version: 4.0.1 @@ -74,17 +74,17 @@ importers: specifier: ^5.0.0 version: 5.0.0 uuid: - specifier: ^11.0.3 - version: 11.0.3 + specifier: ^11.0.4 + version: 11.0.4 vue: specifier: ^3.5.13 version: 3.5.13(typescript@5.6.3) vue-echarts: specifier: ^7.0.3 - version: 7.0.3(@vue/runtime-core@3.5.13)(echarts@5.5.1)(vue@3.5.13(typescript@5.6.3)) + version: 7.0.3(@vue/runtime-core@3.5.13)(echarts@5.6.0)(vue@3.5.13(typescript@5.6.3)) vue-i18n: - specifier: ^10.0.5 - version: 10.0.5(vue@3.5.13(typescript@5.6.3)) + specifier: ^11.0.1 + version: 11.0.1(vue@3.5.13(typescript@5.6.3)) vue-request: specifier: ^2.0.4 version: 2.0.4(patch_hash=imva7qnbqmkxs76or73cwn5jom)(vue@3.5.13(typescript@5.6.3)) @@ -96,8 +96,8 @@ importers: specifier: ^1.4.5 version: 1.4.5 '@eslint/js': - specifier: ^9.16.0 - version: 9.16.0 + specifier: ^9.17.0 + version: 9.17.0 '@rushstack/eslint-patch': specifier: ^1.10.4 version: 1.10.4 @@ -114,29 +114,29 @@ importers: specifier: ^14.1.2 version: 14.1.2 '@types/node': - specifier: ^22.10.1 - version: 22.10.1 + specifier: ^22.10.2 + version: 22.10.2 '@types/uuid': specifier: ^10.0.0 version: 10.0.0 '@typescript-eslint/parser': - specifier: ^8.17.0 - version: 8.17.0(eslint@9.16.0)(typescript@5.6.3) + specifier: ^8.18.2 + version: 8.18.2(eslint@9.17.0)(typescript@5.6.3) '@vitejs/plugin-vue': specifier: ^5.2.1 - version: 5.2.1(vite@6.0.3(@types/node@22.10.1)(less@4.2.1)(sass@1.77.8))(vue@3.5.13(typescript@5.6.3)) + version: 5.2.1(vite@6.0.6(@types/node@22.10.2)(less@4.2.1)(sass@1.77.8))(vue@3.5.13(typescript@5.6.3)) '@vue/tsconfig': specifier: ^0.7.0 version: 0.7.0(typescript@5.6.3)(vue@3.5.13(typescript@5.6.3)) eslint: - specifier: 9.16.0 - version: 9.16.0 + specifier: 9.17.0 + version: 9.17.0 eslint-config-prettier: specifier: ^9.1.0 - version: 9.1.0(eslint@9.16.0) + version: 9.1.0(eslint@9.17.0) eslint-plugin-vue: specifier: ^9.32.0 - version: 9.32.0(eslint@9.16.0) + version: 9.32.0(eslint@9.17.0) husky: specifier: ^9.1.7 version: 9.1.7 @@ -144,11 +144,11 @@ importers: specifier: ^4.2.1 version: 4.2.1 monaco-editor: - specifier: ^0.52.0 - version: 0.52.0 + specifier: ^0.52.2 + version: 0.52.2 npm-run-all2: - specifier: ^7.0.1 - version: 7.0.1 + specifier: ^7.0.2 + version: 7.0.2 prettier: specifier: ^3.4.2 version: 3.4.2 @@ -156,26 +156,26 @@ importers: specifier: ~5.6.3 version: 5.6.3 typescript-eslint: - specifier: ^8.17.0 - version: 8.17.0(eslint@9.16.0)(typescript@5.6.3) + specifier: ^8.18.2 + version: 8.18.2(eslint@9.17.0)(typescript@5.6.3) vite: - specifier: ^6.0.3 - version: 6.0.3(@types/node@22.10.1)(less@4.2.1)(sass@1.77.8) + specifier: ^6.0.6 + version: 6.0.6(@types/node@22.10.2)(less@4.2.1)(sass@1.77.8) vite-bundle-analyzer: specifier: ^0.15.2 version: 0.15.2 vite-plugin-node-polyfills: specifier: ^0.22.0 - version: 0.22.0(rollup@4.27.4)(vite@6.0.3(@types/node@22.10.1)(less@4.2.1)(sass@1.77.8)) + version: 0.22.0(rollup@4.27.4)(vite@6.0.6(@types/node@22.10.2)(less@4.2.1)(sass@1.77.8)) vite-plugin-remove-console: specifier: ^2.2.0 version: 2.2.0 vite-plugin-vue-devtools: - specifier: ^7.6.7 - version: 7.6.7(rollup@4.27.4)(vite@6.0.3(@types/node@22.10.1)(less@4.2.1)(sass@1.77.8))(vue@3.5.13(typescript@5.6.3)) + specifier: ^7.6.8 + version: 7.6.8(rollup@4.27.4)(vite@6.0.6(@types/node@22.10.2)(less@4.2.1)(sass@1.77.8))(vue@3.5.13(typescript@5.6.3)) vue-eslint-parser: specifier: ^9.4.3 - version: 9.4.3(eslint@9.16.0) + version: 9.4.3(eslint@9.17.0) vue-tsc: specifier: ^2.1.10 version: 2.1.10(typescript@5.6.3) @@ -183,11 +183,11 @@ importers: packages: '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==, tarball: https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz} engines: {node: '>=6.0.0'} '@antfu/utils@0.7.10': - resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==} + resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==, tarball: https://registry.npmjs.org/@antfu/utils/-/utils-0.7.10.tgz} '@arco-design/color@0.4.0': resolution: {integrity: sha512-s7p9MSwJgHeL8DwcATaXvWT3m2SigKpxx4JA1BGPHL4gfvaQsmQfrLBDpjOJFJuJ2jG2dMt3R3P8Pm9E65q18g==} @@ -205,15 +205,15 @@ packages: engines: {node: '>=6.9.0'} '@babel/code-frame@7.26.2': - resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==, tarball: https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz} engines: {node: '>=6.9.0'} '@babel/compat-data@7.26.2': - resolution: {integrity: sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==} + resolution: {integrity: sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==, tarball: https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz} engines: {node: '>=6.9.0'} '@babel/core@7.26.0': - resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} + resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==, tarball: https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz} engines: {node: '>=6.9.0'} '@babel/generator@7.24.4': @@ -221,19 +221,19 @@ packages: engines: {node: '>=6.9.0'} '@babel/generator@7.26.2': - resolution: {integrity: sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==} + resolution: {integrity: sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==, tarball: https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz} engines: {node: '>=6.9.0'} '@babel/helper-annotate-as-pure@7.25.9': - resolution: {integrity: sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==} + resolution: {integrity: sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==, tarball: https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz} engines: {node: '>=6.9.0'} '@babel/helper-compilation-targets@7.25.9': - resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} + resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==, tarball: https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz} engines: {node: '>=6.9.0'} '@babel/helper-create-class-features-plugin@7.25.9': - resolution: {integrity: sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==} + resolution: {integrity: sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==, tarball: https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -251,7 +251,7 @@ packages: engines: {node: '>=6.9.0'} '@babel/helper-member-expression-to-functions@7.25.9': - resolution: {integrity: sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==} + resolution: {integrity: sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==, tarball: https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz} engines: {node: '>=6.9.0'} '@babel/helper-module-imports@7.24.3': @@ -259,31 +259,31 @@ packages: engines: {node: '>=6.9.0'} '@babel/helper-module-imports@7.25.9': - resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==, tarball: https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz} engines: {node: '>=6.9.0'} '@babel/helper-module-transforms@7.26.0': - resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==, tarball: https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 '@babel/helper-optimise-call-expression@7.25.9': - resolution: {integrity: sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==} + resolution: {integrity: sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==, tarball: https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz} engines: {node: '>=6.9.0'} '@babel/helper-plugin-utils@7.25.9': - resolution: {integrity: sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==} + resolution: {integrity: sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==, tarball: https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz} engines: {node: '>=6.9.0'} '@babel/helper-replace-supers@7.25.9': - resolution: {integrity: sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==} + resolution: {integrity: sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==, tarball: https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.9.tgz} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 '@babel/helper-skip-transparent-expression-wrappers@7.25.9': - resolution: {integrity: sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==} + resolution: {integrity: sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==, tarball: https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz} engines: {node: '>=6.9.0'} '@babel/helper-split-export-declaration@7.22.6': @@ -311,11 +311,11 @@ packages: engines: {node: '>=6.9.0'} '@babel/helper-validator-option@7.25.9': - resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} + resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==, tarball: https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz} engines: {node: '>=6.9.0'} '@babel/helpers@7.26.0': - resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} + resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==, tarball: https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz} engines: {node: '>=6.9.0'} '@babel/highlight@7.24.2': @@ -338,42 +338,42 @@ packages: hasBin: true '@babel/plugin-proposal-decorators@7.25.9': - resolution: {integrity: sha512-smkNLL/O1ezy9Nhy4CNosc4Va+1wo5w4gzSZeLe6y6dM4mmHfYOCPolXQPHQxonZCF+ZyebxN9vqOolkYrSn5g==} + resolution: {integrity: sha512-smkNLL/O1ezy9Nhy4CNosc4Va+1wo5w4gzSZeLe6y6dM4mmHfYOCPolXQPHQxonZCF+ZyebxN9vqOolkYrSn5g==, tarball: https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.25.9.tgz} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-decorators@7.25.9': - resolution: {integrity: sha512-ryzI0McXUPJnRCvMo4lumIKZUzhYUO/ScI+Mz4YVaTLt04DHNSjEUjKVvbzQjZFLuod/cYEc07mJWhzl6v4DPg==} + resolution: {integrity: sha512-ryzI0McXUPJnRCvMo4lumIKZUzhYUO/ScI+Mz4YVaTLt04DHNSjEUjKVvbzQjZFLuod/cYEc07mJWhzl6v4DPg==, tarball: https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.25.9.tgz} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-import-attributes@7.26.0': - resolution: {integrity: sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==} + resolution: {integrity: sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==, tarball: https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-import-meta@7.10.4': - resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==, tarball: https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-jsx@7.25.9': - resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==} + resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==, tarball: https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-typescript@7.25.9': - resolution: {integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==} + resolution: {integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==, tarball: https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-transform-typescript@7.25.9': - resolution: {integrity: sha512-7PbZQZP50tzv2KGGnhh82GSyMB01yKY9scIjf1a+GfZCtInOWqUH5+1EBU4t9fyR5Oykkkc9vFTs4OHrhHXljQ==} + resolution: {integrity: sha512-7PbZQZP50tzv2KGGnhh82GSyMB01yKY9scIjf1a+GfZCtInOWqUH5+1EBU4t9fyR5Oykkkc9vFTs4OHrhHXljQ==, tarball: https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.9.tgz} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -383,7 +383,7 @@ packages: engines: {node: '>=6.9.0'} '@babel/template@7.25.9': - resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} + resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==, tarball: https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz} engines: {node: '>=6.9.0'} '@babel/traverse@7.24.1': @@ -391,7 +391,7 @@ packages: engines: {node: '>=6.9.0'} '@babel/traverse@7.25.9': - resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==} + resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==, tarball: https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz} engines: {node: '>=6.9.0'} '@babel/types@7.24.0': @@ -405,146 +405,152 @@ packages: '@dzangolab/flag-icon-css@3.4.5': resolution: {integrity: sha512-XqVAi0O/KITtznpMK5TP4D+rWfwst5lrsbPbes5c5SPMGjwK7fuvlTdEmG2XUrxzYqDTIPshywyzdVYKooGdGA==} - '@esbuild/aix-ppc64@0.24.0': - resolution: {integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==, tarball: https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz} + '@esbuild/aix-ppc64@0.24.2': + resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==, tarball: https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.24.0': - resolution: {integrity: sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==, tarball: https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz} + '@esbuild/android-arm64@0.24.2': + resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==, tarball: https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.24.0': - resolution: {integrity: sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==, tarball: https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz} + '@esbuild/android-arm@0.24.2': + resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==, tarball: https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.24.0': - resolution: {integrity: sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==, tarball: https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz} + '@esbuild/android-x64@0.24.2': + resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==, tarball: https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.24.0': - resolution: {integrity: sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==, tarball: https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz} + '@esbuild/darwin-arm64@0.24.2': + resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==, tarball: https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.24.0': - resolution: {integrity: sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==, tarball: https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz} + '@esbuild/darwin-x64@0.24.2': + resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==, tarball: https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.24.0': - resolution: {integrity: sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==, tarball: https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz} + '@esbuild/freebsd-arm64@0.24.2': + resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==, tarball: https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.24.0': - resolution: {integrity: sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==, tarball: https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz} + '@esbuild/freebsd-x64@0.24.2': + resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==, tarball: https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.24.0': - resolution: {integrity: sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==, tarball: https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz} + '@esbuild/linux-arm64@0.24.2': + resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==, tarball: https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.24.0': - resolution: {integrity: sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==, tarball: https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz} + '@esbuild/linux-arm@0.24.2': + resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==, tarball: https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.24.0': - resolution: {integrity: sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==, tarball: https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz} + '@esbuild/linux-ia32@0.24.2': + resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==, tarball: https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.24.0': - resolution: {integrity: sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==, tarball: https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz} + '@esbuild/linux-loong64@0.24.2': + resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==, tarball: https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.24.0': - resolution: {integrity: sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==, tarball: https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz} + '@esbuild/linux-mips64el@0.24.2': + resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==, tarball: https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.24.0': - resolution: {integrity: sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==, tarball: https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz} + '@esbuild/linux-ppc64@0.24.2': + resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==, tarball: https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.24.0': - resolution: {integrity: sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==, tarball: https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz} + '@esbuild/linux-riscv64@0.24.2': + resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==, tarball: https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.24.0': - resolution: {integrity: sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==, tarball: https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz} + '@esbuild/linux-s390x@0.24.2': + resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==, tarball: https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.24.0': - resolution: {integrity: sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==, tarball: https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz} + '@esbuild/linux-x64@0.24.2': + resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==, tarball: https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-x64@0.24.0': - resolution: {integrity: sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==, tarball: https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz} + '@esbuild/netbsd-arm64@0.24.2': + resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==, tarball: https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.24.2': + resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==, tarball: https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.24.0': - resolution: {integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==, tarball: https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz} + '@esbuild/openbsd-arm64@0.24.2': + resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==, tarball: https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.24.0': - resolution: {integrity: sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==, tarball: https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz} + '@esbuild/openbsd-x64@0.24.2': + resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==, tarball: https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.24.0': - resolution: {integrity: sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==, tarball: https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz} + '@esbuild/sunos-x64@0.24.2': + resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==, tarball: https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.24.0': - resolution: {integrity: sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==, tarball: https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz} + '@esbuild/win32-arm64@0.24.2': + resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==, tarball: https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.24.0': - resolution: {integrity: sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==, tarball: https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz} + '@esbuild/win32-ia32@0.24.2': + resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==, tarball: https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.24.0': - resolution: {integrity: sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==, tarball: https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz} + '@esbuild/win32-x64@0.24.2': + resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==, tarball: https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -556,44 +562,44 @@ packages: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 '@eslint-community/regexpp@4.12.1': - resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==, tarball: https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} '@eslint/config-array@0.19.0': - resolution: {integrity: sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==} + resolution: {integrity: sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==, tarball: https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.0.tgz} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/core@0.9.0': - resolution: {integrity: sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==} + resolution: {integrity: sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==, tarball: https://registry.npmjs.org/@eslint/core/-/core-0.9.0.tgz} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/eslintrc@3.2.0': - resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==} + resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==, tarball: https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.16.0': - resolution: {integrity: sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==} + '@eslint/js@9.17.0': + resolution: {integrity: sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==, tarball: https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/object-schema@2.1.4': - resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==} + resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==, tarball: https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/plugin-kit@0.2.3': - resolution: {integrity: sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==} + resolution: {integrity: sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==, tarball: https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@formatjs/ecma402-abstract@2.2.4': - resolution: {integrity: sha512-lFyiQDVvSbQOpU+WFd//ILolGj4UgA/qXrKeZxdV14uKiAUiPAtX6XAn7WBCRi7Mx6I7EybM9E5yYn4BIpZWYg==} + '@formatjs/ecma402-abstract@2.3.2': + resolution: {integrity: sha512-6sE5nyvDloULiyOMbOTJEEgWL32w+VHkZQs8S02Lnn8Y/O5aQhjOEXwWzvR7SsBE/exxlSpY2EsWZgqHbtLatg==, tarball: https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.2.tgz} - '@formatjs/fast-memoize@2.2.3': - resolution: {integrity: sha512-3jeJ+HyOfu8osl3GNSL4vVHUuWFXR03Iz9jjgI7RwjG6ysu/Ymdr0JRCPHfF5yGbTE6JCrd63EpvX1/WybYRbA==} + '@formatjs/fast-memoize@2.2.6': + resolution: {integrity: sha512-luIXeE2LJbQnnzotY1f2U2m7xuQNj2DA8Vq4ce1BY9ebRZaoPB1+8eZ6nXpLzsxuW5spQxr7LdCg+CApZwkqkw==, tarball: https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.6.tgz} - '@formatjs/intl-durationformat@0.6.4': - resolution: {integrity: sha512-kpYLechF9ZvECzzMsvikBl48GkbCEAbZJN4kG/4x0FTVZkBuOWrBlj6DghCn7YsW3Bgsr0n9E0RYO373Kg3m+Q==} + '@formatjs/intl-durationformat@0.7.2': + resolution: {integrity: sha512-GTO67hNFJOv7g3nOEpSJ+0CN0VHI/GvIfv0sTfTjl30aGN4oGV7SEo2IKCQe3NbPTAYUcCRCzKa5+vAW3crS5w==, tarball: https://registry.npmjs.org/@formatjs/intl-durationformat/-/intl-durationformat-0.7.2.tgz} - '@formatjs/intl-localematcher@0.5.8': - resolution: {integrity: sha512-I+WDNWWJFZie+jkfkiK5Mp4hEDyRSEvmyfYadflOno/mmKJKcB17fEpEH0oJu/OWhhCJ8kJBDz2YMd/6cDl7Mg==} + '@formatjs/intl-localematcher@0.5.10': + resolution: {integrity: sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q==, tarball: https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.10.tgz} '@guolao/vue-monaco-editor@1.5.4': resolution: {integrity: sha512-eyBAqxJeDpV4mZYZSpNvh3xUgKCld5eEe0dBtjJhsy2+L0MB6PYFZ/FbPHNwskgp2RoIpfn1DLrIhXXE3lVbwQ==} @@ -606,35 +612,35 @@ packages: optional: true '@humanfs/core@0.19.1': - resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==, tarball: https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz} engines: {node: '>=18.18.0'} '@humanfs/node@0.16.6': - resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==, tarball: https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz} engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==, tarball: https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz} engines: {node: '>=12.22'} '@humanwhocodes/retry@0.3.1': - resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==, tarball: https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz} engines: {node: '>=18.18'} '@humanwhocodes/retry@0.4.1': - resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} + resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==, tarball: https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz} engines: {node: '>=18.18'} - '@intlify/core-base@10.0.5': - resolution: {integrity: sha512-F3snDTQs0MdvnnyzTDTVkOYVAZOE/MHwRvF7mn7Jw1yuih4NrFYLNYIymGlLmq4HU2iIdzYsZ7f47bOcwY73XQ==} + '@intlify/core-base@11.0.1': + resolution: {integrity: sha512-NAmhw1l/llM0HZRpagR/ChJTNymW4ll6/4EDSJML5c8L5Hl/+k6UyF8EIgE6DeHpfheQujkSRngauViHqq6jJQ==, tarball: https://registry.npmjs.org/@intlify/core-base/-/core-base-11.0.1.tgz} engines: {node: '>= 16'} - '@intlify/message-compiler@10.0.5': - resolution: {integrity: sha512-6GT1BJ852gZ0gItNZN2krX5QAmea+cmdjMvsWohArAZ3GmHdnNANEcF9JjPXAMRtQ6Ux5E269ymamg/+WU6tQA==} + '@intlify/message-compiler@11.0.1': + resolution: {integrity: sha512-5RFH8x+Mn3mbjcHXnb6KCXGiczBdiQkWkv99iiA0JpKrNuTAQeW59Pjq/uObMB0eR0shnKYGTkIJxum+DbL3sw==, tarball: https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.0.1.tgz} engines: {node: '>= 16'} - '@intlify/shared@10.0.5': - resolution: {integrity: sha512-bmsP4L2HqBF6i6uaMqJMcFBONVjKt+siGluRq4Ca4C0q7W2eMaVZr8iCgF9dKbcVXutftkC7D6z2SaSMmLiDyA==} + '@intlify/shared@11.0.1': + resolution: {integrity: sha512-lH164+aDDptHZ3dBDbIhRa1dOPQUp+83iugpc+1upTOWCnwyC1PVis6rSWNMMJ8VQxvtHQB9JMib48K55y0PvQ==, tarball: https://registry.npmjs.org/@intlify/shared/-/shared-11.0.1.tgz} engines: {node: '>= 16'} '@jridgewell/gen-mapping@0.3.5': @@ -664,49 +670,49 @@ packages: monaco-editor: '>= 0.21.0 < 1' '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==, tarball: https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz} + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==, tarball: https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz} + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} engines: {node: '>= 8'} '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==, tarball: https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz} + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} '@octokit/auth-token@5.1.1': - resolution: {integrity: sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==} + resolution: {integrity: sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==, tarball: https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.1.tgz} engines: {node: '>= 18'} - '@octokit/core@6.1.2': - resolution: {integrity: sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==} + '@octokit/core@6.1.3': + resolution: {integrity: sha512-z+j7DixNnfpdToYsOutStDgeRzJSMnbj8T1C/oQjB6Aa+kRfNjs/Fn7W6c8bmlt6mfy3FkgeKBRnDjxQow5dow==, tarball: https://registry.npmjs.org/@octokit/core/-/core-6.1.3.tgz} engines: {node: '>= 18'} '@octokit/endpoint@10.1.1': - resolution: {integrity: sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==} + resolution: {integrity: sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==, tarball: https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz} engines: {node: '>= 18'} - '@octokit/graphql@8.1.1': - resolution: {integrity: sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==} + '@octokit/graphql@8.1.2': + resolution: {integrity: sha512-bdlj/CJVjpaz06NBpfHhp4kGJaRZfz7AzC+6EwUImRtrwIw8dIgJ63Xg0OzV9pRn3rIzrt5c2sa++BL0JJ8GLw==, tarball: https://registry.npmjs.org/@octokit/graphql/-/graphql-8.1.2.tgz} engines: {node: '>= 18'} '@octokit/openapi-types@22.2.0': resolution: {integrity: sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==} - '@octokit/request-error@6.1.5': - resolution: {integrity: sha512-IlBTfGX8Yn/oFPMwSfvugfncK2EwRLjzbrpifNaMY8o/HTEAFqCA1FZxjD9cWvSKBHgrIhc4CSBIzMxiLsbzFQ==} + '@octokit/request-error@6.1.6': + resolution: {integrity: sha512-pqnVKYo/at0NuOjinrgcQYpEbv4snvP3bKMRqHaD9kIsk9u1LCpb2smHZi8/qJfgeNqLo5hNW4Z7FezNdEo0xg==, tarball: https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.6.tgz} engines: {node: '>= 18'} - '@octokit/request@9.1.1': - resolution: {integrity: sha512-pyAguc0p+f+GbQho0uNetNQMmLG1e80WjkIaqqgUkihqUp0boRU6nKItXO4VWnr+nbZiLGEyy4TeKRwqaLvYgw==} + '@octokit/request@9.1.4': + resolution: {integrity: sha512-tMbOwGm6wDII6vygP3wUVqFTw3Aoo0FnVQyhihh8vVq12uO3P+vQZeo2CKMpWtPSogpACD0yyZAlVlQnjW71DA==, tarball: https://registry.npmjs.org/@octokit/request/-/request-9.1.4.tgz} engines: {node: '>= 18'} - '@octokit/types@13.5.0': - resolution: {integrity: sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==} + '@octokit/types@13.6.2': + resolution: {integrity: sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==, tarball: https://registry.npmjs.org/@octokit/types/-/types-13.6.2.tgz} '@polka/url@1.0.0-next.28': - resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==} + resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==, tarball: https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz} '@rollup/plugin-inject@5.0.5': resolution: {integrity: sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg==} @@ -727,7 +733,7 @@ packages: optional: true '@rollup/pluginutils@5.1.3': - resolution: {integrity: sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==} + resolution: {integrity: sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==, tarball: https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.3.tgz} engines: {node: '>=14.0.0'} peerDependencies: rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 @@ -829,10 +835,10 @@ packages: resolution: {integrity: sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==} '@sec-ant/readable-stream@0.4.1': - resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==, tarball: https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz} '@sindresorhus/merge-streams@4.0.0': - resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==, tarball: https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz} engines: {node: '>=18'} '@tsconfig/node20@20.1.4': @@ -863,77 +869,62 @@ packages: resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} '@types/node@16.18.121': - resolution: {integrity: sha512-Gk/pOy8H0cvX8qNrwzElYIECpcUn87w4EAEFXFvPJ8qsP9QR/YqukUORSy0zmyDyvdo149idPpy4W6iC5aSbQA==} + resolution: {integrity: sha512-Gk/pOy8H0cvX8qNrwzElYIECpcUn87w4EAEFXFvPJ8qsP9QR/YqukUORSy0zmyDyvdo149idPpy4W6iC5aSbQA==, tarball: https://registry.npmjs.org/@types/node/-/node-16.18.121.tgz} - '@types/node@22.10.1': - resolution: {integrity: sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==} + '@types/node@22.10.2': + resolution: {integrity: sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==, tarball: https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz} '@types/uuid@10.0.0': resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} '@types/web-bluetooth@0.0.20': - resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} + resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==, tarball: https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz} - '@typescript-eslint/eslint-plugin@8.17.0': - resolution: {integrity: sha512-HU1KAdW3Tt8zQkdvNoIijfWDMvdSweFYm4hWh+KwhPstv+sCmWb89hCIP8msFm9N1R/ooh9honpSuvqKWlYy3w==, tarball: https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.17.0.tgz} + '@typescript-eslint/eslint-plugin@8.18.2': + resolution: {integrity: sha512-adig4SzPLjeQ0Tm+jvsozSGiCliI2ajeURDGHjZ2llnA+A67HihCQ+a3amtPhUakd1GlwHxSRvzOZktbEvhPPg==, tarball: https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.2.tgz} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 eslint: ^8.57.0 || ^9.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/parser@8.17.0': - resolution: {integrity: sha512-Drp39TXuUlD49F7ilHHCG7TTg8IkA+hxCuULdmzWYICxGXvDXmDmWEjJYZQYgf6l/TFfYNE167m7isnc3xlIEg==, tarball: https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.17.0.tgz} + '@typescript-eslint/parser@8.18.2': + resolution: {integrity: sha512-y7tcq4StgxQD4mDr9+Jb26dZ+HTZ/SkfqpXSiqeUXZHxOUyjWDKsmwKhJ0/tApR08DgOhrFAoAhyB80/p3ViuA==, tarball: https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.18.2.tgz} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/scope-manager@8.17.0': - resolution: {integrity: sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==, tarball: https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.17.0.tgz} + '@typescript-eslint/scope-manager@8.18.2': + resolution: {integrity: sha512-YJFSfbd0CJjy14r/EvWapYgV4R5CHzptssoag2M7y3Ra7XNta6GPAJPPP5KGB9j14viYXyrzRO5GkX7CRfo8/g==, tarball: https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.18.2.tgz} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@8.17.0': - resolution: {integrity: sha512-q38llWJYPd63rRnJ6wY/ZQqIzPrBCkPdpIsaCfkR3Q4t3p6sb422zougfad4TFW9+ElIFLVDzWGiGAfbb/v2qw==, tarball: https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.17.0.tgz} + '@typescript-eslint/type-utils@8.18.2': + resolution: {integrity: sha512-AB/Wr1Lz31bzHfGm/jgbFR0VB0SML/hd2P1yxzKDM48YmP7vbyJNHRExUE/wZsQj2wUCvbWH8poNHFuxLqCTnA==, tarball: https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.18.2.tgz} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/types@8.17.0': - resolution: {integrity: sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==, tarball: https://registry.npmjs.org/@typescript-eslint/types/-/types-8.17.0.tgz} + '@typescript-eslint/types@8.18.2': + resolution: {integrity: sha512-Z/zblEPp8cIvmEn6+tPDIHUbRu/0z5lqZ+NvolL5SvXWT5rQy7+Nch83M0++XzO0XrWRFWECgOAyE8bsJTl1GQ==, tarball: https://registry.npmjs.org/@typescript-eslint/types/-/types-8.18.2.tgz} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.17.0': - resolution: {integrity: sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==, tarball: https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.17.0.tgz} + '@typescript-eslint/typescript-estree@8.18.2': + resolution: {integrity: sha512-WXAVt595HjpmlfH4crSdM/1bcsqh+1weFRWIa9XMTx/XHZ9TCKMcr725tLYqWOgzKdeDrqVHxFotrvWcEsk2Tg==, tarball: https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.2.tgz} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/utils@8.17.0': - resolution: {integrity: sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w==, tarball: https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.17.0.tgz} + '@typescript-eslint/utils@8.18.2': + resolution: {integrity: sha512-Cr4A0H7DtVIPkauj4sTSXVl+VBWewE9/o40KcF3TV9aqDEOWoXF3/+oRXNby3DYzZeCATvbdksYsGZzplwnK/Q==, tarball: https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.18.2.tgz} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/visitor-keys@8.17.0': - resolution: {integrity: sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==, tarball: https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz} + '@typescript-eslint/visitor-keys@8.18.2': + resolution: {integrity: sha512-zORcwn4C3trOWiCqFQP1x6G3xTRyZ1LYydnj51cRnJ6hxBlr/cKPckk+PKPUw/fXmvfKTcw7bwY3w9izgx5jZw==, tarball: https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.2.tgz} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@vitejs/plugin-vue@5.2.1': @@ -953,10 +944,10 @@ packages: resolution: {integrity: sha512-6xkIYJ5xxghVBhVywMoPMidDDAFT1OoQeXwa27HSgJ6AiIKRe61RXLoik+14Z7r0JvnblXVsjsRLmCr42SGzqg==} '@vue/babel-helper-vue-transform-on@1.2.5': - resolution: {integrity: sha512-lOz4t39ZdmU4DJAa2hwPYmKc8EsuGa2U0L9KaZaOJUt0UwQNjNA3AZTq6uEivhOKhhG1Wvy96SvYBoFmCg3uuw==} + resolution: {integrity: sha512-lOz4t39ZdmU4DJAa2hwPYmKc8EsuGa2U0L9KaZaOJUt0UwQNjNA3AZTq6uEivhOKhhG1Wvy96SvYBoFmCg3uuw==, tarball: https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.2.5.tgz} '@vue/babel-plugin-jsx@1.2.5': - resolution: {integrity: sha512-zTrNmOd4939H9KsRIGmmzn3q2zvv1mjxkYZHgqHZgDrXz5B1Q3WyGEjO2f+JrmKghvl1JIRcvo63LgM1kH5zFg==} + resolution: {integrity: sha512-zTrNmOd4939H9KsRIGmmzn3q2zvv1mjxkYZHgqHZgDrXz5B1Q3WyGEjO2f+JrmKghvl1JIRcvo63LgM1kH5zFg==, tarball: https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.2.5.tgz} peerDependencies: '@babel/core': ^7.0.0-0 peerDependenciesMeta: @@ -964,7 +955,7 @@ packages: optional: true '@vue/babel-plugin-resolve-type@1.2.5': - resolution: {integrity: sha512-U/ibkQrf5sx0XXRnUZD1mo5F7PkpKyTbfXM3a3rC4YnUz6crHEz9Jg09jzzL6QYlXNto/9CePdOg/c87O4Nlfg==} + resolution: {integrity: sha512-U/ibkQrf5sx0XXRnUZD1mo5F7PkpKyTbfXM3a3rC4YnUz6crHEz9Jg09jzzL6QYlXNto/9CePdOg/c87O4Nlfg==, tarball: https://registry.npmjs.org/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.2.5.tgz} peerDependencies: '@babel/core': ^7.0.0-0 @@ -992,16 +983,16 @@ packages: '@vue/devtools-api@6.6.4': resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} - '@vue/devtools-core@7.6.7': - resolution: {integrity: sha512-6fW8Q0H1NHDXdEcuV6dylT5U2Yxg3SdMnVCey99Y6S4R2PNgFL2vC+VU9U9rHIiaoEUkeza42S7FfHxV4VI3Jg==} + '@vue/devtools-core@7.6.8': + resolution: {integrity: sha512-8X4roysTwzQ94o7IobjVcOd1aZF5iunikrMrHPI2uUdigZCi2kFTQc7ffYiFiTNaLElCpjOhCnM7bo7aK1yU7A==, tarball: https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-7.6.8.tgz} peerDependencies: vue: ^3.0.0 - '@vue/devtools-kit@7.6.7': - resolution: {integrity: sha512-V8/jrXY/swHgnblABG9U4QCbE60c6RuPasmv2d9FvVqc5d94t1vDiESuvRmdNJBdWz4/D3q6ffgyAfRVjwHYEw==} + '@vue/devtools-kit@7.6.8': + resolution: {integrity: sha512-JhJ8M3sPU+v0P2iZBF2DkdmR9L0dnT5RXJabJqX6o8KtFs3tebdvfoXV2Dm3BFuqeECuMJIfF1aCzSt+WQ4wrw==, tarball: https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.6.8.tgz} - '@vue/devtools-shared@7.6.7': - resolution: {integrity: sha512-QggO6SviAsolrePAXZ/sA1dSicSPt4TueZibCvydfhNDieL1lAuyMTgQDGst7TEvMGb4vgYv2I+1sDkO4jWNnw==} + '@vue/devtools-shared@7.6.8': + resolution: {integrity: sha512-9MBPO5Z3X1nYGFqTJyohl6Gmf/J7UNN1oicHdyzBVZP4jnhZ4c20MgtaHDIzWmHDHCMYVS5bwKxT3jxh7gOOKA==, tarball: https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.6.8.tgz} '@vue/language-core@2.1.10': resolution: {integrity: sha512-DAI289d0K3AB5TUG3xDp9OuQ71CnrujQwJrQnfuZDwo6eGNf0UoRlPuaVNO+Zrn65PC3j0oB2i7mNmVPggeGeQ==} @@ -1042,14 +1033,14 @@ packages: vue: optional: true - '@vueuse/core@12.0.0': - resolution: {integrity: sha512-C12RukhXiJCbx4MGhjmd/gH52TjJsc3G0E0kQj/kb19H3Nt6n1CA4DRWuTdWWcaFRdlTe0npWDS942mvacvNBw==} + '@vueuse/core@12.3.0': + resolution: {integrity: sha512-cnV8QDKZrsyKC7tWjPbeEUz2cD9sa9faxF2YkR8QqNwfofgbOhmfIgvSYmkp+ttSvfOw4E6hLcQx15mRPr0yBA==, tarball: https://registry.npmjs.org/@vueuse/core/-/core-12.3.0.tgz} - '@vueuse/metadata@12.0.0': - resolution: {integrity: sha512-Yzimd1D3sjxTDOlF05HekU5aSGdKjxhuhRFHA7gDWLn57PRbBIh+SF5NmjhJ0WRgF3my7T8LBucyxdFJjIfRJQ==} + '@vueuse/metadata@12.3.0': + resolution: {integrity: sha512-M/iQHHjMffOv2npsw2ihlUx1CTiBwPEgb7DzByLq7zpg1+Ke8r7s9p5ybUWc5OIeGewtpY4Xy0R2cKqFqM8hFg==, tarball: https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.3.0.tgz} - '@vueuse/shared@12.0.0': - resolution: {integrity: sha512-3i6qtcq2PIio5i/vVYidkkcgvmTjCqrf26u+Fd4LhnbBmIT6FN8y6q/GJERp8lfcB9zVEfjdV0Br0443qZuJpw==} + '@vueuse/shared@12.3.0': + resolution: {integrity: sha512-X3YD35GUeW0d5Gajcwv9jdLAJTV2Jdb/Ll6Ii2JIYcKLYZqv5wxyLeKtiQkqWmHg3v0J0ZWjDUMVOw2E7RCXfA==, tarball: https://registry.npmjs.org/@vueuse/shared/-/shared-12.3.0.tgz} acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} @@ -1062,12 +1053,12 @@ packages: hasBin: true acorn@8.14.0: - resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==, tarball: https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz} engines: {node: '>=0.4.0'} hasBin: true ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==, tarball: https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz} alien-signals@0.2.0: resolution: {integrity: sha512-StlonZhBBrsPPwrDjiPAiVTf/rolxffLxVPT60Qv/t88BZ81BvUVzHgGqEFvJ1ii8HXtm1+zU2Icr59tfWEcag==} @@ -1077,11 +1068,11 @@ packages: engines: {node: '>=4'} ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==, tarball: https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz} engines: {node: '>=8'} ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==, tarball: https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz} engines: {node: '>=12'} antlr4@4.13.2: @@ -1118,14 +1109,14 @@ packages: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} before-after-hook@3.0.2: - resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==} + resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==, tarball: https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz} binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==, tarball: https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz} engines: {node: '>=8'} birpc@0.2.19: - resolution: {integrity: sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==} + resolution: {integrity: sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==, tarball: https://registry.npmjs.org/birpc/-/birpc-0.2.19.tgz} bn.js@4.12.0: resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==} @@ -1137,13 +1128,13 @@ packages: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==, tarball: https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz} brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==, tarball: https://registry.npmjs.org/braces/-/braces-3.0.3.tgz} + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} brorand@1.1.0: @@ -1172,7 +1163,7 @@ packages: resolution: {integrity: sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==} browserslist@4.24.2: - resolution: {integrity: sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==} + resolution: {integrity: sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==, tarball: https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -1186,7 +1177,7 @@ packages: resolution: {integrity: sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==} bundle-name@4.1.0: - resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==, tarball: https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz} engines: {node: '>=18'} call-bind@1.0.7: @@ -1194,18 +1185,18 @@ packages: engines: {node: '>= 0.4'} callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==, tarball: https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz} engines: {node: '>=6'} caniuse-lite@1.0.30001684: - resolution: {integrity: sha512-G1LRwLIQjBQoyq0ZJGqGIJUXzJ8irpbjHLpVRXDvBEScFJ9b17sgK6vlx0GAJFE21okD7zXl08rRRUfq6HdoEQ==} + resolution: {integrity: sha512-G1LRwLIQjBQoyq0ZJGqGIJUXzJ8irpbjHLpVRXDvBEScFJ9b17sgK6vlx0GAJFE21okD7zXl08rRRUfq6HdoEQ==, tarball: https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001684.tgz} 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==} + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==, tarball: https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz} engines: {node: '>=10'} chokidar@3.6.0: @@ -1223,7 +1214,7 @@ packages: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==, tarball: https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz} engines: {node: '>=7.0.0'} color-name@1.1.3: @@ -1245,7 +1236,7 @@ packages: resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==} concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==, tarball: https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz} console-browserify@1.2.0: resolution: {integrity: sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==} @@ -1258,13 +1249,13 @@ packages: engines: {node: '>=12'} convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==, tarball: https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz} copy-anything@2.0.6: resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==} copy-anything@3.0.5: - resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} + resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==, tarball: https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz} engines: {node: '>=12.13'} copy-to-clipboard@3.3.3: @@ -1285,10 +1276,6 @@ packages: create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - cross-spawn@7.0.5: - resolution: {integrity: sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==} - engines: {node: '>= 8'} - cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -1328,15 +1315,18 @@ packages: supports-color: optional: true + decimal.js@10.4.3: + resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==, tarball: https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz} + deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==, tarball: https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz} default-browser-id@5.0.0: - resolution: {integrity: sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==} + resolution: {integrity: sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==, tarball: https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz} engines: {node: '>=18'} default-browser@5.2.1: - resolution: {integrity: sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==} + resolution: {integrity: sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==, tarball: https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz} engines: {node: '>=18'} define-data-property@1.1.4: @@ -1344,7 +1334,7 @@ packages: engines: {node: '>= 0.4'} define-lazy-prop@3.0.0: - resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==, tarball: https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz} engines: {node: '>=12'} define-properties@1.2.1: @@ -1361,11 +1351,11 @@ packages: resolution: {integrity: sha512-ArzcM/II1wCCujdCNyQjXrAFwS4mrLh4C7DZWlaI8mdh7h3BfKdNd3bKXITfl2PT9FtfQqaGvhi1vPRQPimjGA==} engines: {node: '>=10'} - echarts@5.5.1: - resolution: {integrity: sha512-Fce8upazaAXUVUVsjgV6mBnGuqgO+JNDlcgF79Dksy4+wgGpQB2lmYoO4TSweFg/mZITdpGHomw/cNBJZj1icA==} + echarts@5.6.0: + resolution: {integrity: sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==, tarball: https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz} electron-to-chromium@1.5.66: - resolution: {integrity: sha512-pI2QF6+i+zjPbqRzJwkMvtvkdI7MjVbSh2g8dlMguDJIXEPw+kwasS1Jl+YGPEBfGVxsVgGUratAKymPdPo2vQ==} + resolution: {integrity: sha512-pI2QF6+i+zjPbqRzJwkMvtvkdI7MjVbSh2g8dlMguDJIXEPw+kwasS1Jl+YGPEBfGVxsVgGUratAKymPdPo2vQ==, tarball: https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.66.tgz} elliptic@6.6.0: resolution: {integrity: sha512-dpwoQcLc/2WLQvJvLRHKZ+f9FgOdjnq11rurqwekGQygGPsYSK29OMMD2WalatiqQ+XGFDglTNixpPfI+lpaAA==} @@ -1379,7 +1369,7 @@ packages: hasBin: true error-stack-parser-es@0.1.5: - resolution: {integrity: sha512-xHku1X40RO+fO8yJ8Wh2f2rZWVjqyhb1zgq1yZ8aZRQkv6OOKhKWRUaht3eSCUbAOBaKIgM+ykwFLE+QUxgGeg==} + resolution: {integrity: sha512-xHku1X40RO+fO8yJ8Wh2f2rZWVjqyhb1zgq1yZ8aZRQkv6OOKhKWRUaht3eSCUbAOBaKIgM+ykwFLE+QUxgGeg==, tarball: https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-0.1.5.tgz} es-define-property@1.0.0: resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} @@ -1389,13 +1379,13 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - esbuild@0.24.0: - resolution: {integrity: sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==, tarball: https://registry.npmjs.org/esbuild/-/esbuild-0.24.0.tgz} + esbuild@0.24.2: + resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==, tarball: https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz} engines: {node: '>=18'} hasBin: true escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==, tarball: https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz} engines: {node: '>=6'} escape-string-regexp@1.0.5: @@ -1403,7 +1393,7 @@ packages: engines: {node: '>=0.8.0'} escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==, tarball: https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz} engines: {node: '>=10'} eslint-config-prettier@9.1.0: @@ -1423,7 +1413,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} eslint-scope@8.2.0: - resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} + resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==, tarball: https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@3.4.3: @@ -1434,8 +1424,8 @@ packages: resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.16.0: - resolution: {integrity: sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA==} + eslint@9.17.0: + resolution: {integrity: sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==, tarball: https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -1445,7 +1435,7 @@ packages: optional: true espree@10.3.0: - resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==, tarball: https://registry.npmjs.org/espree/-/espree-10.3.0.tgz} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} espree@9.6.1: @@ -1468,7 +1458,7 @@ packages: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==, tarball: https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz} engines: {node: '>=0.10.0'} events@3.3.0: @@ -1479,35 +1469,38 @@ packages: resolution: {integrity: sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==} execa@9.5.1: - resolution: {integrity: sha512-QY5PPtSonnGwhhHDNI7+3RvY285c7iuJFFB+lU+oEzMY/gEGJ808owqJsrr8Otd1E/x07po1LkUBmdAc5duPAg==} + resolution: {integrity: sha512-QY5PPtSonnGwhhHDNI7+3RvY285c7iuJFFB+lU+oEzMY/gEGJ808owqJsrr8Otd1E/x07po1LkUBmdAc5duPAg==, tarball: https://registry.npmjs.org/execa/-/execa-9.5.1.tgz} engines: {node: ^18.19.0 || >=20.5.0} + fast-content-type-parse@2.0.1: + resolution: {integrity: sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==, tarball: https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz} + fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, tarball: https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz} fast-glob@3.3.2: - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==, tarball: https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz} + 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==} + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==, tarball: https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz} fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==, tarball: https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz} fastq@1.17.1: - resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==, tarball: https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz} + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} figures@6.1.0: - resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} + resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==, tarball: https://registry.npmjs.org/figures/-/figures-6.1.0.tgz} engines: {node: '>=18'} file-entry-cache@8.0.0: - resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==, tarball: https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz} engines: {node: '>=16.0.0'} fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==, tarball: https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz} + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} find-up@5.0.0: @@ -1515,17 +1508,17 @@ packages: engines: {node: '>=10'} flat-cache@4.0.1: - resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==, tarball: https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz} engines: {node: '>=16'} flatted@3.3.1: - resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==, tarball: https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz} for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} fs-extra@11.2.0: - resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} + resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==, tarball: https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz} engines: {node: '>=14.14'} fsevents@2.3.3: @@ -1541,7 +1534,7 @@ packages: engines: {node: '>=14.16'} gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==, tarball: https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz} engines: {node: '>=6.9.0'} get-intrinsic@1.2.4: @@ -1549,15 +1542,15 @@ packages: engines: {node: '>= 0.4'} get-stream@9.0.1: - resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==, tarball: https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz} engines: {node: '>=18'} glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==, tarball: https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz} + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==, tarball: https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz} engines: {node: '>=10.13.0'} globals@11.12.0: @@ -1569,7 +1562,7 @@ packages: engines: {node: '>=8'} globals@14.0.0: - resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==, tarball: https://registry.npmjs.org/globals/-/globals-14.0.0.tgz} engines: {node: '>=18'} gopd@1.0.1: @@ -1586,7 +1579,7 @@ packages: engines: {node: '>=4'} has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==, tarball: https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz} engines: {node: '>=8'} has-property-descriptors@1.0.2: @@ -1627,17 +1620,17 @@ packages: resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} hookable@5.5.3: - resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==, tarball: https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz} html-tags@3.3.1: - resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} + resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==, tarball: https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz} engines: {node: '>=8'} https-browserify@1.0.0: resolution: {integrity: sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==} human-signals@8.0.0: - resolution: {integrity: sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==} + resolution: {integrity: sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==, tarball: https://registry.npmjs.org/human-signals/-/human-signals-8.0.0.tgz} engines: {node: '>=18.18.0'} husky@9.1.7: @@ -1665,11 +1658,11 @@ packages: resolution: {integrity: sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==, tarball: https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz} import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==, tarball: https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz} engines: {node: '>=6'} imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==, tarball: https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz} engines: {node: '>=0.8.19'} inherits@2.0.4: @@ -1698,7 +1691,7 @@ packages: resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} is-docker@3.0.0: - resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==, tarball: https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} hasBin: true @@ -1719,7 +1712,7 @@ packages: engines: {node: '>=10.23.0'} is-inside-container@1.0.0: - resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==, tarball: https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz} engines: {node: '>=14.16'} hasBin: true @@ -1732,11 +1725,11 @@ packages: engines: {node: '>= 0.4'} is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==, tarball: https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz} + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} is-plain-obj@4.1.0: - resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==, tarball: https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz} engines: {node: '>=12'} is-regexp@3.1.0: @@ -1744,7 +1737,7 @@ packages: engines: {node: '>=12'} is-stream@4.0.1: - resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==, tarball: https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz} engines: {node: '>=18'} is-typed-array@1.1.13: @@ -1752,18 +1745,18 @@ packages: engines: {node: '>= 0.4'} is-unicode-supported@2.1.0: - resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==, tarball: https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz} engines: {node: '>=18'} is-what@3.14.1: resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==} is-what@4.1.16: - resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} + resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==, tarball: https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz} engines: {node: '>=12.13'} is-wsl@3.1.0: - resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} + resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==, tarball: https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz} engines: {node: '>=16'} isarray@1.0.0: @@ -1773,7 +1766,7 @@ packages: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} isexe@3.1.1: - resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} + resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==, tarball: https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz} engines: {node: '>=16'} isomorphic-timers-promises@1.0.1: @@ -1784,7 +1777,7 @@ packages: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==, tarball: https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz} hasBin: true jsesc@2.5.2: @@ -1793,36 +1786,36 @@ packages: hasBin: true jsesc@3.0.2: - resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==, tarball: https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz} engines: {node: '>=6'} hasBin: true json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==, tarball: https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz} json-parse-even-better-errors@4.0.0: - resolution: {integrity: sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==} + resolution: {integrity: sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==, tarball: https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz} engines: {node: ^18.17.0 || >=20.5.0} json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==, tarball: https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz} json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==, tarball: https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz} json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==, tarball: https://registry.npmjs.org/json5/-/json5-2.2.3.tgz} engines: {node: '>=6'} hasBin: true jsonfile@6.1.0: - resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==, tarball: https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz} keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==, tarball: https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz} kolorist@1.8.0: - resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==, tarball: https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz} less@4.2.1: resolution: {integrity: sha512-CasaJidTIhWmjcqv0Uj5vccMI7pJgfD9lMkKtlnTHAdJdYK/7l8pM9tumLyJ0zhbD4KJLo/YvTj+xznQd5NBhg==} @@ -1830,7 +1823,7 @@ packages: hasBin: true levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==, tarball: https://registry.npmjs.org/levn/-/levn-0.4.1.tgz} engines: {node: '>= 0.8.0'} linkify-it@5.0.0: @@ -1841,19 +1834,19 @@ packages: engines: {node: '>=10'} lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==, tarball: https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz} lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==, tarball: https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz} magic-string@0.30.12: resolution: {integrity: sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==} magic-string@0.30.14: - resolution: {integrity: sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==} + resolution: {integrity: sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==, tarball: https://registry.npmjs.org/magic-string/-/magic-string-0.30.14.tgz} magic-string@0.30.9: resolution: {integrity: sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw==} @@ -1874,15 +1867,15 @@ packages: resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} memorystream@0.3.1: - resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} + resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==, tarball: https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz} engines: {node: '>= 0.10.0'} merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==, tarball: https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz} + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==, tarball: https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz} + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} miller-rabin@4.0.1: @@ -1901,7 +1894,7 @@ packages: resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==, tarball: https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz} minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} @@ -1910,11 +1903,11 @@ packages: mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} - monaco-editor@0.52.0: - resolution: {integrity: sha512-OeWhNpABLCeTqubfqLMXGsqf6OmPU6pHM85kF3dhy6kq5hnhuVS1p3VrEW/XhWHc71P2tHyS5JFySD8mgs1crw==} + monaco-editor@0.52.2: + resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==, tarball: https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz} mrmime@2.0.0: - resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==} + resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==, tarball: https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz} engines: {node: '>=10'} ms@2.1.2: @@ -1927,12 +1920,12 @@ packages: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} nanoid@3.3.8: - resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==, tarball: https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true nanoid@5.0.9: - resolution: {integrity: sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q==} + resolution: {integrity: sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q==, tarball: https://registry.npmjs.org/nanoid/-/nanoid-5.0.9.tgz} engines: {node: ^18 || >=20} hasBin: true @@ -1945,7 +1938,7 @@ packages: hasBin: true node-releases@2.0.18: - resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==, tarball: https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz} node-stdlib-browser@1.2.0: resolution: {integrity: sha512-VSjFxUhRhkyed8AtLwSCkMrJRfQ3e2lGtG3sP6FEgaLKBBbxM/dLfjRe1+iLhjvyLFW3tBQ8+c0pcOtXGbAZJg==} @@ -1959,16 +1952,16 @@ packages: resolution: {integrity: sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==} npm-normalize-package-bin@4.0.0: - resolution: {integrity: sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==} + resolution: {integrity: sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==, tarball: https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz} engines: {node: ^18.17.0 || >=20.5.0} - npm-run-all2@7.0.1: - resolution: {integrity: sha512-Adbv+bJQ8UTAM03rRODqrO5cx0YU5KCG2CvHtSURiadvdTjjgGJXdbc1oQ9CXBh9dnGfHSoSB1Web/0Dzp6kOQ==} + npm-run-all2@7.0.2: + resolution: {integrity: sha512-7tXR+r9hzRNOPNTvXegM+QzCuMjzUIIq66VDunL6j60O4RrExx32XUhlrS7UK4VcdGw5/Wxzb3kfNcFix9JKDA==, tarball: https://registry.npmjs.org/npm-run-all2/-/npm-run-all2-7.0.2.tgz} engines: {node: ^18.17.0 || >=20.5.0, npm: '>= 9'} hasBin: true npm-run-path@6.0.0: - resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} + resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==, tarball: https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz} engines: {node: '>=18'} nth-check@2.1.1: @@ -1993,11 +1986,11 @@ packages: engines: {node: '>= 0.4'} open@10.1.0: - resolution: {integrity: sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==} + resolution: {integrity: sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==, tarball: https://registry.npmjs.org/open/-/open-10.1.0.tgz} engines: {node: '>=18'} optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==, tarball: https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz} engines: {node: '>= 0.8.0'} os-browserify@0.3.0: @@ -2015,7 +2008,7 @@ packages: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==, tarball: https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz} engines: {node: '>=6'} parse-asn1@5.1.7: @@ -2023,7 +2016,7 @@ packages: engines: {node: '>= 0.10'} parse-ms@4.0.0: - resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==, tarball: https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz} engines: {node: '>=18'} parse-node-version@1.0.1: @@ -2042,21 +2035,21 @@ packages: engines: {node: '>=8'} path-key@4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==, tarball: https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz} engines: {node: '>=12'} path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} pathe@1.1.2: - resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==, tarball: https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz} pbkdf2@3.1.2: resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==} engines: {node: '>=0.12'} perfect-debounce@1.0.0: - resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==, tarball: https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz} picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2066,11 +2059,11 @@ packages: engines: {node: '>=8.6'} picomatch@4.0.2: - resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==, tarball: https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz} engines: {node: '>=12'} pidtree@0.6.0: - resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} + resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==, tarball: https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz} engines: {node: '>=0.10'} hasBin: true @@ -2079,7 +2072,7 @@ packages: engines: {node: '>=6'} pinia@2.3.0: - resolution: {integrity: sha512-ohZj3jla0LL0OH5PlLTDMzqKiVw2XARmC1XYLdLWIPBMdhDW/123ZWr4zVAhtJm+aoSkFa13pYXskAvAscIkhQ==, tarball: https://registry.npmjs.org/pinia/-/pinia-2.3.0.tgz} + resolution: {integrity: sha512-ohZj3jla0LL0OH5PlLTDMzqKiVw2XARmC1XYLdLWIPBMdhDW/123ZWr4zVAhtJm+aoSkFa13pYXskAvAscIkhQ==} peerDependencies: typescript: '>=4.4.4' vue: ^2.7.0 || ^3.5.11 @@ -2100,20 +2093,20 @@ packages: engines: {node: '>=4'} postcss@8.4.49: - resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} + resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==, tarball: https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz} engines: {node: ^10 || ^12 || >=14} prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==, tarball: https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz} engines: {node: '>= 0.8.0'} prettier@3.4.2: - resolution: {integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==, tarball: https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz} + resolution: {integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==} engines: {node: '>=14'} hasBin: true pretty-ms@9.2.0: - resolution: {integrity: sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==} + resolution: {integrity: sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==, tarball: https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.2.0.tgz} engines: {node: '>=18'} process-nextick-args@2.0.1: @@ -2137,7 +2130,7 @@ packages: resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==, tarball: https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz} engines: {node: '>=6'} qs@6.12.1: @@ -2149,7 +2142,7 @@ packages: engines: {node: '>=0.4.x'} queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, tarball: https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz} + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} @@ -2158,7 +2151,7 @@ packages: resolution: {integrity: sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==} read-package-json-fast@4.0.0: - resolution: {integrity: sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==} + resolution: {integrity: sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==, tarball: https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-4.0.0.tgz} engines: {node: ^18.17.0 || >=20.5.0} readable-stream@2.3.8: @@ -2176,7 +2169,7 @@ packages: resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==, tarball: https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz} engines: {node: '>=4'} resolve@1.22.8: @@ -2184,11 +2177,11 @@ packages: hasBin: true reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==, tarball: https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz} + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} rfdc@1.4.1: - resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==, tarball: https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz} ripemd160@2.0.2: resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==} @@ -2199,11 +2192,11 @@ packages: hasBin: true run-applescript@7.0.0: - resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==} + resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==, tarball: https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz} engines: {node: '>=18'} run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==, tarball: https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz} + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} @@ -2230,7 +2223,7 @@ packages: hasBin: true semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==, tarball: https://registry.npmjs.org/semver/-/semver-6.3.1.tgz} hasBin: true semver@7.6.2: @@ -2263,21 +2256,21 @@ packages: engines: {node: '>=8'} shell-quote@1.8.1: - resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} + resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==, tarball: https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz} side-channel@1.0.6: resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} engines: {node: '>= 0.4'} signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==, tarball: https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz} engines: {node: '>=14'} simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} sirv@3.0.0: - resolution: {integrity: sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==} + resolution: {integrity: sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==, tarball: https://registry.npmjs.org/sirv/-/sirv-3.0.0.tgz} engines: {node: '>=18'} source-map-js@1.2.1: @@ -2289,7 +2282,7 @@ packages: engines: {node: '>=0.10.0'} speakingurl@14.0.1: - resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} + resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==, tarball: https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz} engines: {node: '>=0.10.0'} state-local@1.0.7: @@ -2308,11 +2301,11 @@ packages: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} strip-final-newline@4.0.0: - resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} + resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==, tarball: https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz} engines: {node: '>=18'} strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==, tarball: https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz} engines: {node: '>=8'} super-regex@0.2.0: @@ -2320,7 +2313,7 @@ packages: engines: {node: '>=14.16'} superjson@2.2.1: - resolution: {integrity: sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==} + resolution: {integrity: sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==, tarball: https://registry.npmjs.org/superjson/-/superjson-2.2.1.tgz} engines: {node: '>=16'} supports-color@5.5.0: @@ -2328,7 +2321,7 @@ packages: engines: {node: '>=4'} supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==, tarball: https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz} engines: {node: '>=8'} supports-preserve-symlinks-flag@1.0.0: @@ -2336,7 +2329,7 @@ packages: engines: {node: '>= 0.4'} svg-tags@1.0.0: - resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==} + resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==, tarball: https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz} time-span@5.1.0: resolution: {integrity: sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==} @@ -2351,24 +2344,24 @@ packages: engines: {node: '>=4'} to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==, tarball: https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz} + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} toggle-selection@1.0.6: resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} totalist@3.0.1: - resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==, tarball: https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz} engines: {node: '>=6'} ts-api-utils@1.4.1: - resolution: {integrity: sha512-5RU2/lxTA3YUZxju61HO2U6EoZLvBLtmV2mbTvqyu4a/7s7RmJPT+1YekhMVsQhznRWk/czIwDUg+V8Q9ZuG4w==, tarball: https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.1.tgz} + resolution: {integrity: sha512-5RU2/lxTA3YUZxju61HO2U6EoZLvBLtmV2mbTvqyu4a/7s7RmJPT+1YekhMVsQhznRWk/czIwDUg+V8Q9ZuG4w==} engines: {node: '>=16'} peerDependencies: typescript: '>=4.2.0' tslib@2.3.0: - resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==} + resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==, tarball: https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz} tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -2377,22 +2370,19 @@ packages: resolution: {integrity: sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==} type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==, tarball: https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz} engines: {node: '>= 0.8.0'} type-fest@0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} - typescript-eslint@8.17.0: - resolution: {integrity: sha512-409VXvFd/f1br1DCbuKNFqQpXICoTB+V51afcwG1pn1a3Cp92MqAUges3YjwEdQ0cMUoCIodjVDAYzyD8h3SYA==, tarball: https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.17.0.tgz} + typescript-eslint@8.18.2: + resolution: {integrity: sha512-KuXezG6jHkvC3MvizeXgupZzaG5wjhU3yE8E7e6viOvAvD9xAWYp8/vy0WULTGe9DYDWcQu7aW03YIV3mSitrQ==, tarball: https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.18.2.tgz} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + typescript: '>=4.8.4 <5.8.0' typescript@5.6.3: resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} @@ -2403,27 +2393,27 @@ packages: resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} undici-types@6.20.0: - resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==, tarball: https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz} unicorn-magic@0.3.0: - resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==, tarball: https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz} engines: {node: '>=18'} universal-user-agent@7.0.2: - resolution: {integrity: sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==} + resolution: {integrity: sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==, tarball: https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz} universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==, tarball: https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz} engines: {node: '>= 10.0.0'} update-browserslist-db@1.1.1: - resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} + resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==, tarball: https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz} hasBin: true peerDependencies: browserslist: '>= 4.21.0' uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==, tarball: https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz} url-join@5.0.0: resolution: {integrity: sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==} @@ -2438,8 +2428,8 @@ packages: util@0.12.5: resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} - uuid@11.0.3: - resolution: {integrity: sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==} + uuid@11.0.4: + resolution: {integrity: sha512-IzL6VtTTYcAhA/oghbFJ1Dkmqev+FpQWnCBaKq/gUluLxliWvO8DPFWfIviRmYbtaavtSQe4WBL++rFjdcGWEg==, tarball: https://registry.npmjs.org/uuid/-/uuid-11.0.4.tgz} hasBin: true vite-bundle-analyzer@0.15.2: @@ -2447,16 +2437,16 @@ packages: hasBin: true vite-hot-client@0.2.4: - resolution: {integrity: sha512-a1nzURqO7DDmnXqabFOliz908FRmIppkBKsJthS8rbe8hBEXwEwe4C3Pp33Z1JoFCYfVL4kTOMLKk0ZZxREIeA==} + resolution: {integrity: sha512-a1nzURqO7DDmnXqabFOliz908FRmIppkBKsJthS8rbe8hBEXwEwe4C3Pp33Z1JoFCYfVL4kTOMLKk0ZZxREIeA==, tarball: https://registry.npmjs.org/vite-hot-client/-/vite-hot-client-0.2.4.tgz} peerDependencies: vite: ^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 - vite-plugin-inspect@0.8.8: - resolution: {integrity: sha512-aZlBuXsWUPJFmMK92GIv6lH7LrwG2POu4KJ+aEdcqnu92OAf+rhBnfMDQvxIJPEB7hE2t5EyY/PMgf5aDLT8EA==} + vite-plugin-inspect@0.8.9: + resolution: {integrity: sha512-22/8qn+LYonzibb1VeFZmISdVao5kC22jmEKm24vfFE8siEn47EpVcCLYMv6iKOYMJfjSvSJfueOwcFCkUnV3A==, tarball: https://registry.npmjs.org/vite-plugin-inspect/-/vite-plugin-inspect-0.8.9.tgz} engines: {node: '>=14'} peerDependencies: '@nuxt/kit': '*' - vite: ^3.1.0 || ^4.0.0 || ^5.0.0-0 + vite: ^3.1.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.1 peerDependenciesMeta: '@nuxt/kit': optional: true @@ -2469,19 +2459,19 @@ packages: vite-plugin-remove-console@2.2.0: resolution: {integrity: sha512-qgjh5pz75MdE9Kzs8J0kBwaCfifHV0ezRbB9rpGsIOxam+ilcGV7WOk91vFJXquzRmiKrFh3Hxlh0JJWAmXTbQ==} - vite-plugin-vue-devtools@7.6.7: - resolution: {integrity: sha512-H1ZyjtpWjP5mHA5R15sQeYgAARuh2Myg3TDFXWZK6QOQRy8s3XjTIt319DogVjU/x3rC3L/jJQjIasRU04mWXA==} + vite-plugin-vue-devtools@7.6.8: + resolution: {integrity: sha512-32aIps8C1Y7UEoqyWf+ES3J1OozsCYMIqTqd+I5qass+R0Tcf8SaA2bX1/rskAzkcKCteVoBjEENmqwTcMebbw==, tarball: https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-7.6.8.tgz} engines: {node: '>=v14.21.3'} peerDependencies: vite: ^3.1.0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 vite-plugin-vue-inspector@5.3.1: - resolution: {integrity: sha512-cBk172kZKTdvGpJuzCCLg8lJ909wopwsu3Ve9FsL1XsnLBiRT9U3MePcqrgGHgCX2ZgkqZmAGR8taxw+TV6s7A==} + resolution: {integrity: sha512-cBk172kZKTdvGpJuzCCLg8lJ909wopwsu3Ve9FsL1XsnLBiRT9U3MePcqrgGHgCX2ZgkqZmAGR8taxw+TV6s7A==, tarball: https://registry.npmjs.org/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-5.3.1.tgz} peerDependencies: vite: ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 - vite@6.0.3: - resolution: {integrity: sha512-Cmuo5P0ENTN6HxLSo6IHsjCLn/81Vgrp81oaiFFMRa8gGDj5xEjIcEpf2ZymZtZR8oU0P2JX5WuUp/rlXcHkAw==, tarball: https://registry.npmjs.org/vite/-/vite-6.0.3.tgz} + vite@6.0.6: + resolution: {integrity: sha512-NSjmUuckPmDU18bHz7QZ+bTYhRR0iA72cs2QAxCqDpafJ0S6qetco0LB3WW2OxlMHS0JmAv+yZ/R3uPmMyGTjQ==, tarball: https://registry.npmjs.org/vite/-/vite-6.0.6.tgz} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: @@ -2538,7 +2528,7 @@ packages: optional: true vue-demi@0.14.10: - resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==, tarball: https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz} + resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} engines: {node: '>=12'} hasBin: true peerDependencies: @@ -2564,8 +2554,8 @@ packages: peerDependencies: eslint: '>=6.0.0' - vue-i18n@10.0.5: - resolution: {integrity: sha512-9/gmDlCblz3i8ypu/afiIc/SUIfTTE1mr0mZhb9pk70xo2csHAM9mp2gdQ3KD2O0AM3Hz/5ypb+FycTj/lHlPQ==} + vue-i18n@11.0.1: + resolution: {integrity: sha512-pWAT8CusK8q9/EpN7V3oxwHwxWm6+Kp2PeTZmRGvdZTkUzMQDpbbmHp0TwQ8xw04XKm23cr6B4GL72y3W8Yekg==, tarball: https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.0.1.tgz} engines: {node: '>= 16'} peerDependencies: vue: ^3.0.0 @@ -2609,12 +2599,12 @@ packages: hasBin: true which@5.0.0: - resolution: {integrity: sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==} + resolution: {integrity: sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==, tarball: https://registry.npmjs.org/which/-/which-5.0.0.tgz} engines: {node: ^18.17.0 || >=20.5.0} hasBin: true word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==, tarball: https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz} engines: {node: '>=0.10.0'} xml-name-validator@4.0.0: @@ -2626,18 +2616,18 @@ packages: engines: {node: '>=0.4'} yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==, tarball: https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz} yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} yoctocolors@2.1.1: - resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==} + resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==, tarball: https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz} engines: {node: '>=18'} - zrender@5.6.0: - resolution: {integrity: sha512-uzgraf4njmmHAbEUxMJ8Oxg+P3fT04O+9p7gY+wJRVxo8Ge+KmYv0WJev945EH4wFuc4OY2NLXz46FZrWS9xJg==} + zrender@5.6.1: + resolution: {integrity: sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==, tarball: https://registry.npmjs.org/zrender/-/zrender-5.6.1.tgz} snapshots: @@ -2945,81 +2935,84 @@ snapshots: '@dzangolab/flag-icon-css@3.4.5': {} - '@esbuild/aix-ppc64@0.24.0': + '@esbuild/aix-ppc64@0.24.2': + optional: true + + '@esbuild/android-arm64@0.24.2': optional: true - '@esbuild/android-arm64@0.24.0': + '@esbuild/android-arm@0.24.2': optional: true - '@esbuild/android-arm@0.24.0': + '@esbuild/android-x64@0.24.2': optional: true - '@esbuild/android-x64@0.24.0': + '@esbuild/darwin-arm64@0.24.2': optional: true - '@esbuild/darwin-arm64@0.24.0': + '@esbuild/darwin-x64@0.24.2': optional: true - '@esbuild/darwin-x64@0.24.0': + '@esbuild/freebsd-arm64@0.24.2': optional: true - '@esbuild/freebsd-arm64@0.24.0': + '@esbuild/freebsd-x64@0.24.2': optional: true - '@esbuild/freebsd-x64@0.24.0': + '@esbuild/linux-arm64@0.24.2': optional: true - '@esbuild/linux-arm64@0.24.0': + '@esbuild/linux-arm@0.24.2': optional: true - '@esbuild/linux-arm@0.24.0': + '@esbuild/linux-ia32@0.24.2': optional: true - '@esbuild/linux-ia32@0.24.0': + '@esbuild/linux-loong64@0.24.2': optional: true - '@esbuild/linux-loong64@0.24.0': + '@esbuild/linux-mips64el@0.24.2': optional: true - '@esbuild/linux-mips64el@0.24.0': + '@esbuild/linux-ppc64@0.24.2': optional: true - '@esbuild/linux-ppc64@0.24.0': + '@esbuild/linux-riscv64@0.24.2': optional: true - '@esbuild/linux-riscv64@0.24.0': + '@esbuild/linux-s390x@0.24.2': optional: true - '@esbuild/linux-s390x@0.24.0': + '@esbuild/linux-x64@0.24.2': optional: true - '@esbuild/linux-x64@0.24.0': + '@esbuild/netbsd-arm64@0.24.2': optional: true - '@esbuild/netbsd-x64@0.24.0': + '@esbuild/netbsd-x64@0.24.2': optional: true - '@esbuild/openbsd-arm64@0.24.0': + '@esbuild/openbsd-arm64@0.24.2': optional: true - '@esbuild/openbsd-x64@0.24.0': + '@esbuild/openbsd-x64@0.24.2': optional: true - '@esbuild/sunos-x64@0.24.0': + '@esbuild/sunos-x64@0.24.2': optional: true - '@esbuild/win32-arm64@0.24.0': + '@esbuild/win32-arm64@0.24.2': optional: true - '@esbuild/win32-ia32@0.24.0': + '@esbuild/win32-ia32@0.24.2': optional: true - '@esbuild/win32-x64@0.24.0': + '@esbuild/win32-x64@0.24.2': optional: true - '@eslint-community/eslint-utils@4.4.1(eslint@9.16.0)': + '@eslint-community/eslint-utils@4.4.1(eslint@9.17.0)': dependencies: - eslint: 9.16.0 + eslint: 9.17.0 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} @@ -3048,7 +3041,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.16.0': {} + '@eslint/js@9.17.0': {} '@eslint/object-schema@2.1.4': {} @@ -3056,30 +3049,31 @@ snapshots: dependencies: levn: 0.4.1 - '@formatjs/ecma402-abstract@2.2.4': + '@formatjs/ecma402-abstract@2.3.2': dependencies: - '@formatjs/fast-memoize': 2.2.3 - '@formatjs/intl-localematcher': 0.5.8 + '@formatjs/fast-memoize': 2.2.6 + '@formatjs/intl-localematcher': 0.5.10 + decimal.js: 10.4.3 tslib: 2.8.1 - '@formatjs/fast-memoize@2.2.3': + '@formatjs/fast-memoize@2.2.6': dependencies: tslib: 2.8.1 - '@formatjs/intl-durationformat@0.6.4': + '@formatjs/intl-durationformat@0.7.2': dependencies: - '@formatjs/ecma402-abstract': 2.2.4 - '@formatjs/intl-localematcher': 0.5.8 + '@formatjs/ecma402-abstract': 2.3.2 + '@formatjs/intl-localematcher': 0.5.10 tslib: 2.8.1 - '@formatjs/intl-localematcher@0.5.8': + '@formatjs/intl-localematcher@0.5.10': dependencies: tslib: 2.8.1 - '@guolao/vue-monaco-editor@1.5.4(monaco-editor@0.52.0)(vue@3.5.13(typescript@5.6.3))': + '@guolao/vue-monaco-editor@1.5.4(monaco-editor@0.52.2)(vue@3.5.13(typescript@5.6.3))': dependencies: - '@monaco-editor/loader': 1.4.0(monaco-editor@0.52.0) - monaco-editor: 0.52.0 + '@monaco-editor/loader': 1.4.0(monaco-editor@0.52.2) + monaco-editor: 0.52.2 vue: 3.5.13(typescript@5.6.3) vue-demi: 0.14.10(vue@3.5.13(typescript@5.6.3)) @@ -3096,17 +3090,17 @@ snapshots: '@humanwhocodes/retry@0.4.1': {} - '@intlify/core-base@10.0.5': + '@intlify/core-base@11.0.1': dependencies: - '@intlify/message-compiler': 10.0.5 - '@intlify/shared': 10.0.5 + '@intlify/message-compiler': 11.0.1 + '@intlify/shared': 11.0.1 - '@intlify/message-compiler@10.0.5': + '@intlify/message-compiler@11.0.1': dependencies: - '@intlify/shared': 10.0.5 + '@intlify/shared': 11.0.1 source-map-js: 1.2.1 - '@intlify/shared@10.0.5': {} + '@intlify/shared@11.0.1': {} '@jridgewell/gen-mapping@0.3.5': dependencies: @@ -3127,9 +3121,9 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.4.15 - '@monaco-editor/loader@1.4.0(monaco-editor@0.52.0)': + '@monaco-editor/loader@1.4.0(monaco-editor@0.52.2)': dependencies: - monaco-editor: 0.52.0 + monaco-editor: 0.52.2 state-local: 1.0.7 '@nodelib/fs.scandir@2.1.5': @@ -3146,41 +3140,42 @@ snapshots: '@octokit/auth-token@5.1.1': {} - '@octokit/core@6.1.2': + '@octokit/core@6.1.3': dependencies: '@octokit/auth-token': 5.1.1 - '@octokit/graphql': 8.1.1 - '@octokit/request': 9.1.1 - '@octokit/request-error': 6.1.5 - '@octokit/types': 13.5.0 + '@octokit/graphql': 8.1.2 + '@octokit/request': 9.1.4 + '@octokit/request-error': 6.1.6 + '@octokit/types': 13.6.2 before-after-hook: 3.0.2 universal-user-agent: 7.0.2 '@octokit/endpoint@10.1.1': dependencies: - '@octokit/types': 13.5.0 + '@octokit/types': 13.6.2 universal-user-agent: 7.0.2 - '@octokit/graphql@8.1.1': + '@octokit/graphql@8.1.2': dependencies: - '@octokit/request': 9.1.1 - '@octokit/types': 13.5.0 + '@octokit/request': 9.1.4 + '@octokit/types': 13.6.2 universal-user-agent: 7.0.2 '@octokit/openapi-types@22.2.0': {} - '@octokit/request-error@6.1.5': + '@octokit/request-error@6.1.6': dependencies: - '@octokit/types': 13.5.0 + '@octokit/types': 13.6.2 - '@octokit/request@9.1.1': + '@octokit/request@9.1.4': dependencies: '@octokit/endpoint': 10.1.1 - '@octokit/request-error': 6.1.5 - '@octokit/types': 13.5.0 + '@octokit/request-error': 6.1.6 + '@octokit/types': 13.6.2 + fast-content-type-parse: 2.0.1 universal-user-agent: 7.0.2 - '@octokit/types@13.5.0': + '@octokit/types@13.6.2': dependencies: '@octokit/openapi-types': 22.2.0 @@ -3298,7 +3293,7 @@ snapshots: '@types/node@16.18.121': {} - '@types/node@22.10.1': + '@types/node@22.10.2': dependencies: undici-types: 6.20.0 @@ -3306,91 +3301,86 @@ snapshots: '@types/web-bluetooth@0.0.20': {} - '@typescript-eslint/eslint-plugin@8.17.0(@typescript-eslint/parser@8.17.0(eslint@9.16.0)(typescript@5.6.3))(eslint@9.16.0)(typescript@5.6.3)': + '@typescript-eslint/eslint-plugin@8.18.2(@typescript-eslint/parser@8.18.2(eslint@9.17.0)(typescript@5.6.3))(eslint@9.17.0)(typescript@5.6.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.17.0(eslint@9.16.0)(typescript@5.6.3) - '@typescript-eslint/scope-manager': 8.17.0 - '@typescript-eslint/type-utils': 8.17.0(eslint@9.16.0)(typescript@5.6.3) - '@typescript-eslint/utils': 8.17.0(eslint@9.16.0)(typescript@5.6.3) - '@typescript-eslint/visitor-keys': 8.17.0 - eslint: 9.16.0 + '@typescript-eslint/parser': 8.18.2(eslint@9.17.0)(typescript@5.6.3) + '@typescript-eslint/scope-manager': 8.18.2 + '@typescript-eslint/type-utils': 8.18.2(eslint@9.17.0)(typescript@5.6.3) + '@typescript-eslint/utils': 8.18.2(eslint@9.17.0)(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.18.2 + eslint: 9.17.0 graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 ts-api-utils: 1.4.1(typescript@5.6.3) - optionalDependencies: typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.17.0(eslint@9.16.0)(typescript@5.6.3)': + '@typescript-eslint/parser@8.18.2(eslint@9.17.0)(typescript@5.6.3)': dependencies: - '@typescript-eslint/scope-manager': 8.17.0 - '@typescript-eslint/types': 8.17.0 - '@typescript-eslint/typescript-estree': 8.17.0(typescript@5.6.3) - '@typescript-eslint/visitor-keys': 8.17.0 + '@typescript-eslint/scope-manager': 8.18.2 + '@typescript-eslint/types': 8.18.2 + '@typescript-eslint/typescript-estree': 8.18.2(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.18.2 debug: 4.3.7 - eslint: 9.16.0 - optionalDependencies: + eslint: 9.17.0 typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.17.0': + '@typescript-eslint/scope-manager@8.18.2': dependencies: - '@typescript-eslint/types': 8.17.0 - '@typescript-eslint/visitor-keys': 8.17.0 + '@typescript-eslint/types': 8.18.2 + '@typescript-eslint/visitor-keys': 8.18.2 - '@typescript-eslint/type-utils@8.17.0(eslint@9.16.0)(typescript@5.6.3)': + '@typescript-eslint/type-utils@8.18.2(eslint@9.17.0)(typescript@5.6.3)': dependencies: - '@typescript-eslint/typescript-estree': 8.17.0(typescript@5.6.3) - '@typescript-eslint/utils': 8.17.0(eslint@9.16.0)(typescript@5.6.3) + '@typescript-eslint/typescript-estree': 8.18.2(typescript@5.6.3) + '@typescript-eslint/utils': 8.18.2(eslint@9.17.0)(typescript@5.6.3) debug: 4.3.7 - eslint: 9.16.0 + eslint: 9.17.0 ts-api-utils: 1.4.1(typescript@5.6.3) - optionalDependencies: typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.17.0': {} + '@typescript-eslint/types@8.18.2': {} - '@typescript-eslint/typescript-estree@8.17.0(typescript@5.6.3)': + '@typescript-eslint/typescript-estree@8.18.2(typescript@5.6.3)': dependencies: - '@typescript-eslint/types': 8.17.0 - '@typescript-eslint/visitor-keys': 8.17.0 + '@typescript-eslint/types': 8.18.2 + '@typescript-eslint/visitor-keys': 8.18.2 debug: 4.3.7 fast-glob: 3.3.2 is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.6.3 ts-api-utils: 1.4.1(typescript@5.6.3) - optionalDependencies: typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.17.0(eslint@9.16.0)(typescript@5.6.3)': + '@typescript-eslint/utils@8.18.2(eslint@9.17.0)(typescript@5.6.3)': dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.16.0) - '@typescript-eslint/scope-manager': 8.17.0 - '@typescript-eslint/types': 8.17.0 - '@typescript-eslint/typescript-estree': 8.17.0(typescript@5.6.3) - eslint: 9.16.0 - optionalDependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0) + '@typescript-eslint/scope-manager': 8.18.2 + '@typescript-eslint/types': 8.18.2 + '@typescript-eslint/typescript-estree': 8.18.2(typescript@5.6.3) + eslint: 9.17.0 typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.17.0': + '@typescript-eslint/visitor-keys@8.18.2': dependencies: - '@typescript-eslint/types': 8.17.0 + '@typescript-eslint/types': 8.18.2 eslint-visitor-keys: 4.2.0 - '@vitejs/plugin-vue@5.2.1(vite@6.0.3(@types/node@22.10.1)(less@4.2.1)(sass@1.77.8))(vue@3.5.13(typescript@5.6.3))': + '@vitejs/plugin-vue@5.2.1(vite@6.0.6(@types/node@22.10.2)(less@4.2.1)(sass@1.77.8))(vue@3.5.13(typescript@5.6.3))': dependencies: - vite: 6.0.3(@types/node@22.10.1)(less@4.2.1)(sass@1.77.8) + vite: 6.0.6(@types/node@22.10.2)(less@4.2.1)(sass@1.77.8) vue: 3.5.13(typescript@5.6.3) '@volar/language-core@2.4.8': @@ -3485,21 +3475,21 @@ snapshots: '@vue/devtools-api@6.6.4': {} - '@vue/devtools-core@7.6.7(vite@6.0.3(@types/node@22.10.1)(less@4.2.1)(sass@1.77.8))(vue@3.5.13(typescript@5.6.3))': + '@vue/devtools-core@7.6.8(vite@6.0.6(@types/node@22.10.2)(less@4.2.1)(sass@1.77.8))(vue@3.5.13(typescript@5.6.3))': dependencies: - '@vue/devtools-kit': 7.6.7 - '@vue/devtools-shared': 7.6.7 + '@vue/devtools-kit': 7.6.8 + '@vue/devtools-shared': 7.6.8 mitt: 3.0.1 nanoid: 5.0.9 pathe: 1.1.2 - vite-hot-client: 0.2.4(vite@6.0.3(@types/node@22.10.1)(less@4.2.1)(sass@1.77.8)) + vite-hot-client: 0.2.4(vite@6.0.6(@types/node@22.10.2)(less@4.2.1)(sass@1.77.8)) vue: 3.5.13(typescript@5.6.3) transitivePeerDependencies: - vite - '@vue/devtools-kit@7.6.7': + '@vue/devtools-kit@7.6.8': dependencies: - '@vue/devtools-shared': 7.6.7 + '@vue/devtools-shared': 7.6.8 birpc: 0.2.19 hookable: 5.5.3 mitt: 3.0.1 @@ -3507,7 +3497,7 @@ snapshots: speakingurl: 14.0.1 superjson: 2.2.1 - '@vue/devtools-shared@7.6.7': + '@vue/devtools-shared@7.6.8': dependencies: rfdc: 1.4.1 @@ -3555,18 +3545,18 @@ snapshots: typescript: 5.6.3 vue: 3.5.13(typescript@5.6.3) - '@vueuse/core@12.0.0(typescript@5.6.3)': + '@vueuse/core@12.3.0(typescript@5.6.3)': dependencies: '@types/web-bluetooth': 0.0.20 - '@vueuse/metadata': 12.0.0 - '@vueuse/shared': 12.0.0(typescript@5.6.3) + '@vueuse/metadata': 12.3.0 + '@vueuse/shared': 12.3.0(typescript@5.6.3) vue: 3.5.13(typescript@5.6.3) transitivePeerDependencies: - typescript - '@vueuse/metadata@12.0.0': {} + '@vueuse/metadata@12.3.0': {} - '@vueuse/shared@12.0.0(typescript@5.6.3)': + '@vueuse/shared@12.3.0(typescript@5.6.3)': dependencies: vue: 3.5.13(typescript@5.6.3) transitivePeerDependencies: @@ -3854,12 +3844,6 @@ snapshots: create-require@1.1.1: {} - cross-spawn@7.0.5: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -3896,6 +3880,8 @@ snapshots: dependencies: ms: 2.1.3 + decimal.js@10.4.3: {} + deep-is@0.1.4: {} default-browser-id@5.0.0: {} @@ -3932,10 +3918,10 @@ snapshots: domain-browser@4.23.0: {} - echarts@5.5.1: + echarts@5.6.0: dependencies: tslib: 2.3.0 - zrender: 5.6.0 + zrender: 5.6.1 electron-to-chromium@1.5.66: {} @@ -3964,32 +3950,33 @@ snapshots: es-errors@1.3.0: {} - esbuild@0.24.0: + esbuild@0.24.2: optionalDependencies: - '@esbuild/aix-ppc64': 0.24.0 - '@esbuild/android-arm': 0.24.0 - '@esbuild/android-arm64': 0.24.0 - '@esbuild/android-x64': 0.24.0 - '@esbuild/darwin-arm64': 0.24.0 - '@esbuild/darwin-x64': 0.24.0 - '@esbuild/freebsd-arm64': 0.24.0 - '@esbuild/freebsd-x64': 0.24.0 - '@esbuild/linux-arm': 0.24.0 - '@esbuild/linux-arm64': 0.24.0 - '@esbuild/linux-ia32': 0.24.0 - '@esbuild/linux-loong64': 0.24.0 - '@esbuild/linux-mips64el': 0.24.0 - '@esbuild/linux-ppc64': 0.24.0 - '@esbuild/linux-riscv64': 0.24.0 - '@esbuild/linux-s390x': 0.24.0 - '@esbuild/linux-x64': 0.24.0 - '@esbuild/netbsd-x64': 0.24.0 - '@esbuild/openbsd-arm64': 0.24.0 - '@esbuild/openbsd-x64': 0.24.0 - '@esbuild/sunos-x64': 0.24.0 - '@esbuild/win32-arm64': 0.24.0 - '@esbuild/win32-ia32': 0.24.0 - '@esbuild/win32-x64': 0.24.0 + '@esbuild/aix-ppc64': 0.24.2 + '@esbuild/android-arm': 0.24.2 + '@esbuild/android-arm64': 0.24.2 + '@esbuild/android-x64': 0.24.2 + '@esbuild/darwin-arm64': 0.24.2 + '@esbuild/darwin-x64': 0.24.2 + '@esbuild/freebsd-arm64': 0.24.2 + '@esbuild/freebsd-x64': 0.24.2 + '@esbuild/linux-arm': 0.24.2 + '@esbuild/linux-arm64': 0.24.2 + '@esbuild/linux-ia32': 0.24.2 + '@esbuild/linux-loong64': 0.24.2 + '@esbuild/linux-mips64el': 0.24.2 + '@esbuild/linux-ppc64': 0.24.2 + '@esbuild/linux-riscv64': 0.24.2 + '@esbuild/linux-s390x': 0.24.2 + '@esbuild/linux-x64': 0.24.2 + '@esbuild/netbsd-arm64': 0.24.2 + '@esbuild/netbsd-x64': 0.24.2 + '@esbuild/openbsd-arm64': 0.24.2 + '@esbuild/openbsd-x64': 0.24.2 + '@esbuild/sunos-x64': 0.24.2 + '@esbuild/win32-arm64': 0.24.2 + '@esbuild/win32-ia32': 0.24.2 + '@esbuild/win32-x64': 0.24.2 escalade@3.2.0: {} @@ -3997,20 +3984,20 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-prettier@9.1.0(eslint@9.16.0): + eslint-config-prettier@9.1.0(eslint@9.17.0): dependencies: - eslint: 9.16.0 + eslint: 9.17.0 - eslint-plugin-vue@9.32.0(eslint@9.16.0): + eslint-plugin-vue@9.32.0(eslint@9.17.0): dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.16.0) - eslint: 9.16.0 + '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0) + eslint: 9.17.0 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@9.16.0) + vue-eslint-parser: 9.4.3(eslint@9.17.0) xml-name-validator: 4.0.0 transitivePeerDependencies: - supports-color @@ -4029,14 +4016,14 @@ snapshots: eslint-visitor-keys@4.2.0: {} - eslint@9.16.0: + eslint@9.17.0: dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.16.0) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0) '@eslint-community/regexpp': 4.12.1 '@eslint/config-array': 0.19.0 '@eslint/core': 0.9.0 '@eslint/eslintrc': 3.2.0 - '@eslint/js': 9.16.0 + '@eslint/js': 9.17.0 '@eslint/plugin-kit': 0.2.3 '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 @@ -4116,6 +4103,8 @@ snapshots: strip-final-newline: 4.0.0 yoctocolors: 2.1.1 + fast-content-type-parse@2.0.1: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.2: @@ -4503,7 +4492,7 @@ snapshots: mitt@3.0.1: {} - monaco-editor@0.52.0: {} + monaco-editor@0.52.2: {} mrmime@2.0.0: {} @@ -4564,10 +4553,10 @@ snapshots: npm-normalize-package-bin@4.0.0: {} - npm-run-all2@7.0.1: + npm-run-all2@7.0.2: dependencies: ansi-styles: 6.2.1 - cross-spawn: 7.0.5 + cross-spawn: 7.0.6 memorystream: 0.3.1 minimatch: 9.0.5 pidtree: 0.6.0 @@ -4991,13 +4980,12 @@ snapshots: type-fest@0.20.2: {} - typescript-eslint@8.17.0(eslint@9.16.0)(typescript@5.6.3): + typescript-eslint@8.18.2(eslint@9.17.0)(typescript@5.6.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.17.0(@typescript-eslint/parser@8.17.0(eslint@9.16.0)(typescript@5.6.3))(eslint@9.16.0)(typescript@5.6.3) - '@typescript-eslint/parser': 8.17.0(eslint@9.16.0)(typescript@5.6.3) - '@typescript-eslint/utils': 8.17.0(eslint@9.16.0)(typescript@5.6.3) - eslint: 9.16.0 - optionalDependencies: + '@typescript-eslint/eslint-plugin': 8.18.2(@typescript-eslint/parser@8.18.2(eslint@9.17.0)(typescript@5.6.3))(eslint@9.17.0)(typescript@5.6.3) + '@typescript-eslint/parser': 8.18.2(eslint@9.17.0)(typescript@5.6.3) + '@typescript-eslint/utils': 8.18.2(eslint@9.17.0)(typescript@5.6.3) + eslint: 9.17.0 typescript: 5.6.3 transitivePeerDependencies: - supports-color @@ -5041,15 +5029,15 @@ snapshots: is-typed-array: 1.1.13 which-typed-array: 1.1.15 - uuid@11.0.3: {} + uuid@11.0.4: {} vite-bundle-analyzer@0.15.2: {} - vite-hot-client@0.2.4(vite@6.0.3(@types/node@22.10.1)(less@4.2.1)(sass@1.77.8)): + vite-hot-client@0.2.4(vite@6.0.6(@types/node@22.10.2)(less@4.2.1)(sass@1.77.8)): dependencies: - vite: 6.0.3(@types/node@22.10.1)(less@4.2.1)(sass@1.77.8) + vite: 6.0.6(@types/node@22.10.2)(less@4.2.1)(sass@1.77.8) - vite-plugin-inspect@0.8.8(rollup@4.27.4)(vite@6.0.3(@types/node@22.10.1)(less@4.2.1)(sass@1.77.8)): + vite-plugin-inspect@0.8.9(rollup@4.27.4)(vite@6.0.6(@types/node@22.10.2)(less@4.2.1)(sass@1.77.8)): dependencies: '@antfu/utils': 0.7.10 '@rollup/pluginutils': 5.1.3(rollup@4.27.4) @@ -5060,38 +5048,38 @@ snapshots: perfect-debounce: 1.0.0 picocolors: 1.1.1 sirv: 3.0.0 - vite: 6.0.3(@types/node@22.10.1)(less@4.2.1)(sass@1.77.8) + vite: 6.0.6(@types/node@22.10.2)(less@4.2.1)(sass@1.77.8) transitivePeerDependencies: - rollup - supports-color - vite-plugin-node-polyfills@0.22.0(rollup@4.27.4)(vite@6.0.3(@types/node@22.10.1)(less@4.2.1)(sass@1.77.8)): + vite-plugin-node-polyfills@0.22.0(rollup@4.27.4)(vite@6.0.6(@types/node@22.10.2)(less@4.2.1)(sass@1.77.8)): dependencies: '@rollup/plugin-inject': 5.0.5(rollup@4.27.4) node-stdlib-browser: 1.2.0 - vite: 6.0.3(@types/node@22.10.1)(less@4.2.1)(sass@1.77.8) + vite: 6.0.6(@types/node@22.10.2)(less@4.2.1)(sass@1.77.8) transitivePeerDependencies: - rollup vite-plugin-remove-console@2.2.0: {} - vite-plugin-vue-devtools@7.6.7(rollup@4.27.4)(vite@6.0.3(@types/node@22.10.1)(less@4.2.1)(sass@1.77.8))(vue@3.5.13(typescript@5.6.3)): + vite-plugin-vue-devtools@7.6.8(rollup@4.27.4)(vite@6.0.6(@types/node@22.10.2)(less@4.2.1)(sass@1.77.8))(vue@3.5.13(typescript@5.6.3)): dependencies: - '@vue/devtools-core': 7.6.7(vite@6.0.3(@types/node@22.10.1)(less@4.2.1)(sass@1.77.8))(vue@3.5.13(typescript@5.6.3)) - '@vue/devtools-kit': 7.6.7 - '@vue/devtools-shared': 7.6.7 + '@vue/devtools-core': 7.6.8(vite@6.0.6(@types/node@22.10.2)(less@4.2.1)(sass@1.77.8))(vue@3.5.13(typescript@5.6.3)) + '@vue/devtools-kit': 7.6.8 + '@vue/devtools-shared': 7.6.8 execa: 9.5.1 sirv: 3.0.0 - vite: 6.0.3(@types/node@22.10.1)(less@4.2.1)(sass@1.77.8) - vite-plugin-inspect: 0.8.8(rollup@4.27.4)(vite@6.0.3(@types/node@22.10.1)(less@4.2.1)(sass@1.77.8)) - vite-plugin-vue-inspector: 5.3.1(vite@6.0.3(@types/node@22.10.1)(less@4.2.1)(sass@1.77.8)) + vite: 6.0.6(@types/node@22.10.2)(less@4.2.1)(sass@1.77.8) + vite-plugin-inspect: 0.8.9(rollup@4.27.4)(vite@6.0.6(@types/node@22.10.2)(less@4.2.1)(sass@1.77.8)) + vite-plugin-vue-inspector: 5.3.1(vite@6.0.6(@types/node@22.10.2)(less@4.2.1)(sass@1.77.8)) transitivePeerDependencies: - '@nuxt/kit' - rollup - supports-color - vue - vite-plugin-vue-inspector@5.3.1(vite@6.0.3(@types/node@22.10.1)(less@4.2.1)(sass@1.77.8)): + vite-plugin-vue-inspector@5.3.1(vite@6.0.6(@types/node@22.10.2)(less@4.2.1)(sass@1.77.8)): dependencies: '@babel/core': 7.26.0 '@babel/plugin-proposal-decorators': 7.25.9(@babel/core@7.26.0) @@ -5102,17 +5090,17 @@ snapshots: '@vue/compiler-dom': 3.5.13 kolorist: 1.8.0 magic-string: 0.30.14 - vite: 6.0.3(@types/node@22.10.1)(less@4.2.1)(sass@1.77.8) + vite: 6.0.6(@types/node@22.10.2)(less@4.2.1)(sass@1.77.8) transitivePeerDependencies: - supports-color - vite@6.0.3(@types/node@22.10.1)(less@4.2.1)(sass@1.77.8): + vite@6.0.6(@types/node@22.10.2)(less@4.2.1)(sass@1.77.8): dependencies: - esbuild: 0.24.0 + esbuild: 0.24.2 postcss: 8.4.49 rollup: 4.27.4 optionalDependencies: - '@types/node': 22.10.1 + '@types/node': 22.10.2 fsevents: 2.3.3 less: 4.2.1 sass: 1.77.8 @@ -5129,9 +5117,9 @@ snapshots: dependencies: vue: 3.5.13(typescript@5.6.3) - vue-echarts@7.0.3(@vue/runtime-core@3.5.13)(echarts@5.5.1)(vue@3.5.13(typescript@5.6.3)): + vue-echarts@7.0.3(@vue/runtime-core@3.5.13)(echarts@5.6.0)(vue@3.5.13(typescript@5.6.3)): dependencies: - echarts: 5.5.1 + echarts: 5.6.0 vue: 3.5.13(typescript@5.6.3) vue-demi: 0.13.11(vue@3.5.13(typescript@5.6.3)) optionalDependencies: @@ -5139,10 +5127,10 @@ snapshots: transitivePeerDependencies: - '@vue/composition-api' - vue-eslint-parser@9.4.3(eslint@9.16.0): + vue-eslint-parser@9.4.3(eslint@9.17.0): dependencies: debug: 4.3.6 - eslint: 9.16.0 + eslint: 9.17.0 eslint-scope: 7.2.2 eslint-visitor-keys: 3.4.3 espree: 9.6.1 @@ -5152,10 +5140,10 @@ snapshots: transitivePeerDependencies: - supports-color - vue-i18n@10.0.5(vue@3.5.13(typescript@5.6.3)): + vue-i18n@11.0.1(vue@3.5.13(typescript@5.6.3)): dependencies: - '@intlify/core-base': 10.0.5 - '@intlify/shared': 10.0.5 + '@intlify/core-base': 11.0.1 + '@intlify/shared': 11.0.1 '@vue/devtools-api': 6.6.4 vue: 3.5.13(typescript@5.6.3) @@ -5214,6 +5202,6 @@ snapshots: yoctocolors@2.1.1: {} - zrender@5.6.0: + zrender@5.6.1: dependencies: tslib: 2.3.0 diff --git a/webui/src/App.vue b/webui/src/App.vue index 9d39b948ff..d9c2c285d7 100644 --- a/webui/src/App.vue +++ b/webui/src/App.vue @@ -1,4 +1,22 @@ + + diff --git a/webui/src/components/pageHeader.vue b/webui/src/components/pageHeader.vue index 1441ac7a38..8504b81de3 100644 --- a/webui/src/components/pageHeader.vue +++ b/webui/src/components/pageHeader.vue @@ -95,6 +95,9 @@ + + + @@ -164,7 +167,9 @@ import { useI18n } from 'vue-i18n' import { useRoute } from 'vue-router' import alert from './alert.vue' import autoUpdateBtn from './autoUpdateBtn.vue' +import globalPauseBtn from './globalPauseBtn.vue' import settingsModal from './settingsModal.vue' + const { t, locale } = useI18n() const { changeLocale } = useLocale() const locales = [...LOCALE_OPTIONS] diff --git a/webui/src/components/plus/plusTryModal.vue b/webui/src/components/plus/plusTryModal.vue index de914dde48..45298b6585 100644 --- a/webui/src/components/plus/plusTryModal.vue +++ b/webui/src/components/plus/plusTryModal.vue @@ -74,5 +74,9 @@ const handleOk = async () => { } const handleCancel = () => { visible.value = false + if (timer.value) { + clearInterval(timer.value) + timer.value = null + } } diff --git a/webui/src/locale/en-US.ts b/webui/src/locale/en-US.ts index efc39862e5..1d0c4ff977 100644 --- a/webui/src/locale/en-US.ts +++ b/webui/src/locale/en-US.ts @@ -29,6 +29,17 @@ export default { 'changeLogModel.changelog': 'Change Log', 'changeLogModel.notNow': 'Not Now', 'changeLogModel.updateNow': 'Update Now', + 'globalPauseModel.title': 'Are you sure to enable global pause mode?', + 'globalPauseModel.description': + 'This will pause all ban and check operations of all downloaders until the next restart or cancel pause.', + 'global.pause.pauseAll': 'Pause', + 'global.pause.pauseAll.tips': + "This will stop PeerBanhelper's all functions and unban all Peers, suitable for modifying downloader configuration", + 'global.pause.pauseAll.result': 'Global pause mode has been started', + 'global.pause.pauseAll.stop': 'Global pause mode has been stopped', + 'global.pause.alert': + 'Global pause mode has been enabled, all ban and check operations have been disabled', + 'global.pause.alert.disable': 'Click to disable', 'router.login': 'Login', 'router.dashboard': 'Status', diff --git a/webui/src/locale/en-US/settings.ts b/webui/src/locale/en-US/settings.ts index 407184076c..3aec65e4ee 100644 --- a/webui/src/locale/en-US/settings.ts +++ b/webui/src/locale/en-US/settings.ts @@ -3,6 +3,7 @@ export default { 'settings.navbar.theme.toDark': 'Click to use dark mode', 'settings.open': 'Goto Settings', 'settings.language': 'Language', + 'settings.globalPause': 'Global Pause', 'settings.modal.title': 'Settings', 'settings.modal.endpointTips': "If you can't access the backend of the PBH, you can try to set the Endpoint here", diff --git a/webui/src/locale/zh-CN.ts b/webui/src/locale/zh-CN.ts index 6aa730879b..4f597f2e63 100644 --- a/webui/src/locale/zh-CN.ts +++ b/webui/src/locale/zh-CN.ts @@ -17,6 +17,9 @@ import settingsLocale from './zh-CN/settings' export default { 'navbar.action.locale': '切换为中文', 'navbar.action.autoUpdate': '自动刷新', + 'navbar.action.globalPause': '全局暂停', + 'navbar.action.globalPause.description': + '这将暂停所有下载器的封禁和检查操作,直至下次重启或者取消暂停。', 'navbar.action.autoUpdate.lastUpdate': '最后更新于:', 'main.workInProgressTips': '请注意,此功能仍在施工中,目前记录和展示的数据较为有限。', 'footer.newVersion': '发现新版本!', @@ -28,6 +31,15 @@ export default { 'changeLogModel.changelog': '更新日志', 'changeLogModel.notNow': '不是现在', 'changeLogModel.updateNow': '立即更新', + 'globalPauseModel.title': '确定启用全局暂停模式吗?', + 'globalPauseModel.description': '这将暂停所有下载器的封禁和检查操作,直至下次重启或者取消暂停。', + 'global.pause.pauseAll': '暂停', + 'global.pause.pauseAll.tips': + '这将停止 PeerBanhelper 的全部功能并解封全部 Peer,适用于修改下载器配置等场景', + 'global.pause.pauseAll.result': '全局暂停模式已启动', + 'global.pause.pauseAll.stop': '全局暂停模式已停止', + 'global.pause.alert': '全局暂停模式已启动,所有检查和封禁操作均已暂停', + 'global.pause.alert.disable': '点击关闭', 'router.login': '登录', 'router.dashboard': '状态', diff --git a/webui/src/locale/zh-CN/settings.ts b/webui/src/locale/zh-CN/settings.ts index 57b786e6e9..4660458980 100644 --- a/webui/src/locale/zh-CN/settings.ts +++ b/webui/src/locale/zh-CN/settings.ts @@ -3,6 +3,7 @@ export default { 'settings.navbar.theme.toDark': '点击切换为暗黑模式', 'settings.open': '前往设置', 'settings.language': '语言', + 'settings.globalPause': '全局暂停', 'settings.modal.title': '设置', 'settings.modal.endpointTips': '如果你无法访问PBH后端,可以尝试在此设置Endpoint', 'settings.modal.pollInterval': '轮询间隔:', diff --git a/webui/src/router/index.ts b/webui/src/router/index.ts index 45600787cf..e99f4baed4 100644 --- a/webui/src/router/index.ts +++ b/webui/src/router/index.ts @@ -178,7 +178,8 @@ export const routerOptions: RouteRecordRaw[] = [ name: 'charts', meta: { label: 'router.metrics.charts', - needLogin: true + needLogin: true, + disableAutoUpdate: true }, component: () => import('@/views/charts/index.vue') }, diff --git a/webui/src/service/banList.ts b/webui/src/service/banList.ts index 04480654c4..85044dbfb4 100644 --- a/webui/src/service/banList.ts +++ b/webui/src/service/banList.ts @@ -6,6 +6,7 @@ import { getCommonHeader } from './utils' export async function getBanList( limit: number, + search?: string, lastBanTime?: number ): Promise> { const endpointStore = useEndpointStore() @@ -16,6 +17,9 @@ export async function getBanList( if (lastBanTime) { url.searchParams.set('lastBanTime', String(lastBanTime)) } + if (search && search.trim() !== '') { + url.searchParams.set('search', encodeURIComponent(search.trim())) + } return fetch(url, { headers: getCommonHeader() }).then((res) => { diff --git a/webui/src/service/version.ts b/webui/src/service/version.ts index 7db9826cf0..372bfa10b3 100644 --- a/webui/src/service/version.ts +++ b/webui/src/service/version.ts @@ -1,9 +1,9 @@ +import type { CommonResponse, CommonResponseWithoutData } from '@/api/model/common' +import type { donateStatus, GlobalConfig, mainfest } from '@/api/model/manifest' import { useEndpointStore } from '@/stores/endpoint' +import { Octokit } from '@octokit/core' import urlJoin from 'url-join' import { getCommonHeader } from './utils' -import { Octokit } from '@octokit/core' -import type { donateStatus, mainfest } from '@/api/model/manifest' -import type { CommonResponse, CommonResponseWithoutData } from '@/api/model/common' export class GetManifestError extends Error { static name = 'GetManifestError' as const name = GetManifestError.name @@ -91,3 +91,23 @@ export function getManifest(endpoint = useEndpointStore().endpoint): Promise> { + const url = new URL(urlJoin(useEndpointStore().endpoint, 'api/general/global'), location.href) + return fetch(url, { headers: getCommonHeader() }).then((res) => { + useEndpointStore().assertResponseLogin(res) + return res.json() + }) +} + +export function UpdateGlobalConfig(config: GlobalConfig): Promise { + const url = new URL(urlJoin(useEndpointStore().endpoint, 'api/general/global'), location.href) + return fetch(url, { + method: 'PATCH', + body: JSON.stringify(config), + headers: getCommonHeader() + }).then((res) => { + useEndpointStore().assertResponseLogin(res) + return res.json() + }) +} diff --git a/webui/src/stores/endpoint.ts b/webui/src/stores/endpoint.ts index b740f02eff..e2993f2101 100644 --- a/webui/src/stores/endpoint.ts +++ b/webui/src/stores/endpoint.ts @@ -1,12 +1,14 @@ -import type { donateStatus, mainfest, release } from '@/api/model/manifest' +import type { donateStatus, GlobalConfig, mainfest, release } from '@/api/model/manifest' import { basePath } from '@/router' import { IncorrectTokenError, login, NeedInitError } from '@/service/login' import { + GetGlobalConfig, getLatestVersion, getManifest, GetManifestError, getPBHPlusStatus, - setPHBPlusKey + setPHBPlusKey, + UpdateGlobalConfig } from '@/service/version' import networkFailRetryNotication from '@/utils/networkRetry' import { useStorage } from '@vueuse/core' @@ -147,9 +149,6 @@ export const useEndpointStore = defineStore('endpoint', () => { const getPlusStatus = async () => { const result = await getPBHPlusStatus() plusStatus.value = result.data - if (result.data.activated) { - console.log('PBH Plus Activated! Thanks for your support ❤️') - } } const setPlusKey = async (key: string) => { const result = await setPHBPlusKey(key) @@ -159,11 +158,29 @@ export const useEndpointStore = defineStore('endpoint', () => { throw new Error(result.message) } } + const globalConfig = ref() + const getGlobalConfig = async () => { + const result = await GetGlobalConfig() + if (result.success) { + globalConfig.value = result.data + } else { + throw new Error(result.message) + } + } + const updateGlobalConfig = async (config: GlobalConfig) => { + const result = await UpdateGlobalConfig(config) + if (result.success) { + getGlobalConfig() + } else { + throw new Error(result.message) + } + } // init setEndpoint(endpoint.value, { retryOnNetWorkFail: true }) setTimeout(async () => getPlusStatus()) setTimeout(async () => setAccessToken(accessToken.value), 3000) + setTimeout(async () => getGlobalConfig()) return { endpointSaved: readonly(endpoint), endpoint: computed(() => { @@ -194,6 +211,9 @@ export const useEndpointStore = defineStore('endpoint', () => { } else if (res.status === 303) { throw new NeedInitError() } - } + }, + getGlobalConfig, + updateGlobalConfig, + globalConfig: readonly(globalConfig) } }) diff --git a/webui/src/views/banlist/components/banList.vue b/webui/src/views/banlist/components/banList.vue index e42e4123f8..01b980de6d 100644 --- a/webui/src/views/banlist/components/banList.vue +++ b/webui/src/views/banlist/components/banList.vue @@ -2,13 +2,29 @@ {{ t('page.banlist.banlist.description') }} - + + + + {{ t('page.banlist.banlist.listItem.unbanall') }} + + + +
- {{ - t('page.banlist.banlist.bottomReached') - }} + {{ t('page.banlist.banlist.bottomReached') }} +
@@ -47,9 +63,11 @@