diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..8caa093f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +.gradle +**/build +.git +.idea \ No newline at end of file diff --git a/.gitignore b/.gitignore index 42d49731..57cf3863 100644 --- a/.gitignore +++ b/.gitignore @@ -44,5 +44,6 @@ buildNumber.properties /wait.sh /conf/ .direnv -data +data/ run +/.env diff --git a/Dockerfile b/Dockerfile.bot similarity index 91% rename from Dockerfile rename to Dockerfile.bot index fc0d17c9..3a04edbc 100644 --- a/Dockerfile +++ b/Dockerfile.bot @@ -1,5 +1,5 @@ # This dockerfile is far from best practice and is a pure "Make it work" approach. Please do not use it as a reference of any kind. -FROM gradle:jdk21-alpine as build +FROM gradle:jdk25-alpine as build COPY . . RUN gradle clean :bot:build :plugin-paper:build --no-daemon @@ -19,7 +19,7 @@ RUN mkdir servers RUN mkdir template RUN mkdir template/plugins COPY docker/resources/bot/wait.sh . -# Copy the plugin jam plugin into the template. +# Copy the plugin jamEntity plugin into the template. COPY --from=build /home/gradle/plugin-paper/build/libs/plugin-paper-*-all.jar ./bot/template/plugins/pluginjam.jar COPY docker/resources/docker-entrypoint.sh . diff --git a/Dockerfile.bot.dev b/Dockerfile.bot.dev new file mode 100644 index 00000000..40a240c5 --- /dev/null +++ b/Dockerfile.bot.dev @@ -0,0 +1,36 @@ +FROM gradle:jdk25-alpine as build +WORKDIR /home/gradle + +COPY --chown=gradle:gradle settingsEntity.gradle* build.gradle* gradle.properties* ./ +COPY --chown=gradle:gradle bot/build.gradle* ./bot/ +COPY --chown=gradle:gradle plugin-paper/build.gradle* ./plugin-paper/ + +RUN gradle dependencies --no-daemon || true + +COPY --chown=gradle:gradle . . + +RUN gradle :bot:build :plugin-paper:build --no-daemon + +# We use a jammy image because we need some more stuff than alpine provides +FROM eclipse-temurin:25-jammy as runtime +WORKDIR /app + +# Setting up the bot +COPY --from=build /home/gradle/bot/build/libs/bot-*-all.jar ./bot.jar +RUN mkdir plugins +RUN mkdir servers +RUN mkdir template +RUN mkdir template/plugins +COPY docker/resources/bot/wait.sh . +# Copy the plugin jamEntity plugin into the template. +COPY --from=build /home/gradle/plugin-paper/build/libs/plugin-paper-*-all.jar ./bot/template/plugins/pluginjam.jar + +COPY docker/resources/docker-entrypoint.sh . + +EXPOSE 8080 + +COPY bot/src/main/resources/log4j2.xml log4j2.xml + +HEALTHCHECK CMD curl --fail http://localhost:8080/swagger-ui || exit 1 + +ENTRYPOINT ["bash", "docker-entrypoint.sh"] diff --git a/Dockerfile.mc b/Dockerfile.mc new file mode 100644 index 00000000..bcbde324 --- /dev/null +++ b/Dockerfile.mc @@ -0,0 +1,15 @@ +FROM gradle:jdk25-alpine AS build +WORKDIR /home/gradle +COPY --chown=gradle:gradle settingsEntity.gradle* build.gradle* gradle.properties* ./ +COPY --chown=gradle:gradle bot/build.gradle* ./bot/ +COPY --chown=gradle:gradle plugin-api/build.gradle* ./plugin-api/ +COPY --chown=gradle:gradle plugin-paper/build.gradle* ./plugin-paper/ +COPY --chown=gradle:gradle plugin-velocity/build.gradle* ./plugin-velocity/ +RUN mkdir -p plugin-paper/Readme.md && \ + gradle dependencies --no-daemon || true +COPY --chown=gradle:gradle plugin-api ./plugin-api +COPY --chown=gradle:gradle plugin-paper ./plugin-paper +RUN gradle :plugin-paper:shadowJar --no-daemon + +FROM itzg/minecraft-server:stable-java25 +COPY --from=build /home/gradle/plugin-paper/build/libs/plugin-paper-*-all.jar /plugins/pluginjam.jar diff --git a/Dockerfile.velocity b/Dockerfile.velocity new file mode 100644 index 00000000..3c56ccfd --- /dev/null +++ b/Dockerfile.velocity @@ -0,0 +1,14 @@ +FROM gradle:jdk25-alpine AS build +WORKDIR /home/gradle +COPY --chown=gradle:gradle settingsEntity.gradle* build.gradle* gradle.properties* ./ +COPY --chown=gradle:gradle bot/build.gradle* ./bot/ +COPY --chown=gradle:gradle plugin-api/build.gradle* ./plugin-api/ +COPY --chown=gradle:gradle plugin-paper/build.gradle* ./plugin-paper/ +COPY --chown=gradle:gradle plugin-velocity/build.gradle* ./plugin-velocity/ +RUN mkdir -p plugin-paper/Readme.md && \ + gradle dependencies --no-daemon || true +COPY --chown=gradle:gradle plugin-api ./plugin-api +COPY --chown=gradle:gradle plugin-velocity ./plugin-velocity +RUN gradle :plugin-velocity:shadowJar --no-daemon +FROM itzg/mc-proxy:latest +COPY --from=build /home/gradle/plugin-velocity/build/libs/plugin-velocity-*-all.jar /plugins/pluginjam.jar diff --git a/README.md b/README.md index 01f6b695..561881a2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Bot and spigot plugin used for the dev cord plugin jam +Bot and spigot plugin used for the dev cord plugin jamEntity ## Start Arguments for paper `-Dpluginjam.port` diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 00000000..7615dda8 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,15 @@ +Thumbs.db +.DS_Store +.gradle +build/ +target/ +out/ +.micronaut/ +.idea +*.iml +*.ipr +*.iws +.project +.settingsEntity +.classpath +.factorypath diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 00000000..7b0161bd --- /dev/null +++ b/backend/README.md @@ -0,0 +1,49 @@ +## Micronaut 5.0.2 Documentation + +- [User Guide](https://docs.micronaut.io/5.0.2/guide/index.html) +- [API Reference](https://docs.micronaut.io/5.0.2/api/index.html) +- [Configuration Reference](https://docs.micronaut.io/5.0.2/guide/configurationreference.html) +- [Micronaut Guides](https://guides.micronaut.io/index.html) + +--- + +- [Micronaut Gradle Plugin documentation](https://micronaut-projects.github.io/micronaut-gradle-plugin/latest/) +- [GraalVM Gradle Plugin documentation](https://graalvm.github.io/native-build-tools/latest/gradle-plugin.html) +- [Shadow Gradle Plugin](https://gradleup.com/shadow/) + +## Feature http-client documentation + +- [Micronaut HTTP Client documentation](https://docs.micronaut.io/latest/guide/index.html#nettyHttpClient) + +## Feature flyway documentation + +- [Micronaut Flyway Database Migration documentation](https://micronaut-projects.github.io/micronaut-flyway/latest/guide/index.html) + + +- [https://flywaydb.org/](https://flywaydb.org/) + +## Feature serialization-jackson documentation + +- [Micronaut Serialization Jackson Core documentation](https://micronaut-projects.github.io/micronaut-serialization/latest/guide/) + +## Feature jdbc-hikari documentation + +- [Micronaut Hikari JDBC Connection Pool documentation](https://micronaut-projects.github.io/micronaut-sql/latest/guide/index.html#jdbc) + +## Feature security-oauth2 documentation + +- [Micronaut Security OAuth 2.0 documentation](https://micronaut-projects.github.io/micronaut-security/latest/guide/index.html#oauth) + +## Feature test-resources documentation + +- [Micronaut Test Resources documentation](https://micronaut-projects.github.io/micronaut-test-resources/latest/guide/) + +## Feature data-jdbc documentation + +- [Micronaut Data JDBC documentation](https://micronaut-projects.github.io/micronaut-data/latest/guide/index.html#jdbc) + +## Feature micronaut-aot documentation + +- [Micronaut AOT documentation](https://micronaut-projects.github.io/micronaut-aot/latest/guide/) + + diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts new file mode 100644 index 00000000..231a1739 --- /dev/null +++ b/backend/build.gradle.kts @@ -0,0 +1,117 @@ +plugins { + id("io.micronaut.application") version "5.0.0" + id("com.gradleup.shadow") version "9.4.1" + id("io.micronaut.test-resources") version "5.0.0" + id("io.micronaut.aot") version "5.0.0" +} + +version = "0.1" +group = "de.chojo.pluginjam" + + + +repositories { + mavenCentral() +} + +dependencies { + annotationProcessor("io.micronaut.data:micronaut-data-processor") + annotationProcessor("io.micronaut:micronaut-http-validation") + annotationProcessor("io.micronaut.security:micronaut-security-processor") + annotationProcessor("io.micronaut.serde:micronaut-serde-processor") + implementation("io.micronaut:micronaut-http-client") + implementation("io.micronaut.data:micronaut-data-jdbc") + implementation("io.micronaut.flyway:micronaut-flyway") + implementation("io.micronaut.security:micronaut-security-oauth2") + implementation("io.micronaut.security:micronaut-security-jwt") + implementation("io.micronaut.security:micronaut-security") + implementation("io.micronaut.serde:micronaut-serde-jackson") + implementation("io.micronaut.sql:micronaut-jdbc-hikari") + implementation("io.micronaut.reactor:micronaut-reactor") + + implementation("com.github.docker-java:docker-java:3.7.1") + implementation("com.github.docker-java:docker-java-transport-httpclient5:3.7.1") + + // discord + implementation("io.github.kaktushose:jda-commands:5.0.0") { + exclude("org.slf4j", "slf4j-simple") + } + implementation("net.dv8tion:JDA:6.4.1") + + runtimeOnly("ch.qos.logback:logback-classic") + runtimeOnly("org.flywaydb:flyway-database-postgresql") + runtimeOnly("org.postgresql:postgresql") + runtimeOnly("org.yaml:snakeyaml") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") + aotPlugins(platform("io.micronaut.platform:micronaut-platform:5.0.2")) + aotPlugins("io.micronaut.security:micronaut-security-aot") +} + + + +application { + mainClass = "de.chojo.pluginjam.Application" +} + +java { + sourceCompatibility = JavaVersion.toVersion("25") + targetCompatibility = JavaVersion.toVersion("25") +} + + + + +graalvmNative.toolchainDetection = false +graalvmNative { + binaries { + all { + buildArgs.add("-H:+SharedArenaSupport") + } + } +} + + + + +micronaut { + runtime("netty") + testRuntime("junit5") + processing { + incremental(true) + annotations("de.chojo.pluginjam.*") + } + testResources { + version = "4.0.0" + } + aot { + // Please review carefully the optimizations enabled below + // Check https://micronaut-projects.github.io/micronaut-aot/latest/guide/ for more details + optimizeServiceLoading = false + convertYamlToJava = false + precomputeOperations = true + cacheEnvironment = true + optimizeClassLoading = true + deduceEnvironment = true + optimizeNetty = true + replaceLogbackXml = true + } + +} +tasks.withType().configureEach { + useClassDataSharing.set(false) +} + +tasks.named("dockerfile") { + + baseImage = "eclipse-temurin:25-jre" +} + + +// https://docs.gradle.org/current/userguide/upgrading_major_version_9.html#test_task_fails_when_no_tests_are_discovered +tasks.withType().configureEach { + failOnNoDiscoveredTests = false +} + + + + diff --git a/backend/gradle/wrapper/gradle-wrapper.jar b/backend/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..b1b8ef56 Binary files /dev/null and b/backend/gradle/wrapper/gradle-wrapper.jar differ diff --git a/backend/gradle/wrapper/gradle-wrapper.properties b/backend/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..df6a6ad7 --- /dev/null +++ b/backend/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,9 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip +networkTimeout=10000 +retries=0 +retryBackOffMs=500 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/backend/gradlew b/backend/gradlew new file mode 100755 index 00000000..b9bb139f --- /dev/null +++ b/backend/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/backend/gradlew.bat b/backend/gradlew.bat new file mode 100644 index 00000000..24c62d56 --- /dev/null +++ b/backend/gradlew.bat @@ -0,0 +1,82 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel + +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/backend/micronaut-cli.yml b/backend/micronaut-cli.yml new file mode 100644 index 00000000..d9e6fda1 --- /dev/null +++ b/backend/micronaut-cli.yml @@ -0,0 +1,6 @@ +applicationType: default +defaultPackage: de.chojo.pluginjam +testFramework: junit +sourceLanguage: java +buildTool: gradle_kotlin +features: [ app-name, data, data-jdbc, flyway, gradle, http-client, http-client-test, java, java-application, jdbc-hikari, junit, logback, micronaut-aot, micronaut-build, micronaut-configuration-validation-gradle-plugin, micronaut-http-validation, netty-server, postgres, readme, security-oauth2, security-processor, serialization-jackson, shade, static-resources, test-resources, yaml, yaml-build ] diff --git a/backend/settings.gradle.kts b/backend/settings.gradle.kts new file mode 100644 index 00000000..96294e18 --- /dev/null +++ b/backend/settings.gradle.kts @@ -0,0 +1,2 @@ +rootProject.name = "backend" + diff --git a/backend/src/main/java/de/chojo/pluginjam/Application.java b/backend/src/main/java/de/chojo/pluginjam/Application.java new file mode 100644 index 00000000..5a94f868 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/Application.java @@ -0,0 +1,22 @@ +package de.chojo.pluginjam; + +import io.micronaut.core.annotation.NonNull; +import io.micronaut.context.ApplicationContextBuilder; +import io.micronaut.context.ApplicationContextConfigurer; +import io.micronaut.context.annotation.ContextConfigurer; +import io.micronaut.runtime.Micronaut; + +public class Application { + + @ContextConfigurer + public static class Configurer implements ApplicationContextConfigurer { + @Override + public void configure(@NonNull ApplicationContextBuilder builder) { + builder.defaultEnvironments("dev"); + } + } + + public static void main(String[] args) { + Micronaut.run(Application.class, args); + } +} \ No newline at end of file diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/Bot.java b/backend/src/main/java/de/chojo/pluginjam/bot/Bot.java new file mode 100644 index 00000000..d4c0b46b --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/Bot.java @@ -0,0 +1,124 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot; + +import com.google.inject.Guice; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import de.chojo.pluginjam.bot.listener.InviteButtonListener; +import de.chojo.pluginjam.database.repository.SettingsRepository; +import de.chojo.pluginjam.service.JamService; +import de.chojo.pluginjam.service.SettingsService; +import de.chojo.pluginjam.service.TeamService; +import de.chojo.pluginjam.service.VoteService; +import io.github.kaktushose.jdac.JDACommands; +import io.github.kaktushose.jdac.annotations.interactions.CommandConfig; +import io.github.kaktushose.jdac.annotations.interactions.CommandScope; +import io.github.kaktushose.jdac.definitions.interactions.command.CommandDefinition; +import io.github.kaktushose.jdac.guice.GuiceExtensionData; +import io.github.kaktushose.jdac.scope.GuildScopeProvider; +import io.micronaut.context.annotation.Value; +import io.micronaut.context.event.ApplicationEventListener; +import io.micronaut.runtime.event.ApplicationStartupEvent; +import jakarta.inject.Singleton; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.interactions.commands.build.CommandData; +import net.dv8tion.jda.api.requests.GatewayIntent; +import net.dv8tion.jda.api.requests.RestAction; +import net.dv8tion.jda.api.sharding.DefaultShardManagerBuilder; +import net.dv8tion.jda.api.sharding.ShardManager; +import net.dv8tion.jda.api.utils.MemberCachePolicy; +import org.jspecify.annotations.NonNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Set; +import java.util.stream.Collectors; + +@Singleton +public class Bot implements ApplicationEventListener { + private static final Logger LOG = LoggerFactory.getLogger(Bot.class); + private final JamService jamService; + private final SettingsService settingsService; + private final TeamService teamService; + private final VoteService voteService; + private final String token; + + private ShardManager shardManager; + + public Bot(JamService jamService, SettingsService settingsService, TeamService teamService, VoteService voteService, @Value("${bot.token}") String token) { + this.jamService = jamService; + this.settingsService = settingsService; + this.teamService = teamService; + this.voteService = voteService; + this.token = token; + } + + @Override + public void onApplicationEvent(ApplicationStartupEvent event) { + try { + start(); + } catch (Exception e) { + LOG.error("Failed to start bot", e); + throw new RuntimeException(e); + } + } + + public void start() { + + LOG.info("Initializing Shard Manager"); + initBot(); + + LOG.info("Initializing Commands"); + buildCommands(); + + buildShutdownHook(); + } + + private void buildShutdownHook() { + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + LOG.info("Received SIGTERM. Shutdown hook activated."); + shardManager.shutdown(); + })); + } + + private void buildCommands() { + var userContextProvider = new CommandContextProvider( + jamService, + settingsService, + teamService, + voteService + ); + var injector = Guice.createInjector(new BotModule(userContextProvider)); + JDACommands.builder(shardManager) + .extensionData(new GuiceExtensionData(injector)) + .globalCommandConfig(CommandDefinition.CommandConfig.of(builder -> { + builder.scope(CommandScope.GUILD); + })) + .guildScopeProvider(new GuildScopeProvider() { + @Override + public @NonNull Set apply(@NonNull CommandData commandData) { + return shardManager.getGuilds().stream() + .map(Guild::getIdLong) + .collect(Collectors.toSet()); + } + }) + .start(); + shardManager.addEventListener(new InviteButtonListener(teamService, settingsService, jamService)); + } + + private void initBot() { + shardManager = DefaultShardManagerBuilder.createDefault(token) + .setEnableShutdownHook(false) + .enableIntents( + GatewayIntent.GUILD_MEMBERS, + GatewayIntent.DIRECT_MESSAGES, + GatewayIntent.GUILD_MESSAGES) + .setMemberCachePolicy(MemberCachePolicy.DEFAULT) + .build(); + RestAction.setDefaultFailure(throwable -> LOG.error("Unhandled exception occurred: ", throwable)); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/BotModule.java b/backend/src/main/java/de/chojo/pluginjam/bot/BotModule.java new file mode 100644 index 00000000..13d32ad1 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/BotModule.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot; + +import com.google.inject.AbstractModule; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; + +public class BotModule extends AbstractModule { + private final CommandContextProvider commandContextProvider; + + public BotModule(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Override + protected void configure() { + bind(CommandContextProvider.class).toInstance(commandContextProvider); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/CommandContextProvider.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/CommandContextProvider.java new file mode 100644 index 00000000..abadbb76 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/CommandContextProvider.java @@ -0,0 +1,36 @@ +package de.chojo.pluginjam.bot.commands; + +import de.chojo.pluginjam.service.JamService; +import de.chojo.pluginjam.service.SettingsService; +import de.chojo.pluginjam.service.TeamService; +import de.chojo.pluginjam.service.VoteService; + +public class CommandContextProvider { + private final JamService jamService; + private final SettingsService settingsService; + private final TeamService teamService; + private final VoteService voteService; + + public CommandContextProvider(JamService jamService, SettingsService settingsService, TeamService teamService, VoteService voteService) { + this.jamService = jamService; + this.settingsService = settingsService; + this.teamService = teamService; + this.voteService = voteService; + } + + public JamService pluginJamService() { + return jamService; + } + + public SettingsService settingsService() { + return settingsService; + } + + public TeamService teamService() { + return teamService; + } + + public VoteService voteService() { + return voteService; + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/ChangeVotesCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/ChangeVotesCommand.java new file mode 100644 index 00000000..3e00acf3 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/ChangeVotesCommand.java @@ -0,0 +1,38 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.jamadmin; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.*; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; + +@Bundle("locale") +@Interaction +public final class ChangeVotesCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public ChangeVotesCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command("jamdamin changevotes") + public void onCommand(CommandEvent event, @Param("voting") boolean voting) { + var guildId = event.getGuild().getIdLong(); + var activeJam = commandContextProvider.pluginJamService().getActiveJam(guildId); + + if (activeJam.isEmpty()) { + event.with().ephemeral(true).reply("error-noactivejam"); + return; + } + + commandContextProvider.pluginJamService().setVoting(activeJam.get().id(), voting); + event.with().ephemeral(true).reply("command-jamadmin-changevotes-message-changed"); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/JamCreateCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/JamCreateCommand.java new file mode 100644 index 00000000..fa5aac80 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/JamCreateCommand.java @@ -0,0 +1,97 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.jamadmin; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.*; +import io.github.kaktushose.jdac.dispatching.events.interactions.AutoCompleteEvent; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import io.github.kaktushose.jdac.message.placeholder.Entry; +import net.dv8tion.jda.api.interactions.commands.Command; + +import java.time.DateTimeException; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Locale; +import java.util.stream.Collectors; + +@Bundle("locale") +@Interaction +public class JamCreateCommand { + public static final String PATTERN = "yyyy.MM.dd HH:mm"; + private static final DateTimeFormatter DATE_PARSER = DateTimeFormatter.ofPattern(PATTERN); + private final CommandContextProvider commandContextProvider; + + @Inject + public JamCreateCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @io.github.kaktushose.jdac.annotations.interactions.Command(value = "jamadmin create") + public void onCommand( + CommandEvent event, + @Param("topic") String topic, + @Param("tagline") String tagline, + @Param("timezone") String timezone, + @Param("registerstart") String registerStart, + @Param("registerend") String registerEnd, + @Param("jamstart") String jamStart, + @Param("jamend") String jamEnd + ) { + var guild = event.getGuild(); + ZoneId parsedTimeZone; + try { + parsedTimeZone = ZoneId.of(timezone); + } catch (DateTimeException e) { + event.reply("error-invalidtimezone"); + return; + } + + try { + var registerStartParsed = parseTime(registerStart, parsedTimeZone); + var registerEndParsed = parseTime(registerEnd, parsedTimeZone); + var jamStartParsed = parseTime(jamStart, parsedTimeZone); + var jamEndParsed = parseTime(jamEnd, parsedTimeZone); + + commandContextProvider.pluginJamService().createJam( + guild.getIdLong(), + topic, + tagline, + parsedTimeZone.getId(), + registerStartParsed, + registerEndParsed, + jamStartParsed, + jamEndParsed + ); + } catch (DateTimeException e) { + event.with().ephemeral(true).reply("error-invalidrimeformat", Entry.entry("FORMAT", PATTERN)); + return; + } + event.with().ephemeral(true).reply("command-jamadmin-create-message-created"); + } + + + private ZonedDateTime parseTime(String time, ZoneId zoneId) throws DateTimeException { + var parsed = LocalDateTime.from(DATE_PARSER.parse(time)); + return ZonedDateTime.ofInstant(parsed, zoneId.getRules().getOffset(parsed), zoneId); + } + + @AutoComplete(value = "onCommand", options = "timezone") + public void onAutoComplete(AutoCompleteEvent event) { + var value = event.getValue().toLowerCase(Locale.ROOT); + var choices = ZoneId.getAvailableZoneIds().stream() + .filter(zone -> zone.toLowerCase(Locale.ROOT).contains(value)) + .limit(25) + .map(zone -> new Command.Choice(zone, zone)) + .collect(Collectors.toList()); + event.replyChoices(choices); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/JamEndCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/JamEndCommand.java new file mode 100644 index 00000000..57f07989 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/JamEndCommand.java @@ -0,0 +1,42 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.jamadmin; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.*; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; + +@Bundle("locale") +@Interaction +public final class JamEndCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public JamEndCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "jamadmin end") + public void onCommand(CommandEvent event, @Param("confirm") boolean confirm) { + if (!confirm) { + event.with().ephemeral(true).reply("error-noconfirm"); + return; + } + + var guildId = event.getGuild().getIdLong(); + + if (!commandContextProvider.pluginJamService().isJamActive(guildId)) { + event.with().ephemeral(true).reply("error-nojamactive"); + return; + } + + commandContextProvider.pluginJamService().endActiveJams(guildId); + event.with().ephemeral(true).reply("command-jamadmin-jam-end-message-ended"); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/JamListCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/JamListCommand.java new file mode 100644 index 00000000..5ce0b221 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/JamListCommand.java @@ -0,0 +1,53 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.jamadmin; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import de.chojo.pluginjam.bot.message.EmbedHelper; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.*; +import io.github.kaktushose.jdac.dispatching.events.interactions.AutoCompleteEvent; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import io.github.kaktushose.jdac.message.placeholder.Entry; +import io.github.kaktushose.jdac.message.resolver.MessageResolver; +import net.dv8tion.jda.api.interactions.commands.Command; + +import java.time.DateTimeException; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Locale; +import java.util.stream.Collectors; + +@Bundle("locale") +@Interaction +public class JamListCommand { + public static final String PATTERN = "yyyy.MM.dd HH:mm"; + private static final DateTimeFormatter DATE_PARSER = DateTimeFormatter.ofPattern(PATTERN); + private final CommandContextProvider commandContextProvider; + private final MessageResolver messageResolver; + + @Inject + public JamListCommand(CommandContextProvider commandContextProvider, MessageResolver messageResolver) { + this.commandContextProvider = commandContextProvider; + this.messageResolver = messageResolver; + } + + @io.github.kaktushose.jdac.annotations.interactions.Command(value = "jamadmin list") + public void onCommand(CommandEvent event) { + var jams = commandContextProvider.pluginJamService().getJams(event.getGuild().getIdLong()); + if (jams == null || jams.isEmpty()) { + event.with().ephemeral(true).reply("command-jamadmin-list-no-jams"); + return; + } + var locale = event.getUserLocale(); + var embed = EmbedHelper.buildJamListEmbed(jams, event, messageResolver, locale); + event.reply(embed); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/JamStartCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/JamStartCommand.java new file mode 100644 index 00000000..22d3ec25 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/jamadmin/JamStartCommand.java @@ -0,0 +1,46 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.jamadmin; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.CommandConfig; +import io.github.kaktushose.jdac.annotations.interactions.CommandScope; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; + +@Bundle("locale") +@Interaction +public final class JamStartCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public JamStartCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "jamadmin start") + public void onCommand(CommandEvent event) { + var guildId = event.getGuild().getIdLong(); + + if (commandContextProvider.pluginJamService().isJamActive(guildId)) { + event.with().ephemeral(true).reply("error-alreadyActive"); + return; + } + + var nextJam = commandContextProvider.pluginJamService().getUpComingJam(guildId); + if (nextJam.isPresent()) { + commandContextProvider.pluginJamService().startJam(nextJam.get().id()); + event.with().ephemeral(true).reply("command-start-message-activated"); + return; + } + + event.with().ephemeral(true).reply("error-noupcomingjam"); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/register/RegisterCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/register/RegisterCommand.java new file mode 100644 index 00000000..d4977e48 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/register/RegisterCommand.java @@ -0,0 +1,76 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.register; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.CommandConfig; +import io.github.kaktushose.jdac.annotations.interactions.CommandScope; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import io.github.kaktushose.jdac.message.placeholder.Entry; +import net.dv8tion.jda.api.utils.TimeFormat; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.chrono.ChronoLocalDateTime; + +@Bundle("locale") +@Interaction +public class RegisterCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public RegisterCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "register") + public void onCommand(CommandEvent event) { + var guildId = event.getGuild().getIdLong(); + var optJam = commandContextProvider.pluginJamService().getCurrentOrUpcoming(guildId); + + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error-noupcomingjam"); + return; + } + + var jam = optJam.get(); + + var registrationStart = jam.time().registrationStart(); + var registrationEnd = jam.time().registrationEnd(); + + if (registrationStart.isAfter(ChronoLocalDateTime.from(ZonedDateTime.now()))) { + event.with().ephemeral(true).reply("command-register-message-notyet", Entry.entry("TIMESTAMP", TimeFormat.DATE_TIME_LONG.format(registrationStart))); + return; + } + + if (registrationEnd.isBefore(ChronoLocalDateTime.from(ZonedDateTime.now()))) { + event.with().ephemeral(true).reply("command-register-message-notanymore"); + return; + } + + if (jam.registrations().stream().anyMatch(member -> member.userId().equals(event.getMember().getIdLong()))) { + event.with().ephemeral(true).reply("command-register-message-alreadyregistered"); + return; + } + + commandContextProvider.pluginJamService().registerUser(jam.id(), event.getMember().getIdLong()); + + var settings = commandContextProvider.settingsService().getSettings(guildId); + var role = event.getGuild().getRoleById(settings.getParticipantRole()); + if (role != null) { + event.getGuild().addRoleToMember(event.getMember(), role).queue(); + } + event.with().ephemeral(true).reply( + "command-register-message-registered", + Entry.entry("TIMESTAMP", TimeFormat.DATE_TIME_LONG.format(ZonedDateTime.of(jam.time().registrationStart(), ZoneId.of(jam.time().zoneId())))) + ); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/register/UnregisterCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/register/UnregisterCommand.java new file mode 100644 index 00000000..057b7015 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/register/UnregisterCommand.java @@ -0,0 +1,59 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.register; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.CommandConfig; +import io.github.kaktushose.jdac.annotations.interactions.CommandScope; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; + +@Bundle("locale") +@Interaction +public class UnregisterCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public UnregisterCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "unregister") + public void onCommand(CommandEvent event) { + var guildId = event.getGuild().getIdLong(); + var optJam = commandContextProvider.pluginJamService().getCurrentOrUpcoming(guildId); + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error-noupcomingjam"); + return; + } + + var jam = optJam.get(); + + if (jam.registrations().stream().noneMatch(r -> r.userId().equals(event.getMember().getIdLong()))) { + event.with().ephemeral(true).reply("command-unregister-message-notregistered"); + return; + } + + var optTeam = commandContextProvider.teamService().getUserTeam(event.getMember().getIdLong()); + if (optTeam.isPresent()) { + event.with().ephemeral(true).reply("command-unregister-message-inteam"); + return; + } + + commandContextProvider.pluginJamService().unregisterUser(jam.id(), event.getMember().getIdLong()); + + var settings = commandContextProvider.settingsService().getSettings(guildId); + var role = event.getGuild().getRoleById(settings.getParticipantRole()); + if (role != null) { + event.getGuild().removeRoleFromMember(event.getMember(), role).queue(); + } + event.with().ephemeral(true).reply("command-unregister-message-unregistered"); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/settings/SettingsInfoCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/settings/SettingsInfoCommand.java new file mode 100644 index 00000000..e6fbdeff --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/settings/SettingsInfoCommand.java @@ -0,0 +1,39 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.settings; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import de.chojo.pluginjam.bot.message.EmbedHelper; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import io.github.kaktushose.jdac.message.resolver.MessageResolver; + +@Bundle("locale") +@Interaction +public final class SettingsInfoCommand { + private final CommandContextProvider commandContextProvider; + private final MessageResolver messageResolver; + + @Inject + public SettingsInfoCommand(CommandContextProvider commandContextProvider, MessageResolver messageResolver) { + this.commandContextProvider = commandContextProvider; + this.messageResolver = messageResolver; + } + + @Command(value = "settings info") + public void onCommand(CommandEvent event) { + var guildId = event.getGuild().getIdLong(); + + var settings = commandContextProvider.settingsService().getSettings(guildId); + var embed = EmbedHelper.buildSettingsInfoEmbed(settings, messageResolver, event.getUserLocale()); + + event.reply(embed); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/settings/SettingsRoleOrgaCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/settings/SettingsRoleOrgaCommand.java new file mode 100644 index 00000000..a2067ebc --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/settings/SettingsRoleOrgaCommand.java @@ -0,0 +1,35 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.settings; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.interactions.commands.OptionType; + +@Bundle("locale") +@Interaction +public final class SettingsRoleOrgaCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public SettingsRoleOrgaCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "settings role orga") + public void onCommand(CommandEvent event, @Param(value = "role", type = OptionType.ROLE) Role role) { + var guildId = event.getGuild().getIdLong(); + commandContextProvider.settingsService().setOrgaRole(guildId, role.getIdLong()); + event.with().ephemeral(true).reply("command-settings-orgarole-message-updated"); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/settings/SettingsRoleParticipantCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/settings/SettingsRoleParticipantCommand.java new file mode 100644 index 00000000..85a1a568 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/settings/SettingsRoleParticipantCommand.java @@ -0,0 +1,35 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.settings; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.interactions.commands.OptionType; + +@Bundle("locale") +@Interaction +public final class SettingsRoleParticipantCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public SettingsRoleParticipantCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "settings role participant") + public void onCommand(CommandEvent event, @Param(value = "role", type = OptionType.ROLE) Role role) { + var guildId = event.getGuild().getIdLong(); + commandContextProvider.settingsService().setJamRole(guildId, role.getIdLong()); + event.with().ephemeral(true).reply("command-settings-jamrole-message-updated"); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/settings/SettingsTeamSizeCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/settings/SettingsTeamSizeCommand.java new file mode 100644 index 00000000..a353e065 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/settings/SettingsTeamSizeCommand.java @@ -0,0 +1,33 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.settings; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; + +@Bundle("locale") +@Interaction +public final class SettingsTeamSizeCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public SettingsTeamSizeCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "settings teamsize") + public void onCommand(CommandEvent event, @Param("size") int size) { + var guildId = event.getGuild().getIdLong(); + commandContextProvider.settingsService().setTeamSize(guildId, size); + event.with().ephemeral(true).reply("command-settings-teamsize-message-updated"); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamCreateCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamCreateCommand.java new file mode 100644 index 00000000..b1c37521 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamCreateCommand.java @@ -0,0 +1,117 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.team; + + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import de.chojo.pluginjam.bot.message.StringValidator; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import net.dv8tion.jda.api.Permission; + +import java.util.Collections; +import java.util.EnumSet; + +@Bundle("locale") +@Interaction +public final class TeamCreateCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public TeamCreateCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "team create") + public void onCommand(CommandEvent event, @Param("name") String teamName) { + var guildId = event.getGuild().getIdLong(); + + var optJam = commandContextProvider.pluginJamService().getCurrentOrUpcoming(guildId); + + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error-nojamactive"); + return; + } + var jam = optJam.get(); + + if (jam.state().voting()) { + event.with().ephemeral(true).reply("error-votingactive"); + return; + } + + if (jam.registrations().stream().noneMatch(r -> r.userId().equals(event.getMember().getIdLong()))) { + event.with().ephemeral(true).reply("command-team-create-message-unregistered"); + return; + } + var existingTeam = commandContextProvider.teamService().getUserTeam(event.getMember().getIdLong()); + if (existingTeam.isPresent()) { + event.with().ephemeral(true).reply("command-team-create-message-alreadymember"); + return; + } + + if (!StringValidator.isValidTeamName(teamName)) { + event.with().ephemeral(true).reply("command-team-create-message-invalidname"); + return; + } + + var optTeam = commandContextProvider.teamService().getTeamByName(jam.id(), teamName); + + if (optTeam.isPresent()) { + event.with().ephemeral(true).reply("command-team-create-message-nametaken"); + return; + } + + event.deferReply(true); + + var categoryList = event.getGuild().getCategoriesByName("Team", true); + + var optCategory = categoryList.stream().filter(cat -> cat.getChannels().size() < 48).findFirst(); + // This is really hacky and I dont like it. + // All this stuff is blocking atm but in a different thread already + var category = optCategory.orElseGet(() -> event.getGuild().createCategory("Team").complete()); + + var role = event.getGuild() + .createRole() + .setPermissions(0L) + .setMentionable(false) + .setHoisted(false) + .setName(teamName) + .complete(); + + var textChannel = event.getGuild().createTextChannel(teamName.replace(" ", "-"), category) + .addRolePermissionOverride(role.getIdLong(), EnumSet.of(Permission.VIEW_CHANNEL), Collections.emptySet()) + .addMemberPermissionOverride(event.getJDA().getSelfUser() + .getIdLong(), EnumSet.of(Permission.VIEW_CHANNEL, Permission.MANAGE_CHANNEL), Collections.emptySet()) + .addRolePermissionOverride(event.getGuild().getPublicRole() + .getIdLong(), Collections.emptySet(), EnumSet.of(Permission.VIEW_CHANNEL)) + .complete(); + + var voiceChannel = event.getGuild().createVoiceChannel(teamName, category) + .addRolePermissionOverride(role.getIdLong(), EnumSet.of(Permission.VIEW_CHANNEL), Collections.emptySet()) + .addMemberPermissionOverride(event.getJDA().getSelfUser() + .getIdLong(), EnumSet.of(Permission.VIEW_CHANNEL, Permission.MANAGE_CHANNEL), Collections.emptySet()) + .addRolePermissionOverride(event.getGuild().getPublicRole() + .getIdLong(), Collections.emptySet(), EnumSet.of(Permission.VIEW_CHANNEL)) + .complete(); + + var team = commandContextProvider.teamService().createTeam( + jam.id(), + teamName, + event.getMember().getIdLong(), + role.getIdLong(), + textChannel.getIdLong(), + voiceChannel.getIdLong() + ); + + commandContextProvider.teamService().joinTeam(team, event.getMember(), event.getGuild()); + event.with().ephemeral(true).reply("command-team-create-message-created"); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamDisbandCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamDisbandCommand.java new file mode 100644 index 00000000..f9f27475 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamDisbandCommand.java @@ -0,0 +1,72 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.team; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import io.github.kaktushose.jdac.message.resolver.MessageResolver; + +import java.util.Optional; + +@Bundle("locale") +@Interaction +public final class TeamDisbandCommand { + private final CommandContextProvider commandContextProvider; + private final MessageResolver messageResolver; + + @Inject + public TeamDisbandCommand(CommandContextProvider commandContextProvider, MessageResolver messageResolver) { + this.commandContextProvider = commandContextProvider; + this.messageResolver = messageResolver; + } + + @Command(value = "team disband") + public void onCommand(CommandEvent event, @Param("confirm") boolean confirm) { + var optJam = commandContextProvider.pluginJamService().getCurrentOrUpcoming(event.getGuild().getIdLong()); + + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error-nojamactive"); + return; + } + var jam = optJam.get(); + + if (jam.state().voting()) { + event.with().ephemeral(true).reply("error-votingactive"); + return; + } + + if (!confirm) { + event.with().ephemeral(true).reply("error-noconfirm"); + return; + } + + var existingTeamOpt = commandContextProvider.teamService().getUserTeam(event.getMember().getIdLong()); + if (existingTeamOpt.isEmpty()) { + event.with().ephemeral(true).reply("error-noteam"); + return; + } + + var team = existingTeamOpt.get(); + + var members = team.members(); + for (var teamMember : members) { + var member = event.getGuild().getMemberById(teamMember.userId()); + var user = member.getUser(); + user.openPrivateChannel() + .flatMap(channel -> channel.sendMessage(messageResolver.resolve("command-team-disband-message-disbanded", member.getGuild().getLocale()))) + .queue(); + } + + commandContextProvider.teamService().disbandTeam(team, event.getGuild()); + event.with().ephemeral(true).reply("command-team-disband-message-disbanded"); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamInviteCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamInviteCommand.java new file mode 100644 index 00000000..eaf890fe --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamInviteCommand.java @@ -0,0 +1,111 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.team; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import io.github.kaktushose.jdac.message.placeholder.Entry; +import io.github.kaktushose.jdac.message.resolver.MessageResolver; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.components.Component; +import net.dv8tion.jda.api.components.actionrow.ActionRow; +import net.dv8tion.jda.api.components.buttons.Button; +import net.dv8tion.jda.api.components.container.Container; +import net.dv8tion.jda.api.components.textdisplay.TextDisplay; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.interactions.commands.OptionType; + +@Bundle("locale") +@Interaction +public final class TeamInviteCommand { + private final CommandContextProvider commandContextProvider; + private final MessageResolver messageResolver; + + @Inject + public TeamInviteCommand(CommandContextProvider commandContextProvider, MessageResolver messageResolver) { + this.commandContextProvider = commandContextProvider; + this.messageResolver = messageResolver; + } + + @Command(value = "team invite") + public void onCommand(CommandEvent event, @Param(value = "user", type = OptionType.USER) User user) { + var guildId = event.getGuild().getIdLong(); + var optJam = commandContextProvider.pluginJamService().getCurrentOrUpcoming(guildId); + + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error-nojamactive"); + return; + } + var jam = optJam.get(); + + if (jam.state().voting()) { + event.with().ephemeral(true).reply("error-votingactive"); + return; + } + + var optTeam = commandContextProvider.teamService().getUserTeam(event.getUser().getIdLong()); + + if (optTeam.isEmpty()) { + event.with().ephemeral(true).reply("error-noteam"); + return; + } + + var team = optTeam.get(); + + if (!team.isLeader(event.getUser())) { + event.with().ephemeral(true).reply("command-team-invite-message-noleader"); + return; + } + + var members = team.members(); + var settings = commandContextProvider.settingsService().getSettings(guildId); + + if (members.size() >= settings.getTeamSize()) { + event.with().ephemeral(true).reply("error-maxteamsize"); + return; + } + + if (jam.registrations().stream().noneMatch(r -> r.userId().equals(user.getIdLong()))) { + event.with().ephemeral(true).reply("command-team-invite-message-notRegistered"); + return; + } + + var currTeam = commandContextProvider.teamService().getUserTeam(user.getIdLong()); + + if (currTeam.isPresent()) { + event.reply("command-team-invite-message-partofteam"); + return; + } + + var teamId = team.id(); + var buttonId = "invite-accept:" + guildId + ":" + teamId + ":" + user.getIdLong(); + + var components = Container.of( + TextDisplay.of(messageResolver.resolve("command-team-invite-message-invitation", event.getGuildLocale(), + Entry.entry("USER", user.getAsMention()), + Entry.entry("TEAM", team.meta().getTeamName())) + ), + ActionRow.of(Button.success(buttonId, messageResolver.resolve("command-team-invite-message-accept", event.getGuildLocale()))) + ); + + var embed = new EmbedBuilder() + .setTitle("You have been invited to join a team on " + event.getGuild().getName()) + .setDescription(event.getUser().getAsMention() + " has invited you to join team **" + team.meta().getTeamName() + "**") + .build(); + + user.openPrivateChannel().queue(channel -> + channel.sendMessageComponents(components).useComponentsV2().queue() + ); + + event.with().ephemeral(true).reply("command-team-invite-message-send"); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamLeaveCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamLeaveCommand.java new file mode 100644 index 00000000..c301bbb2 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamLeaveCommand.java @@ -0,0 +1,56 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.team; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; + +@Bundle("locale") +@Interaction +public final class TeamLeaveCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public TeamLeaveCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "team leave") + public void onCommand(CommandEvent event) { + var guildId = event.getGuild().getIdLong(); + var optJam = commandContextProvider.pluginJamService().getCurrentOrUpcoming(guildId); + + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error-nojamactive"); + return; + } + var jam = optJam.get(); + + if (jam.state().voting()) { + event.with().ephemeral(true).reply("error-votingactive"); + return; + } + + var teamOpt = commandContextProvider.teamService().getUserTeam(event.getMember().getIdLong()); + teamOpt.ifPresentOrElse((team) -> { + if (team.isLeader(event.getUser())) { + event.with().ephemeral(true).reply("command-team-leave-message-leaderleave"); + return; + } + + commandContextProvider.teamService().leaveTeam(team, event.getMember(), event.getGuild()); + event.with().ephemeral(true).reply("command-team-leave-left"); + event.getGuild().getTextChannelById(team.meta().getTextChannelId()).sendMessage("command-team-leave-message-left").queue(); + }, () -> { + event.with().ephemeral(true).reply("error-noteam"); + }); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamListCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamListCommand.java new file mode 100644 index 00000000..7024ed38 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamListCommand.java @@ -0,0 +1,125 @@ + /* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + + package de.chojo.pluginjam.bot.commands.team; + + import com.google.inject.Inject; + import de.chojo.pluginjam.bot.commands.CommandContextProvider; + import de.chojo.pluginjam.bot.message.DefaultValues; + import de.chojo.pluginjam.bot.util.MentionUtil; + import de.chojo.pluginjam.database.entity.team.Team; + import io.github.kaktushose.jdac.annotations.i18n.Bundle; + import io.github.kaktushose.jdac.annotations.interactions.Button; + import io.github.kaktushose.jdac.annotations.interactions.Command; + import io.github.kaktushose.jdac.annotations.interactions.Interaction; + import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; + import io.github.kaktushose.jdac.dispatching.events.interactions.ComponentEvent; + import io.github.kaktushose.jdac.dispatching.reply.Component; + import io.github.kaktushose.jdac.message.resolver.MessageResolver; + import net.dv8tion.jda.api.components.actionrow.ActionRow; + import net.dv8tion.jda.api.components.buttons.ButtonStyle; + import net.dv8tion.jda.api.components.container.Container; + import net.dv8tion.jda.api.components.container.ContainerChildComponent; + import net.dv8tion.jda.api.components.textdisplay.TextDisplay; + import net.dv8tion.jda.api.interactions.DiscordLocale; + + import java.util.List; + import java.util.UUID; + import java.util.stream.Collectors; + + @Bundle("locale") + @Interaction + public class TeamListCommand { + private final CommandContextProvider commandContextProvider; + private final MessageResolver messageResolver; + + private List pageEntries; + private int currentPage; + + @Inject + public TeamListCommand(CommandContextProvider commandContextProvider, MessageResolver messageResolver) { + this.commandContextProvider = commandContextProvider; + this.messageResolver = messageResolver; + } + + @Command(value = "team list") + public void onCommand(CommandEvent event) { + var guildId = event.getGuild().getIdLong(); + var optJam = commandContextProvider.pluginJamService().getCurrentOrUpcoming(guildId); + + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error-nojamactive"); + return; + } + var jam = optJam.get(); + + pageEntries = commandContextProvider.teamService().getTeamsByJamId(jam.id()); + + currentPage = 0; + event.with().ephemeral(true).reply(renderPage(0, pageEntries, event.getUserLocale())); + } + + private Container renderPage(int page, List entries, DiscordLocale locale) { + int pageSize = 1; + + boolean hasNext = page < (entries.size() - 1) / pageSize; + boolean hasPrev = page > 0; + + int start = page * pageSize; + int end = Math.min(start + pageSize, entries.size()); + + List teamContents = entries.subList(start, end).stream().map(team -> { + var membersContent = team.members() + .stream() + .map(teamMember -> String.format("- %s", MentionUtil.user(teamMember.userId()))) + .collect(Collectors.joining(" \n")); + + var contentString = """ + # %s (%s) + ### %s: + *%s* + ### %s: + *%s* + ### %s: + *%s* + """.formatted( + team.meta().getTeamName(), + MentionUtil.role(team.meta().getRoleId()), + messageResolver.resolve("word-description", locale), + DefaultValues.getTeamProjectDescription(team, messageResolver, locale), + messageResolver.resolve("word-project-url", locale), + DefaultValues.getTeamProjectUrl(team, messageResolver, locale), + messageResolver.resolve("word-members", locale), + membersContent); + + return TextDisplay.of(contentString); + }).collect(Collectors.toList()); + + int maxPages = (int) Math.ceil(entries.size() / (double) pageSize); + + teamContents.add( + ActionRow.of( + hasPrev ? Component.enabled("onPrev") : Component.disabled("onPrev"), + net.dv8tion.jda.api.components.buttons.Button.of(ButtonStyle.SECONDARY, UUID.randomUUID().toString(), "%d / %d".formatted(currentPage + 1, maxPages), null), + hasNext ? Component.enabled("onNext") : Component.disabled("onNext") + ) + ); + + return Container.of(teamContents); + } + + @Button(value = "◀", style = ButtonStyle.SECONDARY) + public void onPrev(ComponentEvent event) { + currentPage--; + event.with().keepComponents(false).ephemeral(true).reply(renderPage(currentPage, pageEntries, event.getUserLocale())); + } + + @Button(value = "▶", style = ButtonStyle.SECONDARY) + public void onNext(ComponentEvent event) { + currentPage++; + event.with().keepComponents(false).ephemeral(true).reply(renderPage(currentPage, pageEntries, event.getUserLocale())); + } + } diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamProfileCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamProfileCommand.java new file mode 100644 index 00000000..f877e682 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamProfileCommand.java @@ -0,0 +1,81 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.team; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import de.chojo.pluginjam.bot.message.EmbedHelper; +import de.chojo.pluginjam.database.entity.team.Team; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.*; +import io.github.kaktushose.jdac.dispatching.events.interactions.AutoCompleteEvent; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import io.github.kaktushose.jdac.embeds.EmbedConfig; +import io.github.kaktushose.jdac.embeds.EmbedDataSource; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.interactions.commands.OptionType; + +import java.util.Collections; +import java.util.List; + +@Bundle("locale") +@Interaction +public final class TeamProfileCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public TeamProfileCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "team profile") + public void onCommand( + CommandEvent event, + @Param(name = "user", optional = true, type = OptionType.USER) User user, + @Param(value = "team", optional = true) String teamName + ) { + var guildId = event.getGuild().getIdLong(); + var optJam = commandContextProvider.pluginJamService().getCurrentOrUpcoming(guildId); + + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error-nojamactive"); + return; + } + var jam = optJam.get(); + var teams = commandContextProvider.teamService().getTeamsByJamId(jam.id()); + + if (user != null) { + var userTeam = commandContextProvider.teamService().getUserTeam(user.getIdLong()); + userTeam.ifPresentOrElse(team -> sendProfile(event, team), + () -> event.with().ephemeral(true).reply("command-team-profile-message-nouserteam")); + return; + } + if (teamName != null) { + var namedTeam = commandContextProvider.teamService().getTeamByName(jam.id(), teamName); + + namedTeam.ifPresentOrElse(team -> sendProfile(event, team), + () -> event.with().ephemeral(true).reply("error-unkownteam")); + return; + } + var userTeam = commandContextProvider.teamService().getUserTeam(event.getMember().getIdLong()); + + userTeam.ifPresentOrElse(team -> sendProfile(event, team), + () -> event.with().ephemeral(true).reply("error-noteam")); + } + + private void sendProfile(CommandEvent event, Team team) { + event.reply(EmbedHelper.teamProfileEmbed(team, event)); + } + + @AutoComplete(value = "team profile", options = "team") + public void onAutoComplete(AutoCompleteEvent event) { + var guildId = event.getGuild().getIdLong(); + var teams = commandContextProvider.teamService().getTeamsByJamId(commandContextProvider.pluginJamService().getCurrentOrUpcoming(guildId).get().id()); + var choices = teams.stream().map(team -> team.meta().getTeamName()).map(s -> new net.dv8tion.jda.api.interactions.commands.Command.Choice(s, s)).toList(); + event.replyChoices(choices); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamPromoteCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamPromoteCommand.java new file mode 100644 index 00000000..ffc78021 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamPromoteCommand.java @@ -0,0 +1,56 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.team; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.interactions.commands.OptionType; + +@Bundle("locale") +@Interaction +public class TeamPromoteCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public TeamPromoteCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "team promote") + public void onCommand(CommandEvent event, @Param(value = "user", type = OptionType.USER) User user) { + var guildId = event.getGuild().getIdLong(); + var optJam = commandContextProvider.pluginJamService().getCurrentOrUpcoming(guildId); + + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error-nojamactive"); + return; + } + + var member = event.getGuild().getMember(user); + + var currentTeam = commandContextProvider.teamService().getUserTeam(user.getIdLong()); + + currentTeam.ifPresentOrElse((targetTeam) -> { + if (!targetTeam.isLeader(event.getUser())) { + event.with().ephemeral(true).reply("error-noleader"); + return; + } + + targetTeam.meta().getLeaderId(member.getIdLong()); + commandContextProvider.teamService().saveTeam(targetTeam); + event.with().ephemeral(true).reply("command.team.promote.message.done"); + }, () -> { + event.with().ephemeral(true).reply("error-noteam"); + }); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamRenameCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamRenameCommand.java new file mode 100644 index 00000000..8bf691cd --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/team/TeamRenameCommand.java @@ -0,0 +1,62 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.team; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; + +@Bundle("locale") +@Interaction +public class TeamRenameCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public TeamRenameCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "team rename") + public void onCommand(CommandEvent event, @Param("name") String teamName) { + var guildId = event.getGuild().getIdLong(); + var optJam = commandContextProvider.pluginJamService().getCurrentOrUpcoming(guildId); + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error-nojamactive"); + return; + } + var jam = optJam.get(); + + var existingTeam = commandContextProvider.teamService().getTeamByName(jam.id(), teamName); + if (existingTeam.isPresent()) { + event.with().ephemeral(true).reply("command-team-create-message-nametaken"); + return; + } + + var optCurrTeam = commandContextProvider.teamService().getUserTeam(event.getMember().getIdLong()); + + if (optCurrTeam.isEmpty()) { + event.with().ephemeral(true).reply("error-noteam"); + return; + } + + var team = optCurrTeam.get(); + + if (!team.isLeader(event.getUser())) { + event.with().ephemeral(true).reply("error-noleader"); + return; + } + + team.meta().getTeamName(teamName); + + commandContextProvider.teamService().saveTeam(team); + event.with().ephemeral(true).reply("command-team-rename-message-done"); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/vote/VoteCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/vote/VoteCommand.java new file mode 100644 index 00000000..c6bfdb9e --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/vote/VoteCommand.java @@ -0,0 +1,117 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.vote; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.AutoComplete; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.AutoCompleteEvent; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import io.github.kaktushose.jdac.message.placeholder.Entry; +import net.dv8tion.jda.api.interactions.commands.Command; + +import java.util.Collections; +import java.util.stream.IntStream; + +import static net.dv8tion.jda.api.utils.MarkdownUtil.bold; + +@Bundle("locale") +@Interaction +public class VoteCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public VoteCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @io.github.kaktushose.jdac.annotations.interactions.Command(value = "vote") + public void onCommand(CommandEvent event, @Param("team") String team, @Param("points") int points) { + var guildId = event.getGuild().getIdLong(); + var optJam = commandContextProvider.pluginJamService().getCurrentOrUpcoming(guildId); + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error-nojamactive"); + return; + } + + var jam = optJam.get(); + + event.deferReply(true); + if (!jam.state().voting()) { + event.reply("command-votes-vote-message-notactive"); + return; + } + + if (!jam.registrations().contains(event.getMember().getIdLong())) { + event.reply("error-notregistered"); + return; + } + + var teams = commandContextProvider.teamService().getTeamsByJamId(jam.id()); + var teamCount = teams.size(); + var optVoteTeam = commandContextProvider.teamService().getTeamByName(jam.id(), team); + + if (optVoteTeam.isEmpty()) { + event.reply("error-unkownteam"); + return; + } + + var voteTeam = optVoteTeam.get(); + + if (voteTeam.isMember(event.getUser())) { + event.reply("command-votes-vote-message-ownteam"); + return; + } + + var pointsGiven = commandContextProvider.voteService().getGivenPointsByUser(event.getUser()); + + //TODO: Max points and max points per team are currently hardcoded. should be configurable in the future. + var finalPoints = Math.clamp(points, 0, 5); + + var votesForTeam = commandContextProvider.voteService().getPointsByTeam(voteTeam); + + if (votesForTeam < finalPoints && pointsGiven + finalPoints > teamCount) { + event.reply("command-votes-vote-message-maxpointsreached", Entry.entry("REMAINING", bold(String.valueOf(teamCount - pointsGiven)))); + return; + } + + commandContextProvider.voteService().voteForTeam(event.getUser(), voteTeam, finalPoints); + var pointsGivenAfter = commandContextProvider.voteService().getGivenPointsByUser(event.getUser()); + + event.reply("command-votes-vote-message-done", + Entry.entry("REMAINING", bold(String.valueOf(teamCount - pointsGivenAfter))), + Entry.entry("POINTS", bold(String.valueOf(finalPoints))), + Entry.entry("TEAM", bold(voteTeam.meta().getTeamName())) + ); + } + + @AutoComplete(value = "vote", options = "team") + public void onAutoCompleteTeam(AutoCompleteEvent event) { + var jam = commandContextProvider.pluginJamService().getActiveJam(event.getGuild().getIdLong()); + + if (jam.isEmpty()) { + event.replyChoices(Collections.emptyList()); + return; + } + + var teams = commandContextProvider.teamService().getTeamsByJamId(jam.get().id()).stream() + .filter(team -> team.meta().getTeamName().contains(event.getValue())) + .map(team -> team.meta().getTeamName()) + .map(team -> new Command.Choice(team, team)) + .toList(); + event.replyChoices(teams); + } + + @AutoComplete(value = "vote", options = "points") + public void onAutoCompletePoints(AutoCompleteEvent event) { + event.replyChoices(IntStream.range(0, 6).mapToObj(num -> new Command.Choice(String.valueOf(num), num)) + .toList()); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/vote/VotesInfoCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/vote/VotesInfoCommand.java new file mode 100644 index 00000000..f5ddde4e --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/vote/VotesInfoCommand.java @@ -0,0 +1,40 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.vote; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; + +import java.util.stream.Collectors; + +@Bundle("locale") +@Interaction +public class VotesInfoCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public VotesInfoCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "votes info") + public void onCommand(CommandEvent event) { + var guildId = event.getGuild().getIdLong(); + var optJam = commandContextProvider.pluginJamService().getActiveJam(guildId); + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error-nojamactive"); + return; + } + var jam = optJam.get(); + + // TODO: implement a nice info message + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/commands/vote/VotesRankingCommand.java b/backend/src/main/java/de/chojo/pluginjam/bot/commands/vote/VotesRankingCommand.java new file mode 100644 index 00000000..de8de682 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/commands/vote/VotesRankingCommand.java @@ -0,0 +1,127 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.commands.vote; + +import com.google.inject.Inject; +import de.chojo.pluginjam.bot.commands.CommandContextProvider; +import de.chojo.pluginjam.database.entity.VotingRank; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Button; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import io.github.kaktushose.jdac.dispatching.events.interactions.ComponentEvent; +import io.github.kaktushose.jdac.dispatching.reply.Component; +import io.github.kaktushose.jdac.message.resolver.MessageResolver; +import net.dv8tion.jda.api.components.actionrow.ActionRow; +import net.dv8tion.jda.api.components.buttons.ButtonStyle; +import net.dv8tion.jda.api.components.container.Container; +import net.dv8tion.jda.api.components.container.ContainerChildComponent; +import net.dv8tion.jda.api.components.separator.Separator; +import net.dv8tion.jda.api.components.textdisplay.TextDisplay; +import net.dv8tion.jda.api.interactions.DiscordLocale; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@Bundle("locale") +@Interaction +public class VotesRankingCommand { + private final CommandContextProvider commandContextProvider; + private final MessageResolver messageResolver; + + private List pageEntries; + private int currentPage; + + @Inject + public VotesRankingCommand(CommandContextProvider commandContextProvider, MessageResolver messageResolver) { + this.commandContextProvider = commandContextProvider; + this.messageResolver = messageResolver; + } + + @Command(value = "votes ranking") + public void onCommand(CommandEvent event) { + var guildId = event.getGuild().getIdLong(); + var optJam = commandContextProvider.pluginJamService().getActiveJam(guildId); + + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error-nojamactive"); + return; + } + + var jam = optJam.get(); + + if (jam.state().voting()) { + event.with().ephemeral(true).reply("command-votes-ranking-message-voteactive"); + return; + } + + pageEntries = commandContextProvider.voteService().getVotingRanks(jam.id()); + currentPage = 0; + event.with().ephemeral(true).reply(renderPage(0, pageEntries, event.getUserLocale())); + } + + private Container renderPage(int page, List entries, DiscordLocale locale) { + int pageSize = 10; + + boolean hasNext = page < (entries.size() - 1) / pageSize; + boolean hasPrev = page > 0; + + int start = page * pageSize; + int end = Math.min(start + pageSize, entries.size()); + + var contents = new ArrayList(); + + var rankingContent = entries.subList(start, end).stream() + .map(votingRank -> String.format("`%s` **%s** (*%d Votes*)", + getRankFormat(votingRank.rank()), + votingRank.team().meta().getTeamName(), + votingRank.points())) + .collect(Collectors.joining("\n")); + + contents.add(TextDisplay.of("## " + messageResolver.resolve("command-votes-ranking-description", locale))); + contents.add(Separator.create(true, Separator.Spacing.LARGE)); + contents.add(TextDisplay.of(rankingContent)); + contents.add(Separator.create(true, Separator.Spacing.LARGE)); + + int maxPages = (int) Math.ceil(entries.size() / (double) pageSize); + + contents.add( + ActionRow.of( + hasPrev ? Component.enabled("onPrev") : Component.disabled("onPrev"), + net.dv8tion.jda.api.components.buttons.Button.of(ButtonStyle.SECONDARY, UUID.randomUUID().toString(), "%d / %d".formatted(currentPage + 1, maxPages), null), + hasNext ? Component.enabled("onNext") : Component.disabled("onNext") + ) + ); + + return Container.of(contents); + } + + @Button(value = "◀", style = ButtonStyle.SECONDARY) + public void onPrev(ComponentEvent event) { + currentPage--; + event.with().keepComponents(false).ephemeral(true).reply(renderPage(currentPage, pageEntries, event.getUserLocale())); + } + + @Button(value = "▶", style = ButtonStyle.SECONDARY) + public void onNext(ComponentEvent event) { + currentPage++; + event.with().keepComponents(false).ephemeral(true).reply(renderPage(currentPage, pageEntries, event.getUserLocale())); + } + + private String getRankFormat(int rank) { + return switch (rank) { + case 1 -> "🥇1"; + case 2 -> "🥈2"; + case 3 -> "🥉3"; + default -> " #%d".formatted(rank); + }; + } + +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/config/DockerConfig.java b/backend/src/main/java/de/chojo/pluginjam/bot/config/DockerConfig.java new file mode 100644 index 00000000..410783dc --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/config/DockerConfig.java @@ -0,0 +1,52 @@ +package de.chojo.pluginjam.bot.config; + +import io.micronaut.context.annotation.ConfigurationProperties; + +@ConfigurationProperties("docker") +public class DockerConfig { + private String host = "unix:///var/run/docker.sock"; + private String certPath = "/home/user/.docker"; + private boolean tlsVerify = false; + private String registryUsername; + private String registryPassword; + private String registryEmail; + private String registryUrl; + private String networkName = "plugin-jam-network"; + private String teamServerImage = "plugin-jam-mc-server:latest"; + + public String getHost() { + return host; + } + + public String getCertPath() { + return certPath; + } + + public boolean isTlsVerify() { + return tlsVerify; + } + + public String getRegistryUsername() { + return registryUsername; + } + + public String getRegistryPassword() { + return registryPassword; + } + + public String getRegistryEmail() { + return registryEmail; + } + + public String getRegistryUrl() { + return registryUrl; + } + + public String getNetworkName() { + return networkName; + } + + public String getTeamServerImage() { + return teamServerImage; + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/config/PluginsConfig.java b/backend/src/main/java/de/chojo/pluginjam/bot/config/PluginsConfig.java new file mode 100644 index 00000000..a44dd380 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/config/PluginsConfig.java @@ -0,0 +1,20 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.config; + +import io.micronaut.context.annotation.ConfigurationProperties; + +import java.util.List; + +@ConfigurationProperties("plugins") +public class PluginsConfig { + private List defaultPlugins = List.of(); + + public List getDefaultPlugins() { + return defaultPlugins; + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/listener/InviteButtonListener.java b/backend/src/main/java/de/chojo/pluginjam/bot/listener/InviteButtonListener.java new file mode 100644 index 00000000..e164d53a --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/listener/InviteButtonListener.java @@ -0,0 +1,104 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.listener; + +import de.chojo.pluginjam.service.JamService; +import de.chojo.pluginjam.service.SettingsService; +import de.chojo.pluginjam.service.TeamService; +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import org.slf4j.Logger; + +import static org.slf4j.LoggerFactory.getLogger; + +public class InviteButtonListener extends ListenerAdapter { + private static final Logger log = getLogger(InviteButtonListener.class); + private static final String BUTTON_PREFIX = "invite-accept:"; + + private final TeamService teamService; + private final SettingsService settingsService; + private final JamService jamService; + + public InviteButtonListener(TeamService teamService, SettingsService settingsService, JamService jamService) { + this.teamService = teamService; + this.settingsService = settingsService; + this.jamService = jamService; + } + + @Override + public void onButtonInteraction(ButtonInteractionEvent event) { + var componentId = event.getComponentId(); + if (!componentId.startsWith(BUTTON_PREFIX)) { + return; + } + + event.deferReply().queue(); + + var parts = componentId.substring(BUTTON_PREFIX.length()).split(":"); + if (parts.length != 3) { + event.getHook().editOriginal("Invalid invite data.").queue(); + return; + } + + long guildId; + int teamId; + long userId; + try { + guildId = Long.parseLong(parts[0]); + teamId = Integer.parseInt(parts[1]); + userId = Long.parseLong(parts[2]); + } catch (NumberFormatException e) { + event.getHook().editOriginal("Invalid invite data.").queue(); + return; + } + + var manager = event.getJDA().getShardManager(); + var guild = manager.getGuildById(guildId); + if (guild == null) { + event.getHook().editOriginal("Guild not found.").queue(); + return; + } + + var user = manager.retrieveUserById(userId).complete(); + var member = guild.retrieveMember(user).complete(); + var settings = settingsService.getSettings(guildId); + + var optJam = jamService.getCurrentOrUpcoming(guildId); + if (optJam.isEmpty()) { + event.getHook().editOriginal("The game jam is over.").queue(); + return; + } + + var jam = optJam.get(); + var optTeam = teamService.getTeam(teamId); + if (optTeam.isEmpty()) { + event.getHook().editOriginal("Team not found.").queue(); + return; + } + + var team = optTeam.get(); + var members = team.members(); + + if (members.size() >= settings.getTeamSize()) { + event.getHook().editOriginal("The team is already full.").queue(); + return; + } + + var currTeam = teamService.getUserTeam(user.getIdLong()); + if (currTeam.isPresent()) { + event.getHook().editOriginal("You are already part of a team.").queue(); + return; + } + + teamService.joinTeam(team, member, guild); + event.getHook().editOriginal("You have joined the team!").queue(); + + guild.getTextChannelById(team.meta().getTextChannelId()).sendMessage(member.getAsMention() + " has joined the team!").queue(); + + event.getMessage().editMessageComponents().queue(); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/message/DefaultValues.java b/backend/src/main/java/de/chojo/pluginjam/bot/message/DefaultValues.java new file mode 100644 index 00000000..ae59ccc3 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/message/DefaultValues.java @@ -0,0 +1,22 @@ +package de.chojo.pluginjam.bot.message; + +import de.chojo.pluginjam.database.entity.team.Team; +import io.github.kaktushose.jdac.message.resolver.MessageResolver; +import net.dv8tion.jda.api.interactions.DiscordLocale; + +public class DefaultValues { + + public static String getTeamProjectDescription(Team team, MessageResolver resolver, DiscordLocale locale) { + if (team.meta().getProjectDescription() == null || team.meta().getProjectDescription().isBlank()) { + return resolver.resolve("word-no-description", locale); + } + return team.meta().getProjectDescription(); + } + + public static String getTeamProjectUrl(Team team, MessageResolver resolver, DiscordLocale locale) { + if (team.meta().getProjectDescription() == null || team.meta().getProjectDescription().isBlank()) { + return resolver.resolve("word-no-url", locale); + } + return team.meta().getProjectUrl(); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/message/EmbedHelper.java b/backend/src/main/java/de/chojo/pluginjam/bot/message/EmbedHelper.java new file mode 100644 index 00000000..9e838b46 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/message/EmbedHelper.java @@ -0,0 +1,98 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.bot.message; + +import de.chojo.pluginjam.bot.util.MentionUtil; +import de.chojo.pluginjam.database.entity.SettingsEntity; +import de.chojo.pluginjam.database.entity.jam.Jam; +import de.chojo.pluginjam.database.entity.team.Team; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import io.github.kaktushose.jdac.message.resolver.MessageResolver; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.interactions.DiscordLocale; +import net.dv8tion.jda.api.utils.TimeFormat; + +import java.awt.*; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.List; + +public class EmbedHelper { + + /* public static CompletableFuture embedDetailedStatus(Team team, ServerService serverService, CommandEvent event) { + return CompletableFuture.supplyAsync(() -> { + var serverStatus = serverService.getServerStatus(team.id()); + + var embed = event.embed("status"); + embed.title("%s #%d | %s".formatted(serverStatus.emoji(), team.id(), team.meta().name())); + + if (serverStatus == ServerStatus.VOID) { + embed.description("teamserver.message.detailstatus.nonexisting.description"); + } else { + if (serverStatus == ServerStatus.RUNNING) { + embed.description("teamserver.message.detailstatus.existing.description"); + + var serverStats = serverService.serverHttpService().fetchServerStats(serverService.dockerService().containerName(team.id())); + serverStats.ifPresent(stats -> { + var memory = stats.memory(); + embed.fields().add("word.memory", "$word.used$ %d%n$word.total$: %d%n$word.max$: %d".formatted(memory.usedMb(), memory.totalMb(), memory.maxMb()), true); + embed.fields().add("word.tps", "1 $word.min$: %.2f%n5 $word.min$: %.2f%n 15 $word.min$: %.2f%n$word.averageticktime$ %.2f".formatted( + stats.tps()[0], stats.tps()[1], stats.tps()[2], stats.averageTickTime()), true); + embed.fields().add("word.players", String.valueOf(stats.onlinePlayers()), true); + embed.fields().add("word.system", "$word.activethreads$: %d".formatted(stats.activeThreads()), true); + }); + } else { + embed.description("word.serversetup"); + } + } + return embed.build(); + }); + } + + */ + + public static MessageEmbed teamProfileEmbed(Team team, CommandEvent event) { + var embed = new EmbedBuilder(); + embed.setAuthor(team.meta().getTeamName(), null, team.meta().getProjectUrl()); + embed.setDescription(team.meta().getProjectDescription()); + team.members().forEach(member -> embed.addField(MentionUtil.user(member.userId()), "", true)); + return embed.build(); + } + + public static MessageEmbed buildJamListEmbed(List jams, CommandEvent event, MessageResolver messageResolver, DiscordLocale locale) { + var embed = new EmbedBuilder(); + embed.setTitle(messageResolver.resolve("word-jams", locale)); + embed.setDescription(String.join("\n", jams.stream().map(jam -> formatJam(jam, messageResolver, locale)).toList())); + return embed.build(); + } + + private static String formatJam(Jam jam, MessageResolver messageResolver, DiscordLocale locale) { + return "`" + jam.id() + " | " + jam.meta().topic() + " |`" + getJamState(jam, messageResolver, locale); + } + + private static String getJamState(Jam jam, MessageResolver messageResolver, DiscordLocale locale) { + if (jam.state().active()) { + return messageResolver.resolve("word-active", locale); + } + if (jam.state().ended()) { + return messageResolver.resolve("word-ended", locale); + } + + return messageResolver.resolve("word-upcoming", locale) + " " + TimeFormat.DATE_TIME_LONG.format(ZonedDateTime.of(jam.time().jamStart(), ZoneId.of(jam.time().zoneId()))); + } + + public static MessageEmbed buildSettingsInfoEmbed(SettingsEntity settings, MessageResolver messageResolver, DiscordLocale userLocale) { + var embed = new EmbedBuilder(); + embed.setTitle(messageResolver.resolve("command-settings-info-embed-settings", userLocale)); + embed.addField(messageResolver.resolve("command-settings-info-embed-jamrole", userLocale), MentionUtil.role(settings.getParticipantRole()), true); + embed.addField(messageResolver.resolve("command-settings-info-embed-teamsize", userLocale), String.valueOf(settings.getTeamSize()), true); + embed.addField(messageResolver.resolve("command-settings-info-embed-orgarole", userLocale), MentionUtil.role(settings.getOrgaRole()), true); + embed.setColor(Color.CYAN); + return embed.build(); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/message/StringValidator.java b/backend/src/main/java/de/chojo/pluginjam/bot/message/StringValidator.java new file mode 100644 index 00000000..be84abf8 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/message/StringValidator.java @@ -0,0 +1,10 @@ +package de.chojo.pluginjam.bot.message; + +public class StringValidator { + private static String allowedCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\\.\s_-"; + private static int maxLength = 100; + + public static boolean isValidTeamName(String input) { + return input.length() <= maxLength && input.matches("[" + allowedCharacters + "]+"); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/bot/util/MentionUtil.java b/backend/src/main/java/de/chojo/pluginjam/bot/util/MentionUtil.java new file mode 100644 index 00000000..149e56ae --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/bot/util/MentionUtil.java @@ -0,0 +1,12 @@ +package de.chojo.pluginjam.bot.util; + +public class MentionUtil { + + public static String role(long id) { + return "<@&" + id + ">"; + } + + public static String user(long id) { + return "<@" + id + ">"; + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/controller/LogWebSocket.java b/backend/src/main/java/de/chojo/pluginjam/controller/LogWebSocket.java new file mode 100644 index 00000000..6c7a8c18 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/controller/LogWebSocket.java @@ -0,0 +1,81 @@ +package de.chojo.pluginjam.controller; + +import de.chojo.pluginjam.database.entity.team.Team; +import de.chojo.pluginjam.service.ServerService; +import de.chojo.pluginjam.service.TeamService; +import io.micronaut.http.annotation.Header; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.authentication.Authentication; +import io.micronaut.security.rules.SecurityRule; +import io.micronaut.websocket.WebSocketSession; +import io.micronaut.websocket.annotation.OnClose; +import io.micronaut.websocket.annotation.OnOpen; +import io.micronaut.websocket.annotation.ServerWebSocket; +import org.slf4j.Logger; +import reactor.core.Disposable; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +import static org.slf4j.LoggerFactory.getLogger; + +@ServerWebSocket("/api/server/logs/ws") +@Secured(SecurityRule.IS_AUTHENTICATED) +public class LogWebSocket { + private static final Logger log = getLogger(LogWebSocket.class); + private final ServerService serverService; + private final TeamService teamService; + private final Map subscriptions = new ConcurrentHashMap<>(); + + public LogWebSocket(ServerService serverService, TeamService teamService) { + this.serverService = serverService; + this.teamService = teamService; + } + + @OnOpen + public void onOpen(WebSocketSession session, Authentication authentication) { + log.info("[DEBUG_LOG] WebSocket onOpen triggered for session: {}", session.getId()); + if (authentication == null) { + log.warn("[DEBUG_LOG] No authentication found for session: {}", session.getId()); + session.close(); + return; + } + Optional team = teamService.getUserTeam(Long.parseLong(authentication.getName())); + if (team.isEmpty()) { + log.warn("[DEBUG_LOG] No team found for user: {}", authentication.getName()); + session.close(); + return; + } + + log.info("Opening log websocket for team {} (user {})", team.get().id(), authentication.getName()); + + Disposable subscription = serverService.dockerService().streamLogs(team.get().id(), 100) + .repeat() + .retryWhen(reactor.util.retry.Retry.fixedDelay(Long.MAX_VALUE, java.time.Duration.ofSeconds(5))) + .subscribe(event -> { + if (session.isOpen()) { + session.sendSync(event.getData()); + } + }, throwable -> { + log.error("[DEBUG_LOG] Permanent error in log stream for team {}", team.get().id(), throwable); + session.close(); + }); + + subscriptions.put(session.getId(), subscription); + } + + @io.micronaut.websocket.annotation.OnMessage + public void onMessage(String message, WebSocketSession session) { + // Not used + } + + @OnClose + public void onClose(WebSocketSession session) { + log.info("Closing log websocket for session {}", session.getId()); + Disposable subscription = subscriptions.remove(session.getId()); + if (subscription != null && !subscription.isDisposed()) { + subscription.dispose(); + } + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/controller/ServerController.java b/backend/src/main/java/de/chojo/pluginjam/controller/ServerController.java new file mode 100644 index 00000000..d30952d5 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/controller/ServerController.java @@ -0,0 +1,98 @@ +package de.chojo.pluginjam.controller; + +import de.chojo.pluginjam.model.PowerSignalDTO; +import de.chojo.pluginjam.model.ServerStatus; +import de.chojo.pluginjam.service.ServerService; +import de.chojo.pluginjam.service.TeamService; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.annotation.Body; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; +import io.micronaut.http.annotation.Post; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.authentication.Authentication; +import io.micronaut.security.rules.SecurityRule; + +@Controller("api/server") +@Secured(SecurityRule.IS_AUTHENTICATED) +public class ServerController { + private final ServerService serverService; + private final TeamService teamService; + + public ServerController(ServerService serverService, TeamService teamService) { + this.serverService = serverService; + this.teamService = teamService; + } + + @Get("/status/") + public HttpResponse getStatus(Authentication authentication) { + var team = teamService.getUserTeam(Long.parseLong(authentication.getName())); + if (team.isEmpty()) { + return HttpResponse.notAllowed(); + } + + return HttpResponse.ok(serverService.dockerService().serverStatus(team.get().id())); + } + + @Get("/exists") + public HttpResponse> exists(Authentication authentication) { + var team = teamService.getUserTeam(Long.parseLong(authentication.getName())); + if (team.isEmpty()) { + return HttpResponse.notAllowed(); + } + + boolean exists = serverService.dockerService().exists(team.get().id()); + boolean provisioning = serverService.isProvisioning(team.get().id()); + + return HttpResponse.ok(java.util.Map.of( + "exists", exists, + "provisioning", provisioning + )); + } + + @Post("/provision") + public HttpResponse provision(Authentication authentication) { + var team = teamService.getUserTeam(Long.parseLong(authentication.getName())); + if (team.isEmpty()) { + return HttpResponse.notAllowed(); + } + serverService.provisionServer(team.get().id()); + return HttpResponse.accepted(); + } + + @Post("/reinstall") + public HttpResponse reinstall(Authentication authentication) { + var team = teamService.getUserTeam(Long.parseLong(authentication.getName())); + if (team.isEmpty()) { + return HttpResponse.notAllowed(); + } + serverService.reinstallServer(team.get().id()); + return HttpResponse.accepted(); + } + + @Post("/power") + public HttpResponse powerSignal(Authentication authentication, @Body PowerSignalDTO powerSignal) { + var team = teamService.getUserTeam(Long.parseLong(authentication.getName())); + if (team.isEmpty()) { + return HttpResponse.notAllowed(); + } + System.out.println("[DEBUG_LOG] Received power signal: " + powerSignal.signal() + " for team: " + team.get().id()); + serverService.handlePowerSignal(powerSignal, team.get().id()); + return HttpResponse.ok(); + } + + @Post("/command") + public HttpResponse sendCommand(Authentication authentication, @Body java.util.Map body) { + var team = teamService.getUserTeam(Long.parseLong(authentication.getName())); + if (team.isEmpty()) { + return HttpResponse.notAllowed(); + } + String command = body.get("command"); + if (command == null || command.isBlank()) { + return HttpResponse.badRequest(); + } + serverService.dockerService().sendCommand(team.get().id(), command); + return HttpResponse.ok(); + } + +} diff --git a/backend/src/main/java/de/chojo/pluginjam/controller/TeamController.java b/backend/src/main/java/de/chojo/pluginjam/controller/TeamController.java new file mode 100644 index 00000000..2e5f30ea --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/controller/TeamController.java @@ -0,0 +1,28 @@ +package de.chojo.pluginjam.controller; + +import de.chojo.pluginjam.database.entity.team.Team; +import de.chojo.pluginjam.service.TeamService; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.authentication.Authentication; +import io.micronaut.security.rules.SecurityRule; + +import java.util.Optional; + +@Controller("api/teams") +@Secured(SecurityRule.IS_AUTHENTICATED) +public class TeamController { + private final TeamService teamService; + + public TeamController(TeamService teamService) { + this.teamService = teamService; + } + + @Get("/my-team") + public HttpResponse getMyTeam(Authentication authentication) { + Optional team = teamService.getUserTeam(Long.parseLong(authentication.getName())); + return team.map(HttpResponse::ok).orElseGet(HttpResponse::notFound); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/controller/UserController.java b/backend/src/main/java/de/chojo/pluginjam/controller/UserController.java new file mode 100644 index 00000000..a7dda38e --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/controller/UserController.java @@ -0,0 +1,23 @@ +package de.chojo.pluginjam.controller; + +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.authentication.Authentication; +import io.micronaut.security.rules.SecurityRule; + +import java.util.Map; + +@Controller("/api/user") +public class UserController { + + @Get("/me") + @Secured(SecurityRule.IS_AUTHENTICATED) + public Map getCurrentUser(Authentication authentication) { + return Map.of( + "name", authentication.getAttributes().getOrDefault("name", "Unknown"), + "picture", authentication.getAttributes().getOrDefault("picture", ""), + "id", authentication.getName() + ); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/entity/SettingsEntity.java b/backend/src/main/java/de/chojo/pluginjam/database/entity/SettingsEntity.java new file mode 100644 index 00000000..1b45720b --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/entity/SettingsEntity.java @@ -0,0 +1,85 @@ +package de.chojo.pluginjam.database.entity; + +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; +import io.micronaut.data.annotation.MappedProperty; +import io.micronaut.serde.annotation.Serdeable; + +@Serdeable +@MappedEntity(value = "settings", schema = "gamejam") +public final class SettingsEntity { + @Id + @MappedProperty("guild_id") + private Long guildId; + @MappedProperty("manager_role") + private Long orgaRole; + @MappedProperty("participant_role") + private Long participantRole; + @MappedProperty("locale") + private String locale; + @MappedProperty("team_size") + private Integer teamSize; + + public SettingsEntity( + @Id + Long guildId, + Long orgaRole, + Long participantRole, + String locale, + Integer teamSize + ) { + this.guildId = guildId; + this.orgaRole = orgaRole; + this.participantRole = participantRole; + this.locale = locale; + this.teamSize = teamSize; + } + + public SettingsEntity(long guildId) { + this.guildId = guildId; + this.orgaRole = 0L; + this.participantRole = 0L; + this.locale = "en"; + this.teamSize = 3; + } + + public Long getGuildId() { + return guildId; + } + + public Long getOrgaRole() { + return orgaRole; + } + + public Long getParticipantRole() { + return participantRole; + } + + public String getLocale() { + return locale; + } + + public Integer getTeamSize() { + return teamSize; + } + + public void setGuildId(Long guildId) { + this.guildId = guildId; + } + + public void setOrgaRole(Long orgaRole) { + this.orgaRole = orgaRole; + } + + public void setParticipantRole(Long participantRole) { + this.participantRole = participantRole; + } + + public void setLocale(String locale) { + this.locale = locale; + } + + public void setTeamSize(Integer teamSize) { + this.teamSize = teamSize; + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/entity/VoteEntity.java b/backend/src/main/java/de/chojo/pluginjam/database/entity/VoteEntity.java new file mode 100644 index 00000000..c61646c6 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/entity/VoteEntity.java @@ -0,0 +1,18 @@ +package de.chojo.pluginjam.database.entity; + +import io.micronaut.data.annotation.GeneratedValue; +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; +import io.micronaut.data.annotation.MappedProperty; +import io.micronaut.serde.annotation.Serdeable; + +@Serdeable +@MappedEntity(value = "vote", schema = "gamejam") +public record VoteEntity( + @MappedProperty("team_id") + Long teamId, + @MappedProperty("voter_id") + Long voterId, + Integer points +) { +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/entity/VotingRank.java b/backend/src/main/java/de/chojo/pluginjam/database/entity/VotingRank.java new file mode 100644 index 00000000..91501efa --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/entity/VotingRank.java @@ -0,0 +1,24 @@ +package de.chojo.pluginjam.database.entity; + +import de.chojo.pluginjam.database.entity.team.Team; +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; +import io.micronaut.data.annotation.MappedProperty; +import io.micronaut.data.annotation.Relation; + +import java.util.List; + +@MappedEntity(value = "team_ranking", schema = "gamejam") +public record VotingRank( + @Id + @MappedProperty("team_id") + Long teamId, + Integer rank, + Integer points, + @MappedProperty("jam_id") + Integer jamId, + @Relation(value = Relation.Kind.ONE_TO_ONE, mappedBy = "id") + Team team +) { + +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/entity/jam/Jam.java b/backend/src/main/java/de/chojo/pluginjam/database/entity/jam/Jam.java new file mode 100644 index 00000000..a5a77eb7 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/entity/jam/Jam.java @@ -0,0 +1,25 @@ +package de.chojo.pluginjam.database.entity.jam; + +import io.micronaut.data.annotation.*; +import io.micronaut.serde.annotation.Serdeable; + +import java.util.List; + +@Serdeable +@MappedEntity(value = "jam", schema = "gamejam") +public record Jam( + @Id + @GeneratedValue + Integer id, + @MappedProperty("guild_id") + Long guildId, + @Relation(value = Relation.Kind.ONE_TO_ONE, mappedBy = "jamId") + JamMeta meta, + @Relation(value = Relation.Kind.ONE_TO_ONE, mappedBy = "jamId") + JamTime time, + @Relation(value = Relation.Kind.ONE_TO_ONE, mappedBy = "jamId") + JamState state, + @Relation(value = Relation.Kind.ONE_TO_MANY, mappedBy = "jamId") + List registrations +) { +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/entity/jam/JamMeta.java b/backend/src/main/java/de/chojo/pluginjam/database/entity/jam/JamMeta.java new file mode 100644 index 00000000..f5dd8a2a --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/entity/jam/JamMeta.java @@ -0,0 +1,17 @@ +package de.chojo.pluginjam.database.entity.jam; + +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; +import io.micronaut.data.annotation.MappedProperty; +import io.micronaut.serde.annotation.Serdeable; + +@Serdeable +@MappedEntity(value = "jam_meta", schema = "gamejam") +public record JamMeta( + @Id + @MappedProperty("jam_id") + Integer jamId, + String topic, + String tagline +) { +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/entity/jam/JamRegistration.java b/backend/src/main/java/de/chojo/pluginjam/database/entity/jam/JamRegistration.java new file mode 100644 index 00000000..e1ad09c1 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/entity/jam/JamRegistration.java @@ -0,0 +1,17 @@ +package de.chojo.pluginjam.database.entity.jam; + +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; +import io.micronaut.data.annotation.MappedProperty; +import io.micronaut.serde.annotation.Serdeable; + +@Serdeable +@MappedEntity(value = "jam_registrations", schema = "gamejam") +public record JamRegistration( + @MappedProperty("jam_id") + Integer jamId, + @Id + @MappedProperty("user_id") + Long userId +) { +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/entity/jam/JamState.java b/backend/src/main/java/de/chojo/pluginjam/database/entity/jam/JamState.java new file mode 100644 index 00000000..cf163d11 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/entity/jam/JamState.java @@ -0,0 +1,18 @@ +package de.chojo.pluginjam.database.entity.jam; + +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; +import io.micronaut.data.annotation.MappedProperty; +import io.micronaut.serde.annotation.Serdeable; + +@Serdeable +@MappedEntity(value = "jam_state", schema = "gamejam") +public record JamState( + @Id + @MappedProperty("jam_id") + Integer jamId, + boolean active, + boolean voting, + boolean ended +) { +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/entity/jam/JamTime.java b/backend/src/main/java/de/chojo/pluginjam/database/entity/jam/JamTime.java new file mode 100644 index 00000000..235aa49e --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/entity/jam/JamTime.java @@ -0,0 +1,27 @@ +package de.chojo.pluginjam.database.entity.jam; + +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; +import io.micronaut.data.annotation.MappedProperty; +import io.micronaut.serde.annotation.Serdeable; + +import java.time.LocalDateTime; + +@Serdeable +@MappedEntity(value = "jam_time", schema = "gamejam") +public record JamTime( + @Id + @MappedProperty("jam_id") + Integer jamId, + @MappedProperty("zone_id") + String zoneId, + @MappedProperty("registration_start") + LocalDateTime registrationStart, + @MappedProperty("registration_end") + LocalDateTime registrationEnd, + @MappedProperty("jam_start") + LocalDateTime jamStart, + @MappedProperty("jam_end") + LocalDateTime jamEnd +) { +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/entity/team/Team.java b/backend/src/main/java/de/chojo/pluginjam/database/entity/team/Team.java new file mode 100644 index 00000000..954c049f --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/entity/team/Team.java @@ -0,0 +1,29 @@ +package de.chojo.pluginjam.database.entity.team; + +import io.micronaut.data.annotation.*; +import io.micronaut.serde.annotation.Serdeable; +import net.dv8tion.jda.api.entities.User; + +import java.util.List; + +@Serdeable +@MappedEntity(value = "team", schema = "gamejam") +public record Team( + @Id + @GeneratedValue(GeneratedValue.Type.AUTO) + Integer id, + @MappedProperty("jam_id") + Integer jamId, + @Relation(value = Relation.Kind.ONE_TO_ONE, mappedBy = "teamId") + TeamMeta meta, + @Relation(value = Relation.Kind.ONE_TO_MANY, mappedBy = "teamId") + List members +) { + public boolean isLeader(User user) { + return members.stream().anyMatch(member -> member.userId() == user.getIdLong()); + } + + public boolean isMember(User user) { + return members.stream().anyMatch(member -> member.userId() == user.getIdLong()); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/entity/team/TeamMember.java b/backend/src/main/java/de/chojo/pluginjam/database/entity/team/TeamMember.java new file mode 100644 index 00000000..bee18374 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/entity/team/TeamMember.java @@ -0,0 +1,17 @@ +package de.chojo.pluginjam.database.entity.team; + +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; +import io.micronaut.data.annotation.MappedProperty; +import io.micronaut.serde.annotation.Serdeable; + +@Serdeable +@MappedEntity(value = "team_member", schema = "gamejam") +public record TeamMember( + @MappedProperty("team_id") + Integer teamId, + @Id + @MappedProperty("user_id") + Long userId +) { +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/entity/team/TeamMeta.java b/backend/src/main/java/de/chojo/pluginjam/database/entity/team/TeamMeta.java new file mode 100644 index 00000000..0c50d3fc --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/entity/team/TeamMeta.java @@ -0,0 +1,129 @@ +package de.chojo.pluginjam.database.entity.team; + +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; +import io.micronaut.data.annotation.MappedProperty; +import io.micronaut.serde.annotation.Serdeable; + +@Serdeable +@MappedEntity(value = "team_meta", schema = "gamejam") +public final class TeamMeta { + @Id + @MappedProperty("team_id") + private Integer teamId; + @MappedProperty("team_name") + private String teamName; + @MappedProperty("leader_id") + private Long leaderId; + @MappedProperty("role_id") + private Long roleId; + @MappedProperty("text_channel_id") + private Long textChannelId; + @MappedProperty("voice_channel_id") + private Long voiceChannelId; + @MappedProperty("project_description") + private String projectDescription; + @MappedProperty("project_url") + private String projectUrl; + private String token; + + public TeamMeta( + @Id + Integer teamId, + String teamName, + Long leaderId, + Long roleId, + Long textChannelId, + Long voiceChannelId, + String projectDescription, + String projectUrl, + String token + ) { + this.teamId = teamId; + this.teamName = teamName; + this.leaderId = leaderId; + this.roleId = roleId; + this.textChannelId = textChannelId; + this.voiceChannelId = voiceChannelId; + this.projectDescription = projectDescription; + this.projectUrl = projectUrl; + this.token = token; + } + + @Id + @MappedProperty("team_id") + public Integer getTeamId() { + return teamId; + } + + @MappedProperty("team_name") + public String getTeamName() { + return teamName; + } + + @MappedProperty("leader_id") + public Long getLeaderId() { + return leaderId; + } + + @MappedProperty("role_id") + public Long getRoleId() { + return roleId; + } + + @MappedProperty("text_channel_id") + public Long getTextChannelId() { + return textChannelId; + } + + @MappedProperty("voice_channel_id") + public Long getVoiceChannelId() { + return voiceChannelId; + } + + @MappedProperty("project_description") + public String getProjectDescription() { + return projectDescription; + } + + @MappedProperty("project_url") + public String getProjectUrl() { + return projectUrl; + } + + public String getToken() { + return token; + } + + public void getTeamName(String teamName) { + this.teamName = teamName; + } + + public void getLeaderId(Long leaderId) { + this.leaderId = leaderId; + } + + public void getRoleId(Long roleId) { + this.roleId = roleId; + } + + public void getTextChannelId(Long textChannelId) { + this.textChannelId = textChannelId; + } + + public void getVoiceChannelId(Long voiceChannelId) { + this.voiceChannelId = voiceChannelId; + } + + public void getProjectDescription(String projectDescription) { + this.projectDescription = projectDescription; + } + + public void getProjectUrl(String projectUrl) { + this.projectUrl = projectUrl; + } + + public void token(String token) { + this.token = token; + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/repository/SettingsRepository.java b/backend/src/main/java/de/chojo/pluginjam/database/repository/SettingsRepository.java new file mode 100644 index 00000000..64e14b5c --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/repository/SettingsRepository.java @@ -0,0 +1,22 @@ +package de.chojo.pluginjam.database.repository; + +import de.chojo.pluginjam.database.entity.SettingsEntity; +import io.micronaut.data.annotation.Query; +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.repository.GenericRepository; +import org.jspecify.annotations.NonNull; + +import java.util.Optional; + +@JdbcRepository(dialect = Dialect.POSTGRES) +public interface SettingsRepository extends GenericRepository { + + @Query("INSERT INTO gamejam.settings (guild_id, participant_role, manager_role, locale, team_size) " + + "VALUES (:guildId, :participantRole, :managerRole, :locale, :teamSize) " + + "ON CONFLICT (guild_id) DO UPDATE SET participant_role = :participantRole, manager_role = :managerRole, locale = :locale, team_size = :teamSize") + void save(@NonNull Long guildId, @NonNull Long participantRole, @NonNull Long managerRole, @NonNull String locale, @NonNull Integer teamSize); + + @Query("SELECT guild_id, manager_role, participant_role, team_size, locale FROM gamejam.settings WHERE guild_id = :guildId") + Optional findById(long guildId); +} \ No newline at end of file diff --git a/backend/src/main/java/de/chojo/pluginjam/database/repository/TeamMemberRepository.java b/backend/src/main/java/de/chojo/pluginjam/database/repository/TeamMemberRepository.java new file mode 100644 index 00000000..b49f53b6 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/repository/TeamMemberRepository.java @@ -0,0 +1,13 @@ +package de.chojo.pluginjam.database.repository; + +import de.chojo.pluginjam.database.entity.team.TeamMember; +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.repository.CrudRepository; + +@JdbcRepository(dialect = Dialect.POSTGRES) +public interface TeamMemberRepository extends CrudRepository { + + void deleteByTeamIdAndUserId(Integer teamId, Long userId); + void deleteAllByTeamId(Integer teamId); +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/repository/TeamMetaRepository.java b/backend/src/main/java/de/chojo/pluginjam/database/repository/TeamMetaRepository.java new file mode 100644 index 00000000..a7bff9ab --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/repository/TeamMetaRepository.java @@ -0,0 +1,10 @@ +package de.chojo.pluginjam.database.repository; + +import de.chojo.pluginjam.database.entity.team.TeamMeta; +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.repository.CrudRepository; + +@JdbcRepository(dialect = Dialect.POSTGRES) +public interface TeamMetaRepository extends CrudRepository { +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/repository/TeamRepository.java b/backend/src/main/java/de/chojo/pluginjam/database/repository/TeamRepository.java new file mode 100644 index 00000000..cedf24c4 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/repository/TeamRepository.java @@ -0,0 +1,20 @@ +package de.chojo.pluginjam.database.repository; + +import de.chojo.pluginjam.database.entity.team.Team; +import io.micronaut.data.annotation.Join; +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.repository.CrudRepository; + +import java.util.List; +import java.util.Optional; + +@JdbcRepository(dialect = Dialect.POSTGRES) +@Join(value = "meta", type = Join.Type.LEFT_FETCH) +@Join(value = "members", type = Join.Type.LEFT_FETCH) +public interface TeamRepository extends CrudRepository { + + List findByJamId(Integer jamId); + + Optional findByMembersUserId(Long userId); +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/repository/VotesRankingRepository.java b/backend/src/main/java/de/chojo/pluginjam/database/repository/VotesRankingRepository.java new file mode 100644 index 00000000..2d972fdd --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/repository/VotesRankingRepository.java @@ -0,0 +1,17 @@ +package de.chojo.pluginjam.database.repository; + +import de.chojo.pluginjam.database.entity.VotingRank; +import io.micronaut.data.annotation.Join; +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.repository.CrudRepository; + +import java.util.List; + +@JdbcRepository(dialect = Dialect.POSTGRES) +public interface VotesRankingRepository extends CrudRepository { + + @Join(value = "team", type = Join.Type.LEFT_FETCH) + @Join(value = "team.meta", type = Join.Type.LEFT_FETCH) + List findAllByJamId(Integer jamId); +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/repository/VotesRepository.java b/backend/src/main/java/de/chojo/pluginjam/database/repository/VotesRepository.java new file mode 100644 index 00000000..809c8000 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/repository/VotesRepository.java @@ -0,0 +1,26 @@ +package de.chojo.pluginjam.database.repository; + +import de.chojo.pluginjam.database.entity.VoteEntity; +import de.chojo.pluginjam.database.entity.VotingRank; +import io.micronaut.data.annotation.Query; +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.repository.GenericRepository; + +import java.util.List; + +@JdbcRepository(dialect = Dialect.POSTGRES) +public interface VotesRepository extends GenericRepository { + + @Query("SELECT sum(points) FROM gamejam.votes WHERE team_id = :teamId") + int findByTeamId(int teamId); + + @Query("SELECT sum(points) FROM gamejam.votes WHERE voter_id = :voterId") + int findByVoterId(Long voterId); + + @Query("INSERT INTO gamejam.votes (team_id, voter_id, points) VALUES (:teamId, :voterId, :points) ON CONFLICT (team_id, voter_id) DO UPDATE SET points = :points") + void save(int teamId, Long voterId, Integer points); + + @Query("DELETE FROM gamejam.votes WHERE team_id = :teamId AND voter_id = :voterId") + void delete(int teamId, Long voterId); +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/repository/jam/JamRegistrationRepository.java b/backend/src/main/java/de/chojo/pluginjam/database/repository/jam/JamRegistrationRepository.java new file mode 100644 index 00000000..f48c35b2 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/repository/jam/JamRegistrationRepository.java @@ -0,0 +1,22 @@ +package de.chojo.pluginjam.database.repository.jam; + +import de.chojo.pluginjam.database.entity.jam.JamRegistration; +import io.micronaut.data.annotation.Query; +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.repository.GenericRepository; + +import java.util.List; + +@JdbcRepository(dialect = Dialect.POSTGRES) +public interface JamRegistrationRepository extends GenericRepository { + + @Query("SELECT * FROM gamejam.jam_registrations WHERE jam_id = :jamId") + List findByJamId(Integer jamId); + + @Query("INSERT INTO gamejam.jam_registrations (jam_id, user_id) VALUES (:jamId, :userId) ON CONFLICT DO NOTHING") + void save(Integer jamId, Long userId); + + @Query("DELETE FROM gamejam.jam_registrations WHERE jam_id = :jamId AND user_id = :userId") + void delete(Integer jamId, Long userId); +} diff --git a/backend/src/main/java/de/chojo/pluginjam/database/repository/jam/JamRepository.java b/backend/src/main/java/de/chojo/pluginjam/database/repository/jam/JamRepository.java new file mode 100644 index 00000000..a5795433 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/database/repository/jam/JamRepository.java @@ -0,0 +1,76 @@ +package de.chojo.pluginjam.database.repository.jam; + +import de.chojo.pluginjam.database.entity.jam.Jam; +import io.micronaut.data.annotation.Join; +import io.micronaut.data.annotation.Query; +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.repository.CrudRepository; +import org.jspecify.annotations.NonNull; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +@JdbcRepository(dialect = Dialect.POSTGRES) +public interface JamRepository extends CrudRepository { + + @Join(value = "meta", type = Join.Type.LEFT_FETCH) + @Join(value = "time", type = Join.Type.LEFT_FETCH) + @Join(value = "state", type = Join.Type.LEFT_FETCH) + @Join(value = "registrations", type = Join.Type.LEFT_FETCH) + @NonNull + Optional findById(@NonNull Integer id); + + @Join(value = "meta", type = Join.Type.LEFT_FETCH) + @Join(value = "time", type = Join.Type.LEFT_FETCH) + @Join(value = "state", type = Join.Type.LEFT_FETCH) + @Join(value = "registrations", type = Join.Type.LEFT_FETCH) + List findByGuildId(Long guildId); + + @Join(value = "meta", type = Join.Type.LEFT_FETCH) + @Join(value = "time", type = Join.Type.LEFT_FETCH) + @Join(value = "state", type = Join.Type.LEFT_FETCH) + @Join(value = "registrations", type = Join.Type.LEFT_FETCH) + Optional findByGuildIdAndStateActive(Long guildId, boolean active); + + @Join(value = "meta", type = Join.Type.LEFT_FETCH) + @Join(value = "time", type = Join.Type.LEFT_FETCH) + @Join(value = "state", type = Join.Type.LEFT_FETCH) + @Join(value = "registrations", type = Join.Type.LEFT_FETCH) + List findByGuildIdAndStateActiveAndStateEnded(Long guildId, boolean active, boolean ended); + + @Query(""" + WITH new_jam AS ( + INSERT INTO gamejam.jam (guild_id) VALUES (:guildId) RETURNING id + ), + ins_meta AS ( + INSERT INTO gamejam.jam_meta (jam_id, topic, tagline) SELECT id, :topic ,:tagline FROM new_jam + ), + ins_time AS ( + INSERT INTO gamejam.jam_time (jam_id, zone_id, registration_start, registration_end, jam_start, jam_end) + SELECT id, :zoneId, :registrationStart, :registrationEnd, :jamStart, :jamEnd FROM new_jam + ), + ins_state AS ( + INSERT INTO gamejam.jam_state (jam_id, active, voting, ended) + SELECT id, false, false, false FROM new_jam + ) + SELECT id FROM new_jam + """) + Integer createJam(Long guildId, String topic, String tagline, String zoneId, + LocalDateTime registrationStart, LocalDateTime registrationEnd, + LocalDateTime jamStart, LocalDateTime jamEnd); + + @Query("UPDATE gamejam.jam_state SET active = :active, voting = :voting, ended = :ended WHERE jam_id = :jamId") + void updateState(Integer jamId, boolean active, boolean voting, boolean ended); + + @Query("UPDATE gamejam.jam_state SET voting = :voting WHERE jam_id = :jamId") + void updateVoting(Integer jamId, boolean voting); + + @Query(""" + UPDATE gamejam.jam_state SET active = false, voting = false, ended = true + WHERE jam_id IN (SELECT id FROM gamejam.jam WHERE guild_id = :guildId) + AND active = true + """) + void endActiveJams(Long guildId); +} diff --git a/backend/src/main/java/de/chojo/pluginjam/model/PowerSignalDTO.java b/backend/src/main/java/de/chojo/pluginjam/model/PowerSignalDTO.java new file mode 100644 index 00000000..c775854a --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/model/PowerSignalDTO.java @@ -0,0 +1,20 @@ +package de.chojo.pluginjam.model; + +import io.micronaut.serde.annotation.Serdeable; + +@Serdeable +public class PowerSignalDTO { + public enum Signal { + START, STOP, RESTART + } + + private Signal signal; + + public Signal signal() { + return signal; + } + + public void setSignal(Signal signal) { + this.signal = signal; + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/model/Server.java b/backend/src/main/java/de/chojo/pluginjam/model/Server.java new file mode 100644 index 00000000..42859629 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/model/Server.java @@ -0,0 +1,8 @@ +package de.chojo.pluginjam.model; + +public class Server { + private int id; + private String containerName; + private ServerStatus status; + +} diff --git a/backend/src/main/java/de/chojo/pluginjam/model/ServerStatus.java b/backend/src/main/java/de/chojo/pluginjam/model/ServerStatus.java new file mode 100644 index 00000000..638d21f1 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/model/ServerStatus.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.model; + +public enum ServerStatus { + STARTING_STOPPING("⏳"), + RUNNING("🟢"), + STOPPED("🔴"), + VOID("❌"); + + private final String emoji; + ServerStatus(String emoji) { + this.emoji = emoji; + } + + public String emoji() { + return emoji; + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/security/DiscordUserDetailsMapper.java b/backend/src/main/java/de/chojo/pluginjam/security/DiscordUserDetailsMapper.java new file mode 100644 index 00000000..46d3ffe0 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/security/DiscordUserDetailsMapper.java @@ -0,0 +1,51 @@ +package de.chojo.pluginjam.security; + +import io.micronaut.http.HttpRequest; +import io.micronaut.http.MutableHttpRequest; +import io.micronaut.http.client.HttpClient; +import io.micronaut.http.client.annotation.Client; +import io.micronaut.security.authentication.AuthenticationResponse; +import io.micronaut.security.oauth2.endpoint.authorization.state.State; +import io.micronaut.security.oauth2.endpoint.token.response.OauthAuthenticationMapper; +import io.micronaut.security.oauth2.endpoint.token.response.TokenResponse; +import jakarta.inject.Named; +import jakarta.inject.Singleton; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Mono; + +import java.util.List; +import java.util.Map; + +@Named("discord") +@Singleton +public class DiscordUserDetailsMapper implements OauthAuthenticationMapper { + + private final HttpClient httpClient; + + public DiscordUserDetailsMapper(@Client("https://discord.com/api") HttpClient httpClient) { + this.httpClient = httpClient; + } + + @Override + public Publisher createAuthenticationResponse(TokenResponse tokenResponse, State state) { + MutableHttpRequest request = HttpRequest.GET("/users/@me") + .bearerAuth(tokenResponse.getAccessToken()); + + return Mono.from(httpClient.retrieve(request, Map.class)) + .map(userMap -> { + String username = (String) userMap.get("username"); + String globalName = (String) userMap.get("global_name"); + String id = (String) userMap.get("id"); + String avatar = (String) userMap.get("avatar"); + String avatarUrl = avatar == null + ? "https://cdn.discordapp.com/embed/avatars/" + (Long.parseLong(id) >> 22) % 6 + ".png" + : "https://cdn.discordapp.com/avatars/" + id + "/" + avatar + ".png"; + + return AuthenticationResponse.success(id, + List.of("ROLE_USER"), + Map.of("username", username, + "name", globalName != null ? globalName : username, + "picture", avatarUrl)); + }); + } +} \ No newline at end of file diff --git a/backend/src/main/java/de/chojo/pluginjam/service/DockerService.java b/backend/src/main/java/de/chojo/pluginjam/service/DockerService.java new file mode 100644 index 00000000..cc2ce543 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/service/DockerService.java @@ -0,0 +1,398 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.service; + +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.async.ResultCallback; +import com.github.dockerjava.api.command.InspectContainerResponse; +import com.github.dockerjava.api.exception.NotFoundException; +import com.github.dockerjava.api.model.*; +import com.github.dockerjava.core.DefaultDockerClientConfig; +import com.github.dockerjava.core.DockerClientConfig; +import com.github.dockerjava.core.DockerClientImpl; +import com.github.dockerjava.httpclient5.ApacheDockerHttpClient; +import com.github.dockerjava.transport.DockerHttpClient; +import de.chojo.pluginjam.bot.config.DockerConfig; +import de.chojo.pluginjam.bot.config.PluginsConfig; +import de.chojo.pluginjam.model.ServerStatus; +import io.micronaut.http.sse.Event; +import jakarta.inject.Singleton; +import org.slf4j.Logger; +import reactor.core.publisher.Flux; +import reactor.core.publisher.FluxSink; + +import java.io.IOException; +import java.nio.file.Path; +import java.time.Duration; +import java.util.List; +import java.util.Optional; + +import static org.slf4j.LoggerFactory.getLogger; + +@Singleton +public class DockerService { + private final DockerConfig dockerConfig; + private final DockerClientConfig dockerClientConfig; + private DockerClient dockerClient; + private static final Logger log = getLogger(DockerService.class); + private static final String DOCKER_VOLUME_DATA_DIR = "/data"; + private final String pluginUrls; + + public DockerService(DockerConfig dockerConfig, PluginsConfig pluginsConfig) { + this.dockerConfig = dockerConfig; + this.pluginUrls = pluginsConfig == null ? "" : String.join(",", pluginsConfig.getDefaultPlugins()); + if (dockerConfig != null) { + this.dockerClientConfig = DefaultDockerClientConfig + .createDefaultConfigBuilder() + .withDockerHost(dockerConfig.getHost()) + .withDockerCertPath(dockerConfig.getCertPath()) + .withDockerTlsVerify(dockerConfig.isTlsVerify()) + .withRegistryUsername(dockerConfig.getRegistryUsername()) + .withRegistryPassword(dockerConfig.getRegistryPassword()) + .withRegistryEmail(dockerConfig.getRegistryEmail()) + .withRegistryUrl(dockerConfig.getRegistryUrl()) + .build(); + + initDockerClient(); + } else { + this.dockerClientConfig = null; + } + } + + public void initDockerClient() { + DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder() + .dockerHost(dockerClientConfig.getDockerHost()) + .sslConfig(dockerClientConfig.getSSLConfig()) + .maxConnections(100) + .connectionTimeout(Duration.ofSeconds(30)) + .responseTimeout(Duration.ofSeconds(45)) + .build(); + + dockerClient = DockerClientImpl.getInstance(dockerClientConfig, httpClient); + ensureNetwork(); + } + + private void ensureNetwork() { + var networks = dockerClient.listNetworksCmd() + .withNameFilter(dockerConfig.getNetworkName()) + .exec(); + if (networks.stream().noneMatch(n -> n.getName().equals(dockerConfig.getNetworkName()))) { + dockerClient.createNetworkCmd() + .withName(dockerConfig.getNetworkName()) + .withDriver("bridge") + .exec(); + log.info("Created docker network {}", dockerConfig.getNetworkName()); + } + } + + public void shutdown() throws IOException { + dockerClient.close(); + } + + public void provisionServer(int teamId) { + log.info("Provisioning server for team {}", teamId); + dockerClient.createVolumeCmd() + .withName(volumeName(teamId)) + .exec(); + + HostConfig hostConfig = HostConfig.newHostConfig() + .withBinds(new Bind(volumeName(teamId), new Volume(DOCKER_VOLUME_DATA_DIR))); + + hostConfig.withNetworkMode(dockerConfig.getNetworkName()); + + dockerClient.createContainerCmd(dockerConfig.getTeamServerImage()) + .withName(containerName(teamId)) + .withEnv("EULA=TRUE", "TYPE=PAPER", "VERSION=26.1.2", "CREATE_CONSOLE_IN_PIPE=true", String.format("PLUGINS=%s", pluginUrls)) + .withHostConfig(hostConfig) + .exec(); + log.info("Server provisioned for team with container name {} and volume name {}", containerName(teamId), volumeName(teamId)); + } + + public void destroyServer(int teamId) { + log.info("Destroying server for team {}", teamId); + dockerClient.removeContainerCmd(containerName(teamId)).exec(); + dockerClient.removeVolumeCmd(volumeName(teamId)).exec(); + } + + public void startServer(int teamId) { + log.info("Starting server for team {}", teamId); + try { + ensureNetworkConnection(teamId); + dockerClient.startContainerCmd(containerName(teamId)).exec(); + } catch (Exception e) { + log.error("Failed to start server for team {}", teamId, e); + } + } + + private void ensureNetworkConnection(int teamId) { + var container = container(teamId); + if (container.isEmpty()) return; + + var networkName = dockerConfig.getNetworkName(); + var networkSettings = container.get().getNetworkSettings(); + if (networkSettings != null && networkSettings.getNetworks() != null) { + if (networkSettings.getNetworks().containsKey(networkName)) { + // Already connected to the network name, but maybe it's the wrong ID? + // Docker Java API doesn't easily show the network ID in the list of containers' network settings + // without inspecting the container. + try { + var inspect = dockerClient.inspectContainerCmd(container.get().getId()).exec(); + var network = inspect.getNetworkSettings().getNetworks().get(networkName); + if (network != null) { + var networks = dockerClient.listNetworksCmd().withNameFilter(networkName).exec(); + var networkExists = networks.stream().anyMatch(n -> n.getId().equals(network.getNetworkID())); + if (networkExists) { + return; // Network is fine + } + log.info("Network {} exists but ID mismatch for team {}. Reconnecting.", networkName, teamId); + } + } catch (Exception e) { + log.warn("Failed to inspect container {} for network validation", teamId, e); + } + } + } + + log.info("Ensuring network connection for team {} to {}", teamId, networkName); + try { + // Try to disconnect first just in case it's partially connected to a dead network + try { + dockerClient.disconnectFromNetworkCmd() + .withContainerId(container.get().getId()) + .withNetworkId(networkName) + .withForce(true) + .exec(); + } catch (Exception ignored) { + } + + dockerClient.connectToNetworkCmd() + .withContainerId(container.get().getId()) + .withNetworkId(networkName) + .exec(); + } catch (Exception e) { + log.error("Failed to connect container {} to network {}", teamId, networkName, e); + } + } + + public void stopServer(int teamId) { + log.info("Stopping server for team {}", teamId); + try { + dockerClient.stopContainerCmd(containerName(teamId)).exec(); + } catch (Exception e) { + log.error("Failed to stop server for team {}", teamId, e); + } + } + + public void restartServer(int teamId) { + log.info("Restarting server for team {}", teamId); + try { + ensureNetworkConnection(teamId); + dockerClient.restartContainerCmd(containerName(teamId)).exec(); + } catch (Exception e) { + log.error("Failed to restart server for team {}", teamId, e); + } + } + + public boolean isRunning(int teamId) { + return dockerClient.listContainersCmd() + .withShowAll(true) + .withNameFilter(List.of(containerName(teamId))) + .exec() + .stream() + .anyMatch(container -> container.getState().equals("running")); + } + + public void sendCommand(int teamId, String command) { + var container = container(teamId); + if (container.isEmpty()) { + log.error("Container not found for team {}", teamId); + return; + } + var execId = dockerClient.execCreateCmd(container.get().getId()) + .withAttachStdout(true) + .withAttachStderr(true) + .withUser("1000") + .withCmd("mc-send-to-console", command) + .exec() + .getId(); + try { + dockerClient.execStartCmd(execId) + .exec(new ResultCallback.Adapter() { + @Override + public void onNext(Frame frame) { + log.info("console response for team {}: {}", teamId, new String(frame.getPayload())); + } + }) + .awaitCompletion(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error("Interrupted while sending command to team {}", teamId, e); + } + } + public boolean exists(int teamId) { + return dockerClient.listContainersCmd() + .withShowAll(true) + .withNameFilter(List.of(containerName(teamId))) + .exec() + .stream() + .findAny() + .isPresent(); + } + + public Optional container(int teamId) { + return dockerClient.listContainersCmd() + .withShowAll(true) + .withNameFilter(List.of(containerName(teamId))) + .exec() + .stream() + .findFirst(); + } + + public String logs(int teamId) { + var callback = new ResultCallback.Adapter() { + private final StringBuilder logs = new StringBuilder(); + + @Override + public void onNext(Frame frame) { + logs.append(new String(frame.getPayload())); + } + + public String getLogs() { + return logs.toString(); + } + }; + + try { + dockerClient.logContainerCmd(containerName(teamId)) + .withStdOut(true) + .withStdErr(true) + .exec(callback) + .awaitCompletion(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error("Interrupted while retrieving logs for team {}", teamId, e); + } + + return callback.getLogs(); + } + + public Flux> streamLogs(int teamId, int tail) { + return Flux.create(sink -> { + var callback = new ResultCallback.Adapter() { + private String partialLine = ""; + + @Override + public void onNext(Frame frame) { + String payload = new String(frame.getPayload()); + String[] lines = (partialLine + payload).split("\n", -1); + for (int i = 0; i < lines.length - 1; i++) { + sink.next(Event.of(lines[i])); + } + partialLine = lines[lines.length - 1]; + } + + @Override + public void onError(Throwable throwable) { + sink.error(throwable); + } + + @Override + public void onComplete() { + if (!partialLine.isEmpty()) { + sink.next(Event.of(partialLine)); + } + sink.complete(); + } + }; + + var cmd = dockerClient.logContainerCmd(containerName(teamId)) + .withStdOut(true) + .withStdErr(true) + .withFollowStream(true) + .withTail(tail); + + log.info("Starting log stream for container {}", containerName(teamId)); + var closeable = cmd.exec(callback); + + sink.onCancel(() -> { + try { + closeable.close(); + } catch (IOException e) { + log.error("Error closing log stream for team {}", teamId, e); + } + }); + }, FluxSink.OverflowStrategy.BUFFER); + } + + public void copyArchiveToContainer(int teamId, Path source, Path destination) { + dockerClient.copyArchiveToContainerCmd(containerName(teamId)) + .withHostResource(source.toString()) + .withRemotePath(destination.toString()) + .exec(); + } + + public static String getContainerHealth(DockerClient dockerClient, String containerId) { + InspectContainerResponse inspect = dockerClient.inspectContainerCmd(containerId).exec(); + + if (inspect.getState() != null && inspect.getState().getHealth() != null) { + // "starting", "healthy", "unhealthy" + return inspect.getState().getHealth().getStatus(); + } + + // Falls kein Healthcheck aktiv ist, greift der normale Status (running, exited, etc.) + return inspect.getState() != null ? inspect.getState().getStatus() : "unknown"; + } + + public ServerStatus serverStatus(int teamId) { + try { + InspectContainerResponse inspect = dockerClient.inspectContainerCmd(containerName(teamId)).exec(); + InspectContainerResponse.ContainerState state = inspect.getState(); + + if (state == null) { + return ServerStatus.VOID; + } + + if (Boolean.TRUE.equals(state.getRestarting()) || + "paused".equalsIgnoreCase(state.getStatus()) || + "removing".equalsIgnoreCase(state.getStatus())) { + return ServerStatus.STARTING_STOPPING; + } + + if (Boolean.TRUE.equals(state.getRunning())) { + + if (state.getHealth() != null) { + String healthStatus = state.getHealth().getStatus(); // "starting", "healthy", "unhealthy" + + switch (healthStatus) { + case "starting": + return ServerStatus.STARTING_STOPPING; + + case "healthy": + return ServerStatus.RUNNING; + + case "unhealthy": + return ServerStatus.VOID; + } + } + + return ServerStatus.RUNNING; + } + + return ServerStatus.STOPPED; + + } catch (Exception e) { + return ServerStatus.VOID; + } + } + + private String volumeName(int teamId) { + return "plugin-jam-team-" + teamId; + } + + public String containerName(int teamId) { + return "plugin-jam-team-" + teamId; + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/service/JamService.java b/backend/src/main/java/de/chojo/pluginjam/service/JamService.java new file mode 100644 index 00000000..f79696a5 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/service/JamService.java @@ -0,0 +1,93 @@ +package de.chojo.pluginjam.service; + +import de.chojo.pluginjam.database.entity.jam.Jam; +import de.chojo.pluginjam.database.repository.jam.JamRegistrationRepository; +import de.chojo.pluginjam.database.repository.jam.JamRepository; +import de.chojo.pluginjam.database.repository.SettingsRepository; +import jakarta.inject.Singleton; + +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Optional; + +@Singleton +public class JamService { + private final JamRepository jamRepository; + private final JamRegistrationRepository jamRegistrationRepository; + private final SettingsRepository settingsRepository; + + public JamService(JamRepository jamRepository, JamRegistrationRepository jamRegistrationRepository, SettingsRepository settingsRepository) { + this.jamRepository = jamRepository; + this.jamRegistrationRepository = jamRegistrationRepository; + this.settingsRepository = settingsRepository; + } + + public void createJam( + long guildId, + String topic, + String tagline, + String zoneId, + ZonedDateTime startDate, + ZonedDateTime endDate, + ZonedDateTime registrationStartDate, + ZonedDateTime registrationEndDate + ) { + jamRepository.createJam( + guildId, + topic, + tagline, + zoneId, + registrationStartDate.toLocalDateTime(), + registrationEndDate.toLocalDateTime(), + startDate.toLocalDateTime(), + endDate.toLocalDateTime() + ); + } + + public boolean isJamActive(long guildId) { + return jamRepository.findByGuildIdAndStateActive(guildId, true).isPresent(); + } + + public Optional getJamById(int jamId) { + return jamRepository.findById(jamId); + } + + public Optional getActiveJam(long guildId) { + return jamRepository.findByGuildIdAndStateActive(guildId, true); + } + + public Optional getUpComingJam(long guildId) { + return jamRepository.findByGuildIdAndStateActiveAndStateEnded(guildId, false, false) + .stream() + .findFirst(); + } + + public void endActiveJams(long guildId) { + jamRepository.endActiveJams(guildId); + } + + public Optional getCurrentOrUpcoming(long guildId) { + return getUpComingJam(guildId) + .or(() -> getActiveJam(guildId)); + } + + public void setVoting(int jamId, boolean voting) { + jamRepository.updateVoting(jamId, voting); + } + + public void startJam(int id) { + jamRepository.updateState(id, true, false, false); + } + + public List getJams(long guildId) { + return jamRepository.findByGuildId(guildId); + } + + public void registerUser(int jamId, long userId) { + jamRegistrationRepository.save(jamId, userId); + } + + public void unregisterUser(int jamId, long userId) { + jamRegistrationRepository.delete(jamId, userId); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/service/ServerService.java b/backend/src/main/java/de/chojo/pluginjam/service/ServerService.java new file mode 100644 index 00000000..c9670e40 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/service/ServerService.java @@ -0,0 +1,94 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.pluginjam.service; + +import de.chojo.pluginjam.model.PowerSignalDTO; +import jakarta.inject.Singleton; +import org.slf4j.Logger; + +import java.util.concurrent.CompletableFuture; + +import static org.slf4j.LoggerFactory.getLogger; + +@Singleton +public class ServerService { + private static final Logger log = getLogger(ServerService.class); + private final DockerService dockerService; + private final java.util.Set provisioningTeams = java.util.concurrent.ConcurrentHashMap.newKeySet(); + + public ServerService(DockerService dockerService) { + this.dockerService = dockerService; + } + + public void provisionServer(int teamId) { + if (provisioningTeams.contains(teamId) || dockerService.exists(teamId)) { + return; + } + provisioningTeams.add(teamId); + CompletableFuture.runAsync(() -> { + try { + dockerService.provisionServer(teamId); + } finally { + provisioningTeams.remove(teamId); + } + }); + } + + public void reinstallServer(int teamId) { + log.info("Reinstalling server for team {}", teamId); + if (provisioningTeams.contains(teamId)) { + return; + } + provisioningTeams.add(teamId); + CompletableFuture.runAsync(() -> { + try { + dockerService.stopServer(teamId); + dockerService.destroyServer(teamId); + dockerService.provisionServer(teamId); + } catch (Exception e) { + log.error("Failed to reinstall server for team {}", teamId, e); + } finally { + provisioningTeams.remove(teamId); + } + }); + } + + public boolean isProvisioning(int teamId) { + return provisioningTeams.contains(teamId); + } + + public void startServer(int id) { + dockerService.startServer(id); + //TODO: register to velocity + } + + public void stopServer(int id) { + //TODO: unregister from velocity + dockerService.stopServer(id); + } + + public void restartServer(int id) { + dockerService.restartServer(id); + } + + public void handlePowerSignal(PowerSignalDTO signal, int teamId) { + log.info("Handling power signal {} for team {}", signal.signal(), teamId); + if (signal.signal() == PowerSignalDTO.Signal.START) { + startServer(teamId); + } else if (signal.signal() == PowerSignalDTO.Signal.STOP) { + stopServer(teamId); + } else if (signal.signal() == PowerSignalDTO.Signal.RESTART) { + restartServer(teamId); + } else { + log.warn("Received unknown power signal: {}", signal.signal()); + } + } + + public DockerService dockerService() { + return dockerService; + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/service/SettingsService.java b/backend/src/main/java/de/chojo/pluginjam/service/SettingsService.java new file mode 100644 index 00000000..35c0b2d3 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/service/SettingsService.java @@ -0,0 +1,40 @@ +package de.chojo.pluginjam.service; + +import de.chojo.pluginjam.database.entity.SettingsEntity; +import de.chojo.pluginjam.database.repository.SettingsRepository; +import jakarta.inject.Singleton; + +@Singleton +public class SettingsService { + private final SettingsRepository settingsRepository; + + public SettingsService(SettingsRepository settingsRepository) { + this.settingsRepository = settingsRepository; + } + + public SettingsEntity getSettings(long guildId) { + return settingsRepository.findById(guildId).orElseGet(() -> new SettingsEntity(guildId)); + } + + public void saveSettings(SettingsEntity settings) { + settingsRepository.save(settings.getGuildId(), settings.getParticipantRole(), settings.getOrgaRole(), settings.getLocale(), settings.getTeamSize()); + } + + public void setJamRole(long guildId, long roleId) { + var settings = settingsRepository.findById(guildId).orElseGet(() -> new SettingsEntity(guildId)); + settings.setParticipantRole(roleId); + settingsRepository.save(settings.getGuildId(), settings.getParticipantRole(), settings.getOrgaRole(), settings.getLocale(), settings.getTeamSize()); + } + + public void setOrgaRole(long guildId, long roleId) { + var settings = settingsRepository.findById(guildId).orElseGet(() -> new SettingsEntity(guildId)); + settings.setOrgaRole(roleId); + settingsRepository.save(settings.getGuildId(), settings.getParticipantRole(), settings.getOrgaRole(), settings.getLocale(), settings.getTeamSize()); + } + + public void setTeamSize(long guildId, int teamSize) { + var settings = settingsRepository.findById(guildId).orElseGet(() -> new SettingsEntity(guildId)); + settings.setTeamSize(teamSize); + settingsRepository.save(settings.getGuildId(), settings.getParticipantRole(), settings.getOrgaRole(), settings.getLocale(), settings.getTeamSize()); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/service/TeamService.java b/backend/src/main/java/de/chojo/pluginjam/service/TeamService.java new file mode 100644 index 00000000..3b5aad61 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/service/TeamService.java @@ -0,0 +1,84 @@ +package de.chojo.pluginjam.service; + +import de.chojo.pluginjam.database.entity.team.Team; +import de.chojo.pluginjam.database.entity.team.TeamMember; +import de.chojo.pluginjam.database.entity.team.TeamMeta; +import de.chojo.pluginjam.database.repository.TeamMemberRepository; +import de.chojo.pluginjam.database.repository.TeamMetaRepository; +import de.chojo.pluginjam.database.repository.TeamRepository; +import io.micronaut.transaction.annotation.Transactional; +import jakarta.inject.Singleton; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; + +import java.util.List; +import java.util.Optional; + +@Singleton +public class TeamService { + private final TeamRepository teamRepository; + private final TeamMemberRepository teamMemberRepository; + private final TeamMetaRepository teamMetaRepository; + + public TeamService(TeamRepository teamRepository, TeamMemberRepository teamMemberRepository, TeamMetaRepository teamMetaRepository) { + this.teamRepository = teamRepository; + this.teamMemberRepository = teamMemberRepository; + this.teamMetaRepository = teamMetaRepository; + } + + public Team createTeam(long jamId, String name, long leaderId, long roleId, long textChannelId, long voiceChannelId) { + var team = teamRepository.save(new Team(null, (int) jamId, null, List.of())); + teamMetaRepository.save(new TeamMeta(team.id(), name, leaderId, roleId, textChannelId, voiceChannelId, "", "", "")); + return teamRepository.findById(team.id()).orElseThrow(); + } + + public Optional getTeam(int id) { + return teamRepository.findById(id); + } + + public Optional getUserTeam(long userId) { + return teamRepository.findByMembersUserId(userId); + } + + public void removeUserFromTeam(long userId) { + getUserTeam(userId).ifPresent(team -> teamMemberRepository.deleteByTeamIdAndUserId(team.id(), userId)); + } + + public Optional getTeamByName(int jamId, String teamName) { + return teamRepository.findByJamId(jamId).stream() + .filter(team -> team.meta() != null && teamName.equals(team.meta().getTeamName())) + .findFirst(); + } + + public void joinTeam(Team team, Member user, Guild guild) { + teamMemberRepository.save(new TeamMember(team.id(), user.getIdLong())); + guild.addRoleToMember(user, guild.getRoleById(team.meta().getRoleId())).queue(); + } + + public void leaveTeam(Team team, Member user, Guild guild) { + teamMemberRepository.deleteByTeamIdAndUserId(team.id(), user.getIdLong()); + guild.removeRoleFromMember(user, guild.getRoleById(team.meta().getRoleId())).queue(); + } + + public List getTeamsByJamId(int jamId) { + return teamRepository.findByJamId(jamId); + } + + @Transactional + public void disbandTeam(Team team, Guild guild) { + teamMemberRepository.deleteAllByTeamId(team.id()); + teamRepository.deleteById(team.id()); + teamMetaRepository.deleteById(team.id()); + + guild.getRoleById(team.meta().getRoleId()).delete().queue(); + guild.getTextChannelById(team.meta().getTextChannelId()).delete().queue(); + guild.getVoiceChannelById(team.meta().getVoiceChannelId()).delete().queue(); + } + + @Transactional + public void saveTeam(Team team) { + teamRepository.save(team); + teamMetaRepository.save(team.meta()); + teamMemberRepository.saveAll(team.members()); + } +} diff --git a/backend/src/main/java/de/chojo/pluginjam/service/VoteService.java b/backend/src/main/java/de/chojo/pluginjam/service/VoteService.java new file mode 100644 index 00000000..bd5a8d62 --- /dev/null +++ b/backend/src/main/java/de/chojo/pluginjam/service/VoteService.java @@ -0,0 +1,42 @@ +package de.chojo.pluginjam.service; + +import de.chojo.pluginjam.database.entity.VotingRank; +import de.chojo.pluginjam.database.entity.team.Team; +import de.chojo.pluginjam.database.repository.VotesRankingRepository; +import de.chojo.pluginjam.database.repository.VotesRepository; +import jakarta.inject.Singleton; +import net.dv8tion.jda.api.entities.User; + +import java.util.List; +import java.util.Optional; + +@Singleton +public class VoteService { + private final VotesRepository votesRepository; + private final VotesRankingRepository votesRankingRepository; + + public VoteService(VotesRepository votesRepository, VotesRankingRepository votesRankingRepository) { + this.votesRepository = votesRepository; + this.votesRankingRepository = votesRankingRepository; + } + + public int getGivenPointsByUser(User user) { + return votesRepository.findByVoterId(user.getIdLong()); + } + + public int getPointsByTeam(Team team) { + return votesRepository.findByTeamId(team.id()); + } + + public void voteForTeam(User user, Team team, int points) { + votesRepository.save(team.id(), user.getIdLong(), points); + } + + public Optional getVotingRank(int teamId) { + return votesRankingRepository.findById(teamId); + } + + public List getVotingRanks(int jamId) { + return votesRankingRepository.findAllByJamId(jamId); + } +} diff --git a/backend/src/main/resources/application-dev.yml b/backend/src/main/resources/application-dev.yml new file mode 100644 index 00000000..c7e8fe19 --- /dev/null +++ b/backend/src/main/resources/application-dev.yml @@ -0,0 +1,7 @@ +micronaut: + security: + oauth2: + clients: + default: + client-id: ${OAUTH_CLIENT_ID:XXX} + client-secret: ${OAUTH_CLIENT_SECRET:YYY} diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml new file mode 100644 index 00000000..96c959ee --- /dev/null +++ b/backend/src/main/resources/application.yml @@ -0,0 +1,88 @@ +micronaut: + application: + name: backend + security: + enabled: true + authentication: cookie + token: + jwt: + signatures: + secret: + generator: + secret: "${JWT_GENERATOR_SIGNATURE_SECRET:pleaseChangeThisToALongSecretKey1234567890}" + endpoints: + oauth: + enabled: true + logout: + get-allowed: true + enabled: true + redirect: + login-success: ${FRONTEND_URL:http://localhost:3000}/app/console + login-failure: ${FRONTEND_URL:http://localhost:3000}?error=login_failed + unauthorized: + url: ${FRONTEND_URL:http://localhost:3000}?error=unauthorized + forbidden: + url: ${FRONTEND_URL:http://localhost:3000}?error=forbidden + oauth2: + enabled: true + clients: + discord: + client-id: "${OAUTH_CLIENT_ID:deine_client_id}" + client-secret: "${OAUTH_CLIENT_SECRET:dein_client_secret}" + token: + url: "${OAUTH_TOKEN_URL:https://discord.com/api/oauth2/token}" + auth-method: client-secret-post + authorization: + url: "${OAUTH_AUTH_URL:https://discord.com/oauth2/authorize}" + scopes: + - identify + server: + cors: + enabled: true + configurations: + web: + allowed-origins: + - ${FRONTEND_URL:http://localhost:3000} + allowed-headers: + - authorization + - content-type + - x-requested-with + - cookie + - set-cookie + - last-event-id + allowed-methods: + - GET + - POST + - PUT + - DELETE + - OPTIONS + allow-credentials: true + exposed-headers: + - content-type +datasources: + default: + driver-class-name: org.postgresql.Driver + db-type: postgres + dialect: POSTGRES + jdbc-url: jdbc:postgresql://${DB_HOST}:${DB_PORT}/${DB_NAME} + password: ${DB_PASSWORD} + username: ${DB_USERNAME} +flyway: + datasources: + default: + enabled: true + default-schema: gamejam + create-schemas: true + schemas: + - gamejam +bot: + api-key: ${BOT_API_KEY} + token: ${BOT_TOKEN} + +logger: + levels: + de.chojo: TRACE + io.micronaut.data: TRACE + io.micronaut.security: TRACE + io.micronaut.http.server: TRACE + io.micronaut.http.client: TRACE \ No newline at end of file diff --git a/backend/src/main/resources/db/migration/V1__create_initial_tables.sql b/backend/src/main/resources/db/migration/V1__create_initial_tables.sql new file mode 100644 index 00000000..e599a1c7 --- /dev/null +++ b/backend/src/main/resources/db/migration/V1__create_initial_tables.sql @@ -0,0 +1,143 @@ +CREATE SEQUENCE gamejam.jam_times_id_seq + AS INTEGER; + +CREATE TABLE IF NOT EXISTS gamejam.jam +( + id SERIAL, + guild_id BIGINT NOT NULL, + CONSTRAINT jam_pk + PRIMARY KEY (id) +); + +CREATE INDEX IF NOT EXISTS jam_guild_id_index + ON gamejam.jam (guild_id); + +CREATE TABLE IF NOT EXISTS gamejam.jam_time +( + jam_id INTEGER NOT NULL, + registration_start TIMESTAMP NOT NULL, + registration_end TIMESTAMP NOT NULL, + zone_id TEXT NOT NULL, + jam_start TIMESTAMP NOT NULL, + jam_end TIMESTAMP NOT NULL, + CONSTRAINT jam_times_pk + PRIMARY KEY (jam_id), + CONSTRAINT jam_times_jam_id_fk + FOREIGN KEY (jam_id) REFERENCES gamejam.jam + ON DELETE CASCADE +); + +ALTER SEQUENCE gamejam.jam_times_id_seq OWNED BY gamejam.jam_time.jam_id; + +CREATE TABLE IF NOT EXISTS gamejam.jam_meta +( + jam_id INTEGER NOT NULL, + topic TEXT NOT NULL, + tagline TEXT NOT NULL, + CONSTRAINT jam_topic_pk + PRIMARY KEY (jam_id), + CONSTRAINT jam_topic_jam_id_fk + FOREIGN KEY (jam_id) REFERENCES gamejam.jam + ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS gamejam.team +( + id SERIAL, + jam_id INTEGER NOT NULL, + CONSTRAINT team_pk + PRIMARY KEY (id), + CONSTRAINT team_jam_id_fk + FOREIGN KEY (jam_id) REFERENCES gamejam.jam + ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS gamejam.team_member +( + team_id INTEGER NOT NULL, + user_id BIGINT, + CONSTRAINT team_member_team_id_fk + FOREIGN KEY (team_id) REFERENCES gamejam.team + ON DELETE CASCADE +); + +CREATE UNIQUE INDEX IF NOT EXISTS team_member_team_id_user_id_uindex + ON gamejam.team_member (team_id, user_id); + +CREATE INDEX IF NOT EXISTS team_member_team_id_index + ON gamejam.team_member (team_id); + +CREATE TABLE IF NOT EXISTS gamejam.team_meta +( + team_id INTEGER NOT NULL, + team_name TEXT NOT NULL, + leader_id BIGINT DEFAULT 0 NOT NULL, + role_id BIGINT DEFAULT 0 NOT NULL, + text_channel_id BIGINT DEFAULT 0 NOT NULL, + voice_channel_id BIGINT DEFAULT 0 NOT NULL, + project_description TEXT DEFAULT '' NOT NULL, + project_url TEXT DEFAULT '' NOT NULL, + token TEXT, + CONSTRAINT team_meta_pk + PRIMARY KEY (team_id), + CONSTRAINT team_meta_team_id_fk + FOREIGN KEY (team_id) REFERENCES gamejam.team + ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS gamejam.votes +( + team_id BIGINT NOT NULL, + voter_id BIGINT NOT NULL, + points INTEGER DEFAULT 0 NOT NULL, + CONSTRAINT vote_team_id_fk + FOREIGN KEY (team_id) REFERENCES gamejam.team + ON DELETE CASCADE +); + +CREATE UNIQUE INDEX IF NOT EXISTS vote_team_id_voter_id_uindex + ON gamejam.votes (team_id, voter_id); + +CREATE OR REPLACE VIEW gamejam.team_ranking AS +SELECT ROW_NUMBER() OVER (PARTITION BY jam_id ORDER BY points DESC ) as rank, team_id, points, jam_id +FROM (SELECT team_id, SUM(points) AS points + FROM gamejam.votes + GROUP BY team_id) a + LEFT JOIN gamejam.team t ON t.id = a.team_id +ORDER BY points DESC; + +CREATE TABLE IF NOT EXISTS gamejam.jam_registrations +( + jam_id INTEGER NOT NULL, + user_id BIGINT NOT NULL, + CONSTRAINT jam_registrations_jam_id_fk + FOREIGN KEY (jam_id) REFERENCES gamejam.jam + ON DELETE CASCADE +); + +CREATE UNIQUE INDEX IF NOT EXISTS jam_registrations_jam_id_user_id_uindex + ON gamejam.jam_registrations (jam_id, user_id); + +CREATE TABLE IF NOT EXISTS gamejam.jam_state +( + jam_id INTEGER NOT NULL, + active BOOLEAN DEFAULT FALSE NOT NULL, + voting BOOLEAN DEFAULT FALSE NOT NULL, + ended BOOLEAN DEFAULT FALSE NOT NULL, + CONSTRAINT jam_state_pk + PRIMARY KEY (jam_id), + CONSTRAINT jam_state_jam_id_fk + FOREIGN KEY (jam_id) REFERENCES gamejam.jam + ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS gamejam.settings +( + guild_id BIGINT DEFAULT 0 NOT NULL, + manager_role BIGINT DEFAULT 0, + participant_role BIGINT DEFAULT 0 NOT NULL, + team_size INTEGER DEFAULT 4 NOT NULL, + locale TEXT, + CONSTRAINT settings_pk + PRIMARY KEY (guild_id) +); diff --git a/backend/src/main/resources/locale/de.ftl b/backend/src/main/resources/locale/de.ftl new file mode 100644 index 00000000..b86bcc04 --- /dev/null +++ b/backend/src/main/resources/locale/de.ftl @@ -0,0 +1,273 @@ +command-jamadmin-create-description=Neuen Game-Jam erstellen +command-jamadmin-create-options-jamend-description=Game-Jam-Ende. $words.format$: $words.dateformat$ +command-jamadmin-create-options-jamstart-description=Game-Jam-Start. $words.format$: $words.dateformat$ +command-jamadmin-create-message-created=Game-Jam erstellt. +command-jamadmin-create-options-registerend-description=Registrierungen schließen. $words.format$: $words.dateformat$ +command-jamadmin-create-options-registerstart-description=Registrierungen öffnen. $words.format$: $words.dateformat$ +command-jamadmin-create-options-tagline-description=Topic-Tagline als Zusatz zum Thema +command-jamadmin-create-options-timezone-description=Die Zeitzone des Game Jams. Zum Beispiel "Europe/Berlin". +command-jamadmin-create-options-topic-description=Das Thema des Game-Jam +command-jamadmin-description=Verwalte Game-Jams +command-jamadmin-jam-end-options-confirm-description=Bestätige das Ende. +command-jamadmin-jam-description=Verwalte Game-Jams +command-jamadmin-jam-end-description=Beendet den aktuellen Game-Jam. Löscht alle Rollen und Kanäle +command-jamadmin-jam-end-message-ended=Game-Jam beendet. +command-jamadmin-jam-start-description=Startet den nächsten geplanten Game-Jam +command-jamadmin-votes-close-description=Abstimmungen für den aktuellen Game-Jam schließen. +command-jamadmin-votes-description=Verwalte Abstimmungen +command-jamadmin-votes-message-closed=Abstimmungen für den aktuellen Game-Jam geschlossen. +command-jamadmin-votes-message-opened=Abtimmung für den aktuellen Game-Jam geöffnet. +command-jamadmin-votes-open-description=Abtimmung für den aktuellen Game-Jam öffnen. +command-register-description=Du hast dich für einen kommenden Game-Jam angemeldet. +command-register-message-alreadyregistered=Du bist bereits registriert. +command-register-message-notanymore=Registrierungen sind geschlossen. +command-register-message-notyet=Du kannst dich noch nicht für diesen Game-Jam anmelden. Du kannst dich am { $TIMESTAMP } anmelden. +command-register-message-registered=Du hast dich für den nächsten Game-Jam angemeldet. Er beginnt am { $TIMESTAMP }. +command-server-configure-description=Server konfigurieren +command-server-configure-maxplayers-options-amount-description=Maximale Anzahl von Spielern. +command-server-configure-maxplayers-description=Die maximale Spieleranzahl für diesen Server festlegen +command-server-configure-maxplayers-message-success=Maximale Spielerzahl festlegen. +command-server-configure-message-description=Lege die Willkommensnachricht fest +command-server-configure-message-message-modal-input-message-label=Willkommensnachricht +command-server-configure-message-message-modal-input-message-placeholder=Willkommensnachricht +command-server-configure-message-message-modal-title=Definiere die Willkommensnachricht +command-server-configure-message-message-success=Nachricht einstellen. +command-server-configure-spectatoroverflow-description=Aktiviere den Zuschauerüberlauf +command-server-configure-spectatoroverflow-message-success=Überlauf einstellen. +command-server-configure-spectatoroverflow-options-state-description=True, um den Überlauf zu aktivieren +command-server-configure-whitelist-description=Aktiviere die Whitelist +command-server-configure-whitelist-message-success=Whitelist gesetzt. +command-server-configure-whitelist-options-state-description=True, um die Whitelist zu aktivieren +command-server-description=Verwalte deinen Server +command-server-download-description=Dateien herunterladen +command-server-download-downloadplugindata-message-fail-filetolarge=Die Datei ist zu groß. +command-server-download-downloadplugindata-message-fail-tempfile=Temporäre Datei konnte nicht erstellt werden +command-server-download-downloadplugindata-message-fail-zip=Daten konnten nicht gezippt werden. +command-server-download-downloadplugindata-message-success=Upload durchgeführt +command-server-download-downloadplugindata-message-zipping=Daten zippen. +command-server-download-plugindata-description=Plugin-Dateien herunterladen +command-server-download-plugindata-options-path-description=Pfad im Plugin-Verzeichnis +command-server-plugins-description=Installierte Plugins verwalten +command-server-plugins-install-description=Ein anderes Plugin installieren +command-server-plugins-install-message-fail=Plugin konnte nicht installiert werden +command-server-plugins-install-message-success=Installiert { $NAME }. Neustart, um Änderungen zu übernehmen +command-server-plugins-install-options-plugin-description=Zu installierendes Plugin +command-server-plugins-uninstall-options-deletedata-description=Richtig, um auch die Plugin-Daten zu löschen +command-server-plugins-uninstall-description=Zu deinstallierendes Plugin +command-server-plugins-uninstall-message-success-plugin=Plugin deinstalliert. +command-server-plugins-uninstall-message-success-pluginanddata=Plugin deinstalliert und Daten gelöscht +command-server-plugins-uninstall-options-plugin-description=Das zu deinstallierende Plugin +command-server-process-console-options-command-description=Der zu sendende Befehl +command-server-process-console-description=Einen Befehl über die Konsole senden +command-server-process-console-message-executed=Ausgeführt +command-server-process-console-message-notexecutable=Diese Befehle können nicht ausgeführt werden +command-server-process-description=Serverprozess verwalten +command-server-process-log-description=Neustart des Servers +command-server-process-restart-description=Neustart des Servers +command-server-process-restart-message-restarted=Der Server wurde neu gestartet. +command-server-process-restart-message-restarting=Server neu gestartet +command-server-process-start-description=Start des Servers +command-server-process-start-message-fail=Der Server konnte nicht gestartet werden. Er läuft bereits oder ist nicht eingerichtet. +command-server-process-start-message-success=Server gestartet +command-server-process-status-description=Status des Servers +command-server-process-stop-description=Server anhalten +command-server-process-stop-message-stopped=Server gestoppt +command-server-process-stop-message-stopping=Server gestoppt +command-server-system-delete-description=Löschen der Serverdaten +command-server-system-delete-message-error=Beim Löschen des Servers ist etwas schief gelaufen +command-server-system-delete-message-notsetup=Server ist nicht eingerichtet. +command-server-system-delete-message-success=Der Server wurde erfolgreich gelöscht. +command-server-system-description=Verwalten des Serversystems +command-server-system-setup-description=Den Server einrichten +command-server-system-setup-message-alreadysetup=Der Server wurde bereits erstellt. +command-server-system-setup-message-error=Bei der Einrichtung des Servers ist etwas schief gelaufen +command-server-system-setup-message-success=Der Server wurde erfolgreich eingerichtet. +command-server-upload-description=Dateien hochladen +command-server-upload-plugin-description=Dein Plugin hochladen +command-server-upload-plugin-options-file-description=Deine Plugin-Datei +command-server-upload-plugin-message-fail=Plugin konnte nicht hinzugefügt werden. +command-server-upload-plugin-message-success=Plugin hinzugefügt oder ersetzt. +command-server-upload-plugindata-description=Plugin-Daten hochladen +command-server-upload-plugindata-options-file-description=Datei zum Hochladen +command-server-upload-plugindata-options-path-description=Pfad im Plugin-Verzeichnis +command-server-upload-uploadplugindata-message-fail=Datei konnte nicht hinzugefügt werden. +command-server-upload-uploadplugindata-message-success=Datei hinzugefügt oder ersetzt. +command-server-upload-world-description=Eine Welt hochladen, die die aktuelle Welt ersetzt +command-server-upload-world-options-file-description=Welt als zip +command-server-upload-world-message-failed=Ersetzen der Welt fehlgeschlagen +command-server-upload-world-message-nofileorurl=Keine Datei oder Url angegeben +command-server-upload-world-message-replaced=Ersetzte Welt +command-server-upload-world-message-replacing=Download abgeschlossen und ersetzt. +command-server-upload-world-options-url-description=Link zum Herunterladen der Welt als zip +command-server-util-progressdownloader-message-attempting=Ich versuche, die Datei herunterzuladen. +command-server-util-progressdownloader-message-done=Download abgeschlossen. +command-server-util-progressdownloader-message-downloading=Datei wird heruntergeladen. +command-server-util-progressdownloader-message-fail-download=Datei konnte nicht heruntergeladen werden. +command-server-util-progressdownloader-message-fail-tempfile=Temporäre Datei kann nicht erstellt werden +command-serveradmin-description=Verwaltung der Teamserver +command-serveradmin-info-description=Server-Informationen +command-serveradmin-info-detailed-description=Detaillierte Informationen zu Teamservern +command-serveradmin-info-detailed-options-team-description=team +command-serveradmin-info-short-description=Kurze Informationen über Teamserver +command-serveradmin-refresh-all-description=Alle Server aktualisieren +command-serveradmin-refresh-description=Aktualisiere die Dateien der Vorlage auf allen Servern. +command-serveradmin-refresh-refreshall-message-refreshed={ $AMOUNT } Server aktualisiert. +command-serveradmin-refresh-refreshteam-message-failed=Bei der Aktualisierung von { $TEAM } ist ein Fehler aufgetreten. +command-serveradmin-refresh-refreshteam-message-refreshed=Der Server von Team { $TEAM } wurde aktualisiert. +command-serveradmin-refresh-team-description=Aktualisiere einen Teamserver +command-serveradmin-refresh-team-options-team-description=team +command-serveradmin-restart-all-description=Alle Server starten +command-serveradmin-restart-description=Server neu starten +command-serveradmin-restart-restartall-message-restarted=Neustart von { $AMOUNT } Servern. +command-serveradmin-restart-restartteam-message-failed=Server von Team { $TEAM } wurde nicht gestartet. +command-serveradmin-restart-restartteam-message-restarted=Der Server von Team { $TEAM } wurde neu gestartet. +command-serveradmin-restart-team-description=Start eines Team-Servers +command-serveradmin-restart-team-options-team-description=team +command-serveradmin-start-all-description=Alle Server starten +command-serveradmin-start-description=Server starten +command-serveradmin-start-startall-message-started=Starte { $AMOUNT } Server. +command-serveradmin-start-startteam-message-failed=Der Server von Team { $TEAM } konnte nicht gestartet werden. +command-serveradmin-start-startteam-message-started=Server von Team { $TEAM } wurde gestartet. +command-serveradmin-start-team-description=Starte einen Teamserver +command-serveradmin-start-team-options-team-description=Team +command-serveradmin-stop-all-description=Alle Server anhalten +command-serveradmin-stop-description=Server anhalten +command-serveradmin-stop-stopall-message-stopped={ $AMOUNT } Server gestoppt. +command-serveradmin-stop-stopteam-message-failed=Server von Team { $TEAM } läuft nicht. +command-serveradmin-stop-stopteam-message-stopped=Server von Team { $TEAM } wurde gestoppt. +command-serveradmin-stop-team-description=Einen Teamserver stoppen +command-serveradmin-stop-team-options-team-description=team +command-serveradmin-syncvelocity-description=Serverstatus mit Velocity-Daten synchronisieren +command-serveradmin-syncvelocity-message-synced=Server synchronisiert +command-settings-description=Verwalte die Bot-Einstellungen +command-settings-info-description=Zeige die aktuellen Einstellungen +command-settings-info-embed-jamrole=Game-Jam Rolle +command-settings-info-embed-orgarole=Organisations-Rolle +command-settings-info-embed-settings=Einstellungen +command-settings-info-embed-teamsize=Max Team Size +command-settings-jamrole-description=Stellt die Rolle ein, die den registrierten Mitgliedern zugewiesen wird. +command-settings-jamrole-message-updated=Aktualisiere die Game-Jam Rolle. +command-settings-jamrole-options-role-description=Die Rolle, die nach der Registrierung zugewiesen wird +command-settings-locale-description=Ändert die Bot-Sprache. +command-settings-locale-options-locale-description=Die neue Sprache +command-settings-locale-message-invalid=Ungültige Sprache +command-settings-locale-message-updated=Sprache aktualisiert +command-settings-orgarole-description=Definiere die Rolle des Organisationsteams +command-settings-orgarole-options-role-description=Die Rolle, die den Bot verwalten kann +command-settings-teamsize-description=Definiere die maximale Teamgröße. +command-settings-teamsize-message-updated=Aktualisierte maximale Teamgröße. +command-settings-teamsize-options-size-description=Die maximale Teamgröße. +command-start-message-activated=Der Teamstatus wurde auf aktiv geändert. +command-team-create-description=Erstelle ein Team +command-team-create-message-alreadymember=Du bist bereits Teil eines Teams. Um dein eigenes Team zu erstellen, musst du es erst verlassen. +command-team-create-message-created=Team erstellt. +command-team-create-message-nametaken=Dieser Teamname ist bereits vergeben. +command-team-create-message-unregistered=Du musst dich erst registrieren, um ein Team zu erstellen +command-team-create-options-name-description=Name deines Teams +command-team-description=Verwalte dein Team +command-team-disband-options-confirm-description=Bestätige die Löschung deines Teams mit "true" +command-team-disband-description=Löse dein Team auf +command-team-disband-message-disbanded=Dein Team wurde aufgelöst. +command-team-invite-alreadyMember=Du bist bereits Mitglied in einem Team. +command-team-invite-description=Lade jemanden in dein Team ein +command-team-invite-gameJamOver=Der Game-Jam ist vorbei +command-team-invite-joined=Du bist dem Team beigetreten. +command-team-invite-joinedBroadcast={ $USER } ist dem Team beigetreten. +command-team-invite-message-accept=Akzeptieren +command-team-invite-message-invitation={ $USER } hat dich eingeladen, dem Team { $TEAM } beizutreten. +command-team-invite-message-invited=Du hast eine Einladung für den Game-Jam auf { $GUILD } erhalten +command-team-invite-message-noleader=Nur die/die Gruppenleiter:in kann einladen. +command-team-invite-message-notRegistered=Dieser Benutzer ist nicht für den Game-Jam registriert. +command-team-invite-message-partofteam=Dieser/diese Benutzer:in ist bereits Teil eines Teams. +command-team-invite-message-send=Einladung senden. +command-team-invite-options-user-description=Der/Die Benutzer:in, den/die du einladen möchtest +command-team-leave-description=Verlasse dein Team +command-team-leave-left=Du hast das Team verlassen. +command-team-leave-leftBroadcast={ $USER } hat das Team verlassen. +command-team-leave-message-leaderleave=Der Anführer kann das Team nicht verlassen. +command-team-list-description=Erhalte eine Liste alles existierenden Teams. +command-team-profile-description=Zeigt das Profil eines Teams oder dein eigenes an +command-team-profile-leader=Leiter +command-team-profile-member=Mitglied +command-team-profile-message-nouserteam=Dieser Benutzer ist nicht Teil eines Teams. +command-team-profile-projecturl=Projekt Adresse +command-team-profile-options-team-description=Dieses Team anzeigen +command-team-profile-options-user-description=Das Team dieses Benutzers anzeigen +command-team-promote-description=Ändere den/die Teamleiter:in des Teams +command-team-promote-message-done=Der/Die Teamleiter:in wurde geändert +command-team-promote-message-notinteam=Diese/r Nutzer:in ist nicht Teil des Teams. +command-team-promote-options-user-description=Der/Die neue Teamleiter:in. +command-team-rename-description=Ändere den Namen des Teams. +command-team-rename-message-done=Der Name wurde geändert. +command-team-rename-options-name-description=Der neue Teamname +command-unregister-description=Ziehe deine Registrierung zurück. +command-unregister-message-inteam=Du musst erst dein Team verlassen. +command-unregister-message-notregistered=Du bist derzeit für keinen Game-Jam registriert. +command-unregister-message-unregistered=Deine Registrierung wurde zurückgezogen. +command-votes-description=Bewertung vergeben +command-votes-info-description=Informationen über deine Stimmen. +command-votes-info-embed-title=Vergebene Punkte +command-votes-ranking-description=Das aktuelle Ranking +command-votes-ranking-embed-votes=Punkte +command-votes-ranking-message-voteactive=Abstimmung aktiv. Die Rangliste kann erst nach dem Beenden der Abstimmung angezeigt werden. +command-votes-vote-description=Stimme für ein Team ab. +command-votes-vote-message-done=Du hast { $TEAM } { $POINTS } punkte gegeben. Verbleibende Punkte: { $REMAINING } +command-votes-vote-message-maxpointsreached=Du hast das maximum an Punkten vergeben. Entferne Punkte von anderen teams wenn du weiter Punkte vergeben willst. Verbleibende Punkte: { $REMAINING } +command-votes-vote-message-notactive=Es ist keine Abstimmung aktiv. +command-votes-vote-message-ownteam=Du kannst nicht für dein eigenes Team abstimmen. +command-votes-vote-options-points-description=Die Punkte zum vergeben +command-votes-vote-options-team-description=Das Tam für das du abstimmen möchtest. +command-team-api-description=Informationen über die team api +error-alreadyActive=Ein Game-Jam ist bereits aktiv. +error-invalidpath=Ungültiger Pfad +error-invalidrimeformat=Ungültiges Zeitformat. Das Format ist { $FORMAT } +error-invalidtimezone=Ungültige Zeitzone. +error-maxteamsize=Das Team hat die maximale Größe erreicht. +error-noactivejam=Es gibt keinen aktiven Game-Jam. +error-noconfirm=Bitte bestätige mit dem Parameter confirm +error-nojamactive=Es ist kein Game-Jam im Gange. Teams sind nicht verfügbar. +error-noleader=Nur der/die Teamleiter:in kann dies tun. +error-noteam=Du bist nicht Teil eines Teams. +error-notregistered=Du bist für keinen Game-Jam registriert. +error-noupcomingjam=Es gibt keinen bevorstehenden Game-Jams. +error-pluginnotfound=Plugin nicht gefunden +error-servernotrunning=Server ist nicht online. +error-unkownteam=Dieses Team existiert nicht. +error-votingactive=Das ist nicht möglich während eine Abstimmung aktiv ist. + +teamserver-message-detailstatus-existing-description=Server läuft +teamserver-message-detailstatus-nonexisting-description=Server nicht eingerichtet. + +word-activethreads=Aktive Threads +word-api=Api +word-averageticktime=Durchschnittliche Tick-Zeit +word-max=Max +word-memory=Speicher +word-min=min +word-notrunning=Läuft nicht +word-players=Spieler +word-ports=Ports +word-server=Server +word-serversetup=Server eingerichtet +word-system=System +word-total=Gesamt +word-tps=Tps +word-used=Verwendet +word-upcoming=Bevorstehend +word-ended=Beendet +word-active=Aktiv +word-jamlist=Jam-Liste +word-members=Mitglieder +word-description=Projekt Beschreibung +word-no-description=Keine Beschreibung +word-project-url=Projekt Url +word-no-url=Keine Url + +button-next=Weiter +button-prev=Zurück + + +words-dateformat=yyyy.MM.dd HH:mm +words-format=Format +command-team-edit-description=Bearbeite Team Informationen +command-team-profile-projectdescription=Projekt Beschreibung diff --git a/backend/src/main/resources/locale/en.ftl b/backend/src/main/resources/locale/en.ftl new file mode 100644 index 00000000..95ddc04e --- /dev/null +++ b/backend/src/main/resources/locale/en.ftl @@ -0,0 +1,272 @@ +command-jamadmin-create-description=Create a new game jam +command-jamadmin-create-options-jamend-description=Game Jam end. $words.format$: $words.dateformat$ +command-jamadmin-create-options-jamstart-description=Game Jam start. $words.format$: $words.dateformat$ +command-jamadmin-create-message-created=Jam created. +command-jamadmin-create-options-registerend-description=Registrations close. $words.format$: $words.dateformat$ +command-jamadmin-create-options-registerstart-description=Registrations opening. $words.format$: $words.dateformat$ +command-jamadmin-create-options-tagline-description=Topic tagline as an addition to the topic +command-jamadmin-create-options-timezone-description=The timezone of the game jam. "Europe/Berlin" for example. +command-jamadmin-create-options-topic-description=The topic of the game jam +command-jamadmin-description=Manage jams +command-jamadmin-jam-end-options-confirm-description=Confirm the end +command-jamadmin-jam-description=Manage jams +command-jamadmin-jam-end-description=Ends the currently active jam. Deletes all roles and channel +command-jamadmin-jam-end-message-ended=Jam ended. +command-jamadmin-jam-start-description=Start the next scheduled jam +command-jamadmin-votes-close-description=Close votes for the current jam +command-jamadmin-votes-description=Manage votes +command-jamadmin-votes-message-closed=Votes closed for the current jam. +command-jamadmin-votes-message-opened=Votes for the current jam opened. +command-jamadmin-votes-open-description=Open votes for the current jam +command-register-description=Register for an upcomming Game Jam +command-register-message-alreadyregistered=You are already registered. +command-register-message-notanymore=Registrations are closed. +command-register-message-notyet=You cant register for this game jam yet. You can register at { $TIMESTAMP } +command-register-message-registered=You have registered yourself for the next game jam. It will start at { $TIMESTAMP }. +command-server-configure-description=Configure server +command-server-configure-maxplayers-options-amount-description=Max amount of players. +command-server-configure-maxplayers-description=Set the max players of this server +command-server-configure-maxplayers-message-success=Max players set. +command-server-configure-message-description=Set the welcome message +command-server-configure-message-message-modal-input-message-label=Welcome message +command-server-configure-message-message-modal-input-message-placeholder=Welcome message +command-server-configure-message-message-modal-title=Define the welcome message +command-server-configure-message-message-success=Message set. +command-server-configure-spectatoroverflow-description=Active the spectator overflow +command-server-configure-spectatoroverflow-message-success=Overflow set. +command-server-configure-spectatoroverflow-options-state-description=True to enable overflow +command-server-configure-whitelist-description=Enable the whitelist +command-server-configure-whitelist-message-success=Whitelist set. +command-server-configure-whitelist-options-state-description=True to enable whitelist +command-server-description=Manage your server +command-server-download-description=Download files +command-server-download-downloadplugindata-message-fail-filetolarge=File is too large. +command-server-download-downloadplugindata-message-fail-tempfile=Failed to create temp file +command-server-download-downloadplugindata-message-fail-zip=Failed to zip data. +command-server-download-downloadplugindata-message-success=Upload done +command-server-download-downloadplugindata-message-zipping=Zipping data. +command-server-download-plugindata-description=Download plugin files +command-server-download-plugindata-options-path-description=Path in the plugin directory +command-server-plugins-description=Manage installed plugins +command-server-plugins-install-description=Install another plugin +command-server-plugins-install-message-fail=Could not install plugin +command-server-plugins-install-message-success=Installed { $NAME }. Restart to apply changes +command-server-plugins-install-options-plugin-description=Plugin to install +command-server-plugins-uninstall-options-deletedata-description=True to delete the plugin data as well +command-server-plugins-uninstall-description=Plugin to uninstall +command-server-plugins-uninstall-message-success-plugin=Uninstalled plugin. +command-server-plugins-uninstall-message-success-pluginanddata=Uninstalled plugin and deleted data +command-server-plugins-uninstall-options-plugin-description=The plugin to uninstall +command-server-process-console-options-command-description=The command to send +command-server-process-console-description=Send a command via console +command-server-process-console-message-executed=Executed +command-server-process-console-message-notexecutable=Those commands can not be executed +command-server-process-description=Manage server process +command-server-process-log-description=Restart the server +command-server-process-restart-description=Restart the server +command-server-process-restart-message-restarted=Server restarted. +command-server-process-restart-message-restarting=Server restarting +command-server-process-start-description=Start the server +command-server-process-start-message-fail=Could not start server. It is already running or not set up. +command-server-process-start-message-success=Server started +command-server-process-status-description=Server status +command-server-process-stop-description=Stop the server +command-server-process-stop-message-stopped=Server stopped +command-server-process-stop-message-stopping=Stopping server +command-server-system-delete-description=Delete the server data +command-server-system-delete-message-error=Something went wrong during server deletion +command-server-system-delete-message-notsetup=Server is not set up. +command-server-system-delete-message-success=Server was deleted successfully. +command-server-system-description=Manage the server system +command-server-system-setup-description=Setup the server +command-server-system-setup-message-alreadysetup=Server was already created. +command-server-system-setup-message-error=Something went wrong during server setup +command-server-system-setup-message-success=Server was setup successfully. +command-server-upload-description=Upload files +command-server-upload-plugin-description=Upload your plugin +command-server-upload-plugin-options-file-description=Your plugin file +command-server-upload-plugin-message-fail=Failed to add plugin. +command-server-upload-plugin-message-success=Added or replaced plugin. +command-server-upload-plugindata-description=Upload plugin data +command-server-upload-plugindata-options-file-description=File to upload +command-server-upload-plugindata-options-path-description=Path in the plugin directory +command-server-upload-uploadplugindata-message-fail=Failed to add file. +command-server-upload-uploadplugindata-message-success=Added or replaced file. +command-server-upload-world-description=Upload a world replacing the current world +command-server-upload-world-options-file-description=World as zip +command-server-upload-world-message-failed=Failed to replace world +command-server-upload-world-message-nofileorurl=No file or url provided +command-server-upload-world-message-replaced=Replaced world +command-server-upload-world-message-replacing=Download done. Replacing. +command-server-upload-world-options-url-description=Link to download the world as zip +command-server-util-progressdownloader-message-attempting=Attempting to download file. +command-server-util-progressdownloader-message-done=Download done. +command-server-util-progressdownloader-message-downloading=Downloading file. +command-server-util-progressdownloader-message-fail-download=Could not download file. +command-server-util-progressdownloader-message-fail-tempfile=Failed to create temp file +command-serveradmin-description=Administration of team servers +command-serveradmin-info-description=Server information +command-serveradmin-info-detailed-description=Detailed information about team servers +command-serveradmin-info-detailed-options-team-description=team +command-serveradmin-info-short-description=Short information about team servers +command-serveradmin-refresh-all-description=Refresh all server +command-serveradmin-refresh-description=Refresh files of the template in all servers. +command-serveradmin-refresh-refreshall-message-refreshed=Refreshed { $AMOUNT } servers. +command-serveradmin-refresh-refreshteam-message-failed=Failed during refresh of { $TEAM }. +command-serveradmin-refresh-refreshteam-message-refreshed=Server of team { $TEAM } refreshed. +command-serveradmin-refresh-team-description=Refresh a team server +command-serveradmin-refresh-team-options-team-description=team +command-serveradmin-restart-all-description=Start all server +command-serveradmin-restart-description=Restart servers +command-serveradmin-restart-restartall-message-restarted=Restarted { $AMOUNT } servers. +command-serveradmin-restart-restartteam-message-failed=Server of team { $TEAM } was not running. +command-serveradmin-restart-restartteam-message-restarted=Server of team { $TEAM } restarted. +command-serveradmin-restart-team-description=Start a team server +command-serveradmin-restart-team-options-team-description=team +command-serveradmin-start-all-description=Start all server +command-serveradmin-start-description=Start servers +command-serveradmin-start-startall-message-started=Started { $AMOUNT } servers. +command-serveradmin-start-startteam-message-failed=Could not start server of team { $TEAM }. +command-serveradmin-start-startteam-message-started=Server of team { $TEAM } started. +command-serveradmin-start-team-description=Start a team server +command-serveradmin-start-team-options-team-description=team +command-serveradmin-stop-all-description=Stop all server +command-serveradmin-stop-description=Stop servers +command-serveradmin-stop-stopall-message-stopped=Stopped { $AMOUNT } servers. +command-serveradmin-stop-stopteam-message-failed=Server of team { $TEAM } is not running. +command-serveradmin-stop-stopteam-message-stopped=Server of team { $TEAM } stopped. +command-serveradmin-stop-team-description=Stop a team server +command-serveradmin-stop-team-options-team-description=team +command-serveradmin-syncvelocity-description=Sync servers with velocity data +command-serveradmin-syncvelocity-message-synced=Synced server +command-settings-description=Manage bot settings +command-settings-info-description=Show the current settings +command-settings-info-embed-jamrole=Game Jam Role +command-settings-info-embed-orgarole=Organization Role +command-settings-info-embed-settings=Settings +command-settings-info-embed-teamsize=Max Team Size +command-settings-jamrole-description=Set the role which will be assigned to registered members. +command-settings-jamrole-message-updated=Updated the game jam role. +command-settings-jamrole-options-role-description=The role to assign after registration +command-settings-locale-description=Change the bot language. +command-settings-locale-options-locale-description=The new language +command-settings-locale-message-invalid=Invalid locale +command-settings-locale-message-updated=Locale updated +command-settings-orgarole-description=Define the organisation team role +command-settings-orgarole-options-role-description=The role which can manage the bot +command-settings-teamsize-description=Define the max team size. +command-settings-teamsize-message-updated=Updated max team size. +command-settings-teamsize-options-size-description=The max team size. +command-start-message-activated=Jam state changed to active. +command-team-create-description=Create a team +command-team-create-message-alreadymember=You are already part of a team. You need to leave first to create your own team. +command-team-create-message-created=Team created. +command-team-create-message-nametaken=This team name is already taken. +command-team-create-message-unregistered=You need to register first to create a team +command-team-create-options-name-description=Name of your team +command-team-description=Manage your team +command-team-disband-options-confirm-description=Confirm deletion of your team with "true" +command-team-disband-description=Disband your team +command-team-disband-message-disbanded=Your team was disbanded. +command-team-invite-alreadyMember=You are already member of a team. +command-team-invite-description=Invite someone to your team +command-team-invite-gameJamOver=The game jam is over +command-team-invite-joined=You joined the team. +command-team-invite-joinedBroadcast={ $USER } joined the team. +command-team-invite-message-accept=Accept +command-team-invite-message-invitation={ $USER } invited you to join their team { $TEAM }. +command-team-invite-message-invited=You received a invitation for the game jam on { $GUILD } +command-team-invite-message-noleader=Only the group leader can invite people. +command-team-invite-message-notRegistered=This user is not registered for the game jam. +command-team-invite-message-partofteam=This user is already part of a team. +command-team-invite-message-send=Invitation send. +command-team-invite-options-user-description=The user you want to invite +command-team-leave-description=Leave your team +command-team-leave-left=You left the team. +command-team-leave-leftBroadcast={ $USER } left the team. +command-team-leave-message-leaderleave=The leader cant leave the team. +command-team-list-description=Get a list of all existing teams. +command-team-profile-description=Shows the profile of a team or your own +command-team-profile-leader=Leader +command-team-profile-member=Member +command-team-profile-message-nouserteam=This user is not part of a team. +command-team-profile-projecturl=Project Url +command-team-profile-options-team-description=Show this team +command-team-profile-options-user-description=Show team of this user +command-team-promote-description=Change the team leader. +command-team-promote-message-done=Changed the team leader. +command-team-promote-message-notinteam=This user is not part of your team. +command-team-promote-options-user-description=The new team leader. +command-team-rename-description=Change the team name. +command-team-rename-message-done=Team name changed. +command-team-rename-options-name-description=The new team name +command-unregister-description=Withdraw your registration +command-unregister-message-inteam=You need to leave your team first. +command-unregister-message-notregistered=You are not registered for a jam. +command-unregister-message-unregistered=You are now unregistered. +command-votes-description=Cast votes +command-votes-info-description=Information about your votes. +command-votes-info-embed-title=Given Votes +command-votes-ranking-description=The current ranking +command-votes-ranking-embed-votes=Votes +command-votes-ranking-message-voteactive=A vote is active. Rankings can be accessed after the polls are closed. +command-votes-vote-description=Vote for a team. +command-votes-vote-message-done=You gave { $TEAM } { $POINTS } points. Remaining points: { $REMAINING } +command-votes-vote-message-maxpointsreached=You have reached the max amount of given points. Remove points from other teams if you want to add more. Remaining points: { $REMAINING } +command-votes-vote-message-notactive=No voting is active. +command-votes-vote-message-ownteam=You cant vote for your own team. +command-votes-vote-options-points-description=The points to give. +command-votes-vote-options-team-description=The team you want to vote for. +command-team-api-description=Information about the team api +error-alreadyActive=A jam is already active. +error-invalidpath=Invalid path +error-invalidrimeformat=Invalid time format. Format is { $FORMAT } +error-invalidtimezone=Invalid timezone. +error-maxteamsize=The team has reached the max size. +error-noactivejam=There is no active jam. +error-noconfirm=Please confirm by setting the confirm parameter +error-nojamactive=No jam is in progress. Teams are not available. +error-noleader=Only the leader can do this. +error-noteam=You are not part of a team. +error-notregistered=You are not registered for a jam. +error-noupcomingjam=There is no upcoming jam. +error-pluginnotfound=Plugin not found +error-servernotrunning=Server is not online. +error-unkownteam=This team does not exist. +error-votingactive=This function is not available while a vote is active. + +teamserver-message-detailstatus-existing-description=Server running +teamserver-message-detailstatus-nonexisting-description=Server not set up. + +word-activethreads=Active threads +word-api=Api +word-averageticktime=Average Tick time +word-max=Max +word-memory=Memory +word-min=min +word-notrunning=Not running +word-players=Players +word-ports=Ports +word-server=Server +word-serversetup=Server set up +word-system=System +word-total=Total +word-tps=Tps +word-used=Used +word-upcoming=Upcoming +word-ended=Ended +word-active=Active +word-jamlist=Jam list +word-members=Members +word-description=Project description +word-no-description=No description +word-project-url=Project url +word-no-url=No url + +button-next=Forward +button-prev=Back + +words-dateformat=yyyy.MM.dd HH:mm +words-format=Format +command-team-edit-description=Edit team information +command-team-profile-projectdescription=Project Description diff --git a/backend/src/main/resources/logback.xml b/backend/src/main/resources/logback.xml new file mode 100644 index 00000000..1529d6b4 --- /dev/null +++ b/backend/src/main/resources/logback.xml @@ -0,0 +1,16 @@ + + + + + %cyan(%d{HH:mm:ss.SSS}) %gray([%thread]) %highlight(%-5level) %magenta(%logger{36}) - %msg%n + + + + + + + + + + diff --git a/backend/src/test/java/de/chojo/pluginjam/BackendTest.java b/backend/src/test/java/de/chojo/pluginjam/BackendTest.java new file mode 100644 index 00000000..b353b7fd --- /dev/null +++ b/backend/src/test/java/de/chojo/pluginjam/BackendTest.java @@ -0,0 +1,22 @@ +package de.chojo.pluginjam; + + +import io.micronaut.runtime.EmbeddedApplication; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Assertions; + +import jakarta.inject.Inject; + +@MicronautTest +class BackendTest { + + @Inject + EmbeddedApplication application; + + @Test + void testItWorks() { + Assertions.assertTrue(application.isRunning()); + } + +} diff --git a/backend/src/test/java/de/chojo/pluginjam/model/PowerSignalDTOTest.java b/backend/src/test/java/de/chojo/pluginjam/model/PowerSignalDTOTest.java new file mode 100644 index 00000000..af3220e0 --- /dev/null +++ b/backend/src/test/java/de/chojo/pluginjam/model/PowerSignalDTOTest.java @@ -0,0 +1,22 @@ +package de.chojo.pluginjam.model; + +import io.micronaut.serde.ObjectMapper; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class PowerSignalDTOTest { + + @Test + void testDeserialization() throws IOException { + ObjectMapper objectMapper = ObjectMapper.getDefault(); + String json = "{\"signal\": \"START\"}"; + PowerSignalDTO dto = objectMapper.readValue(json, PowerSignalDTO.class); + assertNotNull(dto, "DTO should not be null"); + assertNotNull(dto.signal(), "Signal should not be null"); + assertEquals(PowerSignalDTO.Signal.START, dto.signal()); + } +} diff --git a/bot/build.gradle.kts b/bot/build.gradle.kts index 654328b0..27e511ec 100644 --- a/bot/build.gradle.kts +++ b/bot/build.gradle.kts @@ -20,16 +20,28 @@ dependencies { implementation("de.chojo", "cjda-util", "2.14.5+jda-6.3.0") { exclude(module = "opus-java") } + + implementation("io.github.kaktushose:jda-commands:5.0.0") + + implementation("net.dv8tion", "JDA", "6.4.1") + implementation(libs.javalin.bundle) implementation(libs.javalin.openapi) implementation(libs.javalin.swagger) annotationProcessor(libs.javalin.annotation) implementation("net.lingala.zip4j", "zip4j", "2.11.6") + // config + implementation("dev.chojo", "ocular", "2.2.1") + implementation("tools.jackson.dataformat:jackson-dataformat-yaml:3.1.4") + // database implementation(libs.bundles.sadu) implementation("org.postgresql", "postgresql", "42.7.11") + // docker api + implementation(libs.bundles.docker) + // Logging implementation(libs.bundles.logging) implementation("club.minnced", "discord-webhooks", "0.8.4") diff --git a/bot/src/main/java/de/chojo/gamejam/Bot.java b/bot/src/main/java/de/chojo/gamejam/Bot.java index 65ab4956..14548617 100644 --- a/bot/src/main/java/de/chojo/gamejam/Bot.java +++ b/bot/src/main/java/de/chojo/gamejam/Bot.java @@ -6,23 +6,18 @@ package de.chojo.gamejam; +import com.google.inject.Guice; import com.zaxxer.hikari.HikariDataSource; import de.chojo.gamejam.api.Api; -import de.chojo.gamejam.commands.jamadmin.JamAdmin; -import de.chojo.gamejam.commands.register.Register; -import de.chojo.gamejam.commands.server.Server; -import de.chojo.gamejam.commands.serveradmin.ServerAdmin; -import de.chojo.gamejam.commands.settings.Settings; -import de.chojo.gamejam.commands.team.Team; -import de.chojo.gamejam.commands.unregister.Unregister; -import de.chojo.gamejam.commands.vote.Votes; import de.chojo.gamejam.configuration.Configuration; import de.chojo.gamejam.data.access.Guilds; +import de.chojo.gamejam.listener.InviteButtonListener; import de.chojo.gamejam.data.access.Teams; +import de.chojo.gamejam.server.DockerService; import de.chojo.gamejam.server.ServerService; +import de.chojo.gamejam.server.CommandContextProvider; import de.chojo.gamejam.util.LogNotify; import de.chojo.gamejam.util.Token; -import de.chojo.jdautil.interactions.dispatching.InteractionHub; import de.chojo.jdautil.localization.ILocalizer; import de.chojo.jdautil.localization.Localizer; import de.chojo.sadu.core.exceptions.ExceptionTransformer; @@ -35,6 +30,8 @@ import de.chojo.sadu.queries.api.configuration.QueryConfiguration; import de.chojo.sadu.updater.QueryReplacement; import de.chojo.sadu.updater.SqlUpdater; +import io.github.kaktushose.jdac.JDACommands; +import io.github.kaktushose.jdac.guice.GuiceExtensionData; import net.dv8tion.jda.api.interactions.DiscordLocale; import net.dv8tion.jda.api.requests.GatewayIntent; import net.dv8tion.jda.api.requests.RestAction; @@ -46,19 +43,13 @@ import javax.security.auth.login.LoginException; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.PosixFilePermission; import java.sql.SQLException; import java.util.List; import java.util.Optional; -import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; import static de.chojo.sadu.queries.api.call.Call.call; import static org.slf4j.LoggerFactory.getLogger; @@ -80,6 +71,7 @@ public class Bot { private ShardManager shardManager; private Guilds guilds; private ServerService serverService; + private DockerService dockerService; private Teams teams; private Api api; @@ -155,26 +147,17 @@ private void buildLocale() { .addLanguage(DiscordLocale.GERMAN) .withLanguageProvider(guild -> Optional.ofNullable(guilds.guild(guild).settings().locale()) .map(DiscordLocale::from)) + .withGuildLocaleCodeProvider((guild, s) -> Optional.empty()) .build(); } private void buildCommands() { - InteractionHub.builder(shardManager) - .withLocalizer(localizer) - .withCommands(new JamAdmin(guilds), - new Register(guilds), - new Settings(guilds), - new Team(guilds, configuration), - new Unregister(guilds), - new Votes(guilds), - new Server(guilds, serverService, configuration), - new ServerAdmin(guilds, serverService)) - .withPagination(builder -> builder.withLocalizer(localizer) - .withCache(cache -> cache.expireAfterAccess(30, TimeUnit.MINUTES))) - .withMenuService(builder -> builder.withLocalizer(localizer) - .withCache(cache -> cache.expireAfterAccess(30, TimeUnit.MINUTES))) - .withModalService(builder -> builder.withLocalizer(localizer)) - .build(); + var userContextProvider = new CommandContextProvider(guilds); + var injector = Guice.createInjector(new BotModule(serverService, userContextProvider)); + JDACommands.builder(shardManager) + .extensionData(new GuiceExtensionData(injector)) + .start(); + shardManager.addEventListener(new InviteButtonListener(guilds)); } private void initBot() { @@ -188,8 +171,6 @@ private void initBot() { .setEventPool(Executors.newScheduledThreadPool(5, createThreadFactory("Event Worker"))) .build(); RestAction.setDefaultFailure(throwable -> log.error("Unhandled exception occurred: ", throwable)); - serverService.inject(teams); - serverService.syncVelocity(); } private void initDb() throws IOException, SQLException { @@ -242,23 +223,7 @@ private void initDb() throws IOException, SQLException { } private void initServer() throws IOException { - serverService = ServerService.create(createScheduledExecutor("Server ping", 1), configuration); - - var templateDir = Path.of(configuration.serverTemplate().templateDir()); - var serverDir = Path.of(configuration.serverManagement().serverDir()); - var pluginDir = Path.of(configuration.plugins().pluginDir()); - - Files.createDirectories(templateDir); - Files.createDirectories(serverDir); - Files.createDirectories(pluginDir); - - Path wait = Path.of("wait.sh"); - Files.copy(getClass().getClassLoader().getResourceAsStream("wait.sh"), - wait, StandardCopyOption.REPLACE_EXISTING); - try { - Files.setPosixFilePermissions(wait, Set.of(PosixFilePermission.OWNER_EXECUTE, PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE)); - } catch (UnsupportedOperationException e) { - log.error("Use linux..."); - } + serverService = new ServerService(configuration); + dockerService = new DockerService(configuration.docker(), configuration.plugins()); } } diff --git a/bot/src/main/java/de/chojo/gamejam/BotModule.java b/bot/src/main/java/de/chojo/gamejam/BotModule.java new file mode 100644 index 00000000..b8f73d9a --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/BotModule.java @@ -0,0 +1,27 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam; + +import com.google.inject.AbstractModule; +import de.chojo.gamejam.server.ServerService; +import de.chojo.gamejam.server.CommandContextProvider; + +public class BotModule extends AbstractModule { + private final ServerService serverService; + private final CommandContextProvider commandContextProvider; + + public BotModule(ServerService serverService, CommandContextProvider commandContextProvider) { + this.serverService = serverService; + this.commandContextProvider = commandContextProvider; + } + + @Override + protected void configure() { + bind(ServerService.class).toInstance(serverService); + bind(CommandContextProvider.class).toInstance(commandContextProvider); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/api/Api.java b/bot/src/main/java/de/chojo/gamejam/api/Api.java index 6a45e058..f5f2bed2 100644 --- a/bot/src/main/java/de/chojo/gamejam/api/Api.java +++ b/bot/src/main/java/de/chojo/gamejam/api/Api.java @@ -7,7 +7,7 @@ package de.chojo.gamejam.api; import de.chojo.gamejam.api.exception.InterruptException; -import de.chojo.gamejam.api.v1.Server; +import de.chojo.gamejam.api.v1.ServerRoute; import de.chojo.gamejam.api.v1.Teams; import de.chojo.gamejam.api.v1.Users; import de.chojo.gamejam.configuration.Configuration; @@ -95,7 +95,7 @@ private void routes() { users.routes(); var teams = new Teams(shardManager, guilds); teams.routes(); - Server server = new Server(serverService, this.teams); + ServerRoute server = new ServerRoute(serverService, this.teams); server.routes(); }); diff --git a/bot/src/main/java/de/chojo/gamejam/api/v1/Server.java b/bot/src/main/java/de/chojo/gamejam/api/v1/ServerRoute.java similarity index 92% rename from bot/src/main/java/de/chojo/gamejam/api/v1/Server.java rename to bot/src/main/java/de/chojo/gamejam/api/v1/ServerRoute.java index 2287ac1b..bb49dd46 100644 --- a/bot/src/main/java/de/chojo/gamejam/api/v1/Server.java +++ b/bot/src/main/java/de/chojo/gamejam/api/v1/ServerRoute.java @@ -28,12 +28,12 @@ import static io.javalin.apibuilder.ApiBuilder.post; import static org.slf4j.LoggerFactory.getLogger; -public class Server { +public class ServerRoute { private final ServerService serverService; private final Teams teams; - private static final Logger log = getLogger(Server.class); + private static final Logger log = getLogger(ServerRoute.class); - public Server(ServerService serverService, Teams teams) { + public ServerRoute(ServerService serverService, Teams teams) { this.serverService = serverService; this.teams = teams; } @@ -73,7 +73,7 @@ public void routes() { } ) private void handle(@NotNull Context ctx) { - String token = ctx.pathParam("token"); + /*String token = ctx.pathParam("token"); Optional team = teams.byToken(token); if (team.isEmpty()) { ctx.status(HttpStatus.NOT_FOUND); @@ -107,10 +107,12 @@ private void handle(@NotNull Context ctx) { ctx.status(HttpStatus.ACCEPTED); String restart = ctx.queryParam("restart"); - if ("true".equals(restart) && teamServer.running()) { + if ("true".equals(restart) && teamServer.isRunning()) { teamServer.restart(); - } else if (teamServer.running()) { + } else if (teamServer.isRunning()) { teamServer.send("say Plugin Updated"); } + + */ } } diff --git a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/JamAdmin.java b/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/JamAdmin.java deleted file mode 100644 index 80be89d5..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/JamAdmin.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.jamadmin; - -import de.chojo.gamejam.commands.jamadmin.handler.Create; -import de.chojo.gamejam.commands.jamadmin.handler.jam.JamEnd; -import de.chojo.gamejam.commands.jamadmin.handler.jam.JamStart; -import de.chojo.gamejam.commands.jamadmin.handler.votes.ChangeVotes; -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.Argument; -import de.chojo.jdautil.interactions.slash.Group; -import de.chojo.jdautil.interactions.slash.Slash; -import de.chojo.jdautil.interactions.slash.SubCommand; -import de.chojo.jdautil.interactions.slash.provider.SlashCommand; - -public class JamAdmin extends SlashCommand { - - - public JamAdmin(Guilds guilds) { - super(Slash.of("jamadmin", "command.jamadmin.description") - .adminCommand() - .subCommand(SubCommand.of("create", "command.jamadmin.create.description") - .handler(new Create(guilds)) - .argument(Argument.text("topic", "command.jamadmin.create.options.topic.description") - .asRequired()) - .argument(Argument.text("tagline", "command.jamadmin.create.options.tagline.description") - .asRequired()) - .argument(Argument.text("timezone", "command.jamadmin.create.options.timezone.description") - .asRequired() - .withAutoComplete()) - .argument(Argument.text("registerstart", "command.jamadmin.create.options.registerstart.description") - .asRequired()) - .argument(Argument.text("registerend", "command.jamadmin.create.options.registerend.description") - .asRequired()) - .argument(Argument.text("jamstart", "command.jamadmin.create.options.jamstart.description") - .asRequired()) - .argument(Argument.text("jamend", "command.jamadmin.create.options.jamend.description") - .asRequired())) - .group(Group.of("jam", "command.jamadmin.jam.description") - .subCommand(SubCommand.of("start", "command.jamadmin.jam.start.description") - .handler(new JamStart(guilds))) - .subCommand(SubCommand.of("end", "command.jamadmin.jam.end.description") - .handler(new JamEnd(guilds)) - .argument(Argument.bool("confirm", "command.jamadmin.jam.end.options.confirm.description")))) - .group(Group.of("votes", "command.jamadmin.votes.description") - .subCommand(SubCommand.of("open", "command.jamadmin.votes.open.description") - .handler(new ChangeVotes(guilds, true, "command.jamadmin.votes.message.opened"))) - .subCommand(SubCommand.of("close", "command.jamadmin.votes.close.description") - .handler(new ChangeVotes(guilds, false, "command.jamadmin.votes.message.closed")))) - .build()); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/Create.java b/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/Create.java deleted file mode 100644 index 10331ce6..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/Create.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.jamadmin.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.wrapper.jam.JamCreator; -import de.chojo.gamejam.data.dao.guild.jams.jam.JamTimes; -import de.chojo.gamejam.data.wrapper.jam.TimeFrame; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.Replacement; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.interactions.commands.Command; -import net.dv8tion.jda.api.interactions.commands.OptionMapping; - -import java.time.DateTimeException; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.util.Locale; -import java.util.stream.Collectors; - -public class Create implements SlashHandler { - public static final String PATTERN = "yyyy.MM.dd HH:mm"; - private static final DateTimeFormatter DATE_PARSER = DateTimeFormatter.ofPattern(PATTERN); - private final Guilds guilds; - - public Create(Guilds guilds) { - this.guilds = guilds; - } - - private ZonedDateTime parseTime(String time, ZoneId zoneId) throws DateTimeException { - var parsed = LocalDateTime.from(DATE_PARSER.parse(time)); - return ZonedDateTime.ofInstant(parsed, zoneId.getRules().getOffset(parsed), zoneId); - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var guild = guilds.guild(event); - var topic = String.join("\n", event.getOption("topic").getAsString(), - event.getOption("tagline", "", OptionMapping::getAsString)); - ZoneId timezone; - try { - timezone = ZoneId.of(event.getOption("timezone").getAsString()); - } catch (DateTimeException e) { - event.reply(context.localize("error.invalidtimezone")).setEphemeral(true).queue(); - return; - } - - var jamBuilder = JamCreator.create() - .setTopic(topic); - try { - var registerStart = parseTime(event.getOption("registerstart").getAsString(), timezone); - var registerEnd = parseTime(event.getOption("registerend").getAsString(), timezone); - var jamStart = parseTime(event.getOption("jamstart").getAsString(), timezone); - var jamEnd = parseTime(event.getOption("jamend").getAsString(), timezone); - var times = new JamTimes(timezone, new TimeFrame(registerStart, registerEnd), new TimeFrame(jamStart, jamEnd)); - jamBuilder.setTimes(times); - } catch (DateTimeException e) { - event.reply(context.localize("error.invalidrimeformat", Replacement.create("FORMAT", PATTERN))) - .setEphemeral(true).queue(); - return; - } - - guild.jams().create(jamBuilder.build()); - event.reply(context.localize("command.jamadmin.create.message.created")).setEphemeral(true).queue(); - } - - @Override - public void onAutoComplete(CommandAutoCompleteInteractionEvent event, EventContext context) { - if ("timezone".equals(event.getFocusedOption().getName())) { - var value = event.getFocusedOption().getValue().toLowerCase(Locale.ROOT); - var choices = ZoneId.getAvailableZoneIds().stream() - .filter(zone -> zone.toLowerCase(Locale.ROOT).contains(value)) - .limit(25) - .map(zone -> new Command.Choice(zone, zone)) - .collect(Collectors.toList()); - event.replyChoices(choices).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/jam/JamEnd.java b/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/jam/JamEnd.java deleted file mode 100644 index 6f2aed1c..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/jam/JamEnd.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.jamadmin.handler.jam; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public final class JamEnd implements SlashHandler { - private final Guilds guilds; - - public JamEnd(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - if (!event.getOption("confirm").getAsBoolean()) { - event.reply(context.localize("error.noconfirm")).setEphemeral(true).queue(); - return; - } - - - guilds.guild(event).jams().activeJam() - .ifPresentOrElse( - jam -> { - jam.state().finish(); - event.reply(context.localize("command.jamadmin.jam.end.message.ended")).setEphemeral(true) - .queue(); - }, - () -> event.reply(context.localize("error.noactivejam")).setEphemeral(true).queue()); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/jam/JamStart.java b/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/jam/JamStart.java deleted file mode 100644 index 32296d60..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/jam/JamStart.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.jamadmin.handler.jam; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public final class JamStart implements SlashHandler { - private final Guilds guilds; - - public JamStart(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var jams = guilds.guild(event).jams(); - var currJam = jams.activeJam(); - if (currJam.isPresent()) { - event.reply(context.localize("error.alreadyActive")).setEphemeral(true).queue(); - return; - } - var next = jams.nextOrCurrent(); - if (next.isPresent()) { - next.get().state().active(true); - event.reply(context.localize("command.start.message.activated")) - .setEphemeral(true) - .queue(); - - } - event.reply(context.localize("error.noupcomingjam")).setEphemeral(true) - .queue(); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/votes/ChangeVotes.java b/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/votes/ChangeVotes.java deleted file mode 100644 index f98bc32d..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/jamadmin/handler/votes/ChangeVotes.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.jamadmin.handler.votes; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public final class ChangeVotes implements SlashHandler { - private final Guilds guilds; - private final boolean voting; - private final String content; - - public ChangeVotes(Guilds guilds, boolean voting, String content) { - this.guilds = guilds; - this.voting = voting; - this.content = content; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - guilds.guild(event).jams().activeJam() - .ifPresentOrElse( - jam -> { - jam.state().voting(voting); - event.reply(context.localize(content)).queue(); - }, - () -> event.reply(context.localize("error.noactivejam")).setEphemeral(true).queue()); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/register/Register.java b/bot/src/main/java/de/chojo/gamejam/commands/register/Register.java deleted file mode 100644 index 8f68e627..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/register/Register.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.register; - -import de.chojo.gamejam.commands.register.handler.Handler; -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.Slash; -import de.chojo.jdautil.interactions.slash.provider.SlashCommand; - -public class Register extends SlashCommand { - public Register(Guilds guilds) { - super(Slash.of("register", "command.register.description") - .command(new Handler(guilds))); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/register/handler/Handler.java b/bot/src/main/java/de/chojo/gamejam/commands/register/handler/Handler.java deleted file mode 100644 index f5944834..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/register/handler/Handler.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.register.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.Replacement; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.utils.TimeFormat; - -import java.time.ZonedDateTime; - -public class Handler implements SlashHandler { - private final Guilds guilds; - - public Handler(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var guild = guilds.guild(event); - var optJam = guild.jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.reply(context.localize("error.noupcomingjam")).setEphemeral(true).queue(); - return; - } - var jam = optJam.get(); - var times = jam.times(); - - if (!times.registration().contains(ZonedDateTime.now())) { - if (times.registration().start().isAfter(ZonedDateTime.now())) { - event.reply(context.localize("command.register.message.notyet", - Replacement.create("TIMESTAMP", TimeFormat.DATE_TIME_LONG.format(times.registration() - .start())))) - .setEphemeral(true) - .queue(); - return; - } - event.reply(context.localize("command.register.message.notanymore")).setEphemeral(true).queue(); - return; - } - - if (jam.registrations().contains(event.getMember().getIdLong())) { - event.reply(context.localize("command.register.message.alreadyregistered")).setEphemeral(true).queue(); - return; - } - - jam.register(event.getMember()); - var settings = guild.jamSettings(); - var role = event.getGuild().getRoleById(settings.jamRole()); - if (role != null) { - event.getGuild().addRoleToMember(event.getMember(), role).queue(); - } - event.reply(context.localize("command.register.message.registered", - Replacement.create("TIMESTAMP", TimeFormat.DATE_TIME_LONG.format(times.jam().start())))) - .setEphemeral(true) - .queue(); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/Server.java b/bot/src/main/java/de/chojo/gamejam/commands/server/Server.java deleted file mode 100644 index 71189604..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/Server.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server; - -import de.chojo.gamejam.commands.server.configure.MaxPlayers; -import de.chojo.gamejam.commands.server.configure.Message; -import de.chojo.gamejam.commands.server.configure.SpectatorOverflow; -import de.chojo.gamejam.commands.server.configure.Whitelist; -import de.chojo.gamejam.commands.server.download.DownloadPluginData; -import de.chojo.gamejam.commands.server.plugins.Install; -import de.chojo.gamejam.commands.server.plugins.Uninstall; -import de.chojo.gamejam.commands.server.process.Console; -import de.chojo.gamejam.commands.server.process.Log; -import de.chojo.gamejam.commands.server.process.Restart; -import de.chojo.gamejam.commands.server.process.Start; -import de.chojo.gamejam.commands.server.process.Status; -import de.chojo.gamejam.commands.server.process.Stop; -import de.chojo.gamejam.commands.server.system.Delete; -import de.chojo.gamejam.commands.server.system.Setup; -import de.chojo.gamejam.commands.server.upload.Plugin; -import de.chojo.gamejam.commands.server.upload.UploadPluginData; -import de.chojo.gamejam.commands.server.upload.World; -import de.chojo.gamejam.configuration.Configuration; -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.server.ServerService; -import de.chojo.gamejam.server.TeamServer; -import de.chojo.jdautil.interactions.slash.Argument; -import de.chojo.jdautil.interactions.slash.Group; -import de.chojo.jdautil.interactions.slash.Slash; -import de.chojo.jdautil.interactions.slash.SubCommand; -import de.chojo.jdautil.interactions.slash.provider.SlashProvider; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -import java.util.Optional; - -public class Server implements SlashProvider { - private final Guilds guilds; - private final ServerService serverService; - private final Configuration configuration; - - public Server(Guilds guilds, ServerService serverService, Configuration configuration) { - this.guilds = guilds; - this.serverService = serverService; - this.configuration = configuration; - } - - @Override - public Slash slash() { - return Slash.of("server", "command.server.description") - .adminCommand() - .group(Group.of("process", "command.server.process.description") - .subCommand(SubCommand.of("start", "command.server.process.start.description") - .handler(new Start(this))) - .subCommand(SubCommand.of("stop", "command.server.process.stop.description") - .handler(new Stop(this))) - .subCommand(SubCommand.of("restart", "command.server.process.restart.description") - .handler(new Restart(this))) - .subCommand(SubCommand.of("console", "command.server.process.console.description") - .handler(new Console(this)) - .argument(Argument.text("command", "command.server.process.console.options.command.description").asRequired())) - .subCommand(SubCommand.of("status", "command.server.process.status.description") - .handler(new Status(this))) - .subCommand(SubCommand.of("log", "command.server.process.log.description") - .handler(new Log(this)))) - .group(Group.of("system", "command.server.system.description") - .subCommand(SubCommand.of("setup", "command.server.system.setup.description") - .handler(new Setup(this))) - .subCommand(SubCommand.of("delete", "command.server.system.delete.description") - .handler(new Delete(this)))) - .group(Group.of("upload", "command.server.upload.description") - .subCommand(SubCommand.of("world", "command.server.upload.world.description") - .handler(new World(this)) - .argument(Argument.text("url", "command.server.upload.world.options.url.description")) - .argument(Argument.attachment("file", "command.server.upload.world.options.file.description"))) - .subCommand(SubCommand.of("plugin", "command.server.upload.plugin.description") - .handler(new Plugin(this)) - .argument(Argument.attachment("file", "command.server.upload.plugin.options.file.description") - .asRequired())) - .subCommand(SubCommand.of("plugindata", "command.server.upload.plugindata.description") - .handler(new UploadPluginData(this, guilds, serverService)) - .argument(Argument.text("path", "command.server.upload.plugindata.options.path.description") - .asRequired() - .withAutoComplete()) - .argument(Argument.attachment("file", "command.server.upload.plugindata.options.file.description") - .asRequired()))) - .group(Group.of("download", "command.server.download.description") - .subCommand(SubCommand.of("plugindata", "command.server.download.plugindata.description") - .handler(new DownloadPluginData(this, guilds, serverService)) - .argument(Argument.text("path", "command.server.download.plugindata.options.path.description") - .asRequired() - .withAutoComplete()))) - .group(Group.of("plugins", "command.server.plugins.description") - .subCommand(SubCommand.of("install", "command.server.plugins.install.description") - .handler(new Install(configuration, this)) - .argument(Argument.text("plugin", "command.server.plugins.install.options.plugin.description") - .asRequired() - .withAutoComplete())) - .subCommand(SubCommand.of("uninstall", "command.server.plugins.uninstall.description") - .handler(new Uninstall(this, configuration, guilds, serverService)) - .argument(Argument.text("plugin", "command.server.plugins.uninstall.options.plugin.description").asRequired() - .withAutoComplete()) - .argument(Argument.bool("deletedata", "command.server.plugins.uninstall.options.deletedata.description") - .asRequired()))) - .group(Group.of("configure", "command.server.configure.description") - .subCommand(SubCommand.of("message", "command.server.configure.message.description") - .handler(new Message(this))) - .subCommand(SubCommand.of("maxplayers", "command.server.configure.maxplayers.description") - .handler(new MaxPlayers(this)) - .argument(Argument.integer("amount", "command.server.configure.maxplayers.options.amount.description").asRequired())) - .subCommand(SubCommand.of("spectatoroverflow", "command.server.configure.spectatoroverflow.description") - .handler(new SpectatorOverflow(this)) - .argument(Argument.bool("state", "command.server.configure.spectatoroverflow.options.state.description").asRequired())) - .subCommand(SubCommand.of("whitelist", "command.server.configure.whitelist.description") - .handler(new Whitelist(this)) - .argument(Argument.bool("state", "command.server.configure.whitelist.options.state.description").asRequired()))) - .build(); - } - - public Optional getServer(SlashCommandInteractionEvent event, EventContext context) { - var optJam = guilds.guild(event).jams().activeJam(); - - if (optJam.isEmpty()) { - event.reply(context.localize("error.nojamactive")).setEphemeral(true).queue(); - return Optional.empty(); - } - - var jam = optJam.get(); - var optTeam = jam.teams().byMember(event.getUser()); - - if (optTeam.isEmpty()) { - event.reply(context.localize("error.noteam")).setEphemeral(true).queue(); - return Optional.empty(); - } - - return Optional.ofNullable(serverService.get(optTeam.get())); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/configure/MaxPlayers.java b/bot/src/main/java/de/chojo/gamejam/commands/server/configure/MaxPlayers.java deleted file mode 100644 index 8008c8f2..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/configure/MaxPlayers.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.configure; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.gamejam.server.TeamServer; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.util.Futures; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import org.slf4j.Logger; - -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; - -import static org.slf4j.LoggerFactory.getLogger; - -public class MaxPlayers implements SlashHandler { - private static final Logger log = getLogger(MaxPlayers.class); - private final Server server; - - public MaxPlayers(Server server) { - this.server = server; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if (optServer.isEmpty()) return; - - var teamServer = optServer.get(); - var request = teamServer.requestBuilder("v1/config/maxplayers") - .POST(HttpRequest.BodyPublishers.ofString(String.valueOf(event.getOption("amount").getAsInt()))) - .build(); - - if (!teamServer.running()) { - event.reply(context.localize("error.servernotrunning")).queue(); - return; - } - - teamServer.http().sendAsync(request, HttpResponse.BodyHandlers.discarding()) - .whenComplete(Futures.whenComplete(res -> event.reply("command.server.configure.maxplayers.message.success").queue(), - err -> log.error("Failed to send request.", err)));; - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/configure/MaxPlayersCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/server/configure/MaxPlayersCommand.java new file mode 100644 index 00000000..a42b5751 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/server/configure/MaxPlayersCommand.java @@ -0,0 +1,49 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.server.configure; + +import com.google.inject.Inject; +import de.chojo.gamejam.server.ServerService; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import org.slf4j.Logger; + +import static org.slf4j.LoggerFactory.getLogger; + +@Bundle("locale") +@Interaction +public class MaxPlayersCommand { + private static final Logger log = getLogger(MaxPlayersCommand.class); + + private final CommandContextProvider userContextService; + private final ServerService serverService; + + @Inject + public MaxPlayersCommand(CommandContextProvider commandContextProvider, ServerService serverService) { + this.userContextService = commandContextProvider; + this.serverService = serverService; + } + + @Command(value = "server configure maxplayers") + public void onCommand(CommandEvent event, @Param("amount" ) int amount) { + var team = userContextService.getUserContext(event.getMember()).team(); + + if (!serverService.dockerService().isRunning(team.id())) { + event.reply("error.servernotrunning"); + return; + } + + var containerName = serverService.dockerService().containerName(team.id()); + + serverService.serverHttpService().configureMaxPlayers(containerName, amount); + event.reply("command.server.configure.maxplayers.message.success"); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/configure/Message.java b/bot/src/main/java/de/chojo/gamejam/commands/server/configure/Message.java deleted file mode 100644 index 9491627e..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/configure/Message.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.configure; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.modals.handler.ModalHandler; -import de.chojo.jdautil.modals.handler.TextInputHandler; -import de.chojo.jdautil.util.Futures; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.components.textinput.TextInputStyle; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -import org.slf4j.Logger; - -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; - -import static org.slf4j.LoggerFactory.getLogger; - -public class Message implements SlashHandler { - private static final Logger log = getLogger(Message.class); - private final Server server; - - public Message(Server server) { - this.server = server; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if (optServer.isEmpty()) return; - - var teamServer = optServer.get(); - - if (!teamServer.running()) { - event.reply(context.localize("error.servernotrunning")).queue(); - return; - } - - context.registerModal(ModalHandler.builder(context.localize("command.server.configure.message.message.modal.title")) - .addInput(TextInputHandler.builder("message", context.localize("command.server.configure.message.message.modal.input.message.label"), TextInputStyle.PARAGRAPH) - .withPlaceholder(context.localize("command.server.configure.message.message.modal.input.message.placeholder")) - .build()) - .withHandler(modalEvent -> { - var content = modalEvent.getValues().get(0).getAsString(); - var request = teamServer.requestBuilder("v1/config/message") - .POST(HttpRequest.BodyPublishers.ofString(content)) - .build(); - teamServer.http().sendAsync(request, HttpResponse.BodyHandlers.discarding()) - .whenComplete(Futures.whenComplete(res -> modalEvent.reply(context.localize("command.server.configure.message.message.success")).queue(), - err -> log.error("Failed to send request.", err))); - }) - .build()); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/configure/MessageCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/server/configure/MessageCommand.java new file mode 100644 index 00000000..b0924338 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/server/configure/MessageCommand.java @@ -0,0 +1,67 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.server.configure; + +import com.google.inject.Inject; +import de.chojo.gamejam.server.ServerService; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Modal; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import io.github.kaktushose.jdac.dispatching.events.interactions.ModalEvent; +import net.dv8tion.jda.api.components.label.Label; +import net.dv8tion.jda.api.components.textinput.TextInput; +import net.dv8tion.jda.api.components.textinput.TextInputStyle; +import org.slf4j.Logger; + +import static org.slf4j.LoggerFactory.getLogger; + +@Bundle("locale") +@Interaction +public class MessageCommand { + private static final Logger log = getLogger(MessageCommand.class); + + private final ServerService serverService; + private final CommandContextProvider commandContextProvider; + + @Inject + public MessageCommand(ServerService serverService, CommandContextProvider commandContextProvider) { + this.serverService = serverService; + this.commandContextProvider = commandContextProvider; + } + + @Command + public void onCommand(CommandEvent event, @Param("message") String message) { + var team = commandContextProvider.getUserContext(event.getMember()).team(); + + if (!serverService.dockerService().isRunning(team.id())) { + event.reply("error.servernotrunning"); + return; + } + + event.replyModal("onModal", + Label.of("command.server.configure.message.message.modal.input.message.label", TextInput.create("message-input", TextInputStyle.SHORT) + .setPlaceholder("command.server.configure.message.message.modal.input.message.placeholder") + .build()) + ); + + serverService.serverHttpService().configureMessage(serverService.dockerService().containerName(team.id()), message); + event.reply("command.server.configure.message.message.success"); + } + + @Modal("command.server.configure.message.message.modal.title") + public void onModal(ModalEvent event) { + var team = commandContextProvider.getUserContext(event.getMember()).team(); + var containerName = serverService.dockerService().containerName(team.id()); + var content = event.value("message-input").getAsString(); + serverService.serverHttpService().configureMessage(containerName, content); + event.reply("command.server.configure.message.message.success"); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/configure/SpectatorOverflow.java b/bot/src/main/java/de/chojo/gamejam/commands/server/configure/SpectatorOverflow.java deleted file mode 100644 index b7a987fa..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/configure/SpectatorOverflow.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.configure; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.gamejam.server.TeamServer; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.util.Futures; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import org.slf4j.Logger; - -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; - -import static org.slf4j.LoggerFactory.getLogger; - -public class SpectatorOverflow implements SlashHandler { - private static final Logger log = getLogger(SpectatorOverflow.class); - private final Server server; - - public SpectatorOverflow(Server server) { - this.server = server; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if (optServer.isEmpty()) return; - - var teamServer = optServer.get(); - - if (!teamServer.running()) { - event.reply(context.localize("error.servernotrunning")).queue(); - return; - } - - var request = teamServer.requestBuilder("v1/config/spectatoroverflow") - .POST(HttpRequest.BodyPublishers.ofString(String.valueOf(event.getOption("state").getAsBoolean()))) - .build(); - teamServer.http().sendAsync(request, HttpResponse.BodyHandlers.discarding()) - .whenComplete(Futures.whenComplete(res -> event.reply(context.localize("command.server.configure.spectatoroverflow.message.success")).queue(), - err -> log.error("Failed to send request.", err)));; - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/configure/SpectatorOverflowCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/server/configure/SpectatorOverflowCommand.java new file mode 100644 index 00000000..2d2de7f2 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/server/configure/SpectatorOverflowCommand.java @@ -0,0 +1,46 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.server.configure; + +import com.google.inject.Inject; +import de.chojo.gamejam.server.ServerService; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import org.slf4j.Logger; + +import static org.slf4j.LoggerFactory.getLogger; + +@Bundle("locale") +@Interaction +public class SpectatorOverflowCommand { + private static final Logger log = getLogger(SpectatorOverflowCommand.class); + private final ServerService serverService; + private final CommandContextProvider commandContextProvider; + + @Inject + public SpectatorOverflowCommand(ServerService serverService, CommandContextProvider commandContextProvider) { + this.serverService = serverService; + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "server configure spectatoroverflow") + public void onCommand(CommandEvent event, @Param("state") boolean state) { + var team = commandContextProvider.getUserContext(event.getMember()).team(); + + if (!serverService.dockerService().isRunning(team.id())) { + event.reply("error.servernotrunning"); + return; + } + + serverService.serverHttpService().configureSpectatorOverflow(serverService.dockerService().containerName(team.id()), state); + event.reply("command.server.configure.spectatoroverflow.message.success"); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/configure/Whitelist.java b/bot/src/main/java/de/chojo/gamejam/commands/server/configure/Whitelist.java deleted file mode 100644 index 8568d957..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/configure/Whitelist.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.configure; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.gamejam.server.TeamServer; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.util.Futures; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import org.slf4j.Logger; - -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; - -import static org.slf4j.LoggerFactory.getLogger; - -public class Whitelist implements SlashHandler { - private static final Logger log = getLogger(Whitelist.class); - private final Server server; - - public Whitelist(Server server) { - this.server = server; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if (optServer.isEmpty()) return; - - var teamServer = optServer.get(); - - if (!teamServer.running()) { - event.reply(context.localize("error.servernotrunning")).queue(); - return; - } - - var request = teamServer.requestBuilder("v1/config/whitelist") - .POST(HttpRequest.BodyPublishers.ofString(String.valueOf(event.getOption("state").getAsBoolean()))) - .build(); - teamServer.http().sendAsync(request, HttpResponse.BodyHandlers.discarding()) - .whenComplete(Futures.whenComplete(res -> event.reply(context.localize("command.server.configure.whitelist.message.success")).queue(), - err -> log.error("Failed to send request.", err)));; - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/download/DownloadPluginData.java b/bot/src/main/java/de/chojo/gamejam/commands/server/download/DownloadPluginData.java deleted file mode 100644 index 584c9012..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/download/DownloadPluginData.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.download; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.dao.guild.jams.Jam; -import de.chojo.gamejam.server.ServerService; -import de.chojo.gamejam.util.TempFile; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.util.Choice; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.exceptions.ErrorResponseException; -import net.dv8tion.jda.api.requests.ErrorResponse; -import net.dv8tion.jda.api.requests.RestAction; -import net.dv8tion.jda.api.utils.FileUpload; -import net.lingala.zip4j.ZipFile; -import net.lingala.zip4j.exception.ZipException; -import org.slf4j.Logger; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -import static org.slf4j.LoggerFactory.getLogger; - -public class DownloadPluginData implements SlashHandler { - private static final Logger log = getLogger(DownloadPluginData.class); - private final Server server; - private final Guilds guilds; - private final ServerService serverService; - - public DownloadPluginData(Server server, Guilds guilds, ServerService serverService) { - this.server = server; - this.guilds = guilds; - this.serverService = serverService; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if (optServer.isEmpty()) return; - var teamServer = optServer.get(); - - var path = event.getOption("path").getAsString(); - - if (path.contains("..")) { - event.reply(context.localize("error.invalidpath")).queue(); - return; - } - - var pluginFile = teamServer.plugins().resolve(path); - - if (pluginFile.toFile().isFile()) { - // No download from plugin root - if (pluginFile.getParent().equals(teamServer.plugins())) { - event.reply(context.localize("error.invalidpath")).queue(); - return; - } - event.replyFiles(FileUpload.fromData(pluginFile, pluginFile.toFile().getName())).queue(); - return; - } - - event.reply(context.localize("command.server.download.downloadplugindata.message.zipping")).queue(); - Path tempFile; - try { - tempFile = TempFile.createPath("download", "zip"); - try (var zip = new ZipFile(tempFile.toFile())) { - zip.addFolder(pluginFile.toFile()); - } - tempFile.toFile().deleteOnExit(); - } catch (ZipException e) { - log.error("Failed to zip date", e); - event.getHook().editOriginal(context.localize("command.server.download.downloadplugindata.message.fail.zip")).queue(); - return; - } catch (IOException e) { - log.error("Failed to create zip file", e); - event.getHook().editOriginal(context.localize("command.server.download.downloadplugindata.message.fail.tempfile")).queue(); - return; - } - event.getHook().editOriginal(context.localize("command.server.download.downloadplugindata.message.success")) - .setFiles(FileUpload.fromData(tempFile, pluginFile.toFile().getName() + ".zip")) - .queue(RestAction.getDefaultSuccess(), err -> { - if (err instanceof ErrorResponseException response) { - if (response.getErrorResponse() == ErrorResponse.FILE_UPLOAD_MAX_SIZE_EXCEEDED) { - event.getHook().editOriginal(context.localize("command.server.download.downloadplugindata.message.fail.filetolarge")).queue(); - } - } - }); - } - - @Override - public void onAutoComplete(CommandAutoCompleteInteractionEvent event, EventContext context) { - var optServer = guilds.guild(event).jams().activeJam() - .map(Jam::teams) - .flatMap(teams -> teams.byMember(event.getUser())) - .map(serverService::get); - if (optServer.isEmpty()) return; - var server = optServer.get(); - - var option = event.getFocusedOption(); - if ("path".equals(option.getName())) { - var plugins = server.plugins(); - var currPath = option.getValue(); - if (currPath.contains("..")) { - event.replyChoices().queue(); - return; - } - var split = currPath.split("/"); - - // Root dir - if (split.length == 1 && !currPath.endsWith("/")) { - var currValue = split[0].toLowerCase(); - var files = files(plugins).stream() - .filter(File::isDirectory) - .filter(file -> file.getName().toLowerCase().startsWith(currValue)) - .limit(25) - .map(line -> fileName(plugins, line)); - event.replyChoices(Choice.toStringChoice(files)).queue(); - return; - } - - if (!currPath.endsWith("/")) { - split = Arrays.copyOfRange(split, 0, split.length - 1); - } - - var path = plugins; - - for (var s : split) { - path = path.resolve(s); - } - - if (currPath.endsWith("/")) { - var files = files(path).stream() - .limit(25) - .map(line -> fileName(plugins, line)); - event.replyChoices(Choice.toStringChoice(files)).queue(); - return; - } - split = currPath.split("/"); - var currValue = currPath.split("/")[split.length - 1].toLowerCase(); - - var files = files(path).stream() - .filter(file -> file.getName().toLowerCase().startsWith(currValue)) - .limit(24) - .map(line -> fileName(plugins, line)) - .collect(Collectors.toCollection(ArrayList::new)); - - event.replyChoices(Choice.toStringChoice(files)).queue(); - } - } - - private List files(Path path) { - return Arrays.stream(path.toFile().listFiles()).toList(); - } - - private String fileName(Path strip, File file) { - if (file.isDirectory()) { - return (file.toPath() + "/").replace(strip.toString(), "").replaceAll("^/", ""); - } - return file.toPath().toString().replace(strip.toString(), "").replaceAll("^/", ""); - - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/plugins/Install.java b/bot/src/main/java/de/chojo/gamejam/commands/server/plugins/Install.java deleted file mode 100644 index 08aa3100..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/plugins/Install.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.plugins; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.gamejam.configuration.Configuration; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.Replacement; -import de.chojo.jdautil.util.Choice; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import org.slf4j.Logger; - -import java.io.IOException; -import java.nio.file.Files; - -import static org.slf4j.LoggerFactory.getLogger; - -public class Install implements SlashHandler { - private static final Logger log = getLogger(Install.class); - private final Configuration configuration; - private final Server server; - - public Install(Configuration configuration, Server server) { - this.configuration = configuration; - this.server = server; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if (optServer.isEmpty()) return; - var teamServer = optServer.get(); - - var pluginName = event.getOption("plugin").getAsString(); - var optPlugin = configuration.plugins().byName(pluginName); - if (optPlugin.isEmpty()) { - event.reply(context.localize("error.pluginnotfound")).queue(); - return; - } - var plugin = optPlugin.get(); - - var pluginPath = teamServer.plugins().resolve(plugin.toFile().getName()); - pluginPath.toFile().delete(); - try { - Files.createSymbolicLink(pluginPath, plugin.toAbsolutePath()); - } catch (IOException e) { - event.reply(context.localize("command.server.plugins.install.message.fail")).queue(); - log.error("Could not install plugin", e); - return; - } - event.reply(context.localize("command.server.plugins.install.message.success", Replacement.create("NAME", pluginName))).queue(); - } - - @Override - public void onAutoComplete(CommandAutoCompleteInteractionEvent event, EventContext context) { - var option = event.getFocusedOption(); - if ("plugin".equals(option.getName())) { - var currValue = option.getValue().toLowerCase(); - if (currValue.isEmpty()) { - event.replyChoices(Choice.toStringChoice(configuration.plugins().pluginNames().stream().limit(25))) - .queue(); - return; - } - var stream = configuration.plugins().pluginNames().stream() - .filter(name -> name.toLowerCase().startsWith(currValue)) - .limit(25); - event.replyChoices(Choice.toStringChoice(stream)).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/plugins/Uninstall.java b/bot/src/main/java/de/chojo/gamejam/commands/server/plugins/Uninstall.java deleted file mode 100644 index 82808724..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/plugins/Uninstall.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.plugins; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.gamejam.configuration.Configuration; -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.dao.guild.jams.Jam; -import de.chojo.gamejam.server.ServerService; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.util.Choice; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -import java.io.File; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class Uninstall implements SlashHandler { - private final Server server; - private final Configuration configuration; - private final Guilds guilds; - private final ServerService serverService; - - public Uninstall(Server server, Configuration configuration, Guilds guilds, ServerService serverService) { - this.server = server; - this.configuration = configuration; - this.guilds = guilds; - this.serverService = serverService; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if (optServer.isEmpty()) return; - var teamServer = optServer.get(); - - var pluginName = event.getOption("plugin").getAsString(); - var optPlugin = configuration.plugins().byName(pluginName); - if (optPlugin.isEmpty()) { - event.reply(context.localize("error.pluginnotfound")).queue(); - return; - } - var plugin = optPlugin.get(); - - var pluginPath = teamServer.plugins().resolve(plugin.toFile().getName()); - pluginPath.toFile().delete(); - - if (event.getOption("deletedata").getAsBoolean()) { - var pluginDir = teamServer.plugins().resolve(pluginName); - teamServer.deleteDirectory(pluginDir); - event.reply(context.localize("command.server.plugins.uninstall.message.success.pluginanddata")).queue(); - } else { - event.reply(context.localize("command.server.plugins.uninstall.message.success.plugin")).queue(); - } - } - - @Override - public void onAutoComplete(CommandAutoCompleteInteractionEvent event, EventContext context) { - var optServer = guilds.guild(event).jams().activeJam() - .map(Jam::teams) - .flatMap(teams -> teams.byMember(event.getUser())) - .map(serverService::get); - if (optServer.isEmpty()) return; - - var option = event.getFocusedOption(); - if ("plugin".equals(option.getName())) { - var allowedPlugins = configuration.plugins().pluginFiles().stream() - .map(File::getName) - .collect(Collectors.toSet()); - - var installedPlugins = Stream.of(optServer.get().plugins().toFile().listFiles(File::isFile)) - .filter(plugin -> allowedPlugins.contains(plugin.getName())) - .map(file -> file.getName().replace(".jar", "")) - .toList(); - - var currValue = option.getValue().toLowerCase(); - - if (currValue.isEmpty()) { - event.replyChoices(Choice.toStringChoice(installedPlugins)) - .queue(); - return; - } - - var stream = installedPlugins.stream() - .filter(name -> name.toLowerCase().startsWith(currValue)) - .limit(25); - event.replyChoices(Choice.toStringChoice(stream)).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Console.java b/bot/src/main/java/de/chojo/gamejam/commands/server/process/Console.java deleted file mode 100644 index 583cf28c..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Console.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.process; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public class Console implements SlashHandler { - private final Server server; - - public Console(Server server) { - this.server = server; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if (optServer.isEmpty()) return; - var teamServer = optServer.get(); - var command = event.getOption("command").getAsString(); - - if (command.startsWith("stop") || command.startsWith("restart")) { - event.reply(context.localize("command.server.process.console.message.notexecutable")).queue(); - return; - } - teamServer.send(command); - event.reply(context.localize("command.server.process.console.message.executed")).queue(); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/process/ConsoleCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/server/process/ConsoleCommand.java new file mode 100644 index 00000000..a4e93f8e --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/server/process/ConsoleCommand.java @@ -0,0 +1,47 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.server.process; + +import com.google.inject.Inject; +import de.chojo.gamejam.server.ServerService; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Param; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; + +@Bundle("locale") +@Interaction +public class ConsoleCommand { + private final ServerService serverService; + private final CommandContextProvider commandContextProvider; + + @Inject + public ConsoleCommand(ServerService serverService, CommandContextProvider commandContextProvider) { + this.serverService = serverService; + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "server process console") + public void onCommand(CommandEvent event, @Param("command") String command) { + var team = commandContextProvider.getUserContext(event.getMember()).team(); + + if (!serverService.dockerService().isRunning(team.id())) { + event.reply("error.servernotrunning"); + return; + } + + if (command.startsWith("stop") || command.startsWith("restart")) { + event.reply("command.server.process.console.message.notexecutable"); + return; + } + + serverService.dockerService().sendCommand(team.id(), command); + event.reply("command.server.process.console.message.executed"); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Log.java b/bot/src/main/java/de/chojo/gamejam/commands/server/process/Log.java deleted file mode 100644 index 60d0d896..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Log.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.process; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.utils.FileUpload; - -import java.io.IOException; -import java.nio.file.Files; - -public class Log implements SlashHandler { - private final Server server; - - public Log(Server server) { - this.server = server; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if(optServer.isEmpty())return; - var teamServer = optServer.get(); - var logFile = teamServer.logFile(); - String content; - try { - content = Files.readString(logFile); - } catch (IOException e) { - content = ""; - } - content = content.substring(Math.max(content.length() - 1950, 0)); - event.reply("```log%n%s%n```".formatted(content)) - .addFiles(FileUpload.fromData(logFile, "latest.log")) - .queue(); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/process/LogCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/server/process/LogCommand.java new file mode 100644 index 00000000..54975729 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/server/process/LogCommand.java @@ -0,0 +1,50 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.server.process; + +import com.google.inject.Inject; +import de.chojo.gamejam.server.ServerService; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import net.dv8tion.jda.api.utils.FileUpload; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +@Bundle("locale") +@Interaction +public class LogCommand { + private final ServerService serverService; + private final CommandContextProvider commandContextProvider; + + @Inject + public LogCommand(ServerService serverService, CommandContextProvider commandContextProvider) { + this.serverService = serverService; + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "server process log") + public void onCommand(CommandEvent event) { + var team = commandContextProvider.getUserContext(event.getMember()).team(); + var dockerService = serverService.dockerService(); + + var logs = dockerService.logs(team.id()); + + var content = logs.substring(Math.max(logs.length() - 1950, 0)); + try(InputStream inputStream = new ByteArrayInputStream(logs.getBytes(StandardCharsets.UTF_8))) { + event.reply("```log%n%s%n```".formatted(content)) + .replyFiles(FileUpload.fromData(inputStream, "latest.log")) + .queue(); + } catch (IOException _) { + } + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Restart.java b/bot/src/main/java/de/chojo/gamejam/commands/server/process/Restart.java deleted file mode 100644 index 487b31b5..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Restart.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.process; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public class Restart implements SlashHandler { - private final Server server; - - public Restart(Server server) { - this.server = server; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if(optServer.isEmpty())return; - var teamServer = optServer.get(); - if (teamServer.exists()) { - teamServer.stop(true) - .thenRun(() -> event.getHook().editOriginal(context.localize("command.server.process.restart.message.restarted")).queue()); - event.reply(context.localize("command.server.process.restart.message.restarting")).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/process/RestartCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/server/process/RestartCommand.java new file mode 100644 index 00000000..17d0bce7 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/server/process/RestartCommand.java @@ -0,0 +1,45 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.server.process; + +import com.google.inject.Inject; +import de.chojo.gamejam.server.ServerService; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; + +@Bundle("locale") +@Interaction +public class RestartCommand { + + private final ServerService serverService; + private final CommandContextProvider commandContextProvider; + + @Inject + public RestartCommand(ServerService serverService, CommandContextProvider commandContextProvider) { + this.serverService = serverService; + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "server process restart") + public void onCommand(CommandEvent event) { + var team = commandContextProvider.getUserContext(event.getMember()).team(); + var dockerService = serverService.dockerService(); + + if (!dockerService.isRunning(team.id())) { + event.reply("error.servernotrunning"); + return; + } + + var teamServer = serverService.get(team); + + serverService.restartServer(teamServer); + event.reply("command.server.process.restart.message.restarting"); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Start.java b/bot/src/main/java/de/chojo/gamejam/commands/server/process/Start.java deleted file mode 100644 index 45f60ed8..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Start.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.process; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public class Start implements SlashHandler { - private final Server server; - - public Start(Server server) { - this.server = server; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if(optServer.isEmpty())return; - var teamServer = optServer.get(); - if (teamServer.exists()) { - if (teamServer.start()) { - event.reply(context.localize("command.server.process.start.message.success")).queue(); - } else { - event.reply(context.localize("command.server.process.start.message.fail")).queue(); - } - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/process/StartCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/server/process/StartCommand.java new file mode 100644 index 00000000..41310405 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/server/process/StartCommand.java @@ -0,0 +1,44 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.server.process; + +import com.google.inject.Inject; +import de.chojo.gamejam.server.ServerService; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; + +@Bundle("locale") +@Interaction +public class StartCommand { + private final ServerService serverService; + private final CommandContextProvider commandContextProvider; + + @Inject + public StartCommand(ServerService serverService, CommandContextProvider commandContextProvider) { + this.serverService = serverService; + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "server process start") + public void onCommand(CommandEvent event) { + var team = commandContextProvider.getUserContext(event.getMember()).team(); + + var dockerService = serverService.dockerService(); + if (!dockerService.exists(team.id())) { + event.reply("command.server.process.start.message.fail"); + return; + } + + var teamServer = serverService.get(team); + + serverService.startServer(teamServer); + event.reply("command.server.process.start.message.success"); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Status.java b/bot/src/main/java/de/chojo/gamejam/commands/server/process/Status.java deleted file mode 100644 index 23cedd31..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Status.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.process; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public class Status implements SlashHandler { - private final Server server; - - public Status(Server server) { - this.server = server; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if (optServer.isEmpty()) return; - - event.replyEmbeds(optServer.get().detailStatus(context).join()).queue(); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/process/StatusCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/server/process/StatusCommand.java new file mode 100644 index 00000000..b67babed --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/server/process/StatusCommand.java @@ -0,0 +1,38 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.server.process; + +import com.google.inject.Inject; +import de.chojo.gamejam.message.EmbedHelper; +import de.chojo.gamejam.server.ServerService; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; + +@Bundle("locale") +@Interaction +public class StatusCommand { + + private final ServerService serverService; + private final CommandContextProvider commandContextProvider; + + @Inject + public StatusCommand(ServerService serverService, CommandContextProvider commandContextProvider) { + this.serverService = serverService; + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "server process status") + public void onCommand(CommandEvent event) { + var team = commandContextProvider.getUserContext(event.getMember()).team(); + + EmbedHelper.embedDetailedStatus(team, serverService, event) + .whenComplete((embed, _) -> event.reply(embed)); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Stop.java b/bot/src/main/java/de/chojo/gamejam/commands/server/process/Stop.java deleted file mode 100644 index 9e28f917..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/process/Stop.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.process; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public class Stop implements SlashHandler { - private final Server server; - - public Stop(Server server) { - this.server = server; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if(optServer.isEmpty())return; - var teamServer = optServer.get(); - if (teamServer.exists()) { - event.reply(context.localize("command.server.process.stop.message.stopping")).queue(); - teamServer.stop(false).thenRun(() -> event.getHook().editOriginal( - context.localize("command.server.process.stop.message.stopped")).queue()); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/process/StopCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/server/process/StopCommand.java new file mode 100644 index 00000000..f188a9e4 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/server/process/StopCommand.java @@ -0,0 +1,41 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.server.process; + +import com.google.inject.Inject; +import de.chojo.gamejam.server.ServerService; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; + +@Bundle("locale") +@Interaction +public class StopCommand { + private final ServerService serverService; + private final CommandContextProvider commandContextProvider; + + @Inject + public StopCommand(ServerService serverService, CommandContextProvider commandContextProvider) { + this.serverService = serverService; + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "server process stop") + public void onCommand(CommandEvent event) { + var team = commandContextProvider.getUserContext(event.getMember()).team(); + var dockerService = serverService.dockerService(); + if (!dockerService.isRunning(team.id())) { + event.reply("error.servernotrunning"); + return; + } + var teamServer = serverService.get(team); + serverService.stopServer(teamServer); + event.reply("command.server.process.stop.message.stopping"); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/system/Delete.java b/bot/src/main/java/de/chojo/gamejam/commands/server/system/Delete.java deleted file mode 100644 index e92327ac..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/system/Delete.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.system; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import org.slf4j.Logger; - -import java.io.IOException; - -import static org.slf4j.LoggerFactory.getLogger; - -public class Delete implements SlashHandler { - private static final Logger log = getLogger(Delete.class); - private final Server server; - - public Delete(Server server) { - this.server = server; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if (optServer.isEmpty()) return; - var teamServer = optServer.get(); - boolean deleted; - try { - deleted = teamServer.purge(); - } catch (IOException e) { - log.error("Could not purge server", e); - event.reply(context.localize("command.server.system.delete.message.error")).queue(); - return; - } - - if (deleted) { - event.reply(context.localize("command.server.system.delete.message.success")).queue(); - } else { - event.reply(context.localize("command.server.system.delete.message.notsetup")).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/system/DeleteCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/server/system/DeleteCommand.java new file mode 100644 index 00000000..92fa3fe4 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/server/system/DeleteCommand.java @@ -0,0 +1,46 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.server.system; + +import com.google.inject.Inject; +import de.chojo.gamejam.server.ServerService; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import org.slf4j.Logger; + +import static org.slf4j.LoggerFactory.getLogger; + +@Bundle("locale") +@Interaction +public class DeleteCommand { + private static final Logger log = getLogger(DeleteCommand.class); + + private final ServerService serverService; + private final CommandContextProvider commandContextProvider; + + @Inject + public DeleteCommand(ServerService serverService, CommandContextProvider commandContextProvider) { + this.serverService = serverService; + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "server system delete") + public void onCommand(CommandEvent event) { + var team = commandContextProvider.getUserContext(event.getMember()).team(); + + if (!serverService.dockerService().exists(team.id())) { + event.reply("command.server.system.delete.message.notsetup"); + return; + } + + serverService.dockerService().destroyServer(team.id()); + event.reply("command.server.system.delete.message.success"); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/system/Setup.java b/bot/src/main/java/de/chojo/gamejam/commands/server/system/Setup.java deleted file mode 100644 index 10fdb03b..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/system/Setup.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.system; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import org.slf4j.Logger; - -import java.io.IOException; - -import static org.slf4j.LoggerFactory.getLogger; - -public class Setup implements SlashHandler { - private static final Logger log = getLogger(Setup.class); - private final Server server; - - public Setup(Server server) { - this.server = server; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if(optServer.isEmpty())return; - var teamServer = optServer.get(); - boolean setup; - try { - setup = teamServer.setup(); - } catch (IOException e) { - log.error("Could not setup server", e); - event.reply(context.localize("command.server.system.setup.message.error")).queue(); - return; - } - - if (setup) { - event.reply(context.localize("command.server.system.setup.message.success")).queue(); - } else { - event.reply(context.localize("command.server.system.setup.message.alreadysetup")).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/system/SetupCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/server/system/SetupCommand.java new file mode 100644 index 00000000..a6351f00 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/server/system/SetupCommand.java @@ -0,0 +1,45 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.server.system; + +import com.google.inject.Inject; +import de.chojo.gamejam.server.ServerService; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import org.slf4j.Logger; + +import static org.slf4j.LoggerFactory.getLogger; + +@Bundle("locale") +@Interaction +public class SetupCommand { + private static final Logger log = getLogger(SetupCommand.class); + private final ServerService serverService; + private final CommandContextProvider commandContextProvider; + + @Inject + public SetupCommand(ServerService serverService, CommandContextProvider commandContextProvider) { + this.serverService = serverService; + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "server system setup") + public void onCommand(CommandEvent event) { + var team = commandContextProvider.getUserContext(event.getMember()).team(); + + if (serverService.dockerService().exists(team.id())) { + event.reply("command.server.system.setup.message.alreadysetup"); + return; + } + + serverService.dockerService().provisionServer(team.id()); + event.reply("command.server.system.setup.message.success"); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/upload/Plugin.java b/bot/src/main/java/de/chojo/gamejam/commands/server/upload/Plugin.java deleted file mode 100644 index 945e84ff..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/upload/Plugin.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.upload; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.gamejam.commands.server.util.ProgressDownloader; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import org.slf4j.Logger; - -import java.io.IOException; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; - -import static org.slf4j.LoggerFactory.getLogger; - -public class Plugin implements SlashHandler { - private static final Logger log = getLogger(Plugin.class); - private final Server server; - - public Plugin(Server server) { - this.server = server; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if (optServer.isEmpty()) return; - var teamServer = optServer.get(); - - var downloadUrl = event.getOption("file").getAsAttachment().getProxy().getUrl(); - - var download = ProgressDownloader.download(event, context, downloadUrl); - - if (download.isEmpty()) return; - - var pluginFile = teamServer.plugins().resolve("plugin.jar"); - try { - Files.copy(download.get(), pluginFile, StandardCopyOption.REPLACE_EXISTING); - event.getHook().editOriginal(context.localize("command.server.upload.plugin.message.success")).queue(); - } catch (IOException e) { - event.getHook().editOriginal(context.localize("command.server.upload.plugin.message.fail")).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/upload/UploadPluginData.java b/bot/src/main/java/de/chojo/gamejam/commands/server/upload/UploadPluginData.java deleted file mode 100644 index 52e9ce17..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/upload/UploadPluginData.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.upload; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.gamejam.commands.server.util.ProgressDownloader; -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.dao.guild.jams.Jam; -import de.chojo.gamejam.server.ServerService; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.util.Choice; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import org.slf4j.Logger; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -import static org.slf4j.LoggerFactory.getLogger; - -public class UploadPluginData implements SlashHandler { - private static final Logger log = getLogger(UploadPluginData.class); - private final Server server; - private final Guilds guilds; - private final ServerService serverService; - - public UploadPluginData(Server server, Guilds guilds, ServerService serverService) { - this.server = server; - this.guilds = guilds; - this.serverService = serverService; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if (optServer.isEmpty()) return; - var teamServer = optServer.get(); - - var downloadUrl = event.getOption("file").getAsAttachment().getProxy().getUrl(); - var path = event.getOption("path").getAsString(); - - if (path.contains("..")) { - event.reply(context.localize("error.invalidpath")).queue(); - return; - } - - var download = ProgressDownloader.download(event, context, downloadUrl); - - if (download.isEmpty()) return; - - var pluginFile = teamServer.plugins().resolve(path); - // No upload in plugin root - if (pluginFile.getParent().equals(teamServer.plugins())) { - event.reply(context.localize("error.invalidpath")).queue(); - return; - } - - // No updates into the update directory - if(pluginFile.equals(teamServer.plugins().resolve("update"))){ - event.reply(context.localize("error.invalidpath")).queue(); - return; - } - try { - Files.copy(download.get(), pluginFile, StandardCopyOption.REPLACE_EXISTING); - event.getHook().editOriginal(context.localize("command.server.upload.uploadplugindata.message.success")).queue(); - } catch (IOException e) { - event.getHook().editOriginal(context.localize("command.server.upload.uploadplugindata.message.fail")).queue(); - } - } - - @Override - public void onAutoComplete(CommandAutoCompleteInteractionEvent event, EventContext context) { - var optServer = guilds.guild(event).jams().activeJam() - .map(Jam::teams) - .flatMap(teams -> teams.byMember(event.getUser())) - .map(serverService::get); - if (optServer.isEmpty()) return; - var server = optServer.get(); - - var option = event.getFocusedOption(); - if ("path".equals(option.getName())) { - var plugins = server.plugins(); - var currPath = option.getValue(); - if (currPath.contains("..")) { - event.replyChoices().queue(); - return; - } - var split = currPath.split("/"); - - // Root dir - if (split.length == 1 && !currPath.endsWith("/")) { - var currValue = split[0].toLowerCase(); - var files = files(plugins).stream() - .filter(File::isDirectory) - .filter(file -> file.getName().toLowerCase().startsWith(currValue)) - .limit(25) - .map(line -> fileName(plugins, line)); - event.replyChoices(Choice.toStringChoice(files)).queue(); - return; - } - - if (!currPath.endsWith("/")) { - split = Arrays.copyOfRange(split, 0, split.length - 1); - } - - var path = plugins; - - for (var s : split) { - path = path.resolve(s); - } - - if (currPath.endsWith("/")) { - var files = files(path).stream() - .limit(25) - .map(line -> fileName(plugins, line)); - event.replyChoices(Choice.toStringChoice(files)).queue(); - return; - } - split = currPath.split("/"); - var currValue = currPath.split("/")[split.length - 1].toLowerCase(); - - var files = files(path).stream() - .filter(file -> file.getName().toLowerCase().startsWith(currValue)) - .limit(24) - .map(line -> fileName(plugins, line)) - .collect(Collectors.toCollection(ArrayList::new)); - - files.add(0, currPath); - - event.replyChoices(Choice.toStringChoice(files)).queue(); - } - } - - private List files(Path path) { - return Arrays.stream(path.toFile().listFiles()).toList(); - } - - private String fileName(Path strip, File file) { - if (file.isDirectory()) { - return (file.toPath() + "/").replace(strip.toString(), "").replaceAll("^/", ""); - } - return file.toPath().toString().replace(strip.toString(), "").replaceAll("^/", ""); - - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/server/upload/World.java b/bot/src/main/java/de/chojo/gamejam/commands/server/upload/World.java deleted file mode 100644 index 720372a3..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/server/upload/World.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.server.upload; - -import de.chojo.gamejam.commands.server.Server; -import de.chojo.gamejam.commands.server.util.ProgressDownloader; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import org.slf4j.Logger; - -import static org.slf4j.LoggerFactory.getLogger; - -public class World implements SlashHandler { - private static final Logger log = getLogger(World.class); - private final Server server; - - public World(Server server) { - this.server = server; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optServer = server.getServer(event, context); - if (optServer.isEmpty()) return; - var teamServer = optServer.get(); - - String downloadUrl = null; - - var urlOption = event.getOption("url"); - if (urlOption != null) { - downloadUrl = urlOption.getAsString(); - } - - var file = event.getOption("file"); - if (file != null) { - downloadUrl = file.getAsAttachment().getProxy().getUrl(); - } - - if (downloadUrl == null) { - event.reply(context.localize("command.server.upload.world.message.nofileorurl")).queue(); - return; - } - - var download = ProgressDownloader.download(event, context, downloadUrl); - - if (download.isEmpty()) return; - - event.getHook().editOriginal(context.localize("command.server.upload.world.message.replacing")).queue(); - if (teamServer.replaceWorld(download.get())) { - event.getHook().editOriginal(context.localize("command.server.upload.world.message.replaced")).queue(); - } else { - event.getHook().editOriginal(context.localize("command.server.upload.world.message.failed")).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/ServerAdmin.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/ServerAdmin.java deleted file mode 100644 index 6a589224..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/ServerAdmin.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.serveradmin; - -import de.chojo.gamejam.commands.serveradmin.handler.SyncVelocity; -import de.chojo.gamejam.commands.serveradmin.handler.info.Detailed; -import de.chojo.gamejam.commands.serveradmin.handler.info.Short; -import de.chojo.gamejam.commands.serveradmin.handler.refresh.RefreshAll; -import de.chojo.gamejam.commands.serveradmin.handler.refresh.RefreshTeam; -import de.chojo.gamejam.commands.serveradmin.handler.restart.RestartAll; -import de.chojo.gamejam.commands.serveradmin.handler.restart.RestartTeam; -import de.chojo.gamejam.commands.serveradmin.handler.start.StartAll; -import de.chojo.gamejam.commands.serveradmin.handler.start.StartTeam; -import de.chojo.gamejam.commands.serveradmin.handler.stop.StopAll; -import de.chojo.gamejam.commands.serveradmin.handler.stop.StopTeam; -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.server.ServerService; -import de.chojo.jdautil.interactions.slash.Argument; -import de.chojo.jdautil.interactions.slash.Group; -import de.chojo.jdautil.interactions.slash.Slash; -import de.chojo.jdautil.interactions.slash.SubCommand; -import de.chojo.jdautil.interactions.slash.provider.SlashCommand; - -public class ServerAdmin extends SlashCommand { - public ServerAdmin(Guilds guilds, ServerService serverService) { - super(Slash.of("serveradmin", "command.serveradmin.description") - .adminCommand() - .group(Group.of("start", "command.serveradmin.start.description") - .subCommand(SubCommand.of("all", "command.serveradmin.start.all.description") - .handler(new StartAll(serverService, guilds))) - .subCommand(SubCommand.of("team", "command.serveradmin.start.team.description") - .handler(new StartTeam(serverService, guilds)) - .argument(Argument.text("team", "command.serveradmin.start.team.options.team.description").asRequired().withAutoComplete()))) - .group(Group.of("restart", "command.serveradmin.restart.description") - .subCommand(SubCommand.of("all", "command.serveradmin.restart.all.description") - .handler(new RestartAll(serverService, guilds))) - .subCommand(SubCommand.of("team", "command.serveradmin.restart.team.description") - .handler(new RestartTeam(serverService, guilds)) - .argument(Argument.text("team", "command.serveradmin.restart.team.options.team.description").asRequired().withAutoComplete()))) - .group(Group.of("stop", "command.serveradmin.stop.description") - .subCommand(SubCommand.of("all", "command.serveradmin.stop.all.description") - .handler(new StopAll(serverService, guilds))) - .subCommand(SubCommand.of("team", "command.serveradmin.stop.team.description") - .handler(new StopTeam(serverService, guilds)) - .argument(Argument.text("team", "command.serveradmin.stop.team.options.team.description").asRequired().withAutoComplete()))) - .group(Group.of("refresh", "command.serveradmin.refresh.description") - .subCommand(SubCommand.of("all", "command.serveradmin.refresh.all.description") - .handler(new RefreshAll(serverService, guilds))) - .subCommand(SubCommand.of("team", "command.serveradmin.refresh.team.description") - .handler(new RefreshTeam(serverService, guilds)) - .argument(Argument.text("team", "command.serveradmin.refresh.team.options.team.description").asRequired().withAutoComplete()))) - .group(Group.of("info", "command.serveradmin.info.description") - .subCommand(SubCommand.of("short", "command.serveradmin.info.short.description") - .handler(new Short(serverService, guilds))) - .subCommand(SubCommand.of("detailed", "command.serveradmin.info.detailed.description") - .handler(new Detailed(serverService, guilds)) - .argument(Argument.text("team", "command.serveradmin.info.detailed.options.team.description").withAutoComplete()))) - .subCommand(SubCommand.of("syncvelocity", "command.serveradmin.syncvelocity.description") - .handler(new SyncVelocity(serverService))) - ); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/ServerAdminCommands.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/ServerAdminCommands.java new file mode 100644 index 00000000..db4b9831 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/ServerAdminCommands.java @@ -0,0 +1,11 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.serveradmin; + +public class ServerAdminCommands { + //TODO: Readd admin commands +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/SyncVelocity.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/SyncVelocity.java deleted file mode 100644 index 34755351..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/SyncVelocity.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.serveradmin.handler; - -import de.chojo.gamejam.server.ServerService; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public class SyncVelocity implements SlashHandler { - private final ServerService serverService; - - public SyncVelocity(ServerService serverService) { - this.serverService = serverService; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - serverService.syncVelocity(); - event.reply(context.localize("command.serveradmin.syncvelocity.message.synced")).queue(); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/info/Detailed.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/info/Detailed.java deleted file mode 100644 index a8577e04..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/info/Detailed.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.serveradmin.handler.info; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.server.ServerService; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.pagination.bag.ListPageBag; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.utils.messages.MessageEditData; - -import java.util.Collections; -import java.util.concurrent.ExecutionException; - -public class Detailed implements SlashHandler { - private final ServerService serverService; - private final Guilds guilds; - - public Detailed(ServerService serverService, Guilds guilds) { - this.serverService = serverService; - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var currentJam = guilds.guild(event).jams().getCurrentJam(); - if (currentJam.isEmpty()) { - event.reply(context.localize("error.noactivejam")).queue(); - return; - } - var jam = currentJam.get(); - - var teamArg = event.getOption("team"); - - if (teamArg != null) { - var team = jam.teams().byName(teamArg.getAsString()); - if (team.isEmpty()) { - event.reply(context.localize("error.unkownteam")).queue(); - return; - } - var teamServer = serverService.get(team.get()); - event.replyEmbeds(teamServer.detailStatus(context).join()).queue(); - return; - } - - var servers = jam.teams().teams().stream() - .map(serverService::get) - .toList(); - - context.registerPage(new ListPageBag<>(servers) { - @Override - public MessageEditData buildPage() { - try { - return currentElement().detailStatus(context).thenApply(MessageEditData::fromEmbeds).get(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throw new RuntimeException(e); - } - } - }); - } - - @Override - public void onAutoComplete(CommandAutoCompleteInteractionEvent event, EventContext context) { - var guild = guilds.guild(event); - var option = event.getFocusedOption(); - if ("team".equals(option.getName())) { - var choices = guild.jams().nextOrCurrent() - .map(jam -> jam.teams().completeTeam(option.getValue())) - .orElse(Collections.emptyList()); - event.replyChoices(choices).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/info/Short.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/info/Short.java deleted file mode 100644 index bf8a1f9b..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/info/Short.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.serveradmin.handler.info; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.server.ServerService; -import de.chojo.gamejam.server.TeamServer; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -import java.util.stream.Collectors; - -public class Short implements SlashHandler { - private final ServerService serverService; - private final Guilds guilds; - - public Short(ServerService serverService, Guilds guilds) { - this.serverService = serverService; - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var currentJam = guilds.guild(event).jams().getCurrentJam(); - if (currentJam.isEmpty()) { - event.reply(context.localize("error.noactivejam")).queue(); - return; - } - var jam = currentJam.get(); - - var servers = jam.teams().teams().stream() - .map(serverService::get) - .map(TeamServer::status) - .collect(Collectors.joining("\n")); - - event.reply(servers).queue(); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/refresh/RefreshAll.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/refresh/RefreshAll.java deleted file mode 100644 index 01078f79..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/refresh/RefreshAll.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.serveradmin.handler.refresh; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.server.ServerService; -import de.chojo.gamejam.server.TeamServer; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.Replacement; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public class RefreshAll implements SlashHandler { - private final ServerService serverService; - private final Guilds guilds; - - public RefreshAll(ServerService serverService, Guilds guilds) { - this.serverService = serverService; - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var currentJam = guilds.guild(event).jams().getCurrentJam(); - if (currentJam.isEmpty()) { - event.reply(context.localize("error.noactivejam")).queue(); - return; - } - var jam = currentJam.get(); - - var count = jam.teams().teams().stream() - .map(serverService::get) - .map(TeamServer::refresh) - .filter(v -> v) - .count(); - event.reply(context.localize("command.serveradmin.refresh.refreshall.message.refreshed", - Replacement.create("AMOUNT", count))).queue(); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/refresh/RefreshTeam.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/refresh/RefreshTeam.java deleted file mode 100644 index 520b5a26..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/refresh/RefreshTeam.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.serveradmin.handler.refresh; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.server.ServerService; -import de.chojo.gamejam.server.TeamServer; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.Replacement; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -import java.util.Collections; - -public class RefreshTeam implements SlashHandler { - private final ServerService serverService; - private final Guilds guilds; - - public RefreshTeam(ServerService serverService, Guilds guilds) { - this.serverService = serverService; - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var currentJam = guilds.guild(event).jams().getCurrentJam(); - if (currentJam.isEmpty()) { - event.reply(context.localize("error.noactivejam")).queue(); - return; - } - var jam = currentJam.get(); - - var optTeam = jam.teams().byName(event.getOption("team").getAsString()); - - if (optTeam.isEmpty()) { - event.reply(context.localize("error.unkownteam")).queue(); - return; - } - - var started = optTeam.map(serverService::get) - .map(TeamServer::refresh) - .orElse(false); - if (started) { - event.reply(context.localize("command.serveradmin.refresh.refreshteam.message.refreshed", - Replacement.create("TEAM", optTeam.get()))).queue(); - } else { - event.reply(context.localize("command.serveradmin.refresh.refreshteam.message.failed", - Replacement.create("TEAM", optTeam.get()))).queue(); - } - } - - @Override - public void onAutoComplete(CommandAutoCompleteInteractionEvent event, EventContext context) { - var guild = guilds.guild(event); - var option = event.getFocusedOption(); - if ("team".equals(option.getName())) { - var choices = guild.jams().nextOrCurrent() - .map(jam -> jam.teams().completeTeam(option.getValue())) - .orElse(Collections.emptyList()); - event.replyChoices(choices).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/restart/RestartAll.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/restart/RestartAll.java deleted file mode 100644 index 8472b89f..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/restart/RestartAll.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.serveradmin.handler.restart; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.server.ServerService; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.Replacement; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public class RestartAll implements SlashHandler { - private final ServerService serverService; - private final Guilds guilds; - - public RestartAll(ServerService serverService, Guilds guilds) { - this.serverService = serverService; - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var currentJam = guilds.guild(event).jams().getCurrentJam(); - if (currentJam.isEmpty()) { - event.reply(context.localize("error.noactivejam")).queue(); - return; - } - var jam = currentJam.get(); - - var count = jam.teams().teams().stream() - .map(serverService::get) - .filter(server -> { - var running = server.running(); - if (running) { - server.stop(true); - } - return running; - }) - .count(); - event.reply(context.localize("command.serveradmin.restart.restartall.message.restarted", - Replacement.create("AMOUNT", count))).queue(); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/restart/RestartTeam.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/restart/RestartTeam.java deleted file mode 100644 index e712f51b..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/restart/RestartTeam.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.serveradmin.handler.restart; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.server.ServerService; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.Replacement; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -import java.util.Collections; - -public class RestartTeam implements SlashHandler { - private final ServerService serverService; - private final Guilds guilds; - - public RestartTeam(ServerService serverService, Guilds guilds) { - this.serverService = serverService; - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var currentJam = guilds.guild(event).jams().getCurrentJam(); - if (currentJam.isEmpty()) { - event.reply(context.localize("error.noactivejam")).queue(); - return; - } - var jam = currentJam.get(); - - var optTeam = jam.teams().byName(event.getOption("team").getAsString()); - - if (optTeam.isEmpty()) { - event.reply(context.localize("error.unkownteam")).queue(); - return; - } - - var started = optTeam.map(serverService::get).map(server -> { - var running = server.running(); - server.restart(); - return running; - }).orElse(false); - if (started) { - event.reply(context.localize("command.serveradmin.restart.restartteam.message.restarted", - Replacement.create("TEAM", optTeam.get()))).queue(); - } else { - event.reply(context.localize("command.serveradmin.restart.restartteam.message.failed", - Replacement.create("TEAM", optTeam.get()))).queue(); - } - } - - @Override - public void onAutoComplete(CommandAutoCompleteInteractionEvent event, EventContext context) { - var guild = guilds.guild(event); - var option = event.getFocusedOption(); - if ("team".equals(option.getName())) { - var choices = guild.jams().nextOrCurrent() - .map(jam -> jam.teams().completeTeam(option.getValue())) - .orElse(Collections.emptyList()); - event.replyChoices(choices).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/start/StartAll.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/start/StartAll.java deleted file mode 100644 index 72bb07ab..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/start/StartAll.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.serveradmin.handler.start; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.server.ServerService; -import de.chojo.gamejam.server.TeamServer; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.Replacement; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public class StartAll implements SlashHandler { - private final ServerService serverService; - private final Guilds guilds; - - public StartAll(ServerService serverService, Guilds guilds) { - this.serverService = serverService; - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var currentJam = guilds.guild(event).jams().getCurrentJam(); - if (currentJam.isEmpty()) { - event.reply(context.localize("error.noactivejam")).queue(); - return; - } - var jam = currentJam.get(); - - var count = jam.teams().teams().stream() - .map(serverService::get) - .map(TeamServer::start) - .filter(v -> v) - .count(); - event.reply(context.localize("command.serveradmin.start.startall.message.started", - Replacement.create("AMOUNT", count))).queue(); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/start/StartTeam.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/start/StartTeam.java deleted file mode 100644 index 60caf872..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/start/StartTeam.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.serveradmin.handler.start; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.server.ServerService; -import de.chojo.gamejam.server.TeamServer; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.Replacement; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -import java.util.Collections; - -public class StartTeam implements SlashHandler { - private final ServerService serverService; - private final Guilds guilds; - - public StartTeam(ServerService serverService, Guilds guilds) { - this.serverService = serverService; - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var currentJam = guilds.guild(event).jams().getCurrentJam(); - if (currentJam.isEmpty()) { - event.reply(context.localize("error.noactivejam")).queue(); - return; - } - var jam = currentJam.get(); - - var optTeam = jam.teams().byName(event.getOption("team").getAsString()); - - if (optTeam.isEmpty()) { - event.reply(context.localize("error.unkownteam")).queue(); - return; - } - - var started = optTeam.map(serverService::get).map(TeamServer::start).orElse(false); - if (started) { - event.reply(context.localize("command.serveradmin.start.startteam.message.started", - Replacement.create("TEAM", optTeam.get()))).queue(); - } else { - event.reply(context.localize("command.serveradmin.start.startteam.message.failed", - Replacement.create("TEAM", optTeam.get()))).queue(); - } - } - - @Override - public void onAutoComplete(CommandAutoCompleteInteractionEvent event, EventContext context) { - var guild = guilds.guild(event); - var option = event.getFocusedOption(); - if ("team".equals(option.getName())) { - var choices = guild.jams().nextOrCurrent() - .map(jam -> jam.teams().completeTeam(option.getValue())) - .orElse(Collections.emptyList()); - event.replyChoices(choices).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/stop/StopAll.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/stop/StopAll.java deleted file mode 100644 index c6939e3c..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/stop/StopAll.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.serveradmin.handler.stop; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.server.ServerService; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.Replacement; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public class StopAll implements SlashHandler { - private final ServerService serverService; - private final Guilds guilds; - - public StopAll(ServerService serverService, Guilds guilds) { - this.serverService = serverService; - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var currentJam = guilds.guild(event).jams().getCurrentJam(); - if (currentJam.isEmpty()) { - event.reply(context.localize("error.noactivejam")).queue(); - return; - } - var jam = currentJam.get(); - - var count = jam.teams().teams().stream() - .map(serverService::get) - .map(server -> { - var running = server.running(); - server.stop(false); - return running; - }) - .filter(v -> v) - .count(); - event.reply(context.localize("command.serveradmin.stop.stopall.message.stopped", - Replacement.create("AMOUNT", count))).queue(); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/stop/StopTeam.java b/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/stop/StopTeam.java deleted file mode 100644 index f6d25f81..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/serveradmin/handler/stop/StopTeam.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.serveradmin.handler.stop; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.server.ServerService; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.Replacement; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -import java.util.Collections; - -public class StopTeam implements SlashHandler { - private final ServerService serverService; - private final Guilds guilds; - - public StopTeam(ServerService serverService, Guilds guilds) { - this.serverService = serverService; - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var currentJam = guilds.guild(event).jams().getCurrentJam(); - if (currentJam.isEmpty()) { - event.reply(context.localize("error.noactivejam")).queue(); - return; - } - var jam = currentJam.get(); - - var optTeam = jam.teams().byName(event.getOption("team").getAsString()); - - if (optTeam.isEmpty()) { - event.reply(context.localize("error.unkownteam")).queue(); - return; - } - - var started = optTeam.map(serverService::get).map(server -> { - var running = server.running(); - server.stop(); - return running; - }).orElse(false); - if (started) { - event.reply(context.localize("command.serveradmin.stop.stopteam.message.stopped", - Replacement.create("TEAM", optTeam.get()))).queue(); - } else { - event.reply(context.localize("command.serveradmin.stop.stopteam.message.failed", - Replacement.create("TEAM", optTeam.get()))).queue(); - } - } - - @Override - public void onAutoComplete(CommandAutoCompleteInteractionEvent event, EventContext context) { - var guild = guilds.guild(event); - var option = event.getFocusedOption(); - if ("team".equals(option.getName())) { - var choices = guild.jams().nextOrCurrent() - .map(jam -> jam.teams().completeTeam(option.getValue())) - .orElse(Collections.emptyList()); - event.replyChoices(choices).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/settings/Settings.java b/bot/src/main/java/de/chojo/gamejam/commands/settings/Settings.java deleted file mode 100644 index 75daa8de..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/settings/Settings.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.settings; - -import de.chojo.gamejam.commands.settings.handler.Info; -import de.chojo.gamejam.commands.settings.handler.JamRole; -import de.chojo.gamejam.commands.settings.handler.Locale; -import de.chojo.gamejam.commands.settings.handler.TeamSize; -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.Argument; -import de.chojo.jdautil.interactions.slash.Slash; -import de.chojo.jdautil.interactions.slash.SubCommand; -import de.chojo.jdautil.interactions.slash.provider.SlashCommand; - -public class Settings extends SlashCommand { - public Settings(Guilds guilds) { - super(Slash.of("settings", "command.settings.description") - .adminCommand() - .subCommand(SubCommand.of("jamrole", "command.settings.jamrole.description") - .handler(new JamRole(guilds)) - .argument(Argument.role("role", "command.settings.jamrole.options.role.description").asRequired())) - .subCommand(SubCommand.of("teamsize", "command.settings.teamsize.description") - .handler(new TeamSize(guilds)) - .argument(Argument.integer("size", "command.settings.teamsize.options.size.description").asRequired())) - .subCommand(SubCommand.of("orgarole", "command.settings.orgarole.description") - // TODO: Command gone? - .handler(null) - .argument(Argument.role("role", "command.settings.orgarole.options.role.description").asRequired())) - .subCommand(SubCommand.of("locale", "command.settings.locale.description") - .handler(new Locale(guilds)) - .argument(Argument.text("locale", "command.settings.locale.options.locale.description").asRequired())) - .subCommand(SubCommand.of("info", "command.settings.info.description") - .handler(new Info(guilds))) - ); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/settings/handler/Info.java b/bot/src/main/java/de/chojo/gamejam/commands/settings/handler/Info.java deleted file mode 100644 index de9e23f3..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/settings/handler/Info.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.settings.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.dao.JamGuild; -import de.chojo.gamejam.data.dao.guild.JamSettings; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.LocalizedEmbedBuilder; -import de.chojo.jdautil.util.MentionUtil; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public final class Info implements SlashHandler { - private final Guilds guilds; - - public Info(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - JamGuild guild = guilds.guild(event.getGuild()); - JamSettings jamSettings = guild.jamSettings(); - var settings = guild.settings(); - var embed = new LocalizedEmbedBuilder(context.guildLocalizer()) - .setTitle("command.settings.info.embed.settings") - .addField("command.settings.info.embed.jamrole", MentionUtil.role(jamSettings.jamRole()), true) - .addField("command.settings.info.embed.teamsize", String.valueOf(jamSettings.teamSize()), true) - .addField("command.settings.info.embed.orgarole", MentionUtil.role(settings.orgaRole()), true) - .build(); - event.replyEmbeds(embed).setEphemeral(true).queue(); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/settings/handler/JamRole.java b/bot/src/main/java/de/chojo/gamejam/commands/settings/handler/JamRole.java deleted file mode 100644 index 3945d971..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/settings/handler/JamRole.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.settings.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public final class JamRole implements SlashHandler { - private final Guilds guilds; - - public JamRole(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - guilds.guild(event.getGuild()).jamSettings().jamRole(event.getOption("role").getAsRole()); - event.reply(context.localize("command.settings.jamrole.message.updated")).setEphemeral(true).queue(); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/settings/handler/Locale.java b/bot/src/main/java/de/chojo/gamejam/commands/settings/handler/Locale.java deleted file mode 100644 index 0dd3c670..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/settings/handler/Locale.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.settings.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.dao.guild.Settings; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public final class Locale implements SlashHandler { - private final Guilds guilds; - - public Locale(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var settings = guilds.guild(event).settings(); - var locale = event.getOption("locale").getAsString(); - context.guildLocalizer().localizer().getLanguage(locale) - .ifPresentOrElse(language -> { - settings.locale(language.getLocale()); - event.reply(context.localize("command.settings.locale.message.updated")).setEphemeral(true).queue(); - context.interactionHub().refreshGuildCommands(event.getGuild()); - }, () -> event.reply(context.localize("command.settings.locale.message.invalid")).setEphemeral(true).queue()); - - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/settings/handler/TeamSize.java b/bot/src/main/java/de/chojo/gamejam/commands/settings/handler/TeamSize.java deleted file mode 100644 index d58a4067..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/settings/handler/TeamSize.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.settings.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public final class TeamSize implements SlashHandler { - private final Guilds guilds; - - public TeamSize(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var settings = guilds.guild(event).jamSettings(); - settings.teamSize(event.getOption("size").getAsInt()); - event.reply(context.localize("command.settings.teamsize.message.updated")).setEphemeral(true).queue(); - - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/Team.java b/bot/src/main/java/de/chojo/gamejam/commands/team/Team.java deleted file mode 100644 index b2ab25a0..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/Team.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.team; - -import de.chojo.gamejam.commands.team.handler.Api; -import de.chojo.gamejam.commands.team.handler.Create; -import de.chojo.gamejam.commands.team.handler.Disband; -import de.chojo.gamejam.commands.team.handler.Edit; -import de.chojo.gamejam.commands.team.handler.Invite; -import de.chojo.gamejam.commands.team.handler.Leave; -import de.chojo.gamejam.commands.team.handler.List; -import de.chojo.gamejam.commands.team.handler.Profile; -import de.chojo.gamejam.commands.team.handler.Promote; -import de.chojo.gamejam.commands.team.handler.Rename; -import de.chojo.gamejam.configuration.Configuration; -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.Argument; -import de.chojo.jdautil.interactions.slash.Slash; -import de.chojo.jdautil.interactions.slash.SubCommand; -import de.chojo.jdautil.interactions.slash.provider.SlashCommand; - -public class Team extends SlashCommand { - public Team(Guilds guilds, Configuration configuration) { - super(Slash.of("team", "command.team.description") - .subCommand(SubCommand.of("create", "command.team.create.description") - .handler(new Create(guilds)) - .argument(Argument.text("name", "command.team.create.options.name.description") - .asRequired())) - .subCommand(SubCommand.of("edit", "Edit Eeam information") - .handler(new Edit(guilds))) - .subCommand(SubCommand.of("invite", "command.team.invite.description") - .handler(new Invite(guilds)) - .argument(Argument.user("user", "command.team.invite.options.user.description") - .asRequired())) - .subCommand(SubCommand.of("leave", "command.team.leave.description") - .handler(new Leave(guilds))) - .subCommand(SubCommand.of("disband", "command.team.disband.description") - .handler(new Disband(guilds)) - .argument(Argument.bool("confirm", "command.team.disband.options.confirm.description") - .asRequired())) - .subCommand(SubCommand.of("promote", "command.team.promote.description") - .handler(new Promote(guilds)) - .argument(Argument.user("user", "command.team.promote.options.user.description") - .asRequired())) - .subCommand(SubCommand.of("profile", "command.team.profile.description") - .handler(new Profile(guilds)) - .argument(Argument.user("user", "command.team.profile.options.user.description")) - .argument(Argument.text("team", "command.team.profile.options.team.description") - .withAutoComplete())) - .subCommand(SubCommand.of("list", "command.team.list.description") - .handler(new List(guilds))) - .subCommand(SubCommand.of("rename", "command.team.rename.description") - .handler(new Rename(guilds)) - .argument(Argument.text("name", "command.team.rename.options.name.description") - .asRequired())) - .subCommand(SubCommand.of("api", "command.team.api.description") - .handler(new Api(configuration, guilds))) - ); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/TeamEditCommand.java b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamEditCommand.java new file mode 100644 index 00000000..fc988b74 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/commands/team/TeamEditCommand.java @@ -0,0 +1,77 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.commands.team; + +import com.google.inject.Inject; +import de.chojo.gamejam.server.CommandContextProvider; +import io.github.kaktushose.jdac.annotations.i18n.Bundle; +import io.github.kaktushose.jdac.annotations.interactions.Command; +import io.github.kaktushose.jdac.annotations.interactions.Interaction; +import io.github.kaktushose.jdac.annotations.interactions.Modal; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import io.github.kaktushose.jdac.dispatching.events.interactions.ModalEvent; +import net.dv8tion.jda.api.components.label.Label; +import net.dv8tion.jda.api.components.textinput.TextInput; +import net.dv8tion.jda.api.components.textinput.TextInputStyle; + +import java.util.List; + +@Bundle("locale") +@Interaction +public class TeamEditCommand { + private final CommandContextProvider commandContextProvider; + + @Inject + public TeamEditCommand(CommandContextProvider commandContextProvider) { + this.commandContextProvider = commandContextProvider; + } + + @Command(value = "team edit") + public void onCommand(CommandEvent event) { + var optJam = commandContextProvider.guilds().guild(event).jams().nextOrCurrent(); + if (optJam.isEmpty()) { + event.with().ephemeral(true).reply("error-nojamactive"); + return; + } + var optTeam = optJam.get().teams().byMember(event.getMember()); + if (optTeam.isEmpty()) { + event.with().ephemeral(true).reply("error.noteam"); + return; + } + + var meta = optTeam.get().meta(); + + event.replyModal("onModal", List.of( + Label.of("Project Description", TextInput.create("descr", TextInputStyle.PARAGRAPH) + .setValue(meta.projectDescription().isBlank() ? "None" : meta.projectDescription()) + .setMaxLength(100) + .build()), + Label.of("Project url", TextInput.create("url", TextInputStyle.SHORT) + .setValue(meta.projectUrl().isBlank() ? "none" : meta.projectUrl()) + .setMaxLength(200) + .build()) + ) + ); + } + + @Modal("Edit Profile") + public void onModal(ModalEvent event) { + var optJam = commandContextProvider.guilds().guild(event).jams().nextOrCurrent(); + if (optJam.isEmpty()) { + return; + } + var optTeam = optJam.get().teams().byMember(event.getMember()); + if (optTeam.isEmpty()) { + return; + } + + var meta = optTeam.get().meta(); + meta.projectDescription(event.value("descr").getAsString()); + meta.projectUrl(event.value("url").getAsString()); + event.reply("Updated"); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Api.java b/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Api.java deleted file mode 100644 index e84c7d63..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Api.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.team.handler; - -import de.chojo.gamejam.configuration.Configuration; -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.LocalizedEmbedBuilder; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.entities.MessageEmbed; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public class Api implements SlashHandler { - private final Configuration configuration; - private final Guilds guilds; - - public Api(Configuration configuration, Guilds guilds) { - this.configuration = configuration; - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optJam = guilds.guild(event).jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.reply(context.localize("error.nojamactive")).setEphemeral(true).queue(); - return; - } - var optTeam = optJam.get().teams().byMember(event.getMember()); - if (optTeam.isEmpty()) { - event.reply(context.localize("error.noteam")).setEphemeral(true).queue(); - return; - } - - MessageEmbed build = new LocalizedEmbedBuilder(context.guildLocalizer()) - .setTitle("Api") - .addField("Token", "`%s`".formatted(optTeam.get().meta().token()), false) - .addField("Upload", "`POST %s/api/v1/server/plugin/%s`".formatted(configuration.api().url(), optTeam.get().meta().token()), false) - .build(); - - event.replyEmbeds(build).setEphemeral(true).queue(); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Create.java b/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Create.java deleted file mode 100644 index 77b1b4ac..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Create.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.team.handler; - - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.util.Token; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.Permission; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -import java.util.Collections; -import java.util.EnumSet; - -public final class Create implements SlashHandler { - private final Guilds guilds; - - public Create(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var jamGuild = guilds.guild(event.getGuild()); - var optJam = jamGuild.jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.reply(context.localize("error.nojamactive")).setEphemeral(true).queue(); - return; - } - var jam = optJam.get(); - - if (jam.state().isVoting()) { - event.reply(context.localize("error.votingactive")).setEphemeral(true).queue(); - return; - } - - if (!jam.registrations().contains(event.getMember().getIdLong())) { - event.reply(context.localize("command.team.create.message.unregistered")).setEphemeral(true).queue(); - return; - } - var jamUser = jam.user(event.getMember()); - - var userTeam = jamUser.team(); - if (userTeam.isPresent()) { - event.reply(context.localize("command.team.create.message.alreadymember")).setEphemeral(true).queue(); - return; - } - var teamName = event.getOption("name").getAsString(); - - // TODO: Enforce constrains of length and allowed chars - - var optTeam = jam.teams().byName(event.getOption("name").getAsString()); - - if (optTeam.isPresent()) { - event.reply(context.localize("command.team.create.message.nametaken")).setEphemeral(true).queue(); - return; - } - - event.deferReply().setEphemeral(true).queue(); - - var categoryList = event.getGuild().getCategoriesByName("Team", true); - - var optCategory = categoryList.stream().filter(cat -> cat.getChannels().size() < 48).findFirst(); - // This is really hacky and I dont like it. - // All this stuff is blocking atm but in a different thread already - var category = optCategory.orElseGet(() -> event.getGuild().createCategory("Team").complete()); - - var role = event.getGuild() - .createRole() - .setPermissions(0L) - .setMentionable(false) - .setHoisted(false) - .setName(teamName) - .complete(); - - var text = event.getGuild().createTextChannel(teamName.replace(" ", "-"), category) - .addRolePermissionOverride(role.getIdLong(), EnumSet.of(Permission.VIEW_CHANNEL), Collections.emptySet()) - .addMemberPermissionOverride(event.getJDA().getSelfUser() - .getIdLong(), EnumSet.of(Permission.VIEW_CHANNEL, Permission.MANAGE_CHANNEL), Collections.emptySet()) - .addRolePermissionOverride(event.getGuild().getPublicRole() - .getIdLong(), Collections.emptySet(), EnumSet.of(Permission.VIEW_CHANNEL)) - .complete(); - - var voice = event.getGuild().createVoiceChannel(teamName, category) - .addRolePermissionOverride(role.getIdLong(), EnumSet.of(Permission.VIEW_CHANNEL), Collections.emptySet()) - .addMemberPermissionOverride(event.getJDA().getSelfUser() - .getIdLong(), EnumSet.of(Permission.VIEW_CHANNEL, Permission.MANAGE_CHANNEL), Collections.emptySet()) - .addRolePermissionOverride(event.getGuild().getPublicRole() - .getIdLong(), Collections.emptySet(), EnumSet.of(Permission.VIEW_CHANNEL)) - .complete(); - - var team = jam.teams() - .create(teamName); - var meta = team.meta(); - meta.leader(event.getMember()); - meta.textChannel(text); - meta.voiceChannel(voice); - meta.role(role); - meta.token(Token.generate(40)); - jamUser.join(team); - - event.getHook().editOriginal(context.localize("command.team.create.message.created")).queue(); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Disband.java b/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Disband.java deleted file mode 100644 index fac2a9a3..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Disband.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.team.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.dao.guild.jams.Jam; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -import java.util.Optional; - -public final class Disband implements SlashHandler { - private final Guilds guilds; - - public Disband(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - Optional optJam = guilds.guild(event).jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.reply(context.localize("error.nojamactive")).setEphemeral(true).queue(); - return; - } - var jam = optJam.get(); - - if (jam.state().isVoting()) { - event.reply(context.localize("error.votingactive")).setEphemeral(true).queue(); - return; - } - - if (!event.getOption("confirm").getAsBoolean()) { - event.reply(context.localize("error.noconfirm")).setEphemeral(true).queue(); - return; - } - - var jamTeam = jam.teams().byMember(event.getMember()); - if (jamTeam.isEmpty()) { - event.reply(context.localize("error.noteam")).setEphemeral(true).queue(); - return; - } - - var team = jamTeam.get(); - - - var members = team.member(); - for (var teamMember : members) { - teamMember.member() - .getUser() - .openPrivateChannel() - .flatMap(channel -> channel.sendMessage(context.localize("command.team.disband.message.disbanded"))) - .queue(); - } - - if (team.disband()) { - event.reply(context.localize("command.team.disband.message.disbanded")).setEphemeral(true).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Edit.java b/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Edit.java deleted file mode 100644 index 0d2a2683..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Edit.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.team.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.modals.handler.ModalHandler; -import de.chojo.jdautil.modals.handler.TextInputHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.components.textinput.TextInputStyle; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public class Edit implements SlashHandler { - private final Guilds guilds; - - public Edit(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optJam = guilds.guild(event).jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.reply(context.localize("error.nojamactive")).setEphemeral(true).queue(); - return; - } - var optTeam = optJam.get().teams().byMember(event.getMember()); - if (optTeam.isEmpty()) { - event.reply(context.localize("error.noteam")).setEphemeral(true).queue(); - return; - } - - var meta = optTeam.get().meta(); - - context.registerModal(ModalHandler.builder("Edit Profile") - .addInput(TextInputHandler.builder("descr", "Project Description", TextInputStyle.PARAGRAPH) - .withValue(meta.projectDescription().isBlank() ? "None" : meta.projectDescription()) - .withMaxLength(100) - .withHandler(mapping -> meta.projectDescription(mapping.getAsString()))) - .addInput(TextInputHandler.builder("url", "Project url", TextInputStyle.SHORT) - .withMaxLength(200) - .withValue(meta.projectUrl().isBlank() ? "none": meta.projectUrl()) - .withHandler(mapping -> meta.projectUrl(mapping.getAsString()))) - .withHandler(modalEvent -> { - modalEvent.reply("Updated").queue(); - }) - .build()); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Invite.java b/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Invite.java deleted file mode 100644 index a4404ec4..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Invite.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.team.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.dao.JamGuild; -import de.chojo.gamejam.data.dao.guild.jams.jam.teams.Team; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.LocalizationContext; -import de.chojo.jdautil.localization.util.LocalizedEmbedBuilder; -import de.chojo.jdautil.localization.util.Replacement; -import de.chojo.jdautil.menus.EntryContext; -import de.chojo.jdautil.menus.MenuAction; -import de.chojo.jdautil.menus.entries.ButtonEntry; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.components.buttons.Button; -import net.dv8tion.jda.api.components.buttons.ButtonStyle; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; - -public final class Invite implements SlashHandler { - private final Guilds guilds; - - public Invite(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - JamGuild guild = guilds.guild(event); - var optJam = guild.jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.reply(context.localize("error.nojamactive")).setEphemeral(true).queue(); - return; - } - var jam = optJam.get(); - - if (jam.state().isVoting()) { - event.reply(context.localize("error.votingactive")).setEphemeral(true).queue(); - return; - } - - var optTeam = jam.teams().byMember(event.getMember()); - if (optTeam.isEmpty()) { - event.reply(context.localize("error.noteam")).setEphemeral(true).queue(); - return; - } - - var team = optTeam.get(); - - if (!team.isLeader(event.getUser())) { - event.reply(context.localize("command.team.invite.message.noleader")).setEphemeral(true).queue(); - return; - } - - var member = team.member(); - var settings = guild.jamSettings(); - - if (member.size() >= settings.teamSize()) { - event.reply(context.localize("error.maxteamsize")).setEphemeral(true).queue(); - return; - } - - var user = event.getOption("user").getAsUser(); - - if (!jam.registrations().contains(user.getIdLong())) { - event.reply(context.localize("command.team.invite.message.notRegistered")).setEphemeral(true).queue(); - return; - } - - var currTeam = jam.teams().byMember(user); - - if (currTeam.isPresent()) { - event.reply(context.localize("command.team.invite.message.partofteam")).queue(); - return; - } - - user.openPrivateChannel().queue(channel -> { - var embed = new LocalizedEmbedBuilder(context.guildLocalizer()) - .setTitle("command.team.invite.message.invited", Replacement.create("GUILD", event.getGuild() - .getName())) - .setDescription("command.team.invite.message.invitation", - Replacement.createMention(event.getUser()), Replacement.create("TEAM", team.meta().name())) - .build(); - event.reply(context.localize("command.team.invite.message.send")).setEphemeral(true).queue(); - context.registerMenu(MenuAction.forChannel(embed, channel) - .addComponent(ButtonEntry.of(Button.of(ButtonStyle.SUCCESS, "accept", "command.team.invite.message.accept"), - button -> accept(button, event.getGuild().getIdLong(), - team, user.getIdLong(), context.guildLocalizer()))) - .build()); - }); - } - - private void accept(EntryContext button, long guildId, Team team, long userId, LocalizationContext localizer) { - var members = team.member(); - var interaction = button.event(); - interaction.deferReply().queue(); - var manager = interaction.getJDA().getShardManager(); - var guild = manager.getGuildById(guildId); - var user = manager.retrieveUserById(userId).complete(); - var member = guild.retrieveMember(user).complete(); - var jamGuild = guilds.guild(guild); - var settings = jamGuild.jamSettings(); - - if (members.size() >= settings.teamSize()) { - interaction.getHook().editOriginal(localizer.localize("error.maxteamsize")).queue(); - return; - } - var jam = jamGuild.jams().nextOrCurrent(); - if (jam.isEmpty()) { - interaction.getHook().editOriginal(localizer.localize("command.team.invite.gameJamOver")).queue(); - return; - } - - var currTeam = jam.get().teams().byMember(user); - - if (currTeam.isPresent()) { - interaction.getHook().editOriginal(localizer.localize("command.team.invite.alreadyMember")).queue(); - return; - } - - jam.get().user(member).join(team); - team.meta().role().ifPresent(role -> guild.addRoleToMember(member, role)); - interaction.getHook().editOriginal(localizer.localize("command.team.invite.joined")).queue(); - team.meta().textChannel().ifPresent(channel -> { - channel.sendMessage(localizer.localize("command.team.invite.joinedBroadcast", Replacement.createMention(member))) - .queue(); - }); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Leave.java b/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Leave.java deleted file mode 100644 index e66e873c..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Leave.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.team.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.dao.JamGuild; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.Replacement; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public final class Leave implements SlashHandler { - private final Guilds guilds; - - public Leave(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var jamGuild = guilds.guild(event); - var optJam = jamGuild.jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.reply(context.localize("error.nojamactive")).setEphemeral(true).queue(); - return; - } - var jam = optJam.get(); - - if (jam.state().isVoting()) { - event.reply(context.localize("error.votingactive")).setEphemeral(true).queue(); - return; - } - - jam.teams().byMember(event.getMember()) - .ifPresentOrElse(team -> { - if (!team.isLeader(event.getUser())) { - event.reply(context.localize("command.team.leave.message.leaderleave")).setEphemeral(true).queue(); - return; - } - team.member(event.getMember()).ifPresent(member -> { - member.leave(); - team.meta().textChannel().ifPresent(channel -> { - channel.sendMessage(context.localize("command.team.leave.leftBroadcast", - Replacement.createMention(event.getMember()))) - .queue(); - }); - event.reply(context.localize("command.team.leave.left")).setEphemeral(true).queue(); - }); - }, () -> event.reply(context.localize("error.noteam")).setEphemeral(true).queue()); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/List.java b/bot/src/main/java/de/chojo/gamejam/commands/team/handler/List.java deleted file mode 100644 index 5434df40..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/List.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.team.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.dao.JamGuild; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.pagination.bag.PrivateListPageBag; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.utils.messages.MessageEditData; - -public class List implements SlashHandler { - private final Guilds guilds; - - public List(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - JamGuild guild = guilds.guild(event); - var optJam = guild.jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.reply(context.localize("error.nojamactive")).setEphemeral(true).queue(); - return; - } - var jam = optJam.get(); - - context.registerPage(new PrivateListPageBag<>(jam.teams().teams(), event.getUser().getIdLong()) { - @Override - public MessageEditData buildPage() { - return MessageEditData.fromEmbeds(currentElement().profileEmbed(context.guildLocalizer())); - } - }, true); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Profile.java b/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Profile.java deleted file mode 100644 index 0b7797f9..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Profile.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.team.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.dao.JamGuild; -import de.chojo.gamejam.data.dao.guild.jams.jam.teams.Team; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.interactions.commands.Command; - -import java.util.Collections; - -public final class Profile implements SlashHandler { - private final Guilds guilds; - - public Profile(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var optJam = guilds.guild(event).jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.reply(context.localize("error.nojamactive")).setEphemeral(true).queue(); - return; - } - var jam = optJam.get(); - var teams = jam.teams(); - - if (event.getOption("user") != null) { - teams.byMember(event.getOption("user").getAsMember()) - .ifPresentOrElse(team -> sendProfile(event, team, context), - () -> event.reply(context.localize("command.team.profile.message.nouserteam")) - .setEphemeral(true) - .queue()); - return; - } - if (event.getOption("team") != null) { - teams.byName(event.getOption("team").getAsString()) - .ifPresentOrElse(team -> sendProfile(event, team, context), - () -> event.reply(context.localize("error.unkownteam")).setEphemeral(true).queue()); - return; - } - teams.byMember(event.getMember()) - .ifPresentOrElse(team -> sendProfile(event, team, context), - () -> event.reply(context.localize("error.noteam")).setEphemeral(true).queue()); - } - - private void sendProfile(SlashCommandInteractionEvent event, Team team, EventContext context) { - event.replyEmbeds(team.profileEmbed(context.guildLocalizer())).setEphemeral(true).queue(); - } - - @Override - public void onAutoComplete(CommandAutoCompleteInteractionEvent event, EventContext context) { - var guild = guilds.guild(event); - var option = event.getFocusedOption(); - if ("team".equals(option.getName())) { - var choices = guild.jams().nextOrCurrent() - .map(jam -> jam.teams().completeTeam(option.getValue())) - .orElse(Collections.emptyList()); - event.replyChoices(choices).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Promote.java b/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Promote.java deleted file mode 100644 index 19840efd..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Promote.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.team.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.dao.JamGuild; -import de.chojo.gamejam.data.dao.guild.jams.jam.teams.Team; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -import java.util.Optional; - -public class Promote implements SlashHandler { - private final Guilds guilds; - - public Promote(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - JamGuild guild = guilds.guild(event); - var optJam = guild.jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.reply(context.localize("error.nojamactive")).setEphemeral(true).queue(); - return; - } - var jam = optJam.get(); - - var user = event.getOption("user").getAsMember(); - jam.teams().byMember(user) - .ifPresentOrElse( - targetTeam -> { - if (!targetTeam.isLeader(event.getUser())) { - event.reply(context.localize("error.noleader")).setEphemeral(true).queue(); - return; - } - - targetTeam.meta().leader(user); - event.reply(context.localize("command.team.promote.message.done")).setEphemeral(true).queue(); - }, - () -> event.reply(context.localize("error.noteam")).setEphemeral(true).queue()); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Rename.java b/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Rename.java deleted file mode 100644 index d31a6bcb..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/team/handler/Rename.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.team.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.dao.JamGuild; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public class Rename implements SlashHandler { - private final Guilds guilds; - - public Rename(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - JamGuild guild = guilds.guild(event); - var optJam = guild.jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.reply(context.localize("error.nojamactive")).setEphemeral(true).queue(); - return; - } - var jam = optJam.get(); - - jam.teams().byName(event.getOption("name").getAsString()) - .ifPresentOrElse( - team -> event.reply(context.localize("command.team.create.message.nametaken")).setEphemeral(true) - .queue(), - () -> { - var optCurrTeam = jam.teams().byMember(event.getUser()); - - if (optCurrTeam.isEmpty()) { - event.reply(context.localize("error.noteam")).setEphemeral(true).queue(); - return; - } - - var team = optCurrTeam.get(); - - if (!team.isLeader(event.getUser())) { - event.reply(context.localize("error.noleader")).setEphemeral(true).queue(); - return; - } - - team.meta().rename(event.getOption("name").getAsString()); - event.reply(context.localize("command.team.rename.message.done")).setEphemeral(true).queue(); - }); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/unregister/Unregister.java b/bot/src/main/java/de/chojo/gamejam/commands/unregister/Unregister.java deleted file mode 100644 index b176e477..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/unregister/Unregister.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.unregister; - -import de.chojo.gamejam.commands.unregister.handler.Handler; -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.Slash; -import de.chojo.jdautil.interactions.slash.provider.SlashCommand; - -public class Unregister extends SlashCommand { - public Unregister(Guilds guilds) { - super(Slash.of("unregister", "command.unregister.description") - .command(new Handler(guilds))); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/unregister/handler/Handler.java b/bot/src/main/java/de/chojo/gamejam/commands/unregister/handler/Handler.java deleted file mode 100644 index a8969d3d..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/unregister/handler/Handler.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.unregister.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -public class Handler implements SlashHandler { - private final Guilds guilds; - - public Handler(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var guild = guilds.guild(event); - var optJam = guild.jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.reply(context.localize("error.noupcomingjam")) - .setEphemeral(true) - .queue(); - return; - } - - var jam = optJam.get(); - - if (!jam.registrations().contains(event.getMember().getIdLong())) { - event.reply(context.localize("command.unregister.message.notregistered")).setEphemeral(true).queue(); - return; - } - - jam.teams().byMember(event.getMember()) - .ifPresentOrElse( - team -> event.reply(context.localize("command.unregister.message.inteam")).queue(), - () -> { - var settings = guild.jamSettings(); - var role = event.getGuild().getRoleById(settings.jamRole()); - if (role != null) { - event.getGuild().removeRoleFromMember(event.getMember(), role).queue(); - } - event.reply(context.localize("command.unregister.message.unregistered")) - .setEphemeral(true) - .queue(); - - }); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/vote/Votes.java b/bot/src/main/java/de/chojo/gamejam/commands/vote/Votes.java deleted file mode 100644 index b52829aa..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/vote/Votes.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.vote; - -import de.chojo.gamejam.commands.vote.handler.Info; -import de.chojo.gamejam.commands.vote.handler.Ranking; -import de.chojo.gamejam.commands.vote.handler.Vote; -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.Argument; -import de.chojo.jdautil.interactions.slash.Slash; -import de.chojo.jdautil.interactions.slash.SubCommand; -import de.chojo.jdautil.interactions.slash.provider.SlashCommand; - -public class Votes extends SlashCommand { - public Votes(Guilds guilds) { - super(Slash.of("votes", "command.votes.description") - .subCommand(SubCommand.of("vote", "command.votes.vote.description") - .handler(new Vote(guilds)) - .argument(Argument.text("team", "command.votes.vote.options.team.description").asRequired() - .withAutoComplete()) - .argument(Argument.integer("points", "command.votes.vote.options.points.description").asRequired() - .withAutoComplete())) - .subCommand(SubCommand.of("info", "command.votes.info.description") - .handler(new Info(guilds))) - .subCommand(SubCommand.of("ranking", "command.votes.ranking.description") - .handler(new Ranking(guilds))) - ); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/vote/handler/Info.java b/bot/src/main/java/de/chojo/gamejam/commands/vote/handler/Info.java deleted file mode 100644 index 6e9cfa57..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/vote/handler/Info.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.vote.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.LocalizedEmbedBuilder; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; - -import java.util.stream.Collectors; - -public class Info implements SlashHandler { - private final Guilds guilds; - - public Info(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var guild = guilds.guild(event); - var optJam = guild.jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.reply(context.localize("error.nojamactive")).setEphemeral(true).queue(); - return; - } - var jam = optJam.get(); - - var voteEntries = jam.user(event.getMember()).votes(); - var given = voteEntries.stream() - .filter(e -> e.points() != 0) - .map(e -> e.team().meta().name() + ": **" + e.points() + "**") - .collect(Collectors.joining("\n")); - - var build = new LocalizedEmbedBuilder(context.guildLocalizer()) - .setTitle("command.votes.info.embed.title") - .setDescription(given) - .build(); - event.replyEmbeds(build).setEphemeral(true).queue(); - - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/vote/handler/Ranking.java b/bot/src/main/java/de/chojo/gamejam/commands/vote/handler/Ranking.java deleted file mode 100644 index edd050fc..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/vote/handler/Ranking.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.vote.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.LocalizedEmbedBuilder; -import de.chojo.jdautil.pagination.bag.ListPageBag; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.utils.messages.MessageEditData; - -public class Ranking implements SlashHandler { - private final Guilds guilds; - - public Ranking(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var guild = guilds.guild(event); - var optJam = guild.jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.reply(context.localize("error.nojamactive")).setEphemeral(true).queue(); - return; - } - - var jam = optJam.get(); - - if (jam.state().isVoting()) { - event.reply(context.localize("command.votes.ranking.message.voteactive")).setEphemeral(true).queue(); - return; - } - - var ranking = jam.votes(); - - var pageBag = new ListPageBag<>(ranking) { - @Override - public MessageEditData buildPage() { - var teamVote = currentElement(); - var embed = new LocalizedEmbedBuilder(context.guildLocalizer()) - .setTitle(teamVote.rank() + " | " + teamVote.team().meta().name()) - .addField("command.votes.ranking.embed.votes", String.valueOf(teamVote.votes()), true) - .build(); - return MessageEditData.fromEmbeds(embed); - } - }; - context.registerPage(pageBag, true); - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/commands/vote/handler/Vote.java b/bot/src/main/java/de/chojo/gamejam/commands/vote/handler/Vote.java deleted file mode 100644 index 9ec1e456..00000000 --- a/bot/src/main/java/de/chojo/gamejam/commands/vote/handler/Vote.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.gamejam.commands.vote.handler; - -import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.dao.guild.jams.jam.teams.Team; -import de.chojo.gamejam.data.dao.guild.jams.jam.user.JamUser; -import de.chojo.gamejam.data.wrapper.votes.VoteEntry; -import de.chojo.jdautil.interactions.slash.structure.handler.SlashHandler; -import de.chojo.jdautil.localization.util.Format; -import de.chojo.jdautil.localization.util.Replacement; -import de.chojo.jdautil.wrapper.EventContext; -import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.interactions.commands.Command; - -import java.util.Collections; -import java.util.stream.IntStream; - -public class Vote implements SlashHandler { - private final Guilds guilds; - - public Vote(Guilds guilds) { - this.guilds = guilds; - } - - @Override - public void onSlashCommand(SlashCommandInteractionEvent event, EventContext context) { - var guild = guilds.guild(event); - var optJam = guild.jams().nextOrCurrent(); - if (optJam.isEmpty()) { - event.reply(context.localize("error.nojamactive")).setEphemeral(true).queue(); - return; - } - - var jam = optJam.get(); - - event.deferReply(true).queue(); - if (!jam.state().isVoting()) { - event.getHook().editOriginal(context.localize("command.votes.vote.message.notactive")).queue(); - return; - } - - if (!jam.registrations().contains(event.getMember().getIdLong())) { - event.getHook().editOriginal(context.localize("error.notregistered")).queue(); - return; - } - - var teams = jam.teams(); - var teamCount = teams.teams().size(); - var optVoteTeam = teams.byName(event.getOption("team").getAsString()); - - if (optVoteTeam.isEmpty()) { - event.getHook().editOriginal(context.localize("error.unkownteam")).queue(); - return; - } - - var voteTeam = optVoteTeam.get(); - - if (voteTeam.member(event.getMember()).isPresent()) { - event.getHook().editOriginal(context.localize("command.votes.vote.message.ownteam")).queue(); - return; - } - - var user = jam.user(event.getMember()); - - var pointsGiven = user.votesGiven(); - - //TODO: Max points and max points per team are currently hardcoded. should be configurable in the future. - var points = Math.min(5, Math.max(0, event.getOption("points").getAsInt())); - - var votes = voteTeam.votes(event.getMember()); - - if (votes < points && pointsGiven + points > teamCount) { - event.getHook().editOriginal(context.localize("command.votes.vote.message.maxpointsreached", - Replacement.create("REMAINING", teamCount - pointsGiven) - .addFormatting(Format.BOLD))) - .queue(); - return; - } - - voteTeam.vote(event.getMember(), points); - - event.getHook().editOriginal(context.localize("command.votes.vote.message.done", - Replacement.create("REMAINING", teamCount - user.votesGiven()).addFormatting(Format.BOLD), - Replacement.create("POINTS", points).addFormatting(Format.BOLD), - Replacement.create("TEAM", voteTeam.meta().name()).addFormatting(Format.BOLD))) - .queue(); - } - - @Override - public void onAutoComplete(CommandAutoCompleteInteractionEvent event, EventContext context) { - var jam = guilds.guild(event).jams().nextOrCurrent(); - var option = event.getFocusedOption(); - if ("team".equals(option.getName())) { - if (jam.isEmpty()) { - event.replyChoices(Collections.emptyList()).queue(); - return; - } - var teams = jam.get().teams().teams().stream() - .filter(team -> team.matchName(option.getValue())) - .map(team -> team.meta().name()) - .map(team -> new Command.Choice(team, team)) - .toList(); - event.replyChoices(teams).queue(); - } - if ("points".equals(option.getName())) { - event.replyChoices(IntStream.range(0, 6).mapToObj(num -> new Command.Choice(String.valueOf(num), num)) - .toList()).queue(); - } - } -} diff --git a/bot/src/main/java/de/chojo/gamejam/configuration/ConfigFile.java b/bot/src/main/java/de/chojo/gamejam/configuration/ConfigFile.java index eb299c9c..a47ed142 100644 --- a/bot/src/main/java/de/chojo/gamejam/configuration/ConfigFile.java +++ b/bot/src/main/java/de/chojo/gamejam/configuration/ConfigFile.java @@ -6,12 +6,7 @@ package de.chojo.gamejam.configuration; -import de.chojo.gamejam.configuration.elements.Api; -import de.chojo.gamejam.configuration.elements.BaseSettings; -import de.chojo.gamejam.configuration.elements.Database; -import de.chojo.gamejam.configuration.elements.Plugins; -import de.chojo.gamejam.configuration.elements.ServerManagement; -import de.chojo.gamejam.configuration.elements.ServerTemplate; +import de.chojo.gamejam.configuration.elements.*; @SuppressWarnings("FieldMayBeFinal") public class ConfigFile { @@ -19,7 +14,14 @@ public class ConfigFile { private Database database = new Database(); private Api api = new Api(); private ServerManagement serverManagement = new ServerManagement(); + + private Docker docker = new Docker(); private Plugins plugins = new Plugins(); + + public void setServerTemplate(ServerTemplate serverTemplate) { + this.serverTemplate = serverTemplate; + } + private ServerTemplate serverTemplate = new ServerTemplate(); public BaseSettings baseSettings() { @@ -38,6 +40,10 @@ public ServerManagement serverManagement(){ return serverManagement; } + public Docker docker() { + return docker; + } + public Plugins plugins() { return plugins; } diff --git a/bot/src/main/java/de/chojo/gamejam/configuration/Configuration.java b/bot/src/main/java/de/chojo/gamejam/configuration/Configuration.java index ab52e432..cf59197a 100644 --- a/bot/src/main/java/de/chojo/gamejam/configuration/Configuration.java +++ b/bot/src/main/java/de/chojo/gamejam/configuration/Configuration.java @@ -6,17 +6,19 @@ package de.chojo.gamejam.configuration; -import de.chojo.gamejam.configuration.elements.Api; -import de.chojo.gamejam.configuration.elements.BaseSettings; -import de.chojo.gamejam.configuration.elements.Database; -import de.chojo.gamejam.configuration.elements.Plugins; -import de.chojo.gamejam.configuration.elements.ServerManagement; -import de.chojo.gamejam.configuration.elements.ServerTemplate; -import de.chojo.jdautil.configuration.BaseConfiguration; - -public class Configuration extends BaseConfiguration { +import de.chojo.gamejam.configuration.elements.*; +import dev.chojo.ocular.Configurations; +import dev.chojo.ocular.dataformats.YamlDataFormat; +import dev.chojo.ocular.key.Key; + +import java.nio.file.Path; +import java.util.List; + +public class Configuration extends Configurations { + public static final Key MAIN = Key.builder(Path.of("config.yml"), ConfigFile::new).build(); + private Configuration() { - super(new ConfigFile()); + super(Path.of("config"), MAIN, List.of(new YamlDataFormat()), Configuration.class.getClassLoader(), null); } public static Configuration create() { @@ -27,26 +29,30 @@ public static Configuration create() { public Database database() { - return config().database(); + return main().database(); + } + + public Docker docker() { + return main().docker(); } public BaseSettings baseSettings() { - return config().baseSettings(); + return main().baseSettings(); } public Api api() { - return config().api(); + return main().api(); } public ServerManagement serverManagement() { - return config().serverManagement(); + return main().serverManagement(); } public Plugins plugins() { - return config().plugins(); + return main().plugins(); } public ServerTemplate serverTemplate() { - return config().serverTemplate(); + return main().serverTemplate(); } } diff --git a/bot/src/main/java/de/chojo/gamejam/configuration/elements/Docker.java b/bot/src/main/java/de/chojo/gamejam/configuration/elements/Docker.java new file mode 100644 index 00000000..99d85426 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/configuration/elements/Docker.java @@ -0,0 +1,68 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.configuration.elements; + +@SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"}) +public class Docker { + private String host = "unix:///var/run/docker.sock"; + private String certPath = "/home/user/.docker"; + private boolean tlsVerify = false; + private String registryUsername; + private String registryPassword; + private String registryEmail; + private String registryUrl; + private String networkName = "plugin-jam-network"; + private String teamServerImage = "plugin-jam-mc-server:latest"; + + public String getHost() { + return host; + } + + public String getCertPath() { + return certPath; + } + + public boolean isTlsVerify() { + return tlsVerify; + } + + public String host() { + return host; + } + + public String certPath() { + return certPath; + } + + public boolean tlsVerify() { + return tlsVerify; + } + + public String registryUsername() { + return registryUsername; + } + + public String registryPassword() { + return registryPassword; + } + + public String registryEmail() { + return registryEmail; + } + + public String registryUrl() { + return registryUrl; + } + + public String networkName() { + return networkName; + } + + public String teamServerImage() { + return teamServerImage; + } +} \ No newline at end of file diff --git a/bot/src/main/java/de/chojo/gamejam/configuration/elements/Plugins.java b/bot/src/main/java/de/chojo/gamejam/configuration/elements/Plugins.java index 7ea8a949..db7a00bd 100644 --- a/bot/src/main/java/de/chojo/gamejam/configuration/elements/Plugins.java +++ b/bot/src/main/java/de/chojo/gamejam/configuration/elements/Plugins.java @@ -16,11 +16,16 @@ @SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"}) public class Plugins { private String pluginDir = "plugins"; + private List defaultPlugins = List.of(); public String pluginDir() { return pluginDir; } + public List defaultPlugins() { + return defaultPlugins; + } + private Path pluginPath() { return Path.of(pluginDir); } diff --git a/bot/src/main/java/de/chojo/gamejam/data/access/Teams.java b/bot/src/main/java/de/chojo/gamejam/data/access/Teams.java index 9c4664ce..f46d6d41 100644 --- a/bot/src/main/java/de/chojo/gamejam/data/access/Teams.java +++ b/bot/src/main/java/de/chojo/gamejam/data/access/Teams.java @@ -6,7 +6,7 @@ package de.chojo.gamejam.data.access; -import de.chojo.gamejam.data.dao.guild.jams.jam.teams.Team; +import de.chojo.gamejam.data.dao.guild.jams.jamEntity.teams.Team; import net.dv8tion.jda.api.sharding.ShardManager; import org.slf4j.Logger; @@ -30,7 +30,7 @@ public Optional byId(int id) { return query(""" SELECT guild_id FROM team t - LEFT JOIN jam j ON t.jam_id = j.id + LEFT JOIN jamEntity j ON t.jam_id = j.id WHERE t.id = ? """) .single(call().bind(id)) diff --git a/bot/src/main/java/de/chojo/gamejam/data/dao/guild/Jams.java b/bot/src/main/java/de/chojo/gamejam/data/dao/guild/Jams.java index 8ad97fa7..09515c62 100644 --- a/bot/src/main/java/de/chojo/gamejam/data/dao/guild/Jams.java +++ b/bot/src/main/java/de/chojo/gamejam/data/dao/guild/Jams.java @@ -8,7 +8,7 @@ import de.chojo.gamejam.data.dao.JamGuild; import de.chojo.gamejam.data.dao.guild.jams.Jam; -import de.chojo.gamejam.data.wrapper.jam.JamCreator; +import de.chojo.gamejam.data.wrapper.jamEntity.JamCreator; import de.chojo.sadu.queries.api.configuration.QueryConfiguration; import java.util.Optional; @@ -26,7 +26,7 @@ public Jams(JamGuild jamGuild) { public void create(JamCreator jamCreator) { try (var conf = QueryConfiguration.getDefault().withSingleTransaction()) { - conf.query("INSERT INTO jam(guild_id) VALUES(?) RETURNING id") + conf.query("INSERT INTO jamEntity(guild_id) VALUES(?) RETURNING id") .single(call().bind(jamGuild.guildId())) .map(r -> r.getInt("id")) .first() @@ -43,8 +43,8 @@ INSERT INTO jam_time( .single(call().bind(id) .bind(times.registration().startTimestamp()) .bind(times.registration().endTimestamp()) - .bind(times.jam().startTimestamp()) - .bind(times.jam().endTimestamp()) + .bind(times.jamEntity().startTimestamp()) + .bind(times.jamEntity().endTimestamp()) .bind(times.zone().getId()) ) .insert(); @@ -64,7 +64,7 @@ public Optional getCurrentJam() { SELECT id FROM jam_time t - LEFT JOIN jam j ON j.id = t.jam_id + LEFT JOIN jamEntity j ON j.id = t.jam_id WHERE registration_start < NOW() AT TIME ZONE 'utc' AND t.jam_end > NOW() AT TIME ZONE 'utc' AND guild_id = ?; @@ -79,7 +79,7 @@ public Optional nextOrCurrent() { SELECT id FROM jam_time t - LEFT JOIN jam j ON j.id = t.jam_id + LEFT JOIN jamEntity j ON j.id = t.jam_id LEFT JOIN jam_state js ON j.id = js.jam_id WHERE js.active OR t.jam_end > NOW() AT TIME ZONE 'utc' AND guild_id = ? @@ -97,7 +97,7 @@ public Optional activeJam() { SELECT id FROM jam_state s - LEFT JOIN jam j ON j.id = s.jam_id + LEFT JOIN jamEntity j ON j.id = s.jam_id WHERE s.active AND guild_id = ? LIMIT 1; @@ -110,7 +110,7 @@ public Optional activeJam() { public Optional byId(int id) { return query(""" - SELECT id FROM jam WHERE guild_id = ? AND id = ? + SELECT id FROM jamEntity WHERE guild_id = ? AND id = ? """) .single(call().bind(jamGuild.guildId()).bind(id)) .map(row -> new Jam(jamGuild, id)) diff --git a/bot/src/main/java/de/chojo/gamejam/data/dao/guild/Settings.java b/bot/src/main/java/de/chojo/gamejam/data/dao/guild/Settings.java index 6a8c7d39..c133dc13 100644 --- a/bot/src/main/java/de/chojo/gamejam/data/dao/guild/Settings.java +++ b/bot/src/main/java/de/chojo/gamejam/data/dao/guild/Settings.java @@ -54,7 +54,7 @@ public Long guildId() { private boolean set(String column, Function stmt) { return query(""" - INSERT INTO settings(guild_id, %s) VALUES(?,?) + INSERT INTO settingsEntity(guild_id, %s) VALUES(?,?) ON CONFLICT(guild_id) DO UPDATE SET %s = excluded.%s diff --git a/bot/src/main/java/de/chojo/gamejam/data/dao/guild/jams/jam/JamTeams.java b/bot/src/main/java/de/chojo/gamejam/data/dao/guild/jams/jam/JamTeams.java index 3e49d6c8..03a0bec1 100644 --- a/bot/src/main/java/de/chojo/gamejam/data/dao/guild/jams/jam/JamTeams.java +++ b/bot/src/main/java/de/chojo/gamejam/data/dao/guild/jams/jam/JamTeams.java @@ -4,10 +4,10 @@ * Copyright (C) 2022 DevCord Team and Contributor */ -package de.chojo.gamejam.data.dao.guild.jams.jam; +package de.chojo.gamejam.data.dao.guild.jams.jamEntity; import de.chojo.gamejam.data.dao.guild.jams.Jam; -import de.chojo.gamejam.data.dao.guild.jams.jam.teams.Team; +import de.chojo.gamejam.data.dao.guild.jams.jamEntity.teams.Team; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.interactions.commands.Command; @@ -19,17 +19,17 @@ import static de.chojo.sadu.queries.api.query.Query.query; public class JamTeams { - private final Jam jam; + private final Jam jamEntity; - public JamTeams(Jam jam) { - this.jam = jam; + public JamTeams(Jam jamEntity) { + this.jamEntity = jamEntity; } public Team create(String name) { var teamId = query(""" INSERT INTO team(jam_id) VALUES(?) RETURNING id AS team_id; """) - .single(call().bind(jam.jamId())) + .single(call().bind(jamEntity.jamId())) .map(row -> row.getInt("team_id")) .first() .orElseThrow(); @@ -38,7 +38,7 @@ public Team create(String name) { """) .single(call().bind(teamId).bind(name)) .insert(); - return new Team(jam, teamId); + return new Team(jamEntity, teamId); } public List teams() { @@ -49,8 +49,8 @@ public List teams() { LEFT JOIN team_meta m ON t.id = m.team_id WHERE jam_id = ? """) - .single(call().bind(jam.jamId())) - .map(r -> new Team(jam, r.getInt("id"))) + .single(call().bind(jamEntity.jamId())) + .map(r -> new Team(jamEntity, r.getInt("id"))) .all(); } @@ -63,11 +63,11 @@ public Optional byMember(User member) { SELECT m.team_id FROM team_member m LEFT JOIN team t ON t.id = m.team_id - LEFT JOIN jam j ON j.id = t.jam_id + LEFT JOIN jamEntity j ON j.id = t.jam_id WHERE j.id = ? AND user_id = ? """) - .single(call().bind(jam.jamId()).bind(member.getIdLong())) + .single(call().bind(jamEntity.jamId()).bind(member.getIdLong())) .map(r -> r.getInt("team_id")) .first() .flatMap(this::byId); @@ -79,7 +79,7 @@ public Optional byName(String name) { WHERE jam_id = ? AND LOWER(m.team_name) = LOWER(?) """) - .single(call().bind(jam.jamId()).bind(name)) + .single(call().bind(jamEntity.jamId()).bind(name)) .map(r -> r.getInt("id")) .first() .flatMap(this::byId); @@ -88,7 +88,7 @@ AND LOWER(m.team_name) = LOWER(?) public Optional byId(int id) { return query("SELECT id, team_name FROM team t LEFT JOIN team_meta m ON t.id = m.team_id WHERE id = ?") .single(call().bind(id)) - .map(r -> new Team(jam, r.getInt("id"))) + .map(r -> new Team(jamEntity, r.getInt("id"))) .first(); } diff --git a/bot/src/main/java/de/chojo/gamejam/data/dao/guild/jams/jam/teams/Team.java b/bot/src/main/java/de/chojo/gamejam/data/dao/guild/jams/jam/teams/Team.java index 7097917c..1135088b 100644 --- a/bot/src/main/java/de/chojo/gamejam/data/dao/guild/jams/jam/teams/Team.java +++ b/bot/src/main/java/de/chojo/gamejam/data/dao/guild/jams/jam/teams/Team.java @@ -13,6 +13,8 @@ import de.chojo.jdautil.localization.LocalizationContext; import de.chojo.jdautil.localization.util.LocalizedEmbedBuilder; import de.chojo.jdautil.util.MentionUtil; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import io.github.kaktushose.jdac.embeds.Embed; import net.dv8tion.jda.api.entities.ISnowflake; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.MessageEmbed; @@ -43,21 +45,21 @@ public void delete() { meta.role().ifPresent(role -> role.delete().queue()); } - public MessageEmbed profileEmbed(LocalizationContext localizer) { + public Embed profileEmbed(CommandEvent commandEvent) { var member = member().stream() .map(u -> u.member().getAsMention()) .collect(Collectors.joining(", ")); var meta = meta(); - return new LocalizedEmbedBuilder(localizer) - .setTitle(meta.name()) - .setDescription(meta.projectDescription()) - .addField("command.team.profile.member", member, true) - .addField("command.team.profile.leader", MentionUtil.user(meta.leader()), true) - .addField("command.team.profile.projecturl", meta.projectUrl(), true) - .setFooter(String.format("#%s", id())) - .build(); + + var embed = commandEvent.embed("team-profile"); + embed.title(meta.name()); + embed.fields().add("command.team.profile.member", member, true); + embed.fields().add("command.team.profile.leader", MentionUtil.user(meta.leader()), true); + embed.fields().add("command.team.profile.projecturl", meta.projectUrl(), true); + + return embed; } public List member() { diff --git a/bot/src/main/java/de/chojo/gamejam/listener/InviteButtonListener.java b/bot/src/main/java/de/chojo/gamejam/listener/InviteButtonListener.java new file mode 100644 index 00000000..354f3987 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/listener/InviteButtonListener.java @@ -0,0 +1,102 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.listener; + +import de.chojo.gamejam.data.access.Guilds; +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import org.slf4j.Logger; + +import static org.slf4j.LoggerFactory.getLogger; + +public class InviteButtonListener extends ListenerAdapter { + private static final Logger log = getLogger(InviteButtonListener.class); + private static final String BUTTON_PREFIX = "invite-accept:"; + + private final Guilds guilds; + + public InviteButtonListener(Guilds guilds) { + this.guilds = guilds; + } + + @Override + public void onButtonInteraction(ButtonInteractionEvent event) { + var componentId = event.getComponentId(); + if (!componentId.startsWith(BUTTON_PREFIX)) { + return; + } + + event.deferReply().queue(); + + var parts = componentId.substring(BUTTON_PREFIX.length()).split(":"); + if (parts.length != 3) { + event.getHook().editOriginal("Invalid invite data.").queue(); + return; + } + + long guildId; + int teamId; + long userId; + try { + guildId = Long.parseLong(parts[0]); + teamId = Integer.parseInt(parts[1]); + userId = Long.parseLong(parts[2]); + } catch (NumberFormatException e) { + event.getHook().editOriginal("Invalid invite data.").queue(); + return; + } + + var manager = event.getJDA().getShardManager(); + var guild = manager.getGuildById(guildId); + if (guild == null) { + event.getHook().editOriginal("Guild not found.").queue(); + return; + } + + var user = manager.retrieveUserById(userId).complete(); + var member = guild.retrieveMember(user).complete(); + var jamGuild = guilds.guild(guild); + var settings = jamGuild.jamSettings(); + + var optJam = jamGuild.jams().nextOrCurrent(); + if (optJam.isEmpty()) { + event.getHook().editOriginal("The game jam is over.").queue(); + return; + } + + var jam = optJam.get(); + var optTeam = jam.teams().byId(teamId); + if (optTeam.isEmpty()) { + event.getHook().editOriginal("Team not found.").queue(); + return; + } + + var team = optTeam.get(); + var members = team.member(); + + if (members.size() >= settings.teamSize()) { + event.getHook().editOriginal("The team is already full.").queue(); + return; + } + + var currTeam = jam.teams().byMember(user); + if (currTeam.isPresent()) { + event.getHook().editOriginal("You are already part of a team.").queue(); + return; + } + + jam.user(member).join(team); + team.meta().role().ifPresent(role -> guild.addRoleToMember(member, role).queue()); + event.getHook().editOriginal("You have joined the team!").queue(); + team.meta().textChannel().ifPresent(channel -> + channel.sendMessage(member.getAsMention() + " has joined the team!").queue() + ); + + // Disable the button after use + event.getMessage().editMessageComponents().queue(); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/message/EmbedHelper.java b/bot/src/main/java/de/chojo/gamejam/message/EmbedHelper.java new file mode 100644 index 00000000..df6cb3cf --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/message/EmbedHelper.java @@ -0,0 +1,48 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.message; + +import de.chojo.gamejam.data.dao.guild.jams.jam.teams.Team; +import de.chojo.gamejam.server.ServerService; +import de.chojo.gamejam.server.ServerStatus; +import io.github.kaktushose.jdac.dispatching.events.interactions.CommandEvent; +import net.dv8tion.jda.api.entities.MessageEmbed; + +import java.util.concurrent.CompletableFuture; + +public class EmbedHelper { + + public static CompletableFuture embedDetailedStatus(Team team, ServerService serverService, CommandEvent event) { + return CompletableFuture.supplyAsync(() -> { + var serverStatus = serverService.getServerStatus(team.id()); + + var embed = event.embed("status"); + embed.title("%s #%d | %s".formatted(serverStatus.emoji(), team.id(), team.meta().name())); + + if (serverStatus == ServerStatus.VOID) { + embed.description("teamserver.message.detailstatus.nonexisting.description"); + } else { + if (serverStatus == ServerStatus.RUNNING) { + embed.description("teamserver.message.detailstatus.existing.description"); + + var serverStats = serverService.serverHttpService().fetchServerStats(serverService.dockerService().containerName(team.id())); + serverStats.ifPresent(stats -> { + var memory = stats.memory(); + embed.fields().add("word.memory", "$word.used$ %d%n$word.total$: %d%n$word.max$: %d".formatted(memory.usedMb(), memory.totalMb(), memory.maxMb()), true); + embed.fields().add("word.tps", "1 $word.min$: %.2f%n5 $word.min$: %.2f%n 15 $word.min$: %.2f%n$word.averageticktime$ %.2f".formatted( + stats.tps()[0], stats.tps()[1], stats.tps()[2], stats.averageTickTime()), true); + embed.fields().add("word.players", String.valueOf(stats.onlinePlayers()), true); + embed.fields().add("word.system", "$word.activethreads$: %d".formatted(stats.activeThreads()), true); + }); + } else { + embed.description("word.serversetup"); + } + } + return embed.build(); + }); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/server/CommandContextProvider.java b/bot/src/main/java/de/chojo/gamejam/server/CommandContextProvider.java new file mode 100644 index 00000000..96493a52 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/server/CommandContextProvider.java @@ -0,0 +1,35 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.server; + +import de.chojo.gamejam.data.access.Guilds; +import net.dv8tion.jda.api.entities.Member; + +public class CommandContextProvider { + private final Guilds guilds; + + public CommandContextProvider(Guilds guilds) { + this.guilds = guilds; + } + + public UserContext getUserContext(Member member) { + var jamOpt = guilds.guild(member.getGuild()).jams().activeJam(); + + if (jamOpt.isEmpty()) { + return new UserContext(null); + } + + var jam = jamOpt.get(); + var team = jam.teams().byMember(member); + return team.map(UserContext::new).orElseGet(() -> new UserContext(null)); + + } + + public Guilds guilds() { + return guilds; + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/server/DockerService.java b/bot/src/main/java/de/chojo/gamejam/server/DockerService.java new file mode 100644 index 00000000..38f2206f --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/server/DockerService.java @@ -0,0 +1,217 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.server; + +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.model.*; +import com.github.dockerjava.core.DefaultDockerClientConfig; +import com.github.dockerjava.core.DockerClientConfig; +import com.github.dockerjava.core.DockerClientImpl; +import com.github.dockerjava.api.async.ResultCallback; +import com.github.dockerjava.httpclient5.ApacheDockerHttpClient; +import com.github.dockerjava.transport.DockerHttpClient; +import de.chojo.gamejam.configuration.elements.Docker; +import de.chojo.gamejam.configuration.elements.Plugins; +import org.slf4j.Logger; + +import java.io.IOException; +import java.nio.file.Path; +import java.time.Duration; +import java.util.List; +import java.util.Optional; + +import static org.slf4j.LoggerFactory.getLogger; + +public class DockerService { + private final Docker dockerConfig; + private final DockerClientConfig dockerClientConfig; + private DockerClient dockerClient; + private static final Logger log = getLogger(DockerService.class); + private static final String DOCKER_VOLUME_DATA_DIR = "/data"; + private final String pluginUrls; + + public DockerService(Docker dockerConfig, Plugins pluginsConfig) { + this.dockerConfig = dockerConfig; + this.pluginUrls = String.join(",", pluginsConfig.defaultPlugins()); + this.dockerClientConfig = DefaultDockerClientConfig + .createDefaultConfigBuilder() + .withDockerHost(dockerConfig.getHost()) + .withDockerCertPath(dockerConfig.getCertPath()) + .withDockerTlsVerify(dockerConfig.isTlsVerify()) + .withRegistryUsername(dockerConfig.registryUsername()) + .withRegistryPassword(dockerConfig.registryPassword()) + .withRegistryEmail(dockerConfig.registryEmail()) + .withRegistryUrl(dockerConfig.registryUrl()) + .build(); + } + + public void initDockerClient() { + DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder() + .dockerHost(dockerClientConfig.getDockerHost()) + .sslConfig(dockerClientConfig.getSSLConfig()) + .maxConnections(100) + .connectionTimeout(Duration.ofSeconds(30)) + .responseTimeout(Duration.ofSeconds(45)) + .build(); + + dockerClient = DockerClientImpl.getInstance(dockerClientConfig, httpClient); + ensureNetwork(); + } + + private void ensureNetwork() { + var networks = dockerClient.listNetworksCmd() + .withNameFilter(dockerConfig.networkName()) + .exec(); + if (networks.stream().noneMatch(n -> n.getName().equals(dockerConfig.networkName()))) { + dockerClient.createNetworkCmd() + .withName(dockerConfig.networkName()) + .withDriver("bridge") + .exec(); + log.info("Created docker network {}", dockerConfig.networkName()); + } + } + + public void shutdown() throws IOException { + dockerClient.close(); + } + + public void provisionServer(int teamId) { + log.info("Provisioning server for team {}", teamId); + dockerClient.createVolumeCmd() + .withName(volumeName(teamId)) + .exec(); + + HostConfig hostConfig = HostConfig.newHostConfig() + .withBinds(new Bind(volumeName(teamId), new Volume(DOCKER_VOLUME_DATA_DIR))); + + hostConfig.withNetworkMode(dockerConfig.networkName()); + + dockerClient.createContainerCmd(dockerConfig.teamServerImage()) + .withName(containerName(teamId)) + .withEnv("EULA=TRUE", "TYPE=PAPER", "VERSION=26.1.2", String.format("PLUGINS=%s", pluginUrls)) + .withHostConfig(hostConfig) + .exec(); + log.info("Server provisioned for team with container name {} and volume name {}", containerName(teamId), volumeName(teamId)); + } + + public void destroyServer(int teamId) { + log.info("Destroying server for team {}", teamId); + dockerClient.removeContainerCmd(containerName(teamId)).exec(); + dockerClient.removeVolumeCmd(volumeName(teamId)).exec(); + } + + public void startServer(int teamId) { + log.info("Starting server for team {}", teamId); + dockerClient.startContainerCmd(containerName(teamId)).exec(); + } + + public void stopServer(int teamId) { + log.info("Stopping server for team {}", teamId); + dockerClient.stopContainerCmd(containerName(teamId)).exec(); + } + + public void restartServer(int teamId) { + dockerClient.restartContainerCmd(containerName(teamId)).exec(); + } + + public boolean isRunning(int teamId) { + return dockerClient.listContainersCmd() + .withShowAll(true) + .withNameFilter(List.of(containerName(teamId))) + .exec() + .stream() + .anyMatch(container -> container.getState().equals("running")); + } + + public void sendCommand(int teamId, String command) { + var container = container(teamId); + if (container.isEmpty()) { + log.error("Container not found for team {}", teamId); + return; + } + var execId = dockerClient.execCreateCmd(container.get().getId()) + .withAttachStdout(true) + .withAttachStderr(true) + .withCmd("rcon-cli", command) + .exec() + .getId(); + try { + dockerClient.execStartCmd(execId) + .exec(new ResultCallback.Adapter() { + @Override + public void onNext(Frame frame) { + log.info("rcon-cli response for team {}: {}", teamId, new String(frame.getPayload())); + } + }) + .awaitCompletion(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error("Interrupted while sending command to team {}", teamId, e); + } + } + public boolean exists(int teamId) { + return dockerClient.listContainersCmd() + .withShowAll(true) + .withNameFilter(List.of(containerName(teamId))) + .exec() + .stream() + .findAny() + .isPresent(); + } + + public Optional container(int teamId) { + return dockerClient.listContainersCmd() + .withShowAll(true) + .withNameFilter(List.of(containerName(teamId))) + .exec() + .stream() + .findFirst(); + } + + public String logs(int teamId) { + var callback = new ResultCallback.Adapter() { + private final StringBuilder logs = new StringBuilder(); + + @Override + public void onNext(Frame frame) { + logs.append(new String(frame.getPayload())); + } + + public String getLogs() { + return logs.toString(); + } + }; + + try { + dockerClient.logContainerCmd(containerName(teamId)) + .withStdOut(true) + .withStdErr(true) + .exec(callback) + .awaitCompletion(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error("Interrupted while retrieving logs for team {}", teamId, e); + } + + return callback.getLogs(); + } + + public void copyArchiveToContainer(int teamId, Path source, Path destination) { + dockerClient.copyArchiveToContainerCmd(containerName(teamId)) + .withHostResource(source.toString()) + .withRemotePath(destination.toString()) + .exec(); + } + + private String volumeName(int teamId) { + return "plugin-jam-team-" + teamId; + } + + public String containerName(int teamId) { + return "plugin-jam-team-" + teamId; + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/server/ServerHttpService.java b/bot/src/main/java/de/chojo/gamejam/server/ServerHttpService.java new file mode 100644 index 00000000..a20a91ef --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/server/ServerHttpService.java @@ -0,0 +1,141 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.server; + +import de.chojo.gamejam.configuration.Configuration; +import de.chojo.gamejam.util.Mapper; +import de.chojo.pluginjam.payload.Registration; +import de.chojo.pluginjam.payload.StatsPayload; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.Optional; + +public class ServerHttpService { + private final HttpClient http = HttpClient.newHttpClient(); + private static final int DEFAULT_API_PORT = 30000; + private final Logger log = LoggerFactory.getLogger(ServerHttpService.class); + private final Configuration configuration; + + public ServerHttpService(Configuration configuration) { + this.configuration = configuration; + } + + public Optional fetchServerStats(String containerName) { + var request = requestBuilder("v1/stats", containerName).GET().build(); + + try { + var response = http.send(request, HttpResponse.BodyHandlers.ofString()); + var stats = Mapper.MAPPER.readValue(response.body(), StatsPayload.class); + return Optional.of(stats); + } catch (IOException | InterruptedException e) { + log.error("Could not read stats", e); + } + return Optional.empty(); + } + + public boolean fetchOnlineStatus(String containerName) { + var request = requestBuilder("v1/online", containerName).GET().build(); + try { + var response = http.send(request, HttpResponse.BodyHandlers.ofString()); + return response.statusCode() == 200; + } catch (IOException | InterruptedException e) { + log.error("Could not read online status", e); + } + return false; + } + + public boolean configureSpectatorOverflow(String containerName, boolean enabled) { + var request = requestBuilder("v1/config/spectatoroverflow", containerName).POST(HttpRequest.BodyPublishers.ofString(Boolean.toString(enabled))).build(); + try { + var response = http.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + log.error("Could not configure spectator overflow"); + } + return response.statusCode() == 200; + } catch (IOException | InterruptedException e) { + log.error("Could not configure spectator overflow", e); + return false; + } + } + + public boolean configureMaxPlayers(String containerName, int maxPlayers) { + var request = requestBuilder("v1/config/maxplayers", containerName).POST(HttpRequest.BodyPublishers.ofString(Integer.toString(maxPlayers))).build(); + try { + var response = http.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + log.error("Could not configure max players"); + } + return response.statusCode() == 200; + } catch (IOException | InterruptedException e) { + log.error("Could not configure max players", e); + return false; + } + } + + public boolean configureMessage(String containerName, String message) { + var request = requestBuilder("v1/config/message", containerName).POST(HttpRequest.BodyPublishers.ofString(message)).build(); + try { + var response = http.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + log.error("Could not configure message"); + } + return response.statusCode() == 200; + } catch (IOException | InterruptedException e) { + log.error("Could not configure message", e); + return false; + } + } + + public void registerToVelocity(TeamServer teamServer, String containerName) { + var registration = new Registration(teamServer.team().id(), teamServer.team().meta().name(), containerName); + var velocityHost = configuration.serverManagement().getVelocityHost(); + var velocityPort = configuration.serverManagement().velocityPort(); + + try { + var body = Mapper.MAPPER.writeValueAsString(registration); + var request = HttpRequest.newBuilder(URI.create("https://%s:%d/%s".formatted(velocityHost, velocityPort, "v1/server"))) + .POST(HttpRequest.BodyPublishers.ofString(body)).build(); + var response = http.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + log.error("Could not register to velocity"); + } + } catch (IOException | InterruptedException e) { + log.error("Could not register to velocity", e); + } + } + + public void unregisterFromVelocity(TeamServer teamServer, String containerName) { + var velocityHost = configuration.serverManagement().getVelocityHost(); + var velocityPort = configuration.serverManagement().velocityPort(); + + var request = HttpRequest.newBuilder(URI.create("https://%s:%d/%s?id=%d&host=%s" + .formatted(velocityHost, velocityPort, "v1/server", teamServer.team().id(), containerName))) + .DELETE().build(); + try { + var response = http.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + log.error("Could not unregister from velocity"); + } + } catch (IOException | InterruptedException e) { + log.error("Could not unregister from velocity", e); + } + } + + public HttpRequest.Builder requestBuilder(String path, String host) { + return HttpRequest.newBuilder(URI.create("https://%s:%d/%s".formatted(host, DEFAULT_API_PORT, path))); + } + + public HttpRequest.Builder requestBuilder(String path, String host, String query) { + return HttpRequest.newBuilder(URI.create("https://%s:%d/%s?%s".formatted(host, DEFAULT_API_PORT, path, query))); + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/server/ServerService.java b/bot/src/main/java/de/chojo/gamejam/server/ServerService.java index 81a7b82f..051f0e3e 100644 --- a/bot/src/main/java/de/chojo/gamejam/server/ServerService.java +++ b/bot/src/main/java/de/chojo/gamejam/server/ServerService.java @@ -6,154 +6,86 @@ package de.chojo.gamejam.server; -import com.fasterxml.jackson.core.JsonProcessingException; import de.chojo.gamejam.configuration.Configuration; import de.chojo.gamejam.data.access.Teams; import de.chojo.gamejam.data.dao.guild.jams.jam.teams.Team; -import de.chojo.gamejam.util.Mapper; -import de.chojo.pluginjam.payload.Registration; import org.slf4j.Logger; -import java.io.IOException; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.Stack; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.stream.IntStream; import static org.slf4j.LoggerFactory.getLogger; -public class ServerService implements Runnable { +public class ServerService { private static final Logger log = getLogger(ServerService.class); private final Map server = new HashMap<>(); - private Teams teams; private final Configuration configuration; - private final Stack freePorts = new Stack<>(); + private final DockerService dockerService; + private final ServerHttpService serverHttpService; - public static ServerService create(ScheduledExecutorService executorService, Configuration configuration) { - var serverService = new ServerService(configuration); - executorService.scheduleAtFixedRate(serverService, 10, 10, TimeUnit.SECONDS); - return serverService; - } - - private ServerService(Configuration configuration) { + public ServerService(Configuration configuration) { this.configuration = configuration; - IntStream.rangeClosed(configuration.serverManagement().minPort(), configuration.serverManagement().maxPort()) - .forEach(freePorts::add); + this.serverHttpService = new ServerHttpService(configuration); + this.dockerService = new DockerService(configuration.docker(), configuration.plugins()); + this.dockerService.initDockerClient(); } public void shutdown() { server.forEach((team, teamServer) -> { - teamServer.stop(); + stopServer(teamServer); }); } - @Override - public void run() { - for (var value : server.values()) { - if (!value.running()) continue; - try { - value.serverRequests() - .ifPresent(server -> { - if (server.restart()) { - log.info("Server of team {} requested restart", value.team()); - value.restart(); - } - }); - } catch (RuntimeException e) { - log.error("Could not reach server {}", value); - } - } + public void startServer(TeamServer teamServer) { + var teamId = teamServer.team().id(); + var containerName = dockerService.containerName(teamId); + dockerService.startServer(teamId); + serverHttpService.registerToVelocity(teamServer, containerName); } - public CompletableFuture syncVelocity() { - return CompletableFuture.supplyAsync(() -> { - log.info("Syncing server with velocity instance."); - freePorts.clear(); - IntStream.rangeClosed(configuration.serverManagement().minPort(), configuration.serverManagement().maxPort()) - .forEach(freePorts::add); - var velocityPort = configuration.serverManagement().velocityPort(); - var velocityHost = configuration.serverManagement().getVelocityHost(); - var httpClient = HttpClient.newHttpClient(); - var req = HttpRequest.newBuilder(URI.create("http://%s:%d/v1/server".formatted(velocityHost, velocityPort))) - .GET() - .build(); - HttpResponse response = null; - var retries = 0; - while (retries < 5) { - try { - response = httpClient.send(req, HttpResponse.BodyHandlers.ofString()); - break; - } catch (IOException e) { - log.error("Could not reach velocity instance"); - } catch (InterruptedException e) { - log.error("Interrupted", e); - } - retries++; - try { - Thread.sleep(5000); - } catch (InterruptedException e) { - return false; - } - } - if (retries == 5) return false; - var collectionType = Mapper.MAPPER.getTypeFactory() - .constructCollectionType(List.class, Registration.class); - List registrations; - try { - registrations = Mapper.MAPPER.readValue(response.body(), collectionType); - } catch (JsonProcessingException e) { - log.error("Could not map response"); - return false; - } - - server.clear(); - for (var registration : registrations) { - var optTeam = teams.byId(registration.id()); - if (optTeam.isEmpty()) { - log.warn("Could not find a matching team for id {} of team {}", registration.id(), registration.name()); - continue; - } - var team = optTeam.get(); - log.info("Registered server for team {} with id {}", team.meta().name(), team.id()); - var teamServer = new TeamServer(this, team, configuration, registration.port(), registration.apiPort()); - teamServer.running(true); - server.put(team, teamServer); - freePorts.removeElement(registration.apiPort()); - freePorts.removeElement(registration.port()); - } - return true; - }); + public void stopServer(TeamServer teamServer) { + var teamId = teamServer.team().id(); + var containerName = dockerService.containerName(teamId); + serverHttpService.unregisterFromVelocity(teamServer, containerName); + dockerService.stopServer(teamId); + } + + public void restartServer(TeamServer teamServer) { + var teamId = teamServer.team().id(); + var containerName = dockerService.containerName(teamId); + serverHttpService.unregisterFromVelocity(teamServer, containerName); + dockerService.restartServer(teamId); + serverHttpService.registerToVelocity(teamServer, containerName); } public TeamServer get(Team team) { - return server.computeIfAbsent(team, key -> new TeamServer(this, key, configuration, nextPort(), nextPort())); + return server.computeIfAbsent(team, t -> new TeamServer(t, dockerService.containerName(t.id()))); } - private int nextPort() { - if (!freePorts.isEmpty()) { - return freePorts.pop(); + public ServerStatus getServerStatus(int teamId) { + if (dockerService.isRunning(teamId)) { + return ServerStatus.VOID; } - throw new RuntimeException("Ports exhausted"); - } - void stopped(TeamServer server, boolean restart) { - this.server.remove(server.team()); - freePorts.push(server.port()); - freePorts.push(server.apiPort()); - if (restart) { - get(server.team()).start(); + var minecraftOnline = serverHttpService.fetchOnlineStatus(dockerService.containerName(teamId)); + var containerRunning = dockerService.isRunning(teamId); + + if (minecraftOnline && containerRunning) { + return ServerStatus.RUNNING; + } + + if (containerRunning) { + return ServerStatus.STARTING_STOPPING; } + + return ServerStatus.STOPPED; + } + + public ServerHttpService serverHttpService() { + return serverHttpService; } - public void inject(Teams teams) { - this.teams = teams; + public DockerService dockerService() { + return dockerService; } } diff --git a/bot/src/main/java/de/chojo/gamejam/server/ServerStatus.java b/bot/src/main/java/de/chojo/gamejam/server/ServerStatus.java new file mode 100644 index 00000000..e8c1ca95 --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/server/ServerStatus.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.server; + +public enum ServerStatus { + STARTING_STOPPING("⏳"), + RUNNING("🟢"), + STOPPED("🔴"), + VOID("❌"); + + private final String emoji; + ServerStatus(String emoji) { + this.emoji = emoji; + } + + public String emoji() { + return emoji; + } +} diff --git a/bot/src/main/java/de/chojo/gamejam/server/TeamServer.java b/bot/src/main/java/de/chojo/gamejam/server/TeamServer.java index 2f34332e..aa6b317e 100644 --- a/bot/src/main/java/de/chojo/gamejam/server/TeamServer.java +++ b/bot/src/main/java/de/chojo/gamejam/server/TeamServer.java @@ -6,504 +6,41 @@ package de.chojo.gamejam.server; -import com.fasterxml.jackson.core.JsonProcessingException; -import de.chojo.gamejam.configuration.Configuration; import de.chojo.gamejam.data.dao.guild.jams.jam.teams.Team; -import de.chojo.gamejam.util.Mapper; -import de.chojo.jdautil.localization.util.LocalizedEmbedBuilder; -import de.chojo.jdautil.util.Futures; -import de.chojo.jdautil.wrapper.EventContext; -import de.chojo.pluginjam.payload.RequestsPayload; -import de.chojo.pluginjam.payload.StatsPayload; -import net.dv8tion.jda.api.entities.MessageEmbed; -import net.lingala.zip4j.ZipFile; import org.slf4j.Logger; -import java.io.File; -import java.io.IOException; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.nio.file.Files; -import java.nio.file.NoSuchFileException; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; import static org.slf4j.LoggerFactory.getLogger; public class TeamServer { - private static final HttpClient http = HttpClient.newHttpClient(); private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH:mm:ss.SSSS"); private static final Logger log = getLogger(TeamServer.class); - private final ServerService serverService; private final Team team; - private final Configuration configuration; - private final int port; - private final int apiPort; - private static final List AIKAR = List.of( - "-XX:+ParallelRefProcEnabled", - "-XX:MaxGCPauseMillis=200", - "-XX:+UnlockExperimentalVMOptions", - "-XX:+DisableExplicitGC", - "-XX:+AlwaysPreTouch", - "-XX:G1NewSizePercent=30", - "-XX:G1MaxNewSizePercent=40", "-XX:G1HeapRegionSize=8M", - "-XX:G1ReservePercent=20", - "-XX:G1HeapWastePercent=5", - "-XX:G1MixedGCCountTarget=4", - "-XX:InitiatingHeapOccupancyPercent=15", - "-XX:G1MixedGCLiveThresholdPercent=90", - "-XX:G1RSetUpdatingPauseTimePercent=5", - "-XX:SurvivorRatio=32", - "-XX:+PerfDisableSharedMem", - "-XX:MaxTenuringThreshold=1", - "-Dusing.aikars.flags=https://mcflags.emc.gs", - "-Daikars.new.flags=true" - ); - private boolean running; + private final String containerName; - - public TeamServer(ServerService serverService, Team team, Configuration configuration, int port, int apiPort) { - this.serverService = serverService; + public TeamServer(Team team, String containerName) { this.team = team; - this.configuration = configuration; - this.port = port; - this.apiPort = apiPort; - } - - public boolean running() { - return running; - } - - public boolean exists() { - return serverDir().toFile().exists(); - } - - /** - * Sets up the server if it doesn't exist yet. - * - * @return true if setup was successful. - * @throws IOException if data could not be written - */ - public boolean setup() throws IOException { - if (exists()) return false; - log.info("Setting up server of team {}", team); - writeTemplate(); - return true; - } - - /** - * Refresh the files of the server present in the template. This is basically a new setup without purging the data beforehand. - *

- * Files with the same name will be overridden - * - * @return true when the refresh was successful - */ - public boolean refresh() { - log.info("Refreshing template files of server {}", team); - try { - writeTemplate(); - } catch (IOException e) { - log.error("Could not refresh template", e); - return false; - } - return true; - } - - private void writeTemplate() throws IOException { - var serverDir = serverDir(); - Files.createDirectories(serverDir); - - var sourceDir = Path.of(configuration.serverTemplate().templateDir()); - var symlinks = configuration.serverTemplate().symLinks() - .stream() - .map(sourceDir::resolve) - .collect(Collectors.toSet()); - try (var files = Files.walk(sourceDir)) { - for (var sourceTarget : files.toList()) { - // skip root dir - if (sourceTarget.getNameCount() == 1) continue; - var filePath = sourceTarget.subpath(1, sourceTarget.getNameCount()); - var serverTarget = serverDir.resolve(filePath); - if (symlinks.contains(sourceTarget)) { - // Not really required since the current and new symlink are probably equal, but the creation will fail otherwise. - if (serverTarget.toFile().isFile() && serverTarget.toFile().delete()) { - log.debug("Deleted old version of file {}", serverTarget); - } - Files.createSymbolicLink(serverTarget, sourceTarget.toAbsolutePath()); - } else { - // ignore already existing directories - if (sourceTarget.toFile().isDirectory() && serverTarget.toFile().exists()) { - continue; - } - Files.copy(sourceTarget, serverTarget, StandardCopyOption.REPLACE_EXISTING); - } - } - } - } - - /** - * Delete all the server data. - * - * @return true when server was deleted. - * @throws IOException - */ - public boolean purge() throws IOException { - if (!exists()) return false; - if(running()) stop().join(); - log.info("Purging server of team {}", team); - return deleteDirectory(serverDir()); - } - - public boolean start() { - if (!exists() || running()) return false; - var server = configuration.serverManagement(); - var command = new ArrayList(); - command.add("screen"); - command.add("-dmS"); - command.add(screenName()); - command.add("java"); - command.add("-Xmx%dM".formatted(server.memory())); - command.add("-Xms%dM".formatted(server.memory())); - command.addAll(AIKAR); - command.addAll(server.parameter()); - command.add("-Dpluginjam.port=" + server.velocityPort()); - command.add("-Dpluginjam.host=" + server.getVelocityHost()); - command.add("-Dpluginjam.team.id=" + team.id()); - command.add("-Dpluginjam.team.name=" + teamName()); - command.add("-Djavalin.port=" + apiPort); - command.add("-Dcom.mojang.eula.agree=true"); - command.add("--enable-preview"); - command.add("-jar"); - command.add("server.jar"); - command.add("--max-players"); - command.add(String.valueOf(server.maxPlayers())); - command.add("--nogui"); - command.add("--port"); - command.add(String.valueOf(port)); - log.info("Starting server server of team {}", team); - try { - new ProcessBuilder() - .directory(serverDir().toFile()) - .command(command) - .redirectOutput(ProcessBuilder.Redirect.to(processLogFile("start"))) - .start(); - running = true; - } catch (IOException e) { - log.error("Could not start server", e); - return false; - } - return true; - } - - public CompletableFuture stop() { - return stop(false); - } - - public CompletableFuture restart() { - return stop(true); - } - - public CompletableFuture stop(boolean restart) { - if (!running) { - return CompletableFuture.completedFuture(null); - } - running = false; - try { - CompletableFuture future = new ProcessBuilder() - .directory(new File("").toPath().toAbsolutePath().toFile()) - .command("./wait.sh", screenName()) - .redirectOutput(ProcessBuilder.Redirect.to(processLogFile("stop"))) - .start() - .onExit() - .whenComplete(Futures.whenComplete( - exit -> { - log.info("Stopped server of team {}", team); - serverService.stopped(this, restart); - }, - err -> log.error("Could not stop server {}", team)) - ) - .thenApply(r -> null); - send("stop"); - log.info("Stopping server of team {}", team); - return future; - } catch (IOException e) { - log.error("Failed to build process builder", e); - throw new RuntimeException(e); - } - } - - public void send(String command) { - log.info("Sending command \"{}\" to server of team {}.", command, team); - try { - new ProcessBuilder() - .directory(serverDir().toFile()) - .redirectOutput(ProcessBuilder.Redirect.to(processLogFile("send"))) - .command(List.of( - "screen", - "-S", - screenName(), - "-p", - "0", - "-X", - "stuff", - "%s^M".formatted(command) - )) - .start(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private File processLogFile(String type) { - var processlog = serverDir().resolve("processlog"); - try { - Files.createDirectories(processlog); - } catch (IOException e) { - throw new RuntimeException(e); - } - return processlog - .resolve("%s_%s.log".formatted(type, FORMATTER.format(LocalDateTime.now()))) - .toFile(); - } - - private Path serverDir() { - return Path.of(configuration.serverManagement().serverDir(), String.valueOf(team().id())); + this.containerName = containerName; } private String teamName() { return team.meta().name().toLowerCase().replace(" ", "_"); } - private String screenName() { - return "team_%d_%d".formatted(team.id(), port); - } - public Team team() { return team; } - public int port() { - return port; - } - - public int apiPort() { - return apiPort; - } - @Override public String toString() { return "TeamServer{" + - "team=" + team + - ", port=" + port + - ", apiPort=" + apiPort + - '}'; - } - - public Path logFile() { - return serverDir().resolve("logs").resolve("latest.log"); - } - - public boolean replaceWorld(Path newWorld) { - log.info("Replacing world"); - var worldDir = serverDir().resolve("world"); - var tempWorld = serverDir().resolve("t_world"); - - try (var zip = new ZipFile(newWorld.toFile())) { - log.info("Extracting zip file"); - zip.extractAll(tempWorld.toAbsolutePath().toString()); - } catch (IOException e) { - log.info("Failed to extract zip file", e); - return false; - } - - var copyWorld = tempWorld; - var dirFiles = List.of(tempWorld.toFile().listFiles()); - - var dirOffstet = 3; - - if (dirFiles.size() == 1) { - log.info("No world data found"); - copyWorld = tempWorld.resolve(dirFiles.get(0).getName()); - dirOffstet++; - } - - if (!copyWorld.resolve("region").toFile().exists()) { - log.warn("No region directory."); - return false; - } - - log.info("Deleting old world"); - if (!deleteDirectory(worldDir)) { - return false; - } - - if (copyWorld.resolve("session.lock").toFile().exists()) { - log.info("Found session lock. Deleting."); - copyWorld.resolve("session.lock").toFile().delete(); - } - - log.info("Copy new world data."); - try (var files = Files.walk(copyWorld)) { - Files.createDirectories(worldDir); - for (var sourceTarget : files.toList()) { - // skip root dir - if (sourceTarget.getNameCount() == dirOffstet) continue; - var filePath = sourceTarget.subpath(dirOffstet, sourceTarget.getNameCount()); - var serverTarget = worldDir.resolve(filePath); - Files.copy(sourceTarget, serverTarget, StandardCopyOption.REPLACE_EXISTING); - } - } catch (IOException e) { - log.error("Could not copy world", e); - return false; - } - - log.info("Cleaning up temp world"); - return deleteDirectory(tempWorld); - } - - public boolean deleteDirectory(Path path) { - try (var files = Files.walk(path)) { - files.sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .forEach(File::delete); - } catch (NoSuchFileException e) { - return true; - } catch (IOException e) { - log.info("Could not delete directory", e); - return false; - } - return true; - } - - public Path plugins() { - var plugins = serverDir().resolve("plugins"); - try { - Files.createDirectories(plugins); - } catch (IOException e) { - //ignore - } - return plugins; - } - - public Path world() { - var plugins = serverDir().resolve("world"); - try { - Files.createDirectories(plugins); - } catch (IOException e) { - //ignore - } - return plugins; - } - - public CompletableFuture detailStatus(EventContext context) { - return CompletableFuture.supplyAsync(() -> { - - var builder = new LocalizedEmbedBuilder(context.guildLocalizer()) - .setTitle("%s #%d | %s".formatted(statusEmoji(), team.id(), team.meta().name())); - if (!exists()) { - builder.setDescription("teamserver.message.detailstatus.nonexisting.description"); - } else { - if (running()) { - builder.setDescription("teamserver.message.detailstatus.existing.description") - .addField("word.ports", "$word.server$: %d%n$word.api$: %d".formatted(port, apiPort), true); - stats().ifPresent(stats -> { - var memory = stats.memory(); - builder.addField("word.memory", "$word.used$ %d%n$word.total$: %d%n$word.max$: %d".formatted(memory.usedMb(), memory.totalMb(), memory.maxMb()), true) - .addField("word.tps", "1 $word.min$: %.2f%n5 $word.min$: %.2f%n 15 $word.min$: %.2f%n$word.averageticktime$ %.2f".formatted( - stats.tps()[0], stats.tps()[1], stats.tps()[2], stats.averageTickTime()), true) - .addField("word.players", String.valueOf(stats.onlinePlayers()), true) - .addField("word.system", "$word.activethreads$: %d".formatted(stats.activeThreads()), true); - }); - } else { - builder.setDescription("word.serversetup") - .addField("word.ports", "word.notrunning", true); - } - } - - return builder.build(); - }); - } - - public String status() { - var status = statusEmoji(); - var ports = ""; - if (exists() && running()) { - ports = "Server: %s Api: %s".formatted(port, apiPort); - } - return "%s %s %s".formatted(status, team, ports); - } - - public Optional stats() { - var request = requestBuilder("v1/stats") - .GET() - .build(); - HttpResponse send = null; - try { - send = http().send(request, HttpResponse.BodyHandlers.ofString()); - } catch (IOException e) { - log.error("Could not read stats", e); - return Optional.empty(); - } catch (InterruptedException e) { - log.error("Interrupted", e); - return Optional.empty(); - } - try { - return Optional.of(Mapper.MAPPER.readValue(send.body(), StatsPayload.class)); - } catch (JsonProcessingException e) { - log.error("Could not parse status", e); - return Optional.empty(); - } - } - - public HttpRequest.Builder requestBuilder(String path) { - return HttpRequest.newBuilder(URI.create("http://localhost:%d/%s".formatted(apiPort(), path))); - } - - public HttpRequest.Builder requestBuilder(String path, String query) { - return HttpRequest.newBuilder(URI.create("http://localhost:%d/%s?%s".formatted(apiPort(), path, query))); - } - - public HttpClient http() { - return http; - } - - public void running(boolean running) { - this.running = running; - } - - private String statusEmoji() { - if (exists() && running()) return "🟢"; - if (exists()) return "🟡"; - return "🔴"; + "team=" + team + "," + + "containerName='" + containerName + + '}'; } - public Optional serverRequests() { - var request = requestBuilder("v1/requests").GET().build(); - HttpResponse send; - try { - send = http().send(request, HttpResponse.BodyHandlers.ofString()); - } catch (IOException e) { - log.error("Could not connect to server"); - throw new RuntimeException(e); - } catch (InterruptedException e) { - log.error("Could not connect to server", e); - throw new RuntimeException(e); - } - try { - return Optional.of(Mapper.MAPPER.readValue(send.body(), RequestsPayload.class)); - } catch (JsonProcessingException e) { - log.error("Could not parse response", e); - throw new RuntimeException(e); - } + public String containerName() { + return containerName; } } diff --git a/bot/src/main/java/de/chojo/gamejam/server/UserContext.java b/bot/src/main/java/de/chojo/gamejam/server/UserContext.java new file mode 100644 index 00000000..36696b5c --- /dev/null +++ b/bot/src/main/java/de/chojo/gamejam/server/UserContext.java @@ -0,0 +1,13 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2022 DevCord Team and Contributor + */ + +package de.chojo.gamejam.server; + +import de.chojo.gamejam.data.dao.guild.jams.jam.teams.Team; + +public record UserContext(Team team) { + +} diff --git a/bot/src/main/resources/database/postgresql/1/setup.sql b/bot/src/main/resources/database/postgresql/1/setup.sql index 2fb393ad..07ee412f 100644 --- a/bot/src/main/resources/database/postgresql/1/setup.sql +++ b/bot/src/main/resources/database/postgresql/1/setup.sql @@ -1,7 +1,7 @@ CREATE SEQUENCE gamejam.jam_times_id_seq AS INTEGER; -CREATE TABLE IF NOT EXISTS gamejam.jam +CREATE TABLE IF NOT EXISTS gamejam.jamEntity ( id SERIAL, guild_id BIGINT NOT NULL, @@ -10,7 +10,7 @@ CREATE TABLE IF NOT EXISTS gamejam.jam ); CREATE INDEX IF NOT EXISTS jam_guild_id_index - ON gamejam.jam (guild_id); + ON gamejam.jamEntity (guild_id); CREATE TABLE IF NOT EXISTS gamejam.jam_time ( @@ -23,7 +23,7 @@ CREATE TABLE IF NOT EXISTS gamejam.jam_time CONSTRAINT jam_times_pk PRIMARY KEY (jam_id), CONSTRAINT jam_times_jam_id_fk - FOREIGN KEY (jam_id) REFERENCES gamejam.jam + FOREIGN KEY (jam_id) REFERENCES gamejam.jamEntity ON DELETE CASCADE ); @@ -36,7 +36,7 @@ CREATE TABLE IF NOT EXISTS gamejam.jam_meta CONSTRAINT jam_topic_pk PRIMARY KEY (jam_id), CONSTRAINT jam_topic_jam_id_fk - FOREIGN KEY (jam_id) REFERENCES gamejam.jam + FOREIGN KEY (jam_id) REFERENCES gamejam.jamEntity ON DELETE CASCADE ); @@ -47,7 +47,7 @@ CREATE TABLE IF NOT EXISTS gamejam.team CONSTRAINT team_pk PRIMARY KEY (id), CONSTRAINT team_jam_id_fk - FOREIGN KEY (jam_id) REFERENCES gamejam.jam + FOREIGN KEY (jam_id) REFERENCES gamejam.jamEntity ON DELETE CASCADE ); @@ -96,7 +96,7 @@ CREATE TABLE IF NOT EXISTS gamejam.jam_registrations jam_id INTEGER NOT NULL, user_id BIGINT NOT NULL, CONSTRAINT jam_registrations_jam_id_fk - FOREIGN KEY (jam_id) REFERENCES gamejam.jam + FOREIGN KEY (jam_id) REFERENCES gamejam.jamEntity ON DELETE CASCADE ); @@ -121,7 +121,7 @@ CREATE TABLE IF NOT EXISTS gamejam.jam_state CONSTRAINT jam_state_pk PRIMARY KEY (jam_id), CONSTRAINT jam_state_jam_id_fk - FOREIGN KEY (jam_id) REFERENCES gamejam.jam + FOREIGN KEY (jam_id) REFERENCES gamejam.jamEntity ON DELETE CASCADE ); @@ -131,7 +131,7 @@ CREATE TABLE IF NOT EXISTS gamejam.version patch INTEGER ); -CREATE TABLE IF NOT EXISTS gamejam.settings +CREATE TABLE IF NOT EXISTS gamejam.settingsEntity ( guild_id BIGINT NOT NULL, manager_role BIGINT, diff --git a/bot/src/main/resources/locale.properties b/bot/src/main/resources/locale.properties index f817ba11..18fd87e8 100644 --- a/bot/src/main/resources/locale.properties +++ b/bot/src/main/resources/locale.properties @@ -8,11 +8,11 @@ command.jamadmin.create.options.tagline.description= command.jamadmin.create.options.timezone.description= command.jamadmin.create.options.topic.description= command.jamadmin.description= -command.jamadmin.jam.end.options.confirm.description= -command.jamadmin.jam.description= -command.jamadmin.jam.end.description= -command.jamadmin.jam.end.message.ended= -command.jamadmin.jam.start.description= +command.jamadmin.jamEntity.end.options.confirm.description= +command.jamadmin.jamEntity.description= +command.jamadmin.jamEntity.end.description= +command.jamadmin.jamEntity.end.message.ended= +command.jamadmin.jamEntity.start.description= command.jamadmin.votes.close.description= command.jamadmin.votes.description= command.jamadmin.votes.message.closed= @@ -139,24 +139,24 @@ command.serveradmin.stop.team.description= command.serveradmin.stop.team.options.team.description= command.serveradmin.syncvelocity.description= command.serveradmin.syncvelocity.message.synced= -command.settings.description= -command.settings.info.description= -command.settings.info.embed.jamrole= -command.settings.info.embed.orgarole= -command.settings.info.embed.settings= -command.settings.info.embed.teamsize= -command.settings.jamrole.description= -command.settings.jamrole.message.updated= -command.settings.jamrole.options.role.description= -command.settings.locale.description= -command.settings.locale.options.locale.description= -command.settings.locale.message.invalid= -command.settings.locale.message.updated= -command.settings.orgarole.description= -command.settings.orgarole.options.role.description= -command.settings.teamsize.description= -command.settings.teamsize.message.updated= -command.settings.teamsize.options.size.description= +command.settingsEntity.description= +command.settingsEntity.info.description= +command.settingsEntity.info.embed.jamrole= +command.settingsEntity.info.embed.orgarole= +command.settingsEntity.info.embed.settingsEntity= +command.settingsEntity.info.embed.teamsize= +command.settingsEntity.jamrole.description= +command.settingsEntity.jamrole.message.updated= +command.settingsEntity.jamrole.options.role.description= +command.settingsEntity.locale.description= +command.settingsEntity.locale.options.locale.description= +command.settingsEntity.locale.message.invalid= +command.settingsEntity.locale.message.updated= +command.settingsEntity.orgarole.description= +command.settingsEntity.orgarole.options.role.description= +command.settingsEntity.teamsize.description= +command.settingsEntity.teamsize.message.updated= +command.settingsEntity.teamsize.options.size.description= command.start.message.activated= command.team.create.description= command.team.create.message.alreadymember= diff --git a/bot/src/main/resources/locale_de.properties b/bot/src/main/resources/locale_de.properties index 4019904d..7bbcc824 100644 --- a/bot/src/main/resources/locale_de.properties +++ b/bot/src/main/resources/locale_de.properties @@ -8,11 +8,11 @@ command.jamadmin.create.options.tagline.description=Topic-Tagline als Zusatz zum command.jamadmin.create.options.timezone.description=Die Zeitzone des Game Jams. Zum Beispiel "Europe/Berlin". command.jamadmin.create.options.topic.description=Das Thema des Game-Jam command.jamadmin.description=Verwalte Game-Jams -command.jamadmin.jam.end.options.confirm.description=Bestätige das Ende. -command.jamadmin.jam.description=Verwalte Game-Jams -command.jamadmin.jam.end.description=Beendet den aktuellen Game-Jam. Löscht alle Rollen und Kanäle -command.jamadmin.jam.end.message.ended=Game-Jam beendet. -command.jamadmin.jam.start.description=Startet den nächsten geplanten Game-Jam +command.jamadmin.jamEntity.end.options.confirm.description=Bestätige das Ende. +command.jamadmin.jamEntity.description=Verwalte Game-Jams +command.jamadmin.jamEntity.end.description=Beendet den aktuellen Game-Jam. Löscht alle Rollen und Kanäle +command.jamadmin.jamEntity.end.message.ended=Game-Jam beendet. +command.jamadmin.jamEntity.start.description=Startet den nächsten geplanten Game-Jam command.jamadmin.votes.close.description=Abstimmungen für den aktuellen Game-Jam schließen. command.jamadmin.votes.description=Verwalte Abstimmungen command.jamadmin.votes.message.closed=Abstimmungen für den aktuellen Game-Jam geschlossen. @@ -139,24 +139,24 @@ command.serveradmin.stop.team.description=Einen Teamserver stoppen command.serveradmin.stop.team.options.team.description=team command.serveradmin.syncvelocity.description=Serverstatus mit Velocity-Daten synchronisieren command.serveradmin.syncvelocity.message.synced=Server synchronisiert -command.settings.description=Verwalte die Bot-Einstellungen -command.settings.info.description=Zeige die aktuellen Einstellungen -command.settings.info.embed.jamrole=Game-Jam Rolle -command.settings.info.embed.orgarole=Organisations-Rolle -command.settings.info.embed.settings=Einstellungen -command.settings.info.embed.teamsize=Max Team Size -command.settings.jamrole.description=Stellt die Rolle ein, die den registrierten Mitgliedern zugewiesen wird. -command.settings.jamrole.message.updated=Aktualisiere die Game-Jam Rolle. -command.settings.jamrole.options.role.description=Die Rolle, die nach der Registrierung zugewiesen wird -command.settings.locale.description=Ändert die Bot-Sprache. -command.settings.locale.options.locale.description=Die neue Sprache -command.settings.locale.message.invalid=Ungültige Sprache -command.settings.locale.message.updated=Sprache aktualisiert -command.settings.orgarole.description=Definiere die Rolle des Organisationsteams -command.settings.orgarole.options.role.description=Die Rolle, die den Bot verwalten kann -command.settings.teamsize.description=Definiere die maximale Teamgröße. -command.settings.teamsize.message.updated=Aktualisierte maximale Teamgröße. -command.settings.teamsize.options.size.description=Die maximale Teamgröße. +command.settingsEntity.description=Verwalte die Bot-Einstellungen +command.settingsEntity.info.description=Zeige die aktuellen Einstellungen +command.settingsEntity.info.embed.jamrole=Game-Jam Rolle +command.settingsEntity.info.embed.orgarole=Organisations-Rolle +command.settingsEntity.info.embed.settingsEntity=Einstellungen +command.settingsEntity.info.embed.teamsize=Max Team Size +command.settingsEntity.jamrole.description=Stellt die Rolle ein, die den registrierten Mitgliedern zugewiesen wird. +command.settingsEntity.jamrole.message.updated=Aktualisiere die Game-Jam Rolle. +command.settingsEntity.jamrole.options.role.description=Die Rolle, die nach der Registrierung zugewiesen wird +command.settingsEntity.locale.description=Ändert die Bot-Sprache. +command.settingsEntity.locale.options.locale.description=Die neue Sprache +command.settingsEntity.locale.message.invalid=Ungültige Sprache +command.settingsEntity.locale.message.updated=Sprache aktualisiert +command.settingsEntity.orgarole.description=Definiere die Rolle des Organisationsteams +command.settingsEntity.orgarole.options.role.description=Die Rolle, die den Bot verwalten kann +command.settingsEntity.teamsize.description=Definiere die maximale Teamgröße. +command.settingsEntity.teamsize.message.updated=Aktualisierte maximale Teamgröße. +command.settingsEntity.teamsize.options.size.description=Die maximale Teamgröße. command.start.message.activated=Der Teamstatus wurde auf aktiv geändert. command.team.create.description=Erstelle ein Team command.team.create.message.alreadymember=Du bist bereits Teil eines Teams. Um dein eigenes Team zu erstellen, musst du es erst verlassen. diff --git a/bot/src/main/resources/locale_en_US.properties b/bot/src/main/resources/locale_en_US.properties index af0f124d..10edd147 100644 --- a/bot/src/main/resources/locale_en_US.properties +++ b/bot/src/main/resources/locale_en_US.properties @@ -1,28 +1,28 @@ -command.jamadmin.create.description=Create a new game jam +command.jamadmin.create.description=Create a new game jamEntity command.jamadmin.create.options.jamend.description=Game Jam end. $words.format$: $words.dateformat$ command.jamadmin.create.options.jamstart.description=Game Jam start. $words.format$: $words.dateformat$ command.jamadmin.create.message.created=Jam created. command.jamadmin.create.options.registerend.description=Registrations close. $words.format$: $words.dateformat$ command.jamadmin.create.options.registerstart.description=Registrations opening. $words.format$: $words.dateformat$ command.jamadmin.create.options.tagline.description=Topic tagline as an addition to the topic -command.jamadmin.create.options.timezone.description=The timezone of the game jam. "Europe/Berlin" for example. -command.jamadmin.create.options.topic.description=The topic of the game jam +command.jamadmin.create.options.timezone.description=The timezone of the game jamEntity. "Europe/Berlin" for example. +command.jamadmin.create.options.topic.description=The topic of the game jamEntity command.jamadmin.description=Manage jams -command.jamadmin.jam.end.options.confirm.description=Confirm the end -command.jamadmin.jam.description=Manage jams -command.jamadmin.jam.end.description=Ends the currently active jam. Deletes all roles and channel -command.jamadmin.jam.end.message.ended=Jam ended. -command.jamadmin.jam.start.description=Start the next scheduled jam -command.jamadmin.votes.close.description=Close votes for the current jam +command.jamadmin.jamEntity.end.options.confirm.description=Confirm the end +command.jamadmin.jamEntity.description=Manage jams +command.jamadmin.jamEntity.end.description=Ends the currently active jamEntity. Deletes all roles and channel +command.jamadmin.jamEntity.end.message.ended=Jam ended. +command.jamadmin.jamEntity.start.description=Start the next scheduled jamEntity +command.jamadmin.votes.close.description=Close votes for the current jamEntity command.jamadmin.votes.description=Manage votes -command.jamadmin.votes.message.closed=Votes closed for the current jam. -command.jamadmin.votes.message.opened=Votes for the current jam opened. -command.jamadmin.votes.open.description=Open votes for the current jam +command.jamadmin.votes.message.closed=Votes closed for the current jamEntity. +command.jamadmin.votes.message.opened=Votes for the current jamEntity opened. +command.jamadmin.votes.open.description=Open votes for the current jamEntity command.register.description=Register for an upcomming Game Jam command.register.message.alreadyregistered=You are already registered. command.register.message.notanymore=Registrations are closed. -command.register.message.notyet=You cant register for this game jam yet. You can register at %TIMESTAMP% -command.register.message.registered=You have registered yourself for the next game jam. It will start at %TIMESTAMP%. +command.register.message.notyet=You cant register for this game jamEntity yet. You can register at %TIMESTAMP% +command.register.message.registered=You have registered yourself for the next game jamEntity. It will start at %TIMESTAMP%. command.server.configure.description=Configure server command.server.configure.maxplayers.options.amount.description=Max amount of players. command.server.configure.maxplayers.description=Set the max players of this server @@ -139,24 +139,24 @@ command.serveradmin.stop.team.description=Stop a team server command.serveradmin.stop.team.options.team.description=team command.serveradmin.syncvelocity.description=Sync servers with velocity data command.serveradmin.syncvelocity.message.synced=Synced server -command.settings.description=Manage bot settings -command.settings.info.description=Show the current settings -command.settings.info.embed.jamrole=Game Jam Role -command.settings.info.embed.orgarole=Organization Role -command.settings.info.embed.settings=Settings -command.settings.info.embed.teamsize=Max Team Size -command.settings.jamrole.description=Set the role which will be assigned to registered members. -command.settings.jamrole.message.updated=Updated the game jam role. -command.settings.jamrole.options.role.description=The role to assign after registration -command.settings.locale.description=Change the bot language. -command.settings.locale.options.locale.description=The new language -command.settings.locale.message.invalid=Invalid locale -command.settings.locale.message.updated=Locale updated -command.settings.orgarole.description=Define the organisation team role -command.settings.orgarole.options.role.description=The role which can manage the bot -command.settings.teamsize.description=Define the max team size. -command.settings.teamsize.message.updated=Updated max team size. -command.settings.teamsize.options.size.description=The max team size. +command.settingsEntity.description=Manage bot settingsEntity +command.settingsEntity.info.description=Show the current settingsEntity +command.settingsEntity.info.embed.jamrole=Game Jam Role +command.settingsEntity.info.embed.orgarole=Organization Role +command.settingsEntity.info.embed.settingsEntity=Settings +command.settingsEntity.info.embed.teamsize=Max Team Size +command.settingsEntity.jamrole.description=Set the role which will be assigned to registered members. +command.settingsEntity.jamrole.message.updated=Updated the game jamEntity role. +command.settingsEntity.jamrole.options.role.description=The role to assign after registration +command.settingsEntity.locale.description=Change the bot language. +command.settingsEntity.locale.options.locale.description=The new language +command.settingsEntity.locale.message.invalid=Invalid locale +command.settingsEntity.locale.message.updated=Locale updated +command.settingsEntity.orgarole.description=Define the organisation team role +command.settingsEntity.orgarole.options.role.description=The role which can manage the bot +command.settingsEntity.teamsize.description=Define the max team size. +command.settingsEntity.teamsize.message.updated=Updated max team size. +command.settingsEntity.teamsize.options.size.description=The max team size. command.start.message.activated=Jam state changed to active. command.team.create.description=Create a team command.team.create.message.alreadymember=You are already part of a team. You need to leave first to create your own team. @@ -170,14 +170,14 @@ command.team.disband.description=Disband your team command.team.disband.message.disbanded=Your team was disbanded. command.team.invite.alreadyMember=You are already member of a team. command.team.invite.description=Invite someone to your team -command.team.invite.gameJamOver=The game jam is over +command.team.invite.gameJamOver=The game jamEntity is over command.team.invite.joined=You joined the team. command.team.invite.joinedBroadcast=%USER% joined the team. command.team.invite.message.accept=Accept command.team.invite.message.invitation=%USER% invited you to join their team %TEAM%. -command.team.invite.message.invited=You received a invitation for the game jam on %GUILD% +command.team.invite.message.invited=You received a invitation for the game jamEntity on %GUILD% command.team.invite.message.noleader=Only the group leader can invite people. -command.team.invite.message.notRegistered=This user is not registered for the game jam. +command.team.invite.message.notRegistered=This user is not registered for the game jamEntity. command.team.invite.message.partofteam=This user is already part of a team. command.team.invite.message.send=Invitation send. command.team.invite.options.user.description=The user you want to invite @@ -202,7 +202,7 @@ command.team.rename.message.done=Team name changed. command.team.rename.options.name.description=The new team name command.unregister.description=Withdraw your registration command.unregister.message.inteam=You need to leave your team first. -command.unregister.message.notregistered=You are not registered for a jam. +command.unregister.message.notregistered=You are not registered for a jamEntity. command.unregister.message.unregistered=You are now unregistered. command.votes.description=Cast votes command.votes.info.description=Information about your votes. @@ -218,18 +218,18 @@ command.votes.vote.message.ownteam=You cant vote for your own team. command.votes.vote.options.points.description=The points to give. command.votes.vote.options.team.description=The team you want to vote for. command.team.api.description=Information about the team api -error.alreadyActive=A jam is already active. +error.alreadyActive=A jamEntity is already active. error.invalidpath=Invalid path error.invalidrimeformat=Invalid time format. Format is %FORMAT% error.invalidtimezone=Invalid timezone. error.maxteamsize=The team has reached the max size. -error.noactivejam=There is no active jam. +error.noactivejam=There is no active jamEntity. error.noconfirm=Please confirm by setting the confirm parameter -error.nojamactive=No jam is in progress. Teams are not available. +error.nojamactive=No jamEntity is in progress. Teams are not available. error.noleader=Only the leader can do this. error.noteam=You are not part of a team. -error.notregistered=You are not registered for a jam. -error.noupcomingjam=There is no upcoming jam. +error.notregistered=You are not registered for a jamEntity. +error.noupcomingjam=There is no upcoming jamEntity. error.pluginnotfound=Plugin not found error.servernotrunning=Server is not online. error.unkownteam=This team does not exist. diff --git a/bot/src/main/resources/log4j2.xml b/bot/src/main/resources/log4j2.xml index f05b7d95..79a4c7c7 100644 --- a/bot/src/main/resources/log4j2.xml +++ b/bot/src/main/resources/log4j2.xml @@ -1,10 +1,10 @@ - + @@ -16,28 +16,6 @@ - - - - - - - - - - - - - - - - - - - @@ -45,13 +23,9 @@ - + - - - - diff --git a/compose.dev.yml b/compose.dev.yml new file mode 100644 index 00000000..47588c5f --- /dev/null +++ b/compose.dev.yml @@ -0,0 +1,71 @@ +services: + mc-server-image: + build: + context: . + dockerfile: Dockerfile.mc + image: plugin-jam-mc-server + entrypoint: ["echo", "image built"] + bot: + build: + context: . + dockerfile: Dockerfile.bot.dev + depends_on: + mc-server-image: + condition: service_completed_successfully + volumes: + - ./data/bot/config:/app/config + - ./data/bot/plugins:/app/plugins + - ./data/bot/servers:/app/servers + - ./data/bot/template:/app/template + - /var/run/docker.sock:/var/run/docker.sock + group_add: + - 131 + networks: + - plugin-jam-network + database: + image: postgres:16 + environment: + POSTGRES_PASSWORD: postgres + ports: + - "127.0.0.1:5433:5432" + networks: + - plugin-jam-network + volumes: + - postgres-data:/var/lib/postgresql/data + velocity: + build: + context: . + dockerfile: Dockerfile.velocity + tty: true + environment: + EULA: "TRUE" + TYPE: VELOCITY + JVM_DD_OPTS: javalin.port=30000 + volumes: + - ./data/velocity/config:/config + ports: + - "127.0.0.1:27777:25577" + networks: + - plugin-jam-network + lobby: + image: itzg/minecraft-server:latest + stdin_open: true + tty: true + environment: + VERSION: 26.1.2 + TYPE: PAPER + EULA: "TRUE" + MEMORY: 2G + volumes: + - ./data/lobby:/data + networks: + - plugin-jam-network + +networks: + plugin-jam-network: + name: plugin-jam-network + driver: bridge + +volumes: + postgres-data: + driver: local \ No newline at end of file diff --git a/compose.yml b/compose.yml index fd3f0bd4..9337343f 100644 --- a/compose.yml +++ b/compose.yml @@ -1,15 +1,27 @@ services: + mc-server-image: + build: + context: . + dockerfile: Dockerfile.mc + image: plugin-jam-mc-server + entrypoint: ["echo", "image built"] bot: image: ghcr.io/devcordde/plugin-jam-bot:latest + depends_on: + mc-server-image: + condition: service_completed_successfully volumes: - ./data/bot/config:/app/config - ./data/bot/plugins:/app/plugins - ./data/bot/servers:/app/servers - ./data/bot/template:/app/template - depends_on: - - + - /var/run/docker.sock:/var/run/docker.sock + networks: + - plugin-jam-network velocity: - image: itzg/mc-proxy:latest + build: + context: . + dockerfile: Dockerfile.velocity tty: true environment: EULA: "TRUE" @@ -17,7 +29,8 @@ services: JVM_DD_OPTS: javalin.port=30000 volumes: - ./data/velocity/config:/config - - ./data/velocity/plugins:/plugins + networks: + - plugin-jam-network lobby: image: itzg/minecraft-server:latest stdin_open: true @@ -28,3 +41,10 @@ services: MEMORY: 2G volumes: - ./data/lobby:/data + networks: + - plugin-jam-network + +networks: + plugin-jam-network: + name: plugin-jam-network + driver: bridge diff --git a/dev.compose.yml b/dev.compose.yml deleted file mode 100644 index d9aa4790..00000000 --- a/dev.compose.yml +++ /dev/null @@ -1,34 +0,0 @@ -services: - bot: - build: . - volumes: - - ./data/bot/config:/app/config - - ./data/bot/plugins:/app/plugins - - ./data/bot/servers:/app/servers - - ./data/bot/template:/app/template - database: - image: postgres:16 - environment: - POSTGRES_PASSWORD: postgres - ports: - - 5433:5432 - velocity: - image: itzg/mc-proxy:latest - tty: true - environment: - EULA: "TRUE" - TYPE: VELOCITY - JVM_DD_OPTS: javalin.port=30000 - volumes: - - ./data/velocity/config:/config - - ./data/velocity/plugins:/plugins - lobby: - image: itzg/minecraft-server:latest - stdin_open: true - tty: true - environment: - VERSION: 1.21 - TYPE: PAPER - MEMORY: 2G - volumes: - - ./data/lobby:/data diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml deleted file mode 100644 index 5d358306..00000000 --- a/docker/docker-compose.yml +++ /dev/null @@ -1,28 +0,0 @@ -version: v3 - -services: - bot: - image: game-jam-bot - build: - dockerfile: ../bot/Dockerfile - context: .. - networks: - - plugin-jam - depends_on: - - database - ports: - - 8888:8888 - database: - image: postgres:18.4 - restart: always - user: postgres - environment: - POSTGRES_PASSWORD: "changeme" - POSTGRES_USER: "postgres" - POSTGRES_DB: "db" - networks: - - plugin-jam -networks: - plugin-jam: - name: "plugin-jam" - external: false diff --git a/docker/resources/docker-entrypoint.sh b/docker/resources/docker-entrypoint.sh index f08937b3..ef4b3cdf 100755 --- a/docker/resources/docker-entrypoint.sh +++ b/docker/resources/docker-entrypoint.sh @@ -1,2 +1,2 @@ #!/bin/sh -exec java -Xms256m -Xmx2048m -Dbot.config=config/config.json -Dlog4j.configurationFile=config/log4j2.xml -Dcjda.localisation.error.name=false -jar ./bot.jar +exec java -Xms256m -Xmx2048m -Dlog4j.configurationFile=log4j2.xml -Dcjda.localisation.error.name=false -jar ./bot.jar \ No newline at end of file diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 00000000..4a7f73a2 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,24 @@ +# Nuxt dev/build outputs +.output +.data +.nuxt +.nitro +.cache +dist + +# Node dependencies +node_modules + +# Logs +logs +*.log + +# Misc +.DS_Store +.fleet +.idea + +# Local env files +.env +.env.* +!.env.example diff --git a/frontend/app/app.config.ts b/frontend/app/app.config.ts new file mode 100644 index 00000000..d58236b1 --- /dev/null +++ b/frontend/app/app.config.ts @@ -0,0 +1,10 @@ +export default defineAppConfig({ + ui: { + colors: { + primary: 'amber', + brandSuccess: 'lime', + brandWarning: 'amber', + brandError: 'rose' + } + } +}) diff --git a/frontend/app/app.vue b/frontend/app/app.vue new file mode 100644 index 00000000..8aadd978 --- /dev/null +++ b/frontend/app/app.vue @@ -0,0 +1,26 @@ + + + diff --git a/frontend/app/assets/css/main.css b/frontend/app/assets/css/main.css new file mode 100644 index 00000000..f8177454 --- /dev/null +++ b/frontend/app/assets/css/main.css @@ -0,0 +1,42 @@ +@import "tailwindcss"; +@import "@nuxt/ui"; + +@theme static { + --font-sans: 'Public Sans', sans-serif; + + --color-green-50: #EFFDF5; + --color-green-100: #D9FBE8; + --color-green-200: #B3F5D1; + --color-green-300: #75EDAE; + --color-green-400: #00DC82; + --color-green-500: #00C16A; + --color-green-600: #00A155; + --color-green-700: #007F45; + --color-green-800: #016538; + --color-green-900: #0A5331; + --color-green-950: #052E16; +} + +@layer base { + button:not(:disabled), + [role="button"]:not(:disabled) { + cursor: pointer; + } +} + +@theme { + --font-sans: 'Open Sans', sans-serif; + --font-accent: 'DynaPuff', sans-serif; +} + +:root { + --ui-radius: 0.205rem; +} + +.dark { + --ui-bg: var(--color-gray-900); + --ui-bg-muted: var(--color-gray-800); + --ui-bg-elevated: var(--color-gray-800); + --ui-bg-accented: var(--color-gray-700); + --ui-bg-inverted: white; +} diff --git a/frontend/app/components/AppHeader.vue b/frontend/app/components/AppHeader.vue new file mode 100644 index 00000000..2c10e14a --- /dev/null +++ b/frontend/app/components/AppHeader.vue @@ -0,0 +1,24 @@ + + + diff --git a/frontend/app/components/Heading.vue b/frontend/app/components/Heading.vue new file mode 100644 index 00000000..8fc6cb5c --- /dev/null +++ b/frontend/app/components/Heading.vue @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/frontend/app/components/PulsingCircle.vue b/frontend/app/components/PulsingCircle.vue new file mode 100644 index 00000000..e218151f --- /dev/null +++ b/frontend/app/components/PulsingCircle.vue @@ -0,0 +1,10 @@ + + \ No newline at end of file diff --git a/frontend/app/components/SettingsGroup.vue b/frontend/app/components/SettingsGroup.vue new file mode 100644 index 00000000..457f2ebd --- /dev/null +++ b/frontend/app/components/SettingsGroup.vue @@ -0,0 +1,17 @@ + + + + + \ No newline at end of file diff --git a/frontend/app/components/UserDropdown.vue b/frontend/app/components/UserDropdown.vue new file mode 100644 index 00000000..81246a15 --- /dev/null +++ b/frontend/app/components/UserDropdown.vue @@ -0,0 +1,38 @@ + + + + + \ No newline at end of file diff --git a/frontend/app/composables/useAuth.ts b/frontend/app/composables/useAuth.ts new file mode 100644 index 00000000..6b6b2942 --- /dev/null +++ b/frontend/app/composables/useAuth.ts @@ -0,0 +1,47 @@ +export const useAuth = () => { + const user = useState('user', () => null) + const config = useRuntimeConfig() + + const fetchUser = async () => { + try { + const headers = useRequestHeaders(['cookie']) + const data = await $fetch('/api/user/me', { + baseURL: config.public.apiBase, + credentials: 'include', + headers + }) + console.log('User data:', data) + user.value = data + } catch (e) { + user.value = null + } + } + + const login = () => { + window.location.href = `${config.public.apiBase}/oauth/login/discord` + } + + const logout = async () => { + try { + await $fetch('/logout', { + method: 'POST', + baseURL: config.public.apiBase, + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + credentials: 'include' + }) + } finally { + user.value = null + window.location.href = '/' + } + } + + return { + user, + fetchUser, + login, + logout + } +} diff --git a/frontend/app/layouts/dashboard.vue b/frontend/app/layouts/dashboard.vue new file mode 100644 index 00000000..d1b85214 --- /dev/null +++ b/frontend/app/layouts/dashboard.vue @@ -0,0 +1,78 @@ + + + diff --git a/frontend/app/layouts/default.vue b/frontend/app/layouts/default.vue new file mode 100644 index 00000000..387e3179 --- /dev/null +++ b/frontend/app/layouts/default.vue @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/frontend/app/middleware/auth-check.global.ts b/frontend/app/middleware/auth-check.global.ts new file mode 100644 index 00000000..d09a18b2 --- /dev/null +++ b/frontend/app/middleware/auth-check.global.ts @@ -0,0 +1,44 @@ +export default defineNuxtRouteMiddleware(async (to, from) => { + if (!to.path.startsWith('/app') || to.path === '/app/setup') { + return + } + + const { user, fetchUser, login } = useAuth() + const config = useRuntimeConfig() + + if (!user.value) { + await fetchUser() + } + + if (!user.value) { + login() + return navigateTo('/'); + } + + try { + const team = await $fetch('/api/teams/my-team', { + baseURL: config.public.apiBase, + credentials: 'include', + headers: useRequestHeaders(['cookie']) + }).catch(() => null) + + console.log('Team:', team) + + if (!team) { + return navigateTo('/app/setup') + } + + const serverInfo = await $fetch<{ exists: boolean }>('/api/server/exists', { + baseURL: config.public.apiBase, + credentials: 'include', + headers: useRequestHeaders(['cookie']) + }) + + if (!serverInfo.exists) { + return navigateTo('/app/setup') + } + } catch (e) { + console.error('Middleware check failed', e) + return navigateTo('/app/setup') + } +}) diff --git a/frontend/app/pages/app/console.vue b/frontend/app/pages/app/console.vue new file mode 100644 index 00000000..6a5bdf0c --- /dev/null +++ b/frontend/app/pages/app/console.vue @@ -0,0 +1,238 @@ + + + \ No newline at end of file diff --git a/frontend/app/pages/app/files.vue b/frontend/app/pages/app/files.vue new file mode 100644 index 00000000..89f04745 --- /dev/null +++ b/frontend/app/pages/app/files.vue @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/frontend/app/pages/app/settings.vue b/frontend/app/pages/app/settings.vue new file mode 100644 index 00000000..deb5d357 --- /dev/null +++ b/frontend/app/pages/app/settings.vue @@ -0,0 +1,69 @@ + + + + + \ No newline at end of file diff --git a/frontend/app/pages/app/setup.vue b/frontend/app/pages/app/setup.vue new file mode 100644 index 00000000..365e0971 --- /dev/null +++ b/frontend/app/pages/app/setup.vue @@ -0,0 +1,171 @@ + + + diff --git a/frontend/app/pages/app/team.vue b/frontend/app/pages/app/team.vue new file mode 100644 index 00000000..b29023ba --- /dev/null +++ b/frontend/app/pages/app/team.vue @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/frontend/app/pages/index.vue b/frontend/app/pages/index.vue new file mode 100644 index 00000000..87595e56 --- /dev/null +++ b/frontend/app/pages/index.vue @@ -0,0 +1,37 @@ + diff --git a/frontend/app/utils/types.ts b/frontend/app/utils/types.ts new file mode 100644 index 00000000..aa8bfd72 --- /dev/null +++ b/frontend/app/utils/types.ts @@ -0,0 +1,22 @@ +export interface User { + id: string + name: string + picture: string +} + +export interface TeamMember { + userId: number +} + +export interface Team { + id: number, + jamId: number, + members: TeamMember[], + meta: { + teamName: string, + leaderId: string, + roleId: number, + voiceChannelId: number, + textChannelId: number, + } +} \ No newline at end of file diff --git a/frontend/bun.lock b/frontend/bun.lock new file mode 100644 index 00000000..111d4c56 --- /dev/null +++ b/frontend/bun.lock @@ -0,0 +1,2287 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "frontend", + "dependencies": { + "@iconify-json/lucide": "^1.2.114", + "@iconify-json/simple-icons": "^1.2.87", + "@nuxt/ui": "^4.9.0", + "nuxt": "^4.4.8", + "tailwindcss": "^4.3.2", + }, + "devDependencies": { + "@nuxt/eslint": "^1.16.0", + "eslint": "^10.6.0", + "typescript": "^6.0.3", + "vue-tsc": "^3.3.5", + }, + }, + }, + "packages": { + "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], + + "@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "1.6.0", "tinyexec": "1.1.2" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="], + + "@apidevtools/json-schema-ref-parser": ["@apidevtools/json-schema-ref-parser@14.2.1", "", { "dependencies": { "js-yaml": "4.1.1" }, "peerDependencies": { "@types/json-schema": "7.0.15" } }, "sha512-HmdFw9CDYqM6B25pqGBpNeLCKvGPlIx1EbLrVL0zPvj50CJQUHyBNBw45Muk0kEIkogo1VZvOKHajdMuAzSxRg=="], + + "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "7.28.5", "js-tokens": "4.0.0", "picocolors": "1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], + + "@babel/compat-data": ["@babel/compat-data@7.29.3", "", {}, "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg=="], + + "@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "7.29.0", "@babel/generator": "7.29.1", "@babel/helper-compilation-targets": "7.28.6", "@babel/helper-module-transforms": "7.28.6", "@babel/helpers": "7.29.2", "@babel/parser": "7.29.3", "@babel/template": "7.28.6", "@babel/traverse": "7.29.0", "@babel/types": "7.29.0", "@jridgewell/remapping": "2.3.5", "convert-source-map": "2.0.0", "debug": "4.4.3", "gensync": "1.0.0-beta.2", "json5": "2.2.3", "semver": "6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="], + + "@babel/generator": ["@babel/generator@8.0.0-rc.5", "", { "dependencies": { "@babel/parser": "8.0.0-rc.5", "@babel/types": "8.0.0-rc.5", "@jridgewell/gen-mapping": "0.3.13", "@jridgewell/trace-mapping": "0.3.31", "@types/jsesc": "2.5.1", "jsesc": "3.1.0" } }, "sha512-nFZPWz3FHIS7y6rMIVoa/WBwjdutfIaRJIBQjzn+t3RnecZoRNlGmGcyR2wb0T/IgSd50Kz/6dG8/LvMCRunjg=="], + + "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "7.29.0" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "7.29.3", "@babel/helper-validator-option": "7.27.1", "browserslist": "4.28.2", "lru-cache": "5.1.1", "semver": "6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], + + "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.29.3", "", { "dependencies": { "@babel/helper-annotate-as-pure": "7.27.3", "@babel/helper-member-expression-to-functions": "7.28.5", "@babel/helper-optimise-call-expression": "7.27.1", "@babel/helper-replace-supers": "7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "7.27.1", "@babel/traverse": "7.29.0", "semver": "6.3.1" }, "peerDependencies": { "@babel/core": "7.29.0" } }, "sha512-RpLYy2sb51oNLjuu1iD3bwBqCBWUzjO0ocp+iaCP/lJtb2CPLcnC2Fftw+4sAzaMELGeWTgExSKADbdo0GFVzA=="], + + "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], + + "@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "7.29.0", "@babel/types": "7.29.0" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "7.29.0", "@babel/types": "7.29.0" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "7.28.6", "@babel/helper-validator-identifier": "7.28.5", "@babel/traverse": "7.29.0" }, "peerDependencies": { "@babel/core": "7.29.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="], + + "@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "7.29.0" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], + + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.28.6", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "7.28.5", "@babel/helper-optimise-call-expression": "7.27.1", "@babel/traverse": "7.29.0" }, "peerDependencies": { "@babel/core": "7.29.0" } }, "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg=="], + + "@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "7.29.0", "@babel/types": "7.29.0" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0-rc.5", "", {}, "sha512-sN7R8rBvDurfaziNfDEIjIntlazmlkCDGO4SNl2RJ3wRCn+QxspLV7hzYAE8WWVd2joVuT8sUxeePdLp2idI1A=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], + + "@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "7.28.6", "@babel/types": "7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="], + + "@babel/parser": ["@babel/parser@7.29.3", "", { "dependencies": { "@babel/types": "7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA=="], + + "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "7.28.6" }, "peerDependencies": { "@babel/core": "7.29.0" } }, "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w=="], + + "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "7.28.6" }, "peerDependencies": { "@babel/core": "7.29.0" } }, "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A=="], + + "@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "7.27.3", "@babel/helper-create-class-features-plugin": "7.29.3", "@babel/helper-plugin-utils": "7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "7.27.1", "@babel/plugin-syntax-typescript": "7.28.6" }, "peerDependencies": { "@babel/core": "7.29.0" } }, "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw=="], + + "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "7.29.0", "@babel/parser": "7.29.3", "@babel/types": "7.29.0" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "7.29.0", "@babel/generator": "7.29.1", "@babel/helper-globals": "7.28.0", "@babel/parser": "7.29.3", "@babel/template": "7.28.6", "@babel/types": "7.29.0", "debug": "4.4.3" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], + + "@babel/types": ["@babel/types@8.0.0-rc.5", "", { "dependencies": { "@babel/helper-string-parser": "8.0.0-rc.5", "@babel/helper-validator-identifier": "8.0.0-rc.5" } }, "sha512-JeSVu/m8x/zpp4CLjYHVNXuhEyOkhPXuxM8YOXjh6L4LlvQNKuUNOTo5KdBuKAcTDHw8DquToTaEkhsBqPXOaA=="], + + "@bomb.sh/tab": ["@bomb.sh/tab@0.0.15", "", { "optionalDependencies": { "cac": "6.7.14", "citty": "0.2.2" }, "bin": { "tab": "dist/bin/cli.mjs" } }, "sha512-Y90ub44TAvbdO9P8mcD/XPyQjFhiR5xmd4Fk7JErmWmEWEUimNnjWiBrVZ16Tj3GA1rLZ+uvCN2V/pzLawv31g=="], + + "@capsizecss/unpack": ["@capsizecss/unpack@4.0.0", "", { "dependencies": { "fontkitten": "1.0.3" } }, "sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA=="], + + "@clack/core": ["@clack/core@1.4.1", "", { "dependencies": { "fast-wrap-ansi": "0.2.0", "sisteransi": "1.0.5" } }, "sha512-FILJa1gGKEFTGZAJE9RpVhrjKz3c3h4ar60dSv6cGuDqufQ84YEIS3GAGvZiN+H6yaLbbvTFNejjCC4tXpZEuw=="], + + "@clack/prompts": ["@clack/prompts@1.5.1", "", { "dependencies": { "@clack/core": "1.4.1", "fast-string-width": "3.0.2", "fast-wrap-ansi": "0.2.0", "sisteransi": "1.0.5" } }, "sha512-zccHj2z2oCCO4yrDiRSlFOxWerGqRiysP7a5jPK6uoI9URKAquwY42Dd/iUP8JWHxEzdRe4TlbvZCo8z1/mhrw=="], + + "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.2", "", {}, "sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ=="], + + "@colordx/core": ["@colordx/core@5.4.3", "", {}, "sha512-kIxYSfA5T8HXjav55UaaH/o/cKivF6jCCGIb8eqtcsfI46wsvlSiT8jMDyrl779qLec3c2c2oHBZo4oAhvbjrQ=="], + + "@dxup/nuxt": ["@dxup/nuxt@0.4.1", "", { "dependencies": { "@dxup/unimport": "0.1.2", "@nuxt/kit": "4.4.8", "chokidar": "5.0.0", "pathe": "2.0.3", "tinyglobby": "0.2.17" }, "optionalDependencies": { "typescript": "6.0.3" } }, "sha512-gtYffW6OfWNvoLW+XD3Mx/K8uUq08PMGLYJoDxc92EzZAWqR0FhcR5iaLm5r/OxyGTKz+P5f5Y7Aoir9+SjYaw=="], + + "@dxup/unimport": ["@dxup/unimport@0.1.2", "", {}, "sha512-/B8YJGPzaYq1NbsQmwgP8EZqg40NpTw4ZB3suuI0TplbxKHeK94jeaawLmVhCv+YwUnOpiWEz9U6SeThku/8JQ=="], + + "@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "2.8.1" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], + + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + + "@es-joy/jsdoccomment": ["@es-joy/jsdoccomment@0.87.0", "", { "dependencies": { "@types/estree": "1.0.9", "@typescript-eslint/types": "8.61.0", "comment-parser": "1.4.7", "esquery": "1.7.0", "jsdoc-type-pratt-parser": "7.2.0" } }, "sha512-mFXZloZMzuJZXSHUmAFu/pXTk0ZJTJBluuAkrvbzidpTN8W6F2bpRFuedSH+85kbdlRLJqc+gfN+kD3JOLJK5g=="], + + "@es-joy/resolve.exports": ["@es-joy/resolve.exports@1.2.0", "", {}, "sha512-Q9hjxWI5xBM+qW2enxfe8wDKdFWMfd0Z29k5ZJnuBqD/CasY5Zryj09aCA6owbGATWz+39p5uIdaHXpopOcG8g=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.7", "", { "os": "android", "cpu": "arm64" }, "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.27.7", "", { "os": "android", "cpu": "x64" }, "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.7", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.7", "", { "os": "linux", "cpu": "arm" }, "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.7", "", { "os": "linux", "cpu": "ia32" }, "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.7", "", { "os": "none", "cpu": "x64" }, "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.7", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.7", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.7", "", { "os": "sunos", "cpu": "x64" }, "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="], + + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "3.4.3" }, "peerDependencies": { "eslint": "10.6.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], + + "@eslint/compat": ["@eslint/compat@2.1.0", "", { "dependencies": { "@eslint/core": "1.2.1" }, "optionalDependencies": { "eslint": "10.6.0" } }, "sha512-LgaSCymEpw7tF53xvDw9SNsraPb1IBHxpdABIOM0hW8UAlP8znrjYtuxfR58FSJ3L9BhwD+FaPRFQpZq84Nh6g=="], + + "@eslint/config-array": ["@eslint/config-array@0.23.5", "", { "dependencies": { "@eslint/object-schema": "3.0.5", "debug": "4.4.3", "minimatch": "10.2.5" } }, "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA=="], + + "@eslint/config-helpers": ["@eslint/config-helpers@0.6.0", "", { "dependencies": { "@eslint/core": "1.2.1" } }, "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA=="], + + "@eslint/config-inspector": ["@eslint/config-inspector@3.0.4", "", { "dependencies": { "ansis": "4.3.0", "cac": "7.0.0", "chokidar": "5.0.0", "devframe": "0.5.4", "jiti": "2.7.0", "tinyglobby": "0.2.17" }, "peerDependencies": { "eslint": "10.6.0" }, "bin": { "eslint-config-inspector": "./bin.mjs", "@eslint/config-inspector": "./bin.mjs" } }, "sha512-qyb1cjiiwD2mlg5GJC4JiO/EOO/rvEtm+GgLtgJ054bu8ZPj6hK1PLCRzxOnBghlPKWVqSjs4i+fXoPkt488Yw=="], + + "@eslint/core": ["@eslint/core@1.2.1", "", { "dependencies": { "@types/json-schema": "7.0.15" } }, "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ=="], + + "@eslint/js": ["@eslint/js@10.0.1", "", { "optionalDependencies": { "eslint": "10.6.0" } }, "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA=="], + + "@eslint/object-schema": ["@eslint/object-schema@3.0.5", "", {}, "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw=="], + + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.7.2", "", { "dependencies": { "@eslint/core": "1.2.1", "levn": "0.4.1" } }, "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A=="], + + "@floating-ui/core": ["@floating-ui/core@1.7.5", "", { "dependencies": { "@floating-ui/utils": "0.2.11" } }, "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ=="], + + "@floating-ui/dom": ["@floating-ui/dom@1.7.6", "", { "dependencies": { "@floating-ui/core": "1.7.5", "@floating-ui/utils": "0.2.11" } }, "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ=="], + + "@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="], + + "@floating-ui/vue": ["@floating-ui/vue@1.1.11", "", { "dependencies": { "@floating-ui/dom": "1.7.6", "@floating-ui/utils": "0.2.11", "vue-demi": "0.14.10" } }, "sha512-HzHKCNVxnGS35r9fCHBc3+uCnjw9IWIlCPL683cGgM9Kgj2BiAl8x1mS7vtvP6F9S/e/q4O6MApwSHj8hNLGfw=="], + + "@humanfs/core": ["@humanfs/core@0.19.2", "", { "dependencies": { "@humanfs/types": "0.15.0" } }, "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA=="], + + "@humanfs/node": ["@humanfs/node@0.16.8", "", { "dependencies": { "@humanfs/core": "0.19.2", "@humanfs/types": "0.15.0", "@humanwhocodes/retry": "0.4.3" } }, "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ=="], + + "@humanfs/types": ["@humanfs/types@0.15.0", "", {}, "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + + "@iconify-json/lucide": ["@iconify-json/lucide@1.2.114", "", { "dependencies": { "@iconify/types": "2.0.0" } }, "sha512-NbvH3B1BYo6wBtS7joLi7f2UVQOqK2dtZodMFf3kkBs+Tnh9TkRuy8oVHr1RM8UK6bUtvAXxfNlGAah0CuvPCw=="], + + "@iconify-json/simple-icons": ["@iconify-json/simple-icons@1.2.87", "", { "dependencies": { "@iconify/types": "2.0.0" } }, "sha512-8YciStObhSji3OZFmWAWK6kBujyqO5bLCxeDwLxf3CR3F4PVelq7keC2LBvgTqviWzSTysj5/g4PCFLiAMVGsw=="], + + "@iconify/collections": ["@iconify/collections@1.0.698", "", { "dependencies": { "@iconify/types": "2.0.0" } }, "sha512-k7lDu5QpRk7arMiSaB8fG2zlQpu3B38q2S1GKnqrJQJPh6ZcTdIB9PWcvaQlYVGEzE2zhRJ1qcZmqHQjrh+/ag=="], + + "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="], + + "@iconify/utils": ["@iconify/utils@3.1.3", "", { "dependencies": { "@antfu/install-pkg": "1.1.0", "@iconify/types": "2.0.0", "import-meta-resolve": "4.2.0" } }, "sha512-LPKOXPn/zV+zis1oOfGWogaXVpqUybF3ZS6SCZIsz8vg0ivVp9+fVqyYB7xq0aiST/VhUQYGO1qo6uoYSiEJqw=="], + + "@iconify/vue": ["@iconify/vue@5.0.1", "", { "dependencies": { "@iconify/types": "2.0.0" }, "peerDependencies": { "vue": "3.5.35" } }, "sha512-aumwwooJlFJ5H5qYWB6ZTAyM0C8hpfcSVLB9/a3qnH1GGvIJ+FEbpEs4s/HfErYe/M5qZeLjwmESR5fFm3lXEw=="], + + "@internationalized/date": ["@internationalized/date@3.12.1", "", { "dependencies": { "@swc/helpers": "0.5.21" } }, "sha512-6IedsVWXyq4P9Tj+TxuU8WGWM70hYLl12nbYU8jkikVpa6WXapFazPUcHUMDMoWftIDE2ILDkFFte6W2nFCkRQ=="], + + "@internationalized/number": ["@internationalized/number@3.6.6", "", { "dependencies": { "@swc/helpers": "0.5.21" } }, "sha512-iFgmQaXHE0vytNfpLZWOC2mEJCBRzcUxt53Xf/yCXG93lRvqas237i3r7X4RKMwO3txiyZD4mQjKAByFv6UGSQ=="], + + "@ioredis/commands": ["@ioredis/commands@1.5.1", "", {}, "sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw=="], + + "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "5.1.2", "string-width-cjs": "npm:string-width@4.2.3", "strip-ansi": "7.2.0", "strip-ansi-cjs": "npm:strip-ansi@6.0.1", "wrap-ansi": "8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + + "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "7.1.3" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "1.5.5", "@jridgewell/trace-mapping": "0.3.31" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "0.3.13", "@jridgewell/trace-mapping": "0.3.31" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/source-map": ["@jridgewell/source-map@0.3.11", "", { "dependencies": { "@jridgewell/gen-mapping": "0.3.13", "@jridgewell/trace-mapping": "0.3.31" } }, "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "3.1.2", "@jridgewell/sourcemap-codec": "1.5.5" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@kwsites/file-exists": ["@kwsites/file-exists@1.1.1", "", { "dependencies": { "debug": "4.4.3" } }, "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw=="], + + "@kwsites/promise-deferred": ["@kwsites/promise-deferred@1.1.1", "", {}, "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw=="], + + "@mapbox/node-pre-gyp": ["@mapbox/node-pre-gyp@2.0.3", "", { "dependencies": { "consola": "3.4.2", "detect-libc": "2.1.2", "https-proxy-agent": "7.0.6", "node-fetch": "2.7.0", "nopt": "8.1.0", "semver": "7.8.2", "tar": "7.5.15" }, "bin": { "node-pre-gyp": "bin/node-pre-gyp" } }, "sha512-uwPAhccfFJlsfCxMYTwOdVfOz3xqyj8xYL3zJj8f0pb30tLohnnFPhLuqp4/qoEz8sNxe4SESZedcBojRefIzg=="], + + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "0.10.2" }, "peerDependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "1.2.0" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "1.20.1" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@nuxt/cli": ["@nuxt/cli@3.35.2", "", { "dependencies": { "@bomb.sh/tab": "0.0.15", "@clack/prompts": "1.5.1", "c12": "3.3.4", "citty": "0.2.2", "confbox": "0.2.4", "consola": "3.4.2", "debug": "4.4.3", "defu": "6.1.7", "exsolve": "1.0.8", "fuse.js": "7.4.2", "fzf": "0.5.2", "giget": "3.2.0", "jiti": "2.7.0", "listhen": "1.10.0", "nypm": "0.6.6", "ofetch": "1.5.1", "ohash": "2.0.11", "pathe": "2.0.3", "perfect-debounce": "2.1.0", "pkg-types": "2.3.1", "scule": "1.3.0", "semver": "7.8.2", "srvx": "0.11.15", "std-env": "4.1.0", "tinyclip": "0.1.12", "tinyexec": "1.1.2", "ufo": "1.6.4", "youch": "4.1.1" }, "optionalDependencies": { "@nuxt/schema": "4.4.8" }, "bin": { "nuxi": "bin/nuxi.mjs", "nuxt": "bin/nuxi.mjs", "nuxi-ng": "bin/nuxi.mjs", "nuxt-cli": "bin/nuxi.mjs" } }, "sha512-sCxNnFuYamqippdj+Cj4Nue55yaUvasaneyf2mnowK5/F1TKln/WVqTH18McxQ4baLlIlVapIFovKjJx1L8XMQ=="], + + "@nuxt/devalue": ["@nuxt/devalue@2.0.2", "", {}, "sha512-GBzP8zOc7CGWyFQS6dv1lQz8VVpz5C2yRszbXufwG/9zhStTIH50EtD87NmWbTMwXDvZLNg8GIpb1UFdH93JCA=="], + + "@nuxt/devtools": ["@nuxt/devtools@3.2.4", "", { "dependencies": { "@nuxt/devtools-kit": "3.2.4", "@nuxt/devtools-wizard": "3.2.4", "@nuxt/kit": "4.4.8", "@vue/devtools-core": "8.1.2", "@vue/devtools-kit": "8.1.2", "birpc": "4.0.0", "consola": "3.4.2", "destr": "2.0.5", "error-stack-parser-es": "1.0.5", "execa": "8.0.1", "fast-npm-meta": "1.5.1", "get-port-please": "3.2.0", "hookable": "6.1.1", "image-meta": "0.2.2", "is-installed-globally": "1.0.0", "launch-editor": "2.13.2", "local-pkg": "1.2.1", "magicast": "0.5.3", "nypm": "0.6.6", "ohash": "2.0.11", "pathe": "2.0.3", "perfect-debounce": "2.1.0", "pkg-types": "2.3.1", "semver": "7.8.2", "simple-git": "3.36.0", "sirv": "3.0.2", "structured-clone-es": "2.0.0", "tinyglobby": "0.2.17", "vite-plugin-inspect": "11.3.3", "vite-plugin-vue-tracer": "1.4.0", "which": "6.0.1", "ws": "8.21.0" }, "peerDependencies": { "vite": "7.3.3" }, "bin": { "devtools": "cli.mjs" } }, "sha512-VPbFy7hlPzWpEZk4BsuVpNuHq1ZYGV9xezjb7/NGuePuNLqeNn74YZugU+PCtva7OwKhEeTXmMK0Mqo/6+nwNA=="], + + "@nuxt/devtools-kit": ["@nuxt/devtools-kit@3.2.4", "", { "dependencies": { "@nuxt/kit": "4.4.8", "execa": "8.0.1" }, "peerDependencies": { "vite": "7.3.3" } }, "sha512-Yxy2Xgmq5hf3dQy983V0xh0OJV2mYwRZz9eVIGc3EaribdFGPDNGMMbYqX9qCty3Pbxn/bCF3J0UyPaNlHVayQ=="], + + "@nuxt/devtools-wizard": ["@nuxt/devtools-wizard@3.2.4", "", { "dependencies": { "@clack/prompts": "1.5.1", "consola": "3.4.2", "diff": "8.0.4", "execa": "8.0.1", "magicast": "0.5.3", "pathe": "2.0.3", "pkg-types": "2.3.1", "semver": "7.8.2" }, "bin": { "devtools-wizard": "cli.mjs" } }, "sha512-5tu2+Quu9XTxwtpzM8CUN0UKn/bzZIfJcoGd+at5Yy1RiUQJ4E52tRK0idW1rMSUDkbkvX3dSnu8Tpj7SAtWdQ=="], + + "@nuxt/eslint": ["@nuxt/eslint@1.16.0", "", { "dependencies": { "@eslint/config-inspector": "3.0.4", "@nuxt/devtools-kit": "3.2.4", "@nuxt/eslint-config": "1.16.0", "@nuxt/eslint-plugin": "1.16.0", "@nuxt/kit": "4.4.8", "chokidar": "5.0.0", "eslint-flat-config-utils": "3.2.0", "eslint-typegen": "2.3.1", "find-up": "8.0.0", "get-port-please": "3.2.0", "mlly": "1.8.2", "pathe": "2.0.3", "unimport": "6.3.0" }, "peerDependencies": { "eslint": "10.6.0" } }, "sha512-VlZBHG86xUY72pZMcZ98cDtsUj972vZ23Pd4wqpxMLgLJ3RtHxQke0vgD/6PZ6uJsRjh7CAUGpsp2+d26ZdaSA=="], + + "@nuxt/eslint-config": ["@nuxt/eslint-config@1.16.0", "", { "dependencies": { "@antfu/install-pkg": "1.1.0", "@clack/prompts": "1.5.1", "@eslint/js": "10.0.1", "@nuxt/eslint-plugin": "1.16.0", "@stylistic/eslint-plugin": "5.10.0", "@typescript-eslint/eslint-plugin": "8.61.0", "@typescript-eslint/parser": "8.61.0", "eslint-config-flat-gitignore": "2.3.0", "eslint-flat-config-utils": "3.2.0", "eslint-merge-processors": "2.0.0", "eslint-plugin-import-lite": "0.6.0", "eslint-plugin-import-x": "4.16.2", "eslint-plugin-jsdoc": "63.0.2", "eslint-plugin-regexp": "3.1.0", "eslint-plugin-unicorn": "65.0.1", "eslint-plugin-vue": "10.9.2", "eslint-processor-vue-blocks": "2.0.0", "globals": "17.6.0", "local-pkg": "1.2.1", "pathe": "2.0.3", "vue-eslint-parser": "10.4.1" }, "peerDependencies": { "eslint": "10.6.0" } }, "sha512-YWEqctFWoKOUTaHWXe6TaUgZ1ewN7jeKz/ni4JopxDGIQ8AIMMST6VMWryybHqbPzEfpx8sEcAAFGM4W4quYhQ=="], + + "@nuxt/eslint-plugin": ["@nuxt/eslint-plugin@1.16.0", "", { "dependencies": { "@typescript-eslint/types": "8.61.0", "@typescript-eslint/utils": "8.61.0" }, "peerDependencies": { "eslint": "10.6.0" } }, "sha512-vbX9EsJqTqIRwf3+sv9mJ/OtFKhH8o8NIbNF9Q6weQZY1MRf8jGEvLnjsyJa5VEEHl5TXSXsPcyrmIK58jsRpw=="], + + "@nuxt/fonts": ["@nuxt/fonts@0.14.0", "", { "dependencies": { "@nuxt/devtools-kit": "3.2.4", "@nuxt/kit": "4.4.8", "consola": "3.4.2", "defu": "6.1.7", "fontless": "0.2.1", "h3": "1.15.11", "magic-regexp": "0.10.0", "ofetch": "1.5.1", "pathe": "2.0.3", "sirv": "3.0.2", "tinyglobby": "0.2.17", "ufo": "1.6.4", "unifont": "0.7.4", "unplugin": "3.0.0", "unstorage": "1.17.5" } }, "sha512-4uXQl9fa5F4ibdgU8zomoOcyMdnwgdem+Pi8JEqeDYI5yPR32Kam6HnuRr47dTb97CstaepAvXPWQUUHMtjsFQ=="], + + "@nuxt/icon": ["@nuxt/icon@2.2.3", "", { "dependencies": { "@iconify/collections": "1.0.698", "@iconify/types": "2.0.0", "@iconify/utils": "3.1.3", "@iconify/vue": "5.0.1", "@nuxt/devtools-kit": "3.2.4", "@nuxt/kit": "4.4.8", "consola": "3.4.2", "local-pkg": "1.2.1", "mlly": "1.8.2", "ohash": "2.0.11", "picomatch": "4.0.4", "std-env": "4.1.0", "tinyglobby": "0.2.17", "ufo": "1.6.4" } }, "sha512-2HWDoMRWGSOWl7fu8wvvLTjoiaEwM7tSDww+6n4yVrNzKJFNAOLpYB9UevEf0CrK32q2JuA7TZnVpdwiPPdQSQ=="], + + "@nuxt/kit": ["@nuxt/kit@4.4.8", "", { "dependencies": { "c12": "3.3.4", "consola": "3.4.2", "defu": "6.1.7", "destr": "2.0.5", "errx": "0.1.0", "exsolve": "1.0.8", "ignore": "7.0.5", "jiti": "2.7.0", "klona": "2.0.6", "mlly": "1.8.2", "ohash": "2.0.11", "pathe": "2.0.3", "pkg-types": "2.3.1", "rc9": "3.0.1", "scule": "1.3.0", "semver": "7.8.2", "tinyglobby": "0.2.17", "ufo": "1.6.4", "unctx": "2.5.0", "untyped": "2.0.0" } }, "sha512-ZUlZ5iYfyfJFDPluhn6ZxFWcsuxWbLnZBc8w3MAROcQ4lYfZ+qFpALBLSNlpc0zhOa++33EE+5PEbOAdVIY+dw=="], + + "@nuxt/nitro-server": ["@nuxt/nitro-server@4.4.8", "", { "dependencies": { "@nuxt/devalue": "2.0.2", "@nuxt/kit": "4.4.8", "@unhead/vue": "2.1.15", "@vue/shared": "3.5.35", "consola": "3.4.2", "defu": "6.1.7", "destr": "2.0.5", "devalue": "5.8.1", "errx": "0.1.0", "escape-string-regexp": "5.0.0", "exsolve": "1.0.8", "h3": "1.15.11", "impound": "1.1.5", "klona": "2.0.6", "mocked-exports": "0.1.1", "nitropack": "2.13.4", "nypm": "0.6.6", "ohash": "2.0.11", "pathe": "2.0.3", "rou3": "0.8.1", "std-env": "4.1.0", "ufo": "1.6.4", "unctx": "2.5.0", "unstorage": "1.17.5", "vue": "3.5.35", "vue-bundle-renderer": "2.2.0", "vue-devtools-stub": "0.1.0" }, "optionalDependencies": { "@babel/plugin-syntax-typescript": "7.28.6" }, "peerDependencies": { "nuxt": "4.4.8" } }, "sha512-cc1fxgSx34Htesx3JBO+hMhbqd6VljXDC06P+UOA5z53cR224TmEFYT/MUuZDkrtt4qLnSG8yq0IxhEM3NCUlw=="], + + "@nuxt/schema": ["@nuxt/schema@4.4.8", "", { "dependencies": { "@vue/shared": "3.5.35", "defu": "6.1.7", "pathe": "2.0.3", "pkg-types": "2.3.1", "std-env": "4.1.0" } }, "sha512-igfWuMF0x0Pmx/XwhPwH/bcXgbuwNnjUjqxCAsY6VQhmGKo0e9soJq3Q0ohj+rBkBfX6o2ysTP1/t2M82aK4qA=="], + + "@nuxt/telemetry": ["@nuxt/telemetry@2.8.0", "", { "dependencies": { "citty": "0.2.2", "consola": "3.4.2", "ofetch": "2.0.0-alpha.3", "rc9": "3.0.1", "std-env": "4.1.0" }, "peerDependencies": { "@nuxt/kit": "4.4.8" }, "bin": { "nuxt-telemetry": "bin/nuxt-telemetry.mjs" } }, "sha512-zAwXY24KYvpLTmiV+osagd2EHkfs5IF+7oDZYTQoit5r0kPlwaCNlzHp5I/wUAWT4LBw6lG8gZ6bWidAdv/erQ=="], + + "@nuxt/ui": ["@nuxt/ui@4.9.0", "", { "dependencies": { "@floating-ui/dom": "1.7.6", "@iconify/vue": "5.0.1", "@nuxt/fonts": "0.14.0", "@nuxt/icon": "2.2.3", "@nuxt/kit": "4.4.8", "@nuxt/schema": "4.4.8", "@nuxtjs/color-mode": "4.0.1", "@standard-schema/spec": "1.1.0", "@tailwindcss/postcss": "4.3.1", "@tailwindcss/vite": "4.3.1", "@tanstack/vue-table": "8.21.3", "@tanstack/vue-virtual": "3.13.29", "@unhead/vue": "2.1.15", "@vueuse/core": "14.3.0", "@vueuse/integrations": "14.3.0", "@vueuse/shared": "14.3.0", "colortranslator": "5.0.0", "consola": "3.4.2", "defu": "6.1.7", "embla-carousel-auto-height": "8.6.0", "embla-carousel-auto-scroll": "8.6.0", "embla-carousel-autoplay": "8.6.0", "embla-carousel-class-names": "8.6.0", "embla-carousel-fade": "8.6.0", "embla-carousel-vue": "8.6.0", "embla-carousel-wheel-gestures": "8.1.0", "fuse.js": "7.4.2", "hookable": "6.1.1", "knitwork": "1.3.0", "magic-string": "0.30.21", "mlly": "1.8.2", "motion-v": "2.3.0", "ohash": "2.0.11", "pathe": "2.0.3", "reka-ui": "2.9.10", "scule": "1.3.0", "tailwind-merge": "3.6.0", "tailwind-variants": "3.2.2", "tinyglobby": "0.2.17", "ufo": "1.6.4", "unplugin": "3.0.0", "unplugin-auto-import": "21.0.0", "unplugin-vue-components": "32.1.0", "vaul-vue": "0.4.1", "vue-component-type-helpers": "3.3.5" }, "optionalDependencies": { "@internationalized/date": "3.12.1", "@internationalized/number": "3.6.6", "valibot": "1.4.1", "vue-router": "5.1.0" }, "peerDependencies": { "@tiptap/core": "3.24.0", "@tiptap/extension-bubble-menu": "3.24.0", "@tiptap/extension-code": "3.24.0", "@tiptap/extension-collaboration": "3.24.0", "@tiptap/extension-drag-handle": "3.24.0", "@tiptap/extension-drag-handle-vue-3": "3.24.0", "@tiptap/extension-floating-menu": "3.24.0", "@tiptap/extension-horizontal-rule": "3.24.0", "@tiptap/extension-image": "3.24.0", "@tiptap/extension-mention": "3.24.0", "@tiptap/extension-node-range": "3.24.0", "@tiptap/extension-placeholder": "3.24.0", "@tiptap/markdown": "3.24.0", "@tiptap/pm": "3.24.0", "@tiptap/starter-kit": "3.24.0", "@tiptap/suggestion": "3.24.0", "@tiptap/vue-3": "3.24.0", "tailwindcss": "4.3.1", "typescript": "6.0.3" }, "bin": { "nuxt-ui": "cli/index.mjs" } }, "sha512-ufcG2UsX6/SMqh/oa4pIKM5AHDDK1ZWRTe6f4+Y5/xFUh1TBD25o4eb35BGFap16Uy/iIow1ih8g8h8wcw1Wcg=="], + + "@nuxt/vite-builder": ["@nuxt/vite-builder@4.4.8", "", { "dependencies": { "@nuxt/kit": "4.4.8", "@rollup/plugin-replace": "6.0.3", "@vitejs/plugin-vue": "6.0.7", "@vitejs/plugin-vue-jsx": "5.1.5", "autoprefixer": "10.5.0", "consola": "3.4.2", "cssnano": "8.0.1", "defu": "6.1.7", "escape-string-regexp": "5.0.0", "exsolve": "1.0.8", "get-port-please": "3.2.0", "jiti": "2.7.0", "knitwork": "1.3.0", "magic-string": "0.30.21", "mlly": "1.8.2", "mocked-exports": "0.1.1", "nypm": "0.6.6", "pathe": "2.0.3", "pkg-types": "2.3.1", "postcss": "8.5.15", "seroval": "1.5.4", "std-env": "4.1.0", "ufo": "1.6.4", "unenv": "2.0.0-rc.24", "vite": "7.3.3", "vite-node": "5.3.0", "vite-plugin-checker": "0.14.1", "vue-bundle-renderer": "2.2.0" }, "optionalDependencies": { "@babel/plugin-syntax-jsx": "7.28.6", "rollup-plugin-visualizer": "7.0.1" }, "peerDependencies": { "nuxt": "4.4.8", "vue": "3.5.35" } }, "sha512-54M/k6qVY85Qeoe1m/lPZ0SANGJEbI50r5uYgh3XT942ENve3K5Nk6TMYp8i5wGGC4TWvPea+1mlCrp8rjsXag=="], + + "@nuxtjs/color-mode": ["@nuxtjs/color-mode@4.0.1", "", { "dependencies": { "@nuxt/kit": "4.4.8", "exsolve": "1.0.8", "pathe": "2.0.3", "pkg-types": "2.3.1", "semver": "7.8.2" } }, "sha512-eiA7hWXi5zNHaYKyJFCGF6i0wFZtuvR7KDXZ6jiSvwxjCpRFwphrw0MOSmNfArTSSsT1wpW+/2H92cejeVfUlg=="], + + "@oxc-minify/binding-android-arm-eabi": ["@oxc-minify/binding-android-arm-eabi@0.133.0", "", { "os": "android", "cpu": "arm" }, "sha512-D8M1+nqwLaACHZsld/t6f+cE4N97XOu5iQ88f1ZaYH4ptFzFrXo5N7wUKACTI4xmNUD+6W0Y4Apk5U2r8HLdBQ=="], + + "@oxc-minify/binding-android-arm64": ["@oxc-minify/binding-android-arm64@0.133.0", "", { "os": "android", "cpu": "arm64" }, "sha512-dnQUJdpOEh/nZfQtvGGN61VcCCcPJ2aCm+ndl8GIA2lk2GpmIBgZ9h+phLVhgUFGt2es+2AQc0xvtK7RFNsViw=="], + + "@oxc-minify/binding-darwin-arm64": ["@oxc-minify/binding-darwin-arm64@0.133.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-K6+aXlOlsCcibpTiTitQYNXWGGwea0fEKF/kGHCNB+MNqOLCkdC7wesycaABYcXcyr58DhDoJnVb8E4Hq95iVw=="], + + "@oxc-minify/binding-darwin-x64": ["@oxc-minify/binding-darwin-x64@0.133.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-BFEXHxYNwThyaO63p1VE5MOOXNGkHsHfkmajOCKXH40TfllTHQenXhpJ9mHDoF7EhaQjArpPjlDY88BuPjhurw=="], + + "@oxc-minify/binding-freebsd-x64": ["@oxc-minify/binding-freebsd-x64@0.133.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-oT5dbcXnS/cbpdXCpudAeVg/fqH1XnKhLUE/vkuRTuocjOd/GA2MoNMMhLWUvqNXO0xJnYmo2ISmDxShkItfOQ=="], + + "@oxc-minify/binding-linux-arm-gnueabihf": ["@oxc-minify/binding-linux-arm-gnueabihf@0.133.0", "", { "os": "linux", "cpu": "arm" }, "sha512-tJ3B+b7DOuTsIMXSmu5xHHCakrBqqcrp4COYd/lelOdDvkbFoDRGnwH91POUOSUEOI/WLzIMkDqAH2SZ3N2jhQ=="], + + "@oxc-minify/binding-linux-arm-musleabihf": ["@oxc-minify/binding-linux-arm-musleabihf@0.133.0", "", { "os": "linux", "cpu": "arm" }, "sha512-XMUHfdilk1KTtOM2vA1bwDso07/wkLm/GgDOO9z/ioxrZoQyjXnJRW665VXa08z2BqEgwHRc1zH9p7s6sKPQbg=="], + + "@oxc-minify/binding-linux-arm64-gnu": ["@oxc-minify/binding-linux-arm64-gnu@0.133.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UEff2jopbwJ4SndmxK06uqXrOpwWiJERJPdgDTBywwXP9QgW0p1YkQnBNt4+jK0I/hdLpbbyaCLLuUPKbaU70w=="], + + "@oxc-minify/binding-linux-arm64-musl": ["@oxc-minify/binding-linux-arm64-musl@0.133.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yqskeIapQvx7Tu/OLsepLPcGsHGzfYy9PX6gIbhaOHfF+LA2zHBKnKb587FGx+lQjHLQR0llfmoSuXQ6q2EN+A=="], + + "@oxc-minify/binding-linux-ppc64-gnu": ["@oxc-minify/binding-linux-ppc64-gnu@0.133.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-r7PnUNxRB9D/gQjCVeasoieJVUF48n43rvk/jYbGAw9sRfYGoEo/rOs0GyTZU9ttss8HzjBaerAbADbAL8K8vw=="], + + "@oxc-minify/binding-linux-riscv64-gnu": ["@oxc-minify/binding-linux-riscv64-gnu@0.133.0", "", { "os": "linux", "cpu": "none" }, "sha512-omXWC8I9lAMMjQIeadfItP5H4VDAiuU2BiVCtHMH3ktTbFq04sxscZhK4NFUUuw3fApDdXmfd7LW18q0JBHarg=="], + + "@oxc-minify/binding-linux-riscv64-musl": ["@oxc-minify/binding-linux-riscv64-musl@0.133.0", "", { "os": "linux", "cpu": "none" }, "sha512-LtFA3Hi8LVD/zuiPLKy9Aiz7N1IOj8rRhdXiW38GKQ9mAhj+Ko6IHGcTk2A7yNDA1DZBl7r+Qd4PEGWgVelPPw=="], + + "@oxc-minify/binding-linux-s390x-gnu": ["@oxc-minify/binding-linux-s390x-gnu@0.133.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-rFsPDsT1j3beSInbrFukAAlTg101PcqdVMXDioR9AgJ1180tZ8s8D+pNDpQTRmPd3956mnpAE+Cs77Xoo/QZAQ=="], + + "@oxc-minify/binding-linux-x64-gnu": ["@oxc-minify/binding-linux-x64-gnu@0.133.0", "", { "os": "linux", "cpu": "x64" }, "sha512-xlrtAmDWZI8BEmsaXMYfblWuLIY5UnnRkit1VLkmVDb5ceZRZf4oEXK1QeYf5Z33dT0WK1Ek++P+TL/ZMCpyGQ=="], + + "@oxc-minify/binding-linux-x64-musl": ["@oxc-minify/binding-linux-x64-musl@0.133.0", "", { "os": "linux", "cpu": "x64" }, "sha512-kd36CDkTkZDMNfVceNTSfpWnitE1+GjZmzJCeq8yaxsgvs/MXg8aauI2RgFjElYZIHSMyZku4pQ7Jtl3ZEYI6w=="], + + "@oxc-minify/binding-openharmony-arm64": ["@oxc-minify/binding-openharmony-arm64@0.133.0", "", { "os": "none", "cpu": "arm64" }, "sha512-pI38dJBqfkNbFoL/GEarAzGDjKGVCZTdg0a8NKh1PP9GqWleXT6HLtXE4CZ+54e+2u68qVYVBwhbWAiRfwlUZA=="], + + "@oxc-minify/binding-wasm32-wasi": ["@oxc-minify/binding-wasm32-wasi@0.133.0", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "1.1.4" }, "cpu": "none" }, "sha512-AkLr+d+LLY4/55J/TrE0srNBUpZPzyU+cygdse7yZ9AhCndryNqe2y6e8naVK0TV7n8lxBd2OGGJAkho6blAkw=="], + + "@oxc-minify/binding-win32-arm64-msvc": ["@oxc-minify/binding-win32-arm64-msvc@0.133.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-V92v7397t2073g+mSfaLHnPeoz6hA/1U4JNLeUBP87eWGZgVxDZ2qz3t3wFyYqXGJ/0VoEwdP8yrHLQQ7QzAOQ=="], + + "@oxc-minify/binding-win32-ia32-msvc": ["@oxc-minify/binding-win32-ia32-msvc@0.133.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-2DP5RbG/SSaRVtmuwgTH6Ati4+uuOJjoI88dQnC5hD0zCC90EVDXZSXyJQ5i/OxLE1UAy58Wo2DJot/OrUspzA=="], + + "@oxc-minify/binding-win32-x64-msvc": ["@oxc-minify/binding-win32-x64-msvc@0.133.0", "", { "os": "win32", "cpu": "x64" }, "sha512-PJ75c6PlBx87tau0W35J43eGCv4wrDmdZ+4ddTZAnGtZqEeCVsLdmDPOEMe2DepogqlSVUF2kGBWtnFUJ5c7Rw=="], + + "@oxc-parser/binding-android-arm-eabi": ["@oxc-parser/binding-android-arm-eabi@0.133.0", "", { "os": "android", "cpu": "arm" }, "sha512-l/44caGse+VpnY9gx0yvvc5QnnG3yG1FO3KZgYvNL1GZrfK86zIwAOgGEVlxDyRymzrU/KHiblPFpevKOmJmUA=="], + + "@oxc-parser/binding-android-arm64": ["@oxc-parser/binding-android-arm64@0.133.0", "", { "os": "android", "cpu": "arm64" }, "sha512-KUHmPMziLBp4u+zbrLdB7iWS7KshuZe+RAp7ELnY9SI9nNXBZ+dp8fiBqWOxhXqn+FQg3a4UcQhwmsJOKV8Jjg=="], + + "@oxc-parser/binding-darwin-arm64": ["@oxc-parser/binding-darwin-arm64@0.133.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-q8dWmnU/8ea2tga9w2f1PinQ5rcMPDUGkF64T189b65YMjUomET4oy5oRldOr4AwOQkneOG/Zttnz1Dvrc62wg=="], + + "@oxc-parser/binding-darwin-x64": ["@oxc-parser/binding-darwin-x64@0.133.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-cOKeIELIB2bJnCKwqx4Rdj+1Lss/U6uCbLxRySZrhyOOQa1flKhwZFjEHRHxk8fU1NKmhK5OnTdPQ4CpjuFuVw=="], + + "@oxc-parser/binding-freebsd-x64": ["@oxc-parser/binding-freebsd-x64@0.133.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-OpaSv4pW3KgFrMYQxTaS0aOE4T1DQF3qZE/4B6uqqv1KgPWWd4UQhJALi8PJPX1RRV5K7ThKXRfF7qGg2+3l1A=="], + + "@oxc-parser/binding-linux-arm-gnueabihf": ["@oxc-parser/binding-linux-arm-gnueabihf@0.133.0", "", { "os": "linux", "cpu": "arm" }, "sha512-JGK1wlGrGwxBIlVSF7KWTX1/ru6BEtf28fRROztDRkLfiW+Kxa4onnriezMIiogfn9hVw2KzYcKiLjkLR2ns8A=="], + + "@oxc-parser/binding-linux-arm-musleabihf": ["@oxc-parser/binding-linux-arm-musleabihf@0.133.0", "", { "os": "linux", "cpu": "arm" }, "sha512-yuZO533Ftonxn/iyoqQzURzLQHMspvsIyfiCSNi1t/ER4eIQaR0SsmUOUm5b/lmSig7IWIUa5/BrbEkAPwcilQ=="], + + "@oxc-parser/binding-linux-arm64-gnu": ["@oxc-parser/binding-linux-arm64-gnu@0.133.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-hvpbqT5pN2rR+3+xtWeizwfR/aZ0vGceg6TqYMl+ToxMpk9/tmnX7kSvQnfEUkoua8mhogzvIKsAkn0wxgblBA=="], + + "@oxc-parser/binding-linux-arm64-musl": ["@oxc-parser/binding-linux-arm64-musl@0.133.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-wJQGamIosQBoJHW9+S5XxrtKRo3eyJxsnS1XCPrqN0LHi8uw1pTqqTfn3t/NVuvbBg7Pumn4ez9Eidgcn0xbEg=="], + + "@oxc-parser/binding-linux-ppc64-gnu": ["@oxc-parser/binding-linux-ppc64-gnu@0.133.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Koaz32/O5+abIfrNGdyndgRvdOZ9jEf5/z3Ep9h3h2QWpdDiUQpVwgH0OcMXCs+l9aXxPLtkupqyVig9W6FDKw=="], + + "@oxc-parser/binding-linux-riscv64-gnu": ["@oxc-parser/binding-linux-riscv64-gnu@0.133.0", "", { "os": "linux", "cpu": "none" }, "sha512-R4vOjWzxhnNWHnVLeiB6jNuIifdy9vcMXZGPc7StXcxBovI+U2zg1QhZ9o8OjV80oGivs1lX5NfPLzk4IPqlRA=="], + + "@oxc-parser/binding-linux-riscv64-musl": ["@oxc-parser/binding-linux-riscv64-musl@0.133.0", "", { "os": "linux", "cpu": "none" }, "sha512-iwgBNUTHiMdxARLYuM0SBlnYeb19iw1Ea5M+4ERZupCsBMLArti6FyZ6UfFjJxIiTDr2oW2DGQFxlQVQ/dW9rA=="], + + "@oxc-parser/binding-linux-s390x-gnu": ["@oxc-parser/binding-linux-s390x-gnu@0.133.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-ZwZNo8FZmB/gVfboQl+wXilBigGl+6nQQs+nITOeAP/HcAOjiHl6XZJL9F/KXNEspODQcbjAiyjUbeCJd9a0fA=="], + + "@oxc-parser/binding-linux-x64-gnu": ["@oxc-parser/binding-linux-x64-gnu@0.133.0", "", { "os": "linux", "cpu": "x64" }, "sha512-govCvWx1dBlED3uu4qXctxpRcouu9I8Kn+DBktGCl760JtlGJzc9l/OmPJKlYWSbrRqKkMZehNeZ/4Wfma7uSA=="], + + "@oxc-parser/binding-linux-x64-musl": ["@oxc-parser/binding-linux-x64-musl@0.133.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ssTlpXD5Mq9uCssDJPzlRWqBt4Y7Zzd9i+XZhWmK/9Y6KUIuAxVYTYiI8lxcGWi0+3/Cz4A8q9UrD4NK9Y2j7g=="], + + "@oxc-parser/binding-openharmony-arm64": ["@oxc-parser/binding-openharmony-arm64@0.133.0", "", { "os": "none", "cpu": "arm64" }, "sha512-51aByfXhPtLEdWG4a2Ihdw6cPWV1ei1AarALpFdDP8MLWDLE2NuUMgbo3DERR2Kt8fT/ok1GUvBiLxVGke9uUQ=="], + + "@oxc-parser/binding-wasm32-wasi": ["@oxc-parser/binding-wasm32-wasi@0.133.0", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "1.1.4" }, "cpu": "none" }, "sha512-2e16tkKp+wDO2GTAmXfxbBcCmGEaFPIJEIRBBmVKNVXSc8/fJsSIaBGyFTPHM9ST5GNWgJcYIt94rDTks+PLwA=="], + + "@oxc-parser/binding-win32-arm64-msvc": ["@oxc-parser/binding-win32-arm64-msvc@0.133.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-KPTNDKbxH1cglrqTyVeXHb4Pk4oksz8EcE1/v8zqU7N4UXbiHfA/IwtXZ2U77fnRAWBbgVkl/lZbL7o3hRdejg=="], + + "@oxc-parser/binding-win32-ia32-msvc": ["@oxc-parser/binding-win32-ia32-msvc@0.133.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-Una1bNYv9zCavQrfnDR9wuZVB3itLjCEH4Oz7i6CwAJN/Xq9b+zbbcxmvdkKvvJt4Ngc/MBmIYlbLo3zS4TQ0A=="], + + "@oxc-parser/binding-win32-x64-msvc": ["@oxc-parser/binding-win32-x64-msvc@0.133.0", "", { "os": "win32", "cpu": "x64" }, "sha512-kjBhCiOGSYTwDJQuuZa7a94JbP8htWu7J0X1KwH74kV2K5eYf6eyJRYmkpCDvr0XEL8tMxYI4WU1VekblFCLgg=="], + + "@oxc-project/types": ["@oxc-project/types@0.133.0", "", {}, "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA=="], + + "@oxc-transform/binding-android-arm-eabi": ["@oxc-transform/binding-android-arm-eabi@0.133.0", "", { "os": "android", "cpu": "arm" }, "sha512-2A79NBpyBKgHJ0FwgC8D1hzp3x2ujyvqq/kG+M76YyDMMkxLhX6A3vjnAnfEKycOoZxuKhwYu8BF9hKq67ykIA=="], + + "@oxc-transform/binding-android-arm64": ["@oxc-transform/binding-android-arm64@0.133.0", "", { "os": "android", "cpu": "arm64" }, "sha512-dynEph/hyoSgBzd2XbNlW37NK97nU6tZMs5jrhObUxSasBV/Gv9THZrWj9AlbWiMXR07WFYE82C9axjntYyBSw=="], + + "@oxc-transform/binding-darwin-arm64": ["@oxc-transform/binding-darwin-arm64@0.133.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4hGgKOG+dZSN3xjcgNWpcihekRG7/YbbAdjyz07yv0HjzA6kdqYAhGrn84374UPO2h6etYJwsCBoM9iJHHvJ8w=="], + + "@oxc-transform/binding-darwin-x64": ["@oxc-transform/binding-darwin-x64@0.133.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-7J11/9PFkznmKuANkCAjt3znV1BcDFXQSgDiBvDxXT3Wm6995/zxrJD5zmo+5XSgY4sm+2V8/ED6ZSD3mKOC5A=="], + + "@oxc-transform/binding-freebsd-x64": ["@oxc-transform/binding-freebsd-x64@0.133.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-5EMAO0vzCpUfhn6aSjIUeJeRI2ztevHwSVr/M8sZ2VBYc79UuOfjjMCQ67LtUbgpvQtpBWkzeAHCP3L7JFYmlg=="], + + "@oxc-transform/binding-linux-arm-gnueabihf": ["@oxc-transform/binding-linux-arm-gnueabihf@0.133.0", "", { "os": "linux", "cpu": "arm" }, "sha512-z6XT8tmo9sPmCIYaFIxDelBU4wXLwwWMX2VNCMIY6bkQp5r+kRtVXYS3yLbJHMKEhRKvw/g+Z7fO9aadsGGEAw=="], + + "@oxc-transform/binding-linux-arm-musleabihf": ["@oxc-transform/binding-linux-arm-musleabihf@0.133.0", "", { "os": "linux", "cpu": "arm" }, "sha512-GQDpEV2VhHG8hT5BviDv+emi9oHYhfv+JJJWROYp+eGgWjiQMp4QZVb6Bu3kwVMzkwy0r200ToA1KThYTq53ug=="], + + "@oxc-transform/binding-linux-arm64-gnu": ["@oxc-transform/binding-linux-arm64-gnu@0.133.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-VstR+NEQAJb80ysWk2vPjEvg0JzwEjKn2hDbC/joa5zGXkCnVVCWgAGG8c6o23S981a7XRpCMcClBgeD1q9H2A=="], + + "@oxc-transform/binding-linux-arm64-musl": ["@oxc-transform/binding-linux-arm64-musl@0.133.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Ec7xJdDrnukgiz20E3iDNzAIgx1XXn8cVVsNNUpgEIAvNlXZaocqlQT8Zalk0Lv3fbkxcJ+9BuWB0ndBRHQtzg=="], + + "@oxc-transform/binding-linux-ppc64-gnu": ["@oxc-transform/binding-linux-ppc64-gnu@0.133.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-6YX38grimcigz20eYpyz6e4c9rDKzwK3i+tcDpgwYj0bWreaAOwrABmSmKplPJOorkDVlbT69wPCN+d11irBQw=="], + + "@oxc-transform/binding-linux-riscv64-gnu": ["@oxc-transform/binding-linux-riscv64-gnu@0.133.0", "", { "os": "linux", "cpu": "none" }, "sha512-WxMIzItRJR66lgaAyyqj0FFwLMpcuCV9mTFcUMQpIz8+Hey1Enk8xuv+7QpSsqCR5zRlwNr092dsFkz5cbvtrw=="], + + "@oxc-transform/binding-linux-riscv64-musl": ["@oxc-transform/binding-linux-riscv64-musl@0.133.0", "", { "os": "linux", "cpu": "none" }, "sha512-+x6dnO87986rjVNjcF0tg8wVS0e/SH8nzLa/X0Wsh7jtEniN7buvR8iqZm8pnsfaZ8DH5F4GCSZpoPRrd9jJ6w=="], + + "@oxc-transform/binding-linux-s390x-gnu": ["@oxc-transform/binding-linux-s390x-gnu@0.133.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-oEyQudXIwWM/+v0vZzPbAi25YMWyvjtQYYjuSrhMEQwe7ZEMDXscX7U1j6alrVdZq2DtCMeror3X/Dv7p/JUwg=="], + + "@oxc-transform/binding-linux-x64-gnu": ["@oxc-transform/binding-linux-x64-gnu@0.133.0", "", { "os": "linux", "cpu": "x64" }, "sha512-G8P/OadKTbyUHz5TK63sDDtUHwn2SXG/o0oGo4GGTzBu70xmUSN5/ZUgpyl6ypAmbshoyw8nC7+msb3BjzHxaA=="], + + "@oxc-transform/binding-linux-x64-musl": ["@oxc-transform/binding-linux-x64-musl@0.133.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Oi/fyOzZ+aytmmsRND5pGgvux4n++v9cG4qNFiXj7qFwSqBKWZHBq7cJLXqbH1I81pyI3kvU1Za+1qk3afXuwg=="], + + "@oxc-transform/binding-openharmony-arm64": ["@oxc-transform/binding-openharmony-arm64@0.133.0", "", { "os": "none", "cpu": "arm64" }, "sha512-/ZElgq+/tcga27X2G2AUpxcYX0baX94Gz658w6Zz2P+6Kr06bfYSrdtC0P7oPrbu3Gy/6kpiSoJPgZy8R2IjYQ=="], + + "@oxc-transform/binding-wasm32-wasi": ["@oxc-transform/binding-wasm32-wasi@0.133.0", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "1.1.4" }, "cpu": "none" }, "sha512-GANcoEa8Nzza7saxdb4qWO24U6jk4nK6G+g87lGp8TTU45CUvWf1Igdze2+NrebgiwOy6F1/h6Esag4DM3JTtQ=="], + + "@oxc-transform/binding-win32-arm64-msvc": ["@oxc-transform/binding-win32-arm64-msvc@0.133.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-2+uDo/+ZvGQu10J8xryg/l5PdBt2vXPtf+0aIosVKJavqCaKcBDdo95OUaEulx0bqvoytAQ4yyz2gcPZ40mjcQ=="], + + "@oxc-transform/binding-win32-ia32-msvc": ["@oxc-transform/binding-win32-ia32-msvc@0.133.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-zpPIZ1S3JHmSEFyyGyPYCwhOiNLyfaPifYxK8BQY21JXyHglu/wUr3/ESFrXb+XegEy/iBlWbzr3FzPtcq1MUw=="], + + "@oxc-transform/binding-win32-x64-msvc": ["@oxc-transform/binding-win32-x64-msvc@0.133.0", "", { "os": "win32", "cpu": "x64" }, "sha512-cADrfLvc/VeyvpvQS+t5ktqfyqyyGANZC5NHp++JAElacfXqq/+k8bYkjqMWzNZ3HxkJtL1qDHfZZCA9+4hlSQ=="], + + "@package-json/types": ["@package-json/types@0.0.12", "", {}, "sha512-uu43FGU34B5VM9mCNjXCwLaGHYjXdNincqKLaraaCW+7S2+SmiBg1Nv8bPnmschrIfZmfKNY9f3fC376MRrObw=="], + + "@parcel/watcher": ["@parcel/watcher@2.5.6", "", { "dependencies": { "detect-libc": "2.1.2", "is-glob": "4.0.3", "node-addon-api": "7.1.1", "picomatch": "4.0.4" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.6", "@parcel/watcher-darwin-arm64": "2.5.6", "@parcel/watcher-darwin-x64": "2.5.6", "@parcel/watcher-freebsd-x64": "2.5.6", "@parcel/watcher-linux-arm-glibc": "2.5.6", "@parcel/watcher-linux-arm-musl": "2.5.6", "@parcel/watcher-linux-arm64-glibc": "2.5.6", "@parcel/watcher-linux-arm64-musl": "2.5.6", "@parcel/watcher-linux-x64-glibc": "2.5.6", "@parcel/watcher-linux-x64-musl": "2.5.6", "@parcel/watcher-win32-arm64": "2.5.6", "@parcel/watcher-win32-ia32": "2.5.6", "@parcel/watcher-win32-x64": "2.5.6" } }, "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ=="], + + "@parcel/watcher-android-arm64": ["@parcel/watcher-android-arm64@2.5.6", "", { "os": "android", "cpu": "arm64" }, "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A=="], + + "@parcel/watcher-darwin-arm64": ["@parcel/watcher-darwin-arm64@2.5.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA=="], + + "@parcel/watcher-darwin-x64": ["@parcel/watcher-darwin-x64@2.5.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg=="], + + "@parcel/watcher-freebsd-x64": ["@parcel/watcher-freebsd-x64@2.5.6", "", { "os": "freebsd", "cpu": "x64" }, "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng=="], + + "@parcel/watcher-linux-arm-glibc": ["@parcel/watcher-linux-arm-glibc@2.5.6", "", { "os": "linux", "cpu": "arm" }, "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ=="], + + "@parcel/watcher-linux-arm-musl": ["@parcel/watcher-linux-arm-musl@2.5.6", "", { "os": "linux", "cpu": "arm" }, "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg=="], + + "@parcel/watcher-linux-arm64-glibc": ["@parcel/watcher-linux-arm64-glibc@2.5.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA=="], + + "@parcel/watcher-linux-arm64-musl": ["@parcel/watcher-linux-arm64-musl@2.5.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA=="], + + "@parcel/watcher-linux-x64-glibc": ["@parcel/watcher-linux-x64-glibc@2.5.6", "", { "os": "linux", "cpu": "x64" }, "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ=="], + + "@parcel/watcher-linux-x64-musl": ["@parcel/watcher-linux-x64-musl@2.5.6", "", { "os": "linux", "cpu": "x64" }, "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg=="], + + "@parcel/watcher-wasm": ["@parcel/watcher-wasm@2.5.6", "", { "dependencies": { "is-glob": "4.0.3", "picomatch": "4.0.4" } }, "sha512-byAiBZ1t3tXQvc8dMD/eoyE7lTXYorhn+6uVW5AC+JGI1KtJC/LvDche5cfUE+qiefH+Ybq0bUCJU0aB1cSHUA=="], + + "@parcel/watcher-win32-arm64": ["@parcel/watcher-win32-arm64@2.5.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q=="], + + "@parcel/watcher-win32-ia32": ["@parcel/watcher-win32-ia32@2.5.6", "", { "os": "win32", "cpu": "ia32" }, "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g=="], + + "@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.6", "", { "os": "win32", "cpu": "x64" }, "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw=="], + + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + + "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], + + "@poppinss/colors": ["@poppinss/colors@4.1.6", "", { "dependencies": { "kleur": "4.1.5" } }, "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg=="], + + "@poppinss/dumper": ["@poppinss/dumper@0.7.0", "", { "dependencies": { "@poppinss/colors": "4.1.6", "@sindresorhus/is": "7.2.0", "supports-color": "10.2.2" } }, "sha512-0UTYalzk2t6S4rA2uHOz5bSSW2CHdv4vggJI6Alg90yvl0UgXs6XSXpH96OH+bRkX4J/06djv29pqXJ0lq5Kag=="], + + "@poppinss/exception": ["@poppinss/exception@1.2.3", "", {}, "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], + + "@rollup/plugin-alias": ["@rollup/plugin-alias@6.0.0", "", { "optionalDependencies": { "rollup": "4.60.4" } }, "sha512-tPCzJOtS7uuVZd+xPhoy5W4vThe6KWXNmsFCNktaAh5RTqcLiSfT4huPQIXkgJ6YCOjJHvecOAzQxLFhPxKr+g=="], + + "@rollup/plugin-commonjs": ["@rollup/plugin-commonjs@29.0.2", "", { "dependencies": { "@rollup/pluginutils": "5.3.0", "commondir": "1.0.1", "estree-walker": "2.0.2", "fdir": "6.5.0", "is-reference": "1.2.1", "magic-string": "0.30.21", "picomatch": "4.0.4" }, "optionalDependencies": { "rollup": "4.60.4" } }, "sha512-S/ggWH1LU7jTyi9DxZOKyxpVd4hF/OZ0JrEbeLjXk/DFXwRny0tjD2c992zOUYQobLrVkRVMDdmHP16HKP7GRg=="], + + "@rollup/plugin-inject": ["@rollup/plugin-inject@5.0.5", "", { "dependencies": { "@rollup/pluginutils": "5.3.0", "estree-walker": "2.0.2", "magic-string": "0.30.21" }, "optionalDependencies": { "rollup": "4.60.4" } }, "sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg=="], + + "@rollup/plugin-json": ["@rollup/plugin-json@6.1.0", "", { "dependencies": { "@rollup/pluginutils": "5.3.0" }, "optionalDependencies": { "rollup": "4.60.4" } }, "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA=="], + + "@rollup/plugin-node-resolve": ["@rollup/plugin-node-resolve@16.0.3", "", { "dependencies": { "@rollup/pluginutils": "5.3.0", "@types/resolve": "1.20.2", "deepmerge": "4.3.1", "is-module": "1.0.0", "resolve": "1.22.12" }, "optionalDependencies": { "rollup": "4.60.4" } }, "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg=="], + + "@rollup/plugin-replace": ["@rollup/plugin-replace@6.0.3", "", { "dependencies": { "@rollup/pluginutils": "5.3.0", "magic-string": "0.30.21" }, "optionalDependencies": { "rollup": "4.60.4" } }, "sha512-J4RZarRvQAm5IF0/LwUUg+obsm+xZhYnbMXmXROyoSE1ATJe3oXSb9L5MMppdxP2ylNSjv6zFBwKYjcKMucVfA=="], + + "@rollup/plugin-terser": ["@rollup/plugin-terser@1.0.0", "", { "dependencies": { "serialize-javascript": "7.0.5", "smob": "1.6.2", "terser": "5.47.1" }, "optionalDependencies": { "rollup": "4.60.4" } }, "sha512-FnCxhTBx6bMOYQrar6C8h3scPt8/JwIzw3+AJ2K++6guogH5fYaIFia+zZuhqv0eo1RN7W1Pz630SyvLbDjhtQ=="], + + "@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "1.0.9", "estree-walker": "2.0.2", "picomatch": "4.0.4" }, "optionalDependencies": { "rollup": "4.60.4" } }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.4", "", { "os": "android", "cpu": "arm" }, "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.4", "", { "os": "android", "cpu": "arm64" }, "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.60.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.60.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.60.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.60.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.60.4", "", { "os": "linux", "cpu": "arm" }, "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.60.4", "", { "os": "linux", "cpu": "arm" }, "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.60.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.60.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.60.4", "", { "os": "linux", "cpu": "none" }, "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ=="], + + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.60.4", "", { "os": "linux", "cpu": "none" }, "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.60.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg=="], + + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.60.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.60.4", "", { "os": "linux", "cpu": "none" }, "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.60.4", "", { "os": "linux", "cpu": "none" }, "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.60.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.60.4", "", { "os": "linux", "cpu": "x64" }, "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.60.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg=="], + + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.60.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.60.4", "", { "os": "none", "cpu": "arm64" }, "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.60.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.60.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.60.4", "", { "os": "win32", "cpu": "x64" }, "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.4", "", { "os": "win32", "cpu": "x64" }, "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw=="], + + "@simple-git/args-pathspec": ["@simple-git/args-pathspec@1.0.3", "", {}, "sha512-ngJMaHlsWDTfjyq9F3VIQ8b7NXbBLq5j9i5bJ6XLYtD6qlDXT7fdKY2KscWWUF8t18xx052Y/PUO1K1TRc9yKA=="], + + "@simple-git/argv-parser": ["@simple-git/argv-parser@1.1.1", "", { "dependencies": { "@simple-git/args-pathspec": "1.0.3" } }, "sha512-Q9lBcfQ+VQCpQqGJFHe5yooOS5hGdLFFbJ5R+R5aDsnkPCahtn1hSkMcORX65J2Z5lxSkD0lQorMsncuBQxYUw=="], + + "@sindresorhus/base62": ["@sindresorhus/base62@1.0.0", "", {}, "sha512-TeheYy0ILzBEI/CO55CP6zJCSdSWeRtGnHy8U8dWSUH4I68iqTsy7HkMktR4xakThc9jotkPQUXT4ITdbV7cHA=="], + + "@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="], + + "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="], + + "@speed-highlight/core": ["@speed-highlight/core@1.2.15", "", {}, "sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw=="], + + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@stylistic/eslint-plugin": ["@stylistic/eslint-plugin@5.10.0", "", { "dependencies": { "@eslint-community/eslint-utils": "4.9.1", "@typescript-eslint/types": "8.61.0", "eslint-visitor-keys": "4.2.1", "espree": "10.4.0", "estraverse": "5.3.0", "picomatch": "4.0.4" }, "peerDependencies": { "eslint": "10.6.0" } }, "sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ=="], + + "@swc/helpers": ["@swc/helpers@0.5.21", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg=="], + + "@tailwindcss/node": ["@tailwindcss/node@4.3.1", "", { "dependencies": { "@jridgewell/remapping": "2.3.5", "enhanced-resolve": "5.21.6", "jiti": "2.7.0", "lightningcss": "1.32.0", "magic-string": "0.30.21", "source-map-js": "1.2.1", "tailwindcss": "4.3.1" } }, "sha512-6NDaqRoAMSXD1mr/RXu0HBvNE9a2n5tHPsxu9XHLws8o4Twes5rBM2205SUUiJ9goAtadrN6xTGX0UDEwp/N4A=="], + + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.3.1", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.3.1", "@tailwindcss/oxide-darwin-arm64": "4.3.1", "@tailwindcss/oxide-darwin-x64": "4.3.1", "@tailwindcss/oxide-freebsd-x64": "4.3.1", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.1", "@tailwindcss/oxide-linux-arm64-gnu": "4.3.1", "@tailwindcss/oxide-linux-arm64-musl": "4.3.1", "@tailwindcss/oxide-linux-x64-gnu": "4.3.1", "@tailwindcss/oxide-linux-x64-musl": "4.3.1", "@tailwindcss/oxide-wasm32-wasi": "4.3.1", "@tailwindcss/oxide-win32-arm64-msvc": "4.3.1", "@tailwindcss/oxide-win32-x64-msvc": "4.3.1" } }, "sha512-yVPyo8RNkabVr3O2EhHEE0Rewu7YKzc1DhIqfL46LKveFrmu9XbDazNOJY7/GRuvw1h6u3utWnR29H/p5JPlgA=="], + + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.3.1", "", { "os": "android", "cpu": "arm64" }, "sha512-SVlyf61g374l5cHyg8x9kf5xmLcOaxvOTsbsqDnSsDJaKOEFZ7GCvi84VAVGpxojYOs1+3K6M0UjXfqPU8vmOQ=="], + + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.3.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-hVnWLwv+e/l7c4WKyVtHVrIPvYdqWHjRB3MDIqARynzFtnQg85kmQEFCbV9Ja0VVx4xXTIiDWY60Y7iz/iNoDA=="], + + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.3.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-Cf7abu0WVgbhU7ANgPUnSAvm7nCvMweusHb8FnaHlLfv/Caq4GYaEZg7ZImzzmjx4lIAfuS8q+eLIS7A7IzxIg=="], + + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.3.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-ZZqzX2Y+GXtXXfqSfpJhDm60OoZfvLHLCgm+J7NVqgHHJjG/m9ugZI77RwTsVd4fnBJuCFP6Ae6kTJb71UdS8g=="], + + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.3.1", "", { "os": "linux", "cpu": "arm" }, "sha512-/Ah/xik0LaMYfv9DZ0S/t4pBlBNYOcqtRwusjgovHkvT8ixueWCLyJjsaF5kQIckjb4IT8Q6K6p/iPmZMixYgg=="], + + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.3.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-gqdFoVJlw444GvpnheZLHmvTzSxI/cOUUh2KSNejQjTcYkW062SVD+En0rUgD+QV91bz1XGIGtt1HJd48xUGbQ=="], + + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.3.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Bwv9KwOvE0VKa86xPFif9b9c3Y1NxOV1P0gLti/IYaWEsQYZXDlxfGEtA8mdDZ7SG3wyNXAWYT5SIn3giL57oA=="], + + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.3.1", "", { "os": "linux", "cpu": "x64" }, "sha512-Ymi8O8T15HYQdOUWUtTI6ldN0neHP85FC+Qz32xTcZ7iJXtem/x8ITev0o1e9e5rkqj4lONZfTRLvkmin1+tKg=="], + + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.3.1", "", { "os": "linux", "cpu": "x64" }, "sha512-M+P/91qJ6uILLw4k2G93GMDRAXj61SMvFQYt39AqvUqYgExXpLL5aepfns7sj4HiAQeolirQF9E0lzRvdf4zPQ=="], + + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.3.1", "", { "cpu": "none" }, "sha512-zsM8uOeqvVGHsAXsJxsT28ttosFahLJKCLOTUBqRAtKnVgGSRitds9T432QiT8b77Yga7JIBkulIRRlJPtYhRA=="], + + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.3.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-aiNvSq9BsVk8V513lDKlrCFAgf8qBMPZTpgEhInL+NwQqs97mYmupVMrPrgBBSL8Pv/0zXu9MrMF9rMun1ZeNg=="], + + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.3.1", "", { "os": "win32", "cpu": "x64" }, "sha512-xDEyu1rg290472FEGaKHnzyDyh5QH+AlWvsU5hMoMtPpzmKlRI0jaYKCgSHDYtaQWZOYbMaduSyCwFwY4n1HmA=="], + + "@tailwindcss/postcss": ["@tailwindcss/postcss@4.3.1", "", { "dependencies": { "@alloc/quick-lru": "5.2.0", "@tailwindcss/node": "4.3.1", "@tailwindcss/oxide": "4.3.1", "postcss": "8.5.15", "tailwindcss": "4.3.1" } }, "sha512-dNJuNbdEJT/SWRuXTYP1WSamelsz3ztkUsdtWQPjrexysrTpaEPM40P/71knXiXLYEojqPOEGitVLLpPMS5T6A=="], + + "@tailwindcss/vite": ["@tailwindcss/vite@4.3.1", "", { "dependencies": { "@tailwindcss/node": "4.3.1", "@tailwindcss/oxide": "4.3.1", "tailwindcss": "4.3.1" }, "peerDependencies": { "vite": "7.3.3" } }, "sha512-hItDHuIIlEV61R+faXu66s1K36aTurO/Qw0e45Vskz57gXl9pWOT6eg3zmcEui6CZXddbN7zd41bwmvag4JGwQ=="], + + "@tanstack/table-core": ["@tanstack/table-core@8.21.3", "", {}, "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg=="], + + "@tanstack/virtual-core": ["@tanstack/virtual-core@3.17.1", "", {}, "sha512-VZyW2Uiml5tmBZwPGrSD3Sz73OxzljQMCmzYHsUTPEuTsERf5xwa+uWb01xEzkz3ZSYTjj8NEb/mKHvgKxyZdA=="], + + "@tanstack/vue-table": ["@tanstack/vue-table@8.21.3", "", { "dependencies": { "@tanstack/table-core": "8.21.3" }, "peerDependencies": { "vue": "3.5.35" } }, "sha512-rusRyd77c5tDPloPskctMyPLFEQUeBzxdQ+2Eow4F7gDPlPOB1UnnhzfpdvqZ8ZyX2rRNGmqNnQWm87OI2OQPw=="], + + "@tanstack/vue-virtual": ["@tanstack/vue-virtual@3.13.29", "", { "dependencies": { "@tanstack/virtual-core": "3.17.1" }, "peerDependencies": { "vue": "3.5.35" } }, "sha512-MWb9tNHjpar3sP34b8+3A4I5j9akveoPXIYqqp7/ipyWd49a/kso+1S1LqEmAVR/+g/k1WWTJC4ktvdCGWgXYQ=="], + + "@tiptap/core": ["@tiptap/core@3.24.0", "", { "peerDependencies": { "@tiptap/pm": "3.24.0" } }, "sha512-GTAsXAI32p4hEZgPzvUv2RPrObxamy9AFhmhG10fXSvN/cDUs8naEYVIqDV3Sh99jMwQEbTFKW1E1mcspsY6ow=="], + + "@tiptap/extension-blockquote": ["@tiptap/extension-blockquote@3.24.0", "", { "peerDependencies": { "@tiptap/core": "3.24.0" } }, "sha512-DgwEEJ1GbDQcT054ynxoaZGmB9apGeUklPrinq9o6xdLHpdg+bO9HCQzggdB8n21VLLglb8jfAEWsVNwh3eASQ=="], + + "@tiptap/extension-bold": ["@tiptap/extension-bold@3.24.0", "", { "peerDependencies": { "@tiptap/core": "3.24.0" } }, "sha512-CujogYaynasklFKHADUseuvj8X2FnWktTCCo3Hl+nlyRvBTmm5TK2aqiamg3v2P4dBh3O6a70mo8BfRJPuiR1g=="], + + "@tiptap/extension-bubble-menu": ["@tiptap/extension-bubble-menu@3.24.0", "", { "dependencies": { "@floating-ui/dom": "1.7.6" }, "peerDependencies": { "@tiptap/core": "3.24.0", "@tiptap/pm": "3.24.0" } }, "sha512-jRXD+JPu9ayvq78g8hsCxx4q/qUFtrdfIYirRSf5YUseuuUbtfrq83AsGabcygpUTefjJkMQoXNITkh6294Ggw=="], + + "@tiptap/extension-bullet-list": ["@tiptap/extension-bullet-list@3.24.0", "", { "peerDependencies": { "@tiptap/extension-list": "3.24.0" } }, "sha512-IOpAm5c4XVVVvkOef+V9XYMVpea+3MgBpCQgn83UQRlwO9eIMwmcyxOznu7gQPQVShTEpkt4T6uK+ZN9o8meIA=="], + + "@tiptap/extension-code": ["@tiptap/extension-code@3.24.0", "", { "peerDependencies": { "@tiptap/core": "3.24.0" } }, "sha512-MAQtrPRQ+HRmcGotWbksdIGeH1gqayFAdvi4lNGeFT7taHXP1o1XD7CQp7iYIKmg8IU4/MQ+RdetSfuC1A9edQ=="], + + "@tiptap/extension-code-block": ["@tiptap/extension-code-block@3.24.0", "", { "peerDependencies": { "@tiptap/core": "3.24.0", "@tiptap/pm": "3.24.0" } }, "sha512-NZglw4oHoH6oJ5+HvxxQCYk+wODJmsxzUpRQdsOmje08sekQH+Zt9i4UKimBhg4urpd5r+dKXTslab9a5eQ86w=="], + + "@tiptap/extension-collaboration": ["@tiptap/extension-collaboration@3.24.0", "", { "peerDependencies": { "@tiptap/core": "3.24.0", "@tiptap/pm": "3.24.0", "@tiptap/y-tiptap": "3.0.3", "yjs": "13.6.30" } }, "sha512-PF9rFZrZtgr7xemnzQaU1uScUz4GwDjE+vLMosugUJxaoz/zSsHRXQ00dHM32MKxBzbWg3A4ZQTHB7YzAtyl3Q=="], + + "@tiptap/extension-document": ["@tiptap/extension-document@3.24.0", "", { "peerDependencies": { "@tiptap/core": "3.24.0" } }, "sha512-yxgM3+yXy2XZzEwH43y2Kp8D1BkblxEWLXqo0YCoAKtxyKCcEaT8kdlf70kS7D0+VSzYU4D0iN7VdQIYHcL2mA=="], + + "@tiptap/extension-drag-handle": ["@tiptap/extension-drag-handle@3.24.0", "", { "dependencies": { "@floating-ui/dom": "1.7.6" }, "peerDependencies": { "@tiptap/core": "3.24.0", "@tiptap/extension-collaboration": "3.24.0", "@tiptap/extension-node-range": "3.24.0", "@tiptap/pm": "3.24.0", "@tiptap/y-tiptap": "3.0.3" } }, "sha512-DMW2Dx89aS28+FXlpl5nlkZT4dhqdaAO6W76qXVUPIHFvO5yWP0q5UzAPGW5JEBOI+LxWj0AkTDMrX0XrLw9oA=="], + + "@tiptap/extension-drag-handle-vue-3": ["@tiptap/extension-drag-handle-vue-3@3.24.0", "", { "peerDependencies": { "@tiptap/extension-drag-handle": "3.24.0", "@tiptap/pm": "3.24.0", "@tiptap/vue-3": "3.24.0", "vue": "3.5.35" } }, "sha512-tk9yKgGPYMRAKeCtEdza532EZzbIID7pO6sSLqmteyDH3MxuaJZLhi6tqRheWm55SlPTTJzAUZuY8EchzPpC7g=="], + + "@tiptap/extension-dropcursor": ["@tiptap/extension-dropcursor@3.24.0", "", { "peerDependencies": { "@tiptap/extensions": "3.24.0" } }, "sha512-Dbv1c5LnvG3PT+yEbCNroyOeeUkHq9wcir2pbC7wri7g7d2sCi0+HvKH0MAxLwY3j5NJJSiSyG2ypMaXOAs4sg=="], + + "@tiptap/extension-floating-menu": ["@tiptap/extension-floating-menu@3.24.0", "", { "peerDependencies": { "@floating-ui/dom": "1.7.6", "@tiptap/core": "3.24.0", "@tiptap/pm": "3.24.0" } }, "sha512-7QEbf3mUzFAkejjQGX9f0L507oMtnOBRwHt2skUTR+9yXgudsN8zaDBSSRHLeMWGk9b7L293ZMA6zCRrZaHrfA=="], + + "@tiptap/extension-gapcursor": ["@tiptap/extension-gapcursor@3.24.0", "", { "peerDependencies": { "@tiptap/extensions": "3.24.0" } }, "sha512-CzCP5/jni5RFwW9jCfBO6auh83GbaioMTpSk6tyR3sd+CbwlBcUdsJFGJkbaRdiSS9dgIyi+6hRbhjpYdHcp+w=="], + + "@tiptap/extension-hard-break": ["@tiptap/extension-hard-break@3.24.0", "", { "peerDependencies": { "@tiptap/core": "3.24.0" } }, "sha512-T/ZEBiHQPMyTqDvXG0tiqBToNeuSemIPmNtdoGSgBN/degVl7VJZqQIrLIvOUHfjf3QkRs7TE/mcqTJsIboO/g=="], + + "@tiptap/extension-heading": ["@tiptap/extension-heading@3.24.0", "", { "peerDependencies": { "@tiptap/core": "3.24.0" } }, "sha512-GCSgapIzQPqEGNcVGE0/Pcjg5wITMLYJlrS3GGVw7BPmECJwgexcoOsEwkxtzJnXT/HpFXbvOFW43sM0KeHSjg=="], + + "@tiptap/extension-horizontal-rule": ["@tiptap/extension-horizontal-rule@3.24.0", "", { "peerDependencies": { "@tiptap/core": "3.24.0", "@tiptap/pm": "3.24.0" } }, "sha512-DFzWJTrb23x+qssLLs85vEyho8ItUGp3RY9XUsVTIAGZn5IsoUw8wMsvIBlH1ux4Ch7gLchtcD6kpTdMdrL9kw=="], + + "@tiptap/extension-image": ["@tiptap/extension-image@3.24.0", "", { "peerDependencies": { "@tiptap/core": "3.24.0" } }, "sha512-mH+bvsX2cPKuZzV7YMQi4FV2YbDP+Kmq36bY+Bwi/x4mYUc8u0cjQxcu8RzLO7GtsgUJPxGMwfkQxmDqXFLZvw=="], + + "@tiptap/extension-italic": ["@tiptap/extension-italic@3.24.0", "", { "peerDependencies": { "@tiptap/core": "3.24.0" } }, "sha512-mf3cbNlbMPUNj3IyUkIke+o3ZpOUrtVeY5Yqs5IM/VhkUUh/PdIzqw74VuqEAJ0Z4oZ6nNDHeYLrl3Be1j99lQ=="], + + "@tiptap/extension-link": ["@tiptap/extension-link@3.24.0", "", { "dependencies": { "linkifyjs": "4.3.3" }, "peerDependencies": { "@tiptap/core": "3.24.0", "@tiptap/pm": "3.24.0" } }, "sha512-MwMoNGG2mL5XGFV1tEGunBRglwsIbW+ZOB2QnKiv+Mcbi2JCWMrorndJZBqpVPR5nM+Bef2KnpchEJmYlQLvKQ=="], + + "@tiptap/extension-list": ["@tiptap/extension-list@3.24.0", "", { "peerDependencies": { "@tiptap/core": "3.24.0", "@tiptap/pm": "3.24.0" } }, "sha512-GcxDVMMmDGj7OFTBrV7JpVgr5wxlr2vmjwH7U8QxZX7OJI5vrsMYl/U6KRTvUpG8wP+Zmo5jRlLM+BbL+a/W3g=="], + + "@tiptap/extension-list-item": ["@tiptap/extension-list-item@3.24.0", "", { "peerDependencies": { "@tiptap/extension-list": "3.24.0" } }, "sha512-zl/U3viJiV9OzkKM37AHIUN1af1TSLrcbHUUoNLkfJ33Nq+NlpaXpCVK0rKRqiLFJf7zk/a5KWG5CrOy9TxjKA=="], + + "@tiptap/extension-list-keymap": ["@tiptap/extension-list-keymap@3.24.0", "", { "peerDependencies": { "@tiptap/extension-list": "3.24.0" } }, "sha512-69fKcrngYGEKWNn4R5oLwl0YuV3FY4kufEValVcjnihUmqJTE1vx+fwctYoTsOGnIuNGpUIQ7f9YDD/0w34qBw=="], + + "@tiptap/extension-mention": ["@tiptap/extension-mention@3.24.0", "", { "peerDependencies": { "@tiptap/core": "3.24.0", "@tiptap/pm": "3.24.0", "@tiptap/suggestion": "3.24.0" } }, "sha512-c68AYrEoHJ4vlBvt5stBUTveKXiNwt5BxaQxgq2R4OXjc3VMoh+XJqo1bBbMNHEJfuGMNpcdfZ2zf09jnBf8/A=="], + + "@tiptap/extension-node-range": ["@tiptap/extension-node-range@3.24.0", "", { "peerDependencies": { "@tiptap/core": "3.24.0", "@tiptap/pm": "3.24.0" } }, "sha512-JUrhuKD5raii6IsARETNq3seAXUB9UpQGjeSJKBKBOv10PBAq0RLKlI0lbv7t9FR4vfZE7is/XpvoZ0v2Vr9kw=="], + + "@tiptap/extension-ordered-list": ["@tiptap/extension-ordered-list@3.24.0", "", { "peerDependencies": { "@tiptap/extension-list": "3.24.0" } }, "sha512-buRa6bmBDw0TztH+rAcusIye14DiLDS+yGheo6GiNCTD7kKJnksXagBdxvip3jhW5sx7gyAKvoBmvGSg1BbsGA=="], + + "@tiptap/extension-paragraph": ["@tiptap/extension-paragraph@3.24.0", "", { "peerDependencies": { "@tiptap/core": "3.24.0" } }, "sha512-wD06aB6hO7LgcrlhGiw7I64k2tus9kNoICX5R+UecBSB1DVJdzKvXoXL2kPNv4DqYvljHdkIeK/OpuOTQd6MJA=="], + + "@tiptap/extension-placeholder": ["@tiptap/extension-placeholder@3.24.0", "", { "peerDependencies": { "@tiptap/extensions": "3.24.0" } }, "sha512-3jfYYCIuwMADhvZ92vB6c80YiTmgTSFR23JqyLps8qkQtV59Va5CBYpwJtSs1+VrbCVnNxhZBHhVXusZO3uRkA=="], + + "@tiptap/extension-strike": ["@tiptap/extension-strike@3.24.0", "", { "peerDependencies": { "@tiptap/core": "3.24.0" } }, "sha512-sfN1iQs6Fdlorrfe8wipDkTPwu/Egx3s2fkY7TAWusTGFHwlovuRUGFKqCL9dI4N3u6uqUMpEuWmQNgv+aQGjQ=="], + + "@tiptap/extension-text": ["@tiptap/extension-text@3.24.0", "", { "peerDependencies": { "@tiptap/core": "3.24.0" } }, "sha512-Im7keLPEihxm3+LyF+drYCoaOY5hlq35lvHAp/el6M8pJ/scts88HrYpdR1Yc4BtpZBIhfHSyWgPaupI4qwdeg=="], + + "@tiptap/extension-underline": ["@tiptap/extension-underline@3.24.0", "", { "peerDependencies": { "@tiptap/core": "3.24.0" } }, "sha512-D4W4X3UMq9dLVIOfPB9+UodQ4eAJ8yDcm8qFWAwq0a15YWH6bnwulCuIdV+U5dEG+yaRxN8haB9GrrID9jmrSA=="], + + "@tiptap/extensions": ["@tiptap/extensions@3.24.0", "", { "peerDependencies": { "@tiptap/core": "3.24.0", "@tiptap/pm": "3.24.0" } }, "sha512-z6gRYzy2ucJp07OQ0F2W07NxyhMTxPYH1ia2eGiQkWax1i56oExpjMsDHP8THWlg8Tb7NnbfKpkfh881EsmofA=="], + + "@tiptap/markdown": ["@tiptap/markdown@3.24.0", "", { "dependencies": { "marked": "17.0.6" }, "peerDependencies": { "@tiptap/core": "3.24.0", "@tiptap/pm": "3.24.0" } }, "sha512-EIEQmH8tOIWAxVnRpYSALIQCU8dVaGQoEVvmsa6B2B/zZeIsBSEZzcVVE2yGEVZtShf3ag37Szr6Lu7lTor3sw=="], + + "@tiptap/pm": ["@tiptap/pm@3.24.0", "", { "dependencies": { "prosemirror-changeset": "2.4.1", "prosemirror-commands": "1.7.1", "prosemirror-dropcursor": "1.8.2", "prosemirror-gapcursor": "1.4.1", "prosemirror-history": "1.5.0", "prosemirror-inputrules": "1.5.1", "prosemirror-keymap": "1.2.3", "prosemirror-model": "1.25.7", "prosemirror-schema-list": "1.5.1", "prosemirror-state": "1.4.4", "prosemirror-tables": "1.8.5", "prosemirror-transform": "1.12.0", "prosemirror-view": "1.41.8" } }, "sha512-QQP/78ryOZDN99gNBV7dgh69/8AYaOYQYFklq/iR+ZRFaaL3+qqHFvPVJapGkzPdymBgNJ34xjFM8n5pJ4QmMg=="], + + "@tiptap/starter-kit": ["@tiptap/starter-kit@3.24.0", "", { "dependencies": { "@tiptap/core": "3.24.0", "@tiptap/extension-blockquote": "3.24.0", "@tiptap/extension-bold": "3.24.0", "@tiptap/extension-bullet-list": "3.24.0", "@tiptap/extension-code": "3.24.0", "@tiptap/extension-code-block": "3.24.0", "@tiptap/extension-document": "3.24.0", "@tiptap/extension-dropcursor": "3.24.0", "@tiptap/extension-gapcursor": "3.24.0", "@tiptap/extension-hard-break": "3.24.0", "@tiptap/extension-heading": "3.24.0", "@tiptap/extension-horizontal-rule": "3.24.0", "@tiptap/extension-italic": "3.24.0", "@tiptap/extension-link": "3.24.0", "@tiptap/extension-list": "3.24.0", "@tiptap/extension-list-item": "3.24.0", "@tiptap/extension-list-keymap": "3.24.0", "@tiptap/extension-ordered-list": "3.24.0", "@tiptap/extension-paragraph": "3.24.0", "@tiptap/extension-strike": "3.24.0", "@tiptap/extension-text": "3.24.0", "@tiptap/extension-underline": "3.24.0", "@tiptap/extensions": "3.24.0", "@tiptap/pm": "3.24.0" } }, "sha512-Ef4PCP96vcY2GonXN9J0M8iC6zvxPTmQlL/QZiCwuYqqnH/hNpYIjNSQdTndiDpxRKofa32Sr2HWktgEnL32Bg=="], + + "@tiptap/suggestion": ["@tiptap/suggestion@3.24.0", "", { "peerDependencies": { "@tiptap/core": "3.24.0", "@tiptap/pm": "3.24.0" } }, "sha512-UlLIij1fxFy7tbCmqUoInWRijzsi8hsbaXKCx6L3KvLXtxHb4hMnDhd6W++rOk9Q1hDpmNf8qNIX498q/ZNstw=="], + + "@tiptap/vue-3": ["@tiptap/vue-3@3.24.0", "", { "optionalDependencies": { "@tiptap/extension-bubble-menu": "3.24.0", "@tiptap/extension-floating-menu": "3.24.0" }, "peerDependencies": { "@floating-ui/dom": "1.7.6", "@tiptap/core": "3.24.0", "@tiptap/pm": "3.24.0", "vue": "3.5.35" } }, "sha512-B7H630A5kGRHPrj60FMLbKC8EBWFvGeP8EytaKqxfByEFW6I2mYeZV4BzDptx6Hgy7h5oTXJ1yjc8DH1Q80WYA=="], + + "@tiptap/y-tiptap": ["@tiptap/y-tiptap@3.0.3", "", { "dependencies": { "lib0": "0.2.117" }, "peerDependencies": { "prosemirror-model": "1.25.7", "prosemirror-state": "1.4.4", "prosemirror-view": "1.41.8", "y-protocols": "1.0.7", "yjs": "13.6.30" } }, "sha512-8UvuV4lTisCE9cMTc/X8kRyTn9edUO7Kball0I6wb17VwZSjNDfh/YKtP4O5vcPawEzFHQIvZGq/k1h37kAf0w=="], + + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="], + + "@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="], + + "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], + + "@types/jsesc": ["@types/jsesc@2.5.1", "", {}, "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + + "@types/resolve": ["@types/resolve@1.20.2", "", {}, "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="], + + "@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="], + + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.61.0", "", { "dependencies": { "@eslint-community/regexpp": "4.12.2", "@typescript-eslint/scope-manager": "8.61.0", "@typescript-eslint/type-utils": "8.61.0", "@typescript-eslint/utils": "8.61.0", "@typescript-eslint/visitor-keys": "8.61.0", "ignore": "7.0.5", "natural-compare": "1.4.0", "ts-api-utils": "2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "8.61.0", "eslint": "10.6.0", "typescript": "6.0.3" } }, "sha512-bFNvl9ZczlVb+wR2Akszf3gHfKVj/8WanXaGJ3UstTA7brNKg0cNdk6X1Psu5V7MZ2oQtzZKOEzIUehaoxbDGw=="], + + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.61.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.61.0", "@typescript-eslint/types": "8.61.0", "@typescript-eslint/typescript-estree": "8.61.0", "@typescript-eslint/visitor-keys": "8.61.0", "debug": "4.4.3" }, "peerDependencies": { "eslint": "10.6.0", "typescript": "6.0.3" } }, "sha512-5B7PfA2e1NQGCnDHd/0lW7W3gvp3d59Ryw54FYO8Uswxo9f6ikw3AZV+Xj/TvpImmpsiYyUqAfhC6kJID1jF6w=="], + + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.61.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "8.61.0", "@typescript-eslint/types": "8.61.0", "debug": "4.4.3" }, "peerDependencies": { "typescript": "6.0.3" } }, "sha512-DV42F7MLJO6Rax7SK1yg43tcnEfGUrurSpSxKuVX+a3RCTzBlH3fuxprrOJXKCJGAaw82xXocikJ0uQaqwXgGA=="], + + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.61.0", "", { "dependencies": { "@typescript-eslint/types": "8.61.0", "@typescript-eslint/visitor-keys": "8.61.0" } }, "sha512-IWdXFHFSb6mlC3HPc7QsLDm5zYEbUla6trDEHf32D3/dnuUyXd87plScSNXSbm0/RxMvObpI17sv/EDTGrGZkA=="], + + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.61.0", "", { "peerDependencies": { "typescript": "6.0.3" } }, "sha512-O5Amvdv9ztMpxpf+vmFULGG78IE6Qwdr3bCGvqwG4nwc9H2qXkOYJJnRbRHyMkQTjv1d03olqwwwzHLMqpFePQ=="], + + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.61.0", "", { "dependencies": { "@typescript-eslint/types": "8.61.0", "@typescript-eslint/typescript-estree": "8.61.0", "@typescript-eslint/utils": "8.61.0", "debug": "4.4.3", "ts-api-utils": "2.5.0" }, "peerDependencies": { "eslint": "10.6.0", "typescript": "6.0.3" } }, "sha512-TuBiQYIkd97yBfInHCTKVYMbX4kvEmpOEuixIuzCU9p8BGT1SfyyO0d0IfDMbPIHcjn/hWnusUX5e8v5Xg+X8A=="], + + "@typescript-eslint/types": ["@typescript-eslint/types@8.61.0", "", {}, "sha512-9QTQpZ5Iin4CdIodfbDQFSeiSJKidgYJYug1P9CC2xWgUTvlmixViqDZNciMjwLBZyJnG4tGmPl97rVAFb1AJg=="], + + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.61.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.61.0", "@typescript-eslint/tsconfig-utils": "8.61.0", "@typescript-eslint/types": "8.61.0", "@typescript-eslint/visitor-keys": "8.61.0", "debug": "4.4.3", "minimatch": "10.2.5", "semver": "7.8.2", "tinyglobby": "0.2.17", "ts-api-utils": "2.5.0" }, "peerDependencies": { "typescript": "6.0.3" } }, "sha512-42zatd5qSvvcV1JdDBCLxYRznvP4eIHpPoZXdkPFnAmanA4FuZ5dibSnCBggY8hQnqajPpoGjXFdZ7fIJKQnlA=="], + + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.61.0", "", { "dependencies": { "@eslint-community/eslint-utils": "4.9.1", "@typescript-eslint/scope-manager": "8.61.0", "@typescript-eslint/types": "8.61.0", "@typescript-eslint/typescript-estree": "8.61.0" }, "peerDependencies": { "eslint": "10.6.0", "typescript": "6.0.3" } }, "sha512-3bzFt7ImFMW/jVYwJamDoe/dMOdFLSC6pom6rRjdh4SZJEYupyMzem8e7vKZLclLfpHjlwSAXOUxtKxGXUiLqA=="], + + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.61.0", "", { "dependencies": { "@typescript-eslint/types": "8.61.0", "eslint-visitor-keys": "5.0.1" } }, "sha512-QVLZu3ZPQEE+HICQyAMZ2yLQhxf0meY/wx6Hx14YcTNj13JB3qHlX3lJ02L3fLGHgERRH71kvYDwiXIguT3AjQ=="], + + "@unhead/vue": ["@unhead/vue@2.1.15", "", { "dependencies": { "hookable": "6.1.1", "unhead": "2.1.15" }, "peerDependencies": { "vue": "3.5.35" } }, "sha512-SSByXfEjhzPn8gXdEdgpYqpLMPSkLUH2HVE0GxZfOtNsJ0GgOHQs0g9T67ZZ1z0kTELLKdtOtYrzrbv9+ffF7g=="], + + "@unrs/resolver-binding-android-arm-eabi": ["@unrs/resolver-binding-android-arm-eabi@1.12.2", "", { "os": "android", "cpu": "arm" }, "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w=="], + + "@unrs/resolver-binding-android-arm64": ["@unrs/resolver-binding-android-arm64@1.12.2", "", { "os": "android", "cpu": "arm64" }, "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ=="], + + "@unrs/resolver-binding-darwin-arm64": ["@unrs/resolver-binding-darwin-arm64@1.12.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w=="], + + "@unrs/resolver-binding-darwin-x64": ["@unrs/resolver-binding-darwin-x64@1.12.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA=="], + + "@unrs/resolver-binding-freebsd-x64": ["@unrs/resolver-binding-freebsd-x64@1.12.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg=="], + + "@unrs/resolver-binding-linux-arm-gnueabihf": ["@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2", "", { "os": "linux", "cpu": "arm" }, "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A=="], + + "@unrs/resolver-binding-linux-arm-musleabihf": ["@unrs/resolver-binding-linux-arm-musleabihf@1.12.2", "", { "os": "linux", "cpu": "arm" }, "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g=="], + + "@unrs/resolver-binding-linux-arm64-gnu": ["@unrs/resolver-binding-linux-arm64-gnu@1.12.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg=="], + + "@unrs/resolver-binding-linux-arm64-musl": ["@unrs/resolver-binding-linux-arm64-musl@1.12.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA=="], + + "@unrs/resolver-binding-linux-loong64-gnu": ["@unrs/resolver-binding-linux-loong64-gnu@1.12.2", "", { "os": "linux", "cpu": "none" }, "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q=="], + + "@unrs/resolver-binding-linux-loong64-musl": ["@unrs/resolver-binding-linux-loong64-musl@1.12.2", "", { "os": "linux", "cpu": "none" }, "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew=="], + + "@unrs/resolver-binding-linux-ppc64-gnu": ["@unrs/resolver-binding-linux-ppc64-gnu@1.12.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg=="], + + "@unrs/resolver-binding-linux-riscv64-gnu": ["@unrs/resolver-binding-linux-riscv64-gnu@1.12.2", "", { "os": "linux", "cpu": "none" }, "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A=="], + + "@unrs/resolver-binding-linux-riscv64-musl": ["@unrs/resolver-binding-linux-riscv64-musl@1.12.2", "", { "os": "linux", "cpu": "none" }, "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w=="], + + "@unrs/resolver-binding-linux-s390x-gnu": ["@unrs/resolver-binding-linux-s390x-gnu@1.12.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw=="], + + "@unrs/resolver-binding-linux-x64-gnu": ["@unrs/resolver-binding-linux-x64-gnu@1.12.2", "", { "os": "linux", "cpu": "x64" }, "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ=="], + + "@unrs/resolver-binding-linux-x64-musl": ["@unrs/resolver-binding-linux-x64-musl@1.12.2", "", { "os": "linux", "cpu": "x64" }, "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A=="], + + "@unrs/resolver-binding-openharmony-arm64": ["@unrs/resolver-binding-openharmony-arm64@1.12.2", "", { "os": "none", "cpu": "arm64" }, "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ=="], + + "@unrs/resolver-binding-wasm32-wasi": ["@unrs/resolver-binding-wasm32-wasi@1.12.2", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "1.1.4" }, "cpu": "none" }, "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A=="], + + "@unrs/resolver-binding-win32-arm64-msvc": ["@unrs/resolver-binding-win32-arm64-msvc@1.12.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g=="], + + "@unrs/resolver-binding-win32-ia32-msvc": ["@unrs/resolver-binding-win32-ia32-msvc@1.12.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g=="], + + "@unrs/resolver-binding-win32-x64-msvc": ["@unrs/resolver-binding-win32-x64-msvc@1.12.2", "", { "os": "win32", "cpu": "x64" }, "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA=="], + + "@valibot/to-json-schema": ["@valibot/to-json-schema@1.7.1", "", { "peerDependencies": { "valibot": "1.4.1" } }, "sha512-3qkmU6KXWh8GIThEAW3kuRHPQBMjWkKy+Ppz3WkUucx53DTpOa6siMn4xDGSOhlVyMrDaJTCTMLYPZVAIk1P0A=="], + + "@vercel/nft": ["@vercel/nft@1.5.0", "", { "dependencies": { "@mapbox/node-pre-gyp": "2.0.3", "@rollup/pluginutils": "5.3.0", "acorn": "8.16.0", "acorn-import-attributes": "1.9.5", "async-sema": "3.1.1", "bindings": "1.5.0", "estree-walker": "2.0.2", "glob": "13.0.6", "graceful-fs": "4.2.11", "node-gyp-build": "4.8.4", "picomatch": "4.0.4", "resolve-from": "5.0.0" }, "bin": { "nft": "out/cli.js" } }, "sha512-IWTDeIoWhQ7ZtRO/JRKH+jhmeQvZYhtGPmzw/QGDY+wDCQqfm25P9yIdoAFagu4fWsK4IwZXDFIjrmp5rRm/sA=="], + + "@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.7", "", { "dependencies": { "@rolldown/pluginutils": "1.0.1" }, "peerDependencies": { "vite": "7.3.3", "vue": "3.5.35" } }, "sha512-km+p+XdSz9Sxm5rqUbqcSfZYaAniKxWBj1KURl+Jr7UaPvvX7BmaWMdP69I5rrFDeQGyxAG7NXdc57vz+snhWg=="], + + "@vitejs/plugin-vue-jsx": ["@vitejs/plugin-vue-jsx@5.1.5", "", { "dependencies": { "@babel/core": "7.29.0", "@babel/plugin-syntax-typescript": "7.28.6", "@babel/plugin-transform-typescript": "7.28.6", "@rolldown/pluginutils": "1.0.1", "@vue/babel-plugin-jsx": "2.0.1" }, "peerDependencies": { "vite": "7.3.3", "vue": "3.5.35" } }, "sha512-jIAsvHOEtWpslLOI2MeElGFxH7M8pM83BU/Tor4RLyiwH0FM4nUW3xdvbw20EeU9wc5IspQwMq225K3CMnJEpA=="], + + "@volar/language-core": ["@volar/language-core@2.4.28", "", { "dependencies": { "@volar/source-map": "2.4.28" } }, "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ=="], + + "@volar/source-map": ["@volar/source-map@2.4.28", "", {}, "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ=="], + + "@volar/typescript": ["@volar/typescript@2.4.28", "", { "dependencies": { "@volar/language-core": "2.4.28", "path-browserify": "1.0.1", "vscode-uri": "3.1.0" } }, "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw=="], + + "@vue-macros/common": ["@vue-macros/common@3.1.2", "", { "dependencies": { "@vue/compiler-sfc": "3.5.35", "ast-kit": "2.2.0", "local-pkg": "1.2.1", "magic-string-ast": "1.0.3", "unplugin-utils": "0.3.1" }, "optionalDependencies": { "vue": "3.5.35" } }, "sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng=="], + + "@vue/babel-helper-vue-transform-on": ["@vue/babel-helper-vue-transform-on@2.0.1", "", {}, "sha512-uZ66EaFbnnZSYqYEyplWvn46GhZ1KuYSThdT68p+am7MgBNbQ3hphTL9L+xSIsWkdktwhPYLwPgVWqo96jDdRA=="], + + "@vue/babel-plugin-jsx": ["@vue/babel-plugin-jsx@2.0.1", "", { "dependencies": { "@babel/helper-module-imports": "7.28.6", "@babel/helper-plugin-utils": "7.28.6", "@babel/plugin-syntax-jsx": "7.28.6", "@babel/template": "7.28.6", "@babel/traverse": "7.29.0", "@babel/types": "7.29.0", "@vue/babel-helper-vue-transform-on": "2.0.1", "@vue/babel-plugin-resolve-type": "2.0.1", "@vue/shared": "3.5.35" }, "optionalDependencies": { "@babel/core": "7.29.0" } }, "sha512-a8CaLQjD/s4PVdhrLD/zT574ZNPnZBOY+IhdtKWRB4HRZ0I2tXBi5ne7d9eCfaYwp5gU5+4KIyFTV1W1YL9xZA=="], + + "@vue/babel-plugin-resolve-type": ["@vue/babel-plugin-resolve-type@2.0.1", "", { "dependencies": { "@babel/code-frame": "7.29.0", "@babel/helper-module-imports": "7.28.6", "@babel/helper-plugin-utils": "7.28.6", "@babel/parser": "7.29.3", "@vue/compiler-sfc": "3.5.35" }, "peerDependencies": { "@babel/core": "7.29.0" } }, "sha512-ybwgIuRGRRBhOU37GImDoWQoz+TlSqap65qVI6iwg/J7FfLTLmMf97TS7xQH9I7Qtr/gp161kYVdhr1ZMraSYQ=="], + + "@vue/compiler-core": ["@vue/compiler-core@3.5.35", "", { "dependencies": { "@babel/parser": "7.29.3", "@vue/shared": "3.5.35", "entities": "7.0.1", "estree-walker": "2.0.2", "source-map-js": "1.2.1" } }, "sha512-BUmHaR1J+O+CKZ9uJucdVTEr1LHsdyvv7vG3eNRhK3CczEHeMd/LtsHAuD7PbrxvI2envCY2v7HI1vC1aBRzKw=="], + + "@vue/compiler-dom": ["@vue/compiler-dom@3.5.35", "", { "dependencies": { "@vue/compiler-core": "3.5.35", "@vue/shared": "3.5.35" } }, "sha512-k+bprkXxuqhVajgTx5mUHuir7TwQzUKOWR40ng1ncAqQRPnrLngGGgqVEEhOnTMlc8btHYVKmrP8s5Qyg0hvYA=="], + + "@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.35", "", { "dependencies": { "@babel/parser": "7.29.3", "@vue/compiler-core": "3.5.35", "@vue/compiler-dom": "3.5.35", "@vue/compiler-ssr": "3.5.35", "@vue/shared": "3.5.35", "estree-walker": "2.0.2", "magic-string": "0.30.21", "postcss": "8.5.15", "source-map-js": "1.2.1" } }, "sha512-G5VPMcXTSywXBgtFOZOnHKBxKSrwXUcvY1iaF5/hRcy7t0J6CH/d8ha9F4nzi00Fax1eLV0QHM7v4mQu68jydw=="], + + "@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.35", "", { "dependencies": { "@vue/compiler-dom": "3.5.35", "@vue/shared": "3.5.35" } }, "sha512-rGhAeXgdM7/ffTJGXT69rCCdTmjDewnFuUZfBQQHTdcEBeWdT5HCGY60y2ytLJr9/Dsu7IntUi5z/w0h6Rjnzw=="], + + "@vue/devtools-api": ["@vue/devtools-api@8.1.2", "", { "dependencies": { "@vue/devtools-kit": "8.1.2" } }, "sha512-vA0O112YqyDuNA1s7Yb2gCgToQ/OxOWiFDO5ThLCcDy0ldHnSd1dUTaSYhOldbqoNgumE4dxtGAoAaSUKUD1Zg=="], + + "@vue/devtools-core": ["@vue/devtools-core@8.1.2", "", { "dependencies": { "@vue/devtools-kit": "8.1.2", "@vue/devtools-shared": "8.1.2" }, "peerDependencies": { "vue": "3.5.35" } }, "sha512-ZGGyaSBP4/+bN2Nd9ZHNYAVDRIzMw1rv2RyXWtyZlo6mQal+IDmTvKY4V+DjAEBhaXt30mHmsgYp1yXJ/2tIWg=="], + + "@vue/devtools-kit": ["@vue/devtools-kit@8.1.2", "", { "dependencies": { "@vue/devtools-shared": "8.1.2", "birpc": "2.9.0", "hookable": "5.5.3", "perfect-debounce": "2.1.0" } }, "sha512-f75/upc+GCyjXErpgPGz4582ujS0L/adAltGy+tqXMGUJpgAcfGr6CxnnhpZY8BHuMYt6KpbF8uaFrrQG66rGQ=="], + + "@vue/devtools-shared": ["@vue/devtools-shared@8.1.2", "", {}, "sha512-X9RyVFYAdkBe4IUf5v48TxBF/6QPmF8CmWrDAjXzfUHrgQ/HGfTC1A6TqgXqZ03ye66l3AD51BAGD69IvKM9sw=="], + + "@vue/language-core": ["@vue/language-core@3.3.5", "", { "dependencies": { "@volar/language-core": "2.4.28", "@vue/compiler-dom": "3.5.35", "@vue/shared": "3.5.35", "alien-signals": "3.2.1", "muggle-string": "0.4.1", "path-browserify": "1.0.1", "picomatch": "4.0.4" } }, "sha512-UkKu5nhX89fg4VhlG/FOeI10G3cj/7radKT/cy9BT4Q9qJmJlSTAc/dP63Xqs29aypN4f39xUV6PsLNk/dcD6g=="], + + "@vue/reactivity": ["@vue/reactivity@3.5.35", "", { "dependencies": { "@vue/shared": "3.5.35" } }, "sha512-tVc+SsHConvh/Lz64qq1pP3rYArBmK42xonovEcxY74SQtvctZodG/zhq54P5dr38cVuw25d27cPNRdlMidpGQ=="], + + "@vue/runtime-core": ["@vue/runtime-core@3.5.35", "", { "dependencies": { "@vue/reactivity": "3.5.35", "@vue/shared": "3.5.35" } }, "sha512-A/xFNX9loIcWDygeQuNCfKuh0CoYBzxhqEMNah5TSFg9Z53DrFYEN2qi5CU9necjM1OWYegYREUTHmXTmhfXtg=="], + + "@vue/runtime-dom": ["@vue/runtime-dom@3.5.35", "", { "dependencies": { "@vue/reactivity": "3.5.35", "@vue/runtime-core": "3.5.35", "@vue/shared": "3.5.35", "csstype": "3.2.3" } }, "sha512-odrJ1C391dbGnyDRh8U+rnP7J2amIEzfmRk5vXy7xi3aZhEXofTvpi0T4HJb6jlNqQZTNPR5MPHSB3RHNkIORA=="], + + "@vue/server-renderer": ["@vue/server-renderer@3.5.35", "", { "dependencies": { "@vue/compiler-ssr": "3.5.35", "@vue/shared": "3.5.35" }, "peerDependencies": { "vue": "3.5.35" } }, "sha512-NkebSOYdB97wi8OQcO3HqzZSlymJi/aWsN/7h74OSVhRTm6qGs3Jp3e0rCXynmWwSlKeRrnlIug+ilYoHBmQDA=="], + + "@vue/shared": ["@vue/shared@3.5.35", "", {}, "sha512-zSbjL7gRXwks2ZQLRGCajBtBXEOXW9Ddhn/HvSdrGkE2dqGnumzW8XtusRrxrE9LvqtiqDXQ+A60Hp6mvdYxfA=="], + + "@vueuse/core": ["@vueuse/core@14.3.0", "", { "dependencies": { "@types/web-bluetooth": "0.0.21", "@vueuse/metadata": "14.3.0", "@vueuse/shared": "14.3.0" }, "peerDependencies": { "vue": "3.5.35" } }, "sha512-aHfz47g0ZhMtTVHmIzMVpJy8ePhhOy68GY5bv110+5DVtZ+W7BsOx+m61UNQqfrWyPztIHIanWa3E2tib3NFIw=="], + + "@vueuse/integrations": ["@vueuse/integrations@14.3.0", "", { "dependencies": { "@vueuse/core": "14.3.0", "@vueuse/shared": "14.3.0" }, "optionalDependencies": { "change-case": "5.4.4", "fuse.js": "7.4.2" }, "peerDependencies": { "vue": "3.5.35" } }, "sha512-76I5FT2ESvCmCaSwapI+a/u/CFtNXmzl9f9lNp1hRtx8vKB8hfiokJr8IvQqcQG5ckGXElyXK516b54ozV3MvA=="], + + "@vueuse/metadata": ["@vueuse/metadata@14.3.0", "", {}, "sha512-BwxmbAzwAVF50+MW57GXOUEV61nFBGnlBvrTqj49PqWJu3uw7hdu72ztXeZ33RdZtDY6kO+bfCAE1PCn88Tktw=="], + + "@vueuse/shared": ["@vueuse/shared@14.3.0", "", { "peerDependencies": { "vue": "3.5.35" } }, "sha512-bZpge9eSXwa4ToSiqJ7j6KRwhAsneMFoSz3LMWKQDkqimm3D/tbFlrklrs/IOqC8tEcYmXQZJ6N0UrjhBirVCg=="], + + "abbrev": ["abbrev@3.0.1", "", {}, "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg=="], + + "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "5.0.1" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], + + "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + + "acorn-import-attributes": ["acorn-import-attributes@1.9.5", "", { "peerDependencies": { "acorn": "8.16.0" } }, "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "8.16.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + + "ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "3.1.3", "fast-json-stable-stringify": "2.1.0", "json-schema-traverse": "0.4.1", "uri-js": "4.4.1" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], + + "alien-signals": ["alien-signals@3.2.1", "", {}, "sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g=="], + + "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "ansis": ["ansis@4.3.0", "", {}, "sha512-44mvgtPvohuU/70DdY5Oz2AIrLJ9k6/5x4KmoSvPwO+5Moijo0+N9D0fKbbYZQWP1hNm5CpOf+E01jhxG/r8xg=="], + + "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "3.0.0", "picomatch": "2.3.2" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], + + "archiver": ["archiver@7.0.1", "", { "dependencies": { "archiver-utils": "5.0.2", "async": "3.2.6", "buffer-crc32": "1.0.0", "readable-stream": "4.7.0", "readdir-glob": "1.1.3", "tar-stream": "3.2.0", "zip-stream": "6.0.1" } }, "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ=="], + + "archiver-utils": ["archiver-utils@5.0.2", "", { "dependencies": { "glob": "10.5.0", "graceful-fs": "4.2.11", "is-stream": "2.0.1", "lazystream": "1.0.1", "lodash": "4.18.1", "normalize-path": "3.0.0", "readable-stream": "4.7.0" } }, "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA=="], + + "are-docs-informative": ["are-docs-informative@0.0.2", "", {}, "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="], + + "ast-kit": ["ast-kit@2.2.0", "", { "dependencies": { "@babel/parser": "7.29.3", "pathe": "2.0.3" } }, "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw=="], + + "ast-walker-scope": ["ast-walker-scope@0.9.0", "", { "dependencies": { "@babel/parser": "7.29.3", "@babel/types": "7.29.0", "ast-kit": "2.2.0" } }, "sha512-IJdzo2vLiElBxKzwS36VsCue/62d6IdWjnPB2v3nuPKeWGynp6FF/CYoLa5i/3jXH/z97ZDdsXz6abpgM6w07A=="], + + "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], + + "async-sema": ["async-sema@3.1.1", "", {}, "sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg=="], + + "autoprefixer": ["autoprefixer@10.5.0", "", { "dependencies": { "browserslist": "4.28.2", "caniuse-lite": "1.0.30001793", "fraction.js": "5.3.4", "picocolors": "1.1.1", "postcss-value-parser": "4.2.0" }, "peerDependencies": { "postcss": "8.5.15" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong=="], + + "b4a": ["b4a@1.8.1", "", {}, "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw=="], + + "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + + "bare-events": ["bare-events@2.8.3", "", {}, "sha512-HdUm8EMQBLaJvGUdidNNbqpA1kYkwNcb+MYxkxCLAPJGQzlv9J0C24h8V65Z4c5GLd/JEALDvpFCQgpLJqc0zw=="], + + "bare-fs": ["bare-fs@4.7.1", "", { "dependencies": { "bare-events": "2.8.3", "bare-path": "3.0.0", "bare-stream": "2.13.1", "bare-url": "2.4.3", "fast-fifo": "1.3.2" } }, "sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw=="], + + "bare-os": ["bare-os@3.9.1", "", {}, "sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ=="], + + "bare-path": ["bare-path@3.0.0", "", { "dependencies": { "bare-os": "3.9.1" } }, "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw=="], + + "bare-stream": ["bare-stream@2.13.1", "", { "dependencies": { "streamx": "2.25.0", "teex": "1.0.1" }, "optionalDependencies": { "bare-events": "2.8.3" } }, "sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow=="], + + "bare-url": ["bare-url@2.4.3", "", { "dependencies": { "bare-path": "3.0.0" } }, "sha512-Kccpc7ACfXaxfeInfqKcZtW4pT5YBn1mesc4sCsun6sRwtbJ4h+sNOaksUpYEJUKfN65YWC6Bw2OJEFiKxq8nQ=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.31", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-MujYO3eP72uvmSE0i4wltsodRfIpZATP3jvzRNRGGxgzId7aVocVJJV3nf01qnzzKFGxQVC9bpWxl5cjxTr/7Q=="], + + "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="], + + "birpc": ["birpc@4.0.0", "", {}, "sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw=="], + + "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], + + "brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "4.0.4" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "2.10.31", "caniuse-lite": "1.0.30001793", "electron-to-chromium": "1.5.359", "node-releases": "2.0.44", "update-browserslist-db": "1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="], + + "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "1.5.1", "ieee754": "1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + + "buffer-crc32": ["buffer-crc32@1.0.0", "", {}, "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w=="], + + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + + "builtin-modules": ["builtin-modules@5.2.0", "", {}, "sha512-02yxLeyxF4dNl6SlY6/5HfRSrSdZ/sCPoxy2kZNP5dZZX8LSAD9aE2gtJIUgWrsQTiMPl3mxESyrobSwvRGisQ=="], + + "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "7.1.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], + + "c12": ["c12@3.3.4", "", { "dependencies": { "chokidar": "5.0.0", "confbox": "0.2.4", "defu": "6.1.7", "dotenv": "17.4.2", "exsolve": "1.0.8", "giget": "3.2.0", "jiti": "2.7.0", "ohash": "2.0.11", "pathe": "2.0.3", "perfect-debounce": "2.1.0", "pkg-types": "2.3.1", "rc9": "3.0.1" }, "optionalDependencies": { "magicast": "0.5.3" } }, "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA=="], + + "cac": ["cac@7.0.0", "", {}, "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ=="], + + "caniuse-api": ["caniuse-api@3.0.0", "", { "dependencies": { "browserslist": "4.28.2", "caniuse-lite": "1.0.30001793", "lodash.memoize": "4.1.2", "lodash.uniq": "4.5.0" } }, "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001793", "", {}, "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA=="], + + "change-case": ["change-case@5.4.4", "", {}, "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w=="], + + "chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], + + "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], + + "ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="], + + "citty": ["citty@0.2.2", "", {}, "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w=="], + + "cliui": ["cliui@9.0.1", "", { "dependencies": { "string-width": "7.2.0", "strip-ansi": "7.2.0", "wrap-ansi": "9.0.2" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="], + + "cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "colortranslator": ["colortranslator@5.0.0", "", {}, "sha512-Z3UPUKasUVDFCDYAjP2fmlVRf1jFHJv1izAmPjiOa0OCIw1W7iC8PZ2GsoDa8uZv+mKyWopxxStT9q05+27h7w=="], + + "commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + + "comment-parser": ["comment-parser@1.4.7", "", {}, "sha512-0h+uSNtQGW3D98eQt3jJ8L06Fves8hncB4V/PKdw/Qb8Hnk19VaKuTr55UNRYiSoVa7WwrFls+rh3ux9agmkeQ=="], + + "commondir": ["commondir@1.0.1", "", {}, "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="], + + "compatx": ["compatx@0.2.0", "", {}, "sha512-6gLRNt4ygsi5NyMVhceOCFv14CIdDFN7fQjX1U4+47qVE/+kjPoXMK65KWK+dWxmFzMTuKazoQ9sch6pM0p5oA=="], + + "compress-commons": ["compress-commons@6.0.2", "", { "dependencies": { "crc-32": "1.2.2", "crc32-stream": "6.0.0", "is-stream": "2.0.1", "normalize-path": "3.0.0", "readable-stream": "4.7.0" } }, "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg=="], + + "confbox": ["confbox@0.2.4", "", {}, "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ=="], + + "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "cookie-es": ["cookie-es@3.1.1", "", {}, "sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg=="], + + "core-js-compat": ["core-js-compat@3.49.0", "", { "dependencies": { "browserslist": "4.28.2" } }, "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA=="], + + "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], + + "crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="], + + "crc32-stream": ["crc32-stream@6.0.0", "", { "dependencies": { "crc-32": "1.2.2", "readable-stream": "4.7.0" } }, "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g=="], + + "croner": ["croner@10.0.1", "", {}, "sha512-ixNtAJndqh173VQ4KodSdJEI6nuioBWI0V1ITNKhZZsO0pEMoDxz539T4FTTbSZ/xIOSuDnzxLVRqBVSvPNE2g=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "3.1.1", "shebang-command": "2.0.0", "which": "2.0.2" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "crossws": ["crossws@0.3.5", "", { "dependencies": { "uncrypto": "0.1.3" } }, "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA=="], + + "css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "1.0.0", "css-what": "6.2.2", "domhandler": "5.0.3", "domutils": "3.2.2", "nth-check": "2.1.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="], + + "css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="], + + "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="], + + "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], + + "cssnano": ["cssnano@8.0.1", "", { "dependencies": { "cssnano-preset-default": "8.0.1", "lilconfig": "3.1.3" }, "peerDependencies": { "postcss": "8.5.15" } }, "sha512-oSiOnPQNNYjusTUlYJiE6xvFQG4don3N0QavaoV1BxIsC1zjvxOwikXlR7lG1EVmZNDDaJkHbQx1VRB8kaoMHA=="], + + "cssnano-preset-default": ["cssnano-preset-default@8.0.1", "", { "dependencies": { "browserslist": "4.28.2", "cssnano-utils": "6.0.0", "postcss-calc": "10.1.1", "postcss-colormin": "8.0.0", "postcss-convert-values": "8.0.0", "postcss-discard-comments": "8.0.0", "postcss-discard-duplicates": "8.0.0", "postcss-discard-empty": "8.0.0", "postcss-discard-overridden": "8.0.0", "postcss-merge-longhand": "8.0.0", "postcss-merge-rules": "8.0.0", "postcss-minify-font-values": "8.0.0", "postcss-minify-gradients": "8.0.0", "postcss-minify-params": "8.0.0", "postcss-minify-selectors": "8.0.1", "postcss-normalize-charset": "8.0.0", "postcss-normalize-display-values": "8.0.0", "postcss-normalize-positions": "8.0.0", "postcss-normalize-repeat-style": "8.0.0", "postcss-normalize-string": "8.0.0", "postcss-normalize-timing-functions": "8.0.0", "postcss-normalize-unicode": "8.0.0", "postcss-normalize-url": "8.0.0", "postcss-normalize-whitespace": "8.0.0", "postcss-ordered-values": "8.0.0", "postcss-reduce-initial": "8.0.0", "postcss-reduce-transforms": "8.0.0", "postcss-svgo": "8.0.0", "postcss-unique-selectors": "8.0.0" }, "peerDependencies": { "postcss": "8.5.15" } }, "sha512-OTdKeYMlvQ8KBgyej5ysktnWJoeyo7rGrVnm+bdpIHGvxhbTGPsOkB+7T1EdTuX00dGlQQb2UEbSPB1OpMXULw=="], + + "cssnano-utils": ["cssnano-utils@6.0.0", "", { "peerDependencies": { "postcss": "8.5.15" } }, "sha512-ztS9W/+uaDn+bkYmDhs+GdMveHJ3CL8IPNHpRqDUQXv5GJOTQAJjV1XUOInr9esLXSabQV1pLRZlJpyUwEqDyQ=="], + + "csso": ["csso@5.0.5", "", { "dependencies": { "css-tree": "2.2.1" } }, "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "db0": ["db0@0.3.4", "", {}, "sha512-RiXXi4WaNzPTHEOu8UPQKMooIbqOEyqA1t7Z6MsdxSCeb8iUC9ko3LcmsLmeUt2SM5bctfArZKkRQggKZz7JNw=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + + "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], + + "default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "4.1.0", "default-browser-id": "5.0.1" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="], + + "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="], + + "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="], + + "defu": ["defu@6.1.7", "", {}, "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ=="], + + "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="], + + "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], + + "destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="], + + "detect-indent": ["detect-indent@7.0.2", "", {}, "sha512-y+8xyqdGLL+6sh0tVeHcfP/QDd8gUgbasolJJpY7NgeQGSZ739bDtSiaiDgtoicy+mtYB81dKLxO9xRhCyIB3A=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "devalue": ["devalue@5.8.1", "", {}, "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw=="], + + "devframe": ["devframe@0.5.4", "", { "dependencies": { "@valibot/to-json-schema": "1.7.1", "birpc": "4.0.0", "cac": "7.0.0", "h3": "2.0.1-rc.22", "mrmime": "2.0.1", "nostics": "0.2.0", "pathe": "2.0.3", "valibot": "1.4.1", "ws": "8.21.0" } }, "sha512-dbHU/LuptR1aMXcizjHUeY3gu7qVaRQoFYqbbyyGuO6Y+avFA4uQtwinHtG1qa7lRel/7b8JrBDm0nbnxU3vqg=="], + + "diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="], + + "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "2.3.0", "domhandler": "5.0.3", "entities": "4.5.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], + + "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], + + "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], + + "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "2.0.0", "domelementtype": "2.3.0", "domhandler": "5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], + + "dot-prop": ["dot-prop@10.1.0", "", { "dependencies": { "type-fest": "5.6.0" } }, "sha512-MVUtAugQMOff5RnBy2d9N31iG0lNwg1qAoAOn7pOK5wf94WIaE3My2p3uwTQuvS2AcqchkcR3bHByjaM0mmi7Q=="], + + "dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="], + + "duplexer": ["duplexer@0.1.2", "", {}, "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg=="], + + "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + + "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.359", "", {}, "sha512-8lPELWuYZIWk7NDvCNthtmMw/7Q5Wu25NpM4djFMHBmk8DubPAtL4YTOp7ou0e7HyJtwkVlWv8XMLURnrtgJQw=="], + + "embla-carousel": ["embla-carousel@8.6.0", "", {}, "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA=="], + + "embla-carousel-auto-height": ["embla-carousel-auto-height@8.6.0", "", { "peerDependencies": { "embla-carousel": "8.6.0" } }, "sha512-/HrJQOEM6aol/oF33gd2QlINcXy3e19fJWvHDuHWp2bpyTa+2dm9tVVJak30m2Qy6QyQ6Fc8DkImtv7pxWOJUQ=="], + + "embla-carousel-auto-scroll": ["embla-carousel-auto-scroll@8.6.0", "", { "peerDependencies": { "embla-carousel": "8.6.0" } }, "sha512-WT9fWhNXFpbQ6kP+aS07oF5IHYLZ1Dx4DkwgCY8Hv2ZyYd2KMCPfMV1q/cA3wFGuLO7GMgKiySLX90/pQkcOdQ=="], + + "embla-carousel-autoplay": ["embla-carousel-autoplay@8.6.0", "", { "peerDependencies": { "embla-carousel": "8.6.0" } }, "sha512-OBu5G3nwaSXkZCo1A6LTaFMZ8EpkYbwIaH+bPqdBnDGQ2fh4+NbzjXjs2SktoPNKCtflfVMc75njaDHOYXcrsA=="], + + "embla-carousel-class-names": ["embla-carousel-class-names@8.6.0", "", { "peerDependencies": { "embla-carousel": "8.6.0" } }, "sha512-l1hm1+7GxQ+zwdU2sea/LhD946on7XO2qk3Xq2XWSwBaWfdgchXdK567yzLtYSHn4sWYdiX+x4nnaj+saKnJkw=="], + + "embla-carousel-fade": ["embla-carousel-fade@8.6.0", "", { "peerDependencies": { "embla-carousel": "8.6.0" } }, "sha512-qaYsx5mwCz72ZrjlsXgs1nKejSrW+UhkbOMwLgfRT7w2LtdEB03nPRI06GHuHv5ac2USvbEiX2/nAHctcDwvpg=="], + + "embla-carousel-reactive-utils": ["embla-carousel-reactive-utils@8.6.0", "", { "peerDependencies": { "embla-carousel": "8.6.0" } }, "sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A=="], + + "embla-carousel-vue": ["embla-carousel-vue@8.6.0", "", { "dependencies": { "embla-carousel": "8.6.0", "embla-carousel-reactive-utils": "8.6.0" }, "peerDependencies": { "vue": "3.5.35" } }, "sha512-v8UO5UsyLocZnu/LbfQA7Dn2QHuZKurJY93VUmZYP//QRWoCWOsionmvLLAlibkET3pGPs7++03VhJKbWD7vhQ=="], + + "embla-carousel-wheel-gestures": ["embla-carousel-wheel-gestures@8.1.0", "", { "dependencies": { "wheel-gestures": "2.2.48" }, "peerDependencies": { "embla-carousel": "8.6.0" } }, "sha512-J68jkYrxbWDmXOm2n2YHl+uMEXzkGSKjWmjaEgL9xVvPb3HqVmg6rJSKfI3sqIDVvm7mkeTy87wtG/5263XqHQ=="], + + "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + + "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], + + "enhanced-resolve": ["enhanced-resolve@5.21.6", "", { "dependencies": { "graceful-fs": "4.2.11", "tapable": "2.3.3" } }, "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ=="], + + "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], + + "error-stack-parser-es": ["error-stack-parser-es@1.0.5", "", {}, "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA=="], + + "errx": ["errx@0.1.0", "", {}, "sha512-fZmsRiDNv07K6s2KkKFTiD2aIvECa7++PKyD5NC32tpRw46qZA3sOz+aM+/V9V0GDHxVTKLziveV4JhzBHDp9Q=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-module-lexer": ["es-module-lexer@2.1.0", "", {}, "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ=="], + + "esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "eslint": ["eslint@10.6.0", "", { "dependencies": { "@eslint-community/eslint-utils": "4.9.1", "@eslint-community/regexpp": "4.12.2", "@eslint/config-array": "0.23.5", "@eslint/config-helpers": "0.6.0", "@eslint/core": "1.2.1", "@eslint/plugin-kit": "0.7.2", "@humanfs/node": "0.16.8", "@humanwhocodes/module-importer": "1.0.1", "@humanwhocodes/retry": "0.4.3", "@types/estree": "1.0.9", "ajv": "6.15.0", "cross-spawn": "7.0.6", "debug": "4.4.3", "escape-string-regexp": "4.0.0", "eslint-scope": "9.1.2", "eslint-visitor-keys": "5.0.1", "espree": "11.2.0", "esquery": "1.7.0", "esutils": "2.0.3", "fast-deep-equal": "3.1.3", "file-entry-cache": "8.0.0", "find-up": "5.0.0", "glob-parent": "6.0.2", "ignore": "5.3.2", "imurmurhash": "0.1.4", "is-glob": "4.0.3", "json-stable-stringify-without-jsonify": "1.0.1", "minimatch": "10.2.5", "natural-compare": "1.4.0", "optionator": "0.9.4" }, "optionalDependencies": { "jiti": "2.7.0" }, "bin": { "eslint": "bin/eslint.js" } }, "sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg=="], + + "eslint-config-flat-gitignore": ["eslint-config-flat-gitignore@2.3.0", "", { "dependencies": { "@eslint/compat": "2.1.0" }, "peerDependencies": { "eslint": "10.6.0" } }, "sha512-bg4ZLGgoARg1naWfsINUUb/52Ksw/K22K+T16D38Y8v+/sGwwIYrGvH/JBjOin+RQtxxC9tzNNiy4shnGtGyyQ=="], + + "eslint-flat-config-utils": ["eslint-flat-config-utils@3.2.0", "", { "dependencies": { "@eslint/config-helpers": "0.5.5", "pathe": "2.0.3" } }, "sha512-PHgo1X5uqIorJONLVD9BIaOSdoYFD3z/AeJljdqDPlWVRpeCYkDbK9k0AXoYVqqNJr6FEYIEr5Rm2TSktLQcHw=="], + + "eslint-import-context": ["eslint-import-context@0.1.9", "", { "dependencies": { "get-tsconfig": "4.14.0", "stable-hash-x": "0.2.0" }, "optionalDependencies": { "unrs-resolver": "1.12.2" } }, "sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg=="], + + "eslint-merge-processors": ["eslint-merge-processors@2.0.0", "", { "peerDependencies": { "eslint": "10.6.0" } }, "sha512-sUuhSf3IrJdGooquEUB5TNpGNpBoQccbnaLHsb1XkBLUPPqCNivCpY05ZcpCOiV9uHwO2yxXEWVczVclzMxYlA=="], + + "eslint-plugin-import-lite": ["eslint-plugin-import-lite@0.6.0", "", { "peerDependencies": { "eslint": "10.6.0" } }, "sha512-80vevx2A7i3H7n1/6pqDO8cc5wRz6OwLDvIyVl9UflBV1N1f46e9Ihzi65IOLYoSxM6YykK2fTw1xm0Ixx6aTQ=="], + + "eslint-plugin-import-x": ["eslint-plugin-import-x@4.16.2", "", { "dependencies": { "@package-json/types": "0.0.12", "@typescript-eslint/types": "8.61.0", "comment-parser": "1.4.7", "debug": "4.4.3", "eslint-import-context": "0.1.9", "is-glob": "4.0.3", "minimatch": "10.2.5", "semver": "7.8.2", "stable-hash-x": "0.2.0", "unrs-resolver": "1.12.2" }, "optionalDependencies": { "@typescript-eslint/utils": "8.61.0" }, "peerDependencies": { "eslint": "10.6.0" } }, "sha512-rM9K8UBHcWKpzQzStn1YRN2T5NvdeIfSVoKu/lKF41znQXHAUcBbYXe5wd6GNjZjTrP7viQ49n1D83x/2gYgIw=="], + + "eslint-plugin-jsdoc": ["eslint-plugin-jsdoc@63.0.2", "", { "dependencies": { "@es-joy/jsdoccomment": "0.87.0", "@es-joy/resolve.exports": "1.2.0", "are-docs-informative": "0.0.2", "comment-parser": "1.4.7", "debug": "4.4.3", "escape-string-regexp": "4.0.0", "espree": "11.2.0", "esquery": "1.7.0", "html-entities": "2.6.0", "object-deep-merge": "2.0.1", "parse-imports-exports": "0.2.4", "semver": "7.8.2", "spdx-expression-parse": "4.0.0", "to-valid-identifier": "1.0.0" }, "peerDependencies": { "eslint": "10.6.0" } }, "sha512-0TchoK1uS4VxHSo3P4CyWQ31Lm+6zsT+xkHMC5KbFKwgOf8YrXPf1Bl8EP7kpgw1wfe/Ui5jz5mSX7ou8WAVuw=="], + + "eslint-plugin-regexp": ["eslint-plugin-regexp@3.1.0", "", { "dependencies": { "@eslint-community/eslint-utils": "4.9.1", "@eslint-community/regexpp": "4.12.2", "comment-parser": "1.4.7", "jsdoc-type-pratt-parser": "7.2.0", "refa": "0.12.1", "regexp-ast-analysis": "0.7.1", "scslre": "0.3.0" }, "peerDependencies": { "eslint": "10.6.0" } }, "sha512-qGXIC3DIKZHcK1H9A9+Byz9gmndY6TTSRkSMTZpNXdyCw2ObSehRgccJv35n9AdUakEjQp5VFNLas6BMXizCZg=="], + + "eslint-plugin-unicorn": ["eslint-plugin-unicorn@65.0.1", "", { "dependencies": { "@babel/helper-validator-identifier": "7.28.5", "@eslint-community/eslint-utils": "4.9.1", "change-case": "5.4.4", "ci-info": "4.4.0", "core-js-compat": "3.49.0", "detect-indent": "7.0.2", "find-up-simple": "1.0.1", "globals": "17.6.0", "indent-string": "5.0.0", "is-builtin-module": "5.0.0", "jsesc": "3.1.0", "pluralize": "8.0.0", "regjsparser": "0.13.1", "semver": "7.8.2", "strip-indent": "4.1.1" }, "peerDependencies": { "eslint": "10.6.0" } }, "sha512-daCrQrgxOoOz2uMPWB3Y3vvv/5q+ncwICI8IjoebiwtW87CaY4tAN5EEiRXTYVnf7qi1v1BGBdHOSnZLV0rx6A=="], + + "eslint-plugin-vue": ["eslint-plugin-vue@10.9.2", "", { "dependencies": { "@eslint-community/eslint-utils": "4.9.1", "natural-compare": "1.4.0", "nth-check": "2.1.1", "postcss-selector-parser": "7.1.1", "semver": "7.8.2", "xml-name-validator": "4.0.0" }, "optionalDependencies": { "@stylistic/eslint-plugin": "5.10.0", "@typescript-eslint/parser": "8.61.0" }, "peerDependencies": { "eslint": "10.6.0", "vue-eslint-parser": "10.4.1" } }, "sha512-4g7ZP3pYcuqd7Zp0pzUKcos0W+RkjBz4EGdhJ92FcYk6v03Ti/GK5NwjgsjxHK+98eXDbHeK7VtX1az7/8doZA=="], + + "eslint-processor-vue-blocks": ["eslint-processor-vue-blocks@2.0.0", "", { "peerDependencies": { "@vue/compiler-sfc": "3.5.35", "eslint": "10.6.0" } }, "sha512-u4W0CJwGoWY3bjXAuFpc/b6eK3NQEI8MoeW7ritKj3G3z/WtHrKjkqf+wk8mPEy5rlMGS+k6AZYOw2XBoN/02Q=="], + + "eslint-scope": ["eslint-scope@9.1.2", "", { "dependencies": { "@types/esrecurse": "4.3.1", "@types/estree": "1.0.9", "esrecurse": "4.3.0", "estraverse": "5.3.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="], + + "eslint-typegen": ["eslint-typegen@2.3.1", "", { "dependencies": { "json-schema-to-typescript-lite": "15.0.0", "ohash": "2.0.11" }, "peerDependencies": { "eslint": "10.6.0" } }, "sha512-zVdh8rThBvv2o5T/K524Fr5iy1Jo0q09rHL7y7FbOhgMB177T2gw+shxfC4ChCEqdq6/y6LJA4j8Fbr/Xls9aw=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + + "espree": ["espree@11.2.0", "", { "dependencies": { "acorn": "8.16.0", "acorn-jsx": "5.3.2", "eslint-visitor-keys": "5.0.1" } }, "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw=="], + + "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "5.3.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "5.3.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "1.0.9" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], + + "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], + + "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], + + "events-universal": ["events-universal@1.0.1", "", { "dependencies": { "bare-events": "2.8.3" } }, "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw=="], + + "execa": ["execa@8.0.1", "", { "dependencies": { "cross-spawn": "7.0.6", "get-stream": "8.0.1", "human-signals": "5.0.0", "is-stream": "3.0.0", "merge-stream": "2.0.0", "npm-run-path": "5.3.0", "onetime": "6.0.0", "signal-exit": "4.1.0", "strip-final-newline": "3.0.0" } }, "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg=="], + + "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="], + + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "@nodelib/fs.walk": "1.2.8", "glob-parent": "5.1.2", "merge2": "1.4.1", "micromatch": "4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fast-npm-meta": ["fast-npm-meta@1.5.1", "", { "bin": { "fast-npm-meta": "dist/cli.mjs" } }, "sha512-tWhw7z4jFuQgZB9tbQyUh5BY9nNd/wimM+fBLfmmJjakkJDNvbJKm0nQ5ruPKC0us1HGg7L6iBk1fxpSzcgSaA=="], + + "fast-string-truncated-width": ["fast-string-truncated-width@3.0.3", "", {}, "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g=="], + + "fast-string-width": ["fast-string-width@3.0.2", "", { "dependencies": { "fast-string-truncated-width": "3.0.3" } }, "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg=="], + + "fast-wrap-ansi": ["fast-wrap-ansi@0.2.0", "", { "dependencies": { "fast-string-width": "3.0.2" } }, "sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w=="], + + "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "1.1.0" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], + + "fdir": ["fdir@6.5.0", "", { "optionalDependencies": { "picomatch": "4.0.4" } }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "4.0.1" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + + "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "find-up": ["find-up@8.0.0", "", { "dependencies": { "locate-path": "8.0.0", "unicorn-magic": "0.3.0" } }, "sha512-JGG8pvDi2C+JxidYdIwQDyS/CgcrIdh18cvgxcBge3wSHRQOrooMD3GlFBcmMJAN9M42SAZjDp5zv1dglJjwww=="], + + "find-up-simple": ["find-up-simple@1.0.1", "", {}, "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ=="], + + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "3.4.2", "keyv": "4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], + + "flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="], + + "fontaine": ["fontaine@0.8.0", "", { "dependencies": { "@capsizecss/unpack": "4.0.0", "css-tree": "3.2.1", "magic-regexp": "0.10.0", "magic-string": "0.30.21", "pathe": "2.0.3", "ufo": "1.6.4", "unplugin": "2.3.11" } }, "sha512-eek1GbzOdWIj9FyQH/emqW1aEdfC3lYRCHepzwlFCm5T77fBSRSyNRKE6/antF1/B1M+SfJXVRQTY9GAr7lnDg=="], + + "fontkitten": ["fontkitten@1.0.3", "", { "dependencies": { "tiny-inflate": "1.0.3" } }, "sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw=="], + + "fontless": ["fontless@0.2.1", "", { "dependencies": { "consola": "3.4.2", "css-tree": "3.2.1", "defu": "6.1.7", "esbuild": "0.27.7", "fontaine": "0.8.0", "jiti": "2.7.0", "lightningcss": "1.32.0", "magic-string": "0.30.21", "ohash": "2.0.11", "pathe": "2.0.3", "ufo": "1.6.4", "unifont": "0.7.4", "unstorage": "1.17.5" }, "optionalDependencies": { "vite": "7.3.3" } }, "sha512-mUWZ8w91/mw2KEcZ6gHNoNNmsAq9Wiw2IypIux5lM03nhXm+WSloXGUNuRETNTLqZexMgpt7Aj/v63qqrsWraQ=="], + + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "7.0.6", "signal-exit": "4.1.0" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + + "fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="], + + "framer-motion": ["framer-motion@12.40.0", "", { "dependencies": { "motion-dom": "12.40.0", "motion-utils": "12.39.0", "tslib": "2.8.1" } }, "sha512-uaBd3qC1v3KQqBEjwTUd183K6PbS+j0yR9w9VmEOLWA/tnUcSn8Xa3uck7t4dgpDoUss8xQTcj8W2L07lrnLFg=="], + + "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "fuse.js": ["fuse.js@7.4.2", "", {}, "sha512-LVbzjD4WA6UP5B1UnP8wuaXJiLnqMdM/E4fiJXTJ5haJ5b/MBNsK29h2fm6swEoQaVQjvYFWKLE2RanyZIoRVQ=="], + + "fzf": ["fzf@0.5.2", "", {}, "sha512-Tt4kuxLXFKHy8KT40zwsUPUkg1CrsgY25FxA2U/j/0WgEDCk3ddc/zLTCCcbSHX9FcKtLuVaDGtGE/STWC+j3Q=="], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "get-east-asian-width": ["get-east-asian-width@1.6.0", "", {}, "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA=="], + + "get-port-please": ["get-port-please@3.2.0", "", {}, "sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A=="], + + "get-stream": ["get-stream@8.0.1", "", {}, "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA=="], + + "get-tsconfig": ["get-tsconfig@4.14.0", "", { "dependencies": { "resolve-pkg-maps": "1.0.0" } }, "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA=="], + + "giget": ["giget@3.2.0", "", { "bin": { "giget": "dist/cli.mjs" } }, "sha512-GvHTWcykIR/fP8cj8dMpuMMkvaeJfPvYnhq0oW+chSeIr+ldX21ifU2Ms6KBoyKZQZmVaUAAhQ2EZ68KJF8a7A=="], + + "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "10.2.5", "minipass": "7.1.3", "path-scurry": "2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], + + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "global-directory": ["global-directory@4.0.1", "", { "dependencies": { "ini": "4.1.1" } }, "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q=="], + + "globals": ["globals@17.6.0", "", {}, "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA=="], + + "globby": ["globby@16.2.0", "", { "dependencies": { "@sindresorhus/merge-streams": "4.0.0", "fast-glob": "3.3.3", "ignore": "7.0.5", "is-path-inside": "4.0.0", "slash": "5.1.0", "unicorn-magic": "0.4.0" } }, "sha512-QrJia2qDf5BB/V6HYlDTs0I0lBahyjLzpGQg3KT7FnCdTonAyPy2RtY802m2k4ALx6Dp752f82WsOczEVr3l6Q=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "gzip-size": ["gzip-size@7.0.0", "", { "dependencies": { "duplexer": "0.1.2" } }, "sha512-O1Ld7Dr+nqPnmGpdhzLmMTQ4vAsD+rHwMm1NLUmoUFFymBOMKxCCrtDxqdBRYXdeEPEi3SyoR4TizJLQrnKBNA=="], + + "h3": ["h3@1.15.11", "", { "dependencies": { "cookie-es": "1.2.3", "crossws": "0.3.5", "defu": "6.1.7", "destr": "2.0.5", "iron-webcrypto": "1.2.1", "node-mock-http": "1.0.4", "radix3": "1.1.2", "ufo": "1.6.4", "uncrypto": "0.1.3" } }, "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg=="], + + "hasown": ["hasown@2.0.3", "", { "dependencies": { "function-bind": "1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="], + + "hey-listen": ["hey-listen@1.0.8", "", {}, "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q=="], + + "hookable": ["hookable@6.1.1", "", {}, "sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ=="], + + "html-entities": ["html-entities@2.6.0", "", {}, "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ=="], + + "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.2", "toidentifier": "1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], + + "http-shutdown": ["http-shutdown@1.2.2", "", {}, "sha512-S9wWkJ/VSY9/k4qcjG318bqJNruzE4HySUhFYknwmu6LBP97KLLfwNf+n4V1BHurvFNkSKLFnK/RsuUnRTf9Vw=="], + + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "7.1.4", "debug": "4.4.3" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + + "httpxy": ["httpxy@0.5.1", "", {}, "sha512-JPhqYiixe1A1I+MXDewWDZqeudBGU8Q9jCHYN8ML+779RQzLjTi78HBvWz4jMxUD6h2/vUL12g4q/mFM0OUw1A=="], + + "human-signals": ["human-signals@5.0.0", "", {}, "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "image-meta": ["image-meta@0.2.2", "", {}, "sha512-3MOLanc3sb3LNGWQl1RlQlNWURE5g32aUphrDyFeCsxBTk08iE3VNe4CwsUZ0Qs1X+EfX0+r29Sxdpza4B+yRA=="], + + "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], + + "impound": ["impound@1.1.5", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.31", "es-module-lexer": "2.1.0", "pathe": "2.0.3", "unplugin": "3.0.0", "unplugin-utils": "0.3.1" } }, "sha512-5AUn+QE0UofqNHu5f2Skf6Svukdg4ehOIq8O0EtqIx4jta0CDZYBPqpIHt0zrlUTiFVYlLpeH39DoikXBjPKpA=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "ini": ["ini@4.1.1", "", {}, "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g=="], + + "ioredis": ["ioredis@5.10.1", "", { "dependencies": { "@ioredis/commands": "1.5.1", "cluster-key-slot": "1.1.2", "debug": "4.4.3", "denque": "2.1.0", "lodash.defaults": "4.2.0", "lodash.isarguments": "3.1.0", "redis-errors": "1.2.0", "redis-parser": "3.0.0", "standard-as-callback": "2.1.0" } }, "sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA=="], + + "iron-webcrypto": ["iron-webcrypto@1.2.1", "", {}, "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg=="], + + "is-builtin-module": ["is-builtin-module@5.0.0", "", { "dependencies": { "builtin-modules": "5.2.0" } }, "sha512-f4RqJKBUe5rQkJ2eJEJBXSticB3hGbN9j0yxxMQFqIW89Jp9WYFtzfTcRlstDKVUTRzSOTLKRfO9vIztenwtxA=="], + + "is-core-module": ["is-core-module@2.16.2", "", { "dependencies": { "hasown": "2.0.3" } }, "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA=="], + + "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-in-ssh": ["is-in-ssh@1.0.0", "", {}, "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw=="], + + "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], + + "is-installed-globally": ["is-installed-globally@1.0.0", "", { "dependencies": { "global-directory": "4.0.1", "is-path-inside": "4.0.0" } }, "sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ=="], + + "is-module": ["is-module@1.0.0", "", {}, "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-path-inside": ["is-path-inside@4.0.0", "", {}, "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA=="], + + "is-reference": ["is-reference@1.2.1", "", { "dependencies": { "@types/estree": "1.0.9" } }, "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ=="], + + "is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="], + + "is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="], + + "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "isomorphic.js": ["isomorphic.js@0.2.5", "", {}, "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw=="], + + "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + + "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], + + "js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="], + + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "jsdoc-type-pratt-parser": ["jsdoc-type-pratt-parser@7.2.0", "", {}, "sha512-dh140MMgjyg3JhJZY/+iEzW+NO5xR2gpbDFKHqotCmexElVntw7GjWjt511+C/Ef02RU5TKYrJo/Xlzk+OLaTw=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-schema-to-typescript-lite": ["json-schema-to-typescript-lite@15.0.0", "", { "dependencies": { "@apidevtools/json-schema-ref-parser": "14.2.1", "@types/json-schema": "7.0.15" } }, "sha512-5mMORSQm9oTLyjM4mWnyNBi2T042Fhg1/0gCIB6X8U/LVpM2A+Nmj2yEyArqVouDmFThDxpEXcnTgSrjkGJRFA=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], + + "klona": ["klona@2.0.6", "", {}, "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA=="], + + "knitwork": ["knitwork@1.3.0", "", {}, "sha512-4LqMNoONzR43B1W0ek0fhXMsDNW/zxa1NdFAVMY+k28pgZLovR4G3PB5MrpTxCy1QaZCqNoiaKPr5w5qZHfSNw=="], + + "launch-editor": ["launch-editor@2.13.2", "", { "dependencies": { "picocolors": "1.1.1", "shell-quote": "1.8.3" } }, "sha512-4VVDnbOpLXy/s8rdRCSXb+zfMeFR0WlJWpET1iA9CQdlZDfwyLjUuGQzXU4VeOoey6AicSAluWan7Etga6Kcmg=="], + + "lazystream": ["lazystream@1.0.1", "", { "dependencies": { "readable-stream": "2.3.8" } }, "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "1.2.1", "type-check": "0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "lib0": ["lib0@0.2.117", "", { "dependencies": { "isomorphic.js": "0.2.5" }, "bin": { "0serve": "bin/0serve.js", "0gentesthtml": "bin/gentesthtml.js", "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js" } }, "sha512-DeXj9X5xDCjgKLU/7RR+/HQEVzuuEUiwldwOGsHK/sfAfELGWEyTcf0x+uOvCvK3O2zPmZePXWL85vtia6GyZw=="], + + "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "2.1.2" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + + "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], + + "linkifyjs": ["linkifyjs@4.3.3", "", {}, "sha512-P8aEP5U/D1/IlTY2OeYsErdwh9bGuLE30NcXtKEjgdHcahveQoQwM2yZNsioQHsWFz0P7KKudisbrzCgR0sDHg=="], + + "listhen": ["listhen@1.10.0", "", { "dependencies": { "@parcel/watcher": "2.5.6", "@parcel/watcher-wasm": "2.5.6", "citty": "0.2.2", "consola": "3.4.2", "crossws": "0.4.5", "defu": "6.1.7", "get-port-please": "3.2.0", "h3": "1.15.11", "http-shutdown": "1.2.2", "jiti": "2.7.0", "mlly": "1.8.2", "node-forge": "1.4.0", "pathe": "2.0.3", "std-env": "4.1.0", "tinyclip": "0.1.12", "ufo": "1.6.4", "untun": "0.1.3", "uqr": "0.1.3" }, "bin": { "listen": "bin/listhen.mjs", "listhen": "bin/listhen.mjs" } }, "sha512-kfz4C0OrC6IpaVMtYDJtf6PFjurxe9NBBoDAh/o2p587INryFOO4DQ9OetbCdDrWFt1m1CJKvYrzkGsuPHw8nQ=="], + + "local-pkg": ["local-pkg@1.2.1", "", { "dependencies": { "mlly": "1.8.2", "pkg-types": "2.3.1", "quansync": "0.2.11" } }, "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q=="], + + "locate-path": ["locate-path@8.0.0", "", { "dependencies": { "p-locate": "6.0.0" } }, "sha512-XT9ewWAC43tiAV7xDAPflMkG0qOPn2QjHqlgX8FOqmWa/rxnyYDulF9T0F7tRy1u+TVTmK/M//6VIOye+2zDXg=="], + + "lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="], + + "lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="], + + "lodash.isarguments": ["lodash.isarguments@3.1.0", "", {}, "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="], + + "lodash.memoize": ["lodash.memoize@4.1.2", "", {}, "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag=="], + + "lodash.uniq": ["lodash.uniq@4.5.0", "", {}, "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ=="], + + "lru-cache": ["lru-cache@11.4.0", "", {}, "sha512-W+R+kFL4HgVxONq2bhXPi3bGpzGe/yEhVOp233qw9wCRtgncJ15P3bC+e4zZMu4Cq7d+WAJjXGW0uUkifhcatA=="], + + "magic-regexp": ["magic-regexp@0.10.0", "", { "dependencies": { "estree-walker": "3.0.3", "magic-string": "0.30.21", "mlly": "1.8.2", "regexp-tree": "0.1.27", "type-level-regexp": "0.1.17", "ufo": "1.6.4", "unplugin": "2.3.11" } }, "sha512-Uly1Bu4lO1hwHUW0CQeSWuRtzCMNO00CmXtS8N6fyvB3B979GOEEeAkiTUDsmbYLAbvpUS/Kt5c4ibosAzVyVg=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "magic-string-ast": ["magic-string-ast@1.0.3", "", { "dependencies": { "magic-string": "0.30.21" } }, "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA=="], + + "magicast": ["magicast@0.5.3", "", { "dependencies": { "@babel/parser": "7.29.3", "@babel/types": "7.29.0", "source-map-js": "1.2.1" } }, "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw=="], + + "marked": ["marked@17.0.6", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-gB0gkNafnonOw0obSTEGZTT86IuhILt2Wfx0mWH/1Au83kybTayroZ/V6nS25mN7u8ASy+5fMhgB3XPNrOZdmA=="], + + "mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="], + + "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "3.0.3", "picomatch": "2.3.2" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "mime": ["mime@4.1.0", "", { "bin": { "mime": "bin/cli.js" } }, "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw=="], + + "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + + "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], + + "mimic-fn": ["mimic-fn@4.0.0", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="], + + "minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "5.0.6" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + + "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + + "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "7.1.3" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], + + "mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "8.16.0", "pathe": "2.0.3", "pkg-types": "1.3.1", "ufo": "1.6.4" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="], + + "mocked-exports": ["mocked-exports@0.1.1", "", {}, "sha512-aF7yRQr/Q0O2/4pIXm6PZ5G+jAd7QS4Yu8m+WEeEHGnbo+7mE36CbLSDQiXYV8bVL3NfmdeqPJct0tUlnjVSnA=="], + + "motion-dom": ["motion-dom@12.40.0", "", { "dependencies": { "motion-utils": "12.39.0" } }, "sha512-HxU3ZaBwNPVQUBQf1xxgq+7JrPNZvjLVxgbpEZL7RrWJnsxOf0/OM+yrHG9ogLQ31Do/r57Oz2gQWPK+6q62mg=="], + + "motion-utils": ["motion-utils@12.39.0", "", {}, "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ=="], + + "motion-v": ["motion-v@2.3.0", "", { "dependencies": { "framer-motion": "12.40.0", "hey-listen": "1.0.8", "motion-dom": "12.40.0", "motion-utils": "12.39.0" }, "peerDependencies": { "@vueuse/core": "14.3.0", "vue": "3.5.35" } }, "sha512-J0CCfXtICCni9RjotDUBOs57xNpYI9yyBSohEOxaRHrmjwOtlw291fhRu/mdgEdSasys96R028YDDOAtWBbRaA=="], + + "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="], + + "nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], + + "nanotar": ["nanotar@0.3.0", "", {}, "sha512-Kv2JYYiCzt16Kt5QwAc9BFG89xfPNBx+oQL4GQXD9nLqPkZBiNaqaCWtwnbk/q7UVsTYevvM1b0UF8zmEI4pCg=="], + + "napi-postinstall": ["napi-postinstall@0.3.4", "", { "bin": { "napi-postinstall": "lib/cli.js" } }, "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ=="], + + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + + "nitropack": ["nitropack@2.13.4", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.2", "@rollup/plugin-alias": "6.0.0", "@rollup/plugin-commonjs": "29.0.2", "@rollup/plugin-inject": "5.0.5", "@rollup/plugin-json": "6.1.0", "@rollup/plugin-node-resolve": "16.0.3", "@rollup/plugin-replace": "6.0.3", "@rollup/plugin-terser": "1.0.0", "@vercel/nft": "1.5.0", "archiver": "7.0.1", "c12": "3.3.4", "chokidar": "5.0.0", "citty": "0.2.2", "compatx": "0.2.0", "confbox": "0.2.4", "consola": "3.4.2", "cookie-es": "2.0.1", "croner": "10.0.1", "crossws": "0.3.5", "db0": "0.3.4", "defu": "6.1.7", "destr": "2.0.5", "dot-prop": "10.1.0", "esbuild": "0.28.0", "escape-string-regexp": "5.0.0", "etag": "1.8.1", "exsolve": "1.0.8", "globby": "16.2.0", "gzip-size": "7.0.0", "h3": "1.15.11", "hookable": "5.5.3", "httpxy": "0.5.1", "ioredis": "5.10.1", "jiti": "2.7.0", "klona": "2.0.6", "knitwork": "1.3.0", "listhen": "1.10.0", "magic-string": "0.30.21", "magicast": "0.5.3", "mime": "4.1.0", "mlly": "1.8.2", "node-fetch-native": "1.6.7", "node-mock-http": "1.0.4", "ofetch": "1.5.1", "ohash": "2.0.11", "pathe": "2.0.3", "perfect-debounce": "2.1.0", "pkg-types": "2.3.1", "pretty-bytes": "7.1.0", "radix3": "1.1.2", "rollup": "4.60.4", "rollup-plugin-visualizer": "7.0.1", "scule": "1.3.0", "semver": "7.8.2", "serve-placeholder": "2.0.2", "serve-static": "2.2.1", "source-map": "0.7.6", "std-env": "4.1.0", "ufo": "1.6.4", "ultrahtml": "1.6.0", "uncrypto": "0.1.3", "unctx": "2.5.0", "unenv": "2.0.0-rc.24", "unimport": "6.3.0", "unplugin-utils": "0.3.1", "unstorage": "1.17.5", "untyped": "2.0.0", "unwasm": "0.5.3", "youch": "4.1.1", "youch-core": "0.3.3" }, "bin": { "nitro": "dist/cli/index.mjs", "nitropack": "dist/cli/index.mjs" } }, "sha512-tX7bT6zxNeMwkc6hxHiZeUoTOjVrcjoh1Z3cmxOlodIqjl4HISgqfGOmkWSayky3Nv9Z5+KQH52F8nmXJY5AAA=="], + + "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], + + "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "5.0.0" } }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + + "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], + + "node-forge": ["node-forge@1.4.0", "", {}, "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ=="], + + "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="], + + "node-mock-http": ["node-mock-http@1.0.4", "", {}, "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ=="], + + "node-releases": ["node-releases@2.0.44", "", {}, "sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ=="], + + "nopt": ["nopt@8.1.0", "", { "dependencies": { "abbrev": "3.0.1" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A=="], + + "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], + + "nostics": ["nostics@0.2.0", "", { "dependencies": { "magic-string": "0.30.21", "oxc-parser": "0.132.0", "unplugin": "3.0.0" } }, "sha512-/WQpI46UMbqvy1okYb+V+9wW3J8/m6GJ33wm691n/tyi6YtJiZ6ssJjENAU7y4evfYrrgYN9HllKDzPvffil1w=="], + + "npm-run-path": ["npm-run-path@5.3.0", "", { "dependencies": { "path-key": "4.0.0" } }, "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ=="], + + "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], + + "nuxt": ["nuxt@4.4.8", "", { "dependencies": { "@dxup/nuxt": "0.4.1", "@nuxt/cli": "3.35.2", "@nuxt/devtools": "3.2.4", "@nuxt/kit": "4.4.8", "@nuxt/nitro-server": "4.4.8", "@nuxt/schema": "4.4.8", "@nuxt/telemetry": "2.8.0", "@nuxt/vite-builder": "4.4.8", "@unhead/vue": "2.1.15", "@vue/shared": "3.5.35", "chokidar": "5.0.0", "compatx": "0.2.0", "consola": "3.4.2", "cookie-es": "3.1.1", "defu": "6.1.7", "devalue": "5.8.1", "errx": "0.1.0", "escape-string-regexp": "5.0.0", "exsolve": "1.0.8", "hookable": "6.1.1", "ignore": "7.0.5", "impound": "1.1.5", "jiti": "2.7.0", "klona": "2.0.6", "knitwork": "1.3.0", "magic-string": "0.30.21", "mlly": "1.8.2", "nanotar": "0.3.0", "nypm": "0.6.6", "ofetch": "1.5.1", "ohash": "2.0.11", "on-change": "6.0.2", "oxc-minify": "0.133.0", "oxc-parser": "0.133.0", "oxc-transform": "0.133.0", "oxc-walker": "1.0.0", "pathe": "2.0.3", "perfect-debounce": "2.1.0", "picomatch": "4.0.4", "pkg-types": "2.3.1", "rou3": "0.8.1", "scule": "1.3.0", "semver": "7.8.2", "std-env": "4.1.0", "tinyglobby": "0.2.17", "ufo": "1.6.4", "ultrahtml": "1.6.0", "uncrypto": "0.1.3", "unctx": "2.5.0", "unhead": "2.1.15", "unimport": "6.3.0", "unplugin": "3.0.0", "unrouting": "0.1.7", "untyped": "2.0.0", "vue": "3.5.35", "vue-router": "5.1.0" }, "optionalDependencies": { "@parcel/watcher": "2.5.6" }, "bin": { "nuxi": "bin/nuxt.mjs", "nuxt": "bin/nuxt.mjs" } }, "sha512-r/DGE4cNkEDclOw9tbJ18zqu+ix3me+7QCfumPdl5lBXGWgCajskzuq/HzDkHKfIZsn7ACVEjMLRNA2teh++bQ=="], + + "nypm": ["nypm@0.6.6", "", { "dependencies": { "citty": "0.2.2", "pathe": "2.0.3", "tinyexec": "1.1.2" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-vRyr0r4cbBapw07Xw8xrj9Teq3o7MUD35rSaTcanDbW+aK2XHDgJFiU6ZTj2GBw7Q12ysdsyFss+Vdz4hQ0Y6Q=="], + + "object-deep-merge": ["object-deep-merge@2.0.1", "", {}, "sha512-aKttDKcU3pyZqKcCkDhsMn70WmZFG2JGDQLP9EcLyTSIFQRCPWLAmBZRLJnrVUrhPG1jETEEbfdgbNtJf1LyMg=="], + + "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], + + "ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "2.0.5", "node-fetch-native": "1.6.7", "ufo": "1.6.4" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="], + + "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], + + "on-change": ["on-change@6.0.2", "", {}, "sha512-08+12qcOVEA0fS9g/VxKS27HaT94nRutUT77J2dr8zv/unzXopvhBuF8tNLWsoLQ5IgrQ6eptGeGqUYat82U1w=="], + + "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], + + "onetime": ["onetime@6.0.0", "", { "dependencies": { "mimic-fn": "4.0.0" } }, "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ=="], + + "open": ["open@10.2.0", "", { "dependencies": { "default-browser": "5.5.0", "define-lazy-prop": "3.0.0", "is-inside-container": "1.0.0", "wsl-utils": "0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], + + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "0.1.4", "fast-levenshtein": "2.0.6", "levn": "0.4.1", "prelude-ls": "1.2.1", "type-check": "0.4.0", "word-wrap": "1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "orderedmap": ["orderedmap@2.1.1", "", {}, "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g=="], + + "oxc-minify": ["oxc-minify@0.133.0", "", { "optionalDependencies": { "@oxc-minify/binding-android-arm-eabi": "0.133.0", "@oxc-minify/binding-android-arm64": "0.133.0", "@oxc-minify/binding-darwin-arm64": "0.133.0", "@oxc-minify/binding-darwin-x64": "0.133.0", "@oxc-minify/binding-freebsd-x64": "0.133.0", "@oxc-minify/binding-linux-arm-gnueabihf": "0.133.0", "@oxc-minify/binding-linux-arm-musleabihf": "0.133.0", "@oxc-minify/binding-linux-arm64-gnu": "0.133.0", "@oxc-minify/binding-linux-arm64-musl": "0.133.0", "@oxc-minify/binding-linux-ppc64-gnu": "0.133.0", "@oxc-minify/binding-linux-riscv64-gnu": "0.133.0", "@oxc-minify/binding-linux-riscv64-musl": "0.133.0", "@oxc-minify/binding-linux-s390x-gnu": "0.133.0", "@oxc-minify/binding-linux-x64-gnu": "0.133.0", "@oxc-minify/binding-linux-x64-musl": "0.133.0", "@oxc-minify/binding-openharmony-arm64": "0.133.0", "@oxc-minify/binding-wasm32-wasi": "0.133.0", "@oxc-minify/binding-win32-arm64-msvc": "0.133.0", "@oxc-minify/binding-win32-ia32-msvc": "0.133.0", "@oxc-minify/binding-win32-x64-msvc": "0.133.0" } }, "sha512-6bNsYU+5WNIaNHB16zHnL24cUaJuKiPzUvjENoMale3+U8ZBMbGYgdgt//frx0ge7UcgEGIpqtukGGNPT0kxfQ=="], + + "oxc-parser": ["oxc-parser@0.133.0", "", { "dependencies": { "@oxc-project/types": "0.133.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.133.0", "@oxc-parser/binding-android-arm64": "0.133.0", "@oxc-parser/binding-darwin-arm64": "0.133.0", "@oxc-parser/binding-darwin-x64": "0.133.0", "@oxc-parser/binding-freebsd-x64": "0.133.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.133.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.133.0", "@oxc-parser/binding-linux-arm64-gnu": "0.133.0", "@oxc-parser/binding-linux-arm64-musl": "0.133.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.133.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.133.0", "@oxc-parser/binding-linux-riscv64-musl": "0.133.0", "@oxc-parser/binding-linux-s390x-gnu": "0.133.0", "@oxc-parser/binding-linux-x64-gnu": "0.133.0", "@oxc-parser/binding-linux-x64-musl": "0.133.0", "@oxc-parser/binding-openharmony-arm64": "0.133.0", "@oxc-parser/binding-wasm32-wasi": "0.133.0", "@oxc-parser/binding-win32-arm64-msvc": "0.133.0", "@oxc-parser/binding-win32-ia32-msvc": "0.133.0", "@oxc-parser/binding-win32-x64-msvc": "0.133.0" } }, "sha512-661RSx+ZcjBmjBYid+Fpp/2F5EbtildpeoZh5HdgnGs+jZ03nqQEQW8yGkt4BGyOC3OMPDQQRl8M5kqD2/g6jw=="], + + "oxc-transform": ["oxc-transform@0.133.0", "", { "optionalDependencies": { "@oxc-transform/binding-android-arm-eabi": "0.133.0", "@oxc-transform/binding-android-arm64": "0.133.0", "@oxc-transform/binding-darwin-arm64": "0.133.0", "@oxc-transform/binding-darwin-x64": "0.133.0", "@oxc-transform/binding-freebsd-x64": "0.133.0", "@oxc-transform/binding-linux-arm-gnueabihf": "0.133.0", "@oxc-transform/binding-linux-arm-musleabihf": "0.133.0", "@oxc-transform/binding-linux-arm64-gnu": "0.133.0", "@oxc-transform/binding-linux-arm64-musl": "0.133.0", "@oxc-transform/binding-linux-ppc64-gnu": "0.133.0", "@oxc-transform/binding-linux-riscv64-gnu": "0.133.0", "@oxc-transform/binding-linux-riscv64-musl": "0.133.0", "@oxc-transform/binding-linux-s390x-gnu": "0.133.0", "@oxc-transform/binding-linux-x64-gnu": "0.133.0", "@oxc-transform/binding-linux-x64-musl": "0.133.0", "@oxc-transform/binding-openharmony-arm64": "0.133.0", "@oxc-transform/binding-wasm32-wasi": "0.133.0", "@oxc-transform/binding-win32-arm64-msvc": "0.133.0", "@oxc-transform/binding-win32-ia32-msvc": "0.133.0", "@oxc-transform/binding-win32-x64-msvc": "0.133.0" } }, "sha512-9lt2b+hkG6yqe0fUDMHhMk7rgI9uTjNxU9wauQiYnHzc4kZI8JP/OhBqXTIJQTrqRJ8CkSH3O5AhQ13ke28yNg=="], + + "oxc-walker": ["oxc-walker@1.0.0", "", { "dependencies": { "magic-regexp": "0.11.0" }, "optionalDependencies": { "oxc-parser": "0.133.0" } }, "sha512-eMsHflAGfOskpWxtp9xP/f5b96XLEU8ifTd2gOOCkdux9HMxKGy5S1ru0Gh1B3aPu+YbfmWUUVkcb7MrZz3XyQ=="], + + "p-limit": ["p-limit@4.0.0", "", { "dependencies": { "yocto-queue": "1.2.2" } }, "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ=="], + + "p-locate": ["p-locate@6.0.0", "", { "dependencies": { "p-limit": "4.0.0" } }, "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw=="], + + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + + "package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], + + "parse-imports-exports": ["parse-imports-exports@0.2.4", "", { "dependencies": { "parse-statements": "1.0.11" } }, "sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ=="], + + "parse-statements": ["parse-statements@1.0.11", "", {}, "sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA=="], + + "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], + + "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], + + "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "11.4.0", "minipass": "7.1.3" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "perfect-debounce": ["perfect-debounce@2.1.0", "", {}, "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + + "pkg-types": ["pkg-types@2.3.1", "", { "dependencies": { "confbox": "0.2.4", "exsolve": "1.0.8", "pathe": "2.0.3" } }, "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg=="], + + "pluralize": ["pluralize@8.0.0", "", {}, "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA=="], + + "postcss": ["postcss@8.5.15", "", { "dependencies": { "nanoid": "3.3.12", "picocolors": "1.1.1", "source-map-js": "1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="], + + "postcss-calc": ["postcss-calc@10.1.1", "", { "dependencies": { "postcss-selector-parser": "7.1.1", "postcss-value-parser": "4.2.0" }, "peerDependencies": { "postcss": "8.5.15" } }, "sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw=="], + + "postcss-colormin": ["postcss-colormin@8.0.0", "", { "dependencies": { "@colordx/core": "5.4.3", "browserslist": "4.28.2", "caniuse-api": "3.0.0", "postcss-value-parser": "4.2.0" }, "peerDependencies": { "postcss": "8.5.15" } }, "sha512-KKwMmsSgsmdYXqrjQeqL3tnuIFtctiR1GEMHdjNpDpz/TCRkkkok2mMcreK2zVV3l7POWOmAkR2xYHUpRUK1DA=="], + + "postcss-convert-values": ["postcss-convert-values@8.0.0", "", { "dependencies": { "browserslist": "4.28.2", "postcss-value-parser": "4.2.0" }, "peerDependencies": { "postcss": "8.5.15" } }, "sha512-Ohtj3rNZWawTRePv5NCHTy8VJSdJ/G/uKuxcxJreOMichuqcT6uEl2TAnopVeJCJ/c13jaSqg7m63yFLM5zBsA=="], + + "postcss-discard-comments": ["postcss-discard-comments@8.0.0", "", { "dependencies": { "postcss-selector-parser": "7.1.1" }, "peerDependencies": { "postcss": "8.5.15" } }, "sha512-zGpvVLj2sbagEp+BTVETvAfkZdGVA6rALNujDK/WTIjdf1/rQOxOG8BBzkI8UQgnw8SkL6xffAfbtGMHFypadw=="], + + "postcss-discard-duplicates": ["postcss-discard-duplicates@8.0.0", "", { "peerDependencies": { "postcss": "8.5.15" } }, "sha512-zjRyYmNGI3PTipKBBtCgExlmZXQn49KvKoaiNnR2g+iXxeNk7GY5Js2ULtZXPrCYeqjPagrzKIBNcBocvXCR7g=="], + + "postcss-discard-empty": ["postcss-discard-empty@8.0.0", "", { "peerDependencies": { "postcss": "8.5.15" } }, "sha512-kxPJg6EqahbBvm+l7hpYYCtpsv8dlz7Tv6wJXUXZaeuY0WGS61DxfGdZR4uVB/Cx+yi3iOHQVSqpSHKMFaBg6Q=="], + + "postcss-discard-overridden": ["postcss-discard-overridden@8.0.0", "", { "peerDependencies": { "postcss": "8.5.15" } }, "sha512-sW2OWH3l9p0FmBSVr228uztFseqroZxwgD7SGF0Ks0dRPDttSo3P8FK5ZBLtWBH2A5+chpB0J2fB/T8heKHLBw=="], + + "postcss-merge-longhand": ["postcss-merge-longhand@8.0.0", "", { "dependencies": { "postcss-value-parser": "4.2.0", "stylehacks": "8.0.0" }, "peerDependencies": { "postcss": "8.5.15" } }, "sha512-YDmAmQ8H+ljfomVpSXvr9NA0GP01fraQJqjWBYoMVGg6rOT+PJLwPyeVo2ekn4WB4ZVSH5ddtK3DTRxbz6CFzg=="], + + "postcss-merge-rules": ["postcss-merge-rules@8.0.0", "", { "dependencies": { "browserslist": "4.28.2", "caniuse-api": "3.0.0", "cssnano-utils": "6.0.0", "postcss-selector-parser": "7.1.1" }, "peerDependencies": { "postcss": "8.5.15" } }, "sha512-bgstL5mpi41dDpnYGDUcI3M814NWkCMcIWpwDqEHXkHg3BT7b4XRAfNEuwJncZOVn/67kVKvWzhfv/7xyrp2uQ=="], + + "postcss-minify-font-values": ["postcss-minify-font-values@8.0.0", "", { "dependencies": { "postcss-value-parser": "4.2.0" }, "peerDependencies": { "postcss": "8.5.15" } }, "sha512-EnOHQEnSt6oH5NrL1DMFAQuwB2IOimFXTCzc9bKfUeH1jREbqIF5MAK4gQJQOC4mPUwJt4sWifAmNZ1qLu6j3Q=="], + + "postcss-minify-gradients": ["postcss-minify-gradients@8.0.0", "", { "dependencies": { "@colordx/core": "5.4.3", "cssnano-utils": "6.0.0", "postcss-value-parser": "4.2.0" }, "peerDependencies": { "postcss": "8.5.15" } }, "sha512-43iAnYIGk0ZjNx5X/rkIcHi6dhmu/vEjY0kqfUfxPuJRO+V7jx8uKIdcnL0dpfNoC5J9TSh3EtzLWbq0gpqnWA=="], + + "postcss-minify-params": ["postcss-minify-params@8.0.0", "", { "dependencies": { "browserslist": "4.28.2", "cssnano-utils": "6.0.0", "postcss-value-parser": "4.2.0" }, "peerDependencies": { "postcss": "8.5.15" } }, "sha512-z7w4QO7G55l4vMUK1Lmx03GW7iyRLgf2V5Dz/7ioSPLnXRjeD+b7m0XfAXUGrbBYYrJ6bXPk+3LoX5u4JfAcSg=="], + + "postcss-minify-selectors": ["postcss-minify-selectors@8.0.1", "", { "dependencies": { "browserslist": "4.28.2", "caniuse-api": "3.0.0", "cssesc": "3.0.0", "postcss-selector-parser": "7.1.1" }, "peerDependencies": { "postcss": "8.5.15" } }, "sha512-c31D46811kTkQDxV1KTTow79axX6gj/01AY5G7cGZg3s31KvAwP13jEFXGAzQbJ7NvOFV1pRqEia6nrAdHU7qg=="], + + "postcss-normalize-charset": ["postcss-normalize-charset@8.0.0", "", { "peerDependencies": { "postcss": "8.5.15" } }, "sha512-s88FUNDSUD8m0wBYvTQQcubVts6zhXwBU8zCD4vkRKiecd0v8cOjHVIF9r/i+5xzS/WG3f98qq4XsOM0JqvfLA=="], + + "postcss-normalize-display-values": ["postcss-normalize-display-values@8.0.0", "", { "dependencies": { "postcss-value-parser": "4.2.0" }, "peerDependencies": { "postcss": "8.5.15" } }, "sha512-gG2nBxD27fiw6Luinb1QYKdM/Co5GornRJgSD+JTwNH4PGKxImP0qyruDDav49aHUPLY3qrL3qN3LvybO7IzxQ=="], + + "postcss-normalize-positions": ["postcss-normalize-positions@8.0.0", "", { "dependencies": { "postcss-value-parser": "4.2.0" }, "peerDependencies": { "postcss": "8.5.15" } }, "sha512-t/wGqpehS20Ke7kc4QAsWpH+AJjUdMK/V5qV2RhrXkj8hO/fT1t1MJ8NL7sedWYk7ZqC7eISEJQonW5j0tU1MQ=="], + + "postcss-normalize-repeat-style": ["postcss-normalize-repeat-style@8.0.0", "", { "dependencies": { "postcss-value-parser": "4.2.0" }, "peerDependencies": { "postcss": "8.5.15" } }, "sha512-3ebOmGdCYKrBYyGKc1xhj0unEnW7beZpVU7JohVeGl7mTxR+7T6egpaawTWAVsB0pEIhcsbJVOjPKCJSoRO6Zg=="], + + "postcss-normalize-string": ["postcss-normalize-string@8.0.0", "", { "dependencies": { "postcss-value-parser": "4.2.0" }, "peerDependencies": { "postcss": "8.5.15" } }, "sha512-TvWCGZ/e04Tv31uJvOUtbexkfgUnqmQ3M2P5DkAaVzvOj+BvTkG2QjpA5Y71SL1SPxJcj4M23fNh+RDVCmG8kA=="], + + "postcss-normalize-timing-functions": ["postcss-normalize-timing-functions@8.0.0", "", { "dependencies": { "postcss-value-parser": "4.2.0" }, "peerDependencies": { "postcss": "8.5.15" } }, "sha512-uEfaXst5Xgqxv7geYUuz6vs9mn88K2NPY2RoIzM3BMmSjsdTSeppV9x2qIgrxsisdbSqF6IVhzI2occcte3hTA=="], + + "postcss-normalize-unicode": ["postcss-normalize-unicode@8.0.0", "", { "dependencies": { "browserslist": "4.28.2", "postcss-value-parser": "4.2.0" }, "peerDependencies": { "postcss": "8.5.15" } }, "sha512-+WYngZaChEeTHZmWhmKtnJ4gTzWdINEaFcgWBnu6WdVu8Ftim8OBTcw768DuCC/3Aax9bZ9WkwrLGHym2Lzf+A=="], + + "postcss-normalize-url": ["postcss-normalize-url@8.0.0", "", { "dependencies": { "postcss-value-parser": "4.2.0" }, "peerDependencies": { "postcss": "8.5.15" } }, "sha512-4Mz9hZHn/QIB+YtFqTXrDmE2193GYxGb3F8uMfLvMicaEXCCUlDIJ658gFFJbqEGl9FYzwPtRiuNgbwlO9kkBg=="], + + "postcss-normalize-whitespace": ["postcss-normalize-whitespace@8.0.0", "", { "dependencies": { "postcss-value-parser": "4.2.0" }, "peerDependencies": { "postcss": "8.5.15" } }, "sha512-V1f8tYnwIP5tscOXQFTKK8Y5EJ+R2GMpFJ6FjzwoKoQnhbqQy3IeSrDjJJb8JjVos8ut6Osi80Zybpayv/XjIQ=="], + + "postcss-ordered-values": ["postcss-ordered-values@8.0.0", "", { "dependencies": { "cssnano-utils": "6.0.0", "postcss-value-parser": "4.2.0" }, "peerDependencies": { "postcss": "8.5.15" } }, "sha512-Dg9+itb6lmD0bxqhQyHCtXAwYRh0wUrx6Mp4/BNXgkLoJmdYMmWi+V+Pypw79Q6iQhxA8KFMHqLBITQJV2gKMA=="], + + "postcss-reduce-initial": ["postcss-reduce-initial@8.0.0", "", { "dependencies": { "browserslist": "4.28.2", "caniuse-api": "3.0.0" }, "peerDependencies": { "postcss": "8.5.15" } }, "sha512-DChcE9d528AKrlpCTHjhsAiOsWCk4H9ApHPS1QqRT3praObWTiWyn6W1UddGpc46K9LQnHwUu4YwaPUukGtXVA=="], + + "postcss-reduce-transforms": ["postcss-reduce-transforms@8.0.0", "", { "dependencies": { "postcss-value-parser": "4.2.0" }, "peerDependencies": { "postcss": "8.5.15" } }, "sha512-cLZT0som7vvumQT9XQCnSKOSnRinNQZd1Hm+J723Ney13E8CIydDhw6JwzsjPtgnYThTqn9Q45906gz6wxaAsw=="], + + "postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "3.0.0", "util-deprecate": "1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], + + "postcss-svgo": ["postcss-svgo@8.0.0", "", { "dependencies": { "postcss-value-parser": "4.2.0", "svgo": "4.0.1" }, "peerDependencies": { "postcss": "8.5.15" } }, "sha512-Q2fMSYEiNE1ioDc/3sxvI24NdgA/MJno2XLNpOxgv8aCcJbym8mZY10/lDY5+AWCIc3Aiqzy2Wcp9/zaIXBZgQ=="], + + "postcss-unique-selectors": ["postcss-unique-selectors@8.0.0", "", { "dependencies": { "postcss-selector-parser": "7.1.1" }, "peerDependencies": { "postcss": "8.5.15" } }, "sha512-iObuolUX+ITJfMU2QQFQdh31JgSjNLPNjVs6YGAqBHvOvAWXMMNget6donQl83aQaeS32i5XeKZURUW/WBxIUw=="], + + "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], + + "powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="], + + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + + "pretty-bytes": ["pretty-bytes@7.1.0", "", {}, "sha512-nODzvTiYVRGRqAOvE84Vk5JDPyyxsVk0/fbA/bq7RqlnhksGpset09XTxbpvLTIjoaF7K8Z8DG8yHtKGTPSYRw=="], + + "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="], + + "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], + + "proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "4.2.11", "retry": "0.12.0", "signal-exit": "3.0.7" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="], + + "prosemirror-changeset": ["prosemirror-changeset@2.4.1", "", { "dependencies": { "prosemirror-transform": "1.12.0" } }, "sha512-96WBLhOaYhJ+kPhLg3uW359Tz6I/MfcrQfL4EGv4SrcqKEMC1gmoGrXHecPE8eOwTVCJ4IwgfzM8fFad25wNfw=="], + + "prosemirror-commands": ["prosemirror-commands@1.7.1", "", { "dependencies": { "prosemirror-model": "1.25.7", "prosemirror-state": "1.4.4", "prosemirror-transform": "1.12.0" } }, "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w=="], + + "prosemirror-dropcursor": ["prosemirror-dropcursor@1.8.2", "", { "dependencies": { "prosemirror-state": "1.4.4", "prosemirror-transform": "1.12.0", "prosemirror-view": "1.41.8" } }, "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw=="], + + "prosemirror-gapcursor": ["prosemirror-gapcursor@1.4.1", "", { "dependencies": { "prosemirror-keymap": "1.2.3", "prosemirror-model": "1.25.7", "prosemirror-state": "1.4.4", "prosemirror-view": "1.41.8" } }, "sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw=="], + + "prosemirror-history": ["prosemirror-history@1.5.0", "", { "dependencies": { "prosemirror-state": "1.4.4", "prosemirror-transform": "1.12.0", "prosemirror-view": "1.41.8", "rope-sequence": "1.3.4" } }, "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg=="], + + "prosemirror-inputrules": ["prosemirror-inputrules@1.5.1", "", { "dependencies": { "prosemirror-state": "1.4.4", "prosemirror-transform": "1.12.0" } }, "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw=="], + + "prosemirror-keymap": ["prosemirror-keymap@1.2.3", "", { "dependencies": { "prosemirror-state": "1.4.4", "w3c-keyname": "2.2.8" } }, "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw=="], + + "prosemirror-model": ["prosemirror-model@1.25.7", "", { "dependencies": { "orderedmap": "2.1.1" } }, "sha512-A79aN8QEFUwI6cax8Yq4Rpcx1TJZ3Kagn+ii7qLo4/V8H3mMiHrhFyhTyHHvpSnOgMPpWiDGSwM3etwrxE50ug=="], + + "prosemirror-schema-list": ["prosemirror-schema-list@1.5.1", "", { "dependencies": { "prosemirror-model": "1.25.7", "prosemirror-state": "1.4.4", "prosemirror-transform": "1.12.0" } }, "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q=="], + + "prosemirror-state": ["prosemirror-state@1.4.4", "", { "dependencies": { "prosemirror-model": "1.25.7", "prosemirror-transform": "1.12.0", "prosemirror-view": "1.41.8" } }, "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw=="], + + "prosemirror-tables": ["prosemirror-tables@1.8.5", "", { "dependencies": { "prosemirror-keymap": "1.2.3", "prosemirror-model": "1.25.7", "prosemirror-state": "1.4.4", "prosemirror-transform": "1.12.0", "prosemirror-view": "1.41.8" } }, "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw=="], + + "prosemirror-transform": ["prosemirror-transform@1.12.0", "", { "dependencies": { "prosemirror-model": "1.25.7" } }, "sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w=="], + + "prosemirror-view": ["prosemirror-view@1.41.8", "", { "dependencies": { "prosemirror-model": "1.25.7", "prosemirror-state": "1.4.4", "prosemirror-transform": "1.12.0" } }, "sha512-TnKDdohEatgyZNGCDWIdccOHXhYloJwbwU+phw/a23KBvJIR9lWQWW7WHHK3vBdOLDNuF7TaX98GObUZOWkOnA=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "radix3": ["radix3@1.1.2", "", {}, "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="], + + "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], + + "rc9": ["rc9@3.0.1", "", { "dependencies": { "defu": "6.1.7", "destr": "2.0.5" } }, "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ=="], + + "readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "3.0.0", "buffer": "6.0.3", "events": "3.3.0", "process": "0.11.10", "string_decoder": "1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="], + + "readdir-glob": ["readdir-glob@1.1.3", "", { "dependencies": { "minimatch": "5.1.9" } }, "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA=="], + + "readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], + + "redis-errors": ["redis-errors@1.2.0", "", {}, "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w=="], + + "redis-parser": ["redis-parser@3.0.0", "", { "dependencies": { "redis-errors": "1.2.0" } }, "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A=="], + + "refa": ["refa@0.12.1", "", { "dependencies": { "@eslint-community/regexpp": "4.12.2" } }, "sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g=="], + + "regexp-ast-analysis": ["regexp-ast-analysis@0.7.1", "", { "dependencies": { "@eslint-community/regexpp": "4.12.2", "refa": "0.12.1" } }, "sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A=="], + + "regexp-tree": ["regexp-tree@0.1.27", "", { "bin": { "regexp-tree": "bin/regexp-tree" } }, "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA=="], + + "regjsparser": ["regjsparser@0.13.1", "", { "dependencies": { "jsesc": "3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw=="], + + "reka-ui": ["reka-ui@2.9.10", "", { "dependencies": { "@floating-ui/dom": "1.7.6", "@floating-ui/vue": "1.1.11", "@internationalized/date": "3.12.1", "@internationalized/number": "3.6.6", "@tanstack/vue-virtual": "3.13.29", "@vueuse/core": "14.3.0", "@vueuse/shared": "14.3.0", "aria-hidden": "1.2.6", "defu": "6.1.7", "ohash": "2.0.11" }, "peerDependencies": { "vue": "3.5.35" } }, "sha512-yuvZVTp4fWH2G3qk+ze/x6YYlyc2Xl1d+eMUlIYrKqzTowBKteoDoN17fitURmqSUck3mc7JbcYgp49DnGu2EQ=="], + + "reserved-identifiers": ["reserved-identifiers@1.2.0", "", {}, "sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw=="], + + "resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "1.3.0", "is-core-module": "2.16.2", "path-parse": "1.0.7", "supports-preserve-symlinks-flag": "1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="], + + "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], + + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + + "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "rollup": ["rollup@4.60.4", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.4", "@rollup/rollup-android-arm64": "4.60.4", "@rollup/rollup-darwin-arm64": "4.60.4", "@rollup/rollup-darwin-x64": "4.60.4", "@rollup/rollup-freebsd-arm64": "4.60.4", "@rollup/rollup-freebsd-x64": "4.60.4", "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", "@rollup/rollup-linux-arm-musleabihf": "4.60.4", "@rollup/rollup-linux-arm64-gnu": "4.60.4", "@rollup/rollup-linux-arm64-musl": "4.60.4", "@rollup/rollup-linux-loong64-gnu": "4.60.4", "@rollup/rollup-linux-loong64-musl": "4.60.4", "@rollup/rollup-linux-ppc64-gnu": "4.60.4", "@rollup/rollup-linux-ppc64-musl": "4.60.4", "@rollup/rollup-linux-riscv64-gnu": "4.60.4", "@rollup/rollup-linux-riscv64-musl": "4.60.4", "@rollup/rollup-linux-s390x-gnu": "4.60.4", "@rollup/rollup-linux-x64-gnu": "4.60.4", "@rollup/rollup-linux-x64-musl": "4.60.4", "@rollup/rollup-openbsd-x64": "4.60.4", "@rollup/rollup-openharmony-arm64": "4.60.4", "@rollup/rollup-win32-arm64-msvc": "4.60.4", "@rollup/rollup-win32-ia32-msvc": "4.60.4", "@rollup/rollup-win32-x64-gnu": "4.60.4", "@rollup/rollup-win32-x64-msvc": "4.60.4", "fsevents": "2.3.3" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g=="], + + "rollup-plugin-visualizer": ["rollup-plugin-visualizer@7.0.1", "", { "dependencies": { "open": "11.0.0", "picomatch": "4.0.4", "source-map": "0.7.6", "yargs": "18.0.0" }, "optionalDependencies": { "rollup": "4.60.4" }, "bin": { "rollup-plugin-visualizer": "dist/bin/cli.js" } }, "sha512-UJUT4+1Ho4OcWmPYU3sYXgUqI8B8Ayfe06MX7y0qCJ1K8aGoKtR/NDd/2nZqM7ADkrzny+I99Ul7GgyoiVNAgg=="], + + "rope-sequence": ["rope-sequence@1.3.4", "", {}, "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ=="], + + "rou3": ["rou3@0.8.1", "", {}, "sha512-ePa+XGk00/3HuCqrEnK3LxJW7I0SdNg6EFzKUJG73hMAdDcOUC/i/aSz7LSDwLrGr33kal/rqOGydzwl6U7zBA=="], + + "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "1.2.3" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="], + + "scslre": ["scslre@0.3.0", "", { "dependencies": { "@eslint-community/regexpp": "4.12.2", "refa": "0.12.1", "regexp-ast-analysis": "0.7.1" } }, "sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ=="], + + "scule": ["scule@1.3.0", "", {}, "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g=="], + + "semver": ["semver@7.8.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ=="], + + "send": ["send@1.2.1", "", { "dependencies": { "debug": "4.4.3", "encodeurl": "2.0.0", "escape-html": "1.0.3", "etag": "1.8.1", "fresh": "2.0.0", "http-errors": "2.0.1", "mime-types": "3.0.2", "ms": "2.1.3", "on-finished": "2.4.1", "range-parser": "1.2.1", "statuses": "2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], + + "serialize-javascript": ["serialize-javascript@7.0.5", "", {}, "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw=="], + + "seroval": ["seroval@1.5.4", "", {}, "sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw=="], + + "serve-placeholder": ["serve-placeholder@2.0.2", "", { "dependencies": { "defu": "6.1.7" } }, "sha512-/TMG8SboeiQbZJWRlfTCqMs2DD3SZgWp0kDQePz9yUuCnDfDh/92gf7/PxGhzXTKBIPASIHxFcZndoNbp6QOLQ=="], + + "serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "2.0.0", "escape-html": "1.0.3", "parseurl": "1.3.3", "send": "1.2.1" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="], + + "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "simple-git": ["simple-git@3.36.0", "", { "dependencies": { "@kwsites/file-exists": "1.1.1", "@kwsites/promise-deferred": "1.1.1", "@simple-git/args-pathspec": "1.0.3", "@simple-git/argv-parser": "1.1.1", "debug": "4.4.3" } }, "sha512-cGQjLjK8bxJw4QuYT7gxHw3/IouVESbhahSsHrX97MzCL1gu2u7oy38W6L2ZIGECEfIBG4BabsWDPjBxJENv9Q=="], + + "sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "1.0.0-next.29", "mrmime": "2.0.1", "totalist": "3.0.1" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="], + + "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], + + "slash": ["slash@5.1.0", "", {}, "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg=="], + + "smob": ["smob@1.6.2", "", {}, "sha512-RQsvleCbF8cVHEv+xuDGaA4pOizFqJ0GgjtMSRo6oP8pnN7WsigHgVGey6aILRBKv4W2YOMHLqbKdnB6hpB9fw=="], + + "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "1.1.2", "source-map": "0.6.1" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + + "spdx-exceptions": ["spdx-exceptions@2.5.0", "", {}, "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="], + + "spdx-expression-parse": ["spdx-expression-parse@4.0.0", "", { "dependencies": { "spdx-exceptions": "2.5.0", "spdx-license-ids": "3.0.23" } }, "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ=="], + + "spdx-license-ids": ["spdx-license-ids@3.0.23", "", {}, "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw=="], + + "srvx": ["srvx@0.11.15", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-iXsux0UcOjdvs0LCMa2Ws3WwcDUozA3JN3BquNXkaFPP7TpRqgunKdEgoZ/uwb1J6xaYHfxtz9Twlh6yzwM6Tg=="], + + "stable-hash-x": ["stable-hash-x@0.2.0", "", {}, "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ=="], + + "standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="], + + "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], + + "std-env": ["std-env@4.1.0", "", {}, "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ=="], + + "streamx": ["streamx@2.25.0", "", { "dependencies": { "events-universal": "1.0.1", "fast-fifo": "1.3.2", "text-decoder": "1.2.7" } }, "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg=="], + + "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "10.6.0", "get-east-asian-width": "1.6.0", "strip-ansi": "7.2.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "8.0.0", "is-fullwidth-code-point": "3.0.0", "strip-ansi": "6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + + "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + + "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-final-newline": ["strip-final-newline@3.0.0", "", {}, "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw=="], + + "strip-indent": ["strip-indent@4.1.1", "", {}, "sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA=="], + + "strip-literal": ["strip-literal@3.1.0", "", { "dependencies": { "js-tokens": "9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="], + + "structured-clone-es": ["structured-clone-es@2.0.0", "", {}, "sha512-5UuAHmBLXYPCl22xWJrFuGmIhBKQzxISPVz6E7nmTmTcAOpUzlbjKJsRrCE4vADmMQ0dzeCnlWn9XufnAGf76Q=="], + + "stylehacks": ["stylehacks@8.0.0", "", { "dependencies": { "browserslist": "4.28.2", "postcss-selector-parser": "7.1.1" }, "peerDependencies": { "postcss": "8.5.15" } }, "sha512-sWyjaJvBqHoVKYPbQ8JRvrGSPaYWtWrJsU+fGVtwKB1GE1rRPu3rC7T6UCuXLoL00Dwb+tsHe2T904r8Vnsx8w=="], + + "supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], + + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], + + "svgo": ["svgo@4.0.1", "", { "dependencies": { "commander": "11.1.0", "css-select": "5.2.2", "css-tree": "3.2.1", "css-what": "6.2.2", "csso": "5.0.5", "picocolors": "1.1.1", "sax": "1.6.0" }, "bin": "./bin/svgo.js" }, "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w=="], + + "tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="], + + "tailwind-merge": ["tailwind-merge@3.6.0", "", {}, "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w=="], + + "tailwind-variants": ["tailwind-variants@3.2.2", "", { "optionalDependencies": { "tailwind-merge": "3.6.0" }, "peerDependencies": { "tailwindcss": "4.3.1" } }, "sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg=="], + + "tailwindcss": ["tailwindcss@4.3.2", "", {}, "sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA=="], + + "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], + + "tar": ["tar@7.5.15", "", { "dependencies": { "@isaacs/fs-minipass": "4.0.1", "chownr": "3.0.0", "minipass": "7.1.3", "minizlib": "3.1.0", "yallist": "5.0.0" } }, "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ=="], + + "tar-stream": ["tar-stream@3.2.0", "", { "dependencies": { "b4a": "1.8.1", "bare-fs": "4.7.1", "fast-fifo": "1.3.2", "streamx": "2.25.0" } }, "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg=="], + + "teex": ["teex@1.0.1", "", { "dependencies": { "streamx": "2.25.0" } }, "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg=="], + + "terser": ["terser@5.47.1", "", { "dependencies": { "@jridgewell/source-map": "0.3.11", "acorn": "8.16.0", "commander": "2.20.3", "source-map-support": "0.5.21" }, "bin": { "terser": "bin/terser" } }, "sha512-tPbLXTI6ohPASb/1YViL428oEHu6/qv1OxqYnfaonVCFHqx4+wCd95pHrQWsL5X4pl90CTyW9piSAsS2L0VoMw=="], + + "text-decoder": ["text-decoder@1.2.7", "", { "dependencies": { "b4a": "1.8.1" } }, "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ=="], + + "tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="], + + "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], + + "tinyclip": ["tinyclip@0.1.12", "", {}, "sha512-Ae3OVUqifDw0wBriIBS7yVaW44Dp6eSHQcyq4Igc7eN2TJH/2YsicswaW+J/OuMvhpDPOKEgpAZCjkb4hpoyeA=="], + + "tinyexec": ["tinyexec@1.1.2", "", {}, "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA=="], + + "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "6.5.0", "picomatch": "4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "to-valid-identifier": ["to-valid-identifier@1.0.0", "", { "dependencies": { "@sindresorhus/base62": "1.0.0", "reserved-identifiers": "1.2.0" } }, "sha512-41wJyvKep3yT2tyPqX/4blcfybknGB4D+oETKLs7Q76UiPqRpUJK3hr1nxelyYO0PHKVzJwlu0aCeEAsGI6rpw=="], + + "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], + + "totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="], + + "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + + "ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": "6.0.3" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + + "type-fest": ["type-fest@5.6.0", "", { "dependencies": { "tagged-tag": "1.0.0" } }, "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA=="], + + "type-level-regexp": ["type-level-regexp@0.1.17", "", {}, "sha512-wTk4DH3cxwk196uGLK/E9pE45aLfeKJacKmcEgEOA/q5dnPGNxXt0cfYdFxb57L+sEpf1oJH4Dnx/pnRcku9jg=="], + + "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], + + "ufo": ["ufo@1.6.4", "", {}, "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA=="], + + "ultrahtml": ["ultrahtml@1.6.0", "", {}, "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw=="], + + "uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="], + + "unctx": ["unctx@2.5.0", "", { "dependencies": { "acorn": "8.16.0", "estree-walker": "3.0.3", "magic-string": "0.30.21", "unplugin": "2.3.11" } }, "sha512-p+Rz9x0R7X+CYDkT+Xg8/GhpcShTlU8n+cf9OtOEf7zEQsNcCZO1dPKNRDqvUTaq+P32PMMkxWHwfrxkqfqAYg=="], + + "unenv": ["unenv@2.0.0-rc.24", "", { "dependencies": { "pathe": "2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="], + + "unhead": ["unhead@2.1.15", "", { "dependencies": { "hookable": "6.1.1" } }, "sha512-MCt5T90mCWyr3Z6pUCdM9lVRXoMoVBlL7z7U4CYVIiaDiuzad/UCfLuMqz5MeNmpZUgoBCQnrucJimU7EZR+XA=="], + + "unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], + + "unifont": ["unifont@0.7.4", "", { "dependencies": { "css-tree": "3.2.1", "ofetch": "1.5.1", "ohash": "2.0.11" } }, "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg=="], + + "unimport": ["unimport@6.3.0", "", { "dependencies": { "acorn": "8.16.0", "escape-string-regexp": "5.0.0", "estree-walker": "3.0.3", "local-pkg": "1.2.1", "magic-string": "0.30.21", "mlly": "1.8.2", "pathe": "2.0.3", "picomatch": "4.0.4", "pkg-types": "2.3.1", "scule": "1.3.0", "strip-literal": "3.1.0", "tinyglobby": "0.2.17", "unplugin": "3.0.0", "unplugin-utils": "0.3.1" }, "optionalDependencies": { "oxc-parser": "0.133.0" } }, "sha512-M+Dxk5W9WRd+8j56W9tp8lGW/dmMc7g5zj7BWQnEjKQhryBstqsi1V0izb0zHwSkEN8cSYV7K75/bykairV2tA=="], + + "unplugin": ["unplugin@3.0.0", "", { "dependencies": { "@jridgewell/remapping": "2.3.5", "picomatch": "4.0.4", "webpack-virtual-modules": "0.6.2" } }, "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg=="], + + "unplugin-auto-import": ["unplugin-auto-import@21.0.0", "", { "dependencies": { "local-pkg": "1.2.1", "magic-string": "0.30.21", "picomatch": "4.0.4", "unimport": "5.7.0", "unplugin": "2.3.11", "unplugin-utils": "0.3.1" }, "optionalDependencies": { "@nuxt/kit": "4.4.8", "@vueuse/core": "14.3.0" } }, "sha512-vWuC8SwqJmxZFYwPojhOhOXDb5xFhNNcEVb9K/RFkyk/3VnfaOjzitWN7v+8DEKpMjSsY2AEGXNgt6I0yQrhRQ=="], + + "unplugin-utils": ["unplugin-utils@0.3.1", "", { "dependencies": { "pathe": "2.0.3", "picomatch": "4.0.4" } }, "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog=="], + + "unplugin-vue-components": ["unplugin-vue-components@32.1.0", "", { "dependencies": { "chokidar": "5.0.0", "local-pkg": "1.2.1", "magic-string": "0.30.21", "mlly": "1.8.2", "obug": "2.1.1", "picomatch": "4.0.4", "tinyglobby": "0.2.17", "unplugin": "3.0.0", "unplugin-utils": "0.3.1" }, "optionalDependencies": { "@nuxt/kit": "4.4.8" }, "peerDependencies": { "vue": "3.5.35" } }, "sha512-YiUkSxuRjab18XFOrX5VsIxXzccrfmHVGsGeJgSgklb829DQmCy9E4vvDUE4tuvZZdxyFJZX0Oc4TPnnxiiMyg=="], + + "unrouting": ["unrouting@0.1.7", "", { "dependencies": { "escape-string-regexp": "5.0.0", "ufo": "1.6.4" } }, "sha512-+0hfD+CVWtD636rc5Fn9VEjjTEDhdqgMpbwAuVoUmydSHDaMNiFW93SJG4LV++RoGSEAyvQN5uABAscYpDphpQ=="], + + "unrs-resolver": ["unrs-resolver@1.12.2", "", { "dependencies": { "napi-postinstall": "0.3.4" }, "optionalDependencies": { "@unrs/resolver-binding-android-arm-eabi": "1.12.2", "@unrs/resolver-binding-android-arm64": "1.12.2", "@unrs/resolver-binding-darwin-arm64": "1.12.2", "@unrs/resolver-binding-darwin-x64": "1.12.2", "@unrs/resolver-binding-freebsd-x64": "1.12.2", "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2", "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2", "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2", "@unrs/resolver-binding-linux-arm64-musl": "1.12.2", "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2", "@unrs/resolver-binding-linux-loong64-musl": "1.12.2", "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2", "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2", "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2", "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2", "@unrs/resolver-binding-linux-x64-gnu": "1.12.2", "@unrs/resolver-binding-linux-x64-musl": "1.12.2", "@unrs/resolver-binding-openharmony-arm64": "1.12.2", "@unrs/resolver-binding-wasm32-wasi": "1.12.2", "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2", "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2", "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" } }, "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ=="], + + "unstorage": ["unstorage@1.17.5", "", { "dependencies": { "anymatch": "3.1.3", "chokidar": "5.0.0", "destr": "2.0.5", "h3": "1.15.11", "lru-cache": "11.4.0", "node-fetch-native": "1.6.7", "ofetch": "1.5.1", "ufo": "1.6.4" }, "optionalDependencies": { "db0": "0.3.4", "ioredis": "5.10.1" } }, "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg=="], + + "untun": ["untun@0.1.3", "", { "dependencies": { "citty": "0.1.6", "consola": "3.4.2", "pathe": "1.1.2" }, "bin": { "untun": "bin/untun.mjs" } }, "sha512-4luGP9LMYszMRZwsvyUd9MrxgEGZdZuZgpVQHEEX0lCYFESasVRvZd0EYpCkOIbJKHMuv0LskpXc/8Un+MJzEQ=="], + + "untyped": ["untyped@2.0.0", "", { "dependencies": { "citty": "0.1.6", "defu": "6.1.7", "jiti": "2.7.0", "knitwork": "1.3.0", "scule": "1.3.0" }, "bin": { "untyped": "dist/cli.mjs" } }, "sha512-nwNCjxJTjNuLCgFr42fEak5OcLuB3ecca+9ksPFNvtfYSLpjf+iJqSIaSnIile6ZPbKYxI5k2AfXqeopGudK/g=="], + + "unwasm": ["unwasm@0.5.3", "", { "dependencies": { "exsolve": "1.0.8", "knitwork": "1.3.0", "magic-string": "0.30.21", "mlly": "1.8.2", "pathe": "2.0.3", "pkg-types": "2.3.1" } }, "sha512-keBgTSfp3r6+s9ZcSma+0chwxQdmLbB5+dAD9vjtB21UTMYuKAxHXCU1K2CbCtnP09EaWeRvACnXk0EJtUx+hw=="], + + "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "3.2.0", "picocolors": "1.1.1" }, "peerDependencies": { "browserslist": "4.28.2" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + + "uqr": ["uqr@0.1.3", "", {}, "sha512-0rjE8iEJe4YmT9TOhwsZtqCMRLc5DXZUI2UEYUUg63ikBkqqE5EYWaI0etFe/5KUcmcYwLih2RND1kq+hrUJXA=="], + + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "2.3.1" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "valibot": ["valibot@1.4.1", "", { "optionalDependencies": { "typescript": "6.0.3" } }, "sha512-klCmFTz2jeDluy9RwX+F884TCiogtdBJ/YaxSx1EOBYXa3NXNWj8kR1jjN8rzluwojJVWWaHJ4r1U5LfICnM3g=="], + + "vaul-vue": ["vaul-vue@0.4.1", "", { "dependencies": { "@vueuse/core": "10.11.1" }, "peerDependencies": { "reka-ui": "2.9.10", "vue": "3.5.35" } }, "sha512-A6jOWOZX5yvyo1qMn7IveoWN91mJI5L3BUKsIwkg6qrTGgHs1Sb1JF/vyLJgnbN1rH4OOOxFbtqL9A46bOyGUQ=="], + + "vite": ["vite@7.3.3", "", { "dependencies": { "esbuild": "0.27.7", "fdir": "6.5.0", "picomatch": "4.0.4", "postcss": "8.5.15", "rollup": "4.60.4", "tinyglobby": "0.2.17" }, "optionalDependencies": { "fsevents": "2.3.3", "jiti": "2.7.0", "lightningcss": "1.32.0", "terser": "5.47.1", "yaml": "2.9.0" }, "bin": { "vite": "bin/vite.js" } }, "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA=="], + + "vite-dev-rpc": ["vite-dev-rpc@1.1.0", "", { "dependencies": { "birpc": "2.9.0", "vite-hot-client": "2.2.0" }, "peerDependencies": { "vite": "7.3.3" } }, "sha512-pKXZlgoXGoE8sEKiKJSng4hI1sQ4wi5YT24FCrwrLt6opmkjlqPPVmiPWWJn8M8byMxRGzp1CrFuqQs4M/Z39A=="], + + "vite-hot-client": ["vite-hot-client@2.2.0", "", { "peerDependencies": { "vite": "7.3.3" } }, "sha512-76Zs9zrHbH7M7wqeyooGQKdX+yg0pQ0xuQ1PbFp4z5a0Lzn2e5IPFoCswnmqZ4GiwqB4Jo3WcDAMO9jARTJl8w=="], + + "vite-node": ["vite-node@5.3.0", "", { "dependencies": { "cac": "6.7.14", "es-module-lexer": "2.1.0", "obug": "2.1.1", "pathe": "2.0.3", "vite": "7.3.3" }, "bin": { "vite-node": "dist/cli.mjs" } }, "sha512-8f20COPYJujc3OKPX6OuyBy3ZIv2det4eRRU4GY1y2MjbeGSUmPjedxg1b72KnTagCofwvZ65ThzjxDW2AtQFQ=="], + + "vite-plugin-checker": ["vite-plugin-checker@0.14.1", "", { "dependencies": { "@babel/code-frame": "7.29.0", "chokidar": "5.0.0", "npm-run-path": "6.0.0", "picocolors": "1.1.1", "picomatch": "4.0.4", "proper-lockfile": "4.1.2", "tiny-invariant": "1.3.3" }, "optionalDependencies": { "eslint": "10.6.0", "optionator": "0.9.4", "typescript": "6.0.3", "vue-tsc": "3.3.5" }, "peerDependencies": { "vite": "7.3.3" } }, "sha512-Mv8oQc9XYBYf+XkP/riqqQCt8lBP6Iad75PZPho1lHRrpxQI0BwX2gwE10enn4f6Hgc+PvR1F7N38KARcaJtzw=="], + + "vite-plugin-inspect": ["vite-plugin-inspect@11.3.3", "", { "dependencies": { "ansis": "4.3.0", "debug": "4.4.3", "error-stack-parser-es": "1.0.5", "ohash": "2.0.11", "open": "10.2.0", "perfect-debounce": "2.1.0", "sirv": "3.0.2", "unplugin-utils": "0.3.1", "vite-dev-rpc": "1.1.0" }, "optionalDependencies": { "@nuxt/kit": "4.4.8" }, "peerDependencies": { "vite": "7.3.3" } }, "sha512-u2eV5La99oHoYPHE6UvbwgEqKKOQGz86wMg40CCosP6q8BkB6e5xPneZfYagK4ojPJSj5anHCrnvC20DpwVdRA=="], + + "vite-plugin-vue-tracer": ["vite-plugin-vue-tracer@1.4.0", "", { "dependencies": { "estree-walker": "3.0.3", "exsolve": "1.0.8", "magic-string": "0.30.21", "pathe": "2.0.3", "source-map-js": "1.2.1" }, "peerDependencies": { "vite": "7.3.3", "vue": "3.5.35" } }, "sha512-0tQCjCqZWVSK6UeRW9S4ABbf47lKQ68zvrT2FNvZmiL+alDydCVyH/T3Jlfbdc3T3C2Iuyyl5aVsMbF8IQIoxA=="], + + "vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="], + + "vue": ["vue@3.5.35", "", { "dependencies": { "@vue/compiler-dom": "3.5.35", "@vue/compiler-sfc": "3.5.35", "@vue/runtime-dom": "3.5.35", "@vue/server-renderer": "3.5.35", "@vue/shared": "3.5.35" }, "optionalDependencies": { "typescript": "6.0.3" } }, "sha512-cx89fnr+0kVGHiNFG6y6s0bdjypJRFNZn6x3WPstNdQR1bi1mbB7h4v5IBGTsPJU3nK1+0Iqj3Zf+hZWMieR4Q=="], + + "vue-bundle-renderer": ["vue-bundle-renderer@2.2.0", "", { "dependencies": { "ufo": "1.6.4" } }, "sha512-sz/0WEdYH1KfaOm0XaBmRZOWgYTEvUDt6yPYaUzl4E52qzgWLlknaPPTTZmp6benaPTlQAI/hN1x3tAzZygycg=="], + + "vue-component-type-helpers": ["vue-component-type-helpers@3.3.5", "", {}, "sha512-Fe1jyPJoUGpJOYKOri44jduR7My4yYINOMJISuMAbmrs+L5LbIDUc8NTWZYY3EJLK0yPLuCmcd5zoCsE4k2/KA=="], + + "vue-demi": ["vue-demi@0.14.10", "", { "peerDependencies": { "vue": "3.5.35" }, "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", "vue-demi-switch": "bin/vue-demi-switch.js" } }, "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg=="], + + "vue-devtools-stub": ["vue-devtools-stub@0.1.0", "", {}, "sha512-RutnB7X8c5hjq39NceArgXg28WZtZpGc3+J16ljMiYnFhKvd8hITxSWQSQ5bvldxMDU6gG5mkxl1MTQLXckVSQ=="], + + "vue-eslint-parser": ["vue-eslint-parser@10.4.1", "", { "dependencies": { "debug": "4.4.3", "eslint-scope": "9.1.2", "eslint-visitor-keys": "5.0.1", "espree": "11.2.0", "esquery": "1.7.0", "semver": "7.8.2" }, "peerDependencies": { "eslint": "10.6.0" } }, "sha512-Gk6gRDj0n/fkRa3C3l0bBheoBckUq/Rs0F/TvMWIS6nzzx67amAViMe9CkNgsP2tXyQONvGiHQESHwFtZ3aYDA=="], + + "vue-router": ["vue-router@5.1.0", "", { "dependencies": { "@babel/generator": "8.0.0-rc.5", "@vue-macros/common": "3.1.2", "@vue/devtools-api": "8.1.2", "ast-walker-scope": "0.9.0", "chokidar": "5.0.0", "json5": "2.2.3", "local-pkg": "1.2.1", "magic-string": "0.30.21", "mlly": "1.8.2", "muggle-string": "0.4.1", "pathe": "2.0.3", "picomatch": "4.0.4", "scule": "1.3.0", "tinyglobby": "0.2.17", "unplugin": "3.0.0", "unplugin-utils": "0.3.1", "yaml": "2.9.0" }, "optionalDependencies": { "@vue/compiler-sfc": "3.5.35", "vite": "7.3.3" }, "peerDependencies": { "vue": "3.5.35" } }, "sha512-HAbiLzLEHQwxPgvsbOJDAwtavszEgLwri6XfyrsPECIFez8+59xc9LofWVdc/HEaSRT822lJ8H9Ns38VVond5g=="], + + "vue-tsc": ["vue-tsc@3.3.5", "", { "dependencies": { "@volar/typescript": "2.4.28", "@vue/language-core": "3.3.5" }, "peerDependencies": { "typescript": "6.0.3" }, "bin": { "vue-tsc": "bin/vue-tsc.js" } }, "sha512-Rzh/G2MmNlMSAMTiQEjDrsb4dgB/jbtEM47rVN2NtidF1dfb/q4w4QvpQBtW5+y3y5H27Hjh7deVwk+YB02fNg=="], + + "w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="], + + "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + + "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], + + "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "0.0.3", "webidl-conversions": "3.0.1" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], + + "wheel-gestures": ["wheel-gestures@2.2.48", "", {}, "sha512-f+Gy33Oa5Z14XY9679Zze+7VFhbsQfBFXodnU2x589l4kxGM9L5Y8zETTmcMR5pWOPQyRv4Z0lNax6xCO0NSlA=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + + "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "6.2.3", "string-width": "7.2.0", "strip-ansi": "7.2.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], + + "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "4.3.0", "string-width": "4.2.3", "strip-ansi": "6.0.1" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "ws": ["ws@8.21.0", "", {}, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], + + "wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "3.1.1" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="], + + "xml-name-validator": ["xml-name-validator@4.0.0", "", {}, "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw=="], + + "y-protocols": ["y-protocols@1.0.7", "", { "dependencies": { "lib0": "0.2.117" }, "peerDependencies": { "yjs": "13.6.30" } }, "sha512-YSVsLoXxO67J6eE/nV4AtFtT3QEotZf5sK5BHxFBXso7VDUT3Tx07IfA6hsu5Q5OmBdMkQVmFZ9QOA7fikWvnw=="], + + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], + + "yargs": ["yargs@18.0.0", "", { "dependencies": { "cliui": "9.0.1", "escalade": "3.2.0", "get-caller-file": "2.0.5", "string-width": "7.2.0", "y18n": "5.0.8", "yargs-parser": "22.0.0" } }, "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg=="], + + "yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="], + + "yjs": ["yjs@13.6.30", "", { "dependencies": { "lib0": "0.2.117" } }, "sha512-vv/9h42eCMC81ZHDFswuu/MKzkl/vyq1BhaNGfHyOonwlG4CJbQF4oiBBJPvfdeCt/PlVDWh7Nov9D34YY09uQ=="], + + "yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="], + + "youch": ["youch@4.1.1", "", { "dependencies": { "@poppinss/colors": "4.1.6", "@poppinss/dumper": "0.7.0", "@speed-highlight/core": "1.2.15", "cookie-es": "3.1.1", "youch-core": "0.3.3" } }, "sha512-mxW3qiSnl+GRxXsaUMzv2Mbada1Y8CDltET9UxejDQe6DBYlSekghl5U5K0ReAikcHDi0G1vKZEmmo/NWAGKLA=="], + + "youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "1.2.3", "error-stack-parser-es": "1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="], + + "zip-stream": ["zip-stream@6.0.1", "", { "dependencies": { "archiver-utils": "5.0.2", "compress-commons": "6.0.2", "readable-stream": "4.7.0" } }, "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA=="], + + "@babel/code-frame/js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "@babel/core/@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "7.29.3", "@babel/types": "7.29.0", "@jridgewell/gen-mapping": "0.3.13", "@jridgewell/trace-mapping": "0.3.31", "jsesc": "3.1.0" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], + + "@babel/core/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "7.27.1", "@babel/helper-validator-identifier": "7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/generator/@babel/parser": ["@babel/parser@8.0.0-rc.5", "", { "dependencies": { "@babel/types": "8.0.0-rc.5" }, "bin": "./bin/babel-parser.js" }, "sha512-/Mfg83rK3+jsRbl4Vbd0jqxc6M1A1/WNFtgrowRM1unEsD3XcNnrBdMM0JWakd0/RN9lseQKwPduW1TiEwKOlQ=="], + + "@babel/helper-annotate-as-pure/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "7.27.1", "@babel/helper-validator-identifier": "7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "3.1.1" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/helper-member-expression-to-functions/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "7.27.1", "@babel/helper-validator-identifier": "7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@babel/helper-module-imports/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "7.27.1", "@babel/helper-validator-identifier": "7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@babel/helper-optimise-call-expression/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "7.27.1", "@babel/helper-validator-identifier": "7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@babel/helper-skip-transparent-expression-wrappers/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "7.27.1", "@babel/helper-validator-identifier": "7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@babel/helpers/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "7.27.1", "@babel/helper-validator-identifier": "7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@babel/parser/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "7.27.1", "@babel/helper-validator-identifier": "7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@babel/template/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "7.27.1", "@babel/helper-validator-identifier": "7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@babel/traverse/@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "7.29.3", "@babel/types": "7.29.0", "@jridgewell/gen-mapping": "0.3.13", "@jridgewell/trace-mapping": "0.3.31", "jsesc": "3.1.0" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], + + "@babel/traverse/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "7.27.1", "@babel/helper-validator-identifier": "7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0-rc.5", "", {}, "sha512-ehJDxHvtbZ85RtX/L2fi0h9AGsBNqB5Euv1EB8RMAvGYvD+2X+QbpzzOpbklnNXO+WSZJNOaetw2BBj27xsWVg=="], + + "@bomb.sh/tab/cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "0.2.0", "emoji-regex": "9.2.2", "strip-ansi": "7.2.0" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "6.2.3", "string-width": "5.1.2", "strip-ansi": "7.2.0" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + + "@nuxt/devtools/which": ["which@6.0.1", "", { "dependencies": { "isexe": "4.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="], + + "@nuxt/kit/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + + "@nuxt/nitro-server/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + + "@nuxt/telemetry/ofetch": ["ofetch@2.0.0-alpha.3", "", {}, "sha512-zpYTCs2byOuft65vI3z43Dd6iSdFbOZZLb9/d21aCpx2rGastVU9dOCv0lu4ykc1Ur1anAYjDi3SUvR0vq50JA=="], + + "@nuxt/vite-builder/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + + "@rollup/plugin-commonjs/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + + "@rollup/plugin-inject/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + + "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + + "@stylistic/eslint-plugin/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], + + "@stylistic/eslint-plugin/espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "8.16.0", "acorn-jsx": "5.3.2", "eslint-visitor-keys": "4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], + + "@tailwindcss/node/tailwindcss": ["tailwindcss@4.3.1", "", {}, "sha512-hk+TB1m+K8CYNrP6rjQaq/Y+4Zylwpa87mLYBKCunwnnQ9p+fHb7kmSfGqyEJoxF/O6CDyABWVFEafNSYKll+Q=="], + + "@tailwindcss/postcss/tailwindcss": ["tailwindcss@4.3.1", "", {}, "sha512-hk+TB1m+K8CYNrP6rjQaq/Y+4Zylwpa87mLYBKCunwnnQ9p+fHb7kmSfGqyEJoxF/O6CDyABWVFEafNSYKll+Q=="], + + "@tailwindcss/vite/tailwindcss": ["tailwindcss@4.3.1", "", {}, "sha512-hk+TB1m+K8CYNrP6rjQaq/Y+4Zylwpa87mLYBKCunwnnQ9p+fHb7kmSfGqyEJoxF/O6CDyABWVFEafNSYKll+Q=="], + + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + + "@vercel/nft/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + + "@vue/babel-plugin-jsx/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "7.27.1", "@babel/helper-validator-identifier": "7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + + "@vue/compiler-sfc/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + + "@vue/devtools-kit/birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="], + + "@vue/devtools-kit/hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], + + "anymatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + + "archiver-utils/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "3.3.1", "jackspeak": "3.4.3", "minimatch": "9.0.9", "minipass": "7.1.3", "package-json-from-dist": "1.0.1", "path-scurry": "1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], + + "archiver-utils/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + + "ast-walker-scope/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "7.27.1", "@babel/helper-validator-identifier": "7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "compress-commons/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + + "csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "1.2.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="], + + "devframe/h3": ["h3@2.0.1-rc.22", "", { "dependencies": { "rou3": "0.8.1", "srvx": "0.11.15" }, "optionalDependencies": { "crossws": "0.4.5" }, "bin": { "h3": "bin/h3.mjs" } }, "sha512-Esv0DMIuPkCTSWCA0vO73vcTqwzH1wjSrAO1TXNu/K3up1sZHa9EKMapbmxCDYBeymC3fVTk4qxp7ogQWQ+KgA=="], + + "dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + + "eslint/find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "6.0.0", "path-exists": "4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "eslint-flat-config-utils/@eslint/config-helpers": ["@eslint/config-helpers@0.5.5", "", { "dependencies": { "@eslint/core": "1.2.1" } }, "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w=="], + + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "4.0.3" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "fontaine/unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "2.3.5", "acorn": "8.16.0", "picomatch": "4.0.4", "webpack-virtual-modules": "0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="], + + "globby/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + + "globby/unicorn-magic": ["unicorn-magic@0.4.0", "", {}, "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw=="], + + "h3/cookie-es": ["cookie-es@1.2.3", "", {}, "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw=="], + + "lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "1.0.3", "inherits": "2.0.4", "isarray": "1.0.0", "process-nextick-args": "2.0.1", "safe-buffer": "5.1.2", "string_decoder": "1.1.1", "util-deprecate": "1.0.2" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + + "listhen/crossws": ["crossws@0.4.5", "", { "optionalDependencies": { "srvx": "0.11.15" } }, "sha512-wUR89x/Rw7/8t+vn0CmGDYM9TD6VtARGb0LD5jq2wjtMy1vCP4M+sm6N6TigWeTYvnA8MoW29NqqXD0ep0rfBA=="], + + "magic-regexp/unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "2.3.5", "acorn": "8.16.0", "picomatch": "4.0.4", "webpack-virtual-modules": "0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="], + + "magicast/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "7.27.1", "@babel/helper-validator-identifier": "7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + + "mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "0.1.8", "mlly": "1.8.2", "pathe": "2.0.3" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + + "nitropack/cookie-es": ["cookie-es@2.0.1", "", {}, "sha512-aVf4A4hI2w70LnF7GG+7xDQUkliwiXWXFvTjkip4+b64ygDQ2sJPRSKFDHbxn8o0xu9QzPkMuuiWIXyFSE2slA=="], + + "nitropack/esbuild": ["esbuild@0.28.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.0", "@esbuild/android-arm": "0.28.0", "@esbuild/android-arm64": "0.28.0", "@esbuild/android-x64": "0.28.0", "@esbuild/darwin-arm64": "0.28.0", "@esbuild/darwin-x64": "0.28.0", "@esbuild/freebsd-arm64": "0.28.0", "@esbuild/freebsd-x64": "0.28.0", "@esbuild/linux-arm": "0.28.0", "@esbuild/linux-arm64": "0.28.0", "@esbuild/linux-ia32": "0.28.0", "@esbuild/linux-loong64": "0.28.0", "@esbuild/linux-mips64el": "0.28.0", "@esbuild/linux-ppc64": "0.28.0", "@esbuild/linux-riscv64": "0.28.0", "@esbuild/linux-s390x": "0.28.0", "@esbuild/linux-x64": "0.28.0", "@esbuild/netbsd-arm64": "0.28.0", "@esbuild/netbsd-x64": "0.28.0", "@esbuild/openbsd-arm64": "0.28.0", "@esbuild/openbsd-x64": "0.28.0", "@esbuild/openharmony-arm64": "0.28.0", "@esbuild/sunos-x64": "0.28.0", "@esbuild/win32-arm64": "0.28.0", "@esbuild/win32-ia32": "0.28.0", "@esbuild/win32-x64": "0.28.0" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw=="], + + "nitropack/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + + "nitropack/hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], + + "nostics/oxc-parser": ["oxc-parser@0.132.0", "", { "dependencies": { "@oxc-project/types": "0.132.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.132.0", "@oxc-parser/binding-android-arm64": "0.132.0", "@oxc-parser/binding-darwin-arm64": "0.132.0", "@oxc-parser/binding-darwin-x64": "0.132.0", "@oxc-parser/binding-freebsd-x64": "0.132.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.132.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.132.0", "@oxc-parser/binding-linux-arm64-gnu": "0.132.0", "@oxc-parser/binding-linux-arm64-musl": "0.132.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.132.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.132.0", "@oxc-parser/binding-linux-riscv64-musl": "0.132.0", "@oxc-parser/binding-linux-s390x-gnu": "0.132.0", "@oxc-parser/binding-linux-x64-gnu": "0.132.0", "@oxc-parser/binding-linux-x64-musl": "0.132.0", "@oxc-parser/binding-openharmony-arm64": "0.132.0", "@oxc-parser/binding-wasm32-wasi": "0.132.0", "@oxc-parser/binding-win32-arm64-msvc": "0.132.0", "@oxc-parser/binding-win32-ia32-msvc": "0.132.0", "@oxc-parser/binding-win32-x64-msvc": "0.132.0" } }, "sha512-+0LAPHaqtfQlvWdpaAa09SmOaZZgP8C552xosEkGJ4+ruEwP1Vgx+sqBgcBCNfR6KDCmagGOZTde8wmAvcI/Hg=="], + + "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], + + "nuxt/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + + "nuxt/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + + "oxc-walker/magic-regexp": ["magic-regexp@0.11.0", "", { "dependencies": { "magic-string": "0.30.21", "regexp-tree": "0.1.27", "type-level-regexp": "0.1.17", "unplugin": "3.0.0" } }, "sha512-LG77Z/gVnwz7oaDpD4heX6ryl+lcr4l1B2gnP4MMvt2pGhGC1Dfj7dl1pXpP4ih+VQFLuAadeKVa+lARAzfW+Q=="], + + "proper-lockfile/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "readdir-glob/minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "2.1.0" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="], + + "rollup/@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "rollup-plugin-visualizer/open": ["open@11.0.0", "", { "dependencies": { "default-browser": "5.5.0", "define-lazy-prop": "3.0.0", "is-in-ssh": "1.0.0", "is-inside-container": "1.0.0", "powershell-utils": "0.1.0", "wsl-utils": "0.3.1" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="], + + "source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "svgo/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], + + "tar/yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + + "unctx/unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "2.3.5", "acorn": "8.16.0", "picomatch": "4.0.4", "webpack-virtual-modules": "0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="], + + "unimport/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + + "unplugin-auto-import/unimport": ["unimport@5.7.0", "", { "dependencies": { "acorn": "8.16.0", "escape-string-regexp": "5.0.0", "estree-walker": "3.0.3", "local-pkg": "1.2.1", "magic-string": "0.30.21", "mlly": "1.8.2", "pathe": "2.0.3", "picomatch": "4.0.4", "pkg-types": "2.3.1", "scule": "1.3.0", "strip-literal": "3.1.0", "tinyglobby": "0.2.17", "unplugin": "2.3.11", "unplugin-utils": "0.3.1" } }, "sha512-njnL6sp8lEA8QQbZrt+52p/g4X0rw3bnGGmUcJnt1jeG8+iiqO779aGz0PirCtydAIVcuTBRlJ52F0u46z309Q=="], + + "unplugin-auto-import/unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "2.3.5", "acorn": "8.16.0", "picomatch": "4.0.4", "webpack-virtual-modules": "0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="], + + "unrouting/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + + "untun/citty": ["citty@0.1.6", "", { "dependencies": { "consola": "3.4.2" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="], + + "untun/pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="], + + "untyped/citty": ["citty@0.1.6", "", { "dependencies": { "consola": "3.4.2" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="], + + "vaul-vue/@vueuse/core": ["@vueuse/core@10.11.1", "", { "dependencies": { "@types/web-bluetooth": "0.0.20", "@vueuse/metadata": "10.11.1", "@vueuse/shared": "10.11.1", "vue-demi": "0.14.10" } }, "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww=="], + + "vite-dev-rpc/birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="], + + "vite-node/cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + + "vite-plugin-checker/npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "4.0.0", "unicorn-magic": "0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="], + + "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "8.0.0", "is-fullwidth-code-point": "3.0.0", "strip-ansi": "6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@babel/core/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-annotate-as-pure/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-member-expression-to-functions/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-module-imports/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-optimise-call-expression/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-skip-transparent-expression-wrappers/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helpers/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/template/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/traverse/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + + "@nuxt/devtools/which/isexe": ["isexe@4.0.0", "", {}, "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="], + + "@vue/babel-plugin-jsx/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "archiver-utils/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "2.1.0" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], + + "archiver-utils/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "10.4.3", "minipass": "7.1.3" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + + "ast-walker-scope/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="], + + "devframe/h3/crossws": ["crossws@0.4.5", "", { "optionalDependencies": { "srvx": "0.11.15" } }, "sha512-wUR89x/Rw7/8t+vn0CmGDYM9TD6VtARGb0LD5jq2wjtMy1vCP4M+sm6N6TigWeTYvnA8MoW29NqqXD0ep0rfBA=="], + + "eslint/find-up/locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + + "lazystream/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + + "lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "5.1.2" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + + "magicast/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], + + "nitropack/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA=="], + + "nitropack/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.0", "", { "os": "android", "cpu": "arm" }, "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ=="], + + "nitropack/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.0", "", { "os": "android", "cpu": "arm64" }, "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw=="], + + "nitropack/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.28.0", "", { "os": "android", "cpu": "x64" }, "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA=="], + + "nitropack/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q=="], + + "nitropack/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ=="], + + "nitropack/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q=="], + + "nitropack/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw=="], + + "nitropack/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw=="], + + "nitropack/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A=="], + + "nitropack/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.0", "", { "os": "linux", "cpu": "ia32" }, "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ=="], + + "nitropack/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.0", "", { "os": "linux", "cpu": "none" }, "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg=="], + + "nitropack/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.0", "", { "os": "linux", "cpu": "none" }, "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w=="], + + "nitropack/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg=="], + + "nitropack/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.0", "", { "os": "linux", "cpu": "none" }, "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ=="], + + "nitropack/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q=="], + + "nitropack/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.0", "", { "os": "linux", "cpu": "x64" }, "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ=="], + + "nitropack/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.0", "", { "os": "none", "cpu": "arm64" }, "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw=="], + + "nitropack/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.0", "", { "os": "none", "cpu": "x64" }, "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw=="], + + "nitropack/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.0", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g=="], + + "nitropack/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA=="], + + "nitropack/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.0", "", { "os": "none", "cpu": "arm64" }, "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w=="], + + "nitropack/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.0", "", { "os": "sunos", "cpu": "x64" }, "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw=="], + + "nitropack/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA=="], + + "nitropack/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA=="], + + "nitropack/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.0", "", { "os": "win32", "cpu": "x64" }, "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw=="], + + "nostics/oxc-parser/@oxc-parser/binding-android-arm-eabi": ["@oxc-parser/binding-android-arm-eabi@0.132.0", "", { "os": "android", "cpu": "arm" }, "sha512-KrLaPWa5c9Y7LkW+rKkaUE3y7DBDrQtaf7rlsSDfv6KAHUjgzAIRA761Lrrp6//Yd/Rlie/yEOt9YENCoJnOcw=="], + + "nostics/oxc-parser/@oxc-parser/binding-android-arm64": ["@oxc-parser/binding-android-arm64@0.132.0", "", { "os": "android", "cpu": "arm64" }, "sha512-SThDrSeamB/kG2+NxcJ5/wSLcV6dUqDknrPLqFYQ0ST/55mtBP4M7Q/f3QbubH6aAd11wpzZn/nwbVRSdobOpg=="], + + "nostics/oxc-parser/@oxc-parser/binding-darwin-arm64": ["@oxc-parser/binding-darwin-arm64@0.132.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Lc0f/TYoKBghE5/2Gsv7bLXk+TJZunx2Tf61X8hG4ARXdc8UYI26dCGccFSd1AyFbK3jfaNXtMnupggDbjPXdQ=="], + + "nostics/oxc-parser/@oxc-parser/binding-darwin-x64": ["@oxc-parser/binding-darwin-x64@0.132.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-RG2eJIpf7C21z9HSSXFw1bTArdpKe7Y4fwcJTwRq1yCSe1vSavaN9GA1sm9KqzemTLAGVktQ+7qBTGp0vQeUZg=="], + + "nostics/oxc-parser/@oxc-parser/binding-freebsd-x64": ["@oxc-parser/binding-freebsd-x64@0.132.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-wQIPntPLtJ8NcBpvKPbEv3NqzV6k8eP8tP/jE9Rg8HTg/j7urZGFSsTCPCW5k77Qfw2DM4vRvc9p3I4yq/Shvw=="], + + "nostics/oxc-parser/@oxc-parser/binding-linux-arm-gnueabihf": ["@oxc-parser/binding-linux-arm-gnueabihf@0.132.0", "", { "os": "linux", "cpu": "arm" }, "sha512-PixKEpeSe3yxQWqNyOCBALRYc72+Tj7ILDofUl3iXo25cVOzLA6jHUhmOINRtWIPh7dbUie3QNeabwaQpZTw6w=="], + + "nostics/oxc-parser/@oxc-parser/binding-linux-arm-musleabihf": ["@oxc-parser/binding-linux-arm-musleabihf@0.132.0", "", { "os": "linux", "cpu": "arm" }, "sha512-sCR+DzGHlyHKnbA2z9zWjTUhIo8Sy0enJl4RDsBwPmkxYynPatpwOAWe8W5127SlW0boqUWHGtr1NWn5UwIhXQ=="], + + "nostics/oxc-parser/@oxc-parser/binding-linux-arm64-gnu": ["@oxc-parser/binding-linux-arm64-gnu@0.132.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-sQBix5P2cW+IpzTcCwYxnh9yALrKSIkKJThspBvMGcygSMnbzkSvhN7SfuX1hvBk8y1XEChsdkU3ET0V5DmzUw=="], + + "nostics/oxc-parser/@oxc-parser/binding-linux-arm64-musl": ["@oxc-parser/binding-linux-arm64-musl@0.132.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-WozHg3Kc//8Sk756HXXgMbEAvqtG+Lzb9JOojwQzIGDtN78Az2dLttkb71akWYUF/8IgYfDSlfKh4Uot8is5Vw=="], + + "nostics/oxc-parser/@oxc-parser/binding-linux-ppc64-gnu": ["@oxc-parser/binding-linux-ppc64-gnu@0.132.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-CmX/ulNBOEwWTyVRmcpYKAcAizW6+OjtLJgo7fXoL9OqQvjF4VER8tPomv44vwzfSCy1BHbsB0ZlZYzYJNj4cA=="], + + "nostics/oxc-parser/@oxc-parser/binding-linux-riscv64-gnu": ["@oxc-parser/binding-linux-riscv64-gnu@0.132.0", "", { "os": "linux", "cpu": "none" }, "sha512-j9oQS+hM90SdhviNGWbPgT4+Rlq+ac++q/zjgwPD1mVHgxHzATvoRGtDx0sXGmFOQ9J9YkwAhYGb5MAHL6TAsA=="], + + "nostics/oxc-parser/@oxc-parser/binding-linux-riscv64-musl": ["@oxc-parser/binding-linux-riscv64-musl@0.132.0", "", { "os": "linux", "cpu": "none" }, "sha512-bLz+Xi+Agnfmd7kWPEsSVwCn2k4EyIalZkNBcQ0OGIv9rqn8VgCPLNd03tM9mKX/5TdlvDXalz0q71BIrOPNqg=="], + + "nostics/oxc-parser/@oxc-parser/binding-linux-s390x-gnu": ["@oxc-parser/binding-linux-s390x-gnu@0.132.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-U6t2qbJU0ypTfyj9QV3W1Y6mITDTL8ai/OR6NUn85vyHthOvobKWgXzU4tu0EskSzlpuVFz1g0jFGulDIUKHxQ=="], + + "nostics/oxc-parser/@oxc-parser/binding-linux-x64-gnu": ["@oxc-parser/binding-linux-x64-gnu@0.132.0", "", { "os": "linux", "cpu": "x64" }, "sha512-WcEaSNHFk8yz5YFlQQAlhq6jOFmZBB/RKE7uzhyCIf+pF1Lmv9gUH4221mle2Gd9iHyWT3ySNph8yZgb1xYdWg=="], + + "nostics/oxc-parser/@oxc-parser/binding-linux-x64-musl": ["@oxc-parser/binding-linux-x64-musl@0.132.0", "", { "os": "linux", "cpu": "x64" }, "sha512-iQrV4iJzQgRwK3BWRmQl1C3C6g3wYpXN2WLdQdyR+efoUnncdShZAVp9OgcojtlD3MDRbuOMGG3SjxF4fL4nlQ=="], + + "nostics/oxc-parser/@oxc-parser/binding-openharmony-arm64": ["@oxc-parser/binding-openharmony-arm64@0.132.0", "", { "os": "none", "cpu": "arm64" }, "sha512-FWzmUGrZ6GUby4U7WIwcCtab6tdmlTO3xTRRKyb5kjIJVEiaUAT8animUG/nK8ZCA8gkRkPOTId4rl6uTqUmJQ=="], + + "nostics/oxc-parser/@oxc-parser/binding-wasm32-wasi": ["@oxc-parser/binding-wasm32-wasi@0.132.0", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "1.1.4" }, "cpu": "none" }, "sha512-TlbMppxJI5CjWDes0QaP6G3aneVg1yikBu5QYI+DUShF9WDL66ccgKFNNGmi/Wybtszw6hxwAvv76T4DaPKnHw=="], + + "nostics/oxc-parser/@oxc-parser/binding-win32-arm64-msvc": ["@oxc-parser/binding-win32-arm64-msvc@0.132.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-RH/NbFjGKqdUAUi7Oh3LQPxUk2hsWFEEQ38HSnbRQT8QjBZFKqL1fMbmsB3N4jy/KPh9iX94+9dmkEMBBbambw=="], + + "nostics/oxc-parser/@oxc-parser/binding-win32-ia32-msvc": ["@oxc-parser/binding-win32-ia32-msvc@0.132.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-JUr4jQY9jxoIB/YTLXr6XofSi5xikj6p5/Ns1h0VOBDT0j1jKU+kMsv2xxv51RwnETcXpA1Yw/9oUAfcqfaqEA=="], + + "nostics/oxc-parser/@oxc-parser/binding-win32-x64-msvc": ["@oxc-parser/binding-win32-x64-msvc@0.132.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2dapgHpA5X8DSXF4AU36hJWYf6zP0tKjMXFRAZFBD62pkevW/uhFDXoFH9Y/3Fd2EtDrw5ByNnR1wVE9X9y0SQ=="], + + "nostics/oxc-parser/@oxc-project/types": ["@oxc-project/types@0.132.0", "", {}, "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ=="], + + "readdir-glob/minimatch/brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "1.0.2" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="], + + "rollup-plugin-visualizer/open/wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "3.1.1", "powershell-utils": "0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="], + + "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "unplugin-auto-import/unimport/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + + "vaul-vue/@vueuse/core/@types/web-bluetooth": ["@types/web-bluetooth@0.0.20", "", {}, "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow=="], + + "vaul-vue/@vueuse/core/@vueuse/metadata": ["@vueuse/metadata@10.11.1", "", {}, "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw=="], + + "vaul-vue/@vueuse/core/@vueuse/shared": ["@vueuse/shared@10.11.1", "", { "dependencies": { "vue-demi": "0.14.10" } }, "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA=="], + + "vite-plugin-checker/npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], + + "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "archiver-utils/glob/minimatch/brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "1.0.2" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="], + + "archiver-utils/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "eslint/find-up/locate-path/p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "3.1.0" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + + "readdir-glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "archiver-utils/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "eslint/find-up/locate-path/p-locate/p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "eslint/find-up/locate-path/p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + } +} diff --git a/frontend/eslint.config.mjs b/frontend/eslint.config.mjs new file mode 100644 index 00000000..934c3a1d --- /dev/null +++ b/frontend/eslint.config.mjs @@ -0,0 +1,6 @@ +// @ts-check +import withNuxt from './.nuxt/eslint.config.mjs' + +export default withNuxt( + // Your custom configs here +) diff --git a/frontend/nuxt.config.ts b/frontend/nuxt.config.ts new file mode 100644 index 00000000..b38de334 --- /dev/null +++ b/frontend/nuxt.config.ts @@ -0,0 +1,60 @@ +// https://nuxt.com/docs/api/configuration/nuxt-config +export default defineNuxtConfig({ + modules: [ + '@nuxt/eslint', + '@nuxt/ui' + ], + + devtools: { + enabled: true + }, + + css: ['~/assets/css/main.css'], + + routeRules: { + '/': {prerender: true} + }, + + compatibilityDate: '2026-06-30', + + runtimeConfig: { + public: { + apiBase: 'http://localhost:8000' + } + }, + + ui: { + theme: { + colors: [ + 'primary', + 'secondary', + 'tertiary', + 'info', + 'success', + 'warning', + 'error', + 'brandSuccess', + 'brandWarning', + 'brandError', + ] + } + }, + + fonts: { + families: [ + {name: 'Comic Relief', provider: 'google'}, + {name: 'Baloo Paaji 2', provider: 'google'}, + {name: 'DynaPuff', provider: 'google'}, + {name: 'Open Sans', provider: 'google'}, + ] + }, + + eslint: { + config: { + stylistic: { + commaDangle: 'never', + braceStyle: '1tbs' + } + } + } +}) diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 00000000..2417b47b --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,27 @@ +{ + "name": "frontend", + "private": true, + "type": "module", + "scripts": { + "build": "nuxt build", + "dev": "nuxt dev", + "preview": "nuxt preview", + "postinstall": "nuxt prepare", + "lint": "eslint .", + "typecheck": "nuxt typecheck" + }, + "dependencies": { + "@iconify-json/lucide": "^1.2.114", + "@iconify-json/simple-icons": "^1.2.87", + "@nuxt/ui": "^4.9.0", + "nuxt": "^4.4.8", + "tailwindcss": "^4.3.2" + }, + "devDependencies": { + "@nuxt/eslint": "^1.16.0", + "eslint": "^10.6.0", + "typescript": "^6.0.3", + "vue-tsc": "^3.3.5" + }, + "packageManager": "pnpm@11.9.0" +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 00000000..103ec507 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,10 @@ +{ + // https://nuxt.com/docs/guide/concepts/typescript + "files": [], + "references": [ + { "path": "./.nuxt/tsconfig.app.json" }, + { "path": "./.nuxt/tsconfig.server.json" }, + { "path": "./.nuxt/tsconfig.shared.json" }, + { "path": "./.nuxt/tsconfig.node.json" } + ] +} diff --git a/plugin-api/src/main/java/de/chojo/pluginjam/payload/Registration.java b/plugin-api/src/main/java/de/chojo/pluginjam/payload/Registration.java index bd93cbd6..dcf8328c 100644 --- a/plugin-api/src/main/java/de/chojo/pluginjam/payload/Registration.java +++ b/plugin-api/src/main/java/de/chojo/pluginjam/payload/Registration.java @@ -6,5 +6,5 @@ package de.chojo.pluginjam.payload; -public record Registration(int id, String name, String host, int port, int apiPort) { +public record Registration(int id, String name, String host) { } diff --git a/plugin-paper/src/main/java/de/chojo/pluginjam/PluginJam.java b/plugin-paper/src/main/java/de/chojo/pluginjam/PluginJam.java index 3efa51c8..0500f0bd 100644 --- a/plugin-paper/src/main/java/de/chojo/pluginjam/PluginJam.java +++ b/plugin-paper/src/main/java/de/chojo/pluginjam/PluginJam.java @@ -8,7 +8,6 @@ import de.chojo.pluginjam.api.Api; import de.chojo.pluginjam.greeting.Welcomer; -import de.chojo.pluginjam.serverapi.ServerApi; import de.chojo.pluginjam.service.CommandBlocker; import de.chojo.pluginjam.service.JoinService; import de.chojo.pluginjam.service.ServerRequests; @@ -26,7 +25,6 @@ public class PluginJam extends EldoPlugin implements Listener { private Api api; private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); private ReportService service; - private ServerApi serverApi; @Override public Level getLogLevel() { @@ -47,7 +45,6 @@ public void onPluginEnable() { api = Api.create(this, serverRequests); service = ReportService.create(this, executor); - serverApi = new ServerApi(this, serverRequests); registerListener(new CommandBlocker(serverRequests, localizer), new Welcomer(this), new JoinService(this, serverRequests, localizer)); } @@ -57,8 +54,4 @@ public void onPluginDisable() { service.shutdown(); executor.shutdown(); } - - public ServerApi api() { - return serverApi; - } } diff --git a/plugin-paper/src/main/java/de/chojo/pluginjam/api/Api.java b/plugin-paper/src/main/java/de/chojo/pluginjam/api/Api.java index 040b93ab..8ca9976d 100644 --- a/plugin-paper/src/main/java/de/chojo/pluginjam/api/Api.java +++ b/plugin-paper/src/main/java/de/chojo/pluginjam/api/Api.java @@ -7,9 +7,9 @@ package de.chojo.pluginjam.api; import de.chojo.pluginjam.PluginJam; -import de.chojo.pluginjam.api.routes.Configuration; -import de.chojo.pluginjam.api.routes.Requests; -import de.chojo.pluginjam.api.routes.Stats; +import de.chojo.pluginjam.api.routes.ConfigurationRoute; +import de.chojo.pluginjam.api.routes.OnlineRoute; +import de.chojo.pluginjam.api.routes.StatsRoute; import de.chojo.pluginjam.service.ServerRequests; import io.javalin.Javalin; import org.bukkit.plugin.Plugin; @@ -21,15 +21,14 @@ public class Api { private static final Logger log = getLogger(Api.class); - private final Configuration configuration; - private final Stats stats; - private final Requests requests; - private Javalin javalin; + private final ConfigurationRoute configurationRoute; + private final StatsRoute statsRoute; + private final OnlineRoute onlineRoute; private Api(Plugin plugin, ServerRequests serverRequests) { - configuration = new Configuration(plugin); - stats = new Stats(plugin); - requests = new Requests(serverRequests); + configurationRoute = new ConfigurationRoute(plugin); + statsRoute = new StatsRoute(plugin); + onlineRoute = new OnlineRoute(); } public static Api create(Plugin plugin, ServerRequests serverRequests) { @@ -41,7 +40,7 @@ public static Api create(Plugin plugin, ServerRequests serverRequests) { private void ignite() { var classLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(PluginJam.class.getClassLoader()); - javalin = Javalin.create(config -> { + Javalin javalin = Javalin.create(config -> { config.concurrency.useVirtualThreads = true; config.routes.apiBuilder(this::routes); }); @@ -51,8 +50,8 @@ private void ignite() { private void routes() { before(ctx -> log.debug("Received request on {}.", ctx.path())); - path("v1", configuration::buildRoutes); - path("v1", stats::buildRoutes); - path("v1", requests::buildRoutes); + path("v1", configurationRoute::buildRoutes); + path("v1", statsRoute::buildRoutes); + path("v1", onlineRoute::buildRoutes); } } diff --git a/plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/Configuration.java b/plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/ConfigurationRoute.java similarity index 95% rename from plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/Configuration.java rename to plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/ConfigurationRoute.java index 526b5f82..2e707afc 100644 --- a/plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/Configuration.java +++ b/plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/ConfigurationRoute.java @@ -12,10 +12,10 @@ import static io.javalin.apibuilder.ApiBuilder.path; import static io.javalin.apibuilder.ApiBuilder.post; -public class Configuration { +public class ConfigurationRoute { private final Plugin plugin; - public Configuration(Plugin plugin) { + public ConfigurationRoute(Plugin plugin) { this.plugin = plugin; } diff --git a/plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/Requests.java b/plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/OnlineRoute.java similarity index 53% rename from plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/Requests.java rename to plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/OnlineRoute.java index cb8b60df..ab68e87d 100644 --- a/plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/Requests.java +++ b/plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/OnlineRoute.java @@ -6,23 +6,15 @@ package de.chojo.pluginjam.api.routes; -import de.chojo.pluginjam.service.ServerRequests; -import org.bukkit.plugin.Plugin; - import static io.javalin.apibuilder.ApiBuilder.get; import static io.javalin.apibuilder.ApiBuilder.path; -public class Requests { - private final ServerRequests requests; - - public Requests(ServerRequests requests) { - this.requests = requests; - } +public class OnlineRoute { public void buildRoutes() { - path("requests", () -> { + path("online", () -> { get("", ctx -> { - ctx.json(requests.get()); + ctx.json("online"); }); }); } diff --git a/plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/Stats.java b/plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/StatsRoute.java similarity index 95% rename from plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/Stats.java rename to plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/StatsRoute.java index 1546798f..4a6cdec8 100644 --- a/plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/Stats.java +++ b/plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/StatsRoute.java @@ -12,11 +12,11 @@ import static io.javalin.apibuilder.ApiBuilder.get; import static io.javalin.apibuilder.ApiBuilder.path; -public class Stats { +public class StatsRoute { private static final int MB = 1024 * 1024; private final Plugin plugin; - public Stats(Plugin plugin) { + public StatsRoute(Plugin plugin) { this.plugin = plugin; } diff --git a/plugin-paper/src/main/java/de/chojo/pluginjam/serverapi/ServerApi.java b/plugin-paper/src/main/java/de/chojo/pluginjam/serverapi/ServerApi.java deleted file mode 100644 index 781de212..00000000 --- a/plugin-paper/src/main/java/de/chojo/pluginjam/serverapi/ServerApi.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * Copyright (C) 2022 DevCord Team and Contributor - */ - -package de.chojo.pluginjam.serverapi; - -import de.chojo.pluginjam.service.ServerRequests; -import org.bukkit.plugin.Plugin; - -public class ServerApi { - private final Plugin plugin; - private final ServerRequests requests; - - public ServerApi(Plugin plugin, ServerRequests requests) { - this.plugin = plugin; - this.requests = requests; - } - - /** - * Requests a restart of the server - */ - public void requestRestart() { - requests.restartByUserOrServer(true); - } - - /** - * Sets the join message of this server - * @param message - */ - public void setMessage(String message) { - plugin.getConfig().set("message", message); - plugin.saveConfig(); - } -} diff --git a/plugin-paper/src/main/java/de/chojo/pluginjam/velocity/ReportService.java b/plugin-paper/src/main/java/de/chojo/pluginjam/velocity/ReportService.java index 63da7a7e..a485f4a0 100644 --- a/plugin-paper/src/main/java/de/chojo/pluginjam/velocity/ReportService.java +++ b/plugin-paper/src/main/java/de/chojo/pluginjam/velocity/ReportService.java @@ -58,7 +58,7 @@ public void run() { private void register() { log.info("Registering server at velocity instance"); - var registration = new Registration(id, name, host, plugin.getServer().getPort(), apiPort); + var registration = new Registration(id, name, host); var builder = HttpRequest.newBuilder(apiUrl("v1", "server")) .POST(HttpRequest.BodyPublishers.ofString(gson.toJson(registration))) .build(); @@ -74,7 +74,7 @@ private void register() { } private void ping() { - var registration = new Registration(id, name, host, plugin.getServer().getPort(), apiPort); + var registration = new Registration(id, name, host); var builder = HttpRequest.newBuilder(apiUrl("v1", "server")) .method("PATCH", HttpRequest.BodyPublishers.ofString(gson.toJson(registration))) .build(); diff --git a/plugin-velocity/src/main/java/de/chojo/pluginjam/servers/ServerRegistry.java b/plugin-velocity/src/main/java/de/chojo/pluginjam/servers/ServerRegistry.java index 0166d86b..c6f5efae 100644 --- a/plugin-velocity/src/main/java/de/chojo/pluginjam/servers/ServerRegistry.java +++ b/plugin-velocity/src/main/java/de/chojo/pluginjam/servers/ServerRegistry.java @@ -26,7 +26,6 @@ public class ServerRegistry implements Runnable { private static final Logger log = getLogger(ServerRegistry.class); private final Map ids = new HashMap<>(); - private final Map ports = new HashMap<>(); private final Map seen = new HashMap<>(); private final ProxyServer proxy; @@ -46,7 +45,7 @@ public void run() { var timeout = Instant.now().minus(2, ChronoUnit.MINUTES); for (var entry : new HashMap<>(seen).entrySet()) { if (entry.getValue().isBefore(timeout)) { - log.info("Server {}:{} timed out.", entry.getKey().name(), entry.getKey().port()); + log.info("Server {} timed out.", entry.getKey().name()); unregister(entry.getKey()); } } @@ -59,25 +58,18 @@ public void register(Registration registration) { throw AlreadyRegisteredException.forName(registration.name()); } - reg = ports.get(registration.port()); - - if (reg != null && !reg.equals(registration)) { - throw AlreadyRegisteredException.forPort(registration.port()); - } - - log.info("Registered server {} on port {}", registration.name(), registration.port()); + log.info("Registered server {}", registration.name()); ids.put(registration.id(), registration); - ports.put(registration.port(), registration); - proxy.registerServer(new ServerInfo(registration.name(), new InetSocketAddress(registration.host(), registration.port()))); + proxy.registerServer(new ServerInfo(registration.name(), new InetSocketAddress(registration.host(), 25565))); ping(registration); } public void ping(Registration registration) { log.debug("Ping of server {} received.", registration.name()); seen.put(registration, Instant.now()); - if (!ids.containsKey(registration.id()) && !ports.containsKey(registration.port())) { + if (!ids.containsKey(registration.id())) { log.info("Received ping of unknown server {} with id {}", registration.id(), registration.name()); log.info("Attempting to register server."); register(registration); @@ -87,13 +79,12 @@ public void ping(Registration registration) { public void unregister(Registration registration) { var removed = ids.remove(registration.id()); if (removed == null) { - log.warn("Unregistered server {} from port {}, but this server is not known.", registration.id(), registration.port()); + log.warn("Unregistered server {}, but this server is not known.", registration.id()); return; } - ports.remove(removed.port()); seen.remove(removed); - log.info("Unregistered server {} on port {}", removed.name(), removed.port()); - proxy.unregisterServer(new ServerInfo(removed.name(), new InetSocketAddress("localhost", removed.port()))); + log.info("Unregistered server {}", removed.name()); + proxy.unregisterServer(new ServerInfo(removed.name(), new InetSocketAddress("localhost", 25565))); } public Collection server() { diff --git a/plugin-velocity/src/main/java/de/chojo/pluginjam/web/server/Server.java b/plugin-velocity/src/main/java/de/chojo/pluginjam/web/server/Server.java index 40e7d6f5..d96d90bc 100644 --- a/plugin-velocity/src/main/java/de/chojo/pluginjam/web/server/Server.java +++ b/plugin-velocity/src/main/java/de/chojo/pluginjam/web/server/Server.java @@ -37,9 +37,8 @@ public void buildRoutes() { delete("", ctx -> { var id = ctx.queryParam("id"); - var port = ctx.queryParam("port"); var host = ctx.queryParam("host"); - var registration = new Registration(Integer.parseInt(id), "", host, Integer.parseInt(port), 0); + var registration = new Registration(Integer.parseInt(id), "", host); registry.unregister(registration); ctx.status(HttpStatus.ACCEPTED); }); diff --git a/settings.gradle.kts b/settings.gradle.kts index c4470c07..92977b96 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -22,6 +22,11 @@ dependencyResolutionManagement { library("log4j-slf4j2", "org.apache.logging.log4j", "log4j-slf4j2-impl").versionRef("log4j") bundle("logging", listOf("slf4j", "log4j-core", "log4j-slf4j2")) + version("docker", "3.7.1") + library("docker-api", "com.github.docker-java", "docker-java-core").versionRef("docker") + library("docker-transport", "com.github.docker-java", "docker-java-transport-httpclient5").versionRef("docker") + bundle("docker", listOf("docker-api", "docker-transport")) + version("javalin", "7.2.2") library("javalin-core", "io.javalin", "javalin").versionRef("javalin")