diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index cde8e9ab2..881cfccfc 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -12,9 +12,13 @@ labels: 'Bug' ### Exceptions & Other Logs -``` -Place log contents here + ``` +``` + +### Plugin versions + + ### Additional information - + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..e5c1b25bd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,7 @@ +contact_links: + - name: Question + url: https://github.com/plan-player-analytics/Plan/discussions/new + about: Ask a question about the plugin on the Discussions forum + - name: Installation help + url: https://discordapp.com/invite/yXKmjzT + about: Get help for installations on Discord diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index c7dc3760a..000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: Suggestion -about: Feature request or an addition ---- - -### Is your feature request related to a problem? Please describe. - - -### I would like to be able to.. - diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000..a2c6adfb4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,15 @@ +name: Suggestion +description: Feature request or an addition +body: +- type: textarea + attributes: + label: I would like to be able to.. + description: A clear and concise description of what you want to happen + validations: + required: true +- type: textarea + attributes: + label: Is your feature request related to a problem? Please describe. + description: This section helps figure out why this feature is needed - What would this feature be used for? How would you use this feature? + validations: + required: true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/plugin_request.md b/.github/ISSUE_TEMPLATE/plugin_request.md deleted file mode 100644 index b760a8d71..000000000 --- a/.github/ISSUE_TEMPLATE/plugin_request.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: Plugin Suggestion -about: Suggest data from another plugin for Plan -labels: 'New Data, DataExtensions, Help Wanted' - ---- - -### Data to display - - -### Plugin information - - -**API or Source Code:** [Link](URL HERE) - -**Project page or Downloads:** [Link](URL HERE) diff --git a/.github/ISSUE_TEMPLATE/plugin_request.yml b/.github/ISSUE_TEMPLATE/plugin_request.yml new file mode 100644 index 000000000..dc34664fb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/plugin_request.yml @@ -0,0 +1,20 @@ +name: Plugin Suggestion +description: Suggest data from another plugin for Plan +labels: [New Data, DataExtensions, Help Wanted] +body: +- type: textarea + attributes: + label: Data to display + description: List any data you would like to see from the plugin + validations: + required: true +- type: input + attributes: + label: Link to API or Source Code + validations: + required: true +- type: input + attributes: + label: Link to Project page or Downloads + validations: + required: true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md deleted file mode 100644 index 523af561d..000000000 --- a/.github/ISSUE_TEMPLATE/question.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -name: Question -about: Ask a Question about the plugin, see 'Possible Pitfalls' under Projects before asking your question. -labels: 'Question' - ---- - - - - -### Additional context - diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 2b3ca8531..d1db7aa3c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,9 +3,13 @@ updates: - package-ecosystem: gradle directory: "/Plan" schedule: - interval: daily + interval: weekly + day: friday open-pull-requests-limit: 99 ignore: + - dependency-name: net.fabricmc:fabric-loader + versions: + - ">=0, < 2" - dependency-name: com.djrapitops:Extension-ProtocolSupport versions: - ">= 4.a, < 5" @@ -15,3 +19,9 @@ updates: - dependency-name: com.h2database:h2 versions: - "> 1.4.199" +- package-ecosystem: npm + directory: "/Plan/react/dashboard" + schedule: + interval: weekly + day: friday + open-pull-requests-limit: 99 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..891d4dd64 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,96 @@ + +name: CI + +on: [push, pull_request] + +jobs: + test: + name: Test, Build & Upload + + runs-on: ubuntu-latest + + services: + mariadb: + image: mariadb:latest + ports: + - 3306 + env: + MYSQL_USER: user + MYSQL_PASSWORD: password + MYSQL_DATABASE: test + MYSQL_ROOT_PASSWORD: password + options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Setup JDK + uses: actions/setup-java@v2 + with: + distribution: 'adopt' + java-version: '17' + - name: Setup Selenium Webdriver + uses: nanasess/setup-chromedriver@v1 + - name: Setup Selenium Webdriver settings + run: | + export DISPLAY=:99 + chromedriver --url-base=/wd/hub & + sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & + - name: Verify MariaDB connection + env: + PORT: ${{ job.services.mariadb.ports[3306] }} + run: | + while ! mysqladmin ping -h"127.0.0.1" -P"$PORT" --silent; do + sleep 1 + done + - name: Cache Gradle packages + uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + - name: Build jars + run: | + cd Plan + ./gradlew shadowJar + - name: Get versions + run: | + cd Plan + ./gradlew snapshotVersion jarNameVersion + git_hash=$(git rev-parse --short "$GITHUB_SHA") + echo "git_hash=$git_hash" >> $GITHUB_ENV + echo "snapshotVersion=$(cat build/versions/snapshot.txt)" >> $GITHUB_ENV + echo "versionString=$(cat build/versions/jar.txt)" >> $GITHUB_ENV + echo "artifactPath=$(pwd)/builds" >> $GITHUB_ENV + - name: Upload Plan.jar + uses: actions/upload-artifact@v3 + with: + name: Plan-${{ env.versionString }}-${{ env.git_hash }}.jar + path: ${{ env.artifactPath }}/Plan-${{ env.snapshotVersion }}.jar + - name: Upload PlanFabric.jar + uses: actions/upload-artifact@v3 + with: + name: PlanFabric-${{ env.versionString }}-${{ env.git_hash }}.jar + path: ${{ env.artifactPath }}/PlanFabric-${{ env.snapshotVersion }}.jar + - name: Test + env: + MYSQL_DB: test + MYSQL_USER: user + MYSQL_PASS: password + MYSQL_PORT: ${{ job.services.mariadb.ports[3306] }} + CHROMEDRIVER: /usr/local/bin/chromedriver + run: | + cd Plan + ./gradlew build + - name: SonarCloud + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + if: "${{ env.SONAR_TOKEN != '' }}" + run: | + cd Plan + ./gradlew sonar -Dsonar.host.url=https://sonarcloud.io -Dsonar.organization=player-analytics-plan diff --git a/.github/workflows/gradle-pr.yml b/.github/workflows/gradle-pr.yml deleted file mode 100644 index b156726b1..000000000 --- a/.github/workflows/gradle-pr.yml +++ /dev/null @@ -1,44 +0,0 @@ - -name: Java PR CI - -on: [pull_request] - -jobs: - build: - - runs-on: ubuntu-latest - - services: - mariadb: - image: mariadb:latest - ports: - - 3306 - env: - MYSQL_USER: user - MYSQL_PASSWORD: password - MYSQL_DATABASE: test - MYSQL_ROOT_PASSWORD: password - options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 - - steps: - - uses: actions/checkout@v1 - - name: Set up JDK 1.11 - uses: actions/setup-java@v1 - with: - java-version: 1.11 - - name: Verify MariaDB connection - env: - PORT: ${{ job.services.mariadb.ports[3306] }} - run: | - while ! mysqladmin ping -h"127.0.0.1" -P"$PORT" --silent; do - sleep 1 - done - - name: Build with Gradle - env: - MYSQL_DB: test - MYSQL_USER: user - MYSQL_PASS: password - MYSQL_PORT: ${{ job.services.mariadb.ports[3306] }} - run: | - cd Plan - ./gradlew build diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml deleted file mode 100644 index 35aa656cf..000000000 --- a/.github/workflows/gradle.yml +++ /dev/null @@ -1,51 +0,0 @@ - -name: Java CI - -on: [push] - -jobs: - build: - - runs-on: ubuntu-latest - - services: - mariadb: - image: mariadb:latest - ports: - - 3306 - env: - MYSQL_USER: user - MYSQL_PASSWORD: password - MYSQL_DATABASE: test - MYSQL_ROOT_PASSWORD: password - options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 - - steps: - - uses: actions/checkout@v1 - - name: Set up JDK 1.11 - uses: actions/setup-java@v1 - with: - java-version: 1.11 - - name: Verify MariaDB connection - env: - PORT: ${{ job.services.mariadb.ports[3306] }} - run: | - while ! mysqladmin ping -h"127.0.0.1" -P"$PORT" --silent; do - sleep 1 - done - - name: Build with Gradle - env: - MYSQL_DB: test - MYSQL_USER: user - MYSQL_PASS: password - MYSQL_PORT: ${{ job.services.mariadb.ports[3306] }} - run: | - cd Plan - ./gradlew build - - name: SonarCloud - run: | - cd Plan - ./gradlew sonar -Dsonar.host.url=https://sonarcloud.io -Dsonar.organization=player-analytics-plan - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/html.yml b/.github/workflows/html.yml deleted file mode 100644 index b7f3fcc99..000000000 --- a/.github/workflows/html.yml +++ /dev/null @@ -1,30 +0,0 @@ -# This workflow will build a Java project with Gradle -# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle - -name: Update html-branch - -on: - release: - types: [ published ] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - name: Checkout ๐Ÿ›Ž๏ธ - uses: actions/checkout@v2.3.1 - - name: Get git TAG - id: tagName - uses: olegtarasov/get-tag@v2.1 - - name: Copy assets ๐Ÿ”ง - run: | - mkdir -p workingdir/Plan/src/main/resources/assets/plan - cp -r Plan/common/src/main/resources/assets/plan workingdir/Plan/src/main/resources/assets/plan - - name: Deploy ๐Ÿš€ - uses: JamesIves/github-pages-deploy-action@4.0.0 - with: - branch: html # The branch the action should deploy to. - folder: workingdir # The folder the action should deploy. - commit-message: ${{ steps.tagName.outputs.tag }} diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml new file mode 100644 index 000000000..37f10ace5 --- /dev/null +++ b/.github/workflows/issues.yml @@ -0,0 +1,28 @@ +name: Issue Automation +on: + issues: + types: + - opened + +# map fields with customized labels +env: + inbox: INBOX + refinement: Needs Refinement + refined: Refined & Actionable + assigned: Assigned to Milestone + done: Done + +jobs: + issue_opened: + name: New Issue Opened + runs-on: ubuntu-latest + if: github.event_name == 'issues' && github.event.action == 'opened' + steps: + - name: Add issue to ${{ env.inbox }} + uses: leonsteinhaeuser/project-beta-automations@v1.2.1 + with: + gh_token: ${{ secrets.GITHUB_TOKEN }} + organization: plan-player-analytics + project_id: 3 + resource_node_id: ${{ github.event.issue.node_id }} + status_value: ${{ env.inbox }} # Target status diff --git a/.github/workflows/javadocs.yml b/.github/workflows/javadocs.yml index 878688a00..62a1b469d 100644 --- a/.github/workflows/javadocs.yml +++ b/.github/workflows/javadocs.yml @@ -16,6 +16,11 @@ jobs: - name: Checkout ๐Ÿ›Ž๏ธ uses: actions/checkout@v2.3.1 + - name: Set up JDK ๐Ÿต + uses: actions/setup-java@v2 + with: + distribution: 'adopt' + java-version: '17' - name: Build Javadocs ๐Ÿ”ง run: | cd Plan @@ -31,8 +36,9 @@ jobs: cp scripts/index.html javadocs/index.html cd javadocs touch .nojekyll + - name: Deploy ๐Ÿš€ - uses: JamesIves/github-pages-deploy-action@4.0.0 + uses: JamesIves/github-pages-deploy-action@4.1.4 with: branch: gh-pages # The branch the action should deploy to. folder: javadocs # The folder the action should deploy. diff --git a/.github/workflows/on-release.yml b/.github/workflows/on-release.yml new file mode 100644 index 000000000..4f675a51e --- /dev/null +++ b/.github/workflows/on-release.yml @@ -0,0 +1,137 @@ +name: Release + +on: + release: + types: [ published ] + +jobs: + update_versions_txt: + name: Update versions.txt + runs-on: ubuntu-latest + steps: + - name: Checkout ๐Ÿ›Ž๏ธ + uses: actions/checkout@v2.3.1 + - name: Get Download URL + run: | + curl 'https://api.github.com/repos/plan-player-analytics/plan/releases/${{ github.event.release.id }}/assets' | jq -r '.[] | {name: .name, url: .browser_download_url} | select(.url | strings | test("Fabric") | not)' > asset.txt + jq -r '.url' asset.txt > url.txt + echo "RELEASE_DOWNLOAD_URL=$(cat url.txt)" >> $GITHUB_ENV + - name: Write REL release line + if: ${{ github.event.release.prerelease == false }} + run: | + echo "REL|${{ github.event.release.tag_name }}|${{ env.RELEASE_DOWNLOAD_URL }}|https://github.com/plan-player-analytics/Plan/releases" > release_line.txt + - name: Write DEV release line + if: ${{ github.event.release.prerelease == true }} + run: | + echo "DEV|${{ github.event.release.tag_name }}|${{ env.RELEASE_DOWNLOAD_URL }}|${{ github.event.release.html_url }}" > release_line.txt + - name: Append to versions.txt + run: | + cat versions.txt > temp.txt + cat release_line.txt temp.txt > versions.txt + - name: Commit and push changes + uses: EndBug/add-and-commit@v9 + with: + committer_name: GitHub Actions + committer_email: 41898282+github-actions[bot]@users.noreply.github.com + message: Update versions.txt ${{ github.event.release.name }} + add: versions.txt + + update_html: + name: Update html-branch + runs-on: ubuntu-latest + + steps: + - name: Checkout ๐Ÿ›Ž๏ธ + uses: actions/checkout@v2.3.1 + - name: Get git TAG + id: tagName + uses: olegtarasov/get-tag@v2.1 + - name: Copy assets ๐Ÿ”ง + run: | + mkdir -p workingdir/Plan/src/main/resources/assets/plan + mkdir -p workingdir/react/dashboard + cp -r Plan/common/src/main/resources/assets/plan workingdir/Plan/src/main/resources/assets/plan + cp -r Plan/react/dashboard workingdir/react/dashboard + - name: Deploy ๐Ÿš€ + uses: JamesIves/github-pages-deploy-action@4.0.0 + with: + branch: html # The branch the action should deploy to. + folder: workingdir # The folder the action should deploy. + commit-message: ${{ steps.tagName.outputs.tag }} + + upload_release_ore: + name: Ore Upload + runs-on: ubuntu-latest + steps: + - name: Download release artifact for upload + run: | + curl 'https://api.github.com/repos/plan-player-analytics/plan/releases/${{ github.event.release.id }}/assets' | jq -r '.[] | {name: .name, url: .browser_download_url} | select(.url | strings | test("Fabric") | not)' > asset.txt + jq -r '.url' asset.txt > url.txt + jq -r '.name' asset.txt > name.txt + wget -i url.txt + echo "JAR_FILENAME=$(cat name.txt)" >> $GITHUB_ENV + - name: Upload artifact for ore upload + uses: actions/upload-artifact@v3 + with: + name: ${{ github.event.release.name }} + path: ${{ env.JAR_FILENAME }} + - name: Upload release to Ore ๐Ÿš€ + if: ${{ github.event.release.prerelease == false }} + uses: dualspiral/ore-upload-action@v1 + with: + plugin: ${{ github.event.release.name }} + description: ${{ github.event.release.body }} + apiKey: ${{ secrets.ORE_API_TOKEN }} + channel: Release + pluginId: plan + createForumPost: true + - name: Upload DEV release to Ore ๐Ÿš€ + if: ${{ github.event.release.prerelease == true }} + uses: dualspiral/ore-upload-action@v1 + with: + plugin: ${{ github.event.release.name }} + description: ${{ github.event.release.body }} + apiKey: ${{ secrets.ORE_API_TOKEN }} + channel: DEV + pluginId: plan + createForumPost: false + + upload_release_curseforge: + name: CurseForge Upload + runs-on: ubuntu-latest + steps: + - name: Download release artifact for upload + run: | + curl 'https://api.github.com/repos/plan-player-analytics/plan/releases/${{ github.event.release.id }}/assets' | jq -r '.[] | {name: .name, url: .browser_download_url} | select(.url | strings | test("Fabric"))' > asset.txt + jq -r '.url' asset.txt > url.txt + jq -r '.name' asset.txt > name.txt + wget -i url.txt + echo "JAR_FILENAME=$(cat name.txt)" >> $GITHUB_ENV + - name: Upload release to CurseForge ๐Ÿš€ + if: ${{ github.event.release.prerelease == false }} + uses: itsmeow/curseforge-upload@master + with: + token: ${{ secrets.CF_API_TOKEN }} + project_id: 508727 + game_endpoint: minecraft + file_path: ${{ env.JAR_FILENAME }} + changelog: ${{ github.event.release.body }} + changelog_type: markdown + display_name: ${{ github.event.release.name }} + game_versions: "2:Java 17,73407:1.19,Fabric" + release_type: release + relations: fabric-api:requiredDependency,luckperms:optionalDependency + - name: Upload prerelease to CurseForge ๐Ÿš€ + if: ${{ github.event.release.prerelease == true }} + uses: itsmeow/curseforge-upload@master + with: + token: ${{ secrets.CF_API_TOKEN }} + project_id: 508727 + game_endpoint: minecraft + file_path: ${{ env.JAR_FILENAME }} + changelog: ${{ github.event.release.body }} + changelog_type: markdown + display_name: ${{ github.event.release.name }} + game_versions: "2:Java 17,73407:1.19,Fabric" + release_type: beta + relations: fabric-api:requiredDependency,luckperms:optionalDependency diff --git a/.gitignore b/.gitignore index 74a16a4ad..12447f2f8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,14 +3,18 @@ Plan.iml PlanPluginBridge.iml .sonar/ builds/ -# Nukkit creates server.log during tests for some reason. +# Nukkit & Fabric create log files during tests for some reason. server.log +/Plan/fabric/logs/ *.db **/.gradle out/ +# Fabric +remappedSrc/ + # Created by https://www.gitignore.io/api/maven,eclipse,intellij,netbeans,osx,windows,notepadpp,windows,java ### Maven ### @@ -164,33 +168,6 @@ $RECYCLE.BIN/ # Windows shortcuts *.lnk - -### NotepadPP ### -# Notepad++ backups # -*.bak - - -### Windows ### -# Windows image file caches -Thumbs.db -ehthumbs.db - -# Folder config file -Desktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msm -*.msp - -# Windows shortcuts -*.lnk - - ### Java ### *.class @@ -203,3 +180,4 @@ $RECYCLE.BIN/ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* +/.vs diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 3b74efd21..00cc33c13 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -34,7 +34,7 @@ This Code of Conduct applies both within project spaces and in public spaces whe ## Enforcement -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team on discord https://discordapp.com/invite/yXKmjzT (you can send a DM to Rsl1122). The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team on discord https://discordapp.com/invite/yXKmjzT (you can send a DM to AuroraLS3). The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. diff --git a/Plan/api/build.gradle b/Plan/api/build.gradle index bdf6ff69d..2e90d75fd 100644 --- a/Plan/api/build.gradle +++ b/Plan/api/build.gradle @@ -4,7 +4,11 @@ dependencies { compileOnly "com.google.code.gson:gson:$gsonVersion" } -ext.apiVersion = '5.4-R0.1' +compileJava { + options.release = 8 +} + +ext.apiVersion = '5.5-R0.1' publishing { repositories { diff --git a/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/CompositeResolver.java b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/CompositeResolver.java index f6dbc0d6a..0d20d1326 100644 --- a/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/CompositeResolver.java +++ b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/CompositeResolver.java @@ -39,28 +39,22 @@ import java.util.function.Predicate; public final class CompositeResolver implements Resolver { private final List prefixes; - private final List>> resolvers; - private final List> canAccess; + private final List resolvers; CompositeResolver() { this.prefixes = new ArrayList<>(); this.resolvers = new ArrayList<>(); - this.canAccess = new ArrayList<>(); } public static CompositeResolver.Builder builder() { return new Builder(); } - private Optional>> getResolver(URIPath target) { + private Optional getResolver(URIPath target) { return target.getPart(0).flatMap(this::findResolver); } - private Optional> getAccessCheck(URIPath target) { - return target.getPart(0).flatMap(this::findAccessCheck); - } - - private Optional>> findResolver(String prefix) { + private Optional findResolver(String prefix) { for (int i = 0; i < prefixes.size(); i++) { if (prefixes.get(i).equals(prefix)) { return Optional.of(resolvers.get(i)); @@ -69,21 +63,11 @@ public final class CompositeResolver implements Resolver { return Optional.empty(); } - private Optional> findAccessCheck(String prefix) { - for (int i = 0; i < prefixes.size(); i++) { - if (prefixes.get(i).equals(prefix)) { - return Optional.of(canAccess.get(i)); - } - } - return Optional.empty(); - } - void add(String prefix, Resolver resolver) { if (prefix == null) throw new IllegalArgumentException("Prefix can not be null"); if (resolver == null) throw new IllegalArgumentException("Resolver can not be null"); prefixes.add(prefix); - resolvers.add(resolver::resolve); - canAccess.add(resolver::canAccess); + resolvers.add(resolver); } void add(String prefix, Function resolver, Predicate accessCheck) { @@ -93,22 +77,27 @@ public final class CompositeResolver implements Resolver { } if (accessCheck == null) throw new IllegalArgumentException("Predicate accessCheck can not be null"); prefixes.add(prefix); - resolvers.add(request -> Optional.ofNullable(resolver.apply(request))); - canAccess.add(accessCheck); + resolvers.add(new FunctionalResolverWrapper(request -> Optional.ofNullable(resolver.apply(request)), accessCheck)); } @Override public boolean canAccess(Request request) { Request forThis = request.omitFirstInPath(); - return getAccessCheck(forThis.getPath()) - .map(resolver -> resolver.test(forThis)) + return getResolver(forThis.getPath()) + .map(resolver -> resolver.canAccess(forThis)) .orElse(true); } @Override public Optional resolve(Request request) { Request forThis = request.omitFirstInPath(); - return getResolver(forThis.getPath()).flatMap(resolver -> resolver.apply(forThis)); + return getResolver(forThis.getPath()).flatMap(resolver -> resolver.resolve(forThis)); + } + + @Override + public boolean requiresAuth(Request request) { + Request forThis = request.omitFirstInPath(); + return getResolver(forThis.getPath()).map(resolver -> resolver.requiresAuth(forThis)).orElse(true); } public static class Builder { diff --git a/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/FunctionalResolverWrapper.java b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/FunctionalResolverWrapper.java new file mode 100644 index 000000000..d9a867c5a --- /dev/null +++ b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/FunctionalResolverWrapper.java @@ -0,0 +1,44 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.web.resolver; + +import com.djrapitops.plan.delivery.web.resolver.request.Request; + +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Predicate; + +public class FunctionalResolverWrapper implements Resolver { + + private final Function> resolver; + private final Predicate accessCheck; + + public FunctionalResolverWrapper(Function> resolver, Predicate accessCheck) { + this.resolver = resolver; + this.accessCheck = accessCheck; + } + + @Override + public boolean canAccess(Request request) { + return accessCheck.test(request); + } + + @Override + public Optional resolve(Request request) { + return resolver.apply(request); + } +} diff --git a/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/ResponseBuilder.java b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/ResponseBuilder.java index 8faf0ca25..c2ae8c216 100644 --- a/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/ResponseBuilder.java +++ b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/ResponseBuilder.java @@ -66,6 +66,11 @@ public class ResponseBuilder { return this; } + protected ResponseBuilder removeHeader(String header) { + response.headers.remove(header); + return this; + } + /** * Utility method for building redirects. * @@ -103,7 +108,8 @@ public class ResponseBuilder { } } - return setContent(content.getBytes(charset)); + return setContent(content.getBytes(charset)) + .removeHeader("Accept-Ranges"); // Can compress } /** diff --git a/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/request/Request.java b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/request/Request.java index 537facc6d..681822e09 100644 --- a/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/request/Request.java +++ b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/request/Request.java @@ -34,25 +34,30 @@ public final class Request { private final URIQuery query; private final WebUser user; private final Map headers; + private final byte[] requestBody; /** * Constructor. * - * @param method HTTP method, GET, PUT, POST, etc - * @param path Requested path /example/target - * @param query Request parameters ?param=value etc - * @param user Web user doing the request (if authenticated) - * @param headers Request headers https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers + * @param method HTTP method, GET, PUT, POST, etc + * @param path Requested path /example/target + * @param query Request parameters ?param=value etc + * @param user Web user doing the request (if authenticated) + * @param headers Request headers https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers + * @param requestBody Raw body as bytes, if present */ - public Request(String method, URIPath path, URIQuery query, WebUser user, Map headers) { + public Request(String method, URIPath path, URIQuery query, WebUser user, Map headers, byte[] requestBody) { this.method = method; this.path = path; this.query = query; this.user = user; this.headers = headers; + this.requestBody = requestBody; } - // Special constructor that figures out URIPath and URIQuery from "/path/and?query=params" + /** + * Special constructor that figures out URIPath and URIQuery from "/path/and?query=params" and has no request body. + */ public Request(String method, String target, WebUser user, Map headers) { this.method = method; if (target.contains("?")) { @@ -65,6 +70,7 @@ public final class Request { } this.user = user; this.headers = headers; + this.requestBody = new byte[0]; } /** @@ -94,6 +100,15 @@ public final class Request { return query; } + /** + * Get the raw body, if present. + * + * @return byte[]. + */ + public byte[] getRequestBody() { + return requestBody; + } + /** * Get the user making the request. * @@ -114,7 +129,7 @@ public final class Request { } public Request omitFirstInPath() { - return new Request(method, path.omitFirst(), query, user, headers); + return new Request(method, path.omitFirst(), query, user, headers, requestBody); } @Override @@ -125,6 +140,7 @@ public final class Request { ", query=" + query + ", user=" + user + ", headers=" + headers + + ", body=" + requestBody.length + '}'; } -} \ No newline at end of file +} diff --git a/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/request/URIQuery.java b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/request/URIQuery.java index 9d0bbeb7a..4d906838c 100644 --- a/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/request/URIQuery.java +++ b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/request/URIQuery.java @@ -56,19 +56,35 @@ public final class URIQuery { } String[] keyAndValue = StringUtils.split(kv, "=", 2); if (keyAndValue.length >= 2) { - try { - parameters.put( - URLDecoder.decode(keyAndValue[0], StandardCharsets.UTF_8.name()), - URLDecoder.decode(keyAndValue[1], StandardCharsets.UTF_8.name()) - ); - } catch (UnsupportedEncodingException e) { - // If UTF-8 is unsupported, we have bigger problems - } + parseAndPutKeyValuePair(parameters, keyAndValue); + } else if (keyAndValue.length == 1) { + parseAndPutKeyEmptyValue(parameters, keyAndValue[0]); } } return parameters; } + private void parseAndPutKeyValuePair(Map parameters, String[] keyAndValue) { + try { + parameters.put( + URLDecoder.decode(keyAndValue[0], StandardCharsets.UTF_8.name()), + URLDecoder.decode(keyAndValue[1], StandardCharsets.UTF_8.name()) + ); + } catch (UnsupportedEncodingException e) { + // If UTF-8 is unsupported, we have bigger problems + } + } + + private void parseAndPutKeyEmptyValue(Map parameters, String s) { + try { + parameters.put( + URLDecoder.decode(s, StandardCharsets.UTF_8.name()), "" + ); + } catch (UnsupportedEncodingException e) { + // If UTF-8 is unsupported, we have bigger problems + } + } + /** * Obtain an URI parameter by key. * @@ -80,6 +96,8 @@ public final class URIQuery { } public String asString() { + if (byKey.isEmpty()) return ""; + StringBuilder builder = new StringBuilder("?"); int i = 0; int max = byKey.size(); @@ -97,8 +115,10 @@ public final class URIQuery { @Override public String toString() { + Map copyOfByKey = new HashMap<>(this.byKey); + copyOfByKey.remove("password"); return "URIQuery{" + - "byKey=" + byKey + + "byKey=" + copyOfByKey + '}'; } } diff --git a/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/request/WebUser.java b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/request/WebUser.java index ccaaa7eb2..743f833af 100644 --- a/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/request/WebUser.java +++ b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/request/WebUser.java @@ -16,29 +16,37 @@ */ package com.djrapitops.plan.delivery.web.resolver.request; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; +import java.util.*; public final class WebUser { private final String playerName; private final String username; + private final UUID playerUUID; private final Set permissions; public WebUser(String playerName) { this.playerName = playerName; + this.playerUUID = null; this.username = playerName; this.permissions = new HashSet<>(); } - public WebUser(String playerName, String username, Collection permissions) { + public WebUser(String playerName, UUID playerUUID, String username, Collection permissions) { this.playerName = playerName; + this.playerUUID = playerUUID; this.username = username; this.permissions = new HashSet<>(permissions); } + /** + * @deprecated WebUser now stores the UUID of the linked user + */ + @Deprecated + public WebUser(String playerName, String username, Collection permissions) { + this(playerName, null, username, permissions); + } + /** * @deprecated WebUser now has username and player name. */ @@ -60,6 +68,14 @@ public final class WebUser { return username; } + public Optional getUUID() { + return Optional.ofNullable(playerUUID); + } + + public Set getPermissions() { + return permissions; + } + @Override public String toString() { return "WebUser{" + diff --git a/Plan/api/src/main/java/com/djrapitops/plan/extension/extractor/ExtensionExtractor.java b/Plan/api/src/main/java/com/djrapitops/plan/extension/extractor/ExtensionExtractor.java index 76c29f06f..4dd2a3fe9 100644 --- a/Plan/api/src/main/java/com/djrapitops/plan/extension/extractor/ExtensionExtractor.java +++ b/Plan/api/src/main/java/com/djrapitops/plan/extension/extractor/ExtensionExtractor.java @@ -45,7 +45,6 @@ public final class ExtensionExtractor { private PluginInfo pluginInfo; private List tabInformation; private List invalidMethods; - private MethodAnnotations methodAnnotations; private Map methods; private Collection conditionalMethods; private Collection tabAnnotations; @@ -102,12 +101,9 @@ public final class ExtensionExtractor { * @deprecated No longer used anywhere, no-op. */ @Deprecated - public void extractAnnotationInformation() { - // no-op - } + public void extractAnnotationInformation() {/* no-op */} private void extractMethods() { - methodAnnotations = new MethodAnnotations(); methods = new EnumMap<>(ExtensionMethod.ParameterType.class); methods.put(ExtensionMethod.ParameterType.SERVER_NONE, new ExtensionMethods()); methods.put(ExtensionMethod.ParameterType.PLAYER_STRING, new ExtensionMethods()); @@ -132,55 +128,41 @@ public final class ExtensionExtractor { method.getAnnotation(BooleanProvider.class).ifPresent(annotation -> { validateMethod(method, annotation); methods.get(method.getParameterType()).addBooleanMethod(method); - methodAnnotations.put(method.getMethod(), BooleanProvider.class, annotation); }); method.getAnnotation(NumberProvider.class).ifPresent(annotation -> { validateMethod(method, annotation); methods.get(method.getParameterType()).addNumberMethod(method); - methodAnnotations.put(method.getMethod(), NumberProvider.class, annotation); }); method.getAnnotation(DoubleProvider.class).ifPresent(annotation -> { validateMethod(method, annotation); methods.get(method.getParameterType()).addDoubleMethod(method); - methodAnnotations.put(method.getMethod(), DoubleProvider.class, annotation); }); method.getAnnotation(PercentageProvider.class).ifPresent(annotation -> { validateMethod(method, annotation); methods.get(method.getParameterType()).addPercentageMethod(method); - methodAnnotations.put(method.getMethod(), PercentageProvider.class, annotation); }); method.getAnnotation(StringProvider.class).ifPresent(annotation -> { validateMethod(method, annotation); methods.get(method.getParameterType()).addStringMethod(method); - methodAnnotations.put(method.getMethod(), StringProvider.class, annotation); }); method.getAnnotation(TableProvider.class).ifPresent(annotation -> { validateMethod(method, annotation); methods.get(method.getParameterType()).addTableMethod(method); - methodAnnotations.put(method.getMethod(), TableProvider.class, annotation); }); method.getAnnotation(GroupProvider.class).ifPresent(annotation -> { validateMethod(method, annotation); methods.get(method.getParameterType()).addGroupMethod(method); - methodAnnotations.put(method.getMethod(), GroupProvider.class, annotation); }); method.getAnnotation(DataBuilderProvider.class).ifPresent(annotation -> { validateMethod(method, annotation); methods.get(method.getParameterType()).addDataBuilderMethod(method); - methodAnnotations.put(method.getMethod(), DataBuilderProvider.class, annotation); }); - method.getAnnotation(Conditional.class).ifPresent(annotation -> { - conditionalMethods.add(method.getMethod()); - methodAnnotations.put(method.getMethod(), Conditional.class, annotation); - }); - method.getAnnotation(Tab.class).ifPresent(annotation -> { - tabAnnotations.add(annotation); - methodAnnotations.put(method.getMethod(), Tab.class, annotation); - }); + method.getAnnotation(Conditional.class).ifPresent(annotation -> conditionalMethods.add(method.getMethod())); + method.getAnnotation(Tab.class).ifPresent(tabAnnotations::add); } - if (methodAnnotations.isEmpty()) { + if (methods.values().stream().allMatch(ExtensionMethods::isEmpty)) { throw new IllegalArgumentException(extensionName + " class had no methods annotated with a Provider annotation"); } @@ -440,10 +422,12 @@ public final class ExtensionExtractor { return tabInformation; } + /** + * @deprecated During refactoring MethodAnnotations was removed. Using {@link ExtensionExtractor#getMethods()} instead. + */ @Deprecated public MethodAnnotations getMethodAnnotations() { - if (methodAnnotations == null) extractMethods(); - return methodAnnotations; + return new MethodAnnotations(); } public Map getMethods() { @@ -455,4 +439,10 @@ public final class ExtensionExtractor { if (invalidMethods == null) extractInvalidMethods(); return invalidMethods; } + + // Visible for testing + Collection getConditionalMethods() { + if (conditionalMethods == null) extractMethods(); + return conditionalMethods; + } } diff --git a/Plan/api/src/main/java/com/djrapitops/plan/extension/extractor/ExtensionMethods.java b/Plan/api/src/main/java/com/djrapitops/plan/extension/extractor/ExtensionMethods.java index cfba74ba9..3cd0a5e20 100644 --- a/Plan/api/src/main/java/com/djrapitops/plan/extension/extractor/ExtensionMethods.java +++ b/Plan/api/src/main/java/com/djrapitops/plan/extension/extractor/ExtensionMethods.java @@ -142,4 +142,15 @@ public class ExtensionMethods { ", dataBuilderProviders=" + dataBuilderProviders + '}'; } + + public boolean isEmpty() { + return booleanProviders.isEmpty() + && numberProviders.isEmpty() + && doubleProviders.isEmpty() + && percentageProviders.isEmpty() + && stringProviders.isEmpty() + && tableProviders.isEmpty() + && groupProviders.isEmpty() + && dataBuilderProviders.isEmpty(); + } } diff --git a/Plan/api/src/main/java/com/djrapitops/plan/extension/icon/Family.java b/Plan/api/src/main/java/com/djrapitops/plan/extension/icon/Family.java index 709fa8cae..ef854197a 100644 --- a/Plan/api/src/main/java/com/djrapitops/plan/extension/icon/Family.java +++ b/Plan/api/src/main/java/com/djrapitops/plan/extension/icon/Family.java @@ -24,8 +24,17 @@ import java.util.Optional; * @author AuroraLS3 */ public enum Family { + /** + * 'fas' (solid) Font awesome family. + */ SOLID, + /** + * 'far' (regular) Font awesome family. + */ REGULAR, + /** + * 'fab' (brand) Font awesome family. + */ BRAND; public static Optional getByName(String name) { diff --git a/Plan/api/src/main/java/com/djrapitops/plan/extension/icon/Icon.java b/Plan/api/src/main/java/com/djrapitops/plan/extension/icon/Icon.java index 0785a1f09..cb0f23465 100644 --- a/Plan/api/src/main/java/com/djrapitops/plan/extension/icon/Icon.java +++ b/Plan/api/src/main/java/com/djrapitops/plan/extension/icon/Icon.java @@ -25,6 +25,9 @@ package com.djrapitops.plan.extension.icon; */ public class Icon { + // Implementation detail, set during icon storage to optimize relation inserts. + int id; + private Family type; private String name; private Color color; diff --git a/Plan/api/src/main/java/com/djrapitops/plan/extension/table/Table.java b/Plan/api/src/main/java/com/djrapitops/plan/extension/table/Table.java index 1aedeb7a7..a8f941345 100644 --- a/Plan/api/src/main/java/com/djrapitops/plan/extension/table/Table.java +++ b/Plan/api/src/main/java/com/djrapitops/plan/extension/table/Table.java @@ -48,6 +48,7 @@ public final class Table { private final String[] columns; private final Icon[] icons; + private final TableColumnFormat[] tableColumnFormats; private final List rows; @@ -56,6 +57,10 @@ public final class Table { columns = new String[5]; icons = new Icon[5]; + tableColumnFormats = new TableColumnFormat[]{ + TableColumnFormat.NONE, TableColumnFormat.NONE, TableColumnFormat.NONE, + TableColumnFormat.NONE, TableColumnFormat.NONE + }; rows = new ArrayList<>(); } @@ -91,6 +96,10 @@ public final class Table { return rows; } + public TableColumnFormat[] getTableColumnFormats() { + return tableColumnFormats; + } + /** * Factory for creating new {@link Table} objects. */ @@ -171,6 +180,61 @@ public final class Table { return column(4, columnName, icon); } + private Factory columnFormat(int index, TableColumnFormat tableColumnFormat) { + building.tableColumnFormats[index] = tableColumnFormat; + return this; + } + + /** + * Apply formatting to column one. + * + * @param tableColumnFormat Format to apply + * @return Factory. + */ + public Factory columnOneFormat(TableColumnFormat tableColumnFormat) { + return columnFormat(0, tableColumnFormat); + } + + /** + * Apply formatting to column two. + * + * @param tableColumnFormat Format to apply + * @return Factory. + */ + public Factory columnTwoFormat(TableColumnFormat tableColumnFormat) { + return columnFormat(1, tableColumnFormat); + } + + /** + * Apply formatting to column three. + * + * @param tableColumnFormat Format to apply + * @return Factory. + */ + public Factory columnThreeFormat(TableColumnFormat tableColumnFormat) { + return columnFormat(2, tableColumnFormat); + } + + /** + * Apply formatting to column four. + * + * @param tableColumnFormat Format to apply + * @return Factory. + */ + public Factory columnFourFormat(TableColumnFormat tableColumnFormat) { + return columnFormat(3, tableColumnFormat); + } + + /** + * Apply formatting to column five. + * + * @param tableColumnFormat Format to apply + * @return Factory. + */ + public Factory columnFiveFormat(TableColumnFormat tableColumnFormat) { + return columnFormat(4, tableColumnFormat); + } + /** * Add a row of values to the table. * diff --git a/Plan/api/src/main/java/com/djrapitops/plan/extension/table/TableColumnFormat.java b/Plan/api/src/main/java/com/djrapitops/plan/extension/table/TableColumnFormat.java new file mode 100644 index 000000000..637d4e4b6 --- /dev/null +++ b/Plan/api/src/main/java/com/djrapitops/plan/extension/table/TableColumnFormat.java @@ -0,0 +1,44 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.extension.table; + +public enum TableColumnFormat { + /** + * String variables to be formatted as links to player page. + */ + PLAYER_NAME, + /** + * String variables to be formatted as colored (using ยง character). + */ + CHAT_COLORED, + /** + * Number variables to be formatted as time amount (eg. 1h 30m 25s). + */ + TIME_MILLISECONDS, + /** + * Number epoch millisecond to be formatted as date without second indicator. + */ + DATE_YEAR, + /** + * Number epoch millisecond to be formatted as date with second indicator. + */ + DATE_SECOND, + /** + * Default formatting, no extra formatting applied. + */ + NONE +} diff --git a/Plan/api/src/test/java/com/djrapitops/plan/extension/extractor/ExtensionExtractorTest.java b/Plan/api/src/test/java/com/djrapitops/plan/extension/extractor/ExtensionExtractorTest.java index 860004788..f37ab2d49 100644 --- a/Plan/api/src/test/java/com/djrapitops/plan/extension/extractor/ExtensionExtractorTest.java +++ b/Plan/api/src/test/java/com/djrapitops/plan/extension/extractor/ExtensionExtractorTest.java @@ -265,7 +265,7 @@ class ExtensionExtractorTest { } ExtensionExtractor underTest = new ExtensionExtractor(new Extension()); - assertEquals("Extension.method did not have any associated Provider for Conditional.", assertThrows(IllegalArgumentException.class, underTest::validateAnnotations).getMessage()); + assertEquals("Extension class had no methods annotated with a Provider annotation", assertThrows(IllegalArgumentException.class, underTest::validateAnnotations).getMessage()); } @Test @@ -489,8 +489,13 @@ class ExtensionExtractorTest { String expected = Stream.of(extension.getClass().getMethod("method").getAnnotation(Conditional.class)) .map(Conditional::value).findFirst().orElseThrow(AssertionError::new); - String result = underTest.getMethodAnnotations().getAnnotations(Conditional.class).stream() - .map(Conditional::value).findFirst().orElseThrow(AssertionError::new); + String result = underTest.getConditionalMethods().stream() + .map(method -> new ExtensionMethod(extension, method)) + .map(extensionMethod -> extensionMethod.getAnnotation(Conditional.class)) + .filter(Optional::isPresent) + .map(Optional::get) + .map(Conditional::value) + .findFirst().orElseThrow(AssertionError::new); assertEquals(expected, result); } diff --git a/Plan/build.gradle b/Plan/build.gradle index 9a2dcb083..93060e812 100644 --- a/Plan/build.gradle +++ b/Plan/build.gradle @@ -1,19 +1,23 @@ +import java.nio.file.Files + // Aggregate Javadocs buildscript { - repositories { mavenCentral() } + repositories { + mavenCentral() + } dependencies { classpath 'com.netflix.nebula:gradle-aggregate-javadocs-plugin:3.0.+' } } plugins { - id "com.github.johnrengelman.shadow" version "7.0.0" + id "com.github.johnrengelman.shadow" version "7.1.2" apply false id "java" + id 'java-library' id "jacoco" id "checkstyle" - id "org.sonarqube" version "3.3" - // id "net.ltgt.apt" version "0.21" - // id "net.ltgt.apt-idea" version "0.21" + id "org.sonarqube" version "3.4.0.2513" + id 'fabric-loom' version '0.12.+' apply false } apply plugin: 'nebula-aggregate-javadocs' @@ -30,13 +34,12 @@ def determineBuildVersion = { def buildVersion = determineBuildVersion() allprojects { - wrapper.gradleVersion = "7.0.2" group "com.djrapitops" - version "5.4-SNAPSHOT" + version "5.5-SNAPSHOT" ext.majorVersion = '5' - ext.minorVersion = '4' + ext.minorVersion = '5' ext.buildVersion = buildVersion ext.fullVersion = project.ext.majorVersion + '.' + project.ext.minorVersion + ' build ' + project.ext.buildVersion @@ -52,70 +55,81 @@ subprojects { // Build plugins apply plugin: "com.github.johnrengelman.shadow" apply plugin: "java" + apply plugin: "java-library" apply plugin: "maven-publish" // Report plugins apply plugin: "checkstyle" apply plugin: "jacoco" - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + compileJava { + options.release = 11 + } ext { - daggerVersion = "2.37" - daggerCompilerVersion = "2.37" + daggerVersion = "2.43.2" - palVersion = "5.0.0" + palVersion = "5.1.0" bukkitVersion = "1.13.2-R0.1-SNAPSHOT" spigotVersion = "1.13.2-R0.1-SNAPSHOT" paperVersion = "1.13.2-R0.1-SNAPSHOT" - spongeVersion = "7.3.0" + spongeVersion = "8.1.0" nukkitVersion = "1.0-SNAPSHOT" bungeeVersion = "1.16-R0.4" velocityVersion = "3.0.0-SNAPSHOT" - redisBungeeVersion = "0.6.3" + redisBungeeVersion = "0.3.8-SNAPSHOT" commonsTextVersion = "1.9" - commonsCompressVersion = "1.20" + commonsCompressVersion = "1.21" + commonsCodecVersion = "1.15" + caffeineVersion = "3.1.1" + mysqlVersion = "8.0.30" + jettyVersion = "11.0.11" caffeineVersion = "2.9.2" - mysqlVersion = "8.0.25" - sqliteVersion = "3.36.0.1" - hikariVersion = "4.0.3" - slf4jVersion = "1.7.31" - geoIpVersion = "2.15.0" - gsonVersion = "2.8.7" - bstatsVersion = "2.2.1" - placeholderapiVersion = "2.10.9" + mysqlVersion = "8.0.26" + sqliteVersion = "3.39.2.1" + hikariVersion = "5.0.1" + slf4jVersion = "2.0.0" + geoIpVersion = "3.0.1" + gsonVersion = "2.9.1" + dependencyDownloadVersion = "1.2.1" + + bstatsVersion = "3.0.0" + placeholderapiVersion = "2.11.2" nkPlaceholderapiVersion = "1.4-SNAPSHOT" + + junitVersion = "5.9.0" + mockitoVersion = "4.8.0" + testContainersVersion = "1.17.3" + swaggerVersion = "2.2.2" } repositories { mavenCentral() + google() maven { url = "https://hub.spigotmc.org/nexus/content/repositories/snapshots/" } // Spigot maven { url = "https://papermc.io/repo/repository/maven-public/" } // Paper maven { url = "https://repo.spongepowered.org/repository/maven-public/" } // Sponge maven { url = "https://oss.sonatype.org/content/repositories/snapshots" } // BungeeCord maven { url = "https://repo.md-5.net/content/repositories/snapshots/" } // RedisBungee maven { url = "https://repo.velocitypowered.com/snapshots/" } // Velocity - maven { url = "https://repo.opencollab.dev/maven-snapshots/" } // Nukkit snapshot - maven { url = "https://repo.opencollab.dev/maven-releases/" } // Nukkit release - maven { url = "https://repo.codemc.org/repository/maven-public" } // bStats maven { url = "https://repo.playeranalytics.net/releases" } // Plan } dependencies { // Dependency Injection used across the project - implementation "com.google.dagger:dagger:$daggerVersion" - annotationProcessor "com.google.dagger:dagger-compiler:$daggerCompilerVersion" - testAnnotationProcessor "com.google.dagger:dagger-compiler:$daggerCompilerVersion" + shadow "com.google.dagger:dagger:$daggerVersion" + annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion" + testImplementation "com.google.dagger:dagger:$daggerVersion" + testAnnotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion" // Test Tooling Dependencies - testImplementation 'org.junit.jupiter:junit-jupiter:5.7.2' // JUnit 5 - testImplementation "org.mockito:mockito-core:3.11.2" // Mockito Core - testImplementation "org.mockito:mockito-junit-jupiter:3.11.2" // Mockito JUnit 5 Extension - testImplementation "org.seleniumhq.selenium:selenium-java:3.141.59" // Selenium (Browser tests) - testImplementation "com.jayway.awaitility:awaitility:1.7.0" // Awaitility (Concurrent wait conditions) + testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion" // JUnit 5 + testImplementation "org.mockito:mockito-core:$mockitoVersion" // Mockito Core + testImplementation "org.mockito:mockito-junit-jupiter:$mockitoVersion" // Mockito JUnit 5 Extension + testImplementation "com.jayway.awaitility:awaitility:1.7.0" + // Awaitility (Concurrent wait conditions) // Testing dependencies required by Plan testImplementation "org.xerial:sqlite-jdbc:$sqliteVersion" // SQLite @@ -123,7 +137,11 @@ subprojects { } configurations { - testArtifacts.extendsFrom testRuntimeOnly + // Include shadowed dependencies in compile classpath of dependent modules + api.extendsFrom shadow + + testArtifacts.extendsFrom testRuntimeOnly // Test classes available to other modules + testImplementation.extendsFrom shadow // Include shadowed dependencies in test classpath } // Test classes available to other modules task testJar(type: Jar) { @@ -143,13 +161,13 @@ subprojects { } checkstyle { - toolVersion "8.33" + toolVersion "8.44" getConfigDirectory().set file("$rootProject.projectDir/config/checkstyle") } jacocoTestReport { reports { - xml.enabled true + xml.required = true // xml.destination file("${buildDir}/jacoco/report.xml") } } @@ -163,6 +181,47 @@ sonarqube { properties { property "sonar.projectName", "Player Analytics" property "sonar.projectKey", "com.djrapitops:Plan" - property "sonar.coverage.jacoco.xmlReportPaths", "**/build/report/jacoco/test/jacocoTestReport.xml" + property "sonar.coverage.jacoco.xmlReportPaths", "build/reports/jacoco/test/jacocoTestReport.xml" } } + +abstract class PrintSnapshotVersionTask extends DefaultTask { + @TaskAction + def print() { + def versionsDir = project.file("$project.buildDir/versions") + def textFile = project.file("$project.buildDir/versions/snapshot.txt") + versionsDir.mkdirs() + Files.deleteIfExists(textFile.toPath()) + textFile.createNewFile() + textFile << "$project.version" + } +} + +abstract class PrintJarNameVersionTask extends DefaultTask { + @TaskAction + def print() { + def versionsDir = project.file("$project.buildDir/versions") + def textFile = project.file("$project.buildDir/versions/jar.txt") + versionsDir.mkdirs() + Files.deleteIfExists(textFile.toPath()) + textFile.createNewFile() + textFile << "$project.majorVersion.$project.minorVersion-build-$project.buildVersion" + } +} + +abstract class PrintHumanReadableVersionTask extends DefaultTask { + @TaskAction + def print() { + def versionsDir = project.file("$project.buildDir/versions") + def textFile = project.file("$project.buildDir/versions/human.txt") + versionsDir.mkdirs() + Files.deleteIfExists(textFile.toPath()) + textFile.createNewFile() + textFile << "$project.fullVersion" + } +} + +// Create a task using the task type +tasks.register('snapshotVersion', PrintSnapshotVersionTask) +tasks.register('jarNameVersion', PrintJarNameVersionTask) +tasks.register('humanReadableVersion', PrintHumanReadableVersionTask) diff --git a/Plan/bukkit/build.gradle b/Plan/bukkit/build.gradle index bf4046435..d48f29c88 100644 --- a/Plan/bukkit/build.gradle +++ b/Plan/bukkit/build.gradle @@ -5,21 +5,22 @@ repositories { } dependencies { - compileOnly project(":common") - implementation project(path: ":common", configuration: 'shadow') - compileOnly project(":api") + implementation project(":api") + implementation project(":common") - implementation "net.playeranalytics:platform-abstraction-layer-bukkit:$palVersion" - implementation "org.bstats:bstats-bukkit:$bstatsVersion" + shadow "net.playeranalytics:platform-abstraction-layer-api:$palVersion" + shadow "net.playeranalytics:platform-abstraction-layer-bukkit:$palVersion" + shadow "org.bstats:bstats-bukkit:$bstatsVersion" compileOnly "me.clip:placeholderapi:$placeholderapiVersion" compileOnly "com.destroystokyo.paper:paper-api:$paperVersion" - testImplementation "com.destroystokyo.paper:paper-api:$paperVersion" + testImplementation "com.destroystokyo.paper:paper-api:$paperVersion" testImplementation project(path: ":common", configuration: 'testArtifacts') } shadowJar { - relocate 'org.bstats', 'com.djrapitops.plan.utilities.metrics' - relocate 'org.slf4j', 'plan.org.slf4j' + configurations = [project.configurations.shadow] + + relocate 'org.bstats', 'net.playeranalytics.bstats.utilities.metrics' } \ No newline at end of file diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/BukkitServerShutdownSave.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/BukkitServerShutdownSave.java index 890c0fb16..1cb682b89 100644 --- a/Plan/bukkit/src/main/java/com/djrapitops/plan/BukkitServerShutdownSave.java +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/BukkitServerShutdownSave.java @@ -35,6 +35,8 @@ import javax.inject.Singleton; @Singleton public class BukkitServerShutdownSave extends ServerShutdownSave { + private static final String IS_STOPPED = "isStopped"; + @Inject public BukkitServerShutdownSave( Locale locale, @@ -57,7 +59,7 @@ public class BukkitServerShutdownSave extends ServerShutdownSave { Class minecraftServerClass = Reflection.getMinecraftClass("MinecraftServer"); Object minecraftServer = Reflection.getField(minecraftServerClass, "SERVER", minecraftServerClass).get(null); - return Reflection.getField(minecraftServerClass, "isStopped", boolean.class).get(minecraftServer); + return Reflection.getField(minecraftServerClass, IS_STOPPED, boolean.class).get(minecraftServer); } catch (Exception | NoClassDefFoundError | NoSuchFieldError e) { return false; } @@ -71,7 +73,7 @@ public class BukkitServerShutdownSave extends ServerShutdownSave { Class craftServerClass = Reflection.getCraftBukkitClass("CraftServer"); Object minecraftServer = Reflection.getField(craftServerClass, "console", minecraftServerClass).get(Bukkit.getServer()); - return (Boolean) minecraftServerClass.getMethod("isStopped").invoke(minecraftServer); + return (Boolean) minecraftServerClass.getMethod(IS_STOPPED).invoke(minecraftServer); } catch (Exception | NoClassDefFoundError | NoSuchFieldError | NoSuchMethodError e) { return false; } @@ -85,7 +87,7 @@ public class BukkitServerShutdownSave extends ServerShutdownSave { Class craftServerClass = Reflection.getCraftBukkitClass("CraftServer"); Object minecraftServer = Reflection.getField(craftServerClass, "console", minecraftServerClass).get(Bukkit.getServer()); - return (Boolean) minecraftServerClass.getMethod("isStopped").invoke(minecraftServer); + return (Boolean) minecraftServerClass.getMethod(IS_STOPPED).invoke(minecraftServer); } catch (Exception | NoClassDefFoundError | NoSuchFieldError | NoSuchMethodError e) { return false; } diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/Plan.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/Plan.java index 3a733add0..41a8224dc 100644 --- a/Plan/bukkit/src/main/java/com/djrapitops/plan/Plan.java +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/Plan.java @@ -25,6 +25,8 @@ import com.djrapitops.plan.gathering.ServerShutdownSave; import com.djrapitops.plan.settings.locale.Locale; import com.djrapitops.plan.settings.locale.lang.PluginLang; import com.djrapitops.plan.settings.theme.PlanColorScheme; +import com.djrapitops.plan.utilities.java.ThreadContextClassLoaderSwap; +import com.djrapitops.plan.utilities.logging.ErrorLogger; import net.playeranalytics.plugin.BukkitPlatformLayer; import net.playeranalytics.plugin.PlatformAbstractionLayer; import net.playeranalytics.plugin.scheduling.RunnableFactory; @@ -55,6 +57,7 @@ public class Plan extends JavaPlugin implements PlanPlugin { private PluginLogger pluginLogger; private RunnableFactory runnableFactory; private PlatformAbstractionLayer abstractionLayer; + private ErrorLogger errorLogger; @Override public void onLoad() { @@ -71,7 +74,8 @@ public class Plan extends JavaPlugin implements PlanPlugin { .server(getServer()) .build(); try { - system = component.system(); + system = ThreadContextClassLoaderSwap.performOperation(getClass().getClassLoader(), component::system); + errorLogger = component.errorLogger(); serverShutdownSave = component.serverShutdownSave(); locale = system.getLocaleSystem().getLocale(); system.enable(); @@ -170,42 +174,42 @@ public class Plan extends JavaPlugin implements PlanPlugin { pluginLogger.warn("Attempted to register '" + name + "'-command, but it is not in plugin.yml!"); continue; } - registering.setExecutor(new BukkitCommand(runnableFactory, system.getErrorLogger(), command)); + registering.setExecutor(new BukkitCommand(runnableFactory, errorLogger, command)); } } /** - * @deprecated Deprecated due to use of APF Config + * @deprecated Deprecated due to use of custom Config */ @Override - @Deprecated + @Deprecated(since = "Config.java (2018)") public void reloadConfig() { throw new IllegalStateException("This method should be used on this plugin. Use onReload() instead"); } /** - * @deprecated Deprecated due to use of APF Config + * @deprecated Deprecated due to use of custom Config */ @Override - @Deprecated + @Deprecated(since = "Config.java (2018)") public FileConfiguration getConfig() { throw new IllegalStateException("This method should be used on this plugin. Use getMainConfig() instead"); } /** - * @deprecated Deprecated due to use of APF Config + * @deprecated Deprecated due to use of custom Config */ @Override - @Deprecated + @Deprecated(since = "Config.java (2018)") public void saveConfig() { throw new IllegalStateException("This method should be used on this plugin. Use getMainConfig().save() instead"); } /** - * @deprecated Deprecated due to use of APF Config + * @deprecated Deprecated due to use of custom Config */ @Override - @Deprecated + @Deprecated(since = "Config.java (2018)") public void saveDefaultConfig() { throw new IllegalStateException("This method should be used on this plugin."); } @@ -214,4 +218,4 @@ public class Plan extends JavaPlugin implements PlanPlugin { public PlanSystem getSystem() { return system; } -} \ No newline at end of file +} diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/PlanBukkitComponent.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/PlanBukkitComponent.java index fbb03f69a..735ab1e49 100644 --- a/Plan/bukkit/src/main/java/com/djrapitops/plan/PlanBukkitComponent.java +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/PlanBukkitComponent.java @@ -24,6 +24,7 @@ import com.djrapitops.plan.modules.bukkit.BukkitPlanModule; import com.djrapitops.plan.modules.bukkit.BukkitServerPropertiesModule; import com.djrapitops.plan.modules.bukkit.BukkitSuperClassBindingModule; import com.djrapitops.plan.modules.bukkit.BukkitTaskModule; +import com.djrapitops.plan.utilities.logging.ErrorLogger; import dagger.BindsInstance; import dagger.Component; import net.playeranalytics.plugin.PlatformAbstractionLayer; @@ -59,6 +60,8 @@ public interface PlanBukkitComponent { ServerShutdownSave serverShutdownSave(); + ErrorLogger errorLogger(); + @Component.Builder interface Builder { diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/PlaceholderCacheRefreshTask.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/PlaceholderCacheRefreshTask.java new file mode 100644 index 000000000..ecb8f5bbb --- /dev/null +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/PlaceholderCacheRefreshTask.java @@ -0,0 +1,66 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.addons.placeholderapi; + +import com.djrapitops.plan.TaskSystem; +import com.djrapitops.plan.settings.config.PlanConfig; +import me.clip.placeholderapi.PlaceholderAPI; +import net.playeranalytics.plugin.scheduling.RunnableFactory; +import net.playeranalytics.plugin.scheduling.TimeAmount; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.List; +import java.util.concurrent.TimeUnit; + +@Singleton +public class PlaceholderCacheRefreshTask extends TaskSystem.Task { + + private final PlanConfig config; + + @Inject + public PlaceholderCacheRefreshTask(PlanConfig config) { + this.config = config; + } + + @Override + public void register(RunnableFactory runnableFactory) { + runnableFactory.create(this).runTaskTimer( + TimeAmount.toTicks(60, TimeUnit.SECONDS), + TimeAmount.toTicks(15, TimeUnit.SECONDS) + ); + } + + @Override + public void run() { + if (config.getNode("Plugins.PlaceholderAPI").isEmpty()) { + cancel(); // Cancel the task and don't do anything if PlaceholderAPI is not installed. + return; + } + List placeholders = config.getStringList("Plugins.PlaceholderAPI.Load_these_placeholders_on_join"); + if (placeholders.isEmpty() || placeholders.size() == 1 && placeholders.contains("%plan_server_uuid%")) { + // Don't do anything if using default settings or placeholder api is not installed + return; + } + + for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { + PlaceholderAPI.setPlaceholders(onlinePlayer, placeholders.toString()); + } + } +} diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/PlanPlaceholderExtension.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/PlanPlaceholderExtension.java index 170a207cc..5a8c4da63 100644 --- a/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/PlanPlaceholderExtension.java +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/PlanPlaceholderExtension.java @@ -28,9 +28,7 @@ import me.clip.placeholderapi.PlaceholderAPIPlugin; import me.clip.placeholderapi.expansion.PlaceholderExpansion; import org.bukkit.OfflinePlayer; -import java.util.Collections; -import java.util.Set; -import java.util.UUID; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; @@ -101,7 +99,7 @@ public class PlanPlaceholderExtension extends PlaceholderExpansion { private String getPlaceholderValue(String params, UUID uuid) { try { - String value = placeholders.onPlaceholderRequest(uuid, params, Collections.emptyList()); + String value = placeholders.onPlaceholderRequest(uuid, parseRequest(params), parseParameters(params)); if ("true".equals(value)) { //hack value = PlaceholderAPIPlugin.booleanTrue(); @@ -116,6 +114,23 @@ public class PlanPlaceholderExtension extends PlaceholderExpansion { } } + private String parseRequest(String params) { + return params.split(":")[0]; + } + + private List parseParameters(String params) { + List parameters = new ArrayList<>(); + boolean first = true; + for (String parameter : params.split(":")) { + if (first) { + first = false; + } else { + parameters.add(parameter); + } + } + return parameters; + } + private String getCached(String params, UUID uuid) { String key = params + "-" + uuid; diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/commands/use/BukkitCMDSender.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/commands/use/BukkitCMDSender.java index 36f869068..7d7ed1bb8 100644 --- a/Plan/bukkit/src/main/java/com/djrapitops/plan/commands/use/BukkitCMDSender.java +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/commands/use/BukkitCMDSender.java @@ -24,15 +24,33 @@ import java.util.UUID; public class BukkitCMDSender implements CMDSender { + private static Boolean hasChatAPI = null; + final CommandSender sender; public BukkitCMDSender(CommandSender sender) { this.sender = sender; } + protected static synchronized boolean hasBungeeChatAPI() { + if (hasChatAPI == null) { + try { + Class.forName("net.md_5.bungee.api.chat.ComponentBuilder"); + hasChatAPI = true; + } catch (ClassNotFoundException e) { + hasChatAPI = false; + } + } + return hasChatAPI; + } + @Override public MessageBuilder buildMessage() { - return new BukkitPartBuilder(this); + if (hasBungeeChatAPI()) { + return new BukkitPartBuilder(this); + } else { + return new ConsoleMessageBuilder(sender::sendMessage); + } } @Override diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/commands/use/BukkitPlayerCMDSender.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/commands/use/BukkitPlayerCMDSender.java index 892e0ad4b..a6646f0a0 100644 --- a/Plan/bukkit/src/main/java/com/djrapitops/plan/commands/use/BukkitPlayerCMDSender.java +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/commands/use/BukkitPlayerCMDSender.java @@ -53,7 +53,7 @@ public class BukkitPlayerCMDSender extends BukkitCMDSender { @Override public boolean supportsChatEvents() { - return true; + return hasBungeeChatAPI(); } @Override diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/domain/BukkitPlayerData.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/domain/BukkitPlayerData.java new file mode 100644 index 000000000..c7ecd2fc4 --- /dev/null +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/domain/BukkitPlayerData.java @@ -0,0 +1,85 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.gathering.domain; + +import org.bukkit.entity.Player; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.Optional; +import java.util.UUID; + +public class BukkitPlayerData implements PlatformPlayerData { + + private final Player player; + private final String joinAddress; + + public BukkitPlayerData(Player player, String joinAddress) { + this.player = player; + this.joinAddress = joinAddress; + } + + @Override + public UUID getUUID() { + return player.getUniqueId(); + } + + @Override + public String getName() { + return player.getName(); + } + + @Override + public Optional getDisplayName() { + return Optional.of(player.getDisplayName()); + } + + @Override + public Optional isBanned() { + return Optional.of(player.isBanned()); + } + + @Override + public Optional isOperator() { + return Optional.of(player.isOp()); + } + + @Override + public Optional getJoinAddress() { + return Optional.ofNullable(joinAddress); + } + + @Override + public Optional getCurrentWorld() { + return Optional.of(player.getWorld().getName()); + } + + @Override + public Optional getCurrentGameMode() { + return Optional.ofNullable(player.getGameMode()).map(Enum::name); + } + + @Override + public Optional getRegisterDate() { + return Optional.of(player.getFirstPlayed()); + } + + @Override + public Optional getIPAddress() { + return Optional.ofNullable(player.getAddress()).map(InetSocketAddress::getAddress); + } +} diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/BukkitAFKListener.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/BukkitAFKListener.java index 7e46c1f57..ba9013897 100644 --- a/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/BukkitAFKListener.java +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/BukkitAFKListener.java @@ -28,9 +28,9 @@ import org.bukkit.event.Listener; import org.bukkit.event.player.*; import javax.inject.Inject; -import java.util.HashMap; import java.util.Map; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; /** * Listener that keeps track of actions that are not considered being AFK. @@ -51,7 +51,7 @@ public class BukkitAFKListener implements Listener { @Inject public BukkitAFKListener(PlanConfig config, ErrorLogger errorLogger) { this.errorLogger = errorLogger; - this.ignorePermissionInfo = new HashMap<>(); + this.ignorePermissionInfo = new ConcurrentHashMap<>(); BukkitAFKListener.assignAFKTracker(config); } diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/ChatListener.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/ChatListener.java index 0f393fbec..7cff53386 100644 --- a/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/ChatListener.java +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/ChatListener.java @@ -20,7 +20,7 @@ import com.djrapitops.plan.delivery.domain.Nickname; import com.djrapitops.plan.gathering.cache.NicknameCache; import com.djrapitops.plan.identification.ServerInfo; import com.djrapitops.plan.storage.database.DBSystem; -import com.djrapitops.plan.storage.database.transactions.events.NicknameStoreTransaction; +import com.djrapitops.plan.storage.database.transactions.events.StoreNicknameTransaction; import com.djrapitops.plan.utilities.logging.ErrorContext; import com.djrapitops.plan.utilities.logging.ErrorLogger; import org.bukkit.entity.Player; @@ -76,7 +76,7 @@ public class ChatListener implements Listener { UUID uuid = player.getUniqueId(); String displayName = player.getDisplayName(); - dbSystem.getDatabase().executeTransaction(new NicknameStoreTransaction( + dbSystem.getDatabase().executeTransaction(new StoreNicknameTransaction( uuid, new Nickname(displayName, time, serverInfo.getServerUUID()), (playerUUID, name) -> nicknameCache.getDisplayName(playerUUID).map(name::equals).orElse(false) )); diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/DeathEventListener.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/DeathEventListener.java index c5d053b37..72dfa8b9f 100644 --- a/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/DeathEventListener.java +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/DeathEventListener.java @@ -20,6 +20,8 @@ import com.djrapitops.plan.delivery.formatting.EntityNameFormatter; import com.djrapitops.plan.delivery.formatting.ItemNameFormatter; import com.djrapitops.plan.gathering.cache.SessionCache; import com.djrapitops.plan.gathering.domain.ActiveSession; +import com.djrapitops.plan.gathering.domain.PlayerKill; +import com.djrapitops.plan.identification.ServerInfo; import com.djrapitops.plan.processing.Processing; import com.djrapitops.plan.processing.processors.player.MobKillProcessor; import com.djrapitops.plan.processing.processors.player.PlayerKillProcessor; @@ -45,14 +47,17 @@ import java.util.Optional; */ public class DeathEventListener implements Listener { + private final ServerInfo serverInfo; private final Processing processing; private final ErrorLogger errorLogger; @Inject public DeathEventListener( + ServerInfo serverInfo, Processing processing, ErrorLogger errorLogger ) { + this.serverInfo = serverInfo; this.processing = processing; this.errorLogger = errorLogger; } @@ -69,13 +74,13 @@ public class DeathEventListener implements Listener { try { Optional foundKiller = findKiller(dead); - if (!foundKiller.isPresent()) { + if (foundKiller.isEmpty()) { return; } Player killer = foundKiller.get(); Runnable processor = dead instanceof Player - ? new PlayerKillProcessor(killer.getUniqueId(), time, dead.getUniqueId(), findWeapon(dead)) + ? new PlayerKillProcessor(getKiller(killer), getVictim((Player) dead), serverInfo.getServerIdentifier(), findWeapon(dead), time) : new MobKillProcessor(killer.getUniqueId()); processing.submitCritical(processor); } catch (Exception e) { @@ -83,6 +88,14 @@ public class DeathEventListener implements Listener { } } + private PlayerKill.Killer getKiller(Player killer) { + return new PlayerKill.Killer(killer.getUniqueId(), killer.getName()); + } + + private PlayerKill.Victim getVictim(Player victim) { + return new PlayerKill.Victim(victim.getUniqueId(), victim.getName()); + } + public Optional findKiller(Entity dead) { EntityDamageEvent entityDamageEvent = dead.getLastDamageCause(); if (!(entityDamageEvent instanceof EntityDamageByEntityEvent)) { diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/GameModeChangeListener.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/GameModeChangeListener.java index 0c5d18cf5..dca3d1b4a 100644 --- a/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/GameModeChangeListener.java +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/GameModeChangeListener.java @@ -21,7 +21,7 @@ import com.djrapitops.plan.gathering.domain.ActiveSession; import com.djrapitops.plan.identification.ServerInfo; import com.djrapitops.plan.settings.config.WorldAliasSettings; import com.djrapitops.plan.storage.database.DBSystem; -import com.djrapitops.plan.storage.database.transactions.events.WorldNameStoreTransaction; +import com.djrapitops.plan.storage.database.transactions.events.StoreWorldNameTransaction; import com.djrapitops.plan.utilities.logging.ErrorContext; import com.djrapitops.plan.utilities.logging.ErrorLogger; import org.bukkit.entity.Player; @@ -78,7 +78,7 @@ public class GameModeChangeListener implements Listener { String gameMode = event.getNewGameMode().name(); String worldName = player.getWorld().getName(); - dbSystem.getDatabase().executeTransaction(new WorldNameStoreTransaction(serverInfo.getServerUUID(), worldName)); + dbSystem.getDatabase().executeTransaction(new StoreWorldNameTransaction(serverInfo.getServerUUID(), worldName)); worldAliasSettings.addWorld(worldName); Optional cachedSession = SessionCache.getCachedSession(uuid); diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/PlayerOnlineListener.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/PlayerOnlineListener.java index f904c4320..e36941eaf 100644 --- a/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/PlayerOnlineListener.java +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/PlayerOnlineListener.java @@ -16,29 +16,20 @@ */ package com.djrapitops.plan.gathering.listeners.bukkit; -import com.djrapitops.plan.delivery.domain.Nickname; -import com.djrapitops.plan.delivery.domain.PlayerName; -import com.djrapitops.plan.delivery.domain.ServerName; -import com.djrapitops.plan.delivery.export.Exporter; -import com.djrapitops.plan.extension.CallEvents; -import com.djrapitops.plan.extension.ExtensionSvc; -import com.djrapitops.plan.gathering.cache.NicknameCache; -import com.djrapitops.plan.gathering.cache.SessionCache; -import com.djrapitops.plan.gathering.domain.ActiveSession; -import com.djrapitops.plan.gathering.geolocation.GeolocationCache; +import com.djrapitops.plan.gathering.cache.JoinAddressCache; +import com.djrapitops.plan.gathering.domain.BukkitPlayerData; +import com.djrapitops.plan.gathering.domain.event.PlayerJoin; +import com.djrapitops.plan.gathering.domain.event.PlayerLeave; +import com.djrapitops.plan.gathering.events.PlayerJoinEventConsumer; +import com.djrapitops.plan.gathering.events.PlayerLeaveEventConsumer; import com.djrapitops.plan.gathering.listeners.Status; import com.djrapitops.plan.identification.ServerInfo; import com.djrapitops.plan.identification.ServerUUID; -import com.djrapitops.plan.processing.Processing; -import com.djrapitops.plan.settings.config.PlanConfig; -import com.djrapitops.plan.settings.config.paths.DataGatheringSettings; -import com.djrapitops.plan.settings.config.paths.ExportSettings; import com.djrapitops.plan.storage.database.DBSystem; -import com.djrapitops.plan.storage.database.Database; -import com.djrapitops.plan.storage.database.transactions.events.*; +import com.djrapitops.plan.storage.database.transactions.events.BanStatusTransaction; +import com.djrapitops.plan.storage.database.transactions.events.KickStoreTransaction; import com.djrapitops.plan.utilities.logging.ErrorContext; import com.djrapitops.plan.utilities.logging.ErrorLogger; -import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; @@ -48,11 +39,7 @@ import org.bukkit.event.player.PlayerLoginEvent; import org.bukkit.event.player.PlayerQuitEvent; import javax.inject.Inject; -import java.net.InetAddress; -import java.util.HashMap; -import java.util.Map; import java.util.UUID; -import java.util.function.Supplier; /** * Event Listener for PlayerJoin, PlayerQuit and PlayerKickEvents. @@ -61,61 +48,47 @@ import java.util.function.Supplier; */ public class PlayerOnlineListener implements Listener { - private final PlanConfig config; - private final Processing processing; + private final PlayerJoinEventConsumer playerJoinEventConsumer; + private final PlayerLeaveEventConsumer playerLeaveEventConsumer; + private final JoinAddressCache joinAddressCache; + private final ServerInfo serverInfo; private final DBSystem dbSystem; - private final ExtensionSvc extensionService; - private final Exporter exporter; - private final GeolocationCache geolocationCache; - private final NicknameCache nicknameCache; - private final SessionCache sessionCache; private final ErrorLogger errorLogger; private final Status status; - private final Map joinAddresses; - @Inject public PlayerOnlineListener( - PlanConfig config, - Processing processing, + PlayerJoinEventConsumer playerJoinEventConsumer, + PlayerLeaveEventConsumer playerLeaveEventConsumer, + JoinAddressCache joinAddressCache, ServerInfo serverInfo, DBSystem dbSystem, - ExtensionSvc extensionService, - Exporter exporter, - GeolocationCache geolocationCache, - NicknameCache nicknameCache, - SessionCache sessionCache, Status status, ErrorLogger errorLogger ) { - this.config = config; - this.processing = processing; + this.playerJoinEventConsumer = playerJoinEventConsumer; + this.playerLeaveEventConsumer = playerLeaveEventConsumer; + this.joinAddressCache = joinAddressCache; this.serverInfo = serverInfo; this.dbSystem = dbSystem; - this.extensionService = extensionService; - this.exporter = exporter; - this.geolocationCache = geolocationCache; - this.nicknameCache = nicknameCache; - this.sessionCache = sessionCache; this.status = status; this.errorLogger = errorLogger; - - joinAddresses = new HashMap<>(); } @EventHandler(priority = EventPriority.MONITOR) public void onPlayerLogin(PlayerLoginEvent event) { try { - PlayerLoginEvent.Result result = event.getResult(); UUID playerUUID = event.getPlayer().getUniqueId(); ServerUUID serverUUID = serverInfo.getServerUUID(); - boolean banned = result == PlayerLoginEvent.Result.KICK_BANNED; + boolean banned = PlayerLoginEvent.Result.KICK_BANNED == event.getResult(); + String joinAddress = event.getHostname(); if (!joinAddress.isEmpty()) { - joinAddresses.put(playerUUID, joinAddress.substring(0, joinAddress.lastIndexOf(':'))); + joinAddress = joinAddress.substring(0, joinAddress.lastIndexOf(':')); + joinAddressCache.put(playerUUID, joinAddress); } - dbSystem.getDatabase().executeTransaction(new BanStatusTransaction(playerUUID, serverUUID, () -> banned)); + dbSystem.getDatabase().executeTransaction(new BanStatusTransaction(playerUUID, serverUUID, banned)); } catch (Exception e) { errorLogger.error(e, ErrorContext.builder().related(event, event.getResult()).build()); } @@ -156,64 +129,28 @@ public class PlayerOnlineListener implements Listener { } private void actOnJoinEvent(PlayerJoinEvent event) { - Player player = event.getPlayer(); - - UUID playerUUID = player.getUniqueId(); - ServerUUID serverUUID = serverInfo.getServerUUID(); long time = System.currentTimeMillis(); - + UUID playerUUID = event.getPlayer().getUniqueId(); BukkitAFKListener.afkTracker.performedAction(playerUUID, time); - String world = player.getWorld().getName(); - String gm = player.getGameMode().name(); - - Database database = dbSystem.getDatabase(); - database.executeTransaction(new WorldNameStoreTransaction(serverUUID, world)); - - InetAddress address = player.getAddress().getAddress(); - Supplier getHostName = () -> getHostname(player); - - String playerName = player.getName(); - String displayName = player.getDisplayName(); - - boolean gatheringGeolocations = config.isTrue(DataGatheringSettings.GEOLOCATIONS); - if (gatheringGeolocations) { - database.executeTransaction( - new GeoInfoStoreTransaction(playerUUID, address, time, geolocationCache::getCountry) - ); - } - - database.executeTransaction(new PlayerServerRegisterTransaction(playerUUID, - player::getFirstPlayed, playerName, serverUUID, getHostName)); - database.executeTransaction(new OperatorStatusTransaction(playerUUID, serverUUID, player.isOp())); - - ActiveSession session = new ActiveSession(playerUUID, serverUUID, time, world, gm); - session.getExtraData().put(PlayerName.class, new PlayerName(playerName)); - session.getExtraData().put(ServerName.class, new ServerName(serverInfo.getServer().getIdentifiableName())); - sessionCache.cacheSession(playerUUID, session) - .ifPresent(previousSession -> database.executeTransaction(new SessionEndTransaction(previousSession))); - - database.executeTransaction(new NicknameStoreTransaction( - playerUUID, new Nickname(displayName, time, serverUUID), - (uuid, name) -> nicknameCache.getDisplayName(playerUUID).map(name::equals).orElse(false) - )); - - processing.submitNonCritical(() -> extensionService.updatePlayerValues(playerUUID, playerName, CallEvents.PLAYER_JOIN)); - if (config.isTrue(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE)) { - processing.submitNonCritical(() -> exporter.exportPlayerPage(playerUUID, playerName)); - } - } - - private String getHostname(Player player) { - return joinAddresses.get(player.getUniqueId()); + playerJoinEventConsumer.onJoinGameServer(PlayerJoin.builder() + .server(serverInfo.getServer()) + .player(new BukkitPlayerData(event.getPlayer(), joinAddressCache.getNullableString(playerUUID))) + .time(time) + .build()); } @EventHandler(priority = EventPriority.NORMAL) public void beforePlayerQuit(PlayerQuitEvent event) { - Player player = event.getPlayer(); - UUID playerUUID = player.getUniqueId(); - String playerName = player.getName(); - processing.submitNonCritical(() -> extensionService.updatePlayerValues(playerUUID, playerName, CallEvents.PLAYER_LEAVE)); + try { + playerLeaveEventConsumer.beforeLeave(PlayerLeave.builder() + .server(serverInfo.getServer()) + .player(new BukkitPlayerData(event.getPlayer(), null)) + .time(System.currentTimeMillis()) + .build()); + } catch (Exception e) { + errorLogger.error(e, ErrorContext.builder().related(event).build()); + } } @EventHandler(priority = EventPriority.MONITOR) @@ -227,23 +164,13 @@ public class PlayerOnlineListener implements Listener { private void actOnQuitEvent(PlayerQuitEvent event) { long time = System.currentTimeMillis(); - Player player = event.getPlayer(); - String playerName = player.getName(); - UUID playerUUID = player.getUniqueId(); - ServerUUID serverUUID = serverInfo.getServerUUID(); - + UUID playerUUID = event.getPlayer().getUniqueId(); BukkitAFKListener.afkTracker.loggedOut(playerUUID, time); - joinAddresses.remove(playerUUID); - nicknameCache.removeDisplayName(playerUUID); - - dbSystem.getDatabase().executeTransaction(new BanStatusTransaction(playerUUID, serverUUID, player::isBanned)); - - sessionCache.endSession(playerUUID, time) - .ifPresent(endedSession -> dbSystem.getDatabase().executeTransaction(new SessionEndTransaction(endedSession))); - - if (config.isTrue(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE)) { - processing.submitNonCritical(() -> exporter.exportPlayerPage(playerUUID, playerName)); - } + playerLeaveEventConsumer.onLeaveGameServer(PlayerLeave.builder() + .server(serverInfo.getServer()) + .player(new BukkitPlayerData(event.getPlayer(), null)) + .time(System.currentTimeMillis()) + .build()); } } diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/WorldChangeListener.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/WorldChangeListener.java index d5dceb498..96503a8ca 100644 --- a/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/WorldChangeListener.java +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/WorldChangeListener.java @@ -21,7 +21,7 @@ import com.djrapitops.plan.gathering.domain.ActiveSession; import com.djrapitops.plan.identification.ServerInfo; import com.djrapitops.plan.settings.config.WorldAliasSettings; import com.djrapitops.plan.storage.database.DBSystem; -import com.djrapitops.plan.storage.database.transactions.events.WorldNameStoreTransaction; +import com.djrapitops.plan.storage.database.transactions.events.StoreWorldNameTransaction; import com.djrapitops.plan.utilities.logging.ErrorContext; import com.djrapitops.plan.utilities.logging.ErrorLogger; import org.bukkit.entity.Player; @@ -72,7 +72,7 @@ public class WorldChangeListener implements Listener { String worldName = player.getWorld().getName(); String gameMode = player.getGameMode().name(); - dbSystem.getDatabase().executeTransaction(new WorldNameStoreTransaction(serverInfo.getServerUUID(), worldName)); + dbSystem.getDatabase().executeTransaction(new StoreWorldNameTransaction(serverInfo.getServerUUID(), worldName)); worldAliasSettings.addWorld(worldName); Optional cachedSession = SessionCache.getCachedSession(uuid); diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/timed/BukkitPingCounter.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/timed/BukkitPingCounter.java index 35d235d26..511513ba7 100644 --- a/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/timed/BukkitPingCounter.java +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/timed/BukkitPingCounter.java @@ -44,6 +44,7 @@ import org.bukkit.event.player.PlayerQuitEvent; import javax.inject.Inject; import javax.inject.Singleton; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; @@ -62,16 +63,16 @@ public class BukkitPingCounter extends TaskSystem.Task implements Listener { //the server is pinging the client every 40 Ticks (2 sec) - so check it then //https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/PlayerConnection.java#L178 - private static boolean pingMethodAvailable; + private final Map startRecording; private final Map>> playerHistory; private final Listeners listeners; private final PlanConfig config; private final DBSystem dbSystem; private final ServerInfo serverInfo; - private final RunnableFactory runnableFactory; + private final boolean pingMethodAvailable; private PingMethod pingMethod; @Inject @@ -79,14 +80,13 @@ public class BukkitPingCounter extends TaskSystem.Task implements Listener { Listeners listeners, PlanConfig config, DBSystem dbSystem, - ServerInfo serverInfo, - RunnableFactory runnableFactory + ServerInfo serverInfo ) { this.listeners = listeners; this.config = config; this.dbSystem = dbSystem; this.serverInfo = serverInfo; - this.runnableFactory = runnableFactory; + startRecording = new ConcurrentHashMap<>(); playerHistory = new HashMap<>(); Optional pingMethod = loadPingMethod(); @@ -117,7 +117,7 @@ public class BukkitPingCounter extends TaskSystem.Task implements Listener { } Logger.getGlobal().log( Level.WARNING, - "Plan: No Ping method found - Ping will not be recorded:" + reasonsForUnavailability.toString() + () -> "Plan: No Ping method found - Ping will not be recorded:" + reasonsForUnavailability.toString() ); return Optional.empty(); @@ -138,6 +138,16 @@ public class BukkitPingCounter extends TaskSystem.Task implements Listener { @Override public void run() { long time = System.currentTimeMillis(); + + Iterator> starts = startRecording.entrySet().iterator(); + while (starts.hasNext()) { + Map.Entry start = starts.next(); + if (time >= start.getValue()) { + addPlayer(start.getKey()); + starts.remove(); + } + } + Iterator>>> iterator = playerHistory.entrySet().iterator(); while (iterator.hasNext()) { @@ -164,11 +174,12 @@ public class BukkitPingCounter extends TaskSystem.Task implements Listener { } } - public void addPlayer(Player player) { - playerHistory.put(player.getUniqueId(), new ArrayList<>()); + public void addPlayer(UUID uuid) { + playerHistory.put(uuid, new ArrayList<>()); } public void removePlayer(Player player) { + startRecording.remove(player.getUniqueId()); playerHistory.remove(player.getUniqueId()); } @@ -182,15 +193,11 @@ public class BukkitPingCounter extends TaskSystem.Task implements Listener { @EventHandler public void onPlayerJoin(PlayerJoinEvent joinEvent) { Player player = joinEvent.getPlayer(); - Long pingDelay = config.get(TimeSettings.PING_PLAYER_LOGIN_DELAY); - if (pingDelay >= TimeUnit.HOURS.toMillis(2L)) { + Long pingDelayMs = config.get(TimeSettings.PING_PLAYER_LOGIN_DELAY); + if (pingDelayMs >= TimeUnit.HOURS.toMillis(2L)) { return; } - runnableFactory.create(() -> { - if (player.isOnline()) { - addPlayer(player); - } - }).runTaskLater(TimeAmount.toTicks(pingDelay, TimeUnit.MILLISECONDS)); + startRecording.put(player.getUniqueId(), System.currentTimeMillis() + pingDelayMs); } @EventHandler diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/timed/PingMethodReflection.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/timed/PingMethodReflection.java new file mode 100644 index 000000000..2dd7d8dd8 --- /dev/null +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/timed/PingMethodReflection.java @@ -0,0 +1,64 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016-2018 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.djrapitops.plan.gathering.timed; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; + +public class PingMethodReflection { + + private PingMethodReflection() { + /* Static utility class */ + } + + /** + * Get methods for getting the ping value. + * + * @param craftPlayerClass Class for player + * @param entityPlayer Class for player entity + * @param methodName getHandle method + * @param fieldName Latency field name + * @return [getHandle method, getPlayer method] + * @throws NoSuchMethodException Method doesn't exist + * @throws IllegalAccessException Method can't be accessed + * @throws NoSuchFieldException Field can't be accessed + * @throws IllegalArgumentException Something else + */ + public static MethodHandle[] getMethods( + Class craftPlayerClass, + Class entityPlayer, + String methodName, + String fieldName + ) throws IllegalAccessException, NoSuchFieldException, NoSuchMethodException { + MethodHandles.Lookup lookup = MethodHandles.publicLookup(); + Method handleMethod = craftPlayerClass.getDeclaredMethod(methodName); + + return new MethodHandle[]{ + lookup.unreflect(handleMethod), + lookup.findGetter(entityPlayer, fieldName, Integer.TYPE) + }; + } + +} diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/timed/ReflectiveLatencyFieldMethod.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/timed/ReflectiveLatencyFieldMethod.java index 0ac13c7df..6b4bbf95c 100644 --- a/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/timed/ReflectiveLatencyFieldMethod.java +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/timed/ReflectiveLatencyFieldMethod.java @@ -27,8 +27,6 @@ import com.djrapitops.plan.utilities.java.Reflection; import org.bukkit.entity.Player; import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.reflect.Method; public class ReflectiveLatencyFieldMethod implements PingMethod { @@ -37,26 +35,26 @@ public class ReflectiveLatencyFieldMethod implements PingMethod { private String reasonForUnavailability; + private static void setMethods() throws IllegalAccessException, NoSuchFieldException, NoSuchMethodException { + MethodHandle[] methodHandles = PingMethodReflection.getMethods( + Reflection.getCraftBukkitClass("entity.CraftPlayer"), + Reflection.getMinecraftClass("EntityPlayer"), + "getHandle", + "latency" + ); + getHandleMethod = methodHandles[0]; + pingField = methodHandles[1]; + } + @Override public boolean isAvailable() { - MethodHandle localHandle = null; - MethodHandle localPing = null; try { - Class craftPlayerClass = Reflection.getCraftBukkitClass("entity.CraftPlayer"); - Class entityPlayer = Reflection.getMinecraftClass("EntityPlayer"); - - MethodHandles.Lookup lookup = MethodHandles.publicLookup(); - - Method getHandleMethod = craftPlayerClass.getDeclaredMethod("getHandle"); - localHandle = lookup.unreflect(getHandleMethod); - - localPing = lookup.findGetter(entityPlayer, "latency", Integer.TYPE); - } catch (NoSuchMethodException | IllegalAccessException | NoSuchFieldException | IllegalArgumentException reflectiveEx) { + setMethods(); + } catch (NoSuchMethodException | IllegalAccessException | NoSuchFieldException | + IllegalArgumentException reflectiveEx) { reasonForUnavailability = reflectiveEx.toString(); return false; } - getHandleMethod = localHandle; - pingField = localPing; return pingField != null; } diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/timed/ReflectiveLevelEntityPlayerLatencyFieldMethod.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/timed/ReflectiveLevelEntityPlayerLatencyFieldMethod.java index 687b94418..24929f7fa 100644 --- a/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/timed/ReflectiveLevelEntityPlayerLatencyFieldMethod.java +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/timed/ReflectiveLevelEntityPlayerLatencyFieldMethod.java @@ -27,8 +27,6 @@ import com.djrapitops.plan.utilities.java.Reflection; import org.bukkit.entity.Player; import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.reflect.Method; public class ReflectiveLevelEntityPlayerLatencyFieldMethod implements PingMethod { @@ -37,26 +35,34 @@ public class ReflectiveLevelEntityPlayerLatencyFieldMethod implements PingMethod private String reasonForUnavailability; + private static void setMethods() throws IllegalAccessException, NoSuchFieldException, NoSuchMethodException, ClassNotFoundException { + MethodHandle[] methodHandles = PingMethodReflection.getMethods( + Reflection.getCraftBukkitClass("entity.CraftPlayer"), + getEntityPlayer(), + "getHandle", + "latency" + ); + getHandleMethod = methodHandles[0]; + pingField = methodHandles[1]; + } + + private static Class getEntityPlayer() throws ClassNotFoundException { + try { + return Class.forName("net.minecraft.server.level.EntityPlayer"); + } catch (NullPointerException classLoaderError) { + throw new ClassNotFoundException("net.minecraft.server.level.EntityPlayer"); + } + } + @Override public boolean isAvailable() { - MethodHandle localHandle = null; - MethodHandle localPing = null; try { - Class craftPlayerClass = Reflection.getCraftBukkitClass("entity.CraftPlayer"); - Class entityPlayer = Class.forName("net.minecraft.server.level.EntityPlayer"); - - MethodHandles.Lookup lookup = MethodHandles.publicLookup(); - - Method getHandleMethod = craftPlayerClass.getDeclaredMethod("getHandle"); - localHandle = lookup.unreflect(getHandleMethod); - - localPing = lookup.findGetter(entityPlayer, "latency", Integer.TYPE); - } catch (NoSuchMethodException | IllegalAccessException | NoSuchFieldException | ClassNotFoundException | IllegalArgumentException reflectiveEx) { + setMethods(); + } catch (NoSuchMethodException | IllegalAccessException | NoSuchFieldException | ClassNotFoundException | + IllegalArgumentException reflectiveEx) { reasonForUnavailability = reflectiveEx.toString(); return false; } - getHandleMethod = localHandle; - pingField = localPing; return pingField != null; } diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/timed/ReflectivePingFieldMethod.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/timed/ReflectivePingFieldMethod.java index 3d29bf604..4d71acc7a 100644 --- a/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/timed/ReflectivePingFieldMethod.java +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/timed/ReflectivePingFieldMethod.java @@ -27,8 +27,6 @@ import com.djrapitops.plan.utilities.java.Reflection; import org.bukkit.entity.Player; import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.reflect.Method; public class ReflectivePingFieldMethod implements PingMethod { @@ -37,26 +35,26 @@ public class ReflectivePingFieldMethod implements PingMethod { private String reasonForUnavailability; + private static void setMethods() throws IllegalAccessException, NoSuchFieldException, NoSuchMethodException { + MethodHandle[] methodHandles = PingMethodReflection.getMethods( + Reflection.getCraftBukkitClass("entity.CraftPlayer"), + Reflection.getMinecraftClass("EntityPlayer"), + "getHandle", + "ping" + ); + getHandleMethod = methodHandles[0]; + pingField = methodHandles[1]; + } + @Override public boolean isAvailable() { - MethodHandle localHandle = null; - MethodHandle localPing = null; try { - Class craftPlayerClass = Reflection.getCraftBukkitClass("entity.CraftPlayer"); - Class entityPlayer = Reflection.getMinecraftClass("EntityPlayer"); - - MethodHandles.Lookup lookup = MethodHandles.publicLookup(); - - Method getHandleMethod = craftPlayerClass.getDeclaredMethod("getHandle"); - localHandle = lookup.unreflect(getHandleMethod); - - localPing = lookup.findGetter(entityPlayer, "ping", Integer.TYPE); - } catch (NoSuchMethodException | IllegalAccessException | NoSuchFieldException | IllegalArgumentException reflectiveEx) { + setMethods(); + } catch (NoSuchMethodException | IllegalAccessException | NoSuchFieldException | + IllegalArgumentException reflectiveEx) { reasonForUnavailability = reflectiveEx.toString(); return false; } - getHandleMethod = localHandle; - pingField = localPing; return pingField != null; } diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/timed/ReflectiveUnmappedLatencyFieldMethod.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/timed/ReflectiveUnmappedLatencyFieldMethod.java index 67e68c4ce..ebf699129 100644 --- a/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/timed/ReflectiveUnmappedLatencyFieldMethod.java +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/timed/ReflectiveUnmappedLatencyFieldMethod.java @@ -27,8 +27,6 @@ import com.djrapitops.plan.utilities.java.Reflection; import org.bukkit.entity.Player; import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.reflect.Method; public class ReflectiveUnmappedLatencyFieldMethod implements PingMethod { @@ -37,27 +35,34 @@ public class ReflectiveUnmappedLatencyFieldMethod implements PingMethod { private String reasonForUnavailability; + private static void setMethods() throws IllegalAccessException, NoSuchFieldException, NoSuchMethodException, ClassNotFoundException { + MethodHandle[] methodHandles = PingMethodReflection.getMethods( + Reflection.getCraftBukkitClass("entity.CraftPlayer"), + getEntityPlayer(), + "getHandle", + "e" + ); + getHandleMethod = methodHandles[0]; + pingField = methodHandles[1]; + } + + private static Class getEntityPlayer() throws ClassNotFoundException { + try { + return Class.forName("net.minecraft.server.level.EntityPlayer"); + } catch (NullPointerException classLoaderError) { + throw new ClassNotFoundException("net.minecraft.server.level.EntityPlayer"); + } + } + @Override public boolean isAvailable() { - MethodHandle localHandle = null; - MethodHandle localPing = null; try { - Class craftPlayerClass = Reflection.getCraftBukkitClass("entity.CraftPlayer"); - Class entityPlayer = Class.forName("net.minecraft.server.level.EntityPlayer"); - - MethodHandles.Lookup lookup = MethodHandles.publicLookup(); - - Method getHandleMethod = craftPlayerClass.getDeclaredMethod("getHandle"); - localHandle = lookup.unreflect(getHandleMethod); - - // e is latency in unmapped jar - localPing = lookup.findGetter(entityPlayer, "e", Integer.TYPE); - } catch (NoSuchMethodException | IllegalAccessException | NoSuchFieldException | ClassNotFoundException | IllegalArgumentException reflectiveEx) { + setMethods(); + } catch (NoSuchMethodException | IllegalAccessException | NoSuchFieldException | ClassNotFoundException | + IllegalArgumentException reflectiveEx) { reasonForUnavailability = reflectiveEx.toString(); return false; } - getHandleMethod = localHandle; - pingField = localPing; return pingField != null; } diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/modules/bukkit/BukkitTaskModule.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/modules/bukkit/BukkitTaskModule.java index e52ec1048..90193ba84 100644 --- a/Plan/bukkit/src/main/java/com/djrapitops/plan/modules/bukkit/BukkitTaskModule.java +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/modules/bukkit/BukkitTaskModule.java @@ -17,6 +17,10 @@ package com.djrapitops.plan.modules.bukkit; import com.djrapitops.plan.TaskSystem; +import com.djrapitops.plan.addons.placeholderapi.PlaceholderCacheRefreshTask; +import com.djrapitops.plan.delivery.web.ResourceWriteTask; +import com.djrapitops.plan.delivery.web.WebAssetVersionCheckTask; +import com.djrapitops.plan.delivery.webserver.auth.ActiveCookieExpiryCleanupTask; import com.djrapitops.plan.delivery.webserver.cache.JSONFileStorage; import com.djrapitops.plan.extension.ExtensionServerDataUpdater; import com.djrapitops.plan.gathering.ShutdownDataPreservation; @@ -26,6 +30,7 @@ import com.djrapitops.plan.gathering.timed.ServerTPSCounter; import com.djrapitops.plan.gathering.timed.SystemUsageBuffer; import com.djrapitops.plan.settings.upkeep.ConfigStoreTask; import com.djrapitops.plan.storage.upkeep.DBCleanTask; +import com.djrapitops.plan.storage.upkeep.ExtensionDisableOnGameServerTask; import com.djrapitops.plan.storage.upkeep.LogsFolderCleanTask; import com.djrapitops.plan.storage.upkeep.OldDependencyCacheDeletionTask; import dagger.Binds; @@ -83,4 +88,24 @@ public interface BukkitTaskModule { @Binds @IntoSet TaskSystem.Task bindOldDependencyCacheDeletion(OldDependencyCacheDeletionTask deletionTask); + + @Binds + @IntoSet + TaskSystem.Task bindResourceWriteTask(ResourceWriteTask resourceWriteTask); + + @Binds + @IntoSet + TaskSystem.Task bindWebAssetVersionCheckTask(WebAssetVersionCheckTask webAssetVersionCheckTask); + + @Binds + @IntoSet + TaskSystem.Task bindActiveCookieStoreExpiryTask(ActiveCookieExpiryCleanupTask activeCookieExpiryCleanupTask); + + @Binds + @IntoSet + TaskSystem.Task bindExtensionDisableOnGameServerTask(ExtensionDisableOnGameServerTask extensionDisableOnGameServerTask); + + @Binds + @IntoSet + TaskSystem.Task bindPlaceholderWarmupTask(PlaceholderCacheRefreshTask placeholderCacheRefreshTask); } diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/storage/database/BukkitDBSystem.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/storage/database/BukkitDBSystem.java index d788e6446..d3f157696 100644 --- a/Plan/bukkit/src/main/java/com/djrapitops/plan/storage/database/BukkitDBSystem.java +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/storage/database/BukkitDBSystem.java @@ -32,7 +32,6 @@ import javax.inject.Singleton; @Singleton public class BukkitDBSystem extends DBSystem { - private final PlanConfig config; @Inject public BukkitDBSystem( @@ -42,8 +41,7 @@ public class BukkitDBSystem extends DBSystem { PlanConfig config, PluginLogger logger ) { - super(locale, sqLiteDB, logger); - this.config = config; + super(config, locale, sqLiteDB, logger); databases.add(mySQLDB); databases.add(sqLiteDB.usingDefaultFile()); diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/utilities/java/Reflection.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/utilities/java/Reflection.java index 09a65d55c..f6e150a74 100644 --- a/Plan/bukkit/src/main/java/com/djrapitops/plan/utilities/java/Reflection.java +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/utilities/java/Reflection.java @@ -71,7 +71,7 @@ public final class Reflection { field.setAccessible(true); // A function for retrieving a specific field value - return new FieldAccessor() { + return new FieldAccessor<>() { @Override @SuppressWarnings("unchecked") @@ -140,7 +140,7 @@ public final class Reflection { private static Class getCanonicalClass(String canonicalName) { try { return Class.forName(canonicalName); - } catch (ClassNotFoundException e) { + } catch (ClassNotFoundException | NullPointerException e) { throw new IllegalArgumentException("Cannot find " + canonicalName, e); } } diff --git a/Plan/bukkit/src/test/java/com/djrapitops/plan/gathering/listeners/BukkitAFKListenerTest.java b/Plan/bukkit/src/test/java/com/djrapitops/plan/gathering/listeners/BukkitAFKListenerTest.java index 14819efaa..0fcabb21c 100644 --- a/Plan/bukkit/src/test/java/com/djrapitops/plan/gathering/listeners/BukkitAFKListenerTest.java +++ b/Plan/bukkit/src/test/java/com/djrapitops/plan/gathering/listeners/BukkitAFKListenerTest.java @@ -16,6 +16,8 @@ */ package com.djrapitops.plan.gathering.listeners; +import com.djrapitops.plan.gathering.cache.SessionCache; +import com.djrapitops.plan.gathering.domain.ActiveSession; import com.djrapitops.plan.gathering.listeners.bukkit.BukkitAFKListener; import com.djrapitops.plan.settings.config.PlanConfig; import com.djrapitops.plan.settings.config.paths.TimeSettings; @@ -51,11 +53,15 @@ class BukkitAFKListenerTest { when(config.get(TimeSettings.AFK_THRESHOLD)).thenReturn(TimeUnit.MINUTES.toMillis(3)); errorLogger = Mockito.mock(ErrorLogger.class); underTest = new BukkitAFKListener(config, errorLogger); + + new SessionCache().cacheSession(TestConstants.PLAYER_ONE_UUID, new ActiveSession(null, null, 0, null, null)); + new SessionCache().cacheSession(TestConstants.PLAYER_TWO_UUID, new ActiveSession(null, null, 0, null, null)); } @AfterEach void ensureNoErrors() { verifyNoInteractions(errorLogger); + SessionCache.clear(); } @Test diff --git a/Plan/bukkit/src/test/java/utilities/mocks/BukkitMockComponent.java b/Plan/bukkit/src/test/java/utilities/mocks/BukkitMockComponent.java index a12798324..0eae93b0d 100644 --- a/Plan/bukkit/src/test/java/utilities/mocks/BukkitMockComponent.java +++ b/Plan/bukkit/src/test/java/utilities/mocks/BukkitMockComponent.java @@ -20,6 +20,7 @@ import com.djrapitops.plan.DaggerPlanBukkitComponent; import com.djrapitops.plan.PlanBukkitComponent; import com.djrapitops.plan.PlanPlugin; import com.djrapitops.plan.PlanSystem; +import com.djrapitops.plan.storage.database.SQLDB; import org.bukkit.Server; import org.bukkit.command.ConsoleCommandSender; import org.bukkit.scheduler.BukkitScheduler; @@ -44,6 +45,7 @@ public class BukkitMockComponent { public BukkitMockComponent(Path tempDir) { this.tempDir = tempDir; + SQLDB.setDownloadDriver(false); } public PlanPlugin getPlanMock() throws Exception { diff --git a/Plan/bungeecord/build.gradle b/Plan/bungeecord/build.gradle index b241ecaef..ca44b8486 100644 --- a/Plan/bungeecord/build.gradle +++ b/Plan/bungeecord/build.gradle @@ -1,20 +1,20 @@ dependencies { - compileOnly project(":common") - implementation project(path: ":common", configuration: 'shadow') - compileOnly project(":api") + implementation project(":api") + implementation project(":common") - implementation "net.playeranalytics:platform-abstraction-layer-bungeecord:$palVersion" - implementation "org.bstats:bstats-bungeecord:$bstatsVersion" + shadow "net.playeranalytics:platform-abstraction-layer-api:$palVersion" + shadow "net.playeranalytics:platform-abstraction-layer-bungeecord:$palVersion" + shadow "org.bstats:bstats-bungeecord:$bstatsVersion" compileOnly "net.md-5:bungeecord-api:$bungeeVersion" compileOnly "com.imaginarycode.minecraft:RedisBungee:$redisBungeeVersion" + testImplementation "net.md-5:bungeecord-api:$bungeeVersion" testImplementation "com.imaginarycode.minecraft:RedisBungee:$redisBungeeVersion" - testImplementation project(path: ":common", configuration: 'testArtifacts') } shadowJar { - relocate 'org.bstats', 'com.djrapitops.plan.utilities.metrics' - relocate 'org.slf4j', 'plan.org.slf4j' + configurations = [project.configurations.shadow] + relocate 'org.bstats', 'net.playeranalytics.bstats.utilities.metrics' } \ No newline at end of file diff --git a/Plan/bungeecord/src/main/java/com/djrapitops/plan/PlanBungee.java b/Plan/bungeecord/src/main/java/com/djrapitops/plan/PlanBungee.java index b5e26eb54..6c3e5c0d0 100644 --- a/Plan/bungeecord/src/main/java/com/djrapitops/plan/PlanBungee.java +++ b/Plan/bungeecord/src/main/java/com/djrapitops/plan/PlanBungee.java @@ -23,6 +23,8 @@ import com.djrapitops.plan.exceptions.EnableException; import com.djrapitops.plan.settings.locale.Locale; import com.djrapitops.plan.settings.locale.lang.PluginLang; import com.djrapitops.plan.settings.theme.PlanColorScheme; +import com.djrapitops.plan.utilities.java.ThreadContextClassLoaderSwap; +import com.djrapitops.plan.utilities.logging.ErrorLogger; import net.md_5.bungee.api.plugin.Plugin; import net.playeranalytics.plugin.BungeePlatformLayer; import net.playeranalytics.plugin.PlatformAbstractionLayer; @@ -46,6 +48,7 @@ public class PlanBungee extends Plugin implements PlanPlugin { private PluginLogger logger; private RunnableFactory runnableFactory; private PlatformAbstractionLayer abstractionLayer; + private ErrorLogger errorLogger; @Override public void onLoad() { @@ -61,7 +64,8 @@ public class PlanBungee extends Plugin implements PlanPlugin { .abstractionLayer(abstractionLayer) .build(); try { - system = component.system(); + system = ThreadContextClassLoaderSwap.performOperation(getClass().getClassLoader(), component::system); + errorLogger = component.errorLogger(); locale = system.getLocaleSystem().getLocale(); system.enable(); @@ -107,7 +111,7 @@ public class PlanBungee extends Plugin implements PlanPlugin { return; } for (String name : command.getAliases()) { - getProxy().getPluginManager().registerCommand(this, new BungeeCommand(runnableFactory, system.getErrorLogger(), command, name)); + getProxy().getPluginManager().registerCommand(this, new BungeeCommand(runnableFactory, errorLogger, command, name)); } } diff --git a/Plan/bungeecord/src/main/java/com/djrapitops/plan/PlanBungeeComponent.java b/Plan/bungeecord/src/main/java/com/djrapitops/plan/PlanBungeeComponent.java index 25f173d02..e4bd36db3 100644 --- a/Plan/bungeecord/src/main/java/com/djrapitops/plan/PlanBungeeComponent.java +++ b/Plan/bungeecord/src/main/java/com/djrapitops/plan/PlanBungeeComponent.java @@ -19,6 +19,7 @@ package com.djrapitops.plan; import com.djrapitops.plan.commands.PlanCommand; import com.djrapitops.plan.modules.*; import com.djrapitops.plan.modules.bungee.*; +import com.djrapitops.plan.utilities.logging.ErrorLogger; import dagger.BindsInstance; import dagger.Component; import net.playeranalytics.plugin.PlatformAbstractionLayer; @@ -50,6 +51,8 @@ public interface PlanBungeeComponent { PlanSystem system(); + ErrorLogger errorLogger(); + @Component.Builder interface Builder { diff --git a/Plan/bungeecord/src/main/java/com/djrapitops/plan/command/use/BungeePartBuilder.java b/Plan/bungeecord/src/main/java/com/djrapitops/plan/command/use/BungeePartBuilder.java index 2021a20f8..f4f965be5 100644 --- a/Plan/bungeecord/src/main/java/com/djrapitops/plan/command/use/BungeePartBuilder.java +++ b/Plan/bungeecord/src/main/java/com/djrapitops/plan/command/use/BungeePartBuilder.java @@ -20,6 +20,7 @@ import com.djrapitops.plan.commands.use.MessageBuilder; import net.md_5.bungee.api.chat.ClickEvent; import net.md_5.bungee.api.chat.ComponentBuilder; import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.hover.content.Text; import org.apache.commons.text.TextStringBuilder; import java.util.Arrays; @@ -68,7 +69,7 @@ class BungeePartBuilder implements MessageBuilder { @Override public MessageBuilder hover(String text) { - part.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(text).create())); + part.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(text))); return this; } @@ -81,7 +82,7 @@ class BungeePartBuilder implements MessageBuilder { public MessageBuilder hover(Collection lines) { ComponentBuilder hoverMsg = new ComponentBuilder(""); hoverMsg.append(new TextStringBuilder().appendWithSeparators(lines, "\n").build()); - part.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, hoverMsg.create())); + part.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(hoverMsg.create()))); return this; } diff --git a/Plan/bungeecord/src/main/java/com/djrapitops/plan/gathering/domain/BungeePlayerData.java b/Plan/bungeecord/src/main/java/com/djrapitops/plan/gathering/domain/BungeePlayerData.java new file mode 100644 index 000000000..21c8d6401 --- /dev/null +++ b/Plan/bungeecord/src/main/java/com/djrapitops/plan/gathering/domain/BungeePlayerData.java @@ -0,0 +1,78 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.gathering.domain; + +import net.md_5.bungee.api.connection.ProxiedPlayer; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.UnknownHostException; +import java.util.Optional; +import java.util.UUID; + +public class BungeePlayerData implements PlatformPlayerData { + + private final ProxiedPlayer player; + + public BungeePlayerData(ProxiedPlayer player) {this.player = player;} + + @Override + public UUID getUUID() { + return player.getUniqueId(); + } + + @Override + public String getName() { + return player.getName(); + } + + @Override + public Optional getIPAddress() { + Optional ip = getIPFromSocketAddress(); + if (ip.isPresent()) return ip; + return getIpFromOldMethod(); + } + + @SuppressWarnings("deprecation") // ProxiedPlayer#getAddress is deprecated + private Optional getIpFromOldMethod() { + try { + return Optional.ofNullable(player.getAddress()).map(InetSocketAddress::getAddress); + } catch (NoSuchMethodError e) { + return Optional.empty(); + } + } + + private Optional getIPFromSocketAddress() { + try { + SocketAddress socketAddress = player.getSocketAddress(); + if (socketAddress instanceof InetSocketAddress) { + return Optional.of(((InetSocketAddress) socketAddress).getAddress()); + } + + // Unix domain socket address requires Java 16 compatibility. + // These connections come from the same physical machine + Class jdk16SocketAddressType = Class.forName("java.net.UnixDomainSocketAddress"); + if (jdk16SocketAddressType.isAssignableFrom(socketAddress.getClass())) { + return Optional.of(InetAddress.getLocalHost()); + } + } catch (NoSuchMethodError | ClassNotFoundException | UnknownHostException e) { + // Ignored + } + return Optional.empty(); + } +} diff --git a/Plan/bungeecord/src/main/java/com/djrapitops/plan/gathering/listeners/bungee/PlayerOnlineListener.java b/Plan/bungeecord/src/main/java/com/djrapitops/plan/gathering/listeners/bungee/PlayerOnlineListener.java index 17185f6cb..b8c02df69 100644 --- a/Plan/bungeecord/src/main/java/com/djrapitops/plan/gathering/listeners/bungee/PlayerOnlineListener.java +++ b/Plan/bungeecord/src/main/java/com/djrapitops/plan/gathering/listeners/bungee/PlayerOnlineListener.java @@ -16,23 +16,13 @@ */ package com.djrapitops.plan.gathering.listeners.bungee; -import com.djrapitops.plan.delivery.domain.PlayerName; -import com.djrapitops.plan.delivery.domain.ServerName; -import com.djrapitops.plan.delivery.export.Exporter; -import com.djrapitops.plan.extension.CallEvents; -import com.djrapitops.plan.extension.ExtensionSvc; -import com.djrapitops.plan.gathering.cache.SessionCache; -import com.djrapitops.plan.gathering.domain.ActiveSession; -import com.djrapitops.plan.gathering.geolocation.GeolocationCache; +import com.djrapitops.plan.gathering.domain.BungeePlayerData; +import com.djrapitops.plan.gathering.domain.event.PlayerJoin; +import com.djrapitops.plan.gathering.domain.event.PlayerLeave; +import com.djrapitops.plan.gathering.events.PlayerJoinEventConsumer; +import com.djrapitops.plan.gathering.events.PlayerLeaveEventConsumer; +import com.djrapitops.plan.gathering.events.PlayerSwitchServerEventConsumer; import com.djrapitops.plan.identification.ServerInfo; -import com.djrapitops.plan.processing.Processing; -import com.djrapitops.plan.settings.config.PlanConfig; -import com.djrapitops.plan.settings.config.paths.DataGatheringSettings; -import com.djrapitops.plan.settings.config.paths.ExportSettings; -import com.djrapitops.plan.storage.database.DBSystem; -import com.djrapitops.plan.storage.database.Database; -import com.djrapitops.plan.storage.database.transactions.events.GeoInfoStoreTransaction; -import com.djrapitops.plan.storage.database.transactions.events.PlayerRegisterTransaction; import com.djrapitops.plan.utilities.logging.ErrorContext; import com.djrapitops.plan.utilities.logging.ErrorLogger; import net.md_5.bungee.api.connection.ProxiedPlayer; @@ -44,8 +34,6 @@ import net.md_5.bungee.event.EventHandler; import net.md_5.bungee.event.EventPriority; import javax.inject.Inject; -import java.net.InetAddress; -import java.util.UUID; /** * Player Join listener for Bungee. @@ -54,34 +42,24 @@ import java.util.UUID; */ public class PlayerOnlineListener implements Listener { - private final PlanConfig config; - private final Processing processing; - private final DBSystem dbSystem; - private final ExtensionSvc extensionService; - private final Exporter exporter; - private final GeolocationCache geolocationCache; - private final SessionCache sessionCache; + private final PlayerJoinEventConsumer joinEventConsumer; + private final PlayerLeaveEventConsumer leaveEventConsumer; + private final PlayerSwitchServerEventConsumer switchServerEventConsumer; + private final ServerInfo serverInfo; private final ErrorLogger errorLogger; @Inject public PlayerOnlineListener( - PlanConfig config, - Processing processing, - DBSystem dbSystem, - ExtensionSvc extensionService, - Exporter exporter, GeolocationCache geolocationCache, - SessionCache sessionCache, + PlayerJoinEventConsumer joinEventConsumer, + PlayerLeaveEventConsumer leaveEventConsumer, + PlayerSwitchServerEventConsumer switchServerEventConsumer, ServerInfo serverInfo, ErrorLogger errorLogger ) { - this.config = config; - this.processing = processing; - this.dbSystem = dbSystem; - this.extensionService = extensionService; - this.exporter = exporter; - this.geolocationCache = geolocationCache; - this.sessionCache = sessionCache; + this.joinEventConsumer = joinEventConsumer; + this.leaveEventConsumer = leaveEventConsumer; + this.switchServerEventConsumer = switchServerEventConsumer; this.serverInfo = serverInfo; this.errorLogger = errorLogger; } @@ -96,82 +74,48 @@ public class PlayerOnlineListener implements Listener { } private void actOnLogin(PostLoginEvent event) { - ProxiedPlayer player = event.getPlayer(); - UUID playerUUID = player.getUniqueId(); - String playerName = player.getName(); - InetAddress address = player.getAddress().getAddress(); long time = System.currentTimeMillis(); + ProxiedPlayer player = event.getPlayer(); - ActiveSession session = new ActiveSession(playerUUID, serverInfo.getServerUUID(), time, null, null); - session.getExtraData().put(PlayerName.class, new PlayerName(playerName)); - session.getExtraData().put(ServerName.class, new ServerName("Proxy Server")); - sessionCache.cacheSession(playerUUID, session); - Database database = dbSystem.getDatabase(); - - boolean gatheringGeolocations = config.isTrue(DataGatheringSettings.GEOLOCATIONS); - if (gatheringGeolocations) { - database.executeTransaction( - new GeoInfoStoreTransaction(playerUUID, address, time, geolocationCache::getCountry) - ); - } - - database.executeTransaction(new PlayerRegisterTransaction(playerUUID, () -> time, playerName)); - processing.submitNonCritical(() -> extensionService.updatePlayerValues(playerUUID, playerName, CallEvents.PLAYER_JOIN)); - if (config.isTrue(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE)) { - processing.submitNonCritical(() -> exporter.exportPlayerPage(playerUUID, playerName)); - } + joinEventConsumer.onJoinProxyServer(PlayerJoin.builder() + .server(serverInfo.getServer()) + .player(new BungeePlayerData(player)) + .time(time) + .build()); } @EventHandler(priority = EventPriority.NORMAL) public void beforeLogout(PlayerDisconnectEvent event) { - ProxiedPlayer player = event.getPlayer(); - UUID playerUUID = player.getUniqueId(); - String playerName = player.getName(); - processing.submitNonCritical(() -> extensionService.updatePlayerValues(playerUUID, playerName, CallEvents.PLAYER_LEAVE)); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onLogout(PlayerDisconnectEvent event) { try { - actOnLogout(event); + leaveEventConsumer.beforeLeave(PlayerLeave.builder() + .server(serverInfo.getServer()) + .player(new BungeePlayerData(event.getPlayer())) + .time(System.currentTimeMillis()) + .build()); } catch (Exception e) { errorLogger.error(e, ErrorContext.builder().related(event).build()); } } - private void actOnLogout(PlayerDisconnectEvent event) { - ProxiedPlayer player = event.getPlayer(); - String playerName = player.getName(); - UUID playerUUID = player.getUniqueId(); - - sessionCache.endSession(playerUUID, System.currentTimeMillis()); - if (config.isTrue(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE)) { - processing.submitNonCritical(() -> exporter.exportPlayerPage(playerUUID, playerName)); + @EventHandler(priority = EventPriority.HIGHEST) + public void onLogout(PlayerDisconnectEvent event) { + try { + leaveEventConsumer.onLeaveProxyServer(PlayerLeave.builder() + .server(serverInfo.getServer()) + .player(new BungeePlayerData(event.getPlayer())) + .time(System.currentTimeMillis()) + .build()); + } catch (Exception e) { + errorLogger.error(e, ErrorContext.builder().related(event).build()); } } @EventHandler(priority = EventPriority.HIGHEST) public void onServerSwitch(ServerSwitchEvent event) { try { - actOnServerSwitch(event); + switchServerEventConsumer.onServerSwitch(new BungeePlayerData(event.getPlayer()), System.currentTimeMillis()); } catch (Exception e) { errorLogger.error(e, ErrorContext.builder().related(event).build()); } } - - private void actOnServerSwitch(ServerSwitchEvent event) { - ProxiedPlayer player = event.getPlayer(); - String playerName = player.getName(); - UUID playerUUID = player.getUniqueId(); - - long time = System.currentTimeMillis(); - // Replaces the current session in the cache. - ActiveSession session = new ActiveSession(playerUUID, serverInfo.getServerUUID(), time, null, null); - session.getExtraData().put(PlayerName.class, new PlayerName(playerName)); - session.getExtraData().put(ServerName.class, new ServerName("Proxy Server")); - sessionCache.cacheSession(playerUUID, session); - if (config.isTrue(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE)) { - processing.submitNonCritical(() -> exporter.exportPlayerPage(playerUUID, playerName)); - } - } } diff --git a/Plan/bungeecord/src/main/java/com/djrapitops/plan/gathering/timed/BungeePingCounter.java b/Plan/bungeecord/src/main/java/com/djrapitops/plan/gathering/timed/BungeePingCounter.java index bcfbf5413..fea9b69c4 100644 --- a/Plan/bungeecord/src/main/java/com/djrapitops/plan/gathering/timed/BungeePingCounter.java +++ b/Plan/bungeecord/src/main/java/com/djrapitops/plan/gathering/timed/BungeePingCounter.java @@ -44,6 +44,7 @@ import net.playeranalytics.plugin.server.Listeners; import javax.inject.Inject; import javax.inject.Singleton; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; /** @@ -54,35 +55,43 @@ import java.util.concurrent.TimeUnit; @Singleton public class BungeePingCounter extends TaskSystem.Task implements Listener { + private final Map startRecording; private final Map>> playerHistory; private final Listeners listeners; private final PlanConfig config; private final DBSystem dbSystem; private final ServerInfo serverInfo; - private final RunnableFactory runnableFactory; @Inject public BungeePingCounter( Listeners listeners, PlanConfig config, DBSystem dbSystem, - ServerInfo serverInfo, - RunnableFactory runnableFactory + ServerInfo serverInfo ) { this.listeners = listeners; this.config = config; this.dbSystem = dbSystem; this.serverInfo = serverInfo; - this.runnableFactory = runnableFactory; + startRecording = new ConcurrentHashMap<>(); playerHistory = new HashMap<>(); } @Override public void run() { long time = System.currentTimeMillis(); - Iterator>>> iterator = playerHistory.entrySet().iterator(); + Iterator> starts = startRecording.entrySet().iterator(); + while (starts.hasNext()) { + Map.Entry start = starts.next(); + if (time >= start.getValue()) { + addPlayer(start.getKey()); + starts.remove(); + } + } + + Iterator>>> iterator = playerHistory.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry>> entry = iterator.next(); UUID uuid = entry.getKey(); @@ -119,12 +128,13 @@ public class BungeePingCounter extends TaskSystem.Task implements Listener { } } - public void addPlayer(ProxiedPlayer player) { - playerHistory.put(player.getUniqueId(), new ArrayList<>()); + public void addPlayer(UUID uuid) { + playerHistory.put(uuid, new ArrayList<>()); } public void removePlayer(ProxiedPlayer player) { playerHistory.remove(player.getUniqueId()); + startRecording.remove(player.getUniqueId()); } private int getPing(ProxiedPlayer player) { @@ -134,15 +144,11 @@ public class BungeePingCounter extends TaskSystem.Task implements Listener { @EventHandler public void onPlayerJoin(ServerConnectedEvent joinEvent) { ProxiedPlayer player = joinEvent.getPlayer(); - Long pingDelay = config.get(TimeSettings.PING_PLAYER_LOGIN_DELAY); - if (pingDelay >= TimeUnit.HOURS.toMillis(2L)) { + Long pingDelayMs = config.get(TimeSettings.PING_PLAYER_LOGIN_DELAY); + if (pingDelayMs >= TimeUnit.HOURS.toMillis(2L)) { return; } - runnableFactory.create(() -> { - if (player.isConnected()) { - addPlayer(player); - } - }).runTaskLater(TimeAmount.toTicks(pingDelay, TimeUnit.MILLISECONDS)); + startRecording.put(player.getUniqueId(), System.currentTimeMillis() + pingDelayMs); } @EventHandler diff --git a/Plan/bungeecord/src/main/java/com/djrapitops/plan/identification/BungeeServerInfo.java b/Plan/bungeecord/src/main/java/com/djrapitops/plan/identification/BungeeServerInfo.java index 4e1712aaa..af0a618b7 100644 --- a/Plan/bungeecord/src/main/java/com/djrapitops/plan/identification/BungeeServerInfo.java +++ b/Plan/bungeecord/src/main/java/com/djrapitops/plan/identification/BungeeServerInfo.java @@ -28,6 +28,7 @@ import com.djrapitops.plan.settings.locale.lang.PluginLang; import net.playeranalytics.plugin.server.PluginLogger; import javax.inject.Inject; +import javax.inject.Named; import javax.inject.Singleton; /** @@ -38,6 +39,7 @@ import javax.inject.Singleton; @Singleton public class BungeeServerInfo extends ServerInfo { + private final String currentVersion; private final ServerLoader fromFile; private final ServerLoader fromDatabase; @@ -49,6 +51,7 @@ public class BungeeServerInfo extends ServerInfo { @Inject public BungeeServerInfo( + @Named("currentVersion") String currentVersion, ServerProperties serverProperties, ServerFileLoader fromFile, ServerDBLoader fromDatabase, @@ -58,6 +61,7 @@ public class BungeeServerInfo extends ServerInfo { PluginLogger logger ) { super(serverProperties); + this.currentVersion = currentVersion; this.fromFile = fromFile; this.fromDatabase = fromDatabase; this.processing = processing; @@ -89,7 +93,7 @@ public class BungeeServerInfo extends ServerInfo { } /** - * @throws EnableException + * @throws EnableException If IP setting is unset */ private void checkIfDefaultIP() { String ip = serverProperties.getIp(); @@ -101,7 +105,7 @@ public class BungeeServerInfo extends ServerInfo { } /** - * @throws EnableException + * @throws EnableException If IP setting is unset */ private Server registerServer() { Server proxy = createServerObject(); @@ -115,11 +119,11 @@ public class BungeeServerInfo extends ServerInfo { } /** - * @throws EnableException + * @throws EnableException If IP setting is unset */ private Server createServerObject() { ServerUUID serverUUID = generateNewUUID(); String accessAddress = addresses.getAccessAddress().orElseThrow(() -> new EnableException("Velocity can not have '0.0.0.0' or '' as an address. Set up 'Server.IP' setting.")); - return new Server(-1, serverUUID, "BungeeCord", accessAddress, true); + return new Server(-1, serverUUID, "BungeeCord", accessAddress, true, currentVersion); } } diff --git a/Plan/bungeecord/src/main/java/com/djrapitops/plan/modules/bungee/BungeeTaskModule.java b/Plan/bungeecord/src/main/java/com/djrapitops/plan/modules/bungee/BungeeTaskModule.java index 1fb9697cc..a0633a7e9 100644 --- a/Plan/bungeecord/src/main/java/com/djrapitops/plan/modules/bungee/BungeeTaskModule.java +++ b/Plan/bungeecord/src/main/java/com/djrapitops/plan/modules/bungee/BungeeTaskModule.java @@ -17,6 +17,9 @@ package com.djrapitops.plan.modules.bungee; import com.djrapitops.plan.TaskSystem; +import com.djrapitops.plan.delivery.web.ResourceWriteTask; +import com.djrapitops.plan.delivery.web.WebAssetVersionCheckTask; +import com.djrapitops.plan.delivery.webserver.auth.ActiveCookieExpiryCleanupTask; import com.djrapitops.plan.delivery.webserver.cache.JSONFileStorage; import com.djrapitops.plan.extension.ExtensionServerDataUpdater; import com.djrapitops.plan.gathering.timed.BungeePingCounter; @@ -72,4 +75,16 @@ public interface BungeeTaskModule { @Binds @IntoSet TaskSystem.Task bindOldDependencyCacheDeletion(OldDependencyCacheDeletionTask deletionTask); + + @Binds + @IntoSet + TaskSystem.Task bindResourceWriteTask(ResourceWriteTask resourceWriteTask); + + @Binds + @IntoSet + TaskSystem.Task bindWebAssetVersionCheckTask(WebAssetVersionCheckTask webAssetVersionCheckTask); + + @Binds + @IntoSet + TaskSystem.Task bindActiveCookieStoreExpiryTask(ActiveCookieExpiryCleanupTask activeCookieExpiryCleanupTask); } diff --git a/Plan/bungeecord/src/test/java/com/djrapitops/plan/BungeeSystemTest.java b/Plan/bungeecord/src/test/java/com/djrapitops/plan/BungeeSystemTest.java index 1cb48dce0..f1bea5199 100644 --- a/Plan/bungeecord/src/test/java/com/djrapitops/plan/BungeeSystemTest.java +++ b/Plan/bungeecord/src/test/java/com/djrapitops/plan/BungeeSystemTest.java @@ -48,13 +48,13 @@ class BungeeSystemTest { private DBPreparer dbPreparer; @BeforeEach - void prepareSystem(@TempDir Path temp) throws Exception { + void prepareSystem(@TempDir Path temp) { component = new BungeeMockComponent(temp); - dbPreparer = new DBPreparer(component.getPlanSystem(), TEST_PORT_NUMBER); + dbPreparer = new DBPreparer(new BungeeSystemTestDependencies(component.getPlanSystem()), TEST_PORT_NUMBER); } @Test - void bungeeEnables() throws Exception { + void bungeeEnables() { PlanSystem bungeeSystem = component.getPlanSystem(); try { PlanConfig config = bungeeSystem.getConfigSystem().getConfig(); @@ -75,45 +75,41 @@ class BungeeSystemTest { @Test void bungeeDoesNotEnableWithDefaultIP() { - EnableException thrown = assertThrows(EnableException.class, () -> { - PlanSystem bungeeSystem = component.getPlanSystem(); - try { - PlanConfig config = bungeeSystem.getConfigSystem().getConfig(); - config.set(WebserverSettings.PORT, TEST_PORT_NUMBER); - config.set(ProxySettings.IP, "0.0.0.0"); + PlanSystem bungeeSystem = component.getPlanSystem(); + try { + PlanConfig config = bungeeSystem.getConfigSystem().getConfig(); + config.set(WebserverSettings.PORT, TEST_PORT_NUMBER); + config.set(ProxySettings.IP, "0.0.0.0"); - DBSystem dbSystem = bungeeSystem.getDatabaseSystem(); - SQLiteDB db = dbSystem.getSqLiteFactory().usingDefaultFile(); - db.setTransactionExecutorServiceProvider(MoreExecutors::newDirectExecutorService); - dbSystem.setActiveDatabase(db); + DBSystem dbSystem = bungeeSystem.getDatabaseSystem(); + SQLiteDB db = dbSystem.getSqLiteFactory().usingDefaultFile(); + db.setTransactionExecutorServiceProvider(MoreExecutors::newDirectExecutorService); + dbSystem.setActiveDatabase(db); - bungeeSystem.enable(); // Throws EnableException - } finally { - bungeeSystem.disable(); - } - }); + EnableException thrown = assertThrows(EnableException.class, bungeeSystem::enable); + assertEquals("IP setting still 0.0.0.0 - Configure Alternative_IP/IP that connects to the Proxy server.", thrown.getMessage()); + } finally { + bungeeSystem.disable(); + } - assertEquals("IP setting still 0.0.0.0 - Configure Alternative_IP/IP that connects to the Proxy server.", thrown.getMessage()); } @Test void testEnableNoMySQL() { - assertThrows(EnableException.class, () -> { - PlanSystem bungeeSystem = component.getPlanSystem(); - try { - PlanConfig config = bungeeSystem.getConfigSystem().getConfig(); - config.set(WebserverSettings.PORT, TEST_PORT_NUMBER); - config.set(ProxySettings.IP, "8.8.8.8"); + PlanSystem bungeeSystem = component.getPlanSystem(); + try { + PlanConfig config = bungeeSystem.getConfigSystem().getConfig(); + config.set(WebserverSettings.PORT, TEST_PORT_NUMBER); + config.set(ProxySettings.IP, "8.8.8.8"); - bungeeSystem.enable(); // Throws EnableException - } finally { - bungeeSystem.disable(); - } - }); + assertThrows(EnableException.class, bungeeSystem::enable); + } finally { + bungeeSystem.disable(); + } } @Test - void testEnableWithMySQL() throws Exception { + void testEnableWithMySQL() { PlanSystem bungeeSystem = component.getPlanSystem(); try { PlanConfig config = bungeeSystem.getConfigSystem().getConfig(); diff --git a/Plan/bungeecord/src/test/java/com/djrapitops/plan/BungeeSystemTestDependencies.java b/Plan/bungeecord/src/test/java/com/djrapitops/plan/BungeeSystemTestDependencies.java new file mode 100644 index 000000000..07eed5014 --- /dev/null +++ b/Plan/bungeecord/src/test/java/com/djrapitops/plan/BungeeSystemTestDependencies.java @@ -0,0 +1,48 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan; + +import com.djrapitops.plan.settings.config.PlanConfig; +import com.djrapitops.plan.storage.database.DBSystem; +import utilities.DBPreparer; + +public class BungeeSystemTestDependencies implements DBPreparer.Dependencies { + + private final PlanSystem system; + + public BungeeSystemTestDependencies(PlanSystem system) { + this.system = system; + } + + @Override + public PlanConfig config() { + return system.getConfigSystem().getConfig(); + } + + @Override + public DBSystem dbSystem() { + return system.getDatabaseSystem(); + } + + @Override + public void enable() { + } + + @Override + public void disable() { + } +} diff --git a/Plan/bungeecord/src/test/java/utilities/mocks/BungeeMockComponent.java b/Plan/bungeecord/src/test/java/utilities/mocks/BungeeMockComponent.java index 3e20d15a2..73690a839 100644 --- a/Plan/bungeecord/src/test/java/utilities/mocks/BungeeMockComponent.java +++ b/Plan/bungeecord/src/test/java/utilities/mocks/BungeeMockComponent.java @@ -20,6 +20,7 @@ import com.djrapitops.plan.DaggerPlanBungeeComponent; import com.djrapitops.plan.PlanBungee; import com.djrapitops.plan.PlanBungeeComponent; import com.djrapitops.plan.PlanSystem; +import com.djrapitops.plan.storage.database.SQLDB; import java.nio.file.Path; @@ -37,9 +38,10 @@ public class BungeeMockComponent { public BungeeMockComponent(Path tempDir) { this.tempDir = tempDir; + SQLDB.setDownloadDriver(false); } - public PlanBungee getPlanMock() throws Exception { + public PlanBungee getPlanMock() { if (planMock == null) { planMock = PlanBungeeMocker.setUp() .withDataFolder(tempDir.toFile()) @@ -51,7 +53,7 @@ public class BungeeMockComponent { return planMock; } - public PlanSystem getPlanSystem() throws Exception { + public PlanSystem getPlanSystem() { if (component == null) { PlanBungee planMock = getPlanMock(); component = DaggerPlanBungeeComponent.builder() diff --git a/Plan/bungeecord/src/test/java/utilities/mocks/PlanBungeeMocker.java b/Plan/bungeecord/src/test/java/utilities/mocks/PlanBungeeMocker.java index 37fff4719..4bbd1c296 100644 --- a/Plan/bungeecord/src/test/java/utilities/mocks/PlanBungeeMocker.java +++ b/Plan/bungeecord/src/test/java/utilities/mocks/PlanBungeeMocker.java @@ -25,6 +25,7 @@ import net.md_5.bungee.api.plugin.PluginDescription; import net.md_5.bungee.api.plugin.PluginManager; import org.mockito.Mockito; import utilities.TestConstants; +import utilities.TestResources; import utilities.mocks.objects.TestLogger; import java.io.File; @@ -38,9 +39,10 @@ import static org.mockito.Mockito.when; * * @author AuroraLS3 */ -public class PlanBungeeMocker extends Mocker { +public class PlanBungeeMocker { private PlanBungee planMock; + private File tempFolder; private PlanBungeeMocker() { } @@ -51,7 +53,6 @@ public class PlanBungeeMocker extends Mocker { private PlanBungeeMocker mockPlugin() { planMock = Mockito.mock(PlanBungee.class); - super.planMock = planMock; doReturn(new ColorScheme("ยง1", "ยง2", "ยง3")).when(planMock).getColorScheme(); @@ -63,11 +64,12 @@ public class PlanBungeeMocker extends Mocker { } PlanBungeeMocker withDataFolder(File tempFolder) { - when(planMock.getDataFolder()).thenReturn(tempFolder); + this.tempFolder = tempFolder; + when(planMock.getDataFolder()).thenReturn(this.tempFolder); return this; } - PlanBungeeMocker withResourceFetchingFromJar() throws Exception { + PlanBungeeMocker withResourceFetchingFromJar() { return this; } @@ -91,7 +93,8 @@ public class PlanBungeeMocker extends Mocker { } PlanBungeeMocker withPluginDescription() { - File pluginYml = getFile("/bungee.yml"); + File pluginYml = tempFolder.toPath().resolve("jar").resolve("bungee.yml").toFile(); + TestResources.copyResourceIntoFile(pluginYml, "/bungee.yml"); HashSet empty = new HashSet<>(); PluginDescription pluginDescription = new PluginDescription("Plan", "", "9.9.9", "AuroraLS3", empty, empty, pluginYml, ""); when(planMock.getDescription()).thenReturn(pluginDescription); diff --git a/Plan/common/build.gradle b/Plan/common/build.gradle index b53a20355..82b29c744 100644 --- a/Plan/common/build.gradle +++ b/Plan/common/build.gradle @@ -1,74 +1,193 @@ +import dev.vankka.dependencydownload.task.GenerateDependencyDownloadResourceTask +import org.apache.tools.ant.filters.ReplaceTokens + +plugins { + id "dev.vankka.dependencydownload.plugin" version "$dependencyDownloadVersion" + id "com.github.node-gradle.node" version "3.4.0" + id "io.swagger.core.v3.swagger-gradle-plugin" version "2.2.2" +} + +configurations { + // Runtime downloading scopes + mysqlDriver + sqliteDriver + testImplementation.extendsFrom mysqlDriver, sqliteDriver + compileOnly.extendsFrom mysqlDriver, sqliteDriver + + swaggerJson // swagger.json configuration +} + +task generateResourceForMySQLDriver(type: GenerateDependencyDownloadResourceTask) { + var conf = configurations.mysqlDriver + configuration = conf + file = "assets/plan/dependencies/" + conf.name + ".txt" + // Not necessary to include in the resource + includeShadowJarRelocations = false +} + +task generateResourceForSQLiteDriver(type: GenerateDependencyDownloadResourceTask) { + var conf = configurations.sqliteDriver + configuration = conf + file = "assets/plan/dependencies/" + conf.name + ".txt" + // Not necessary to include in the resource + includeShadowJarRelocations = false +} + dependencies { - implementation "net.playeranalytics:platform-abstraction-layer-api:$palVersion" implementation project(":api") - compileOnly project(":extensions") - implementation project(path: ":extensions", configuration: 'shadow') - implementation "org.apache.commons:commons-text:$commonsTextVersion" - implementation "org.apache.commons:commons-compress:$commonsCompressVersion" - implementation "com.github.ben-manes.caffeine:caffeine:$caffeineVersion" - implementation "mysql:mysql-connector-java:$mysqlVersion" - implementation "org.xerial:sqlite-jdbc:$sqliteVersion" - implementation "com.zaxxer:HikariCP:$hikariVersion" - implementation "org.slf4j:slf4j-nop:$slf4jVersion" - implementation "org.slf4j:slf4j-api:$slf4jVersion" - implementation "com.maxmind.geoip2:geoip2:$geoIpVersion" - implementation "com.google.code.gson:gson:$gsonVersion" + shadow project(":extensions") + + shadow "net.playeranalytics:platform-abstraction-layer-api:$palVersion" + compileOnly "net.kyori:adventure-api:4.9.3" + shadow("dev.vankka:dependencydownload-runtime:$dependencyDownloadVersion") { + // Effectively disables relocating + exclude module: "jar-relocator" + } + mysqlDriver "mysql:mysql-connector-java:$mysqlVersion" + sqliteDriver "org.xerial:sqlite-jdbc:$sqliteVersion" + + shadow "org.apache.commons:commons-text:$commonsTextVersion" + shadow "org.apache.commons:commons-compress:$commonsCompressVersion" + shadow "commons-codec:commons-codec:$commonsCodecVersion" + shadow "com.github.ben-manes.caffeine:caffeine:$caffeineVersion" + shadow "com.zaxxer:HikariCP:$hikariVersion" + shadow "org.slf4j:slf4j-nop:$slf4jVersion" + shadow "org.slf4j:slf4j-api:$slf4jVersion" + shadow "com.maxmind.geoip2:geoip2:$geoIpVersion" + shadow "com.google.code.gson:gson:$gsonVersion" + shadow "org.eclipse.jetty:jetty-server:$jettyVersion" + shadow "org.eclipse.jetty:jetty-alpn-java-server:$jettyVersion" + shadow "org.eclipse.jetty.http2:http2-server:$jettyVersion" + shadow 'com.googlecode.json-simple:json-simple:1.1.1' // json simple used by UUIDFetcher + + // Swagger annotations + implementation "jakarta.ws.rs:jakarta.ws.rs-api:3.1.0" + implementation "io.swagger.core.v3:swagger-core-jakarta:$swaggerVersion" + implementation "io.swagger.core.v3:swagger-jaxrs2-jakarta:$swaggerVersion" testImplementation project(":api") testImplementation "com.google.code.gson:gson:$gsonVersion" + testImplementation "org.seleniumhq.selenium:selenium-java:4.4.0" + testImplementation "org.testcontainers:testcontainers:$testContainersVersion" + testImplementation "org.testcontainers:junit-jupiter:$testContainersVersion" + testImplementation "org.testcontainers:nginx:$testContainersVersion" } -import org.apache.tools.ant.filters.ReplaceTokens task updateVersion(type: Copy) { from('src/main/resources') { include 'plugin.yml' include 'bungee.yml' include 'nukkit.yml' + include 'fabric.mod.json' } into 'build/sources/resources/' filter(ReplaceTokens, tokens: [version: '' + project.ext.fullVersion]) } + +node { + download = true + version = "16.14.2" + nodeProjectDir = file("$rootDir/react/dashboard") +} + +task yarnBundle(type: YarnTask) { + inputs.files(fileTree("$rootDir/react/dashboard/src")) + inputs.file("$rootDir/react/dashboard/package.json") + + outputs.dir("$rootDir/react/dashboard/build") + + dependsOn yarn_install + args = ['run', 'build'] +} + +task copyYarnBuildResults { + inputs.files(fileTree("$rootDir/react/dashboard/build")) + outputs.dir("$rootDir/common/build/resources/main/assets/plan/web") + + dependsOn yarnBundle + doLast { + mkdir "$rootDir/common/build/resources/main/assets/plan/web" + copy { + from "$rootDir/react/dashboard/build" + into "$rootDir/common/build/resources/main/assets/plan/web" + } + } +} + +task determineAssetModifications { + inputs.files(fileTree(dir: 'src/main/resources/assets/plan/web')) + inputs.files(fileTree(dir: 'src/main/resources/assets/plan/locale')) + outputs.file("build/resources/main/assets/plan/AssetVersion.yml") + + doLast { + mkdir "build/resources/main/assets/plan" + def versionFile = file("build/resources/main/assets/plan/AssetVersion.yml") + versionFile.text = "" // Clear previous build + ConfigurableFileTree tree = fileTree(dir: 'src/main/resources/assets/plan/web') + tree.forEach { File f -> + def gitModified = new ByteArrayOutputStream() + exec { + commandLine 'git', 'log', '-1', '--pretty=%ct', f.toString() + standardOutput = gitModified + } + def gitModifiedAsString = gitModified.toString().strip() + // git returns UNIX time in seconds, but most things in Java use UNIX time in milliseconds + def modified = gitModifiedAsString.isEmpty() ? System.currentTimeMillis() : Long.parseLong(gitModifiedAsString) * 1000 + def relativePath = tree.getDir().toPath().relativize(f.toPath()) // File path relative to the tree + versionFile.text += String.format( // writing YAML as raw text probably isn't the best idea + "%s: %s\n", relativePath.toString().replace('.', ','), modified + ) + } + tree = fileTree(dir: 'src/main/resources/assets/plan/locale') + tree.forEach { File f -> + def gitModified = new ByteArrayOutputStream() + exec { + commandLine 'git', 'log', '-1', '--pretty=%ct', f.toString() + standardOutput = gitModified + } + def gitModifiedAsString = gitModified.toString().strip() + // git returns UNIX time in seconds, but most things in Java use UNIX time in milliseconds + def modified = gitModifiedAsString.isEmpty() ? System.currentTimeMillis() : Long.parseLong(gitModifiedAsString) * 1000 + def relativePath = tree.getDir().toPath().relativize(f.toPath()) // File path relative to the tree + versionFile.text += String.format( // writing YAML as raw text probably isn't the best idea + "%s: %s\n", relativePath.toString().replace('.', ','), modified + ) + } + } +} + +resolve { // Swagger json generation task + outputFileName = 'swagger' + outputFormat = 'JSON' + prettyPrint = 'TRUE' + classpath = sourceSets.main.runtimeClasspath + buildClasspath = classpath + resourcePackages = [ + 'com.djrapitops.plan.delivery.webserver', + 'com.djrapitops.plan.delivery.webserver.resolver.auth', + 'com.djrapitops.plan.delivery.webserver.resolver.json', + ] + outputDir = file('build/generated-resources/swagger/assets/plan/web/') +} +task swaggerJsonJar(type: Jar) { + dependsOn resolve + archiveClassifier.set("resolve") + from 'build/generated-resources/swagger' +} +artifacts { + swaggerJson swaggerJsonJar +} + processResources { - duplicatesStrategy = DuplicatesStrategy.INCLUDE + dependsOn copyYarnBuildResults + dependsOn determineAssetModifications + dependsOn generateResourceForMySQLDriver + dependsOn generateResourceForSQLiteDriver dependsOn updateVersion + duplicatesStrategy = DuplicatesStrategy.INCLUDE from 'build/sources/resources' } shadowJar { - dependsOn processResources - - // Exclude these files - exclude "**/*.svg" - exclude "**/*.psd" - - exclude "**/module-info.class" - exclude "module-info.class" - exclude 'META-INF/versions/' // Causes Sponge to crash - exclude 'org/apache/http/**/*' // Unnecessary http client depended on by geolite2 implementation - exclude 'mozilla/**/*' - - // Exclude unnecessary SQLite drivers - exclude '**/Linux/android-arm/libsqlitejdbc.so' - exclude '**/DragonFlyBSD/**/libsqlitejdbc.so' - - relocate 'com.google.protobuf', 'plan.com.mysql.cj.x.google.protobuf' - - relocate 'com.maxmind', 'plan.com.maxmind' - relocate 'com.fasterxml', 'plan.com.fasterxml' - relocate 'com.zaxxer', 'plan.com.zaxxer' - relocate 'com.google.gson', 'plan.com.google.gson' - relocate 'com.google.errorprone', 'plan.com.google.errorprone' - relocate 'org.bstats', 'plan.org.bstats' - relocate 'org.slf4j', 'plan.org.slf4j' - - // Exclude test dependencies - exclude "org/junit/**/*" - exclude "org/opentest4j/**/*" - exclude "org/checkerframework/**/*" - exclude "org/apiguardian/**/*" - exclude "org/mockito/**/*" - exclude "org/selenium/**/*" - exclude "org/jayway/**/*" - exclude "google/protobuf/**/*" - exclude "jargs/gnu/**/*" -} \ No newline at end of file + configurations = [project.configurations.shadow] +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/DataService.java b/Plan/common/src/main/java/com/djrapitops/plan/DataService.java index 722bb20ba..997579ec3 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/DataService.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/DataService.java @@ -17,67 +17,60 @@ package com.djrapitops.plan; import com.djrapitops.plan.storage.database.queries.Query; +import com.djrapitops.plan.storage.database.transactions.Transaction; +import com.djrapitops.plan.utilities.java.TriConsumer; import java.util.Optional; -import java.util.function.Consumer; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; -/** - * Service for sourcing, mapping and consuming data. - *

- * The service is in charge of two data flows: - * - push, given to consumers - * - pull, obtained from sources - *

- * The mappers facilitate a one way type transformation if needed. - *

- * The interface is very abstract about how data is obtained, - * but here are my general ideas of where the abstraction is coming from. - * - push cause one or multiple consumers to modify stored data - * - pull cause one or multiple suppliers to fetch stored data - * - mappers are stateless type transformers in memory - *

- * Example use-case: - * - PlayerJoinEvent is mapped to a generic event - * - that generic event is then consumed and mapped until the data is in a database. - *

- * - Some kind of data is wanted to place on a web page - * - It is requested and the suppliers and mappers give the wanted type of data. - *

- * Further research needed: - * - Can this limited type system represent types of data that need parameters - * (such as differentiate between two servers data) - */ public interface DataService { - DataService push(Class type, M data); - - Optional pull(Class type); - - Optional pull(Class type, P parameter); - - B mapTo(Class toType, A from); - - default Optional pull(Class type, Class

parameterType) { - return pull(type, () -> pull(parameterType).orElse(null)); + default void push(K identifier, T value) { + push(identifier, value, (Class) value.getClass()); } - default Optional pull(Class type, Supplier

parameter) { - return pull(type, parameter.get()); + void push(K identifier, T value, Class type); + + default DataService registerOptionalMapper(Class identifierType, Class from, Class to, BiFunction> mapper) { + return registerMapper(identifierType, from, to, (id, value) -> mapper.apply(id, value).orElse(null)); } - DataService registerMapper(Class typeA, Class typeB, Function mapper); + DataService registerMapper(Class identifierType, Class from, Class to, BiFunction mapper); - DataService registerConsumer(Class type, Consumer consumer); + DataService registerMapper(Class identifierType, Class from, Class to, Function mapper); - DataService registerSupplier(Class type, Supplier supplier); + default DataService registerDataServiceMapper(Class identifierType, Class from, Class to, BiFunction mapper) { + return registerMapper(identifierType, from, to, value -> mapper.apply(this, value)); + } - DataService registerSupplier(Class type, Class

parameterType, Function supplierWithParameter); + DataService registerMapper(Class fromIdentifier, Class from, Class toIdentifier, Class to, TriConsumer> mapper); - DataService registerDBSupplier(Class type, Class

parameterType, Function> supplierWithParameter); + DataService registerSink(Class identifierType, Class type, BiConsumer consumer); - interface Mapping { + DataService registerDatabaseSink(Class identifierType, Class type, BiFunction consumer); + + Optional pull(Class type, K identifier); + + Optional pullWithoutId(Class type); + + DataService registerPullSource(Class identifierType, Class type, Function source); + + default DataService registerOptionalPullSource(Class identifierType, Class type, Function> source) { + return registerPullSource(identifierType, type, id -> source.apply(id).orElse(null)); + } + + DataService registerDatabasePullSource(Class identifierType, Class type, Function> source); + + DataService registerPullSource(Class type, Supplier source); + + DataService registerDatabasePullSource(Class type, Supplier> source); + + Optional map(K identifier, A value, Class toType); + + interface Pipeline { void register(DataService service); } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/DataSvc.java b/Plan/common/src/main/java/com/djrapitops/plan/DataSvc.java index 3f9f9b6ea..16b921a57 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/DataSvc.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/DataSvc.java @@ -18,121 +18,133 @@ package com.djrapitops.plan; import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.queries.Query; +import com.djrapitops.plan.storage.database.transactions.Transaction; +import com.djrapitops.plan.utilities.java.TriConsumer; import dagger.Lazy; import javax.inject.Inject; import javax.inject.Singleton; import java.util.*; import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Consumer; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; @Singleton public class DataSvc implements DataService { - private final MultiHashMap mappers; - private final MultiHashMap mappersReverse; - private final Map suppliers; - private final Map suppliersWithParameter; - private final MultiHashMap consumers; - private final Lazy dbSystem; + private final Map pullSources; + private final Map noIdentifierPullSources; + + private final MultiHashMap sinks; + + private final MultiHashMap mappers; + @Inject public DataSvc( Lazy dbSystem ) { this.dbSystem = dbSystem; + pullSources = new HashMap<>(); + noIdentifierPullSources = new HashMap<>(); + sinks = new MultiHashMap<>(); mappers = new MultiHashMap<>(); - mappersReverse = new MultiHashMap<>(); - suppliers = new ConcurrentHashMap<>(); - suppliersWithParameter = new ConcurrentHashMap<>(); - consumers = new MultiHashMap<>(); } @Override - public DataService push(Class type, A data) { - if (data == null) return this; - List mappers = this.mappers.get(type); - for (Mapper mapper : mappers) { - push(mapper.typeB, mapper.func.apply(data)); + public void push(K identifier, T value, Class type) { + ClassPair classPair = new ClassPair<>((Class) identifier.getClass(), type); + for (BiConsumer sink : sinks.get(classPair)) { + sink.accept(identifier, value); } - List consumers = this.consumers.get(type); - for (Consumer consumer : consumers) { - consumer.accept(data); - } - if (mappers.isEmpty() && consumers.isEmpty()) { - System.out.println("WARN: Nothing consumed " + type); + + for (Mapper mapper : mappers.get(classPair)) { + push(identifier, mapper.func.apply(identifier, value), mapper.typeB); } + } + + @Override + public Optional map(K identifier, A value, Class toType) { + ClassPair classPair = new ClassPair<>((Class) identifier.getClass(), (Class) value.getClass()); + + List candidates = this.mappers.get(classPair); + return candidates + .stream() + .filter(mapper -> Objects.equals(mapper.typeB, toType)) + .findAny() + .map(mapper -> toType.cast(mapper.func.apply(identifier, value))); + } + + @Override + public DataService registerMapper(Class identifierType, Class from, Class to, BiFunction mapper) { + ClassPair classPair = new ClassPair<>(identifierType, from); + mappers.putOne(classPair, new Mapper<>(from, to, mapper)); return this; } @Override - public Optional pull(Class type) { - Supplier present = this.suppliers.get(type); - if (present != null) return Optional.ofNullable(present.get()); - - List mappers = this.mappersReverse.get(type); - for (Mapper mapper : mappers) { - Optional found = pull(mapper.typeA).map(mapper.func); - if (found.isPresent()) return found; - } - - System.out.println("WARN: Nothing supplied " + type); - return Optional.empty(); + public DataService registerMapper(Class identifierType, Class from, Class to, Function mapper) { + return registerMapper(identifierType, from, to, (id, value) -> mapper.apply(value)); } @Override - public B mapTo(Class toType, A from) { - List mappers = this.mappers.get(from.getClass()); - for (Mapper mapper : mappers) { - if (mapper.typeB.equals(toType)) { - return toType.cast(mapper.func); - } - } - // TODO Figure out type mapping resolution when it needs more than one mapping - System.out.println("WARN: No mapper for " + from.getClass() + " -> " + toType); - return null; - } - - @Override - public DataService registerMapper(Class typeA, Class typeB, Function mapper) { - Mapper asWrapper = new Mapper<>(typeA, typeB, mapper); - // TODO Prevent two mappers for same 2 types with a data structure - mappers.putOne(typeA, asWrapper); - mappersReverse.putOne(typeB, asWrapper); + public DataService registerMapper(Class fromIdentifier, Class from, Class toIdentifier, Class to, TriConsumer> mapper) { + ClassPair classPair = new ClassPair<>(fromIdentifier, from); + sinks.putOne(classPair, (id, value) -> mapper.accept(fromIdentifier.cast(id), from.cast(value), this::push)); return this; } @Override - public DataService registerConsumer(Class type, Consumer consumer) { - consumers.putOne(type, consumer); + public DataService registerSink(Class identifierType, Class type, BiConsumer consumer) { + ClassPair classPair = new ClassPair<>(identifierType, type); + sinks.putOne(classPair, consumer); return this; } @Override - public DataService registerSupplier(Class type, Supplier supplier) { - suppliers.put(type, supplier); + public DataService registerDatabaseSink(Class identifierType, Class type, BiFunction consumer) { + return registerSink(identifierType, type, (id, value) -> dbSystem.get().getDatabase().executeTransaction(consumer.apply(id, value))); + } + + @Override + public Optional pull(Class type, K identifier) { + ClassPair classPair = new ClassPair<>((Class) identifier.getClass(), type); + return Optional.ofNullable(pullSources.get(classPair)) + .map(source -> source.apply(identifier)) + .map(type::cast); + } + + @Override + public Optional pullWithoutId(Class type) { + return Optional.ofNullable(noIdentifierPullSources.get(type)) + .map(Supplier::get) + .map(type::cast); + } + + @Override + public DataService registerPullSource(Class identifierType, Class type, Function source) { + ClassPair classPair = new ClassPair<>(identifierType, type); + pullSources.put(classPair, source); return this; } @Override - public Optional pull(Class type, P parameter) { - if (parameter == null) return Optional.empty(); - Function function = suppliersWithParameter.get(new ClassPair<>(type, parameter.getClass())); - return function != null ? Optional.of(function.apply(parameter)) : Optional.empty(); + public DataService registerDatabasePullSource(Class identifierType, Class type, Function> source) { + return registerPullSource(identifierType, type, identifier -> dbSystem.get().getDatabase().query(source.apply(identifier))); } @Override - public DataService registerSupplier(Class type, Class

parameterType, Function supplierWithParameter) { - suppliersWithParameter.put(new ClassPair<>(type, parameterType), supplierWithParameter); + public DataService registerPullSource(Class type, Supplier source) { + noIdentifierPullSources.put(type, source); return this; } @Override - public DataService registerDBSupplier(Class type, Class

parameterType, Function> queryVisitor) { - return registerSupplier(type, parameterType, parameter -> dbSystem.get().getDatabase().query(queryVisitor.apply(parameter))); + public DataService registerDatabasePullSource(Class type, Supplier> source) { + return registerPullSource(type, () -> dbSystem.get().getDatabase().query(source.get())); } private static class ClassPair { @@ -159,16 +171,6 @@ public class DataSvc implements DataService { } } - private static class KeyValuePair { - final K key; - final V value; - - public KeyValuePair(K key, V value) { - this.key = key; - this.value = value; - } - } - private static class MultiHashMap extends ConcurrentHashMap> { void putOne(A key, B value) { @@ -176,15 +178,14 @@ public class DataSvc implements DataService { values.add(value); put(key, values); } - } - private static class Mapper { + private static class Mapper { final Class typeA; final Class typeB; - final Function func; + final BiFunction func; - public Mapper(Class typeA, Class typeB, Function func) { + public Mapper(Class typeA, Class typeB, BiFunction func) { this.typeA = typeA; this.typeB = typeB; this.func = func; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/PlanPlugin.java b/Plan/common/src/main/java/com/djrapitops/plan/PlanPlugin.java index 504a1d61b..57eb02949 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/PlanPlugin.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/PlanPlugin.java @@ -18,6 +18,7 @@ package com.djrapitops.plan; import com.djrapitops.plan.commands.use.ColorScheme; import com.djrapitops.plan.commands.use.Subcommand; +import net.playeranalytics.plugin.PluginInformation; import java.io.File; import java.io.InputStream; @@ -45,7 +46,10 @@ public interface PlanPlugin { void onDisable(); - @Deprecated + /** + * @deprecated Use {@code @Named("dataFolder") File}, or {@link PluginInformation#getDataFolder()} + */ + @Deprecated(since = "2021-03-09") File getDataFolder(); } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/PlanSystem.java b/Plan/common/src/main/java/com/djrapitops/plan/PlanSystem.java index f229c2b77..8bb3879f6 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/PlanSystem.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/PlanSystem.java @@ -19,6 +19,7 @@ package com.djrapitops.plan; import com.djrapitops.plan.api.PlanAPI; import com.djrapitops.plan.delivery.DeliveryUtilities; import com.djrapitops.plan.delivery.export.ExportSystem; +import com.djrapitops.plan.delivery.formatting.Formatters; import com.djrapitops.plan.delivery.web.ResolverSvc; import com.djrapitops.plan.delivery.web.ResourceSvc; import com.djrapitops.plan.delivery.webserver.NonProxyWebserverDisableChecker; @@ -55,6 +56,8 @@ import javax.inject.Singleton; @Singleton public class PlanSystem implements SubSystem { + private static final long SERVER_ENABLE_TIME = System.currentTimeMillis(); + private boolean enabled = false; private final PlanFiles files; @@ -134,21 +137,39 @@ public class PlanSystem implements SubSystem { this.logger = logger; this.errorLogger = errorLogger; - logger.info(""); + logger.info("ยง2"); logger.info("ยง2 โ–ˆโ–ˆโ–Œ"); logger.info("ยง2 โ–ˆโ–ˆโ–Œ โ–ˆโ–ˆโ–Œ"); logger.info("ยง2 โ–ˆโ–ˆโ–Œโ–ˆโ–ˆโ–Œโ–ˆโ–ˆโ–Œโ–ˆโ–ˆโ–Œ ยง2Player Analytics"); logger.info("ยง2 โ–ˆโ–ˆโ–Œโ–ˆโ–ˆโ–Œโ–ˆโ–ˆโ–Œโ–ˆโ–ˆโ–Œ ยงfv" + versionChecker.getCurrentVersion()); - logger.info(""); + logger.info("ยง2"); } - @Deprecated + public static long getServerEnableTime() { + return SERVER_ENABLE_TIME; + } + + /** + * @deprecated Use {@link com.djrapitops.plan.delivery.webserver.Addresses} instead. + */ + @Deprecated(since = "Addresses.java") public String getMainAddress() { return webServerSystem.getAddresses().getMainAddress().orElse(webServerSystem.getAddresses().getFallbackLocalhostAddress()); } - @Override - public void enable() { + /** + * Enables only the systems that are required for {@link com.djrapitops.plan.commands.PlanCommand}. + * + * @see #enableOtherThanCommands() + */ + public void enableForCommands() { + enableSystems(configSystem); + } + + /** + * Enables the rest of the systems that are not enabled in {@link #enableForCommands()}. + */ + public void enableOtherThanCommands() { extensionService.register(); resolverService.register(); resourceService.register(); @@ -159,7 +180,6 @@ public class PlanSystem implements SubSystem { enableSystems( files, - configSystem, localeSystem, versionChecker, databaseSystem, @@ -182,6 +202,15 @@ public class PlanSystem implements SubSystem { extensionService.registerExtensions(); enabled = true; + + String javaVersion = System.getProperty("java.specification.version"); + if ("1.8".equals(javaVersion) || "9".equals(javaVersion) || "10".equals(javaVersion) + ) { + logger.warn("! ------- Deprecation warning ------- !"); + logger.warn("Plan version 5.5 will require Java 11 or newer,"); + logger.warn("consider updating your JVM as soon as possible."); + logger.warn("! ----------------------------------- !"); + } } private void enableSystems(SubSystem... systems) { @@ -191,23 +220,9 @@ public class PlanSystem implements SubSystem { } @Override - public void disable() { - enabled = false; - disableSystems( - taskSystem, - cacheSystem, - listenerSystem, - importSystem, - exportSystem, - processing, - databaseSystem, - webServerSystem, - serverInfo, - localeSystem, - configSystem, - files, - versionChecker - ); + public void enable() { + enableForCommands(); + enableOtherThanCommands(); } private void disableSystems(SubSystem... systems) { @@ -288,14 +303,27 @@ public class PlanSystem implements SubSystem { return extensionService; } - /** - * Originally visible for testing purposes. - * - * @return the error logger of the system - * @deprecated A smell, dagger should be used to construct things instead. - */ - @Deprecated - public ErrorLogger getErrorLogger() { - return errorLogger; + @Override + public void disable() { + enabled = false; + Formatters.clearSingleton(); + + extensionService.disableUpdates(); + + disableSystems( + taskSystem, + cacheSystem, + listenerSystem, + importSystem, + exportSystem, + processing, + databaseSystem, + webServerSystem, + serverInfo, + localeSystem, + configSystem, + files, + versionChecker + ); } -} \ No newline at end of file +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/api/CommonAPI.java b/Plan/common/src/main/java/com/djrapitops/plan/api/CommonAPI.java index 02ce3ee30..6d8c68a9d 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/api/CommonAPI.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/api/CommonAPI.java @@ -46,7 +46,7 @@ import java.util.stream.Collectors; * @deprecated Plan API v4 has been deprecated, use the APIv5 instead (https://github.com/plan-player-analytics/Plan/wiki/APIv5). */ @Singleton -@Deprecated +@Deprecated(forRemoval = true, since = "5.0") public class CommonAPI implements PlanAPI { private final DBSystem dbSystem; @@ -107,7 +107,7 @@ public class CommonAPI implements PlanAPI { @Override public ServerContainer fetchServerContainer(UUID serverUUID) { - return new ServerContainer(new com.djrapitops.plan.delivery.domain.container.ServerContainer()); + return new ServerContainer(); } @Override diff --git a/Plan/common/src/main/java/com/djrapitops/plan/api/PlanAPI.java b/Plan/common/src/main/java/com/djrapitops/plan/api/PlanAPI.java index 8e1724a6a..2f5fa51d8 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/api/PlanAPI.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/api/PlanAPI.java @@ -37,7 +37,7 @@ import java.util.UUID; * @author AuroraLS3 * @deprecated Plan API v4 has been deprecated, use the APIv5 instead (https://github.com/plan-player-analytics/Plan/wiki/APIv5). */ -@Deprecated +@Deprecated(since = "5.0") public interface PlanAPI { static PlanAPI getInstance() { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/api/data/PlayerContainer.java b/Plan/common/src/main/java/com/djrapitops/plan/api/data/PlayerContainer.java index edfa87da3..c308dcd13 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/api/data/PlayerContainer.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/api/data/PlayerContainer.java @@ -31,7 +31,7 @@ import java.util.Optional; * @author AuroraLS3 * @deprecated Plan API v4 has been deprecated, use the APIv5 instead (https://github.com/plan-player-analytics/Plan/wiki/APIv5). */ -@Deprecated +@Deprecated(since = "5.0") public class PlayerContainer { private final com.djrapitops.plan.delivery.domain.container.PlayerContainer container; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/api/data/ServerContainer.java b/Plan/common/src/main/java/com/djrapitops/plan/api/data/ServerContainer.java index 90f3f92d3..1c93f364f 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/api/data/ServerContainer.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/api/data/ServerContainer.java @@ -24,23 +24,18 @@ import java.util.Optional; * Wrapper for a ServerContainer. *

* The actual object is wrapped to avoid exposing too much API that might change. - * See {@link com.djrapitops.plan.delivery.domain.keys.ServerKeys} for Key objects. *

* The Keys might change in the future, but the Optional API should help dealing with those cases. * * @author AuroraLS3 * @deprecated Plan API v4 has been deprecated, use the APIv5 instead (https://github.com/plan-player-analytics/Plan/wiki/APIv5). */ -@Deprecated +@Deprecated(forRemoval = true, since = "5.0") public class ServerContainer { - private final com.djrapitops.plan.delivery.domain.container.ServerContainer container; - - public ServerContainer(com.djrapitops.plan.delivery.domain.container.ServerContainer container) { - this.container = container; - } + public ServerContainer() {/*Empty constructor, no-op derpecated api class*/} public Optional getValue(Key key) { - return container.getValue(key); + return Optional.empty(); } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/commands/PlanCommand.java b/Plan/common/src/main/java/com/djrapitops/plan/commands/PlanCommand.java index 23fc1a6c6..fa0ecc5d8 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/commands/PlanCommand.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/commands/PlanCommand.java @@ -332,12 +332,36 @@ public class PlanCommand { .subcommand(clearCommand()) .subcommand(removeCommand()) .subcommand(uninstalledCommand()) + .subcommand(removeJoinAddressesCommand()) + .subcommand(onlineUuidMigration()) .requirePermission(Permissions.DATA_BASE) .description(locale.getString(HelpLang.DB)) .inDepthDescription(locale.getString(DeepHelpLang.DB)) .build(); } + private Subcommand onlineUuidMigration() { + return Subcommand.builder() + .aliases("migrate_to_online_uuids", "migratetoonlineuuids") + .requirePermission(Permissions.DATA_CLEAR) + .optionalArgument("--remove_offline", "Remove offline players if given") + .description(locale.getString(HelpLang.ONLINE_UUID_MIGRATION)) + .inDepthDescription("Moves and combines offline uuid data to online uuids where possible. Leaves offline-only players to database.") + .onCommand((sender, arguments) -> databaseCommands.onOnlineConversion(commandName, sender, arguments)) + .build(); + } + + private Subcommand removeJoinAddressesCommand() { + return Subcommand.builder() + .aliases("remove_join_addresses", "removejoinaddresses") + .requirePermission(Permissions.DATA_CLEAR) + .requiredArgument(locale.getString(HelpLang.ARG_SERVER), locale.getString(HelpLang.DESC_ARG_SERVER_IDENTIFIER)) + .description(locale.getString(HelpLang.JOIN_ADDRESS_REMOVAL)) + .onCommand((sender, arguments) -> databaseCommands.onFixFabricJoinAddresses(commandName, sender, arguments)) + .onTabComplete(this::serverNames) + .build(); + } + private Subcommand backupCommand() { return Subcommand.builder() .aliases("backup") @@ -369,7 +393,7 @@ public class PlanCommand { return DBType.names(); } Optional firstArgument = arguments.get(0); - if (!firstArgument.isPresent()) { + if (firstArgument.isEmpty()) { return tabCompleteCache.getMatchingBackupFilenames(null); } String part = firstArgument.get(); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/commands/TabCompleteCache.java b/Plan/common/src/main/java/com/djrapitops/plan/commands/TabCompleteCache.java index d98d05a90..0be2ba566 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/commands/TabCompleteCache.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/commands/TabCompleteCache.java @@ -20,6 +20,7 @@ import com.djrapitops.plan.SubSystem; import com.djrapitops.plan.delivery.domain.auth.User; import com.djrapitops.plan.identification.Server; import com.djrapitops.plan.identification.ServerUUID; +import com.djrapitops.plan.processing.Processing; import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.queries.objects.ServerQueries; import com.djrapitops.plan.storage.database.queries.objects.UserIdentifierQueries; @@ -39,6 +40,7 @@ import java.util.stream.Collectors; @Singleton public class TabCompleteCache implements SubSystem { + private final Processing processing; private final PlanFiles files; private final DBSystem dbSystem; @@ -49,9 +51,11 @@ public class TabCompleteCache implements SubSystem { @Inject public TabCompleteCache( + Processing processing, PlanFiles files, DBSystem dbSystem ) { + this.processing = processing; this.files = files; this.dbSystem = dbSystem; playerIdentifiers = new ArrayList<>(); @@ -62,15 +66,17 @@ public class TabCompleteCache implements SubSystem { @Override public void enable() { - refreshPlayerIdentifiers(); - refreshServerIdentifiers(); - refreshUserIdentifiers(); - refreshBackupFileNames(); + processing.submitNonCritical(() -> { + refreshPlayerIdentifiers(); + refreshServerIdentifiers(); + refreshUserIdentifiers(); + refreshBackupFileNames(); - Collections.sort(playerIdentifiers); - Collections.sort(serverIdentifiers); - Collections.sort(userIdentifiers); - Collections.sort(backupFileNames); + Collections.sort(playerIdentifiers); + Collections.sort(serverIdentifiers); + Collections.sort(userIdentifiers); + Collections.sort(backupFileNames); + }); } private void refreshServerIdentifiers() { @@ -109,22 +115,30 @@ public class TabCompleteCache implements SubSystem { } public List getMatchingServerIdentifiers(String searchFor) { - if (searchFor == null || searchFor.isEmpty()) return serverIdentifiers; + if (searchFor == null || searchFor.isEmpty()) { + return serverIdentifiers.size() < 100 ? serverIdentifiers : Collections.emptyList(); + } return serverIdentifiers.stream().filter(identifier -> identifier.startsWith(searchFor)).collect(Collectors.toList()); } public List getMatchingPlayerIdentifiers(String searchFor) { - if (searchFor == null || searchFor.isEmpty()) return playerIdentifiers; + if (searchFor == null || searchFor.isEmpty()) { + return playerIdentifiers.size() < 100 ? playerIdentifiers : Collections.emptyList(); + } return playerIdentifiers.stream().filter(identifier -> identifier.startsWith(searchFor)).collect(Collectors.toList()); } public List getMatchingUserIdentifiers(String searchFor) { - if (searchFor == null || searchFor.isEmpty()) return userIdentifiers; + if (searchFor == null || searchFor.isEmpty()) { + return userIdentifiers.size() < 100 ? userIdentifiers : Collections.emptyList(); + } return userIdentifiers.stream().filter(identifier -> identifier.startsWith(searchFor)).collect(Collectors.toList()); } public List getMatchingBackupFilenames(String searchFor) { - if (searchFor == null || searchFor.isEmpty()) return backupFileNames; + if (searchFor == null || searchFor.isEmpty()) { + return backupFileNames.size() < 100 ? backupFileNames : Collections.emptyList(); + } return backupFileNames.stream().filter(identifier -> identifier.startsWith(searchFor)).collect(Collectors.toList()); } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/commands/subcommands/Confirmation.java b/Plan/common/src/main/java/com/djrapitops/plan/commands/subcommands/Confirmation.java index a1e080072..ccf3c497c 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/commands/subcommands/Confirmation.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/commands/subcommands/Confirmation.java @@ -39,7 +39,7 @@ public class Confirmation { ) { this.locale = locale; awaiting = Caffeine.newBuilder() - .expireAfterWrite(90, TimeUnit.SECONDS) + .expireAfterWrite(5, TimeUnit.MINUTES) .build(); } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/commands/subcommands/DataUtilityCommands.java b/Plan/common/src/main/java/com/djrapitops/plan/commands/subcommands/DataUtilityCommands.java index e844e3daa..0a8319df2 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/commands/subcommands/DataUtilityCommands.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/commands/subcommands/DataUtilityCommands.java @@ -231,7 +231,7 @@ public class DataUtilityCommands { } PlayerContainer player = dbSystem.getDatabase().query(ContainerFetchQueries.fetchPlayerContainer(playerUUID)); - if (!player.getValue(PlayerKeys.REGISTERED).isPresent()) { + if (player.getValue(PlayerKeys.REGISTERED).isEmpty()) { throw new IllegalArgumentException(locale.getString(CommandLang.FAIL_PLAYER_NOT_FOUND_REGISTER, identifier)); } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/commands/subcommands/DatabaseCommands.java b/Plan/common/src/main/java/com/djrapitops/plan/commands/subcommands/DatabaseCommands.java index 684bde7e1..8624da57a 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/commands/subcommands/DatabaseCommands.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/commands/subcommands/DatabaseCommands.java @@ -19,12 +19,16 @@ package com.djrapitops.plan.commands.subcommands; import com.djrapitops.plan.commands.use.Arguments; import com.djrapitops.plan.commands.use.CMDSender; import com.djrapitops.plan.commands.use.ColorScheme; +import com.djrapitops.plan.commands.use.MessageBuilder; import com.djrapitops.plan.delivery.formatting.Formatter; import com.djrapitops.plan.delivery.formatting.Formatters; import com.djrapitops.plan.exceptions.database.DBOpException; +import com.djrapitops.plan.gathering.domain.BaseUser; import com.djrapitops.plan.identification.Identifiers; import com.djrapitops.plan.identification.Server; import com.djrapitops.plan.identification.ServerInfo; +import com.djrapitops.plan.identification.ServerUUID; +import com.djrapitops.plan.processing.Processing; import com.djrapitops.plan.query.QuerySvc; import com.djrapitops.plan.settings.config.PlanConfig; import com.djrapitops.plan.settings.config.paths.DatabaseSettings; @@ -35,21 +39,24 @@ import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.DBType; import com.djrapitops.plan.storage.database.Database; import com.djrapitops.plan.storage.database.SQLiteDB; +import com.djrapitops.plan.storage.database.queries.objects.BaseUserQueries; import com.djrapitops.plan.storage.database.queries.objects.ServerQueries; import com.djrapitops.plan.storage.database.transactions.BackupCopyTransaction; -import com.djrapitops.plan.storage.database.transactions.commands.RemoveEverythingTransaction; -import com.djrapitops.plan.storage.database.transactions.commands.RemovePlayerTransaction; -import com.djrapitops.plan.storage.database.transactions.commands.SetServerAsUninstalledTransaction; +import com.djrapitops.plan.storage.database.transactions.Transaction; +import com.djrapitops.plan.storage.database.transactions.commands.*; +import com.djrapitops.plan.storage.database.transactions.patches.BadFabricJoinAddressValuePatch; import com.djrapitops.plan.storage.file.PlanFiles; import com.djrapitops.plan.utilities.logging.ErrorContext; import com.djrapitops.plan.utilities.logging.ErrorLogger; +import net.playeranalytics.plugin.player.UUIDFetcher; import javax.inject.Inject; import javax.inject.Singleton; import java.io.File; import java.io.IOException; -import java.util.UUID; +import java.util.*; import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; @Singleton public class DatabaseCommands { @@ -66,8 +73,10 @@ public class DatabaseCommands { private final Identifiers identifiers; private final PluginStatusCommands statusCommands; private final ErrorLogger errorLogger; + private final Processing processing; private final Formatter timestamp; + private final Formatter clock; @Inject public DatabaseCommands( @@ -83,7 +92,8 @@ public class DatabaseCommands { Formatters formatters, Identifiers identifiers, PluginStatusCommands statusCommands, - ErrorLogger errorLogger + ErrorLogger errorLogger, + Processing processing ) { this.locale = locale; this.confirmation = confirmation; @@ -99,6 +109,8 @@ public class DatabaseCommands { this.errorLogger = errorLogger; this.timestamp = formatters.iso8601NoClockLong(); + clock = formatters.clockLong(); + this.processing = processing; } public void onBackup(CMDSender sender, Arguments arguments) { @@ -323,6 +335,56 @@ public class DatabaseCommands { } } + public void onFixFabricJoinAddresses(String mainCommand, CMDSender sender, Arguments arguments) { + String identifier = arguments.concatenate(" "); + Optional serverUUID = identifiers.getServerUUID(identifier); + if (serverUUID.isEmpty()) { + throw new IllegalArgumentException(locale.getString(CommandLang.FAIL_SERVER_NOT_FOUND, identifier)); + } + + Database database = dbSystem.getDatabase(); + + if (sender.supportsChatEvents()) { + sender.buildMessage() + .addPart(colors.getMainColor() + locale.getString(CommandLang.CONFIRM_JOIN_ADDRESS_REMOVAL, identifier, database.getType().getName())).newLine() + .addPart(colors.getTertiaryColor() + locale.getString(CommandLang.CONFIRM)) + .addPart("ยง2ยงl[\u2714]").command("/" + mainCommand + " accept").hover(locale.getString(CommandLang.CONFIRM_ACCEPT)) + .addPart(" ") + .addPart("ยง4ยงl[\u2718]").command("/" + mainCommand + " cancel").hover(locale.getString(CommandLang.CONFIRM_DENY)) + .send(); + } else { + sender.buildMessage() + .addPart(colors.getMainColor() + locale.getString(CommandLang.CONFIRM_JOIN_ADDRESS_REMOVAL, identifier, database.getType().getName())).newLine() + .addPart(colors.getTertiaryColor() + locale.getString(CommandLang.CONFIRM)).addPart("ยงa/" + mainCommand + " accept") + .addPart(" ") + .addPart("ยงc/" + mainCommand + " cancel") + .send(); + } + + confirmation.confirm(sender, choice -> { + if (Boolean.TRUE.equals(choice)) { + performJoinAddressRemoval(sender, serverUUID.get(), database); + } else { + sender.send(colors.getMainColor() + locale.getString(CommandLang.CONFIRM_CANCELLED_DATA)); + } + }); + } + + private void performJoinAddressRemoval(CMDSender sender, ServerUUID serverUUID, Database database) { + try { + sender.send(locale.getString(CommandLang.DB_WRITE, database.getType().getName())); + database.executeTransaction(new BadFabricJoinAddressValuePatch(serverUUID)) + .thenRunAsync(() -> sender.send(locale.getString(CommandLang.PROGRESS_SUCCESS))) + .exceptionally(error -> { + sender.send(locale.getString(CommandLang.PROGRESS_FAIL, error.getMessage())); + return null; + }); + } catch (DBOpException e) { + sender.send(locale.getString(CommandLang.PROGRESS_FAIL, e.getMessage())); + errorLogger.error(e, ErrorContext.builder().related(sender, database.getType().getName()).build()); + } + } + public void onRemove(String mainCommand, CMDSender sender, Arguments arguments) { String identifier = arguments.concatenate(" "); UUID playerUUID = identifiers.getPlayerUUID(identifier); @@ -387,7 +449,6 @@ public class DatabaseCommands { String identifier = arguments.concatenate(" "); Server server = dbSystem.getDatabase() .query(ServerQueries.fetchServerMatchingIdentifier(identifier)) - .filter(s -> !s.isProxy()) .orElseThrow(() -> new IllegalArgumentException(locale.getString(CommandLang.FAIL_SERVER_NOT_FOUND, identifier))); if (server.getUuid().equals(serverInfo.getServerUUID())) { @@ -420,4 +481,102 @@ public class DatabaseCommands { } statusCommands.onReload(sender); } + + public void onOnlineConversion(String mainCommand, CMDSender sender, Arguments arguments) { + boolean removeOfflinePlayers = arguments.get(0) + .map("--remove_offline"::equals) + .orElse(false); + sender.send(locale.getString(CommandLang.PROGRESS_PREPARING)); + processing.submitNonCritical(() -> { + Map baseUsersByUUID = dbSystem.getDatabase().query(BaseUserQueries.fetchAllBaseUsersByUUID()); + List playerNames = baseUsersByUUID.values().stream().map(BaseUser::getName).collect(Collectors.toList()); + sender.send("Performing lookup for " + playerNames.size() + " uuids from Mojang.."); + sender.send("Preparation estimated complete at: " + clock.apply(System.currentTimeMillis() + playerNames.size() * 100) + " (due to request rate limiting)"); + Map onlineUUIDsOfPlayers = getUUIDViaUUIDFetcher(playerNames); + + if (onlineUUIDsOfPlayers.isEmpty()) { + sender.send(locale.getString(CommandLang.PROGRESS_FAIL, "Did not get any UUIDs from Mojang.")); + return; + } + + int totalProfiles = baseUsersByUUID.size(); + int offlineOnlyUsers = 0; + int combine = 0; + int move = 0; + + List transactions = new ArrayList<>(); + + for (BaseUser user : baseUsersByUUID.values()) { + String playerName = user.getName(); + UUID recordedUUID = user.getUuid(); + UUID actualUUID = onlineUUIDsOfPlayers.get(playerName); + + if (actualUUID == null) { + offlineOnlyUsers++; + if (removeOfflinePlayers) transactions.add(new RemovePlayerTransaction(recordedUUID)); + continue; + } + if (recordedUUID == actualUUID) { + continue; + } + BaseUser alreadyExistingProfile = baseUsersByUUID.get(actualUUID); + if (alreadyExistingProfile == null) { + move++; + transactions.add(new ChangeUserUUIDTransaction(recordedUUID, actualUUID)); + } else { + combine++; + transactions.add(new CombineUserTransaction(recordedUUID, actualUUID)); + } + } + + MessageBuilder messageBuilder = sender.buildMessage() + .addPart(colors.getMainColor() + "Moving to online-only UUIDs (irreversible):").newLine() + .addPart(colors.getSecondaryColor() + " Total players in database: " + totalProfiles).newLine() + .addPart(colors.getSecondaryColor() + (removeOfflinePlayers ? "Removing (no online UUID): " : " Offline only (no online UUID): ") + offlineOnlyUsers).newLine() + .addPart(colors.getSecondaryColor() + " Moving to new UUID: " + move).newLine() + .addPart(colors.getSecondaryColor() + " Combining offline and online profiles: " + combine).newLine() + .newLine() + .addPart(colors.getSecondaryColor() + " Estimated online UUID players in database after: " + (totalProfiles - combine - offlineOnlyUsers) + (removeOfflinePlayers ? "" : " (+" + offlineOnlyUsers + " offline)")).newLine() + .addPart(colors.getTertiaryColor() + locale.getString(CommandLang.CONFIRM)); + if (sender.supportsChatEvents()) { + messageBuilder + .addPart("ยง2ยงl[\u2714]").command("/" + mainCommand + " accept").hover(locale.getString(CommandLang.CONFIRM_ACCEPT)) + .addPart(" ") + .addPart("ยง4ยงl[\u2718]").command("/" + mainCommand + " cancel").hover(locale.getString(CommandLang.CONFIRM_DENY)) + .send(); + } else { + messageBuilder + .addPart(colors.getTertiaryColor() + locale.getString(CommandLang.CONFIRM)).addPart("ยงa/" + mainCommand + " accept") + .addPart(" ") + .addPart("ยงc/" + mainCommand + " cancel") + .send(); + } + + confirmation.confirm(sender, choice -> { + if (Boolean.TRUE.equals(choice)) { + transactions.forEach(dbSystem.getDatabase()::executeTransaction); + dbSystem.getDatabase().executeTransaction(new Transaction() { + @Override + protected void performOperations() { + sender.send(locale.getString(CommandLang.PROGRESS_SUCCESS)); + } + }); + } else { + sender.send(colors.getMainColor() + locale.getString(CommandLang.CONFIRM_CANCELLED_DATA)); + } + }); + + }); + } + + private Map getUUIDViaUUIDFetcher(List playerNames) { + try { + return new UUIDFetcher(playerNames).call(); + } catch (Exception | NoClassDefFoundError failure) { + errorLogger.error(failure, ErrorContext.builder() + .related("Migrating offline uuids to online uuids") + .build()); + return new HashMap<>(); + } + } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/commands/subcommands/LinkCommands.java b/Plan/common/src/main/java/com/djrapitops/plan/commands/subcommands/LinkCommands.java index 13f430cbe..7088684f3 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/commands/subcommands/LinkCommands.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/commands/subcommands/LinkCommands.java @@ -125,7 +125,7 @@ public class LinkCommands { String serversListed = dbSystem.getDatabase() .query(ServerQueries.fetchPlanServerInformationCollection()) .stream().sorted() - .map(server -> m + server.getId().orElse(0) + "::" + t + server.getName() + "::" + s + server.getUuid() + "\n") + .map(server -> m + server.getId().orElse(0) + "::" + t + server.getName() + "::" + s + server.getUuid() + "::" + s + server.getPlanVersion() + "\n") .collect(StringBuilder::new, StringBuilder::append, StringBuilder::append) .toString(); sender.buildMessage() @@ -194,7 +194,7 @@ public class LinkCommands { .addPart(colors.getMainColor() + locale.getString(CommandLang.LINK_NETWORK)) .apply(builder -> linkTo(builder, sender, address)) .send(); - if (!dbSystem.getDatabase().query(ServerQueries.fetchProxyServerInformation()).isPresent()) { + if (dbSystem.getDatabase().query(ServerQueries.fetchProxyServerInformation()).isEmpty()) { throw new IllegalArgumentException(locale.getString(CommandLang.NOTIFY_NO_NETWORK)); } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/commands/subcommands/RegistrationCommands.java b/Plan/common/src/main/java/com/djrapitops/plan/commands/subcommands/RegistrationCommands.java index c6f0c3e3f..3609284ef 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/commands/subcommands/RegistrationCommands.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/commands/subcommands/RegistrationCommands.java @@ -31,8 +31,8 @@ import com.djrapitops.plan.settings.locale.lang.HelpLang; import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.Database; import com.djrapitops.plan.storage.database.queries.objects.WebUserQueries; -import com.djrapitops.plan.storage.database.transactions.commands.RegisterWebUserTransaction; import com.djrapitops.plan.storage.database.transactions.commands.RemoveWebUserTransaction; +import com.djrapitops.plan.storage.database.transactions.commands.StoreWebUserTransaction; import com.djrapitops.plan.utilities.PassEncryptUtil; import com.djrapitops.plan.utilities.logging.ErrorContext; import com.djrapitops.plan.utilities.logging.ErrorLogger; @@ -158,7 +158,7 @@ public class RegistrationCommands { boolean userExists = database.query(WebUserQueries.fetchUser(username)).isPresent(); if (userExists) throw new IllegalArgumentException(locale.getString(CommandLang.FAIL_WEB_USER_EXISTS)); - database.executeTransaction(new RegisterWebUserTransaction(user)) + database.executeTransaction(new StoreWebUserTransaction(user)) .get(); // Wait for completion sender.send(locale.getString(CommandLang.WEB_USER_REGISTER_SUCCESS, username)); @@ -177,31 +177,26 @@ public class RegistrationCommands { UUID playerUUID = sender.getUUID().orElse(null); String username; - if (!givenUsername.isPresent() && playerUUID != null) { - Optional found = database.query(WebUserQueries.fetchUser(playerUUID)); - if (!found.isPresent()) { - throw new IllegalArgumentException(locale.getString(CommandLang.USER_NOT_LINKED)); - } - username = found.get().getUsername(); - } else if (!givenUsername.isPresent()) { + if (givenUsername.isEmpty() && playerUUID != null) { + username = database.query(WebUserQueries.fetchUser(playerUUID)) + .map(User::getUsername) + .orElseThrow(() -> new IllegalArgumentException(locale.getString(CommandLang.USER_NOT_LINKED))); + } else if (givenUsername.isEmpty()) { throw new IllegalArgumentException(locale.getString(CommandLang.FAIL_REQ_ONE_ARG, "<" + locale.getString(HelpLang.ARG_USERNAME) + ">")); } else { username = givenUsername.get(); } - Optional found = database.query(WebUserQueries.fetchUser(username)); - if (!found.isPresent()) { - throw new IllegalArgumentException(locale.getString(FailReason.USER_DOES_NOT_EXIST)); - } - User presentUser = found.get(); - boolean ownsTheUser = Objects.equals(playerUUID, presentUser.getLinkedToUUID()); + User user = database.query(WebUserQueries.fetchUser(username)) + .orElseThrow(() -> new IllegalArgumentException(locale.getString(FailReason.USER_DOES_NOT_EXIST))); + boolean ownsTheUser = Objects.equals(playerUUID, user.getLinkedToUUID()); if (!(ownsTheUser || sender.hasPermission(Permissions.UNREGISTER_OTHER.getPerm()))) { throw new IllegalArgumentException(locale.getString(CommandLang.USER_NOT_LINKED)); } if (sender.supportsChatEvents()) { sender.buildMessage() - .addPart(colors.getMainColor() + locale.getString(CommandLang.CONFIRM_UNREGISTER, presentUser.getUsername(), presentUser.getLinkedTo())).newLine() + .addPart(colors.getMainColor() + locale.getString(CommandLang.CONFIRM_UNREGISTER, user.getUsername(), user.getLinkedTo())).newLine() .addPart(colors.getTertiaryColor() + locale.getString(CommandLang.CONFIRM)) .addPart("ยง2ยงl[\u2714]").command("/" + mainCommand + " accept").hover(locale.getString(CommandLang.CONFIRM_ACCEPT)) .addPart(" ") @@ -209,7 +204,7 @@ public class RegistrationCommands { .send(); } else { sender.buildMessage() - .addPart(colors.getMainColor() + locale.getString(CommandLang.CONFIRM_UNREGISTER, presentUser.getUsername(), presentUser.getLinkedTo())).newLine() + .addPart(colors.getMainColor() + locale.getString(CommandLang.CONFIRM_UNREGISTER, user.getUsername(), user.getLinkedTo())).newLine() .addPart(colors.getTertiaryColor() + locale.getString(CommandLang.CONFIRM)).addPart("ยงa/" + mainCommand + " accept") .addPart(" ") .addPart("ยงc/" + mainCommand + " cancel") @@ -219,7 +214,7 @@ public class RegistrationCommands { confirmation.confirm(sender, choice -> { if (Boolean.TRUE.equals(choice)) { try { - sender.send(colors.getMainColor() + locale.getString(CommandLang.UNREGISTER, presentUser.getUsername())); + sender.send(colors.getMainColor() + locale.getString(CommandLang.UNREGISTER, user.getUsername())); database.executeTransaction(new RemoveWebUserTransaction(username)) .get(); // Wait for completion ActiveCookieStore.removeUserCookie(username); @@ -230,19 +225,15 @@ public class RegistrationCommands { errorLogger.warn(e, ErrorContext.builder().related("unregister command", sender, sender.getPlayerName().orElse("console"), arguments).build()); } } else { - sender.send(colors.getMainColor() + locale.getString(CommandLang.CONFIRM_CANCELLED_UNREGISTER, presentUser.getUsername())); + sender.send(colors.getMainColor() + locale.getString(CommandLang.CONFIRM_CANCELLED_UNREGISTER, user.getUsername())); } }); } public void onLogoutCommand(CMDSender sender, Arguments arguments) { - Optional username = arguments.get(0); - if (!username.isPresent()) { - throw new IllegalArgumentException(locale.getString(CommandLang.FAIL_REQ_ONE_ARG, locale.getString(HelpLang.ARG_USERNAME) + "/*")); - } - - String loggingOut = username.get(); + String loggingOut = arguments.get(0) + .orElseThrow(() -> new IllegalArgumentException(locale.getString(CommandLang.FAIL_REQ_ONE_ARG, locale.getString(HelpLang.ARG_USERNAME) + "/*"))); if ("*".equals(loggingOut)) { activeCookieStore.removeAll(); diff --git a/Plan/velocity/src/main/java/com/djrapitops/plan/commands/use/VelocityMessageBuilder.java b/Plan/common/src/main/java/com/djrapitops/plan/commands/use/AdventureMessageBuilder.java similarity index 89% rename from Plan/velocity/src/main/java/com/djrapitops/plan/commands/use/VelocityMessageBuilder.java rename to Plan/common/src/main/java/com/djrapitops/plan/commands/use/AdventureMessageBuilder.java index e83071d73..9669f2433 100644 --- a/Plan/velocity/src/main/java/com/djrapitops/plan/commands/use/VelocityMessageBuilder.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/commands/use/AdventureMessageBuilder.java @@ -16,6 +16,7 @@ */ package com.djrapitops.plan.commands.use; +import net.kyori.adventure.audience.Audience; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.event.ClickEvent; @@ -24,16 +25,21 @@ import org.apache.commons.text.TextStringBuilder; import java.util.Collection; -public class VelocityMessageBuilder implements MessageBuilder { +/** + * Shared Adventure {@link MessageBuilder} shared by Velocity and Sponge. + */ +public class AdventureMessageBuilder implements MessageBuilder { - private final VelocityCMDSender sender; + private final CMDSender sender; + private final Audience audience; private final TextComponent.Builder builder; // Store reference to previous component to properly add hover & click events private Component previousComponent; - public VelocityMessageBuilder(VelocityCMDSender sender) { + AdventureMessageBuilder(CMDSender sender, Audience audience) { this.sender = sender; + this.audience = audience; builder = Component.text(); } @@ -112,6 +118,6 @@ public class VelocityMessageBuilder implements MessageBuilder { if (previousComponent != null) { builder.append(previousComponent); } - sender.commandSource.sendMessage(builder.build()); + audience.sendMessage(builder.build()); } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/commands/use/ChatFormatter.java b/Plan/common/src/main/java/com/djrapitops/plan/commands/use/ChatFormatter.java index b8a799b4b..918f1bf34 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/commands/use/ChatFormatter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/commands/use/ChatFormatter.java @@ -22,11 +22,11 @@ import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.List; -public abstract class ChatFormatter { +public interface ChatFormatter { - public abstract int getWidth(String part); + int getWidth(String part); - public String table(String message, String separator) { + default String table(String message, String separator) { String[] lines = StringUtils.split(message, '\n'); List rows = new ArrayList<>(); Maximum.ForInteger rowWidth = new Maximum.ForInteger(0); @@ -54,7 +54,7 @@ public abstract class ChatFormatter { return table.toString(); } - public List tableAsParts(String message, String separator) { + default List tableAsParts(String message, String separator) { String[] lines = StringUtils.split(message, '\n'); List rows = new ArrayList<>(); Maximum.ForInteger rowWidth = new Maximum.ForInteger(0); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/commands/use/CommandWithSubcommands.java b/Plan/common/src/main/java/com/djrapitops/plan/commands/use/CommandWithSubcommands.java index db3def93f..1b4a893f4 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/commands/use/CommandWithSubcommands.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/commands/use/CommandWithSubcommands.java @@ -50,6 +50,21 @@ public class CommandWithSubcommands extends Subcommand { return subcommands.stream().filter(sender::hasAllPermissionsFor).collect(Collectors.toList()); } + public List getSubcommands() { + return subcommands; + } + + public Optional findSubCommand(Arguments arguments) { + return arguments.get(0).flatMap(alias -> { + for (Subcommand subcommand : subcommands) { + if (subcommand.getAliases().contains(alias)) { + return Optional.of(subcommand); + } + } + return Optional.empty(); + }); + } + public void onHelp(CMDSender sender, Arguments arguments) { List hasPermissionFor = getPermittedSubcommands(sender); sender.buildMessage() diff --git a/Plan/common/src/main/java/com/djrapitops/plan/commands/use/ConsoleChatFormatter.java b/Plan/common/src/main/java/com/djrapitops/plan/commands/use/ConsoleChatFormatter.java index 2acf5115c..2f5e1f13f 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/commands/use/ConsoleChatFormatter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/commands/use/ConsoleChatFormatter.java @@ -18,7 +18,7 @@ package com.djrapitops.plan.commands.use; import org.apache.commons.lang3.StringUtils; -public class ConsoleChatFormatter extends ChatFormatter { +public class ConsoleChatFormatter implements ChatFormatter { @Override public int getWidth(String part) { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/commands/use/ConsoleMessageBuilder.java b/Plan/common/src/main/java/com/djrapitops/plan/commands/use/ConsoleMessageBuilder.java new file mode 100644 index 000000000..4c8bcc203 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/commands/use/ConsoleMessageBuilder.java @@ -0,0 +1,83 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.commands.use; + +import java.util.Collection; +import java.util.function.Consumer; + +public class ConsoleMessageBuilder implements MessageBuilder { + + private final StringBuilder stringBuilder; + private final Consumer messageBus; + + public ConsoleMessageBuilder(Consumer messageBus) { + this.messageBus = messageBus; + this.stringBuilder = new StringBuilder(); + } + + @Override + public MessageBuilder addPart(String msg) { + stringBuilder.append(msg); + return this; + } + + @Override + public MessageBuilder newLine() { + return addPart("\n"); + } + + @Override + public MessageBuilder link(String address) { + return addPart(address); + } + + @Override + public MessageBuilder command(String command) { + return this; + } + + @Override + public MessageBuilder hover(String text) { + return this; + } + + @Override + public MessageBuilder hover(String... text) { + return this; + } + + @Override + public MessageBuilder hover(Collection text) { + return this; + } + + @Override + public MessageBuilder indent(int spaces) { + stringBuilder.append(" ".repeat(Math.max(0, spaces))); + return this; + } + + @Override + public MessageBuilder tabular(CharSequence columnSeparator) { + return this; + } + + @Override + public void send() { + messageBus.accept(stringBuilder.toString()); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/commands/use/PlayerChatFormatter.java b/Plan/common/src/main/java/com/djrapitops/plan/commands/use/PlayerChatFormatter.java index dc48e638b..f6d8820fd 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/commands/use/PlayerChatFormatter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/commands/use/PlayerChatFormatter.java @@ -16,7 +16,7 @@ */ package com.djrapitops.plan.commands.use; -public class PlayerChatFormatter extends ChatFormatter { +public class PlayerChatFormatter implements ChatFormatter { @Override public int getWidth(String part) { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/data/element/AnalysisContainer.java b/Plan/common/src/main/java/com/djrapitops/plan/data/element/AnalysisContainer.java index 5d3b4bfca..618f566f0 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/data/element/AnalysisContainer.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/data/element/AnalysisContainer.java @@ -29,7 +29,7 @@ import java.util.UUID; * @see InspectContainer * @deprecated PluginData API has been deprecated - see https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API for new API. */ -@Deprecated +@Deprecated(since = "5.0") public final class AnalysisContainer extends InspectContainer { private final Map> playerTableValues; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/data/element/InspectContainer.java b/Plan/common/src/main/java/com/djrapitops/plan/data/element/InspectContainer.java index 541fd690b..964ca0462 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/data/element/InspectContainer.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/data/element/InspectContainer.java @@ -29,7 +29,7 @@ import java.util.TreeMap; * @see TableContainer * @deprecated PluginData API has been deprecated - see https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API for new API. */ -@Deprecated +@Deprecated(since = "5.0") public class InspectContainer { protected final List values; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/data/element/TableContainer.java b/Plan/common/src/main/java/com/djrapitops/plan/data/element/TableContainer.java index 1dc3eff33..e265e21f5 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/data/element/TableContainer.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/data/element/TableContainer.java @@ -28,7 +28,7 @@ import java.util.List; * @author AuroraLS3 * @deprecated PluginData API has been deprecated - see https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API for new API. */ -@Deprecated +@Deprecated(since = "5.0") public class TableContainer { protected final String[] header; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/data/plugin/BanData.java b/Plan/common/src/main/java/com/djrapitops/plan/data/plugin/BanData.java index d5aff5533..ea2e31153 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/data/plugin/BanData.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/data/plugin/BanData.java @@ -25,7 +25,7 @@ import java.util.UUID; * @author AuroraLS3 * @deprecated PluginData API has been deprecated - see https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API for new API. */ -@Deprecated +@Deprecated(since = "5.0") public interface BanData { boolean isBanned(UUID uuid); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/data/plugin/PluginData.java b/Plan/common/src/main/java/com/djrapitops/plan/data/plugin/PluginData.java index d420092b6..96f849a09 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/data/plugin/PluginData.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/data/plugin/PluginData.java @@ -35,7 +35,7 @@ import java.util.UUID; * @author AuroraLS3 * @deprecated PluginData API has been deprecated - see https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API for new API. */ -@Deprecated +@Deprecated(since = "5.0") public abstract class PluginData { private final ContainerSize size; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/data/store/containers/AnalysisContainer.java b/Plan/common/src/main/java/com/djrapitops/plan/data/store/containers/AnalysisContainer.java index 2c6b37cec..377bfb139 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/data/store/containers/AnalysisContainer.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/data/store/containers/AnalysisContainer.java @@ -24,6 +24,6 @@ import com.djrapitops.plan.delivery.domain.container.DynamicDataContainer; * @author AuroraLS3 * @deprecated AnalysisContainer is no longer used. */ -@Deprecated +@Deprecated(since = "5.0") public class AnalysisContainer extends DynamicDataContainer { } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/JoinAddressCount.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/JoinAddressCount.java new file mode 100644 index 000000000..f9cf75f15 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/JoinAddressCount.java @@ -0,0 +1,72 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.domain; + +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.Objects; + +/** + * Represents a single join address - number pair. + * + * @author AuroraLS3 + */ +public class JoinAddressCount implements Comparable { + + private final int count; + private String joinAddress; + + public JoinAddressCount(Map.Entry entry) { + this(entry.getKey(), entry.getValue()); + } + + public JoinAddressCount(String joinAddress, int count) { + this.joinAddress = joinAddress; + this.count = count; + } + + public String getJoinAddress() { + return joinAddress; + } + + public void setJoinAddress(String joinAddress) { + this.joinAddress = joinAddress; + } + + public int getCount() { + return count; + } + + @Override + public int compareTo(@NotNull JoinAddressCount other) { + return String.CASE_INSENSITIVE_ORDER.compare(this.joinAddress, other.joinAddress); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + JoinAddressCount that = (JoinAddressCount) o; + return getCount() == that.getCount() && Objects.equals(getJoinAddress(), that.getJoinAddress()); + } + + @Override + public int hashCode() { + return Objects.hash(getJoinAddress(), getCount()); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/SessionEndTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/JoinAddressCounts.java similarity index 55% rename from Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/SessionEndTransaction.java rename to Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/JoinAddressCounts.java index ab80cc47e..988c533b2 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/SessionEndTransaction.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/JoinAddressCounts.java @@ -14,27 +14,29 @@ * You should have received a copy of the GNU Lesser General Public License * along with Plan. If not, see . */ -package com.djrapitops.plan.storage.database.transactions.events; +package com.djrapitops.plan.delivery.domain; -import com.djrapitops.plan.gathering.domain.FinishedSession; -import com.djrapitops.plan.storage.database.queries.DataStoreQueries; -import com.djrapitops.plan.storage.database.transactions.Transaction; +import java.util.List; /** - * Transaction for storing a session after a session has ended. - * * @author AuroraLS3 */ -public class SessionEndTransaction extends Transaction { +public class JoinAddressCounts implements DateHolder { - private final FinishedSession session; + private final long date; + private final List joinAddresses; - public SessionEndTransaction(FinishedSession session) { - this.session = session; + public JoinAddressCounts(long date, List joinAddresses) { + this.date = date; + this.joinAddresses = joinAddresses; } @Override - protected void performOperations() { - execute(DataStoreQueries.storeSession(session)); + public long getDate() { + return date; } -} \ No newline at end of file + + public List getJoinAddresses() { + return joinAddresses; + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/PlayerIdentifier.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/PlayerIdentifier.java new file mode 100644 index 000000000..664ce15e4 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/PlayerIdentifier.java @@ -0,0 +1,67 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.domain; + +import java.util.Objects; +import java.util.UUID; + +public class PlayerIdentifier { + private final UUID uuid; + private final String name; + + public PlayerIdentifier(UUID uuid, String name) { + this.uuid = uuid; + this.name = name; + } + + public UUID getUuid() { + return uuid; + } + + public String getName() { + return name; + } + + public boolean isSame(PlayerIdentifier that) { + return Objects.equals(this, that); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PlayerIdentifier that = (PlayerIdentifier) o; + return Objects.equals(getUuid(), that.getUuid()); + } + + @Override + public int hashCode() { + return Objects.hash(getUuid(), getName()); + } + + @Override + public String toString() { + return "PlayerIdentifier{" + + "uuid=" + uuid + + ", name='" + name + '\'' + + '}'; + } + + public String toJson() { + return "{\"name\": \"" + name + "\", \"uuid\": \"" + uuid + "\"}"; + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/ServerIdentifier.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/ServerIdentifier.java new file mode 100644 index 000000000..9bcda2799 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/ServerIdentifier.java @@ -0,0 +1,73 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.domain; + +import com.djrapitops.plan.identification.ServerUUID; + +import java.util.Objects; + +public class ServerIdentifier { + + private final ServerUUID uuid; + private final String name; + + public ServerIdentifier(ServerUUID uuid, String name) { + this.uuid = uuid; + this.name = name; + } + + public ServerIdentifier(ServerUUID serverUUID, ServerName serverName) { + this(serverUUID, serverName.get()); + } + + public ServerUUID getUuid() { + return uuid; + } + + public String getName() { + return name; + } + + public boolean isSame(ServerIdentifier that) { + return Objects.equals(getUuid(), that.getUuid()) && Objects.equals(getName(), that.getName()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ServerIdentifier that = (ServerIdentifier) o; + return Objects.equals(getUuid(), that.getUuid()) && Objects.equals(getName(), that.getName()); + } + + @Override + public int hashCode() { + return Objects.hash(getUuid(), getName()); + } + + @Override + public String toString() { + return "ServerIdentifier{" + + "uuid=" + uuid + + ", name='" + name + '\'' + + '}'; + } + + public String toJson() { + return "{\"name\": \"" + name + "\", \"uuid\": {\"uuid\": \"" + uuid + "\"}}"; + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/TimeSegment.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/TimeSegment.java new file mode 100644 index 000000000..8e0c46f71 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/TimeSegment.java @@ -0,0 +1,75 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.domain; + +import java.util.Comparator; +import java.util.Objects; + +public class TimeSegment { + + private final long start; + private final long end; + private final T value; + + public TimeSegment(long start, long end, T value) { + this.start = start; + this.end = end; + this.value = value; + } + + public static Comparator> earliestStartFirstComparator() { + return Comparator.comparingLong(segment -> segment.start); + } + + public static Comparator> earliestEndFirstComparator() { + return Comparator.comparingLong(segment -> segment.end); + } + + public long getStart() { + return start; + } + + public long getEnd() { + return end; + } + + public T getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TimeSegment that = (TimeSegment) o; + return getStart() == that.getStart() && getEnd() == that.getEnd() && Objects.equals(getValue(), that.getValue()); + } + + @Override + public int hashCode() { + return Objects.hash(getStart(), getEnd(), getValue()); + } + + @Override + public String toString() { + return "TimeSegment{" + + "start=" + start + + ", end=" + end + + ", value=" + value + + '}'; + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/WebUser.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/WebUser.java index ff5a149e0..f41ab67ac 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/WebUser.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/WebUser.java @@ -24,10 +24,10 @@ import java.util.Objects; * Object containing webserver security user information. * * @author AuroraLS3 - *

+ * @deprecated Use {@link com.djrapitops.plan.delivery.domain.auth.User} instead * TODO Rewrite Authentication stuff */ -@Deprecated +@Deprecated(since = "2022-02-12, User.java") public class WebUser { private final String username; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/auth/User.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/auth/User.java index 26faf409d..e89dff5dc 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/auth/User.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/auth/User.java @@ -51,7 +51,7 @@ public class User implements Comparable { } public WebUser toWebUser() { - return new WebUser(linkedTo, username, permissions); + return new WebUser(linkedTo, linkedToUUID, username, permissions); } public String getUsername() { @@ -70,12 +70,18 @@ public class User implements Comparable { return passwordHash; } - @Deprecated + /** + * @deprecated Permission list should be used instead. + */ + @Deprecated(since = "2022-05-04", forRemoval = true) public int getPermissionLevel() { return permissionLevel; } - @Deprecated + /** + * @deprecated Permission list should be used instead. + */ + @Deprecated(since = "2022-05-04", forRemoval = true) public void setPermissionLevel(int permissionLevel) { this.permissionLevel = permissionLevel; } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/FilterDto.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/FilterDto.java new file mode 100644 index 000000000..bbfe971e5 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/FilterDto.java @@ -0,0 +1,73 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.domain.datatransfer; + +import com.djrapitops.plan.storage.database.queries.filter.Filter; + +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; + +/** + * Represents a query filter. + * + * @see Filter + * @see com.djrapitops.plan.modules.FiltersModule + */ +public class FilterDto implements Comparable { + private final String kind; + private final Map options; + private final String[] expectedParameters; + + public FilterDto(String kind, Filter filter) { + this.kind = kind; + this.options = filter.getOptions(); + this.expectedParameters = filter.getExpectedParameters(); + } + + public String getKind() { + return kind; + } + + public Map getOptions() { + return options; + } + + public String[] getExpectedParameters() { + return expectedParameters; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FilterDto that = (FilterDto) o; + return Objects.equals(kind, that.kind) && Objects.equals(options, that.options) && Arrays.equals(expectedParameters, that.expectedParameters); + } + + @Override + public int hashCode() { + int result = Objects.hash(kind, options); + result = 31 * result + Arrays.hashCode(expectedParameters); + return result; + } + + @Override + public int compareTo(FilterDto o) { + return String.CASE_INSENSITIVE_ORDER.compare(this.kind, o.kind); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/SpecifiedFilterInformation.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/InputFilterDto.java similarity index 78% rename from Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/SpecifiedFilterInformation.java rename to Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/InputFilterDto.java index 0555c289b..013b5ec62 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/SpecifiedFilterInformation.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/InputFilterDto.java @@ -14,8 +14,9 @@ * You should have received a copy of the GNU Lesser General Public License * along with Plan. If not, see . */ -package com.djrapitops.plan.storage.database.queries.filter; +package com.djrapitops.plan.delivery.domain.datatransfer; +import com.djrapitops.plan.storage.database.queries.filter.Filter; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; @@ -27,18 +28,18 @@ import java.util.*; * * @author AuroraLS3 */ -public class SpecifiedFilterInformation { +public class InputFilterDto { private final String kind; private final Map parameters; - public SpecifiedFilterInformation(String kind, Map parameters) { + public InputFilterDto(String kind, Map parameters) { this.kind = kind; this.parameters = parameters; } - public static List parse(String json) throws IOException { - return new Gson().getAdapter(new TypeToken>() {}).fromJson(json); + public static List parse(String json, Gson gson) throws IOException { + return gson.getAdapter(new TypeToken>() {}).fromJson(json); } public String getKind() { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/InputQueryDto.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/InputQueryDto.java new file mode 100644 index 000000000..243a32101 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/InputQueryDto.java @@ -0,0 +1,60 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.domain.datatransfer; + +import java.util.List; +import java.util.Objects; + +public class InputQueryDto { + + public final List filters; + private final ViewDto view; + + public InputQueryDto(ViewDto view, List filters) { + this.view = view; + this.filters = filters; + } + + public ViewDto getView() { + return view; + } + + public List getFilters() { + return filters; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + InputQueryDto that = (InputQueryDto) o; + return Objects.equals(getView(), that.getView()) && Objects.equals(getFilters(), that.getFilters()); + } + + @Override + public int hashCode() { + return Objects.hash(getView(), getFilters()); + } + + @Override + public String toString() { + return "InputQueryDto{" + + "view=" + view + + ", filters=" + filters + + '}'; + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/ServerDto.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/ServerDto.java new file mode 100644 index 000000000..38cd401f2 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/ServerDto.java @@ -0,0 +1,81 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.domain.datatransfer; + +import com.djrapitops.plan.identification.Server; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +/** + * Represents outgoing server information json. + */ +public class ServerDto implements Comparable { + + private final String serverUUID; + private final String serverName; + private final boolean proxy; + + public ServerDto(String serverUUID, String serverName, boolean proxy) { + this.serverUUID = serverUUID; + this.serverName = serverName; + this.proxy = proxy; + } + + public static ServerDto fromServer(Server server) { + return new ServerDto(server.getUuid().toString(), server.getIdentifiableName(), server.isProxy()); + } + + public String getServerUUID() { + return serverUUID; + } + + public String getServerName() { + return serverName; + } + + public boolean isProxy() { + return proxy; + } + + @Override + public int compareTo(@NotNull ServerDto other) { + return String.CASE_INSENSITIVE_ORDER.compare(this.serverName, other.serverName); + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + if (other == null || getClass() != other.getClass()) return false; + ServerDto serverDto = (ServerDto) other; + return isProxy() == serverDto.isProxy() && Objects.equals(getServerUUID(), serverDto.getServerUUID()) && Objects.equals(getServerName(), serverDto.getServerName()); + } + + @Override + public int hashCode() { + return Objects.hash(getServerUUID(), getServerName(), isProxy()); + } + + @Override + public String toString() { + return "ServerDto{" + + "serverUUID='" + serverUUID + '\'' + + ", serverName='" + serverName + '\'' + + ", proxy=" + proxy + + '}'; + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/ViewDto.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/ViewDto.java new file mode 100644 index 000000000..12d899923 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/ViewDto.java @@ -0,0 +1,85 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.domain.datatransfer; + +import com.djrapitops.plan.delivery.formatting.Formatter; +import com.djrapitops.plan.delivery.formatting.Formatters; +import com.djrapitops.plan.identification.ServerUUID; +import org.apache.commons.lang3.StringUtils; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * Represents query page view that the user wants to see data for. + */ +public class ViewDto { + private static final String DATE_PATTERN = "dd/MM/yyyy kk:mm"; + + private final String afterDate; + private final String afterTime; + private final String beforeDate; + private final String beforeTime; + private final List servers; + + public ViewDto(Formatters formatters, List servers) { + this.servers = servers; + long now = System.currentTimeMillis(); + long monthAgo = now - TimeUnit.DAYS.toMillis(30); + + Formatter formatter = formatters.javascriptDateFormatterLong(); + String[] after = StringUtils.split(formatter.apply(monthAgo), " "); + String[] before = StringUtils.split(formatter.apply(now), " "); + + this.afterDate = after[0]; + this.afterTime = after[1]; + this.beforeDate = before[0]; + this.beforeTime = before[1]; + } + + public long getAfterEpochMs() throws ParseException { + return new SimpleDateFormat(DATE_PATTERN).parse(afterDate + " " + afterTime).getTime(); + } + + public long getBeforeEpochMs() throws ParseException { + return new SimpleDateFormat(DATE_PATTERN).parse(beforeDate + " " + beforeTime).getTime(); + } + + public List getServerUUIDs() { + return servers.stream() + .map(ServerDto::getServerUUID) + .map(ServerUUID::fromString) + .collect(Collectors.toList()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ViewDto viewDto = (ViewDto) o; + return Objects.equals(afterDate, viewDto.afterDate) && Objects.equals(afterTime, viewDto.afterTime) && Objects.equals(beforeDate, viewDto.beforeDate) && Objects.equals(beforeTime, viewDto.beforeTime) && Objects.equals(servers, viewDto.servers); + } + + @Override + public int hashCode() { + return Objects.hash(afterDate, afterTime, beforeDate, beforeTime, servers); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/ExtensionDataDto.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/ExtensionDataDto.java new file mode 100644 index 000000000..8bef31b08 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/ExtensionDataDto.java @@ -0,0 +1,77 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.domain.datatransfer.extension; + +import com.djrapitops.plan.extension.implementation.results.ExtensionData; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class ExtensionDataDto { + + private final ExtensionInformationDto extensionInformation; + private final List tabs; + + private final boolean onlyGenericTab; + private final boolean wide; + + public ExtensionDataDto(ExtensionData extensionData) { + this.extensionInformation = new ExtensionInformationDto(extensionData.getExtensionInformation()); + this.tabs = extensionData.getTabs().stream().map(ExtensionTabDataDto::new).collect(Collectors.toList()); + + onlyGenericTab = extensionData.hasOnlyGenericTab(); + wide = extensionData.doesNeedWiderSpace(); + } + + public ExtensionInformationDto getExtensionInformation() { + return extensionInformation; + } + + public List getTabs() { + return tabs; + } + + public boolean isOnlyGenericTab() { + return onlyGenericTab; + } + + public boolean isWide() { + return wide; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ExtensionDataDto that = (ExtensionDataDto) o; + return Objects.equals(getExtensionInformation(), that.getExtensionInformation()) && Objects.equals(getTabs(), that.getTabs()); + } + + @Override + public int hashCode() { + return Objects.hash(getExtensionInformation(), getTabs()); + } + + @Override + public String toString() { + return "ExtensionDataDto{" + + "extensionInformation=" + extensionInformation + + ", tabs=" + tabs + + '}'; + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/ExtensionDescriptionDto.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/ExtensionDescriptionDto.java new file mode 100644 index 000000000..aed21226d --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/ExtensionDescriptionDto.java @@ -0,0 +1,82 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.domain.datatransfer.extension; + +import com.djrapitops.plan.extension.implementation.results.ExtensionDescription; + +import java.util.Objects; + +public class ExtensionDescriptionDto { + + private final String name; + private final String text; + private final String description; + private final IconDto icon; + private final int priority; + + public ExtensionDescriptionDto(ExtensionDescription description) { + this.name = description.getName(); + this.text = description.getText(); + this.description = description.getDescription().orElse(null); + this.icon = new IconDto(description.getIcon()); + this.priority = description.getPriority(); + } + + public String getName() { + return name; + } + + public String getText() { + return text; + } + + public String getDescription() { + return description; + } + + public IconDto getIcon() { + return icon; + } + + public int getPriority() { + return priority; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ExtensionDescriptionDto that = (ExtensionDescriptionDto) o; + return getPriority() == that.getPriority() && Objects.equals(getName(), that.getName()) && Objects.equals(getText(), that.getText()) && Objects.equals(getDescription(), that.getDescription()) && Objects.equals(getIcon(), that.getIcon()); + } + + @Override + public int hashCode() { + return Objects.hash(getName(), getText(), getDescription(), getIcon(), getPriority()); + } + + @Override + public String toString() { + return "ExtensionDescriptionDto{" + + "name='" + name + '\'' + + ", text='" + text + '\'' + + ", description='" + description + '\'' + + ", icon=" + icon + + ", priority=" + priority + + '}'; + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/ExtensionInformationDto.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/ExtensionInformationDto.java new file mode 100644 index 000000000..5d0c04f24 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/ExtensionInformationDto.java @@ -0,0 +1,61 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.domain.datatransfer.extension; + +import com.djrapitops.plan.extension.implementation.results.ExtensionInformation; + +import java.util.Objects; + +public class ExtensionInformationDto { + + private final String pluginName; + private final IconDto icon; + + public ExtensionInformationDto(ExtensionInformation extensionInformation) { + this.pluginName = extensionInformation.getPluginName(); + this.icon = new IconDto(extensionInformation.getIcon()); + } + + public String getPluginName() { + return pluginName; + } + + public IconDto getIcon() { + return icon; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ExtensionInformationDto that = (ExtensionInformationDto) o; + return Objects.equals(getPluginName(), that.getPluginName()) && Objects.equals(getIcon(), that.getIcon()); + } + + @Override + public int hashCode() { + return Objects.hash(getPluginName(), getIcon()); + } + + @Override + public String toString() { + return "ExtensionInformationDto{" + + "pluginName='" + pluginName + '\'' + + ", icon=" + icon + + '}'; + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/ExtensionTabDataDto.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/ExtensionTabDataDto.java new file mode 100644 index 000000000..c2307a3f2 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/ExtensionTabDataDto.java @@ -0,0 +1,94 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.domain.datatransfer.extension; + +import com.djrapitops.plan.delivery.formatting.Formatter; +import com.djrapitops.plan.delivery.formatting.Formatters; +import com.djrapitops.plan.extension.FormatType; +import com.djrapitops.plan.extension.implementation.results.ExtensionTabData; + +import java.util.*; +import java.util.stream.Collectors; + +public class ExtensionTabDataDto { + + private final TabInformationDto tabInformation; // Can be null in case where no tab was defined for provider. + private final List values; + private final List tableData; + + public ExtensionTabDataDto(ExtensionTabData extensionTabData) { + this.tabInformation = new TabInformationDto(extensionTabData.getTabInformation()); + values = constructValues(extensionTabData.getValueOrder(), extensionTabData); + tableData = extensionTabData.getTableData().stream().map(ExtensionTableDataDto::new).collect(Collectors.toList()); + } + + private List constructValues(List order, ExtensionTabData tabData) { + Formatters formatters = Formatters.getInstance(); + Formatter decimalFormatter = formatters.decimals(); + Formatter percentageFormatter = formatters.percentage(); + + Map> numberFormatters = new EnumMap<>(FormatType.class); + numberFormatters.put(FormatType.DATE_SECOND, formatters.secondLong()); + numberFormatters.put(FormatType.DATE_YEAR, formatters.yearLong()); + numberFormatters.put(FormatType.TIME_MILLISECONDS, formatters.timeAmount()); + numberFormatters.put(FormatType.NONE, Object::toString); + + List extensionValues = new ArrayList<>(); + for (String key : order) { + tabData.getBoolean(key).ifPresent(data -> extensionValues.add(new ExtensionValueDataDto(data.getDescription(), "BOOLEAN", data.getFormattedValue()))); + tabData.getDouble(key).ifPresent(data -> extensionValues.add(new ExtensionValueDataDto(data.getDescription(), "DOUBLE", data.getFormattedValue(decimalFormatter)))); + tabData.getPercentage(key).ifPresent(data -> extensionValues.add(new ExtensionValueDataDto(data.getDescription(), "PERCENTAGE", data.getFormattedValue(percentageFormatter)))); + tabData.getNumber(key).ifPresent(data -> extensionValues.add(new ExtensionValueDataDto(data.getDescription(), data.getFormatType() == FormatType.NONE ? "NUMBER" : data.getFormatType().name(), data.getFormattedValue(numberFormatters.get(data.getFormatType()))))); + tabData.getString(key).ifPresent(data -> extensionValues.add(new ExtensionValueDataDto(data.getDescription(), data.isPlayerName() ? "HTML" : "STRING", data.getFormattedValue()))); + } + return extensionValues; + } + + public TabInformationDto getTabInformation() { + return tabInformation; + } + + public List getValues() { + return values; + } + + public List getTableData() { + return tableData; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ExtensionTabDataDto that = (ExtensionTabDataDto) o; + return Objects.equals(getTabInformation(), that.getTabInformation()) && Objects.equals(getValues(), that.getValues()) && Objects.equals(getTableData(), that.getTableData()); + } + + @Override + public int hashCode() { + return Objects.hash(getTabInformation(), getValues(), getTableData()); + } + + @Override + public String toString() { + return "ExtensionTabDataDto{" + + "tabInformation=" + tabInformation + + ", values=" + values + + ", tableData=" + tableData + + '}'; + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/ExtensionTableDataDto.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/ExtensionTableDataDto.java new file mode 100644 index 000000000..3941150c7 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/ExtensionTableDataDto.java @@ -0,0 +1,85 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.domain.datatransfer.extension; + +import com.djrapitops.plan.delivery.rendering.html.icon.Color; +import com.djrapitops.plan.extension.implementation.results.ExtensionTableData; + +import java.util.Objects; + +public class ExtensionTableDataDto { + + private final String tableName; + private final TableDto table; + private final String tableColor; + private final String tableColorClass; + private final boolean wide; + + public ExtensionTableDataDto(ExtensionTableData extensionTableData) { + tableName = extensionTableData.getProviderName(); + tableColor = extensionTableData.getTableColor().name(); + tableColorClass = Color.getByName(extensionTableData.getTableColor().name()).orElse(Color.NONE) + .getBackgroundColorClass(); + table = new TableDto(extensionTableData.getTable()); + + wide = extensionTableData.isWideTable(); + } + + public String getTableName() { + return tableName; + } + + public TableDto getTable() { + return table; + } + + public String getTableColor() { + return tableColor; + } + + public String getTableColorClass() { + return tableColorClass; + } + + public boolean isWide() { + return wide; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ExtensionTableDataDto that = (ExtensionTableDataDto) o; + return isWide() == that.isWide() && Objects.equals(getTableName(), that.getTableName()) && Objects.equals(getTable(), that.getTable()) && Objects.equals(getTableColor(), that.getTableColor()) && Objects.equals(getTableColorClass(), that.getTableColorClass()); + } + + @Override + public int hashCode() { + return Objects.hash(getTableName(), getTable(), getTableColor(), getTableColorClass(), isWide()); + } + + @Override + public String toString() { + return "ExtensionTableDataDto{" + + "tableName='" + tableName + '\'' + + ", table=" + table + + ", tableColor='" + tableColor + '\'' + + ", tableColorClass='" + tableColorClass + '\'' + + ", wide=" + wide + + '}'; + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/ExtensionValueDataDto.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/ExtensionValueDataDto.java new file mode 100644 index 000000000..d0c3c7bf6 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/ExtensionValueDataDto.java @@ -0,0 +1,67 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.domain.datatransfer.extension; + +import com.djrapitops.plan.extension.implementation.results.ExtensionDescription; + +import java.util.Objects; + +public class ExtensionValueDataDto { + + private final ExtensionDescriptionDto description; + private final String type; + private final Object value; + + public ExtensionValueDataDto(ExtensionDescription description, String type, Object value) { + this.description = new ExtensionDescriptionDto(description); + this.type = type; + this.value = value; + } + + public ExtensionDescriptionDto getDescription() { + return description; + } + + public Object getValue() { + return value; + } + + public String getType() { + return type; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ExtensionValueDataDto that = (ExtensionValueDataDto) o; + return Objects.equals(getDescription(), that.getDescription()) && Objects.equals(getValue(), that.getValue()); + } + + @Override + public int hashCode() { + return Objects.hash(getDescription(), getValue()); + } + + @Override + public String toString() { + return "ExtensionValueDataDto{" + + "description=" + description + + ", value=" + value + + '}'; + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/ExtensionsDto.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/ExtensionsDto.java new file mode 100644 index 000000000..51d13db75 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/ExtensionsDto.java @@ -0,0 +1,77 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.domain.datatransfer.extension; + +import com.djrapitops.plan.extension.implementation.results.ExtensionData; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class ExtensionsDto { + + private final String playerUUID; + private final String serverUUID; + private final String serverName; + private final List extensionData; + + public ExtensionsDto(String playerUUID, String serverUUID, String serverName, List extensionData) { + this.playerUUID = playerUUID; + this.serverUUID = serverUUID; + this.serverName = serverName; + this.extensionData = extensionData.stream().sorted().map(ExtensionDataDto::new).collect(Collectors.toList()); + } + + public String getServerUUID() { + return serverUUID; + } + + public String getServerName() { + return serverName; + } + + public List getExtensionData() { + return extensionData; + } + + public String getPlayerUUID() { + return playerUUID; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ExtensionsDto that = (ExtensionsDto) o; + return Objects.equals(getPlayerUUID(), that.getPlayerUUID()) && Objects.equals(getServerUUID(), that.getServerUUID()) && Objects.equals(getServerName(), that.getServerName()) && Objects.equals(getExtensionData(), that.getExtensionData()); + } + + @Override + public int hashCode() { + return Objects.hash(getPlayerUUID(), getServerUUID(), getServerName(), getExtensionData()); + } + + @Override + public String toString() { + return "ExtensionsDto{" + + "playerUUID='" + playerUUID + '\'' + + ", serverUUID='" + serverUUID + '\'' + + ", serverName='" + serverName + '\'' + + ", extensionData=" + extensionData + + '}'; + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/IconDto.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/IconDto.java new file mode 100644 index 000000000..70564b65a --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/IconDto.java @@ -0,0 +1,84 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.domain.datatransfer.extension; + + +import com.djrapitops.plan.delivery.rendering.html.icon.Icon; + +import java.util.Objects; + +public class IconDto { + + private final String family; + private final String familyClass; + private final String color; + private final String colorClass; + private final String iconName; + + public IconDto(com.djrapitops.plan.extension.icon.Icon extensionIcon) { + Icon icon = Icon.fromExtensionIcon(extensionIcon); + family = icon.getFamily().name(); + familyClass = icon.getFamily().getFamilyClass(); + color = icon.getColor().name(); + colorClass = icon.getColor().getHtmlClass(); + iconName = icon.getName(); + } + + public String getFamily() { + return family; + } + + public String getFamilyClass() { + return familyClass; + } + + public String getColor() { + return color; + } + + public String getColorClass() { + return colorClass; + } + + public String getIconName() { + return iconName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + IconDto iconDto = (IconDto) o; + return Objects.equals(getFamily(), iconDto.getFamily()) && Objects.equals(getFamilyClass(), iconDto.getFamilyClass()) && Objects.equals(getColor(), iconDto.getColor()) && Objects.equals(getColorClass(), iconDto.getColorClass()) && Objects.equals(getIconName(), iconDto.getIconName()); + } + + @Override + public int hashCode() { + return Objects.hash(getFamily(), getFamilyClass(), getColor(), getColorClass(), getIconName()); + } + + @Override + public String toString() { + return "IconDto{" + + "family='" + family + '\'' + + ", familyClass='" + familyClass + '\'' + + ", color='" + color + '\'' + + ", colorClass='" + colorClass + '\'' + + ", iconName='" + iconName + '\'' + + '}'; + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/TabInformationDto.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/TabInformationDto.java new file mode 100644 index 000000000..5c6c7e674 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/TabInformationDto.java @@ -0,0 +1,77 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.domain.datatransfer.extension; + +import com.djrapitops.plan.extension.ElementOrder; +import com.djrapitops.plan.extension.implementation.TabInformation; + +import java.util.List; +import java.util.Objects; + +public class TabInformationDto { + + private final String tabName; + private final IconDto icon; // can be null + private final List elementOrder; // can be null / miss values + private final int tabPriority; + + public TabInformationDto(TabInformation tabInformation) { + tabName = tabInformation.getTabName(); + icon = tabInformation.getTabIcon() != null ? new IconDto(tabInformation.getTabIcon()) : null; + elementOrder = tabInformation.getTabElementOrder(); + tabPriority = tabInformation.getTabPriority(); + } + + public String getTabName() { + return tabName; + } + + public IconDto getIcon() { + return icon; + } + + public List getElementOrder() { + return elementOrder; + } + + public int getTabPriority() { + return tabPriority; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TabInformationDto that = (TabInformationDto) o; + return getTabPriority() == that.getTabPriority() && Objects.equals(getTabName(), that.getTabName()) && Objects.equals(getIcon(), that.getIcon()) && Objects.equals(getElementOrder(), that.getElementOrder()); + } + + @Override + public int hashCode() { + return Objects.hash(getTabName(), getIcon(), getElementOrder(), getTabPriority()); + } + + @Override + public String toString() { + return "TabInformationDto{" + + "tabName='" + tabName + '\'' + + ", icon=" + icon + + ", elementOrder=" + elementOrder + + ", tabPriority=" + tabPriority + + '}'; + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/TableCellDto.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/TableCellDto.java new file mode 100644 index 000000000..440e6e149 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/TableCellDto.java @@ -0,0 +1,71 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.domain.datatransfer.extension; + +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +/** + * @author AuroraLS3 + */ +public class TableCellDto { + + private final String value; + @Nullable + private final Object valueUnformatted; + + public TableCellDto(String value) { + this.value = value; + this.valueUnformatted = null; + } + + public TableCellDto(String value, @Nullable Object valueUnformatted) { + this.value = value; + this.valueUnformatted = valueUnformatted; + } + + public String getValue() { + return value; + } + + @Nullable + public Object getValueUnformatted() { + return valueUnformatted; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TableCellDto that = (TableCellDto) o; + return Objects.equals(getValue(), that.getValue()) && Objects.equals(getValueUnformatted(), that.getValueUnformatted()); + } + + @Override + public int hashCode() { + return Objects.hash(getValue(), getValueUnformatted()); + } + + @Override + public String toString() { + return "TableCellDto{" + + "value='" + value + '\'' + + ", valueUnformatted=" + valueUnformatted + + '}'; + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/TableDto.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/TableDto.java new file mode 100644 index 000000000..d2b099500 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/extension/TableDto.java @@ -0,0 +1,98 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.domain.datatransfer.extension; + +import com.djrapitops.plan.delivery.rendering.html.structure.HtmlTable; +import com.djrapitops.plan.extension.table.Table; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class TableDto { + + private final List columns; + private final List icons; + + private final List> rows; + + public TableDto(Table table) { + columns = Arrays.stream(table.getColumns()) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + icons = Arrays.stream(table.getIcons()) + .filter(Objects::nonNull) + .map(IconDto::new) + .collect(Collectors.toList()); + + rows = HtmlTable.mapToRows(table.getRows(), table.getTableColumnFormats()).stream() + .map(row -> constructRow(columns, row)) + .collect(Collectors.toList()); + } + + private List constructRow(List columns, TableCellDto[] row) { + List constructedRow = new ArrayList<>(); + + int headerLength = row.length - 1; + int columnCount = columns.size(); + for (int i = 0; i < columnCount; i++) { + if (i > headerLength) { + constructedRow.add(new TableCellDto("-")); + } else { + TableCellDto cell = row[i]; + constructedRow.add(cell != null ? cell : new TableCellDto("-")); + } + } + return constructedRow; + } + + public List getColumns() { + return columns; + } + + public List getIcons() { + return icons; + } + + public List> getRows() { + return rows; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TableDto tableDto = (TableDto) o; + return Objects.equals(getColumns(), tableDto.getColumns()) && Objects.equals(getIcons(), tableDto.getIcons()) && Objects.equals(getRows(), tableDto.getRows()); + } + + @Override + public int hashCode() { + return Objects.hash(getColumns(), getIcons(), getRows()); + } + + @Override + public String toString() { + return "TableDto{" + + "columns=" + columns + + ", icons=" + icons + + ", rows=" + rows + + '}'; + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/package-info.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/package-info.java new file mode 100644 index 000000000..ad3bc05e9 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/package-info.java @@ -0,0 +1,4 @@ +/** + * Data transfer objects or DTOs, which represent the outgoing or incoming serialized json. + */ +package com.djrapitops.plan.delivery.domain.datatransfer; \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/keys/AnalysisKeys.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/keys/AnalysisKeys.java index b14d06828..b3ea4cbd4 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/keys/AnalysisKeys.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/keys/AnalysisKeys.java @@ -25,7 +25,7 @@ package com.djrapitops.plan.delivery.domain.keys; * @see com.djrapitops.plan.data.store.containers.AnalysisContainer for Suppliers for each Key. * @deprecated AnalysisContainer can no longer be obtained, so this is deprecated. */ -@Deprecated +@Deprecated(since = "5.0") public class AnalysisKeys { public static final PlaceholderKey TIME_ZONE = CommonPlaceholderKeys.TIME_ZONE; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/keys/CommonKeys.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/keys/CommonKeys.java index b6a7407a0..ce2548980 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/keys/CommonKeys.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/keys/CommonKeys.java @@ -16,12 +16,13 @@ */ package com.djrapitops.plan.delivery.domain.keys; -import com.djrapitops.plan.delivery.domain.mutators.PlayersMutator; -import com.djrapitops.plan.delivery.domain.mutators.SessionsMutator; -import com.djrapitops.plan.delivery.domain.mutators.TPSMutator; -import com.djrapitops.plan.gathering.domain.*; +import com.djrapitops.plan.gathering.domain.FinishedSession; +import com.djrapitops.plan.gathering.domain.Ping; +import com.djrapitops.plan.gathering.domain.PlayerKill; +import com.djrapitops.plan.gathering.domain.WorldTimes; -import java.util.*; +import java.util.List; +import java.util.UUID; /** * Class holding Key objects that are commonly used across multiple DataContainers. @@ -38,28 +39,19 @@ public class CommonKeys { public static final Key SERVER_UUID = new Key<>(UUID.class, "server_uuid"); public static final Key NAME = new Key<>(String.class, "name"); public static final PlaceholderKey REGISTERED = new PlaceholderKey<>(Long.class, "registered"); - public static final Key> PING = new Key<>(new Type>() {}, "ping"); + public static final Key> PING = new Key<>(new Type<>() {}, "ping"); - public static final Key> SESSIONS = new Key<>(new Type>() {}, "sessions"); + public static final Key> SESSIONS = new Key<>(new Type<>() {}, "sessions"); public static final Key WORLD_TIMES = new Key<>(WorldTimes.class, "world_times"); public static final PlaceholderKey LAST_SEEN = new PlaceholderKey<>(Long.class, "lastSeen"); - @Deprecated - public static final Key> PLAYER_DEATHS = new Key<>(new Type>() {}, "player_deaths"); - public static final Key> PLAYER_KILLS = new Key<>(new Type>() {}, "player_kills"); + public static final Key> PLAYER_KILLS = new Key<>(new Type<>() {}, "player_kills"); public static final Key PLAYER_KILL_COUNT = new Key<>(Integer.class, "player_kill_count"); - public static final Key PLAYER_DEATH_COUNT = new Key<>(Integer.class, "player_death_count"); public static final Key MOB_KILL_COUNT = new Key<>(Integer.class, "mob_kill_count"); - public static final Key MOB_DEATH_COUNT = new Key<>(Integer.class, "mob_death_count"); public static final Key DEATH_COUNT = new Key<>(Integer.class, "death_count"); public static final Key BANNED = new Key<>(Boolean.class, "banned"); public static final Key OPERATOR = new Key<>(Boolean.class, "operator"); - - public static final Key SESSIONS_MUTATOR = new Key<>(SessionsMutator.class, "SESSIONS_MUTATOR"); - public static final Key TPS_MUTATOR = new Key<>(TPSMutator.class, "TPS_MUTATOR"); - public static final Key PLAYERS_MUTATOR = new Key<>(PlayersMutator.class, "PLAYERS_MUTATOR"); - - public static final Key>>> ACTIVITY_DATA = new Key<>(new Type>>>() {}, "ACTIVITY_DATA"); + public static final Key JOIN_ADDRESS = new Key<>(String.class, "join_address"); } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/keys/PerServerKeys.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/keys/PerServerKeys.java index 5ec03ed5d..e3c0bd4e4 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/keys/PerServerKeys.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/keys/PerServerKeys.java @@ -17,7 +17,9 @@ package com.djrapitops.plan.delivery.domain.keys; import com.djrapitops.plan.delivery.domain.container.PerServerContainer; -import com.djrapitops.plan.gathering.domain.*; +import com.djrapitops.plan.gathering.domain.FinishedSession; +import com.djrapitops.plan.gathering.domain.Ping; +import com.djrapitops.plan.gathering.domain.WorldTimes; import java.util.List; @@ -40,20 +42,13 @@ public class PerServerKeys { public static final Key> SESSIONS = CommonKeys.SESSIONS; public static final Key WORLD_TIMES = CommonKeys.WORLD_TIMES; - @Deprecated - public static final Key> PLAYER_KILLS = CommonKeys.PLAYER_KILLS; - @Deprecated - public static final Key> PLAYER_DEATHS = CommonKeys.PLAYER_DEATHS; public static final Key PLAYER_KILL_COUNT = CommonKeys.PLAYER_KILL_COUNT; - @Deprecated - public static final Key PLAYER_DEATH_COUNT = CommonKeys.PLAYER_DEATH_COUNT; public static final Key MOB_KILL_COUNT = CommonKeys.MOB_KILL_COUNT; - @Deprecated - public static final Key MOB_DEATH_COUNT = CommonKeys.MOB_DEATH_COUNT; public static final Key DEATH_COUNT = CommonKeys.DEATH_COUNT; public static final Key LAST_SEEN = CommonKeys.LAST_SEEN; public static final Key BANNED = CommonKeys.BANNED; public static final Key OPERATOR = CommonKeys.OPERATOR; + public static final Key JOIN_ADDRESS = CommonKeys.JOIN_ADDRESS; } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/keys/PlayerKeys.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/keys/PlayerKeys.java index f1d5c31fd..62ea425eb 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/keys/PlayerKeys.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/keys/PlayerKeys.java @@ -38,12 +38,12 @@ public class PlayerKeys { public static final Key UUID = CommonKeys.UUID; public static final Key NAME = CommonKeys.NAME; - public static final Key> NICKNAMES = new Key<>(new Type>() {}, "nicknames"); + public static final Key> NICKNAMES = new Key<>(new Type<>() {}, "nicknames"); public static final PlaceholderKey REGISTERED = CommonKeys.REGISTERED; public static final Key KICK_COUNT = new Key<>(Integer.class, "kick_count"); - public static final Key> GEO_INFO = new Key<>(new Type>() {}, "geo_info"); + public static final Key> GEO_INFO = new Key<>(new Type<>() {}, "geo_info"); public static final Key> PING = CommonKeys.PING; public static final Key ACTIVE_SESSION = new Key<>(ActiveSession.class, "active_session"); @@ -51,10 +51,7 @@ public class PlayerKeys { public static final Key WORLD_TIMES = CommonKeys.WORLD_TIMES; public static final Key> PLAYER_KILLS = CommonKeys.PLAYER_KILLS; - public static final Key> PLAYER_DEATHS_KILLS = new Key<>(new Type>() { - }, "player_deaths_kills"); - @Deprecated - public static final Key> PLAYER_DEATHS = CommonKeys.PLAYER_DEATHS; + public static final Key> PLAYER_DEATHS_KILLS = new Key<>(new Type<>() {}, "player_deaths_kills"); public static final Key PLAYER_KILL_COUNT = CommonKeys.PLAYER_KILL_COUNT; public static final Key MOB_KILL_COUNT = CommonKeys.MOB_KILL_COUNT; public static final Key DEATH_COUNT = CommonKeys.DEATH_COUNT; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/keys/ServerKeys.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/keys/ServerKeys.java deleted file mode 100644 index 32c73f6a3..000000000 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/keys/ServerKeys.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * This file is part of Player Analytics (Plan). - * - * Plan is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License v3 as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Plan is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Plan. If not, see . - */ -package com.djrapitops.plan.delivery.domain.keys; - -import com.djrapitops.plan.delivery.domain.DateObj; -import com.djrapitops.plan.delivery.domain.container.PlayerContainer; -import com.djrapitops.plan.delivery.domain.container.ServerContainer; -import com.djrapitops.plan.extension.implementation.results.ExtensionData; -import com.djrapitops.plan.gathering.domain.*; - -import java.util.List; -import java.util.Map; -import java.util.UUID; - -/** - * Keys for the ServerContainer. - * - * @author AuroraLS3 - * @see ServerContainer For DataContainer. - */ -public class ServerKeys { - - private ServerKeys() { - /* Static variable class */ - } - - public static final Key SERVER_UUID = CommonKeys.SERVER_UUID; - public static final Key NAME = CommonKeys.NAME; - - public static final Key> PLAYERS = new Key<>(new Type>() {}, "players"); - public static final Key> OPERATORS = new Key<>(new Type>() {}, "operators"); - public static final Key PLAYER_COUNT = new Key<>(Integer.class, "player_count"); - - public static final Key> SESSIONS = CommonKeys.SESSIONS; - public static final Key> PING = CommonKeys.PING; - public static final Key WORLD_TIMES = CommonKeys.WORLD_TIMES; - - public static final Key> PLAYER_KILLS = CommonKeys.PLAYER_KILLS; - public static final Key PLAYER_KILL_COUNT = CommonKeys.PLAYER_KILL_COUNT; - public static final Key MOB_KILL_COUNT = CommonKeys.MOB_KILL_COUNT; - public static final Key DEATH_COUNT = CommonKeys.DEATH_COUNT; - - public static final Key> TPS = new Key<>(new Type>() {}, "tps"); - public static final Key> ALL_TIME_PEAK_PLAYERS = new Key<>(new Type>() {}, "all_time_peak_players"); - public static final Key> RECENT_PEAK_PLAYERS = new Key<>(new Type>() {}, "recent_peak_players"); - @Deprecated - public static final Key> COMMAND_USAGE = new Key<>(new Type>() {}, "command_usage"); - public static final Key> EXTENSION_DATA = new Key<>(new Type>() {}, "extension_data"); -} \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/keys/Type.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/keys/Type.java index f784132f3..d2e87f74d 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/keys/Type.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/keys/Type.java @@ -34,11 +34,11 @@ public abstract class Type { } public static Type ofClass(Class of) { - return new Type() {}; + return new Type<>() {}; } public static Type of(K object) { - return new Type() {}; + return new Type<>() {}; } public Class> getGenericsClass() { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/mutators/PlayerKillMutator.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/mutators/PlayerKillMutator.java index bbb799efa..84043b3bb 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/mutators/PlayerKillMutator.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/mutators/PlayerKillMutator.java @@ -16,6 +16,7 @@ */ package com.djrapitops.plan.delivery.domain.mutators; +import com.djrapitops.plan.delivery.domain.ServerIdentifier; import com.djrapitops.plan.delivery.formatting.Formatters; import com.djrapitops.plan.gathering.domain.PlayerKill; import com.djrapitops.plan.utilities.java.Lists; @@ -44,10 +45,20 @@ public class PlayerKillMutator { public List> toJSONAsMap(Formatters formatters) { return Lists.map(kills, kill -> { + PlayerKill.Killer killer = kill.getKiller(); + PlayerKill.Victim victim = kill.getVictim(); + ServerIdentifier server = kill.getServer(); + Map killMap = new HashMap<>(); killMap.put("date", formatters.secondLong().apply(kill.getDate())); - killMap.put("victim", kill.getVictimName().orElse(kill.getVictim().toString())); - killMap.put("killer", kill.getKillerName().orElse(kill.getKiller().toString())); + killMap.put("killer", killer.getName()); + killMap.put("victim", victim.getName()); + killMap.put("killerUUID", killer.getUuid().toString()); + killMap.put("victimUUID", victim.getUuid().toString()); + killMap.put("killerName", killer.getName()); + killMap.put("victimName", victim.getName()); + killMap.put("serverUUID", server.getUuid().toString()); + killMap.put("serverName", server.getName()); killMap.put("weapon", kill.getWeapon()); return killMap; } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/mutators/PlayersMutator.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/mutators/PlayersMutator.java index 8e2742ae7..3a8cc1e51 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/mutators/PlayersMutator.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/mutators/PlayersMutator.java @@ -17,10 +17,8 @@ package com.djrapitops.plan.delivery.domain.mutators; import com.djrapitops.plan.delivery.domain.DateObj; -import com.djrapitops.plan.delivery.domain.container.DataContainer; import com.djrapitops.plan.delivery.domain.container.PlayerContainer; import com.djrapitops.plan.delivery.domain.keys.PlayerKeys; -import com.djrapitops.plan.delivery.domain.keys.ServerKeys; import com.djrapitops.plan.gathering.domain.FinishedSession; import com.djrapitops.plan.gathering.domain.GeoInfo; import com.djrapitops.plan.gathering.domain.Ping; @@ -51,10 +49,6 @@ public class PlayersMutator { return new PlayersMutator(new ArrayList<>(mutator.players)); } - public static PlayersMutator forContainer(DataContainer container) { - return new PlayersMutator(container.getValue(ServerKeys.PLAYERS).orElse(new ArrayList<>())); - } - public > PlayersMutator filterBy(T by) { return new PlayersMutator(Lists.filter(players, by)); } @@ -65,7 +59,7 @@ public class PlayersMutator { .map(sessions -> sessions.stream().anyMatch(session -> { long start = session.getStart(); long end = session.getEnd(); - return (after <= start && start <= before) || (after <= end && end <= before); + return after <= start && start <= before || after <= end && end <= before; })).orElse(false) ); } @@ -127,7 +121,7 @@ public class PlayersMutator { Map> pingPerCountry = new HashMap<>(); for (PlayerContainer player : players) { Optional mostRecent = GeoInfoMutator.forContainer(player).mostRecent(); - if (!mostRecent.isPresent()) { + if (mostRecent.isEmpty()) { continue; } List pings = player.getValue(PlayerKeys.PING).orElse(new ArrayList<>()); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/mutators/SessionsMutator.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/mutators/SessionsMutator.java index 08dcad5c8..eb56755d0 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/mutators/SessionsMutator.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/mutators/SessionsMutator.java @@ -27,9 +27,12 @@ import com.djrapitops.plan.delivery.rendering.html.Html; import com.djrapitops.plan.delivery.rendering.json.graphs.Graphs; import com.djrapitops.plan.delivery.rendering.json.graphs.pie.WorldPie; import com.djrapitops.plan.gathering.domain.*; +import com.djrapitops.plan.gathering.domain.event.JoinAddress; import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.settings.config.WorldAliasSettings; import com.djrapitops.plan.utilities.analysis.Median; +import com.djrapitops.plan.utilities.comparators.DateHolderOldestComparator; +import com.djrapitops.plan.utilities.comparators.DateHolderRecentComparator; import com.djrapitops.plan.utilities.java.Lists; import java.util.*; @@ -101,6 +104,10 @@ public class SessionsMutator { return sessions; } + public TimeSegmentsMutator onlineTimeSegments() { + return TimeSegmentsMutator.sessionClockSegments(sort(new DateHolderOldestComparator()).all()); + } + public SessionsMutator filterPlayedOnServer(ServerUUID serverUUID) { return filterBy(session -> session.getServerUUID().equals(serverUUID) @@ -282,20 +289,15 @@ public class SessionsMutator { sessionMap.put("mob_kills", session.getMobKillCount()); sessionMap.put("deaths", session.getDeathCount()); sessionMap.put("player_kills", session.getExtraData(PlayerKills.class) - .map(PlayerKills::asList).map(kills -> kills.stream().map( - kill -> { - Map killMap = new HashMap<>(); - killMap.put("date", formatters.secondLong().apply(kill.getDate())); - killMap.put("victim", kill.getVictimName().orElse(kill.getVictim().toString())); - killMap.put("killer", playerName); - killMap.put("weapon", kill.getWeapon()); - return killMap; - }).collect(Collectors.toList()) - ).orElseGet(ArrayList::new)); + .map(PlayerKills::asMutator) + .map(killsMutator -> killsMutator.toJSONAsMap(formatters)) + .orElseGet(ArrayList::new)); sessionMap.put("first_session", session.isFirstSession()); WorldPie worldPie = graphs.pie().worldPie(session.getExtraData(WorldTimes.class).orElseGet(WorldTimes::new)); sessionMap.put("world_series", worldPie.getSlices()); sessionMap.put("gm_series", worldPie.toHighChartsDrillDownMaps()); + sessionMap.put("join_address", session.getExtraData(JoinAddress.class) + .map(JoinAddress::getAddress).orElse("-")); session.getExtraData(AveragePing.class).ifPresent(averagePing -> sessionMap.put("avg_ping", formatters.decimals().apply(averagePing.getValue()) + " ms") @@ -303,4 +305,22 @@ public class SessionsMutator { return sessionMap; }); } + + public Optional latestSession() { + List orderedSessions = sort(new DateHolderRecentComparator()).all(); + return orderedSessions.isEmpty() ? Optional.empty() : Optional.of(orderedSessions.get(0)); + } + + public Optional previousSession() { + List orderedSessions = sort(new DateHolderRecentComparator()).all(); + for (FinishedSession session : orderedSessions) { + if (session.getExtraData(ActiveSession.class).isPresent()) { + continue; + } + // First non-active session is previous one. + return Optional.of(session); + } + + return Optional.empty(); + } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/mutators/TPSMutator.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/mutators/TPSMutator.java index 8b5114615..3b153178f 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/mutators/TPSMutator.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/mutators/TPSMutator.java @@ -16,8 +16,6 @@ */ package com.djrapitops.plan.delivery.domain.mutators; -import com.djrapitops.plan.delivery.domain.container.DataContainer; -import com.djrapitops.plan.delivery.domain.keys.ServerKeys; import com.djrapitops.plan.delivery.rendering.json.graphs.line.LineGraph; import com.djrapitops.plan.delivery.rendering.json.graphs.line.Point; import com.djrapitops.plan.gathering.domain.TPS; @@ -47,10 +45,6 @@ public class TPSMutator { this.tpsData = tpsData; } - public static TPSMutator forContainer(DataContainer container) { - return new TPSMutator(container.getValue(ServerKeys.TPS).orElse(new ArrayList<>())); - } - public static TPSMutator copyOf(TPSMutator mutator) { return new TPSMutator(new ArrayList<>(mutator.tpsData)); } @@ -63,7 +57,7 @@ public class TPSMutator { return filterBy(tps -> tps.getDate() >= after && tps.getDate() <= before); } - public TPSMutator filterTPSBetween(int above, int below) { + public TPSMutator filterTPSBetween(double above, double below) { return filterBy(tps -> tps.getTicksPerSecond() > above && tps.getTicksPerSecond() < below); } @@ -164,13 +158,13 @@ public class TPSMutator { return count * 1.0 / tpsData.size(); } - public int lowTpsSpikeCount(int threshold) { + public int lowTpsSpikeCount(double threshold) { boolean wasLow = false; int spikeCount = 0; for (TPS tpsObj : tpsData) { double tps = tpsObj.getTicksPerSecond(); - if (tps < threshold) { + if (0 <= tps && tps < threshold) { if (!wasLow) { spikeCount++; wasLow = true; @@ -183,6 +177,13 @@ public class TPSMutator { return spikeCount; } + public int averagePlayers() { + return (int) tpsData.stream() + .mapToInt(TPS::getPlayers) + .filter(num -> num >= 0) + .average().orElse(-1); + } + public double averageTPS() { return tpsData.stream() .mapToDouble(TPS::getTicksPerSecond) diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/mutators/TimeSegmentsMutator.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/mutators/TimeSegmentsMutator.java new file mode 100644 index 000000000..1cbce52a6 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/mutators/TimeSegmentsMutator.java @@ -0,0 +1,59 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.domain.mutators; + +import com.djrapitops.plan.delivery.domain.DateObj; +import com.djrapitops.plan.delivery.domain.TimeSegment; +import com.djrapitops.plan.gathering.domain.FinishedSession; +import com.djrapitops.plan.utilities.comparators.DateHolderOldestComparator; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class TimeSegmentsMutator { + + private final List> segments; + + public TimeSegmentsMutator(List> segments) { + this.segments = segments; + } + + public static TimeSegmentsMutator sessionClockSegments(List sessions) { + List> changes = new ArrayList<>(); + + for (FinishedSession session : sessions) { + long startTime = (session.getStart()) % TimeUnit.DAYS.toMillis(1); + long endTime = (session.getEnd()) % TimeUnit.DAYS.toMillis(1); + changes.add(new DateObj<>(startTime, 1)); + changes.add(new DateObj<>(endTime, -1)); + } + + changes.sort(new DateHolderOldestComparator()); + + int count = 0; + long previousTime = 0L; + List> segments = new ArrayList<>(); + for (DateObj change : changes) { + segments.add(new TimeSegment<>(previousTime, change.getDate(), count)); + count += change.getValue(); + previousTime = change.getDate(); + } + + return new TimeSegmentsMutator<>(segments); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/export/ExportScheduler.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/export/ExportScheduler.java index 1c9ea094c..569b48b26 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/export/ExportScheduler.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/export/ExportScheduler.java @@ -17,11 +17,14 @@ package com.djrapitops.plan.delivery.export; import com.djrapitops.plan.identification.Server; +import com.djrapitops.plan.identification.ServerInfo; import com.djrapitops.plan.settings.config.PlanConfig; import com.djrapitops.plan.settings.config.paths.ExportSettings; import com.djrapitops.plan.storage.database.DBSystem; +import com.djrapitops.plan.storage.database.Database; import com.djrapitops.plan.storage.database.queries.objects.ServerQueries; import com.djrapitops.plan.utilities.logging.ErrorLogger; +import net.playeranalytics.plugin.scheduling.PluginRunnable; import net.playeranalytics.plugin.scheduling.RunnableFactory; import net.playeranalytics.plugin.scheduling.TimeAmount; @@ -37,10 +40,11 @@ import java.util.concurrent.TimeUnit; * @author AuroraLS3 */ @Singleton -public class ExportScheduler { +public class ExportScheduler extends PluginRunnable { private final PlanConfig config; private final DBSystem dbSystem; + private final ServerInfo serverInfo; private final RunnableFactory runnableFactory; private final Exporter exporter; @@ -50,27 +54,41 @@ public class ExportScheduler { public ExportScheduler( PlanConfig config, DBSystem dbSystem, + ServerInfo serverInfo, RunnableFactory runnableFactory, Exporter exporter, ErrorLogger errorLogger ) { this.config = config; this.dbSystem = dbSystem; + this.serverInfo = serverInfo; this.runnableFactory = runnableFactory; this.exporter = exporter; this.errorLogger = errorLogger; } - public void scheduleExport() { + @Override + public void run() { + scheduleExport(); + } + + private void scheduleExport() { + Database database = dbSystem.getDatabase(); + boolean hasProxy = database.query(ServerQueries.fetchProxyServerInformation()).isPresent(); + if (serverInfo.getServer().isNotProxy() && hasProxy) { + return; + } + scheduleServerPageExport(); schedulePlayersPageExport(); } private void schedulePlayersPageExport() { - long period = TimeAmount.toTicks(config.get(ExportSettings.EXPORT_PERIOD), TimeUnit.MILLISECONDS); + long period = TimeAmount.toTicks(config.get(ExportSettings.EXPORT_PERIOD), TimeUnit.MILLISECONDS) + / 4; runnableFactory.create( new ExportTask(exporter, Exporter::exportPlayersPage, errorLogger) - ).runTaskTimerAsynchronously(0L, period); + ).runTaskTimerAsynchronously(TimeAmount.toTicks(2, TimeUnit.MINUTES), period); } private void scheduleServerPageExport() { @@ -85,17 +103,17 @@ public class ExportScheduler { Optional proxy = servers.stream().filter(Server::isProxy).findFirst(); proxy.ifPresent(mainServer -> runnableFactory.create( - new ExportTask(exporter, same -> same.exportServerPage(mainServer), errorLogger)) - .runTaskTimerAsynchronously(0L, period) + new ExportTask(exporter, same -> same.exportServerPage(mainServer), errorLogger)) + .runTaskTimerAsynchronously(TimeAmount.toTicks(1, TimeUnit.MINUTES), period) ); int offsetMultiplier = proxy.isPresent() ? 1 : 0; // Delay first server export if on a network. for (Server server : servers) { runnableFactory.create( - new ExportTask(exporter, same -> { - same.exportServerPage(server); - same.exportServerJSON(server); - }, errorLogger)) + new ExportTask(exporter, same -> { + same.exportServerPage(server); + same.exportServerJSON(server); + }, errorLogger)) .runTaskTimerAsynchronously(offset * offsetMultiplier, period); offsetMultiplier++; } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/export/ExportSystem.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/export/ExportSystem.java index 613f19ee1..b014ed7e4 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/export/ExportSystem.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/export/ExportSystem.java @@ -17,10 +17,7 @@ package com.djrapitops.plan.delivery.export; import com.djrapitops.plan.SubSystem; -import com.djrapitops.plan.identification.ServerInfo; -import com.djrapitops.plan.storage.database.DBSystem; -import com.djrapitops.plan.storage.database.Database; -import com.djrapitops.plan.storage.database.queries.objects.ServerQueries; +import net.playeranalytics.plugin.scheduling.RunnableFactory; import javax.inject.Inject; import javax.inject.Singleton; @@ -33,34 +30,32 @@ import javax.inject.Singleton; @Singleton public class ExportSystem implements SubSystem { - private final DBSystem dbSystem; - private final ServerInfo serverInfo; + private final Exporter exporter; private final ExportScheduler exportScheduler; + private final RunnableFactory runnableFactory; @Inject public ExportSystem( - DBSystem dbSystem, - ServerInfo serverInfo, - ExportScheduler exportScheduler + Exporter exporter, + ExportScheduler exportScheduler, + RunnableFactory runnableFactory ) { - this.dbSystem = dbSystem; - this.serverInfo = serverInfo; + this.exporter = exporter; this.exportScheduler = exportScheduler; + this.runnableFactory = runnableFactory; } @Override public void enable() { - Database database = dbSystem.getDatabase(); - boolean hasProxy = database.query(ServerQueries.fetchProxyServerInformation()).isPresent(); - if (serverInfo.getServer().isNotProxy() && hasProxy) { - return; - } - - exportScheduler.scheduleExport(); + runnableFactory.create(exportScheduler).runTaskAsynchronously(); } @Override public void disable() { // Nothing to disable } + + public Exporter getExporter() { + return exporter; + } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/export/NetworkPageExporter.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/export/NetworkPageExporter.java index a5ab7aadb..3745f5812 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/export/NetworkPageExporter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/export/NetworkPageExporter.java @@ -98,8 +98,14 @@ public class NetworkPageExporter extends FileExporter { // Fixes refreshingJsonRequest ignoring old data of export String html = StringUtils.replaceEach(page.toHtml(), - new String[]{"loadPlayersOnlineGraph, 'network-overview', true);"}, - new String[]{"loadPlayersOnlineGraph, 'network-overview');"}); + new String[]{"loadPlayersOnlineGraph, 'network-overview', true);", + "· Performance", + "" + }, + new String[]{"loadPlayersOnlineGraph, 'network-overview');", + "· Performance (Unavailable with Export)", + "" + }); export(to, exportPaths.resolveExportPaths(html)); } @@ -141,17 +147,15 @@ public class NetworkPageExporter extends FileExporter { } private void exportJSON(ExportPaths exportPaths, Path toDirectory, String resource) throws IOException { - Optional found = getJSONResponse(resource); - if (!found.isPresent()) { - throw new NotFoundException(resource + " was not properly exported: not found"); - } + Response response = getJSONResponse(resource) + .orElseThrow(() -> new NotFoundException(resource + " was not properly exported: not found")); String jsonResourceName = toFileName(toJSONResourceName(resource)) + ".json"; String relativePlayerLink = toRelativePathFromRoot("player"); export(toDirectory.resolve("data").resolve(jsonResourceName), // Replace ../player in urls to fix player page links - StringUtils.replaceEach(found.get().getAsString(), + StringUtils.replaceEach(response.getAsString(), new String[]{StringEscapeUtils.escapeJson("../player"), StringEscapeUtils.escapeJson("./player")}, new String[]{StringEscapeUtils.escapeJson(relativePlayerLink), StringEscapeUtils.escapeJson(relativePlayerLink)} ) @@ -168,7 +172,7 @@ public class NetworkPageExporter extends FileExporter { return jsonHandler.getResolver().resolve(new Request("GET", "/v1/" + resource, null, Collections.emptyMap())); } catch (WebException e) { // The rest of the exceptions should not be thrown - throw new IllegalStateException("Unexpected exception thrown: " + e.toString(), e); + throw new IllegalStateException("Unexpected exception thrown: " + e, e); } } @@ -177,14 +181,14 @@ public class NetworkPageExporter extends FileExporter { "./img/Flaticon_circle.png", "./css/sb-admin-2.css", "./css/style.css", + "./css/noauth.css", "./vendor/datatables/datatables.min.js", "./vendor/datatables/datatables.min.css", - "./vendor/highcharts/highstock.js", - "./vendor/highcharts/map.js", - "./vendor/highcharts/world.js", - "./vendor/highcharts/drilldown.js", - "./vendor/highcharts/highcharts-more.js", - "./vendor/highcharts/no-data-to-display.js", + "./vendor/highcharts/modules/map.js", + "./vendor/highcharts/mapdata/world.js", + "./vendor/highcharts/modules/drilldown.js", + "./vendor/highcharts/highcharts.js", + "./vendor/highcharts/modules/no-data-to-display.js", "./vendor/masonry/masonry.pkgd.min.js", "./vendor/fontawesome-free/css/all.min.css", "./vendor/fontawesome-free/webfonts/fa-brands-400.eot", diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/export/PlayerPageExporter.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/export/PlayerPageExporter.java index e74910a7f..1efc7f947 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/export/PlayerPageExporter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/export/PlayerPageExporter.java @@ -113,15 +113,13 @@ public class PlayerPageExporter extends FileExporter { } private void exportJSON(ExportPaths exportPaths, Path toDirectory, String resource) throws IOException { - Optional found = getJSONResponse(resource); - if (!found.isPresent()) { - throw new NotFoundException(resource + " was not properly exported: no response"); - } + Response response = getJSONResponse(resource) + .orElseThrow(() -> new NotFoundException(resource + " was not properly exported: no response")); String jsonResourceName = toFileName(toJSONResourceName(resource)) + ".json"; - export(toDirectory.resolve(jsonResourceName), found.get().getBytes()); - exportPaths.put("../v1/player?player=${encodeURIComponent(playerName)}", "./" + jsonResourceName); + export(toDirectory.resolve(jsonResourceName), response.getBytes()); + exportPaths.put("../v1/player?player=${encodeURIComponent(playerUUID)}", "./" + jsonResourceName); } private String toJSONResourceName(String resource) { @@ -133,7 +131,7 @@ public class PlayerPageExporter extends FileExporter { return jsonHandler.getResolver().resolve(new Request("GET", "/v1/" + resource, null, Collections.emptyMap())); } catch (WebException e) { // The rest of the exceptions should not be thrown - throw new IllegalStateException("Unexpected exception thrown: " + e.toString(), e); + throw new IllegalStateException("Unexpected exception thrown: " + e, e); } } @@ -143,14 +141,14 @@ public class PlayerPageExporter extends FileExporter { "../img/Flaticon_circle.png", "../css/sb-admin-2.css", "../css/style.css", + "../css/noauth.css", "../vendor/datatables/datatables.min.js", "../vendor/datatables/datatables.min.css", - "../vendor/highcharts/highstock.js", - "../vendor/highcharts/map.js", - "../vendor/highcharts/world.js", - "../vendor/highcharts/drilldown.js", - "../vendor/highcharts/highcharts-more.js", - "../vendor/highcharts/no-data-to-display.js", + "../vendor/highcharts/modules/map.js", + "../vendor/highcharts/mapdata/world.js", + "../vendor/highcharts/modules/drilldown.js", + "../vendor/highcharts/highcharts.js", + "../vendor/highcharts/modules/no-data-to-display.js", "../vendor/fullcalendar/fullcalendar.min.css", "../vendor/momentjs/moment.js", "../vendor/masonry/masonry.pkgd.min.js", diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/export/PlayersPageExporter.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/export/PlayersPageExporter.java index 584661c55..2ea8dffff 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/export/PlayersPageExporter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/export/PlayersPageExporter.java @@ -96,23 +96,27 @@ public class PlayersPageExporter extends FileExporter { // Fixes refreshingJsonRequest ignoring old data of export String html = StringUtils.replaceEach(page.toHtml(), - new String[]{"}, 'playerlist', true);"}, - new String[]{"}, 'playerlist');"}); + new String[]{ + "}, 'playerlist', true);", + "" + }, + new String[]{ + "}, 'playerlist');", + "" + }); export(to, exportPaths.resolveExportPaths(html)); } private void exportJSON(Path toDirectory) throws IOException { - Optional found = getJSONResponse("players"); - if (!found.isPresent()) { - throw new NotFoundException("players page was not properly exported: not found"); - } + Response response = getJSONResponse("players") + .orElseThrow(() -> new NotFoundException("players page was not properly exported: not found")); String jsonResourceName = toFileName(toJSONResourceName("players")) + ".json"; export(toDirectory.resolve("data").resolve(jsonResourceName), // Replace ../player in urls to fix player page links - StringUtils.replace(found.get().getAsString(), "../player", toRelativePathFromRoot("player")) + StringUtils.replace(response.getAsString(), "../player", toRelativePathFromRoot("player")) ); exportPaths.put("./v1/players", toRelativePathFromRoot("data/" + jsonResourceName)); } @@ -136,6 +140,7 @@ public class PlayersPageExporter extends FileExporter { "img/Flaticon_circle.png", "css/sb-admin-2.css", "css/style.css", + "css/noauth.css", "vendor/datatables/datatables.min.js", "vendor/datatables/datatables.min.css", "vendor/fontawesome-free/css/all.min.css", diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/export/ServerPageExporter.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/export/ServerPageExporter.java index 15679ebc2..82800a6da 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/export/ServerPageExporter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/export/ServerPageExporter.java @@ -110,13 +110,15 @@ public class ServerPageExporter extends FileExporter { String html = StringUtils.replaceEach(page.toHtml(), new String[]{ "loadOptimizedPerformanceGraph, 'performance', true);", - "loadServerCalendar, 'online-activity-overview', true);", - "}, 'playerlist', true);" + "loadserverCalendar, 'online-activity-overview', true);", + "}, 'playerlist', true);", + "" }, new String[]{ "loadOptimizedPerformanceGraph, 'performance');", - "loadServerCalendar, 'online-activity-overview');", - "}, 'playerlist');" + "loadserverCalendar, 'online-activity-overview');", + "}, 'playerlist');", + "" }); export(to, exportPaths.resolveExportPaths(html)); @@ -164,17 +166,15 @@ public class ServerPageExporter extends FileExporter { } private void exportJSON(Path toDirectory, String resource) throws IOException { - Optional found = getJSONResponse(resource); - if (!found.isPresent()) { - throw new NotFoundException(resource + " was not properly exported: not found"); - } + Response response = getJSONResponse(resource) + .orElseThrow(() -> new NotFoundException(resource + " was not properly exported: not found")); String jsonResourceName = toFileName(toJSONResourceName(resource)) + ".json"; export(toDirectory.resolve("data").resolve(jsonResourceName), // Replace ../player in urls to fix player page links StringUtils.replace( - found.get().getAsString(), + response.getAsString(), StringEscapeUtils.escapeJson("../player"), StringEscapeUtils.escapeJson(toRelativePathFromRoot("player")) ) @@ -191,7 +191,7 @@ public class ServerPageExporter extends FileExporter { return jsonHandler.getResolver().resolve(new Request("GET", "/v1/" + resource, null, Collections.emptyMap())); } catch (WebException e) { // The rest of the exceptions should not be thrown - throw new IllegalStateException("Unexpected exception thrown: " + e.toString(), e); + throw new IllegalStateException("Unexpected exception thrown: " + e, e); } } @@ -203,12 +203,11 @@ public class ServerPageExporter extends FileExporter { "../css/style.css", "../vendor/datatables/datatables.min.js", "../vendor/datatables/datatables.min.css", - "../vendor/highcharts/highstock.js", - "../vendor/highcharts/map.js", - "../vendor/highcharts/world.js", - "../vendor/highcharts/drilldown.js", - "../vendor/highcharts/highcharts-more.js", - "../vendor/highcharts/no-data-to-display.js", + "../vendor/highcharts/modules/map.js", + "../vendor/highcharts/mapdata/world.js", + "../vendor/highcharts/modules/drilldown.js", + "../vendor/highcharts/highcharts.js", + "../vendor/highcharts/modules/no-data-to-display.js", "../vendor/fullcalendar/fullcalendar.min.css", "../vendor/momentjs/moment.js", "../vendor/masonry/masonry.pkgd.min.js", diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/formatting/Formatters.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/formatting/Formatters.java index 4f08ed864..074ebc5fb 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/formatting/Formatters.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/formatting/Formatters.java @@ -23,6 +23,7 @@ import com.djrapitops.plan.settings.locale.Locale; import javax.inject.Inject; import javax.inject.Singleton; +import java.util.concurrent.atomic.AtomicReference; /** * Factory for new instances of different {@link Formatter}s. @@ -73,6 +74,16 @@ public class Formatters { decimalFormatter = new DecimalFormatter(config); percentageFormatter = new PercentageFormatter(decimalFormatter); byteSizeFormatter = new ByteSizeFormatter(decimalFormatter); + + Formatters.Holder.set(this); + } + + public static Formatters getInstance() { + return Holder.formatters.get(); + } + + public static void clearSingleton() { + Holder.formatters.set(null); } public Formatter year() { @@ -142,4 +153,16 @@ public class Formatters { public Formatter byteSizeLong() { return value -> byteSizeFormatter.apply((double) value); } + + static class Holder { + static final AtomicReference formatters = new AtomicReference<>(); + + private Holder() { + /* Static variable holder */ + } + + static void set(Formatters service) { + Formatters.Holder.formatters.set(service); + } + } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/html/Contributors.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/html/Contributors.java index 8be254288..a67831b9a 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/html/Contributors.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/html/Contributors.java @@ -17,7 +17,9 @@ package com.djrapitops.plan.delivery.rendering.html; import java.util.Arrays; +import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; import static com.djrapitops.plan.delivery.rendering.html.Contributors.For.CODE; import static com.djrapitops.plan.delivery.rendering.html.Contributors.For.LANG; @@ -29,7 +31,7 @@ import static com.djrapitops.plan.delivery.rendering.html.Contributors.For.LANG; */ public class Contributors { - private static final Contributor[] CONTRIBUTORS = new Contributor[]{ + private static final Contributor[] CONTRIBUTOR_ARRAY = new Contributor[]{ new Contributor("aidn5", CODE), new Contributor("Antonok", CODE), new Contributor("Argetan", CODE), @@ -56,6 +58,7 @@ public class Contributors { new Contributor("Malachiel", LANG), new Contributor("Miclebrick", CODE), new Contributor("Morsmorse", LANG), + new Contributor("MAXOUXAX", CODE), new Contributor("Nogapra", LANG), new Contributor("Sander0542", LANG), new Contributor("Saph1s", LANG), @@ -67,6 +70,7 @@ public class Contributors { new Contributor("yukieji", LANG), new Contributor("qsefthuopq", LANG), new Contributor("Karlatemp", CODE, LANG), + new Contributor("KasperiP", LANG), new Contributor("Mastory_Md5", LANG), new Contributor("FluxCapacitor2", CODE), new Contributor("galexrt", LANG), @@ -83,7 +87,17 @@ public class Contributors { new Contributor("\u6d1b\u4f0a", LANG), new Contributor("portlek", CODE), new Contributor("mbax", CODE), - new Contributor("rymiel", CODE) + new Contributor("KairuByte", CODE), + new Contributor("rymiel", CODE), + new Contributor("Perchun_Pak", LANG), + new Contributor("HexedHero", CODE), + new Contributor("DrexHD", CODE), + new Contributor("zisunny104", LANG), + new Contributor("SkipM4", LANG), + new Contributor("ahdg6", CODE), + new Contributor("BratishkaErik", LANG), + new Contributor("Pingger", CODE), + new Contributor("stashenko", LANG), }; private Contributors() { @@ -91,13 +105,17 @@ public class Contributors { } public static String generateContributorHtml() { - int estimatedLength = CONTRIBUTORS.length * 40 + 50; + int estimatedLength = CONTRIBUTOR_ARRAY.length * 40 + 50; StringBuilder html = new StringBuilder(estimatedLength); - Arrays.stream(CONTRIBUTORS).sorted() + Arrays.stream(CONTRIBUTOR_ARRAY).sorted() .forEach(contributor -> contributor.appendHtml(html)); return html.toString(); } + public static List getContributors() { + return Arrays.stream(CONTRIBUTOR_ARRAY).sorted().collect(Collectors.toList()); + } + enum For { CODE("fa-code"), LANG("fa-language"); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/html/Html.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/html/Html.java index 6035fcda1..c9ae1129d 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/html/Html.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/html/Html.java @@ -21,8 +21,8 @@ import org.apache.commons.text.StringSubstitutor; import org.apache.commons.text.TextStringBuilder; import java.io.Serializable; -import java.io.UnsupportedEncodingException; import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; @@ -78,7 +78,12 @@ public enum Html { * @return String with span elements. */ public static String swapColorCodesToSpan(String string) { + return swapColorCodesToSpan(string, string.contains("§") ? "§" : "ยง"); + } + + private static String swapColorCodesToSpan(String string, String splitWith) { if (string == null) return null; + if (!string.contains(splitWith)) return string; Html[] replacer = new Html[]{ Html.COLOR_0, Html.COLOR_1, Html.COLOR_2, Html.COLOR_3, @@ -88,8 +93,6 @@ public enum Html { }; Map colorMap = new HashMap<>(); - String splitWith = string.contains("§") ? "§" : "ยง"; - for (Html html : replacer) { colorMap.put(Character.toLowerCase(html.name().charAt(6)), html.create()); colorMap.put('k', ""); @@ -105,6 +108,8 @@ public enum Html { boolean skipFirst = !string.startsWith(splitWith); int placedSpans = 0; + int hexNumbersLeft = 0; + for (String part : split) { if (part.isEmpty()) { continue; @@ -116,6 +121,17 @@ public enum Html { } char colorChar = part.charAt(0); + // Deal with hex colors + if (hexNumbersLeft > 1) { + result.append(colorChar); + hexNumbersLeft--; + continue; + } else if (hexNumbersLeft == 1) { + result.append(colorChar).append(";\">").append(part.substring(1)); + hexNumbersLeft--; + continue; + } + if (colorChar == 'r') { appendEndTags(result, placedSpans); placedSpans = 0; // Colors were reset @@ -123,6 +139,14 @@ public enum Html { continue; } + // Deal with hex colors + if (colorChar == 'x') { + result.append(""); - } + result.append("".repeat(Math.max(0, placedSpans))); } /** @@ -176,13 +198,9 @@ public enum Html { } public static String encodeToURL(String string) { - try { - return StringUtils.replace( - URLEncoder.encode(string, "UTF-8"), - "+", "%20" // Encoding replaces spaces with + - ); - } catch (UnsupportedEncodingException e) { - return string; - } + return StringUtils.replace( + URLEncoder.encode(string, StandardCharsets.UTF_8), + "+", "%20" // Encoding replaces spaces with + + ); } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/html/icon/Family.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/html/icon/Family.java index d02e99284..b53a96ad2 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/html/icon/Family.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/html/icon/Family.java @@ -19,22 +19,24 @@ package com.djrapitops.plan.delivery.rendering.html.icon; import java.util.Optional; public enum Family { - SOLID(" fa fa-", "\">"), - REGULAR(" far fa-", "\">"), - BRAND(" fab fa-", "\">"), - @Deprecated - LINE(" material-icons\">", ""); + SOLID("fa"), + REGULAR("far"), + BRAND("fab"), + @Deprecated(since = "5.0") + LINE(" material-icons"); - private final String middle; - private final String suffix; + private final String familyClass; - Family(String middle, String suffix) { - this.middle = middle; - this.suffix = suffix; + Family(String familyClass) { + this.familyClass = familyClass; } public String appendAround(String color, String name) { - return ""; + } + + public String getFamilyClass() { + return familyClass; } public static Optional getByName(String name) { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/html/structure/DynamicHtmlTable.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/html/structure/DynamicHtmlTable.java index bb5c6fa3e..d96f8351d 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/html/structure/DynamicHtmlTable.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/html/structure/DynamicHtmlTable.java @@ -23,6 +23,10 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.List; +/** + * @deprecated Table html generation is to be done in frontend in the future. + */ +@Deprecated(since = "5.5") public class DynamicHtmlTable implements HtmlTable { private final Header[] headers; private final List rows; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/html/structure/HtmlTable.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/html/structure/HtmlTable.java index c9999aa39..f66f2e401 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/html/structure/HtmlTable.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/html/structure/HtmlTable.java @@ -16,12 +16,23 @@ */ package com.djrapitops.plan.delivery.rendering.html.structure; +import com.djrapitops.plan.delivery.domain.datatransfer.extension.TableCellDto; +import com.djrapitops.plan.delivery.formatting.Formatters; +import com.djrapitops.plan.delivery.rendering.html.Html; import com.djrapitops.plan.delivery.rendering.html.icon.Color; import com.djrapitops.plan.delivery.rendering.html.icon.Icon; import com.djrapitops.plan.extension.table.Table; +import com.djrapitops.plan.extension.table.TableColumnFormat; import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +/** + * @deprecated Table html generation is to be done in frontend in the future. + */ +@Deprecated(since = "5.5") public interface HtmlTable { static HtmlTable fromExtensionTable(Table table, com.djrapitops.plan.extension.icon.Color tableColor) { @@ -52,6 +63,43 @@ public interface HtmlTable { return headers.toArray(new Header[0]); } + static List mapToRows(List rows, TableColumnFormat[] tableColumnFormats) { + return rows.stream() + .map(row -> { + List mapped = new ArrayList<>(row.length); + for (int i = 0; i < row.length; i++) { + Object value = row[i]; + if (value == null) { + mapped.add(null); + } else { + TableColumnFormat format = tableColumnFormats[i]; + mapped.add(new TableCellDto(applyFormat(format, value), value)); + } + } + return mapped.toArray(new TableCellDto[0]); + }) + .collect(Collectors.toList()); + } + + static String applyFormat(TableColumnFormat format, Object value) { + try { + switch (format) { + case TIME_MILLISECONDS: + return Formatters.getInstance().timeAmount().apply(Long.parseLong(value.toString())); + case DATE_YEAR: + return Formatters.getInstance().yearLong().apply(Long.parseLong(value.toString())); + case DATE_SECOND: + return Formatters.getInstance().secondLong().apply(Long.parseLong(value.toString())); + case PLAYER_NAME: + return Html.LINK.create("../player/" + Html.encodeToURL(Html.swapColorCodesToSpan(value.toString()))); + default: + return Html.swapColorCodesToSpan(value.toString()); + } + } catch (Exception e) { + return Objects.toString(value); + } + } + String toHtml(); class Header { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/html/structure/HtmlTableWithColoredHeader.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/html/structure/HtmlTableWithColoredHeader.java index 350f41ad1..a812d942b 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/html/structure/HtmlTableWithColoredHeader.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/html/structure/HtmlTableWithColoredHeader.java @@ -16,24 +16,29 @@ */ package com.djrapitops.plan.delivery.rendering.html.structure; +import com.djrapitops.plan.delivery.domain.datatransfer.extension.TableCellDto; import com.djrapitops.plan.delivery.rendering.html.icon.Color; import com.djrapitops.plan.extension.table.Table; import java.util.List; +/** + * @deprecated Table html generation is to be done in frontend in the future. + */ +@Deprecated(since = "5.5") public class HtmlTableWithColoredHeader implements HtmlTable { private final Header[] headers; private final Color headerColor; - private final List rows; + private final List rows; - public HtmlTableWithColoredHeader(Header[] headers, Color headerColor, List rows) { + public HtmlTableWithColoredHeader(Header[] headers, Color headerColor, List rows) { this.headers = headers; this.headerColor = headerColor; this.rows = rows; } public HtmlTableWithColoredHeader(Table table, Color headerColor) { - this(HtmlTable.mapToHeaders(table), headerColor, table.getRows()); + this(HtmlTable.mapToHeaders(table), headerColor, HtmlTable.mapToRows(table.getRows(), table.getTableColumnFormats())); } @Override @@ -63,15 +68,15 @@ public class HtmlTableWithColoredHeader implements HtmlTable { StringBuilder builtBody = new StringBuilder(); builtBody.append(""); if (rows.isEmpty()) { - appendRow(builtBody, "No Data"); + appendRow(builtBody, new TableCellDto("No Data")); } - for (Object[] row : rows) { + for (TableCellDto[] row : rows) { appendRow(builtBody, row); } return builtBody.append("").toString(); } - private void appendRow(StringBuilder builtBody, Object... row) { + private void appendRow(StringBuilder builtBody, TableCellDto... row) { int headerLength = row.length - 1; builtBody.append(""); for (int i = 0; i < headers.length; i++) { @@ -80,8 +85,8 @@ public class HtmlTableWithColoredHeader implements HtmlTable { builtBody.append("-"); } else { builtBody.append(""); - Object value = row[i]; - builtBody.append(value != null ? value : '-'); + TableCellDto cell = row[i]; + builtBody.append(cell != null ? cell.getValue() : '-'); } builtBody.append(""); } catch (ClassCastException | ArrayIndexOutOfBoundsException e) { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/JSONFactory.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/JSONFactory.java index 95dcdfeb9..5f32811f4 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/JSONFactory.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/JSONFactory.java @@ -17,6 +17,7 @@ package com.djrapitops.plan.delivery.rendering.json; import com.djrapitops.plan.delivery.domain.DateObj; +import com.djrapitops.plan.delivery.domain.datatransfer.ServerDto; import com.djrapitops.plan.delivery.domain.mutators.PlayerKillMutator; import com.djrapitops.plan.delivery.domain.mutators.SessionsMutator; import com.djrapitops.plan.delivery.domain.mutators.TPSMutator; @@ -25,6 +26,7 @@ import com.djrapitops.plan.delivery.formatting.Formatters; import com.djrapitops.plan.delivery.rendering.json.graphs.Graphs; import com.djrapitops.plan.extension.implementation.results.ExtensionTabData; import com.djrapitops.plan.extension.implementation.storage.queries.ExtensionServerTableDataQuery; +import com.djrapitops.plan.gathering.ServerUptimeCalculator; import com.djrapitops.plan.gathering.cache.SessionCache; import com.djrapitops.plan.gathering.domain.*; import com.djrapitops.plan.identification.Server; @@ -34,6 +36,7 @@ import com.djrapitops.plan.settings.config.PlanConfig; import com.djrapitops.plan.settings.config.paths.DisplaySettings; import com.djrapitops.plan.settings.config.paths.TimeSettings; import com.djrapitops.plan.settings.locale.Locale; +import com.djrapitops.plan.settings.locale.lang.GenericLang; import com.djrapitops.plan.settings.locale.lang.HtmlLang; import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.Database; @@ -48,6 +51,7 @@ import javax.inject.Inject; import javax.inject.Singleton; import java.util.*; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; /** * Factory with different JSON creation methods placed to a single class. @@ -61,6 +65,7 @@ public class JSONFactory { private final Locale locale; private final DBSystem dbSystem; private final ServerInfo serverInfo; + private final ServerUptimeCalculator serverUptimeCalculator; private final Graphs graphs; private final Formatters formatters; @@ -70,6 +75,7 @@ public class JSONFactory { Locale locale, DBSystem dbSystem, ServerInfo serverInfo, + ServerUptimeCalculator serverUptimeCalculator, Graphs graphs, Formatters formatters ) { @@ -77,6 +83,7 @@ public class JSONFactory { this.locale = locale; this.dbSystem = dbSystem; this.serverInfo = serverInfo; + this.serverUptimeCalculator = serverUptimeCalculator; this.graphs = graphs; this.formatters = formatters; } @@ -162,12 +169,18 @@ public class JSONFactory { } } - public List> serverPlayerKillsAsJSONMap(ServerUUID serverUUID) { + public List> serverPlayerKillsAsJSONMaps(ServerUUID serverUUID) { Database db = dbSystem.getDatabase(); List kills = db.query(KillQueries.fetchPlayerKillsOnServer(serverUUID, 100)); return new PlayerKillMutator(kills).toJSONAsMap(formatters); } + public Optional serverForIdentifier(String identifier) { + return dbSystem.getDatabase() + .query(ServerQueries.fetchServerMatchingIdentifier(identifier)) + .map(ServerDto::fromServer); + } + public Map serversAsJSONMaps() { Database db = dbSystem.getDatabase(); long now = System.currentTimeMillis(); @@ -183,7 +196,12 @@ public class JSONFactory { .findFirst() .map(Server::getUuid).orElse(null); - Map> tpsData = db.query( + Map serverUuidToId = new HashMap<>(); + for (Server server : serverInformation.values()) { + server.getId().ifPresent(serverId -> serverUuidToId.put(server.getUuid(), serverId)); + } + + Map> tpsDataByServerId = db.query( TPSQueries.fetchTPSDataOfAllServersBut(weekAgo, now, proxyUUID) ); Map totalPlayerCounts = db.query(PlayerCountQueries.newPlayerCounts(0, now)); @@ -199,6 +217,7 @@ public class JSONFactory { ServerUUID serverUUID = entry.getKey(); Map server = new HashMap<>(); server.put("name", entry.getValue().getIdentifiableName()); + server.put("serverUUID", entry.getValue().getUuid().toString()); Optional> recentPeak = db.query(TPSQueries.fetchPeakPlayerCount(serverUUID, now - TimeUnit.DAYS.toMillis(2L))); Optional> allTimePeak = db.query(TPSQueries.fetchAllTimePeakPlayerCount(serverUUID)); @@ -207,7 +226,7 @@ public class JSONFactory { server.put("last_peak_players", recentPeak.map(DateObj::getValue).orElse(0)); server.put("best_peak_players", allTimePeak.map(DateObj::getValue).orElse(0)); - TPSMutator tpsMonth = new TPSMutator(tpsData.getOrDefault(serverUUID, Collections.emptyList())); + TPSMutator tpsMonth = new TPSMutator(tpsDataByServerId.getOrDefault(serverUuidToId.get(serverUUID), Collections.emptyList())); server.put("playersOnline", tpsMonth.all().stream() .map(tps -> new double[]{tps.getDate(), tps.getPlayers()}) .toArray(double[][]::new)); @@ -219,6 +238,8 @@ public class JSONFactory { server.put("avg_tps", averageTPS != -1 ? decimals.apply(averageTPS) : locale.get(HtmlLang.UNIT_NO_DATA).toString()); server.put("low_tps_spikes", tpsWeek.lowTpsSpikeCount(config.get(DisplaySettings.GRAPH_TPS_THRESHOLD_MED))); server.put("downtime", timeAmount.apply(tpsWeek.serverDownTime())); + server.put("current_uptime", serverUptimeCalculator.getServerUptimeMillis(serverUUID).map(timeAmount) + .orElse(locale.getString(GenericLang.UNAVAILABLE))); Optional online = tpsWeek.getLast(); server.put("online", online.map(point -> point.getDate() >= now - TimeUnit.MINUTES.toMillis(3L) ? point.getPlayers() : "Possibly offline") @@ -258,4 +279,10 @@ public class JSONFactory { return tableEntries; } + public Map> listServers() { + Collection servers = dbSystem.getDatabase().query(ServerQueries.fetchPlanServerInformationCollection()); + return Collections.singletonMap("servers", servers.stream() + .map(ServerDto::fromServer) + .collect(Collectors.toList())); + } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/PerformanceJSONCreator.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/PerformanceJSONCreator.java index 8d7767445..b94722ba6 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/PerformanceJSONCreator.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/PerformanceJSONCreator.java @@ -95,7 +95,7 @@ public class PerformanceJSONCreator implements ServerTabJSONCreator createInsightsMap(List tpsData) { TPSMutator tpsMutator = new TPSMutator(tpsData); - Integer tpsThreshold = config.get(DisplaySettings.GRAPH_TPS_THRESHOLD_MED); + Double tpsThreshold = config.get(DisplaySettings.GRAPH_TPS_THRESHOLD_MED); TPSMutator lowTPS = tpsMutator.filterTPSBetween(-1, tpsThreshold); Map insights = new HashMap<>(); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/PlayerJSONCreator.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/PlayerJSONCreator.java index c70e9f14c..b181a7f55 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/PlayerJSONCreator.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/PlayerJSONCreator.java @@ -17,17 +17,23 @@ package com.djrapitops.plan.delivery.rendering.json; import com.djrapitops.plan.delivery.domain.container.PlayerContainer; +import com.djrapitops.plan.delivery.domain.datatransfer.extension.ExtensionsDto; import com.djrapitops.plan.delivery.domain.keys.PlayerKeys; import com.djrapitops.plan.delivery.domain.mutators.*; import com.djrapitops.plan.delivery.formatting.Formatter; import com.djrapitops.plan.delivery.formatting.Formatters; import com.djrapitops.plan.delivery.rendering.html.Html; import com.djrapitops.plan.delivery.rendering.json.graphs.Graphs; +import com.djrapitops.plan.delivery.rendering.json.graphs.line.PingGraph; import com.djrapitops.plan.delivery.rendering.json.graphs.pie.WorldPie; +import com.djrapitops.plan.extension.implementation.results.ExtensionData; +import com.djrapitops.plan.extension.implementation.storage.queries.ExtensionPlayerDataQuery; import com.djrapitops.plan.gathering.cache.SessionCache; import com.djrapitops.plan.gathering.domain.GeoInfo; import com.djrapitops.plan.gathering.domain.PlayerKill; import com.djrapitops.plan.gathering.domain.WorldTimes; +import com.djrapitops.plan.gathering.domain.event.JoinAddress; +import com.djrapitops.plan.identification.Server; import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.settings.config.PlanConfig; import com.djrapitops.plan.settings.config.paths.DisplaySettings; @@ -42,6 +48,7 @@ import com.djrapitops.plan.storage.database.queries.containers.PlayerContainerQu import com.djrapitops.plan.storage.database.queries.objects.ServerQueries; import com.djrapitops.plan.utilities.comparators.DateHolderRecentComparator; import com.djrapitops.plan.utilities.java.Lists; +import com.djrapitops.plan.utilities.java.Maps; import org.apache.commons.text.StringEscapeUtils; import javax.inject.Inject; @@ -100,6 +107,11 @@ public class PlayerJSONCreator { PingMutator.forContainer(player).addPingToSessions(sessionsMutator.all()); Map data = new HashMap<>(); + + long now = System.currentTimeMillis(); + data.put("timestamp", now); + data.put("timestamp_f", year.apply(now)); + data.put("info", createInfoJSONMap(player, serverNames)); data.put("online_activity", createOnlineActivityJSONMap(sessionsMutator)); data.put("kill_data", createPvPPvEMap(player)); @@ -122,10 +134,26 @@ public class PlayerJSONCreator { data.put("calendar_series", graphs.calendar().playerCalendar(player).getEntries()); data.put("server_pie_series", graphs.pie().serverPreferencePie(serverNames, worldTimesPerServer).getSlices()); data.put("server_pie_colors", pieColors); + data.put("ping_graph", createPingGraphJson(player)); data.put("first_day", 1); // Monday + data.put("extensions", playerExtensionData(playerUUID)); return data; } + private Map createPingGraphJson(PlayerContainer player) { + PingGraph pingGraph = graphs.line().pingGraph(player.getUnsafe(PlayerKeys.PING)); + return Maps.builder(String.class, Object.class) + .put("min_ping_series", pingGraph.getMinGraph().getPoints()) + .put("avg_ping_series", pingGraph.getAvgGraph().getPoints()) + .put("max_ping_series", pingGraph.getMaxGraph().getPoints()) + .put("colors", Maps.builder(String.class, String.class) + .put("min", theme.getValue(ThemeVal.GRAPH_MIN_PING)) + .put("avg", theme.getValue(ThemeVal.GRAPH_AVG_PING)) + .put("max", theme.getValue(ThemeVal.GRAPH_MAX_PING)) + .build()) + .build(); + } + private Map createOnlineActivityJSONMap(SessionsMutator sessionsMutator) { long now = System.currentTimeMillis(); long monthAgo = now - TimeUnit.DAYS.toMillis(30L); @@ -166,6 +194,8 @@ public class PlayerJSONCreator { Map info = new HashMap<>(); + info.put("name", player.getValue(PlayerKeys.NAME).orElse(player.getUnsafe(PlayerKeys.UUID).toString())); + info.put("uuid", player.getUnsafe(PlayerKeys.UUID).toString()); info.put("online", SessionCache.getCachedSession(player.getUnsafe(PlayerKeys.UUID)).isPresent()); info.put("operator", player.getValue(PlayerKeys.OPERATOR).orElse(false)); info.put("banned", player.getValue(PlayerKeys.BANNED).orElse(false)); @@ -182,6 +212,10 @@ public class PlayerJSONCreator { info.put("activity_index", decimals.apply(activityIndex.getValue())); info.put("activity_index_group", activityIndex.getGroup()); info.put("favorite_server", perServer.favoriteServer().map(favoriteServer -> serverNames.getOrDefault(favoriteServer, favoriteServer.toString())).orElse(locale.getString(GenericLang.UNKNOWN))); + info.put("latest_join_address", sessions.latestSession() + .flatMap(session -> session.getExtraData(JoinAddress.class)) + .map(JoinAddress::getAddress) + .orElse("-")); double averagePing = ping.average(); int worstPing = ping.max(); int bestPing = ping.min(); @@ -268,6 +302,24 @@ public class PlayerJSONCreator { return list.size() <= index ? Optional.empty() : Optional.of(list.get(index)); } + + public List playerExtensionData(UUID playerUUID) { + Database database = dbSystem.getDatabase(); + Map> extensionPlayerData = database.query(new ExtensionPlayerDataQuery(playerUUID)); + Map servers = database.query(ServerQueries.fetchPlanServerInformation()); + + List playerData = new ArrayList<>(); + for (Map.Entry entry : servers.entrySet()) { + ServerUUID serverUUID = entry.getKey(); + playerData.add(new ExtensionsDto( + playerUUID.toString(), serverUUID.toString(), + entry.getValue().getIdentifiableName(), + extensionPlayerData.getOrDefault(serverUUID, Collections.emptyList()) + )); + } + return playerData; + } + public static class Nickname { final String nickname; final String server; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/ServerAccordion.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/ServerAccordion.java index fb1ec9089..d534d9a74 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/ServerAccordion.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/ServerAccordion.java @@ -78,11 +78,13 @@ public class ServerAccordion { SessionsMutator sessionsMutator = SessionsMutator.forContainer(ofServer); server.put("server_name", serverName); + server.put("server_uuid", serverUUID.toString()); server.put("banned", ofServer.getValue(PerServerKeys.BANNED).orElse(false)); server.put("operator", ofServer.getValue(PerServerKeys.OPERATOR).orElse(false)); server.put("registered", year.apply(ofServer.getValue(PerServerKeys.REGISTERED).orElse(0L))); server.put("last_seen", year.apply(sessionsMutator.toLastSeen())); + server.put("join_address", ofServer.getValue(PerServerKeys.JOIN_ADDRESS).orElse("-")); server.put("session_count", sessionsMutator.count()); server.put("playtime", timeAmount.apply(sessionsMutator.toPlaytime())); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/ServerOverviewJSONCreator.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/ServerOverviewJSONCreator.java index 0fd06754b..c2aabca69 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/ServerOverviewJSONCreator.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/ServerOverviewJSONCreator.java @@ -22,6 +22,7 @@ import com.djrapitops.plan.delivery.domain.mutators.TPSMutator; import com.djrapitops.plan.delivery.formatting.Formatter; import com.djrapitops.plan.delivery.formatting.Formatters; import com.djrapitops.plan.gathering.ServerSensor; +import com.djrapitops.plan.gathering.ServerUptimeCalculator; import com.djrapitops.plan.gathering.domain.TPS; import com.djrapitops.plan.identification.ServerInfo; import com.djrapitops.plan.identification.ServerUUID; @@ -65,6 +66,7 @@ public class ServerOverviewJSONCreator implements ServerTabJSONCreator timeAmount; private final Formatter decimals; private final Formatter percentage; + private final ServerUptimeCalculator serverUptimeCalculator; private final Formatter year; @Inject @@ -74,6 +76,7 @@ public class ServerOverviewJSONCreator implements ServerTabJSONCreator serverSensor, + ServerUptimeCalculator serverUptimeCalculator, Formatters formatters ) { this.config = config; @@ -81,6 +84,7 @@ public class ServerOverviewJSONCreator implements ServerTabJSONCreator values = lowestResolutionData.toArrays(new LineGraph.GapStrategy( config.isTrue(DisplaySettings.GAPS_IN_GRAPH_DATA), lowestResolution + TimeUnit.MINUTES.toMillis(1), @@ -172,6 +183,8 @@ public class GraphJSONCreator { .put("diskThresholdMed", config.get(DisplaySettings.GRAPH_DISK_THRESHOLD_MED)) .put("diskThresholdHigh", config.get(DisplaySettings.GRAPH_DISK_THRESHOLD_HIGH)) .build()) + .put("serverName", serverName) + .put("serverUUID", serverUUID) .build(); } @@ -379,7 +392,7 @@ public class GraphJSONCreator { long now = System.currentTimeMillis(); List pings = db.query(PingQueries.fetchPingDataOfServer(now - TimeUnit.DAYS.toMillis(180L), now, serverUUID)); - PingGraph pingGraph = graphs.line().pingGraph(new PingMutator(pings).mutateToByMinutePings().all());// TODO Optimize in query + PingGraph pingGraph = graphs.line().pingGraph(new PingMutator(pings).mutateToByMinutePings().all()); return "{\"min_ping_series\":" + pingGraph.getMinGraph().toHighChartsSeries() + ",\"avg_ping_series\":" + pingGraph.getAvgGraph().toHighChartsSeries() + @@ -417,7 +430,7 @@ public class GraphJSONCreator { public Map playerHostnamePieJSONAsMap() { String[] pieColors = theme.getPieColors(ThemeVal.GRAPH_WORLD_PIE); - Map joinAddresses = dbSystem.getDatabase().query(UserInfoQueries.joinAddresses()); + Map joinAddresses = dbSystem.getDatabase().query(JoinAddressQueries.latestJoinAddresses()); translateUnknown(joinAddresses); @@ -429,7 +442,7 @@ public class GraphJSONCreator { public Map playerHostnamePieJSONAsMap(ServerUUID serverUUID) { String[] pieColors = theme.getPieColors(ThemeVal.GRAPH_WORLD_PIE); - Map joinAddresses = dbSystem.getDatabase().query(UserInfoQueries.joinAddresses(serverUUID)); + Map joinAddresses = dbSystem.getDatabase().query(JoinAddressQueries.latestJoinAddresses(serverUUID)); translateUnknown(joinAddresses); @@ -440,10 +453,46 @@ public class GraphJSONCreator { } public void translateUnknown(Map joinAddresses) { - Integer unknown = joinAddresses.get("unknown"); + Integer unknown = joinAddresses.get(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP); if (unknown != null) { - joinAddresses.remove("unknown"); + joinAddresses.remove(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP); joinAddresses.put(locale.getString(GenericLang.UNKNOWN).toLowerCase(), unknown); } } + + public Map joinAddressesByDay(ServerUUID serverUUID, long after, long before) { + String[] pieColors = theme.getPieColors(ThemeVal.GRAPH_WORLD_PIE); + List>> joinAddresses = dbSystem.getDatabase().query(JoinAddressQueries.joinAddressesPerDay(serverUUID, config.getTimeZone().getOffset(System.currentTimeMillis()), after, before)); + + return mapToJson(pieColors, joinAddresses); + } + + public Map joinAddressesByDay(long after, long before) { + String[] pieColors = theme.getPieColors(ThemeVal.GRAPH_WORLD_PIE); + List>> joinAddresses = dbSystem.getDatabase().query(JoinAddressQueries.joinAddressesPerDay(config.getTimeZone().getOffset(System.currentTimeMillis()), after, before)); + + return mapToJson(pieColors, joinAddresses); + } + + private Map mapToJson(String[] pieColors, List>> joinAddresses) { + for (DateObj> addressesByDate : joinAddresses) { + translateUnknown(addressesByDate.getValue()); + } + + List joinAddressCounts = joinAddresses.stream() + .map(addressesOnDay -> new JoinAddressCounts( + addressesOnDay.getDate(), + addressesOnDay.getValue().entrySet() + .stream() + .map(JoinAddressCount::new) + .sorted() + .collect(Collectors.toList()))) + .sorted(new DateHolderOldestComparator()) + .collect(Collectors.toList()); + + return Maps.builder(String.class, Object.class) + .put("colors", pieColors) + .put("join_addresses_by_date", joinAddressCounts) + .build(); + } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/graphs/calendar/PlayerCalendar.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/graphs/calendar/PlayerCalendar.java index 8a7c6b77b..d2bff1e3b 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/graphs/calendar/PlayerCalendar.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/graphs/calendar/PlayerCalendar.java @@ -101,14 +101,14 @@ public class PlayerCalendar { long end = session.getEnd(); entries.add(CalendarEntry - .of(locale.getString(HtmlLang.SESSION) + ": " + length, + .of(length + " " + locale.getString(HtmlLang.SESSION), start + timeZone.getOffset(start)) .withEnd(end + timeZone.getOffset(end)) ); for (PlayerKill kill : session.getExtraData(PlayerKills.class).map(PlayerKills::asList).orElseGet(ArrayList::new)) { long time = kill.getDate(); - String victim = kill.getVictimName().orElse(kill.getVictim().toString()); + String victim = kill.getVictim().getName(); entries.add(CalendarEntry .of(locale.getString(HtmlLang.KILLED) + ": " + victim, time) .withEnd(time + fiveMinutes) diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/graphs/special/SpecialGraphFactory.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/graphs/special/SpecialGraphFactory.java index dc8726e35..27c259631 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/graphs/special/SpecialGraphFactory.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/graphs/special/SpecialGraphFactory.java @@ -19,9 +19,16 @@ package com.djrapitops.plan.delivery.rendering.json.graphs.special; import com.djrapitops.plan.delivery.domain.mutators.SessionsMutator; import com.djrapitops.plan.gathering.domain.FinishedSession; import com.djrapitops.plan.settings.config.PlanConfig; +import com.djrapitops.plan.storage.file.PlanFiles; +import com.djrapitops.plan.utilities.logging.ErrorContext; +import com.djrapitops.plan.utilities.logging.ErrorLogger; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; import javax.inject.Inject; import javax.inject.Singleton; +import java.io.IOException; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -33,12 +40,19 @@ import java.util.Map; @Singleton public class SpecialGraphFactory { + private final PlanFiles files; private final PlanConfig config; + private final Gson gson; + private final ErrorLogger errorLogger; + + private Map geoCodes = null; @Inject - public SpecialGraphFactory(PlanConfig config) { - // Inject Constructor. + public SpecialGraphFactory(PlanFiles files, PlanConfig config, Gson gson, ErrorLogger errorLogger) { + this.files = files; this.config = config; + this.gson = gson; + this.errorLogger = errorLogger; } public PunchCard punchCard(List sessions) { @@ -50,7 +64,18 @@ public class SpecialGraphFactory { } public WorldMap worldMap(Map geolocationCounts) { - return new WorldMap(geolocationCounts); + if (geoCodes == null) prepareGeocodes(); + return new WorldMap(geoCodes, geolocationCounts); + } + + private void prepareGeocodes() { + try { + geoCodes = files.getResourceFromJar("geocodes.json") + .asParsed(gson, new TypeToken<>() {}); + } catch (IOException e) { + geoCodes = new HashMap<>(); + errorLogger.error(e, ErrorContext.builder().whatToDo("Failed to read geocodes.json from jar. Try restarting the server.").build()); + } } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/graphs/special/WorldMap.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/graphs/special/WorldMap.java index 55f66a993..a800d5157 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/graphs/special/WorldMap.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/graphs/special/WorldMap.java @@ -31,33 +31,20 @@ import java.util.stream.Collectors; */ public class WorldMap { + private final Map geoCodes; private final Map geoCodeCounts; - WorldMap(PlayersMutator mutator) { + WorldMap(Map geoCodes, PlayersMutator mutator) { + this.geoCodes = geoCodes; this.geoCodeCounts = toGeoCodeCounts(mutator.getGeolocations()); } - WorldMap(Map geolocationCounts) { + WorldMap(Map geoCodes, Map geolocationCounts) { + this.geoCodes = geoCodes; this.geoCodeCounts = toGeoCodeCounts(geolocationCounts); } - private static Map getGeoCodes() { - Map geoCodes = new HashMap<>(); - // Countries & Codes have been copied from a iso-a3 specification file. - // Each index corresponds to each code. - String[] countries = new String[]{"Afghanistan", "Albania", "Algeria", "American Samoa", "Andorra", "Angola", "Anguilla", "Antigua and Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria", "Azerbaijan", "Bahamas, The", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegovina", "Botswana", "Brazil", "British Virgin Islands", "Brunei", "Bulgaria", "Burkina Faso", "Burma", "Burundi", "Cabo Verde", "Cambodia", "Cameroon", "Canada", "Cayman Islands", "Central African Republic", "Chad", "Chile", "China", "Colombia", "Comoros", "Congo, Democratic Republic of the", "Congo, Republic of the", "Cook Islands", "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba", "Curacao", "Cyprus", "Czech Republic", "Czechia", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Falkland Islands (Islas Malvinas)", "Faroe Islands", "Fiji", "Finland", "France", "French Polynesia", "Gabon", "Gambia, The", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guam", "Guatemala", "Guernsey", "Guinea-Bissau", "Guinea", "Guyana", "Haiti", "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran", "Iraq", "Ireland", "Isle of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya", "Kiribati", "North Korea", "South Korea", "Kosovo", "Kuwait", "Kyrgyzstan", "Laos", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg", "Macau", "Macedonia", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Mauritania", "Mauritius", "Mexico", "Micronesia, Federated States of", "Moldova", "Monaco", "Mongolia", "Montenegro", "Morocco", "Mozambique", "Namibia", "Nepal", "Netherlands", "New Caledonia", "New Zealand", "Nicaragua", "Nigeria", "Niger", "Niue", "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Poland", "Portugal", "Puerto Rico", "Qatar", "Romania", "Russia", "Rwanda", "Saint Kitts and Nevis", "Saint Lucia", "Saint Martin", "Saint Pierre and Miquelon", "Saint Vincent and the Grenadines", "Samoa", "San Marino", "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore", "Sint Maarten", "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa", "South Sudan", "Spain", "Sri Lanka", "Sudan", "Suriname", "Swaziland", "Sweden", "Switzerland", "Syria", "Taiwan", "Tajikistan", "Tanzania", "Thailand", "Timor-Leste", "Togo", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom", "United States", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela", "Vietnam", "Virgin Islands", "West Bank", "Yemen", "Zambia", "Zimbabwe"}; - String[] codes = new String[]{"AFG", "ALB", "DZA", "ASM", "AND", "AGO", "AIA", "ATG", "ARG", "ARM", "ABW", "AUS", "AUT", "AZE", "BHM", "BHR", "BGD", "BRB", "BLR", "BEL", "BLZ", "BEN", "BMU", "BTN", "BOL", "BIH", "BWA", "BRA", "VGB", "BRN", "BGR", "BFA", "MMR", "BDI", "CPV", "KHM", "CMR", "CAN", "CYM", "CAF", "TCD", "CHL", "CHN", "COL", "COM", "COD", "COG", "COK", "CRI", "CIV", "HRV", "CUB", "CUW", "CYP", "CZE", "CZE", "DNK", "DJI", "DMA", "DOM", "ECU", "EGY", "SLV", "GNQ", "ERI", "EST", "ETH", "FLK", "FRO", "FJI", "FIN", "FRA", "PYF", "GAB", "GMB", "GEO", "DEU", "GHA", "GIB", "GRC", "GRL", "GRD", "GUM", "GTM", "GGY", "GNB", "GIN", "GUY", "HTI", "HND", "HKG", "HUN", "ISL", "IND", "IDN", "IRN", "IRQ", "IRL", "IMN", "ISR", "ITA", "JAM", "JPN", "JEY", "JOR", "KAZ", "KEN", "KIR", "KOR", "PRK", "KSV", "KWT", "KGZ", "LAO", "LVA", "LBN", "LSO", "LBR", "LBY", "LIE", "LTU", "LUX", "MAC", "MKD", "MDG", "MWI", "MYS", "MDV", "MLI", "MLT", "MHL", "MRT", "MUS", "MEX", "FSM", "MDA", "MCO", "MNG", "MNE", "MAR", "MOZ", "NAM", "NPL", "NLD", "NCL", "NZL", "NIC", "NGA", "NER", "NIU", "MNP", "NOR", "OMN", "PAK", "PLW", "PAN", "PNG", "PRY", "PER", "PHL", "POL", "PRT", "PRI", "QAT", "ROU", "RUS", "RWA", "KNA", "LCA", "MAF", "SPM", "VCT", "WSM", "SMR", "STP", "SAU", "SEN", "SRB", "SYC", "SLE", "SGP", "SXM", "SVK", "SVN", "SLB", "SOM", "ZAF", "SSD", "ESP", "LKA", "SDN", "SUR", "SWZ", "SWE", "CHE", "SYR", "TWN", "TJK", "TZA", "THA", "TLS", "TGO", "TON", "TTO", "TUN", "TUR", "TKM", "TUV", "UGA", "UKR", "ARE", "GBR", "USA", "URY", "UZB", "VUT", "VEN", "VNM", "VGB", "WBG", "YEM", "ZMB", "ZWE"}; - for (int i = 0; i < countries.length; i++) { - String country = countries[i]; - String countryCode = codes[i]; - - geoCodes.put(country.toLowerCase(), countryCode); - } - return geoCodes; - } - private Map toGeoCodeCounts(Map geolocationCounts) { - Map geoCodes = getGeoCodes(); Map codeCounts = new HashMap<>(); for (Map.Entry entry : geolocationCounts.entrySet()) { @@ -75,7 +62,6 @@ public class WorldMap { private Map toGeoCodeCounts(List geoLocations) { Map codeCounts = new HashMap<>(); - Map geoCodes = getGeoCodes(); for (String geoLocation : geoLocations) { String countryCode = geoCodes.get(geoLocation.toLowerCase()); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/network/NetworkOverviewJSONCreator.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/network/NetworkOverviewJSONCreator.java index 48f688a54..4457adff6 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/network/NetworkOverviewJSONCreator.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/network/NetworkOverviewJSONCreator.java @@ -22,10 +22,13 @@ import com.djrapitops.plan.delivery.formatting.Formatter; import com.djrapitops.plan.delivery.formatting.Formatters; import com.djrapitops.plan.delivery.rendering.json.Trend; import com.djrapitops.plan.gathering.ServerSensor; +import com.djrapitops.plan.gathering.ServerUptimeCalculator; import com.djrapitops.plan.identification.ServerInfo; import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.settings.config.PlanConfig; import com.djrapitops.plan.settings.config.paths.TimeSettings; +import com.djrapitops.plan.settings.locale.Locale; +import com.djrapitops.plan.settings.locale.lang.GenericLang; import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.Database; import com.djrapitops.plan.storage.database.queries.analysis.NetworkActivityIndexQueries; @@ -50,24 +53,30 @@ public class NetworkOverviewJSONCreator implements NetworkTabJSONCreator day; private final PlanConfig config; + private final Locale locale; private final DBSystem dbSystem; private final ServerInfo serverInfo; private final ServerSensor serverSensor; private final Formatter timeAmount; + private final ServerUptimeCalculator serverUptimeCalculator; private final Formatter year; @Inject public NetworkOverviewJSONCreator( PlanConfig config, + Locale locale, DBSystem dbSystem, ServerInfo serverInfo, ServerSensor serverSensor, + ServerUptimeCalculator serverUptimeCalculator, Formatters formatters ) { this.config = config; + this.locale = locale; this.dbSystem = dbSystem; this.serverInfo = serverInfo; this.serverSensor = serverSensor; + this.serverUptimeCalculator = serverUptimeCalculator; year = formatters.year(); day = formatters.dayLong(); @@ -127,6 +136,8 @@ public class NetworkOverviewJSONCreator implements NetworkTabJSONCreator. - */ -package com.djrapitops.plan.delivery.rendering.pages; - -import com.djrapitops.plan.delivery.formatting.PlaceholderReplacer; -import com.djrapitops.plan.delivery.rendering.html.Contributors; -import com.djrapitops.plan.delivery.rendering.html.icon.Icon; -import com.djrapitops.plan.settings.locale.Locale; -import com.djrapitops.plan.settings.theme.Theme; -import com.djrapitops.plan.utilities.java.UnaryChain; -import com.djrapitops.plan.version.VersionChecker; - -/** - * Html String generator for /login and /register page. - * - * @author AuroraLS3 - */ -public class ErrorsPage implements Page { - - private final String template; - private final Locale locale; - private final Theme theme; - private final VersionChecker versionChecker; - - ErrorsPage( - String htmlTemplate, - Locale locale, - Theme theme, - VersionChecker versionChecker) { - this.template = htmlTemplate; - this.locale = locale; - this.theme = theme; - this.versionChecker = versionChecker; - } - - @Override - public String toHtml() { - PlaceholderReplacer placeholders = new PlaceholderReplacer(); - placeholders.put("title", Icon.called("bug").build().toHtml() + " Error logs"); - placeholders.put("titleText", "Error logs"); - placeholders.put("paragraph", buildBody()); - placeholders.put("version", versionChecker.getUpdateButton().orElse(versionChecker.getCurrentVersionButton())); - placeholders.put("updateModal", versionChecker.getUpdateModal()); - placeholders.put("contributors", Contributors.generateContributorHtml()); - return UnaryChain.of(template) - .chain(theme::replaceThemeColors) - .chain(placeholders::apply) - .chain(locale::replaceLanguageInHtml) - .apply(); - } - - private String buildBody() { - //language=HTML - return "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - "
Logfile Occurrences
\n" + - "\n" + - ""; - } - -} \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/InternalErrorPage.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/InternalErrorPage.java index 1d8f4efba..cb63709ff 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/InternalErrorPage.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/InternalErrorPage.java @@ -20,7 +20,9 @@ import com.djrapitops.plan.delivery.formatting.PlaceholderReplacer; import com.djrapitops.plan.delivery.rendering.html.Contributors; import com.djrapitops.plan.delivery.rendering.html.Html; import com.djrapitops.plan.delivery.rendering.html.icon.Icon; +import com.djrapitops.plan.exceptions.ExceptionWithContext; import com.djrapitops.plan.version.VersionChecker; +import org.apache.commons.text.TextStringBuilder; /** * Page to display error stacktrace. @@ -52,19 +54,30 @@ public class InternalErrorPage implements Page { placeholders.put("title", Icon.called("bug") + " 500 Internal Error occurred"); placeholders.put("titleText", "500 Internal Error occurred"); placeholders.put("paragraph", createContent()); - placeholders.put("version", versionChecker.getUpdateButton().orElse(versionChecker.getCurrentVersionButton())); + placeholders.put("versionButton", versionChecker.getUpdateButton().orElse(versionChecker.getCurrentVersionButton())); + placeholders.put("version", versionChecker.getCurrentVersion()); placeholders.put("updateModal", versionChecker.getUpdateModal()); placeholders.put("contributors", Contributors.generateContributorHtml()); return placeholders.apply(template); } private String createContent() { - StringBuilder paragraph = new StringBuilder(); + TextStringBuilder paragraph = new TextStringBuilder(); paragraph.append("Please report this issue here: "); paragraph.append(Html.LINK.create("https://github.com/plan-player-analytics/Plan/issues", "Issues")); paragraph.append("

");
         paragraph.append(error).append(" | ").append(errorMsg);
 
+        if (error instanceof ExceptionWithContext) {
+            ((ExceptionWithContext) error).getContext()
+                    .ifPresent(context -> paragraph.append(context.getWhatToDo()
+                                    .map(whatToDo -> "
What to do about it: " + whatToDo) + .orElse("
Error message: " + error.getMessage())) + .append("

Related things:
") + .appendWithSeparators(context.toLines(), "
") + .append("
")); + } + for (StackTraceElement element : error.getStackTrace()) { paragraph.append("
"); paragraph.append(" ").append(element); @@ -78,7 +91,7 @@ public class InternalErrorPage implements Page { return paragraph.toString(); } - private void appendCause(Throwable cause, StringBuilder paragraph) { + private void appendCause(Throwable cause, TextStringBuilder paragraph) { paragraph.append("
Caused by: ").append(cause); for (StackTraceElement element : cause.getStackTrace()) { paragraph.append("
"); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/LoginPage.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/LoginPage.java index f5e30b48f..4302f93a8 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/LoginPage.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/LoginPage.java @@ -21,6 +21,7 @@ import com.djrapitops.plan.identification.ServerInfo; import com.djrapitops.plan.settings.locale.Locale; import com.djrapitops.plan.settings.theme.Theme; import com.djrapitops.plan.utilities.java.UnaryChain; +import com.djrapitops.plan.version.VersionChecker; /** * Html String generator for /login and /register page. @@ -34,22 +35,27 @@ public class LoginPage implements Page { private final Locale locale; private final Theme theme; + private final VersionChecker versionChecker; + LoginPage( String htmlTemplate, ServerInfo serverInfo, Locale locale, - Theme theme + Theme theme, + VersionChecker versionChecker ) { this.template = htmlTemplate; this.serverInfo = serverInfo; this.locale = locale; this.theme = theme; + this.versionChecker = versionChecker; } @Override public String toHtml() { PlaceholderReplacer placeholders = new PlaceholderReplacer(); placeholders.put("command", getCommand()); + placeholders.put("version", versionChecker.getCurrentVersion()); return UnaryChain.of(template) .chain(theme::replaceThemeColors) .chain(placeholders::apply) diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/NetworkPage.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/NetworkPage.java index 2faf0d81b..0bd9e2366 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/NetworkPage.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/NetworkPage.java @@ -52,32 +52,31 @@ public class NetworkPage implements Page { private final VersionChecker versionChecker; private final PlanConfig config; private final Theme theme; - private final Locale locale; private final ServerInfo serverInfo; private final JSONStorage jsonStorage; private final Formatters formatters; + private final Locale locale; NetworkPage( String templateHtml, - DBSystem dbSystem, VersionChecker versionChecker, PlanConfig config, Theme theme, - Locale locale, ServerInfo serverInfo, JSONStorage jsonStorage, - Formatters formatters + Formatters formatters, + Locale locale ) { this.templateHtml = templateHtml; this.dbSystem = dbSystem; this.versionChecker = versionChecker; this.config = config; this.theme = theme; - this.locale = locale; this.serverInfo = serverInfo; this.jsonStorage = jsonStorage; this.formatters = formatters; + this.locale = locale; } @Override @@ -99,7 +98,8 @@ public class NetworkPage implements Page { placeholders.put("avgPingColor", theme.getValue(ThemeVal.GRAPH_AVG_PING)); placeholders.put("timeZone", config.getTimeZoneOffsetHours()); - placeholders.put("version", versionChecker.getUpdateButton().orElse(versionChecker.getCurrentVersionButton())); + placeholders.put("versionButton", versionChecker.getUpdateButton().orElse(versionChecker.getCurrentVersionButton())); + placeholders.put("version", versionChecker.getCurrentVersion()); placeholders.put("updateModal", versionChecker.getUpdateModal()); placeholders.put("contributors", Contributors.generateContributorHtml()); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/PageFactory.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/PageFactory.java index 1eda6ed2e..e026e6e63 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/PageFactory.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/PageFactory.java @@ -28,6 +28,7 @@ import com.djrapitops.plan.identification.Server; import com.djrapitops.plan.identification.ServerInfo; import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.settings.config.PlanConfig; +import com.djrapitops.plan.settings.config.paths.PluginSettings; import com.djrapitops.plan.settings.locale.Locale; import com.djrapitops.plan.settings.theme.Theme; import com.djrapitops.plan.storage.database.DBSystem; @@ -55,39 +56,44 @@ public class PageFactory { private final Lazy versionChecker; private final Lazy files; private final Lazy config; - private final Lazy locale; private final Lazy theme; private final Lazy dbSystem; private final Lazy serverInfo; private final Lazy jsonStorage; private final Lazy formatters; + private final Lazy locale; @Inject public PageFactory( Lazy versionChecker, Lazy files, Lazy config, - Lazy locale, Lazy theme, Lazy dbSystem, Lazy serverInfo, Lazy jsonStorage, - Lazy formatters + Lazy formatters, + Lazy locale ) { this.versionChecker = versionChecker; this.files = files; this.config = config; - this.locale = locale; this.theme = theme; this.dbSystem = dbSystem; this.serverInfo = serverInfo; this.jsonStorage = jsonStorage; this.formatters = formatters; + this.locale = locale; } - public PlayersPage playersPage() throws IOException { + public Page playersPage() throws IOException { + if (config.get().isTrue(PluginSettings.FRONTEND_BETA)) { + String reactHtml = getResource("index.html"); + return () -> reactHtml; + } + return new PlayersPage(getResource("players.html"), versionChecker.get(), - config.get(), locale.get(), theme.get(), serverInfo.get()); + config.get(), theme.get(), serverInfo.get()); } /** @@ -101,28 +107,44 @@ public class PageFactory { public Page serverPage(ServerUUID serverUUID) throws IOException { Server server = dbSystem.get().getDatabase().query(ServerQueries.fetchServerMatchingIdentifier(serverUUID)) .orElseThrow(() -> new NotFoundException("Server not found in the database")); + + if (config.get().isTrue(PluginSettings.FRONTEND_BETA)) { + String reactHtml = getResource("index.html"); + return () -> reactHtml; + } + return new ServerPage( getResource("server.html"), server, config.get(), theme.get(), - locale.get(), versionChecker.get(), dbSystem.get(), serverInfo.get(), jsonStorage.get(), - formatters.get() + formatters.get(), + locale.get() ); } - public PlayerPage playerPage(UUID playerUUID) throws IOException { + public Page playerPage(UUID playerUUID) throws IOException { Database db = dbSystem.get().getDatabase(); PlayerContainer player = db.query(ContainerFetchQueries.fetchPlayerContainer(playerUUID)); + + if (config.get().isTrue(PluginSettings.FRONTEND_BETA)) { + String reactHtml = getResource("index.html"); + return () -> reactHtml; + } + return new PlayerPage( getResource("player.html"), player, versionChecker.get(), - config.get(), this, theme.get(), locale.get(), - formatters.get(), serverInfo.get() + config.get(), + this, + theme.get(), + formatters.get(), + serverInfo.get(), + locale.get() ); } @@ -159,14 +181,22 @@ public class PageFactory { return new PlayerPluginTab(navs.toString(), tabs.toString()); } - public NetworkPage networkPage() throws IOException { + public Page networkPage() throws IOException { + if (config.get().isTrue(PluginSettings.FRONTEND_BETA)) { + String reactHtml = getResource("index.html"); + return () -> reactHtml; + } + return new NetworkPage(getResource("network.html"), dbSystem.get(), versionChecker.get(), - config.get(), theme.get(), locale.get(), + config.get(), + theme.get(), serverInfo.get(), jsonStorage.get(), - formatters.get()); + formatters.get(), + locale.get() + ); } public Page internalErrorPage(String message, Throwable error) { @@ -177,20 +207,19 @@ public class PageFactory { } catch (IOException noParse) { return () -> "Error occurred: " + error.toString() + ", additional error occurred when attempting to render error page to user: " + - noParse.toString(); + noParse; } } public Page errorPage(String title, String error) throws IOException { return new ErrorMessagePage( getResource("error.html"), title, error, - versionChecker.get(), locale.get(), theme.get()); + versionChecker.get(), theme.get()); } public Page errorPage(Icon icon, String title, String error) throws IOException { return new ErrorMessagePage( - getResource("error.html"), icon, title, error, - locale.get(), theme.get(), versionChecker.get()); + getResource("error.html"), icon, title, error, theme.get(), versionChecker.get()); } public String getResource(String name) throws IOException { @@ -204,11 +233,16 @@ public class PageFactory { } public Page loginPage() throws IOException { - return new LoginPage(getResource("login.html"), serverInfo.get(), locale.get(), theme.get()); + if (config.get().isTrue(PluginSettings.FRONTEND_BETA)) { + String reactHtml = getResource("index.html"); + return () -> reactHtml; + } + + return new LoginPage(getResource("login.html"), serverInfo.get(), locale.get(), theme.get(), versionChecker.get()); } public Page registerPage() throws IOException { - return new LoginPage(getResource("register.html"), serverInfo.get(), locale.get(), theme.get()); + return new LoginPage(getResource("register.html"), serverInfo.get(), locale.get(), theme.get(), versionChecker.get()); } public Page queryPage() throws IOException { @@ -219,6 +253,7 @@ public class PageFactory { } public Page errorsPage() throws IOException { - return new ErrorsPage(getResource("error.html"), locale.get(), theme.get(), versionChecker.get()); + String reactHtml = getResource("index.html"); + return () -> reactHtml; } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/PlayerPage.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/PlayerPage.java index 1e00afdb9..87f081a10 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/PlayerPage.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/PlayerPage.java @@ -25,6 +25,7 @@ import com.djrapitops.plan.delivery.rendering.html.Contributors; import com.djrapitops.plan.delivery.rendering.html.Html; import com.djrapitops.plan.identification.ServerInfo; import com.djrapitops.plan.settings.config.PlanConfig; +import com.djrapitops.plan.settings.config.paths.DisplaySettings; import com.djrapitops.plan.settings.locale.Locale; import com.djrapitops.plan.settings.theme.Theme; import com.djrapitops.plan.settings.theme.ThemeVal; @@ -48,11 +49,11 @@ public class PlayerPage implements Page { private final PlanConfig config; private final PageFactory pageFactory; private final Theme theme; - private final Locale locale; private final ServerInfo serverInfo; private final Formatter clockLongFormatter; private final Formatter secondLongFormatter; + private final Locale locale; PlayerPage( String templateHtml, @@ -61,9 +62,9 @@ public class PlayerPage implements Page { PlanConfig config, PageFactory pageFactory, Theme theme, - Locale locale, Formatters formatters, - ServerInfo serverInfo + ServerInfo serverInfo, + Locale locale ) { this.templateHtml = templateHtml; this.player = player; @@ -71,16 +72,16 @@ public class PlayerPage implements Page { this.config = config; this.pageFactory = pageFactory; this.theme = theme; - this.locale = locale; this.serverInfo = serverInfo; clockLongFormatter = formatters.clockLong(); secondLongFormatter = formatters.secondLong(); + this.locale = locale; } @Override public String toHtml() { - if (!player.getValue(PlayerKeys.REGISTERED).isPresent()) { + if (player.getValue(PlayerKeys.REGISTERED).isEmpty()) { throw new IllegalStateException("Player is not registered"); } return createFor(player); @@ -94,11 +95,14 @@ public class PlayerPage implements Page { placeholders.put("refresh", clockLongFormatter.apply(now)); placeholders.put("refreshFull", secondLongFormatter.apply(now)); - placeholders.put("version", versionChecker.getUpdateButton().orElse(versionChecker.getCurrentVersionButton())); + placeholders.put("versionButton", versionChecker.getUpdateButton().orElse(versionChecker.getCurrentVersionButton())); + placeholders.put("version", versionChecker.getCurrentVersion()); placeholders.put("updateModal", versionChecker.getUpdateModal()); String playerName = player.getValue(PlayerKeys.NAME).orElse(playerUUID.toString()); placeholders.put("playerName", playerName); + placeholders.put("playerUUID", playerUUID); + placeholders.put("playerHeadUrl", config.get(DisplaySettings.PLAYER_HEAD_IMG_URL)); placeholders.put("timeZone", config.getTimeZoneOffsetHours()); placeholders.put("gmPieColors", theme.getValue(ThemeVal.GRAPH_GM_PIE)); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/PlayersPage.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/PlayersPage.java index 78a8b3db0..19edd782c 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/PlayersPage.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/PlayersPage.java @@ -23,7 +23,6 @@ import com.djrapitops.plan.settings.config.PlanConfig; import com.djrapitops.plan.settings.config.paths.PluginSettings; import com.djrapitops.plan.settings.config.paths.ProxySettings; import com.djrapitops.plan.settings.config.paths.WebserverSettings; -import com.djrapitops.plan.settings.locale.Locale; import com.djrapitops.plan.settings.theme.Theme; import com.djrapitops.plan.version.VersionChecker; @@ -37,7 +36,6 @@ public class PlayersPage implements Page { private final String templateHtml; private final VersionChecker versionChecker; private final PlanConfig config; - private final Locale locale; private final Theme theme; private final ServerInfo serverInfo; @@ -45,14 +43,12 @@ public class PlayersPage implements Page { String templateHtml, VersionChecker versionChecker, PlanConfig config, - Locale locale, Theme theme, ServerInfo serverInfo ) { this.templateHtml = templateHtml; this.versionChecker = versionChecker; this.config = config; - this.locale = locale; this.theme = theme; this.serverInfo = serverInfo; } @@ -62,7 +58,8 @@ public class PlayersPage implements Page { PlaceholderReplacer placeholders = new PlaceholderReplacer(); placeholders.put("refreshBarrier", config.get(WebserverSettings.REDUCED_REFRESH_BARRIER)); - placeholders.put("version", versionChecker.getUpdateButton().orElse(versionChecker.getCurrentVersionButton())); + placeholders.put("versionButton", versionChecker.getUpdateButton().orElse(versionChecker.getCurrentVersionButton())); + placeholders.put("version", versionChecker.getCurrentVersion()); placeholders.put("updateModal", versionChecker.getUpdateModal()); placeholders.put("contributors", Contributors.generateContributorHtml()); if (serverInfo.getServer().isProxy()) { @@ -71,6 +68,6 @@ public class PlayersPage implements Page { placeholders.put("networkName", config.get(PluginSettings.SERVER_NAME)); } - return locale.replaceLanguageInHtml(placeholders.apply(theme.replaceThemeColors(templateHtml))); + return placeholders.apply(theme.replaceThemeColors(templateHtml)); } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/QueryPage.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/QueryPage.java index d42857cbb..111015108 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/QueryPage.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/QueryPage.java @@ -38,7 +38,9 @@ public class QueryPage implements Page { public QueryPage( String template, - Locale locale, Theme theme, VersionChecker versionChecker + Locale locale, + Theme theme, + VersionChecker versionChecker ) { this.template = template; this.locale = locale; @@ -49,7 +51,8 @@ public class QueryPage implements Page { @Override public String toHtml() { PlaceholderReplacer placeholders = new PlaceholderReplacer(); - placeholders.put("version", versionChecker.getUpdateButton().orElse(versionChecker.getCurrentVersionButton())); + placeholders.put("versionButton", versionChecker.getUpdateButton().orElse(versionChecker.getCurrentVersionButton())); + placeholders.put("version", versionChecker.getCurrentVersion()); placeholders.put("updateModal", versionChecker.getUpdateModal()); placeholders.put("contributors", Contributors.generateContributorHtml()); return UnaryChain.of(template) diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/ServerPage.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/ServerPage.java index e46125130..2c55f4876 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/ServerPage.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/ServerPage.java @@ -50,34 +50,34 @@ public class ServerPage implements Page { private final Server server; private final PlanConfig config; private final Theme theme; - private final Locale locale; private final VersionChecker versionChecker; private final DBSystem dbSystem; private final ServerInfo serverInfo; private final JSONStorage jsonStorage; private final Formatters formatters; + private final Locale locale; ServerPage( String templateHtml, Server server, PlanConfig config, Theme theme, - Locale locale, VersionChecker versionChecker, DBSystem dbSystem, ServerInfo serverInfo, JSONStorage jsonStorage, - Formatters formatters + Formatters formatters, + Locale locale ) { this.templateHtml = templateHtml; this.server = server; this.config = config; this.theme = theme; - this.locale = locale; this.versionChecker = versionChecker; this.dbSystem = dbSystem; this.serverInfo = serverInfo; this.jsonStorage = jsonStorage; this.formatters = formatters; + this.locale = locale; } @Override @@ -94,7 +94,8 @@ public class ServerPage implements Page { placeholders.put("gmPieColors", theme.getValue(ThemeVal.GRAPH_GM_PIE)); placeholders.put("contributors", Contributors.generateContributorHtml()); - placeholders.put("version", versionChecker.getUpdateButton().orElse(versionChecker.getCurrentVersionButton())); + placeholders.put("versionButton", versionChecker.getUpdateButton().orElse(versionChecker.getCurrentVersionButton())); + placeholders.put("version", versionChecker.getCurrentVersion()); placeholders.put("updateModal", versionChecker.getUpdateModal()); CachingSupplier pluginTabs = new CachingSupplier<>(() -> { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/web/AssetVersions.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/web/AssetVersions.java new file mode 100644 index 000000000..29de242a2 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/web/AssetVersions.java @@ -0,0 +1,64 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.web; + +import com.djrapitops.plan.settings.config.Config; +import com.djrapitops.plan.settings.config.ConfigNode; +import com.djrapitops.plan.settings.config.ConfigReader; +import com.djrapitops.plan.storage.file.PlanFiles; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.io.IOException; +import java.util.Optional; + +@Singleton +public class AssetVersions { + + private final PlanFiles files; + private Config webAssetConfig; + + @Inject + public AssetVersions( + PlanFiles files + ) { + this.files = files; + } + + public void prepare() throws IOException { + try (ConfigReader reader = new ConfigReader(files.getResourceFromJar("AssetVersion.yml").asInputStream())) { + webAssetConfig = reader.read(); + } + } + + public Optional getAssetVersion(String resource) { + if (webAssetConfig == null) return Optional.empty(); + + return webAssetConfig.getNode(resource.replace('.', ',')).map(ConfigNode::getLong); + } + + public Optional getLatestWebAssetVersion() { + if (webAssetConfig == null) return Optional.empty(); + + long max = 0; + for (String configPath : webAssetConfig.getConfigPaths()) { + max = Math.max(max, webAssetConfig.getLong(configPath)); + } + + return Optional.of(max); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/web/ResourceSvc.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/web/ResourceSvc.java index 2af14d94c..54fb14e0c 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/web/ResourceSvc.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/web/ResourceSvc.java @@ -146,7 +146,7 @@ public class ResourceSvc implements ResourceService { } } catch (IOException e) { errorLogger.warn(e, ErrorContext.builder() - .whatToDo("Report this or provide " + fileName + " in " + files.getCustomizationDirectory()) + .whatToDo("Report this or provide " + fileName + " in " + resourceSettings.getCustomizationDirectory()) .related("Fetching resource", "Of: " + pluginName, fileName).build()); } // Return original by default @@ -174,7 +174,7 @@ public class ResourceSvc implements ResourceService { WebResource original = source.get(); byte[] bytes = original.asBytes(); OpenOption[] overwrite = {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE}; - Path to = files.getCustomizationDirectory().resolve(fileName); + Path to = resourceSettings.getCustomizationDirectory().resolve(fileName); Path dir = to.getParent(); if (!Files.isSymbolicLink(dir)) Files.createDirectories(dir); Files.write(to, bytes, overwrite); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/web/ResourceWriteTask.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/web/ResourceWriteTask.java new file mode 100644 index 000000000..b44b00ae9 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/web/ResourceWriteTask.java @@ -0,0 +1,94 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.web; + +import com.djrapitops.plan.TaskSystem; +import com.djrapitops.plan.settings.config.ConfigNode; +import com.djrapitops.plan.settings.config.PlanConfig; +import com.djrapitops.plan.storage.file.PlanFiles; +import com.djrapitops.plan.utilities.logging.ErrorContext; +import com.djrapitops.plan.utilities.logging.ErrorLogger; +import net.playeranalytics.plugin.scheduling.RunnableFactory; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.io.FileNotFoundException; +import java.io.UncheckedIOException; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +/** + * Task in charge of writing html customized files on enable when they don't exist yet. + * + * @author AuroraLS3 + */ +@Singleton +public class ResourceWriteTask extends TaskSystem.Task { + + private final PlanConfig config; + private final PlanFiles files; + private final ErrorLogger errorLogger; + + @Inject + public ResourceWriteTask(PlanConfig config, PlanFiles files, ErrorLogger errorLogger) { + this.config = config; + this.files = files; + this.errorLogger = errorLogger; + } + + @Override + public void register(RunnableFactory runnableFactory) { + runnableFactory.create(this).runTaskLaterAsynchronously(3, TimeUnit.SECONDS); + } + + @Override + public void run() { + try { + runTask(); + } finally { + cancel(); + } + } + + private void runTask() { + ResourceService resourceService = ResourceService.getInstance(); + getPlanCustomizationNode().ifPresent(customizationNode -> { + for (ConfigNode child : customizationNode.getChildren()) { + if (child.getBoolean()) { + String resource = child.getKey(false).replace(',', '.'); + writeResource(resourceService, resource); + } + } + }); + } + + private void writeResource(ResourceService resourceService, String resource) { + try { + resourceService.getResource("Plan", resource, () -> files.getResourceFromJar("web/" + resource).asWebResource()); + } catch (UncheckedIOException resourceNoLongerFound) { + if (!(resourceNoLongerFound.getCause() instanceof FileNotFoundException)) { + errorLogger.error(resourceNoLongerFound, ErrorContext.builder() + .whatToDo("A resource could not be read from the jar: " + resource + ", try restarting the server. Report this if error still occurs afterwards: " + resourceNoLongerFound.getMessage()) + .build()); + } + } + } + + private Optional getPlanCustomizationNode() { + return config.getResourceSettings().getCustomizationConfigNode().getNode("Plan"); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/web/WebAssetVersionCheckTask.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/web/WebAssetVersionCheckTask.java new file mode 100644 index 000000000..b59596b96 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/web/WebAssetVersionCheckTask.java @@ -0,0 +1,140 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.web; + +import com.djrapitops.plan.TaskSystem; +import com.djrapitops.plan.delivery.formatting.Formatters; +import com.djrapitops.plan.settings.config.ConfigNode; +import com.djrapitops.plan.settings.config.PlanConfig; +import com.djrapitops.plan.storage.file.PlanFiles; +import net.playeranalytics.plugin.scheduling.RunnableFactory; +import net.playeranalytics.plugin.server.PluginLogger; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +/** + * Task in charge of checking html customized files on enable to see if they are outdated. + */ +@Singleton +public class WebAssetVersionCheckTask extends TaskSystem.Task { + + private final PlanConfig config; + private final PlanFiles files; + private final PluginLogger logger; + private final AssetVersions assetVersions; + private final Formatters formatters; + + @Inject + public WebAssetVersionCheckTask( + PlanConfig config, + PlanFiles files, + PluginLogger logger, + AssetVersions assetVersions, + Formatters formatters + ) { + this.config = config; + this.files = files; + this.logger = logger; + this.assetVersions = assetVersions; + this.formatters = formatters; + } + + @Override + public void register(RunnableFactory runnableFactory) { + runnableFactory.create(this).runTaskLaterAsynchronously(3, TimeUnit.SECONDS); + } + + @Override + public void run() { + try { + runTask(); + } finally { + cancel(); + } + } + + private void runTask() { + Optional planCustomizationNode = getPlanCustomizationNode(); + if (planCustomizationNode.isPresent()) { + try { + assetVersions.prepare(); + } catch (IOException e) { + logger.warn(String.format("Could not read web asset versions, %s", e.toString())); + logger.warn("Web asset version check will be skipped!"); + return; + } + + List outdated = new ArrayList<>(); + + for (ConfigNode child : planCustomizationNode.get().getChildren()) { + if (child.getBoolean()) { + String resource = child.getKey(false).replace(',', '.'); + findOutdatedResource(resource).ifPresent(outdated::add); + } + } + + if (!outdated.isEmpty()) { + logger.warn("You have customized files which are out of date due to recent updates!"); + logger.warn("Plan may not work properly until these files are updated to include the new modifications."); + logger.warn("See https://github.com/plan-player-analytics/Plan/commits/html to compare changes"); + } + for (AssetInfo asset : outdated) { + logger.warn(String.format("- %s was modified %s, but the plugin contains a version from %s", + asset.filename, + formatters.secondLong().apply(asset.modifiedAt), + formatters.secondLong().apply(asset.expectedModifiedAt) + )); + } + } + } + + private Optional findOutdatedResource(String resource) { + Optional resourceFile = files.attemptToFind(resource); + Optional webAssetVersion = assetVersions.getAssetVersion(resource); + if (resourceFile.isPresent() && webAssetVersion.isPresent() && webAssetVersion.get() > resourceFile.get().lastModified()) { + return Optional.of(new AssetInfo( + resource, + resourceFile.get().lastModified(), + webAssetVersion.get() + )); + } + return Optional.empty(); + } + + private Optional getPlanCustomizationNode() { + return config.getResourceSettings().getCustomizationConfigNode().getNode("Plan"); + } + + private static class AssetInfo { + public String filename; + public long modifiedAt; + public long expectedModifiedAt; + + public AssetInfo(String filename, long modifiedAt, long expectedModifiedAt) { + this.filename = filename; + this.modifiedAt = modifiedAt; + this.expectedModifiedAt = expectedModifiedAt; + } + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/Addresses.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/Addresses.java index 17dede8b4..5376d0f31 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/Addresses.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/Addresses.java @@ -16,6 +16,7 @@ */ package com.djrapitops.plan.delivery.webserver; +import com.djrapitops.plan.delivery.webserver.http.WebServer; import com.djrapitops.plan.identification.Server; import com.djrapitops.plan.identification.properties.ServerProperties; import com.djrapitops.plan.settings.config.PlanConfig; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/NonProxyWebserverDisableChecker.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/NonProxyWebserverDisableChecker.java index e5a0c793f..6555ab817 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/NonProxyWebserverDisableChecker.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/NonProxyWebserverDisableChecker.java @@ -16,6 +16,7 @@ */ package com.djrapitops.plan.delivery.webserver; +import com.djrapitops.plan.delivery.webserver.http.WebServer; import com.djrapitops.plan.settings.config.PlanConfig; import com.djrapitops.plan.settings.config.paths.PluginSettings; import com.djrapitops.plan.settings.config.paths.WebserverSettings; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/RequestBodyConverter.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/RequestBodyConverter.java new file mode 100644 index 000000000..12d8638cb --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/RequestBodyConverter.java @@ -0,0 +1,48 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.webserver; + +import com.djrapitops.plan.delivery.web.resolver.request.Request; +import com.djrapitops.plan.delivery.web.resolver.request.URIQuery; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +import java.nio.charset.StandardCharsets; + +public class RequestBodyConverter { + + private RequestBodyConverter() { + /* Static utility class */ + } + + /** + * Get the body of a request as an url-encoded form. + * + * @return {@link URIQuery}. + */ + public static URIQuery formBody(Request request) { + return new URIQuery(new String(request.getRequestBody(), StandardCharsets.UTF_8)); + } + + public static T bodyJson(Request request, Gson gson, Class ofType) { + return gson.fromJson(new String(request.getRequestBody(), StandardCharsets.UTF_8), ofType); + } + + public static T bodyJson(Request request, Gson gson, TypeToken ofType) { + return gson.fromJson(new String(request.getRequestBody(), StandardCharsets.UTF_8), ofType.getType()); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/RequestHandler.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/RequestHandler.java deleted file mode 100644 index 563fec43e..000000000 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/RequestHandler.java +++ /dev/null @@ -1,241 +0,0 @@ -/* - * This file is part of Player Analytics (Plan). - * - * Plan is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License v3 as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Plan is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Plan. If not, see . - */ -package com.djrapitops.plan.delivery.webserver; - -import com.djrapitops.plan.delivery.domain.auth.User; -import com.djrapitops.plan.delivery.web.resolver.Response; -import com.djrapitops.plan.delivery.web.resolver.request.Request; -import com.djrapitops.plan.delivery.web.resolver.request.URIPath; -import com.djrapitops.plan.delivery.web.resolver.request.URIQuery; -import com.djrapitops.plan.delivery.web.resolver.request.WebUser; -import com.djrapitops.plan.delivery.webserver.auth.*; -import com.djrapitops.plan.exceptions.WebUserAuthException; -import com.djrapitops.plan.settings.config.PlanConfig; -import com.djrapitops.plan.settings.config.paths.PluginSettings; -import com.djrapitops.plan.settings.config.paths.WebserverSettings; -import com.djrapitops.plan.settings.locale.Locale; -import com.djrapitops.plan.settings.locale.lang.PluginLang; -import com.djrapitops.plan.storage.database.DBSystem; -import com.djrapitops.plan.utilities.logging.ErrorContext; -import com.djrapitops.plan.utilities.logging.ErrorLogger; -import com.sun.net.httpserver.Headers; -import com.sun.net.httpserver.HttpExchange; -import com.sun.net.httpserver.HttpHandler; -import net.playeranalytics.plugin.server.PluginLogger; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.text.TextStringBuilder; - -import javax.inject.Inject; -import javax.inject.Singleton; -import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * HttpHandler for WebServer request management. - * - * @author AuroraLS3 - */ -@Singleton -public class RequestHandler implements HttpHandler { - - private final Locale locale; - private final PlanConfig config; - private final DBSystem dbSystem; - private final Addresses addresses; - private final ResponseResolver responseResolver; - private final ResponseFactory responseFactory; - private final PluginLogger logger; - private final ErrorLogger errorLogger; - - private final ActiveCookieStore activeCookieStore; - private final PassBruteForceGuard bruteForceGuard; - private List ipWhitelist = null; - - private final AtomicBoolean warnedAboutXForwardedSecurityIssue = new AtomicBoolean(false); - - @Inject - RequestHandler( - Locale locale, - PlanConfig config, - DBSystem dbSystem, - Addresses addresses, - ResponseResolver responseResolver, - ResponseFactory responseFactory, - ActiveCookieStore activeCookieStore, - PluginLogger logger, - ErrorLogger errorLogger - ) { - this.locale = locale; - this.config = config; - this.dbSystem = dbSystem; - this.addresses = addresses; - this.responseResolver = responseResolver; - this.responseFactory = responseFactory; - this.activeCookieStore = activeCookieStore; - this.logger = logger; - this.errorLogger = errorLogger; - - bruteForceGuard = new PassBruteForceGuard(); - } - - @Override - public void handle(HttpExchange exchange) { - try { - Response response = getResponse(exchange); - response.getHeaders().putIfAbsent("Access-Control-Allow-Origin", config.get(WebserverSettings.CORS_ALLOW_ORIGIN)); - response.getHeaders().putIfAbsent("Access-Control-Allow-Methods", "GET, OPTIONS"); - response.getHeaders().putIfAbsent("Access-Control-Allow-Credentials", "true"); - response.getHeaders().putIfAbsent("X-Robots-Tag", "noindex, nofollow"); - ResponseSender sender = new ResponseSender(addresses, exchange, response); - sender.send(); - } catch (Exception e) { - if (config.isTrue(PluginSettings.DEV_MODE)) { - logger.warn("THIS ERROR IS ONLY LOGGED IN DEV MODE:"); - errorLogger.warn(e, ErrorContext.builder() - .whatToDo("THIS ERROR IS ONLY LOGGED IN DEV MODE") - .related(exchange.getRequestMethod(), exchange.getRemoteAddress(), exchange.getRequestHeaders(), exchange.getResponseHeaders(), exchange.getRequestURI()) - .build()); - } - } finally { - exchange.close(); - } - } - - public Response getResponse(HttpExchange exchange) { - if (ipWhitelist == null) { - ipWhitelist = config.isTrue(WebserverSettings.IP_WHITELIST) - ? config.get(WebserverSettings.WHITELIST) - : Collections.emptyList(); - } - String accessor = getAccessorAddress(exchange); - Request request = null; - Response response; - try { - request = buildRequest(exchange); - if (bruteForceGuard.shouldPreventRequest(accessor)) { - response = responseFactory.failedLoginAttempts403(); - } else if (!ipWhitelist.isEmpty() && !ipWhitelist.contains(accessor)) { - response = responseFactory.ipWhitelist403(accessor); - logger.info(locale.getString(PluginLang.WEB_SERVER_NOTIFY_IP_WHITELIST_BLOCK, accessor, exchange.getRequestURI().toString())); - } else { - response = responseResolver.getResponse(request); - } - } catch (WebUserAuthException thrownByAuthentication) { - FailReason failReason = thrownByAuthentication.getFailReason(); - if (failReason == FailReason.USER_PASS_MISMATCH) { - bruteForceGuard.increaseAttemptCountOnFailedLogin(accessor); - response = responseFactory.badRequest(failReason.getReason(), "/auth/login"); - } else { - String from = exchange.getRequestURI().toASCIIString(); - String directTo = StringUtils.startsWithAny(from, "/auth/", "/login") ? "/login" : "/login?from=." + from; - response = Response.builder() - .redirectTo(directTo) - .setHeader("Set-Cookie", "auth=expired; Path=/; Max-Age=1; SameSite=Lax; Secure;") - .build(); - } - } - - if (bruteForceGuard.shouldPreventRequest(accessor)) { - response = responseFactory.failedLoginAttempts403(); - } - if (response.getCode() != 401 // Not failed - && response.getCode() != 403 // Not blocked - && (request != null && request.getUser().isPresent()) // Logged in - ) { - bruteForceGuard.resetAttemptCount(accessor); - } - return response; - } - - private String getAccessorAddress(HttpExchange exchange) { - if (config.isTrue(WebserverSettings.IP_WHITELIST_X_FORWARDED)) { - String header = exchange.getRequestHeaders().getFirst("X-Forwarded-For"); - if (header == null) { - warnAboutXForwardedForSecurityIssue(); - } else { - return header; - } - } - return exchange.getRemoteAddress().getAddress().getHostAddress(); - } - - private void warnAboutXForwardedForSecurityIssue() { - if (!warnedAboutXForwardedSecurityIssue.get()) { - logger.warn("Security Vulnerability due to misconfiguration: X-Forwarded-For header was not present in a request & '" + - WebserverSettings.IP_WHITELIST_X_FORWARDED.getPath() + "' is 'true'!"); - logger.warn("This could mean non-reverse-proxy access is not blocked & someone can use IP Spoofing to bypass security!"); - logger.warn("Make sure you can only access Plan panel from your reverse-proxy or disable this setting."); - } - warnedAboutXForwardedSecurityIssue.set(true); - } - - private Request buildRequest(HttpExchange exchange) { - String requestMethod = exchange.getRequestMethod(); - URIPath path = new URIPath(exchange.getRequestURI().getPath()); - URIQuery query = new URIQuery(exchange.getRequestURI().getRawQuery()); - WebUser user = getWebUser(exchange); - Map headers = getRequestHeaders(exchange); - return new Request(requestMethod, path, query, user, headers); - } - - private WebUser getWebUser(HttpExchange exchange) { - return getAuthentication(exchange.getRequestHeaders()) - .map(Authentication::getUser) // Can throw WebUserAuthException - .map(User::toWebUser) - .orElse(null); - } - - private Map getRequestHeaders(HttpExchange exchange) { - Map headers = new HashMap<>(); - for (Map.Entry> e : exchange.getRequestHeaders().entrySet()) { - List value = e.getValue(); - headers.put(e.getKey(), new TextStringBuilder().appendWithSeparators(value, ";").build()); - } - return headers; - } - - private Optional getAuthentication(Headers requestHeaders) { - if (config.isTrue(WebserverSettings.DISABLED_AUTHENTICATION)) { - return Optional.empty(); - } - - List cookies = requestHeaders.get("Cookie"); - if (cookies != null && !cookies.isEmpty()) { - for (String cookie : new TextStringBuilder().appendWithSeparators(cookies, ";").build().split(";")) { - String[] split = cookie.trim().split("=", 2); - String name = split[0]; - String value = split[1]; - if ("auth".equals(name)) { - return Optional.of(new CookieAuthentication(activeCookieStore, value)); - } - } - } - - List authorization = requestHeaders.get("Authorization"); - if (authorization == null || authorization.isEmpty()) return Optional.empty(); - - String authLine = authorization.get(0); - if (StringUtils.contains(authLine, "Basic ")) { - return Optional.of(new BasicAuthentication(StringUtils.split(authLine, ' ')[1], dbSystem.getDatabase())); - } - return Optional.empty(); - } - - public ResponseResolver getResponseResolver() { - return responseResolver; - } -} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/ResponseFactory.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/ResponseFactory.java index 34160c2b5..057b5a5a2 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/ResponseFactory.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/ResponseFactory.java @@ -38,6 +38,8 @@ import com.djrapitops.plan.storage.database.queries.containers.ContainerFetchQue import com.djrapitops.plan.storage.file.PlanFiles; import com.djrapitops.plan.utilities.java.Maps; import com.djrapitops.plan.utilities.java.UnaryChain; +import dagger.Lazy; +import org.apache.commons.lang3.StringUtils; import javax.inject.Inject; import javax.inject.Singleton; @@ -61,6 +63,7 @@ public class ResponseFactory { private final Locale locale; private final DBSystem dbSystem; private final Theme theme; + private final Lazy addresses; @Inject public ResponseFactory( @@ -68,13 +71,15 @@ public class ResponseFactory { PageFactory pageFactory, Locale locale, DBSystem dbSystem, - Theme theme + Theme theme, + Lazy addresses ) { this.files = files; this.pageFactory = pageFactory; this.locale = locale; this.dbSystem = dbSystem; this.theme = theme; + this.addresses = addresses; } public WebResource getResource(String resourceName) { @@ -167,10 +172,10 @@ public class ResponseFactory { public Response javaScriptResponse(String fileName) { try { String content = UnaryChain.of(getResource(fileName).asString()) + .chain(this::replaceMainAddressPlaceholder) .chain(theme::replaceThemeColors) .chain(resource -> { - if (fileName.startsWith("vendor/") || fileName.startsWith("/vendor/")) - return resource; + if (fileName.startsWith("vendor/") || fileName.startsWith("/vendor/")) {return resource;} return locale.replaceLanguageInJavascript(resource); }) .apply(); @@ -180,10 +185,16 @@ public class ResponseFactory { .setStatus(200) .build(); } catch (UncheckedIOException e) { - return notFound404("JS File not found from jar: " + fileName + ", " + e.toString()); + return notFound404("JS File not found from jar: " + fileName + ", " + e); } } + private String replaceMainAddressPlaceholder(String resource) { + String address = addresses.get().getAccessAddress() + .orElseGet(addresses.get()::getFallbackLocalhostAddress); + return StringUtils.replace(resource, "PLAN_BASE_ADDRESS", address); + } + public Response cssResponse(String fileName) { try { String content = theme.replaceThemeColors(getResource(fileName).asString()); @@ -193,7 +204,7 @@ public class ResponseFactory { .setStatus(200) .build(); } catch (UncheckedIOException e) { - return notFound404("CSS File not found from jar: " + fileName + ", " + e.toString()); + return notFound404("CSS File not found from jar: " + fileName + ", " + e); } } @@ -205,7 +216,7 @@ public class ResponseFactory { .setStatus(200) .build(); } catch (UncheckedIOException e) { - return notFound404("Image File not found from jar: " + fileName + ", " + e.toString()); + return notFound404("Image File not found from jar: " + fileName + ", " + e); } } @@ -228,7 +239,7 @@ public class ResponseFactory { .setContent(getResource(fileName)) .build(); } catch (UncheckedIOException e) { - return notFound404("Font File not found from jar: " + fileName + ", " + e.toString()); + return notFound404("Font File not found from jar: " + fileName + ", " + e); } } @@ -432,4 +443,26 @@ public class ResponseFactory { return forInternalError(e, "Failed to generate errors page"); } } + + public Response jsonFileResponse(String file) { + try { + return Response.builder() + .setMimeType(MimeType.JSON) + .setContent(getResource(file)) + .build(); + } catch (UncheckedIOException e) { + return forInternalError(e, "Could not read " + file); + } + } + + public Response reactPageResponse() { + try { + return Response.builder() + .setMimeType(MimeType.HTML) + .setContent(getResource("index.html")) + .build(); + } catch (UncheckedIOException e) { + return forInternalError(e, "Could not read index.html"); + } + } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/ResponseResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/ResponseResolver.java index 38c8effa9..81e18b438 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/ResponseResolver.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/ResponseResolver.java @@ -26,19 +26,27 @@ import com.djrapitops.plan.delivery.web.resolver.exception.NotFoundException; import com.djrapitops.plan.delivery.web.resolver.request.Request; import com.djrapitops.plan.delivery.web.resolver.request.WebUser; import com.djrapitops.plan.delivery.webserver.auth.FailReason; +import com.djrapitops.plan.delivery.webserver.http.WebServer; import com.djrapitops.plan.delivery.webserver.resolver.*; import com.djrapitops.plan.delivery.webserver.resolver.auth.*; import com.djrapitops.plan.delivery.webserver.resolver.json.RootJSONResolver; +import com.djrapitops.plan.delivery.webserver.resolver.swagger.SwaggerJsonResolver; +import com.djrapitops.plan.delivery.webserver.resolver.swagger.SwaggerPageResolver; import com.djrapitops.plan.exceptions.WebUserAuthException; import com.djrapitops.plan.exceptions.connection.ForbiddenException; import com.djrapitops.plan.utilities.logging.ErrorContext; import com.djrapitops.plan.utilities.logging.ErrorLogger; import dagger.Lazy; +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.info.Contact; +import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.annotations.info.License; import javax.inject.Inject; import javax.inject.Singleton; import java.util.List; import java.util.Optional; +import java.util.function.Supplier; import java.util.regex.Pattern; /** @@ -50,6 +58,12 @@ import java.util.regex.Pattern; * @author AuroraLS3 */ @Singleton +@OpenAPIDefinition(info = @Info( + title = "Plan API endpoints", + description = "If authentication is enabled (see response of /v1/whoami) logging in is required for endpoints (/auth/login). Pass 'Cookie' header in the requests after login.", + contact = @Contact(name = "Github Discussions", url = "https://github.com/plan-player-analytics/Plan/discussions/categories/apis-and-development"), + license = @License(name = "GNU Lesser General Public License v3.0 (LGPLv3.0)", url = "https://github.com/plan-player-analytics/Plan/blob/master/LICENSE") +)) public class ResponseResolver { private final QueryPageResolver queryPageResolver; @@ -65,6 +79,8 @@ public class ResponseResolver { private final LogoutResolver logoutResolver; private final RegisterResolver registerResolver; private final ErrorsPageResolver errorsPageResolver; + private final SwaggerJsonResolver swaggerJsonResolver; + private final SwaggerPageResolver swaggerPageResolver; private final ErrorLogger errorLogger; private final ResolverService resolverService; @@ -92,6 +108,9 @@ public class ResponseResolver { RegisterResolver registerResolver, ErrorsPageResolver errorsPageResolver, + SwaggerJsonResolver swaggerJsonResolver, + SwaggerPageResolver swaggerPageResolver, + ErrorLogger errorLogger ) { this.resolverService = resolverService; @@ -110,16 +129,21 @@ public class ResponseResolver { this.logoutResolver = logoutResolver; this.registerResolver = registerResolver; this.errorsPageResolver = errorsPageResolver; + this.swaggerJsonResolver = swaggerJsonResolver; + this.swaggerPageResolver = swaggerPageResolver; this.errorLogger = errorLogger; } public void registerPages() { String plugin = "Plan"; - resolverService.registerResolver(plugin, "/robots.txt", (NoAuthResolver) request -> Optional.of(responseFactory.robotsResponse())); + resolverService.registerResolver(plugin, "/robots.txt", fileResolver(responseFactory::robotsResponse)); + resolverService.registerResolver(plugin, "/manifest.json", fileResolver(() -> responseFactory.jsonFileResponse("manifest.json"))); + resolverService.registerResolver(plugin, "/asset-manifest.json", fileResolver(() -> responseFactory.jsonFileResponse("asset-manifest.json"))); + resolverService.registerResolver(plugin, "/favicon.ico", fileResolver(responseFactory::faviconResponse)); + resolverService.registerResolver(plugin, "/query", queryPageResolver); resolverService.registerResolver(plugin, "/players", playersPageResolver); resolverService.registerResolver(plugin, "/player", playerPageResolver); - resolverService.registerResolver(plugin, "/favicon.ico", (NoAuthResolver) request -> Optional.of(responseFactory.faviconResponse())); resolverService.registerResolver(plugin, "/network", serverPageResolver); resolverService.registerResolver(plugin, "/server", serverPageResolver); @@ -132,9 +156,15 @@ public class ResponseResolver { resolverService.registerResolver(plugin, "/errors", errorsPageResolver); resolverService.registerResolverForMatches(plugin, Pattern.compile("^/$"), rootPageResolver); - resolverService.registerResolverForMatches(plugin, Pattern.compile("^.*/(vendor|css|js|img)/.*"), staticResourceResolver); + resolverService.registerResolverForMatches(plugin, Pattern.compile(StaticResourceResolver.PATH_REGEX), staticResourceResolver); resolverService.registerResolver(plugin, "/v1", rootJSONResolver.getResolver()); + resolverService.registerResolver(plugin, "/docs/swagger.json", swaggerJsonResolver); + resolverService.registerResolver(plugin, "/docs", swaggerPageResolver); + } + + private NoAuthResolver fileResolver(Supplier response) { + return request -> Optional.of(response.get()); } public Response getResponse(Request request) { @@ -173,7 +203,7 @@ public class ResponseResolver { for (Resolver resolver : foundResolvers) { boolean isAuthRequired = webServer.get().isAuthRequired() && resolver.requiresAuth(request); if (isAuthRequired) { - if (!user.isPresent()) { + if (user.isEmpty()) { if (webServer.get().isUsingHTTPS()) { throw new WebUserAuthException(FailReason.NO_USER_PRESENT); } else { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/ResponseSender.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/ResponseSender.java deleted file mode 100644 index ce624585f..000000000 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/ResponseSender.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * This file is part of Player Analytics (Plan). - * - * Plan is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License v3 as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Plan is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Plan. If not, see . - */ -package com.djrapitops.plan.delivery.webserver; - -import com.djrapitops.plan.delivery.web.resolver.Response; -import com.sun.net.httpserver.Headers; -import com.sun.net.httpserver.HttpExchange; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.Map; -import java.util.zip.GZIPOutputStream; - -/** - * Utility for sending a Response to HttpExchange. - * - * @author AuroraLS3 - */ -public class ResponseSender { - - private final Addresses addresses; - private final HttpExchange exchange; - private final Response response; - - public ResponseSender(Addresses addresses, HttpExchange exchange, Response response) { - this.addresses = addresses; - this.exchange = exchange; - this.response = response; - } - - public void send() throws IOException { - setResponseHeaders(); - if ("HEAD".equals(exchange.getRequestMethod()) || response.getCode() == 204) { - sendHeadResponse(); - } else if ("bytes".equalsIgnoreCase(response.getHeaders().get("Accept-Ranges"))) { - sendRawBytes(); - } else { - sendCompressed(); - } - } - - public void sendHeadResponse() throws IOException { - try { - exchange.getResponseHeaders().remove("Content-Length"); - beginSend(); - } finally { - exchange.getRequestBody().close(); - } - } - - private void setResponseHeaders() { - Headers headers = exchange.getResponseHeaders(); - - Map responseHeaders = response.getHeaders(); - correctRedirect(responseHeaders); - - for (Map.Entry header : responseHeaders.entrySet()) { - headers.set(header.getKey(), header.getValue()); - } - } - - private void correctRedirect(Map responseHeaders) { - String redirect = responseHeaders.get("Location"); - if (redirect != null) { - if (redirect.startsWith("http") || !redirect.startsWith("/")) return; - addresses.getAccessAddress().ifPresent(address -> responseHeaders.put("Location", address + redirect)); - } - } - - private void sendCompressed() throws IOException { - exchange.getResponseHeaders().set("Content-Encoding", "gzip"); - beginSend(); - try (OutputStream out = new GZIPOutputStream(exchange.getResponseBody())) { - send(out); - } - } - - private void beginSend() throws IOException { - String length = response.getHeaders().get("Content-Length"); - if (length == null || "0".equals(length)) { - exchange.getResponseHeaders().remove("Content-Length"); - } - // Return a content length of -1 for HTTP code 204 (No content) - // and HEAD requests to avoid warning messages. - exchange.sendResponseHeaders(response.getCode(), (response.getCode() == 204 || "HEAD".equals(exchange.getRequestMethod()) || length == null) ? -1 : Long.parseLong(length)); - } - - private void sendRawBytes() throws IOException { - beginSend(); - try (OutputStream out = exchange.getResponseBody()) { - send(out); - } - } - - private void send(OutputStream out) throws IOException { - try ( - ByteArrayInputStream bis = new ByteArrayInputStream(response.getBytes()) - ) { - byte[] buffer = new byte[2048]; - int count; - while ((count = bis.read(buffer)) != -1) { - out.write(buffer, 0, count); - } - } - } -} \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/WebServer.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/WebServer.java deleted file mode 100644 index d872d2c88..000000000 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/WebServer.java +++ /dev/null @@ -1,309 +0,0 @@ -/* - * This file is part of Player Analytics (Plan). - * - * Plan is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License v3 as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Plan is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Plan. If not, see . - */ -package com.djrapitops.plan.delivery.webserver; - -import com.djrapitops.plan.SubSystem; -import com.djrapitops.plan.settings.config.PlanConfig; -import com.djrapitops.plan.settings.config.paths.PluginSettings; -import com.djrapitops.plan.settings.config.paths.WebserverSettings; -import com.djrapitops.plan.settings.locale.Locale; -import com.djrapitops.plan.settings.locale.lang.PluginLang; -import com.djrapitops.plan.storage.file.PlanFiles; -import com.djrapitops.plan.utilities.logging.ErrorContext; -import com.djrapitops.plan.utilities.logging.ErrorLogger; -import com.sun.net.httpserver.HttpServer; -import com.sun.net.httpserver.HttpsConfigurator; -import com.sun.net.httpserver.HttpsParameters; -import com.sun.net.httpserver.HttpsServer; -import net.playeranalytics.plugin.server.PluginLogger; -import org.apache.commons.lang3.concurrent.BasicThreadFactory; - -import javax.inject.Inject; -import javax.inject.Singleton; -import javax.net.ssl.*; -import java.io.*; -import java.net.BindException; -import java.net.InetSocketAddress; -import java.nio.file.InvalidPathException; -import java.nio.file.Paths; -import java.security.*; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.util.concurrent.*; - -/** - * @author AuroraLS3 - */ -@Singleton -public class WebServer implements SubSystem { - - private final Locale locale; - private final PlanFiles files; - private final PlanConfig config; - - private final Addresses addresses; - private final RequestHandler requestHandler; - - private final PluginLogger logger; - private final ErrorLogger errorLogger; - - private int port; - private boolean enabled = false; - private HttpServer server; - - private boolean usingHttps = false; - - @Inject - public WebServer( - Locale locale, - PlanFiles files, - PlanConfig config, - Addresses addresses, - PluginLogger logger, - ErrorLogger errorLogger, - RequestHandler requestHandler - ) { - this.locale = locale; - this.files = files; - this.config = config; - this.addresses = addresses; - - this.requestHandler = requestHandler; - - this.logger = logger; - this.errorLogger = errorLogger; - } - - @Override - public void enable() { - this.port = config.get(WebserverSettings.PORT); - - initServer(); - - if (!addresses.getAccessAddress().isPresent()) { - logger.warn(locale.getString(PluginLang.ENABLE_NOTIFY_BAD_IP)); - } - - if (!isEnabled()) { - if (config.isTrue(WebserverSettings.DISABLED)) { - logger.warn(locale.getString(PluginLang.ENABLE_NOTIFY_WEB_SERVER_DISABLED)); - } else { - logger.error(locale.getString(PluginLang.WEB_SERVER_FAIL_PORT_BIND, port)); - } - } else if (config.isTrue(WebserverSettings.IP_WHITELIST)) { - logger.info(locale.getString(PluginLang.WEB_SERVER_NOTIFY_IP_WHITELIST)); - } - - requestHandler.getResponseResolver().registerPages(); - } - - /** - * Starts up the WebServer in a new Thread Pool. - */ - private void initServer() { - if (config.isTrue(WebserverSettings.DISABLED)) { - // WebServer has been disabled. - return; - } - - if (enabled) { - // Server is already enabled stop code - return; - } - - try { - usingHttps = startHttpsServer(); - - if (!usingHttps) { - logger.info("ยงe" + locale.getString(PluginLang.WEB_SERVER_NOTIFY_HTTP_USER_AUTH)); - server = HttpServer.create(new InetSocketAddress(config.get(WebserverSettings.INTERNAL_IP), port), 10); - } else if (server == null) { - logger.info("ยงe" + locale.getString(PluginLang.WEB_SERVER_NOTIFY_USING_PROXY_MODE)); - server = HttpServer.create(new InetSocketAddress(config.get(WebserverSettings.INTERNAL_IP), port), 10); - } else if (config.isTrue(WebserverSettings.DISABLED_AUTHENTICATION)) { - logger.info(locale.getString(PluginLang.WEB_SERVER_NOTIFY_HTTPS_USER_AUTH)); - } - server.createContext("/", requestHandler); - - ExecutorService executor = new ThreadPoolExecutor( - 4, 8, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100), - new BasicThreadFactory.Builder() - .namingPattern("Plan WebServer Thread-%d") - .uncaughtExceptionHandler((thread, throwable) -> { - if (config.isTrue(PluginSettings.DEV_MODE)) { - errorLogger.warn(throwable, ErrorContext.builder() - .whatToDo("THIS ERROR IS ONLY LOGGED IN DEV MODE") - .build()); - } - }).build() - ); - server.setExecutor(executor); - server.start(); - - enabled = true; - - String address = addresses.getAccessAddress().orElse(addresses.getFallbackLocalhostAddress()); - logger.info(locale.getString(PluginLang.ENABLED_WEB_SERVER, server.getAddress().getPort(), address)); - - boolean usingAlternativeIP = config.isTrue(WebserverSettings.SHOW_ALTERNATIVE_IP); - if (!usingAlternativeIP && !addresses.getAccessAddress().isPresent()) { - logger.info("ยงe" + locale.getString(PluginLang.ENABLE_NOTIFY_EMPTY_IP)); - } - } catch (BindException failedToBind) { - logger.error("Webserver failed to bind port: " + failedToBind.toString()); - enabled = false; - } catch (IllegalArgumentException | IllegalStateException | IOException e) { - errorLogger.error(e, ErrorContext.builder().related("Trying to enable webserver", config.get(WebserverSettings.INTERNAL_IP) + ":" + port).build()); - enabled = false; - } - } - - private boolean startHttpsServer() throws BindException { - String keyStorePath = config.get(WebserverSettings.CERTIFICATE_PATH); - - if ("proxy".equalsIgnoreCase(keyStorePath)) { - return true; - } - - try { - if (!Paths.get(keyStorePath).isAbsolute()) { - keyStorePath = files.getDataFolder() + File.separator + keyStorePath; - } - } catch (InvalidPathException e) { - logger.error("WebServer: Could not find Keystore: " + e.getMessage()); - errorLogger.error(e, ErrorContext.builder() - .whatToDo(e.getMessage() + ", Fix this path to point to a valid keystore file: " + keyStorePath) - .related(keyStorePath).build()); - } - - char[] storepass = config.get(WebserverSettings.CERTIFICATE_STOREPASS).toCharArray(); - char[] keypass = config.get(WebserverSettings.CERTIFICATE_KEYPASS).toCharArray(); - String alias = config.get(WebserverSettings.CERTIFICATE_ALIAS); - - boolean startSuccessful = false; - String keyStoreKind = keyStorePath.endsWith(".p12") ? "PKCS12" : "JKS"; - try (FileInputStream fIn = new FileInputStream(keyStorePath)) { - KeyStore keystore = KeyStore.getInstance(keyStoreKind); - - keystore.load(fIn, storepass); - Certificate cert = keystore.getCertificate(alias); - - if (cert == null) { - throw new IllegalStateException("Alias: '" + alias + "' was not found in file " + keyStorePath + "."); - } - - logger.info("Certificate: " + cert.getType()); - - KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509"); - keyManagerFactory.init(keystore, keypass); - - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509"); - trustManagerFactory.init(keystore); - - server = HttpsServer.create(new InetSocketAddress(config.get(WebserverSettings.INTERNAL_IP), port), 10); - SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); - sslContext.init(keyManagerFactory.getKeyManagers(), null/*trustManagerFactory.getTrustManagers()*/, null); - - ((HttpsServer) server).setHttpsConfigurator(new HttpsConfigurator(sslContext) { - @Override - public void configure(HttpsParameters params) { - SSLEngine engine = sslContext.createSSLEngine(); - - params.setNeedClientAuth(false); - params.setCipherSuites(engine.getEnabledCipherSuites()); - params.setProtocols(engine.getEnabledProtocols()); - - SSLParameters defaultSSLParameters = sslContext.getDefaultSSLParameters(); - params.setSSLParameters(defaultSSLParameters); - } - }); - startSuccessful = true; - } catch (IllegalStateException e) { - logger.error(e.getMessage()); - } catch (KeyManagementException | NoSuchAlgorithmException e) { - logger.error(locale.getString(PluginLang.WEB_SERVER_FAIL_SSL_CONTEXT)); - errorLogger.error(e, ErrorContext.builder().related(keyStoreKind).build()); - } catch (EOFException e) { - logger.error(locale.getString(PluginLang.WEB_SERVER_FAIL_EMPTY_FILE)); - } catch (FileNotFoundException e) { - logger.info(locale.getString(PluginLang.WEB_SERVER_NOTIFY_NO_CERT_FILE, keyStorePath)); - logger.info(locale.getString(PluginLang.WEB_SERVER_NOTIFY_HTTP)); - } catch (BindException e) { - throw e; // Pass to above error handler - } catch (IOException e) { - errorLogger.error(e, ErrorContext.builder().related(config.get(WebserverSettings.INTERNAL_IP) + ":" + port).build()); - } catch (KeyStoreException | CertificateException | UnrecoverableKeyException e) { - logger.error(locale.getString(PluginLang.WEB_SERVER_FAIL_STORE_LOAD)); - errorLogger.error(e, ErrorContext.builder() - .whatToDo("Make sure the Certificate settings are correct / You can try remaking the keystore without -passin or -passout parameters.") - .related(keyStorePath).build()); - } - return startSuccessful; - } - - /** - * @return if the WebServer is enabled - */ - public boolean isEnabled() { - return enabled; - } - - /** - * Shuts down the server - Async thread is closed with shutdown boolean. - */ - @Override - public void disable() { - if (server != null) { - shutdown(); - logger.info(locale.getString(PluginLang.DISABLED_WEB_SERVER)); - } - enabled = false; - } - - private void shutdown() { - server.stop(0); - Executor executor = server.getExecutor(); - if (executor instanceof ExecutorService) { - ExecutorService service = (ExecutorService) executor; - service.shutdown(); - try { - if (!service.awaitTermination(5, TimeUnit.SECONDS)) { - service.shutdownNow(); - } - } catch (InterruptedException e) { - logger.error("WebServer ExecutorService shutdown thread interrupted on disable: " + e.getMessage()); - Thread.currentThread().interrupt(); - } - } - } - - public String getProtocol() { - return usingHttps ? "https" : "http"; - } - - public boolean isUsingHTTPS() { - return usingHttps; - } - - public boolean isAuthRequired() { - return isUsingHTTPS() && config.isFalse(WebserverSettings.DISABLED_AUTHENTICATION); - } - - public int getPort() { - return port; - } -} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/WebServerSystem.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/WebServerSystem.java index f3d333611..6eacfea85 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/WebServerSystem.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/WebServerSystem.java @@ -19,6 +19,7 @@ package com.djrapitops.plan.delivery.webserver; import com.djrapitops.plan.SubSystem; import com.djrapitops.plan.delivery.web.ResourceService; import com.djrapitops.plan.delivery.webserver.auth.ActiveCookieStore; +import com.djrapitops.plan.delivery.webserver.http.WebServer; import javax.inject.Inject; import javax.inject.Singleton; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/auth/ActiveCookieExpiryCleanupTask.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/auth/ActiveCookieExpiryCleanupTask.java new file mode 100644 index 000000000..86d835353 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/auth/ActiveCookieExpiryCleanupTask.java @@ -0,0 +1,88 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.webserver.auth; + +import com.djrapitops.plan.TaskSystem; +import com.djrapitops.plan.settings.config.PlanConfig; +import com.djrapitops.plan.settings.config.paths.PluginSettings; +import dagger.Lazy; +import net.playeranalytics.plugin.scheduling.RunnableFactory; +import net.playeranalytics.plugin.scheduling.TimeAmount; +import net.playeranalytics.plugin.server.PluginLogger; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +@Singleton +public class ActiveCookieExpiryCleanupTask extends TaskSystem.Task { + + private final PlanConfig config; + private final Lazy activeCookieStore; + private final PluginLogger logger; + + private final Map expiryDates; + + @Inject + public ActiveCookieExpiryCleanupTask(PlanConfig config, Lazy activeCookieStore, PluginLogger logger) { + this.config = config; + this.activeCookieStore = activeCookieStore; + this.logger = logger; + this.expiryDates = new ConcurrentHashMap<>(); + } + + @Override + public void register(RunnableFactory runnableFactory) { + runnableFactory.create(this) + .runTaskTimerAsynchronously( + TimeAmount.toTicks(5, TimeUnit.SECONDS), + TimeAmount.toTicks(1, TimeUnit.SECONDS)); + } + + @Override + public void run() { + long time = System.currentTimeMillis(); + + Set cookiesToRemove = new HashSet<>(); + for (Map.Entry entry : expiryDates.entrySet()) { + Long expiryTime = entry.getValue(); + if (expiryTime <= time) { + String cookie = entry.getKey(); + cookiesToRemove.add(cookie); + } + } + + for (String cookie : cookiesToRemove) { + activeCookieStore.get().removeCookie(cookie); + expiryDates.remove(cookie); + if (config.isTrue(PluginSettings.DEV_MODE)) { + logger.info("Cookie " + cookie + " has expired: " + time); + } + } + } + + public void addExpiry(String cookie, Long time) { + expiryDates.put(cookie, time); + if (config.isTrue(PluginSettings.DEV_MODE)) { + logger.info("Cookie " + cookie + " will expire " + time); + } + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/auth/ActiveCookieStore.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/auth/ActiveCookieStore.java index a0383928d..781c5c42a 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/auth/ActiveCookieStore.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/auth/ActiveCookieStore.java @@ -18,20 +18,21 @@ package com.djrapitops.plan.delivery.webserver.auth; import com.djrapitops.plan.SubSystem; import com.djrapitops.plan.delivery.domain.auth.User; +import com.djrapitops.plan.exceptions.database.DBOpException; import com.djrapitops.plan.processing.Processing; import com.djrapitops.plan.settings.config.PlanConfig; import com.djrapitops.plan.settings.config.paths.WebserverSettings; import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.queries.objects.WebUserQueries; import com.djrapitops.plan.storage.database.transactions.events.CookieChangeTransaction; -import net.playeranalytics.plugin.scheduling.RunnableFactory; -import net.playeranalytics.plugin.scheduling.Task; -import net.playeranalytics.plugin.scheduling.TimeAmount; +import net.playeranalytics.plugin.server.PluginLogger; import org.apache.commons.codec.digest.DigestUtils; import javax.inject.Inject; import javax.inject.Singleton; -import java.util.*; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; @@ -39,35 +40,34 @@ import java.util.concurrent.TimeUnit; public class ActiveCookieStore implements SubSystem { private static final Map USERS_BY_COOKIE = new ConcurrentHashMap<>(); - public static long cookieExpiresAfter = TimeUnit.HOURS.toMillis(2L); - private static ActiveCookieStore activeCookieStore; + public static long cookieExpiresAfterMs = TimeUnit.HOURS.toMillis(2L); + + private final ActiveCookieExpiryCleanupTask activeCookieExpiryCleanupTask; private final PlanConfig config; private final DBSystem dbSystem; - private final RunnableFactory runnableFactory; private final Processing processing; - - private final Collection expiryTasks; + private final PluginLogger logger; @Inject public ActiveCookieStore( + ActiveCookieExpiryCleanupTask activeCookieExpiryCleanupTask, PlanConfig config, DBSystem dbSystem, - RunnableFactory runnableFactory, - Processing processing + Processing processing, + PluginLogger logger ) { - ActiveCookieStore.activeCookieStore = this; + this.logger = logger; + Holder.setActiveCookieStore(this); + this.activeCookieExpiryCleanupTask = activeCookieExpiryCleanupTask; this.config = config; this.dbSystem = dbSystem; this.processing = processing; - this.runnableFactory = runnableFactory; - - expiryTasks = new ArrayList<>(); } private static void removeCookieStatic(String cookie) { - activeCookieStore.removeCookie(cookie); + Holder.getActiveCookieStore().removeCookie(cookie); } public static void removeUserCookie(String username) { @@ -77,33 +77,33 @@ public class ActiveCookieStore implements SubSystem { .ifPresent(ActiveCookieStore::removeCookieStatic); } + private static void setCookiesExpireAfter(Long expireAfterMs) { + cookieExpiresAfterMs = expireAfterMs; + } + @Override public void enable() { - cookieExpiresAfter = config.get(WebserverSettings.COOKIES_EXPIRE_AFTER); + ActiveCookieStore.setCookiesExpireAfter(config.get(WebserverSettings.COOKIES_EXPIRE_AFTER)); processing.submitNonCritical(this::loadActiveCookies); } private void loadActiveCookies() { USERS_BY_COOKIE.clear(); - USERS_BY_COOKIE.putAll(dbSystem.getDatabase().query(WebUserQueries.fetchActiveCookies())); - for (Map.Entry entry : dbSystem.getDatabase().query(WebUserQueries.getCookieExpiryTimes()).entrySet()) { - long timeToExpiry = Math.max(entry.getValue() - System.currentTimeMillis(), 0L); - expiryTasks.add(runnableFactory.create(() -> removeCookie(entry.getKey())) - .runTaskLaterAsynchronously(TimeAmount.toTicks(timeToExpiry, TimeUnit.MILLISECONDS))); + try { + USERS_BY_COOKIE.putAll(dbSystem.getDatabase().query(WebUserQueries.fetchActiveCookies())); + for (Map.Entry entry : dbSystem.getDatabase().query(WebUserQueries.getCookieExpiryTimes()).entrySet()) { + long timeToExpiry = Math.max(entry.getValue() - System.currentTimeMillis(), 0L); + activeCookieExpiryCleanupTask.addExpiry(entry.getKey(), System.currentTimeMillis() + timeToExpiry); + } + } catch (DBOpException databaseClosedUnexpectedly) { + logger.info("Database closed unexpectedly so active cookies could not be loaded."); + // Safe to ignore https://github.com/plan-player-analytics/Plan/issues/2188 } } @Override public void disable() { USERS_BY_COOKIE.clear(); - expiryTasks.forEach(task -> { - try { - task.cancel(); - } catch (Exception e) { - // Ignore, task has already been cancelled - } - }); - expiryTasks.clear(); } public Optional checkCookie(String cookie) { @@ -114,14 +114,13 @@ public class ActiveCookieStore implements SubSystem { String cookie = DigestUtils.sha256Hex(user.getUsername() + UUID.randomUUID() + System.currentTimeMillis()); USERS_BY_COOKIE.put(cookie, user); saveNewCookie(user, cookie, System.currentTimeMillis()); - expiryTasks.add(runnableFactory.create(() -> removeCookie(cookie)) - .runTaskLaterAsynchronously(TimeAmount.toTicks(cookieExpiresAfter, TimeUnit.MILLISECONDS))); + activeCookieExpiryCleanupTask.addExpiry(cookie, System.currentTimeMillis() + cookieExpiresAfterMs); return cookie; } private void saveNewCookie(User user, String cookie, long now) { dbSystem.getDatabase().executeTransaction(CookieChangeTransaction.storeCookie( - user.getUsername(), cookie, now + cookieExpiresAfter + user.getUsername(), cookie, now + cookieExpiresAfterMs )); } @@ -129,16 +128,35 @@ public class ActiveCookieStore implements SubSystem { Optional foundUser = checkCookie(cookie); if (foundUser.isPresent()) { USERS_BY_COOKIE.remove(cookie); - deleteCookie(foundUser.get().getUsername()); + deleteCookieByUser(foundUser.get().getUsername()); + deleteCookie(cookie); } } - private void deleteCookie(String username) { - dbSystem.getDatabase().executeTransaction(CookieChangeTransaction.removeCookie(username)); + private void deleteCookie(String cookie) { + dbSystem.getDatabase().executeTransaction(CookieChangeTransaction.removeCookie(cookie)); + } + + private void deleteCookieByUser(String username) { + dbSystem.getDatabase().executeTransaction(CookieChangeTransaction.removeCookieByUser(username)); } public void removeAll() { disable(); dbSystem.getDatabase().executeTransaction(CookieChangeTransaction.removeAll()); } + + public static class Holder { + private static ActiveCookieStore activeCookieStore; + + private Holder() {} + + public static ActiveCookieStore getActiveCookieStore() { + return activeCookieStore; + } + + public static void setActiveCookieStore(ActiveCookieStore activeCookieStore) { + Holder.activeCookieStore = activeCookieStore; + } + } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/auth/AllowedIpList.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/auth/AllowedIpList.java new file mode 100644 index 000000000..a711f6b74 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/auth/AllowedIpList.java @@ -0,0 +1,54 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.webserver.auth; + +import com.djrapitops.plan.settings.config.PlanConfig; +import com.djrapitops.plan.settings.config.paths.WebserverSettings; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +@Singleton +public class AllowedIpList { + + private final PlanConfig config; + private final AtomicReference> allowList = new AtomicReference<>(null); + + @Inject + public AllowedIpList(PlanConfig config) { + this.config = config; + } + + private synchronized void prepare() { + if (allowList.get() == null) { + allowList.set(config.isTrue(WebserverSettings.IP_WHITELIST) + ? config.get(WebserverSettings.WHITELIST) + : Collections.emptyList()); + } + } + + public boolean isAllowed(String accessAddress) { + prepare(); + + List ips = allowList.get(); + + return ips.isEmpty() || ips.contains(accessAddress); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/auth/AuthenticationExtractor.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/auth/AuthenticationExtractor.java new file mode 100644 index 000000000..68396114a --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/auth/AuthenticationExtractor.java @@ -0,0 +1,48 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.webserver.auth; + +import com.djrapitops.plan.delivery.webserver.http.InternalRequest; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.List; +import java.util.Optional; + +@Singleton +public class AuthenticationExtractor { + + private final ActiveCookieStore activeCookieStore; + + @Inject + public AuthenticationExtractor(ActiveCookieStore activeCookieStore) { + this.activeCookieStore = activeCookieStore; + } + + public Optional extractAuthentication(InternalRequest internalRequest) { + return getCookieAuthentication(internalRequest.getCookies()); + } + + private Optional getCookieAuthentication(List cookies) { + for (Cookie cookie : cookies) { + if ("auth".equals(cookie.getName())) { + return Optional.of(new CookieAuthentication(activeCookieStore, cookie.getValue())); + } + } + return Optional.empty(); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/auth/BasicAuthentication.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/auth/BasicAuthentication.java deleted file mode 100644 index 09890abe4..000000000 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/auth/BasicAuthentication.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * This file is part of Player Analytics (Plan). - * - * Plan is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License v3 as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Plan is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Plan. If not, see . - */ -package com.djrapitops.plan.delivery.webserver.auth; - -import com.djrapitops.plan.delivery.domain.auth.User; -import com.djrapitops.plan.exceptions.PassEncryptException; -import com.djrapitops.plan.exceptions.WebUserAuthException; -import com.djrapitops.plan.exceptions.database.DBOpException; -import com.djrapitops.plan.storage.database.Database; -import com.djrapitops.plan.storage.database.queries.objects.WebUserQueries; -import com.djrapitops.plan.utilities.Base64Util; -import org.apache.commons.lang3.StringUtils; - -import java.util.Arrays; - -/** - * Authentication handling for Basic Auth. - *

- * Basic access authentication (Wikipedia): - * https://en.wikipedia.org/wiki/Basic_access_authentication - * - * @author AuroraLS3 - */ -public class BasicAuthentication implements Authentication { - - private final String authenticationString; - private final Database database; - - public BasicAuthentication(String authenticationString, Database database) { - this.authenticationString = authenticationString; - this.database = database; - } - - @Override - public User getUser() { - String decoded = Base64Util.decode(authenticationString); - - String[] userInfo = StringUtils.split(decoded, ':'); - if (userInfo.length != 2) { - throw new WebUserAuthException(FailReason.USER_AND_PASS_NOT_SPECIFIED, Arrays.toString(userInfo)); - } - - String username = userInfo[0]; - String passwordRaw = userInfo[1]; - - Database.State dbState = database.getState(); - if (dbState != Database.State.OPEN) { - throw new WebUserAuthException(FailReason.DATABASE_NOT_OPEN, "State was: " + dbState.name()); - } - - try { - User user = database.query(WebUserQueries.fetchUser(username)) - .orElseThrow(() -> new WebUserAuthException(FailReason.USER_DOES_NOT_EXIST, username)); - - boolean correctPass = user.doesPasswordMatch(passwordRaw); - if (!correctPass) { - throw new WebUserAuthException(FailReason.USER_PASS_MISMATCH, username); - } - return user; - } catch (DBOpException | PassEncryptException e) { - throw new WebUserAuthException(e); - } - } -} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/auth/Cookie.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/auth/Cookie.java new file mode 100644 index 000000000..ad90b7922 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/auth/Cookie.java @@ -0,0 +1,44 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.webserver.auth; + +import org.apache.commons.lang3.StringUtils; + +public class Cookie { + + private final String name; + private final String value; + + public Cookie(String rawRepresentation) { + String[] split = StringUtils.split(rawRepresentation, "=", 2); + name = split[0]; + value = split[1]; + } + + public Cookie(String name, String value) { + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/auth/FailReason.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/auth/FailReason.java index fc11b9287..d7e6afa13 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/auth/FailReason.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/auth/FailReason.java @@ -25,18 +25,20 @@ import com.djrapitops.plan.settings.locale.lang.Lang; * @see com.djrapitops.plan.exceptions.WebUserAuthException */ public enum FailReason implements Lang { - NO_USER_PRESENT("User cookie not present"), - EXPIRED_COOKIE("User cookie has expired"), - USER_AND_PASS_NOT_SPECIFIED("User and Password not specified"), - USER_DOES_NOT_EXIST("User does not exist"), - USER_INFORMATION_NOT_FOUND("Registration failed, try again (The code expires after 15 minutes)"), - USER_PASS_MISMATCH("User and Password did not match"), - DATABASE_NOT_OPEN("Database is not open, check db status with /plan info"), - ERROR("Authentication failed due to error"); + NO_USER_PRESENT("html.error.auth.noCookie", "User cookie not present"), + EXPIRED_COOKIE("html.error.auth.expiredCookie", "User cookie has expired"), + USER_AND_PASS_NOT_SPECIFIED("html.error.auth.emptyForm", "User and Password not specified"), + USER_DOES_NOT_EXIST("html.error.auth.userNotFound", "User does not exist"), + USER_INFORMATION_NOT_FOUND("html.error.auth.registrationFailed", "Registration failed, try again (The code expires after 15 minutes)"), + USER_PASS_MISMATCH("html.error.auth.loginFailed", "User and Password did not match"), + DATABASE_NOT_OPEN("html.error.auth.dbClosed", "Database is not open, check db status with /plan info"), + ERROR("html.error.auth.generic", "Authentication failed due to error"); + private final String key; private final String reason; - FailReason(String reason) { + FailReason(String key, String reason) { + this.key = key; this.reason = reason; } @@ -49,6 +51,9 @@ public enum FailReason implements Lang { return "HTML - " + name(); } + @Override + public String getKey() {return key;} + @Override public String getDefault() { return getReason(); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/cache/AsyncJSONResolverService.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/cache/AsyncJSONResolverService.java index 5db71df30..5a1623b97 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/cache/AsyncJSONResolverService.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/cache/AsyncJSONResolverService.java @@ -20,14 +20,15 @@ import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.processing.Processing; import com.djrapitops.plan.settings.config.PlanConfig; import com.djrapitops.plan.settings.config.paths.WebserverSettings; -import com.djrapitops.plan.utilities.UnitSemaphoreAccessLock; import javax.inject.Inject; import javax.inject.Singleton; import java.util.Map; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import java.util.concurrent.locks.ReentrantLock; import java.util.function.Function; import java.util.function.Supplier; @@ -44,7 +45,7 @@ public class AsyncJSONResolverService { private final JSONStorage jsonStorage; private final Map> currentlyProcessing; private final Map previousUpdates; - private final UnitSemaphoreAccessLock accessLock; // Access lock prevents double processing same resource + private final ReentrantLock accessLock; // Access lock prevents double processing same resource @Inject public AsyncJSONResolverService( @@ -58,11 +59,11 @@ public class AsyncJSONResolverService { currentlyProcessing = new ConcurrentHashMap<>(); previousUpdates = new ConcurrentHashMap<>(); - accessLock = new UnitSemaphoreAccessLock(); + accessLock = new ReentrantLock(); } public JSONStorage.StoredJSON resolve( - long newerThanTimestamp, DataID dataID, ServerUUID serverUUID, Function creator + Optional newerThanTimestamp, DataID dataID, ServerUUID serverUUID, Function creator ) { String identifier = dataID.of(serverUUID); Supplier jsonCreator = () -> creator.apply(serverUUID); @@ -71,24 +72,29 @@ public class AsyncJSONResolverService { public JSONStorage.StoredJSON resolve( - long newerThanTimestamp, DataID dataID, Supplier jsonCreator + Optional newerThanTimestamp, DataID dataID, Supplier jsonCreator ) { String identifier = dataID.name(); return getStoredOrCreateJSON(newerThanTimestamp, identifier, jsonCreator); } private JSONStorage.StoredJSON getStoredOrCreateJSON( - long timestamp, String identifier, Supplier jsonCreator + Optional givenTimestamp, String identifier, Supplier jsonCreator ) { - JSONStorage.StoredJSON storedJSON = getNewFromCache(timestamp, identifier); - if (storedJSON != null) return storedJSON; + JSONStorage.StoredJSON storedJSON = null; + Future updatedJSON = null; + if (givenTimestamp.isPresent()) { + long timestamp = givenTimestamp.get(); + storedJSON = getNewFromCache(timestamp, identifier); + if (storedJSON != null) return storedJSON; - // No new enough version, let's refresh and send old version of the file - Future updatedJSON = scheduleJSONForUpdate(timestamp, identifier, jsonCreator); + // No new enough version, let's refresh and send old version of the file + updatedJSON = scheduleJSONForUpdate(timestamp, identifier, jsonCreator); + storedJSON = getOldFromCache(timestamp, identifier).orElse(null); + } - storedJSON = getOldFromCache(timestamp, identifier); if (storedJSON != null) { - return storedJSON; + return storedJSON; // Found old from cache } else { // Update not performed if the last update was recent and the file is deleted before next update // Fall back to waiting for the updated file if old version of the file doesn't exist. @@ -111,8 +117,8 @@ public class AsyncJSONResolverService { } } - private JSONStorage.StoredJSON getOldFromCache(long newerThanTimestamp, String identifier) { - return jsonStorage.fetchJsonMadeBefore(identifier, newerThanTimestamp).orElse(null); + private Optional getOldFromCache(long newerThanTimestamp, String identifier) { + return jsonStorage.fetchJsonMadeBefore(identifier, newerThanTimestamp); } private JSONStorage.StoredJSON getNewFromCache(long newerThanTimestamp, String identifier) { @@ -125,7 +131,7 @@ public class AsyncJSONResolverService { long updateThreshold = config.get(WebserverSettings.REDUCED_REFRESH_BARRIER); Future updatedJSON; - accessLock.enter(); + accessLock.lock(); try { // Check if the json is already being created updatedJSON = currentlyProcessing.get(identifier); @@ -135,7 +141,7 @@ public class AsyncJSONResolverService { currentlyProcessing.put(identifier, updatedJSON); } } finally { - accessLock.exit(); + accessLock.unlock(); } return updatedJSON; } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/cache/DataID.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/cache/DataID.java index 90642364f..db1658f38 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/cache/DataID.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/cache/DataID.java @@ -49,8 +49,10 @@ public enum DataID { PLAYERBASE_OVERVIEW, PERFORMANCE_OVERVIEW, EXTENSION_NAV, - EXTENSION_TABS - ; + EXTENSION_TABS, + EXTENSION_JSON, + LIST_SERVERS, + JOIN_ADDRESSES_BY_DAY; public String of(ServerUUID serverUUID) { return name() + '-' + serverUUID; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/configuration/WebserverConfiguration.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/configuration/WebserverConfiguration.java new file mode 100644 index 000000000..78591c44a --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/configuration/WebserverConfiguration.java @@ -0,0 +1,122 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.webserver.configuration; + +import com.djrapitops.plan.delivery.webserver.auth.AllowedIpList; +import com.djrapitops.plan.delivery.webserver.http.AccessAddressPolicy; +import com.djrapitops.plan.settings.config.PlanConfig; +import com.djrapitops.plan.settings.config.paths.WebserverSettings; +import com.djrapitops.plan.storage.file.PlanFiles; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.io.File; +import java.nio.file.InvalidPathException; +import java.nio.file.Paths; + +@Singleton +public class WebserverConfiguration { + + private final PlanFiles files; + private final PlanConfig config; + private final AllowedIpList allowedIpList; + private final WebserverLogMessages webserverLogMessages; + + @Inject + public WebserverConfiguration(PlanFiles files, PlanConfig config, AllowedIpList allowedIpList, WebserverLogMessages webserverLogMessages) { + this.files = files; + this.config = config; + this.allowedIpList = allowedIpList; + this.webserverLogMessages = webserverLogMessages; + } + + public WebserverLogMessages getWebserverLogMessages() { + return webserverLogMessages; + } + + public boolean logAccessToConsole() { + return config.isTrue(WebserverSettings.LOG_ACCESS_TO_CONSOLE); + } + + public boolean isAuthenticationDisabled() { + return config.isTrue(WebserverSettings.DISABLED_AUTHENTICATION); + } + + public boolean isAuthenticationEnabled() { + return config.isFalse(WebserverSettings.DISABLED_AUTHENTICATION); + } + + public AccessAddressPolicy getAccessAddressPolicy() { + return config.isTrue(WebserverSettings.IP_USE_X_FORWARDED_FOR) + ? AccessAddressPolicy.X_FORWARDED_FOR_HEADER + : AccessAddressPolicy.SOCKET_IP; + } + + public AllowedIpList getAllowedIpList() { + return allowedIpList; + } + + public String getAllowedCorsOrigin() { + return config.get(WebserverSettings.CORS_ALLOW_ORIGIN); + } + + public int getPort() { + return config.get(WebserverSettings.PORT); + } + + public String getInternalIP() { + return config.get(WebserverSettings.INTERNAL_IP); + } + + public boolean isWebserverDisabled() { + return config.isTrue(WebserverSettings.DISABLED); + } + + public String getKeyStorePath() { + String keyStorePath = config.get(WebserverSettings.CERTIFICATE_PATH); + + if ("proxy".equalsIgnoreCase(keyStorePath)) { + return keyStorePath; + } + + try { + if (!Paths.get(keyStorePath).isAbsolute()) { + keyStorePath = files.getDataFolder() + File.separator + keyStorePath; + } + } catch (InvalidPathException e) { + webserverLogMessages.keystoreNotFoundError(e, keyStorePath); + } + + return keyStorePath; + } + + public String getKeyStorePassword() { + return config.get(WebserverSettings.CERTIFICATE_STOREPASS); + } + + public String getKeyManagerPassword() { + return config.get(WebserverSettings.CERTIFICATE_KEYPASS); + } + + public String getAlias() { + return config.get(WebserverSettings.CERTIFICATE_ALIAS); + } + + public boolean isProxyModeHttps() { + return "proxy".equals(config.get(WebserverSettings.CERTIFICATE_PATH)); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/configuration/WebserverLogMessages.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/configuration/WebserverLogMessages.java new file mode 100644 index 000000000..f4ddec2f6 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/configuration/WebserverLogMessages.java @@ -0,0 +1,135 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.webserver.configuration; + +import com.djrapitops.plan.delivery.formatting.Formatters; +import com.djrapitops.plan.delivery.webserver.Addresses; +import com.djrapitops.plan.settings.config.paths.WebserverSettings; +import com.djrapitops.plan.settings.locale.Locale; +import com.djrapitops.plan.settings.locale.lang.PluginLang; +import com.djrapitops.plan.utilities.logging.ErrorContext; +import com.djrapitops.plan.utilities.logging.ErrorLogger; +import net.playeranalytics.plugin.server.PluginLogger; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.nio.file.InvalidPathException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +@Singleton +public class WebserverLogMessages { + + private final Formatters formatters; + private final PluginLogger logger; + private final ErrorLogger errorLogger; + private final Locale locale; + private final Addresses addresses; + + private final AtomicLong warnedAboutXForwardedSecurityIssue = new AtomicLong(0L); + + @Inject + public WebserverLogMessages(Formatters formatters, PluginLogger logger, ErrorLogger errorLogger, Locale locale, Addresses addresses) { + this.formatters = formatters; + this.logger = logger; + this.errorLogger = errorLogger; + this.locale = locale; + this.addresses = addresses; + } + + public void warnAboutXForwardedForSecurityIssue() { + if (System.currentTimeMillis() - warnedAboutXForwardedSecurityIssue.get() > TimeUnit.MINUTES.toMillis(2L)) { + logger.warn("Security Vulnerability due to misconfiguration: X-Forwarded-For header was not present in a request & '" + + WebserverSettings.IP_USE_X_FORWARDED_FOR.getPath() + "' is 'true'!"); + logger.warn("This could mean non-reverse-proxy access is not blocked & someone can use IP Spoofing to bypass security!"); + logger.warn("Make sure you can only access Plan panel from your reverse-proxy or disable this setting."); + warnedAboutXForwardedSecurityIssue.set(System.currentTimeMillis()); + } + } + + public void warnAboutWhitelistBlock(String accessAddress, String requestedURIString) { + logger.info(locale.getString(PluginLang.WEB_SERVER_NOTIFY_IP_WHITELIST_BLOCK, accessAddress, requestedURIString)); + } + + public void infoWebserverEnabled(int port) { + String address = addresses.getAccessAddress().orElse(addresses.getFallbackLocalhostAddress()); + logger.info(locale.getString(PluginLang.ENABLED_WEB_SERVER, port, address)); + } + + public void warnWebserverDisabledByConfig() { + logger.warn(locale.getString(PluginLang.ENABLE_NOTIFY_WEB_SERVER_DISABLED)); + } + + public void keystoreNotFoundError(InvalidPathException error, String keyStorePath) { + String errorMessage = error.getMessage(); + logger.error("WebServer: Could not find Keystore: " + errorMessage); + errorLogger.error(error, ErrorContext.builder() + .whatToDo(errorMessage + ", Fix this path to point to a valid keystore file: " + keyStorePath) + .related(keyStorePath).build()); + } + + public void authenticationNotPossible() { + logger.info(locale.getString(PluginLang.WEB_SERVER_NOTIFY_HTTP)); + logger.info(locale.getString(PluginLang.WEB_SERVER_NOTIFY_HTTP_USER_AUTH)); + } + + public void authenticationUsingProxy() { + logger.info(locale.getString(PluginLang.WEB_SERVER_NOTIFY_USING_PROXY_MODE)); + } + + public void invalidCertificate() { + logger.warn(locale.getString(PluginLang.WEB_SERVER_FAIL_STORE_LOAD)); + } + + public void keystoreFileNotFound(String keyStorePath) { + logger.info(locale.getString(PluginLang.WEB_SERVER_NOTIFY_NO_CERT_FILE, keyStorePath)); + } + + public void certificateExpiryIn(long expires) { + logger.info(locale.getString(PluginLang.WEB_SERVER_NOTIFY_CERT_EXPIRE_DATE, formatters.yearLong().apply(expires))); + } + + public void certificateExpiryIsNear(long timeMillisToExpiry) { + if (timeMillisToExpiry > 0) { + logger.warn(locale.getString(PluginLang.WEB_SERVER_NOTIFY_CERT_EXPIRE_DATE_SOON, formatters.timeAmount().apply(timeMillisToExpiry))); + } else { + logger.warn(locale.getString(PluginLang.WEB_SERVER_NOTIFY_CERT_EXPIRE_DATE_PASSED)); + } + } + + public void invalidCertificateMissingAlias(String alias, String keystorePath) { + logger.error(locale.getString(PluginLang.WEB_SERVER_NOTIFY_CERT_NO_SUCH_ALIAS, alias, keystorePath)); + } + + public void unableToLoadKeystore(Exception e, String keystorePath) { + logger.error(locale.getString(PluginLang.WEB_SERVER_FAIL_STORE_LOAD)); + errorLogger.error(e, ErrorContext.builder() + .whatToDo("Make sure the Certificate settings are correct / You can try remaking the keystore without -passin or -passout parameters.") + .related(keystorePath).build()); + } + + public void wrongCertFileFormat() { + logger.error(locale.getString(PluginLang.WEB_SERVER_FAIL_EMPTY_FILE)); + } + + public void keystoreLoadingError(Exception e) { + errorLogger.error(e, ErrorContext.builder() + .logErrorMessage() + .whatToDo("Make sure the Certificate settings are correct / You can try remaking the keystore without -passin or -passout parameters.") + .build()); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/AccessAddressPolicy.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/AccessAddressPolicy.java new file mode 100644 index 000000000..5f4a6cd7e --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/AccessAddressPolicy.java @@ -0,0 +1,24 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.webserver.http; + +public enum AccessAddressPolicy { + + SOCKET_IP, + X_FORWARDED_FOR_HEADER + +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/AccessLogger.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/AccessLogger.java new file mode 100644 index 000000000..5d3558b45 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/AccessLogger.java @@ -0,0 +1,89 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.webserver.http; + +import com.djrapitops.plan.delivery.web.resolver.Response; +import com.djrapitops.plan.delivery.web.resolver.request.Request; +import com.djrapitops.plan.delivery.webserver.configuration.WebserverConfiguration; +import com.djrapitops.plan.exceptions.database.DBOpException; +import com.djrapitops.plan.storage.database.DBSystem; +import com.djrapitops.plan.storage.database.transactions.events.StoreRequestTransaction; +import com.djrapitops.plan.utilities.logging.ErrorContext; +import com.djrapitops.plan.utilities.logging.ErrorLogger; +import net.playeranalytics.plugin.server.PluginLogger; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.concurrent.CompletionException; + +@Singleton +public class AccessLogger { + + private final WebserverConfiguration webserverConfiguration; + private final DBSystem dbSystem; + private final PluginLogger logger; + private final ErrorLogger errorLogger; + + @Inject + public AccessLogger(WebserverConfiguration webserverConfiguration, DBSystem dbSystem, PluginLogger logger, ErrorLogger errorLogger) { + this.webserverConfiguration = webserverConfiguration; + this.dbSystem = dbSystem; + this.logger = logger; + this.errorLogger = errorLogger; + } + + public void log(InternalRequest internalRequest, Request request, Response response) { + if (webserverConfiguration.logAccessToConsole()) { + int code = response.getCode(); + String message = "Access Log: " + internalRequest.getMethod() + " " + + getRequestURI(internalRequest, request) + + " (from " + internalRequest.getAccessAddress(webserverConfiguration) + ") - " + + code; + + int codeFamily = code - (code % 100); // 5XX, 4XX etc + switch (codeFamily) { + case 500: + logger.error(message); + break; + case 400: + logger.warn(message); + break; + case 300: + case 200: + case 100: + default: + logger.info(message); + break; + } + } + try { + dbSystem.getDatabase().executeTransaction( + new StoreRequestTransaction(webserverConfiguration, internalRequest, request, response) + ); + } catch (CompletionException | DBOpException e) { + errorLogger.warn(e, ErrorContext.builder() + .related("Logging request failed") + .related(getRequestURI(internalRequest, request)) + .build()); + } + } + + private String getRequestURI(InternalRequest internalRequest, Request request) { + return request != null ? request.getPath().asString() + request.getQuery().asString() + : internalRequest.getRequestedURIString(); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/InternalRequest.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/InternalRequest.java new file mode 100644 index 000000000..4b96f1719 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/InternalRequest.java @@ -0,0 +1,78 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.webserver.http; + +import com.djrapitops.plan.delivery.domain.auth.User; +import com.djrapitops.plan.delivery.web.resolver.request.Request; +import com.djrapitops.plan.delivery.web.resolver.request.WebUser; +import com.djrapitops.plan.delivery.webserver.auth.Authentication; +import com.djrapitops.plan.delivery.webserver.auth.AuthenticationExtractor; +import com.djrapitops.plan.delivery.webserver.auth.Cookie; +import com.djrapitops.plan.delivery.webserver.configuration.WebserverConfiguration; + +import java.util.List; +import java.util.Optional; + +/** + * Represents a HTTP request. + * + * @see com.djrapitops.plan.delivery.web.resolver.request.Request for API based request, as this interface is for internal use. + */ +public interface InternalRequest { + + long getTimestamp(); + + default String getAccessAddress(WebserverConfiguration webserverConfiguration) { + AccessAddressPolicy accessAddressPolicy = webserverConfiguration.getAccessAddressPolicy(); + if (accessAddressPolicy == AccessAddressPolicy.X_FORWARDED_FOR_HEADER) { + String fromHeader = getAccessAddressFromHeader(); + if (fromHeader == null) { + webserverConfiguration.getWebserverLogMessages().warnAboutXForwardedForSecurityIssue(); + return getAccessAddressFromSocketIp(); + } else { + return fromHeader; + } + } + return getAccessAddressFromSocketIp(); + } + + Request toRequest(); + + List getCookies(); + + String getMethod(); + + String getAccessAddressFromSocketIp(); + + String getAccessAddressFromHeader(); + + String getRequestedURIString(); + + default WebUser getWebUser(WebserverConfiguration webserverConfiguration, AuthenticationExtractor authenticationExtractor) { + return getAuthentication(webserverConfiguration, authenticationExtractor) + .map(Authentication::getUser) // Can throw WebUserAuthException + .map(User::toWebUser) + .orElse(null); + } + + default Optional getAuthentication(WebserverConfiguration webserverConfiguration, AuthenticationExtractor authenticationExtractor) { + if (webserverConfiguration.isAuthenticationDisabled()) { + return Optional.empty(); + } + return authenticationExtractor.extractAuthentication(this); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/JettyInternalRequest.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/JettyInternalRequest.java new file mode 100644 index 000000000..10ddef05a --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/JettyInternalRequest.java @@ -0,0 +1,139 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.webserver.http; + +import com.djrapitops.plan.delivery.web.resolver.request.URIPath; +import com.djrapitops.plan.delivery.web.resolver.request.URIQuery; +import com.djrapitops.plan.delivery.web.resolver.request.WebUser; +import com.djrapitops.plan.delivery.webserver.auth.AuthenticationExtractor; +import com.djrapitops.plan.delivery.webserver.auth.Cookie; +import com.djrapitops.plan.delivery.webserver.configuration.WebserverConfiguration; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.text.TextStringBuilder; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.server.Request; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Spliterators; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +public class JettyInternalRequest implements InternalRequest { + + private final Request baseRequest; + private final HttpServletRequest request; + private final WebserverConfiguration webserverConfiguration; + private final AuthenticationExtractor authenticationExtractor; + + public JettyInternalRequest(Request baseRequest, HttpServletRequest request, WebserverConfiguration webserverConfiguration, AuthenticationExtractor authenticationExtractor) { + this.baseRequest = baseRequest; + this.request = request; + this.webserverConfiguration = webserverConfiguration; + this.authenticationExtractor = authenticationExtractor; + } + + @Override + public long getTimestamp() {return baseRequest.getTimeStamp();} + + @Override + public String getMethod() {return baseRequest.getMethod();} + + @Override + public String getAccessAddressFromSocketIp() { + return baseRequest.getRemoteAddr(); + } + + @Override + public String getAccessAddressFromHeader() { + return baseRequest.getHeader(HttpHeader.X_FORWARDED_FOR.asString()); + } + + @Override + public com.djrapitops.plan.delivery.web.resolver.request.Request toRequest() { + String requestMethod = baseRequest.getMethod(); + URIPath path = new URIPath(baseRequest.getHttpURI().getDecodedPath()); + URIQuery query = new URIQuery(baseRequest.getHttpURI().getQuery()); + byte[] requestBody = readRequestBody(); + WebUser user = getWebUser(webserverConfiguration, authenticationExtractor); + Map headers = getRequestHeaders(); + return new com.djrapitops.plan.delivery.web.resolver.request.Request(requestMethod, path, query, user, headers, requestBody); + } + + private Map getRequestHeaders() { + return streamHeaderNames() + .collect(Collectors.toMap(Function.identity(), baseRequest::getHeader, + (one, two) -> one + ';' + two)); + } + + private Stream streamHeaderNames() { + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(baseRequest.getHeaderNames().asIterator(), 0), false); + } + + private byte[] readRequestBody() { + try (BufferedReader reader = request.getReader(); + ByteArrayOutputStream buf = new ByteArrayOutputStream(512)) { + int b; + while ((b = reader.read()) != -1) { + buf.write((byte) b); + } + return buf.toByteArray(); + } catch (IOException ignored) { + // requestBody stays empty + return new byte[0]; + } + } + + @Override + public List getCookies() { + List textCookies = getCookieHeaders(); + List cookies = new ArrayList<>(); + if (!textCookies.isEmpty()) { + String[] separated = new TextStringBuilder().appendWithSeparators(textCookies, ";").build().split(";"); + for (String textCookie : separated) { + cookies.add(new Cookie(textCookie)); + } + } + return cookies; + } + + private List getCookieHeaders() { + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(request.getHeaders(HttpHeader.COOKIE.asString()).asIterator(), 0), false) + .collect(Collectors.toList()); + } + + @Override + public String getRequestedURIString() { + return baseRequest.getRequestURI(); + } + + @Override + public String toString() { + return "JettyInternalRequest{" + + "baseRequest=" + baseRequest + + ", request=" + request + + ", webserverConfiguration=" + webserverConfiguration + + ", authenticationExtractor=" + authenticationExtractor + + '}'; + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/JettyRequestHandler.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/JettyRequestHandler.java new file mode 100644 index 000000000..f1186721c --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/JettyRequestHandler.java @@ -0,0 +1,78 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.webserver.http; + +import com.djrapitops.plan.delivery.web.resolver.Response; +import com.djrapitops.plan.delivery.webserver.Addresses; +import com.djrapitops.plan.delivery.webserver.auth.AuthenticationExtractor; +import com.djrapitops.plan.delivery.webserver.configuration.WebserverConfiguration; +import com.djrapitops.plan.settings.config.PlanConfig; +import com.djrapitops.plan.settings.config.paths.PluginSettings; +import com.djrapitops.plan.utilities.logging.ErrorContext; +import com.djrapitops.plan.utilities.logging.ErrorLogger; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import net.playeranalytics.plugin.server.PluginLogger; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.io.IOException; + +@Singleton +public class JettyRequestHandler extends AbstractHandler { + + private final WebserverConfiguration webserverConfiguration; + private final AuthenticationExtractor authenticationExtractor; + private final Addresses addresses; + private final RequestHandler requestHandler; + private final PlanConfig config; + private final PluginLogger logger; + private final ErrorLogger errorLogger; + + @Inject + public JettyRequestHandler(WebserverConfiguration webserverConfiguration, AuthenticationExtractor authenticationExtractor, Addresses addresses, RequestHandler requestHandler, PlanConfig config, PluginLogger logger, ErrorLogger errorLogger) { + this.webserverConfiguration = webserverConfiguration; + this.authenticationExtractor = authenticationExtractor; + this.addresses = addresses; + this.requestHandler = requestHandler; + this.config = config; + this.logger = logger; + this.errorLogger = errorLogger; + } + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException, ServletException { + try { + InternalRequest internalRequest = new JettyInternalRequest(baseRequest, servletRequest, webserverConfiguration, authenticationExtractor); + Response response = requestHandler.getResponse(internalRequest); + new JettyResponseSender(response, servletRequest, servletResponse, addresses).send(); + baseRequest.setHandled(true); + } catch (Exception e) { + if (config.isTrue(PluginSettings.DEV_MODE)) { + logger.warn("THIS ERROR IS ONLY LOGGED IN DEV MODE:"); + errorLogger.warn(e, ErrorContext.builder() + .whatToDo("THIS ERROR IS ONLY LOGGED IN DEV MODE") + .related(baseRequest.getMethod(), baseRequest.getRemoteAddr(), target, baseRequest.getRequestURI()) + .build()); + } + } + + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/JettyResponseSender.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/JettyResponseSender.java new file mode 100644 index 000000000..bb45ff128 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/JettyResponseSender.java @@ -0,0 +1,150 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.webserver.http; + +import com.djrapitops.plan.delivery.web.resolver.MimeType; +import com.djrapitops.plan.delivery.web.resolver.Response; +import com.djrapitops.plan.delivery.webserver.Addresses; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.StringUtils; +import org.eclipse.jetty.http.HttpHeader; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Map; +import java.util.zip.GZIPOutputStream; + +public class JettyResponseSender { + + private final Response response; + private final HttpServletRequest servletRequest; + private final HttpServletResponse servletResponse; + private final Addresses addresses; + + public JettyResponseSender(Response response, HttpServletRequest servletRequest, HttpServletResponse servletResponse, Addresses addresses) { + this.response = response; + this.servletRequest = servletRequest; + this.servletResponse = servletResponse; + this.addresses = addresses; + } + + public void send() throws IOException { + if ("HEAD".equals(servletRequest.getMethod()) || response.getCode() == 204) { + setResponseHeaders(); + sendHeadResponse(); + } else if (canGzip()) { + sendCompressed(); + } else { + setResponseHeaders(); + sendRawBytes(); + } + } + + private boolean canGzip() { + String method = servletRequest.getMethod(); + String mimeType = response.getHeaders().get(HttpHeader.CONTENT_TYPE.asString()); + return "GET".equals(method) && StringUtils.containsAny(mimeType, MimeType.HTML, MimeType.CSS, MimeType.JS, MimeType.JSON, "text/plain"); + } + + public void sendHeadResponse() throws IOException { + try { + response.getHeaders().remove(HttpHeader.CONTENT_LENGTH.asString()); + beginSend(); + } finally { + servletResponse.getOutputStream().close(); + } + } + + private void setResponseHeaders() { + Map responseHeaders = response.getHeaders(); + correctRedirect(responseHeaders); + + for (Map.Entry header : responseHeaders.entrySet()) { + servletResponse.setHeader(header.getKey(), header.getValue()); + } + } + + private void correctRedirect(Map responseHeaders) { + String redirect = responseHeaders.get(HttpHeader.LOCATION.asString()); + if (redirect != null) { + if (redirect.startsWith("http") || !redirect.startsWith("/")) return; + addresses.getAccessAddress().ifPresent(address -> responseHeaders.put(HttpHeader.LOCATION.asString(), address + redirect)); + } + } + + private void sendCompressed() throws IOException { + response.getHeaders().remove(HttpHeader.ACCEPT_RANGES.asString()); + response.getHeaders().put(HttpHeader.CONTENT_ENCODING.asString(), "gzip"); + + byte[] gzipped = gzip(); + try (OutputStream out = servletResponse.getOutputStream()) { + response.getHeaders().put(HttpHeader.CONTENT_LENGTH.asString(), String.valueOf(gzipped.length)); + setResponseHeaders(); + + servletResponse.setStatus(response.getCode()); + + send(out, gzipped); + } + } + + private byte[] gzip() throws IOException { + try (ByteArrayOutputStream bufferStream = new ByteArrayOutputStream(); + GZIPOutputStream gzipStream = new GZIPOutputStream(bufferStream) + ) { + gzipStream.write(response.getBytes()); + gzipStream.finish(); + gzipStream.flush(); + return bufferStream.toByteArray(); + } + } + + private void beginSend() { + String length = response.getHeaders().get(HttpHeader.CONTENT_LENGTH.asString()); + if (length == null || "0".equals(length) || response.getCode() == 204 || "HEAD".equals(servletRequest.getMethod())) { + servletResponse.setHeader(HttpHeader.CONTENT_LENGTH.asString(), null); + } + // Return a content length of -1 for HTTP code 204 (No content) + // and HEAD requests to avoid warning messages. + servletResponse.setStatus(response.getCode()); + } + + private void sendRawBytes() throws IOException { + beginSend(); + try (OutputStream out = servletResponse.getOutputStream()) { + send(out); + } + } + + private void send(OutputStream out) throws IOException { + send(out, response.getBytes()); + } + + private void send(OutputStream out, byte[] bytes) throws IOException { + try ( + ByteArrayInputStream bis = new ByteArrayInputStream(bytes) + ) { + byte[] buffer = new byte[2048]; + int count; + while ((count = bis.read(buffer)) != -1) { + out.write(buffer, 0, count); + } + } + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/JettyWebserver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/JettyWebserver.java new file mode 100644 index 000000000..ae44673c3 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/JettyWebserver.java @@ -0,0 +1,285 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.webserver.http; + +import com.djrapitops.plan.delivery.webserver.ResponseResolver; +import com.djrapitops.plan.delivery.webserver.configuration.WebserverConfiguration; +import com.djrapitops.plan.delivery.webserver.configuration.WebserverLogMessages; +import com.djrapitops.plan.exceptions.EnableException; +import com.djrapitops.plan.utilities.java.ThreadContextClassLoaderSwap; +import net.playeranalytics.plugin.server.PluginLogger; +import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory; +import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; +import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; +import org.eclipse.jetty.server.*; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +@Singleton +public class JettyWebserver implements WebServer { + + private final PluginLogger logger; + private final WebserverConfiguration webserverConfiguration; + private final LegacyJettySSLContextLoader legacyJettySSLContextLoader; + private final JettyRequestHandler jettyRequestHandler; + private final ResponseResolver responseResolver; + private final WebserverLogMessages webserverLogMessages; + + private int port; + private boolean usingHttps; + private Server webserver; + + @Inject + public JettyWebserver(PluginLogger logger, WebserverConfiguration webserverConfiguration, LegacyJettySSLContextLoader legacyJettySSLContextLoader, JettyRequestHandler jettyRequestHandler, ResponseResolver responseResolver) { + this.logger = logger; + this.webserverConfiguration = webserverConfiguration; + webserverLogMessages = webserverConfiguration.getWebserverLogMessages(); + this.legacyJettySSLContextLoader = legacyJettySSLContextLoader; + this.jettyRequestHandler = jettyRequestHandler; + this.responseResolver = responseResolver; + } + + @Override + public void enable() { + if (isEnabled()) return; + + if (webserverConfiguration.isWebserverDisabled()) { + webserverLogMessages.warnWebserverDisabledByConfig(); + return; + } + + webserver = new Server(); + webserver.setStopAtShutdown(true); + + this.port = webserverConfiguration.getPort(); + + HttpConfiguration configuration = new HttpConfiguration(); + Optional sslContext = getSslContextFactory(); + sslContext.ifPresent(ssl -> { + configuration.setSecureScheme("https"); + configuration.setSecurePort(port); + + SecureRequestCustomizer serverNameIdentifierCheckSkipper = new SecureRequestCustomizer(); + serverNameIdentifierCheckSkipper.setSniHostCheck(false); + serverNameIdentifierCheckSkipper.setSniRequired(false); + configuration.addCustomizer(serverNameIdentifierCheckSkipper); + + usingHttps = true; + }); + + HttpConnectionFactory httpConnector = new HttpConnectionFactory(configuration); + + HTTP2CServerConnectionFactory http2CConnector = new HTTP2CServerConnectionFactory(configuration); + http2CConnector.setConnectProtocolEnabled(true); + + + ServerConnector connector = sslContext + .map(sslContextFactory -> { + HTTP2ServerConnectionFactory http2Connector = new HTTP2ServerConnectionFactory(configuration); + http2Connector.setConnectProtocolEnabled(true); + ALPNServerConnectionFactory alpn = getAlpnServerConnectionFactory(httpConnector.getProtocol()); + + return new ServerConnector(webserver, sslContextFactory, alpn, httpConnector, http2Connector, http2CConnector); + }) + .orElseGet(() -> { + if (webserverConfiguration.isProxyModeHttps()) { + webserverLogMessages.authenticationUsingProxy(); + } else { + webserverLogMessages.authenticationNotPossible(); + } + return new ServerConnector(webserver, httpConnector, http2CConnector); + }); + + connector.setPort(port); + String internalIP = webserverConfiguration.getInternalIP(); + connector.setHost(internalIP); + webserver.addConnector(connector); + + webserver.setHandler(jettyRequestHandler); + + String startFailure = "Failed to start Jetty webserver: "; + try { + webserver.start(); + } catch (IOException e) { + if (e.getMessage().contains("Failed to bind")) { + boolean defaultInternalIp = "0.0.0.0".equals(internalIP); + String causeHelp = defaultInternalIp ? ", is the port (" + port + ") in use?" : ", is the Internal_IP (" + internalIP + ") invalid? (Use 0.0.0.0 for automatic)"; + throw new EnableException(startFailure + e.getMessage().replace("0.0.0.0", "") + causeHelp, e); + } else { + throw new EnableException(startFailure + e.toString(), e); + } + } catch (Exception e) { + throw new EnableException(startFailure + e.toString(), e); + } + + webserverLogMessages.infoWebserverEnabled(getPort()); + sslContext.map(SslContextFactory::getKeyStore).ifPresent(this::logCertificateExpiryInformation); + + responseResolver.registerPages(); + } + + private void logCertificateExpiryInformation(KeyStore keyStore) { + try { + Certificate certificate = keyStore.getCertificate(webserverConfiguration.getAlias()); + if (certificate instanceof X509Certificate) { + long expires = ((X509Certificate) certificate).getNotAfter().getTime(); + long timeLeft = expires - System.currentTimeMillis(); + webserverLogMessages.certificateExpiryIn(expires); + if (timeLeft < TimeUnit.DAYS.toMillis(7L)) { + webserverLogMessages.certificateExpiryIsNear(timeLeft); + } + } + } catch (KeyStoreException ignored) { + // Don't care, just warning the user. + } + } + + private ALPNServerConnectionFactory getAlpnServerConnectionFactory(String protocol) { + ClassLoader pluginClassLoader = getClass().getClassLoader(); + return ThreadContextClassLoaderSwap.performOperation(pluginClassLoader, () -> { + try { + Class.forName("org.eclipse.jetty.alpn.java.server.JDK9ServerALPNProcessor"); + // ALPN is protocol upgrade protocol required for upgrading http 1.1 connections to 2 + ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory("h2", "h2c", "http/1.1"); + alpn.setDefaultProtocol(protocol); + return alpn; + } catch (IllegalStateException | ClassNotFoundException ignored) { + logger.warn("JDK9ServerALPNProcessor not found. ALPN (HTTP/2 upgrade protocol) is not available."); + return null; + } + }); + } + + private Optional getSslContextFactory() { + if (webserverConfiguration.isProxyModeHttps()) { + return Optional.empty(); + } + + String keyStorePath = webserverConfiguration.getKeyStorePath(); + if (!new File(keyStorePath).exists()) { + webserverLogMessages.keystoreFileNotFound(keyStorePath); + return Optional.empty(); + } + + String storepass = webserverConfiguration.getKeyStorePassword(); + String keypass = webserverConfiguration.getKeyManagerPassword(); + String alias = webserverConfiguration.getAlias(); + + if (keyStorePath.endsWith(".jks") && "DefaultPlanCert".equals(alias)) { + logger.warn("You're using self-signed PlanCert.jks certificate included with Plan.jar (Considered legacy since 5.5), it has expired and can cause issues."); + logger.info("Create new self-signed certificate using openssl:"); + logger.info(" openssl req -x509 -newkey rsa:4096 -keyout myKey.pem -out cert.pem -days 3650"); + logger.info(" openssl pkcs12 -export -out keyStore.p12 -inkey myKey.pem -in cert.pem -name alias -passout pass: -passin pass:"); + logger.info("Then change config settings to match."); + logger.info(" SSL_certificate:"); + logger.info(" KeyStore_path: keyStore.p12"); + logger.info(" Key_pass: "); + logger.info(" Store_pass: "); + logger.info(" Alias: alias"); + return legacyJettySSLContextLoader.load(keyStorePath, storepass, keypass, alias); + } + + if (!verifyAliasIsInKeystore(keyStorePath, storepass, alias)) { + return Optional.empty(); + } + + SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); + sslContextFactory.setSniRequired(false); + + sslContextFactory.setKeyStorePath(keyStorePath); + sslContextFactory.setKeyStorePassword(storepass); + sslContextFactory.setKeyManagerPassword(keypass); + sslContextFactory.setCertAlias(alias); + return Optional.of(sslContextFactory); + } + + private boolean verifyAliasIsInKeystore(String keyStorePath, String storepass, String alias) { + String keyStoreKind = keyStorePath.endsWith(".p12") ? "PKCS12" : "JKS"; + try (FileInputStream fIn = new FileInputStream(keyStorePath)) { + KeyStore keystore = KeyStore.getInstance(keyStoreKind); + + keystore.load(fIn, storepass.toCharArray()); + Certificate cert = keystore.getCertificate(alias); + + if (cert == null) { + webserverLogMessages.invalidCertificateMissingAlias(alias, keyStorePath); + return false; + } + return true; + } catch (KeyStoreException | CertificateException e) { + webserverLogMessages.unableToLoadKeystore(e, keyStorePath); + } catch (EOFException e) { + webserverLogMessages.wrongCertFileFormat(); + } catch (NoSuchAlgorithmException | IOException e) { + webserverLogMessages.keystoreLoadingError(e); + } + return false; + } + + @Override + public boolean isEnabled() { + return webserver != null && (webserver.isStarting() || webserver.isStarted()); + } + + @Override + public void disable() { + try { + if (webserver != null) { + webserver.stop(); + webserver.destroy(); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + @Override + public String getProtocol() { + return isUsingHTTPS() ? "https" : "http"; + } + + @Override + public boolean isUsingHTTPS() { + return usingHttps || webserverConfiguration.isProxyModeHttps(); + } + + @Override + public boolean isAuthRequired() { + return isUsingHTTPS() && webserverConfiguration.isAuthenticationEnabled(); + } + + @Override + public int getPort() { + return port; + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/LegacyJettySSLContextLoader.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/LegacyJettySSLContextLoader.java new file mode 100644 index 000000000..e486cfd57 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/LegacyJettySSLContextLoader.java @@ -0,0 +1,102 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.webserver.http; + +import com.djrapitops.plan.settings.locale.Locale; +import com.djrapitops.plan.settings.locale.lang.PluginLang; +import com.djrapitops.plan.utilities.logging.ErrorContext; +import com.djrapitops.plan.utilities.logging.ErrorLogger; +import net.playeranalytics.plugin.server.PluginLogger; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; +import java.io.EOFException; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.security.*; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.util.Optional; + +@Singleton +public class LegacyJettySSLContextLoader { + + private final Locale locale; + private final PluginLogger logger; + private final ErrorLogger errorLogger; + + @Inject + public LegacyJettySSLContextLoader(Locale locale, PluginLogger logger, ErrorLogger errorLogger) { + this.locale = locale; + this.logger = logger; + this.errorLogger = errorLogger; + } + + public Optional load(String keyStorePath, String storepass, String keypass, String alias) { + String keyStoreKind = keyStorePath.endsWith(".p12") ? "PKCS12" : "JKS"; + try (FileInputStream fIn = new FileInputStream(keyStorePath)) { + KeyStore keystore = KeyStore.getInstance(keyStoreKind); + + keystore.load(fIn, storepass.toCharArray()); + Certificate cert = keystore.getCertificate(alias); + + if (cert == null) { + throw new IllegalStateException("Alias: '" + alias + "' was not found in file " + keyStorePath + "."); + } + + logger.info("Certificate: " + cert.getType()); + + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509"); + keyManagerFactory.init(keystore, keypass.toCharArray()); + + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509"); + trustManagerFactory.init(keystore); + + SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); + sslContext.init(keyManagerFactory.getKeyManagers(), null/*trustManagerFactory.getTrustManagers()*/, null); + + SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); + sslContextFactory.setSslContext(sslContext); + + return Optional.of(sslContextFactory); + } catch (IllegalStateException e) { + logger.error(e.getMessage()); + } catch (KeyManagementException | NoSuchAlgorithmException e) { + logger.error(locale.getString(PluginLang.WEB_SERVER_FAIL_SSL_CONTEXT)); + errorLogger.error(e, ErrorContext.builder().related(keyStoreKind).build()); + } catch (EOFException e) { + logger.error(locale.getString(PluginLang.WEB_SERVER_FAIL_EMPTY_FILE)); + } catch (FileNotFoundException e) { + logger.info(locale.getString(PluginLang.WEB_SERVER_NOTIFY_NO_CERT_FILE, keyStorePath)); + logger.info(locale.getString(PluginLang.WEB_SERVER_NOTIFY_HTTP)); + } catch (IOException e) { + errorLogger.error(e, ErrorContext.builder().related(keyStorePath).build()); + } catch (KeyStoreException | CertificateException | UnrecoverableKeyException e) { + logger.error(locale.getString(PluginLang.WEB_SERVER_FAIL_STORE_LOAD)); + errorLogger.error(e, ErrorContext.builder() + .whatToDo("Make sure the Certificate settings are correct / You can try remaking the keystore without -passin or -passout parameters.") + .related(keyStorePath).build()); + } + return Optional.empty(); + } + +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/RequestHandler.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/RequestHandler.java new file mode 100644 index 000000000..253f51390 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/RequestHandler.java @@ -0,0 +1,137 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.webserver.http; + +import com.djrapitops.plan.delivery.web.resolver.Response; +import com.djrapitops.plan.delivery.web.resolver.request.Request; +import com.djrapitops.plan.delivery.webserver.PassBruteForceGuard; +import com.djrapitops.plan.delivery.webserver.ResponseFactory; +import com.djrapitops.plan.delivery.webserver.ResponseResolver; +import com.djrapitops.plan.delivery.webserver.auth.FailReason; +import com.djrapitops.plan.delivery.webserver.configuration.WebserverConfiguration; +import com.djrapitops.plan.exceptions.WebUserAuthException; +import org.apache.commons.lang3.StringUtils; +import org.eclipse.jetty.http.HttpHeader; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Optional; + +@Singleton +public class RequestHandler { + + private final WebserverConfiguration webserverConfiguration; + private final ResponseFactory responseFactory; + private final ResponseResolver responseResolver; + + private final PassBruteForceGuard bruteForceGuard; + private final AccessLogger accessLogger; + + @Inject + public RequestHandler(WebserverConfiguration webserverConfiguration, ResponseFactory responseFactory, ResponseResolver responseResolver, AccessLogger accessLogger) { + this.webserverConfiguration = webserverConfiguration; + this.responseFactory = responseFactory; + this.responseResolver = responseResolver; + this.accessLogger = accessLogger; + + bruteForceGuard = new PassBruteForceGuard(); + } + + public Response getResponse(InternalRequest internalRequest) { + String accessAddress = internalRequest.getAccessAddress(webserverConfiguration); + + Response response; + Request request = null; + if (bruteForceGuard.shouldPreventRequest(accessAddress)) { + response = responseFactory.failedLoginAttempts403(); + } else if (!webserverConfiguration.getAllowedIpList().isAllowed(accessAddress)) { + webserverConfiguration.getWebserverLogMessages() + .warnAboutWhitelistBlock(accessAddress, internalRequest.getRequestedURIString()); + response = responseFactory.ipWhitelist403(accessAddress); + } else { + try { + request = internalRequest.toRequest(); + response = attemptToResolve(request, accessAddress); + } catch (WebUserAuthException thrownByAuthentication) { + response = processFailedAuthentication(internalRequest, accessAddress, thrownByAuthentication); + } + } + + response.getHeaders().putIfAbsent("Access-Control-Allow-Origin", webserverConfiguration.getAllowedCorsOrigin()); + response.getHeaders().putIfAbsent("Access-Control-Allow-Methods", "GET, OPTIONS"); + response.getHeaders().putIfAbsent("Access-Control-Allow-Credentials", "true"); + response.getHeaders().putIfAbsent("X-Robots-Tag", "noindex, nofollow"); + + accessLogger.log(internalRequest, request, response); + + return response; + } + + private Response attemptToResolve(Request request, String accessAddress) { + Response response = protocolUpgradeResponse(request) + .orElseGet(() -> responseResolver.getResponse(request)); + request.getUser().ifPresent(user -> processSuccessfulLogin(response.getCode(), accessAddress)); + return response; + } + + private Optional protocolUpgradeResponse(Request request) { + Optional upgrade = request.getHeader(HttpHeader.UPGRADE.asString()); + if (upgrade.isPresent()) { + String value = upgrade.get(); + if ("h2c".equals(value) || "h2".equals(value)) { + return Optional.of(Response.builder() + .setStatus(101) + .setHeader("Connection", HttpHeader.UPGRADE.asString()) + .setHeader(HttpHeader.UPGRADE.asString(), value) + .build()); + } + } + return Optional.empty(); + } + + private Response processFailedAuthentication(InternalRequest internalRequest, String accessAddress, WebUserAuthException thrownByAuthentication) { + FailReason failReason = thrownByAuthentication.getFailReason(); + if (failReason == FailReason.USER_PASS_MISMATCH) { + return processWrongPassword(accessAddress, failReason); + } else { + String from = internalRequest.getRequestedURIString(); + String directTo = StringUtils.startsWithAny(from, "/auth/", "/login") ? "/login" : "/login?from=." + from; + return Response.builder() + .redirectTo(directTo) + .setHeader("Set-Cookie", "auth=expired; Path=/; Max-Age=0; SameSite=Lax; Secure;") + .build(); + } + } + + private Response processWrongPassword(String accessAddress, FailReason failReason) { + bruteForceGuard.increaseAttemptCountOnFailedLogin(accessAddress); + if (bruteForceGuard.shouldPreventRequest(accessAddress)) { + return responseFactory.failedLoginAttempts403(); + } else { + return responseFactory.badRequest(failReason.getReason(), "/auth/login"); + } + } + + private void processSuccessfulLogin(int responseCode, String accessAddress) { + boolean successfulLogin = responseCode != 401; + boolean notForbidden = responseCode != 403; + if (successfulLogin && notForbidden) { + bruteForceGuard.resetAttemptCount(accessAddress); + } + } + +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/WebServer.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/WebServer.java new file mode 100644 index 000000000..05e1f2723 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/WebServer.java @@ -0,0 +1,37 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.webserver.http; + +import com.djrapitops.plan.SubSystem; + +public interface WebServer extends SubSystem { + @Override + void enable(); + + boolean isEnabled(); + + @Override + void disable(); + + String getProtocol(); + + boolean isUsingHTTPS(); + + boolean isAuthRequired(); + + int getPort(); +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/PlayerPageResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/PlayerPageResolver.java index d8e8d3a60..e4a2c1e13 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/PlayerPageResolver.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/PlayerPageResolver.java @@ -24,6 +24,8 @@ import com.djrapitops.plan.delivery.web.resolver.request.URIPath; import com.djrapitops.plan.delivery.web.resolver.request.WebUser; import com.djrapitops.plan.delivery.webserver.ResponseFactory; import com.djrapitops.plan.identification.UUIDUtility; +import com.djrapitops.plan.settings.config.PlanConfig; +import com.djrapitops.plan.settings.config.paths.PluginSettings; import org.apache.commons.lang3.StringUtils; import javax.inject.Inject; @@ -39,14 +41,17 @@ import java.util.UUID; @Singleton public class PlayerPageResolver implements Resolver { + private final PlanConfig config; private final ResponseFactory responseFactory; private final UUIDUtility uuidUtility; @Inject public PlayerPageResolver( + PlanConfig config, ResponseFactory responseFactory, UUIDUtility uuidUtility ) { + this.config = config; this.responseFactory = responseFactory; this.uuidUtility = uuidUtility; } @@ -60,13 +65,13 @@ public class PlayerPageResolver implements Resolver { return uuidUtility.getNameOf(nameOrUUID).map(user.getName()::equalsIgnoreCase) // uuid matches user .orElse(false); // uuid or name don't match }).orElse(true); // No name or UUID given - return user.hasPermission("page.player.other") || (user.hasPermission("page.player.self") && isOwnPage); + return user.hasPermission("page.player.other") || user.hasPermission("page.player.self") && isOwnPage; } @Override public Optional resolve(Request request) { URIPath path = request.getPath(); - if (StringUtils.containsAny(path.asString(), "/vendor/", "/js/", "/css/", "/img/")) { + if (StringUtils.containsAny(path.asString(), "/vendor/", "/js/", "/css/", "/img/", "/static/")) { return Optional.empty(); } return path.getPart(1) @@ -82,7 +87,7 @@ public class PlayerPageResolver implements Resolver { return responseFactory.rawPlayerPageResponse(playerUUID); } - if (path.getPart(2).isPresent()) { + if (path.getPart(2).isPresent() && config.isFalse(PluginSettings.FRONTEND_BETA)) { // Redirect /player/{uuid/name}/ to /player/{uuid} return responseFactory.redirectResponse("../" + Html.encodeToURL(playerUUID.toString())); } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/RootPageResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/RootPageResolver.java index b89912cad..c47360a3c 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/RootPageResolver.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/RootPageResolver.java @@ -22,8 +22,8 @@ import com.djrapitops.plan.delivery.web.resolver.Response; import com.djrapitops.plan.delivery.web.resolver.request.Request; import com.djrapitops.plan.delivery.web.resolver.request.WebUser; import com.djrapitops.plan.delivery.webserver.ResponseFactory; -import com.djrapitops.plan.delivery.webserver.WebServer; import com.djrapitops.plan.delivery.webserver.auth.FailReason; +import com.djrapitops.plan.delivery.webserver.http.WebServer; import com.djrapitops.plan.exceptions.WebUserAuthException; import com.djrapitops.plan.identification.Server; import com.djrapitops.plan.identification.ServerInfo; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/ServerPageResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/ServerPageResolver.java index 8bacfdfe7..079f80018 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/ServerPageResolver.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/ServerPageResolver.java @@ -95,7 +95,7 @@ public class ServerPageResolver implements Resolver { private Optional getServerUUID(URIPath path) { if (serverInfo.getServer().isProxy() && path.getPart(0).map("network"::equals).orElse(false) - && !path.getPart(1).isPresent() // No slash at the end. + && path.getPart(1).isEmpty() // No slash at the end. ) { return Optional.of(serverInfo.getServerUUID()); } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/StaticResourceResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/StaticResourceResolver.java index 7ada6211d..6f1f980a0 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/StaticResourceResolver.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/StaticResourceResolver.java @@ -35,6 +35,9 @@ import java.util.Optional; @Singleton public class StaticResourceResolver implements NoAuthResolver { + private static final String PART_REGEX = "(vendor|css|js|img|static)"; + public static final String PATH_REGEX = "^.*/" + PART_REGEX + "/.*"; + private final ResponseFactory responseFactory; @Inject @@ -67,7 +70,7 @@ public class StaticResourceResolver implements NoAuthResolver { private URIPath getPath(Request request) { URIPath path = request.getPath(); // Remove everything before /vendor /css /js or /img - while (!path.getPart(0).map(part -> part.matches("(vendor|css|js|img)")).orElse(true)) { + while (!path.getPart(0).map(part -> part.matches(PART_REGEX)).orElse(true)) { path = path.omitFirst(); } return path; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/auth/LoginPageResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/auth/LoginPageResolver.java index a3b40a171..56c3c4423 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/auth/LoginPageResolver.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/auth/LoginPageResolver.java @@ -21,7 +21,7 @@ import com.djrapitops.plan.delivery.web.resolver.Response; import com.djrapitops.plan.delivery.web.resolver.request.Request; import com.djrapitops.plan.delivery.web.resolver.request.WebUser; import com.djrapitops.plan.delivery.webserver.ResponseFactory; -import com.djrapitops.plan.delivery.webserver.WebServer; +import com.djrapitops.plan.delivery.webserver.http.WebServer; import dagger.Lazy; import javax.inject.Inject; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/auth/LoginResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/auth/LoginResolver.java index 8bdb1bd0f..67452a79f 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/auth/LoginResolver.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/auth/LoginResolver.java @@ -22,6 +22,7 @@ import com.djrapitops.plan.delivery.web.resolver.Response; import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException; import com.djrapitops.plan.delivery.web.resolver.request.Request; import com.djrapitops.plan.delivery.web.resolver.request.URIQuery; +import com.djrapitops.plan.delivery.webserver.RequestBodyConverter; import com.djrapitops.plan.delivery.webserver.auth.ActiveCookieStore; import com.djrapitops.plan.delivery.webserver.auth.FailReason; import com.djrapitops.plan.exceptions.PassEncryptException; @@ -29,6 +30,13 @@ import com.djrapitops.plan.exceptions.WebUserAuthException; import com.djrapitops.plan.exceptions.database.DBOpException; import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.queries.objects.WebUserQueries; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; import javax.inject.Inject; import javax.inject.Singleton; @@ -36,6 +44,7 @@ import java.util.Collections; import java.util.Optional; @Singleton +@Path("/auth/login") public class LoginResolver implements NoAuthResolver { private final DBSystem dbSystem; @@ -50,6 +59,21 @@ public class LoginResolver implements NoAuthResolver { this.activeCookieStore = activeCookieStore; } + @POST + @Operation( + description = "Log in as user. Pass user=username&password=password in response body.", + requestBody = @RequestBody( + required = true, + content = @Content( + examples = {@ExampleObject("user=username&password=password")} + ) + ), + responses = { + @ApiResponse(responseCode = "200", description = "Login success, read Set-Cookie header for cookie"), + @ApiResponse(responseCode = "400", description = "Bad input user details"), + @ApiResponse(responseCode = "403", description = "Too many attempts, wait"), + } + ) @Override public Optional resolve(Request request) { try { @@ -63,15 +87,16 @@ public class LoginResolver implements NoAuthResolver { public Response getResponse(String cookie) { return Response.builder() .setStatus(200) - .setHeader("Set-Cookie", "auth=" + cookie + "; Path=/; Max-Age=" + ActiveCookieStore.cookieExpiresAfter + "; SameSite=Lax; Secure;") + .setHeader("Set-Cookie", "auth=" + cookie + "; Path=/; Max-Age=" + ActiveCookieStore.cookieExpiresAfterMs + "; SameSite=Lax; Secure;") .setJSONContent(Collections.singletonMap("success", true)) .build(); } public User getUser(Request request) { + URIQuery form = RequestBodyConverter.formBody(request); URIQuery query = request.getQuery(); - String username = query.get("user").orElseThrow(() -> new BadRequestException("'user' parameter not defined")); - String password = query.get("password").orElseThrow(() -> new BadRequestException("'password' parameter not defined")); + String username = getUser(form, query); + String password = getPassword(form, query); User user = dbSystem.getDatabase().query(WebUserQueries.fetchUser(username)) .orElseThrow(() -> new WebUserAuthException(FailReason.USER_PASS_MISMATCH)); @@ -81,4 +106,16 @@ public class LoginResolver implements NoAuthResolver { } return user; } + + private String getPassword(URIQuery form, URIQuery query) { + return form.get("password") + .orElseGet(() -> query.get("password") + .orElseThrow(() -> new BadRequestException("'password' parameter not defined"))); + } + + private String getUser(URIQuery form, URIQuery query) { + return form.get("user") + .orElseGet(() -> query.get("user") + .orElseThrow(() -> new BadRequestException("'user' parameter not defined"))); + } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/auth/LogoutResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/auth/LogoutResolver.java index 356481ecd..d064827b0 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/auth/LogoutResolver.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/auth/LogoutResolver.java @@ -22,12 +22,20 @@ import com.djrapitops.plan.delivery.web.resolver.request.Request; import com.djrapitops.plan.delivery.webserver.auth.ActiveCookieStore; import com.djrapitops.plan.delivery.webserver.auth.FailReason; import com.djrapitops.plan.exceptions.WebUserAuthException; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; import javax.inject.Inject; import javax.inject.Singleton; import java.util.Optional; @Singleton +@Path("/auth/logout") public class LogoutResolver implements NoAuthResolver { private final ActiveCookieStore activeCookieStore; @@ -39,6 +47,15 @@ public class LogoutResolver implements NoAuthResolver { this.activeCookieStore = activeCookieStore; } + @GET + @Operation( + description = "Logout the user by removing cookie", + responses = { + @ApiResponse(responseCode = "302 (success)", description = "Logout successful, redirects to /login"), + @ApiResponse(responseCode = "302 (failure)", description = "Cookie had already expired, redirects to /login"), + }, + requestBody = @RequestBody(content = @Content(examples = @ExampleObject())) + ) @Override public Optional resolve(Request request) { String cookies = request.getHeader("Cookie").orElse(""); @@ -47,10 +64,9 @@ public class LogoutResolver implements NoAuthResolver { if (cookie.isEmpty()) continue; String[] split = cookie.split("="); String name = split[0]; - String value = split[1]; - if ("auth".equals(name)) { - foundCookie = value; - activeCookieStore.removeCookie(value); + if ("auth".equals(name) && split.length > 1) { + foundCookie = split[1]; + activeCookieStore.removeCookie(foundCookie); } } @@ -63,7 +79,7 @@ public class LogoutResolver implements NoAuthResolver { public Response getResponse() { return Response.builder() .redirectTo("/login") - .setHeader("Set-Cookie", "auth=expired; Max-Age=1; SameSite=Lax; Secure;") + .setHeader("Set-Cookie", "auth=expired; Max-Age=0; SameSite=Lax; Secure;") .build(); } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/auth/RegisterPageResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/auth/RegisterPageResolver.java index f68601919..c346c50bf 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/auth/RegisterPageResolver.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/auth/RegisterPageResolver.java @@ -21,7 +21,7 @@ import com.djrapitops.plan.delivery.web.resolver.Response; import com.djrapitops.plan.delivery.web.resolver.request.Request; import com.djrapitops.plan.delivery.web.resolver.request.WebUser; import com.djrapitops.plan.delivery.webserver.ResponseFactory; -import com.djrapitops.plan.delivery.webserver.WebServer; +import com.djrapitops.plan.delivery.webserver.http.WebServer; import dagger.Lazy; import javax.inject.Inject; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/auth/RegisterResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/auth/RegisterResolver.java index 3b7ce4154..e86910a04 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/auth/RegisterResolver.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/auth/RegisterResolver.java @@ -16,16 +16,27 @@ */ package com.djrapitops.plan.delivery.webserver.resolver.auth; +import com.djrapitops.plan.delivery.web.resolver.MimeType; import com.djrapitops.plan.delivery.web.resolver.NoAuthResolver; import com.djrapitops.plan.delivery.web.resolver.Response; import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException; import com.djrapitops.plan.delivery.web.resolver.request.Request; import com.djrapitops.plan.delivery.web.resolver.request.URIQuery; +import com.djrapitops.plan.delivery.webserver.RequestBodyConverter; import com.djrapitops.plan.delivery.webserver.auth.RegistrationBin; import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.queries.objects.WebUserQueries; import com.djrapitops.plan.utilities.PassEncryptUtil; import com.djrapitops.plan.utilities.java.Maps; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; import javax.inject.Inject; import javax.inject.Singleton; @@ -33,13 +44,35 @@ import java.util.Collections; import java.util.Optional; @Singleton +@Path("/auth/register") public class RegisterResolver implements NoAuthResolver { private final DBSystem dbSystem; @Inject - public RegisterResolver(DBSystem dbSystem) {this.dbSystem = dbSystem;} + public RegisterResolver(DBSystem dbSystem) { + this.dbSystem = dbSystem; + } + @GET + @Operation( + description = "Start new registration and check if registration is complete. POST user=username&password=password to start new registration.", + responses = { + @ApiResponse(responseCode = "200 (new)", description = "New registration started (when given request body details)", content = @Content(mediaType = MimeType.JSON, examples = @ExampleObject("{\"success\": true, \"code\": \"474AF76D5362\"}"))), + @ApiResponse(responseCode = "200 (unfinished)", description = "Registration not yet completed (when given code as parameter)", content = @Content(mediaType = MimeType.JSON, examples = @ExampleObject("{\"success\": false}"))), + @ApiResponse(responseCode = "200 (completed)", description = "Registration completed (when given code as parameter)", content = @Content(mediaType = MimeType.JSON, examples = @ExampleObject("{\"success\": true}"))), + @ApiResponse(responseCode = "400", description = "Given username has already been registered", content = @Content(mediaType = MimeType.JSON, examples = @ExampleObject("{\"status\": 400, \"error\": \"User already exists!\"}"))), + }, + parameters = { + @Parameter(in = ParameterIn.QUERY, name = "code", description = "Registration code for finishing registration, Check if registration is complete - success: true if yes.") + }, + requestBody = @RequestBody( + description = "Register a new user", + content = @Content( + examples = @ExampleObject("user=username&password=password") + ) + ) + ) @Override public Optional resolve(Request request) { return Optional.of(getResponse(request)); @@ -55,12 +88,13 @@ public class RegisterResolver implements NoAuthResolver { .build(); } - String username = query.get("user").orElseThrow(() -> new BadRequestException("'user' parameter not defined")); + URIQuery form = RequestBodyConverter.formBody(request); + String username = getUser(form, query); boolean alreadyExists = dbSystem.getDatabase().query(WebUserQueries.fetchUser(username)).isPresent(); if (alreadyExists) throw new BadRequestException("User already exists!"); - String password = query.get("password").orElseThrow(() -> new BadRequestException("'password' parameter not defined")); + String password = getPassword(form, query); try { String code = RegistrationBin.addInfoForRegistration(username, password); return Response.builder() @@ -75,4 +109,15 @@ public class RegisterResolver implements NoAuthResolver { } } + private String getPassword(URIQuery form, URIQuery query) { + return form.get("password") + .orElseGet(() -> query.get("password") + .orElseThrow(() -> new BadRequestException("'password' parameter not defined"))); + } + + private String getUser(URIQuery form, URIQuery query) { + return form.get("user") + .orElseGet(() -> query.get("user") + .orElseThrow(() -> new BadRequestException("'user' parameter not defined"))); + } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/ErrorsJSONResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/ErrorsJSONResolver.java index a97e3b80a..6695151cd 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/ErrorsJSONResolver.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/ErrorsJSONResolver.java @@ -22,6 +22,15 @@ import com.djrapitops.plan.delivery.web.resolver.Response; import com.djrapitops.plan.delivery.web.resolver.request.Request; import com.djrapitops.plan.delivery.web.resolver.request.WebUser; import com.djrapitops.plan.storage.file.PlanFiles; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; import javax.inject.Inject; import javax.inject.Singleton; @@ -36,6 +45,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; @Singleton +@Path("/v1/errors") public class ErrorsJSONResolver implements Resolver { private final PlanFiles files; @@ -50,6 +60,14 @@ public class ErrorsJSONResolver implements Resolver { return request.getUser().orElse(new WebUser("")).hasPermission("page.server"); } + @GET + @Operation( + description = "Get list of Plan error logs", + responses = { + @ApiResponse(responseCode = "200", description = "List of error files and their contents", content = @Content(array = @ArraySchema(schema = @Schema(implementation = ErrorFile.class)))) + }, + requestBody = @RequestBody(content = @Content(examples = @ExampleObject())) + ) @Override public Optional resolve(Request request) { return Optional.of(getResponse()); @@ -63,12 +81,12 @@ public class ErrorsJSONResolver implements Resolver { } private List loadErrorLogs() { - File[] files = this.files.getLogsFolder().listFiles(); + File[] logFiles = this.files.getLogsFolder().listFiles(); // Can't use Collections.emptyList since Gson doesn't serialize it - if (files == null || files.length == 0) return new ArrayList<>(); + if (logFiles == null || logFiles.length == 0) return new ArrayList<>(); List errorFiles = new ArrayList<>(); - for (File file : files) { + for (File file : logFiles) { errorFiles.add(new ErrorFile(file.getName(), read(file))); } @@ -79,7 +97,7 @@ public class ErrorsJSONResolver implements Resolver { try (Stream lines = Files.lines(file.toPath())) { return lines.collect(Collectors.toList()); } catch (IOException e) { - return Collections.singletonList("Failed to read " + file.getAbsolutePath() + ": " + e.toString()); + return Collections.singletonList("Failed to read " + file.getAbsolutePath() + ": " + e); } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/ExtensionJSONResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/ExtensionJSONResolver.java new file mode 100644 index 000000000..037c4c4f1 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/ExtensionJSONResolver.java @@ -0,0 +1,124 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.webserver.resolver.json; + +import com.djrapitops.plan.delivery.domain.datatransfer.extension.ExtensionDataDto; +import com.djrapitops.plan.delivery.web.resolver.MimeType; +import com.djrapitops.plan.delivery.web.resolver.Resolver; +import com.djrapitops.plan.delivery.web.resolver.Response; +import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException; +import com.djrapitops.plan.delivery.web.resolver.exception.NotFoundException; +import com.djrapitops.plan.delivery.web.resolver.request.Request; +import com.djrapitops.plan.delivery.web.resolver.request.WebUser; +import com.djrapitops.plan.delivery.webserver.cache.AsyncJSONResolverService; +import com.djrapitops.plan.delivery.webserver.cache.DataID; +import com.djrapitops.plan.delivery.webserver.cache.JSONStorage; +import com.djrapitops.plan.extension.implementation.results.ExtensionData; +import com.djrapitops.plan.extension.implementation.storage.queries.ExtensionServerDataQuery; +import com.djrapitops.plan.identification.Identifiers; +import com.djrapitops.plan.identification.ServerUUID; +import com.djrapitops.plan.storage.database.DBSystem; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.ws.rs.GET; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * @author AuroraLS3 + */ +@Singleton +public class ExtensionJSONResolver implements Resolver { + + private final DBSystem dbSystem; + private final Identifiers identifiers; + private final AsyncJSONResolverService jsonResolverService; + + @Inject + public ExtensionJSONResolver(DBSystem dbSystem, Identifiers identifiers, AsyncJSONResolverService jsonResolverService) { + this.dbSystem = dbSystem; + this.identifiers = identifiers; + this.jsonResolverService = jsonResolverService; + } + + @Override + public boolean canAccess(Request request) { + WebUser permissions = request.getUser().orElse(new WebUser("")); + return permissions.hasPermission("page.server") || permissions.hasPermission("page.network"); + } + + @GET + @Operation( + description = "Get extension data of a specific server.", + responses = { + @ApiResponse(responseCode = "200", content = @Content(mediaType = MimeType.JSON)), + @ApiResponse(responseCode = "400", description = "If 'server' parameter is not given"), + @ApiResponse(responseCode = "404", description = "If 'server' parameter is not an existing server") + }, + parameters = @Parameter(in = ParameterIn.QUERY, required = true, name = "server", description = "Server identifier to get data for", examples = { + @ExampleObject("Server 1"), + @ExampleObject("1"), + @ExampleObject("1fb39d2a-eb82-4868-b245-1fad17d823b3"), + }), + requestBody = @RequestBody(content = @Content(examples = @ExampleObject())) + ) + @Override + public Optional resolve(Request request) { + String identifier = request.getQuery().get("server") + .orElseThrow(() -> new BadRequestException("'server' parameter was not given")); + ServerUUID serverUUID = identifiers.getServerUUID(identifier) + .orElseThrow(() -> new NotFoundException("Server with identifier '" + identifier + "' was not found in database")); + return Optional.of(getResponse(request, serverUUID)); + } + + private JSONStorage.StoredJSON getJSON(Request request, ServerUUID serverUUID) { + Optional timestamp = Identifiers.getTimestamp(request); + + return jsonResolverService.resolve( + timestamp, DataID.EXTENSION_JSON, serverUUID, + this::getExtensionData + ); + } + + private Response getResponse(Request request, ServerUUID serverUUID) { + JSONStorage.StoredJSON json = getJSON(request, serverUUID); + + return Response.builder() + .setJSONContent(json.json) + .build(); + } + + private Map> getExtensionData(ServerUUID serverUUID) { + List extensionData = dbSystem.getDatabase().query(new ExtensionServerDataQuery(serverUUID)); + return Map.of( + "extensions", extensionData.stream() + .sorted() + .map(ExtensionDataDto::new) + .collect(Collectors.toList()) + ); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/FiltersJSONResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/FiltersJSONResolver.java index 9abde9d0a..b3a8a8c90 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/FiltersJSONResolver.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/FiltersJSONResolver.java @@ -17,8 +17,10 @@ package com.djrapitops.plan.delivery.webserver.resolver.json; import com.djrapitops.plan.delivery.domain.DateObj; -import com.djrapitops.plan.delivery.formatting.Formatter; +import com.djrapitops.plan.delivery.domain.datatransfer.FilterDto; +import com.djrapitops.plan.delivery.domain.datatransfer.ViewDto; import com.djrapitops.plan.delivery.formatting.Formatters; +import com.djrapitops.plan.delivery.rendering.json.JSONFactory; import com.djrapitops.plan.delivery.rendering.json.graphs.Graphs; import com.djrapitops.plan.delivery.rendering.json.graphs.line.LineGraph; import com.djrapitops.plan.delivery.rendering.json.graphs.line.Point; @@ -36,7 +38,14 @@ import com.djrapitops.plan.storage.database.queries.objects.TPSQueries; import com.djrapitops.plan.utilities.java.Lists; import com.djrapitops.plan.utilities.logging.ErrorContext; import com.djrapitops.plan.utilities.logging.ErrorLogger; -import org.apache.commons.lang3.StringUtils; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; import javax.inject.Inject; import javax.inject.Singleton; @@ -45,11 +54,13 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @Singleton +@Path("/v1/filters") public class FiltersJSONResolver implements Resolver { private final ServerInfo serverInfo; private final DBSystem dbSystem; private final QueryFilters filters; + private final JSONFactory jsonFactory; private final Graphs graphs; private final Formatters formatters; private final ErrorLogger errorLogger; @@ -59,6 +70,7 @@ public class FiltersJSONResolver implements Resolver { ServerInfo serverInfo, DBSystem dbSystem, QueryFilters filters, + JSONFactory jsonFactory, Graphs graphs, Formatters formatters, ErrorLogger errorLogger @@ -66,6 +78,7 @@ public class FiltersJSONResolver implements Resolver { this.serverInfo = serverInfo; this.dbSystem = dbSystem; this.filters = filters; + this.jsonFactory = jsonFactory; this.graphs = graphs; this.formatters = formatters; this.errorLogger = errorLogger; @@ -77,6 +90,14 @@ public class FiltersJSONResolver implements Resolver { return user.hasPermission("page.players"); } + @GET + @Operation( + description = "Get list of available filters, view and graph points for visualizing the view", + responses = { + @ApiResponse(responseCode = "200", content = @Content(mediaType = MimeType.JSON, schema = @Schema(implementation = FilterResponseDto.class))) + }, + requestBody = @RequestBody(content = @Content(examples = @ExampleObject())) + ) @Override public Optional resolve(Request request) { return Optional.of(getResponse()); @@ -85,9 +106,9 @@ public class FiltersJSONResolver implements Resolver { private Response getResponse() { return Response.builder() .setMimeType(MimeType.JSON) - .setJSONContent(new FilterResponseJSON( + .setJSONContent(new FilterResponseDto( filters.getFilters(), - new ViewJSON(formatters), + new ViewDto(formatters, jsonFactory.listServers().get("servers")), fetchViewGraphPoints() )).build(); } @@ -111,17 +132,17 @@ public class FiltersJSONResolver implements Resolver { /** * JSON serialization class. */ - class FilterResponseJSON { - final List filters; - final ViewJSON view; + class FilterResponseDto { + final List filters; + final ViewDto view; final List viewPoints; - public FilterResponseJSON(Map filtersByKind, ViewJSON view, List viewPoints) { + public FilterResponseDto(Map filtersByKind, ViewDto view, List viewPoints) { this.viewPoints = viewPoints; this.filters = new ArrayList<>(); for (Map.Entry entry : filtersByKind.entrySet()) { try { - filters.add(new FilterJSON(entry.getKey(), entry.getValue())); + filters.add(new FilterDto(entry.getKey(), entry.getValue())); } catch (Exception e) { errorLogger.error(e, ErrorContext.builder() .whatToDo("Report this, filter '" + entry.getKey() + "' has implementation error.") @@ -133,63 +154,4 @@ public class FiltersJSONResolver implements Resolver { this.view = view; } } - - /** - * JSON serialization class. - */ - static class FilterJSON implements Comparable { - final String kind; - final Map options; - final String[] expectedParameters; - - public FilterJSON(String kind, Filter filter) { - this.kind = kind; - this.options = filter.getOptions(); - this.expectedParameters = filter.getExpectedParameters(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - FilterJSON that = (FilterJSON) o; - return Objects.equals(kind, that.kind) && Objects.equals(options, that.options) && Arrays.equals(expectedParameters, that.expectedParameters); - } - - @Override - public int hashCode() { - int result = Objects.hash(kind, options); - result = 31 * result + Arrays.hashCode(expectedParameters); - return result; - } - - @Override - public int compareTo(FilterJSON o) { - return String.CASE_INSENSITIVE_ORDER.compare(this.kind, o.kind); - } - } - - /** - * JSON serialization class. - */ - static class ViewJSON { - final String afterDate; - final String afterTime; - final String beforeDate; - final String beforeTime; - - public ViewJSON(Formatters formatters) { - long now = System.currentTimeMillis(); - long monthAgo = now - TimeUnit.DAYS.toMillis(30); - - Formatter formatter = formatters.javascriptDateFormatterLong(); - String[] after = StringUtils.split(formatter.apply(monthAgo), " "); - String[] before = StringUtils.split(formatter.apply(now), " "); - - this.afterDate = after[0]; - this.afterTime = after[1]; - this.beforeDate = before[0]; - this.beforeTime = before[1]; - } - } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/GraphsJSONResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/GraphsJSONResolver.java index d8104ebec..052d5b9e3 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/GraphsJSONResolver.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/GraphsJSONResolver.java @@ -22,12 +22,22 @@ import com.djrapitops.plan.delivery.web.resolver.Resolver; import com.djrapitops.plan.delivery.web.resolver.Response; import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException; import com.djrapitops.plan.delivery.web.resolver.request.Request; +import com.djrapitops.plan.delivery.web.resolver.request.URIQuery; import com.djrapitops.plan.delivery.web.resolver.request.WebUser; import com.djrapitops.plan.delivery.webserver.cache.AsyncJSONResolverService; import com.djrapitops.plan.delivery.webserver.cache.DataID; import com.djrapitops.plan.delivery.webserver.cache.JSONStorage; import com.djrapitops.plan.identification.Identifiers; import com.djrapitops.plan.identification.ServerUUID; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; import javax.inject.Inject; import javax.inject.Singleton; @@ -40,6 +50,7 @@ import java.util.Optional; * @author AuroraLS3 */ @Singleton +@Path("/v1/graph") public class GraphsJSONResolver implements Resolver { private final Identifiers identifiers; @@ -69,6 +80,41 @@ public class GraphsJSONResolver implements Resolver { * @throws BadRequestException If 'type' parameter is not defined or supported. * @throws BadRequestException If 'server' parameter is not defined or server is not found in database. */ + @GET + @Operation( + description = "Get graph data", + parameters = { + @Parameter(in = ParameterIn.QUERY, name = "type", description = "Type of the graph, see https://github.com/plan-player-analytics/Plan/blob/master/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/GraphsJSONResolver.java", required = true, examples = { + @ExampleObject(value = "performance", description = "Deprecated, use optimizedPerformance"), + @ExampleObject("optimizedPerformance"), + @ExampleObject("playersOnline"), + @ExampleObject("uniqueAndNew"), + @ExampleObject("hourlyUniqueAndNew"), + @ExampleObject("serverCalendar"), + @ExampleObject("worldPie"), + @ExampleObject("activity"), + @ExampleObject("geolocation"), + @ExampleObject("aggregatedPing"), + @ExampleObject("punchCard"), + @ExampleObject("serverPie"), + @ExampleObject("joinAddressPie"), + @ExampleObject("joinAddressByDay"), + }), + @Parameter(in = ParameterIn.QUERY, name = "server", description = "Server identifier to get data for", examples = { + @ExampleObject("Server 1"), + @ExampleObject("1"), + @ExampleObject("1fb39d2a-eb82-4868-b245-1fad17d823b3"), + }), + @Parameter(in = ParameterIn.QUERY, name = "timestamp", description = "Epoch millisecond for the request, newer value is wanted") + }, + responses = { + @ApiResponse(responseCode = "200", description = "Graph data json", content = @Content()), + @ApiResponse(responseCode = "400", description = "'type' parameter not given", content = @Content(examples = { + @ExampleObject("{\"status\": 400, \"error\": \"'type' parameter was not defined.\"}") + })), + }, + requestBody = @RequestBody(content = @Content(examples = @ExampleObject())) + ) @Override public Optional resolve(Request request) { return Optional.of(getResponse(request)); @@ -87,19 +133,19 @@ public class GraphsJSONResolver implements Resolver { } private JSONStorage.StoredJSON getGraphJSON(Request request, DataID dataID) { - long timestamp = Identifiers.getTimestamp(request); + Optional timestamp = Identifiers.getTimestamp(request); JSONStorage.StoredJSON storedJSON; if (request.getQuery().get("server").isPresent()) { ServerUUID serverUUID = identifiers.getServerUUID(request); // Can throw BadRequestException storedJSON = jsonResolverService.resolve( timestamp, dataID, serverUUID, - theServerUUID -> generateGraphDataJSONOfType(dataID, theServerUUID) + theServerUUID -> generateGraphDataJSONOfType(dataID, theServerUUID, request.getQuery()) ); } else { // Assume network storedJSON = jsonResolverService.resolve( - timestamp, dataID, () -> generateGraphDataJSONOfType(dataID) + timestamp, dataID, () -> generateGraphDataJSONOfType(dataID, request.getQuery()) ); } return storedJSON; @@ -133,12 +179,14 @@ public class GraphsJSONResolver implements Resolver { return DataID.GRAPH_SERVER_PIE; case "joinAddressPie": return DataID.GRAPH_HOSTNAME_PIE; + case "joinAddressByDay": + return DataID.JOIN_ADDRESSES_BY_DAY; default: throw new BadRequestException("unknown 'type' parameter."); } } - private Object generateGraphDataJSONOfType(DataID id, ServerUUID serverUUID) { + private Object generateGraphDataJSONOfType(DataID id, ServerUUID serverUUID, URIQuery query) { switch (id) { case GRAPH_PERFORMANCE: return graphJSON.performanceGraphJSON(serverUUID); @@ -164,12 +212,21 @@ public class GraphsJSONResolver implements Resolver { return graphJSON.pingGraphsJSON(serverUUID); case GRAPH_PUNCHCARD: return graphJSON.punchCardJSONAsMap(serverUUID); + case JOIN_ADDRESSES_BY_DAY: + try { + return graphJSON.joinAddressesByDay(serverUUID, + query.get("after").map(Long::parseLong).orElse(0L), + query.get("before").map(Long::parseLong).orElse(System.currentTimeMillis()) + ); + } catch (NumberFormatException e) { + throw new BadRequestException("'after' or 'before' is not a epoch millisecond (number) " + e.getMessage()); + } default: return Collections.singletonMap("error", "Undefined ID: " + id.name()); } } - private Object generateGraphDataJSONOfType(DataID id) { + private Object generateGraphDataJSONOfType(DataID id, URIQuery query) { switch (id) { case GRAPH_ACTIVITY: return graphJSON.activityGraphsJSONAsMap(); @@ -183,6 +240,15 @@ public class GraphsJSONResolver implements Resolver { return graphJSON.playerHostnamePieJSONAsMap(); case GRAPH_WORLD_MAP: return graphJSON.geolocationGraphsJSONAsMap(); + case JOIN_ADDRESSES_BY_DAY: + try { + return graphJSON.joinAddressesByDay( + query.get("after").map(Long::parseLong).orElse(0L), + query.get("before").map(Long::parseLong).orElse(System.currentTimeMillis()) + ); + } catch (NumberFormatException e) { + throw new BadRequestException("'after' or 'before' is not a epoch millisecond (number) " + e.getMessage()); + } default: return Collections.singletonMap("error", "Undefined ID: " + id.name()); } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/LocaleJSONResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/LocaleJSONResolver.java new file mode 100644 index 000000000..722a1188a --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/LocaleJSONResolver.java @@ -0,0 +1,191 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.webserver.resolver.json; + +import com.djrapitops.plan.delivery.web.resolver.MimeType; +import com.djrapitops.plan.delivery.web.resolver.NoAuthResolver; +import com.djrapitops.plan.delivery.web.resolver.Response; +import com.djrapitops.plan.delivery.web.resolver.ResponseBuilder; +import com.djrapitops.plan.delivery.web.resolver.request.Request; +import com.djrapitops.plan.delivery.webserver.Addresses; +import com.djrapitops.plan.settings.config.Config; +import com.djrapitops.plan.settings.config.ConfigNode; +import com.djrapitops.plan.settings.config.ConfigReader; +import com.djrapitops.plan.settings.locale.LangCode; +import com.djrapitops.plan.settings.locale.Locale; +import com.djrapitops.plan.settings.locale.LocaleSystem; +import com.djrapitops.plan.settings.locale.lang.Lang; +import com.djrapitops.plan.storage.file.PlanFiles; +import com.djrapitops.plan.storage.file.Resource; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.*; + +/** + * Resolves JSON requests for /v1/locale and /v1/locale/{@link LangCode#toString()}. + * + * @author Kopo942 + */ +@Singleton +@Path("/v1/locale/{langCode}") +public class LocaleJSONResolver implements NoAuthResolver { + + private final LocaleSystem localeSystem; + private final Locale locale; + private final PlanFiles files; + private final Addresses addresses; + + @Inject + public LocaleJSONResolver( + LocaleSystem localeSystem, + Locale locale, + PlanFiles files, + Addresses addresses + ) { + this.localeSystem = localeSystem; + this.locale = locale; + this.files = files; + this.addresses = addresses; + } + + @GET + @Operation( + responses = { + @ApiResponse(responseCode = "200 (/locale)", description = "List of available locales", content = @Content(mediaType = MimeType.JSON, examples = { + @ExampleObject("{\"defaultLanguage\": \"EN\", \"languages\": {\"EN\": \"English\", \"FI\": \"Finnish\"}, \"languageVersions\": {\"EN\": 1657189514266, \"FI\": 1657189514266}}") + })), + @ApiResponse(responseCode = "200 (/locale/{langCode})", description = "Contents of the locale.json file matching given langCode"), + @ApiResponse(responseCode = "404", description = "Language by langCode was not found") + }, + parameters = { + @Parameter(in = ParameterIn.PATH, name = "langCode", description = "Language code. NOT REQUIRED. /v1/locale lists available language codes.", allowEmptyValue = true, example = "/v1/locale/EN") + }, + requestBody = @RequestBody(content = @Content(examples = @ExampleObject())) + ) + @Override + public Optional resolve(Request request) { + return Optional.of(getResponse(request)); + } + + private Response getResponse(Request request) { + ResponseBuilder builder = Response.builder(); + + Optional langCode = request.getPath().getPart(1); + Map json = langCode + .map(this::getLocaleJSON) + .orElseGet(this::getLanguageListJSON); + + if (!json.isEmpty()) { + return builder.setJSONContent(json).build(); + } else { + String address = addresses.getMainAddress().orElse(addresses.getFallbackLocalhostAddress()); + return builder.setStatus(404) + .setJSONContent("{\n" + + " \"message\": \"Language not found, see " + address + + "/v1/locale for available language codes.\"\n" + + "}") + .build(); + } + } + + private Map getLanguageListJSON() { + Map json = new HashMap<>(); + Map languages = new TreeMap<>(); + Map languageVersions = new TreeMap<>(); + + long maxLocaleVersion = localeSystem.getMaxLocaleVersion(); + Optional customLocaleVersion = localeSystem.getCustomLocaleVersion(); + + for (LangCode lang : LangCode.values()) { + if (lang == LangCode.CUSTOM && locale.getLangCode() != LangCode.CUSTOM) continue; + languages.put(lang.toString(), lang.getName()); + long localeVersion = localeSystem.getLocaleVersion(lang).orElse(maxLocaleVersion); + languageVersions.put(lang.toString(), localeVersion); + } + customLocaleVersion.ifPresent(version -> languageVersions.put(LangCode.CUSTOM.toString(), version)); + + json.put("defaultLanguage", locale.getLangCode().toString()); + json.put("languages", languages); + json.put("languageVersions", languageVersions); + + return json; + } + + private Map getLocaleJSON(String langCode) { + try { + LangCode code = LangCode.valueOf(langCode.toUpperCase()); + Map json = new TreeMap<>(); + Resource file; + + if (code == LangCode.CUSTOM) { + if (locale.getLangCode() != LangCode.CUSTOM || !files.getFileFromPluginFolder("locale.yml").exists()) { + return json; + } + file = files.getResourceFromPluginFolder("locale.yml"); + } else { + file = files.getResourceFromJar("locale/" + code.getFileName()); + } + + return dfs(loadLocale(file), json); + } catch (IllegalArgumentException noSuchEnum) { + return Collections.emptyMap(); + } catch (IOException dfsFileLookupError) { + throw new UncheckedIOException(dfsFileLookupError); + } + } + + private Config loadLocale(Resource resource) throws IOException { + try (ConfigReader reader = new ConfigReader(resource.asInputStream())) { + Config config = reader.read(); + addMissingKeys(config); + return config; + } + } + + private void addMissingKeys(Config config) { + for (Map.Entry entry : LocaleSystem.getKeys().entrySet()) { + String key = entry.getKey(); + if (config.contains(key)) continue; + config.set(key, locale.getString(entry.getValue())); + } + } + + private Map dfs(ConfigNode node, Map parent) { + for (ConfigNode child : node.getChildren()) { + if (!child.isLeafNode()) { + Map childMap = new TreeMap<>(); + parent.put(child.getKey(false), childMap); + dfs(child, childMap); + } else { + parent.put(child.getKey(false), child.getString()); + } + } + return parent; + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/MetadataJSONResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/MetadataJSONResolver.java new file mode 100644 index 000000000..41e847a3a --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/MetadataJSONResolver.java @@ -0,0 +1,93 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.webserver.resolver.json; + +import com.djrapitops.plan.delivery.rendering.html.Contributors; +import com.djrapitops.plan.delivery.web.resolver.NoAuthResolver; +import com.djrapitops.plan.delivery.web.resolver.Response; +import com.djrapitops.plan.delivery.web.resolver.request.Request; +import com.djrapitops.plan.identification.ServerInfo; +import com.djrapitops.plan.settings.config.PlanConfig; +import com.djrapitops.plan.settings.config.paths.DisplaySettings; +import com.djrapitops.plan.settings.config.paths.ProxySettings; +import com.djrapitops.plan.settings.config.paths.WebserverSettings; +import com.djrapitops.plan.settings.theme.Theme; +import com.djrapitops.plan.settings.theme.ThemeVal; +import com.djrapitops.plan.utilities.java.Maps; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import java.util.Optional; + +@Singleton +@Path("/v1/metadata") +public class MetadataJSONResolver implements NoAuthResolver { + + private final String mainCommand; + private final PlanConfig config; + private final Theme theme; + private final ServerInfo serverInfo; + + @Inject + public MetadataJSONResolver( + @Named("mainCommandName") String mainCommand, + PlanConfig config, + Theme theme, + ServerInfo serverInfo + ) { + this.mainCommand = mainCommand; + this.config = config; + // Dagger inject constructor + this.theme = theme; + this.serverInfo = serverInfo; + } + + @GET + @Operation( + description = "Get metadata required for displaying Plan React frontend", + requestBody = @RequestBody(content = @Content(examples = @ExampleObject())) + ) + @Override + public Optional resolve(Request request) { + return Optional.of(getResponse()); + } + + private Response getResponse() { + return Response.builder() + .setJSONContent(Maps.builder(String.class, Object.class) + .put("timestamp", System.currentTimeMillis()) + .put("contributors", Contributors.getContributors()) + .put("defaultTheme", config.get(DisplaySettings.THEME)) + .put("gmPieColors", theme.getPieColors(ThemeVal.GRAPH_GM_PIE)) + .put("playerHeadImageUrl", config.get(DisplaySettings.PLAYER_HEAD_IMG_URL)) + .put("isProxy", serverInfo.getServer().isProxy()) + .put("serverName", serverInfo.getServer().getIdentifiableName()) + .put("serverUUID", serverInfo.getServer().getUuid().toString()) + .put("networkName", serverInfo.getServer().isProxy() ? config.get(ProxySettings.NETWORK_NAME) : null) + .put("mainCommand", mainCommand) + .put("refreshBarrierMs", config.get(WebserverSettings.REDUCED_REFRESH_BARRIER)) + .build()) + .build(); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/NetworkJSONResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/NetworkJSONResolver.java index cbd7a4bd0..203c9c156 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/NetworkJSONResolver.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/NetworkJSONResolver.java @@ -44,7 +44,8 @@ public class NetworkJSONResolver { AsyncJSONResolverService asyncJSONResolverService, JSONFactory jsonFactory, NetworkOverviewJSONCreator networkOverviewJSONCreator, NetworkPlayerBaseOverviewJSONCreator networkPlayerBaseOverviewJSONCreator, - NetworkSessionsOverviewJSONCreator networkSessionsOverviewJSONCreator + NetworkSessionsOverviewJSONCreator networkSessionsOverviewJSONCreator, + NetworkPerformanceJSONResolver networkPerformanceJSONResolver ) { this.asyncJSONResolverService = asyncJSONResolverService; resolver = CompositeResolver.builder() @@ -53,6 +54,9 @@ public class NetworkJSONResolver { .add("sessionsOverview", forJSON(DataID.SESSIONS_OVERVIEW, networkSessionsOverviewJSONCreator)) .add("servers", forJSON(DataID.SERVERS, jsonFactory::serversAsJSONMaps)) .add("pingTable", forJSON(DataID.PING_TABLE, jsonFactory::pingPerGeolocation)) + .add("listServers", forJSON(DataID.LIST_SERVERS, jsonFactory::listServers)) + .add("serverOptions", forJSON(DataID.LIST_SERVERS, jsonFactory::listServers)) + .add("performanceOverview", networkPerformanceJSONResolver) .build(); } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/NetworkMetadataJSONResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/NetworkMetadataJSONResolver.java new file mode 100644 index 000000000..1e8dbdd19 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/NetworkMetadataJSONResolver.java @@ -0,0 +1,82 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.webserver.resolver.json; + +import com.djrapitops.plan.delivery.domain.datatransfer.ServerDto; +import com.djrapitops.plan.delivery.web.resolver.Resolver; +import com.djrapitops.plan.delivery.web.resolver.Response; +import com.djrapitops.plan.delivery.web.resolver.request.Request; +import com.djrapitops.plan.delivery.web.resolver.request.WebUser; +import com.djrapitops.plan.identification.ServerInfo; +import com.djrapitops.plan.storage.database.DBSystem; +import com.djrapitops.plan.storage.database.queries.objects.ServerQueries; +import com.djrapitops.plan.utilities.java.Maps; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * @author AuroraLS3 + */ +@Singleton +@Path("/v1/networkMetadata") +public class NetworkMetadataJSONResolver implements Resolver { + + private final ServerInfo serverInfo; + private final DBSystem dbSystem; + + @Inject + public NetworkMetadataJSONResolver(ServerInfo serverInfo, DBSystem dbSystem) { + this.serverInfo = serverInfo; + this.dbSystem = dbSystem; + } + + @Override + public boolean canAccess(Request request) { + return request.getUser().orElse(new WebUser("")).hasPermission("page.network"); + } + + @GET + @Operation( + description = "Get metadata about the network such as list of servers.", + requestBody = @RequestBody(content = @Content(examples = @ExampleObject())) + ) + @Override + public Optional resolve(Request request) { + return Optional.of(getResponse()); + } + + private Response getResponse() { + return Response.builder() + .setJSONContent(Maps.builder(String.class, Object.class) + .put("servers", dbSystem.getDatabase().query(ServerQueries.fetchPlanServerInformationCollection()) + .stream().map(ServerDto::fromServer) + .sorted() + .collect(Collectors.toList())) + .put("currentServer", ServerDto.fromServer(serverInfo.getServer())) + .build()) + .build(); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/NetworkPerformanceJSONResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/NetworkPerformanceJSONResolver.java new file mode 100644 index 000000000..2f5034317 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/NetworkPerformanceJSONResolver.java @@ -0,0 +1,224 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.webserver.resolver.json; + +import com.djrapitops.plan.delivery.domain.mutators.TPSMutator; +import com.djrapitops.plan.delivery.formatting.Formatter; +import com.djrapitops.plan.delivery.formatting.Formatters; +import com.djrapitops.plan.delivery.web.resolver.MimeType; +import com.djrapitops.plan.delivery.web.resolver.Resolver; +import com.djrapitops.plan.delivery.web.resolver.Response; +import com.djrapitops.plan.delivery.web.resolver.request.Request; +import com.djrapitops.plan.delivery.web.resolver.request.WebUser; +import com.djrapitops.plan.gathering.domain.TPS; +import com.djrapitops.plan.identification.ServerUUID; +import com.djrapitops.plan.settings.config.PlanConfig; +import com.djrapitops.plan.settings.config.paths.DisplaySettings; +import com.djrapitops.plan.settings.locale.Locale; +import com.djrapitops.plan.settings.locale.lang.GenericLang; +import com.djrapitops.plan.storage.database.DBSystem; +import com.djrapitops.plan.storage.database.Database; +import com.djrapitops.plan.storage.database.queries.objects.TPSQueries; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * Creates JSON payload for /server-page Performance tab. + * + * @author AuroraLS3 + */ +@Singleton +@Path("/v1/network/performanceOverview") +public class NetworkPerformanceJSONResolver implements Resolver { + + private final PlanConfig config; + private final Locale locale; + private final DBSystem dbSystem; + + private final Formatter decimals; + private final Formatter timeAmount; + private final Formatter percentage; + private final Formatter byteSize; + private final Gson gson; + + @Inject + public NetworkPerformanceJSONResolver( + PlanConfig config, + Locale locale, + DBSystem dbSystem, + Formatters formatters, + Gson gson + ) { + this.config = config; + this.locale = locale; + this.dbSystem = dbSystem; + + decimals = formatters.decimals(); + percentage = formatters.percentage(); + timeAmount = formatters.timeAmount(); + byteSize = formatters.byteSize(); + this.gson = gson; + } + + @Override + public boolean canAccess(Request request) { + return request.getUser().orElse(new WebUser("")).hasPermission("page.network"); + } + + @GET + @Operation( + description = "Get performance overview information for multiple servers", + responses = { + @ApiResponse(responseCode = "200", content = @Content(mediaType = MimeType.JSON, examples = { + @ExampleObject("{\"numbers\": {}}") + })) + }, + parameters = { + @Parameter(in = ParameterIn.QUERY, name = "servers", required = true, description = "JSON list of server uuids (URI encoded)", example = "%5B%22a779e107-0474-4d9f-8f4d-f1efb068d32e%22%5D (is [\"a779e107-0474-4d9f-8f4d-f1efb068d32e\"])") + }, + requestBody = @RequestBody(content = @Content(examples = @ExampleObject())) + ) + @Override + public Optional resolve(Request request) { + List serverUUIDs = request.getQuery().get("servers") + .map(this::getUUIDList) + .orElse(Collections.emptyList()) + .stream().map(ServerUUID::from) + .collect(Collectors.toList()); + return Optional.of(Response.builder() + .setJSONContent(createJSONAsMap(serverUUIDs)) + .build()); + } + + private List getUUIDList(String jsonString) { + return gson.fromJson(jsonString, new TypeToken>() {}.getType()); + } + + public Map createJSONAsMap(Collection serverUUIDs) { + Map serverOverview = new HashMap<>(); + Database db = dbSystem.getDatabase(); + long now = System.currentTimeMillis(); + long monthAgo = now - TimeUnit.DAYS.toMillis(30L); + Map> tpsData = db.query(TPSQueries.fetchTPSDataOfServers(monthAgo, now, serverUUIDs)); + + serverOverview.put("numbers", createNumbersMap(tpsData)); + return serverOverview; + } + + private Map createNumbersMap(Map> tpsData) { + long now = System.currentTimeMillis(); + long dayAgo = now - TimeUnit.DAYS.toMillis(1L); + long weekAgo = now - TimeUnit.DAYS.toMillis(7L); + + Map numbers = new HashMap<>(); + + List tpsDataOfAllServers = new ArrayList<>(); + tpsData.values().forEach(tpsDataOfAllServers::addAll); + TPSMutator tpsDataMonth = new TPSMutator(tpsDataOfAllServers); + TPSMutator tpsDataWeek = tpsDataMonth.filterDataBetween(weekAgo, now); + TPSMutator tpsDataDay = tpsDataWeek.filterDataBetween(dayAgo, now); + + Map mutatorsOfServersMonth = new HashMap<>(); + Map mutatorsOfServersWeek = new HashMap<>(); + Map mutatorsOfServersDay = new HashMap<>(); + for (Map.Entry> entry : tpsData.entrySet()) { + TPSMutator mutator = new TPSMutator(entry.getValue()); + mutatorsOfServersMonth.put(entry.getKey(), mutator); + mutatorsOfServersWeek.put(entry.getKey(), mutator.filterDataBetween(weekAgo, now)); + mutatorsOfServersDay.put(entry.getKey(), mutator.filterDataBetween(dayAgo, now)); + } + + Double tpsThreshold = config.get(DisplaySettings.GRAPH_TPS_THRESHOLD_MED); + numbers.put("low_tps_spikes_30d", tpsDataMonth.lowTpsSpikeCount(tpsThreshold)); + numbers.put("low_tps_spikes_7d", tpsDataWeek.lowTpsSpikeCount(tpsThreshold)); + numbers.put("low_tps_spikes_24h", tpsDataDay.lowTpsSpikeCount(tpsThreshold)); + + long downtimeMonth = getTotalDowntime(mutatorsOfServersMonth); + long downtimeWeek = getTotalDowntime(mutatorsOfServersWeek); + long downtimeDay = getTotalDowntime(mutatorsOfServersDay); + numbers.put("server_downtime_30d", timeAmount.apply(downtimeMonth)); + numbers.put("server_downtime_7d", timeAmount.apply(downtimeWeek)); + numbers.put("server_downtime_24h", timeAmount.apply(downtimeDay)); + + if (!tpsData.isEmpty()) { + numbers.put("avg_server_downtime_30d", timeAmount.apply(downtimeMonth / tpsData.size())); + numbers.put("avg_server_downtime_7d", timeAmount.apply(downtimeWeek / tpsData.size())); + numbers.put("avg_server_downtime_24h", timeAmount.apply(downtimeDay / tpsData.size())); + } else { + numbers.put("avg_server_downtime_30d", "-"); + numbers.put("avg_server_downtime_7d", "-"); + numbers.put("avg_server_downtime_24h", "-"); + } + + numbers.put("players_30d", format(tpsDataMonth.averagePlayers())); + numbers.put("players_7d", format(tpsDataWeek.averagePlayers())); + numbers.put("players_24h", format(tpsDataDay.averagePlayers())); + numbers.put("tps_30d", format(tpsDataMonth.averageTPS())); + numbers.put("tps_7d", format(tpsDataWeek.averageTPS())); + numbers.put("tps_24h", format(tpsDataDay.averageTPS())); + numbers.put("cpu_30d", formatPercentage(tpsDataMonth.averageCPU())); + numbers.put("cpu_7d", formatPercentage(tpsDataWeek.averageCPU())); + numbers.put("cpu_24h", formatPercentage(tpsDataDay.averageCPU())); + numbers.put("ram_30d", formatBytes(tpsDataMonth.averageRAM())); + numbers.put("ram_7d", formatBytes(tpsDataWeek.averageRAM())); + numbers.put("ram_24h", formatBytes(tpsDataDay.averageRAM())); + numbers.put("entities_30d", format((int) tpsDataMonth.averageEntities())); + numbers.put("entities_7d", format((int) tpsDataWeek.averageEntities())); + numbers.put("entities_24h", format((int) tpsDataDay.averageEntities())); + numbers.put("chunks_30d", format((int) tpsDataMonth.averageChunks())); + numbers.put("chunks_7d", format((int) tpsDataWeek.averageChunks())); + numbers.put("chunks_24h", format((int) tpsDataDay.averageChunks())); + + return numbers; + } + + private long getTotalDowntime(Map mutatorsOfServersMonth) { + long downTime = 0L; + for (TPSMutator tpsMutator : mutatorsOfServersMonth.values()) { + downTime += tpsMutator.serverDownTime(); + } + return downTime; + } + + private String format(double value) { + return value != -1 ? decimals.apply(value) : locale.get(GenericLang.UNAVAILABLE).toString(); + } + + private String formatBytes(double value) { + return value != -1 ? byteSize.apply(value) : locale.get(GenericLang.UNAVAILABLE).toString(); + } + + private String formatPercentage(double value) { + return value != -1 ? percentage.apply(value / 100.0) : locale.get(GenericLang.UNAVAILABLE).toString(); + } + +} \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/NetworkTabJSONResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/NetworkTabJSONResolver.java index ce8988b4f..eac329888 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/NetworkTabJSONResolver.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/NetworkTabJSONResolver.java @@ -24,7 +24,9 @@ import com.djrapitops.plan.delivery.web.resolver.request.Request; import com.djrapitops.plan.delivery.web.resolver.request.WebUser; import com.djrapitops.plan.delivery.webserver.cache.AsyncJSONResolverService; import com.djrapitops.plan.delivery.webserver.cache.DataID; +import com.djrapitops.plan.delivery.webserver.cache.JSONStorage; import com.djrapitops.plan.identification.Identifiers; +import com.djrapitops.plan.utilities.java.Maps; import java.util.Optional; import java.util.function.Supplier; @@ -60,9 +62,19 @@ public class NetworkTabJSONResolver implements Resolver { } private Response getResponse(Request request) { + JSONStorage.StoredJSON json = asyncJSONResolverService.resolve(Identifiers.getTimestamp(request), dataID, jsonCreator); + if (json == null) { + return Response.builder() + .setMimeType(MimeType.JSON) + .setJSONContent(Maps.builder(String.class, String.class) + .put("error", "Json failed to generate for some reason, see /Plan/logs for errors") + .build()) + .build(); + } + return Response.builder() .setMimeType(MimeType.JSON) - .setJSONContent(asyncJSONResolverService.resolve(Identifiers.getTimestamp(request), dataID, jsonCreator).json) + .setJSONContent(json.json) .build(); } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/PlayerJSONResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/PlayerJSONResolver.java index 43040a452..76a0fab9b 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/PlayerJSONResolver.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/PlayerJSONResolver.java @@ -24,6 +24,15 @@ import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException; import com.djrapitops.plan.delivery.web.resolver.request.Request; import com.djrapitops.plan.delivery.web.resolver.request.WebUser; import com.djrapitops.plan.identification.Identifiers; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; import javax.inject.Inject; import javax.inject.Singleton; @@ -31,6 +40,7 @@ import java.util.Optional; import java.util.UUID; @Singleton +@Path("/v1/player") public class PlayerJSONResolver implements Resolver { private final Identifiers identifiers; @@ -58,6 +68,19 @@ public class PlayerJSONResolver implements Resolver { return false; } + @GET + @Operation( + description = "Get player data for visualizing a single player", + responses = { + @ApiResponse(responseCode = "200", content = @Content(mediaType = MimeType.JSON)), + @ApiResponse(responseCode = "400", description = "If 'player' parameter is not given") + }, + parameters = @Parameter(in = ParameterIn.QUERY, name = "player", description = "Identifier for the player", examples = { + @ExampleObject("dade56b7-366a-495a-a087-5bf0178536d4"), + @ExampleObject("AuroraLS3"), + }), + requestBody = @RequestBody(content = @Content(examples = @ExampleObject())) + ) @Override public Optional resolve(Request request) { return Optional.of(getResponse(request)); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/PlayerKillsJSONResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/PlayerKillsJSONResolver.java index 0b797dc70..922448adc 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/PlayerKillsJSONResolver.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/PlayerKillsJSONResolver.java @@ -27,6 +27,15 @@ import com.djrapitops.plan.delivery.webserver.cache.DataID; import com.djrapitops.plan.delivery.webserver.cache.JSONStorage; import com.djrapitops.plan.identification.Identifiers; import com.djrapitops.plan.identification.ServerUUID; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; import javax.inject.Inject; import javax.inject.Singleton; @@ -39,6 +48,7 @@ import java.util.Optional; * @author AuroraLS3 */ @Singleton +@Path("/v1/kills") public class PlayerKillsJSONResolver implements Resolver { private final Identifiers identifiers; @@ -61,6 +71,23 @@ public class PlayerKillsJSONResolver implements Resolver { return request.getUser().orElse(new WebUser("")).hasPermission("page.server"); } + @GET + @Operation( + description = "Get player kill data for a server", + responses = { + @ApiResponse(responseCode = "200", content = @Content(mediaType = MimeType.JSON, examples = { + @ExampleObject("{\"player_kills\": []}") + })), + @ApiResponse(responseCode = "400 (no parameter)", description = "If 'server' parameter is not given"), + @ApiResponse(responseCode = "400 (no match)", description = "If 'server' parameter does not match an existing server") + }, + parameters = @Parameter(in = ParameterIn.QUERY, name = "server", description = "Identifier for the server", examples = { + @ExampleObject("dade56b7-366a-495a-a087-5bf0178536d4"), + @ExampleObject("Server 1"), + @ExampleObject("1"), + }), + requestBody = @RequestBody(content = @Content(examples = @ExampleObject())) + ) @Override public Optional resolve(Request request) { return Optional.of(getResponse(request)); @@ -68,9 +95,9 @@ public class PlayerKillsJSONResolver implements Resolver { private Response getResponse(Request request) { ServerUUID serverUUID = identifiers.getServerUUID(request); - long timestamp = Identifiers.getTimestamp(request); + Optional timestamp = Identifiers.getTimestamp(request); JSONStorage.StoredJSON storedJSON = jsonResolverService.resolve(timestamp, DataID.KILLS, serverUUID, - theUUID -> Collections.singletonMap("player_kills", jsonFactory.serverPlayerKillsAsJSONMap(theUUID)) + theUUID -> Collections.singletonMap("player_kills", jsonFactory.serverPlayerKillsAsJSONMaps(theUUID)) ); return Response.builder() .setMimeType(MimeType.JSON) diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/PlayersTableJSONResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/PlayersTableJSONResolver.java index 3e17cf30f..83267bdff 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/PlayersTableJSONResolver.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/PlayersTableJSONResolver.java @@ -27,6 +27,15 @@ import com.djrapitops.plan.delivery.webserver.cache.DataID; import com.djrapitops.plan.delivery.webserver.cache.JSONStorage; import com.djrapitops.plan.identification.Identifiers; import com.djrapitops.plan.identification.ServerUUID; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; import javax.inject.Inject; import javax.inject.Singleton; @@ -38,6 +47,7 @@ import java.util.Optional; * @author AuroraLS3 */ @Singleton +@Path("/v1/players") public class PlayersTableJSONResolver implements Resolver { private final Identifiers identifiers; @@ -65,6 +75,19 @@ public class PlayersTableJSONResolver implements Resolver { return user.hasPermission("page.players"); } + @GET + @Operation( + description = "Get player table data for /players page or a server", + responses = { + @ApiResponse(responseCode = "200", content = @Content(mediaType = MimeType.JSON)), + }, + parameters = @Parameter(in = ParameterIn.QUERY, name = "server", description = "Server identifier to get data for (optional)", examples = { + @ExampleObject("Server 1"), + @ExampleObject("1"), + @ExampleObject("1fb39d2a-eb82-4868-b245-1fad17d823b3"), + }), + requestBody = @RequestBody(content = @Content(examples = @ExampleObject())) + ) @Override public Optional resolve(Request request) { return Optional.of(getResponse(request)); @@ -78,7 +101,7 @@ public class PlayersTableJSONResolver implements Resolver { } private JSONStorage.StoredJSON getStoredJSON(Request request) { - long timestamp = Identifiers.getTimestamp(request); + Optional timestamp = Identifiers.getTimestamp(request); JSONStorage.StoredJSON storedJSON; if (request.getQuery().get("server").isPresent()) { ServerUUID serverUUID = identifiers.getServerUUID(request); // Can throw BadRequestException diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/QueryJSONResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/QueryJSONResolver.java index 3ca615b44..c0d3bf81f 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/QueryJSONResolver.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/QueryJSONResolver.java @@ -17,6 +17,9 @@ package com.djrapitops.plan.delivery.webserver.resolver.json; import com.djrapitops.plan.delivery.domain.DateMap; +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; +import com.djrapitops.plan.delivery.domain.datatransfer.InputQueryDto; +import com.djrapitops.plan.delivery.domain.datatransfer.ViewDto; import com.djrapitops.plan.delivery.formatting.Formatter; import com.djrapitops.plan.delivery.formatting.Formatters; import com.djrapitops.plan.delivery.rendering.json.PlayersTableJSONCreator; @@ -27,9 +30,11 @@ import com.djrapitops.plan.delivery.web.resolver.Response; import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException; import com.djrapitops.plan.delivery.web.resolver.request.Request; import com.djrapitops.plan.delivery.web.resolver.request.WebUser; +import com.djrapitops.plan.delivery.webserver.RequestBodyConverter; import com.djrapitops.plan.delivery.webserver.cache.JSONStorage; import com.djrapitops.plan.extension.implementation.storage.queries.ExtensionQueryResultTableDataQuery; import com.djrapitops.plan.identification.ServerInfo; +import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.settings.config.PlanConfig; import com.djrapitops.plan.settings.config.paths.DisplaySettings; import com.djrapitops.plan.settings.config.paths.TimeSettings; @@ -39,23 +44,32 @@ import com.djrapitops.plan.storage.database.Database; import com.djrapitops.plan.storage.database.queries.analysis.NetworkActivityIndexQueries; import com.djrapitops.plan.storage.database.queries.filter.Filter; import com.djrapitops.plan.storage.database.queries.filter.QueryFilters; -import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation; import com.djrapitops.plan.storage.database.queries.objects.GeoInfoQueries; import com.djrapitops.plan.storage.database.queries.objects.SessionQueries; import com.djrapitops.plan.storage.database.queries.objects.playertable.QueryTablePlayersQuery; import com.djrapitops.plan.utilities.java.Maps; import com.google.gson.Gson; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; import net.playeranalytics.plugin.scheduling.TimeAmount; import javax.inject.Inject; import javax.inject.Singleton; import java.io.IOException; import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.*; @Singleton +@Path("/v1/query") public class QueryJSONResolver implements Resolver { private final QueryFilters filters; @@ -67,6 +81,7 @@ public class QueryJSONResolver implements Resolver { private final GraphJSONCreator graphJSONCreator; private final Locale locale; private final Formatters formatters; + private final Gson gson; @Inject public QueryJSONResolver( @@ -76,7 +91,8 @@ public class QueryJSONResolver implements Resolver { ServerInfo serverInfo, JSONStorage jsonStorage, GraphJSONCreator graphJSONCreator, Locale locale, - Formatters formatters + Formatters formatters, + Gson gson ) { this.filters = filters; this.config = config; @@ -86,6 +102,7 @@ public class QueryJSONResolver implements Resolver { this.graphJSONCreator = graphJSONCreator; this.locale = locale; this.formatters = formatters; + this.gson = gson; } @Override @@ -94,6 +111,21 @@ public class QueryJSONResolver implements Resolver { return user.hasPermission("page.players"); } + @GET + @Operation( + description = "Perform a query or get cached results. Use q to do new query, timestamp to see cached query.", + responses = { + @ApiResponse(responseCode = "200", content = @Content(mediaType = MimeType.JSON)), + @ApiResponse(responseCode = "400 (invalid view)", description = "If 'view' date formats does not match afterDate dd/mm/yyyy, afterTime hh:mm, beforeDate dd/mm/yyyy, beforeTime hh:mm"), + @ApiResponse(responseCode = "400 (no query)", description = "If request body is empty and 'q' request parameter is not given"), + @ApiResponse(responseCode = "400 (invalid query)", description = "If request body is empty and 'q' json request parameter doesn't contain 'view' property"), + }, + parameters = { + @Parameter(in = ParameterIn.QUERY, name = "timestamp", description = "Epoch millisecond for cached query"), + @Parameter(in = ParameterIn.QUERY, name = "q", description = "URI encoded json, alternative is to POST in request body", schema = @Schema(implementation = InputQueryDto.class)) + }, + requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = InputQueryDto.class))) + ) @Override public Optional resolve(Request request) { return Optional.of(getResponse(request)); @@ -103,17 +135,33 @@ public class QueryJSONResolver implements Resolver { Optional cachedResult = checkForCachedResult(request); if (cachedResult.isPresent()) return cachedResult.get(); + InputQueryDto inputQuery = parseInputQuery(request); + List queries = inputQuery.getFilters(); + + Filter.Result result = filters.apply(queries); + List resultPath = result.getInverseResultPath(); + Collections.reverse(resultPath); + + return buildAndStoreResponse(inputQuery.getView(), result, resultPath); + } + + private InputQueryDto parseInputQuery(Request request) { + if (request.getRequestBody().length == 0) { + return parseInputQueryFromQueryParams(request); + } else { + return RequestBodyConverter.bodyJson(request, gson, InputQueryDto.class); + } + } + + private InputQueryDto parseInputQueryFromQueryParams(Request request) { String q = request.getQuery().get("q").orElseThrow(() -> new BadRequestException("'q' parameter not set (expecting json array)")); - String view = request.getQuery().get("view").orElseThrow(() -> new BadRequestException("'view' parameter not set (expecting json object {afterDate, afterTime, beforeDate, beforeTime})")); - try { - String query = URLDecoder.decode(q, "UTF-8"); - List queries = SpecifiedFilterInformation.parse(query); - Filter.Result result = filters.apply(queries); - List resultPath = result.getInverseResultPath(); - Collections.reverse(resultPath); - - return buildAndStoreResponse(view, result, resultPath); + String query = URLDecoder.decode(q, StandardCharsets.UTF_8); + List queryFilters = InputFilterDto.parse(query, gson); + ViewDto view = request.getQuery().get("view") + .map(viewJson -> gson.fromJson(viewJson, ViewDto.class)) + .orElseThrow(() -> new BadRequestException("'view' parameter not set (expecting json object {afterDate, afterTime, beforeDate, beforeTime})")); + return new InputQueryDto(view, queryFilters); } catch (IOException e) { throw new BadRequestException("Failed to decode json: '" + q + "', " + e.getMessage()); } @@ -132,16 +180,16 @@ public class QueryJSONResolver implements Resolver { } } - private Response buildAndStoreResponse(String view, Filter.Result result, List resultPath) { + private Response buildAndStoreResponse(ViewDto view, Filter.Result result, List resultPath) { try { long timestamp = System.currentTimeMillis(); Map json = Maps.builder(String.class, Object.class) .put("path", resultPath) - .put("view", new Gson().fromJson(view, FiltersJSONResolver.ViewJSON.class)) + .put("view", view) .put("timestamp", timestamp) .build(); if (!result.isEmpty()) { - json.put("data", getDataFor(result.getResultUUIDs(), view)); + json.put("data", getDataFor(result.getResultUserIds(), view)); } JSONStorage.StoredJSON stored = jsonStorage.storeJson("query", json, timestamp); @@ -155,23 +203,22 @@ public class QueryJSONResolver implements Resolver { } } - private Map getDataFor(Set playerUUIDs, String view) throws ParseException { - FiltersJSONResolver.ViewJSON viewJSON = new Gson().fromJson(view, FiltersJSONResolver.ViewJSON.class); - SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy kk:mm"); - long after = dateFormat.parse(viewJSON.afterDate + " " + viewJSON.afterTime).getTime(); - long before = dateFormat.parse(viewJSON.beforeDate + " " + viewJSON.beforeTime).getTime(); + private Map getDataFor(Set userIds, ViewDto view) throws ParseException { + long after = view.getAfterEpochMs(); + long before = view.getBeforeEpochMs(); + List serverUUIDs = view.getServerUUIDs(); return Maps.builder(String.class, Object.class) - .put("players", getPlayersTableData(playerUUIDs, after, before)) - .put("activity", getActivityGraphData(playerUUIDs, after, before)) - .put("geolocation", getGeolocationData(playerUUIDs)) - .put("sessions", getSessionSummaryData(playerUUIDs, after, before)) + .put("players", getPlayersTableData(userIds, serverUUIDs, after, before)) + .put("activity", getActivityGraphData(userIds, serverUUIDs, after, before)) + .put("geolocation", getGeolocationData(userIds)) + .put("sessions", getSessionSummaryData(userIds, serverUUIDs, after, before)) .build(); } - private Map getSessionSummaryData(Set playerUUIDs, long after, long before) { + private Map getSessionSummaryData(Set userIds, List serverUUIDs, long after, long before) { Database database = dbSystem.getDatabase(); - Map summary = database.query(SessionQueries.summaryOfPlayers(playerUUIDs, after, before)); + Map summary = database.query(SessionQueries.summaryOfPlayers(userIds, serverUUIDs, after, before)); Map formattedSummary = new HashMap<>(); Formatter timeAmount = formatters.timeAmount(); for (Map.Entry entry : summary.entrySet()) { @@ -182,14 +229,14 @@ public class QueryJSONResolver implements Resolver { return formattedSummary; } - private Map getGeolocationData(Set playerUUIDs) { + private Map getGeolocationData(Set userIds) { Database database = dbSystem.getDatabase(); return graphJSONCreator.createGeolocationJSON( - database.query(GeoInfoQueries.networkGeolocationCounts(playerUUIDs)) + database.query(GeoInfoQueries.networkGeolocationCounts(userIds)) ); } - private Map getActivityGraphData(Set playerUUIDs, long after, long before) { + private Map getActivityGraphData(Set userIds, List serverUUIDs, long after, long before) { Database database = dbSystem.getDatabase(); Long threshold = config.get(TimeSettings.ACTIVE_PLAY_THRESHOLD); @@ -198,17 +245,17 @@ public class QueryJSONResolver implements Resolver { DateMap> activityData = new DateMap<>(); for (long time = before; time >= stopDate; time -= TimeAmount.WEEK.toMillis(1L)) { - activityData.put(time, database.query(NetworkActivityIndexQueries.fetchActivityIndexGroupingsOn(time, threshold, playerUUIDs))); + activityData.put(time, database.query(NetworkActivityIndexQueries.fetchActivityIndexGroupingsOn(time, threshold, userIds, serverUUIDs))); } return graphJSONCreator.createActivityGraphJSON(activityData); } - private Map getPlayersTableData(Set playerUUIDs, long after, long before) { + private Map getPlayersTableData(Set userIds, List serverUUIDs, long after, long before) { Database database = dbSystem.getDatabase(); return new PlayersTableJSONCreator( - database.query(new QueryTablePlayersQuery(playerUUIDs, after, before, config.get(TimeSettings.ACTIVE_PLAY_THRESHOLD))), - database.query(new ExtensionQueryResultTableDataQuery(serverInfo.getServerUUID(), playerUUIDs)), + database.query(new QueryTablePlayersQuery(userIds, serverUUIDs, after, before, config.get(TimeSettings.ACTIVE_PLAY_THRESHOLD))), + database.query(new ExtensionQueryResultTableDataQuery(serverInfo.getServerUUID(), userIds)), config.get(DisplaySettings.OPEN_PLAYER_LINKS_IN_NEW_TAB), formatters, locale ).toJSONMap(); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/RootJSONResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/RootJSONResolver.java index bc9b65ca7..a2e097ce0 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/RootJSONResolver.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/RootJSONResolver.java @@ -54,11 +54,18 @@ public class RootJSONResolver { PlayerBaseOverviewJSONCreator playerBaseOverviewJSONCreator, PerformanceJSONCreator performanceJSONCreator, ErrorsJSONResolver errorsJSONResolver, + LocaleJSONResolver localeJSONResolver, PlayerJSONResolver playerJSONResolver, NetworkJSONResolver networkJSONResolver, FiltersJSONResolver filtersJSONResolver, - QueryJSONResolver queryJSONResolver + QueryJSONResolver queryJSONResolver, + VersionJSONResolver versionJSONResolver, + MetadataJSONResolver metadataJSONResolver, + NetworkMetadataJSONResolver networkMetadataJSONResolver, + WhoAmIJSONResolver whoAmIJSONResolver, + ServerIdentityJSONResolver serverIdentityJSONResolver, + ExtensionJSONResolver extensionJSONResolver ) { this.identifiers = identifiers; this.asyncJSONResolverService = asyncJSONResolverService; @@ -80,6 +87,13 @@ public class RootJSONResolver { .add("filters", filtersJSONResolver) .add("query", queryJSONResolver) .add("errors", errorsJSONResolver) + .add("version", versionJSONResolver) + .add("locale", localeJSONResolver) + .add("metadata", metadataJSONResolver) + .add("networkMetadata", networkMetadataJSONResolver) + .add("serverIdentity", serverIdentityJSONResolver) + .add("whoami", whoAmIJSONResolver) + .add("extensionData", extensionJSONResolver) .build(); } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/ServerIdentityJSONResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/ServerIdentityJSONResolver.java new file mode 100644 index 000000000..eff9ae5c1 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/ServerIdentityJSONResolver.java @@ -0,0 +1,86 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.webserver.resolver.json; + +import com.djrapitops.plan.delivery.domain.datatransfer.ServerDto; +import com.djrapitops.plan.delivery.rendering.json.JSONFactory; +import com.djrapitops.plan.delivery.web.resolver.MimeType; +import com.djrapitops.plan.delivery.web.resolver.Resolver; +import com.djrapitops.plan.delivery.web.resolver.Response; +import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException; +import com.djrapitops.plan.delivery.web.resolver.exception.NotFoundException; +import com.djrapitops.plan.delivery.web.resolver.request.Request; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.ws.rs.GET; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Optional; + +/** + * @author AuroraLS3 + */ +@Singleton +public class ServerIdentityJSONResolver implements Resolver { + + private final JSONFactory jsonFactory; + + @Inject + public ServerIdentityJSONResolver(JSONFactory jsonFactory) { + this.jsonFactory = jsonFactory; + } + + @Override + public boolean canAccess(Request request) { + return request.getUser() + .map(user -> user.hasPermission("page.server")) + .orElse(false); + } + + @GET + @Operation( + description = "Get server identity for an identifier", + responses = { + @ApiResponse(responseCode = "200", content = @Content(mediaType = MimeType.JSON, schema = @Schema(implementation = ServerDto.class))), + @ApiResponse(responseCode = "400", description = "If 'server' parameter is not given"), + @ApiResponse(responseCode = "404", description = "If 'server' parameter is not an existing server") + }, + parameters = @Parameter(in = ParameterIn.QUERY, required = true, name = "server", description = "Server identifier to get data for", examples = { + @ExampleObject("Server 1"), + @ExampleObject("1"), + @ExampleObject("1fb39d2a-eb82-4868-b245-1fad17d823b3"), + }), + requestBody = @RequestBody(content = @Content(examples = @ExampleObject())) + ) + @Override + public Optional resolve(Request request) { + String serverIdentifier = request.getQuery().get("server") + .orElseThrow(() -> new BadRequestException("Missing 'server' query parameter")); + ServerDto server = jsonFactory.serverForIdentifier(serverIdentifier) + .orElseThrow(() -> new NotFoundException("Server with identifier '" + serverIdentifier + "' was not found in the database")); + return Optional.of(Response.builder() + .setJSONContent(server) + .build()); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/SessionsJSONResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/SessionsJSONResolver.java index ef20212a9..934cd1275 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/SessionsJSONResolver.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/SessionsJSONResolver.java @@ -27,6 +27,15 @@ import com.djrapitops.plan.delivery.webserver.cache.DataID; import com.djrapitops.plan.delivery.webserver.cache.JSONStorage; import com.djrapitops.plan.identification.Identifiers; import com.djrapitops.plan.identification.ServerUUID; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; import javax.inject.Inject; import javax.inject.Singleton; @@ -39,6 +48,7 @@ import java.util.Optional; * @author AuroraLS3 */ @Singleton +@Path("/v1/sessions") public class SessionsJSONResolver implements Resolver { private final Identifiers identifiers; @@ -61,6 +71,20 @@ public class SessionsJSONResolver implements Resolver { return request.getUser().orElse(new WebUser("")).hasPermission("page.server"); } + @GET + @Operation( + description = "Get sessions for a server or whole network", + responses = { + @ApiResponse(responseCode = "200", content = @Content(mediaType = MimeType.JSON)), + @ApiResponse(responseCode = "400", description = "If 'server' parameter is not an existing server") + }, + parameters = @Parameter(in = ParameterIn.QUERY, name = "server", description = "Server identifier to get data for (optional)", examples = { + @ExampleObject("Server 1"), + @ExampleObject("1"), + @ExampleObject("1fb39d2a-eb82-4868-b245-1fad17d823b3"), + }), + requestBody = @RequestBody(content = @Content(examples = @ExampleObject())) + ) @Override public Optional resolve(Request request) { return Optional.of(getResponse(request)); @@ -74,7 +98,7 @@ public class SessionsJSONResolver implements Resolver { } private JSONStorage.StoredJSON getStoredJSON(Request request) { - long timestamp = Identifiers.getTimestamp(request); + Optional timestamp = Identifiers.getTimestamp(request); if (request.getQuery().get("server").isPresent()) { ServerUUID serverUUID = identifiers.getServerUUID(request); return jsonResolverService.resolve(timestamp, DataID.SESSIONS, serverUUID, diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/VersionJSONResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/VersionJSONResolver.java new file mode 100644 index 000000000..e9b5521fa --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/VersionJSONResolver.java @@ -0,0 +1,94 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.webserver.resolver.json; + +import com.djrapitops.plan.delivery.web.resolver.MimeType; +import com.djrapitops.plan.delivery.web.resolver.Resolver; +import com.djrapitops.plan.delivery.web.resolver.Response; +import com.djrapitops.plan.delivery.web.resolver.request.Request; +import com.djrapitops.plan.version.VersionChecker; +import com.djrapitops.plan.version.VersionInfo; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import javax.inject.Inject; +import javax.inject.Named; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * Resolves requests for /v1/version. + * + * @author Kopo942 + */ +@Path("/v1/version") +public class VersionJSONResolver implements Resolver { + + private final VersionChecker versionChecker; + private final String currentVersion; + + @Inject + public VersionJSONResolver( + @Named("currentVersion") String currentVersion, + VersionChecker versionChecker + ) { + this.currentVersion = currentVersion; + this.versionChecker = versionChecker; + } + + @Override + public boolean canAccess(Request request) { + return true; + } + + @GET + @Operation( + description = "Get Plan version and update information", + responses = { + @ApiResponse(responseCode = "200", content = @Content(mediaType = MimeType.JSON)), + }, + requestBody = @RequestBody(content = @Content(examples = @ExampleObject())) + ) + @Override + public Optional resolve(Request request) { + return Optional.of(getResponse()); + } + + private Response getResponse() { + Map json = new HashMap<>(); + Optional newVersion = versionChecker.getNewVersionAvailable(); + boolean updateAvailable = newVersion.isPresent(); + + json.put("currentVersion", this.currentVersion); + json.put("updateAvailable", updateAvailable); + + if (updateAvailable) { + json.put("newVersion", newVersion.get().getVersion().asString()); + json.put("downloadUrl", newVersion.get().getDownloadUrl()); + json.put("changelogUrl", newVersion.get().getChangeLogUrl()); + json.put("isRelease", newVersion.get().isRelease()); + } + + return Response.builder().setJSONContent(json).build(); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/WhoAmIJSONResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/WhoAmIJSONResolver.java new file mode 100644 index 000000000..af6b357f9 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/WhoAmIJSONResolver.java @@ -0,0 +1,86 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.webserver.resolver.json; + +import com.djrapitops.plan.delivery.web.resolver.MimeType; +import com.djrapitops.plan.delivery.web.resolver.NoAuthResolver; +import com.djrapitops.plan.delivery.web.resolver.Response; +import com.djrapitops.plan.delivery.web.resolver.request.Request; +import com.djrapitops.plan.delivery.web.resolver.request.WebUser; +import com.djrapitops.plan.delivery.webserver.http.WebServer; +import com.djrapitops.plan.utilities.java.Maps; +import dagger.Lazy; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Optional; + +@Singleton +@Path("/v1/whoami") +public class WhoAmIJSONResolver implements NoAuthResolver { + + private final Lazy webServer; + + @Inject + public WhoAmIJSONResolver(Lazy webServer) { + this.webServer = webServer; + } + + @GET + @Operation( + description = "Get information about the currently logged in user", + responses = { + @ApiResponse(responseCode = "200", content = @Content(mediaType = MimeType.JSON, examples = { + @ExampleObject(value = "{\"authRequired\": false, \"loggedIn\": false}", description = "Authentication is disabled"), + @ExampleObject(value = "{\"authRequired\": true, \"loggedIn\": false}", description = "Not logged in"), + @ExampleObject(value = "{\"authRequired\": true, \"loggedIn\": true, \"user\": {}}", description = "Logged in as user"), + })), + }, + requestBody = @RequestBody(content = @Content(examples = @ExampleObject())) + ) + @Override + public Optional resolve(Request request) { + return Optional.of(getResponse(request)); + } + + private Response getResponse(Request request) { + Optional foundUser = request.getUser(); + if (foundUser.isEmpty()) { + return Response.builder() + .setJSONContent(Maps.builder(String.class, Boolean.class) + .put("authRequired", webServer.get().isAuthRequired()) + .put("loggedIn", false) + .build()) + .build(); + } + + return Response.builder() + .setJSONContent(Maps.builder(String.class, Object.class) + .put("authRequired", webServer.get().isAuthRequired()) + .put("loggedIn", true) + .put("user", foundUser.get()) + .build()) + .build(); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/swagger/SwaggerJsonResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/swagger/SwaggerJsonResolver.java new file mode 100644 index 000000000..21e649147 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/swagger/SwaggerJsonResolver.java @@ -0,0 +1,49 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.webserver.resolver.swagger; + +import com.djrapitops.plan.delivery.web.resolver.Resolver; +import com.djrapitops.plan.delivery.web.resolver.Response; +import com.djrapitops.plan.delivery.web.resolver.request.Request; +import com.djrapitops.plan.delivery.webserver.ResponseFactory; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Optional; + +@Singleton +public class SwaggerJsonResolver implements Resolver { + + private final ResponseFactory responseFactory; + + @Inject + public SwaggerJsonResolver(ResponseFactory responseFactory) { + this.responseFactory = responseFactory; + } + + @Override + public boolean canAccess(Request request) { + return request.getUser() + .filter(user -> user.hasPermission("page.server")) + .isPresent(); + } + + @Override + public Optional resolve(Request request) { + return Optional.of(responseFactory.jsonFileResponse("swagger.json")); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/swagger/SwaggerPageResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/swagger/SwaggerPageResolver.java new file mode 100644 index 000000000..294c277dc --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/swagger/SwaggerPageResolver.java @@ -0,0 +1,49 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.webserver.resolver.swagger; + +import com.djrapitops.plan.delivery.web.resolver.Resolver; +import com.djrapitops.plan.delivery.web.resolver.Response; +import com.djrapitops.plan.delivery.web.resolver.request.Request; +import com.djrapitops.plan.delivery.webserver.ResponseFactory; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Optional; + +@Singleton +public class SwaggerPageResolver implements Resolver { + + private final ResponseFactory responseFactory; + + @Inject + public SwaggerPageResolver(ResponseFactory responseFactory) { + this.responseFactory = responseFactory; + } + + @Override + public boolean canAccess(Request request) { + return request.getUser() + .filter(user -> user.hasPermission("page.server")) + .isPresent(); + } + + @Override + public Optional resolve(Request request) { + return Optional.of(responseFactory.reactPageResponse()); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/exceptions/MissingPipelineException.java b/Plan/common/src/main/java/com/djrapitops/plan/exceptions/MissingPipelineException.java new file mode 100644 index 000000000..9f3728b10 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/exceptions/MissingPipelineException.java @@ -0,0 +1,24 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.exceptions; + +public class MissingPipelineException extends IllegalStateException { + + public MissingPipelineException() { + super("A data service pipeline is missing!"); + } +} diff --git a/Plan/common/src/test/java/utilities/mocks/Mocker.java b/Plan/common/src/main/java/com/djrapitops/plan/exceptions/database/DBClosedException.java similarity index 60% rename from Plan/common/src/test/java/utilities/mocks/Mocker.java rename to Plan/common/src/main/java/com/djrapitops/plan/exceptions/database/DBClosedException.java index 679a21e59..7d8ac283a 100644 --- a/Plan/common/src/test/java/utilities/mocks/Mocker.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/exceptions/database/DBClosedException.java @@ -14,27 +14,16 @@ * You should have received a copy of the GNU Lesser General Public License * along with Plan. If not, see . */ -package utilities.mocks; - -import com.djrapitops.plan.PlanPlugin; -import utilities.TestResources; - -import java.io.File; +package com.djrapitops.plan.exceptions.database; /** - * Abstract Mocker for methods that can be used for both Bungee and Bukkit. + * Database is closed and exception is needed to stop execution, not to be logged. * * @author AuroraLS3 */ -abstract class Mocker { +public class DBClosedException extends DBOpException { - PlanPlugin planMock; - - File getFile(String fileName) { - // Read the resource from jar to a temporary file - File file = new File(new File(planMock.getDataFolder(), "jar"), fileName); - TestResources.copyResourceIntoFile(file, fileName); - return file; + public DBClosedException(String message) { + super(message); } - } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/exceptions/database/DBOpException.java b/Plan/common/src/main/java/com/djrapitops/plan/exceptions/database/DBOpException.java index 3af7bcb77..ba6c53e44 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/exceptions/database/DBOpException.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/exceptions/database/DBOpException.java @@ -29,6 +29,7 @@ import java.util.Optional; */ public class DBOpException extends IllegalStateException implements ExceptionWithContext { + public static final String CONSTRAINT_VIOLATION = "Constraint Violation"; private final ErrorContext context; public DBOpException(String message) { @@ -73,6 +74,7 @@ public class DBOpException extends IllegalStateException implements ExceptionWit break; // Duplicate key case 1062: + case 1022: case 23001: case 23505: context.related("Duplicate key") @@ -84,7 +86,6 @@ public class DBOpException extends IllegalStateException implements ExceptionWit case 531: case 787: case 1043: - case 1299: case 1555: case 2579: case 1811: @@ -101,7 +102,7 @@ public class DBOpException extends IllegalStateException implements ExceptionWit case 1364: case 1451: case 1557: - context.related("Constraint Violation") + context.related(CONSTRAINT_VIOLATION) .whatToDo("Report this, there is an SQL Constraint Violation."); break; // Custom rules based on reported errors @@ -110,9 +111,10 @@ public class DBOpException extends IllegalStateException implements ExceptionWit context.related("SQLite file is corrupt.") .whatToDo("SQLite database is corrupt, restore database.db, .db-shm & .db-wal files from a backup, or repair the database: See https://wordpress.semnaitik.com/repair-sqlite-database/."); break; - case 13: + case 13: // SQLite + case 1021: // MariaDB context.related("Disk or temporary directory is full.") - .whatToDo("Disk or temporary directory is full, attempt to clear space in the temporary directory. See https://sqlite.org/rescode.html#full"); + .whatToDo("Disk or temporary directory is full, attempt to clear space in the temporary directory. See https://sqlite.org/rescode.html#full. If you use the Pterodactyl panel, increase the \"tmpfs_size\" config setting. See https://pterodactyl.io/wings/1.0/configuration.html#other-values"); break; case 1104: context.whatToDo("MySQL has too small query limits for the query. SET SQL_BIG_SELECTS=1 or SET MAX_JOIN_SIZE=# (higher number)"); @@ -126,9 +128,24 @@ public class DBOpException extends IllegalStateException implements ExceptionWit break; case 1267: case 1366: + case 1115: context.related("Incorrect character encoding in MySQL") .whatToDo("Convert your MySQL database and tables to use utf8mb4: https://www.a2hosting.com/kb/developer-corner/mysql/convert-mysql-database-utf-8"); break; + case 1299: // SQLite + case 1048: // MySQL or MariaDB + case 1452: + case 1121: + case 1171: + case 1830: + case 1263: + context.related(CONSTRAINT_VIOLATION) + .whatToDo("Report this error. NOT NULL constraint violation occurred."); + break; + case 1071: + context.related("column byte length exceeded") + .whatToDo("Update your MySQL, column key size was exceeded (max key length is 767 bytes in 5.6) - MySQL 5.7 increases the limit."); + break; default: context.related("Unknown SQL Error code"); } @@ -141,4 +158,11 @@ public class DBOpException extends IllegalStateException implements ExceptionWit public Optional getContext() { return Optional.ofNullable(context); } -} \ No newline at end of file + + public boolean isUserIdConstraintViolation() { + return context != null + && context.getRelated().contains(DBOpException.CONSTRAINT_VIOLATION) + && getCause() != null + && getCause().getMessage().contains("user_id"); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/ExtensionSvc.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/ExtensionSvc.java index 135d7bcf4..8ee5bd696 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/extension/ExtensionSvc.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/ExtensionSvc.java @@ -39,6 +39,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; /** * Implementation for {@link ExtensionService}. @@ -58,6 +59,7 @@ public class ExtensionSvc implements ExtensionService { private final ErrorLogger errorLogger; private final Map extensionGatherers; + private final AtomicBoolean enabled; @Inject public ExtensionSvc( @@ -80,6 +82,7 @@ public class ExtensionSvc implements ExtensionService { this.errorLogger = errorLogger; extensionGatherers = new HashMap<>(); + enabled = new AtomicBoolean(true); } public void register() { @@ -88,6 +91,7 @@ public class ExtensionSvc implements ExtensionService { public void registerExtensions() { try { + enabled.set(true); extensionRegister.registerBuiltInExtensions(config.getExtensionSettings().getDisabled()); } catch (IllegalStateException failedToRegisterOne) { ErrorContext.Builder context = ErrorContext.builder() @@ -152,12 +156,14 @@ public class ExtensionSvc implements ExtensionService { } public void updatePlayerValues(UUID playerUUID, String playerName, CallEvents event) { + if (!enabled.get()) return; // Plugin is disabling for (DataValueGatherer gatherer : extensionGatherers.values()) { updatePlayerValues(gatherer, playerUUID, playerName, event); } } public void updatePlayerValues(DataValueGatherer gatherer, UUID playerUUID, String playerName, CallEvents event) { + if (!enabled.get()) return; // Plugin is disabling if (gatherer.shouldSkipEvent(event)) return; if (playerUUID == null && playerName == null) return; @@ -172,14 +178,20 @@ public class ExtensionSvc implements ExtensionService { } public void updateServerValues(CallEvents event) { + if (!enabled.get()) return; // Plugin is disabling for (DataValueGatherer gatherer : extensionGatherers.values()) { updateServerValues(gatherer, event); } } public void updateServerValues(DataValueGatherer gatherer, CallEvents event) { + if (!enabled.get()) return; // Plugin is disabling if (gatherer.shouldSkipEvent(event)) return; gatherer.updateValues(); } + + public void disableUpdates() { + enabled.set(false); + } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/icon/IconAccessor.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/icon/IconAccessor.java new file mode 100644 index 000000000..6c8b9a07a --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/icon/IconAccessor.java @@ -0,0 +1,33 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.extension.icon; + +public class IconAccessor { + + private IconAccessor() { + /* Static access class for package private field */ + } + + public static Integer getId(Icon icon) { + return icon != null ? icon.id : null; + } + + public static void setId(Icon icon, int id) { + icon.id = id; + } + +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/ExtDataBuilder.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/ExtDataBuilder.java index 6fc6722bd..6b8933f07 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/ExtDataBuilder.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/builder/ExtDataBuilder.java @@ -54,7 +54,7 @@ public class ExtDataBuilder implements ExtensionDataBuilder { @Override public ExtensionDataBuilder addValue(Class ofType, Supplier> dataValue) { try { - if (ofType != null && dataValue != null) values.add(new ClassValuePair(ofType, dataValue.get())); + if (ofType != null && dataValue != null) addValue(ofType, dataValue.get()); } catch (NotReadyException | UnsupportedOperationException ignored) { // This exception is ignored by default to allow throwing errors inside the lambda to keep code clean. } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/gathering/DataValueGatherer.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/gathering/DataValueGatherer.java index 8a479007b..593c7e267 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/gathering/DataValueGatherer.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/gathering/DataValueGatherer.java @@ -49,6 +49,7 @@ import java.util.HashSet; import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.concurrent.RejectedExecutionException; /** * Object that can be called to place data about players to the database. @@ -293,6 +294,14 @@ public class DataValueGatherer { } public void updateValues(UUID playerUUID, String playerName) { + try { + tryToUpdateValues(playerUUID, playerName); + } catch (RejectedExecutionException ignore) { + // Database has shut down + } + } + + private void tryToUpdateValues(UUID playerUUID, String playerName) { Parameters parameters = Parameters.player(serverInfo.getServerUUID(), playerUUID, playerName); ExtensionDataBuilder dataBuilder = extension.getExtension().newExtensionDataBuilder(); @@ -305,6 +314,14 @@ public class DataValueGatherer { } public void updateValues() { + try { + tryToUpdateValues(); + } catch (RejectedExecutionException ignore) { + // Database has shut down + } + } + + private void tryToUpdateValues() { Parameters parameters = Parameters.server(serverInfo.getServerUUID()); ExtensionDataBuilder dataBuilder = extension.getExtension().newExtensionDataBuilder(); @@ -362,15 +379,19 @@ public class DataValueGatherer { private void logFailure(Throwable cause, String pluginName, String methodName) { ErrorContext.Builder context = ErrorContext.builder() - .whatToDo("Report and/or disable " + pluginName + " extension in the Plan config.") + .whatToDo(getWhatToDoMessage(pluginName)) .related(pluginName) .related("Method:" + methodName); errorLogger.warn(cause, context.build()); } + private String getWhatToDoMessage(String pluginName) { + return "Report and/or disable " + pluginName + " extension in the Plan config."; + } + private void logFailure(DataExtensionMethodCallException methodCallFailed) { ErrorContext.Builder context = ErrorContext.builder() - .whatToDo("Report and/or disable " + methodCallFailed.getPluginName() + " extension in the Plan config.") + .whatToDo(getWhatToDoMessage(methodCallFailed.getPluginName())) .related(methodCallFailed.getPluginName()) .related("Method:" + methodCallFailed.getMethodName().orElse("-")); errorLogger.warn(methodCallFailed, context.build()); @@ -378,7 +399,7 @@ public class DataValueGatherer { private void logFailure(Throwable unexpectedError) { ErrorContext.Builder context = ErrorContext.builder() - .whatToDo("Report and/or disable " + extension.getPluginName() + " extension in the Plan config.") + .whatToDo(getWhatToDoMessage(extension.getPluginName())) .related(extension.getPluginName()); errorLogger.warn(unexpectedError, context.build()); } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/results/ExtensionData.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/results/ExtensionData.java index 2bb496b91..cbb8f0320 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/results/ExtensionData.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/results/ExtensionData.java @@ -29,12 +29,12 @@ public class ExtensionData implements Comparable { private ExtensionInformation extensionInformation; - private final Map tabs; + private final List tabs; private ExtensionData(int pluginID) { this.pluginID = pluginID; - tabs = new HashMap<>(); + tabs = new ArrayList<>(); } public int getPluginID() { @@ -46,11 +46,11 @@ public class ExtensionData implements Comparable { } public boolean hasOnlyGenericTab() { - return tabs.size() == 1 && tabs.containsKey(""); + return tabs.size() == 1 && "".equals(tabs.get(0).getTabInformation().getTabName()); } public boolean doesNeedWiderSpace() { - for (ExtensionTabData tab : tabs.values()) { + for (ExtensionTabData tab : tabs) { for (ExtensionTableData table : tab.getTableData()) { if (table.isWideTable()) return true; } @@ -59,9 +59,8 @@ public class ExtensionData implements Comparable { } public List getTabs() { - List tabList = new ArrayList<>(tabs.values()); - Collections.sort(tabList); - return tabList; + Collections.sort(tabs); + return tabs; } @Override @@ -124,12 +123,14 @@ public class ExtensionData implements Comparable { } public Builder addTab(ExtensionTabData tab) { - data.tabs.put(tab.getTabInformation().getTabName(), tab); + data.tabs.add(tab); return this; } public Optional getTab(String tabName) { - return Optional.ofNullable(data.tabs.get(tabName)); + return data.tabs.stream() + .filter(tab -> tabName.equals(tab.getTabInformation().getTabName())) + .findFirst(); } public ExtensionData build() { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/results/ExtensionStringData.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/results/ExtensionStringData.java index 66adcf14c..e8201a1ab 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/results/ExtensionStringData.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/results/ExtensionStringData.java @@ -47,6 +47,10 @@ public class ExtensionStringData implements DescribedExtensionData { return description; } + public boolean isPlayerName() { + return playerName; + } + public String getFormattedValue() { String withColors = Html.swapColorCodesToSpan(value); return !playerName ? withColors : Html.LINK.create("../player/" + Html.encodeToURL(value), withColors); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/results/ExtensionTableData.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/results/ExtensionTableData.java index 05f1d415e..1f296fc69 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/results/ExtensionTableData.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/results/ExtensionTableData.java @@ -43,6 +43,18 @@ public class ExtensionTableData implements Comparable { return HtmlTable.fromExtensionTable(table, tableColor); } + public String getProviderName() { + return providerName; + } + + public Table getTable() { + return table; + } + + public Color getTableColor() { + return tableColor; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionAggregateBooleansQuery.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionAggregateBooleansQuery.java index 7f2e20ff6..5c38b3799 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionAggregateBooleansQuery.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionAggregateBooleansQuery.java @@ -104,7 +104,7 @@ public class ExtensionAggregateBooleansQuery implements Query>(sql, 1000) { + return db.query(new QueryStatement<>(sql, 1000) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setBoolean(1, true); // selectTrueBooleans parameter diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionAggregateDoublesQuery.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionAggregateDoublesQuery.java index 464bc6387..010e050cb 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionAggregateDoublesQuery.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionAggregateDoublesQuery.java @@ -103,7 +103,7 @@ public class ExtensionAggregateDoublesQuery implements Query>(sql, 1000) { + return db.query(new QueryStatement<>(sql, 1000) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionAggregateGroupsQuery.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionAggregateGroupsQuery.java index b8e5b3938..0835b5c7f 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionAggregateGroupsQuery.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionAggregateGroupsQuery.java @@ -92,7 +92,7 @@ public class ExtensionAggregateGroupsQuery implements Query>(sql, 1000) { + return db.query(new QueryStatement<>(sql, 1000) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionAggregateNumbersQuery.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionAggregateNumbersQuery.java index c0caa7467..79d7ce3c1 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionAggregateNumbersQuery.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionAggregateNumbersQuery.java @@ -107,7 +107,7 @@ public class ExtensionAggregateNumbersQuery implements Query>(sql, 1000) { + return db.query(new QueryStatement<>(sql, 1000) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionAggregatePercentagesQuery.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionAggregatePercentagesQuery.java index 92c0c06be..e67c77bc3 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionAggregatePercentagesQuery.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionAggregatePercentagesQuery.java @@ -94,7 +94,7 @@ public class ExtensionAggregatePercentagesQuery implements Query>(sql, 1000) { + return db.query(new QueryStatement<>(sql, 1000) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionInformationQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionInformationQueries.java index 26fd2af79..dc6b36a15 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionInformationQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionInformationQueries.java @@ -61,7 +61,7 @@ public class ExtensionInformationQueries { ExtensionPluginTable.ICON_ID + "=" + ExtensionIconTable.TABLE_NAME + '.' + ExtensionIconTable.ID + WHERE + ExtensionPluginTable.SERVER_UUID + "=?"; - return new QueryStatement>(sql, 100) { + return new QueryStatement<>(sql, 100) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -102,7 +102,7 @@ public class ExtensionInformationQueries { INNER_JOIN + ExtensionIconTable.TABLE_NAME + " on " + ExtensionPluginTable.ICON_ID + "=" + ExtensionIconTable.TABLE_NAME + '.' + ExtensionIconTable.ID; - return new QueryAllStatement>>(sql, 100) { + return new QueryAllStatement<>(sql, 100) { @Override public Map> processResults(ResultSet set) throws SQLException { Map> byServerUUID = new HashMap<>(); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionPlayerDataQuery.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionPlayerDataQuery.java index 21743ba49..f99b17da0 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionPlayerDataQuery.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionPlayerDataQuery.java @@ -137,7 +137,7 @@ public class ExtensionPlayerDataQuery implements Query>(sql, 1000) { + return new QueryStatement<>(sql, 1000) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, playerUUID.toString()); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionPlayerGroupsQuery.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionPlayerGroupsQuery.java index 98a86df40..6d14518d0 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionPlayerGroupsQuery.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionPlayerGroupsQuery.java @@ -86,7 +86,7 @@ public class ExtensionPlayerGroupsQuery implements Query>(sql, 1000) { + return new QueryStatement<>(sql, 1000) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, playerUUID.toString()); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionPlayerTablesQuery.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionPlayerTablesQuery.java index 9b5f22e0b..bd3c96a63 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionPlayerTablesQuery.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionPlayerTablesQuery.java @@ -23,6 +23,7 @@ import com.djrapitops.plan.extension.icon.Icon; import com.djrapitops.plan.extension.implementation.results.ExtensionData; import com.djrapitops.plan.extension.table.Table; import com.djrapitops.plan.extension.table.TableAccessor; +import com.djrapitops.plan.extension.table.TableColumnFormat; import com.djrapitops.plan.storage.database.SQLDB; import com.djrapitops.plan.storage.database.queries.Query; import com.djrapitops.plan.storage.database.queries.QueryStatement; @@ -76,7 +77,7 @@ public class ExtensionPlayerTablesQuery implements Query(selectTableValues, 10000) { + return new QueryStatement<>(selectTableValues, 10000) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, playerUUID.toString()); @@ -119,6 +120,10 @@ public class ExtensionPlayerTablesQuery implements Query(selectTables, 100) { + return new QueryStatement<>(selectTables, 100) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, playerUUID.toString()); @@ -186,18 +191,31 @@ public class ExtensionPlayerTablesQuery implements Query> { private final ServerUUID serverUUID; - private final Collection playerUUIDs; + private final Collection userIds; - public ExtensionQueryResultTableDataQuery(ServerUUID serverUUID, Collection playerUUIDs) { + public ExtensionQueryResultTableDataQuery(ServerUUID serverUUID, Collection userIds) { this.serverUUID = serverUUID; - this.playerUUIDs = playerUUIDs; + this.userIds = userIds; } @Override @@ -77,6 +77,9 @@ public class ExtensionQueryResultTableDataQuery implements Query> fetchPlayerData() { + String selectUuids = SELECT + UsersTable.USER_UUID + + FROM + UsersTable.TABLE_NAME + + WHERE + UsersTable.ID + " IN (" + new TextStringBuilder().appendWithSeparators(userIds, ",") + ")"; String sql = SELECT + "v1." + ExtensionPlayerValueTable.USER_UUID + " as uuid," + @@ -93,16 +96,15 @@ public class ExtensionQueryResultTableDataQuery implements Query>(sql, 1000) { + return new QueryStatement<>(sql, 1000) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setBoolean(1, true); // Select only values that should be shown @@ -118,6 +120,10 @@ public class ExtensionQueryResultTableDataQuery implements Query> fetchPlayerGroups() { + String selectUuids = SELECT + UsersTable.USER_UUID + + FROM + UsersTable.TABLE_NAME + + WHERE + UsersTable.ID + " IN (" + new TextStringBuilder().appendWithSeparators(userIds, ",") + ")"; + String sql = SELECT + "v1." + ExtensionGroupsTable.USER_UUID + " as uuid," + "v1." + ExtensionGroupsTable.GROUP_NAME + " as group_value," + @@ -126,14 +132,13 @@ public class ExtensionQueryResultTableDataQuery implements Query>(sql, 1000) { + return new QueryStatement<>(sql, 1000) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionServerDataQuery.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionServerDataQuery.java index 6ed988c7d..650e9f31b 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionServerDataQuery.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionServerDataQuery.java @@ -140,7 +140,7 @@ public class ExtensionServerDataQuery implements Query> { WHERE + ExtensionPluginTable.SERVER_UUID + "=?" + AND + "p1." + ExtensionProviderTable.HIDDEN + "=?"; - return new QueryStatement>(sql, 1000) { + return new QueryStatement<>(sql, 1000) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionServerTableDataQuery.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionServerTableDataQuery.java index 8768f2787..fc18efc11 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionServerTableDataQuery.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionServerTableDataQuery.java @@ -76,10 +76,10 @@ public class ExtensionServerTableDataQuery implements Query> fetchPlayerData() { String selectLimitedNumberOfPlayerUUIDsByLastSeenDate = SELECT + - SessionsTable.TABLE_NAME + '.' + SessionsTable.USER_UUID + - ",MAX(" + SessionsTable.SESSION_END + ") as last_seen" + + UsersTable.USER_UUID + ",MAX(" + SessionsTable.SESSION_END + ") as last_seen" + FROM + SessionsTable.TABLE_NAME + - GROUP_BY + SessionsTable.TABLE_NAME + '.' + SessionsTable.USER_UUID + + INNER_JOIN + UsersTable.TABLE_NAME + " u on u." + UsersTable.ID + '=' + SessionsTable.TABLE_NAME + '.' + SessionsTable.USER_ID + + GROUP_BY + SessionsTable.TABLE_NAME + '.' + SessionsTable.USER_ID + ORDER_BY + "last_seen DESC LIMIT ?"; String sql = SELECT + @@ -105,7 +105,7 @@ public class ExtensionServerTableDataQuery implements Query>(sql, 1000) { + return new QueryStatement<>(sql, 1000) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setInt(1, xMostRecentPlayers); // Limit to x most recently seen players @@ -123,10 +123,10 @@ public class ExtensionServerTableDataQuery implements Query> fetchPlayerGroups() { String selectLimitedNumberOfPlayerUUIDsByLastSeenDate = SELECT + - SessionsTable.TABLE_NAME + '.' + SessionsTable.USER_UUID + - ",MAX(" + SessionsTable.SESSION_END + ") as last_seen" + + UsersTable.USER_UUID + ",MAX(" + SessionsTable.SESSION_END + ") as last_seen" + FROM + SessionsTable.TABLE_NAME + - GROUP_BY + SessionsTable.TABLE_NAME + '.' + SessionsTable.USER_UUID + + INNER_JOIN + UsersTable.TABLE_NAME + " u on u." + UsersTable.ID + '=' + SessionsTable.TABLE_NAME + '.' + SessionsTable.USER_ID + + GROUP_BY + SessionsTable.TABLE_NAME + '.' + SessionsTable.USER_ID + ORDER_BY + "last_seen DESC LIMIT ?"; String sql = SELECT + @@ -143,7 +143,7 @@ public class ExtensionServerTableDataQuery implements Query>(sql, 1000) { + return new QueryStatement<>(sql, 1000) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setInt(1, xMostRecentPlayers); // Limit to x most recently seen players diff --git a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionServerTablesQuery.java b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionServerTablesQuery.java index 62836833a..efe53ca47 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionServerTablesQuery.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/storage/queries/ExtensionServerTablesQuery.java @@ -23,6 +23,7 @@ import com.djrapitops.plan.extension.icon.Icon; import com.djrapitops.plan.extension.implementation.results.ExtensionData; import com.djrapitops.plan.extension.table.Table; import com.djrapitops.plan.extension.table.TableAccessor; +import com.djrapitops.plan.extension.table.TableColumnFormat; import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.SQLDB; import com.djrapitops.plan.storage.database.queries.Query; @@ -79,7 +80,7 @@ public class ExtensionServerTablesQuery implements Query(selectTableValues, 10000) { + return new QueryStatement<>(selectTableValues, 10000) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -123,6 +124,11 @@ public class ExtensionServerTablesQuery implements Query

@@ -83,9 +110,9 @@ public class WorldAliasSettings { */ public void addWorld(String world) { if (world == null || world.isEmpty()) throw new IllegalArgumentException("Attempted to save empty world alias"); + if (getAlias(world).isPresent()) return; - ConfigNode aliasSect = getAliasSection(); - + ConfigNode aliasSect = getAliasListNode(); String previousValue = aliasSect.getString(world); if (previousValue == null || previousValue.isEmpty()) { aliasSect.set(world, world); @@ -111,18 +138,17 @@ public class WorldAliasSettings { entry -> entry.getValue().getTotal() // GMTimes.getTotal )); - ConfigNode aliases = getAliasSection(); - Map playtimePerAlias = new HashMap<>(); for (Map.Entry entry : playtimePerWorld.entrySet()) { String worldName = entry.getKey(); long playtime = entry.getValue(); - if (worldName != null && !aliases.contains(worldName)) { + Optional foundAlias = getAlias(worldName); + if (foundAlias.isEmpty()) { addWorld(worldName); } - String alias = aliases.getString(worldName); + String alias = foundAlias.orElse(worldName); playtimePerAlias.put(alias, playtimePerAlias.getOrDefault(alias, 0L) + playtime); } @@ -130,8 +156,6 @@ public class WorldAliasSettings { } public Map getGMTimesPerAlias(WorldTimes worldTimes) { - ConfigNode aliases = getAliasSection(); - Map gmTimesPerAlias = new HashMap<>(); String[] gms = GMTimes.getGMKeyArray(); @@ -140,11 +164,12 @@ public class WorldAliasSettings { String worldName = entry.getKey(); GMTimes gmTimes = entry.getValue(); - if (!aliases.contains(worldName)) { + Optional foundAlias = getAlias(worldName); + if (foundAlias.isEmpty()) { addWorld(worldName); } - String alias = aliases.getString(worldName); + String alias = foundAlias.orElse(worldName); GMTimes aliasGMTimes = gmTimesPerAlias.getOrDefault(alias, new GMTimes()); for (String gm : gms) { @@ -157,21 +182,20 @@ public class WorldAliasSettings { public String getLongestWorldPlayed(ActiveSession session) { Optional foundWorldTimes = session.getExtraData(WorldTimes.class); - if (!foundWorldTimes.isPresent()) { + if (foundWorldTimes.isEmpty()) { return locale.get().getString(HtmlLang.UNIT_NO_DATA); } WorldTimes worldTimes = foundWorldTimes.orElseGet(WorldTimes::new); - ConfigNode aliases = getAliasSection(); return worldTimes.getCurrentWorld() - .map(currentWorld -> "Current: " + (aliases.contains(currentWorld) ? aliases.getString(currentWorld) : currentWorld)) + .map(currentWorld -> "Current: " + getAlias(currentWorld).orElse(currentWorld)) .orElse("Current: " + locale.get().getString(GenericLang.UNAVAILABLE)); } public String getLongestWorldPlayed(FinishedSession session) { Optional foundWorldTimes = session.getExtraData(WorldTimes.class); - if (!foundWorldTimes.isPresent()) { + if (foundWorldTimes.isEmpty()) { return locale.get().getString(HtmlLang.UNIT_NO_DATA); } WorldTimes worldTimes = foundWorldTimes.orElseGet(WorldTimes::new); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/config/changes/ConfigChange.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/config/changes/ConfigChange.java index 0c20499f4..d474e29e7 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/config/changes/ConfigChange.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/config/changes/ConfigChange.java @@ -19,6 +19,7 @@ package com.djrapitops.plan.settings.config.changes; import com.djrapitops.plan.settings.config.Config; import com.djrapitops.plan.settings.config.ConfigNode; +import java.util.ArrayList; import java.util.Collections; import java.util.Optional; @@ -57,6 +58,75 @@ public interface ConfigChange { } } + class MoveLevelDown implements ConfigChange { + + final String oldPath; + final String newPath; + + public MoveLevelDown(String oldPath, String newPath) { + this.oldPath = oldPath; + this.newPath = newPath; + } + + @Override + public boolean hasBeenApplied(Config config) { + return config.getNode(oldPath).isEmpty() || config.getNode(newPath).isPresent(); + } + + @Override + public synchronized void apply(Config config) { + if (!config.moveChild(oldPath, "Temp." + oldPath)) { + throw new IllegalStateException("Failed to move config node from '" + oldPath + "' to 'Temp." + oldPath + "' while moving to '" + newPath + "'"); + } + if (!config.moveChild("Temp." + oldPath, newPath)) { + throw new IllegalStateException("Failed to move config node from 'Temp." + oldPath + "' to '" + newPath + "' while moving from '" + oldPath + "'"); + } + } + + @Override + public String getAppliedMessage() { + return "Moved " + oldPath + " to " + newPath; + } + } + + class MovedValue implements ConfigChange { + + final String oldPath; + final String newPath; + + public MovedValue(String oldPath, String newPath) { + this.oldPath = oldPath; + this.newPath = newPath; + } + + @Override + public boolean hasBeenApplied(Config config) { + return config.getNode(oldPath) + .map(ConfigNode::getString) + .isEmpty() + && config.getNode(newPath).isPresent(); + } + + @Override + public void apply(Config config) { + Optional oldNode = config.getNode(oldPath); + if (oldNode.isPresent()) { + ConfigNode node = oldNode.get(); + config.getNode(newPath) + .orElseGet(() -> config.addNode(newPath)) + .copyValue(node); + // Set value to null + node.set(null); + node.setComment(new ArrayList<>()); + } + } + + @Override + public String getAppliedMessage() { + return "Moved " + oldPath + " to " + newPath; + } + } + class Copied extends Removed { final String newPath; @@ -86,7 +156,7 @@ public interface ConfigChange { @Override public boolean hasBeenApplied(Config config) { - return !config.getNode(oldPath).isPresent(); + return config.getNode(oldPath).isEmpty(); } @Override @@ -112,7 +182,7 @@ public interface ConfigChange { @Override public boolean hasBeenApplied(Config config) { Optional node = config.getNode(path); - return !node.isPresent() || node.get().getComment().isEmpty(); + return node.isEmpty() || node.get().getComment().isEmpty(); } @Override @@ -142,7 +212,7 @@ public interface ConfigChange { @Override public boolean hasBeenApplied(Config config) { Optional oldNode = config.getNode(oldPath); - return !oldNode.isPresent(); + return oldNode.isEmpty(); } @Override diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/config/changes/ConfigUpdater.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/config/changes/ConfigUpdater.java index df9b4b726..de10c839b 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/config/changes/ConfigUpdater.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/config/changes/ConfigUpdater.java @@ -148,6 +148,30 @@ public class ConfigUpdater { new ConfigChange.Removed("Database.H2.User"), new ConfigChange.Removed("Database.H2.Password"), new ConfigChange.Removed("Database.H2"), + + new ConfigChange.MoveLevelDown("World_aliases", "World_aliases.List"), + + new ConfigChange.MovedValue("Webserver.Alternative_IP", "Webserver.Alternative_IP.Enabled"), + new ConfigChange.MovedValue("Webserver.Security.IP_whitelist", "Webserver.Security.IP_whitelist.Enabled"), + new ConfigChange.Moved("Formatting.Dates.Show_recent_day_names.DatePattern", "Formatting.Dates.Show_recent_day_names_date_pattern"), + new ConfigChange.MovedValue("Export.Server_refresh_period", "Export.Server_refresh_period.Time"), + new ConfigChange.MovedValue("Webserver.Security.Cookies_expire_after", "Webserver.Security.Cookies_expire_after.Time"), + new ConfigChange.MovedValue("Webserver.Cache.Reduced_refresh_barrier", "Webserver.Cache.Reduced_refresh_barrier.Time"), + new ConfigChange.MovedValue("Webserver.Cache.Invalidate_disk_cache_after", "Webserver.Cache.Invalidate_disk_cache_after.Time"), + new ConfigChange.MovedValue("Webserver.Cache.Invalidate_memory_cache_after", "Webserver.Cache.Invalidate_memory_cache_after.Time"), + new ConfigChange.MovedValue("Webserver.Cache.Invalidate_query_results_on_disk_after", "Webserver.Cache.Invalidate_query_results_on_disk_after.Time"), + new ConfigChange.MovedValue("Time.Thresholds.Remove_disabled_extension_data_after", "Time.Thresholds.Remove_disabled_extension_data_after.Time"), + new ConfigChange.MovedValue("Time.Thresholds.Remove_time_series_data_after", "Time.Thresholds.Remove_time_series_data_after.Time"), + new ConfigChange.MovedValue("Time.Thresholds.Remove_ping_data_after", "Time.Thresholds.Remove_ping_data_after.Time"), + new ConfigChange.MovedValue("Time.Thresholds.AFK_threshold", "Time.Thresholds.AFK_threshold.Time"), + new ConfigChange.MovedValue("Time.Thresholds.Remove_inactive_player_data_after", "Time.Thresholds.Remove_inactive_player_data_after.Time"), + new ConfigChange.MovedValue("Time.Periodic_tasks.Extension_data_refresh_every", "Time.Periodic_tasks.Extension_data_refresh_every.Time"), + new ConfigChange.MovedValue("Time.Periodic_tasks.Check_DB_for_server_config_files_every", "Time.Periodic_tasks.Check_DB_for_server_config_files_every.Time"), + new ConfigChange.MovedValue("Time.Periodic_tasks.Clean_Database_every", "Time.Periodic_tasks.Clean_Database_every.Time"), + new ConfigChange.MovedValue("Time.Delays.Ping_server_enable_delay", "Time.Delays.Ping_server_enable_delay.Time"), + new ConfigChange.MovedValue("Time.Delays.Ping_player_join_delay", "Time.Delays.Ping_player_join_delay.Time"), + new ConfigChange.MovedValue("Time.Delays.Wait_for_DB_Transactions_on_disable", "Time.Delays.Wait_for_DB_Transactions_on_disable.Time"), + new ConfigChange.MovedValue("Time.Thresholds.Activity_index.Playtime_threshold", "Time.Thresholds.Activity_index.Playtime_threshold.Time"), }; } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/CustomizedFileSettings.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/CustomizedFileSettings.java new file mode 100644 index 000000000..047a45e0e --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/CustomizedFileSettings.java @@ -0,0 +1,26 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.settings.config.paths; + +import com.djrapitops.plan.settings.config.paths.key.BooleanSetting; +import com.djrapitops.plan.settings.config.paths.key.Setting; +import com.djrapitops.plan.settings.config.paths.key.StringSetting; + +public class CustomizedFileSettings { + public static final Setting WEB_DEV_MODE = new BooleanSetting("Customized_files.Enable_web_dev_mode"); + public static final Setting PATH = new StringSetting("Customized_files.Path"); +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/DataGatheringSettings.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/DataGatheringSettings.java index f0fa90207..f662d4e7b 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/DataGatheringSettings.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/DataGatheringSettings.java @@ -32,6 +32,7 @@ public class DataGatheringSettings { public static final Setting DISK_SPACE = new BooleanSetting("Data_gathering.Disk_space"); public static final Setting LOG_UNKNOWN_COMMANDS = new BooleanSetting("Data_gathering.Commands.Log_unknown"); public static final Setting COMBINE_COMMAND_ALIASES = new BooleanSetting("Data_gathering.Commands.Log_aliases_as_main_command"); + public static final Setting PRESERVE_JOIN_ADDRESS_CASE = new BooleanSetting("Data_gathering.Preserve_join_address_case"); private DataGatheringSettings() { /* static variable class */ diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/DisplaySettings.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/DisplaySettings.java index b62d65320..d06435e78 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/DisplaySettings.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/DisplaySettings.java @@ -17,10 +17,7 @@ package com.djrapitops.plan.settings.config.paths; import com.djrapitops.plan.settings.config.ConfigNode; -import com.djrapitops.plan.settings.config.paths.key.BooleanSetting; -import com.djrapitops.plan.settings.config.paths.key.IntegerSetting; -import com.djrapitops.plan.settings.config.paths.key.Setting; -import com.djrapitops.plan.settings.config.paths.key.StringSetting; +import com.djrapitops.plan.settings.config.paths.key.*; /** * {@link Setting} values that are in "Display_options" section. @@ -30,20 +27,21 @@ import com.djrapitops.plan.settings.config.paths.key.StringSetting; public class DisplaySettings { public static final Setting THEME = new StringSetting("Display_options.Theme"); + public static final Setting PLAYER_HEAD_IMG_URL = new StringSetting("Display_options.Player_head_image_url"); public static final Setting SESSIONS_PER_PAGE = new IntegerSetting("Display_options.Sessions.Show_on_page"); public static final Setting ORDER_WORLD_PIE_BY_PERCENTAGE = new BooleanSetting("Display_options.Sessions.Order_world_pies_by_percentage"); public static final Setting PLAYERS_PER_SERVER_PAGE = new IntegerSetting("Display_options.Players_table.Show_on_server_page"); public static final Setting PLAYERS_PER_PLAYERS_PAGE = new IntegerSetting("Display_options.Players_table.Show_on_players_page"); public static final Setting OPEN_PLAYER_LINKS_IN_NEW_TAB = new BooleanSetting("Display_options.Open_player_links_in_new_tab"); public static final Setting GAPS_IN_GRAPH_DATA = new BooleanSetting("Display_options.Graphs.Show_gaps_in_data"); - public static final Setting GRAPH_TPS_THRESHOLD_HIGH = new IntegerSetting("Display_options.Graphs.TPS.High_threshold"); - public static final Setting GRAPH_TPS_THRESHOLD_MED = new IntegerSetting("Display_options.Graphs.TPS.Medium_threshold"); + public static final Setting GRAPH_TPS_THRESHOLD_HIGH = new DoubleSetting("Display_options.Graphs.TPS.High_threshold"); + public static final Setting GRAPH_TPS_THRESHOLD_MED = new DoubleSetting("Display_options.Graphs.TPS.Medium_threshold"); public static final Setting GRAPH_DISK_THRESHOLD_HIGH = new IntegerSetting("Display_options.Graphs.Disk_space.High_threshold"); public static final Setting GRAPH_DISK_THRESHOLD_MED = new IntegerSetting("Display_options.Graphs.Disk_space.Medium_threshold"); public static final Setting CMD_COLOR_MAIN = new StringSetting("Display_options.Command_colors.Main"); public static final Setting CMD_COLOR_SECONDARY = new StringSetting("Display_options.Command_colors.Secondary"); public static final Setting CMD_COLOR_TERTIARY = new StringSetting("Display_options.Command_colors.Highlight"); - public static final Setting WORLD_ALIASES = new Setting("World_aliases", ConfigNode.class) { + public static final Setting WORLD_ALIASES = new Setting<>("World_aliases.List", ConfigNode.class) { @Override public ConfigNode getValueFrom(ConfigNode node) { return node.getNode(path).orElseGet(() -> node.addNode(path)); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/FormatSettings.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/FormatSettings.java index 6f23a4bfa..a89d82bf3 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/FormatSettings.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/FormatSettings.java @@ -29,7 +29,7 @@ public class FormatSettings { public static final Setting DECIMALS = new StringSetting("Formatting.Decimal_points"); public static final Setting DATE_RECENT_DAYS = new BooleanSetting("Formatting.Dates.Show_recent_day_names"); - public static final Setting DATE_RECENT_DAYS_PATTERN = new StringSetting("Formatting.Dates.Show_recent_day_names.DatePattern"); + public static final Setting DATE_RECENT_DAYS_PATTERN = new StringSetting("Formatting.Dates.Show_recent_day_names_date_pattern"); public static final Setting DATE_FULL = new StringSetting("Formatting.Dates.Full"); public static final Setting DATE_NO_SECONDS = new StringSetting("Formatting.Dates.NoSeconds"); public static final Setting DATE_CLOCK = new StringSetting("Formatting.Dates.JustClock"); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/PluginSettings.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/PluginSettings.java index df05dd134..898adf802 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/PluginSettings.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/PluginSettings.java @@ -42,6 +42,7 @@ public class PluginSettings { public static final Setting CHECK_FOR_UPDATES = new BooleanSetting("Plugin.Update_notifications.Check_for_updates"); public static final Setting NOTIFY_ABOUT_DEV_RELEASES = new BooleanSetting("Plugin.Update_notifications.Notify_about_DEV_releases"); public static final Setting PROXY_COPY_CONFIG = new BooleanSetting("Plugin.Configuration.Allow_proxy_to_manage_settings"); + public static final Setting FRONTEND_BETA = new BooleanSetting("Plugin.Frontend_BETA"); private PluginSettings() { /* static variable class */ diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/WebserverSettings.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/WebserverSettings.java index e771f8eae..7b3233e77 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/WebserverSettings.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/WebserverSettings.java @@ -29,7 +29,7 @@ import java.util.concurrent.TimeUnit; public class WebserverSettings { public static final Setting PORT = new IntegerSetting("Webserver.Port"); - public static final Setting SHOW_ALTERNATIVE_IP = new BooleanSetting("Webserver.Alternative_IP"); + public static final Setting SHOW_ALTERNATIVE_IP = new BooleanSetting("Webserver.Alternative_IP.Enabled"); public static final Setting ALTERNATIVE_IP = new StringSetting("Webserver.Alternative_IP.Address"); public static final Setting INTERNAL_IP = new StringSetting("Webserver.Internal_IP"); public static final Setting CORS_ALLOW_ORIGIN = new StringSetting("Webserver.Security.CORS.Allow_origin"); @@ -37,11 +37,12 @@ public class WebserverSettings { public static final Setting CERTIFICATE_KEYPASS = new StringSetting("Webserver.Security.SSL_certificate.Key_pass"); public static final Setting CERTIFICATE_STOREPASS = new StringSetting("Webserver.Security.SSL_certificate.Store_pass"); public static final Setting CERTIFICATE_ALIAS = new StringSetting("Webserver.Security.SSL_certificate.Alias"); - public static final Setting IP_WHITELIST_X_FORWARDED = new BooleanSetting("Webserver.Security.Use_X-Forwarded-For_Header"); - public static final Setting IP_WHITELIST = new BooleanSetting("Webserver.Security.IP_whitelist"); + public static final Setting IP_USE_X_FORWARDED_FOR = new BooleanSetting("Webserver.Security.Use_X-Forwarded-For_Header"); + public static final Setting IP_WHITELIST = new BooleanSetting("Webserver.Security.IP_whitelist.Enabled"); public static final Setting> WHITELIST = new StringListSetting("Webserver.Security.IP_whitelist.Whitelist"); public static final Setting DISABLED = new BooleanSetting("Webserver.Disable_Webserver"); public static final Setting DISABLED_AUTHENTICATION = new BooleanSetting("Webserver.Security.Disable_authentication"); + public static final Setting LOG_ACCESS_TO_CONSOLE = new BooleanSetting("Webserver.Security.Access_log.Print_to_console"); public static final Setting EXTERNAL_LINK = new StringSetting("Webserver.External_Webserver_address"); public static final Setting REDUCED_REFRESH_BARRIER = new TimeSetting("Webserver.Cache.Reduced_refresh_barrier"); @@ -49,7 +50,7 @@ public class WebserverSettings { public static final Setting INVALIDATE_DISK_CACHE = new TimeSetting("Webserver.Cache.Invalidate_disk_cache_after"); public static final Setting INVALIDATE_MEMORY_CACHE = new TimeSetting("Webserver.Cache.Invalidate_memory_cache_after", TimeUnit.MINUTES.toMillis(5L)); public static final Setting COOKIES_EXPIRE_AFTER = new TimeSetting("Webserver.Security.Cookies_expire_after", TimeUnit.HOURS.toMillis(2L)); - + public static final Setting REMOVE_ACCESS_LOG_AFTER_DAYS = new IntegerSetting("Webserver.Security.Access_log.Remove_logs_after_days"); private WebserverSettings() { /* static variable class */ } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/key/DoubleSetting.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/key/DoubleSetting.java new file mode 100644 index 000000000..e3308cd35 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/key/DoubleSetting.java @@ -0,0 +1,40 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.settings.config.paths.key; + +import com.djrapitops.plan.settings.config.ConfigNode; + +import java.util.function.Predicate; + +/** + * Setting implementation for Double (floating point) value settings. + */ +public class DoubleSetting extends Setting { + + public DoubleSetting(String path) { + super(path, Double.class); + } + + public DoubleSetting(String path, Predicate validator) { + super(path, Double.class, validator); + } + + @Override + public Double getValueFrom(ConfigNode node) { + return node.getDouble(path); + } +} \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/key/StringListSetting.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/key/StringListSetting.java index 25715dea0..0747395ff 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/key/StringListSetting.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/key/StringListSetting.java @@ -30,11 +30,11 @@ import java.util.function.Predicate; public class StringListSetting extends Setting> { public StringListSetting(String path) { - super(path, new Type>() {}); + super(path, new Type<>() {}); } public StringListSetting(String path, Predicate> validator) { - super(path, new Type>() {}, validator); + super(path, new Type<>() {}, validator); } @Override diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/key/TimeSetting.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/key/TimeSetting.java index 1ca6d940b..0e3f55e16 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/key/TimeSetting.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/config/paths/key/TimeSetting.java @@ -48,7 +48,7 @@ public class TimeSetting extends Setting { @Override public Long getValueFrom(ConfigNode node) { - Long duration = node.getLong(path); + Long duration = node.getLong(path + ".Time"); if (duration == null) { return null; } @@ -62,7 +62,7 @@ public class TimeSetting extends Setting { TimeUnit unit = TimeUnit.valueOf(unitName.toUpperCase()); return unit.toMillis(duration); } catch (IllegalArgumentException e) { - return -1L; + throw new IllegalStateException("Config value for " + path + ".Unit has a bad value: " + e.getMessage()); } } -} \ No newline at end of file +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/LangCode.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/LangCode.java index 357fe6e18..946a69ae6 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/LangCode.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/LangCode.java @@ -25,20 +25,20 @@ public enum LangCode { CUSTOM("Custom", ""), EN("English", "AuroraLS3"), - ES("Spanish", "Catalina, itaquito, Elguerrero & 4drian3d"), - CN("Simplified Chinese", "f0rb1d (\u4f5b\u58c1\u706f), qsefthuopq, shaokeyibb, Fur_xia & 10935336"), - CS("Czech", "Shadowhackercz, QuakyCZ, MrFriggo & WolverStones"), + ES("Espaรฑol", "Catalina, itaquito, Elguerrero & 4drian3d"), + CN("\u6C49\u8BED", "f0rb1d (\u4f5b\u58c1\u706f), qsefthuopq, shaokeyibb, Fur_xia, 10935336 & SkipM4"), // Simplified Chinese + CS("ฤeลกtina", "Shadowhackercz, QuakyCZ, MrFriggo & WolverStones"), DE("Deutsch", "Eyremba, fuzzlemann, Morsmorse & hallo1142"), - FI("Finnish", "AuroraLS3"), - FR("French", "CyanTech, Aurelien & Nogapra"), - IT("Italian", "Malachiel & Mastory_Md5"), - JA("Japanese", "yukieji"), - KO("Korean", "Guinness_Akihiko"), - NL("Dutch", "Sander0542"), - RU("Russian", "Saph1s"), - TR("Turkish", "TDJisvan, BruilsiozPro & EyuphanMandiraci"), - PT_BR("Portuguese (Brazil)", "jvmuller"), - ZH_TW("Traditional Chinese", "\u6d1b\u4f0a"); + FI("suomi", "AuroraLS3, KasperiP"), + FR("franรงais", "CyanTech, Aurelien & Nogapra"), + IT("Italiano", "Malachiel & Mastory_Md5"), + JA("\u65E5\u672C\u8A9E", "yukieji"), + KO("\uD55C\uAD6D\uC5B4", "Guinness_Akihiko"), + NL("Nederlands", "Sander0542"), + RU("ั€ัƒััะบะธะน", "Saph1s, Perhun_Pak, BratishkaErik & stashenko"), + TR("Tรผrkรงe", "TDJisvan, BruilsiozPro & EyuphanMandiraci"), + PT_BR("Portuguรชs", "jvmuller"), + ZH_TW("\u6F22\u8A9E", "\u6d1b\u4f0a & zisunny104"); private final String name; private final String authors; @@ -65,6 +65,6 @@ public enum LangCode { } public String getFileName() { - return "locale_" + name() + ".txt"; + return "locale_" + name() + ".yml"; } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/Locale.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/Locale.java index 9da20f38f..22f9e8449 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/Locale.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/Locale.java @@ -162,8 +162,8 @@ public class Locale extends HashMap { for (Lang extra : new Lang[]{ HtmlLang.UNIT_NO_DATA, HtmlLang.TITLE_WORLD_PLAYTIME, - HtmlLang.LABEL_OPERATOR, - HtmlLang.LABEL_BANNED, +// HtmlLang.LABEL_OPERATOR, +// HtmlLang.LABEL_BANNED, HtmlLang.SIDE_SESSIONS, HtmlLang.LABEL_PLAYTIME, HtmlLang.LABEL_AFK_TIME, diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/LocaleFileReader.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/LocaleFileReader.java index 373838170..99cb8712b 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/LocaleFileReader.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/LocaleFileReader.java @@ -16,6 +16,8 @@ */ package com.djrapitops.plan.settings.locale; +import com.djrapitops.plan.settings.config.Config; +import com.djrapitops.plan.settings.config.ConfigReader; import com.djrapitops.plan.settings.locale.lang.Lang; import com.djrapitops.plan.storage.file.Resource; @@ -30,13 +32,33 @@ import java.util.Map; */ public class LocaleFileReader { - private final List lines; + private final Resource resource; - public LocaleFileReader(Resource resource) throws IOException { - lines = resource.asLines(); + public LocaleFileReader(Resource resource) { + this.resource = resource; } - public Locale load(LangCode code) { + public Locale load(LangCode code) throws IOException { + try (ConfigReader reader = new ConfigReader(resource.asInputStream())) { + Config config = reader.read(); + Locale locale = new Locale(code); + Map keys = LocaleSystem.getKeys(); + + config.getConfigPaths().forEach(key -> { + Lang msg = keys.get(key); + if (msg != null) { + locale.put(msg, new Message(config.getString(key))); + } + }); + return locale; + } + } + + /** + * Used to migrate old files to new format. + */ + public Locale loadLegacy(LangCode code) throws IOException { + final List lines = resource.asLines(); Locale locale = new Locale(code); Map identifiers = LocaleSystem.getIdentifiers(); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/LocaleFileWriter.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/LocaleFileWriter.java index 2404e7e4f..540f32fed 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/LocaleFileWriter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/LocaleFileWriter.java @@ -16,16 +16,12 @@ */ package com.djrapitops.plan.settings.locale; +import com.djrapitops.plan.settings.config.Config; +import com.djrapitops.plan.settings.config.ConfigWriter; import com.djrapitops.plan.settings.locale.lang.Lang; -import com.djrapitops.plan.utilities.comparators.LocaleEntryComparator; -import com.djrapitops.plan.utilities.comparators.StringLengthComparator; import java.io.File; import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.util.List; -import java.util.stream.Collectors; /** * Utility for writing a Locale into a file. @@ -41,49 +37,18 @@ public class LocaleFileWriter { } public void writeToFile(File file) throws IOException { - // Find longest identifier length for spacing - int length = LocaleSystem.getIdentifiers().keySet().stream() - .min(new StringLengthComparator()) - .map(String::length).orElse(0) + 2; - addMissingLang(); - List lines = createLines(length); + Config writing = new Config(file); + locale.forEach((lang, message) -> writing.set(lang.getKey(), message.toString())); + writing.dfs((node, result) -> node.sort()); - write(file, lines); - } - - private void write(File file, List lines) throws IOException { - if (!file.exists()) { - Files.createFile(file.toPath()); - } - Files.write(file.toPath(), lines, StandardCharsets.UTF_8); - } - - private List createLines(int length) { - return locale.entrySet().stream() - .sorted(new LocaleEntryComparator()) - .map(entry -> { - String value = entry.getValue() != null ? entry.getValue().toString() : entry.getKey().getDefault(); - return getSpacedIdentifier(entry.getKey().getIdentifier(), length) + "|| " + value; - }) - .collect(Collectors.toList()); + new ConfigWriter(file.toPath()).write(writing); } private void addMissingLang() { - for (Lang lang : LocaleSystem.getIdentifiers().values()) { - if (!locale.containsKey(lang)) { - locale.put(lang, new Message(lang.getDefault())); - } + for (Lang lang : LocaleSystem.getKeys().values()) { + locale.computeIfAbsent(lang, k -> new Message(lang.getDefault())); } } - - private String getSpacedIdentifier(String identifier, int length) { - StringBuilder b = new StringBuilder(identifier); - while (b.length() < length) { - b.append(" "); - } - return b.toString(); - } - -} \ No newline at end of file +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/LocaleSystem.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/LocaleSystem.java index e9a777acb..72e679b52 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/LocaleSystem.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/LocaleSystem.java @@ -17,10 +17,12 @@ package com.djrapitops.plan.settings.locale; import com.djrapitops.plan.SubSystem; +import com.djrapitops.plan.delivery.web.AssetVersions; import com.djrapitops.plan.delivery.webserver.auth.FailReason; import com.djrapitops.plan.settings.config.PlanConfig; import com.djrapitops.plan.settings.config.paths.PluginSettings; import com.djrapitops.plan.settings.locale.lang.*; +import com.djrapitops.plan.storage.file.FileResource; import com.djrapitops.plan.storage.file.PlanFiles; import com.djrapitops.plan.utilities.logging.ErrorContext; import com.djrapitops.plan.utilities.logging.ErrorLogger; @@ -46,6 +48,7 @@ public class LocaleSystem implements SubSystem { private final PlanFiles files; private final PlanConfig config; + private final AssetVersions assetVersions; private final PluginLogger logger; private final ErrorLogger errorLogger; @@ -55,18 +58,42 @@ public class LocaleSystem implements SubSystem { public LocaleSystem( PlanFiles files, PlanConfig config, + AssetVersions assetVersions, PluginLogger logger, ErrorLogger errorLogger ) { this.files = files; this.config = config; + this.assetVersions = assetVersions; this.logger = logger; this.errorLogger = errorLogger; this.locale = new Locale(); } + /** + * Get the txt keys of all Lang entries (legacy locale files that need yml conversion). + * + * @return Map of txt key (eg {@code "HTML - LOGIN_CREATE_ACCOUNT"}) - Lang (eg. {@link HtmlLang#LOGIN_CREATE_ACCOUNT}) + */ public static Map getIdentifiers() { - Lang[][] lang = new Lang[][]{ + return Arrays.stream(getValuesArray()) + .flatMap(Arrays::stream) + .collect(Collectors.toMap(Lang::getIdentifier, Function.identity())); + } + + /** + * Get the yml keys of all Lang entries. + * + * @return Map of yml key (eg. {@code "html.login.register"}) - Lang (eg. {@link HtmlLang#LOGIN_CREATE_ACCOUNT}) + */ + public static Map getKeys() { + return Arrays.stream(getValuesArray()) + .flatMap(Arrays::stream) + .collect(Collectors.toMap(Lang::getKey, Function.identity())); + } + + private static Lang[][] getValuesArray() { + return new Lang[][]{ CommandLang.values(), DeepHelpLang.values(), ErrorPageLang.values(), @@ -78,14 +105,12 @@ public class LocaleSystem implements SubSystem { JSLang.values(), PluginLang.values(), }; - - return Arrays.stream(lang) - .flatMap(Arrays::stream) - .collect(Collectors.toMap(Lang::getIdentifier, Function.identity())); } @Override public void enable() { + convertFromLegacyFormat(); + File localeFile = files.getLocaleFile(); if (config.isTrue(PluginSettings.WRITE_NEW_LOCALE)) { @@ -126,6 +151,33 @@ public class LocaleSystem implements SubSystem { } } + private void convertFromLegacyFormat() { + File oldCustomFile = files.getFileFromPluginFolder("locale.txt"); + if (!files.getLocaleFile().exists() && oldCustomFile.exists()) { + try { + logger.info("Converting locale.txt to yml..."); + Locale loaded = new LocaleFileReader(new FileResource("locale.txt", oldCustomFile)).loadLegacy(LangCode.CUSTOM); + new LocaleFileWriter(loaded).writeToFile(files.getLocaleFile()); + } catch (IOException e) { + errorLogger.error(e, ErrorContext.builder().whatToDo("Fix write permissions to " + files.getLocaleFile().toString()).build()); + } + } + + for (LangCode code : LangCode.values()) { + if (code == LangCode.CUSTOM) continue; + File oldFile = files.getFileFromPluginFolder("locale_" + code + ".txt"); + if (!files.getFileFromPluginFolder(code.getFileName()).exists() && oldFile.exists()) { + try { + logger.info("Converting " + oldFile.getName() + " to yml..."); + Locale loaded = new LocaleFileReader(new FileResource(oldFile.getName(), oldFile)).loadLegacy(LangCode.CUSTOM); + new LocaleFileWriter(loaded).writeToFile(files.getFileFromPluginFolder(code.getFileName())); + } catch (IOException e) { + errorLogger.error(e, ErrorContext.builder().whatToDo("Fix write permissions to " + files.getFileFromPluginFolder(code.getFileName()).toString()).build()); + } + } + } + } + private Optional loadSettingLocale() { try { String setting = config.get(PluginSettings.LOCALE); @@ -134,7 +186,7 @@ public class LocaleSystem implements SubSystem { if (code == LangCode.CUSTOM) continue; Locale writing = Locale.forLangCode(code, files); new LocaleFileWriter(writing).writeToFile( - files.getDataDirectory().resolve("locale_" + code.name() + ".txt").toFile() + files.getDataDirectory().resolve("locale_" + code.name() + ".yml").toFile() ); } @@ -144,7 +196,7 @@ public class LocaleSystem implements SubSystem { return Optional.of(Locale.forLangCodeString(files, setting)); } } catch (IOException e) { - logger.warn("Failed to read locale from jar: " + config.get(PluginSettings.LOCALE) + ", " + e.toString()); + logger.warn("Failed to read locale from jar: " + config.get(PluginSettings.LOCALE) + ", " + e); logger.warn("Using Default Locale as a fallback (EN)"); } return Optional.empty(); @@ -154,7 +206,7 @@ public class LocaleSystem implements SubSystem { try { return Optional.of(Locale.fromFile(localeFile)); } catch (IOException e) { - logger.warn("Failed to read locale file at " + localeFile.getAbsolutePath() + ", " + e.toString()); + logger.warn("Failed to read locale file at " + localeFile.getAbsolutePath() + ", " + e); logger.warn("Using Default Locale as a fallback (EN)"); } return Optional.empty(); @@ -168,4 +220,20 @@ public class LocaleSystem implements SubSystem { public Locale getLocale() { return locale; } + + public long getMaxLocaleVersion() { + return assetVersions.getLatestWebAssetVersion().orElse(0L); + } + + public Optional getLocaleVersion(LangCode langCode) { + return assetVersions.getAssetVersion(langCode.getFileName()); + } + + public Optional getCustomLocaleVersion() { + File localeFile = files.getLocaleFile(); + if (!localeFile.exists()) { + return Optional.empty(); + } + return Optional.of(localeFile.lastModified()); + } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/CommandLang.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/CommandLang.java index bdb0cb014..711aa5b63 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/CommandLang.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/CommandLang.java @@ -22,120 +22,125 @@ package com.djrapitops.plan.settings.locale.lang; * @author AuroraLS3 */ public enum CommandLang implements Lang { - CONFIRM_EXPIRED("Cmd Confirm - Expired", "Confirmation expired, use the command again"), - CONFIRM_FAIL_ACCEPT("Cmd Confirm - Fail on accept", "The accepted action errored upon execution: ${0}"), - CONFIRM_FAIL_DENY("Cmd Confirm - Fail on deny", "The denied action errored upon execution: ${0}"), - CONFIRM("Cmd Confirm - confirmation", "Confirm: "), - CONFIRM_ACCEPT("Cmd Confirm - accept", "Accept"), - CONFIRM_DENY("Cmd Confirm - deny", "Cancel"), - CONFIRM_OVERWRITE_DB("Cmd Confirm - overwriting db", "You are about to overwrite data in Plan ${0} with data in ${1}"), - CONFIRM_CLEAR_DB("Cmd Confirm - clearing db", "You are about to remove all Plan-data in ${0}"), - CONFIRM_REMOVE_PLAYER_DB("Cmd Confirm - remove player db", "You are about to remove data of ${0} from ${1}"), - CONFIRM_UNREGISTER("Cmd Confirm - unregister", "You are about to unregister '${0}' linked to ${1}"), - CONFIRM_CANCELLED_DATA("Cmd Confirm - cancelled, no data change", "Cancelled. No data was changed."), - CONFIRM_CANCELLED_UNREGISTER("Cmd Confirm - cancelled, unregister", "Cancelled. '${0}' was not unregistered"), + CONFIRM_EXPIRED("command.confirmation.expired", "Cmd Confirm - Expired", "Confirmation expired, use the command again"), + CONFIRM_FAIL_ACCEPT("command.fail.onAccept", "Cmd Confirm - Fail on accept", "The accepted action errored upon execution: ${0}"), + CONFIRM_FAIL_DENY("command.fail.onDeny", "Cmd Confirm - Fail on deny", "The denied action errored upon execution: ${0}"), + CONFIRM("command.confirmation.confirm", "Cmd Confirm - confirmation", "Confirm: "), + CONFIRM_ACCEPT("command.confirmation.accept", "Cmd Confirm - accept", "Accept"), + CONFIRM_DENY("command.confirmation.deny", "Cmd Confirm - deny", "Cancel"), + CONFIRM_OVERWRITE_DB("command.confirmation.dbOverwrite", "Cmd Confirm - overwriting db", "You are about to overwrite data in Plan ${0} with data in ${1}"), + CONFIRM_CLEAR_DB("command.confirmation.dbClear", "Cmd Confirm - clearing db", "You are about to remove all Plan-data in ${0}"), + CONFIRM_REMOVE_PLAYER_DB("command.confirmation.dbRemovePlayer", "Cmd Confirm - remove player db", "You are about to remove data of ${0} from ${1}"), + CONFIRM_UNREGISTER("command.confirmation.unregister", "Cmd Confirm - unregister", "You are about to unregister '${0}' linked to ${1}"), + CONFIRM_CANCELLED_DATA("command.confirmation.cancelNoChanges", "Cmd Confirm - cancelled, no data change", "Cancelled. No data was changed."), + CONFIRM_CANCELLED_UNREGISTER("command.confirmation.cancelNoUnregister", "Cmd Confirm - cancelled, unregister", "Cancelled. '${0}' was not unregistered"), - FAIL_PLAYER_NOT_FOUND("Cmd FAIL - No player", "Player '${0}' was not found, they have no UUID."), - FAIL_PLAYER_NOT_FOUND_REGISTER("Cmd FAIL - No player register", "Player '${0}' was not found in the database."), - FAIL_SERVER_NOT_FOUND("Cmd FAIL - No server", "Server '${0}' was not found from the database."), - FAIL_EMPTY_SEARCH_STRING("Cmd FAIL - Empty search string", "The search string can not be empty"), - FAIL_ACCEPTS_ARGUMENTS("Cmd FAIL - Accepts only these arguments", "Accepts following as ${0}: ${1}"), - FAIL_REQ_ARGS("Cmd FAIL - Requires Arguments", "ยงcArguments required (${0}) ${1}"), - FAIL_REQ_ONE_ARG("Cmd FAIL - Require only one Argument", "ยงcSingle Argument required ${1}"), - FAIL_NO_PERMISSION("Cmd FAIL - No Permission", "ยงcYou do not have the required permission."), - FAIL_USERNAME_NOT_VALID("Cmd FAIL - Invalid Username", "ยงcUser does not have an UUID."), - FAIL_USERNAME_NOT_KNOWN("Cmd FAIL - Unknown Username", "ยงcUser has not been seen on this server"), - FAIL_DATABASE_NOT_OPEN("Cmd FAIL - Database not open", "ยงcDatabase is ${0} - Please try again a bit later."), - WARN_DATABASE_NOT_OPEN("Cmd WARN - Database not open", "ยงeDatabase is ${0} - This might take longer than expected.."), - USER_NOT_LINKED("Cmd FAIL - Users not linked", "User is not linked to your account and you don't have permission to remove other user's accounts."), + FAIL_PLAYER_NOT_FOUND("command.fail.playerNotFound", "Cmd FAIL - No player", "Player '${0}' was not found, they have no UUID."), + FAIL_PLAYER_NOT_FOUND_REGISTER("command.fail.playerNotInDatabase", "Cmd FAIL - No player register", "Player '${0}' was not found in the database."), + FAIL_SERVER_NOT_FOUND("command.fail.serverNotFound", "Cmd FAIL - No server", "Server '${0}' was not found from the database."), + FAIL_EMPTY_SEARCH_STRING("command.fail.emptyString", "Cmd FAIL - Empty search string", "The search string can not be empty"), + FAIL_ACCEPTS_ARGUMENTS("command.fail.invalidArguments", "Cmd FAIL - Accepts only these arguments", "Accepts following as ${0}: ${1}"), + FAIL_REQ_ARGS("command.fail.missingArguments", "Cmd FAIL - Requires Arguments", "ยงcArguments required (${0}) ${1}"), + FAIL_REQ_ONE_ARG("command.fail.tooManyArguments", "Cmd FAIL - Require only one Argument", "ยงcSingle Argument required ${1}"), + FAIL_NO_PERMISSION("command.fail.noPermission", "Cmd FAIL - No Permission", "ยงcYou do not have the required permission."), + FAIL_USERNAME_NOT_VALID("command.fail.invalidUsername", "Cmd FAIL - Invalid Username", "ยงcUser does not have an UUID."), + FAIL_USERNAME_NOT_KNOWN("command.fail.unknownUsername", "Cmd FAIL - Unknown Username", "ยงcUser has not been seen on this server"), + FAIL_DATABASE_NOT_OPEN("command.database.failDbNotOpen", "Cmd FAIL - Database not open", "ยงcDatabase is ${0} - Please try again a bit later."), + WARN_DATABASE_NOT_OPEN("command.database.warnDbNotOpen", "Cmd WARN - Database not open", "ยงeDatabase is ${0} - This might take longer than expected.."), + USER_NOT_LINKED("command.fail.missingLink", "Cmd FAIL - Users not linked", "User is not linked to your account and you don't have permission to remove other user's accounts."), - FAIL_WEB_USER_EXISTS("Cmd FAIL - WebUser exists", "ยงcUser already exists!"), - FAIL_WEB_USER_NOT_EXISTS("Cmd FAIL - WebUser does not exists", "ยงcUser does not exists!"), - FAIL_NO_SUCH_FEATURE("Cmd FAIL - No Feature", "ยงeDefine a feature to disable! (currently supports ${0})"), - FAIL_SEE_CONFIG_SETTING("Cmd FAIL - see config", "see '${0}' in config.yml"), + FAIL_WEB_USER_EXISTS("command.fail.webUserExists", "Cmd FAIL - WebUser exists", "ยงcUser already exists!"), + FAIL_WEB_USER_NOT_EXISTS("command.fail.webUserNotFound", "Cmd FAIL - WebUser does not exists", "ยงcUser does not exists!"), + FAIL_NO_SUCH_FEATURE("command.fail.missingFeature", "Cmd FAIL - No Feature", "ยงeDefine a feature to disable! (currently supports ${0})"), + FAIL_SEE_CONFIG_SETTING("command.fail.seeConfig", "Cmd FAIL - see config", "see '${0}' in config.yml"), - FEATURE_DISABLED("Cmd SUCCESS - Feature disabled", "ยงaDisabled '${0}' temporarily until next plugin reload."), + FEATURE_DISABLED("command.general.featureDisabled", "Cmd SUCCESS - Feature disabled", "ยงaDisabled '${0}' temporarily until next plugin reload."), - WEB_USER_REGISTER_SUCCESS("Cmd SUCCESS - WebUser register", "ยงaAdded a new user (${0}) successfully!"), - WEB_USER_REGISTER_NOTIFY("Cmd Notify - WebUser register", "Registered new user: '${0}' Perm level: ${1}"), - WEB_USER_LIST("Web User Listing", " ยง2${0} ยง7: ยงf${1}"), - NO_WEB_USER_NOTIFY("Cmd Notify - No WebUser", "You might not have a web user, use /plan register "), - WEB_PERMISSION_LEVELS("Cmd Web - Permission Levels", ">\\ยง70: Access all pages\\ยง71: Access '/players' and all player pages\\ยง72: Access player page with the same username as the webuser\\ยง73+: No permissions"), + WEB_USER_REGISTER_SUCCESS("command.general.successWebUserRegister", "Cmd SUCCESS - WebUser register", "ยงaAdded a new user (${0}) successfully!"), + WEB_USER_REGISTER_NOTIFY("command.general.notifyWebUserRegister", "Cmd Notify - WebUser register", "Registered new user: '${0}' Perm level: ${1}"), + WEB_USER_LIST("command.general.webUserList", "Web User Listing", " ยง2${0} ยง7: ยงf${1}"), + NO_WEB_USER_NOTIFY("command.general.noWebuser", "Cmd Notify - No WebUser", "You might not have a web user, use /plan register "), + WEB_PERMISSION_LEVELS("command.general.webPermissionLevels", "Cmd Web - Permission Levels", ">\\ยง70: Access all pages\\ยง71: Access '/players' and all player pages\\ยง72: Access player page with the same username as the webuser\\ยง73+: No permissions"), - LINK_CLICK_ME("Cmd - Click Me", "Click me"), - LINK("Cmd - Link", "Link"), - LINK_SERVER("Cmd - Link Server", "Server page: "), - LINK_PLAYER("Cmd - Link Player", "Player page: "), - LINK_PLAYERS("Cmd - Link Players", "Players page: "), - LINK_NETWORK("Cmd - Link Network", "Network page: "), - LINK_JSON("Cmd - Link Player JSON", "Player json: "), - LINK_REGISTER("Cmd - Link Register", "Register page: "), + LINK_CLICK_ME("command.link.clickMe", "Cmd - Click Me", "Click me"), + LINK("command.link.link", "Cmd - Link", "Link"), + LINK_SERVER("command.link.server", "Cmd - Link Server", "Server page: "), + LINK_PLAYER("command.link.player", "Cmd - Link Player", "Player page: "), + LINK_PLAYERS("command.link.players", "Cmd - Link Players", "Players page: "), + LINK_NETWORK("command.link.network", "Cmd - Link Network", "Network page: "), + LINK_JSON("command.link.playerJson", "Cmd - Link Player JSON", "Player json: "), + LINK_REGISTER("command.link.register", "Cmd - Link Register", "Register page: "), - HEADER_HELP("Cmd Header - Help", "> ยง2/${0} Help"), - FOOTER_HELP("Cmd Footer - Help", "ยง7Hover over command or arguments or use '/${0} ?' to learn more about them."), - HEADER_SEARCH("Cmd Header - Search", "> ยง2${0} Results for ยงf${1}ยง2:"), - HEADER_ANALYSIS("Cmd Header - Analysis", "> ยง2Analysis Results"), - HEADER_INFO("Cmd Header - Info", "> ยง2Player Analytics"), - HEADER_INSPECT("Cmd Header - Inspect", "> ยง2Player: ยงf${0}"), - HEADER_SERVERS("Cmd Header - Servers", "> ยง2Servers"), - HEADER_PLAYERS("Cmd Header - Players", "> ยง2Players"), - HEADER_WEB_USERS("Cmd Header - Web Users", "> ยง2${0} Web Users"), - HEADER_NETWORK("Cmd Header - Network", "> ยง2Network Page"), - HEADER_SERVER_LIST("Cmd Header - server list", "id::name::uuid"), - HEADER_WEB_USER_LIST("Cmd Header - web user list", "username::linked to::permission level"), + HEADER_HELP("command.header.help", "Cmd Header - Help", "> ยง2/${0} Help"), + FOOTER_HELP("command.footer.help", "Cmd Footer - Help", "ยง7Hover over command or arguments or use '/${0} ?' to learn more about them."), + HEADER_SEARCH("command.header.search", "Cmd Header - Search", "> ยง2${0} Results for ยงf${1}ยง2:"), + HEADER_ANALYSIS("command.header.analysis", "Cmd Header - Analysis", "> ยง2Analysis Results"), + HEADER_INFO("command.header.info", "Cmd Header - Info", "> ยง2Player Analytics"), + HEADER_INSPECT("command.header.inspect", "Cmd Header - Inspect", "> ยง2Player: ยงf${0}"), + HEADER_SERVERS("command.header.servers", "Cmd Header - Servers", "> ยง2Servers"), + HEADER_PLAYERS("command.header.players", "Cmd Header - Players", "> ยง2Players"), + HEADER_WEB_USERS("command.header.webUsers", "Cmd Header - Web Users", "> ยง2${0} Web Users"), + HEADER_NETWORK("command.header.network", "Cmd Header - Network", "> ยง2Network Page"), + HEADER_SERVER_LIST("command.header.serverList", "Cmd Header - server list", "id::name::uuid::version"), + HEADER_WEB_USER_LIST("command.header.webUserList", "Cmd Header - web user list", "username::linked to::permission level"), - INFO_VERSION("Cmd Info - Version", " ยง2Version: ยงf${0}"), - INFO_UPDATE("Cmd Info - Update", " ยง2Update Available: ยงf${0}"), - INFO_DATABASE("Cmd Info - Database", " ยง2Current Database: ยงf${0}"), - INFO_PROXY_CONNECTION("Cmd Info - Bungee Connection", " ยง2Connected to Proxy: ยงf${0}"), + INFO_VERSION("command.subcommand.info.version", "Cmd Info - Version", " ยง2Version: ยงf${0}"), + INFO_UPDATE("command.subcommand.info.update", "Cmd Info - Update", " ยง2Update Available: ยงf${0}"), + INFO_DATABASE("command.subcommand.info.database", "Cmd Info - Database", " ยง2Current Database: ยงf${0}"), + INFO_PROXY_CONNECTION("command.subcommand.info.proxy", "Cmd Info - Bungee Connection", " ยง2Connected to Proxy: ยงf${0}"), - INGAME_ACTIVITY_INDEX("Cmd Qinspect - Activity Index", " ยง2Activity Index: ยงf${0} | ${1}"), - INGAME_REGISTERED("Cmd Qinspect - Registered", " ยง2Registered: ยงf${0}"), - INGAME_LAST_SEEN("Cmd Qinspect - Last Seen", " ยง2Last Seen: ยงf${0}"), - INGAME_GEOLOCATION("Cmd Qinspect - Geolocation", " ยง2Logged in from: ยงf${0}"), - INGAME_PLAYTIME("Cmd Qinspect - Playtime", " ยง2Playtime: ยงf${0}"), - INGAME_ACTIVE_PLAYTIME("Cmd Qinspect - Active Playtime", " ยง2Active Playtime: ยงf${0}"), - INGAME_AFK_PLAYTIME("Cmd Qinspect - AFK Playtime", " ยง2AFK Time: ยงf${0}"), - INGAME_LONGEST_SESSION("Cmd Qinspect - Longest Session", " ยง2Longest Session: ยงf${0}"), - INGAME_TIMES_KICKED("Cmd Qinspect - Times Kicked", " ยง2Times Kicked: ยงf${0}"), - INGAME_PLAYER_KILLS("Cmd Qinspect - Player Kills", " ยง2Player Kills: ยงf${0}"), - INGAME_MOB_KILLS("Cmd Qinspect - Mob Kills", " ยง2Mob Kills: ยงf${0}"), - INGAME_DEATHS("Cmd Qinspect - Deaths", " ยง2Deaths: ยงf${0}"), + INGAME_ACTIVITY_INDEX("command.ingame.activityIndex", "Cmd Qinspect - Activity Index", " ยง2Activity Index: ยงf${0} | ${1}"), + INGAME_REGISTERED("command.ingame.registered", "Cmd Qinspect - Registered", " ยง2Registered: ยงf${0}"), + INGAME_LAST_SEEN("command.ingame.lastSeen", "Cmd Qinspect - Last Seen", " ยง2Last Seen: ยงf${0}"), + INGAME_GEOLOCATION("command.ingame.geolocation", "Cmd Qinspect - Geolocation", " ยง2Logged in from: ยงf${0}"), + INGAME_PLAYTIME("command.ingame.playtime", "Cmd Qinspect - Playtime", " ยง2Playtime: ยงf${0}"), + INGAME_ACTIVE_PLAYTIME("command.ingame.activePlaytime", "Cmd Qinspect - Active Playtime", " ยง2Active Playtime: ยงf${0}"), + INGAME_AFK_PLAYTIME("command.ingame.afkPlaytime", "Cmd Qinspect - AFK Playtime", " ยง2AFK Time: ยงf${0}"), + INGAME_LONGEST_SESSION("command.ingame.longestSession", "Cmd Qinspect - Longest Session", " ยง2Longest Session: ยงf${0}"), + INGAME_TIMES_KICKED("command.ingame.timesKicked", "Cmd Qinspect - Times Kicked", " ยง2Times Kicked: ยงf${0}"), + INGAME_PLAYER_KILLS("command.ingame.playerKills", "Cmd Qinspect - Player Kills", " ยง2Player Kills: ยงf${0}"), + INGAME_MOB_KILLS("command.ingame.mobKills", "Cmd Qinspect - Mob Kills", " ยง2Mob Kills: ยงf${0}"), + INGAME_DEATHS("command.ingame.deaths", "Cmd Qinspect - Deaths", " ยง2Deaths: ยงf${0}"), - DB_BACKUP_CREATE("Cmd db - creating backup", "Creating a backup file '${0}.db' with contents of ${1}"), - DB_WRITE("Cmd db - write", "Writing to ${0}.."), - DB_REMOVAL("Cmd db - removal", "Removing Plan-data from ${0}.."), - DB_REMOVAL_PLAYER("Cmd db - removal player", "Removing data of ${0} from ${1}.."), - DB_UNINSTALLED("Cmd db - server uninstalled", "ยงaIf the server is still installed, it will automatically set itself as installed in the database."), - UNREGISTER("Cmd unregister - unregistering", "Unregistering '${0}'.."), + DB_BACKUP_CREATE("command.database.creatingBackup", "Cmd db - creating backup", "Creating a backup file '${0}.db' with contents of ${1}"), + DB_WRITE("command.database.write", "Cmd db - write", "Writing to ${0}.."), + DB_REMOVAL("command.database.removal", "Cmd db - removal", "Removing Plan-data from ${0}.."), + DB_REMOVAL_PLAYER("command.database.playerRemoval", "Cmd db - removal player", "Removing data of ${0} from ${1}.."), + DB_UNINSTALLED("command.database.serverUninstalled", "Cmd db - server uninstalled", "ยงaIf the server is still installed, it will automatically set itself as installed in the database."), + UNREGISTER("command.database.unregister", "Cmd unregister - unregistering", "Unregistering '${0}'.."), - DISABLE_DISABLED("Cmd Disable - Disabled", "ยงaPlan systems are now disabled. You can still use reload to restart the plugin."), + DISABLE_DISABLED("command.general.pluginDisabled", "Cmd Disable - Disabled", "ยงaPlan systems are now disabled. You can still use reload to restart the plugin."), - NOTIFY_NO_NETWORK("Cmd network - No network", "Server is not connected to a network. The link redirects to server page."), - RELOAD_COMPLETE("Cmd Info - Reload Complete", "ยงaReload Complete"), - RELOAD_FAILED("Cmd Info - Reload Failed", "ยงcSomething went wrong during reload of the plugin, a restart is recommended."), - NO_ADDRESS_NOTIFY("Cmd Notify - No Address", "ยงeNo address was available - using localhost as fallback. Set up 'Alternative_IP' settings."), - HOTSWAP_REMINDER("Manage - Remind HotSwap", "ยงeRemember to swap to the new database (/plan db hotswap ${0}) & reload the plugin."), - PROGRESS_START("Manage - Start", "> ยง2Processing data.."), - PROGRESS("Manage - Progress", "${0} / ${1} processed.."), - PROGRESS_SUCCESS("Manage - Success", "> ยงaSuccess!"), - PROGRESS_FAIL("Manage - Fail", "> ยงcSomething went wrong: ${0}"), - CONFIRMATION("Manage - Fail, Confirmation", "> ยงcAdd '-a' argument to confirm execution: ${0}"), - IMPORTERS("Manage - List Importers", "Importers: "), - CONFIRM_OVERWRITE("Manage - Confirm Overwrite", "Data in ${0} will be overwritten!"), - CONFIRM_REMOVAL("Manage - Confirm Removal", "Data in ${0} will be removed!"), - FAIL_SAME_DB("Manage - Fail Same Database", "> ยงcCan not operate on to and from the same database!"), - FAIL_INCORRECT_DB("Manage - Fail Incorrect Database", "> ยงc'${0}' is not a supported database."), - FAIL_FILE_NOT_FOUND("Manage - Fail File not found", "> ยงcNo File found at ${0}"), - FAIL_IMPORTER_NOT_FOUND("Manage - Fail No Importer", "ยงeImporter '${0}' doesn't exist"), - FAIL_EXPORTER_NOT_FOUND("Manage - Fail No Exporter", "ยงeExporter '${0}' doesn't exist"), - NO_SERVER("Manage - Fail No Server", "No server found with given parameters."), - UNINSTALLING_SAME_SERVER("Manage - Fail Same server", "Can not mark this server as uninstalled (You are on it)"); + NOTIFY_NO_NETWORK("command.link.noNetwork", "Cmd network - No network", "Server is not connected to a network. The link redirects to server page."), + RELOAD_COMPLETE("command.general.reloadComplete", "Cmd Info - Reload Complete", "ยงaReload Complete"), + RELOAD_FAILED("command.general.reloadFailed", "Cmd Info - Reload Failed", "ยงcSomething went wrong during reload of the plugin, a restart is recommended."), + NO_ADDRESS_NOTIFY("command.general.noAddress", "Cmd Notify - No Address", "ยงeNo address was available - using localhost as fallback. Set up 'Alternative_IP' settings."), + HOTSWAP_REMINDER("command.database.manage.hotswap", "Manage - Remind HotSwap", "ยงeRemember to swap to the new database (/plan db hotswap ${0}) & reload the plugin."), + PROGRESS_START("command.database.manage.start", "Manage - Start", "> ยง2Processing data.."), + PROGRESS("command.database.manage.progress", "Manage - Progress", "${0} / ${1} processed.."), + PROGRESS_PREPARING("command.database.manage.preparing", "Manage - preparing", "Preparing.."), + PROGRESS_SUCCESS("command.database.manage.success", "Manage - Success", "> ยงaSuccess!"), + PROGRESS_FAIL("command.database.manage.fail", "Manage - Fail", "> ยงcSomething went wrong: ${0}"), + CONFIRMATION("command.database.manage.confirm", "Manage - Fail, Confirmation", "> ยงcAdd '-a' argument to confirm execution: ${0}"), + IMPORTERS("command.database.manage.importers", "Manage - List Importers", "Importers: "), + CONFIRM_OVERWRITE("command.database.manage.confirmOverwrite", "Manage - Confirm Overwrite", "Data in ${0} will be overwritten!"), + CONFIRM_REMOVAL("command.database.manage.confirmRemoval", "Manage - Confirm Removal", "Data in ${0} will be removed!"), + CONFIRM_JOIN_ADDRESS_REMOVAL("command.database.manage.confirmPartialRemoval", "Manage - Confirm Partial Removal", "Join Address Data for Server ${0} in ${1} will be removed!"), + FAIL_SAME_DB("command.database.manage.failSameDB", "Manage - Fail Same Database", "> ยงcCan not operate on to and from the same database!"), + FAIL_INCORRECT_DB("command.database.manage.failIncorrectDB", "Manage - Fail Incorrect Database", "> ยงc'${0}' is not a supported database."), + FAIL_FILE_NOT_FOUND("command.database.manage.failFileNotFound", "Manage - Fail File not found", "> ยงcNo File found at ${0}"), + FAIL_IMPORTER_NOT_FOUND("command.general.failNoImporter", "Manage - Fail No Importer", "ยงeImporter '${0}' doesn't exist"), + FAIL_EXPORTER_NOT_FOUND("command.general.failNoExporter", "Manage - Fail No Exporter", "ยงeExporter '${0}' doesn't exist"), + NO_SERVER("command.database.manage.failNoServer", "Manage - Fail No Server", "No server found with given parameters."), + UNINSTALLING_SAME_SERVER("command.database.manage.failSameServer", "Manage - Fail Same server", "Can not mark this server as uninstalled (You are on it)"), + ; + private final String key; private final String identifier; private final String defaultValue; - CommandLang(String identifier, String defaultValue) { + CommandLang(String key, String identifier, String defaultValue) { + this.key = key; this.identifier = identifier; this.defaultValue = defaultValue; } @@ -145,6 +150,9 @@ public enum CommandLang implements Lang { return identifier; } + @Override + public String getKey() {return key;} + @Override public String getDefault() { return defaultValue; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/DeepHelpLang.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/DeepHelpLang.java index f3166235a..771e33442 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/DeepHelpLang.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/DeepHelpLang.java @@ -22,36 +22,38 @@ package com.djrapitops.plan.settings.locale.lang; * @author AuroraLS3 */ public enum DeepHelpLang implements Lang { - SERVER("In Depth Help - /plan server", "Obtain a link to the /server page of a specific server, or the current server if no arguments are given."), - SERVERS("In Depth Help - /plan servers", "List ids, names and uuids of servers in the database."), - NETWORK("In Depth Help - /plan network", "Obtain a link to the /network page, only does so on networks."), - PLAYER("In Depth Help - /plan player", "Obtain a link to the /player page of a specific player, or the current player."), - PLAYERS("In Depth Help - /plan players", "Obtain a link to the /players page to see a list of players."), - SEARCH("In Depth Help - /plan search", "List all matching player names to given part of a name."), - INGAME("In Depth Help - /plan ingame", "Displays some information about the player in game."), - REGISTER("In Depth Help - /plan register", "Use without arguments to get link to register page. Use --code [code] after registration to get a user."), - UNREGISTER("In Depth Help - /plan unregister", "Use without arguments to unregister player linked user, or with username argument to unregister another user."), - LOGOUT("In Depth Help - /plan logout", "Give username argument to log out another user from the panel, give * as argument to log out everyone."), - INFO("In Depth Help - /plan info", "Display the current status of the plugin."), - RELOAD("In Depth Help - /plan reload", "Disable and enable the plugin to reload any changes in config."), - DISABLE("In Depth Help - /plan disable", "Disable the plugin or part of it until next reload/restart."), - USERS("In Depth Help - /plan users", "Lists web users as a table."), - DB("In Depth Help - /plan db", "Use different database subcommands to change the data in some way"), - DB_BACKUP("In Depth Help - /plan db backup", "Uses SQLite to backup the target database to a file."), - DB_RESTORE("In Depth Help - /plan db restore", "Uses SQLite backup file and overwrites contents of the target database."), - DB_MOVE("In Depth Help - /plan db move", "Overwrites contents in the other database with the contents in another."), - DB_HOTSWAP("In Depth Help - /plan db hotswap", "Reloads the plugin with the other database and changes the config to match."), - DB_CLEAR("In Depth Help - /plan db clear", "Clears all Plan tables, removing all Plan-data in the process."), - DB_REMOVE("In Depth Help - /plan db remove", "Removes all data linked to a player from the Current database."), - DB_UNINSTALLED("In Depth Help - /plan db uninstalled", "Marks a server in Plan database as uninstalled so that it will not show up in server queries."), - EXPORT("In Depth Help - /plan export", "Performs an export to export location defined in the config."), - IMPORT("In Depth Help - /plan import", "Performs an import to load data into the database."), - JSON("In Depth Help - /plan json", "Allows you to download a player's data in json format. All of it."); + SERVER("command.help.server.inDepth", "In Depth Help - /plan server", "Obtain a link to the /server page of a specific server, or the current server if no arguments are given."), + SERVERS("command.help.servers.inDepth", "In Depth Help - /plan servers", "List ids, names and uuids of servers in the database."), + NETWORK("command.help.network.inDepth", "In Depth Help - /plan network", "Obtain a link to the /network page, only does so on networks."), + PLAYER("command.help.player.inDepth", "In Depth Help - /plan player", "Obtain a link to the /player page of a specific player, or the current player."), + PLAYERS("command.help.players.inDepth", "In Depth Help - /plan players", "Obtain a link to the /players page to see a list of players."), + SEARCH("command.help.search.inDepth", "In Depth Help - /plan search", "List all matching player names to given part of a name."), + INGAME("command.help.ingame.inDepth", "In Depth Help - /plan ingame", "Displays some information about the player in game."), + REGISTER("command.help.register.inDepth", "In Depth Help - /plan register", "Use without arguments to get link to register page. Use --code [code] after registration to get a user."), + UNREGISTER("command.help.unregister.inDepth", "In Depth Help - /plan unregister", "Use without arguments to unregister player linked user, or with username argument to unregister another user."), + LOGOUT("command.help.logout.inDepth", "In Depth Help - /plan logout", "Give username argument to log out another user from the panel, give * as argument to log out everyone."), + INFO("command.help.info.inDepth", "In Depth Help - /plan info", "Display the current status of the plugin."), + RELOAD("command.help.reload.inDepth", "In Depth Help - /plan reload", "Disable and enable the plugin to reload any changes in config."), + DISABLE("command.help.disable.inDepth", "In Depth Help - /plan disable", "Disable the plugin or part of it until next reload/restart."), + USERS("command.help.users.inDepth", "In Depth Help - /plan users", "Lists web users as a table."), + DB("command.help.database.inDepth", "In Depth Help - /plan db", "Use different database subcommands to change the data in some way"), + DB_BACKUP("command.help.dbBackup.inDepth", "In Depth Help - /plan db backup", "Uses SQLite to backup the target database to a file."), + DB_RESTORE("command.help.dbRestore.inDepth", "In Depth Help - /plan db restore", "Uses SQLite backup file and overwrites contents of the target database."), + DB_MOVE("command.help.dbMove.inDepth", "In Depth Help - /plan db move", "Overwrites contents in the other database with the contents in another."), + DB_HOTSWAP("command.help.dbHotswap.inDepth", "In Depth Help - /plan db hotswap", "Reloads the plugin with the other database and changes the config to match."), + DB_CLEAR("command.help.dbClear.inDepth", "In Depth Help - /plan db clear", "Clears all Plan tables, removing all Plan-data in the process."), + DB_REMOVE("command.help.dbRemove.inDepth", "In Depth Help - /plan db remove", "Removes all data linked to a player from the Current database."), + DB_UNINSTALLED("command.help.dbUninstalled.inDepth", "In Depth Help - /plan db uninstalled", "Marks a server in Plan database as uninstalled so that it will not show up in server queries."), + EXPORT("command.help.export.inDepth", "In Depth Help - /plan export", "Performs an export to export location defined in the config."), + IMPORT("command.help.import.inDepth", "In Depth Help - /plan import", "Performs an import to load data into the database."), + JSON("command.help.json.inDepth", "In Depth Help - /plan json", "Allows you to download a player's data in json format. All of it."); + private final String key; private final String identifier; private final String defaultValue; - DeepHelpLang(String identifier, String defaultValue) { + DeepHelpLang(String key, String identifier, String defaultValue) { + this.key = key; this.identifier = identifier; this.defaultValue = defaultValue; } @@ -61,6 +63,9 @@ public enum DeepHelpLang implements Lang { return identifier; } + @Override + public String getKey() {return key;} + @Override public String getDefault() { return defaultValue; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/ErrorPageLang.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/ErrorPageLang.java index 65f333de5..7b1590c73 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/ErrorPageLang.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/ErrorPageLang.java @@ -22,21 +22,24 @@ package com.djrapitops.plan.settings.locale.lang; * @author AuroraLS3 */ public enum ErrorPageLang implements Lang { - UUID_404("Player UUID was not found in the database."), - NO_SERVERS_404("No Servers online to perform the request."), - NOT_PLAYED_404("Plan has not seen this player."), - UNKNOWN_PAGE_404("Make sure you're accessing a link given by a command, Examples:

/player/{uuid/name}
/server/{uuid/name/id}

"), - UNAUTHORIZED_401("Unauthorized"), - AUTHENTICATION_FAILED_401("Authentication Failed."), - AUTH_FAIL_TIPS_401("- Ensure you have registered a user with /plan register
- Check that the username and password are correct
- Username and password are case-sensitive

If you have forgotten your password, ask a staff member to delete your old user and re-register."), - FORBIDDEN_403("Forbidden"), - ACCESS_DENIED_403("Access Denied"), - NOT_FOUND_404("Not Found"), - PAGE_NOT_FOUND_404("Page does not exist."); + UUID_404("html.error.UUIDNotFound", "Player UUID was not found in the database."), + NO_SERVERS_404("html.error.noServersOnline", "No Servers online to perform the request."), + NOT_PLAYED_404("html.error.playerNotSeen", "Plan has not seen this player."), + UNKNOWN_PAGE_404("html.error.404UnknownPage", "Make sure you're accessing a link given by a command, Examples:

/player/{uuid/name}
/server/{uuid/name/id}

"), + UNAUTHORIZED_401("html.error.401Unauthorized", "Unauthorized"), + AUTHENTICATION_FAILED_401("html.error.authFailed", "Authentication Failed."), + AUTH_FAIL_TIPS_401("html.error.authFailedTips", "- Ensure you have registered a user with /plan register
- Check that the username and password are correct
- Username and password are case-sensitive

If you have forgotten your password, ask a staff member to delete your old user and re-register."), + FORBIDDEN_403("html.error.403Forbidden", "Forbidden"), + ACCESS_DENIED_403("403AccessDenied", "Access Denied"), + NOT_FOUND_404("html.error.404NotFound", "Not Found"), + NO_SUCH_SERVER_404("html.error.serverNotSeen", "Server doesn't exist"), + PAGE_NOT_FOUND_404("html.error.404PageNotFound", "Page does not exist."); + private final String key; private final String defaultValue; - ErrorPageLang(String defaultValue) { + ErrorPageLang(String key, String defaultValue) { + this.key = key; this.defaultValue = defaultValue; } @@ -45,6 +48,11 @@ public enum ErrorPageLang implements Lang { return "HTML ERRORS - " + name(); } + @Override + public String getKey() { + return key; + } + @Override public String getDefault() { return defaultValue; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/FilterLang.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/FilterLang.java index 3b2ad9585..f6033ece6 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/FilterLang.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/FilterLang.java @@ -17,15 +17,16 @@ package com.djrapitops.plan.settings.locale.lang; public enum FilterLang implements Lang { - OPERATORS("Operators"), - NON_OPERATORS("Non operators"), - BANNED("Banned"), - NOT_BANNED("Not banned"), - ; + OPERATORS("html.query.filter.operators", "Operators"), + NON_OPERATORS("html.query.filter.nonOperators", "Non operators"), + BANNED("html.query.filter.banned", "Banned"), + NOT_BANNED("html.query.filter.notBanned", "Not banned"); + private final String key; private final String defaultValue; - FilterLang(String defaultValue) { + FilterLang(String key, String defaultValue) { + this.key = key; this.defaultValue = defaultValue; } @@ -34,6 +35,9 @@ public enum FilterLang implements Lang { return "HTML - " + name() + " (Filters)"; } + @Override + public String getKey() {return key;} + @Override public String getDefault() { return defaultValue; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/GenericLang.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/GenericLang.java index 05166fe26..a354447af 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/GenericLang.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/GenericLang.java @@ -22,17 +22,19 @@ package com.djrapitops.plan.settings.locale.lang; * @author AuroraLS3 */ public enum GenericLang implements Lang { - YES("Positive", "Yes"), - NO("Negative", "No"), - UNKNOWN("Unknown", "Unknown"), - UNAVAILABLE("Unavailable", "Unavailable"), - TODAY("Today", "'Today'"), - YESTERDAY("Yesterday", "'Yesterday'"); + YES("plugin.generic.yes", "Positive", "Yes"), + NO("plugin.generic.no", "Negative", "No"), + UNKNOWN("plugin.generic.unknown", "Unknown", "Unknown"), + UNAVAILABLE("plugin.generic.unavailable", "Unavailable", "Unavailable"), + TODAY("plugin.generic.today", "Today", "'Today'"), + YESTERDAY("plugin.generic.yesterday", "Yesterday", "'Yesterday'"); + private final String key; private final String identifier; private final String defaultValue; - GenericLang(String identifier, String defaultValue) { + GenericLang(String key, String identifier, String defaultValue) { + this.key = key; this.identifier = identifier; this.defaultValue = defaultValue; } @@ -42,6 +44,9 @@ public enum GenericLang implements Lang { return identifier; } + @Override + public String getKey() {return key;} + @Override public String getDefault() { return defaultValue; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/HelpLang.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/HelpLang.java index 27789b269..63c0b8b54 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/HelpLang.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/HelpLang.java @@ -22,60 +22,64 @@ package com.djrapitops.plan.settings.locale.lang; * @author AuroraLS3 */ public enum HelpLang implements Lang { - ARG_SERVER("CMD Arg Name - server", "server"), - ARG_NAME_UUID("CMD Arg Name - name or uuid", "name/uuid"), - ARG_CODE("CMD Arg Name - code", "${code}"), - ARG_USERNAME("CMD Arg Name - username", "username"), - ARG_FEATURE("CMD Arg Name - feature", "feature"), - ARG_SUBCOMMAND("CMD Arg Name - subcommand", "subcommand"), - ARG_BACKUP_FILE("CMD Arg Name - backup-file", "backup-file"), - ARG_EXPORT_KIND("CMD Arg Name - export kind", "export kind"), - ARG_IMPORT_KIND("CMD Arg Name - import kind", "import kind"), - DESC_ARG_SERVER_IDENTIFIER("CMD Arg - server identifier", "Name, ID or UUID of a server"), - DESC_ARG_PLAYER_IDENTIFIER("CMD Arg - player identifier", "Name or UUID of a player"), - DESC_ARG_PLAYER_IDENTIFIER_REMOVE("CMD Arg - player identifier remove", "Identifier for a player that will be removed from current database."), - DESC_ARG_CODE("CMD Arg - code", "Code used to finalize registration."), - DESC_ARG_USERNAME("CMD Arg - username", "Username of another user. If not specified player linked user is used."), - DESC_ARG_FEATURE("CMD Arg - feature", "Name of the feature to disable: ${0}"), - DESC_ARG_SUBCOMMAND("CMD Arg - subcommand", "Use the command without subcommand to see help."), - DESC_ARG_BACKUP_FILE("CMD Arg - backup-file", "Name of the backup file (case sensitive)"), - DESC_ARG_DB_BACKUP("CMD Arg - db type backup", "Type of the database to backup. Current database is used if not specified."), - DESC_ARG_DB_RESTORE("CMD Arg - db type restore", "Type of the database to restore to. Current database is used if not specified."), - DESC_ARG_DB_MOVE_FROM("CMD Arg - db type move from", "Type of the database to move data from."), - DESC_ARG_DB_MOVE_TO("CMD Arg - db type move to", "Type of the database to move data to. Can not be same as previous."), - DESC_ARG_DB_HOTSWAP("CMD Arg - db type hotswap", "Type of the database to start using."), - DESC_ARG_DB_REMOVE("CMD Arg - db type clear", "Type of the database to remove all data from."), + ARG_SERVER("command.argument.server.name", "CMD Arg Name - server", "server"), + ARG_NAME_UUID("command.argument.nameOrUUID.name", "CMD Arg Name - name or uuid", "name/uuid"), + ARG_CODE("command.argument.code.name", "CMD Arg Name - code", "${code}"), + ARG_USERNAME("command.argument.username.name", "CMD Arg Name - username", "username"), + ARG_FEATURE("command.argument.feature.name", "CMD Arg Name - feature", "feature"), + ARG_SUBCOMMAND("command.argument.subcommand.name", "CMD Arg Name - subcommand", "subcommand"), + ARG_BACKUP_FILE("command.argument.backupFile.name", "CMD Arg Name - backup-file", "backup-file"), + ARG_EXPORT_KIND("command.argument.exportKind", "CMD Arg Name - export kind", "export kind"), + ARG_IMPORT_KIND("command.argument.importKind", "CMD Arg Name - import kind", "import kind"), + DESC_ARG_SERVER_IDENTIFIER("command.argument.server.description", "CMD Arg - server identifier", "Name, ID or UUID of a server"), + DESC_ARG_PLAYER_IDENTIFIER("command.argument.nameOrUUID.description", "CMD Arg - player identifier", "Name or UUID of a player"), + DESC_ARG_PLAYER_IDENTIFIER_REMOVE("command.argument.nameOrUUID.removeDescription", "CMD Arg - player identifier remove", "Identifier for a player that will be removed from current database."), + DESC_ARG_CODE("command.argument.code.description", "CMD Arg - code", "Code used to finalize registration."), + DESC_ARG_USERNAME("command.argument.username.description", "CMD Arg - username", "Username of another user. If not specified player linked user is used."), + DESC_ARG_FEATURE("command.argument.feature.description", "CMD Arg - feature", "Name of the feature to disable: ${0}"), + DESC_ARG_SUBCOMMAND("command.argument.subcommand.description", "CMD Arg - subcommand", "Use the command without subcommand to see help."), + DESC_ARG_BACKUP_FILE("command.argument.backupFile.description", "CMD Arg - backup-file", "Name of the backup file (case sensitive)"), + DESC_ARG_DB_BACKUP("command.argument.dbBackup.description", "CMD Arg - db type backup", "Type of the database to backup. Current database is used if not specified."), + DESC_ARG_DB_RESTORE("command.argument.dbRestore.description", "CMD Arg - db type restore", "Type of the database to restore to. Current database is used if not specified."), + DESC_ARG_DB_MOVE_FROM("command.argument.dbTypeMoveFrom.description", "CMD Arg - db type move from", "Type of the database to move data from."), + DESC_ARG_DB_MOVE_TO("command.argument.dbTypeMoveTo.description", "CMD Arg - db type move to", "Type of the database to move data to. Can not be same as previous."), + DESC_ARG_DB_HOTSWAP("command.argument.dbTypeHotswap.description", "CMD Arg - db type hotswap", "Type of the database to start using."), + DESC_ARG_DB_REMOVE("command.argument.dbTypeRemove.description", "CMD Arg - db type clear", "Type of the database to remove all data from."), - SERVER("Command Help - /plan server", "View the Server Page"), - SERVERS("Command Help - /plan servers", "List servers in Database"), - NETWORK("Command Help - /plan network", "View the Network Page"), - PLAYER("Command Help - /plan player", "View a Player Page"), - PLAYERS("Command Help - /plan players", "View the Players Page"), - SEARCH("Command Help - /plan search", "Search for a player name"), - INGAME("Command Help - /plan ingame", "View Player info in game"), - REGISTER("Command Help - /plan register", "Register a user for Plan website"), - UNREGISTER("Command Help - /plan unregister", "Unregister a user of Plan website"), - INFO("Command Help - /plan info", "Information about the plugin"), - RELOAD("Command Help - /plan reload", "Restart Plan"), - DISABLE("Command Help - /plan disable", "Disable the plugin or part of it"), - USERS("Command Help - /plan users", "List all web users"), - DB("Command Help - /plan db", "Manage Plan database"), - DB_BACKUP("Command Help - /plan db backup", "Backup data of a database to a file"), - DB_RESTORE("Command Help - /plan db restore", "Restore data from a file to a database"), - DB_MOVE("Command Help - /plan db move", "Move data between databases"), - DB_HOTSWAP("Command Help - /plan db hotswap", "Change Database quickly"), - DB_CLEAR("Command Help - /plan db clear", "Remove ALL Plan data from a database"), - DB_REMOVE("Command Help - /plan db remove", "Remove player's data from Current database"), - DB_UNINSTALLED("Command Help - /plan db uninstalled", "Set a server as uninstalled in the database."), - EXPORT("Command Help - /plan export", "Export html or json files manually"), - IMPORT("Command Help - /plan import", "Import data"), - JSON("Command Help - /plan json", "View json of Player's raw data."), - LOGOUT("Command Help - /plan logout", "Log out other users from the panel."); + SERVER("command.help.server.description", "Command Help - /plan server", "View the Server Page"), + SERVERS("command.help.servers.description", "Command Help - /plan servers", "List servers in Database"), + NETWORK("command.help.network.description", "Command Help - /plan network", "View the Network Page"), + PLAYER("command.help.player.description", "Command Help - /plan player", "View a Player Page"), + PLAYERS("command.help.players.description", "Command Help - /plan players", "View the Players Page"), + SEARCH("command.help.search.description", "Command Help - /plan search", "Search for a player name"), + INGAME("command.help.ingame.description", "Command Help - /plan ingame", "View Player info in game"), + REGISTER("command.help.register.description", "Command Help - /plan register", "Register a user for Plan website"), + UNREGISTER("command.help.unregister.description", "Command Help - /plan unregister", "Unregister a user of Plan website"), + INFO("command.help.info.description", "Command Help - /plan info", "Information about the plugin"), + RELOAD("command.help.reload.description", "Command Help - /plan reload", "Restart Plan"), + DISABLE("command.help.disable.description", "Command Help - /plan disable", "Disable the plugin or part of it"), + USERS("command.help.users.description", "Command Help - /plan users", "List all web users"), + DB("command.help.database.description", "Command Help - /plan db", "Manage Plan database"), + DB_BACKUP("command.help.dbBackup.description", "Command Help - /plan db backup", "Backup data of a database to a file"), + DB_RESTORE("command.help.dbRestore.description", "Command Help - /plan db restore", "Restore data from a file to a database"), + DB_MOVE("command.help.dbMove.description", "Command Help - /plan db move", "Move data between databases"), + DB_HOTSWAP("command.help.dbHotswap.description", "Command Help - /plan db hotswap", "Change Database quickly"), + DB_CLEAR("command.help.dbClear.description", "Command Help - /plan db clear", "Remove ALL Plan data from a database"), + DB_REMOVE("command.help.dbRemove.description", "Command Help - /plan db remove", "Remove player's data from Current database"), + DB_UNINSTALLED("command.help.dbUninstalled.description", "Command Help - /plan db uninstalled", "Set a server as uninstalled in the database."), + EXPORT("command.help.export.description", "Command Help - /plan export", "Export html or json files manually"), + IMPORT("command.help.import.description", "Command Help - /plan import", "Import data"), + JSON("command.help.json.description", "Command Help - /plan json", "View json of Player's raw data."), + LOGOUT("command.help.logout.description", "Command Help - /plan logout", "Log out other users from the panel."), + JOIN_ADDRESS_REMOVAL("command.help.removejoinaddresses.description", "Command Help - /plan db removejoinaddresses", "Remove join addresses of a specified server"), + ONLINE_UUID_MIGRATION("command.help.migrateToOnlineUuids.description", "Command Help - /plan db migratetoonlineuuids", "Migrate offline uuid data to online uuids"); private final String identifier; + private final String key; private final String defaultValue; - HelpLang(String identifier, String defaultValue) { + HelpLang(String key, String identifier, String defaultValue) { + this.key = key; this.identifier = identifier; this.defaultValue = defaultValue; } @@ -85,6 +89,9 @@ public enum HelpLang implements Lang { return identifier; } + @Override + public String getKey() {return key;} + @Override public String getDefault() { return defaultValue; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/HtmlLang.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/HtmlLang.java index 2a48ef8d5..609a7bb4b 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/HtmlLang.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/HtmlLang.java @@ -23,252 +23,290 @@ package com.djrapitops.plan.settings.locale.lang; */ public enum HtmlLang implements Lang { - TITLE_NETWORK("Network"), - // Network Page Navigation - SIDE_INFORMATION("INFORMATION"), // Nav group title - SIDE_NETWORK_OVERVIEW("Network Overview"), - SIDE_SERVERS("Servers"), - SIDE_OVERVIEW("Overview"), - SIDE_SESSIONS("Sessions"), - SIDE_PLAYERBASE("Playerbase"), - SIDE_PLAYER_LIST("Player List"), - SIDE_PLAYERBASE_OVERVIEW("Playerbase Overview"), - SIDE_GEOLOCATIONS("Geolocations"), - SIDE_PLUGINS("PLUGINS"), // Nav group title - SIDE_LINKS("LINKS"), - UNIT_NO_DATA("No Data"), // Generic + TITLE_NETWORK("html.label.network", "Network"), + // Sidebar + SIDE_INFORMATION("html.label.information", "INFORMATION"), // Nav group title + SIDE_NETWORK_OVERVIEW("html.label.networkOverview", "Network Overview"), + SIDE_SERVERS("html.label.servers", "Servers"), + SIDE_OVERVIEW("html.label.overview", "Overview"), + SIDE_SESSIONS("html.label.sessions", "Sessions"), + SIDE_PLAYERBASE("html.label.playerbase", "Playerbase"), + SIDE_PLAYER_LIST("html.label.playerList", "Player List"), + SIDE_GEOLOCATIONS("html.label.geolocations", "Geolocations"), + SIDE_LINKS("html.label.links", "LINKS"), + SIDE_PERFORMANCE("html.label.performance", "Performance"), + SIDE_PLUGINS_OVERVIEW("html.label.pluginsOverview", "Plugins Overview"), + QUERY_MAKE("html.label.query", "Make a query"), + UNIT_NO_DATA("generic.noData", "No Data"), // Generic + GRAPH_NO_DATA("html.label.noDataToDisplay", "No Data to Display"), // Modals - TITLE_THEME_SELECT("Theme Select"), - LINK_NIGHT_MODE("Night Mode"), - TEXT_PLUGIN_INFORMATION("Information about the plugin"), - TEXT_LICENSED_UNDER("Player Analytics is developed and licensed under"), - LINK_WIKI("Plan Wiki, Tutorials & Documentation"), - LINK_ISSUES("Report Issues"), - LINK_DISCORD("General Support on Discord"), - AND_BUG_REPORTERS("& Bug reporters!"), - TEXT_DEVELOPED_BY("is developed by"), - TEXT_CONTRIBUTORS_THANKS("In addition following awesome people have contributed:"), - TEXT_CONTRIBUTORS_CODE("code contributor"), - TEXT_CONTRIBUTORS_LOCALE("translator"), - TEXT_CONTRIBUTORS_MONEY("Extra special thanks to those who have monetarily supported the development."), - TEXT_METRICS("bStats Metrics"), - TITLE_VERSION("Version"), - TITLE_IS_AVAILABLE("is Available"), - TEXT_VERSION("A new version has been released and is now available for download."), - TEXT_DEV_VERSION("This version is a DEV release."), - LINK_CHANGELOG("View Changelog"), - LINK_DOWNLOAD("Download"), + TITLE_THEME_SELECT("html.label.themeSelect", "Theme Select"), + LINK_NIGHT_MODE("html.button.nightMode", "Night Mode"), + TEXT_PLUGIN_INFORMATION("html.modal.info.text", "Information about the plugin"), + TEXT_LICENSED_UNDER("html.modal.info.license", "Player Analytics is developed and licensed under"), + LINK_WIKI("html.modal.info.wiki", "Plan Wiki, Tutorials & Documentation"), + LINK_ISSUES("html.modal.info.bugs", "Report Issues"), + LINK_DISCORD("html.modal.info.discord", "General Support on Discord"), + AND_BUG_REPORTERS("html.modal.info.contributors.bugreporters", "& Bug reporters!"), + TEXT_DEVELOPED_BY("html.modal.info.developer", "is developed by"), + TEXT_CONTRIBUTORS_THANKS("html.modal.info.contributors.text", "In addition following awesome people have contributed:"), + TEXT_CONTRIBUTORS_CODE("html.modal.info.contributors.code", "code contributor"), + TEXT_CONTRIBUTORS_LOCALE("html.modal.info.contributors.translator", "translator"), + TEXT_CONTRIBUTORS_MONEY("html.modal.info.contributors.donate", "Extra special thanks to those who have monetarily supported the development."), + TEXT_METRICS("html.modal.info.metrics", "bStats Metrics"), + TITLE_VERSION("html.modal.version.title", "Version"), + TITLE_IS_AVAILABLE("html.modal.version.available", "is Available"), + TEXT_VERSION("html.modal.version.text", "A new version has been released and is now available for download."), + TEXT_DEV_VERSION("html.modal.version.dev", "This version is a DEV release."), + LINK_CHANGELOG("html.modal.version.changelog", "View Changelog"), + LINK_DOWNLOAD("html.modal.version.download", "Download"), // Network overview tab - TITLE_GRAPH_NETWORK_ONLINE_ACTIVITY("Network Online Activity"), - TITLE_GRAPH_DAY_BY_DAY("Day by Day"), - TITLE_GRAPH_HOUR_BY_HOUR("Hour by Hour"), - UNIT_THE_PLAYERS("Players"), - TITLE_LAST_24_HOURS("Last 24 hours"), - TITLE_LAST_7_DAYS("Last 7 days"), - TITLE_LAST_30_DAYS("Last 30 days"), - LABEL_UNIQUE_PLAYERS("Unique Players"), - LABEL_NEW_PLAYERS("New Players"), - LABEL_REGULAR_PLAYERS("Regular Players"), - LABEL_TOTAL_PLAYERS("Total Players"), - TITLE_NETWORK_AS_NUMBERS("Network as Numbers"), - LABEL_PLAYERS_ONLINE("Players Online"), - LABEL_TOTAL_PLAYTIME("Total Playtime"), - LABEL_PLAYTIME("Playtime"), - LABEL_ACTIVE_PLAYTIME("Active Playtime"), - LABEL_LAST_PEAK("Last Peak"), - LABEL_BEST_PEAK("Best Peak"), - LABEL_AVG_PLAYTIME("Average Playtime"), - LABEL_AVG_SESSIONS("Average Sessions"), - LABEL_AVG_ACTIVE_PLAYTIME("Average Active Playtime"), - LABEL_AVG_AFK_TIME("Average AFK Time"), - LABEL_PER_PLAYER("/ Player"), - LABEL_AVG_SESSION_LENGTH("Average Session Length"), - TITLE_WEEK_COMPARISON("Week Comparison"), - TITLE_TRENDS("Trends for 30 days"), - TITLE_TREND("Trend"), - COMPARING_7_DAYS("Comparing 7 days"), + TITLE_GRAPH_NETWORK_ONLINE_ACTIVITY("html.label.networkOnlineActivity", "Network Online Activity"), + TITLE_GRAPH_DAY_BY_DAY("html.label.dayByDay", "Day by Day"), + TITLE_GRAPH_HOUR_BY_HOUR("html.label.hourByHour", "Hour by Hour"), + UNIT_THE_PLAYERS("html.unit.players", "Players"), + TITLE_LAST_24_HOURS("html.label.last24hours", "Last 24 hours"), + TITLE_LAST_7_DAYS("html.label.last7days", "Last 7 days"), + TITLE_LAST_30_DAYS("html.label.last30days", "Last 30 days"), + LABEL_UNIQUE_PLAYERS("html.label.uniquePlayers", "Unique Players"), + LABEL_UNIQUE_PLAYERS_7_DAYS("html.label.uniquePlayers7days", "Unique Players (7 days)"), + LABEL_NEW_PLAYERS("html.label.newPlayers", "New Players"), + LABEL_NEW_PLAYERS_7_DAYS("html.label.newPlayers7days", "New Players (7 days)"), + LABEL_REGULAR_PLAYERS("html.label.regularPlayers", "Regular Players"), + LABEL_TOTAL_PLAYERS("html.label.totalPlayers", "Total Players"), + TITLE_NETWORK_AS_NUMBERS("html.label.networkAsNumbers", "Network as Numbers"), + LABEL_PLAYERS_ONLINE("html.label.playersOnline", "Players Online"), + LABEL_PLAYERS_ONLINE_NOW("html.label.playersOnlineNow", "Players Online (Now)"), + LABEL_TOTAL_PLAYTIME("html.label.totalPlaytime", "Total Playtime"), + LABEL_PLAYTIME("html.label.playtime", "Playtime"), + LABEL_ACTIVE_PLAYTIME("html.label.activePlaytime", "Active Playtime"), + LABEL_LAST_PEAK("html.label.lastPeak", "Last Peak"), + LABEL_BEST_PEAK("html.label.bestPeak", "Best Peak"), + LABEL_AVG_PLAYTIME("html.label.averagePlaytime", "Average Playtime"), + LABEL_AVG_SESSIONS("html.label.averageSessions", "Average Sessions"), + LABEL_AVG_ACTIVE_PLAYTIME("html.label.averageActivePlaytime", "Average Active Playtime"), + LABEL_AVG_AFK_TIME("html.label.averageAfkTime", "Average AFK Time"), + LABEL_PER_PLAYER("html.label.perPlayer", "/ Player"), + LABEL_AVG_SESSION_LENGTH("html.label.averageSessionLength", "Average Session Length"), + TITLE_WEEK_COMPARISON("html.label.weekComparison", "Week Comparison"), + TITLE_TRENDS("html.label.trends30days", "Trends for 30 days"), + TITLE_TREND("html.label.trend", "Trend"), + COMPARING_7_DAYS("html.label.comparing7days", "Comparing 7 days"), // Servers tab - TITLE_ONLINE_ACTIVITY("Online Activity"), - TITLE_30_DAYS("30 days"), - TITLE_AS_NUMBERS("as Numbers"), - LABEL_AVG_TPS("Average TPS"), - LABEL_AVG_ENTITIES("Average Entities"), - LABEL_AVG_CHUNKS("Average Chunks"), - LABEL_LOW_TPS("Low TPS Spikes"), - LABEL_DOWNTIME("Downtime"), - // Sessions tab - TITLE_RECENT_SESSIONS("Recent Sessions"), - TITLE_PLAYER("Player"), - TITLE_SESSION_START("Session Started"), - TITLE_LENGTH(" Length"), - TITLE_SERVER("Server"), // Can cause issue with datatables.js - TITLE_MOST_PLAYED_WORLD("Most played World"), - TEXT_CLICK_TO_EXPAND("Click to expand"), - TITLE_SERVER_PLAYTIME_30("Server Playtime for 30 days"), - TITLE_INSIGHTS("Insights for 30 days"), - LABEL_AFK_TIME("AFK Time"), - LABEL_AFK("AFK"), - // Playerbase overview tab - TITLE_PLAYERBASE_DEVELOPMENT("Playerbase development"), - TITLE_CURRENT_PLAYERBASE("Current Playerbase"), - TITLE_JOIN_ADDRESSES("Join Addresses"), - COMPARING_60_DAYS("Comparing 30d ago to Now"), - TITLE_30_DAYS_AGO("30 days ago"), - TITLE_NOW("Now"), - LABEL_PER_REGULAR_PLAYER("/ Regular Player"), - LABEL_NEW("New"), - LABEL_REGULAR("Regular"), - LABEL_INACTIVE("Inactive"), - SIDE_TO_MAIN_PAGE("to main page"), + TITLE_ONLINE_ACTIVITY("html.label.onlineActivity", "Online Activity"), + TITLE_30_DAYS("html.label.thirtyDays", "30 days"), + TITLE_AS_NUMBERS("html.label.asNumbers", "as Numbers"), + LABEL_AVG_TPS("html.label.averageTps", "Average TPS"), + LABEL_AVG_TPS_7_DAYS("html.label.averageTps7days", "Average TPS (7 days)"), + LABEL_AVG_PLAYERS("html.label.averagePlayers", "Average Players"), + LABEL_CPU("html.label.cpu", "CPU"), + LABEL_CPU_USAGE("html.label.cpuUsage", "CPU Usage"), + LABEL_AVG_CPU("html.label.averageCpuUsage", "Average CPU Usage"), + LABEL_RAM("html.label.ram", "RAM"), + LABEL_RAM_USAGE("html.label.ramUsage", "RAM Usage"), + LABEL_AVG_RAM("html.label.averageRamUsage", "Average RAM Usage"), + LABEL_AVG_ENTITIES("html.label.averageEntities", "Average Entities"), + LABEL_AVG_CHUNKS("html.label.averageChunks", "Average Chunks"), + LABEL_LOW_TPS("html.label.lowTpsSpikes", "Low TPS Spikes"), + LABEL_LOW_TPS_7_DAYS("html.label.lowTpsSpikes7days", "Low TPS Spikes (7 days)"), + LABEL_DOWNTIME("html.label.downtime", "Downtime"), + // Sessions tab #tab-sessions-overview + TITLE_RECENT_SESSIONS("html.label.recentSessions", "Recent Sessions"), + TITLE_PLAYER("html.label.player", "Player"), + TITLE_SESSION_START("html.label.sessionStart", "Session Started"), + TITLE_LENGTH("html.label.length", " Length"), + TITLE_SERVER("html.label.server", "Server"), // Can cause issue with datatables.js + TITLE_MOST_PLAYED_WORLD("html.label.mostPlayedWorld", "Most played World"), + TEXT_CLICK_TO_EXPAND("html.text.clickToExpand", "Click to expand"), + TITLE_SERVER_PLAYTIME_30("html.label.serverPlaytime30days", "Server Playtime for 30 days"), + TITLE_INSIGHTS("html.label.insights30days", "Insights for 30 days"), + LABEL_AFK_TIME("html.label.afkTime", "AFK Time"), + LABEL_AFK("html.label.afk", "AFK"), + // Playerbase overview tab #tab-playerbase-overview + TITLE_PLAYERBASE_OVERVIEW("html.label.playerbaseOverview", "Playerbase Overview"), + TITLE_PLAYERBASE_DEVELOPMENT("html.label.playerbaseDevelopment", "Playerbase development"), + TITLE_CURRENT_PLAYERBASE("html.label.currentPlayerbase", "Current Playerbase"), + TITLE_JOIN_ADDRESSES("html.label.joinAddresses", "Join Addresses"), + TITLE_LATEST_JOIN_ADDRESSES("html.label.latestJoinAddresses", "Latest Join Addresses"), + COMPARING_60_DAYS("html.text.comparing30daysAgo", "Comparing 30d ago to Now"), + TITLE_30_DAYS_AGO("html.label.thirtyDaysAgo", "30 days ago"), + TITLE_NOW("html.label.now", "Now"), + LABEL_PER_REGULAR_PLAYER("html.label.perRegularPlayer", "/ Regular Player"), + LABEL_NEW("html.label.new", "New"), + LABEL_REGULAR("html.label.regular", "Regular"), + LABEL_INACTIVE("html.label.inactive", "Inactive"), + SIDE_TO_MAIN_PAGE("html.label.toMainPage", "to main page"), // Geolocations tab - TITLE_CONNECTION_INFO("Connection Information"), - TITLE_COUNTRY("Country"), - TITLE_AVG_PING("Average Ping"), - TITLE_WORST_PING("Worst Ping"), - TITLE_BEST_PING("Best Ping"), - TEXT_NO_EXTENSION_DATA("No Extension Data"), + TITLE_CONNECTION_INFO("html.label.connectionInfo", "Connection Information"), + TITLE_COUNTRY("html.label.country", "Country"), + TITLE_AVG_PING("html.label.averagePing", "Average Ping"), + TITLE_WORST_PING("html.label.worstPing", "Worst Ping"), + TITLE_BEST_PING("html.label.bestPing", "Best Ping"), + TEXT_NO_EXTENSION_DATA("html.text.noExtensionData", "No Extension Data"), // Server page - LINK_BACK_NETWORK("Network page"), - SIDE_PVP_PVE("PvP & PvE"), - SIDE_PERFORMANCE("Performance"), - LABEL_RETENTION("New Player Retention"), - DESCRIBE_RETENTION_PREDICTION("This value is a prediction based on previous players."), - TITLE_SERVER_AS_NUMBERS("Server as Numbers"), - TITLE_ONLINE_ACTIVITY_AS_NUMBERS("Online Activity as Numbers"), - COMPARING_15_DAYS("Comparing 15 days"), - TITLE_GRAPH_PUNCHCARD("Punchcard for 30 Days"), - LABEL_ONLINE_FIRST_JOIN("Players online on first join"), - LABEL_FIRST_SESSION_LENGTH("First session length"), - LABEL_LONE_JOINS("Lone joins"), - LABEL_LONE_NEW_JOINS("Lone newbie joins"), - LABEL_MOST_ACTIVE_GAMEMODE("Most Active Gamemode"), - LABEL_SERVER_OCCUPIED("Server occupied"), - TITLE_PVP_PVE_NUMBERS("PvP & PvE as Numbers"), - LABEL_1ST_WEAPON("Deadliest PvP Weapon"), - LABEL_2ND_WEAPON("2nd PvP Weapon"), - LABEL_3RD_WEAPON("3rd PvP Weapon"), - LABEL_AVG_KDR("Average KDR"), - LABEL_PLAYER_KILLS("Player Kills"), - LABEL_AVG_MOB_KDR("Average Mob KDR"), - LABEL_MOB_KILLS("Mob Kills"), - LABEL_MOB_DEATHS("Mob Caused Deaths"), - LABEL_DEATHS("Deaths"), - TITLE_RECENT_KILLS("Recent Kills"), - TITLE_ALL("All"), - TITLE_TPS("TPS"), - TITLE_CPU_RAM("CPU & RAM"), - TITLE_WORLD("World Load"), - TITLE_PING("Ping"), - TITLE_DISK("Disk Space"), - LABEL_AVG("Average"), - TITLE_PERFORMANCE_AS_NUMBERS("Performance as Numbers"), - LABEL_SERVER_DOWNTIME("Server Downtime"), - LABEL_DURING_LOW_TPS("During Low TPS Spikes:"), - TEXT_NO_LOW_TPS("No low tps spikes"), + LINK_BACK_NETWORK("html.label.networkPage", "Network page"), + SIDE_PVP_PVE("html.label.pvpPve", "PvP & PvE"), + LABEL_RETENTION("html.label.newPlayerRetention", "New Player Retention"), + LABEL_RETENTION_GENERAL("html.label.playerRetention", "Player Retention"), + DESCRIBE_RETENTION_PREDICTION("html.description.newPlayerRetention", "This value is a prediction based on previous players."), + TITLE_SERVER_AS_NUMBERS("html.label.serverAsNumberse", "Server as Numbers"), + TITLE_ONLINE_ACTIVITY_AS_NUMBERS("html.label.onlineActivityAsNumbers", "Online Activity as Numbers"), + COMPARING_15_DAYS("html.text.comparing15days", "Comparing 15 days"), + TITLE_GRAPH_PUNCHCARD("html.label.punchcard30days", "Punchcard for 30 Days"), + LABEL_ONLINE_FIRST_JOIN("html.label.onlineOnFirstJoin", "Players online on first join"), + LABEL_FIRST_SESSION_LENGTH_AVERAGE("html.label.firstSessionLength.average", "Average first session length"), + LABEL_FIRST_SESSION_LENGTH_MEDIAN("html.label.firstSessionLength.median", "Median first session length"), + LABEL_LONE_JOINS("html.label.loneJoins", "Lone joins"), + LABEL_LONE_NEW_JOINS("html.label.loneNewbieJoins", "Lone newbie joins"), + LABEL_MOST_ACTIVE_GAMEMODE("html.label.mostActiveGamemode", "Most Active Gamemode"), + LABEL_SERVER_OCCUPIED("html.label.serverOccupied", "Server occupied"), + TITLE_PVP_PVE_NUMBERS("html.label.pvpPveAsNumbers", "PvP & PvE as Numbers"), + LABEL_1ST_WEAPON("html.label.deadliestWeapon", "Deadliest PvP Weapon"), + LABEL_2ND_WEAPON("html.label.secondDeadliestWeapon", "2nd PvP Weapon"), + LABEL_3RD_WEAPON("html.label.thirdDeadliestWeapon", "3rd PvP Weapon"), + LABEL_AVG_KDR("html.label.averageKdr", "Average KDR"), + LABEL_PLAYER_KILLS("html.label.playerKills", "Player Kills"), + LABEL_AVG_MOB_KDR("html.label.averageMobKdr", "Average Mob KDR"), + LABEL_MOB_KILLS("html.label.mobKills", "Mob Kills"), + LABEL_MOB_DEATHS("html.label.mobDeaths", "Mob Caused Deaths"), + LABEL_DEATHS("html.label.deaths", "Deaths"), + TITLE_RECENT_KILLS("html.label.recentKills", "Recent Kills"), + TITLE_ALL("html.label.all", "All"), + TITLE_TPS("html.label.tps", "TPS"), + TITLE_CPU_RAM("html.label.cpuRam", "CPU & RAM"), + TITLE_WORLD("html.label.world", "World Load"), + TITLE_PING("html.label.ping", "Ping"), + TITLE_DISK("html.label.disk", "Disk Space"), + LABEL_AVG("html.label.average", "Average"), + TITLE_PERFORMANCE_AS_NUMBERS("html.label.performanceAsNumbers", "Performance as Numbers"), + LABEL_SERVER_DOWNTIME("html.label.serverDowntime", "Server Downtime"), + LABEL_TOTAL_SERVER_DOWNTIME("html.label.totalServerDowntime", "Total Server Downtime"), + LABEL_AVERAGE_SERVER_DOWNTIME("html.label.averageServerDowntime", "Average Downtime / Server"), + LABEL_DURING_LOW_TPS("html.label.duringLowTps", "During Low TPS Spikes:"), + TEXT_NO_LOW_TPS("html.text.noLowTps", "No low tps spikes"), // Player Page - TITLE_SEEN_NICKNAMES("Seen Nicknames"), - LABEL_LAST_SEEN("Last Seen"), - TITLE_LAST_CONNECTED("Last Connected"), - LABEL_PLAYER_DEATHS("Player Caused Deaths"), - TITLE_PVP_KILLS("Recent PvP Kills"), - TITLE_PVP_DEATHS("Recent PvP Deaths"), - TITLE_SERVER_PLAYTIME("Server Playtime"), - LINK_BACK_SERVER("Server page"), - SIDE_SERVERS_TITLE("SERVERS"), + TITLE_SEEN_NICKNAMES("html.label.seenNicknames", "Seen Nicknames"), + LABEL_LAST_SEEN("html.label.lastSeen", "Last Seen"), + TITLE_LAST_CONNECTED("html.label.lastConnected", "Last Connected"), + LABEL_PLAYER_DEATHS("html.label.playerDeaths", "Player Caused Deaths"), + TITLE_PVP_KILLS("html.label.recentPvpKills", "Recent PvP Kills"), + TITLE_PVP_DEATHS("html.label.recentPvpDeaths", "Recent PvP Deaths"), + TITLE_SERVER_PLAYTIME("html.label.serverPlaytime", "Server Playtime"), + LINK_BACK_SERVER("html.label.serverPage", "Server page"), + SIDE_SERVERS_TITLE("html.label.serversTitle", "SERVERS"), // Were missing - TITLE_SERVER_OVERVIEW("Server Overview"), - TITLE_ONLINE_ACTIVITY_OVERVIEW("Online Activity Overview"), - PER_DAY("/ Day"), - TITLE_WORLD_PLAYTIME("World Playtime"), - TITLE_PLAYER_OVERVIEW("Player Overview"), - LABEL_LONGEST_SESSION("Longest Session"), - LABEL_REGISTERED("Registered"), - TITLE_TITLE_PLAYER_PUNCHCARD("Punchcard"), - TITLE_ALL_TIME("All Time"), - LABEL_NAME("Name"), + TITLE_SERVER_OVERVIEW("html.label.serverOverview", "Server Overview"), + TITLE_ONLINE_ACTIVITY_OVERVIEW("html.label.playersOnlineOverview", "Online Activity Overview"), + PER_DAY("html.label.perDay", "/ Day"), + TITLE_WORLD_PLAYTIME("html.label.worldPlaytime", "World Playtime"), + TITLE_PLAYER_OVERVIEW("html.label.playerOverview", "Player Overview"), + LABEL_LONGEST_SESSION("html.label.longestSession", "Longest Session"), + LABEL_REGISTERED("html.label.registered", "Registered"), + TITLE_TITLE_PLAYER_PUNCHCARD("html.label.punchcard", "Punchcard"), + TITLE_ALL_TIME("html.label.allTime", "All Time"), + LABEL_NAME("html.label.name", "Name"), + // React + LABEL_TITLE_SESSION_CALENDAR("html.label.sessionCalendar", "Session Calendar"), + LABEL_TITLE_SERVER_CALENDAR("html.label.serverCalendar", "Server Calendar"), + LABEL_LABEL_JOIN_ADDRESS("html.label.joinAddress", "Join Address"), + LABEL_LABEL_SESSION_MEDIAN("html.label.medianSessionLength", "Median Session Length"), + LABEL_LABEL_KDR("html.label.kdr", "KDR"), + LABEL_TITLE_INSIGHTS("html.label.insights", "Insights"), // ---------------------------------- // OLD // ---------------------------------- - NAV_PLUGINS("Plugins"), - PLAYERS_TEXT("Players"), - TOTAL_PLAYERS("Total Players"), - UNIQUE_CALENDAR("Unique:"), - NEW_CALENDAR("New:"), - SESSION("Session"), - KILLED("Killed"), - LABEL_LOADED_ENTITIES("Loaded Entities"), - LABEL_LOADED_CHUNKS("Loaded Chunks"), - LABEL_ENTITIES("Entities"), - LABEL_FREE_DISK_SPACE("Free Disk Space"), - ONLINE(" Online"), - OFFLINE(" Offline"), - LABEL_TIMES_KICKED("Times Kicked"), - TOTAL_ACTIVE_TEXT("Total Active"), - TOTAL_AFK("Total AFK"), - LABEL_SESSION_MEDIAN("Session Median"), - LABEL_ACTIVITY_INDEX("Activity Index"), - INDEX_ACTIVE("Active"), - INDEX_VERY_ACTIVE("Very Active"), - INDEX_REGULAR("Regular"), - INDEX_IRREGULAR("Irregular"), - INDEX_INACTIVE("Inactive"), - LABEL_FAVORITE_SERVER("Favorite Server"), - LABEL_NICKNAME("Nickname"), - LOCAL_MACHINE("Local Machine"), - TITLE_CALENDAR(" Calendar"), - LABEL_OPERATOR("Operator"), - LABEL_BANNED("Banned"), - LABEL_MOB_KDR("Mob KDR"), - WITH("With"), - NO_KILLS("No Kills"), - LABEL_MAX_FREE_DISK("Max Free Disk"), - LABEL_MIN_FREE_DISK("Min Free Disk"), + NAV_PLUGINS("html.label.plugins", "Plugins"), + PLAYERS_TEXT("html.label.players", "Players"), + TOTAL_PLAYERS("html.label.totalPlayersOld", "Total Players"), + UNIQUE_CALENDAR("html.calendar.unique", "Unique:"), + NEW_CALENDAR("html.calendar.new", "New:"), + SESSION("html.label.session", "Session"), + KILLED("html.label.killed", "Killed"), + LABEL_LOADED_ENTITIES("html.label.loadedEntities", "Loaded Entities"), + LABEL_LOADED_CHUNKS("html.label.loadedChunks", "Loaded Chunks"), + LABEL_ENTITIES("html.label.entities", "Entities"), + LABEL_FREE_DISK_SPACE("html.label.diskSpace", "Free Disk Space"), + ONLINE("html.value.online", " Online"), + OFFLINE("html.value.offline", " Offline"), + LABEL_TIMES_KICKED("html.label.timesKicked", "Times Kicked"), + TOTAL_ACTIVE_TEXT("html.label.totalActive", "Total Active"), + TOTAL_AFK("html.label.totalAfk", "Total AFK"), + LABEL_SESSION_MEDIAN("html.label.sessionMedian", "Session Median"), + LABEL_ACTIVITY_INDEX("html.label.activityIndex", "Activity Index"), + INDEX_ACTIVE("html.label.active", "Active"), + INDEX_VERY_ACTIVE("html.label.veryActive", "Very Active"), + INDEX_REGULAR("html.label.indexRegular", "Regular"), + INDEX_IRREGULAR("html.label.irregular", "Irregular"), + INDEX_INACTIVE("html.label.indexInactive", "Inactive"), + LABEL_FAVORITE_SERVER("html.label.favoriteServer", "Favorite Server"), + LABEL_NICKNAME("html.label.nickname", "Nickname"), + LOCAL_MACHINE("html.value.localMachine", "Local Machine"), + TITLE_CALENDAR("html.label.calendar", " Calendar"), + LABEL_OPERATOR("html.label.operator", "Operator"), + LABEL_BANNED("html.label.banned", "Banned"), + LABEL_MOB_KDR("html.label.mobKdr", "Mob KDR"), + WITH("html.value.with", "With"), + NO_KILLS("html.value.noKills", "No Kills"), + LABEL_MAX_FREE_DISK("html.label.maxFreeDisk", "Max Free Disk"), + LABEL_MIN_FREE_DISK("html.label.minFreeDisk", "Min Free Disk"), + LABEL_CURRENT_UPTIME("html.label.currentUptime", "Current Uptime"), + LABEL_TOTAL("html.label.total", "Total"), + LABEL_ALPHABETICAL("html.label.alphabetical", "Alphabetical"), + LABEL_SORT_BY("html.label.sortBy", "Sort By"), + LABEL_STACKED("html.label.stacked", "Stacked"), + LABEL_PROJECTION("html.label.geoProjection.dropdown", "Select projection"), + LABEL_PROJECTION_MILLER("html.label.geoProjection.miller", "Miller"), + LABEL_PROJECTION_MERCATOR("html.label.geoProjection.mercator", "Mercator"), + LABEL_PROJECTION_EQUAL_EARTH("html.label.geoProjection.equalEarth", "Equal Earth"), + LABEL_PROJECTION_ORTOGRAPHIC("html.label.geoProjection.ortographic", "Ortographic"), + LABEL_SERVER_SELECTOR("html.label.serverSelector", "Server selector"), + LABEL_APPLY("html.label.apply", "Apply"), - LOGIN_LOGIN("Login"), - LOGIN_LOGOUT("Logout"), - LOGIN_USERNAME("\"Username\""), - LOGIN_PASSWORD("\"Password\""), - LOGIN_FORGOT_PASSWORD("Forgot Password?"), - LOGIN_CREATE_ACCOUNT("Create an Account!"), - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_1("Forgot password? Unregister and register again."), - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_2("Use the following command in game to remove your current user:"), - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_3("Or using console:"), - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_4("After using the command, "), - LOGIN_FAILED("Login failed: "), - REGISTER("Register"), - REGISTER_CREATE_USER("Create a new user"), - REGISTER_USERNAME_TIP("Username can be up to 50 characters."), - REGISTER_PASSWORD_TIP("Password should be more than 8 characters, but there are no limitations."), - REGISTER_HAVE_ACCOUNT("Already have an account? Login!"), - REGISTER_USERNAME_LENGTH("Username can be up to 50 characters, yours is "), - REGISTER_SPECIFY_USERNAME("You need to specify a Username"), - REGISTER_SPECIFY_PASSWORD("You need to specify a Password"), - REGISTER_COMPLETE("Complete Registration"), - REGISTER_COMPLETE_INSTRUCTIONS_1("You can now finish registering the user."), - REGISTER_COMPLETE_INSTRUCTIONS_2("Code expires in 15 minutes"), - REGISTER_COMPLETE_INSTRUCTIONS_3("Use the following command in game to finish registration:"), - REGISTER_COMPLETE_INSTRUCTIONS_4("Or using console:"), - REGISTER_FAILED("Registration failed: "), - REGISTER_CHECK_FAILED("Checking registration status failed: "), + LOGIN_LOGIN("html.login.login", "Login"), + LOGIN_LOGOUT("html.login.logout", "Logout"), + LOGIN_USERNAME("html.login.username", "Username"), + LOGIN_PASSWORD("html.login.password", "Password"), + LOGIN_FORGOT_PASSWORD("html.login.forgotPassword", "Forgot Password?"), + LOGIN_CREATE_ACCOUNT("html.login.register", "Create an Account!"), + LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_1("html.login.forgotPassword1", "Forgot password? Unregister and register again."), + LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_2("html.login.forgotPassword2", "Use the following command in game to remove your current user:"), + LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_3("html.login.forgotPassword3", "Or using console:"), + LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_4("html.login.forgotPassword4", "After using the command, "), + LOGIN_FAILED("html.login.failed", "Login failed: "), + REGISTER("html.register.register", "Register"), + REGISTER_CREATE_USER("html.register.createNewUser", "Create a new user"), + REGISTER_USERNAME_TIP("html.register.usernameTip", "Username can be up to 50 characters."), + REGISTER_PASSWORD_TIP("html.register.passwordTip", "Password should be more than 8 characters, but there are no limitations."), + REGISTER_HAVE_ACCOUNT("html.register.login", "Already have an account? Login!"), + REGISTER_USERNAME_LENGTH("html.register.error.usernameLength", "Username can be up to 50 characters, yours is "), + REGISTER_SPECIFY_USERNAME("html.register.error.noUsername", "You need to specify a Username"), + REGISTER_SPECIFY_PASSWORD("html.register.error.noPassword", "You need to specify a Password"), + REGISTER_COMPLETE("html.register.completion", "Complete Registration"), + REGISTER_COMPLETE_INSTRUCTIONS_1("html.register.completion1", "You can now finish registering the user."), + REGISTER_COMPLETE_INSTRUCTIONS_2("html.register.completion2", "Code expires in 15 minutes"), + REGISTER_COMPLETE_INSTRUCTIONS_3("html.register.completion3", "Use the following command in game to finish registration:"), + REGISTER_COMPLETE_INSTRUCTIONS_4("html.register.completion4", "Or using console:"), + REGISTER_FAILED("html.register.error.failed", "Registration failed: "), + REGISTER_CHECK_FAILED("html.register.error.checkFailed", "Checking registration status failed: "), - QUERY_PERFORM_QUERY("Perform Query!"), - QUERY_LOADING_FILTERS("Loading filters.."), - QUERY_ADD_FILTER("Add a filter.."), - QUERY_TIME_TO(">to"), - QUERY_TIME_FROM(">from"), - QUERY_SHOW_VIEW("Show a view"), - QUERY("Query<"), - QUERY_MAKE_ANOTHER("Make another query"), - QUERY_MAKE("Make a query"), + QUERY_PERFORM_QUERY("html.query.performQuery", "Perform Query!"), + QUERY_LOADING_FILTERS("html.query.filters.loading", "Loading filters.."), + QUERY_ADD_FILTER("html.query.filters.add", "Add a filter.."), + QUERY_TIME_TO("html.query.label.to", ">to"), + QUERY_TIME_FROM("html.query.label.from", ">from"), + QUERY_SHOW_VIEW("html.query.label.view", "Show a view"), + QUERY("html.query.title.text", "Query<"), + QUERY_MAKE_ANOTHER("html.query.label.makeAnother", "Make another query"), - WARNING_NO_GAME_SERVERS("Some data requires Plan to be installed on game servers."), - WARNING_NO_GEOLOCATIONS("Geolocation gathering needs to be enabled in the config (Accept GeoLite2 EULA)."), - WARNING_NO_SPONGE_CHUNKS("Chunks unavailable on Sponge"), - ; + WARNING_NO_GAME_SERVERS("html.description.noGameServers", "Some data requires Plan to be installed on game servers."), + WARNING_NO_GEOLOCATIONS("html.description.noGeolocations", "Geolocation gathering needs to be enabled in the config (Accept GeoLite2 EULA)."), + WARNING_NO_SPONGE_CHUNKS("html.description.noSpongeChunks", "Chunks unavailable on Sponge"); + private final String key; private final String defaultValue; - HtmlLang(String defaultValue) { + HtmlLang(String key, String defaultValue) { + this.key = key; this.defaultValue = defaultValue; } @@ -277,6 +315,11 @@ public enum HtmlLang implements Lang { return "HTML - " + name(); } + @Override + public String getKey() { + return key; + } + @Override public String getDefault() { return defaultValue; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/JSLang.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/JSLang.java index 09e222328..e687ac01e 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/JSLang.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/JSLang.java @@ -23,50 +23,51 @@ package com.djrapitops.plan.settings.locale.lang; */ public enum JSLang implements Lang { - TEXT_PREDICTED_RETENTION("This value is a prediction based on previous players"), - TEXT_NO_SERVERS("No servers found in the database"), - TEXT_SERVER_INSTRUCTIONS("It appears that Plan is not installed on any game servers or not connected to the same database. See
wiki for Network tutorial."), - TEXT_NO_SERVER("No server to display online activity for"), - LABEL_REGISTERED_PLAYERS("Registered Players"), - LINK_SERVER_ANALYSIS("Server Analysis"), - LINK_QUICK_VIEW("Quick view"), - TEXT_FIRST_SESSION("First session"), - LABEL_SESSION_ENDED(" Ended"), - LINK_PLAYER_PAGE("Player Page"), - LABEL_NO_SESSION_KILLS("None"), - UNIT_ENTITIES("Entities"), - UNIT_CHUNKS("Chunks"), - LABEL_RELATIVE_JOIN_ACTIVITY("Relative Join Activity"), - LABEL_DAY_OF_WEEK("Day of the Week"), - LABEL_WEEK_DAYS("'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'"), + TEXT_PREDICTED_RETENTION("html.description.predictedNewPlayerRetention", "This value is a prediction based on previous players"), + TEXT_NO_SERVERS("html.description.noServers", "No servers found in the database"), + TEXT_SERVER_INSTRUCTIONS("html.description.noServersLong", "It appears that Plan is not installed on any game servers or not connected to the same database. See wiki for Network tutorial."), + TEXT_NO_SERVER("html.description.noServerOnlinActivity", "No server to display online activity for"), + LABEL_REGISTERED_PLAYERS("html.label.registeredPlayers", "Registered Players"), + LINK_SERVER_ANALYSIS("html.label.serverAnalysis", "Server Analysis"), + LINK_QUICK_VIEW("html.label.quickView", "Quick view"), + TEXT_FIRST_SESSION("html.label.firstSession", "First session"), + LABEL_SESSION_ENDED("html.label.sessionEnded", " Ended"), + LINK_PLAYER_PAGE("html.label.playerPage", "Player Page"), + LABEL_NO_SESSION_KILLS("html.generic.none", "None"), + // UNIT_ENTITIES("html.unit.entities", "Entities"), + UNIT_CHUNKS("html.unit.chunks", "Chunks"), + LABEL_RELATIVE_JOIN_ACTIVITY("html.label.relativeJoinActivity", "Relative Join Activity"), + LABEL_DAY_OF_WEEK("html.label.dayOfweek", "Day of the Week"), + LABEL_WEEK_DAYS("html.label.weekdays", "'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'"), - QUERY_ARE_ACTIVITY_GROUP("are in Activity Groups"), - QUERY_JOINED_WITH_ADDRESS("joined with address"), - QUERY_JOINED_FROM_COUNTRY("have joined from country"), - QUERY_ARE_PLUGIN_GROUP("are in ${plugin}'s ${group} Groups"), - QUERY_OF_PLAYERS("of Players who "), - QUERY_AND("and "), - QUERY_PLAYED_BETWEEN("Played between"), - QUERY_REGISTERED_BETWEEN("Registered between"), - QUERY_ZERO_RESULTS("Query produced 0 results"), - QUERY_RESULTS("Query Results"), - QUERY_RESULTS_MATCH("matched ${resultCount} players"), - QUERY_VIEW(" View:"), - QUERY_ACTIVITY_OF_MATCHED_PLAYERS("Activity of matched players"), - QUERY_ACTIVITY_ON("Activity on "), - QUERY_ARE("`are`"), - QUERY_SESSIONS_WITHIN_VIEW("Sessions within view"), + QUERY_ARE_ACTIVITY_GROUP("html.query.filter.activity.text", "are in Activity Groups"), + QUERY_JOINED_WITH_ADDRESS("html.query.filter.joinAddress.text", "joined with address"), + QUERY_JOINED_FROM_COUNTRY("html.query.filter.country.text", "have joined from country"), + QUERY_ARE_PLUGIN_GROUP("html.query.filter.pluginGroup.text", "are in ${plugin}'s ${group} Groups"), + QUERY_OF_PLAYERS("html.query.filter.generic.start", "of Players who "), + QUERY_AND("html.query.filter.generic.and", "and "), + QUERY_PLAYED_BETWEEN("html.query.filter.playedBetween.text", "Played between"), + QUERY_REGISTERED_BETWEEN("html.query.filter.registeredBetween.text", "Registered between"), + QUERY_ZERO_RESULTS("html.query.results.none", "Query produced 0 results"), + QUERY_RESULTS("html.query.results.title", "Query Results"), + QUERY_RESULTS_MATCH("html.query.results.match", "matched ${resultCount} players"), + QUERY_VIEW("html.query.filter.view", " View:"), + QUERY_ACTIVITY_OF_MATCHED_PLAYERS("html.query.title.activity", "Activity of matched players"), + QUERY_ACTIVITY_ON("html.query.title.activityOnDate", "Activity on "), + QUERY_ARE("html.query.generic.are", "`are`"), + QUERY_SESSIONS_WITHIN_VIEW("html.query.title.sessionsWithinView", "Sessions within view"), - FILTER_GROUP("Group: "), - FILTER_ALL_PLAYERS("All players"), - FILTER_ACTIVITY_INDEX_NOW("Current activity group"), - FILTER_BANNED("Ban status"), - FILTER_OPS("Operator status"), - ; + FILTER_GROUP("html.query.filter.pluginGroup.name", "Group: "), + FILTER_ALL_PLAYERS("html.query.filter.generic.allPlayers", "All players"), + FILTER_ACTIVITY_INDEX_NOW("html.query.filter.title.activityGroup", "Current activity group"), + FILTER_BANNED("html.query.filter.banStatus.name", "Ban status"), + FILTER_OPS("html.query.filter.operatorStatus.name", "Operator status"); + private final String key; private final String defaultValue; - JSLang(String defaultValue) { + JSLang(String key, String defaultValue) { + this.key = key; this.defaultValue = defaultValue; } @@ -75,6 +76,9 @@ public enum JSLang implements Lang { return "HTML - " + name(); } + @Override + public String getKey() {return key;} + @Override public String getDefault() { return defaultValue; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/Lang.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/Lang.java index f235f9a19..c1a3163bb 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/Lang.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/Lang.java @@ -25,6 +25,8 @@ public interface Lang { String getIdentifier(); + String getKey(); + String getDefault(); } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/PluginLang.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/PluginLang.java index 9ce4fb152..41a43f0ef 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/PluginLang.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/PluginLang.java @@ -22,74 +22,83 @@ package com.djrapitops.plan.settings.locale.lang; * @author AuroraLS3 */ public enum PluginLang implements Lang { - ENABLED("Enable", "Player Analytics Enabled."), - ENABLED_WEB_SERVER("Enable - WebServer", "Webserver running on PORT ${0} ( ${1} )"), - ENABLED_DATABASE("Enable - Database", "${0}-database connection established."), - API_ADD_RESOURCE_JS("API - js+", "PageExtension: ${0} added javascript(s) to ${1}, ${2}"), - API_ADD_RESOURCE_CSS("API - css+", "PageExtension: ${0} added stylesheet(s) to ${1}, ${2}"), + ENABLED("plugin.enable.enabled", "Enable", "Player Analytics Enabled."), + ENABLED_WEB_SERVER("plugin.enable.webserver", "Enable - WebServer", "Webserver running on PORT ${0} ( ${1} )"), + ENABLED_DATABASE("plugin.enable.database", "Enable - Database", "${0}-database connection established."), + API_ADD_RESOURCE_JS("plugin.apiJSAdded", "API - js+", "PageExtension: ${0} added javascript(s) to ${1}, ${2}"), + API_ADD_RESOURCE_CSS("plugin.apiCSSAdded", "API - css+", "PageExtension: ${0} added stylesheet(s) to ${1}, ${2}"), - ENABLE_NOTIFY_STORING_PRESERVED_SESSIONS("Enable - Storing preserved sessions", "Storing sessions that were preserved before previous shutdown."), - ENABLE_NOTIFY_EMPTY_IP("Enable - Notify Empty IP", "IP in server.properties is empty & Alternative_IP is not in use. Incorrect links might be given!"), - ENABLE_NOTIFY_BAD_IP("Enable - Notify Bad IP", "0.0.0.0 is not a valid address, set up Alternative_IP settings. Incorrect links might be given!"), - ENABLE_NOTIFY_WEB_SERVER_DISABLED("Enable - Notify Webserver disabled", "WebServer was not initialized. (WebServer.DisableWebServer: true)"), - ENABLE_NOTIFY_GEOLOCATIONS_INTERNET_REQUIRED("Enable - Notify Geolocations Internet Required", "Plan Requires internet access on first run to download GeoLite2 Geolocation database."), - ENABLE_NOTIFY_GEOLOCATIONS_DISABLED("Enable - Notify Geolocations disabled", "Geolocation gathering is not active. (Data.Geolocations: false)"), - ENABLE_FAIL_DB("Enable FAIL - Database", "${0}-Database Connection failed: ${1}"), - ENABLE_FAIL_WRONG_DB("Enable FAIL - Wrong Database Type", "${0} is not a supported Database"), - ENABLE_FAIL_DB_PATCH("Enable FAIL - Database Patch", "Database Patching failed, plugin has to be disabled. Please report this issue"), - ENABLE_FAIL_NO_WEB_SERVER_PROXY("Enable FAIL - WebServer (Proxy)", "WebServer did not initialize!"), - ENABLE_FAIL_GEODB_WRITE("Enable FAIL - GeoDB Write", "Something went wrong saving the downloaded GeoLite2 Geolocation database"), + ENABLE_NOTIFY_STORING_PRESERVED_SESSIONS("plugin.enable.notify.storeSessions", "Enable - Storing preserved sessions", "Storing sessions that were preserved before previous shutdown."), + ENABLE_NOTIFY_EMPTY_IP("plugin.enable.notify.emptyIP", "Enable - Notify Empty IP", "IP in server.properties is empty & Alternative_IP is not in use. Incorrect links might be given!"), + ENABLE_NOTIFY_BAD_IP("plugin.enable.notify.badIP", "Enable - Notify Bad IP", "0.0.0.0 is not a valid address, set up Alternative_IP settings. Incorrect links might be given!"), + ENABLE_NOTIFY_WEB_SERVER_DISABLED("plugin.enable.notify.webserverDisabled", "Enable - Notify Webserver disabled", "WebServer was not initialized. (WebServer.DisableWebServer: true)"), + ENABLE_NOTIFY_GEOLOCATIONS_INTERNET_REQUIRED("plugin.enable.notify.geoInternetRequired", "Enable - Notify Geolocations Internet Required", "Plan Requires internet access on first run to download GeoLite2 Geolocation database."), + ENABLE_NOTIFY_GEOLOCATIONS_DISABLED("plugin.enable.notify.geoDisabled", "Enable - Notify Geolocations disabled", "Geolocation gathering is not active. (Data.Geolocations: false)"), + ENABLE_FAIL_DB("plugin.enable.fail.database", "Enable FAIL - Database", "${0}-Database Connection failed: ${1}"), + ENABLE_FAIL_WRONG_DB("plugin.enable.fail.databaseType", "Enable FAIL - Wrong Database Type", "${0} is not a supported Database"), + ENABLE_FAIL_DB_PATCH("plugin.enable.fail.databasePatch", "Enable FAIL - Database Patch", "Database Patching failed, plugin has to be disabled. Please report this issue"), + ENABLE_FAIL_NO_WEB_SERVER_PROXY("plugin.enable.fail.webServer", "Enable FAIL - WebServer (Proxy)", "WebServer did not initialize!"), + ENABLE_FAIL_GEODB_WRITE("plugin.enable.fail.geoDBWrite", "Enable FAIL - GeoDB Write", "Something went wrong saving the downloaded GeoLite2 Geolocation database"), - WEB_SERVER_FAIL_PORT_BIND("WebServer FAIL - Port Bind", "WebServer was not initialized successfully. Is the port (${0}) in use?"), - WEB_SERVER_FAIL_SSL_CONTEXT("WebServer FAIL - SSL Context", "WebServer: SSL Context Initialization Failed."), - WEB_SERVER_FAIL_STORE_LOAD("WebServer FAIL - Store Load", "WebServer: SSL Certificate loading Failed."), - WEB_SERVER_FAIL_EMPTY_FILE("WebServer FAIL - EOF", "WebServer: EOF when reading Certificate file. (Check that the file is not empty)"), - WEB_SERVER_NOTIFY_NO_CERT_FILE("WebServer - Notify no Cert file", "WebServer: Certificate KeyStore File not Found: ${0}"), - WEB_SERVER_NOTIFY_HTTP("WebServer - Notify HTTP", "WebServer: No Certificate -> Using HTTP-server for Visualization."), - WEB_SERVER_NOTIFY_USING_PROXY_MODE("WebServer - Notify Using Proxy", "WebServer: Proxy-mode HTTPS enabled, make sure that your reverse-proxy is routing using HTTPS and Plan Alternative_IP.Address points to the Proxy"), - WEB_SERVER_NOTIFY_HTTP_USER_AUTH("WebServer - Notify HTTP User Auth", "WebServer: User Authorization Disabled! (Not secure over HTTP)"), - WEB_SERVER_NOTIFY_HTTPS_USER_AUTH("WebServer - Notify HTTPS User Auth", "WebServer: User Authorization Disabled! (Disabled in config)"), - WEB_SERVER_NOTIFY_IP_WHITELIST("Webserver - Notify IP Whitelist", "Webserver: IP Whitelist is enabled."), - WEB_SERVER_NOTIFY_IP_WHITELIST_BLOCK("Webserver - Notify IP Whitelist Block", "Webserver: ${0} was denied access to '${1}'. (not whitelisted)"), + WEB_SERVER_FAIL_PORT_BIND("plugin.webserver.fail.portInUse", "WebServer FAIL - Port Bind", "WebServer was not initialized successfully. Is the port (${0}) in use?"), + WEB_SERVER_FAIL_SSL_CONTEXT("plugin.webserver.fail.SSLContext", "WebServer FAIL - SSL Context", "WebServer: SSL Context Initialization Failed."), + WEB_SERVER_FAIL_STORE_LOAD("plugin.webserver.fail.certStoreLoad", "WebServer FAIL - Store Load", "WebServer: SSL Certificate loading Failed."), + WEB_SERVER_FAIL_EMPTY_FILE("plugin.webserver.fail.certFileEOF", "WebServer FAIL - EOF", "WebServer: EOF when reading Certificate file. (Check that the file is not empty)"), + WEB_SERVER_NOTIFY_NO_CERT_FILE("plugin.webserver.notify.noCertFile", "WebServer - Notify no Cert file", "WebServer: Certificate KeyStore File not Found: ${0}"), + WEB_SERVER_NOTIFY_HTTP("plugin.webserver.notify.http", "WebServer - Notify HTTP", "WebServer: No Certificate -> Using HTTP-server for Visualization."), + WEB_SERVER_NOTIFY_USING_PROXY_MODE("plugin.webserver.notify.reverseProxy", "WebServer - Notify Using Proxy", "WebServer: Proxy-mode HTTPS enabled, make sure that your reverse-proxy is routing using HTTPS and Plan Alternative_IP.Address points to the Proxy"), + WEB_SERVER_NOTIFY_HTTP_USER_AUTH("plugin.webserver.notify.authDisabledNoHTTPS", "WebServer - Notify HTTP User Auth", "WebServer: User Authorization Disabled! (Not secure over HTTP)"), + WEB_SERVER_NOTIFY_HTTPS_USER_AUTH("plugin.webserver.notify.authDisabledConfig", "WebServer - Notify HTTPS User Auth", "WebServer: User Authorization Disabled! (Disabled in config)"), + WEB_SERVER_NOTIFY_IP_WHITELIST("plugin.webserver.notify.ipWhitelist", "Webserver - Notify IP Whitelist", "Webserver: IP Whitelist is enabled."), + WEB_SERVER_NOTIFY_IP_WHITELIST_BLOCK("plugin.webserver.notify.ipWhitelistBlock", "Webserver - Notify IP Whitelist Block", "Webserver: ${0} was denied access to '${1}'. (not whitelisted)"), + WEB_SERVER_NOTIFY_CERT_EXPIRE_DATE("plugin.webserver.notify.certificateExpiresOn", "Webserver notify - Cert expiry", "Webserver: Loaded certificate is valid until ${0}."), + WEB_SERVER_NOTIFY_CERT_EXPIRE_DATE_SOON("plugin.webserver.notify.certificateExpiresSoon", "Webserver notify - Cert expiry soon", "Webserver: Certificate expires in ${0}, consider renewing the certificate."), + WEB_SERVER_NOTIFY_CERT_EXPIRE_DATE_PASSED("plugin.webserver.notify.certificateExpiresPassed", "Webserver notify - Cert expiry passed", "Webserver: Certificate has expired, consider renewing the certificate."), + WEB_SERVER_NOTIFY_CERT_NO_SUCH_ALIAS("plugin.webserver.notify.certificateNoSuchAlias", "Webserver notify - Cert no alias", "Webserver: Certificate with alias '${0}' was not found inside the keystore file '${1}'."), - DISABLED("Disable", "Player Analytics Disabled."), - DISABLED_WEB_SERVER("Disable - WebServer", "Webserver has been disabled."), - DISABLED_PROCESSING("Disable - Processing", "Processing critical unprocessed tasks. (${0})"), - DISABLED_PROCESSING_COMPLETE("Disable - Processing Complete", "Processing complete."), - DISABLED_UNSAVED_SESSIONS("Disable - Unsaved Session Save", "Saving unfinished sessions.."), - DISABLED_UNSAVED_SESSIONS_TIMEOUT("Disable - Unsaved Session Save Timeout", "Timeout hit, storing the unfinished sessions on next enable instead."), - DISABLED_WAITING_SQLITE("Disable - Waiting SQLite", "Waiting queries to finish to avoid SQLite crashing JVM.."), - DISABLED_WAITING_SQLITE_COMPLETE("Disable - Waiting SQLite Complete", "Closed SQLite connection."), - DISABLED_WAITING_TRANSACTIONS("Disable - Waiting Transactions", "Waiting for unfinished transactions to avoid data loss.."), - DISABLED_WAITING_TRANSACTIONS_COMPLETE("Disable - Waiting Transactions Complete", "Transaction queue closed."), + DISABLED("plugin.disable.disabled", "Disable", "Player Analytics Disabled."), + DISABLED_WEB_SERVER("plugin.disable.webserver", "Disable - WebServer", "Webserver has been disabled."), + DISABLED_PROCESSING("plugin.disable.database", "Disable - Processing", "Processing critical unprocessed tasks. (${0})"), + DISABLED_PROCESSING_COMPLETE("plugin.disable.processingComplete", "Disable - Processing Complete", "Processing complete."), + DISABLED_UNSAVED_SESSIONS("plugin.disable.savingSessions", "Disable - Unsaved Session Save", "Saving unfinished sessions.."), + DISABLED_UNSAVED_SESSIONS_TIMEOUT("plugin.disable.savingSessionsTimeout", "Disable - Unsaved Session Save Timeout", "Timeout hit, storing the unfinished sessions on next enable instead."), + DISABLED_WAITING_SQLITE("plugin.disable.waitingDb", "Disable - Waiting SQLite", "Waiting queries to finish to avoid SQLite crashing JVM.."), + DISABLED_WAITING_SQLITE_COMPLETE("plugin.disable.waitingDbComplete", "Disable - Waiting SQLite Complete", "Closed SQLite connection."), + DISABLED_WAITING_TRANSACTIONS("plugin.disable.waitingTransactions", "Disable - Waiting Transactions", "Waiting for unfinished transactions to avoid data loss.."), + DISABLED_WAITING_TRANSACTIONS_COMPLETE("plugin.disable.waitingTransactionsComplete", "Disable - Waiting Transactions Complete", "Transaction queue closed."), - VERSION_NEWEST("Version - Latest", "You're using the latest version."), - VERSION_AVAILABLE("Version - New", "New Release (${0}) is available ${1}"), - VERSION_AVAILABLE_SPIGOT("Version - New (old)", "New Version is available at ${0}"), - VERSION_AVAILABLE_DEV("Version - DEV", " This is a DEV release."), - VERSION_FAIL_READ_VERSIONS("Version FAIL - Read versions.txt", "Version information could not be loaded from Github/versions.txt"), - VERSION_FAIL_READ_OLD("Version FAIL - Read info (old)", "Failed to check newest version number"), + VERSION_NEWEST("plugin.version.isLatest", "Version - Latest", "You're using the latest version."), + VERSION_AVAILABLE("plugin.version.updateAvailable", "Version - New", "New Release (${0}) is available ${1}"), + VERSION_AVAILABLE_SPIGOT("plugin.version.updateAvailableSpigot", "Version - New (old)", "New Version is available at ${0}"), + VERSION_AVAILABLE_DEV("plugin.version.isDev", "Version - DEV", " This is a DEV release."), + VERSION_FAIL_READ_VERSIONS("plugin.version.checkFailGithub", "Version FAIL - Read versions.txt", "Version information could not be loaded from Github/versions.txt"), + VERSION_FAIL_READ_OLD("plugin.version.checkFail", "Version FAIL - Read info (old)", "Failed to check newest version number"), - VERSION_UPDATE("HTML - Version Update", "Update"), - VERSION_UPDATE_AVAILABLE("HTML - Version Update Available", "Version ${0} is Available!"), - VERSION_UPDATE_INFO("HTML - Version Update Info", "A new version has been released and is now available for download."), - VERSION_UPDATE_DEV("HTML - Version Update Dev", "This version is a DEV release."), - VERSION_CHANGE_LOG("HTML - Version Change log", "View Changelog"), - VERSION_DOWNLOAD("HTML - Version Download", "Download Plan-${0}.jar"), - VERSION_CURRENT("HTML - Version Current", "You have version ${0}"), + VERSION_UPDATE("html.version.updateButton", "HTML - Version Update", "Update"), + VERSION_UPDATE_AVAILABLE("html.version.updateModal.title", "HTML - Version Update Available", "Version ${0} is Available!"), + VERSION_UPDATE_INFO("html.version.updateModal.text", "HTML - Version Update Info", "A new version has been released and is now available for download."), + VERSION_UPDATE_DEV("html.version.isDev", "HTML - Version Update Dev", "This version is a DEV release."), + VERSION_CHANGE_LOG("html.version.changelog", "HTML - Version Change log", "View Changelog"), + VERSION_DOWNLOAD("html.version.download", "HTML - Version Download", "Download Plan-${0}.jar"), + VERSION_CURRENT("html.version.current", "HTML - Version Current", "You have version ${0}"), - DB_APPLY_PATCH("Database - Apply Patch", "Applying Patch: ${0}.."), - DB_APPLIED_PATCHES("Database - Patches Applied", "All database patches applied successfully."), - DB_APPLIED_PATCHES_ALREADY("Database - Patches Applied Already", "All database patches already applied."), - DB_NOTIFY_CLEAN("Database Notify - Clean", "Removed data of ${0} players."), - DB_NOTIFY_SQLITE_WAL("Database Notify - SQLite No WAL", "SQLite WAL mode not supported on this server version, using default. This may or may not affect performance."), - DB_MYSQL_LAUNCH_OPTIONS_FAIL("Database MySQL - Launch Options Error", "Launch Options were faulty, using default (${0})"), - LOADING_SERVER_INFO("ServerInfo - Loading", "Loading server identifying information"); + DB_APPLY_PATCH("plugin.generic.dbApplyingPatch", "Database - Apply Patch", "Applying Patch: ${0}.."), + DB_APPLIED_PATCHES("plugin.generic.dbPatchesApplied", "Database - Patches Applied", "All database patches applied successfully."), + DB_APPLIED_PATCHES_ALREADY("plugin.generic.dbPatchesAlreadyApplied", "Database - Patches Applied Already", "All database patches already applied."), + DB_NOTIFY_CLEAN("plugin.generic.dbNotifyClean", "Database Notify - Clean", "Removed data of ${0} players."), + DB_NOTIFY_SQLITE_WAL("plugin.generic.dbNotifySQLiteWAL", "Database Notify - SQLite No WAL", "SQLite WAL mode not supported on this server version, using default. This may or may not affect performance."), + DB_MYSQL_LAUNCH_OPTIONS_FAIL("plugin.generic.dbFaultyLaunchOptions", "Database MySQL - Launch Options Error", "Launch Options were faulty, using default (${0})"), + LOADING_SERVER_INFO("plugin.generic.loadingServerInfo", "ServerInfo - Loading", "Loading server identifying information"), + LOADED_SERVER_INFO("plugin.generic.loadedServerInfo", "ServerInfo - Loaded", "Server identifier loaded: ${0}"), + DB_SCHEMA_PATCH("plugin.generic.dbSchemaPatch", "Database Notify - Patch", "Database: Making sure schema is up to date.."), + ; + private final String key; private final String identifier; private final String defaultValue; - PluginLang(String identifier, String defaultValue) { + PluginLang(String key, String identifier, String defaultValue) { + this.key = key; this.identifier = identifier; this.defaultValue = defaultValue; } @@ -99,6 +108,9 @@ public enum PluginLang implements Lang { return identifier; } + @Override + public String getKey() {return key;} + @Override public String getDefault() { return defaultValue; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/network/NetworkSettingManager.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/network/NetworkSettingManager.java index 6ef151967..b76079a8c 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/network/NetworkSettingManager.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/network/NetworkSettingManager.java @@ -166,10 +166,12 @@ public class NetworkSettingManager implements SubSystem { private void updateConfigFromDBIfUpdated() { Database database = dbSystem.getDatabase(); Set serverUUIDs = database.query(ServerQueries.fetchPlanServerInformation()).keySet(); - // Remove the proxy server from the list - serverUUIDs.remove(serverInfo.getServerUUID()); for (ServerUUID serverUUID : serverUUIDs) { + // Remove the proxy server on the list + if (serverUUID.equals(serverInfo.getServerUUID())) { + continue; + } updateConfigFromDBIfUpdated(database, serverUUID); } } @@ -201,8 +203,8 @@ public class NetworkSettingManager implements SubSystem { Database database = dbSystem.getDatabase(); try (ConfigReader reader = new ConfigReader(file.toPath())) { - Config config = reader.read(); - database.executeTransaction(new StoreConfigTransaction(serverUUID, config, file.lastModified())); + Config serverConfig = reader.read(); + database.executeTransaction(new StoreConfigTransaction(serverUUID, serverConfig, file.lastModified())); } catch (IOException e) { throw new UncheckedIOException(e); } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/network/ServerSettingsManager.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/network/ServerSettingsManager.java index 60df29e94..337f04d86 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/network/ServerSettingsManager.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/network/ServerSettingsManager.java @@ -108,7 +108,7 @@ public class ServerSettingsManager implements SubSystem { Database database = dbSystem.getDatabase(); Optional serverUUID = serverInfo.getServerUUIDSafe(); - if (!serverUUID.isPresent()) { + if (serverUUID.isEmpty()) { return; } @@ -132,7 +132,7 @@ public class ServerSettingsManager implements SubSystem { long lastModified = configFile.exists() ? configFile.lastModified() : -1; Optional serverUUID = serverInfo.getServerUUIDSafe(); - if (!serverUUID.isPresent()) { + if (serverUUID.isEmpty()) { return; } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/theme/ThemeVal.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/theme/ThemeVal.java index 84ab42597..f5e7706bf 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/theme/ThemeVal.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/theme/ThemeVal.java @@ -67,12 +67,7 @@ public enum ThemeVal { GRAPH_MAX_PING("GraphColors.Ping.Max", "#ffa000"), GRAPH_MIN_PING("GraphColors.Ping.Min", "#ffd54f"), WORLD_MAP_HIGH("GraphColors.WorldMap_High", "#267f00"), - WORLD_MAP_LOW("GraphColors.WorldMap_Low", "#EEFFEE"), - - @Deprecated - PARSED_SESSION_ACCORDION("ParsedElements.SessionAccordion", "teal"), - @Deprecated - PARSED_SERVER_ACCORDION("ParsedElements.ServerAccordion", "light-green"); + WORLD_MAP_LOW("GraphColors.WorldMap_Low", "#EEFFEE"); private final String themePath; private final String defaultValue; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/upkeep/ConfigStoreTask.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/upkeep/ConfigStoreTask.java index 9ea70351a..cd4554ee9 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/upkeep/ConfigStoreTask.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/upkeep/ConfigStoreTask.java @@ -58,9 +58,12 @@ public class ConfigStoreTask extends TaskSystem.Task { @Override public void run() { - long lastModified = files.getConfigFile().lastModified(); - dbSystem.getDatabase().executeTransaction(new StoreConfigTransaction(serverInfo.getServerUUID(), config, lastModified)); - cancel(); + try { + long lastModified = files.getConfigFile().lastModified(); + dbSystem.getDatabase().executeTransaction(new StoreConfigTransaction(serverInfo.getServerUUID(), config, lastModified)); + } finally { + cancel(); + } } @Override diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/upkeep/NetworkConfigStoreTask.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/upkeep/NetworkConfigStoreTask.java index d3aaf17dd..d422dacbd 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/upkeep/NetworkConfigStoreTask.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/upkeep/NetworkConfigStoreTask.java @@ -51,8 +51,11 @@ public class NetworkConfigStoreTask extends TaskSystem.Task { @Override public void run() { - updateDBConfigs(); - cancel(); + try { + updateDBConfigs(); + } finally { + cancel(); + } } @Override diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/DBAccessLock.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/DBAccessLock.java index 757ae6a40..b6fc18375 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/DBAccessLock.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/DBAccessLock.java @@ -16,7 +16,7 @@ */ package com.djrapitops.plan.storage.database; -import com.djrapitops.plan.exceptions.EnableException; +import com.djrapitops.plan.exceptions.database.DBClosedException; import com.djrapitops.plan.storage.database.transactions.Transaction; import com.djrapitops.plan.storage.database.transactions.init.OperationCriticalTransaction; @@ -58,7 +58,7 @@ public class DBAccessLock { synchronized (lockObject) { lockObject.wait(); if (database.getState() == Database.State.CLOSED) { - throw new EnableException("Database failed to open, Query has failed. (This exception is necessary to not keep query threads waiting)"); + throw new DBClosedException("Database failed to open, Query has failed. (This exception is necessary to not keep query threads waiting)"); } } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/DBSystem.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/DBSystem.java index 8b5db4ccd..bd7965f7e 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/DBSystem.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/DBSystem.java @@ -19,13 +19,21 @@ package com.djrapitops.plan.storage.database; import com.djrapitops.plan.SubSystem; import com.djrapitops.plan.exceptions.EnableException; import com.djrapitops.plan.exceptions.database.DBInitException; +import com.djrapitops.plan.settings.config.PlanConfig; +import com.djrapitops.plan.settings.config.paths.DatabaseSettings; import com.djrapitops.plan.settings.locale.Locale; import com.djrapitops.plan.settings.locale.lang.PluginLang; import net.playeranalytics.plugin.server.PluginLogger; +import org.jetbrains.annotations.NotNull; import javax.inject.Singleton; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Paths; import java.util.HashSet; import java.util.Set; +import java.util.stream.Stream; /** * System that holds the active databases. @@ -35,6 +43,7 @@ import java.util.Set; @Singleton public class DBSystem implements SubSystem { + protected final PlanConfig config; protected final Locale locale; private final SQLiteDB.Factory sqLiteFactory; protected final PluginLogger logger; @@ -43,10 +52,12 @@ public class DBSystem implements SubSystem { protected final Set databases; public DBSystem( + PlanConfig config, Locale locale, SQLiteDB.Factory sqLiteDB, PluginLogger logger ) { + this.config = config; this.locale = locale; this.sqLiteFactory = sqLiteDB; this.logger = logger; @@ -94,7 +105,30 @@ public class DBSystem implements SubSystem { } catch (DBInitException e) { Throwable cause = e.getCause(); String message = cause == null ? e.getMessage() : cause.getMessage(); - throw new EnableException(db.getType().getName() + " init failure: " + message, cause); + if (message.contains("The driver has not received any packets from the server.")) { + throw new EnableException(getMySQLConnectionFailureMessage()); + } else { + throw new EnableException("Failed to start " + db.getType().getName() + ": " + message, cause); + } + } + } + + @NotNull + private String getMySQLConnectionFailureMessage() { + return "Failed to start " + db.getType().getName() + ": Communications link failure. Plan could not connect to MySQL-" + + "\n- Check that database address '" + config.get(DatabaseSettings.MYSQL_HOST) + ":" + config.get(DatabaseSettings.MYSQL_PORT) + "' accessible." + + "\n- Check that database called '" + config.get(DatabaseSettings.MYSQL_DATABASE) + "' exists inside MySQL." + + "\n- Check that MySQL user '" + config.get(DatabaseSettings.MYSQL_USER) + "' has privileges to access the database." + + "\n- Check that other MySQL settings in Plan config correct." + + (isInsideDocker() ? "\n- Check that your docker container networking is set up correctly https://pterodactyl.io/tutorials/mysql_setup.html (Since your server is running inside a docker)" : "") + + "\n More help: https://github.com/plan-player-analytics/Plan/wiki/Bungee-Set-Up#step-2-create-a-mysql-database-for-plan"; + } + + private boolean isInsideDocker() { + try (Stream stream = Files.lines(Paths.get("/proc/1/cgroup"))) { + return stream.anyMatch(line -> line.contains("/docker")); + } catch (IOException | InvalidPathException | SecurityException e) { + return false; } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/Database.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/Database.java index 13eeaf013..ff521e6c7 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/Database.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/Database.java @@ -17,11 +17,16 @@ package com.djrapitops.plan.storage.database; import com.djrapitops.plan.exceptions.database.DBInitException; -import com.djrapitops.plan.storage.database.queries.Query; +import com.djrapitops.plan.storage.database.queries.*; import com.djrapitops.plan.storage.database.sql.building.Sql; import com.djrapitops.plan.storage.database.transactions.Transaction; -import java.util.concurrent.Future; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; /** * Interface for interacting with a Plan SQL database. @@ -52,13 +57,75 @@ public interface Database { */ T query(Query query); + default Optional queryOptional(String sql, RowExtractor rowExtractor, Object... parameters) { + return query(new QueryStatement<>(sql) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + QueryParameterSetter.setParameters(statement, parameters); + } + + @Override + public Optional processResults(ResultSet set) throws SQLException { + return set.next() ? Optional.ofNullable(rowExtractor.extract(set)) : Optional.empty(); + } + }); + } + + default List queryList(String sql, RowExtractor rowExtractor, Object... parameters) { + return queryCollection(sql, rowExtractor, ArrayList::new, parameters); + } + + default Set querySet(String sql, RowExtractor rowExtractor, Object... parameters) { + return queryCollection(sql, rowExtractor, HashSet::new, parameters); + } + + default , T> C queryCollection(String sql, RowExtractor rowExtractor, Supplier collectionConstructor, Object... parameters) { + return query(new QueryStatement<>(sql, 1000) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + QueryParameterSetter.setParameters(statement, parameters); + } + + @Override + public C processResults(ResultSet set) throws SQLException { + C collection = collectionConstructor.get(); + while (set.next()) { + collection.add(rowExtractor.extract(set)); + } + return collection; + } + }); + } + + default Map queryMap(String sql, MapRowExtractor rowExtractor, Object... parameters) { + return queryMap(sql, rowExtractor, HashMap::new, parameters); + } + + default , K, V> M queryMap(String sql, MapRowExtractor rowExtractor, Supplier mapConstructor, Object... parameters) { + return query(new QueryStatement<>(sql, 100) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + QueryParameterSetter.setParameters(statement, parameters); + } + + @Override + public M processResults(ResultSet set) throws SQLException { + M map = mapConstructor.get(); + while (set.next()) { + rowExtractor.extract(set, map); + } + return map; + } + }); + } + /** * Execute an SQL Transaction. * * @param transaction Transaction to execute. * @return Future that is finished when the transaction has been executed. */ - Future executeTransaction(Transaction transaction); + CompletableFuture executeTransaction(Transaction transaction); /** * Used to get the {@code DBType} of the Database diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/MySQLDB.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/MySQLDB.java index ac854a110..dd452b265 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/MySQLDB.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/MySQLDB.java @@ -23,6 +23,7 @@ import com.djrapitops.plan.settings.config.PlanConfig; import com.djrapitops.plan.settings.config.paths.DatabaseSettings; import com.djrapitops.plan.settings.locale.Locale; import com.djrapitops.plan.settings.locale.lang.PluginLang; +import com.djrapitops.plan.storage.file.PlanFiles; import com.djrapitops.plan.utilities.logging.ErrorContext; import com.djrapitops.plan.utilities.logging.ErrorLogger; import com.zaxxer.hikari.HikariConfig; @@ -34,11 +35,10 @@ import net.playeranalytics.plugin.server.PluginLogger; import javax.inject.Inject; import javax.inject.Singleton; -import java.sql.Connection; -import java.sql.Driver; -import java.sql.DriverManager; -import java.sql.SQLException; +import java.io.IOException; +import java.sql.*; import java.util.Enumeration; +import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; @@ -56,12 +56,13 @@ public class MySQLDB extends SQLDB { public MySQLDB( Locale locale, PlanConfig config, + PlanFiles files, Lazy serverInfo, RunnableFactory runnableFactory, PluginLogger pluginLogger, ErrorLogger errorLogger ) { - super(() -> serverInfo.get().getServerUUID(), locale, config, runnableFactory, pluginLogger, errorLogger); + super(() -> serverInfo.get().getServerUUID(), locale, config, files, runnableFactory, pluginLogger, errorLogger); } private static synchronized void increment() { @@ -73,11 +74,12 @@ public class MySQLDB extends SQLDB { return DBType.MYSQL; } - private void loadMySQLDriver() { + @Override + protected List getDependencyResource() { try { - Class.forName("com.mysql.cj.jdbc.Driver"); - } catch (ClassNotFoundException e) { - errorLogger.critical(e, ErrorContext.builder().whatToDo("Install MySQL Driver to the server").build()); + return files.getResourceFromJar("dependencies/mysqlDriver.txt").asLines(); + } catch (IOException e) { + throw new DBInitException("Failed to get MySQL dependency information", e); } } @@ -86,9 +88,18 @@ public class MySQLDB extends SQLDB { */ @Override public void setupDataSource() { - try { - loadMySQLDriver(); + if (driverClassLoader == null) { + logger.info("Downloading MySQL Driver, this may take a while..."); + downloadDriver(); + } + Thread currentThread = Thread.currentThread(); + ClassLoader previousClassLoader = currentThread.getContextClassLoader(); + + // Set the context class loader to the driver class loader for Hikari to use for finding the Driver + currentThread.setContextClassLoader(driverClassLoader); + + try { HikariConfig hikariConfig = new HikariConfig(); String host = config.get(DatabaseSettings.MYSQL_HOST); @@ -96,7 +107,7 @@ public class MySQLDB extends SQLDB { String database = config.get(DatabaseSettings.MYSQL_DATABASE); String launchOptions = config.get(DatabaseSettings.MYSQL_LAUNCH_OPTIONS); // REGEX: match "?", match "word=word&" *-times, match "word=word" - if (launchOptions.isEmpty() || !launchOptions.matches("\\?(((\\w|[-])+=.+)&)*((\\w|[-])+=.+)")) { + if (launchOptions.isEmpty() || !launchOptions.matches("\\?((([\\w-])+=.+)&)*(([\\w-])+=.+)")) { launchOptions = "?rewriteBatchedStatements=true&useSSL=false"; logger.error(locale.getString(PluginLang.DB_MYSQL_LAUNCH_OPTIONS_FAIL, launchOptions)); } @@ -113,7 +124,7 @@ public class MySQLDB extends SQLDB { hikariConfig.setPoolName("Plan Connection Pool-" + increment); increment(); - hikariConfig.setAutoCommit(true); + hikariConfig.setAutoCommit(false); try { hikariConfig.setMaximumPoolSize(config.get(DatabaseSettings.MAX_CONNECTIONS)); } catch (IllegalStateException e) { @@ -121,7 +132,7 @@ public class MySQLDB extends SQLDB { hikariConfig.setMaximumPoolSize(1); } hikariConfig.setMaxLifetime(TimeUnit.MINUTES.toMillis(25L)); - hikariConfig.setLeakDetectionThreshold(TimeUnit.MINUTES.toMillis(10L)); + hikariConfig.setLeakDetectionThreshold(TimeUnit.SECONDS.toMillis(29L)); this.dataSource = new HikariDataSource(hikariConfig); } catch (HikariPool.PoolInitializationException e) { @@ -129,6 +140,9 @@ public class MySQLDB extends SQLDB { } finally { unloadMySQLDriver(); } + + // Reset the context classloader back to what it was originally set to, now that the DataSource is created + currentThread.setContextClassLoader(previousClassLoader); } private void unloadMySQLDriver() { @@ -136,7 +150,9 @@ public class MySQLDB extends SQLDB { Enumeration drivers = DriverManager.getDrivers(); while (drivers.hasMoreElements()) { Driver driver = drivers.nextElement(); - if ("com.mysql.cj.jdbc.Driver".equals(driver.getClass().getName())) { + Class driverClass = driver.getClass(); + // Checks that it's from our class loader to avoid unloading another plugin's/the server's driver + if ("com.mysql.cj.jdbc.Driver".equals(driverClass.getName()) && driverClass.getClassLoader() == driverClassLoader) { try { DriverManager.deregisterDriver(driver); } catch (SQLException e) { @@ -158,9 +174,16 @@ public class MySQLDB extends SQLDB { } } if (connection.getAutoCommit()) connection.setAutoCommit(false); + setTimezoneToUTC(connection); return connection; } + private void setTimezoneToUTC(Connection connection) throws SQLException { + try (Statement statement = connection.createStatement()) { + statement.execute("set time_zone = '+00:00'"); + } + } + @Override public void close() { super.close(); @@ -175,7 +198,7 @@ public class MySQLDB extends SQLDB { connection.close(); } } catch (SQLException e) { - errorLogger.critical(e, ErrorContext.builder().related("Closing connection").build()); + errorLogger.error(e, ErrorContext.builder().related("Closing connection").build()); } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/ProxyDBSystem.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/ProxyDBSystem.java index 4bd0de52f..b0fcedce5 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/ProxyDBSystem.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/ProxyDBSystem.java @@ -16,6 +16,7 @@ */ package com.djrapitops.plan.storage.database; +import com.djrapitops.plan.settings.config.PlanConfig; import com.djrapitops.plan.settings.locale.Locale; import net.playeranalytics.plugin.server.PluginLogger; @@ -32,12 +33,13 @@ public class ProxyDBSystem extends DBSystem { @Inject public ProxyDBSystem( + PlanConfig config, Locale locale, MySQLDB mySQLDB, SQLiteDB.Factory sqLiteDB, PluginLogger logger ) { - super(locale, sqLiteDB, logger); + super(config, locale, sqLiteDB, logger); databases.add(mySQLDB); db = mySQLDB; } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/SQLDB.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/SQLDB.java index 8bdb76262..c5dd54a7f 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/SQLDB.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/SQLDB.java @@ -26,15 +26,21 @@ import com.djrapitops.plan.settings.config.paths.TimeSettings; import com.djrapitops.plan.settings.locale.Locale; import com.djrapitops.plan.settings.locale.lang.PluginLang; import com.djrapitops.plan.storage.database.queries.Query; +import com.djrapitops.plan.storage.database.transactions.ThrowawayTransaction; import com.djrapitops.plan.storage.database.transactions.Transaction; import com.djrapitops.plan.storage.database.transactions.init.CreateIndexTransaction; import com.djrapitops.plan.storage.database.transactions.init.CreateTablesTransaction; import com.djrapitops.plan.storage.database.transactions.init.OperationCriticalTransaction; import com.djrapitops.plan.storage.database.transactions.init.RemoveIncorrectTebexPackageDataPatch; import com.djrapitops.plan.storage.database.transactions.patches.*; +import com.djrapitops.plan.storage.file.PlanFiles; import com.djrapitops.plan.utilities.java.ThrowableUtils; import com.djrapitops.plan.utilities.logging.ErrorContext; import com.djrapitops.plan.utilities.logging.ErrorLogger; +import dev.vankka.dependencydownload.DependencyManager; +import dev.vankka.dependencydownload.classloader.IsolatedClassLoader; +import dev.vankka.dependencydownload.repository.Repository; +import dev.vankka.dependencydownload.repository.StandardRepository; import net.playeranalytics.plugin.scheduling.PluginRunnable; import net.playeranalytics.plugin.scheduling.RunnableFactory; import net.playeranalytics.plugin.scheduling.TimeAmount; @@ -43,11 +49,13 @@ import org.apache.commons.lang3.concurrent.BasicThreadFactory; import java.sql.Connection; import java.sql.SQLException; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.*; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.function.Supplier; @@ -58,21 +66,33 @@ import java.util.function.Supplier; */ public abstract class SQLDB extends AbstractDatabase { + private static final List DRIVER_REPOSITORIES = Arrays.asList( + new StandardRepository("https://papermc.io/repo/repository/maven-public/"), + new StandardRepository("https://repo1.maven.org/maven2/") + ); + private static boolean downloadDriver = true; + private final Supplier serverUUIDSupplier; protected final Locale locale; protected final PlanConfig config; + protected final PlanFiles files; protected final RunnableFactory runnableFactory; protected final PluginLogger logger; protected final ErrorLogger errorLogger; + private final AtomicInteger transactionQueueSize = new AtomicInteger(0); private Supplier transactionExecutorServiceProvider; private ExecutorService transactionExecutor; + private final AtomicBoolean dropUnimportantTransactions = new AtomicBoolean(false); + private final AtomicBoolean ranIntoFatalError = new AtomicBoolean(false); + protected ClassLoader driverClassLoader; protected SQLDB( Supplier serverUUIDSupplier, Locale locale, PlanConfig config, + PlanFiles files, RunnableFactory runnableFactory, PluginLogger logger, ErrorLogger errorLogger @@ -80,6 +100,7 @@ public abstract class SQLDB extends AbstractDatabase { this.serverUUIDSupplier = serverUUIDSupplier; this.locale = locale; this.config = config; + this.files = files; this.runnableFactory = runnableFactory; this.logger = logger; this.errorLogger = errorLogger; @@ -98,6 +119,34 @@ public abstract class SQLDB extends AbstractDatabase { }; } + public static void setDownloadDriver(boolean downloadDriver) { + SQLDB.downloadDriver = downloadDriver; + } + + protected abstract List getDependencyResource(); + + public void downloadDriver() { + if (downloadDriver) { + DependencyManager dependencyManager = new DependencyManager(files.getDataDirectory().resolve("libraries")); + dependencyManager.loadFromResource(getDependencyResource()); + CompletableFuture[] results = dependencyManager.download(null, DRIVER_REPOSITORIES); + for (int i = 0; i < results.length; i++) { + CompletableFuture result = results[i]; + Repository repository = DRIVER_REPOSITORIES.get(i); + result.exceptionally(error -> { + logger.warn("Failed to download " + getType().getName() + "-driver from " + repository.getHost() + ": " + error.getMessage() + ", " + error.getCause()); + return null; + }); + } + + IsolatedClassLoader classLoader = new IsolatedClassLoader(); + dependencyManager.load(null, classLoader); + this.driverClassLoader = classLoader; + } else { + this.driverClassLoader = getClass().getClassLoader(); + } + } + @Override public void init() { List unfinishedTransactions = closeTransactionExecutor(transactionExecutor); @@ -159,31 +208,38 @@ public abstract class SQLDB extends AbstractDatabase { new VersionTableRemovalPatch(), new DiskUsagePatch(), new WorldsOptimizationPatch(), - new WorldTimesOptimizationPatch(), new KillsOptimizationPatch(), - new SessionsOptimizationPatch(), - new PingOptimizationPatch(), new NicknamesOptimizationPatch(), - new UserInfoOptimizationPatch(), - new GeoInfoOptimizationPatch(), new TransferTableRemovalPatch(), new BadAFKThresholdValuePatch(), new DeleteIPsPatch(), new ExtensionShowInPlayersTablePatch(), new ExtensionTableRowValueLengthPatch(), new CommandUsageTableRemovalPatch(), - new RegisterDateMinimizationPatch(), new BadNukkitRegisterValuePatch(), new LinkedToSecurityTablePatch(), new LinkUsersToPlayersSecurityTablePatch(), new LitebansTableHeaderPatch(), new UserInfoHostnamePatch(), new ServerIsProxyPatch(), - new UserInfoHostnameAllowNullPatch(), new ServerTableRowPatch(), new PlayerTableRowPatch(), new ExtensionTableProviderValuesForPatch(), - new RemoveIncorrectTebexPackageDataPatch() + new RemoveIncorrectTebexPackageDataPatch(), + new ExtensionTableProviderFormattersPatch(), + new ServerPlanVersionPatch(), + new RemoveDanglingUserDataPatch(), + new RemoveDanglingServerDataPatch(), + new GeoInfoOptimizationPatch(), + new PingOptimizationPatch(), + new UserInfoOptimizationPatch(), + new WorldTimesOptimizationPatch(), + new SessionsOptimizationPatch(), + new UserInfoHostnameAllowNullPatch(), + new RegisterDateMinimizationPatch(), + new UsersTableNameLengthPatch(), + new SessionJoinAddressPatch(), + new RemoveUsernameFromAccessLogPatch() }; } @@ -193,6 +249,12 @@ public abstract class SQLDB extends AbstractDatabase { * Updates to latest schema. */ private void setupDatabase() { + executeTransaction(new OperationCriticalTransaction() { + @Override + protected void performOperations() { + logger.info(locale.getString(PluginLang.DB_SCHEMA_PATCH)); + } + }); executeTransaction(new CreateTablesTransaction()); for (Patch patch : patches()) { executeTransaction(patch); @@ -200,6 +262,7 @@ public abstract class SQLDB extends AbstractDatabase { executeTransaction(new OperationCriticalTransaction() { @Override protected void performOperations() { + logger.info(locale.getString(PluginLang.DB_APPLIED_PATCHES)); if (getState() == State.PATCHING) setState(State.OPEN); } }); @@ -238,9 +301,17 @@ public abstract class SQLDB extends AbstractDatabase { public void close() { if (getState() == State.OPEN) setState(State.CLOSING); closeTransactionExecutor(transactionExecutor); + unloadDriverClassloader(); setState(State.CLOSED); } + private void unloadDriverClassloader() { + // Unloading class loader using close() causes issues when reloading. + // It is better to leak this memory than crash the plugin on reload. + + driverClassLoader = null; + } + public abstract Connection getConnection() throws SQLException; public abstract void returnToPool(Connection connection); @@ -252,38 +323,66 @@ public abstract class SQLDB extends AbstractDatabase { } @Override - public Future executeTransaction(Transaction transaction) { + public CompletableFuture executeTransaction(Transaction transaction) { if (getState() == State.CLOSED) { throw new DBOpException("Transaction tried to execute although database is closed."); } Exception origin = new Exception(); - return CompletableFuture.supplyAsync(() -> { - accessLock.checkAccess(transaction); - transaction.executeTransaction(this); + if (determineIfShouldDropUnimportantTransactions(transactionQueueSize.incrementAndGet()) + && transaction instanceof ThrowawayTransaction) { + // Drop throwaway transaction immediately. return CompletableFuture.completedFuture(null); + } + + return CompletableFuture.supplyAsync(() -> { + try { + accessLock.checkAccess(transaction); + if (!ranIntoFatalError.get()) { + transaction.executeTransaction(this); + } + return CompletableFuture.completedFuture(null); + } finally { + transactionQueueSize.decrementAndGet(); + } }, getTransactionExecutor()).exceptionally(errorHandler(transaction, origin)); } + private boolean determineIfShouldDropUnimportantTransactions(int queueSize) { + boolean dropTransactions = dropUnimportantTransactions.get(); + if (queueSize >= 500 && !dropTransactions) { + logger.warn("Database can't keep up with transactions (Queue size: " + queueSize + "), dropping some unimportant transactions from execution."); + dropUnimportantTransactions.set(true); + return true; + } else if (queueSize < 50 && dropTransactions) { + dropUnimportantTransactions.set(false); + return false; + } + return dropTransactions; + } + private Function> errorHandler(Transaction transaction, Exception origin) { return throwable -> { if (throwable == null) { return CompletableFuture.completedFuture(null); } if (throwable.getCause() instanceof FatalDBException) { + ranIntoFatalError.set(true); logger.error("Database failed to open, " + transaction.getClass().getName() + " failed to be executed."); FatalDBException actual = (FatalDBException) throwable.getCause(); Optional whatToDo = actual.getContext().flatMap(ErrorContext::getWhatToDo); - whatToDo.ifPresent(message -> logger.error("What to do: " + message)); - if (!whatToDo.isPresent()) logger.error("Error msg: " + actual.getMessage()); + whatToDo.ifPresentOrElse( + message -> logger.error("What to do: " + message), + () -> logger.error("Error msg: " + actual.getMessage()) + ); setState(State.CLOSED); } ThrowableUtils.appendEntryPointToCause(throwable, origin); ErrorContext errorContext = ErrorContext.builder() .related("Transaction: " + transaction.getClass()) - .related("DB State: " + getState()) + .related("DB State: " + getState() + " - fatal: " + ranIntoFatalError.get()) .build(); if (getState() == State.CLOSED) { errorLogger.critical(throwable, errorContext); @@ -329,4 +428,16 @@ public abstract class SQLDB extends AbstractDatabase { public PluginLogger getLogger() { return logger; } + + public Locale getLocale() { + return locale; + } + + public boolean shouldDropUnimportantTransactions() { + return dropUnimportantTransactions.get(); + } + + public int getTransactionQueueSize() { + return transactionQueueSize.get(); + } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/SQLiteDB.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/SQLiteDB.java index bae68f143..7db8118d3 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/SQLiteDB.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/SQLiteDB.java @@ -25,7 +25,6 @@ import com.djrapitops.plan.storage.file.PlanFiles; import com.djrapitops.plan.storage.upkeep.DBKeepAliveTask; import com.djrapitops.plan.utilities.MiscUtils; import com.djrapitops.plan.utilities.SemaphoreAccessCounter; -import com.djrapitops.plan.utilities.logging.ErrorContext; import com.djrapitops.plan.utilities.logging.ErrorLogger; import dagger.Lazy; import net.playeranalytics.plugin.scheduling.RunnableFactory; @@ -35,10 +34,15 @@ import net.playeranalytics.plugin.server.PluginLogger; import javax.inject.Inject; import javax.inject.Singleton; import java.io.File; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.net.URLConnection; import java.sql.Connection; -import java.sql.DriverManager; import java.sql.SQLException; +import java.util.List; import java.util.Objects; +import java.util.Properties; /** * @author AuroraLS3 @@ -57,20 +61,32 @@ public class SQLiteDB extends SQLDB { */ private final SemaphoreAccessCounter connectionLock = new SemaphoreAccessCounter(); + private Constructor connectionConstructor; + private SQLiteDB( File databaseFile, Locale locale, PlanConfig config, + PlanFiles files, Lazy serverInfo, RunnableFactory runnableFactory, PluginLogger logger, ErrorLogger errorLogger ) { - super(() -> serverInfo.get().getServerUUID(), locale, config, runnableFactory, logger, errorLogger); + super(() -> serverInfo.get().getServerUUID(), locale, config, files, runnableFactory, logger, errorLogger); dbName = databaseFile.getName(); this.databaseFile = databaseFile; } + @Override + protected List getDependencyResource() { + try { + return files.getResourceFromJar("dependencies/sqliteDriver.txt").asLines(); + } catch (IOException e) { + throw new DBInitException("Failed to get SQLite dependency information: " + e.getMessage(), e); + } + } + @Override public void setupDataSource() { try { @@ -78,19 +94,16 @@ public class SQLiteDB extends SQLDB { connection = getNewConnection(databaseFile); } catch (SQLException e) { - throw new DBInitException(e.getMessage(), e); + throw new DBInitException(e.toString(), e); } startConnectionPingTask(); } public Connection getNewConnection(File dbFile) throws SQLException { - try { - Class.forName("org.sqlite.JDBC"); - } catch (ClassNotFoundException e) { - errorLogger.critical(e, ErrorContext.builder().whatToDo("Install SQLite Driver to the server").build()); - return null; + if (driverClassLoader == null) { + logger.info("Downloading SQLite Driver, this may take a while..."); + downloadDriver(); } - String dbFilePath = dbFile.getAbsolutePath(); Connection newConnection = getConnectionFor(dbFilePath); @@ -99,11 +112,52 @@ public class SQLiteDB extends SQLDB { } private Connection getConnectionFor(String dbFilePath) throws SQLException { + ensureConstructorIsAvailable(); + return tryToConnect(dbFilePath, true); + } + + private void ensureConstructorIsAvailable() { + if (connectionConstructor != null) { + return; + } + try { - return DriverManager.getConnection("jdbc:sqlite:" + dbFilePath + "?journal_mode=WAL"); - } catch (SQLException ignored) { + Class connectionClass = driverClassLoader.loadClass("org.sqlite.jdbc4.JDBC4Connection"); + connectionConstructor = connectionClass.getConstructor(String.class, String.class, Properties.class); + } catch (ClassNotFoundException | NoSuchMethodException e) { + throw new DBInitException("Failed to initialize SQLite Driver", e); + } + } + + private Connection tryToConnect(String dbFilePath, boolean withWAL) throws SQLException { + try { + Properties properties = new Properties(); + if (withWAL) properties.put("journal_mode", "WAL"); + + return (Connection) connectionConstructor.newInstance("jdbc:sqlite:" + dbFilePath, dbFilePath, properties); + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + if (!withWAL && cause instanceof SQLException) { + throw (SQLException) cause; + } else if (!(cause instanceof SQLException)) { + throw new DBInitException("Failed to initialize SQLite Driver", cause); + } + + // Run the method again with withWAL set to false, if it fails again, an exception will be thrown above logger.info(locale.getString(PluginLang.DB_NOTIFY_SQLITE_WAL)); - return DriverManager.getConnection("jdbc:sqlite:" + dbFilePath); + return tryToConnect(dbFilePath, false); + } catch (InstantiationException | IllegalAccessException e) { + throw new DBInitException("Failed to initialize SQLite Driver", e); + } finally { + new URLConnection(null) { + @Override + public void connect() { + // Hack for fixing a class loading crash (https://github.com/plan-player-analytics/Plan/issues/2202) + // Caused by https://github.com/xerial/sqlite-jdbc/issues/656 + // Where setDefaultUseCaches is set to false + // TODO Remove after the underlying issue has been fixed in SQLite + } + }.setDefaultUseCaches(true); } } @@ -216,7 +270,7 @@ public class SQLiteDB extends SQLDB { public SQLiteDB usingFile(File databaseFile) { return new SQLiteDB(databaseFile, - locale, config, serverInfo, + locale, config, files, serverInfo, runnableFactory, logger, errorLogger1 ); } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/DataStoreQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/DataStoreQueries.java index b3e9aa7c6..19c3f4eae 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/DataStoreQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/DataStoreQueries.java @@ -18,6 +18,7 @@ package com.djrapitops.plan.storage.database.queries; import com.djrapitops.plan.delivery.domain.Nickname; import com.djrapitops.plan.gathering.domain.*; +import com.djrapitops.plan.gathering.domain.event.JoinAddress; import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.sql.tables.*; import com.djrapitops.plan.storage.database.transactions.ExecBatchStatement; @@ -68,6 +69,8 @@ public class DataStoreQueries { statement.setInt(5, session.getMobKillCount()); statement.setLong(6, session.getAfkTime()); statement.setString(7, session.getServerUUID().toString()); + statement.setString(8, session.getExtraData(JoinAddress.class) + .map(JoinAddress::getAddress).orElse(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP)); } }; } @@ -146,7 +149,7 @@ public class DataStoreQueries { * @param playerName Name of the player. * @return Executable, use inside a {@link com.djrapitops.plan.storage.database.transactions.Transaction} */ - public static Executable registerBaseUser(UUID playerUUID, long registered, String playerName) { + public static ExecStatement registerBaseUser(UUID playerUUID, long registered, String playerName) { return new ExecStatement(UsersTable.INSERT_STATEMENT) { @Override public void prepare(PreparedStatement statement) throws SQLException { @@ -193,7 +196,7 @@ public class DataStoreQueries { statement.setLong(2, registered); statement.setString(3, serverUUID.toString()); statement.setBoolean(4, false); // Banned - statement.setString(5, joinAddress); + statement.setString(5, StringUtils.truncate(joinAddress, JoinAddressTable.JOIN_ADDRESS_MAX_LENGTH)); statement.setBoolean(6, false); // Operator } }; @@ -301,8 +304,8 @@ public class DataStoreQueries { public static Executable updateJoinAddress(UUID playerUUID, ServerUUID serverUUID, String joinAddress) { String sql = "UPDATE " + UserInfoTable.TABLE_NAME + " SET " + UserInfoTable.JOIN_ADDRESS + "=?" + - WHERE + UserInfoTable.USER_UUID + "=?" + - AND + UserInfoTable.SERVER_UUID + "=?"; + WHERE + UserInfoTable.USER_ID + "=" + UsersTable.SELECT_USER_ID + + AND + UserInfoTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID; return new ExecStatement(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/LargeFetchQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/LargeFetchQueries.java index 3b439894e..b4d446a50 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/LargeFetchQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/LargeFetchQueries.java @@ -51,7 +51,7 @@ public class LargeFetchQueries { * @return Map: Server UUID - List of TPS data */ public static Query>> fetchAllTPSData() { - String serverIDColumn = ServerTable.TABLE_NAME + '.' + ServerTable.SERVER_ID; + String serverIDColumn = ServerTable.TABLE_NAME + '.' + ServerTable.ID; String serverUUIDColumn = ServerTable.TABLE_NAME + '.' + ServerTable.SERVER_UUID + " as s_uuid"; String sql = SELECT + TPSTable.DATE + ',' + @@ -66,7 +66,7 @@ public class LargeFetchQueries { FROM + TPSTable.TABLE_NAME + INNER_JOIN + ServerTable.TABLE_NAME + " on " + serverIDColumn + "=" + TPSTable.SERVER_ID; - return new QueryAllStatement>>(sql, 50000) { + return new QueryAllStatement<>(sql, 50000) { @Override public Map> processResults(ResultSet set) throws SQLException { Map> serverMap = new HashMap<>(); @@ -101,7 +101,7 @@ public class LargeFetchQueries { public static Query>> fetchAllWorldNames() { String sql = SELECT + '*' + FROM + WorldTable.TABLE_NAME; - return new QueryAllStatement>>(sql, 1000) { + return new QueryAllStatement<>(sql, 1000) { @Override public Map> processResults(ResultSet set) throws SQLException { Map> worldMap = new HashMap<>(); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/LargeStoreQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/LargeStoreQueries.java index a03f5030a..c7563f6d2 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/LargeStoreQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/LargeStoreQueries.java @@ -19,15 +19,19 @@ package com.djrapitops.plan.storage.database.queries; import com.djrapitops.plan.delivery.domain.Nickname; import com.djrapitops.plan.delivery.domain.World; import com.djrapitops.plan.delivery.domain.auth.User; +import com.djrapitops.plan.exceptions.database.DBOpException; import com.djrapitops.plan.gathering.domain.*; +import com.djrapitops.plan.gathering.domain.event.JoinAddress; import com.djrapitops.plan.identification.Server; import com.djrapitops.plan.identification.ServerUUID; +import com.djrapitops.plan.storage.database.queries.objects.JoinAddressQueries; import com.djrapitops.plan.storage.database.queries.objects.WorldTimesQueries; import com.djrapitops.plan.storage.database.sql.tables.*; import com.djrapitops.plan.storage.database.transactions.ExecBatchStatement; import com.djrapitops.plan.storage.database.transactions.Executable; import org.apache.commons.lang3.StringUtils; +import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Types; @@ -153,6 +157,7 @@ public class LargeStoreQueries { statement.setString(3, server.getWebAddress()); statement.setBoolean(4, true); statement.setBoolean(5, server.isProxy()); + statement.setString(6, server.getPlanVersion()); statement.addBatch(); } } @@ -284,6 +289,8 @@ public class LargeStoreQueries { statement.setInt(5, session.getMobKillCount()); statement.setLong(6, session.getAfkTime()); statement.setString(7, session.getServerUUID().toString()); + statement.setString(8, session.getExtraData(JoinAddress.class) + .map(JoinAddress::getAddress).orElse(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP)); statement.addBatch(); } } @@ -293,6 +300,7 @@ public class LargeStoreQueries { public static Executable storeAllSessionsWithKillAndWorldData(Collection sessions) { return connection -> { Set existingWorlds = WorldTimesQueries.fetchWorlds().executeWithConnection(connection); + tryStoreAllJoinAddresses(sessions, connection, 0); storeAllWorldNames(sessions, existingWorlds).execute(connection); storeAllSessionsWithoutKillOrWorldData(sessions).execute(connection); storeSessionKillData(sessions).execute(connection); @@ -300,16 +308,54 @@ public class LargeStoreQueries { }; } + private static void tryStoreAllJoinAddresses(Collection sessions, Connection connection, int attempt) { + try { + List existingJoinAddresses = JoinAddressQueries.allJoinAddresses().executeWithConnection(connection); + storeAllJoinAddresses(sessions, existingJoinAddresses).execute(connection); + } catch (DBOpException e) { + if (e.getMessage().contains("Duplicate entry") && attempt < 3) { + tryStoreAllJoinAddresses(sessions, connection, attempt + 1); + } else { + throw e; + } + } + } + + private static Executable storeAllJoinAddresses(Collection sessions, List existingJoinAddresses) { + return new ExecBatchStatement(JoinAddressTable.INSERT_STATEMENT) { + @Override + public void prepare(PreparedStatement statement) { + sessions.stream() + .map(FinishedSession::getExtraData) + .map(extraData -> extraData.get(JoinAddress.class)) + .filter(Optional::isPresent) + .map(Optional::get) + .map(JoinAddress::getAddress) + .map(joinAddress -> StringUtils.truncate(joinAddress, JoinAddressTable.JOIN_ADDRESS_MAX_LENGTH)) + .distinct() + .filter(address -> !existingJoinAddresses.contains(address)) + .forEach(address -> { + try { + statement.setString(1, address); + statement.addBatch(); + } catch (SQLException e) { + throw DBOpException.forCause(JoinAddressTable.INSERT_STATEMENT, e); + } + }); + } + }; + } + private static Executable storeAllWorldNames(Collection sessions, Set existingWorlds) { Set worlds = sessions.stream().flatMap(session -> { - ServerUUID serverUUID = session.getServerUUID(); - return session.getExtraData(WorldTimes.class) - .map(WorldTimes::getWorldTimes) - .map(Map::keySet) - .orElseGet(Collections::emptySet) - .stream() - .map(worldName -> new World(worldName, serverUUID)); - }).filter(world -> !existingWorlds.contains(world)) + ServerUUID serverUUID = session.getServerUUID(); + return session.getExtraData(WorldTimes.class) + .map(WorldTimes::getWorldTimes) + .map(Map::keySet) + .orElseGet(Collections::emptySet) + .stream() + .map(worldName -> new World(worldName, serverUUID)); + }).filter(world -> !existingWorlds.contains(world)) .collect(Collectors.toSet()); if (worlds.isEmpty()) return Executable.empty(); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/MapRowExtractor.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/MapRowExtractor.java new file mode 100644 index 000000000..521c648a1 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/MapRowExtractor.java @@ -0,0 +1,28 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.storage.database.queries; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Map; + +@FunctionalInterface +public interface MapRowExtractor { + + void extract(ResultSet set, Map to) throws SQLException; + +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/PerServerAggregateQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/PerServerAggregateQueries.java index 47072b56f..04fbf8a3e 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/PerServerAggregateQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/PerServerAggregateQueries.java @@ -18,7 +18,9 @@ package com.djrapitops.plan.storage.database.queries; import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.sql.tables.KillsTable; +import com.djrapitops.plan.storage.database.sql.tables.ServerTable; import com.djrapitops.plan.storage.database.sql.tables.SessionsTable; +import com.djrapitops.plan.storage.database.sql.tables.UsersTable; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -51,11 +53,12 @@ public class PerServerAggregateQueries { */ public static Query> lastSeenOnServers(UUID playerUUID) { String sql = SELECT + "MAX(" + SessionsTable.SESSION_END + ") as last_seen, " + - SessionsTable.SERVER_UUID + + ServerTable.SERVER_UUID + FROM + SessionsTable.TABLE_NAME + - WHERE + SessionsTable.USER_UUID + "=?" + - GROUP_BY + SessionsTable.SERVER_UUID; - return new QueryStatement>(sql) { + INNER_JOIN + ServerTable.TABLE_NAME + " se on se." + ServerTable.ID + '=' + SessionsTable.TABLE_NAME + '.' + SessionsTable.SERVER_ID + + WHERE + SessionsTable.USER_ID + "=" + UsersTable.SELECT_USER_ID + + GROUP_BY + SessionsTable.SERVER_ID; + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, playerUUID.toString()); @@ -65,7 +68,7 @@ public class PerServerAggregateQueries { public Map processResults(ResultSet set) throws SQLException { Map lastSeenMap = new HashMap<>(); while (set.next()) { - ServerUUID serverUUID = ServerUUID.fromString(set.getString(SessionsTable.SERVER_UUID)); + ServerUUID serverUUID = ServerUUID.fromString(set.getString(ServerTable.SERVER_UUID)); long lastSeen = set.getLong("last_seen"); lastSeenMap.put(serverUUID, lastSeen); } @@ -81,7 +84,8 @@ public class PerServerAggregateQueries { * @return Map: Server UUID - Player kill count */ public static Query> playerKillCountOnServers(UUID playerUUID) { - String sql = SELECT + "COUNT(1) as kill_count, " + KillsTable.SERVER_UUID + FROM + KillsTable.TABLE_NAME + + String sql = SELECT + "COUNT(1) as kill_count, " + KillsTable.SERVER_UUID + + FROM + KillsTable.TABLE_NAME + WHERE + KillsTable.KILLER_UUID + "=?" + GROUP_BY + KillsTable.SERVER_UUID; return getQueryForCountOf(playerUUID, sql, "kill_count"); @@ -95,23 +99,27 @@ public class PerServerAggregateQueries { */ public static Query> mobKillCountOnServers(UUID playerUUID) { String sql = SELECT + "SUM(" + SessionsTable.MOB_KILLS + ") as kill_count, " + - SessionsTable.SERVER_UUID + FROM + SessionsTable.TABLE_NAME + - WHERE + SessionsTable.USER_UUID + "=?" + - GROUP_BY + SessionsTable.SERVER_UUID; + ServerTable.SERVER_UUID + " as server_uuid" + + FROM + SessionsTable.TABLE_NAME + + INNER_JOIN + ServerTable.TABLE_NAME + " se on se." + ServerTable.ID + '=' + SessionsTable.TABLE_NAME + '.' + SessionsTable.SERVER_ID + + WHERE + SessionsTable.USER_ID + "=" + UsersTable.SELECT_USER_ID + + GROUP_BY + SessionsTable.SERVER_ID; return getQueryForCountOf(playerUUID, sql, "kill_count"); } public static Query> totalDeathCountOnServers(UUID playerUUID) { String sql = SELECT + "SUM(" + SessionsTable.DEATHS + ") as death_count, " + - SessionsTable.SERVER_UUID + FROM + SessionsTable.TABLE_NAME + - WHERE + SessionsTable.USER_UUID + "=?" + - GROUP_BY + SessionsTable.SERVER_UUID; + ServerTable.SERVER_UUID + " as server_uuid" + + FROM + SessionsTable.TABLE_NAME + + INNER_JOIN + ServerTable.TABLE_NAME + " se on se." + ServerTable.ID + '=' + SessionsTable.TABLE_NAME + '.' + SessionsTable.SERVER_ID + + WHERE + SessionsTable.USER_ID + "=" + UsersTable.SELECT_USER_ID + + GROUP_BY + SessionsTable.SERVER_ID; return getQueryForCountOf(playerUUID, sql, "death_count"); } private static QueryStatement> getQueryForCountOf(UUID playerUUID, String sql, String column) { - return new QueryStatement>(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, playerUUID.toString()); @@ -121,7 +129,7 @@ public class PerServerAggregateQueries { public Map processResults(ResultSet set) throws SQLException { Map killCountMap = new HashMap<>(); while (set.next()) { - ServerUUID serverUUID = ServerUUID.fromString(set.getString(SessionsTable.SERVER_UUID)); + ServerUUID serverUUID = ServerUUID.fromString(set.getString("server_uuid")); int count = set.getInt(column); killCountMap.put(serverUUID, count); } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/PlayerFetchQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/PlayerFetchQueries.java index d2fe2f134..0af68cf47 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/PlayerFetchQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/PlayerFetchQueries.java @@ -17,6 +17,7 @@ package com.djrapitops.plan.storage.database.queries; import com.djrapitops.plan.identification.ServerUUID; +import com.djrapitops.plan.storage.database.sql.tables.ServerTable; import com.djrapitops.plan.storage.database.sql.tables.UserInfoTable; import com.djrapitops.plan.storage.database.sql.tables.UsersTable; @@ -49,7 +50,7 @@ public class PlayerFetchQueries { String sql = SELECT + UsersTable.USER_NAME + FROM + UsersTable.TABLE_NAME + WHERE + UsersTable.USER_UUID + "=?"; - return new QueryStatement>(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, playerUUID.toString()); @@ -72,14 +73,19 @@ public class PlayerFetchQueries { * @return True if the player's BaseUser is found */ public static Query isPlayerRegistered(UUID playerUUID) { - String sql = SELECT + "COUNT(1) as c" + + String sql = SELECT + UsersTable.ID + FROM + UsersTable.TABLE_NAME + WHERE + UsersTable.USER_UUID + "=?"; - return new HasMoreThanZeroQueryStatement(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, playerUUID.toString()); } + + @Override + public Boolean processResults(ResultSet set) throws SQLException { + return set.next(); + } }; } @@ -93,8 +99,8 @@ public class PlayerFetchQueries { public static Query isPlayerRegisteredOnServer(UUID playerUUID, ServerUUID serverUUID) { String sql = SELECT + "COUNT(1) as c" + FROM + UserInfoTable.TABLE_NAME + - WHERE + UserInfoTable.USER_UUID + "=?" + - AND + UserInfoTable.SERVER_UUID + "=?"; + WHERE + UserInfoTable.USER_ID + "=" + UsersTable.SELECT_USER_ID + + AND + UserInfoTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID; return new HasMoreThanZeroQueryStatement(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { @@ -109,7 +115,7 @@ public class PlayerFetchQueries { FROM + UsersTable.TABLE_NAME + WHERE + UsersTable.USER_UUID + "=? LIMIT 1"; - return new QueryStatement>(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, playerUUID.toString()); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/QueryAPIQuery.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/QueryAPIQuery.java index 3c9c3f900..d7f54fcfa 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/QueryAPIQuery.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/QueryAPIQuery.java @@ -23,6 +23,7 @@ import com.djrapitops.plan.storage.database.SQLDB; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.Objects; public class QueryAPIQuery implements Query { @@ -57,4 +58,24 @@ public class QueryAPIQuery implements Query { throw DBOpException.forCause(sql, e); } } + + @Override + public String toString() { + return "QueryAPIQuery{" + + "sql='" + sql + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + QueryAPIQuery that = (QueryAPIQuery) o; + return Objects.equals(sql, that.sql); + } + + @Override + public int hashCode() { + return Objects.hash(sql); + } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/QueryParameterSetter.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/QueryParameterSetter.java new file mode 100644 index 000000000..c91de3f6d --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/QueryParameterSetter.java @@ -0,0 +1,56 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.storage.database.queries; + +import com.djrapitops.plan.identification.ServerUUID; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.UUID; + +public class QueryParameterSetter { + + private QueryParameterSetter() {} + + public static void setParameters(PreparedStatement statement, Object... parameters) throws SQLException { + int index = 1; + for (Object parameter : parameters) { + setParameter(statement, index, parameter); + index++; + } + } + + private static void setParameter(PreparedStatement statement, int index, Object parameter) throws SQLException { + if (parameter == null) { + statement.setNull(index, Types.VARCHAR); + } else if (parameter instanceof Integer) { + statement.setInt(index, (Integer) parameter); + } else if (parameter instanceof Long) { + statement.setLong(index, (Long) parameter); + } else if (parameter instanceof Double) { + statement.setDouble(index, (Double) parameter); + } else if (parameter instanceof Float) { + statement.setFloat(index, (Float) parameter); + } else if (parameter instanceof String) { + statement.setString(index, (String) parameter); + } else if (parameter instanceof UUID || parameter instanceof ServerUUID) { + statement.setString(index, parameter.toString()); + } + } + +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/QueryStatement.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/QueryStatement.java index 06d5d4efc..240df92cf 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/QueryStatement.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/QueryStatement.java @@ -23,6 +23,7 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.Objects; /** * SQL query that closes proper elements. @@ -65,14 +66,12 @@ public abstract class QueryStatement implements Query { } public T executeQuery(PreparedStatement statement) throws SQLException { - try { + try (statement) { statement.setFetchSize(fetchSize); prepare(statement); try (ResultSet set = statement.executeQuery()) { return processResults(set); } - } finally { - statement.close(); } } @@ -88,4 +87,17 @@ public abstract class QueryStatement implements Query { public String toString() { return "Query (" + sql + ')'; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + QueryStatement that = (QueryStatement) o; + return Objects.equals(getSql(), that.getSql()); + } + + @Override + public int hashCode() { + return Objects.hash(getSql()); + } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/RowExtractor.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/RowExtractor.java new file mode 100644 index 000000000..2bd3b6b89 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/RowExtractor.java @@ -0,0 +1,27 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.storage.database.queries; + +import java.sql.ResultSet; +import java.sql.SQLException; + +@FunctionalInterface +public interface RowExtractor { + + T extract(ResultSet set) throws SQLException; + +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/RowExtractors.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/RowExtractors.java new file mode 100644 index 000000000..628d6d24c --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/RowExtractors.java @@ -0,0 +1,52 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.storage.database.queries; + +import com.djrapitops.plan.identification.ServerUUID; + +import java.util.UUID; + +public class RowExtractors { + private RowExtractors() {} + + public static RowExtractor getInt(String columnName) { + return set -> { + int value = set.getInt(columnName); + return set.wasNull() ? null : value; + }; + } + + public static RowExtractor getLong(String columnName) { + return set -> { + long value = set.getLong(columnName); + return set.wasNull() ? null : value; + }; + } + + public static RowExtractor getString(String columnName) { + return set -> set.getString(columnName); + } + + public static RowExtractor getUUID(String columnName) { + return set -> UUID.fromString(set.getString(columnName)); + } + + public static RowExtractor getServerUUID(String columnName) { + return set -> ServerUUID.fromString(set.getString(columnName)); + } + +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/ServerAggregateQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/ServerAggregateQueries.java index 0e0f2445e..33f5025b9 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/ServerAggregateQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/ServerAggregateQueries.java @@ -17,6 +17,7 @@ package com.djrapitops.plan.storage.database.queries; import com.djrapitops.plan.identification.ServerUUID; +import com.djrapitops.plan.storage.database.sql.tables.ServerTable; import com.djrapitops.plan.storage.database.sql.tables.UserInfoTable; import com.djrapitops.plan.storage.database.sql.tables.UsersTable; @@ -46,7 +47,7 @@ public class ServerAggregateQueries { */ public static Query baseUserCount() { String sql = SELECT + "COUNT(1) as c FROM " + UsersTable.TABLE_NAME; - return new QueryAllStatement(sql) { + return new QueryAllStatement<>(sql) { @Override public Integer processResults(ResultSet set) throws SQLException { return set.next() ? set.getInt("c") : 0; @@ -62,8 +63,8 @@ public class ServerAggregateQueries { */ public static Query serverUserCount(ServerUUID serverUUID) { String sql = SELECT + "COUNT(1) as c FROM " + UserInfoTable.TABLE_NAME + - WHERE + UserInfoTable.SERVER_UUID + "=?"; - return new QueryStatement(sql) { + WHERE + UserInfoTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID; + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -85,14 +86,17 @@ public class ServerAggregateQueries { * @return Map: Server UUID - Count of users registered to that server */ public static Query> serverUserCounts() { - String sql = SELECT + "COUNT(1) as c, " + UserInfoTable.SERVER_UUID + FROM + UserInfoTable.TABLE_NAME + - GROUP_BY + UserInfoTable.SERVER_UUID; - return new QueryAllStatement>(sql, 100) { + String sql = SELECT + "COUNT(1) as c, " + ServerTable.SERVER_UUID + + FROM + UserInfoTable.TABLE_NAME + + INNER_JOIN + ServerTable.TABLE_NAME + " s on s." + ServerTable.ID + '=' + UserInfoTable.TABLE_NAME + '.' + UserInfoTable.SERVER_ID + + GROUP_BY + UserInfoTable.SERVER_ID; + + return new QueryAllStatement<>(sql, 100) { @Override public Map processResults(ResultSet set) throws SQLException { Map ofServer = new HashMap<>(); while (set.next()) { - ServerUUID serverUUID = ServerUUID.fromString(set.getString(UserInfoTable.SERVER_UUID)); + ServerUUID serverUUID = ServerUUID.fromString(set.getString(ServerTable.SERVER_UUID)); int count = set.getInt("c"); ofServer.put(serverUUID, count); } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/analysis/ActivityIndexQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/analysis/ActivityIndexQueries.java index 12e91f60a..433447f25 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/analysis/ActivityIndexQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/analysis/ActivityIndexQueries.java @@ -20,8 +20,11 @@ import com.djrapitops.plan.delivery.domain.mutators.ActivityIndex; import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.queries.Query; import com.djrapitops.plan.storage.database.queries.QueryStatement; +import com.djrapitops.plan.storage.database.sql.tables.ServerTable; import com.djrapitops.plan.storage.database.sql.tables.SessionsTable; import com.djrapitops.plan.storage.database.sql.tables.UserInfoTable; +import com.djrapitops.plan.storage.database.sql.tables.UsersTable; +import com.djrapitops.plan.utilities.Benchmark; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -72,30 +75,33 @@ public class ActivityIndexQueries { // Static method class } + @Benchmark.Slow("407ms") public static Query fetchRegularPlayerCount(long date, ServerUUID serverUUID, long playtimeThreshold) { return fetchActivityGroupCount(date, serverUUID, playtimeThreshold, ActivityIndex.REGULAR, 5.1); } public static String selectActivityIndexSQL() { String selectActivePlaytimeSQL = SELECT + - "ux." + UserInfoTable.USER_UUID + ",COALESCE(active_playtime,0) AS active_playtime" + + "ux." + UserInfoTable.USER_ID + ",COALESCE(active_playtime,0) AS active_playtime" + FROM + UserInfoTable.TABLE_NAME + " ux" + - LEFT_JOIN + '(' + SELECT + SessionsTable.USER_UUID + + LEFT_JOIN + '(' + SELECT + SessionsTable.USER_ID + ",SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + '-' + SessionsTable.AFK_TIME + ") as active_playtime" + FROM + SessionsTable.TABLE_NAME + - WHERE + SessionsTable.SERVER_UUID + "=?" + + WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + AND + SessionsTable.SESSION_END + ">=?" + AND + SessionsTable.SESSION_START + "<=?" + - GROUP_BY + SessionsTable.USER_UUID + - ") sx on sx.uuid=ux.uuid"; + GROUP_BY + SessionsTable.USER_ID + + ") sx on sx." + SessionsTable.USER_ID + "=ux." + UserInfoTable.USER_ID; String selectThreeWeeks = selectActivePlaytimeSQL + UNION_ALL + selectActivePlaytimeSQL + UNION_ALL + selectActivePlaytimeSQL; return SELECT + "5.0 - 5.0 * AVG(1.0 / (?/2.0 * (q1.active_playtime*1.0/?) +1.0)) as activity_index," + - "q1." + SessionsTable.USER_UUID + + "u." + UsersTable.ID + " as " + SessionsTable.USER_ID + ',' + + "u." + UsersTable.USER_UUID + FROM + '(' + selectThreeWeeks + ") q1" + - GROUP_BY + "q1." + SessionsTable.USER_UUID; + INNER_JOIN + UsersTable.TABLE_NAME + " u on u." + UsersTable.ID + "=q1." + UserInfoTable.USER_ID + + GROUP_BY + "q1." + UserInfoTable.USER_ID; } public static void setSelectActivityIndexSQLParameters(PreparedStatement statement, int index, long playtimeThreshold, ServerUUID serverUUID, long date) throws SQLException { @@ -116,18 +122,15 @@ public class ActivityIndexQueries { public static Query fetchActivityGroupCount(long date, ServerUUID serverUUID, long playtimeThreshold, double above, double below) { String selectActivityIndex = selectActivityIndexSQL(); - String selectIndexes = SELECT + "COALESCE(activity_index, 0) as activity_index" + + String selectCount = SELECT + "COUNT(1) as count, COALESCE(activity_index, 0) as activity_index" + FROM + UserInfoTable.TABLE_NAME + " u" + - LEFT_JOIN + '(' + selectActivityIndex + ") q2 on q2." + SessionsTable.USER_UUID + "=u." + UserInfoTable.USER_UUID + - WHERE + "u." + UserInfoTable.SERVER_UUID + "=?" + - AND + "u." + UserInfoTable.REGISTERED + "<=?"; + LEFT_JOIN + '(' + selectActivityIndex + ") q2 on q2." + SessionsTable.USER_ID + "=u." + UserInfoTable.USER_ID + + WHERE + "u." + UserInfoTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + + AND + "u." + UserInfoTable.REGISTERED + "<=?" + + AND + "activity_index>=?" + + AND + "activity_index=?" + - AND + "i.activity_index(selectCount) { + return new QueryStatement<>(selectCount) { @Override public void prepare(PreparedStatement statement) throws SQLException { setSelectActivityIndexSQLParameters(statement, 1, playtimeThreshold, serverUUID, date); @@ -149,11 +152,11 @@ public class ActivityIndexQueries { String selectIndexes = SELECT + "activity_index" + FROM + UserInfoTable.TABLE_NAME + " u" + - LEFT_JOIN + '(' + selectActivityIndex + ") s on s." + SessionsTable.USER_UUID + "=u." + UserInfoTable.USER_UUID + - WHERE + "u." + UserInfoTable.SERVER_UUID + "=?" + + LEFT_JOIN + '(' + selectActivityIndex + ") s on s." + SessionsTable.USER_ID + "=u." + UserInfoTable.USER_ID + + WHERE + "u." + UserInfoTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + AND + "u." + UserInfoTable.REGISTERED + "<=?"; - return new QueryStatement>(selectIndexes) { + return new QueryStatement<>(selectIndexes) { @Override public void prepare(PreparedStatement statement) throws SQLException { setSelectActivityIndexSQLParameters(statement, 1, threshold, serverUUID, date); @@ -179,14 +182,14 @@ public class ActivityIndexQueries { String selectActivePlayerCount = SELECT + "COUNT(1) as count" + FROM + '(' + selectActivityIndex + ") q2" + - INNER_JOIN + UserInfoTable.TABLE_NAME + " u on u." + UserInfoTable.USER_UUID + "=q2." + SessionsTable.USER_UUID + - WHERE + "u." + UserInfoTable.SERVER_UUID + "=?" + + INNER_JOIN + UserInfoTable.TABLE_NAME + " u on u." + UserInfoTable.USER_ID + "=q2." + SessionsTable.USER_ID + + WHERE + "u." + UserInfoTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + AND + "u." + UserInfoTable.REGISTERED + ">=?" + AND + "u." + UserInfoTable.REGISTERED + "<=?" + AND + "q2.activity_index>=?" + AND + "q2.activity_index(selectActivePlayerCount) { + return new QueryStatement<>(selectActivePlayerCount) { @Override public void prepare(PreparedStatement statement) throws SQLException { setSelectActivityIndexSQLParameters(statement, 1, threshold, serverUUID, before); @@ -218,13 +221,13 @@ public class ActivityIndexQueries { FROM + '(' + selectActivityIndex + ") q2" + // Join two select activity index queries together to query Regular and Inactive players INNER_JOIN + '(' + selectActivityIndex.replace("q1", "q3") + ") q4" + - " on q2." + SessionsTable.USER_UUID + "=q4." + SessionsTable.USER_UUID + + " on q2." + SessionsTable.USER_ID + "=q4." + SessionsTable.USER_ID + WHERE + "q2.activity_index>=?" + AND + "q2.activity_index=?" + AND + "q4.activity_index(selectActivePlayerCount) { + return new QueryStatement<>(selectActivePlayerCount) { @Override public void prepare(PreparedStatement statement) throws SQLException { setSelectActivityIndexSQLParameters(statement, 1, threshold, serverUUID, start); @@ -246,16 +249,16 @@ public class ActivityIndexQueries { return database -> { // INNER JOIN limits the users to only those that are regular String selectPlaytimePerPlayer = SELECT + - "p." + SessionsTable.USER_UUID + "," + + "p." + SessionsTable.USER_ID + "," + "SUM(p." + SessionsTable.SESSION_END + "-p." + SessionsTable.SESSION_START + ") as playtime" + FROM + SessionsTable.TABLE_NAME + " p" + - INNER_JOIN + '(' + selectActivityIndexSQL() + ") q2 on q2." + SessionsTable.USER_UUID + "=p." + SessionsTable.USER_UUID + + INNER_JOIN + '(' + selectActivityIndexSQL() + ") q2 on q2." + SessionsTable.USER_ID + "=p." + SessionsTable.USER_ID + WHERE + "p." + SessionsTable.SESSION_END + "<=?" + AND + "p." + SessionsTable.SESSION_START + ">=?" + - AND + "p." + SessionsTable.SERVER_UUID + "=?" + + AND + "p." + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + AND + "q2.activity_index>=?" + AND + "q2.activity_index(selectAverage, 100) { @@ -281,13 +284,13 @@ public class ActivityIndexQueries { return database -> { // INNER JOIN limits the users to only those that are regular String selectSessionLengthPerPlayer = SELECT + - "p." + SessionsTable.USER_UUID + "," + + "p." + SessionsTable.USER_ID + "," + "p." + SessionsTable.SESSION_END + "-p." + SessionsTable.SESSION_START + " as length" + FROM + SessionsTable.TABLE_NAME + " p" + - INNER_JOIN + '(' + selectActivityIndexSQL() + ") q2 on q2." + SessionsTable.USER_UUID + "=p." + SessionsTable.USER_UUID + + INNER_JOIN + '(' + selectActivityIndexSQL() + ") q2 on q2." + SessionsTable.USER_ID + "=p." + SessionsTable.USER_ID + WHERE + "p." + SessionsTable.SESSION_END + "<=?" + AND + "p." + SessionsTable.SESSION_START + ">=?" + - AND + "p." + SessionsTable.SERVER_UUID + "=?" + + AND + "p." + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + AND + "q2.activity_index>=?" + AND + "q2.activity_index { // INNER JOIN limits the users to only those that are regular String selectPlaytimePerPlayer = SELECT + - "p." + SessionsTable.USER_UUID + "," + + "p." + SessionsTable.USER_ID + "," + "SUM(p." + SessionsTable.AFK_TIME + ") as afk" + FROM + SessionsTable.TABLE_NAME + " p" + - INNER_JOIN + '(' + selectActivityIndexSQL() + ") q2 on q2." + SessionsTable.USER_UUID + "=p." + SessionsTable.USER_UUID + + INNER_JOIN + '(' + selectActivityIndexSQL() + ") q2 on q2." + SessionsTable.USER_ID + "=p." + SessionsTable.USER_ID + WHERE + "p." + SessionsTable.SESSION_END + "<=?" + AND + "p." + SessionsTable.SESSION_START + ">=?" + - AND + "p." + SessionsTable.SERVER_UUID + "=?" + + AND + "p." + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + AND + "q2.activity_index>=?" + AND + "q2.activity_index(selectAverage, 100) { @@ -347,17 +350,17 @@ public class ActivityIndexQueries { } public static Query> activityIndexForNewPlayers(long after, long before, ServerUUID serverUUID, Long threshold) { - String selectNewUUIDs = SELECT + UserInfoTable.USER_UUID + + String selectNewUUIDs = SELECT + UserInfoTable.USER_ID + FROM + UserInfoTable.TABLE_NAME + WHERE + UserInfoTable.REGISTERED + "<=?" + AND + UserInfoTable.REGISTERED + ">=?" + - AND + UserInfoTable.SERVER_UUID + "=?"; + AND + UserInfoTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID; String sql = SELECT + "activity_index" + FROM + '(' + selectNewUUIDs + ") n" + - INNER_JOIN + '(' + selectActivityIndexSQL() + ") a on n." + SessionsTable.USER_UUID + "=a." + SessionsTable.USER_UUID; + INNER_JOIN + '(' + selectActivityIndexSQL() + ") a on n." + SessionsTable.USER_ID + "=a." + SessionsTable.USER_ID; - return new QueryStatement>(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setLong(1, before); @@ -378,24 +381,24 @@ public class ActivityIndexQueries { } public static Query averageActivityIndexForRetainedPlayers(long after, long before, ServerUUID serverUUID, Long threshold) { - String selectNewUUIDs = SELECT + UserInfoTable.USER_UUID + + String selectNewUUIDs = SELECT + UserInfoTable.USER_ID + FROM + UserInfoTable.TABLE_NAME + WHERE + UserInfoTable.REGISTERED + "<=?" + AND + UserInfoTable.REGISTERED + ">=?" + - AND + UserInfoTable.SERVER_UUID + "=?"; + AND + UserInfoTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID; - String selectUniqueUUIDs = SELECT + "DISTINCT " + SessionsTable.USER_UUID + + String selectUniqueUUIDs = SELECT + "DISTINCT " + SessionsTable.USER_ID + FROM + SessionsTable.TABLE_NAME + WHERE + SessionsTable.SESSION_START + ">=?" + AND + SessionsTable.SESSION_END + "<=?" + - AND + SessionsTable.SERVER_UUID + "=?"; + AND + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID; String sql = SELECT + "AVG(activity_index) as average" + FROM + '(' + selectNewUUIDs + ") n" + - INNER_JOIN + '(' + selectUniqueUUIDs + ") u on n." + SessionsTable.USER_UUID + "=u." + SessionsTable.USER_UUID + - INNER_JOIN + '(' + selectActivityIndexSQL() + ") a on n." + SessionsTable.USER_UUID + "=a." + SessionsTable.USER_UUID; + INNER_JOIN + '(' + selectUniqueUUIDs + ") u on n." + SessionsTable.USER_ID + "=u." + SessionsTable.USER_ID + + INNER_JOIN + '(' + selectActivityIndexSQL() + ") a on n." + SessionsTable.USER_ID + "=a." + SessionsTable.USER_ID; - return new QueryStatement(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setLong(1, before); @@ -418,25 +421,25 @@ public class ActivityIndexQueries { } public static Query averageActivityIndexForNonRetainedPlayers(long after, long before, ServerUUID serverUUID, Long threshold) { - String selectNewUUIDs = SELECT + UserInfoTable.USER_UUID + + String selectNewUUIDs = SELECT + UserInfoTable.USER_ID + FROM + UserInfoTable.TABLE_NAME + WHERE + UserInfoTable.REGISTERED + "<=?" + AND + UserInfoTable.REGISTERED + ">=?" + - AND + UserInfoTable.SERVER_UUID + "=?"; + AND + UserInfoTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID; - String selectUniqueUUIDs = SELECT + "DISTINCT " + SessionsTable.USER_UUID + + String selectUniqueUUIDs = SELECT + "DISTINCT " + SessionsTable.USER_ID + FROM + SessionsTable.TABLE_NAME + WHERE + SessionsTable.SESSION_START + ">=?" + AND + SessionsTable.SESSION_END + "<=?" + - AND + SessionsTable.SERVER_UUID + "=?"; + AND + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID; String sql = SELECT + "AVG(activity_index) as average" + FROM + '(' + selectNewUUIDs + ") n" + - LEFT_JOIN + '(' + selectUniqueUUIDs + ") u on n." + SessionsTable.USER_UUID + "=u." + SessionsTable.USER_UUID + - INNER_JOIN + '(' + selectActivityIndexSQL() + ") a on n." + SessionsTable.USER_UUID + "=a." + SessionsTable.USER_UUID + - WHERE + "n." + SessionsTable.USER_UUID + IS_NULL; + LEFT_JOIN + '(' + selectUniqueUUIDs + ") u on n." + SessionsTable.USER_ID + "=u." + SessionsTable.USER_ID + + INNER_JOIN + '(' + selectActivityIndexSQL() + ") a on n." + SessionsTable.USER_ID + "=a." + SessionsTable.USER_ID + + WHERE + "n." + SessionsTable.USER_ID + IS_NULL; - return new QueryStatement(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setLong(1, before); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/analysis/NetworkActivityIndexQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/analysis/NetworkActivityIndexQueries.java index f4488c0ca..bb14659c9 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/analysis/NetworkActivityIndexQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/analysis/NetworkActivityIndexQueries.java @@ -17,8 +17,10 @@ package com.djrapitops.plan.storage.database.queries.analysis; import com.djrapitops.plan.delivery.domain.mutators.ActivityIndex; +import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.queries.Query; import com.djrapitops.plan.storage.database.queries.QueryStatement; +import com.djrapitops.plan.storage.database.sql.tables.ServerTable; import com.djrapitops.plan.storage.database.sql.tables.SessionsTable; import com.djrapitops.plan.storage.database.sql.tables.UsersTable; import org.apache.commons.text.TextStringBuilder; @@ -74,24 +76,37 @@ public class NetworkActivityIndexQueries { } public static String selectActivityIndexSQL() { + return selectActivityIndexSQL(Collections.emptyList()); + } + + public static String selectActivityIndexSQL(Collection onServers) { + String selectServerIds = SELECT + ServerTable.ID + + FROM + ServerTable.TABLE_NAME + + WHERE + ServerTable.SERVER_UUID + " IN ('" + new TextStringBuilder().appendWithSeparators(onServers, "','") + "')"; + String selectActivePlaytimeSQL = SELECT + - "ux." + UsersTable.USER_UUID + ",COALESCE(active_playtime,0) AS active_playtime" + + "ux." + UsersTable.ID + "," + + "ux." + UsersTable.USER_UUID + "," + + "COALESCE(active_playtime,0) AS active_playtime" + FROM + UsersTable.TABLE_NAME + " ux" + - LEFT_JOIN + '(' + SELECT + SessionsTable.USER_UUID + + LEFT_JOIN + '(' + SELECT + SessionsTable.USER_ID + ",SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + '-' + SessionsTable.AFK_TIME + ") as active_playtime" + FROM + SessionsTable.TABLE_NAME + WHERE + SessionsTable.SESSION_END + ">=?" + AND + SessionsTable.SESSION_START + "<=?" + - GROUP_BY + SessionsTable.USER_UUID + - ") sx on sx.uuid=ux.uuid"; + (onServers.isEmpty() ? "" : AND + SessionsTable.SERVER_ID + " IN (" + selectServerIds + ")") + + GROUP_BY + SessionsTable.USER_ID + + ") sx on sx." + SessionsTable.USER_ID + "=ux." + UsersTable.ID; String selectThreeWeeks = selectActivePlaytimeSQL + UNION_ALL + selectActivePlaytimeSQL + UNION_ALL + selectActivePlaytimeSQL; return SELECT + "5.0 - 5.0 * AVG(1.0 / (?/2.0 * (q1.active_playtime*1.0/?) +1.0)) as activity_index," + - "q1." + SessionsTable.USER_UUID + + "u." + UsersTable.ID + " as " + SessionsTable.USER_ID + ',' + + "u." + UsersTable.USER_UUID + FROM + '(' + selectThreeWeeks + ") q1" + - GROUP_BY + "q1." + SessionsTable.USER_UUID; + INNER_JOIN + UsersTable.TABLE_NAME + " u on u." + UsersTable.ID + "=q1." + UsersTable.ID + + GROUP_BY + "q1." + UsersTable.ID; } public static void setSelectActivityIndexSQLParameters(PreparedStatement statement, int index, long playtimeThreshold, long date) throws SQLException { @@ -111,7 +126,7 @@ public class NetworkActivityIndexQueries { String selectIndexes = SELECT + "COALESCE(activity_index, 0) as activity_index" + FROM + UsersTable.TABLE_NAME + " u" + - LEFT_JOIN + '(' + selectActivityIndex + ") q2 on q2." + SessionsTable.USER_UUID + "=u." + UsersTable.USER_UUID + + LEFT_JOIN + '(' + selectActivityIndex + ") q2 on q2." + SessionsTable.USER_ID + "=u." + UsersTable.ID + WHERE + "u." + UsersTable.REGISTERED + "<=?"; String selectCount = SELECT + "COUNT(1) as count" + @@ -119,7 +134,7 @@ public class NetworkActivityIndexQueries { WHERE + "i.activity_index>=?" + AND + "i.activity_index(selectCount) { + return new QueryStatement<>(selectCount) { @Override public void prepare(PreparedStatement statement) throws SQLException { setSelectActivityIndexSQLParameters(statement, 1, playtimeThreshold, date); @@ -140,10 +155,10 @@ public class NetworkActivityIndexQueries { String selectIndexes = SELECT + "activity_index" + FROM + UsersTable.TABLE_NAME + " u" + - LEFT_JOIN + '(' + selectActivityIndex + ") s on s." + SessionsTable.USER_UUID + "=u." + UsersTable.USER_UUID + + LEFT_JOIN + '(' + selectActivityIndex + ") s on s." + SessionsTable.USER_ID + "=u." + UsersTable.ID + WHERE + "u." + UsersTable.REGISTERED + "<=?"; - return new QueryStatement>(selectIndexes) { + return new QueryStatement<>(selectIndexes) { @Override public void prepare(PreparedStatement statement) throws SQLException { setSelectActivityIndexSQLParameters(statement, 1, threshold, date); @@ -163,17 +178,17 @@ public class NetworkActivityIndexQueries { }; } - public static Query> fetchActivityIndexGroupingsOn(long date, long threshold, Collection playerUUIDs) { - String selectActivityIndex = selectActivityIndexSQL(); + public static Query> fetchActivityIndexGroupingsOn(long date, long threshold, Collection userIds, List serverUUIDs) { + String selectActivityIndex = selectActivityIndexSQL(serverUUIDs); String selectIndexes = SELECT + "activity_index" + FROM + UsersTable.TABLE_NAME + " u" + - LEFT_JOIN + '(' + selectActivityIndex + ") s on s." + SessionsTable.USER_UUID + "=u." + UsersTable.USER_UUID + + LEFT_JOIN + '(' + selectActivityIndex + ") s on s." + SessionsTable.USER_ID + "=u." + UsersTable.ID + WHERE + "u." + UsersTable.REGISTERED + "<=?" + - AND + "u." + UsersTable.USER_UUID + " IN ('" + - new TextStringBuilder().appendWithSeparators(playerUUIDs, "','").build() + "')"; + AND + "u." + UsersTable.ID + " IN (" + + new TextStringBuilder().appendWithSeparators(userIds, ",").build() + ")"; - return new QueryStatement>(selectIndexes) { + return new QueryStatement<>(selectIndexes) { @Override public void prepare(PreparedStatement statement) throws SQLException { setSelectActivityIndexSQLParameters(statement, 1, threshold, date); @@ -198,13 +213,13 @@ public class NetworkActivityIndexQueries { String selectActivePlayerCount = SELECT + "COUNT(1) as count" + FROM + '(' + selectActivityIndex + ") q2" + - INNER_JOIN + UsersTable.TABLE_NAME + " u on u." + UsersTable.USER_UUID + "=q2." + SessionsTable.USER_UUID + + INNER_JOIN + UsersTable.TABLE_NAME + " u on q2." + SessionsTable.USER_ID + "=u." + UsersTable.ID + WHERE + "u." + UsersTable.REGISTERED + ">=?" + AND + "u." + UsersTable.REGISTERED + "<=?" + AND + "q2.activity_index>=?" + AND + "q2.activity_index(selectActivePlayerCount) { + return new QueryStatement<>(selectActivePlayerCount) { @Override public void prepare(PreparedStatement statement) throws SQLException { setSelectActivityIndexSQLParameters(statement, 1, threshold, before); @@ -234,13 +249,13 @@ public class NetworkActivityIndexQueries { FROM + '(' + selectActivityIndex + ") q2" + // Join two select activity index queries together to query Regular and Inactive players INNER_JOIN + '(' + selectActivityIndex.replace("q1", "q3") + ") q4" + - " on q2." + SessionsTable.USER_UUID + "=q4." + SessionsTable.USER_UUID + + " on q2." + SessionsTable.USER_ID + "=q4." + SessionsTable.USER_ID + WHERE + "q2.activity_index>=?" + AND + "q2.activity_index=?" + AND + "q4.activity_index(selectActivePlayerCount) { + return new QueryStatement<>(selectActivePlayerCount) { @Override public void prepare(PreparedStatement statement) throws SQLException { setSelectActivityIndexSQLParameters(statement, 1, threshold, end); @@ -262,15 +277,15 @@ public class NetworkActivityIndexQueries { return database -> { // INNER JOIN limits the users to only those that are regular String selectPlaytimePerPlayer = SELECT + - "p." + SessionsTable.USER_UUID + "," + + "p." + SessionsTable.USER_ID + "," + "SUM(p." + SessionsTable.SESSION_END + "-p." + SessionsTable.SESSION_START + ") as playtime" + FROM + SessionsTable.TABLE_NAME + " p" + - INNER_JOIN + '(' + selectActivityIndexSQL() + ") q2 on q2." + SessionsTable.USER_UUID + "=p." + SessionsTable.USER_UUID + + INNER_JOIN + '(' + selectActivityIndexSQL() + ") q2 on q2." + SessionsTable.USER_ID + "=p." + SessionsTable.USER_ID + WHERE + "p." + SessionsTable.SESSION_END + "<=?" + AND + "p." + SessionsTable.SESSION_START + ">=?" + AND + "q2.activity_index>=?" + AND + "q2.activity_index(selectAverage, 100) { @@ -295,10 +310,10 @@ public class NetworkActivityIndexQueries { return database -> { // INNER JOIN limits the users to only those that are regular String selectSessionLengthPerPlayer = SELECT + - "p." + SessionsTable.USER_UUID + "," + + "p." + SessionsTable.USER_ID + "," + "p." + SessionsTable.SESSION_END + "-p." + SessionsTable.SESSION_START + " as length" + FROM + SessionsTable.TABLE_NAME + " p" + - INNER_JOIN + '(' + selectActivityIndexSQL() + ") q2 on q2." + SessionsTable.USER_UUID + "=p." + SessionsTable.USER_UUID + + INNER_JOIN + '(' + selectActivityIndexSQL() + ") q2 on q2." + SessionsTable.USER_ID + "=p." + SessionsTable.USER_ID + WHERE + "p." + SessionsTable.SESSION_END + "<=?" + AND + "p." + SessionsTable.SESSION_START + ">=?" + AND + "q2.activity_index>=?" + @@ -327,15 +342,15 @@ public class NetworkActivityIndexQueries { return database -> { // INNER JOIN limits the users to only those that are regular String selectPlaytimePerPlayer = SELECT + - "p." + SessionsTable.USER_UUID + "," + + "p." + SessionsTable.USER_ID + "," + "SUM(p." + SessionsTable.AFK_TIME + ") as afk" + FROM + SessionsTable.TABLE_NAME + " p" + - INNER_JOIN + '(' + selectActivityIndexSQL() + ") q2 on q2." + SessionsTable.USER_UUID + "=p." + SessionsTable.USER_UUID + + INNER_JOIN + '(' + selectActivityIndexSQL() + ") q2 on q2." + SessionsTable.USER_ID + "=p." + SessionsTable.USER_ID + WHERE + "p." + SessionsTable.SESSION_END + "<=?" + AND + "p." + SessionsTable.SESSION_START + ">=?" + AND + "q2.activity_index>=?" + AND + "q2.activity_index(selectAverage, 100) { @@ -357,16 +372,16 @@ public class NetworkActivityIndexQueries { } public static Query> activityIndexForNewPlayers(long after, long before, Long threshold) { - String selectNewUUIDs = SELECT + UsersTable.USER_UUID + + String selectNewUUIDs = SELECT + UsersTable.ID + FROM + UsersTable.TABLE_NAME + WHERE + UsersTable.REGISTERED + "<=?" + AND + UsersTable.REGISTERED + ">=?"; String sql = SELECT + "activity_index" + FROM + '(' + selectNewUUIDs + ") n" + - INNER_JOIN + '(' + selectActivityIndexSQL() + ") a on n." + SessionsTable.USER_UUID + "=a." + SessionsTable.USER_UUID; + INNER_JOIN + '(' + selectActivityIndexSQL() + ") a on n." + UsersTable.ID + "=a." + SessionsTable.USER_ID; - return new QueryStatement>(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setLong(1, before); @@ -386,22 +401,22 @@ public class NetworkActivityIndexQueries { } public static Query averageActivityIndexForRetainedPlayers(long after, long before, Long threshold) { - String selectNewUUIDs = SELECT + UsersTable.USER_UUID + + String selectNewUUIDs = SELECT + UsersTable.ID + FROM + UsersTable.TABLE_NAME + WHERE + UsersTable.REGISTERED + "<=?" + AND + UsersTable.REGISTERED + ">=?"; - String selectUniqueUUIDs = SELECT + "DISTINCT " + SessionsTable.USER_UUID + + String selectUniqueUUIDs = SELECT + "DISTINCT " + SessionsTable.USER_ID + FROM + SessionsTable.TABLE_NAME + WHERE + SessionsTable.SESSION_START + ">=?" + AND + SessionsTable.SESSION_END + "<=?"; String sql = SELECT + "AVG(activity_index) as average" + FROM + '(' + selectNewUUIDs + ") n" + - INNER_JOIN + '(' + selectUniqueUUIDs + ") u on n." + SessionsTable.USER_UUID + "=u." + SessionsTable.USER_UUID + - INNER_JOIN + '(' + selectActivityIndexSQL() + ") a on n." + SessionsTable.USER_UUID + "=a." + SessionsTable.USER_UUID; + INNER_JOIN + '(' + selectUniqueUUIDs + ") u on n." + UsersTable.ID + "=u." + SessionsTable.USER_ID + + INNER_JOIN + '(' + selectActivityIndexSQL() + ") a on n." + UsersTable.ID + "=a." + SessionsTable.USER_ID; - return new QueryStatement(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setLong(1, before); @@ -421,57 +436,20 @@ public class NetworkActivityIndexQueries { }; } - public static Query averageActivityIndexForNonRetainedPlayers(long after, long before, Long threshold) { - String selectNewUUIDs = SELECT + UsersTable.USER_UUID + - FROM + UsersTable.TABLE_NAME + - WHERE + UsersTable.REGISTERED + "<=?" + - AND + UsersTable.REGISTERED + ">=?"; - - String selectUniqueUUIDs = SELECT + "DISTINCT " + SessionsTable.USER_UUID + - FROM + SessionsTable.TABLE_NAME + - WHERE + SessionsTable.SESSION_START + ">=?" + - AND + SessionsTable.SESSION_END + "<=?"; - - String sql = SELECT + "AVG(activity_index) as average" + - FROM + '(' + selectNewUUIDs + ") n" + - LEFT_JOIN + '(' + selectUniqueUUIDs + ") u on n." + SessionsTable.USER_UUID + "=u." + SessionsTable.USER_UUID + - INNER_JOIN + '(' + selectActivityIndexSQL() + ") a on n." + SessionsTable.USER_UUID + "=a." + SessionsTable.USER_UUID + - WHERE + "n." + SessionsTable.USER_UUID + IS_NULL; - - return new QueryStatement(sql) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setLong(1, before); - statement.setLong(2, after); - - // Have played in the last half of the time frame - long half = before - (before - after) / 2; - statement.setLong(3, half); - statement.setLong(4, before); - setSelectActivityIndexSQLParameters(statement, 5, threshold, before); - } - - @Override - public ActivityIndex processResults(ResultSet set) throws SQLException { - return set.next() ? new ActivityIndex(set.getDouble("average"), before) : new ActivityIndex(0.0, before); - } - }; - } - - public static Query> activityIndexForAllPlayers(long date, long playtimeThreshold) { + public static Query> activityIndexForAllPlayers(long date, long playtimeThreshold) { String selectActivityIndex = selectActivityIndexSQL(); - return new QueryStatement>(selectActivityIndex, 1000) { + return new QueryStatement<>(selectActivityIndex, 1000) { @Override public void prepare(PreparedStatement statement) throws SQLException { setSelectActivityIndexSQLParameters(statement, 1, playtimeThreshold, date); } @Override - public Map processResults(ResultSet set) throws SQLException { - Map indexes = new HashMap<>(); + public Map processResults(ResultSet set) throws SQLException { + Map indexes = new HashMap<>(); while (set.next()) { indexes.put( - UUID.fromString(set.getString(SessionsTable.USER_UUID)), + set.getInt(UsersTable.ID), new ActivityIndex(set.getDouble("activity_index"), date) ); } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/analysis/PlayerCountQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/analysis/PlayerCountQueries.java index e186f1bfa..61236a29d 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/analysis/PlayerCountQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/analysis/PlayerCountQueries.java @@ -20,6 +20,7 @@ import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.queries.Query; import com.djrapitops.plan.storage.database.queries.QueryStatement; import com.djrapitops.plan.storage.database.sql.building.Sql; +import com.djrapitops.plan.storage.database.sql.tables.ServerTable; import com.djrapitops.plan.storage.database.sql.tables.SessionsTable; import com.djrapitops.plan.storage.database.sql.tables.UserInfoTable; import com.djrapitops.plan.storage.database.sql.tables.UsersTable; @@ -27,7 +28,10 @@ import com.djrapitops.plan.storage.database.sql.tables.UsersTable; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.*; +import java.util.Map; +import java.util.NavigableMap; +import java.util.TreeMap; +import java.util.UUID; import static com.djrapitops.plan.storage.database.sql.building.Sql.*; @@ -38,49 +42,21 @@ import static com.djrapitops.plan.storage.database.sql.building.Sql.*; */ public class PlayerCountQueries { + private static final String PLAYER_COUNT = "player_count"; + private PlayerCountQueries() { // Static method class } - private static QueryStatement queryPlayerCount(String sql, long after, long before, ServerUUID serverUUID) { - return new QueryStatement(sql) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setLong(1, before); - statement.setLong(2, after); - statement.setString(3, serverUUID.toString()); - } - - @Override - public Integer processResults(ResultSet set) throws SQLException { - return set.next() ? set.getInt("player_count") : 0; - } - }; - } - - private static QueryStatement queryPlayerCount(String sql, long after, long before) { - return new QueryStatement(sql) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setLong(1, before); - statement.setLong(2, after); - } - - @Override - public Integer processResults(ResultSet set) throws SQLException { - return set.next() ? set.getInt("player_count") : 0; - } - }; - } - public static Query uniquePlayerCount(long after, long before, ServerUUID serverUUID) { - String sql = SELECT + "COUNT(DISTINCT " + SessionsTable.USER_UUID + ") as player_count" + - FROM + SessionsTable.TABLE_NAME + - WHERE + SessionsTable.SESSION_END + "<=?" + - AND + SessionsTable.SESSION_START + ">=?" + - AND + SessionsTable.SERVER_UUID + "=?"; - - return queryPlayerCount(sql, after, before, serverUUID); + return database -> database.queryOptional(SELECT + "COUNT(DISTINCT " + SessionsTable.USER_ID + ") as " + PLAYER_COUNT + + FROM + SessionsTable.TABLE_NAME + + WHERE + SessionsTable.SESSION_END + "<=?" + + AND + SessionsTable.SESSION_START + ">=?" + + AND + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID, + set -> set.getInt(PLAYER_COUNT), + before, after, serverUUID) + .orElse(0); } /** @@ -91,37 +67,29 @@ public class PlayerCountQueries { * @return Unique player count (players who played within time frame) */ public static Query uniquePlayerCount(long after, long before) { - String sql = SELECT + "COUNT(DISTINCT " + SessionsTable.USER_UUID + ") as player_count" + - FROM + SessionsTable.TABLE_NAME + - WHERE + SessionsTable.SESSION_END + "<=?" + - AND + SessionsTable.SESSION_START + ">=?"; - - return queryPlayerCount(sql, after, before); + return database -> database.queryOptional(SELECT + "COUNT(DISTINCT " + SessionsTable.USER_ID + ") as " + PLAYER_COUNT + + FROM + SessionsTable.TABLE_NAME + + WHERE + SessionsTable.SESSION_END + "<=?" + + AND + SessionsTable.SESSION_START + ">=?", + set -> set.getInt(PLAYER_COUNT), + before, after) + .orElse(0); } public static Query> uniquePlayerCounts(long after, long before) { - String sql = SELECT + SessionsTable.SERVER_UUID + ",COUNT(DISTINCT " + SessionsTable.USER_UUID + ") as player_count" + + String sql = SELECT + ServerTable.SERVER_UUID + ",COUNT(DISTINCT " + SessionsTable.USER_ID + ") as " + PLAYER_COUNT + FROM + SessionsTable.TABLE_NAME + + INNER_JOIN + ServerTable.TABLE_NAME + " se on se." + ServerTable.ID + "=" + SessionsTable.TABLE_NAME + '.' + SessionsTable.SERVER_ID + WHERE + SessionsTable.SESSION_END + "<=?" + AND + SessionsTable.SESSION_START + ">=?" + - GROUP_BY + UserInfoTable.SERVER_UUID; + GROUP_BY + SessionsTable.SERVER_ID; - return new QueryStatement>(sql) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setLong(1, before); - statement.setLong(2, after); - } - - @Override - public Map processResults(ResultSet set) throws SQLException { - Map byServer = new HashMap<>(); - while (set.next()) { - byServer.put(ServerUUID.fromString(set.getString(UserInfoTable.SERVER_UUID)), set.getInt("player_count")); - } - return byServer; - } - }; + return database -> database.queryMap(sql, + (set, byServer) -> byServer.put( + ServerUUID.fromString(set.getString(ServerTable.SERVER_UUID)), + set.getInt(PLAYER_COUNT) + ), + before, after); } /** @@ -139,31 +107,17 @@ public class PlayerCountQueries { String selectUniquePlayersPerDay = SELECT + sql.dateToEpochSecond(sql.dateToDayStamp(sql.epochSecondToDate('(' + SessionsTable.SESSION_START + "+?)/1000"))) + "*1000 as date," + - "COUNT(DISTINCT " + SessionsTable.USER_UUID + ") as player_count" + + "COUNT(DISTINCT " + SessionsTable.USER_ID + ") as " + PLAYER_COUNT + FROM + SessionsTable.TABLE_NAME + WHERE + SessionsTable.SESSION_END + "<=?" + AND + SessionsTable.SESSION_START + ">=?" + - AND + SessionsTable.SERVER_UUID + "=?" + + AND + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + GROUP_BY + "date"; - return database.query(new QueryStatement>(selectUniquePlayersPerDay, 100) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setLong(1, timeZoneOffset); - statement.setLong(2, before); - statement.setLong(3, after); - statement.setString(4, serverUUID.toString()); - } - - @Override - public NavigableMap processResults(ResultSet set) throws SQLException { - NavigableMap uniquePerDay = new TreeMap<>(); - while (set.next()) { - uniquePerDay.put(set.getLong("date"), set.getInt("player_count")); - } - return uniquePerDay; - } - }); + return database.queryMap(selectUniquePlayersPerDay, + (set, perDay) -> perDay.put(set.getLong("date"), set.getInt(PLAYER_COUNT)), + TreeMap::new, + timeZoneOffset, before, after, serverUUID); }; } @@ -182,31 +136,17 @@ public class PlayerCountQueries { String selectUniquePlayersPerDay = SELECT + sql.dateToEpochSecond(sql.dateToHourStamp(sql.epochSecondToDate('(' + SessionsTable.SESSION_START + "+?)/1000"))) + "*1000 as date," + - "COUNT(DISTINCT " + SessionsTable.USER_UUID + ") as player_count" + + "COUNT(DISTINCT " + SessionsTable.USER_ID + ") as " + PLAYER_COUNT + FROM + SessionsTable.TABLE_NAME + WHERE + SessionsTable.SESSION_END + "<=?" + AND + SessionsTable.SESSION_START + ">=?" + - AND + SessionsTable.SERVER_UUID + "=?" + + AND + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + GROUP_BY + "date"; - return database.query(new QueryStatement>(selectUniquePlayersPerDay, 100) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setLong(1, timeZoneOffset); - statement.setLong(2, before); - statement.setLong(3, after); - statement.setString(4, serverUUID.toString()); - } - - @Override - public NavigableMap processResults(ResultSet set) throws SQLException { - NavigableMap uniquePerDay = new TreeMap<>(); - while (set.next()) { - uniquePerDay.put(set.getLong("date"), set.getInt("player_count")); - } - return uniquePerDay; - } - }); + return database.queryMap(selectUniquePlayersPerDay, + (set, perDay) -> perDay.put(set.getLong("date"), set.getInt(PLAYER_COUNT)), + TreeMap::new, + timeZoneOffset, before, after, serverUUID); }; } @@ -224,29 +164,16 @@ public class PlayerCountQueries { String selectUniquePlayersPerDay = SELECT + sql.dateToEpochSecond(sql.dateToDayStamp(sql.epochSecondToDate('(' + SessionsTable.SESSION_START + "+?)/1000"))) + "*1000 as date," + - "COUNT(DISTINCT " + SessionsTable.USER_UUID + ") as player_count" + + "COUNT(DISTINCT " + SessionsTable.USER_ID + ") as " + PLAYER_COUNT + FROM + SessionsTable.TABLE_NAME + WHERE + SessionsTable.SESSION_END + "<=?" + AND + SessionsTable.SESSION_START + ">=?" + GROUP_BY + "date"; - return database.query(new QueryStatement>(selectUniquePlayersPerDay, 100) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setLong(1, timeZoneOffset); - statement.setLong(2, before); - statement.setLong(3, after); - } - - @Override - public NavigableMap processResults(ResultSet set) throws SQLException { - NavigableMap uniquePerDay = new TreeMap<>(); - while (set.next()) { - uniquePerDay.put(set.getLong("date"), set.getInt("player_count")); - } - return uniquePerDay; - } - }); + return database.queryMap(selectUniquePlayersPerDay, + (set, perDay) -> perDay.put(set.getLong("date"), set.getInt(PLAYER_COUNT)), + TreeMap::new, + timeZoneOffset, before, after); }; } @@ -264,29 +191,16 @@ public class PlayerCountQueries { String selectUniquePlayersPerDay = SELECT + sql.dateToEpochSecond(sql.dateToHourStamp(sql.epochSecondToDate('(' + SessionsTable.SESSION_START + "+?)/1000"))) + "*1000 as date," + - "COUNT(DISTINCT " + SessionsTable.USER_UUID + ") as player_count" + + "COUNT(DISTINCT " + SessionsTable.USER_ID + ") as " + PLAYER_COUNT + FROM + SessionsTable.TABLE_NAME + WHERE + SessionsTable.SESSION_END + "<=?" + AND + SessionsTable.SESSION_START + ">=?" + GROUP_BY + "date"; - return database.query(new QueryStatement>(selectUniquePlayersPerDay, 100) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setLong(1, timeZoneOffset); - statement.setLong(2, before); - statement.setLong(3, after); - } - - @Override - public NavigableMap processResults(ResultSet set) throws SQLException { - NavigableMap uniquePerDay = new TreeMap<>(); - while (set.next()) { - uniquePerDay.put(set.getLong("date"), set.getInt("player_count")); - } - return uniquePerDay; - } - }); + return database.queryMap(selectUniquePlayersPerDay, + (set, perDay) -> perDay.put(set.getLong("date"), set.getInt(PLAYER_COUNT)), + TreeMap::new, + timeZoneOffset, before, after); }; } @@ -296,73 +210,60 @@ public class PlayerCountQueries { String selectUniquePlayersPerDay = SELECT + sql.dateToEpochSecond(sql.dateToDayStamp(sql.epochSecondToDate('(' + SessionsTable.SESSION_START + "+?)/1000"))) + "*1000 as date," + - "COUNT(DISTINCT " + SessionsTable.USER_UUID + ") as player_count" + + "COUNT(DISTINCT " + SessionsTable.USER_ID + ") as " + PLAYER_COUNT + FROM + SessionsTable.TABLE_NAME + WHERE + SessionsTable.SESSION_END + "<=?" + AND + SessionsTable.SESSION_START + ">=?" + - AND + SessionsTable.SERVER_UUID + "=?" + + AND + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + GROUP_BY + "date"; - String selectAverage = SELECT + "AVG(player_count) as average" + FROM + '(' + selectUniquePlayersPerDay + ") q1"; + String selectAverage = SELECT + "AVG(" + PLAYER_COUNT + ") as average" + FROM + '(' + selectUniquePlayersPerDay + ") q1"; - return database.query(new QueryStatement(selectAverage, 100) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setLong(1, timeZoneOffset); - statement.setLong(2, before); - statement.setLong(3, after); - statement.setString(4, serverUUID.toString()); - } - - @Override - public Integer processResults(ResultSet set) throws SQLException { - return set.next() ? (int) set.getDouble("average") : 0; - } - }); + return database.queryOptional(selectAverage, + set -> (int) set.getDouble("average"), + timeZoneOffset, before, after, serverUUID) + .orElse(0); }; } public static Query newPlayerCount(long after, long before, ServerUUID serverUUID) { - String sql = SELECT + "COUNT(1) as player_count" + + String sql = SELECT + "COUNT(1) as " + PLAYER_COUNT + FROM + UserInfoTable.TABLE_NAME + WHERE + UserInfoTable.REGISTERED + "<=?" + AND + UserInfoTable.REGISTERED + ">=?" + - AND + UserInfoTable.SERVER_UUID + "=?"; + AND + UserInfoTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID; - return queryPlayerCount(sql, after, before, serverUUID); + return database -> database.queryOptional(sql, + set -> set.getInt(PLAYER_COUNT), + before, after, serverUUID) + .orElse(0); } public static Query newPlayerCount(long after, long before) { - String sql = SELECT + "COUNT(1) as player_count" + + String sql = SELECT + "COUNT(1) as " + PLAYER_COUNT + FROM + UsersTable.TABLE_NAME + WHERE + UsersTable.REGISTERED + "<=?" + AND + UsersTable.REGISTERED + ">=?"; - return queryPlayerCount(sql, after, before); + return database -> database.queryOptional(sql, + set -> set.getInt(PLAYER_COUNT), + before, after) + .orElse(0); } public static Query> newPlayerCounts(long after, long before) { - String sql = SELECT + UserInfoTable.SERVER_UUID + ",COUNT(1) as player_count" + + String sql = SELECT + "s." + ServerTable.SERVER_UUID + ",COUNT(1) as " + PLAYER_COUNT + FROM + UserInfoTable.TABLE_NAME + + INNER_JOIN + ServerTable.TABLE_NAME + " s on s." + ServerTable.ID + '=' + UserInfoTable.TABLE_NAME + '.' + UserInfoTable.SERVER_ID + WHERE + UserInfoTable.REGISTERED + "<=?" + AND + UserInfoTable.REGISTERED + ">=?" + - GROUP_BY + UserInfoTable.SERVER_UUID; + GROUP_BY + UserInfoTable.SERVER_ID; - return new QueryStatement>(sql) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setLong(1, before); - statement.setLong(2, after); - } - - @Override - public Map processResults(ResultSet set) throws SQLException { - Map byServer = new HashMap<>(); - while (set.next()) { - byServer.put(ServerUUID.fromString(set.getString(UserInfoTable.SERVER_UUID)), set.getInt("player_count")); - } - return byServer; - } - }; + return database -> database.queryMap(sql, + (set, byServer) -> byServer.put( + ServerUUID.fromString(set.getString(ServerTable.SERVER_UUID)), + set.getInt(PLAYER_COUNT) + ), + before, after); } /** @@ -380,31 +281,17 @@ public class PlayerCountQueries { String selectNewPlayersQuery = SELECT + sql.dateToEpochSecond(sql.dateToDayStamp(sql.epochSecondToDate('(' + UserInfoTable.REGISTERED + "+?)/1000"))) + "*1000 as date," + - "COUNT(1) as player_count" + + "COUNT(1) as " + PLAYER_COUNT + FROM + UserInfoTable.TABLE_NAME + WHERE + UserInfoTable.REGISTERED + "<=?" + AND + UserInfoTable.REGISTERED + ">=?" + - AND + UserInfoTable.SERVER_UUID + "=?" + + AND + UserInfoTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + GROUP_BY + "date"; - return database.query(new QueryStatement>(selectNewPlayersQuery, 100) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setLong(1, timeZoneOffset); - statement.setLong(2, before); - statement.setLong(3, after); - statement.setString(4, serverUUID.toString()); - } - - @Override - public NavigableMap processResults(ResultSet set) throws SQLException { - NavigableMap newPerDay = new TreeMap<>(); - while (set.next()) { - newPerDay.put(set.getLong("date"), set.getInt("player_count")); - } - return newPerDay; - } - }); + return database.queryMap(selectNewPlayersQuery, + (set, perDay) -> perDay.put(set.getLong("date"), set.getInt(PLAYER_COUNT)), + TreeMap::new, + timeZoneOffset, before, after, serverUUID); }; } @@ -423,31 +310,17 @@ public class PlayerCountQueries { String selectNewPlayersQuery = SELECT + sql.dateToEpochSecond(sql.dateToHourStamp(sql.epochSecondToDate('(' + UserInfoTable.REGISTERED + "+?)/1000"))) + "*1000 as date," + - "COUNT(1) as player_count" + + "COUNT(1) as " + PLAYER_COUNT + FROM + UserInfoTable.TABLE_NAME + WHERE + UserInfoTable.REGISTERED + "<=?" + AND + UserInfoTable.REGISTERED + ">=?" + - AND + UserInfoTable.SERVER_UUID + "=?" + + AND + UserInfoTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + GROUP_BY + "date"; - return database.query(new QueryStatement>(selectNewPlayersQuery, 100) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setLong(1, timeZoneOffset); - statement.setLong(2, before); - statement.setLong(3, after); - statement.setString(4, serverUUID.toString()); - } - - @Override - public NavigableMap processResults(ResultSet set) throws SQLException { - NavigableMap newPerDay = new TreeMap<>(); - while (set.next()) { - newPerDay.put(set.getLong("date"), set.getInt("player_count")); - } - return newPerDay; - } - }); + return database.queryMap(selectNewPlayersQuery, + (set, perDay) -> perDay.put(set.getLong("date"), set.getInt(PLAYER_COUNT)), + TreeMap::new, + timeZoneOffset, before, after, serverUUID); }; } @@ -465,29 +338,16 @@ public class PlayerCountQueries { String selectNewPlayersQuery = SELECT + sql.dateToEpochSecond(sql.dateToDayStamp(sql.epochSecondToDate('(' + UserInfoTable.REGISTERED + "+?)/1000"))) + "*1000 as date," + - "COUNT(1) as player_count" + + "COUNT(1) as " + PLAYER_COUNT + FROM + UsersTable.TABLE_NAME + WHERE + UsersTable.REGISTERED + "<=?" + AND + UsersTable.REGISTERED + ">=?" + GROUP_BY + "date"; - return database.query(new QueryStatement>(selectNewPlayersQuery, 100) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setLong(1, timeZoneOffset); - statement.setLong(2, before); - statement.setLong(3, after); - } - - @Override - public NavigableMap processResults(ResultSet set) throws SQLException { - NavigableMap newPerDay = new TreeMap<>(); - while (set.next()) { - newPerDay.put(set.getLong("date"), set.getInt("player_count")); - } - return newPerDay; - } - }); + return database.queryMap(selectNewPlayersQuery, + (set, perDay) -> perDay.put(set.getLong("date"), set.getInt(PLAYER_COUNT)), + TreeMap::new, + timeZoneOffset, before, after); }; } @@ -505,29 +365,16 @@ public class PlayerCountQueries { String selectNewPlayersQuery = SELECT + sql.dateToEpochSecond(sql.dateToHourStamp(sql.epochSecondToDate('(' + UserInfoTable.REGISTERED + "+?)/1000"))) + "*1000 as date," + - "COUNT(1) as player_count" + + "COUNT(1) as " + PLAYER_COUNT + FROM + UsersTable.TABLE_NAME + WHERE + UsersTable.REGISTERED + "<=?" + AND + UsersTable.REGISTERED + ">=?" + GROUP_BY + "date"; - return database.query(new QueryStatement>(selectNewPlayersQuery, 100) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setLong(1, timeZoneOffset); - statement.setLong(2, before); - statement.setLong(3, after); - } - - @Override - public NavigableMap processResults(ResultSet set) throws SQLException { - NavigableMap newPerDay = new TreeMap<>(); - while (set.next()) { - newPerDay.put(set.getLong("date"), set.getInt("player_count")); - } - return newPerDay; - } - }); + return database.queryMap(selectNewPlayersQuery, + (set, perDay) -> perDay.put(set.getLong("date"), set.getInt(PLAYER_COUNT)), + TreeMap::new, + timeZoneOffset, before, after); }; } @@ -537,84 +384,62 @@ public class PlayerCountQueries { String selectNewPlayersQuery = SELECT + sql.dateToEpochSecond(sql.dateToDayStamp(sql.epochSecondToDate('(' + UserInfoTable.REGISTERED + "+?)/1000"))) + "*1000 as date," + - "COUNT(1) as player_count" + + "COUNT(1) as " + PLAYER_COUNT + FROM + UserInfoTable.TABLE_NAME + WHERE + UserInfoTable.REGISTERED + "<=?" + AND + UserInfoTable.REGISTERED + ">=?" + - AND + UserInfoTable.SERVER_UUID + "=?" + + AND + UserInfoTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + GROUP_BY + "date"; - String selectAverage = SELECT + "AVG(player_count) as average" + FROM + '(' + selectNewPlayersQuery + ") q1"; + String selectAverage = SELECT + "AVG(" + PLAYER_COUNT + ") as average" + FROM + '(' + selectNewPlayersQuery + ") q1"; - return database.query(new QueryStatement(selectAverage, 100) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setLong(1, timeZoneOffset); - statement.setLong(2, before); - statement.setLong(3, after); - statement.setString(4, serverUUID.toString()); - } - - @Override - public Integer processResults(ResultSet set) throws SQLException { - return set.next() ? (int) set.getDouble("average") : 0; - } - }); + return database.queryOptional(selectAverage, + set -> (int) set.getDouble("average"), + timeZoneOffset, before, after, serverUUID) + .orElse(0); }; } public static Query retainedPlayerCount(long after, long before, ServerUUID serverUUID) { - String selectNewUUIDs = SELECT + UserInfoTable.USER_UUID + - FROM + UserInfoTable.TABLE_NAME + + String selectUniqueUUIDs = SELECT + DISTINCT + "s." + SessionsTable.USER_ID + + FROM + SessionsTable.TABLE_NAME + " s" + + INNER_JOIN + UserInfoTable.TABLE_NAME + " ux" + + " on ux." + UserInfoTable.USER_ID + "=s." + SessionsTable.USER_ID + + AND + "ux." + UserInfoTable.SERVER_ID + "=s." + SessionsTable.SERVER_ID + WHERE + UserInfoTable.REGISTERED + ">=?" + AND + UserInfoTable.REGISTERED + "<=?" + - AND + UserInfoTable.SERVER_UUID + "=?"; - - String selectUniqueUUIDs = SELECT + DISTINCT + SessionsTable.USER_UUID + - FROM + SessionsTable.TABLE_NAME + - WHERE + SessionsTable.SESSION_START + ">=?" + + AND + SessionsTable.SESSION_START + ">=?" + AND + SessionsTable.SESSION_END + "<=?" + - AND + SessionsTable.SERVER_UUID + "=?"; + AND + "s." + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID; - String sql = SELECT + "COUNT(1) as player_count" + - FROM + '(' + selectNewUUIDs + ") q1" + - INNER_JOIN + '(' + selectUniqueUUIDs + ") q2 on q1." + UserInfoTable.USER_UUID + "=q2." + SessionsTable.USER_UUID; - - return new QueryStatement(sql) { + return new QueryStatement<>(selectUniqueUUIDs) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setLong(1, after); statement.setLong(2, before); - statement.setString(3, serverUUID.toString()); // Have played in the last half of the time frame long half = before - (before - after) / 2; - statement.setLong(4, half); - statement.setLong(5, before); - statement.setString(6, serverUUID.toString()); + statement.setLong(3, half); + statement.setLong(4, before); + statement.setString(5, serverUUID.toString()); } @Override public Integer processResults(ResultSet set) throws SQLException { - return set.next() ? set.getInt("player_count") : 0; + int count = 0; + while (set.next()) { + count++; + } + return count; } }; } public static Query operators(ServerUUID serverUUID) { - String sql = SELECT + "COUNT(1) as player_count" + FROM + UserInfoTable.TABLE_NAME + - WHERE + UserInfoTable.SERVER_UUID + "=?" + + String sql = SELECT + "COUNT(1) as " + PLAYER_COUNT + FROM + UserInfoTable.TABLE_NAME + + WHERE + UserInfoTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + AND + UserInfoTable.OP + "=?"; - return new QueryStatement(sql) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setString(1, serverUUID.toString()); - statement.setBoolean(2, true); - } - - @Override - public Integer processResults(ResultSet set) throws SQLException { - return set.next() ? set.getInt("player_count") : 0; - } - }; + return db -> db.queryOptional(sql, set -> set.getInt(PLAYER_COUNT), serverUUID, true) + .orElse(0); } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/analysis/TopListQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/analysis/TopListQueries.java new file mode 100644 index 000000000..c2841a3a6 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/analysis/TopListQueries.java @@ -0,0 +1,86 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.storage.database.queries.analysis; + +import com.djrapitops.plan.identification.ServerUUID; +import com.djrapitops.plan.storage.database.queries.Query; +import com.djrapitops.plan.storage.database.sql.tables.ServerTable; +import com.djrapitops.plan.storage.database.sql.tables.SessionsTable; +import com.djrapitops.plan.storage.database.sql.tables.UsersTable; + +import java.util.Optional; + +import static com.djrapitops.plan.storage.database.sql.building.Sql.*; + +public class TopListQueries { + + private TopListQueries() { + // Static query generation class + } + + public static Query>> fetchNthTop10PlaytimePlayerOn(ServerUUID serverUUID, int n, long after, long before) { + String sql = SELECT + UsersTable.USER_NAME + ", " + + "SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + ") as playtime" + + FROM + SessionsTable.TABLE_NAME + " s" + + INNER_JOIN + UsersTable.TABLE_NAME + " u on u." + UsersTable.ID + "=s." + SessionsTable.USER_ID + + WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + + AND + SessionsTable.SESSION_START + ">?" + + AND + SessionsTable.SESSION_END + " db.queryOptional(sql, set -> new TopListEntry<>(set.getString(UsersTable.USER_NAME), set.getLong("playtime")), + serverUUID, after, before, n - 1); + } + + public static Query>> fetchNthTop10ActivePlaytimePlayerOn(ServerUUID serverUUID, int n, long after, long before) { + String sql = SELECT + UsersTable.USER_NAME + ", " + + "SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + '-' + SessionsTable.AFK_TIME + ") as active_playtime" + + FROM + SessionsTable.TABLE_NAME + " s" + + INNER_JOIN + UsersTable.TABLE_NAME + " u on u." + UsersTable.ID + "=s." + SessionsTable.USER_ID + + WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + + AND + SessionsTable.SESSION_START + ">?" + + AND + SessionsTable.SESSION_END + " db.queryOptional(sql, set -> new TopListEntry<>(set.getString(UsersTable.USER_NAME), set.getLong("active_playtime")), + serverUUID, after, before, n - 1); + } + + public static class TopListEntry { + private final String playerName; + private final T value; + + public TopListEntry(String playerName, T value) { + this.playerName = playerName; + this.value = value; + } + + public String getPlayerName() { + return playerName; + } + + public T getValue() { + return value; + } + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/containers/PerServerContainerQuery.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/containers/PerServerContainerQuery.java index 5509c0ac4..5a9a8bccd 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/containers/PerServerContainerQuery.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/containers/PerServerContainerQuery.java @@ -23,6 +23,7 @@ import com.djrapitops.plan.delivery.domain.keys.Key; import com.djrapitops.plan.delivery.domain.keys.PerServerKeys; import com.djrapitops.plan.gathering.domain.FinishedSession; import com.djrapitops.plan.gathering.domain.UserInfo; +import com.djrapitops.plan.gathering.domain.event.JoinAddress; import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.SQLDB; import com.djrapitops.plan.storage.database.queries.PerServerAggregateQueries; @@ -64,6 +65,12 @@ public class PerServerContainerQuery implements Query { for (Map.Entry> entry : sessions.entrySet()) { ServerUUID serverUUID = entry.getKey(); List serverSessions = entry.getValue(); + if (!serverSessions.isEmpty()) { + serverSessions.get(0).getExtraData().get(JoinAddress.class).map(JoinAddress::getAddress) + .ifPresent(address -> + perServerContainer.putToContainerOfServer(serverUUID, PerServerKeys.JOIN_ADDRESS, address) + ); + } DataContainer serverContainer = perServerContainer.getOrDefault(serverUUID, new SupplierDataContainer()); serverContainer.putRawData(PerServerKeys.SESSIONS, serverSessions); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/containers/PlayerContainerQuery.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/containers/PlayerContainerQuery.java index 6508e56fe..48e3b0fa6 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/containers/PlayerContainerQuery.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/containers/PlayerContainerQuery.java @@ -22,6 +22,7 @@ import com.djrapitops.plan.delivery.domain.keys.Key; import com.djrapitops.plan.delivery.domain.keys.PlayerKeys; import com.djrapitops.plan.delivery.domain.mutators.PerServerMutator; import com.djrapitops.plan.delivery.domain.mutators.SessionsMutator; +import com.djrapitops.plan.gathering.cache.SessionCache; import com.djrapitops.plan.gathering.domain.ActiveSession; import com.djrapitops.plan.gathering.domain.BaseUser; import com.djrapitops.plan.gathering.domain.FinishedSession; @@ -91,6 +92,7 @@ public class PlayerContainerQuery implements Query { container.putSupplier(PlayerKeys.MOB_KILL_COUNT, () -> SessionsMutator.forContainer(container).toMobKillCount()); container.putSupplier(PlayerKeys.DEATH_COUNT, () -> SessionsMutator.forContainer(container).toDeathCount()); + SessionCache.getCachedSession(uuid).ifPresent(session -> container.putRawData(PlayerKeys.ACTIVE_SESSION, session)); return container; } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/containers/ServerPlayerContainersQuery.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/containers/ServerPlayerContainersQuery.java deleted file mode 100644 index e42e1b09e..000000000 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/containers/ServerPlayerContainersQuery.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * This file is part of Player Analytics (Plan). - * - * Plan is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License v3 as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Plan is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Plan. If not, see . - */ -package com.djrapitops.plan.storage.database.queries.containers; - -import com.djrapitops.plan.delivery.domain.Nickname; -import com.djrapitops.plan.delivery.domain.container.PerServerContainer; -import com.djrapitops.plan.delivery.domain.container.PlayerContainer; -import com.djrapitops.plan.delivery.domain.keys.PlayerKeys; -import com.djrapitops.plan.delivery.domain.mutators.PerServerMutator; -import com.djrapitops.plan.delivery.domain.mutators.SessionsMutator; -import com.djrapitops.plan.gathering.domain.*; -import com.djrapitops.plan.identification.ServerUUID; -import com.djrapitops.plan.storage.database.SQLDB; -import com.djrapitops.plan.storage.database.queries.Query; -import com.djrapitops.plan.storage.database.queries.objects.*; - -import java.util.*; - -/** - * Used to get PlayerContainers of all players on a server, some limitations apply to DataContainer keys. - *

- * Limitations: - * - PlayerContainers do not support: PlayerKeys WORLD_TIMES, PLAYER_KILLS, PLAYER_KILL_COUNT - * - PlayerContainers PlayerKeys.PER_SERVER does not support: PerServerKeys WORLD_TIMES, PLAYER_KILLS, PLAYER_KILL_COUNT - *

- * Blocking methods are not called until DataContainer getter methods are called. - * - * @author AuroraLS3 - */ -public class ServerPlayerContainersQuery implements Query> { - - private final ServerUUID serverUUID; - - public ServerPlayerContainersQuery(ServerUUID serverUUID) { - this.serverUUID = serverUUID; - } - - @Override - public List executeQuery(SQLDB db) { - List containers = new ArrayList<>(); - - Collection baseUsers = db.query(BaseUserQueries.fetchServerBaseUsers(serverUUID)); - - Map> geoInformation = db.query(GeoInfoQueries.fetchServerGeoInformation(serverUUID)); - Map> nicknames = db.query(NicknameQueries.fetchNicknameDataOfServer(serverUUID)); - Map> pingData = db.query(PingQueries.fetchPingDataOfServer(serverUUID)); - Map> sessions = db.query(SessionQueries.fetchSessionsOfServer(serverUUID)); - - Map userInformation = db.query(UserInfoQueries.fetchUserInformationOfServer(serverUUID)); - - Map perServerInfo = getPerServerData( - userInformation, - sessions, - pingData - ); - - for (BaseUser user : baseUsers) { - PlayerContainer container = new PlayerContainer(); - - // BaseUser - UUID uuid = user.getUuid(); - container.putRawData(PlayerKeys.UUID, uuid); - container.putRawData(PlayerKeys.NAME, user.getName()); - container.putRawData(PlayerKeys.REGISTERED, user.getRegistered()); - container.putRawData(PlayerKeys.KICK_COUNT, user.getTimesKicked()); - - // GeoInfo - container.putRawData(PlayerKeys.GEO_INFO, geoInformation.getOrDefault(uuid, Collections.emptyList())); - - // Ping - container.putRawData(PlayerKeys.PING, pingData.get(uuid)); - - // Nickname, only used for the raw server JSON. - container.putRawData(PlayerKeys.NICKNAMES, nicknames.get(uuid)); - - // PerServerContainer - container.putRawData(PlayerKeys.PER_SERVER, perServerInfo.get(uuid)); - - container.putCachingSupplier(PlayerKeys.SESSIONS, () -> { - List playerSessions = sessions.getOrDefault(uuid, new ArrayList<>()); - container.getValue(PlayerKeys.ACTIVE_SESSION) - .map(ActiveSession::toFinishedSessionFromStillActive) - .ifPresent(playerSessions::add); - return playerSessions; - } - ); - - // Calculating getters - container.putCachingSupplier(PlayerKeys.WORLD_TIMES, () -> { - WorldTimes worldTimes = new PerServerMutator(container.getUnsafe(PlayerKeys.PER_SERVER)).flatMapWorldTimes(); - container.getValue(PlayerKeys.ACTIVE_SESSION) - .ifPresent(session -> worldTimes.add( - session.getExtraData(WorldTimes.class).orElseGet(WorldTimes::new)) - ); - return worldTimes; - }); - container.putSupplier(PlayerKeys.BANNED, () -> PerServerMutator.forContainer(container).isBanned()); - container.putSupplier(PlayerKeys.OPERATOR, () -> PerServerMutator.forContainer(container).isOperator()); - - container.putSupplier(PlayerKeys.LAST_SEEN, () -> SessionsMutator.forContainer(container).toLastSeen()); - - container.putSupplier(PlayerKeys.PLAYER_KILLS, () -> SessionsMutator.forContainer(container).toPlayerKillList()); - container.putSupplier(PlayerKeys.PLAYER_KILL_COUNT, () -> container.getUnsafe(PlayerKeys.PLAYER_KILLS).size()); - container.putSupplier(PlayerKeys.MOB_KILL_COUNT, () -> SessionsMutator.forContainer(container).toMobKillCount()); - container.putSupplier(PlayerKeys.DEATH_COUNT, () -> SessionsMutator.forContainer(container).toDeathCount()); - - containers.add(container); - } - return containers; - } - - /** - * Create PerServerContainers for each player. - * - * @param userInformation Map: Player UUID - UserInfo of this server - * @param sessions Map: Player UUID - List of Sessions of this server - * @param ping Map: Player UUID - List of Ping data of this server - * @return Map: Player UUID - PerServerContainer - */ - private Map getPerServerData( - Map userInformation, - Map> sessions, - Map> ping - ) { - Map perServerContainers = new HashMap<>(); - - for (Map.Entry entry : userInformation.entrySet()) { - UUID playerUUID = entry.getKey(); - PerServerContainer perServerContainer = perServerContainers.getOrDefault(playerUUID, new PerServerContainer()); - - perServerContainer.putUserInfo(entry.getValue()); // Information found withing UserInfo - perServerContainer.putSessions(sessions.get(playerUUID)); // Session list - perServerContainer.putPing(ping.get(playerUUID)); // Ping list - perServerContainer.putCalculatingSuppliers(); // Derivative values - - perServerContainers.put(playerUUID, perServerContainer); - } - - return perServerContainers; - } -} \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/Filter.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/Filter.java index a193975ac..df7a0e71f 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/Filter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/Filter.java @@ -16,6 +16,8 @@ */ package com.djrapitops.plan.storage.database.queries.filter; +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; + import java.util.*; /** @@ -40,11 +42,11 @@ public interface Filter { * @return Set of UUIDs this filter applies to * @throws IllegalArgumentException If the arguments are not valid. */ - Set getMatchingUUIDs(SpecifiedFilterInformation query); + Set getMatchingUserIds(InputFilterDto query); - default Result apply(SpecifiedFilterInformation query) { + default Result apply(InputFilterDto query) { try { - return new Result(null, getKind(), getMatchingUUIDs(query)); + return new Result(null, getKind(), getMatchingUserIds(query)); } catch (CompleteSetException allMatch) { return new Result(null, getKind() + " (skip)", new HashSet<>()); } @@ -55,35 +57,35 @@ public interface Filter { private final String filterKind; private final int resultSize; - private final Set currentUUIDs; + private final Set currentUserIds; - private Result(Result previous, String filterKind, Set currentUUIDs) { + private Result(Result previous, String filterKind, Set currentUserIds) { this.previous = previous; this.filterKind = filterKind; - this.resultSize = currentUUIDs.size(); - this.currentUUIDs = currentUUIDs; + this.resultSize = currentUserIds.size(); + this.currentUserIds = currentUserIds; } - public Result apply(Filter filter, SpecifiedFilterInformation query) { + public Result apply(Filter filter, InputFilterDto query) { try { - Set got = filter.getMatchingUUIDs(query); - currentUUIDs.retainAll(got); - return new Result(this, filter.getKind(), currentUUIDs); + Set got = filter.getMatchingUserIds(query); + currentUserIds.retainAll(got); + return new Result(this, filter.getKind(), currentUserIds); } catch (CompleteSetException allMatch) { return notApplied(filter); } } public Result notApplied(Filter filter) { - return new Result(this, filter.getKind() + " (skip)", currentUUIDs); + return new Result(this, filter.getKind() + " (skip)", currentUserIds); } public boolean isEmpty() { return resultSize <= 0; } - public Set getResultUUIDs() { - return currentUUIDs; + public Set getResultUserIds() { + return currentUserIds; } public List getInverseResultPath() { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/QueryFilters.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/QueryFilters.java index 33b810d58..5c83bede3 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/QueryFilters.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/QueryFilters.java @@ -16,6 +16,7 @@ */ package com.djrapitops.plan.storage.database.queries.filter; +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException; import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.queries.filter.filters.AllPlayersFilter; @@ -81,25 +82,25 @@ public class QueryFilters { * @return the result object or null if none of the filterQueries could be applied. * @throws BadRequestException If the request kind is not supported or if filter was given bad options. */ - public Filter.Result apply(List filterQueries) { + public Filter.Result apply(List filterQueries) { prepareFilters(); Filter.Result current = null; if (filterQueries.isEmpty()) return allPlayersFilter.apply(null); - for (SpecifiedFilterInformation specifiedFilterInformation : filterQueries) { - current = apply(current, specifiedFilterInformation); + for (InputFilterDto inputFilterDto : filterQueries) { + current = apply(current, inputFilterDto); if (current != null && current.isEmpty()) break; } return current; } - private Filter.Result apply(Filter.Result current, SpecifiedFilterInformation specifiedFilterInformation) { - String kind = specifiedFilterInformation.getKind(); + private Filter.Result apply(Filter.Result current, InputFilterDto inputFilterDto) { + String kind = inputFilterDto.getKind(); Filter filter = getFilter(kind).orElseThrow(() -> new BadRequestException("Filter kind not supported: '" + kind + "'")); - return getResult(current, filter, specifiedFilterInformation); + return getResult(current, filter, inputFilterDto); } - private Filter.Result getResult(Filter.Result current, Filter filter, SpecifiedFilterInformation query) { + private Filter.Result getResult(Filter.Result current, Filter filter, InputFilterDto query) { try { return current == null ? filter.apply(query) : current.apply(filter, query); } catch (IllegalArgumentException badOptions) { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/ActivityIndexFilter.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/ActivityIndexFilter.java index ffffd70e5..6bbf1a909 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/ActivityIndexFilter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/ActivityIndexFilter.java @@ -16,6 +16,7 @@ */ package com.djrapitops.plan.storage.database.queries.filter.filters; +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; import com.djrapitops.plan.delivery.domain.mutators.ActivityIndex; import com.djrapitops.plan.settings.config.PlanConfig; import com.djrapitops.plan.settings.config.paths.TimeSettings; @@ -23,11 +24,13 @@ import com.djrapitops.plan.settings.locale.Locale; import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.queries.analysis.NetworkActivityIndexQueries; import com.djrapitops.plan.storage.database.queries.filter.CompleteSetException; -import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation; import javax.inject.Inject; import javax.inject.Singleton; -import java.util.*; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; @Singleton @@ -63,7 +66,7 @@ public class ActivityIndexFilter extends MultiOptionFilter { } @Override - public Set getMatchingUUIDs(SpecifiedFilterInformation query) { + public Set getMatchingUserIds(InputFilterDto query) { List selected = getSelected(query); String[] options = getOptionsArray(); @@ -78,7 +81,7 @@ public class ActivityIndexFilter extends MultiOptionFilter { } long date = System.currentTimeMillis(); long playtimeThreshold = config.get(TimeSettings.ACTIVE_PLAY_THRESHOLD); - Map indexes = dbSystem.getDatabase().query(NetworkActivityIndexQueries.activityIndexForAllPlayers(date, playtimeThreshold)); + Map indexes = dbSystem.getDatabase().query(NetworkActivityIndexQueries.activityIndexForAllPlayers(date, playtimeThreshold)); return indexes.entrySet().stream() .filter(entry -> selected.contains(entry.getValue().getGroup(locale))) diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/AllPlayersFilter.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/AllPlayersFilter.java index 8b1853327..9e244117e 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/AllPlayersFilter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/AllPlayersFilter.java @@ -16,15 +16,14 @@ */ package com.djrapitops.plan.storage.database.queries.filter.filters; +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.queries.filter.Filter; -import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation; import com.djrapitops.plan.storage.database.queries.objects.UserIdentifierQueries; import javax.inject.Inject; import javax.inject.Singleton; import java.util.Set; -import java.util.UUID; /** * Special filter only used in cases where no filters are specified. @@ -52,7 +51,7 @@ public class AllPlayersFilter implements Filter { } @Override - public Set getMatchingUUIDs(SpecifiedFilterInformation query) { - return dbSystem.getDatabase().query(UserIdentifierQueries.fetchAllPlayerUUIDs()); + public Set getMatchingUserIds(InputFilterDto query) { + return dbSystem.getDatabase().query(UserIdentifierQueries.fetchAllUserIds()); } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/BannedFilter.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/BannedFilter.java index e7cecb527..21be9db10 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/BannedFilter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/BannedFilter.java @@ -16,11 +16,11 @@ */ package com.djrapitops.plan.storage.database.queries.filter.filters; +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; import com.djrapitops.plan.settings.locale.Locale; import com.djrapitops.plan.settings.locale.lang.FilterLang; import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.queries.filter.CompleteSetException; -import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation; import com.djrapitops.plan.storage.database.queries.objects.UserInfoQueries; import javax.inject.Inject; @@ -57,17 +57,17 @@ public class BannedFilter extends MultiOptionFilter { } @Override - public Set getMatchingUUIDs(SpecifiedFilterInformation query) { + public Set getMatchingUserIds(InputFilterDto query) { List selected = getSelected(query); - Set uuids = new HashSet<>(); + Set userIds = new HashSet<>(); String[] options = getOptionsArray(); boolean includeBanned = selected.contains(options[0]); boolean includeNotBanned = selected.contains(options[1]); if (includeBanned && includeNotBanned) throw new CompleteSetException(); // Full set, no need for query - if (includeBanned) uuids.addAll(dbSystem.getDatabase().query(UserInfoQueries.uuidsOfBanned())); - if (includeNotBanned) uuids.addAll(dbSystem.getDatabase().query(UserInfoQueries.uuidsOfNotBanned())); - return uuids; + if (includeBanned) userIds.addAll(dbSystem.getDatabase().query(UserInfoQueries.userIdsOfBanned())); + if (includeNotBanned) userIds.addAll(dbSystem.getDatabase().query(UserInfoQueries.userIdsOfNotBanned())); + return userIds; } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/DateRangeFilter.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/DateRangeFilter.java index 10a1655fd..24597c8dc 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/DateRangeFilter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/DateRangeFilter.java @@ -16,10 +16,10 @@ */ package com.djrapitops.plan.storage.database.queries.filter.filters; +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException; import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.queries.filter.Filter; -import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation; import com.djrapitops.plan.storage.database.queries.objects.BaseUserQueries; import com.djrapitops.plan.utilities.java.Maps; import org.apache.commons.lang3.StringUtils; @@ -61,15 +61,15 @@ public abstract class DateRangeFilter implements Filter { .build(); } - protected long getAfter(SpecifiedFilterInformation query) { + protected long getAfter(InputFilterDto query) { return getTime(query, "afterDate", "afterTime"); } - protected long getBefore(SpecifiedFilterInformation query) { + protected long getBefore(InputFilterDto query) { return getTime(query, "beforeDate", "beforeTime"); } - private long getTime(SpecifiedFilterInformation query, String dateKey, String timeKey) { + private long getTime(InputFilterDto query, String dateKey, String timeKey) { String date = query.get(dateKey).orElseThrow(() -> new BadRequestException("'" + dateKey + "' not specified in parameters for " + getKind())); String time = query.get(timeKey).orElseThrow(() -> new BadRequestException("'" + timeKey + "' not specified in parameters for " + getKind())); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/GeolocationsFilter.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/GeolocationsFilter.java index b548f35c2..3234b3908 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/GeolocationsFilter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/GeolocationsFilter.java @@ -16,13 +16,16 @@ */ package com.djrapitops.plan.storage.database.queries.filter.filters; +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; import com.djrapitops.plan.storage.database.DBSystem; -import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation; import com.djrapitops.plan.storage.database.queries.objects.GeoInfoQueries; import javax.inject.Inject; import javax.inject.Singleton; -import java.util.*; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; @Singleton public class GeolocationsFilter extends MultiOptionFilter { @@ -47,7 +50,7 @@ public class GeolocationsFilter extends MultiOptionFilter { } @Override - public Set getMatchingUUIDs(SpecifiedFilterInformation query) { - return dbSystem.getDatabase().query(GeoInfoQueries.uuidsOfPlayersWithGeolocations(getSelected(query))); + public Set getMatchingUserIds(InputFilterDto query) { + return dbSystem.getDatabase().query(GeoInfoQueries.userIdsOfPlayersWithGeolocations(getSelected(query))); } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/JoinAddressFilter.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/JoinAddressFilter.java index 162b2eac2..3083d5c97 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/JoinAddressFilter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/JoinAddressFilter.java @@ -16,13 +16,16 @@ */ package com.djrapitops.plan.storage.database.queries.filter.filters; +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; import com.djrapitops.plan.storage.database.DBSystem; -import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation; -import com.djrapitops.plan.storage.database.queries.objects.UserInfoQueries; +import com.djrapitops.plan.storage.database.queries.objects.JoinAddressQueries; import javax.inject.Inject; import javax.inject.Singleton; -import java.util.*; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; @Singleton public class JoinAddressFilter extends MultiOptionFilter { @@ -43,11 +46,11 @@ public class JoinAddressFilter extends MultiOptionFilter { } private List getSelectionOptions() { - return dbSystem.getDatabase().query(UserInfoQueries.uniqueJoinAddresses()); + return dbSystem.getDatabase().query(JoinAddressQueries.uniqueJoinAddresses()); } @Override - public Set getMatchingUUIDs(SpecifiedFilterInformation query) { - return dbSystem.getDatabase().query(UserInfoQueries.uuidsOfPlayersWithJoinAddresses(getSelected(query))); + public Set getMatchingUserIds(InputFilterDto query) { + return dbSystem.getDatabase().query(JoinAddressQueries.userIdsOfPlayersWithJoinAddresses(getSelected(query))); } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/MultiOptionFilter.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/MultiOptionFilter.java index d18bf9369..30033e605 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/MultiOptionFilter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/MultiOptionFilter.java @@ -16,9 +16,9 @@ */ package com.djrapitops.plan.storage.database.queries.filter.filters; +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; import com.djrapitops.plan.storage.database.queries.filter.CompleteSetException; import com.djrapitops.plan.storage.database.queries.filter.Filter; -import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; @@ -31,7 +31,7 @@ public abstract class MultiOptionFilter implements Filter { return new String[]{"selected"}; } - protected List getSelected(SpecifiedFilterInformation query) { + protected List getSelected(InputFilterDto query) { String selectedJSON = query.get("selected").orElseThrow(IllegalArgumentException::new); List selected = new Gson().fromJson(selectedJSON, new TypeToken>() {}.getType()); if (selected.isEmpty()) throw new CompleteSetException(); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/OperatorsFilter.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/OperatorsFilter.java index 9f98dfe61..75bd95db1 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/OperatorsFilter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/OperatorsFilter.java @@ -16,11 +16,11 @@ */ package com.djrapitops.plan.storage.database.queries.filter.filters; +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; import com.djrapitops.plan.settings.locale.Locale; import com.djrapitops.plan.settings.locale.lang.FilterLang; import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.queries.filter.CompleteSetException; -import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation; import com.djrapitops.plan.storage.database.queries.objects.UserInfoQueries; import javax.inject.Inject; @@ -57,17 +57,17 @@ public class OperatorsFilter extends MultiOptionFilter { } @Override - public Set getMatchingUUIDs(SpecifiedFilterInformation query) { + public Set getMatchingUserIds(InputFilterDto query) { List selected = getSelected(query); - Set uuids = new HashSet<>(); + Set userIds = new HashSet<>(); String[] options = getOptionsArray(); boolean includeOperators = selected.contains(options[0]); boolean includeNonOperators = selected.contains(options[1]); if (includeOperators && includeNonOperators) throw new CompleteSetException(); // Full set, no need for query - if (includeOperators) uuids.addAll(dbSystem.getDatabase().query(UserInfoQueries.uuidsOfOperators())); - if (includeNonOperators) uuids.addAll(dbSystem.getDatabase().query(UserInfoQueries.uuidsOfNonOperators())); - return uuids; + if (includeOperators) userIds.addAll(dbSystem.getDatabase().query(UserInfoQueries.userIdsOfOperators())); + if (includeNonOperators) userIds.addAll(dbSystem.getDatabase().query(UserInfoQueries.userIdsOfNonOperators())); + return userIds; } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/PlayedBetweenDateRangeFilter.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/PlayedBetweenDateRangeFilter.java index 7721a109a..a6b52be96 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/PlayedBetweenDateRangeFilter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/PlayedBetweenDateRangeFilter.java @@ -16,14 +16,19 @@ */ package com.djrapitops.plan.storage.database.queries.filter.filters; +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; +import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.DBSystem; -import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation; +import com.djrapitops.plan.storage.database.queries.objects.ServerQueries; import com.djrapitops.plan.storage.database.queries.objects.SessionQueries; +import com.google.gson.Gson; import javax.inject.Inject; import javax.inject.Singleton; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Set; -import java.util.UUID; @Singleton public class PlayedBetweenDateRangeFilter extends DateRangeFilter { @@ -42,9 +47,18 @@ public class PlayedBetweenDateRangeFilter extends DateRangeFilter { } @Override - public Set getMatchingUUIDs(SpecifiedFilterInformation query) { + public Set getMatchingUserIds(InputFilterDto query) { long after = getAfter(query); long before = getBefore(query); - return dbSystem.getDatabase().query(SessionQueries.uuidsOfPlayedBetween(after, before)); + List serverNames = getServerNames(query); + List serverUUIDs = serverNames.isEmpty() ? Collections.emptyList() : dbSystem.getDatabase().query(ServerQueries.fetchServersMatchingIdentifiers(serverNames)); + return dbSystem.getDatabase().query(SessionQueries.userIdsOfPlayedBetween(after, before, serverUUIDs)); + } + + private List getServerNames(InputFilterDto query) { + return query.get("servers") + .map(serversList -> new Gson().fromJson(serversList, String[].class)) + .map(Arrays::asList) + .orElseGet(Collections::emptyList); } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/PlayedOnServerFilter.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/PlayedOnServerFilter.java new file mode 100644 index 000000000..a2fa2513f --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/PlayedOnServerFilter.java @@ -0,0 +1,63 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.storage.database.queries.filter.filters; + +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; +import com.djrapitops.plan.identification.ServerUUID; +import com.djrapitops.plan.storage.database.DBSystem; +import com.djrapitops.plan.storage.database.queries.objects.ServerQueries; +import com.djrapitops.plan.storage.database.queries.objects.UserInfoQueries; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@Singleton +public class PlayedOnServerFilter extends MultiOptionFilter { + + private final DBSystem dbSystem; + + @Inject + public PlayedOnServerFilter(DBSystem dbSystem) { + this.dbSystem = dbSystem; + } + + @Override + public String getKind() { + return "playedOnServer"; + } + + @Override + public Map getOptions() { + return Collections.singletonMap("options", getSelectionOptions()); + } + + private List getSelectionOptions() { + return dbSystem.getDatabase().query(ServerQueries.fetchGameServerNames()); + } + + @Override + public Set getMatchingUserIds(InputFilterDto query) { + List serverNames = getSelected(query); + List serverUUIDs = serverNames.isEmpty() ? Collections.emptyList() : dbSystem.getDatabase().query(ServerQueries.fetchServersMatchingIdentifiers(serverNames)); + + return dbSystem.getDatabase().query(UserInfoQueries.userIdsOfRegisteredBetween(0, System.currentTimeMillis(), serverUUIDs)); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/PluginBooleanGroupFilter.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/PluginBooleanGroupFilter.java new file mode 100644 index 000000000..8f157da57 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/PluginBooleanGroupFilter.java @@ -0,0 +1,246 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.storage.database.queries.filter.filters; + +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; +import com.djrapitops.plan.identification.Server; +import com.djrapitops.plan.identification.ServerUUID; +import com.djrapitops.plan.storage.database.DBSystem; +import com.djrapitops.plan.storage.database.Database; +import com.djrapitops.plan.storage.database.queries.Query; +import com.djrapitops.plan.storage.database.queries.QueryAllStatement; +import com.djrapitops.plan.storage.database.queries.QueryStatement; +import com.djrapitops.plan.storage.database.queries.objects.ServerQueries; +import com.djrapitops.plan.storage.database.sql.tables.*; +import org.apache.commons.lang3.StringUtils; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.*; + +import static com.djrapitops.plan.storage.database.sql.building.Sql.*; + +@Singleton +public class PluginBooleanGroupFilter extends MultiOptionFilter { + + private final DBSystem dbSystem; + + @Inject + public PluginBooleanGroupFilter(DBSystem dbSystem) { + this.dbSystem = dbSystem; + } + + private static Query> pluginBooleanOptionsQuery() { + String selectOptions = SELECT + DISTINCT + + "server." + ServerTable.ID + " as server_id," + + "server." + ServerTable.NAME + " as server_name," + + "plugin." + ExtensionPluginTable.PLUGIN_NAME + " as plugin_name," + + "provider." + ExtensionProviderTable.TEXT + " as provider_text" + + FROM + ServerTable.TABLE_NAME + " server" + + INNER_JOIN + ExtensionPluginTable.TABLE_NAME + " plugin on plugin." + ExtensionPluginTable.SERVER_UUID + "=server." + ServerTable.SERVER_UUID + + INNER_JOIN + ExtensionProviderTable.TABLE_NAME + " provider on provider." + ExtensionProviderTable.PLUGIN_ID + "=plugin." + ExtensionPluginTable.ID + + INNER_JOIN + ExtensionPlayerValueTable.TABLE_NAME + " value on value." + ExtensionPlayerValueTable.PROVIDER_ID + "=provider." + ExtensionProviderTable.ID + + WHERE + "value." + ExtensionPlayerValueTable.BOOLEAN_VALUE + " IS NOT NULL" + + ORDER_BY + "server_name ASC, plugin_name ASC, provider_text ASC"; + return new QueryAllStatement<>(selectOptions) { + @Override + public List processResults(ResultSet set) throws SQLException { + List options = new ArrayList<>(); + while (set.next()) { + int serverId = set.getInt("server_id"); + String serverName = set.getString("server_name"); + String pluginName = set.getString("plugin_name"); + String providerText = set.getString("provider_text"); + options.add(new PluginBooleanOption( + Server.getIdentifiableName(serverName, serverId), + pluginName, + providerText + )); + } + Collections.sort(options); + return options; + } + }; + } + + private static Query> playersInGroups( + Map selected, + Map namesToUUIDs + ) { + return db -> { + Set userIds = new HashSet<>(); + for (Map.Entry option : selected.entrySet()) { + PluginBooleanOption pluginBooleanOption = option.getKey(); + SelectedBoolean selectedBoolean = option.getValue(); + userIds.addAll( + db.query(playersInGroup( + namesToUUIDs.get(pluginBooleanOption.getServerName()), + pluginBooleanOption.getPluginName(), + pluginBooleanOption.getProviderText(), + selectedBoolean + )) + ); + } + return userIds; + }; + } + + private static Query> playersInGroup( + ServerUUID serverUUID, String pluginName, String providerText, SelectedBoolean selectedBoolean + ) { + String selectUUIDsWithBooleanValues = SELECT + DISTINCT + "u." + UsersTable.ID + " as id" + + FROM + ExtensionPluginTable.TABLE_NAME + " plugin" + + INNER_JOIN + ExtensionProviderTable.TABLE_NAME + " provider on provider." + ExtensionProviderTable.PLUGIN_ID + "=plugin." + ExtensionPluginTable.ID + + INNER_JOIN + ExtensionPlayerValueTable.TABLE_NAME + " value on value." + ExtensionPlayerValueTable.PROVIDER_ID + "=provider." + ExtensionProviderTable.ID + + INNER_JOIN + UsersTable.TABLE_NAME + " u on u." + UsersTable.USER_UUID + "=value." + ExtensionPlayerValueTable.USER_UUID + + WHERE + "plugin." + ExtensionPluginTable.SERVER_UUID + "=?" + + AND + "plugin." + ExtensionPluginTable.PLUGIN_NAME + "=?" + + AND + "provider." + ExtensionProviderTable.TEXT + "=?" + + AND + "value." + ExtensionPlayerValueTable.BOOLEAN_VALUE + (selectedBoolean == SelectedBoolean.BOTH ? "IS NOT NULL" : "=?"); + + return new QueryStatement<>(selectUUIDsWithBooleanValues) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setString(1, serverUUID.toString()); + statement.setString(2, pluginName); + statement.setString(3, providerText); + if (selectedBoolean != SelectedBoolean.BOTH) { + statement.setBoolean(4, selectedBoolean == SelectedBoolean.TRUE); + } + } + + @Override + public Set processResults(ResultSet set) throws SQLException { + Set userIds = new HashSet<>(); + while (set.next()) { + userIds.add(set.getInt("id")); + } + return userIds; + } + }; + } + + @Override + public String getKind() { + return "pluginsBooleanGroups"; + } + + private List getOptionList() { + Database database = dbSystem.getDatabase(); + List pluginBooleanOptions = database.query(pluginBooleanOptionsQuery()); + + List options = new ArrayList<>(); + for (PluginBooleanOption pluginBooleanOption : pluginBooleanOptions) { + String names = pluginBooleanOption.format(); + options.add(names + ": true"); + options.add(names + ": false"); + } + + return options; + } + + @Override + public Map getOptions() { + return Collections.singletonMap("options", getOptionList()); + } + + @Override + public Set getMatchingUserIds(InputFilterDto query) { + Map selectedBooleanOptions = new HashMap<>(); + for (String selected : getSelected(query)) { + String[] optionAndBoolean = StringUtils.split(selected, ":", 2); + PluginBooleanOption pluginBooleanOption = PluginBooleanOption.parse(optionAndBoolean[0].trim()); + String selectedBoolean = optionAndBoolean[1].trim().toUpperCase(); + selectedBooleanOptions.computeIfPresent(pluginBooleanOption, (key, existing) -> SelectedBoolean.BOTH); + selectedBooleanOptions.computeIfAbsent(pluginBooleanOption, key -> SelectedBoolean.valueOf(selectedBoolean)); + } + + Database db = dbSystem.getDatabase(); + Map namesToUUIDs = db.query(ServerQueries.fetchServerNamesToUUIDs()); + return db.query(playersInGroups(selectedBooleanOptions, namesToUUIDs)); + } + + public enum SelectedBoolean { + TRUE, + FALSE, + BOTH + } + + public static class PluginBooleanOption implements Comparable { + private final String serverName; + private final String pluginName; + private final String providerText; + + public PluginBooleanOption(String serverName, String pluginName, String providerText) { + this.serverName = serverName; + this.pluginName = pluginName; + this.providerText = providerText; + } + + public static PluginBooleanOption parse(String fromFormatted) { + String[] split1 = StringUtils.split(fromFormatted, ",", 2); + String[] split2 = StringUtils.split(split1[1], "-", 2); + String serverName = split1[0].trim(); + String pluginName = split2[0].trim(); + String providerName = split2[1].trim(); + return new PluginBooleanOption(serverName, pluginName, providerName); + } + + public String getServerName() { + return serverName; + } + + public String getPluginName() { + return pluginName; + } + + public String getProviderText() { + return providerText; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PluginBooleanOption that = (PluginBooleanOption) o; + return Objects.equals(getServerName(), that.getServerName()) && Objects.equals(getPluginName(), that.getPluginName()) && Objects.equals(getProviderText(), that.getProviderText()); + } + + @Override + public int hashCode() { + return Objects.hash(getServerName(), getPluginName(), getProviderText()); + } + + @Override + public int compareTo(PluginBooleanOption o) { + int serverNameAlphabetical = String.CASE_INSENSITIVE_ORDER.compare(serverName, o.serverName); + if (serverNameAlphabetical != 0) return serverNameAlphabetical; + + int pluginNameAlphabetical = String.CASE_INSENSITIVE_ORDER.compare(pluginName, o.pluginName); + if (pluginNameAlphabetical != 0) return pluginNameAlphabetical; + + return String.CASE_INSENSITIVE_ORDER.compare(providerText, o.providerText); + } + + public String format() { + return serverName + ", " + pluginName + " - " + providerText; + } + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/PluginGroupsFilter.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/PluginGroupsFilter.java index a580dbea0..ff6191fcb 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/PluginGroupsFilter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/PluginGroupsFilter.java @@ -16,14 +16,14 @@ */ package com.djrapitops.plan.storage.database.queries.filter.filters; +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; import com.djrapitops.plan.extension.implementation.providers.ProviderIdentifier; -import com.djrapitops.plan.extension.implementation.storage.queries.ExtensionUUIDsInGroupQuery; +import com.djrapitops.plan.extension.implementation.storage.queries.ExtensionUserIdsInGroupQuery; import com.djrapitops.plan.identification.Server; import com.djrapitops.plan.identification.ServerInfo; import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.queries.QueryAllStatement; -import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation; import com.djrapitops.plan.storage.database.sql.tables.ExtensionGroupsTable; import com.djrapitops.plan.storage.database.sql.tables.ExtensionPluginTable; import com.djrapitops.plan.storage.database.sql.tables.ExtensionProviderTable; @@ -67,9 +67,9 @@ public class PluginGroupsFilter extends MultiOptionFilter { } @Override - public Set getMatchingUUIDs(SpecifiedFilterInformation query) { + public Set getMatchingUserIds(InputFilterDto query) { return dbSystem.getDatabase().query( - new ExtensionUUIDsInGroupQuery(identifier.getPluginName(), identifier.getProviderName(), identifier.getServerUUID(), getSelected(query)) + new ExtensionUserIdsInGroupQuery(identifier.getPluginName(), identifier.getProviderName(), identifier.getServerUUID(), getSelected(query)) ); } @@ -83,7 +83,7 @@ public class PluginGroupsFilter extends MultiOptionFilter { super(SELECT + DISTINCT + "pl." + ExtensionPluginTable.PLUGIN_NAME + " as plugin_name," + "s." + ServerTable.NAME + " as server_name," + - "s." + ServerTable.SERVER_ID + " as server_id," + + "s." + ServerTable.ID + " as server_id," + "pl." + ExtensionPluginTable.SERVER_UUID + " as server_uuid," + "pr." + ExtensionProviderTable.PROVIDER_NAME + " as provider_name," + "gr." + ExtensionGroupsTable.GROUP_NAME + " as group_name" + diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/RegisteredBetweenDateRangeFilter.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/RegisteredBetweenDateRangeFilter.java index 913c2d1e1..44c260e2c 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/RegisteredBetweenDateRangeFilter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/RegisteredBetweenDateRangeFilter.java @@ -16,14 +16,20 @@ */ package com.djrapitops.plan.storage.database.queries.filter.filters; +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; +import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.DBSystem; -import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation; import com.djrapitops.plan.storage.database.queries.objects.BaseUserQueries; +import com.djrapitops.plan.storage.database.queries.objects.ServerQueries; +import com.djrapitops.plan.storage.database.queries.objects.UserInfoQueries; +import com.google.gson.Gson; import javax.inject.Inject; import javax.inject.Singleton; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Set; -import java.util.UUID; @Singleton public class RegisteredBetweenDateRangeFilter extends DateRangeFilter { @@ -42,9 +48,21 @@ public class RegisteredBetweenDateRangeFilter extends DateRangeFilter { } @Override - public Set getMatchingUUIDs(SpecifiedFilterInformation query) { + public Set getMatchingUserIds(InputFilterDto query) { long after = getAfter(query); long before = getBefore(query); - return dbSystem.getDatabase().query(BaseUserQueries.uuidsOfRegisteredBetween(after, before)); + List serverNames = getServerNames(query); + List serverUUIDs = serverNames.isEmpty() ? Collections.emptyList() : dbSystem.getDatabase().query(ServerQueries.fetchServersMatchingIdentifiers(serverNames)); + return dbSystem.getDatabase().query( + serverUUIDs.isEmpty() ? BaseUserQueries.userIdsOfRegisteredBetween(after, before) + : UserInfoQueries.userIdsOfRegisteredBetween(after, before, serverUUIDs) + ); + } + + private List getServerNames(InputFilterDto query) { + return query.get("servers") + .map(serversList -> new Gson().fromJson(serversList, String[].class)) + .map(Arrays::asList) + .orElseGet(Collections::emptyList); } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/BaseUserQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/BaseUserQueries.java index 487d5ee54..c99580492 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/BaseUserQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/BaseUserQueries.java @@ -18,15 +18,12 @@ package com.djrapitops.plan.storage.database.queries.objects; import com.djrapitops.plan.gathering.domain.BaseUser; import com.djrapitops.plan.gathering.domain.UserInfo; -import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.queries.Query; -import com.djrapitops.plan.storage.database.queries.QueryAllStatement; -import com.djrapitops.plan.storage.database.queries.QueryStatement; +import com.djrapitops.plan.storage.database.queries.RowExtractors; import com.djrapitops.plan.storage.database.sql.building.Select; -import com.djrapitops.plan.storage.database.sql.tables.UserInfoTable; import com.djrapitops.plan.storage.database.sql.tables.UsersTable; +import org.apache.commons.text.TextStringBuilder; -import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; @@ -54,25 +51,25 @@ public class BaseUserQueries { public static Query> fetchAllBaseUsers() { String sql = Select.all(UsersTable.TABLE_NAME).toString(); - return new QueryAllStatement>(sql, 20000) { - @Override - public Collection processResults(ResultSet set) throws SQLException { - return extractBaseUsers(set); - } - }; + return db -> db.queryList(sql, BaseUserQueries::extractBaseUser); } - private static Collection extractBaseUsers(ResultSet set) throws SQLException { - Collection users = new HashSet<>(); - while (set.next()) { - UUID playerUUID = UUID.fromString(set.getString(UsersTable.USER_UUID)); - String name = set.getString(UsersTable.USER_NAME); - long registered = set.getLong(UsersTable.REGISTERED); - int kicked = set.getInt(UsersTable.TIMES_KICKED); + public static Query> fetchAllBaseUsersByUUID() { + String sql = Select.all(UsersTable.TABLE_NAME).toString(); - users.add(new BaseUser(playerUUID, name, registered, kicked)); - } - return users; + return db -> db.queryMap(sql, (results, map) -> { + BaseUser baseUser = extractBaseUser(results); + map.put(baseUser.getUuid(), baseUser); + }, HashMap::new); + } + + private static BaseUser extractBaseUser(ResultSet set) throws SQLException { + UUID playerUUID = UUID.fromString(set.getString(UsersTable.USER_UUID)); + String name = set.getString(UsersTable.USER_NAME); + long registered = set.getLong(UsersTable.REGISTERED); + int kicked = set.getInt(UsersTable.TIMES_KICKED); + + return new BaseUser(playerUUID, name, registered, kicked); } /** @@ -86,96 +83,38 @@ public class BaseUserQueries { public static Query> fetchBaseUserOfPlayer(UUID playerUUID) { String sql = Select.all(UsersTable.TABLE_NAME).where(UsersTable.USER_UUID + "=?").toString(); - return new QueryStatement>(sql, 20000) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setString(1, playerUUID.toString()); - } - - @Override - public Optional processResults(ResultSet set) throws SQLException { - if (set.next()) { - UUID playerUUID = UUID.fromString(set.getString(UsersTable.USER_UUID)); - String name = set.getString(UsersTable.USER_NAME); - long registered = set.getLong(UsersTable.REGISTERED); - int kicked = set.getInt(UsersTable.TIMES_KICKED); - - return Optional.of(new BaseUser(playerUUID, name, registered, kicked)); - } - return Optional.empty(); - } - }; + return db -> db.queryOptional(sql, BaseUserQueries::extractBaseUser, playerUUID); } - /** - * Query database for common user information for players that have played on a specific server. - *

- * Only one {@link BaseUser} per player exists unlike {@link UserInfo} which is available per server. - *

- * This will fetch BaseUsers for which UserInfo object also exists on the server. - * - * @param serverUUID UUID of the Plan server. - * @return Collection: BaseUsers - */ - public static Query> fetchServerBaseUsers(ServerUUID serverUUID) { - String sql = SELECT + - UsersTable.TABLE_NAME + '.' + UsersTable.USER_UUID + ',' + - UsersTable.USER_NAME + ',' + - UsersTable.TABLE_NAME + '.' + UsersTable.REGISTERED + ',' + - UsersTable.TIMES_KICKED + - FROM + UsersTable.TABLE_NAME + - INNER_JOIN + UserInfoTable.TABLE_NAME + " on " + - UsersTable.TABLE_NAME + '.' + UsersTable.USER_UUID + "=" + UserInfoTable.TABLE_NAME + '.' + UserInfoTable.USER_UUID + - WHERE + UserInfoTable.SERVER_UUID + "=?"; - return new QueryStatement>(sql, 1000) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setString(1, serverUUID.toString()); - } - - @Override - public Collection processResults(ResultSet set) throws SQLException { - return extractBaseUsers(set); - } - }; - } - - public static Query> uuidsOfRegisteredBetween(long after, long before) { - String sql = SELECT + DISTINCT + UsersTable.USER_UUID + + public static Query> userIdsOfRegisteredBetween(long after, long before) { + String sql = SELECT + DISTINCT + UsersTable.ID + FROM + UsersTable.TABLE_NAME + WHERE + UsersTable.REGISTERED + ">=?" + AND + UsersTable.REGISTERED + "<=?"; - return new QueryStatement>(sql) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setLong(1, after); - statement.setLong(2, before); - } - @Override - public Set processResults(ResultSet set) throws SQLException { - Set uuids = new HashSet<>(); - while (set.next()) { - uuids.add(UUID.fromString(set.getString(UsersTable.USER_UUID))); - } - return uuids; - } - }; + return db -> db.querySet(sql, RowExtractors.getInt(UsersTable.ID), after, before); } public static Query> minimumRegisterDate() { String sql = SELECT + min(UsersTable.REGISTERED) + " as min" + FROM + UsersTable.TABLE_NAME; - return new QueryAllStatement>(sql) { - @Override - public Optional processResults(ResultSet set) throws SQLException { - if (set.next()) { - long min = set.getLong("min"); - if (!set.wasNull()) return Optional.of(min); - } - return Optional.empty(); - } - }; + return db -> db.queryOptional(sql, RowExtractors.getLong("min")); } + // Visible for testing + public static Query> fetchUserId(UUID playerUUID) { + String sql = Select.from(UsersTable.TABLE_NAME, UsersTable.ID) + .where(UsersTable.USER_UUID + "=?") + .toString(); + + return db -> db.queryOptional(sql, RowExtractors.getInt(UsersTable.ID), playerUUID); + } + + public static Query> fetchExistingUUIDs(Set outOfPlayerUUIDs) { + String sql = SELECT + UsersTable.USER_UUID + + FROM + UsersTable.TABLE_NAME + + WHERE + UsersTable.USER_UUID + " IN ('" + new TextStringBuilder().appendWithSeparators(outOfPlayerUUIDs, "','").build() + "')"; + + return db -> db.querySet(sql, RowExtractors.getUUID(UsersTable.USER_UUID)); + } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/GeoInfoQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/GeoInfoQueries.java index 054e49e5f..53e411aa0 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/GeoInfoQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/GeoInfoQueries.java @@ -20,13 +20,14 @@ import com.djrapitops.plan.gathering.domain.GeoInfo; import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.queries.Query; import com.djrapitops.plan.storage.database.queries.QueryAllStatement; -import com.djrapitops.plan.storage.database.queries.QueryStatement; +import com.djrapitops.plan.storage.database.queries.RowExtractors; import com.djrapitops.plan.storage.database.sql.tables.GeoInfoTable; +import com.djrapitops.plan.storage.database.sql.tables.ServerTable; import com.djrapitops.plan.storage.database.sql.tables.UserInfoTable; +import com.djrapitops.plan.storage.database.sql.tables.UsersTable; import com.djrapitops.plan.utilities.java.Lists; import org.apache.commons.text.TextStringBuilder; -import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; @@ -53,10 +54,11 @@ public class GeoInfoQueries { String sql = SELECT + GeoInfoTable.GEOLOCATION + ',' + GeoInfoTable.LAST_USED + ',' + - GeoInfoTable.USER_UUID + - FROM + GeoInfoTable.TABLE_NAME; + UsersTable.USER_UUID + + FROM + GeoInfoTable.TABLE_NAME + " g" + + INNER_JOIN + UsersTable.TABLE_NAME + " u on g.user_id=u.id"; - return new QueryAllStatement>>(sql, 50000) { + return new QueryAllStatement<>(sql, 10000) { @Override public Map> processResults(ResultSet set) throws SQLException { return extractGeoInformation(set); @@ -67,7 +69,7 @@ public class GeoInfoQueries { private static Map> extractGeoInformation(ResultSet set) throws SQLException { Map> geoInformation = new HashMap<>(); while (set.next()) { - UUID uuid = UUID.fromString(set.getString(GeoInfoTable.USER_UUID)); + UUID uuid = UUID.fromString(set.getString(UsersTable.USER_UUID)); List userGeoInfo = geoInformation.computeIfAbsent(uuid, Lists::create); GeoInfo geoInfo = new GeoInfo(set.getString(GeoInfoTable.GEOLOCATION), set.getLong(GeoInfoTable.LAST_USED)); @@ -87,172 +89,91 @@ public class GeoInfoQueries { GeoInfoTable.GEOLOCATION + ",MAX(" + GeoInfoTable.LAST_USED + ") as " + GeoInfoTable.LAST_USED + FROM + GeoInfoTable.TABLE_NAME + - WHERE + GeoInfoTable.USER_UUID + "=?" + + WHERE + GeoInfoTable.USER_ID + "=" + UsersTable.SELECT_USER_ID + GROUP_BY + GeoInfoTable.GEOLOCATION; - return new QueryStatement>(sql, 100) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setString(1, playerUUID.toString()); - } - - @Override - public List processResults(ResultSet set) throws SQLException { - List geoInfo = new ArrayList<>(); - while (set.next()) { - String geolocation = set.getString(GeoInfoTable.GEOLOCATION); - long lastUsed = set.getLong(GeoInfoTable.LAST_USED); - geoInfo.add(new GeoInfo(geolocation, lastUsed)); - } - return geoInfo; - } - }; + return db -> db.queryList(sql, GeoInfoQueries::extractGeoInfo, playerUUID); } - public static Query>> fetchServerGeoInformation(ServerUUID serverUUID) { - String sql = SELECT + GeoInfoTable.TABLE_NAME + '.' + GeoInfoTable.USER_UUID + ',' + - GeoInfoTable.GEOLOCATION + ',' + - GeoInfoTable.LAST_USED + - FROM + GeoInfoTable.TABLE_NAME + - INNER_JOIN + UserInfoTable.TABLE_NAME + " on " + - GeoInfoTable.TABLE_NAME + '.' + GeoInfoTable.USER_UUID + "=" + UserInfoTable.TABLE_NAME + '.' + UserInfoTable.USER_UUID + - WHERE + UserInfoTable.SERVER_UUID + "=?"; - return new QueryStatement>>(sql, 10000) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setString(1, serverUUID.toString()); - } - - @Override - public Map> processResults(ResultSet set) throws SQLException { - return extractGeoInformation(set); - } - }; + private static GeoInfo extractGeoInfo(ResultSet set) throws SQLException { + String geolocation = set.getString(GeoInfoTable.GEOLOCATION); + long lastUsed = set.getLong(GeoInfoTable.LAST_USED); + return new GeoInfo(geolocation, lastUsed); } public static Query> networkGeolocationCounts() { - String subQuery1 = SELECT + - GeoInfoTable.USER_UUID + ", " + - GeoInfoTable.GEOLOCATION + ", " + - GeoInfoTable.LAST_USED + - FROM + GeoInfoTable.TABLE_NAME; - String subQuery2 = SELECT + - GeoInfoTable.USER_UUID + ", " + - "MAX(" + GeoInfoTable.LAST_USED + ") as m" + - FROM + GeoInfoTable.TABLE_NAME + - GROUP_BY + GeoInfoTable.USER_UUID; - String sql = SELECT + GeoInfoTable.GEOLOCATION + ", COUNT(1) as c FROM " + - "(" + subQuery1 + ") AS q1" + - INNER_JOIN + "(" + subQuery2 + ") AS q2 ON q1.uuid = q2.uuid" + - WHERE + GeoInfoTable.LAST_USED + "=m" + - GROUP_BY + GeoInfoTable.GEOLOCATION; + String sql = SELECT + + "a." + GeoInfoTable.GEOLOCATION + ", " + + "COUNT(1) as c" + + FROM + GeoInfoTable.TABLE_NAME + " a" + + // Super smart optimization https://stackoverflow.com/a/28090544 + // Join the last_used column, but only if there's a bigger one. + // That way the biggest a.last_used value will have NULL on the b.last_used column and MAX doesn't need to be used. + LEFT_JOIN + GeoInfoTable.TABLE_NAME + " b ON a." + GeoInfoTable.USER_ID + "=b." + GeoInfoTable.USER_ID + AND + "a." + GeoInfoTable.LAST_USED + ">(sql) { - @Override - public Map processResults(ResultSet set) throws SQLException { - Map geolocationCounts = new HashMap<>(); - while (set.next()) { - geolocationCounts.put(set.getString(GeoInfoTable.GEOLOCATION), set.getInt("c")); - } - return geolocationCounts; - } - }; + return db -> db.queryMap(sql, GeoInfoQueries::extractGeolocationCounts); } - public static Query> networkGeolocationCounts(Collection playerUUIDs) { - String subQuery1 = SELECT + - GeoInfoTable.USER_UUID + ", " + - GeoInfoTable.GEOLOCATION + ", " + - GeoInfoTable.LAST_USED + - FROM + GeoInfoTable.TABLE_NAME + - WHERE + GeoInfoTable.USER_UUID + " IN ('" + - new TextStringBuilder().appendWithSeparators(playerUUIDs, "','").build() + "')"; - String subQuery2 = SELECT + - GeoInfoTable.USER_UUID + ", " + - "MAX(" + GeoInfoTable.LAST_USED + ") as m" + - FROM + GeoInfoTable.TABLE_NAME + - GROUP_BY + GeoInfoTable.USER_UUID; - String sql = SELECT + GeoInfoTable.GEOLOCATION + ", COUNT(1) as c FROM " + - "(" + subQuery1 + ") AS q1" + - INNER_JOIN + "(" + subQuery2 + ") AS q2 ON q1.uuid = q2.uuid" + - WHERE + GeoInfoTable.LAST_USED + "=m" + - GROUP_BY + GeoInfoTable.GEOLOCATION; + private static void extractGeolocationCounts(ResultSet set, Map geolocationCounts) throws SQLException { + geolocationCounts.put( + set.getString(GeoInfoTable.GEOLOCATION), + set.getInt("c") + ); + } - return new QueryAllStatement>(sql) { - @Override - public Map processResults(ResultSet set) throws SQLException { - Map geolocationCounts = new HashMap<>(); - while (set.next()) { - geolocationCounts.put(set.getString(GeoInfoTable.GEOLOCATION), set.getInt("c")); - } - return geolocationCounts; - } - }; + public static Query> networkGeolocationCounts(Collection userIds) { + String sql = SELECT + + "a." + GeoInfoTable.GEOLOCATION + ", " + + "COUNT(1) as c" + + FROM + GeoInfoTable.TABLE_NAME + " a" + + // Super smart optimization https://stackoverflow.com/a/28090544 + // Join the last_used column, but only if there's a bigger one. + // That way the biggest a.last_used value will have NULL on the b.last_used column and MAX doesn't need to be used. + LEFT_JOIN + GeoInfoTable.TABLE_NAME + " b ON a." + GeoInfoTable.USER_ID + "=b." + GeoInfoTable.USER_ID + AND + "a." + GeoInfoTable.LAST_USED + " db.queryMap(sql, GeoInfoQueries::extractGeolocationCounts); } public static Query> serverGeolocationCounts(ServerUUID serverUUID) { - String selectGeolocations = SELECT + - GeoInfoTable.USER_UUID + ", " + - GeoInfoTable.GEOLOCATION + ", " + - GeoInfoTable.LAST_USED + - FROM + GeoInfoTable.TABLE_NAME; - String selectLatestGeolocationDate = SELECT + - GeoInfoTable.USER_UUID + ", " + - "MAX(" + GeoInfoTable.LAST_USED + ") as m" + - FROM + GeoInfoTable.TABLE_NAME + - GROUP_BY + GeoInfoTable.USER_UUID; - String sql = SELECT + GeoInfoTable.GEOLOCATION + ", COUNT(1) as c FROM " + - "(" + selectGeolocations + ") AS q1" + - INNER_JOIN + "(" + selectLatestGeolocationDate + ") AS q2 ON q1.uuid = q2.uuid" + - INNER_JOIN + UserInfoTable.TABLE_NAME + " u on u." + UserInfoTable.USER_UUID + "=q1.uuid" + - WHERE + GeoInfoTable.LAST_USED + "=m" + - AND + "u." + UserInfoTable.SERVER_UUID + "=?" + - GROUP_BY + GeoInfoTable.GEOLOCATION; + String sql = SELECT + + "a." + GeoInfoTable.GEOLOCATION + ", " + + "COUNT(1) as c" + + FROM + GeoInfoTable.TABLE_NAME + " a" + + // Super smart optimization https://stackoverflow.com/a/28090544 + // Join the last_used column, but only if there's a bigger one. + // That way the biggest a.last_used value will have NULL on the b.last_used column and MAX doesn't need to be used. + LEFT_JOIN + GeoInfoTable.TABLE_NAME + " b ON a." + GeoInfoTable.USER_ID + "=b." + GeoInfoTable.USER_ID + AND + "a." + GeoInfoTable.LAST_USED + ">(sql) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setString(1, serverUUID.toString()); - } - - @Override - public Map processResults(ResultSet set) throws SQLException { - Map geolocationCounts = new HashMap<>(); - while (set.next()) { - geolocationCounts.put(set.getString(GeoInfoTable.GEOLOCATION), set.getInt("c")); - } - return geolocationCounts; - } - }; + return db -> db.queryMap(sql, GeoInfoQueries::extractGeolocationCounts, serverUUID); } public static Query> uniqueGeolocations() { String sql = SELECT + GeoInfoTable.GEOLOCATION + FROM + GeoInfoTable.TABLE_NAME + ORDER_BY + GeoInfoTable.GEOLOCATION + " ASC"; - return new QueryAllStatement>(sql) { - @Override - public List processResults(ResultSet set) throws SQLException { - List geolocations = new ArrayList<>(); - while (set.next()) geolocations.add(set.getString(GeoInfoTable.GEOLOCATION)); - return geolocations; - } - }; + + return db -> db.queryList(sql, RowExtractors.getString(GeoInfoTable.GEOLOCATION)); } - public static Query> uuidsOfPlayersWithGeolocations(List selected) { - String sql = SELECT + GeoInfoTable.USER_UUID + - FROM + GeoInfoTable.TABLE_NAME + + public static Query> userIdsOfPlayersWithGeolocations(List selected) { + String sql = SELECT + "u." + UsersTable.ID + + FROM + GeoInfoTable.TABLE_NAME + " g" + + INNER_JOIN + UsersTable.TABLE_NAME + " u on u.id=g." + GeoInfoTable.USER_ID + WHERE + GeoInfoTable.GEOLOCATION + " IN ('" + new TextStringBuilder().appendWithSeparators(selected, "','") + "')"; - return new QueryAllStatement>(sql) { - @Override - public Set processResults(ResultSet set) throws SQLException { - Set geolocations = new HashSet<>(); - while (set.next()) geolocations.add(UUID.fromString(set.getString(GeoInfoTable.USER_UUID))); - return geolocations; - } - }; + return db -> db.querySet(sql, RowExtractors.getInt(UsersTable.ID)); } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/JoinAddressQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/JoinAddressQueries.java new file mode 100644 index 000000000..0fcb80fa1 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/JoinAddressQueries.java @@ -0,0 +1,203 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.storage.database.queries.objects; + +import com.djrapitops.plan.delivery.domain.DateObj; +import com.djrapitops.plan.identification.ServerUUID; +import com.djrapitops.plan.storage.database.queries.Query; +import com.djrapitops.plan.storage.database.queries.QueryAllStatement; +import com.djrapitops.plan.storage.database.queries.QueryStatement; +import com.djrapitops.plan.storage.database.queries.RowExtractors; +import com.djrapitops.plan.storage.database.sql.building.Sql; +import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable; +import com.djrapitops.plan.storage.database.sql.tables.ServerTable; +import com.djrapitops.plan.storage.database.sql.tables.SessionsTable; +import org.apache.commons.text.TextStringBuilder; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.*; +import java.util.stream.Collectors; + +import static com.djrapitops.plan.storage.database.sql.building.Sql.*; + +public class JoinAddressQueries { + + private JoinAddressQueries() { + /* Static method class */ + } + + public static Query> latestJoinAddresses() { + String selectLatestJoinAddresses = SELECT + + "COUNT(1) as total," + + JoinAddressTable.JOIN_ADDRESS + + FROM + SessionsTable.TABLE_NAME + " a" + + LEFT_JOIN + SessionsTable.TABLE_NAME + " b on a." + SessionsTable.ID + " db.queryMap(selectLatestJoinAddresses, JoinAddressQueries::extractJoinAddressCounts, TreeMap::new); + } + + private static void extractJoinAddressCounts(ResultSet set, Map joinAddresses) throws SQLException { + joinAddresses.put(set.getString(JoinAddressTable.JOIN_ADDRESS), set.getInt("total")); + } + + public static Query> latestJoinAddresses(ServerUUID serverUUID) { + String selectLatestJoinAddresses = SELECT + + "COUNT(1) as total," + + JoinAddressTable.JOIN_ADDRESS + + FROM + SessionsTable.TABLE_NAME + " a" + + LEFT_JOIN + SessionsTable.TABLE_NAME + " b on a." + SessionsTable.ID + " db.queryMap(selectLatestJoinAddresses, JoinAddressQueries::extractJoinAddressCounts, TreeMap::new, serverUUID); + } + + public static QueryStatement> allJoinAddresses() { + String sql = SELECT + JoinAddressTable.JOIN_ADDRESS + + FROM + JoinAddressTable.TABLE_NAME + + ORDER_BY + JoinAddressTable.JOIN_ADDRESS + " ASC"; + + return new QueryAllStatement<>(sql, 100) { + @Override + public List processResults(ResultSet set) throws SQLException { + List joinAddresses = new ArrayList<>(); + while (set.next()) joinAddresses.add(set.getString(JoinAddressTable.JOIN_ADDRESS)); + return joinAddresses; + } + }; + } + + public static Query> uniqueJoinAddresses() { + return db -> { + List addresses = db.query(allJoinAddresses()); + if (!addresses.contains(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP)) { + addresses.add(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP); + } + return addresses; + }; + } + + public static Query> userIdsOfPlayersWithJoinAddresses(List joinAddresses) { + String sql = SELECT + DISTINCT + SessionsTable.USER_ID + + FROM + JoinAddressTable.TABLE_NAME + " j" + + INNER_JOIN + SessionsTable.TABLE_NAME + " s on s." + SessionsTable.JOIN_ADDRESS_ID + "=j." + JoinAddressTable.ID + + WHERE + JoinAddressTable.JOIN_ADDRESS + " IN (" + + new TextStringBuilder().appendWithSeparators(joinAddresses.stream().map(item -> '?').iterator(), ",") + + ')'; // Don't append addresses directly, SQL injection hazard + + return db -> db.querySet(sql, RowExtractors.getInt(SessionsTable.USER_ID), joinAddresses.toArray()); + } + + public static Query>>> joinAddressesPerDay(ServerUUID serverUUID, long timezoneOffset, long after, long before) { + return db -> { + Sql sql = db.getSql(); + + String selectAddresses = SELECT + + sql.dateToEpochSecond(sql.dateToDayStamp(sql.epochSecondToDate('(' + SessionsTable.SESSION_START + "+?)/1000"))) + + "*1000 as date," + + JoinAddressTable.JOIN_ADDRESS + + ", COUNT(1) as count" + + FROM + SessionsTable.TABLE_NAME + " s" + + LEFT_JOIN + JoinAddressTable.TABLE_NAME + " j on s." + SessionsTable.JOIN_ADDRESS_ID + "=j." + JoinAddressTable.ID + + WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + + AND + SessionsTable.SESSION_START + ">?" + + AND + SessionsTable.SESSION_START + "<=?" + + GROUP_BY + "date,j." + JoinAddressTable.ID; + + return db.query(new QueryStatement<>(selectAddresses, 1000) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setLong(1, timezoneOffset); + statement.setString(2, serverUUID.toString()); + statement.setLong(3, after); + statement.setLong(4, before); + } + + @Override + public List>> processResults(ResultSet set) throws SQLException { + Map> addressesByDate = new HashMap<>(); + while (set.next()) { + long date = set.getLong("date"); + String joinAddress = set.getString(JoinAddressTable.JOIN_ADDRESS); + int count = set.getInt("count"); + Map joinAddresses = addressesByDate.computeIfAbsent(date, k -> new TreeMap<>()); + joinAddresses.put(joinAddress, count); + } + + return addressesByDate.entrySet() + .stream().map(entry -> new DateObj<>(entry.getKey(), entry.getValue())) + .collect(Collectors.toList()); + } + }); + }; + } + + public static Query>>> joinAddressesPerDay(long timezoneOffset, long after, long before) { + return db -> { + Sql sql = db.getSql(); + + String selectAddresses = SELECT + + sql.dateToEpochSecond(sql.dateToDayStamp(sql.epochSecondToDate('(' + SessionsTable.SESSION_START + "+?)/1000"))) + + "*1000 as date," + + JoinAddressTable.JOIN_ADDRESS + + ", COUNT(1) as count" + + FROM + SessionsTable.TABLE_NAME + " s" + + LEFT_JOIN + JoinAddressTable.TABLE_NAME + " j on s." + SessionsTable.JOIN_ADDRESS_ID + "=j." + JoinAddressTable.ID + + WHERE + SessionsTable.SESSION_START + ">?" + + AND + SessionsTable.SESSION_START + "<=?" + + GROUP_BY + "date,j." + JoinAddressTable.ID; + + return db.query(new QueryStatement<>(selectAddresses, 1000) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setLong(1, timezoneOffset); + statement.setLong(2, after); + statement.setLong(3, before); + } + + @Override + public List>> processResults(ResultSet set) throws SQLException { + Map> addressesByDate = new HashMap<>(); + while (set.next()) { + long date = set.getLong("date"); + String joinAddress = set.getString(JoinAddressTable.JOIN_ADDRESS); + int count = set.getInt("count"); + Map joinAddresses = addressesByDate.computeIfAbsent(date, k -> new TreeMap<>()); + joinAddresses.put(joinAddress, count); + } + + return addressesByDate.entrySet() + .stream().map(entry -> new DateObj<>(entry.getKey(), entry.getValue())) + .collect(Collectors.toList()); + } + }); + }; + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/KillQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/KillQueries.java index b682d0165..51ba992de 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/KillQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/KillQueries.java @@ -16,13 +16,16 @@ */ package com.djrapitops.plan.storage.database.queries.objects; +import com.djrapitops.plan.delivery.domain.ServerIdentifier; import com.djrapitops.plan.gathering.domain.PlayerKill; +import com.djrapitops.plan.identification.Server; import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.queries.Query; import com.djrapitops.plan.storage.database.queries.QueryStatement; +import com.djrapitops.plan.storage.database.queries.RowExtractors; import com.djrapitops.plan.storage.database.sql.tables.KillsTable; +import com.djrapitops.plan.storage.database.sql.tables.ServerTable; import com.djrapitops.plan.storage.database.sql.tables.SessionsTable; -import com.djrapitops.plan.storage.database.sql.tables.UserInfoTable; import com.djrapitops.plan.storage.database.sql.tables.UsersTable; import java.sql.PreparedStatement; @@ -50,17 +53,21 @@ public class KillQueries { String sql = SELECT + KillsTable.KILLER_UUID + ", " + KillsTable.VICTIM_UUID + ", " + + KillsTable.SERVER_UUID + ", " + "v." + UsersTable.USER_NAME + " as victim_name, " + "k." + UsersTable.USER_NAME + " as killer_name," + KillsTable.DATE + ", " + - KillsTable.WEAPON + - FROM + KillsTable.TABLE_NAME + - INNER_JOIN + UsersTable.TABLE_NAME + " v on v." + UsersTable.USER_UUID + "=" + KillsTable.VICTIM_UUID + - INNER_JOIN + UsersTable.TABLE_NAME + " k on k." + UsersTable.USER_UUID + "=" + KillsTable.KILLER_UUID + - WHERE + KillsTable.TABLE_NAME + '.' + KillsTable.SERVER_UUID + "=?" + + KillsTable.WEAPON + ", " + + "server." + ServerTable.NAME + " as server_name," + + "server." + ServerTable.ID + " as server_id" + + FROM + KillsTable.TABLE_NAME + " ki" + + INNER_JOIN + UsersTable.TABLE_NAME + " v on v." + UsersTable.USER_UUID + "=ki." + KillsTable.VICTIM_UUID + + INNER_JOIN + UsersTable.TABLE_NAME + " k on k." + UsersTable.USER_UUID + "=ki." + KillsTable.KILLER_UUID + + INNER_JOIN + ServerTable.TABLE_NAME + " server on server." + ServerTable.SERVER_UUID + "=ki." + KillsTable.SERVER_UUID + + WHERE + "ki." + KillsTable.SERVER_UUID + "=?" + ORDER_BY + KillsTable.DATE + " DESC LIMIT ?"; - return new QueryStatement>(sql, limit) { + return new QueryStatement<>(sql, limit) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -82,17 +89,21 @@ public class KillQueries { String sql = SELECT + KillsTable.KILLER_UUID + ", " + KillsTable.VICTIM_UUID + ", " + + KillsTable.SERVER_UUID + ", " + "v." + UsersTable.USER_NAME + " as victim_name, " + "k." + UsersTable.USER_NAME + " as killer_name," + KillsTable.DATE + ", " + - KillsTable.WEAPON + - FROM + KillsTable.TABLE_NAME + - INNER_JOIN + UsersTable.TABLE_NAME + " v on v." + UsersTable.USER_UUID + "=" + KillsTable.VICTIM_UUID + - INNER_JOIN + UsersTable.TABLE_NAME + " k on k." + UsersTable.USER_UUID + "=" + KillsTable.KILLER_UUID + - WHERE + KillsTable.TABLE_NAME + '.' + KillsTable.KILLER_UUID + "=?" + + KillsTable.WEAPON + ", " + + "server." + ServerTable.NAME + " as server_name," + + "server." + ServerTable.ID + " as server_id" + + FROM + KillsTable.TABLE_NAME + " ki" + + INNER_JOIN + UsersTable.TABLE_NAME + " v on v." + UsersTable.USER_UUID + "=ki." + KillsTable.VICTIM_UUID + + INNER_JOIN + UsersTable.TABLE_NAME + " k on k." + UsersTable.USER_UUID + "=ki." + KillsTable.KILLER_UUID + + INNER_JOIN + ServerTable.TABLE_NAME + " server on server." + ServerTable.SERVER_UUID + "=ki." + KillsTable.SERVER_UUID + + WHERE + "ki." + KillsTable.KILLER_UUID + "=?" + ORDER_BY + KillsTable.DATE + " DESC"; - return new QueryStatement>(sql, 100) { + return new QueryStatement<>(sql, 100) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, playerUUID.toString()); @@ -113,17 +124,21 @@ public class KillQueries { String sql = SELECT + KillsTable.KILLER_UUID + ", " + KillsTable.VICTIM_UUID + ", " + + KillsTable.SERVER_UUID + ", " + "v." + UsersTable.USER_NAME + " as victim_name, " + "k." + UsersTable.USER_NAME + " as killer_name," + KillsTable.DATE + ", " + - KillsTable.WEAPON + - FROM + KillsTable.TABLE_NAME + - INNER_JOIN + UsersTable.TABLE_NAME + " v on v." + UsersTable.USER_UUID + "=" + KillsTable.VICTIM_UUID + - INNER_JOIN + UsersTable.TABLE_NAME + " k on k." + UsersTable.USER_UUID + "=" + KillsTable.KILLER_UUID + - WHERE + KillsTable.TABLE_NAME + '.' + KillsTable.VICTIM_UUID + "=?" + + KillsTable.WEAPON + ", " + + "server." + ServerTable.NAME + " as server_name," + + "server." + ServerTable.ID + " as server_id" + + FROM + KillsTable.TABLE_NAME + " ki" + + INNER_JOIN + UsersTable.TABLE_NAME + " v on v." + UsersTable.USER_UUID + "=ki." + KillsTable.VICTIM_UUID + + INNER_JOIN + UsersTable.TABLE_NAME + " k on k." + UsersTable.USER_UUID + "=ki." + KillsTable.KILLER_UUID + + INNER_JOIN + ServerTable.TABLE_NAME + " server on server." + ServerTable.SERVER_UUID + "=ki." + KillsTable.SERVER_UUID + + WHERE + "ki." + KillsTable.VICTIM_UUID + "=?" + ORDER_BY + KillsTable.DATE + " DESC"; - return new QueryStatement>(sql, 100) { + return new QueryStatement<>(sql, 100) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, playerUUID.toString()); @@ -148,7 +163,13 @@ public class KillQueries { UUID victim = UUID.fromString(set.getString(KillsTable.VICTIM_UUID)); long date = set.getLong(KillsTable.DATE); String weapon = set.getString(KillsTable.WEAPON); - return Optional.of(new PlayerKill(killer, victim, weapon, date, victimName, killerName)); + return Optional.of(new PlayerKill( + new PlayerKill.Killer(killer, killerName), + new PlayerKill.Victim(victim, victimName), + new ServerIdentifier(ServerUUID.fromString(set.getString(KillsTable.SERVER_UUID)), + Server.getIdentifiableName(set.getString("server_name"), set.getInt("server_id")) + ), weapon, date + )); } return Optional.empty(); } @@ -159,19 +180,9 @@ public class KillQueries { WHERE + KillsTable.SERVER_UUID + "=?" + AND + KillsTable.DATE + ">=?" + AND + KillsTable.DATE + "<=?"; - return new QueryStatement(sql) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setString(1, serverUUID.toString()); - statement.setLong(2, after); - statement.setLong(3, before); - } - @Override - public Long processResults(ResultSet set) throws SQLException { - return set.next() ? set.getLong("count") : 0L; - } - }; + return db -> db.queryOptional(sql, RowExtractors.getLong("count"), serverUUID, after, before) + .orElse(0L); } public static Query averageKDR(long after, long before, ServerUUID serverUUID) { @@ -181,19 +192,20 @@ public class KillQueries { AND + KillsTable.DATE + ">=?" + AND + KillsTable.DATE + "<=?" + GROUP_BY + KillsTable.KILLER_UUID; - String selectDeathCounts = SELECT + "COUNT(1) as deaths," + KillsTable.VICTIM_UUID + + String selectPlayerDeathCounts = SELECT + "COUNT(1) as deaths," + KillsTable.VICTIM_UUID + FROM + KillsTable.TABLE_NAME + WHERE + KillsTable.SERVER_UUID + "=?" + AND + KillsTable.DATE + ">=?" + AND + KillsTable.DATE + "<=?" + GROUP_BY + KillsTable.VICTIM_UUID; - String sql = SELECT + "u." + UserInfoTable.USER_UUID + ",kills, deaths" + - FROM + UserInfoTable.TABLE_NAME + " u" + - LEFT_JOIN + '(' + selectKillCounts + ") q1 on q1." + KillsTable.KILLER_UUID + "=u." + UserInfoTable.USER_UUID + - LEFT_JOIN + '(' + selectDeathCounts + ") q2 on q2." + KillsTable.VICTIM_UUID + "=u." + UserInfoTable.USER_UUID + - WHERE + "u." + UserInfoTable.SERVER_UUID + "=?"; + String sql = SELECT + "kills, deaths" + + FROM + UsersTable.TABLE_NAME + " u" + + LEFT_JOIN + '(' + selectKillCounts + ") q1 on q1." + KillsTable.KILLER_UUID + "=u." + UsersTable.USER_UUID + + LEFT_JOIN + '(' + selectPlayerDeathCounts + ") q2 on q2." + KillsTable.VICTIM_UUID + "=u." + UsersTable.USER_UUID + + WHERE + "kills" + IS_NOT_NULL + + AND + "deaths" + IS_NOT_NULL; - return new QueryStatement(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -202,7 +214,6 @@ public class KillQueries { statement.setString(4, serverUUID.toString()); statement.setLong(5, after); statement.setLong(6, before); - statement.setString(7, serverUUID.toString()); } @Override @@ -223,43 +234,23 @@ public class KillQueries { public static Query mobKillCount(long after, long before, ServerUUID serverUUID) { String sql = SELECT + "SUM(" + SessionsTable.MOB_KILLS + ") as count" + FROM + SessionsTable.TABLE_NAME + - WHERE + SessionsTable.SERVER_UUID + "=?" + + WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + AND + SessionsTable.SESSION_END + ">=?" + AND + SessionsTable.SESSION_START + "<=?"; - return new QueryStatement(sql) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setString(1, serverUUID.toString()); - statement.setLong(2, after); - statement.setLong(3, before); - } - @Override - public Long processResults(ResultSet set) throws SQLException { - return set.next() ? set.getLong("count") : 0L; - } - }; + + return db -> db.queryOptional(sql, RowExtractors.getLong("count"), serverUUID, after, before) + .orElse(0L); } public static Query deathCount(long after, long before, ServerUUID serverUUID) { String sql = SELECT + "SUM(" + SessionsTable.DEATHS + ") as count" + FROM + SessionsTable.TABLE_NAME + - WHERE + SessionsTable.SERVER_UUID + "=?" + + WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + AND + SessionsTable.SESSION_END + ">=?" + AND + SessionsTable.SESSION_START + "<=?"; - return new QueryStatement(sql) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setString(1, serverUUID.toString()); - statement.setLong(2, after); - statement.setLong(3, before); - } - - @Override - public Long processResults(ResultSet set) throws SQLException { - return set.next() ? set.getLong("count") : 0L; - } - }; + return db -> db.queryOptional(sql, RowExtractors.getLong("count"), serverUUID, after, before) + .orElse(0L); } public static Query> topWeaponsOfServer(long after, long before, ServerUUID serverUUID, int limit) { @@ -273,22 +264,7 @@ public class KillQueries { FROM + '(' + innerSQL + ") q1" + ORDER_BY + "kills DESC LIMIT ?"; - return new QueryStatement>(sql, limit) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setString(1, serverUUID.toString()); - statement.setLong(2, after); - statement.setLong(3, before); - statement.setInt(4, limit); - } - - @Override - public List processResults(ResultSet set) throws SQLException { - List weapons = new ArrayList<>(); - while (set.next()) weapons.add(set.getString(KillsTable.WEAPON)); - return weapons; - } - }; + return db -> db.queryList(sql, RowExtractors.getString(KillsTable.WEAPON), serverUUID, after, before, limit); } public static Query> topWeaponsOfPlayer(long after, long before, UUID playerUUID, int limit) { @@ -302,21 +278,6 @@ public class KillQueries { FROM + '(' + innerSQL + ") q1" + ORDER_BY + "kills DESC LIMIT ?"; - return new QueryStatement>(sql, limit) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setString(1, playerUUID.toString()); - statement.setLong(2, after); - statement.setLong(3, before); - statement.setInt(4, limit); - } - - @Override - public List processResults(ResultSet set) throws SQLException { - List weapons = new ArrayList<>(); - while (set.next()) weapons.add(set.getString(KillsTable.WEAPON)); - return weapons; - } - }; + return db -> db.queryList(sql, RowExtractors.getString(KillsTable.WEAPON), playerUUID, after, before, limit); } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/NicknameQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/NicknameQueries.java index 4387cb779..23bbd8393 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/NicknameQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/NicknameQueries.java @@ -20,12 +20,10 @@ import com.djrapitops.plan.delivery.domain.Nickname; import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.queries.Query; import com.djrapitops.plan.storage.database.queries.QueryAllStatement; -import com.djrapitops.plan.storage.database.queries.QueryStatement; import com.djrapitops.plan.storage.database.sql.tables.NicknamesTable; import com.djrapitops.plan.utilities.java.Lists; import com.djrapitops.plan.utilities.java.Maps; -import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; @@ -56,7 +54,7 @@ public class NicknameQueries { NicknamesTable.SERVER_UUID + FROM + NicknamesTable.TABLE_NAME; - return new QueryAllStatement>>>(sql, 5000) { + return new QueryAllStatement<>(sql, 5000) { @Override public Map>> processResults(ResultSet set) throws SQLException { Map>> map = new HashMap<>(); @@ -84,32 +82,24 @@ public class NicknameQueries { AND + NicknamesTable.SERVER_UUID + "=?" + GROUP_BY + NicknamesTable.USER_UUID; String sql = SELECT + - NicknamesTable.LAST_USED + ',' + NicknamesTable.NICKNAME + + NicknamesTable.LAST_USED + ',' + + NicknamesTable.NICKNAME + ',' + + NicknamesTable.SERVER_UUID + FROM + NicknamesTable.TABLE_NAME + WHERE + NicknamesTable.USER_UUID + "=?" + AND + NicknamesTable.SERVER_UUID + "=?" + AND + NicknamesTable.LAST_USED + "=(" + subQuery + ')'; - return new QueryStatement>(sql) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setString(1, playerUUID.toString()); - statement.setString(2, serverUUID.toString()); - statement.setString(3, playerUUID.toString()); - statement.setString(4, serverUUID.toString()); - } - @Override - public Optional processResults(ResultSet set) throws SQLException { - if (set.next()) { - return Optional.of(new Nickname( - set.getString(NicknamesTable.NICKNAME), - set.getLong(NicknamesTable.LAST_USED), - serverUUID - )); - } - return Optional.empty(); - } - }; + return db -> db.queryOptional(sql, NicknameQueries::extractNickname, + playerUUID, serverUUID, playerUUID, serverUUID); + } + + private static Nickname extractNickname(ResultSet set) throws SQLException { + return new Nickname( + set.getString(NicknamesTable.NICKNAME), + set.getLong(NicknamesTable.LAST_USED), + ServerUUID.fromString(set.getString(NicknamesTable.SERVER_UUID)) + ); } public static Query> fetchNicknameDataOfPlayer(UUID playerUUID) { @@ -120,62 +110,7 @@ public class NicknameQueries { FROM + NicknamesTable.TABLE_NAME + WHERE + NicknamesTable.USER_UUID + "=?"; - return new QueryStatement>(sql, 5000) { - - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setString(1, playerUUID.toString()); - } - - @Override - public List processResults(ResultSet set) throws SQLException { - List nicknames = new ArrayList<>(); - while (set.next()) { - ServerUUID serverUUID = ServerUUID.fromString(set.getString(NicknamesTable.SERVER_UUID)); - String nickname = set.getString(NicknamesTable.NICKNAME); - nicknames.add(new Nickname(nickname, set.getLong(NicknamesTable.LAST_USED), serverUUID)); - } - return nicknames; - } - }; + return db -> db.queryList(sql, NicknameQueries::extractNickname, playerUUID); } - /** - * Query database for nickname information of a server. - * - * @param serverUUID UUID the the Plan server. - * @return Map: Player UUID - List of Nicknames on the server. - */ - public static Query>> fetchNicknameDataOfServer(ServerUUID serverUUID) { - String sql = SELECT + - NicknamesTable.NICKNAME + ',' + - NicknamesTable.LAST_USED + ',' + - NicknamesTable.USER_UUID + - FROM + NicknamesTable.TABLE_NAME + - WHERE + NicknamesTable.SERVER_UUID + "=?"; - - return new QueryStatement>>(sql, 5000) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setString(1, serverUUID.toString()); - } - - @Override - public Map> processResults(ResultSet set) throws SQLException { - Map> serverMap = new HashMap<>(); - while (set.next()) { - UUID uuid = UUID.fromString(set.getString(NicknamesTable.USER_UUID)); - - List nicknames = serverMap.computeIfAbsent(uuid, Lists::create); - - nicknames.add(new Nickname( - set.getString(NicknamesTable.NICKNAME), - set.getLong(NicknamesTable.LAST_USED), - serverUUID - )); - } - return serverMap; - } - }; - } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/PingQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/PingQueries.java index 145df595c..304596e0f 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/PingQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/PingQueries.java @@ -23,6 +23,8 @@ import com.djrapitops.plan.storage.database.queries.QueryAllStatement; import com.djrapitops.plan.storage.database.queries.QueryStatement; import com.djrapitops.plan.storage.database.sql.tables.GeoInfoTable; import com.djrapitops.plan.storage.database.sql.tables.PingTable; +import com.djrapitops.plan.storage.database.sql.tables.ServerTable; +import com.djrapitops.plan.storage.database.sql.tables.UsersTable; import com.djrapitops.plan.utilities.java.Lists; import java.sql.PreparedStatement; @@ -54,10 +56,12 @@ public class PingQueries { PingTable.MAX_PING + ',' + PingTable.MIN_PING + ',' + PingTable.AVG_PING + ',' + - PingTable.USER_UUID + ',' + - PingTable.SERVER_UUID + - FROM + PingTable.TABLE_NAME; - return new QueryAllStatement>>(sql, 100000) { + "u." + UsersTable.USER_UUID + " as uuid," + + "s." + ServerTable.SERVER_UUID + " as server_uuid" + + FROM + PingTable.TABLE_NAME + " p" + + INNER_JOIN + UsersTable.TABLE_NAME + " u on u.id=p." + PingTable.USER_ID + + INNER_JOIN + ServerTable.TABLE_NAME + " s on s.id=p." + PingTable.SERVER_ID; + return new QueryAllStatement<>(sql, 100000) { @Override public Map> processResults(ResultSet set) throws SQLException { return extractUserPings(set); @@ -69,8 +73,8 @@ public class PingQueries { Map> userPings = new HashMap<>(); while (set.next()) { - UUID uuid = UUID.fromString(set.getString(PingTable.USER_UUID)); - ServerUUID serverUUID = ServerUUID.fromString(set.getString(PingTable.SERVER_UUID)); + UUID uuid = UUID.fromString(set.getString("uuid")); + ServerUUID serverUUID = ServerUUID.fromString(set.getString("server_uuid")); long date = set.getLong(PingTable.DATE); double avgPing = set.getDouble(PingTable.AVG_PING); int minPing = set.getInt(PingTable.MIN_PING); @@ -93,10 +97,11 @@ public class PingQueries { * @return List of Ping entries for this player. */ public static Query> fetchPingDataOfPlayer(UUID playerUUID) { - String sql = SELECT + '*' + FROM + PingTable.TABLE_NAME + - WHERE + PingTable.USER_UUID + "=?"; + String sql = SELECT + '*' + FROM + PingTable.TABLE_NAME + " p" + + INNER_JOIN + ServerTable.TABLE_NAME + " s on s." + ServerTable.ID + "=p." + PingTable.SERVER_ID + + WHERE + PingTable.USER_ID + "=" + UsersTable.SELECT_USER_ID; - return new QueryStatement>(sql, 10000) { + return new QueryStatement<>(sql, 10000) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, playerUUID.toString()); @@ -109,7 +114,7 @@ public class PingQueries { while (set.next()) { pings.add(new Ping( set.getLong(PingTable.DATE), - ServerUUID.fromString(set.getString(PingTable.SERVER_UUID)), + ServerUUID.fromString(set.getString(ServerTable.SERVER_UUID)), set.getInt(PingTable.MIN_PING), set.getInt(PingTable.MAX_PING), set.getDouble(PingTable.AVG_PING) @@ -122,41 +127,17 @@ public class PingQueries { }; } - public static Query>> fetchPingDataOfServer(ServerUUID serverUUID) { - String sql = SELECT + - PingTable.DATE + ',' + - PingTable.MAX_PING + ',' + - PingTable.MIN_PING + ',' + - PingTable.AVG_PING + ',' + - PingTable.USER_UUID + ',' + - PingTable.SERVER_UUID + - FROM + PingTable.TABLE_NAME + - WHERE + PingTable.SERVER_UUID + "=?"; - return new QueryStatement>>(sql, 100000) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setString(1, serverUUID.toString()); - } - - @Override - public Map> processResults(ResultSet set) throws SQLException { - return extractUserPings(set); - } - }; - } - public static Query> fetchPingDataOfServer(long after, long before, ServerUUID serverUUID) { String sql = SELECT + PingTable.DATE + ", " + PingTable.MAX_PING + ", " + PingTable.MIN_PING + ", " + - PingTable.AVG_PING + ", " + - PingTable.SERVER_UUID + + PingTable.AVG_PING + FROM + PingTable.TABLE_NAME + - WHERE + PingTable.SERVER_UUID + "=?" + + WHERE + PingTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + AND + PingTable.DATE + ">=?" + AND + PingTable.DATE + "<=?"; - return new QueryStatement>(sql, 1000) { + return new QueryStatement<>(sql, 1000) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -169,7 +150,6 @@ public class PingQueries { List pings = new ArrayList<>(); while (set.next()) { - ServerUUID serverUUID = ServerUUID.fromString(set.getString(PingTable.SERVER_UUID)); long date = set.getLong(PingTable.DATE); double avgPing = set.getDouble(PingTable.AVG_PING); int minPing = set.getInt(PingTable.MIN_PING); @@ -187,37 +167,21 @@ public class PingQueries { } public static Query> fetchPingDataOfServerByGeolocation(ServerUUID serverUUID) { - String selectPingOfServer = SELECT + - PingTable.MAX_PING + ", " + - PingTable.MIN_PING + ", " + - PingTable.AVG_PING + ", " + - PingTable.USER_UUID + ", " + - PingTable.SERVER_UUID + - FROM + PingTable.TABLE_NAME; - - String selectGeolocations = SELECT + - GeoInfoTable.USER_UUID + ", " + - GeoInfoTable.GEOLOCATION + ", " + - GeoInfoTable.LAST_USED + - FROM + GeoInfoTable.TABLE_NAME; - String selectLatestGeolocationDate = SELECT + - GeoInfoTable.USER_UUID + ", " + - "MAX(" + GeoInfoTable.LAST_USED + ") as m" + - FROM + GeoInfoTable.TABLE_NAME + - GROUP_BY + GeoInfoTable.USER_UUID; - - String selectPingByGeolocation = SELECT + GeoInfoTable.GEOLOCATION + + String selectPingByGeolocation = SELECT + "a." + GeoInfoTable.GEOLOCATION + ", MIN(" + PingTable.MIN_PING + ") as minPing" + ", MAX(" + PingTable.MAX_PING + ") as maxPing" + ", AVG(" + PingTable.AVG_PING + ") as avgPing" + - FROM + "(" + selectGeolocations + ") AS q1" + - INNER_JOIN + "(" + selectLatestGeolocationDate + ") AS q2 ON q1.uuid = q2.uuid" + - INNER_JOIN + '(' + selectPingOfServer + ") sp on sp." + PingTable.USER_UUID + "=q1.uuid" + - WHERE + GeoInfoTable.LAST_USED + "=m" + - AND + "sp." + PingTable.SERVER_UUID + "=?" + - GROUP_BY + GeoInfoTable.GEOLOCATION; + FROM + GeoInfoTable.TABLE_NAME + " a" + + // Super smart optimization https://stackoverflow.com/a/28090544 + // Join the last_used column, but only if there's a bigger one. + // That way the biggest a.last_used value will have NULL on the b.last_used column and MAX doesn't need to be used. + LEFT_JOIN + GeoInfoTable.TABLE_NAME + " b ON a." + GeoInfoTable.USER_ID + "=b." + GeoInfoTable.USER_ID + AND + "a." + GeoInfoTable.LAST_USED + ">(selectPingByGeolocation) { + return new QueryStatement<>(selectPingByGeolocation) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -243,37 +207,20 @@ public class PingQueries { } public static Query> fetchPingDataOfNetworkByGeolocation() { - String selectPingOfServer = SELECT + - PingTable.MAX_PING + ", " + - PingTable.MIN_PING + ", " + - PingTable.AVG_PING + ", " + - PingTable.USER_UUID + ", " + - PingTable.SERVER_UUID + - FROM + PingTable.TABLE_NAME; - - String selectGeolocations = SELECT + - GeoInfoTable.USER_UUID + ", " + - GeoInfoTable.GEOLOCATION + ", " + - GeoInfoTable.LAST_USED + - FROM + GeoInfoTable.TABLE_NAME; - String selectLatestGeolocationDate = SELECT + - GeoInfoTable.USER_UUID + ", " + - "MAX(" + GeoInfoTable.LAST_USED + ") as m" + - FROM + GeoInfoTable.TABLE_NAME + - GROUP_BY + GeoInfoTable.USER_UUID; - - String selectPingByGeolocation = SELECT + GeoInfoTable.GEOLOCATION + + String selectPingByGeolocation = SELECT + "a." + GeoInfoTable.GEOLOCATION + ", MIN(" + PingTable.MIN_PING + ") as minPing" + ", MAX(" + PingTable.MAX_PING + ") as maxPing" + ", AVG(" + PingTable.AVG_PING + ") as avgPing" + - FROM + "(" + - "(" + selectGeolocations + ") AS q1" + - INNER_JOIN + "(" + selectLatestGeolocationDate + ") AS q2 ON q1.uuid = q2.uuid" + - INNER_JOIN + '(' + selectPingOfServer + ") sp on sp." + PingTable.USER_UUID + "=q1.uuid)" + - WHERE + GeoInfoTable.LAST_USED + "=m" + - GROUP_BY + GeoInfoTable.GEOLOCATION; + FROM + GeoInfoTable.TABLE_NAME + " a" + + // Super smart optimization https://stackoverflow.com/a/28090544 + // Join the last_used column, but only if there's a bigger one. + // That way the biggest a.last_used value will have NULL on the b.last_used column and MAX doesn't need to be used. + LEFT_JOIN + GeoInfoTable.TABLE_NAME + " b ON a." + GeoInfoTable.USER_ID + "=b." + GeoInfoTable.USER_ID + AND + "a." + GeoInfoTable.LAST_USED + ">(selectPingByGeolocation) { + return new QueryAllStatement<>(selectPingByGeolocation) { @Override public Map processResults(ResultSet set) throws SQLException { // TreeMap to sort alphabetically @@ -295,11 +242,11 @@ public class PingQueries { public static Query averagePing(long after, long before, ServerUUID serverUUID) { String sql = SELECT + "AVG(" + PingTable.AVG_PING + ") as average" + FROM + PingTable.TABLE_NAME + - WHERE + PingTable.SERVER_UUID + "=?" + + WHERE + PingTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + AND + PingTable.DATE + ">=?" + AND + PingTable.DATE + "<=?"; - return new QueryStatement(sql, 1000) { + return new QueryStatement<>(sql, 1000) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/ServerQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/ServerQueries.java index e2a143714..f5f71c50a 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/ServerQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/ServerQueries.java @@ -23,12 +23,14 @@ import com.djrapitops.plan.storage.database.queries.QueryAllStatement; import com.djrapitops.plan.storage.database.queries.QueryStatement; import com.djrapitops.plan.storage.database.sql.building.Select; import com.djrapitops.plan.storage.database.sql.tables.ServerTable; +import com.djrapitops.plan.utilities.java.Maps; import org.apache.commons.lang3.math.NumberUtils; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; +import java.util.stream.Collectors; import static com.djrapitops.plan.storage.database.sql.building.Sql.*; @@ -43,6 +45,31 @@ public class ServerQueries { /* Static method class */ } + public static Query> fetchUninstalledServerInformation() { + String sql = SELECT + '*' + FROM + ServerTable.TABLE_NAME + WHERE + ServerTable.INSTALLED + "=?"; + return new QueryStatement<>(sql, 100) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setBoolean(1, false); + } + + @Override + public Collection processResults(ResultSet set) throws SQLException { + Collection servers = new HashSet<>(); + while (set.next()) { + servers.add(new Server( + set.getInt(ServerTable.ID), + ServerUUID.fromString(set.getString(ServerTable.SERVER_UUID)), + set.getString(ServerTable.NAME), + set.getString(ServerTable.WEB_ADDRESS), + set.getBoolean(ServerTable.PROXY), + set.getString(ServerTable.PLAN_VERSION))); + } + return servers; + } + }; + } + /** * Query database for all Plan server information. * @@ -51,7 +78,7 @@ public class ServerQueries { public static Query> fetchPlanServerInformation() { String sql = SELECT + '*' + FROM + ServerTable.TABLE_NAME + WHERE + ServerTable.INSTALLED + "=?"; - return new QueryStatement>(sql, 100) { + return new QueryStatement<>(sql, 100) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setBoolean(1, true); @@ -63,12 +90,12 @@ public class ServerQueries { while (set.next()) { ServerUUID serverUUID = ServerUUID.fromString(set.getString(ServerTable.SERVER_UUID)); servers.put(serverUUID, new Server( - set.getInt(ServerTable.SERVER_ID), + set.getInt(ServerTable.ID), serverUUID, set.getString(ServerTable.NAME), set.getString(ServerTable.WEB_ADDRESS), - set.getBoolean(ServerTable.PROXY) - )); + set.getBoolean(ServerTable.PROXY), + set.getString(ServerTable.PLAN_VERSION))); } return servers; } @@ -85,13 +112,13 @@ public class ServerQueries { public static Query> fetchServerMatchingIdentifier(String identifier) { String sql = SELECT + '*' + FROM + ServerTable.TABLE_NAME + - " WHERE (LOWER(" + ServerTable.SERVER_UUID + ") LIKE LOWER(?)" + + WHERE + "(LOWER(" + ServerTable.SERVER_UUID + ") LIKE LOWER(?)" + OR + "LOWER(" + ServerTable.NAME + ") LIKE LOWER(?)" + - OR + ServerTable.SERVER_ID + "=?" + - OR + ServerTable.SERVER_ID + "=?)" + + OR + ServerTable.ID + "=?" + + OR + ServerTable.ID + "=?)" + AND + ServerTable.INSTALLED + "=?" + - " LIMIT 1"; - return new QueryStatement>(sql) { + LIMIT + '1'; + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, identifier); @@ -106,12 +133,12 @@ public class ServerQueries { public Optional processResults(ResultSet set) throws SQLException { if (set.next()) { return Optional.of(new Server( - set.getInt(ServerTable.SERVER_ID), + set.getInt(ServerTable.ID), ServerUUID.fromString(set.getString(ServerTable.SERVER_UUID)), set.getString(ServerTable.NAME), set.getString(ServerTable.WEB_ADDRESS), - set.getBoolean(ServerTable.PROXY) - )); + set.getBoolean(ServerTable.PROXY), + set.getString(ServerTable.PLAN_VERSION))); } return Optional.empty(); } @@ -122,8 +149,8 @@ public class ServerQueries { String sql = SELECT + '*' + FROM + ServerTable.TABLE_NAME + WHERE + ServerTable.INSTALLED + "=?" + AND + ServerTable.PROXY + "=?" + - " LIMIT 1"; - return new QueryStatement>(sql) { + LIMIT + '1'; + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setBoolean(1, true); @@ -134,30 +161,48 @@ public class ServerQueries { public Optional processResults(ResultSet set) throws SQLException { if (set.next()) { return Optional.of(new Server( - set.getInt(ServerTable.SERVER_ID), + set.getInt(ServerTable.ID), ServerUUID.fromString(set.getString(ServerTable.SERVER_UUID)), set.getString(ServerTable.NAME), set.getString(ServerTable.WEB_ADDRESS), - set.getBoolean(ServerTable.PROXY) - )); + set.getBoolean(ServerTable.PROXY), + set.getString(ServerTable.PLAN_VERSION))); } return Optional.empty(); } }; } - public static Query> fetchServerNames() { + public static Query> fetchGameServerNames() { String sql = Select.from(ServerTable.TABLE_NAME, - ServerTable.SERVER_UUID, ServerTable.NAME) + ServerTable.ID, ServerTable.SERVER_UUID, ServerTable.NAME) + .where(ServerTable.PROXY + "=0") .toString(); - return new QueryAllStatement>(sql) { + return new QueryAllStatement<>(sql) { + @Override + public List processResults(ResultSet set) throws SQLException { + List names = new ArrayList<>(); + while (set.next()) { + names.add(Server.getIdentifiableName(set.getString(ServerTable.NAME), set.getInt(ServerTable.ID))); + } + return names; + } + }; + } + + public static Query> fetchServerNames() { + String sql = Select.from(ServerTable.TABLE_NAME, + ServerTable.ID, ServerTable.SERVER_UUID, ServerTable.NAME) + .toString(); + + return new QueryAllStatement<>(sql) { @Override public Map processResults(ResultSet set) throws SQLException { Map names = new HashMap<>(); while (set.next()) { ServerUUID serverUUID = ServerUUID.fromString(set.getString(ServerTable.SERVER_UUID)); - names.put(serverUUID, set.getString(ServerTable.NAME)); + names.put(serverUUID, Server.getIdentifiableName(set.getString(ServerTable.NAME), set.getInt(ServerTable.ID))); } return names; } @@ -168,13 +213,13 @@ public class ServerQueries { if (identifier.isEmpty()) return db -> Collections.emptyList(); String sql = SELECT + '*' + FROM + ServerTable.TABLE_NAME + - " WHERE (LOWER(" + ServerTable.SERVER_UUID + ") LIKE LOWER(?)" + + WHERE + "(LOWER(" + ServerTable.SERVER_UUID + ") LIKE LOWER(?)" + OR + "LOWER(" + ServerTable.NAME + ") LIKE LOWER(?)" + - OR + ServerTable.SERVER_ID + "=?" + - OR + ServerTable.SERVER_ID + "=?)" + + OR + ServerTable.ID + "=?" + + OR + ServerTable.ID + "=?)" + AND + ServerTable.INSTALLED + "=?" + - " LIMIT 1"; - return new QueryStatement>(sql) { + LIMIT + '1'; + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, '%' + identifier + '%'); @@ -190,12 +235,12 @@ public class ServerQueries { List matches = new ArrayList<>(); while (set.next()) { matches.add(new Server( - set.getInt(ServerTable.SERVER_ID), + set.getInt(ServerTable.ID), ServerUUID.fromString(set.getString(ServerTable.SERVER_UUID)), set.getString(ServerTable.NAME), set.getString(ServerTable.WEB_ADDRESS), - set.getBoolean(ServerTable.PROXY) - )); + set.getBoolean(ServerTable.PROXY), + set.getString(ServerTable.PLAN_VERSION))); } return matches; } @@ -205,7 +250,7 @@ public class ServerQueries { public static Query fetchServerCount() { String sql = SELECT + "COUNT(1) as c" + FROM + ServerTable.TABLE_NAME + WHERE + ServerTable.INSTALLED + "=?"; - return new QueryStatement(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setBoolean(1, true); @@ -219,9 +264,9 @@ public class ServerQueries { } public static Query fetchBiggestServerID() { - String sql = SELECT + "MAX(" + ServerTable.SERVER_ID + ") as max_id" + FROM + ServerTable.TABLE_NAME + + String sql = SELECT + "MAX(" + ServerTable.ID + ") as max_id" + FROM + ServerTable.TABLE_NAME + WHERE + ServerTable.INSTALLED + "=?"; - return new QueryStatement(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setBoolean(1, true); @@ -233,4 +278,18 @@ public class ServerQueries { } }; } + + public static Query> fetchServerNamesToUUIDs() { + return db -> Maps.reverse(db.query(fetchServerNames())); + } + + public static Query> fetchServersMatchingIdentifiers(List serverNames) { + return db -> { + Map nameToUUIDMap = db.query(ServerQueries.fetchServerNamesToUUIDs()); + return serverNames.stream() + .map(nameToUUIDMap::get) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + }; + } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/SessionQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/SessionQueries.java index 690c0e0b6..fd05897de 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/SessionQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/SessionQueries.java @@ -18,9 +18,11 @@ package com.djrapitops.plan.storage.database.queries.objects; import com.djrapitops.plan.delivery.domain.DateHolder; import com.djrapitops.plan.delivery.domain.PlayerName; +import com.djrapitops.plan.delivery.domain.ServerIdentifier; import com.djrapitops.plan.delivery.domain.ServerName; import com.djrapitops.plan.delivery.domain.mutators.SessionsMutator; import com.djrapitops.plan.gathering.domain.*; +import com.djrapitops.plan.gathering.domain.event.JoinAddress; import com.djrapitops.plan.identification.Server; import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.queries.Query; @@ -53,12 +55,12 @@ public class SessionQueries { private static final String SELECT_SESSIONS_STATEMENT = SELECT + "s." + SessionsTable.ID + ',' + - "s." + SessionsTable.USER_UUID + ',' + - "s." + SessionsTable.SERVER_UUID + ',' + "u." + UsersTable.USER_NAME + " as name," + + "u." + UsersTable.USER_UUID + ',' + "u_info." + UserInfoTable.REGISTERED + " as registered," + "server." + ServerTable.NAME + " as server_name," + - "server." + ServerTable.SERVER_ID + " as server_id," + + "server." + ServerTable.ID + " as server_id," + + "server." + ServerTable.SERVER_UUID + " as server_uuid," + SessionsTable.SESSION_START + ',' + SessionsTable.SESSION_END + ',' + SessionsTable.MOB_KILLS + ',' + @@ -69,17 +71,21 @@ public class SessionQueries { WorldTimesTable.ADVENTURE + ',' + WorldTimesTable.SPECTATOR + ',' + WorldTable.NAME + ',' + + "j." + JoinAddressTable.JOIN_ADDRESS + " as join_address," + KillsTable.KILLER_UUID + ',' + KillsTable.VICTIM_UUID + ',' + "v." + UsersTable.USER_NAME + " as victim_name, " + + "k." + UsersTable.USER_NAME + " as killer_name, " + KillsTable.DATE + ',' + KillsTable.WEAPON + FROM + SessionsTable.TABLE_NAME + " s" + - INNER_JOIN + UsersTable.TABLE_NAME + " u on u." + UsersTable.USER_UUID + "=s." + SessionsTable.USER_UUID + - INNER_JOIN + ServerTable.TABLE_NAME + " server on server." + ServerTable.SERVER_UUID + "=s." + SessionsTable.SERVER_UUID + - LEFT_JOIN + UserInfoTable.TABLE_NAME + " u_info on (u_info." + UserInfoTable.USER_UUID + "=s." + SessionsTable.USER_UUID + AND + "u_info." + UserInfoTable.SERVER_UUID + "=s." + SessionsTable.SERVER_UUID + ')' + + INNER_JOIN + JoinAddressTable.TABLE_NAME + " j on s." + SessionsTable.JOIN_ADDRESS_ID + "=j." + JoinAddressTable.ID + + INNER_JOIN + UsersTable.TABLE_NAME + " u on u." + UsersTable.ID + "=s." + SessionsTable.USER_ID + + INNER_JOIN + ServerTable.TABLE_NAME + " server on server." + ServerTable.ID + "=s." + SessionsTable.SERVER_ID + + LEFT_JOIN + UserInfoTable.TABLE_NAME + " u_info on (u_info." + UserInfoTable.USER_ID + "=s." + SessionsTable.USER_ID + AND + "u_info." + UserInfoTable.SERVER_ID + "=s." + SessionsTable.SERVER_ID + ')' + LEFT_JOIN + KillsTable.TABLE_NAME + " ON " + "s." + SessionsTable.ID + '=' + KillsTable.TABLE_NAME + '.' + KillsTable.SESSION_ID + LEFT_JOIN + UsersTable.TABLE_NAME + " v on v." + UsersTable.USER_UUID + '=' + KillsTable.VICTIM_UUID + + LEFT_JOIN + UsersTable.TABLE_NAME + " k on k." + UsersTable.USER_UUID + '=' + KillsTable.KILLER_UUID + INNER_JOIN + WorldTimesTable.TABLE_NAME + " ON s." + SessionsTable.ID + '=' + WorldTimesTable.TABLE_NAME + '.' + WorldTimesTable.SESSION_ID + INNER_JOIN + WorldTable.TABLE_NAME + " ON " + WorldTimesTable.TABLE_NAME + '.' + WorldTimesTable.WORLD_ID + '=' + WorldTable.TABLE_NAME + '.' + WorldTable.ID; @@ -93,34 +99,7 @@ public class SessionQueries { public static Query> fetchAllSessions() { String sql = SELECT_SESSIONS_STATEMENT + ORDER_BY_SESSION_START_DESC; - return new QueryAllStatement>(sql, 50000) { - @Override - public List processResults(ResultSet set) throws SQLException { - return extractDataFromSessionSelectStatement(set); - } - }; - } - - /** - * Query the database for Session data of a server with kill and world data. - * - * @param serverUUID UUID of the Plan server. - * @return Map: Player UUID - List of sessions on the server. - */ - public static Query>> fetchSessionsOfServer(ServerUUID serverUUID) { - return db -> SessionsMutator.sortByPlayers(db.query(fetchSessionsOfServerFlat(serverUUID))); - } - - public static QueryStatement> fetchSessionsOfServerFlat(ServerUUID serverUUID) { - String sql = SELECT_SESSIONS_STATEMENT + - WHERE + "s." + SessionsTable.SERVER_UUID + "=?" + - ORDER_BY_SESSION_START_DESC; - return new QueryStatement>(sql, 50000) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setString(1, serverUUID.toString()); - } - + return new QueryAllStatement<>(sql, 50000) { @Override public List processResults(ResultSet set) throws SQLException { return extractDataFromSessionSelectStatement(set); @@ -136,9 +115,9 @@ public class SessionQueries { */ public static Query>> fetchSessionsOfPlayer(UUID playerUUID) { String sql = SELECT_SESSIONS_STATEMENT + - WHERE + "s." + SessionsTable.USER_UUID + "=?" + + WHERE + "s." + SessionsTable.USER_ID + "=" + UsersTable.SELECT_USER_ID + ORDER_BY_SESSION_START_DESC; - return new QueryStatement>>(sql, 50000) { + return new QueryStatement<>(sql, 1000) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, playerUUID.toString()); @@ -162,10 +141,10 @@ public class SessionQueries { Comparator longRecentComparator = (one, two) -> Long.compare(two, one); // Descending order, most recent first. while (set.next()) { - ServerUUID serverUUID = ServerUUID.fromString(set.getString(SessionsTable.SERVER_UUID)); + ServerUUID serverUUID = ServerUUID.fromString(set.getString("server_uuid")); Map> serverSessions = byServer.computeIfAbsent(serverUUID, Maps::create); - UUID playerUUID = UUID.fromString(set.getString(SessionsTable.USER_UUID)); + UUID playerUUID = UUID.fromString(set.getString(UsersTable.USER_UUID)); SortedMap playerSessions = serverSessions.computeIfAbsent(playerUUID, key -> new TreeMap<>(longRecentComparator)); long sessionStart = set.getLong(SessionsTable.SESSION_START); @@ -183,6 +162,7 @@ public class SessionQueries { extraData.put(FinishedSession.Id.class, new FinishedSession.Id(set.getInt(SessionsTable.ID))); extraData.put(MobKillCounter.class, new MobKillCounter(set.getInt(SessionsTable.MOB_KILLS))); extraData.put(DeathCounter.class, new DeathCounter(set.getInt(SessionsTable.DEATHS))); + extraData.put(JoinAddress.class, new JoinAddress(set.getString("join_address"))); Optional existingWorldTimes = extraData.get(WorldTimes.class); Optional existingPlayerKills = extraData.get(PlayerKills.class); @@ -200,30 +180,39 @@ public class SessionQueries { worldTimes.setGMTimesForWorld(worldName, gmTimes); } - if (!existingWorldTimes.isPresent()) extraData.put(WorldTimes.class, worldTimes); + if (existingWorldTimes.isEmpty()) extraData.put(WorldTimes.class, worldTimes); + + ServerName serverName = new ServerName( + Server.getIdentifiableName( + set.getString("server_name"), + set.getInt("server_id") + )); + extraData.put(ServerName.class, serverName); PlayerKills playerKills = existingPlayerKills.orElseGet(PlayerKills::new); String victimName = set.getString("victim_name"); if (victimName != null) { - UUID killer = UUID.fromString(set.getString(KillsTable.KILLER_UUID)); - UUID victim = UUID.fromString(set.getString(KillsTable.VICTIM_UUID)); - long date = set.getLong(KillsTable.DATE); + PlayerKill.Killer killer = new PlayerKill.Killer( + UUID.fromString(set.getString(KillsTable.KILLER_UUID)), + set.getString("killer_name") + ); + PlayerKill.Victim victim = new PlayerKill.Victim( + UUID.fromString(set.getString(KillsTable.VICTIM_UUID)), + victimName + ); + ServerIdentifier serverIdentifier = new ServerIdentifier(serverUUID, serverName); String weapon = set.getString(KillsTable.WEAPON); - PlayerKill newKill = new PlayerKill(killer, victim, weapon, date, victimName); + long date = set.getLong(KillsTable.DATE); + PlayerKill newKill = new PlayerKill(killer, victim, serverIdentifier, weapon, date); if (!playerKills.contains(newKill)) { playerKills.add(newKill); } } - if (!existingPlayerKills.isPresent()) extraData.put(PlayerKills.class, playerKills); + if (existingPlayerKills.isEmpty()) extraData.put(PlayerKills.class, playerKills); extraData.put(PlayerName.class, new PlayerName(set.getString("name"))); - extraData.put(ServerName.class, new ServerName( - Server.getIdentifiableName( - set.getString("server_name"), - set.getInt("server_id") - ))); session.setAsFirstSessionIfMatches(set.getLong("registered")); @@ -241,19 +230,20 @@ public class SessionQueries { public static Query> fetchServerSessionsWithoutKillOrWorldData(long after, long before, ServerUUID serverUUID) { String sql = SELECT + - SessionsTable.ID + ',' + - SessionsTable.USER_UUID + ',' + + SessionsTable.TABLE_NAME + '.' + SessionsTable.ID + ',' + + UsersTable.USER_UUID + ',' + SessionsTable.SESSION_START + ',' + SessionsTable.SESSION_END + ',' + SessionsTable.DEATHS + ',' + SessionsTable.MOB_KILLS + ',' + SessionsTable.AFK_TIME + FROM + SessionsTable.TABLE_NAME + - WHERE + SessionsTable.SERVER_UUID + "=?" + + INNER_JOIN + UsersTable.TABLE_NAME + " u on u." + UsersTable.ID + '=' + SessionsTable.TABLE_NAME + '.' + SessionsTable.USER_ID + + WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + AND + SessionsTable.SESSION_START + ">=?" + AND + SessionsTable.SESSION_START + "<=?"; - return new QueryStatement>(sql, 1000) { + return new QueryStatement<>(sql, 1000) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -265,7 +255,7 @@ public class SessionQueries { public List processResults(ResultSet set) throws SQLException { List sessions = new ArrayList<>(); while (set.next()) { - UUID uuid = UUID.fromString(set.getString(SessionsTable.USER_UUID)); + UUID uuid = UUID.fromString(set.getString(UsersTable.USER_UUID)); long start = set.getLong(SessionsTable.SESSION_START); long end = set.getLong(SessionsTable.SESSION_END); @@ -288,10 +278,10 @@ public class SessionQueries { private static Query fetchLatestSessionStartLimitForServer(ServerUUID serverUUID, int limit) { String sql = SELECT + SessionsTable.SESSION_START + FROM + SessionsTable.TABLE_NAME + - WHERE + SessionsTable.SERVER_UUID + "=?" + + WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + ORDER_BY_SESSION_START_DESC + " LIMIT ?"; - return new QueryStatement(sql, limit) { + return new QueryStatement<>(sql, limit) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -313,7 +303,7 @@ public class SessionQueries { String sql = SELECT + SessionsTable.SESSION_START + FROM + SessionsTable.TABLE_NAME + ORDER_BY_SESSION_START_DESC + " LIMIT ?"; - return new QueryStatement(sql, limit) { + return new QueryStatement<>(sql, limit) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setInt(1, limit); @@ -332,7 +322,7 @@ public class SessionQueries { public static Query> fetchLatestSessionsOfServer(ServerUUID serverUUID, int limit) { String sql = SELECT_SESSIONS_STATEMENT + - WHERE + "s." + SessionsTable.SERVER_UUID + "=?" + + WHERE + "s." + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + AND + "s." + SessionsTable.SESSION_START + ">=?" + ORDER_BY_SESSION_START_DESC; @@ -356,7 +346,7 @@ public class SessionQueries { public static Query> fetchLatestSessions(int limit) { String sql = SELECT_SESSIONS_STATEMENT // Fix for "First Session" icons in the Most recent sessions on network page - .replace(LEFT_JOIN + UserInfoTable.TABLE_NAME + " u_info on (u_info." + UserInfoTable.USER_UUID + "=s." + SessionsTable.USER_UUID + AND + "u_info." + UserInfoTable.SERVER_UUID + "=s." + SessionsTable.SERVER_UUID + ')', "") + .replace(LEFT_JOIN + UserInfoTable.TABLE_NAME + " u_info on (u_info." + UserInfoTable.USER_ID + "=s." + SessionsTable.USER_ID + AND + "u_info." + UserInfoTable.SERVER_ID + "=s." + SessionsTable.SERVER_ID + ')', "") .replace("u_info", "u") + WHERE + "s." + SessionsTable.SESSION_START + ">=?" + ORDER_BY_SESSION_START_DESC; @@ -379,10 +369,10 @@ public class SessionQueries { public static Query sessionCount(long after, long before, ServerUUID serverUUID) { String sql = SELECT + "COUNT(1) as count" + FROM + SessionsTable.TABLE_NAME + - WHERE + SessionsTable.SERVER_UUID + "=?" + + WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + AND + SessionsTable.SESSION_END + ">=?" + AND + SessionsTable.SESSION_START + "<=?"; - return new QueryStatement(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -402,7 +392,7 @@ public class SessionQueries { FROM + SessionsTable.TABLE_NAME + WHERE + SessionsTable.SESSION_END + ">=?" + AND + SessionsTable.SESSION_START + "<=?"; - return new QueryStatement(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setLong(1, after); @@ -435,7 +425,7 @@ public class SessionQueries { FROM + SessionsTable.TABLE_NAME + WHERE + SessionsTable.SESSION_END + "<=?" + AND + SessionsTable.SESSION_START + ">=?" + - AND + SessionsTable.SERVER_UUID + "=?" + + AND + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + GROUP_BY + "date"; return database.query(new QueryStatement>(selectSessionsPerDay, 100) { @@ -462,10 +452,10 @@ public class SessionQueries { public static Query playtime(long after, long before, ServerUUID serverUUID) { String sql = SELECT + "SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + ") as playtime" + FROM + SessionsTable.TABLE_NAME + - WHERE + SessionsTable.SERVER_UUID + "=?" + + WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + AND + SessionsTable.SESSION_END + ">=?" + AND + SessionsTable.SESSION_START + "<=?"; - return new QueryStatement(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -481,13 +471,14 @@ public class SessionQueries { } public static Query> playtimeOfPlayer(long after, long before, UUID playerUUID) { - String sql = SELECT + SessionsTable.SERVER_UUID + ",SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + ") as playtime" + + String sql = SELECT + ServerTable.SERVER_UUID + ",SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + ") as playtime" + FROM + SessionsTable.TABLE_NAME + - WHERE + SessionsTable.USER_UUID + "=?" + + INNER_JOIN + ServerTable.TABLE_NAME + " se on se." + ServerTable.ID + '=' + SessionsTable.TABLE_NAME + '.' + SessionsTable.SERVER_ID + + WHERE + SessionsTable.USER_ID + "=" + UsersTable.SELECT_USER_ID + AND + SessionsTable.SESSION_END + ">=?" + AND + SessionsTable.SESSION_START + "<=?" + - GROUP_BY + SessionsTable.SERVER_UUID; - return new QueryStatement>(sql) { + GROUP_BY + SessionsTable.SERVER_ID; + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, playerUUID.toString()); @@ -499,7 +490,7 @@ public class SessionQueries { public Map processResults(ResultSet set) throws SQLException { Map playtimeOfPlayer = new HashMap<>(); while (set.next()) { - playtimeOfPlayer.put(ServerUUID.fromString(set.getString(SessionsTable.SERVER_UUID)), set.getLong("playtime")); + playtimeOfPlayer.put(ServerUUID.fromString(set.getString(ServerTable.SERVER_UUID)), set.getLong("playtime")); } return playtimeOfPlayer; } @@ -511,7 +502,7 @@ public class SessionQueries { FROM + SessionsTable.TABLE_NAME + WHERE + SessionsTable.SESSION_END + ">=?" + AND + SessionsTable.SESSION_START + "<=?"; - return new QueryStatement(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setLong(1, after); @@ -544,7 +535,7 @@ public class SessionQueries { FROM + SessionsTable.TABLE_NAME + WHERE + SessionsTable.SESSION_END + "<=?" + AND + SessionsTable.SESSION_START + ">=?" + - AND + SessionsTable.SERVER_UUID + "=?" + + AND + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + GROUP_BY + "date"; return database.query(new QueryStatement>(selectPlaytimePerDay, 100) { @@ -578,7 +569,7 @@ public class SessionQueries { FROM + SessionsTable.TABLE_NAME + WHERE + SessionsTable.SESSION_END + "<=?" + AND + SessionsTable.SESSION_START + ">=?" + - AND + SessionsTable.SERVER_UUID + "=?" + + AND + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + GROUP_BY + "date"; String selectAverage = SELECT + "AVG(playtime) as average" + FROM + '(' + selectPlaytimePerDay + ") q1"; @@ -602,13 +593,13 @@ public class SessionQueries { public static Query averagePlaytimePerPlayer(long after, long before, ServerUUID serverUUID) { return database -> { String selectPlaytimePerPlayer = SELECT + - SessionsTable.USER_UUID + "," + + SessionsTable.USER_ID + "," + "SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + ") as playtime" + FROM + SessionsTable.TABLE_NAME + WHERE + SessionsTable.SESSION_END + "<=?" + AND + SessionsTable.SESSION_START + ">=?" + - AND + SessionsTable.SERVER_UUID + "=?" + - GROUP_BY + SessionsTable.USER_UUID; + AND + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + + GROUP_BY + SessionsTable.USER_ID; String selectAverage = SELECT + "AVG(playtime) as average" + FROM + '(' + selectPlaytimePerPlayer + ") q1"; return database.query(new QueryStatement(selectAverage, 100) { @@ -637,12 +628,12 @@ public class SessionQueries { public static Query averagePlaytimePerPlayer(long after, long before) { return database -> { String selectPlaytimePerPlayer = SELECT + - SessionsTable.USER_UUID + "," + + SessionsTable.USER_ID + "," + "SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + ") as playtime" + FROM + SessionsTable.TABLE_NAME + WHERE + SessionsTable.SESSION_END + "<=?" + AND + SessionsTable.SESSION_START + ">=?" + - GROUP_BY + SessionsTable.USER_UUID; + GROUP_BY + SessionsTable.USER_ID; String selectAverage = SELECT + "AVG(playtime) as average" + FROM + '(' + selectPlaytimePerPlayer + ") q1"; return database.query(new QueryStatement(selectAverage, 100) { @@ -663,13 +654,13 @@ public class SessionQueries { public static Query averageAfkPerPlayer(long after, long before, ServerUUID serverUUID) { return database -> { String selectAfkPerPlayer = SELECT + - SessionsTable.USER_UUID + "," + + SessionsTable.USER_ID + "," + "SUM(" + SessionsTable.AFK_TIME + ") as afk" + FROM + SessionsTable.TABLE_NAME + WHERE + SessionsTable.SESSION_END + "<=?" + AND + SessionsTable.SESSION_START + ">=?" + - AND + SessionsTable.SERVER_UUID + "=?" + - GROUP_BY + SessionsTable.USER_UUID; + AND + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + + GROUP_BY + SessionsTable.USER_ID; String selectAverage = SELECT + "AVG(afk) as average" + FROM + '(' + selectAfkPerPlayer + ") q1"; return database.query(new QueryStatement(selectAverage, 100) { @@ -698,12 +689,12 @@ public class SessionQueries { public static Query averageAfkPerPlayer(long after, long before) { return database -> { String selectAfkPerPlayer = SELECT + - SessionsTable.USER_UUID + "," + + SessionsTable.USER_ID + "," + "SUM(" + SessionsTable.AFK_TIME + ") as afk" + FROM + SessionsTable.TABLE_NAME + WHERE + SessionsTable.SESSION_END + "<=?" + AND + SessionsTable.SESSION_START + ">=?" + - GROUP_BY + SessionsTable.USER_UUID; + GROUP_BY + SessionsTable.USER_ID; String selectAverage = SELECT + "AVG(afk) as average" + FROM + '(' + selectAfkPerPlayer + ") q1"; return database.query(new QueryStatement(selectAverage, 100) { @@ -724,10 +715,10 @@ public class SessionQueries { public static Query afkTime(long after, long before, ServerUUID serverUUID) { String sql = SELECT + "SUM(" + SessionsTable.AFK_TIME + ") as afk_time" + FROM + SessionsTable.TABLE_NAME + - WHERE + SessionsTable.SERVER_UUID + "=?" + + WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + AND + SessionsTable.SESSION_END + ">=?" + AND + SessionsTable.SESSION_START + "<=?"; - return new QueryStatement(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -747,7 +738,7 @@ public class SessionQueries { FROM + SessionsTable.TABLE_NAME + WHERE + SessionsTable.SESSION_END + ">=?" + AND + SessionsTable.SESSION_START + "<=?"; - return new QueryStatement(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setLong(1, after); @@ -764,14 +755,14 @@ public class SessionQueries { public static Query> playtimePerServer(long after, long before) { String sql = SELECT + "SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + ") as playtime," + - "s." + ServerTable.SERVER_ID + ',' + + "s." + ServerTable.ID + ',' + "s." + ServerTable.NAME + FROM + SessionsTable.TABLE_NAME + - INNER_JOIN + ServerTable.TABLE_NAME + " s on s." + ServerTable.SERVER_UUID + '=' + SessionsTable.TABLE_NAME + '.' + SessionsTable.SERVER_UUID + + INNER_JOIN + ServerTable.TABLE_NAME + " s on s." + ServerTable.ID + '=' + SessionsTable.TABLE_NAME + '.' + SessionsTable.SERVER_ID + WHERE + SessionsTable.SESSION_END + ">=?" + AND + SessionsTable.SESSION_START + "<=?" + - GROUP_BY + "s." + ServerTable.SERVER_ID; - return new QueryStatement>(sql, 100) { + GROUP_BY + "s." + ServerTable.ID; + return new QueryStatement<>(sql, 100) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setLong(1, after); @@ -784,7 +775,7 @@ public class SessionQueries { while (set.next()) { String name = Server.getIdentifiableName( set.getString(ServerTable.NAME), - set.getInt(ServerTable.SERVER_ID) + set.getInt(ServerTable.ID) ); playtimePerServer.put(name, set.getLong("playtime")); } @@ -796,9 +787,9 @@ public class SessionQueries { public static Query lastSeen(UUID playerUUID, ServerUUID serverUUID) { String sql = SELECT + "MAX(" + SessionsTable.SESSION_END + ") as last_seen" + FROM + SessionsTable.TABLE_NAME + - WHERE + SessionsTable.USER_UUID + "=?" + - AND + SessionsTable.SERVER_UUID + "=?"; - return new QueryStatement(sql) { + WHERE + SessionsTable.USER_ID + "=" + UsersTable.SELECT_USER_ID + + AND + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID; + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, playerUUID.toString()); @@ -816,10 +807,10 @@ public class SessionQueries { String sql = SELECT + "SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + '-' + SessionsTable.AFK_TIME + ") as playtime" + FROM + SessionsTable.TABLE_NAME + - WHERE + SessionsTable.SERVER_UUID + "=?" + + WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + AND + SessionsTable.SESSION_END + ">=?" + AND + SessionsTable.SESSION_START + "<=?"; - return new QueryStatement(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -834,12 +825,18 @@ public class SessionQueries { }; } - public static Query> uuidsOfPlayedBetween(long after, long before) { - String sql = SELECT + DISTINCT + SessionsTable.USER_UUID + + public static Query> userIdsOfPlayedBetween(long after, long before, List serverUUIDs) { + String selectServerIds = SELECT + ServerTable.ID + + FROM + ServerTable.TABLE_NAME + + WHERE + ServerTable.SERVER_UUID + " IN ('" + new TextStringBuilder().appendWithSeparators(serverUUIDs, "','") + "')"; + + String sql = SELECT + DISTINCT + "u." + UsersTable.ID + FROM + SessionsTable.TABLE_NAME + + INNER_JOIN + UsersTable.TABLE_NAME + " u on u." + UsersTable.ID + '=' + SessionsTable.USER_ID + WHERE + SessionsTable.SESSION_END + ">=?" + - AND + SessionsTable.SESSION_START + "<=?"; - return new QueryStatement>(sql) { + AND + SessionsTable.SESSION_START + "<=?" + + (serverUUIDs.isEmpty() ? "" : AND + SessionsTable.SERVER_ID + " IN (" + selectServerIds + ")"); + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setLong(1, after); @@ -847,17 +844,22 @@ public class SessionQueries { } @Override - public Set processResults(ResultSet set) throws SQLException { - Set uuids = new HashSet<>(); + public Set processResults(ResultSet set) throws SQLException { + Set userIds = new HashSet<>(); while (set.next()) { - uuids.add(UUID.fromString(set.getString(SessionsTable.USER_UUID))); + userIds.add(set.getInt(UsersTable.ID)); } - return uuids; + return userIds; } }; } - public static Query> summaryOfPlayers(Set playerUUIDs, long after, long before) { + public static Query> summaryOfPlayers(Set userIds, List serverUUIDs, long after, long before) { + String uuidsInSet = " IN (" + new TextStringBuilder().appendWithSeparators(userIds, ",") + ")"; + String selectServerIds = SELECT + ServerTable.ID + + FROM + ServerTable.TABLE_NAME + + WHERE + ServerTable.SERVER_UUID + " IN ('" + new TextStringBuilder().appendWithSeparators(serverUUIDs, "','") + "')"; + String selectAggregates = SELECT + "SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + ") as playtime," + "SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + '-' + SessionsTable.AFK_TIME + ") as active_playtime," + @@ -865,10 +867,10 @@ public class SessionQueries { FROM + SessionsTable.TABLE_NAME + WHERE + SessionsTable.SESSION_START + ">?" + AND + SessionsTable.SESSION_END + ">(selectAggregates) { + return new QueryStatement<>(selectAggregates) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setLong(1, after); @@ -881,7 +883,7 @@ public class SessionQueries { long sessionCount = set.getLong("session_count"); long playtime = set.getLong("playtime"); long activePlaytime = set.getLong("active_playtime"); - int playerCount = playerUUIDs.size(); + int playerCount = userIds.size(); return Maps.builder(String.class, Long.class) .put("total_playtime", playtime) .put("average_playtime", playerCount != 0 ? playtime / playerCount : -1L) @@ -903,7 +905,7 @@ public class SessionQueries { public static Query earliestSessionStart() { String sql = SELECT + "MIN(" + SessionsTable.SESSION_START + ") as m" + FROM + SessionsTable.TABLE_NAME; - return new QueryAllStatement(sql) { + return new QueryAllStatement<>(sql) { @Override public Long processResults(ResultSet set) throws SQLException { return set.next() ? set.getLong("m") : -1L; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/TPSQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/TPSQueries.java index 98e8a4698..8b73fbda1 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/TPSQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/TPSQueries.java @@ -23,6 +23,7 @@ import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.queries.Query; import com.djrapitops.plan.storage.database.queries.QueryStatement; import com.djrapitops.plan.storage.database.sql.tables.ServerTable; +import com.djrapitops.plan.utilities.Benchmark; import com.djrapitops.plan.utilities.java.Lists; import java.sql.PreparedStatement; @@ -58,7 +59,7 @@ public class TPSQueries { max("t." + CHUNKS) + " as " + CHUNKS + ',' + max("t." + FREE_DISK) + " as " + FREE_DISK + FROM + TABLE_NAME + " t" + - WHERE + SERVER_ID + "=" + ServerTable.STATEMENT_SELECT_SERVER_ID + + WHERE + SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + AND + DATE + ">=?" + AND + DATE + "> fetchTPSDataOfServer(long after, long before, ServerUUID serverUUID) { String sql = SELECT + "*" + FROM + TABLE_NAME + - WHERE + SERVER_ID + "=" + ServerTable.STATEMENT_SELECT_SERVER_ID + + WHERE + SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + AND + DATE + ">=?" + AND + DATE + "<=?" + ORDER_BY + DATE; - return new QueryStatement>(sql, 50000) { + return new QueryStatement<>(sql, 50000) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -129,10 +130,10 @@ public class TPSQueries { String sql = SELECT + min(DATE) + " as " + DATE + ',' + max(PLAYERS_ONLINE) + " as " + PLAYERS_ONLINE + FROM + TABLE_NAME + - WHERE + SERVER_ID + "=" + ServerTable.STATEMENT_SELECT_SERVER_ID + + WHERE + SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + GROUP_BY + floor(DATE + "/?"); - return new QueryStatement>>(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -151,11 +152,11 @@ public class TPSQueries { public static Query>> fetchPlayersOnlineOfServer(long after, long before, ServerUUID serverUUID) { String sql = SELECT + ServerTable.SERVER_UUID + ',' + DATE + ',' + PLAYERS_ONLINE + FROM + TABLE_NAME + - INNER_JOIN + ServerTable.TABLE_NAME + " on " + ServerTable.TABLE_NAME + '.' + ServerTable.SERVER_ID + '=' + SERVER_ID + + INNER_JOIN + ServerTable.TABLE_NAME + " on " + ServerTable.TABLE_NAME + '.' + ServerTable.ID + '=' + SERVER_ID + WHERE + ServerTable.SERVER_UUID + "=?" + AND + DATE + "?"; - return new QueryStatement>>(sql, 1000) { + return new QueryStatement<>(sql, 1000) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -176,15 +177,15 @@ public class TPSQueries { }; } - public static Query>> fetchTPSDataOfAllServersBut(long after, long before, ServerUUID leaveOut) { - String sql = SELECT + '*' + + public static Query>> fetchTPSDataOfAllServersBut(long after, long before, ServerUUID leaveOut) { + String sql = SELECT + DATE + ',' + TPS + ',' + PLAYERS_ONLINE + ',' + SERVER_ID + FROM + TABLE_NAME + - INNER_JOIN + ServerTable.TABLE_NAME + " on " + ServerTable.TABLE_NAME + '.' + ServerTable.SERVER_ID + '=' + SERVER_ID + + INNER_JOIN + ServerTable.TABLE_NAME + " on " + ServerTable.TABLE_NAME + '.' + ServerTable.ID + '=' + SERVER_ID + WHERE + ServerTable.SERVER_UUID + "!=?" + AND + ServerTable.INSTALLED + "=?" + AND + DATE + "?"; - return new QueryStatement>>(sql, 1000) { + return new QueryStatement<>(sql, 5000) { @Override public void prepare(PreparedStatement statement) throws SQLException { if (leaveOut != null) { @@ -198,12 +199,16 @@ public class TPSQueries { } @Override - public Map> processResults(ResultSet set) throws SQLException { - Map> byServer = new HashMap<>(); + public Map> processResults(ResultSet set) throws SQLException { + Map> byServer = new HashMap<>(); while (set.next()) { - ServerUUID serverUUID = ServerUUID.fromString(set.getString(ServerTable.SERVER_UUID)); - List ofServer = byServer.computeIfAbsent(serverUUID, Lists::create); - ofServer.add(extractTPS(set)); + Integer serverUID = set.getInt(SERVER_ID); + List ofServer = byServer.computeIfAbsent(serverUID, Lists::create); + ofServer.add(TPSBuilder.get() + .date(set.getLong(DATE)) + .tps(set.getDouble(TPS)) + .playersOnline(set.getInt(PLAYERS_ONLINE)) + .toTPS()); } return byServer; } @@ -211,17 +216,17 @@ public class TPSQueries { } public static Query>> fetchPeakPlayerCount(ServerUUID serverUUID, long afterDate) { - String subQuery = '(' + SELECT + "MAX(" + PLAYERS_ONLINE + ')' + FROM + TABLE_NAME + WHERE + SERVER_ID + "=" + ServerTable.STATEMENT_SELECT_SERVER_ID + + String subQuery = '(' + SELECT + "MAX(" + PLAYERS_ONLINE + ')' + FROM + TABLE_NAME + WHERE + SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + AND + DATE + ">= ?)"; String sql = SELECT + DATE + ',' + PLAYERS_ONLINE + FROM + TABLE_NAME + - WHERE + SERVER_ID + "=" + ServerTable.STATEMENT_SELECT_SERVER_ID + + WHERE + SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + AND + DATE + ">= ?" + AND + PLAYERS_ONLINE + "=" + subQuery + ORDER_BY + DATE + " DESC LIMIT 1"; - return new QueryStatement>>(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -243,6 +248,7 @@ public class TPSQueries { }; } + @Benchmark.Slow("1s") public static Query>> fetchAllTimePeakPlayerCount(ServerUUID serverUUID) { return fetchPeakPlayerCount(serverUUID, 0); } @@ -250,10 +256,10 @@ public class TPSQueries { public static Query> fetchLatestTPSEntryForServer(ServerUUID serverUUID) { String sql = SELECT + "*" + FROM + TABLE_NAME + - WHERE + SERVER_ID + '=' + ServerTable.STATEMENT_SELECT_SERVER_ID + + WHERE + SERVER_ID + '=' + ServerTable.SELECT_SERVER_ID + ORDER_BY + DATE + " DESC LIMIT 1"; - return new QueryStatement>(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -280,11 +286,11 @@ public class TPSQueries { public static Query averageTPS(long after, long before, ServerUUID serverUUID) { String sql = SELECT + "AVG(" + TPS + ") as average" + FROM + TABLE_NAME + - WHERE + SERVER_ID + '=' + ServerTable.STATEMENT_SELECT_SERVER_ID + + WHERE + SERVER_ID + '=' + ServerTable.SELECT_SERVER_ID + AND + TPS + ">=0" + AND + DATE + "?"; - return new QueryStatement(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -301,11 +307,11 @@ public class TPSQueries { public static Query averageCPU(long after, long before, ServerUUID serverUUID) { String sql = SELECT + "AVG(" + CPU_USAGE + ") as average" + FROM + TABLE_NAME + - WHERE + SERVER_ID + '=' + ServerTable.STATEMENT_SELECT_SERVER_ID + + WHERE + SERVER_ID + '=' + ServerTable.SELECT_SERVER_ID + AND + CPU_USAGE + ">=0" + AND + DATE + "?"; - return new QueryStatement(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -322,11 +328,11 @@ public class TPSQueries { public static Query averageRAM(long after, long before, ServerUUID serverUUID) { String sql = SELECT + "AVG(" + RAM_USAGE + ") as average" + FROM + TABLE_NAME + - WHERE + SERVER_ID + '=' + ServerTable.STATEMENT_SELECT_SERVER_ID + + WHERE + SERVER_ID + '=' + ServerTable.SELECT_SERVER_ID + AND + RAM_USAGE + ">=0" + AND + DATE + "?"; - return new QueryStatement(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -343,11 +349,11 @@ public class TPSQueries { public static Query averageChunks(long after, long before, ServerUUID serverUUID) { String sql = SELECT + "AVG(" + CHUNKS + ") as average" + FROM + TABLE_NAME + - WHERE + SERVER_ID + '=' + ServerTable.STATEMENT_SELECT_SERVER_ID + + WHERE + SERVER_ID + '=' + ServerTable.SELECT_SERVER_ID + AND + CHUNKS + ">=0" + AND + DATE + "?"; - return new QueryStatement(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -364,11 +370,11 @@ public class TPSQueries { public static Query averageEntities(long after, long before, ServerUUID serverUUID) { String sql = SELECT + "AVG(" + ENTITIES + ") as average" + FROM + TABLE_NAME + - WHERE + SERVER_ID + '=' + ServerTable.STATEMENT_SELECT_SERVER_ID + + WHERE + SERVER_ID + '=' + ServerTable.SELECT_SERVER_ID + AND + ENTITIES + ">=0" + AND + DATE + "?"; - return new QueryStatement(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -385,11 +391,11 @@ public class TPSQueries { public static Query maxFreeDisk(long after, long before, ServerUUID serverUUID) { String sql = SELECT + "MAX(" + FREE_DISK + ") as free" + FROM + TABLE_NAME + - WHERE + SERVER_ID + '=' + ServerTable.STATEMENT_SELECT_SERVER_ID + + WHERE + SERVER_ID + '=' + ServerTable.SELECT_SERVER_ID + AND + FREE_DISK + ">=0" + AND + DATE + "?"; - return new QueryStatement(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -406,11 +412,11 @@ public class TPSQueries { public static Query minFreeDisk(long after, long before, ServerUUID serverUUID) { String sql = SELECT + "MIN(" + FREE_DISK + ") as free" + FROM + TABLE_NAME + - WHERE + SERVER_ID + '=' + ServerTable.STATEMENT_SELECT_SERVER_ID + + WHERE + SERVER_ID + '=' + ServerTable.SELECT_SERVER_ID + AND + FREE_DISK + ">=0" + AND + DATE + "?"; - return new QueryStatement(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -427,11 +433,11 @@ public class TPSQueries { public static Query averageFreeDisk(long after, long before, ServerUUID serverUUID) { String sql = SELECT + "AVG(" + FREE_DISK + ") as average" + FROM + TABLE_NAME + - WHERE + SERVER_ID + '=' + ServerTable.STATEMENT_SELECT_SERVER_ID + + WHERE + SERVER_ID + '=' + ServerTable.SELECT_SERVER_ID + AND + FREE_DISK + ">=0" + AND + DATE + "?"; - return new QueryStatement(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -445,4 +451,81 @@ public class TPSQueries { } }; } + + public static Query>> fetchTPSDataOfServers(long after, long before, Collection serverUUIDs) { + String sql = SELECT + "*" + FROM + TABLE_NAME + + WHERE + SERVER_ID + " IN " + ServerTable.selectServerIds(serverUUIDs) + + AND + DATE + ">=?" + + AND + DATE + "<=?" + + ORDER_BY + DATE; + return new QueryStatement<>(sql, 50000) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setLong(1, after); + statement.setLong(2, before); + } + + @Override + public Map> processResults(ResultSet set) throws SQLException { + Map> data = new HashMap<>(); + while (set.next()) { + int serverId = set.getInt(SERVER_ID); + data.computeIfAbsent(serverId, Lists::create) + .add(extractTPS(set)); + } + return data; + } + }; + } + + @Benchmark.Slow("1s") + public static Query> fetchLatestServerStartTime(ServerUUID serverUUID, long dataGapThreshold) { + String selectPreviousRowNumber = SELECT + + "-1+ROW_NUMBER() over (ORDER BY " + DATE + ") AS previous_rn, " + + DATE + " AS d1" + + FROM + TABLE_NAME + + WHERE + SERVER_ID + '=' + ServerTable.SELECT_SERVER_ID + + ORDER_BY + "d1 DESC"; + String selectRowNumber = SELECT + + "ROW_NUMBER() over (ORDER BY " + DATE + ") AS rn, " + + DATE + " AS previous_date" + + FROM + TABLE_NAME + + WHERE + SERVER_ID + '=' + ServerTable.SELECT_SERVER_ID + + ORDER_BY + "previous_date DESC"; + String selectFirstEntryDate = SELECT + "MIN(" + DATE + ") as start_time" + + FROM + TABLE_NAME + + WHERE + SERVER_ID + '=' + ServerTable.SELECT_SERVER_ID; + // Finds the start time since difference between d1 and previous date is a gap, + // so d1 is always first entry after a gap in the data. MAX finds the latest. + // Union ensures if there are no gaps to use the first date recorded. + String selectStartTime = SELECT + + "MAX(d1) AS start_time" + + FROM + "(" + selectPreviousRowNumber + ") t1" + + INNER_JOIN + + "(" + selectRowNumber + ") t2 ON t1.previous_rn=t2.rn" + + WHERE + "d1 - previous_date > ?" + + UNION + selectFirstEntryDate; + + return new QueryStatement<>(selectStartTime) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setString(1, serverUUID.toString()); + statement.setString(2, serverUUID.toString()); + statement.setLong(3, dataGapThreshold); + statement.setString(4, serverUUID.toString()); + } + + @Override + public Optional processResults(ResultSet set) throws SQLException { + long startTime = 0; + while (set.next()) { + long gotStartTime = set.getLong("start_time"); + if (!set.wasNull()) { + startTime = Math.max(startTime, gotStartTime); + } + } + return startTime != 0 ? Optional.of(startTime) : Optional.empty(); + } + }; + } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/UserIdentifierQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/UserIdentifierQueries.java index d5d72cabb..330d21345 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/UserIdentifierQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/UserIdentifierQueries.java @@ -22,6 +22,7 @@ import com.djrapitops.plan.storage.database.queries.QueryAllStatement; import com.djrapitops.plan.storage.database.queries.QueryStatement; import com.djrapitops.plan.storage.database.sql.building.Select; import com.djrapitops.plan.storage.database.sql.tables.NicknamesTable; +import com.djrapitops.plan.storage.database.sql.tables.ServerTable; import com.djrapitops.plan.storage.database.sql.tables.UserInfoTable; import com.djrapitops.plan.storage.database.sql.tables.UsersTable; @@ -51,7 +52,7 @@ public class UserIdentifierQueries { public static Query> fetchAllPlayerUUIDs() { String sql = Select.from(UsersTable.TABLE_NAME, UsersTable.USER_UUID).toString(); - return new QueryAllStatement>(sql, 20000) { + return new QueryAllStatement<>(sql, 20000) { @Override public Set processResults(ResultSet set) throws SQLException { Set playerUUIDs = new HashSet<>(); @@ -75,9 +76,9 @@ public class UserIdentifierQueries { UsersTable.TABLE_NAME + '.' + UsersTable.USER_UUID + ',' + FROM + UsersTable.TABLE_NAME + INNER_JOIN + UserInfoTable.TABLE_NAME + " on " + - UsersTable.TABLE_NAME + '.' + UsersTable.USER_UUID + "=" + UserInfoTable.TABLE_NAME + '.' + UserInfoTable.USER_UUID + - WHERE + UserInfoTable.SERVER_UUID + "=?"; - return new QueryStatement>(sql, 1000) { + UsersTable.TABLE_NAME + '.' + UsersTable.ID + "=" + UserInfoTable.TABLE_NAME + '.' + UserInfoTable.USER_ID + + WHERE + UserInfoTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID; + return new QueryStatement<>(sql, 1000) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -103,7 +104,7 @@ public class UserIdentifierQueries { public static Query> fetchAllPlayerNames() { String sql = Select.from(UsersTable.TABLE_NAME, UsersTable.USER_UUID, UsersTable.USER_NAME).toString(); - return new QueryAllStatement>(sql, 20000) { + return new QueryAllStatement<>(sql, 20000) { @Override public Map processResults(ResultSet set) throws SQLException { Map names = new HashMap<>(); @@ -129,7 +130,7 @@ public class UserIdentifierQueries { .where("UPPER(" + UsersTable.USER_NAME + ")=UPPER(?)") .toString(); - return new QueryStatement>(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, playerName); @@ -155,7 +156,7 @@ public class UserIdentifierQueries { public static Query> fetchPlayerNameOf(UUID playerUUID) { String sql = Select.from(UsersTable.TABLE_NAME, UsersTable.USER_NAME).where(UsersTable.USER_UUID + "=?").toString(); - return new QueryStatement>(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, playerUUID.toString()); @@ -182,7 +183,7 @@ public class UserIdentifierQueries { UsersTable.TABLE_NAME + '.' + UsersTable.USER_UUID + "=" + NicknamesTable.TABLE_NAME + '.' + NicknamesTable.USER_UUID + WHERE + "LOWER(" + NicknamesTable.NICKNAME + ") LIKE LOWER(?)"; - return new QueryStatement>(sql, 5000) { + return new QueryStatement<>(sql, 5000) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, '%' + searchFor + '%'); @@ -202,4 +203,38 @@ public class UserIdentifierQueries { } }; } + + public static Query> fetchAllUserIds() { + String sql = Select.from(UsersTable.TABLE_NAME, UsersTable.ID).toString(); + + return new QueryAllStatement<>(sql, 2000) { + @Override + public Set processResults(ResultSet set) throws SQLException { + Set playerUUIDs = new HashSet<>(); + while (set.next()) { + playerUUIDs.add(set.getInt(UsersTable.ID)); + } + return playerUUIDs; + } + }; + } + + public static Query> fetchUserId(UUID playerUUID) { + String sql = Select.from(UsersTable.TABLE_NAME, UsersTable.ID).where(UsersTable.USER_UUID + "=?").toString(); + + return new QueryStatement<>(sql) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setString(1, playerUUID.toString()); + } + + @Override + public Optional processResults(ResultSet set) throws SQLException { + if (set.next()) { + return Optional.of(set.getInt(UsersTable.ID)); + } + return Optional.empty(); + } + }; + } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/UserInfoQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/UserInfoQueries.java index 94f7feb1d..cb8d64bb8 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/UserInfoQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/UserInfoQueries.java @@ -21,7 +21,9 @@ import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.queries.Query; import com.djrapitops.plan.storage.database.queries.QueryAllStatement; import com.djrapitops.plan.storage.database.queries.QueryStatement; +import com.djrapitops.plan.storage.database.sql.tables.ServerTable; import com.djrapitops.plan.storage.database.sql.tables.UserInfoTable; +import com.djrapitops.plan.storage.database.sql.tables.UsersTable; import com.djrapitops.plan.utilities.java.Lists; import org.apache.commons.text.TextStringBuilder; @@ -52,21 +54,23 @@ public class UserInfoQueries { */ public static Query>> fetchAllUserInformation() { String sql = SELECT + - UserInfoTable.REGISTERED + ',' + + "ux." + UserInfoTable.REGISTERED + ',' + UserInfoTable.BANNED + ',' + UserInfoTable.OP + ',' + - UserInfoTable.USER_UUID + ',' + - UserInfoTable.SERVER_UUID + ',' + + "u." + UsersTable.USER_UUID + ',' + + "s." + ServerTable.SERVER_UUID + " as server_uuid," + UserInfoTable.JOIN_ADDRESS + - FROM + UserInfoTable.TABLE_NAME; + FROM + UserInfoTable.TABLE_NAME + " ux" + + INNER_JOIN + UsersTable.TABLE_NAME + " u on u." + UsersTable.ID + '=' + "ux." + UserInfoTable.USER_ID + + INNER_JOIN + ServerTable.TABLE_NAME + " s on s." + ServerTable.ID + '=' + "ux." + UserInfoTable.SERVER_ID; - return new QueryAllStatement>>(sql, 50000) { + return new QueryAllStatement<>(sql, 50000) { @Override public Map> processResults(ResultSet set) throws SQLException { Map> serverMap = new HashMap<>(); while (set.next()) { - ServerUUID serverUUID = ServerUUID.fromString(set.getString(UserInfoTable.SERVER_UUID)); - UUID uuid = UUID.fromString(set.getString(UserInfoTable.USER_UUID)); + ServerUUID serverUUID = ServerUUID.fromString(set.getString("server_uuid")); + UUID uuid = UUID.fromString(set.getString(UsersTable.USER_UUID)); List userInfos = serverMap.computeIfAbsent(serverUUID, Lists::create); @@ -93,12 +97,13 @@ public class UserInfoQueries { UserInfoTable.TABLE_NAME + '.' + UserInfoTable.REGISTERED + ',' + UserInfoTable.BANNED + ',' + UserInfoTable.OP + ',' + - UserInfoTable.SERVER_UUID + ',' + + ServerTable.SERVER_UUID + ',' + UserInfoTable.JOIN_ADDRESS + FROM + UserInfoTable.TABLE_NAME + - WHERE + UserInfoTable.TABLE_NAME + '.' + UserInfoTable.USER_UUID + "=?"; + INNER_JOIN + ServerTable.TABLE_NAME + " s on s." + ServerTable.ID + '=' + UserInfoTable.TABLE_NAME + '.' + UserInfoTable.SERVER_ID + + WHERE + UserInfoTable.TABLE_NAME + '.' + UserInfoTable.USER_ID + "=" + UsersTable.SELECT_USER_ID; - return new QueryStatement>(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, playerUUID.toString()); @@ -111,7 +116,7 @@ public class UserInfoQueries { long registered = set.getLong(UserInfoTable.REGISTERED); boolean op = set.getBoolean(UserInfoTable.OP); boolean banned = set.getBoolean(UserInfoTable.BANNED); - ServerUUID serverUUID = ServerUUID.fromString(set.getString(UserInfoTable.SERVER_UUID)); + ServerUUID serverUUID = ServerUUID.fromString(set.getString(ServerTable.SERVER_UUID)); String joinAddress = set.getString(UserInfoTable.JOIN_ADDRESS); userInformation.add(new UserInfo(playerUUID, serverUUID, registered, op, joinAddress, banned)); @@ -121,59 +126,17 @@ public class UserInfoQueries { }; } - /** - * Query database for all User information of a specific server. - * - * @param serverUUID UUID of the Plan server. - * @return Map: Player UUID - user information - */ - public static Query> fetchUserInformationOfServer(ServerUUID serverUUID) { - String sql = SELECT + - UserInfoTable.REGISTERED + ',' + - UserInfoTable.BANNED + ',' + - UserInfoTable.JOIN_ADDRESS + ',' + - UserInfoTable.OP + ',' + - UserInfoTable.USER_UUID + ',' + - UserInfoTable.SERVER_UUID + - FROM + UserInfoTable.TABLE_NAME + - WHERE + UserInfoTable.SERVER_UUID + "=?"; - - return new QueryStatement>(sql, 1000) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setString(1, serverUUID.toString()); - } - - @Override - public Map processResults(ResultSet set) throws SQLException { - Map userInformation = new HashMap<>(); - while (set.next()) { - ServerUUID serverUUID = ServerUUID.fromString(set.getString(UserInfoTable.SERVER_UUID)); - UUID uuid = UUID.fromString(set.getString(UserInfoTable.USER_UUID)); - - long registered = set.getLong(UserInfoTable.REGISTERED); - boolean banned = set.getBoolean(UserInfoTable.BANNED); - boolean op = set.getBoolean(UserInfoTable.OP); - - String joinAddress = set.getString(UserInfoTable.JOIN_ADDRESS); - - userInformation.put(uuid, new UserInfo(uuid, serverUUID, registered, op, joinAddress, banned)); - } - return userInformation; - } - }; - } - public static Query> fetchRegisterDates(long after, long before, ServerUUID serverUUID) { String sql = SELECT + - UserInfoTable.USER_UUID + ',' + - UserInfoTable.REGISTERED + - FROM + UserInfoTable.TABLE_NAME + - WHERE + UserInfoTable.SERVER_UUID + "=?" + - AND + UserInfoTable.REGISTERED + ">=?" + - AND + UserInfoTable.REGISTERED + "<=?"; + UsersTable.USER_UUID + ',' + + "ux." + UserInfoTable.REGISTERED + + FROM + UserInfoTable.TABLE_NAME + " ux" + + INNER_JOIN + UsersTable.TABLE_NAME + " u on u." + UsersTable.ID + '=' + "ux." + UserInfoTable.USER_ID + + WHERE + UserInfoTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + + AND + "ux." + UserInfoTable.REGISTERED + ">=?" + + AND + "ux." + UserInfoTable.REGISTERED + "<=?"; - return new QueryStatement>(sql, 1000) { + return new QueryStatement<>(sql, 1000) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -186,7 +149,7 @@ public class UserInfoQueries { Map registerDates = new HashMap<>(); while (set.next()) { registerDates.put( - UUID.fromString(set.getString(UserInfoTable.USER_UUID)), + UUID.fromString(set.getString(UsersTable.USER_UUID)), set.getLong(UserInfoTable.REGISTERED) ); } @@ -195,146 +158,77 @@ public class UserInfoQueries { }; } - public static Query> joinAddresses() { - String sql = SELECT + - "COUNT(1) as total," + - "LOWER(COALESCE(" + UserInfoTable.JOIN_ADDRESS + ", ?)) as address" + - FROM + '(' + - SELECT + DISTINCT + - UserInfoTable.USER_UUID + ',' + - UserInfoTable.JOIN_ADDRESS + + public static Query> userIdsOfOperators() { + return getUserIdsForBooleanGroup(UserInfoTable.OP, true); + } + + public static Query> getUserIdsForBooleanGroup(String column, boolean value) { + String sql = SELECT + "u." + UsersTable.ID + FROM + UserInfoTable.TABLE_NAME + - ") q1" + - GROUP_BY + "address" + - ORDER_BY + "address ASC"; - - return new QueryStatement>(sql, 100) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setString(1, "Unknown"); - } - - @Override - public Map processResults(ResultSet set) throws SQLException { - Map joinAddresses = new TreeMap<>(); - while (set.next()) { - joinAddresses.put(set.getString("address"), set.getInt("total")); - } - return joinAddresses; - } - }; - } - - public static Query> joinAddresses(ServerUUID serverUUID) { - String sql = SELECT + - "COUNT(1) as total," + - "LOWER(COALESCE(" + UserInfoTable.JOIN_ADDRESS + ", ?)) as address" + - FROM + UserInfoTable.TABLE_NAME + - WHERE + UserInfoTable.SERVER_UUID + "=?" + - GROUP_BY + "address" + - ORDER_BY + "address ASC"; - - return new QueryStatement>(sql, 100) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setString(1, "Unknown"); - statement.setString(2, serverUUID.toString()); - } - - @Override - public Map processResults(ResultSet set) throws SQLException { - Map joinAddresses = new TreeMap<>(); - while (set.next()) { - joinAddresses.put(set.getString("address"), set.getInt("total")); - } - return joinAddresses; - } - }; - } - - public static Query> uniqueJoinAddresses() { - String sql = SELECT + DISTINCT + "LOWER(COALESCE(" + UserInfoTable.JOIN_ADDRESS + ", ?)) as address" + - FROM + UserInfoTable.TABLE_NAME + - ORDER_BY + "address ASC"; - return new QueryStatement>(sql, 100) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setString(1, "unknown"); - } - - @Override - public List processResults(ResultSet set) throws SQLException { - List joinAddresses = new ArrayList<>(); - while (set.next()) joinAddresses.add(set.getString("address")); - return joinAddresses; - } - }; - } - - public static Query> uuidsOfOperators() { - return getUUIDsForBooleanGroup(UserInfoTable.OP, true); - } - - public static Query> getUUIDsForBooleanGroup(String column, boolean value) { - String sql = SELECT + UserInfoTable.USER_UUID + FROM + UserInfoTable.TABLE_NAME + + INNER_JOIN + UsersTable.TABLE_NAME + " u on u." + UsersTable.ID + '=' + UserInfoTable.TABLE_NAME + '.' + UserInfoTable.USER_ID + WHERE + column + "=?"; - return new QueryStatement>(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setBoolean(1, value); } @Override - public Set processResults(ResultSet set) throws SQLException { - return extractUUIDs(set); + public Set processResults(ResultSet set) throws SQLException { + return extractUserIds(set); } }; } - public static Set extractUUIDs(ResultSet set) throws SQLException { - Set uuids = new HashSet<>(); + public static Set extractUserIds(ResultSet set) throws SQLException { + return extractUserIds(set, UsersTable.ID); + } + + public static Set extractUserIds(ResultSet set, String column) throws SQLException { + Set userIds = new HashSet<>(); while (set.next()) { - uuids.add(UUID.fromString(set.getString(UserInfoTable.USER_UUID))); + userIds.add(set.getInt(column)); } - return uuids; + return userIds; } - public static Query> uuidsOfNonOperators() { - return getUUIDsForBooleanGroup(UserInfoTable.OP, false); + public static Query> userIdsOfNonOperators() { + return getUserIdsForBooleanGroup(UserInfoTable.OP, false); } - public static Query> uuidsOfBanned() { - return getUUIDsForBooleanGroup(UserInfoTable.BANNED, true); + public static Query> userIdsOfBanned() { + return getUserIdsForBooleanGroup(UserInfoTable.BANNED, true); } - public static Query> uuidsOfNotBanned() { - return getUUIDsForBooleanGroup(UserInfoTable.BANNED, false); + public static Query> userIdsOfNotBanned() { + return getUserIdsForBooleanGroup(UserInfoTable.BANNED, false); } - public static Query> uuidsOfPlayersWithJoinAddresses(List joinAddresses) { - String selectLowercaseJoinAddresses = SELECT + - UserInfoTable.USER_UUID + ',' + - "LOWER(COALESCE(" + UserInfoTable.JOIN_ADDRESS + ", ?)) as address" + - FROM + UserInfoTable.TABLE_NAME; - String sql = SELECT + DISTINCT + UserInfoTable.USER_UUID + - FROM + '(' + selectLowercaseJoinAddresses + ") q1" + - WHERE + "address IN (" + - new TextStringBuilder().appendWithSeparators(joinAddresses.stream().map(item -> '?').iterator(), ",") + - ')'; // Don't append addresses directly, SQL injection hazard + public static Query> userIdsOfRegisteredBetween(long after, long before, List serverUUIDs) { + String selectServerIds = SELECT + ServerTable.ID + + FROM + ServerTable.TABLE_NAME + + WHERE + ServerTable.SERVER_UUID + " IN ('" + new TextStringBuilder().appendWithSeparators(serverUUIDs, "','") + "')"; - return new QueryStatement>(sql) { + String sql = SELECT + DISTINCT + "u." + UsersTable.ID + + FROM + UserInfoTable.TABLE_NAME + " ux" + + INNER_JOIN + UsersTable.TABLE_NAME + " u on u." + UsersTable.ID + "=ux." + UserInfoTable.USER_ID + + INNER_JOIN + "(" + selectServerIds + ") sel_server on sel_server." + ServerTable.ID + "=ux." + UserInfoTable.SERVER_ID + + WHERE + "ux." + UserInfoTable.REGISTERED + ">=?" + + AND + "ux." + UserInfoTable.REGISTERED + "<=?"; + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { - statement.setString(1, "unknown"); - for (int i = 1; i <= joinAddresses.size(); i++) { - String address = joinAddresses.get(i - 1); - statement.setString(i + 1, address); - } + statement.setLong(1, after); + statement.setLong(2, before); } @Override - public Set processResults(ResultSet set) throws SQLException { - return extractUUIDs(set); + public Set processResults(ResultSet set) throws SQLException { + Set userIds = new HashSet<>(); + while (set.next()) { + userIds.add(set.getInt(UsersTable.ID)); + } + return userIds; } }; } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/WebUserQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/WebUserQueries.java index 4a412c6fa..307101d27 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/WebUserQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/WebUserQueries.java @@ -19,17 +19,16 @@ package com.djrapitops.plan.storage.database.queries.objects; import com.djrapitops.plan.delivery.domain.WebUser; import com.djrapitops.plan.delivery.domain.auth.User; import com.djrapitops.plan.storage.database.queries.Query; -import com.djrapitops.plan.storage.database.queries.QueryAllStatement; -import com.djrapitops.plan.storage.database.queries.QueryStatement; import com.djrapitops.plan.storage.database.sql.tables.CookieTable; import com.djrapitops.plan.storage.database.sql.tables.SecurityTable; import com.djrapitops.plan.storage.database.sql.tables.UsersTable; -import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.Types; -import java.util.*; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; import static com.djrapitops.plan.storage.database.sql.building.Sql.*; @@ -47,129 +46,36 @@ public class WebUserQueries { public static Query> fetchUser(String username) { String sql = SELECT + '*' + FROM + SecurityTable.TABLE_NAME + LEFT_JOIN + UsersTable.TABLE_NAME + " on " + SecurityTable.LINKED_TO + "=" + UsersTable.USER_UUID + - WHERE + SecurityTable.USERNAME + "=? LIMIT 1"; - return new QueryStatement>(sql) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setString(1, username); - } + WHERE + SecurityTable.USERNAME + "=?" + LIMIT + "1"; - @Override - public Optional processResults(ResultSet set) throws SQLException { - if (set.next()) { - String linkedTo = set.getString(UsersTable.USER_NAME); - UUID linkedToUUID = linkedTo != null ? UUID.fromString(set.getString(SecurityTable.LINKED_TO)) : null; - String passwordHash = set.getString(SecurityTable.SALT_PASSWORD_HASH); - int permissionLevel = set.getInt(SecurityTable.PERMISSION_LEVEL); - List permissions = WebUser.getPermissionsForLevel(permissionLevel); - return Optional.of(new User(username, linkedTo != null ? linkedTo : "console", linkedToUUID, passwordHash, permissionLevel, permissions)); - } - return Optional.empty(); - } - }; + return db -> db.queryOptional(sql, WebUserQueries::extractUser, username); } public static Query> fetchUserLinkedTo(String playerName) { String sql = SELECT + '*' + FROM + SecurityTable.TABLE_NAME + LEFT_JOIN + UsersTable.TABLE_NAME + " on " + SecurityTable.LINKED_TO + "=" + UsersTable.USER_UUID + - WHERE + UsersTable.USER_NAME + "=? LIMIT 1"; - return new QueryStatement>(sql) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setString(1, playerName); - } - - @Override - public Optional processResults(ResultSet set) throws SQLException { - if (set.next()) { - String linkedTo = set.getString(UsersTable.USER_NAME); - UUID linkedToUUID = linkedTo != null ? UUID.fromString(set.getString(SecurityTable.LINKED_TO)) : null; - String passwordHash = set.getString(SecurityTable.SALT_PASSWORD_HASH); - int permissionLevel = set.getInt(SecurityTable.PERMISSION_LEVEL); - List permissions = WebUser.getPermissionsForLevel(permissionLevel); - return Optional.of(new User(playerName, linkedTo != null ? linkedTo : "console", linkedToUUID, passwordHash, permissionLevel, permissions)); - } - return Optional.empty(); - } - }; + WHERE + UsersTable.USER_NAME + "=?" + LIMIT + "1"; + return db -> db.queryOptional(sql, WebUserQueries::extractUser, playerName); } public static Query> fetchUser(UUID linkedToUUID) { String sql = SELECT + '*' + FROM + SecurityTable.TABLE_NAME + LEFT_JOIN + UsersTable.TABLE_NAME + " on " + SecurityTable.LINKED_TO + "=" + UsersTable.USER_UUID + - WHERE + SecurityTable.LINKED_TO + "=? LIMIT 1"; - return new QueryStatement>(sql) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - if (linkedToUUID == null) { - statement.setNull(1, Types.VARCHAR); - } else { - statement.setString(1, linkedToUUID.toString()); - } - } - - @Override - public Optional processResults(ResultSet set) throws SQLException { - if (set.next()) { - String username = set.getString(SecurityTable.USERNAME); - String linkedTo = set.getString(UsersTable.USER_NAME); - String passwordHash = set.getString(SecurityTable.SALT_PASSWORD_HASH); - int permissionLevel = set.getInt(SecurityTable.PERMISSION_LEVEL); - List permissions = WebUser.getPermissionsForLevel(permissionLevel); - return Optional.of(new User(username, linkedTo != null ? linkedTo : "console", linkedToUUID, passwordHash, permissionLevel, permissions)); - } - return Optional.empty(); - } - }; + WHERE + SecurityTable.LINKED_TO + "=?" + LIMIT + "1"; + return db -> db.queryOptional(sql, WebUserQueries::extractUser, linkedToUUID); } public static Query> fetchAllUsers() { String sql = SELECT + '*' + FROM + SecurityTable.TABLE_NAME + LEFT_JOIN + UsersTable.TABLE_NAME + " on " + SecurityTable.LINKED_TO + "=" + UsersTable.USER_UUID; - return new QueryAllStatement>(sql) { - - @Override - public List processResults(ResultSet set) throws SQLException { - List users = new ArrayList<>(); - while (set.next()) { - String username = set.getString(SecurityTable.USERNAME); - String linkedTo = set.getString(UsersTable.USER_NAME); - UUID linkedToUUID = linkedTo != null ? UUID.fromString(set.getString(SecurityTable.LINKED_TO)) : null; - String passwordHash = set.getString(SecurityTable.SALT_PASSWORD_HASH); - int permissionLevel = set.getInt(SecurityTable.PERMISSION_LEVEL); - List permissions = WebUser.getPermissionsForLevel(permissionLevel); - users.add(new User(username, linkedTo != null ? linkedTo : "console", linkedToUUID, passwordHash, permissionLevel, permissions)); - } - return users; - } - }; + return db -> db.queryList(sql, WebUserQueries::extractUser); } public static Query> matchUsers(String partOfUsername) { String sql = SELECT + '*' + FROM + SecurityTable.TABLE_NAME + LEFT_JOIN + UsersTable.TABLE_NAME + " on " + SecurityTable.LINKED_TO + "=" + UsersTable.USER_UUID + WHERE + "LOWER(" + SecurityTable.USERNAME + ") LIKE LOWER(?)"; - return new QueryStatement>(sql) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setString(1, '%' + partOfUsername + '%'); - } - - @Override - public List processResults(ResultSet set) throws SQLException { - List users = new ArrayList<>(); - while (set.next()) { - String username = set.getString(SecurityTable.USERNAME); - String linkedTo = set.getString(UsersTable.USER_NAME); - UUID linkedToUUID = linkedTo != null ? UUID.fromString(set.getString(SecurityTable.LINKED_TO)) : null; - String passwordHash = set.getString(SecurityTable.SALT_PASSWORD_HASH); - int permissionLevel = set.getInt(SecurityTable.PERMISSION_LEVEL); - List permissions = WebUser.getPermissionsForLevel(permissionLevel); - users.add(new User(username, linkedTo != null ? linkedTo : "console", linkedToUUID, passwordHash, permissionLevel, permissions)); - } - return users; - } - }; + return db -> db.queryList(sql, WebUserQueries::extractUser, '%' + partOfUsername + '%'); } public static Query> fetchActiveCookies() { @@ -178,23 +84,8 @@ public class WebUserQueries { LEFT_JOIN + UsersTable.TABLE_NAME + " on " + SecurityTable.LINKED_TO + "=" + UsersTable.USER_UUID + WHERE + CookieTable.EXPIRES + ">?"; - return new QueryStatement>(sql) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setLong(1, System.currentTimeMillis()); - } - - @Override - public Map processResults(ResultSet set) throws SQLException { - Map usersByCookie = new HashMap<>(); - while (set.next()) { - String cookie = set.getString(CookieTable.COOKIE); - User user = extractUser(set); - usersByCookie.put(cookie, user); - } - return usersByCookie; - } - }; + return db -> db.queryMap(sql, (set, byCookie) -> byCookie.put(set.getString(CookieTable.COOKIE), extractUser(set)), + System.currentTimeMillis()); } private static User extractUser(ResultSet set) throws SQLException { @@ -208,13 +99,7 @@ public class WebUserQueries { } public static Query> getCookieExpiryTimes() { - return new QueryAllStatement>(SELECT + CookieTable.COOKIE + ',' + CookieTable.EXPIRES + FROM + CookieTable.TABLE_NAME) { - @Override - public Map processResults(ResultSet set) throws SQLException { - HashMap expiryTimes = new HashMap<>(); - while (set.next()) expiryTimes.put(set.getString(CookieTable.COOKIE), set.getLong(CookieTable.EXPIRES)); - return expiryTimes; - } - }; + String sql = SELECT + CookieTable.COOKIE + ',' + CookieTable.EXPIRES + FROM + CookieTable.TABLE_NAME; + return db -> db.queryMap(sql, (set, expiryTimes) -> expiryTimes.put(set.getString(CookieTable.COOKIE), set.getLong(CookieTable.EXPIRES))); } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/WorldTimesQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/WorldTimesQueries.java index 0a284ce2e..868f8045d 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/WorldTimesQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/WorldTimesQueries.java @@ -23,9 +23,7 @@ import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.queries.Query; import com.djrapitops.plan.storage.database.queries.QueryAllStatement; import com.djrapitops.plan.storage.database.queries.QueryStatement; -import com.djrapitops.plan.storage.database.sql.tables.SessionsTable; -import com.djrapitops.plan.storage.database.sql.tables.WorldTable; -import com.djrapitops.plan.storage.database.sql.tables.WorldTimesTable; +import com.djrapitops.plan.storage.database.sql.tables.*; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -64,10 +62,10 @@ public class WorldTimesQueries { public static Query fetchServerTotalWorldTimes(ServerUUID serverUUID) { String sql = SELECT_WORLD_TIMES_STATEMENT_START + SELECT_WORLD_TIMES_JOIN_WORLD_NAME + - WHERE + WorldTimesTable.TABLE_NAME + '.' + WorldTimesTable.SERVER_UUID + "=?" + + WHERE + WorldTimesTable.TABLE_NAME + '.' + WorldTimesTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + GROUP_BY + WORLD_COLUMN; - return new QueryStatement(sql, 1000) { + return new QueryStatement<>(sql, 1000) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -99,10 +97,10 @@ public class WorldTimesQueries { public static Query fetchPlayerTotalWorldTimes(UUID playerUUID) { String sql = SELECT_WORLD_TIMES_STATEMENT_START + SELECT_WORLD_TIMES_JOIN_WORLD_NAME + - WHERE + WorldTimesTable.USER_UUID + "=?" + + WHERE + WorldTimesTable.USER_ID + "=" + UsersTable.SELECT_USER_ID + GROUP_BY + WORLD_COLUMN; - return new QueryStatement(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, playerUUID.toString()); @@ -133,12 +131,13 @@ public class WorldTimesQueries { */ public static Query> fetchPlayerWorldTimesOnServers(UUID playerUUID) { String sql = SELECT_WORLD_TIMES_STATEMENT_START + - WorldTimesTable.TABLE_NAME + '.' + WorldTimesTable.SERVER_UUID + ',' + + "s." + ServerTable.SERVER_UUID + ',' + SELECT_WORLD_TIMES_JOIN_WORLD_NAME + - WHERE + WorldTimesTable.TABLE_NAME + '.' + WorldTimesTable.USER_UUID + "=?" + - GROUP_BY + WORLD_COLUMN + ',' + WorldTimesTable.TABLE_NAME + '.' + WorldTimesTable.SERVER_UUID; + INNER_JOIN + ServerTable.TABLE_NAME + " s on " + WorldTimesTable.TABLE_NAME + '.' + WorldTimesTable.SERVER_ID + "=s." + ServerTable.ID + + WHERE + WorldTimesTable.TABLE_NAME + '.' + WorldTimesTable.USER_ID + "=" + UsersTable.SELECT_USER_ID + + GROUP_BY + WORLD_COLUMN + ',' + WorldTimesTable.TABLE_NAME + '.' + WorldTimesTable.SERVER_ID; - return new QueryStatement>(sql, 1000) { + return new QueryStatement<>(sql, 1000) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, playerUUID.toString()); @@ -150,7 +149,7 @@ public class WorldTimesQueries { Map worldTimesMap = new HashMap<>(); while (set.next()) { - ServerUUID serverUUID = ServerUUID.fromString(set.getString(WorldTimesTable.SERVER_UUID)); + ServerUUID serverUUID = ServerUUID.fromString(set.getString(ServerTable.SERVER_UUID)); WorldTimes worldTimes = worldTimesMap.getOrDefault(serverUUID, new WorldTimes()); String worldName = set.getString(WORLD_COLUMN); @@ -180,11 +179,11 @@ public class WorldTimesQueries { "SUM(" + WorldTimesTable.SPECTATOR + ") as SPECTATOR" + FROM + WorldTimesTable.TABLE_NAME + " w1" + INNER_JOIN + SessionsTable.TABLE_NAME + " s1 on s1." + SessionsTable.ID + '=' + WorldTimesTable.SESSION_ID + - WHERE + "w1." + WorldTimesTable.SERVER_UUID + "=?" + + WHERE + "w1." + WorldTimesTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + AND + SessionsTable.SESSION_START + ">=?" + AND + SessionsTable.SESSION_END + "<=?"; - return new QueryStatement(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -201,7 +200,7 @@ public class WorldTimesQueries { public static QueryStatement> fetchWorlds() { String worldNameSql = SELECT + '*' + FROM + WorldTable.TABLE_NAME; - return new QueryAllStatement>(worldNameSql) { + return new QueryAllStatement<>(worldNameSql) { @Override public Set processResults(ResultSet set) throws SQLException { Set worlds = new HashSet<>(); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/playertable/NetworkTablePlayersQuery.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/playertable/NetworkTablePlayersQuery.java index 0212d9df9..e0c67e12f 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/playertable/NetworkTablePlayersQuery.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/playertable/NetworkTablePlayersQuery.java @@ -55,31 +55,24 @@ public class NetworkTablePlayersQuery implements Query> { @Override public List executeQuery(SQLDB db) { - String selectGeolocations = SELECT + DISTINCT + - GeoInfoTable.USER_UUID + ", " + - GeoInfoTable.GEOLOCATION + ", " + - GeoInfoTable.LAST_USED + - FROM + GeoInfoTable.TABLE_NAME; - String selectLatestGeolocationDate = SELECT + - GeoInfoTable.USER_UUID + ", " + - "MAX(" + GeoInfoTable.LAST_USED + ") as last_used_g" + - FROM + GeoInfoTable.TABLE_NAME + - GROUP_BY + GeoInfoTable.USER_UUID; String selectLatestGeolocations = SELECT + - "g1." + GeoInfoTable.GEOLOCATION + ',' + - "g1." + GeoInfoTable.USER_UUID + - FROM + "(" + selectGeolocations + ") AS g1" + - INNER_JOIN + "(" + selectLatestGeolocationDate + ") AS g2 ON g1.uuid = g2.uuid" + - WHERE + GeoInfoTable.LAST_USED + "=last_used_g"; + "a." + GeoInfoTable.USER_ID + ',' + + "a." + GeoInfoTable.GEOLOCATION + + FROM + GeoInfoTable.TABLE_NAME + " a" + + // Super smart optimization https://stackoverflow.com/a/28090544 + // Join the last_used column, but only if there's a bigger one. + // That way the biggest a.last_used value will have NULL on the b.last_used column and MAX doesn't need to be used. + LEFT_JOIN + GeoInfoTable.TABLE_NAME + " b ON a." + GeoInfoTable.USER_ID + "=b." + GeoInfoTable.USER_ID + AND + "a." + GeoInfoTable.LAST_USED + "> { "u." + UsersTable.USER_UUID + ',' + "u." + UsersTable.USER_NAME + ',' + "u." + UsersTable.REGISTERED + ',' + - "ban." + UserInfoTable.USER_UUID + " as banned," + + "ban." + UserInfoTable.USER_ID + " as banned," + "geo." + GeoInfoTable.GEOLOCATION + ',' + "ses.last_seen," + "ses.count," + "ses.active_playtime," + "act.activity_index" + FROM + UsersTable.TABLE_NAME + " u" + - LEFT_JOIN + '(' + selectBanned + ") ban on ban." + UserInfoTable.USER_UUID + "=u." + UsersTable.USER_UUID + - LEFT_JOIN + '(' + selectLatestGeolocations + ") geo on geo." + GeoInfoTable.USER_UUID + "=u." + UsersTable.USER_UUID + - LEFT_JOIN + '(' + selectSessionData + ") ses on ses." + SessionsTable.USER_UUID + "=u." + UsersTable.USER_UUID + - LEFT_JOIN + '(' + NetworkActivityIndexQueries.selectActivityIndexSQL() + ") act on u." + UsersTable.USER_UUID + "=act." + UserInfoTable.USER_UUID + + LEFT_JOIN + '(' + selectBanned + ") ban on ban." + UserInfoTable.USER_ID + "=u." + UsersTable.ID + + LEFT_JOIN + '(' + selectLatestGeolocations + ") geo on geo." + GeoInfoTable.USER_ID + "=u." + UsersTable.ID + + LEFT_JOIN + '(' + selectSessionData + ") ses on ses." + SessionsTable.USER_ID + "=u." + UsersTable.ID + + LEFT_JOIN + '(' + NetworkActivityIndexQueries.selectActivityIndexSQL() + ") act on u." + UsersTable.ID + "=act." + UserInfoTable.USER_ID + ORDER_BY + "ses.last_seen DESC LIMIT ?"; - return db.query(new QueryStatement>(selectBaseUsers, 1000) { + return db.query(new QueryStatement<>(selectBaseUsers, 1000) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setBoolean(1, true); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/playertable/QueryTablePlayersQuery.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/playertable/QueryTablePlayersQuery.java index b28ec7126..cfc05342b 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/playertable/QueryTablePlayersQuery.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/playertable/QueryTablePlayersQuery.java @@ -18,14 +18,12 @@ package com.djrapitops.plan.storage.database.queries.objects.playertable; import com.djrapitops.plan.delivery.domain.TablePlayer; import com.djrapitops.plan.delivery.domain.mutators.ActivityIndex; +import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.SQLDB; import com.djrapitops.plan.storage.database.queries.Query; import com.djrapitops.plan.storage.database.queries.QueryStatement; import com.djrapitops.plan.storage.database.queries.analysis.NetworkActivityIndexQueries; -import com.djrapitops.plan.storage.database.sql.tables.GeoInfoTable; -import com.djrapitops.plan.storage.database.sql.tables.SessionsTable; -import com.djrapitops.plan.storage.database.sql.tables.UserInfoTable; -import com.djrapitops.plan.storage.database.sql.tables.UsersTable; +import com.djrapitops.plan.storage.database.sql.tables.*; import org.apache.commons.text.TextStringBuilder; import java.sql.PreparedStatement; @@ -45,7 +43,8 @@ import static com.djrapitops.plan.storage.database.sql.building.Sql.*; */ public class QueryTablePlayersQuery implements Query> { - private final Collection playerUUIDs; + private final Collection userIds; + private final List serverUUIDs; private final long afterDate; private final long beforeDate; private final long activeMsThreshold; @@ -53,13 +52,15 @@ public class QueryTablePlayersQuery implements Query> { /** * Create a new query. * - * @param playerUUIDs UUIDs of the players in the query - * @param beforeDate View data before this epoch ms + * @param userIds User ids of the players in the query + * @param serverUUIDs View data for these Server UUIDs * @param afterDate View data after this epoch ms + * @param beforeDate View data before this epoch ms * @param activeMsThreshold Playtime threshold for Activity Index calculation */ - public QueryTablePlayersQuery(Collection playerUUIDs, long afterDate, long beforeDate, long activeMsThreshold) { - this.playerUUIDs = playerUUIDs; + public QueryTablePlayersQuery(Collection userIds, List serverUUIDs, long afterDate, long beforeDate, long activeMsThreshold) { + this.userIds = userIds; + this.serverUUIDs = serverUUIDs; this.afterDate = afterDate; this.beforeDate = beforeDate; this.activeMsThreshold = activeMsThreshold; @@ -67,61 +68,57 @@ public class QueryTablePlayersQuery implements Query> { @Override public List executeQuery(SQLDB db) { - String uuidsInSet = " IN ('" + new TextStringBuilder().appendWithSeparators(playerUUIDs, "','").build() + "')"; + String selectServerIds = SELECT + ServerTable.ID + + FROM + ServerTable.TABLE_NAME + + WHERE + ServerTable.SERVER_UUID + " IN ('" + new TextStringBuilder().appendWithSeparators(serverUUIDs, "','") + "')"; - String selectGeolocations = SELECT + DISTINCT + - GeoInfoTable.USER_UUID + ", " + - GeoInfoTable.GEOLOCATION + ", " + - GeoInfoTable.LAST_USED + - FROM + GeoInfoTable.TABLE_NAME; - String selectLatestGeolocationDate = SELECT + - GeoInfoTable.USER_UUID + ", " + - "MAX(" + GeoInfoTable.LAST_USED + ") as last_used_g" + - FROM + GeoInfoTable.TABLE_NAME + - GROUP_BY + GeoInfoTable.USER_UUID; String selectLatestGeolocations = SELECT + - "g1." + GeoInfoTable.GEOLOCATION + ',' + - "g1." + GeoInfoTable.USER_UUID + - FROM + "(" + selectGeolocations + ") AS g1" + - INNER_JOIN + "(" + selectLatestGeolocationDate + ") AS g2 ON g1.uuid = g2.uuid" + - WHERE + GeoInfoTable.LAST_USED + "=last_used_g"; + "a." + GeoInfoTable.USER_ID + ',' + + "a." + GeoInfoTable.GEOLOCATION + + FROM + GeoInfoTable.TABLE_NAME + " a" + + // Super smart optimization https://stackoverflow.com/a/28090544 + // Join the last_used column, but only if there's a bigger one. + // That way the biggest a.last_used value will have NULL on the b.last_used column and MAX doesn't need to be used. + LEFT_JOIN + GeoInfoTable.TABLE_NAME + " b ON a." + GeoInfoTable.USER_ID + "=b." + GeoInfoTable.USER_ID + AND + "a." + GeoInfoTable.LAST_USED + "=?" + AND + "s." + SessionsTable.SESSION_END + "<=?" + - AND + "s." + SessionsTable.USER_UUID + - uuidsInSet + - GROUP_BY + "s." + SessionsTable.USER_UUID; + AND + "s." + SessionsTable.USER_ID + userIdsInSet + + GROUP_BY + "s." + SessionsTable.USER_ID; - String selectBanned = SELECT + DISTINCT + "ub." + UserInfoTable.USER_UUID + + String selectBanned = SELECT + DISTINCT + "ub." + UserInfoTable.USER_ID + FROM + UserInfoTable.TABLE_NAME + " ub" + WHERE + UserInfoTable.BANNED + "=?" + - AND + UserInfoTable.USER_UUID + uuidsInSet; + AND + "ub." + UserInfoTable.USER_ID + userIdsInSet + + (serverUUIDs.isEmpty() ? "" : AND + "ub." + UserInfoTable.SERVER_ID + " IN (" + selectServerIds + ")"); String selectBaseUsers = SELECT + "u." + UsersTable.USER_UUID + ',' + "u." + UsersTable.USER_NAME + ',' + "u." + UsersTable.REGISTERED + ',' + - "ban." + UserInfoTable.USER_UUID + " as banned," + + "ban." + UserInfoTable.USER_ID + " as banned," + "geo." + GeoInfoTable.GEOLOCATION + ',' + "ses.last_seen," + "ses.count," + "ses.active_playtime," + "act.activity_index" + FROM + UsersTable.TABLE_NAME + " u" + - LEFT_JOIN + '(' + selectBanned + ") ban on ban." + UserInfoTable.USER_UUID + "=u." + UsersTable.USER_UUID + - LEFT_JOIN + '(' + selectLatestGeolocations + ") geo on geo." + GeoInfoTable.USER_UUID + "=u." + UsersTable.USER_UUID + - LEFT_JOIN + '(' + selectSessionData + ") ses on ses." + SessionsTable.USER_UUID + "=u." + UsersTable.USER_UUID + - LEFT_JOIN + '(' + NetworkActivityIndexQueries.selectActivityIndexSQL() + ") act on u." + SessionsTable.USER_UUID + "=act." + UserInfoTable.USER_UUID + - WHERE + "u." + UserInfoTable.USER_UUID + - uuidsInSet + + LEFT_JOIN + '(' + selectBanned + ") ban on ban." + UserInfoTable.USER_ID + "=u." + UsersTable.ID + + LEFT_JOIN + '(' + selectLatestGeolocations + ") geo on geo." + GeoInfoTable.USER_ID + "=u." + UsersTable.ID + + LEFT_JOIN + '(' + selectSessionData + ") ses on ses." + SessionsTable.USER_ID + "=u." + UsersTable.ID + + LEFT_JOIN + '(' + NetworkActivityIndexQueries.selectActivityIndexSQL() + ") act on u." + UsersTable.ID + "=act." + UserInfoTable.USER_ID + + WHERE + "u." + UsersTable.ID + userIdsInSet + ORDER_BY + "ses.last_seen DESC"; - return db.query(new QueryStatement>(selectBaseUsers, 1000) { + return db.query(new QueryStatement<>(selectBaseUsers, 1000) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setBoolean(1, true); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/playertable/ServerTablePlayersQuery.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/playertable/ServerTablePlayersQuery.java index f0608ebaf..5dee74c61 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/playertable/ServerTablePlayersQuery.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/playertable/ServerTablePlayersQuery.java @@ -23,10 +23,7 @@ import com.djrapitops.plan.storage.database.SQLDB; import com.djrapitops.plan.storage.database.queries.Query; import com.djrapitops.plan.storage.database.queries.QueryStatement; import com.djrapitops.plan.storage.database.queries.analysis.ActivityIndexQueries; -import com.djrapitops.plan.storage.database.sql.tables.GeoInfoTable; -import com.djrapitops.plan.storage.database.sql.tables.SessionsTable; -import com.djrapitops.plan.storage.database.sql.tables.UserInfoTable; -import com.djrapitops.plan.storage.database.sql.tables.UsersTable; +import com.djrapitops.plan.storage.database.sql.tables.*; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -66,30 +63,23 @@ public class ServerTablePlayersQuery implements Query> { @Override public List executeQuery(SQLDB db) { - String selectGeolocations = SELECT + DISTINCT + - GeoInfoTable.USER_UUID + ", " + - GeoInfoTable.GEOLOCATION + ", " + - GeoInfoTable.LAST_USED + - FROM + GeoInfoTable.TABLE_NAME; - String selectLatestGeolocationDate = SELECT + - GeoInfoTable.USER_UUID + ", " + - "MAX(" + GeoInfoTable.LAST_USED + ") as last_used_g" + - FROM + GeoInfoTable.TABLE_NAME + - GROUP_BY + GeoInfoTable.USER_UUID; String selectLatestGeolocations = SELECT + - "g1." + GeoInfoTable.GEOLOCATION + ',' + - "g1." + GeoInfoTable.USER_UUID + - FROM + "(" + selectGeolocations + ") AS g1" + - INNER_JOIN + "(" + selectLatestGeolocationDate + ") AS g2 ON g1.uuid = g2.uuid" + - WHERE + GeoInfoTable.LAST_USED + "=last_used_g"; + "a." + GeoInfoTable.USER_ID + ',' + + "a." + GeoInfoTable.GEOLOCATION + + FROM + GeoInfoTable.TABLE_NAME + " a" + + // Super smart optimization https://stackoverflow.com/a/28090544 + // Join the last_used column, but only if there's a bigger one. + // That way the biggest a.last_used value will have NULL on the b.last_used column and MAX doesn't need to be used. + LEFT_JOIN + GeoInfoTable.TABLE_NAME + " b ON a." + GeoInfoTable.USER_ID + "=b." + GeoInfoTable.USER_ID + AND + "a." + GeoInfoTable.LAST_USED + "> { "ses.active_playtime," + "act.activity_index" + FROM + UsersTable.TABLE_NAME + " u" + - INNER_JOIN + UserInfoTable.TABLE_NAME + " on u." + UsersTable.USER_UUID + "=" + UserInfoTable.TABLE_NAME + '.' + UserInfoTable.USER_UUID + - LEFT_JOIN + '(' + selectLatestGeolocations + ") geo on geo." + GeoInfoTable.USER_UUID + "=u." + UsersTable.USER_UUID + - LEFT_JOIN + '(' + selectSessionData + ") ses on ses." + SessionsTable.USER_UUID + "=u." + UsersTable.USER_UUID + - LEFT_JOIN + '(' + ActivityIndexQueries.selectActivityIndexSQL() + ") act on u." + SessionsTable.USER_UUID + "=act." + UserInfoTable.USER_UUID + - WHERE + UserInfoTable.SERVER_UUID + "=?" + + INNER_JOIN + UserInfoTable.TABLE_NAME + " on u." + UsersTable.ID + "=" + UserInfoTable.TABLE_NAME + '.' + UserInfoTable.USER_ID + + LEFT_JOIN + '(' + selectLatestGeolocations + ") geo on geo." + GeoInfoTable.USER_ID + "=u." + UsersTable.ID + + LEFT_JOIN + '(' + selectSessionData + ") ses on ses." + SessionsTable.USER_ID + "=u." + UsersTable.ID + + LEFT_JOIN + '(' + ActivityIndexQueries.selectActivityIndexSQL() + ") act on u." + UsersTable.ID + "=act." + UserInfoTable.USER_ID + + WHERE + UserInfoTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + ORDER_BY + "ses.last_seen DESC LIMIT ?"; - return db.query(new QueryStatement>(selectBaseUsers, 1000) { + return db.query(new QueryStatement<>(selectBaseUsers, 1000) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); // Session query diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/schema/MySQLSchemaQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/schema/MySQLSchemaQueries.java index e9483417c..babdf39dc 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/schema/MySQLSchemaQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/schema/MySQLSchemaQueries.java @@ -54,7 +54,7 @@ public class MySQLSchemaQueries { FROM + "INFORMATION_SCHEMA.KEY_COLUMN_USAGE" + WHERE + "REFERENCED_TABLE_SCHEMA = DATABASE()" + AND + "REFERENCED_TABLE_NAME = ?"; - return new QueryStatement>(keySQL) { + return new QueryStatement<>(keySQL) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, referencedTable); @@ -114,7 +114,7 @@ public class MySQLSchemaQueries { FROM + "information_schema.COLUMNS" + WHERE + "TABLE_NAME=? AND COLUMN_NAME=? AND TABLE_SCHEMA=DATABASE()"; - return new QueryStatement(sql) { + return new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, table); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/schema/SQLiteSchemaQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/schema/SQLiteSchemaQueries.java index 06c6b8fdc..6f29f0df1 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/schema/SQLiteSchemaQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/schema/SQLiteSchemaQueries.java @@ -49,7 +49,7 @@ public class SQLiteSchemaQueries { } public static Query doesColumnExist(String tableName, String columnName) { - return new QueryAllStatement("PRAGMA table_info(" + tableName + ')') { + return new QueryAllStatement<>("PRAGMA table_info(" + tableName + ')') { @Override public Boolean processResults(ResultSet set) throws SQLException { while (set.next()) { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/schema/SessionIDServerIDRelationQuery.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/schema/SessionIDServerIDRelationQuery.java index 10609701b..99dec70a7 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/schema/SessionIDServerIDRelationQuery.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/schema/SessionIDServerIDRelationQuery.java @@ -38,7 +38,7 @@ public class SessionIDServerIDRelationQuery extends QueryAllStatement. + */ +package com.djrapitops.plan.storage.database.sql.tables; + +import com.djrapitops.plan.storage.database.DBType; +import com.djrapitops.plan.storage.database.sql.building.CreateTableBuilder; +import com.djrapitops.plan.storage.database.sql.building.Sql; + +public class AccessLogTable { + + public static final String TABLE_NAME = "plan_access_log"; + public static final String ID = "id"; + public static final String TIME = "time"; + public static final String FROM_IP = "from_ip"; + public static final String REQUEST_METHOD = "request_method"; + public static final String REQUEST_URI = "request_uri"; + public static final String RESPONSE_CODE = "response_code"; + public static final String INSERT_NO_USER = "INSERT INTO " + TABLE_NAME + " (" + + TIME + ',' + FROM_IP + ',' + REQUEST_METHOD + ',' + REQUEST_URI + ',' + RESPONSE_CODE + + ") VALUES (?, ?, ?, ?, ?)"; + + private AccessLogTable() { + /* Static constant class */ + } + + public static String createTableSql(DBType dbType) { + return CreateTableBuilder.create(TABLE_NAME, dbType) + .column(ID, Sql.INT).primaryKey() + .column(TIME, Sql.LONG).notNull() + .column(FROM_IP, Sql.varchar(45)) // Max IPv6 text length 45 chars + .column(REQUEST_METHOD, Sql.varchar(8)).notNull() + .column(REQUEST_URI, Sql.TEXT).notNull() + .column(RESPONSE_CODE, Sql.INT) + .build(); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/CookieTable.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/CookieTable.java index 589a24973..88b701be7 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/CookieTable.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/CookieTable.java @@ -20,6 +20,7 @@ import com.djrapitops.plan.storage.database.DBType; import com.djrapitops.plan.storage.database.sql.building.CreateTableBuilder; import com.djrapitops.plan.storage.database.sql.building.Sql; +import static com.djrapitops.plan.storage.database.sql.building.Sql.DELETE_FROM; import static com.djrapitops.plan.storage.database.sql.building.Sql.WHERE; /** @@ -31,6 +32,7 @@ public class CookieTable { public static final String TABLE_NAME = "plan_cookies"; + public static final String ID = "id"; public static final String WEB_USERNAME = "web_username"; public static final String COOKIE = "cookie"; public static final String EXPIRES = "expires"; @@ -40,13 +42,16 @@ public class CookieTable { COOKIE + ',' + EXPIRES + ") VALUES (?,?,?)"; - public static final String DELETE_BY_USER_STATEMENT = "DELETE FROM " + TABLE_NAME + + public static final String DELETE_BY_COOKIE_STATEMENT = DELETE_FROM + TABLE_NAME + + WHERE + COOKIE + "=?"; + + public static final String DELETE_BY_USER_STATEMENT = DELETE_FROM + TABLE_NAME + WHERE + WEB_USERNAME + "=?"; - public static final String DELETE_OLDER_STATEMENT = "DELETE FROM " + TABLE_NAME + + public static final String DELETE_OLDER_STATEMENT = DELETE_FROM + TABLE_NAME + WHERE + EXPIRES + "<=?"; - public static final String DELETE_ALL_STATEMENT = "DELETE FROM " + TABLE_NAME; + public static final String DELETE_ALL_STATEMENT = DELETE_FROM + TABLE_NAME; private CookieTable() { @@ -55,6 +60,7 @@ public class CookieTable { public static String createTableSQL(DBType dbType) { return CreateTableBuilder.create(TABLE_NAME, dbType) + .column(ID, Sql.INT).primaryKey() .column(WEB_USERNAME, Sql.varchar(100)).notNull() .column(EXPIRES, Sql.LONG).notNull() .column(COOKIE, Sql.varchar(64)).notNull() diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/ExtensionIconTable.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/ExtensionIconTable.java index fb153b147..6834812ad 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/ExtensionIconTable.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/ExtensionIconTable.java @@ -28,7 +28,7 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Types; -import static com.djrapitops.plan.storage.database.sql.building.Sql.*; +import static com.djrapitops.plan.storage.database.sql.building.Sql.INT; /** * Table information about 'plan_extension_icons'. @@ -44,12 +44,6 @@ public class ExtensionIconTable { public static final String FAMILY = "family"; public static final String COLOR = "color"; - public static final String STATEMENT_SELECT_ICON_ID = '(' + SELECT + ID + - FROM + TABLE_NAME + - WHERE + ICON_NAME + "=?" + - AND + FAMILY + "=?" + - AND + COLOR + "=? LIMIT 1)"; - public static void set3IconValuesToStatement(PreparedStatement statement, Icon icon) throws SQLException { set3IconValuesToStatement(statement, 1, icon); } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/ExtensionTableProviderTable.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/ExtensionTableProviderTable.java index 226284715..4d68824f2 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/ExtensionTableProviderTable.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/ExtensionTableProviderTable.java @@ -58,6 +58,12 @@ public class ExtensionTableProviderTable { public static final String ICON_4_ID = "icon_4_id"; public static final String ICON_5_ID = "icon_5_id"; + public static final String FORMAT_1 = "format_1"; + public static final String FORMAT_2 = "format_2"; + public static final String FORMAT_3 = "format_3"; + public static final String FORMAT_4 = "format_4"; + public static final String FORMAT_5 = "format_5"; + public static final int VALUES_FOR_PLAYER = 0; public static final int VALUES_FOR_SERVER = 1; @@ -92,6 +98,11 @@ public class ExtensionTableProviderTable { .column(ICON_3_ID, INT) .column(ICON_4_ID, INT) .column(ICON_5_ID, INT) + .column(FORMAT_1, Sql.varchar(15)) + .column(FORMAT_2, Sql.varchar(15)) + .column(FORMAT_3, Sql.varchar(15)) + .column(FORMAT_4, Sql.varchar(15)) + .column(FORMAT_5, Sql.varchar(15)) .column(TAB_ID, INT) .foreignKey(PLUGIN_ID, ExtensionPluginTable.TABLE_NAME, ExtensionPluginTable.ID) .foreignKey(ICON_1_ID, ExtensionIconTable.TABLE_NAME, ExtensionIconTable.ID) diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/GeoInfoTable.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/GeoInfoTable.java index a61843a0d..ee4eb5392 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/GeoInfoTable.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/GeoInfoTable.java @@ -41,19 +41,19 @@ public class GeoInfoTable { public static final String TABLE_NAME = "plan_geolocations"; public static final String ID = "id"; - public static final String USER_UUID = "uuid"; + public static final String USER_ID = "user_id"; public static final String GEOLOCATION = "geolocation"; public static final String LAST_USED = "last_used"; public static final String INSERT_STATEMENT = "INSERT INTO " + TABLE_NAME + " (" - + USER_UUID + ',' + + USER_ID + ',' + GEOLOCATION + ',' + LAST_USED - + ") VALUES (?, ?, ?)"; + + ") VALUES (" + UsersTable.SELECT_USER_ID + ", ?, ?)"; - public static final String UPDATE_STATEMENT = "UPDATE " + TABLE_NAME + " SET " - + LAST_USED + "=?" + - WHERE + USER_UUID + "=?" + + public static final String UPDATE_STATEMENT = "UPDATE " + TABLE_NAME + " SET " + + LAST_USED + "=?" + + WHERE + USER_ID + "=" + UsersTable.SELECT_USER_ID + AND + GEOLOCATION + "=?"; private GeoInfoTable() { @@ -63,9 +63,10 @@ public class GeoInfoTable { public static String createTableSQL(DBType dbType) { return CreateTableBuilder.create(TABLE_NAME, dbType) .column(ID, Sql.INT).primaryKey() - .column(USER_UUID, Sql.varchar(36)).notNull() + .column(USER_ID, Sql.INT).notNull() .column(GEOLOCATION, Sql.varchar(50)).notNull() .column(LAST_USED, Sql.LONG).notNull().defaultValue("0") + .foreignKey(USER_ID, UsersTable.TABLE_NAME, UsersTable.ID) .toString(); } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/JoinAddressTable.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/JoinAddressTable.java new file mode 100644 index 000000000..c9f82b80b --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/JoinAddressTable.java @@ -0,0 +1,46 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.storage.database.sql.tables; + +import com.djrapitops.plan.storage.database.DBType; +import com.djrapitops.plan.storage.database.sql.building.CreateTableBuilder; +import com.djrapitops.plan.storage.database.sql.building.Sql; + +import static com.djrapitops.plan.storage.database.sql.building.Sql.*; + +public class JoinAddressTable { + + public static final String TABLE_NAME = "plan_join_address"; + public static final String ID = "id"; + public static final String JOIN_ADDRESS = "join_address"; + public static final int JOIN_ADDRESS_MAX_LENGTH = 191; + + public static final String SELECT_ID = '(' + SELECT + ID + FROM + TABLE_NAME + WHERE + JOIN_ADDRESS + "=?)"; + public static final String INSERT_STATEMENT = "INSERT INTO " + TABLE_NAME + + " (" + JOIN_ADDRESS + ") VALUES (?)"; + public static final String DEFAULT_VALUE_FOR_LOOKUP = "unknown"; + + private JoinAddressTable() {} + + public static String createTableSQL(DBType dbType) { + return CreateTableBuilder.create(TABLE_NAME, dbType) + .column(ID, Sql.INT).primaryKey() + .column(JOIN_ADDRESS, Sql.varchar(JOIN_ADDRESS_MAX_LENGTH)).unique() + .toString(); + } + +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/KillsTable.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/KillsTable.java index 1ba8fe4f4..c0e35861f 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/KillsTable.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/KillsTable.java @@ -88,7 +88,7 @@ public class KillsTable { ServerUUID serverUUID = session.getServerUUID(); Optional playerKills = session.getExtraData().get(PlayerKills.class); - if (!playerKills.isPresent()) return; + if (playerKills.isEmpty()) return; for (PlayerKill kill : playerKills.get().asList()) { // Session ID select statement parameters @@ -99,7 +99,7 @@ public class KillsTable { // Kill data statement.setString(5, playerUUID.toString()); - statement.setString(6, kill.getVictim().toString()); + statement.setString(6, kill.getVictim().getUuid().toString()); statement.setString(7, serverUUID.toString()); statement.setLong(8, kill.getDate()); statement.setString(9, StringUtils.truncate(kill.getWeapon(), WEAPON_COLUMN_LENGTH)); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/MetadataTable.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/MetadataTable.java deleted file mode 100644 index 40c13ea4b..000000000 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/MetadataTable.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * This file is part of Player Analytics (Plan). - * - * Plan is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License v3 as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Plan is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Plan. If not, see . - */ -package com.djrapitops.plan.storage.database.sql.tables; - -import com.djrapitops.plan.storage.database.DBType; -import com.djrapitops.plan.storage.database.queries.Query; -import com.djrapitops.plan.storage.database.queries.QueryStatement; -import com.djrapitops.plan.storage.database.sql.building.CreateTableBuilder; -import com.djrapitops.plan.storage.database.sql.building.Sql; -import com.djrapitops.plan.storage.database.transactions.ExecStatement; -import com.djrapitops.plan.storage.database.transactions.Executable; - -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; - -import static com.djrapitops.plan.storage.database.sql.building.Sql.*; - -public class MetadataTable { - - public static final String TABLE_NAME = "plan_database_metadata"; - - public static final String KEY = "id"; - public static final String VALUE = "uuid"; - public static final String INSERT_STATEMENT = "INSERT INTO " + TABLE_NAME + " (" + - KEY + ',' + - VALUE + - ") VALUES (?, ?)"; - - public static final String UPDATE_STATEMENT = "UPDATE " + TABLE_NAME + " SET " + VALUE + "=?" + - WHERE + KEY + "=?"; - - public static final String SELECT_VALUE_OF_KEY = SELECT + VALUE + FROM + TABLE_NAME + WHERE + KEY + "=?"; - - private MetadataTable() { - /* Static information class */ - } - - public static Executable insertValue(String key, String value) { - return new ExecStatement(MetadataTable.INSERT_STATEMENT) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setString(1, key); - statement.setString(2, value); - } - }; - } - - public static Query getValueOrNull(String key) { - return new QueryStatement(MetadataTable.SELECT_VALUE_OF_KEY) { - @Override - public void prepare(PreparedStatement statement) throws SQLException { - statement.setString(1, key); - } - - @Override - public String processResults(ResultSet set) throws SQLException { - return set.next() ? set.getString(MetadataTable.VALUE) : null; - } - }; - } - - public static String createTableSQL(DBType dbType) { - return CreateTableBuilder.create(TABLE_NAME, dbType) - .column(KEY, Sql.varchar(36)).notNull() - .column(VALUE, Sql.varchar(75)).notNull() - .toString(); - } -} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/PingTable.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/PingTable.java index 9c61dde16..284a8b4cf 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/PingTable.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/PingTable.java @@ -34,21 +34,21 @@ public class PingTable { public static final String TABLE_NAME = "plan_ping"; public static final String ID = "id"; - public static final String USER_UUID = "uuid"; - public static final String SERVER_UUID = "server_uuid"; + public static final String USER_ID = "user_id"; + public static final String SERVER_ID = "server_id"; public static final String DATE = "date"; public static final String MAX_PING = "max_ping"; public static final String AVG_PING = "avg_ping"; public static final String MIN_PING = "min_ping"; public static final String INSERT_STATEMENT = "INSERT INTO " + TABLE_NAME + " (" + - USER_UUID + ',' + - SERVER_UUID + ',' + + USER_ID + ',' + + SERVER_ID + ',' + DATE + ',' + MIN_PING + ',' + MAX_PING + ',' + AVG_PING + - ") VALUES (?, ?, ?, ?, ?, ?)"; + ") VALUES (" + UsersTable.SELECT_USER_ID + ',' + ServerTable.SELECT_SERVER_ID + ", ?, ?, ?, ?)"; private PingTable() { /* Static information class */ @@ -57,12 +57,14 @@ public class PingTable { public static String createTableSQL(DBType dbType) { return CreateTableBuilder.create(TABLE_NAME, dbType) .column(ID, Sql.INT).primaryKey() - .column(USER_UUID, Sql.varchar(36)).notNull() - .column(SERVER_UUID, Sql.varchar(36)).notNull() + .column(USER_ID, Sql.INT).notNull() + .column(SERVER_ID, Sql.INT).notNull() .column(DATE, Sql.LONG).notNull() .column(MAX_PING, Sql.INT).notNull() .column(MIN_PING, Sql.INT).notNull() .column(AVG_PING, Sql.DOUBLE).notNull() + .foreignKey(USER_ID, UsersTable.TABLE_NAME, UsersTable.ID) + .foreignKey(SERVER_ID, ServerTable.TABLE_NAME, ServerTable.ID) .toString(); } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/SecurityTable.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/SecurityTable.java index c9c8d3923..3b29d56a9 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/SecurityTable.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/SecurityTable.java @@ -29,6 +29,7 @@ public class SecurityTable { public static final String TABLE_NAME = "plan_security"; + public static final String ID = "id"; public static final String USERNAME = "username"; public static final String LINKED_TO = "linked_to_uuid"; public static final String SALT_PASSWORD_HASH = "salted_pass_hash"; @@ -46,6 +47,7 @@ public class SecurityTable { public static String createTableSQL(DBType dbType) { return CreateTableBuilder.create(TABLE_NAME, dbType) + .column(ID, Sql.INT).primaryKey() .column(USERNAME, Sql.varchar(100)).notNull().unique() .column(LINKED_TO, Sql.varchar(36)).defaultValue("''") .column(SALT_PASSWORD_HASH, Sql.varchar(100)).notNull().unique() diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/ServerTable.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/ServerTable.java index e79f7547b..c2658d7d1 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/ServerTable.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/ServerTable.java @@ -17,11 +17,15 @@ package com.djrapitops.plan.storage.database.sql.tables; import com.djrapitops.plan.identification.Server; +import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.DBType; import com.djrapitops.plan.storage.database.sql.building.CreateTableBuilder; import com.djrapitops.plan.storage.database.sql.building.Insert; import com.djrapitops.plan.storage.database.sql.building.Sql; import com.djrapitops.plan.storage.database.sql.building.Update; +import org.apache.commons.text.TextStringBuilder; + +import java.util.Collection; import static com.djrapitops.plan.storage.database.sql.building.Sql.*; @@ -35,32 +39,32 @@ public class ServerTable { public static final String TABLE_NAME = "plan_servers"; - public static final String SERVER_ID = "id"; + public static final String ID = "id"; public static final String SERVER_UUID = "uuid"; public static final String NAME = "name"; public static final String WEB_ADDRESS = "web_address"; public static final String INSTALLED = "is_installed"; public static final String PROXY = "is_proxy"; - @Deprecated - public static final String MAX_PLAYERS = "max_players"; + public static final String PLAN_VERSION = "plan_version"; public static final String INSERT_STATEMENT = Insert.values(TABLE_NAME, SERVER_UUID, NAME, - WEB_ADDRESS, INSTALLED, PROXY); + WEB_ADDRESS, INSTALLED, PROXY, PLAN_VERSION); public static final String UPDATE_STATEMENT = Update.values(TABLE_NAME, - SERVER_UUID, - NAME, - WEB_ADDRESS, - INSTALLED, - PROXY) + SERVER_UUID, + NAME, + WEB_ADDRESS, + INSTALLED, + PROXY, + PLAN_VERSION) .where(SERVER_UUID + "=?") .toString(); - public static final String STATEMENT_SELECT_SERVER_ID = - '(' + SELECT + TABLE_NAME + '.' + SERVER_ID + + public static final String SELECT_SERVER_ID = + '(' + SELECT + TABLE_NAME + '.' + ID + FROM + TABLE_NAME + - WHERE + TABLE_NAME + '.' + SERVER_UUID + "=? LIMIT 1)"; + WHERE + TABLE_NAME + '.' + SERVER_UUID + "=?" + LIMIT + "1)"; private ServerTable() { /* Static information class */ @@ -68,13 +72,21 @@ public class ServerTable { public static String createTableSQL(DBType dbType) { return CreateTableBuilder.create(TABLE_NAME, dbType) - .column(SERVER_ID, Sql.INT).primaryKey() + .column(ID, Sql.INT).primaryKey() .column(SERVER_UUID, Sql.varchar(36)).notNull().unique() .column(NAME, Sql.varchar(100)) .column(WEB_ADDRESS, Sql.varchar(100)) .column(INSTALLED, Sql.BOOL).notNull().defaultValue(true) .column(PROXY, Sql.BOOL).notNull().defaultValue(false) - .column(MAX_PLAYERS, Sql.INT).notNull().defaultValue("-1") + .column(PLAN_VERSION, Sql.varchar(18)) .toString(); } + + public static String selectServerIds(Collection serverUUIDs) { + return '(' + SELECT + TABLE_NAME + '.' + ID + + FROM + TABLE_NAME + + WHERE + TABLE_NAME + '.' + SERVER_UUID + " IN ('" + + new TextStringBuilder().appendWithSeparators(serverUUIDs, "','").build() + + "'))"; + } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/SessionsTable.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/SessionsTable.java index bce5b474e..31382caf4 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/SessionsTable.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/SessionsTable.java @@ -32,6 +32,7 @@ import static com.djrapitops.plan.storage.database.sql.building.Sql.*; * {@link Version10Patch} * {@link SessionAFKTimePatch} * {@link SessionsOptimizationPatch} + * {@link com.djrapitops.plan.storage.database.transactions.patches.SessionJoinAddressPatch} * * @author AuroraLS3 */ @@ -40,27 +41,29 @@ public class SessionsTable { public static final String TABLE_NAME = "plan_sessions"; public static final String ID = "id"; - public static final String USER_UUID = "uuid"; - public static final String SERVER_UUID = "server_uuid"; + public static final String USER_ID = "user_id"; + public static final String SERVER_ID = "server_id"; public static final String SESSION_START = "session_start"; public static final String SESSION_END = "session_end"; public static final String MOB_KILLS = "mob_kills"; public static final String DEATHS = "deaths"; public static final String AFK_TIME = "afk_time"; + public static final String JOIN_ADDRESS_ID = "join_address_id"; public static final String INSERT_STATEMENT = "INSERT INTO " + TABLE_NAME + " (" - + USER_UUID + ',' + + USER_ID + ',' + SESSION_START + ',' + SESSION_END + ',' + DEATHS + ',' + MOB_KILLS + ',' + AFK_TIME + ',' - + SERVER_UUID - + ") VALUES (?, ?, ?, ?, ?, ?, ?)"; + + SERVER_ID + ',' + + JOIN_ADDRESS_ID + + ") VALUES (" + UsersTable.SELECT_USER_ID + ", ?, ?, ?, ?, ?, " + ServerTable.SELECT_SERVER_ID + ", " + JoinAddressTable.SELECT_ID + ")"; public static final String SELECT_SESSION_ID_STATEMENT = "(SELECT " + TABLE_NAME + '.' + ID + FROM + TABLE_NAME + - WHERE + TABLE_NAME + '.' + USER_UUID + "=?" + - AND + TABLE_NAME + '.' + SERVER_UUID + "=?" + + WHERE + TABLE_NAME + '.' + USER_ID + "=" + UsersTable.SELECT_USER_ID + + AND + TABLE_NAME + '.' + SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + AND + SESSION_START + "=?" + AND + SESSION_END + "=? LIMIT 1)"; @@ -71,13 +74,16 @@ public class SessionsTable { public static String createTableSQL(DBType dbType) { return CreateTableBuilder.create(TABLE_NAME, dbType) .column(ID, Sql.INT).primaryKey() - .column(USER_UUID, Sql.varchar(36)).notNull() - .column(SERVER_UUID, Sql.varchar(36)).notNull() + .column(USER_ID, Sql.INT).notNull() + .column(SERVER_ID, Sql.INT).notNull() .column(SESSION_START, Sql.LONG).notNull() .column(SESSION_END, Sql.LONG).notNull() .column(MOB_KILLS, Sql.INT).notNull() .column(DEATHS, Sql.INT).notNull() .column(AFK_TIME, Sql.LONG).notNull() + .column(JOIN_ADDRESS_ID, INT).notNull().defaultValue("1") // References JoinAddressTable.ID, but no foreign key to allow null values. + .foreignKey(USER_ID, UsersTable.TABLE_NAME, UsersTable.ID) + .foreignKey(SERVER_ID, ServerTable.TABLE_NAME, ServerTable.ID) .toString(); } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/TPSTable.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/TPSTable.java index ec9c569c3..3f45fee5b 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/TPSTable.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/TPSTable.java @@ -29,6 +29,7 @@ public class TPSTable { public static final String TABLE_NAME = "plan_tps"; + public static final String ID = "id"; public static final String SERVER_ID = "server_id"; public static final String DATE = "date"; public static final String TPS = "tps"; @@ -50,7 +51,7 @@ public class TPSTable { + CHUNKS + ',' + FREE_DISK + ") VALUES (" - + ServerTable.STATEMENT_SELECT_SERVER_ID + ',' + + ServerTable.SELECT_SERVER_ID + ',' + "?, ?, ?, ?, ?, ?, ?, ?)"; private TPSTable() { @@ -59,6 +60,7 @@ public class TPSTable { public static String createTableSQL(DBType dbType) { return CreateTableBuilder.create(TABLE_NAME, dbType) + .column(ID, Sql.INT).primaryKey() .column(SERVER_ID, Sql.INT).notNull() .column(DATE, Sql.LONG).notNull() .column(TPS, Sql.DOUBLE).notNull() @@ -68,7 +70,7 @@ public class TPSTable { .column(ENTITIES, Sql.INT).notNull() .column(CHUNKS, Sql.INT).notNull() .column(FREE_DISK, Sql.LONG).notNull() - .foreignKey(SERVER_ID, ServerTable.TABLE_NAME, ServerTable.SERVER_ID) + .foreignKey(SERVER_ID, ServerTable.TABLE_NAME, ServerTable.ID) .toString(); } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/UserInfoTable.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/UserInfoTable.java index 504c51030..259192d64 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/UserInfoTable.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/UserInfoTable.java @@ -37,21 +37,25 @@ public class UserInfoTable { public static final String TABLE_NAME = "plan_user_info"; public static final String ID = "id"; - public static final String USER_UUID = "uuid"; - public static final String SERVER_UUID = "server_uuid"; + public static final String USER_ID = "user_id"; + public static final String SERVER_ID = "server_id"; public static final String REGISTERED = "registered"; public static final String OP = "opped"; public static final String BANNED = "banned"; + /** + * @deprecated Join address is now stored in {@link JoinAddressTable}, this column may become unreliable in the future. + */ + @Deprecated(since = "5.4 build 1722") public static final String JOIN_ADDRESS = "join_address"; public static final String INSERT_STATEMENT = "INSERT INTO " + TABLE_NAME + " (" + - USER_UUID + ',' + + USER_ID + ',' + REGISTERED + ',' + - SERVER_UUID + ',' + + SERVER_ID + ',' + BANNED + ',' + JOIN_ADDRESS + ',' + OP + - ") VALUES (?, ?, ?, ?, ?, ?)"; + ") VALUES (" + UsersTable.SELECT_USER_ID + ", ?, " + ServerTable.SELECT_SERVER_ID + ", ?, ?, ?)"; private UserInfoTable() { /* Static information class */ @@ -60,12 +64,14 @@ public class UserInfoTable { public static String createTableSQL(DBType dbType) { return CreateTableBuilder.create(TABLE_NAME, dbType) .column(ID, Sql.INT).primaryKey() - .column(USER_UUID, Sql.varchar(36)).notNull() - .column(SERVER_UUID, Sql.varchar(36)).notNull() - .column(JOIN_ADDRESS, Sql.varchar(255)) + .column(USER_ID, Sql.INT).notNull() + .column(SERVER_ID, Sql.INT).notNull() + .column(JOIN_ADDRESS, Sql.varchar(JoinAddressTable.JOIN_ADDRESS_MAX_LENGTH)) .column(REGISTERED, Sql.LONG).notNull() .column(OP, Sql.BOOL).notNull().defaultValue(false) .column(BANNED, Sql.BOOL).notNull().defaultValue(false) + .foreignKey(USER_ID, UsersTable.TABLE_NAME, UsersTable.ID) + .foreignKey(SERVER_ID, ServerTable.TABLE_NAME, ServerTable.ID) .toString(); } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/UsersTable.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/UsersTable.java index f9460906c..ab351e4be 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/UsersTable.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/UsersTable.java @@ -21,6 +21,8 @@ import com.djrapitops.plan.storage.database.sql.building.CreateTableBuilder; import com.djrapitops.plan.storage.database.sql.building.Insert; import com.djrapitops.plan.storage.database.sql.building.Sql; +import static com.djrapitops.plan.storage.database.sql.building.Sql.*; + /** * Table information about 'plan_users'. *

@@ -29,6 +31,7 @@ import com.djrapitops.plan.storage.database.sql.building.Sql; * Patches that apply to this table: * {@link com.djrapitops.plan.storage.database.transactions.patches.Version10Patch} * {@link com.djrapitops.plan.storage.database.transactions.patches.RegisterDateMinimizationPatch} + * {@link com.djrapitops.plan.storage.database.transactions.patches.UsersTableNameLengthPatch} * * @author AuroraLS3 */ @@ -43,6 +46,9 @@ public class UsersTable { public static final String TIMES_KICKED = "times_kicked"; public static final String INSERT_STATEMENT = Insert.values(TABLE_NAME, USER_UUID, USER_NAME, REGISTERED, TIMES_KICKED); + public static final String SELECT_USER_ID = '(' + SELECT + TABLE_NAME + '.' + ID + + FROM + TABLE_NAME + + WHERE + TABLE_NAME + '.' + USER_UUID + "=?" + LIMIT + "1)"; private UsersTable() { /* Static information class */ @@ -53,7 +59,7 @@ public class UsersTable { .column(ID, Sql.INT).primaryKey() .column(USER_UUID, Sql.varchar(36)).notNull().unique() .column(REGISTERED, Sql.LONG).notNull() - .column(USER_NAME, Sql.varchar(16)).notNull() + .column(USER_NAME, Sql.varchar(36)).notNull() .column(TIMES_KICKED, Sql.INT).notNull().defaultValue("0") .toString(); } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/WorldTimesTable.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/WorldTimesTable.java index 956355751..941e694e4 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/WorldTimesTable.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/WorldTimesTable.java @@ -50,8 +50,8 @@ public class WorldTimesTable { public static final String TABLE_NAME = "plan_world_times"; public static final String ID = "id"; - public static final String USER_UUID = "uuid"; - public static final String SERVER_UUID = "server_uuid"; + public static final String USER_ID = "user_id"; + public static final String SERVER_ID = "server_id"; public static final String SESSION_ID = "session_id"; public static final String WORLD_ID = "world_id"; public static final String SURVIVAL = "survival_time"; @@ -62,8 +62,8 @@ public class WorldTimesTable { public static final String INSERT_STATEMENT = "INSERT INTO " + WorldTimesTable.TABLE_NAME + " (" + WorldTimesTable.SESSION_ID + ',' + WorldTimesTable.WORLD_ID + ',' + - WorldTimesTable.USER_UUID + ',' + - WorldTimesTable.SERVER_UUID + ',' + + WorldTimesTable.USER_ID + ',' + + WorldTimesTable.SERVER_ID + ',' + WorldTimesTable.SURVIVAL + ',' + WorldTimesTable.CREATIVE + ',' + WorldTimesTable.ADVENTURE + ',' + @@ -71,7 +71,9 @@ public class WorldTimesTable { ") VALUES ( " + SessionsTable.SELECT_SESSION_ID_STATEMENT + ',' + WorldTable.SELECT_WORLD_ID_STATEMENT + ',' + - "?, ?, ?, ?, ?, ?)"; + UsersTable.SELECT_USER_ID + ',' + + ServerTable.SELECT_SERVER_ID + ',' + + "?, ?, ?, ?)"; private WorldTimesTable() { /* Static information class */ @@ -80,9 +82,9 @@ public class WorldTimesTable { public static String createTableSQL(DBType dbType) { return CreateTableBuilder.create(TABLE_NAME, dbType) .column(ID, Sql.INT).primaryKey() - .column(USER_UUID, Sql.varchar(36)).notNull() + .column(USER_ID, Sql.INT).notNull() .column(WORLD_ID, Sql.INT).notNull() - .column(SERVER_UUID, Sql.varchar(36)).notNull() + .column(SERVER_ID, Sql.INT).notNull() .column(SESSION_ID, Sql.INT).notNull() .column(SURVIVAL, Sql.LONG).notNull().defaultValue("0") .column(CREATIVE, Sql.LONG).notNull().defaultValue("0") @@ -90,6 +92,8 @@ public class WorldTimesTable { .column(SPECTATOR, Sql.LONG).notNull().defaultValue("0") .foreignKey(WORLD_ID, WorldTable.TABLE_NAME, WorldTable.ID) .foreignKey(SESSION_ID, SessionsTable.TABLE_NAME, SessionsTable.ID) + .foreignKey(USER_ID, UsersTable.TABLE_NAME, UsersTable.ID) + .foreignKey(SERVER_ID, ServerTable.TABLE_NAME, ServerTable.ID) .toString(); } @@ -97,7 +101,7 @@ public class WorldTimesTable { UUID uuid = session.getPlayerUUID(); ServerUUID serverUUID = session.getServerUUID(); Optional worldTimes = session.getExtraData().get(WorldTimes.class); - if (!worldTimes.isPresent()) return; + if (worldTimes.isEmpty()) return; for (Map.Entry worldTimesEntry : worldTimes.get().getWorldTimes().entrySet()) { String worldName = worldTimesEntry.getKey(); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/BackupCopyTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/BackupCopyTransaction.java index 6e88a44dd..80cb1cccd 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/BackupCopyTransaction.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/BackupCopyTransaction.java @@ -84,6 +84,7 @@ public class BackupCopyTransaction extends RemoveEverythingTransaction { } private void copyPlanServerInformation() { + copy(LargeStoreQueries::storeAllPlanServerInformation, ServerQueries.fetchUninstalledServerInformation()); copy(LargeStoreQueries::storeAllPlanServerInformation, ServerQueries.fetchPlanServerInformationCollection()); } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/ExecStatement.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/ExecStatement.java index 75a895778..f6a4f78bd 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/ExecStatement.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/ExecStatement.java @@ -18,9 +18,7 @@ package com.djrapitops.plan.storage.database.transactions; import com.djrapitops.plan.exceptions.database.DBOpException; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.SQLException; +import java.sql.*; /** * SQL executing statement that closes appropriate elements. @@ -35,23 +33,35 @@ public abstract class ExecStatement implements Executable { this.sql = sql; } + public int executeReturningId(Connection connection) { + try (PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { + prepare(preparedStatement); + preparedStatement.executeUpdate(); + return executeReturningId(preparedStatement); + } catch (SQLException e) { + throw DBOpException.forCause(sql, e); + } + } + + private int executeReturningId(PreparedStatement preparedStatement) throws SQLException { + try (ResultSet ids = preparedStatement.getGeneratedKeys()) { + return ids.next() ? ids.getInt(1) : -1; + } + } + @Override public boolean execute(Connection connection) { - try { - try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) { - return execute(preparedStatement); - } + try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) { + return execute(preparedStatement); } catch (SQLException e) { throw DBOpException.forCause(sql, e); } } public boolean execute(PreparedStatement statement) throws SQLException { - try { + try (statement) { prepare(statement); return callExecute(statement); - } finally { - statement.close(); } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/StoreServerInformationTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/StoreServerInformationTransaction.java index 6797b6f5b..ccb18e97d 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/StoreServerInformationTransaction.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/StoreServerInformationTransaction.java @@ -54,7 +54,8 @@ public class StoreServerInformationTransaction extends Transaction { statement.setString(3, server.getWebAddress()); statement.setBoolean(4, true); statement.setBoolean(5, server.isProxy()); - statement.setString(6, serverUUIDString); + statement.setString(6, server.getPlanVersion()); + statement.setString(7, serverUUIDString); } }; } @@ -68,6 +69,7 @@ public class StoreServerInformationTransaction extends Transaction { statement.setString(3, server.getWebAddress()); statement.setBoolean(4, true); statement.setBoolean(5, server.isProxy()); + statement.setString(6, server.getPlanVersion()); } }; } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/Transaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/Transaction.java index 91085f95c..6afe11725 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/Transaction.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/Transaction.java @@ -18,12 +18,16 @@ package com.djrapitops.plan.storage.database.transactions; import com.djrapitops.plan.exceptions.database.DBOpException; import com.djrapitops.plan.identification.ServerUUID; +import com.djrapitops.plan.settings.locale.lang.PluginLang; import com.djrapitops.plan.storage.database.DBType; import com.djrapitops.plan.storage.database.Database; import com.djrapitops.plan.storage.database.SQLDB; import com.djrapitops.plan.storage.database.queries.Query; import com.djrapitops.plan.storage.database.queries.QueryAPIQuery; import com.djrapitops.plan.storage.database.queries.QueryStatement; +import com.djrapitops.plan.storage.database.queries.schema.MySQLSchemaQueries; +import com.djrapitops.plan.storage.database.queries.schema.SQLiteSchemaQueries; +import com.djrapitops.plan.storage.database.transactions.patches.Patch; import com.djrapitops.plan.utilities.logging.ErrorContext; import net.playeranalytics.plugin.scheduling.TimeAmount; @@ -68,8 +72,8 @@ public abstract class Transaction { if (db.isUnderHeavyLoad()) { try { - Thread.sleep(db.getHeavyLoadDelayMs()); Thread.yield(); + Thread.sleep(db.getHeavyLoadDelayMs()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } @@ -78,7 +82,10 @@ public abstract class Transaction { try { initializeConnection(db); if (shouldBeExecuted()) { - initializeTransaction(db); + initializeTransaction(); + if (this instanceof Patch) { + db.getLogger().info(db.getLocale().getString(PluginLang.DB_APPLY_PATCH, getName())); + } performOperations(); if (connection != null) connection.commit(); } @@ -107,7 +114,7 @@ public abstract class Transaction { if (!db.isUnderHeavyLoad()) { db.getLogger().warn("Database appears to be under heavy load. Dropping some unimportant transactions and adding short pauses for next 10 minutes."); db.getRunnableFactory().create(db::assumeNoMoreHeavyLoad) - .runTaskLaterAsynchronously(TimeAmount.toTicks(10, TimeUnit.MINUTES)); + .runTaskLaterAsynchronously(TimeAmount.toTicks(2, TimeUnit.MINUTES)); } db.increaseHeavyLoadDelay(); executeTransaction(db); // Recurse to attempt again. @@ -166,7 +173,7 @@ public abstract class Transaction { } } - private void initializeTransaction(SQLDB db) { + private void initializeTransaction() { try { createSavePoint(); } catch (SQLException e) { @@ -207,6 +214,10 @@ public abstract class Transaction { return executable.execute(connection); } + protected int executeReturningId(ExecStatement executable) { + return executable.executeReturningId(connection); + } + protected boolean execute(String sql) { return execute(new ExecStatement(sql) { @Override @@ -258,6 +269,22 @@ public abstract class Transaction { } public boolean dbIsNotUnderHeavyLoad() { - return !db.isUnderHeavyLoad(); + return !db.isUnderHeavyLoad() && !db.shouldDropUnimportantTransactions(); + } + + public String getName() { + String simpleName = getClass().getSimpleName(); + return simpleName.isEmpty() ? getClass().getName() : simpleName; + } + + protected boolean hasTable(String tableName) { + switch (dbType) { + case SQLITE: + return query(SQLiteSchemaQueries.doesTableExist(tableName)); + case MYSQL: + return query(MySQLSchemaQueries.doesTableExist(tableName)); + default: + throw new IllegalStateException("Unsupported Database Type: " + dbType.getName()); + } } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/commands/ChangeUserUUIDTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/commands/ChangeUserUUIDTransaction.java new file mode 100644 index 000000000..d85f05a44 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/commands/ChangeUserUUIDTransaction.java @@ -0,0 +1,68 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.storage.database.transactions.commands; + +import com.djrapitops.plan.storage.database.sql.tables.*; +import com.djrapitops.plan.storage.database.transactions.ExecStatement; +import com.djrapitops.plan.storage.database.transactions.Executable; +import com.djrapitops.plan.storage.database.transactions.Transaction; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.UUID; + +import static com.djrapitops.plan.storage.database.sql.building.Sql.WHERE; + +/** + * Intends to correct UUID of a user. + * + * @author AuroraLS3 + */ +public class ChangeUserUUIDTransaction extends Transaction { + + protected final UUID oldUUID; + protected final UUID newUUID; + + public ChangeUserUUIDTransaction(UUID oldUUID, UUID newUUID) { + this.oldUUID = oldUUID; + this.newUUID = newUUID; + } + + @Override + protected void performOperations() { + execute(updateUUID(ExtensionGroupsTable.TABLE_NAME, ExtensionGroupsTable.USER_UUID)); + execute(updateUUID(ExtensionPlayerTableValueTable.TABLE_NAME, ExtensionPlayerTableValueTable.USER_UUID)); + execute(updateUUID(NicknamesTable.TABLE_NAME, NicknamesTable.USER_UUID)); + execute(updateUUID(UsersTable.TABLE_NAME, UsersTable.USER_UUID)); + execute(updateUUID(KillsTable.TABLE_NAME, KillsTable.VICTIM_UUID)); + execute(updateUUID(KillsTable.TABLE_NAME, KillsTable.KILLER_UUID)); + + if (hasTable("plan_platforms")) execute(updateUUID("plan_platforms", "uuid")); + if (hasTable("plan_tebex_payments")) execute(updateUUID("plan_tebex_payments", "uuid")); + if (hasTable("plan_version_protocol")) execute(updateUUID("plan_version_protocol", "uuid")); + } + + private Executable updateUUID(String tableName, String columnName) { + return new ExecStatement("UPDATE " + tableName + " SET " + columnName + "=?" + WHERE + columnName + "=?") { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setString(1, newUUID.toString()); + statement.setString(2, oldUUID.toString()); + } + }; + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/commands/CombineUserTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/commands/CombineUserTransaction.java new file mode 100644 index 000000000..bbe226507 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/commands/CombineUserTransaction.java @@ -0,0 +1,88 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.storage.database.transactions.commands; + +import com.djrapitops.plan.storage.database.queries.objects.BaseUserQueries; +import com.djrapitops.plan.storage.database.sql.tables.*; +import com.djrapitops.plan.storage.database.transactions.ExecStatement; +import com.djrapitops.plan.storage.database.transactions.Executable; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Optional; +import java.util.UUID; + +import static com.djrapitops.plan.storage.database.sql.building.Sql.*; + +/** + * Intends to correct UUID of a user. + * + * @author AuroraLS3 + */ +public class CombineUserTransaction extends ChangeUserUUIDTransaction { + + public CombineUserTransaction(UUID oldUUID, UUID newUUID) { + super(oldUUID, newUUID); + } + + @Override + protected void performOperations() { + Optional foundOldId = query(BaseUserQueries.fetchUserId(oldUUID)); + Optional foundNewId = query(BaseUserQueries.fetchUserId(newUUID)); + if (foundOldId.isEmpty() || foundNewId.isEmpty()) return; + + Integer oldId = foundOldId.get(); + Integer newId = foundNewId.get(); + + execute(updateUserId(GeoInfoTable.TABLE_NAME, GeoInfoTable.USER_ID, oldId, newId)); + execute(updateUserId(PingTable.TABLE_NAME, PingTable.USER_ID, oldId, newId)); + execute(updateUserId(SessionsTable.TABLE_NAME, SessionsTable.USER_ID, oldId, newId)); + execute(updateUserId(WorldTimesTable.TABLE_NAME, WorldTimesTable.USER_ID, oldId, newId)); + + execute(updateUserInfo(newId, oldId)); + execute(DELETE_FROM + UserInfoTable.TABLE_NAME + WHERE + UserInfoTable.USER_ID + "=" + oldId); + execute(DELETE_FROM + UsersTable.TABLE_NAME + WHERE + UsersTable.ID + "=" + oldId); + + super.performOperations(); // Change UUID fields to match where user_id is not used + } + + private Executable updateUserInfo(Integer newId, Integer oldId) { + String sql = "UPDATE " + UserInfoTable.TABLE_NAME + + " SET " + UserInfoTable.USER_ID + "=?" + + WHERE + UserInfoTable.USER_ID + "=?" + + AND + UserInfoTable.SERVER_ID + " NOT IN (" + + SELECT + UserInfoTable.SERVER_ID + FROM + UserInfoTable.TABLE_NAME + WHERE + UserInfoTable.USER_ID + "=?)"; + return new ExecStatement(sql) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setInt(1, newId); + statement.setInt(2, oldId); + statement.setInt(3, newId); + } + }; + } + + private Executable updateUserId(String tableName, String columnName, Integer oldId, Integer newId) { + return new ExecStatement("UPDATE " + tableName + " SET " + columnName + "=?" + WHERE + columnName + "=?") { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setInt(1, newId); + statement.setInt(2, oldId); + } + }; + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/commands/RemoveEverythingTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/commands/RemoveEverythingTransaction.java index 4fe81d757..89fd25434 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/commands/RemoveEverythingTransaction.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/commands/RemoveEverythingTransaction.java @@ -17,27 +17,30 @@ package com.djrapitops.plan.storage.database.transactions.commands; import com.djrapitops.plan.storage.database.sql.tables.*; -import com.djrapitops.plan.storage.database.transactions.ThrowawayTransaction; - -import static com.djrapitops.plan.storage.database.sql.building.Sql.DELETE_FROM; +import com.djrapitops.plan.storage.database.transactions.events.StoreJoinAddressTransaction; +import com.djrapitops.plan.storage.database.transactions.patches.Patch; /** * Transaction that removes everything from the database. * * @author AuroraLS3 */ -public class RemoveEverythingTransaction extends ThrowawayTransaction { +public class RemoveEverythingTransaction extends Patch { @Override - protected void performOperations() { - // Delete statements are run in a specific order as some tables have foreign keys, - // or had at some point in the past. + public boolean hasBeenApplied() { + return false; + } + + @Override + protected void applyPatch() { clearTable(SettingsTable.TABLE_NAME); clearTable(GeoInfoTable.TABLE_NAME); clearTable(NicknamesTable.TABLE_NAME); clearTable(KillsTable.TABLE_NAME); clearTable(WorldTimesTable.TABLE_NAME); clearTable(SessionsTable.TABLE_NAME); + clearTable(JoinAddressTable.TABLE_NAME); clearTable(WorldTable.TABLE_NAME); clearTable(PingTable.TABLE_NAME); clearTable(UserInfoTable.TABLE_NAME); @@ -56,9 +59,11 @@ public class RemoveEverythingTransaction extends ThrowawayTransaction { clearTable(ExtensionTabTable.TABLE_NAME); clearTable(ExtensionPluginTable.TABLE_NAME); clearTable(ExtensionIconTable.TABLE_NAME); + + executeOther(new StoreJoinAddressTransaction(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP)); } private void clearTable(String tableName) { - execute(DELETE_FROM + tableName); + execute("DELETE FROM " + tableName); } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/commands/RemovePlayerTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/commands/RemovePlayerTransaction.java index a1e793dc7..128eb0e96 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/commands/RemovePlayerTransaction.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/commands/RemovePlayerTransaction.java @@ -49,13 +49,13 @@ public class RemovePlayerTransaction extends ThrowawayTransaction { protected void performOperations() { query(PlayerFetchQueries.playerUserName(playerUUID)).ifPresent(this::deleteWebUser); - deleteFromTable(GeoInfoTable.TABLE_NAME); + deleteFromUserIdTable(GeoInfoTable.TABLE_NAME); deleteFromTable(NicknamesTable.TABLE_NAME); deleteFromKillsTable(); - deleteFromTable(WorldTimesTable.TABLE_NAME); - deleteFromTable(SessionsTable.TABLE_NAME); - deleteFromTable(PingTable.TABLE_NAME); - deleteFromTable(UserInfoTable.TABLE_NAME); + deleteFromUserIdTable(WorldTimesTable.TABLE_NAME); + deleteFromUserIdTable(SessionsTable.TABLE_NAME); + deleteFromUserIdTable(PingTable.TABLE_NAME); + deleteFromUserIdTable(UserInfoTable.TABLE_NAME); deleteFromTable(UsersTable.TABLE_NAME); deleteFromTable(ExtensionPlayerTableValueTable.TABLE_NAME); @@ -76,6 +76,15 @@ public class RemovePlayerTransaction extends ThrowawayTransaction { }); } + private void deleteFromUserIdTable(String tableName) { + execute(new ExecStatement(DELETE_FROM + tableName + WHERE + "user_id=" + UsersTable.SELECT_USER_ID) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setString(1, playerUUID.toString()); + } + }); + } + private void deleteFromKillsTable() { String sql = DELETE_FROM + KillsTable.TABLE_NAME + WHERE + KillsTable.KILLER_UUID + "=?" + diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/commands/RegisterWebUserTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/commands/StoreWebUserTransaction.java similarity index 94% rename from Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/commands/RegisterWebUserTransaction.java rename to Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/commands/StoreWebUserTransaction.java index 626a54ea2..1dc439539 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/commands/RegisterWebUserTransaction.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/commands/StoreWebUserTransaction.java @@ -30,11 +30,11 @@ import java.sql.Types; * * @author AuroraLS3 */ -public class RegisterWebUserTransaction extends Transaction { +public class StoreWebUserTransaction extends Transaction { private final User user; - public RegisterWebUserTransaction(User user) { + public StoreWebUserTransaction(User user) { this.user = user; } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/BanStatusTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/BanStatusTransaction.java index 2545b3c2c..ca4a4dd07 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/BanStatusTransaction.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/BanStatusTransaction.java @@ -18,7 +18,9 @@ package com.djrapitops.plan.storage.database.transactions.events; import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.sql.building.Update; +import com.djrapitops.plan.storage.database.sql.tables.ServerTable; import com.djrapitops.plan.storage.database.sql.tables.UserInfoTable; +import com.djrapitops.plan.storage.database.sql.tables.UsersTable; import com.djrapitops.plan.storage.database.transactions.ExecStatement; import com.djrapitops.plan.storage.database.transactions.Executable; import com.djrapitops.plan.storage.database.transactions.Transaction; @@ -39,6 +41,10 @@ public class BanStatusTransaction extends Transaction { private final ServerUUID serverUUID; private final BooleanSupplier banStatus; + public BanStatusTransaction(UUID playerUUID, ServerUUID serverUUID, boolean banStatus) { + this(playerUUID, serverUUID, () -> banStatus); + } + public BanStatusTransaction(UUID playerUUID, ServerUUID serverUUID, BooleanSupplier banStatus) { this.playerUUID = playerUUID; this.serverUUID = serverUUID; @@ -52,8 +58,8 @@ public class BanStatusTransaction extends Transaction { private Executable updateBanStatus() { String sql = Update.values(UserInfoTable.TABLE_NAME, UserInfoTable.BANNED) - .where(UserInfoTable.USER_UUID + "=?") - .and(UserInfoTable.SERVER_UUID + "=?") + .where(UserInfoTable.USER_ID + "=" + UsersTable.SELECT_USER_ID) + .and(UserInfoTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID) .toString(); return new ExecStatement(sql) { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/CookieChangeTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/CookieChangeTransaction.java index f3416ec45..db8cd309c 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/CookieChangeTransaction.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/CookieChangeTransaction.java @@ -39,23 +39,41 @@ public class CookieChangeTransaction extends Transaction { return new CookieChangeTransaction(username, cookie, expires); } - public static CookieChangeTransaction removeCookie(String username) { + public static CookieChangeTransaction removeCookieByUser(String username) { return new CookieChangeTransaction(username, null, null); } + public static CookieChangeTransaction removeCookie(String cookie) { + return new CookieChangeTransaction(null, cookie, null); + } + public static CookieChangeTransaction removeAll() { return new CookieChangeTransaction(null, null, null); } @Override protected void performOperations() { - if (username == null) { + if (username == null && cookie == null) { execute(new ExecStatement(CookieTable.DELETE_ALL_STATEMENT) { @Override public void prepare(PreparedStatement statement) { // No parameters } }); + } else if (username == null) { + execute(new ExecStatement(CookieTable.DELETE_BY_COOKIE_STATEMENT) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setString(1, cookie); + } + }); + // Perform cleanup at the same time + execute(new ExecStatement(CookieTable.DELETE_OLDER_STATEMENT) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setLong(1, System.currentTimeMillis()); + } + }); } else if (cookie == null) { execute(new ExecStatement(CookieTable.DELETE_BY_USER_STATEMENT) { @Override diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/OperatorStatusTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/OperatorStatusTransaction.java index 323a967b0..ebd7dd57f 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/OperatorStatusTransaction.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/OperatorStatusTransaction.java @@ -18,7 +18,9 @@ package com.djrapitops.plan.storage.database.transactions.events; import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.sql.building.Update; +import com.djrapitops.plan.storage.database.sql.tables.ServerTable; import com.djrapitops.plan.storage.database.sql.tables.UserInfoTable; +import com.djrapitops.plan.storage.database.sql.tables.UsersTable; import com.djrapitops.plan.storage.database.transactions.ExecStatement; import com.djrapitops.plan.storage.database.transactions.Executable; import com.djrapitops.plan.storage.database.transactions.ThrowawayTransaction; @@ -51,8 +53,8 @@ public class OperatorStatusTransaction extends ThrowawayTransaction { private Executable updateOperatorStatus() { String sql = Update.values(UserInfoTable.TABLE_NAME, UserInfoTable.OP) - .where(UserInfoTable.USER_UUID + "=?") - .and(UserInfoTable.SERVER_UUID + "=?") + .where(UserInfoTable.USER_ID + "=" + UsersTable.SELECT_USER_ID) + .and(UserInfoTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID) .toString(); return new ExecStatement(sql) { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/PingStoreTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/PingStoreTransaction.java index a88f2a787..79aefaa78 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/PingStoreTransaction.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/PingStoreTransaction.java @@ -17,9 +17,11 @@ package com.djrapitops.plan.storage.database.transactions.events; import com.djrapitops.plan.delivery.domain.DateObj; +import com.djrapitops.plan.exceptions.database.DBOpException; import com.djrapitops.plan.gathering.domain.Ping; import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.queries.DataStoreQueries; +import com.djrapitops.plan.storage.database.queries.PlayerFetchQueries; import com.djrapitops.plan.storage.database.transactions.Transaction; import com.djrapitops.plan.utilities.Predicates; import com.djrapitops.plan.utilities.analysis.Median; @@ -48,7 +50,43 @@ public class PingStoreTransaction extends Transaction { @Override protected void performOperations() { Ping ping = calculateAggregatePing(); - execute(DataStoreQueries.storePing(playerUUID, serverUUID, ping)); + + DBOpException userInsertError = null; + if (Boolean.FALSE.equals(query(PlayerFetchQueries.isPlayerRegistered(playerUUID)))) { + userInsertError = tryToRegisterUser(ping.getDate()); + } + + try { + execute(DataStoreQueries.storePing(playerUUID, serverUUID, ping)); + } catch (DBOpException failed) { + if (userInsertError != null) failed.addSuppressed(userInsertError); + if (failed.isUserIdConstraintViolation()) { + retry(ping, failed); + } else { + throw failed; + } + } + } + + private void retry(Ping ping, DBOpException failed) { + DBOpException userInsertError = null; + try { + userInsertError = tryToRegisterUser(ping.getDate()); + execute(DataStoreQueries.storePing(playerUUID, serverUUID, ping)); + } catch (DBOpException failedAgain) { + if (userInsertError != null) failedAgain.addSuppressed(userInsertError); + failedAgain.addSuppressed(failed); + throw failedAgain; + } + } + + private DBOpException tryToRegisterUser(long date) { + try { + execute(DataStoreQueries.registerBaseUser(playerUUID, date, playerUUID.toString())); + return null; + } catch (DBOpException failedInsert) { + return failedInsert; + } } private Ping calculateAggregatePing() { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/PlayerRegisterTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/PlayerRegisterTransaction.java index e0eb371a4..c71377822 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/PlayerRegisterTransaction.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/PlayerRegisterTransaction.java @@ -22,6 +22,7 @@ import com.djrapitops.plan.storage.database.queries.DataStoreQueries; import com.djrapitops.plan.storage.database.queries.PlayerFetchQueries; import com.djrapitops.plan.storage.database.transactions.Transaction; +import java.util.Optional; import java.util.UUID; import java.util.function.LongSupplier; @@ -36,6 +37,8 @@ public class PlayerRegisterTransaction extends Transaction { protected final LongSupplier registered; private final String playerName; + private Integer userId; + public PlayerRegisterTransaction(UUID playerUUID, LongSupplier registered, String playerName) { this.playerUUID = playerUUID; this.registered = registered; @@ -54,12 +57,14 @@ public class PlayerRegisterTransaction extends Transaction { insertUser(registerDate); SessionCache.getCachedSession(playerUUID).ifPresent(session -> session.setAsFirstSessionIfMatches(registerDate)); } - execute(DataStoreQueries.updatePlayerName(playerUUID, playerName)); + if (!playerUUID.toString().equals(playerName)) { + execute(DataStoreQueries.updatePlayerName(playerUUID, playerName)); + } } private void insertUser(long registerDate) { try { - execute(DataStoreQueries.registerBaseUser(playerUUID, registerDate, playerName)); + userId = executeReturningId(DataStoreQueries.registerBaseUser(playerUUID, registerDate, playerName)); } catch (DBOpException failed) { boolean alreadySaved = failed.getMessage().contains("Duplicate entry"); if (!alreadySaved) { @@ -67,4 +72,8 @@ public class PlayerRegisterTransaction extends Transaction { } } } + + public Optional getUserId() { + return Optional.ofNullable(userId); + } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/ShutdownDataPreservationTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/ShutdownDataPreservationTransaction.java new file mode 100644 index 000000000..12483bf2f --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/ShutdownDataPreservationTransaction.java @@ -0,0 +1,83 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.storage.database.transactions.events; + +import com.djrapitops.plan.delivery.domain.PlayerName; +import com.djrapitops.plan.gathering.domain.FinishedSession; +import com.djrapitops.plan.gathering.domain.PlayerKill; +import com.djrapitops.plan.gathering.domain.PlayerKills; +import com.djrapitops.plan.storage.database.queries.LargeStoreQueries; +import com.djrapitops.plan.storage.database.queries.objects.BaseUserQueries; +import com.djrapitops.plan.storage.database.transactions.Transaction; + +import java.util.*; +import java.util.function.LongSupplier; + +public class ShutdownDataPreservationTransaction extends Transaction { + + private final List finishedSessions; + + public ShutdownDataPreservationTransaction(List finishedSessions) { + this.finishedSessions = finishedSessions; + } + + @Override + protected void performOperations() { + ensureAllPlayersAreRegistered(); + + execute(LargeStoreQueries.storeAllSessionsWithKillAndWorldData(finishedSessions)); + } + + private void ensureAllPlayersAreRegistered() { + Set playerUUIDs = new HashSet<>(); + Map playerNames = new HashMap<>(); + Map earliestDates = new HashMap<>(); + for (FinishedSession finishedSession : finishedSessions) { + UUID playerUUID = finishedSession.getPlayerUUID(); + playerUUIDs.add(playerUUID); + finishedSession.getExtraData(PlayerKills.class) + .map(PlayerKills::asList) + .ifPresent(kills -> { + for (PlayerKill kill : kills) { + playerUUIDs.add(kill.getKiller().getUuid()); + playerUUIDs.add(kill.getVictim().getUuid()); + } + }); + + finishedSession.getExtraData(PlayerName.class) + .map(PlayerName::get) + .ifPresent(playerName -> playerNames.put(playerUUID, playerName)); + long start = finishedSession.getStart(); + Long previous = earliestDates.get(playerUUID); + if (previous == null || start < previous) { + earliestDates.put(playerUUID, start); + } + } + + Set existingUUIDs = query(BaseUserQueries.fetchExistingUUIDs(playerUUIDs)); + + for (UUID playerUUID : playerUUIDs) { + if (!existingUUIDs.contains(playerUUID)) { + LongSupplier registerDate = () -> Optional.ofNullable(earliestDates.get(playerUUID)) + .orElseGet(System::currentTimeMillis); + String playerName = Optional.ofNullable(playerNames.get(playerUUID)) + .orElseGet(playerUUID::toString); + executeOther(new PlayerRegisterTransaction(playerUUID, registerDate, playerName)); + } + } + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/GeoInfoStoreTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/StoreGeoInfoTransaction.java similarity index 60% rename from Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/GeoInfoStoreTransaction.java rename to Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/StoreGeoInfoTransaction.java index ee912a5fd..8aeeafdeb 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/GeoInfoStoreTransaction.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/StoreGeoInfoTransaction.java @@ -16,8 +16,10 @@ */ package com.djrapitops.plan.storage.database.transactions.events; +import com.djrapitops.plan.exceptions.database.DBOpException; import com.djrapitops.plan.gathering.domain.GeoInfo; import com.djrapitops.plan.storage.database.queries.DataStoreQueries; +import com.djrapitops.plan.storage.database.queries.PlayerFetchQueries; import com.djrapitops.plan.storage.database.transactions.Transaction; import java.net.InetAddress; @@ -29,7 +31,7 @@ import java.util.function.UnaryOperator; * * @author AuroraLS3 */ -public class GeoInfoStoreTransaction extends Transaction { +public class StoreGeoInfoTransaction extends Transaction { private final UUID playerUUID; private String ip; @@ -38,14 +40,14 @@ public class GeoInfoStoreTransaction extends Transaction { private GeoInfo geoInfo; - public GeoInfoStoreTransaction(UUID playerUUID, String ip, long time, UnaryOperator geolocationFunction) { + public StoreGeoInfoTransaction(UUID playerUUID, String ip, long time, UnaryOperator geolocationFunction) { this.playerUUID = playerUUID; this.ip = ip; this.time = time; this.geolocationFunction = geolocationFunction; } - public GeoInfoStoreTransaction( + public StoreGeoInfoTransaction( UUID playerUUID, InetAddress ip, long time, @@ -57,7 +59,7 @@ public class GeoInfoStoreTransaction extends Transaction { this.geolocationFunction = geolocationFunction; } - public GeoInfoStoreTransaction(UUID playerUUID, GeoInfo geoInfo) { + public StoreGeoInfoTransaction(UUID playerUUID, GeoInfo geoInfo) { this.playerUUID = playerUUID; this.geoInfo = geoInfo; } @@ -72,6 +74,37 @@ public class GeoInfoStoreTransaction extends Transaction { protected void performOperations() { if (geoInfo == null) geoInfo = createGeoInfo(); if (geoInfo.getGeolocation() == null) return; // Don't save null geolocation. - execute(DataStoreQueries.storeGeoInfo(playerUUID, geoInfo)); + + if (Boolean.FALSE.equals(query(PlayerFetchQueries.isPlayerRegistered(playerUUID)))) { + registerPlayer(); + } + + try { + execute(DataStoreQueries.storeGeoInfo(playerUUID, geoInfo)); + } catch (DBOpException failed) { + if (failed.isUserIdConstraintViolation()) { + retry(failed); + } else { + throw failed; + } + } + } + + private void retry(DBOpException failed) { + try { + executeOther(new PlayerRegisterTransaction(playerUUID, System::currentTimeMillis, playerUUID.toString())); + execute(DataStoreQueries.storeGeoInfo(playerUUID, geoInfo)); + } catch (DBOpException failedAgain) { + failedAgain.addSuppressed(failed); + throw failedAgain; + } + } + + private void registerPlayer() { + try { + execute(DataStoreQueries.registerBaseUser(playerUUID, geoInfo.getDate(), playerUUID.toString())); + } catch (DBOpException ignored) { + // Ignored. Likely that another transaction managed to insert first. + } } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/StoreJoinAddressTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/StoreJoinAddressTransaction.java new file mode 100644 index 000000000..c1227d411 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/StoreJoinAddressTransaction.java @@ -0,0 +1,76 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.storage.database.transactions.events; + +import com.djrapitops.plan.delivery.domain.container.CachingSupplier; +import com.djrapitops.plan.storage.database.queries.HasMoreThanZeroQueryStatement; +import com.djrapitops.plan.storage.database.queries.Query; +import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable; +import com.djrapitops.plan.storage.database.transactions.ExecStatement; +import com.djrapitops.plan.storage.database.transactions.Transaction; +import org.apache.commons.lang3.StringUtils; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.function.Supplier; + +import static com.djrapitops.plan.storage.database.sql.building.Sql.*; + +public class StoreJoinAddressTransaction extends Transaction { + + private final Supplier joinAddress; + + public StoreJoinAddressTransaction(String joinAddress) { + this(() -> joinAddress); + } + + public StoreJoinAddressTransaction(Supplier joinAddress) { + this.joinAddress = new CachingSupplier<>(joinAddress); + } + + @Override + protected boolean shouldBeExecuted() { + return !query(hasAddressAlready()); + } + + private Query hasAddressAlready() { + String sql = SELECT + "COUNT(*) as c" + + FROM + JoinAddressTable.TABLE_NAME + + WHERE + JoinAddressTable.JOIN_ADDRESS + "=?"; + return new HasMoreThanZeroQueryStatement(sql) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + String address = getAddress(); + statement.setString(1, address); + } + }; + } + + private String getAddress() { + return StringUtils.truncate(joinAddress.get(), JoinAddressTable.JOIN_ADDRESS_MAX_LENGTH); + } + + @Override + protected void performOperations() { + execute(new ExecStatement(JoinAddressTable.INSERT_STATEMENT) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setString(1, getAddress()); + } + }); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/NicknameStoreTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/StoreNicknameTransaction.java similarity index 93% rename from Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/NicknameStoreTransaction.java rename to Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/StoreNicknameTransaction.java index 18deb7af0..dad78647b 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/NicknameStoreTransaction.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/StoreNicknameTransaction.java @@ -28,13 +28,13 @@ import java.util.function.BiPredicate; * * @author AuroraLS3 */ -public class NicknameStoreTransaction extends ThrowawayTransaction { +public class StoreNicknameTransaction extends ThrowawayTransaction { private final UUID playerUUID; private final Nickname nickname; private final BiPredicate isNicknameCachedCheck; - public NicknameStoreTransaction(UUID playerUUID, Nickname nickname, BiPredicate isNicknameCachedCheck) { + public StoreNicknameTransaction(UUID playerUUID, Nickname nickname, BiPredicate isNicknameCachedCheck) { this.playerUUID = playerUUID; this.nickname = nickname; this.isNicknameCachedCheck = isNicknameCachedCheck; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/StoreRequestTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/StoreRequestTransaction.java new file mode 100644 index 000000000..63dc7516f --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/StoreRequestTransaction.java @@ -0,0 +1,66 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.storage.database.transactions.events; + +import com.djrapitops.plan.delivery.web.resolver.Response; +import com.djrapitops.plan.delivery.web.resolver.request.Request; +import com.djrapitops.plan.delivery.webserver.configuration.WebserverConfiguration; +import com.djrapitops.plan.delivery.webserver.http.InternalRequest; +import com.djrapitops.plan.storage.database.sql.tables.AccessLogTable; +import com.djrapitops.plan.storage.database.transactions.ExecStatement; +import com.djrapitops.plan.storage.database.transactions.Transaction; +import org.apache.commons.lang3.StringUtils; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +public class StoreRequestTransaction extends Transaction { + + private final WebserverConfiguration webserverConfiguration; + + private final InternalRequest internalRequest; + private final Request request; // can be null + private final Response response; + + public StoreRequestTransaction(WebserverConfiguration webserverConfiguration, InternalRequest internalRequest, Request request, Response response) { + this.webserverConfiguration = webserverConfiguration; + this.internalRequest = internalRequest; + this.request = request; + this.response = response; + } + + @Override + protected void performOperations() { + execute(new ExecStatement(AccessLogTable.INSERT_NO_USER) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setLong(1, internalRequest.getTimestamp()); + statement.setString(2, internalRequest.getAccessAddress(webserverConfiguration)); + String method = internalRequest.getMethod(); + statement.setString(3, method != null ? method : "?"); + statement.setString(4, getTruncatedURI()); + statement.setInt(5, response.getCode()); + } + }); + } + + private String getTruncatedURI() { + String uri = request != null ? request.getPath().asString() + request.getQuery().asString() + : internalRequest.getRequestedURIString(); + return StringUtils.truncate(uri, 65000); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/PlayerServerRegisterTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/StoreServerPlayerTransaction.java similarity index 85% rename from Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/PlayerServerRegisterTransaction.java rename to Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/StoreServerPlayerTransaction.java index 89f73b5b4..54ced4090 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/PlayerServerRegisterTransaction.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/StoreServerPlayerTransaction.java @@ -31,18 +31,22 @@ import java.util.function.Supplier; * * @author AuroraLS3 */ -public class PlayerServerRegisterTransaction extends PlayerRegisterTransaction { +public class StoreServerPlayerTransaction extends PlayerRegisterTransaction { private final ServerUUID serverUUID; private final Supplier getJoinAddress; - public PlayerServerRegisterTransaction(UUID playerUUID, LongSupplier registered, - String playerName, ServerUUID serverUUID, Supplier getJoinAddress) { + public StoreServerPlayerTransaction(UUID playerUUID, LongSupplier registered, + String playerName, ServerUUID serverUUID, Supplier getJoinAddress) { super(playerUUID, registered, playerName); this.serverUUID = serverUUID; this.getJoinAddress = getJoinAddress; } + public StoreServerPlayerTransaction(UUID playerUUID, long registerDate, String name, ServerUUID serverUUID, String joinAddress) { + this(playerUUID, () -> registerDate, name, serverUUID, () -> joinAddress); + } + @Override protected void performOperations() { super.performOperations(); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/StoreSessionTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/StoreSessionTransaction.java new file mode 100644 index 000000000..536ff61c1 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/StoreSessionTransaction.java @@ -0,0 +1,91 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.storage.database.transactions.events; + +import com.djrapitops.plan.delivery.domain.PlayerName; +import com.djrapitops.plan.exceptions.database.DBOpException; +import com.djrapitops.plan.gathering.domain.FinishedSession; +import com.djrapitops.plan.gathering.domain.event.JoinAddress; +import com.djrapitops.plan.storage.database.queries.DataStoreQueries; +import com.djrapitops.plan.storage.database.queries.PlayerFetchQueries; +import com.djrapitops.plan.storage.database.transactions.Transaction; + +import java.util.UUID; + +/** + * Transaction for storing a session after a session has ended. + * + * @author AuroraLS3 + */ +public class StoreSessionTransaction extends Transaction { + + private final FinishedSession session; + + public StoreSessionTransaction(FinishedSession session) { + this.session = session; + } + + @Override + protected void performOperations() { + if (Boolean.FALSE.equals(query(PlayerFetchQueries.isPlayerRegistered(session.getPlayerUUID())))) { + registerPlayer(); + } + try { + storeSession(); + } catch (DBOpException failed) { + if (failed.isUserIdConstraintViolation()) { + retry(failed); + } else { + throw failed; + } + } + } + + private void storeSession() { + storeJoinAddressIfPresent(); + execute(DataStoreQueries.storeSession(session)); + } + + private void storeJoinAddressIfPresent() { + session.getExtraData(JoinAddress.class) + .map(JoinAddress::getAddress) + .map(StoreJoinAddressTransaction::new) + .ifPresent(this::executeOther); + } + + private void retry(DBOpException failed) { + try { + registerPlayer(); + storeSession(); + } catch (DBOpException anotherFail) { + anotherFail.addSuppressed(failed); + throw anotherFail; + } + } + + private void registerPlayer() { + try { + UUID playerUUID = session.getPlayerUUID(); + String playerName = session.getExtraData(PlayerName.class) + .map(PlayerName::get) + .orElseGet(playerUUID::toString); + execute(DataStoreQueries.registerBaseUser(playerUUID, session.getStart(), playerName)); + } catch (DBOpException ignored) { + // Ignored. Likely that another transaction managed to insert first. + } + } +} \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/WorldNameStoreTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/StoreWorldNameTransaction.java similarity index 95% rename from Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/WorldNameStoreTransaction.java rename to Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/StoreWorldNameTransaction.java index 92564e757..ec5649226 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/WorldNameStoreTransaction.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/StoreWorldNameTransaction.java @@ -33,12 +33,12 @@ import static com.djrapitops.plan.storage.database.sql.building.Sql.*; * * @author AuroraLS3 */ -public class WorldNameStoreTransaction extends Transaction { +public class StoreWorldNameTransaction extends Transaction { private final ServerUUID serverUUID; private final String worldName; - public WorldNameStoreTransaction(ServerUUID serverUUID, String worldName) { + public StoreWorldNameTransaction(ServerUUID serverUUID, String worldName) { this.serverUUID = serverUUID; this.worldName = worldName; } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/init/CreateIndexTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/init/CreateIndexTransaction.java index 8b584b9fd..c9bb68aae 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/init/CreateIndexTransaction.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/init/CreateIndexTransaction.java @@ -34,21 +34,18 @@ public class CreateIndexTransaction extends Transaction { createIndex(UsersTable.TABLE_NAME, "plan_users_uuid_index", UsersTable.USER_UUID ); - createIndex(UserInfoTable.TABLE_NAME, "plan_user_info_uuid_index", - UserInfoTable.USER_UUID, - UserInfoTable.SERVER_UUID - ); - createIndex(SessionsTable.TABLE_NAME, "plan_sessions_uuid_index", - SessionsTable.USER_UUID, - SessionsTable.SERVER_UUID - ); + + // replaced by foreign keys + dropIndex(UserInfoTable.TABLE_NAME, "plan_user_info_uuid_index"); + // replaced by foreign keys + dropIndex(SessionsTable.TABLE_NAME, "plan_sessions_uuid_index"); + createIndex(SessionsTable.TABLE_NAME, "plan_sessions_date_index", SessionsTable.SESSION_START ); - createIndex(WorldTimesTable.TABLE_NAME, "plan_world_times_uuid_index", - WorldTimesTable.USER_UUID, - WorldTimesTable.SERVER_UUID - ); + // Replaced by foreign keys + dropIndex(WorldTimesTable.TABLE_NAME, "plan_world_times_uuid_index"); + createIndex(KillsTable.TABLE_NAME, "plan_kills_uuid_index", KillsTable.KILLER_UUID, KillsTable.VICTIM_UUID, @@ -57,16 +54,18 @@ public class CreateIndexTransaction extends Transaction { createIndex(KillsTable.TABLE_NAME, "plan_kills_date_index", KillsTable.DATE ); - createIndex(PingTable.TABLE_NAME, "plan_ping_uuid_index", - PingTable.USER_UUID, - PingTable.SERVER_UUID - ); + // Replaced with foreign keys. + dropIndex(PingTable.TABLE_NAME, "plan_ping_uuid_index"); + createIndex(PingTable.TABLE_NAME, "plan_ping_date_index", PingTable.DATE ); createIndex(TPSTable.TABLE_NAME, "plan_tps_date_index", TPSTable.DATE ); + + createIndex(SessionsTable.TABLE_NAME, "plan_session_join_address_index", + SessionsTable.JOIN_ADDRESS_ID); } private void createIndex(String tableName, String indexName, String... indexedColumns) { @@ -92,4 +91,15 @@ public class CreateIndexTransaction extends Transaction { execute(sql.toString()); } + + private void dropIndex(String tableName, String indexName) { + boolean isMySQL = dbType == DBType.MYSQL; + if (isMySQL) { + boolean indexExists = query(MySQLSchemaQueries.doesIndexExist(indexName, tableName)); + if (!indexExists) return; + execute("DROP INDEX " + indexName + " ON " + tableName); + } else { + execute("DROP INDEX IF EXISTS " + indexName); + } + } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/init/CreateTablesTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/init/CreateTablesTransaction.java index c43a713ef..b37551843 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/init/CreateTablesTransaction.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/init/CreateTablesTransaction.java @@ -17,6 +17,7 @@ package com.djrapitops.plan.storage.database.transactions.init; import com.djrapitops.plan.storage.database.sql.tables.*; +import com.djrapitops.plan.storage.database.transactions.events.StoreJoinAddressTransaction; /** * Transaction that creates the table schema of Plan database. @@ -36,6 +37,8 @@ public class CreateTablesTransaction extends OperationCriticalTransaction { execute(UserInfoTable.createTableSQL(dbType)); execute(GeoInfoTable.createTableSQL(dbType)); execute(NicknamesTable.createTableSQL(dbType)); + execute(JoinAddressTable.createTableSQL(dbType)); + executeOther(new StoreJoinAddressTransaction(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP)); execute(SessionsTable.createTableSQL(dbType)); execute(KillsTable.createTableSQL(dbType)); execute(PingTable.createTableSQL(dbType)); @@ -45,6 +48,7 @@ public class CreateTablesTransaction extends OperationCriticalTransaction { execute(SecurityTable.createTableSQL(dbType)); execute(SettingsTable.createTableSQL(dbType)); execute(CookieTable.createTableSQL(dbType)); + execute(AccessLogTable.createTableSql(dbType)); // DataExtension tables execute(ExtensionIconTable.createTableSQL(dbType)); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/init/RemoveDuplicateUserInfoTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/init/RemoveDuplicateUserInfoTransaction.java index 40557935f..a370b2330 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/init/RemoveDuplicateUserInfoTransaction.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/init/RemoveDuplicateUserInfoTransaction.java @@ -44,8 +44,8 @@ public class RemoveDuplicateUserInfoTransaction extends ThrowawayTransaction { SELECT + DISTINCT + "u2." + UserInfoTable.ID + " as id" + FROM + UserInfoTable.TABLE_NAME + " u1" + INNER_JOIN + UserInfoTable.TABLE_NAME + " u2 on " + - "u1." + UserInfoTable.USER_UUID + "=u2." + UserInfoTable.USER_UUID + AND + - "u1." + UserInfoTable.SERVER_UUID + "=u2." + UserInfoTable.SERVER_UUID + AND + + "u1." + UserInfoTable.USER_ID + "=u2." + UserInfoTable.USER_ID + AND + + "u1." + UserInfoTable.SERVER_ID + "=u2." + UserInfoTable.SERVER_ID + AND + "u1." + UserInfoTable.ID + " getDuplicates() { - return query(new QueryAllStatement>(STATEMENT_SELECT_DUPLICATE_IDS) { + return query(new QueryAllStatement<>(STATEMENT_SELECT_DUPLICATE_IDS) { @Override public Collection processResults(ResultSet set) throws SQLException { Set duplicateIDs = new HashSet<>(); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/init/RemoveOldAccessLogTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/init/RemoveOldAccessLogTransaction.java new file mode 100644 index 000000000..beba357cd --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/init/RemoveOldAccessLogTransaction.java @@ -0,0 +1,37 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.storage.database.transactions.init; + +import com.djrapitops.plan.storage.database.sql.tables.AccessLogTable; +import com.djrapitops.plan.storage.database.transactions.ThrowawayTransaction; + +import static com.djrapitops.plan.storage.database.sql.building.Sql.DELETE_FROM; +import static com.djrapitops.plan.storage.database.sql.building.Sql.WHERE; + +public class RemoveOldAccessLogTransaction extends ThrowawayTransaction { + + private final long thresholdMs; + + public RemoveOldAccessLogTransaction(long thresholdMs) { + this.thresholdMs = thresholdMs; + } + + @Override + protected void performOperations() { + execute(DELETE_FROM + AccessLogTable.TABLE_NAME + WHERE + AccessLogTable.TIME + "<" + (System.currentTimeMillis() - thresholdMs)); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/init/RemoveOldExtensionsTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/init/RemoveOldExtensionsTransaction.java index 805aa9912..debc61c79 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/init/RemoveOldExtensionsTransaction.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/init/RemoveOldExtensionsTransaction.java @@ -17,6 +17,7 @@ package com.djrapitops.plan.storage.database.transactions.init; import com.djrapitops.plan.identification.ServerUUID; +import com.djrapitops.plan.settings.config.ExtensionSettings; import com.djrapitops.plan.storage.database.queries.Query; import com.djrapitops.plan.storage.database.queries.QueryStatement; import com.djrapitops.plan.storage.database.sql.tables.*; @@ -38,10 +39,12 @@ import static com.djrapitops.plan.storage.database.sql.building.Sql.*; */ public class RemoveOldExtensionsTransaction extends ThrowawayTransaction { + private final ExtensionSettings extensionSettings; private final long deleteOlder; private final ServerUUID serverUUID; - public RemoveOldExtensionsTransaction(long deleteAfterMs, ServerUUID serverUUID) { + public RemoveOldExtensionsTransaction(ExtensionSettings extensionSettings, long deleteAfterMs, ServerUUID serverUUID) { + this.extensionSettings = extensionSettings; deleteOlder = System.currentTimeMillis() - deleteAfterMs; this.serverUUID = serverUUID; } @@ -120,23 +123,27 @@ public class RemoveOldExtensionsTransaction extends ThrowawayTransaction { } private Query> inactiveProviderIDsQuery() { - String sql = SELECT + "pr." + ExtensionProviderTable.ID + + String sql = SELECT + "pr." + ExtensionProviderTable.ID + ',' + + "pl." + ExtensionPluginTable.LAST_UPDATED + ',' + + "pl." + ExtensionPluginTable.PLUGIN_NAME + FROM + ExtensionProviderTable.TABLE_NAME + " pr" + INNER_JOIN + ExtensionPluginTable.TABLE_NAME + " pl on pl." + ExtensionPluginTable.ID + "=pr." + ExtensionProviderTable.PLUGIN_ID + - WHERE + ExtensionPluginTable.LAST_UPDATED + ">(sql, 100) { + WHERE + ExtensionPluginTable.SERVER_UUID + "=?"; + return new QueryStatement<>(sql, 100) { @Override public void prepare(PreparedStatement statement) throws SQLException { - statement.setLong(1, deleteOlder); - statement.setString(2, serverUUID.toString()); + statement.setString(1, serverUUID.toString()); } @Override public Collection processResults(ResultSet set) throws SQLException { Collection providerIds = new HashSet<>(); while (set.next()) { - providerIds.add(set.getInt(ExtensionProviderTable.ID)); + boolean manuallyDisabled = !extensionSettings.isEnabled(set.getString(ExtensionPluginTable.PLUGIN_NAME)); + boolean dataIsOld = set.getLong(ExtensionPluginTable.LAST_UPDATED) < deleteOlder; + if (manuallyDisabled || dataIsOld) { + providerIds.add(set.getInt(ExtensionProviderTable.ID)); + } } return providerIds; } @@ -149,7 +156,7 @@ public class RemoveOldExtensionsTransaction extends ThrowawayTransaction { INNER_JOIN + ExtensionPluginTable.TABLE_NAME + " pl on pl." + ExtensionPluginTable.ID + "=pr." + ExtensionTableProviderTable.PLUGIN_ID + WHERE + ExtensionPluginTable.LAST_UPDATED + ">(sql, 100) { + return new QueryStatement<>(sql, 100) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setLong(1, deleteOlder); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/init/RemoveOldSampledDataTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/init/RemoveOldSampledDataTransaction.java index 0c59d86fe..e34f571eb 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/init/RemoveOldSampledDataTransaction.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/init/RemoveOldSampledDataTransaction.java @@ -65,7 +65,7 @@ public class RemoveOldSampledDataTransaction extends ThrowawayTransaction { String sql = DELETE_FROM + TPSTable.TABLE_NAME + WHERE + TPSTable.DATE + ". + */ +package com.djrapitops.plan.storage.database.transactions.patches; + +import com.djrapitops.plan.identification.ServerUUID; +import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable; +import com.djrapitops.plan.storage.database.sql.tables.ServerTable; +import com.djrapitops.plan.storage.database.sql.tables.SessionsTable; +import com.djrapitops.plan.storage.database.sql.tables.UserInfoTable; +import com.djrapitops.plan.storage.database.transactions.ExecStatement; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; + +import static com.djrapitops.plan.storage.database.sql.building.Sql.*; + +/** + * Patch that removes player IPs that were gathered as join address on Fabric. + * + * @author AuroraLS3 + */ +public class BadFabricJoinAddressValuePatch extends Patch { + + private final ServerUUID serverUUID; + + public BadFabricJoinAddressValuePatch(ServerUUID serverUUID) {this.serverUUID = serverUUID;} + + @Override + public boolean hasBeenApplied() { + return false; // There is no good way to detect this, so this patch has to be applied manually + } + + @Override + protected void applyPatch() { + execute(new ExecStatement("UPDATE " + UserInfoTable.TABLE_NAME + " SET " + UserInfoTable.JOIN_ADDRESS + "=?" + + WHERE + UserInfoTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setNull(1, Types.VARCHAR); + statement.setString(2, serverUUID.toString()); + } + }); + execute(new ExecStatement("UPDATE " + SessionsTable.TABLE_NAME + + " SET " + SessionsTable.JOIN_ADDRESS_ID + "=" + JoinAddressTable.SELECT_ID + + WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setString(1, JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP); + statement.setString(2, serverUUID.toString()); + } + }); + execute("DELETE FROM " + JoinAddressTable.TABLE_NAME + + WHERE + JoinAddressTable.ID + " NOT IN (" + SELECT + DISTINCT + SessionsTable.JOIN_ADDRESS_ID + FROM + SessionsTable.TABLE_NAME + ")"); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/BadNukkitRegisterValuePatch.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/BadNukkitRegisterValuePatch.java index e21fe1dfa..ff6e552f9 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/BadNukkitRegisterValuePatch.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/BadNukkitRegisterValuePatch.java @@ -60,12 +60,14 @@ public class BadNukkitRegisterValuePatch extends Patch { private void multiplyInTable(String tableName, String registered) { String sql = "UPDATE " + tableName + " SET " + registered + "=" + registered + "*1000" + - WHERE + registered + "?"; execute(new ExecStatement(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setLong(1, System.currentTimeMillis() / 1000L); + statement.setLong(2, 0L); } }); } -} \ No newline at end of file +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/DeleteIPsPatch.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/DeleteIPsPatch.java index 91762c06f..c97a717a3 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/DeleteIPsPatch.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/DeleteIPsPatch.java @@ -58,11 +58,11 @@ public class DeleteIPsPatch extends Patch { execute(GeoInfoTable.createTableSQL(dbType)); execute("INSERT INTO " + GeoInfoTable.TABLE_NAME + " (" + - GeoInfoTable.USER_UUID + ',' + + GeoInfoTable.USER_ID + ',' + GeoInfoTable.LAST_USED + ',' + GeoInfoTable.GEOLOCATION + ") SELECT " + DISTINCT + - GeoInfoTable.USER_UUID + ',' + + GeoInfoTable.USER_ID + ',' + GeoInfoTable.LAST_USED + ',' + GeoInfoTable.GEOLOCATION + FROM + tempTableName @@ -72,13 +72,13 @@ public class DeleteIPsPatch extends Patch { } private boolean hasLessDataInPlanIPs() { - Integer inIPs = query(new QueryAllStatement(SELECT + "COUNT(1) as c" + FROM + oldTableName) { + Integer inIPs = query(new QueryAllStatement<>(SELECT + "COUNT(1) as c" + FROM + oldTableName) { @Override public Integer processResults(ResultSet set) throws SQLException { return set.next() ? set.getInt("c") : 0; } }); - Integer inGeoInfo = query(new QueryAllStatement(SELECT + "COUNT(1) as c" + FROM + GeoInfoTable.TABLE_NAME) { + Integer inGeoInfo = query(new QueryAllStatement<>(SELECT + "COUNT(1) as c" + FROM + GeoInfoTable.TABLE_NAME) { @Override public Integer processResults(ResultSet set) throws SQLException { return set.next() ? set.getInt("c") : 0; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/ExtensionTableProviderFormattersPatch.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/ExtensionTableProviderFormattersPatch.java new file mode 100644 index 000000000..3538f15e8 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/ExtensionTableProviderFormattersPatch.java @@ -0,0 +1,41 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.storage.database.transactions.patches; + +import com.djrapitops.plan.storage.database.sql.building.Sql; + +import static com.djrapitops.plan.storage.database.sql.tables.ExtensionTableProviderTable.*; + +/** + * Adds format_1 to _5 fields to plan_extension_tables table. + */ +public class ExtensionTableProviderFormattersPatch extends Patch { + + @Override + public boolean hasBeenApplied() { + return hasColumn(TABLE_NAME, FORMAT_1); + } + + @Override + protected void applyPatch() { + addColumn(TABLE_NAME, FORMAT_1 + " " + Sql.varchar(15)); + addColumn(TABLE_NAME, FORMAT_2 + " " + Sql.varchar(15)); + addColumn(TABLE_NAME, FORMAT_3 + " " + Sql.varchar(15)); + addColumn(TABLE_NAME, FORMAT_4 + " " + Sql.varchar(15)); + addColumn(TABLE_NAME, FORMAT_5 + " " + Sql.varchar(15)); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/ExtensionTableRowValueLengthPatch.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/ExtensionTableRowValueLengthPatch.java index 2c28dc9f1..b89dd8e43 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/ExtensionTableRowValueLengthPatch.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/ExtensionTableRowValueLengthPatch.java @@ -17,7 +17,6 @@ package com.djrapitops.plan.storage.database.transactions.patches; import com.djrapitops.plan.storage.database.DBType; -import com.djrapitops.plan.storage.database.queries.schema.MySQLSchemaQueries; import com.djrapitops.plan.storage.database.sql.building.Sql; import com.djrapitops.plan.storage.database.sql.tables.ExtensionPlayerTableValueTable; import com.djrapitops.plan.storage.database.sql.tables.ExtensionServerTableValueTable; @@ -40,12 +39,8 @@ public class ExtensionTableRowValueLengthPatch extends Patch { @Override public boolean hasBeenApplied() { return dbType == DBType.SQLITE || // SQLite does not limit varchar lengths - (columnVarcharLength(playerTable, ExtensionPlayerTableValueTable.VALUE_4) >= 250 - && columnVarcharLength(serverTable, ExtensionServerTableValueTable.VALUE_5) >= 250); - } - - private int columnVarcharLength(String table, String column) { - return query(MySQLSchemaQueries.columnVarcharLength(table, column)); + columnVarcharLength(playerTable, ExtensionPlayerTableValueTable.VALUE_4) >= 250 + && columnVarcharLength(serverTable, ExtensionServerTableValueTable.VALUE_5) >= 250; } @Override diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/GeoInfoOptimizationPatch.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/GeoInfoOptimizationPatch.java index 5c2b0b5b2..57f8870f9 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/GeoInfoOptimizationPatch.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/GeoInfoOptimizationPatch.java @@ -31,17 +31,17 @@ public class GeoInfoOptimizationPatch extends Patch { private final String oldTableName; public GeoInfoOptimizationPatch() { - oldTableName = "plan_ips"; - tempTableName = "temp_ips"; + oldTableName = GeoInfoTable.TABLE_NAME; + tempTableName = "temp_geoinformation"; } @Override public boolean hasBeenApplied() { return !hasTable(oldTableName) - || (hasColumn(oldTableName, GeoInfoTable.ID) - && hasColumn(oldTableName, GeoInfoTable.USER_UUID) - && !hasColumn(oldTableName, "user_id") - && !hasTable(tempTableName)); // If this table exists the patch has failed to finish. + || hasColumn(oldTableName, GeoInfoTable.ID) + && hasColumn(oldTableName, GeoInfoTable.USER_ID) + && !hasColumn(oldTableName, "uuid") + && !hasTable(tempTableName); // If this table exists the patch has failed to finish. } @Override @@ -50,11 +50,11 @@ public class GeoInfoOptimizationPatch extends Patch { execute(GeoInfoTable.createTableSQL(dbType)); execute("INSERT INTO " + GeoInfoTable.TABLE_NAME + " (" + - GeoInfoTable.USER_UUID + ',' + + GeoInfoTable.USER_ID + ',' + GeoInfoTable.LAST_USED + ',' + GeoInfoTable.GEOLOCATION + ") " + SELECT + DISTINCT + - "(SELECT plan_users.uuid FROM plan_users WHERE plan_users.id = " + tempTableName + ".user_id LIMIT 1), " + + "(SELECT plan_users.id FROM plan_users WHERE plan_users.uuid = " + tempTableName + ".uuid LIMIT 1)," + GeoInfoTable.LAST_USED + ',' + GeoInfoTable.GEOLOCATION + FROM + tempTableName diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/KillsServerIDPatch.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/KillsServerIDPatch.java index 5de7e6b84..ef3940d98 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/KillsServerIDPatch.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/KillsServerIDPatch.java @@ -41,7 +41,7 @@ public class KillsServerIDPatch extends Patch { // KillsOptimizationPatch makes this patch incompatible with newer patch versions. return hasColumn(tableName, KillsTable.SERVER_UUID) - || (hasColumn(tableName, columnName) && allValuesHaveValueZero(tableName, columnName)); + || hasColumn(tableName, columnName) && allValuesHaveValueZero(tableName, columnName); } @Override diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/LinkUsersToPlayersSecurityTablePatch.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/LinkUsersToPlayersSecurityTablePatch.java index feb29a2ae..21cd30436 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/LinkUsersToPlayersSecurityTablePatch.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/LinkUsersToPlayersSecurityTablePatch.java @@ -60,7 +60,7 @@ public class LinkUsersToPlayersSecurityTablePatch extends Patch { String sql = "UPDATE " + SecurityTable.TABLE_NAME + " SET " + SecurityTable.LINKED_TO + "=?" + WHERE + SecurityTable.USERNAME + "=?"; - Map byUsername = query(new QueryAllStatement>(querySQL) { + Map byUsername = query(new QueryAllStatement<>(querySQL) { @Override public Map processResults(ResultSet set) throws SQLException { Map byUsername = new HashMap<>(); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/LitebansTableHeaderPatch.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/LitebansTableHeaderPatch.java index 16bc4938d..e5749b9dc 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/LitebansTableHeaderPatch.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/LitebansTableHeaderPatch.java @@ -50,7 +50,7 @@ public class LitebansTableHeaderPatch extends Patch { OR + "pr." + ExtensionTableProviderTable.PROVIDER_NAME + "=?" + OR + "pr." + ExtensionTableProviderTable.PROVIDER_NAME + "=?" + OR + "pr." + ExtensionTableProviderTable.PROVIDER_NAME + "=?)"; - found = query(new QueryStatement>(sql) { + found = query(new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, "Litebans"); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/NicknameLastSeenPatch.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/NicknameLastSeenPatch.java index 0382605a9..048dba2d2 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/NicknameLastSeenPatch.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/NicknameLastSeenPatch.java @@ -78,15 +78,15 @@ public class NicknameLastSeenPatch extends Patch { private Map getServerUUIDsByID() { String sql = Select.from(ServerTable.TABLE_NAME, - ServerTable.SERVER_ID, ServerTable.SERVER_UUID) + ServerTable.ID, ServerTable.SERVER_UUID) .toString(); - return query(new QueryAllStatement>(sql) { + return query(new QueryAllStatement<>(sql) { @Override public Map processResults(ResultSet set) throws SQLException { Map uuids = new HashMap<>(); while (set.next()) { - int id = set.getInt(ServerTable.SERVER_ID); + int id = set.getInt(ServerTable.ID); uuids.put(id, ServerUUID.fromString(set.getString(ServerTable.SERVER_UUID))); } return uuids; @@ -96,7 +96,7 @@ public class NicknameLastSeenPatch extends Patch { private Map> getNicknamesByUserID(Map serverUUIDsByID) { String fetchSQL = "SELECT * FROM plan_actions WHERE action_id=3 ORDER BY date DESC"; - return query(new QueryAllStatement>>(fetchSQL, 10000) { + return query(new QueryAllStatement<>(fetchSQL, 10000) { @Override public Map> processResults(ResultSet set) throws SQLException { Map> map = new HashMap<>(); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/Patch.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/Patch.java index 35f0c3be4..34d3af401 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/Patch.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/Patch.java @@ -72,17 +72,6 @@ public abstract class Patch extends OperationCriticalTransaction { execute("SET FOREIGN_KEY_CHECKS=0"); } - protected boolean hasTable(String tableName) { - switch (dbType) { - case SQLITE: - return query(SQLiteSchemaQueries.doesTableExist(tableName)); - case MYSQL: - return query(MySQLSchemaQueries.doesTableExist(tableName)); - default: - throw new IllegalStateException("Unsupported Database Type: " + dbType.getName()); - } - } - protected boolean hasColumn(String tableName, String columnName) { switch (dbType) { case MYSQL: @@ -145,7 +134,7 @@ public abstract class Patch extends OperationCriticalTransaction { protected boolean allValuesHaveValueZero(String tableName, String column) { String sql = SELECT + '*' + FROM + tableName + WHERE + column + "=? LIMIT 1"; - return query(new QueryStatement(sql) { + return query(new QueryStatement<>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setInt(1, 0); @@ -157,4 +146,8 @@ public abstract class Patch extends OperationCriticalTransaction { } }); } + + protected int columnVarcharLength(String table, String column) { + return dbType == DBType.SQLITE ? Integer.MAX_VALUE : query(MySQLSchemaQueries.columnVarcharLength(table, column)); + } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/PingOptimizationPatch.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/PingOptimizationPatch.java index 0b3f4bb89..329ce9d1d 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/PingOptimizationPatch.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/PingOptimizationPatch.java @@ -22,7 +22,7 @@ import com.djrapitops.plan.storage.database.sql.tables.PingTable; import static com.djrapitops.plan.storage.database.sql.building.Sql.FROM; /** - * Replaces user_id and server_id foreign keys with respective uuid fields in ping table. + * Replaces uuid and server_uuid with foreign keys in ping table. * * @author AuroraLS3 */ @@ -38,10 +38,10 @@ public class PingOptimizationPatch extends Patch { @Override public boolean hasBeenApplied() { - return hasColumn(tableName, PingTable.USER_UUID) - && hasColumn(tableName, PingTable.SERVER_UUID) - && !hasColumn(tableName, "user_id") - && !hasColumn(tableName, "server_id") + return hasColumn(tableName, PingTable.USER_ID) + && hasColumn(tableName, PingTable.SERVER_ID) + && !hasColumn(tableName, "uuid") + && !hasColumn(tableName, "server_uuid") && !hasTable(tempTableName); // If this table exists the patch has failed to finish. } @@ -52,16 +52,16 @@ public class PingOptimizationPatch extends Patch { execute(PingTable.createTableSQL(dbType)); execute("INSERT INTO " + tableName + " (" + - PingTable.USER_UUID + ',' + - PingTable.SERVER_UUID + ',' + + PingTable.USER_ID + ',' + + PingTable.SERVER_ID + ',' + PingTable.ID + ',' + PingTable.MIN_PING + ',' + PingTable.MAX_PING + ',' + PingTable.AVG_PING + ',' + PingTable.DATE + ") SELECT " + - "(SELECT plan_users.uuid FROM plan_users WHERE plan_users.id = " + tempTableName + ".user_id LIMIT 1), " + - "(SELECT plan_servers.uuid FROM plan_servers WHERE plan_servers.id = " + tempTableName + ".server_id LIMIT 1), " + + "(SELECT plan_users.id FROM plan_users WHERE plan_users.uuid = " + tempTableName + ".uuid LIMIT 1), " + + "(SELECT plan_servers.id FROM plan_servers WHERE plan_servers.uuid = " + tempTableName + ".server_uuid LIMIT 1), " + PingTable.ID + ',' + PingTable.MIN_PING + ',' + PingTable.MAX_PING + ',' + diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/PlayerTableRowPatch.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/PlayerTableRowPatch.java index 538a5f338..60d784376 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/PlayerTableRowPatch.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/PlayerTableRowPatch.java @@ -47,7 +47,7 @@ public class PlayerTableRowPatch extends Patch { FROM + TABLE_NAME + WHERE + TABLE_ROW + "=?" + GROUP_BY + TABLE_ID; - return query(new QueryStatement(columnCountPerTableSql) { + return query(new QueryStatement<>(columnCountPerTableSql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setInt(1, 0); @@ -92,7 +92,7 @@ public class PlayerTableRowPatch extends Patch { public Map> fetchTableRowIds() { String columnCountPerTableSql = SELECT + TABLE_ID + ',' + ID + FROM + TABLE_NAME; - return query(new QueryAllStatement>>(columnCountPerTableSql) { + return query(new QueryAllStatement<>(columnCountPerTableSql) { @Override public Map> processResults(ResultSet set) throws SQLException { HashMap> rowsPerTableId = new HashMap<>(); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/RegisterDateMinimizationPatch.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/RegisterDateMinimizationPatch.java index 781df5b4d..4e3dd092b 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/RegisterDateMinimizationPatch.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/RegisterDateMinimizationPatch.java @@ -51,20 +51,23 @@ public class RegisterDateMinimizationPatch extends Patch { } private Query> fetchSmallestServerRegisterDates() { - String sql = SELECT + "u1.uuid,u1." + UsersTable.REGISTERED + ",min_registered" + FROM + '(' + - SELECT + UserInfoTable.USER_UUID + ',' + + String selectSmallestRegisterDates = SELECT + + UserInfoTable.USER_ID + ',' + "MIN(" + UserInfoTable.REGISTERED + ") as min_registered" + FROM + UserInfoTable.TABLE_NAME + - GROUP_BY + UserInfoTable.USER_UUID + ") u2" + - INNER_JOIN + UsersTable.TABLE_NAME + " u1 on u1.uuid=u2.uuid" + + GROUP_BY + UserInfoTable.USER_ID; + + String sql = SELECT + UsersTable.USER_UUID + ",u1." + UsersTable.REGISTERED + ",min_registered" + + FROM + '(' + selectSmallestRegisterDates + ") u2" + + INNER_JOIN + UsersTable.TABLE_NAME + " u1 on u1." + UsersTable.ID + "=u2." + UserInfoTable.USER_ID + WHERE + "u1." + UsersTable.REGISTERED + ">min_registered"; - return new QueryAllStatement>(sql, 500) { + return new QueryAllStatement<>(sql, 500) { @Override public Map processResults(ResultSet set) throws SQLException { Map dates = new HashMap<>(); while (set.next()) { - UUID playerUUID = UUID.fromString(set.getString(1)); + UUID playerUUID = UUID.fromString(set.getString(UsersTable.USER_UUID)); long newRegisterDate = set.getLong("min_registered"); dates.put(playerUUID, newRegisterDate); } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/RemoveDanglingServerDataPatch.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/RemoveDanglingServerDataPatch.java new file mode 100644 index 000000000..9e57d3717 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/RemoveDanglingServerDataPatch.java @@ -0,0 +1,132 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.storage.database.transactions.patches; + +import com.djrapitops.plan.storage.database.queries.Query; +import com.djrapitops.plan.storage.database.queries.QueryAllStatement; +import com.djrapitops.plan.storage.database.sql.tables.*; +import com.djrapitops.plan.storage.database.transactions.ExecBatchStatement; +import com.djrapitops.plan.storage.database.transactions.Executable; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashSet; +import java.util.Set; + +/** + * Takes care of data without foreign keys that is missing the foreign key target in plan_servers. + */ +public class RemoveDanglingServerDataPatch extends Patch { + + private boolean userInfoTableOk; + private boolean pingTableOk; + private boolean worldTimesTableOk; + private boolean sessionsTableOk; + private boolean pingOptimizationFailed; + private boolean userInfoOptimizationFailed; + private boolean worldTimesOptimizationFailed; + private boolean sessionsOptimizationFailed; + + @Override + public boolean hasBeenApplied() { + userInfoTableOk = hasColumn(UserInfoTable.TABLE_NAME, UserInfoTable.SERVER_ID); + pingTableOk = hasColumn(PingTable.TABLE_NAME, PingTable.SERVER_ID); + worldTimesTableOk = hasColumn(WorldTimesTable.TABLE_NAME, WorldTimesTable.SERVER_ID); + sessionsTableOk = hasColumn(SessionsTable.TABLE_NAME, SessionsTable.SERVER_ID); + pingOptimizationFailed = hasTable("temp_ping"); + userInfoOptimizationFailed = hasTable("temp_user_info"); + worldTimesOptimizationFailed = hasTable("temp_world_times"); + sessionsOptimizationFailed = hasTable("temp_sessions"); + + return userInfoTableOk + && pingTableOk + && worldTimesTableOk + && sessionsTableOk + && !pingOptimizationFailed + && !userInfoOptimizationFailed + && !worldTimesOptimizationFailed + && !sessionsOptimizationFailed; + } + + @Override + protected void applyPatch() { + Set serverUuids = query(getServerUuids()); + + if (!userInfoTableOk) fixTable(UserInfoTable.TABLE_NAME, serverUuids); + if (!pingTableOk) fixTable(PingTable.TABLE_NAME, serverUuids); + if (!worldTimesTableOk) fixTable(WorldTimesTable.TABLE_NAME, serverUuids); + if (!sessionsTableOk) fixTable(SessionsTable.TABLE_NAME, serverUuids); + + if (pingOptimizationFailed) fixTable("temp_ping", serverUuids); + if (userInfoOptimizationFailed) fixTable("temp_user_info", serverUuids); + if (worldTimesOptimizationFailed) fixTable("temp_world_times", serverUuids); + if (sessionsOptimizationFailed) fixTable("temp_sessions", serverUuids); + } + + private void fixTable(String tableName, Set serverUuids) { + Set badUuids = query(getServerUuids(tableName)); + badUuids.removeAll(serverUuids); + + if (!badUuids.isEmpty()) { + execute(deleteBadUuids(tableName, badUuids)); + } + } + + private Executable deleteBadUuids(String tableName, Set badUuids) { + String sql = "DELETE FROM " + tableName + " WHERE server_uuid=?"; + return new ExecBatchStatement(sql) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + for (String badUuid : badUuids) { + statement.setString(1, badUuid); + statement.addBatch(); + } + } + }; + } + + private Query> getServerUuids() { + String sql = "SELECT uuid FROM " + ServerTable.TABLE_NAME; + + return new QueryAllStatement<>(sql) { + @Override + public Set processResults(ResultSet set) throws SQLException { + HashSet uuids = new HashSet<>(); + while (set.next()) { + uuids.add(set.getString("uuid")); + } + return uuids; + } + }; + } + + private Query> getServerUuids(String tableName) { + String sql = "SELECT DISTINCT server_uuid FROM " + tableName; + + return new QueryAllStatement<>(sql) { + @Override + public Set processResults(ResultSet set) throws SQLException { + HashSet uuids = new HashSet<>(); + while (set.next()) { + uuids.add(set.getString("server_uuid")); + } + return uuids; + } + }; + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/RemoveDanglingUserDataPatch.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/RemoveDanglingUserDataPatch.java new file mode 100644 index 000000000..74ad1e007 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/RemoveDanglingUserDataPatch.java @@ -0,0 +1,125 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.storage.database.transactions.patches; + +import com.djrapitops.plan.storage.database.queries.Query; +import com.djrapitops.plan.storage.database.queries.QueryAllStatement; +import com.djrapitops.plan.storage.database.sql.tables.*; +import com.djrapitops.plan.storage.database.transactions.ExecBatchStatement; +import com.djrapitops.plan.storage.database.transactions.Executable; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashSet; +import java.util.Set; + +/** + * Takes care of data without foreign keys that is missing the foreign key target in plan_users. + */ +public class RemoveDanglingUserDataPatch extends Patch { + + private boolean userInfoTableOk; + private boolean geolocationsTableOk; + private boolean pingTableOk; + private boolean worldTimesTableOk; + private boolean sessionsTableOk; + private boolean pingOptimizationFailed; + private boolean userInfoOptimizationFailed; + private boolean worldTimesOptimizationFailed; + private boolean sessionsOptimizationFailed; + private boolean geolocationOptimizationFailed; + + @Override + public boolean hasBeenApplied() { + userInfoTableOk = hasColumn(UserInfoTable.TABLE_NAME, UserInfoTable.USER_ID); + geolocationsTableOk = hasColumn(GeoInfoTable.TABLE_NAME, GeoInfoTable.USER_ID); + pingTableOk = hasColumn(PingTable.TABLE_NAME, PingTable.USER_ID); + worldTimesTableOk = hasColumn(WorldTimesTable.TABLE_NAME, WorldTimesTable.USER_ID); + sessionsTableOk = hasColumn(SessionsTable.TABLE_NAME, SessionsTable.USER_ID); + pingOptimizationFailed = hasTable("temp_ping"); + userInfoOptimizationFailed = hasTable("temp_user_info"); + worldTimesOptimizationFailed = hasTable("temp_world_times"); + sessionsOptimizationFailed = hasTable("temp_sessions"); + geolocationOptimizationFailed = hasTable("temp_geoinformation"); + + return userInfoTableOk + && geolocationsTableOk + && pingTableOk + && worldTimesTableOk + && sessionsTableOk + && !pingOptimizationFailed + && !userInfoOptimizationFailed + && !worldTimesOptimizationFailed + && !sessionsOptimizationFailed + && !geolocationOptimizationFailed; + } + + @Override + protected void applyPatch() { + Set uuids = query(getUuids(UsersTable.TABLE_NAME)); + + if (!userInfoTableOk) fixTable(UserInfoTable.TABLE_NAME, uuids); + if (!geolocationsTableOk) fixTable(GeoInfoTable.TABLE_NAME, uuids); + if (!pingTableOk) fixTable(PingTable.TABLE_NAME, uuids); + if (!worldTimesTableOk) fixTable(WorldTimesTable.TABLE_NAME, uuids); + if (!sessionsTableOk) fixTable(SessionsTable.TABLE_NAME, uuids); + + if (pingOptimizationFailed) fixTable("temp_ping", uuids); + if (userInfoOptimizationFailed) fixTable("temp_user_info", uuids); + if (worldTimesOptimizationFailed) fixTable("temp_world_times", uuids); + if (sessionsOptimizationFailed) fixTable("temp_sessions", uuids); + if (geolocationOptimizationFailed) fixTable("temp_geoinformation", uuids); + } + + private void fixTable(String tableName, Set uuids) { + Set badUuids = query(getUuids(tableName)); + badUuids.removeAll(uuids); + + if (!badUuids.isEmpty()) { + execute(deleteBadUuids(tableName, badUuids)); + } + } + + private Executable deleteBadUuids(String tableName, Set badUuids) { + String sql = "DELETE FROM " + tableName + " WHERE uuid=?"; + return new ExecBatchStatement(sql) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + for (String badUuid : badUuids) { + statement.setString(1, badUuid); + statement.addBatch(); + } + } + }; + } + + private Query> getUuids(String tableName) { + String sql = "SELECT DISTINCT uuid FROM " + tableName; + + return new QueryAllStatement<>(sql) { + @Override + public Set processResults(ResultSet set) throws SQLException { + HashSet uuids = new HashSet<>(); + while (set.next()) { + uuids.add(set.getString("uuid")); + } + return uuids; + } + }; + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/RemoveUsernameFromAccessLogPatch.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/RemoveUsernameFromAccessLogPatch.java new file mode 100644 index 000000000..2ac61ffbd --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/RemoveUsernameFromAccessLogPatch.java @@ -0,0 +1,61 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.storage.database.transactions.patches; + +import com.djrapitops.plan.storage.database.queries.QueryAllStatement; +import com.djrapitops.plan.storage.database.sql.tables.AccessLogTable; +import com.djrapitops.plan.storage.database.transactions.ExecStatement; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; + +import static com.djrapitops.plan.storage.database.sql.building.Sql.*; + +/** + * @author AuroraLS3 + */ +public class RemoveUsernameFromAccessLogPatch extends Patch { + + @Override + public boolean hasBeenApplied() { + if (!hasColumn(AccessLogTable.TABLE_NAME, "username")) { + return true; + } + + String sql = SELECT + "COUNT(*) as c" + + FROM + AccessLogTable.TABLE_NAME + + WHERE + "username" + IS_NOT_NULL; + return query(new QueryAllStatement<>(sql) { + @Override + public Boolean processResults(ResultSet set) throws SQLException { + return set.next() && set.getInt("c") > 0; + } + }); + } + + @Override + protected void applyPatch() { + execute(new ExecStatement("UPDATE " + AccessLogTable.TABLE_NAME + " SET username=?") { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setNull(1, Types.VARCHAR); + } + }); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/ServerPlanVersionPatch.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/ServerPlanVersionPatch.java new file mode 100644 index 000000000..4ed8090d5 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/ServerPlanVersionPatch.java @@ -0,0 +1,33 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.storage.database.transactions.patches; + +import com.djrapitops.plan.storage.database.sql.building.Sql; +import com.djrapitops.plan.storage.database.sql.tables.ServerTable; + +public class ServerPlanVersionPatch extends Patch { + + @Override + public boolean hasBeenApplied() { + return hasColumn(ServerTable.TABLE_NAME, ServerTable.PLAN_VERSION); + } + + @Override + protected void applyPatch() { + addColumn(ServerTable.TABLE_NAME, ServerTable.PLAN_VERSION + " " + Sql.varchar(18) + " DEFAULT 'Old'"); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/ServerTableRowPatch.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/ServerTableRowPatch.java index 02bbf2600..bf2440980 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/ServerTableRowPatch.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/ServerTableRowPatch.java @@ -47,7 +47,7 @@ public class ServerTableRowPatch extends Patch { FROM + TABLE_NAME + WHERE + TABLE_ROW + "=?" + GROUP_BY + TABLE_ID; - return query(new QueryStatement(columnCountPerTableSql) { + return query(new QueryStatement<>(columnCountPerTableSql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setInt(1, 0); @@ -92,7 +92,7 @@ public class ServerTableRowPatch extends Patch { public Map> fetchTableRowIds() { String columnCountPerTableSql = SELECT + TABLE_ID + ',' + ID + FROM + TABLE_NAME; - return query(new QueryAllStatement>>(columnCountPerTableSql) { + return query(new QueryAllStatement<>(columnCountPerTableSql) { @Override public Map> processResults(ResultSet set) throws SQLException { HashMap> rowsPerTableId = new HashMap<>(); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/SessionJoinAddressPatch.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/SessionJoinAddressPatch.java new file mode 100644 index 000000000..63b90869c --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/SessionJoinAddressPatch.java @@ -0,0 +1,150 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.storage.database.transactions.patches; + +import com.djrapitops.plan.storage.database.queries.Query; +import com.djrapitops.plan.storage.database.queries.QueryAllStatement; +import com.djrapitops.plan.storage.database.queries.QueryStatement; +import com.djrapitops.plan.storage.database.sql.building.Sql; +import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable; +import com.djrapitops.plan.storage.database.sql.tables.SessionsTable; +import com.djrapitops.plan.storage.database.sql.tables.UserInfoTable; +import com.djrapitops.plan.storage.database.transactions.ExecBatchStatement; +import org.apache.commons.lang3.StringUtils; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import static com.djrapitops.plan.storage.database.sql.building.Sql.*; + +/** + * Adds join_address_id to plan_sessions, and populates latest session rows with value from plan_user_info. + * + * @author AuroraLS3 + */ +public class SessionJoinAddressPatch extends Patch { + + public static Query> uniqueJoinAddressesOld() { + String sql = SELECT + DISTINCT + "COALESCE(" + UserInfoTable.JOIN_ADDRESS + ", ?) as address" + + FROM + UserInfoTable.TABLE_NAME + + ORDER_BY + "address ASC"; + return new QueryStatement<>(sql, 100) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setString(1, JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP); + } + + @Override + public List processResults(ResultSet set) throws SQLException { + List joinAddresses = new ArrayList<>(); + while (set.next()) joinAddresses.add(set.getString("address")); + return joinAddresses; + } + }; + } + + @Override + public boolean hasBeenApplied() { + return hasColumn(SessionsTable.TABLE_NAME, SessionsTable.JOIN_ADDRESS_ID); + } + + @Override + protected void applyPatch() { + Integer defaultAddressId = getDefaultAddressId(); + addColumn(SessionsTable.TABLE_NAME, SessionsTable.JOIN_ADDRESS_ID + ' ' + Sql.INT + " DEFAULT " + defaultAddressId); + + populateAddresses(); + populateLatestSessions(); + } + + private Integer getDefaultAddressId() { + return query(new QueryStatement<>(SELECT + ID + + FROM + JoinAddressTable.TABLE_NAME + + WHERE + JoinAddressTable.JOIN_ADDRESS + "=?") { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setString(1, JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP); + } + + @Override + public Integer processResults(ResultSet set) throws SQLException { + return set.next() ? set.getInt(JoinAddressTable.ID) : 1; + } + }); + } + + private void populateAddresses() { + List joinAddresses = query(uniqueJoinAddressesOld()); + joinAddresses.remove(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP); + execute(new ExecBatchStatement(JoinAddressTable.INSERT_STATEMENT) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + for (String joinAddress : joinAddresses) { + statement.setString(1, StringUtils.truncate(joinAddress, JoinAddressTable.JOIN_ADDRESS_MAX_LENGTH)); + statement.addBatch(); + } + } + }); + } + + private void populateLatestSessions() { + String sql = SELECT + + "MAX(s." + SessionsTable.ID + ") as session_id," + + "j." + JoinAddressTable.ID + " as join_address_id" + + FROM + UserInfoTable.TABLE_NAME + " u" + + INNER_JOIN + SessionsTable.TABLE_NAME + " s on s." + SessionsTable.USER_ID + "=u." + UserInfoTable.USER_ID + + AND + "s." + SessionsTable.SERVER_ID + "=u." + UserInfoTable.SERVER_ID + + INNER_JOIN + JoinAddressTable.TABLE_NAME + " j on j." + JoinAddressTable.JOIN_ADDRESS + "=u." + UserInfoTable.JOIN_ADDRESS + + GROUP_BY + "u." + UserInfoTable.USER_ID + ",u." + UserInfoTable.SERVER_ID; + + Map joinAddressIdsBySessionId = query(new QueryAllStatement<>(sql) { + @Override + public Map processResults(ResultSet set) throws SQLException { + Map joinAddressBySessionId = new TreeMap<>(); + while (set.next()) { + joinAddressBySessionId.put( + set.getInt("session_id"), + set.getInt("join_address_id") + ); + } + return joinAddressBySessionId; + } + }); + + String updateSql = "UPDATE " + SessionsTable.TABLE_NAME + " SET " + SessionsTable.JOIN_ADDRESS_ID + "=?" + + WHERE + SessionsTable.ID + "=?"; + + execute(new ExecBatchStatement(updateSql) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + for (Map.Entry sessionIdAndJoinAddressId : joinAddressIdsBySessionId.entrySet()) { + Integer sessionId = sessionIdAndJoinAddressId.getKey(); + Integer joinAddressId = sessionIdAndJoinAddressId.getValue(); + statement.setInt(1, joinAddressId); + statement.setInt(2, sessionId); + statement.addBatch(); + } + } + }); + } + +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/SessionsOptimizationPatch.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/SessionsOptimizationPatch.java index 4a5acb448..7e79150a9 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/SessionsOptimizationPatch.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/SessionsOptimizationPatch.java @@ -40,10 +40,10 @@ public class SessionsOptimizationPatch extends Patch { @Override public boolean hasBeenApplied() { - return hasColumn(tableName, SessionsTable.USER_UUID) - && hasColumn(tableName, SessionsTable.SERVER_UUID) - && !hasColumn(tableName, "user_id") - && !hasColumn(tableName, "server_id") + return hasColumn(tableName, SessionsTable.USER_ID) + && hasColumn(tableName, SessionsTable.SERVER_ID) + && !hasColumn(tableName, "uuid") + && !hasColumn(tableName, "server_uuid") && !hasTable(tempTableName); // If this table exists the patch has failed to finish. } @@ -58,8 +58,8 @@ public class SessionsOptimizationPatch extends Patch { execute(SessionsTable.createTableSQL(dbType)); execute("INSERT INTO " + tableName + " (" + - SessionsTable.USER_UUID + ',' + - SessionsTable.SERVER_UUID + ',' + + SessionsTable.USER_ID + ',' + + SessionsTable.SERVER_ID + ',' + SessionsTable.ID + ',' + SessionsTable.SESSION_START + ',' + SessionsTable.SESSION_END + ',' + @@ -67,8 +67,8 @@ public class SessionsOptimizationPatch extends Patch { SessionsTable.DEATHS + ',' + SessionsTable.AFK_TIME + ") SELECT " + - "(SELECT plan_users.uuid FROM plan_users WHERE plan_users.id = " + tempTableName + ".user_id LIMIT 1), " + - "(SELECT plan_servers.uuid FROM plan_servers WHERE plan_servers.id = " + tempTableName + ".server_id LIMIT 1), " + + "(SELECT plan_users.id FROM plan_users WHERE plan_users.uuid = " + tempTableName + ".uuid LIMIT 1), " + + "(SELECT plan_servers.id FROM plan_servers WHERE plan_servers.uuid = " + tempTableName + ".server_uuid LIMIT 1), " + SessionsTable.ID + ',' + SessionsTable.SESSION_START + ',' + SessionsTable.SESSION_END + ',' + diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/UserInfoHostnameAllowNullPatch.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/UserInfoHostnameAllowNullPatch.java index 4ee5a4c04..652d10b31 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/UserInfoHostnameAllowNullPatch.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/UserInfoHostnameAllowNullPatch.java @@ -32,14 +32,14 @@ import static com.djrapitops.plan.storage.database.sql.building.Sql.FROM; */ public class UserInfoHostnameAllowNullPatch extends Patch { - private final String tempTableName = "temp_user_info_join_address_patching"; - private final String tableName = UserInfoTable.TABLE_NAME; + private static final String TEMP_TABLE_NAME = "temp_user_info_join_address_patching"; + private static final String TABLE_NAME = UserInfoTable.TABLE_NAME; @Override public boolean hasBeenApplied() { - return hasColumn(tableName, UserInfoTable.JOIN_ADDRESS) - && !hasColumn(tableName, "hostname") - && !hasTable(tempTableName); + return hasColumn(TABLE_NAME, UserInfoTable.JOIN_ADDRESS) + && !hasColumn(TABLE_NAME, "hostname") + && !hasTable(TEMP_TABLE_NAME); } @Override @@ -47,23 +47,23 @@ public class UserInfoHostnameAllowNullPatch extends Patch { tempOldTable(); execute(UserInfoTable.createTableSQL(dbType)); - execute(new ExecStatement("INSERT INTO " + tableName + " (" + + execute(new ExecStatement("INSERT INTO " + TABLE_NAME + " (" + UserInfoTable.ID + ',' + - UserInfoTable.USER_UUID + ',' + - UserInfoTable.SERVER_UUID + ',' + + UserInfoTable.USER_ID + ',' + + UserInfoTable.SERVER_ID + ',' + UserInfoTable.REGISTERED + ',' + UserInfoTable.OP + ',' + UserInfoTable.BANNED + ',' + UserInfoTable.JOIN_ADDRESS + ") SELECT " + UserInfoTable.ID + ',' + - UserInfoTable.USER_UUID + ',' + - UserInfoTable.SERVER_UUID + ',' + + UserInfoTable.USER_ID + ',' + + UserInfoTable.SERVER_ID + ',' + UserInfoTable.REGISTERED + ',' + UserInfoTable.OP + ',' + UserInfoTable.BANNED + ',' + "?" + - FROM + tempTableName + FROM + TEMP_TABLE_NAME ) { @Override public void prepare(PreparedStatement statement) throws SQLException { @@ -71,13 +71,13 @@ public class UserInfoHostnameAllowNullPatch extends Patch { } }); - dropTable(tempTableName); + dropTable(TEMP_TABLE_NAME); } private void tempOldTable() { - if (!hasTable(tempTableName)) { - renameTable(tableName, tempTableName); + if (!hasTable(TEMP_TABLE_NAME)) { + renameTable(TABLE_NAME, TEMP_TABLE_NAME); } } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/UserInfoHostnamePatch.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/UserInfoHostnamePatch.java index 3864f592b..b3d785d0d 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/UserInfoHostnamePatch.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/UserInfoHostnamePatch.java @@ -17,6 +17,7 @@ package com.djrapitops.plan.storage.database.transactions.patches; import com.djrapitops.plan.storage.database.sql.building.Sql; +import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable; import com.djrapitops.plan.storage.database.sql.tables.UserInfoTable; /** @@ -34,6 +35,6 @@ public class UserInfoHostnamePatch extends Patch { @Override protected void applyPatch() { addColumn(UserInfoTable.TABLE_NAME, UserInfoTable.JOIN_ADDRESS + ' ' - + Sql.varchar(255)); + + Sql.varchar(JoinAddressTable.JOIN_ADDRESS_MAX_LENGTH)); } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/UserInfoOptimizationPatch.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/UserInfoOptimizationPatch.java index 0da5a1794..9af5e518e 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/UserInfoOptimizationPatch.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/UserInfoOptimizationPatch.java @@ -38,10 +38,10 @@ public class UserInfoOptimizationPatch extends Patch { @Override public boolean hasBeenApplied() { - return hasColumn(tableName, UserInfoTable.USER_UUID) - && hasColumn(tableName, UserInfoTable.SERVER_UUID) - && !hasColumn(tableName, "user_id") - && !hasColumn(tableName, "server_id") + return hasColumn(tableName, UserInfoTable.USER_ID) + && hasColumn(tableName, UserInfoTable.SERVER_ID) + && !hasColumn(tableName, "uuid") + && !hasColumn(tableName, "server_uuid") && !hasTable(tempTableName); // If this table exists the patch has failed to finish. } @@ -52,17 +52,19 @@ public class UserInfoOptimizationPatch extends Patch { execute(UserInfoTable.createTableSQL(dbType)); execute("INSERT INTO " + tableName + " (" + - UserInfoTable.USER_UUID + ',' + - UserInfoTable.SERVER_UUID + ',' + + UserInfoTable.USER_ID + ',' + + UserInfoTable.SERVER_ID + ',' + UserInfoTable.REGISTERED + ',' + UserInfoTable.BANNED + ',' + - UserInfoTable.OP + + UserInfoTable.OP + ',' + + UserInfoTable.JOIN_ADDRESS + ") SELECT " + - "(SELECT plan_users.uuid FROM plan_users WHERE plan_users.id = " + tempTableName + ".user_id LIMIT 1), " + - "(SELECT plan_servers.uuid FROM plan_servers WHERE plan_servers.id = " + tempTableName + ".server_id LIMIT 1), " + + "(SELECT plan_users.id FROM plan_users WHERE plan_users.uuid = " + tempTableName + ".uuid LIMIT 1), " + + "(SELECT plan_servers.id FROM plan_servers WHERE plan_servers.uuid = " + tempTableName + ".server_uuid LIMIT 1), " + UserInfoTable.REGISTERED + ',' + UserInfoTable.BANNED + ',' + - UserInfoTable.OP + + UserInfoTable.OP + ',' + + UserInfoTable.JOIN_ADDRESS + FROM + tempTableName ); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/UsersTableNameLengthPatch.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/UsersTableNameLengthPatch.java new file mode 100644 index 000000000..42483132d --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/UsersTableNameLengthPatch.java @@ -0,0 +1,34 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.storage.database.transactions.patches; + +import com.djrapitops.plan.storage.database.DBType; +import com.djrapitops.plan.storage.database.sql.building.Sql; +import com.djrapitops.plan.storage.database.sql.tables.UsersTable; + +public class UsersTableNameLengthPatch extends Patch { + @Override + public boolean hasBeenApplied() { + return dbType == DBType.SQLITE || // SQLite does not limit varchar lengths + columnVarcharLength(UsersTable.TABLE_NAME, UsersTable.USER_NAME) >= 36; + } + + @Override + protected void applyPatch() { + execute("ALTER TABLE " + UsersTable.TABLE_NAME + " MODIFY " + UsersTable.USER_NAME + " " + Sql.varchar(36)); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/WorldTimesOptimizationPatch.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/WorldTimesOptimizationPatch.java index 875f56ba9..d1f0be60e 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/WorldTimesOptimizationPatch.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/WorldTimesOptimizationPatch.java @@ -41,10 +41,10 @@ public class WorldTimesOptimizationPatch extends Patch { @Override public boolean hasBeenApplied() { return hasColumn(tableName, WorldTimesTable.ID) - && hasColumn(tableName, WorldTimesTable.USER_UUID) - && hasColumn(tableName, WorldTimesTable.SERVER_UUID) - && !hasColumn(tableName, "user_id") - && !hasColumn(tableName, "server_id") + && hasColumn(tableName, WorldTimesTable.USER_ID) + && hasColumn(tableName, WorldTimesTable.SERVER_ID) + && !hasColumn(tableName, "uuid") + && !hasColumn(tableName, "server_uuid") && !hasTable(tempTableName); // If this table exists the patch has failed to finish. } @@ -54,26 +54,49 @@ public class WorldTimesOptimizationPatch extends Patch { tempOldTable(); execute(WorldTimesTable.createTableSQL(dbType)); - execute("INSERT INTO " + tableName + " (" + - WorldTimesTable.USER_UUID + ',' + - WorldTimesTable.SERVER_UUID + ',' + - WorldTimesTable.ADVENTURE + ',' + - WorldTimesTable.CREATIVE + ',' + - WorldTimesTable.SURVIVAL + ',' + - WorldTimesTable.SPECTATOR + ',' + - WorldTimesTable.SESSION_ID + ',' + - WorldTimesTable.WORLD_ID + - ") SELECT " + - "(SELECT plan_users.uuid FROM plan_users WHERE plan_users.id = " + tempTableName + ".user_id LIMIT 1), " + - "(SELECT plan_servers.uuid FROM plan_servers WHERE plan_servers.id = " + tempTableName + ".server_id LIMIT 1), " + - WorldTimesTable.ADVENTURE + ',' + - WorldTimesTable.CREATIVE + ',' + - WorldTimesTable.SURVIVAL + ',' + - WorldTimesTable.SPECTATOR + ',' + - WorldTimesTable.SESSION_ID + ',' + - WorldTimesTable.WORLD_ID + - FROM + tempTableName - ); + if (hasColumn(tempTableName, WorldTimesTable.ID)) { + execute("INSERT INTO " + tableName + " (" + + WorldTimesTable.USER_ID + ',' + + WorldTimesTable.SERVER_ID + ',' + + WorldTimesTable.ADVENTURE + ',' + + WorldTimesTable.CREATIVE + ',' + + WorldTimesTable.SURVIVAL + ',' + + WorldTimesTable.SPECTATOR + ',' + + WorldTimesTable.SESSION_ID + ',' + + WorldTimesTable.WORLD_ID + + ") SELECT " + + "(SELECT plan_users.id FROM plan_users WHERE plan_users.uuid = " + tempTableName + ".uuid LIMIT 1), " + + "(SELECT plan_servers.id FROM plan_servers WHERE plan_servers.uuid = " + tempTableName + ".server_uuid LIMIT 1), " + + WorldTimesTable.ADVENTURE + ',' + + WorldTimesTable.CREATIVE + ',' + + WorldTimesTable.SURVIVAL + ',' + + WorldTimesTable.SPECTATOR + ',' + + WorldTimesTable.SESSION_ID + ',' + + WorldTimesTable.WORLD_ID + + FROM + tempTableName + ); + } else { + execute("INSERT INTO " + tableName + " (" + + WorldTimesTable.USER_ID + ',' + + WorldTimesTable.SERVER_ID + ',' + + WorldTimesTable.ADVENTURE + ',' + + WorldTimesTable.CREATIVE + ',' + + WorldTimesTable.SURVIVAL + ',' + + WorldTimesTable.SPECTATOR + ',' + + WorldTimesTable.SESSION_ID + ',' + + WorldTimesTable.WORLD_ID + + ") SELECT " + + WorldTimesTable.USER_ID + ',' + + WorldTimesTable.SERVER_ID + ',' + + WorldTimesTable.ADVENTURE + ',' + + WorldTimesTable.CREATIVE + ',' + + WorldTimesTable.SURVIVAL + ',' + + WorldTimesTable.SPECTATOR + ',' + + WorldTimesTable.SESSION_ID + ',' + + WorldTimesTable.WORLD_ID + + FROM + tempTableName + ); + } dropTable(tempTableName); } catch (Exception e) { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/WorldsServerIDPatch.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/WorldsServerIDPatch.java index b64704fe8..3def954d7 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/WorldsServerIDPatch.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/WorldsServerIDPatch.java @@ -21,6 +21,7 @@ import com.djrapitops.plan.storage.database.queries.LargeStoreQueries; import com.djrapitops.plan.storage.database.queries.QueryAllStatement; import com.djrapitops.plan.storage.database.queries.QueryStatement; import com.djrapitops.plan.storage.database.queries.objects.ServerQueries; +import com.djrapitops.plan.storage.database.sql.tables.ServerTable; import com.djrapitops.plan.storage.database.sql.tables.SessionsTable; import com.djrapitops.plan.storage.database.sql.tables.WorldTable; import com.djrapitops.plan.storage.database.sql.tables.WorldTimesTable; @@ -73,16 +74,19 @@ public class WorldsServerIDPatch extends Patch { String worldIDColumn = WorldTimesTable.TABLE_NAME + '.' + WorldTimesTable.WORLD_ID; String worldSessionIDColumn = WorldTimesTable.TABLE_NAME + '.' + WorldTimesTable.SESSION_ID; String sessionIDColumn = SessionsTable.TABLE_NAME + '.' + SessionsTable.ID; - String sessionServerUUIDColumn = SessionsTable.TABLE_NAME + '.' + SessionsTable.SERVER_UUID; + String sessionServerIDColumn = SessionsTable.TABLE_NAME + '.' + SessionsTable.SERVER_ID; + String serverIDColumn = ServerTable.TABLE_NAME + '.' + ServerTable.ID; + String serverUUIDColumn = ServerTable.TABLE_NAME + '.' + ServerTable.SERVER_UUID; String sql = SELECT + DISTINCT + WorldTable.NAME + FROM + WorldTable.TABLE_NAME + INNER_JOIN + WorldTimesTable.TABLE_NAME + " on " + worldIDColumn + "=" + WorldTable.TABLE_NAME + '.' + WorldTable.ID + INNER_JOIN + SessionsTable.TABLE_NAME + " on " + worldSessionIDColumn + "=" + sessionIDColumn + - WHERE + sessionServerUUIDColumn + "=?"; + INNER_JOIN + ServerTable.TABLE_NAME + " on " + serverIDColumn + "=" + sessionServerIDColumn + + WHERE + serverUUIDColumn + "=?"; - return query(new QueryStatement>(sql, 1000) { + return query(new QueryStatement<>(sql, 1000) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); @@ -134,7 +138,7 @@ public class WorldsServerIDPatch extends Patch { public List getWorldObjects() { String sql = SELECT + '*' + FROM + WorldTable.TABLE_NAME; - return query(new QueryAllStatement>(sql, 100) { + return query(new QueryAllStatement<>(sql, 100) { @Override public List processResults(ResultSet set) throws SQLException { List objects = new ArrayList<>(); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/file/FileResource.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/file/FileResource.java index 11b91201b..964337d5f 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/file/FileResource.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/file/FileResource.java @@ -76,7 +76,7 @@ public class FileResource implements Resource { @Override public String asString() throws IOException { StringBuilder flat = new StringBuilder(); - try (Scanner scanner = new Scanner(file, "UTF-8")) { + try (Scanner scanner = new Scanner(file, StandardCharsets.UTF_8)) { while (scanner.hasNextLine()) { flat.append(scanner.nextLine()).append("\r\n"); } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/file/JarResource.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/file/JarResource.java index 475cef309..3f5e923be 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/file/JarResource.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/file/JarResource.java @@ -22,6 +22,7 @@ import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Scanner; @@ -61,7 +62,7 @@ public class JarResource implements Resource { List lines = new ArrayList<>(); try ( InputStream inputStream = asInputStream(); - Scanner scanner = new Scanner(inputStream, "UTF-8") + Scanner scanner = new Scanner(inputStream, StandardCharsets.UTF_8) ) { while (scanner.hasNextLine()) { lines.add(scanner.nextLine()); @@ -75,7 +76,7 @@ public class JarResource implements Resource { StringBuilder flat = new StringBuilder(); try ( InputStream inputStream = asInputStream(); - Scanner scanner = new Scanner(inputStream, "UTF-8") + Scanner scanner = new Scanner(inputStream, StandardCharsets.UTF_8) ) { while (scanner.hasNextLine()) { flat.append(scanner.nextLine()).append("\r\n"); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/file/PlanFiles.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/file/PlanFiles.java index 40bb1d0ef..4a8c57410 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/file/PlanFiles.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/file/PlanFiles.java @@ -18,6 +18,9 @@ package com.djrapitops.plan.storage.file; import com.djrapitops.plan.SubSystem; import com.djrapitops.plan.exceptions.EnableException; +import com.djrapitops.plan.settings.config.PlanConfig; +import com.djrapitops.plan.settings.config.paths.CustomizedFileSettings; +import dagger.Lazy; import javax.inject.Inject; import javax.inject.Named; @@ -42,13 +45,17 @@ public class PlanFiles implements SubSystem { private final File dataFolder; private final File configFile; + private final Lazy config; + @Inject public PlanFiles( @Named("dataFolder") File dataFolder, - JarResource.StreamFunction getResourceStream + JarResource.StreamFunction getResourceStream, + Lazy config ) { this.dataFolder = dataFolder; this.getResourceStream = getResourceStream; + this.config = config; this.configFile = getFileFromPluginFolder("config.yml"); } @@ -60,10 +67,6 @@ public class PlanFiles implements SubSystem { return dataFolder.toPath(); } - public Path getCustomizationDirectory() { - return getDataDirectory().resolve("web"); - } - public File getLogsFolder() { try { File folder = getFileFromPluginFolder("logs"); @@ -84,7 +87,7 @@ public class PlanFiles implements SubSystem { } public File getLocaleFile() { - return getFileFromPluginFolder("locale.txt"); + return getFileFromPluginFolder("locale.yml"); } public File getFileFromPluginFolder(String name) { @@ -129,16 +132,28 @@ public class PlanFiles implements SubSystem { return new FileResource(resourceName, getFileFromPluginFolder(resourceName)); } + // TODO Customized file logic should be moved to another class so the circular dependency on config can be removed. public Optional getCustomizableResource(String resourceName) { - return Optional.ofNullable(ResourceCache.getOrCache(resourceName, - () -> attemptToFind(resourceName) - .map(found -> new FileResource(resourceName, found)) - .orElse(null) - )); + return Optional.ofNullable(findCustomized(resourceName)); } - private Optional attemptToFind(String resourceName) { - Path dir = getCustomizationDirectory(); + private Resource findCustomized(String resourceName) { + if (config.get().isTrue(CustomizedFileSettings.WEB_DEV_MODE)) { + // Bypass cache in web developer mode. + return getFileResource(resourceName); + } else { + return ResourceCache.getOrCache(resourceName, () -> getFileResource(resourceName)); + } + } + + private FileResource getFileResource(String resourceName) { + return attemptToFind(resourceName) + .map(found -> new FileResource(resourceName, found)) + .orElse(null); + } + + public Optional attemptToFind(String resourceName) { + Path dir = config.get().getResourceSettings().getCustomizationDirectory(); if (dir.toFile().exists() && dir.toFile().isDirectory()) { Path asPath = dir.resolve(resourceName); File found = asPath.toFile(); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/file/Resource.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/file/Resource.java index 92d080d19..6e46a8513 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/file/Resource.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/file/Resource.java @@ -17,6 +17,8 @@ package com.djrapitops.plan.storage.file; import com.djrapitops.plan.delivery.web.resource.WebResource; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; import org.apache.commons.lang3.StringUtils; import java.io.IOException; @@ -64,6 +66,10 @@ public interface Resource { */ String asString() throws IOException; + default T asParsed(Gson gson, TypeToken token) throws IOException { + return gson.fromJson(asString(), token.getType()); + } + /** * Map to a WebResource used by {@link com.djrapitops.plan.delivery.web.ResourceService} APIs. * diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/upkeep/DBCleanTask.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/upkeep/DBCleanTask.java index a0f3a9ed0..fd0cefd0b 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/upkeep/DBCleanTask.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/upkeep/DBCleanTask.java @@ -24,6 +24,7 @@ import com.djrapitops.plan.identification.ServerInfo; import com.djrapitops.plan.query.QuerySvc; import com.djrapitops.plan.settings.config.PlanConfig; import com.djrapitops.plan.settings.config.paths.TimeSettings; +import com.djrapitops.plan.settings.config.paths.WebserverSettings; import com.djrapitops.plan.settings.locale.Locale; import com.djrapitops.plan.settings.locale.lang.PluginLang; import com.djrapitops.plan.storage.database.DBSystem; @@ -32,8 +33,10 @@ import com.djrapitops.plan.storage.database.queries.Query; import com.djrapitops.plan.storage.database.queries.QueryStatement; import com.djrapitops.plan.storage.database.queries.objects.ServerQueries; import com.djrapitops.plan.storage.database.sql.tables.SessionsTable; +import com.djrapitops.plan.storage.database.sql.tables.UsersTable; import com.djrapitops.plan.storage.database.transactions.commands.RemovePlayerTransaction; import com.djrapitops.plan.storage.database.transactions.init.RemoveDuplicateUserInfoTransaction; +import com.djrapitops.plan.storage.database.transactions.init.RemoveOldAccessLogTransaction; import com.djrapitops.plan.storage.database.transactions.init.RemoveOldExtensionsTransaction; import com.djrapitops.plan.storage.database.transactions.init.RemoveOldSampledDataTransaction; import com.djrapitops.plan.utilities.logging.ErrorLogger; @@ -102,6 +105,7 @@ public class DBCleanTask extends TaskSystem.Task { try { if (database.getState() != Database.State.CLOSED) { + database.executeTransaction(new RemoveOldAccessLogTransaction(TimeUnit.DAYS.toMillis(config.get(WebserverSettings.REMOVE_ACCESS_LOG_AFTER_DAYS)))); database.executeTransaction(new RemoveOldSampledDataTransaction( serverInfo.getServerUUID(), config.get(TimeSettings.DELETE_TPS_DATA_AFTER), @@ -124,7 +128,7 @@ public class DBCleanTask extends TaskSystem.Task { // This is needed since the last updated number is updated at reload and it would lead to all data // for plugins being deleted all the time. if (System.currentTimeMillis() - lastReload <= deleteExtensionDataAfter) { - database.executeTransaction(new RemoveOldExtensionsTransaction(deleteExtensionDataAfter, serverInfo.getServerUUID())); + database.executeTransaction(new RemoveOldExtensionsTransaction(config.getExtensionSettings(), deleteExtensionDataAfter, serverInfo.getServerUUID())); } } } catch (DBOpException e) { @@ -169,13 +173,15 @@ public class DBCleanTask extends TaskSystem.Task { } private Query> fetchInactivePlayerUUIDs(long keepActiveAfter) { - String sql = SELECT + "uuid, last_seen" + FROM + - '(' + SELECT + "MAX(" + SessionsTable.SESSION_END + ") as last_seen, " + - SessionsTable.USER_UUID + + String selectLastSeen = SELECT + "MAX(" + SessionsTable.SESSION_END + ") as last_seen, " + + SessionsTable.USER_ID + FROM + SessionsTable.TABLE_NAME + - GROUP_BY + SessionsTable.USER_UUID + ") as q1" + + GROUP_BY + SessionsTable.USER_ID; + String sql = SELECT + "uuid, last_seen" + + FROM + '(' + selectLastSeen + ") as q1" + + INNER_JOIN + UsersTable.TABLE_NAME + " u on u." + UsersTable.ID + '=' + "q1." + SessionsTable.USER_ID + WHERE + "last_seen < ?"; - return new QueryStatement>(sql, 20000) { + return new QueryStatement<>(sql, 20000) { @Override public void prepare(PreparedStatement statement) throws SQLException { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/upkeep/ExtensionDisableOnGameServerTask.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/upkeep/ExtensionDisableOnGameServerTask.java new file mode 100644 index 000000000..0d8d8fc10 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/upkeep/ExtensionDisableOnGameServerTask.java @@ -0,0 +1,76 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.storage.upkeep; + +import com.djrapitops.plan.TaskSystem; +import com.djrapitops.plan.extension.implementation.storage.queries.HasExtensionDataForPluginQuery; +import com.djrapitops.plan.identification.Server; +import com.djrapitops.plan.identification.ServerUUID; +import com.djrapitops.plan.settings.config.ExtensionSettings; +import com.djrapitops.plan.settings.config.PlanConfig; +import com.djrapitops.plan.storage.database.DBSystem; +import com.djrapitops.plan.storage.database.Database; +import com.djrapitops.plan.storage.database.queries.objects.ServerQueries; +import net.playeranalytics.plugin.scheduling.RunnableFactory; +import net.playeranalytics.plugin.server.PluginLogger; + +import javax.inject.Inject; +import javax.inject.Singleton; + +@Singleton +public class ExtensionDisableOnGameServerTask extends TaskSystem.Task { + + private final PlanConfig config; + private final DBSystem dbSystem; + private final PluginLogger logger; + + @Inject + public ExtensionDisableOnGameServerTask(PlanConfig config, DBSystem dbSystem, PluginLogger logger) { + this.config = config; + this.dbSystem = dbSystem; + this.logger = logger; + } + + @Override + public void register(RunnableFactory runnableFactory) { + runnableFactory.create(this).runTaskAsynchronously(); + } + + @Override + public void run() { + String pluginName = "Litebans"; + checkAndDisableProxyExtensions(pluginName); + } + + private void checkAndDisableProxyExtensions(String pluginName) { + Database db = dbSystem.getDatabase(); + db.query(ServerQueries.fetchProxyServerInformation()) + .map(Server::getUuid) + .ifPresent(proxyUUID -> checkAndDisableProxyExtension(proxyUUID, pluginName)); + } + + private void checkAndDisableProxyExtension(ServerUUID proxyUUID, String pluginName) { + Database db = dbSystem.getDatabase(); + ExtensionSettings extensionSettings = config.getExtensionSettings(); + + boolean isInstalledOnProxy = db.query(new HasExtensionDataForPluginQuery(pluginName, proxyUUID)); + if (isInstalledOnProxy && extensionSettings.isEnabled(pluginName)) { + extensionSettings.setEnabled(pluginName, false); + logger.info("Set " + pluginName + " Extension as disabled in config since it is already enabled on the proxy server. This is to avoid duplicate data."); + } + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/upkeep/OldDependencyCacheDeletionTask.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/upkeep/OldDependencyCacheDeletionTask.java index 8456302fe..d64dd55d7 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/upkeep/OldDependencyCacheDeletionTask.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/upkeep/OldDependencyCacheDeletionTask.java @@ -35,7 +35,7 @@ public class OldDependencyCacheDeletionTask extends TaskSystem.Task { private final File oldDependencyCache; private final File dependencyCache; - private final File libraries; + private final File librariesCache; private final ErrorLogger errorLogger; @@ -46,7 +46,7 @@ public class OldDependencyCacheDeletionTask extends TaskSystem.Task { ) { oldDependencyCache = files.getDataDirectory().resolve("dependency_cache").toFile(); dependencyCache = files.getDataDirectory().resolve("dep_cache").toFile(); - libraries = files.getDataDirectory().resolve("libraries").toFile(); + librariesCache = files.getDataDirectory().resolve("libraries").toFile(); this.errorLogger = errorLogger; } @@ -58,9 +58,27 @@ public class OldDependencyCacheDeletionTask extends TaskSystem.Task { @Override public void run() { + try { + runTask(); + } finally { + cancel(); + } + } + + private void runTask() { tryToDeleteDirectory(oldDependencyCache); tryToDeleteDirectory(dependencyCache); - tryToDeleteDirectory(libraries); + + if (librariesCache.exists()) { + // Only delete sub folders as jar files in the directory are still needed + File[] files = librariesCache.listFiles(); + if (files == null) return; + for (File file : files) { + if (file.isDirectory()) { + tryToDeleteDirectory(file); + } + } + } } private void tryToDeleteDirectory(File directory) { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/utilities/Benchmark.java b/Plan/common/src/main/java/com/djrapitops/plan/utilities/Benchmark.java new file mode 100644 index 000000000..ee67545e1 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/utilities/Benchmark.java @@ -0,0 +1,97 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.utilities; + +import com.djrapitops.plan.delivery.formatting.Formatter; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +/** + * @author AuroraLS3 + */ +public class Benchmark { + + private static final Formatter FORMATTER = new BenchmarkFormatter(); + + private Benchmark() { + /* Only used for in-development benchmarks. */ + } + + public static void bench(Runnable runnable) { + long start = System.nanoTime(); + try { + runnable.run(); + } finally { + long end = System.nanoTime(); + long durationNanos = end - start; + + printResult(durationNanos); + } + } + + public static T bench(Supplier supplier) { + long start = System.nanoTime(); + try { + return supplier.get(); + } finally { + long end = System.nanoTime(); + long durationNanos = end - start; + + printResult(durationNanos); + } + } + + private static void printResult(long durationNanos) { + String timeTaken = FORMATTER.apply(durationNanos); + + StackTraceElement caller = Thread.currentThread().getStackTrace()[3]; + String calledFrom = caller.getClassName() + "#" + caller.getMethodName() + ":" + caller.getLineNumber(); + + StringBuilder builder = new StringBuilder(timeTaken); + while (builder.length() < 16) { + builder.append(' '); + } + builder.append(" - ").append(calledFrom); + + System.out.print(builder.toString()); + } + + + @Retention(RetentionPolicy.SOURCE) + @Target({ElementType.METHOD, ElementType.TYPE}) + public static @interface Slow { + String value(); // How long the benchmark took when done (look at git blame for date) + } + + private static class BenchmarkFormatter implements Formatter { + @Override + public String apply(Long benchLengthNanos) { + long nanos = benchLengthNanos % TimeUnit.MILLISECONDS.toNanos(1L); + long millis = TimeUnit.NANOSECONDS.toMillis(benchLengthNanos) % TimeUnit.SECONDS.toMillis(1L); + long seconds = TimeUnit.NANOSECONDS.toSeconds(benchLengthNanos); + + String subSeconds = millis <= 0 ? "woah (" + nanos + "ns)" : "fast (" + millis + "ms)"; + return seconds <= 0 ? subSeconds : "slow (" + seconds + "s)"; + } + } + +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/icon/Color.java b/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/icon/Color.java index 46e70c69e..e6be812da 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/icon/Color.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/icon/Color.java @@ -21,7 +21,7 @@ import java.util.Optional; /** * @deprecated This Class exists to keep plugins that used PluginData from breaking. */ -@Deprecated +@Deprecated(since = "5.0") public enum Color { RED("col-red"), PINK("col-pink"), diff --git a/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/icon/Family.java b/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/icon/Family.java index e13b676a7..db5007368 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/icon/Family.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/icon/Family.java @@ -21,7 +21,7 @@ import java.util.Optional; /** * @deprecated This Class exists to keep plugins that used PluginData from breaking. */ -@Deprecated +@Deprecated(since = "5.0") public enum Family { SOLID(" fa fa-", "\">"), REGULAR(" far fa-", "\">"), diff --git a/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/icon/Icon.java b/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/icon/Icon.java index 44da91f3b..3fbcaaedc 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/icon/Icon.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/icon/Icon.java @@ -19,7 +19,7 @@ package com.djrapitops.plan.utilities.html.icon; /** * @deprecated This Class exists to keep plugins that used PluginData from breaking. */ -@Deprecated +@Deprecated(since = "5.0") public class Icon { private Family type; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/utilities/java/Maps.java b/Plan/common/src/main/java/com/djrapitops/plan/utilities/java/Maps.java index e69cfb623..cac71d1ee 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/utilities/java/Maps.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/utilities/java/Maps.java @@ -44,6 +44,14 @@ public class Maps { return new Builder<>(); } + public static Map reverse(Map map) { + Map reversed = new HashMap<>(); + for (Map.Entry entry : map.entrySet()) { + reversed.put(entry.getValue(), entry.getKey()); + } + return reversed; + } + public static class Builder { private final Map map; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/utilities/java/OptionalArray.java b/Plan/common/src/main/java/com/djrapitops/plan/utilities/java/OptionalArray.java new file mode 100644 index 000000000..205e50d8d --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/utilities/java/OptionalArray.java @@ -0,0 +1,39 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.utilities.java; + +import java.util.Optional; + +public class OptionalArray { + + private final T[] array; + + private OptionalArray(T[] array) { + this.array = array; + } + + public static OptionalArray of(T[] array) { + return new OptionalArray<>(array); + } + + public Optional get(int index) { + if (index < array.length) { + return Optional.of(array[index]); + } + return Optional.empty(); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/utilities/java/ThreadContextClassLoaderSwap.java b/Plan/common/src/main/java/com/djrapitops/plan/utilities/java/ThreadContextClassLoaderSwap.java new file mode 100644 index 000000000..50be12024 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/utilities/java/ThreadContextClassLoaderSwap.java @@ -0,0 +1,51 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.utilities.java; + +import java.util.function.Supplier; + +public class ThreadContextClassLoaderSwap { + + private ThreadContextClassLoaderSwap() { + /* static method utility class */ + } + + public static void performOperation(ClassLoader usingClassLoader, Runnable operation) { + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + try { + // Jetty uses Thread context classloader, so we need to change to plugin classloader where the ALPNProcessor is. + Thread.currentThread().setContextClassLoader(usingClassLoader); + + operation.run(); + } finally { + Thread.currentThread().setContextClassLoader(contextClassLoader); + } + } + + public static T performOperation(ClassLoader usingClassLoader, Supplier operation) { + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + try { + // Jetty uses Thread context classloader, so we need to change to plugin classloader where the ALPNProcessor is. + Thread.currentThread().setContextClassLoader(usingClassLoader); + + return operation.get(); + } finally { + Thread.currentThread().setContextClassLoader(contextClassLoader); + } + } + +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/utilities/logging/ErrorContext.java b/Plan/common/src/main/java/com/djrapitops/plan/utilities/logging/ErrorContext.java index 29b33f6bb..9746f9d6a 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/utilities/logging/ErrorContext.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/utilities/logging/ErrorContext.java @@ -26,8 +26,9 @@ import java.util.*; */ public class ErrorContext implements Serializable { - private final List related; + private final transient List related; private String whatToDo; + private boolean logErrorMessage = false; private ErrorContext() { related = new ArrayList<>(); @@ -41,6 +42,10 @@ public class ErrorContext implements Serializable { return Optional.ofNullable(whatToDo); } + public boolean shouldLogErrorMessage() { + return logErrorMessage; + } + public Collection toLines() { List lines = new ArrayList<>(); getWhatToDo().ifPresent(lines::add); @@ -55,6 +60,10 @@ public class ErrorContext implements Serializable { if (this.whatToDo == null && context.whatToDo != null) this.whatToDo = context.whatToDo; } + public List getRelated() { + return related; + } + public static class Builder { private final ErrorContext context; @@ -67,6 +76,11 @@ public class ErrorContext implements Serializable { return this; } + public Builder logErrorMessage() { + context.logErrorMessage = true; + return this; + } + public Builder related(Object related) { context.related.add(related); return this; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/utilities/logging/ErrorLogger.java b/Plan/common/src/main/java/com/djrapitops/plan/utilities/logging/ErrorLogger.java index b7069aa06..9aef38c3e 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/utilities/logging/ErrorLogger.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/utilities/logging/ErrorLogger.java @@ -20,20 +20,24 @@ import com.djrapitops.plan.exceptions.ExceptionWithContext; public interface ErrorLogger { default void critical(T throwable) { - critical((Throwable) throwable, throwable.getContext().orElse(ErrorContext.builder().related("Missing Context").build())); + critical((Throwable) throwable, throwable.getContext().orElseGet(this::createMissingContext)); } void critical(Throwable throwable, ErrorContext context); default void error(T throwable) { - error((Throwable) throwable, throwable.getContext().orElse(ErrorContext.builder().related("Missing Context").build())); + error((Throwable) throwable, throwable.getContext().orElseGet(this::createMissingContext)); } void error(Throwable throwable, ErrorContext context); default void warn(T throwable) { - warn((Throwable) throwable, throwable.getContext().orElse(ErrorContext.builder().related("Missing Context").build())); + warn((Throwable) throwable, throwable.getContext().orElseGet(this::createMissingContext)); } void warn(Throwable throwable, ErrorContext context); + + default ErrorContext createMissingContext() { + return ErrorContext.builder().related("Missing Context").build(); + } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/utilities/logging/PluginErrorLogger.java b/Plan/common/src/main/java/com/djrapitops/plan/utilities/logging/PluginErrorLogger.java index 2f59b70b4..975053c22 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/utilities/logging/PluginErrorLogger.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/utilities/logging/PluginErrorLogger.java @@ -19,6 +19,7 @@ package com.djrapitops.plan.utilities.logging; import com.djrapitops.plan.PlanPlugin; import com.djrapitops.plan.delivery.formatting.Formatters; import com.djrapitops.plan.exceptions.ExceptionWithContext; +import com.djrapitops.plan.exceptions.database.DBClosedException; import com.djrapitops.plan.identification.properties.ServerProperties; import com.djrapitops.plan.storage.file.PlanFiles; import com.djrapitops.plan.utilities.java.Lists; @@ -92,6 +93,10 @@ public class PluginErrorLogger implements ErrorLogger { } private void log(Consumer logMethod, Throwable throwable, ErrorContext context) { + if (isExceptionThatShouldNotBeLogged(throwable)) { + return; + } + String errorName = throwable.getClass().getSimpleName(); String hash = hash(throwable); Path logsDir = files.getLogsDirectory(); @@ -105,6 +110,11 @@ public class PluginErrorLogger implements ErrorLogger { } } + private boolean isExceptionThatShouldNotBeLogged(Throwable throwable) { + return throwable instanceof DBClosedException + || throwable.getCause() != null && isExceptionThatShouldNotBeLogged(throwable.getCause()); + } + private void logToFile(Path errorLog, Throwable throwable, ErrorContext context, String hash) { if (Files.exists(errorLog)) { logExisting(errorLog, throwable, context, hash); @@ -125,16 +135,15 @@ public class PluginErrorLogger implements ErrorLogger { private void logExisting(Path errorLog, Throwable throwable, ErrorContext context, String hash) { // Read existing - List lines; try (Stream read = Files.lines(errorLog)) { - lines = read.collect(Collectors.toList()); - } catch (IOException e) { + List lines = read.collect(Collectors.toList()); + + int occurrences = getOccurrences(lines) + 1; + List newLines = buildNewLines(context, lines, occurrences, hash); + overwrite(errorLog, throwable, newLines); + } catch (IOException | IndexOutOfBoundsException e) { logAfterReadError(errorLog, throwable, context, hash); - return; } - int occurrences = getOccurrences(lines) + 1; - List newLines = buildNewLines(context, lines, occurrences, hash); - overwrite(errorLog, throwable, newLines); } private void overwrite(Path errorLog, Throwable throwable, List newLines) { @@ -184,6 +193,8 @@ public class PluginErrorLogger implements ErrorLogger { } private int getOccurrences(List lines) { + if (lines.isEmpty()) return 0; + String occurLine = lines.get(0); return Integer.parseInt(StringUtils.splitByWholeSeparator(occurLine, ": ")[2].trim()); } @@ -204,7 +215,7 @@ public class PluginErrorLogger implements ErrorLogger { String errorMsg = throwable.getMessage(); String errorLocation = errorLog.toString(); return new String[]{ - "Ran into " + errorName + " - logged to " + errorLocation, + "Ran into " + errorName + (context.shouldLogErrorMessage() ? ": " + throwable.getMessage() : "") + " - logged to " + errorLocation, "(INCLUDE CONTENTS OF THE FILE IN ANY REPORTS)", context.getWhatToDo().map(td -> "What to do: " + td).orElse("Error msg: \"" + errorMsg + "\"") }; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/version/VersionChecker.java b/Plan/common/src/main/java/com/djrapitops/plan/version/VersionChecker.java index 5b1eafc9f..b3c181ef4 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/version/VersionChecker.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/version/VersionChecker.java @@ -42,16 +42,16 @@ import java.util.Optional; @Singleton public class VersionChecker implements SubSystem { - private final VersionNumber currentVersion; - private final Locale locale; - private final PlanConfig config; - private final PluginLogger logger; - private final RunnableFactory runnableFactory; - private final ErrorLogger errorLogger; + protected final VersionNumber currentVersion; + protected final Locale locale; + protected final PlanConfig config; + protected final PluginLogger logger; + protected final RunnableFactory runnableFactory; + protected final ErrorLogger errorLogger; private static final String DOWNLOAD_ICON_HTML = " "; - private VersionInfo newVersionAvailable; + protected VersionInfo newVersionAvailable; @Inject public VersionChecker( @@ -74,9 +74,20 @@ public class VersionChecker implements SubSystem { return newVersionAvailable != null; } - private void checkForUpdates() { + protected Optional> loadVersionInfo() { try { - List versions = VersionInfoLoader.load(); + return Optional.of(VersionInfoLoader.load()); + } catch (IOException e) { + errorLogger.warn(e, ErrorContext.builder() + .related(locale.getString(PluginLang.VERSION_FAIL_READ_VERSIONS)) + .whatToDo("Allow Plan to check for updates from Github/versions.txt or disable update check.") + .build()); + return Optional.empty(); + } + } + + private void checkForUpdates() { + loadVersionInfo().ifPresent(versions -> { if (config.isFalse(PluginSettings.NOTIFY_ABOUT_DEV_RELEASES)) { versions = Lists.filter(versions, VersionInfo::isRelease); } @@ -94,14 +105,10 @@ public class VersionChecker implements SubSystem { } else { logger.info(locale.getString(PluginLang.VERSION_NEWEST)); } - } catch (IOException e) { - errorLogger.warn(e, ErrorContext.builder() - .related(locale.getString(PluginLang.VERSION_FAIL_READ_VERSIONS)) - .whatToDo("Allow Plan to check for updates from Github/versions.txt or disable update check.") - .build()); - } + }); } + @Override public void enable() { if (config.isFalse(PluginSettings.CHECK_FOR_UPDATES)) { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/version/VersionInfo.java b/Plan/common/src/main/java/com/djrapitops/plan/version/VersionInfo.java index ae802cc8f..3afb14a4a 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/version/VersionInfo.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/version/VersionInfo.java @@ -75,4 +75,14 @@ public class VersionInfo implements Comparable { public int compareTo(VersionInfo o) { return this.version.compareTo(o.version); } + + @Override + public String toString() { + return "VersionInfo{" + + "release=" + release + + ", version=" + version + + ", downloadUrl='" + downloadUrl + '\'' + + ", changeLogUrl='" + changeLogUrl + '\'' + + '}'; + } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/version/VersionNumber.java b/Plan/common/src/main/java/com/djrapitops/plan/version/VersionNumber.java index e66046f9e..8a8620143 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/version/VersionNumber.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/version/VersionNumber.java @@ -23,7 +23,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; public class VersionNumber implements Comparable { - private static final Pattern MATCH_NUMBERS = Pattern.compile("([0-9][0-9]*)"); + private static final Pattern MATCH_NUMBERS = Pattern.compile("([0-9]+)"); private final String version; private final List versionNumbers; diff --git a/Plan/common/src/main/resources/assets/plan/bungeeconfig.yml b/Plan/common/src/main/resources/assets/plan/bungeeconfig.yml index a941d3acb..997f86b3f 100644 --- a/Plan/common/src/main/resources/assets/plan/bungeeconfig.yml +++ b/Plan/common/src/main/resources/assets/plan/bungeeconfig.yml @@ -19,6 +19,7 @@ Plugin: # Display update notification on the website Check_for_updates: true Notify_about_DEV_releases: false + Frontend_BETA: false # ----------------------------------------------------- # Supported databases: MySQL # ----------------------------------------------------- @@ -38,20 +39,25 @@ Database: # ----------------------------------------------------- Webserver: Port: 8804 - Alternative_IP: false + Alternative_IP: + Enabled: false # %port% is replaced automatically with Webserver.Port Address: your.domain.here:%port% # InternalIP usually does not need to be changed, only change it if you know what you're doing! # 0.0.0.0 allocates Internal (local) IP automatically for the WebServer. Internal_IP: 0.0.0.0 Cache: - Reduced_refresh_barrier: 15 + Reduced_refresh_barrier: + Time: 15 Unit: SECONDS - Invalidate_query_results_on_disk_after: 7 + Invalidate_query_results_on_disk_after: + Time: 7 Unit: DAYS - Invalidate_disk_cache_after: 2 + Invalidate_disk_cache_after: + Time: 2 Unit: DAYS - Invalidate_memory_cache_after: 5 + Invalidate_memory_cache_after: + Time: 5 Unit: MINUTES Security: SSL_certificate: @@ -68,12 +74,17 @@ Webserver: # Allows using the whitelist & brute-force shield with a reverse-proxy. # ! Make sure non-proxy access is not possible, it would allow IP spoofing ! Use_X-Forwarded-For_Header: false - IP_whitelist: false + Access_log: + Print_to_console: false + Remove_logs_after_days: 30 + IP_whitelist: + Enabled: false Whitelist: - "192.168.0.0" - "0:0:0:0:0:0:0:1" # Does not affect existing cookies - Cookies_expire_after: 2 + Cookies_expire_after: + Time: 2 Unit: HOURS Disable_Webserver: false External_Webserver_address: "https://www.example.address" @@ -85,48 +96,64 @@ Data_gathering: Accept_GeoLite2_EULA: false Ping: true Disk_space: true + # Does not affect already gathered data + Preserve_join_address_case: false # ----------------------------------------------------- # Supported time units: MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS # ----------------------------------------------------- Time: Delays: - Ping_server_enable_delay: 300 + Ping_server_enable_delay: + Time: 300 Unit: SECONDS - Ping_player_join_delay: 30 + Ping_player_join_delay: + Time: 30 Unit: SECONDS - Wait_for_DB_Transactions_on_disable: 20 + Wait_for_DB_Transactions_on_disable: + Time: 20 Unit: SECONDS Thresholds: # How long player needs to be idle until Plan considers them AFK - AFK_threshold: 3 + AFK_threshold: + Time: 3 Unit: MINUTES # Activity Index considers last 3 weeks and uses these thresholds in the calculation # The index is a number from 0 to 5. # These numbers were calibrated with data of 250 players (Small sample size). Activity_index: - Playtime_threshold: 30 + Playtime_threshold: + Time: 30 Unit: MINUTES - Remove_inactive_player_data_after: 180 + Remove_inactive_player_data_after: + Time: 3650 Unit: DAYS # Includes players online, tps and performance time series - Remove_time_series_data_after: 90 + Remove_time_series_data_after: + Time: 90 Unit: DAYS - Remove_ping_data_after: 14 + Remove_ping_data_after: + Time: 14 Unit: DAYS - Remove_disabled_extension_data_after: 2 + Remove_disabled_extension_data_after: + Time: 2 Unit: DAYS Periodic_tasks: - Extension_data_refresh_every: 1 + Extension_data_refresh_every: + Time: 1 Unit: HOURS - Check_DB_for_server_config_files_every: 1 + Check_DB_for_server_config_files_every: + Time: 1 Unit: MINUTES - Clean_Database_every: 1 + Clean_Database_every: + Time: 1 Unit: HOURS # ----------------------------------------------------- Display_options: # More information about Themes: # https://github.com/plan-player-analytics/Plan/wiki/Themes Theme: default + # Can use ${playerName} or ${playerUUID} or ${playerUUIDNoDash} + Player_head_image_url: "https://crafatar.com/avatars/${playerUUID}?size=120&default=MHF_Steve&overlay" Sessions: Show_on_page: 50 # By Default World playtime pie is ordered alphabetically. @@ -168,8 +195,8 @@ Formatting: Dates: # Show_recent_day_names replaces day number with Today, Yesterday, Wednesday etc. Show_recent_day_names: true - # Non-regex pattern to replace - DatePattern: 'MMM d YYYY' + # Non-regex pattern to replace + Show_recent_day_names_date_pattern: 'MMM d YYYY' Full: 'MMM d YYYY, HH:mm:ss' NoSeconds: 'MMM d YYYY, HH:mm' JustClock: 'HH:mm:ss' @@ -180,7 +207,13 @@ Formatting: # World aliases can be used to rename worlds and to combine multiple worlds into a group. # ----------------------------------------------------- World_aliases: - world: world + # List of world names: aliases, case sensitive. Set alias of two worlds to same one to group them. + # Automatically generated, if regex matches world will not be added here. + List: + world: world + # List of - "alias:regex" rules, Set alias of multiple worlds that match regex to group them + Regex: + - "Alias for world:^abc$" # ----------------------------------------------------- # These settings will make Plan write .js, .css, .json and .html files to some location on disk. # Relative path will render to /plugins/Plan/path @@ -200,7 +233,8 @@ Export: Export_player_on_login_and_logout: false # If there are multiple servers the period is divided evenly to avoid export of all servers at once # Also affects Players page export - Server_refresh_period: 20 + Server_refresh_period: + Time: 20 Unit: MINUTES # ----------------------------------------------------- # These settings affect Plugin data integration. @@ -208,5 +242,9 @@ Export: # ----------------------------------------------------- Plugins: Buycraft: - # http://help.buycraft.net/article/36-where-to-find-the-secret-key - Secret: "-" \ No newline at end of file + # https://docs.tebex.io/store/faq#how-can-i-find-my-secret-key + Secret: "-" +Customized_files: + Path: "web" + # Web dev mode enables all customized files and disables webserver resource cache for instant changes on browser refresh. + Enable_web_dev_mode: false \ No newline at end of file diff --git a/Plan/common/src/main/resources/assets/plan/config.yml b/Plan/common/src/main/resources/assets/plan/config.yml index 2155ba5c7..12794f214 100644 --- a/Plan/common/src/main/resources/assets/plan/config.yml +++ b/Plan/common/src/main/resources/assets/plan/config.yml @@ -20,6 +20,7 @@ Plugin: Notify_about_DEV_releases: false Configuration: Allow_proxy_to_manage_settings: true + Frontend_BETA: false # ----------------------------------------------------- # Supported databases: SQLite, MySQL # ----------------------------------------------------- @@ -40,20 +41,25 @@ Database: # ----------------------------------------------------- Webserver: Port: 8804 - Alternative_IP: false + Alternative_IP: + Enabled: false # %port% is replaced automatically with Webserver.Port Address: your.domain.here:%port% # InternalIP usually does not need to be changed, only change it if you know what you're doing! # 0.0.0.0 allocates Internal (local) IP automatically for the WebServer. Internal_IP: 0.0.0.0 Cache: - Reduced_refresh_barrier: 15 + Reduced_refresh_barrier: + Time: 15 Unit: SECONDS - Invalidate_query_results_on_disk_after: 7 + Invalidate_query_results_on_disk_after: + Time: 7 Unit: DAYS - Invalidate_disk_cache_after: 2 + Invalidate_disk_cache_after: + Time: 2 Unit: DAYS - Invalidate_memory_cache_after: 5 + Invalidate_memory_cache_after: + Time: 5 Unit: MINUTES Security: SSL_certificate: @@ -70,12 +76,17 @@ Webserver: # Allows using the whitelist with a reverse-proxy. # ! Make sure non-proxy access is not possible, it would allow IP spoofing ! Use_X-Forwarded-For_Header: false - IP_whitelist: false + Access_log: + Print_to_console: false + Remove_logs_after_days: 30 + IP_whitelist: + Enabled: false Whitelist: - "192.168.0.0" - "0:0:0:0:0:0:0:1" # Does not affect existing cookies - Cookies_expire_after: 2 + Cookies_expire_after: + Time: 2 Unit: HOURS Disable_Webserver: false External_Webserver_address: https://www.example.address @@ -90,48 +101,64 @@ Data_gathering: Commands: Log_unknown: false Log_aliases_as_main_command: true + # Does not affect already gathered data + Preserve_join_address_case: false # ----------------------------------------------------- # Supported time units: MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS # ----------------------------------------------------- Time: Delays: - Ping_server_enable_delay: 300 + Ping_server_enable_delay: + Time: 300 Unit: SECONDS - Ping_player_join_delay: 30 + Ping_player_join_delay: + Time: 30 Unit: SECONDS - Wait_for_DB_Transactions_on_disable: 20 + Wait_for_DB_Transactions_on_disable: + Time: 20 Unit: SECONDS Thresholds: # How long player needs to be idle until Plan considers them AFK - AFK_threshold: 3 + AFK_threshold: + Time: 3 Unit: MINUTES # Activity Index considers last 3 weeks and uses these thresholds in the calculation # The index is a number from 0 to 5. # These numbers were calibrated with data of 250 players (Small sample size). Activity_index: - Playtime_threshold: 30 + Playtime_threshold: + Time: 30 Unit: MINUTES - Remove_inactive_player_data_after: 180 + Remove_inactive_player_data_after: + Time: 3650 Unit: DAYS # Includes players online, tps and performance time series - Remove_time_series_data_after: 90 + Remove_time_series_data_after: + Time: 90 Unit: DAYS - Remove_ping_data_after: 14 + Remove_ping_data_after: + Time: 14 Unit: DAYS - Remove_disabled_extension_data_after: 2 + Remove_disabled_extension_data_after: + Time: 2 Unit: DAYS Periodic_tasks: - Extension_data_refresh_every: 1 + Extension_data_refresh_every: + Time: 1 Unit: HOURS - Check_DB_for_server_config_files_every: 1 + Check_DB_for_server_config_files_every: + Time: 1 Unit: MINUTES - Clean_Database_every: 1 + Clean_Database_every: + Time: 1 Unit: HOURS # ----------------------------------------------------- Display_options: # More information about Themes: # https://github.com/plan-player-analytics/Plan/wiki/Themes Theme: default + # Can use ${playerName} or ${playerUUID} or ${playerUUIDNoDash} + Player_head_image_url: "https://crafatar.com/avatars/${playerUUID}?size=120&default=MHF_Steve&overlay" Sessions: Show_on_page: 50 # By Default World playtime pie is ordered alphabetically. @@ -173,8 +200,8 @@ Formatting: Dates: # Show_recent_day_names replaces day number with Today, Yesterday, Wednesday etc. Show_recent_day_names: true - # Non-regex pattern to replace - DatePattern: 'MMM d YYYY' + # Non-regex pattern to replace + Show_recent_day_names_date_pattern: 'MMM d YYYY' Full: 'MMM d YYYY, HH:mm:ss' NoSeconds: 'MMM d YYYY, HH:mm' JustClock: HH:mm:ss @@ -185,7 +212,13 @@ Formatting: # World aliases can be used to rename worlds and to combine multiple worlds into a group. # ----------------------------------------------------- World_aliases: - world: world + # List of world names: aliases, case sensitive. Set alias of two worlds to same one to group them. + # Automatically generated, if regex matches world will not be added here. + List: + world: world + # List of - "alias:regex" rules, Set alias of multiple worlds that match regex to group them + Regex: + - "Alias for world:^abc$" # ----------------------------------------------------- # These settings will make Plan write .js, .css, .json and .html files to some location on disk. # Relative path will render to /plugins/Plan/path @@ -206,7 +239,8 @@ Export: Export_player_on_login_and_logout: false # If there are multiple servers the period is divided evenly to avoid export of all servers at once # Also affects Players page export - Server_refresh_period: 20 + Server_refresh_period: + Time: 20 Unit: MINUTES # ----------------------------------------------------- # These settings affect Plugin data integration. @@ -214,11 +248,15 @@ Export: # ----------------------------------------------------- Plugins: Buycraft: - # http://help.buycraft.net/article/36-where-to-find-the-secret-key + # https://docs.tebex.io/store/faq#how-can-i-find-my-secret-key Secret: '-' Factions: HideFactions: - ExampleFaction Towny: HideTowns: - - ExampleTown \ No newline at end of file + - ExampleTown +Customized_files: + Path: "web" + # Web dev mode enables all customized files and disables webserver resource cache for instant changes on browser refresh. + Enable_web_dev_mode: false \ No newline at end of file diff --git a/Plan/common/src/main/resources/assets/plan/geocodes.json b/Plan/common/src/main/resources/assets/plan/geocodes.json new file mode 100644 index 000000000..5f6e82985 --- /dev/null +++ b/Plan/common/src/main/resources/assets/plan/geocodes.json @@ -0,0 +1,225 @@ +{ + "belarus": "BLR", + "japan": "JPN", + "gambia, the": "GMB", + "south africa": "ZAF", + "guinea": "GIN", + "saint kitts and nevis": "KNA", + "san marino": "SMR", + "estonia": "EST", + "dominican republic": "DOM", + "cook islands": "COK", + "philippines": "PHL", + "cuba": "CUB", + "mauritius": "MUS", + "mali": "MLI", + "switzerland": "CHE", + "hong kong": "HKG", + "armenia": "ARM", + "vietnam": "VNM", + "australia": "AUS", + "laos": "LAO", + "aruba": "ABW", + "solomon islands": "SLB", + "turkey": "TUR", + "ukraine": "UKR", + "austria": "AUT", + "united states": "USA", + "cambodia": "KHM", + "monaco": "MCO", + "hungary": "HUN", + "kenya": "KEN", + "greenland": "GRL", + "greece": "GRC", + "paraguay": "PRY", + "palau": "PLW", + "congo, republic of the": "COG", + "vanuatu": "VUT", + "cyprus": "CYP", + "colombia": "COL", + "azerbaijan": "AZE", + "syria": "SYR", + "rwanda": "RWA", + "libya": "LBY", + "burkina faso": "BFA", + "guernsey": "GGY", + "afghanistan": "AFG", + "norway": "NOR", + "kiribati": "KIR", + "dominica": "DMA", + "bulgaria": "BGR", + "south korea": "KOR", + "bahrain": "BHR", + "guatemala": "GTM", + "ghana": "GHA", + "somalia": "SOM", + "jamaica": "JAM", + "togo": "TGO", + "liechtenstein": "LIE", + "thailand": "THA", + "france": "FRA", + "serbia": "SRB", + "senegal": "SEN", + "zambia": "ZMB", + "comoros": "COM", + "namibia": "NAM", + "saint pierre and miquelon": "SPM", + "taiwan": "TWN", + "canada": "CAN", + "french polynesia": "PYF", + "bahamas, the": "BHM", + "honduras": "HND", + "virgin islands": "VGB", + "maldives": "MDV", + "chile": "CHL", + "oman": "OMN", + "timor-leste": "TLS", + "brazil": "BRA", + "guyana": "GUY", + "lesotho": "LSO", + "germany": "DEU", + "india": "IND", + "malaysia": "MYS", + "peru": "PER", + "trinidad and tobago": "TTO", + "northern mariana islands": "MNP", + "denmark": "DNK", + "sri lanka": "LKA", + "jersey": "JEY", + "belize": "BLZ", + "kuwait": "KWT", + "slovenia": "SVN", + "haiti": "HTI", + "zimbabwe": "ZWE", + "el salvador": "SLV", + "macedonia": "MKD", + "saint lucia": "LCA", + "bolivia": "BOL", + "china": "CHN", + "falkland islands (islas malvinas)": "FLK", + "antigua and barbuda": "ATG", + "brunei": "BRN", + "israel": "ISR", + "west bank": "WBG", + "bangladesh": "BGD", + "czechia": "CZE", + "ireland": "IRL", + "albania": "ALB", + "poland": "POL", + "united arab emirates": "ARE", + "botswana": "BWA", + "andorra": "AND", + "venezuela": "VEN", + "marshall islands": "MHL", + "malawi": "MWI", + "moldova": "MDA", + "russia": "RUS", + "sweden": "SWE", + "north korea": "PRK", + "madagascar": "MDG", + "turkmenistan": "TKM", + "iran": "IRN", + "iraq": "IRQ", + "seychelles": "SYC", + "indonesia": "IDN", + "nicaragua": "NIC", + "faroe islands": "FRO", + "puerto rico": "PRI", + "gibraltar": "GIB", + "curacao": "CUW", + "equatorial guinea": "GNQ", + "ethiopia": "ETH", + "ecuador": "ECU", + "guinea-bissau": "GNB", + "saudi arabia": "SAU", + "mauritania": "MRT", + "spain": "ESP", + "algeria": "DZA", + "congo, democratic republic of the": "COD", + "american samoa": "ASM", + "mozambique": "MOZ", + "cameroon": "CMR", + "portugal": "PRT", + "costa rica": "CRI", + "lithuania": "LTU", + "south sudan": "SSD", + "panama": "PAN", + "lebanon": "LBN", + "luxembourg": "LUX", + "mongolia": "MNG", + "italy": "ITA", + "finland": "FIN", + "bosnia and herzegovina": "BIH", + "benin": "BEN", + "nigeria": "NGA", + "sudan": "SDN", + "chad": "TCD", + "liberia": "LBR", + "djibouti": "DJI", + "tajikistan": "TJK", + "fiji": "FJI", + "isle of man": "IMN", + "singapore": "SGP", + "mexico": "MEX", + "samoa": "WSM", + "tunisia": "TUN", + "bhutan": "BTN", + "uganda": "UGA", + "uruguay": "URY", + "gabon": "GAB", + "british virgin islands": "VGB", + "niger": "NER", + "kyrgyzstan": "KGZ", + "pakistan": "PAK", + "cabo verde": "CPV", + "kosovo": "KSV", + "georgia": "GEO", + "yemen": "YEM", + "cayman islands": "CYM", + "argentina": "ARG", + "anguilla": "AIA", + "jordan": "JOR", + "swaziland": "SWZ", + "burundi": "BDI", + "slovakia": "SVK", + "belgium": "BEL", + "uzbekistan": "UZB", + "tanzania": "TZA", + "grenada": "GRD", + "netherlands": "NLD", + "sao tome and principe": "STP", + "guam": "GUM", + "eritrea": "ERI", + "croatia": "HRV", + "micronesia, federated states of": "FSM", + "niue": "NIU", + "nepal": "NPL", + "morocco": "MAR", + "bermuda": "BMU", + "saint martin": "MAF", + "suriname": "SUR", + "burma": "MMR", + "central african republic": "CAF", + "romania": "ROU", + "angola": "AGO", + "new zealand": "NZL", + "czech republic": "CZE", + "sierra leone": "SLE", + "latvia": "LVA", + "kazakhstan": "KAZ", + "sint maarten": "SXM", + "cote d\u0027ivoire": "CIV", + "egypt": "EGY", + "united kingdom": "GBR", + "malta": "MLT", + "iceland": "ISL", + "montenegro": "MNE", + "macau": "MAC", + "papua new guinea": "PNG", + "tuvalu": "TUV", + "qatar": "QAT", + "saint vincent and the grenadines": "VCT", + "tonga": "TON", + "barbados": "BRB", + "new caledonia": "NCL" +} diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_CN.txt b/Plan/common/src/main/resources/assets/plan/locale/locale_CN.txt deleted file mode 100644 index b47f908e8..000000000 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_CN.txt +++ /dev/null @@ -1,517 +0,0 @@ -API - css+ || ้กต้ขๆ‰ฉๅฑ•๏ผš ${0} ๆทปๅŠ ๆ ทๅผ่กจ(s) ๅˆฐ ${1}, ${2} -API - js+ || ้กต้ขๆ‰ฉๅฑ•๏ผš ${0} ๆทปๅŠ  javascript(s) ๅˆฐ ${1}, ${2} -Cmd - Click Me || ็‚นๅ‡ปๆญคๅค„ -Cmd - Link || ้“พๆŽฅ -Cmd - Link Network || ็พค็ป„็ฝ‘็ปœ้กต้ข: -Cmd - Link Player || ็Žฉๅฎถไธชไบบ้กต้ข: -Cmd - Link Player JSON || ็Žฉๅฎถ json: -Cmd - Link Players || ๅ…จไฝ“็Žฉๅฎถ้กต้ข: -Cmd - Link Register || ๆณจๅ†Œ้กต้ข page: -Cmd - Link Server || ๆœๅŠกๅ™จ้กต้ข page: -CMD Arg - backup-file || ๅค‡ไปฝๆ–‡ไปถ็š„ๅ็งฐ๏ผˆๅŒบๅˆ†ๅคงๅฐๅ†™๏ผ‰ -CMD Arg - code || ๆณจๅ†Œ้œ€่ฆ็”จๅˆฐ็š„ไปฃ็ ใ€‚ -CMD Arg - db type backup || ่ฆๅค‡ไปฝ็š„ๆ•ฐๆฎๅบ“็š„็ฑปๅž‹ใ€‚ๅฆ‚ๆžœๆœชๆŒ‡ๅฎš๏ผŒๅˆ™ไฝฟ็”จๅฝ“ๅ‰ๆ•ฐๆฎๅบ“ใ€‚ -CMD Arg - db type clear || ่ฆๆธ…็ฉบๆ•ฐๆฎ็š„ๆ•ฐๆฎๅบ“็ฑปๅž‹ใ€‚ -CMD Arg - db type hotswap || ่ฆๅผ€ๅง‹ไฝฟ็”จ็š„ๆ–ฐๆ•ฐๆฎๅบ“็ฑปๅž‹ใ€‚ -CMD Arg - db type move from || ่ฆไปŽ็งปๅ‡บๆ•ฐๆฎ็š„ๆ•ฐๆฎๅบ“็ฑปๅž‹ใ€‚ -CMD Arg - db type move to || ่ฆๅฐ†ๆ•ฐๆฎ็งปๅ…ฅ็š„ๆ•ฐๆฎๅบ“็ฑปๅž‹ใ€‚ไธ่ƒฝๅ’Œไน‹ๅ‰ไธ€ๆ ทใ€‚ -CMD Arg - db type restore || ่ฆ่ฟ˜ๅŽŸ็š„ๆ•ฐๆฎๅบ“็š„็ฑปๅž‹ใ€‚ๅฆ‚ๆžœๆœชๆŒ‡ๅฎš๏ผŒๅˆ™ไฝฟ็”จๅฝ“ๅ‰ๆ•ฐๆฎๅบ“ใ€‚ -CMD Arg - feature || ่ฆ็ฆ็”จ็š„ๅŠŸ่ƒฝๅ็งฐ๏ผš${0} -CMD Arg - player identifier || ็Žฉๅฎถ็š„ๅ็งฐๆˆ– UUID -CMD Arg - player identifier remove || ่ฆไปŽๅฝ“ๅ‰ๆ•ฐๆฎๅบ“ๅˆ ้™ค็š„็Žฉๅฎถๆ ‡่ฏ†็ฌฆ -CMD Arg - server identifier || ๆœๅŠกๅ™จ็š„ๅ็งฐ๏ผŒID ๆˆ– UUID -CMD Arg - subcommand || ไฝฟ็”จไธๅธฆๅญๅ‘ฝไปค็š„ๅ‘ฝไปคๅณๅฏๆŸฅ็œ‹ๅธฎๅŠฉใ€‚๏ผˆ็›ดๆŽฅ่พ“ๅ…ฅ๏ผ‰ -CMD Arg - username || ๅฆไธ€ไธช็”จๆˆท็š„็”จๆˆทๅใ€‚ๅฆ‚ๆžœๆœชๆŒ‡ๅฎš๏ผŒๅˆ™ไฝฟ็”จ็Žฉๅฎถ็ป‘ๅฎš็š„็”จๆˆทใ€‚ -CMD Arg Name - backup-file || ๅค‡ไปฝๆ–‡ไปถ -CMD Arg Name - code || ${code} -CMD Arg Name - export kind || ๅฏผๅ‡บ็ฑปๅž‹ -CMD Arg Name - feature || ๅŠŸ่ƒฝ -CMD Arg Name - import kind || ๅฏผๅ…ฅ็ฑปๅž‹ -CMD Arg Name - name or uuid || ๅ็งฐ/uuid -CMD Arg Name - server || ๆœๅŠกๅ™จ -CMD Arg Name - subcommand || ๅญๅ‘ฝไปค -CMD Arg Name - username || ็”จๆˆทๅ -Cmd Confirm - accept || ๆŽฅๅ— -Cmd Confirm - cancelled, no data change || ๅทฒๅ–ๆถˆใ€‚ๆฒกๆœ‰ๆ•ฐๆฎ่ขซๆ›ดๆ”นใ€‚ -Cmd Confirm - cancelled, unregister || ๅทฒๅ–ๆถˆใ€‚ '${0}' ๅฐšๆœชๆณจ้”€ -Cmd Confirm - clearing db || ไฝ ๅฐ†่ฆๅˆ ้™ค ${0} ไธญ็š„ๆ‰€ๆœ‰ Plan ็š„ๆ•ฐๆฎ -Cmd Confirm - confirmation || ็กฎ่ฎค: -Cmd Confirm - deny || ๅ–ๆถˆ -Cmd Confirm - Expired || ็กฎ่ฎคๅทฒ่ฟ‡ๆœŸ๏ผŒ่ฏทๅ†ๆฌกไฝฟ็”จๅ‘ฝไปค -Cmd Confirm - Fail on accept || ๆŽฅๅ—ๆ“ไฝœๅœจๆ‰ง่กŒๆ—ถๅ‡บ้”™๏ผš ${0} -Cmd Confirm - Fail on deny || ๆ‹’็ปๆ“ไฝœๅœจๆ‰ง่กŒๆ—ถๅ‡บ้”™๏ผš ${0} -Cmd Confirm - overwriting db || ไฝ ๅฐ†่ฆ็”จ ${1} ไธญ็š„ๆ•ฐๆฎ่ฆ†็›– Plan ${0} ไธญ็š„ๆ•ฐๆฎใ€‚ -Cmd Confirm - remove player db || ไฝ ๅฐ†ไปŽ ${1} ไธญๅˆ ้™ค ${0} ็š„ๆ•ฐๆฎใ€‚ -Cmd Confirm - unregister || ๆ‚จๅณๅฐ†่งฃ้™คไธŽ ${1} ้“พๆŽฅ็š„ '${0}' ็š„ๆณจๅ†Œใ€‚ -Cmd db - creating backup || ๅˆ›ๅปบไธ€ไธชๅค‡ไปฝๆ–‡ไปถ '${0}.db'๏ผŒๅ†…ๅฎนไธบ ${1}ใ€‚ -Cmd db - removal || ไปŽ ${0} ไธญๅˆ ้™ค Plan ็š„ๆ•ฐๆฎ... -Cmd db - removal player || ไปŽ ${1} ไธญๅˆ ้™ค ${0} ็š„ๆ•ฐๆฎ... -Cmd db - server uninstalled || ยงaๅฆ‚ๆžœๆœๅŠกๅ™จๆฒกๆœ‰็œŸ็š„ๅธ่ฝฝ๏ผŒๅˆ™ๅฎƒๅฐ†่‡ชๅŠจๅœจๆ•ฐๆฎๅบ“ไธญๆŠŠ่‡ชๅทฑ่ฎพ็ฝฎไธบๅทฒๅฎ‰่ฃ…ใ€‚ -Cmd db - write || ๆญฃๅœจๅ†™ๅ…ฅ${0}... -Cmd Disable - Disabled || ยงa Plan ็ณป็ปŸ็Žฐๅœจๅทฒ่ขซ็ฆ็”จใ€‚ไฝ ไป็„ถๅฏไปฅไฝฟ็”จ reload ๆฅ้‡ๆ–ฐๅฏๅŠจๆ’ไปถใ€‚ -Cmd FAIL - Accepts only these arguments || ๆŽฅๅ—ไปฅไธ‹ๅ†…ๅฎน ${0}: ${1} -Cmd FAIL - Database not open || ยงcๆ•ฐๆฎๅบ“ไธบ ${0} - ่ฏท็จๅŽๅ†่ฏ•ใ€‚ -Cmd FAIL - Empty search string || ๆœ็ดขๅญ—็ฌฆไธฒไธ่ƒฝไธบ็ฉบ -Cmd FAIL - Invalid Username || ยงc่ฏฅ็”จๆˆทๆฒกๆœ‰ UUIDใ€‚ -Cmd FAIL - No Feature || ยงe่ฏท่ฎพ็ฝฎ่ฆ็ฆ็”จ็š„ๅŠŸ่ƒฝ๏ผ๏ผˆๅฝ“ๅ‰ๆ”ฏๆŒ ${0}๏ผ‰ -Cmd FAIL - No Permission || ยงcไฝ ๆฒกๆœ‰ๆ‰€้œ€็š„ๆƒ้™ใ€‚ -Cmd FAIL - No player || ๆ‰พไธๅˆฐ็Žฉๅฎถ '${0}'๏ผŒไป–ไปฌๆฒกๆœ‰ UUIDใ€‚ -Cmd FAIL - No player register || ๅœจๆ•ฐๆฎๅบ“ไธญๆ‰พไธๅˆฐ็Žฉๅฎถ '${0}'ใ€‚ -Cmd FAIL - No server || ๅœจๆ•ฐๆฎๅบ“ไธญๆ‰พไธๅˆฐๆœๅŠกๅ™จ '${0}'ใ€‚ -Cmd FAIL - Require only one Argument || ยงc้œ€่ฆๅ•ไธชๅ‚ๆ•ฐ ${1} -Cmd FAIL - Requires Arguments || ยงc้œ€่ฆๅ‚ๆ•ฐ (${0}) ${1} -Cmd FAIL - see config || ๆŸฅ็œ‹้…็ฝฎๆ–‡ไปถไธญ็š„ '${0}' -Cmd FAIL - Unknown Username || ยงcๅœจๆญคๆœๅŠกๅ™จไธŠๆœชๆ‰พๅˆฐ่ฏฅ็”จๆˆท -Cmd FAIL - Users not linked || ๆญค็”จๆˆทๆœช็ป‘ๅฎšๅˆฐไฝ ็š„ๅธๆˆท๏ผŒไธ”ไฝ ๆ— ๆƒๅˆ ้™คๅ…ถไป–็”จๆˆท็š„ๅธๆˆทใ€‚ -Cmd FAIL - WebUser does not exists || ยงc็”จๆˆทไธๅญ˜ๅœจ๏ผ -Cmd FAIL - WebUser exists || ยงc็”จๆˆทๅทฒๅญ˜ๅœจ๏ผ -Cmd Footer - Help || ยง7ๅฐ†้ผ ๆ ‡ๆ‚ฌๅœๅœจๅ‚ๆ•ฐๆˆ–ๅ‘ฝไปคไธŠๆฅไบ†่งฃๆ›ดๅคšๆœ‰ๅ…ณๅฎƒไปฌ็š„ไฟกๆฏ๏ผŒๆˆ–่€…ไฝฟ็”จ '/${0} ?'ใ€‚ -Cmd Header - Analysis || > ยง2ๅˆ†ๆž็ป“ๆžœ -Cmd Header - Help || > ยง2/${0} ๅธฎๅŠฉ -Cmd Header - Info || > ยง2็Žฉๅฎถๅˆ†ๆž -Cmd Header - Inspect || > ยง2็Žฉๅฎถ: ยงf${0} -Cmd Header - Network || > ยง2็พค็ป„็ฝ‘็ปœ้กต้ข -Cmd Header - Players || > ยง2ๅ…จไฝ“็Žฉๅฎถ -Cmd Header - Search || > ยง2${0} ๅฏนไบŽ ยงf${1}ยง2 ็š„็ป“ๆžœ: -Cmd Header - server list || id::ๅ็งฐ::uuid -Cmd Header - Servers || > ยง2ๅ…จ้ƒจๆœๅŠกๅ™จ -Cmd Header - web user list || ็”จๆˆทๅ::็ป‘ๅฎšๅˆฐ::ๆƒ้™็ญ‰็บง -Cmd Header - Web Users || > ยง2${0} ็ฝ‘้กต็”จๆˆท -Cmd Info - Bungee Connection || ยง2่ฟžๆŽฅ่‡ณไปฃ็†๏ผšยงf${0} -Cmd Info - Database || ยง2ๅฝ“ๅ‰ๆ•ฐๆฎๅบ“๏ผšยงf${0} -Cmd Info - Reload Complete || ยงa้‡่ฝฝๅฎŒๆˆ -Cmd Info - Reload Failed || ยงc้‡ๆ–ฐๅŠ ่ฝฝๆ’ไปถๅ‡บไบ†็‚น้—ฎ้ข˜๏ผŒๅปบ่ฎฎ้‡ๆ–ฐๅฏๅŠจใ€‚ -Cmd Info - Update || ยง2ๆœ‰ๅฏ็”จๆ›ดๆ–ฐ๏ผšยงf${0} -Cmd Info - Version || ยง2็‰ˆๆœฌ๏ผšยงf${0} -Cmd network - No network || ๆœๅŠกๅ™จๆœช่ฟžๆŽฅๅˆฐ็พค็ป„ใ€‚ๆญค้“พๆŽฅๅทฒ้‡ๅฎšๅ‘ๅˆฐๆœๅŠกๅ™จ้กต้ขใ€‚ -Cmd Notify - No Address || ยงeๆฒกๆœ‰ๅฏ็”จ็š„ๅœฐๅ€ - ๅทฒไฝฟ็”จ localhost ไฝœไธบๅŽๅค‡ๅœฐๅ€ใ€‚ๅœจ้…็ฝฎๆ–‡ไปถไธญ็š„ 'Alternative_IP' ่ฎพ็ฝฎๅœฐๅ€ใ€‚ -Cmd Notify - No WebUser || ไฝ ๅฏ่ƒฝๆฒกๆœ‰็ฝ‘้กต่ดฆๆˆท๏ผŒ่ฏทไฝฟ็”จ /plan register ๆฅๆณจๅ†Œ -Cmd Notify - WebUser register || ๆ–ฐ็”จๆˆทๅทฒๆณจๅ†Œ๏ผš '${0}' ๆƒ้™็ญ‰็บง๏ผš ${1} -Cmd Qinspect - Active Playtime || ยง2ๆดป่ทƒๆ—ถ้—ด๏ผšยงf${0} -Cmd Qinspect - Activity Index || ยง2ๆดป่ทƒๆŒ‡ๆ•ฐ๏ผšยงf${0} | ${1} -Cmd Qinspect - AFK Playtime || ยง2ๆŒ‚ๆœบๆ—ถ้—ด๏ผšยงf${0} -Cmd Qinspect - Deaths || ยง2ๆญปไบกๆ•ฐ๏ผšยงf${0} -Cmd Qinspect - Geolocation || ยง2็™ปๅฝ•ไฝ็ฝฎ๏ผšยงf${0} -Cmd Qinspect - Last Seen || ยง2ไธŠๆฌกๅœจ็บฟ๏ผšยงf${0} -Cmd Qinspect - Longest Session || ยง2ๆœ€้•ฟ็š„ไธ€ๆฌกๆธธ็Žฉ๏ผšยงf${0} -Cmd Qinspect - Mob Kills || ยง2็”Ÿ็‰ฉๅ‡ปๆ€ๆ•ฐ๏ผšยงf${0} -Cmd Qinspect - Player Kills || ยง2็Žฉๅฎถๅ‡ปๆ€ๆ•ฐ๏ผšยงf${0} -Cmd Qinspect - Playtime || ยง2ๆธธ็Žฉๆ—ถ้—ด๏ผšยงf${0} -Cmd Qinspect - Registered || ยง2ๆณจๅ†Œๆ—ถ้—ด๏ผšยงf${0} -Cmd Qinspect - Times Kicked || ยง2่ขซ่ธขๆฌกๆ•ฐ๏ผšยงf${0} -Cmd SUCCESS - Feature disabled || ยงaๆš‚ๆ—ถ็ฆ็”จ '${0}' ็›ดๅˆฐไธ‹ไธ€ๆฌก้‡่ฝฝๆ’ไปถใ€‚ -Cmd SUCCESS - WebUser register || ยงaๆˆๅŠŸๆทปๅŠ ไบ†ๆ–ฐ็”จๆˆท(${0})๏ผ -Cmd unregister - unregistering || ๆณจ้”€ '${0}' ไธญ... -Cmd WARN - Database not open || ยงeๆ•ฐๆฎๅบ“็Šถๆ€ไธบ ${0} - ่ฟ™ๅฏ่ƒฝ้œ€่ฆๆฏ”้ข„ๆœŸๆ›ด้•ฟ็š„ๆ—ถ้—ด... -Cmd Web - Permission Levels || >\ยง70: ่ฎฟ้—ฎๆ‰€ๆœ‰้กต้ข\ยง71: ่ฎฟ้—ฎ '/players' ๅ’Œๅ…จไฝ“็Žฉๅฎถ้กต้ข\ยง72: ่ฎฟ้—ฎ็”จๆˆทๅไธŽ็ฝ‘้กต็”จๆˆทๅไธ€่‡ด็š„็Žฉๅฎถ้กต\ยง73+: ๆฒกๆœ‰ๆƒ้™ -Command Help - /plan db || ็ฎก็† Plan ๆ•ฐๆฎๅบ“ -Command Help - /plan db backup || ๅฐ†ๆ•ฐๆฎๅบ“็š„ๆ•ฐๆฎๅค‡ไปฝๅˆฐไธ€ไธชๆ–‡ไปถไธญ -Command Help - /plan db clear || ไปŽๆ•ฐๆฎๅบ“ไธญๅˆ ้™คๆ‰€ๆœ‰ Plan ๆ•ฐๆฎ -Command Help - /plan db hotswap || ็ƒญไบคๆขๆ•ฐๆฎๅบ“ๅนถ้‡ๅฏๆ’ไปถ -Command Help - /plan db move || ๅœจๆ•ฐๆฎๅบ“้—ด็งปๅŠจๆ•ฐๆฎ -Command Help - /plan db remove || ไปŽๅฝ“ๅ‰ๆ•ฐๆฎๅบ“ไธญๅˆ ้™ค็Žฉๅฎถ็š„ๆ•ฐๆฎ -Command Help - /plan db restore || ๅฐ†ๆ•ฐๆฎไปŽๆ–‡ไปถๆขๅคๅˆฐๆ•ฐๆฎๅบ“ -Command Help - /plan db uninstalled || ๅœจๆ•ฐๆฎๅบ“ไธญๆŠŠไธ€ไธชๆœๅŠกๅ™จ่ฎพ็ฝฎไธบๅทฒๅธ่ฝฝใ€‚ -Command Help - /plan disable || ็ฆ็”จๆ•ดไธชๆ’ไปถๆˆ–็ฆ็”จๆ’ไปถ็š„้ƒจๅˆ†ๅŠŸ่ƒฝ -Command Help - /plan export || ๆ‰‹ๅŠจๅฏผๅ‡บ html ๆˆ– json ๆ–‡ไปถ -Command Help - /plan import || ๅฏผๅ…ฅๆ•ฐๆฎ -Command Help - /plan info || ๅ…ณไบŽๆญคๆ’ไปถ็š„ไฟกๆฏ -Command Help - /plan ingame || ๅœจๆธธๆˆไธญๆŸฅ็œ‹็Žฉๅฎถไฟกๆฏ -Command Help - /plan json || ๆŸฅ็œ‹็Žฉๅฎถ็š„ๅŽŸๅง‹ๆ•ฐๆฎ jsonใ€‚ -Command Help - /plan logout || ๅฐ†ๅ…ถไป–็”จๆˆทไปŽ้ขๆฟไธŠ็™ปๅ‡บใ€‚ -Command Help - /plan network || ๆŸฅ็œ‹็พค็ป„็ฝ‘็ปœ้กต้ข -Command Help - /plan player || ๆŸฅ็œ‹็Žฉๅฎถ้กต้ข -Command Help - /plan players || ๆŸฅ็œ‹ๅ…จไฝ“็Žฉๅฎถ้กต้ข -Command Help - /plan register || ๆณจๅ†Œไธ€ไธช็ฝ‘้กต็”จๆˆท -Command Help - /plan reload || ้‡ๅฏ Plan -Command Help - /plan search || ๆœ็ดข็Žฉๅฎถ -Command Help - /plan server || ๆŸฅ็œ‹ๆœๅŠกๅ™จ้กต้ข -Command Help - /plan servers || ๅˆ—ๅ‡บๆ•ฐๆฎๅบ“ไธญ็š„ๆœๅŠกๅ™จ -Command Help - /plan unregister || ๆณจ้”€ไธ€ไธช Plan ็ฝ‘้กต่ดฆๆˆท -Command Help - /plan users || ๅˆ—ๅ‡บๆ‰€ๆœ‰็ฝ‘้กต่ดฆๆˆท -Database - Apply Patch || ๆญฃๅœจๅบ”็”จ่กฅไธ๏ผš${0}... -Database - Patches Applied || ๅทฒๆˆๅŠŸๅบ”็”จๆ‰€ๆœ‰ๆ•ฐๆฎๅบ“่กฅไธใ€‚ -Database - Patches Applied Already || ๅทฒๅบ”็”จๆ‰€ๆœ‰ๆ•ฐๆฎๅบ“่กฅไธใ€‚ -Database MySQL - Launch Options Error || ๅฏๅŠจๅ‚ๆ•ฐๅ‡บ้”™๏ผŒๆญฃไฝฟ็”จ้ป˜่ฎคๅ‚ๆ•ฐ๏ผˆ${0}๏ผ‰ -Database Notify - Clean || ็งป้™คไบ† ${0} ไฝ็”จๆˆท็š„ๆ•ฐๆฎใ€‚ -Database Notify - SQLite No WAL || ๆญคๆœๅŠกๅ™จ็‰ˆๆœฌไธๆ”ฏๆŒ SQLite WAL ๆจกๅผ๏ผŒๆญฃไฝฟ็”จ้ป˜่ฎคๆจกๅผใ€‚่ฟ™ๅฏ่ƒฝไผšๅฝฑๅ“ๆ€ง่ƒฝใ€‚ -Disable || Plan ๆ’ไปถๅทฒ็ฆ็”จใ€‚ -Disable - Processing || ๆญฃๅœจๅค„็†ๆœชๅค„็†็š„ๅ…ณ้”ฎไปปๅŠกใ€‚(${0}) -Disable - Processing Complete || ๅค„็†ๅฎŒๆฏ•ใ€‚ -Disable - Unsaved Session Save || ไฟๅญ˜ๆœชๅฎŒๆˆ็š„ไผš่ฏไธญ... -Disable - Unsaved Session Save Timeout || ่ถ…ๆ—ถ๏ผŒๅฐ†ๅœจไธ‹ไธ€ๆฌกๅฏๅŠจๅ‚จๅญ˜ๆœชๅฎŒๆˆ็š„ไผš่ฏใ€‚ -Disable - Waiting SQLite || ๆญฃๅœจ็ญ‰ๅพ…ๆŸฅ่ฏขๅฎŒๆˆ๏ผŒไปฅ้ฟๅ… SQLite ไฝฟ JVM ๅดฉๆบƒ... -Disable - Waiting SQLite Complete || SQLite ่ฟžๆŽฅๅทฒๅ…ณ้—ญใ€‚ -Disable - Waiting Transactions || ๆญฃๅœจ็ญ‰ๅพ…ๆœชๅฎŒๆˆ็š„ไบ‹ๅŠกไปฅ้ฟๅ…ๆ•ฐๆฎไธขๅคฑ... -Disable - Waiting Transactions Complete || ไบ‹ๅŠก้˜Ÿๅˆ—ๅทฒๅ…ณ้—ญใ€‚ -Disable - WebServer || ็ฝ‘้กตๆœๅŠกๅ™จๅทฒๅ…ณ้—ญใ€‚ -Enable || Plan ๆ’ไปถๅทฒๅฏ็”จใ€‚ -Enable - Database || ${0} - ๅทฒ่ฟžๆŽฅๅˆฐๆ•ฐๆฎๅบ“ใ€‚ -Enable - Notify Bad IP || 0.0.0.0 ไธๆ˜ฏๆœ‰ๆ•ˆ็š„ๅœฐๅ€๏ผŒ่ฏทไฟฎๆ”น Alternative_IP ่ฎพ็ฝฎ. ๅฆๅˆ™ๅฏ่ƒฝไผšๅฏผ่‡ด็ฝ‘้กตๅœฐๅ€้”™่ฏฏ! -Enable - Notify Empty IP || server.properties ไธญ็š„ IP ไธบ็ฉบไธ”ๆœชไฝฟ็”จๅค‡็”จ IPใ€‚่ฟ™ๅฏ่ƒฝไผšๅฏผ่‡ดๅœฐๅ€ๅ‡บ้”™๏ผ -Enable - Notify Geolocations disabled || ๅทฒๅ…ณ้—ญๅœฐ็†ไฝ็ฝฎๆ”ถ้›†ใ€‚(Data.Geolocations: false) -Enable - Notify Geolocations Internet Required || Plan ้œ€่ฆๅœจ้ฆ–ๆฌก่ฟ่กŒๆ—ถ่ฎฟ้—ฎไบ’่”็ฝ‘ไปฅไธ‹่ฝฝ GeoLite2 ๅœฐ็†ไฝ็ฝฎๆ•ฐๆฎๅบ“ใ€‚ -Enable - Notify Webserver disabled || ็ฝ‘้กตๆœๅŠกๅ™จๆœชๅˆๅง‹ๅŒ–ใ€‚(WebServer.DisableWebServer: true) -Enable - Storing preserved sessions || ๆญฃๅœจๅ‚จๅญ˜ไน‹ๅ‰ๅ…ณๆœบๅ‰็•™ไธ‹็š„ไผš่ฏใ€‚ -Enable - WebServer || ็ฝ‘้กตๆœๅŠกๅ™จๅทฒๅœจ ${0} ( ${1} ) ็ซฏๅฃไธŠ่ฟ่กŒ -Enable FAIL - Database || ${0} - ่ฟžๆŽฅๅˆฐๆ•ฐๆฎๅบ“ๅคฑ่ดฅ๏ผš${1} -Enable FAIL - Database Patch || ๆ•ฐๆฎๅบ“่กฅไธๅคฑ่ดฅ๏ผŒๆ’ไปถๅฟ…้กป่ขซ็ฆ็”จใ€‚่ฏทๆŠฅๅ‘Šๆญค้—ฎ้ข˜ -Enable FAIL - GeoDB Write || ไฟๅญ˜ๅทฒไธ‹่ฝฝ็š„ GeoLite2 ๅœฐ็†ไฝ็ฝฎๆ•ฐๆฎๅบ“ๆ—ถๅ‘็”Ÿ้—ฎ้ข˜ -Enable FAIL - WebServer (Proxy) || ็ฝ‘้กตๆœๅŠกๅ™จๆฒกๆœ‰ๅˆๅง‹ๅŒ–! -Enable FAIL - Wrong Database Type || ${0} ๆ˜ฏไธๆ”ฏๆŒ็š„ๆ•ฐๆฎๅบ“็ฑปๅž‹ -HTML - AND_BUG_REPORTERS || ๅ’Œๅ…ถไป–้—ฎ้ข˜ๆŠฅๅ‘Š่€…๏ผ -HTML - BANNED (Filters) || ่ขซๅฐ็ฆ -HTML - COMPARING_15_DAYS || ๅฏนๆฏ” 15 ๅคฉ็š„ๆƒ…ๅ†ต -HTML - COMPARING_60_DAYS || ๅฏนๆฏ” 30 ๅคฉๅ‰ๅ’Œ็Žฐๅœจ็š„ๆƒ…ๅ†ต -HTML - COMPARING_7_DAYS || ๅฏนๆฏ” 7 ๅคฉ็š„ๆƒ…ๅ†ต -HTML - DATABASE_NOT_OPEN || ๆ•ฐๆฎๅบ“ๆœชๅผ€ๆ”พ, ไฝฟ็”จ /plan info ๆŸฅ็œ‹ๆ•ฐๆฎๅบ“็Šถๆ€ -HTML - DESCRIBE_RETENTION_PREDICTION || ่ฟ™ไธชๆ•ฐๅ€ผๆ˜ฏๅŸบไบŽไน‹ๅ‰็š„็Žฉๅฎถๆ•ฐๆฎ้ข„ๆต‹็š„ใ€‚ -HTML - ERROR || ่ฎค่ฏๆ—ถๅ‘็”Ÿ้”™่ฏฏ -HTML - EXPIRED_COOKIE || ็”จๆˆท Cookie ๅทฒ่ฟ‡ๆœŸ -HTML - FILTER_ACTIVITY_INDEX_NOW || ๆดป่ทƒๅบฆๅˆ†็ป„ -HTML - FILTER_ALL_PLAYERS || ๅ…จไฝ“็Žฉๅฎถ -HTML - FILTER_BANNED || ๅฐ็ฆ็Šถๆ€ -HTML - FILTER_GROUP || ๅฐ็ป„๏ผš -HTML - FILTER_OPS || ็ฎก็†ๅ‘˜็Šถๆ€ -HTML - INDEX_ACTIVE || ๆดป่ทƒ -HTML - INDEX_INACTIVE || ไธๆดป่ทƒ -HTML - INDEX_IRREGULAR || ๅถๅฐ”ไธŠ็บฟ -HTML - INDEX_REGULAR || ็ปๅธธไธŠ็บฟ -HTML - INDEX_VERY_ACTIVE || ้žๅธธๆดป่ทƒ -HTML - KILLED || ่ขซๅ‡ปๆ€ๆ•ฐ -HTML - LABEL_1ST_WEAPON || ๆœ€่‡ดๅ‘ฝ็š„ PVP ๆญฆๅ™จ -HTML - LABEL_2ND_WEAPON || ็ฌฌไบŒ่‡ดๅ‘ฝ็š„ PVP ๆญฆๅ™จ -HTML - LABEL_3RD_WEAPON || ็ฌฌไธ‰่‡ดๅ‘ฝ็š„ PVP ๆญฆๅ™จ -HTML - LABEL_ACTIVE_PLAYTIME || ๆดป่ทƒๆ—ถ้—ด -HTML - LABEL_ACTIVITY_INDEX || ๆดป่ทƒๆŒ‡ๆ•ฐ -HTML - LABEL_AFK || ๆŒ‚ๆœบ -HTML - LABEL_AFK_TIME || ๆŒ‚ๆœบๆ—ถ้—ด -HTML - LABEL_AVG || ๅนณๅ‡ -HTML - LABEL_AVG_ACTIVE_PLAYTIME || ๅนณๅ‡ๆดป่ทƒๆ—ถ้—ด -HTML - LABEL_AVG_AFK_TIME || ๅนณๅ‡ๆŒ‚ๆœบๆ—ถ้—ด -HTML - LABEL_AVG_CHUNKS || ๅนณๅ‡ๅŒบๅ—ๆ•ฐ -HTML - LABEL_AVG_ENTITIES || ๅนณๅ‡ๅฎžไฝ“ไนฆ -HTML - LABEL_AVG_KDR || ๅนณๅ‡ KDR -HTML - LABEL_AVG_MOB_KDR || ๅนณๅ‡็”Ÿ็‰ฉ KDR -HTML - LABEL_AVG_PLAYTIME || ๅนณๅ‡ๆธธ็Žฉๆ—ถ้—ด -HTML - LABEL_AVG_SESSION_LENGTH || ๅนณๅ‡ไผš่ฏๆ—ถ้•ฟ -HTML - LABEL_AVG_SESSIONS || ๅนณๅ‡ไผš่ฏ -HTML - LABEL_AVG_TPS || ๅนณๅ‡ TPS -HTML - LABEL_BANNED || ๅทฒ่ขซๅฐ็ฆ -HTML - LABEL_BEST_PEAK || ๆ‰€ๆœ‰ๆ—ถ้—ดๅณฐๅ€ผ -HTML - LABEL_DAY_OF_WEEK || ๆ˜ŸๆœŸ -HTML - LABEL_DEATHS || ๆญปไบกๆ•ฐ -HTML - LABEL_DOWNTIME || ๅœๆœบๆ—ถ้—ด -HTML - LABEL_DURING_LOW_TPS || ๆŒ็ปญไฝŽ TPS ๆ—ถ้—ด -HTML - LABEL_ENTITIES || ๅฎžไฝ“ -HTML - LABEL_FAVORITE_SERVER || ๆœ€ๅ–œ็ˆฑ็š„ๆœๅŠกๅ™จ -HTML - LABEL_FIRST_SESSION_LENGTH || ็ฌฌไธ€ๆฌกไผš่ฏๆ—ถ้•ฟ -HTML - LABEL_FREE_DISK_SPACE || ๅ‰ฉไฝ™็กฌ็›˜็ฉบ้—ด -HTML - LABEL_INACTIVE || ไธๆดป่ทƒ -HTML - LABEL_LAST_PEAK || ไธŠๆฌกๅœจ็บฟๅณฐๅ€ผ -HTML - LABEL_LAST_SEEN || ๆœ€ๅŽๅœจ็บฟๆ—ถ้—ด -HTML - LABEL_LOADED_CHUNKS || ๅทฒๅŠ ่ฝฝๅŒบๅ— -HTML - LABEL_LOADED_ENTITIES || ๅทฒๅŠ ่ฝฝๅฎžไฝ“ -HTML - LABEL_LONE_JOINS || ๅ•็‹ฌๅŠ ๅ…ฅ -HTML - LABEL_LONE_NEW_JOINS || ๅ•็‹ฌๆ–ฐ็ŽฉๅฎถๅŠ ๅ…ฅ -HTML - LABEL_LONGEST_SESSION || ๆœ€้•ฟไผš่ฏๆ—ถ้—ด -HTML - LABEL_LOW_TPS || ไฝŽ TPS ๆ—ถ้—ด -HTML - LABEL_MAX_FREE_DISK || ๆœ€ๅคงๅฏ็”จ็กฌ็›˜็ฉบ้—ด -HTML - LABEL_MIN_FREE_DISK || ๆœ€ๅฐๅฏ็”จ็กฌ็›˜็ฉบ้—ด -HTML - LABEL_MOB_DEATHS || ่ขซ็”Ÿ็‰ฉๅ‡ปๆ€ๆ•ฐ -HTML - LABEL_MOB_KDR || ็”Ÿ็‰ฉ KDR -HTML - LABEL_MOB_KILLS || ็”Ÿ็‰ฉๅ‡ปๆ€ๆ•ฐ -HTML - LABEL_MOST_ACTIVE_GAMEMODE || ๆœ€ๅธธ็Žฉ็š„ๆธธๆˆๆจกๅผ -HTML - LABEL_NAME || ๅ็งฐ -HTML - LABEL_NEW || ๆ–ฐ -HTML - LABEL_NEW_PLAYERS || ๆ–ฐ็Žฉๅฎถ -HTML - LABEL_NICKNAME || ๆ˜ต็งฐ -HTML - LABEL_NO_SESSION_KILLS || ๆ—  -HTML - LABEL_ONLINE_FIRST_JOIN || ็ฌฌไธ€ๆฌก่ฟ›ๅ…ฅๆœๅŠกๅ™จ็š„ๅœจ็บฟ็Žฉๅฎถ -HTML - LABEL_OPERATOR || ็ฎก็†ๅ‘˜ -HTML - LABEL_PER_PLAYER || / ็Žฉๅฎถ -HTML - LABEL_PER_REGULAR_PLAYER || / ๆ™ฎ้€š็Žฉๅฎถ -HTML - LABEL_PLAYER_DEATHS || ่ขซ็Žฉๅฎถๅ‡ปๆ€ๆฌกๆ•ฐ -HTML - LABEL_PLAYER_KILLS || ๅ‡ปๆ€็Žฉๅฎถๆ•ฐ -HTML - LABEL_PLAYERS_ONLINE || ๅœจ็บฟ็Žฉๅฎถ -HTML - LABEL_PLAYTIME || ๆธธ็Žฉๆ—ถ้—ด -HTML - LABEL_REGISTERED || ๆณจๅ†Œๆ—ถ้—ด -HTML - LABEL_REGISTERED_PLAYERS || ๅทฒๆณจๅ†Œ็š„็Žฉๅฎถ -HTML - LABEL_REGULAR || ๆ™ฎ้€š -HTML - LABEL_REGULAR_PLAYERS || ๆ™ฎ้€š็Žฉๅฎถ -HTML - LABEL_RELATIVE_JOIN_ACTIVITY || ๆœ€่ฟ‘ๅŠ ๅ…ฅๆดปๅŠจ -HTML - LABEL_RETENTION || ๆ–ฐ็Žฉๅฎถ็•™ๅ‘็Ž‡ -HTML - LABEL_SERVER_DOWNTIME || ๆœๅŠกๅ™จๅœๆœบๆ—ถ้—ด -HTML - LABEL_SERVER_OCCUPIED || ๆœๅŠกๅ™จๅœจ็บฟๆ—ถ้—ด -HTML - LABEL_SESSION_ENDED || ไผš่ฏ็ป“ๆŸ -HTML - LABEL_SESSION_MEDIAN || ๅนณๅ‡ไผš่ฏ้•ฟๅบฆ -HTML - LABEL_TIMES_KICKED || ่ขซ่ธขๅ‡บๆฌกๆ•ฐ -HTML - LABEL_TOTAL_PLAYERS || ๆ€ป็Žฉๅฎถๆ•ฐ -HTML - LABEL_TOTAL_PLAYTIME || ๆ€ปๆธธ็Žฉๆ—ถ้—ด -HTML - LABEL_UNIQUE_PLAYERS || ็‹ฌ็ซ‹็Žฉๅฎถ -HTML - LABEL_WEEK_DAYS || 'ๆ˜ŸๆœŸไธ€', 'ๆ˜ŸๆœŸไบŒ', 'ๆ˜ŸๆœŸไธ‰', 'ๆ˜ŸๆœŸๅ››', 'ๆ˜ŸๆœŸไบ”', 'ๆ˜ŸๆœŸๅ…ญ', 'ๆ˜ŸๆœŸๆ—ฅ' -HTML - LINK_BACK_NETWORK || ็พค็ป„็ฝ‘็ปœ้กต้ข -HTML - LINK_BACK_SERVER || ๆœๅŠกๅ™จ้กต้ข -HTML - LINK_CHANGELOG || ๆŸฅ็œ‹ๆ›ดๆ–ฐๆ—ฅๅฟ— -HTML - LINK_DISCORD || ไธ€่ˆฌ้—ฎ้ข˜ๆ”ฏๆŒ๏ผšDiscord -HTML - LINK_DOWNLOAD || ไธ‹่ฝฝ -HTML - LINK_ISSUES || ๆŠฅๅ‘Š้—ฎ้ข˜ -HTML - LINK_NIGHT_MODE || ๅคœ้—ดๆจกๅผ -HTML - LINK_PLAYER_PAGE || ็Žฉๅฎถ้กต้ข -HTML - LINK_QUICK_VIEW || ๅฟซ้€Ÿๆต่งˆ -HTML - LINK_SERVER_ANALYSIS || ๆœๅŠกๅ™จๅˆ†ๆž -HTML - LINK_WIKI || Plan Wiki,ๆ•™็จ‹ๅ’Œๆ–‡ๆกฃ -HTML - LOCAL_MACHINE || ๆœฌๅœฐไธปๆœบ -HTML - LOGIN_CREATE_ACCOUNT || ๅˆ›ๅปบไธ€ไธช่ดฆๆˆท๏ผ -HTML - LOGIN_FAILED || ็™ปๅฝ•ๅคฑ่ดฅ๏ผš -HTML - LOGIN_FORGOT_PASSWORD || ๅฟ˜่ฎฐๅฏ†็ ๏ผŸ -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_1 || ๅฟ˜่ฎฐๅฏ†็ ๏ผŸ ๆณจ้”€ๅนถๅ†ๆฌกๆณจๅ†Œใ€‚ -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_2 || ๅœจๆธธๆˆไธญไฝฟ็”จไปฅไธ‹ๅ‘ฝไปคๆฅๅˆ ้™คๅฝ“ๅ‰่ดฆๆˆท๏ผš -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_3 || ๆˆ–ไฝฟ็”จๆŽงๅˆถๅฐๅ‘ฝไปค๏ผš -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_4 || ไฝฟ็”จๅ‘ฝไปคๅŽ๏ผŒ -HTML - LOGIN_LOGIN || ็™ปๅฝ• -HTML - LOGIN_LOGOUT || ็™ปๅ‡บ -HTML - LOGIN_PASSWORD || "ๅฏ†็ " -HTML - LOGIN_USERNAME || "็”จๆˆทๅ" -HTML - NAV_PLUGINS || ๆ’ไปถ -HTML - NEW_CALENDAR || ๆ–ฐ๏ผš -HTML - NO_KILLS || ๆฒกๆœ‰ๅ‡ปๆ€ๆ•ฐ -HTML - NO_USER_PRESENT || ็”จๆˆท cookie ไธๅญ˜ๅœจ -HTML - NON_OPERATORS (Filters) || ้ž็ฎก็†ๅ‘˜ -HTML - NOT_BANNED (Filters) || ๆœช่ขซๅฐ็ฆ -HTML - OFFLINE || ็ฆป็บฟ -HTML - ONLINE || ๅœจ็บฟ -HTML - OPERATORS (Filters) || ็ฎก็†ๅ‘˜ -HTML - PER_DAY || / ๅคฉ -HTML - PLAYERS_TEXT || ็Žฉๅฎถ -HTML - QUERY || ๆŸฅ่ฏข< -HTML - QUERY_ACTIVITY_OF_MATCHED_PLAYERS || ๅŒน้…็Žฉๅฎถ็š„ๆดป่ทƒๅบฆ -HTML - QUERY_ACTIVITY_ON || ๆดป่ทƒๅœจ -HTML - QUERY_ADD_FILTER || ๆทปๅŠ ่ฟ‡ๆปคๅ™จ.. -HTML - QUERY_AND || ๅค–ๅŠ  -HTML - QUERY_ARE || `ๆ˜ฏ` -HTML - QUERY_ARE_ACTIVITY_GROUP || ๅœจๆดป่ทƒๅบฆๅˆ†็ป„ไธญ -HTML - QUERY_ARE_PLUGIN_GROUP || ๅœจ ${plugin} ๆ’ไปถ็š„ ${group} ๅˆ†็ป„ไธญ -HTML - QUERY_JOINED_WITH_ADDRESS || ๅŠ ๅ…ฅๅœฐๅ€ -HTML - QUERY_LOADING_FILTERS || ๅŠ ่ฝฝ่ฟ‡ๆปคๅ™จไธญ... -HTML - QUERY_MAKE || ่ฟ›่กŒๆŸฅ่ฏข -HTML - QUERY_MAKE_ANOTHER || ่ฟ›่กŒๅฆไธ€ไธชๆŸฅ่ฏข -HTML - QUERY_OF_PLAYERS || ๆŸฅ่ฏข็Žฉๅฎถ๏ผš -HTML - QUERY_PERFORM_QUERY || ๆ‰ง่กŒๆŸฅ่ฏข๏ผ -HTML - QUERY_PLAYED_BETWEEN || ๅœจๆญคๆœŸ้—ดๆธธ็Žฉ่ฟ‡ -HTML - QUERY_REGISTERED_BETWEEN || ๅœจๆญคๆœŸ้—ดๆณจๅ†Œ -HTML - QUERY_RESULTS || ๆŸฅ่ฏข็ป“ๆžœ -HTML - QUERY_RESULTS_MATCH || ๅŒน้…ๅˆฐ ${resultCount} ไธช็Žฉๅฎถ -HTML - QUERY_SESSIONS_WITHIN_VIEW || ๆŸฅ็œ‹่Œƒๅ›ดๅ†…็š„ไผš่ฏ -HTML - QUERY_SHOW_VIEW || ๆ—ฅๆœŸ่Œƒๅ›ด -HTML - QUERY_TIME_FROM || >ไปŽ -HTML - QUERY_TIME_TO || >ๅˆฐ -HTML - QUERY_VIEW || ๆ—ฅๆœŸ่Œƒๅ›ด: -HTML - QUERY_ZERO_RESULTS || ๆŸฅ่ฏขๅˆฐ 0 ไธช็ป“ๆžœ -HTML - REGISTER || ๆณจๅ†Œ -HTML - REGISTER_CHECK_FAILED || ๆฃ€ๆŸฅๆณจๅ†Œ็Šถๆ€ๅคฑ่ดฅ๏ผš -HTML - REGISTER_COMPLETE || ๆณจๅ†ŒๅฎŒๆˆ -HTML - REGISTER_COMPLETE_INSTRUCTIONS_1 || ๆ‚จ็ŽฐๅœจๅฏไปฅๅฎŒๆˆ็”จๆˆทๆณจๅ†Œๆต็จ‹ใ€‚ -HTML - REGISTER_COMPLETE_INSTRUCTIONS_2 || ๆณจๅ†Œไปฃ็ ๅฐ†ๅœจ 15 ๅˆ†้’ŸๅŽ่ฟ‡ๆœŸ -HTML - REGISTER_COMPLETE_INSTRUCTIONS_3 || ๅœจๆธธๆˆไธญไฝฟ็”จไปฅไธ‹ๅ‘ฝไปคๅฎŒๆˆๆณจๅ†Œ๏ผš -HTML - REGISTER_COMPLETE_INSTRUCTIONS_4 || ๆˆ–ไฝฟ็”จๆŽงๅˆถๅฐ๏ผš -HTML - REGISTER_CREATE_USER || ๅˆ›ๅปบไธ€ไธชๆ–ฐ็”จๆˆท -HTML - REGISTER_FAILED || ๆณจๅ†Œๅคฑ่ดฅ๏ผš -HTML - REGISTER_HAVE_ACCOUNT || ๅทฒ็ปๆœ‰ๅธๅทไบ†๏ผŸ ็™ปๅฝ•๏ผ -HTML - REGISTER_PASSWORD_TIP || ๅฏ†็ ไธ่ƒฝ่ถ…่ฟ‡8ไธชๅญ—็ฌฆ๏ผŒๆฒกๆœ‰ๅ…ถไป–้™ๅˆถใ€‚ -HTML - REGISTER_SPECIFY_PASSWORD || ไฝ ้œ€่ฆๅกซๅ†™ๅฏ†็  -HTML - REGISTER_SPECIFY_USERNAME || ไฝ ้œ€่ฆๅกซๅ†™็”จๆˆทๅ -HTML - REGISTER_USERNAME_LENGTH || ็”จๆˆทๅๆœ€ๅคšๅฏไปฅๅŒ…ๅซ 50 ไธชๅญ—็ฌฆ๏ผŒไฝ ็š„็”จๆˆทๅๆœ‰ -HTML - REGISTER_USERNAME_TIP || ็”จๆˆทๅๆœ€ๅคšๅฏไปฅๅŒ…ๅซ 50 ไธชๅญ—็ฌฆใ€‚ -HTML - SESSION || ไผš่ฏๆฌกๆ•ฐ -HTML - SIDE_GEOLOCATIONS || ๅœฐ็†ไฝ็ฝฎ -HTML - SIDE_INFORMATION || ไฟกๆฏ -HTML - SIDE_LINKS || ้“พๆŽฅ -HTML - SIDE_NETWORK_OVERVIEW || ็พค็ป„็ฝ‘็ปœๆ€ป่งˆ -HTML - SIDE_OVERVIEW || ๆ€ป่งˆ -HTML - SIDE_PERFORMANCE || ๆ€ง่ƒฝ -HTML - SIDE_PLAYER_LIST || ็Žฉๅฎถๅˆ—่กจ -HTML - SIDE_PLAYERBASE || ็Žฉๅฎถๆ•ฐๆฎ -HTML - SIDE_PLAYERBASE_OVERVIEW || ็Žฉๅฎถๆ•ฐๆฎๆ€ป่งˆ -HTML - SIDE_PLUGINS || ๆ’ไปถ -HTML - SIDE_PVP_PVE || PvP ๅ’Œ PvE -HTML - SIDE_SERVERS || ๆœๅŠกๅ™จ -HTML - SIDE_SERVERS_TITLE || ๆœๅŠกๅ™จ -HTML - SIDE_SESSIONS || ไผš่ฏ -HTML - SIDE_TO_MAIN_PAGE || ๅ›žๅˆฐไธป้กต้ข -HTML - TEXT_CLICK_TO_EXPAND || ็‚นๅ‡ปๅฑ•ๅผ€ -HTML - TEXT_CONTRIBUTORS_CODE || ไปฃ็ ่ดก็Œฎ่€… -HTML - TEXT_CONTRIBUTORS_LOCALE || ็ฟป่ฏ‘่€… -HTML - TEXT_CONTRIBUTORS_MONEY || ็‰นๅˆซๆ„Ÿ่ฐข้‚ฃไบ›ๅœจ็ปๆตŽไธŠๆ”ฏๆŒๅผ€ๅ‘็š„ไบบไปฌใ€‚ -HTML - TEXT_CONTRIBUTORS_THANKS || ไปฅไธ‹ ไผ˜็ง€ไบบ็‰ฉ ไนŸๅšๅ‡บไบ†่ดก็Œฎ๏ผš -HTML - TEXT_DEV_VERSION || ่ฟ™ๆ˜ฏไธ€ไธชๅผ€ๅ‘็‰ˆๆœฌใ€‚ -HTML - TEXT_DEVELOPED_BY || ็š„ๅผ€ๅ‘่€…ๆ˜ฏ -HTML - TEXT_FIRST_SESSION || ็ฌฌไธ€ๆญคไผš่ฏ -HTML - TEXT_LICENSED_UNDER || Player Analytics ๅผ€ๅ‘ๅ’ŒๆŽˆๆƒไบŽ -HTML - TEXT_METRICS || bStats ็ปŸ่ฎก -HTML - TEXT_NO_EXTENSION_DATA || ๆฒกๆœ‰ๆ‰ฉๅฑ•ๆ•ฐๆฎ -HTML - TEXT_NO_LOW_TPS || ๆฒกๆœ‰ไฝŽ TPS ๆ—ถ้—ด -HTML - TEXT_NO_SERVER || ๆฒกๆœ‰ๅฏๆ˜พ็คบๅœจ็บฟๆดปๅŠจ็š„ๆœๅŠกๅ™จ -HTML - TEXT_NO_SERVERS || ๆ•ฐๆฎๅบ“ไธญๆ‰พไธๅˆฐๆœๅŠกๅ™จ -HTML - TEXT_PLUGIN_INFORMATION || ๆ’ไปถไฟกๆฏ -HTML - TEXT_PREDICTED_RETENTION || ่ฟ™ไธชๆ•ฐๅ€ผๆ˜ฏๅŸบไบŽไน‹ๅ‰็š„็Žฉๅฎถๆ•ฐๆฎ้ข„ๆต‹็š„ -HTML - TEXT_SERVER_INSTRUCTIONS || ็œ‹่ตทๆฅ Plan ๆฒกๆœ‰ๅฎ‰่ฃ…ๅœจไปปไฝ•ๆธธๆˆๆœๅŠกๅ™จไธŠๆˆ–่€…ๆธธๆˆๆœๅŠกๅ™จๆœช่ฟžๆŽฅๅˆฐ็›ธๅŒ็š„ๆ•ฐๆฎๅบ“ใ€‚ ็พค็ป„็ฝ‘็ปœๆ•™็จ‹่ฏทๅ‚่ง๏ผšwiki -HTML - TEXT_VERSION || ๆœ‰ๆ–ฐ็‰ˆๆœฌๅฏไพ›ไธ‹่ฝฝใ€‚ -HTML - TITLE_30_DAYS || 30 ๅคฉ -HTML - TITLE_30_DAYS_AGO || 30 ๅคฉๅ‰ -HTML - TITLE_ALL || ๅ…จ้ƒจ -HTML - TITLE_ALL_TIME || ๆ‰€ๆœ‰ๆ—ถ้—ด -HTML - TITLE_AS_NUMBERS || ๆ•ฐๆฎ -HTML - TITLE_AVG_PING || ๅนณๅ‡ๅปถ่ฟŸ -HTML - TITLE_BEST_PING || ๆœ€ไฝŽๅปถ่ฟŸ -HTML - TITLE_CALENDAR || ๆ—ฅๅŽ† -HTML - TITLE_CONNECTION_INFO || ่ฟžๆŽฅไฟกๆฏ -HTML - TITLE_COUNTRY || ๅ›ฝๅฎถๅ’ŒๅœฐๅŒบ -HTML - TITLE_CPU_RAM || CPU ๅ’Œๅ†…ๅญ˜ -HTML - TITLE_CURRENT_PLAYERBASE || ๅฝ“ๅ‰็Žฉๅฎถๆ•ฐ -HTML - TITLE_DISK || ็กฌ็›˜็ฉบ้—ด -HTML - TITLE_GRAPH_DAY_BY_DAY || ๆŒ‰ๅคฉๆŸฅ็œ‹ -HTML - TITLE_GRAPH_HOUR_BY_HOUR || ๆŒ‰ๅฐๆ—ถๆŸฅ็œ‹ -HTML - TITLE_GRAPH_NETWORK_ONLINE_ACTIVITY || ็พค็ป„็ฝ‘็ปœๅœจ็บฟๆดปๅŠจ -HTML - TITLE_GRAPH_PUNCHCARD || 30 ๅคฉๆ‰“ๅก -HTML - TITLE_INSIGHTS || 30 ๅคฉๅˆ†ๆž -HTML - TITLE_IS_AVAILABLE || ๅฏ็”จ -HTML - TITLE_JOIN_ADDRESSES || ๅŠ ๅ…ฅๅœฐๅ€ -HTML - TITLE_LAST_24_HOURS || ่ฟ‡ๅŽป 24 ๅฐๆ—ถ -HTML - TITLE_LAST_30_DAYS || ่ฟ‡ๅŽป 30 ๅคฉ -HTML - TITLE_LAST_7_DAYS || ่ฟ‡ๅŽป 7 ๅคฉ -HTML - TITLE_LAST_CONNECTED || ๆœ€ๅŽ่ฟžๆŽฅๆ—ถ้—ด -HTML - TITLE_LENGTH || ๆธธ็Žฉๆ—ถ้•ฟ -HTML - TITLE_MOST_PLAYED_WORLD || ็Žฉ็š„ๆœ€ๅคš็š„ไธ–็•Œ -HTML - TITLE_NETWORK || ็พค็ป„็ฝ‘็ปœ -HTML - TITLE_NETWORK_AS_NUMBERS || ็พค็ป„็ฝ‘็ปœๆ•ฐๆฎ -HTML - TITLE_NOW || ็Žฐๅœจ -HTML - TITLE_ONLINE_ACTIVITY || ๅœจ็บฟๆดปๅŠจ -HTML - TITLE_ONLINE_ACTIVITY_AS_NUMBERS || ๅœจ็บฟๆดปๅŠจๆ•ฐๆฎ -HTML - TITLE_ONLINE_ACTIVITY_OVERVIEW || ๅœจ็บฟๆดปๅŠจๆ€ป่งˆ -HTML - TITLE_PERFORMANCE_AS_NUMBERS || ๆ€ง่ƒฝๆ•ฐๆฎ -HTML - TITLE_PING || ๅปถ่ฟŸ -HTML - TITLE_PLAYER || ็Žฉๅฎถ -HTML - TITLE_PLAYER_OVERVIEW || ็Žฉๅฎถๆ€ป่งˆ -HTML - TITLE_PLAYERBASE_DEVELOPMENT || ็Žฉๅฎถๅ‘ๅฑ• -HTML - TITLE_PVP_DEATHS || ๆœ€่ฟ‘็š„ PVP ๆญปไบก -HTML - TITLE_PVP_KILLS || ๆœ€่ฟ‘็š„ PVP ๅ‡ปๆ€ -HTML - TITLE_PVP_PVE_NUMBERS || PvP ๅ’Œ PvE ๆ•ฐๆฎ -HTML - TITLE_RECENT_KILLS || ๆœ€่ฟ‘ๅ‡ปๆ€ -HTML - TITLE_RECENT_SESSIONS || ๆœ€่ฟ‘ไผš่ฏ -HTML - TITLE_SEEN_NICKNAMES || ็”จ่ฟ‡็š„ๆ˜ต็งฐ -HTML - TITLE_SERVER || ๆœๅŠกๅ™จ -HTML - TITLE_SERVER_AS_NUMBERS || ๆœๅŠกๅ™จๆ•ฐๆฎ -HTML - TITLE_SERVER_OVERVIEW || ๆœๅŠกๅ™จๆ€ป่งˆ -HTML - TITLE_SERVER_PLAYTIME || ๆœๅŠกๅ™จๆธธๆˆๆ—ถ้—ด -HTML - TITLE_SERVER_PLAYTIME_30 || ๆœ€่ฟ‘ 30 ๅคฉๅ†…็š„ๆœๅŠกๅ™จๆธธ็Žฉๆ—ถ้—ด -HTML - TITLE_SESSION_START || ไผš่ฏๅผ€ๅง‹ไบŽ -HTML - TITLE_THEME_SELECT || ไธป้ข˜้€‰ๆ‹ฉ -HTML - TITLE_TITLE_PLAYER_PUNCHCARD || ๆ‰“ๅก -HTML - TITLE_TPS || TPS -HTML - TITLE_TREND || ่ถ‹ๅŠฟ -HTML - TITLE_TRENDS || 30 ๅคฉ่ถ‹ๅŠฟ -HTML - TITLE_VERSION || ็‰ˆๆœฌ -HTML - TITLE_WEEK_COMPARISON || ๆฏๅ‘จๅฏนๆฏ” -HTML - TITLE_WORLD || ไธ–็•ŒๅŠ ่ฝฝ -HTML - TITLE_WORLD_PLAYTIME || ไธ–็•Œๆธธ็Žฉๆ—ถ้—ด -HTML - TITLE_WORST_PING || ๆœ€้ซ˜ๅปถ่ฟŸ -HTML - TOTAL_ACTIVE_TEXT || ๆ€ปๆดป่ทƒๆ—ถ้•ฟ -HTML - TOTAL_AFK || ๆ€ปๆŒ‚ๆœบๆ—ถ้•ฟ -HTML - TOTAL_PLAYERS || ๆ€ปๆธธ็Žฉๆ—ถ้•ฟ -HTML - UNIQUE_CALENDAR || ็‹ฌ็ซ‹๏ผš -HTML - UNIT_CHUNKS || ๅŒบๅ— -HTML - UNIT_ENTITIES || ๅฎžไฝ“ -HTML - UNIT_NO_DATA || ๆฒกๆœ‰ๆ•ฐๆฎ -HTML - UNIT_THE_PLAYERS || ็Žฉๅฎถ -HTML - USER_AND_PASS_NOT_SPECIFIED || ๆœชๆŒ‡ๅฎš็”จๆˆทๅไธŽๅฏ†็  -HTML - USER_DOES_NOT_EXIST || ็”จๆˆทไธๅญ˜ๅœจ -HTML - USER_INFORMATION_NOT_FOUND || ๆณจๅ†Œๅคฑ่ดฅ๏ผŒ่ฏท้‡่ฏ•๏ผˆๆณจๅ†Œไปฃ็ ๆœ‰ๆ•ˆๆœŸ 15 ๅˆ†้’Ÿ๏ผ‰ -HTML - USER_PASS_MISMATCH || ็”จๆˆทๅๅ’Œๅฏ†็ ไธๅŒน้… -HTML - Version Change log || ๆŸฅ็œ‹ๆ›ดๆ–ฐๆ—ฅๅฟ— -HTML - Version Current || ไฝ ็š„็‰ˆๆœฌๆ˜ฏ ${0} -HTML - Version Download || ไธ‹่ฝฝ Plan - ${0}.jar -HTML - Version Update || ๆ›ดๆ–ฐ -HTML - Version Update Available || ๆ–ฐ็‰ˆๆœฌ ${0} ็Žฐๅœจๅฏ็”จ๏ผ -HTML - Version Update Dev || ่ฟ™ๆ˜ฏไธ€ไธชๅผ€ๅ‘็‰ˆๆœฌใ€‚ -HTML - Version Update Info || ๆœ‰ๆ–ฐ็‰ˆๆœฌๅฏไพ›ไธ‹่ฝฝใ€‚ -HTML - WARNING_NO_GAME_SERVERS || ่ฆ่Žทๅ–ๆŸไบ›ๆ•ฐๆฎ๏ผŒไฝ ้œ€่ฆๅฐ† Plan ๅฎ‰่ฃ…ๅœจๆธธๆˆๆœๅŠกๅ™จไธŠใ€‚ -HTML - WARNING_NO_GEOLOCATIONS || ้œ€่ฆๅœจ้…็ฝฎๆ–‡ไปถไธญๅฏ็”จๅœฐ็†ไฝ็ฝฎๆ”ถ้›†(Accept GeoLite2 EULA)ใ€‚ -HTML - WARNING_NO_SPONGE_CHUNKS || ๅŒบๅ—ๆ•ฐๆฎๅœจ Sponge ๆœๅŠก็ซฏไธๅฏ็”จ -HTML - WITH || ไธŽ -HTML ERRORS - ACCESS_DENIED_403 || ๆ‹’็ป่ฎฟ้—ฎ -HTML ERRORS - AUTH_FAIL_TIPS_401 || - ็กฎไฟไฝ ๅทฒไฝฟ็”จ /plan register ๆฅๆณจๅ†Œ็”จๆˆท
- ๆฃ€ๆŸฅ็”จๆˆทๅไธŽๅฏ†็ ๆ˜ฏๅฆๆญฃ็กฎ
- ็”จๆˆทๅไธŽๅฏ†็ ๅŒบๅˆ†ๅคงๅฐๅ†™

่‹ฅๆ‚จๅฟ˜่ฎฐไบ†ๅฏ†็ ๏ผŒ่ฏท่ฎฉๅทฅไฝœไบบๅ‘˜ๅˆ ้™คๆ‚จ็š„ๆ—งๅฏ†็ ๅนถ้‡ๆ–ฐๆณจๅ†Œใ€‚ -HTML ERRORS - AUTHENTICATION_FAILED_401 || ่ฎค่ฏๅคฑ่ดฅใ€‚ -HTML ERRORS - FORBIDDEN_403 || ็ฆๆญข่ฎฟ้—ฎ -HTML ERRORS - NO_SERVERS_404 || ๆ— ๅฏๆ‰ง่กŒๆญค่ฏทๆฑ‚็š„ๅœจ็บฟๆœๅŠกๅ™จใ€‚ -HTML ERRORS - NOT_FOUND_404 || ๆœชๆ‰พๅˆฐ -HTML ERRORS - NOT_PLAYED_404 || Plan ๆฒกๆœ‰ๆ‰พๅˆฐๆญค็Žฉๅฎถใ€‚ -HTML ERRORS - PAGE_NOT_FOUND_404 || ้กต้ขไธๅญ˜ๅœจใ€‚ -HTML ERRORS - UNAUTHORIZED_401 || ๆœช่ฎค่ฏ -HTML ERRORS - UNKNOWN_PAGE_404 || ่ฏท็กฎไฟๆ‚จๆญฃ้€š่ฟ‡ๅ‘ฝไปคๆ‰€็ป™ๅ‡บ็š„้“พๆŽฅ่ฎฟ้—ฎ๏ผŒ็คบไพ‹๏ผš

/player/็Žฉๅฎถๅ
/server/ๆœๅŠกๅ™จๅ

-HTML ERRORS - UUID_404 || ๆœชๅœจๆ•ฐๆฎๅบ“ไธญๆ‰พๅˆฐๆญค็Žฉๅฎถ็š„ UUIDใ€‚ -In Depth Help - /plan db || ไฝฟ็”จไธๅŒ็š„ๆ•ฐๆฎๅบ“ๅญๅ‘ฝไปคๆฅๆŸ็งๆ–นๅผๆ›ดๆ”นๆ•ฐๆฎ -In Depth Help - /plan db backup || ไฝฟ็”จ SQLite ๅฐ†็›ฎๆ ‡ๆ•ฐๆฎๅบ“ๅค‡ไปฝๅˆฐๆ–‡ไปถไธญใ€‚ -In Depth Help - /plan db clear || ๆธ…้™คๆ‰€ๆœ‰ Plan ๆ•ฐๆฎ่กจ๏ผŒๅนถๅˆ ้™คๆ‰€ๆœ‰ๅค„็†ไธญ็š„ Plan ๆ•ฐๆฎใ€‚ -In Depth Help - /plan db hotswap || ็”จๅฆไธ€ไธชๆ•ฐๆฎๅบ“้‡ๆ–ฐๅŠ ่ฝฝๆ’ไปถ๏ผŒๅนถๆ”นๅ˜้…็ฝฎไฝฟๅ…ถๅŒน้…ใ€‚ -In Depth Help - /plan db move || ็”จไธ€ไธชๆ•ฐๆฎๅบ“ไธญ็š„ๅ†…ๅฎน่ฆ†็›–ๅฆไธ€ไธชๆ•ฐๆฎๅบ“ไธญ็š„ๅ†…ๅฎนใ€‚ -In Depth Help - /plan db remove || ไปŽๅฝ“ๅ‰ๆ•ฐๆฎๅบ“ไธญๅˆ ้™คไธŽๆŸไธช็Žฉๅฎถ็›ธๅ…ณ็š„ๆ‰€ๆœ‰ๆ•ฐๆฎใ€‚ -In Depth Help - /plan db restore || ไฝฟ็”จ SQLite ๅค‡ไปฝๆ–‡ไปถๅนถ่ฆ†็›–็›ฎๆ ‡ๆ•ฐๆฎๅบ“็š„ๅ†…ๅฎนใ€‚ -In Depth Help - /plan db uninstalled || ๅฐ† Plan ๆ•ฐๆฎๅบ“ไธญ็š„ไธ€ไธชๆœๅŠกๅ™จๆ ‡่ฎฐไธบๅทฒๅธ่ฝฝ๏ผŒ่ฟ™ๆ ทๅฎƒๅฐฑไธไผšๅœจๆœๅŠกๅ™จๆŸฅ่ฏข้กต้ขไธญๆ˜พ็คบๅ‡บๆฅใ€‚ -In Depth Help - /plan disable || ็ฆ็”จๆ•ดไธชๆ’ไปถๆˆ–็ฆ็”จๆ’ไปถ็š„้ƒจๅˆ†ๅŠŸ่ƒฝ๏ผŒ็›ดๅˆฐไธ‹ๆฌก้‡ๆ–ฐๅŠ ่ฝฝ/้‡ๆ–ฐๅฏๅŠจใ€‚ -In Depth Help - /plan export || ๆŠŠๆ•ฐๆฎๅฏผๅ‡บๅˆฐ้…็ฝฎๆ–‡ไปถไธญๆŒ‡ๅฎš็š„ๅฏผๅ‡บไฝ็ฝฎใ€‚ -In Depth Help - /plan import || ๆ‰ง่กŒๅฏผๅ…ฅ๏ผŒๅฐ†ๆ•ฐๆฎๅŠ ่ฝฝๅˆฐๆ•ฐๆฎๅบ“ใ€‚ -In Depth Help - /plan info || ๆ˜พ็คบๆ’ไปถ็š„ๅฝ“ๅ‰็Šถๆ€ใ€‚ -In Depth Help - /plan ingame || ๆ˜พ็คบๆญฃๅœจๆธธๆˆไธญ็š„็Žฉๅฎถ็š„ไธ€ไบ›ไฟกๆฏใ€‚ -In Depth Help - /plan json || ๅ…่ฎธไฝ ไธ‹่ฝฝ json ๆ ผๅผ็š„็Žฉๅฎถๆ•ฐๆฎใ€‚ๆ‰€ๆœ‰็š„ๆ•ฐๆฎ้ƒฝๅœจ้‡Œ้ขใ€‚ -In Depth Help - /plan logout || ่พ“ๅ…ฅ็”จๆˆทๅไฝœไธบๅ‚ๆ•ฐๅฏไปฅๆณจ้”€ Plan ไธŠ็š„ไธ€ไธช็”จๆˆท๏ผŒ่พ“ๅ…ฅ * ไฝœไธบๅ‚ๆ•ฐๅฏไปฅๆณจ้”€ๆ‰€ๆœ‰็”จๆˆทใ€‚ -In Depth Help - /plan network || ่Žทๅพ—ไธ€ไธชๆŒ‡ๅ‘ /network page๏ผˆ็พค็ป„็ฝ‘็ปœ๏ผ‰ ็š„้“พๆŽฅ๏ผŒๅช่ƒฝๅœจ็พค็ป„็ฝ‘็ปœไธŠ่ฟ™ๆ ทๅšใ€‚ -In Depth Help - /plan player || ่Žทๅพ—ไธ€ไธชๆŒ‡ๅ‘็‰นๅฎš็Žฉๅฎถๆˆ–ๅฝ“ๅ‰็Žฉๅฎถ็š„ /player page๏ผˆ็Žฉๅฎถ้กต้ข๏ผ‰ ็š„้“พๆŽฅใ€‚ -In Depth Help - /plan players || ่Žทๅพ—ไธ€ไธชๆŒ‡ๅ‘ /players page๏ผˆๅ…จไฝ“็Žฉๅฎถ้กต้ข๏ผ‰ ็š„้“พๆŽฅ๏ผŒไปฅๆŸฅ็œ‹็Žฉๅฎถๅˆ—่กจใ€‚ -In Depth Help - /plan register || ็›ดๆŽฅไฝฟ็”จไผš่Žทๅพ—ๆณจๅ†Œ้กต้ข็š„้“พๆŽฅใ€‚ๆทปๅŠ  --code[ๆณจๅ†Œไปฃ็ ] ๅ‚ๆ•ฐๅฏไปฅๆณจๅ†Œไธ€ไธช่ดฆๆˆทใ€‚ -In Depth Help - /plan reload || ็ฆ็”จ็„ถๅŽ้‡ๆ–ฐๅฏ็”จๆœฌๆ’ไปถ๏ผŒไผš้‡ๆ–ฐๅŠ ่ฝฝ้…็ฝฎไธญ็š„่ฎพ็ฝฎใ€‚ -In Depth Help - /plan search || ๅˆ—ๅ‡บๆ‰€ๆœ‰ไธŽ็ป™ๅฎšๅๅญ—้ƒจๅˆ†็›ธๅŒน้…็š„็Žฉๅฎถๅๅญ—ใ€‚ -In Depth Help - /plan server || ่Žทๅ–ไธ€ไธชๆŒ‡ๅ‘็‰นๅฎšๆœๅŠกๅ™จ็š„ /server page๏ผˆๆœๅŠกๅ™จ้กต้ข๏ผ‰ ็š„้“พๆŽฅ๏ผŒๅฆ‚ๆžœๆฒกๆœ‰็ป™ๅ‡บๅ‚ๆ•ฐ๏ผŒๅˆ™่Žทๅ–ๅฝ“ๅ‰ๆœๅŠกๅ™จ็š„้“พๆŽฅใ€‚ -In Depth Help - /plan servers || ๅˆ—ๅ‡บๆ•ฐๆฎๅบ“ไธญๆ‰€ๆœ‰ๆœๅŠกๅ™จ็š„IDใ€ๅ็งฐๅ’ŒUUIDใ€‚ -In Depth Help - /plan unregister || ไธๅซๅ‚ๆ•ฐไฝฟ็”จไผšๆณจ้”€ๅฝ“ๅ‰็ป‘ๅฎš็š„่ดฆๆˆท๏ผŒไฝฟ็”จ็”จๆˆทๅไฝœไธบๅ‚ๆ•ฐ่ƒฝๆณจ้”€ๅฆไธ€ไธช็”จๆˆทใ€‚ -In Depth Help - /plan users || ไปฅ่กจๆ ผๅฝขๅผๅˆ—ๅ‡บ็ฝ‘้กต็”จๆˆทใ€‚ -Manage - Confirm Overwrite || ๆ•ฐๆฎๅบ“ ${0} ไธญ็š„ๆ•ฐๆฎๅฐ†่ขซ่ฆ†็›–! -Manage - Confirm Removal || ๆ•ฐๆฎๅบ“ ${0} ไธญ็š„ๆ•ฐๆฎๅฐ†่ขซๅˆ ้™ค! -Manage - Fail || > ยงcๅ‘็”Ÿไบ†้”™่ฏฏ๏ผš${0} -Manage - Fail File not found || > ยงcๆฒกๆœ‰ๅœจ ${0} ๅ‘็Žฐๆ–‡ไปถ -Manage - Fail Incorrect Database || > ยงc'${0}' ๆ˜ฏไธ€ไธชไธๆ”ฏๆŒ็š„ๆ•ฐๆฎๅบ“ -Manage - Fail No Exporter || ยงeๅฏผๅ‡บๅ™จ '${0}' ไธๅญ˜ๅœจ -Manage - Fail No Importer || ยงeๅฏผๅ…ฅๅ™จ '${0}' ไธๅญ˜ๅœจ -Manage - Fail No Server || ๆฒกๆœ‰ๆ‰พๅˆฐๅ…ทๆœ‰็ป™ๅฎšๅ‚ๆ•ฐ็š„ๆœๅŠกๅ™จใ€‚ -Manage - Fail Same Database || > ยงcไธ่ƒฝๅœจๅŒไธ€ไธชๆ•ฐๆฎๅบ“ไธญๆ“ไฝœ! -Manage - Fail Same server || ไธ่ƒฝๅฐ†ๆญคๆœๅŠกๅ™จๆ ‡่ฎฐไธบๅทฒๅธ่ฝฝ๏ผˆไฝ ๅœจ่ฟ™ไธชๆœๅŠกๅ™จไธŠ๏ผ‰ใ€‚ -Manage - Fail, Confirmation || > ยงcๆทปๅŠ  '-a' ๅ‚ๆ•ฐๆฅ็กฎ่ฎคๆ‰ง่กŒ๏ผš${0} -Manage - List Importers || ๅฏผๅ…ฅๅ™จ๏ผš -Manage - Progress || ${0} / ${1} ๅค„็†ไธญ... -Manage - Remind HotSwap || ยงe่ฏทๅˆ‡ๆขๅˆฐๆ–ฐ็š„ๆ•ฐๆฎๅบ“(/plan db hotswap ${0})ๅนถ้‡ๆ–ฐๅŠ ่ฝฝๆ’ไปถใ€‚ -Manage - Start || > ยง2ๅค„็†ๆ•ฐๆฎไธญ... -Manage - Success || > ยงaๆˆๅŠŸ๏ผ -Negative || ๅฆ -Positive || ๆ˜ฏ -Today || 'ไปŠๅคฉ' -Unavailable || ไธๅฏ็”จ -Unknown || ไฝ็ฝฎ -Version - DEV || ่ฟ™ๆ˜ฏไธ€ไธชๅผ€ๅ‘็‰ˆๆœฌใ€‚ -Version - Latest || ไฝ ๆญฃๅœจไฝฟ็”จๆœ€ๆ–ฐ็‰ˆๆœฌใ€‚ -Version - New || ๆœ‰ๆ–ฐ็‰ˆๆœฌ (${0}) ๅฏ็”จ ${1} -Version - New (old) || ๆœ‰ๆ–ฐ็‰ˆๆœฌๅฏ็”จ๏ผš${0} -Version FAIL - Read info (old) || ๆ— ๆณ•ๆฃ€ๆŸฅๆœ€ๆ–ฐ็‰ˆๆœฌๅท -Version FAIL - Read versions.txt || ๆ— ๆณ•ไปŽ Github/versions.txt ๅŠ ่ฝฝ็‰ˆๆœฌไฟกๆฏ -Web User Listing || ยง2${0} ยง7: ยงf${1} -WebServer - Notify HTTP || ็ฝ‘้กตๆœๅŠกๅ™จ๏ผšๆ— ่ฏไนฆ -> ๆญฃไฝฟ็”จ HTTP ๆœๅŠกๅ™จๆไพ›ๅฏ่ง†ๅŒ–ๆ•ˆๆžœใ€‚ -WebServer - Notify HTTP User Auth || ็ฝ‘้กตๆœๅŠกๅ™จ๏ผšๅทฒ็ฆ็”จ็”จๆˆท็™ปๅฝ•๏ผ๏ผˆHTTP ๆ–นๅผไธๅฎ‰ๅ…จ๏ผ‰ -WebServer - Notify HTTPS User Auth || ็ฝ‘้กตๆœๅŠกๅ™จ: ็”จๆˆท็™ปๅฝ•ๅทฒๅ…ณ้—ญ! ๏ผˆๅทฒๅœจ้…็ฝฎๆ–‡ไปถไธญ็ฆ็”จ๏ผ‰ -Webserver - Notify IP Whitelist || ็ฝ‘้กตๆœๅŠกๅ™จ: IP ็™ฝๅๅ•ๅทฒๅฏ็”จใ€‚ -Webserver - Notify IP Whitelist Block || ็ฝ‘้กตๆœๅŠกๅ™จ๏ผš${0} ่ขซๆ‹’็ป่ฎฟ้—ฎ '${1}'. ๏ผˆไธๅœจ็™ฝๅๅ•ไธญ๏ผ‰ -WebServer - Notify no Cert file || ็ฝ‘้กตๆœๅŠกๅ™จ๏ผšๆ‰พไธๅˆฐ่ฏไนฆๅฏ†้’ฅๅบ“ๆ–‡ไปถ๏ผš${0} -WebServer - Notify Using Proxy || ็ฝ‘้กตๆœๅŠกๅ™จ: HTTPS ไปฃ็†ๆจกๅผๅทฒๅผ€ๅฏ, ่ฏท็กฎไฟไฝ ็š„ๅๅ‘ไปฃ็†ๅทฒ็ป้…็ฝฎไธบ HTTPS ๆจกๅผๅนถไธ” Plan ็š„ Alternative_IP.Address ้€‰้กนๅทฒ็ปๆŒ‡ๅ‘ไปฃ็† -WebServer FAIL - EOF || ็ฝ‘้กตๆœๅŠกๅ™จ: ๅœจ่ฏปๅ–่ฏไนฆๆ–‡ไปถๆ—ถๅ‡บ็Žฐไบ†EOFๅผ‚ๅธธ. ๏ผˆ่ฏทๆฃ€ๆŸฅ่ฏไนฆๆ–‡ไปถๅฎŒๆ•ดๆ€ง๏ผ‰ -WebServer FAIL - Port Bind || ๆœชๆˆๅŠŸๅˆๅง‹ๅŒ–็ฝ‘้กตๆœๅŠกๅ™จใ€‚็ซฏๅฃ(${0})ๆ˜ฏๅฆ่ขซๅทฒ่ขซๅ ็”จ๏ผŸ -WebServer FAIL - SSL Context || ็ฝ‘้กตๆœๅŠกๅ™จ๏ผšSSL ็Žฏๅขƒๅˆๅง‹ๅŒ–ๅคฑ่ดฅใ€‚ -WebServer FAIL - Store Load || ็ฝ‘้กตๆœๅŠกๅ™จ๏ผšSSL ่ฏไนฆ่ฝฝๅ…ฅๅคฑ่ดฅใ€‚ -Yesterday || 'ๆ˜จๅคฉ' diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_CN.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_CN.yml new file mode 100644 index 000000000..666c47432 --- /dev/null +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_CN.yml @@ -0,0 +1,669 @@ +403AccessDenied: "ๆ‹’็ป่ฎฟ้—ฎ" +command: + argument: + backupFile: + description: "ๅค‡ไปฝๆ–‡ไปถ็š„ๅ็งฐ๏ผˆๅŒบๅˆ†ๅคงๅฐๅ†™๏ผ‰" + name: "ๅค‡ไปฝๆ–‡ไปถ" + code: + description: "ๆณจๅ†Œ้œ€่ฆ็”จๅˆฐ็š„ไปฃ็ ใ€‚" + name: "${code}" + dbBackup: + description: "่ฆๅค‡ไปฝ็š„ๆ•ฐๆฎๅบ“็š„็ฑปๅž‹ใ€‚ๅฆ‚ๆžœๆœชๆŒ‡ๅฎš๏ผŒๅˆ™ไฝฟ็”จๅฝ“ๅ‰ๆ•ฐๆฎๅบ“ใ€‚" + dbRestore: + description: "่ฆ่ฟ˜ๅŽŸ็š„ๆ•ฐๆฎๅบ“็š„็ฑปๅž‹ใ€‚ๅฆ‚ๆžœๆœชๆŒ‡ๅฎš๏ผŒๅˆ™ไฝฟ็”จๅฝ“ๅ‰ๆ•ฐๆฎๅบ“ใ€‚" + dbTypeHotswap: + description: "่ฆๅผ€ๅง‹ไฝฟ็”จ็š„ๆ–ฐๆ•ฐๆฎๅบ“็ฑปๅž‹ใ€‚" + dbTypeMoveFrom: + description: "่ฆไปŽ็งปๅ‡บๆ•ฐๆฎ็š„ๆ•ฐๆฎๅบ“็ฑปๅž‹ใ€‚" + dbTypeMoveTo: + description: "่ฆๅฐ†ๆ•ฐๆฎ็งปๅ…ฅ็š„ๆ•ฐๆฎๅบ“็ฑปๅž‹ใ€‚ไธ่ƒฝๅ’Œไน‹ๅ‰ไธ€ๆ ทใ€‚" + dbTypeRemove: + description: "่ฆๆธ…็ฉบๆ•ฐๆฎ็š„ๆ•ฐๆฎๅบ“็ฑปๅž‹ใ€‚" + exportKind: "ๅฏผๅ‡บ็ฑปๅž‹" + feature: + description: "่ฆ็ฆ็”จ็š„ๅŠŸ่ƒฝๅ็งฐ๏ผš${0}" + name: "ๅŠŸ่ƒฝ" + importKind: "ๅฏผๅ…ฅ็ฑปๅž‹" + nameOrUUID: + description: "็Žฉๅฎถ็š„ๅ็งฐๆˆ– UUID" + name: "ๅ็งฐ/uuid" + removeDescription: "่ฆไปŽๅฝ“ๅ‰ๆ•ฐๆฎๅบ“ๅˆ ้™ค็š„็Žฉๅฎถๆ ‡่ฏ†็ฌฆ" + server: + description: "ๆœๅŠกๅ™จ็š„ๅ็งฐ๏ผŒID ๆˆ– UUID" + name: "ๆœๅŠกๅ™จ" + subcommand: + description: "ไฝฟ็”จไธๅธฆๅญๅ‘ฝไปค็š„ๅ‘ฝไปคๅณๅฏๆŸฅ็œ‹ๅธฎๅŠฉใ€‚๏ผˆ็›ดๆŽฅ่พ“ๅ…ฅ๏ผ‰" + name: "ๅญๅ‘ฝไปค" + username: + description: "ๅฆไธ€ไธช็”จๆˆท็š„็”จๆˆทๅใ€‚ๅฆ‚ๆžœๆœชๆŒ‡ๅฎš๏ผŒๅˆ™ไฝฟ็”จ็Žฉๅฎถ็ป‘ๅฎš็š„็”จๆˆทใ€‚" + name: "็”จๆˆทๅ" + confirmation: + accept: "ๆŽฅๅ—" + cancelNoChanges: "ๅทฒๅ–ๆถˆใ€‚ๆฒกๆœ‰ๆ•ฐๆฎ่ขซๆ›ดๆ”นใ€‚" + cancelNoUnregister: "ๅทฒๅ–ๆถˆใ€‚ '${0}' ๅฐšๆœชๆณจ้”€" + confirm: "็กฎ่ฎค: " + dbClear: "ไฝ ๅฐ†่ฆๅˆ ้™ค ${0} ไธญ็š„ๆ‰€ๆœ‰ Plan ็š„ๆ•ฐๆฎ" + dbOverwrite: "ไฝ ๅฐ†่ฆ็”จ ${1} ไธญ็š„ๆ•ฐๆฎ่ฆ†็›– Plan ${0} ไธญ็š„ๆ•ฐๆฎใ€‚" + dbRemovePlayer: "ไฝ ๅฐ†ไปŽ ${1} ไธญๅˆ ้™ค ${0} ็š„ๆ•ฐๆฎใ€‚" + deny: "ๅ–ๆถˆ" + expired: "็กฎ่ฎคๅทฒ่ฟ‡ๆœŸ๏ผŒ่ฏทๅ†ๆฌกไฝฟ็”จๅ‘ฝไปค" + unregister: "ๆ‚จๅณๅฐ†่งฃ้™คไธŽ ${1} ้“พๆŽฅ็š„ '${0}' ็š„ๆณจๅ†Œใ€‚" + database: + creatingBackup: "ๅˆ›ๅปบไธ€ไธชๅค‡ไปฝๆ–‡ไปถ '${0}.db'๏ผŒๅ†…ๅฎนไธบ ${1}ใ€‚" + failDbNotOpen: "ยงcๆ•ฐๆฎๅบ“ไธบ ${0} - ่ฏท็จๅŽๅ†่ฏ•ใ€‚" + manage: + confirm: "> ยงcๆทปๅŠ  '-a' ๅ‚ๆ•ฐๆฅ็กฎ่ฎคๆ‰ง่กŒ๏ผš${0}" + confirmOverwrite: "ๆ•ฐๆฎๅบ“ ${0} ไธญ็š„ๆ•ฐๆฎๅฐ†่ขซ่ฆ†็›–!" + confirmPartialRemoval: "Join Address Data for Server ${0} in ${1} will be removed!" + confirmRemoval: "ๆ•ฐๆฎๅบ“ ${0} ไธญ็š„ๆ•ฐๆฎๅฐ†่ขซๅˆ ้™ค!" + fail: "> ยงcๅ‘็”Ÿไบ†้”™่ฏฏ๏ผš${0}" + failFileNotFound: "> ยงcๆฒกๆœ‰ๅœจ ${0} ๅ‘็Žฐๆ–‡ไปถ" + failIncorrectDB: "> ยงc'${0}' ๆ˜ฏไธ€ไธชไธๆ”ฏๆŒ็š„ๆ•ฐๆฎๅบ“" + failNoServer: "ๆฒกๆœ‰ๆ‰พๅˆฐๅ…ทๆœ‰็ป™ๅฎšๅ‚ๆ•ฐ็š„ๆœๅŠกๅ™จใ€‚" + failSameDB: "> ยงcไธ่ƒฝๅœจๅŒไธ€ไธชๆ•ฐๆฎๅบ“ไธญๆ“ไฝœ!" + failSameServer: "ไธ่ƒฝๅฐ†ๆญคๆœๅŠกๅ™จๆ ‡่ฎฐไธบๅทฒๅธ่ฝฝ๏ผˆไฝ ๅœจ่ฟ™ไธชๆœๅŠกๅ™จไธŠ๏ผ‰ใ€‚" + hotswap: "ยงe่ฏทๅˆ‡ๆขๅˆฐๆ–ฐ็š„ๆ•ฐๆฎๅบ“(/plan db hotswap ${0})ๅนถ้‡ๆ–ฐๅŠ ่ฝฝๆ’ไปถใ€‚" + importers: "ๅฏผๅ…ฅๅ™จ๏ผš" + preparing: "Preparing.." + progress: "${0} / ${1} ๅค„็†ไธญ..." + start: "> ยง2ๅค„็†ๆ•ฐๆฎไธญ..." + success: "> ยงaๆˆๅŠŸ๏ผ" + playerRemoval: "ไปŽ ${1} ไธญๅˆ ้™ค ${0} ็š„ๆ•ฐๆฎ..." + removal: "ไปŽ ${0} ไธญๅˆ ้™ค Plan ็š„ๆ•ฐๆฎ..." + serverUninstalled: "ยงaๅฆ‚ๆžœๆœๅŠกๅ™จๆฒกๆœ‰็œŸ็š„ๅธ่ฝฝ๏ผŒๅˆ™ๅฎƒๅฐ†่‡ชๅŠจๅœจๆ•ฐๆฎๅบ“ไธญๆŠŠ่‡ชๅทฑ่ฎพ็ฝฎไธบๅทฒๅฎ‰่ฃ…ใ€‚" + unregister: "ๆณจ้”€ '${0}' ไธญ..." + warnDbNotOpen: "ยงeๆ•ฐๆฎๅบ“็Šถๆ€ไธบ ${0} - ่ฟ™ๅฏ่ƒฝ้œ€่ฆๆฏ”้ข„ๆœŸๆ›ด้•ฟ็š„ๆ—ถ้—ด..." + write: "ๆญฃๅœจๅ†™ๅ…ฅ${0}..." + fail: + emptyString: "ๆœ็ดขๅญ—็ฌฆไธฒไธ่ƒฝไธบ็ฉบ" + invalidArguments: "ๆŽฅๅ—ไปฅไธ‹ๅ†…ๅฎน ${0}: ${1}" + invalidUsername: "ยงc่ฏฅ็”จๆˆทๆฒกๆœ‰ UUIDใ€‚" + missingArguments: "ยงc้œ€่ฆๅ‚ๆ•ฐ (${0}) ${1}" + missingFeature: "ยงe่ฏท่ฎพ็ฝฎ่ฆ็ฆ็”จ็š„ๅŠŸ่ƒฝ๏ผ๏ผˆๅฝ“ๅ‰ๆ”ฏๆŒ ${0}๏ผ‰" + missingLink: "ๆญค็”จๆˆทๆœช็ป‘ๅฎšๅˆฐไฝ ็š„ๅธๆˆท๏ผŒไธ”ไฝ ๆ— ๆƒๅˆ ้™คๅ…ถไป–็”จๆˆท็š„ๅธๆˆทใ€‚" + noPermission: "ยงcไฝ ๆฒกๆœ‰ๆ‰€้œ€็š„ๆƒ้™ใ€‚" + onAccept: "ๆŽฅๅ—ๆ“ไฝœๅœจๆ‰ง่กŒๆ—ถๅ‡บ้”™๏ผš ${0}" + onDeny: "ๆ‹’็ปๆ“ไฝœๅœจๆ‰ง่กŒๆ—ถๅ‡บ้”™๏ผš ${0}" + playerNotFound: "ๆ‰พไธๅˆฐ็Žฉๅฎถ '${0}'๏ผŒไป–ไปฌๆฒกๆœ‰ UUIDใ€‚" + playerNotInDatabase: "ๅœจๆ•ฐๆฎๅบ“ไธญๆ‰พไธๅˆฐ็Žฉๅฎถ '${0}'ใ€‚" + seeConfig: "ๆŸฅ็œ‹้…็ฝฎๆ–‡ไปถไธญ็š„ '${0}'" + serverNotFound: "ๅœจๆ•ฐๆฎๅบ“ไธญๆ‰พไธๅˆฐๆœๅŠกๅ™จ '${0}'ใ€‚" + tooManyArguments: "ยงc้œ€่ฆๅ•ไธชๅ‚ๆ•ฐ ${1}" + unknownUsername: "ยงcๅœจๆญคๆœๅŠกๅ™จไธŠๆœชๆ‰พๅˆฐ่ฏฅ็”จๆˆท" + webUserExists: "ยงc็”จๆˆทๅทฒๅญ˜ๅœจ๏ผ" + webUserNotFound: "ยงc็”จๆˆทไธๅญ˜ๅœจ๏ผ" + footer: + help: "ยง7ๅฐ†้ผ ๆ ‡ๆ‚ฌๅœๅœจๅ‚ๆ•ฐๆˆ–ๅ‘ฝไปคไธŠๆฅไบ†่งฃๆ›ดๅคšๆœ‰ๅ…ณๅฎƒไปฌ็š„ไฟกๆฏ๏ผŒๆˆ–่€…ไฝฟ็”จ '/${0} ?'ใ€‚" + general: + failNoExporter: "ยงeๅฏผๅ‡บๅ™จ '${0}' ไธๅญ˜ๅœจ" + failNoImporter: "ยงeๅฏผๅ…ฅๅ™จ '${0}' ไธๅญ˜ๅœจ" + featureDisabled: "ยงaๆš‚ๆ—ถ็ฆ็”จ '${0}' ็›ดๅˆฐไธ‹ไธ€ๆฌก้‡่ฝฝๆ’ไปถใ€‚" + noAddress: "ยงeๆฒกๆœ‰ๅฏ็”จ็š„ๅœฐๅ€ - ๅทฒไฝฟ็”จ localhost ไฝœไธบๅŽๅค‡ๅœฐๅ€ใ€‚ๅœจ้…็ฝฎๆ–‡ไปถไธญ็š„ 'Alternative_IP' ่ฎพ็ฝฎๅœฐๅ€ใ€‚" + noWebuser: "ไฝ ๅฏ่ƒฝๆฒกๆœ‰็ฝ‘้กต่ดฆๆˆท๏ผŒ่ฏทไฝฟ็”จ /plan register ๆฅๆณจๅ†Œ" + notifyWebUserRegister: "ๆ–ฐ็”จๆˆทๅทฒๆณจๅ†Œ๏ผš '${0}' ๆƒ้™็ญ‰็บง๏ผš ${1}" + pluginDisabled: "ยงa Plan ็ณป็ปŸ็Žฐๅœจๅทฒ่ขซ็ฆ็”จใ€‚ไฝ ไป็„ถๅฏไปฅไฝฟ็”จ reload ๆฅ้‡ๆ–ฐๅฏๅŠจๆ’ไปถใ€‚" + reloadComplete: "ยงa้‡่ฝฝๅฎŒๆˆ" + reloadFailed: "ยงc้‡ๆ–ฐๅŠ ่ฝฝๆ’ไปถๅ‡บไบ†็‚น้—ฎ้ข˜๏ผŒๅปบ่ฎฎ้‡ๆ–ฐๅฏๅŠจใ€‚" + successWebUserRegister: "ยงaๆˆๅŠŸๆทปๅŠ ไบ†ๆ–ฐ็”จๆˆท(${0})๏ผ" + webPermissionLevels: ">\ยง70: ่ฎฟ้—ฎๆ‰€ๆœ‰้กต้ข\ยง71: ่ฎฟ้—ฎ '/players' ๅ’Œๅ…จไฝ“็Žฉๅฎถ้กต้ข\ยง72: ่ฎฟ้—ฎ็”จๆˆทๅไธŽ็ฝ‘้กต็”จๆˆทๅไธ€่‡ด็š„็Žฉๅฎถ้กต\ยง73+: ๆฒกๆœ‰ๆƒ้™" + webUserList: " ยง2${0} ยง7: ยงf${1}" + header: + analysis: "> ยง2ๅˆ†ๆž็ป“ๆžœ" + help: "> ยง2/${0} ๅธฎๅŠฉ" + info: "> ยง2็Žฉๅฎถๅˆ†ๆž" + inspect: "> ยง2็Žฉๅฎถ: ยงf${0}" + network: "> ยง2็พค็ป„็ฝ‘็ปœ้กต้ข" + players: "> ยง2ๅ…จไฝ“็Žฉๅฎถ" + search: "> ยง2${0} ๅฏนไบŽ ยงf${1}ยง2 ็š„็ป“ๆžœ:" + serverList: "id::ๅ็งฐ::uuid::version" + servers: "> ยง2ๅ…จ้ƒจๆœๅŠกๅ™จ" + webUserList: "็”จๆˆทๅ::็ป‘ๅฎšๅˆฐ::ๆƒ้™็ญ‰็บง" + webUsers: "> ยง2${0} ็ฝ‘้กต็”จๆˆท" + help: + database: + description: "็ฎก็† Plan ๆ•ฐๆฎๅบ“" + inDepth: "ไฝฟ็”จไธๅŒ็š„ๆ•ฐๆฎๅบ“ๅญๅ‘ฝไปคๆฅๆŸ็งๆ–นๅผๆ›ดๆ”นๆ•ฐๆฎ" + dbBackup: + description: "ๅฐ†ๆ•ฐๆฎๅบ“็š„ๆ•ฐๆฎๅค‡ไปฝๅˆฐไธ€ไธชๆ–‡ไปถไธญ" + inDepth: "ไฝฟ็”จ SQLite ๅฐ†็›ฎๆ ‡ๆ•ฐๆฎๅบ“ๅค‡ไปฝๅˆฐๆ–‡ไปถไธญใ€‚" + dbClear: + description: "ไปŽๆ•ฐๆฎๅบ“ไธญๅˆ ้™คๆ‰€ๆœ‰ Plan ๆ•ฐๆฎ" + inDepth: "ๆธ…้™คๆ‰€ๆœ‰ Plan ๆ•ฐๆฎ่กจ๏ผŒๅนถๅˆ ้™คๆ‰€ๆœ‰ๅค„็†ไธญ็š„ Plan ๆ•ฐๆฎใ€‚" + dbHotswap: + description: "็ƒญไบคๆขๆ•ฐๆฎๅบ“ๅนถ้‡ๅฏๆ’ไปถ" + inDepth: "็”จๅฆไธ€ไธชๆ•ฐๆฎๅบ“้‡ๆ–ฐๅŠ ่ฝฝๆ’ไปถ๏ผŒๅนถๆ”นๅ˜้…็ฝฎไฝฟๅ…ถๅŒน้…ใ€‚" + dbMove: + description: "ๅœจๆ•ฐๆฎๅบ“้—ด็งปๅŠจๆ•ฐๆฎ" + inDepth: "็”จไธ€ไธชๆ•ฐๆฎๅบ“ไธญ็š„ๅ†…ๅฎน่ฆ†็›–ๅฆไธ€ไธชๆ•ฐๆฎๅบ“ไธญ็š„ๅ†…ๅฎนใ€‚" + dbRemove: + description: "ไปŽๅฝ“ๅ‰ๆ•ฐๆฎๅบ“ไธญๅˆ ้™ค็Žฉๅฎถ็š„ๆ•ฐๆฎ" + inDepth: "ไปŽๅฝ“ๅ‰ๆ•ฐๆฎๅบ“ไธญๅˆ ้™คไธŽๆŸไธช็Žฉๅฎถ็›ธๅ…ณ็š„ๆ‰€ๆœ‰ๆ•ฐๆฎใ€‚" + dbRestore: + description: "ๅฐ†ๆ•ฐๆฎไปŽๆ–‡ไปถๆขๅคๅˆฐๆ•ฐๆฎๅบ“" + inDepth: "ไฝฟ็”จ SQLite ๅค‡ไปฝๆ–‡ไปถๅนถ่ฆ†็›–็›ฎๆ ‡ๆ•ฐๆฎๅบ“็š„ๅ†…ๅฎนใ€‚" + dbUninstalled: + description: "ๅœจๆ•ฐๆฎๅบ“ไธญๆŠŠไธ€ไธชๆœๅŠกๅ™จ่ฎพ็ฝฎไธบๅทฒๅธ่ฝฝใ€‚" + inDepth: "ๅฐ† Plan ๆ•ฐๆฎๅบ“ไธญ็š„ไธ€ไธชๆœๅŠกๅ™จๆ ‡่ฎฐไธบๅทฒๅธ่ฝฝ๏ผŒ่ฟ™ๆ ทๅฎƒๅฐฑไธไผšๅœจๆœๅŠกๅ™จๆŸฅ่ฏข้กต้ขไธญๆ˜พ็คบๅ‡บๆฅใ€‚" + disable: + description: "็ฆ็”จๆ•ดไธชๆ’ไปถๆˆ–็ฆ็”จๆ’ไปถ็š„้ƒจๅˆ†ๅŠŸ่ƒฝ" + inDepth: "็ฆ็”จๆ•ดไธชๆ’ไปถๆˆ–็ฆ็”จๆ’ไปถ็š„้ƒจๅˆ†ๅŠŸ่ƒฝ๏ผŒ็›ดๅˆฐไธ‹ๆฌก้‡ๆ–ฐๅŠ ่ฝฝ/้‡ๆ–ฐๅฏๅŠจใ€‚" + export: + description: "ๆ‰‹ๅŠจๅฏผๅ‡บ html ๆˆ– json ๆ–‡ไปถ" + inDepth: "ๆŠŠๆ•ฐๆฎๅฏผๅ‡บๅˆฐ้…็ฝฎๆ–‡ไปถไธญๆŒ‡ๅฎš็š„ๅฏผๅ‡บไฝ็ฝฎใ€‚" + import: + description: "ๅฏผๅ…ฅๆ•ฐๆฎ" + inDepth: "ๆ‰ง่กŒๅฏผๅ…ฅ๏ผŒๅฐ†ๆ•ฐๆฎๅŠ ่ฝฝๅˆฐๆ•ฐๆฎๅบ“ใ€‚" + info: + description: "ๅ…ณไบŽๆญคๆ’ไปถ็š„ไฟกๆฏ" + inDepth: "ๆ˜พ็คบๆ’ไปถ็š„ๅฝ“ๅ‰็Šถๆ€ใ€‚" + ingame: + description: "ๅœจๆธธๆˆไธญๆŸฅ็œ‹็Žฉๅฎถไฟกๆฏ" + inDepth: "ๆ˜พ็คบๆญฃๅœจๆธธๆˆไธญ็š„็Žฉๅฎถ็š„ไธ€ไบ›ไฟกๆฏใ€‚" + json: + description: "ๆŸฅ็œ‹็Žฉๅฎถ็š„ๅŽŸๅง‹ๆ•ฐๆฎ jsonใ€‚" + inDepth: "ๅ…่ฎธไฝ ไธ‹่ฝฝ json ๆ ผๅผ็š„็Žฉๅฎถๆ•ฐๆฎใ€‚ๆ‰€ๆœ‰็š„ๆ•ฐๆฎ้ƒฝๅœจ้‡Œ้ขใ€‚" + logout: + description: "ๅฐ†ๅ…ถไป–็”จๆˆทไปŽ้ขๆฟไธŠ็™ปๅ‡บใ€‚" + inDepth: "่พ“ๅ…ฅ็”จๆˆทๅไฝœไธบๅ‚ๆ•ฐๅฏไปฅๆณจ้”€ Plan ไธŠ็š„ไธ€ไธช็”จๆˆท๏ผŒ่พ“ๅ…ฅ * ไฝœไธบๅ‚ๆ•ฐๅฏไปฅๆณจ้”€ๆ‰€ๆœ‰็”จๆˆทใ€‚" + migrateToOnlineUuids: + description: "Migrate offline uuid data to online uuids" + network: + description: "ๆŸฅ็œ‹็พค็ป„็ฝ‘็ปœ้กต้ข" + inDepth: "่Žทๅพ—ไธ€ไธชๆŒ‡ๅ‘ /network page๏ผˆ็พค็ป„็ฝ‘็ปœ๏ผ‰ ็š„้“พๆŽฅ๏ผŒๅช่ƒฝๅœจ็พค็ป„็ฝ‘็ปœไธŠ่ฟ™ๆ ทๅšใ€‚" + player: + description: "ๆŸฅ็œ‹็Žฉๅฎถ้กต้ข" + inDepth: "่Žทๅพ—ไธ€ไธชๆŒ‡ๅ‘็‰นๅฎš็Žฉๅฎถๆˆ–ๅฝ“ๅ‰็Žฉๅฎถ็š„ /player page๏ผˆ็Žฉๅฎถ้กต้ข๏ผ‰ ็š„้“พๆŽฅใ€‚" + players: + description: "ๆŸฅ็œ‹ๅ…จไฝ“็Žฉๅฎถ้กต้ข" + inDepth: "่Žทๅพ—ไธ€ไธชๆŒ‡ๅ‘ /players page๏ผˆๅ…จไฝ“็Žฉๅฎถ้กต้ข๏ผ‰ ็š„้“พๆŽฅ๏ผŒไปฅๆŸฅ็œ‹็Žฉๅฎถๅˆ—่กจใ€‚" + register: + description: "ๆณจๅ†Œไธ€ไธช็ฝ‘้กต็”จๆˆท" + inDepth: "็›ดๆŽฅไฝฟ็”จไผš่Žทๅพ—ๆณจๅ†Œ้กต้ข็š„้“พๆŽฅใ€‚ๆทปๅŠ  --code[ๆณจๅ†Œไปฃ็ ] ๅ‚ๆ•ฐๅฏไปฅๆณจๅ†Œไธ€ไธช่ดฆๆˆทใ€‚" + reload: + description: "้‡ๅฏ Plan" + inDepth: "็ฆ็”จ็„ถๅŽ้‡ๆ–ฐๅฏ็”จๆœฌๆ’ไปถ๏ผŒไผš้‡ๆ–ฐๅŠ ่ฝฝ้…็ฝฎไธญ็š„่ฎพ็ฝฎใ€‚" + removejoinaddresses: + description: "Remove join addresses of a specified server" + search: + description: "ๆœ็ดข็Žฉๅฎถ" + inDepth: "ๅˆ—ๅ‡บๆ‰€ๆœ‰ไธŽ็ป™ๅฎšๅๅญ—้ƒจๅˆ†็›ธๅŒน้…็š„็Žฉๅฎถๅๅญ—ใ€‚" + server: + description: "ๆŸฅ็œ‹ๆœๅŠกๅ™จ้กต้ข" + inDepth: "่Žทๅ–ไธ€ไธชๆŒ‡ๅ‘็‰นๅฎšๆœๅŠกๅ™จ็š„ /server page๏ผˆๆœๅŠกๅ™จ้กต้ข๏ผ‰ ็š„้“พๆŽฅ๏ผŒๅฆ‚ๆžœๆฒกๆœ‰็ป™ๅ‡บๅ‚ๆ•ฐ๏ผŒๅˆ™่Žทๅ–ๅฝ“ๅ‰ๆœๅŠกๅ™จ็š„้“พๆŽฅใ€‚" + servers: + description: "ๅˆ—ๅ‡บๆ•ฐๆฎๅบ“ไธญ็š„ๆœๅŠกๅ™จ" + inDepth: "ๅˆ—ๅ‡บๆ•ฐๆฎๅบ“ไธญๆ‰€ๆœ‰ๆœๅŠกๅ™จ็š„IDใ€ๅ็งฐๅ’ŒUUIDใ€‚" + unregister: + description: "ๆณจ้”€ไธ€ไธช Plan ็ฝ‘้กต่ดฆๆˆท" + inDepth: "ไธๅซๅ‚ๆ•ฐไฝฟ็”จไผšๆณจ้”€ๅฝ“ๅ‰็ป‘ๅฎš็š„่ดฆๆˆท๏ผŒไฝฟ็”จ็”จๆˆทๅไฝœไธบๅ‚ๆ•ฐ่ƒฝๆณจ้”€ๅฆไธ€ไธช็”จๆˆทใ€‚" + users: + description: "ๅˆ—ๅ‡บๆ‰€ๆœ‰็ฝ‘้กต่ดฆๆˆท" + inDepth: "ไปฅ่กจๆ ผๅฝขๅผๅˆ—ๅ‡บ็ฝ‘้กต็”จๆˆทใ€‚" + ingame: + activePlaytime: " ยง2ๆดป่ทƒๆ—ถ้—ด๏ผšยงf${0}" + activityIndex: " ยง2ๆดป่ทƒๆŒ‡ๆ•ฐ๏ผšยงf${0} | ${1}" + afkPlaytime: " ยง2ๆŒ‚ๆœบๆ—ถ้—ด๏ผšยงf${0}" + deaths: " ยง2ๆญปไบกๆ•ฐ๏ผšยงf${0}" + geolocation: " ยง2็™ปๅฝ•ไฝ็ฝฎ๏ผšยงf${0}" + lastSeen: " ยง2ไธŠๆฌกๅœจ็บฟ๏ผšยงf${0}" + longestSession: " ยง2ๆœ€้•ฟ็š„ไธ€ๆฌกๆธธ็Žฉ๏ผšยงf${0}" + mobKills: " ยง2็”Ÿ็‰ฉๅ‡ปๆ€ๆ•ฐ๏ผšยงf${0}" + playerKills: " ยง2็Žฉๅฎถๅ‡ปๆ€ๆ•ฐ๏ผšยงf${0}" + playtime: " ยง2ๆธธ็Žฉๆ—ถ้—ด๏ผšยงf${0}" + registered: " ยง2ๆณจๅ†Œๆ—ถ้—ด๏ผšยงf${0}" + timesKicked: " ยง2่ขซ่ธขๆฌกๆ•ฐ๏ผšยงf${0}" + link: + clickMe: "็‚นๅ‡ปๆญคๅค„" + link: "้“พๆŽฅ" + network: "็พค็ป„็ฝ‘็ปœ้กต้ข: " + noNetwork: "ๆœๅŠกๅ™จๆœช่ฟžๆŽฅๅˆฐ็พค็ป„ใ€‚ๆญค้“พๆŽฅๅทฒ้‡ๅฎšๅ‘ๅˆฐๆœๅŠกๅ™จ้กต้ขใ€‚" + player: "็Žฉๅฎถไธชไบบ้กต้ข: " + playerJson: "็Žฉๅฎถ json: " + players: "ๅ…จไฝ“็Žฉๅฎถ้กต้ข: " + register: "ๆณจๅ†Œ้กต้ข page: " + server: "ๆœๅŠกๅ™จ้กต้ข page: " + subcommand: + info: + database: " ยง2ๅฝ“ๅ‰ๆ•ฐๆฎๅบ“๏ผšยงf${0}" + proxy: " ยง2่ฟžๆŽฅ่‡ณไปฃ็†๏ผšยงf${0}" + update: " ยง2ๆœ‰ๅฏ็”จๆ›ดๆ–ฐ๏ผšยงf${0}" + version: " ยง2็‰ˆๆœฌ๏ผšยงf${0}" +generic: + noData: "ๆ— ๆ•ฐๆฎ" +html: + button: + nightMode: "ๅคœ้—ดๆจกๅผ" + calendar: + new: "ๆ–ฐ๏ผš" + unique: "็‹ฌ็ซ‹๏ผš" + description: + newPlayerRetention: "่ฟ™ไธชๆ•ฐๅ€ผๆ˜ฏๅŸบไบŽไน‹ๅ‰็š„็Žฉๅฎถๆ•ฐๆฎ้ข„ๆต‹็š„ใ€‚" + noGameServers: "่ฆ่Žทๅ–ๆŸไบ›ๆ•ฐๆฎ๏ผŒไฝ ้œ€่ฆๅฐ† Plan ๅฎ‰่ฃ…ๅœจๆธธๆˆๆœๅŠกๅ™จไธŠใ€‚" + noGeolocations: "้œ€่ฆๅœจ้…็ฝฎๆ–‡ไปถไธญๅฏ็”จๅœฐ็†ไฝ็ฝฎๆ”ถ้›†(Accept GeoLite2 EULA)ใ€‚" + noServerOnlinActivity: "ๆฒกๆœ‰ๅฏๆ˜พ็คบๅœจ็บฟๆดปๅŠจ็š„ๆœๅŠกๅ™จ" + noServers: "ๆ•ฐๆฎๅบ“ไธญๆ‰พไธๅˆฐๆœๅŠกๅ™จ" + noServersLong: '็œ‹่ตทๆฅ Plan ๆฒกๆœ‰ๅฎ‰่ฃ…ๅœจไปปไฝ•ๆธธๆˆๆœๅŠกๅ™จไธŠๆˆ–่€…ๆธธๆˆๆœๅŠกๅ™จๆœช่ฟžๆŽฅๅˆฐ็›ธๅŒ็š„ๆ•ฐๆฎๅบ“ใ€‚ ็พค็ป„็ฝ‘็ปœๆ•™็จ‹่ฏทๅ‚่ง๏ผšwiki' + noSpongeChunks: "ๅŒบๅ—ๆ•ฐๆฎๅœจ Sponge ๆœๅŠก็ซฏไธๅฏ็”จ" + predictedNewPlayerRetention: "่ฟ™ไธชๆ•ฐๅ€ผๆ˜ฏๅŸบไบŽไน‹ๅ‰็š„็Žฉๅฎถๆ•ฐๆฎ้ข„ๆต‹็š„" + error: + 401Unauthorized: "ๆœช่ฎค่ฏ" + 403Forbidden: "็ฆๆญข่ฎฟ้—ฎ" + 404NotFound: "ๆœชๆ‰พๅˆฐ" + 404PageNotFound: "้กต้ขไธๅญ˜ๅœจใ€‚" + 404UnknownPage: "่ฏท็กฎไฟๆ‚จๆญฃ้€š่ฟ‡ๅ‘ฝไปคๆ‰€็ป™ๅ‡บ็š„้“พๆŽฅ่ฎฟ้—ฎ๏ผŒ็คบไพ‹๏ผš

/player/็Žฉๅฎถๅ
/server/ๆœๅŠกๅ™จๅ

" + UUIDNotFound: "ๆœชๅœจๆ•ฐๆฎๅบ“ไธญๆ‰พๅˆฐๆญค็Žฉๅฎถ็š„ UUIDใ€‚" + auth: + dbClosed: "ๆ•ฐๆฎๅบ“ๆœชๅผ€ๆ”พ, ไฝฟ็”จ /plan info ๆŸฅ็œ‹ๆ•ฐๆฎๅบ“็Šถๆ€" + emptyForm: "ๆœชๆŒ‡ๅฎš็”จๆˆทๅไธŽๅฏ†็ " + expiredCookie: "็”จๆˆท Cookie ๅทฒ่ฟ‡ๆœŸ" + generic: "่ฎค่ฏๆ—ถๅ‘็”Ÿ้”™่ฏฏ" + loginFailed: "็”จๆˆทๅๅ’Œๅฏ†็ ไธๅŒน้…" + noCookie: "็”จๆˆท cookie ไธๅญ˜ๅœจ" + registrationFailed: "ๆณจๅ†Œๅคฑ่ดฅ๏ผŒ่ฏท้‡่ฏ•๏ผˆๆณจๅ†Œไปฃ็ ๆœ‰ๆ•ˆๆœŸ 15 ๅˆ†้’Ÿ๏ผ‰" + userNotFound: "็”จๆˆทๅไธๅญ˜ๅœจ" + authFailed: "่ฎค่ฏๅคฑ่ดฅใ€‚" + authFailedTips: "- ็กฎไฟไฝ ๅทฒไฝฟ็”จ /plan register ๆฅๆณจๅ†Œ็”จๆˆท
- ๆฃ€ๆŸฅ็”จๆˆทๅไธŽๅฏ†็ ๆ˜ฏๅฆๆญฃ็กฎ
- ็”จๆˆทๅไธŽๅฏ†็ ๅŒบๅˆ†ๅคงๅฐๅ†™

่‹ฅๆ‚จๅฟ˜่ฎฐไบ†ๅฏ†็ ๏ผŒ่ฏท่ฎฉๅทฅไฝœไบบๅ‘˜ๅˆ ้™คๆ‚จ็š„ๆ—งๅฏ†็ ๅนถ้‡ๆ–ฐๆณจๅ†Œใ€‚" + noServersOnline: "ๆ— ๅฏๆ‰ง่กŒๆญค่ฏทๆฑ‚็š„ๅœจ็บฟๆœๅŠกๅ™จใ€‚" + playerNotSeen: "Plan ๆฒกๆœ‰ๆ‰พๅˆฐๆญค็Žฉๅฎถใ€‚" + serverNotSeen: "Server doesn't exist" + generic: + none: "ๆ— " + label: + active: "ๆดป่ทƒ" + activePlaytime: "ๆดป่ทƒๆ—ถ้—ด" + activityIndex: "ๆดป่ทƒๆŒ‡ๆ•ฐ" + afk: "ๆŒ‚ๆœบ" + afkTime: "ๆŒ‚ๆœบๆ—ถ้—ด" + all: "ๅ…จ้ƒจ" + allTime: "ๆ‰€ๆœ‰ๆ—ถ้—ด" + alphabetical: "Alphabetical" + apply: "Apply" + asNumbers: "ๆ•ฐๆฎ" + average: "ๅนณๅ‡" + averageActivePlaytime: "ๅนณๅ‡ๆดป่ทƒๆ—ถ้—ด" + averageAfkTime: "ๅนณๅ‡ๆŒ‚ๆœบๆ—ถ้—ด" + averageChunks: "ๅนณๅ‡ๅŒบๅ—ๆ•ฐ" + averageCpuUsage: "Average CPU Usage" + averageEntities: "ๅนณๅ‡ๅฎžไฝ“ๆ•ฐ" + averageKdr: "ๅนณๅ‡ KDR" + averageMobKdr: "ๅนณๅ‡็”Ÿ็‰ฉ KDR" + averagePing: "ๅนณๅ‡ๅปถ่ฟŸ" + averagePlayers: "Average Players" + averagePlaytime: "ๅนณๅ‡ๆธธ็Žฉๆ—ถ้—ด" + averageRamUsage: "Average RAM Usage" + averageServerDowntime: "Average Downtime / Server" + averageSessionLength: "ๅนณๅ‡ไผš่ฏๆ—ถ้•ฟ" + averageSessions: "ๅนณๅ‡ไผš่ฏ" + averageTps: "ๅนณๅ‡ TPS" + averageTps7days: "Average TPS (7 days)" + banned: "ๅทฒ่ขซๅฐ็ฆ" + bestPeak: "ๆ‰€ๆœ‰ๆ—ถ้—ดๅณฐๅ€ผ" + bestPing: "ๆœ€ไฝŽๅปถ่ฟŸ" + calendar: " ๆ—ฅๅŽ†" + comparing7days: "ๅฏนๆฏ” 7 ๅคฉ็š„ๆƒ…ๅ†ต" + connectionInfo: "่ฟžๆŽฅไฟกๆฏ" + country: "ๅ›ฝๅฎถๅ’ŒๅœฐๅŒบ" + cpu: "CPU" + cpuRam: "CPU ๅ’Œๅ†…ๅญ˜" + cpuUsage: "CPU Usage" + currentPlayerbase: "ๅฝ“ๅ‰็Žฉๅฎถๆ•ฐ" + currentUptime: "Current Uptime" + dayByDay: "ๆŒ‰ๅคฉๆŸฅ็œ‹" + dayOfweek: "ๆ˜ŸๆœŸ" + deadliestWeapon: "ๆœ€่‡ดๅ‘ฝ็š„ PVP ๆญฆๅ™จ" + deaths: "ๆญปไบกๆ•ฐ" + disk: "็กฌ็›˜็ฉบ้—ด" + diskSpace: "ๅ‰ฉไฝ™็กฌ็›˜็ฉบ้—ด" + downtime: "ๅœๆœบๆ—ถ้—ด" + duringLowTps: "ๆŒ็ปญไฝŽ TPS ๆ—ถ้—ด" + entities: "ๅฎžไฝ“" + favoriteServer: "ๆœ€ๅ–œ็ˆฑ็š„ๆœๅŠกๅ™จ" + firstSession: "็ฌฌไธ€ๆญคไผš่ฏ" + firstSessionLength: + average: "Average first session length" + median: "Median first session length" + geoProjection: + dropdown: "Select projection" + equalEarth: "Equal Earth" + mercator: "Mercator" + miller: "Miller" + ortographic: "Ortographic" + geolocations: "ๅœฐ็†ไฝ็ฝฎ" + hourByHour: "ๆŒ‰ๅฐๆ—ถๆŸฅ็œ‹" + inactive: "ไธๆดป่ทƒ" + indexInactive: "ไธๆดป่ทƒ" + indexRegular: "็ปๅธธไธŠ็บฟ" + information: "ไฟกๆฏ" + insights: "Insights" + insights30days: "30 ๅคฉๅˆ†ๆž" + irregular: "ๅถๅฐ”ไธŠ็บฟ" + joinAddress: "Join Address" + joinAddresses: "ๅŠ ๅ…ฅๅœฐๅ€" + kdr: "KDR" + killed: "่ขซๅ‡ปๆ€ๆ•ฐ" + last24hours: "่ฟ‡ๅŽป 24 ๅฐๆ—ถ" + last30days: "่ฟ‡ๅŽป 30 ๅคฉ" + last7days: "่ฟ‡ๅŽป 7 ๅคฉ" + lastConnected: "ๆœ€ๅŽ่ฟžๆŽฅๆ—ถ้—ด" + lastPeak: "ไธŠๆฌกๅœจ็บฟๅณฐๅ€ผ" + lastSeen: "ๆœ€ๅŽๅœจ็บฟๆ—ถ้—ด" + latestJoinAddresses: "Latest Join Addresses" + length: " ๆธธ็Žฉๆ—ถ้•ฟ" + links: "้“พๆŽฅ" + loadedChunks: "ๅทฒๅŠ ่ฝฝๅŒบๅ—" + loadedEntities: "ๅทฒๅŠ ่ฝฝๅฎžไฝ“" + loneJoins: "ๅ•็‹ฌๅŠ ๅ…ฅ" + loneNewbieJoins: "ๅ•็‹ฌๆ–ฐ็ŽฉๅฎถๅŠ ๅ…ฅ" + longestSession: "ๆœ€้•ฟไผš่ฏๆ—ถ้—ด" + lowTpsSpikes: "ไฝŽ TPS ๆ—ถ้—ด" + lowTpsSpikes7days: "Low TPS Spikes (7 days)" + maxFreeDisk: "ๆœ€ๅคงๅฏ็”จ็กฌ็›˜็ฉบ้—ด" + medianSessionLength: "Median Session Length" + minFreeDisk: "ๆœ€ๅฐๅฏ็”จ็กฌ็›˜็ฉบ้—ด" + mobDeaths: "่ขซ็”Ÿ็‰ฉๅ‡ปๆ€ๆ•ฐ" + mobKdr: "็”Ÿ็‰ฉ KDR" + mobKills: "็”Ÿ็‰ฉๅ‡ปๆ€ๆ•ฐ" + mostActiveGamemode: "ๆœ€ๅธธ็Žฉ็š„ๆธธๆˆๆจกๅผ" + mostPlayedWorld: "็Žฉ็š„ๆœ€ๅคš็š„ไธ–็•Œ" + name: "ๅ็งฐ" + network: "็พค็ป„็ฝ‘็ปœ" + networkAsNumbers: "็พค็ป„็ฝ‘็ปœๆ•ฐๆฎ" + networkOnlineActivity: "็พค็ป„็ฝ‘็ปœๅœจ็บฟๆดปๅŠจ" + networkOverview: "็พค็ป„็ฝ‘็ปœๆ€ป่งˆ" + networkPage: "็พค็ป„็ฝ‘็ปœ้กต้ข" + new: "ๆ–ฐ" + newPlayerRetention: "ๆ–ฐ็Žฉๅฎถ็•™ๅ‘็Ž‡" + newPlayers: "ๆ–ฐ็Žฉๅฎถ" + newPlayers7days: "New Players (7 days)" + nickname: "ๆ˜ต็งฐ" + noDataToDisplay: "No Data to Display" + now: "็Žฐๅœจ" + onlineActivity: "ๅœจ็บฟๆดปๅŠจ" + onlineActivityAsNumbers: "ๅœจ็บฟๆดปๅŠจๆ•ฐๆฎ" + onlineOnFirstJoin: "็ฌฌไธ€ๆฌก่ฟ›ๅ…ฅๆœๅŠกๅ™จ็š„ๅœจ็บฟ็Žฉๅฎถ" + operator: "็ฎก็†ๅ‘˜" + overview: "ๆ€ป่งˆ" + perDay: "/ ๅคฉ" + perPlayer: "/ ็Žฉๅฎถ" + perRegularPlayer: "/ ๆ™ฎ้€š็Žฉๅฎถ" + performance: "ๆ€ง่ƒฝ" + performanceAsNumbers: "ๆ€ง่ƒฝๆ•ฐๆฎ" + ping: "ๅปถ่ฟŸ" + player: "็Žฉๅฎถ" + playerDeaths: "่ขซ็Žฉๅฎถๅ‡ปๆ€ๆฌกๆ•ฐ" + playerKills: "ๅ‡ปๆ€็Žฉๅฎถๆ•ฐ" + playerList: "็Žฉๅฎถๅˆ—่กจ" + playerOverview: "็Žฉๅฎถๆ€ป่งˆ" + playerPage: "็Žฉๅฎถ้กต้ข" + playerRetention: "Player Retention" + playerbase: "็Žฉๅฎถๆ•ฐๆฎ" + playerbaseDevelopment: "็Žฉๅฎถๅ‘ๅฑ•" + playerbaseOverview: "Playerbase Overview" + players: "็Žฉๅฎถ" + playersOnline: "ๅœจ็บฟ็Žฉๅฎถ" + playersOnlineNow: "Players Online (Now)" + playersOnlineOverview: "ๅœจ็บฟๆดปๅŠจๆ€ป่งˆ" + playtime: "ๆธธ็Žฉๆ—ถ้—ด" + plugins: "ๆ’ไปถ" + pluginsOverview: "Plugins Overview" + punchcard: "ๆ‰“ๅก" + punchcard30days: "30 ๅคฉๆ‰“ๅก" + pvpPve: "PvP ๅ’Œ PvE" + pvpPveAsNumbers: "PvP ๅ’Œ PvE ๆ•ฐๆฎ" + query: "่ฟ›่กŒๆŸฅ่ฏข" + quickView: "ๅฟซ้€Ÿๆต่งˆ" + ram: "RAM" + ramUsage: "RAM Usage" + recentKills: "ๆœ€่ฟ‘ๅ‡ปๆ€" + recentPvpDeaths: "ๆœ€่ฟ‘็š„ PVP ๆญปไบก" + recentPvpKills: "ๆœ€่ฟ‘็š„ PVP ๅ‡ปๆ€" + recentSessions: "ๆœ€่ฟ‘ไผš่ฏ" + registered: "ๆณจๅ†Œๆ—ถ้—ด" + registeredPlayers: "ๅทฒๆณจๅ†Œ็š„็Žฉๅฎถ" + regular: "ๆ™ฎ้€š" + regularPlayers: "ๆ™ฎ้€š็Žฉๅฎถ" + relativeJoinActivity: "ๆœ€่ฟ‘ๅŠ ๅ…ฅๆดปๅŠจ" + secondDeadliestWeapon: "็ฌฌไบŒ่‡ดๅ‘ฝ็š„ PVP ๆญฆๅ™จ" + seenNicknames: "็”จ่ฟ‡็š„ๆ˜ต็งฐ" + server: "ๆœๅŠกๅ™จ" + serverAnalysis: "ๆœๅŠกๅ™จๅˆ†ๆž" + serverAsNumberse: "ๆœๅŠกๅ™จๆ•ฐๆฎ" + serverCalendar: "Server Calendar" + serverDowntime: "ๆœๅŠกๅ™จๅœๆœบๆ—ถ้—ด" + serverOccupied: "ๆœๅŠกๅ™จๅœจ็บฟๆ—ถ้—ด" + serverOverview: "ๆœๅŠกๅ™จๆ€ป่งˆ" + serverPage: "ๆœๅŠกๅ™จ้กต้ข" + serverPlaytime: "ๆœๅŠกๅ™จๆธธๆˆๆ—ถ้—ด" + serverPlaytime30days: "ๆœ€่ฟ‘ 30 ๅคฉๅ†…็š„ๆœๅŠกๅ™จๆธธ็Žฉๆ—ถ้—ด" + serverSelector: "Server selector" + servers: "ๆœๅŠกๅ™จ" + serversTitle: "ๆœๅŠกๅ™จ" + session: "ไผš่ฏๆฌกๆ•ฐ" + sessionCalendar: "Session Calendar" + sessionEnded: " ไผš่ฏ็ป“ๆŸ" + sessionMedian: "ๅนณๅ‡ไผš่ฏ้•ฟๅบฆ" + sessionStart: "ไผš่ฏๅผ€ๅง‹ไบŽ" + sessions: "ไผš่ฏ" + sortBy: "Sort By" + stacked: "Stacked" + themeSelect: "ไธป้ข˜้€‰ๆ‹ฉ" + thirdDeadliestWeapon: "็ฌฌไธ‰่‡ดๅ‘ฝ็š„ PVP ๆญฆๅ™จ" + thirtyDays: "30 ๅคฉ" + thirtyDaysAgo: "30 ๅคฉๅ‰" + timesKicked: "่ขซ่ธขๅ‡บๆฌกๆ•ฐ" + toMainPage: "ๅ›žๅˆฐไธป้กต้ข" + total: "Total" + totalActive: "ๆ€ปๆดป่ทƒๆ—ถ้•ฟ" + totalAfk: "ๆ€ปๆŒ‚ๆœบๆ—ถ้•ฟ" + totalPlayers: "ๆ€ป็Žฉๅฎถๆ•ฐ" + totalPlayersOld: "ๆ€ปๆธธ็Žฉๆ—ถ้•ฟ" + totalPlaytime: "ๆ€ปๆธธ็Žฉๆ—ถ้—ด" + totalServerDowntime: "Total Server Downtime" + tps: "TPS" + trend: "่ถ‹ๅŠฟ" + trends30days: "30 ๅคฉ่ถ‹ๅŠฟ" + uniquePlayers: "็‹ฌ็ซ‹็Žฉๅฎถ" + uniquePlayers7days: "Unique Players (7 days)" + veryActive: "้žๅธธๆดป่ทƒ" + weekComparison: "ๆฏๅ‘จๅฏนๆฏ”" + weekdays: "'ๆ˜ŸๆœŸไธ€', 'ๆ˜ŸๆœŸไบŒ', 'ๆ˜ŸๆœŸไธ‰', 'ๆ˜ŸๆœŸๅ››', 'ๆ˜ŸๆœŸไบ”', 'ๆ˜ŸๆœŸๅ…ญ', 'ๆ˜ŸๆœŸๆ—ฅ'" + world: "ไธ–็•ŒๅŠ ่ฝฝ" + worldPlaytime: "ไธ–็•Œๆธธ็Žฉๆ—ถ้—ด" + worstPing: "ๆœ€้ซ˜ๅปถ่ฟŸ" + login: + failed: "็™ปๅฝ•ๅคฑ่ดฅ๏ผš" + forgotPassword: "ๅฟ˜่ฎฐๅฏ†็ ๏ผŸ" + forgotPassword1: "ๅฟ˜่ฎฐๅฏ†็ ๏ผŸ ๆณจ้”€ๅนถๅ†ๆฌกๆณจๅ†Œใ€‚" + forgotPassword2: "ๅœจๆธธๆˆไธญไฝฟ็”จไปฅไธ‹ๅ‘ฝไปคๆฅๅˆ ้™คๅฝ“ๅ‰่ดฆๆˆท๏ผš" + forgotPassword3: "ๆˆ–ไฝฟ็”จๆŽงๅˆถๅฐๅ‘ฝไปค๏ผš" + forgotPassword4: "ไฝฟ็”จๅ‘ฝไปคๅŽ๏ผŒ" + login: "็™ปๅฝ•" + logout: "็™ปๅ‡บ" + password: "ๅฏ†็ " + register: "ๅˆ›ๅปบไธ€ไธช่ดฆๆˆท๏ผ" + username: "็”จๆˆทๅ" + modal: + info: + bugs: "ๆŠฅๅ‘Š้—ฎ้ข˜" + contributors: + bugreporters: "ๅ’Œๅ…ถไป–้—ฎ้ข˜ๆŠฅๅ‘Š่€…๏ผ" + code: "ไปฃ็ ่ดก็Œฎ่€…" + donate: "็‰นๅˆซๆ„Ÿ่ฐข้‚ฃไบ›ๅœจ็ปๆตŽไธŠๆ”ฏๆŒๅผ€ๅ‘็š„ไบบไปฌใ€‚" + text: 'ไปฅไธ‹ ไผ˜็ง€ไบบ็‰ฉ ไนŸๅšๅ‡บไบ†่ดก็Œฎ๏ผš' + translator: "็ฟป่ฏ‘่€…" + developer: "็š„ๅผ€ๅ‘่€…ๆ˜ฏ" + discord: "ไธ€่ˆฌ้—ฎ้ข˜ๆ”ฏๆŒ๏ผšDiscord" + license: "Player Analytics ๅผ€ๅ‘ๅ’ŒๆŽˆๆƒไบŽ " + metrics: "bStats ็ปŸ่ฎก" + text: "ๆ’ไปถไฟกๆฏ" + wiki: "Plan Wiki,ๆ•™็จ‹ๅ’Œๆ–‡ๆกฃ" + version: + available: "ๅฏ็”จ" + changelog: "ๆŸฅ็œ‹ๆ›ดๆ–ฐๆ—ฅๅฟ—" + dev: "่ฟ™ๆ˜ฏไธ€ไธชๅผ€ๅ‘็‰ˆๆœฌใ€‚" + download: "ไธ‹่ฝฝ" + text: "ๆœ‰ๆ–ฐ็‰ˆๆœฌๅฏไพ›ไธ‹่ฝฝใ€‚" + title: "็‰ˆๆœฌ" + query: + filter: + activity: + text: "ๅœจๆดป่ทƒๅบฆๅˆ†็ป„ไธญ" + banStatus: + name: "ๅฐ็ฆ็Šถๆ€" + banned: "่ขซๅฐ็ฆ" + country: + text: "have joined from country" + generic: + allPlayers: "ๅ…จไฝ“็Žฉๅฎถ" + and: "ๅค–ๅŠ  " + start: "ๆŸฅ่ฏข็Žฉๅฎถ๏ผš" + joinAddress: + text: "ๅŠ ๅ…ฅๅœฐๅ€" + nonOperators: "้ž็ฎก็†ๅ‘˜" + notBanned: "ๆœช่ขซๅฐ็ฆ" + operatorStatus: + name: "็ฎก็†ๅ‘˜็Šถๆ€" + operators: "็ฎก็†ๅ‘˜" + playedBetween: + text: "ๅœจๆญคๆœŸ้—ดๆธธ็Žฉ่ฟ‡" + pluginGroup: + name: "ๅฐ็ป„๏ผš" + text: "ๅœจ ${plugin} ๆ’ไปถ็š„ ${group} ๅˆ†็ป„ไธญ" + registeredBetween: + text: "ๅœจๆญคๆœŸ้—ดๆณจๅ†Œ" + title: + activityGroup: "ๆดป่ทƒๅบฆๅˆ†็ป„" + view: " ๆ—ฅๆœŸ่Œƒๅ›ด:" + filters: + add: "ๆทปๅŠ ่ฟ‡ๆปคๅ™จ.." + loading: "ๅŠ ่ฝฝ่ฟ‡ๆปคๅ™จไธญ..." + generic: + are: "`ๆ˜ฏ`" + label: + from: ">ไปŽ " + makeAnother: "่ฟ›่กŒๅฆไธ€ไธชๆŸฅ่ฏข" + to: ">ๅˆฐ " + view: "ๆ—ฅๆœŸ่Œƒๅ›ด" + performQuery: "ๆ‰ง่กŒๆŸฅ่ฏข๏ผ" + results: + match: "ๅŒน้…ๅˆฐ ${resultCount} ไธช็Žฉๅฎถ" + none: "ๆŸฅ่ฏขๅˆฐ 0 ไธช็ป“ๆžœ" + title: "ๆŸฅ่ฏข็ป“ๆžœ" + title: + activity: "ๅŒน้…็Žฉๅฎถ็š„ๆดป่ทƒๅบฆ" + activityOnDate: 'ๆดป่ทƒๅœจ ' + sessionsWithinView: "ๆŸฅ็œ‹่Œƒๅ›ดๅ†…็š„ไผš่ฏ" + text: "ๆŸฅ่ฏข<" + register: + completion: "ๆณจๅ†ŒๅฎŒๆˆ" + completion1: "ๆ‚จ็ŽฐๅœจๅฏไปฅๅฎŒๆˆ็”จๆˆทๆณจๅ†Œๆต็จ‹ใ€‚" + completion2: "ๆณจๅ†Œไปฃ็ ๅฐ†ๅœจ 15 ๅˆ†้’ŸๅŽ่ฟ‡ๆœŸ" + completion3: "ๅœจๆธธๆˆไธญไฝฟ็”จไปฅไธ‹ๅ‘ฝไปคๅฎŒๆˆๆณจๅ†Œ๏ผš" + completion4: "ๆˆ–ไฝฟ็”จๆŽงๅˆถๅฐ๏ผš" + createNewUser: "ๅˆ›ๅปบไธ€ไธชๆ–ฐ็”จๆˆท" + error: + checkFailed: "ๆฃ€ๆŸฅๆณจๅ†Œ็Šถๆ€ๅคฑ่ดฅ๏ผš" + failed: "ๆณจๅ†Œๅคฑ่ดฅ๏ผš" + noPassword: "ไฝ ้œ€่ฆๅกซๅ†™ๅฏ†็ " + noUsername: "ไฝ ้œ€่ฆๅกซๅ†™็”จๆˆทๅ" + usernameLength: "็”จๆˆทๅๆœ€ๅคšๅฏไปฅๅŒ…ๅซ 50 ไธชๅญ—็ฌฆ๏ผŒไฝ ็š„็”จๆˆทๅๆœ‰" + login: "ๅทฒ็ปๆœ‰ๅธๅทไบ†๏ผŸ ็™ปๅฝ•๏ผ" + passwordTip: "ๅฏ†็ ไธ่ƒฝ่ถ…่ฟ‡8ไธชๅญ—็ฌฆ๏ผŒๆฒกๆœ‰ๅ…ถไป–้™ๅˆถใ€‚" + register: "ๆณจๅ†Œ" + usernameTip: "็”จๆˆทๅๆœ€ๅคšๅฏไปฅๅŒ…ๅซ 50 ไธชๅญ—็ฌฆใ€‚" + text: + clickToExpand: "็‚นๅ‡ปๅฑ•ๅผ€" + comparing15days: "ๅฏนๆฏ” 15 ๅคฉ็š„ๆƒ…ๅ†ต" + comparing30daysAgo: "ๅฏนๆฏ” 30 ๅคฉๅ‰ๅ’Œ็Žฐๅœจ็š„ๆƒ…ๅ†ต" + noExtensionData: "ๆฒกๆœ‰ๆ‰ฉๅฑ•ๆ•ฐๆฎ" + noLowTps: "ๆฒกๆœ‰ไฝŽ TPS ๆ—ถ้—ด" + unit: + chunks: "ๅŒบๅ—" + players: "็Žฉๅฎถ" + value: + localMachine: "ๆœฌๅœฐไธปๆœบ" + noKills: "ๆฒกๆœ‰ๅ‡ปๆ€ๆ•ฐ" + offline: " ็ฆป็บฟ" + online: " ๅœจ็บฟ" + with: " ไธŽ" + version: + changelog: "ๆŸฅ็œ‹ๆ›ดๆ–ฐๆ—ฅๅฟ—" + current: "ไฝ ็š„็‰ˆๆœฌๆ˜ฏ ${0}" + download: "ไธ‹่ฝฝ Plan - ${0}.jar" + isDev: "่ฟ™ๆ˜ฏไธ€ไธชๅผ€ๅ‘็‰ˆๆœฌใ€‚" + updateButton: "ๆ›ดๆ–ฐ" + updateModal: + text: "ๆœ‰ๆ–ฐ็‰ˆๆœฌๅฏไพ›ไธ‹่ฝฝใ€‚" + title: "ๆ–ฐ็‰ˆๆœฌ ${0} ็Žฐๅœจๅฏ็”จ๏ผ" +plugin: + apiCSSAdded: "้กต้ขๆ‰ฉๅฑ•๏ผš ${0} ๆทปๅŠ ๆ ทๅผ่กจ(s) ๅˆฐ ${1}, ${2}" + apiJSAdded: "้กต้ขๆ‰ฉๅฑ•๏ผš ${0} ๆทปๅŠ  javascript(s) ๅˆฐ ${1}, ${2}" + disable: + database: "ๆญฃๅœจๅค„็†ๆœชๅค„็†็š„ๅ…ณ้”ฎไปปๅŠกใ€‚(${0})" + disabled: "Plan ๆ’ไปถๅทฒ็ฆ็”จใ€‚" + processingComplete: "ๅค„็†ๅฎŒๆฏ•ใ€‚" + savingSessions: "ไฟๅญ˜ๆœชๅฎŒๆˆ็š„ไผš่ฏไธญ..." + savingSessionsTimeout: "่ถ…ๆ—ถ๏ผŒๅฐ†ๅœจไธ‹ไธ€ๆฌกๅฏๅŠจๅ‚จๅญ˜ๆœชๅฎŒๆˆ็š„ไผš่ฏใ€‚" + waitingDb: "ๆญฃๅœจ็ญ‰ๅพ…ๆŸฅ่ฏขๅฎŒๆˆ๏ผŒไปฅ้ฟๅ… SQLite ไฝฟ JVM ๅดฉๆบƒ..." + waitingDbComplete: "SQLite ่ฟžๆŽฅๅทฒๅ…ณ้—ญใ€‚" + waitingTransactions: "ๆญฃๅœจ็ญ‰ๅพ…ๆœชๅฎŒๆˆ็š„ไบ‹ๅŠกไปฅ้ฟๅ…ๆ•ฐๆฎไธขๅคฑ..." + waitingTransactionsComplete: "ไบ‹ๅŠก้˜Ÿๅˆ—ๅทฒๅ…ณ้—ญใ€‚" + webserver: "็ฝ‘้กตๆœๅŠกๅ™จๅทฒๅ…ณ้—ญใ€‚" + enable: + database: "${0} - ๅทฒ่ฟžๆŽฅๅˆฐๆ•ฐๆฎๅบ“ใ€‚" + enabled: "Plan ๆ’ไปถๅทฒๅฏ็”จใ€‚" + fail: + database: "${0} - ่ฟžๆŽฅๅˆฐๆ•ฐๆฎๅบ“ๅคฑ่ดฅ๏ผš${1}" + databasePatch: "ๆ•ฐๆฎๅบ“่กฅไธๅคฑ่ดฅ๏ผŒๆ’ไปถๅฟ…้กป่ขซ็ฆ็”จใ€‚่ฏทๆŠฅๅ‘Šๆญค้—ฎ้ข˜" + databaseType: "${0} ๆ˜ฏไธๆ”ฏๆŒ็š„ๆ•ฐๆฎๅบ“็ฑปๅž‹" + geoDBWrite: "ไฟๅญ˜ๅทฒไธ‹่ฝฝ็š„ GeoLite2 ๅœฐ็†ไฝ็ฝฎๆ•ฐๆฎๅบ“ๆ—ถๅ‘็”Ÿ้—ฎ้ข˜" + webServer: "็ฝ‘้กตๆœๅŠกๅ™จๆฒกๆœ‰ๅˆๅง‹ๅŒ–!" + notify: + badIP: "0.0.0.0 ไธๆ˜ฏๆœ‰ๆ•ˆ็š„ๅœฐๅ€๏ผŒ่ฏทไฟฎๆ”น Alternative_IP ่ฎพ็ฝฎ. ๅฆๅˆ™ๅฏ่ƒฝไผšๅฏผ่‡ด็ฝ‘้กตๅœฐๅ€้”™่ฏฏ!" + emptyIP: "server.properties ไธญ็š„ IP ไธบ็ฉบไธ”ๆœชไฝฟ็”จๅค‡็”จ IPใ€‚่ฟ™ๅฏ่ƒฝไผšๅฏผ่‡ดๅœฐๅ€ๅ‡บ้”™๏ผ" + geoDisabled: "ๅทฒๅ…ณ้—ญๅœฐ็†ไฝ็ฝฎๆ”ถ้›†ใ€‚(Data.Geolocations: false)" + geoInternetRequired: "Plan ้œ€่ฆๅœจ้ฆ–ๆฌก่ฟ่กŒๆ—ถ่ฎฟ้—ฎไบ’่”็ฝ‘ไปฅไธ‹่ฝฝ GeoLite2 ๅœฐ็†ไฝ็ฝฎๆ•ฐๆฎๅบ“ใ€‚" + storeSessions: "ๆญฃๅœจๅ‚จๅญ˜ไน‹ๅ‰ๅ…ณๆœบๅ‰็•™ไธ‹็š„ไผš่ฏใ€‚" + webserverDisabled: "็ฝ‘้กตๆœๅŠกๅ™จๆœชๅˆๅง‹ๅŒ–ใ€‚(WebServer.DisableWebServer: true)" + webserver: "็ฝ‘้กตๆœๅŠกๅ™จๅทฒๅœจ ${0} ( ${1} ) ็ซฏๅฃไธŠ่ฟ่กŒ" + generic: + dbApplyingPatch: "ๆญฃๅœจๅบ”็”จ่กฅไธ๏ผš${0}..." + dbFaultyLaunchOptions: "ๅฏๅŠจๅ‚ๆ•ฐๅ‡บ้”™๏ผŒๆญฃไฝฟ็”จ้ป˜่ฎคๅ‚ๆ•ฐ๏ผˆ${0}๏ผ‰" + dbNotifyClean: "็งป้™คไบ† ${0} ไฝ็”จๆˆท็š„ๆ•ฐๆฎใ€‚" + dbNotifySQLiteWAL: "ๆญคๆœๅŠกๅ™จ็‰ˆๆœฌไธๆ”ฏๆŒ SQLite WAL ๆจกๅผ๏ผŒๆญฃไฝฟ็”จ้ป˜่ฎคๆจกๅผใ€‚่ฟ™ๅฏ่ƒฝไผšๅฝฑๅ“ๆ€ง่ƒฝใ€‚" + dbPatchesAlreadyApplied: "ๅทฒๅบ”็”จๆ‰€ๆœ‰ๆ•ฐๆฎๅบ“่กฅไธใ€‚" + dbPatchesApplied: "ๅทฒๆˆๅŠŸๅบ”็”จๆ‰€ๆœ‰ๆ•ฐๆฎๅบ“่กฅไธใ€‚" + dbSchemaPatch: "Database: Making sure schema is up to date.." + loadedServerInfo: "Server identifier loaded: ${0}" + loadingServerInfo: "Loading server identifying information" + no: "ๅฆ" + today: "'ไปŠๅคฉ'" + unavailable: "ไธๅฏ็”จ" + unknown: "ไฝ็ฝฎ" + yes: "ๆ˜ฏ" + yesterday: "'ๆ˜จๅคฉ'" + version: + checkFail: "ๆ— ๆณ•ๆฃ€ๆŸฅๆœ€ๆ–ฐ็‰ˆๆœฌๅท" + checkFailGithub: "ๆ— ๆณ•ไปŽ Github/versions.txt ๅŠ ่ฝฝ็‰ˆๆœฌไฟกๆฏ" + isDev: " ่ฟ™ๆ˜ฏไธ€ไธชๅผ€ๅ‘็‰ˆๆœฌใ€‚" + isLatest: "ไฝ ๆญฃๅœจไฝฟ็”จๆœ€ๆ–ฐ็‰ˆๆœฌใ€‚" + updateAvailable: "ๆœ‰ๆ–ฐ็‰ˆๆœฌ (${0}) ๅฏ็”จ ${1}" + updateAvailableSpigot: "ๆœ‰ๆ–ฐ็‰ˆๆœฌๅฏ็”จ๏ผš${0}" + webserver: + fail: + SSLContext: "็ฝ‘้กตๆœๅŠกๅ™จ๏ผšSSL ็Žฏๅขƒๅˆๅง‹ๅŒ–ๅคฑ่ดฅใ€‚" + certFileEOF: "็ฝ‘้กตๆœๅŠกๅ™จ: ๅœจ่ฏปๅ–่ฏไนฆๆ–‡ไปถๆ—ถๅ‡บ็Žฐไบ†EOFๅผ‚ๅธธ. ๏ผˆ่ฏทๆฃ€ๆŸฅ่ฏไนฆๆ–‡ไปถๅฎŒๆ•ดๆ€ง๏ผ‰" + certStoreLoad: "็ฝ‘้กตๆœๅŠกๅ™จ๏ผšSSL ่ฏไนฆ่ฝฝๅ…ฅๅคฑ่ดฅใ€‚" + portInUse: "ๆœชๆˆๅŠŸๅˆๅง‹ๅŒ–็ฝ‘้กตๆœๅŠกๅ™จใ€‚็ซฏๅฃ(${0})ๆ˜ฏๅฆ่ขซๅทฒ่ขซๅ ็”จ๏ผŸ" + notify: + authDisabledConfig: "็ฝ‘้กตๆœๅŠกๅ™จ: ็”จๆˆท็™ปๅฝ•ๅทฒๅ…ณ้—ญ! ๏ผˆๅทฒๅœจ้…็ฝฎๆ–‡ไปถไธญ็ฆ็”จ๏ผ‰" + authDisabledNoHTTPS: "็ฝ‘้กตๆœๅŠกๅ™จ๏ผšๅทฒ็ฆ็”จ็”จๆˆท็™ปๅฝ•๏ผ๏ผˆHTTP ๆ–นๅผไธๅฎ‰ๅ…จ๏ผ‰" + certificateExpiresOn: "Webserver: Loaded certificate is valid until ${0}." + certificateExpiresPassed: "Webserver: Certificate has expired, consider renewing the certificate." + certificateExpiresSoon: "Webserver: Certificate expires in ${0}, consider renewing the certificate." + certificateNoSuchAlias: "Webserver: Certificate with alias '${0}' was not found inside the keystore file '${1}'." + http: "็ฝ‘้กตๆœๅŠกๅ™จ๏ผšๆ— ่ฏไนฆ -> ๆญฃไฝฟ็”จ HTTP ๆœๅŠกๅ™จๆไพ›ๅฏ่ง†ๅŒ–ๆ•ˆๆžœใ€‚" + ipWhitelist: "็ฝ‘้กตๆœๅŠกๅ™จ: IP ็™ฝๅๅ•ๅทฒๅฏ็”จใ€‚" + ipWhitelistBlock: "็ฝ‘้กตๆœๅŠกๅ™จ๏ผš${0} ่ขซๆ‹’็ป่ฎฟ้—ฎ '${1}'. ๏ผˆไธๅœจ็™ฝๅๅ•ไธญ๏ผ‰" + noCertFile: "็ฝ‘้กตๆœๅŠกๅ™จ๏ผšๆ‰พไธๅˆฐ่ฏไนฆๅฏ†้’ฅๅบ“ๆ–‡ไปถ๏ผš${0}" + reverseProxy: "็ฝ‘้กตๆœๅŠกๅ™จ: HTTPS ไปฃ็†ๆจกๅผๅทฒๅผ€ๅฏ, ่ฏท็กฎไฟไฝ ็š„ๅๅ‘ไปฃ็†ๅทฒ็ป้…็ฝฎไธบ HTTPS ๆจกๅผๅนถไธ” Plan ็š„ Alternative_IP.Address ้€‰้กนๅทฒ็ปๆŒ‡ๅ‘ไปฃ็†" diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_CS.txt b/Plan/common/src/main/resources/assets/plan/locale/locale_CS.txt deleted file mode 100644 index 1f8603797..000000000 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_CS.txt +++ /dev/null @@ -1,517 +0,0 @@ -API - css+ || Rozลกรญล™enรญ: ${0} pล™idรกn stylesheet k ${1}, ${2} -API - js+ || Rozลกรญล™enรญ: ${0} pล™idรกn javascript k ${1}, ${2} -Cmd - Click Me || Klikni na mฤ› -Cmd - Link || Odkaz -Cmd - Link Network || Strรกnka sรญtฤ›: -Cmd - Link Player || Strรกnka hrรกฤe: -Cmd - Link Player JSON || JSON hrรกฤ: -Cmd - Link Players || Strรกnka hrรกฤลฏ: -Cmd - Link Register || Strรกnka registrace: -Cmd - Link Server || Strรกnka serveru: -CMD Arg - backup-file || Nรกzev souboru zรกlohy (citlivรฉ na velikost pรญsmen) -CMD Arg - code || Kรณd pouลพitรฝ k dokonฤenรญ registrace.. -CMD Arg - db type backup || Typ databรกze k zรกlohovรกnรญ. Pouลพita aktuรกlnรญ databรกze, pokud nenรญ specifikovรกno. -CMD Arg - db type clear || Typ databรกze ze kterรฉ smazat vลกechna data. -CMD Arg - db type hotswap || Typ databรกze k pouลพitรญ. -CMD Arg - db type move from || Typ databรกze odkud pล™esunout data. -CMD Arg - db type move to || Typ databรกze kam pล™esunout data. Nemลฏลพe bรฝt stejnรฉ jako minulรฉ. -CMD Arg - db type restore || Typ databรกze kam obnovit. Aktuรกlnรญ databรกze je pouลพita, pokud nenรญ specifikovรกno. -CMD Arg - feature || Nรกzev funkce k vypnutรญ: ${0} -CMD Arg - player identifier || Jmรฉno ฤi UUID hrรกฤe -CMD Arg - player identifier remove || Identifikรกtor pro hrรกฤe, kterรฝ bude smazรกn z aktuรกlnรญ databรกze. -CMD Arg - server identifier || Jmรฉno, ID nebo UUID serveru -CMD Arg - subcommand || Pouลพijte pล™รญkaz bez sub pล™รญkazu k pomoci. -CMD Arg - username || Pล™ezdรญvka jinรฉho uลพivatele. Pokud nenรญ specifikovรกno, je pouลพit linknutรฝ uลพivatel. -CMD Arg Name - backup-file || zรกloha -CMD Arg Name - code || ${code} -CMD Arg Name - export kind || druh exportu -CMD Arg Name - feature || funkce -CMD Arg Name - import kind || druh importu -CMD Arg Name - name or uuid || jmeno/uuid -CMD Arg Name - server || server -CMD Arg Name - subcommand || subpล™รญkaz -CMD Arg Name - username || uลพivatel -Cmd Confirm - accept || Pล™ijmout -Cmd Confirm - cancelled, no data change || Zruลกeno. ลฝรกdnรก data nebyla zmฤ›nฤ›na. -Cmd Confirm - cancelled, unregister || Zruลกeno. '${0}' nebyl odregistrovรกn -Cmd Confirm - clearing db || Chystรกte se smazat vลกechna Plan data v ${0} -Cmd Confirm - confirmation || Potvrdit: -Cmd Confirm - deny || Zruลกit -Cmd Confirm - Expired || Potvrzenรญ vyprลกelo, pouลพijte pล™รญkaz znovu -Cmd Confirm - Fail on accept || Chyba pro pล™ijmutou akci pล™i vykonรกnรญ: ${0} -Cmd Confirm - Fail on deny || Chyba pro odmรญtnout akci pล™i vykonรกnรญ: ${0} -Cmd Confirm - overwriting db || Chystรกte se pล™epsat data v Plan ${0} s daty v ${1} -Cmd Confirm - remove player db || Chystรกte se smazat data ${0} z ${1} -Cmd Confirm - unregister || Chystรกte se odregistrovat '${0}' linknutรฉho s ${1} -Cmd db - creating backup || Vytvรกล™รญm soubor zรกlohy '${0}.db' s obsahem z ${1} -Cmd db - removal || Maลพu Plan data z ${0}.. -Cmd db - removal player || Maลพu data ${0} z ${1}.. -Cmd db - server uninstalled || ยงaPokud je server stรกle nainstalovanรฝ, automaticky se ukรกลพe jako instalovanรฝ v databรกzi. -Cmd db - write || Zapisuji do ${0}.. -Cmd Disable - Disabled || ยงaPlan systรฉmy jsou nynรญ vypnuty. Mลฏลพete stรกle pouลพรญt /planbungee reload pro restart pluginu. -Cmd FAIL - Accepts only these arguments || Pล™ijรญmรก nรกsledujรญcรญ jako ${0}: ${1} -Cmd FAIL - Database not open || ยงcDatabรกze je ${0} - Zkuste to znovu pozdฤ›ji. -Cmd FAIL - Empty search string || Vyhledรกvacรญ pojem nemลฏลพe bรฝt prรกzdnรฝ -Cmd FAIL - Invalid Username || ยงcUลพivatel nemรก UUID. -Cmd FAIL - No Feature || ยงeDefinujte funkci k vypnutรญ! (aktuรกlnฤ› podporuje ${0}) -Cmd FAIL - No Permission || ยงcNa toto nemรกte potล™ebnรก prรกva. -Cmd FAIL - No player || Hrรกฤ '${0}' nebyl nalezen, nebo nemรก UUID. -Cmd FAIL - No player register || Hrรกฤ '${0}' nebyl nalezen v databรกzi. -Cmd FAIL - No server || Server '${0}' nebyl nalezen v databรกzi. -Cmd FAIL - Require only one Argument || ยงcJe zapotล™ebรญ jednotnรฝ argument ${1} -Cmd FAIL - Requires Arguments || ยงcPotล™ebnรฉ argumenty (${0}) ${1} -Cmd FAIL - see config || zhlรฉdnฤ›te '${0}' v config.yml -Cmd FAIL - Unknown Username || ยงcUลพivatel nebyl vidฤ›n na tomto serveru -Cmd FAIL - Users not linked || Uลพivatel nenรญ linknutรฝ s vaลกรญm รบฤtem a nemรกte prรกvo mazat ostatnรญ รบฤty uลพivatelลฏ. -Cmd FAIL - WebUser does not exists || ยงcUลพivatel neexistuje! -Cmd FAIL - WebUser exists || ยงcUลพivatel jiลพ existuje! -Cmd Footer - Help || ยง7Podrลพte kurzor nad pล™รญkazem nebo argumentem ฤi pouลพijte '/${0} ?' pro vรญce informacรญ o nich. -Cmd Header - Analysis || > ยง2Vรฝsledky Analรฝzy -Cmd Header - Help || > ยง2/${0} Pomoc -Cmd Header - Info || > ยง2Analรฝza Hrรกฤลฏ -Cmd Header - Inspect || > ยง2Hrรกฤ: ยงf${0} -Cmd Header - Network || > ยง2Strรกnka Sรญtฤ› -Cmd Header - Players || > ยง2Hrรกฤลฏ -Cmd Header - Search || > ยง2${0} Vรฝsledky pro ยงf${1}ยง2: -Cmd Header - server list || id::name::uuid -Cmd Header - Servers || > ยง2Servery -Cmd Header - web user list || username::linked to::permission level -Cmd Header - Web Users || > ยง2${0} Web uลพivatelรฉ -Cmd Info - Bungee Connection || ยง2Pล™ipojen na Proxy: ยงf${0} -Cmd Info - Database || ยง2Aktivnรญ databรกze: ยงf${0} -Cmd Info - Reload Complete || ยงaReload Dokonฤen -Cmd Info - Reload Failed || ยงcNฤ›co se pokazilo pล™i reloadu pluginu, doporuฤejem restart serveru. -Cmd Info - Update || ยง2Dostupnรก aktualizace: ยงf${0} -Cmd Info - Version || ยง2Verze: ยงf${0} -Cmd network - No network || Server nenรญ pล™ipojen k sรญti. Odkaz odkazuje na server strรกnku. -Cmd Notify - No Address || ยงeลฝรกdnรก adresa nebyla dostupnรก - pouลพรญvรกm localhost jako rezervu. Nastavte "Alternative_IP". -Cmd Notify - No WebUser || Pravdฤ›podobnฤ› nemรกte uลพivatele webu, pouลพijte /plan register -Cmd Notify - WebUser register || Registrovรกn novรฝ uลพivatel: '${0}' รšroveลˆ prรกv: ${1} -Cmd Qinspect - Active Playtime || ยง2Aktivnรญ hernรญ ฤas: ยงf${0} -Cmd Qinspect - Activity Index || ยง2Index Aktivity: ยงf${0} | ${1} -Cmd Qinspect - AFK Playtime || ยง2AFK ฤas: ยงf${0} -Cmd Qinspect - Deaths || ยง2Smrti: ยงf${0} -Cmd Qinspect - Geolocation || ยง2Pล™ihlรกลกen z: ยงf${0} -Cmd Qinspect - Last Seen || ยง2Naposledy vidฤ›n: ยงf${0} -Cmd Qinspect - Longest Session || ยง2Nejdelลกรญ relace: ยงf${0} -Cmd Qinspect - Mob Kills || ยง2Zabitรญ mobลฏ: ยงf${0} -Cmd Qinspect - Player Kills || ยง2Zabitรญ hrรกฤลฏ: ยงf${0} -Cmd Qinspect - Playtime || ยง2Hernรญ ฤas: ยงf${0} -Cmd Qinspect - Registered || ยง2Registrovรกn: ยงf${0} -Cmd Qinspect - Times Kicked || ยง2Poฤet vykopnutรญ: ยงf${0} -Cmd SUCCESS - Feature disabled || ยงaDoฤasnฤ› vypnuto '${0}' do dalลกรญho reloadu pluginu. -Cmd SUCCESS - WebUser register || ยงaรšspฤ›ลกnฤ› pล™idรกn novรฝ uลพivatel (${0})! Mลฏลพete zobrazit web panel na nรกsledujรญcรญm odkazu. -Cmd unregister - unregistering || Odregistrovรกnรญ '${0}'.. -Cmd WARN - Database not open || ยงeDatabรกze je ${0} - Toto mลฏลพe trvat dรฉle.. -Cmd Web - Permission Levels || >\ยง70: Pล™รญstup ke vลกem strรกnkรกm\ยง71: Pล™รญstup k '/players' a vลกem hrรกฤskรฝm strรกnkรกm\ยง72: Pล™รญstup k hrรกฤskรฉ strรกnce se stejnรฝm jmรฉnem jako web uลพivatel\ยง73+: ลฝรกdnรก prรกva -Command Help - /plan db || Spravovat Plan databรกzi -Command Help - /plan db backup || Zรกlohovat data z databรกze do souboru -Command Help - /plan db clear || Smazat Vล ECHNA Plan data z databรกze -Command Help - /plan db hotswap || Rychlรก zmฤ›na databรกze -Command Help - /plan db move || Pล™esun dat mezi databรกzemi -Command Help - /plan db remove || Smazat data hrรกฤe z aktuรกlnรญ databรกze -Command Help - /plan db restore || Obnova dat ze souboru do databรกze -Command Help - /plan db uninstalled || Nastavit server jako odinstalovanรฝ v databรกzi. -Command Help - /plan disable || Vypne plugin ฤi jeho ฤรกst -Command Help - /plan export || Export html ฤi json souborลฏ manuรกlnฤ› -Command Help - /plan import || Import dat -Command Help - /plan info || Zjistit verzi Plan -Command Help - /plan ingame || Ukรกzat Hrรกฤskรฉ info ve hล™e -Command Help - /plan json || Ukรกzat json Hrรกฤskรฝch ฤistรฝch dat. -Command Help - /plan logout || Log out other users from the panel. -Command Help - /plan network || Ukรกzat strรกnku sรญtฤ› -Command Help - /plan player || Ukรกzat strรกnku hrรกฤe -Command Help - /plan players || Ukรกzat strรกnku hrรกฤลฏ -Command Help - /plan register || Registrovat web uลพivatele -Command Help - /plan reload || Restartuje plugin Plan -Command Help - /plan search || Hledat podle jmรฉna hrรกฤe -Command Help - /plan server || Ukรกzat strรกnku serveru -Command Help - /plan servers || Seznam serverลฏ v databรกzi -Command Help - /plan unregister || Odregistrovat uลพivatele z Plan webu -Command Help - /plan users || Zobrazit seznam uลพivatelลฏ webu -Database - Apply Patch || Aplikuji patch: ${0}.. -Database - Patches Applied || Vลกechny databรกze byly รบspฤ›ลกnฤ› aktualizovรกny. -Database - Patches Applied Already || Vลกechny databรกze jiลพ jsou aktualizovรกny. -Database MySQL - Launch Options Error || Moลพnosti spuลกtฤ›nรญ byly chybnรฉ, pouลพรญvรกm defaultnรญ (${0}) -Database Notify - Clean || Smazรกny data ${0} hrรกฤลฏ. -Database Notify - SQLite No WAL || SQLite WAL mรณd nenรญ podporovรกn na verzi tohoto serveru, pouลพรญvรกm default. Toto mลฏลพe ฤi nemusรญ ovlivnit vรฝkon. -Disable || Player Analytics vypnuty. -Disable - Processing || Zpracovรกvรกnรญ kritickรฝch nezpracovanรฝch รบkonลฏ. (${0}) -Disable - Processing Complete || Zpracovรกnรญ dokonฤeno. -Disable - Unsaved Session Save || Uklรกdรกnรญ nedokonฤenรฉ relace. -Disable - Unsaved Session Save Timeout || Pล™ekroฤen ฤasovรฝ limit, uklรกdรกnm nedokonฤenรฉ relace pro pล™รญลกtรญ spojenรญ. -Disable - Waiting SQLite || ฤŒekรกnรญ na dokonฤenรญ dotazu, aby se zabrรกnilo pรกdu JVM SQLite.. -Disable - Waiting SQLite Complete || Uzavล™enรฉ pล™ipojenรญ SQLite. -Disable - Waiting Transactions || ฤŒekรกm na nedokonฤenรฉ transakce, aby nedoลกlo ke ztrรกtฤ› dat.. -Disable - Waiting Transactions Complete || Fronta Transakce uzavล™ena. -Disable - WebServer || Webserver je jiลพ vypnutรฝ. -Enable || Player Analytics zapnuty. -Enable - Database || ${0}-pล™ipojenรญ k databรกzi navรกzรกno. -Enable - Notify Bad IP || 0.0.0.0 nenรญ validnรญ adresa, nastavte Alternative_IP. Mohou bรฝt poskytnuty ลกpatnรฉ odkazy! -Enable - Notify Empty IP || IP v server.properties je prรกzdnรฉ & Alternative_IP se nepouลพรญvรก. Mohou bรฝt poskytnuty ลกpatnรฉ odkazy! -Enable - Notify Geolocations disabled || Sbรญrรกnรญ geolokace nenรญ aktivnรญ. (Data.Geolocations: false) -Enable - Notify Geolocations Internet Required || Plan potล™ebuje internetovรฉ pล™ipojenรญ pro prvnรญ start ke staลพenรญ GeoLite2 Geolocation databรกze. -Enable - Notify Webserver disabled || WebServer nebyl inicializovรกn. (WebServer.DisableWebServer: true) -Enable - Storing preserved sessions || Uloลพenรญ relace, kterรฉ byly zachovalรฉ pล™ed pล™edchozรญm spojenรญm. -Enable - WebServer || Webserver bฤ›ลพรญ na PORTU ${0} ( ${1} ) -Enable FAIL - Database || ${0}-Pล™ipojenรญ k databรกzi selhalo: ${1} -Enable FAIL - Database Patch || Patch databรกze selhal, plugin musรญ bรฝt vypnut. Prosรญme nahlaลกte tento problรฉm -Enable FAIL - GeoDB Write || Nฤ›co se pokazilo pล™i uklรกdรกnรญ staลพenรฉ GeoLite2 Geolocation databรกze -Enable FAIL - WebServer (Proxy) || WebServer se nespustil! -Enable FAIL - Wrong Database Type || ${0} nenรญ podporovanรก databรกze -HTML - AND_BUG_REPORTERS || & Nahlรกลกenรญ bugu! -HTML - BANNED (Filters) || Zabanovanรญ -HTML - COMPARING_15_DAYS || Srovnรกnรญ poslednรญch 15 dnรญ -HTML - COMPARING_60_DAYS || Srovnรกnรญ poslednรญch 60 dnรญ -HTML - COMPARING_7_DAYS || Srovnรกnรญ poslednรญch 7 dnรญ -HTML - DATABASE_NOT_OPEN || Databรกze nenรญ otevล™enรก, ovฤ›ล™te status databรกze s /plan info -HTML - DESCRIBE_RETENTION_PREDICTION || This value is a prediction based on previous players. -HTML - ERROR || Autentifikace neuspฤ›ลกnรก kvลฏli chybฤ› -HTML - EXPIRED_COOKIE || Uลพivatelskรฉ cookie expirovalo -HTML - FILTER_ACTIVITY_INDEX_NOW || Aktuรกlnรญ skupina aktivit -HTML - FILTER_ALL_PLAYERS || Vลกichni hrรกฤi -HTML - FILTER_BANNED || Ban status -HTML - FILTER_GROUP || Skupina: -HTML - FILTER_OPS || OP status -HTML - INDEX_ACTIVE || Aktivnรญ -HTML - INDEX_INACTIVE || Neaktivnรญ -HTML - INDEX_IRREGULAR || Nepravidelnรฝ -HTML - INDEX_REGULAR || Pravidelnรฝ -HTML - INDEX_VERY_ACTIVE || Velmi aktivnรญ -HTML - KILLED || Zabit -HTML - LABEL_1ST_WEAPON || Nejsmrtelnฤ›jลกรญ PvP zbraลˆ -HTML - LABEL_2ND_WEAPON || 2. PvP Zbraลˆ -HTML - LABEL_3RD_WEAPON || 3. PvP Zbraลˆ -HTML - LABEL_ACTIVE_PLAYTIME || Aktivnรญ hernรญ ฤas -HTML - LABEL_ACTIVITY_INDEX || Index aktivity -HTML - LABEL_AFK || AFK -HTML - LABEL_AFK_TIME || AFK ฤas -HTML - LABEL_AVG || Prลฏmฤ›r -HTML - LABEL_AVG_ACTIVE_PLAYTIME || Prลฏmฤ›rnรก hernรญ aktivita -HTML - LABEL_AVG_AFK_TIME || Prลฏmฤ›rnรฝ AFK ฤas -HTML - LABEL_AVG_CHUNKS || Prลฏmฤ›r chunkลฏ -HTML - LABEL_AVG_ENTITIES || Prลฏmฤ›r entit -HTML - LABEL_AVG_KDR || Prลฏmฤ›r KDR -HTML - LABEL_AVG_MOB_KDR || Prลฏmฤ›r Mob KDR -HTML - LABEL_AVG_PLAYTIME || Prลฏmฤ›r hernรญ doby -HTML - LABEL_AVG_SESSION_LENGTH || Prลฏmฤ›rnรก dรฉlka relace -HTML - LABEL_AVG_SESSIONS || Prลฏmฤ›rnรก relace -HTML - LABEL_AVG_TPS || Prลฏmฤ›r TPS -HTML - LABEL_BANNED || Zabanovรกn -HTML - LABEL_BEST_PEAK || Nejvรญce hrรกฤลฏ -HTML - LABEL_DAY_OF_WEEK || Den tรฝdne -HTML - LABEL_DEATHS || Smrti -HTML - LABEL_DOWNTIME || Offline doba -HTML - LABEL_DURING_LOW_TPS || Pล™i nรญzkรฝch TPS: -HTML - LABEL_ENTITIES || Entity -HTML - LABEL_FAVORITE_SERVER || Oblรญbenรฝ server -HTML - LABEL_FIRST_SESSION_LENGTH || Dรฉlka prvnรญ relace -HTML - LABEL_FREE_DISK_SPACE || Volnรฉ mรญsto na disku -HTML - LABEL_INACTIVE || Neaktivnรญ -HTML - LABEL_LAST_PEAK || Naposledy nejvรญce hrรกฤลฏ -HTML - LABEL_LAST_SEEN || Naposledy vidฤ›n -HTML - LABEL_LOADED_CHUNKS || Naฤtenรฉ chunky -HTML - LABEL_LOADED_ENTITIES || Naฤtenรฉ entity -HTML - LABEL_LONE_JOINS || Samotnรก pล™ipojenรญ -HTML - LABEL_LONE_NEW_JOINS || Samotnรก pล™ipojenรญ novรกฤkลฏ -HTML - LABEL_LONGEST_SESSION || Nejdelลกรญ relace -HTML - LABEL_LOW_TPS || Nejniลพลกรญ TPS -HTML - LABEL_MAX_FREE_DISK || Max. volnรฉho disku -HTML - LABEL_MIN_FREE_DISK || Min. volnรฉho disku -HTML - LABEL_MOB_DEATHS || Smrti zpลฏsobenรฉ moby -HTML - LABEL_MOB_KDR || Mob KDR -HTML - LABEL_MOB_KILLS || Zabitรญ mobovรฉ -HTML - LABEL_MOST_ACTIVE_GAMEMODE || Nejvรญce aktivnรญ mรณd -HTML - LABEL_NAME || Jmรฉno -HTML - LABEL_NEW || Novรฝ -HTML - LABEL_NEW_PLAYERS || Novรญ hrรกฤi -HTML - LABEL_NICKNAME || Pล™ezdรญvka -HTML - LABEL_NO_SESSION_KILLS || ลฝรกdnรฝ -HTML - LABEL_ONLINE_FIRST_JOIN || Online hrรกฤi pล™i prvnรญm pล™ipojenรญ -HTML - LABEL_OPERATOR || Operรกtor -HTML - LABEL_PER_PLAYER || na hrรกฤe -HTML - LABEL_PER_REGULAR_PLAYER || na pravidelnรฉho hrรกฤe -HTML - LABEL_PLAYER_DEATHS || Smrti zpลฏsobenรฉ hrรกฤi -HTML - LABEL_PLAYER_KILLS || Zabitรญ hrรกฤi -HTML - LABEL_PLAYERS_ONLINE || Hrรกฤi online -HTML - LABEL_PLAYTIME || Hernรญ ฤas -HTML - LABEL_REGISTERED || Registrovรกn -HTML - LABEL_REGISTERED_PLAYERS || Registrovanรญ hrรกฤi -HTML - LABEL_REGULAR || Pravidelnรฝ -HTML - LABEL_REGULAR_PLAYERS || Pravidelnรญ hrรกฤi -HTML - LABEL_RELATIVE_JOIN_ACTIVITY || Relativnรญ aktivita pล™ipojenรญ -HTML - LABEL_RETENTION || Udrลพenรญ novรฝch hrรกฤลฏ -HTML - LABEL_SERVER_DOWNTIME || Server offline -HTML - LABEL_SERVER_OCCUPIED || Server plnรฝ -HTML - LABEL_SESSION_ENDED || Ukonฤeno -HTML - LABEL_SESSION_MEDIAN || Stล™ednรญ hodnota relacรญ -HTML - LABEL_TIMES_KICKED || Poฤet vykopnutรญ -HTML - LABEL_TOTAL_PLAYERS || Hrรกฤลฏ celkem -HTML - LABEL_TOTAL_PLAYTIME || Hernรญ doba celkem -HTML - LABEL_UNIQUE_PLAYERS || Unikรกtnรญ hrรกฤi -HTML - LABEL_WEEK_DAYS || 'Pondฤ›lรญ', 'รšterรฝ', 'Stล™eda', 'ฤŒtvrtek', 'Pรกtek', 'Sobota', 'Nedฤ›le' -HTML - LINK_BACK_NETWORK || Strรกnka sรญtฤ› -HTML - LINK_BACK_SERVER || Strรกnka serveru -HTML - LINK_CHANGELOG || Ukรกzat changelog -HTML - LINK_DISCORD || Obecnรก podpora na Discordu -HTML - LINK_DOWNLOAD || Staลพenรญ -HTML - LINK_ISSUES || Nahlรกsit potรญลพe -HTML - LINK_NIGHT_MODE || Noฤnรญ mรณd -HTML - LINK_PLAYER_PAGE || Strรกnka hrรกฤe -HTML - LINK_QUICK_VIEW || Rychlรฉ zobrazenรญ -HTML - LINK_SERVER_ANALYSIS || Analรฝza serveru -HTML - LINK_WIKI || Plan Wiki, Tutoriรกly & Dokumentace -HTML - LOCAL_MACHINE || Lokรกlnรญ stroj -HTML - LOGIN_CREATE_ACCOUNT || Vytvoล™te si รบฤet! -HTML - LOGIN_FAILED || Pล™ihlaลกovรกnรญ selhalo: -HTML - LOGIN_FORGOT_PASSWORD || Zapomnฤ›l jste heslo? -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_1 || Zapomnฤ›l jste heslo? Odregistruj se a registruj se znovu. -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_2 || Pro odstranฤ›nรญ aktuรกlnรญho uลพivatele pouลพij nรกsledujรญcรญ pล™รญkaz ve hล™e: -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_3 || Nebo pouลพij konzoli: -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_4 || Po vykonรกnรญ pล™รญkazu, -HTML - LOGIN_LOGIN || Pล™ihlรกsit se -HTML - LOGIN_LOGOUT || Odhlรกsit se -HTML - LOGIN_PASSWORD || "Heslo" -HTML - LOGIN_USERNAME || "Uลพivatelskรฉ jmรฉno" -HTML - NAV_PLUGINS || Pluginy -HTML - NEW_CALENDAR || Novรฉ: -HTML - NO_KILLS || ลฝรกdnรฉ zabitรญ -HTML - NO_USER_PRESENT || Cookies uลพivatele nejsou k dispozici. -HTML - NON_OPERATORS (Filters) || Bez OP -HTML - NOT_BANNED (Filters) || Nezabanovanรญ -HTML - OFFLINE || Offline -HTML - ONLINE || Online -HTML - OPERATORS (Filters) || Operรกtoล™i -HTML - PER_DAY || / Den -HTML - PLAYERS_TEXT || Hrรกฤi -HTML - QUERY || Dotaz< -HTML - QUERY_ACTIVITY_OF_MATCHED_PLAYERS || Aktivita dotyฤnรฝch hrรกฤลฏ -HTML - QUERY_ACTIVITY_ON || Aktivita -HTML - QUERY_ADD_FILTER || Pล™idat filtr.. -HTML - QUERY_AND || a -HTML - QUERY_ARE || ` jsou` -HTML - QUERY_ARE_ACTIVITY_GROUP || jsou ve skupinรกch aktivit -HTML - QUERY_ARE_PLUGIN_GROUP || jsou v ${plugin}'s ${group} Skupiny -HTML - QUERY_JOINED_WITH_ADDRESS || joined with address -HTML - QUERY_LOADING_FILTERS || Naฤรญtรกnรญ filtrลฏ.. -HTML - QUERY_MAKE || Vytvoล™it dotaz -HTML - QUERY_MAKE_ANOTHER || Vytvoล™it dalลกรญ dotaz -HTML - QUERY_OF_PLAYERS || hrรกฤลฏ, kteล™รญ -HTML - QUERY_PERFORM_QUERY || Provรฉst dotaz! -HTML - QUERY_PLAYED_BETWEEN || Hrรกl mezi -HTML - QUERY_REGISTERED_BETWEEN || Registrovรกn mezi -HTML - QUERY_RESULTS || Vรฝsledky dotazu -HTML - QUERY_RESULTS_MATCH || nalezeno ${resultCount} hrรกฤลฏ -HTML - QUERY_SESSIONS_WITHIN_VIEW || Relace v rรกmci pohledu -HTML - QUERY_SHOW_VIEW || Zobrazit pohled -HTML - QUERY_TIME_FROM || >od -HTML - QUERY_TIME_TO || >do -HTML - QUERY_VIEW || Pohled: -HTML - QUERY_ZERO_RESULTS || Nebyla nalezena ลพรกdnรก data. -HTML - REGISTER || Registrovat -HTML - REGISTER_CHECK_FAILED || Kontrola registrace neรบspฤ›ลกnรก: -HTML - REGISTER_COMPLETE || Dokonฤit registraci -HTML - REGISTER_COMPLETE_INSTRUCTIONS_1 || Nynรญ mลฏลพete dokonฤit registraci uลพivatele. -HTML - REGISTER_COMPLETE_INSTRUCTIONS_2 || Kรณd vyprลกรญ za 15 minut. -HTML - REGISTER_COMPLETE_INSTRUCTIONS_3 || Pro dokonฤenรญ registrace pouลพijte nรกsledujรญcรญ pล™รญkaz ve hล™e: -HTML - REGISTER_COMPLETE_INSTRUCTIONS_4 || Nebo pouลพijte pล™รญkaz v konzoli: -HTML - REGISTER_CREATE_USER || Vytvoล™it novรฉho uลพivatele -HTML - REGISTER_FAILED || Registrace se nezdaล™ila: -HTML - REGISTER_HAVE_ACCOUNT || Mรกte jiลพ รบฤet? Pล™ihlaste se! -HTML - REGISTER_PASSWORD_TIP || Heslo by mฤ›lo bรฝt dlouhรฉ alespoลˆ 8 znakลฏ bez omezenรญ. -HTML - REGISTER_SPECIFY_PASSWORD || Napiลกte heslo -HTML - REGISTER_SPECIFY_USERNAME || Zvolte uลพivatelskรฉ jmรฉno -HTML - REGISTER_USERNAME_LENGTH || Uลพivatelskรฉ jmรฉno mลฏลพe bรฝt dlouhรฉ maximรกlnฤ› 50 znakลฏ. Vaลกe mรก -HTML - REGISTER_USERNAME_TIP || Uลพivatelskรฉ jmรฉno mลฏลพe bรฝt dlouhรฉ 50 znakลฏ. -HTML - SESSION || Relace -HTML - SIDE_GEOLOCATIONS || Geolokace -HTML - SIDE_INFORMATION || INFORMACE -HTML - SIDE_LINKS || ODKAZY -HTML - SIDE_NETWORK_OVERVIEW || Pล™ehled proxy -HTML - SIDE_OVERVIEW || Pล™ehled -HTML - SIDE_PERFORMANCE || Vรฝkon -HTML - SIDE_PLAYER_LIST || Seznam hrรกฤลฏ -HTML - SIDE_PLAYERBASE || Hrรกฤi -HTML - SIDE_PLAYERBASE_OVERVIEW || Pล™ekled zรกkladny hrรกฤลฏ -HTML - SIDE_PLUGINS || PLUGINY -HTML - SIDE_PVP_PVE || PvP & PvE -HTML - SIDE_SERVERS || Servery -HTML - SIDE_SERVERS_TITLE || SERVERY -HTML - SIDE_SESSIONS || Relace -HTML - SIDE_TO_MAIN_PAGE || Zpฤ›t na hlavnรญ strรกnku -HTML - TEXT_CLICK_TO_EXPAND || Kliknฤ›te pro rozbalenรญ -HTML - TEXT_CONTRIBUTORS_CODE || pล™ispฤ›vatel kรณdu -HTML - TEXT_CONTRIBUTORS_LOCALE || pล™ekladaฤ -HTML - TEXT_CONTRIBUTORS_MONEY || Extra speciรกlnรญ podฤ›kovรกnรญ tฤ›m, kteล™รญ penฤ›ลพnฤ› pomohli vรฝvoji. -HTML - TEXT_CONTRIBUTORS_THANKS || Nadรกle nรกsledujรญcรญ skvฤ›lรญ lidรฉ, kteล™รญ pล™ispฤ›li: -HTML - TEXT_DEV_VERSION || Toto je vรฝvojรกล™skรก verze. -HTML - TEXT_DEVELOPED_BY || je vyvรญjena od -HTML - TEXT_FIRST_SESSION || Prvnรญ relace -HTML - TEXT_LICENSED_UNDER || Player Analytics je vyvรญjeno a licencovรกno pod -HTML - TEXT_METRICS || bStats Metrics -HTML - TEXT_NO_EXTENSION_DATA || ลฝรกdnรก data rozลกรญล™enรญ -HTML - TEXT_NO_LOW_TPS || ลฝรกdnรฉ nรญzkรฉ TPS rekordy -HTML - TEXT_NO_SERVER || ลฝรกdnรฝ server pro kterรฝ lze ukรกzat online aktivitu -HTML - TEXT_NO_SERVERS || ลฝรกdnรฉ servery nenalezeny v databรกzi -HTML - TEXT_PLUGIN_INFORMATION || Informace o pluginu -HTML - TEXT_PREDICTED_RETENTION || Tato hodnota je odhad zaloลพenรฝ na pล™edchozรญch hrรกฤรญch -HTML - TEXT_SERVER_INSTRUCTIONS || Vypadรก to, ลพe plugin nenรญ nainstalovรกn na ลพรกdnรฉm serveru nebo nenรญ propojen s databรกzรญ. Podรญvejte se na tutoriรกl na wiki. -HTML - TEXT_VERSION || Byla vydรกna novรก verze a je dostupnรก ke staลพenรญ. -HTML - TITLE_30_DAYS || 30 dnรญ -HTML - TITLE_30_DAYS_AGO || pล™ed 30 dny -HTML - TITLE_ALL || Vลกe -HTML - TITLE_ALL_TIME || Celkovฤ› -HTML - TITLE_AS_NUMBERS || statistiky -HTML - TITLE_AVG_PING || Prลฏmฤ›rnรฝ ping -HTML - TITLE_BEST_PING || Nejlepลกรญ ping -HTML - TITLE_CALENDAR || Kalendรกล™ -HTML - TITLE_CONNECTION_INFO || Informace o pล™ipojenรญ -HTML - TITLE_COUNTRY || Stรกt -HTML - TITLE_CPU_RAM || CPU & RAM -HTML - TITLE_CURRENT_PLAYERBASE || Aktuรกlnรญ zรกkladna hrรกฤลฏ -HTML - TITLE_DISK || Mรญsto na disku -HTML - TITLE_GRAPH_DAY_BY_DAY || Den po dni -HTML - TITLE_GRAPH_HOUR_BY_HOUR || Hodina po hodinฤ› -HTML - TITLE_GRAPH_NETWORK_ONLINE_ACTIVITY || Online aktivita na proxy -HTML - TITLE_GRAPH_PUNCHCARD || ล tรญtek pro 30 dnรญ -HTML - TITLE_INSIGHTS || Postล™ehy za 30 dnรญ -HTML - TITLE_IS_AVAILABLE || je dostupnรฝ -HTML - TITLE_JOIN_ADDRESSES || Pล™ipojovacรญ IP -HTML - TITLE_LAST_24_HOURS || Poslednรญch 24 hodin -HTML - TITLE_LAST_30_DAYS || Poslednรญch 30 dnรญ -HTML - TITLE_LAST_7_DAYS || Poslednรญch 7 dnรญ -HTML - TITLE_LAST_CONNECTED || Poslednรญ pล™ipojenรญ -HTML - TITLE_LENGTH || Dรฉlka -HTML - TITLE_MOST_PLAYED_WORLD || Nejvรญc hranรฝ svฤ›t -HTML - TITLE_NETWORK || Proxy -HTML - TITLE_NETWORK_AS_NUMBERS || Statistiky proxy -HTML - TITLE_NOW || Nynรญ -HTML - TITLE_ONLINE_ACTIVITY || Online aktivita -HTML - TITLE_ONLINE_ACTIVITY_AS_NUMBERS || Online aktivita v ฤรญslech -HTML - TITLE_ONLINE_ACTIVITY_OVERVIEW || Pล™ehled online aktivity -HTML - TITLE_PERFORMANCE_AS_NUMBERS || Statistiky vรฝkonu -HTML - TITLE_PING || Ping -HTML - TITLE_PLAYER || Hrรกฤ -HTML - TITLE_PLAYER_OVERVIEW || Pล™ehled hrรกฤe -HTML - TITLE_PLAYERBASE_DEVELOPMENT || Vรฝvoj aktivity hrรกฤลฏ -HTML - TITLE_PVP_DEATHS || Poslednรญ PvP smrti -HTML - TITLE_PVP_KILLS || Poslednรญ PvP zabitรญ -HTML - TITLE_PVP_PVE_NUMBERS || PvP & PvE v ฤรญslech -HTML - TITLE_RECENT_KILLS || Poslednรญ zabitรญ -HTML - TITLE_RECENT_SESSIONS || Poslednรญ relace -HTML - TITLE_SEEN_NICKNAMES || Vidฤ›nรฉ pล™ezdรญvky -HTML - TITLE_SERVER || Server -HTML - TITLE_SERVER_AS_NUMBERS || Statistiky serveru -HTML - TITLE_SERVER_OVERVIEW || Pล™ehled serveru -HTML - TITLE_SERVER_PLAYTIME || Hernรญ ฤas serveru -HTML - TITLE_SERVER_PLAYTIME_30 || Hernรญ ฤas serveru za 30 dnรญ -HTML - TITLE_SESSION_START || Zapoฤatรก relace -HTML - TITLE_THEME_SELECT || Zvolenรฉ tรฉma -HTML - TITLE_TITLE_PLAYER_PUNCHCARD || ล tรญtky -HTML - TITLE_TPS || TPS -HTML - TITLE_TREND || Trend -HTML - TITLE_TRENDS || Trendy za 30 dnรญ -HTML - TITLE_VERSION || Verze -HTML - TITLE_WEEK_COMPARISON || Tรฝdennรญ srovnรกnรญ -HTML - TITLE_WORLD || Naฤtenรญ svฤ›ta -HTML - TITLE_WORLD_PLAYTIME || Hernรญ ฤas svฤ›ta -HTML - TITLE_WORST_PING || Nejhorลกรญ ping -HTML - TOTAL_ACTIVE_TEXT || Celkovรก aktivita -HTML - TOTAL_AFK || Celkem AFK -HTML - TOTAL_PLAYERS || Celkem hrรกฤลฏ -HTML - UNIQUE_CALENDAR || Unikรกtnรญ: -HTML - UNIT_CHUNKS || Chunkลฏ -HTML - UNIT_ENTITIES || Entit -HTML - UNIT_NO_DATA || ลฝรกdnรก data -HTML - UNIT_THE_PLAYERS || hrรกฤลฏ -HTML - USER_AND_PASS_NOT_SPECIFIED || Uลพivatel a heslo nespecifikovรกno -HTML - USER_DOES_NOT_EXIST || Uลพivatel neexistuje -HTML - USER_INFORMATION_NOT_FOUND || Registrace selhala, zkuste to znovu. (Kรณd expiruje za 15 minut) -HTML - USER_PASS_MISMATCH || Uลพivatel nebo heslo nesouhlasรญ -HTML - Version Change log || Zobrazit zmฤ›ny -HTML - Version Current || Verze ${0} -HTML - Version Download || Stรกhnout verzi Plan-${0}.jar -HTML - Version Update || Aktualizovat -HTML - Version Update Available || Novรก verze ${0} je dostupnรก! -HTML - Version Update Dev || Toto je vรฝvojรกล™skรก verze. -HTML - Version Update Info || Novรก verze je dostupnรก ke staลพenรญ. -HTML - WARNING_NO_GAME_SERVERS || Nฤ›kterรก data vyลพadujรญ, aby byla na hernรญ servery nainstalovรกna aplikace Plan. -HTML - WARNING_NO_GEOLOCATIONS || V konfiguraci je tล™eba povolit shromaลพฤovรกnรญ geolokacรญ (Pล™ijmฤ›te GeoLite2 EULA). -HTML - WARNING_NO_SPONGE_CHUNKS || Chunky nejsou na Sponge dostupnรฉ -HTML - WITH || S -HTML ERRORS - ACCESS_DENIED_403 || Pล™รญstup zamรญtnut -HTML ERRORS - AUTH_FAIL_TIPS_401 || - Ujistฤ›te se, ลพe jste zaregistrovanรญ s uลพivatelem s /plan register
- Zkontrolujte zda je jmรฉno a heslo sprรกvnรฉ
- Jmรฉno a heslo jsou citlivรก na velkรก/malรก pรญsmena

Pokud jste zapomnฤ›li heslo, zeptejte se ฤlena tรฝmu ke smazรกnรญ vaลกeho starรฉho uลพivatele ฤi novรฉ registraci. -HTML ERRORS - AUTHENTICATION_FAILED_401 || Ovฤ›ล™enรญ selhalo. -HTML ERRORS - FORBIDDEN_403 || Zakรกzรกno -HTML ERRORS - NO_SERVERS_404 || ลฝรกdnรฉ online servery k vykonรกnรญ ลพรกdosti. -HTML ERRORS - NOT_FOUND_404 || Nenalezeno -HTML ERRORS - NOT_PLAYED_404 || Hrรกฤ nebyl nenalezen. -HTML ERRORS - PAGE_NOT_FOUND_404 || Strรกnka neexistuje. -HTML ERRORS - UNAUTHORIZED_401 || Neautorizovรกno -HTML ERRORS - UNKNOWN_PAGE_404 || Ujistฤ›te se, ลพe pล™istupujete na odkaz poskytnutรฝ pล™รญkazem, Pล™รญklad:

/player/{uuid/name}
/server/{uuid/name/id}

-HTML ERRORS - UUID_404 || Hrรกฤskรฉ UUID nebylo nalezeno v databรกzi. -In Depth Help - /plan db || Pouลพรญt jinรฉ subpล™รญkazy databรกze ke zmฤ›nฤ› dat -In Depth Help - /plan db backup || Pouลพitรญ SQLite k zรกlohovรกnรญ cรญlovรฉ databรกze do souboru. -In Depth Help - /plan db clear || Smaลพe vลกechny tabulky Plan, ฤรญmลพ smaลพe vลกechna Plan-data v procesu. -In Depth Help - /plan db hotswap || Reload pluginu s ostatnรญ databรกzรญ a zmฤ›nรญ config pro sprรกvnost. -In Depth Help - /plan db move || Nรกhradรญ obsah v databรกzi s obsahem z jinรฉ databรกze. -In Depth Help - /plan db remove || Smaลพe vลกechna data spojenรก s hrรกฤem z Aktuรกlnรญ databรกze. -In Depth Help - /plan db restore || Pouลพije SQLite soubor zรกlohu k pล™epsรกnรญ dat cรญlovรฉ databรกze. -In Depth Help - /plan db uninstalled || Oznaฤรญ server v Plan databรกzi jako odinstalovanรฝ, takลพe se neukรกลพe v server ลพรกdostech. -In Depth Help - /plan disable || Vypne plugin ฤi jeho ฤรกst do dalลกรญho reloadu/restartu. -In Depth Help - /plan export || Vykonรก export k lokaci definovanรฉ v configu. -In Depth Help - /plan import || Vykonรก import k naฤtenรญ dat z databรกze. -In Depth Help - /plan info || Zobrazรญ aktuรกlnรญ status pluginu. -In Depth Help - /plan ingame || Ukรกลพe nฤ›kterรฉ informace o hrรกฤi ve hล™e. -In Depth Help - /plan json || Dovolรญ stรกhnout hrรกฤskรก data v json formรกtu. Vลกechna. -In Depth Help - /plan logout || Give username argument to log out another user from the panel, give * as argument to log out everyone. -In Depth Help - /plan network || Zรญskรกnรญ odkazu k /network page, pouze na sรญtรญch. -In Depth Help - /plan player || Zรญskรกnรญ odkazu k /player page specifickรฉho hrรกฤe, ฤi aktuรกlnรญho. -In Depth Help - /plan players || Zรญskรกnรญ odkazu k /players page k zobrazenรญ seznamu hrรกฤลฏ. -In Depth Help - /plan register || Pouลพijte bez argumentลฏ k odkazu na strรกnku registrace. Pouลพijte --code [kod] pro registraci k zรญskรกnรญ uลพivatele. -In Depth Help - /plan reload || Vypnout a zapnout plugin k obnovฤ› jakรฝchkoli zmฤ›n v configu. -In Depth Help - /plan search || Zobrazit seznam vลกech hrรกฤลฏ souhlasรญsรญch s danรฝm jmรฉnem. -In Depth Help - /plan server || Zรญskat odkaz k /server page specifickรฉho serveru, ฤi aktuรกlnรญho serveru, pokud nebyly dรกny argumenty. -In Depth Help - /plan servers || Seznam id, jmen a uuid serverลฏ v databรกzi. -In Depth Help - /plan unregister || Pouลพijte bez argumentลฏ k odregistraci sebe, nebo zadejte jmรฉno jinรฉho uลพivatele. -In Depth Help - /plan users || Tabulka uลพivatelลฏ webu. -Manage - Confirm Overwrite || Data v ${0} budou pล™epsรกna! -Manage - Confirm Removal || Data v ${0} budou smazรกna! -Manage - Fail || > ยงcNฤ›co se pokazilo: ${0} -Manage - Fail File not found || > ยงcNenalezen soubor v ${0} -Manage - Fail Incorrect Database || > ยงc'${0}' nenรญ podporovanรก databรกze. -Manage - Fail No Exporter || ยงeExporter '${0}' neexistuje -Manage - Fail No Importer || ยงeImporter '${0}' neexistuje -Manage - Fail No Server || Nenalezen ลพรกdnรฝ server s danรฝmi parametry. -Manage - Fail Same Database || > ยงcNelze spravovat z a do stejnรฉ databรกze! -Manage - Fail Same server || Nelze oznaฤit tento server jako odinstalovรกn (Jste na nฤ›m) -Manage - Fail, Confirmation || > ยงcPล™idejte '-a' argument k potvrzenรญ provedenรญ: ${0} -Manage - List Importers || Importery: -Manage - Progress || ${0} / ${1} zpracovรกvรกnรญ.. -Manage - Remind HotSwap || ยงeNezapomeลˆte pล™ehodit na novou databรกzi (/plan db hotswap ${0}) & reload pluginu. -Manage - Start || > ยง2Zpracovรกvรกm data.. -Manage - Success || > ยงaรšspฤ›ch! -Negative || Ne -Positive || Ano -Today || 'Dnes' -Unavailable || Nedostupnรฉ -Unknown || Neznรกmรฉ -Version - DEV || Toto je vรฝvojรกล™skรก verze. -Version - Latest || Pouลพรญvรกte poslednรญ verzi. -Version - New || Novรฉ vydรกnรญ (${0}) je dostupnรฉ ${1} -Version - New (old) || Novรก verze je dostupnรก na ${0} -Version FAIL - Read info (old) || Selhalo zjiลกtฤ›nรญ ฤรญsla nejnovฤ›jลกรญ verze -Version FAIL - Read versions.txt || Informace o verzi z Github/versions.txt nemลฏลพe bรฝt nalezena. -Web User Listing || ยง2${0} ยง7: ยงf${1} -WebServer - Notify HTTP || WebServer: Nenรญ certifikรกt -> Pouลพรญvรกm HTTP-server pro visualizaci. -WebServer - Notify HTTP User Auth || WebServer: Autorizace uลพivatelลฏ vypnutรก! (Nenรญ bezpeฤnรฉ skrz HTTP) -WebServer - Notify HTTPS User Auth || WebServer: Autorizace uลพivatele vypnuta! (Vypnuto v configu) -Webserver - Notify IP Whitelist || Webserver: IP Whitelist je zapnutรฝ. -Webserver - Notify IP Whitelist Block || Webserver: ${0} byl odmรญtnut pล™รญstup na '${1}'. (nenรญ na whitelistu) -WebServer - Notify no Cert file || WebServer: Certifikaฤnรญ KeyStore soubor nenalezen: ${0} -WebServer - Notify Using Proxy || WebServer: Proxy-mode HTTPS zapnut, ujistฤ›te se, ลพe reverse-proxy je funkฤรญ s HTTPS a Plan Alternative_IP.Address mรญล™รญ na proxy -WebServer FAIL - EOF || WebServer: EOF pล™i ฤtenรญ souboru Certifikรกtu. (Zkontrolujte, zda soubor nenรญ prรกzdnรฝ) -WebServer FAIL - Port Bind || WebServer nebyl รบspฤ›ลกnฤ› spuลกtฤ›n. Je port (${0}) jiลพ pouลพรญvรกn? -WebServer FAIL - SSL Context || WebServer: SSL Context spuลกtฤ›nรญ selhalo. -WebServer FAIL - Store Load || WebServer: SSL Certificate naฤรญtรกnรญ selhalo. -Yesterday || 'Vฤera' diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_CS.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_CS.yml new file mode 100644 index 000000000..8b6118db9 --- /dev/null +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_CS.yml @@ -0,0 +1,669 @@ +403AccessDenied: "Pล™รญstup zamรญtnut" +command: + argument: + backupFile: + description: "Nรกzev souboru zรกlohy (citlivรฉ na velikost pรญsmen)" + name: "zรกloha" + code: + description: "Kรณd pouลพitรฝ k dokonฤenรญ registrace.." + name: "${code}" + dbBackup: + description: "Typ databรกze k zรกlohovรกnรญ. Pouลพita aktuรกlnรญ databรกze, pokud nenรญ specifikovรกno." + dbRestore: + description: "Typ databรกze kam obnovit. Aktuรกlnรญ databรกze je pouลพita, pokud nenรญ specifikovรกno." + dbTypeHotswap: + description: "Typ databรกze k pouลพitรญ." + dbTypeMoveFrom: + description: "Typ databรกze odkud pล™esunout data." + dbTypeMoveTo: + description: "Typ databรกze kam pล™esunout data. Nemลฏลพe bรฝt stejnรฉ jako minulรฉ." + dbTypeRemove: + description: "Typ databรกze ze kterรฉ smazat vลกechna data." + exportKind: "druh exportu" + feature: + description: "Nรกzev funkce k vypnutรญ: ${0}" + name: "funkce" + importKind: "druh importu" + nameOrUUID: + description: "Jmรฉno ฤi UUID hrรกฤe" + name: "jmeno/uuid" + removeDescription: "Identifikรกtor pro hrรกฤe, kterรฝ bude smazรกn z aktuรกlnรญ databรกze." + server: + description: "Jmรฉno, ID nebo UUID serveru" + name: "server" + subcommand: + description: "Pouลพijte pล™รญkaz bez sub pล™รญkazu k pomoci." + name: "subpล™รญkaz" + username: + description: "Pล™ezdรญvka jinรฉho uลพivatele. Pokud nenรญ specifikovรกno, je pouลพit linknutรฝ uลพivatel." + name: "uลพivatel" + confirmation: + accept: "Pล™ijmout" + cancelNoChanges: "Zruลกeno. ลฝรกdnรก data nebyla zmฤ›nฤ›na." + cancelNoUnregister: "Zruลกeno. '${0}' nebyl odregistrovรกn" + confirm: "Potvrdit: " + dbClear: "Chystรกte se smazat vลกechna Plan data v ${0}" + dbOverwrite: "Chystรกte se pล™epsat data v Plan ${0} s daty v ${1}" + dbRemovePlayer: "Chystรกte se smazat data ${0} z ${1}" + deny: "Zruลกit" + expired: "Potvrzenรญ vyprลกelo, pouลพijte pล™รญkaz znovu" + unregister: "Chystรกte se odregistrovat '${0}' linknutรฉho s ${1}" + database: + creatingBackup: "Vytvรกล™รญm soubor zรกlohy '${0}.db' s obsahem z ${1}" + failDbNotOpen: "ยงcDatabรกze je ${0} - Zkuste to znovu pozdฤ›ji." + manage: + confirm: "> ยงcPล™idejte '-a' argument k potvrzenรญ provedenรญ: ${0}" + confirmOverwrite: "Data v ${0} budou pล™epsรกna!" + confirmPartialRemoval: "Join Address Data for Server ${0} in ${1} will be removed!" + confirmRemoval: "Data v ${0} budou smazรกna!" + fail: "> ยงcNฤ›co se pokazilo: ${0}" + failFileNotFound: "> ยงcNenalezen soubor v ${0}" + failIncorrectDB: "> ยงc'${0}' nenรญ podporovanรก databรกze." + failNoServer: "Nenalezen ลพรกdnรฝ server s danรฝmi parametry." + failSameDB: "> ยงcNelze spravovat z a do stejnรฉ databรกze!" + failSameServer: "Nelze oznaฤit tento server jako odinstalovรกn (Jste na nฤ›m)" + hotswap: "ยงeNezapomeลˆte pล™ehodit na novou databรกzi (/plan db hotswap ${0}) & reload pluginu." + importers: "Importery:" + preparing: "Preparing.." + progress: "${0} / ${1} zpracovรกvรกnรญ.." + start: "> ยง2Zpracovรกvรกm data.." + success: "> ยงaรšspฤ›ch!" + playerRemoval: "Maลพu data ${0} z ${1}.." + removal: "Maลพu Plan data z ${0}.." + serverUninstalled: "ยงaPokud je server stรกle nainstalovanรฝ, automaticky se ukรกลพe jako instalovanรฝ v databรกzi." + unregister: "Odregistrovรกnรญ '${0}'.." + warnDbNotOpen: "ยงeDatabรกze je ${0} - Toto mลฏลพe trvat dรฉle.." + write: "Zapisuji do ${0}.." + fail: + emptyString: "Vyhledรกvacรญ pojem nemลฏลพe bรฝt prรกzdnรฝ" + invalidArguments: "Pล™ijรญmรก nรกsledujรญcรญ jako ${0}: ${1}" + invalidUsername: "ยงcUลพivatel nemรก UUID." + missingArguments: "ยงcPotล™ebnรฉ argumenty (${0}) ${1}" + missingFeature: "ยงeDefinujte funkci k vypnutรญ! (aktuรกlnฤ› podporuje ${0})" + missingLink: "Uลพivatel nenรญ linknutรฝ s vaลกรญm รบฤtem a nemรกte prรกvo mazat ostatnรญ รบฤty uลพivatelลฏ." + noPermission: "ยงcNa toto nemรกte potล™ebnรก prรกva." + onAccept: "Chyba pro pล™ijmutou akci pล™i vykonรกnรญ: ${0}" + onDeny: "Chyba pro odmรญtnout akci pล™i vykonรกnรญ: ${0}" + playerNotFound: "Hrรกฤ '${0}' nebyl nalezen, nebo nemรก UUID." + playerNotInDatabase: "Hrรกฤ '${0}' nebyl nalezen v databรกzi." + seeConfig: "zhlรฉdnฤ›te '${0}' v config.yml" + serverNotFound: "Server '${0}' nebyl nalezen v databรกzi." + tooManyArguments: "ยงcJe zapotล™ebรญ jednotnรฝ argument ${1}" + unknownUsername: "ยงcUลพivatel nebyl vidฤ›n na tomto serveru" + webUserExists: "ยงcUลพivatel jiลพ existuje!" + webUserNotFound: "ยงcUลพivatel neexistuje!" + footer: + help: "ยง7Podrลพte kurzor nad pล™รญkazem nebo argumentem ฤi pouลพijte '/${0} ?' pro vรญce informacรญ o nich." + general: + failNoExporter: "ยงeExporter '${0}' neexistuje" + failNoImporter: "ยงeImporter '${0}' neexistuje" + featureDisabled: "ยงaDoฤasnฤ› vypnuto '${0}' do dalลกรญho reloadu pluginu." + noAddress: 'ยงeลฝรกdnรก adresa nebyla dostupnรก - pouลพรญvรกm localhost jako rezervu. Nastavte "Alternative_IP".' + noWebuser: "Pravdฤ›podobnฤ› nemรกte uลพivatele webu, pouลพijte /plan register " + notifyWebUserRegister: "Registrovรกn novรฝ uลพivatel: '${0}' รšroveลˆ prรกv: ${1}" + pluginDisabled: "ยงaPlan systรฉmy jsou nynรญ vypnuty. Mลฏลพete stรกle pouลพรญt /planbungee reload pro restart pluginu." + reloadComplete: "ยงaReload Dokonฤen" + reloadFailed: "ยงcNฤ›co se pokazilo pล™i reloadu pluginu, doporuฤejeme restart serveru." + successWebUserRegister: "ยงaรšspฤ›ลกnฤ› pล™idรกn novรฝ uลพivatel (${0})! Mลฏลพete zobrazit web panel na nรกsledujรญcรญm odkazu." + webPermissionLevels: ">\ยง70: Pล™รญstup ke vลกem strรกnkรกm\ยง71: Pล™รญstup k '/players' a vลกem hrรกฤskรฝm strรกnkรกm\ยง72: Pล™รญstup k hrรกฤskรฉ strรกnce se stejnรฝm jmรฉnem jako web uลพivatel\ยง73+: ลฝรกdnรก prรกva" + webUserList: " ยง2${0} ยง7: ยงf${1}" + header: + analysis: "> ยง2Vรฝsledky Analรฝzy" + help: "> ยง2/${0} Pomoc" + info: "> ยง2Analรฝza Hrรกฤลฏ" + inspect: "> ยง2Hrรกฤ: ยงf${0}" + network: "> ยง2Strรกnka Sรญtฤ›" + players: "> ยง2Hrรกฤลฏ" + search: "> ยง2${0} Vรฝsledky pro ยงf${1}ยง2:" + serverList: "id::jmรฉno::uuid::version" + servers: "> ยง2Servery" + webUserList: "uลพivatel::spojen s::รบroveลˆ prรกv" + webUsers: "> ยง2${0} Web uลพivatelรฉ" + help: + database: + description: "Spravovat Plan databรกzi" + inDepth: "Pouลพรญt jinรฉ subpล™รญkazy databรกze ke zmฤ›nฤ› dat" + dbBackup: + description: "Zรกlohovat data z databรกze do souboru" + inDepth: "Pouลพitรญ SQLite k zรกlohovรกnรญ cรญlovรฉ databรกze do souboru." + dbClear: + description: "Smazat Vล ECHNA Plan data z databรกze" + inDepth: "Smaลพe vลกechny tabulky Plan, ฤรญmลพ smaลพe vลกechna Plan-data v procesu." + dbHotswap: + description: "Rychlรก zmฤ›na databรกze" + inDepth: "Reload pluginu s ostatnรญ databรกzรญ a zmฤ›nรญ config pro sprรกvnost." + dbMove: + description: "Pล™esun dat mezi databรกzemi" + inDepth: "Nรกhradรญ obsah v databรกzi s obsahem z jinรฉ databรกze." + dbRemove: + description: "Smazat data hrรกฤe z aktuรกlnรญ databรกze" + inDepth: "Smaลพe vลกechna data spojenรก s hrรกฤem z Aktuรกlnรญ databรกze." + dbRestore: + description: "Obnova dat ze souboru do databรกze" + inDepth: "Pouลพije SQLite soubor zรกlohu k pล™epsรกnรญ dat cรญlovรฉ databรกze." + dbUninstalled: + description: "Nastavit server jako odinstalovanรฝ v databรกzi." + inDepth: "Oznaฤรญ server v Plan databรกzi jako odinstalovanรฝ, takลพe se neukรกลพe v server ลพรกdostech." + disable: + description: "Vypne plugin ฤi jeho ฤรกst" + inDepth: "Vypne plugin ฤi jeho ฤรกst do dalลกรญho reloadu/restartu." + export: + description: "Export html ฤi json souborลฏ manuรกlnฤ›" + inDepth: "Vykonรก export k lokaci definovanรฉ v configu." + import: + description: "Import dat" + inDepth: "Vykonรก import k naฤtenรญ dat z databรกze." + info: + description: "Zjistit verzi Plan" + inDepth: "Zobrazรญ aktuรกlnรญ status pluginu." + ingame: + description: "Ukรกzat Hrรกฤskรฉ info ve hล™e" + inDepth: "Ukรกลพe nฤ›kterรฉ informace o hrรกฤi ve hล™e." + json: + description: "Ukรกzat json Hrรกฤskรฝch ฤistรฝch dat." + inDepth: "Dovolรญ stรกhnout hrรกฤskรก data v json formรกtu. Vลกechna." + logout: + description: "Log out other users from the panel." + inDepth: "Vloลพte argument pล™ezdรญvky hrรกฤe k odhlรกลกenรญ jinรฉho uลพivatele, pouลพijte * pro odhlรกลกenรญ vลกech." + migrateToOnlineUuids: + description: "Migrate offline uuid data to online uuids" + network: + description: "Ukรกzat strรกnku sรญtฤ›" + inDepth: "Zรญskรกnรญ odkazu k /network page, pouze na sรญtรญch." + player: + description: "Ukรกzat strรกnku hrรกฤe" + inDepth: "Zรญskรกnรญ odkazu k /player page specifickรฉho hrรกฤe, ฤi aktuรกlnรญho." + players: + description: "Ukรกzat strรกnku hrรกฤลฏ" + inDepth: "Zรญskรกnรญ odkazu k /players page k zobrazenรญ seznamu hrรกฤลฏ." + register: + description: "Registrovat web uลพivatele" + inDepth: "Pouลพijte bez argumentลฏ k odkazu na strรกnku registrace. Pouลพijte --code [kod] pro registraci k zรญskรกnรญ uลพivatele." + reload: + description: "Restartuje plugin Plan" + inDepth: "Vypnout a zapnout plugin k obnovฤ› jakรฝchkoli zmฤ›n v configu." + removejoinaddresses: + description: "Remove join addresses of a specified server" + search: + description: "Hledat podle jmรฉna hrรกฤe" + inDepth: "Zobrazit seznam vลกech hrรกฤลฏ souhlasรญsรญch s danรฝm jmรฉnem." + server: + description: "Ukรกzat strรกnku serveru" + inDepth: "Zรญskat odkaz k /server page specifickรฉho serveru, ฤi aktuรกlnรญho serveru, pokud nebyly dรกny argumenty." + servers: + description: "Seznam serverลฏ v databรกzi" + inDepth: "Seznam id, jmen a uuid serverลฏ v databรกzi." + unregister: + description: "Odregistrovat uลพivatele z Plan webu" + inDepth: "Pouลพijte bez argumentลฏ k odregistraci sebe, nebo zadejte jmรฉno jinรฉho uลพivatele." + users: + description: "Zobrazit seznam uลพivatelลฏ webu" + inDepth: "Tabulka uลพivatelลฏ webu." + ingame: + activePlaytime: " ยง2Aktivnรญ hernรญ ฤas: ยงf${0}" + activityIndex: " ยง2Index Aktivity: ยงf${0} | ${1}" + afkPlaytime: " ยง2AFK ฤas: ยงf${0}" + deaths: " ยง2Smrti: ยงf${0}" + geolocation: " ยง2Pล™ihlรกลกen z: ยงf${0}" + lastSeen: " ยง2Naposledy vidฤ›n: ยงf${0}" + longestSession: " ยง2Nejdelลกรญ relace: ยงf${0}" + mobKills: " ยง2Zabitรญ mobลฏ: ยงf${0}" + playerKills: " ยง2Zabitรญ hrรกฤลฏ: ยงf${0}" + playtime: " ยง2Hernรญ ฤas: ยงf${0}" + registered: " ยง2Registrovรกn: ยงf${0}" + timesKicked: " ยง2Poฤet vykopnutรญ: ยงf${0}" + link: + clickMe: "Klikni na mฤ›" + link: "Odkaz" + network: "Strรกnka sรญtฤ›: " + noNetwork: "Server nenรญ pล™ipojen k sรญti. Odkaz odkazuje na server strรกnku." + player: "Strรกnka hrรกฤe: " + playerJson: "JSON hrรกฤ:" + players: "Strรกnka hrรกฤลฏ: " + register: "Strรกnka registrace: " + server: "Strรกnka serveru: " + subcommand: + info: + database: " ยง2Aktivnรญ databรกze: ยงf${0}" + proxy: " ยง2Pล™ipojen na Proxy: ยงf${0}" + update: " ยง2Dostupnรก aktualizace: ยงf${0}" + version: " ยง2Verze: ยงf${0}" +generic: + noData: "ลฝรกdnรก data" +html: + button: + nightMode: "Noฤnรญ mรณd" + calendar: + new: "Novรฉ:" + unique: "Unikรกtnรญ:" + description: + newPlayerRetention: "Tato hodnota je odhad dle pล™edchozรญch hrรกฤลฏ." + noGameServers: "Nฤ›kterรก data vyลพadujรญ, aby byla na hernรญ servery nainstalovรกna aplikace Plan." + noGeolocations: "V konfiguraci je tล™eba povolit shromaลพฤovรกnรญ geolokacรญ (Pล™ijmฤ›te GeoLite2 EULA)." + noServerOnlinActivity: "ลฝรกdnรฝ server pro kterรฝ lze ukรกzat online aktivitu" + noServers: "ลฝรกdnรฉ servery nenalezeny v databรกzi" + noServersLong: 'Vypadรก to, ลพe plugin nenรญ nainstalovรกn na ลพรกdnรฉm serveru nebo nenรญ propojen s databรกzรญ. Podรญvejte se na tutoriรกl na wiki.' + noSpongeChunks: "Chunky nejsou na Sponge dostupnรฉ" + predictedNewPlayerRetention: "Tato hodnota je odhad zaloลพenรฝ na pล™edchozรญch hrรกฤรญch" + error: + 401Unauthorized: "Neautorizovรกno" + 403Forbidden: "Zakรกzรกno" + 404NotFound: "Nenalezeno" + 404PageNotFound: "Strรกnka neexistuje." + 404UnknownPage: "Ujistฤ›te se, ลพe pล™istupujete na odkaz poskytnutรฝ pล™รญkazem, Pล™รญklad:

/player/{uuid/name}
/server/{uuid/name/id}

" + UUIDNotFound: "Hrรกฤskรฉ UUID nebylo nalezeno v databรกzi." + auth: + dbClosed: "Databรกze nenรญ otevล™enรก, ovฤ›ล™te status databรกze s /plan info" + emptyForm: "Uลพivatel a heslo nespecifikovรกno" + expiredCookie: "Uลพivatelskรฉ cookie expirovalo" + generic: "Autentifikace neuspฤ›ลกnรก kvลฏli chybฤ›" + loginFailed: "Uลพivatel nebo heslo nesouhlasรญ" + noCookie: "Cookies uลพivatele nejsou k dispozici." + registrationFailed: "Registrace selhala, zkuste to znovu. (Kรณd expiruje za 15 minut)" + userNotFound: "Uลพivatel neexistuje" + authFailed: "Ovฤ›ล™enรญ selhalo." + authFailedTips: "- Ujistฤ›te se, ลพe jste zaregistrovanรญ s uลพivatelem s /plan register
- Zkontrolujte zda je jmรฉno a heslo sprรกvnรฉ
- Jmรฉno a heslo jsou citlivรก na velkรก/malรก pรญsmena

Pokud jste zapomnฤ›li heslo, zeptejte se ฤlena tรฝmu ke smazรกnรญ vaลกeho starรฉho uลพivatele ฤi novรฉ registraci." + noServersOnline: "ลฝรกdnรฉ online servery k vykonรกnรญ ลพรกdosti." + playerNotSeen: "Hrรกฤ nebyl nenalezen." + serverNotSeen: "Server doesn't exist" + generic: + none: "ลฝรกdnรฝ" + label: + active: "Aktivnรญ" + activePlaytime: "Aktivnรญ hernรญ ฤas" + activityIndex: "Index aktivity" + afk: "AFK" + afkTime: "AFK ฤas" + all: "Vลกe" + allTime: "Celkovฤ›" + alphabetical: "Alphabetical" + apply: "Apply" + asNumbers: "statistiky" + average: "Prลฏmฤ›r" + averageActivePlaytime: "Prลฏmฤ›rnรก hernรญ aktivita" + averageAfkTime: "Prลฏmฤ›rnรฝ AFK ฤas" + averageChunks: "Prลฏmฤ›r chunkลฏ" + averageCpuUsage: "Average CPU Usage" + averageEntities: "Prลฏmฤ›r entit" + averageKdr: "Prลฏmฤ›r KDR" + averageMobKdr: "Prลฏmฤ›r Mob KDR" + averagePing: "Prลฏmฤ›rnรฝ ping" + averagePlayers: "Average Players" + averagePlaytime: "Prลฏmฤ›r hernรญ doby" + averageRamUsage: "Average RAM Usage" + averageServerDowntime: "Average Downtime / Server" + averageSessionLength: "Prลฏmฤ›rnรก dรฉlka relace" + averageSessions: "Prลฏmฤ›rnรก relace" + averageTps: "Prลฏmฤ›r TPS" + averageTps7days: "Average TPS (7 days)" + banned: "Zabanovรกn" + bestPeak: "Nejvรญce hrรกฤลฏ" + bestPing: "Nejlepลกรญ ping" + calendar: " Kalendรกล™" + comparing7days: "Srovnรกnรญ poslednรญch 7 dnรญ" + connectionInfo: "Informace o pล™ipojenรญ" + country: "Stรกt" + cpu: "CPU" + cpuRam: "CPU & RAM" + cpuUsage: "CPU Usage" + currentPlayerbase: "Aktuรกlnรญ zรกkladna hrรกฤลฏ" + currentUptime: "Current Uptime" + dayByDay: "Den po dni" + dayOfweek: "Den tรฝdne" + deadliestWeapon: "Nejsmrtelnฤ›jลกรญ PvP zbraลˆ" + deaths: "Smrti" + disk: "Mรญsto na disku" + diskSpace: "Volnรฉ mรญsto na disku" + downtime: "Offline doba" + duringLowTps: "Pล™i nรญzkรฝch TPS:" + entities: "Entity" + favoriteServer: "Oblรญbenรฝ server" + firstSession: "Prvnรญ relace" + firstSessionLength: + average: "Average first session length" + median: "Median first session length" + geoProjection: + dropdown: "Select projection" + equalEarth: "Equal Earth" + mercator: "Mercator" + miller: "Miller" + ortographic: "Ortographic" + geolocations: "Geolokace" + hourByHour: "Hodina po hodinฤ›" + inactive: "Neaktivnรญ" + indexInactive: "Neaktivnรญ" + indexRegular: "Pravidelnรฝ" + information: "INFORMACE" + insights: "Insights" + insights30days: "Postล™ehy za 30 dnรญ" + irregular: "Nepravidelnรฝ" + joinAddress: "Join Address" + joinAddresses: "Pล™ipojovacรญ IP" + kdr: "KDR" + killed: "Zabit" + last24hours: "Poslednรญch 24 hodin" + last30days: "Poslednรญch 30 dnรญ" + last7days: "Poslednรญch 7 dnรญ" + lastConnected: "Poslednรญ pล™ipojenรญ" + lastPeak: "Naposledy nejvรญce hrรกฤลฏ" + lastSeen: "Naposledy vidฤ›n" + latestJoinAddresses: "Latest Join Addresses" + length: " Dรฉlka" + links: "ODKAZY" + loadedChunks: "Naฤtenรฉ chunky" + loadedEntities: "Naฤtenรฉ entity" + loneJoins: "Samotnรก pล™ipojenรญ" + loneNewbieJoins: "Samotnรก pล™ipojenรญ novรกฤkลฏ" + longestSession: "Nejdelลกรญ relace" + lowTpsSpikes: "Nejniลพลกรญ TPS" + lowTpsSpikes7days: "Low TPS Spikes (7 days)" + maxFreeDisk: "Max. volnรฉho disku" + medianSessionLength: "Median Session Length" + minFreeDisk: "Min. volnรฉho disku" + mobDeaths: "Smrti zpลฏsobenรฉ moby" + mobKdr: "Mob KDR" + mobKills: "Zabitรญ mobovรฉ" + mostActiveGamemode: "Nejvรญce aktivnรญ mรณd" + mostPlayedWorld: "Nejvรญc hranรฝ svฤ›t" + name: "Jmรฉno" + network: "Proxy" + networkAsNumbers: "Statistiky proxy" + networkOnlineActivity: "Online aktivita na proxy" + networkOverview: "Pล™ehled proxy" + networkPage: "Strรกnka sรญtฤ›" + new: "Novรฝ" + newPlayerRetention: "Udrลพenรญ novรฝch hrรกฤลฏ" + newPlayers: "Novรญ hrรกฤi" + newPlayers7days: "New Players (7 days)" + nickname: "Pล™ezdรญvka" + noDataToDisplay: "No Data to Display" + now: "Nynรญ" + onlineActivity: "Online aktivita" + onlineActivityAsNumbers: "Online aktivita v ฤรญslech" + onlineOnFirstJoin: "Online hrรกฤi pล™i prvnรญm pล™ipojenรญ" + operator: "Operรกtor" + overview: "Pล™ehled" + perDay: "/ Den" + perPlayer: "na hrรกฤe" + perRegularPlayer: "na pravidelnรฉho hrรกฤe" + performance: "Vรฝkon" + performanceAsNumbers: "Statistiky vรฝkonu" + ping: "Ping" + player: "Hrรกฤ" + playerDeaths: "Smrti zpลฏsobenรฉ hrรกฤi" + playerKills: "Zabitรญ hrรกฤi" + playerList: "Seznam hrรกฤลฏ" + playerOverview: "Pล™ehled hrรกฤe" + playerPage: "Strรกnka hrรกฤe" + playerRetention: "Player Retention" + playerbase: "Hrรกฤi" + playerbaseDevelopment: "Vรฝvoj aktivity hrรกฤลฏ" + playerbaseOverview: "Playerbase Overview" + players: "Hrรกฤi" + playersOnline: "Hrรกฤi online" + playersOnlineNow: "Players Online (Now)" + playersOnlineOverview: "Pล™ehled online aktivity" + playtime: "Hernรญ ฤas" + plugins: "Pluginy" + pluginsOverview: "Plugins Overview" + punchcard: "ล tรญtky" + punchcard30days: "ล tรญtek pro 30 dnรญ" + pvpPve: "PvP & PvE" + pvpPveAsNumbers: "PvP & PvE v ฤรญslech" + query: "Vytvoล™it dotaz" + quickView: "Rychlรฉ zobrazenรญ" + ram: "RAM" + ramUsage: "RAM Usage" + recentKills: "Poslednรญ zabitรญ" + recentPvpDeaths: "Poslednรญ PvP smrti" + recentPvpKills: "Poslednรญ PvP zabitรญ" + recentSessions: "Poslednรญ relace" + registered: "Registrovรกn" + registeredPlayers: "Registrovanรญ hrรกฤi" + regular: "Pravidelnรฝ" + regularPlayers: "Pravidelnรญ hrรกฤi" + relativeJoinActivity: "Relativnรญ aktivita pล™ipojenรญ" + secondDeadliestWeapon: "2. PvP Zbraลˆ" + seenNicknames: "Vidฤ›nรฉ pล™ezdรญvky" + server: "Server" + serverAnalysis: "Analรฝza serveru" + serverAsNumberse: "Statistiky serveru" + serverCalendar: "Server Calendar" + serverDowntime: "Server offline" + serverOccupied: "Server plnรฝ" + serverOverview: "Pล™ehled serveru" + serverPage: "Strรกnka serveru" + serverPlaytime: "Hernรญ ฤas serveru" + serverPlaytime30days: "Hernรญ ฤas serveru za 30 dnรญ" + serverSelector: "Server selector" + servers: "Servery" + serversTitle: "SERVERY" + session: "Relace" + sessionCalendar: "Session Calendar" + sessionEnded: " Ukonฤeno" + sessionMedian: "Stล™ednรญ hodnota relacรญ" + sessionStart: "Zapoฤatรก relace" + sessions: "Relace" + sortBy: "Sort By" + stacked: "Stacked" + themeSelect: "Zvolenรฉ tรฉma" + thirdDeadliestWeapon: "3. PvP Zbraลˆ" + thirtyDays: "30 dnรญ" + thirtyDaysAgo: "pล™ed 30 dny" + timesKicked: "Poฤet vykopnutรญ" + toMainPage: "Zpฤ›t na hlavnรญ strรกnku" + total: "Total" + totalActive: "Celkovรก aktivita" + totalAfk: "Celkem AFK" + totalPlayers: "Hrรกฤลฏ celkem" + totalPlayersOld: "Celkem hrรกฤลฏ" + totalPlaytime: "Hernรญ doba celkem" + totalServerDowntime: "Total Server Downtime" + tps: "TPS" + trend: "Trend" + trends30days: "Trendy za 30 dnรญ" + uniquePlayers: "Unikรกtnรญ hrรกฤi" + uniquePlayers7days: "Unique Players (7 days)" + veryActive: "Velmi aktivnรญ" + weekComparison: "Tรฝdennรญ srovnรกnรญ" + weekdays: "'Pondฤ›lรญ', 'รšterรฝ', 'Stล™eda', 'ฤŒtvrtek', 'Pรกtek', 'Sobota', 'Nedฤ›le'" + world: "Naฤtenรญ svฤ›ta" + worldPlaytime: "Hernรญ ฤas svฤ›ta" + worstPing: "Nejhorลกรญ ping" + login: + failed: "Pล™ihlaลกovรกnรญ selhalo: " + forgotPassword: "Zapomnฤ›l jste heslo?" + forgotPassword1: "Zapomnฤ›l jste heslo? Odregistruj se a registruj se znovu." + forgotPassword2: "Pro odstranฤ›nรญ aktuรกlnรญho uลพivatele pouลพij nรกsledujรญcรญ pล™รญkaz ve hล™e:" + forgotPassword3: "Nebo pouลพij konzoli:" + forgotPassword4: "Po vykonรกnรญ pล™รญkazu, " + login: "Pล™ihlรกsit se" + logout: "Odhlรกsit se" + password: "Heslo" + register: "Vytvoล™te si รบฤet!" + username: "Uลพivatelskรฉ jmรฉno" + modal: + info: + bugs: "Nahlรกsit potรญลพe" + contributors: + bugreporters: "& Nahlรกลกenรญ bugu!" + code: "pล™ispฤ›vatel kรณdu" + donate: "Extra speciรกlnรญ podฤ›kovรกnรญ tฤ›m, kteล™รญ penฤ›ลพnฤ› pomohli vรฝvoji." + text: 'Nadรกle nรกsledujรญcรญ skvฤ›lรญ lidรฉ, kteล™รญ pล™ispฤ›li:' + translator: "pล™ekladaฤ" + developer: "je vyvรญjena od" + discord: "Obecnรก podpora na Discordu" + license: "Player Analytics je vyvรญjeno a licencovรกno pod" + metrics: "bStats Metrics" + text: "Informace o pluginu" + wiki: "Plan Wiki, Tutoriรกly & Dokumentace" + version: + available: "je dostupnรฝ" + changelog: "Ukรกzat changelog" + dev: "Toto je vรฝvojรกล™skรก verze." + download: "Staลพenรญ" + text: "Byla vydรกna novรก verze a je dostupnรก ke staลพenรญ." + title: "Verze" + query: + filter: + activity: + text: "jsou ve skupinรกch aktivit" + banStatus: + name: "Ban status" + banned: "Zabanovanรญ" + country: + text: "have joined from country" + generic: + allPlayers: "Vลกichni hrรกฤi" + and: "a " + start: "hrรกฤลฏ, kteล™รญ" + joinAddress: + text: "joined with address" + nonOperators: "Bez OP" + notBanned: "Nezabanovanรญ" + operatorStatus: + name: "OP status" + operators: "Operรกtoล™i" + playedBetween: + text: "Hrรกl mezi" + pluginGroup: + name: "Skupina: " + text: "jsou v ${plugin}'s ${group} Skupiny" + registeredBetween: + text: "Registrovรกn mezi" + title: + activityGroup: "Aktuรกlnรญ skupina aktivit" + view: " Pohled:" + filters: + add: "Pล™idat filtr.." + loading: "Naฤรญtรกnรญ filtrลฏ.." + generic: + are: "` jsou`" + label: + from: ">od" + makeAnother: "Vytvoล™it dalลกรญ dotaz" + to: ">do" + view: "Zobrazit pohled" + performQuery: "Provรฉst dotaz!" + results: + match: "nalezeno ${resultCount} hrรกฤลฏ" + none: "Nebyla nalezena ลพรกdnรก data." + title: "Vรฝsledky dotazu" + title: + activity: "Aktivita dotyฤnรฝch hrรกฤลฏ" + activityOnDate: 'Aktivita ' + sessionsWithinView: "Relace v rรกmci pohledu" + text: "Dotaz<" + register: + completion: "Dokonฤit registraci" + completion1: "Nynรญ mลฏลพete dokonฤit registraci uลพivatele." + completion2: "Kรณd vyprลกรญ za 15 minut." + completion3: "Pro dokonฤenรญ registrace pouลพijte nรกsledujรญcรญ pล™รญkaz ve hล™e:" + completion4: "Nebo pouลพijte pล™รญkaz v konzoli:" + createNewUser: "Vytvoล™it novรฉho uลพivatele" + error: + checkFailed: "Kontrola registrace neรบspฤ›ลกnรก: " + failed: "Registrace se nezdaล™ila: " + noPassword: "Napiลกte heslo" + noUsername: "Zvolte uลพivatelskรฉ jmรฉno" + usernameLength: "Uลพivatelskรฉ jmรฉno mลฏลพe bรฝt dlouhรฉ maximรกlnฤ› 50 znakลฏ. Vaลกe mรก " + login: "Mรกte jiลพ รบฤet? Pล™ihlaste se!" + passwordTip: "Heslo by mฤ›lo bรฝt dlouhรฉ alespoลˆ 8 znakลฏ bez omezenรญ." + register: "Registrovat" + usernameTip: "Uลพivatelskรฉ jmรฉno mลฏลพe bรฝt dlouhรฉ 50 znakลฏ." + text: + clickToExpand: "Kliknฤ›te pro rozbalenรญ" + comparing15days: "Srovnรกnรญ poslednรญch 15 dnรญ" + comparing30daysAgo: "Srovnรกnรญ poslednรญch 60 dnรญ" + noExtensionData: "ลฝรกdnรก data rozลกรญล™enรญ" + noLowTps: "ลฝรกdnรฉ nรญzkรฉ TPS rekordy" + unit: + chunks: "Chunkลฏ" + players: "hrรกฤลฏ" + value: + localMachine: "Lokรกlnรญ stroj" + noKills: "ลฝรกdnรฉ zabitรญ" + offline: " Offline" + online: " Online" + with: "S" + version: + changelog: "Zobrazit zmฤ›ny" + current: "Verze ${0}" + download: "Stรกhnout verzi Plan-${0}.jar" + isDev: "Toto je vรฝvojรกล™skรก verze." + updateButton: "Aktualizovat" + updateModal: + text: "Novรก verze je dostupnรก ke staลพenรญ." + title: "Novรก verze ${0} je dostupnรก!" +plugin: + apiCSSAdded: "Rozลกรญล™enรญ: ${0} pล™idรกn stylesheet k ${1}, ${2}" + apiJSAdded: "Rozลกรญล™enรญ: ${0} pล™idรกn javascript k ${1}, ${2}" + disable: + database: "Zpracovรกvรกnรญ kritickรฝch nezpracovanรฝch รบkonลฏ. (${0})" + disabled: "Player Analytics vypnuty." + processingComplete: "Zpracovรกnรญ dokonฤeno." + savingSessions: "Uklรกdรกnรญ nedokonฤenรฉ relace." + savingSessionsTimeout: "Pล™ekroฤen ฤasovรฝ limit, uklรกdรกnm nedokonฤenรฉ relace pro pล™รญลกtรญ spojenรญ." + waitingDb: "ฤŒekรกnรญ na dokonฤenรญ dotazu, aby se zabrรกnilo pรกdu JVM SQLite.." + waitingDbComplete: "Uzavล™enรฉ pล™ipojenรญ SQLite." + waitingTransactions: "ฤŒekรกm na nedokonฤenรฉ transakce, aby nedoลกlo ke ztrรกtฤ› dat.." + waitingTransactionsComplete: "Fronta Transakce uzavล™ena." + webserver: "Webserver je jiลพ vypnutรฝ." + enable: + database: "${0}-pล™ipojenรญ k databรกzi navรกzรกno." + enabled: "Player Analytics zapnuty." + fail: + database: "${0}-Pล™ipojenรญ k databรกzi selhalo: ${1}" + databasePatch: "Patch databรกze selhal, plugin musรญ bรฝt vypnut. Prosรญme nahlaลกte tento problรฉm" + databaseType: "${0} nenรญ podporovanรก databรกze" + geoDBWrite: "Nฤ›co se pokazilo pล™i uklรกdรกnรญ staลพenรฉ GeoLite2 Geolocation databรกze" + webServer: "WebServer se nespustil!" + notify: + badIP: "0.0.0.0 nenรญ validnรญ adresa, nastavte Alternative_IP. Mohou bรฝt poskytnuty ลกpatnรฉ odkazy!" + emptyIP: "IP v server.properties je prรกzdnรฉ & Alternative_IP se nepouลพรญvรก. Mohou bรฝt poskytnuty ลกpatnรฉ odkazy!" + geoDisabled: "Sbรญrรกnรญ geolokace nenรญ aktivnรญ. (Data.Geolocations: false)" + geoInternetRequired: "Plan potล™ebuje internetovรฉ pล™ipojenรญ pro prvnรญ start ke staลพenรญ GeoLite2 Geolocation databรกze." + storeSessions: "Uloลพenรญ relace, kterรฉ byly zachovalรฉ pล™ed pล™edchozรญm spojenรญm." + webserverDisabled: "WebServer nebyl inicializovรกn. (WebServer.DisableWebServer: true)" + webserver: "Webserver bฤ›ลพรญ na PORTU ${0} ( ${1} )" + generic: + dbApplyingPatch: "Aplikuji patch: ${0}.." + dbFaultyLaunchOptions: "Moลพnosti spuลกtฤ›nรญ byly chybnรฉ, pouลพรญvรกm defaultnรญ (${0})" + dbNotifyClean: "Smazรกny data ${0} hrรกฤลฏ." + dbNotifySQLiteWAL: "SQLite WAL mรณd nenรญ podporovรกn na verzi tohoto serveru, pouลพรญvรกm default. Toto mลฏลพe ฤi nemusรญ ovlivnit vรฝkon." + dbPatchesAlreadyApplied: "Vลกechny databรกze jiลพ jsou aktualizovรกny." + dbPatchesApplied: "Vลกechny databรกze byly รบspฤ›ลกnฤ› aktualizovรกny." + dbSchemaPatch: "Database: Making sure schema is up to date.." + loadedServerInfo: "Server identifier loaded: ${0}" + loadingServerInfo: "Loading server identifying information" + no: "Ne" + today: "'Dnes'" + unavailable: "Nedostupnรฉ" + unknown: "Neznรกmรฉ" + yes: "Ano" + yesterday: "'Vฤera'" + version: + checkFail: "Selhalo zjiลกtฤ›nรญ ฤรญsla nejnovฤ›jลกรญ verze" + checkFailGithub: "Informace o verzi z Github/versions.txt nemลฏลพe bรฝt nalezena." + isDev: " Toto je vรฝvojรกล™skรก verze." + isLatest: "Pouลพรญvรกte poslednรญ verzi." + updateAvailable: "Novรฉ vydรกnรญ (${0}) je dostupnรฉ ${1}" + updateAvailableSpigot: "Novรก verze je dostupnรก na ${0}" + webserver: + fail: + SSLContext: "WebServer: SSL Context spuลกtฤ›nรญ selhalo." + certFileEOF: "WebServer: EOF pล™i ฤtenรญ souboru Certifikรกtu. (Zkontrolujte, zda soubor nenรญ prรกzdnรฝ)" + certStoreLoad: "WebServer: SSL Certificate naฤรญtรกnรญ selhalo." + portInUse: "WebServer nebyl รบspฤ›ลกnฤ› spuลกtฤ›n. Je port (${0}) jiลพ pouลพรญvรกn?" + notify: + authDisabledConfig: "WebServer: Autorizace uลพivatele vypnuta! (Vypnuto v configu)" + authDisabledNoHTTPS: "WebServer: Autorizace uลพivatelลฏ vypnutรก! (Nenรญ bezpeฤnรฉ skrz HTTP)" + certificateExpiresOn: "Webserver: Loaded certificate is valid until ${0}." + certificateExpiresPassed: "Webserver: Certificate has expired, consider renewing the certificate." + certificateExpiresSoon: "Webserver: Certificate expires in ${0}, consider renewing the certificate." + certificateNoSuchAlias: "Webserver: Certificate with alias '${0}' was not found inside the keystore file '${1}'." + http: "WebServer: Nenรญ certifikรกt -> Pouลพรญvรกm HTTP-server pro visualizaci." + ipWhitelist: "Webserver: IP Whitelist je zapnutรฝ." + ipWhitelistBlock: "Webserver: ${0} byl odmรญtnut pล™รญstup na '${1}'. (nenรญ na whitelistu)" + noCertFile: "WebServer: Certifikaฤnรญ KeyStore soubor nenalezen: ${0}" + reverseProxy: "WebServer: Proxy-mode HTTPS zapnut, ujistฤ›te se, ลพe reverse-proxy je funkฤรญ s HTTPS a Plan Alternative_IP.Address mรญล™รญ na proxy" diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_DE.txt b/Plan/common/src/main/resources/assets/plan/locale/locale_DE.txt deleted file mode 100644 index 0ac1e521c..000000000 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_DE.txt +++ /dev/null @@ -1,517 +0,0 @@ -API - css+ || PageExtension: ${0} added stylesheet(s) to ${1}, ${2} -API - js+ || PageExtension: ${0} added javascript(s) to ${1}, ${2} -Cmd - Click Me || Klicke hier -Cmd - Link || Link -Cmd - Link Network || Netzwerk-Seite: -Cmd - Link Player || Spieler-Seite: -Cmd - Link Player JSON || Spieler JSON: -Cmd - Link Players || Spieler-Seite: -Cmd - Link Register || RRegisterseite: -Cmd - Link Server || Server Seite: -CMD Arg - backup-file || Name der Backup Datei (GroรŸ- und Kleinschreibung beachten!) -CMD Arg - code || Code, um die Registrierung abzuschlieรŸen. -CMD Arg - db type backup || Datenbanktyp, von welchem ein Backup erstellt werden soll. Wenn keiner angegeben wird, wird die aktuell festgelegte Datenbank verwendet. -CMD Arg - db type clear || Datenbanktyp, von welchem alle Daten gelรถscht werden sollen. -CMD Arg - db type hotswap || Datenbanktyp, welcher verwendet werden soll. -CMD Arg - db type move from || Datenbanktyp, von welchem Daten verschoben werden sollen. -CMD Arg - db type move to || Datenbanktyp, zu welchem Daten verschoben werden sollen. Kann nicht der selbe wie vorher sein. -CMD Arg - db type restore || Datenbanktyp, welcher wiederhergestellt werden soll. Wenn keiner angegeben wird, wird die aktuell festgelegte Datenbank verwendet. -CMD Arg - feature || Name des zu deaktivierenden Features: ${0} -CMD Arg - player identifier || Name oder UUID eines Spieler -CMD Arg - player identifier remove || Anhabe eines Spielers, welcher aus der aktuellen Datenbank entfernt werden soll -CMD Arg - server identifier || Name, ID oder UUID eines Servers -CMD Arg - subcommand || Nutze den Befehl ohne Unterbefehl, um die Hilfe anzuzeigen -CMD Arg - username || Nutzername eines anderen Nutzers. Wenn nicht angegeben, wird der mit dem Spieler verknรผpfte Benutzer verwendet. -CMD Arg Name - backup-file || backup-file -CMD Arg Name - code || ${code} -CMD Arg Name - export kind || export kind -CMD Arg Name - feature || feature -CMD Arg Name - import kind || import kind -CMD Arg Name - name or uuid || name/uuid -CMD Arg Name - server || server -CMD Arg Name - subcommand || subcommand -CMD Arg Name - username || username -Cmd Confirm - accept || Annehmen -Cmd Confirm - cancelled, no data change || Abgebrochen. Es wurden keine Daten verรคndert. -Cmd Confirm - cancelled, unregister || Abgebrochen. '${0}' wurde nicht unregistriert -Cmd Confirm - clearing db || Du bist dabei sรคmtliche Plan-Daten in ${0} zu entfernen. -Cmd Confirm - confirmation || Bestรคtigen: -Cmd Confirm - deny || Abbrechen -Cmd Confirm - Expired || Bestรคtigungsanfrage abgelaufen, nutze den Befehl erneut -Cmd Confirm - Fail on accept || The accepted action errored upon execution: ${0} -Cmd Confirm - Fail on deny || The denied action errored upon execution: ${0} -Cmd Confirm - overwriting db || Du bist dabei Daten in Plan ${0} mit Daten in ${1} zu รผberschreiben -Cmd Confirm - remove player db || Du bist dabei Daten von ${0} aus ${1} zu entfernen -Cmd Confirm - unregister || You are about to unregister '${0}' linked to ${1} -Cmd db - creating backup || Creating a backup file '${0}.db' with contents of ${1} -Cmd db - removal || Entferne Plan-Daten von ${0}.. -Cmd db - removal player || Entferne Daten von ${0} aus ${1}.. -Cmd db - server uninstalled || ยงaWenn der Server noch installiert ist, setzt er sich automatisch als installiert in die Datenbank. -Cmd db - write || Schreibe in ${0}.. -Cmd Disable - Disabled || ยงaPlan ist nun deaktiviert. Nutze reload um das Plugin neu zu starten. -Cmd FAIL - Accepts only these arguments || Accepts following as ${0}: ${1} -Cmd FAIL - Database not open || ยงcDatenbank ist ${0} - Bitte versuche es spรคter erneut. -Cmd FAIL - Empty search string || TDer Suchstring kann nicht leer sein -Cmd FAIL - Invalid Username || ยงcDieser Benutzer besitzt keine UUID. -Cmd FAIL - No Feature || ยงeWelches Feature soll deaktiviert werden? (momentan unterstรผtzt: ${0}) -Cmd FAIL - No Permission || ยงcDafรผr fehlt dir die Berechtigung. -Cmd FAIL - No player || Player '${0}' was not found, they have no UUID. -Cmd FAIL - No player register || Player '${0}' was not found in the database. -Cmd FAIL - No server || Server '${0}' was not found from the database. -Cmd FAIL - Require only one Argument || ยงcNur ein Argument erforderlich ${1} -Cmd FAIL - Requires Arguments || ยงcArgumente erforderlich (${0}) ${1} -Cmd FAIL - see config || see '${0}' in config.yml -Cmd FAIL - Unknown Username || ยงcDieser Benutzer war noch nie auf dem Server. -Cmd FAIL - Users not linked || Dieser Benutzer ist nicht mit deinem Konto verknรผpft und du hast nicht die Berechtigung, die Konten anderer Benutzer zu entfernen. -Cmd FAIL - WebUser does not exists || ยงcDieser Benutzer existiert nicht! -Cmd FAIL - WebUser exists || ยงcDieser Benutzer existiert schon! -Cmd Footer - Help || ยง7Fahre รผber Vefehle oder Argumente oder nutze '/${0} ?' um mehr รผber diese zu lernen. -Cmd Header - Analysis || > ยง2Analyse-Ergebnis: -Cmd Header - Help || > ยง2/${0} Hilfe -Cmd Header - Info || > ยง2Benutzeranalyse: -Cmd Header - Inspect || > ยง2Benutzer: ยงf${0} -Cmd Header - Network || > ยง2Netzwerkseite -Cmd Header - Players || > ยง2Spieler -Cmd Header - Search || > ยง2${0} Ergebnisse fรผr ยงf${1}ยง2: -Cmd Header - server list || id::name::uuid -Cmd Header - Servers || > ยง2Server -Cmd Header - web user list || username::linked to::permission level -Cmd Header - Web Users || > ยง2${0} Accounts -Cmd Info - Bungee Connection || ยง2Verbunden mit Bungee: ยงf${0} -Cmd Info - Database || ยง2Aktuelle Datenbank: ยงf${0} -Cmd Info - Reload Complete || ยงaReload erfolgreich. -Cmd Info - Reload Failed || ยงcBeim Reload ist etwas schief gelaufen. Es wird empfohlen, den Server neuzustarten. -Cmd Info - Update || ยง2Update verfรผgbar: ยงf${0} -Cmd Info - Version || ยง2Version: ยงf${0} -Cmd network - No network || Server ist nicht in einem Netzwerk. Der Link leitet auf die Server Seite um. -Cmd Notify - No Address || ยงeEs war keine Adresse verfรผgbar - Verwendung von localhost als Fallback. Richte 'Alternative_IP' in den Einstellungen ein. -Cmd Notify - No WebUser || Mรถglicherweise hast du keinen Account. Erstelle einen mit /plan register -Cmd Notify - WebUser register || Neuer Account hinzugefรผgt: '${0}' Rechte-Level: ${1} -Cmd Qinspect - Active Playtime || ยง2Active Playtime: ยงf${0} -Cmd Qinspect - Activity Index || ยง2Aktivitรคtsindex: ยงf${0} | ${1} -Cmd Qinspect - AFK Playtime || ยง2AFK Time: ยงf${0} -Cmd Qinspect - Deaths || ยง2Tode: ยงf${0} -Cmd Qinspect - Geolocation || ยง2Eingeloggt aus: ยงf${0} -Cmd Qinspect - Last Seen || ยง2Zuletzt gesehen: ยงf${0} -Cmd Qinspect - Longest Session || ยง2Lรคngste Session: ยงf${0} -Cmd Qinspect - Mob Kills || ยง2Getรถtete Mobs: ยงf${0} -Cmd Qinspect - Player Kills || ยง2Getรถtete Spieler: ยงf${0} -Cmd Qinspect - Playtime || ยง2Spielzeit: ยงf${0} -Cmd Qinspect - Registered || ยง2Registrierung: ยงf${0} -Cmd Qinspect - Times Kicked || ยง2Kicks: ยงf${0} -Cmd SUCCESS - Feature disabled || ยงa'${0}' wurde bis zum nรคchsten Reload des Plugins deaktiviert. -Cmd SUCCESS - WebUser register || ยงaNeuer Account (${0}) erfolgreich hinzugefรผgt! -Cmd unregister - unregistering || Lรถschen der Registrierung von '${0}'.. -Cmd WARN - Database not open || ยงeDatenbank ist ${0} - Dies kรถnnte lรคnger als erwartet dauern.. -Cmd Web - Permission Levels || >\ยง70: Zugriff auf alle Seiten\ยง71: Zugriff auf '/players' Und alle Spielerseiten\ยง72: Zugriff auf alle Spielerseiten mit dem gleichen Username wie der Web-Account\ยง73+: Keine Berechtigung -Command Help - /plan db || Verwalte die Plan Datenbank -Command Help - /plan db backup || Erstelle ein Backup der Datenbank in eine Datei -Command Help - /plan db clear || Lรถsche ALLE Daten von Plan -Command Help - /plan db hotswap || ร„ndere die Datenbank schnell -Command Help - /plan db move || Bewege die Daten zwischen den Datenbanken -Command Help - /plan db remove || Lรถsche Daten eines Spielers aus der aktuellen Datenbank -Command Help - /plan db restore || Stelle Daten aus einer Datei in die Datenbank wiederher -Command Help - /plan db uninstalled || Set a server as uninstalled in the database. -Command Help - /plan disable || Deaktiviere das Plugin oder einen Teil -Command Help - /plan export || Exportiere JSON oder HTMl Dateien manuell -Command Help - /plan import || Importiere Daten -Command Help - /plan info || Informationen รผber das Plugin -Command Help - /plan ingame || Zeigt die Spielerinfo im Spiel -Command Help - /plan json || JSON der Rohdaten eines Spielers anzeigen. -Command Help - /plan logout || Melde andere Nutzer aus dem Panel ab. -Command Help - /plan network || Netzwerk-Seite -Command Help - /plan player || Zeigt eine Spielerseite an -Command Help - /plan players || Spieler-Seite -Command Help - /plan register || Registriere einen Account -Command Help - /plan reload || Plan neuladen -Command Help - /plan search || Nach einem Spieler suchen -Command Help - /plan server || Server-รœbersicht -Command Help - /plan servers || Liste die Server in der Datenbank auf -Command Help - /plan unregister || Registrierung eines Benutzers der Plan-Website aufheben -Command Help - /plan users || Zeige alle Web-Benutzer -Database - Apply Patch || Wende Patch an: ${0}.. -Database - Patches Applied || Alle Datenbankpatches wurden erfolgreich angewendet. -Database - Patches Applied Already || Alle Datenbankpatches wurden bereits angewendet. -Database MySQL - Launch Options Error || Startoptionen sind falsch, nutze Voreinstellungen (${0}) -Database Notify - Clean || Daten von ${0} Spielern gelรถscht. -Database Notify - SQLite No WAL || SQLite WAL wird auf dieser Serverversion nicht unterstรผtzt, nutze Voreinstellungen. Dies beeintrรคchtigt mรถglicherweise die Serverperformance. -Disable || Player Analytics ausgeschaltet. -Disable - Processing || Verarbeite kritische unverarbeitete Aufgaben. (${0}) -Disable - Processing Complete || Verarbeitung komplett. -Disable - Unsaved Session Save || Speichere unfertige Sitzungen.. -Disable - Unsaved Session Save Timeout || Timeout hit, storing the unfinished sessions on next enable instead. -Disable - Waiting SQLite || Waiting queries to finish to avoid SQLite crashing JVM.. -Disable - Waiting SQLite Complete || Closed SQLite connection. -Disable - Waiting Transactions || Waiting for unfinished transactions to avoid data loss.. -Disable - Waiting Transactions Complete || Transaction queue closed. -Disable - WebServer || Webserver deaktiviert. -Enable || Player Analytics eingeschaltet. -Enable - Database || ${0}-dDatenbankverbindung hergestellt. -Enable - Notify Bad IP || 0.0.0.0 is not a valid address, set up Alternative_IP settings. Incorrect links might be given! -Enable - Notify Empty IP || IP in der server.properties ist leer & Alternative_IP ist nicht in Verwendung. Es werden falsche Links verwendet! -Enable - Notify Geolocations disabled || Geolocation wird nicht aufgezeichnet (Data.Geolocations: false) -Enable - Notify Geolocations Internet Required || Plan braucht einen Internetzugang um die GeoLite2 Geolocation Datenbank runterzuladen. -Enable - Notify Webserver disabled || Webserver wurde nicht geladen. (WebServer.DisableWebServer: true) -Enable - Storing preserved sessions || Storing sessions that were preserved before previous shutdown. -Enable - WebServer || Webserver lรคuft auf PORT ${0} ( ${1} ) -Enable FAIL - Database || ${0}-Datenbankverbindung fehlgeschlagen: ${1} -Enable FAIL - Database Patch || Datenbank-Patch ist fehlgeschlagen. Plugin wurde deaktiviert. Wir bitten dich, uns diesen Vorfall mitzuteilen. -Enable FAIL - GeoDB Write || Etwas ist beim Speichern der GeoLite2 Geolocation Datenbank fehlgeschlagen -Enable FAIL - WebServer (Proxy) || Webserver ist nicht geladen! -Enable FAIL - Wrong Database Type || ${0} ist keine gรผltige Datenbank -HTML - AND_BUG_REPORTERS || & Bug reporters! -HTML - BANNED (Filters) || Banned -HTML - COMPARING_15_DAYS || Vergleiche 15 Tage -HTML - COMPARING_60_DAYS || Vergleiche 30 Tage bis Jetzt -HTML - COMPARING_7_DAYS || Vergleiche 7 Tage -HTML - DATABASE_NOT_OPEN || Datenbank ist nicht offen, รผberprรผfe den DB Status mit /plan info -HTML - DESCRIBE_RETENTION_PREDICTION || Dieser Wert ist eine Vorhersage, die auf frรผheren Spielern basiert. -HTML - ERROR || Authentifizierung fehlgeschlagen -HTML - EXPIRED_COOKIE || Benutzer-Cookie ist abgelaufen -HTML - FILTER_ACTIVITY_INDEX_NOW || Aktuelle Aktivitรคtsgruppe -HTML - FILTER_ALL_PLAYERS || Alle Spieler -HTML - FILTER_BANNED || Bann-Status -HTML - FILTER_GROUP || Gruppe: -HTML - FILTER_OPS || Operator Status -HTML - INDEX_ACTIVE || Aktiv -HTML - INDEX_INACTIVE || Inaktiv -HTML - INDEX_IRREGULAR || UnregelmรครŸig -HTML - INDEX_REGULAR || RegelmรครŸig -HTML - INDEX_VERY_ACTIVE || Sehr aktiv -HTML - KILLED || Getรถtet -HTML - LABEL_1ST_WEAPON || Tรถdlichste PvP Waffe -HTML - LABEL_2ND_WEAPON || 2. PvP Waffe -HTML - LABEL_3RD_WEAPON || 3. PvP Waffe -HTML - LABEL_ACTIVE_PLAYTIME || Aktive Spielzeit -HTML - LABEL_ACTIVITY_INDEX || Aktivitรคtsindex -HTML - LABEL_AFK || AFK -HTML - LABEL_AFK_TIME || AFK Zeit -HTML - LABEL_AVG || Durchschnitt -HTML - LABEL_AVG_ACTIVE_PLAYTIME || Durchschnittliche aktive Spielzeit -HTML - LABEL_AVG_AFK_TIME || Durchschnittliche AFK Zeit -HTML - LABEL_AVG_CHUNKS || Durchschnittliche Chunks -HTML - LABEL_AVG_ENTITIES || Durchschnittliche Entitรคten -HTML - LABEL_AVG_KDR || Durschnittliche KDR -HTML - LABEL_AVG_MOB_KDR || Durschnittliche Mob KDR -HTML - LABEL_AVG_PLAYTIME || Durschnittliche Spielzeit -HTML - LABEL_AVG_SESSION_LENGTH || Durschnittliche Sitzungslรคnge -HTML - LABEL_AVG_SESSIONS || Durchschnittliche Sessions -HTML - LABEL_AVG_TPS || Durschnittliche TPS -HTML - LABEL_BANNED || Gebannt -HTML - LABEL_BEST_PEAK || Rekord -HTML - LABEL_DAY_OF_WEEK || Tag der Woche -HTML - LABEL_DEATHS || Tode -HTML - LABEL_DOWNTIME || Downtime -HTML - LABEL_DURING_LOW_TPS || Wรคhrend niedriger TPS-Spitzen: -HTML - LABEL_ENTITIES || Entitรคten -HTML - LABEL_FAVORITE_SERVER || Lieblingsserver -HTML - LABEL_FIRST_SESSION_LENGTH || Erste Sitzungslรคnge -HTML - LABEL_FREE_DISK_SPACE || Freier Festplattenspeicher -HTML - LABEL_INACTIVE || Inaktiv -HTML - LABEL_LAST_PEAK || Letzter Hรถchststand -HTML - LABEL_LAST_SEEN || Zuletzt gesehen -HTML - LABEL_LOADED_CHUNKS || Geladene Chunks -HTML - LABEL_LOADED_ENTITIES || Geladenen Entitรคten -HTML - LABEL_LONE_JOINS || Lone joins -HTML - LABEL_LONE_NEW_JOINS || Lone newbie joins -HTML - LABEL_LONGEST_SESSION || Lรคngste Sitzung -HTML - LABEL_LOW_TPS || Low TPS Spitzen -HTML - LABEL_MAX_FREE_DISK || Max Freier Speicher -HTML - LABEL_MIN_FREE_DISK || Min Freier Speicher -HTML - LABEL_MOB_DEATHS || Tode durch Mobs -HTML - LABEL_MOB_KDR || Mob KDR -HTML - LABEL_MOB_KILLS || Mob Kills -HTML - LABEL_MOST_ACTIVE_GAMEMODE || Meist genutzter Spielmodus -HTML - LABEL_NAME || Name -HTML - LABEL_NEW || Neu -HTML - LABEL_NEW_PLAYERS || Neue Spieler -HTML - LABEL_NICKNAME || Nickname -HTML - LABEL_NO_SESSION_KILLS || Keiner -HTML - LABEL_ONLINE_FIRST_JOIN || Spieler beim ersten Beitritt online -HTML - LABEL_OPERATOR || Operator -HTML - LABEL_PER_PLAYER || / Spieler -HTML - LABEL_PER_REGULAR_PLAYER || / Regulรคrer Spieler -HTML - LABEL_PLAYER_DEATHS || Tode durch Spieler -HTML - LABEL_PLAYER_KILLS || Getรถtete Spieler -HTML - LABEL_PLAYERS_ONLINE || Spieler Online -HTML - LABEL_PLAYTIME || Spielzeit -HTML - LABEL_REGISTERED || Registriert -HTML - LABEL_REGISTERED_PLAYERS || Registrierte Spieler -HTML - LABEL_REGULAR || Regulรคr -HTML - LABEL_REGULAR_PLAYERS || Regulรคre Spieler -HTML - LABEL_RELATIVE_JOIN_ACTIVITY || Relative Join Activity -HTML - LABEL_RETENTION || Erhaltung neuer Spieler -HTML - LABEL_SERVER_DOWNTIME || Server Downtime -HTML - LABEL_SERVER_OCCUPIED || Server occupied -HTML - LABEL_SESSION_ENDED || Sitzung beendet -HTML - LABEL_SESSION_MEDIAN || Sitzungsdurchschnitt -HTML - LABEL_TIMES_KICKED || Mal gekickt -HTML - LABEL_TOTAL_PLAYERS || Gesamte Spieler -HTML - LABEL_TOTAL_PLAYTIME || Gesamte Spielzeit -HTML - LABEL_UNIQUE_PLAYERS || Einzigartige Spieler -HTML - LABEL_WEEK_DAYS || 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag' -HTML - LINK_BACK_NETWORK || Netzwerk Seite -HTML - LINK_BACK_SERVER || Server Seite -HTML - LINK_CHANGELOG || Zeige Changelog -HTML - LINK_DISCORD || Genereller Support auf Discord -HTML - LINK_DOWNLOAD || Download -HTML - LINK_ISSUES || Melde einen Bug -HTML - LINK_NIGHT_MODE || Dark Mode -HTML - LINK_PLAYER_PAGE || Spieler Seite -HTML - LINK_QUICK_VIEW || Schnellansicht -HTML - LINK_SERVER_ANALYSIS || Server Analyse -HTML - LINK_WIKI || Plan Wiki, Tutorials & Documentation -HTML - LOCAL_MACHINE || Lokale Maschine -HTML - LOGIN_CREATE_ACCOUNT || Create an Account! -HTML - LOGIN_FAILED || Login failed: -HTML - LOGIN_FORGOT_PASSWORD || Passwort vergessen? -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_1 || Forgot password? Unregister and register again. -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_2 || Use the following command in game to remove your current user: -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_3 || Or using console: -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_4 || After using the command, -HTML - LOGIN_LOGIN || Login -HTML - LOGIN_LOGOUT || Logout -HTML - LOGIN_PASSWORD || "Password" -HTML - LOGIN_USERNAME || "Username" -HTML - NAV_PLUGINS || Plugins -HTML - NEW_CALENDAR || Neu: -HTML - NO_KILLS || Keine Kills -HTML - NO_USER_PRESENT || Benutzer-Cookie nicht vorhanden -HTML - NON_OPERATORS (Filters) || Non operators -HTML - NOT_BANNED (Filters) || Not banned -HTML - OFFLINE || Offline -HTML - ONLINE || Online -HTML - OPERATORS (Filters) || Operators -HTML - PER_DAY || / Tag -HTML - PLAYERS_TEXT || Spieler -HTML - QUERY || Query< -HTML - QUERY_ACTIVITY_OF_MATCHED_PLAYERS || Activity of matched players -HTML - QUERY_ACTIVITY_ON || Activity on -HTML - QUERY_ADD_FILTER || Add a filter.. -HTML - QUERY_AND || and -HTML - QUERY_ARE || `are` -HTML - QUERY_ARE_ACTIVITY_GROUP || are in Activity Groups -HTML - QUERY_ARE_PLUGIN_GROUP || are in ${plugin}'s ${group} Groups -HTML - QUERY_JOINED_WITH_ADDRESS || joined with address -HTML - QUERY_LOADING_FILTERS || Loading filters.. -HTML - QUERY_MAKE || Make a query -HTML - QUERY_MAKE_ANOTHER || Make another query -HTML - QUERY_OF_PLAYERS || of Players who -HTML - QUERY_PERFORM_QUERY || Perform Query! -HTML - QUERY_PLAYED_BETWEEN || Played between -HTML - QUERY_REGISTERED_BETWEEN || Registered between -HTML - QUERY_RESULTS || Query Results -HTML - QUERY_RESULTS_MATCH || matched ${resultCount} players -HTML - QUERY_SESSIONS_WITHIN_VIEW || Sessions within view -HTML - QUERY_SHOW_VIEW || Show a view -HTML - QUERY_TIME_FROM || >from -HTML - QUERY_TIME_TO || >to -HTML - QUERY_VIEW || View: -HTML - QUERY_ZERO_RESULTS || Query produced 0 results -HTML - REGISTER || Register -HTML - REGISTER_CHECK_FAILED || Checking registration status failed: -HTML - REGISTER_COMPLETE || Complete Registration -HTML - REGISTER_COMPLETE_INSTRUCTIONS_1 || You can now finish registering the user. -HTML - REGISTER_COMPLETE_INSTRUCTIONS_2 || Code expires in 15 minutes -HTML - REGISTER_COMPLETE_INSTRUCTIONS_3 || Use the following command in game to finish registration: -HTML - REGISTER_COMPLETE_INSTRUCTIONS_4 || Or using console: -HTML - REGISTER_CREATE_USER || Create a new user -HTML - REGISTER_FAILED || Registration failed: -HTML - REGISTER_HAVE_ACCOUNT || Already have an account? Login! -HTML - REGISTER_PASSWORD_TIP || Password should be more than 8 characters, but there are no limitations. -HTML - REGISTER_SPECIFY_PASSWORD || You need to specify a Password -HTML - REGISTER_SPECIFY_USERNAME || You need to specify a Username -HTML - REGISTER_USERNAME_LENGTH || Username can be up to 50 characters, yours is -HTML - REGISTER_USERNAME_TIP || Username can be up to 50 characters. -HTML - SESSION || Sitzung -HTML - SIDE_GEOLOCATIONS || Geolocations -HTML - SIDE_INFORMATION || INFORMATION -HTML - SIDE_LINKS || LINKS -HTML - SIDE_NETWORK_OVERVIEW || Netzwerk รœbersicht -HTML - SIDE_OVERVIEW || รœbersicht -HTML - SIDE_PERFORMANCE || Leistung -HTML - SIDE_PLAYER_LIST || Spielerliste -HTML - SIDE_PLAYERBASE || Spielerbasis -HTML - SIDE_PLAYERBASE_OVERVIEW || Spielerbasis รœbersicht -HTML - SIDE_PLUGINS || PLUGINS -HTML - SIDE_PVP_PVE || PvP & PvE -HTML - SIDE_SERVERS || Server -HTML - SIDE_SERVERS_TITLE || SERVER -HTML - SIDE_SESSIONS || Sitzungen -HTML - SIDE_TO_MAIN_PAGE || zur Hauptseite -HTML - TEXT_CLICK_TO_EXPAND || Klicke zum erweitern -HTML - TEXT_CONTRIBUTORS_CODE || Code Mitwirkender -HTML - TEXT_CONTRIBUTORS_LOCALE || รœbersetzer -HTML - TEXT_CONTRIBUTORS_MONEY || Extra Dank an die Leute, die das Projekt finanziell unterstรผtzt haben. -HTML - TEXT_CONTRIBUTORS_THANKS || AuรŸerdem haben die folgenden tollen Leute mitgewirkt: -HTML - TEXT_DEV_VERSION || Diese Version ist ein DEV Release. -HTML - TEXT_DEVELOPED_BY || entwickelt von -HTML - TEXT_FIRST_SESSION || Erste Sitzung -HTML - TEXT_LICENSED_UNDER || Player Analytics entwickelt und lizensiert unter -HTML - TEXT_METRICS || bStats Metrik -HTML - TEXT_NO_EXTENSION_DATA || No Extension Data -HTML - TEXT_NO_LOW_TPS || Keine tiefen TPS-Spitzen -HTML - TEXT_NO_SERVER || Keine Server gefunden, um die Online Aktivitรคt anzuzeigen -HTML - TEXT_NO_SERVERS || Keine Server in der Datenbank gefunden -HTML - TEXT_PLUGIN_INFORMATION || Informationen รผber das Plugin -HTML - TEXT_PREDICTED_RETENTION || Dieser Wert ist eine Vorraussage, der sich auf, der auf den Spielern basiert -HTML - TEXT_SERVER_INSTRUCTIONS || It appears that Plan is not installed on any game servers or not connected to the same database. See wiki for Network tutorial. -HTML - TEXT_VERSION || Eine neue Version steht zum Download bereit -HTML - TITLE_30_DAYS || 30 Tage -HTML - TITLE_30_DAYS_AGO || 30 Tage vorher -HTML - TITLE_ALL || Gesamt -HTML - TITLE_ALL_TIME || Gesamte zeit -HTML - TITLE_AS_NUMBERS || als Zahlen -HTML - TITLE_AVG_PING || Durchschnittlicher Ping -HTML - TITLE_BEST_PING || Bester Ping -HTML - TITLE_CALENDAR || Kalender -HTML - TITLE_CONNECTION_INFO || Verbindungsinformationen -HTML - TITLE_COUNTRY || Land -HTML - TITLE_CPU_RAM || CPU & RAM -HTML - TITLE_CURRENT_PLAYERBASE || Aktuelle Spielerbasis -HTML - TITLE_DISK || Festplattenspeicher -HTML - TITLE_GRAPH_DAY_BY_DAY || Tag fรผr Tag -HTML - TITLE_GRAPH_HOUR_BY_HOUR || Hour by Hour -HTML - TITLE_GRAPH_NETWORK_ONLINE_ACTIVITY || Netzwerk Online Aktivitรคt -HTML - TITLE_GRAPH_PUNCHCARD || Lochkarte fรผr 30 Tage -HTML - TITLE_INSIGHTS || Insights for 30 days -HTML - TITLE_IS_AVAILABLE || ist Verfรผgbar -HTML - TITLE_JOIN_ADDRESSES || Join Addresses -HTML - TITLE_LAST_24_HOURS || Letzte 24 Stunden -HTML - TITLE_LAST_30_DAYS || Letzte 30 Tage -HTML - TITLE_LAST_7_DAYS || Letzte 7 Tage -HTML - TITLE_LAST_CONNECTED || Letzte Verbindung -HTML - TITLE_LENGTH || Lรคnge -HTML - TITLE_MOST_PLAYED_WORLD || Meist gespielte Welt -HTML - TITLE_NETWORK || Netzwerk -HTML - TITLE_NETWORK_AS_NUMBERS || Netzwerk als Zahlen -HTML - TITLE_NOW || Jetzt -HTML - TITLE_ONLINE_ACTIVITY || Online Aktivitรคt -HTML - TITLE_ONLINE_ACTIVITY_AS_NUMBERS || Online Aktivitรคt als Zahlen -HTML - TITLE_ONLINE_ACTIVITY_OVERVIEW || Online Aktivitรคtsรผbersicht -HTML - TITLE_PERFORMANCE_AS_NUMBERS || Leistung als Zahlen -HTML - TITLE_PING || Ping -HTML - TITLE_PLAYER || Spieler -HTML - TITLE_PLAYER_OVERVIEW || Spieler รœbersicht -HTML - TITLE_PLAYERBASE_DEVELOPMENT || Entwicklung der Spielerbasis -HTML - TITLE_PVP_DEATHS || Kรผrzlich durch PvP gestorben -HTML - TITLE_PVP_KILLS || Kรผrzliche PvP-Kills -HTML - TITLE_PVP_PVE_NUMBERS || PvP & PvE als Nummer -HTML - TITLE_RECENT_KILLS || Kรผrzliche Kills -HTML - TITLE_RECENT_SESSIONS || Letzte Sessions -HTML - TITLE_SEEN_NICKNAMES || Registrierte Nicknames -HTML - TITLE_SERVER || Server -HTML - TITLE_SERVER_AS_NUMBERS || Server als Nummern -HTML - TITLE_SERVER_OVERVIEW || Server รœbersicht -HTML - TITLE_SERVER_PLAYTIME || Server Spielzeit -HTML - TITLE_SERVER_PLAYTIME_30 || Server Spielzeit fรผr 30 Tage -HTML - TITLE_SESSION_START || Sitzung gestartet -HTML - TITLE_THEME_SELECT || Thema ausgewรคhlt -HTML - TITLE_TITLE_PLAYER_PUNCHCARD || Lochkarte -HTML - TITLE_TPS || TPS -HTML - TITLE_TREND || Trend -HTML - TITLE_TRENDS || Trends fรผr 30 Tage -HTML - TITLE_VERSION || Version -HTML - TITLE_WEEK_COMPARISON || Wochenvergleich -HTML - TITLE_WORLD || World Load -HTML - TITLE_WORLD_PLAYTIME || Spielzeit in der Welt -HTML - TITLE_WORST_PING || Schlechtester Ping -HTML - TOTAL_ACTIVE_TEXT || Gesamte Aktive Spielzeit -HTML - TOTAL_AFK || Gesamte AFK-Zeit -HTML - TOTAL_PLAYERS || Gesamte Spieler -HTML - UNIQUE_CALENDAR || Einzigartig: -HTML - UNIT_CHUNKS || Chunks -HTML - UNIT_ENTITIES || Entities -HTML - UNIT_NO_DATA || Keine Daten -HTML - UNIT_THE_PLAYERS || Spieler -HTML - USER_AND_PASS_NOT_SPECIFIED || User und Passwort nicht spezifiziert -HTML - USER_DOES_NOT_EXIST || User existiert nicht -HTML - USER_INFORMATION_NOT_FOUND || Registration failed, try again (The code expires after 15 minutes) -HTML - USER_PASS_MISMATCH || User und Password stimmen nicht รผberein -HTML - Version Change log || View Changelog -HTML - Version Current || You have version ${0} -HTML - Version Download || Download Plan-${0}.jar -HTML - Version Update || Update -HTML - Version Update Available || Version ${0} is Available! -HTML - Version Update Dev || This version is a DEV release. -HTML - Version Update Info || A new version has been released and is now available for download. -HTML - WARNING_NO_GAME_SERVERS || Some data requires Plan to be installed on game servers. -HTML - WARNING_NO_GEOLOCATIONS || Geolocation gathering needs to be enabled in the config (Accept GeoLite2 EULA). -HTML - WARNING_NO_SPONGE_CHUNKS || Chunks unavailable on Sponge -HTML - WITH || Breite -HTML ERRORS - ACCESS_DENIED_403 || Zugriff verweigert -HTML ERRORS - AUTH_FAIL_TIPS_401 || - Stelle sicher, dass du einen Account mit /plan register hinzugefรผgt hast.
- รœberprรผfe, ob Passwort und Benutzername korrekt sind
- Bei Benutzername und Passwort auf GroรŸ- und Kleinschreibung achten!

- Wenn du dein Passwort vergessen hast, bitte ein Teammitglied deinen Account zu lรถschen und neu zu erstellen. -HTML ERRORS - AUTHENTICATION_FAILED_401 || Authentifizierung fehlgeschlagen. -HTML ERRORS - FORBIDDEN_403 || Verboten -HTML ERRORS - NO_SERVERS_404 || Keine Server online, die die Anfrage ausfรผhren kรถnnen. -HTML ERRORS - NOT_FOUND_404 || Nicht gefunden. -HTML ERRORS - NOT_PLAYED_404 || Der Spieler war nie auf dem Server. -HTML ERRORS - PAGE_NOT_FOUND_404 || Diese Seite existiert nicht. -HTML ERRORS - UNAUTHORIZED_401 || Unautorisiert -HTML ERRORS - UNKNOWN_PAGE_404 || Stelle sicher, dass du einen Link benutzt, der von einem Befehl generiert wurde. Beispielsweise:

/player/{uuid/name}
/server/{uuid/name/id}

-HTML ERRORS - UUID_404 || Die UUID des Spielers wurde nicht in der Datenbank gefunden. -In Depth Help - /plan db || Use different database subcommands to change the data in some way -In Depth Help - /plan db backup || Uses SQLite to backup the target database to a file. -In Depth Help - /plan db clear || Clears all Plan tables, removing all Plan-data in the process. -In Depth Help - /plan db hotswap || Reloads the plugin with the other database and changes the config to match. -In Depth Help - /plan db move || Overwrites contents in the other database with the contents in another. -In Depth Help - /plan db remove || Removes all data linked to a player from the Current database. -In Depth Help - /plan db restore || Uses SQLite backup file and overwrites contents of the target database. -In Depth Help - /plan db uninstalled || Marks a server in Plan database as uninstalled so that it will not show up in server queries. -In Depth Help - /plan disable || Disable the plugin or part of it until next reload/restart. -In Depth Help - /plan export || Performs an export to export location defined in the config. -In Depth Help - /plan import || Performs an import to load data into the database. -In Depth Help - /plan info || Display the current status of the plugin. -In Depth Help - /plan ingame || Zeigt einige Informationen zu einem Spieler im Spiel. -In Depth Help - /plan json || Allows you to download a player's data in json format. All of it. -In Depth Help - /plan logout || Give username argument to log out another user from the panel, give * as argument to log out everyone. -In Depth Help - /plan network || Obtain a link to the /network page, only does so on networks. -In Depth Help - /plan player || Obtain a link to the /player page of a specific player, or the current player. -In Depth Help - /plan players || Obtain a link to the /players page to see a list of players. -In Depth Help - /plan register || Use without arguments to get link to register page. Use --code [code] after registration to get a user. -In Depth Help - /plan reload || Disable and enable the plugin to reload any changes in config. -In Depth Help - /plan search || List all matching player names to given part of a name. -In Depth Help - /plan server || Obtain a link to the /server page of a specific server, or the current server if no arguments are given. -In Depth Help - /plan servers || List ids, names and uuids of servers in the database. -In Depth Help - /plan unregister || Use without arguments to unregister player linked user, or with username argument to unregister another user. -In Depth Help - /plan users || Lists web users as a table. -Manage - Confirm Overwrite || Daten in ${0} werden รผberschrieben! -Manage - Confirm Removal || Daten in ${0} werden gelรถscht! -Manage - Fail || > ยงcEtwas ist schiefgelaufen: ${0} -Manage - Fail File not found || > ยงcKeine Daten in ${0} gefunden -Manage - Fail Incorrect Database || > ยงc'${0}' ist keine unterstรผtzte Datenbank. -Manage - Fail No Exporter || ยงeExporter '${0}' doesn't exist -Manage - Fail No Importer || ยงeImporter '${0}' existiert nicht -Manage - Fail No Server || Es wurden keine Server mit diesen Angaben gefunden. -Manage - Fail Same Database || > ยงcKann nicht von und zu der gleichen Datenbank agieren! -Manage - Fail Same server || Server kann nicht als uninstallier markiert werden. (Du bist da dran). -Manage - Fail, Confirmation || > ยงcFรผge '-a' zum Befehl hinzu um die Ausfรผhrung zu bestรคtigen: ${0} -Manage - List Importers || Importer: -Manage - Progress || ${0} / ${1} processed.. -Manage - Remind HotSwap || ยงeDenk dran, zur neuen Datenbank zu wechseln (/plan db hotswap ${0}) und um das Plugin neu zu laden. -Manage - Start || > ยง2Verarbeite Daten... -Manage - Success || > ยงaErfolgreich! -Negative || Nein -Positive || Ja -Today || 'Heute' -Unavailable || Nicht verfรผgbar -Unknown || Unbekannt -Version - DEV || Dies ist eine Entwicklungsversion! -Version - Latest || Du verwendest die neuste Version -Version - New || Eine neue Version (${0}) ist verfรผgbar! ${1} -Version - New (old) || Eine Neue Version ist hier verfรผgbar: ${0} -Version FAIL - Read info (old) || รœberprรผfung auf die neuste Versionsnummer fehlgeschlagen. -Version FAIL - Read versions.txt || Versionsinformationen konnten nicht ovn Github/versions.txt gelesen werden. -Web User Listing || ยง2${0} ยง7: ยงf${1} -WebServer - Notify HTTP || WebServer: Kein Zertifikat -> Benutze HTTP-server zur Visualisierung. -WebServer - Notify HTTP User Auth || WebServer: Benutzer-Authentifizierung abgeschaltet! (Nicht sicher รผber HTTP) -WebServer - Notify HTTPS User Auth || WebServer: User Authorization Disabled! (Disabled in config) -Webserver - Notify IP Whitelist || Webserver: IP Whitelist is enabled. -Webserver - Notify IP Whitelist Block || Webserver: ${0} was denied access to '${1}'. (not whitelisted) -WebServer - Notify no Cert file || WebServer: Zertifikat KeyStore Datei nicht gefunden: ${0} -WebServer - Notify Using Proxy || WebServer: Proxy-mode HTTPS enabled, make sure that your reverse-proxy is routing using HTTPS and Plan Alternative_IP.Address points to the Proxy -WebServer FAIL - EOF || WebServer: EOF when reading Certificate file. (Check that the file is not empty) -WebServer FAIL - Port Bind || WebServer wurde nicht erfolgreich initalisiert. Ist der Port (${0}) schon benutzt? -WebServer FAIL - SSL Context || WebServer: SSL Context Initialisierung fehlgeschlagen. -WebServer FAIL - Store Load || WebServer: SSL Zertifikat konnte nicht geladen werden. -Yesterday || 'Gestern' diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_DE.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_DE.yml new file mode 100644 index 000000000..0140fc746 --- /dev/null +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_DE.yml @@ -0,0 +1,669 @@ +403AccessDenied: "Zugriff verweigert" +command: + argument: + backupFile: + description: "Name der Backup Datei (GroรŸ- und Kleinschreibung beachten!)" + name: "backup-file" + code: + description: "Code, um die Registrierung abzuschlieรŸen." + name: "${code}" + dbBackup: + description: "Datenbanktyp, von welchem ein Backup erstellt werden soll. Wenn keiner angegeben wird, wird die aktuell festgelegte Datenbank verwendet." + dbRestore: + description: "Datenbanktyp, welcher wiederhergestellt werden soll. Wenn keiner angegeben wird, wird die aktuell festgelegte Datenbank verwendet." + dbTypeHotswap: + description: "Datenbanktyp, welcher verwendet werden soll." + dbTypeMoveFrom: + description: "Datenbanktyp, von welchem Daten verschoben werden sollen." + dbTypeMoveTo: + description: "Datenbanktyp, zu welchem Daten verschoben werden sollen. Kann nicht der selbe wie vorher sein." + dbTypeRemove: + description: "Datenbanktyp, von welchem alle Daten gelรถscht werden sollen." + exportKind: "export kind" + feature: + description: "Name des zu deaktivierenden Features: ${0}" + name: "feature" + importKind: "import kind" + nameOrUUID: + description: "Name oder UUID eines Spieler" + name: "name/uuid" + removeDescription: "Anhabe eines Spielers, welcher aus der aktuellen Datenbank entfernt werden soll" + server: + description: "Name, ID oder UUID eines Servers" + name: "server" + subcommand: + description: "Nutze den Befehl ohne Unterbefehl, um die Hilfe anzuzeigen" + name: "subcommand" + username: + description: "Nutzername eines anderen Nutzers. Wenn nicht angegeben, wird der mit dem Spieler verknรผpfte Benutzer verwendet." + name: "username" + confirmation: + accept: "Annehmen" + cancelNoChanges: "Abgebrochen. Es wurden keine Daten verรคndert." + cancelNoUnregister: "Abgebrochen. '${0}' wurde nicht unregistriert" + confirm: "Bestรคtigen: " + dbClear: "Du bist dabei sรคmtliche Plan-Daten in ${0} zu entfernen." + dbOverwrite: "Du bist dabei Daten in Plan ${0} mit Daten in ${1} zu รผberschreiben" + dbRemovePlayer: "Du bist dabei Daten von ${0} aus ${1} zu entfernen" + deny: "Abbrechen" + expired: "Bestรคtigungsanfrage abgelaufen, nutze den Befehl erneut" + unregister: "You are about to unregister '${0}' linked to ${1}" + database: + creatingBackup: "Creating a backup file '${0}.db' with contents of ${1}" + failDbNotOpen: "ยงcDatenbank ist ${0} - Bitte versuche es spรคter erneut." + manage: + confirm: "> ยงcFรผge '-a' zum Befehl hinzu um die Ausfรผhrung zu bestรคtigen: ${0}" + confirmOverwrite: "Daten in ${0} werden รผberschrieben!" + confirmPartialRemoval: "Join Address Data for Server ${0} in ${1} will be removed!" + confirmRemoval: "Daten in ${0} werden gelรถscht!" + fail: "> ยงcEtwas ist schiefgelaufen: ${0}" + failFileNotFound: "> ยงcKeine Daten in ${0} gefunden" + failIncorrectDB: "> ยงc'${0}' ist keine unterstรผtzte Datenbank." + failNoServer: "Es wurden keine Server mit diesen Angaben gefunden." + failSameDB: "> ยงcKann nicht von und zu der gleichen Datenbank agieren!" + failSameServer: "Server kann nicht als uninstallier markiert werden. (Du bist da dran)." + hotswap: "ยงeDenk dran, zur neuen Datenbank zu wechseln (/plan db hotswap ${0}) und um das Plugin neu zu laden." + importers: "Importer:" + preparing: "Preparing.." + progress: "${0} / ${1} processed.." + start: "> ยง2Verarbeite Daten..." + success: "> ยงaErfolgreich!" + playerRemoval: "Entferne Daten von ${0} aus ${1}.." + removal: "Entferne Plan-Daten von ${0}.." + serverUninstalled: "ยงaWenn der Server noch installiert ist, setzt er sich automatisch als installiert in die Datenbank." + unregister: "Lรถschen der Registrierung von '${0}'.." + warnDbNotOpen: "ยงeDatenbank ist ${0} - Dies kรถnnte lรคnger als erwartet dauern.." + write: "Schreibe in ${0}.." + fail: + emptyString: "TDer Suchstring kann nicht leer sein" + invalidArguments: "Accepts following as ${0}: ${1}" + invalidUsername: "ยงcDieser Benutzer besitzt keine UUID." + missingArguments: "ยงcArgumente erforderlich (${0}) ${1}" + missingFeature: "ยงeWelches Feature soll deaktiviert werden? (momentan unterstรผtzt: ${0})" + missingLink: "Dieser Benutzer ist nicht mit deinem Konto verknรผpft und du hast nicht die Berechtigung, die Konten anderer Benutzer zu entfernen." + noPermission: "ยงcDafรผr fehlt dir die Berechtigung." + onAccept: "The accepted action errored upon execution: ${0}" + onDeny: "The denied action errored upon execution: ${0}" + playerNotFound: "Player '${0}' was not found, they have no UUID." + playerNotInDatabase: "Player '${0}' was not found in the database." + seeConfig: "see '${0}' in config.yml" + serverNotFound: "Server '${0}' was not found from the database." + tooManyArguments: "ยงcNur ein Argument erforderlich ${1}" + unknownUsername: "ยงcDieser Benutzer war noch nie auf dem Server." + webUserExists: "ยงcDieser Benutzer existiert schon!" + webUserNotFound: "ยงcDieser Benutzer existiert nicht!" + footer: + help: "ยง7Fahre รผber Vefehle oder Argumente oder nutze '/${0} ?' um mehr รผber diese zu lernen." + general: + failNoExporter: "ยงeExporter '${0}' doesn't exist" + failNoImporter: "ยงeImporter '${0}' existiert nicht" + featureDisabled: "ยงa'${0}' wurde bis zum nรคchsten Reload des Plugins deaktiviert." + noAddress: "ยงeEs war keine Adresse verfรผgbar - Verwendung von localhost als Fallback. Richte 'Alternative_IP' in den Einstellungen ein." + noWebuser: "Mรถglicherweise hast du keinen Account. Erstelle einen mit /plan register " + notifyWebUserRegister: "Neuer Account hinzugefรผgt: '${0}' Rechte-Level: ${1}" + pluginDisabled: "ยงaPlan ist nun deaktiviert. Nutze reload um das Plugin neu zu starten." + reloadComplete: "ยงaReload erfolgreich." + reloadFailed: "ยงcBeim Reload ist etwas schief gelaufen. Es wird empfohlen, den Server neuzustarten." + successWebUserRegister: "ยงaNeuer Account (${0}) erfolgreich hinzugefรผgt!" + webPermissionLevels: ">\ยง70: Zugriff auf alle Seiten\ยง71: Zugriff auf '/players' Und alle Spielerseiten\ยง72: Zugriff auf alle Spielerseiten mit dem gleichen Username wie der Web-Account\ยง73+: Keine Berechtigung" + webUserList: " ยง2${0} ยง7: ยงf${1}" + header: + analysis: "> ยง2Analyse-Ergebnis:" + help: "> ยง2/${0} Hilfe" + info: "> ยง2Benutzeranalyse:" + inspect: "> ยง2Benutzer: ยงf${0}" + network: "> ยง2Netzwerkseite" + players: "> ยง2Spieler" + search: "> ยง2${0} Ergebnisse fรผr ยงf${1}ยง2:" + serverList: "id::name::uuid::version" + servers: "> ยง2Server" + webUserList: "username::linked to::permission level" + webUsers: "> ยง2${0} Accounts" + help: + database: + description: "Verwalte die Plan Datenbank" + inDepth: "Use different database subcommands to change the data in some way" + dbBackup: + description: "Erstelle ein Backup der Datenbank in eine Datei" + inDepth: "Uses SQLite to backup the target database to a file." + dbClear: + description: "Lรถsche ALLE Daten von Plan" + inDepth: "Clears all Plan tables, removing all Plan-data in the process." + dbHotswap: + description: "ร„ndere die Datenbank schnell" + inDepth: "Reloads the plugin with the other database and changes the config to match." + dbMove: + description: "Bewege die Daten zwischen den Datenbanken" + inDepth: "Overwrites contents in the other database with the contents in another." + dbRemove: + description: "Lรถsche Daten eines Spielers aus der aktuellen Datenbank" + inDepth: "Removes all data linked to a player from the Current database." + dbRestore: + description: "Stelle Daten aus einer Datei in die Datenbank wiederher" + inDepth: "Uses SQLite backup file and overwrites contents of the target database." + dbUninstalled: + description: "Set a server as uninstalled in the database." + inDepth: "Marks a server in Plan database as uninstalled so that it will not show up in server queries." + disable: + description: "Deaktiviere das Plugin oder einen Teil" + inDepth: "Disable the plugin or part of it until next reload/restart." + export: + description: "Exportiere JSON oder HTMl Dateien manuell" + inDepth: "Performs an export to export location defined in the config." + import: + description: "Importiere Daten" + inDepth: "Performs an import to load data into the database." + info: + description: "Informationen รผber das Plugin" + inDepth: "Display the current status of the plugin." + ingame: + description: "Zeigt die Spielerinfo im Spiel" + inDepth: "Zeigt einige Informationen zu einem Spieler im Spiel." + json: + description: "JSON der Rohdaten eines Spielers anzeigen." + inDepth: "Allows you to download a player's data in json format. All of it." + logout: + description: "Melde andere Nutzer aus dem Panel ab." + inDepth: "Give username argument to log out another user from the panel, give * as argument to log out everyone." + migrateToOnlineUuids: + description: "Migrate offline uuid data to online uuids" + network: + description: "Netzwerk-Seite" + inDepth: "Obtain a link to the /network page, only does so on networks." + player: + description: "Zeigt eine Spielerseite an" + inDepth: "Obtain a link to the /player page of a specific player, or the current player." + players: + description: "Spieler-Seite" + inDepth: "Obtain a link to the /players page to see a list of players." + register: + description: "Registriere einen Account" + inDepth: "Use without arguments to get link to register page. Use --code [code] after registration to get a user." + reload: + description: "Plan neuladen" + inDepth: "Disable and enable the plugin to reload any changes in config." + removejoinaddresses: + description: "Remove join addresses of a specified server" + search: + description: "Nach einem Spieler suchen" + inDepth: "List all matching player names to given part of a name." + server: + description: "Server-รœbersicht" + inDepth: "Obtain a link to the /server page of a specific server, or the current server if no arguments are given." + servers: + description: "Liste die Server in der Datenbank auf" + inDepth: "List ids, names and uuids of servers in the database." + unregister: + description: "Registrierung eines Benutzers der Plan-Website aufheben" + inDepth: "Use without arguments to unregister player linked user, or with username argument to unregister another user." + users: + description: "Zeige alle Web-Benutzer" + inDepth: "Lists web users as a table." + ingame: + activePlaytime: " ยง2Active Playtime: ยงf${0}" + activityIndex: " ยง2Aktivitรคtsindex: ยงf${0} | ${1}" + afkPlaytime: " ยง2AFK Time: ยงf${0}" + deaths: " ยง2Tode: ยงf${0}" + geolocation: " ยง2Eingeloggt aus: ยงf${0}" + lastSeen: " ยง2Zuletzt gesehen: ยงf${0}" + longestSession: " ยง2Lรคngste Session: ยงf${0}" + mobKills: " ยง2Getรถtete Mobs: ยงf${0}" + playerKills: " ยง2Getรถtete Spieler: ยงf${0}" + playtime: " ยง2Spielzeit: ยงf${0}" + registered: " ยง2Registrierung: ยงf${0}" + timesKicked: " ยง2Kicks: ยงf${0}" + link: + clickMe: "Klicke hier" + link: "Link" + network: "Netzwerk-Seite: " + noNetwork: "Server ist nicht in einem Netzwerk. Der Link leitet auf die Server Seite um." + player: "Spieler-Seite: " + playerJson: "Spieler JSON: " + players: "Spieler-Seite: " + register: "RRegisterseite: " + server: "Server Seite: " + subcommand: + info: + database: " ยง2Aktuelle Datenbank: ยงf${0}" + proxy: " ยง2Verbunden mit Bungee: ยงf${0}" + update: " ยง2Update verfรผgbar: ยงf${0}" + version: " ยง2Version: ยงf${0}" +generic: + noData: "Keine Daten" +html: + button: + nightMode: "Dark Mode" + calendar: + new: "Neu:" + unique: "Einzigartig:" + description: + newPlayerRetention: "Dieser Wert ist eine Vorhersage, die auf frรผheren Spielern basiert." + noGameServers: "Some data requires Plan to be installed on game servers." + noGeolocations: "Geolocation gathering needs to be enabled in the config (Accept GeoLite2 EULA)." + noServerOnlinActivity: "Keine Server gefunden, um die Online Aktivitรคt anzuzeigen" + noServers: "Keine Server in der Datenbank gefunden" + noServersLong: 'It appears that Plan is not installed on any game servers or not connected to the same database. See wiki for Network tutorial.' + noSpongeChunks: "Chunks unavailable on Sponge" + predictedNewPlayerRetention: "Dieser Wert ist eine Vorraussage, der sich auf, der auf den Spielern basiert" + error: + 401Unauthorized: "Unautorisiert" + 403Forbidden: "Verboten" + 404NotFound: "Nicht gefunden." + 404PageNotFound: "Diese Seite existiert nicht." + 404UnknownPage: "Stelle sicher, dass du einen Link benutzt, der von einem Befehl generiert wurde. Beispielsweise:

/player/{uuid/name}
/server/{uuid/name/id}

" + UUIDNotFound: "Die UUID des Spielers wurde nicht in der Datenbank gefunden." + auth: + dbClosed: "Datenbank ist nicht offen, รผberprรผfe den DB Status mit /plan info" + emptyForm: "User und Passwort nicht spezifiziert" + expiredCookie: "Benutzer-Cookie ist abgelaufen" + generic: "Authentifizierung fehlgeschlagen" + loginFailed: "User und Password stimmen nicht รผberein" + noCookie: "Benutzer-Cookie nicht vorhanden" + registrationFailed: "Registration failed, try again (The code expires after 15 minutes)" + userNotFound: "User existiert nicht" + authFailed: "Authentifizierung fehlgeschlagen." + authFailedTips: "- Stelle sicher, dass du einen Account mit /plan register hinzugefรผgt hast.
- รœberprรผfe, ob Passwort und Benutzername korrekt sind
- Bei Benutzername und Passwort auf GroรŸ- und Kleinschreibung achten!

- Wenn du dein Passwort vergessen hast, bitte ein Teammitglied deinen Account zu lรถschen und neu zu erstellen." + noServersOnline: "Keine Server online, die die Anfrage ausfรผhren kรถnnen." + playerNotSeen: "Der Spieler war nie auf dem Server." + serverNotSeen: "Server doesn't exist" + generic: + none: "Keiner" + label: + active: "Aktiv" + activePlaytime: "Aktive Spielzeit" + activityIndex: "Aktivitรคtsindex" + afk: "AFK" + afkTime: "AFK Zeit" + all: "Gesamt" + allTime: "Gesamte zeit" + alphabetical: "Alphabetical" + apply: "Apply" + asNumbers: "als Zahlen" + average: "Durchschnitt" + averageActivePlaytime: "Durchschnittliche aktive Spielzeit" + averageAfkTime: "Durchschnittliche AFK Zeit" + averageChunks: "Durchschnittliche Chunks" + averageCpuUsage: "Average CPU Usage" + averageEntities: "Durchschnittliche Entitรคten" + averageKdr: "Durschnittliche KDR" + averageMobKdr: "Durschnittliche Mob KDR" + averagePing: "Durchschnittlicher Ping" + averagePlayers: "Average Players" + averagePlaytime: "Durschnittliche Spielzeit" + averageRamUsage: "Average RAM Usage" + averageServerDowntime: "Average Downtime / Server" + averageSessionLength: "Durschnittliche Sitzungslรคnge" + averageSessions: "Durchschnittliche Sessions" + averageTps: "Durschnittliche TPS" + averageTps7days: "Average TPS (7 days)" + banned: "Gebannt" + bestPeak: "Rekord" + bestPing: "Bester Ping" + calendar: " Kalender" + comparing7days: "Vergleiche 7 Tage" + connectionInfo: "Verbindungsinformationen" + country: "Land" + cpu: "CPU" + cpuRam: "CPU & RAM" + cpuUsage: "CPU Usage" + currentPlayerbase: "Aktuelle Spielerbasis" + currentUptime: "Current Uptime" + dayByDay: "Tag fรผr Tag" + dayOfweek: "Tag der Woche" + deadliestWeapon: "Tรถdlichste PvP Waffe" + deaths: "Tode" + disk: "Festplattenspeicher" + diskSpace: "Freier Festplattenspeicher" + downtime: "Downtime" + duringLowTps: "Wรคhrend niedriger TPS-Spitzen:" + entities: "Entitรคten" + favoriteServer: "Lieblingsserver" + firstSession: "Erste Sitzung" + firstSessionLength: + average: "Average first session length" + median: "Median first session length" + geoProjection: + dropdown: "Select projection" + equalEarth: "Equal Earth" + mercator: "Mercator" + miller: "Miller" + ortographic: "Ortographic" + geolocations: "Geolocations" + hourByHour: "Hour by Hour" + inactive: "Inaktiv" + indexInactive: "Inaktiv" + indexRegular: "RegelmรครŸig" + information: "INFORMATION" + insights: "Insights" + insights30days: "Insights for 30 days" + irregular: "UnregelmรครŸig" + joinAddress: "Join Address" + joinAddresses: "Join Addresses" + kdr: "KDR" + killed: "Getรถtet" + last24hours: "Letzte 24 Stunden" + last30days: "Letzte 30 Tage" + last7days: "Letzte 7 Tage" + lastConnected: "Letzte Verbindung" + lastPeak: "Letzter Hรถchststand" + lastSeen: "Zuletzt gesehen" + latestJoinAddresses: "Latest Join Addresses" + length: " Lรคnge" + links: "LINKS" + loadedChunks: "Geladene Chunks" + loadedEntities: "Geladenen Entitรคten" + loneJoins: "Lone joins" + loneNewbieJoins: "Lone newbie joins" + longestSession: "Lรคngste Sitzung" + lowTpsSpikes: "Low TPS Spitzen" + lowTpsSpikes7days: "Low TPS Spikes (7 days)" + maxFreeDisk: "Max Freier Speicher" + medianSessionLength: "Median Session Length" + minFreeDisk: "Min Freier Speicher" + mobDeaths: "Tode durch Mobs" + mobKdr: "Mob KDR" + mobKills: "Mob Kills" + mostActiveGamemode: "Meist genutzter Spielmodus" + mostPlayedWorld: "Meist gespielte Welt" + name: "Name" + network: "Netzwerk" + networkAsNumbers: "Netzwerk als Zahlen" + networkOnlineActivity: "Netzwerk Online Aktivitรคt" + networkOverview: "Netzwerk รœbersicht" + networkPage: "Netzwerk Seite" + new: "Neu" + newPlayerRetention: "Erhaltung neuer Spieler" + newPlayers: "Neue Spieler" + newPlayers7days: "New Players (7 days)" + nickname: "Nickname" + noDataToDisplay: "No Data to Display" + now: "Jetzt" + onlineActivity: "Online Aktivitรคt" + onlineActivityAsNumbers: "Online Aktivitรคt als Zahlen" + onlineOnFirstJoin: "Spieler beim ersten Beitritt online" + operator: "Operator" + overview: "รœbersicht" + perDay: "/ Tag" + perPlayer: "/ Spieler" + perRegularPlayer: "/ Regulรคrer Spieler" + performance: "Leistung" + performanceAsNumbers: "Leistung als Zahlen" + ping: "Ping" + player: "Spieler" + playerDeaths: "Tode durch Spieler" + playerKills: "Getรถtete Spieler" + playerList: "Spielerliste" + playerOverview: "Spieler รœbersicht" + playerPage: "Spieler Seite" + playerRetention: "Player Retention" + playerbase: "Spielerbasis" + playerbaseDevelopment: "Entwicklung der Spielerbasis" + playerbaseOverview: "Playerbase Overview" + players: "Spieler" + playersOnline: "Spieler Online" + playersOnlineNow: "Players Online (Now)" + playersOnlineOverview: "Online Aktivitรคtsรผbersicht" + playtime: "Spielzeit" + plugins: "Plugins" + pluginsOverview: "Plugins Overview" + punchcard: "Lochkarte" + punchcard30days: "Lochkarte fรผr 30 Tage" + pvpPve: "PvP & PvE" + pvpPveAsNumbers: "PvP & PvE als Nummer" + query: "Make a query" + quickView: "Schnellansicht" + ram: "RAM" + ramUsage: "RAM Usage" + recentKills: "Kรผrzliche Kills" + recentPvpDeaths: "Kรผrzlich durch PvP gestorben" + recentPvpKills: "Kรผrzliche PvP-Kills" + recentSessions: "Letzte Sessions" + registered: "Registriert" + registeredPlayers: "Registrierte Spieler" + regular: "Regulรคr" + regularPlayers: "Regulรคre Spieler" + relativeJoinActivity: "Relative Join Activity" + secondDeadliestWeapon: "2. PvP Waffe" + seenNicknames: "Registrierte Nicknames" + server: "Server" + serverAnalysis: "Server Analyse" + serverAsNumberse: "Server als Nummern" + serverCalendar: "Server Calendar" + serverDowntime: "Server Downtime" + serverOccupied: "Server occupied" + serverOverview: "Server รœbersicht" + serverPage: "Server Seite" + serverPlaytime: "Server Spielzeit" + serverPlaytime30days: "Server Spielzeit fรผr 30 Tage" + serverSelector: "Server selector" + servers: "Server" + serversTitle: "SERVER" + session: "Sitzung" + sessionCalendar: "Session Calendar" + sessionEnded: " Sitzung beendet" + sessionMedian: "Sitzungsdurchschnitt" + sessionStart: "Sitzung gestartet" + sessions: "Sitzungen" + sortBy: "Sort By" + stacked: "Stacked" + themeSelect: "Thema ausgewรคhlt" + thirdDeadliestWeapon: "3. PvP Waffe" + thirtyDays: "30 Tage" + thirtyDaysAgo: "30 Tage vorher" + timesKicked: "Mal gekickt" + toMainPage: "zur Hauptseite" + total: "Total" + totalActive: "Gesamte Aktive Spielzeit" + totalAfk: "Gesamte AFK-Zeit" + totalPlayers: "Gesamte Spieler" + totalPlayersOld: "Gesamte Spieler" + totalPlaytime: "Gesamte Spielzeit" + totalServerDowntime: "Total Server Downtime" + tps: "TPS" + trend: "Trend" + trends30days: "Trends fรผr 30 Tage" + uniquePlayers: "Einzigartige Spieler" + uniquePlayers7days: "Unique Players (7 days)" + veryActive: "Sehr aktiv" + weekComparison: "Wochenvergleich" + weekdays: "'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag'" + world: "World Load" + worldPlaytime: "Spielzeit in der Welt" + worstPing: "Schlechtester Ping" + login: + failed: "Login failed: " + forgotPassword: "Passwort vergessen?" + forgotPassword1: "Forgot password? Unregister and register again." + forgotPassword2: "Use the following command in game to remove your current user:" + forgotPassword3: "Or using console:" + forgotPassword4: "After using the command, " + login: "Login" + logout: "Logout" + password: "Password" + register: "Create an Account!" + username: "Username" + modal: + info: + bugs: "Melde einen Bug" + contributors: + bugreporters: "& Bug reporters!" + code: "Code Mitwirkender" + donate: "Extra Dank an die Leute, die das Projekt finanziell unterstรผtzt haben." + text: 'AuรŸerdem haben die folgenden tollen Leute mitgewirkt:' + translator: "รœbersetzer" + developer: "entwickelt von" + discord: "Genereller Support auf Discord" + license: "Player Analytics entwickelt und lizensiert unter" + metrics: "bStats Metrik" + text: "Informationen รผber das Plugin" + wiki: "Plan Wiki, Tutorials & Documentation" + version: + available: "ist Verfรผgbar" + changelog: "Zeige Changelog" + dev: "Diese Version ist ein DEV Release." + download: "Download" + text: "Eine neue Version steht zum Download bereit" + title: "Version" + query: + filter: + activity: + text: "are in Activity Groups" + banStatus: + name: "Bann-Status" + banned: "Banned" + country: + text: "have joined from country" + generic: + allPlayers: "Alle Spieler" + and: "and " + start: "of Players who " + joinAddress: + text: "joined with address" + nonOperators: "Non operators" + notBanned: "Not banned" + operatorStatus: + name: "Operator Status" + operators: "Operators" + playedBetween: + text: "Played between" + pluginGroup: + name: "Gruppe: " + text: "are in ${plugin}'s ${group} Groups" + registeredBetween: + text: "Registered between" + title: + activityGroup: "Aktuelle Aktivitรคtsgruppe" + view: " View:" + filters: + add: "Add a filter.." + loading: "Loading filters.." + generic: + are: "`are`" + label: + from: ">from" + makeAnother: "Make another query" + to: ">to" + view: "Show a view" + performQuery: "Perform Query!" + results: + match: "matched ${resultCount} players" + none: "Query produced 0 results" + title: "Query Results" + title: + activity: "Activity of matched players" + activityOnDate: 'Activity on ' + sessionsWithinView: "Sessions within view" + text: "Query<" + register: + completion: "Complete Registration" + completion1: "You can now finish registering the user." + completion2: "Code expires in 15 minutes" + completion3: "Use the following command in game to finish registration:" + completion4: "Or using console:" + createNewUser: "Create a new user" + error: + checkFailed: "Checking registration status failed: " + failed: "Registration failed: " + noPassword: "You need to specify a Password" + noUsername: "You need to specify a Username" + usernameLength: "Username can be up to 50 characters, yours is " + login: "Already have an account? Login!" + passwordTip: "Password should be more than 8 characters, but there are no limitations." + register: "Register" + usernameTip: "Username can be up to 50 characters." + text: + clickToExpand: "Klicke zum erweitern" + comparing15days: "Vergleiche 15 Tage" + comparing30daysAgo: "Vergleiche 30 Tage bis Jetzt" + noExtensionData: "No Extension Data" + noLowTps: "Keine tiefen TPS-Spitzen" + unit: + chunks: "Chunks" + players: "Spieler" + value: + localMachine: "Lokale Maschine" + noKills: "Keine Kills" + offline: " Offline" + online: " Online" + with: "Breite" + version: + changelog: "View Changelog" + current: "You have version ${0}" + download: "Download Plan-${0}.jar" + isDev: "This version is a DEV release." + updateButton: "Update" + updateModal: + text: "A new version has been released and is now available for download." + title: "Version ${0} is Available!" +plugin: + apiCSSAdded: "PageExtension: ${0} added stylesheet(s) to ${1}, ${2}" + apiJSAdded: "PageExtension: ${0} added javascript(s) to ${1}, ${2}" + disable: + database: "Verarbeite kritische unverarbeitete Aufgaben. (${0})" + disabled: "Player Analytics ausgeschaltet." + processingComplete: "Verarbeitung komplett." + savingSessions: "Speichere unfertige Sitzungen.." + savingSessionsTimeout: "Timeout hit, storing the unfinished sessions on next enable instead." + waitingDb: "Waiting queries to finish to avoid SQLite crashing JVM.." + waitingDbComplete: "Closed SQLite connection." + waitingTransactions: "Waiting for unfinished transactions to avoid data loss.." + waitingTransactionsComplete: "Transaction queue closed." + webserver: "Webserver deaktiviert." + enable: + database: "${0}-dDatenbankverbindung hergestellt." + enabled: "Player Analytics eingeschaltet." + fail: + database: "${0}-Datenbankverbindung fehlgeschlagen: ${1}" + databasePatch: "Datenbank-Patch ist fehlgeschlagen. Plugin wurde deaktiviert. Wir bitten dich, uns diesen Vorfall mitzuteilen." + databaseType: "${0} ist keine gรผltige Datenbank" + geoDBWrite: "Etwas ist beim Speichern der GeoLite2 Geolocation Datenbank fehlgeschlagen" + webServer: "Webserver ist nicht geladen!" + notify: + badIP: "0.0.0.0 is not a valid address, set up Alternative_IP settings. Incorrect links might be given!" + emptyIP: "IP in der server.properties ist leer & Alternative_IP ist nicht in Verwendung. Es werden falsche Links verwendet!" + geoDisabled: "Geolocation wird nicht aufgezeichnet (Data.Geolocations: false)" + geoInternetRequired: "Plan braucht einen Internetzugang um die GeoLite2 Geolocation Datenbank runterzuladen." + storeSessions: "Storing sessions that were preserved before previous shutdown." + webserverDisabled: "Webserver wurde nicht geladen. (WebServer.DisableWebServer: true)" + webserver: "Webserver lรคuft auf PORT ${0} ( ${1} )" + generic: + dbApplyingPatch: "Wende Patch an: ${0}.." + dbFaultyLaunchOptions: "Startoptionen sind falsch, nutze Voreinstellungen (${0})" + dbNotifyClean: "Daten von ${0} Spielern gelรถscht." + dbNotifySQLiteWAL: "SQLite WAL wird auf dieser Serverversion nicht unterstรผtzt, nutze Voreinstellungen. Dies beeintrรคchtigt mรถglicherweise die Serverperformance." + dbPatchesAlreadyApplied: "Alle Datenbankpatches wurden bereits angewendet." + dbPatchesApplied: "Alle Datenbankpatches wurden erfolgreich angewendet." + dbSchemaPatch: "Database: Making sure schema is up to date.." + loadedServerInfo: "Server identifier loaded: ${0}" + loadingServerInfo: "Loading server identifying information" + no: "Nein" + today: "'Heute'" + unavailable: "Nicht verfรผgbar" + unknown: "Unbekannt" + yes: "Ja" + yesterday: "'Gestern'" + version: + checkFail: "รœberprรผfung auf die neuste Versionsnummer fehlgeschlagen." + checkFailGithub: "Versionsinformationen konnten nicht ovn Github/versions.txt gelesen werden." + isDev: " Dies ist eine Entwicklungsversion!" + isLatest: "Du verwendest die neuste Version" + updateAvailable: "Eine neue Version (${0}) ist verfรผgbar! ${1}" + updateAvailableSpigot: "Eine Neue Version ist hier verfรผgbar: ${0}" + webserver: + fail: + SSLContext: "WebServer: SSL Context Initialisierung fehlgeschlagen." + certFileEOF: "WebServer: EOF when reading Certificate file. (Check that the file is not empty)" + certStoreLoad: "WebServer: SSL Zertifikat konnte nicht geladen werden." + portInUse: "WebServer wurde nicht erfolgreich initalisiert. Ist der Port (${0}) schon benutzt?" + notify: + authDisabledConfig: "WebServer: User Authorization Disabled! (Disabled in config)" + authDisabledNoHTTPS: "WebServer: Benutzer-Authentifizierung abgeschaltet! (Nicht sicher รผber HTTP)" + certificateExpiresOn: "Webserver: Loaded certificate is valid until ${0}." + certificateExpiresPassed: "Webserver: Certificate has expired, consider renewing the certificate." + certificateExpiresSoon: "Webserver: Certificate expires in ${0}, consider renewing the certificate." + certificateNoSuchAlias: "Webserver: Certificate with alias '${0}' was not found inside the keystore file '${1}'." + http: "WebServer: Kein Zertifikat -> Benutze HTTP-server zur Visualisierung." + ipWhitelist: "Webserver: IP Whitelist is enabled." + ipWhitelistBlock: "Webserver: ${0} was denied access to '${1}'. (not whitelisted)" + noCertFile: "WebServer: Zertifikat KeyStore Datei nicht gefunden: ${0}" + reverseProxy: "WebServer: Proxy-mode HTTPS enabled, make sure that your reverse-proxy is routing using HTTPS and Plan Alternative_IP.Address points to the Proxy" diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_EN.txt b/Plan/common/src/main/resources/assets/plan/locale/locale_EN.txt deleted file mode 100644 index 5b15a7446..000000000 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_EN.txt +++ /dev/null @@ -1,517 +0,0 @@ -API - css+ || PageExtension: ${0} added stylesheet(s) to ${1}, ${2} -API - js+ || PageExtension: ${0} added javascript(s) to ${1}, ${2} -Cmd - Click Me || Click me -Cmd - Link || Link -Cmd - Link Network || Network page: -Cmd - Link Player || Player page: -Cmd - Link Player JSON || Player json: -Cmd - Link Players || Players page: -Cmd - Link Register || Register page: -Cmd - Link Server || Server page: -CMD Arg - backup-file || Name of the backup file (case sensitive) -CMD Arg - code || Code used to finalize registration. -CMD Arg - db type backup || Type of the database to backup. Current database is used if not specified. -CMD Arg - db type clear || Type of the database to remove all data from. -CMD Arg - db type hotswap || Type of the database to start using. -CMD Arg - db type move from || Type of the database to move data from. -CMD Arg - db type move to || Type of the database to move data to. Can not be same as previous. -CMD Arg - db type restore || Type of the database to restore to. Current database is used if not specified. -CMD Arg - feature || Name of the feature to disable: ${0} -CMD Arg - player identifier || Name or UUID of a player -CMD Arg - player identifier remove || Identifier for a player that will be removed from current database. -CMD Arg - server identifier || Name, ID or UUID of a server -CMD Arg - subcommand || Use the command without subcommand to see help. -CMD Arg - username || Username of another user. If not specified player linked user is used. -CMD Arg Name - backup-file || backup-file -CMD Arg Name - code || ${code} -CMD Arg Name - export kind || export kind -CMD Arg Name - feature || feature -CMD Arg Name - import kind || import kind -CMD Arg Name - name or uuid || name/uuid -CMD Arg Name - server || server -CMD Arg Name - subcommand || subcommand -CMD Arg Name - username || username -Cmd Confirm - accept || Accept -Cmd Confirm - cancelled, no data change || Cancelled. No data was changed. -Cmd Confirm - cancelled, unregister || Cancelled. '${0}' was not unregistered -Cmd Confirm - clearing db || You are about to remove all Plan-data in ${0} -Cmd Confirm - confirmation || Confirm: -Cmd Confirm - deny || Cancel -Cmd Confirm - Expired || Confirmation expired, use the command again -Cmd Confirm - Fail on accept || The accepted action errored upon execution: ${0} -Cmd Confirm - Fail on deny || The denied action errored upon execution: ${0} -Cmd Confirm - overwriting db || You are about to overwrite data in Plan ${0} with data in ${1} -Cmd Confirm - remove player db || You are about to remove data of ${0} from ${1} -Cmd Confirm - unregister || You are about to unregister '${0}' linked to ${1} -Cmd db - creating backup || Creating a backup file '${0}.db' with contents of ${1} -Cmd db - removal || Removing Plan-data from ${0}.. -Cmd db - removal player || Removing data of ${0} from ${1}.. -Cmd db - server uninstalled || ยงaIf the server is still installed, it will automatically set itself as installed in the database. -Cmd db - write || Writing to ${0}.. -Cmd Disable - Disabled || ยงaPlan systems are now disabled. You can still use reload to restart the plugin. -Cmd FAIL - Accepts only these arguments || Accepts following as ${0}: ${1} -Cmd FAIL - Database not open || ยงcDatabase is ${0} - Please try again a bit later. -Cmd FAIL - Empty search string || The search string can not be empty -Cmd FAIL - Invalid Username || ยงcUser does not have an UUID. -Cmd FAIL - No Feature || ยงeDefine a feature to disable! (currently supports ${0}) -Cmd FAIL - No Permission || ยงcYou do not have the required permission. -Cmd FAIL - No player || Player '${0}' was not found, they have no UUID. -Cmd FAIL - No player register || Player '${0}' was not found in the database. -Cmd FAIL - No server || Server '${0}' was not found from the database. -Cmd FAIL - Require only one Argument || ยงcSingle Argument required ${1} -Cmd FAIL - Requires Arguments || ยงcArguments required (${0}) ${1} -Cmd FAIL - see config || see '${0}' in config.yml -Cmd FAIL - Unknown Username || ยงcUser has not been seen on this server -Cmd FAIL - Users not linked || User is not linked to your account and you don't have permission to remove other user's accounts. -Cmd FAIL - WebUser does not exists || ยงcUser does not exists! -Cmd FAIL - WebUser exists || ยงcUser already exists! -Cmd Footer - Help || ยง7Hover over command or arguments or use '/${0} ?' to learn more about them. -Cmd Header - Analysis || > ยง2Analysis Results -Cmd Header - Help || > ยง2/${0} Help -Cmd Header - Info || > ยง2Player Analytics -Cmd Header - Inspect || > ยง2Player: ยงf${0} -Cmd Header - Network || > ยง2Network Page -Cmd Header - Players || > ยง2Players -Cmd Header - Search || > ยง2${0} Results for ยงf${1}ยง2: -Cmd Header - server list || id::name::uuid -Cmd Header - Servers || > ยง2Servers -Cmd Header - web user list || username::linked to::permission level -Cmd Header - Web Users || > ยง2${0} Web Users -Cmd Info - Bungee Connection || ยง2Connected to Proxy: ยงf${0} -Cmd Info - Database || ยง2Current Database: ยงf${0} -Cmd Info - Reload Complete || ยงaReload Complete -Cmd Info - Reload Failed || ยงcSomething went wrong during reload of the plugin, a restart is recommended. -Cmd Info - Update || ยง2Update Available: ยงf${0} -Cmd Info - Version || ยง2Version: ยงf${0} -Cmd network - No network || Server is not connected to a network. The link redirects to server page. -Cmd Notify - No Address || ยงeNo address was available - using localhost as fallback. Set up 'Alternative_IP' settings. -Cmd Notify - No WebUser || You might not have a web user, use /plan register -Cmd Notify - WebUser register || Registered new user: '${0}' Perm level: ${1} -Cmd Qinspect - Active Playtime || ยง2Active Playtime: ยงf${0} -Cmd Qinspect - Activity Index || ยง2Activity Index: ยงf${0} | ${1} -Cmd Qinspect - AFK Playtime || ยง2AFK Time: ยงf${0} -Cmd Qinspect - Deaths || ยง2Deaths: ยงf${0} -Cmd Qinspect - Geolocation || ยง2Logged in from: ยงf${0} -Cmd Qinspect - Last Seen || ยง2Last Seen: ยงf${0} -Cmd Qinspect - Longest Session || ยง2Longest Session: ยงf${0} -Cmd Qinspect - Mob Kills || ยง2Mob Kills: ยงf${0} -Cmd Qinspect - Player Kills || ยง2Player Kills: ยงf${0} -Cmd Qinspect - Playtime || ยง2Playtime: ยงf${0} -Cmd Qinspect - Registered || ยง2Registered: ยงf${0} -Cmd Qinspect - Times Kicked || ยง2Times Kicked: ยงf${0} -Cmd SUCCESS - Feature disabled || ยงaDisabled '${0}' temporarily until next plugin reload. -Cmd SUCCESS - WebUser register || ยงaAdded a new user (${0}) successfully! -Cmd unregister - unregistering || Unregistering '${0}'.. -Cmd WARN - Database not open || ยงeDatabase is ${0} - This might take longer than expected.. -Cmd Web - Permission Levels || >\ยง70: Access all pages\ยง71: Access '/players' and all player pages\ยง72: Access player page with the same username as the webuser\ยง73+: No permissions -Command Help - /plan db || Manage Plan database -Command Help - /plan db backup || Backup data of a database to a file -Command Help - /plan db clear || Remove ALL Plan data from a database -Command Help - /plan db hotswap || Change Database quickly -Command Help - /plan db move || Move data between Databases -Command Help - /plan db remove || Remove player's data from Current database -Command Help - /plan db restore || Restore data from a file to a database -Command Help - /plan db uninstalled || Set a server as uninstalled in the database. -Command Help - /plan disable || Disable the plugin or part of it -Command Help - /plan export || Export html or json files manually -Command Help - /plan import || Import data -Command Help - /plan info || Information about the plugin -Command Help - /plan ingame || View Player info in game -Command Help - /plan json || View json of Player's raw data. -Command Help - /plan logout || Log out other users from the panel. -Command Help - /plan network || View the Network Page -Command Help - /plan player || View a Player Page -Command Help - /plan players || View the Players Page -Command Help - /plan register || Register a Web User -Command Help - /plan reload || Restart Plan -Command Help - /plan search || Search for a player name -Command Help - /plan server || View the Server Page -Command Help - /plan servers || List servers in Database -Command Help - /plan unregister || Unregister a user of Plan website -Command Help - /plan users || List all web users -Database - Apply Patch || Applying Patch: ${0}.. -Database - Patches Applied || All database patches applied successfully. -Database - Patches Applied Already || All database patches already applied. -Database MySQL - Launch Options Error || Launch Options were faulty, using default (${0}) -Database Notify - Clean || Removed data of ${0} players. -Database Notify - SQLite No WAL || SQLite WAL mode not supported on this server version, using default. This may or may not affect performance. -Disable || Player Analytics Disabled. -Disable - Processing || Processing critical unprocessed tasks. (${0}) -Disable - Processing Complete || Processing complete. -Disable - Unsaved Session Save || Saving unfinished sessions.. -Disable - Unsaved Session Save Timeout || Timeout hit, storing the unfinished sessions on next enable instead. -Disable - Waiting SQLite || Waiting queries to finish to avoid SQLite crashing JVM.. -Disable - Waiting SQLite Complete || Closed SQLite connection. -Disable - Waiting Transactions || Waiting for unfinished transactions to avoid data loss.. -Disable - Waiting Transactions Complete || Transaction queue closed. -Disable - WebServer || Webserver has been disabled. -Enable || Player Analytics Enabled. -Enable - Database || ${0}-database connection established. -Enable - Notify Bad IP || 0.0.0.0 is not a valid address, set up Alternative_IP settings. Incorrect links might be given! -Enable - Notify Empty IP || IP in server.properties is empty & Alternative_IP is not in use. Incorrect links might be given! -Enable - Notify Geolocations disabled || Geolocation gathering is not active. (Data.Geolocations: false) -Enable - Notify Geolocations Internet Required || Plan Requires internet access on first run to download GeoLite2 Geolocation database. -Enable - Notify Webserver disabled || WebServer was not initialized. (WebServer.DisableWebServer: true) -Enable - Storing preserved sessions || Storing sessions that were preserved before previous shutdown. -Enable - WebServer || Webserver running on PORT ${0} ( ${1} ) -Enable FAIL - Database || ${0}-Database Connection failed: ${1} -Enable FAIL - Database Patch || Database Patching failed, plugin has to be disabled. Please report this issue -Enable FAIL - GeoDB Write || Something went wrong saving the downloaded GeoLite2 Geolocation database -Enable FAIL - WebServer (Proxy) || WebServer did not initialize! -Enable FAIL - Wrong Database Type || ${0} is not a supported Database -HTML - AND_BUG_REPORTERS || & Bug reporters! -HTML - BANNED (Filters) || Banned -HTML - COMPARING_15_DAYS || Comparing 15 days -HTML - COMPARING_60_DAYS || Comparing 30d ago to Now -HTML - COMPARING_7_DAYS || Comparing 7 days -HTML - DATABASE_NOT_OPEN || Database is not open, check db status with /plan info -HTML - DESCRIBE_RETENTION_PREDICTION || This value is a prediction based on previous players. -HTML - ERROR || Authentication failed due to error -HTML - EXPIRED_COOKIE || User cookie has expired -HTML - FILTER_ACTIVITY_INDEX_NOW || Current activity group -HTML - FILTER_ALL_PLAYERS || All players -HTML - FILTER_BANNED || Ban status -HTML - FILTER_GROUP || Group: -HTML - FILTER_OPS || Operator status -HTML - INDEX_ACTIVE || Active -HTML - INDEX_INACTIVE || Inactive -HTML - INDEX_IRREGULAR || Irregular -HTML - INDEX_REGULAR || Regular -HTML - INDEX_VERY_ACTIVE || Very Active -HTML - KILLED || Killed -HTML - LABEL_1ST_WEAPON || Deadliest PvP Weapon -HTML - LABEL_2ND_WEAPON || 2nd PvP Weapon -HTML - LABEL_3RD_WEAPON || 3rd PvP Weapon -HTML - LABEL_ACTIVE_PLAYTIME || Active Playtime -HTML - LABEL_ACTIVITY_INDEX || Activity Index -HTML - LABEL_AFK || AFK -HTML - LABEL_AFK_TIME || AFK Time -HTML - LABEL_AVG || Average -HTML - LABEL_AVG_ACTIVE_PLAYTIME || Average Active Playtime -HTML - LABEL_AVG_AFK_TIME || Average AFK Time -HTML - LABEL_AVG_CHUNKS || Average Chunks -HTML - LABEL_AVG_ENTITIES || Average Entities -HTML - LABEL_AVG_KDR || Average KDR -HTML - LABEL_AVG_MOB_KDR || Average Mob KDR -HTML - LABEL_AVG_PLAYTIME || Average Playtime -HTML - LABEL_AVG_SESSION_LENGTH || Average Session Length -HTML - LABEL_AVG_SESSIONS || Average Sessions -HTML - LABEL_AVG_TPS || Average TPS -HTML - LABEL_BANNED || Banned -HTML - LABEL_BEST_PEAK || All Time Peak -HTML - LABEL_DAY_OF_WEEK || Day of the Week -HTML - LABEL_DEATHS || Deaths -HTML - LABEL_DOWNTIME || Downtime -HTML - LABEL_DURING_LOW_TPS || During Low TPS Spikes: -HTML - LABEL_ENTITIES || Entities -HTML - LABEL_FAVORITE_SERVER || Favorite Server -HTML - LABEL_FIRST_SESSION_LENGTH || First session length -HTML - LABEL_FREE_DISK_SPACE || Free Disk Space -HTML - LABEL_INACTIVE || Inactive -HTML - LABEL_LAST_PEAK || Last Peak -HTML - LABEL_LAST_SEEN || Last Seen -HTML - LABEL_LOADED_CHUNKS || Loaded Chunks -HTML - LABEL_LOADED_ENTITIES || Loaded Entities -HTML - LABEL_LONE_JOINS || Lone joins -HTML - LABEL_LONE_NEW_JOINS || Lone newbie joins -HTML - LABEL_LONGEST_SESSION || Longest Session -HTML - LABEL_LOW_TPS || Low TPS Spikes -HTML - LABEL_MAX_FREE_DISK || Max Free Disk -HTML - LABEL_MIN_FREE_DISK || Min Free Disk -HTML - LABEL_MOB_DEATHS || Mob caused Deaths -HTML - LABEL_MOB_KDR || Mob KDR -HTML - LABEL_MOB_KILLS || Mob Kills -HTML - LABEL_MOST_ACTIVE_GAMEMODE || Most Active Gamemode -HTML - LABEL_NAME || Name -HTML - LABEL_NEW || New -HTML - LABEL_NEW_PLAYERS || New Players -HTML - LABEL_NICKNAME || Nickname -HTML - LABEL_NO_SESSION_KILLS || None -HTML - LABEL_ONLINE_FIRST_JOIN || Players online on first join -HTML - LABEL_OPERATOR || Operator -HTML - LABEL_PER_PLAYER || / Player -HTML - LABEL_PER_REGULAR_PLAYER || / Regular Player -HTML - LABEL_PLAYER_DEATHS || Player caused Deaths -HTML - LABEL_PLAYER_KILLS || Player Kills -HTML - LABEL_PLAYERS_ONLINE || Players Online -HTML - LABEL_PLAYTIME || Playtime -HTML - LABEL_REGISTERED || Registered -HTML - LABEL_REGISTERED_PLAYERS || Registered Players -HTML - LABEL_REGULAR || Regular -HTML - LABEL_REGULAR_PLAYERS || Regular Players -HTML - LABEL_RELATIVE_JOIN_ACTIVITY || Relative Join Activity -HTML - LABEL_RETENTION || New Player Retention -HTML - LABEL_SERVER_DOWNTIME || Server Downtime -HTML - LABEL_SERVER_OCCUPIED || Server occupied -HTML - LABEL_SESSION_ENDED || Ended -HTML - LABEL_SESSION_MEDIAN || Session Median -HTML - LABEL_TIMES_KICKED || Times Kicked -HTML - LABEL_TOTAL_PLAYERS || Total Players -HTML - LABEL_TOTAL_PLAYTIME || Total Playtime -HTML - LABEL_UNIQUE_PLAYERS || Unique Players -HTML - LABEL_WEEK_DAYS || 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' -HTML - LINK_BACK_NETWORK || Network page -HTML - LINK_BACK_SERVER || Server page -HTML - LINK_CHANGELOG || View Changelog -HTML - LINK_DISCORD || General Support on Discord -HTML - LINK_DOWNLOAD || Download -HTML - LINK_ISSUES || Report Issues -HTML - LINK_NIGHT_MODE || Night Mode -HTML - LINK_PLAYER_PAGE || Player Page -HTML - LINK_QUICK_VIEW || Quick view -HTML - LINK_SERVER_ANALYSIS || Server Analysis -HTML - LINK_WIKI || Plan Wiki, Tutorials & Documentation -HTML - LOCAL_MACHINE || Local Machine -HTML - LOGIN_CREATE_ACCOUNT || Create an Account! -HTML - LOGIN_FAILED || Login failed: -HTML - LOGIN_FORGOT_PASSWORD || Forgot Password? -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_1 || Forgot password? Unregister and register again. -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_2 || Use the following command in game to remove your current user: -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_3 || Or using console: -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_4 || After using the command, -HTML - LOGIN_LOGIN || Login -HTML - LOGIN_LOGOUT || Logout -HTML - LOGIN_PASSWORD || "Password" -HTML - LOGIN_USERNAME || "Username" -HTML - NAV_PLUGINS || Plugins -HTML - NEW_CALENDAR || New: -HTML - NO_KILLS || No Kills -HTML - NO_USER_PRESENT || User cookie not present -HTML - NON_OPERATORS (Filters) || Non operators -HTML - NOT_BANNED (Filters) || Not banned -HTML - OFFLINE || Offline -HTML - ONLINE || Online -HTML - OPERATORS (Filters) || Operators -HTML - PER_DAY || / Day -HTML - PLAYERS_TEXT || Players -HTML - QUERY || Query< -HTML - QUERY_ACTIVITY_OF_MATCHED_PLAYERS || Activity of matched players -HTML - QUERY_ACTIVITY_ON || Activity on -HTML - QUERY_ADD_FILTER || Add a filter.. -HTML - QUERY_AND || and -HTML - QUERY_ARE || `are` -HTML - QUERY_ARE_ACTIVITY_GROUP || are in Activity Groups -HTML - QUERY_ARE_PLUGIN_GROUP || are in ${plugin}'s ${group} Groups -HTML - QUERY_JOINED_WITH_ADDRESS || joined with address -HTML - QUERY_LOADING_FILTERS || Loading filters.. -HTML - QUERY_MAKE || Make a query -HTML - QUERY_MAKE_ANOTHER || Make another query -HTML - QUERY_OF_PLAYERS || of Players who -HTML - QUERY_PERFORM_QUERY || Perform Query! -HTML - QUERY_PLAYED_BETWEEN || Played between -HTML - QUERY_REGISTERED_BETWEEN || Registered between -HTML - QUERY_RESULTS || Query Results -HTML - QUERY_RESULTS_MATCH || matched ${resultCount} players -HTML - QUERY_SESSIONS_WITHIN_VIEW || Sessions within view -HTML - QUERY_SHOW_VIEW || Show a view -HTML - QUERY_TIME_FROM || >from -HTML - QUERY_TIME_TO || >to -HTML - QUERY_VIEW || View: -HTML - QUERY_ZERO_RESULTS || Query produced 0 results -HTML - REGISTER || Register -HTML - REGISTER_CHECK_FAILED || Checking registration status failed: -HTML - REGISTER_COMPLETE || Complete Registration -HTML - REGISTER_COMPLETE_INSTRUCTIONS_1 || You can now finish registering the user. -HTML - REGISTER_COMPLETE_INSTRUCTIONS_2 || Code expires in 15 minutes -HTML - REGISTER_COMPLETE_INSTRUCTIONS_3 || Use the following command in game to finish registration: -HTML - REGISTER_COMPLETE_INSTRUCTIONS_4 || Or using console: -HTML - REGISTER_CREATE_USER || Create a new user -HTML - REGISTER_FAILED || Registration failed: -HTML - REGISTER_HAVE_ACCOUNT || Already have an account? Login! -HTML - REGISTER_PASSWORD_TIP || Password should be more than 8 characters, but there are no limitations. -HTML - REGISTER_SPECIFY_PASSWORD || You need to specify a Password -HTML - REGISTER_SPECIFY_USERNAME || You need to specify a Username -HTML - REGISTER_USERNAME_LENGTH || Username can be up to 50 characters, yours is -HTML - REGISTER_USERNAME_TIP || Username can be up to 50 characters. -HTML - SESSION || Session -HTML - SIDE_GEOLOCATIONS || Geolocations -HTML - SIDE_INFORMATION || INFORMATION -HTML - SIDE_LINKS || LINKS -HTML - SIDE_NETWORK_OVERVIEW || Network Overview -HTML - SIDE_OVERVIEW || Overview -HTML - SIDE_PERFORMANCE || Performance -HTML - SIDE_PLAYER_LIST || Player List -HTML - SIDE_PLAYERBASE || Playerbase -HTML - SIDE_PLAYERBASE_OVERVIEW || Playerbase Overview -HTML - SIDE_PLUGINS || PLUGINS -HTML - SIDE_PVP_PVE || PvP & PvE -HTML - SIDE_SERVERS || Servers -HTML - SIDE_SERVERS_TITLE || SERVERS -HTML - SIDE_SESSIONS || Sessions -HTML - SIDE_TO_MAIN_PAGE || to main page -HTML - TEXT_CLICK_TO_EXPAND || Click to expand -HTML - TEXT_CONTRIBUTORS_CODE || code contributor -HTML - TEXT_CONTRIBUTORS_LOCALE || translator -HTML - TEXT_CONTRIBUTORS_MONEY || Extra special thanks to those who have monetarily supported the development. -HTML - TEXT_CONTRIBUTORS_THANKS || In addition following awesome people have contributed: -HTML - TEXT_DEV_VERSION || This version is a DEV release. -HTML - TEXT_DEVELOPED_BY || is developed by -HTML - TEXT_FIRST_SESSION || First session -HTML - TEXT_LICENSED_UNDER || Player Analytics is developed and licensed under -HTML - TEXT_METRICS || bStats Metrics -HTML - TEXT_NO_EXTENSION_DATA || No Extension Data -HTML - TEXT_NO_LOW_TPS || No low tps spikes -HTML - TEXT_NO_SERVER || No server to display online activity for -HTML - TEXT_NO_SERVERS || No servers found in the database -HTML - TEXT_PLUGIN_INFORMATION || Information about the plugin -HTML - TEXT_PREDICTED_RETENTION || This value is a prediction based on previous players -HTML - TEXT_SERVER_INSTRUCTIONS || It appears that Plan is not installed on any game servers or not connected to the same database. See wiki for Network tutorial. -HTML - TEXT_VERSION || A new version has been released and is now available for download. -HTML - TITLE_30_DAYS || 30 days -HTML - TITLE_30_DAYS_AGO || 30 days ago -HTML - TITLE_ALL || All -HTML - TITLE_ALL_TIME || All Time -HTML - TITLE_AS_NUMBERS || as Numbers -HTML - TITLE_AVG_PING || Average Ping -HTML - TITLE_BEST_PING || Best Ping -HTML - TITLE_CALENDAR || Calendar -HTML - TITLE_CONNECTION_INFO || Connection Information -HTML - TITLE_COUNTRY || Country -HTML - TITLE_CPU_RAM || CPU & RAM -HTML - TITLE_CURRENT_PLAYERBASE || Current Playerbase -HTML - TITLE_DISK || Disk space -HTML - TITLE_GRAPH_DAY_BY_DAY || Day by Day -HTML - TITLE_GRAPH_HOUR_BY_HOUR || Hour by Hour -HTML - TITLE_GRAPH_NETWORK_ONLINE_ACTIVITY || Network Online Activity -HTML - TITLE_GRAPH_PUNCHCARD || Punchcard for 30 days -HTML - TITLE_INSIGHTS || Insights for 30 days -HTML - TITLE_IS_AVAILABLE || is Available -HTML - TITLE_JOIN_ADDRESSES || Join Addresses -HTML - TITLE_LAST_24_HOURS || Last 24 hours -HTML - TITLE_LAST_30_DAYS || Last 30 days -HTML - TITLE_LAST_7_DAYS || Last 7 days -HTML - TITLE_LAST_CONNECTED || Last Connected -HTML - TITLE_LENGTH || Length -HTML - TITLE_MOST_PLAYED_WORLD || Most played World -HTML - TITLE_NETWORK || Network -HTML - TITLE_NETWORK_AS_NUMBERS || Network as Numbers -HTML - TITLE_NOW || Now -HTML - TITLE_ONLINE_ACTIVITY || Online Activity -HTML - TITLE_ONLINE_ACTIVITY_AS_NUMBERS || Online Activity as Numbers -HTML - TITLE_ONLINE_ACTIVITY_OVERVIEW || Online Activity Overview -HTML - TITLE_PERFORMANCE_AS_NUMBERS || Performance as Numbers -HTML - TITLE_PING || Ping -HTML - TITLE_PLAYER || Player -HTML - TITLE_PLAYER_OVERVIEW || Player Overview -HTML - TITLE_PLAYERBASE_DEVELOPMENT || Playerbase Development -HTML - TITLE_PVP_DEATHS || Recent PvP Deaths -HTML - TITLE_PVP_KILLS || Recent PvP Kills -HTML - TITLE_PVP_PVE_NUMBERS || PvP & PvE as Numbers -HTML - TITLE_RECENT_KILLS || Recent Kills -HTML - TITLE_RECENT_SESSIONS || Recent Sessions -HTML - TITLE_SEEN_NICKNAMES || Seen Nicknames -HTML - TITLE_SERVER || Server -HTML - TITLE_SERVER_AS_NUMBERS || Server as Numbers -HTML - TITLE_SERVER_OVERVIEW || Server Overview -HTML - TITLE_SERVER_PLAYTIME || Server Playtime -HTML - TITLE_SERVER_PLAYTIME_30 || Server Playtime for 30 days -HTML - TITLE_SESSION_START || Session Started -HTML - TITLE_THEME_SELECT || Theme Select -HTML - TITLE_TITLE_PLAYER_PUNCHCARD || Punchcard -HTML - TITLE_TPS || TPS -HTML - TITLE_TREND || Trend -HTML - TITLE_TRENDS || Trends for 30 days -HTML - TITLE_VERSION || Version -HTML - TITLE_WEEK_COMPARISON || Week Comparison -HTML - TITLE_WORLD || World Load -HTML - TITLE_WORLD_PLAYTIME || World Playtime -HTML - TITLE_WORST_PING || Worst Ping -HTML - TOTAL_ACTIVE_TEXT || Total Active -HTML - TOTAL_AFK || Total AFK -HTML - TOTAL_PLAYERS || Total Players -HTML - UNIQUE_CALENDAR || Unique: -HTML - UNIT_CHUNKS || Chunks -HTML - UNIT_ENTITIES || Entities -HTML - UNIT_NO_DATA || No Data -HTML - UNIT_THE_PLAYERS || Players -HTML - USER_AND_PASS_NOT_SPECIFIED || User and Password not specified -HTML - USER_DOES_NOT_EXIST || User does not exist -HTML - USER_INFORMATION_NOT_FOUND || Registration failed, try again (The code expires after 15 minutes) -HTML - USER_PASS_MISMATCH || User and Password did not match -HTML - Version Change log || View Changelog -HTML - Version Current || You have version ${0} -HTML - Version Download || Download Plan-${0}.jar -HTML - Version Update || Update -HTML - Version Update Available || Version ${0} is Available! -HTML - Version Update Dev || This version is a DEV release. -HTML - Version Update Info || A new version has been released and is now available for download. -HTML - WARNING_NO_GAME_SERVERS || Some data requires Plan to be installed on game servers. -HTML - WARNING_NO_GEOLOCATIONS || Geolocation gathering needs to be enabled in the config (Accept GeoLite2 EULA). -HTML - WARNING_NO_SPONGE_CHUNKS || Chunks unavailable on Sponge -HTML - WITH || With -HTML ERRORS - ACCESS_DENIED_403 || Access Denied -HTML ERRORS - AUTH_FAIL_TIPS_401 || - Ensure you have registered a user with /plan register
- Check that the username and password are correct
- Username and password are case-sensitive

If you have forgotten your password, ask a staff member to delete your old user and re-register. -HTML ERRORS - AUTHENTICATION_FAILED_401 || Authentication Failed. -HTML ERRORS - FORBIDDEN_403 || Forbidden -HTML ERRORS - NO_SERVERS_404 || No Servers online to perform the request. -HTML ERRORS - NOT_FOUND_404 || Not Found -HTML ERRORS - NOT_PLAYED_404 || Plan has not seen this player. -HTML ERRORS - PAGE_NOT_FOUND_404 || Page does not exist. -HTML ERRORS - UNAUTHORIZED_401 || Unauthorized -HTML ERRORS - UNKNOWN_PAGE_404 || Make sure you're accessing a link given by a command, Examples:

/player/{uuid/name}
/server/{uuid/name/id}

-HTML ERRORS - UUID_404 || Player UUID was not found in the database. -In Depth Help - /plan db || Use different database subcommands to change the data in some way -In Depth Help - /plan db backup || Uses SQLite to backup the target database to a file. -In Depth Help - /plan db clear || Clears all Plan tables, removing all Plan-data in the process. -In Depth Help - /plan db hotswap || Reloads the plugin with the other database and changes the config to match. -In Depth Help - /plan db move || Overwrites contents in the other database with the contents in another. -In Depth Help - /plan db remove || Removes all data linked to a player from the Current database. -In Depth Help - /plan db restore || Uses SQLite backup file and overwrites contents of the target database. -In Depth Help - /plan db uninstalled || Marks a server in Plan database as uninstalled so that it will not show up in server queries. -In Depth Help - /plan disable || Disable the plugin or part of it until next reload/restart. -In Depth Help - /plan export || Performs an export to export location defined in the config. -In Depth Help - /plan import || Performs an import to load data into the database. -In Depth Help - /plan info || Display the current status of the plugin. -In Depth Help - /plan ingame || Displays some information about the player in game. -In Depth Help - /plan json || Allows you to download a player's data in json format. All of it. -In Depth Help - /plan logout || Give username argument to log out another user from the panel, give * as argument to log out everyone. -In Depth Help - /plan network || Obtain a link to the /network page, only does so on networks. -In Depth Help - /plan player || Obtain a link to the /player page of a specific player, or the current player. -In Depth Help - /plan players || Obtain a link to the /players page to see a list of players. -In Depth Help - /plan register || Use without arguments to get link to register page. Use --code [code] after registration to get a user. -In Depth Help - /plan reload || Disable and enable the plugin to reload any changes in config. -In Depth Help - /plan search || List all matching player names to given part of a name. -In Depth Help - /plan server || Obtain a link to the /server page of a specific server, or the current server if no arguments are given. -In Depth Help - /plan servers || List ids, names and uuids of servers in the database. -In Depth Help - /plan unregister || Use without arguments to unregister player linked user, or with username argument to unregister another user. -In Depth Help - /plan users || Lists web users as a table. -Manage - Confirm Overwrite || Data in ${0} will be overwritten! -Manage - Confirm Removal || Data in ${0} will be removed! -Manage - Fail || > ยงcSomething went wrong: ${0} -Manage - Fail File not found || > ยงcNo File found at ${0} -Manage - Fail Incorrect Database || > ยงc'${0}' is not a supported database. -Manage - Fail No Exporter || ยงeExporter '${0}' doesn't exist -Manage - Fail No Importer || ยงeImporter '${0}' doesn't exist -Manage - Fail No Server || No server found with given parameters. -Manage - Fail Same Database || > ยงcCan not operate on to and from the same database! -Manage - Fail Same server || Can not mark this server as uninstalled (You are on it) -Manage - Fail, Confirmation || > ยงcAdd '-a' argument to confirm execution: ${0} -Manage - List Importers || Importers: -Manage - Progress || ${0} / ${1} processed.. -Manage - Remind HotSwap || ยงeRemember to swap to the new database (/plan db hotswap ${0}) & reload the plugin. -Manage - Start || > ยง2Processing data.. -Manage - Success || > ยงaSuccess! -Negative || No -Positive || Yes -Today || 'Today' -Unavailable || Unavailable -Unknown || Unknown -Version - DEV || This is a DEV release. -Version - Latest || You're using the latest version. -Version - New || New Release (${0}) is available ${1} -Version - New (old) || New Version is available at ${0} -Version FAIL - Read info (old) || Failed to check newest version number -Version FAIL - Read versions.txt || Version information could not be loaded from Github/versions.txt -Web User Listing || ยง2${0} ยง7: ยงf${1} -WebServer - Notify HTTP || WebServer: No Certificate -> Using HTTP-server for Visualization. -WebServer - Notify HTTP User Auth || WebServer: User Authorization Disabled! (Not secure over HTTP) -WebServer - Notify HTTPS User Auth || WebServer: User Authorization Disabled! (Disabled in config) -Webserver - Notify IP Whitelist || Webserver: IP Whitelist is enabled. -Webserver - Notify IP Whitelist Block || Webserver: ${0} was denied access to '${1}'. (not whitelisted) -WebServer - Notify no Cert file || WebServer: Certificate KeyStore File not Found: ${0} -WebServer - Notify Using Proxy || WebServer: Proxy-mode HTTPS enabled, make sure that your reverse-proxy is routing using HTTPS and Plan Alternative_IP.Address points to the Proxy -WebServer FAIL - EOF || WebServer: EOF when reading Certificate file. (Check that the file is not empty) -WebServer FAIL - Port Bind || WebServer was not initialized successfully. Is the port (${0}) in use? -WebServer FAIL - SSL Context || WebServer: SSL Context Initialization Failed. -WebServer FAIL - Store Load || WebServer: SSL Certificate loading Failed. -Yesterday || 'Yesterday' diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_EN.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_EN.yml new file mode 100644 index 000000000..7a19cc643 --- /dev/null +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_EN.yml @@ -0,0 +1,669 @@ +403AccessDenied: "Access Denied" +command: + argument: + backupFile: + description: "Name of the backup file (case sensitive)" + name: "backup-file" + code: + description: "Code used to finalize registration." + name: "${code}" + dbBackup: + description: "Type of the database to backup. Current database is used if not specified." + dbRestore: + description: "Type of the database to restore to. Current database is used if not specified." + dbTypeHotswap: + description: "Type of the database to start using." + dbTypeMoveFrom: + description: "Type of the database to move data from." + dbTypeMoveTo: + description: "Type of the database to move data to. Can not be same as previous." + dbTypeRemove: + description: "Type of the database to remove all data from." + exportKind: "export kind" + feature: + description: "Name of the feature to disable: ${0}" + name: "feature" + importKind: "import kind" + nameOrUUID: + description: "Name or UUID of a player" + name: "name/uuid" + removeDescription: "Identifier for a player that will be removed from current database." + server: + description: "Name, ID or UUID of a server" + name: "server" + subcommand: + description: "Use the command without subcommand to see help." + name: "subcommand" + username: + description: "Username of another user. If not specified player linked user is used." + name: "username" + confirmation: + accept: "Accept" + cancelNoChanges: "Cancelled. No data was changed." + cancelNoUnregister: "Cancelled. '${0}' was not unregistered" + confirm: "Confirm: " + dbClear: "You are about to remove all Plan-data in ${0}" + dbOverwrite: "You are about to overwrite data in Plan ${0} with data in ${1}" + dbRemovePlayer: "You are about to remove data of ${0} from ${1}" + deny: "Cancel" + expired: "Confirmation expired, use the command again" + unregister: "You are about to unregister '${0}' linked to ${1}" + database: + creatingBackup: "Creating a backup file '${0}.db' with contents of ${1}" + failDbNotOpen: "ยงcDatabase is ${0} - Please try again a bit later." + manage: + confirm: "> ยงcAdd '-a' argument to confirm execution: ${0}" + confirmOverwrite: "Data in ${0} will be overwritten!" + confirmPartialRemoval: "Join Address Data for Server ${0} in ${1} will be removed!" + confirmRemoval: "Data in ${0} will be removed!" + fail: "> ยงcSomething went wrong: ${0}" + failFileNotFound: "> ยงcNo File found at ${0}" + failIncorrectDB: "> ยงc'${0}' is not a supported database." + failNoServer: "No server found with given parameters." + failSameDB: "> ยงcCan not operate on to and from the same database!" + failSameServer: "Can not mark this server as uninstalled (You are on it)" + hotswap: "ยงeRemember to swap to the new database (/plan db hotswap ${0}) & reload the plugin." + importers: "Importers:" + preparing: "Preparing.." + progress: "${0} / ${1} processed.." + start: "> ยง2Processing data.." + success: "> ยงaSuccess!" + playerRemoval: "Removing data of ${0} from ${1}.." + removal: "Removing Plan-data from ${0}.." + serverUninstalled: "ยงaIf the server is still installed, it will automatically set itself as installed in the database." + unregister: "Unregistering '${0}'.." + warnDbNotOpen: "ยงeDatabase is ${0} - This might take longer than expected.." + write: "Writing to ${0}.." + fail: + emptyString: "The search string can not be empty" + invalidArguments: "Accepts following as ${0}: ${1}" + invalidUsername: "ยงcUser does not have an UUID." + missingArguments: "ยงcArguments required (${0}) ${1}" + missingFeature: "ยงeDefine a feature to disable! (currently supports ${0})" + missingLink: "User is not linked to your account and you don't have permission to remove other user's accounts." + noPermission: "ยงcYou do not have the required permission." + onAccept: "The accepted action errored upon execution: ${0}" + onDeny: "The denied action errored upon execution: ${0}" + playerNotFound: "Player '${0}' was not found, they have no UUID." + playerNotInDatabase: "Player '${0}' was not found in the database." + seeConfig: "see '${0}' in config.yml" + serverNotFound: "Server '${0}' was not found from the database." + tooManyArguments: "ยงcSingle Argument required ${1}" + unknownUsername: "ยงcUser has not been seen on this server" + webUserExists: "ยงcUser already exists!" + webUserNotFound: "ยงcUser does not exists!" + footer: + help: "ยง7Hover over command or arguments or use '/${0} ?' to learn more about them." + general: + failNoExporter: "ยงeExporter '${0}' doesn't exist" + failNoImporter: "ยงeImporter '${0}' doesn't exist" + featureDisabled: "ยงaDisabled '${0}' temporarily until next plugin reload." + noAddress: "ยงeNo address was available - using localhost as fallback. Set up 'Alternative_IP' settings." + noWebuser: "You might not have a web user, use /plan register " + notifyWebUserRegister: "Registered new user: '${0}' Perm level: ${1}" + pluginDisabled: "ยงaPlan systems are now disabled. You can still use reload to restart the plugin." + reloadComplete: "ยงaReload Complete" + reloadFailed: "ยงcSomething went wrong during reload of the plugin, a restart is recommended." + successWebUserRegister: "ยงaAdded a new user (${0}) successfully!" + webPermissionLevels: ">\ยง70: Access all pages\ยง71: Access '/players' and all player pages\ยง72: Access player page with the same username as the webuser\ยง73+: No permissions" + webUserList: " ยง2${0} ยง7: ยงf${1}" + header: + analysis: "> ยง2Analysis Results" + help: "> ยง2/${0} Help" + info: "> ยง2Player Analytics" + inspect: "> ยง2Player: ยงf${0}" + network: "> ยง2Network Page" + players: "> ยง2Players" + search: "> ยง2${0} Results for ยงf${1}ยง2:" + serverList: "id::name::uuid::version" + servers: "> ยง2Servers" + webUserList: "username::linked to::permission level" + webUsers: "> ยง2${0} Web Users" + help: + database: + description: "Manage Plan database" + inDepth: "Use different database subcommands to change the data in some way" + dbBackup: + description: "Backup data of a database to a file" + inDepth: "Uses SQLite to backup the target database to a file." + dbClear: + description: "Remove ALL Plan data from a database" + inDepth: "Clears all Plan tables, removing all Plan-data in the process." + dbHotswap: + description: "Change Database quickly" + inDepth: "Reloads the plugin with the other database and changes the config to match." + dbMove: + description: "Move data between Databases" + inDepth: "Overwrites contents in the other database with the contents in another." + dbRemove: + description: "Remove player's data from Current database" + inDepth: "Removes all data linked to a player from the Current database." + dbRestore: + description: "Restore data from a file to a database" + inDepth: "Uses SQLite backup file and overwrites contents of the target database." + dbUninstalled: + description: "Set a server as uninstalled in the database." + inDepth: "Marks a server in Plan database as uninstalled so that it will not show up in server queries." + disable: + description: "Disable the plugin or part of it" + inDepth: "Disable the plugin or part of it until next reload/restart." + export: + description: "Export html or json files manually" + inDepth: "Performs an export to export location defined in the config." + import: + description: "Import data" + inDepth: "Performs an import to load data into the database." + info: + description: "Information about the plugin" + inDepth: "Display the current status of the plugin." + ingame: + description: "View Player info in game" + inDepth: "Displays some information about the player in game." + json: + description: "View json of Player's raw data." + inDepth: "Allows you to download a player's data in json format. All of it." + logout: + description: "Log out other users from the panel." + inDepth: "Give username argument to log out another user from the panel, give * as argument to log out everyone." + migrateToOnlineUuids: + description: "Migrate offline uuid data to online uuids" + network: + description: "View the Network Page" + inDepth: "Obtain a link to the /network page, only does so on networks." + player: + description: "View a Player Page" + inDepth: "Obtain a link to the /player page of a specific player, or the current player." + players: + description: "View the Players Page" + inDepth: "Obtain a link to the /players page to see a list of players." + register: + description: "Register a Web User" + inDepth: "Use without arguments to get link to register page. Use --code [code] after registration to get a user." + reload: + description: "Restart Plan" + inDepth: "Disable and enable the plugin to reload any changes in config." + removejoinaddresses: + description: "Remove join addresses of a specified server" + search: + description: "Search for a player name" + inDepth: "List all matching player names to given part of a name." + server: + description: "View the Server Page" + inDepth: "Obtain a link to the /server page of a specific server, or the current server if no arguments are given." + servers: + description: "List servers in Database" + inDepth: "List ids, names and uuids of servers in the database." + unregister: + description: "Unregister a user of Plan website" + inDepth: "Use without arguments to unregister player linked user, or with username argument to unregister another user." + users: + description: "List all web users" + inDepth: "Lists web users as a table." + ingame: + activePlaytime: " ยง2Active Playtime: ยงf${0}" + activityIndex: " ยง2Activity Index: ยงf${0} | ${1}" + afkPlaytime: " ยง2AFK Time: ยงf${0}" + deaths: " ยง2Deaths: ยงf${0}" + geolocation: " ยง2Logged in from: ยงf${0}" + lastSeen: " ยง2Last Seen: ยงf${0}" + longestSession: " ยง2Longest Session: ยงf${0}" + mobKills: " ยง2Mob Kills: ยงf${0}" + playerKills: " ยง2Player Kills: ยงf${0}" + playtime: " ยง2Playtime: ยงf${0}" + registered: " ยง2Registered: ยงf${0}" + timesKicked: " ยง2Times Kicked: ยงf${0}" + link: + clickMe: "Click me" + link: "Link" + network: "Network page: " + noNetwork: "Server is not connected to a network. The link redirects to server page." + player: "Player page: " + playerJson: "Player json: " + players: "Players page: " + register: "Register page: " + server: "Server page: " + subcommand: + info: + database: " ยง2Current Database: ยงf${0}" + proxy: " ยง2Connected to Proxy: ยงf${0}" + update: " ยง2Update Available: ยงf${0}" + version: " ยง2Version: ยงf${0}" +generic: + noData: "No Data" +html: + button: + nightMode: "Night Mode" + calendar: + new: "New:" + unique: "Unique:" + description: + newPlayerRetention: "This value is a prediction based on previous players." + noGameServers: "Some data requires Plan to be installed on game servers." + noGeolocations: "Geolocation gathering needs to be enabled in the config (Accept GeoLite2 EULA)." + noServerOnlinActivity: "No server to display online activity for" + noServers: "No servers found in the database" + noServersLong: 'It appears that Plan is not installed on any game servers or not connected to the same database. See wiki for Network tutorial.' + noSpongeChunks: "Chunks unavailable on Sponge" + predictedNewPlayerRetention: "This value is a prediction based on previous players" + error: + 401Unauthorized: "Unauthorized" + 403Forbidden: "Forbidden" + 404NotFound: "Not Found" + 404PageNotFound: "Page does not exist." + 404UnknownPage: "Make sure you're accessing a link given by a command, Examples:

/player/{uuid/name}
/server/{uuid/name/id}

" + UUIDNotFound: "Player UUID was not found in the database." + auth: + dbClosed: "Database is not open, check db status with /plan info" + emptyForm: "User and Password not specified" + expiredCookie: "User cookie has expired" + generic: "Authentication failed due to error" + loginFailed: "User and Password did not match" + noCookie: "User cookie not present" + registrationFailed: "Registration failed, try again (The code expires after 15 minutes)" + userNotFound: "User does not exist" + authFailed: "Authentication Failed." + authFailedTips: "- Ensure you have registered a user with /plan register
- Check that the username and password are correct
- Username and password are case-sensitive

If you have forgotten your password, ask a staff member to delete your old user and re-register." + noServersOnline: "No Servers online to perform the request." + playerNotSeen: "Plan has not seen this player." + serverNotSeen: "Server doesn't exist" + generic: + none: "None" + label: + active: "Active" + activePlaytime: "Active Playtime" + activityIndex: "Activity Index" + afk: "AFK" + afkTime: "AFK Time" + all: "All" + allTime: "All Time" + alphabetical: "Alphabetical" + apply: "Apply" + asNumbers: "as Numbers" + average: "Average" + averageActivePlaytime: "Average Active Playtime" + averageAfkTime: "Average AFK Time" + averageChunks: "Average Chunks" + averageCpuUsage: "Average CPU Usage" + averageEntities: "Average Entities" + averageKdr: "Average KDR" + averageMobKdr: "Average Mob KDR" + averagePing: "Average Ping" + averagePlayers: "Average Players" + averagePlaytime: "Average Playtime" + averageRamUsage: "Average RAM Usage" + averageServerDowntime: "Average Downtime / Server" + averageSessionLength: "Average Session Length" + averageSessions: "Average Sessions" + averageTps: "Average TPS" + averageTps7days: "Average TPS (7 days)" + banned: "Banned" + bestPeak: "All Time Peak" + bestPing: "Best Ping" + calendar: " Calendar" + comparing7days: "Comparing 7 days" + connectionInfo: "Connection Information" + country: "Country" + cpu: "CPU" + cpuRam: "CPU & RAM" + cpuUsage: "CPU Usage" + currentPlayerbase: "Current Playerbase" + currentUptime: "Current Uptime" + dayByDay: "Day by Day" + dayOfweek: "Day of the Week" + deadliestWeapon: "Deadliest PvP Weapon" + deaths: "Deaths" + disk: "Disk space" + diskSpace: "Free Disk Space" + downtime: "Downtime" + duringLowTps: "During Low TPS Spikes:" + entities: "Entities" + favoriteServer: "Favorite Server" + firstSession: "First session" + firstSessionLength: + average: "Average first session length" + median: "Median first session length" + geoProjection: + dropdown: "Select projection" + equalEarth: "Equal Earth" + mercator: "Mercator" + miller: "Miller" + ortographic: "Ortographic" + geolocations: "Geolocations" + hourByHour: "Hour by Hour" + inactive: "Inactive" + indexInactive: "Inactive" + indexRegular: "Regular" + information: "INFORMATION" + insights: "Insights" + insights30days: "Insights for 30 days" + irregular: "Irregular" + joinAddress: "Join Address" + joinAddresses: "Join Addresses" + kdr: "KDR" + killed: "Killed" + last24hours: "Last 24 hours" + last30days: "Last 30 days" + last7days: "Last 7 days" + lastConnected: "Last Connected" + lastPeak: "Last Peak" + lastSeen: "Last Seen" + latestJoinAddresses: "Latest Join Addresses" + length: " Length" + links: "LINKS" + loadedChunks: "Loaded Chunks" + loadedEntities: "Loaded Entities" + loneJoins: "Lone joins" + loneNewbieJoins: "Lone newbie joins" + longestSession: "Longest Session" + lowTpsSpikes: "Low TPS Spikes" + lowTpsSpikes7days: "Low TPS Spikes (7 days)" + maxFreeDisk: "Max Free Disk" + medianSessionLength: "Median Session Length" + minFreeDisk: "Min Free Disk" + mobDeaths: "Mob caused Deaths" + mobKdr: "Mob KDR" + mobKills: "Mob Kills" + mostActiveGamemode: "Most Active Gamemode" + mostPlayedWorld: "Most played World" + name: "Name" + network: "Network" + networkAsNumbers: "Network as Numbers" + networkOnlineActivity: "Network Online Activity" + networkOverview: "Network Overview" + networkPage: "Network page" + new: "New" + newPlayerRetention: "New Player Retention" + newPlayers: "New Players" + newPlayers7days: "New Players (7 days)" + nickname: "Nickname" + noDataToDisplay: "No Data to Display" + now: "Now" + onlineActivity: "Online Activity" + onlineActivityAsNumbers: "Online Activity as Numbers" + onlineOnFirstJoin: "Players online on first join" + operator: "Operator" + overview: "Overview" + perDay: "/ Day" + perPlayer: "/ Player" + perRegularPlayer: "/ Regular Player" + performance: "Performance" + performanceAsNumbers: "Performance as Numbers" + ping: "Ping" + player: "Player" + playerDeaths: "Player caused Deaths" + playerKills: "Player Kills" + playerList: "Player List" + playerOverview: "Player Overview" + playerPage: "Player Page" + playerRetention: "Player Retention" + playerbase: "Playerbase" + playerbaseDevelopment: "Playerbase Development" + playerbaseOverview: "Playerbase Overview" + players: "Players" + playersOnline: "Players Online" + playersOnlineNow: "Players Online (Now)" + playersOnlineOverview: "Online Activity Overview" + playtime: "Playtime" + plugins: "Plugins" + pluginsOverview: "Plugins Overview" + punchcard: "Punchcard" + punchcard30days: "Punchcard for 30 days" + pvpPve: "PvP & PvE" + pvpPveAsNumbers: "PvP & PvE as Numbers" + query: "Make a query" + quickView: "Quick view" + ram: "RAM" + ramUsage: "RAM Usage" + recentKills: "Recent Kills" + recentPvpDeaths: "Recent PvP Deaths" + recentPvpKills: "Recent PvP Kills" + recentSessions: "Recent Sessions" + registered: "Registered" + registeredPlayers: "Registered Players" + regular: "Regular" + regularPlayers: "Regular Players" + relativeJoinActivity: "Relative Join Activity" + secondDeadliestWeapon: "2nd PvP Weapon" + seenNicknames: "Seen Nicknames" + server: "Server" + serverAnalysis: "Server Analysis" + serverAsNumberse: "Server as Numbers" + serverCalendar: "Server Calendar" + serverDowntime: "Server Downtime" + serverOccupied: "Server occupied" + serverOverview: "Server Overview" + serverPage: "Server page" + serverPlaytime: "Server Playtime" + serverPlaytime30days: "Server Playtime for 30 days" + serverSelector: "Server selector" + servers: "Servers" + serversTitle: "SERVERS" + session: "Session" + sessionCalendar: "Session Calendar" + sessionEnded: " Ended" + sessionMedian: "Session Median" + sessionStart: "Session Started" + sessions: "Sessions" + sortBy: "Sort By" + stacked: "Stacked" + themeSelect: "Theme Select" + thirdDeadliestWeapon: "3rd PvP Weapon" + thirtyDays: "30 days" + thirtyDaysAgo: "30 days ago" + timesKicked: "Times Kicked" + toMainPage: "to main page" + total: "Total" + totalActive: "Total Active" + totalAfk: "Total AFK" + totalPlayers: "Total Players" + totalPlayersOld: "Total Players" + totalPlaytime: "Total Playtime" + totalServerDowntime: "Total Server Downtime" + tps: "TPS" + trend: "Trend" + trends30days: "Trends for 30 days" + uniquePlayers: "Unique Players" + uniquePlayers7days: "Unique Players (7 days)" + veryActive: "Very Active" + weekComparison: "Week Comparison" + weekdays: "'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'" + world: "World Load" + worldPlaytime: "World Playtime" + worstPing: "Worst Ping" + login: + failed: "Login failed: " + forgotPassword: "Forgot Password?" + forgotPassword1: "Forgot password? Unregister and register again." + forgotPassword2: "Use the following command in game to remove your current user:" + forgotPassword3: "Or using console:" + forgotPassword4: "After using the command, " + login: "Login" + logout: "Logout" + password: "Password" + register: "Create an Account!" + username: "Username" + modal: + info: + bugs: "Report Issues" + contributors: + bugreporters: "& Bug reporters!" + code: "code contributor" + donate: "Extra special thanks to those who have monetarily supported the development." + text: 'In addition following awesome people have contributed:' + translator: "translator" + developer: "is developed by" + discord: "General Support on Discord" + license: "Player Analytics is developed and licensed under" + metrics: "bStats Metrics" + text: "Information about the plugin" + wiki: "Plan Wiki, Tutorials & Documentation" + version: + available: "is Available" + changelog: "View Changelog" + dev: "This version is a DEV release." + download: "Download" + text: "A new version has been released and is now available for download." + title: "Version" + query: + filter: + activity: + text: "are in Activity Groups" + banStatus: + name: "Ban status" + banned: "Banned" + country: + text: "have joined from country" + generic: + allPlayers: "All players" + and: "and " + start: "of Players who " + joinAddress: + text: "joined with address" + nonOperators: "Non operators" + notBanned: "Not banned" + operatorStatus: + name: "Operator status" + operators: "Operators" + playedBetween: + text: "Played between" + pluginGroup: + name: "Group: " + text: "are in ${plugin}'s ${group} Groups" + registeredBetween: + text: "Registered between" + title: + activityGroup: "Current activity group" + view: " View:" + filters: + add: "Add a filter.." + loading: "Loading filters.." + generic: + are: "`are`" + label: + from: ">from" + makeAnother: "Make another query" + to: ">to" + view: "Show a view" + performQuery: "Perform Query!" + results: + match: "matched ${resultCount} players" + none: "Query produced 0 results" + title: "Query Results" + title: + activity: "Activity of matched players" + activityOnDate: 'Activity on ' + sessionsWithinView: "Sessions within view" + text: "Query<" + register: + completion: "Complete Registration" + completion1: "You can now finish registering the user." + completion2: "Code expires in 15 minutes" + completion3: "Use the following command in game to finish registration:" + completion4: "Or using console:" + createNewUser: "Create a new user" + error: + checkFailed: "Checking registration status failed: " + failed: "Registration failed: " + noPassword: "You need to specify a Password" + noUsername: "You need to specify a Username" + usernameLength: "Username can be up to 50 characters, yours is " + login: "Already have an account? Login!" + passwordTip: "Password should be more than 8 characters, but there are no limitations." + register: "Register" + usernameTip: "Username can be up to 50 characters." + text: + clickToExpand: "Click to expand" + comparing15days: "Comparing 15 days" + comparing30daysAgo: "Comparing 30d ago to Now" + noExtensionData: "No Extension Data" + noLowTps: "No low tps spikes" + unit: + chunks: "Chunks" + players: "Players" + value: + localMachine: "Local Machine" + noKills: "No Kills" + offline: " Offline" + online: " Online" + with: "With" + version: + changelog: "View Changelog" + current: "You have version ${0}" + download: "Download Plan-${0}.jar" + isDev: "This version is a DEV release." + updateButton: "Update" + updateModal: + text: "A new version has been released and is now available for download." + title: "Version ${0} is Available!" +plugin: + apiCSSAdded: "PageExtension: ${0} added stylesheet(s) to ${1}, ${2}" + apiJSAdded: "PageExtension: ${0} added javascript(s) to ${1}, ${2}" + disable: + database: "Processing critical unprocessed tasks. (${0})" + disabled: "Player Analytics Disabled." + processingComplete: "Processing complete." + savingSessions: "Saving unfinished sessions.." + savingSessionsTimeout: "Timeout hit, storing the unfinished sessions on next enable instead." + waitingDb: "Waiting queries to finish to avoid SQLite crashing JVM.." + waitingDbComplete: "Closed SQLite connection." + waitingTransactions: "Waiting for unfinished transactions to avoid data loss.." + waitingTransactionsComplete: "Transaction queue closed." + webserver: "Webserver has been disabled." + enable: + database: "${0}-database connection established." + enabled: "Player Analytics Enabled." + fail: + database: "${0}-Database Connection failed: ${1}" + databasePatch: "Database Patching failed, plugin has to be disabled. Please report this issue" + databaseType: "${0} is not a supported Database" + geoDBWrite: "Something went wrong saving the downloaded GeoLite2 Geolocation database" + webServer: "WebServer did not initialize!" + notify: + badIP: "0.0.0.0 is not a valid address, set up Alternative_IP settings. Incorrect links might be given!" + emptyIP: "IP in server.properties is empty & Alternative_IP is not in use. Incorrect links might be given!" + geoDisabled: "Geolocation gathering is not active. (Data.Geolocations: false)" + geoInternetRequired: "Plan Requires internet access on first run to download GeoLite2 Geolocation database." + storeSessions: "Storing sessions that were preserved before previous shutdown." + webserverDisabled: "WebServer was not initialized. (WebServer.DisableWebServer: true)" + webserver: "Webserver running on PORT ${0} ( ${1} )" + generic: + dbApplyingPatch: "Applying Patch: ${0}.." + dbFaultyLaunchOptions: "Launch Options were faulty, using default (${0})" + dbNotifyClean: "Removed data of ${0} players." + dbNotifySQLiteWAL: "SQLite WAL mode not supported on this server version, using default. This may or may not affect performance." + dbPatchesAlreadyApplied: "All database patches already applied." + dbPatchesApplied: "All database patches applied successfully." + dbSchemaPatch: "Database: Making sure schema is up to date.." + loadedServerInfo: "Server identifier loaded: ${0}" + loadingServerInfo: "Loading server identifying information" + no: "No" + today: "'Today'" + unavailable: "Unavailable" + unknown: "Unknown" + yes: "Yes" + yesterday: "'Yesterday'" + version: + checkFail: "Failed to check newest version number" + checkFailGithub: "Version information could not be loaded from Github/versions.txt" + isDev: " This is a DEV release." + isLatest: "You're using the latest version." + updateAvailable: "New Release (${0}) is available ${1}" + updateAvailableSpigot: "New Version is available at ${0}" + webserver: + fail: + SSLContext: "WebServer: SSL Context Initialization Failed." + certFileEOF: "WebServer: EOF when reading Certificate file. (Check that the file is not empty)" + certStoreLoad: "WebServer: SSL Certificate loading Failed." + portInUse: "WebServer was not initialized successfully. Is the port (${0}) in use?" + notify: + authDisabledConfig: "WebServer: User Authorization Disabled! (Disabled in config)" + authDisabledNoHTTPS: "WebServer: User Authorization Disabled! (Not secure over HTTP)" + certificateExpiresOn: "Webserver: Loaded certificate is valid until ${0}." + certificateExpiresPassed: "Webserver: Certificate has expired, consider renewing the certificate." + certificateExpiresSoon: "Webserver: Certificate expires in ${0}, consider renewing the certificate." + certificateNoSuchAlias: "Webserver: Certificate with alias '${0}' was not found inside the keystore file '${1}'." + http: "WebServer: No Certificate -> Using HTTP-server for Visualization." + ipWhitelist: "Webserver: IP Whitelist is enabled." + ipWhitelistBlock: "Webserver: ${0} was denied access to '${1}'. (not whitelisted)" + noCertFile: "WebServer: Certificate KeyStore File not Found: ${0}" + reverseProxy: "WebServer: Proxy-mode HTTPS enabled, make sure that your reverse-proxy is routing using HTTPS and Plan Alternative_IP.Address points to the Proxy" diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_ES.txt b/Plan/common/src/main/resources/assets/plan/locale/locale_ES.txt deleted file mode 100644 index 77ca26497..000000000 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_ES.txt +++ /dev/null @@ -1,517 +0,0 @@ -API - css+ || Extensiรณn de Pรกgina: ${0} aรฑadido stylesheet(s) a ${1}, ${2} -API - js+ || Extensiรณn de Pรกgina: ${0} aรฑadido javascript(s) a ${1}, ${2} -Cmd - Click Me || Hazme click -Cmd - Link || Link -Cmd - Link Network || Pรกgina Global: -Cmd - Link Player || Pรกgina de Jugador: -Cmd - Link Player JSON || Json del Jugador: -Cmd - Link Players || Pรกgina de Jugadores: -Cmd - Link Register || Pรกgina de Registro: -Cmd - Link Server || Pรกgina de Servidor: -CMD Arg - backup-file || Nombre del archivo de la copia de seguridad (case sensitive) -CMD Arg - code || Cรณdigo usado para finalizar el registro. -CMD Arg - db type backup || Tipo de base de datos a respaldar. Se utilizara la base de datos actual en caso no especificar. -CMD Arg - db type clear || Tipo de base de datos de la cual se removera todos los datos. -CMD Arg - db type hotswap || Tipo de base de datos que se utilizara. -CMD Arg - db type move from || Tipo de base de datos de la cual se movera informaciรณn. -CMD Arg - db type move to || Tipo de base de datos a la cual se movera la informaciรณn. No puede ser la misma que la anterior. -CMD Arg - db type restore || Tipo de base de datos a restaurar. Se utilizara la base de datos actual en caso no especificar. -CMD Arg - feature || Nombre de la caracterรญstica a deshabilitar: ${0} -CMD Arg - player identifier || Nombre o UUID de un jugador -CMD Arg - player identifier remove || Identificador del jugador que sera eliminado de la base de datos actual. -CMD Arg - server identifier || Nombre, ID o UUID del servidor -CMD Arg - subcommand || Usa el comando sin otro argumento para visualizar la ayuda. -CMD Arg - username || Nombre de usuario de otro jugador. En caso no se especifique, se usara al jugador vinculado. -CMD Arg Name - backup-file || copia de seguridad -CMD Arg Name - code || ${code} -CMD Arg Name - export kind || exportar tipo -CMD Arg Name - feature || caracteristica -CMD Arg Name - import kind || importar tipo -CMD Arg Name - name or uuid || nombre/uuid -CMD Arg Name - server || servidor -CMD Arg Name - subcommand || subcomando -CMD Arg Name - username || nombre de usuario -Cmd Confirm - accept || Aceptar -Cmd Confirm - cancelled, no data change || Cancelado. No hubo ediciรณn de datos. -Cmd Confirm - cancelled, unregister || Cancelado. '${0}' no se encuentra en el registro. -Cmd Confirm - clearing db || Eliminaras todos los datos de plan en ${0} -Cmd Confirm - confirmation || Confirmar: -Cmd Confirm - deny || Cancelar -Cmd Confirm - Expired || Confirmaciรณn expirada, vuelve a usar el comando -Cmd Confirm - Fail on accept || Ocurrio un error al aceptar la acciรณn: ${0} -Cmd Confirm - Fail on deny || Ocurrio un error al denegar la acciรณn: ${0} -Cmd Confirm - overwriting db || Estas a punto de sobreescribir datos en Plan ${0} con los datos en ${1} -Cmd Confirm - remove player db || Eliminaras los datos de ${0} desde ${1} -Cmd Confirm - unregister || Estas a punto de quitar del registro a '${0}' vinculado con ${1} -Cmd db - creating backup || Creando una copia de seguridad de '${0}.db' que contiene ${1} -Cmd db - removal || Removiendo los datos de Plan de ${0}.. -Cmd db - removal player || Removiendo los datos de ${0} desde ${1}.. -Cmd db - server uninstalled || ยงaSi el servidor ya esta instalado, se establecerรก automรกticamente como instalado en la base de datos. -Cmd db - write || Escribiendo a ${0}.. -Cmd Disable - Disabled || ยงaLos sistemas de Plan estan ahora deshabilitados. Puedes usar reload para recargar el plugin. -Cmd FAIL - Accepts only these arguments || Acepta lo siguiente como ${0}: ${1} -Cmd FAIL - Database not open || ยงcLa base de datos es ${0} - Por favor, prueba un poco mรกs tarde. -Cmd FAIL - Empty search string || El argumento de busqueda no puede estar vacio -Cmd FAIL - Invalid Username || ยงcEste usuario no cuenta con UUID. -Cmd FAIL - No Feature || ยงeยกDefine una caracterรญstica para deshabilitarla! (currently supports ${0}) -Cmd FAIL - No Permission || ยงcNo tienes el permiso requerido. -Cmd FAIL - No player || El jugador '${0}' no fue encontrado ya que no cuenta con UUID. -Cmd FAIL - No player register || El jugador '${0}' no fue encontrado en la base de datos. -Cmd FAIL - No server || El servidor '${0}' no fue encontrado en la base de datos. -Cmd FAIL - Require only one Argument || ยงcArgumento รบnico requerido ${1} -Cmd FAIL - Requires Arguments || ยงcArgumentos requeridos (${0}) ${1} -Cmd FAIL - see config || revisa '${0}' en config.yml -Cmd FAIL - Unknown Username || ยงcEste usuario no ha sido visto en el servidor -Cmd FAIL - Users not linked || El usuario no esta vinculado a tu cuenta y no tienes permiso para remover a otro usuarios. -Cmd FAIL - WebUser does not exists || ยงcยกEste usuario no existe! -Cmd FAIL - WebUser exists || ยงcยกEste usuario ya existe! -Cmd Footer - Help || ยง7Pasa el raton encima del comando o argumento o use '/${0} ?' para obtener mรกs informaciรณn. -Cmd Header - Analysis || > ยง2Resultados del anรกlisis -Cmd Header - Help || > ยง2/${0} Ayuda -Cmd Header - Info || > ยง2Anรกlisis del jugador -Cmd Header - Inspect || > ยง2Jugador: ยงf${0} -Cmd Header - Network || > ยง2Pรกgina de red -Cmd Header - Players || > ยง2Jugadores -Cmd Header - Search || > ยง2${0} Resultados para ยงf${1}ยง2: -Cmd Header - server list || id::name::uuid -Cmd Header - Servers || > ยง2Servidores -Cmd Header - web user list || username::linked to::permission level -Cmd Header - Web Users || > ยง2${0} Usuarios web -Cmd Info - Bungee Connection || ยง2Conectado al Proxy: ยงf${0} -Cmd Info - Database || ยง2Base de datos actual: ยงf${0} -Cmd Info - Reload Complete || ยงaRecarga completada -Cmd Info - Reload Failed || ยงcUn error ocurriรณ intentando recargar el plugin. Se recomienda su reseteo. -Cmd Info - Update || ยง2Actualizaciรณn disponible: ยงf${0} -Cmd Info - Version || ยง2Versiรณn: ยงf${0} -Cmd network - No network || El servidor no esta conectado a una network. La direcciรณn web te redireccionara a la pรกgina del servidor. -Cmd Notify - No Address || ยงeNo hay direcciรณn disponible - usando localhost como alternativa. Configura las opciones de 'Alternative_IP'. -Cmd Notify - No WebUser || Puede que no tengas un usuario web, usa /plan register -Cmd Notify - WebUser register || Nuevo usuario registrado: '${0}' Nivel de permisos: ${1} -Cmd Qinspect - Active Playtime || ยง2Active Playtime: ยงf${0} -Cmd Qinspect - Activity Index || ยง2รndice de actividad: ยงf${0} | ${1} -Cmd Qinspect - AFK Playtime || ยง2Tiempo AFK: ยงf${0} -Cmd Qinspect - Deaths || ยง2Muertes: ยงf${0} -Cmd Qinspect - Geolocation || ยง2Conectado desde: ยงf${0} -Cmd Qinspect - Last Seen || ยง2รšltima vez visto: ยงf${0} -Cmd Qinspect - Longest Session || ยง2Sesiรณn mรกs larga: ยงf${0} -Cmd Qinspect - Mob Kills || ยง2Mobs asesinados: ยงf${0} -Cmd Qinspect - Player Kills || ยง2Jugadores asesinados: ยงf${0} -Cmd Qinspect - Playtime || ยง2Tiempo de juego: ยงf${0} -Cmd Qinspect - Registered || ยง2Registrado: ยงf${0} -Cmd Qinspect - Times Kicked || ยง2Veces expulsado: ยงf${0} -Cmd SUCCESS - Feature disabled || ยงaDeshabilitado '${0}' temporalmente hasta la prรณxima recarga del plugin. -Cmd SUCCESS - WebUser register || ยงaยกSe ha aรฑadido al nuevo usuario (${0}) exitosamente! -Cmd unregister - unregistering || Eliminando del registro '${0}'.. -Cmd WARN - Database not open || ยงeLa base de datos es ${0} - Esto puede durar mรกs de lo esperado.. -Cmd Web - Permission Levels || >\ยง70: Acceso a todas las pรกginas\ยง71: Acceso '/players' y a todas las pรกginas de los jugadores\ยง72: Acceso a la pรกgina del jugador con el mismo nombre de usuario que el usuario web\ยง73+: Sin permisos -Command Help - /plan db || Maneja la base de datos de Plan -Command Help - /plan db backup || Realiza una copia de seguridad de la base de datos en un archivo -Command Help - /plan db clear || Remover TODOS los datos de Plan de una base de datos -Command Help - /plan db hotswap || Cambia la base de datos rรกpidamente -Command Help - /plan db move || Mueve los datos entre base de datos -Command Help - /plan db remove || Remover datos del jugador de la base de datos actual -Command Help - /plan db restore || Restaurar datos de un archivo a la base de datos -Command Help - /plan db uninstalled || Establecer un servidor como desinstalado de la base de datos. -Command Help - /plan disable || Desabilitar el plugin o parte del mismo -Command Help - /plan export || Exportar archivos html o json manualmente -Command Help - /plan import || Importar datos -Command Help - /plan info || Informaciรณn sobre el plugin -Command Help - /plan ingame || Ver informaciรณn de los jugadores en el juego -Command Help - /plan json || Ver json de los datos del jugador sin procesar. -Command Help - /plan logout || Cierra la sesiรณn de otro jugador en el panel. -Command Help - /plan network || Ver la pรกgina de la red -Command Help - /plan player || Ver la pรกgina de un jugador -Command Help - /plan players || Ver la pรกgina de los jugadores -Command Help - /plan register || Registrar un usuario web -Command Help - /plan reload || Resetear Plan -Command Help - /plan search || Buscar el nombre de un jugador -Command Help - /plan server || Mira la pรกgina del servidor -Command Help - /plan servers || Listar los servidores en la base de datos -Command Help - /plan unregister || Quitar el registro de un usuario en la pรกgina web de Plan -Command Help - /plan users || Lista de los usuarios web -Database - Apply Patch || Aplicar parche: ${0}.. -Database - Patches Applied || Los parches de la base de datos se han aplicado correctamente. -Database - Patches Applied Already || Los parches de la base de datos ya se han aplicado anteriormente. -Database MySQL - Launch Options Error || Opciones de Lanzamiento fallidos. Usando los predeterminados (${0}) -Database Notify - Clean || Eliminados los datos de ${0} jugadores. -Database Notify - SQLite No WAL || El modo SQLite WAL no esta soportado en esta versiรณn. Usando el predeterminado. Esto puede afectar o no al rendimiento. -Disable || Anรกlisis del jugador deshabilitado. -Disable - Processing || Procesando las tareas no procesadas. (${0}) -Disable - Processing Complete || Procesado completado. -Disable - Unsaved Session Save || Salvando sesiones incompletas.. -Disable - Unsaved Session Save Timeout || Tiempo de espera excedido, almacenando las sesiones sin finalizar para la siguiente habilitaciรณn del servicio. -Disable - Waiting SQLite || Esperando consultas para finalizar, con el fin de evitar que SQLite pueda daรฑar el proceso JVM.. -Disable - Waiting SQLite Complete || Cerrando la conexiรณn SQLite. -Disable - Waiting Transactions || Esperando por transacciones no completadas para evitar perdida de datos.. -Disable - Waiting Transactions Complete || Consulta de la transacciรณn cerradaTransaction queue closed. -Disable - WebServer || Servidores web han sido deshabilitados. -Enable || Analisis de jugadores habilitado. -Enable - Database || ${0}-conexiรณn a la base de datos establecida. -Enable - Notify Bad IP || 0.0.0.0 no es una direcciรณn vรกlida, establece los ajustes AlternativeIP. ยกSe darรกn los links incorrectos! -Enable - Notify Empty IP || La IP en el server.properties esta vacรญa y AlternativeIP no estรก en uso. ยกSe darรกn los links incorrectos! -Enable - Notify Geolocations disabled || La recolecciรณn por Geolocalizaciรณn no estรก activada. (Data.Geolocations: false) -Enable - Notify Geolocations Internet Required || Plan requiere acceso a internet la primera vez para descargar la base de datos de GeoLite2 Geolocation. -Enable - Notify Webserver disabled || El servidor web no se ha iniciado. (WebServer.DisableWebServer: true) -Enable - Storing preserved sessions || Almacenando sesiones que fueron preservadas antes de los apagados recientes. -Enable - WebServer || El servidor web esta funcionando en el puerto ${0} ( ${1} ) -Enable FAIL - Database || ${0}-Conexion a la base de datos incorrecta: ${1} -Enable FAIL - Database Patch || El parcheado de la base de datos ha fallado, el plugin tiene que ser deshabilitado. Por favor, reporta este error. -Enable FAIL - GeoDB Write || Ha ocurrido un error al intentar descargar la base de datos GeoLite2 Geolocation. -Enable FAIL - WebServer (Proxy) || ยกEl servidor web no se ha iniciado! -Enable FAIL - Wrong Database Type || ${0} no es una base de datos sostenible. -HTML - AND_BUG_REPORTERS || & Reporteros de Fallos! -HTML - BANNED (Filters) || Baneado -HTML - COMPARING_15_DAYS || Comparando 15 dias -HTML - COMPARING_60_DAYS || Comparando desde hace 30d hasta ahora -HTML - COMPARING_7_DAYS || Comparando 7 dias -HTML - DATABASE_NOT_OPEN || La base de datos no estรก abierta, mira su estado con /plan info -HTML - DESCRIBE_RETENTION_PREDICTION || Este valor es una predicciรณn basada en jugadores anteriores. -HTML - ERROR || La autenticaciรณn ha llevado a error -HTML - EXPIRED_COOKIE || Las cookies del jugador han expirado -HTML - FILTER_ACTIVITY_INDEX_NOW || Actividad actual del grupo -HTML - FILTER_ALL_PLAYERS || Todos los jugadores -HTML - FILTER_BANNED || Estado de ban -HTML - FILTER_GROUP || Grupo: -HTML - FILTER_OPS || Estado de Operador -HTML - INDEX_ACTIVE || Activo -HTML - INDEX_INACTIVE || Inactivo -HTML - INDEX_IRREGULAR || Irregular -HTML - INDEX_REGULAR || Regular -HTML - INDEX_VERY_ACTIVE || Muy activo -HTML - KILLED || Muerto -HTML - LABEL_1ST_WEAPON || Arma PvP mรกs mortal -HTML - LABEL_2ND_WEAPON || 2ยช arma PvP -HTML - LABEL_3RD_WEAPON || 3ยช arma PvP -HTML - LABEL_ACTIVE_PLAYTIME || Tiempo de juego activo -HTML - LABEL_ACTIVITY_INDEX || รndice de actividad -HTML - LABEL_AFK || AFK -HTML - LABEL_AFK_TIME || Tiempo AFK -HTML - LABEL_AVG || Promedio -HTML - LABEL_AVG_ACTIVE_PLAYTIME || Tiempo de juego activo promedio -HTML - LABEL_AVG_AFK_TIME || Tiempo AFK promedio -HTML - LABEL_AVG_CHUNKS || Chunks Promedio -HTML - LABEL_AVG_ENTITIES || Entidades promedio -HTML - LABEL_AVG_KDR || KDR promedio -HTML - LABEL_AVG_MOB_KDR || KDR de mobs promedio -HTML - LABEL_AVG_PLAYTIME || Jugabilidad promedio -HTML - LABEL_AVG_SESSION_LENGTH || Duraciรณn de sesion promedio -HTML - LABEL_AVG_SESSIONS || Sesiones promedio -HTML - LABEL_AVG_TPS || TPS promedio -HTML - LABEL_BANNED || Baneado -HTML - LABEL_BEST_PEAK || Mejor pico -HTML - LABEL_DAY_OF_WEEK || Dia de la semana -HTML - LABEL_DEATHS || Muertes -HTML - LABEL_DOWNTIME || Falta de tiempo -HTML - LABEL_DURING_LOW_TPS || Durante picos bajos de TPS: -HTML - LABEL_ENTITIES || Entidades -HTML - LABEL_FAVORITE_SERVER || Servidor favorito -HTML - LABEL_FIRST_SESSION_LENGTH || Duraciรณn de primera sesion -HTML - LABEL_FREE_DISK_SPACE || Espacio libre en el disco duro -HTML - LABEL_INACTIVE || Inactivo -HTML - LABEL_LAST_PEAK || รšltimo pico -HTML - LABEL_LAST_SEEN || รšltima vez visto -HTML - LABEL_LOADED_CHUNKS || Chunks cargados -HTML - LABEL_LOADED_ENTITIES || Entidades cargadas -HTML - LABEL_LONE_JOINS || Uniones solitarias -HTML - LABEL_LONE_NEW_JOINS || Uniones solitarias nuevas -HTML - LABEL_LONGEST_SESSION || Sesiรณn mรกs larga -HTML - LABEL_LOW_TPS || Picos bajos TPS -HTML - LABEL_MAX_FREE_DISK || Mรกximo espacio libre en el disco duro -HTML - LABEL_MIN_FREE_DISK || Mรญnimo espacio libre en el disco duro -HTML - LABEL_MOB_DEATHS || Muertes causadas por mobs -HTML - LABEL_MOB_KDR || KDR de mobs -HTML - LABEL_MOB_KILLS || Asesinatos de mobs -HTML - LABEL_MOST_ACTIVE_GAMEMODE || Modo de juego mas activo -HTML - LABEL_NAME || Nombre -HTML - LABEL_NEW || Nuevo -HTML - LABEL_NEW_PLAYERS || Jugadores nuevos -HTML - LABEL_NICKNAME || Nick -HTML - LABEL_NO_SESSION_KILLS || Nada -HTML - LABEL_ONLINE_FIRST_JOIN || Jugadores en lรญnea en primera uniรณn -HTML - LABEL_OPERATOR || Operador. -HTML - LABEL_PER_PLAYER || / Jugador -HTML - LABEL_PER_REGULAR_PLAYER || / Jugador regular -HTML - LABEL_PLAYER_DEATHS || Muertes causadas por jugadores -HTML - LABEL_PLAYER_KILLS || Muertes del jugador -HTML - LABEL_PLAYERS_ONLINE || Jugadores en lรญnea -HTML - LABEL_PLAYTIME || Tiempo de juego -HTML - LABEL_REGISTERED || Registrado -HTML - LABEL_REGISTERED_PLAYERS || Jugadores registrados -HTML - LABEL_REGULAR || Normal -HTML - LABEL_REGULAR_PLAYERS || Jugadores normal -HTML - LABEL_RELATIVE_JOIN_ACTIVITY || Actividad de uniรณn relativa -HTML - LABEL_RETENTION || Retenciรณn nuevo jugador -HTML - LABEL_SERVER_DOWNTIME || Tiempo de falta de servidor -HTML - LABEL_SERVER_OCCUPIED || Servidor ocupado -HTML - LABEL_SESSION_ENDED || Acabado -HTML - LABEL_SESSION_MEDIAN || Sesiรณn media. -HTML - LABEL_TIMES_KICKED || Veces kickeado -HTML - LABEL_TOTAL_PLAYERS || Jugadores totales -HTML - LABEL_TOTAL_PLAYTIME || Jugabilidad total -HTML - LABEL_UNIQUE_PLAYERS || Jugadores รบnicos -HTML - LABEL_WEEK_DAYS || 'Lunes', 'Martes', 'Miรฉrcoles', 'Jueves', 'Viernes', 'Sรกbado', 'Domingo' -HTML - LINK_BACK_NETWORK || Pรกgina web -HTML - LINK_BACK_SERVER || Pรกgina del servidor -HTML - LINK_CHANGELOG || Ver el registro de cambios -HTML - LINK_DISCORD || Soporte general en Discord -HTML - LINK_DOWNLOAD || Descarga -HTML - LINK_ISSUES || Reportar errores -HTML - LINK_NIGHT_MODE || Modo noche -HTML - LINK_PLAYER_PAGE || Pรกgina de jugador -HTML - LINK_QUICK_VIEW || Vista rรกpida -HTML - LINK_SERVER_ANALYSIS || Anรกlisis de servidor -HTML - LINK_WIKI || Wiki, Tutoriales & Documentaciรณn de Plan -HTML - LOCAL_MACHINE || Mรกquina local -HTML - LOGIN_CREATE_ACCOUNT || Crear una cuenta! -HTML - LOGIN_FAILED || Ingreso fallido: -HTML - LOGIN_FORGOT_PASSWORD || Contraseรฑa Olvidada? -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_1 || Contraseรฑa Olvidada? Elimina tu registro y vuelve a registrarte. -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_2 || Usa el siguiente comando en el juego para remover tu usuario actual: -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_3 || O usando consola: -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_4 || Despuรฉs de usar el comando, -HTML - LOGIN_LOGIN || Ingresar -HTML - LOGIN_LOGOUT || Cerrar Sesiรณn -HTML - LOGIN_PASSWORD || "Contraseรฑa" -HTML - LOGIN_USERNAME || "Usuario" -HTML - NAV_PLUGINS || Plugins -HTML - NEW_CALENDAR || Nuevo: -HTML - NO_KILLS || Sin muertes -HTML - NO_USER_PRESENT || Las cookies del jugador no esta presentes -HTML - NON_OPERATORS (Filters) || No se encuentran operadores -HTML - NOT_BANNED (Filters) || No se encuentran baneados -HTML - OFFLINE || Desconectado -HTML - ONLINE || Conectado -HTML - OPERATORS (Filters) || Operadores -HTML - PER_DAY || / Dia -HTML - PLAYERS_TEXT || Jugadores -HTML - QUERY || Consulta< -HTML - QUERY_ACTIVITY_OF_MATCHED_PLAYERS || Actividad de los jugadores encontrados -HTML - QUERY_ACTIVITY_ON || Actividad en -HTML - QUERY_ADD_FILTER || Aรฑadir un filtro.. -HTML - QUERY_AND || y -HTML - QUERY_ARE || `are` -HTML - QUERY_ARE_ACTIVITY_GROUP || are in Activity Groups -HTML - QUERY_ARE_PLUGIN_GROUP || are in ${plugin}'s ${group} Groups -HTML - QUERY_JOINED_WITH_ADDRESS || joined with address -HTML - QUERY_LOADING_FILTERS || Cargando filtros.. -HTML - QUERY_MAKE || Realiza una consulta -HTML - QUERY_MAKE_ANOTHER || Realiza otra consulta -HTML - QUERY_OF_PLAYERS || of Players who -HTML - QUERY_PERFORM_QUERY || Perform Query! -HTML - QUERY_PLAYED_BETWEEN || Jugado entre -HTML - QUERY_REGISTERED_BETWEEN || Registrados entre -HTML - QUERY_RESULTS || Resultados de consulta -HTML - QUERY_RESULTS_MATCH || coincididos ${resultCount} jugadores -HTML - QUERY_SESSIONS_WITHIN_VIEW || Sessions within view -HTML - QUERY_SHOW_VIEW || Mostrar una vista -HTML - QUERY_TIME_FROM || >de -HTML - QUERY_TIME_TO || >a -HTML - QUERY_VIEW || Vista: -HTML - QUERY_ZERO_RESULTS || La consulta ha entregado 0 resultados -HTML - REGISTER || Registro -HTML - REGISTER_CHECK_FAILED || Checking registration status failed: -HTML - REGISTER_COMPLETE || Registro Completado -HTML - REGISTER_COMPLETE_INSTRUCTIONS_1 || Ya puedes finalizar el registro del usuario. -HTML - REGISTER_COMPLETE_INSTRUCTIONS_2 || El cรณdigo expira en 15 minutos -HTML - REGISTER_COMPLETE_INSTRUCTIONS_3 || Usa el siguiente comando en el juego para finalizar el registro: -HTML - REGISTER_COMPLETE_INSTRUCTIONS_4 || O usando la consola: -HTML - REGISTER_CREATE_USER || Crear un nuevo usuario -HTML - REGISTER_FAILED || Registraciรณn fallida: -HTML - REGISTER_HAVE_ACCOUNT || Ya te has registrado? Ingresa! -HTML - REGISTER_PASSWORD_TIP || La contraseรฑa debe ser mayor a 8 caracteres, por lo demรกs, no hay limitaciones -HTML - REGISTER_SPECIFY_PASSWORD || Necesita especificar una contraseรฑa -HTML - REGISTER_SPECIFY_USERNAME || Necesita especificar un nombre de usuario -HTML - REGISTER_USERNAME_LENGTH || El nombre de usuario no puede superar los 50 caracteres, el suyo lo supera -HTML - REGISTER_USERNAME_TIP || El nombre de usuario no puede superar los 50 caracteres. -HTML - SESSION || Sesiรณn -HTML - SIDE_GEOLOCATIONS || Geolocalizaciones -HTML - SIDE_INFORMATION || INFORMACIร“N -HTML - SIDE_LINKS || LINKS -HTML - SIDE_NETWORK_OVERVIEW || Vista general de red -HTML - SIDE_OVERVIEW || Vista general -HTML - SIDE_PERFORMANCE || Rendimiento -HTML - SIDE_PLAYER_LIST || Lista de jugadores -HTML - SIDE_PLAYERBASE || Base de jugadores -HTML - SIDE_PLAYERBASE_OVERVIEW || Vista general de base de jugadores -HTML - SIDE_PLUGINS || PLUGINS -HTML - SIDE_PVP_PVE || PvP y PvE -HTML - SIDE_SERVERS || Servidores -HTML - SIDE_SERVERS_TITLE || SERVERS -HTML - SIDE_SESSIONS || Sesiones -HTML - SIDE_TO_MAIN_PAGE || hasta la pรกgina principal -HTML - TEXT_CLICK_TO_EXPAND || Haz clic para expandir -HTML - TEXT_CONTRIBUTORS_CODE || cรณdigo de contribuidor -HTML - TEXT_CONTRIBUTORS_LOCALE || traductor -HTML - TEXT_CONTRIBUTORS_MONEY || Gracias especialmente a aquellas personas que ayudaron en el desarrollo econรณmico. -HTML - TEXT_CONTRIBUTORS_THANKS || Sobre todo las siguientes personas maravillosas que han contribuido: -HTML - TEXT_DEV_VERSION || Esta versiรณn es para DEV.. -HTML - TEXT_DEVELOPED_BY || esta desarrollado por -HTML - TEXT_FIRST_SESSION || Primera sesiรณn -HTML - TEXT_LICENSED_UNDER || Player Analytics esta desarrollado y licenciado por -HTML - TEXT_METRICS || Metricas bStats -HTML - TEXT_NO_EXTENSION_DATA || Sin extensiรณn de datos. -HTML - TEXT_NO_LOW_TPS || Sin picos bajos TPS. -HTML - TEXT_NO_SERVER || Sin un servidor donde registrar la actividad online. -HTML - TEXT_NO_SERVERS || Ningun servidor encontrado en la base de datos. -HTML - TEXT_PLUGIN_INFORMATION || Informaciรณn sobre el plugin. -HTML - TEXT_PREDICTED_RETENTION || Este valor es una predicciรณn que viene dado por jugadores anteriores. -HTML - TEXT_SERVER_INSTRUCTIONS || It appears that Plan is not installed on any game servers or not connected to the same database. See wiki for Network tutorial. -HTML - TEXT_VERSION || Una nueva versiรณn se ha lanzado y esta lista para ser descargada. -HTML - TITLE_30_DAYS || 30 dรญas -HTML - TITLE_30_DAYS_AGO || Hace 30 dรญas -HTML - TITLE_ALL || Todo -HTML - TITLE_ALL_TIME || Todo el tiempo -HTML - TITLE_AS_NUMBERS || como nรบmeros -HTML - TITLE_AVG_PING || Ping promedio -HTML - TITLE_BEST_PING || Mejor Ping -HTML - TITLE_CALENDAR || Calendario -HTML - TITLE_CONNECTION_INFO || Informaciรณn de conexiรณn -HTML - TITLE_COUNTRY || Paรญs -HTML - TITLE_CPU_RAM || CPU y RAM -HTML - TITLE_CURRENT_PLAYERBASE || base del jugador actual -HTML - TITLE_DISK || Espacio del disco -HTML - TITLE_GRAPH_DAY_BY_DAY || Dรญa a dรญa -HTML - TITLE_GRAPH_HOUR_BY_HOUR || Hora a Hora -HTML - TITLE_GRAPH_NETWORK_ONLINE_ACTIVITY || Actividad online de red -HTML - TITLE_GRAPH_PUNCHCARD || Tarjeta por 30 dรญas -HTML - TITLE_INSIGHTS || Ideas por 30 dรญas -HTML - TITLE_IS_AVAILABLE || estรก disponible -HTML - TITLE_JOIN_ADDRESSES || Direcciones de entrada -HTML - TITLE_LAST_24_HOURS || รšltimas 24 horas -HTML - TITLE_LAST_30_DAYS || รšltimos 30 dias -HTML - TITLE_LAST_7_DAYS || รšltimos 7 dรญas -HTML - TITLE_LAST_CONNECTED || รšltima vez conectado -HTML - TITLE_LENGTH || Duraciรณn -HTML - TITLE_MOST_PLAYED_WORLD || Mundo mรกs jugado -HTML - TITLE_NETWORK || Red -HTML - TITLE_NETWORK_AS_NUMBERS || Red en nรบmeros -HTML - TITLE_NOW || Ahora -HTML - TITLE_ONLINE_ACTIVITY || Actividad online -HTML - TITLE_ONLINE_ACTIVITY_AS_NUMBERS || Actividad online en nรบmeros -HTML - TITLE_ONLINE_ACTIVITY_OVERVIEW || Vista general de la actividad online -HTML - TITLE_PERFORMANCE_AS_NUMBERS || Rendimiento en nรบmeros -HTML - TITLE_PING || Ping -HTML - TITLE_PLAYER || Jugador -HTML - TITLE_PLAYER_OVERVIEW || Vista general de jugador -HTML - TITLE_PLAYERBASE_DEVELOPMENT || Desarrollo de la base de jugadores -HTML - TITLE_PVP_DEATHS || Asesinatos PvP recientes -HTML - TITLE_PVP_KILLS || Muertes PvP recientes -HTML - TITLE_PVP_PVE_NUMBERS || PvP y PvE en nรบmeros -HTML - TITLE_RECENT_KILLS || Asesinatos recientes -HTML - TITLE_RECENT_SESSIONS || Sesiones recientes -HTML - TITLE_SEEN_NICKNAMES || Nombres de usuarios vistos -HTML - TITLE_SERVER || Servidor -HTML - TITLE_SERVER_AS_NUMBERS || Servidor en nรบmeros -HTML - TITLE_SERVER_OVERVIEW || Vista general del servidor -HTML - TITLE_SERVER_PLAYTIME || Jugabilidad en nรบmeros -HTML - TITLE_SERVER_PLAYTIME_30 || Jugabilidad de 30 dรญas -HTML - TITLE_SESSION_START || Sesiรณn iniciada -HTML - TITLE_THEME_SELECT || Selecciรณn de tema -HTML - TITLE_TITLE_PLAYER_PUNCHCARD || Tarjeta -HTML - TITLE_TPS || TPS -HTML - TITLE_TREND || Tendencia -HTML - TITLE_TRENDS || Tendencias de 30 dรญas -HTML - TITLE_VERSION || Versiรณn -HTML - TITLE_WEEK_COMPARISON || Comparaciรณn semanal -HTML - TITLE_WORLD || Carga de mundo -HTML - TITLE_WORLD_PLAYTIME || Jugabilidad de mundo -HTML - TITLE_WORST_PING || Peor ping -HTML - TOTAL_ACTIVE_TEXT || Activos totales -HTML - TOTAL_AFK || AFK total -HTML - TOTAL_PLAYERS || Jugadores totales -HTML - UNIQUE_CALENDAR || รšnicos: -HTML - UNIT_CHUNKS || Chunks -HTML - UNIT_ENTITIES || Entidades -HTML - UNIT_NO_DATA || Sin datos -HTML - UNIT_THE_PLAYERS || Jugadores -HTML - USER_AND_PASS_NOT_SPECIFIED || Usuario y contraseรฑa no especificados -HTML - USER_DOES_NOT_EXIST || El usuario no existe -HTML - USER_INFORMATION_NOT_FOUND || Registro fallido, vuelve a intentar (El cรณdigo expirara en 15 minutos) -HTML - USER_PASS_MISMATCH || El usuario y la contraseรฑa no coincide -HTML - Version Change log || Revisar la lista de cambios -HTML - Version Current || Tienes la version ${0} -HTML - Version Download || Descargar Plan-${0}.jar -HTML - Version Update || Actualizaciรณn -HTML - Version Update Available || La versiรณn ${0} esta disponible! -HTML - Version Update Dev || Esta versiรณn pertenece a una DEV release. -HTML - Version Update Info || Una nueva versiรณn ha sido lanzada y esta lista para descargar. -HTML - WARNING_NO_GAME_SERVERS || Some data requires Plan to be installed on game servers. -HTML - WARNING_NO_GEOLOCATIONS || La recopilaciรณn de la Geolocalizaciรณn necesita ser habilitada en la configuraciรณn (Y aceptar el GeoLite2 EULA). -HTML - WARNING_NO_SPONGE_CHUNKS || Chunks no disponibles en Sponge -HTML - WITH || Con -HTML ERRORS - ACCESS_DENIED_403 || Acceso denegado -HTML ERRORS - AUTH_FAIL_TIPS_401 || - Asegura que has registrado un usuario con /plan register
- Comprueba que el usuario y la contraseรฑa son correctos.
- Se pueden destinguir las minรบsculas y mayรบsculas del nick y la contraseรฑa.

Si has olvidado tu contraseรฑa, pรญdele a un miembro staff que borre la cuenta y te vuelves a registrar. -HTML ERRORS - AUTHENTICATION_FAILED_401 || Autenticaciรณn fallada. -HTML ERRORS - FORBIDDEN_403 || Prohibido -HTML ERRORS - NO_SERVERS_404 || Ningun servidor online donde dejar la solicitud. -HTML ERRORS - NOT_FOUND_404 || No encontrado -HTML ERRORS - NOT_PLAYED_404 || Ese jugador no ha jugado en este servidor. -HTML ERRORS - PAGE_NOT_FOUND_404 || La pรกgina no existe. -HTML ERRORS - UNAUTHORIZED_401 || No autorizado -HTML ERRORS - UNKNOWN_PAGE_404 || Asegurate que estas entrando por un link dado por un comando, Ejemplos:

/player/{uuid/nombre}
/server/{uuid/nombre/id}

-HTML ERRORS - UUID_404 || La UUID del jugador no ha sido encontrada. -In Depth Help - /plan db || Usa diferentes bases de datos para cambiar los datos de alguna manera. -In Depth Help - /plan db backup || Usa SQLite para crear copia de seguridad de la base de datos en un archivo. -In Depth Help - /plan db clear || Limpia todas las tablas de Plan, removiendo todos los datos en el proceso. -In Depth Help - /plan db hotswap || Recarga el plugin con otra base de datos y cambia la configuraciรณn para coincidir los cambios. -In Depth Help - /plan db move || Sobreescribe el contenido de una base de datos con el contenido que se encuentra en otra base de datos. -In Depth Help - /plan db remove || Remueve los datos vinculados a un jugador de la base de datos actual. -In Depth Help - /plan db restore || Usa una copia de seguridad en un archivo SQLite y sobreescribe el contenido de la base de datos especificada. -In Depth Help - /plan db uninstalled || Establece un servidor en la base de datos de Plan como desinstalado y no sera mostrado en la consulta del servidor. -In Depth Help - /plan disable || Deshabilita el plugin o parte de este hasta el proximo reload o reinicio. -In Depth Help - /plan export || Realiza una exportaciรณn al directorio de exportaciรณn definido en la configuraciรณn. -In Depth Help - /plan import || Realiza un importe de los datos hacia la base de datos. -In Depth Help - /plan info || Muestra el estado actual del plugin. -In Depth Help - /plan ingame || Proporciona informaciรณn sobre un jugador en el juego. -In Depth Help - /plan json || Permite descargar todos los datos del jugador en un archivo de formato json. -In Depth Help - /plan logout || Give username argument to log out another user from the panel, give * as argument to log out everyone. -In Depth Help - /plan network || Obten una direcciรณn web a la pรกgina /network, solo en caso tengas configuraciรณn global. -In Depth Help - /plan player || Obten una direcciรณn web a la pรกgina /player de un jugador especifico, o del jugador actual. -In Depth Help - /plan players || Obten una direcciรณn web a la pรกgina /players para ver la lista de jugadores. -In Depth Help - /plan register || Usa sin argumentos para obtener una direcciรณn web a la pรกgina de registro. Usa --code [cรณdigo] despuรฉs del registro para obtener un usuario. -In Depth Help - /plan reload || Deshabilita y habilita el plugin para recargar los cambios en la configuraciรณn. -In Depth Help - /plan search || Lista de todos los jugadores que coincidan con una parte del nombre especificada. -In Depth Help - /plan server || Obtener una direcciรณn web a la pรกgina /server del servidor en especifico o del servidor actual si no se especifica algun argumentos. -In Depth Help - /plan servers || Lista de ids, nombres y uuids de los servidores en la base de datos. -In Depth Help - /plan unregister || Usa sin argumentos para eliminar del registro al jugador vinculado, o con un argumento de nombre de usuario para eliminar su registro. -In Depth Help - /plan users || Lista de usuarios web en una tabla. -Manage - Confirm Overwrite || ยกLos datos en ${0} serรกn sobrescritos! -Manage - Confirm Removal || ยกLos datos en ${0} serรกn removidos! -Manage - Fail || > ยงcAlgo fue mal: ${0} -Manage - Fail File not found || > ยงcNingรบn archivo encontrado en ${0} -Manage - Fail Incorrect Database || > ยงc'${0}' no es una base de datos permitida. -Manage - Fail No Exporter || ยงeEl exportador '${0}' no existe -Manage - Fail No Importer || ยงeEl importador '${0}' no existe -Manage - Fail No Server || Ningรบn servidor encontrado con los parรกmetros dados. -Manage - Fail Same Database || > ยงcยกNo se puede operar desde y para la misma base de datos! -Manage - Fail Same server || No se ha podido marcar el servidor como desinstalado (Estas en el) -Manage - Fail, Confirmation || > ยงcAรฑade el argumento '-a' para confirmar la ejecuciรณn: ${0} -Manage - List Importers || Importadores: -Manage - Progress || ${0} / ${1} procesando.. -Manage - Remind HotSwap || ยงeRecuerda cambiarte a la nueva base de datos (/plan db hotswap ${0}) & recarga el plugin. -Manage - Start || > ยง2Procesando datos.. -Manage - Success || > ยงaยกร‰xito! -Negative || No -Positive || Si -Today || 'Hoy' -Unavailable || No disponible -Unknown || Desconocido -Version - DEV || Este es un lanzamiento para DEV. -Version - Latest || Estas usando la version mรกs reciente. -Version - New || Nuevo lanzamiento (${0}) disponible ${1} -Version - New (old) || Nueva versiรณn disponible en ${0} -Version FAIL - Read info (old) || Error en comprobar el numero de la nueva versiรณn. -Version FAIL - Read versions.txt || La informaciรณn de la versiรณn no se ha podido cargar desde Github/versions.txt -Web User Listing || ยง2${0} ยง7: ยงf${1} -WebServer - Notify HTTP || Servidor web: No Certificado -> Usando HTTP-server para visualizaciรณn. -WebServer - Notify HTTP User Auth || Servidor web: Autorizaciรณn del usuario desactivada! (No seguro en HTTP) -WebServer - Notify HTTPS User Auth || Servidor web: Autorizaciรณn del usuario desactivada! (Deshabilitada en la configuracion) -Webserver - Notify IP Whitelist || Servidor web: La lista blanca por IP fue habilitada. -Webserver - Notify IP Whitelist Block || Servidor web: ${0} fue denegado del acceso a '${1}'. (no se encuentra en la lista de permitidos) -WebServer - Notify no Cert file || Servidor web: Certificado de archivo KeyStore no encontrado: ${0} -WebServer - Notify Using Proxy || Servidor web: Modo Proxy HTTPS habilitado, asegurate que tu reverse-proxy estรก usando HTTPS y los puntos del Proxy Plan AlternativeIP.Link -WebServer FAIL - EOF || Servidor web: EOF leyendo el archivo de Certificado. (Comprueba que el archivo no estรก vacรญo) -WebServer FAIL - Port Bind || El servidor web no se ha iniciado exitosamente. ยฟEsta el puerto (${0}) en uso? -WebServer FAIL - SSL Context || Servidor web: Inicializaciรณn de Contexto SSL erroneo. -WebServer FAIL - Store Load || Servidor web: Carga del Certificado SSL erronea. -Yesterday || 'Ayer' diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_ES.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_ES.yml new file mode 100644 index 000000000..13296633b --- /dev/null +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_ES.yml @@ -0,0 +1,669 @@ +403AccessDenied: "Acceso denegado" +command: + argument: + backupFile: + description: "Nombre del archivo de la copia de seguridad (case sensitive)" + name: "copia de seguridad" + code: + description: "Cรณdigo usado para finalizar el registro." + name: "${code}" + dbBackup: + description: "Tipo de base de datos a respaldar. Se utilizara la base de datos actual en caso no especificar." + dbRestore: + description: "Tipo de base de datos a restaurar. Se utilizara la base de datos actual en caso no especificar." + dbTypeHotswap: + description: "Tipo de base de datos que se utilizara." + dbTypeMoveFrom: + description: "Tipo de base de datos de la cual se movera informaciรณn." + dbTypeMoveTo: + description: "Tipo de base de datos a la cual se movera la informaciรณn. No puede ser la misma que la anterior." + dbTypeRemove: + description: "Tipo de base de datos de la cual se removera todos los datos." + exportKind: "exportar tipo" + feature: + description: "Nombre de la caracterรญstica a deshabilitar: ${0}" + name: "caracteristica" + importKind: "importar tipo" + nameOrUUID: + description: "Nombre o UUID de un jugador" + name: "nombre/uuid" + removeDescription: "Identificador del jugador que sera eliminado de la base de datos actual." + server: + description: "Nombre, ID o UUID del servidor" + name: "servidor" + subcommand: + description: "Usa el comando sin otro argumento para visualizar la ayuda." + name: "subcomando" + username: + description: "Nombre de usuario de otro jugador. En caso no se especifique, se usara al jugador vinculado." + name: "nombre de usuario" + confirmation: + accept: "Aceptar" + cancelNoChanges: "Cancelado. No hubo ediciรณn de datos." + cancelNoUnregister: "Cancelado. '${0}' no se encuentra en el registro." + confirm: "Confirmar: " + dbClear: "Eliminaras todos los datos de plan en ${0}" + dbOverwrite: "Estas a punto de sobreescribir datos en Plan ${0} con los datos en ${1}" + dbRemovePlayer: "Eliminaras los datos de ${0} desde ${1}" + deny: "Cancelar" + expired: "Confirmaciรณn expirada, vuelve a usar el comando" + unregister: "Estas a punto de quitar del registro a '${0}' vinculado con ${1}" + database: + creatingBackup: "Creando una copia de seguridad de '${0}.db' que contiene ${1}" + failDbNotOpen: "ยงcLa base de datos es ${0} - Por favor, prueba un poco mรกs tarde." + manage: + confirm: "> ยงcAรฑade el argumento '-a' para confirmar la ejecuciรณn: ${0}" + confirmOverwrite: "ยกLos datos en ${0} serรกn sobrescritos!" + confirmPartialRemoval: "Join Address Data for Server ${0} in ${1} will be removed!" + confirmRemoval: "ยกLos datos en ${0} serรกn removidos!" + fail: "> ยงcAlgo fue mal: ${0}" + failFileNotFound: "> ยงcNingรบn archivo encontrado en ${0}" + failIncorrectDB: "> ยงc'${0}' no es una base de datos permitida." + failNoServer: "Ningรบn servidor encontrado con los parรกmetros dados." + failSameDB: "> ยงcยกNo se puede operar desde y para la misma base de datos!" + failSameServer: "No se ha podido marcar el servidor como desinstalado (Estas en el)" + hotswap: "ยงeRecuerda cambiarte a la nueva base de datos (/plan db hotswap ${0}) & recarga el plugin." + importers: "Importadores: " + preparing: "Preparing.." + progress: "${0} / ${1} procesando.." + start: "> ยง2Procesando datos.." + success: "> ยงaยกร‰xito!" + playerRemoval: "Removiendo los datos de ${0} desde ${1}.." + removal: "Removiendo los datos de Plan de ${0}.." + serverUninstalled: "ยงaSi el servidor ya esta instalado, se establecerรก automรกticamente como instalado en la base de datos." + unregister: "Eliminando del registro '${0}'.." + warnDbNotOpen: "ยงeLa base de datos es ${0} - Esto puede durar mรกs de lo esperado.." + write: "Escribiendo a ${0}.." + fail: + emptyString: "El argumento de busqueda no puede estar vacio" + invalidArguments: "Acepta lo siguiente como ${0}: ${1}" + invalidUsername: "ยงcEste usuario no cuenta con UUID." + missingArguments: "ยงcArgumentos requeridos (${0}) ${1}" + missingFeature: "ยงeยกDefine una caracterรญstica para deshabilitarla! (currently supports ${0})" + missingLink: "El usuario no esta vinculado a tu cuenta y no tienes permiso para remover a otro usuarios." + noPermission: "ยงcNo tienes el permiso requerido." + onAccept: "Ocurrio un error al aceptar la acciรณn: ${0}" + onDeny: "Ocurrio un error al denegar la acciรณn: ${0}" + playerNotFound: "El jugador '${0}' no fue encontrado ya que no cuenta con UUID." + playerNotInDatabase: "El jugador '${0}' no fue encontrado en la base de datos." + seeConfig: "revisa '${0}' en config.yml" + serverNotFound: "El servidor '${0}' no fue encontrado en la base de datos." + tooManyArguments: "ยงcArgumento รบnico requerido ${1}" + unknownUsername: "ยงcEste usuario no ha sido visto en el servidor" + webUserExists: "ยงcยกEste usuario ya existe!" + webUserNotFound: "ยงcยกEste usuario no existe!" + footer: + help: "ยง7Pasa el raton encima del comando o argumento o use '/${0} ?' para obtener mรกs informaciรณn." + general: + failNoExporter: "ยงeEl exportador '${0}' no existe" + failNoImporter: "ยงeEl importador '${0}' no existe" + featureDisabled: "ยงaDeshabilitado '${0}' temporalmente hasta la prรณxima recarga del plugin." + noAddress: "ยงeNo hay direcciรณn disponible - usando localhost como alternativa. Configura las opciones de 'Alternative_IP'." + noWebuser: "Puede que no tengas un usuario web, usa /plan register " + notifyWebUserRegister: "Nuevo usuario registrado: '${0}' Nivel de permisos: ${1}" + pluginDisabled: "ยงaLos sistemas de Plan estan ahora deshabilitados. Puedes usar reload para recargar el plugin." + reloadComplete: "ยงaRecarga completada" + reloadFailed: "ยงcUn error ocurriรณ intentando recargar el plugin. Se recomienda su reseteo." + successWebUserRegister: "ยงaยกSe ha aรฑadido al nuevo usuario (${0}) exitosamente!" + webPermissionLevels: ">\ยง70: Acceso a todas las pรกginas\ยง71: Acceso '/players' y a todas las pรกginas de los jugadores\ยง72: Acceso a la pรกgina del jugador con el mismo nombre de usuario que el usuario web\ยง73+: Sin permisos" + webUserList: " ยง2${0} ยง7: ยงf${1}" + header: + analysis: "> ยง2Resultados del anรกlisis" + help: "> ยง2/${0} Ayuda" + info: "> ยง2Anรกlisis del jugador" + inspect: "> ยง2Jugador: ยงf${0}" + network: "> ยง2Pรกgina de red" + players: "> ยง2Jugadores" + search: "> ยง2${0} Resultados para ยงf${1}ยง2:" + serverList: "id::name::uuid::version" + servers: "> ยง2Servidores" + webUserList: "username::linked to::permission level" + webUsers: "> ยง2${0} Usuarios web" + help: + database: + description: "Maneja la base de datos de Plan" + inDepth: "Usa diferentes bases de datos para cambiar los datos de alguna manera." + dbBackup: + description: "Realiza una copia de seguridad de la base de datos en un archivo" + inDepth: "Usa SQLite para crear copia de seguridad de la base de datos en un archivo." + dbClear: + description: "Remover TODOS los datos de Plan de una base de datos" + inDepth: "Limpia todas las tablas de Plan, removiendo todos los datos en el proceso." + dbHotswap: + description: "Cambia la base de datos rรกpidamente" + inDepth: "Recarga el plugin con otra base de datos y cambia la configuraciรณn para coincidir los cambios." + dbMove: + description: "Mueve los datos entre base de datos" + inDepth: "Sobreescribe el contenido de una base de datos con el contenido que se encuentra en otra base de datos." + dbRemove: + description: "Remover datos del jugador de la base de datos actual" + inDepth: "Remueve los datos vinculados a un jugador de la base de datos actual." + dbRestore: + description: "Restaurar datos de un archivo a la base de datos" + inDepth: "Usa una copia de seguridad en un archivo SQLite y sobreescribe el contenido de la base de datos especificada." + dbUninstalled: + description: "Establecer un servidor como desinstalado de la base de datos." + inDepth: "Establece un servidor en la base de datos de Plan como desinstalado y no sera mostrado en la consulta del servidor." + disable: + description: "Desabilitar el plugin o parte del mismo" + inDepth: "Deshabilita el plugin o parte de este hasta el proximo reload o reinicio." + export: + description: "Exportar archivos html o json manualmente" + inDepth: "Realiza una exportaciรณn al directorio de exportaciรณn definido en la configuraciรณn." + import: + description: "Importar datos" + inDepth: "Realiza un importe de los datos hacia la base de datos." + info: + description: "Informaciรณn sobre el plugin" + inDepth: "Muestra el estado actual del plugin." + ingame: + description: "Ver informaciรณn de los jugadores en el juego" + inDepth: "Proporciona informaciรณn sobre un jugador en el juego." + json: + description: "Ver json de los datos del jugador sin procesar." + inDepth: "Permite descargar todos los datos del jugador en un archivo de formato json." + logout: + description: "Cierra la sesiรณn de otro jugador en el panel." + inDepth: "Give username argument to log out another user from the panel, give * as argument to log out everyone." + migrateToOnlineUuids: + description: "Migrate offline uuid data to online uuids" + network: + description: "Ver la pรกgina de la red" + inDepth: "Obten una direcciรณn web a la pรกgina /network, solo en caso tengas configuraciรณn global." + player: + description: "Ver la pรกgina de un jugador" + inDepth: "Obten una direcciรณn web a la pรกgina /player de un jugador especifico, o del jugador actual." + players: + description: "Ver la pรกgina de los jugadores" + inDepth: "Obten una direcciรณn web a la pรกgina /players para ver la lista de jugadores." + register: + description: "Registrar un usuario web" + inDepth: "Usa sin argumentos para obtener una direcciรณn web a la pรกgina de registro. Usa --code [cรณdigo] despuรฉs del registro para obtener un usuario." + reload: + description: "Resetear Plan" + inDepth: "Deshabilita y habilita el plugin para recargar los cambios en la configuraciรณn." + removejoinaddresses: + description: "Remove join addresses of a specified server" + search: + description: "Buscar el nombre de un jugador" + inDepth: "Lista de todos los jugadores que coincidan con una parte del nombre especificada." + server: + description: "Mira la pรกgina del servidor" + inDepth: "Obtener una direcciรณn web a la pรกgina /server del servidor en especifico o del servidor actual si no se especifica algun argumentos." + servers: + description: "Listar los servidores en la base de datos" + inDepth: "Lista de ids, nombres y uuids de los servidores en la base de datos." + unregister: + description: "Quitar el registro de un usuario en la pรกgina web de Plan" + inDepth: "Usa sin argumentos para eliminar del registro al jugador vinculado, o con un argumento de nombre de usuario para eliminar su registro." + users: + description: "Lista de los usuarios web" + inDepth: "Lista de usuarios web en una tabla." + ingame: + activePlaytime: " ยง2Active Playtime: ยงf${0}" + activityIndex: " ยง2รndice de actividad: ยงf${0} | ${1}" + afkPlaytime: " ยง2Tiempo AFK: ยงf${0}" + deaths: " ยง2Muertes: ยงf${0}" + geolocation: " ยง2Conectado desde: ยงf${0}" + lastSeen: " ยง2รšltima vez visto: ยงf${0}" + longestSession: " ยง2Sesiรณn mรกs larga: ยงf${0}" + mobKills: " ยง2Mobs asesinados: ยงf${0}" + playerKills: " ยง2Jugadores asesinados: ยงf${0}" + playtime: " ยง2Tiempo de juego: ยงf${0}" + registered: " ยง2Registrado: ยงf${0}" + timesKicked: " ยง2Veces expulsado: ยงf${0}" + link: + clickMe: "Hazme click" + link: "Link" + network: "Pรกgina Global: " + noNetwork: "El servidor no esta conectado a una network. La direcciรณn web te redireccionara a la pรกgina del servidor." + player: "Pรกgina de Jugador: " + playerJson: "Json del Jugador: " + players: "Pรกgina de Jugadores: " + register: "Pรกgina de Registro: " + server: "Pรกgina de Servidor: " + subcommand: + info: + database: " ยง2Base de datos actual: ยงf${0}" + proxy: " ยง2Conectado al Proxy: ยงf${0}" + update: " ยง2Actualizaciรณn disponible: ยงf${0}" + version: " ยง2Versiรณn: ยงf${0}" +generic: + noData: "Sin datos" +html: + button: + nightMode: "Modo noche" + calendar: + new: "Nuevo:" + unique: "รšnicos:" + description: + newPlayerRetention: "Este valor es una predicciรณn basada en jugadores anteriores." + noGameServers: "Some data requires Plan to be installed on game servers." + noGeolocations: "La recopilaciรณn de la Geolocalizaciรณn necesita ser habilitada en la configuraciรณn (Y aceptar el GeoLite2 EULA)." + noServerOnlinActivity: "Sin un servidor donde registrar la actividad online." + noServers: "Ningun servidor encontrado en la base de datos." + noServersLong: 'It appears that Plan is not installed on any game servers or not connected to the same database. See wiki for Network tutorial.' + noSpongeChunks: "Chunks no disponibles en Sponge" + predictedNewPlayerRetention: "Este valor es una predicciรณn que viene dado por jugadores anteriores." + error: + 401Unauthorized: "No autorizado" + 403Forbidden: "Prohibido" + 404NotFound: "No encontrado" + 404PageNotFound: "La pรกgina no existe." + 404UnknownPage: "Asegurate que estas entrando por un link dado por un comando, Ejemplos:

/player/{uuid/nombre}
/server/{uuid/nombre/id}

" + UUIDNotFound: "La UUID del jugador no ha sido encontrada." + auth: + dbClosed: "La base de datos no estรก abierta, mira su estado con /plan info" + emptyForm: "Usuario y contraseรฑa no especificados" + expiredCookie: "Las cookies del jugador han expirado" + generic: "La autenticaciรณn ha llevado a error" + loginFailed: "El usuario y la contraseรฑa no coincide" + noCookie: "Las cookies del jugador no esta presentes" + registrationFailed: "Registro fallido, vuelve a intentar (El cรณdigo expirara en 15 minutos)" + userNotFound: "El usuario no existe" + authFailed: "Autenticaciรณn fallada." + authFailedTips: "- Asegura que has registrado un usuario con /plan register
- Comprueba que el usuario y la contraseรฑa son correctos.
- Se pueden destinguir las minรบsculas y mayรบsculas del nick y la contraseรฑa.

Si has olvidado tu contraseรฑa, pรญdele a un miembro staff que borre la cuenta y te vuelves a registrar." + noServersOnline: "Ningun servidor online donde dejar la solicitud." + playerNotSeen: "Ese jugador no ha jugado en este servidor." + serverNotSeen: "Server doesn't exist" + generic: + none: "Nada" + label: + active: "Activo" + activePlaytime: "Tiempo de juego activo" + activityIndex: "รndice de actividad" + afk: "AFK" + afkTime: "Tiempo AFK" + all: "Todo" + allTime: "Todo el tiempo" + alphabetical: "Alphabetical" + apply: "Apply" + asNumbers: "como nรบmeros" + average: "Promedio" + averageActivePlaytime: "Tiempo de juego activo promedio" + averageAfkTime: "Tiempo AFK promedio" + averageChunks: "Chunks Promedio" + averageCpuUsage: "Average CPU Usage" + averageEntities: "Entidades promedio" + averageKdr: "KDR promedio" + averageMobKdr: "KDR de mobs promedio" + averagePing: "Ping promedio" + averagePlayers: "Average Players" + averagePlaytime: "Jugabilidad promedio" + averageRamUsage: "Average RAM Usage" + averageServerDowntime: "Average Downtime / Server" + averageSessionLength: "Duraciรณn de sesion promedio" + averageSessions: "Sesiones promedio" + averageTps: "TPS promedio" + averageTps7days: "Average TPS (7 days)" + banned: "Baneado" + bestPeak: "Mejor pico" + bestPing: "Mejor Ping" + calendar: " Calendario" + comparing7days: "Comparando 7 dias" + connectionInfo: "Informaciรณn de conexiรณn" + country: "Paรญs" + cpu: "CPU" + cpuRam: "CPU y RAM" + cpuUsage: "CPU Usage" + currentPlayerbase: "base del jugador actual" + currentUptime: "Current Uptime" + dayByDay: "Dรญa a dรญa" + dayOfweek: "Dia de la semana" + deadliestWeapon: "Arma PvP mรกs mortal" + deaths: "Muertes" + disk: "Espacio del disco" + diskSpace: "Espacio libre en el disco duro" + downtime: "Falta de tiempo" + duringLowTps: "Durante picos bajos de TPS:" + entities: "Entidades" + favoriteServer: "Servidor favorito" + firstSession: "Primera sesiรณn" + firstSessionLength: + average: "Average first session length" + median: "Median first session length" + geoProjection: + dropdown: "Select projection" + equalEarth: "Equal Earth" + mercator: "Mercator" + miller: "Miller" + ortographic: "Ortographic" + geolocations: "Geolocalizaciones" + hourByHour: "Hora a Hora" + inactive: "Inactivo" + indexInactive: "Inactivo" + indexRegular: "Regular" + information: "INFORMACIร“N" + insights: "Insights" + insights30days: "Ideas por 30 dรญas" + irregular: "Irregular" + joinAddress: "Join Address" + joinAddresses: "Direcciones de entrada" + kdr: "KDR" + killed: "Muerto" + last24hours: "รšltimas 24 horas" + last30days: "รšltimos 30 dias" + last7days: "รšltimos 7 dรญas" + lastConnected: "รšltima vez conectado" + lastPeak: "รšltimo pico" + lastSeen: "รšltima vez visto" + latestJoinAddresses: "Latest Join Addresses" + length: " Duraciรณn" + links: "LINKS" + loadedChunks: "Chunks cargados" + loadedEntities: "Entidades cargadas" + loneJoins: "Uniones solitarias" + loneNewbieJoins: "Uniones solitarias nuevas" + longestSession: "Sesiรณn mรกs larga" + lowTpsSpikes: "Picos bajos TPS" + lowTpsSpikes7days: "Low TPS Spikes (7 days)" + maxFreeDisk: "Mรกximo espacio libre en el disco duro" + medianSessionLength: "Median Session Length" + minFreeDisk: "Mรญnimo espacio libre en el disco duro" + mobDeaths: "Muertes causadas por mobs" + mobKdr: "KDR de mobs" + mobKills: "Asesinatos de mobs" + mostActiveGamemode: "Modo de juego mas activo" + mostPlayedWorld: "Mundo mรกs jugado" + name: "Nombre" + network: "Red" + networkAsNumbers: "Red en nรบmeros" + networkOnlineActivity: "Actividad online de red" + networkOverview: "Vista general de red" + networkPage: "Pรกgina web" + new: "Nuevo" + newPlayerRetention: "Retenciรณn nuevo jugador" + newPlayers: "Jugadores nuevos" + newPlayers7days: "New Players (7 days)" + nickname: "Nick" + noDataToDisplay: "No Data to Display" + now: "Ahora" + onlineActivity: "Actividad online" + onlineActivityAsNumbers: "Actividad online en nรบmeros" + onlineOnFirstJoin: "Jugadores en lรญnea en primera uniรณn" + operator: "Operador" + overview: "Vista general" + perDay: "/ Dia" + perPlayer: "/ Jugador" + perRegularPlayer: "/ Jugador regular" + performance: "Rendimiento" + performanceAsNumbers: "Rendimiento en nรบmeros" + ping: "Ping" + player: "Jugador" + playerDeaths: "Muertes causadas por jugadores" + playerKills: "Muertes del jugador" + playerList: "Lista de jugadores" + playerOverview: "Vista general de jugador" + playerPage: "Pรกgina de jugador" + playerRetention: "Player Retention" + playerbase: "Base de jugadores" + playerbaseDevelopment: "Desarrollo de la base de jugadores" + playerbaseOverview: "Playerbase Overview" + players: "Jugadores" + playersOnline: "Jugadores en lรญnea" + playersOnlineNow: "Players Online (Now)" + playersOnlineOverview: "Vista general de la actividad online" + playtime: "Tiempo de juego" + plugins: "Plugins" + pluginsOverview: "Plugins Overview" + punchcard: "Tarjeta" + punchcard30days: "Tarjeta por 30 dรญas" + pvpPve: "PvP y PvE" + pvpPveAsNumbers: "PvP y PvE en nรบmeros" + query: "Realiza una consulta" + quickView: "Vista rรกpida" + ram: "RAM" + ramUsage: "RAM Usage" + recentKills: "Asesinatos recientes" + recentPvpDeaths: "Asesinatos PvP recientes" + recentPvpKills: "Muertes PvP recientes" + recentSessions: "Sesiones recientes" + registered: "Registrado" + registeredPlayers: "Jugadores registrados" + regular: "Normal" + regularPlayers: "Jugadores normal" + relativeJoinActivity: "Actividad de uniรณn relativa" + secondDeadliestWeapon: "2ยช arma PvP" + seenNicknames: "Nombres de usuarios vistos" + server: "Servidor" + serverAnalysis: "Anรกlisis de servidor" + serverAsNumberse: "Servidor en nรบmeros" + serverCalendar: "Server Calendar" + serverDowntime: "Tiempo de falta de servidor" + serverOccupied: "Servidor ocupado" + serverOverview: "Vista general del servidor" + serverPage: "Pรกgina del servidor" + serverPlaytime: "Jugabilidad en nรบmeros" + serverPlaytime30days: "Jugabilidad de 30 dรญas" + serverSelector: "Server selector" + servers: "Servidores" + serversTitle: "SERVERS" + session: "Sesiรณn" + sessionCalendar: "Session Calendar" + sessionEnded: " Acabado" + sessionMedian: "Sesiรณn media." + sessionStart: "Sesiรณn iniciada" + sessions: "Sesiones" + sortBy: "Sort By" + stacked: "Stacked" + themeSelect: "Selecciรณn de tema" + thirdDeadliestWeapon: "3ยช arma PvP" + thirtyDays: "30 dรญas" + thirtyDaysAgo: "Hace 30 dรญas" + timesKicked: "Veces kickeado" + toMainPage: "hasta la pรกgina principal" + total: "Total" + totalActive: "Activos totales" + totalAfk: "AFK total" + totalPlayers: "Jugadores totales" + totalPlayersOld: "Jugadores totales" + totalPlaytime: "Jugabilidad total" + totalServerDowntime: "Total Server Downtime" + tps: "TPS" + trend: "Tendencia" + trends30days: "Tendencias de 30 dรญas" + uniquePlayers: "Jugadores รบnicos" + uniquePlayers7days: "Unique Players (7 days)" + veryActive: "Muy activo" + weekComparison: "Comparaciรณn semanal" + weekdays: "'Lunes', 'Martes', 'Miรฉrcoles', 'Jueves', 'Viernes', 'Sรกbado', 'Domingo'" + world: "Carga de mundo" + worldPlaytime: "Jugabilidad de mundo" + worstPing: "Peor ping" + login: + failed: "Ingreso fallido: " + forgotPassword: "Contraseรฑa Olvidada?" + forgotPassword1: "Contraseรฑa Olvidada? Elimina tu registro y vuelve a registrarte." + forgotPassword2: "Usa el siguiente comando en el juego para remover tu usuario actual:" + forgotPassword3: "O usando consola:" + forgotPassword4: "Despuรฉs de usar el comando, " + login: "Ingresar" + logout: "Cerrar Sesiรณn" + password: "Contraseรฑa" + register: "Crear una cuenta!" + username: "Usuario" + modal: + info: + bugs: "Reportar errores" + contributors: + bugreporters: "& Reporteros de Fallos!" + code: "cรณdigo de contribuidor" + donate: "Gracias especialmente a aquellas personas que ayudaron en el desarrollo econรณmico." + text: 'Sobre todo las siguientes personas maravillosas que han contribuido:' + translator: "traductor" + developer: "esta desarrollado por" + discord: "Soporte general en Discord" + license: "Player Analytics esta desarrollado y licenciado por" + metrics: "Metricas bStats" + text: "Informaciรณn sobre el plugin." + wiki: "Wiki, Tutoriales & Documentaciรณn de Plan" + version: + available: "estรก disponible" + changelog: "Ver el registro de cambios" + dev: "Esta versiรณn es para DEV.." + download: "Descarga" + text: "Una nueva versiรณn se ha lanzado y esta lista para ser descargada." + title: "Versiรณn" + query: + filter: + activity: + text: "are in Activity Groups" + banStatus: + name: "Estado de ban" + banned: "Baneado" + country: + text: "have joined from country" + generic: + allPlayers: "Todos los jugadores" + and: "y " + start: "of Players who " + joinAddress: + text: "joined with address" + nonOperators: "No se encuentran operadores" + notBanned: "No se encuentran baneados" + operatorStatus: + name: "Estado de Operador" + operators: "Operadores" + playedBetween: + text: "Jugado entre" + pluginGroup: + name: "Grupo: " + text: "are in ${plugin}'s ${group} Groups" + registeredBetween: + text: "Registrados entre" + title: + activityGroup: "Actividad actual del grupo" + view: " Vista:" + filters: + add: "Aรฑadir un filtro.." + loading: "Cargando filtros.." + generic: + are: "`are`" + label: + from: ">de" + makeAnother: "Realiza otra consulta" + to: ">a" + view: "Mostrar una vista" + performQuery: "Perform Query!" + results: + match: "coincididos ${resultCount} jugadores" + none: "La consulta ha entregado 0 resultados" + title: "Resultados de consulta" + title: + activity: "Actividad de los jugadores encontrados" + activityOnDate: 'Actividad en ' + sessionsWithinView: "Sessions within view" + text: "Consulta<" + register: + completion: "Registro Completado" + completion1: "Ya puedes finalizar el registro del usuario." + completion2: "El cรณdigo expira en 15 minutos" + completion3: "Usa el siguiente comando en el juego para finalizar el registro:" + completion4: "O usando la consola:" + createNewUser: "Crear un nuevo usuario" + error: + checkFailed: "Checking registration status failed: " + failed: "Registraciรณn fallida: " + noPassword: "Necesita especificar una contraseรฑa" + noUsername: "Necesita especificar un nombre de usuario" + usernameLength: "El nombre de usuario no puede superar los 50 caracteres, el suyo lo supera " + login: "Ya te has registrado? Ingresa!" + passwordTip: "La contraseรฑa debe ser mayor a 8 caracteres, por lo demรกs, no hay limitaciones" + register: "Registro" + usernameTip: "El nombre de usuario no puede superar los 50 caracteres." + text: + clickToExpand: "Haz clic para expandir" + comparing15days: "Comparando 15 dias" + comparing30daysAgo: "Comparando desde hace 30d hasta ahora" + noExtensionData: "Sin extensiรณn de datos." + noLowTps: "Sin picos bajos TPS." + unit: + chunks: "Chunks" + players: "Jugadores" + value: + localMachine: "Mรกquina local" + noKills: "Sin muertes" + offline: " Desconectado" + online: " Conectado" + with: "Con" + version: + changelog: "Revisar la lista de cambios" + current: "Tienes la version ${0}" + download: "Descargar Plan-${0}.jar" + isDev: "Esta versiรณn pertenece a una DEV release." + updateButton: "Actualizaciรณn" + updateModal: + text: "Una nueva versiรณn ha sido lanzada y esta lista para descargar." + title: "La versiรณn ${0} esta disponible!" +plugin: + apiCSSAdded: "Extensiรณn de Pรกgina: ${0} aรฑadido stylesheet(s) a ${1}, ${2}" + apiJSAdded: "Extensiรณn de Pรกgina: ${0} aรฑadido javascript(s) a ${1}, ${2}" + disable: + database: "Procesando las tareas no procesadas. (${0})" + disabled: "Anรกlisis del jugador deshabilitado." + processingComplete: "Procesado completado." + savingSessions: "Salvando sesiones incompletas.." + savingSessionsTimeout: "Tiempo de espera excedido, almacenando las sesiones sin finalizar para la siguiente habilitaciรณn del servicio." + waitingDb: "Esperando consultas para finalizar, con el fin de evitar que SQLite pueda daรฑar el proceso JVM.." + waitingDbComplete: "Cerrando la conexiรณn SQLite." + waitingTransactions: "Esperando por transacciones no completadas para evitar perdida de datos.." + waitingTransactionsComplete: "Consulta de la transacciรณn cerradaTransaction queue closed." + webserver: "Servidores web han sido deshabilitados." + enable: + database: "${0}-conexiรณn a la base de datos establecida." + enabled: "Analisis de jugadores habilitado." + fail: + database: "${0}-Conexion a la base de datos incorrecta: ${1}" + databasePatch: "El parcheado de la base de datos ha fallado, el plugin tiene que ser deshabilitado. Por favor, reporta este error." + databaseType: "${0} no es una base de datos sostenible." + geoDBWrite: "Ha ocurrido un error al intentar descargar la base de datos GeoLite2 Geolocation." + webServer: "ยกEl servidor web no se ha iniciado!" + notify: + badIP: "0.0.0.0 no es una direcciรณn vรกlida, establece los ajustes AlternativeIP. ยกSe darรกn los links incorrectos!" + emptyIP: "La IP en el server.properties esta vacรญa y AlternativeIP no estรก en uso. ยกSe darรกn los links incorrectos!" + geoDisabled: "La recolecciรณn por Geolocalizaciรณn no estรก activada. (Data.Geolocations: false)" + geoInternetRequired: "Plan requiere acceso a internet la primera vez para descargar la base de datos de GeoLite2 Geolocation." + storeSessions: "Almacenando sesiones que fueron preservadas antes de los apagados recientes." + webserverDisabled: "El servidor web no se ha iniciado. (WebServer.DisableWebServer: true)" + webserver: "El servidor web esta funcionando en el puerto ${0} ( ${1} )" + generic: + dbApplyingPatch: "Aplicar parche: ${0}.." + dbFaultyLaunchOptions: "Opciones de Lanzamiento fallidos. Usando los predeterminados (${0})" + dbNotifyClean: "Eliminados los datos de ${0} jugadores." + dbNotifySQLiteWAL: "El modo SQLite WAL no esta soportado en esta versiรณn. Usando el predeterminado. Esto puede afectar o no al rendimiento." + dbPatchesAlreadyApplied: "Los parches de la base de datos ya se han aplicado anteriormente." + dbPatchesApplied: "Los parches de la base de datos se han aplicado correctamente." + dbSchemaPatch: "Database: Making sure schema is up to date.." + loadedServerInfo: "Server identifier loaded: ${0}" + loadingServerInfo: "Loading server identifying information" + no: "No" + today: "'Hoy'" + unavailable: "No disponible" + unknown: "Desconocido" + yes: "Si" + yesterday: "'Ayer'" + version: + checkFail: "Error en comprobar el numero de la nueva versiรณn." + checkFailGithub: "La informaciรณn de la versiรณn no se ha podido cargar desde Github/versions.txt" + isDev: " Este es un lanzamiento para DEV." + isLatest: "Estas usando la version mรกs reciente." + updateAvailable: "Nuevo lanzamiento (${0}) disponible ${1}" + updateAvailableSpigot: "Nueva versiรณn disponible en ${0}" + webserver: + fail: + SSLContext: "Servidor web: Inicializaciรณn de Contexto SSL erroneo." + certFileEOF: "Servidor web: EOF leyendo el archivo de Certificado. (Comprueba que el archivo no estรก vacรญo)" + certStoreLoad: "Servidor web: Carga del Certificado SSL erronea." + portInUse: "El servidor web no se ha iniciado exitosamente. ยฟEsta el puerto (${0}) en uso?" + notify: + authDisabledConfig: "Servidor web: Autorizaciรณn del usuario desactivada! (Deshabilitada en la configuracion)" + authDisabledNoHTTPS: "Servidor web: Autorizaciรณn del usuario desactivada! (No seguro en HTTP)" + certificateExpiresOn: "Webserver: Loaded certificate is valid until ${0}." + certificateExpiresPassed: "Webserver: Certificate has expired, consider renewing the certificate." + certificateExpiresSoon: "Webserver: Certificate expires in ${0}, consider renewing the certificate." + certificateNoSuchAlias: "Webserver: Certificate with alias '${0}' was not found inside the keystore file '${1}'." + http: "Servidor web: No Certificado -> Usando HTTP-server para visualizaciรณn." + ipWhitelist: "Servidor web: La lista blanca por IP fue habilitada." + ipWhitelistBlock: "Servidor web: ${0} fue denegado del acceso a '${1}'. (no se encuentra en la lista de permitidos)" + noCertFile: "Servidor web: Certificado de archivo KeyStore no encontrado: ${0}" + reverseProxy: "Servidor web: Modo Proxy HTTPS habilitado, asegurate que tu reverse-proxy estรก usando HTTPS y los puntos del Proxy Plan AlternativeIP.Link" diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_FI.txt b/Plan/common/src/main/resources/assets/plan/locale/locale_FI.txt deleted file mode 100644 index 00ae17fa1..000000000 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_FI.txt +++ /dev/null @@ -1,517 +0,0 @@ -API - css+ || PageExtension: ${0} lisรคsi tyylejรค sivulle ${1}, ${2} -API - js+ || PageExtension: ${0} lisรคsi javascriptia sivulle ${1}, ${2} -Cmd - Click Me || Klikkaa minua -Cmd - Link || Linkki -Cmd - Link Network || Verkoston sivu: -Cmd - Link Player || Pelaajan sivu: -Cmd - Link Player JSON || Pelaajan json: -Cmd - Link Players || Pelaajat sivu: -Cmd - Link Register || Rekisterรถitymis sivu: -Cmd - Link Server || Palvelimen sivu: -CMD Arg - backup-file || Varmuuskopiotiedoston nimi (kirjainkooolla on vรคliรค) -CMD Arg - code || Koodi jota kรคytetรครคn rekisterรถitymiseen. -CMD Arg - db type backup || Tietokannan tyyppi joka varmuuskopioidaan. Nykyinen jos ei annettu. -CMD Arg - db type clear || Tietokannan tyyppi josta kaikki tieto poistetaan. -CMD Arg - db type hotswap || Tietokannan tyyppi jota aletaan kรคyttรครค. -CMD Arg - db type move from || Tietokannan tyyppi josta tietoa poistetaan. -CMD Arg - db type move to || Tietokannan tyyppi johon tietoa siirretรครคn. Ei voi olla sama kuin edellinen. -CMD Arg - db type restore || Tietokannan tyyppi johon tiedot palautetaan. Nykyiseen jos ei annettu. -CMD Arg - feature || Ominaisuuden nimi joka poistetaan kรคytรถstรค: ${0} -CMD Arg - player identifier || Pelaajan UUID tai nimi. -CMD Arg - player identifier remove || Poistettavan pelaajan UUID tai nimi. -CMD Arg - server identifier || Palvelimen nimi, ID tai UUID -CMD Arg - subcommand || Kรคytรค komentoa ilman alikomentoa nรคhdรคksesi kรคyttรถohjeet. -CMD Arg - username || Toisen kรคyttรคjรคn kรคyttรคjรคnimi. Jos ei annettu, pelaajaan linkitettyรค kรคyttรคjรครค kรคytetรครคn. -CMD Arg Name - backup-file || varmuuskopio-tiedosto -CMD Arg Name - code || ${koodi} -CMD Arg Name - export kind || viennin muoto -CMD Arg Name - feature || ominaisuus -CMD Arg Name - import kind || tuonnin muoto -CMD Arg Name - name or uuid || nimi/uuid -CMD Arg Name - server || palvelin -CMD Arg Name - subcommand || alikomento -CMD Arg Name - username || kรคyttรคjรคnimi -Cmd Confirm - accept || Hyvรคksy -Cmd Confirm - cancelled, no data change || Peruutettu. Tietoja ei muutettu. -Cmd Confirm - cancelled, unregister || Peruutettu. '${0}' ei poistettu rekisteristรค -Cmd Confirm - clearing db || Olet poistamassa kaikki Plan tiedot tietokannasta ${0} -Cmd Confirm - confirmation || Hyvรคksy: -Cmd Confirm - deny || Peruuta -Cmd Confirm - Expired || Vahvistus vanheni, kรคytรค komentoa uudelleen -Cmd Confirm - Fail on accept || Hyvรคksytty toiminto antoi virheen: ${0} -Cmd Confirm - Fail on deny || Kielletty toiminto antoi virheen: ${0} -Cmd Confirm - overwriting db || Olet ylikirjoittamassa kaikki Plan tiedot tietokannassa ${0} tiedolla ${1} tietokannasta -Cmd Confirm - remove player db || Olet poistamassa pelaajan ${0} tiedot tietokannasta ${1} -Cmd Confirm - unregister || Olet poistamassa rekisterรถitymistiedon '${0}' joka on linkitetty pelaajaan ${1} -Cmd db - creating backup || Luotiin varmuuskopiotiedosto '${0}.db' ${1} tietokannan tiedoista -Cmd db - removal || Poistetaan Plan-tietoja ${0} tietokannasta.. -Cmd db - removal player || Poistetaan pelaajan ${0} tietoja ${1} tietokannasta.. -Cmd db - server uninstalled || ยงaJos palvelin on yhรค asennettu, se merkkaa itsensรค asennetuksi seuraavalla kรคynnistyksellรค. -Cmd db - write || Kirjoitetaan tietoja ${0} tietokantaan.. -Cmd Disable - Disabled || ยงaPlan on nyt poissa pรครคltรค. Voit kรคyttรครค reload komentoa uudelleenkรคynnistykseen. -Cmd FAIL - Accepts only these arguments || Hyvรคksyy seuraavat vaihtoehdot ${0}: ${1} -Cmd FAIL - Database not open || ยงcTietokanta: ${0} - Yritรค uudelleen myรถhemmin. -Cmd FAIL - Empty search string || Hakusana ei voi olla tyhjรค -Cmd FAIL - Invalid Username || ยงcPelaajalla ei lรถytynyt UUID:ta. -Cmd FAIL - No Feature || ยงeValitse sammutettava osa! (tรคllรคhetkellรค tuetut: ${0}) -Cmd FAIL - No Permission || ยงcSinulla ei ole lupaa. -Cmd FAIL - No player || Pelaajaa '${0}' ei lรถydetty, heillรค ei ole UUID:ta. -Cmd FAIL - No player register || Pelaajaa '${0}' ei lรถytynyt tietokannasta. -Cmd FAIL - No server || Palvelinta '${0}' ei lรถytynyt tietokannasta. -Cmd FAIL - Require only one Argument || ยงcAnna ainakin yksi muuttuja ${1} -Cmd FAIL - Requires Arguments || ยงcMรครคritรค enemmรคn muuttujia (${0}) ${1} -Cmd FAIL - see config || katso '${0}' asetustiedostossa config.yml -Cmd FAIL - Unknown Username || ยงcPelaajaa ei ole nรคhty tรคllรค palvelimella. -Cmd FAIL - Users not linked || Kรคyttรคjรค ei ole linkitetty sinun pelaajaasi, eikรค sinulla ole lupaa poistaa toisten kรคyttรคjiรค. -Cmd FAIL - WebUser does not exists || ยงcKรคyttรคjรครค ei ole olemassa! -Cmd FAIL - WebUser exists || ยงcKรคyttรคjรค on jo olemassa! -Cmd Footer - Help || ยง7Laita hiiri komennon tai vaihtoehtojen pรครคlle tai kรคytรค '/${0} ?' saadaksesi lisรคtietoa -Cmd Header - Analysis || > ยง2Analyysin tulokset -Cmd Header - Help || > ยง2/${0} Apu -Cmd Header - Info || > ยง2Player Analytics -Cmd Header - Inspect || > ยง2Pelaaja: ยงf${0} -Cmd Header - Network || > ยง2Verkoston Sivu -Cmd Header - Players || > ยง2Pelaajat -Cmd Header - Search || > ยง2${0} Tulosta haulle ยงf${1}ยง2: -Cmd Header - server list || id::nimi::uuid -Cmd Header - Servers || > ยง2Palvelimet -Cmd Header - web user list || kรคyttรคjรคnimi::linkitetty pelaajaan::lupa taso -Cmd Header - Web Users || > ยง2${0} Web Kรคyttรคjรคt -Cmd Info - Bungee Connection || ยง2Yhdistetty Proxyyn: ยงf${0} -Cmd Info - Database || ยง2Nykyinen Tietokanta: ยงf${0} -Cmd Info - Reload Complete || ยงaUudelleenlataus onnistui! -Cmd Info - Reload Failed || ยงcUudelleenlatauksessa esiintyi ongelmia. Kรคynnistystรค uudelleen suositellaan. -Cmd Info - Update || ยง2Pรคivitys saatavilla: ยงf${0} -Cmd Info - Version || ยง2Versio: ยงf${0} -Cmd network - No network || Palvelinta ei ole liitetty verkostoon. Linkki menee palvelimen sivulle. -Cmd Notify - No Address || ยงeOsoitetta ei ollut saatavilla - kรคytetรครคn localhost:ia sen sijasta. Aseta 'Alternative_IP' asetukset. -Cmd Notify - No WebUser || Sinulla ei ehkรค ole Web Kรคyttรคjรครค, kรคytรค /plan register -komentoa -Cmd Notify - WebUser register || Rekisterรถitiin uusi Web Kรคyttรคjรค: '${0}' Lupa taso: ${1} -Cmd Qinspect - Active Playtime || ยง2Aktiivinen peliaika: ยงf${0} -Cmd Qinspect - Activity Index || ยง2Aktiivisuus Indeksi: ยงf${0} | ${1} -Cmd Qinspect - AFK Playtime || ยง2AFK aika: ยงf${0} -Cmd Qinspect - Deaths || ยง2Kuolemat: ยงf${0} -Cmd Qinspect - Geolocation || ยง2Kirjautui sisรครคn maasta: ยงf${0} -Cmd Qinspect - Last Seen || ยง2Viimeksi nรคhty: ยงf${0} -Cmd Qinspect - Longest Session || ยง2Pisin istunto: ยงf${0} -Cmd Qinspect - Mob Kills || ยง2Otusten Tappoja: ยงf${0} -Cmd Qinspect - Player Kills || ยง2Pelaajien Tappoja: ยงf${0} -Cmd Qinspect - Playtime || ยง2Peliaika: ยงf${0} -Cmd Qinspect - Registered || ยง2Rekisterรถitynyt: ยงf${0} -Cmd Qinspect - Times Kicked || ยง2Potkittu Pellolle: ยงf${0} -Cmd SUCCESS - Feature disabled || ยงaSammutettiin '${0}' toistaiseksi, kunnes Plan ladataan uudelleen. -Cmd SUCCESS - WebUser register || ยงaLisรคttiin uusi Web Kรคyttรคjรค (${0})! -Cmd unregister - unregistering || Poistetaan '${0}' rekisteristรค.. -Cmd WARN - Database not open || ยงeTietokanta: ${0} - Tรคmรค voi viedรค hiukan aikaa.. -Cmd Web - Permission Levels || >\ยง70: Pรครคsy kaikille sivuille\ยง71: Pรครคsy '/players' ja pelaajien sivuille\ยง72: Pรครคsy pelaajan sivulle, jolla on sama nimi kuin Web Kรคyttรคjรคllรค\ยง73+: Ei pรครคsyรค -Command Help - /plan db || Hallitse Plan tietokantoja -Command Help - /plan db backup || Varmuuskopioi tietokanta tiedostoon -Command Help - /plan db clear || Poista KAIKKI Plan tiedot tietokannasta -Command Help - /plan db hotswap || Vaihda tietokantaa lennosta -Command Help - /plan db move || Siirrรค tietoa tietokantojen vรคlillรค -Command Help - /plan db remove || Poista pelaajan tiedot nykyisestรค tietokannasta -Command Help - /plan db restore || Palauta tiedot tiedostosta tietokantaan -Command Help - /plan db uninstalled || Aseta palvelin poistetuksi tietokannassa. -Command Help - /plan disable || Sammuta Plan tai osa siitรค -Command Help - /plan export || Vie html tai json tietoja manuaalisesti -Command Help - /plan import || Tuo tietoja -Command Help - /plan info || Tietoa ohjelmasta -Command Help - /plan ingame || Katso pelaajan tietoja pelissรค -Command Help - /plan json || Nรคkymรค pelaajan raakadatasta. -Command Help - /plan logout || Log out other users from the panel. -Command Help - /plan network || Katso Verkoston sivua -Command Help - /plan player || Katso Pelaajan sivua -Command Help - /plan players || Katso Pelaajat sivua -Command Help - /plan register || Rekisterรถi Web Kรคyttรคjรค -Command Help - /plan reload || Kรคynnistรค Plan uudelleen -Command Help - /plan search || Etsi pelaajan nimeรค -Command Help - /plan server || Katso Palvelimen sivua -Command Help - /plan servers || Listaa tietokannassa olevat palvelimet -Command Help - /plan unregister || Poista Plan sivuston kรคyttรคjรค rekisteristรค -Command Help - /plan users || Listaa sivuston kรคyttรคjรคt -Database - Apply Patch || Muutetaan Tietokantaa: ${0}.. -Database - Patches Applied || Tietokannan muutokset suoritettu onnistuneesti. -Database - Patches Applied Already || Kaikki tietokannan muutokset oli jo tehty. -Database MySQL - Launch Options Error || LauchOptions-asetus oli virheellinen, kรคytetรครคn oletusta (${0}) -Database Notify - Clean || Poistetiin ${0}n pelaajan tiedot. -Database Notify - SQLite No WAL || SQLite WAL tilaa ei tueta tรคllรค versiolla, Kรคytetรครคn perustilaa. Tรคmรค voi vaikuttaa suorituskykyyn. -Disable || Player Analytics Sammutettu. -Disable - Processing || Suoritetaan kriittisiรค suorittamattomia toimintoja. (${0}) -Disable - Processing Complete || Suoritus valmis. -Disable - Unsaved Session Save || Tallennetaan pรครคttymรคttรถmรคt istunnot.. -Disable - Unsaved Session Save Timeout || Timeout hit, storing the unfinished sessions on next enable instead. -Disable - Waiting SQLite || Waiting queries to finish to avoid SQLite crashing JVM.. -Disable - Waiting SQLite Complete || Closed SQLite connection. -Disable - Waiting Transactions || Waiting for unfinished transactions to avoid data loss.. -Disable - Waiting Transactions Complete || Transaction queue closed. -Disable - WebServer || Web palvelin on sammutettu. -Enable || Player Analytics Kรคynnistetty. -Enable - Database || ${0}-tietokanta yhteys luotu. -Enable - Notify Bad IP || 0.0.0.0 ei ole toimiva osoite, aseta Alternative_IP asetukset. Linkit ovat virheellisiรค! -Enable - Notify Empty IP || IP server.properties tiedostossa on tyhjรค & Alternative_IP ei ole kรคytรถssรค. Linkit ovat virheellisiรค! -Enable - Notify Geolocations disabled || Sijaintien kerรคys ei ole pรครคllรค. (Data.Geolocations: false) -Enable - Notify Geolocations Internet Required || Plan Vaatii internetin ensimmรคisellรค kรคynnistyskerralla GeoLite2 tietokannan lataamiseen. -Enable - Notify Webserver disabled || Web Palvelinta ei kรคynnistetty. (WebServer.DisableWebServer: true) -Enable - Storing preserved sessions || Storing sessions that were preserved before previous shutdown. -Enable - WebServer || Web Palvelin pyรถrii PORTILLA ${0} ( ${1} ) -Enable FAIL - Database || ${0}-Tietokanta yhteys epรคonnistui: ${1} -Enable FAIL - Database Patch || Tietokannan muutokset epรคonnistuivat, Plan jouduttiin sulkemaan. Ilmoita tรคstรค ongelmasta -Enable FAIL - GeoDB Write || Jokin meni pieleen tallentaessa GeoLite2 tietokantaa -Enable FAIL - WebServer (Proxy) || Web Palvelin ei kรคynnistynyt! -Enable FAIL - Wrong Database Type || ${0} ei ole tuettu Tietokanta -HTML - AND_BUG_REPORTERS || & Bugien ilmoittajat! -HTML - BANNED (Filters) || Pannassa -HTML - COMPARING_15_DAYS || Verrataan 15 pรคivรครค -HTML - COMPARING_60_DAYS || Verrataan 30 pรคivรครค sitten nykyhetkeen -HTML - COMPARING_7_DAYS || Verrataan 7 pรคivรครค -HTML - DATABASE_NOT_OPEN || Tietokanta ei ole auki, tarkista tietokannan status /plan info komennolla -HTML - DESCRIBE_RETENTION_PREDICTION || This value is a prediction based on previous players. -HTML - ERROR || Todennus epรคonnistui virheen vuoksi -HTML - EXPIRED_COOKIE || Kรคyttรคjรคn kirjautumisevรคste vanheni -HTML - FILTER_ACTIVITY_INDEX_NOW || Current activity group -HTML - FILTER_ALL_PLAYERS || All players -HTML - FILTER_BANNED || Ban status -HTML - FILTER_GROUP || Group: -HTML - FILTER_OPS || Operator status -HTML - INDEX_ACTIVE || Aktiivinen -HTML - INDEX_INACTIVE || Inaktiivinen -HTML - INDEX_IRREGULAR || Epรคsรครคnnรถllinen -HTML - INDEX_REGULAR || Sรครคnnรถllinen -HTML - INDEX_VERY_ACTIVE || Todella Aktiivinen -HTML - KILLED || Tappanut -HTML - LABEL_1ST_WEAPON || Tappavin PvP Ase -HTML - LABEL_2ND_WEAPON || 2. PvP Ase -HTML - LABEL_3RD_WEAPON || 3. PvP Ase -HTML - LABEL_ACTIVE_PLAYTIME || Aktiivinen peliaika -HTML - LABEL_ACTIVITY_INDEX || Aktiivisuus Indeksi -HTML - LABEL_AFK || AFK -HTML - LABEL_AFK_TIME || Aika AFK:ina -HTML - LABEL_AVG || Keskimรคrรคinen -HTML - LABEL_AVG_ACTIVE_PLAYTIME || Keskimรคrรคinen Aktiivinen peliaika -HTML - LABEL_AVG_AFK_TIME || Keskimรคrรคinen AFK aika -HTML - LABEL_AVG_CHUNKS || Average Chunks -HTML - LABEL_AVG_ENTITIES || Average Entities -HTML - LABEL_AVG_KDR || Keskimรคrรคinen Tapposuhde -HTML - LABEL_AVG_MOB_KDR || Keskimรคrรคinen Otus-Tapposuhde -HTML - LABEL_AVG_PLAYTIME || Keskimรคrรคinen peliaika -HTML - LABEL_AVG_SESSION_LENGTH || Keskimรคrรคinen istunnon pituus -HTML - LABEL_AVG_SESSIONS || Keskimรครคrรคinen Sessiomรครคrรค -HTML - LABEL_AVG_TPS || Keskimรคrรคinen TPS -HTML - LABEL_BANNED || Pannassa -HTML - LABEL_BEST_PEAK || Paras Huippu -HTML - LABEL_DAY_OF_WEEK || Viikon pรคivรค -HTML - LABEL_DEATHS || Kuolemat -HTML - LABEL_DOWNTIME || Poissa pรครคltรค -HTML - LABEL_DURING_LOW_TPS || Matalan TPS:n aikana: -HTML - LABEL_ENTITIES || Entiteetit -HTML - LABEL_FAVORITE_SERVER || Lempipalvelin -HTML - LABEL_FIRST_SESSION_LENGTH || Ensimmรคisen istunnon pituus -HTML - LABEL_FREE_DISK_SPACE || Vapaa Levytila -HTML - LABEL_INACTIVE || Inaktiivinen -HTML - LABEL_LAST_PEAK || Viimeisin huippu -HTML - LABEL_LAST_SEEN || Nรคhty Viimeksi -HTML - LABEL_LOADED_CHUNKS || Ladatut Chunkit -HTML - LABEL_LOADED_ENTITIES || Ladatut Entiteetit -HTML - LABEL_LONE_JOINS || Yksinรคiset pelaajien liittymiset -HTML - LABEL_LONE_NEW_JOINS || Yksinรคiset uusien pelaajien liittymiset -HTML - LABEL_LONGEST_SESSION || Pisin istunto -HTML - LABEL_LOW_TPS || Matalan TPS:n piikit -HTML - LABEL_MAX_FREE_DISK || Maksimi vapaa levytila -HTML - LABEL_MIN_FREE_DISK || Minimi vapaa levytila -HTML - LABEL_MOB_DEATHS || Otusten aiheuttamat Kuolemat -HTML - LABEL_MOB_KDR || Otus-Tapposuhde -HTML - LABEL_MOB_KILLS || Tapetut Otukset -HTML - LABEL_MOST_ACTIVE_GAMEMODE || Aktiivisin pelitila -HTML - LABEL_NAME || Nimi -HTML - LABEL_NEW || Uudet -HTML - LABEL_NEW_PLAYERS || Uusia pelaajia -HTML - LABEL_NICKNAME || Lempinimi -HTML - LABEL_NO_SESSION_KILLS || None -HTML - LABEL_ONLINE_FIRST_JOIN || Paikalla ekalla liittymiskerralla -HTML - LABEL_OPERATOR || Operaattori -HTML - LABEL_PER_PLAYER || / Pelaaja -HTML - LABEL_PER_REGULAR_PLAYER || / Kantapelaaja -HTML - LABEL_PLAYER_DEATHS || Pelaajien aiheuttamat Kuolemat -HTML - LABEL_PLAYER_KILLS || Tapetut Pelaajat -HTML - LABEL_PLAYERS_ONLINE || Pelaajia Paikalla -HTML - LABEL_PLAYTIME || Peliaika -HTML - LABEL_REGISTERED || Rekisterรถitynyt -HTML - LABEL_REGISTERED_PLAYERS || Rekisterรถidyt pelaajat -HTML - LABEL_REGULAR || Kantapelaaja -HTML - LABEL_REGULAR_PLAYERS || Kantapelaajia -HTML - LABEL_RELATIVE_JOIN_ACTIVITY || Verrannollinen liittymis aktiivisuus -HTML - LABEL_RETENTION || Uusien pysyvyys -HTML - LABEL_SERVER_DOWNTIME || Palvelin pois pรครคltรค -HTML - LABEL_SERVER_OCCUPIED || Palvelin pelaajien kรคytรถssรค -HTML - LABEL_SESSION_ENDED || Istunto Pรครคttyi -HTML - LABEL_SESSION_MEDIAN || Istuntojen Mediaani -HTML - LABEL_TIMES_KICKED || Heitetty pihalle -HTML - LABEL_TOTAL_PLAYERS || Pelaajia -HTML - LABEL_TOTAL_PLAYTIME || Peliaikaa yhteensรค -HTML - LABEL_UNIQUE_PLAYERS || Uniikkeja pelaajia -HTML - LABEL_WEEK_DAYS || 'Maanantai', 'Tiistai', 'Keskiviikko', 'Torstai', 'Perjantai', 'Lauantai', 'Sunnuntai' -HTML - LINK_BACK_NETWORK || Verkosto -HTML - LINK_BACK_SERVER || Palvelin -HTML - LINK_CHANGELOG || Katso muutoslokia -HTML - LINK_DISCORD || Discord-tuki -HTML - LINK_DOWNLOAD || Lataa -HTML - LINK_ISSUES || Ilmoita ongelmista -HTML - LINK_NIGHT_MODE || Yรถ-tila -HTML - LINK_PLAYER_PAGE || Pelaajan sivu -HTML - LINK_QUICK_VIEW || Pika-katsaus -HTML - LINK_SERVER_ANALYSIS || Palvelimen Analyysi -HTML - LINK_WIKI || Plan Wiki, ohjeet ja dokumentaatio -HTML - LOCAL_MACHINE || Paikallinen laite -HTML - LOGIN_CREATE_ACCOUNT || Luo kรคyttรคjรค! -HTML - LOGIN_FAILED || Kirjautuminen epรคonnistui: -HTML - LOGIN_FORGOT_PASSWORD || Unohtuiko salasana? -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_1 || Unohtuiko salasana? Poista kรคyttรคjรค ja uudelleen rekisterรถidy. -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_2 || Kรคytรค komentoa pelissรค poistaaksesi kรคyttรคjรคsi: -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_3 || Tai konsolia: -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_4 || Komennon jรคlkeen, -HTML - LOGIN_LOGIN || Kirjaudu -HTML - LOGIN_LOGOUT || Kirjaudu ulos -HTML - LOGIN_PASSWORD || "Salasana" -HTML - LOGIN_USERNAME || "Kรคyttรคjรคnimi" -HTML - NAV_PLUGINS || Lisรคosat -HTML - NEW_CALENDAR || Uudet: -HTML - NO_KILLS || Ei Tappoja -HTML - NO_USER_PRESENT || Kรคyttรคjรคn kirjautumisevรคstettรค ei annettu -HTML - NON_OPERATORS (Filters) || Ei-operaattorit -HTML - NOT_BANNED (Filters) || Ei-pannassa -HTML - OFFLINE || Ei Paikalla -HTML - ONLINE || Paikalla -HTML - OPERATORS (Filters) || Operaattorit -HTML - PER_DAY || / Pรคivรค -HTML - PLAYERS_TEXT || Pelaajia -HTML - QUERY || Kysely< -HTML - QUERY_ACTIVITY_OF_MATCHED_PLAYERS || Vastaavien pelaajien aktiivisuus -HTML - QUERY_ACTIVITY_ON || Aktiivisuus -HTML - QUERY_ADD_FILTER || Lisรครค suodatin.. -HTML - QUERY_AND || ja -HTML - QUERY_ARE || `ovat` -HTML - QUERY_ARE_ACTIVITY_GROUP || ovat Aktiivisuus Luokissa -HTML - QUERY_ARE_PLUGIN_GROUP || ovat ${plugin}:n ${group} Luokissa -HTML - QUERY_JOINED_WITH_ADDRESS || joined with address -HTML - QUERY_LOADING_FILTERS || Ladataan suodattimia.. -HTML - QUERY_MAKE || Tee kysely -HTML - QUERY_MAKE_ANOTHER || Tee toinen kysely -HTML - QUERY_OF_PLAYERS || pelaajista ketkรค -HTML - QUERY_PERFORM_QUERY || Tee kysely! -HTML - QUERY_PLAYED_BETWEEN || Pelasivat vรคlillรค -HTML - QUERY_REGISTERED_BETWEEN || Rekisterรถityvรคt vรคlillรค -HTML - QUERY_RESULTS || Kyselyn tulokset -HTML - QUERY_RESULTS_MATCH || vastasi ${resultCount} pelaajaa -HTML - QUERY_SESSIONS_WITHIN_VIEW || Istunnot nรคkymรคn sisรคllรค -HTML - QUERY_SHOW_VIEW || Nรคytรค nรคkymรค -HTML - QUERY_TIME_FROM || >tรคstรค -HTML - QUERY_TIME_TO || >tรคnne -HTML - QUERY_VIEW || Nรคkymรค: -HTML - QUERY_ZERO_RESULTS || Ei tuloksia -HTML - REGISTER || Rekisterรถidy -HTML - REGISTER_CHECK_FAILED || Rekisterรถitymisen tilan tarkistus epรคonnistui: -HTML - REGISTER_COMPLETE || Viimeistele rekisterรถinti -HTML - REGISTER_COMPLETE_INSTRUCTIONS_1 || Voit viimeistellรค kรคyttรคjรคn rekisterรถinnin. -HTML - REGISTER_COMPLETE_INSTRUCTIONS_2 || Koodi vanhenee 15 minuutissa -HTML - REGISTER_COMPLETE_INSTRUCTIONS_3 || Kรคytรค seuraavaa komentoa pelissรค viimeistellรคksesi rekisterรถinnin: -HTML - REGISTER_COMPLETE_INSTRUCTIONS_4 || Tai konsolia: -HTML - REGISTER_CREATE_USER || Luo uusi kรคyttรคjรค -HTML - REGISTER_FAILED || Rekisterรถinti epรคonnistui: -HTML - REGISTER_HAVE_ACCOUNT || Aiempi kรคyttรคjรค? Kirjaudu sisรครคn! -HTML - REGISTER_PASSWORD_TIP || Salasana kannattaa olla yli 8 merkkiรค, mutta ei ole rajoituksia. -HTML - REGISTER_SPECIFY_PASSWORD || Anna salasana -HTML - REGISTER_SPECIFY_USERNAME || Anna kรคyttรคjรคnimi -HTML - REGISTER_USERNAME_LENGTH || Kรคyttรคjรคnimi voi olla enintรครคn 50 merkkiรค, sinun on -HTML - REGISTER_USERNAME_TIP || Kรคyttรคjรคnimi voi olla enintรครคn 50 merkkiรค. -HTML - SESSION || Istunto -HTML - SIDE_GEOLOCATIONS || Sijainnit -HTML - SIDE_INFORMATION || TIETOJA -HTML - SIDE_LINKS || LINKIT -HTML - SIDE_NETWORK_OVERVIEW || Verkoston yhteenveto -HTML - SIDE_OVERVIEW || Yhteenveto -HTML - SIDE_PERFORMANCE || Suorituskyky -HTML - SIDE_PLAYER_LIST || Pelaajalista -HTML - SIDE_PLAYERBASE || Pelaajakunta -HTML - SIDE_PLAYERBASE_OVERVIEW || Pelaajakunnan yhteenveto -HTML - SIDE_PLUGINS || LISร„OSAT -HTML - SIDE_PVP_PVE || PvP & PvE -HTML - SIDE_SERVERS || Palvelimet -HTML - SIDE_SERVERS_TITLE || PALVELIMET -HTML - SIDE_SESSIONS || Istunnot -HTML - SIDE_TO_MAIN_PAGE || pรครคsivu -HTML - TEXT_CLICK_TO_EXPAND || Klikkaa laajentaaksesi -HTML - TEXT_CONTRIBUTORS_CODE || koodin tuottaja -HTML - TEXT_CONTRIBUTORS_LOCALE || kรครคntรคjรค -HTML - TEXT_CONTRIBUTORS_MONEY || Suuret kiitokset rahallisesti tukeneille henkilรถille. -HTML - TEXT_CONTRIBUTORS_THANKS || Myรถs seuraavat mahtavat ihmiset ovat tukeneet kehitystรค: -HTML - TEXT_DEV_VERSION || Tรคmรค versio on KEHITYS versio. -HTML - TEXT_DEVELOPED_BY || on kehittรคnyt -HTML - TEXT_FIRST_SESSION || Ensimmรคinen sessio -HTML - TEXT_LICENSED_UNDER || Player Analytics:iรค kehitetรครคn, ja kรคyttรครค lisenssiรค -HTML - TEXT_METRICS || bStats Metriikat -HTML - TEXT_NO_EXTENSION_DATA || Ei dataa lisรคosista -HTML - TEXT_NO_LOW_TPS || Ei matalan TPS:n piikkejรค -HTML - TEXT_NO_SERVER || Ei palvelinta jolle nรคyttรครค aktiivisuutta -HTML - TEXT_NO_SERVERS || Palvelimia ei lรถytynyt tietokannasta -HTML - TEXT_PLUGIN_INFORMATION || Tietoa lisรคosasta -HTML - TEXT_PREDICTED_RETENTION || Tรคmรค arvo on arvattu ennustus edellisten pelaajien perusteella -HTML - TEXT_SERVER_INSTRUCTIONS || Vaikuttaa ettรค Plan peli-palvelimia ei ole asennettu tai yhdistetty samaan tietokantaan. Katso wikiin lisรคtietoja varten. -HTML - TEXT_VERSION || Uusi versio on julkaistu ja on nyt ladattavissa. -HTML - TITLE_30_DAYS || 30 pรคivรครค -HTML - TITLE_30_DAYS_AGO || 30 pรคivรครค sitten -HTML - TITLE_ALL || Kaikki -HTML - TITLE_ALL_TIME || Kaikkien aikojen -HTML - TITLE_AS_NUMBERS || Numeroina -HTML - TITLE_AVG_PING || Keskimรครคrรคinen Vasteaika -HTML - TITLE_BEST_PING || Paras Vasteaika -HTML - TITLE_CALENDAR || Kalenteri -HTML - TITLE_CONNECTION_INFO || Yhteyksien tiedot -HTML - TITLE_COUNTRY || Maa -HTML - TITLE_CPU_RAM || CPU & RAM -HTML - TITLE_CURRENT_PLAYERBASE || Nykyiset pelaajat -HTML - TITLE_DISK || Levytila -HTML - TITLE_GRAPH_DAY_BY_DAY || Pรคivittรคinen katsaus -HTML - TITLE_GRAPH_HOUR_BY_HOUR || Tunnittainen katsaus -HTML - TITLE_GRAPH_NETWORK_ONLINE_ACTIVITY || Verkoston paikallaolo -HTML - TITLE_GRAPH_PUNCHCARD || Reikรคkortti 30 pรคivรคlle -HTML - TITLE_INSIGHTS || Katsauksia 30 pรคivรคlle -HTML - TITLE_IS_AVAILABLE || on saatavilla -HTML - TITLE_JOIN_ADDRESSES || Join Addresses -HTML - TITLE_LAST_24_HOURS || Viimeiset 24 tuntia -HTML - TITLE_LAST_30_DAYS || Viimeiset 30 pรคivรครค -HTML - TITLE_LAST_7_DAYS || Viimeiset 7 pรคivรครค -HTML - TITLE_LAST_CONNECTED || Viimeisin yhteys -HTML - TITLE_LENGTH || Pituus -HTML - TITLE_MOST_PLAYED_WORLD || Eniten pelattu maailma -HTML - TITLE_NETWORK || Verkosto -HTML - TITLE_NETWORK_AS_NUMBERS || Verkosto Numeroina -HTML - TITLE_NOW || Nyt -HTML - TITLE_ONLINE_ACTIVITY || Paikallaolo -HTML - TITLE_ONLINE_ACTIVITY_AS_NUMBERS || Paikallaolo Numeroina -HTML - TITLE_ONLINE_ACTIVITY_OVERVIEW || Yhteenveto Paikallaolosta -HTML - TITLE_PERFORMANCE_AS_NUMBERS || Suorituskyky Numeroina -HTML - TITLE_PING || Vasteaika -HTML - TITLE_PLAYER || Pelaaja -HTML - TITLE_PLAYER_OVERVIEW || Yhteenveto Pelaajasta -HTML - TITLE_PLAYERBASE_DEVELOPMENT || Pelaajakunnan kehitys -HTML - TITLE_PVP_DEATHS || Viimeaikaiset PvP Kuolemat -HTML - TITLE_PVP_KILLS || Viimeaikaiset PvP Tapot -HTML - TITLE_PVP_PVE_NUMBERS || PvP & PvE Numeroina -HTML - TITLE_RECENT_KILLS || Viimeaikaiset Tapot -HTML - TITLE_RECENT_SESSIONS || Viimeisimmรคt Istunnot -HTML - TITLE_SEEN_NICKNAMES || Nรคhdyt Lempinimet -HTML - TITLE_SERVER || Palvelin -HTML - TITLE_SERVER_AS_NUMBERS || Palvelin Numeroina -HTML - TITLE_SERVER_OVERVIEW || Yhteenveto Palvelimesta -HTML - TITLE_SERVER_PLAYTIME || Palvelimen peliaika -HTML - TITLE_SERVER_PLAYTIME_30 || Palvelimen peliaika 30 pรคivรคlle -HTML - TITLE_SESSION_START || Istunto alkoi -HTML - TITLE_THEME_SELECT || Teemavalikko -HTML - TITLE_TITLE_PLAYER_PUNCHCARD || Reikรคkortti -HTML - TITLE_TPS || TPS -HTML - TITLE_TREND || Suunta -HTML - TITLE_TRENDS || Suunnat 30 pรคivรคlle -HTML - TITLE_VERSION || Versio -HTML - TITLE_WEEK_COMPARISON || Viikkojen vertaus -HTML - TITLE_WORLD || Maailmojen Resurssit -HTML - TITLE_WORLD_PLAYTIME || Maailmakohtainen Peliaika -HTML - TITLE_WORST_PING || Huonoin Vasteaika -HTML - TOTAL_ACTIVE_TEXT || Aktiivisena -HTML - TOTAL_AFK || AFK -HTML - TOTAL_PLAYERS || Kaikki Pelaajat -HTML - UNIQUE_CALENDAR || Uniikit: -HTML - UNIT_CHUNKS || Chunkkia -HTML - UNIT_ENTITIES || Entiteettiรค -HTML - UNIT_NO_DATA || Ei tietoa -HTML - UNIT_THE_PLAYERS || Pelaajia -HTML - USER_AND_PASS_NOT_SPECIFIED || Kรคyttรคjรครค ja salasana vaaditaan. -HTML - USER_DOES_NOT_EXIST || Kรคyttรคjรครค ei ole olemassa -HTML - USER_INFORMATION_NOT_FOUND || Rekisterรถityminen epรคonnistui, yritรค uudestaan (Koodi vanhenee 15 minuutin jรคlkeen) -HTML - USER_PASS_MISMATCH || Kรคyttรคjรค ja salasana ei tรคsmรครค -HTML - Version Change log || Katso muutoslistaa -HTML - Version Current || Sinulla on versio ${0} -HTML - Version Download || Lataa Plan-${0}.jar -HTML - Version Update || Pรคivitys -HTML - Version Update Available || Versio ${0} on Saatavilla! -HTML - Version Update Dev || Tรคmรค versio on DEV julkaisu. -HTML - Version Update Info || Uusi versio on julkaistu ja on ladattavissa. -HTML - WARNING_NO_GAME_SERVERS || Some data requires Plan to be installed on game servers. -HTML - WARNING_NO_GEOLOCATIONS || Geolocation gathering needs to be enabled in the config (Accept GeoLite2 EULA). -HTML - WARNING_NO_SPONGE_CHUNKS || Chunks unavailable on Sponge -HTML - WITH || Millรค -HTML ERRORS - ACCESS_DENIED_403 || Pรครคsy Kielletty -HTML ERRORS - AUTH_FAIL_TIPS_401 || - Varmista ettรค olet rekisterรถinyt kรคyttรคjรคn komennolla /plan register
- Tarkista ettรค kรคyttรคjรคnimi ja salaasana ovat oikein
- Nimi ja salasana ovat CASE SENSITIVE

Jos unohdit salasanasi, pyydรค valvojia poistamaan kรคyttรคjรคsi ja uudelleenrekisterรถidy. -HTML ERRORS - AUTHENTICATION_FAILED_401 || Todennus ei onnistunut. -HTML ERRORS - FORBIDDEN_403 || Kielletty -HTML ERRORS - NO_SERVERS_404 || Ei palvelimia jolla toiminto voitaisiin suorittaa. -HTML ERRORS - NOT_FOUND_404 || Ei lรถytynyt -HTML ERRORS - NOT_PLAYED_404 || Pelaaja ei ole pelannut palvelimella. -HTML ERRORS - PAGE_NOT_FOUND_404 || Sivua ei ole olemassa. -HTML ERRORS - UNAUTHORIZED_401 || Todennusta ei suoritettu loppuun. -HTML ERRORS - UNKNOWN_PAGE_404 || Varmista menneeesi komennon antamaan osoitteeseen, Esim:

/player/{uuid/nimi}
/server/{uuid/nimi/id}

-HTML ERRORS - UUID_404 || Pelaajan UUID:ta ei lรถytynyt tietokannasta. -In Depth Help - /plan db || Kรคytรค eri tietokanta alikomentoja vaikuttaaksesi tietokantaan -In Depth Help - /plan db backup || Kรคyttรครค SQLiteรค varmuuskopioimaan tiedot tiedostoon. -In Depth Help - /plan db clear || Tyhjentรครค kaikki Plan taulut, poistaen tiedot samalla. -In Depth Help - /plan db hotswap || Kรคynnistรครค ohjelman uudelleen toisella tietokannalla ja muuttaa samalla asetustiedoston tiedot. -In Depth Help - /plan db move || Korvaa tiedot tietokannassa toisen tietokannan tiedoilla. -In Depth Help - /plan db remove || Poistaa kaikki pelaajaan liitetyt tiedot nykyisestรค tietokannasta. -In Depth Help - /plan db restore || Kรคyttรครค SQLiteรค palauttamaan tiedot tiedostosta ylikirjoittaen tietokannan tiedot. -In Depth Help - /plan db uninstalled || Merkitsee palvelimen poistetuksi jotta se ei nรคy palvelin-kyselyissรค. -In Depth Help - /plan disable || Sammuta ohjelma tai osa siitรค seuraavaan kรคynnistykseen asti. -In Depth Help - /plan export || Toimittaa viennin asetuksissa olevaan sijaintiin -In Depth Help - /plan import || Tuo tietoja tietokantaan -In Depth Help - /plan info || Nรคyttรครค tietoja ohjelmasta -In Depth Help - /plan ingame || Nรคyttรครค tietoja pelaajasta pelin sisรคllรค. -In Depth Help - /plan json || Antaa lataa pelaajasta tiedot json-muodossa. -In Depth Help - /plan logout || Give username argument to log out another user from the panel, give * as argument to log out everyone. -In Depth Help - /plan network || Hanki linkki /network sivulle, toimii vain verkostoissa -In Depth Help - /plan player || Hanki linkki annetun tai nykyisen pelaajan /player sivulle. -In Depth Help - /plan players || Hanki linkki /players sivulle nรคhdรคksesi pelaajalistan -In Depth Help - /plan register || Kรคytรค ilman argumentteja rekisterรถimissivua varten. Kรคytรค --code [code] rekisterรถidรคksesi kรคyttรคjรคn. -In Depth Help - /plan reload || Sammuta ja kรคynnistรค ohjelma uudelleen asetusten uudelleenlataamiseksi. -In Depth Help - /plan search || Etsi pelaajia joiden nimessรค on haettu teksti -In Depth Help - /plan server || Hanki linkki annetun tai nykyisen palvelimen /server sivulle -In Depth Help - /plan servers || Listaa id:t, nimet ja uuid:t tietokannassa olevista palvelimista. -In Depth Help - /plan unregister || Kรคytรค ilman argumentteja poistaaksesi nykyiseen pelaajaan linkitetty kรคyttรคjรค, tai anna kรคyttรคjรคnimi joka poistaa -In Depth Help - /plan users || Listaa sivuston kรคyttรคjรคt. -Manage - Confirm Overwrite || Tiedot ${0}:ssa ylikirjoitetaan! -Manage - Confirm Removal || Tiedot ${0}:ssa poistetaan! -Manage - Fail || > ยงcJokin meni vikaan: ${0} -Manage - Fail File not found || > ยงcTiedostoa ei lรถytynyt ${0} -Manage - Fail Incorrect Database || > ยงc'${0}' ei ole tuettu tietokanta. -Manage - Fail No Exporter || ยงeViejรครค '${0}' ei ole olemassa -Manage - Fail No Importer || ยงeTuojaa '${0}' ei ole olemassa -Manage - Fail No Server || Palvelinta ei lรถytynyt annetuilla parametreilla. -Manage - Fail Same Database || > ยงcEi voi kรคyttรครค samaa tietokantaa molempiin asioihin! -Manage - Fail Same server || Ei voi merkitรค tรคtรค palvelinta poistetuksi (Olet siellรค) -Manage - Fail, Confirmation || > ยงcLisรครค '-a' vahvistaaksesi: ${0} -Manage - List Importers || Tuojat: -Manage - Progress || ${0} / ${1} muutettu.. -Manage - Remind HotSwap || ยงeMuista vaihtaa tietokantaa (/plan db hotswap ${0}) & kรคynnistรค Plan uudelleen. -Manage - Start || > ยง2Muutetaan tietoa.. -Manage - Success || > ยงaOnnistui! -Negative || Ei -Positive || Kyllรค -Today || 'Tรคnรครคn' -Unavailable || Ei saatavilla -Unknown || Tuntematon -Version - DEV || Tรคmรค on KEHITYS julkaisu. -Version - Latest || Kรคytรคt uusinta versiota. -Version - New || Uusi Julkaisu (${0}) on saatavilla ${1} -Version - New (old) || Uusi Versio on saatavilla ${0} -Version FAIL - Read info (old) || Uuden version tarkistus epรคonnistui -Version FAIL - Read versions.txt || Uuden version tarkistus epรคonnistui Github/versions.txt:ta -Web User Listing || ยง2${0} ยง7: ยงf${1} -WebServer - Notify HTTP || Web Palvelin: Ei Sertifikaattia -> Kรคytetรครคn HTTP-Palvelinta. -WebServer - Notify HTTP User Auth || Web Palvelin: Kรคyttรคjien varmennus ei kรคytรถssรค! (Ei turvallista HTTP-protokollalla) -WebServer - Notify HTTPS User Auth || Web Palvelin: Kรคyttรคjien varmennus ei kรคytรถssรค! (Sammutettu asetuksista) -Webserver - Notify IP Whitelist || Web Palvelin: IP sallimislista kรคytรถssรค. -Webserver - Notify IP Whitelist Block || Web Palvelin: ${0} kiellettiin pรครคsy osoitteeseen '${1}'. (ei sallimislistalla) -WebServer - Notify no Cert file || Web Palvelin: Sertifikaatti AvainKirjasto tiedostoa ei lรถytynyt: ${0} -WebServer - Notify Using Proxy || Web Palvelin: Proxy-tolan HTTPS kรคytรถssรค, varmista ettรค reverse-proxy kรคyttรครค HTTPS ja ettรค Plan Alternative_IP.Address osoittaa Proxy-palvelimeen -WebServer FAIL - EOF || Web Palvelin: EOF lukiessa Sertifikaattia. (Tarkista ettรค tiedosto ei ole tyhjรค) -WebServer FAIL - Port Bind || Web Palvelin ei kรคynnistynyt. Onko portti (${0}) kรคytรถssรค? -WebServer FAIL - SSL Context || Web Palvelin: SSL Viitepalvelun kรคynnistys ei onnistunut. -WebServer FAIL - Store Load || Web Palvelin: SSL Sertifikaatin lataus ei onnistunut. -Yesterday || 'Eilen' diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_FI.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_FI.yml new file mode 100644 index 000000000..a0dad6c4d --- /dev/null +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_FI.yml @@ -0,0 +1,669 @@ +403AccessDenied: "Pรครคsy Kielletty" +command: + argument: + backupFile: + description: "Varmuuskopiotiedoston nimi (kirjainkooolla on vรคliรค)" + name: "varmuuskopio-tiedosto" + code: + description: "Koodi jota kรคytetรครคn rekisterรถitymiseen." + name: "${koodi}" + dbBackup: + description: "Tietokannan tyyppi joka varmuuskopioidaan. Nykyinen jos ei annettu." + dbRestore: + description: "Tietokannan tyyppi johon tiedot palautetaan. Nykyiseen jos ei annettu." + dbTypeHotswap: + description: "Tietokannan tyyppi jota aletaan kรคyttรครค." + dbTypeMoveFrom: + description: "Tietokannan tyyppi josta tietoa poistetaan." + dbTypeMoveTo: + description: "Tietokannan tyyppi johon tietoa siirretรครคn. Ei voi olla sama kuin edellinen." + dbTypeRemove: + description: "Tietokannan tyyppi josta kaikki tieto poistetaan." + exportKind: "viennin muoto" + feature: + description: "Ominaisuuden nimi joka poistetaan kรคytรถstรค: ${0}" + name: "ominaisuus" + importKind: "tuonnin muoto" + nameOrUUID: + description: "Pelaajan UUID tai nimi." + name: "nimi/uuid" + removeDescription: "Poistettavan pelaajan UUID tai nimi." + server: + description: "Palvelimen nimi, ID tai UUID" + name: "palvelin" + subcommand: + description: "Kรคytรค komentoa ilman alikomentoa nรคhdรคksesi kรคyttรถohjeet." + name: "alikomento" + username: + description: "Toisen kรคyttรคjรคn kรคyttรคjรคnimi. Jos ei annettu, pelaajaan linkitettyรค kรคyttรคjรครค kรคytetรครคn." + name: "kรคyttรคjรคnimi" + confirmation: + accept: "Hyvรคksy" + cancelNoChanges: "Peruutettu. Tietoja ei muutettu." + cancelNoUnregister: "Peruutettu. '${0}' ei poistettu rekisteristรค" + confirm: "Hyvรคksy:" + dbClear: "Olet poistamassa kaikki Plan tiedot tietokannasta ${0}" + dbOverwrite: "Olet ylikirjoittamassa kaikki Plan tiedot tietokannassa ${0} tiedolla ${1} tietokannasta" + dbRemovePlayer: "Olet poistamassa pelaajan ${0} tiedot tietokannasta ${1}" + deny: "Peruuta" + expired: "Vahvistus vanheni, kรคytรค komentoa uudelleen" + unregister: "Olet poistamassa rekisterรถitymistiedon '${0}' joka on linkitetty pelaajaan ${1}" + database: + creatingBackup: "Luotiin varmuuskopiotiedosto '${0}.db' ${1} tietokannan tiedoista" + failDbNotOpen: "ยงcTietokanta: ${0} - Yritรค uudelleen myรถhemmin." + manage: + confirm: "> ยงcLisรครค '-a' vahvistaaksesi: ${0}" + confirmOverwrite: "Tiedot ${0}:ssa ylikirjoitetaan!" + confirmPartialRemoval: "Join Address Data for Server ${0} in ${1} will be removed!" + confirmRemoval: "Tiedot ${0}:ssa poistetaan!" + fail: "> ยงcJokin meni vikaan: ${0}" + failFileNotFound: "> ยงcTiedostoa ei lรถytynyt ${0}" + failIncorrectDB: "> ยงc'${0}' ei ole tuettu tietokanta." + failNoServer: "Palvelinta ei lรถytynyt annetuilla parametreilla." + failSameDB: "> ยงcEi voi kรคyttรครค samaa tietokantaa molempiin asioihin!" + failSameServer: "Ei voi merkitรค tรคtรค palvelinta poistetuksi (Olet siellรค)" + hotswap: "ยงeMuista vaihtaa tietokantaa (/plan db hotswap ${0}) & kรคynnistรค Plan uudelleen." + importers: "Tuojat:" + preparing: "Preparing.." + progress: "${0} / ${1} muutettu.." + start: "> ยง2Muutetaan tietoa.." + success: "> ยงaOnnistui!" + playerRemoval: "Poistetaan pelaajan ${0} tietoja ${1} tietokannasta.." + removal: "Poistetaan Plan-tietoja ${0} tietokannasta.." + serverUninstalled: "ยงaJos palvelin on yhรค asennettu, se merkkaa itsensรค asennetuksi seuraavalla kรคynnistyksellรค." + unregister: "Poistetaan '${0}' rekisteristรค.." + warnDbNotOpen: "ยงeTietokanta: ${0} - Tรคmรค voi viedรค hiukan aikaa.." + write: "Kirjoitetaan tietoja ${0} tietokantaan.." + fail: + emptyString: "Hakusana ei voi olla tyhjรค" + invalidArguments: "Hyvรคksyy seuraavat vaihtoehdot ${0}: ${1}" + invalidUsername: "ยงcPelaajalla ei lรถytynyt UUID:ta." + missingArguments: "ยงcMรครคritรค enemmรคn muuttujia (${0}) ${1}" + missingFeature: "ยงeValitse sammutettava osa! (tรคllรคhetkellรค tuetut: ${0})" + missingLink: "Kรคyttรคjรค ei ole linkitetty sinun pelaajaasi, eikรค sinulla ole lupaa poistaa toisten kรคyttรคjiรค." + noPermission: "ยงcSinulla ei ole lupaa." + onAccept: "Hyvรคksytty toiminto antoi virheen: ${0}" + onDeny: "Kielletty toiminto antoi virheen: ${0}" + playerNotFound: "Pelaajaa '${0}' ei lรถydetty, heillรค ei ole UUID:ta." + playerNotInDatabase: "Pelaajaa '${0}' ei lรถytynyt tietokannasta." + seeConfig: "katso '${0}' asetustiedostossa config.yml" + serverNotFound: "Palvelinta '${0}' ei lรถytynyt tietokannasta." + tooManyArguments: "ยงcAnna ainakin yksi muuttuja ${1}" + unknownUsername: "ยงcPelaajaa ei ole nรคhty tรคllรค palvelimella." + webUserExists: "ยงcKรคyttรคjรค on jo olemassa!" + webUserNotFound: "ยงcKรคyttรคjรครค ei ole olemassa!" + footer: + help: "ยง7Laita hiiri komennon tai vaihtoehtojen pรครคlle tai kรคytรค '/${0} ?' saadaksesi lisรคtietoa" + general: + failNoExporter: "ยงeViejรครค '${0}' ei ole olemassa" + failNoImporter: "ยงeTuojaa '${0}' ei ole olemassa" + featureDisabled: "ยงaSammutettiin '${0}' toistaiseksi, kunnes Plan ladataan uudelleen." + noAddress: "ยงeOsoitetta ei ollut saatavilla - kรคytetรครคn localhost:ia sen sijasta. Aseta 'Alternative_IP' asetukset." + noWebuser: "Sinulla ei ehkรค ole Web Kรคyttรคjรครค, kรคytรค /plan register -komentoa" + notifyWebUserRegister: "Rekisterรถitiin uusi Web Kรคyttรคjรค: '${0}' Lupa taso: ${1}" + pluginDisabled: "ยงaPlan on nyt poissa pรครคltรค. Voit kรคyttรครค reload komentoa uudelleenkรคynnistykseen." + reloadComplete: "ยงaUudelleenlataus onnistui!" + reloadFailed: "ยงcUudelleenlatauksessa esiintyi ongelmia. Kรคynnistystรค uudelleen suositellaan." + successWebUserRegister: "ยงaLisรคttiin uusi Web Kรคyttรคjรค (${0})!" + webPermissionLevels: ">\ยง70: Pรครคsy kaikille sivuille\ยง71: Pรครคsy '/players' ja pelaajien sivuille\ยง72: Pรครคsy pelaajan sivulle, jolla on sama nimi kuin Web Kรคyttรคjรคllรค\ยง73+: Ei pรครคsyรค" + webUserList: " ยง2${0} ยง7: ยงf${1}" + header: + analysis: "> ยง2Analyysin tulokset" + help: "> ยง2/${0} Apu" + info: "> ยง2Player Analytics" + inspect: "> ยง2Pelaaja: ยงf${0}" + network: "> ยง2Verkoston Sivu" + players: "> ยง2Pelaajat" + search: "> ยง2${0} Tulosta haulle ยงf${1}ยง2:" + serverList: "id::nimi::uuid::version" + servers: "> ยง2Palvelimet" + webUserList: "kรคyttรคjรคnimi::linkitetty pelaajaan::lupa taso" + webUsers: "> ยง2${0} Web Kรคyttรคjรคt" + help: + database: + description: "Hallitse Plan tietokantoja" + inDepth: "Kรคytรค eri tietokanta alikomentoja vaikuttaaksesi tietokantaan" + dbBackup: + description: "Varmuuskopioi tietokanta tiedostoon" + inDepth: "Kรคyttรครค SQLiteรค varmuuskopioimaan tiedot tiedostoon." + dbClear: + description: "Poista KAIKKI Plan tiedot tietokannasta" + inDepth: "Tyhjentรครค kaikki Plan taulut, poistaen tiedot samalla." + dbHotswap: + description: "Vaihda tietokantaa lennosta" + inDepth: "Kรคynnistรครค ohjelman uudelleen toisella tietokannalla ja muuttaa samalla asetustiedoston tiedot." + dbMove: + description: "Siirrรค tietoa tietokantojen vรคlillรค" + inDepth: "Korvaa tiedot tietokannassa toisen tietokannan tiedoilla." + dbRemove: + description: "Poista pelaajan tiedot nykyisestรค tietokannasta" + inDepth: "Poistaa kaikki pelaajaan liitetyt tiedot nykyisestรค tietokannasta." + dbRestore: + description: "Palauta tiedot tiedostosta tietokantaan" + inDepth: "Kรคyttรครค SQLiteรค palauttamaan tiedot tiedostosta ylikirjoittaen tietokannan tiedot." + dbUninstalled: + description: "Aseta palvelin poistetuksi tietokannassa." + inDepth: "Merkitsee palvelimen poistetuksi jotta se ei nรคy palvelin-kyselyissรค." + disable: + description: "Sammuta Plan tai osa siitรค" + inDepth: "Sammuta ohjelma tai osa siitรค seuraavaan kรคynnistykseen asti." + export: + description: "Vie html tai json tietoja manuaalisesti" + inDepth: "Toimittaa viennin asetuksissa olevaan sijaintiin" + import: + description: "Tuo tietoja" + inDepth: "Tuo tietoja tietokantaan" + info: + description: "Tietoa ohjelmasta" + inDepth: "Nรคyttรครค tietoja ohjelmasta" + ingame: + description: "Katso pelaajan tietoja pelissรค" + inDepth: "Nรคyttรครค tietoja pelaajasta pelin sisรคllรค." + json: + description: "Nรคkymรค pelaajan raakadatasta." + inDepth: "Antaa lataa pelaajasta tiedot json-muodossa." + logout: + description: "Log out other users from the panel." + inDepth: "Give username argument to log out another user from the panel, give * as argument to log out everyone." + migrateToOnlineUuids: + description: "Migrate offline uuid data to online uuids" + network: + description: "Katso Verkoston sivua" + inDepth: "Hanki linkki /network sivulle, toimii vain verkostoissa" + player: + description: "Katso Pelaajan sivua" + inDepth: "Hanki linkki annetun tai nykyisen pelaajan /player sivulle." + players: + description: "Katso Pelaajat sivua" + inDepth: "Hanki linkki /players sivulle nรคhdรคksesi pelaajalistan" + register: + description: "Rekisterรถi Web Kรคyttรคjรค" + inDepth: "Kรคytรค ilman argumentteja rekisterรถimissivua varten. Kรคytรค --code [code] rekisterรถidรคksesi kรคyttรคjรคn." + reload: + description: "Kรคynnistรค Plan uudelleen" + inDepth: "Sammuta ja kรคynnistรค ohjelma uudelleen asetusten uudelleenlataamiseksi." + removejoinaddresses: + description: "Remove join addresses of a specified server" + search: + description: "Etsi pelaajan nimeรค" + inDepth: "Etsi pelaajia joiden nimessรค on haettu teksti" + server: + description: "Katso Palvelimen sivua" + inDepth: "Hanki linkki annetun tai nykyisen palvelimen /server sivulle" + servers: + description: "Listaa tietokannassa olevat palvelimet" + inDepth: "Listaa id:t, nimet ja uuid:t tietokannassa olevista palvelimista." + unregister: + description: "Poista Plan sivuston kรคyttรคjรค rekisteristรค" + inDepth: "Kรคytรค ilman argumentteja poistaaksesi nykyiseen pelaajaan linkitetty kรคyttรคjรค, tai anna kรคyttรคjรคnimi joka poistaa" + users: + description: "Listaa sivuston kรคyttรคjรคt" + inDepth: "Listaa sivuston kรคyttรคjรคt." + ingame: + activePlaytime: " ยง2Aktiivinen peliaika: ยงf${0}" + activityIndex: " ยง2Aktiivisuus Indeksi: ยงf${0} | ${1}" + afkPlaytime: " ยง2AFK aika: ยงf${0}" + deaths: " ยง2Kuolemat: ยงf${0}" + geolocation: " ยง2Kirjautui sisรครคn maasta: ยงf${0}" + lastSeen: " ยง2Viimeksi nรคhty: ยงf${0}" + longestSession: " ยง2Pisin istunto: ยงf${0}" + mobKills: " ยง2Otusten Tappoja: ยงf${0}" + playerKills: " ยง2Pelaajien Tappoja: ยงf${0}" + playtime: " ยง2Peliaika: ยงf${0}" + registered: " ยง2Rekisterรถitynyt: ยงf${0}" + timesKicked: " ยง2Potkittu Pellolle: ยงf${0}" + link: + clickMe: "Klikkaa minua" + link: "Linkki" + network: "Verkoston sivu:" + noNetwork: "Palvelinta ei ole liitetty verkostoon. Linkki menee palvelimen sivulle." + player: "Pelaajan sivu:" + playerJson: "Pelaajan json:" + players: "Pelaajat sivu:" + register: "Rekisterรถitymis sivu:" + server: "Palvelimen sivu:" + subcommand: + info: + database: " ยง2Nykyinen Tietokanta: ยงf${0}" + proxy: " ยง2Yhdistetty Proxyyn: ยงf${0}" + update: " ยง2Pรคivitys saatavilla: ยงf${0}" + version: " ยง2Versio: ยงf${0}" +generic: + noData: "Ei tietoa" +html: + button: + nightMode: "Yรถ-tila" + calendar: + new: "Uudet:" + unique: "Uniikit:" + description: + newPlayerRetention: "This value is a prediction based on previous players." + noGameServers: "Some data requires Plan to be installed on game servers." + noGeolocations: "Geolocation gathering needs to be enabled in the config (Accept GeoLite2 EULA)." + noServerOnlinActivity: "Ei palvelinta jolle nรคyttรครค aktiivisuutta" + noServers: "Palvelimia ei lรถytynyt tietokannasta" + noServersLong: 'Vaikuttaa ettรค Plan peli-palvelimia ei ole asennettu tai yhdistetty samaan tietokantaan. Katso wikiin lisรคtietoja varten.' + noSpongeChunks: "Chunks unavailable on Sponge" + predictedNewPlayerRetention: "Tรคmรค arvo on arvattu ennustus edellisten pelaajien perusteella" + error: + 401Unauthorized: "Todennusta ei suoritettu loppuun." + 403Forbidden: "Kielletty" + 404NotFound: "Ei lรถytynyt" + 404PageNotFound: "Sivua ei ole olemassa." + 404UnknownPage: "Varmista menneeesi komennon antamaan osoitteeseen, Esim:

/player/{uuid/nimi}
/server/{uuid/nimi/id}

" + UUIDNotFound: "Pelaajan UUID:ta ei lรถytynyt tietokannasta." + auth: + dbClosed: "Tietokanta ei ole auki, tarkista tietokannan status /plan info komennolla" + emptyForm: "Kรคyttรคjรครค ja salasana vaaditaan." + expiredCookie: "Kรคyttรคjรคn kirjautumisevรคste vanheni" + generic: "Todennus epรคonnistui virheen vuoksi" + loginFailed: "Kรคyttรคjรค ja salasana ei tรคsmรครค" + noCookie: "Kรคyttรคjรคn kirjautumisevรคstettรค ei annettu" + registrationFailed: "Rekisterรถityminen epรคonnistui, yritรค uudestaan (Koodi vanhenee 15 minuutin jรคlkeen)" + userNotFound: "Kรคyttรคjรครค ei ole olemassa" + authFailed: "Todennus ei onnistunut." + authFailedTips: "- Varmista ettรค olet rekisterรถinyt kรคyttรคjรคn komennolla /plan register
- Tarkista ettรค kรคyttรคjรคnimi ja salaasana ovat oikein
- Nimi ja salasana ovat CASE SENSITIVE

Jos unohdit salasanasi, pyydรค valvojia poistamaan kรคyttรคjรคsi ja uudelleenrekisterรถidy." + noServersOnline: "Ei palvelimia jolla toiminto voitaisiin suorittaa." + playerNotSeen: "Pelaaja ei ole pelannut palvelimella." + serverNotSeen: "Server doesn't exist" + generic: + none: "None" + label: + active: "Aktiivinen" + activePlaytime: "Aktiivinen peliaika" + activityIndex: "Aktiivisuus Indeksi" + afk: "AFK" + afkTime: "Aika AFK:ina" + all: "Kaikki" + allTime: "Kaikkien aikojen" + alphabetical: "Alphabetical" + apply: "Apply" + asNumbers: "Numeroina" + average: "Keskimรคrรคinen" + averageActivePlaytime: "Keskimรคrรคinen Aktiivinen peliaika" + averageAfkTime: "Keskimรคrรคinen AFK aika" + averageChunks: "Average Chunks" + averageCpuUsage: "Average CPU Usage" + averageEntities: "Average Entities" + averageKdr: "Keskimรคrรคinen Tapposuhde" + averageMobKdr: "Keskimรคrรคinen Otus-Tapposuhde" + averagePing: "Keskimรครคrรคinen Vasteaika" + averagePlayers: "Average Players" + averagePlaytime: "Keskimรคrรคinen peliaika" + averageRamUsage: "Average RAM Usage" + averageServerDowntime: "Average Downtime / Server" + averageSessionLength: "Keskimรคrรคinen istunnon pituus" + averageSessions: "Keskimรครคrรคinen Sessiomรครคrรค" + averageTps: "Keskimรคrรคinen TPS" + averageTps7days: "Average TPS (7 days)" + banned: "Pannassa" + bestPeak: "Paras Huippu" + bestPing: "Paras Vasteaika" + calendar: " Kalenteri" + comparing7days: "Verrataan 7 pรคivรครค" + connectionInfo: "Yhteyksien tiedot" + country: "Maa" + cpu: "CPU" + cpuRam: "CPU & RAM" + cpuUsage: "CPU Usage" + currentPlayerbase: "Nykyiset pelaajat" + currentUptime: "Current Uptime" + dayByDay: "Pรคivittรคinen katsaus" + dayOfweek: "Viikonpรคivรค" + deadliestWeapon: "Tappavin PvP Ase" + deaths: "Kuolemat" + disk: "Levytila" + diskSpace: "Vapaa Levytila" + downtime: "Poissa pรครคltรค" + duringLowTps: "Matalan TPS:n aikana:" + entities: "Entiteetit" + favoriteServer: "Lempipalvelin" + firstSession: "Ensimmรคinen sessio" + firstSessionLength: + average: "Average first session length" + median: "Median first session length" + geoProjection: + dropdown: "Select projection" + equalEarth: "Equal Earth" + mercator: "Mercator" + miller: "Miller" + ortographic: "Ortographic" + geolocations: "Sijainnit" + hourByHour: "Tunnittainen katsaus" + inactive: "Inaktiivinen" + indexInactive: "Inaktiivinen" + indexRegular: "Sรครคnnรถllinen" + information: "TIETOJA" + insights: "Insights" + insights30days: "Katsauksia 30 pรคivรคlle" + irregular: "Epรคsรครคnnรถllinen" + joinAddress: "Join Address" + joinAddresses: "Join Addresses" + kdr: "KDR" + killed: "Tappanut" + last24hours: "Viimeiset 24 tuntia" + last30days: "Viimeiset 30 pรคivรครค" + last7days: "Viimeiset 7 pรคivรครค" + lastConnected: "Viimeisin yhteys" + lastPeak: "Viimeisin huippu" + lastSeen: "Nรคhty Viimeksi" + latestJoinAddresses: "Latest Join Addresses" + length: " Pituus" + links: "LINKIT" + loadedChunks: "Ladatut Chunkit" + loadedEntities: "Ladatut Entiteetit" + loneJoins: "Yksinรคiset pelaajien liittymiset" + loneNewbieJoins: "Yksinรคiset uusien pelaajien liittymiset" + longestSession: "Pisin istunto" + lowTpsSpikes: "Matalan TPS:n piikit" + lowTpsSpikes7days: "Low TPS Spikes (7 days)" + maxFreeDisk: "Maksimi vapaa levytila" + medianSessionLength: "Median Session Length" + minFreeDisk: "Minimi vapaa levytila" + mobDeaths: "Otusten aiheuttamat Kuolemat" + mobKdr: "Otus-Tapposuhde" + mobKills: "Tapetut Otukset" + mostActiveGamemode: "Aktiivisin pelitila" + mostPlayedWorld: "Eniten pelattu maailma" + name: "Nimi" + network: "Verkosto" + networkAsNumbers: "Verkosto Numeroina" + networkOnlineActivity: "Verkoston paikallaolo" + networkOverview: "Verkoston yhteenveto" + networkPage: "Verkosto" + new: "Uudet" + newPlayerRetention: "Uusien pysyvyys" + newPlayers: "Uusia pelaajia" + newPlayers7days: "New Players (7 days)" + nickname: "Lempinimi" + noDataToDisplay: "No Data to Display" + now: "Nyt" + onlineActivity: "Paikallaolo" + onlineActivityAsNumbers: "Paikallaolo Numeroina" + onlineOnFirstJoin: "Paikalla ekalla liittymiskerralla" + operator: "Operaattori" + overview: "Yhteenveto" + perDay: "/ Pรคivรค" + perPlayer: "/ Pelaaja" + perRegularPlayer: "/ Kantapelaaja" + performance: "Suorituskyky" + performanceAsNumbers: "Suorituskyky Numeroina" + ping: "Vasteaika" + player: "Pelaaja" + playerDeaths: "Pelaajien aiheuttamat Kuolemat" + playerKills: "Tapetut Pelaajat" + playerList: "Pelaajalista" + playerOverview: "Yhteenveto Pelaajasta" + playerPage: "Pelaajan sivu" + playerRetention: "Player Retention" + playerbase: "Pelaajakunta" + playerbaseDevelopment: "Pelaajakunnan kehitys" + playerbaseOverview: "Playerbase Overview" + players: "Pelaajia" + playersOnline: "Pelaajia Paikalla" + playersOnlineNow: "Players Online (Now)" + playersOnlineOverview: "Yhteenveto Paikallaolosta" + playtime: "Peliaika" + plugins: "Lisรคosat" + pluginsOverview: "Plugins Overview" + punchcard: "Reikรคkortti" + punchcard30days: "Reikรคkortti 30 pรคivรคlle" + pvpPve: "PvP & PvE" + pvpPveAsNumbers: "PvP & PvE Numeroina" + query: "Tee kysely" + quickView: "Pika-katsaus" + ram: "RAM" + ramUsage: "RAM Usage" + recentKills: "Viimeaikaiset Tapot" + recentPvpDeaths: "Viimeaikaiset PvP Kuolemat" + recentPvpKills: "Viimeaikaiset PvP Tapot" + recentSessions: "Viimeisimmรคt Istunnot" + registered: "Rekisterรถitynyt" + registeredPlayers: "Rekisterรถidyt pelaajat" + regular: "Kantapelaaja" + regularPlayers: "Kantapelaajia" + relativeJoinActivity: "Verrannollinen liittymis aktiivisuus" + secondDeadliestWeapon: "2. PvP Ase" + seenNicknames: "Nรคhdyt Lempinimet" + server: "Palvelin" + serverAnalysis: "Palvelimen Analyysi" + serverAsNumberse: "Palvelin Numeroina" + serverCalendar: "Server Calendar" + serverDowntime: "Palvelin pois pรครคltรค" + serverOccupied: "Palvelin pelaajien kรคytรถssรค" + serverOverview: "Yhteenveto Palvelimesta" + serverPage: "Palvelin" + serverPlaytime: "Palvelimen peliaika" + serverPlaytime30days: "Palvelimen peliaika 30 pรคivรคlle" + serverSelector: "Server selector" + servers: "Palvelimet" + serversTitle: "PALVELIMET" + session: "Istunto" + sessionCalendar: "Session Calendar" + sessionEnded: " Istunto Pรครคttyi" + sessionMedian: "Istuntojen Mediaani" + sessionStart: "Istunto alkoi" + sessions: "Istunnot" + sortBy: "Sort By" + stacked: "Stacked" + themeSelect: "Teemavalikko" + thirdDeadliestWeapon: "3. PvP Ase" + thirtyDays: "30 pรคivรครค" + thirtyDaysAgo: "30 pรคivรครค sitten" + timesKicked: "Heitetty pihalle" + toMainPage: "pรครคsivu" + total: "Total" + totalActive: "Aktiivisena" + totalAfk: "AFK" + totalPlayers: "Pelaajia" + totalPlayersOld: "Kaikki Pelaajat" + totalPlaytime: "Peliaikaa yhteensรค" + totalServerDowntime: "Total Server Downtime" + tps: "TPS" + trend: "Suunta" + trends30days: "Suunnat 30 pรคivรคlle" + uniquePlayers: "Uniikkeja pelaajia" + uniquePlayers7days: "Unique Players (7 days)" + veryActive: "Todella Aktiivinen" + weekComparison: "Viikkojen vertaus" + weekdays: "'Maanantai', 'Tiistai', 'Keskiviikko', 'Torstai', 'Perjantai', 'Lauantai', 'Sunnuntai'" + world: "Maailmojen Resurssit" + worldPlaytime: "Maailmakohtainen Peliaika" + worstPing: "Huonoin Vasteaika" + login: + failed: "Kirjautuminen epรคonnistui:" + forgotPassword: "Unohtuiko salasana?" + forgotPassword1: "Unohtuiko salasana? Poista kรคyttรคjรค ja uudelleen rekisterรถidy." + forgotPassword2: "Kรคytรค komentoa pelissรค poistaaksesi kรคyttรคjรคsi:" + forgotPassword3: "Tai konsolia:" + forgotPassword4: "Komennon jรคlkeen," + login: "Kirjaudu" + logout: "Kirjaudu ulos" + password: "Salasana" + register: "Luo kรคyttรคjรค!" + username: "Kรคyttรคjรคnimi" + modal: + info: + bugs: "Ilmoita ongelmista" + contributors: + bugreporters: "& Bugien ilmoittajat!" + code: "koodin tuottaja" + donate: "Suuret kiitokset rahallisesti tukeneille henkilรถille." + text: 'Myรถs seuraavat mahtavat ihmiset ovat tukeneet kehitystรค:' + translator: "kรครคntรคjรค" + developer: "on kehittรคnyt" + discord: "Discord-tuki" + license: "Player Analytics:iรค kehitetรครคn, ja kรคyttรครค lisenssiรค" + metrics: "bStats Metriikat" + text: "Tietoa lisรคosasta" + wiki: "Plan Wiki, ohjeet ja dokumentaatio" + version: + available: "on saatavilla" + changelog: "Katso muutoslokia" + dev: "Tรคmรค versio on KEHITYS versio." + download: "Lataa" + text: "Uusi versio on julkaistu ja on nyt ladattavissa." + title: "Versio" + query: + filter: + activity: + text: "ovat Aktiivisuus Luokissa" + banStatus: + name: "Ban status" + banned: "Pannassa" + country: + text: "have joined from country" + generic: + allPlayers: "All players" + and: "ja" + start: "pelaajista ketkรค" + joinAddress: + text: "joined with address" + nonOperators: "Ei-operaattorit" + notBanned: "Ei-pannassa" + operatorStatus: + name: "Operator status" + operators: "Operaattorit" + playedBetween: + text: "Pelasivat vรคlillรค" + pluginGroup: + name: "Group: " + text: "ovat ${plugin}:n ${group} Luokissa" + registeredBetween: + text: "Rekisterรถityvรคt vรคlillรค" + title: + activityGroup: "Current activity group" + view: " Nรคkymรค:" + filters: + add: "Lisรครค suodatin.." + loading: "Ladataan suodattimia.." + generic: + are: "`ovat`" + label: + from: ">tรคstรค" + makeAnother: "Tee toinen kysely" + to: ">tรคnne" + view: "Nรคytรค nรคkymรค" + performQuery: "Tee kysely!" + results: + match: "vastasi ${resultCount} pelaajaa" + none: "Ei tuloksia" + title: "Kyselyn tulokset" + title: + activity: "Vastaavien pelaajien aktiivisuus" + activityOnDate: 'Aktiivisuus ' + sessionsWithinView: "Istunnot nรคkymรคn sisรคllรค" + text: "Kysely<" + register: + completion: "Viimeistele rekisterรถinti" + completion1: "Voit viimeistellรค kรคyttรคjรคn rekisterรถinnin." + completion2: "Koodi vanhenee 15 minuutissa" + completion3: "Kรคytรค seuraavaa komentoa pelissรค viimeistellรคksesi rekisterรถinnin:" + completion4: "Tai konsolia:" + createNewUser: "Luo uusi kรคyttรคjรค" + error: + checkFailed: "Rekisterรถitymisen tilan tarkistus epรคonnistui:" + failed: "Rekisterรถinti epรคonnistui:" + noPassword: "Anna salasana" + noUsername: "Anna kรคyttรคjรคnimi" + usernameLength: "Kรคyttรคjรคnimi voi olla enintรครคn 50 merkkiรค, sinun on" + login: "Aiempi kรคyttรคjรค? Kirjaudu sisรครคn!" + passwordTip: "Salasana kannattaa olla yli 8 merkkiรค, mutta ei ole rajoituksia." + register: "Rekisterรถidy" + usernameTip: "Kรคyttรคjรคnimi voi olla enintรครคn 50 merkkiรค." + text: + clickToExpand: "Klikkaa laajentaaksesi" + comparing15days: "Verrataan 15 pรคivรครค" + comparing30daysAgo: "Verrataan 30 pรคivรครค sitten nykyhetkeen" + noExtensionData: "Ei dataa lisรคosista" + noLowTps: "Ei matalan TPS:n piikkejรค" + unit: + chunks: "Chunkkia" + players: "Pelaajia" + value: + localMachine: "Paikallinen laite" + noKills: "Ei Tappoja" + offline: " Ei Paikalla" + online: " Paikalla" + with: "Millรค" + version: + changelog: "Katso muutoslistaa" + current: "Sinulla on versio ${0}" + download: "Lataa Plan-${0}.jar" + isDev: "Tรคmรค versio on DEV julkaisu." + updateButton: "Pรคivitys" + updateModal: + text: "Uusi versio on julkaistu ja on ladattavissa." + title: "Versio ${0} on Saatavilla!" +plugin: + apiCSSAdded: "PageExtension: ${0} lisรคsi tyylejรค sivulle ${1}, ${2}" + apiJSAdded: "PageExtension: ${0} lisรคsi javascriptia sivulle ${1}, ${2}" + disable: + database: "Suoritetaan kriittisiรค suorittamattomia toimintoja. (${0})" + disabled: "Player Analytics Sammutettu." + processingComplete: "Suoritus valmis." + savingSessions: "Tallennetaan pรครคttymรคttรถmรคt istunnot.." + savingSessionsTimeout: "Timeout hit, storing the unfinished sessions on next enable instead." + waitingDb: "Waiting queries to finish to avoid SQLite crashing JVM.." + waitingDbComplete: "Closed SQLite connection." + waitingTransactions: "Waiting for unfinished transactions to avoid data loss.." + waitingTransactionsComplete: "Transaction queue closed." + webserver: "Web palvelin on sammutettu." + enable: + database: "${0}-tietokanta yhteys luotu." + enabled: "Player Analytics Kรคynnistetty." + fail: + database: "${0}-Tietokanta yhteys epรคonnistui: ${1}" + databasePatch: "Tietokannan muutokset epรคonnistuivat, Plan jouduttiin sulkemaan. Ilmoita tรคstรค ongelmasta" + databaseType: "${0} ei ole tuettu Tietokanta" + geoDBWrite: "Jokin meni pieleen tallentaessa GeoLite2 tietokantaa" + webServer: "Web Palvelin ei kรคynnistynyt!" + notify: + badIP: "0.0.0.0 ei ole toimiva osoite, aseta Alternative_IP asetukset. Linkit ovat virheellisiรค!" + emptyIP: "IP server.properties tiedostossa on tyhjรค & Alternative_IP ei ole kรคytรถssรค. Linkit ovat virheellisiรค!" + geoDisabled: "Sijaintien kerรคys ei ole pรครคllรค. (Data.Geolocations: false)" + geoInternetRequired: "Plan Vaatii internetin ensimmรคisellรค kรคynnistyskerralla GeoLite2 tietokannan lataamiseen." + storeSessions: "Storing sessions that were preserved before previous shutdown." + webserverDisabled: "Web Palvelinta ei kรคynnistetty. (WebServer.DisableWebServer: true)" + webserver: "Web Palvelin pyรถrii PORTILLA ${0} ( ${1} )" + generic: + dbApplyingPatch: "Muutetaan Tietokantaa: ${0}.." + dbFaultyLaunchOptions: "LauchOptions-asetus oli virheellinen, kรคytetรครคn oletusta (${0})" + dbNotifyClean: "Poistetiin ${0}n pelaajan tiedot." + dbNotifySQLiteWAL: "SQLite WAL tilaa ei tueta tรคllรค versiolla, Kรคytetรครคn perustilaa. Tรคmรค voi vaikuttaa suorituskykyyn." + dbPatchesAlreadyApplied: "Kaikki tietokannan muutokset oli jo tehty." + dbPatchesApplied: "Tietokannan muutokset suoritettu onnistuneesti." + dbSchemaPatch: "Database: Making sure schema is up to date.." + loadedServerInfo: "Server identifier loaded: ${0}" + loadingServerInfo: "Loading server identifying information" + no: "Ei" + today: "'Tรคnรครคn'" + unavailable: "Ei saatavilla" + unknown: "Tuntematon" + yes: "Kyllรค" + yesterday: "'Eilen'" + version: + checkFail: "Uuden version tarkistus epรคonnistui" + checkFailGithub: "Uuden version tarkistus epรคonnistui Github/versions.txt:ta" + isDev: " Tรคmรค on KEHITYS julkaisu." + isLatest: "Kรคytรคt uusinta versiota." + updateAvailable: "Uusi Julkaisu (${0}) on saatavilla ${1}" + updateAvailableSpigot: "Uusi Versio on saatavilla ${0}" + webserver: + fail: + SSLContext: "Web Palvelin: SSL Viitepalvelun kรคynnistys ei onnistunut." + certFileEOF: "Web Palvelin: EOF lukiessa Sertifikaattia. (Tarkista ettรค tiedosto ei ole tyhjรค)" + certStoreLoad: "Web Palvelin: SSL Sertifikaatin lataus ei onnistunut." + portInUse: "Web Palvelin ei kรคynnistynyt. Onko portti (${0}) kรคytรถssรค?" + notify: + authDisabledConfig: "Web Palvelin: Kรคyttรคjien varmennus ei kรคytรถssรค! (Sammutettu asetuksista)" + authDisabledNoHTTPS: "Web Palvelin: Kรคyttรคjien varmennus ei kรคytรถssรค! (Ei turvallista HTTP-protokollalla)" + certificateExpiresOn: "Webserver: Loaded certificate is valid until ${0}." + certificateExpiresPassed: "Webserver: Certificate has expired, consider renewing the certificate." + certificateExpiresSoon: "Webserver: Certificate expires in ${0}, consider renewing the certificate." + certificateNoSuchAlias: "Webserver: Certificate with alias '${0}' was not found inside the keystore file '${1}'." + http: "Web Palvelin: Ei Sertifikaattia -> Kรคytetรครคn HTTP-Palvelinta." + ipWhitelist: "Web Palvelin: IP sallimislista kรคytรถssรค." + ipWhitelistBlock: "Web Palvelin: ${0} kiellettiin pรครคsy osoitteeseen '${1}'. (ei sallimislistalla)" + noCertFile: "Web Palvelin: Sertifikaatti AvainKirjasto tiedostoa ei lรถytynyt: ${0}" + reverseProxy: "Web Palvelin: Proxy-tolan HTTPS kรคytรถssรค, varmista ettรค reverse-proxy kรคyttรครค HTTPS ja ettรค Plan Alternative_IP.Address osoittaa Proxy-palvelimeen" diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_FR.txt b/Plan/common/src/main/resources/assets/plan/locale/locale_FR.txt deleted file mode 100644 index 7d4c08bce..000000000 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_FR.txt +++ /dev/null @@ -1,517 +0,0 @@ -API - css+ || Extension de Page : ${0} a ajoutรฉ une ou plusieurs feuilles de style ร  ${1}, ${2} -API - js+ || Extension de Page : ${0} a ajoutรฉ un ou plusieurs JavaScript ร  ${1}, ${2} -Cmd - Click Me || Cliquez ici -Cmd - Link || Lien -Cmd - Link Network || Page du Rรฉseau : -Cmd - Link Player || Page du Joueur : -Cmd - Link Player JSON || JSON du Joueur : -Cmd - Link Players || Page des Joueurs : -Cmd - Link Register || Page d'enregistrement : -Cmd - Link Server || Page du Serveur : -CMD Arg - backup-file || Nom du fichier de sauvegarde (sensibles aux minuscules/majuscules) -CMD Arg - code || Code utilisรฉ pour finaliser l'enregistrement. -CMD Arg - db type backup || Type de la base de donnรฉes pour sauvegarder. Si non spรฉcifiรฉ, la base de donnรฉes actuelle est utilisรฉe. -CMD Arg - db type clear || Type de la base de donnรฉes pour supprimer toutes les donnรฉes. -CMD Arg - db type hotswap || Type de la base de donnรฉes pour commencer l'utilisation. -CMD Arg - db type move from || Type de la base de donnรฉes pour fournir les donnรฉes. -CMD Arg - db type move to || Type de la base de donnรฉes pour recevoir les donnรฉes. Doit รชtre diffรฉrente de la prรฉcรฉdente. -CMD Arg - db type restore || Type de la base de donnรฉes pour restaurer. Si non spรฉcifiรฉ, la base de donnรฉes actuelle est utilisรฉe. -CMD Arg - feature || Nom de la fonctionnalitรฉ ร  dรฉsactiver : ${0} -CMD Arg - player identifier || Nom ou UUID d'un joueur -CMD Arg - player identifier remove || Identifiant d'un joueur qui sera supprimรฉ de la base de donnรฉes actuelle. -CMD Arg - server identifier || Nom, ID ou UUID d'un serveur -CMD Arg - subcommand || Utilisez cette commande sans sous-commande pour afficher l'aide. -CMD Arg - username || Nom d'utilisateur d'un autre utilisateur. Si non spรฉcifiรฉ, l'utilisateur liรฉ au joueur est utilisรฉ. -CMD Arg Name - backup-file || fichier-de-sauvegarde -CMD Arg Name - code || ${code} -CMD Arg Name - export kind || type d'exportation -CMD Arg Name - feature || fonctionnalitรฉ -CMD Arg Name - import kind || type d'importation -CMD Arg Name - name or uuid || nom/uuid -CMD Arg Name - server || serveur -CMD Arg Name - subcommand || sous-commande -CMD Arg Name - username || nom d'utilisateur -Cmd Confirm - accept || Accepter -Cmd Confirm - cancelled, no data change || Annulรฉ. Aucune donnรฉe n'a รฉtรฉ modifiรฉe. -Cmd Confirm - cancelled, unregister || Annulรฉ. '${0}' n'รฉtait pas enregistrรฉ -Cmd Confirm - clearing db || Vous รชtes sur le point de supprimer toutes les donnรฉes de Plan de ${0} -Cmd Confirm - confirmation || Confirmer : -Cmd Confirm - deny || Annuler -Cmd Confirm - Expired || La confirmation a expirรฉ, utilisez ร  nouveau la commande -Cmd Confirm - Fail on accept || L'action acceptรฉe a รฉchouรฉ lors de l'exรฉcution : ${0} -Cmd Confirm - Fail on deny || L'action refusรฉe a รฉchouรฉ lors de l'exรฉcution : ${0} -Cmd Confirm - overwriting db || Vous รชtes sur le point d'รฉcraser des donnรฉes dans de Plan ${0} avec les donnรฉes de ${1} -Cmd Confirm - remove player db || Vous รชtes sur le point de supprimer les donnรฉes de ${0} depuis ${1} -Cmd Confirm - unregister || Vous รชtes sur le point de dรฉsenregistrer '${0}' liรฉ ร  ${1} -Cmd db - creating backup || Crรฉation d'un fichier de sauvegarde '${0}.db' avec les contenus de ${1} -Cmd db - removal || Suppression des donnรฉes de Plan depuis ${0}.. -Cmd db - removal player || Suppression des donnรฉes de ${0} depuis ${1}.. -Cmd db - server uninstalled || ยงaSi le serveur est toujours installรฉ, il se dรฉfinira automatiquement comme dans la base de donnรฉes. -Cmd db - write || ร‰criture ร  ${0}.. -Cmd Disable - Disabled || ยงaLes systรจmes de Plan sont maintenant dรฉsactivรฉs. Vous pouvez toujours exรฉcuter la commande 'reload' pour les redรฉmarrer. -Cmd FAIL - Accepts only these arguments || Accepte les รฉlรฉments suivants comme ${0} : ${1} -Cmd FAIL - Database not open || ยงcLa base de donnรฉes est : ${0} - Merci de rรฉessayer plus tard. -Cmd FAIL - Empty search string || La chaรฎne de recherche ne peut pas รชtre vide -Cmd FAIL - Invalid Username || ยงcCet utilisateur ne possรจde pas d'UUID. -Cmd FAIL - No Feature || ยงeDรฉfinir une fonctionnalitรฉ ร  dรฉsactiver ! (supporte actuellement ${0}) -Cmd FAIL - No Permission || ยงcVous ne possรฉdez pas la permission requise. -Cmd FAIL - No player || Joueur '${0}' non trouvรฉ, il n'a pas d'UUID. -Cmd FAIL - No player register || Joueur '${0}' non trouvรฉ dans la base de donnรฉes. -Cmd FAIL - No server || Serveur '${0}' non trouvรฉ dans la base de donnรฉes. -Cmd FAIL - Require only one Argument || ยงcUn argument est requis ${1} -Cmd FAIL - Requires Arguments || ยงcDes arguments sont requis (${0}) ${1} -Cmd FAIL - see config || voir '${0}' dans config.yml -Cmd FAIL - Unknown Username || ยงcCet utilisateur ne s'est jamais connectรฉ sur ce serveur. -Cmd FAIL - Users not linked || Aucun utilisateur n'est pas liรฉ ร  votre compte et vous n'avez pas la permission de supprimer les comptes d'autres utilisateurs. -Cmd FAIL - WebUser does not exists || ยงcCet utilisateur n'existe pas ! -Cmd FAIL - WebUser exists || ยงcCet utilisateur existe dรฉjร  ! -Cmd Footer - Help || ยง7Survolez la commande / les arguments ou utilisez '/${0} ?' pour en savoir plus sur eux. -Cmd Header - Analysis || > ยง2Rรฉsultats de l'analyse -Cmd Header - Help || > ยง2/${0} Help -Cmd Header - Info || > ยง2Analyse du joueur -Cmd Header - Inspect || > ยง2Joueur : ยงf${0} -Cmd Header - Network || > ยง2Page du rรฉseau -Cmd Header - Players || > ยง2Joueurs -Cmd Header - Search || > ยง2${0} Rรฉsultats pour ยงf${1}ยง2 : -Cmd Header - server list || id::nom d'utilisateur::uuid -Cmd Header - Servers || > ยง2Serveurs -Cmd Header - web user list || nom d'utilisateur::liรฉ ร ::permission niveau -Cmd Header - Web Users || > ยง2${0} Utilisateurs Web -Cmd Info - Bungee Connection || ยง2Connectรฉ : ยงf${0} -Cmd Info - Database || ยง2Base de donnรฉes actuelle : ยงf${0} -Cmd Info - Reload Complete || ยงaRechargement terminรฉ. -Cmd Info - Reload Failed || ยงcUne erreur s'est produite lors du rechargement du plugin, un redรฉmarrage total est recommandรฉ. -Cmd Info - Update || ยง2Mise ร  jour disponible : ยงf${0} -Cmd Info - Version || ยง2Version : ยงf${0} -Cmd network - No network || Le serveur n'est pas connectรฉ ร  un rรฉseau. Le lien redirige vers la page du serveur. -Cmd Notify - No Address || ยงeAucune adresse รฉtant disponible - utilisation localhost comme solution de repli. Veuillez configurer le paramรจtre 'Alternative_IP's. -Cmd Notify - No WebUser || Vous n'avez peut-รชtre pas d'utilisateur Web, essayez d'exรฉcuter '/plan register ' afin d'y remรฉdier. -Cmd Notify - WebUser register || Nouvel utilisateur enregistrรฉ : '${0}' Niveau de permission : ${1}. -Cmd Qinspect - Active Playtime || ยง2Temps Actif : ยงf${0} -Cmd Qinspect - Activity Index || ยง2Indice d'Activitรฉ : ยงf${0} | ${1} -Cmd Qinspect - AFK Playtime || ยง2Temps AFK : ยงf${0} -Cmd Qinspect - Deaths || ยง2Morts : ยงf${0} -Cmd Qinspect - Geolocation || ยง2Gรฉolocalisation : ยงf${0} -Cmd Qinspect - Last Seen || ยง2Derniรจre Connexion : ยงf${0} -Cmd Qinspect - Longest Session || ยง2Session la plus longue : ยงf${0} -Cmd Qinspect - Mob Kills || ยง2Kills de Mobs : ยงf${0} -Cmd Qinspect - Player Kills || ยง2Kills de Joueurs : ยงf${0} -Cmd Qinspect - Playtime || ยง2Temps de Jeu : ยงf${0} -Cmd Qinspect - Registered || ยง2Incription : ยงf${0} -Cmd Qinspect - Times Kicked || ยง2Nombre d'ร‰jections : ยงf${0} -Cmd SUCCESS - Feature disabled || ยงaFonctionnalitรฉ '${0}' temporairement dรฉsactivรฉe jusqu'au prochain rechargement du plugin. -Cmd SUCCESS - WebUser register || ยงaAjout d'un nouvel utilisateur (${0}) avec succรจs ! -Cmd unregister - unregistering || Dรฉsenregistrement de '${0}'.. -Cmd WARN - Database not open || ยงeLa base de donnรฉes est : ${0} - Cela pourrait prendre plus de temps que prรฉvu... -Cmd Web - Permission Levels || >\ยง70 : Accรฉder ร  toutes les pages.\ยง71 : Accรฉder au '/players' et ร  toutes les pages des joueurs.\ยง72 : Accรฉder ร  la page du joueur avec le mรชme nom d'utilisateur que l'utilisateur Web.\ยง73+ : Pas de permission. -Command Help - /plan db || Gรฉrer la base de donnรฉes de Plan -Command Help - /plan db backup || Sauvegarder la base donnรฉes vers un fichier -Command Help - /plan db clear || Supprimer TOUTES les donnรฉes de Plan dans une base de donnรฉes -Command Help - /plan db hotswap || Changer rapidement de base de donnรฉes -Command Help - /plan db move || Dรฉplacer des donnรฉes entre bases de donnรฉes -Command Help - /plan db remove || Supprimer les donnรฉes d'un joueur dans la base de donnรฉes -Command Help - /plan db restore || Restaurer les donnรฉes depuis un fichier ou une base de donnรฉes -Command Help - /plan db uninstalled || Dรฉfinir un serveur comme dรฉsinstallรฉ au sein de la base de donnรฉes -Command Help - /plan disable || Dรฉsactiver le plugin ou une partie de lui -Command Help - /plan export || Exporter les fichiers HTML ou JSON manuellement -Command Help - /plan import || Importer des donnรฉes -Command Help - /plan info || Informations concernant le plugin -Command Help - /plan ingame || Visualiser les informations d'un Joueur (en jeu) -Command Help - /plan json || Visualiser le JSON des donnรฉes brutes d'un joueur -Command Help - /plan logout || Dรฉconnecter les autres utilisateurs du panel -Command Help - /plan network || Visualiser la page du rรฉseau -Command Help - /plan player || Visualiser la page d'un joueur -Command Help - /plan players || Visualiser la page des joueurs -Command Help - /plan register || Enregistrer un utilisateur Web -Command Help - /plan reload || Recharger Plan -Command Help - /plan search || Rechercher un joueur -Command Help - /plan server || Visualiser la page du serveur -Command Help - /plan servers || Obtenir la liste des serveurs dans la base de donnรฉes -Command Help - /plan unregister || Dรฉsenregistrer un utilisateur du site de Plan -Command Help - /plan users || Lister les utilisateurs du site Web -Database - Apply Patch || Application de correctifs : ${0}... -Database - Patches Applied || Tous les correctifs pour la base de donnรฉes ont รฉtรฉ appliquรฉs avec succรจs. -Database - Patches Applied Already || Tous les correctifs pour la base de donnรฉes ont dรฉjร  รฉtรฉ appliquรฉs. -Database MySQL - Launch Options Error || Configurations dรฉfectueuses, utilisation de celles par dรฉfaut (${0}) -Database Notify - Clean || Suppression des donnรฉes de ${0} joueurs. -Database Notify - SQLite No WAL || Le mode WAL de SQLite n'est pas pris en charge sur cette version du serveur, en utilisant le mode par dรฉfaut. Cela peut possiblement affecter les performances. -Disable || Plan a รฉtรฉ dรฉsactivรฉ. -Disable - Processing || Traitement des tรขches critiques inachevรฉes... (${0}) -Disable - Processing Complete || Traitement complรฉtรฉ. -Disable - Unsaved Session Save || Sauvegarde des sessions inachevรฉes... -Disable - Unsaved Session Save Timeout || Timeout atteint - stockage des sessions non terminรฉes lors du prochain dรฉmarrage. -Disable - Waiting SQLite || En attente de la finalisation des requรชtes pour รฉviter que SQLite ne plante la JVM.. -Disable - Waiting SQLite Complete || Connexion SQLite fermรฉe. -Disable - Waiting Transactions || En attente des transactions non terminรฉes pour รฉviter la perte de donnรฉes.. -Disable - Waiting Transactions Complete || File d'attente des transactions fermรฉe. -Disable - WebServer || Le serveur Web a รฉtรฉ dรฉsactivรฉ. -Enable || Plan a รฉtรฉ activรฉ. -Enable - Database || Connexion ร  la base de donnรฉes รฉtablie. (${0}) -Enable - Notify Bad IP || L'adresse IP situรฉe dans le fichier 'server.properties' est รฉrronรฉe. Attention, des liens incorrects seront donnรฉs ! -Enable - Notify Empty IP || L'adresse IP situรฉe dans le fichier 'server.properties' est vide et l'option 'Alternative_IP' n'est pas utilisรฉe. Attention, des liens incorrects seront donnรฉs ! -Enable - Notify Geolocations disabled || La Gรฉolocalisation n'est pas active. (Data.Geolocations: false) -Enable - Notify Geolocations Internet Required || Plan nรฉcessite un accรจs ร  Internet lors de sa premiรจre utilisation pour tรฉlรฉcharger la base de donnรฉes 'GeoLite2 Geolocation'. -Enable - Notify Webserver disabled || Le serveur Web n'a pas รฉtรฉ initialisรฉ. (WebServer.DisableWebServer: true) -Enable - Storing preserved sessions || Stockage des sessions ayant รฉtรฉ prรฉservรฉes lors de l'arrรชt prรฉcรฉdent. -Enable - WebServer || Le serveur Web communique ร  travers le port ${0} ( ${1} ). -Enable FAIL - Database || La connexion ร  la base de donnรฉes a รฉchouรฉ. (${0}) : ${1} -Enable FAIL - Database Patch || L'application de correctifs pour la base de donnรฉes a รฉchouรฉ, le plugin doit รชtre dรฉsactivรฉ. Merci de signaler ce problรจme. -Enable FAIL - GeoDB Write || Une erreur s'est produite lors de l'enregistrement de la base de donnรฉes 'GeoLite2 Geolocation' tรฉlรฉchargรฉe prรฉcรฉdemment. -Enable FAIL - WebServer (Proxy) || Le serveur Web n'a pas รฉtรฉ initialisรฉ ! -Enable FAIL - Wrong Database Type || ${0} n'est pas une base de donnรฉes prise en charge. -HTML - AND_BUG_REPORTERS || & Rapporteurs de Bugsโ€ฏ! -HTML - BANNED (Filters) || Banni(e) -HTML - COMPARING_15_DAYS || Comparaison des 15 derniers Jours -HTML - COMPARING_60_DAYS || Comparaison des 60 derniers Jours -HTML - COMPARING_7_DAYS || Comparaison des 7 derniers Jours -HTML - DATABASE_NOT_OPEN || La base de donnรฉes n'est pas ouverte, vรฉrifiez son รฉtat avec la commande /plan info -HTML - DESCRIBE_RETENTION_PREDICTION || Cette valeur est une prรฉdiction basรฉe sur les joueurs prรฉcรฉdents. -HTML - ERROR || Authentification รฉchouรฉe en raison d'une erreur -HTML - EXPIRED_COOKIE || Le cookie de l'utilisateur a expirรฉ -HTML - FILTER_ACTIVITY_INDEX_NOW || Groupe d'Activitรฉ actuel -HTML - FILTER_ALL_PLAYERS || Tous les Joueurs -HTML - FILTER_BANNED || Statut de Bannissement -HTML - FILTER_GROUP || Groupe : -HTML - FILTER_OPS || Statut d'Opรฉrateur -HTML - INDEX_ACTIVE || Actif -HTML - INDEX_INACTIVE || Inactif -HTML - INDEX_IRREGULAR || Irrรฉgulier -HTML - INDEX_REGULAR || Rรฉgulier -HTML - INDEX_VERY_ACTIVE || Trรจs Actif -HTML - KILLED || Tuรฉ(e) -HTML - LABEL_1ST_WEAPON || 1รจre Arme de Combat (la plus mortelle) -HTML - LABEL_2ND_WEAPON || 2แต‰ Arme de Combat -HTML - LABEL_3RD_WEAPON || 3แต‰ Arme de Combat -HTML - LABEL_ACTIVE_PLAYTIME || Temps Actif -HTML - LABEL_ACTIVITY_INDEX || Indice d'Activitรฉ -HTML - LABEL_AFK || AFK -HTML - LABEL_AFK_TIME || Temps AFK -HTML - LABEL_AVG || Moyen(ne) -HTML - LABEL_AVG_ACTIVE_PLAYTIME || Temps Actif moyen -HTML - LABEL_AVG_AFK_TIME || Temps AFK moyen -HTML - LABEL_AVG_CHUNKS || Quantitรฉ moyenne de Chunks -HTML - LABEL_AVG_ENTITIES || Quantitรฉ moyenne d'Entitรฉs -HTML - LABEL_AVG_KDR || Ratio - Kills / Morts - moyen -HTML - LABEL_AVG_MOB_KDR || Ratio - Kills / Morts - de Mobs moyen -HTML - LABEL_AVG_PLAYTIME || Temps de Jeu moyen -HTML - LABEL_AVG_SESSION_LENGTH || Durรฉe moyenne d'une Session -HTML - LABEL_AVG_SESSIONS || Quantitรฉ moyenne de Sessions -HTML - LABEL_AVG_TPS || TPS moyen -HTML - LABEL_BANNED || Banni(e) -HTML - LABEL_BEST_PEAK || Pic maximal de Joueurs en Ligne -HTML - LABEL_DAY_OF_WEEK || Jour de la Semaine -HTML - LABEL_DEATHS || Morts -HTML - LABEL_DOWNTIME || Temps Hors-Ligne -HTML - LABEL_DURING_LOW_TPS || Pendant les pics de TPS bas : -HTML - LABEL_ENTITIES || Entitรฉs -HTML - LABEL_FAVORITE_SERVER || Serveur Favori -HTML - LABEL_FIRST_SESSION_LENGTH || Durรฉe de la premiรจre Session -HTML - LABEL_FREE_DISK_SPACE || Espace Disque disponible -HTML - LABEL_INACTIVE || Inactif(ve) -HTML - LABEL_LAST_PEAK || Dernier pic de Joueurs en Ligne -HTML - LABEL_LAST_SEEN || Derniรจre Connexion -HTML - LABEL_LOADED_CHUNKS || Chunks Chargรฉs -HTML - LABEL_LOADED_ENTITIES || Entitรฉs Chargรฉes -HTML - LABEL_LONE_JOINS || Connexions de Joueurs Seuls -HTML - LABEL_LONE_NEW_JOINS || Connexions de Dรฉbutants Seuls -HTML - LABEL_LONGEST_SESSION || Session la plus Longue -HTML - LABEL_LOW_TPS || Pics de TPS bas -HTML - LABEL_MAX_FREE_DISK || Espace Disque MAX disponible -HTML - LABEL_MIN_FREE_DISK || Espace Disque MIN disponible -HTML - LABEL_MOB_DEATHS || Morts causรฉes par un Mob -HTML - LABEL_MOB_KDR || Ratio - Kills / Morts de Mobs - -HTML - LABEL_MOB_KILLS || Kills de Mobs -HTML - LABEL_MOST_ACTIVE_GAMEMODE || Mode de Jeu le plus utilisรฉ -HTML - LABEL_NAME || Nom -HTML - LABEL_NEW || Nouveau(elle) -HTML - LABEL_NEW_PLAYERS || Nouveaux Joueurs -HTML - LABEL_NICKNAME || Surnom -HTML - LABEL_NO_SESSION_KILLS || Vide -HTML - LABEL_ONLINE_FIRST_JOIN || Joueurs en Ligne lors de la premiรจre Connexion -HTML - LABEL_OPERATOR || Opรฉrateur -HTML - LABEL_PER_PLAYER || / Joueur -HTML - LABEL_PER_REGULAR_PLAYER || / Joueur Rรฉgulier -HTML - LABEL_PLAYER_DEATHS || Dรฉcรจs causรฉs par le Joueur -HTML - LABEL_PLAYER_KILLS || Kills de Joueurs -HTML - LABEL_PLAYERS_ONLINE || Joueurs en Ligne -HTML - LABEL_PLAYTIME || Temps de Jeu -HTML - LABEL_REGISTERED || Inscription -HTML - LABEL_REGISTERED_PLAYERS || Joueurs Enregistrรฉs -HTML - LABEL_REGULAR || Rรฉgulier(รจre) -HTML - LABEL_REGULAR_PLAYERS || Joueurs Rรฉguliers -HTML - LABEL_RELATIVE_JOIN_ACTIVITY || Activitรฉ de Connexion relative -HTML - LABEL_RETENTION || Rรฉtention des nouveaux Joueurs -HTML - LABEL_SERVER_DOWNTIME || Temps Hors-Ligne du Serveur -HTML - LABEL_SERVER_OCCUPIED || Serveur Inoccupรฉ -HTML - LABEL_SESSION_ENDED || Session Terminรฉe -HTML - LABEL_SESSION_MEDIAN || Session Mรฉdiane -HTML - LABEL_TIMES_KICKED || Nombre d'ร‰jections -HTML - LABEL_TOTAL_PLAYERS || Joueurs Totaux -HTML - LABEL_TOTAL_PLAYTIME || Temps de Jeu Total -HTML - LABEL_UNIQUE_PLAYERS || Joueurs Uniques -HTML - LABEL_WEEK_DAYS || 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche' -HTML - LINK_BACK_NETWORK || Page du Rรฉseau -HTML - LINK_BACK_SERVER || Page du Serveur -HTML - LINK_CHANGELOG || Visualiser les changements -HTML - LINK_DISCORD || Support gรฉnรฉral sur Discord -HTML - LINK_DOWNLOAD || Tรฉlรฉchargement -HTML - LINK_ISSUES || Rapport de bugs -HTML - LINK_NIGHT_MODE || Mode Nuit -HTML - LINK_PLAYER_PAGE || Page du Joueur -HTML - LINK_QUICK_VIEW || Aperรงu Rapide -HTML - LINK_SERVER_ANALYSIS || Analyse du Serveur -HTML - LINK_WIKI || Wiki, Documentation & Tutoriaux de Plan -HTML - LOCAL_MACHINE || Machine Locale -HTML - LOGIN_CREATE_ACCOUNT || Crรฉer un compte ! -HTML - LOGIN_FAILED || Connexion รฉchouรฉe : -HTML - LOGIN_FORGOT_PASSWORD || Mot de Passe oubliรฉ ? -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_1 || Mot de Passe oubliรฉ ? Dรฉsenregistrez puis rรฉenregistrez-vous. -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_2 || Utilisez la commande suivante en jeu pour supprimer votre utilisateur actuel : -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_3 || Ou en utilisant la console : -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_4 || Aprรจs avoir utilisรฉ la commande, -HTML - LOGIN_LOGIN || Connexion -HTML - LOGIN_LOGOUT || Dรฉconnexion -HTML - LOGIN_PASSWORD || "Mot de Passe" -HTML - LOGIN_USERNAME || "Nom d'Utilisateur" -HTML - NAV_PLUGINS || Plugins -HTML - NEW_CALENDAR || Nouveau : -HTML - NO_KILLS || Pas de Kills -HTML - NO_USER_PRESENT || Cookie de l'utilisateur non prรฉsent -HTML - NON_OPERATORS (Filters) || Non Opรฉrateur(trice) -HTML - NOT_BANNED (Filters) || Non Banni(e) -HTML - OFFLINE || Hors-Ligne -HTML - ONLINE || En Ligne -HTML - OPERATORS (Filters) || Opรฉrateurs -HTML - PER_DAY || / Jour -HTML - PLAYERS_TEXT || Joueurs -HTML - QUERY || Requรชte< -HTML - QUERY_ACTIVITY_OF_MATCHED_PLAYERS || Activitรฉ des joueurs appariรฉs -HTML - QUERY_ACTIVITY_ON || Activitรฉ sur -HTML - QUERY_ADD_FILTER || Ajouter un filtre.. -HTML - QUERY_AND || et -HTML - QUERY_ARE || `sont` -HTML - QUERY_ARE_ACTIVITY_GROUP || sont dans des Groupes d'Activitรฉ -HTML - QUERY_ARE_PLUGIN_GROUP || sont dans le groupe {group} de ${plugin} -HTML - QUERY_JOINED_WITH_ADDRESS || ont rejoint avec l'adresse -HTML - QUERY_LOADING_FILTERS || Chargement des Filtres.. -HTML - QUERY_MAKE || Faire une Requรชte -HTML - QUERY_MAKE_ANOTHER || Faire une autre Requรชte -HTML - QUERY_OF_PLAYERS || des Joueurs qui -HTML - QUERY_PERFORM_QUERY || Exรฉcuter la Requรชte ! -HTML - QUERY_PLAYED_BETWEEN || Jouรฉs entre -HTML - QUERY_REGISTERED_BETWEEN || Enregistrรฉs entre -HTML - QUERY_RESULTS || Rรฉsultats de la Requรชte -HTML - QUERY_RESULTS_MATCH || ${resultCount} Joueurs appariรฉs -HTML - QUERY_SESSIONS_WITHIN_VIEW || Sessions ร  portรฉe de vue -HTML - QUERY_SHOW_VIEW || Visualiser une vue -HTML - QUERY_TIME_FROM || >de -HTML - QUERY_TIME_TO || >ร  -HTML - QUERY_VIEW || Vue : -HTML - QUERY_ZERO_RESULTS || La Requรชte n'a produit aucun rรฉsultat -HTML - REGISTER || Enregistrer -HTML - REGISTER_CHECK_FAILED || La vรฉrification de l'รฉtat de l'enregistrement a รฉchouรฉ : -HTML - REGISTER_COMPLETE || Enregistrement complet -HTML - REGISTER_COMPLETE_INSTRUCTIONS_1 || Vous pouvez maintenant terminer l'enregistrement de l'utilisateur. -HTML - REGISTER_COMPLETE_INSTRUCTIONS_2 || Le Code expire dans 15 minutes -HTML - REGISTER_COMPLETE_INSTRUCTIONS_3 || Utilisez la commande suivante en jeu pour terminer l'enregistrement : -HTML - REGISTER_COMPLETE_INSTRUCTIONS_4 || Ou en utilisant la console : -HTML - REGISTER_CREATE_USER || Crรฉer un nouvel utilisateur -HTML - REGISTER_FAILED || Enregistrement รฉchouรฉ : -HTML - REGISTER_HAVE_ACCOUNT || Vous avez dรฉjร  un compte ? Connectez-vous ! -HTML - REGISTER_PASSWORD_TIP || Le Mot de Passe devrait comporter plus de 8 caractรจres, mais il n'y a aucune limite. -HTML - REGISTER_SPECIFY_PASSWORD || Veuillez spรฉcifier un Mot de Passe -HTML - REGISTER_SPECIFY_USERNAME || Veuillez spรฉcifier un Nom d'Utilisateur -HTML - REGISTER_USERNAME_LENGTH || Le Nom d'Utilisateur peut comporter jusqu'ร  50 caractรจres, le vรดtre est -HTML - REGISTER_USERNAME_TIP || Le Nom d'Utilisateur peut comporter jusqu'ร  50 caractรจres. -HTML - SESSION || Session -HTML - SIDE_GEOLOCATIONS || Gรฉolocalisation -HTML - SIDE_INFORMATION || INFORMATIONS -HTML - SIDE_LINKS || LIENS -HTML - SIDE_NETWORK_OVERVIEW || Aperรงu du Rรฉseau -HTML - SIDE_OVERVIEW || Vue d'Ensemble -HTML - SIDE_PERFORMANCE || Performances -HTML - SIDE_PLAYER_LIST || Liste des Joueurs -HTML - SIDE_PLAYERBASE || Base de Joueurs -HTML - SIDE_PLAYERBASE_OVERVIEW || Aperรงu de la Base de Joueurs -HTML - SIDE_PLUGINS || PLUGINS -HTML - SIDE_PVP_PVE || PvP & PvE -HTML - SIDE_SERVERS || Serveurs -HTML - SIDE_SERVERS_TITLE || SERVEURS -HTML - SIDE_SESSIONS || Sessions -HTML - SIDE_TO_MAIN_PAGE || Retour ร  la page principale -HTML - TEXT_CLICK_TO_EXPAND || Cliquez pour agrandir -HTML - TEXT_CONTRIBUTORS_CODE || Contributeurs -HTML - TEXT_CONTRIBUTORS_LOCALE || Traducteurs -HTML - TEXT_CONTRIBUTORS_MONEY || Un merci spรฉcial ร  ceux qui ont financiรจrement soutenu le dรฉveloppement. -HTML - TEXT_CONTRIBUTORS_THANKS || En outre, ces gens formidables ont contribuรฉ : -HTML - TEXT_DEV_VERSION || Cette version est rรฉservรฉe au dรฉveloppement. -HTML - TEXT_DEVELOPED_BY || est dรฉveloppรฉ par -HTML - TEXT_FIRST_SESSION || Premiรจre session -HTML - TEXT_LICENSED_UNDER || Player Analytics est dรฉveloppรฉ et fait l'objet d'une licence en vertu de -HTML - TEXT_METRICS || Mรฉtriques bStats -HTML - TEXT_NO_EXTENSION_DATA || Pas de donnรฉes d'extension -HTML - TEXT_NO_LOW_TPS || Pas de pics de TPS bas -HTML - TEXT_NO_SERVER || Aucun serveur pour afficher l'activitรฉ en ligne. -HTML - TEXT_NO_SERVERS || Il n'y a pas de serveur dans la base de donnรฉes. -HTML - TEXT_PLUGIN_INFORMATION || Informations concernant le plugin -HTML - TEXT_PREDICTED_RETENTION || Cette valeur est une prรฉdiction basรฉe sur les anciennes donnรฉes du joueur. -HTML - TEXT_SERVER_INSTRUCTIONS || Il semblerait que Plan ne soit installรฉ sur aucun des serveurs de jeu ou qu'il ne soit pas connectรฉ ร  la mรชme base de donnรฉes. Voir wiki pour un tutoriel sur la mise en place d'un Rรฉseau. -HTML - TEXT_VERSION || Une nouvelle version a รฉtรฉ publiรฉe et est maintenant disponible en tรฉlรฉchargement. -HTML - TITLE_30_DAYS || 30 jours -HTML - TITLE_30_DAYS_AGO || Il y a 30 jours -HTML - TITLE_ALL || Tout -HTML - TITLE_ALL_TIME || Tout le Temps -HTML - TITLE_AS_NUMBERS || en Chiffres -HTML - TITLE_AVG_PING || Latence moyenne -HTML - TITLE_BEST_PING || Meilleure Latence -HTML - TITLE_CALENDAR || Calendrier -HTML - TITLE_CONNECTION_INFO || Renseignements sur la Connexion -HTML - TITLE_COUNTRY || Pays -HTML - TITLE_CPU_RAM || CPU & RAM -HTML - TITLE_CURRENT_PLAYERBASE || Base de Joueurs actuelle -HTML - TITLE_DISK || Espace Disque -HTML - TITLE_GRAPH_DAY_BY_DAY || Jour par Jour -HTML - TITLE_GRAPH_HOUR_BY_HOUR || Heure par Heure -HTML - TITLE_GRAPH_NETWORK_ONLINE_ACTIVITY || Activitรฉ en Ligne du Rรฉseau -HTML - TITLE_GRAPH_PUNCHCARD || Carte perforรฉe sur 30 jours -HTML - TITLE_INSIGHTS || Perspectives sur 30 jours -HTML - TITLE_IS_AVAILABLE || est Disponible -HTML - TITLE_JOIN_ADDRESSES || Adresses de Connexion -HTML - TITLE_LAST_24_HOURS || 24 Derniรจres heures -HTML - TITLE_LAST_30_DAYS || 30 Derniers jours -HTML - TITLE_LAST_7_DAYS || 7 Derniers jours -HTML - TITLE_LAST_CONNECTED || Dernier Connectรฉ -HTML - TITLE_LENGTH || Longueur -HTML - TITLE_MOST_PLAYED_WORLD || Monde le plus Frรฉquentรฉ -HTML - TITLE_NETWORK || Rรฉseau -HTML - TITLE_NETWORK_AS_NUMBERS || Rรฉseau en Chiffres -HTML - TITLE_NOW || Maintenant -HTML - TITLE_ONLINE_ACTIVITY || Activitรฉ en ligne -HTML - TITLE_ONLINE_ACTIVITY_AS_NUMBERS || Activitรฉ en ligne en Chiffres -HTML - TITLE_ONLINE_ACTIVITY_OVERVIEW || Aperรงu de l'Activitรฉ en Ligne -HTML - TITLE_PERFORMANCE_AS_NUMBERS || Performances en Chiffres -HTML - TITLE_PING || Latence -HTML - TITLE_PLAYER || Joueur -HTML - TITLE_PLAYER_OVERVIEW || Aperรงu des Joueurs -HTML - TITLE_PLAYERBASE_DEVELOPMENT || ร‰volution de la base de Joueurs -HTML - TITLE_PVP_DEATHS || Dรฉcรจs rรฉcents en PvP -HTML - TITLE_PVP_KILLS || Kills rรฉcents en PvP -HTML - TITLE_PVP_PVE_NUMBERS || PvP & PvE en Chiffres -HTML - TITLE_RECENT_KILLS || Kills rรฉcents -HTML - TITLE_RECENT_SESSIONS || Sessions rรฉcentes -HTML - TITLE_SEEN_NICKNAMES || Surnoms vus -HTML - TITLE_SERVER || Serveur -HTML - TITLE_SERVER_AS_NUMBERS || Serveur en Chiffres -HTML - TITLE_SERVER_OVERVIEW || Aperรงu du Serveur -HTML - TITLE_SERVER_PLAYTIME || Temps de Jeu du Serveur -HTML - TITLE_SERVER_PLAYTIME_30 || Temps de Jeu du Serveur sur 30 Jours -HTML - TITLE_SESSION_START || Dรฉbut de la Session -HTML - TITLE_THEME_SELECT || Sรฉlection du Thรจme -HTML - TITLE_TITLE_PLAYER_PUNCHCARD || Carte Perforรฉe -HTML - TITLE_TPS || TPS -HTML - TITLE_TREND || Tendances -HTML - TITLE_TRENDS || Tendances sur 30 Jours -HTML - TITLE_VERSION || Version -HTML - TITLE_WEEK_COMPARISON || Comparaison Hebdomadaire -HTML - TITLE_WORLD || Charge du Monde -HTML - TITLE_WORLD_PLAYTIME || Temps de Jeu par Monde -HTML - TITLE_WORST_PING || Pire Latence -HTML - TOTAL_ACTIVE_TEXT || Temps Actif Total -HTML - TOTAL_AFK || Temps Inactif Total -HTML - TOTAL_PLAYERS || Joueurs Totaux -HTML - UNIQUE_CALENDAR || Unique : -HTML - UNIT_CHUNKS || Chunks -HTML - UNIT_ENTITIES || Entitรฉs -HTML - UNIT_NO_DATA || Aucune donnรฉe -HTML - UNIT_THE_PLAYERS || Joueurs -HTML - USER_AND_PASS_NOT_SPECIFIED || Utilisateur et mot de passe non spรฉcifiรฉs -HTML - USER_DOES_NOT_EXIST || Cet utilisateur n'existe pas -HTML - USER_INFORMATION_NOT_FOUND || Enregistrement รฉchouรฉ, veuillez rรฉessayer (Le code expire au bout de 15 minutes) -HTML - USER_PASS_MISMATCH || L'utilisateur et le mot de passe ne correspondent pas -HTML - Version Change log || Visualiser le Changelog -HTML - Version Current || Vous avez la Version ${0} -HTML - Version Download || Tรฉlรฉcharger Plan-${0}.jar -HTML - Version Update || Mise ร  Jour -HTML - Version Update Available || La Version ${0} est Disponible ! -HTML - Version Update Dev || Cette version est rรฉservรฉe au Dรฉveloppement. -HTML - Version Update Info || Une nouvelle version a รฉtรฉ publiรฉe et peut รชtre tรฉlรฉchargรฉe. -HTML - WARNING_NO_GAME_SERVERS || Certaines donnรฉes nรฉcessitent l'installation de Plan sur les serveurs de jeu. -HTML - WARNING_NO_GEOLOCATIONS || La collecte de la gรฉolocalisation doit รชtre activรฉe dans la configuration (Accepter l'EULA de GeoLite2). -HTML - WARNING_NO_SPONGE_CHUNKS || Chunks indisponibles sur Sponge -HTML - WITH || Avec -HTML ERRORS - ACCESS_DENIED_403 || Accรจs refusรฉ. -HTML ERRORS - AUTH_FAIL_TIPS_401 || - Assurez-vous d'avoir enregistrรฉ un utilisateur avec :'/plan register'.
- Vรฉrifiez que le nom d'utilisateur et le mot de passe soient corrects.
- Le nom d'utilisateur et le mot de passe sont sensibles au format majuscule/minuscule.

Si vous avez oubliรฉ votre mot de passe, demandez ร  un membre du staff de supprimer votre ancien utilisateur puis de vous rรฉinscrire. -HTML ERRORS - AUTHENTICATION_FAILED_401 || Authentification รฉchouรฉe. -HTML ERRORS - FORBIDDEN_403 || Accรจs interdit. -HTML ERRORS - NO_SERVERS_404 || Aucun serveur en ligne pour exรฉcuter la demande. -HTML ERRORS - NOT_FOUND_404 || Non trouvรฉ. -HTML ERRORS - NOT_PLAYED_404 || Cet utilisateur ne s'est jamais connectรฉ sur ce serveur. -HTML ERRORS - PAGE_NOT_FOUND_404 || Cette page n'existe pas. -HTML ERRORS - UNAUTHORIZED_401 || Non autorisรฉ. -HTML ERRORS - UNKNOWN_PAGE_404 || Assurez-vous que vous accรฉdez ร  un lien donnรฉ par une commande. Exemples :

/player/{uuid/nom}
/server/{uuid/nom/id}

-HTML ERRORS - UUID_404 || L'UUID de cet utilisateur n'a pas รฉtรฉ trouvรฉ dans la base de donnรฉes. -In Depth Help - /plan db || Utilise diffรฉrentes sous-commandes de base de donnรฉes pour modifier les donnรฉes d'une maniรจre ou d'une autre. -In Depth Help - /plan db backup || Utilise SQLite pour sauvegarder la base de donnรฉes cible dans un fichier. -In Depth Help - /plan db clear || Efface toutes les tables de Plan, supprimant par la mรชme occasion toutes les donnรฉes en traitement de Plan. -In Depth Help - /plan db hotswap || Recharge le plugin avec l'autre base de donnรฉes et modifie la configuration en consรฉquence. -In Depth Help - /plan db move || ร‰crase le contenu de l'autre base de donnรฉes avec le contenu d'une autre. -In Depth Help - /plan db remove || Supprime toutes les donnรฉes liรฉes ร  un joueur de la base de donnรฉes actuelle. -In Depth Help - /plan db restore || Utilise le fichier de sauvegarde SQLite et รฉcrase le contenu de la base de donnรฉes cible. -In Depth Help - /plan db uninstalled || Marque un serveur dans la base de donnรฉes Plan comme รฉtant dรฉsinstallรฉ afin qu'il n'apparaisse pas dans les requรชtes de serveur. -In Depth Help - /plan disable || Dรฉsactive le plugin ou une partie de celui-ci jusqu'au prochain rechargement/redรฉmarrage. -In Depth Help - /plan export || Effectue une exportation vers un emplacement dรฉfini dans la configuration. -In Depth Help - /plan import || Effectue une importation pour charger des donnรฉes dans la base de donnรฉes. -In Depth Help - /plan info || Affiche l'รฉtat actuel du plugin. -In Depth Help - /plan ingame || Affiche des informations sur un joueur en jeu. -In Depth Help - /plan json || Permet de tรฉlรฉcharger les donnรฉes d'un joueur au format JSON. Toutes les donnรฉes. -In Depth Help - /plan logout || Donnez un nom d'utilisateur ร  dรฉconnecter du panel, donnez * comme argument pour dรฉconnecter tout le monde. -In Depth Help - /plan network || Obtient un lien vers la page /network, uniquement sur les rรฉseaux. -In Depth Help - /plan player || Obtient un lien vers la page /player d'un joueur spรฉcifique, ou du joueur actuel. -In Depth Help - /plan players || Permet d'obtenir un lien vers la page /players pour voir la liste des joueurs. -In Depth Help - /plan register || Utiliser sans arguments pour obtenir un lien vers la page d'enregistrement. Utilisez --code [code] aprรจs l'enregistrement pour obtenir un utilisateur. -In Depth Help - /plan reload || Dรฉsactive et active le plugin pour recharger tout changement dans la configuration. -In Depth Help - /plan search || Liste tous les noms de joueurs correspondant ร  une partie donnรฉe d'un nom. -In Depth Help - /plan server || Obtient un lien vers la page /server d'un serveur spรฉcifique, ou du serveur courant si aucun argument n'est donnรฉ. -In Depth Help - /plan servers || Liste les ids, noms et uuids des serveurs de la base de donnรฉes. -In Depth Help - /plan unregister || Utilisez sans argument pour dรฉsenregistrer un utilisateur liรฉ ร  un joueur, ou avec un nom d'utilisateur pour dรฉsenregistrer un autre utilisateur. -In Depth Help - /plan users || Liste les utilisateurs Web sous forme de tableau. -Manage - Confirm Overwrite || Les donnรฉes ${0} seront รฉcrasรฉes ! -Manage - Confirm Removal || Les donnรฉes ${0} seront supprimรฉes ! -Manage - Fail || > ยงcUne erreur est survenue : ${0}. -Manage - Fail File not found || > ยงcAucun fichier trouvรฉ depuis ${0}. -Manage - Fail Incorrect Database || > ยงc'${0}' n'est pas une base de donnรฉes prise en charge. -Manage - Fail No Exporter || ยงeL'exportateur '${0}' n'existe pas. -Manage - Fail No Importer || ยงeL'importateur '${0}' n'existe pas. -Manage - Fail No Server || Aucun serveur trouvรฉ avec les paramรจtres suivants. -Manage - Fail Same Database || > ยงcNe peut pas opรฉrer sur et depuis la mรชme base de donnรฉes ! -Manage - Fail Same server || Impossible de marquer ce serveur comme dรฉsinstallรฉ (vous y รชtes). -Manage - Fail, Confirmation || > ยงcAjoutez l'argument '-a' afin de confirmer l'exรฉcution : ${0} -Manage - List Importers || Importateurs : -Manage - Progress || ${0} / ${1} traitรฉ(s).. -Manage - Remind HotSwap || ยงeN'oubliez pas de passer ร  la nouvelle base de donnรฉes (/plan db hotswap ${0}) & de recharger Plan. -Manage - Start || > ยง2Traitement des donnรฉes... -Manage - Success || > ยงaSuccรจs ! -Negative || Non -Positive || Oui -Today || 'Aujourd''hui' -Unavailable || Indisponible -Unknown || Inconnu -Version - DEV || Ceci est une version de dรฉveloppement. -Version - Latest || Vous utilisez la derniรจre version. -Version - New || Une nouvelle version (${0}) est disponible ${1} -Version - New (old) || Une nouvelle version est disponible depuis ${0} -Version FAIL - Read info (old) || Impossible de vรฉrifier le dernier numรฉro de la version -Version FAIL - Read versions.txt || Les informations de la version n'ont pas pu รชtre chargรฉes depuis Github/versions.txt -Web User Listing || ยง2${0} ยง7 : ยงf${1} -WebServer - Notify HTTP || Serveur Web : Aucun certificat -> Utilisation du serveur HTTP pour la visualisation. -WebServer - Notify HTTP User Auth || Serveur Web : Authentification utilisateur dรฉsactivรฉe ! (Non sรฉcurisรฉe avec HTTP) -WebServer - Notify HTTPS User Auth || Serveur Web : Authentification d'utilisateur dรฉsactivรฉe ! (dans la configuration) -Webserver - Notify IP Whitelist || Serveur Web : La liste blanche d'adresses IP n'est pas activรฉe. -Webserver - Notify IP Whitelist Block || Serveur Web : ${0} n'a pas pu accรฉder ร  '${1}'. (pas sur la liste blanche) -WebServer - Notify no Cert file || Serveur Web : Fichier KeyStore du certificat introuvable : ${0} -WebServer - Notify Using Proxy || Serveur Web : le Proxy-mode HTTPS est activรฉ, assurez-vous que votre proxy inversรฉ est routรฉ en utilisant HTTPS et Plan Alternative_IP.Address. -WebServer FAIL - EOF || Serveur Web : EOF lors de la lecture du Certificat. (Assurez-vous que le fuchier n'est pas vide) -WebServer FAIL - Port Bind || Le Serveur Web n'a pas รฉtรฉ initialisรฉ avec succรจs. Le port (${0}) est-il actuellement utilisรฉ ? -WebServer FAIL - SSL Context || Serveur Web : ร‰chec d'initialisation du contexte SSL. -WebServer FAIL - Store Load || Serveur Web : ร‰chec du chargement du certificat SSL. -Yesterday || 'Hier' diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_FR.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_FR.yml new file mode 100644 index 000000000..aa643263d --- /dev/null +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_FR.yml @@ -0,0 +1,669 @@ +403AccessDenied: "Accรจs refusรฉ." +command: + argument: + backupFile: + description: "Nom du fichier de sauvegarde (sensibles aux minuscules/majuscules)" + name: "fichier-de-sauvegarde" + code: + description: "Code utilisรฉ pour finaliser l'enregistrement." + name: "${code}" + dbBackup: + description: "Type de la base de donnรฉes pour sauvegarder. Si non spรฉcifiรฉ, la base de donnรฉes actuelle est utilisรฉe." + dbRestore: + description: "Type de la base de donnรฉes pour restaurer. Si non spรฉcifiรฉ, la base de donnรฉes actuelle est utilisรฉe." + dbTypeHotswap: + description: "Type de la base de donnรฉes pour commencer l'utilisation." + dbTypeMoveFrom: + description: "Type de la base de donnรฉes pour fournir les donnรฉes." + dbTypeMoveTo: + description: "Type de la base de donnรฉes pour recevoir les donnรฉes. Doit รชtre diffรฉrente de la prรฉcรฉdente." + dbTypeRemove: + description: "Type de la base de donnรฉes pour supprimer toutes les donnรฉes." + exportKind: "type d'exportation" + feature: + description: "Nom de la fonctionnalitรฉ ร  dรฉsactiver : ${0}" + name: "fonctionnalitรฉ" + importKind: "type d'importation" + nameOrUUID: + description: "Nom ou UUID d'un joueur" + name: "nom/uuid" + removeDescription: "Identifiant d'un joueur qui sera supprimรฉ de la base de donnรฉes actuelle." + server: + description: "Nom, ID ou UUID d'un serveur" + name: "serveur" + subcommand: + description: "Utilisez cette commande sans sous-commande pour afficher l'aide." + name: "sous-commande" + username: + description: "Nom d'utilisateur d'un autre utilisateur. Si non spรฉcifiรฉ, l'utilisateur liรฉ au joueur est utilisรฉ." + name: "nom d'utilisateur" + confirmation: + accept: "Accepter" + cancelNoChanges: "Annulรฉ. Aucune donnรฉe n'a รฉtรฉ modifiรฉe." + cancelNoUnregister: "Annulรฉ. '${0}' n'รฉtait pas enregistrรฉ" + confirm: "Confirmer : " + dbClear: "Vous รชtes sur le point de supprimer toutes les donnรฉes de Plan de ${0}" + dbOverwrite: "Vous รชtes sur le point d'รฉcraser des donnรฉes dans de Plan ${0} avec les donnรฉes de ${1}" + dbRemovePlayer: "Vous รชtes sur le point de supprimer les donnรฉes de ${0} depuis ${1}" + deny: "Annuler" + expired: "La confirmation a expirรฉ, utilisez ร  nouveau la commande" + unregister: "Vous รชtes sur le point de dรฉsenregistrer '${0}' liรฉ ร  ${1}" + database: + creatingBackup: "Crรฉation d'un fichier de sauvegarde '${0}.db' avec les contenus de ${1}" + failDbNotOpen: "ยงcLa base de donnรฉes est : ${0} - Merci de rรฉessayer plus tard." + manage: + confirm: "> ยงcAjoutez l'argument '-a' afin de confirmer l'exรฉcution : ${0}" + confirmOverwrite: "Les donnรฉes ${0} seront รฉcrasรฉes !" + confirmPartialRemoval: "Join Address Data for Server ${0} in ${1} will be removed!" + confirmRemoval: "Les donnรฉes ${0} seront supprimรฉes !" + fail: "> ยงcUne erreur est survenue : ${0}." + failFileNotFound: "> ยงcAucun fichier trouvรฉ depuis ${0}." + failIncorrectDB: "> ยงc'${0}' n'est pas une base de donnรฉes prise en charge." + failNoServer: "Aucun serveur trouvรฉ avec les paramรจtres suivants." + failSameDB: "> ยงcNe peut pas opรฉrer sur et depuis la mรชme base de donnรฉes !" + failSameServer: "Impossible de marquer ce serveur comme dรฉsinstallรฉ (vous y รชtes)." + hotswap: "ยงeN'oubliez pas de passer ร  la nouvelle base de donnรฉes (/plan db hotswap ${0}) & de recharger Plan." + importers: "Importateurs :" + preparing: "Preparing.." + progress: "${0} / ${1} traitรฉ(s).." + start: "> ยง2Traitement des donnรฉes..." + success: "> ยงaSuccรจs !" + playerRemoval: "Suppression des donnรฉes de ${0} depuis ${1}.." + removal: "Suppression des donnรฉes de Plan depuis ${0}.." + serverUninstalled: "ยงaSi le serveur est toujours installรฉ, il se dรฉfinira automatiquement comme dans la base de donnรฉes." + unregister: "Dรฉsenregistrement de '${0}'.." + warnDbNotOpen: "ยงeLa base de donnรฉes est : ${0} - Cela pourrait prendre plus de temps que prรฉvu..." + write: "ร‰criture ร  ${0}.." + fail: + emptyString: "La chaรฎne de recherche ne peut pas รชtre vide" + invalidArguments: "Accepte les รฉlรฉments suivants comme ${0} : ${1}" + invalidUsername: "ยงcCet utilisateur ne possรจde pas d'UUID." + missingArguments: "ยงcDes arguments sont requis (${0}) ${1}" + missingFeature: "ยงeDรฉfinir une fonctionnalitรฉ ร  dรฉsactiver ! (supporte actuellement ${0})" + missingLink: "Aucun utilisateur n'est pas liรฉ ร  votre compte et vous n'avez pas la permission de supprimer les comptes d'autres utilisateurs." + noPermission: "ยงcVous ne possรฉdez pas la permission requise." + onAccept: "L'action acceptรฉe a รฉchouรฉ lors de l'exรฉcution : ${0}" + onDeny: "L'action refusรฉe a รฉchouรฉ lors de l'exรฉcution : ${0}" + playerNotFound: "Joueur '${0}' non trouvรฉ, il n'a pas d'UUID." + playerNotInDatabase: "Joueur '${0}' non trouvรฉ dans la base de donnรฉes." + seeConfig: "voir '${0}' dans config.yml" + serverNotFound: "Serveur '${0}' non trouvรฉ dans la base de donnรฉes." + tooManyArguments: "ยงcUn argument est requis ${1}" + unknownUsername: "ยงcCet utilisateur ne s'est jamais connectรฉ sur ce serveur." + webUserExists: "ยงcCet utilisateur existe dรฉjร  !" + webUserNotFound: "ยงcCet utilisateur n'existe pas !" + footer: + help: "ยง7Survolez la commande / les arguments ou utilisez '/${0} ?' pour en savoir plus sur eux." + general: + failNoExporter: "ยงeL'exportateur '${0}' n'existe pas." + failNoImporter: "ยงeL'importateur '${0}' n'existe pas." + featureDisabled: "ยงaFonctionnalitรฉ '${0}' temporairement dรฉsactivรฉe jusqu'au prochain rechargement du plugin." + noAddress: "ยงeAucune adresse รฉtant disponible - utilisation localhost comme solution de repli. Veuillez configurer le paramรจtre 'Alternative_IP's." + noWebuser: "Vous n'avez peut-รชtre pas d'utilisateur Web, essayez d'exรฉcuter '/plan register ' afin d'y remรฉdier." + notifyWebUserRegister: "Nouvel utilisateur enregistrรฉ : '${0}' Niveau de permission : ${1}." + pluginDisabled: "ยงaLes systรจmes de Plan sont maintenant dรฉsactivรฉs. Vous pouvez toujours exรฉcuter la commande 'reload' pour les redรฉmarrer." + reloadComplete: "ยงaRechargement terminรฉ." + reloadFailed: "ยงcUne erreur s'est produite lors du rechargement du plugin, un redรฉmarrage total est recommandรฉ." + successWebUserRegister: "ยงaAjout d'un nouvel utilisateur (${0}) avec succรจs !" + webPermissionLevels: ">\ยง70 : Accรฉder ร  toutes les pages.\ยง71 : Accรฉder au '/players' et ร  toutes les pages des joueurs.\ยง72 : Accรฉder ร  la page du joueur avec le mรชme nom d'utilisateur que l'utilisateur Web.\ยง73+ : Pas de permission." + webUserList: " ยง2${0} ยง7 : ยงf${1}" + header: + analysis: "> ยง2Rรฉsultats de l'analyse" + help: "> ยง2/${0} Help" + info: "> ยง2Analyse du joueur" + inspect: "> ยง2Joueur : ยงf${0}" + network: "> ยง2Page du rรฉseau" + players: "> ยง2Joueurs" + search: "> ยง2${0} Rรฉsultats pour ยงf${1}ยง2 :" + serverList: "id::nom d'utilisateur::uuid::version" + servers: "> ยง2Serveurs" + webUserList: "nom d'utilisateur::liรฉ ร ::permission niveau" + webUsers: "> ยง2${0} Utilisateurs Web" + help: + database: + description: "Gรฉrer la base de donnรฉes de Plan" + inDepth: "Utilise diffรฉrentes sous-commandes de base de donnรฉes pour modifier les donnรฉes d'une maniรจre ou d'une autre." + dbBackup: + description: "Sauvegarder la base donnรฉes vers un fichier" + inDepth: "Utilise SQLite pour sauvegarder la base de donnรฉes cible dans un fichier." + dbClear: + description: "Supprimer TOUTES les donnรฉes de Plan dans une base de donnรฉes" + inDepth: "Efface toutes les tables de Plan, supprimant par la mรชme occasion toutes les donnรฉes en traitement de Plan." + dbHotswap: + description: "Changer rapidement de base de donnรฉes" + inDepth: "Recharge le plugin avec l'autre base de donnรฉes et modifie la configuration en consรฉquence." + dbMove: + description: "Dรฉplacer des donnรฉes entre bases de donnรฉes" + inDepth: "ร‰crase le contenu de l'autre base de donnรฉes avec le contenu d'une autre." + dbRemove: + description: "Supprimer les donnรฉes d'un joueur dans la base de donnรฉes" + inDepth: "Supprime toutes les donnรฉes liรฉes ร  un joueur de la base de donnรฉes actuelle." + dbRestore: + description: "Restaurer les donnรฉes depuis un fichier ou une base de donnรฉes" + inDepth: "Utilise le fichier de sauvegarde SQLite et รฉcrase le contenu de la base de donnรฉes cible." + dbUninstalled: + description: "Dรฉfinir un serveur comme dรฉsinstallรฉ au sein de la base de donnรฉes" + inDepth: "Marque un serveur dans la base de donnรฉes Plan comme รฉtant dรฉsinstallรฉ afin qu'il n'apparaisse pas dans les requรชtes de serveur." + disable: + description: "Dรฉsactiver le plugin ou une partie de lui" + inDepth: "Dรฉsactive le plugin ou une partie de celui-ci jusqu'au prochain rechargement/redรฉmarrage." + export: + description: "Exporter les fichiers HTML ou JSON manuellement" + inDepth: "Effectue une exportation vers un emplacement dรฉfini dans la configuration." + import: + description: "Importer des donnรฉes" + inDepth: "Effectue une importation pour charger des donnรฉes dans la base de donnรฉes." + info: + description: "Informations concernant le plugin" + inDepth: "Affiche l'รฉtat actuel du plugin." + ingame: + description: "Visualiser les informations d'un Joueur (en jeu)" + inDepth: "Affiche des informations sur un joueur en jeu." + json: + description: "Visualiser le JSON des donnรฉes brutes d'un joueur" + inDepth: "Permet de tรฉlรฉcharger les donnรฉes d'un joueur au format JSON. Toutes les donnรฉes." + logout: + description: "Dรฉconnecter les autres utilisateurs du panel" + inDepth: "Donnez un nom d'utilisateur ร  dรฉconnecter du panel, donnez * comme argument pour dรฉconnecter tout le monde." + migrateToOnlineUuids: + description: "Migrate offline uuid data to online uuids" + network: + description: "Visualiser la page du rรฉseau" + inDepth: "Obtient un lien vers la page /network, uniquement sur les rรฉseaux." + player: + description: "Visualiser la page d'un joueur" + inDepth: "Obtient un lien vers la page /player d'un joueur spรฉcifique, ou du joueur actuel." + players: + description: "Visualiser la page des joueurs" + inDepth: "Permet d'obtenir un lien vers la page /players pour voir la liste des joueurs." + register: + description: "Enregistrer un utilisateur Web" + inDepth: "Utiliser sans arguments pour obtenir un lien vers la page d'enregistrement. Utilisez --code [code] aprรจs l'enregistrement pour obtenir un utilisateur." + reload: + description: "Recharger Plan" + inDepth: "Dรฉsactive et active le plugin pour recharger tout changement dans la configuration." + removejoinaddresses: + description: "Remove join addresses of a specified server" + search: + description: "Rechercher un joueur" + inDepth: "Liste tous les noms de joueurs correspondant ร  une partie donnรฉe d'un nom." + server: + description: "Visualiser la page du serveur" + inDepth: "Obtient un lien vers la page /server d'un serveur spรฉcifique, ou du serveur courant si aucun argument n'est donnรฉ." + servers: + description: "Obtenir la liste des serveurs dans la base de donnรฉes" + inDepth: "Liste les ids, noms et uuids des serveurs de la base de donnรฉes." + unregister: + description: "Dรฉsenregistrer un utilisateur du site de Plan" + inDepth: "Utilisez sans argument pour dรฉsenregistrer un utilisateur liรฉ ร  un joueur, ou avec un nom d'utilisateur pour dรฉsenregistrer un autre utilisateur." + users: + description: "Lister les utilisateurs du site Web" + inDepth: "Liste les utilisateurs Web sous forme de tableau." + ingame: + activePlaytime: " ยง2Temps Actif : ยงf${0}" + activityIndex: " ยง2Indice d'Activitรฉ : ยงf${0} | ${1}" + afkPlaytime: " ยง2Temps AFK : ยงf${0}" + deaths: " ยง2Morts : ยงf${0}" + geolocation: " ยง2Gรฉolocalisation : ยงf${0}" + lastSeen: " ยง2Derniรจre Connexion : ยงf${0}" + longestSession: " ยง2Session la plus longue : ยงf${0}" + mobKills: " ยง2Kills de Mobs : ยงf${0}" + playerKills: " ยง2Kills de Joueurs : ยงf${0}" + playtime: " ยง2Temps de Jeu : ยงf${0}" + registered: " ยง2Incription : ยงf${0}" + timesKicked: " ยง2Nombre d'ร‰jections : ยงf${0}" + link: + clickMe: "Cliquez ici" + link: "Lien" + network: "Page du Rรฉseau : " + noNetwork: "Le serveur n'est pas connectรฉ ร  un rรฉseau. Le lien redirige vers la page du serveur." + player: "Page du Joueur : " + playerJson: "JSON du Joueur : " + players: "Page des Joueurs : " + register: "Page d'enregistrement : " + server: "Page du Serveur : " + subcommand: + info: + database: " ยง2Base de donnรฉes actuelle : ยงf${0}" + proxy: " ยง2Connectรฉ : ยงf${0}" + update: " ยง2Mise ร  jour disponible : ยงf${0}" + version: " ยง2Version : ยงf${0}" +generic: + noData: "Aucune donnรฉe" +html: + button: + nightMode: "Mode Nuit" + calendar: + new: "Nouveau :" + unique: "Unique :" + description: + newPlayerRetention: "Cette valeur est une prรฉdiction basรฉe sur les joueurs prรฉcรฉdents." + noGameServers: "Certaines donnรฉes nรฉcessitent l'installation de Plan sur les serveurs de jeu." + noGeolocations: "La collecte de la gรฉolocalisation doit รชtre activรฉe dans la configuration (Accepter l'EULA de GeoLite2)." + noServerOnlinActivity: "Aucun serveur pour afficher l'activitรฉ en ligne." + noServers: "Il n'y a pas de serveur dans la base de donnรฉes." + noServersLong: 'Il semblerait que Plan ne soit installรฉ sur aucun des serveurs de jeu ou qu'il ne soit pas connectรฉ ร  la mรชme base de donnรฉes. Voir wiki pour un tutoriel sur la mise en place d'un Rรฉseau.' + noSpongeChunks: "Chunks indisponibles sur Sponge" + predictedNewPlayerRetention: "Cette valeur est une prรฉdiction basรฉe sur les anciennes donnรฉes du joueur." + error: + 401Unauthorized: "Non autorisรฉ." + 403Forbidden: "Accรจs interdit." + 404NotFound: "Non trouvรฉ." + 404PageNotFound: "Cette page n'existe pas." + 404UnknownPage: "Assurez-vous que vous accรฉdez ร  un lien donnรฉ par une commande. Exemples :

/player/{uuid/nom}
/server/{uuid/nom/id}

" + UUIDNotFound: "L'UUID de cet utilisateur n'a pas รฉtรฉ trouvรฉ dans la base de donnรฉes." + auth: + dbClosed: "La base de donnรฉes n'est pas ouverte, vรฉrifiez son รฉtat avec la commande /plan info" + emptyForm: "Utilisateur et mot de passe non spรฉcifiรฉs" + expiredCookie: "Le cookie de l'utilisateur a expirรฉ" + generic: "Authentification รฉchouรฉe en raison d'une erreur" + loginFailed: "L'utilisateur et le mot de passe ne correspondent pas" + noCookie: "Cookie de l'utilisateur non prรฉsent" + registrationFailed: "Enregistrement รฉchouรฉ, veuillez rรฉessayer (Le code expire au bout de 15 minutes)" + userNotFound: "Cet utilisateur n'existe pas" + authFailed: "Authentification รฉchouรฉe." + authFailedTips: "- Assurez-vous d'avoir enregistrรฉ un utilisateur avec :'/plan register'.
- Vรฉrifiez que le nom d'utilisateur et le mot de passe soient corrects.
- Le nom d'utilisateur et le mot de passe sont sensibles au format majuscule/minuscule.

Si vous avez oubliรฉ votre mot de passe, demandez ร  un membre du staff de supprimer votre ancien utilisateur puis de vous rรฉinscrire." + noServersOnline: "Aucun serveur en ligne pour exรฉcuter la demande." + playerNotSeen: "Cet utilisateur ne s'est jamais connectรฉ sur ce serveur." + serverNotSeen: "Server doesn't exist" + generic: + none: "Vide" + label: + active: "Actif" + activePlaytime: "Temps Actif" + activityIndex: "Indice d'Activitรฉ" + afk: "AFK" + afkTime: "Temps AFK" + all: "Tout" + allTime: "Tout le Temps" + alphabetical: "Alphabetical" + apply: "Apply" + asNumbers: "en Chiffres" + average: "Moyen(ne)" + averageActivePlaytime: "Temps Actif moyen" + averageAfkTime: "Temps AFK moyen" + averageChunks: "Quantitรฉ moyenne de Chunks" + averageCpuUsage: "Average CPU Usage" + averageEntities: "Quantitรฉ moyenne d'Entitรฉs" + averageKdr: "Ratio - Kills / Morts - moyen" + averageMobKdr: "Ratio - Kills / Morts - de Mobs moyen" + averagePing: "Latence moyenne" + averagePlayers: "Average Players" + averagePlaytime: "Temps de Jeu moyen" + averageRamUsage: "Average RAM Usage" + averageServerDowntime: "Average Downtime / Server" + averageSessionLength: "Durรฉe moyenne d'une Session" + averageSessions: "Quantitรฉ moyenne de Sessions" + averageTps: "TPS moyen" + averageTps7days: "Average TPS (7 days)" + banned: "Banni(e)" + bestPeak: "Pic maximal de Joueurs en Ligne" + bestPing: "Meilleure Latence" + calendar: " Calendrier" + comparing7days: "Comparaison des 7 derniers Jours" + connectionInfo: "Renseignements sur la Connexion" + country: "Pays" + cpu: "CPU" + cpuRam: "CPU & RAM" + cpuUsage: "CPU Usage" + currentPlayerbase: "Base de Joueurs actuelle" + currentUptime: "Current Uptime" + dayByDay: "Jour par Jour" + dayOfweek: "Jour de la Semaine" + deadliestWeapon: "1รจre Arme de Combat (la plus mortelle)" + deaths: "Morts" + disk: "Espace Disque" + diskSpace: "Espace Disque disponible" + downtime: "Temps Hors-Ligne" + duringLowTps: "Pendant les pics de TPS bas :" + entities: "Entitรฉs" + favoriteServer: "Serveur Favori" + firstSession: "Premiรจre session" + firstSessionLength: + average: "Average first session length" + median: "Median first session length" + geoProjection: + dropdown: "Select projection" + equalEarth: "Equal Earth" + mercator: "Mercator" + miller: "Miller" + ortographic: "Ortographic" + geolocations: "Gรฉolocalisation" + hourByHour: "Heure par Heure" + inactive: "Inactif(ve)" + indexInactive: "Inactif" + indexRegular: "Rรฉgulier" + information: "INFORMATIONS" + insights: "Insights" + insights30days: "Perspectives sur 30 jours" + irregular: "Irrรฉgulier" + joinAddress: "Join Address" + joinAddresses: "Adresses de Connexion" + kdr: "KDR" + killed: "Tuรฉ(e)" + last24hours: "24 Derniรจres heures" + last30days: "30 Derniers jours" + last7days: "7 Derniers jours" + lastConnected: "Dernier Connectรฉ" + lastPeak: "Dernier pic de Joueurs en Ligne" + lastSeen: "Derniรจre Connexion" + latestJoinAddresses: "Latest Join Addresses" + length: " Longueur" + links: "LIENS" + loadedChunks: "Chunks Chargรฉs" + loadedEntities: "Entitรฉs Chargรฉes" + loneJoins: "Connexions de Joueurs Seuls" + loneNewbieJoins: "Connexions de Dรฉbutants Seuls" + longestSession: "Session la plus Longue" + lowTpsSpikes: "Pics de TPS bas" + lowTpsSpikes7days: "Low TPS Spikes (7 days)" + maxFreeDisk: "Espace Disque MAX disponible" + medianSessionLength: "Median Session Length" + minFreeDisk: "Espace Disque MIN disponible" + mobDeaths: "Morts causรฉes par un Mob" + mobKdr: "Ratio - Kills / Morts de Mobs -" + mobKills: "Kills de Mobs" + mostActiveGamemode: "Mode de Jeu le plus utilisรฉ" + mostPlayedWorld: "Monde le plus Frรฉquentรฉ" + name: "Nom" + network: "Rรฉseau" + networkAsNumbers: "Rรฉseau en Chiffres" + networkOnlineActivity: "Activitรฉ en Ligne du Rรฉseau" + networkOverview: "Aperรงu du Rรฉseau" + networkPage: "Page du Rรฉseau" + new: "Nouveau(elle)" + newPlayerRetention: "Rรฉtention des nouveaux Joueurs" + newPlayers: "Nouveaux Joueurs" + newPlayers7days: "New Players (7 days)" + nickname: "Surnom" + noDataToDisplay: "No Data to Display" + now: "Maintenant" + onlineActivity: "Activitรฉ en ligne" + onlineActivityAsNumbers: "Activitรฉ en ligne en Chiffres" + onlineOnFirstJoin: "Joueurs en Ligne lors de la premiรจre Connexion" + operator: "Opรฉrateur" + overview: "Vue d'Ensemble" + perDay: "/ Jour" + perPlayer: "/ Joueur" + perRegularPlayer: "/ Joueur Rรฉgulier" + performance: "Performances" + performanceAsNumbers: "Performances en Chiffres" + ping: "Latence" + player: "Joueur" + playerDeaths: "Dรฉcรจs causรฉs par le Joueur" + playerKills: "Kills de Joueurs" + playerList: "Liste des Joueurs" + playerOverview: "Aperรงu des Joueurs" + playerPage: "Page du Joueur" + playerRetention: "Player Retention" + playerbase: "Base de Joueurs" + playerbaseDevelopment: "ร‰volution de la base de Joueurs" + playerbaseOverview: "Playerbase Overview" + players: "Joueurs" + playersOnline: "Joueurs en Ligne" + playersOnlineNow: "Players Online (Now)" + playersOnlineOverview: "Aperรงu de l'Activitรฉ en Ligne" + playtime: "Temps de Jeu" + plugins: "Plugins" + pluginsOverview: "Plugins Overview" + punchcard: "Carte Perforรฉe" + punchcard30days: "Carte perforรฉe sur 30 jours" + pvpPve: "PvP & PvE" + pvpPveAsNumbers: "PvP & PvE en Chiffres" + query: "Faire une Requรชte" + quickView: "Aperรงu Rapide" + ram: "RAM" + ramUsage: "RAM Usage" + recentKills: "Kills rรฉcents" + recentPvpDeaths: "Dรฉcรจs rรฉcents en PvP" + recentPvpKills: "Kills rรฉcents en PvP" + recentSessions: "Sessions rรฉcentes" + registered: "Inscription" + registeredPlayers: "Joueurs Enregistrรฉs" + regular: "Rรฉgulier(รจre)" + regularPlayers: "Joueurs Rรฉguliers" + relativeJoinActivity: "Activitรฉ de Connexion relative" + secondDeadliestWeapon: "2แต‰ Arme de Combat" + seenNicknames: "Surnoms vus" + server: "Serveur" + serverAnalysis: "Analyse du Serveur" + serverAsNumberse: "Serveur en Chiffres" + serverCalendar: "Server Calendar" + serverDowntime: "Temps Hors-Ligne du Serveur" + serverOccupied: "Serveur Inoccupรฉ" + serverOverview: "Aperรงu du Serveur" + serverPage: "Page du Serveur" + serverPlaytime: "Temps de Jeu du Serveur" + serverPlaytime30days: "Temps de Jeu du Serveur sur 30 Jours" + serverSelector: "Server selector" + servers: "Serveurs" + serversTitle: "SERVEURS" + session: "Session" + sessionCalendar: "Session Calendar" + sessionEnded: " Session Terminรฉe" + sessionMedian: "Session Mรฉdiane" + sessionStart: "Dรฉbut de la Session" + sessions: "Sessions" + sortBy: "Sort By" + stacked: "Stacked" + themeSelect: "Sรฉlection du Thรจme" + thirdDeadliestWeapon: "3แต‰ Arme de Combat" + thirtyDays: "30 jours" + thirtyDaysAgo: "Il y a 30 jours" + timesKicked: "Nombre d'ร‰jections" + toMainPage: "Retour ร  la page principale" + total: "Total" + totalActive: "Temps Actif Total" + totalAfk: "Temps Inactif Total" + totalPlayers: "Joueurs Totaux" + totalPlayersOld: "Joueurs Totaux" + totalPlaytime: "Temps de Jeu Total" + totalServerDowntime: "Total Server Downtime" + tps: "TPS" + trend: "Tendances" + trends30days: "Tendances sur 30 Jours" + uniquePlayers: "Joueurs Uniques" + uniquePlayers7days: "Unique Players (7 days)" + veryActive: "Trรจs Actif" + weekComparison: "Comparaison Hebdomadaire" + weekdays: "'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche'" + world: "Charge du Monde" + worldPlaytime: "Temps de Jeu par Monde" + worstPing: "Pire Latence" + login: + failed: "Connexion รฉchouรฉe : " + forgotPassword: "Mot de Passe oubliรฉ ?" + forgotPassword1: "Mot de Passe oubliรฉ ? Dรฉsenregistrez puis rรฉenregistrez-vous." + forgotPassword2: "Utilisez la commande suivante en jeu pour supprimer votre utilisateur actuel :" + forgotPassword3: "Ou en utilisant la console :" + forgotPassword4: "Aprรจs avoir utilisรฉ la commande, " + login: "Connexion" + logout: "Dรฉconnexion" + password: "Mot de Passe" + register: "Crรฉer un compte !" + username: "Nom d'Utilisateur" + modal: + info: + bugs: "Rapport de bugs" + contributors: + bugreporters: "& Rapporteurs de Bugsโ€ฏ!" + code: "Contributeurs" + donate: "Un merci spรฉcial ร  ceux qui ont financiรจrement soutenu le dรฉveloppement." + text: 'En outre, ces gens formidables ont contribuรฉ :' + translator: "Traducteurs" + developer: "est dรฉveloppรฉ par" + discord: "Support gรฉnรฉral sur Discord" + license: "Player Analytics est dรฉveloppรฉ et fait l'objet d'une licence en vertu de" + metrics: "Mรฉtriques bStats" + text: "Informations concernant le plugin" + wiki: "Wiki, Documentation & Tutoriaux de Plan" + version: + available: "est Disponible" + changelog: "Visualiser les changements" + dev: "Cette version est rรฉservรฉe au dรฉveloppement." + download: "Tรฉlรฉchargement" + text: "Une nouvelle version a รฉtรฉ publiรฉe et est maintenant disponible en tรฉlรฉchargement." + title: "Version" + query: + filter: + activity: + text: "sont dans des Groupes d'Activitรฉ" + banStatus: + name: "Statut de Bannissement" + banned: "Banni(e)" + country: + text: "have joined from country" + generic: + allPlayers: "Tous les Joueurs" + and: "et" + start: "des Joueurs qui" + joinAddress: + text: "ont rejoint avec l'adresse" + nonOperators: "Non Opรฉrateur(trice)" + notBanned: "Non Banni(e)" + operatorStatus: + name: "Statut d'Opรฉrateur" + operators: "Opรฉrateurs" + playedBetween: + text: "Jouรฉs entre" + pluginGroup: + name: "Groupe : " + text: "sont dans le groupe {group} de ${plugin}" + registeredBetween: + text: "Enregistrรฉs entre" + title: + activityGroup: "Groupe d'Activitรฉ actuel" + view: " Vue :" + filters: + add: "Ajouter un filtre.." + loading: "Chargement des Filtres.." + generic: + are: "`sont`" + label: + from: ">de" + makeAnother: "Faire une autre Requรชte" + to: ">ร " + view: "Visualiser une vue" + performQuery: "Exรฉcuter la Requรชte !" + results: + match: "${resultCount} Joueurs appariรฉs" + none: "La Requรชte n'a produit aucun rรฉsultat" + title: "Rรฉsultats de la Requรชte" + title: + activity: "Activitรฉ des joueurs appariรฉs" + activityOnDate: 'Activitรฉ sur ' + sessionsWithinView: "Sessions ร  portรฉe de vue" + text: "Requรชte<" + register: + completion: "Enregistrement complet" + completion1: "Vous pouvez maintenant terminer l'enregistrement de l'utilisateur." + completion2: "Le Code expire dans 15 minutes" + completion3: "Utilisez la commande suivante en jeu pour terminer l'enregistrement :" + completion4: "Ou en utilisant la console :" + createNewUser: "Crรฉer un nouvel utilisateur" + error: + checkFailed: "La vรฉrification de l'รฉtat de l'enregistrement a รฉchouรฉ : " + failed: "Enregistrement รฉchouรฉ : " + noPassword: "Veuillez spรฉcifier un Mot de Passe" + noUsername: "Veuillez spรฉcifier un Nom d'Utilisateur" + usernameLength: "Le Nom d'Utilisateur peut comporter jusqu'ร  50 caractรจres, le vรดtre est" + login: "Vous avez dรฉjร  un compte ? Connectez-vous !" + passwordTip: "Le Mot de Passe devrait comporter plus de 8 caractรจres, mais il n'y a aucune limite." + register: "Enregistrer" + usernameTip: "Le Nom d'Utilisateur peut comporter jusqu'ร  50 caractรจres." + text: + clickToExpand: "Cliquez pour agrandir" + comparing15days: "Comparaison des 15 derniers Jours" + comparing30daysAgo: "Comparaison des 60 derniers Jours" + noExtensionData: "Pas de donnรฉes d'extension" + noLowTps: "Pas de pics de TPS bas" + unit: + chunks: "Chunks" + players: "Joueurs" + value: + localMachine: "Machine Locale" + noKills: "Pas de Kills" + offline: " Hors-Ligne" + online: " En Ligne" + with: "Avec" + version: + changelog: "Visualiser le Changelog" + current: "Vous avez la Version ${0}" + download: "Tรฉlรฉcharger Plan-${0}.jar" + isDev: "Cette version est rรฉservรฉe au Dรฉveloppement." + updateButton: "Mise ร  Jour" + updateModal: + text: "Une nouvelle version a รฉtรฉ publiรฉe et peut รชtre tรฉlรฉchargรฉe." + title: "La Version ${0} est Disponible !" +plugin: + apiCSSAdded: "Extension de Page : ${0} a ajoutรฉ une ou plusieurs feuilles de style ร  ${1}, ${2}" + apiJSAdded: "Extension de Page : ${0} a ajoutรฉ un ou plusieurs JavaScript ร  ${1}, ${2}" + disable: + database: "Traitement des tรขches critiques inachevรฉes... (${0})" + disabled: "Plan a รฉtรฉ dรฉsactivรฉ." + processingComplete: "Traitement complรฉtรฉ." + savingSessions: "Sauvegarde des sessions inachevรฉes..." + savingSessionsTimeout: "Timeout atteint - stockage des sessions non terminรฉes lors du prochain dรฉmarrage." + waitingDb: "En attente de la finalisation des requรชtes pour รฉviter que SQLite ne plante la JVM.." + waitingDbComplete: "Connexion SQLite fermรฉe." + waitingTransactions: "En attente des transactions non terminรฉes pour รฉviter la perte de donnรฉes.." + waitingTransactionsComplete: "File d'attente des transactions fermรฉe." + webserver: "Le serveur Web a รฉtรฉ dรฉsactivรฉ." + enable: + database: "Connexion ร  la base de donnรฉes รฉtablie. (${0})" + enabled: "Plan a รฉtรฉ activรฉ." + fail: + database: "La connexion ร  la base de donnรฉes a รฉchouรฉ. (${0}) : ${1}" + databasePatch: "L'application de correctifs pour la base de donnรฉes a รฉchouรฉ, le plugin doit รชtre dรฉsactivรฉ. Merci de signaler ce problรจme." + databaseType: "${0} n'est pas une base de donnรฉes prise en charge." + geoDBWrite: "Une erreur s'est produite lors de l'enregistrement de la base de donnรฉes 'GeoLite2 Geolocation' tรฉlรฉchargรฉe prรฉcรฉdemment." + webServer: "Le serveur Web n'a pas รฉtรฉ initialisรฉ !" + notify: + badIP: "L'adresse IP situรฉe dans le fichier 'server.properties' est รฉrronรฉe. Attention, des liens incorrects seront donnรฉs !" + emptyIP: "L'adresse IP situรฉe dans le fichier 'server.properties' est vide et l'option 'Alternative_IP' n'est pas utilisรฉe. Attention, des liens incorrects seront donnรฉs !" + geoDisabled: "La Gรฉolocalisation n'est pas active. (Data.Geolocations: false)" + geoInternetRequired: "Plan nรฉcessite un accรจs ร  Internet lors de sa premiรจre utilisation pour tรฉlรฉcharger la base de donnรฉes 'GeoLite2 Geolocation'." + storeSessions: "Stockage des sessions ayant รฉtรฉ prรฉservรฉes lors de l'arrรชt prรฉcรฉdent." + webserverDisabled: "Le serveur Web n'a pas รฉtรฉ initialisรฉ. (WebServer.DisableWebServer: true)" + webserver: "Le serveur Web communique ร  travers le port ${0} ( ${1} )." + generic: + dbApplyingPatch: "Application de correctifs : ${0}..." + dbFaultyLaunchOptions: "Configurations dรฉfectueuses, utilisation de celles par dรฉfaut (${0})" + dbNotifyClean: "Suppression des donnรฉes de ${0} joueurs." + dbNotifySQLiteWAL: "Le mode WAL de SQLite n'est pas pris en charge sur cette version du serveur, en utilisant le mode par dรฉfaut. Cela peut possiblement affecter les performances." + dbPatchesAlreadyApplied: "Tous les correctifs pour la base de donnรฉes ont dรฉjร  รฉtรฉ appliquรฉs." + dbPatchesApplied: "Tous les correctifs pour la base de donnรฉes ont รฉtรฉ appliquรฉs avec succรจs." + dbSchemaPatch: "Database: Making sure schema is up to date.." + loadedServerInfo: "Server identifier loaded: ${0}" + loadingServerInfo: "Loading server identifying information" + no: "Non" + today: "'Aujourd''hui'" + unavailable: "Indisponible" + unknown: "Inconnu" + yes: "Oui" + yesterday: "'Hier'" + version: + checkFail: "Impossible de vรฉrifier le dernier numรฉro de la version" + checkFailGithub: "Les informations de la version n'ont pas pu รชtre chargรฉes depuis Github/versions.txt" + isDev: " Ceci est une version de dรฉveloppement." + isLatest: "Vous utilisez la derniรจre version." + updateAvailable: "Une nouvelle version (${0}) est disponible ${1}" + updateAvailableSpigot: "Une nouvelle version est disponible depuis ${0}" + webserver: + fail: + SSLContext: "Serveur Web : ร‰chec d'initialisation du contexte SSL." + certFileEOF: "Serveur Web : EOF lors de la lecture du Certificat. (Assurez-vous que le fuchier n'est pas vide)" + certStoreLoad: "Serveur Web : ร‰chec du chargement du certificat SSL." + portInUse: "Le Serveur Web n'a pas รฉtรฉ initialisรฉ avec succรจs. Le port (${0}) est-il actuellement utilisรฉ ?" + notify: + authDisabledConfig: "Serveur Web : Authentification d'utilisateur dรฉsactivรฉe ! (dans la configuration)" + authDisabledNoHTTPS: "Serveur Web : Authentification utilisateur dรฉsactivรฉe ! (Non sรฉcurisรฉe avec HTTP)" + certificateExpiresOn: "Webserver: Loaded certificate is valid until ${0}." + certificateExpiresPassed: "Webserver: Certificate has expired, consider renewing the certificate." + certificateExpiresSoon: "Webserver: Certificate expires in ${0}, consider renewing the certificate." + certificateNoSuchAlias: "Webserver: Certificate with alias '${0}' was not found inside the keystore file '${1}'." + http: "Serveur Web : Aucun certificat -> Utilisation du serveur HTTP pour la visualisation." + ipWhitelist: "Serveur Web : La liste blanche d'adresses IP n'est pas activรฉe." + ipWhitelistBlock: "Serveur Web : ${0} n'a pas pu accรฉder ร  '${1}'. (pas sur la liste blanche)" + noCertFile: "Serveur Web : Fichier KeyStore du certificat introuvable : ${0}" + reverseProxy: "Serveur Web : le Proxy-mode HTTPS est activรฉ, assurez-vous que votre proxy inversรฉ est routรฉ en utilisant HTTPS et Plan Alternative_IP.Address." diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_IT.txt b/Plan/common/src/main/resources/assets/plan/locale/locale_IT.txt deleted file mode 100644 index 16e61ba3b..000000000 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_IT.txt +++ /dev/null @@ -1,517 +0,0 @@ -API - css+ || PageExtension: ${0} added stylesheet(s) to ${1}, ${2} -API - js+ || PageExtension: ${0} added javascript(s) to ${1}, ${2} -Cmd - Click Me || Cliccami -Cmd - Link || Link -Cmd - Link Network || Network page: -Cmd - Link Player || Player page: -Cmd - Link Player JSON || Player json: -Cmd - Link Players || Players page: -Cmd - Link Register || Register page: -Cmd - Link Server || Server page: -CMD Arg - backup-file || Name of the backup file (case sensitive) -CMD Arg - code || Code used to finalize registration. -CMD Arg - db type backup || Type of the database to backup. Current database is used if not specified. -CMD Arg - db type clear || Type of the database to remove all data from. -CMD Arg - db type hotswap || Type of the database to start using. -CMD Arg - db type move from || Type of the database to move data from. -CMD Arg - db type move to || Type of the database to move data to. Can not be same as previous. -CMD Arg - db type restore || Type of the database to restore to. Current database is used if not specified. -CMD Arg - feature || Name of the feature to disable: ${0} -CMD Arg - player identifier || Name or UUID of a player -CMD Arg - player identifier remove || Identifier for a player that will be removed from current database. -CMD Arg - server identifier || Name, ID or UUID of a server -CMD Arg - subcommand || Use the command without subcommand to see help. -CMD Arg - username || Username of another user. If not specified player linked user is used. -CMD Arg Name - backup-file || backup-file -CMD Arg Name - code || ${code} -CMD Arg Name - export kind || export kind -CMD Arg Name - feature || feature -CMD Arg Name - import kind || import kind -CMD Arg Name - name or uuid || name/uuid -CMD Arg Name - server || server -CMD Arg Name - subcommand || subcommand -CMD Arg Name - username || username -Cmd Confirm - accept || Accept -Cmd Confirm - cancelled, no data change || Cancelled. No data was changed. -Cmd Confirm - cancelled, unregister || Cancelled. '${0}' was not unregistered -Cmd Confirm - clearing db || You are about to remove all Plan-data in ${0} -Cmd Confirm - confirmation || Confirm: -Cmd Confirm - deny || Cancel -Cmd Confirm - Expired || Confirmation expired, use the command again -Cmd Confirm - Fail on accept || The accepted action errored upon execution: ${0} -Cmd Confirm - Fail on deny || The denied action errored upon execution: ${0} -Cmd Confirm - overwriting db || You are about to overwrite data in Plan ${0} with data in ${1} -Cmd Confirm - remove player db || You are about to remove data of ${0} from ${1} -Cmd Confirm - unregister || You are about to unregister '${0}' linked to ${1} -Cmd db - creating backup || Creating a backup file '${0}.db' with contents of ${1} -Cmd db - removal || Removing Plan-data from ${0}.. -Cmd db - removal player || Removing data of ${0} from ${1}.. -Cmd db - server uninstalled || ยงaIf the server is still installed, it will automatically set itself as installed in the database. -Cmd db - write || Writing to ${0}.. -Cmd Disable - Disabled || ยงaPlan รจ stato disabilitato. Puoi usare reload per riavviare il plugin. -Cmd FAIL - Accepts only these arguments || Accepts following as ${0}: ${1} -Cmd FAIL - Database not open || ยงcDatabase รจ ${0} - Riprova piรน tardi. -Cmd FAIL - Empty search string || The search string can not be empty -Cmd FAIL - Invalid Username || ยงcQuesto Utente non ha un UUID. -Cmd FAIL - No Feature || ยงeDefinisci una feature da disabilitate! (Feature Supportate ${0}) -Cmd FAIL - No Permission || ยงcNon hai nessun permesso. -Cmd FAIL - No player || Player '${0}' was not found, they have no UUID. -Cmd FAIL - No player register || Player '${0}' was not found in the database. -Cmd FAIL - No server || Server '${0}' was not found from the database. -Cmd FAIL - Require only one Argument || ยงcArgomento singolo richiesto ${1} -Cmd FAIL - Requires Arguments || ยงcArgomenti richiesti (${0}) ${1} -Cmd FAIL - see config || see '${0}' in config.yml -Cmd FAIL - Unknown Username || ยงcGiocatore mai visto su questo server -Cmd FAIL - Users not linked || User is not linked to your account and you don't have permission to remove other user's accounts. -Cmd FAIL - WebUser does not exists || ยงcGiocatore non esistente! -Cmd FAIL - WebUser exists || ยงcGiocatore giร  esistente! -Cmd Footer - Help || ยง7Hover over command or arguments or use '/${0} ?' to learn more about them. -Cmd Header - Analysis || > ยง2Risultati Analisi -Cmd Header - Help || > ยง2/${0} Help -Cmd Header - Info || > ยง2Analisi Giocatore -Cmd Header - Inspect || > ยง2Nome: ยงf${0} -Cmd Header - Network || > ยง2Pagina Network -Cmd Header - Players || > ยง2Giocatori -Cmd Header - Search || > ยง2${0} Risultati per ยงf${1}ยง2: -Cmd Header - server list || id::name::uuid -Cmd Header - Servers || > ยง2Servers -Cmd Header - web user list || username::linked to::permission level -Cmd Header - Web Users || > ยง2${0} Utenti Web -Cmd Info - Bungee Connection || ยง2Connesso al Proxy: ยงf${0} -Cmd Info - Database || ยง2Database corrente: ยงf${0} -Cmd Info - Reload Complete || ยงaRicaricamento completato -Cmd Info - Reload Failed || ยงcErrore durante il reload del plugin, un riavvio รจ raccomandato. -Cmd Info - Update || ยง2Aggiornamento Disponibile: ยงf${0} -Cmd Info - Version || ยง2Versione: ยงf${0} -Cmd network - No network || Server is not connected to a network. The link redirects to server page. -Cmd Notify - No Address || ยงeNo address was available - using localhost as fallback. Set up 'Alternative_IP' settings. -Cmd Notify - No WebUser || Nessun utente web registrato, usa /plan register -Cmd Notify - WebUser register || Nuovi utenti web registrati: '${0}' Livello permessi: ${1} -Cmd Qinspect - Active Playtime || ยง2Active Playtime: ยงf${0} -Cmd Qinspect - Activity Index || ยง2Indice Attivitร : ยงf${0} | ${1} -Cmd Qinspect - AFK Playtime || ยง2AFK Time: ยงf${0} -Cmd Qinspect - Deaths || ยง2Morti: ยงf${0} -Cmd Qinspect - Geolocation || ยง2Loggato da: ยงf${0} -Cmd Qinspect - Last Seen || ยง2Ultima visita: ยงf${0} -Cmd Qinspect - Longest Session || ยง2Sessione piรน lunga: ยงf${0} -Cmd Qinspect - Mob Kills || ยง2Mob Uccisi: ยงf${0} -Cmd Qinspect - Player Kills || ยง2Giocatori Uccisi: ยงf${0} -Cmd Qinspect - Playtime || ยง2Tempo di Gioco: ยงf${0} -Cmd Qinspect - Registered || ยง2Registrato: ยงf${0} -Cmd Qinspect - Times Kicked || ยง2Espulso: ยงf${0} -Cmd SUCCESS - Feature disabled || ยงaDisabilitato temporaneamente '${0}' fino al prossimo reload del plugin. -Cmd SUCCESS - WebUser register || ยงaAggiunto il nuovo utente (${0})! -Cmd unregister - unregistering || Unregistering '${0}'.. -Cmd WARN - Database not open || ยงeDatabase รจ ${0} - Potrebbe volerci un pรฒ di piรน tempo.. -Cmd Web - Permission Levels || >\ยง70: Accesso a tutte le pagine\ยง71: Accesso '/players' e tutti i dati dei giocatori\ยง72: Accedi alle pagine dei giocatori con lo stesso username dell'Utente Web.\ยง73+: Nessun permesso -Command Help - /plan db || Manage Plan database -Command Help - /plan db backup || Backup data of a database to a file -Command Help - /plan db clear || Remove ALL Plan data from a database -Command Help - /plan db hotswap || Cambia il database rapidamente -Command Help - /plan db move || Sposta i dati tra i database -Command Help - /plan db remove || Remove player's data from Current database -Command Help - /plan db restore || Restore data from a file to a database -Command Help - /plan db uninstalled || Set a server as uninstalled in the database. -Command Help - /plan disable || Disable the plugin or part of it -Command Help - /plan export || Export html or json files manually -Command Help - /plan import || Import data -Command Help - /plan info || Information about the plugin -Command Help - /plan ingame || Mostra info dei giocatori in gioco -Command Help - /plan json || View json of Player's raw data. -Command Help - /plan logout || Log out other users from the panel. -Command Help - /plan network || Mostra la pagina del network -Command Help - /plan player || Mostra la pagina di un giocatore -Command Help - /plan players || Mostra la pagina dei giocatori -Command Help - /plan register || Registra un nuovo utente web -Command Help - /plan reload || Ricarica Plan -Command Help - /plan search || Cerca il nome di un giocatore -Command Help - /plan server || Mostra la pagina del server -Command Help - /plan servers || Elenca i server nel Database -Command Help - /plan unregister || Unregister a user of Plan website -Command Help - /plan users || List all web users -Database - Apply Patch || Applicando Patch: ${0}.. -Database - Patches Applied || Tutte le patch sono satte applicate al database. -Database - Patches Applied Already || Tutte le patch sono giร  state applicate. -Database MySQL - Launch Options Error || Le opzioni di Lancio sono errate, usando le opzioni default (${0}) -Database Notify - Clean || Rimossi i dati di ${0} giocatorri. -Database Notify - SQLite No WAL || Modalitร  SQLite WAL non supportato da questa versione del server, uso le impostazioni predefinite. Ciรฒ potrebbe causare dei problemi. -Disable || Analisi Giocatori Disabilitato. -Disable - Processing || Elaborazione di attivitร  critiche non elaborate. (${0}) -Disable - Processing Complete || Elaborazione completata. -Disable - Unsaved Session Save || Salvataggio di sessioni incompiute.. -Disable - Unsaved Session Save Timeout || Timeout hit, storing the unfinished sessions on next enable instead. -Disable - Waiting SQLite || Waiting queries to finish to avoid SQLite crashing JVM.. -Disable - Waiting SQLite Complete || Closed SQLite connection. -Disable - Waiting Transactions || Waiting for unfinished transactions to avoid data loss.. -Disable - Waiting Transactions Complete || Transaction queue closed. -Disable - WebServer || Il Webserver รจ stato disabilitato. -Enable || Analisi Giocatori Abilitata. -Enable - Database || ${0}-Connessione al database stabilita. -Enable - Notify Bad IP || 0.0.0.0 is not a valid address, set up Alternative_IP settings. Incorrect links might be given! -Enable - Notify Empty IP || L'IP in server.properties รจ vuoto e Alternative_IP non รจ in uso. Correggi queste informazioni! -Enable - Notify Geolocations disabled || Geolocalizazione non attiva. (Data.Geolocations: false) -Enable - Notify Geolocations Internet Required || Plan Richiede l'accesso a Internet al primo avvio per scaricare il database di geolocalizzazione GeoLite2. -Enable - Notify Webserver disabled || WebServer non avviato. (WebServer.DisableWebServer: true) -Enable - Storing preserved sessions || Storing sessions that were preserved before previous shutdown. -Enable - WebServer || Webserver avviato sulla PORTA ${0} ( ${1} ) -Enable FAIL - Database || ${0}-Connessione al Database fallita: ${1} -Enable FAIL - Database Patch || Upgrade del Database fallito, plan รจ stato disabilitato. Si prega di segnalare questo problema. -Enable FAIL - GeoDB Write || Qualcosa รจ andato storto durante lo scaricamento del database GeoLite2 -Enable FAIL - WebServer (Proxy) || WebServer non avviato! -Enable FAIL - Wrong Database Type || ${0} non รจ un Database supportato -HTML - AND_BUG_REPORTERS || & Bug reporters! -HTML - BANNED (Filters) || Banned -HTML - COMPARING_15_DAYS || Comparazione di 15 giorni -HTML - COMPARING_60_DAYS || Comparazione di 30g fa a Ora -HTML - COMPARING_7_DAYS || Comparazione di 7 giorni -HTML - DATABASE_NOT_OPEN || Database non trovato, controlla il database con /plan info -HTML - DESCRIBE_RETENTION_PREDICTION || This value is a prediction based on previous players. -HTML - ERROR || Autenticazione fallita a causa di un errore -HTML - EXPIRED_COOKIE || User cookie has expired -HTML - FILTER_ACTIVITY_INDEX_NOW || Current activity group -HTML - FILTER_ALL_PLAYERS || All players -HTML - FILTER_BANNED || Ban status -HTML - FILTER_GROUP || Group: -HTML - FILTER_OPS || Operator status -HTML - INDEX_ACTIVE || Attivo -HTML - INDEX_INACTIVE || Inattivo -HTML - INDEX_IRREGULAR || Irregolare -HTML - INDEX_REGULAR || Regolare -HTML - INDEX_VERY_ACTIVE || Molto Attivo -HTML - KILLED || Ucciso -HTML - LABEL_1ST_WEAPON || Arma PvP Preferita -HTML - LABEL_2ND_WEAPON || 2ยฐ Arma PvP Preferita -HTML - LABEL_3RD_WEAPON || 3ยฐ Arma PvP Preferita -HTML - LABEL_ACTIVE_PLAYTIME || Active Playtime -HTML - LABEL_ACTIVITY_INDEX || Indice Inattivitร  -HTML - LABEL_AFK || AFK -HTML - LABEL_AFK_TIME || Tempo AFK -HTML - LABEL_AVG || Media -HTML - LABEL_AVG_ACTIVE_PLAYTIME || Average Active Playtime -HTML - LABEL_AVG_AFK_TIME || Average AFK Time -HTML - LABEL_AVG_CHUNKS || Average Chunks -HTML - LABEL_AVG_ENTITIES || Average Entities -HTML - LABEL_AVG_KDR || Media KDR -HTML - LABEL_AVG_MOB_KDR || Media Mob KDR -HTML - LABEL_AVG_PLAYTIME || Media Tempo di Gioco -HTML - LABEL_AVG_SESSION_LENGTH || Media Lunghezza Sessione -HTML - LABEL_AVG_SESSIONS || Average Sessions -HTML - LABEL_AVG_TPS || Media TPS -HTML - LABEL_BANNED || Bannato -HTML - LABEL_BEST_PEAK || Record Migliore -HTML - LABEL_DAY_OF_WEEK || Giorno della Settimana -HTML - LABEL_DEATHS || Morti -HTML - LABEL_DOWNTIME || Downtime -HTML - LABEL_DURING_LOW_TPS || Durante Spicchi TPS bassi: -HTML - LABEL_ENTITIES || Entitร  -HTML - LABEL_FAVORITE_SERVER || Server Preferito -HTML - LABEL_FIRST_SESSION_LENGTH || Lunghezza Prima sessione -HTML - LABEL_FREE_DISK_SPACE || Spazio Disco Disponibile -HTML - LABEL_INACTIVE || Inattivo -HTML - LABEL_LAST_PEAK || Record Settimanale -HTML - LABEL_LAST_SEEN || Ultima Visita -HTML - LABEL_LOADED_CHUNKS || Chunks Caricati -HTML - LABEL_LOADED_ENTITIES || Entitร  Caricate -HTML - LABEL_LONE_JOINS || Entrate Solitarie -HTML - LABEL_LONE_NEW_JOINS || Nuove entrate Solitarie -HTML - LABEL_LONGEST_SESSION || Sessione piรน lunga -HTML - LABEL_LOW_TPS || Spicchi TPS bassi -HTML - LABEL_MAX_FREE_DISK || Spazio Disco libero Max -HTML - LABEL_MIN_FREE_DISK || Spazio Disco libero Min -HTML - LABEL_MOB_DEATHS || Mob che hanno causato la Morte -HTML - LABEL_MOB_KDR || Mob KDR -HTML - LABEL_MOB_KILLS || Mob uccisi -HTML - LABEL_MOST_ACTIVE_GAMEMODE || La Gamemode piรน attiva -HTML - LABEL_NAME || Nome -HTML - LABEL_NEW || Nuovi -HTML - LABEL_NEW_PLAYERS || Nuovi Giocatori -HTML - LABEL_NICKNAME || Nick -HTML - LABEL_NO_SESSION_KILLS || Nessuno -HTML - LABEL_ONLINE_FIRST_JOIN || Giocatori Online entrati la prima volta -HTML - LABEL_OPERATOR || Operatore -HTML - LABEL_PER_PLAYER || / Giocatore -HTML - LABEL_PER_REGULAR_PLAYER || / Giocatore regolare -HTML - LABEL_PLAYER_DEATHS || Giocatori che hanno causato la Morte -HTML - LABEL_PLAYER_KILLS || Giocatori Uccisi -HTML - LABEL_PLAYERS_ONLINE || Giocatori Online -HTML - LABEL_PLAYTIME || Gioco -HTML - LABEL_REGISTERED || Registrato -HTML - LABEL_REGISTERED_PLAYERS || Giocatori Registrati -HTML - LABEL_REGULAR || Regolari -HTML - LABEL_REGULAR_PLAYERS || Giocatori Regolari -HTML - LABEL_RELATIVE_JOIN_ACTIVITY || Attivitร  Entrate Relative -HTML - LABEL_RETENTION || Retenzione Nuovo Giocatore -HTML - LABEL_SERVER_DOWNTIME || Server Downtime -HTML - LABEL_SERVER_OCCUPIED || Server occupato -HTML - LABEL_SESSION_ENDED || Fine -HTML - LABEL_SESSION_MEDIAN || Media Sessioni -HTML - LABEL_TIMES_KICKED || Cacciato -HTML - LABEL_TOTAL_PLAYERS || Giocatori Totali -HTML - LABEL_TOTAL_PLAYTIME || Tempo di gioco Totale -HTML - LABEL_UNIQUE_PLAYERS || Giocatori unici -HTML - LABEL_WEEK_DAYS || 'Lunedรฌ', 'Martedรฌ', 'Mercoledรฌ', 'Giovedรฌ', 'Venerdรฌ', 'Sabato', 'Domenica' -HTML - LINK_BACK_NETWORK || Pagina del Network -HTML - LINK_BACK_SERVER || Pagina del Server -HTML - LINK_CHANGELOG || Vedi aggiornamenti -HTML - LINK_DISCORD || Supporto generale su Discord -HTML - LINK_DOWNLOAD || Download -HTML - LINK_ISSUES || Segnala Problemi -HTML - LINK_NIGHT_MODE || Modalitร  Notte -HTML - LINK_PLAYER_PAGE || Pagina Giocatore -HTML - LINK_QUICK_VIEW || Vista Rapida -HTML - LINK_SERVER_ANALYSIS || Analisi Server -HTML - LINK_WIKI || Plan Wiki, Tutorial e Documentazione -HTML - LOCAL_MACHINE || Macchina Locale -HTML - LOGIN_CREATE_ACCOUNT || Create an Account! -HTML - LOGIN_FAILED || Login failed: -HTML - LOGIN_FORGOT_PASSWORD || Forgot Password? -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_1 || Forgot password? Unregister and register again. -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_2 || Use the following command in game to remove your current user: -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_3 || Or using console: -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_4 || After using the command, -HTML - LOGIN_LOGIN || Login -HTML - LOGIN_LOGOUT || Logout -HTML - LOGIN_PASSWORD || "Password" -HTML - LOGIN_USERNAME || "Username" -HTML - NAV_PLUGINS || Plugins -HTML - NEW_CALENDAR || Nuovo: -HTML - NO_KILLS || Nessuna Uccisione -HTML - NO_USER_PRESENT || User cookie not present -HTML - NON_OPERATORS (Filters) || Non operators -HTML - NOT_BANNED (Filters) || Not banned -HTML - OFFLINE || Offline -HTML - ONLINE || Online -HTML - OPERATORS (Filters) || Operators -HTML - PER_DAY || / Giorno -HTML - PLAYERS_TEXT || Giocatori -HTML - QUERY || Query< -HTML - QUERY_ACTIVITY_OF_MATCHED_PLAYERS || Activity of matched players -HTML - QUERY_ACTIVITY_ON || Activity on -HTML - QUERY_ADD_FILTER || Add a filter.. -HTML - QUERY_AND || and -HTML - QUERY_ARE || `are` -HTML - QUERY_ARE_ACTIVITY_GROUP || are in Activity Groups -HTML - QUERY_ARE_PLUGIN_GROUP || are in ${plugin}'s ${group} Groups -HTML - QUERY_JOINED_WITH_ADDRESS || joined with address -HTML - QUERY_LOADING_FILTERS || Loading filters.. -HTML - QUERY_MAKE || Make a query -HTML - QUERY_MAKE_ANOTHER || Make another query -HTML - QUERY_OF_PLAYERS || of Players who -HTML - QUERY_PERFORM_QUERY || Perform Query! -HTML - QUERY_PLAYED_BETWEEN || Played between -HTML - QUERY_REGISTERED_BETWEEN || Registered between -HTML - QUERY_RESULTS || Query Results -HTML - QUERY_RESULTS_MATCH || matched ${resultCount} players -HTML - QUERY_SESSIONS_WITHIN_VIEW || Sessions within view -HTML - QUERY_SHOW_VIEW || Show a view -HTML - QUERY_TIME_FROM || >from -HTML - QUERY_TIME_TO || >to -HTML - QUERY_VIEW || View: -HTML - QUERY_ZERO_RESULTS || Query produced 0 results -HTML - REGISTER || Register -HTML - REGISTER_CHECK_FAILED || Checking registration status failed: -HTML - REGISTER_COMPLETE || Complete Registration -HTML - REGISTER_COMPLETE_INSTRUCTIONS_1 || You can now finish registering the user. -HTML - REGISTER_COMPLETE_INSTRUCTIONS_2 || Code expires in 15 minutes -HTML - REGISTER_COMPLETE_INSTRUCTIONS_3 || Use the following command in game to finish registration: -HTML - REGISTER_COMPLETE_INSTRUCTIONS_4 || Or using console: -HTML - REGISTER_CREATE_USER || Create a new user -HTML - REGISTER_FAILED || Registration failed: -HTML - REGISTER_HAVE_ACCOUNT || Already have an account? Login! -HTML - REGISTER_PASSWORD_TIP || Password should be more than 8 characters, but there are no limitations. -HTML - REGISTER_SPECIFY_PASSWORD || You need to specify a Password -HTML - REGISTER_SPECIFY_USERNAME || You need to specify a Username -HTML - REGISTER_USERNAME_LENGTH || Username can be up to 50 characters, yours is -HTML - REGISTER_USERNAME_TIP || Username can be up to 50 characters. -HTML - SESSION || Session -HTML - SIDE_GEOLOCATIONS || Geolocalizazione -HTML - SIDE_INFORMATION || INFORMAZIONE -HTML - SIDE_LINKS || LINKS -HTML - SIDE_NETWORK_OVERVIEW || Informazioni sul Network -HTML - SIDE_OVERVIEW || Informazioni -HTML - SIDE_PERFORMANCE || Performance -HTML - SIDE_PLAYER_LIST || Elenco Giocatori -HTML - SIDE_PLAYERBASE || Playerbase -HTML - SIDE_PLAYERBASE_OVERVIEW || Statistiche Playerbase -HTML - SIDE_PLUGINS || PLUGINS -HTML - SIDE_PVP_PVE || PvP & PvE -HTML - SIDE_SERVERS || Servers -HTML - SIDE_SERVERS_TITLE || SERVERS -HTML - SIDE_SESSIONS || Sessioni -HTML - SIDE_TO_MAIN_PAGE || Ritorna alla pagina principale -HTML - TEXT_CLICK_TO_EXPAND || Clicca per espendere -HTML - TEXT_CONTRIBUTORS_CODE || contributori codice -HTML - TEXT_CONTRIBUTORS_LOCALE || traduttore -HTML - TEXT_CONTRIBUTORS_MONEY || Un ringraziamento speciale a coloro che hanno sostenuto monetariamente lo sviluppo. -HTML - TEXT_CONTRIBUTORS_THANKS || Inoltre, hanno contribuito queste fantastiche persone: -HTML - TEXT_DEV_VERSION || Questa versione รจ una versione DEV. -HTML - TEXT_DEVELOPED_BY || รจ stato svillupato da -HTML - TEXT_FIRST_SESSION || Prima sessione -HTML - TEXT_LICENSED_UNDER || Player Analytics รจ stato svillpato e licenziato sotto -HTML - TEXT_METRICS || Metriche bStats -HTML - TEXT_NO_EXTENSION_DATA || Nessun dato estensione -HTML - TEXT_NO_LOW_TPS || Nennun picco basso di TPS -HTML - TEXT_NO_SERVER || Nessun server con cui mostare attivitร  -HTML - TEXT_NO_SERVERS || Nessun server trovato in questo database -HTML - TEXT_PLUGIN_INFORMATION || Informazioni su questo plugin -HTML - TEXT_PREDICTED_RETENTION || Questo valore รจ una previsione basata sui giocatori precedenti -HTML - TEXT_SERVER_INSTRUCTIONS || It appears that Plan is not installed on any game servers or not connected to the same database. See wiki for Network tutorial. -HTML - TEXT_VERSION || Una nuova versione รจ stata rilasciata ed รจ ora disponibile da scaricare. -HTML - TITLE_30_DAYS || 30 giorni -HTML - TITLE_30_DAYS_AGO || 30 giorni fa -HTML - TITLE_ALL || Tutto -HTML - TITLE_ALL_TIME || Tutto il Tempo -HTML - TITLE_AS_NUMBERS || Statistiche -HTML - TITLE_AVG_PING || Ping Medio -HTML - TITLE_BEST_PING || Ping Migliore -HTML - TITLE_CALENDAR || Calendario -HTML - TITLE_CONNECTION_INFO || Informazioni sulla Connessione -HTML - TITLE_COUNTRY || Nazione -HTML - TITLE_CPU_RAM || CPU & RAM -HTML - TITLE_CURRENT_PLAYERBASE || Playerbase Corrente -HTML - TITLE_DISK || Spazio sul Disco -HTML - TITLE_GRAPH_DAY_BY_DAY || Giorno Per Giorno -HTML - TITLE_GRAPH_HOUR_BY_HOUR || Hour by Hour -HTML - TITLE_GRAPH_NETWORK_ONLINE_ACTIVITY || Attivitร  Online Network -HTML - TITLE_GRAPH_PUNCHCARD || Attivitร  in 30 giorni -HTML - TITLE_INSIGHTS || Grafico in 30 giorni -HTML - TITLE_IS_AVAILABLE || รจ Disponibile -HTML - TITLE_JOIN_ADDRESSES || Join Addresses -HTML - TITLE_LAST_24_HOURS || Ultime 24 ore -HTML - TITLE_LAST_30_DAYS || Ultimi 30 giorni -HTML - TITLE_LAST_7_DAYS || Ultimi 7 giorni -HTML - TITLE_LAST_CONNECTED || Ultima connessione -HTML - TITLE_LENGTH || Durata -HTML - TITLE_MOST_PLAYED_WORLD || Mondo piรน giocato -HTML - TITLE_NETWORK || Network -HTML - TITLE_NETWORK_AS_NUMBERS || Statistiche Network -HTML - TITLE_NOW || Ora -HTML - TITLE_ONLINE_ACTIVITY || Attivitร  Online -HTML - TITLE_ONLINE_ACTIVITY_AS_NUMBERS || Statistiche Attivitร  Online -HTML - TITLE_ONLINE_ACTIVITY_OVERVIEW || Panoramica delle attivitร  online -HTML - TITLE_PERFORMANCE_AS_NUMBERS || Prestazioni come numeri -HTML - TITLE_PING || Ping -HTML - TITLE_PLAYER || Player -HTML - TITLE_PLAYER_OVERVIEW || Statistiche Giocatore -HTML - TITLE_PLAYERBASE_DEVELOPMENT || Grafico Playerbase -HTML - TITLE_PVP_DEATHS || Morti PvP Recenti -HTML - TITLE_PVP_KILLS || Uccisioni PvP Recenti -HTML - TITLE_PVP_PVE_NUMBERS || Statistiche PvP & PvE -HTML - TITLE_RECENT_KILLS || Uccisioni Recenti -HTML - TITLE_RECENT_SESSIONS || Sessioni Recenti -HTML - TITLE_SEEN_NICKNAMES || Nick Usati -HTML - TITLE_SERVER || Server -HTML - TITLE_SERVER_AS_NUMBERS || Statistiche Server -HTML - TITLE_SERVER_OVERVIEW || Vista Server -HTML - TITLE_SERVER_PLAYTIME || Tempo di Gioco Server -HTML - TITLE_SERVER_PLAYTIME_30 || Tempo di Gioco Server di 30 giorni -HTML - TITLE_SESSION_START || Sessione Iniziata -HTML - TITLE_THEME_SELECT || Selezione Tema -HTML - TITLE_TITLE_PLAYER_PUNCHCARD || Presenza Settimanale -HTML - TITLE_TPS || TPS -HTML - TITLE_TREND || Tendenza -HTML - TITLE_TRENDS || Tendenza per 30 giorni -HTML - TITLE_VERSION || Versione -HTML - TITLE_WEEK_COMPARISON || Confronto settimanale -HTML - TITLE_WORLD || Caricamento Mondo -HTML - TITLE_WORLD_PLAYTIME || Tempo di gioco Mondo -HTML - TITLE_WORST_PING || Ping Peggiore -HTML - TOTAL_ACTIVE_TEXT || Totale Attivo -HTML - TOTAL_AFK || Totale AFK -HTML - TOTAL_PLAYERS || Totale Giocatori -HTML - UNIQUE_CALENDAR || Unico: -HTML - UNIT_CHUNKS || Chunks -HTML - UNIT_ENTITIES || Entitร  -HTML - UNIT_NO_DATA || Nessun Dato -HTML - UNIT_THE_PLAYERS || Giocatori -HTML - USER_AND_PASS_NOT_SPECIFIED || Utente e Password non specificati -HTML - USER_DOES_NOT_EXIST || Utente non esistente -HTML - USER_INFORMATION_NOT_FOUND || Registration failed, try again (The code expires after 15 minutes) -HTML - USER_PASS_MISMATCH || Utente e Password non corrispondono -HTML - Version Change log || View Changelog -HTML - Version Current || You have version ${0} -HTML - Version Download || Download Plan-${0}.jar -HTML - Version Update || Update -HTML - Version Update Available || Version ${0} is Available! -HTML - Version Update Dev || This version is a DEV release. -HTML - Version Update Info || A new version has been released and is now available for download. -HTML - WARNING_NO_GAME_SERVERS || Some data requires Plan to be installed on game servers. -HTML - WARNING_NO_GEOLOCATIONS || Geolocation gathering needs to be enabled in the config (Accept GeoLite2 EULA). -HTML - WARNING_NO_SPONGE_CHUNKS || Chunks unavailable on Sponge -HTML - WITH || Con -HTML ERRORS - ACCESS_DENIED_403 || Acccesso Bloccato -HTML ERRORS - AUTH_FAIL_TIPS_401 || - Assicurati di registare un nuovo utente usando /plan register
- Controlla che il nome e password siano corretti
- Nome e password sono case-sensitive

Se hai dimenticato la tua passwrod, chiedi a uno staff di rimuovere i tuoi dati e registrarti di nuovo. -HTML ERRORS - AUTHENTICATION_FAILED_401 || Autenticazione Fallita. -HTML ERRORS - FORBIDDEN_403 || Vietato -HTML ERRORS - NO_SERVERS_404 || Nessun Server online per fare la richiesta. -HTML ERRORS - NOT_FOUND_404 || Non Trovato -HTML ERRORS - NOT_PLAYED_404 || Questo giocatore non ha mai giocato su questo server. -HTML ERRORS - PAGE_NOT_FOUND_404 || Questa pagina non esiste. -HTML ERRORS - UNAUTHORIZED_401 || Non Autorizzato -HTML ERRORS - UNKNOWN_PAGE_404 || Assicurati che stai seguendo il link usando il commando, Esempi:

/player/{uuid/name}
/server/{uuid/name/id}

-HTML ERRORS - UUID_404 || UUID del Giocatore non trovato nel database. -In Depth Help - /plan db || Use different database subcommands to change the data in some way -In Depth Help - /plan db backup || Uses SQLite to backup the target database to a file. -In Depth Help - /plan db clear || Clears all Plan tables, removing all Plan-data in the process. -In Depth Help - /plan db hotswap || Reloads the plugin with the other database and changes the config to match. -In Depth Help - /plan db move || Overwrites contents in the other database with the contents in another. -In Depth Help - /plan db remove || Removes all data linked to a player from the Current database. -In Depth Help - /plan db restore || Uses SQLite backup file and overwrites contents of the target database. -In Depth Help - /plan db uninstalled || Marks a server in Plan database as uninstalled so that it will not show up in server queries. -In Depth Help - /plan disable || Disable the plugin or part of it until next reload/restart. -In Depth Help - /plan export || Performs an export to export location defined in the config. -In Depth Help - /plan import || Performs an import to load data into the database. -In Depth Help - /plan info || Display the current status of the plugin. -In Depth Help - /plan ingame || Visualizza alcune informazioni sul giocatore in gioco. -In Depth Help - /plan json || Allows you to download a player's data in json format. All of it. -In Depth Help - /plan logout || Give username argument to log out another user from the panel, give * as argument to log out everyone. -In Depth Help - /plan network || Obtain a link to the /network page, only does so on networks. -In Depth Help - /plan player || Obtain a link to the /player page of a specific player, or the current player. -In Depth Help - /plan players || Obtain a link to the /players page to see a list of players. -In Depth Help - /plan register || Use without arguments to get link to register page. Use --code [code] after registration to get a user. -In Depth Help - /plan reload || Disable and enable the plugin to reload any changes in config. -In Depth Help - /plan search || List all matching player names to given part of a name. -In Depth Help - /plan server || Obtain a link to the /server page of a specific server, or the current server if no arguments are given. -In Depth Help - /plan servers || List ids, names and uuids of servers in the database. -In Depth Help - /plan unregister || Use without arguments to unregister player linked user, or with username argument to unregister another user. -In Depth Help - /plan users || Lists web users as a table. -Manage - Confirm Overwrite || Dati in ${0} saranno sovrascritti! -Manage - Confirm Removal || Dati in ${0} saranno rimossi! -Manage - Fail || > ยงcErrore: ${0} -Manage - Fail File not found || > ยงcNessun file trovato su ${0} -Manage - Fail Incorrect Database || > ยงc'${0}' non รจ un database supportato. -Manage - Fail No Exporter || ยงeEsportatore '${0}' non essite -Manage - Fail No Importer || ยงeImportatore '${0}' non essite -Manage - Fail No Server || Nessun server trovato con i seguenti parametri. -Manage - Fail Same Database || > ยงcImpossibile operare da e verso lo stesso database! -Manage - Fail Same server || Impossibile rimuovere questo server (Sei dentro a questo server) -Manage - Fail, Confirmation || > ยงcAggiungi l'argomento '-a' per confermare l'esecuzione: ${0} -Manage - List Importers || Importatori: -Manage - Progress || ${0} / ${1} processed.. -Manage - Remind HotSwap || ยงeRircorda di cambiare il nuovo database (/plan db hotswap ${0}) e ricaricare il plugin. -Manage - Start || > ยง2Processando i dati.. -Manage - Success || > ยงaCompletato! -Negative || No -Positive || Sรฌ -Today || 'Oggi' -Unavailable || Non Disponibile -Unknown || Sconosciuto -Version - DEV || Questa รจ una versione DEV. -Version - Latest || Stai usando l'ultima versione. -Version - New || Nuova versione (${0}) รจ disponibile ${1} -Version - New (old) || Nuova versione disponibile su ${0} -Version FAIL - Read info (old) || Impossibile controllare nuove versioni -Version FAIL - Read versions.txt || Informazioni sulla vesione non trovate su Github/versions.txt -Web User Listing || ยง2${0} ยง7: ยงf${1} -WebServer - Notify HTTP || WebServer: Nessun Certificato -> Uso ora server HTTP per la Visualizazione. -WebServer - Notify HTTP User Auth || WebServer: Autorizzazione Utente Disabilitato! (Non sicuro su HTTP) -WebServer - Notify HTTPS User Auth || WebServer: User Authorization Disabled! (Disabled in config) -Webserver - Notify IP Whitelist || Webserver: IP Whitelist is enabled. -Webserver - Notify IP Whitelist Block || Webserver: ${0} was denied access to '${1}'. (not whitelisted) -WebServer - Notify no Cert file || WebServer: Chiave del Certfiicato non trovato: ${0} -WebServer - Notify Using Proxy || WebServer: Proxy-mode HTTPS enabled, make sure that your reverse-proxy is routing using HTTPS and Plan Alternative_IP.Address points to the Proxy -WebServer FAIL - EOF || WebServer: EOF when reading Certificate file. (Check that the file is not empty) -WebServer FAIL - Port Bind || WebServer non avviato. La porta (${0}) รจ giร  occupata? -WebServer FAIL - SSL Context || WebServer: Inilizazione Contenuto SSL Fallito. -WebServer FAIL - Store Load || WebServer: Caricamento Certificato SSL Fallito. -Yesterday || 'Ieri' diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_IT.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_IT.yml new file mode 100644 index 000000000..c81690b6d --- /dev/null +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_IT.yml @@ -0,0 +1,669 @@ +403AccessDenied: "Acccesso Bloccato" +command: + argument: + backupFile: + description: "Name of the backup file (case sensitive)" + name: "backup-file" + code: + description: "Code used to finalize registration." + name: "${code}" + dbBackup: + description: "Type of the database to backup. Current database is used if not specified." + dbRestore: + description: "Type of the database to restore to. Current database is used if not specified." + dbTypeHotswap: + description: "Type of the database to start using." + dbTypeMoveFrom: + description: "Type of the database to move data from." + dbTypeMoveTo: + description: "Type of the database to move data to. Can not be same as previous." + dbTypeRemove: + description: "Type of the database to remove all data from." + exportKind: "export kind" + feature: + description: "Name of the feature to disable: ${0}" + name: "feature" + importKind: "import kind" + nameOrUUID: + description: "Name or UUID of a player" + name: "name/uuid" + removeDescription: "Identifier for a player that will be removed from current database." + server: + description: "Name, ID or UUID of a server" + name: "server" + subcommand: + description: "Use the command without subcommand to see help." + name: "subcommand" + username: + description: "Username of another user. If not specified player linked user is used." + name: "username" + confirmation: + accept: "Accept" + cancelNoChanges: "Cancelled. No data was changed." + cancelNoUnregister: "Cancelled. '${0}' was not unregistered" + confirm: "Confirm: " + dbClear: "You are about to remove all Plan-data in ${0}" + dbOverwrite: "You are about to overwrite data in Plan ${0} with data in ${1}" + dbRemovePlayer: "You are about to remove data of ${0} from ${1}" + deny: "Cancel" + expired: "Confirmation expired, use the command again" + unregister: "You are about to unregister '${0}' linked to ${1}" + database: + creatingBackup: "Creating a backup file '${0}.db' with contents of ${1}" + failDbNotOpen: "ยงcDatabase รจ ${0} - Riprova piรน tardi." + manage: + confirm: "> ยงcAggiungi l'argomento '-a' per confermare l'esecuzione: ${0}" + confirmOverwrite: "Dati in ${0} saranno sovrascritti!" + confirmPartialRemoval: "Join Address Data for Server ${0} in ${1} will be removed!" + confirmRemoval: "Dati in ${0} saranno rimossi!" + fail: "> ยงcErrore: ${0}" + failFileNotFound: "> ยงcNessun file trovato su ${0}" + failIncorrectDB: "> ยงc'${0}' non รจ un database supportato." + failNoServer: "Nessun server trovato con i seguenti parametri." + failSameDB: "> ยงcImpossibile operare da e verso lo stesso database!" + failSameServer: "Impossibile rimuovere questo server (Sei dentro a questo server)" + hotswap: "ยงeRircorda di cambiare il nuovo database (/plan db hotswap ${0}) e ricaricare il plugin." + importers: "Importatori: " + preparing: "Preparing.." + progress: "${0} / ${1} processed.." + start: "> ยง2Processando i dati.." + success: "> ยงaCompletato!" + playerRemoval: "Removing data of ${0} from ${1}.." + removal: "Removing Plan-data from ${0}.." + serverUninstalled: "ยงaIf the server is still installed, it will automatically set itself as installed in the database." + unregister: "Unregistering '${0}'.." + warnDbNotOpen: "ยงeDatabase รจ ${0} - Potrebbe volerci un pรฒ di piรน tempo.." + write: "Writing to ${0}.." + fail: + emptyString: "The search string can not be empty" + invalidArguments: "Accepts following as ${0}: ${1}" + invalidUsername: "ยงcQuesto Utente non ha un UUID." + missingArguments: "ยงcArgomenti richiesti (${0}) ${1}" + missingFeature: "ยงeDefinisci una feature da disabilitate! (Feature Supportate ${0})" + missingLink: "User is not linked to your account and you don't have permission to remove other user's accounts." + noPermission: "ยงcNon hai nessun permesso." + onAccept: "The accepted action errored upon execution: ${0}" + onDeny: "The denied action errored upon execution: ${0}" + playerNotFound: "Player '${0}' was not found, they have no UUID." + playerNotInDatabase: "Player '${0}' was not found in the database." + seeConfig: "see '${0}' in config.yml" + serverNotFound: "Server '${0}' was not found from the database." + tooManyArguments: "ยงcArgomento singolo richiesto ${1}" + unknownUsername: "ยงcGiocatore mai visto su questo server" + webUserExists: "ยงcGiocatore giร  esistente!" + webUserNotFound: "ยงcGiocatore non esistente!" + footer: + help: "ยง7Hover over command or arguments or use '/${0} ?' to learn more about them." + general: + failNoExporter: "ยงeEsportatore '${0}' non essite" + failNoImporter: "ยงeImportatore '${0}' non essite" + featureDisabled: "ยงaDisabilitato temporaneamente '${0}' fino al prossimo reload del plugin." + noAddress: "ยงeNo address was available - using localhost as fallback. Set up 'Alternative_IP' settings." + noWebuser: "Nessun utente web registrato, usa /plan register " + notifyWebUserRegister: "Nuovi utenti web registrati: '${0}' Livello permessi: ${1}" + pluginDisabled: "ยงaPlan รจ stato disabilitato. Puoi usare reload per riavviare il plugin." + reloadComplete: "ยงaRicaricamento completato" + reloadFailed: "ยงcErrore durante il reload del plugin, un riavvio รจ raccomandato." + successWebUserRegister: "ยงaAggiunto il nuovo utente (${0})!" + webPermissionLevels: ">\ยง70: Accesso a tutte le pagine\ยง71: Accesso '/players' e tutti i dati dei giocatori\ยง72: Accedi alle pagine dei giocatori con lo stesso username dell'Utente Web.\ยง73+: Nessun permesso" + webUserList: " ยง2${0} ยง7: ยงf${1}" + header: + analysis: "> ยง2Risultati Analisi" + help: "> ยง2/${0} Help" + info: "> ยง2Analisi Giocatore" + inspect: "> ยง2Nome: ยงf${0}" + network: "> ยง2Pagina Network" + players: "> ยง2Giocatori" + search: "> ยง2${0} Risultati per ยงf${1}ยง2:" + serverList: "id::name::uuid::version" + servers: "> ยง2Servers" + webUserList: "username::linked to::permission level" + webUsers: "> ยง2${0} Utenti Web" + help: + database: + description: "Manage Plan database" + inDepth: "Use different database subcommands to change the data in some way" + dbBackup: + description: "Backup data of a database to a file" + inDepth: "Uses SQLite to backup the target database to a file." + dbClear: + description: "Remove ALL Plan data from a database" + inDepth: "Clears all Plan tables, removing all Plan-data in the process." + dbHotswap: + description: "Cambia il database rapidamente" + inDepth: "Reloads the plugin with the other database and changes the config to match." + dbMove: + description: "Sposta i dati tra i database" + inDepth: "Overwrites contents in the other database with the contents in another." + dbRemove: + description: "Remove player's data from Current database" + inDepth: "Removes all data linked to a player from the Current database." + dbRestore: + description: "Restore data from a file to a database" + inDepth: "Uses SQLite backup file and overwrites contents of the target database." + dbUninstalled: + description: "Set a server as uninstalled in the database." + inDepth: "Marks a server in Plan database as uninstalled so that it will not show up in server queries." + disable: + description: "Disable the plugin or part of it" + inDepth: "Disable the plugin or part of it until next reload/restart." + export: + description: "Export html or json files manually" + inDepth: "Performs an export to export location defined in the config." + import: + description: "Import data" + inDepth: "Performs an import to load data into the database." + info: + description: "Information about the plugin" + inDepth: "Display the current status of the plugin." + ingame: + description: "Mostra info dei giocatori in gioco" + inDepth: "Visualizza alcune informazioni sul giocatore in gioco." + json: + description: "View json of Player's raw data." + inDepth: "Allows you to download a player's data in json format. All of it." + logout: + description: "Log out other users from the panel." + inDepth: "Give username argument to log out another user from the panel, give * as argument to log out everyone." + migrateToOnlineUuids: + description: "Migrate offline uuid data to online uuids" + network: + description: "Mostra la pagina del network" + inDepth: "Obtain a link to the /network page, only does so on networks." + player: + description: "Mostra la pagina di un giocatore" + inDepth: "Obtain a link to the /player page of a specific player, or the current player." + players: + description: "Mostra la pagina dei giocatori" + inDepth: "Obtain a link to the /players page to see a list of players." + register: + description: "Registra un nuovo utente web" + inDepth: "Use without arguments to get link to register page. Use --code [code] after registration to get a user." + reload: + description: "Ricarica Plan" + inDepth: "Disable and enable the plugin to reload any changes in config." + removejoinaddresses: + description: "Remove join addresses of a specified server" + search: + description: "Cerca il nome di un giocatore" + inDepth: "List all matching player names to given part of a name." + server: + description: "Mostra la pagina del server" + inDepth: "Obtain a link to the /server page of a specific server, or the current server if no arguments are given." + servers: + description: "Elenca i server nel Database" + inDepth: "List ids, names and uuids of servers in the database." + unregister: + description: "Unregister a user of Plan website" + inDepth: "Use without arguments to unregister player linked user, or with username argument to unregister another user." + users: + description: "List all web users" + inDepth: "Lists web users as a table." + ingame: + activePlaytime: " ยง2Active Playtime: ยงf${0}" + activityIndex: " ยง2Indice Attivitร : ยงf${0} | ${1}" + afkPlaytime: " ยง2AFK Time: ยงf${0}" + deaths: " ยง2Morti: ยงf${0}" + geolocation: " ยง2Loggato da: ยงf${0}" + lastSeen: " ยง2Ultima visita: ยงf${0}" + longestSession: " ยง2Sessione piรน lunga: ยงf${0}" + mobKills: " ยง2Mob Uccisi: ยงf${0}" + playerKills: " ยง2Giocatori Uccisi: ยงf${0}" + playtime: " ยง2Tempo di Gioco: ยงf${0}" + registered: " ยง2Registrato: ยงf${0}" + timesKicked: " ยง2Espulso: ยงf${0}" + link: + clickMe: "Cliccami" + link: "Link" + network: "Network page: " + noNetwork: "Server is not connected to a network. The link redirects to server page." + player: "Player page: " + playerJson: "Player json: " + players: "Players page: " + register: "Register page: " + server: "Server page: " + subcommand: + info: + database: " ยง2Database corrente: ยงf${0}" + proxy: " ยง2Connesso al Proxy: ยงf${0}" + update: " ยง2Aggiornamento Disponibile: ยงf${0}" + version: " ยง2Versione: ยงf${0}" +generic: + noData: "Nessun Dato" +html: + button: + nightMode: "Modalitร  Notte" + calendar: + new: "Nuovo:" + unique: "Unico:" + description: + newPlayerRetention: "This value is a prediction based on previous players." + noGameServers: "Some data requires Plan to be installed on game servers." + noGeolocations: "Geolocation gathering needs to be enabled in the config (Accept GeoLite2 EULA)." + noServerOnlinActivity: "Nessun server con cui mostare attivitร " + noServers: "Nessun server trovato in questo database" + noServersLong: 'It appears that Plan is not installed on any game servers or not connected to the same database. See wiki for Network tutorial.' + noSpongeChunks: "Chunks unavailable on Sponge" + predictedNewPlayerRetention: "Questo valore รจ una previsione basata sui giocatori precedenti" + error: + 401Unauthorized: "Non Autorizzato" + 403Forbidden: "Vietato" + 404NotFound: "Non Trovato" + 404PageNotFound: "Questa pagina non esiste." + 404UnknownPage: "Assicurati che stai seguendo il link usando il commando, Esempi:

/player/{uuid/name}
/server/{uuid/name/id}

" + UUIDNotFound: "UUID del Giocatore non trovato nel database." + auth: + dbClosed: "Database non trovato, controlla il database con /plan info" + emptyForm: "Utente e Password non specificati" + expiredCookie: "User cookie has expired" + generic: "Autenticazione fallita a causa di un errore" + loginFailed: "Utente e Password non corrispondono" + noCookie: "User cookie not present" + registrationFailed: "Registration failed, try again (The code expires after 15 minutes)" + userNotFound: "Utente non esistente" + authFailed: "Autenticazione Fallita." + authFailedTips: "- Assicurati di registare un nuovo utente usando /plan register
- Controlla che il nome e password siano corretti
- Nome e password sono case-sensitive

Se hai dimenticato la tua passwrod, chiedi a uno staff di rimuovere i tuoi dati e registrarti di nuovo." + noServersOnline: "Nessun Server online per fare la richiesta." + playerNotSeen: "Questo giocatore non ha mai giocato su questo server." + serverNotSeen: "Server doesn't exist" + generic: + none: "Nessuno" + label: + active: "Attivo" + activePlaytime: "Active Playtime" + activityIndex: "Indice Inattivitร " + afk: "AFK" + afkTime: "Tempo AFK" + all: "Tutto" + allTime: "Tutto il Tempo" + alphabetical: "Alphabetical" + apply: "Apply" + asNumbers: "Statistiche" + average: "Media" + averageActivePlaytime: "Average Active Playtime" + averageAfkTime: "Average AFK Time" + averageChunks: "Average Chunks" + averageCpuUsage: "Average CPU Usage" + averageEntities: "Average Entities" + averageKdr: "Media KDR" + averageMobKdr: "Media Mob KDR" + averagePing: "Ping Medio" + averagePlayers: "Average Players" + averagePlaytime: "Media Tempo di Gioco" + averageRamUsage: "Average RAM Usage" + averageServerDowntime: "Average Downtime / Server" + averageSessionLength: "Media Lunghezza Sessione" + averageSessions: "Average Sessions" + averageTps: "Media TPS" + averageTps7days: "Average TPS (7 days)" + banned: "Bannato" + bestPeak: "Record Migliore" + bestPing: "Ping Migliore" + calendar: " Calendario" + comparing7days: "Comparazione di 7 giorni" + connectionInfo: "Informazioni sulla Connessione" + country: "Nazione" + cpu: "CPU" + cpuRam: "CPU & RAM" + cpuUsage: "CPU Usage" + currentPlayerbase: "Playerbase Corrente" + currentUptime: "Current Uptime" + dayByDay: "Giorno Per Giorno" + dayOfweek: "Giorno della Settimana" + deadliestWeapon: "Arma PvP Preferita" + deaths: "Morti" + disk: "Spazio sul Disco" + diskSpace: "Spazio Disco Disponibile" + downtime: "Downtime" + duringLowTps: "Durante Spicchi TPS bassi:" + entities: "Entitร " + favoriteServer: "Server Preferito" + firstSession: "Prima sessione" + firstSessionLength: + average: "Average first session length" + median: "Median first session length" + geoProjection: + dropdown: "Select projection" + equalEarth: "Equal Earth" + mercator: "Mercator" + miller: "Miller" + ortographic: "Ortographic" + geolocations: "Geolocalizazione" + hourByHour: "Hour by Hour" + inactive: "Inattivo" + indexInactive: "Inattivo" + indexRegular: "Regolare" + information: "INFORMAZIONE" + insights: "Insights" + insights30days: "Grafico in 30 giorni" + irregular: "Irregolare" + joinAddress: "Join Address" + joinAddresses: "Join Addresses" + kdr: "KDR" + killed: "Ucciso" + last24hours: "Ultime 24 ore" + last30days: "Ultimi 30 giorni" + last7days: "Ultimi 7 giorni" + lastConnected: "Ultima connessione" + lastPeak: "Record Settimanale" + lastSeen: "Ultima Visita" + latestJoinAddresses: "Latest Join Addresses" + length: " Durata" + links: "LINKS" + loadedChunks: "Chunks Caricati" + loadedEntities: "Entitร  Caricate" + loneJoins: "Entrate Solitarie" + loneNewbieJoins: "Nuove entrate Solitarie" + longestSession: "Sessione piรน lunga" + lowTpsSpikes: "Spicchi TPS bassi" + lowTpsSpikes7days: "Low TPS Spikes (7 days)" + maxFreeDisk: "Spazio Disco libero Max" + medianSessionLength: "Median Session Length" + minFreeDisk: "Spazio Disco libero Min" + mobDeaths: "Mob che hanno causato la Morte" + mobKdr: "Mob KDR" + mobKills: "Mob uccisi" + mostActiveGamemode: "La Gamemode piรน attiva" + mostPlayedWorld: "Mondo piรน giocato" + name: "Nome" + network: "Network" + networkAsNumbers: "Statistiche Network" + networkOnlineActivity: "Attivitร  Online Network" + networkOverview: "Informazioni sul Network" + networkPage: "Pagina del Network" + new: "Nuovi" + newPlayerRetention: "Retenzione Nuovo Giocatore" + newPlayers: "Nuovi Giocatori" + newPlayers7days: "New Players (7 days)" + nickname: "Nick" + noDataToDisplay: "No Data to Display" + now: "Ora" + onlineActivity: "Attivitร  Online" + onlineActivityAsNumbers: "Statistiche Attivitร  Online" + onlineOnFirstJoin: "Giocatori Online entrati la prima volta" + operator: "Operatore" + overview: "Informazioni" + perDay: "/ Giorno" + perPlayer: "/ Giocatore" + perRegularPlayer: "/ Giocatore regolare" + performance: "Performance" + performanceAsNumbers: "Prestazioni come numeri" + ping: "Ping" + player: "Player" + playerDeaths: "Giocatori che hanno causato la Morte" + playerKills: "Giocatori Uccisi" + playerList: "Elenco Giocatori" + playerOverview: "Statistiche Giocatore" + playerPage: "Pagina Giocatore" + playerRetention: "Player Retention" + playerbase: "Playerbase" + playerbaseDevelopment: "Grafico Playerbase" + playerbaseOverview: "Playerbase Overview" + players: "Giocatori" + playersOnline: "Giocatori Online" + playersOnlineNow: "Players Online (Now)" + playersOnlineOverview: "Panoramica delle attivitร  online" + playtime: "Gioco" + plugins: "Plugins" + pluginsOverview: "Plugins Overview" + punchcard: "Presenza Settimanale" + punchcard30days: "Attivitร  in 30 giorni" + pvpPve: "PvP & PvE" + pvpPveAsNumbers: "Statistiche PvP & PvE" + query: "Make a query" + quickView: "Vista Rapida" + ram: "RAM" + ramUsage: "RAM Usage" + recentKills: "Uccisioni Recenti" + recentPvpDeaths: "Morti PvP Recenti" + recentPvpKills: "Uccisioni PvP Recenti" + recentSessions: "Sessioni Recenti" + registered: "Registrato" + registeredPlayers: "Giocatori Registrati" + regular: "Regolari" + regularPlayers: "Giocatori Regolari" + relativeJoinActivity: "Attivitร  Entrate Relative" + secondDeadliestWeapon: "2ยฐ Arma PvP Preferita" + seenNicknames: "Nick Usati" + server: "Server" + serverAnalysis: "Analisi Server" + serverAsNumberse: "Statistiche Server" + serverCalendar: "Server Calendar" + serverDowntime: "Server Downtime" + serverOccupied: "Server occupato" + serverOverview: "Vista Server" + serverPage: "Pagina del Server" + serverPlaytime: "Tempo di Gioco Server" + serverPlaytime30days: "Tempo di Gioco Server di 30 giorni" + serverSelector: "Server selector" + servers: "Servers" + serversTitle: "SERVERS" + session: "Session" + sessionCalendar: "Session Calendar" + sessionEnded: " Fine" + sessionMedian: "Media Sessioni" + sessionStart: "Sessione Iniziata" + sessions: "Sessioni" + sortBy: "Sort By" + stacked: "Stacked" + themeSelect: "Selezione Tema" + thirdDeadliestWeapon: "3ยฐ Arma PvP Preferita" + thirtyDays: "30 giorni" + thirtyDaysAgo: "30 giorni fa" + timesKicked: "Cacciato" + toMainPage: "Ritorna alla pagina principale" + total: "Total" + totalActive: "Totale Attivo" + totalAfk: "Totale AFK" + totalPlayers: "Giocatori Totali" + totalPlayersOld: "Totale Giocatori" + totalPlaytime: "Tempo di gioco Totale" + totalServerDowntime: "Total Server Downtime" + tps: "TPS" + trend: "Tendenza" + trends30days: "Tendenza per 30 giorni" + uniquePlayers: "Giocatori unici" + uniquePlayers7days: "Unique Players (7 days)" + veryActive: "Molto Attivo" + weekComparison: "Confronto settimanale" + weekdays: "'Lunedรฌ', 'Martedรฌ', 'Mercoledรฌ', 'Giovedรฌ', 'Venerdรฌ', 'Sabato', 'Domenica'" + world: "Caricamento Mondo" + worldPlaytime: "Tempo di gioco Mondo" + worstPing: "Ping Peggiore" + login: + failed: "Login failed: " + forgotPassword: "Forgot Password?" + forgotPassword1: "Forgot password? Unregister and register again." + forgotPassword2: "Use the following command in game to remove your current user:" + forgotPassword3: "Or using console:" + forgotPassword4: "After using the command, " + login: "Login" + logout: "Logout" + password: "Password" + register: "Create an Account!" + username: "Username" + modal: + info: + bugs: "Segnala Problemi" + contributors: + bugreporters: "& Bug reporters!" + code: "contributori codice" + donate: "Un ringraziamento speciale a coloro che hanno sostenuto monetariamente lo sviluppo." + text: 'Inoltre, hanno contribuito queste fantastiche persone:' + translator: "traduttore" + developer: "รจ stato svillupato da" + discord: "Supporto generale su Discord" + license: "Player Analytics รจ stato svillpato e licenziato sotto" + metrics: "Metriche bStats" + text: "Informazioni su questo plugin" + wiki: "Plan Wiki, Tutorial e Documentazione" + version: + available: "รจ Disponibile" + changelog: "Vedi aggiornamenti" + dev: "Questa versione รจ una versione DEV." + download: "Download" + text: "Una nuova versione รจ stata rilasciata ed รจ ora disponibile da scaricare." + title: "Versione" + query: + filter: + activity: + text: "are in Activity Groups" + banStatus: + name: "Ban status" + banned: "Banned" + country: + text: "have joined from country" + generic: + allPlayers: "All players" + and: "and " + start: "of Players who " + joinAddress: + text: "joined with address" + nonOperators: "Non operators" + notBanned: "Not banned" + operatorStatus: + name: "Operator status" + operators: "Operators" + playedBetween: + text: "Played between" + pluginGroup: + name: "Group: " + text: "are in ${plugin}'s ${group} Groups" + registeredBetween: + text: "Registered between" + title: + activityGroup: "Current activity group" + view: " View:" + filters: + add: "Add a filter.." + loading: "Loading filters.." + generic: + are: "`are`" + label: + from: ">from" + makeAnother: "Make another query" + to: ">to" + view: "Show a view" + performQuery: "Perform Query!" + results: + match: "matched ${resultCount} players" + none: "Query produced 0 results" + title: "Query Results" + title: + activity: "Activity of matched players" + activityOnDate: 'Activity on ' + sessionsWithinView: "Sessions within view" + text: "Query<" + register: + completion: "Complete Registration" + completion1: "You can now finish registering the user." + completion2: "Code expires in 15 minutes" + completion3: "Use the following command in game to finish registration:" + completion4: "Or using console:" + createNewUser: "Create a new user" + error: + checkFailed: "Checking registration status failed: " + failed: "Registration failed: " + noPassword: "You need to specify a Password" + noUsername: "You need to specify a Username" + usernameLength: "Username can be up to 50 characters, yours is " + login: "Already have an account? Login!" + passwordTip: "Password should be more than 8 characters, but there are no limitations." + register: "Register" + usernameTip: "Username can be up to 50 characters." + text: + clickToExpand: "Clicca per espendere" + comparing15days: "Comparazione di 15 giorni" + comparing30daysAgo: "Comparazione di 30g fa a Ora" + noExtensionData: "Nessun dato estensione" + noLowTps: "Nennun picco basso di TPS" + unit: + chunks: "Chunks" + players: "Giocatori" + value: + localMachine: "Macchina Locale" + noKills: "Nessuna Uccisione" + offline: " Offline" + online: " Online" + with: "Con" + version: + changelog: "View Changelog" + current: "You have version ${0}" + download: "Download Plan-${0}.jar" + isDev: "This version is a DEV release." + updateButton: "Update" + updateModal: + text: "A new version has been released and is now available for download." + title: "Version ${0} is Available!" +plugin: + apiCSSAdded: "PageExtension: ${0} added stylesheet(s) to ${1}, ${2}" + apiJSAdded: "PageExtension: ${0} added javascript(s) to ${1}, ${2}" + disable: + database: "Elaborazione di attivitร  critiche non elaborate. (${0})" + disabled: "Analisi Giocatori Disabilitato." + processingComplete: "Elaborazione completata." + savingSessions: "Salvataggio di sessioni incompiute.." + savingSessionsTimeout: "Timeout hit, storing the unfinished sessions on next enable instead." + waitingDb: "Waiting queries to finish to avoid SQLite crashing JVM.." + waitingDbComplete: "Closed SQLite connection." + waitingTransactions: "Waiting for unfinished transactions to avoid data loss.." + waitingTransactionsComplete: "Transaction queue closed." + webserver: "Il Webserver รจ stato disabilitato." + enable: + database: "${0}-Connessione al database stabilita." + enabled: "Analisi Giocatori Abilitata." + fail: + database: "${0}-Connessione al Database fallita: ${1}" + databasePatch: "Upgrade del Database fallito, plan รจ stato disabilitato. Si prega di segnalare questo problema." + databaseType: "${0} non รจ un Database supportato" + geoDBWrite: "Qualcosa รจ andato storto durante lo scaricamento del database GeoLite2" + webServer: "WebServer non avviato!" + notify: + badIP: "0.0.0.0 is not a valid address, set up Alternative_IP settings. Incorrect links might be given!" + emptyIP: "L'IP in server.properties รจ vuoto e Alternative_IP non รจ in uso. Correggi queste informazioni!" + geoDisabled: "Geolocalizazione non attiva. (Data.Geolocations: false)" + geoInternetRequired: "Plan Richiede l'accesso a Internet al primo avvio per scaricare il database di geolocalizzazione GeoLite2." + storeSessions: "Storing sessions that were preserved before previous shutdown." + webserverDisabled: "WebServer non avviato. (WebServer.DisableWebServer: true)" + webserver: "Webserver avviato sulla PORTA ${0} ( ${1} )" + generic: + dbApplyingPatch: "Applicando Patch: ${0}.." + dbFaultyLaunchOptions: "Le opzioni di Lancio sono errate, usando le opzioni default (${0})" + dbNotifyClean: "Rimossi i dati di ${0} giocatorri." + dbNotifySQLiteWAL: "Modalitร  SQLite WAL non supportato da questa versione del server, uso le impostazioni predefinite. Ciรฒ potrebbe causare dei problemi." + dbPatchesAlreadyApplied: "Tutte le patch sono giร  state applicate." + dbPatchesApplied: "Tutte le patch sono satte applicate al database." + dbSchemaPatch: "Database: Making sure schema is up to date.." + loadedServerInfo: "Server identifier loaded: ${0}" + loadingServerInfo: "Loading server identifying information" + no: "No" + today: "'Oggi'" + unavailable: "Non Disponibile" + unknown: "Sconosciuto" + yes: "Sรฌ" + yesterday: "'Ieri'" + version: + checkFail: "Impossibile controllare nuove versioni" + checkFailGithub: "Informazioni sulla vesione non trovate su Github/versions.txt" + isDev: " Questa รจ una versione DEV." + isLatest: "Stai usando l'ultima versione." + updateAvailable: "Nuova versione (${0}) รจ disponibile ${1}" + updateAvailableSpigot: "Nuova versione disponibile su ${0}" + webserver: + fail: + SSLContext: "WebServer: Inilizazione Contenuto SSL Fallito." + certFileEOF: "WebServer: EOF when reading Certificate file. (Check that the file is not empty)" + certStoreLoad: "WebServer: Caricamento Certificato SSL Fallito." + portInUse: "WebServer non avviato. La porta (${0}) รจ giร  occupata?" + notify: + authDisabledConfig: "WebServer: User Authorization Disabled! (Disabled in config)" + authDisabledNoHTTPS: "WebServer: Autorizzazione Utente Disabilitato! (Non sicuro su HTTP)" + certificateExpiresOn: "Webserver: Loaded certificate is valid until ${0}." + certificateExpiresPassed: "Webserver: Certificate has expired, consider renewing the certificate." + certificateExpiresSoon: "Webserver: Certificate expires in ${0}, consider renewing the certificate." + certificateNoSuchAlias: "Webserver: Certificate with alias '${0}' was not found inside the keystore file '${1}'." + http: "WebServer: Nessun Certificato -> Uso ora server HTTP per la Visualizazione." + ipWhitelist: "Webserver: IP Whitelist is enabled." + ipWhitelistBlock: "Webserver: ${0} was denied access to '${1}'. (not whitelisted)" + noCertFile: "WebServer: Chiave del Certfiicato non trovato: ${0}" + reverseProxy: "WebServer: Proxy-mode HTTPS enabled, make sure that your reverse-proxy is routing using HTTPS and Plan Alternative_IP.Address points to the Proxy" diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_JA.txt b/Plan/common/src/main/resources/assets/plan/locale/locale_JA.txt deleted file mode 100644 index 114f29ed8..000000000 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_JA.txt +++ /dev/null @@ -1,517 +0,0 @@ -API - css+ || ใƒšใƒผใ‚ธๆ‹กๅผตๆฉŸ่ƒฝ: ใ€Œ${0}ใ€ใŒใ€Œ${1}ใ€,ใ€Œ${2}ใ€ใซใ‚นใ‚ฟใ‚คใƒซใ‚ทใƒผใƒˆใ‚’่ฟฝๅŠ  -API - js+ || ใƒšใƒผใ‚ธๆ‹กๅผตๆฉŸ่ƒฝ: ใ€Œ${0}ใ€ใŒใ€Œ${1}ใ€,ใ€Œ${2}ใ€ใซJavascriptใฎๆฉŸ่ƒฝใ‚’่ฟฝๅŠ  -Cmd - Click Me || ใ“ใ“ใ‚’ใ‚ฏใƒชใƒƒใ‚ฏ -Cmd - Link || ใƒชใƒณใ‚ฏ -Cmd - Link Network || ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏใƒšใƒผใ‚ธ: -Cmd - Link Player || ใƒ—ใƒฌใ‚คใƒคใƒผใƒšใƒผใ‚ธ: -Cmd - Link Player JSON || ใƒ—ใƒฌใ‚คใƒคใƒผใฎjson: -Cmd - Link Players || ใƒ—ใƒฌใ‚คใƒคใƒผใƒšใƒผใ‚ธ: -Cmd - Link Register || ็™ป้Œฒใƒšใƒผใ‚ธ: -Cmd - Link Server || ใ‚ตใƒผใƒใƒผใƒšใƒผใ‚ธ: -CMD Arg - backup-file || ใƒใƒƒใ‚ฏใ‚ขใƒƒใƒ—ใƒ•ใ‚กใ‚คใƒซๅ (ๅคงๆ–‡ๅญ—ใจๅฐๆ–‡ๅญ—ใฏๅŒบๅˆฅใ•ใ‚Œใพใ™) -CMD Arg - code || ็™ป้Œฒใ‚’ๅฎŒไบ†ใ•ใ›ใ‚‹ใŸใ‚ใซๅฟ…่ฆใชใ‚ณใƒผใƒ‰ -CMD Arg - db type backup || ใƒใƒƒใ‚ฏใ‚ขใƒƒใƒ—ใ™ใ‚‹ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ€็ฉบๆฌ„ใฎๅ ดๅˆใฏ็พๅœจใฎใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใŒๆŒ‡ๅฎšใ•ใ‚Œใพใ™ -CMD Arg - db type clear || ๅ…จใƒ‡ใƒผใ‚ฟใ‚’ๅ‰Š้™คใ™ใ‚‹ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚น -CMD Arg - db type hotswap || ไฝฟ็”จใ™ใ‚‹ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚น -CMD Arg - db type move from || ใƒ‡ใƒผใ‚ฟ็งป่กŒๅ…ƒใฎใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚น -CMD Arg - db type move to || ใƒ‡ใƒผใ‚ฟ็งป่กŒๅ…ˆใฎใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ€ไปฅๅ‰ใฎใ‚ณใƒžใƒณใƒ‰ใจๅŒใ˜็‰ฉใ‚’ไฝฟ็”จใ™ใ‚‹ใ“ใจใฏใงใใพใ›ใ‚“ -CMD Arg - db type restore || ใƒ‡ใƒผใ‚ฟใ‚’ใƒชใ‚นใƒˆใ‚ขใ™ใ‚‹ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ€็ฉบๆฌ„ใฎๅ ดๅˆใฏ็พๅœจใฎใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใŒๆŒ‡ๅฎšใ•ใ‚Œใพใ™ -CMD Arg - feature || ใ€Œ${0}ใ€ใ‚’็„กๅŠนๅŒ–ใ™ใ‚‹ๆฉŸ่ƒฝใฎๅๅ‰ -CMD Arg - player identifier || ใƒ—ใƒฌใ‚คใƒคใƒผใฎๅๅ‰ใ‚‚ใ—ใใฏUUID -CMD Arg - player identifier remove || ็พๅœจใฎใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ‹ใ‚‰ๅ‰Š้™คใ•ใ‚Œใ‚‹ใƒ—ใƒฌใ‚คใƒคใƒผใฎ่ญ˜ๅˆฅๅญ -CMD Arg - server identifier || ใ‚ตใƒผใƒใƒผใฎๅๅ‰ใ‚‚ใ—ใใฏIDใ€UUID -CMD Arg - subcommand || ใ‚ณใƒžใƒณใƒ‰ใ‚’ใ‚ตใƒ–ใ‚ณใƒžใƒณใƒ‰็„กใ—ใงๅฎŸ่กŒใ™ใ‚‹ใ“ใจใงใƒ˜ใƒซใƒ—ใ‚’่ฆ‹ใ‚‹ใ“ใจใŒๅฏ่ƒฝใงใ™ -CMD Arg - username || ไป–ใฎใƒฆใƒผใ‚ถใƒผๅใ€็ฉบๆฌ„ใฎๅ ดๅˆใฏใƒชใƒณใ‚ฏใ•ใ‚Œใฆใ„ใ‚‹ใƒ—ใƒฌใ‚คใƒคใƒผๅใŒๆŒ‡ๅฎšใ•ใ‚Œใพใ™ -CMD Arg Name - backup-file || ใƒใƒƒใ‚ฏใ‚ขใƒƒใƒ—ใƒ•ใ‚กใ‚คใƒซ -CMD Arg Name - code || ${code} -CMD Arg Name - export kind || ใ‚จใ‚ฏใ‚นใƒใƒผใƒˆ็ณป -CMD Arg Name - feature || ๆฉŸ่ƒฝ -CMD Arg Name - import kind || ใ‚คใƒณใƒใƒผใƒˆ็ณป -CMD Arg Name - name or uuid || ๅๅ‰/uuid -CMD Arg Name - server || ใ‚ตใƒผใƒใƒผ -CMD Arg Name - subcommand || ใ‚ตใƒ–ใ‚ณใƒžใƒณใƒ‰ -CMD Arg Name - username || ใƒฆใƒผใ‚ถใƒผใƒใƒผใƒ  -Cmd Confirm - accept || ็ขบๅฎš -Cmd Confirm - cancelled, no data change || ใ‚ญใƒฃใƒณใ‚ปใƒซใ•ใ‚Œใพใ—ใŸใ€‚ใƒ‡ใƒผใ‚ฟใฎๅค‰ๆ›ดใฏ่กŒใ‚ใ‚Œใพใ›ใ‚“ใ€‚ -Cmd Confirm - cancelled, unregister || ใ‚ญใƒฃใƒณใ‚ปใƒซใ•ใ‚Œใพใ—ใŸใ€‚ใ€Œ'${0}'ใ€ใฏ็™ป้Œฒๆธˆใฟใงใ™ใ€‚ -Cmd Confirm - clearing db || ใ€Œ${0}ใ€ๅ†…ใซๅญ˜ๅœจใ™ใ‚‹Planใฎใƒ‡ใƒผใ‚ฟใ‚’ๅ‰Š้™คใ—ใ‚ˆใ†ใจใ—ใฆใ„ใพใ™ -Cmd Confirm - confirmation || ็ขบ่ช: -Cmd Confirm - deny || ใ‚ญใƒฃใƒณใ‚ปใƒซ -Cmd Confirm - Expired || ๆœ‰ๅŠนๆœŸ้™ๅˆ‡ใ‚ŒใฎใŸใ‚ใ€ๅ†ๅบฆใ‚ณใƒžใƒณใƒ‰ใ‚’ไฝฟ็”จใ—ใฆใใ ใ•ใ„ -Cmd Confirm - Fail on accept || ็ขบๅฎšไธญใซไปฅไธ‹ใฎใ‚จใƒฉใƒผใŒ็™บ็”Ÿใ—ใพใŸ: ${0} -Cmd Confirm - Fail on deny || ใ‚ญใƒฃใƒณใ‚ปใƒซไธญใซไปฅไธ‹ใฎใ‚จใƒฉใƒผใŒ็™บ็”Ÿใ—ใพใŸ: ${0} -Cmd Confirm - overwriting db || Planใฎใƒ‡ใƒผใ‚ฟใ€Œ${0}ใ€ใ‚’ใ€Œ${1}ใ€ใงไธŠๆ›ธใใ—ใ‚ˆใ†ใจใ—ใฆใ„ใพใ™ -Cmd Confirm - remove player db || ใ€Œ${0}ใ€ใ‚’ใ€Œ${1}ใ€ใ‹ใ‚‰ๅ‰Š้™คใ—ใ‚ˆใ†ใจใ—ใฆใ„ใพใ™ -Cmd Confirm - unregister || ใ€Œ${1}ใ€ใซใƒชใƒณใ‚ฏใ•ใ‚Œใฆใ„ใ‚‹ใ€Œ${0}ใ€ใ‚’่งฃ้™คใ—ใ‚ˆใ†ใจใ—ใฆใ„ใพใ™ -Cmd db - creating backup || ใ€Œ${1}ใ€ใฎใƒ‡ใƒผใ‚ฟใ‚’ๅซใ‚€ใƒใƒƒใ‚ฏใ‚ขใƒƒใƒ—ใ€Œ${0}.dbใ€ใ‚’ไฝœๆˆไธญ -Cmd db - removal || ใ€Œ${0}ใ€ใ‹ใ‚‰Planใฎใƒ‡ใƒผใ‚ฟใ‚’ๅ‰Š้™คไธญ.. -Cmd db - removal player || ใ€Œ${1}ใ€ใ‹ใ‚‰ใ€Œ${0}ใ€ใฎใƒ‡ใƒผใ‚ฟใ‚’ๅ‰Š้™คไธญ.. -Cmd db - server uninstalled || ยงaใ‚ตใƒผใƒใƒผใŒใพใ ่ฟฝๅŠ ไธญใฎๅ ดๅˆใ€ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใซ่ฟฝๅŠ ๆธˆใฟใจใ—ใฆ่กจ็คบใ•ใ‚Œใพใ™ -Cmd db - write || ใ€Œ${0}ใ€ใซๆ›ธใ่พผใฟไธญใงใ™.. -Cmd Disable - Disabled || ยงaใ€ŒPlanใ€ใฏ็„กๅŠนใซใชใ‚Šใพใ—ใŸใ€‚ใ€Œreloadใ€ใ‚ณใƒžใƒณใƒ‰ใ‚’ไฝฟใฃใฆใƒ—ใƒฉใ‚ฐใ‚คใƒณใ‚’ๅ†่ตทๅ‹•ใงใใพใ™ -Cmd FAIL - Accepts only these arguments || ใ€Œ${0}ใ€ใจใ—ใพใ™ใ€‚ใ€Œ${1}ใ€ -Cmd FAIL - Database not open || ยงcใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใฏ${0}ใงใ™ - ใ—ใฐใ‚‰ใใ—ใฆใ‹ใ‚‰ใ‚‚ใ†ไธ€ๅบฆใŠ่ฉฆใ—ไธ‹ใ•ใ„ -Cmd FAIL - Empty search string || ๆคœ็ดขๆฌ„ใฏ็ฉบๆฌ„ใซใงใใพใ›ใ‚“ -Cmd FAIL - Invalid Username || ยงcใ“ใฎใƒฆใƒผใ‚ถใƒผใฏUUIDใ‚’ๆ‰€ๆŒใ—ใฆใ„ใพใ›ใ‚“ -Cmd FAIL - No Feature || ยงeใ“ใฎๆฉŸ่ƒฝใฏ็พๅœจไฝฟ็”จใ•ใ‚Œใฆใ„ใพใ›ใ‚“๏ผ (็พๅœจใ€ใ€Œ${0}ใ€ใ‚’ใ‚ตใƒใƒผใƒˆใ—ใฆใ„ใพใ™) -Cmd FAIL - No Permission || ยงcใ‚ใชใŸใซใฏๅฎŸ่กŒใ™ใ‚‹ๆจฉ้™ใŒใ‚ใ‚Šใพใ›ใ‚“ -Cmd FAIL - No player || ใƒ—ใƒฌใ‚คใƒคใƒผใ€Œ${0}ใ€ใฏUUIDใŒ็„กใ„ใŸใ‚่ฆ‹ใคใ‘ใ‚‹ใ“ใจใŒใงใใพใ›ใ‚“ใงใ—ใŸ -Cmd FAIL - No player register || ใƒ—ใƒฌใ‚คใƒคใƒผใ€Œ${0}ใ€ใฏใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นๅ†…ใซใฏๅญ˜ๅœจใ—ใพใ›ใ‚“ -Cmd FAIL - No server || ใ‚ตใƒผใƒใƒผใ€Œ${0}ใ€ใฏใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นๅ†…ใซใฏๅญ˜ๅœจใ—ใพใ›ใ‚“ -Cmd FAIL - Require only one Argument || ยงcไธ€ใคใฎๅผ•ๆ•ฐใ€Œ${1}ใ€ใŒๅฟ…่ฆใงใ™ -Cmd FAIL - Requires Arguments || ยงcๅผ•ๆ•ฐใ€Œ(${0}) ${1}ใ€ใŒๅฟ…่ฆใงใ™ -Cmd FAIL - see config || ใ€Œconfig.ymlใ€ๅ†…ใฎใ€Œ${0}ใ€ใ‚’ๅ‚็…งใ—ใฆใใ ใ•ใ„ -Cmd FAIL - Unknown Username || ยงcๅ…ฅๅŠ›ใ•ใ‚ŒใŸใƒฆใƒผใ‚ถใƒผใฏBukkit/Spigotใ‚ตใƒผใƒใƒผไธŠใซใ„ใพใ›ใ‚“ -Cmd FAIL - Users not linked || ๅ…ฅๅŠ›ใ•ใ‚ŒใŸใƒฆใƒผใ‚ถใƒผใฏใ‚ใชใŸใฎใ‚ขใ‚ซใ‚ฆใƒณใƒˆใซใƒชใƒณใ‚ฏใ•ใ‚Œใฆใ„ใชใ„ใŸใ‚ใ€ใใ‚Œใ‚’ๅ‰Š้™คใ™ใ‚‹ๆจฉ้™ใฏใ‚ใ‚Šใพใ›ใ‚“ -Cmd FAIL - WebUser does not exists || ยงcๅ…ฅๅŠ›ใ•ใ‚ŒใŸใƒฆใƒผใ‚ถใƒผใฏๅญ˜ๅœจใ—ใพใ›ใ‚“! -Cmd FAIL - WebUser exists || ยงcๅ…ฅๅŠ›ใ•ใ‚ŒใŸใƒฆใƒผใ‚ถใƒผๅใฏๆ—ขใซไฝฟใ‚ใ‚Œใฆใ„ใพใ™! -Cmd Footer - Help || ยง7ใ‚ณใƒžใƒณใƒ‰ใ‚„ๅผ•ๆ•ฐใฎไธŠใซใ‚ซใƒผใ‚ฝใƒซใ‚’ๅˆใ‚ใ›ใ‚‹ใ‹ใ€Œ/${0} ?ใ€ใ‚’ไฝฟ็”จใ™ใ‚‹ใ“ใจใง่ฉณ็ดฐใ‚’็ขบ่ชใงใใพใ™ -Cmd Header - Analysis || > ยง2ๅˆ†ๆž็ตๆžœ -Cmd Header - Help || > ยง2/${0}ใฎ่ฉณ็ดฐ -Cmd Header - Info || > ยง2ใƒ—ใƒฌใ‚คใƒคใƒผใฎๅˆ†ๆž็ตๆžœ -Cmd Header - Inspect || > ยง2ใƒ—ใƒฌใ‚คใƒคใƒผ: ยงf${0} -Cmd Header - Network || > ยง2ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏใƒšใƒผใ‚ธ -Cmd Header - Players || > ยง2ใƒ—ใƒฌใ‚คใƒคใƒผ -Cmd Header - Search || > ยง2${0} ยงf${1}ยง2 ใฎ็ตๆžœ: -Cmd Header - server list || Minecraft id::ๅๅ‰::uuid -Cmd Header - Servers || > ยง2ใ‚ตใƒผใƒใƒผ -Cmd Header - web user list || ใƒฆใƒผใ‚ถใƒผๅ::ๅˆฅใƒ—ใƒฌใ‚คใƒคใƒผใจใฎใƒชใƒณใ‚ฏ::ๆจฉ้™ใƒฌใƒ™ใƒซ -Cmd Header - Web Users || > ยง2${0} ใ‚ฆใ‚งใƒ–ใƒฆใƒผใ‚ถใƒผ -Cmd Info - Bungee Connection || ยง2BungeeCordใซๆŽฅ็ถšๆธˆใฟ: ยงf${0} -Cmd Info - Database || ยง2็พๅœจใฎใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚น: ยงf${0} -Cmd Info - Reload Complete || ยงaใƒชใƒญใƒผใƒ‰ใŒๅฎŒไบ†ใ—ใพใ—ใŸ -Cmd Info - Reload Failed || ยงcใƒ—ใƒฉใ‚ฐใ‚คใƒณใฎใƒชใƒญใƒผใƒ‰ไธญใซไฝ•ใ‚‰ใ‹ใฎๅ•้กŒใŒ็™บ็”Ÿใ—ใพใ—ใŸใ€Bukkit/Spigotใ‚ตใƒผใƒใƒผใ‹BungeeCordใฎๅ†่ตทๅ‹•ใ‚’ใŠๅ‹งใ‚ใ—ใพใ™ -Cmd Info - Update || ยง2ๅˆฉ็”จๅฏ่ƒฝใชใ‚ขใƒƒใƒ—ใƒ‡ใƒผใƒˆ: ยงf${0} -Cmd Info - Version || ยง2ใƒใƒผใ‚ธใƒงใƒณ: ยงf${0} -Cmd network - No network || ใ‚ตใƒผใƒใƒผใŒใƒใƒƒใƒˆใƒฏใƒผใ‚ฏใซๆŽฅ็ถšใ•ใ‚Œใฆใ„ใพใ›ใ‚“ใ€‚ใƒชใƒณใ‚ฏใฏใ‚ตใƒผใƒใƒผใƒกใƒผใ‚ธใซใƒชใƒ€ใ‚คใƒฌใ‚ฏใƒˆใ•ใ‚Œใพใ™ -Cmd Notify - No Address || ยงe่จญๅฎšใ—ใŸใ‚ขใƒ‰ใƒฌใ‚นใŒๅˆฉ็”จใงใใพใ›ใ‚“ใงใ—ใŸ - ใƒ•ใ‚ฉใƒผใƒซใƒใƒƒใ‚ฏใจใ—ใฆlocalhostใ‚’ไฝฟ็”จใ—ใฆใ„ใพใ™ใ€‚ใ€ŒAlternative_IPใ€ใฎ่จญๅฎšใ‚’่กŒใ„ใพใ™ใ€‚ -Cmd Notify - No WebUser || ใ‚ฆใ‚งใƒ–ใƒฆใƒผใ‚ถใƒผใฎๆƒ…ๅ ฑใŒใชใ„ๅฏ่ƒฝๆ€งใŒใ‚ใ‚Šใพใ™ใ€ใ€Œ/plan register <ใƒ‘ใ‚นใƒฏใƒผใƒ‰>ใ€ใ‚’ไฝฟ็”จใ—ใฆใƒฆใƒผใ‚ถใƒผใ‚’็™ป้Œฒใ—ใฆไธ‹ใ•ใ„ -Cmd Notify - WebUser register || ็™ป้ŒฒใŒๅฎŒไบ†ใ—ใพใ—ใŸ: '${0}' ๆจฉ้™ใƒฌใƒ™ใƒซ: ${1} -Cmd Qinspect - Active Playtime || ยง2ใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–ใชใƒ—ใƒฌใ‚คๆ™‚้–“: ยงf${0} -Cmd Qinspect - Activity Index || ยง2ๆดปๅ‹•ๆŒ‡ๆ•ฐ: ยงf${0} | ${1} -Cmd Qinspect - AFK Playtime || ยง2ๆ”พ็ฝฎๆ™‚้–“: ยงf${0} -Cmd Qinspect - Deaths || ยง2ๆญปไบกๅ›žๆ•ฐ: ยงf${0} -Cmd Qinspect - Geolocation || ยง2ๆŽฅ็ถšๅœฐๅŸŸ: ยงf${0} -Cmd Qinspect - Last Seen || ยง2ๆœ€็ต‚ใƒญใ‚ฐใ‚คใƒณๆ—ฅ: ยงf${0} -Cmd Qinspect - Longest Session || ยง2ๆœ€้•ทใƒญใ‚ฐใ‚คใƒณๆ™‚้–“: ยงf${0} -Cmd Qinspect - Mob Kills || ยง2ใ‚ญใƒซใ‚ซใ‚ฆใƒณใƒˆ(ใƒขใƒ–): ยงf${0} -Cmd Qinspect - Player Kills || ยง2ใ‚ญใƒซใ‚ซใ‚ฆใƒณใƒˆ(ใƒ—ใƒฌใ‚คใƒคใƒผ): ยงf${0} -Cmd Qinspect - Playtime || ยง2ใƒ—ใƒฌใ‚คๆ™‚้–“: ยงf${0} -Cmd Qinspect - Registered || ยง2็™ป้Œฒๆ—ฅ: ยงf${0} -Cmd Qinspect - Times Kicked || ยง2ใ‚ญใƒƒใ‚ฏใ•ใ‚ŒใŸๅ›žๆ•ฐ: ยงf${0} -Cmd SUCCESS - Feature disabled || ยงaๆฌกใซใƒ—ใƒฉใ‚ฐใ‚คใƒณใŒใƒชใƒญใƒผใƒ‰ใ•ใ‚Œใ‚‹ใพใงไธ€ๆ™‚็š„ใซใ€Œ${0}ใ€ใ‚’็„กๅŠนใซใ—ใพใ—ใŸใ€‚ -Cmd SUCCESS - WebUser register || ยงaๆ–ฐ่ฆใƒฆใƒผใ‚ถใƒผใ€Œ(${0})ใ€ใฎ็™ป้ŒฒใซๆˆๅŠŸใ—ใพใ—ใŸ๏ผ -Cmd unregister - unregistering || ใ€Œ${0}ใ€ใฎ็™ป้Œฒใ‚’่งฃ้™คไธญ.. -Cmd WARN - Database not open || ยงeใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใฏ${0}ใงใ™ - ๆƒณๅฎšไปฅไธŠใฎๆ™‚้–“ใŒใ‹ใ‹ใ‚‹ใ‹ใ‚‚ใ—ใ‚Œใพใ›ใ‚“ -Cmd Web - Permission Levels || >\ยง70: ๅ…จใฆใฎใƒšใƒผใ‚ธใซใ‚ขใ‚ฏใ‚ปใ‚นใงใใพใ™\ยง71:ใ€Œ/playersใ€ใจๅ…จใฆใฎใƒ—ใƒฌใ‚คใƒคใƒผใƒšใƒผใ‚ธใซใ‚ขใ‚ฏใ‚ปใ‚นใงใใพใ™\ยง72: ใ‚ฆใ‚งใƒ–ใƒฆใƒผใ‚ถใƒผใจๅŒใ˜ใƒฆใƒผใ‚ถใƒผๅใงใƒ—ใƒฌใ‚คใƒคใƒผใƒšใƒผใ‚ธใซใ‚ขใ‚ฏใ‚ปใ‚นใงใใพใ™\ยง73+:ๆจฉ้™ใ‚’ไฟๆŒใ—ใฆใ„ใพใ›ใ‚“ -Command Help - /plan db || Planใฎใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ‚’็ฎก็†ใ—ใพใ™ -Command Help - /plan db backup || ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใฎใƒ‡ใƒผใ‚ฟใ‚’ใƒ•ใ‚กใ‚คใƒซใซใƒใƒƒใ‚ฏใ‚ขใƒƒใƒ—ใ—ใพใ™ -Command Help - /plan db clear || ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นๅ†…ใฎPlanใƒ‡ใƒผใ‚ฟใ‚’ๅ‰Š้™คใ—ใพใ™ -Command Help - /plan db hotswap || ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ‚’้ซ˜้€Ÿใงๅค‰ๆ›ดใ—ใพใ™ -Command Help - /plan db move || ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚น้–“ใงใƒ‡ใƒผใ‚ฟใ‚’็งปๅ‹•ใ—ใพใ™ -Command Help - /plan db remove || ็พๅœจไฝฟ็”จใ—ใฆใ„ใ‚‹ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ‹ใ‚‰ใƒ—ใƒฌใ‚คใƒคใƒผใƒ‡ใƒผใ‚ฟใ‚’ๅ‰Š้™คใ—ใพใ™ -Command Help - /plan db restore || ใƒ•ใ‚กใ‚คใƒซใ‹ใ‚‰ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใซใƒ‡ใƒผใ‚ฟใ‚’ๅพฉๅ…ƒใ—ใพใ™ -Command Help - /plan db uninstalled || ใ‚ตใƒผใƒใƒผใซ่จญๅฎšใ•ใ‚Œใฆใ„ใ‚‹ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ‚’ๆœช่จญๅฎšใซใ—ใพใ™ -Command Help - /plan disable || Planใ‚‚ใ—ใใฏPlanใฎไธ€้ƒจใ‚’็„กๅŠนๅŒ–ใ—ใพใ™ -Command Help - /plan export || ๆ‰‹ๅ‹•ใงHTMLใ‹Jsonใƒ•ใ‚กใ‚คใƒซใซใ‚จใ‚ฏใ‚นใƒใƒผใƒˆใ—ใพใ™ -Command Help - /plan import || ใƒ‡ใƒผใ‚ฟใ‚’ใ‚คใƒณใƒใƒผใƒˆใ—ใพใ™ -Command Help - /plan info || Planใฎๆƒ…ๅ ฑใ‚’่กจ็คบใ—ใพใ™ -Command Help - /plan ingame || ใƒ—ใƒฌใ‚คใƒคใƒผๆƒ…ๅ ฑใ‚’ใ‚ฒใƒผใƒ ๅ†…ใง่กจ็คบใ—ใพใ™ -Command Help - /plan json || Jsonใงไฟๅญ˜ใ•ใ‚Œใฆใ„ใ‚‹ใƒ—ใƒฌใ‚คใƒคใƒผใƒ‡ใƒผใ‚ฟใ‚’่กจ็คบใ—ใพใ™ -Command Help - /plan logout || Log out other users from the panel. -Command Help - /plan network || ใ€Œใƒใƒƒใƒˆใƒฏใƒผใ‚ฏใ€ใฎใƒšใƒผใ‚ธใฎURLใ‚’่กจ็คบใ—ใพใ™ -Command Help - /plan player || ใ€Œใƒ—ใƒฌใ‚คใƒคใƒผใ€ใฎURLใ‚’่กจ็คบใ—ใพใ™ -Command Help - /plan players || ใ€Œใƒ—ใƒฌใ‚คใƒคใƒผใ€ใฎใƒšใƒผใ‚ธใฎURLใ‚’่กจ็คบใ—ใพใ™ -Command Help - /plan register || ใ‚ฆใ‚งใƒ–ใƒฆใƒผใ‚ถใƒผใ‚’็™ป้Œฒใ—ใพใ™ -Command Help - /plan reload || ใ€ŒPlanใ€ใ‚’ๅ†่ตทๅ‹•ใ—ใพใ™ -Command Help - /plan search || ใƒ—ใƒฌใ‚คใƒคใƒผๅใ‚’ๆคœ็ดขใ—ใพใ™ -Command Help - /plan server || ใ‚ตใƒผใƒใƒผใƒšใƒผใ‚ธใฎURLใ‚’่กจ็คบใ—ใพใ™ -Command Help - /plan servers || ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นๅ†…ใฎBukkit/Spigotใ‚ตใƒผใƒใƒผไธ€่ฆงใ‚’่กจ็คบใ—ใพใ™ -Command Help - /plan unregister || ใ‚ฆใ‚งใƒ–ใƒšใƒผใ‚ธใ‹ใ‚‰ใƒฆใƒผใ‚ถใƒผใ‚’ๆœช็™ป้Œฒใซใ—ใพใ™ -Command Help - /plan users || ใ‚ฆใ‚งใƒ–ใƒšใƒผใ‚ธใฎๅ…จใƒฆใƒผใ‚ถใƒผใ‚’่กจ็คบใพใ™ -Database - Apply Patch || ๆฌกใฎใƒ‘ใƒƒใƒใ‚’้ฉ็”จใ—ใฆใ„ใพใ™: ${0}.. -Database - Patches Applied || ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใซๅ…จใฆใฎใƒ‘ใƒƒใƒใŒๆญฃๅธธใซ้ฉ็”จใ•ใ‚Œใพใ—ใŸ -Database - Patches Applied Already || ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใซๅ…จใฆใฎใƒ‘ใƒƒใƒใŒ้ฉ็”จๆธˆใฟใงใ™ -Database MySQL - Launch Options Error || ่ตทๅ‹•ใ‚ชใƒ—ใ‚ทใƒงใƒณใซๅ•้กŒใŒใ‚ใ‚Šใพใ™,ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใฎใ‚ชใƒ—ใ‚ทใƒงใƒณใ‚’ไฝฟ็”จใ—ใฆไธ‹ใ•ใ„ (${0}) -Database Notify - Clean || ${0} ใฎใƒ—ใƒฌใ‚คใƒคใƒผใƒ‡ใƒผใ‚ฟใ‚’ๅ‰Š้™คใ—ใฆใ„ใพใ™ -Database Notify - SQLite No WAL || SQLiteใฎWALใƒขใƒผใƒ‰ใฏใ“ใฎใ‚ตใƒผใƒใฎใƒใƒผใ‚ธใƒงใƒณใงใฏใ‚ตใƒใƒผใƒˆใ•ใ‚Œใฆใ„ใชใ„ใŸใ‚ใ€ๅˆๆœŸ่จญๅฎšใซๅค‰ๆ›ดใ—ใพใ™ใ€‚ใ“ใ‚Œใฏใ‚ตใƒผใƒใƒผใฎใƒ‘ใƒ•ใ‚ฉใƒผใƒžใƒณใ‚นใซๅฝฑ้Ÿฟใ‚’ไธŽใˆใ‚‹ๅฏ่ƒฝๆ€งใŒใ‚ใ‚Šใพใ™ -Disable || ใƒ—ใƒฌใ‚คใƒคใƒผๅˆ†ๆžใŒ็„กๅŠนใซใชใ‚Šใพใ—ใŸ -Disable - Processing || ๆœชๅฎŸ่กŒใฎ้‡่ฆใชๅ‡ฆ็†ใŒใ‚ใ‚Šใพใ™ (${0}) -Disable - Processing Complete || ๅ‡ฆ็†ใŒๅฎŒไบ†ใ—ใพใ—ใŸ -Disable - Unsaved Session Save || ๆœชไฟๅญ˜ใฎใ‚ปใƒƒใ‚ทใƒงใƒณใ‚’ไฟๅญ˜ใ—ใฆใ„ใพใ™ใƒปใƒป -Disable - Unsaved Session Save Timeout || Timeout hit, storing the unfinished sessions on next enable instead. -Disable - Waiting SQLite || Waiting queries to finish to avoid SQLite crashing JVM.. -Disable - Waiting SQLite Complete || Closed SQLite connection. -Disable - Waiting Transactions || Waiting for unfinished transactions to avoid data loss.. -Disable - Waiting Transactions Complete || Transaction queue closed. -Disable - WebServer || ใ‚ฆใ‚งใƒ–ใ‚ตใƒผใƒใƒผใŒ็„กๅŠนใซใชใ‚Šใพใ—ใŸ -Enable || ใƒ—ใƒฌใ‚คใƒคใƒผๅˆ†ๆžใŒๆœ‰ๅŠนใซใชใ‚Šใพใ—ใŸ -Enable - Database || ${0}ใฎใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใฎๆŽฅ็ถšใŒ็ขบ็ซ‹ใ—ใพใ—ใŸ -Enable - Notify Bad IP || IPใ€Œ0.0.0.0ใ€ใฏๆœ‰ๅŠนใงใฏใชใ„ใŸใ‚, ใ€ŒAlternative_IPใ€ใฎ่จญๅฎšใ‚’่กŒใฃใฆใใ ใ•ใ„ใ€‚่ชคใฃใŸใƒชใƒณใ‚ฏใŒ่กจ็คบใ•ใ‚Œใ‚‹ๅฏ่ƒฝๆ€งใŒใ‚ใ‚Šใพใ™! -Enable - Notify Empty IP || server.propertiesใฎ่จญๅฎšใงใ€IPใฎ้ …็›ฎใŒ่จญๅฎšใ•ใ‚ŒใฆใŠใ‚‰ใšAlternative IPใŒไฝฟ็”จใ•ใ‚Œใฆใ„ใพใ›ใ‚“ใ€‚ใใฎใŸใ‚่ชคใฃใŸใƒชใƒณใ‚ฏใŒ่กจ็คบใ•ใ‚Œใพใ™! -Enable - Notify Geolocations disabled || ไฝ็ฝฎๆƒ…ๅ ฑใ‚ตใƒผใƒ“ใ‚นใŒๆœ‰ๅŠนใงใฏใ‚ใ‚Šใพใ›ใ‚“ใ€‚ (Data.Geolocations: false) -Enable - Notify Geolocations Internet Required || ใ€ŒPlanใ€ใฏๅˆๅ›ž่ตทๅ‹•ๆ™‚ใ€ใ€ŒGeoLite2ใ€ใฎไฝ็ฝฎๆƒ…ๅ ฑใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ‚’ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ใ™ใ‚‹ใŸใ‚ใ‚คใƒณใ‚ฟใƒผใƒใƒƒใƒˆใ‚ขใ‚ฏใ‚ปใ‚นใŒๅฟ…่ฆใงใ™ -Enable - Notify Webserver disabled || ใ‚ฆใ‚งใƒ–ใ‚ตใƒผใƒใƒผใฎๅˆๆœŸๅŒ–ใซๅคฑๆ•—ใ—ใพใ—ใŸ (WebServer.DisableWebServer: true) -Enable - Storing preserved sessions || Storing sessions that were preserved before previous shutdown. -Enable - WebServer || ใ‚ฆใ‚งใƒ–ใ‚ตใƒผใƒใƒผใฏๆฌกใฎใƒใƒผใƒˆใงๅฎŸ่กŒใ•ใ‚Œใฆใ„ใพใ™: ${0} ( ${1} ) -Enable FAIL - Database || ใ€Œ${0}ใ€ใฎใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใฎๆŽฅ็ถšใซๅคฑๆ•—ใ—ใพใ—ใŸ: ${1} -Enable FAIL - Database Patch || ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใฎใƒ‘ใƒƒใƒ้ฉ็”จใซๅคฑๆ•—ใ—ใพใ—ใŸใ€ใƒ—ใƒฉใ‚ฐใ‚คใƒณใ‚’็„กๅŠนใซใ™ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚ใƒใ‚ฐๅ ฑๅ‘Šใ‚’ใŠ้ก˜ใ„ใ—ใพใ™ -Enable FAIL - GeoDB Write || ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ใ—ใŸใ€ŒGeoLite2ใ€ใฎไฝ็ฝฎๆƒ…ๅ ฑใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ‚’ไฟๅญ˜ไธญใซไฝ•ใ‚‰ใ‹ใฎใ‚จใƒฉใƒผใŒ็™บ็”Ÿใ—ใพใ—ใŸ -Enable FAIL - WebServer (Proxy) || ใ‚ฆใ‚งใƒ–ใ‚ตใƒผใƒใƒผใฎๅˆๆœŸๅŒ–ใซๅคฑๆ•—ใ—ใพใ—ใŸ! -Enable FAIL - Wrong Database Type || ใ€Œ${0}ใ€ใฏใ‚ตใƒใƒผใƒˆใ•ใ‚Œใฆใ„ใชใ„ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใงใ™ -HTML - AND_BUG_REPORTERS || & Bug reporters! -HTML - BANNED (Filters) || Banned -HTML - COMPARING_15_DAYS || ็›ด่ฟ‘15ๆ—ฅใจใฎๆฏ”่ผƒ -HTML - COMPARING_60_DAYS || 30ๆ—ฅๅ‰ใจใฎๆฏ”่ผƒ -HTML - COMPARING_7_DAYS || ็›ด่ฟ‘1้€ฑ้–“ใจใฎๆฏ”่ผƒ -HTML - DATABASE_NOT_OPEN || ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ‚’้–‹ใใ“ใจใŒใงใใพใ›ใ‚“ใงใ—ใŸใ€‚ใ€Œ/plan infoใ€ใ‚ณใƒžใƒณใƒ‰ใ‚’ๅฎŸ่กŒใ—ใฆ็Šถๆณใ‚’็ขบ่ชใ—ใฆไธ‹ใ•ใ„ -HTML - DESCRIBE_RETENTION_PREDICTION || This value is a prediction based on previous players. -HTML - ERROR || ใ‚จใƒฉใƒผใŒ็™บ็”Ÿใ—ใŸใŸใ‚่ช่จผใซๅคฑๆ•—ใ—ใพใ—ใŸ -HTML - EXPIRED_COOKIE || ใƒฆใƒผใ‚ถใƒผใฎใ‚ฏใƒƒใ‚ญใƒผใฎๆœ‰ๅŠนๆœŸ้™ๅˆ‡ใ‚Œใงใ™ -HTML - FILTER_ACTIVITY_INDEX_NOW || Current activity group -HTML - FILTER_ALL_PLAYERS || All players -HTML - FILTER_BANNED || Ban status -HTML - FILTER_GROUP || Group: -HTML - FILTER_OPS || Operator status -HTML - INDEX_ACTIVE || ใ‚ˆใใƒญใ‚ฐใ‚คใƒณใ—ใฆใ„ใ‚‹ -HTML - INDEX_INACTIVE || ไผ‘ๆญขไธญ -HTML - INDEX_IRREGULAR || ใŸใพใซใƒญใ‚ฐใ‚คใƒณใ—ใฆใ„ใ‚‹ -HTML - INDEX_REGULAR || ใ—ใฐใ—ใฐใƒญใ‚ฐใ‚คใƒณใ—ใฆใ„ใ‚‹ -HTML - INDEX_VERY_ACTIVE || ใจใฆใ‚‚ใƒญใ‚ฐใ‚คใƒณใ—ใฆใ„ใ‚‹ -HTML - KILLED || ๆฎบใ—ใŸไบบ -HTML - LABEL_1ST_WEAPON || ๆœ€ใ‚‚PvPใงไฝฟ็”จใ•ใ‚Œใฆใ„ใ‚‹ๆญฆๅ™จ -HTML - LABEL_2ND_WEAPON || 2็•ช็›ฎใซPvPใงไฝฟ็”จใ•ใ‚Œใฆใ„ใ‚‹ๆญฆๅ™จ -HTML - LABEL_3RD_WEAPON || 3็•ช็›ฎใซPvPใงไฝฟ็”จใ•ใ‚Œใฆใ„ใ‚‹ๆญฆๅ™จ -HTML - LABEL_ACTIVE_PLAYTIME || Active Playtime -HTML - LABEL_ACTIVITY_INDEX || ๆดปๅ‹•ๆŒ‡ๆ•ฐ -HTML - LABEL_AFK || ้›ขๅธญ -HTML - LABEL_AFK_TIME || ้›ขๅธญๆ™‚้–“ -HTML - LABEL_AVG || ๅนณๅ‡ -HTML - LABEL_AVG_ACTIVE_PLAYTIME || Average Active Playtime -HTML - LABEL_AVG_AFK_TIME || Average AFK Time -HTML - LABEL_AVG_CHUNKS || Average Chunks -HTML - LABEL_AVG_ENTITIES || Average Entities -HTML - LABEL_AVG_KDR || ๅนณๅ‡KDR -HTML - LABEL_AVG_MOB_KDR || ใƒขใƒ–ใซๅฏพใ—ใฆใฎKDR -HTML - LABEL_AVG_PLAYTIME || ๅนณๅ‡ใƒ—ใƒฌใ‚คๆ™‚้–“ -HTML - LABEL_AVG_SESSION_LENGTH || ๅนณๅ‡ๆŽฅ็ถšๆ™‚้–“ -HTML - LABEL_AVG_SESSIONS || Average Sessions -HTML - LABEL_AVG_TPS || ๅนณๅ‡TPS -HTML - LABEL_BANNED || BANๅฑฅๆญด -HTML - LABEL_BEST_PEAK || ๅ…จไฝ“ใฎใƒ”ใƒผใ‚ฏใ‚ฟใ‚คใƒ  -HTML - LABEL_DAY_OF_WEEK || ๆ›œๆ—ฅ -HTML - LABEL_DEATHS || ๆญปไบกๅ›žๆ•ฐ -HTML - LABEL_DOWNTIME || ใƒ€ใ‚ฆใƒณใ‚ฟใ‚คใƒ  -HTML - LABEL_DURING_LOW_TPS || TPSใฎไฝŽไธ‹ใพใงใฎๆ™‚้–“: -HTML - LABEL_ENTITIES || ใ‚จใƒณใƒ†ใ‚ฃใƒ†ใ‚ฃๆ•ฐ -HTML - LABEL_FAVORITE_SERVER || ใŠๆฐ—ใซๅ…ฅใ‚Šใฎใ‚ตใƒผใƒใƒผ -HTML - LABEL_FIRST_SESSION_LENGTH || ๅˆๅ‚ๅŠ ๆ™‚ใฎๆŽฅ็ถšๆ™‚้–“ -HTML - LABEL_FREE_DISK_SPACE || ใƒ‰ใƒฉใ‚คใƒ–ใฎ็ฉบใๅฎน้‡ -HTML - LABEL_INACTIVE || ไผ‘ๆญขไธญ -HTML - LABEL_LAST_PEAK || ็›ด่ฟ‘ใฎใƒ”ใƒผใ‚ฏใ‚ฟใ‚คใƒ  -HTML - LABEL_LAST_SEEN || ็›ด่ฟ‘ใฎใ‚ชใƒณใƒฉใ‚คใƒณ -HTML - LABEL_LOADED_CHUNKS || ใƒญใƒผใƒ‰ใ•ใ‚ŒใŸใƒใƒฃใƒณใ‚ฏๆ•ฐ -HTML - LABEL_LOADED_ENTITIES || ใƒญใƒผใƒ‰ใ•ใ‚ŒใŸใ‚จใƒณใƒ†ใ‚ฃๆ•ฐ -HTML - LABEL_LONE_JOINS || ไธ€ไบบใงใฎๆŽฅ็ถš -HTML - LABEL_LONE_NEW_JOINS || ๆ–ฐใ—ใไธ€ไบบใงใฎๅ‚ๅŠ  -HTML - LABEL_LONGEST_SESSION || ๆœ€้•ทๆŽฅ็ถšๆ™‚้–“ -HTML - LABEL_LOW_TPS || TPSใฎไฝŽไธ‹ๅ€ค -HTML - LABEL_MAX_FREE_DISK || ใƒ‡ใ‚ฃใ‚นใ‚ฏใฎๆœ€ๅคง็ฉบใๅฎน้‡ -HTML - LABEL_MIN_FREE_DISK || ใƒ‡ใ‚ฃใ‚นใ‚ฏใฎๆœ€ไฝŽ็ฉบใๅฎน้‡ -HTML - LABEL_MOB_DEATHS || Mobใซใ‚ˆใฃใฆๆฎบใ•ใ‚ŒใŸๅ›žๆ•ฐ -HTML - LABEL_MOB_KDR || Mobใซๅฏพใ—ใฆใฎKDR -HTML - LABEL_MOB_KILLS || Mobใ‚’ๆฎบใ—ใŸๅ›žๆ•ฐ -HTML - LABEL_MOST_ACTIVE_GAMEMODE || ๆœ€ใ‚‚ไฝฟ็”จใ—ใŸใ‚ฒใƒผใƒ ใƒขใƒผใƒ‰ -HTML - LABEL_NAME || ๅๅ‰ -HTML - LABEL_NEW || New -HTML - LABEL_NEW_PLAYERS || ๆ–ฐ่ฆใƒ—ใƒฌใ‚คใƒคใƒผ -HTML - LABEL_NICKNAME || ใƒ‹ใƒƒใ‚ฏใƒใƒผใƒ  -HTML - LABEL_NO_SESSION_KILLS || ใชใ— -HTML - LABEL_ONLINE_FIRST_JOIN || ๆ–ฐ่ฆใƒญใ‚ฐใ‚คใƒณๆ™‚ใฎใ‚ชใƒณใƒฉใ‚คใƒณใƒ—ใƒฌใ‚คใƒคใƒผ -HTML - LABEL_OPERATOR || ็ฎก็†่€… -HTML - LABEL_PER_PLAYER || /ใƒ—ใƒฌใ‚คใƒคใƒผ(1ใƒ—ใƒฌใ‚คใƒคใƒผใ‚ใŸใ‚Šใฎ) -HTML - LABEL_PER_REGULAR_PLAYER || /็™ป้Œฒๆธˆใฟใƒ—ใƒฌใ‚คใƒคใƒผ(1็™ป้Œฒๆธˆใฟใƒ—ใƒฌใ‚คใƒคใƒผใ‚ใŸใ‚Šใฎ) -HTML - LABEL_PLAYER_DEATHS || ใƒ—ใƒฌใ‚คใƒคใƒผใซใ‚ˆใ‚‹ใ‚ญใƒซ -HTML - LABEL_PLAYER_KILLS || ใƒ—ใƒฌใ‚คใƒคใƒผใ‚ญใƒซ -HTML - LABEL_PLAYERS_ONLINE || ใ‚ชใƒณใƒฉใ‚คใƒณใฎใƒ—ใƒฌใ‚คใƒคใƒผ -HTML - LABEL_PLAYTIME || ใƒ—ใƒฌใ‚คๆ™‚้–“ -HTML - LABEL_REGISTERED || ็™ป้Œฒ -HTML - LABEL_REGISTERED_PLAYERS || ็™ป้Œฒๆธˆใฟใƒ—ใƒฌใ‚คใƒคใƒผ -HTML - LABEL_REGULAR || ใ‚ˆใใ‚ชใƒณใƒฉใ‚คใƒณใฎใƒ—ใƒฌใ‚คใƒคใƒผ -HTML - LABEL_REGULAR_PLAYERS || ใ‚ˆใใ‚ชใƒณใƒฉใ‚คใƒณใฎใƒ—ใƒฌใ‚คใƒคใƒผ -HTML - LABEL_RELATIVE_JOIN_ACTIVITY || ใ‚ชใƒณใƒฉใ‚คใƒณใจๆดปๅ‹•ใจใฎ้–ขไฟ‚ๆ€ง -HTML - LABEL_RETENTION || ๆ–ฐ่ฆใƒ—ใƒฌใ‚คใƒคใƒผใฎ็ถ™็ถš็Ž‡ -HTML - LABEL_SERVER_DOWNTIME || ใ‚ตใƒผใƒใƒผใƒ€ใ‚ฆใƒณใ‚ฟใ‚คใƒ  -HTML - LABEL_SERVER_OCCUPIED || ใƒ—ใƒฌใ‚คใƒคใƒผใŒใƒญใ‚ฐใ‚คใƒณใ—ใฆใ„ใ‚‹ๆ™‚้–“ -HTML - LABEL_SESSION_ENDED || ใƒญใ‚ฐใ‚ขใ‚ฆใƒˆใ—ใŸๆ™‚้–“ -HTML - LABEL_SESSION_MEDIAN || ๅนณๅ‡ใ‚ชใƒณใƒฉใ‚คใƒณ -HTML - LABEL_TIMES_KICKED || ใ‚ญใƒƒใ‚ฏๅ›žๆ•ฐ -HTML - LABEL_TOTAL_PLAYERS || ใƒˆใƒผใ‚ฟใƒซใƒ—ใƒฌใ‚คใƒคใƒผๆ•ฐ -HTML - LABEL_TOTAL_PLAYTIME || ใƒˆใƒผใ‚ฟใƒซใƒ—ใƒฌใ‚คๆ™‚้–“ -HTML - LABEL_UNIQUE_PLAYERS || ๆŽฅ็ถšใ—ใŸใƒ—ใƒฌใ‚คใƒคใƒผใฎ็ทๆ•ฐ -HTML - LABEL_WEEK_DAYS || 'ๆœˆๆ›œๆ—ฅ', '็ซๆ›œๆ—ฅ', 'ๆฐดๆ›œๆ—ฅ', 'ๆœจๆ›œๆ—ฅ', '้‡‘ๆ›œๆ—ฅ', 'ๅœŸๆ›œๆ—ฅ', 'ๆ—ฅๆ›œๆ—ฅ' -HTML - LINK_BACK_NETWORK || ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏใƒšใƒผใ‚ธ -HTML - LINK_BACK_SERVER || ใ‚ตใƒผใƒใƒผใƒšใƒผใ‚ธ -HTML - LINK_CHANGELOG || ๅค‰ๆ›ดๅฑฅๆญดใฎ็ขบ่ช -HTML - LINK_DISCORD || Discordใฎใ‚ตใƒใƒผใƒˆใƒใƒฃใƒณใƒใƒซ -HTML - LINK_DOWNLOAD || ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ -HTML - LINK_ISSUES || ใƒใ‚ฐๅ ฑๅ‘Š -HTML - LINK_NIGHT_MODE || ใƒŠใ‚คใƒˆใƒขใƒผใƒ‰ -HTML - LINK_PLAYER_PAGE || ใƒ—ใƒฌใ‚คใƒคใƒผใƒšใƒผใ‚ธ -HTML - LINK_QUICK_VIEW || ใ‚ฏใ‚คใƒƒใ‚ฏใƒ“ใƒฅใƒผ -HTML - LINK_SERVER_ANALYSIS || ใ‚ตใƒผใƒใƒผใฎๅˆ†ๆž็ตๆžœ -HTML - LINK_WIKI || ใ€ŒPlanใ€ใฎwikiใ€ใƒใƒฅใƒผใƒˆใƒชใ‚ขใƒซใจใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆ -HTML - LOCAL_MACHINE || ใƒญใƒผใ‚ซใƒซใƒžใ‚ทใƒณ -HTML - LOGIN_CREATE_ACCOUNT || Create an Account! -HTML - LOGIN_FAILED || Login failed: -HTML - LOGIN_FORGOT_PASSWORD || Forgot Password? -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_1 || Forgot password? Unregister and register again. -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_2 || Use the following command in game to remove your current user: -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_3 || Or using console: -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_4 || After using the command, -HTML - LOGIN_LOGIN || Login -HTML - LOGIN_LOGOUT || Logout -HTML - LOGIN_PASSWORD || "Password" -HTML - LOGIN_USERNAME || "Username" -HTML - NAV_PLUGINS || ใƒ—ใƒฉใ‚ฐใ‚คใƒณ -HTML - NEW_CALENDAR || New: -HTML - NO_KILLS || ใƒ—ใƒฌใ‚คใƒคใƒผใ‚ญใƒซใชใ— -HTML - NO_USER_PRESENT || ใƒฆใƒผใ‚ถใƒผใฎใ‚ฏใƒƒใ‚ญใƒผใŒๅญ˜ๅœจใ—ใพใ›ใ‚“ -HTML - NON_OPERATORS (Filters) || Non operators -HTML - NOT_BANNED (Filters) || Not banned -HTML - OFFLINE || ใ‚ชใƒ•ใƒฉใ‚คใƒณ -HTML - ONLINE || ใ‚ชใƒณใƒฉใ‚คใƒณ -HTML - OPERATORS (Filters) || Operators -HTML - PER_DAY || /ๆ—ฅ -HTML - PLAYERS_TEXT || ใƒ—ใƒฌใ‚คใƒคใƒผ -HTML - QUERY || Query< -HTML - QUERY_ACTIVITY_OF_MATCHED_PLAYERS || Activity of matched players -HTML - QUERY_ACTIVITY_ON || Activity on -HTML - QUERY_ADD_FILTER || Add a filter.. -HTML - QUERY_AND || and -HTML - QUERY_ARE || `are` -HTML - QUERY_ARE_ACTIVITY_GROUP || are in Activity Groups -HTML - QUERY_ARE_PLUGIN_GROUP || are in ${plugin}'s ${group} Groups -HTML - QUERY_JOINED_WITH_ADDRESS || joined with address -HTML - QUERY_LOADING_FILTERS || Loading filters.. -HTML - QUERY_MAKE || Make a query -HTML - QUERY_MAKE_ANOTHER || Make another query -HTML - QUERY_OF_PLAYERS || of Players who -HTML - QUERY_PERFORM_QUERY || Perform Query! -HTML - QUERY_PLAYED_BETWEEN || Played between -HTML - QUERY_REGISTERED_BETWEEN || Registered between -HTML - QUERY_RESULTS || Query Results -HTML - QUERY_RESULTS_MATCH || matched ${resultCount} players -HTML - QUERY_SESSIONS_WITHIN_VIEW || Sessions within view -HTML - QUERY_SHOW_VIEW || Show a view -HTML - QUERY_TIME_FROM || >from -HTML - QUERY_TIME_TO || >to -HTML - QUERY_VIEW || View: -HTML - QUERY_ZERO_RESULTS || Query produced 0 results -HTML - REGISTER || Register -HTML - REGISTER_CHECK_FAILED || Checking registration status failed: -HTML - REGISTER_COMPLETE || Complete Registration -HTML - REGISTER_COMPLETE_INSTRUCTIONS_1 || You can now finish registering the user. -HTML - REGISTER_COMPLETE_INSTRUCTIONS_2 || Code expires in 15 minutes -HTML - REGISTER_COMPLETE_INSTRUCTIONS_3 || Use the following command in game to finish registration: -HTML - REGISTER_COMPLETE_INSTRUCTIONS_4 || Or using console: -HTML - REGISTER_CREATE_USER || Create a new user -HTML - REGISTER_FAILED || Registration failed: -HTML - REGISTER_HAVE_ACCOUNT || Already have an account? Login! -HTML - REGISTER_PASSWORD_TIP || Password should be more than 8 characters, but there are no limitations. -HTML - REGISTER_SPECIFY_PASSWORD || You need to specify a Password -HTML - REGISTER_SPECIFY_USERNAME || You need to specify a Username -HTML - REGISTER_USERNAME_LENGTH || Username can be up to 50 characters, yours is -HTML - REGISTER_USERNAME_TIP || Username can be up to 50 characters. -HTML - SESSION || ใ‚ชใƒณใƒฉใ‚คใƒณ -HTML - SIDE_GEOLOCATIONS || ๅœฐๅŸŸ -HTML - SIDE_INFORMATION || ใ‚คใƒณใƒ•ใ‚ฉใƒกใƒผใ‚ทใƒงใƒณ -HTML - SIDE_LINKS || LINKS -HTML - SIDE_NETWORK_OVERVIEW || ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏๆฆ‚่ฆ -HTML - SIDE_OVERVIEW || ๆฆ‚่ฆ -HTML - SIDE_PERFORMANCE || ใƒ‘ใƒ•ใ‚ฉใƒผใƒžใƒณใ‚น -HTML - SIDE_PLAYER_LIST || ใƒ—ใƒฌใ‚คใƒคใƒผไธ€่ฆง -HTML - SIDE_PLAYERBASE || ใƒ—ใƒฌใ‚คใƒคใƒผใƒ™ใƒผใ‚น -HTML - SIDE_PLAYERBASE_OVERVIEW || ใƒ—ใƒฌใ‚คใƒคใƒผใƒ™ใƒผใ‚นๆฆ‚่ฆ -HTML - SIDE_PLUGINS || ใƒ—ใƒฉใ‚ฐใ‚คใƒณ -HTML - SIDE_PVP_PVE || PvPใจPvE -HTML - SIDE_SERVERS || ๆŽฅ็ถšใ•ใ‚Œใฆใ„ใ‚‹ใ‚ตใƒผใƒใƒผ -HTML - SIDE_SERVERS_TITLE || ๆŽฅ็ถšใ•ใ‚Œใฆใ„ใ‚‹ใ‚ตใƒผใƒใƒผ -HTML - SIDE_SESSIONS || ๆŽฅ็ถšๅฑฅๆญด -HTML - SIDE_TO_MAIN_PAGE || ใƒกใ‚คใƒณใƒšใƒผใ‚ธใซๆˆปใ‚‹ -HTML - TEXT_CLICK_TO_EXPAND || ใ‚ฏใƒชใƒƒใ‚ฏใ—ใฆๅฑ•้–‹ -HTML - TEXT_CONTRIBUTORS_CODE || :ใƒ—ใƒญใ‚ฐใƒฉใƒŸใƒณใ‚ฐ่ฒข็Œฎ่€…ใ€€ -HTML - TEXT_CONTRIBUTORS_LOCALE || :็ฟป่จณ่€…ใ€€ -HTML - TEXT_CONTRIBUTORS_MONEY || ใ“ใฎใƒ—ใƒฉใ‚ฐใ‚คใƒณ้–‹็™บใซๅ‹Ÿ้‡‘ใ—ใฆ้ ‚ใ„ใŸไบบใ€…ใธ็‰นๅˆฅใชๆ„Ÿ่ฌใ‚’ -HTML - TEXT_CONTRIBUTORS_THANKS || ๅŠ ใˆใฆใ€ไปฅไธ‹ใฎ็ด ๆ™ดใ‚‰ใ—ใ„ไบบใ€…ใŒ้–‹็™บใซ่ฒข็Œฎใ—ใฆใ„ใพใ™ -HTML - TEXT_DEV_VERSION || ใ“ใฎใƒใƒผใ‚ธใƒงใƒณใฏ้–‹็™บ็‰ˆใงใ™ -HTML - TEXT_DEVELOPED_BY || ้–‹็™บ่€…: -HTML - TEXT_FIRST_SESSION || ๅˆๅ‚ๅŠ  -HTML - TEXT_LICENSED_UNDER || ใ€ŒPlayer Analyticsใ€ใฏไปฅไธ‹ใฎใƒฉใ‚คใ‚ปใƒณใ‚นใฎไธ‹ใ€้–‹็™บใ•ใ‚Œใฆใ„ใพใ™ -HTML - TEXT_METRICS || ใ€ŒbStats Metricsใ€ใŒๆœ‰ๅŠนใซใชใฃใฆใ„ใพใ™ -HTML - TEXT_NO_EXTENSION_DATA || ใ€ŒExtension Dataใ€ใŒๅญ˜ๅœจใ—ใพใ›ใ‚“ -HTML - TEXT_NO_LOW_TPS || TPSใฎไฝŽไธ‹ใŒๅญ˜ๅœจใ—ใพใ›ใ‚“ -HTML - TEXT_NO_SERVER || ใ‚ชใƒณใƒฉใ‚คใƒณใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ“ใƒ†ใ‚ฃใ‚’่กจ็คบใ™ใ‚‹ใ‚ตใƒผใƒใƒผใŒใ‚ใ‚Šใพใ›ใ‚“ -HTML - TEXT_NO_SERVERS || ใƒ‡ใƒผใ‚ฟใƒผใƒ™ใƒผใ‚นๅ†…ใซ็™ป้Œฒใ•ใ‚ŒใŸใ‚ตใƒผใƒใƒผใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“ -HTML - TEXT_PLUGIN_INFORMATION || ใƒ—ใƒฉใ‚ฐใ‚คใƒณใซ้–ขใ™ใ‚‹ๆƒ…ๅ ฑ -HTML - TEXT_PREDICTED_RETENTION || ใ“ใ‚Œใฏไปฅๅ‰ใฎใƒ—ใƒฌใƒผใƒคใƒผใ‹ใ‚‰ๅŸบใฅใ„ใŸไบˆๆธฌๅ€คใงใ™ -HTML - TEXT_SERVER_INSTRUCTIONS || It appears that Plan is not installed on any game servers or not connected to the same database. See wiki for Network tutorial. -HTML - TEXT_VERSION || ๆ–ฐใƒใƒผใ‚ธใƒงใƒณใŒใƒชใƒชใƒผใ‚นใ•ใ‚ŒใฆใŠใ‚Šใ€ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ๅฏ่ƒฝใงใ™ -HTML - TITLE_30_DAYS || 1ใƒถๆœˆ -HTML - TITLE_30_DAYS_AGO || 1ใƒถๆœˆๅ‰ -HTML - TITLE_ALL || ๅ…จใฆ -HTML - TITLE_ALL_TIME || ๅ…จไฝ“ -HTML - TITLE_AS_NUMBERS || ใฎๆƒ…ๅ ฑ -HTML - TITLE_AVG_PING || ๅนณๅ‡Pingๅ€ค -HTML - TITLE_BEST_PING || ๆœ€้ซ˜Pingๅ€ค -HTML - TITLE_CALENDAR || ใ‚ซใƒฌใƒณใƒ€ใƒผ -HTML - TITLE_CONNECTION_INFO || ๆŽฅ็ถšๆƒ…ๅ ฑ -HTML - TITLE_COUNTRY || ๅ›ฝ/ๅœฐๅŸŸ -HTML - TITLE_CPU_RAM || CPUใจใƒกใƒขใƒชใƒผ -HTML - TITLE_CURRENT_PLAYERBASE || ใƒญใ‚ฐใ‚คใƒณใƒ—ใƒฌใ‚คใƒคใƒผ -HTML - TITLE_DISK || ใƒ‰ใƒฉใ‚คใƒ–ใฎๅฎน้‡ -HTML - TITLE_GRAPH_DAY_BY_DAY || ่ฉณ็ดฐๆƒ…ๅ ฑ -HTML - TITLE_GRAPH_HOUR_BY_HOUR || Hour by Hour -HTML - TITLE_GRAPH_NETWORK_ONLINE_ACTIVITY || ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏๅ†…ใฎๆŽฅ็ถš็Šถๆณ -HTML - TITLE_GRAPH_PUNCHCARD || 1ใƒถๆœˆใฎใƒ‘ใƒณใƒใƒœใƒผใƒ‰ -HTML - TITLE_INSIGHTS || 1ใƒถๆœˆใฎใƒ‘ใƒณใƒใƒœใƒผใƒ‰ -HTML - TITLE_IS_AVAILABLE || ใŒๅˆฉ็”จๅฏ่ƒฝใงใ™ -HTML - TITLE_JOIN_ADDRESSES || Join Addresses -HTML - TITLE_LAST_24_HOURS || 24ๆ™‚้–“ -HTML - TITLE_LAST_30_DAYS || 1ใƒถๆœˆ -HTML - TITLE_LAST_7_DAYS || ไธ€้€ฑ้–“ -HTML - TITLE_LAST_CONNECTED || ็›ด่ฟ‘ใฎๆŽฅ็ถš -HTML - TITLE_LENGTH || ้•ทใ• -HTML - TITLE_MOST_PLAYED_WORLD || ใ‚ˆใใƒ—ใƒฌใ‚คใ—ใฆใ„ใ‚‹ใƒฏใƒผใƒซใƒ‰ -HTML - TITLE_NETWORK || ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏ -HTML - TITLE_NETWORK_AS_NUMBERS || ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏๆ•ฐ -HTML - TITLE_NOW || ็พๅœจ -HTML - TITLE_ONLINE_ACTIVITY || ๆŽฅ็ถš็Šถๆณ -HTML - TITLE_ONLINE_ACTIVITY_AS_NUMBERS || ๆŽฅ็ถš็Šถๆณใฎๆƒ…ๅ ฑ -HTML - TITLE_ONLINE_ACTIVITY_OVERVIEW || ๆŽฅ็ถš็Šถๆณใฎๆฆ‚่ฆ -HTML - TITLE_PERFORMANCE_AS_NUMBERS || ใƒ‘ใƒ•ใ‚ฉใƒผใƒžใƒณใ‚นใฎๆƒ…ๅ ฑ -HTML - TITLE_PING || Ping -HTML - TITLE_PLAYER || ใƒ—ใƒฌใ‚คใƒคใƒผ -HTML - TITLE_PLAYER_OVERVIEW || ใƒ—ใƒฌใ‚คใƒคใƒผใฎๆฆ‚่ฆ -HTML - TITLE_PLAYERBASE_DEVELOPMENT || ็™ป้Œฒใ•ใ‚Œใฆใ„ใ‚‹ใƒ—ใƒฌใ‚คใƒคใƒผใฎๆŽจ็งป -HTML - TITLE_PVP_DEATHS || ๆœ€่ฟ‘ใฎPVPใซใ‚ˆใ‚‹ๆญปไบก -HTML - TITLE_PVP_KILLS || ๆœ€่ฟ‘ใฎPVPใซใ‚ˆใ‚‹ใ‚ญใƒซ -HTML - TITLE_PVP_PVE_NUMBERS || PVPใจPvEใฎๆƒ…ๅ ฑ -HTML - TITLE_RECENT_KILLS || ๆœ€่ฟ‘ใฎใ‚ญใƒซ -HTML - TITLE_RECENT_SESSIONS || ๆœ€่ฟ‘ใฎใƒญใ‚ฐใ‚คใƒณ -HTML - TITLE_SEEN_NICKNAMES || ใƒ‹ใƒƒใ‚ฏใƒใƒผใƒ ไธ€่ฆง -HTML - TITLE_SERVER || ใ‚ตใƒผใƒใƒผ -HTML - TITLE_SERVER_AS_NUMBERS || ใ‚ตใƒผใƒใƒผใฎ็Šถๆณ -HTML - TITLE_SERVER_OVERVIEW || ใ‚ตใƒผใƒใƒผใฎๆฆ‚่ฆ -HTML - TITLE_SERVER_PLAYTIME || ๅ„ใ‚ตใƒผใƒใƒผใฎใƒ—ใƒฌใ‚คๆ™‚้–“ -HTML - TITLE_SERVER_PLAYTIME_30 || ๅ„ใ‚ตใƒผใƒใƒผใงใฎ1ใƒถๆœˆใฎใƒ—ใƒฌใ‚คๆ™‚้–“ -HTML - TITLE_SESSION_START || ๆŽฅ็ถšใ—ใŸๆ™‚้–“ -HTML - TITLE_THEME_SELECT || ใƒ†ใƒผใƒž้ธๆŠž -HTML - TITLE_TITLE_PLAYER_PUNCHCARD || ใƒ‘ใƒณใƒใ‚ซใƒผใƒ‰ -HTML - TITLE_TPS || TPS -HTML - TITLE_TREND || ๅข—ๆธ› -HTML - TITLE_TRENDS || 1ใƒถๆœˆ้–“ใฎๅข—ๆธ› -HTML - TITLE_VERSION || ใƒใƒผใ‚ธใƒงใƒณ -HTML - TITLE_WEEK_COMPARISON || ็›ด่ฟ‘1ๅ‘จ้–“ใงใฎๆฏ”่ผƒ -HTML - TITLE_WORLD || ใƒฏใƒผใƒซใƒ‰ใฎใƒญใƒผใƒ‰ๆ•ฐ -HTML - TITLE_WORLD_PLAYTIME || ใƒฏใƒผใƒซใƒ‰ใ”ใจใฎใƒ—ใƒฌใ‚คๆ™‚้–“ -HTML - TITLE_WORST_PING || ๆœ€ไฝŽPingๅ€ค -HTML - TOTAL_ACTIVE_TEXT || ็ดฏ่จˆๆดปๅ‹•ๆ™‚้–“ -HTML - TOTAL_AFK || ็ดฏ่จˆ้›ขๅธญๆ™‚้–“ -HTML - TOTAL_PLAYERS || ๅ…จใƒ—ใƒฌใ‚คใƒคใƒผๆ•ฐ -HTML - UNIQUE_CALENDAR || ๆŽฅ็ถšใ—ใŸใƒ—ใƒฌใ‚คใƒคใƒผใฎ็ทๆ•ฐ: -HTML - UNIT_CHUNKS || ใƒใƒฃใƒณใ‚ฏ -HTML - UNIT_ENTITIES || ใ‚จใƒณใƒ†ใ‚ฃใƒ†ใ‚ฃๆ•ฐ -HTML - UNIT_NO_DATA || ใƒ‡ใƒผใ‚ฟใชใ— -HTML - UNIT_THE_PLAYERS || ใƒ—ใƒฌใ‚คใƒคใƒผ -HTML - USER_AND_PASS_NOT_SPECIFIED || ใƒฆใƒผใ‚ถใƒผใจใƒ‘ใ‚นใƒฏใƒผใƒ‰ใŒๅ…ฅๅŠ›ใ•ใ‚Œใฆใพใ›ใ‚“ -HTML - USER_DOES_NOT_EXIST || ๅ…ฅๅŠ›ใ•ใ‚ŒใŸใƒฆใƒผใ‚ถใƒผใฏๅญ˜ๅœจใ—ใพใ›ใ‚“ -HTML - USER_INFORMATION_NOT_FOUND || Registration failed, try again (The code expires after 15 minutes) -HTML - USER_PASS_MISMATCH || ๅ…ฅๅŠ›ใ•ใ‚ŒใŸใƒฆใƒผใ‚ถใƒผๅใจใƒ‘ใ‚นใƒฏใƒผใƒ‰ใŒ้–“้•ใฃใฆใ„ใพใ™ -HTML - Version Change log || View Changelog -HTML - Version Current || You have version ${0} -HTML - Version Download || Download Plan-${0}.jar -HTML - Version Update || Update -HTML - Version Update Available || Version ${0} is Available! -HTML - Version Update Dev || This version is a DEV release. -HTML - Version Update Info || A new version has been released and is now available for download. -HTML - WARNING_NO_GAME_SERVERS || Some data requires Plan to be installed on game servers. -HTML - WARNING_NO_GEOLOCATIONS || Geolocation gathering needs to be enabled in the config (Accept GeoLite2 EULA). -HTML - WARNING_NO_SPONGE_CHUNKS || Chunks unavailable on Sponge -HTML - WITH || ๆญปไบกๅŽŸๅ›  -HTML ERRORS - ACCESS_DENIED_403 || ใ‚ขใ‚ฏใ‚ปใ‚นใŒๆ‹’ๅฆใ•ใ‚Œใพใ—ใŸ -HTML ERRORS - AUTH_FAIL_TIPS_401 || - ็™ป้Œฒใ—ใŸใƒฆใƒผใ‚ถใƒผใ‚’ใ€Œ/plan register ใ€ใง็ขบ่ชใงใใพใ™ใ€‚
- ๅ…ฅๅŠ›ใ—ใŸใƒฆใƒผใ‚ถใƒผๅใจใƒ‘ใ‚นใƒฏใƒผใƒ‰ใŒๆญฃใ—ใ„ใ“ใจใ‚’็ขบ่ชใ—ใฆไธ‹ใ•ใ„
- ใƒฆใƒผใ‚ถใƒผๅใจใƒ‘ใ‚นใƒฏใƒผใƒ‰ใฏๅคงๆ–‡ๅญ—ใจๅฐๆ–‡ๅญ—ใŒๅŒบๅˆฅใ•ใ‚Œใฆใ„ใพใ™

ใƒ‘ใ‚นใƒฏใƒผใƒ‰ใ‚’ๅฟ˜ใ‚ŒใŸๅ ดๅˆใฏใ€็ฎก็†่€…ใซๅคใ„ใƒฆใƒผใ‚ถใƒผใ‚’ๅ‰Š้™คใ—ใฆๆ–ฐใ—ใใƒฆใƒผใ‚ถใƒผใ‚’ๅ†็™ป้Œฒใ™ใ‚‹ใ‚ˆใ†ไพ้ ผใ—ใฆไธ‹ใ•ใ„ -HTML ERRORS - AUTHENTICATION_FAILED_401 || ่ช่จผใซๅคฑๆ•—ใ—ใพใ—ใŸ -HTML ERRORS - FORBIDDEN_403 || ้–ฒ่ฆง็ฆๆญข -HTML ERRORS - NO_SERVERS_404 || ใƒชใ‚ฏใ‚จใ‚นใƒˆใ‚’ๅ‡ฆ็†ใ™ใ‚‹ใ‚ตใƒผใƒใƒผใŒใ‚ชใƒณใƒฉใ‚คใƒณใงใฏใ‚ใ‚Šใพใ›ใ‚“ -HTML ERRORS - NOT_FOUND_404 || ใƒšใƒผใ‚ธใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“ใงใ—ใŸ -HTML ERRORS - NOT_PLAYED_404 || ใƒ—ใƒฌใ‚คใƒคใƒผใฏใ“ใฎใ‚ตใƒผใƒใƒผใงใƒ—ใƒฌใ‚คใ—ใฆใ„ใพใ›ใ‚“ -HTML ERRORS - PAGE_NOT_FOUND_404 || ใƒšใƒผใ‚ธใฏๅญ˜ๅœจใ—ใพใ›ใ‚“ -HTML ERRORS - UNAUTHORIZED_401 || ๆœช่ช่จผ็Šถๆ…‹ใงใ™ -HTML ERRORS - UNKNOWN_PAGE_404 || ใƒชใƒณใ‚ฏใŒ้–“้•ใฃใฆใ„ใพใ™ใ€ใ‚ณใƒžใƒณใƒ‰็ญ‰ใ‚’ไฝฟ็”จใ—URLใ‚’็ขบ่ชใ—ใฆไธ‹ใ•ใ„ใ€‚ URLไพ‹:

/player/{uuid/name}
/server/{uuid/name/id}

-HTML ERRORS - UUID_404 || ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นๅ†…ใซใƒ—ใƒฌใƒคใƒผใฎUUIDใŒๅญ˜ๅœจใ—ใพใ›ใ‚“ -In Depth Help - /plan db || ็•ฐใชใ‚‹ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใฎใ‚ตใƒ–ใ‚ณใƒžใƒณใƒ‰ใ‚’ไฝฟ็”จใ™ใ‚‹ใ“ใจใงใ€ๆง˜ใ€…ใชๆ–นๆณ•ใงใƒ‡ใƒผใ‚ฟใ‚’ๅค‰ๆ›ด/ๆ›ดๆ–ฐ/ๅ‰Š้™คใ—ใพใ™ -In Depth Help - /plan db backup || SQLiteใซใ‚ˆใฃใฆใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ‚’ใƒ•ใ‚กใ‚คใƒซใซใƒใƒƒใ‚ฏใ‚ขใƒƒใƒ—ใ—ใพใ™ -In Depth Help - /plan db clear || ๅ…จใฆใฎPlanใฎใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใƒ†ใƒผใƒ–ใƒซใ‚’ๅ‰Š้™คใ—ใพใ™ใ€‚ใ“ใฎๅ‡ฆ็†ใซใ‚ˆใฃใฆPlanใฎใƒ‡ใƒผใ‚ฟใฏๅ…จใฆๅ‰Š้™คใ•ใ‚Œใพใ™ -In Depth Help - /plan db hotswap || ใ‚ณใƒณใƒ•ใ‚ฃใ‚ฐใฎ่จญๅฎšใ‚„็•ฐใชใ‚‹ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ‚’่จญๅฎšๆ™‚ใ€่จญๅฎšใŒๆญฃใ—ใๅๆ˜ ใ•ใ‚Œใ‚‹ใ‚ˆใ†ใซใƒ—ใƒฉใ‚ฐใ‚คใƒณใ‚’ใƒชใƒญใƒผใƒ‰ใ—ใพใ™ -In Depth Help - /plan db move || ไป–ใฎใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใฎใƒ‡ใƒผใ‚ฟใ‚’ๅˆฅใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใซ็งปๅ‹•ใ•ใ›ใพใ™ใ€‚ใ“ใฎๆ™‚ใ€ๅˆฅใฎใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นๅ†…ใฎใƒ‡ใƒผใ‚ฟใฏไธŠๆ›ธใใ•ใ‚Œใพใ™ -In Depth Help - /plan db remove || ็พๅœจไฝฟ็”จใ—ใฆใ„ใ‚‹ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ‹ใ‚‰ใƒ—ใƒฌใ‚คใƒคใƒผใจใƒชใƒณใ‚ฏใ—ใฆใ„ใ‚‹ใƒ‡ใƒผใ‚ฟใ‚’ๅ…จใฆๅ‰Š้™คใ—ใพใ™ -In Depth Help - /plan db restore || SQLiteใฎใƒใƒƒใ‚ฏใ‚ขใƒƒใƒ—ใƒ•ใ‚กใ‚คใƒซใ‚’็”จใ„ใฆใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ‚’ๅพฉๅ…ƒใ—ใพใ™ใ€‚ใ“ใฎๆ™‚ใ€ๅพฉๅ…ƒๅ…ˆใฎใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นๅ†…ใฎใƒ‡ใƒผใ‚ฟใฏไธŠๆ›ธใใ•ใ‚Œใพใ™ -In Depth Help - /plan db uninstalled || ใ‚ตใƒผใƒใƒผใงไฝฟ็”จใ—ใฆใ„ใŸPlanใฎใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใจใฎๆŽฅ็ถšใ‚’่งฃ้™คใ—ใ€ใ‚ตใƒผใƒใƒผใฎใ‚ฏใ‚จใƒชใซ่กจ็คบใ•ใ‚Œใชใ„ใ‚ˆใ†ใซใ—ใพใ™ -In Depth Help - /plan disable || Planใ‚‚ใ—ใใฏใใฎไธ€้ƒจใ‚’ใƒชใƒญใƒผใƒ‰/ใƒชใ‚นใ‚ฟใƒผใƒˆใ‚ณใƒžใƒณใƒ‰ใ‚’ไฝฟ็”จใ™ใ‚‹ใพใงใ€็„กๅŠนๅŒ–ใ—ใพใ™ -In Depth Help - /plan export || ใ‚ณใƒณใƒ•ใ‚ฃใ‚ฐใงๆŒ‡ๅฎšใ—ใŸๅ‡บๅŠ›ๅ…ˆใซใƒ‡ใƒผใ‚ฟใ‚’ใ‚จใ‚ฏใ‚นใƒใƒผใƒˆใ—ใพใ™ -In Depth Help - /plan import || ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใซใƒ‡ใƒผใ‚ฟใ‚’ใƒญใƒผใƒ‰ใ™ใ‚‹ใŸใ‚ใฎใ‚คใƒณใƒใƒผใƒˆใ‚’่กŒใ„ใพใ™ -In Depth Help - /plan info || Planใฎ็พๅœจใฎ็Šถๆ…‹ใ‚’่กจ็คบใ—ใพใ™ -In Depth Help - /plan ingame || ใ‚ฒใƒผใƒ ๅ†…ใซใ„ใ‚‹ใƒ—ใƒฌใ‚คใƒคใƒผใซ้–ขใ™ใ‚‹ๆƒ…ๅ ฑใ‚’่กจ็คบใ—ใพใ™ใ€‚ -In Depth Help - /plan json || ใƒ—ใƒฌใ‚คใƒคใƒผใฎใƒ‡ใƒผใ‚ฟใ‚’Jsonใงใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ใงใใ‚‹ใ‚ˆใ†ใซใ—ใพใ™ใ€‚ใ“ใฎJsonใซใฏๅ…จใฆใฎใƒ—ใƒฌใ‚คใƒคใƒผใƒ‡ใƒผใ‚ฟใŒๆ ผ็ดใ•ใ‚Œใฆใ„ใพใ™ -In Depth Help - /plan logout || Give username argument to log out another user from the panel, give * as argument to log out everyone. -In Depth Help - /plan network || ใ€Œ/networkใ€ใƒšใƒผใ‚ธใธใฎใƒชใƒณใ‚ฏใ‚’ๅ–ๅพ—ใ—ใพใ™ใ€ใ“ใ‚Œใฏใ€Œใƒใƒƒใƒˆใƒฏใƒผใ‚ฏใ€ใŒๆœ‰ๅŠนๆ™‚ใฎใฟๅฎŸ่กŒใ•ใ‚Œใพใ™ -In Depth Help - /plan player || ็‰นๅฎšใ‚‚ใ—ใใฏใ€็พๅœจใฎใƒ—ใƒฌใ‚คใƒคใƒผใฎใ€Œ/playerใ€ใƒšใƒผใ‚ธใธใฎใƒชใƒณใ‚ฏใ‚’ๅ–ๅพ—ใ—ใพใ™ -In Depth Help - /plan players || ใƒ—ใƒฌใ‚คใƒคใƒผไธ€่ฆงใฎใ€Œ/playerใ€ใƒšใƒผใ‚ธใธใฎใƒชใƒณใ‚ฏใ‚’ๅ–ๅพ—ใ—ใพใ™ -In Depth Help - /plan register || ๅผ•ๆ•ฐใชใ—ใงๅฎŸ่กŒใ•ใ‚ŒใŸๅ ดๅˆใ€็™ป้Œฒใƒšใƒผใ‚ธใธใฎใƒชใƒณใ‚ฏใŒๅ–ๅพ—ใงใใพใ™ใ€‚ๅผ•ๆ•ฐใ€Œ --code [ใ‚ณใƒผใƒ‰]ใ€ใ‚’ไฝฟ็”จใ—ใฆๅฎŸ่กŒใ™ใ‚‹ใ“ใจใง็™ป้Œฒใ—ใŸใƒฆใƒผใ‚ถใƒผใ‚’ๅ–ๅพ—ใงใใพใ™ใ€‚ -In Depth Help - /plan reload || ใ“ใฎใƒ—ใƒฉใ‚ฐใ‚คใƒณใ‚’ไธ€ๅบฆ็„กๅŠนใซใ—ใ€ๆœ‰ๅŠนใซใ—ใพใ™ใ€‚ใ“ใฎๆ™‚ใ€ใ‚ณใƒณใƒ•ใ‚ฃใ‚ฐใฎๅค‰ๆ›ดใŒๅๆ˜ ใ•ใ‚Œใพใ™ -In Depth Help - /plan search || ๅๅ‰ใŒไธ€่‡ดใ‚‚ใ—ใใฏใ€้ƒจๅˆ†ไธ€่‡ดใ™ใ‚‹ๅ…จใฆใฎใƒ—ใƒฌใ‚คใƒคใƒผใ‚’่กจ็คบใ—ใพใ™ -In Depth Help - /plan server || ็‰นๅฎšใฎใ‚ตใƒผใƒใƒผใฎใ€Œ/serverใ€ใƒšใƒผใ‚ธใธใฎใƒชใƒณใ‚ฏใ‚’ๅ–ๅพ—ใ—ใพใ™ใ€‚ๅผ•ๆ•ฐใŒๆœชๆŒ‡ๅฎšใฎๅ ดๅˆใ€็พๅœจใฎใ‚ตใƒผใƒใƒผใฎใ€Œ/serverใ€ใƒšใƒผใ‚ธใธใฎใƒชใƒณใ‚ฏใซใชใ‚Šใพใ™ -In Depth Help - /plan servers || ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นๅ†…ใซๅญ˜ๅœจใ™ใ‚‹ๅ…จใ‚ตใƒผใƒใƒผใฎIDใ€ๅๅ‰ใ€UUIDใ‚’่กจ็คบใ—ใพใ™ -In Depth Help - /plan unregister || ๅˆฅใฎใƒฆใƒผใ‚ถใƒผใฎ็™ป้Œฒใ‚’่งฃ้™คใ—ใพใ™ใ€‚ๅผ•ๆ•ฐใŒๆœชๆŒ‡ๅฎšใฎๅ ดๅˆใ€ใƒฆใƒผใ‚ถใƒผใซใƒชใƒณใ‚ฏใ•ใ‚ŒใŸใฎใƒ—ใƒฌใ‚คใƒคใƒผใฎ็™ป้Œฒใ‚’่งฃ้™คใ—ใพใ™ใ€‚ -In Depth Help - /plan users || ใ‚ฆใ‚งใƒ–ใƒฆใƒผใ‚ถใƒผใฎไธ€่ฆงใ‚’่กจใง่กจ็คบใ—ใพใ™ -Manage - Confirm Overwrite || ${0}ใฎใƒ‡ใƒผใ‚ฟใฏไธŠๆ›ธใใ•ใ‚Œใพใ™! -Manage - Confirm Removal || ${0}ใฎใƒ‡ใƒผใ‚ฟใฏๅ‰Š้™คใ•ใ‚Œใพใ™! -Manage - Fail || > ยงcไฝ•ใ‹ใŒใ†ใพใใ„ใใพใ›ใ‚“ใงใ—ใŸ: ${0} -Manage - Fail File not found || > ยงcใ€Œ${0}ใ€ใซใƒ•ใ‚กใ‚คใƒซใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“ใงใ—ใŸ -Manage - Fail Incorrect Database || > ยงcใ€Œ${0}ใ€ใฏใ‚ตใƒใƒผใƒˆใ•ใ‚Œใฆใ„ใชใ„ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใงใ™ -Manage - Fail No Exporter || ยงeใ‚จใ‚ฏใ‚นใƒใƒผใ‚ฟใƒผ ใ€Œ${0}ใ€ใŒๅญ˜ๅœจใ—ใพใ›ใ‚“ -Manage - Fail No Importer || ยงeใ‚คใƒณใƒใƒผใ‚ฟใƒผ ใ€Œ${0}ใ€ใŒๅญ˜ๅœจใ—ใพใ›ใ‚“ -Manage - Fail No Server || ๆŒ‡ๅฎšใ•ใ‚ŒใŸใƒ‘ใƒฉใƒกใƒผใ‚ฟใƒผใ‚’ๆŒใคใ‚ตใƒผใƒใƒผใŒๅญ˜ๅœจใ—ใพใ›ใ‚“ใงใ—ใŸ -Manage - Fail Same Database || > ยงcๅŒใ˜ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ‚’ๆ“ไฝœใ™ใ‚‹ใ“ใจใฏใงใใพใ›ใ‚“๏ผ -Manage - Fail Same server || ใ“ใฎใ‚ตใƒผใƒใƒผใ‚’ใ‚ขใƒณใ‚คใƒณใ‚นใƒˆใƒผใƒซใ™ใ‚‹ใ‚ตใƒผใƒใƒผใจใ—ใฆๆŒ‡ๅฎšใ™ใ‚‹ใ“ใจใฏใงใใพใ›ใ‚“(ใ‚ใชใŸใŒใ“ใฎใ‚ตใƒผใƒใƒผใซใƒญใ‚ฐใ‚คใƒณใ—ใฆใ„ใ‚‹ใŸใ‚) -Manage - Fail, Confirmation || > ยงcๅฎŸ่กŒใ‚’็ขบ่ชใ™ใ‚‹ใŸใ‚ใซๅผ•ๆ•ฐใ€Œ-aใ€ใ‚’่ฟฝๅŠ ใ—ใพใ™: ${0} -Manage - List Importers || ใ‚คใƒณใƒใƒผใ‚ฟใƒผ: -Manage - Progress || ${0} / ${1} ใ‚’ๅ‡ฆ็†ไธญ.. -Manage - Remind HotSwap || ยงeๆ–ฐใ—ใ„ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใซไบคๆ›ใ™ใ‚‹ใ“ใจใ‚’ๅฟ˜ใ‚Œใชใ„ใงไธ‹ใ•ใ„(/plan db hotswap ${0})ใ€‚ใใ—ใฆใ€ใƒ—ใƒฉใ‚ฐใ‚คใƒณใ‚’ใƒชใƒญใƒผใƒ‰ใ—ใฆไธ‹ใ•ใ„ -Manage - Start || > ยง2ใƒ‡ใƒผใ‚ฟใ‚’ๅ‡ฆ็†ไธญใงใ™.. -Manage - Success || > ยงaๆˆๅŠŸใ—ใพใ—ใŸ! -Negative || ใชใ„ -Positive || ใ‚ใ‚‹ -Today || 'ๆœฌๆ—ฅ' -Unavailable || ๅˆฉ็”จไธๅฏ -Unknown || ไธๆ˜Ž -Version - DEV || ใ“ใฎใƒใƒผใ‚ธใƒงใƒณใฏ้–‹็™บ็‰ˆใงใ™ -Version - Latest || ๆœ€ๆ–ฐ็‰ˆใฎใ€ŒPlanใ€ใ‚’ไฝฟ็”จใ—ใฆใ„ใพใ™ -Version - New || ๆ–ฐใ—ใ„ใƒใƒผใ‚ธใƒงใƒณใฎ${0}ใŒๆฌกใฎURLใงๅ…ฅๆ‰‹ๅฏ่ƒฝใงใ™ ${1} -Version - New (old) || ๆ–ฐใ—ใ„ใƒใƒผใ‚ธใƒงใƒณใฏๆฌกใฎURLใงๅ…ฅๆ‰‹ๅฏ่ƒฝใงใ™${0} -Version FAIL - Read info (old) || ๆ–ฐใ—ใ„ใƒใƒผใ‚ธใƒงใƒณใฎใƒใ‚งใƒƒใ‚ฏใซๅคฑๆ•—ใ—ใพใ—ใŸ -Version FAIL - Read versions.txt || Github/versions.txtใซๅญ˜ๅœจใ™ใ‚‹ใƒใƒผใ‚ธใƒงใƒณๆƒ…ๅ ฑใฎใƒญใƒผใƒ‰ใซๅคฑๆ•—ใ—ใพใ—ใŸ -Web User Listing || ยง2${0} ยง7: ยงf${1} -WebServer - Notify HTTP || Webใ‚ตใƒผใƒใƒผ: ่จผๆ˜Žๆ›ธใŒๅญ˜ๅœจใพใ›ใ‚“ -> HTTPใ‚ตใƒผใƒใƒผใ‚’ไฝฟ็”จใ—ใพใ™ -WebServer - Notify HTTP User Auth || Webใ‚ตใƒผใƒใƒผ: ใƒฆใƒผใ‚ถใƒผ่ช่จผใ‚’็„กๅŠนใซใ—ใพใ™ (HTTP็ตŒ็”ฑใ ใจๅฎ‰ๅ…จใงใฏใชใ„ใŸใ‚ใงใ™) -WebServer - Notify HTTPS User Auth || Webใ‚ตใƒผใƒใƒผ: ใƒฆใƒผใ‚ถใƒผ่ช่จผใ‚’็„กๅŠนใซใ—ใพใ™! (Configใง็„กๅŠนๅŒ–ใ•ใ‚Œใฆใ„ใพใ™) -Webserver - Notify IP Whitelist || Webใ‚ตใƒผใƒใƒผ: IPใฎใƒ›ใƒฏใ‚คใƒˆใƒชใ‚นใƒˆใŒๆœ‰ๅŠนใซใชใฃใฆใ„ใพใ™ -Webserver - Notify IP Whitelist Block || Webใ‚ตใƒผใƒใƒผ: ใ€Œ${0}ใ€ใฎใ€Œ${1}ใ€ใธใฎใ‚ขใ‚ฏใ‚ปใ‚นใŒๆ‹’ๅฆใ•ใ‚Œใพใ—ใŸ(ใƒ›ใƒฏใ‚คใƒˆใƒชใ‚นใƒˆใซใฏๆœช็™ป้Œฒใงใ™) -WebServer - Notify no Cert file || Webใ‚ตใƒผใƒใƒผ: ๆฌกใฎใƒ‘ใ‚นใซไฟๅญ˜ใ•ใ‚ŒใŸ่ช่จผใ‚ญใƒผใƒ•ใ‚กใ‚คใƒซใŒๅญ˜ๅœจใ—ใพใ›ใ‚“: ${0} -WebServer - Notify Using Proxy || Webใ‚ตใƒผใƒใƒผ: Proxy-mode HTTPSใŒๆœ‰ๅŠนใซใชใฃใฆใ„ใพใ™ใ€ใƒชใƒใƒผใ‚นใƒ—ใƒญใ‚ญใ‚ทใŒHTTPSใ‚’ไฝฟ็”จใ—ใฆใƒซใƒผใƒ†ใ‚ฃใƒณใ‚ฐใ•ใ‚Œใฆใ„ใ‚‹ใ“ใจใจใ€PlanใฎAlternative_IP.AddressใŒใƒ—ใƒญใ‚ญใ‚ทใ‚’ๆŒ‡ๅฎšใ—ใฆใ„ใ‚‹ใ“ใจใ‚’็ขบ่ชใ—ใฆใใ ใ•ใ„ใ€‚ -WebServer FAIL - EOF || Webใ‚ตใƒผใƒใƒผ: ่จผๆ˜Žๆ›ธใƒ•ใ‚กใ‚คใƒซใ‚’ใƒญใƒผใƒ‰ไธญใซEOFใ‚จใƒฉใƒผใŒ็™บ็”Ÿใ—ใพใ—ใŸ (ใƒ•ใ‚กใ‚คใƒซใŒๆฎปใซใชใฃใฆใ„ใชใ„ใ‹็ขบ่ชใ—ใฆใใ ใ•ใ„) -WebServer FAIL - Port Bind || Webใ‚ตใƒผใƒใƒผ: ๅˆๆœŸๅŒ–ใŒๆญฃๅธธใซ็ต‚ไบ†ใ—ใพใ›ใ‚“ใงใ—ใŸใ€‚ใƒใƒผใƒˆ็•ชๅท(${0})ใฏไฝฟ็”จใ•ใ‚Œใฆใ„ใพใ›ใ‚“ใ‹? -WebServer FAIL - SSL Context || Webใ‚ตใƒผใƒใƒผ: SSLใ‚ณใƒณใƒ†ใ‚ญใ‚นใƒˆใฎๅˆๆœŸๅŒ–ใซๅคฑๆ•—ใ—ใพใ—ใŸใ€‚ -WebServer FAIL - Store Load || Webใ‚ตใƒผใƒใƒผ: SSL่จผๆ˜Žๆ›ธใฎใƒญใƒผใƒ‰ใซๅคฑๆ•—ใ—ใพใ—ใŸ -Yesterday || 'ๆ˜จๆ—ฅ' diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_JA.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_JA.yml new file mode 100644 index 000000000..f8be0226a --- /dev/null +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_JA.yml @@ -0,0 +1,669 @@ +403AccessDenied: "ใ‚ขใ‚ฏใ‚ปใ‚นใŒๆ‹’ๅฆใ•ใ‚Œใพใ—ใŸ" +command: + argument: + backupFile: + description: "ใƒใƒƒใ‚ฏใ‚ขใƒƒใƒ—ใƒ•ใ‚กใ‚คใƒซๅ (ๅคงๆ–‡ๅญ—ใจๅฐๆ–‡ๅญ—ใฏๅŒบๅˆฅใ•ใ‚Œใพใ™)" + name: "ใƒใƒƒใ‚ฏใ‚ขใƒƒใƒ—ใƒ•ใ‚กใ‚คใƒซ" + code: + description: "็™ป้Œฒใ‚’ๅฎŒไบ†ใ•ใ›ใ‚‹ใŸใ‚ใซๅฟ…่ฆใชใ‚ณใƒผใƒ‰" + name: "${code}" + dbBackup: + description: "ใƒใƒƒใ‚ฏใ‚ขใƒƒใƒ—ใ™ใ‚‹ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ€็ฉบๆฌ„ใฎๅ ดๅˆใฏ็พๅœจใฎใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใŒๆŒ‡ๅฎšใ•ใ‚Œใพใ™" + dbRestore: + description: "ใƒ‡ใƒผใ‚ฟใ‚’ใƒชใ‚นใƒˆใ‚ขใ™ใ‚‹ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ€็ฉบๆฌ„ใฎๅ ดๅˆใฏ็พๅœจใฎใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใŒๆŒ‡ๅฎšใ•ใ‚Œใพใ™" + dbTypeHotswap: + description: "ไฝฟ็”จใ™ใ‚‹ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚น" + dbTypeMoveFrom: + description: "ใƒ‡ใƒผใ‚ฟ็งป่กŒๅ…ƒใฎใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚น" + dbTypeMoveTo: + description: "ใƒ‡ใƒผใ‚ฟ็งป่กŒๅ…ˆใฎใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ€ไปฅๅ‰ใฎใ‚ณใƒžใƒณใƒ‰ใจๅŒใ˜็‰ฉใ‚’ไฝฟ็”จใ™ใ‚‹ใ“ใจใฏใงใใพใ›ใ‚“" + dbTypeRemove: + description: "ๅ…จใƒ‡ใƒผใ‚ฟใ‚’ๅ‰Š้™คใ™ใ‚‹ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚น" + exportKind: "ใ‚จใ‚ฏใ‚นใƒใƒผใƒˆ็ณป" + feature: + description: "ใ€Œ${0}ใ€ใ‚’็„กๅŠนๅŒ–ใ™ใ‚‹ๆฉŸ่ƒฝใฎๅๅ‰" + name: "ๆฉŸ่ƒฝ" + importKind: "ใ‚คใƒณใƒใƒผใƒˆ็ณป" + nameOrUUID: + description: "ใƒ—ใƒฌใ‚คใƒคใƒผใฎๅๅ‰ใ‚‚ใ—ใใฏUUID" + name: "ๅๅ‰/uuid" + removeDescription: "็พๅœจใฎใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ‹ใ‚‰ๅ‰Š้™คใ•ใ‚Œใ‚‹ใƒ—ใƒฌใ‚คใƒคใƒผใฎ่ญ˜ๅˆฅๅญ" + server: + description: "ใ‚ตใƒผใƒใƒผใฎๅๅ‰ใ‚‚ใ—ใใฏIDใ€UUID" + name: "ใ‚ตใƒผใƒใƒผ" + subcommand: + description: "ใ‚ณใƒžใƒณใƒ‰ใ‚’ใ‚ตใƒ–ใ‚ณใƒžใƒณใƒ‰็„กใ—ใงๅฎŸ่กŒใ™ใ‚‹ใ“ใจใงใƒ˜ใƒซใƒ—ใ‚’่ฆ‹ใ‚‹ใ“ใจใŒๅฏ่ƒฝใงใ™" + name: "ใ‚ตใƒ–ใ‚ณใƒžใƒณใƒ‰" + username: + description: "ไป–ใฎใƒฆใƒผใ‚ถใƒผๅใ€็ฉบๆฌ„ใฎๅ ดๅˆใฏใƒชใƒณใ‚ฏใ•ใ‚Œใฆใ„ใ‚‹ใƒ—ใƒฌใ‚คใƒคใƒผๅใŒๆŒ‡ๅฎšใ•ใ‚Œใพใ™" + name: "ใƒฆใƒผใ‚ถใƒผใƒใƒผใƒ " + confirmation: + accept: "็ขบๅฎš" + cancelNoChanges: "ใ‚ญใƒฃใƒณใ‚ปใƒซใ•ใ‚Œใพใ—ใŸใ€‚ใƒ‡ใƒผใ‚ฟใฎๅค‰ๆ›ดใฏ่กŒใ‚ใ‚Œใพใ›ใ‚“ใ€‚" + cancelNoUnregister: "ใ‚ญใƒฃใƒณใ‚ปใƒซใ•ใ‚Œใพใ—ใŸใ€‚ใ€Œ'${0}'ใ€ใฏ็™ป้Œฒๆธˆใฟใงใ™ใ€‚" + confirm: "็ขบ่ช: " + dbClear: "ใ€Œ${0}ใ€ๅ†…ใซๅญ˜ๅœจใ™ใ‚‹Planใฎใƒ‡ใƒผใ‚ฟใ‚’ๅ‰Š้™คใ—ใ‚ˆใ†ใจใ—ใฆใ„ใพใ™" + dbOverwrite: "Planใฎใƒ‡ใƒผใ‚ฟใ€Œ${0}ใ€ใ‚’ใ€Œ${1}ใ€ใงไธŠๆ›ธใใ—ใ‚ˆใ†ใจใ—ใฆใ„ใพใ™" + dbRemovePlayer: "ใ€Œ${0}ใ€ใ‚’ใ€Œ${1}ใ€ใ‹ใ‚‰ๅ‰Š้™คใ—ใ‚ˆใ†ใจใ—ใฆใ„ใพใ™" + deny: "ใ‚ญใƒฃใƒณใ‚ปใƒซ" + expired: "ๆœ‰ๅŠนๆœŸ้™ๅˆ‡ใ‚ŒใฎใŸใ‚ใ€ๅ†ๅบฆใ‚ณใƒžใƒณใƒ‰ใ‚’ไฝฟ็”จใ—ใฆใใ ใ•ใ„" + unregister: "ใ€Œ${1}ใ€ใซใƒชใƒณใ‚ฏใ•ใ‚Œใฆใ„ใ‚‹ใ€Œ${0}ใ€ใ‚’่งฃ้™คใ—ใ‚ˆใ†ใจใ—ใฆใ„ใพใ™" + database: + creatingBackup: "ใ€Œ${1}ใ€ใฎใƒ‡ใƒผใ‚ฟใ‚’ๅซใ‚€ใƒใƒƒใ‚ฏใ‚ขใƒƒใƒ—ใ€Œ${0}.dbใ€ใ‚’ไฝœๆˆไธญ" + failDbNotOpen: "ยงcใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใฏ${0}ใงใ™ - ใ—ใฐใ‚‰ใใ—ใฆใ‹ใ‚‰ใ‚‚ใ†ไธ€ๅบฆใŠ่ฉฆใ—ไธ‹ใ•ใ„" + manage: + confirm: "> ยงcๅฎŸ่กŒใ‚’็ขบ่ชใ™ใ‚‹ใŸใ‚ใซๅผ•ๆ•ฐใ€Œ-aใ€ใ‚’่ฟฝๅŠ ใ—ใพใ™: ${0}" + confirmOverwrite: "${0}ใฎใƒ‡ใƒผใ‚ฟใฏไธŠๆ›ธใใ•ใ‚Œใพใ™!" + confirmPartialRemoval: "Join Address Data for Server ${0} in ${1} will be removed!" + confirmRemoval: "${0}ใฎใƒ‡ใƒผใ‚ฟใฏๅ‰Š้™คใ•ใ‚Œใพใ™!" + fail: "> ยงcไฝ•ใ‹ใŒใ†ใพใใ„ใใพใ›ใ‚“ใงใ—ใŸ: ${0}" + failFileNotFound: "> ยงcใ€Œ${0}ใ€ใซใƒ•ใ‚กใ‚คใƒซใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“ใงใ—ใŸ" + failIncorrectDB: "> ยงcใ€Œ${0}ใ€ใฏใ‚ตใƒใƒผใƒˆใ•ใ‚Œใฆใ„ใชใ„ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใงใ™" + failNoServer: "ๆŒ‡ๅฎšใ•ใ‚ŒใŸใƒ‘ใƒฉใƒกใƒผใ‚ฟใƒผใ‚’ๆŒใคใ‚ตใƒผใƒใƒผใŒๅญ˜ๅœจใ—ใพใ›ใ‚“ใงใ—ใŸ" + failSameDB: "> ยงcๅŒใ˜ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ‚’ๆ“ไฝœใ™ใ‚‹ใ“ใจใฏใงใใพใ›ใ‚“๏ผ" + failSameServer: "ใ“ใฎใ‚ตใƒผใƒใƒผใ‚’ใ‚ขใƒณใ‚คใƒณใ‚นใƒˆใƒผใƒซใ™ใ‚‹ใ‚ตใƒผใƒใƒผใจใ—ใฆๆŒ‡ๅฎšใ™ใ‚‹ใ“ใจใฏใงใใพใ›ใ‚“(ใ‚ใชใŸใŒใ“ใฎใ‚ตใƒผใƒใƒผใซใƒญใ‚ฐใ‚คใƒณใ—ใฆใ„ใ‚‹ใŸใ‚)" + hotswap: "ยงeๆ–ฐใ—ใ„ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใซไบคๆ›ใ™ใ‚‹ใ“ใจใ‚’ๅฟ˜ใ‚Œใชใ„ใงไธ‹ใ•ใ„(/plan db hotswap ${0})ใ€‚ใใ—ใฆใ€ใƒ—ใƒฉใ‚ฐใ‚คใƒณใ‚’ใƒชใƒญใƒผใƒ‰ใ—ใฆไธ‹ใ•ใ„" + importers: "ใ‚คใƒณใƒใƒผใ‚ฟใƒผ:" + preparing: "Preparing.." + progress: "${0} / ${1} ใ‚’ๅ‡ฆ็†ไธญ.." + start: "> ยง2ใƒ‡ใƒผใ‚ฟใ‚’ๅ‡ฆ็†ไธญใงใ™.." + success: "> ยงaๆˆๅŠŸใ—ใพใ—ใŸ!" + playerRemoval: "ใ€Œ${1}ใ€ใ‹ใ‚‰ใ€Œ${0}ใ€ใฎใƒ‡ใƒผใ‚ฟใ‚’ๅ‰Š้™คไธญ.." + removal: "ใ€Œ${0}ใ€ใ‹ใ‚‰Planใฎใƒ‡ใƒผใ‚ฟใ‚’ๅ‰Š้™คไธญ.." + serverUninstalled: "ยงaใ‚ตใƒผใƒใƒผใŒใพใ ่ฟฝๅŠ ไธญใฎๅ ดๅˆใ€ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใซ่ฟฝๅŠ ๆธˆใฟใจใ—ใฆ่กจ็คบใ•ใ‚Œใพใ™" + unregister: "ใ€Œ${0}ใ€ใฎ็™ป้Œฒใ‚’่งฃ้™คไธญ.." + warnDbNotOpen: "ยงeใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใฏ${0}ใงใ™ - ๆƒณๅฎšไปฅไธŠใฎๆ™‚้–“ใŒใ‹ใ‹ใ‚‹ใ‹ใ‚‚ใ—ใ‚Œใพใ›ใ‚“" + write: "ใ€Œ${0}ใ€ใซๆ›ธใ่พผใฟไธญใงใ™.." + fail: + emptyString: "ๆคœ็ดขๆฌ„ใฏ็ฉบๆฌ„ใซใงใใพใ›ใ‚“" + invalidArguments: "ใ€Œ${0}ใ€ใจใ—ใพใ™ใ€‚ใ€Œ${1}ใ€" + invalidUsername: "ยงcใ“ใฎใƒฆใƒผใ‚ถใƒผใฏUUIDใ‚’ๆ‰€ๆŒใ—ใฆใ„ใพใ›ใ‚“" + missingArguments: "ยงcๅผ•ๆ•ฐใ€Œ(${0}) ${1}ใ€ใŒๅฟ…่ฆใงใ™" + missingFeature: "ยงeใ“ใฎๆฉŸ่ƒฝใฏ็พๅœจไฝฟ็”จใ•ใ‚Œใฆใ„ใพใ›ใ‚“๏ผ (็พๅœจใ€ใ€Œ${0}ใ€ใ‚’ใ‚ตใƒใƒผใƒˆใ—ใฆใ„ใพใ™)" + missingLink: "ๅ…ฅๅŠ›ใ•ใ‚ŒใŸใƒฆใƒผใ‚ถใƒผใฏใ‚ใชใŸใฎใ‚ขใ‚ซใ‚ฆใƒณใƒˆใซใƒชใƒณใ‚ฏใ•ใ‚Œใฆใ„ใชใ„ใŸใ‚ใ€ใใ‚Œใ‚’ๅ‰Š้™คใ™ใ‚‹ๆจฉ้™ใฏใ‚ใ‚Šใพใ›ใ‚“" + noPermission: "ยงcใ‚ใชใŸใซใฏๅฎŸ่กŒใ™ใ‚‹ๆจฉ้™ใŒใ‚ใ‚Šใพใ›ใ‚“" + onAccept: "็ขบๅฎšไธญใซไปฅไธ‹ใฎใ‚จใƒฉใƒผใŒ็™บ็”Ÿใ—ใพใŸ: ${0}" + onDeny: "ใ‚ญใƒฃใƒณใ‚ปใƒซไธญใซไปฅไธ‹ใฎใ‚จใƒฉใƒผใŒ็™บ็”Ÿใ—ใพใŸ: ${0}" + playerNotFound: "ใƒ—ใƒฌใ‚คใƒคใƒผใ€Œ${0}ใ€ใฏUUIDใŒ็„กใ„ใŸใ‚่ฆ‹ใคใ‘ใ‚‹ใ“ใจใŒใงใใพใ›ใ‚“ใงใ—ใŸ" + playerNotInDatabase: "ใƒ—ใƒฌใ‚คใƒคใƒผใ€Œ${0}ใ€ใฏใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นๅ†…ใซใฏๅญ˜ๅœจใ—ใพใ›ใ‚“" + seeConfig: "ใ€Œconfig.ymlใ€ๅ†…ใฎใ€Œ${0}ใ€ใ‚’ๅ‚็…งใ—ใฆใใ ใ•ใ„" + serverNotFound: "ใ‚ตใƒผใƒใƒผใ€Œ${0}ใ€ใฏใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นๅ†…ใซใฏๅญ˜ๅœจใ—ใพใ›ใ‚“" + tooManyArguments: "ยงcไธ€ใคใฎๅผ•ๆ•ฐใ€Œ${1}ใ€ใŒๅฟ…่ฆใงใ™" + unknownUsername: "ยงcๅ…ฅๅŠ›ใ•ใ‚ŒใŸใƒฆใƒผใ‚ถใƒผใฏBukkit/Spigotใ‚ตใƒผใƒใƒผไธŠใซใ„ใพใ›ใ‚“" + webUserExists: "ยงcๅ…ฅๅŠ›ใ•ใ‚ŒใŸใƒฆใƒผใ‚ถใƒผๅใฏๆ—ขใซไฝฟใ‚ใ‚Œใฆใ„ใพใ™!" + webUserNotFound: "ยงcๅ…ฅๅŠ›ใ•ใ‚ŒใŸใƒฆใƒผใ‚ถใƒผใฏๅญ˜ๅœจใ—ใพใ›ใ‚“!" + footer: + help: "ยง7ใ‚ณใƒžใƒณใƒ‰ใ‚„ๅผ•ๆ•ฐใฎไธŠใซใ‚ซใƒผใ‚ฝใƒซใ‚’ๅˆใ‚ใ›ใ‚‹ใ‹ใ€Œ/${0} ?ใ€ใ‚’ไฝฟ็”จใ™ใ‚‹ใ“ใจใง่ฉณ็ดฐใ‚’็ขบ่ชใงใใพใ™" + general: + failNoExporter: "ยงeใ‚จใ‚ฏใ‚นใƒใƒผใ‚ฟใƒผ ใ€Œ${0}ใ€ใŒๅญ˜ๅœจใ—ใพใ›ใ‚“" + failNoImporter: "ยงeใ‚คใƒณใƒใƒผใ‚ฟใƒผ ใ€Œ${0}ใ€ใŒๅญ˜ๅœจใ—ใพใ›ใ‚“" + featureDisabled: "ยงaๆฌกใซใƒ—ใƒฉใ‚ฐใ‚คใƒณใŒใƒชใƒญใƒผใƒ‰ใ•ใ‚Œใ‚‹ใพใงไธ€ๆ™‚็š„ใซใ€Œ${0}ใ€ใ‚’็„กๅŠนใซใ—ใพใ—ใŸใ€‚" + noAddress: "ยงe่จญๅฎšใ—ใŸใ‚ขใƒ‰ใƒฌใ‚นใŒๅˆฉ็”จใงใใพใ›ใ‚“ใงใ—ใŸ - ใƒ•ใ‚ฉใƒผใƒซใƒใƒƒใ‚ฏใจใ—ใฆlocalhostใ‚’ไฝฟ็”จใ—ใฆใ„ใพใ™ใ€‚ใ€ŒAlternative_IPใ€ใฎ่จญๅฎšใ‚’่กŒใ„ใพใ™ใ€‚" + noWebuser: "ใ‚ฆใ‚งใƒ–ใƒฆใƒผใ‚ถใƒผใฎๆƒ…ๅ ฑใŒใชใ„ๅฏ่ƒฝๆ€งใŒใ‚ใ‚Šใพใ™ใ€ใ€Œ/plan register <ใƒ‘ใ‚นใƒฏใƒผใƒ‰>ใ€ใ‚’ไฝฟ็”จใ—ใฆใƒฆใƒผใ‚ถใƒผใ‚’็™ป้Œฒใ—ใฆไธ‹ใ•ใ„" + notifyWebUserRegister: "็™ป้ŒฒใŒๅฎŒไบ†ใ—ใพใ—ใŸ: '${0}' ๆจฉ้™ใƒฌใƒ™ใƒซ: ${1}" + pluginDisabled: "ยงaใ€ŒPlanใ€ใฏ็„กๅŠนใซใชใ‚Šใพใ—ใŸใ€‚ใ€Œreloadใ€ใ‚ณใƒžใƒณใƒ‰ใ‚’ไฝฟใฃใฆใƒ—ใƒฉใ‚ฐใ‚คใƒณใ‚’ๅ†่ตทๅ‹•ใงใใพใ™" + reloadComplete: "ยงaใƒชใƒญใƒผใƒ‰ใŒๅฎŒไบ†ใ—ใพใ—ใŸ" + reloadFailed: "ยงcใƒ—ใƒฉใ‚ฐใ‚คใƒณใฎใƒชใƒญใƒผใƒ‰ไธญใซไฝ•ใ‚‰ใ‹ใฎๅ•้กŒใŒ็™บ็”Ÿใ—ใพใ—ใŸใ€Bukkit/Spigotใ‚ตใƒผใƒใƒผใ‹BungeeCordใฎๅ†่ตทๅ‹•ใ‚’ใŠๅ‹งใ‚ใ—ใพใ™" + successWebUserRegister: "ยงaๆ–ฐ่ฆใƒฆใƒผใ‚ถใƒผใ€Œ(${0})ใ€ใฎ็™ป้ŒฒใซๆˆๅŠŸใ—ใพใ—ใŸ๏ผ" + webPermissionLevels: ">\ยง70: ๅ…จใฆใฎใƒšใƒผใ‚ธใซใ‚ขใ‚ฏใ‚ปใ‚นใงใใพใ™\ยง71:ใ€Œ/playersใ€ใจๅ…จใฆใฎใƒ—ใƒฌใ‚คใƒคใƒผใƒšใƒผใ‚ธใซใ‚ขใ‚ฏใ‚ปใ‚นใงใใพใ™\ยง72: ใ‚ฆใ‚งใƒ–ใƒฆใƒผใ‚ถใƒผใจๅŒใ˜ใƒฆใƒผใ‚ถใƒผๅใงใƒ—ใƒฌใ‚คใƒคใƒผใƒšใƒผใ‚ธใซใ‚ขใ‚ฏใ‚ปใ‚นใงใใพใ™\ยง73+:ๆจฉ้™ใ‚’ไฟๆŒใ—ใฆใ„ใพใ›ใ‚“" + webUserList: " ยง2${0} ยง7: ยงf${1}" + header: + analysis: "> ยง2ๅˆ†ๆž็ตๆžœ" + help: "> ยง2/${0}ใฎ่ฉณ็ดฐ" + info: "> ยง2ใƒ—ใƒฌใ‚คใƒคใƒผใฎๅˆ†ๆž็ตๆžœ" + inspect: "> ยง2ใƒ—ใƒฌใ‚คใƒคใƒผ: ยงf${0}" + network: "> ยง2ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏใƒšใƒผใ‚ธ" + players: "> ยง2ใƒ—ใƒฌใ‚คใƒคใƒผ" + search: "> ยง2${0} ยงf${1}ยง2 ใฎ็ตๆžœ:" + serverList: "Minecraft id::ๅๅ‰::uuid::version" + servers: "> ยง2ใ‚ตใƒผใƒใƒผ" + webUserList: "ใƒฆใƒผใ‚ถใƒผๅ::ๅˆฅใƒ—ใƒฌใ‚คใƒคใƒผใจใฎใƒชใƒณใ‚ฏ::ๆจฉ้™ใƒฌใƒ™ใƒซ" + webUsers: "> ยง2${0} ใ‚ฆใ‚งใƒ–ใƒฆใƒผใ‚ถใƒผ" + help: + database: + description: "Planใฎใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ‚’็ฎก็†ใ—ใพใ™" + inDepth: "็•ฐใชใ‚‹ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใฎใ‚ตใƒ–ใ‚ณใƒžใƒณใƒ‰ใ‚’ไฝฟ็”จใ™ใ‚‹ใ“ใจใงใ€ๆง˜ใ€…ใชๆ–นๆณ•ใงใƒ‡ใƒผใ‚ฟใ‚’ๅค‰ๆ›ด/ๆ›ดๆ–ฐ/ๅ‰Š้™คใ—ใพใ™" + dbBackup: + description: "ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใฎใƒ‡ใƒผใ‚ฟใ‚’ใƒ•ใ‚กใ‚คใƒซใซใƒใƒƒใ‚ฏใ‚ขใƒƒใƒ—ใ—ใพใ™" + inDepth: "SQLiteใซใ‚ˆใฃใฆใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ‚’ใƒ•ใ‚กใ‚คใƒซใซใƒใƒƒใ‚ฏใ‚ขใƒƒใƒ—ใ—ใพใ™" + dbClear: + description: "ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นๅ†…ใฎPlanใƒ‡ใƒผใ‚ฟใ‚’ๅ‰Š้™คใ—ใพใ™" + inDepth: "ๅ…จใฆใฎPlanใฎใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใƒ†ใƒผใƒ–ใƒซใ‚’ๅ‰Š้™คใ—ใพใ™ใ€‚ใ“ใฎๅ‡ฆ็†ใซใ‚ˆใฃใฆPlanใฎใƒ‡ใƒผใ‚ฟใฏๅ…จใฆๅ‰Š้™คใ•ใ‚Œใพใ™" + dbHotswap: + description: "ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ‚’้ซ˜้€Ÿใงๅค‰ๆ›ดใ—ใพใ™" + inDepth: "ใ‚ณใƒณใƒ•ใ‚ฃใ‚ฐใฎ่จญๅฎšใ‚„็•ฐใชใ‚‹ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ‚’่จญๅฎšๆ™‚ใ€่จญๅฎšใŒๆญฃใ—ใๅๆ˜ ใ•ใ‚Œใ‚‹ใ‚ˆใ†ใซใƒ—ใƒฉใ‚ฐใ‚คใƒณใ‚’ใƒชใƒญใƒผใƒ‰ใ—ใพใ™" + dbMove: + description: "ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚น้–“ใงใƒ‡ใƒผใ‚ฟใ‚’็งปๅ‹•ใ—ใพใ™" + inDepth: "ไป–ใฎใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใฎใƒ‡ใƒผใ‚ฟใ‚’ๅˆฅใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใซ็งปๅ‹•ใ•ใ›ใพใ™ใ€‚ใ“ใฎๆ™‚ใ€ๅˆฅใฎใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นๅ†…ใฎใƒ‡ใƒผใ‚ฟใฏไธŠๆ›ธใใ•ใ‚Œใพใ™" + dbRemove: + description: "็พๅœจไฝฟ็”จใ—ใฆใ„ใ‚‹ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ‹ใ‚‰ใƒ—ใƒฌใ‚คใƒคใƒผใƒ‡ใƒผใ‚ฟใ‚’ๅ‰Š้™คใ—ใพใ™" + inDepth: "็พๅœจไฝฟ็”จใ—ใฆใ„ใ‚‹ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ‹ใ‚‰ใƒ—ใƒฌใ‚คใƒคใƒผใจใƒชใƒณใ‚ฏใ—ใฆใ„ใ‚‹ใƒ‡ใƒผใ‚ฟใ‚’ๅ…จใฆๅ‰Š้™คใ—ใพใ™" + dbRestore: + description: "ใƒ•ใ‚กใ‚คใƒซใ‹ใ‚‰ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใซใƒ‡ใƒผใ‚ฟใ‚’ๅพฉๅ…ƒใ—ใพใ™" + inDepth: "SQLiteใฎใƒใƒƒใ‚ฏใ‚ขใƒƒใƒ—ใƒ•ใ‚กใ‚คใƒซใ‚’็”จใ„ใฆใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ‚’ๅพฉๅ…ƒใ—ใพใ™ใ€‚ใ“ใฎๆ™‚ใ€ๅพฉๅ…ƒๅ…ˆใฎใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นๅ†…ใฎใƒ‡ใƒผใ‚ฟใฏไธŠๆ›ธใใ•ใ‚Œใพใ™" + dbUninstalled: + description: "ใ‚ตใƒผใƒใƒผใซ่จญๅฎšใ•ใ‚Œใฆใ„ใ‚‹ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ‚’ๆœช่จญๅฎšใซใ—ใพใ™" + inDepth: "ใ‚ตใƒผใƒใƒผใงไฝฟ็”จใ—ใฆใ„ใŸPlanใฎใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใจใฎๆŽฅ็ถšใ‚’่งฃ้™คใ—ใ€ใ‚ตใƒผใƒใƒผใฎใ‚ฏใ‚จใƒชใซ่กจ็คบใ•ใ‚Œใชใ„ใ‚ˆใ†ใซใ—ใพใ™" + disable: + description: "Planใ‚‚ใ—ใใฏPlanใฎไธ€้ƒจใ‚’็„กๅŠนๅŒ–ใ—ใพใ™" + inDepth: "Planใ‚‚ใ—ใใฏใใฎไธ€้ƒจใ‚’ใƒชใƒญใƒผใƒ‰/ใƒชใ‚นใ‚ฟใƒผใƒˆใ‚ณใƒžใƒณใƒ‰ใ‚’ไฝฟ็”จใ™ใ‚‹ใพใงใ€็„กๅŠนๅŒ–ใ—ใพใ™" + export: + description: "ๆ‰‹ๅ‹•ใงHTMLใ‹Jsonใƒ•ใ‚กใ‚คใƒซใซใ‚จใ‚ฏใ‚นใƒใƒผใƒˆใ—ใพใ™" + inDepth: "ใ‚ณใƒณใƒ•ใ‚ฃใ‚ฐใงๆŒ‡ๅฎšใ—ใŸๅ‡บๅŠ›ๅ…ˆใซใƒ‡ใƒผใ‚ฟใ‚’ใ‚จใ‚ฏใ‚นใƒใƒผใƒˆใ—ใพใ™" + import: + description: "ใƒ‡ใƒผใ‚ฟใ‚’ใ‚คใƒณใƒใƒผใƒˆใ—ใพใ™" + inDepth: "ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใซใƒ‡ใƒผใ‚ฟใ‚’ใƒญใƒผใƒ‰ใ™ใ‚‹ใŸใ‚ใฎใ‚คใƒณใƒใƒผใƒˆใ‚’่กŒใ„ใพใ™" + info: + description: "Planใฎๆƒ…ๅ ฑใ‚’่กจ็คบใ—ใพใ™" + inDepth: "Planใฎ็พๅœจใฎ็Šถๆ…‹ใ‚’่กจ็คบใ—ใพใ™" + ingame: + description: "ใƒ—ใƒฌใ‚คใƒคใƒผๆƒ…ๅ ฑใ‚’ใ‚ฒใƒผใƒ ๅ†…ใง่กจ็คบใ—ใพใ™" + inDepth: "ใ‚ฒใƒผใƒ ๅ†…ใซใ„ใ‚‹ใƒ—ใƒฌใ‚คใƒคใƒผใซ้–ขใ™ใ‚‹ๆƒ…ๅ ฑใ‚’่กจ็คบใ—ใพใ™ใ€‚" + json: + description: "Jsonใงไฟๅญ˜ใ•ใ‚Œใฆใ„ใ‚‹ใƒ—ใƒฌใ‚คใƒคใƒผใƒ‡ใƒผใ‚ฟใ‚’่กจ็คบใ—ใพใ™" + inDepth: "ใƒ—ใƒฌใ‚คใƒคใƒผใฎใƒ‡ใƒผใ‚ฟใ‚’Jsonใงใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ใงใใ‚‹ใ‚ˆใ†ใซใ—ใพใ™ใ€‚ใ“ใฎJsonใซใฏๅ…จใฆใฎใƒ—ใƒฌใ‚คใƒคใƒผใƒ‡ใƒผใ‚ฟใŒๆ ผ็ดใ•ใ‚Œใฆใ„ใพใ™" + logout: + description: "Log out other users from the panel." + inDepth: "Give username argument to log out another user from the panel, give * as argument to log out everyone." + migrateToOnlineUuids: + description: "Migrate offline uuid data to online uuids" + network: + description: "ใ€Œใƒใƒƒใƒˆใƒฏใƒผใ‚ฏใ€ใฎใƒšใƒผใ‚ธใฎURLใ‚’่กจ็คบใ—ใพใ™" + inDepth: "ใ€Œ/networkใ€ใƒšใƒผใ‚ธใธใฎใƒชใƒณใ‚ฏใ‚’ๅ–ๅพ—ใ—ใพใ™ใ€ใ“ใ‚Œใฏใ€Œใƒใƒƒใƒˆใƒฏใƒผใ‚ฏใ€ใŒๆœ‰ๅŠนๆ™‚ใฎใฟๅฎŸ่กŒใ•ใ‚Œใพใ™" + player: + description: "ใ€Œใƒ—ใƒฌใ‚คใƒคใƒผใ€ใฎURLใ‚’่กจ็คบใ—ใพใ™" + inDepth: "็‰นๅฎšใ‚‚ใ—ใใฏใ€็พๅœจใฎใƒ—ใƒฌใ‚คใƒคใƒผใฎใ€Œ/playerใ€ใƒšใƒผใ‚ธใธใฎใƒชใƒณใ‚ฏใ‚’ๅ–ๅพ—ใ—ใพใ™" + players: + description: "ใ€Œใƒ—ใƒฌใ‚คใƒคใƒผใ€ใฎใƒšใƒผใ‚ธใฎURLใ‚’่กจ็คบใ—ใพใ™" + inDepth: "ใƒ—ใƒฌใ‚คใƒคใƒผไธ€่ฆงใฎใ€Œ/playerใ€ใƒšใƒผใ‚ธใธใฎใƒชใƒณใ‚ฏใ‚’ๅ–ๅพ—ใ—ใพใ™" + register: + description: "ใ‚ฆใ‚งใƒ–ใƒฆใƒผใ‚ถใƒผใ‚’็™ป้Œฒใ—ใพใ™" + inDepth: "ๅผ•ๆ•ฐใชใ—ใงๅฎŸ่กŒใ•ใ‚ŒใŸๅ ดๅˆใ€็™ป้Œฒใƒšใƒผใ‚ธใธใฎใƒชใƒณใ‚ฏใŒๅ–ๅพ—ใงใใพใ™ใ€‚ๅผ•ๆ•ฐใ€Œ --code [ใ‚ณใƒผใƒ‰]ใ€ใ‚’ไฝฟ็”จใ—ใฆๅฎŸ่กŒใ™ใ‚‹ใ“ใจใง็™ป้Œฒใ—ใŸใƒฆใƒผใ‚ถใƒผใ‚’ๅ–ๅพ—ใงใใพใ™ใ€‚" + reload: + description: "ใ€ŒPlanใ€ใ‚’ๅ†่ตทๅ‹•ใ—ใพใ™" + inDepth: "ใ“ใฎใƒ—ใƒฉใ‚ฐใ‚คใƒณใ‚’ไธ€ๅบฆ็„กๅŠนใซใ—ใ€ๆœ‰ๅŠนใซใ—ใพใ™ใ€‚ใ“ใฎๆ™‚ใ€ใ‚ณใƒณใƒ•ใ‚ฃใ‚ฐใฎๅค‰ๆ›ดใŒๅๆ˜ ใ•ใ‚Œใพใ™" + removejoinaddresses: + description: "Remove join addresses of a specified server" + search: + description: "ใƒ—ใƒฌใ‚คใƒคใƒผๅใ‚’ๆคœ็ดขใ—ใพใ™" + inDepth: "ๅๅ‰ใŒไธ€่‡ดใ‚‚ใ—ใใฏใ€้ƒจๅˆ†ไธ€่‡ดใ™ใ‚‹ๅ…จใฆใฎใƒ—ใƒฌใ‚คใƒคใƒผใ‚’่กจ็คบใ—ใพใ™" + server: + description: "ใ‚ตใƒผใƒใƒผใƒšใƒผใ‚ธใฎURLใ‚’่กจ็คบใ—ใพใ™" + inDepth: "็‰นๅฎšใฎใ‚ตใƒผใƒใƒผใฎใ€Œ/serverใ€ใƒšใƒผใ‚ธใธใฎใƒชใƒณใ‚ฏใ‚’ๅ–ๅพ—ใ—ใพใ™ใ€‚ๅผ•ๆ•ฐใŒๆœชๆŒ‡ๅฎšใฎๅ ดๅˆใ€็พๅœจใฎใ‚ตใƒผใƒใƒผใฎใ€Œ/serverใ€ใƒšใƒผใ‚ธใธใฎใƒชใƒณใ‚ฏใซใชใ‚Šใพใ™" + servers: + description: "ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นๅ†…ใฎBukkit/Spigotใ‚ตใƒผใƒใƒผไธ€่ฆงใ‚’่กจ็คบใ—ใพใ™" + inDepth: "ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นๅ†…ใซๅญ˜ๅœจใ™ใ‚‹ๅ…จใ‚ตใƒผใƒใƒผใฎIDใ€ๅๅ‰ใ€UUIDใ‚’่กจ็คบใ—ใพใ™" + unregister: + description: "ใ‚ฆใ‚งใƒ–ใƒšใƒผใ‚ธใ‹ใ‚‰ใƒฆใƒผใ‚ถใƒผใ‚’ๆœช็™ป้Œฒใซใ—ใพใ™" + inDepth: "ๅˆฅใฎใƒฆใƒผใ‚ถใƒผใฎ็™ป้Œฒใ‚’่งฃ้™คใ—ใพใ™ใ€‚ๅผ•ๆ•ฐใŒๆœชๆŒ‡ๅฎšใฎๅ ดๅˆใ€ใƒฆใƒผใ‚ถใƒผใซใƒชใƒณใ‚ฏใ•ใ‚ŒใŸใฎใƒ—ใƒฌใ‚คใƒคใƒผใฎ็™ป้Œฒใ‚’่งฃ้™คใ—ใพใ™ใ€‚" + users: + description: "ใ‚ฆใ‚งใƒ–ใƒšใƒผใ‚ธใฎๅ…จใƒฆใƒผใ‚ถใƒผใ‚’่กจ็คบใพใ™" + inDepth: "ใ‚ฆใ‚งใƒ–ใƒฆใƒผใ‚ถใƒผใฎไธ€่ฆงใ‚’่กจใง่กจ็คบใ—ใพใ™" + ingame: + activePlaytime: " ยง2ใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–ใชใƒ—ใƒฌใ‚คๆ™‚้–“: ยงf${0}" + activityIndex: " ยง2ๆดปๅ‹•ๆŒ‡ๆ•ฐ: ยงf${0} | ${1}" + afkPlaytime: " ยง2ๆ”พ็ฝฎๆ™‚้–“: ยงf${0}" + deaths: " ยง2ๆญปไบกๅ›žๆ•ฐ: ยงf${0}" + geolocation: " ยง2ๆŽฅ็ถšๅœฐๅŸŸ: ยงf${0}" + lastSeen: " ยง2ๆœ€็ต‚ใƒญใ‚ฐใ‚คใƒณๆ—ฅ: ยงf${0}" + longestSession: " ยง2ๆœ€้•ทใƒญใ‚ฐใ‚คใƒณๆ™‚้–“: ยงf${0}" + mobKills: " ยง2ใ‚ญใƒซใ‚ซใ‚ฆใƒณใƒˆ(ใƒขใƒ–): ยงf${0}" + playerKills: " ยง2ใ‚ญใƒซใ‚ซใ‚ฆใƒณใƒˆ(ใƒ—ใƒฌใ‚คใƒคใƒผ): ยงf${0}" + playtime: " ยง2ใƒ—ใƒฌใ‚คๆ™‚้–“: ยงf${0}" + registered: " ยง2็™ป้Œฒๆ—ฅ: ยงf${0}" + timesKicked: " ยง2ใ‚ญใƒƒใ‚ฏใ•ใ‚ŒใŸๅ›žๆ•ฐ: ยงf${0}" + link: + clickMe: "ใ“ใ“ใ‚’ใ‚ฏใƒชใƒƒใ‚ฏ" + link: "ใƒชใƒณใ‚ฏ" + network: "ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏใƒšใƒผใ‚ธ: " + noNetwork: "ใ‚ตใƒผใƒใƒผใŒใƒใƒƒใƒˆใƒฏใƒผใ‚ฏใซๆŽฅ็ถšใ•ใ‚Œใฆใ„ใพใ›ใ‚“ใ€‚ใƒชใƒณใ‚ฏใฏใ‚ตใƒผใƒใƒผใƒกใƒผใ‚ธใซใƒชใƒ€ใ‚คใƒฌใ‚ฏใƒˆใ•ใ‚Œใพใ™" + player: "ใƒ—ใƒฌใ‚คใƒคใƒผใƒšใƒผใ‚ธ: " + playerJson: "ใƒ—ใƒฌใ‚คใƒคใƒผใฎjson: " + players: "ใƒ—ใƒฌใ‚คใƒคใƒผใƒšใƒผใ‚ธ: " + register: "็™ป้Œฒใƒšใƒผใ‚ธ: " + server: "ใ‚ตใƒผใƒใƒผใƒšใƒผใ‚ธ: " + subcommand: + info: + database: " ยง2็พๅœจใฎใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚น: ยงf${0}" + proxy: " ยง2BungeeCordใซๆŽฅ็ถšๆธˆใฟ: ยงf${0}" + update: " ยง2ๅˆฉ็”จๅฏ่ƒฝใชใ‚ขใƒƒใƒ—ใƒ‡ใƒผใƒˆ: ยงf${0}" + version: " ยง2ใƒใƒผใ‚ธใƒงใƒณ: ยงf${0}" +generic: + noData: "ใƒ‡ใƒผใ‚ฟใชใ—" +html: + button: + nightMode: "ใƒŠใ‚คใƒˆใƒขใƒผใƒ‰" + calendar: + new: "New:" + unique: "ๆŽฅ็ถšใ—ใŸใƒ—ใƒฌใ‚คใƒคใƒผใฎ็ทๆ•ฐ:" + description: + newPlayerRetention: "This value is a prediction based on previous players." + noGameServers: "Some data requires Plan to be installed on game servers." + noGeolocations: "Geolocation gathering needs to be enabled in the config (Accept GeoLite2 EULA)." + noServerOnlinActivity: "ใ‚ชใƒณใƒฉใ‚คใƒณใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ“ใƒ†ใ‚ฃใ‚’่กจ็คบใ™ใ‚‹ใ‚ตใƒผใƒใƒผใŒใ‚ใ‚Šใพใ›ใ‚“" + noServers: "ใƒ‡ใƒผใ‚ฟใƒผใƒ™ใƒผใ‚นๅ†…ใซ็™ป้Œฒใ•ใ‚ŒใŸใ‚ตใƒผใƒใƒผใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“" + noServersLong: 'It appears that Plan is not installed on any game servers or not connected to the same database. See wiki for Network tutorial.' + noSpongeChunks: "Chunks unavailable on Sponge" + predictedNewPlayerRetention: "ใ“ใ‚Œใฏไปฅๅ‰ใฎใƒ—ใƒฌใƒผใƒคใƒผใ‹ใ‚‰ๅŸบใฅใ„ใŸไบˆๆธฌๅ€คใงใ™" + error: + 401Unauthorized: "ๆœช่ช่จผ็Šถๆ…‹ใงใ™" + 403Forbidden: "้–ฒ่ฆง็ฆๆญข" + 404NotFound: "ใƒšใƒผใ‚ธใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“ใงใ—ใŸ" + 404PageNotFound: "ใƒšใƒผใ‚ธใฏๅญ˜ๅœจใ—ใพใ›ใ‚“" + 404UnknownPage: "ใƒชใƒณใ‚ฏใŒ้–“้•ใฃใฆใ„ใพใ™ใ€ใ‚ณใƒžใƒณใƒ‰็ญ‰ใ‚’ไฝฟ็”จใ—URLใ‚’็ขบ่ชใ—ใฆไธ‹ใ•ใ„ใ€‚ URLไพ‹:

/player/{uuid/name}
/server/{uuid/name/id}

" + UUIDNotFound: "ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นๅ†…ใซใƒ—ใƒฌใƒคใƒผใฎUUIDใŒๅญ˜ๅœจใ—ใพใ›ใ‚“" + auth: + dbClosed: "ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ‚’้–‹ใใ“ใจใŒใงใใพใ›ใ‚“ใงใ—ใŸใ€‚ใ€Œ/plan infoใ€ใ‚ณใƒžใƒณใƒ‰ใ‚’ๅฎŸ่กŒใ—ใฆ็Šถๆณใ‚’็ขบ่ชใ—ใฆไธ‹ใ•ใ„" + emptyForm: "ใƒฆใƒผใ‚ถใƒผใจใƒ‘ใ‚นใƒฏใƒผใƒ‰ใŒๅ…ฅๅŠ›ใ•ใ‚Œใฆใพใ›ใ‚“" + expiredCookie: "ใƒฆใƒผใ‚ถใƒผใฎใ‚ฏใƒƒใ‚ญใƒผใฎๆœ‰ๅŠนๆœŸ้™ๅˆ‡ใ‚Œใงใ™" + generic: "ใ‚จใƒฉใƒผใŒ็™บ็”Ÿใ—ใŸใŸใ‚่ช่จผใซๅคฑๆ•—ใ—ใพใ—ใŸ" + loginFailed: "ๅ…ฅๅŠ›ใ•ใ‚ŒใŸใƒฆใƒผใ‚ถใƒผๅใจใƒ‘ใ‚นใƒฏใƒผใƒ‰ใŒ้–“้•ใฃใฆใ„ใพใ™" + noCookie: "ใƒฆใƒผใ‚ถใƒผใฎใ‚ฏใƒƒใ‚ญใƒผใŒๅญ˜ๅœจใ—ใพใ›ใ‚“" + registrationFailed: "Registration failed, try again (The code expires after 15 minutes)" + userNotFound: "ๅ…ฅๅŠ›ใ•ใ‚ŒใŸใƒฆใƒผใ‚ถใƒผใฏๅญ˜ๅœจใ—ใพใ›ใ‚“" + authFailed: "่ช่จผใซๅคฑๆ•—ใ—ใพใ—ใŸ" + authFailedTips: "- ็™ป้Œฒใ—ใŸใƒฆใƒผใ‚ถใƒผใ‚’ใ€Œ/plan register ใ€ใง็ขบ่ชใงใใพใ™ใ€‚
- ๅ…ฅๅŠ›ใ—ใŸใƒฆใƒผใ‚ถใƒผๅใจใƒ‘ใ‚นใƒฏใƒผใƒ‰ใŒๆญฃใ—ใ„ใ“ใจใ‚’็ขบ่ชใ—ใฆไธ‹ใ•ใ„
- ใƒฆใƒผใ‚ถใƒผๅใจใƒ‘ใ‚นใƒฏใƒผใƒ‰ใฏๅคงๆ–‡ๅญ—ใจๅฐๆ–‡ๅญ—ใŒๅŒบๅˆฅใ•ใ‚Œใฆใ„ใพใ™

ใƒ‘ใ‚นใƒฏใƒผใƒ‰ใ‚’ๅฟ˜ใ‚ŒใŸๅ ดๅˆใฏใ€็ฎก็†่€…ใซๅคใ„ใƒฆใƒผใ‚ถใƒผใ‚’ๅ‰Š้™คใ—ใฆๆ–ฐใ—ใใƒฆใƒผใ‚ถใƒผใ‚’ๅ†็™ป้Œฒใ™ใ‚‹ใ‚ˆใ†ไพ้ ผใ—ใฆไธ‹ใ•ใ„" + noServersOnline: "ใƒชใ‚ฏใ‚จใ‚นใƒˆใ‚’ๅ‡ฆ็†ใ™ใ‚‹ใ‚ตใƒผใƒใƒผใŒใ‚ชใƒณใƒฉใ‚คใƒณใงใฏใ‚ใ‚Šใพใ›ใ‚“" + playerNotSeen: "ใƒ—ใƒฌใ‚คใƒคใƒผใฏใ“ใฎใ‚ตใƒผใƒใƒผใงใƒ—ใƒฌใ‚คใ—ใฆใ„ใพใ›ใ‚“" + serverNotSeen: "Server doesn't exist" + generic: + none: "ใชใ—" + label: + active: "ใ‚ˆใใƒญใ‚ฐใ‚คใƒณใ—ใฆใ„ใ‚‹" + activePlaytime: "Active Playtime" + activityIndex: "ๆดปๅ‹•ๆŒ‡ๆ•ฐ" + afk: "้›ขๅธญ" + afkTime: "้›ขๅธญๆ™‚้–“" + all: "ๅ…จใฆ" + allTime: "ๅ…จไฝ“" + alphabetical: "Alphabetical" + apply: "Apply" + asNumbers: "ใฎๆƒ…ๅ ฑ" + average: "ๅนณๅ‡" + averageActivePlaytime: "Average Active Playtime" + averageAfkTime: "Average AFK Time" + averageChunks: "Average Chunks" + averageCpuUsage: "Average CPU Usage" + averageEntities: "Average Entities" + averageKdr: "ๅนณๅ‡KDR" + averageMobKdr: "ใƒขใƒ–ใซๅฏพใ—ใฆใฎKDR" + averagePing: "ๅนณๅ‡Pingๅ€ค" + averagePlayers: "Average Players" + averagePlaytime: "ๅนณๅ‡ใƒ—ใƒฌใ‚คๆ™‚้–“" + averageRamUsage: "Average RAM Usage" + averageServerDowntime: "Average Downtime / Server" + averageSessionLength: "ๅนณๅ‡ๆŽฅ็ถšๆ™‚้–“" + averageSessions: "Average Sessions" + averageTps: "ๅนณๅ‡TPS" + averageTps7days: "Average TPS (7 days)" + banned: "BANๅฑฅๆญด" + bestPeak: "ๅ…จไฝ“ใฎใƒ”ใƒผใ‚ฏใ‚ฟใ‚คใƒ " + bestPing: "ๆœ€้ซ˜Pingๅ€ค" + calendar: "ใ‚ซใƒฌใƒณใƒ€ใƒผ" + comparing7days: "็›ด่ฟ‘1้€ฑ้–“ใจใฎๆฏ”่ผƒ" + connectionInfo: "ๆŽฅ็ถšๆƒ…ๅ ฑ" + country: "ๅ›ฝ/ๅœฐๅŸŸ" + cpu: "CPU" + cpuRam: "CPUใจใƒกใƒขใƒชใƒผ" + cpuUsage: "CPU Usage" + currentPlayerbase: "ใƒญใ‚ฐใ‚คใƒณใƒ—ใƒฌใ‚คใƒคใƒผ" + currentUptime: "Current Uptime" + dayByDay: "่ฉณ็ดฐๆƒ…ๅ ฑ" + dayOfweek: "ๆ›œๆ—ฅ" + deadliestWeapon: "ๆœ€ใ‚‚PvPใงไฝฟ็”จใ•ใ‚Œใฆใ„ใ‚‹ๆญฆๅ™จ" + deaths: "ๆญปไบกๅ›žๆ•ฐ" + disk: "ใƒ‰ใƒฉใ‚คใƒ–ใฎๅฎน้‡" + diskSpace: "ใƒ‰ใƒฉใ‚คใƒ–ใฎ็ฉบใๅฎน้‡" + downtime: "ใƒ€ใ‚ฆใƒณใ‚ฟใ‚คใƒ " + duringLowTps: "TPSใฎไฝŽไธ‹ใพใงใฎๆ™‚้–“:" + entities: "ใ‚จใƒณใƒ†ใ‚ฃใƒ†ใ‚ฃๆ•ฐ" + favoriteServer: "ใŠๆฐ—ใซๅ…ฅใ‚Šใฎใ‚ตใƒผใƒใƒผ" + firstSession: "ๅˆๅ‚ๅŠ " + firstSessionLength: + average: "Average first session length" + median: "Median first session length" + geoProjection: + dropdown: "Select projection" + equalEarth: "Equal Earth" + mercator: "Mercator" + miller: "Miller" + ortographic: "Ortographic" + geolocations: "ๅœฐๅŸŸ" + hourByHour: "Hour by Hour" + inactive: "ไผ‘ๆญขไธญ" + indexInactive: "ไผ‘ๆญขไธญ" + indexRegular: "ใ—ใฐใ—ใฐใƒญใ‚ฐใ‚คใƒณใ—ใฆใ„ใ‚‹" + information: "ใ‚คใƒณใƒ•ใ‚ฉใƒกใƒผใ‚ทใƒงใƒณ" + insights: "Insights" + insights30days: "1ใƒถๆœˆใฎใƒ‘ใƒณใƒใƒœใƒผใƒ‰" + irregular: "ใŸใพใซใƒญใ‚ฐใ‚คใƒณใ—ใฆใ„ใ‚‹" + joinAddress: "Join Address" + joinAddresses: "Join Addresses" + kdr: "KDR" + killed: "ๆฎบใ—ใŸไบบ" + last24hours: "24ๆ™‚้–“" + last30days: "1ใƒถๆœˆ" + last7days: "ไธ€้€ฑ้–“" + lastConnected: "็›ด่ฟ‘ใฎๆŽฅ็ถš" + lastPeak: "็›ด่ฟ‘ใฎใƒ”ใƒผใ‚ฏใ‚ฟใ‚คใƒ " + lastSeen: "็›ด่ฟ‘ใฎใ‚ชใƒณใƒฉใ‚คใƒณ" + latestJoinAddresses: "Latest Join Addresses" + length: " ้•ทใ•" + links: "LINKS" + loadedChunks: "ใƒญใƒผใƒ‰ใ•ใ‚ŒใŸใƒใƒฃใƒณใ‚ฏๆ•ฐ" + loadedEntities: "ใƒญใƒผใƒ‰ใ•ใ‚ŒใŸใ‚จใƒณใƒ†ใ‚ฃๆ•ฐ" + loneJoins: "ไธ€ไบบใงใฎๆŽฅ็ถš" + loneNewbieJoins: "ๆ–ฐใ—ใไธ€ไบบใงใฎๅ‚ๅŠ " + longestSession: "ๆœ€้•ทๆŽฅ็ถšๆ™‚้–“" + lowTpsSpikes: "TPSใฎไฝŽไธ‹ๅ€ค" + lowTpsSpikes7days: "Low TPS Spikes (7 days)" + maxFreeDisk: "ใƒ‡ใ‚ฃใ‚นใ‚ฏใฎๆœ€ๅคง็ฉบใๅฎน้‡" + medianSessionLength: "Median Session Length" + minFreeDisk: "ใƒ‡ใ‚ฃใ‚นใ‚ฏใฎๆœ€ไฝŽ็ฉบใๅฎน้‡" + mobDeaths: "Mobใซใ‚ˆใฃใฆๆฎบใ•ใ‚ŒใŸๅ›žๆ•ฐ" + mobKdr: "Mobใซๅฏพใ—ใฆใฎKDR" + mobKills: "Mobใ‚’ๆฎบใ—ใŸๅ›žๆ•ฐ" + mostActiveGamemode: "ๆœ€ใ‚‚ไฝฟ็”จใ—ใŸใ‚ฒใƒผใƒ ใƒขใƒผใƒ‰" + mostPlayedWorld: "ใ‚ˆใใƒ—ใƒฌใ‚คใ—ใฆใ„ใ‚‹ใƒฏใƒผใƒซใƒ‰" + name: "ๅๅ‰" + network: "ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏ" + networkAsNumbers: "ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏๆ•ฐ" + networkOnlineActivity: "ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏๅ†…ใฎๆŽฅ็ถš็Šถๆณ" + networkOverview: "ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏๆฆ‚่ฆ" + networkPage: "ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏใƒšใƒผใ‚ธ" + new: "New" + newPlayerRetention: "ๆ–ฐ่ฆใƒ—ใƒฌใ‚คใƒคใƒผใฎ็ถ™็ถš็Ž‡" + newPlayers: "ๆ–ฐ่ฆใƒ—ใƒฌใ‚คใƒคใƒผ" + newPlayers7days: "New Players (7 days)" + nickname: "ใƒ‹ใƒƒใ‚ฏใƒใƒผใƒ " + noDataToDisplay: "No Data to Display" + now: "็พๅœจ" + onlineActivity: "ๆŽฅ็ถš็Šถๆณ" + onlineActivityAsNumbers: "ๆŽฅ็ถš็Šถๆณใฎๆƒ…ๅ ฑ" + onlineOnFirstJoin: "ๆ–ฐ่ฆใƒญใ‚ฐใ‚คใƒณๆ™‚ใฎใ‚ชใƒณใƒฉใ‚คใƒณใƒ—ใƒฌใ‚คใƒคใƒผ" + operator: "็ฎก็†่€…" + overview: "ๆฆ‚่ฆ" + perDay: "/ๆ—ฅ" + perPlayer: "/ใƒ—ใƒฌใ‚คใƒคใƒผ(1ใƒ—ใƒฌใ‚คใƒคใƒผใ‚ใŸใ‚Šใฎ)" + perRegularPlayer: "/็™ป้Œฒๆธˆใฟใƒ—ใƒฌใ‚คใƒคใƒผ(1็™ป้Œฒๆธˆใฟใƒ—ใƒฌใ‚คใƒคใƒผใ‚ใŸใ‚Šใฎ)" + performance: "ใƒ‘ใƒ•ใ‚ฉใƒผใƒžใƒณใ‚น" + performanceAsNumbers: "ใƒ‘ใƒ•ใ‚ฉใƒผใƒžใƒณใ‚นใฎๆƒ…ๅ ฑ" + ping: "Ping" + player: "ใƒ—ใƒฌใ‚คใƒคใƒผ" + playerDeaths: "ใƒ—ใƒฌใ‚คใƒคใƒผใซใ‚ˆใ‚‹ใ‚ญใƒซ" + playerKills: "ใƒ—ใƒฌใ‚คใƒคใƒผใ‚ญใƒซ" + playerList: "ใƒ—ใƒฌใ‚คใƒคใƒผไธ€่ฆง" + playerOverview: "ใƒ—ใƒฌใ‚คใƒคใƒผใฎๆฆ‚่ฆ" + playerPage: "ใƒ—ใƒฌใ‚คใƒคใƒผใƒšใƒผใ‚ธ" + playerRetention: "Player Retention" + playerbase: "ใƒ—ใƒฌใ‚คใƒคใƒผใƒ™ใƒผใ‚น" + playerbaseDevelopment: "็™ป้Œฒใ•ใ‚Œใฆใ„ใ‚‹ใƒ—ใƒฌใ‚คใƒคใƒผใฎๆŽจ็งป" + playerbaseOverview: "Playerbase Overview" + players: "ใƒ—ใƒฌใ‚คใƒคใƒผ" + playersOnline: "ใ‚ชใƒณใƒฉใ‚คใƒณใฎใƒ—ใƒฌใ‚คใƒคใƒผ" + playersOnlineNow: "Players Online (Now)" + playersOnlineOverview: "ๆŽฅ็ถš็Šถๆณใฎๆฆ‚่ฆ" + playtime: "ใƒ—ใƒฌใ‚คๆ™‚้–“" + plugins: "ใƒ—ใƒฉใ‚ฐใ‚คใƒณ" + pluginsOverview: "Plugins Overview" + punchcard: "ใƒ‘ใƒณใƒใ‚ซใƒผใƒ‰" + punchcard30days: "1ใƒถๆœˆใฎใƒ‘ใƒณใƒใƒœใƒผใƒ‰" + pvpPve: "PvPใจPvE" + pvpPveAsNumbers: "PVPใจPvEใฎๆƒ…ๅ ฑ" + query: "Make a query" + quickView: "ใ‚ฏใ‚คใƒƒใ‚ฏใƒ“ใƒฅใƒผ" + ram: "RAM" + ramUsage: "RAM Usage" + recentKills: "ๆœ€่ฟ‘ใฎใ‚ญใƒซ" + recentPvpDeaths: "ๆœ€่ฟ‘ใฎPVPใซใ‚ˆใ‚‹ๆญปไบก" + recentPvpKills: "ๆœ€่ฟ‘ใฎPVPใซใ‚ˆใ‚‹ใ‚ญใƒซ" + recentSessions: "ๆœ€่ฟ‘ใฎใƒญใ‚ฐใ‚คใƒณ" + registered: "็™ป้Œฒ" + registeredPlayers: "็™ป้Œฒๆธˆใฟใƒ—ใƒฌใ‚คใƒคใƒผ" + regular: "ใ‚ˆใใ‚ชใƒณใƒฉใ‚คใƒณใฎใƒ—ใƒฌใ‚คใƒคใƒผ" + regularPlayers: "ใ‚ˆใใ‚ชใƒณใƒฉใ‚คใƒณใฎใƒ—ใƒฌใ‚คใƒคใƒผ" + relativeJoinActivity: "ใ‚ชใƒณใƒฉใ‚คใƒณใจๆดปๅ‹•ใจใฎ้–ขไฟ‚ๆ€ง" + secondDeadliestWeapon: "2็•ช็›ฎใซPvPใงไฝฟ็”จใ•ใ‚Œใฆใ„ใ‚‹ๆญฆๅ™จ" + seenNicknames: "ใƒ‹ใƒƒใ‚ฏใƒใƒผใƒ ไธ€่ฆง" + server: "ใ‚ตใƒผใƒใƒผ" + serverAnalysis: "ใ‚ตใƒผใƒใƒผใฎๅˆ†ๆž็ตๆžœ" + serverAsNumberse: "ใ‚ตใƒผใƒใƒผใฎ็Šถๆณ" + serverCalendar: "Server Calendar" + serverDowntime: "ใ‚ตใƒผใƒใƒผใƒ€ใ‚ฆใƒณใ‚ฟใ‚คใƒ " + serverOccupied: "ใƒ—ใƒฌใ‚คใƒคใƒผใŒใƒญใ‚ฐใ‚คใƒณใ—ใฆใ„ใ‚‹ๆ™‚้–“" + serverOverview: "ใ‚ตใƒผใƒใƒผใฎๆฆ‚่ฆ" + serverPage: "ใ‚ตใƒผใƒใƒผใƒšใƒผใ‚ธ" + serverPlaytime: "ๅ„ใ‚ตใƒผใƒใƒผใฎใƒ—ใƒฌใ‚คๆ™‚้–“" + serverPlaytime30days: "ๅ„ใ‚ตใƒผใƒใƒผใงใฎ1ใƒถๆœˆใฎใƒ—ใƒฌใ‚คๆ™‚้–“" + serverSelector: "Server selector" + servers: "ๆŽฅ็ถšใ•ใ‚Œใฆใ„ใ‚‹ใ‚ตใƒผใƒใƒผ" + serversTitle: "ๆŽฅ็ถšใ•ใ‚Œใฆใ„ใ‚‹ใ‚ตใƒผใƒใƒผ" + session: "ใ‚ชใƒณใƒฉใ‚คใƒณ" + sessionCalendar: "Session Calendar" + sessionEnded: " ใƒญใ‚ฐใ‚ขใ‚ฆใƒˆใ—ใŸๆ™‚้–“" + sessionMedian: "ๅนณๅ‡ใ‚ชใƒณใƒฉใ‚คใƒณ" + sessionStart: "ๆŽฅ็ถšใ—ใŸๆ™‚้–“" + sessions: "ๆŽฅ็ถšๅฑฅๆญด" + sortBy: "Sort By" + stacked: "Stacked" + themeSelect: "ใƒ†ใƒผใƒž้ธๆŠž" + thirdDeadliestWeapon: "3็•ช็›ฎใซPvPใงไฝฟ็”จใ•ใ‚Œใฆใ„ใ‚‹ๆญฆๅ™จ" + thirtyDays: "1ใƒถๆœˆ" + thirtyDaysAgo: "1ใƒถๆœˆๅ‰" + timesKicked: "ใ‚ญใƒƒใ‚ฏๅ›žๆ•ฐ" + toMainPage: "ใƒกใ‚คใƒณใƒšใƒผใ‚ธใซๆˆปใ‚‹" + total: "Total" + totalActive: "็ดฏ่จˆๆดปๅ‹•ๆ™‚้–“" + totalAfk: "็ดฏ่จˆ้›ขๅธญๆ™‚้–“" + totalPlayers: "ใƒˆใƒผใ‚ฟใƒซใƒ—ใƒฌใ‚คใƒคใƒผๆ•ฐ" + totalPlayersOld: "ๅ…จใƒ—ใƒฌใ‚คใƒคใƒผๆ•ฐ" + totalPlaytime: "ใƒˆใƒผใ‚ฟใƒซใƒ—ใƒฌใ‚คๆ™‚้–“" + totalServerDowntime: "Total Server Downtime" + tps: "TPS" + trend: "ๅข—ๆธ›" + trends30days: "1ใƒถๆœˆ้–“ใฎๅข—ๆธ›" + uniquePlayers: "ๆŽฅ็ถšใ—ใŸใƒ—ใƒฌใ‚คใƒคใƒผใฎ็ทๆ•ฐ" + uniquePlayers7days: "Unique Players (7 days)" + veryActive: "ใจใฆใ‚‚ใƒญใ‚ฐใ‚คใƒณใ—ใฆใ„ใ‚‹" + weekComparison: "็›ด่ฟ‘1ๅ‘จ้–“ใงใฎๆฏ”่ผƒ" + weekdays: "'ๆœˆๆ›œๆ—ฅ', '็ซๆ›œๆ—ฅ', 'ๆฐดๆ›œๆ—ฅ', 'ๆœจๆ›œๆ—ฅ', '้‡‘ๆ›œๆ—ฅ', 'ๅœŸๆ›œๆ—ฅ', 'ๆ—ฅๆ›œๆ—ฅ'" + world: "ใƒฏใƒผใƒซใƒ‰ใฎใƒญใƒผใƒ‰ๆ•ฐ" + worldPlaytime: "ใƒฏใƒผใƒซใƒ‰ใ”ใจใฎใƒ—ใƒฌใ‚คๆ™‚้–“" + worstPing: "ๆœ€ไฝŽPingๅ€ค" + login: + failed: "Login failed: " + forgotPassword: "Forgot Password?" + forgotPassword1: "Forgot password? Unregister and register again." + forgotPassword2: "Use the following command in game to remove your current user:" + forgotPassword3: "Or using console:" + forgotPassword4: "After using the command, " + login: "Login" + logout: "Logout" + password: "Password" + register: "Create an Account!" + username: "Username" + modal: + info: + bugs: "ใƒใ‚ฐๅ ฑๅ‘Š" + contributors: + bugreporters: "& Bug reporters!" + code: ":ใƒ—ใƒญใ‚ฐใƒฉใƒŸใƒณใ‚ฐ่ฒข็Œฎ่€…ใ€€" + donate: "ใ“ใฎใƒ—ใƒฉใ‚ฐใ‚คใƒณ้–‹็™บใซๅ‹Ÿ้‡‘ใ—ใฆ้ ‚ใ„ใŸไบบใ€…ใธ็‰นๅˆฅใชๆ„Ÿ่ฌใ‚’" + text: 'ๅŠ ใˆใฆใ€ไปฅไธ‹ใฎ็ด ๆ™ดใ‚‰ใ—ใ„ไบบใ€…ใŒ้–‹็™บใซ่ฒข็Œฎใ—ใฆใ„ใพใ™' + translator: ":็ฟป่จณ่€…ใ€€" + developer: "้–‹็™บ่€…:" + discord: "Discordใฎใ‚ตใƒใƒผใƒˆใƒใƒฃใƒณใƒใƒซ" + license: "ใ€ŒPlayer Analyticsใ€ใฏไปฅไธ‹ใฎใƒฉใ‚คใ‚ปใƒณใ‚นใฎไธ‹ใ€้–‹็™บใ•ใ‚Œใฆใ„ใพใ™" + metrics: "ใ€ŒbStats Metricsใ€ใŒๆœ‰ๅŠนใซใชใฃใฆใ„ใพใ™" + text: "ใƒ—ใƒฉใ‚ฐใ‚คใƒณใซ้–ขใ™ใ‚‹ๆƒ…ๅ ฑ" + wiki: "ใ€ŒPlanใ€ใฎwikiใ€ใƒใƒฅใƒผใƒˆใƒชใ‚ขใƒซใจใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆ" + version: + available: "ใŒๅˆฉ็”จๅฏ่ƒฝใงใ™" + changelog: "ๅค‰ๆ›ดๅฑฅๆญดใฎ็ขบ่ช" + dev: "ใ“ใฎใƒใƒผใ‚ธใƒงใƒณใฏ้–‹็™บ็‰ˆใงใ™" + download: "ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰" + text: "ๆ–ฐใƒใƒผใ‚ธใƒงใƒณใŒใƒชใƒชใƒผใ‚นใ•ใ‚ŒใฆใŠใ‚Šใ€ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ๅฏ่ƒฝใงใ™" + title: "ใƒใƒผใ‚ธใƒงใƒณ" + query: + filter: + activity: + text: "are in Activity Groups" + banStatus: + name: "Ban status" + banned: "Banned" + country: + text: "have joined from country" + generic: + allPlayers: "All players" + and: "and " + start: "of Players who " + joinAddress: + text: "joined with address" + nonOperators: "Non operators" + notBanned: "Not banned" + operatorStatus: + name: "Operator status" + operators: "Operators" + playedBetween: + text: "Played between" + pluginGroup: + name: "Group: " + text: "are in ${plugin}'s ${group} Groups" + registeredBetween: + text: "Registered between" + title: + activityGroup: "Current activity group" + view: " View:" + filters: + add: "Add a filter.." + loading: "Loading filters.." + generic: + are: "`are`" + label: + from: ">from" + makeAnother: "Make another query" + to: ">to" + view: "Show a view" + performQuery: "Perform Query!" + results: + match: "matched ${resultCount} players" + none: "Query produced 0 results" + title: "Query Results" + title: + activity: "Activity of matched players" + activityOnDate: 'Activity on ' + sessionsWithinView: "Sessions within view" + text: "Query<" + register: + completion: "Complete Registration" + completion1: "You can now finish registering the user." + completion2: "Code expires in 15 minutes" + completion3: "Use the following command in game to finish registration:" + completion4: "Or using console:" + createNewUser: "Create a new user" + error: + checkFailed: "Checking registration status failed: " + failed: "Registration failed: " + noPassword: "You need to specify a Password" + noUsername: "You need to specify a Username" + usernameLength: "Username can be up to 50 characters, yours is " + login: "Already have an account? Login!" + passwordTip: "Password should be more than 8 characters, but there are no limitations." + register: "Register" + usernameTip: "Username can be up to 50 characters." + text: + clickToExpand: "ใ‚ฏใƒชใƒƒใ‚ฏใ—ใฆๅฑ•้–‹" + comparing15days: "็›ด่ฟ‘15ๆ—ฅใจใฎๆฏ”่ผƒ" + comparing30daysAgo: "30ๆ—ฅๅ‰ใจใฎๆฏ”่ผƒ" + noExtensionData: "ใ€ŒExtension Dataใ€ใŒๅญ˜ๅœจใ—ใพใ›ใ‚“" + noLowTps: "TPSใฎไฝŽไธ‹ใŒๅญ˜ๅœจใ—ใพใ›ใ‚“" + unit: + chunks: "ใƒใƒฃใƒณใ‚ฏ" + players: "ใƒ—ใƒฌใ‚คใƒคใƒผ" + value: + localMachine: "ใƒญใƒผใ‚ซใƒซใƒžใ‚ทใƒณ" + noKills: "ใƒ—ใƒฌใ‚คใƒคใƒผใ‚ญใƒซใชใ—" + offline: " ใ‚ชใƒ•ใƒฉใ‚คใƒณ" + online: " ใ‚ชใƒณใƒฉใ‚คใƒณ" + with: "ๆญปไบกๅŽŸๅ› " + version: + changelog: "View Changelog" + current: "You have version ${0}" + download: "Download Plan-${0}.jar" + isDev: "This version is a DEV release." + updateButton: "Update" + updateModal: + text: "A new version has been released and is now available for download." + title: "Version ${0} is Available!" +plugin: + apiCSSAdded: "ใƒšใƒผใ‚ธๆ‹กๅผตๆฉŸ่ƒฝ: ใ€Œ${0}ใ€ใŒใ€Œ${1}ใ€,ใ€Œ${2}ใ€ใซใ‚นใ‚ฟใ‚คใƒซใ‚ทใƒผใƒˆใ‚’่ฟฝๅŠ " + apiJSAdded: "ใƒšใƒผใ‚ธๆ‹กๅผตๆฉŸ่ƒฝ: ใ€Œ${0}ใ€ใŒใ€Œ${1}ใ€,ใ€Œ${2}ใ€ใซJavascriptใฎๆฉŸ่ƒฝใ‚’่ฟฝๅŠ " + disable: + database: "ๆœชๅฎŸ่กŒใฎ้‡่ฆใชๅ‡ฆ็†ใŒใ‚ใ‚Šใพใ™ (${0})" + disabled: "ใƒ—ใƒฌใ‚คใƒคใƒผๅˆ†ๆžใŒ็„กๅŠนใซใชใ‚Šใพใ—ใŸ" + processingComplete: "ๅ‡ฆ็†ใŒๅฎŒไบ†ใ—ใพใ—ใŸ" + savingSessions: "ๆœชไฟๅญ˜ใฎใ‚ปใƒƒใ‚ทใƒงใƒณใ‚’ไฟๅญ˜ใ—ใฆใ„ใพใ™ใƒปใƒป" + savingSessionsTimeout: "Timeout hit, storing the unfinished sessions on next enable instead." + waitingDb: "Waiting queries to finish to avoid SQLite crashing JVM.." + waitingDbComplete: "Closed SQLite connection." + waitingTransactions: "Waiting for unfinished transactions to avoid data loss.." + waitingTransactionsComplete: "Transaction queue closed." + webserver: "ใ‚ฆใ‚งใƒ–ใ‚ตใƒผใƒใƒผใŒ็„กๅŠนใซใชใ‚Šใพใ—ใŸ" + enable: + database: "${0}ใฎใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใฎๆŽฅ็ถšใŒ็ขบ็ซ‹ใ—ใพใ—ใŸ" + enabled: "ใƒ—ใƒฌใ‚คใƒคใƒผๅˆ†ๆžใŒๆœ‰ๅŠนใซใชใ‚Šใพใ—ใŸ" + fail: + database: "ใ€Œ${0}ใ€ใฎใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใฎๆŽฅ็ถšใซๅคฑๆ•—ใ—ใพใ—ใŸ: ${1}" + databasePatch: "ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใฎใƒ‘ใƒƒใƒ้ฉ็”จใซๅคฑๆ•—ใ—ใพใ—ใŸใ€ใƒ—ใƒฉใ‚ฐใ‚คใƒณใ‚’็„กๅŠนใซใ™ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚ใƒใ‚ฐๅ ฑๅ‘Šใ‚’ใŠ้ก˜ใ„ใ—ใพใ™" + databaseType: "ใ€Œ${0}ใ€ใฏใ‚ตใƒใƒผใƒˆใ•ใ‚Œใฆใ„ใชใ„ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใงใ™" + geoDBWrite: "ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ใ—ใŸใ€ŒGeoLite2ใ€ใฎไฝ็ฝฎๆƒ…ๅ ฑใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ‚’ไฟๅญ˜ไธญใซไฝ•ใ‚‰ใ‹ใฎใ‚จใƒฉใƒผใŒ็™บ็”Ÿใ—ใพใ—ใŸ" + webServer: "ใ‚ฆใ‚งใƒ–ใ‚ตใƒผใƒใƒผใฎๅˆๆœŸๅŒ–ใซๅคฑๆ•—ใ—ใพใ—ใŸ!" + notify: + badIP: "IPใ€Œ0.0.0.0ใ€ใฏๆœ‰ๅŠนใงใฏใชใ„ใŸใ‚, ใ€ŒAlternative_IPใ€ใฎ่จญๅฎšใ‚’่กŒใฃใฆใใ ใ•ใ„ใ€‚่ชคใฃใŸใƒชใƒณใ‚ฏใŒ่กจ็คบใ•ใ‚Œใ‚‹ๅฏ่ƒฝๆ€งใŒใ‚ใ‚Šใพใ™!" + emptyIP: "server.propertiesใฎ่จญๅฎšใงใ€IPใฎ้ …็›ฎใŒ่จญๅฎšใ•ใ‚ŒใฆใŠใ‚‰ใšAlternative IPใŒไฝฟ็”จใ•ใ‚Œใฆใ„ใพใ›ใ‚“ใ€‚ใใฎใŸใ‚่ชคใฃใŸใƒชใƒณใ‚ฏใŒ่กจ็คบใ•ใ‚Œใพใ™!" + geoDisabled: "ไฝ็ฝฎๆƒ…ๅ ฑใ‚ตใƒผใƒ“ใ‚นใŒๆœ‰ๅŠนใงใฏใ‚ใ‚Šใพใ›ใ‚“ใ€‚ (Data.Geolocations: false)" + geoInternetRequired: "ใ€ŒPlanใ€ใฏๅˆๅ›ž่ตทๅ‹•ๆ™‚ใ€ใ€ŒGeoLite2ใ€ใฎไฝ็ฝฎๆƒ…ๅ ฑใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ‚’ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ใ™ใ‚‹ใŸใ‚ใ‚คใƒณใ‚ฟใƒผใƒใƒƒใƒˆใ‚ขใ‚ฏใ‚ปใ‚นใŒๅฟ…่ฆใงใ™" + storeSessions: "Storing sessions that were preserved before previous shutdown." + webserverDisabled: "ใ‚ฆใ‚งใƒ–ใ‚ตใƒผใƒใƒผใฎๅˆๆœŸๅŒ–ใซๅคฑๆ•—ใ—ใพใ—ใŸ (WebServer.DisableWebServer: true)" + webserver: "ใ‚ฆใ‚งใƒ–ใ‚ตใƒผใƒใƒผใฏๆฌกใฎใƒใƒผใƒˆใงๅฎŸ่กŒใ•ใ‚Œใฆใ„ใพใ™: ${0} ( ${1} )" + generic: + dbApplyingPatch: "ๆฌกใฎใƒ‘ใƒƒใƒใ‚’้ฉ็”จใ—ใฆใ„ใพใ™: ${0}.." + dbFaultyLaunchOptions: "่ตทๅ‹•ใ‚ชใƒ—ใ‚ทใƒงใƒณใซๅ•้กŒใŒใ‚ใ‚Šใพใ™,ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใฎใ‚ชใƒ—ใ‚ทใƒงใƒณใ‚’ไฝฟ็”จใ—ใฆไธ‹ใ•ใ„ (${0})" + dbNotifyClean: "${0} ใฎใƒ—ใƒฌใ‚คใƒคใƒผใƒ‡ใƒผใ‚ฟใ‚’ๅ‰Š้™คใ—ใฆใ„ใพใ™" + dbNotifySQLiteWAL: "SQLiteใฎWALใƒขใƒผใƒ‰ใฏใ“ใฎใ‚ตใƒผใƒใฎใƒใƒผใ‚ธใƒงใƒณใงใฏใ‚ตใƒใƒผใƒˆใ•ใ‚Œใฆใ„ใชใ„ใŸใ‚ใ€ๅˆๆœŸ่จญๅฎšใซๅค‰ๆ›ดใ—ใพใ™ใ€‚ใ“ใ‚Œใฏใ‚ตใƒผใƒใƒผใฎใƒ‘ใƒ•ใ‚ฉใƒผใƒžใƒณใ‚นใซๅฝฑ้Ÿฟใ‚’ไธŽใˆใ‚‹ๅฏ่ƒฝๆ€งใŒใ‚ใ‚Šใพใ™" + dbPatchesAlreadyApplied: "ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใซๅ…จใฆใฎใƒ‘ใƒƒใƒใŒ้ฉ็”จๆธˆใฟใงใ™" + dbPatchesApplied: "ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใซๅ…จใฆใฎใƒ‘ใƒƒใƒใŒๆญฃๅธธใซ้ฉ็”จใ•ใ‚Œใพใ—ใŸ" + dbSchemaPatch: "Database: Making sure schema is up to date.." + loadedServerInfo: "Server identifier loaded: ${0}" + loadingServerInfo: "Loading server identifying information" + no: "ใชใ„" + today: "'ๆœฌๆ—ฅ'" + unavailable: "ๅˆฉ็”จไธๅฏ" + unknown: "ไธๆ˜Ž" + yes: "ใ‚ใ‚‹" + yesterday: "'ๆ˜จๆ—ฅ'" + version: + checkFail: "ๆ–ฐใ—ใ„ใƒใƒผใ‚ธใƒงใƒณใฎใƒใ‚งใƒƒใ‚ฏใซๅคฑๆ•—ใ—ใพใ—ใŸ" + checkFailGithub: "Github/versions.txtใซๅญ˜ๅœจใ™ใ‚‹ใƒใƒผใ‚ธใƒงใƒณๆƒ…ๅ ฑใฎใƒญใƒผใƒ‰ใซๅคฑๆ•—ใ—ใพใ—ใŸ" + isDev: " ใ“ใฎใƒใƒผใ‚ธใƒงใƒณใฏ้–‹็™บ็‰ˆใงใ™" + isLatest: "ๆœ€ๆ–ฐ็‰ˆใฎใ€ŒPlanใ€ใ‚’ไฝฟ็”จใ—ใฆใ„ใพใ™" + updateAvailable: "ๆ–ฐใ—ใ„ใƒใƒผใ‚ธใƒงใƒณใฎ${0}ใŒๆฌกใฎURLใงๅ…ฅๆ‰‹ๅฏ่ƒฝใงใ™ ${1}" + updateAvailableSpigot: "ๆ–ฐใ—ใ„ใƒใƒผใ‚ธใƒงใƒณใฏๆฌกใฎURLใงๅ…ฅๆ‰‹ๅฏ่ƒฝใงใ™${0}" + webserver: + fail: + SSLContext: "Webใ‚ตใƒผใƒใƒผ: SSLใ‚ณใƒณใƒ†ใ‚ญใ‚นใƒˆใฎๅˆๆœŸๅŒ–ใซๅคฑๆ•—ใ—ใพใ—ใŸใ€‚" + certFileEOF: "Webใ‚ตใƒผใƒใƒผ: ่จผๆ˜Žๆ›ธใƒ•ใ‚กใ‚คใƒซใ‚’ใƒญใƒผใƒ‰ไธญใซEOFใ‚จใƒฉใƒผใŒ็™บ็”Ÿใ—ใพใ—ใŸ (ใƒ•ใ‚กใ‚คใƒซใŒๆฎปใซใชใฃใฆใ„ใชใ„ใ‹็ขบ่ชใ—ใฆใใ ใ•ใ„)" + certStoreLoad: "Webใ‚ตใƒผใƒใƒผ: SSL่จผๆ˜Žๆ›ธใฎใƒญใƒผใƒ‰ใซๅคฑๆ•—ใ—ใพใ—ใŸ" + portInUse: "Webใ‚ตใƒผใƒใƒผ: ๅˆๆœŸๅŒ–ใŒๆญฃๅธธใซ็ต‚ไบ†ใ—ใพใ›ใ‚“ใงใ—ใŸใ€‚ใƒใƒผใƒˆ็•ชๅท(${0})ใฏไฝฟ็”จใ•ใ‚Œใฆใ„ใพใ›ใ‚“ใ‹?" + notify: + authDisabledConfig: "Webใ‚ตใƒผใƒใƒผ: ใƒฆใƒผใ‚ถใƒผ่ช่จผใ‚’็„กๅŠนใซใ—ใพใ™! (Configใง็„กๅŠนๅŒ–ใ•ใ‚Œใฆใ„ใพใ™)" + authDisabledNoHTTPS: "Webใ‚ตใƒผใƒใƒผ: ใƒฆใƒผใ‚ถใƒผ่ช่จผใ‚’็„กๅŠนใซใ—ใพใ™ (HTTP็ตŒ็”ฑใ ใจๅฎ‰ๅ…จใงใฏใชใ„ใŸใ‚ใงใ™)" + certificateExpiresOn: "Webserver: Loaded certificate is valid until ${0}." + certificateExpiresPassed: "Webserver: Certificate has expired, consider renewing the certificate." + certificateExpiresSoon: "Webserver: Certificate expires in ${0}, consider renewing the certificate." + certificateNoSuchAlias: "Webserver: Certificate with alias '${0}' was not found inside the keystore file '${1}'." + http: "Webใ‚ตใƒผใƒใƒผ: ่จผๆ˜Žๆ›ธใŒๅญ˜ๅœจใพใ›ใ‚“ -> HTTPใ‚ตใƒผใƒใƒผใ‚’ไฝฟ็”จใ—ใพใ™" + ipWhitelist: "Webใ‚ตใƒผใƒใƒผ: IPใฎใƒ›ใƒฏใ‚คใƒˆใƒชใ‚นใƒˆใŒๆœ‰ๅŠนใซใชใฃใฆใ„ใพใ™" + ipWhitelistBlock: "Webใ‚ตใƒผใƒใƒผ: ใ€Œ${0}ใ€ใฎใ€Œ${1}ใ€ใธใฎใ‚ขใ‚ฏใ‚ปใ‚นใŒๆ‹’ๅฆใ•ใ‚Œใพใ—ใŸ(ใƒ›ใƒฏใ‚คใƒˆใƒชใ‚นใƒˆใซใฏๆœช็™ป้Œฒใงใ™)" + noCertFile: "Webใ‚ตใƒผใƒใƒผ: ๆฌกใฎใƒ‘ใ‚นใซไฟๅญ˜ใ•ใ‚ŒใŸ่ช่จผใ‚ญใƒผใƒ•ใ‚กใ‚คใƒซใŒๅญ˜ๅœจใ—ใพใ›ใ‚“: ${0}" + reverseProxy: "Webใ‚ตใƒผใƒใƒผ: Proxy-mode HTTPSใŒๆœ‰ๅŠนใซใชใฃใฆใ„ใพใ™ใ€ใƒชใƒใƒผใ‚นใƒ—ใƒญใ‚ญใ‚ทใŒHTTPSใ‚’ไฝฟ็”จใ—ใฆใƒซใƒผใƒ†ใ‚ฃใƒณใ‚ฐใ•ใ‚Œใฆใ„ใ‚‹ใ“ใจใจใ€PlanใฎAlternative_IP.AddressใŒใƒ—ใƒญใ‚ญใ‚ทใ‚’ๆŒ‡ๅฎšใ—ใฆใ„ใ‚‹ใ“ใจใ‚’็ขบ่ชใ—ใฆใใ ใ•ใ„ใ€‚" diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_KO.txt b/Plan/common/src/main/resources/assets/plan/locale/locale_KO.txt deleted file mode 100644 index 2381b35e1..000000000 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_KO.txt +++ /dev/null @@ -1,517 +0,0 @@ -API - css+ || PageExtension: ${0} added stylesheet(s) to ${1}, ${2} -API - js+ || PageExtension: ${0} added javascript(s) to ${1}, ${2} -Cmd - Click Me || ํด๋ฆญ -Cmd - Link || ๋งํฌ -Cmd - Link Network || ๋„คํŠธ์›Œํฌ ํŽ˜์ด์ง€: -Cmd - Link Player || ํ”Œ๋ ˆ์ด์–ด ํŽ˜์ด์ง€: -Cmd - Link Player JSON || ํ”Œ๋ ˆ์ด์–ด json: -Cmd - Link Players || ํ”Œ๋ ˆ์ด์–ด ๋ชฉ๋ก ํŽ˜์ด์ง€: -Cmd - Link Register || ๊ฐ€์ž… ํŽ˜์ด์ง€: -Cmd - Link Server || ์„œ๋ฒ„ ํŽ˜์ด์ง€: -CMD Arg - backup-file || ๋ฐฑ์—… ํŒŒ์ผ ์ด๋ฆ„(๋Œ€์†Œ๋ฌธ์ž ๊ตฌ๋ถ„) -CMD Arg - code || ๋“ฑ๋ก์„ ์™„๋ฃŒํ•˜๋Š”๋ฐ์— ์‚ฌ์šฉ๋˜๋Š” ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค. -CMD Arg - db type backup || ๋ฐฑ์—…ํ•  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์œ ํ˜•์ž…๋‹ˆ๋‹ค. ์ง€์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ํ˜„์žฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. -CMD Arg - db type clear || ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ฑฐํ•  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์œ ํ˜•์ž…๋‹ˆ๋‹ค. -CMD Arg - db type hotswap || ์‚ฌ์šฉ ์‹œ์ž‘ํ•  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์œ ํ˜•์ž…๋‹ˆ๋‹ค. -CMD Arg - db type move from || ๋ฐ์ดํ„ฐ๋ฅผ ์ด๋™ํ•  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์œ ํ˜•์ž…๋‹ˆ๋‹ค. -CMD Arg - db type move to || ๋ฐ์ดํ„ฐ๋ฅผ ์ด๋™ํ•  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์œ ํ˜•์ž…๋‹ˆ๋‹ค. -CMD Arg - db type restore || ๋ณต์›ํ•  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ์œ ํ˜•์ž…๋‹ˆ๋‹ค. ์ง€์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ํ˜„์žฌ ์‚ฌ์šฉ ์ค‘์ธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค. -CMD Arg - feature || ๋น„ํ™œ์„ฑํ™”ํ•  ๊ธฐ๋Šฅ์˜ ์ด๋ฆ„: ${0} -CMD Arg - player identifier || Name or UUID of a player -CMD Arg - player identifier remove || ํ˜„์žฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์‚ญ์ œํ•  ํ”Œ๋ ˆ์ด์–ด ์‹๋ณ„์ž์ž…๋‹ˆ๋‹ค. -CMD Arg - server identifier || Name, ID or UUID of a server -CMD Arg - subcommand || ๋„์›€๋ง์„ ๋ณด๋ ค๋ฉด ํ•˜์œ„ ๋ช…๋ น์–ด ์—†์ด ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค. -CMD Arg - username || ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž์˜ ์‚ฌ์šฉ์ž ์ด๋ฆ„์ž…๋‹ˆ๋‹ค. ์ง€์ •๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ํ”Œ๋ ˆ์ด์–ด ์—ฐ๊ฒฐ ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. -CMD Arg Name - backup-file || ๋ฐฑ์—… ํŒŒ์ผ -CMD Arg Name - code || ${code} -CMD Arg Name - export kind || ์ถ”์ถœ ์ข…๋ฅ˜ -CMD Arg Name - feature || ๊ธฐ๋Šฅ -CMD Arg Name - import kind || ๋กœ๋“œ ์ข…๋ฅ˜ -CMD Arg Name - name or uuid || name/uuid -CMD Arg Name - server || ์„œ๋ฒ„ -CMD Arg Name - subcommand || ํ•˜์œ„ ๋ช…๋ น -CMD Arg Name - username || ์‚ฌ์šฉ์ž ์ด๋ฆ„ -Cmd Confirm - accept || ๋™์˜ํ•˜๊ธฐ -Cmd Confirm - cancelled, no data change || ์ทจ์†Œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. -Cmd Confirm - cancelled, unregister || ์ทจ์†Œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. '${0}' ์ด(๊ฐ€) ๋“ฑ๋ก๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. -Cmd Confirm - clearing db || ${0} ์— ๋Œ€ํ•œ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ์‚ญ์ œํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. -Cmd Confirm - confirmation || ํ™•์ธ: -Cmd Confirm - deny || ์ทจ์†Œ -Cmd Confirm - Expired || ํ™•์ธ์ด ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋ช…๋ น์„ ๋‹ค์‹œ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค. -Cmd Confirm - Fail on accept || ์‹คํ–‰์‹œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ ์ˆ˜๋ฝ ๋œ ์ž‘์—…: ${0} -Cmd Confirm - Fail on deny || ์‹คํ–‰์‹œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฑฐ๋ถ€ ๋œ ์ž‘์—…: ${0} -Cmd Confirm - overwriting db || Plan์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฎ์–ด ์“ฐ๋ ค๊ณ ํ•ฉ๋‹ˆ๋‹ค. ${0} ๋ฐ์ดํ„ฐ ํฌํ•จ ${1} -Cmd Confirm - remove player db || ${1}์—์„œ ${0}์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ฑฐํ•˜๋ ค๊ณ ํ•ฉ๋‹ˆ๋‹ค. -Cmd Confirm - unregister || ${1}์— ์—ฐ๊ฒฐ๋œ '${0}'์˜ ๋“ฑ๋ก์„ ์ทจ์†Œํ•˜๋ ค๊ณ ํ•ฉ๋‹ˆ๋‹ค. -Cmd db - creating backup || ${1} ๋‚ด์šฉ์œผ๋กœ ๋ฐฑ์—… ํŒŒ์ผ '${0}.db'๋งŒ๋“ค๊ธฐ -Cmd db - removal || ${0}์—์„œ Plan ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ฑฐํ•˜๋Š” ์ค‘ .. -Cmd db - removal player || ${1}์—์„œ ${0}์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ฑฐํ•˜๋Š” ์ค‘ .. -Cmd db - server uninstalled || ยงa์„œ๋ฒ„๊ฐ€ ์—ฌ์ „ํžˆ ์„ค์น˜๋˜์–ด ์žˆ์œผ๋ฉด ์ž๋™์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์„ค์น˜๋œ ๊ฒƒ์œผ๋กœ ์„ค์ •๋ฉ๋‹ˆ๋‹ค. -Cmd db - write || ์“ฐ๊ธฐ ${0}.. -Cmd Disable - Disabled || ยงa์ด์ œ Plan ์‹œ์Šคํ…œ์ด ๋น„ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค. reload๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๋‹ค์‹œ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -Cmd FAIL - Accepts only these arguments || ๋‹ค์Œ์„ ์ˆ˜๋ฝํ•ฉ๋‹ˆ๋‹ค. ${0}: ${1} -Cmd FAIL - Database not open || ยงc๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋Š” ${0} - ์ž…๋‹ˆ๋‹ค. ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•˜์‹ญ์‹œ์˜ค. -Cmd FAIL - Empty search string || ๊ฒ€์ƒ‰ ๋ฌธ์ž์—ด์€ ๋น„์›Œ ๋‘˜ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. -Cmd FAIL - Invalid Username || ยงc์‚ฌ์šฉ์ž์—๊ฒŒ UUID๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. -Cmd FAIL - No Feature || ยงe๋น„ํ™œ์„ฑํ™” ํ•  ๊ธฐ๋Šฅ์„ ์ •์˜ํ•˜์‹ญ์‹œ์˜ค! (ํ˜„์žฌ ${0}) -Cmd FAIL - No Permission || ยงcํ•„์š”ํ•œ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค. -Cmd FAIL - No player || ํ”Œ๋ ˆ์ด์–ด '${0}' ์ฐพ์„ ์ˆ˜ ์—†์œผ๋ฉฐ UUID๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. -Cmd FAIL - No player register || ํ”Œ๋ ˆ์ด์–ด '${0}' ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. -Cmd FAIL - No server || ์„œ๋ฒ„ '${0}' ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. -Cmd FAIL - Require only one Argument || ยงc๋‹จ์ผ ์ธ์ˆ˜๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ${1} -Cmd FAIL - Requires Arguments || ยงcํ•„์ˆ˜ ์ธ์ˆ˜ (${0}) ${1} -Cmd FAIL - see config || config.yml์˜ '${0}'์ฐธ์กฐ -Cmd FAIL - Unknown Username || ยงc์ด ์„œ๋ฒ„์— ์‚ฌ์šฉ์ž๊ฐ€ ํ‘œ์‹œ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. -Cmd FAIL - Users not linked || ์‚ฌ์šฉ์ž๊ฐ€ ๊ท€ํ•˜์˜ ๊ณ„์ •์— ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์ง€ ์•Š์œผ๋ฉฐ ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž์˜ ๊ณ„์ •์„ ์ œ๊ฑฐ ํ•  ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค. -Cmd FAIL - WebUser does not exists || ยงc์‚ฌ์šฉ์ž๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค! -Cmd FAIL - WebUser exists || ยงc์‚ฌ์šฉ์ž๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค! -Cmd Footer - Help || ยง7๋ช…๋ น์–ด ๋˜๋Š” ์ธ์ˆ˜์— ๋งˆ์šฐ์Šค ์ปค์„œ๋กœ ์˜ฌ๋ฆฌ๊ฑฐ๋‚˜ '/${0} ?' ์‚ฌ์šฉํ•ด์„œ ์ž์„ธํžˆ ํ™•์ธ๋ฐ”๋ž๋‹ˆ๋‹ค. -Cmd Header - Analysis || > ยง2๋ถ„์„ ๊ฒฐ๊ณผ -Cmd Header - Help || > ยง2/${0} Help -Cmd Header - Info || > ยง2ํ”Œ๋ ˆ์ด์–ด ๋ถ„์„ -Cmd Header - Inspect || > ยง2ํ”Œ๋ ˆ์ด์–ด: ยงf${0} -Cmd Header - Network || > ยง2๋„คํŠธ์›Œํฌ ํŽ˜์ด์ง€ -Cmd Header - Players || > ยง2ํ”Œ๋ ˆ์ด์–ด ์ˆ˜ -Cmd Header - Search || > ยง2${0} Results for ยงf${1}ยง2: -Cmd Header - server list || id::name::uuid -Cmd Header - Servers || > ยง2์„œ๋ฒ„ ๋ชฉ๋ก -Cmd Header - web user list || username::linked to::permission level -Cmd Header - Web Users || > ยง2${0} ์›น ์‚ฌ์šฉ์ž -Cmd Info - Bungee Connection || ยง2ํ”„๋ก์‹œ์— ์—ฐ๊ฒฐ๋จ: ยงf${0} -Cmd Info - Database || ยง2ํ˜„์žฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค: ยงf${0} -Cmd Info - Reload Complete || ยงa๊ตฌ์„ฑํŒŒ์ผ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์™„๋ฃŒํ–ˆ์Šต๋‹ˆ๋‹ค. -Cmd Info - Reload Failed || ยงcํ”Œ๋Ÿฌ๊ทธ์ธ ๋ฆฌ๋กœ๋“œ ๋„์ค‘ ์—๋Ÿฌ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. -Cmd Info - Update || ยง2์ตœ์‹  ๋ฒ„์ „: ยงf${0} -Cmd Info - Version || ยง2๋ฒ„์ „: ยงf${0} -Cmd network - No network || ์„œ๋ฒ„๊ฐ€ ๋„คํŠธ์›Œํฌ์— ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋งํฌ๋Š” ์„œ๋ฒ„ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋””๋ ‰์…˜๋ฉ๋‹ˆ๋‹ค. -Cmd Notify - No Address || ยงe์‚ฌ์šฉ๊ฐ€๋Šฅํ•œ ์ฃผ์†Œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. - localhost ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. 'Alternative_IP' ํ•ญ๋ชฉ์„ ํ†ตํ•ด ์„ค์ •ํ•˜์‹ญ์‹œ์˜ค. -Cmd Notify - No WebUser || ์›น ์‚ฌ์šฉ์ž๊ฐ€ ์—†์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /plan register ๋ฅผ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค. -Cmd Notify - WebUser register || ๋“ฑ๋ก๋œ ์‹ ๊ทœ ์œ ์ €: '${0}' Perm level: ${1} -Cmd Qinspect - Active Playtime || ยง2ํ”Œ๋ ˆ์ดํ•œ ์‹œ๊ฐ„: ยงf${0} -Cmd Qinspect - Activity Index || ยง2ํ™œ๋™ ์ •๋ณด: ยงf${0} | ${1} -Cmd Qinspect - AFK Playtime || ยง2AFK ์‹œ๊ฐ„: ยงf${0} -Cmd Qinspect - Deaths || ยง2์ฃฝ์€ ํšŸ์ˆ˜: ยงf${0} -Cmd Qinspect - Geolocation || ยง2ยงf${0}์—์„œ ๋กœ๊ทธ์ธ -Cmd Qinspect - Last Seen || ยง2๋งˆ์ง€๋ง‰์œผ๋กœ ๋ณธ : ยงf${0} -Cmd Qinspect - Longest Session || ยง2๊ฐ€์žฅ ๊ธด ์„ธ์…˜: ยงf${0} -Cmd Qinspect - Mob Kills || ยง2๋ชฌ์Šคํ„ฐ ํ‚ฌ์ˆ˜: ยงf${0} -Cmd Qinspect - Player Kills || ยง2ํ”Œ๋ ˆ์ด์–ด ํ‚ฌ์ˆ˜: ยงf${0} -Cmd Qinspect - Playtime || ยง2ํ”Œ๋ ˆ์ด ์‹œ๊ฐ„: ยงf${0} -Cmd Qinspect - Registered || ยง2์ตœ์ดˆ ์ ‘์†: ยงf${0} -Cmd Qinspect - Times Kicked || ยง2์ถ”๋ฐฉ ํšŸ์ˆ˜: ยงf${0} -Cmd SUCCESS - Feature disabled || ยงa๋‹ค์Œ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๋‹ค์‹œ ๋กœ๋“œํ•  ๋•Œ๊นŒ์ง€ ์ผ์‹œ์ ์œผ๋กœ '${0}'์„ (๋ฅผ) ๋น„ํ™œ์„ฑํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค. -Cmd SUCCESS - WebUser register || ยงa์ƒˆ ์‚ฌ์šฉ์ž (${0})๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค! -Cmd unregister - unregistering || '${0}'๋“ฑ๋ก ์ทจ์†Œ .. -Cmd WARN - Database not open || ยงe${0}๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์˜ˆ์ƒ๋ณด๋‹ค ์˜ค๋ž˜ ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค .. -Cmd Web - Permission Levels || >\ยง70: ๋ชจ๋“  ํŽ˜์ด์ง€์— ์•ก์„ธ์Šค \ยง71: '/players'๋ฐ ๋ชจ๋“  ํ”Œ๋ ˆ์ด์–ด ํŽ˜์ด์ง€์— ์•ก์„ธ์Šค \ยง72: ์›น ์‚ฌ์šฉ์ž์™€ ๋™์ผํ•œ ์‚ฌ์šฉ์ž ์ด๋ฆ„์œผ๋กœ ํ”Œ๋ ˆ์ด์–ด ํŽ˜์ด์ง€์— ์•ก์„ธ์Šค \ยง73+: ๊ถŒํ•œ ์—†์Œ -Command Help - /plan db || Plan ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ด€๋ฆฌ์ž -Command Help - /plan db backup || ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํŒŒ์ผ๋กœ ๋ฐฑ์—…ํ•ฉ๋‹ˆ๋‹ค. -Command Help - /plan db clear || ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์žˆ๋Š” ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. -Command Help - /plan db hotswap || ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๋น ๋ฅด๊ฒŒ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค. -Command Help - /plan db move || ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ฐ„์— ๋ฐ์ดํ„ฐ ์ด๋™ ํ•ฉ๋‹ˆ๋‹ค. -Command Help - /plan db remove || ํ˜„์žฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ํ”Œ๋ ˆ์ด์–ด ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค. -Command Help - /plan db restore || ํŒŒ์ผ์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณต์›ํ•ฉ๋‹ˆ๋‹ค. -Command Help - /plan db uninstalled || ์„œ๋ฒ„์—์„œ ์„ค์น˜ํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. -Command Help - /plan disable || ํ”Œ๋Ÿฌ๊ทธ์ธ ๋น„ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค. -Command Help - /plan export || HTML ๋˜๋Š” JSON ํ˜•์‹ ํŒŒ์ผ๋กœ ๋‚ด๋ณด๋ƒ…๋‹ˆ๋‹ค. -Command Help - /plan import || ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ต๋‹ˆ๋‹ค. -Command Help - /plan info || Plan ํ”Œ๋Ÿฌ๊ทธ์ธ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. -Command Help - /plan ingame || ๊ฒŒ์ž„์—์„œ ํ”Œ๋ ˆ์ด์–ด ์ •๋ณด๋ฅผ ์—ด๋žŒํ•ฉ๋‹ˆ๋‹ค. -Command Help - /plan json || ํ”Œ๋ ˆ์ด์–ด ๋กœ์šฐ ๋ฐ์ดํ„ฐ์˜ json๋ฅผ ์—ด๋žŒํ•ฉ๋‹ˆ๋‹ค. -Command Help - /plan logout || Log out other users from the panel. -Command Help - /plan network || ๋„คํŠธ์›Œํฌ ์ •๋ณด๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. -Command Help - /plan player || ํ”Œ๋ ˆ์ด์–ด ์ •๋ณด ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. -Command Help - /plan players || ํ”Œ๋ ˆ์ด์–ด ๋ชฉ๋ก ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. -Command Help - /plan register || Plan ์›น ์‚ฌ์ดํŠธ์— ์‚ฌ์šฉ์ž ๋“ฑ๋ก -Command Help - /plan reload || Plan ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๋ฆฌ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค. -Command Help - /plan search || ํ”Œ๋ ˆ์ด์–ด๋ช…์„ ๊ฒ€์ƒ‰ํ•ฉ๋‹ˆ๋‹ค. -Command Help - /plan server || ์„œ๋ฒ„ ์ •๋ณด ์—ด๋žŒํ•˜๊ธฐ -Command Help - /plan servers || ์„œ๋ฒ„ ๋ชฉ๋ก ์—ด๋žŒํ•˜๊ธฐ -Command Help - /plan unregister || Plan ํŽ˜์ด์ง€ ๊ณ„์ • ํƒˆํ‡ดํ•ฉ๋‹ˆ๋‹ค. -Command Help - /plan users || ๋ชจ๋“  ์›น ์‚ฌ์šฉ์ž ๋‚˜์—ดํ•ฉ๋‹ˆ๋‹ค. -Database - Apply Patch || ํŒจ์น˜ ์ ์šฉ ์ค‘: ${0}.. -Database - Patches Applied || ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํŒจ์น˜๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ ์šฉ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. -Database - Patches Applied Already || ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํŒจ์น˜๊ฐ€ ์ด๋ฏธ ์ ์šฉ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. -Database MySQL - Launch Options Error || ๊ธฐ๋ณธ๊ฐ’์„ ์‚ฌ์šฉํ•˜์—ฌ ์‹œ์ž‘ ์˜ต์…˜์— ๊ฒฐํ•จ์ด ์žˆ์Šต๋‹ˆ๋‹ค. (${0}) -Database Notify - Clean || ${0} ํ”Œ๋ ˆ์ด์–ด์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ญ์ œํ–ˆ์Šต๋‹ˆ๋‹ค -Database Notify - SQLite No WAL || ์ด ์„œ๋ฒ„ ๋ฒ„์ „์—์„œ๋Š” SQLite WAL ๋ชจ๋“œ๊ฐ€ ์ง€์›๋˜์ง€ ์•Š์œผ๋ฉฐ ๊ธฐ๋ณธ๊ฐ’์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์„ฑ๋Šฅ์— ์˜ํ–ฅ์„ ์ค„ ์ˆ˜๋„ ์žˆ๊ณ  ๊ทธ๋ ‡์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. -Disable || ํ”Œ๋ ˆ์ด์–ด ๋ถ„์„์ด ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค. -Disable - Processing || ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์€ ์ค‘์š”ํ•œ ์ž‘์—…์ด ์žˆ์Šต๋‹ˆ๋‹ค.. (${0}) -Disable - Processing Complete || ์ฒ˜๋ฆฌ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. -Disable - Unsaved Session Save || ์™„๋ฃŒ๋˜์ง€ ์•Š์€ ์„ธ์…˜ ์ €์žฅ .. -Disable - Unsaved Session Save Timeout || Timeout hit, storing the unfinished sessions on next enable instead. -Disable - Waiting SQLite || Waiting queries to finish to avoid SQLite crashing JVM.. -Disable - Waiting SQLite Complete || Closed SQLite connection. -Disable - Waiting Transactions || Waiting for unfinished transactions to avoid data loss.. -Disable - Waiting Transactions Complete || Transaction queue closed. -Disable - WebServer || ์›น ์„œ๋ฒ„๊ฐ€ ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค. -Enable || ํ”Œ๋ ˆ์ด์–ด ๋ถ„์„์ด ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค. -Enable - Database || ${0}-๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์ด ์„ค์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค. -Enable - Notify Bad IP || 0.0.0.0์€ ์œ ํšจํ•œ ์ฃผ์†Œ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. Alternative_IP ์„ค์ •์„ ์ง€์ •ํ•˜์‹ญ์‹œ์˜ค. ์ž˜๋ชป๋œ ๋งํฌ๋กœ ์ œ๊ณต ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค! -Enable - Notify Empty IP || server.properties์˜ IP๊ฐ€ ๋น„์–ด ์žˆ๊ณ  ๋Œ€์ฒด IP๊ฐ€ ์‚ฌ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ž˜๋ชป๋œ ๋งํฌ๊ฐ€ ์ œ๊ณต ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค! -Enable - Notify Geolocations disabled || ์œ„์น˜ ์ •๋ณด ์ˆ˜์ง‘์ด ํ™œ์„ฑํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. (Data.Geolocations : false) -Enable - Notify Geolocations Internet Required || Plan GeoLite2 Geolocation ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๋‹ค์šด๋กœ๋“œํ•˜๋ ค๋ฉด ์ฒ˜์Œ ์‹คํ–‰ํ•  ๋•Œ ์ธํ„ฐ๋„ท ์•ก์„ธ์Šค๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. -Enable - Notify Webserver disabled || ์›น ์„œ๋ฒ„๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. (WebServer.DisableWebServer : true) -Enable - Storing preserved sessions || Storing sessions that were preserved before previous shutdown. -Enable - WebServer || ํ•ด๋‹น ํฌํŠธ์—์„œ ์‹คํ–‰๋˜๋Š” ์›น ์„œ๋ฒ„ ${0} ( ${1} ) -Enable FAIL - Database || ${0}-๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์‹คํŒจ: ${1} -Enable FAIL - Database Patch || ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํŒจ์น˜์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๋น„ํ™œ์„ฑํ™”ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ฌธ์ œ๋ฅผ ์‹ ๊ณ ํ•˜์‹ญ์‹œ์˜ค -Enable FAIL - GeoDB Write || ๋‹ค์šด๋กœ๋“œ ํ•œ GeoLite2 Geolocation ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์ €์žฅํ•˜๋Š” ๋„์ค‘์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. -Enable FAIL - WebServer (Proxy) || ์›น ์„œ๋ฒ„๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค! -Enable FAIL - Wrong Database Type || ${0} ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋Š” ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. -HTML - AND_BUG_REPORTERS || & Bug reporters! -HTML - BANNED (Filters) || Banned -HTML - COMPARING_15_DAYS || ์ง€๋‚œ 15์ผ ๋น„๊ต -HTML - COMPARING_60_DAYS || ์ง€๋‚œ 30์ผ ๋น„๊ต -HTML - COMPARING_7_DAYS || ์ง€๋‚œ 7์ผ ๋น„๊ต -HTML - DATABASE_NOT_OPEN || ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์—ด๋ ค ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. /plan info ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค. -HTML - DESCRIBE_RETENTION_PREDICTION || This value is a prediction based on previous players. -HTML - ERROR || ์˜ค๋ฅ˜๋กœ ์ธํ•ด ์ธ์ฆ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. -HTML - EXPIRED_COOKIE || ์‚ฌ์šฉ์ž ์ฟ ํ‚ค๊ฐ€ ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. -HTML - FILTER_ACTIVITY_INDEX_NOW || Current activity group -HTML - FILTER_ALL_PLAYERS || All players -HTML - FILTER_BANNED || Ban status -HTML - FILTER_GROUP || Group: -HTML - FILTER_OPS || Operator status -HTML - INDEX_ACTIVE || ํ™œ๋™์ ์ธ -HTML - INDEX_INACTIVE || ๋น„ํ™œ์„ฑ -HTML - INDEX_IRREGULAR || ๋ถˆ๊ทœ์น™ํ•œ -HTML - INDEX_REGULAR || ๊ทœ์น™์ ์ธ -HTML - INDEX_VERY_ACTIVE || ๋งค์šฐ ํ™œ์„ฑํ™”๋œ -HTML - KILLED || Killed -HTML - LABEL_1ST_WEAPON || ์น˜๋ช…์ ์ธ PvP ๋ฌด๊ธฐ -HTML - LABEL_2ND_WEAPON || 2nd PvP ๋ฌด๊ธฐ -HTML - LABEL_3RD_WEAPON || 3rd PvP ๋ฌด๊ธฐ -HTML - LABEL_ACTIVE_PLAYTIME || Active Playtime -HTML - LABEL_ACTIVITY_INDEX || ํ™œ๋™ ์ƒ‰์ธ -HTML - LABEL_AFK || AFK -HTML - LABEL_AFK_TIME || AFK ์‹œ๊ฐ„ -HTML - LABEL_AVG || ํ‰๊ท  -HTML - LABEL_AVG_ACTIVE_PLAYTIME || Average Active Playtime -HTML - LABEL_AVG_AFK_TIME || Average AFK Time -HTML - LABEL_AVG_CHUNKS || Average Chunks -HTML - LABEL_AVG_ENTITIES || Average Entities -HTML - LABEL_AVG_KDR || ํ‰๊ท  ์‚ฌ๋ง ํšŸ์ˆ˜(KDR) -HTML - LABEL_AVG_MOB_KDR || ํ‰๊ท  ๋ชฌ์Šคํ„ฐ ์‚ฌ๋ง ํšŸ์ˆ˜(Mob KDR) -HTML - LABEL_AVG_PLAYTIME || ํ‰๊ท  ํ”Œ๋ ˆ์ด ํƒ€์ž„ -HTML - LABEL_AVG_SESSION_LENGTH || ํ‰๊ท  ์ ‘์† ์‹œ๊ฐ„(์„ธ์…˜ ๊ธธ์ด) -HTML - LABEL_AVG_SESSIONS || Average Sessions -HTML - LABEL_AVG_TPS || ํ‰๊ท  TPS -HTML - LABEL_BANNED || Banned -HTML - LABEL_BEST_PEAK || ์ตœ๊ณ ์˜ ํ”ผํฌ -HTML - LABEL_DAY_OF_WEEK || ์š”์ผ -HTML - LABEL_DEATHS || ์ฃฝ์€ ํšŸ์ˆ˜ -HTML - LABEL_DOWNTIME || ๋‹ค์šดํƒ€์ž„ -HTML - LABEL_DURING_LOW_TPS || ๋‚ฎ์€ TPS ์ŠคํŒŒ์ดํฌ ๋™์•ˆ: -HTML - LABEL_ENTITIES || ์—”ํ‹ฐํ‹ฐ -HTML - LABEL_FAVORITE_SERVER || ์ฆ๊ฒจ์ฐพ๋Š” ์„œ๋ฒ„ -HTML - LABEL_FIRST_SESSION_LENGTH || ์ตœ์ดˆ ์ ‘์† ์‹œ๊ฐ„ -HTML - LABEL_FREE_DISK_SPACE || ์—ฌ์œ  ๋””์Šคํฌ ๊ณต๊ฐ„ -HTML - LABEL_INACTIVE || ๋น„ํ™œ์„ฑ -HTML - LABEL_LAST_PEAK || ๋งˆ์ง€๋ง‰ ํ”ผํฌ -HTML - LABEL_LAST_SEEN || ๋งˆ์ง€๋ง‰์œผ๋กœ ๋ณธ -HTML - LABEL_LOADED_CHUNKS || ๋กœ๋“œ๋œ ์ฒญํฌ -HTML - LABEL_LOADED_ENTITIES || ๋กœ๋“œ๋œ ์—”ํ‹ฐํ‹ฐ -HTML - LABEL_LONE_JOINS || ์ตœ๊ทผ ์ ‘์†(Lone Joins) -HTML - LABEL_LONE_NEW_JOINS || ์ตœ๊ทผ ์‹ ๊ทœ ์ ‘์†(Lone New Joins) -HTML - LABEL_LONGEST_SESSION || ๊ฐ€์žฅ ๊ธด ์ ‘์† ์‹œ๊ฐ„ -HTML - LABEL_LOW_TPS || ๋‚ฎ์€ TPS ์ŠคํŒŒ์ดํฌ -HTML - LABEL_MAX_FREE_DISK || ์ตœ๋Œ€ ์—ฌ์œ  ๋””์Šคํฌ์šฉ๋Ÿ‰ -HTML - LABEL_MIN_FREE_DISK || ์ตœ์†Œ ์—ฌ์œ  ๋””์Šคํฌ์šฉ๋Ÿ‰ -HTML - LABEL_MOB_DEATHS || ๋ชฌ์Šคํ„ฐํ•œํ…Œ ์ฃฝ์€ ํšŸ์ˆ˜ -HTML - LABEL_MOB_KDR || ๋ชฌ์Šคํ„ฐ KDR -HTML - LABEL_MOB_KILLS || ๋ชฌ์Šคํ„ฐ ํ‚ฌ์ˆ˜ -HTML - LABEL_MOST_ACTIVE_GAMEMODE || ๊ฐ€์žฅ ํ™œ๋™์ ์ธ ๊ฒŒ์ž„๋ชจ๋“œ -HTML - LABEL_NAME || ์ด๋ฆ„ -HTML - LABEL_NEW || ์‹ ๊ทœ -HTML - LABEL_NEW_PLAYERS || ์‹ ๊ทœ ํ”Œ๋ ˆ์ด์–ด์ˆ˜ -HTML - LABEL_NICKNAME || ๋‹‰๋„ค์ž„ -HTML - LABEL_NO_SESSION_KILLS || None -HTML - LABEL_ONLINE_FIRST_JOIN || ์ฒ˜์Œ ์ฐธ๊ฐ€ํ•  ๋•Œ ์˜จ๋ผ์ธ ํ”Œ๋ ˆ์ด์–ด -HTML - LABEL_OPERATOR || ๊ด€๋ฆฌ์ž(OP) -HTML - LABEL_PER_PLAYER || / ํ”Œ๋ ˆ์ด์–ด -HTML - LABEL_PER_REGULAR_PLAYER || / ์ผ๋ฐ˜ ํ”Œ๋ ˆ์ด์–ด -HTML - LABEL_PLAYER_DEATHS || ํ”Œ๋ ˆ์ด์–ด์— ์˜ํ•œ ์ฃฝ์€ ํšŸ์ˆ˜ -HTML - LABEL_PLAYER_KILLS || ํ”Œ๋ ˆ์ด์–ด ํ‚ฌ์ˆ˜ -HTML - LABEL_PLAYERS_ONLINE || ์ ‘์†์ค‘์ธ ํ”Œ๋ ˆ์ด์–ด -HTML - LABEL_PLAYTIME || ํ”Œ๋ ˆ์ดํƒ€์ž„ -HTML - LABEL_REGISTERED || ์‹ ๊ทœ -HTML - LABEL_REGISTERED_PLAYERS || ์‹ ๊ทœ ํ”Œ๋ ˆ์ด์–ด -HTML - LABEL_REGULAR || ์‹ ๊ทœ -HTML - LABEL_REGULAR_PLAYERS || ์‹ ๊ทœ ํ”Œ๋ ˆ์ด์–ด -HTML - LABEL_RELATIVE_JOIN_ACTIVITY || ์ƒ๋Œ€ ์กฐ์ธ ํ™œ๋™ -HTML - LABEL_RETENTION || ์‹ ๊ทœ ํ”Œ๋ ˆ์ด์–ด ์œ ์ง€ -HTML - LABEL_SERVER_DOWNTIME || ์„œ๋ฒ„ ๋‹ค์šดํƒ€์ž„ -HTML - LABEL_SERVER_OCCUPIED || ์ ์œ ๋œ ์„œ๋ฒ„ -HTML - LABEL_SESSION_ENDED || ์ข…๋ฃŒ -HTML - LABEL_SESSION_MEDIAN || ์„ธ์…˜ ์ค‘์•™๊ฐ’ -HTML - LABEL_TIMES_KICKED || ์ ‘์†์ข…๋ฃŒํ•œ ์‹œ๊ฐ„ -HTML - LABEL_TOTAL_PLAYERS || ์ด ํ”Œ๋ ˆ์ด์–ด์ˆ˜ -HTML - LABEL_TOTAL_PLAYTIME || ์ด ํ”Œ๋ ˆ์ดํƒ€์ž„ -HTML - LABEL_UNIQUE_PLAYERS || ๊ธฐ์กด ํ”Œ๋ ˆ์ด์–ด -HTML - LABEL_WEEK_DAYS || '์›”์š”์ผ', 'ํ™”์š”์ผ', '์ˆ˜์š”์ผ', '๋ชฉ์š”์ผ', '๊ธˆ์š”์ผ', 'ํ† ์š”์ผ', '์ผ์š”์ผ' -HTML - LINK_BACK_NETWORK || ๋„คํŠธ์›Œํฌ ํŽ˜์ด์ง€ -HTML - LINK_BACK_SERVER || ์„œ๋ฒ„ ํŽ˜์ด์ง€ -HTML - LINK_CHANGELOG || ๋ณ€๊ฒฝ ๋กœ๊ทธ ๋ณด๊ธฐ -HTML - LINK_DISCORD || ๋””์Šค์ฝ”๋“œ๋กœ ๊ธฐ์ˆ ์ง€์› -HTML - LINK_DOWNLOAD || ๋‹ค์šด๋กœ๋“œ -HTML - LINK_ISSUES || ๋ฌธ์ œ ๋ณด๊ณ  -HTML - LINK_NIGHT_MODE || ์•ผ๊ฐ„ ๋ชจ๋“œ -HTML - LINK_PLAYER_PAGE || ํ”Œ๋ ˆ์ด์–ด ํŽ˜์ด์ง€ -HTML - LINK_QUICK_VIEW || ํ€ต ๋ทฐ -HTML - LINK_SERVER_ANALYSIS || ์„œ๋ฒ„ ๋ถ„์„ -HTML - LINK_WIKI || Plan ์œ„ํ‚ค, ํŠœํ† ๋ฆฌ์–ผ & ๋ฌธ์„œ -HTML - LOCAL_MACHINE || ๋กœ์ปฌ ๋จธ์‹  -HTML - LOGIN_CREATE_ACCOUNT || Create an Account! -HTML - LOGIN_FAILED || Login failed: -HTML - LOGIN_FORGOT_PASSWORD || Forgot Password? -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_1 || Forgot password? Unregister and register again. -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_2 || Use the following command in game to remove your current user: -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_3 || Or using console: -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_4 || After using the command, -HTML - LOGIN_LOGIN || Login -HTML - LOGIN_LOGOUT || Logout -HTML - LOGIN_PASSWORD || "Password" -HTML - LOGIN_USERNAME || "Username" -HTML - NAV_PLUGINS || ํ”Œ๋Ÿฌ๊ทธ์ธ -HTML - NEW_CALENDAR || New: -HTML - NO_KILLS || ์‚ด์ธ ์—†์Œ -HTML - NO_USER_PRESENT || ์‚ฌ์šฉ์ž ์ฟ ํ‚ค๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. -HTML - NON_OPERATORS (Filters) || Non operators -HTML - NOT_BANNED (Filters) || Not banned -HTML - OFFLINE || ์˜คํ”„๋ผ์ธ -HTML - ONLINE || ์˜จ๋ผ์ธ -HTML - OPERATORS (Filters) || Operators -HTML - PER_DAY || / Day -HTML - PLAYERS_TEXT || ํ”Œ๋ ˆ์ด์–ด๋“ค -HTML - QUERY || Query< -HTML - QUERY_ACTIVITY_OF_MATCHED_PLAYERS || Activity of matched players -HTML - QUERY_ACTIVITY_ON || Activity on -HTML - QUERY_ADD_FILTER || Add a filter.. -HTML - QUERY_AND || and -HTML - QUERY_ARE || `are` -HTML - QUERY_ARE_ACTIVITY_GROUP || are in Activity Groups -HTML - QUERY_ARE_PLUGIN_GROUP || are in ${plugin}'s ${group} Groups -HTML - QUERY_JOINED_WITH_ADDRESS || joined with address -HTML - QUERY_LOADING_FILTERS || Loading filters.. -HTML - QUERY_MAKE || Make a query -HTML - QUERY_MAKE_ANOTHER || Make another query -HTML - QUERY_OF_PLAYERS || of Players who -HTML - QUERY_PERFORM_QUERY || Perform Query! -HTML - QUERY_PLAYED_BETWEEN || Played between -HTML - QUERY_REGISTERED_BETWEEN || Registered between -HTML - QUERY_RESULTS || Query Results -HTML - QUERY_RESULTS_MATCH || matched ${resultCount} players -HTML - QUERY_SESSIONS_WITHIN_VIEW || Sessions within view -HTML - QUERY_SHOW_VIEW || Show a view -HTML - QUERY_TIME_FROM || >from -HTML - QUERY_TIME_TO || >to -HTML - QUERY_VIEW || View: -HTML - QUERY_ZERO_RESULTS || Query produced 0 results -HTML - REGISTER || Register -HTML - REGISTER_CHECK_FAILED || Checking registration status failed: -HTML - REGISTER_COMPLETE || Complete Registration -HTML - REGISTER_COMPLETE_INSTRUCTIONS_1 || You can now finish registering the user. -HTML - REGISTER_COMPLETE_INSTRUCTIONS_2 || Code expires in 15 minutes -HTML - REGISTER_COMPLETE_INSTRUCTIONS_3 || Use the following command in game to finish registration: -HTML - REGISTER_COMPLETE_INSTRUCTIONS_4 || Or using console: -HTML - REGISTER_CREATE_USER || Create a new user -HTML - REGISTER_FAILED || Registration failed: -HTML - REGISTER_HAVE_ACCOUNT || Already have an account? Login! -HTML - REGISTER_PASSWORD_TIP || Password should be more than 8 characters, but there are no limitations. -HTML - REGISTER_SPECIFY_PASSWORD || You need to specify a Password -HTML - REGISTER_SPECIFY_USERNAME || You need to specify a Username -HTML - REGISTER_USERNAME_LENGTH || Username can be up to 50 characters, yours is -HTML - REGISTER_USERNAME_TIP || Username can be up to 50 characters. -HTML - SESSION || ์„ธ์…˜ -HTML - SIDE_GEOLOCATIONS || ์ง€๋ฆฌ์  ์œ„์น˜ -HTML - SIDE_INFORMATION || ์ •๋ณด -HTML - SIDE_LINKS || LINKS -HTML - SIDE_NETWORK_OVERVIEW || ๋„คํŠธ์›Œํฌ ๊ฐœ์š” -HTML - SIDE_OVERVIEW || ๊ฐœ์š” -HTML - SIDE_PERFORMANCE || ์„ฑ๋Šฅ -HTML - SIDE_PLAYER_LIST || ํ”Œ๋ ˆ์ด์–ด ๋ชฉ๋ก -HTML - SIDE_PLAYERBASE || ํ”Œ๋ ˆ์ด์–ด ๊ธฐ๋ฐ˜ -HTML - SIDE_PLAYERBASE_OVERVIEW || ํ”Œ๋ ˆ์ด์–ด ๊ธฐ๋ฐ˜ ๊ฐœ์š” -HTML - SIDE_PLUGINS || ํ”Œ๋Ÿฌ๊ทธ์ธ ๋ชฉ๋ก -HTML - SIDE_PVP_PVE || PvP & PvE -HTML - SIDE_SERVERS || ์„œ๋ฒ„ ๋ชฉ๋ก -HTML - SIDE_SERVERS_TITLE || ์„œ๋ฒ„ ๋ชฉ๋ก -HTML - SIDE_SESSIONS || ์„ธ์…˜ ๋ชฉ๋ก -HTML - SIDE_TO_MAIN_PAGE || ๋ฉ”์ธ ํŽ˜์ด์ง€๋กœ -HTML - TEXT_CLICK_TO_EXPAND || ํ™•์žฅํ•˜๋ ค๋ฉด ํด๋ฆญ -HTML - TEXT_CONTRIBUTORS_CODE || ์ฝ”๋“œ ๊ธฐ์—ฌ์ž -HTML - TEXT_CONTRIBUTORS_LOCALE || ๋ฒˆ์—ญ -HTML - TEXT_CONTRIBUTORS_MONEY || ๊ธˆ์ „์ ์œผ๋กœ ๊ฐœ๋ฐœ์„ ์ง€์› ํ•ด์ฃผ์‹  ๋ถ„๋“ค๊ป˜ ํŠน๋ณ„ํžˆ ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค. -HTML - TEXT_CONTRIBUTORS_THANKS || ๋˜ํ•œ ๋‹ค์Œ ๋ฉ‹์ง„ ์‚ฌ๋žŒ๋“ค์ด ๊ธฐ์—ฌํ–ˆ์Šต๋‹ˆ๋‹ค. -HTML - TEXT_DEV_VERSION || ์ด ๋ฒ„์ „์€ DEV ๋ฆด๋ฆฌ์Šค์ž…๋‹ˆ๋‹ค. -HTML - TEXT_DEVELOPED_BY || ์— ์˜ํ•ด ๊ฐœ๋ฐœ๋˜์—ˆ์Šต๋‹ˆ๋‹ค -HTML - TEXT_FIRST_SESSION || ์ฒซ ๋ฒˆ์งธ ์„ธ์…˜ -HTML - TEXT_LICENSED_UNDER || ํ”Œ๋ ˆ์ด์–ด ๋ถ„์„ ๊ฐœ๋ฐœ ๋ฐ ๋ผ์ด์„ผ์Šคํ•˜์— ์žˆ์Šต๋‹ˆ๋‹ค -HTML - TEXT_METRICS || bStats ๋ฉ”ํŠธ๋ฆญ -HTML - TEXT_NO_EXTENSION_DATA || ํ™•์žฅ ๋ฐ์ดํ„ฐ ์—†์Œ -HTML - TEXT_NO_LOW_TPS || ๋‚ฎ์€ tps ์ŠคํŒŒ์ดํฌ ์—†์Œ -HTML - TEXT_NO_SERVER || ์˜จ๋ผ์ธ ํ™œ๋™์„ ํ‘œ์‹œ ํ•  ์„œ๋ฒ„๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. -HTML - TEXT_NO_SERVERS || ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์„œ๋ฒ„๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. -HTML - TEXT_PLUGIN_INFORMATION || ํ”Œ๋Ÿฌ๊ทธ์ธ์— ๋Œ€ํ•œ ์ •๋ณด -HTML - TEXT_PREDICTED_RETENTION || ์ด ๊ฐ’์€ ๊ธฐ์กด ํ”Œ๋ ˆ์ด์–ด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•œ ์˜ˆ์ธก์ž…๋‹ˆ๋‹ค. -HTML - TEXT_SERVER_INSTRUCTIONS || It appears that Plan is not installed on any game servers or not connected to the same database. See wiki for Network tutorial. -HTML - TEXT_VERSION || ์ƒˆ ๋ฒ„์ „์ด ์ถœ์‹œ๋˜์—ˆ์œผ๋ฉฐ ์ด์ œ ๋‹ค์šด๋กœ๋“œ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -HTML - TITLE_30_DAYS || 30์ผ -HTML - TITLE_30_DAYS_AGO || 30์ผ ์ „ -HTML - TITLE_ALL || ๋ชจ๋‘ -HTML - TITLE_ALL_TIME || ๋ชจ๋“  ์‹œ๊ฐ„ -HTML - TITLE_AS_NUMBERS || ์ˆซ์ž๋กœ -HTML - TITLE_AVG_PING || ํ‰๊ท  Ping -HTML - TITLE_BEST_PING || ์ตœ๊ณ  Ping -HTML - TITLE_CALENDAR || ๋‹ฌ๋ ฅ -HTML - TITLE_CONNECTION_INFO || ์—ฐ๊ฒฐ ์ •๋ณด -HTML - TITLE_COUNTRY || ๊ตญ๊ฐ€ -HTML - TITLE_CPU_RAM || CPU & RAM -HTML - TITLE_CURRENT_PLAYERBASE || ํ˜„์žฌ ํ”Œ๋ ˆ์ด์–ด ๋ฒ ์ด์Šค -HTML - TITLE_DISK || ๋””์Šคํฌ ๊ณต๊ฐ„ -HTML - TITLE_GRAPH_DAY_BY_DAY || Day by Day -HTML - TITLE_GRAPH_HOUR_BY_HOUR || Hour by Hour -HTML - TITLE_GRAPH_NETWORK_ONLINE_ACTIVITY || ๋„คํŠธ์›Œํฌ ์˜จ๋ผ์ธ ํ™œ๋™ -HTML - TITLE_GRAPH_PUNCHCARD || 30์ผ ๋™์•ˆ์˜ ํŽ€์น˜ ์นด๋“œ -HTML - TITLE_INSIGHTS || 30์ผ ๋™์•ˆ์˜ ์ธ์‚ฌ์ดํŠธ -HTML - TITLE_IS_AVAILABLE || ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค -HTML - TITLE_JOIN_ADDRESSES || Join Addresses -HTML - TITLE_LAST_24_HOURS || ์ง€๋‚œ 24์‹œ๊ฐ„ -HTML - TITLE_LAST_30_DAYS || ์ง€๋‚œ 30์ผ -HTML - TITLE_LAST_7_DAYS || ์ง€๋‚œ 7์ผ -HTML - TITLE_LAST_CONNECTED || ๋งˆ์ง€๋ง‰ ์—ฐ๊ฒฐ -HTML - TITLE_LENGTH || ๊ธธ์ด -HTML - TITLE_MOST_PLAYED_WORLD || ๊ฐ€์žฅ ๋งŽ์ด ํ”Œ๋ ˆ์ด ํ•œ ๋งต -HTML - TITLE_NETWORK || ๋„คํŠธ์›Œํฌ -HTML - TITLE_NETWORK_AS_NUMBERS || ๋„คํŠธ์›Œํฌ ์ˆซ์ž -HTML - TITLE_NOW || ํ˜„์žฌ -HTML - TITLE_ONLINE_ACTIVITY || ์˜จ๋ผ์ธ ํ™œ๋™ -HTML - TITLE_ONLINE_ACTIVITY_AS_NUMBERS || ์˜จ๋ผ์ธ ํ™œ๋™ ์ˆซ์ž -HTML - TITLE_ONLINE_ACTIVITY_OVERVIEW || ์˜จ๋ผ์ธ ํ™œ๋™ ๊ฐœ์š” -HTML - TITLE_PERFORMANCE_AS_NUMBERS || ์„ฑ๋Šฅ ํ†ต๊ณ„ -HTML - TITLE_PING || Ping -HTML - TITLE_PLAYER || ํ”Œ๋ ˆ์ด์–ด -HTML - TITLE_PLAYER_OVERVIEW || ํ”Œ๋ ˆ์ด์–ด ๊ฐœ์š” -HTML - TITLE_PLAYERBASE_DEVELOPMENT || Playerbase ๊ฐœ๋ฐœ -HTML - TITLE_PVP_DEATHS || ์ตœ๊ทผ PvP ์‚ฌ๋ง -HTML - TITLE_PVP_KILLS || ์ตœ๊ทผ PvP ์ฒ˜์น˜ -HTML - TITLE_PVP_PVE_NUMBERS || PvP & PvE ํ†ต๊ณ„ -HTML - TITLE_RECENT_KILLS || ์ตœ๊ทผ ํ‚ฌ -HTML - TITLE_RECENT_SESSIONS || ์ตœ๊ทผ ์„ธ์…˜ -HTML - TITLE_SEEN_NICKNAMES || ๋ณธ ๋ณ„๋ช… -HTML - TITLE_SERVER || ์„œ๋ฒ„ -HTML - TITLE_SERVER_AS_NUMBERS || ์„œ๋ฒ„ ๋ฒˆํ˜ธ -HTML - TITLE_SERVER_OVERVIEW || ์„œ๋ฒ„ ๊ฐœ์š” -HTML - TITLE_SERVER_PLAYTIME || ์„œ๋ฒ„ ํ”Œ๋ ˆ์ด ํƒ€์ž„ -HTML - TITLE_SERVER_PLAYTIME_30 || ์„œ๋ฒ„ ํ”Œ๋ ˆ์ด ํƒ€์ž„ - ์ตœ๊ทผ 30์ผ -HTML - TITLE_SESSION_START || ์„ธ์…˜ ์‹œ์ž‘ -HTML - TITLE_THEME_SELECT || ํ…Œ๋งˆ ์„ ํƒ -HTML - TITLE_TITLE_PLAYER_PUNCHCARD || ํŽ€์น˜ ์นด๋“œ -HTML - TITLE_TPS || TPS -HTML - TITLE_TREND || ํŠธ๋ Œ๋“œ -HTML - TITLE_TRENDS || 30์ผ ๋™์•ˆ์˜ ํŠธ๋ Œ๋“œ -HTML - TITLE_VERSION || ๋ฒ„์ „ -HTML - TITLE_WEEK_COMPARISON || ์ฃผ ๋น„๊ต -HTML - TITLE_WORLD || ์›”๋“œ ๋กœ๋“œ -HTML - TITLE_WORLD_PLAYTIME || ๋งต ํ”Œ๋ ˆ์ด ํƒ€์ž„ -HTML - TITLE_WORST_PING || Worst Ping -HTML - TOTAL_ACTIVE_TEXT || ์ด ํ™œ์„ฑํ™” ์‹œ๊ฐ„ -HTML - TOTAL_AFK || ์ด AFK ์‹œ๊ฐ„ -HTML - TOTAL_PLAYERS || ์ด ํ”Œ๋ ˆ์ด์–ด -HTML - UNIQUE_CALENDAR || Unique: -HTML - UNIT_CHUNKS || ์ฒญํฌ ์ˆ˜ -HTML - UNIT_ENTITIES || ์—”ํ‹ฐํ‹ฐ ์ˆ˜ -HTML - UNIT_NO_DATA || ๋ฐ์ดํ„ฐ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. -HTML - UNIT_THE_PLAYERS || ํ”Œ๋ ˆ์ด์–ด -HTML - USER_AND_PASS_NOT_SPECIFIED || ์‚ฌ์šฉ์ž ๋ฐ ์•”ํ˜ธ๊ฐ€ ์ง€์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. -HTML - USER_DOES_NOT_EXIST || ์‚ฌ์šฉ์ž๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค -HTML - USER_INFORMATION_NOT_FOUND || ๋“ฑ๋ก ์‹คํŒจ, ๋‹ค์‹œ ์‹œ๋„ (์ฝ”๋“œ๋Š” 15๋ถ„ ํ›„์— ๋งŒ๋ฃŒ ๋จ) -HTML - USER_PASS_MISMATCH || ์‚ฌ์šฉ์ž์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค -HTML - Version Change log || View Changelog -HTML - Version Current || You have version ${0} -HTML - Version Download || Download Plan-${0}.jar -HTML - Version Update || Update -HTML - Version Update Available || Version ${0} is Available! -HTML - Version Update Dev || This version is a DEV release. -HTML - Version Update Info || A new version has been released and is now available for download. -HTML - WARNING_NO_GAME_SERVERS || Some data requires Plan to be installed on game servers. -HTML - WARNING_NO_GEOLOCATIONS || Geolocation gathering needs to be enabled in the config (Accept GeoLite2 EULA). -HTML - WARNING_NO_SPONGE_CHUNKS || Chunks unavailable on Sponge -HTML - WITH || With -HTML ERRORS - ACCESS_DENIED_403 || Access Denied 403 -HTML ERRORS - AUTH_FAIL_TIPS_401 || - Ensure you have registered a user with /plan register
- Check that the username and password are correct
- Username and password are case-sensitive

If you have forgotten your password, ask a staff member to delete your old user and re-register. -HTML ERRORS - AUTHENTICATION_FAILED_401 || Authentication Failed. 401 -HTML ERRORS - FORBIDDEN_403 || Forbidden 403 -HTML ERRORS - NO_SERVERS_404 || No Servers online to perform the request. 404 -HTML ERRORS - NOT_FOUND_404 || Not Found 404 -HTML ERRORS - NOT_PLAYED_404 || Plan has not seen this player. 404 -HTML ERRORS - PAGE_NOT_FOUND_404 || Page does not exist. 404 -HTML ERRORS - UNAUTHORIZED_401 || Unauthorized 401 -HTML ERRORS - UNKNOWN_PAGE_404 || Make sure you're accessing a link given by a command, Examples:

/player/{uuid/name}
/server/{uuid/name/id}

-HTML ERRORS - UUID_404 || Player UUID was not found in the database. 404 -In Depth Help - /plan db || Use different database subcommands to change the data in some way -In Depth Help - /plan db backup || Uses SQLite to backup the target database to a file. -In Depth Help - /plan db clear || Clears all Plan tables, removing all Plan-data in the process. -In Depth Help - /plan db hotswap || Reloads the plugin with the other database and changes the config to match. -In Depth Help - /plan db move || Overwrites contents in the other database with the contents in another. -In Depth Help - /plan db remove || Removes all data linked to a player from the Current database. -In Depth Help - /plan db restore || Uses SQLite backup file and overwrites contents of the target database. -In Depth Help - /plan db uninstalled || Marks a server in Plan database as uninstalled so that it will not show up in server queries. -In Depth Help - /plan disable || Disable the plugin or part of it until next reload/restart. -In Depth Help - /plan export || Performs an export to export location defined in the config. -In Depth Help - /plan import || Performs an import to load data into the database. -In Depth Help - /plan info || Display the current status of the plugin. -In Depth Help - /plan ingame || Displays some information about the player in game. -In Depth Help - /plan json || Allows you to download a player's data in json format. All of it. -In Depth Help - /plan logout || Give username argument to log out another user from the panel, give * as argument to log out everyone. -In Depth Help - /plan network || Obtain a link to the /network page, only does so on networks. -In Depth Help - /plan player || Obtain a link to the /player page of a specific player, or the current player. -In Depth Help - /plan players || Obtain a link to the /players page to see a list of players. -In Depth Help - /plan register || Use without arguments to get link to register page. Use --code [code] after registration to get a user. -In Depth Help - /plan reload || Disable and enable the plugin to reload any changes in config. -In Depth Help - /plan search || List all matching player names to given part of a name. -In Depth Help - /plan server || Obtain a link to the /server page of a specific server, or the current server if no arguments are given. -In Depth Help - /plan servers || List ids, names and uuids of servers in the database. -In Depth Help - /plan unregister || Use without arguments to unregister player linked user, or with username argument to unregister another user. -In Depth Help - /plan users || ์›น ์‚ฌ์šฉ์ž๋ฅผ ํ‘œ๋กœ ๋‚˜์—ดํ•ฉ๋‹ˆ๋‹ค. -Manage - Confirm Overwrite || Data in ${0} will be overwritten! -Manage - Confirm Removal || Data in ${0} will be removed! -Manage - Fail || > ยงc๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: ${0} -Manage - Fail File not found || > ยงc${0}์— ํŒŒ์ผ์ด ์—†์Šต๋‹ˆ๋‹ค. -Manage - Fail Incorrect Database || > ยงc'${0}' ์ง€์›๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. -Manage - Fail No Exporter || ยงeExporter '${0}' doesn't exist -Manage - Fail No Importer || ยงeImporter '${0}' doesn't exist -Manage - Fail No Server || ์ฃผ์–ด์ง„ ๋งค๊ฐœ ๋ณ€์ˆ˜๋ฅผ ๊ฐ€์ง„ ์„œ๋ฒ„๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. -Manage - Fail Same Database || > ยงc๋™์ผํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์ž‘์—…ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค! -Manage - Fail Same server || ์ด ์„œ๋ฒ„๋ฅผ ์ œ๊ฑฐ๋œ ๊ฒƒ์œผ๋กœ ํ‘œ์‹œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.(ํ˜„์žฌ ์‚ฌ์šฉ ์ค‘์ž…๋‹ˆ๋‹ค.) -Manage - Fail, Confirmation || > ยงcAdd '-a' argument to confirm execution: ${0} -Manage - List Importers || Importers: -Manage - Progress || ${0} / ${1} ์ฒ˜๋ฆฌ ์ค‘.. -Manage - Remind HotSwap || ยงe์ƒˆ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค (/plan db hotswap ${0})๋กœ ๊ต์ฒดํ•˜๊ณ  ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๋‹ค์‹œ ๋กœ๋“œํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. -Manage - Start || > ยง2๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ์ค‘.. -Manage - Success || > ยงa์„ฑ๊ณต! -Negative || ์•„๋‹ˆ์˜ค -Positive || ์˜ˆ -Today || '์˜ค๋Š˜' -Unavailable || Unavailable -Unknown || ์•Œ ์ˆ˜ ์—†์Œ -Version - DEV || ์ด๊ฒƒ์€ DEV ๋ฆด๋ฆฌ์Šค์ž…๋‹ˆ๋‹ค. -Version - Latest || ์ตœ์‹  ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. -Version - New || ์ตœ์‹  ๋ฒ„์ „ (${0}) ์ด๋ฉฐ, ํ˜„์žฌ ๋ฒ„์ „์€ ${1} ์ž…๋‹ˆ๋‹ค. -Version - New (old) || ์ตœ์‹  ๋ฒ„์ „์€ ${0} -Version FAIL - Read info (old) || ์ตœ์‹  ๋ฒ„์ „์„ ํ™•์ธํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค. -Version FAIL - Read versions.txt || Github/versions.txt ์—์„œ ๋ฒ„์ „ ์ •๋ณด๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. -Web User Listing || ยง2${0} ยง7: ยงf${1} -WebServer - Notify HTTP || ์›น์„œ๋ฒ„: ์ธ์ฆ์„œ ์—†์Œ-> ์‹œ๊ฐํ™”๋ฅผ ์œ„ํ•ด HTTP ์„œ๋ฒ„ ์‚ฌ์šฉ. -WebServer - Notify HTTP User Auth || ์›น์„œ๋ฒ„: ์‚ฌ์šฉ์ž ์ธ์ฆ์ด ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค! (HTTP๋ฅผ ํ†ตํ•ด ์•ˆ์ „ํ•˜์ง€ ์•Š์Œ) -WebServer - Notify HTTPS User Auth || ์›น์„œ๋ฒ„: ์‚ฌ์šฉ์ž ์ธ์ฆ์ด ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค! (๊ตฌ์„ฑ์—์„œ ๋น„ํ™œ์„ฑํ™” ๋จ) -Webserver - Notify IP Whitelist || ์›น์„œ๋ฒ„: IP ํ™”์ดํŠธ๋ฆฌ์ŠคํŠธ๊ฐ€ ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค. -Webserver - Notify IP Whitelist Block || ์›น์„œ๋ฒ„: ${0} '${1}'์— ๋Œ€ํ•œ ์•ก์„ธ์Šค๊ฐ€ ๊ฑฐ๋ถ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. (ํ—ˆ์šฉ๋˜์ง€ ์•Š์Œ) -WebServer - Notify no Cert file || ์›น์„œ๋ฒ„: ์ธ์ฆ์„œ ํ‚ค ์ €์žฅ์†Œ ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Œ : ${0} -WebServer - Notify Using Proxy || ์›น์„œ๋ฒ„: ํ”„๋ก์‹œ ๋ชจ๋“œ HTTPS๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ์„ค์ • ํ•œ ๊ฒฝ์šฐ ์—ญ๋ฐฉํ–ฅ ํ”„๋ก์‹œ๊ฐ€ HTTPS๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ผ์šฐํŒ…๋˜๊ณ  Alternative_IP ๊ณ„ํš์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. -WebServer FAIL - EOF || ์›น์„œ๋ฒ„: ์ธ์ฆ์„œ ํŒŒ์ผ์„ ์ฝ์„ ๋•Œ EOF. (ํŒŒ์ผ์ด ๋น„์–ด ์žˆ์ง€ ์•Š์€์ง€ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค) -WebServer FAIL - Port Bind || ์›น์„œ๋ฒ„: ์„ฑ๊ณต์ ์œผ๋กœ ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ํ•ด๋‹น ํฌํŠธ (${0})๊ฐ€ ์‚ฌ์šฉ ์ค‘์ž…๋‹ˆ๊นŒ? -WebServer FAIL - SSL Context || ์›น์„œ๋ฒ„: SSL ์ปจํ…์ŠคํŠธ ์ดˆ๊ธฐํ™”์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. -WebServer FAIL - Store Load || ์›น์„œ๋ฒ„: SSL ์ธ์ฆ์„œ ๋กœ๋“œ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. -Yesterday || '์–ด์ œ' diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_KO.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_KO.yml new file mode 100644 index 000000000..2e92a21c4 --- /dev/null +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_KO.yml @@ -0,0 +1,669 @@ +403AccessDenied: "Access Denied 403" +command: + argument: + backupFile: + description: "๋ฐฑ์—… ํŒŒ์ผ ์ด๋ฆ„(๋Œ€์†Œ๋ฌธ์ž ๊ตฌ๋ถ„)" + name: "๋ฐฑ์—… ํŒŒ์ผ" + code: + description: "๋“ฑ๋ก์„ ์™„๋ฃŒํ•˜๋Š”๋ฐ์— ์‚ฌ์šฉ๋˜๋Š” ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค." + name: "${code}" + dbBackup: + description: "๋ฐฑ์—…ํ•  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์œ ํ˜•์ž…๋‹ˆ๋‹ค. ์ง€์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ํ˜„์žฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค." + dbRestore: + description: "๋ณต์›ํ•  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ์œ ํ˜•์ž…๋‹ˆ๋‹ค. ์ง€์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ํ˜„์žฌ ์‚ฌ์šฉ ์ค‘์ธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค." + dbTypeHotswap: + description: "์‚ฌ์šฉ ์‹œ์ž‘ํ•  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์œ ํ˜•์ž…๋‹ˆ๋‹ค." + dbTypeMoveFrom: + description: "๋ฐ์ดํ„ฐ๋ฅผ ์ด๋™ํ•  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์œ ํ˜•์ž…๋‹ˆ๋‹ค." + dbTypeMoveTo: + description: "๋ฐ์ดํ„ฐ๋ฅผ ์ด๋™ํ•  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์œ ํ˜•์ž…๋‹ˆ๋‹ค." + dbTypeRemove: + description: "๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ฑฐํ•  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์œ ํ˜•์ž…๋‹ˆ๋‹ค." + exportKind: "์ถ”์ถœ ์ข…๋ฅ˜" + feature: + description: "๋น„ํ™œ์„ฑํ™”ํ•  ๊ธฐ๋Šฅ์˜ ์ด๋ฆ„: ${0}" + name: "๊ธฐ๋Šฅ" + importKind: "๋กœ๋“œ ์ข…๋ฅ˜" + nameOrUUID: + description: "Name or UUID of a player" + name: "name/uuid" + removeDescription: "ํ˜„์žฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์‚ญ์ œํ•  ํ”Œ๋ ˆ์ด์–ด ์‹๋ณ„์ž์ž…๋‹ˆ๋‹ค." + server: + description: "Name, ID or UUID of a server" + name: "์„œ๋ฒ„" + subcommand: + description: "๋„์›€๋ง์„ ๋ณด๋ ค๋ฉด ํ•˜์œ„ ๋ช…๋ น์–ด ์—†์ด ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค." + name: "ํ•˜์œ„ ๋ช…๋ น" + username: + description: "๋‹ค๋ฅธ ์‚ฌ์šฉ์ž์˜ ์‚ฌ์šฉ์ž ์ด๋ฆ„์ž…๋‹ˆ๋‹ค. ์ง€์ •๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ํ”Œ๋ ˆ์ด์–ด ์—ฐ๊ฒฐ ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค." + name: "์‚ฌ์šฉ์ž ์ด๋ฆ„" + confirmation: + accept: "๋™์˜ํ•˜๊ธฐ" + cancelNoChanges: "์ทจ์†Œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค." + cancelNoUnregister: "์ทจ์†Œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. '${0}' ์ด(๊ฐ€) ๋“ฑ๋ก๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค." + confirm: "ํ™•์ธ: " + dbClear: "${0} ์— ๋Œ€ํ•œ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ์‚ญ์ œํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค." + dbOverwrite: "Plan์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฎ์–ด ์“ฐ๋ ค๊ณ ํ•ฉ๋‹ˆ๋‹ค. ${0} ๋ฐ์ดํ„ฐ ํฌํ•จ ${1}" + dbRemovePlayer: "${1}์—์„œ ${0}์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ฑฐํ•˜๋ ค๊ณ ํ•ฉ๋‹ˆ๋‹ค." + deny: "์ทจ์†Œ" + expired: "ํ™•์ธ์ด ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋ช…๋ น์„ ๋‹ค์‹œ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค." + unregister: "${1}์— ์—ฐ๊ฒฐ๋œ '${0}'์˜ ๋“ฑ๋ก์„ ์ทจ์†Œํ•˜๋ ค๊ณ ํ•ฉ๋‹ˆ๋‹ค." + database: + creatingBackup: "${1} ๋‚ด์šฉ์œผ๋กœ ๋ฐฑ์—… ํŒŒ์ผ '${0}.db'๋งŒ๋“ค๊ธฐ" + failDbNotOpen: "ยงc๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋Š” ${0} - ์ž…๋‹ˆ๋‹ค. ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•˜์‹ญ์‹œ์˜ค." + manage: + confirm: "> ยงcAdd '-a' argument to confirm execution: ${0}" + confirmOverwrite: "Data in ${0} will be overwritten!" + confirmPartialRemoval: "Join Address Data for Server ${0} in ${1} will be removed!" + confirmRemoval: "Data in ${0} will be removed!" + fail: "> ยงc๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: ${0}" + failFileNotFound: "> ยงc${0}์— ํŒŒ์ผ์ด ์—†์Šต๋‹ˆ๋‹ค." + failIncorrectDB: "> ยงc'${0}' ์ง€์›๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค." + failNoServer: "์ฃผ์–ด์ง„ ๋งค๊ฐœ ๋ณ€์ˆ˜๋ฅผ ๊ฐ€์ง„ ์„œ๋ฒ„๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค." + failSameDB: "> ยงc๋™์ผํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์ž‘์—…ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค!" + failSameServer: "์ด ์„œ๋ฒ„๋ฅผ ์ œ๊ฑฐ๋œ ๊ฒƒ์œผ๋กœ ํ‘œ์‹œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.(ํ˜„์žฌ ์‚ฌ์šฉ ์ค‘์ž…๋‹ˆ๋‹ค.)" + hotswap: "ยงe์ƒˆ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค (/plan db hotswap ${0})๋กœ ๊ต์ฒดํ•˜๊ณ  ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๋‹ค์‹œ ๋กœ๋“œํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค." + importers: "Importers: " + preparing: "Preparing.." + progress: "${0} / ${1} ์ฒ˜๋ฆฌ ์ค‘.." + start: "> ยง2๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ์ค‘.." + success: "> ยงa์„ฑ๊ณต!" + playerRemoval: "${1}์—์„œ ${0}์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ฑฐํ•˜๋Š” ์ค‘ .." + removal: "${0}์—์„œ Plan ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ฑฐํ•˜๋Š” ์ค‘ .." + serverUninstalled: "ยงa์„œ๋ฒ„๊ฐ€ ์—ฌ์ „ํžˆ ์„ค์น˜๋˜์–ด ์žˆ์œผ๋ฉด ์ž๋™์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์„ค์น˜๋œ ๊ฒƒ์œผ๋กœ ์„ค์ •๋ฉ๋‹ˆ๋‹ค." + unregister: "'${0}'๋“ฑ๋ก ์ทจ์†Œ .." + warnDbNotOpen: "ยงe${0}๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์˜ˆ์ƒ๋ณด๋‹ค ์˜ค๋ž˜ ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค .." + write: "์“ฐ๊ธฐ ${0}.." + fail: + emptyString: "๊ฒ€์ƒ‰ ๋ฌธ์ž์—ด์€ ๋น„์›Œ ๋‘˜ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค." + invalidArguments: "๋‹ค์Œ์„ ์ˆ˜๋ฝํ•ฉ๋‹ˆ๋‹ค. ${0}: ${1}" + invalidUsername: "ยงc์‚ฌ์šฉ์ž์—๊ฒŒ UUID๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค." + missingArguments: "ยงcํ•„์ˆ˜ ์ธ์ˆ˜ (${0}) ${1}" + missingFeature: "ยงe๋น„ํ™œ์„ฑํ™” ํ•  ๊ธฐ๋Šฅ์„ ์ •์˜ํ•˜์‹ญ์‹œ์˜ค! (ํ˜„์žฌ ${0})" + missingLink: "์‚ฌ์šฉ์ž๊ฐ€ ๊ท€ํ•˜์˜ ๊ณ„์ •์— ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์ง€ ์•Š์œผ๋ฉฐ ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž์˜ ๊ณ„์ •์„ ์ œ๊ฑฐ ํ•  ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค." + noPermission: "ยงcํ•„์š”ํ•œ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค." + onAccept: "์‹คํ–‰์‹œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ ์ˆ˜๋ฝ ๋œ ์ž‘์—…: ${0}" + onDeny: "์‹คํ–‰์‹œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฑฐ๋ถ€ ๋œ ์ž‘์—…: ${0}" + playerNotFound: "ํ”Œ๋ ˆ์ด์–ด '${0}' ์ฐพ์„ ์ˆ˜ ์—†์œผ๋ฉฐ UUID๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค." + playerNotInDatabase: "ํ”Œ๋ ˆ์ด์–ด '${0}' ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค." + seeConfig: "config.yml์˜ '${0}'์ฐธ์กฐ" + serverNotFound: "์„œ๋ฒ„ '${0}' ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค." + tooManyArguments: "ยงc๋‹จ์ผ ์ธ์ˆ˜๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ${1}" + unknownUsername: "ยงc์ด ์„œ๋ฒ„์— ์‚ฌ์šฉ์ž๊ฐ€ ํ‘œ์‹œ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค." + webUserExists: "ยงc์‚ฌ์šฉ์ž๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค!" + webUserNotFound: "ยงc์‚ฌ์šฉ์ž๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค!" + footer: + help: "ยง7๋ช…๋ น์–ด ๋˜๋Š” ์ธ์ˆ˜์— ๋งˆ์šฐ์Šค ์ปค์„œ๋กœ ์˜ฌ๋ฆฌ๊ฑฐ๋‚˜ '/${0} ?' ์‚ฌ์šฉํ•ด์„œ ์ž์„ธํžˆ ํ™•์ธ๋ฐ”๋ž๋‹ˆ๋‹ค." + general: + failNoExporter: "ยงeExporter '${0}' doesn't exist" + failNoImporter: "ยงeImporter '${0}' doesn't exist" + featureDisabled: "ยงa๋‹ค์Œ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๋‹ค์‹œ ๋กœ๋“œํ•  ๋•Œ๊นŒ์ง€ ์ผ์‹œ์ ์œผ๋กœ '${0}'์„ (๋ฅผ) ๋น„ํ™œ์„ฑํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค." + noAddress: "ยงe์‚ฌ์šฉ๊ฐ€๋Šฅํ•œ ์ฃผ์†Œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. - localhost ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. 'Alternative_IP' ํ•ญ๋ชฉ์„ ํ†ตํ•ด ์„ค์ •ํ•˜์‹ญ์‹œ์˜ค." + noWebuser: "์›น ์‚ฌ์šฉ์ž๊ฐ€ ์—†์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /plan register ๋ฅผ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค." + notifyWebUserRegister: "๋“ฑ๋ก๋œ ์‹ ๊ทœ ์œ ์ €: '${0}' Perm level: ${1}" + pluginDisabled: "ยงa์ด์ œ Plan ์‹œ์Šคํ…œ์ด ๋น„ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค. reload๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๋‹ค์‹œ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + reloadComplete: "ยงa๊ตฌ์„ฑํŒŒ์ผ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์™„๋ฃŒํ–ˆ์Šต๋‹ˆ๋‹ค." + reloadFailed: "ยงcํ”Œ๋Ÿฌ๊ทธ์ธ ๋ฆฌ๋กœ๋“œ ๋„์ค‘ ์—๋Ÿฌ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค." + successWebUserRegister: "ยงa์ƒˆ ์‚ฌ์šฉ์ž (${0})๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค!" + webPermissionLevels: ">\ยง70: ๋ชจ๋“  ํŽ˜์ด์ง€์— ์•ก์„ธ์Šค \ยง71: '/players'๋ฐ ๋ชจ๋“  ํ”Œ๋ ˆ์ด์–ด ํŽ˜์ด์ง€์— ์•ก์„ธ์Šค \ยง72: ์›น ์‚ฌ์šฉ์ž์™€ ๋™์ผํ•œ ์‚ฌ์šฉ์ž ์ด๋ฆ„์œผ๋กœ ํ”Œ๋ ˆ์ด์–ด ํŽ˜์ด์ง€์— ์•ก์„ธ์Šค \ยง73+: ๊ถŒํ•œ ์—†์Œ" + webUserList: " ยง2${0} ยง7: ยงf${1}" + header: + analysis: "> ยง2๋ถ„์„ ๊ฒฐ๊ณผ" + help: "> ยง2/${0} Help" + info: "> ยง2ํ”Œ๋ ˆ์ด์–ด ๋ถ„์„" + inspect: "> ยง2ํ”Œ๋ ˆ์ด์–ด: ยงf${0}" + network: "> ยง2๋„คํŠธ์›Œํฌ ํŽ˜์ด์ง€" + players: "> ยง2ํ”Œ๋ ˆ์ด์–ด ์ˆ˜" + search: "> ยง2${0} Results for ยงf${1}ยง2:" + serverList: "id::name::uuid::version" + servers: "> ยง2์„œ๋ฒ„ ๋ชฉ๋ก" + webUserList: "username::linked to::permission level" + webUsers: "> ยง2${0} ์›น ์‚ฌ์šฉ์ž" + help: + database: + description: "Plan ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ด€๋ฆฌ์ž" + inDepth: "Use different database subcommands to change the data in some way" + dbBackup: + description: "๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํŒŒ์ผ๋กœ ๋ฐฑ์—…ํ•ฉ๋‹ˆ๋‹ค." + inDepth: "Uses SQLite to backup the target database to a file." + dbClear: + description: "๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์žˆ๋Š” ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค." + inDepth: "Clears all Plan tables, removing all Plan-data in the process." + dbHotswap: + description: "๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๋น ๋ฅด๊ฒŒ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค." + inDepth: "Reloads the plugin with the other database and changes the config to match." + dbMove: + description: "๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ฐ„์— ๋ฐ์ดํ„ฐ ์ด๋™ ํ•ฉ๋‹ˆ๋‹ค." + inDepth: "Overwrites contents in the other database with the contents in another." + dbRemove: + description: "ํ˜„์žฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ํ”Œ๋ ˆ์ด์–ด ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค." + inDepth: "Removes all data linked to a player from the Current database." + dbRestore: + description: "ํŒŒ์ผ์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณต์›ํ•ฉ๋‹ˆ๋‹ค." + inDepth: "Uses SQLite backup file and overwrites contents of the target database." + dbUninstalled: + description: "์„œ๋ฒ„์—์„œ ์„ค์น˜ํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค." + inDepth: "Marks a server in Plan database as uninstalled so that it will not show up in server queries." + disable: + description: "ํ”Œ๋Ÿฌ๊ทธ์ธ ๋น„ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค." + inDepth: "Disable the plugin or part of it until next reload/restart." + export: + description: "HTML ๋˜๋Š” JSON ํ˜•์‹ ํŒŒ์ผ๋กœ ๋‚ด๋ณด๋ƒ…๋‹ˆ๋‹ค." + inDepth: "Performs an export to export location defined in the config." + import: + description: "์ €์žฅ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ต๋‹ˆ๋‹ค." + inDepth: "Performs an import to load data into the database." + info: + description: "Plan ํ”Œ๋Ÿฌ๊ทธ์ธ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค." + inDepth: "Display the current status of the plugin." + ingame: + description: "๊ฒŒ์ž„์—์„œ ํ”Œ๋ ˆ์ด์–ด ์ •๋ณด๋ฅผ ์—ด๋žŒํ•ฉ๋‹ˆ๋‹ค." + inDepth: "Displays some information about the player in game." + json: + description: "ํ”Œ๋ ˆ์ด์–ด ๋กœ์šฐ ๋ฐ์ดํ„ฐ์˜ json๋ฅผ ์—ด๋žŒํ•ฉ๋‹ˆ๋‹ค." + inDepth: "Allows you to download a player's data in json format. All of it." + logout: + description: "Log out other users from the panel." + inDepth: "Give username argument to log out another user from the panel, give * as argument to log out everyone." + migrateToOnlineUuids: + description: "Migrate offline uuid data to online uuids" + network: + description: "๋„คํŠธ์›Œํฌ ์ •๋ณด๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค." + inDepth: "Obtain a link to the /network page, only does so on networks." + player: + description: "ํ”Œ๋ ˆ์ด์–ด ์ •๋ณด ํ™•์ธํ•ฉ๋‹ˆ๋‹ค." + inDepth: "Obtain a link to the /player page of a specific player, or the current player." + players: + description: "ํ”Œ๋ ˆ์ด์–ด ๋ชฉ๋ก ํ™•์ธํ•ฉ๋‹ˆ๋‹ค." + inDepth: "Obtain a link to the /players page to see a list of players." + register: + description: "Plan ์›น ์‚ฌ์ดํŠธ์— ์‚ฌ์šฉ์ž ๋“ฑ๋ก" + inDepth: "Use without arguments to get link to register page. Use --code [code] after registration to get a user." + reload: + description: "Plan ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๋ฆฌ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค." + inDepth: "Disable and enable the plugin to reload any changes in config." + removejoinaddresses: + description: "Remove join addresses of a specified server" + search: + description: "ํ”Œ๋ ˆ์ด์–ด๋ช…์„ ๊ฒ€์ƒ‰ํ•ฉ๋‹ˆ๋‹ค." + inDepth: "List all matching player names to given part of a name." + server: + description: "์„œ๋ฒ„ ์ •๋ณด ์—ด๋žŒํ•˜๊ธฐ" + inDepth: "Obtain a link to the /server page of a specific server, or the current server if no arguments are given." + servers: + description: "์„œ๋ฒ„ ๋ชฉ๋ก ์—ด๋žŒํ•˜๊ธฐ" + inDepth: "List ids, names and uuids of servers in the database." + unregister: + description: "Plan ํŽ˜์ด์ง€ ๊ณ„์ • ํƒˆํ‡ดํ•ฉ๋‹ˆ๋‹ค." + inDepth: "Use without arguments to unregister player linked user, or with username argument to unregister another user." + users: + description: "๋ชจ๋“  ์›น ์‚ฌ์šฉ์ž ๋‚˜์—ดํ•ฉ๋‹ˆ๋‹ค." + inDepth: "์›น ์‚ฌ์šฉ์ž๋ฅผ ํ‘œ๋กœ ๋‚˜์—ดํ•ฉ๋‹ˆ๋‹ค." + ingame: + activePlaytime: " ยง2ํ”Œ๋ ˆ์ดํ•œ ์‹œ๊ฐ„: ยงf${0}" + activityIndex: " ยง2ํ™œ๋™ ์ •๋ณด: ยงf${0} | ${1}" + afkPlaytime: " ยง2AFK ์‹œ๊ฐ„: ยงf${0}" + deaths: " ยง2์ฃฝ์€ ํšŸ์ˆ˜: ยงf${0}" + geolocation: " ยง2ยงf${0}์—์„œ ๋กœ๊ทธ์ธ" + lastSeen: " ยง2๋งˆ์ง€๋ง‰์œผ๋กœ ๋ณธ : ยงf${0}" + longestSession: " ยง2๊ฐ€์žฅ ๊ธด ์„ธ์…˜: ยงf${0}" + mobKills: " ยง2๋ชฌ์Šคํ„ฐ ํ‚ฌ์ˆ˜: ยงf${0}" + playerKills: " ยง2ํ”Œ๋ ˆ์ด์–ด ํ‚ฌ์ˆ˜: ยงf${0}" + playtime: " ยง2ํ”Œ๋ ˆ์ด ์‹œ๊ฐ„: ยงf${0}" + registered: " ยง2์ตœ์ดˆ ์ ‘์†: ยงf${0}" + timesKicked: " ยง2์ถ”๋ฐฉ ํšŸ์ˆ˜: ยงf${0}" + link: + clickMe: "ํด๋ฆญ" + link: "๋งํฌ" + network: "๋„คํŠธ์›Œํฌ ํŽ˜์ด์ง€: " + noNetwork: "์„œ๋ฒ„๊ฐ€ ๋„คํŠธ์›Œํฌ์— ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋งํฌ๋Š” ์„œ๋ฒ„ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋””๋ ‰์…˜๋ฉ๋‹ˆ๋‹ค." + player: "ํ”Œ๋ ˆ์ด์–ด ํŽ˜์ด์ง€: " + playerJson: "ํ”Œ๋ ˆ์ด์–ด json: " + players: "ํ”Œ๋ ˆ์ด์–ด ๋ชฉ๋ก ํŽ˜์ด์ง€: " + register: "๊ฐ€์ž… ํŽ˜์ด์ง€: " + server: "์„œ๋ฒ„ ํŽ˜์ด์ง€: " + subcommand: + info: + database: " ยง2ํ˜„์žฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค: ยงf${0}" + proxy: " ยง2ํ”„๋ก์‹œ์— ์—ฐ๊ฒฐ๋จ: ยงf${0}" + update: " ยง2์ตœ์‹  ๋ฒ„์ „: ยงf${0}" + version: " ยง2๋ฒ„์ „: ยงf${0}" +generic: + noData: "๋ฐ์ดํ„ฐ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค." +html: + button: + nightMode: "์•ผ๊ฐ„ ๋ชจ๋“œ" + calendar: + new: "New:" + unique: "Unique:" + description: + newPlayerRetention: "This value is a prediction based on previous players." + noGameServers: "Some data requires Plan to be installed on game servers." + noGeolocations: "Geolocation gathering needs to be enabled in the config (Accept GeoLite2 EULA)." + noServerOnlinActivity: "์˜จ๋ผ์ธ ํ™œ๋™์„ ํ‘œ์‹œ ํ•  ์„œ๋ฒ„๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค." + noServers: "๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์„œ๋ฒ„๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค." + noServersLong: 'It appears that Plan is not installed on any game servers or not connected to the same database. See wiki for Network tutorial.' + noSpongeChunks: "Chunks unavailable on Sponge" + predictedNewPlayerRetention: "์ด ๊ฐ’์€ ๊ธฐ์กด ํ”Œ๋ ˆ์ด์–ด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•œ ์˜ˆ์ธก์ž…๋‹ˆ๋‹ค." + error: + 401Unauthorized: "Unauthorized 401" + 403Forbidden: "Forbidden 403" + 404NotFound: "Not Found 404" + 404PageNotFound: "Page does not exist. 404" + 404UnknownPage: "Make sure you're accessing a link given by a command, Examples:

/player/{uuid/name}
/server/{uuid/name/id}

" + UUIDNotFound: "Player UUID was not found in the database. 404" + auth: + dbClosed: "๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์—ด๋ ค ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. /plan info ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค." + emptyForm: "์‚ฌ์šฉ์ž ๋ฐ ์•”ํ˜ธ๊ฐ€ ์ง€์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค." + expiredCookie: "์‚ฌ์šฉ์ž ์ฟ ํ‚ค๊ฐ€ ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + generic: "์˜ค๋ฅ˜๋กœ ์ธํ•ด ์ธ์ฆ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค." + loginFailed: "์‚ฌ์šฉ์ž์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค" + noCookie: "์‚ฌ์šฉ์ž ์ฟ ํ‚ค๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค." + registrationFailed: "๋“ฑ๋ก ์‹คํŒจ, ๋‹ค์‹œ ์‹œ๋„ (์ฝ”๋“œ๋Š” 15๋ถ„ ํ›„์— ๋งŒ๋ฃŒ ๋จ)" + userNotFound: "์‚ฌ์šฉ์ž๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค" + authFailed: "Authentication Failed. 401" + authFailedTips: "- Ensure you have registered a user with /plan register
- Check that the username and password are correct
- Username and password are case-sensitive

If you have forgotten your password, ask a staff member to delete your old user and re-register." + noServersOnline: "No Servers online to perform the request. 404" + playerNotSeen: "Plan has not seen this player. 404" + serverNotSeen: "Server doesn't exist" + generic: + none: "None" + label: + active: "ํ™œ๋™์ ์ธ" + activePlaytime: "Active Playtime" + activityIndex: "ํ™œ๋™ ์ƒ‰์ธ" + afk: "AFK" + afkTime: "AFK ์‹œ๊ฐ„" + all: "๋ชจ๋‘" + allTime: "๋ชจ๋“  ์‹œ๊ฐ„" + alphabetical: "Alphabetical" + apply: "Apply" + asNumbers: "์ˆซ์ž๋กœ" + average: "ํ‰๊ท " + averageActivePlaytime: "Average Active Playtime" + averageAfkTime: "Average AFK Time" + averageChunks: "Average Chunks" + averageCpuUsage: "Average CPU Usage" + averageEntities: "Average Entities" + averageKdr: "ํ‰๊ท  ์‚ฌ๋ง ํšŸ์ˆ˜(KDR)" + averageMobKdr: "ํ‰๊ท  ๋ชฌ์Šคํ„ฐ ์‚ฌ๋ง ํšŸ์ˆ˜(Mob KDR)" + averagePing: "ํ‰๊ท  Ping" + averagePlayers: "Average Players" + averagePlaytime: "ํ‰๊ท  ํ”Œ๋ ˆ์ด ํƒ€์ž„" + averageRamUsage: "Average RAM Usage" + averageServerDowntime: "Average Downtime / Server" + averageSessionLength: "ํ‰๊ท  ์ ‘์† ์‹œ๊ฐ„(์„ธ์…˜ ๊ธธ์ด)" + averageSessions: "Average Sessions" + averageTps: "ํ‰๊ท  TPS" + averageTps7days: "Average TPS (7 days)" + banned: "Banned" + bestPeak: "์ตœ๊ณ ์˜ ํ”ผํฌ" + bestPing: "์ตœ๊ณ  Ping" + calendar: " ๋‹ฌ๋ ฅ" + comparing7days: "์ง€๋‚œ 7์ผ ๋น„๊ต" + connectionInfo: "์—ฐ๊ฒฐ ์ •๋ณด" + country: "๊ตญ๊ฐ€" + cpu: "CPU" + cpuRam: "CPU & RAM" + cpuUsage: "CPU Usage" + currentPlayerbase: "ํ˜„์žฌ ํ”Œ๋ ˆ์ด์–ด ๋ฒ ์ด์Šค" + currentUptime: "Current Uptime" + dayByDay: "Day by Day" + dayOfweek: "์š”์ผ" + deadliestWeapon: "์น˜๋ช…์ ์ธ PvP ๋ฌด๊ธฐ" + deaths: "์ฃฝ์€ ํšŸ์ˆ˜" + disk: "๋””์Šคํฌ ๊ณต๊ฐ„" + diskSpace: "์—ฌ์œ  ๋””์Šคํฌ ๊ณต๊ฐ„" + downtime: "๋‹ค์šดํƒ€์ž„" + duringLowTps: "๋‚ฎ์€ TPS ์ŠคํŒŒ์ดํฌ ๋™์•ˆ:" + entities: "์—”ํ‹ฐํ‹ฐ" + favoriteServer: "์ฆ๊ฒจ์ฐพ๋Š” ์„œ๋ฒ„" + firstSession: "์ฒซ ๋ฒˆ์งธ ์„ธ์…˜" + firstSessionLength: + average: "Average first session length" + median: "Median first session length" + geoProjection: + dropdown: "Select projection" + equalEarth: "Equal Earth" + mercator: "Mercator" + miller: "Miller" + ortographic: "Ortographic" + geolocations: "์ง€๋ฆฌ์  ์œ„์น˜" + hourByHour: "Hour by Hour" + inactive: "๋น„ํ™œ์„ฑ" + indexInactive: "๋น„ํ™œ์„ฑ" + indexRegular: "๊ทœ์น™์ ์ธ" + information: "์ •๋ณด" + insights: "Insights" + insights30days: "30์ผ ๋™์•ˆ์˜ ์ธ์‚ฌ์ดํŠธ" + irregular: "๋ถˆ๊ทœ์น™ํ•œ" + joinAddress: "Join Address" + joinAddresses: "Join Addresses" + kdr: "KDR" + killed: "Killed" + last24hours: "์ง€๋‚œ 24์‹œ๊ฐ„" + last30days: "์ง€๋‚œ 30์ผ" + last7days: "์ง€๋‚œ 7์ผ" + lastConnected: "๋งˆ์ง€๋ง‰ ์—ฐ๊ฒฐ" + lastPeak: "๋งˆ์ง€๋ง‰ ํ”ผํฌ" + lastSeen: "๋งˆ์ง€๋ง‰์œผ๋กœ ๋ณธ" + latestJoinAddresses: "Latest Join Addresses" + length: " ๊ธธ์ด" + links: "LINKS" + loadedChunks: "๋กœ๋“œ๋œ ์ฒญํฌ" + loadedEntities: "๋กœ๋“œ๋œ ์—”ํ‹ฐํ‹ฐ" + loneJoins: "์ตœ๊ทผ ์ ‘์†(Lone Joins)" + loneNewbieJoins: "์ตœ๊ทผ ์‹ ๊ทœ ์ ‘์†(Lone New Joins)" + longestSession: "๊ฐ€์žฅ ๊ธด ์ ‘์† ์‹œ๊ฐ„" + lowTpsSpikes: "๋‚ฎ์€ TPS ์ŠคํŒŒ์ดํฌ" + lowTpsSpikes7days: "Low TPS Spikes (7 days)" + maxFreeDisk: "์ตœ๋Œ€ ์—ฌ์œ  ๋””์Šคํฌ์šฉ๋Ÿ‰" + medianSessionLength: "Median Session Length" + minFreeDisk: "์ตœ์†Œ ์—ฌ์œ  ๋””์Šคํฌ์šฉ๋Ÿ‰" + mobDeaths: "๋ชฌ์Šคํ„ฐํ•œํ…Œ ์ฃฝ์€ ํšŸ์ˆ˜" + mobKdr: "๋ชฌ์Šคํ„ฐ KDR" + mobKills: "๋ชฌ์Šคํ„ฐ ํ‚ฌ์ˆ˜" + mostActiveGamemode: "๊ฐ€์žฅ ํ™œ๋™์ ์ธ ๊ฒŒ์ž„๋ชจ๋“œ" + mostPlayedWorld: "๊ฐ€์žฅ ๋งŽ์ด ํ”Œ๋ ˆ์ด ํ•œ ๋งต" + name: "์ด๋ฆ„" + network: "๋„คํŠธ์›Œํฌ" + networkAsNumbers: "๋„คํŠธ์›Œํฌ ์ˆซ์ž" + networkOnlineActivity: "๋„คํŠธ์›Œํฌ ์˜จ๋ผ์ธ ํ™œ๋™" + networkOverview: "๋„คํŠธ์›Œํฌ ๊ฐœ์š”" + networkPage: "๋„คํŠธ์›Œํฌ ํŽ˜์ด์ง€" + new: "์‹ ๊ทœ" + newPlayerRetention: "์‹ ๊ทœ ํ”Œ๋ ˆ์ด์–ด ์œ ์ง€" + newPlayers: "์‹ ๊ทœ ํ”Œ๋ ˆ์ด์–ด์ˆ˜" + newPlayers7days: "New Players (7 days)" + nickname: "๋‹‰๋„ค์ž„" + noDataToDisplay: "No Data to Display" + now: "ํ˜„์žฌ" + onlineActivity: "์˜จ๋ผ์ธ ํ™œ๋™" + onlineActivityAsNumbers: "์˜จ๋ผ์ธ ํ™œ๋™ ์ˆซ์ž" + onlineOnFirstJoin: "์ฒ˜์Œ ์ฐธ๊ฐ€ํ•  ๋•Œ ์˜จ๋ผ์ธ ํ”Œ๋ ˆ์ด์–ด" + operator: "๊ด€๋ฆฌ์ž(OP)" + overview: "๊ฐœ์š”" + perDay: "/ Day" + perPlayer: "/ ํ”Œ๋ ˆ์ด์–ด" + perRegularPlayer: "/ ์ผ๋ฐ˜ ํ”Œ๋ ˆ์ด์–ด" + performance: "์„ฑ๋Šฅ" + performanceAsNumbers: "์„ฑ๋Šฅ ํ†ต๊ณ„" + ping: "Ping" + player: "ํ”Œ๋ ˆ์ด์–ด" + playerDeaths: "ํ”Œ๋ ˆ์ด์–ด์— ์˜ํ•œ ์ฃฝ์€ ํšŸ์ˆ˜" + playerKills: "ํ”Œ๋ ˆ์ด์–ด ํ‚ฌ์ˆ˜" + playerList: "ํ”Œ๋ ˆ์ด์–ด ๋ชฉ๋ก" + playerOverview: "ํ”Œ๋ ˆ์ด์–ด ๊ฐœ์š”" + playerPage: "ํ”Œ๋ ˆ์ด์–ด ํŽ˜์ด์ง€" + playerRetention: "Player Retention" + playerbase: "ํ”Œ๋ ˆ์ด์–ด ๊ธฐ๋ฐ˜" + playerbaseDevelopment: "Playerbase ๊ฐœ๋ฐœ" + playerbaseOverview: "Playerbase Overview" + players: "ํ”Œ๋ ˆ์ด์–ด๋“ค" + playersOnline: "์ ‘์†์ค‘์ธ ํ”Œ๋ ˆ์ด์–ด" + playersOnlineNow: "Players Online (Now)" + playersOnlineOverview: "์˜จ๋ผ์ธ ํ™œ๋™ ๊ฐœ์š”" + playtime: "ํ”Œ๋ ˆ์ดํƒ€์ž„" + plugins: "ํ”Œ๋Ÿฌ๊ทธ์ธ" + pluginsOverview: "Plugins Overview" + punchcard: "ํŽ€์น˜ ์นด๋“œ" + punchcard30days: "30์ผ ๋™์•ˆ์˜ ํŽ€์น˜ ์นด๋“œ" + pvpPve: "PvP & PvE" + pvpPveAsNumbers: "PvP & PvE ํ†ต๊ณ„" + query: "Make a query" + quickView: "ํ€ต ๋ทฐ" + ram: "RAM" + ramUsage: "RAM Usage" + recentKills: "์ตœ๊ทผ ํ‚ฌ" + recentPvpDeaths: "์ตœ๊ทผ PvP ์‚ฌ๋ง" + recentPvpKills: "์ตœ๊ทผ PvP ์ฒ˜์น˜" + recentSessions: "์ตœ๊ทผ ์„ธ์…˜" + registered: "์‹ ๊ทœ" + registeredPlayers: "์‹ ๊ทœ ํ”Œ๋ ˆ์ด์–ด" + regular: "์‹ ๊ทœ" + regularPlayers: "์‹ ๊ทœ ํ”Œ๋ ˆ์ด์–ด" + relativeJoinActivity: "์ƒ๋Œ€ ์กฐ์ธ ํ™œ๋™" + secondDeadliestWeapon: "2nd PvP ๋ฌด๊ธฐ" + seenNicknames: "๋ณธ ๋ณ„๋ช…" + server: "์„œ๋ฒ„" + serverAnalysis: "์„œ๋ฒ„ ๋ถ„์„" + serverAsNumberse: "์„œ๋ฒ„ ๋ฒˆํ˜ธ" + serverCalendar: "Server Calendar" + serverDowntime: "์„œ๋ฒ„ ๋‹ค์šดํƒ€์ž„" + serverOccupied: "์ ์œ ๋œ ์„œ๋ฒ„" + serverOverview: "์„œ๋ฒ„ ๊ฐœ์š”" + serverPage: "์„œ๋ฒ„ ํŽ˜์ด์ง€" + serverPlaytime: "์„œ๋ฒ„ ํ”Œ๋ ˆ์ด ํƒ€์ž„" + serverPlaytime30days: "์„œ๋ฒ„ ํ”Œ๋ ˆ์ด ํƒ€์ž„ - ์ตœ๊ทผ 30์ผ" + serverSelector: "Server selector" + servers: "์„œ๋ฒ„ ๋ชฉ๋ก" + serversTitle: "์„œ๋ฒ„ ๋ชฉ๋ก" + session: "์„ธ์…˜" + sessionCalendar: "Session Calendar" + sessionEnded: " ์ข…๋ฃŒ" + sessionMedian: "์„ธ์…˜ ์ค‘์•™๊ฐ’" + sessionStart: "์„ธ์…˜ ์‹œ์ž‘" + sessions: "์„ธ์…˜ ๋ชฉ๋ก" + sortBy: "Sort By" + stacked: "Stacked" + themeSelect: "ํ…Œ๋งˆ ์„ ํƒ" + thirdDeadliestWeapon: "3rd PvP ๋ฌด๊ธฐ" + thirtyDays: "30์ผ" + thirtyDaysAgo: "30์ผ ์ „" + timesKicked: "์ ‘์†์ข…๋ฃŒํ•œ ์‹œ๊ฐ„" + toMainPage: "๋ฉ”์ธ ํŽ˜์ด์ง€๋กœ" + total: "Total" + totalActive: "์ด ํ™œ์„ฑํ™” ์‹œ๊ฐ„" + totalAfk: "์ด AFK ์‹œ๊ฐ„" + totalPlayers: "์ด ํ”Œ๋ ˆ์ด์–ด์ˆ˜" + totalPlayersOld: "์ด ํ”Œ๋ ˆ์ด์–ด" + totalPlaytime: "์ด ํ”Œ๋ ˆ์ดํƒ€์ž„" + totalServerDowntime: "Total Server Downtime" + tps: "TPS" + trend: "ํŠธ๋ Œ๋“œ" + trends30days: "30์ผ ๋™์•ˆ์˜ ํŠธ๋ Œ๋“œ" + uniquePlayers: "๊ธฐ์กด ํ”Œ๋ ˆ์ด์–ด" + uniquePlayers7days: "Unique Players (7 days)" + veryActive: "๋งค์šฐ ํ™œ์„ฑํ™”๋œ" + weekComparison: "์ฃผ ๋น„๊ต" + weekdays: "'์›”์š”์ผ', 'ํ™”์š”์ผ', '์ˆ˜์š”์ผ', '๋ชฉ์š”์ผ', '๊ธˆ์š”์ผ', 'ํ† ์š”์ผ', '์ผ์š”์ผ'" + world: "์›”๋“œ ๋กœ๋“œ" + worldPlaytime: "๋งต ํ”Œ๋ ˆ์ด ํƒ€์ž„" + worstPing: "Worst Ping" + login: + failed: "Login failed: " + forgotPassword: "Forgot Password?" + forgotPassword1: "Forgot password? Unregister and register again." + forgotPassword2: "Use the following command in game to remove your current user:" + forgotPassword3: "Or using console:" + forgotPassword4: "After using the command, " + login: "Login" + logout: "Logout" + password: "Password" + register: "Create an Account!" + username: "Username" + modal: + info: + bugs: "๋ฌธ์ œ ๋ณด๊ณ " + contributors: + bugreporters: "& Bug reporters!" + code: "์ฝ”๋“œ ๊ธฐ์—ฌ์ž" + donate: "๊ธˆ์ „์ ์œผ๋กœ ๊ฐœ๋ฐœ์„ ์ง€์› ํ•ด์ฃผ์‹  ๋ถ„๋“ค๊ป˜ ํŠน๋ณ„ํžˆ ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค." + text: '๋˜ํ•œ ๋‹ค์Œ ๋ฉ‹์ง„ ์‚ฌ๋žŒ๋“ค์ด ๊ธฐ์—ฌํ–ˆ์Šต๋‹ˆ๋‹ค.' + translator: "๋ฒˆ์—ญ" + developer: "์— ์˜ํ•ด ๊ฐœ๋ฐœ๋˜์—ˆ์Šต๋‹ˆ๋‹ค" + discord: "๋””์Šค์ฝ”๋“œ๋กœ ๊ธฐ์ˆ ์ง€์›" + license: "ํ”Œ๋ ˆ์ด์–ด ๋ถ„์„ ๊ฐœ๋ฐœ ๋ฐ ๋ผ์ด์„ผ์Šคํ•˜์— ์žˆ์Šต๋‹ˆ๋‹ค" + metrics: "bStats ๋ฉ”ํŠธ๋ฆญ" + text: "ํ”Œ๋Ÿฌ๊ทธ์ธ์— ๋Œ€ํ•œ ์ •๋ณด" + wiki: "Plan ์œ„ํ‚ค, ํŠœํ† ๋ฆฌ์–ผ & ๋ฌธ์„œ" + version: + available: "์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค" + changelog: "๋ณ€๊ฒฝ ๋กœ๊ทธ ๋ณด๊ธฐ" + dev: "์ด ๋ฒ„์ „์€ DEV ๋ฆด๋ฆฌ์Šค์ž…๋‹ˆ๋‹ค." + download: "๋‹ค์šด๋กœ๋“œ" + text: "์ƒˆ ๋ฒ„์ „์ด ์ถœ์‹œ๋˜์—ˆ์œผ๋ฉฐ ์ด์ œ ๋‹ค์šด๋กœ๋“œ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + title: "๋ฒ„์ „" + query: + filter: + activity: + text: "are in Activity Groups" + banStatus: + name: "Ban status" + banned: "Banned" + country: + text: "have joined from country" + generic: + allPlayers: "All players" + and: "and " + start: "of Players who " + joinAddress: + text: "joined with address" + nonOperators: "Non operators" + notBanned: "Not banned" + operatorStatus: + name: "Operator status" + operators: "Operators" + playedBetween: + text: "Played between" + pluginGroup: + name: "Group: " + text: "are in ${plugin}'s ${group} Groups" + registeredBetween: + text: "Registered between" + title: + activityGroup: "Current activity group" + view: " View:" + filters: + add: "Add a filter.." + loading: "Loading filters.." + generic: + are: "`are`" + label: + from: ">from" + makeAnother: "Make another query" + to: ">to" + view: "Show a view" + performQuery: "Perform Query!" + results: + match: "matched ${resultCount} players" + none: "Query produced 0 results" + title: "Query Results" + title: + activity: "Activity of matched players" + activityOnDate: 'Activity on ' + sessionsWithinView: "Sessions within view" + text: "Query<" + register: + completion: "Complete Registration" + completion1: "You can now finish registering the user." + completion2: "Code expires in 15 minutes" + completion3: "Use the following command in game to finish registration:" + completion4: "Or using console:" + createNewUser: "Create a new user" + error: + checkFailed: "Checking registration status failed: " + failed: "Registration failed: " + noPassword: "You need to specify a Password" + noUsername: "You need to specify a Username" + usernameLength: "Username can be up to 50 characters, yours is " + login: "Already have an account? Login!" + passwordTip: "Password should be more than 8 characters, but there are no limitations." + register: "Register" + usernameTip: "Username can be up to 50 characters." + text: + clickToExpand: "ํ™•์žฅํ•˜๋ ค๋ฉด ํด๋ฆญ" + comparing15days: "์ง€๋‚œ 15์ผ ๋น„๊ต" + comparing30daysAgo: "์ง€๋‚œ 30์ผ ๋น„๊ต" + noExtensionData: "ํ™•์žฅ ๋ฐ์ดํ„ฐ ์—†์Œ" + noLowTps: "๋‚ฎ์€ tps ์ŠคํŒŒ์ดํฌ ์—†์Œ" + unit: + chunks: "์ฒญํฌ ์ˆ˜" + players: "ํ”Œ๋ ˆ์ด์–ด" + value: + localMachine: "๋กœ์ปฌ ๋จธ์‹ " + noKills: "์‚ด์ธ ์—†์Œ" + offline: " ์˜คํ”„๋ผ์ธ" + online: " ์˜จ๋ผ์ธ" + with: "With" + version: + changelog: "View Changelog" + current: "You have version ${0}" + download: "Download Plan-${0}.jar" + isDev: "This version is a DEV release." + updateButton: "Update" + updateModal: + text: "A new version has been released and is now available for download." + title: "Version ${0} is Available!" +plugin: + apiCSSAdded: "PageExtension: ${0} added stylesheet(s) to ${1}, ${2}" + apiJSAdded: "PageExtension: ${0} added javascript(s) to ${1}, ${2}" + disable: + database: "์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์€ ์ค‘์š”ํ•œ ์ž‘์—…์ด ์žˆ์Šต๋‹ˆ๋‹ค.. (${0})" + disabled: "ํ”Œ๋ ˆ์ด์–ด ๋ถ„์„์ด ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + processingComplete: "์ฒ˜๋ฆฌ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + savingSessions: "์™„๋ฃŒ๋˜์ง€ ์•Š์€ ์„ธ์…˜ ์ €์žฅ .." + savingSessionsTimeout: "Timeout hit, storing the unfinished sessions on next enable instead." + waitingDb: "Waiting queries to finish to avoid SQLite crashing JVM.." + waitingDbComplete: "Closed SQLite connection." + waitingTransactions: "Waiting for unfinished transactions to avoid data loss.." + waitingTransactionsComplete: "Transaction queue closed." + webserver: "์›น ์„œ๋ฒ„๊ฐ€ ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + enable: + database: "${0}-๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์ด ์„ค์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + enabled: "ํ”Œ๋ ˆ์ด์–ด ๋ถ„์„์ด ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + fail: + database: "${0}-๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์‹คํŒจ: ${1}" + databasePatch: "๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํŒจ์น˜์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๋น„ํ™œ์„ฑํ™”ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ฌธ์ œ๋ฅผ ์‹ ๊ณ ํ•˜์‹ญ์‹œ์˜ค" + databaseType: "${0} ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋Š” ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค." + geoDBWrite: "๋‹ค์šด๋กœ๋“œ ํ•œ GeoLite2 Geolocation ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์ €์žฅํ•˜๋Š” ๋„์ค‘์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค." + webServer: "์›น ์„œ๋ฒ„๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค!" + notify: + badIP: "0.0.0.0์€ ์œ ํšจํ•œ ์ฃผ์†Œ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. Alternative_IP ์„ค์ •์„ ์ง€์ •ํ•˜์‹ญ์‹œ์˜ค. ์ž˜๋ชป๋œ ๋งํฌ๋กœ ์ œ๊ณต ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!" + emptyIP: "server.properties์˜ IP๊ฐ€ ๋น„์–ด ์žˆ๊ณ  ๋Œ€์ฒด IP๊ฐ€ ์‚ฌ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ž˜๋ชป๋œ ๋งํฌ๊ฐ€ ์ œ๊ณต ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!" + geoDisabled: "์œ„์น˜ ์ •๋ณด ์ˆ˜์ง‘์ด ํ™œ์„ฑํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. (Data.Geolocations : false)" + geoInternetRequired: "Plan GeoLite2 Geolocation ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๋‹ค์šด๋กœ๋“œํ•˜๋ ค๋ฉด ์ฒ˜์Œ ์‹คํ–‰ํ•  ๋•Œ ์ธํ„ฐ๋„ท ์•ก์„ธ์Šค๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค." + storeSessions: "Storing sessions that were preserved before previous shutdown." + webserverDisabled: "์›น ์„œ๋ฒ„๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. (WebServer.DisableWebServer : true)" + webserver: "ํ•ด๋‹น ํฌํŠธ์—์„œ ์‹คํ–‰๋˜๋Š” ์›น ์„œ๋ฒ„ ${0} ( ${1} )" + generic: + dbApplyingPatch: "ํŒจ์น˜ ์ ์šฉ ์ค‘: ${0}.." + dbFaultyLaunchOptions: "๊ธฐ๋ณธ๊ฐ’์„ ์‚ฌ์šฉํ•˜์—ฌ ์‹œ์ž‘ ์˜ต์…˜์— ๊ฒฐํ•จ์ด ์žˆ์Šต๋‹ˆ๋‹ค. (${0})" + dbNotifyClean: "${0} ํ”Œ๋ ˆ์ด์–ด์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ญ์ œํ–ˆ์Šต๋‹ˆ๋‹ค" + dbNotifySQLiteWAL: "์ด ์„œ๋ฒ„ ๋ฒ„์ „์—์„œ๋Š” SQLite WAL ๋ชจ๋“œ๊ฐ€ ์ง€์›๋˜์ง€ ์•Š์œผ๋ฉฐ ๊ธฐ๋ณธ๊ฐ’์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์„ฑ๋Šฅ์— ์˜ํ–ฅ์„ ์ค„ ์ˆ˜๋„ ์žˆ๊ณ  ๊ทธ๋ ‡์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค." + dbPatchesAlreadyApplied: "๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํŒจ์น˜๊ฐ€ ์ด๋ฏธ ์ ์šฉ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + dbPatchesApplied: "๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํŒจ์น˜๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ ์šฉ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + dbSchemaPatch: "Database: Making sure schema is up to date.." + loadedServerInfo: "Server identifier loaded: ${0}" + loadingServerInfo: "Loading server identifying information" + no: "์•„๋‹ˆ์˜ค" + today: "'์˜ค๋Š˜'" + unavailable: "Unavailable" + unknown: "์•Œ ์ˆ˜ ์—†์Œ" + yes: "์˜ˆ" + yesterday: "'์–ด์ œ'" + version: + checkFail: "์ตœ์‹  ๋ฒ„์ „์„ ํ™•์ธํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค." + checkFailGithub: "Github/versions.txt ์—์„œ ๋ฒ„์ „ ์ •๋ณด๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค." + isDev: " ์ด๊ฒƒ์€ DEV ๋ฆด๋ฆฌ์Šค์ž…๋‹ˆ๋‹ค." + isLatest: "์ตœ์‹  ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค." + updateAvailable: "์ตœ์‹  ๋ฒ„์ „ (${0}) ์ด๋ฉฐ, ํ˜„์žฌ ๋ฒ„์ „์€ ${1} ์ž…๋‹ˆ๋‹ค." + updateAvailableSpigot: "์ตœ์‹  ๋ฒ„์ „์€ ${0}" + webserver: + fail: + SSLContext: "์›น์„œ๋ฒ„: SSL ์ปจํ…์ŠคํŠธ ์ดˆ๊ธฐํ™”์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค." + certFileEOF: "์›น์„œ๋ฒ„: ์ธ์ฆ์„œ ํŒŒ์ผ์„ ์ฝ์„ ๋•Œ EOF. (ํŒŒ์ผ์ด ๋น„์–ด ์žˆ์ง€ ์•Š์€์ง€ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค)" + certStoreLoad: "์›น์„œ๋ฒ„: SSL ์ธ์ฆ์„œ ๋กœ๋“œ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค." + portInUse: "์›น์„œ๋ฒ„: ์„ฑ๊ณต์ ์œผ๋กœ ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ํ•ด๋‹น ํฌํŠธ (${0})๊ฐ€ ์‚ฌ์šฉ ์ค‘์ž…๋‹ˆ๊นŒ?" + notify: + authDisabledConfig: "์›น์„œ๋ฒ„: ์‚ฌ์šฉ์ž ์ธ์ฆ์ด ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค! (๊ตฌ์„ฑ์—์„œ ๋น„ํ™œ์„ฑํ™” ๋จ)" + authDisabledNoHTTPS: "์›น์„œ๋ฒ„: ์‚ฌ์šฉ์ž ์ธ์ฆ์ด ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค! (HTTP๋ฅผ ํ†ตํ•ด ์•ˆ์ „ํ•˜์ง€ ์•Š์Œ)" + certificateExpiresOn: "Webserver: Loaded certificate is valid until ${0}." + certificateExpiresPassed: "Webserver: Certificate has expired, consider renewing the certificate." + certificateExpiresSoon: "Webserver: Certificate expires in ${0}, consider renewing the certificate." + certificateNoSuchAlias: "Webserver: Certificate with alias '${0}' was not found inside the keystore file '${1}'." + http: "์›น์„œ๋ฒ„: ์ธ์ฆ์„œ ์—†์Œ-> ์‹œ๊ฐํ™”๋ฅผ ์œ„ํ•ด HTTP ์„œ๋ฒ„ ์‚ฌ์šฉ." + ipWhitelist: "์›น์„œ๋ฒ„: IP ํ™”์ดํŠธ๋ฆฌ์ŠคํŠธ๊ฐ€ ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + ipWhitelistBlock: "์›น์„œ๋ฒ„: ${0} '${1}'์— ๋Œ€ํ•œ ์•ก์„ธ์Šค๊ฐ€ ๊ฑฐ๋ถ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. (ํ—ˆ์šฉ๋˜์ง€ ์•Š์Œ)" + noCertFile: "์›น์„œ๋ฒ„: ์ธ์ฆ์„œ ํ‚ค ์ €์žฅ์†Œ ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Œ : ${0}" + reverseProxy: "์›น์„œ๋ฒ„: ํ”„๋ก์‹œ ๋ชจ๋“œ HTTPS๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ์„ค์ • ํ•œ ๊ฒฝ์šฐ ์—ญ๋ฐฉํ–ฅ ํ”„๋ก์‹œ๊ฐ€ HTTPS๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ผ์šฐํŒ…๋˜๊ณ  Alternative_IP ๊ณ„ํš์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค." diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_NL.txt b/Plan/common/src/main/resources/assets/plan/locale/locale_NL.txt deleted file mode 100644 index f40a8de11..000000000 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_NL.txt +++ /dev/null @@ -1,517 +0,0 @@ -API - css+ || PaginaUitbreiding: ${0} heeft stylesheet(s) toegevoegd aan ${1}, ${2} -API - js+ || PaginaUitbreiding: ${0} heeft javascript(s) toegevoegd aan ${1}, ${2} -Cmd - Click Me || Klik mij -Cmd - Link || Koppel -Cmd - Link Network || Netwerkpagina: -Cmd - Link Player || Spelerpagina: -Cmd - Link Player JSON || Speler json: -Cmd - Link Players || Spelerspagina: -Cmd - Link Register || Registratiepagina: -Cmd - Link Server || Serverpagina: -CMD Arg - backup-file || Naam van het backup bestand (hoofdlettergevoelig) -CMD Arg - code || Code gebruikt om de registratie af te ronden. -CMD Arg - db type backup || Type van de database waarvan u een back-up wilt maken. Current database is used if not specified. -CMD Arg - db type clear || Type van de database waarvan u alle date van wilt verwijderen. -CMD Arg - db type hotswap || Type van de database om te gaan gebruiken. -CMD Arg - db type move from || Type van de database waaruit u gegevens wilt verplaatsen. -CMD Arg - db type move to || Type van de database waarnaar de gegevens moeten worden verplaatst. Kan niet hetzelfde zijn als de vorige. -CMD Arg - db type restore || Type van de database waarnaar moet worden teruggezet. De huidige database wordt gebruikt indien deze niet gespecificeerd is. -CMD Arg - feature || Naam van de functie die moet worden uitgeschakeld: ${0} -CMD Arg - player identifier || Naam of UUID van een speler -CMD Arg - player identifier remove || Identificatie voor een speler die uit de huidige database moet worden verwijderd. -CMD Arg - server identifier || Naam, ID of UUID van een server -CMD Arg - subcommand || Gebruik de opdracht zonder subopdracht om hulp te zien. -CMD Arg - username || Gebruikersnaam van een andere gebruiker. Indien niet gespecificeerd, dan wordt de spelers gekoppelde gebruiker gebruikt. -CMD Arg Name - backup-file || backup bestand -CMD Arg Name - code || ${code} -CMD Arg Name - export kind || soort uitvoer -CMD Arg Name - feature || feature -CMD Arg Name - import kind || soort invoer -CMD Arg Name - name or uuid || naam/uuid -CMD Arg Name - server || server -CMD Arg Name - subcommand || subopdracht -CMD Arg Name - username || gebruikersnaam -Cmd Confirm - accept || Accepteer -Cmd Confirm - cancelled, no data change || Geannuleerd. Er zijn geen gegevens gewijzigd. -Cmd Confirm - cancelled, unregister || Geannuleerd. '${0}' is niet uitgeschreven -Cmd Confirm - clearing db || U staat op het punt om alle abonnementsgegevens te verwijderen in ${0} -Cmd Confirm - confirmation || Bevestig: -Cmd Confirm - deny || Annuleren -Cmd Confirm - Expired || Bevestiging verlopen, gebruik de opdracht opnieuw -Cmd Confirm - Fail on accept || De geaccepteerde actie gaf een fout bij de uitvoering: ${0} -Cmd Confirm - Fail on deny || De geweigerde actie heeft een fout gemaakt bij de uitvoering: ${0} -Cmd Confirm - overwriting db || U staat op het punt gegevens in Plan ${0} te overschrijven met gegevens in ${1} -Cmd Confirm - remove player db || U staat op het punt om gegevens van ${0} te verwijderen van ${1} -Cmd Confirm - unregister || U staat op het punt de registratie van '${0}' gekoppeld aan ${1} ongedaan te maken -Cmd db - creating backup || Een back-upbestand '${0}.db' maken met een inhoud van ${1} -Cmd db - removal || Plan-gegevens verwijderen van ${0}.. -Cmd db - removal player || Gegevens van ${0} verwijderen uit ${1}.. -Cmd db - server uninstalled || ยงaAls de server nog steeds is geรฏnstalleerd, wordt deze automatisch ingesteld als geรฏnstalleerd in de database. -Cmd db - write || Schrijven naar ${0}.. -Cmd Disable - Disabled || ยงaPlansystemen zijn nu uitgeschakeld. U kunt nog steeds opnieuw laden gebruiken om de plug-in opnieuw te starten. -Cmd FAIL - Accepts only these arguments || Accepteert het volgende als ${0}: ${1} -Cmd FAIL - Database not open || ยงcDatabase is ${0} - probeer het later opnieuw. -Cmd FAIL - Empty search string || De zoekreeks mag niet leeg zijn -Cmd FAIL - Invalid Username || ยงcGebruiker heeft geen UUID. -Cmd FAIL - No Feature || ยงeDefinieer een functie om uit te schakelen! (ondersteunt momenteel ${0}) -Cmd FAIL - No Permission || ยงcU beschikt niet over de vereiste toestemming. -Cmd FAIL - No player || Speler '${0}' is niet gevonden, deze heeft geen UUID. -Cmd FAIL - No player register || Speler '${0}' is niet gevonden in de database. -Cmd FAIL - No server || Server '${0}' is niet gevonden in de database. -Cmd FAIL - Require only one Argument || ยงcEรฉn argument vereist ${1} -Cmd FAIL - Requires Arguments || ยงcArgumenten vereist (${0}) ${1} -Cmd FAIL - see config || zien '${0}' in config.yml -Cmd FAIL - Unknown Username || ยงcGebruiker is niet gezien op deze server -Cmd FAIL - Users not linked || Gebruiker is niet gekoppeld aan uw account en u heeft geen toestemming om de accounts van andere gebruikers te verwijderen. -Cmd FAIL - WebUser does not exists || ยงcGebruiker bestaat niet! -Cmd FAIL - WebUser exists || ยงcGebruiker bestaat al! -Cmd Footer - Help || ยง7Plaats de muisaanwijzer op een opdracht of argumenten of gebruik '/${0} ?' om er meer over te weten te komen. -Cmd Header - Analysis || > ยง2Analyse resultaten -Cmd Header - Help || > ยง2/${0} Hulp -Cmd Header - Info || > ยง2Spelersanalyse -Cmd Header - Inspect || > ยง2Speler: ยงf${0} -Cmd Header - Network || > ยง2Netwerkpagina -Cmd Header - Players || > ยง2Spelers -Cmd Header - Search || > ยง2${0} Resultaten voor ยงf${1}ยง2: -Cmd Header - server list || id::name::uuid -Cmd Header - Servers || > ยง2Servers -Cmd Header - web user list || username::linked to::permission level -Cmd Header - Web Users || > ยง2${0} Webgebruikers -Cmd Info - Bungee Connection || ยง2Verbonden met proxy: ยงf${0} -Cmd Info - Database || ยง2Huidige database: ยงf${0} -Cmd Info - Reload Complete || ยงaHerladen voltooid -Cmd Info - Reload Failed || ยงcEr is iets misgegaan tijdens het herladen van de plugin, een herstart wordt aanbevolen. -Cmd Info - Update || ยง2Update Beschikbaar: ยงf${0} -Cmd Info - Version || ยง2Versie: ยงf${0} -Cmd network - No network || Server is niet verbonden met een netwerk. De link verwijst door naar de serverpagina. -Cmd Notify - No Address || ยงeEr was geen adres beschikbaar - localhost wordt gebruikt als terugval. Stel de 'Alternative_IP'-instelling in. -Cmd Notify - No WebUser || U heeft misschien geen webgebruiker, gebruik /plan register -Cmd Notify - WebUser register || Geregistreerde nieuwe gebruiker: '${0}' Recht niveau: ${1} -Cmd Qinspect - Active Playtime || ยง2Actieve speeltijd: ยงf${0} -Cmd Qinspect - Activity Index || ยง2Activiteitsindex: ยงf${0} | ${1} -Cmd Qinspect - AFK Playtime || ยง2AFK Tijd: ยงf${0} -Cmd Qinspect - Deaths || ยง2Sterfgevallen: ยงf${0} -Cmd Qinspect - Geolocation || ยง2Ingelogd vanaf: ยงf${0} -Cmd Qinspect - Last Seen || ยง2Laatst gezien: ยงf${0} -Cmd Qinspect - Longest Session || ยง2Langste sessie: ยงf${0} -Cmd Qinspect - Mob Kills || ยง2Mob moorden: ยงf${0} -Cmd Qinspect - Player Kills || ยง2Speler moorden: ยงf${0} -Cmd Qinspect - Playtime || ยง2Speeltijd: ยงf${0} -Cmd Qinspect - Registered || ยง2Geregistreerd: ยงf${0} -Cmd Qinspect - Times Kicked || ยง2Aantal keer afgetrapt: ยงf${0} -Cmd SUCCESS - Feature disabled || ยงa'${0}' tijdelijk uitgeschakeld tot de volgende keer dat de plugin opnieuw wordt geladen. -Cmd SUCCESS - WebUser register || ยงaNieuwe gebruiker (${0}) sucesvol toegevoegd! -Cmd unregister - unregistering || '${0}' Uitschrijven.. -Cmd WARN - Database not open || ยงeDatabase is ${0} - Dit kan langer duren dan verwacht.. -Cmd Web - Permission Levels || >\ยง70: Toeging tot alle pagina's\ยง71: Toegang tot '/players' en alle spelerpagina's\ยง72: Toegang tot de spelerpagina met dezelfde gebruikersnaam als de webgebruiker\ยง73+: Geen rechten -Command Help - /plan db || Beheer Plan database -Command Help - /plan db backup || Back-up van gegevens van een database naar een bestand -Command Help - /plan db clear || ALLE Plan-gegevens verwijderen uit een database -Command Help - /plan db hotswap || Snel van database veranderen -Command Help - /plan db move || Gegevens verplaatsen tussen databases -Command Help - /plan db remove || Spelersgegevens uit de huidige database verwijderen -Command Help - /plan db restore || Gegevens herstellen van een bestand naar een database -Command Help - /plan db uninstalled || Stel een server in als verwijderd in de database. -Command Help - /plan disable || Schakel de plugin of een deel ervan uit -Command Help - /plan export || Exporteer html- of json-bestanden handmatig -Command Help - /plan import || Gegevens importeren -Command Help - /plan info || Informatie over de plugin -Command Help - /plan ingame || Spelersinfo bekijken in het spel -Command Help - /plan json || Bekijk json van een spelers onbewerkte data. -Command Help - /plan logout || Andere gebruikers uitloggen uit het paneel. -Command Help - /plan network || Bekijk de netwerkpagina -Command Help - /plan player || Een spelerspagina bekijken -Command Help - /plan players || Bekijk de spelerspagina -Command Help - /plan register || Registreer een webgebruiker -Command Help - /plan reload || Herstart Plan -Command Help - /plan search || Zoek naar een spelersnaam -Command Help - /plan server || Bekijk de serverpagina -Command Help - /plan servers || Servers in database weergeven -Command Help - /plan unregister || Een gebruiker van de Plan-website uitschrijven -Command Help - /plan users || Lijst van alle webgebruikers -Database - Apply Patch || Patch Toepassen: ${0}.. -Database - Patches Applied || Alle databasepatches zijn succesvol toegepast. -Database - Patches Applied Already || Alle databasepatches zijn al toegepast. -Database MySQL - Launch Options Error || Startopties waren defect, gebruik maken van standaardopties (${0}) -Database Notify - Clean || Gegevens van ${0} spelers zijn verwijderd. -Database Notify - SQLite No WAL || SQLite WAL-modus wordt niet ondersteund op deze serverversie, de standaard wordt niet gebruiker. Dit heeft mogelijk invloed op de prestaties. -Disable || Player Analytics uitgeschakeld. -Disable - Processing || Kritieke onverwerkte taken verwerken. (${0}) -Disable - Processing Complete || Verwerking voltooid. -Disable - Unsaved Session Save || Onvoltooide sessies opslaan.. -Disable - Unsaved Session Save Timeout || Time-outhit, de onvoltooide sessies worden opgeslagen bij de volgende inschakeling. -Disable - Waiting SQLite || Wachten op het voltooien van query's om te voorkomen dat JVM door SQLite crasht.. -Disable - Waiting SQLite Complete || SQLite-verbinding gesloten. -Disable - Waiting Transactions || Wachten op onvoltooide transacties om gegevensverlies te voorkomen.. -Disable - Waiting Transactions Complete || Transactiewachtrij gesloten. -Disable - WebServer || Webserver is uitgeschakeld. -Enable || Player Analytics ingeschakeld. -Enable - Database || ${0}-databaseverbinding tot stand gebracht. -Enable - Notify Bad IP || 0.0.0.0 is geen geldig adres, stel de Alternative_IP-instelling in. Er kunnen onjuiste links worden gegeven! -Enable - Notify Empty IP || IP in server.properties is leeg en de Alternative_IP-instelling is niet in gebruik. Er kunnen onjuiste links worden gegeven! -Enable - Notify Geolocations disabled || Geolocatie verzamelen is niet actief. (Data.Geolocations: false) -Enable - Notify Geolocations Internet Required || Plan Vereist internettoegang bij de eerste run om de GeoLite2 Geolocation-database te downloaden. -Enable - Notify Webserver disabled || Webserver is niet geรฏnitialiseerd. (WebServer.DisableWebServer: true) -Enable - Storing preserved sessions || Sessies opslaan die zijn bewaard vรณรณr de vorige afsluiting. -Enable - WebServer || Webserver draait op PORT ${0} ( ${1} ) -Enable FAIL - Database || ${0}-Databaseverbinding mislukt: ${1} -Enable FAIL - Database Patch || Database patchen is mislukt, plugin moet worden uitgeschakeld. Meld dit probleem a.u.b. -Enable FAIL - GeoDB Write || Er is iets misgegaan bij het opslaan van de gedownloade GeoLite2 Geolocation-database -Enable FAIL - WebServer (Proxy) || WebServer is niet geรฏnitialiseerd! -Enable FAIL - Wrong Database Type || ${0} is geen ondersteunde database -HTML - AND_BUG_REPORTERS || & Bug melders! -HTML - BANNED (Filters) || Verbannen -HTML - COMPARING_15_DAYS || 15 dagen vergelijken -HTML - COMPARING_60_DAYS || 30 dagen tot nu vergelijken -HTML - COMPARING_7_DAYS || 7 dagen vergelijken -HTML - DATABASE_NOT_OPEN || Database is niet open, controleer db-status met /plan info -HTML - DESCRIBE_RETENTION_PREDICTION || Deze waarde is een voorspelling op basis van eerdere spelers. -HTML - ERROR || Authenticatie mislukt vanwege fout -HTML - EXPIRED_COOKIE || Gebruikerscookie is verlopen -HTML - FILTER_ACTIVITY_INDEX_NOW || Huidige activiteitengroep -HTML - FILTER_ALL_PLAYERS || Alle spelers -HTML - FILTER_BANNED || Verbodsstatus -HTML - FILTER_GROUP || Groep: -HTML - FILTER_OPS || Operatorstatus -HTML - INDEX_ACTIVE || Actief -HTML - INDEX_INACTIVE || Inactief -HTML - INDEX_IRREGULAR || Onregelmatig -HTML - INDEX_REGULAR || Regelmatig -HTML - INDEX_VERY_ACTIVE || Heel Actief -HTML - KILLED || Vermoord -HTML - LABEL_1ST_WEAPON || Dodelijkste PvP-wapen -HTML - LABEL_2ND_WEAPON || 2e PvP-wapen -HTML - LABEL_3RD_WEAPON || 3e PvP-wapen -HTML - LABEL_ACTIVE_PLAYTIME || Actieve Speeltijd -HTML - LABEL_ACTIVITY_INDEX || Activiteitsindex -HTML - LABEL_AFK || AFK -HTML - LABEL_AFK_TIME || AFK Tijd -HTML - LABEL_AVG || Gemiddelde -HTML - LABEL_AVG_ACTIVE_PLAYTIME || Gemiddelde Actieve Speeltijd -HTML - LABEL_AVG_AFK_TIME || Gemiddelde AFK Tijd -HTML - LABEL_AVG_CHUNKS || Gemiddelde Chunks -HTML - LABEL_AVG_ENTITIES || Gemiddelde Entiteiten -HTML - LABEL_AVG_KDR || Gemiddeld KDR -HTML - LABEL_AVG_MOB_KDR || Gemiddeld Mob KDR -HTML - LABEL_AVG_PLAYTIME || Gemiddelde Speeltijd -HTML - LABEL_AVG_SESSION_LENGTH || Gemiddelde sessieduur -HTML - LABEL_AVG_SESSIONS || Gemiddelde sessies -HTML - LABEL_AVG_TPS || Gemiddelde TPS -HTML - LABEL_BANNED || Verbannen -HTML - LABEL_BEST_PEAK || Piek aller tijden -HTML - LABEL_DAY_OF_WEEK || Dag van de Week -HTML - LABEL_DEATHS || Sterfgevallen -HTML - LABEL_DOWNTIME || Uitvaltijd -HTML - LABEL_DURING_LOW_TPS || Tijdens lage TPS-pieken: -HTML - LABEL_ENTITIES || Entiteiten -HTML - LABEL_FAVORITE_SERVER || Favoeriete Server -HTML - LABEL_FIRST_SESSION_LENGTH || Duur eerste sessie -HTML - LABEL_FREE_DISK_SPACE || Vrije schijfruimte -HTML - LABEL_INACTIVE || Inactief -HTML - LABEL_LAST_PEAK || Laatste piek -HTML - LABEL_LAST_SEEN || Laatste gezien -HTML - LABEL_LOADED_CHUNKS || Geladen Chunks -HTML - LABEL_LOADED_ENTITIES || Geladen Entiteiten -HTML - LABEL_LONE_JOINS || Eenzame inloggen -HTML - LABEL_LONE_NEW_JOINS || Eenzame nieuweling inloggen -HTML - LABEL_LONGEST_SESSION || Langste sessie -HTML - LABEL_LOW_TPS || Lage TPS-pieken -HTML - LABEL_MAX_FREE_DISK || Max Vrije schijfruimte -HTML - LABEL_MIN_FREE_DISK || Min Vrije schijfruimte -HTML - LABEL_MOB_DEATHS || Mob veroorzaakt doden -HTML - LABEL_MOB_KDR || Mob KDR -HTML - LABEL_MOB_KILLS || Mob Moorden -HTML - LABEL_MOST_ACTIVE_GAMEMODE || Meest actieve spelmodus -HTML - LABEL_NAME || Naam -HTML - LABEL_NEW || Nieuw -HTML - LABEL_NEW_PLAYERS || Nieuwe Spelers -HTML - LABEL_NICKNAME || Gebruikersnaam -HTML - LABEL_NO_SESSION_KILLS || Geen -HTML - LABEL_ONLINE_FIRST_JOIN || Spelers online bij eerste deelname -HTML - LABEL_OPERATOR || Operator -HTML - LABEL_PER_PLAYER || / Spelers -HTML - LABEL_PER_REGULAR_PLAYER || / Reguliere speler -HTML - LABEL_PLAYER_DEATHS || Speler veroorzaakte moorden -HTML - LABEL_PLAYER_KILLS || Player moorden -HTML - LABEL_PLAYERS_ONLINE || Spelers Online -HTML - LABEL_PLAYTIME || Speeltijd -HTML - LABEL_REGISTERED || Geregistreerd -HTML - LABEL_REGISTERED_PLAYERS || Geregistreerde spelers -HTML - LABEL_REGULAR || Regulier -HTML - LABEL_REGULAR_PLAYERS || Reguliere speler -HTML - LABEL_RELATIVE_JOIN_ACTIVITY || Relatieve deelname aan activiteit -HTML - LABEL_RETENTION || Retentie nieuwe speler -HTML - LABEL_SERVER_DOWNTIME || Serveruitva -HTML - LABEL_SERVER_OCCUPIED || Server bezet -HTML - LABEL_SESSION_ENDED || Beรซindigd -HTML - LABEL_SESSION_MEDIAN || Sessiemediaan -HTML - LABEL_TIMES_KICKED || Aantal keer afgetapt -HTML - LABEL_TOTAL_PLAYERS || Totaal aantal spelers -HTML - LABEL_TOTAL_PLAYTIME || Totale speeltijd -HTML - LABEL_UNIQUE_PLAYERS || Unieke spelers -HTML - LABEL_WEEK_DAYS || 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrijdag', 'Zaterdag', 'Zondag' -HTML - LINK_BACK_NETWORK || Netwerkpagina -HTML - LINK_BACK_SERVER || Serverpagina -HTML - LINK_CHANGELOG || Bekijk changelog -HTML - LINK_DISCORD || Algemene ondersteuning op Discord -HTML - LINK_DOWNLOAD || Download -HTML - LINK_ISSUES || Problemen melden -HTML - LINK_NIGHT_MODE || Donkeremodus -HTML - LINK_PLAYER_PAGE || Spelerpagina -HTML - LINK_QUICK_VIEW || Snel overzicht -HTML - LINK_SERVER_ANALYSIS || Serveranalyse -HTML - LINK_WIKI || Plan Wiki, Tutorials & Documentatie -HTML - LOCAL_MACHINE || Lokaal apparaat -HTML - LOGIN_CREATE_ACCOUNT || Maak een account! -HTML - LOGIN_FAILED || Login niet gelukt: -HTML - LOGIN_FORGOT_PASSWORD || Wachtwoord Vergeten? -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_1 || Wachtwoord Vergeten? Uitschrijven en opnieuw registreren. -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_2 || Gebruik de volgende opdracht in het spel om je huidige gebruiker te verwijderen: -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_3 || Of via console: -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_4 || Na gebruik van de opdracht, -HTML - LOGIN_LOGIN || Inloggen -HTML - LOGIN_LOGOUT || Uitloggen -HTML - LOGIN_PASSWORD || "Wachtwoord" -HTML - LOGIN_USERNAME || "Gebruikersnaam" -HTML - NAV_PLUGINS || Plugins -HTML - NEW_CALENDAR || Nieuwe: -HTML - NO_KILLS || Geen Moorden -HTML - NO_USER_PRESENT || Gebruikerscookie niet aanwezig -HTML - NON_OPERATORS (Filters) || Geen operators -HTML - NOT_BANNED (Filters) || Niet verbannen -HTML - OFFLINE || Offline -HTML - ONLINE || Online -HTML - OPERATORS (Filters) || Operators -HTML - PER_DAY || / Dag -HTML - PLAYERS_TEXT || Spelers -HTML - QUERY || Query< -HTML - QUERY_ACTIVITY_OF_MATCHED_PLAYERS || Activiteit van gematchte spelers -HTML - QUERY_ACTIVITY_ON || Activiteit op -HTML - QUERY_ADD_FILTER || Voeg een filter toe.. -HTML - QUERY_AND || en -HTML - QUERY_ARE || `zijn` -HTML - QUERY_ARE_ACTIVITY_GROUP || zijn in activiteitengroepen -HTML - QUERY_ARE_PLUGIN_GROUP || zijn in ${plugin}'s ${group} groepen -HTML - QUERY_JOINED_WITH_ADDRESS || aangemeld met adres -HTML - QUERY_LOADING_FILTERS || Filters laden.. -HTML - QUERY_MAKE || Maak een query -HTML - QUERY_MAKE_ANOTHER || Maak nog een query -HTML - QUERY_OF_PLAYERS || van Spelers die -HTML - QUERY_PERFORM_QUERY || Query Uitvoeren! -HTML - QUERY_PLAYED_BETWEEN || Spelers tussen -HTML - QUERY_REGISTERED_BETWEEN || Geregistreerd tussen -HTML - QUERY_RESULTS || Query Resultaat -HTML - QUERY_RESULTS_MATCH || matchte ${resultCount} spelers -HTML - QUERY_SESSIONS_WITHIN_VIEW || Sessies in weergave -HTML - QUERY_SHOW_VIEW || Toon een weergave -HTML - QUERY_TIME_FROM || >van -HTML - QUERY_TIME_TO || >naar -HTML - QUERY_VIEW || Weergave: -HTML - QUERY_ZERO_RESULTS || Query heeft 0 resultaten opgeleverd -HTML - REGISTER || Registreer -HTML - REGISTER_CHECK_FAILED || Het controleren van de registratiestatus is mislukt: -HTML - REGISTER_COMPLETE || Voltooi registratie -HTML - REGISTER_COMPLETE_INSTRUCTIONS_1 || U kunt nu de registratie van de gebruiker voltooien. -HTML - REGISTER_COMPLETE_INSTRUCTIONS_2 || Code verloopt over 15 minuten -HTML - REGISTER_COMPLETE_INSTRUCTIONS_3 || Gebruik de volgende opdracht in het spel om de registratie te voltooien:: -HTML - REGISTER_COMPLETE_INSTRUCTIONS_4 || Of via console: -HTML - REGISTER_CREATE_USER || Nieuwe gebruiker aanmaken -HTML - REGISTER_FAILED || Registratie mislukt: -HTML - REGISTER_HAVE_ACCOUNT || Heb je al een account? Log in! -HTML - REGISTER_PASSWORD_TIP || Wachtwoord kan het beste langer zijn dan 8 tekens, maar er zijn geen beperkingen. -HTML - REGISTER_SPECIFY_PASSWORD || U moet een wachtwoord opgeven -HTML - REGISTER_SPECIFY_USERNAME || U moet een gebruikersnaam opgeven -HTML - REGISTER_USERNAME_LENGTH || Gebruikersnaam kan maximaal 50 tekens lang zijn, de jouwe is -HTML - REGISTER_USERNAME_TIP || Gebruikersnaam mag maximaal 50 tekens bevatten. -HTML - SESSION || Sessie -HTML - SIDE_GEOLOCATIONS || Geolocaties -HTML - SIDE_INFORMATION || INFORMATIE -HTML - SIDE_LINKS || LINKS -HTML - SIDE_NETWORK_OVERVIEW || Netwerkoverzicht -HTML - SIDE_OVERVIEW || Overzicht -HTML - SIDE_PERFORMANCE || Prestatie -HTML - SIDE_PLAYER_LIST || Speler lijst -HTML - SIDE_PLAYERBASE || Spelersbasis -HTML - SIDE_PLAYERBASE_OVERVIEW || Spelersbasis-overzicht -HTML - SIDE_PLUGINS || PLUGINS -HTML - SIDE_PVP_PVE || PvP & PvE -HTML - SIDE_SERVERS || Servers -HTML - SIDE_SERVERS_TITLE || SERVERS -HTML - SIDE_SESSIONS || Sessies -HTML - SIDE_TO_MAIN_PAGE || naar hoofdpagina -HTML - TEXT_CLICK_TO_EXPAND || Klik om uit te breiden -HTML - TEXT_CONTRIBUTORS_CODE || code bijdrager -HTML - TEXT_CONTRIBUTORS_LOCALE || vertaler -HTML - TEXT_CONTRIBUTORS_MONEY || Extra speciale dank aan degenen die de ontwikkeling financieel hebben ondersteund. -HTML - TEXT_CONTRIBUTORS_THANKS || Daarnaast hebben de volgende geweldige mensen bijgedragen: -HTML - TEXT_DEV_VERSION || Deze versie is een DEV-release. -HTML - TEXT_DEVELOPED_BY || is ontwikkeld door -HTML - TEXT_FIRST_SESSION || Eerste sessie -HTML - TEXT_LICENSED_UNDER || Player Analytics is ontwikkeld en gelicentieerd onder -HTML - TEXT_METRICS || bStats-statistieken -HTML - TEXT_NO_EXTENSION_DATA || Geen extensiegegevens -HTML - TEXT_NO_LOW_TPS || Geen lage tps-pieken -HTML - TEXT_NO_SERVER || Er is geen server om online activiteit voor weer te geven -HTML - TEXT_NO_SERVERS || Er zijn geen servers gevonden in de database -HTML - TEXT_PLUGIN_INFORMATION || Informatie over de plugin -HTML - TEXT_PREDICTED_RETENTION || Deze waarde is een voorspelling op basis van eerdere spelers -HTML - TEXT_SERVER_INSTRUCTIONS || Het lijkt erop dat Plan op geen enkele gameserver is geรฏnstalleerd of niet is verbonden met dezelfde database. Zie de wiki voor een netwerkhandleiding. -HTML - TEXT_VERSION || Een nieuwe versie is uitgebracht en is nu beschikbaar om te downloaden. -HTML - TITLE_30_DAYS || 30 dagen -HTML - TITLE_30_DAYS_AGO || 30 dagen geleden -HTML - TITLE_ALL || Alle -HTML - TITLE_ALL_TIME || Alle Tijd -HTML - TITLE_AS_NUMBERS || als nummers -HTML - TITLE_AVG_PING || Gemiddelde ping -HTML - TITLE_BEST_PING || Beste ping -HTML - TITLE_CALENDAR || Kalender -HTML - TITLE_CONNECTION_INFO || Verbindingsinformatie -HTML - TITLE_COUNTRY || Land -HTML - TITLE_CPU_RAM || CPU & RAM -HTML - TITLE_CURRENT_PLAYERBASE || Huidige spelerbasis -HTML - TITLE_DISK || Schijfruimte -HTML - TITLE_GRAPH_DAY_BY_DAY || Dag voor dag -HTML - TITLE_GRAPH_HOUR_BY_HOUR || Uur voor uur -HTML - TITLE_GRAPH_NETWORK_ONLINE_ACTIVITY || Online netwerkactiviteit -HTML - TITLE_GRAPH_PUNCHCARD || Ponskaart voor 30 dagen -HTML - TITLE_INSIGHTS || Inzichten voor 30 dagen -HTML - TITLE_IS_AVAILABLE || is beschikbaar -HTML - TITLE_JOIN_ADDRESSES || Inlog adressen -HTML - TITLE_LAST_24_HOURS || Afgelopen 24 uur -HTML - TITLE_LAST_30_DAYS || Afgelopen 30 dagen -HTML - TITLE_LAST_7_DAYS || Afgelopen 7 dagen -HTML - TITLE_LAST_CONNECTED || Laatst verbonden -HTML - TITLE_LENGTH || Lengte -HTML - TITLE_MOST_PLAYED_WORLD || Meest gespeelde wereld -HTML - TITLE_NETWORK || Netwerk -HTML - TITLE_NETWORK_AS_NUMBERS || Netwerk als getallen -HTML - TITLE_NOW || Nu -HTML - TITLE_ONLINE_ACTIVITY || Online activiteit -HTML - TITLE_ONLINE_ACTIVITY_AS_NUMBERS || Online activiteit als getallen -HTML - TITLE_ONLINE_ACTIVITY_OVERVIEW || Overzicht van online activiteiten -HTML - TITLE_PERFORMANCE_AS_NUMBERS || Prestaties als getallen -HTML - TITLE_PING || Ping -HTML - TITLE_PLAYER || Speler -HTML - TITLE_PLAYER_OVERVIEW || Speler overzicht -HTML - TITLE_PLAYERBASE_DEVELOPMENT || Spelerbasis-ontwikkeling -HTML - TITLE_PVP_DEATHS || Recente PvP-doden -HTML - TITLE_PVP_KILLS || Recente PvP-moorden -HTML - TITLE_PVP_PVE_NUMBERS || PvP & PvE als getallen -HTML - TITLE_RECENT_KILLS || Recente moorden -HTML - TITLE_RECENT_SESSIONS || Recente sessies -HTML - TITLE_SEEN_NICKNAMES || Bijnamen gezien -HTML - TITLE_SERVER || Server -HTML - TITLE_SERVER_AS_NUMBERS || Server als getallen -HTML - TITLE_SERVER_OVERVIEW || Serveroverzicht -HTML - TITLE_SERVER_PLAYTIME || Server speeltijd -HTML - TITLE_SERVER_PLAYTIME_30 || Server speeltijd voor 30 dagen -HTML - TITLE_SESSION_START || Sessie gestart -HTML - TITLE_THEME_SELECT || Thema selecteren -HTML - TITLE_TITLE_PLAYER_PUNCHCARD || Ponskaart -HTML - TITLE_TPS || TPS -HTML - TITLE_TREND || Trend -HTML - TITLE_TRENDS || Trends voor 30 dagen -HTML - TITLE_VERSION || Versie -HTML - TITLE_WEEK_COMPARISON || Weekvergelijking -HTML - TITLE_WORLD || Wereldbelasting -HTML - TITLE_WORLD_PLAYTIME || Wereld speeltijd -HTML - TITLE_WORST_PING || Slechtste ping -HTML - TOTAL_ACTIVE_TEXT || Totaal actief -HTML - TOTAL_AFK || Totaal AFK -HTML - TOTAL_PLAYERS || Totaal aantal spelers -HTML - UNIQUE_CALENDAR || Uniek: -HTML - UNIT_CHUNKS || Chunks -HTML - UNIT_ENTITIES || Entiteiten -HTML - UNIT_NO_DATA || Geen gegevens -HTML - UNIT_THE_PLAYERS || Spelers -HTML - USER_AND_PASS_NOT_SPECIFIED || Gebruiker en wachtwoord niet gespecificeerd -HTML - USER_DOES_NOT_EXIST || Gebruiker bestaat niet -HTML - USER_INFORMATION_NOT_FOUND || Registratie mislukt, probeer het opnieuw (de code verloopt na 15 minuten) -HTML - USER_PASS_MISMATCH || Gebruiker en wachtwoord kwamen niet overeen -HTML - Version Change log || Bekijk changelog -HTML - Version Current || Je hebt versie ${0} -HTML - Version Download || Download Plan-${0}.jar -HTML - Version Update || Update -HTML - Version Update Available || Versie ${0} is beschikbaar! -HTML - Version Update Dev || Deze versie is een DEV-release. -HTML - Version Update Info || Een nieuwe versie is uitgebracht en is nu beschikbaar om te downloaden. -HTML - WARNING_NO_GAME_SERVERS || Voor sommige gegevens moet Plan op gameservers zijn geรฏnstalleerd. -HTML - WARNING_NO_GEOLOCATIONS || Het verzamelen van geolocatie moet worden ingeschakeld in de configuratie (Accept GeoLite2 EULA). -HTML - WARNING_NO_SPONGE_CHUNKS || Chunks niet beschikbaar op Sponge -HTML - WITH || Met -HTML ERRORS - ACCESS_DENIED_403 || Toegang geweigerd -HTML ERRORS - AUTH_FAIL_TIPS_401 || - Zorg ervoor dat u een gebruiker heeft geregistreerd met /plan register
- Controleer of de gebruikersnaam en het wachtwoord correct zijn
- De gebruikersnaam en het wachtwoord zijn hoofdlettergevoelig

Als u uw wachtwoord bent vergeten kunt u een beheerder vragen om uw oude account te verwijderen en een nieuwe te registreren. -HTML ERRORS - AUTHENTICATION_FAILED_401 || Authenticatie mislukt. -HTML ERRORS - FORBIDDEN_403 || Verboden -HTML ERRORS - NO_SERVERS_404 || Er zijn geen servers online om het verzoek uit te voeren. -HTML ERRORS - NOT_FOUND_404 || Niet gevonden -HTML ERRORS - NOT_PLAYED_404 || Plan heeft deze speler niet gezien. -HTML ERRORS - PAGE_NOT_FOUND_404 || Pagina bestaat niet. -HTML ERRORS - UNAUTHORIZED_401 || Onbevoegd -HTML ERRORS - UNKNOWN_PAGE_404 || Zorg ervoor dat u een link opent die door een opdracht wordt gegeven, Voorbeelden:

/player/{uuid/name}
/server/{uuid/name/id}

-HTML ERRORS - UUID_404 || Speler UUID is niet gevonden in de database. -In Depth Help - /plan db || Gebruik verschillende database-subopdrachten om de gegevens op de een of andere manier te wijzigen -In Depth Help - /plan db backup || Gebruikt SQLite om een back-up te maken van de database naar een bestand. -In Depth Help - /plan db clear || Wist alle Plan-tabellen en verwijdert alle Plan-gegevens tijdens het proces. -In Depth Help - /plan db hotswap || Laat de plugin opnieuw met de andere database en wijzig de configuratie zodat deze overeenkomt. -In Depth Help - /plan db move || Overschrijft inhoud in de database met de inhoud in een andere. -In Depth Help - /plan db remove || Verwijder alle gegevens die aan een speler zijn gekoppeld uit de huidige database. -In Depth Help - /plan db restore || Gebruikt SQLite-back-upbestand en overschrijft de inhoud van de database. -In Depth Help - /plan db uninstalled || Markeert een server in de Plan-database als verwijderd, zodat deze niet wordt weergegeven in serverquery's. -In Depth Help - /plan disable || Schakel de plug-in of een deel ervan uit tot de volgende herlaad/herstart. -In Depth Help - /plan export || Voer een export uit naar de exportlocatie gedefinieerd in de configuratie. -In Depth Help - /plan import || Voer een import uit om gegevens naar de database te laden. -In Depth Help - /plan info || Toon de huidige status van de plugin. -In Depth Help - /plan ingame || Geef informatie weer over de speler in het spel. -In Depth Help - /plan json || Hiermee kunt u alle gegevens van een speler downloaden als json. -In Depth Help - /plan logout || Geef gebruikersnaam argument om een andere gebruiker uit te loggen van het paneel, geef * als argument om iedereen uit te loggen. -In Depth Help - /plan network || Verkrijg een link naar de /network pagina, dit werkt alleen op netwerken. -In Depth Help - /plan player || Verkrijg een link naar de /player pagina van een specifieke speler, of de huidige speler. -In Depth Help - /plan players || Verkrijg een link naar de /players pagina om een lijst met spelers te zien. -In Depth Help - /plan register || Gebruik zonder argumenten om een link naar de registratiepagina te krijgen. Gebruik --code [code] na registratie om een gebruiker te krijgen. -In Depth Help - /plan reload || Schakel de plugin uit en in om eventuele wijzigingen in de configuratie opnieuw te laden. -In Depth Help - /plan search || Lijst van alle overeenkomende spelersnamen op een gedeeldte van een naam. -In Depth Help - /plan server || Verkrijg een link naar de /server pagina van een specifieke server, of de huidige server als er geen argumenten worden gegeven. -In Depth Help - /plan servers || Geef een lijst met id's, namen en uuids van servers weer uit de database. -In Depth Help - /plan unregister || Gebruik zonder argumenten om de aan een speler gekoppelde gebruiker uit te schrijven, of met gebruikersnaamargument om een andere gebruiker uit te schrijven. -In Depth Help - /plan users || Geeft webgebruikers weer als een tabel. -Manage - Confirm Overwrite || Gegevens in ${0} worden overschreven! -Manage - Confirm Removal || Gegevens in ${0} worden verwijderd! -Manage - Fail || > ยงcEr is iets fout gegaan: ${0} -Manage - Fail File not found || > Geen bestand gevonden op ${0} -Manage - Fail Incorrect Database || > ยงc'${0}' is geen ondersteunde database. -Manage - Fail No Exporter || ยงeExporteur '${0}' bestaat niet -Manage - Fail No Importer || ยงeImporteur '${0}' bestaat niet -Manage - Fail No Server || Geen server gevonden met opgegeven parameters. -Manage - Fail Same Database || > ยงcKan niet van en naar dezelfde database werken! -Manage - Fail Same server || Kan deze server niet markeren als verwijderd (u bevindt zich erop) -Manage - Fail, Confirmation || > ยงcVoeg het '-a' argument toe om uitvoering te bevestigen: ${0} -Manage - List Importers || Importeurs: -Manage - Progress || ${0} / ${1} verwerkt.. -Manage - Remind HotSwap || ยงeVergeet niet om naar de nieuwe database te wisselen (/plan db hotswap ${0}) en de plugin opnieuw te laden. -Manage - Start || > ยง2Gegevensverwerking.. -Manage - Success || > ยงaSucces! -Negative || Nee -Positive || Ja -Today || 'Vandaag' -Unavailable || Niet beschikbaar -Unknown || Onbekend -Version - DEV || Dit is een DEV-release. -Version - Latest || Je gebruikt de laatste versie. -Version - New || Nieuwe release (${0}) is beschikbaar voor ${1} -Version - New (old) || Nieuwe versie is beschikbaar op ${0} -Version FAIL - Read info (old) || Het nieuwste versienummer kan niet worden gecontroleerd -Version FAIL - Read versions.txt || Versie-informatie kon niet worden geladen vanuit Github/versions.txt -Web User Listing || ยง2${0} ยง7: ยงf${1} -WebServer - Notify HTTP || WebServer: Geen Certificaat -> HTTP-server gebruiken voor visualisatie. -WebServer - Notify HTTP User Auth || WebServer: Gebruikersautorisatie uitgeschakeld! (Niet veilig via HTTP) -WebServer - Notify HTTPS User Auth || WebServer: Gebruikersautorisatie uitgeschakeld! (Uitgeschakeld in configuratie) -Webserver - Notify IP Whitelist || Webserver: IP Whitelist is ingeschakeld. -Webserver - Notify IP Whitelist Block || Webserver: ${0} heeft geen toegang to '${1}'. (niet op de whitelist) -WebServer - Notify no Cert file || WebServer: Certificaat KeyStore-bestand niet gevonden: ${0} -WebServer - Notify Using Proxy || WebServer: HTTPS-proxymodus ingeschakeld, zorg ervoor dat uw reverse-proxy routert met HTTPS en de Plan Alternative_IP.Address-instelling wijst naar de proxy -WebServer FAIL - EOF || WebServer: EOF bij het lezen van het certificaatbestand. (Controleer of het bestand niet leeg is) -WebServer FAIL - Port Bind || WebServer is niet succesvol geรฏnitialiseerd. Is poort (${0}) in gebruik? -WebServer FAIL - SSL Context || WebServer: SSL-contextinitialisatie mislukt. -WebServer FAIL - Store Load || WebServer: Laden SSL-certificaat mislukt. -Yesterday || 'Gisteren' diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_NL.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_NL.yml new file mode 100644 index 000000000..5d05f8183 --- /dev/null +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_NL.yml @@ -0,0 +1,669 @@ +403AccessDenied: "Toegang geweigerd" +command: + argument: + backupFile: + description: "Naam van het backup bestand (hoofdlettergevoelig)" + name: "backup bestand" + code: + description: "Code gebruikt om de registratie af te ronden." + name: "${code}" + dbBackup: + description: "Type van de database waarvan u een back-up wilt maken. Current database is used if not specified." + dbRestore: + description: "Type van de database waarnaar moet worden teruggezet. De huidige database wordt gebruikt indien deze niet gespecificeerd is." + dbTypeHotswap: + description: "Type van de database om te gaan gebruiken." + dbTypeMoveFrom: + description: "Type van de database waaruit u gegevens wilt verplaatsen." + dbTypeMoveTo: + description: "Type van de database waarnaar de gegevens moeten worden verplaatst. Kan niet hetzelfde zijn als de vorige." + dbTypeRemove: + description: "Type van de database waarvan u alle date van wilt verwijderen." + exportKind: "soort uitvoer" + feature: + description: "Naam van de functie die moet worden uitgeschakeld: ${0}" + name: "feature" + importKind: "soort invoer" + nameOrUUID: + description: "Naam of UUID van een speler" + name: "naam/uuid" + removeDescription: "Identificatie voor een speler die uit de huidige database moet worden verwijderd." + server: + description: "Naam, ID of UUID van een server" + name: "server" + subcommand: + description: "Gebruik de opdracht zonder subopdracht om hulp te zien." + name: "subopdracht" + username: + description: "Gebruikersnaam van een andere gebruiker. Indien niet gespecificeerd, dan wordt de spelers gekoppelde gebruiker gebruikt." + name: "gebruikersnaam" + confirmation: + accept: "Accepteer" + cancelNoChanges: "Geannuleerd. Er zijn geen gegevens gewijzigd." + cancelNoUnregister: "Geannuleerd. '${0}' is niet uitgeschreven" + confirm: "Bevestig: " + dbClear: "U staat op het punt om alle abonnementsgegevens te verwijderen in ${0}" + dbOverwrite: "U staat op het punt gegevens in Plan ${0} te overschrijven met gegevens in ${1}" + dbRemovePlayer: "U staat op het punt om gegevens van ${0} te verwijderen van ${1}" + deny: "Annuleren" + expired: "Bevestiging verlopen, gebruik de opdracht opnieuw" + unregister: "U staat op het punt de registratie van '${0}' gekoppeld aan ${1} ongedaan te maken" + database: + creatingBackup: "Een back-upbestand '${0}.db' maken met een inhoud van ${1}" + failDbNotOpen: "ยงcDatabase is ${0} - probeer het later opnieuw." + manage: + confirm: "> ยงcVoeg het '-a' argument toe om uitvoering te bevestigen: ${0}" + confirmOverwrite: "Gegevens in ${0} worden overschreven!" + confirmPartialRemoval: "Join Address Data for Server ${0} in ${1} will be removed!" + confirmRemoval: "Gegevens in ${0} worden verwijderd!" + fail: "> ยงcEr is iets fout gegaan: ${0}" + failFileNotFound: "> Geen bestand gevonden op ${0}" + failIncorrectDB: "> ยงc'${0}' is geen ondersteunde database." + failNoServer: "Geen server gevonden met opgegeven parameters." + failSameDB: "> ยงcKan niet van en naar dezelfde database werken!" + failSameServer: "Kan deze server niet markeren als verwijderd (u bevindt zich erop)" + hotswap: "ยงeVergeet niet om naar de nieuwe database te wisselen (/plan db hotswap ${0}) en de plugin opnieuw te laden." + importers: "Importeurs:" + preparing: "Preparing.." + progress: "${0} / ${1} verwerkt.." + start: "> ยง2Gegevensverwerking.." + success: "> ยงaSucces!" + playerRemoval: "Gegevens van ${0} verwijderen uit ${1}.." + removal: "Plan-gegevens verwijderen van ${0}.." + serverUninstalled: "ยงaAls de server nog steeds is geรฏnstalleerd, wordt deze automatisch ingesteld als geรฏnstalleerd in de database." + unregister: "'${0}' Uitschrijven.." + warnDbNotOpen: "ยงeDatabase is ${0} - Dit kan langer duren dan verwacht.." + write: "Schrijven naar ${0}.." + fail: + emptyString: "De zoekreeks mag niet leeg zijn" + invalidArguments: "Accepteert het volgende als ${0}: ${1}" + invalidUsername: "ยงcGebruiker heeft geen UUID." + missingArguments: "ยงcArgumenten vereist (${0}) ${1}" + missingFeature: "ยงeDefinieer een functie om uit te schakelen! (ondersteunt momenteel ${0})" + missingLink: "Gebruiker is niet gekoppeld aan uw account en u heeft geen toestemming om de accounts van andere gebruikers te verwijderen." + noPermission: "ยงcU beschikt niet over de vereiste toestemming." + onAccept: "De geaccepteerde actie gaf een fout bij de uitvoering: ${0}" + onDeny: "De geweigerde actie heeft een fout gemaakt bij de uitvoering: ${0}" + playerNotFound: "Speler '${0}' is niet gevonden, deze heeft geen UUID." + playerNotInDatabase: "Speler '${0}' is niet gevonden in de database." + seeConfig: "zien '${0}' in config.yml" + serverNotFound: "Server '${0}' is niet gevonden in de database." + tooManyArguments: "ยงcEรฉn argument vereist ${1}" + unknownUsername: "ยงcGebruiker is niet gezien op deze server" + webUserExists: "ยงcGebruiker bestaat al!" + webUserNotFound: "ยงcGebruiker bestaat niet!" + footer: + help: "ยง7Plaats de muisaanwijzer op een opdracht of argumenten of gebruik '/${0} ?' om er meer over te weten te komen." + general: + failNoExporter: "ยงeExporteur '${0}' bestaat niet" + failNoImporter: "ยงeImporteur '${0}' bestaat niet" + featureDisabled: "ยงa'${0}' tijdelijk uitgeschakeld tot de volgende keer dat de plugin opnieuw wordt geladen." + noAddress: "ยงeEr was geen adres beschikbaar - localhost wordt gebruikt als terugval. Stel de 'Alternative_IP'-instelling in." + noWebuser: "U heeft misschien geen webgebruiker, gebruik /plan register " + notifyWebUserRegister: "Geregistreerde nieuwe gebruiker: '${0}' Recht niveau: ${1}" + pluginDisabled: "ยงaPlansystemen zijn nu uitgeschakeld. U kunt nog steeds opnieuw laden gebruiken om de plug-in opnieuw te starten." + reloadComplete: "ยงaHerladen voltooid" + reloadFailed: "ยงcEr is iets misgegaan tijdens het herladen van de plugin, een herstart wordt aanbevolen." + successWebUserRegister: "ยงaNieuwe gebruiker (${0}) sucesvol toegevoegd!" + webPermissionLevels: ">\ยง70: Toeging tot alle pagina's\ยง71: Toegang tot '/players' en alle spelerpagina's\ยง72: Toegang tot de spelerpagina met dezelfde gebruikersnaam als de webgebruiker\ยง73+: Geen rechten" + webUserList: " ยง2${0} ยง7: ยงf${1}" + header: + analysis: "> ยง2Analyse resultaten" + help: "> ยง2/${0} Hulp" + info: "> ยง2Spelersanalyse" + inspect: "> ยง2Speler: ยงf${0}" + network: "> ยง2Netwerkpagina" + players: "> ยง2Spelers" + search: "> ยง2${0} Resultaten voor ยงf${1}ยง2:" + serverList: "id::name::uuid::version" + servers: "> ยง2Servers" + webUserList: "username::linked to::permission level" + webUsers: "> ยง2${0} Webgebruikers" + help: + database: + description: "Beheer Plan database" + inDepth: "Gebruik verschillende database-subopdrachten om de gegevens op de een of andere manier te wijzigen" + dbBackup: + description: "Back-up van gegevens van een database naar een bestand" + inDepth: "Gebruikt SQLite om een back-up te maken van de database naar een bestand." + dbClear: + description: "ALLE Plan-gegevens verwijderen uit een database" + inDepth: "Wist alle Plan-tabellen en verwijdert alle Plan-gegevens tijdens het proces." + dbHotswap: + description: "Snel van database veranderen" + inDepth: "Laat de plugin opnieuw met de andere database en wijzig de configuratie zodat deze overeenkomt." + dbMove: + description: "Gegevens verplaatsen tussen databases" + inDepth: "Overschrijft inhoud in de database met de inhoud in een andere." + dbRemove: + description: "Spelersgegevens uit de huidige database verwijderen" + inDepth: "Verwijder alle gegevens die aan een speler zijn gekoppeld uit de huidige database." + dbRestore: + description: "Gegevens herstellen van een bestand naar een database" + inDepth: "Gebruikt SQLite-back-upbestand en overschrijft de inhoud van de database." + dbUninstalled: + description: "Stel een server in als verwijderd in de database." + inDepth: "Markeert een server in de Plan-database als verwijderd, zodat deze niet wordt weergegeven in serverquery's." + disable: + description: "Schakel de plugin of een deel ervan uit" + inDepth: "Schakel de plug-in of een deel ervan uit tot de volgende herlaad/herstart." + export: + description: "Exporteer html- of json-bestanden handmatig" + inDepth: "Voer een export uit naar de exportlocatie gedefinieerd in de configuratie." + import: + description: "Gegevens importeren" + inDepth: "Voer een import uit om gegevens naar de database te laden." + info: + description: "Informatie over de plugin" + inDepth: "Toon de huidige status van de plugin." + ingame: + description: "Spelersinfo bekijken in het spel" + inDepth: "Geef informatie weer over de speler in het spel." + json: + description: "Bekijk json van een spelers onbewerkte data." + inDepth: "Hiermee kunt u alle gegevens van een speler downloaden als json." + logout: + description: "Andere gebruikers uitloggen uit het paneel." + inDepth: "Geef gebruikersnaam argument om een andere gebruiker uit te loggen van het paneel, geef * als argument om iedereen uit te loggen." + migrateToOnlineUuids: + description: "Migrate offline uuid data to online uuids" + network: + description: "Bekijk de netwerkpagina" + inDepth: "Verkrijg een link naar de /network pagina, dit werkt alleen op netwerken." + player: + description: "Een spelerspagina bekijken" + inDepth: "Verkrijg een link naar de /player pagina van een specifieke speler, of de huidige speler." + players: + description: "Bekijk de spelerspagina" + inDepth: "Verkrijg een link naar de /players pagina om een lijst met spelers te zien." + register: + description: "Registreer een webgebruiker" + inDepth: "Gebruik zonder argumenten om een link naar de registratiepagina te krijgen. Gebruik --code [code] na registratie om een gebruiker te krijgen." + reload: + description: "Herstart Plan" + inDepth: "Schakel de plugin uit en in om eventuele wijzigingen in de configuratie opnieuw te laden." + removejoinaddresses: + description: "Remove join addresses of a specified server" + search: + description: "Zoek naar een spelersnaam" + inDepth: "Lijst van alle overeenkomende spelersnamen op een gedeeldte van een naam." + server: + description: "Bekijk de serverpagina" + inDepth: "Verkrijg een link naar de /server pagina van een specifieke server, of de huidige server als er geen argumenten worden gegeven." + servers: + description: "Servers in database weergeven" + inDepth: "Geef een lijst met id's, namen en uuids van servers weer uit de database." + unregister: + description: "Een gebruiker van de Plan-website uitschrijven" + inDepth: "Gebruik zonder argumenten om de aan een speler gekoppelde gebruiker uit te schrijven, of met gebruikersnaamargument om een andere gebruiker uit te schrijven." + users: + description: "Lijst van alle webgebruikers" + inDepth: "Geeft webgebruikers weer als een tabel." + ingame: + activePlaytime: " ยง2Actieve speeltijd: ยงf${0}" + activityIndex: " ยง2Activiteitsindex: ยงf${0} | ${1}" + afkPlaytime: " ยง2AFK Tijd: ยงf${0}" + deaths: " ยง2Sterfgevallen: ยงf${0}" + geolocation: " ยง2Ingelogd vanaf: ยงf${0}" + lastSeen: " ยง2Laatst gezien: ยงf${0}" + longestSession: " ยง2Langste sessie: ยงf${0}" + mobKills: " ยง2Mob moorden: ยงf${0}" + playerKills: " ยง2Speler moorden: ยงf${0}" + playtime: " ยง2Speeltijd: ยงf${0}" + registered: " ยง2Geregistreerd: ยงf${0}" + timesKicked: " ยง2Aantal keer afgetrapt: ยงf${0}" + link: + clickMe: "Klik mij" + link: "Koppel" + network: "Netwerkpagina: " + noNetwork: "Server is niet verbonden met een netwerk. De link verwijst door naar de serverpagina." + player: "Spelerpagina: " + playerJson: "Speler json: " + players: "Spelerspagina: " + register: "Registratiepagina: " + server: "Serverpagina: " + subcommand: + info: + database: " ยง2Huidige database: ยงf${0}" + proxy: " ยง2Verbonden met proxy: ยงf${0}" + update: " ยง2Update Beschikbaar: ยงf${0}" + version: " ยง2Versie: ยงf${0}" +generic: + noData: "Geen gegevens" +html: + button: + nightMode: "Donkeremodus" + calendar: + new: "Nieuwe:" + unique: "Uniek:" + description: + newPlayerRetention: "Deze waarde is een voorspelling op basis van eerdere spelers." + noGameServers: "Voor sommige gegevens moet Plan op gameservers zijn geรฏnstalleerd." + noGeolocations: "Het verzamelen van geolocatie moet worden ingeschakeld in de configuratie (Accept GeoLite2 EULA)." + noServerOnlinActivity: "Er is geen server om online activiteit voor weer te geven" + noServers: "Er zijn geen servers gevonden in de database" + noServersLong: 'Het lijkt erop dat Plan op geen enkele gameserver is geรฏnstalleerd of niet is verbonden met dezelfde database. Zie de wiki voor een netwerkhandleiding.' + noSpongeChunks: "Chunks niet beschikbaar op Sponge" + predictedNewPlayerRetention: "Deze waarde is een voorspelling op basis van eerdere spelers" + error: + 401Unauthorized: "Onbevoegd" + 403Forbidden: "Verboden" + 404NotFound: "Niet gevonden" + 404PageNotFound: "Pagina bestaat niet." + 404UnknownPage: "Zorg ervoor dat u een link opent die door een opdracht wordt gegeven, Voorbeelden:

/player/{uuid/name}
/server/{uuid/name/id}

" + UUIDNotFound: "Speler UUID is niet gevonden in de database." + auth: + dbClosed: "Database is niet open, controleer db-status met /plan info" + emptyForm: "Gebruiker en wachtwoord niet gespecificeerd" + expiredCookie: "Gebruikerscookie is verlopen" + generic: "Authenticatie mislukt vanwege fout" + loginFailed: "Gebruiker en wachtwoord kwamen niet overeen" + noCookie: "Gebruikerscookie niet aanwezig" + registrationFailed: "Registratie mislukt, probeer het opnieuw (de code verloopt na 15 minuten)" + userNotFound: "Gebruiker bestaat niet" + authFailed: "Authenticatie mislukt." + authFailedTips: "- Zorg ervoor dat u een gebruiker heeft geregistreerd met /plan register
- Controleer of de gebruikersnaam en het wachtwoord correct zijn
- De gebruikersnaam en het wachtwoord zijn hoofdlettergevoelig

Als u uw wachtwoord bent vergeten kunt u een beheerder vragen om uw oude account te verwijderen en een nieuwe te registreren." + noServersOnline: "Er zijn geen servers online om het verzoek uit te voeren." + playerNotSeen: "Plan heeft deze speler niet gezien." + serverNotSeen: "Server doesn't exist" + generic: + none: "Geen" + label: + active: "Actief" + activePlaytime: "Actieve Speeltijd" + activityIndex: "Activiteitsindex" + afk: "AFK" + afkTime: "AFK Tijd" + all: "Alle" + allTime: "Alle Tijd" + alphabetical: "Alphabetical" + apply: "Apply" + asNumbers: "als nummers" + average: "Gemiddelde" + averageActivePlaytime: "Gemiddelde Actieve Speeltijd" + averageAfkTime: "Gemiddelde AFK Tijd" + averageChunks: "Gemiddelde Chunks" + averageCpuUsage: "Average CPU Usage" + averageEntities: "Gemiddelde Entiteiten" + averageKdr: "Gemiddeld KDR" + averageMobKdr: "Gemiddeld Mob KDR" + averagePing: "Gemiddelde ping" + averagePlayers: "Average Players" + averagePlaytime: "Gemiddelde Speeltijd" + averageRamUsage: "Average RAM Usage" + averageServerDowntime: "Average Downtime / Server" + averageSessionLength: "Gemiddelde sessieduur" + averageSessions: "Gemiddelde sessies" + averageTps: "Gemiddelde TPS" + averageTps7days: "Average TPS (7 days)" + banned: "Verbannen" + bestPeak: "Piek aller tijden" + bestPing: "Beste ping" + calendar: " Kalender" + comparing7days: "7 dagen vergelijken" + connectionInfo: "Verbindingsinformatie" + country: "Land" + cpu: "CPU" + cpuRam: "CPU & RAM" + cpuUsage: "CPU Usage" + currentPlayerbase: "Huidige spelerbasis" + currentUptime: "Current Uptime" + dayByDay: "Dag voor dag" + dayOfweek: "Dag van de Week" + deadliestWeapon: "Dodelijkste PvP-wapen" + deaths: "Sterfgevallen" + disk: "Schijfruimte" + diskSpace: "Vrije schijfruimte" + downtime: "Uitvaltijd" + duringLowTps: "Tijdens lage TPS-pieken:" + entities: "Entiteiten" + favoriteServer: "Favoeriete Server" + firstSession: "Eerste sessie" + firstSessionLength: + average: "Average first session length" + median: "Median first session length" + geoProjection: + dropdown: "Select projection" + equalEarth: "Equal Earth" + mercator: "Mercator" + miller: "Miller" + ortographic: "Ortographic" + geolocations: "Geolocaties" + hourByHour: "Uur voor uur" + inactive: "Inactief" + indexInactive: "Inactief" + indexRegular: "Regelmatig" + information: "INFORMATIE" + insights: "Insights" + insights30days: "Inzichten voor 30 dagen" + irregular: "Onregelmatig" + joinAddress: "Join Address" + joinAddresses: "Inlog adressen" + kdr: "KDR" + killed: "Vermoord" + last24hours: "Afgelopen 24 uur" + last30days: "Afgelopen 30 dagen" + last7days: "Afgelopen 7 dagen" + lastConnected: "Laatst verbonden" + lastPeak: "Laatste piek" + lastSeen: "Laatste gezien" + latestJoinAddresses: "Latest Join Addresses" + length: " Lengte" + links: "LINKS" + loadedChunks: "Geladen Chunks" + loadedEntities: "Geladen Entiteiten" + loneJoins: "Eenzame inloggen" + loneNewbieJoins: "Eenzame nieuweling inloggen" + longestSession: "Langste sessie" + lowTpsSpikes: "Lage TPS-pieken" + lowTpsSpikes7days: "Low TPS Spikes (7 days)" + maxFreeDisk: "Max Vrije schijfruimte" + medianSessionLength: "Median Session Length" + minFreeDisk: "Min Vrije schijfruimte" + mobDeaths: "Mob veroorzaakt doden" + mobKdr: "Mob KDR" + mobKills: "Mob Moorden" + mostActiveGamemode: "Meest actieve spelmodus" + mostPlayedWorld: "Meest gespeelde wereld" + name: "Naam" + network: "Netwerk" + networkAsNumbers: "Netwerk als getallen" + networkOnlineActivity: "Online netwerkactiviteit" + networkOverview: "Netwerkoverzicht" + networkPage: "Netwerkpagina" + new: "Nieuw" + newPlayerRetention: "Retentie nieuwe speler" + newPlayers: "Nieuwe Spelers" + newPlayers7days: "New Players (7 days)" + nickname: "Gebruikersnaam" + noDataToDisplay: "No Data to Display" + now: "Nu" + onlineActivity: "Online activiteit" + onlineActivityAsNumbers: "Online activiteit als getallen" + onlineOnFirstJoin: "Spelers online bij eerste deelname" + operator: "Operator" + overview: "Overzicht" + perDay: "/ Dag" + perPlayer: "/ Spelers" + perRegularPlayer: "/ Reguliere speler" + performance: "Prestatie" + performanceAsNumbers: "Prestaties als getallen" + ping: "Ping" + player: "Speler" + playerDeaths: "Speler veroorzaakte moorden" + playerKills: "Player moorden" + playerList: "Speler lijst" + playerOverview: "Speler overzicht" + playerPage: "Spelerpagina" + playerRetention: "Player Retention" + playerbase: "Spelersbasis" + playerbaseDevelopment: "Spelerbasis-ontwikkeling" + playerbaseOverview: "Playerbase Overview" + players: "Spelers" + playersOnline: "Spelers Online" + playersOnlineNow: "Players Online (Now)" + playersOnlineOverview: "Overzicht van online activiteiten" + playtime: "Speeltijd" + plugins: "Plugins" + pluginsOverview: "Plugins Overview" + punchcard: "Ponskaart" + punchcard30days: "Ponskaart voor 30 dagen" + pvpPve: "PvP & PvE" + pvpPveAsNumbers: "PvP & PvE als getallen" + query: "Maak een query" + quickView: "Snel overzicht" + ram: "RAM" + ramUsage: "RAM Usage" + recentKills: "Recente moorden" + recentPvpDeaths: "Recente PvP-doden" + recentPvpKills: "Recente PvP-moorden" + recentSessions: "Recente sessies" + registered: "Geregistreerd" + registeredPlayers: "Geregistreerde spelers" + regular: "Regulier" + regularPlayers: "Reguliere speler" + relativeJoinActivity: "Relatieve deelname aan activiteit" + secondDeadliestWeapon: "2e PvP-wapen" + seenNicknames: "Bijnamen gezien" + server: "Server" + serverAnalysis: "Serveranalyse" + serverAsNumberse: "Server als getallen" + serverCalendar: "Server Calendar" + serverDowntime: "Serveruitva" + serverOccupied: "Server bezet" + serverOverview: "Serveroverzicht" + serverPage: "Serverpagina" + serverPlaytime: "Server speeltijd" + serverPlaytime30days: "Server speeltijd voor 30 dagen" + serverSelector: "Server selector" + servers: "Servers" + serversTitle: "SERVERS" + session: "Sessie" + sessionCalendar: "Session Calendar" + sessionEnded: " Beรซindigd" + sessionMedian: "Sessiemediaan" + sessionStart: "Sessie gestart" + sessions: "Sessies" + sortBy: "Sort By" + stacked: "Stacked" + themeSelect: "Thema selecteren" + thirdDeadliestWeapon: "3e PvP-wapen" + thirtyDays: "30 dagen" + thirtyDaysAgo: "30 dagen geleden" + timesKicked: "Aantal keer afgetapt" + toMainPage: "naar hoofdpagina" + total: "Total" + totalActive: "Totaal actief" + totalAfk: "Totaal AFK" + totalPlayers: "Totaal aantal spelers" + totalPlayersOld: "Totaal aantal spelers" + totalPlaytime: "Totale speeltijd" + totalServerDowntime: "Total Server Downtime" + tps: "TPS" + trend: "Trend" + trends30days: "Trends voor 30 dagen" + uniquePlayers: "Unieke spelers" + uniquePlayers7days: "Unique Players (7 days)" + veryActive: "Heel Actief" + weekComparison: "Weekvergelijking" + weekdays: "'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrijdag', 'Zaterdag', 'Zondag'" + world: "Wereldbelasting" + worldPlaytime: "Wereld speeltijd" + worstPing: "Slechtste ping" + login: + failed: "Login niet gelukt: " + forgotPassword: "Wachtwoord Vergeten?" + forgotPassword1: "Wachtwoord Vergeten? Uitschrijven en opnieuw registreren." + forgotPassword2: "Gebruik de volgende opdracht in het spel om je huidige gebruiker te verwijderen:" + forgotPassword3: "Of via console:" + forgotPassword4: "Na gebruik van de opdracht, " + login: "Inloggen" + logout: "Uitloggen" + password: "Wachtwoord" + register: "Maak een account!" + username: "Gebruikersnaam" + modal: + info: + bugs: "Problemen melden" + contributors: + bugreporters: "& Bug melders!" + code: "code bijdrager" + donate: "Extra speciale dank aan degenen die de ontwikkeling financieel hebben ondersteund." + text: 'Daarnaast hebben de volgende geweldige mensen bijgedragen:' + translator: "vertaler" + developer: "is ontwikkeld door" + discord: "Algemene ondersteuning op Discord" + license: "Player Analytics is ontwikkeld en gelicentieerd onder" + metrics: "bStats-statistieken" + text: "Informatie over de plugin" + wiki: "Plan Wiki, Tutorials & Documentatie" + version: + available: "is beschikbaar" + changelog: "Bekijk changelog" + dev: "Deze versie is een DEV-release." + download: "Download" + text: "Een nieuwe versie is uitgebracht en is nu beschikbaar om te downloaden." + title: "Versie" + query: + filter: + activity: + text: "zijn in activiteitengroepen" + banStatus: + name: "Verbodsstatus" + banned: "Verbannen" + country: + text: "have joined from country" + generic: + allPlayers: "Alle spelers" + and: "en " + start: "van Spelers die " + joinAddress: + text: "aangemeld met adres" + nonOperators: "Geen operators" + notBanned: "Niet verbannen" + operatorStatus: + name: "Operatorstatus" + operators: "Operators" + playedBetween: + text: "Spelers tussen" + pluginGroup: + name: "Groep: " + text: "zijn in ${plugin}'s ${group} groepen" + registeredBetween: + text: "Geregistreerd tussen" + title: + activityGroup: "Huidige activiteitengroep" + view: " Weergave:" + filters: + add: "Voeg een filter toe.." + loading: "Filters laden.." + generic: + are: "`zijn`" + label: + from: ">van" + makeAnother: "Maak nog een query" + to: ">naar" + view: "Toon een weergave" + performQuery: "Query Uitvoeren!" + results: + match: "matchte ${resultCount} spelers" + none: "Query heeft 0 resultaten opgeleverd" + title: "Query Resultaat" + title: + activity: "Activiteit van gematchte spelers" + activityOnDate: 'Activiteit op ' + sessionsWithinView: "Sessies in weergave" + text: "Query<" + register: + completion: "Voltooi registratie" + completion1: "U kunt nu de registratie van de gebruiker voltooien." + completion2: "Code verloopt over 15 minuten" + completion3: "Gebruik de volgende opdracht in het spel om de registratie te voltooien::" + completion4: "Of via console:" + createNewUser: "Nieuwe gebruiker aanmaken" + error: + checkFailed: "Het controleren van de registratiestatus is mislukt: " + failed: "Registratie mislukt: " + noPassword: "U moet een wachtwoord opgeven" + noUsername: "U moet een gebruikersnaam opgeven" + usernameLength: "Gebruikersnaam kan maximaal 50 tekens lang zijn, de jouwe is " + login: "Heb je al een account? Log in!" + passwordTip: "Wachtwoord kan het beste langer zijn dan 8 tekens, maar er zijn geen beperkingen." + register: "Registreer" + usernameTip: "Gebruikersnaam mag maximaal 50 tekens bevatten." + text: + clickToExpand: "Klik om uit te breiden" + comparing15days: "15 dagen vergelijken" + comparing30daysAgo: "30 dagen tot nu vergelijken" + noExtensionData: "Geen extensiegegevens" + noLowTps: "Geen lage tps-pieken" + unit: + chunks: "Chunks" + players: "Spelers" + value: + localMachine: "Lokaal apparaat" + noKills: "Geen Moorden" + offline: " Offline" + online: " Online" + with: "Met" + version: + changelog: "Bekijk changelog" + current: "Je hebt versie ${0}" + download: "Download Plan-${0}.jar" + isDev: "Deze versie is een DEV-release." + updateButton: "Update" + updateModal: + text: "Een nieuwe versie is uitgebracht en is nu beschikbaar om te downloaden." + title: "Versie ${0} is beschikbaar!" +plugin: + apiCSSAdded: "PaginaUitbreiding: ${0} heeft stylesheet(s) toegevoegd aan ${1}, ${2}" + apiJSAdded: "PaginaUitbreiding: ${0} heeft javascript(s) toegevoegd aan ${1}, ${2}" + disable: + database: "Kritieke onverwerkte taken verwerken. (${0})" + disabled: "Player Analytics uitgeschakeld." + processingComplete: "Verwerking voltooid." + savingSessions: "Onvoltooide sessies opslaan.." + savingSessionsTimeout: "Time-outhit, de onvoltooide sessies worden opgeslagen bij de volgende inschakeling." + waitingDb: "Wachten op het voltooien van query's om te voorkomen dat JVM door SQLite crasht.." + waitingDbComplete: "SQLite-verbinding gesloten." + waitingTransactions: "Wachten op onvoltooide transacties om gegevensverlies te voorkomen.." + waitingTransactionsComplete: "Transactiewachtrij gesloten." + webserver: "Webserver is uitgeschakeld." + enable: + database: "${0}-databaseverbinding tot stand gebracht." + enabled: "Player Analytics ingeschakeld." + fail: + database: "${0}-Databaseverbinding mislukt: ${1}" + databasePatch: "Database patchen is mislukt, plugin moet worden uitgeschakeld. Meld dit probleem a.u.b." + databaseType: "${0} is geen ondersteunde database" + geoDBWrite: "Er is iets misgegaan bij het opslaan van de gedownloade GeoLite2 Geolocation-database" + webServer: "WebServer is niet geรฏnitialiseerd!" + notify: + badIP: "0.0.0.0 is geen geldig adres, stel de Alternative_IP-instelling in. Er kunnen onjuiste links worden gegeven!" + emptyIP: "IP in server.properties is leeg en de Alternative_IP-instelling is niet in gebruik. Er kunnen onjuiste links worden gegeven!" + geoDisabled: "Geolocatie verzamelen is niet actief. (Data.Geolocations: false)" + geoInternetRequired: "Plan Vereist internettoegang bij de eerste run om de GeoLite2 Geolocation-database te downloaden." + storeSessions: "Sessies opslaan die zijn bewaard vรณรณr de vorige afsluiting." + webserverDisabled: "Webserver is niet geรฏnitialiseerd. (WebServer.DisableWebServer: true)" + webserver: "Webserver draait op PORT ${0} ( ${1} )" + generic: + dbApplyingPatch: "Patch Toepassen: ${0}.." + dbFaultyLaunchOptions: "Startopties waren defect, gebruik maken van standaardopties (${0})" + dbNotifyClean: "Gegevens van ${0} spelers zijn verwijderd." + dbNotifySQLiteWAL: "SQLite WAL-modus wordt niet ondersteund op deze serverversie, de standaard wordt niet gebruiker. Dit heeft mogelijk invloed op de prestaties." + dbPatchesAlreadyApplied: "Alle databasepatches zijn al toegepast." + dbPatchesApplied: "Alle databasepatches zijn succesvol toegepast." + dbSchemaPatch: "Database: Making sure schema is up to date.." + loadedServerInfo: "Server identifier loaded: ${0}" + loadingServerInfo: "Loading server identifying information" + no: "Nee" + today: "'Vandaag'" + unavailable: "Niet beschikbaar" + unknown: "Onbekend" + yes: "Ja" + yesterday: "'Gisteren'" + version: + checkFail: "Het nieuwste versienummer kan niet worden gecontroleerd" + checkFailGithub: "Versie-informatie kon niet worden geladen vanuit Github/versions.txt" + isDev: " Dit is een DEV-release." + isLatest: "Je gebruikt de laatste versie." + updateAvailable: "Nieuwe release (${0}) is beschikbaar voor ${1}" + updateAvailableSpigot: "Nieuwe versie is beschikbaar op ${0}" + webserver: + fail: + SSLContext: "WebServer: SSL-contextinitialisatie mislukt." + certFileEOF: "WebServer: EOF bij het lezen van het certificaatbestand. (Controleer of het bestand niet leeg is)" + certStoreLoad: "WebServer: Laden SSL-certificaat mislukt." + portInUse: "WebServer is niet succesvol geรฏnitialiseerd. Is poort (${0}) in gebruik?" + notify: + authDisabledConfig: "WebServer: Gebruikersautorisatie uitgeschakeld! (Uitgeschakeld in configuratie)" + authDisabledNoHTTPS: "WebServer: Gebruikersautorisatie uitgeschakeld! (Niet veilig via HTTP)" + certificateExpiresOn: "Webserver: Loaded certificate is valid until ${0}." + certificateExpiresPassed: "Webserver: Certificate has expired, consider renewing the certificate." + certificateExpiresSoon: "Webserver: Certificate expires in ${0}, consider renewing the certificate." + certificateNoSuchAlias: "Webserver: Certificate with alias '${0}' was not found inside the keystore file '${1}'." + http: "WebServer: Geen Certificaat -> HTTP-server gebruiken voor visualisatie." + ipWhitelist: "Webserver: IP Whitelist is ingeschakeld." + ipWhitelistBlock: "Webserver: ${0} heeft geen toegang to '${1}'. (niet op de whitelist)" + noCertFile: "WebServer: Certificaat KeyStore-bestand niet gevonden: ${0}" + reverseProxy: "WebServer: HTTPS-proxymodus ingeschakeld, zorg ervoor dat uw reverse-proxy routert met HTTPS en de Plan Alternative_IP.Address-instelling wijst naar de proxy" diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_PT_BR.txt b/Plan/common/src/main/resources/assets/plan/locale/locale_PT_BR.txt deleted file mode 100644 index b93272a1f..000000000 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_PT_BR.txt +++ /dev/null @@ -1,517 +0,0 @@ -API - css+ || PageExtension: ${0} added stylesheet(s) to ${1}, ${2} -API - js+ || PageExtension: ${0} added javascript(s) to ${1}, ${2} -Cmd - Click Me || Clique aqui -Cmd - Link || Link -Cmd - Link Network || Network page: -Cmd - Link Player || Player page: -Cmd - Link Player JSON || Player json: -Cmd - Link Players || Players page: -Cmd - Link Register || Register page: -Cmd - Link Server || Server page: -CMD Arg - backup-file || Name of the backup file (case sensitive) -CMD Arg - code || Code used to finalize registration. -CMD Arg - db type backup || Type of the database to backup. Current database is used if not specified. -CMD Arg - db type clear || Type of the database to remove all data from. -CMD Arg - db type hotswap || Type of the database to start using. -CMD Arg - db type move from || Type of the database to move data from. -CMD Arg - db type move to || Type of the database to move data to. Can not be same as previous. -CMD Arg - db type restore || Type of the database to restore to. Current database is used if not specified. -CMD Arg - feature || Name of the feature to disable: ${0} -CMD Arg - player identifier || Name or UUID of a player -CMD Arg - player identifier remove || Identifier for a player that will be removed from current database. -CMD Arg - server identifier || Name, ID or UUID of a server -CMD Arg - subcommand || Use the command without subcommand to see help. -CMD Arg - username || Username of another user. If not specified player linked user is used. -CMD Arg Name - backup-file || backup-file -CMD Arg Name - code || ${code} -CMD Arg Name - export kind || export kind -CMD Arg Name - feature || feature -CMD Arg Name - import kind || import kind -CMD Arg Name - name or uuid || name/uuid -CMD Arg Name - server || server -CMD Arg Name - subcommand || subcommand -CMD Arg Name - username || username -Cmd Confirm - accept || Accept -Cmd Confirm - cancelled, no data change || Cancelled. No data was changed. -Cmd Confirm - cancelled, unregister || Cancelled. '${0}' was not unregistered -Cmd Confirm - clearing db || You are about to remove all Plan-data in ${0} -Cmd Confirm - confirmation || Confirm: -Cmd Confirm - deny || Cancel -Cmd Confirm - Expired || Confirmation expired, use the command again -Cmd Confirm - Fail on accept || The accepted action errored upon execution: ${0} -Cmd Confirm - Fail on deny || The denied action errored upon execution: ${0} -Cmd Confirm - overwriting db || You are about to overwrite data in Plan ${0} with data in ${1} -Cmd Confirm - remove player db || You are about to remove data of ${0} from ${1} -Cmd Confirm - unregister || You are about to unregister '${0}' linked to ${1} -Cmd db - creating backup || Creating a backup file '${0}.db' with contents of ${1} -Cmd db - removal || Removing Plan-data from ${0}.. -Cmd db - removal player || Removing data of ${0} from ${1}.. -Cmd db - server uninstalled || ยงaIf the server is still installed, it will automatically set itself as installed in the database. -Cmd db - write || Writing to ${0}.. -Cmd Disable - Disabled || ยงaO sistema do Plan agora estรก desativado. Vocรช pode usar reload para reiniciar o plugin. -Cmd FAIL - Accepts only these arguments || Accepts following as ${0}: ${1} -Cmd FAIL - Database not open || ยงcDatabase is ${0} - Please try again a bit later. -Cmd FAIL - Empty search string || The search string can not be empty -Cmd FAIL - Invalid Username || ยงcEsse usuรกrio nรฃo tem uma UUID. -Cmd FAIL - No Feature || ยงeDefina um recurso para desativar! (atualmente suporta ${0}) -Cmd FAIL - No Permission || ยงcVocรช nรฃo tem a permissรฃo necessรกria. -Cmd FAIL - No player || Player '${0}' was not found, they have no UUID. -Cmd FAIL - No player register || Player '${0}' was not found in the database. -Cmd FAIL - No server || Server '${0}' was not found from the database. -Cmd FAIL - Require only one Argument || ยงcรšnico argumento necessรกrio ${1} -Cmd FAIL - Requires Arguments || ยงcArgumentos necessรกrios (${0}) ${1} -Cmd FAIL - see config || see '${0}' in config.yml -Cmd FAIL - Unknown Username || ยงcEsse usuรกrio nรฃo foi encontrado nesse servidor -Cmd FAIL - Users not linked || User is not linked to your account and you don't have permission to remove other user's accounts. -Cmd FAIL - WebUser does not exists || ยงcEsse usuรกrio nรฃo existe! -Cmd FAIL - WebUser exists || ยงcEsse usuรกrio jรก existe! -Cmd Footer - Help || ยง7Hover over command or arguments or use '/${0} ?' to learn more about them. -Cmd Header - Analysis || > ยง2Resultados da Anรกlise -Cmd Header - Help || > ยง2/${0} Help -Cmd Header - Info || > ยง2Anรกlise do Jogador -Cmd Header - Inspect || > ยง2Jogador: ยงf${0} -Cmd Header - Network || > ยง2Pรกgina da Network -Cmd Header - Players || > ยง2Jogadores -Cmd Header - Search || > ยง2${0} Resultados para ยงf${1}ยง2: -Cmd Header - server list || id::name::uuid -Cmd Header - Servers || > ยง2Servidores -Cmd Header - web user list || username::linked to::permission level -Cmd Header - Web Users || > ยง2${0} Usuรกrios da Web -Cmd Info - Bungee Connection || ยง2Conectados ao Bungee: ยงf${0} -Cmd Info - Database || ยง2Banco de dados atual: ยงf${0} -Cmd Info - Reload Complete || ยงaReload Realizado -Cmd Info - Reload Failed || ยงcAlguma coisa ocorreu ao recarregar o plugin, um restart รฉ recomendรกvel. -Cmd Info - Update || ยง2Atualizaรงรฃo Disponรญvel: ยงf${0} -Cmd Info - Version || ยง2Versรฃo: ยงf${0} -Cmd network - No network || Server is not connected to a network. The link redirects to server page. -Cmd Notify - No Address || ยงeNo address was available - using localhost as fallback. Set up 'Alternative_IP' settings. -Cmd Notify - No WebUser || Vocรช nรฃo tem um usuรกrio para web, utilize /plan register -Cmd Notify - WebUser register || Novo usuรกrio registrado: '${0}' Nรญvel de permissรฃo: ${1} -Cmd Qinspect - Active Playtime || ยง2Active Playtime: ยงf${0} -Cmd Qinspect - Activity Index || ยง2รndice de Atividade: ยงf${0} | ${1} -Cmd Qinspect - AFK Playtime || ยง2AFK Time: ยงf${0} -Cmd Qinspect - Deaths || ยง2Mortes: ยงf${0} -Cmd Qinspect - Geolocation || ยง2Conectado de: ยงf${0} -Cmd Qinspect - Last Seen || ยง2รšltima vez visto: ยงf${0} -Cmd Qinspect - Longest Session || ยง2Sessรฃo mais longa: ยงf${0} -Cmd Qinspect - Mob Kills || ยง2Assassinato de Mobs: ยงf${0} -Cmd Qinspect - Player Kills || ยง2Assassinato de Jogadores: ยงf${0} -Cmd Qinspect - Playtime || ยง2Tempo de Jogo: ยงf${0} -Cmd Qinspect - Registered || ยง2Registered: ยงf${0} -Cmd Qinspect - Times Kicked || ยง2Vezes Kickado: ยงf${0} -Cmd SUCCESS - Feature disabled || ยงaDesativado '${0}' temporariamente atรฉ o prรณximo reload do plugin. -Cmd SUCCESS - WebUser register || ยงaFoi adicionado um novo usuรกrio (${0})! -Cmd unregister - unregistering || Unregistering '${0}'.. -Cmd WARN - Database not open || ยงeDatabase is ${0} - This might take longer than expected.. -Cmd Web - Permission Levels || >\ยง70: Acesse todas as pรกginas\ยง71: Acesse '/players' e todas as pรกginas de jogadores\ยง72: Acesse a pรกgina de jogado com o mesmo usuรกrio do login\ยง73+: Sem permissรตes -Command Help - /plan db || Manage Plan database -Command Help - /plan db backup || Backup data of a database to a file -Command Help - /plan db clear || Remove ALL Plan data from a database -Command Help - /plan db hotswap || Alterar o banco de dados rapidamente -Command Help - /plan db move || Mover dados entre banco de dados -Command Help - /plan db remove || Remove player's data from Current database -Command Help - /plan db restore || Restore data from a file to a database -Command Help - /plan db uninstalled || Set a server as uninstalled in the database. -Command Help - /plan disable || Disable the plugin or part of it -Command Help - /plan export || Export html or json files manually -Command Help - /plan import || Import data -Command Help - /plan info || Information about the plugin -Command Help - /plan ingame || Visualziar informaรงรตes do Jogador in-game -Command Help - /plan json || View json of Player's raw data. -Command Help - /plan logout || Log out other users from the panel. -Command Help - /plan network || Visualizar a pรกgina da Network -Command Help - /plan player || Visualizar a pรกgina do jogador -Command Help - /plan players || Visualizar a pรกgina de Jogadores -Command Help - /plan register || Registrar um usuรกrio web -Command Help - /plan reload || Reiniciar Plan -Command Help - /plan search || Buscar por um nome de jogador -Command Help - /plan server || Visualizar a pรกgina do servidor -Command Help - /plan servers || Listar servidores do banco de dados -Command Help - /plan unregister || Unregister a user of Plan website -Command Help - /plan users || List all web users -Database - Apply Patch || Aplicando o Patch: ${0}.. -Database - Patches Applied || Todos os patchs de bancos de dados foram aplicados. -Database - Patches Applied Already || Todos os patchs de bancos de dados jรก foram aplicados. -Database MySQL - Launch Options Error || Opรงรตes de execuรงรฃo estavam com problemas, usando configuraรงรฃo padrรฃo (${0}) -Database Notify - Clean || Removido dados de ${0} jogadores. -Database Notify - SQLite No WAL || O modo WAL do SQLite nรฃo รฉ suportado nessa versรฃo do servidor, entรฃo serรก usado a configuraรงรฃo padrรฃo. Isso pode ou nรฃo afetar o desempenho. -Disable || Anรกlise de Jogadores Desativado. -Disable - Processing || Processando tarefas crรญticas nรฃo processadas anteriormente. (${0}) -Disable - Processing Complete || Processamento completo. -Disable - Unsaved Session Save || Saving unfinished sessions.. -Disable - Unsaved Session Save Timeout || Timeout hit, storing the unfinished sessions on next enable instead. -Disable - Waiting SQLite || Waiting queries to finish to avoid SQLite crashing JVM.. -Disable - Waiting SQLite Complete || Closed SQLite connection. -Disable - Waiting Transactions || Waiting for unfinished transactions to avoid data loss.. -Disable - Waiting Transactions Complete || Transaction queue closed. -Disable - WebServer || O servidor web foi desativado. -Enable || Anรกlise de Jogadores Ativado. -Enable - Database || ${0}-conexรฃo com o banco de dados estabilizada. -Enable - Notify Bad IP || 0.0.0.0 is not a valid address, set up Alternative_IP settings. Incorrect links might be given! -Enable - Notify Empty IP || O IP no server.properties estรก vazio & o IP alternativo nรฃo estรก sendo usado. Os dados informados estรฃo incorretos! -Enable - Notify Geolocations disabled || A coleta de geolocalizaรงรฃo estรก desativada. (Data.Geolocations: false) -Enable - Notify Geolocations Internet Required || O Plan requer acesso ร  internet na primeira execuรงรฃo para baixar o banco de dados de geolocalizaรงรฃo do GeoLite2. -Enable - Notify Webserver disabled || O servidor web nรฃo foi inicializado. (WebServer.DisableWebServer: true) -Enable - Storing preserved sessions || Storing sessions that were preserved before previous shutdown. -Enable - WebServer || O servidor web estรก rodando na PORTA ${0} ( ${1} ) -Enable FAIL - Database || ${0}-Falha na Conexรฃo do Banco de Dados: ${1} -Enable FAIL - Database Patch || O patch do banco de dados falhou, o plugin teve que ser desativado. Reporte esse problema no GitHub ou Discord para suporte. -Enable FAIL - GeoDB Write || Algo deu errado ao salvar o banco de dados de geolocalizaรงรฃo do GeoLite2 -Enable FAIL - WebServer (Proxy) || O servidor web nรฃo pode ser inicializado! -Enable FAIL - Wrong Database Type || ${0} nรฃo รฉ um banco de dados suportado -HTML - AND_BUG_REPORTERS || & Bug reporters! -HTML - BANNED (Filters) || Banned -HTML - COMPARING_15_DAYS || Comparing 15 days -HTML - COMPARING_60_DAYS || Comparing 30d ago to Now -HTML - COMPARING_7_DAYS || Comparing 7 days -HTML - DATABASE_NOT_OPEN || Database is not open, check db status with /plan info -HTML - DESCRIBE_RETENTION_PREDICTION || This value is a prediction based on previous players. -HTML - ERROR || Falha ao autenticar -HTML - EXPIRED_COOKIE || User cookie has expired -HTML - FILTER_ACTIVITY_INDEX_NOW || Current activity group -HTML - FILTER_ALL_PLAYERS || All players -HTML - FILTER_BANNED || Ban status -HTML - FILTER_GROUP || Group: -HTML - FILTER_OPS || Operator status -HTML - INDEX_ACTIVE || Ativo -HTML - INDEX_INACTIVE || Inativo -HTML - INDEX_IRREGULAR || Irregular -HTML - INDEX_REGULAR || Regular -HTML - INDEX_VERY_ACTIVE || Muito Ativo -HTML - KILLED || Assassinou -HTML - LABEL_1ST_WEAPON || Deadliest PvP Weapon -HTML - LABEL_2ND_WEAPON || 2nd PvP Weapon -HTML - LABEL_3RD_WEAPON || 3rd PvP Weapon -HTML - LABEL_ACTIVE_PLAYTIME || Active Playtime -HTML - LABEL_ACTIVITY_INDEX || รndice de Atividade -HTML - LABEL_AFK || AFK -HTML - LABEL_AFK_TIME || AFK Time -HTML - LABEL_AVG || Average -HTML - LABEL_AVG_ACTIVE_PLAYTIME || Average Active Playtime -HTML - LABEL_AVG_AFK_TIME || Average AFK Time -HTML - LABEL_AVG_CHUNKS || Average Chunks -HTML - LABEL_AVG_ENTITIES || Average Entities -HTML - LABEL_AVG_KDR || Average KDR -HTML - LABEL_AVG_MOB_KDR || Average Mob KDR -HTML - LABEL_AVG_PLAYTIME || Average Playtime -HTML - LABEL_AVG_SESSION_LENGTH || Average Session Length -HTML - LABEL_AVG_SESSIONS || Average Sessions -HTML - LABEL_AVG_TPS || Average TPS -HTML - LABEL_BANNED || Banido -HTML - LABEL_BEST_PEAK || Pico Mรกximo -HTML - LABEL_DAY_OF_WEEK || Day of the Week -HTML - LABEL_DEATHS || Mortes -HTML - LABEL_DOWNTIME || Downtime -HTML - LABEL_DURING_LOW_TPS || During Low TPS Spikes: -HTML - LABEL_ENTITIES || Entidades -HTML - LABEL_FAVORITE_SERVER || Servidor Favorito -HTML - LABEL_FIRST_SESSION_LENGTH || First session length -HTML - LABEL_FREE_DISK_SPACE || Espaรงo de Disco Livre -HTML - LABEL_INACTIVE || Inactive -HTML - LABEL_LAST_PEAK || รšltimo Pico -HTML - LABEL_LAST_SEEN || รšltima Vez Visto -HTML - LABEL_LOADED_CHUNKS || Chunks Carregados -HTML - LABEL_LOADED_ENTITIES || Entidades Carregadas -HTML - LABEL_LONE_JOINS || Lone joins -HTML - LABEL_LONE_NEW_JOINS || Lone newbie joins -HTML - LABEL_LONGEST_SESSION || Longest Session -HTML - LABEL_LOW_TPS || Low TPS Spikes -HTML - LABEL_MAX_FREE_DISK || Max Free Disk -HTML - LABEL_MIN_FREE_DISK || Min Free Disk -HTML - LABEL_MOB_DEATHS || Mortes causadas por Mobs -HTML - LABEL_MOB_KDR || KDR por Mob -HTML - LABEL_MOB_KILLS || Assassinato de Mobs -HTML - LABEL_MOST_ACTIVE_GAMEMODE || Most Active Gamemode -HTML - LABEL_NAME || Nome -HTML - LABEL_NEW || New -HTML - LABEL_NEW_PLAYERS || Novos Jogadores -HTML - LABEL_NICKNAME || Nick -HTML - LABEL_NO_SESSION_KILLS || None -HTML - LABEL_ONLINE_FIRST_JOIN || Players online on first join -HTML - LABEL_OPERATOR || Operador -HTML - LABEL_PER_PLAYER || / Player -HTML - LABEL_PER_REGULAR_PLAYER || / Regular Player -HTML - LABEL_PLAYER_DEATHS || Mortes causadas por Jogadores -HTML - LABEL_PLAYER_KILLS || Assassinato -HTML - LABEL_PLAYERS_ONLINE || Jogadores Online -HTML - LABEL_PLAYTIME || Tempo de Jogo -HTML - LABEL_REGISTERED || Registrados -HTML - LABEL_REGISTERED_PLAYERS || Registered Players -HTML - LABEL_REGULAR || Regular -HTML - LABEL_REGULAR_PLAYERS || Regular Players -HTML - LABEL_RELATIVE_JOIN_ACTIVITY || Relative Join Activity -HTML - LABEL_RETENTION || Retenรงรฃo de Novos Jogadores -HTML - LABEL_SERVER_DOWNTIME || Server Downtime -HTML - LABEL_SERVER_OCCUPIED || Server occupied -HTML - LABEL_SESSION_ENDED || Sessรตes Finalizadas -HTML - LABEL_SESSION_MEDIAN || Mรฉdia de Sessรตes -HTML - LABEL_TIMES_KICKED || Vezes Kickado -HTML - LABEL_TOTAL_PLAYERS || Total Players -HTML - LABEL_TOTAL_PLAYTIME || Total Playtime -HTML - LABEL_UNIQUE_PLAYERS || Jogadores รšnicos -HTML - LABEL_WEEK_DAYS || 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' -HTML - LINK_BACK_NETWORK || Network page -HTML - LINK_BACK_SERVER || Server page -HTML - LINK_CHANGELOG || View Changelog -HTML - LINK_DISCORD || General Support on Discord -HTML - LINK_DOWNLOAD || Download -HTML - LINK_ISSUES || Report Issues -HTML - LINK_NIGHT_MODE || Night Mode -HTML - LINK_PLAYER_PAGE || Player Page -HTML - LINK_QUICK_VIEW || Quick view -HTML - LINK_SERVER_ANALYSIS || Anรกlise do Servidor -HTML - LINK_WIKI || Plan Wiki, Tutorials & Documentation -HTML - LOCAL_MACHINE || Mรกquina Local -HTML - LOGIN_CREATE_ACCOUNT || Create an Account! -HTML - LOGIN_FAILED || Login failed: -HTML - LOGIN_FORGOT_PASSWORD || Forgot Password? -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_1 || Forgot password? Unregister and register again. -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_2 || Use the following command in game to remove your current user: -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_3 || Or using console: -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_4 || After using the command, -HTML - LOGIN_LOGIN || Login -HTML - LOGIN_LOGOUT || Logout -HTML - LOGIN_PASSWORD || "Password" -HTML - LOGIN_USERNAME || "Username" -HTML - NAV_PLUGINS || Plugins -HTML - NEW_CALENDAR || Novo: -HTML - NO_KILLS || Sem Kills -HTML - NO_USER_PRESENT || User cookie not present -HTML - NON_OPERATORS (Filters) || Non operators -HTML - NOT_BANNED (Filters) || Not banned -HTML - OFFLINE || Offline -HTML - ONLINE || Online -HTML - OPERATORS (Filters) || Operators -HTML - PER_DAY || / Dia -HTML - PLAYERS_TEXT || Jogadores -HTML - QUERY || Query< -HTML - QUERY_ACTIVITY_OF_MATCHED_PLAYERS || Activity of matched players -HTML - QUERY_ACTIVITY_ON || Activity on -HTML - QUERY_ADD_FILTER || Add a filter.. -HTML - QUERY_AND || and -HTML - QUERY_ARE || `are` -HTML - QUERY_ARE_ACTIVITY_GROUP || are in Activity Groups -HTML - QUERY_ARE_PLUGIN_GROUP || are in ${plugin}'s ${group} Groups -HTML - QUERY_JOINED_WITH_ADDRESS || joined with address -HTML - QUERY_LOADING_FILTERS || Loading filters.. -HTML - QUERY_MAKE || Make a query -HTML - QUERY_MAKE_ANOTHER || Make another query -HTML - QUERY_OF_PLAYERS || of Players who -HTML - QUERY_PERFORM_QUERY || Perform Query! -HTML - QUERY_PLAYED_BETWEEN || Played between -HTML - QUERY_REGISTERED_BETWEEN || Registered between -HTML - QUERY_RESULTS || Query Results -HTML - QUERY_RESULTS_MATCH || matched ${resultCount} players -HTML - QUERY_SESSIONS_WITHIN_VIEW || Sessions within view -HTML - QUERY_SHOW_VIEW || Show a view -HTML - QUERY_TIME_FROM || >from -HTML - QUERY_TIME_TO || >to -HTML - QUERY_VIEW || View: -HTML - QUERY_ZERO_RESULTS || Query produced 0 results -HTML - REGISTER || Register -HTML - REGISTER_CHECK_FAILED || Checking registration status failed: -HTML - REGISTER_COMPLETE || Complete Registration -HTML - REGISTER_COMPLETE_INSTRUCTIONS_1 || You can now finish registering the user. -HTML - REGISTER_COMPLETE_INSTRUCTIONS_2 || Code expires in 15 minutes -HTML - REGISTER_COMPLETE_INSTRUCTIONS_3 || Use the following command in game to finish registration: -HTML - REGISTER_COMPLETE_INSTRUCTIONS_4 || Or using console: -HTML - REGISTER_CREATE_USER || Create a new user -HTML - REGISTER_FAILED || Registration failed: -HTML - REGISTER_HAVE_ACCOUNT || Already have an account? Login! -HTML - REGISTER_PASSWORD_TIP || Password should be more than 8 characters, but there are no limitations. -HTML - REGISTER_SPECIFY_PASSWORD || You need to specify a Password -HTML - REGISTER_SPECIFY_USERNAME || You need to specify a Username -HTML - REGISTER_USERNAME_LENGTH || Username can be up to 50 characters, yours is -HTML - REGISTER_USERNAME_TIP || Username can be up to 50 characters. -HTML - SESSION || Sessรฃo -HTML - SIDE_GEOLOCATIONS || Geolocalizaรงรตes -HTML - SIDE_INFORMATION || INFORMATION -HTML - SIDE_LINKS || LINKS -HTML - SIDE_NETWORK_OVERVIEW || Network Overview -HTML - SIDE_OVERVIEW || Visรฃo Global -HTML - SIDE_PERFORMANCE || Desempenho -HTML - SIDE_PLAYER_LIST || Lista de Jogadores -HTML - SIDE_PLAYERBASE || Playerbase -HTML - SIDE_PLAYERBASE_OVERVIEW || Playerbase Overview -HTML - SIDE_PLUGINS || PLUGINS -HTML - SIDE_PVP_PVE || PvP & PvE -HTML - SIDE_SERVERS || Servidores -HTML - SIDE_SERVERS_TITLE || SERVERS -HTML - SIDE_SESSIONS || Sessรตes -HTML - SIDE_TO_MAIN_PAGE || to main page -HTML - TEXT_CLICK_TO_EXPAND || Click to expand -HTML - TEXT_CONTRIBUTORS_CODE || code contributor -HTML - TEXT_CONTRIBUTORS_LOCALE || translator -HTML - TEXT_CONTRIBUTORS_MONEY || Extra special thanks to those who have monetarily supported the development. -HTML - TEXT_CONTRIBUTORS_THANKS || In addition following awesome people have contributed: -HTML - TEXT_DEV_VERSION || This version is a DEV release. -HTML - TEXT_DEVELOPED_BY || is developed by -HTML - TEXT_FIRST_SESSION || First session -HTML - TEXT_LICENSED_UNDER || Player Analytics is developed and licensed under -HTML - TEXT_METRICS || bStats Metrics -HTML - TEXT_NO_EXTENSION_DATA || No Extension Data -HTML - TEXT_NO_LOW_TPS || No low tps spikes -HTML - TEXT_NO_SERVER || No server to display online activity for -HTML - TEXT_NO_SERVERS || No servers found in the database -HTML - TEXT_PLUGIN_INFORMATION || Information about the plugin -HTML - TEXT_PREDICTED_RETENTION || This value is a prediction based on previous players -HTML - TEXT_SERVER_INSTRUCTIONS || It appears that Plan is not installed on any game servers or not connected to the same database. See wiki for Network tutorial. -HTML - TEXT_VERSION || A new version has been released and is now available for download. -HTML - TITLE_30_DAYS || 30 days -HTML - TITLE_30_DAYS_AGO || 30 days ago -HTML - TITLE_ALL || Todos -HTML - TITLE_ALL_TIME || All Time -HTML - TITLE_AS_NUMBERS || as Numbers -HTML - TITLE_AVG_PING || Average Ping -HTML - TITLE_BEST_PING || Best Ping -HTML - TITLE_CALENDAR || Calendรกrio -HTML - TITLE_CONNECTION_INFO || Connection Information -HTML - TITLE_COUNTRY || Paรญs -HTML - TITLE_CPU_RAM || CPU & RAM -HTML - TITLE_CURRENT_PLAYERBASE || Base de Jogadores Atual -HTML - TITLE_DISK || Espaรงo de disco -HTML - TITLE_GRAPH_DAY_BY_DAY || Day by Day -HTML - TITLE_GRAPH_HOUR_BY_HOUR || Hour by Hour -HTML - TITLE_GRAPH_NETWORK_ONLINE_ACTIVITY || Network Online Activity -HTML - TITLE_GRAPH_PUNCHCARD || Cartรฃo Perfurado for 30 days -HTML - TITLE_INSIGHTS || Insights for 30 days -HTML - TITLE_IS_AVAILABLE || is Available -HTML - TITLE_JOIN_ADDRESSES || Join Addresses -HTML - TITLE_LAST_24_HOURS || รšltimas 24 horas -HTML - TITLE_LAST_30_DAYS || รšltimos 30 dias -HTML - TITLE_LAST_7_DAYS || รšltimos 7 dias -HTML - TITLE_LAST_CONNECTED || รšltima Conexรฃo -HTML - TITLE_LENGTH || Length -HTML - TITLE_MOST_PLAYED_WORLD || Most played World -HTML - TITLE_NETWORK || Network -HTML - TITLE_NETWORK_AS_NUMBERS || Network as Numbers -HTML - TITLE_NOW || Now -HTML - TITLE_ONLINE_ACTIVITY || Online Activity -HTML - TITLE_ONLINE_ACTIVITY_AS_NUMBERS || Online Activity as Numbers -HTML - TITLE_ONLINE_ACTIVITY_OVERVIEW || Online Activity Overview -HTML - TITLE_PERFORMANCE_AS_NUMBERS || Performance as Numbers -HTML - TITLE_PING || Ping -HTML - TITLE_PLAYER || Player -HTML - TITLE_PLAYER_OVERVIEW || Player Overview -HTML - TITLE_PLAYERBASE_DEVELOPMENT || Desenvolvimento da base de Jogadores -HTML - TITLE_PVP_DEATHS || Recent PvP Deaths -HTML - TITLE_PVP_KILLS || Recent PvP Kills -HTML - TITLE_PVP_PVE_NUMBERS || PvP & PvE as Numbers -HTML - TITLE_RECENT_KILLS || Recent Kills -HTML - TITLE_RECENT_SESSIONS || Sessรตes Recentes -HTML - TITLE_SEEN_NICKNAMES || Nicks Vistos -HTML - TITLE_SERVER || Servidor -HTML - TITLE_SERVER_AS_NUMBERS || Server as Numbers -HTML - TITLE_SERVER_OVERVIEW || Server Overview -HTML - TITLE_SERVER_PLAYTIME || Server Playtime -HTML - TITLE_SERVER_PLAYTIME_30 || Server Playtime for 30 days -HTML - TITLE_SESSION_START || Session Started -HTML - TITLE_THEME_SELECT || Theme Select -HTML - TITLE_TITLE_PLAYER_PUNCHCARD || Punchcard -HTML - TITLE_TPS || TPS -HTML - TITLE_TREND || Trend -HTML - TITLE_TRENDS || Trends for 30 days -HTML - TITLE_VERSION || Version -HTML - TITLE_WEEK_COMPARISON || Week Comparison -HTML - TITLE_WORLD || World Load -HTML - TITLE_WORLD_PLAYTIME || Tempo de Jogo por Mundo -HTML - TITLE_WORST_PING || Worst Ping -HTML - TOTAL_ACTIVE_TEXT || Tempo Total Ativo -HTML - TOTAL_AFK || Tempo Total AFK -HTML - TOTAL_PLAYERS || Total de Jogadores -HTML - UNIQUE_CALENDAR || รšnicos: -HTML - UNIT_CHUNKS || Chunks -HTML - UNIT_ENTITIES || Entities -HTML - UNIT_NO_DATA || No Data -HTML - UNIT_THE_PLAYERS || Players -HTML - USER_AND_PASS_NOT_SPECIFIED || Usuรกrio e Senha nรฃo especรญficado -HTML - USER_DOES_NOT_EXIST || Usuรกrio nรฃo existe -HTML - USER_INFORMATION_NOT_FOUND || Registration failed, try again (The code expires after 15 minutes) -HTML - USER_PASS_MISMATCH || Usuรกrio e Senha nรฃo coincidem -HTML - Version Change log || View Changelog -HTML - Version Current || You have version ${0} -HTML - Version Download || Download Plan-${0}.jar -HTML - Version Update || Update -HTML - Version Update Available || Version ${0} is Available! -HTML - Version Update Dev || This version is a DEV release. -HTML - Version Update Info || A new version has been released and is now available for download. -HTML - WARNING_NO_GAME_SERVERS || Some data requires Plan to be installed on game servers. -HTML - WARNING_NO_GEOLOCATIONS || Geolocation gathering needs to be enabled in the config (Accept GeoLite2 EULA). -HTML - WARNING_NO_SPONGE_CHUNKS || Chunks unavailable on Sponge -HTML - WITH || Com -HTML ERRORS - ACCESS_DENIED_403 || Acesso Negado -HTML ERRORS - AUTH_FAIL_TIPS_401 || - Certifique-se de ter registrado um usuรกrio com /plan register
- Verifique se o nome de usuรกrio e a senha estรฃo corretos
- O nome de usuรกrio e senha fazem distinรงรฃo entre maiรบsculas e minรบsculas, verifique se escreveu corretamente

Se vocรช esqueceu sua senha, peรงa para um staff que exclua seu antigo usuรกrio e registre um novo. -HTML ERRORS - AUTHENTICATION_FAILED_401 || Falha na Autenticaรงรฃo. -HTML ERRORS - FORBIDDEN_403 || Proibido -HTML ERRORS - NO_SERVERS_404 || Nenhum servidor online para executar a solicitaรงรฃo. -HTML ERRORS - NOT_FOUND_404 || Nรฃo Encontrado -HTML ERRORS - NOT_PLAYED_404 || Esse jogador nรฃo jogou nesse servidor. -HTML ERRORS - PAGE_NOT_FOUND_404 || Pรกgina nรฃo existe. -HTML ERRORS - UNAUTHORIZED_401 || Acesso nรฃo autorizado -HTML ERRORS - UNKNOWN_PAGE_404 || Certifique-se de que vocรช estรก acessando um link fornecido por comando, exemplos:

/player/{uuid/nome}
/server/{uuid/nome/id}

-HTML ERRORS - UUID_404 || UUID de jogador nรฃo encontrado no banco de dados. -In Depth Help - /plan db || Use different database subcommands to change the data in some way -In Depth Help - /plan db backup || Uses SQLite to backup the target database to a file. -In Depth Help - /plan db clear || Clears all Plan tables, removing all Plan-data in the process. -In Depth Help - /plan db hotswap || Reloads the plugin with the other database and changes the config to match. -In Depth Help - /plan db move || Overwrites contents in the other database with the contents in another. -In Depth Help - /plan db remove || Removes all data linked to a player from the Current database. -In Depth Help - /plan db restore || Uses SQLite backup file and overwrites contents of the target database. -In Depth Help - /plan db uninstalled || Marks a server in Plan database as uninstalled so that it will not show up in server queries. -In Depth Help - /plan disable || Disable the plugin or part of it until next reload/restart. -In Depth Help - /plan export || Performs an export to export location defined in the config. -In Depth Help - /plan import || Performs an import to load data into the database. -In Depth Help - /plan info || Display the current status of the plugin. -In Depth Help - /plan ingame || Mostra algumas informaรงรตes sobre o jogador in-game. -In Depth Help - /plan json || Allows you to download a player's data in json format. All of it. -In Depth Help - /plan logout || Give username argument to log out another user from the panel, give * as argument to log out everyone. -In Depth Help - /plan network || Obtain a link to the /network page, only does so on networks. -In Depth Help - /plan player || Obtain a link to the /player page of a specific player, or the current player. -In Depth Help - /plan players || Obtain a link to the /players page to see a list of players. -In Depth Help - /plan register || Use without arguments to get link to register page. Use --code [code] after registration to get a user. -In Depth Help - /plan reload || Disable and enable the plugin to reload any changes in config. -In Depth Help - /plan search || List all matching player names to given part of a name. -In Depth Help - /plan server || Obtain a link to the /server page of a specific server, or the current server if no arguments are given. -In Depth Help - /plan servers || List ids, names and uuids of servers in the database. -In Depth Help - /plan unregister || Use without arguments to unregister player linked user, or with username argument to unregister another user. -In Depth Help - /plan users || Lists web users as a table. -Manage - Confirm Overwrite || Dados em ${0} serรฃo sobrescritos! -Manage - Confirm Removal || Dados em ${0} serรฃo removidos! -Manage - Fail || > ยงcAlguma coisa deu errado: ${0} -Manage - Fail File not found || > ยงcNรฃo foi encontrado um arquivo em ${0} -Manage - Fail Incorrect Database || > ยงc'${0}' nรฃo รฉ um banco de dados suportado. -Manage - Fail No Exporter || ยงeExporter '${0}' doesn't exist -Manage - Fail No Importer || ยงeImportador '${0}' nรฃo existe -Manage - Fail No Server || No server found with given parameters. -Manage - Fail Same Database || > ยงcNรฃo รฉ possรญvel operar do mesmo banco de dados! -Manage - Fail Same server || Can not mark this server as uninstalled (You are on it) -Manage - Fail, Confirmation || > ยงcAdicione o argumento '-a' para confirmar a execuรงรฃo: ${0} -Manage - List Importers || Importadores: -Manage - Progress || ${0} / ${1} processed.. -Manage - Remind HotSwap || ยงeLembre-se de trocar para o novo banco de dados (/plan db hotswap ${0}) & reinicie o plugin. -Manage - Start || > ยง2Processando dados.. -Manage - Success || > ยงaSucesso! -Negative || Nรฃo -Positive || Sim -Today || 'Hoje' -Unavailable || Unavailable -Unknown || Desconhecido -Version - DEV || Essa รฉ uma versรฃo em desenvolvimento. -Version - Latest || Vocรช estรก usando a รบltima versรฃo. -Version - New || Nova Versรฃo (${0}) estรก disponรญvel ${1} -Version - New (old) || Nova Versรฃo estรก disponรญvel em ${0} -Version FAIL - Read info (old) || Falha ao verificar disponibilidade de atualizaรงรฃo -Version FAIL - Read versions.txt || Informaรงรฃo da versรฃo nรฃo pode ser carregada de Github/versions.txt -Web User Listing || ยง2${0} ยง7: ยงf${1} -WebServer - Notify HTTP || Servidor Web: Sem Certificado -> Usando protocolo HTTP para visualizaรงรฃo. -WebServer - Notify HTTP User Auth || Servidor Web: Autenticaรงรฃo de usuรกrio desativada! (Nรฃo seguro por HTTP) -WebServer - Notify HTTPS User Auth || WebServer: User Authorization Disabled! (Disabled in config) -Webserver - Notify IP Whitelist || Webserver: IP Whitelist is enabled. -Webserver - Notify IP Whitelist Block || Webserver: ${0} was denied access to '${1}'. (not whitelisted) -WebServer - Notify no Cert file || Servidor Web: Arquivo KeyStore nรฃo encontrado: ${0} -WebServer - Notify Using Proxy || WebServer: Proxy-mode HTTPS enabled, make sure that your reverse-proxy is routing using HTTPS and Plan Alternative_IP.Address points to the Proxy -WebServer FAIL - EOF || WebServer: EOF when reading Certificate file. (Check that the file is not empty) -WebServer FAIL - Port Bind || O servidor web nรฃo foi inicializado. A porta (${0}) jรก estรก em uso? -WebServer FAIL - SSL Context || Servidor Web: Falha ao inicializar certificado SSL. -WebServer FAIL - Store Load || Servidor Web: Falha ao carregar certificado SSL. -Yesterday || 'Ontem' diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_PT_BR.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_PT_BR.yml new file mode 100644 index 000000000..7202f190e --- /dev/null +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_PT_BR.yml @@ -0,0 +1,669 @@ +403AccessDenied: "Acesso Negado" +command: + argument: + backupFile: + description: "Name of the backup file (case sensitive)" + name: "backup-file" + code: + description: "Code used to finalize registration." + name: "${code}" + dbBackup: + description: "Type of the database to backup. Current database is used if not specified." + dbRestore: + description: "Type of the database to restore to. Current database is used if not specified." + dbTypeHotswap: + description: "Type of the database to start using." + dbTypeMoveFrom: + description: "Type of the database to move data from." + dbTypeMoveTo: + description: "Type of the database to move data to. Can not be same as previous." + dbTypeRemove: + description: "Type of the database to remove all data from." + exportKind: "export kind" + feature: + description: "Name of the feature to disable: ${0}" + name: "feature" + importKind: "import kind" + nameOrUUID: + description: "Name or UUID of a player" + name: "name/uuid" + removeDescription: "Identifier for a player that will be removed from current database." + server: + description: "Name, ID or UUID of a server" + name: "server" + subcommand: + description: "Use the command without subcommand to see help." + name: "subcommand" + username: + description: "Username of another user. If not specified player linked user is used." + name: "username" + confirmation: + accept: "Accept" + cancelNoChanges: "Cancelled. No data was changed." + cancelNoUnregister: "Cancelled. '${0}' was not unregistered" + confirm: "Confirm: " + dbClear: "You are about to remove all Plan-data in ${0}" + dbOverwrite: "You are about to overwrite data in Plan ${0} with data in ${1}" + dbRemovePlayer: "You are about to remove data of ${0} from ${1}" + deny: "Cancel" + expired: "Confirmation expired, use the command again" + unregister: "You are about to unregister '${0}' linked to ${1}" + database: + creatingBackup: "Creating a backup file '${0}.db' with contents of ${1}" + failDbNotOpen: "ยงcDatabase is ${0} - Please try again a bit later." + manage: + confirm: "> ยงcAdicione o argumento '-a' para confirmar a execuรงรฃo: ${0}" + confirmOverwrite: "Dados em ${0} serรฃo sobrescritos!" + confirmPartialRemoval: "Join Address Data for Server ${0} in ${1} will be removed!" + confirmRemoval: "Dados em ${0} serรฃo removidos!" + fail: "> ยงcAlguma coisa deu errado: ${0}" + failFileNotFound: "> ยงcNรฃo foi encontrado um arquivo em ${0}" + failIncorrectDB: "> ยงc'${0}' nรฃo รฉ um banco de dados suportado." + failNoServer: "No server found with given parameters." + failSameDB: "> ยงcNรฃo รฉ possรญvel operar do mesmo banco de dados!" + failSameServer: "Can not mark this server as uninstalled (You are on it)" + hotswap: "ยงeLembre-se de trocar para o novo banco de dados (/plan db hotswap ${0}) & reinicie o plugin." + importers: "Importadores:" + preparing: "Preparing.." + progress: "${0} / ${1} processed.." + start: "> ยง2Processando dados.." + success: "> ยงaSucesso!" + playerRemoval: "Removing data of ${0} from ${1}.." + removal: "Removing Plan-data from ${0}.." + serverUninstalled: "ยงaIf the server is still installed, it will automatically set itself as installed in the database." + unregister: "Unregistering '${0}'.." + warnDbNotOpen: "ยงeDatabase is ${0} - This might take longer than expected.." + write: "Writing to ${0}.." + fail: + emptyString: "The search string can not be empty" + invalidArguments: "Accepts following as ${0}: ${1}" + invalidUsername: "ยงcEsse usuรกrio nรฃo tem uma UUID." + missingArguments: "ยงcArgumentos necessรกrios (${0}) ${1}" + missingFeature: "ยงeDefina um recurso para desativar! (atualmente suporta ${0})" + missingLink: "User is not linked to your account and you don't have permission to remove other user's accounts." + noPermission: "ยงcVocรช nรฃo tem a permissรฃo necessรกria." + onAccept: "The accepted action errored upon execution: ${0}" + onDeny: "The denied action errored upon execution: ${0}" + playerNotFound: "Player '${0}' was not found, they have no UUID." + playerNotInDatabase: "Player '${0}' was not found in the database." + seeConfig: "see '${0}' in config.yml" + serverNotFound: "Server '${0}' was not found from the database." + tooManyArguments: "ยงcรšnico argumento necessรกrio ${1}" + unknownUsername: "ยงcEsse usuรกrio nรฃo foi encontrado nesse servidor" + webUserExists: "ยงcEsse usuรกrio jรก existe!" + webUserNotFound: "ยงcEsse usuรกrio nรฃo existe!" + footer: + help: "ยง7Hover over command or arguments or use '/${0} ?' to learn more about them." + general: + failNoExporter: "ยงeExporter '${0}' doesn't exist" + failNoImporter: "ยงeImportador '${0}' nรฃo existe" + featureDisabled: "ยงaDesativado '${0}' temporariamente atรฉ o prรณximo reload do plugin." + noAddress: "ยงeNo address was available - using localhost as fallback. Set up 'Alternative_IP' settings." + noWebuser: "Vocรช nรฃo tem um usuรกrio para web, utilize /plan register " + notifyWebUserRegister: "Novo usuรกrio registrado: '${0}' Nรญvel de permissรฃo: ${1}" + pluginDisabled: "ยงaO sistema do Plan agora estรก desativado. Vocรช pode usar reload para reiniciar o plugin." + reloadComplete: "ยงaReload Realizado" + reloadFailed: "ยงcAlguma coisa ocorreu ao recarregar o plugin, um restart รฉ recomendรกvel." + successWebUserRegister: "ยงaFoi adicionado um novo usuรกrio (${0})!" + webPermissionLevels: ">\ยง70: Acesse todas as pรกginas\ยง71: Acesse '/players' e todas as pรกginas de jogadores\ยง72: Acesse a pรกgina de jogado com o mesmo usuรกrio do login\ยง73+: Sem permissรตes" + webUserList: " ยง2${0} ยง7: ยงf${1}" + header: + analysis: "> ยง2Resultados da Anรกlise" + help: "> ยง2/${0} Help" + info: "> ยง2Anรกlise do Jogador" + inspect: "> ยง2Jogador: ยงf${0}" + network: "> ยง2Pรกgina da Network" + players: "> ยง2Jogadores" + search: "> ยง2${0} Resultados para ยงf${1}ยง2:" + serverList: "id::name::uuid::version" + servers: "> ยง2Servidores" + webUserList: "username::linked to::permission level" + webUsers: "> ยง2${0} Usuรกrios da Web" + help: + database: + description: "Manage Plan database" + inDepth: "Use different database subcommands to change the data in some way" + dbBackup: + description: "Backup data of a database to a file" + inDepth: "Uses SQLite to backup the target database to a file." + dbClear: + description: "Remove ALL Plan data from a database" + inDepth: "Clears all Plan tables, removing all Plan-data in the process." + dbHotswap: + description: "Alterar o banco de dados rapidamente" + inDepth: "Reloads the plugin with the other database and changes the config to match." + dbMove: + description: "Mover dados entre banco de dados" + inDepth: "Overwrites contents in the other database with the contents in another." + dbRemove: + description: "Remove player's data from Current database" + inDepth: "Removes all data linked to a player from the Current database." + dbRestore: + description: "Restore data from a file to a database" + inDepth: "Uses SQLite backup file and overwrites contents of the target database." + dbUninstalled: + description: "Set a server as uninstalled in the database." + inDepth: "Marks a server in Plan database as uninstalled so that it will not show up in server queries." + disable: + description: "Disable the plugin or part of it" + inDepth: "Disable the plugin or part of it until next reload/restart." + export: + description: "Export html or json files manually" + inDepth: "Performs an export to export location defined in the config." + import: + description: "Import data" + inDepth: "Performs an import to load data into the database." + info: + description: "Information about the plugin" + inDepth: "Display the current status of the plugin." + ingame: + description: "Visualziar informaรงรตes do Jogador in-game" + inDepth: "Mostra algumas informaรงรตes sobre o jogador in-game." + json: + description: "View json of Player's raw data." + inDepth: "Allows you to download a player's data in json format. All of it." + logout: + description: "Log out other users from the panel." + inDepth: "Give username argument to log out another user from the panel, give * as argument to log out everyone." + migrateToOnlineUuids: + description: "Migrate offline uuid data to online uuids" + network: + description: "Visualizar a pรกgina da Network" + inDepth: "Obtain a link to the /network page, only does so on networks." + player: + description: "Visualizar a pรกgina do jogador" + inDepth: "Obtain a link to the /player page of a specific player, or the current player." + players: + description: "Visualizar a pรกgina de Jogadores" + inDepth: "Obtain a link to the /players page to see a list of players." + register: + description: "Registrar um usuรกrio web" + inDepth: "Use without arguments to get link to register page. Use --code [code] after registration to get a user." + reload: + description: "Reiniciar Plan" + inDepth: "Disable and enable the plugin to reload any changes in config." + removejoinaddresses: + description: "Remove join addresses of a specified server" + search: + description: "Buscar por um nome de jogador" + inDepth: "List all matching player names to given part of a name." + server: + description: "Visualizar a pรกgina do servidor" + inDepth: "Obtain a link to the /server page of a specific server, or the current server if no arguments are given." + servers: + description: "Listar servidores do banco de dados" + inDepth: "List ids, names and uuids of servers in the database." + unregister: + description: "Unregister a user of Plan website" + inDepth: "Use without arguments to unregister player linked user, or with username argument to unregister another user." + users: + description: "List all web users" + inDepth: "Lists web users as a table." + ingame: + activePlaytime: " ยง2Active Playtime: ยงf${0}" + activityIndex: " ยง2รndice de Atividade: ยงf${0} | ${1}" + afkPlaytime: " ยง2AFK Time: ยงf${0}" + deaths: " ยง2Mortes: ยงf${0}" + geolocation: " ยง2Conectado de: ยงf${0}" + lastSeen: " ยง2รšltima vez visto: ยงf${0}" + longestSession: " ยง2Sessรฃo mais longa: ยงf${0}" + mobKills: " ยง2Assassinato de Mobs: ยงf${0}" + playerKills: " ยง2Assassinato de Jogadores: ยงf${0}" + playtime: " ยง2Tempo de Jogo: ยงf${0}" + registered: " ยง2Registered: ยงf${0}" + timesKicked: " ยง2Vezes Kickado: ยงf${0}" + link: + clickMe: "Clique aqui" + link: "Link" + network: "Network page: " + noNetwork: "Server is not connected to a network. The link redirects to server page." + player: "Player page: " + playerJson: "Player json: " + players: "Players page: " + register: "Register page: " + server: "Server page: " + subcommand: + info: + database: " ยง2Banco de dados atual: ยงf${0}" + proxy: " ยง2Conectados ao Bungee: ยงf${0}" + update: " ยง2Atualizaรงรฃo Disponรญvel: ยงf${0}" + version: " ยง2Versรฃo: ยงf${0}" +generic: + noData: "No Data" +html: + button: + nightMode: "Night Mode" + calendar: + new: "Novo:" + unique: "รšnicos:" + description: + newPlayerRetention: "This value is a prediction based on previous players." + noGameServers: "Some data requires Plan to be installed on game servers." + noGeolocations: "Geolocation gathering needs to be enabled in the config (Accept GeoLite2 EULA)." + noServerOnlinActivity: "No server to display online activity for" + noServers: "No servers found in the database" + noServersLong: 'It appears that Plan is not installed on any game servers or not connected to the same database. See wiki for Network tutorial.' + noSpongeChunks: "Chunks unavailable on Sponge" + predictedNewPlayerRetention: "This value is a prediction based on previous players" + error: + 401Unauthorized: "Acesso nรฃo autorizado" + 403Forbidden: "Proibido" + 404NotFound: "Nรฃo Encontrado" + 404PageNotFound: "Pรกgina nรฃo existe." + 404UnknownPage: "Certifique-se de que vocรช estรก acessando um link fornecido por comando, exemplos:

/player/{uuid/nome}
/server/{uuid/nome/id}

" + UUIDNotFound: "UUID de jogador nรฃo encontrado no banco de dados." + auth: + dbClosed: "Database is not open, check db status with /plan info" + emptyForm: "Usuรกrio e Senha nรฃo especรญficado" + expiredCookie: "User cookie has expired" + generic: "Falha ao autenticar" + loginFailed: "Usuรกrio e Senha nรฃo coincidem" + noCookie: "User cookie not present" + registrationFailed: "Registration failed, try again (The code expires after 15 minutes)" + userNotFound: "Usuรกrio nรฃo existe" + authFailed: "Falha na Autenticaรงรฃo." + authFailedTips: "- Certifique-se de ter registrado um usuรกrio com /plan register
- Verifique se o nome de usuรกrio e a senha estรฃo corretos
- O nome de usuรกrio e senha fazem distinรงรฃo entre maiรบsculas e minรบsculas, verifique se escreveu corretamente

Se vocรช esqueceu sua senha, peรงa para um staff que exclua seu antigo usuรกrio e registre um novo." + noServersOnline: "Nenhum servidor online para executar a solicitaรงรฃo." + playerNotSeen: "Esse jogador nรฃo jogou nesse servidor." + serverNotSeen: "Server doesn't exist" + generic: + none: "None" + label: + active: "Ativo" + activePlaytime: "Active Playtime" + activityIndex: "รndice de Atividade" + afk: "AFK" + afkTime: "AFK Time" + all: "Todos" + allTime: "All Time" + alphabetical: "Alphabetical" + apply: "Apply" + asNumbers: "as Numbers" + average: "Average" + averageActivePlaytime: "Average Active Playtime" + averageAfkTime: "Average AFK Time" + averageChunks: "Average Chunks" + averageCpuUsage: "Average CPU Usage" + averageEntities: "Average Entities" + averageKdr: "Average KDR" + averageMobKdr: "Average Mob KDR" + averagePing: "Average Ping" + averagePlayers: "Average Players" + averagePlaytime: "Average Playtime" + averageRamUsage: "Average RAM Usage" + averageServerDowntime: "Average Downtime / Server" + averageSessionLength: "Average Session Length" + averageSessions: "Average Sessions" + averageTps: "Average TPS" + averageTps7days: "Average TPS (7 days)" + banned: "Banido" + bestPeak: "Pico Mรกximo" + bestPing: "Best Ping" + calendar: " Calendรกrio" + comparing7days: "Comparing 7 days" + connectionInfo: "Connection Information" + country: "Paรญs" + cpu: "CPU" + cpuRam: "CPU & RAM" + cpuUsage: "CPU Usage" + currentPlayerbase: "Base de Jogadores Atual" + currentUptime: "Current Uptime" + dayByDay: "Day by Day" + dayOfweek: "Day of the Week" + deadliestWeapon: "Deadliest PvP Weapon" + deaths: "Mortes" + disk: "Espaรงo de disco" + diskSpace: "Espaรงo de Disco Livre" + downtime: "Downtime" + duringLowTps: "During Low TPS Spikes:" + entities: "Entidades" + favoriteServer: "Servidor Favorito" + firstSession: "First session" + firstSessionLength: + average: "Average first session length" + median: "Median first session length" + geoProjection: + dropdown: "Select projection" + equalEarth: "Equal Earth" + mercator: "Mercator" + miller: "Miller" + ortographic: "Ortographic" + geolocations: "Geolocalizaรงรตes" + hourByHour: "Hour by Hour" + inactive: "Inactive" + indexInactive: "Inativo" + indexRegular: "Regular" + information: "INFORMATION" + insights: "Insights" + insights30days: "Insights for 30 days" + irregular: "Irregular" + joinAddress: "Join Address" + joinAddresses: "Join Addresses" + kdr: "KDR" + killed: "Assassinou" + last24hours: "รšltimas 24 horas" + last30days: "รšltimos 30 dias" + last7days: "รšltimos 7 dias" + lastConnected: "รšltima Conexรฃo" + lastPeak: "รšltimo Pico" + lastSeen: "รšltima Vez Visto" + latestJoinAddresses: "Latest Join Addresses" + length: " Length" + links: "LINKS" + loadedChunks: "Chunks Carregados" + loadedEntities: "Entidades Carregadas" + loneJoins: "Lone joins" + loneNewbieJoins: "Lone newbie joins" + longestSession: "Longest Session" + lowTpsSpikes: "Low TPS Spikes" + lowTpsSpikes7days: "Low TPS Spikes (7 days)" + maxFreeDisk: "Max Free Disk" + medianSessionLength: "Median Session Length" + minFreeDisk: "Min Free Disk" + mobDeaths: "Mortes causadas por Mobs" + mobKdr: "KDR por Mob" + mobKills: "Assassinato de Mobs" + mostActiveGamemode: "Most Active Gamemode" + mostPlayedWorld: "Most played World" + name: "Nome" + network: "Network" + networkAsNumbers: "Network as Numbers" + networkOnlineActivity: "Network Online Activity" + networkOverview: "Network Overview" + networkPage: "Network page" + new: "New" + newPlayerRetention: "Retenรงรฃo de Novos Jogadores" + newPlayers: "Novos Jogadores" + newPlayers7days: "New Players (7 days)" + nickname: "Nick" + noDataToDisplay: "No Data to Display" + now: "Now" + onlineActivity: "Online Activity" + onlineActivityAsNumbers: "Online Activity as Numbers" + onlineOnFirstJoin: "Players online on first join" + operator: "Operador" + overview: "Visรฃo Global" + perDay: "/ Dia" + perPlayer: "/ Player" + perRegularPlayer: "/ Regular Player" + performance: "Desempenho" + performanceAsNumbers: "Performance as Numbers" + ping: "Ping" + player: "Player" + playerDeaths: "Mortes causadas por Jogadores" + playerKills: "Assassinato" + playerList: "Lista de Jogadores" + playerOverview: "Player Overview" + playerPage: "Player Page" + playerRetention: "Player Retention" + playerbase: "Playerbase" + playerbaseDevelopment: "Desenvolvimento da base de Jogadores" + playerbaseOverview: "Playerbase Overview" + players: "Jogadores" + playersOnline: "Jogadores Online" + playersOnlineNow: "Players Online (Now)" + playersOnlineOverview: "Online Activity Overview" + playtime: "Tempo de Jogo" + plugins: "Plugins" + pluginsOverview: "Plugins Overview" + punchcard: "Punchcard" + punchcard30days: "Cartรฃo Perfurado for 30 days" + pvpPve: "PvP & PvE" + pvpPveAsNumbers: "PvP & PvE as Numbers" + query: "Make a query" + quickView: "Quick view" + ram: "RAM" + ramUsage: "RAM Usage" + recentKills: "Recent Kills" + recentPvpDeaths: "Recent PvP Deaths" + recentPvpKills: "Recent PvP Kills" + recentSessions: "Sessรตes Recentes" + registered: "Registrados" + registeredPlayers: "Registered Players" + regular: "Regular" + regularPlayers: "Regular Players" + relativeJoinActivity: "Relative Join Activity" + secondDeadliestWeapon: "2nd PvP Weapon" + seenNicknames: "Nicks Vistos" + server: "Servidor" + serverAnalysis: "Anรกlise do Servidor" + serverAsNumberse: "Server as Numbers" + serverCalendar: "Server Calendar" + serverDowntime: "Server Downtime" + serverOccupied: "Server occupied" + serverOverview: "Server Overview" + serverPage: "Server page" + serverPlaytime: "Server Playtime" + serverPlaytime30days: "Server Playtime for 30 days" + serverSelector: "Server selector" + servers: "Servidores" + serversTitle: "SERVERS" + session: "Sessรฃo" + sessionCalendar: "Session Calendar" + sessionEnded: " Sessรตes Finalizadas" + sessionMedian: "Mรฉdia de Sessรตes" + sessionStart: "Session Started" + sessions: "Sessรตes" + sortBy: "Sort By" + stacked: "Stacked" + themeSelect: "Theme Select" + thirdDeadliestWeapon: "3rd PvP Weapon" + thirtyDays: "30 days" + thirtyDaysAgo: "30 days ago" + timesKicked: "Vezes Kickado" + toMainPage: "to main page" + total: "Total" + totalActive: "Tempo Total Ativo" + totalAfk: "Tempo Total AFK" + totalPlayers: "Total Players" + totalPlayersOld: "Total de Jogadores" + totalPlaytime: "Total Playtime" + totalServerDowntime: "Total Server Downtime" + tps: "TPS" + trend: "Trend" + trends30days: "Trends for 30 days" + uniquePlayers: "Jogadores รšnicos" + uniquePlayers7days: "Unique Players (7 days)" + veryActive: "Muito Ativo" + weekComparison: "Week Comparison" + weekdays: "'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'" + world: "World Load" + worldPlaytime: "Tempo de Jogo por Mundo" + worstPing: "Worst Ping" + login: + failed: "Login failed: " + forgotPassword: "Forgot Password?" + forgotPassword1: "Forgot password? Unregister and register again." + forgotPassword2: "Use the following command in game to remove your current user:" + forgotPassword3: "Or using console:" + forgotPassword4: "After using the command, " + login: "Login" + logout: "Logout" + password: "Password" + register: "Create an Account!" + username: "Username" + modal: + info: + bugs: "Report Issues" + contributors: + bugreporters: "& Bug reporters!" + code: "code contributor" + donate: "Extra special thanks to those who have monetarily supported the development." + text: 'In addition following awesome people have contributed:' + translator: "translator" + developer: "is developed by" + discord: "General Support on Discord" + license: "Player Analytics is developed and licensed under" + metrics: "bStats Metrics" + text: "Information about the plugin" + wiki: "Plan Wiki, Tutorials & Documentation" + version: + available: "is Available" + changelog: "View Changelog" + dev: "This version is a DEV release." + download: "Download" + text: "A new version has been released and is now available for download." + title: "Version" + query: + filter: + activity: + text: "are in Activity Groups" + banStatus: + name: "Ban status" + banned: "Banned" + country: + text: "have joined from country" + generic: + allPlayers: "All players" + and: "and " + start: "of Players who " + joinAddress: + text: "joined with address" + nonOperators: "Non operators" + notBanned: "Not banned" + operatorStatus: + name: "Operator status" + operators: "Operators" + playedBetween: + text: "Played between" + pluginGroup: + name: "Group: " + text: "are in ${plugin}'s ${group} Groups" + registeredBetween: + text: "Registered between" + title: + activityGroup: "Current activity group" + view: " View:" + filters: + add: "Add a filter.." + loading: "Loading filters.." + generic: + are: "`are`" + label: + from: ">from" + makeAnother: "Make another query" + to: ">to" + view: "Show a view" + performQuery: "Perform Query!" + results: + match: "matched ${resultCount} players" + none: "Query produced 0 results" + title: "Query Results" + title: + activity: "Activity of matched players" + activityOnDate: 'Activity on ' + sessionsWithinView: "Sessions within view" + text: "Query<" + register: + completion: "Complete Registration" + completion1: "You can now finish registering the user." + completion2: "Code expires in 15 minutes" + completion3: "Use the following command in game to finish registration:" + completion4: "Or using console:" + createNewUser: "Create a new user" + error: + checkFailed: "Checking registration status failed: " + failed: "Registration failed: " + noPassword: "You need to specify a Password" + noUsername: "You need to specify a Username" + usernameLength: "Username can be up to 50 characters, yours is " + login: "Already have an account? Login!" + passwordTip: "Password should be more than 8 characters, but there are no limitations." + register: "Register" + usernameTip: "Username can be up to 50 characters." + text: + clickToExpand: "Click to expand" + comparing15days: "Comparing 15 days" + comparing30daysAgo: "Comparing 30d ago to Now" + noExtensionData: "No Extension Data" + noLowTps: "No low tps spikes" + unit: + chunks: "Chunks" + players: "Players" + value: + localMachine: "Mรกquina Local" + noKills: "Sem Kills" + offline: " Offline" + online: " Online" + with: "Com" + version: + changelog: "View Changelog" + current: "You have version ${0}" + download: "Download Plan-${0}.jar" + isDev: "This version is a DEV release." + updateButton: "Update" + updateModal: + text: "A new version has been released and is now available for download." + title: "Version ${0} is Available!" +plugin: + apiCSSAdded: "PageExtension: ${0} added stylesheet(s) to ${1}, ${2}" + apiJSAdded: "PageExtension: ${0} added javascript(s) to ${1}, ${2}" + disable: + database: "Processando tarefas crรญticas nรฃo processadas anteriormente. (${0})" + disabled: "Anรกlise de Jogadores Desativado." + processingComplete: "Processamento completo." + savingSessions: "Saving unfinished sessions.." + savingSessionsTimeout: "Timeout hit, storing the unfinished sessions on next enable instead." + waitingDb: "Waiting queries to finish to avoid SQLite crashing JVM.." + waitingDbComplete: "Closed SQLite connection." + waitingTransactions: "Waiting for unfinished transactions to avoid data loss.." + waitingTransactionsComplete: "Transaction queue closed." + webserver: "O servidor web foi desativado." + enable: + database: "${0}-conexรฃo com o banco de dados estabilizada." + enabled: "Anรกlise de Jogadores Ativado." + fail: + database: "${0}-Falha na Conexรฃo do Banco de Dados: ${1}" + databasePatch: "O patch do banco de dados falhou, o plugin teve que ser desativado. Reporte esse problema no GitHub ou Discord para suporte." + databaseType: "${0} nรฃo รฉ um banco de dados suportado" + geoDBWrite: "Algo deu errado ao salvar o banco de dados de geolocalizaรงรฃo do GeoLite2" + webServer: "O servidor web nรฃo pode ser inicializado!" + notify: + badIP: "0.0.0.0 is not a valid address, set up Alternative_IP settings. Incorrect links might be given!" + emptyIP: "O IP no server.properties estรก vazio & o IP alternativo nรฃo estรก sendo usado. Os dados informados estรฃo incorretos!" + geoDisabled: "A coleta de geolocalizaรงรฃo estรก desativada. (Data.Geolocations: false)" + geoInternetRequired: "O Plan requer acesso ร  internet na primeira execuรงรฃo para baixar o banco de dados de geolocalizaรงรฃo do GeoLite2." + storeSessions: "Storing sessions that were preserved before previous shutdown." + webserverDisabled: "O servidor web nรฃo foi inicializado. (WebServer.DisableWebServer: true)" + webserver: "O servidor web estรก rodando na PORTA ${0} ( ${1} )" + generic: + dbApplyingPatch: "Aplicando o Patch: ${0}.." + dbFaultyLaunchOptions: "Opรงรตes de execuรงรฃo estavam com problemas, usando configuraรงรฃo padrรฃo (${0})" + dbNotifyClean: "Removido dados de ${0} jogadores." + dbNotifySQLiteWAL: "O modo WAL do SQLite nรฃo รฉ suportado nessa versรฃo do servidor, entรฃo serรก usado a configuraรงรฃo padrรฃo. Isso pode ou nรฃo afetar o desempenho." + dbPatchesAlreadyApplied: "Todos os patchs de bancos de dados jรก foram aplicados." + dbPatchesApplied: "Todos os patchs de bancos de dados foram aplicados." + dbSchemaPatch: "Database: Making sure schema is up to date.." + loadedServerInfo: "Server identifier loaded: ${0}" + loadingServerInfo: "Loading server identifying information" + no: "Nรฃo" + today: "'Hoje'" + unavailable: "Unavailable" + unknown: "Desconhecido" + yes: "Sim" + yesterday: "'Ontem'" + version: + checkFail: "Falha ao verificar disponibilidade de atualizaรงรฃo" + checkFailGithub: "Informaรงรฃo da versรฃo nรฃo pode ser carregada de Github/versions.txt" + isDev: " Essa รฉ uma versรฃo em desenvolvimento." + isLatest: "Vocรช estรก usando a รบltima versรฃo." + updateAvailable: "Nova Versรฃo (${0}) estรก disponรญvel ${1}" + updateAvailableSpigot: "Nova Versรฃo estรก disponรญvel em ${0}" + webserver: + fail: + SSLContext: "Servidor Web: Falha ao inicializar certificado SSL." + certFileEOF: "WebServer: EOF when reading Certificate file. (Check that the file is not empty)" + certStoreLoad: "Servidor Web: Falha ao carregar certificado SSL." + portInUse: "O servidor web nรฃo foi inicializado. A porta (${0}) jรก estรก em uso?" + notify: + authDisabledConfig: "WebServer: User Authorization Disabled! (Disabled in config)" + authDisabledNoHTTPS: "Servidor Web: Autenticaรงรฃo de usuรกrio desativada! (Nรฃo seguro por HTTP)" + certificateExpiresOn: "Webserver: Loaded certificate is valid until ${0}." + certificateExpiresPassed: "Webserver: Certificate has expired, consider renewing the certificate." + certificateExpiresSoon: "Webserver: Certificate expires in ${0}, consider renewing the certificate." + certificateNoSuchAlias: "Webserver: Certificate with alias '${0}' was not found inside the keystore file '${1}'." + http: "Servidor Web: Sem Certificado -> Usando protocolo HTTP para visualizaรงรฃo." + ipWhitelist: "Webserver: IP Whitelist is enabled." + ipWhitelistBlock: "Webserver: ${0} was denied access to '${1}'. (not whitelisted)" + noCertFile: "Servidor Web: Arquivo KeyStore nรฃo encontrado: ${0}" + reverseProxy: "WebServer: Proxy-mode HTTPS enabled, make sure that your reverse-proxy is routing using HTTPS and Plan Alternative_IP.Address points to the Proxy" diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_RU.txt b/Plan/common/src/main/resources/assets/plan/locale/locale_RU.txt deleted file mode 100644 index ca8fb7989..000000000 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_RU.txt +++ /dev/null @@ -1,517 +0,0 @@ -API - css+ || PageExtension: ${0} ะดะพะฑะฐะฒะธะป ั‚ะฐะฑะปะธั†ั‹ ัั‚ะธะปะตะน ะฒ ${1}, ${2} -API - js+ || PageExtension: ${0} ะดะพะฑะฐะฒะธะป JavaScript(ั‹) ะบ ${1}, ${2} -Cmd - Click Me || ะะฐะถะผะธ ะฝะฐ ะผะตะฝั -Cmd - Link || ะกัั‹ะปะบะฐ -Cmd - Link Network || Network page: -Cmd - Link Player || Player page: -Cmd - Link Player JSON || Player json: -Cmd - Link Players || Players page: -Cmd - Link Register || Register page: -Cmd - Link Server || Server page: -CMD Arg - backup-file || Name of the backup file (case sensitive) -CMD Arg - code || Code used to finalize registration. -CMD Arg - db type backup || Type of the database to backup. Current database is used if not specified. -CMD Arg - db type clear || Type of the database to remove all data from. -CMD Arg - db type hotswap || Type of the database to start using. -CMD Arg - db type move from || Type of the database to move data from. -CMD Arg - db type move to || Type of the database to move data to. Can not be same as previous. -CMD Arg - db type restore || Type of the database to restore to. Current database is used if not specified. -CMD Arg - feature || Name of the feature to disable: ${0} -CMD Arg - player identifier || Name or UUID of a player -CMD Arg - player identifier remove || Identifier for a player that will be removed from current database. -CMD Arg - server identifier || Name, ID or UUID of a server -CMD Arg - subcommand || Use the command without subcommand to see help. -CMD Arg - username || Username of another user. If not specified player linked user is used. -CMD Arg Name - backup-file || backup-file -CMD Arg Name - code || ${code} -CMD Arg Name - export kind || export kind -CMD Arg Name - feature || feature -CMD Arg Name - import kind || import kind -CMD Arg Name - name or uuid || name/uuid -CMD Arg Name - server || server -CMD Arg Name - subcommand || subcommand -CMD Arg Name - username || username -Cmd Confirm - accept || Accept -Cmd Confirm - cancelled, no data change || Cancelled. No data was changed. -Cmd Confirm - cancelled, unregister || Cancelled. '${0}' was not unregistered -Cmd Confirm - clearing db || You are about to remove all Plan-data in ${0} -Cmd Confirm - confirmation || Confirm: -Cmd Confirm - deny || Cancel -Cmd Confirm - Expired || Confirmation expired, use the command again -Cmd Confirm - Fail on accept || The accepted action errored upon execution: ${0} -Cmd Confirm - Fail on deny || The denied action errored upon execution: ${0} -Cmd Confirm - overwriting db || You are about to overwrite data in Plan ${0} with data in ${1} -Cmd Confirm - remove player db || You are about to remove data of ${0} from ${1} -Cmd Confirm - unregister || You are about to unregister '${0}' linked to ${1} -Cmd db - creating backup || Creating a backup file '${0}.db' with contents of ${1} -Cmd db - removal || Removing Plan-data from ${0}.. -Cmd db - removal player || Removing data of ${0} from ${1}.. -Cmd db - server uninstalled || ยงaIf the server is still installed, it will automatically set itself as installed in the database. -Cmd db - write || Writing to ${0}.. -Cmd Disable - Disabled || ยงaPlan ัะตะนั‡ะฐั ะพั‚ะบะปัŽั‡ะตะฝ. ะ’ั‹ ะฒัั‘ ะตั‰ะต ะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ reload ะดะปั ะฟะตั€ะตะทะฐะณั€ัƒะทะบะธ ะฟะปะฐะณะธะฝะฐ. -Cmd FAIL - Accepts only these arguments || Accepts following as ${0}: ${1} -Cmd FAIL - Database not open || ยงcะ‘ะฐะทะฐ ะดะฐะฝะฝั‹ั… ${0} - ะŸะพะถะฐะปัƒะนัั‚ะฐ ะฟะพะฟั€ะพะฑัƒะนั‚ะต ัะฝะพะฒะฐ ะฝะตะผะฝะพะณะพ ะฟะพะทะถะต. -Cmd FAIL - Empty search string || The search string can not be empty -Cmd FAIL - Invalid Username || ยงcะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะฝะต ะธะผะตะตั‚ UUID. -Cmd FAIL - No Feature || ยงeะžะฟั€ะตะดะตะปะธั‚ะต ั„ัƒะฝะบั†ะธัŽ ะดะปั ะพั‚ะบะปัŽั‡ะตะฝะธั! (ะฒ ะฝะฐัั‚ะพัั‰ะตะต ะฒั€ะตะผั ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ ${0}) -Cmd FAIL - No Permission || ยงcะฃ ะฒะฐั ะฝะตั‚ ะฝะตะพะฑั…ะพะดะธะผั‹ั… ะฟั€ะฐะฒ. -Cmd FAIL - No player || Player '${0}' was not found, they have no UUID. -Cmd FAIL - No player register || Player '${0}' was not found in the database. -Cmd FAIL - No server || Server '${0}' was not found from the database. -Cmd FAIL - Require only one Argument || ยงcะขั€ะตะฑัƒะตั‚ัั ั‚ะพะปัŒะบะพ ะพะดะธะฝ ะฐั€ะณัƒะผะตะฝั‚ ${1} -Cmd FAIL - Requires Arguments || ยงcะขั€ะตะฑัƒะตั‚ัั ะฐั€ะณัƒะผะตะฝั‚ (${0}) ${1} -Cmd FAIL - see config || see '${0}' in config.yml -Cmd FAIL - Unknown Username || ยงcะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะฝะต ะฑั‹ะป ะทะฐะผะตั‡ะตะฝ ะฝะฐ ัั‚ะพะผ ัะตั€ะฒะตั€ะต -Cmd FAIL - Users not linked || ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะฝะต ัะฒัะทะฐะฝ ั ะฒะฐัˆะตะน ัƒั‡ะตั‚ะฝะพะน ะทะฐะฟะธััŒัŽ, ะธ ัƒ ะฒะฐั ะฝะตั‚ ะฟั€ะฐะฒ ะฝะฐ ัƒะดะฐะปะตะฝะธะต ัƒั‡ะตั‚ะฝั‹ั… ะทะฐะฟะธัะตะน ะดั€ัƒะณะธั… ะฟะพะปัŒะทะพะฒะฐั‚ะตะปะตะน. -Cmd FAIL - WebUser does not exists || ยงcะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะฝะต ััƒั‰ะตัั‚ะฒัƒะตั‚! -Cmd FAIL - WebUser exists || ยงcะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ัƒะถะต ััƒั‰ะตัั‚ะฒัƒะตั‚! -Cmd Footer - Help || ยง7Hover over command or arguments or use '/${0} ?' to learn more about them. -Cmd Header - Analysis || > ยง2ะ ะตะทัƒะปัŒั‚ะฐั‚ั‹ ะฐะฝะฐะปะธะทะฐ -Cmd Header - Help || > ยง2/${0} Help -Cmd Header - Info || > ยง2ะะฝะฐะปะธั‚ะธะบะฐ ะธะณั€ะพะบะฐ -Cmd Header - Inspect || > ยง2ะ˜ะณั€ะพะบ: ยงf${0} -Cmd Header - Network || > ยง2ะกะตั‚ะตะฒะฐั ัั‚ั€ะฐะฝะธั†ะฐ -Cmd Header - Players || > ยง2ะ˜ะณั€ะพะบะธ -Cmd Header - Search || > ยง2${0} ะ ะตะทัƒะปัŒั‚ะฐั‚ั‹ ะดะปั ยงf${1}ยง2: -Cmd Header - server list || id::name::uuid -Cmd Header - Servers || > ยง2ะกะตั€ะฒะตั€ั‹ -Cmd Header - web user list || username::linked to::permission level -Cmd Header - Web Users || > ยง2${0} ะ’ะตะฑ-ะฟะพะปัŒะทะพะฒะฐั‚ะตะปะธ -Cmd Info - Bungee Connection || ยง2ะŸะพะดะบะปัŽั‡ะตะฝ ะบ ะฟั€ะพะบัะธ: ยงf${0} -Cmd Info - Database || ยง2ะขะตะบัƒั‰ะฐั ะฑะฐะทะฐ ะดะฐะฝะฝั‹ั…: ยงf${0} -Cmd Info - Reload Complete || ยงaะŸะตั€ะตะทะฐะณั€ัƒะทะบะฐ ะทะฐะฒะตั€ัˆะตะฝะฐ -Cmd Info - Reload Failed || ยงcะงั‚ะพ-ั‚ะพ ะฟะพัˆะปะพ ะฝะต ั‚ะฐะบ ะฒะพ ะฒั€ะตะผั ะฟะตั€ะตะทะฐะณั€ัƒะทะบะธ ะฟะปะฐะณะธะฝะฐ, ั€ะตะบะพะผะตะฝะดัƒะตั‚ัั ะฟะตั€ะตะทะฐะณั€ัƒะทะบะฐ. -Cmd Info - Update || ยง2ะ”ะพัั‚ัƒะฟะฝะพ ะพะฑะฝะพะฒะปะตะฝะธะต: ยงf${0} -Cmd Info - Version || ยง2ะ’ะตั€ัะธั: ยงf${0} -Cmd network - No network || Server is not connected to a network. The link redirects to server page. -Cmd Notify - No Address || ยงeะะดั€ะตั ะฝะตะดะพัั‚ัƒะฟะตะฝ - ะธัะฟะพะปัŒะทัƒะตั‚ัั localhost ะฒ ะบะฐั‡ะตัั‚ะฒะต ะทะฐะฟะฐัะฝะพะณะพ. ะะฐัั‚ั€ะพะนั‚ะต 'Alternative_IP'. -Cmd Notify - No WebUser || ะ’ะพะทะผะพะถะฝะพ, ะฒั‹ ะฝะต ะธะผะตะตั‚ะต ะฒะตะฑ-ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั, ะธัะฟะพะปัŒะทัƒะนั‚ะต /plan register -Cmd Notify - WebUser register || ะ—ะฐั€ะตะณะธัั‚ั€ะธั€ะพะฒะฐะฝ ะฝะพะฒั‹ะน ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒ: '${0}' ะฃั€ะพะฒะตะฝัŒ ะฟั€ะฐะฒ: ${1} -Cmd Qinspect - Active Playtime || ยง2ะะบั‚ะธะฒะฝะพะต ะฒั€ะตะผั ะธะณั€ั‹: ยงf${0} -Cmd Qinspect - Activity Index || ยง2ะ˜ะฝะดะตะบั ะฐะบั‚ะธะฒะฝะพัั‚ะธ: ยงf${0} | ${1} -Cmd Qinspect - AFK Playtime || ยง2ะ’ั€ะตะผั AFK: ยงf${0} -Cmd Qinspect - Deaths || ยง2ะกะผะตั€ั‚ะธ: ยงf${0} -Cmd Qinspect - Geolocation || ยง2ะŸั€ะธัะพะตะดะธะฝะธะปัั ะธะท: ยงf${0} -Cmd Qinspect - Last Seen || ยง2ะŸะพัะปะตะดะฝะตะต ะฟะพัะตั‰ะตะฝะธะต: ยงf${0} -Cmd Qinspect - Longest Session || ยง2ะกะฐะผะฐั ะดะปะธะฝะฝะฐั ัะตััะธั: ยงf${0} -Cmd Qinspect - Mob Kills || ยง2ะฃะฑะธั‚ะพ ะผะพะฑะพะฒ: ยงf${0} -Cmd Qinspect - Player Kills || ยง2ะฃะฑะธั‚ะพ ะธะณั€ะพะบะพะฒ: ยงf${0} -Cmd Qinspect - Playtime || ยง2ะ’ั€ะตะผั ะธะณั€ั‹: ยงf${0} -Cmd Qinspect - Registered || ยง2ะ—ะฐั€ะตะณะธัั‚ั€ะธั€ะพะฒะฐะปัั: ยงf${0} -Cmd Qinspect - Times Kicked || ยง2ะšะพะป-ะฒะพ ะบะธะบะพะฒ: ยงf${0} -Cmd SUCCESS - Feature disabled || ยงaะ’ั€ะตะผะตะฝะฝะพ ะพั‚ะบะปัŽั‡ะตะฝะพ '${0}' ะดะพ ัะปะตะดัƒัŽั‰ะตะน ะฟะตั€ะตะทะฐะณั€ัƒะทะบะธ ะฟะปะฐะณะธะฝะฐ. -Cmd SUCCESS - WebUser register || ยงaะะพะฒั‹ะน ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒ (${0}) ัƒัะฟะตัˆะฝะพ ะดะพะฑะฐะฒะปะตะฝ! -Cmd unregister - unregistering || Unregistering '${0}'.. -Cmd WARN - Database not open || ยงeะ‘ะฐะทะฐ ะดะฐะฝะฝั‹ั… ${0} - ะญั‚ะพ ะผะพะถะตั‚ ะทะฐะฝัั‚ัŒ ะฑะพะปัŒัˆะต ะฒั€ะตะผะตะฝะธ, ั‡ะตะผ ะพะถะธะดะฐะปะพััŒ. -Cmd Web - Permission Levels || >\ยง70: ะ”ะพัั‚ัƒะฟ ะบะพ ะฒัะตะผ ัั‚ั€ะฐะฝะธั†ะฐะผ\ยง71: ะ”ะพัั‚ัƒะฟ ะบ '/players' ะธ ะฒัะตะผ ัั‚ั€ะฐะฝะธั†ะฐะผ ะธะณั€ะพะบะพะฒ\ยง72: ะ”ะพัั‚ัƒะฟ ะบ ัั‚ั€ะฐะฝะธั†ะต ะธะณั€ะพะบะฐ ั ั‚ะตะผ ะถะต ะธะผะตะฝะตะผ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั, ั‡ั‚ะพ ะธ ะดะปั ะฒะตะฑ-ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั\ยง73+: ะะตั‚ ะฟั€ะฐะฒ -Command Help - /plan db || Manage Plan database -Command Help - /plan db backup || Backup data of a database to a file -Command Help - /plan db clear || Remove ALL Plan data from a database -Command Help - /plan db hotswap || ะ‘ั‹ัั‚ั€ะพะต ะธะทะผะตะฝะตะฝะธะต ะฑะฐะทั‹ ะดะฐะฝะฝั‹ั… -Command Help - /plan db move || ะŸะตั€ะตะผะตั‰ะตะฝะธะต ะดะฐะฝะฝั‹ั… ะผะตะถะดัƒ ะฑะฐะทะฐะผะธ ะดะฐะฝะฝั‹ั… -Command Help - /plan db remove || Remove player's data from Current database -Command Help - /plan db restore || Restore data from a file to a database -Command Help - /plan db uninstalled || Set a server as uninstalled in the database. -Command Help - /plan disable || Disable the plugin or part of it -Command Help - /plan export || Export html or json files manually -Command Help - /plan import || Import data -Command Help - /plan info || Information about the plugin -Command Help - /plan ingame || ะŸั€ะพัะผะพั‚ั€ ะธะฝั„ะพั€ะผะฐั†ะธะธ ะพะฑ ะธะณั€ะพะบะต ะฒ ะธะณั€ะต -Command Help - /plan json || View json of Player's raw data. -Command Help - /plan logout || Log out other users from the panel. -Command Help - /plan network || ะŸั€ะพัะผะพั‚ั€ ะฒะตะฑ-ัั‚ั€ะฐะฝะธั†ั‹ -Command Help - /plan player || ะŸั€ะพัะผะพั‚ั€ ัั‚ั€ะฐะฝะธั†ั‹ ะธะณั€ะพะบะฐ -Command Help - /plan players || ะŸะพัะผะพั‚ั€ะตั‚ัŒ ัั‚ั€ะฐะฝะธั†ัƒ ั ะธะณั€ะพะบะฐะผะธ -Command Help - /plan register || ะ—ะฐั€ะตะณะธัั‚ั€ะธั€ะพะฒะฐั‚ัŒ ะฒะตะฑ-ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั -Command Help - /plan reload || ะŸะตั€ะตะทะฐะฟัƒัั‚ะธั‚ัŒ Plan -Command Help - /plan search || ะŸะพะธัะบ ะฟะพ ะธะผะตะฝะธ ะธะณั€ะพะบะฐ -Command Help - /plan server || ะŸั€ะพัะผะพั‚ั€ ัั‚ั€ะฐะฝะธั†ั‹ ัะตั€ะฒะตั€ะฐ -Command Help - /plan servers || ะกะฟะธัะพะบ ัะตั€ะฒะตั€ะพะฒ ะฒ ะฑะฐะทะต ะดะฐะฝะฝั‹ั… -Command Help - /plan unregister || Unregister a user of Plan website -Command Help - /plan users || List all web users -Database - Apply Patch || ะŸั€ะธะผะตะฝะตะฝะธะต ะธัะฟั€ะฐะฒะปะตะฝะธะน: ${0}.. -Database - Patches Applied || ะ’ัะต ะธัะฟั€ะฐะฒะปะตะฝะธั ะฑะฐะทั‹ ะดะฐะฝะฝั‹ั… ัƒัะฟะตัˆะฝะพ ะฟั€ะธะผะตะฝะตะฝั‹. -Database - Patches Applied Already || ะ’ัะต ะธัะฟั€ะฐะฒะปะตะฝะธั ะฑะฐะทั‹ ะดะฐะฝะฝั‹ั… ัƒะถะต ะฟั€ะธะผะตะฝะตะฝั‹. -Database MySQL - Launch Options Error || ะŸะฐั€ะฐะผะตั‚ั€ั‹ ะทะฐะฟัƒัะบะฐ ะฑั‹ะปะธ ะพัˆะธะฑะพั‡ะฝั‹ะผะธ, ะธัะฟะพะปัŒะทัƒัŽั‚ัั ัั‚ะฐะฝะดะฐั€ั‚ะฝั‹ะต (${0}) -Database Notify - Clean || ะฃะดะฐะปะตะฝั‹ ะดะฐะฝะฝั‹ะต ${0} ะธะณั€ะพะบะพะฒ. -Database Notify - SQLite No WAL || ะ ะตะถะธะผ SQLite WAL ะฝะต ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ัั ะฝะฐ ัั‚ะพะน ะฒะตั€ัะธะธ ัะตั€ะฒะตั€ะฐ, ะธัะฟะพะปัŒะทัƒะตั‚ัั ัั‚ะฐะฝะดะฐั€ั‚ะฝั‹ะน. ะญั‚ะพ ะผะพะถะตั‚ ะธะปะธ ะฝะต ะผะพะถะตั‚ ะฟะพะฒะปะธัั‚ัŒ ะฝะฐ ะฟั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ัŒ. -Disable || ะะฝะฐะปะธั‚ะธะบะฐ ะธะณั€ะพะบะฐ ะพั‚ะบะปัŽั‡ะตะฝะฐ. -Disable - Processing || ะžะฑั€ะฐะฑะพั‚ะบะฐ ะบั€ะธั‚ะธั‡ะตัะบะธั… ะฝะตะพะฑั€ะฐะฑะพั‚ะฐะฝะฝั‹ั… ะทะฐะดะฐั‡. (${0}) -Disable - Processing Complete || ะžะฑั€ะฐะฑะพั‚ะบะฐ ะทะฐะฒะตั€ัˆะตะฝะฐ. -Disable - Unsaved Session Save || ะกะพั…ั€ะฐะฝะตะฝะธะต ะฝะตะทะฐะฒะตั€ัˆะตะฝะฝั‹ั… ัะตััะธะน.. -Disable - Unsaved Session Save Timeout || Timeout hit, storing the unfinished sessions on next enable instead. -Disable - Waiting SQLite || Waiting queries to finish to avoid SQLite crashing JVM.. -Disable - Waiting SQLite Complete || Closed SQLite connection. -Disable - Waiting Transactions || Waiting for unfinished transactions to avoid data loss.. -Disable - Waiting Transactions Complete || Transaction queue closed. -Disable - WebServer || ะ’ะตะฑ-ัะตั€ะฒะตั€ ะฑั‹ะป ะพั‚ะบะปัŽั‡ะตะฝ. -Enable || ะะฝะฐะปะธั‚ะธะบะฐ ะธะณั€ะพะบะฐ ะฒะบะปัŽั‡ะตะฝะฐ. -Enable - Database || ${0}-ัะพะตะดะธะฝะตะฝะธะต ั ะฑะฐะทะพะน ะดะฐะฝะฝั‹ั… ัƒัั‚ะฐะฝะพะฒะปะตะฝะพ. -Enable - Notify Bad IP || 0.0.0.0 ะฝะต ัะฒะปัะตั‚ัั ะดะพะฟัƒัั‚ะธะผั‹ะผ ะฐะดั€ะตัะพะผ, ะฝะฐัั‚ั€ะพะนั‚ะต ะฟะฐั€ะฐะผะตั‚ั€ั‹ Alternative_IP. ะœะพะณัƒั‚ ะฑั‹ั‚ัŒ ะดะฐะฝั‹ ะฝะตะฒะตั€ะฝั‹ะต ััั‹ะปะบะธ! -Enable - Notify Empty IP || IP ะฒ server.properties ะฟัƒัั‚, ะฐ Alternative_IP ะฝะต ะธัะฟะพะปัŒะทัƒะตั‚ัั. ะœะพะณัƒั‚ ะฑั‹ั‚ัŒ ะดะฐะฝั‹ ะฝะตะฒะตั€ะฝั‹ะต ััั‹ะปะบะธ! -Enable - Notify Geolocations disabled || ะ“ะตะพะปะพะบะฐั†ะธะพะฝะฝั‹ะน ัะฑะพั€ ะฝะต ะฐะบั‚ะธะฒะตะฝ. (Data.Geolocations: false) -Enable - Notify Geolocations Internet Required || Plan'ัƒ ะขั€ะตะฑัƒะตั‚ัั ะดะพัั‚ัƒะฟ ะฒ ะ˜ะฝั‚ะตั€ะฝะตั‚ ะฟั€ะธ ะฟะตั€ะฒะพะผ ะทะฐะฟัƒัะบะต, ั‡ั‚ะพะฑั‹ ะทะฐะณั€ัƒะทะธั‚ัŒ ะฑะฐะทัƒ ะณะตะพะปะพะบะฐั†ะธะธ GeoLite2. -Enable - Notify Webserver disabled || ะ’ะตะฑ-ัะตั€ะฒะตั€ ะฝะต ะฑั‹ะป ะธะฝะธั†ะธะฐะปะธะทะธั€ะพะฒะฐะฝ. (WebServer.DisableWebServer: true) -Enable - Storing preserved sessions || Storing sessions that were preserved before previous shutdown. -Enable - WebServer || ะ’ะตะฑ-ัะตั€ะฒะตั€ ั€ะฐะฑะพั‚ะฐะตั‚ ะฝะฐ ะฟะพั€ั‚ะต ${0} ( ${1} ) -Enable FAIL - Database || ${0}-ะžัˆะธะฑะบะฐ ะฟะพะดะบะปัŽั‡ะตะฝะธั ะบ ะฑะฐะทะต ะดะฐะฝะฝั‹ั…: ${1} -Enable FAIL - Database Patch || ะžัˆะธะฑะบะฐ ะพะฑะฝะพะฒะปะตะฝะธั ะฑะฐะทั‹ ะดะฐะฝะฝั‹ั…, ะฟะปะฐะณะธะฝ ะดะพะปะถะตะฝ ะฑั‹ั‚ัŒ ะพั‚ะบะปัŽั‡ะตะฝ. ะŸะพะถะฐะปัƒะนัั‚ะฐ, ัะพะพะฑั‰ะธั‚ะต ะพะฑ ัั‚ะพะน ะฟั€ะพะฑะปะตะผะต -Enable FAIL - GeoDB Write || ะงั‚ะพ-ั‚ะพ ะฟะพัˆะปะพ ะฝะต ั‚ะฐะบ ะฟั€ะธ ัะพั…ั€ะฐะฝะตะฝะธะธ ะทะฐะณั€ัƒะถะตะฝะฝะพะน ะฑะฐะทั‹ ะณะตะพะปะพะบะฐั†ะธะธ GeoLite2 -Enable FAIL - WebServer (Proxy) || ะ’ะตะฑ-ัะตั€ะฒะตั€ ะฝะต ะธะฝะธั†ะธะฐะปะธะทะธั€ะพะฒะฐะฝ! -Enable FAIL - Wrong Database Type || ${0} ะฝะตะฟะพะดะดะตั€ะถะธะฒะฐะตะผะฐั ะฑะฐะทะฐ ะดะฐะฝะฝั‹ั… -HTML - AND_BUG_REPORTERS || & Bug reporters! -HTML - BANNED (Filters) || Banned -HTML - COMPARING_15_DAYS || ะกั€ะฐะฒะฝะตะฝะธะต 15 ะดะฝะตะน -HTML - COMPARING_60_DAYS || ะกั€ะฐะฒะฝะตะฝะธะต 30 ะดะฝะตะน ะฝะฐะทะฐะด ะธ ัะตะนั‡ะฐั -HTML - COMPARING_7_DAYS || ะกั€ะฐะฒะฝะตะฝะธะต 7 ะดะฝะตะน -HTML - DATABASE_NOT_OPEN || ะ‘ะฐะทะฐ ะดะฐะฝะฝั‹ั… ะฝะต ะพั‚ะบั€ั‹ั‚ะฐ, ะฟั€ะพะฒะตั€ัŒั‚ะต ัั‚ะฐั‚ัƒั ะ‘ะ” ั ะฟะพะผะพั‰ัŒัŽ /plan info -HTML - DESCRIBE_RETENTION_PREDICTION || This value is a prediction based on previous players. -HTML - ERROR || ะะตัƒัะฟะตัˆะฝะฐั ะฐะฒั‚ะพั€ะธะทะฐั†ะธั ะธะท-ะทะฐ ะพัˆะธะฑะบะธ -HTML - EXPIRED_COOKIE || ะกั€ะพะบ ะดะตะนัั‚ะฒะธั ั„ะฐะนะปะฐ cookie ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะธัั‚ะตะบ -HTML - FILTER_ACTIVITY_INDEX_NOW || Current activity group -HTML - FILTER_ALL_PLAYERS || All players -HTML - FILTER_BANNED || Ban status -HTML - FILTER_GROUP || Group: -HTML - FILTER_OPS || Operator status -HTML - INDEX_ACTIVE || ะะบั‚ะธะฒะฝั‹ะน -HTML - INDEX_INACTIVE || ะะตะฐะบั‚ะธะฒะฝั‹ะน -HTML - INDEX_IRREGULAR || ะะตั€ะตะณัƒะปัั€ะฝั‹ะน -HTML - INDEX_REGULAR || ะ ะตะณัƒะปัั€ะฝั‹ะน -HTML - INDEX_VERY_ACTIVE || ะžั‡ะตะฝัŒ ะฐะบั‚ะธะฒะฝั‹ะน -HTML - KILLED || ะฃะฑะธั‚ -HTML - LABEL_1ST_WEAPON || ะกะฐะผะพะต ัะผะตั€ั‚ะพะฝะพัะฝะพะต ะพั€ัƒะถะธะต ะฒ PvP -HTML - LABEL_2ND_WEAPON || 2-ะต PvP ะพั€ัƒะถะธะต -HTML - LABEL_3RD_WEAPON || 3-ะต PvP ะพั€ัƒะถะธะต -HTML - LABEL_ACTIVE_PLAYTIME || Active Playtime -HTML - LABEL_ACTIVITY_INDEX || ะ˜ะฝะดะตะบั ะฐะบั‚ะธะฒะฝะพัั‚ะธ -HTML - LABEL_AFK || AFK -HTML - LABEL_AFK_TIME || ะ’ั€ะตะผั AFK -HTML - LABEL_AVG || ะกั€ะตะด. -HTML - LABEL_AVG_ACTIVE_PLAYTIME || Average Active Playtime -HTML - LABEL_AVG_AFK_TIME || Average AFK Time -HTML - LABEL_AVG_CHUNKS || Average Chunks -HTML - LABEL_AVG_ENTITIES || Average Entities -HTML - LABEL_AVG_KDR || ะกั€ะตะด. KDR -HTML - LABEL_AVG_MOB_KDR || ะกั€ะตะด. ะผะพะฑ KDR -HTML - LABEL_AVG_PLAYTIME || ะกั€ะตะดะฝะตะต ะฒั€ะตะผั ะธะณั€ั‹ -HTML - LABEL_AVG_SESSION_LENGTH || ะกั€ะตะดะฝัั ะฟั€ะพะดะพะปะถะธั‚ะตะปัŒะฝะพัั‚ัŒ ัะตะฐะฝัะฐ -HTML - LABEL_AVG_SESSIONS || Average Sessions -HTML - LABEL_AVG_TPS || ะกั€ะตะดะฝะธะน TPS -HTML - LABEL_BANNED || ะ—ะฐะฑะฐะฝะตะฝ -HTML - LABEL_BEST_PEAK || ะœะฐะบัะธะผะฐะปัŒะฝั‹ะน ะฟะธะบ -HTML - LABEL_DAY_OF_WEEK || ะ”ะตะฝัŒ ะฝะตะดะตะปะธ -HTML - LABEL_DEATHS || ะกะผะตั€ั‚ะธ -HTML - LABEL_DOWNTIME || ะ’ั€ะตะผั ะฟั€ะพัั‚ะพั -HTML - LABEL_DURING_LOW_TPS || ะ’ะพ ะฒั€ะตะผั ะฝะธะทะบะพะณะพ TPS: -HTML - LABEL_ENTITIES || ะžะฑัŠะตะบั‚ั‹ -HTML - LABEL_FAVORITE_SERVER || ะ›ัŽะฑะธะผั‹ะน ัะตั€ะฒะตั€ -HTML - LABEL_FIRST_SESSION_LENGTH || ะŸั€ะพะดะพะปะถะธั‚ะตะปัŒะฝะพัั‚ัŒ ะฟะตั€ะฒะพะณะพ ัะตะฐะฝัะฐ -HTML - LABEL_FREE_DISK_SPACE || ะกะฒะพะฑะพะดะฝะพะต ะดะธัะบะพะฒะพะต ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒะพ -HTML - LABEL_INACTIVE || ะะตะฐะบั‚ะธะฒะฝั‹ะน -HTML - LABEL_LAST_PEAK || ะŸะพัะปะตะดะฝะธะน ะŸะธะบ -HTML - LABEL_LAST_SEEN || ะŸะพัะปะตะดะฝะตะต ะฟะพัะตั‰ะตะฝะธะต -HTML - LABEL_LOADED_CHUNKS || ะ—ะฐะณั€ัƒะถะตะฝะฝั‹ะต ั‡ะฐะฝะบะธ -HTML - LABEL_LOADED_ENTITIES || ะ—ะฐะณั€ัƒะถะตะฝะฝั‹ะต ะพะฑัŠะตะบั‚ั‹ -HTML - LABEL_LONE_JOINS || ะžะดะธะฝะพั‡ะบะฐ ะฟั€ะธัะพะตะดะตะฝะธะปัั -HTML - LABEL_LONE_NEW_JOINS || ะžะดะธะฝะพะบะธะน ะฝะพะฒะธั‡ะพะบ ะฟั€ะธัะพะตะดะธะฝัะตั‚ัั -HTML - LABEL_LONGEST_SESSION || ะกะฐะผะฐั ะดะปะธะฝะฝะฐั ัะตััะธั -HTML - LABEL_LOW_TPS || ะะธะทะบะธะน TPS -HTML - LABEL_MAX_FREE_DISK || ะœะฐะบั. ัะฒะพะฑะพะดะฝั‹ะน ะดะธัะบ -HTML - LABEL_MIN_FREE_DISK || ะœะธะฝ. ัะฒะพะฑะพะดะฝั‹ะน ะดะธัะบ -HTML - LABEL_MOB_DEATHS || ะกะผะตั€ั‚ัŒ ะธะท-ะทะฐ ะผะพะฑะพะฒ -HTML - LABEL_MOB_KDR || ะœะพะฑ KDR -HTML - LABEL_MOB_KILLS || ะฃะฑะธะนัั‚ะฒะฐ ะผะพะฑะพะฒ -HTML - LABEL_MOST_ACTIVE_GAMEMODE || ะกะฐะผั‹ะน ะฐะบั‚ะธะฒะฝั‹ะน ะธะณั€ะพะฒะพะน ั€ะตะถะธะผ -HTML - LABEL_NAME || ะ˜ะผั -HTML - LABEL_NEW || ะะพะฒั‹ะน -HTML - LABEL_NEW_PLAYERS || ะะพะฒั‹ะต ะธะณั€ะพะบะธ -HTML - LABEL_NICKNAME || ะะธะบะฝะตะนะผ -HTML - LABEL_NO_SESSION_KILLS || ะะธะบั‚ะพ -HTML - LABEL_ONLINE_FIRST_JOIN || ะšะพะป-ะฒะพ ะธะณั€ะพะบะพะฒ ะพะฝะปะฐะนะฝ ะฟั€ะธ ะฟะตั€ะฒะพะผ ะฟั€ะธัะพะตะดะธะฝะตะฝะธะธ -HTML - LABEL_OPERATOR || ะžะฟะตั€ะฐั‚ะพั€ -HTML - LABEL_PER_PLAYER || / ะ˜ะณั€ะพะบ -HTML - LABEL_PER_REGULAR_PLAYER || / ะŸะพัั‚ะพัะฝะฝั‹ะน ะธะณั€ะพะบ -HTML - LABEL_PLAYER_DEATHS || ะกะผะตั€ั‚ัŒ ะธะท-ะทะฐ ะธะณั€ะพะบะพะฒ -HTML - LABEL_PLAYER_KILLS || ะฃะฑะธั‚ะพ ะธะณั€ะพะบะพะฒ -HTML - LABEL_PLAYERS_ONLINE || ะ˜ะณั€ะพะบะธ ะพะฝะปะฐะนะฝ -HTML - LABEL_PLAYTIME || ะ’ั€ะตะผั ะธะณั€ั‹ -HTML - LABEL_REGISTERED || ะ—ะฐั€ะตะณะธัั‚ั€ะธั€ะพะฒะฐะฝะฝั‹ะน -HTML - LABEL_REGISTERED_PLAYERS || ะ—ะฐั€ะตะณะธัั‚ั€ะธั€ะพะฒะฐะฝะฝั‹ะต ะธะณั€ะพะบะธ -HTML - LABEL_REGULAR || ะŸะพัั‚ะพัะฝะฝั‹ะน -HTML - LABEL_REGULAR_PLAYERS || ะŸะพัั‚ะพัะฝะฝั‹ะต ะธะณั€ะพะบะธ -HTML - LABEL_RELATIVE_JOIN_ACTIVITY || ะกั€ะฐะฒะฝะธั‚ะตะปัŒะฝะฐั ะฐะบั‚ะธะฒะฝะพัั‚ัŒ ะฟั€ะธัะพะตะดะธะฝะตะฝะธั -HTML - LABEL_RETENTION || ะกะพั…ั€ะฐะฝะตะฝะธะต ะฝะพะฒะพะณะพ ะธะณั€ะพะบะฐ -HTML - LABEL_SERVER_DOWNTIME || ะ’ั€ะตะผั ะฟั€ะพัั‚ะพั ัะตั€ะฒะตั€ะฐ -HTML - LABEL_SERVER_OCCUPIED || ะกะตั€ะฒะตั€ ะทะฐะฝัั‚ -HTML - LABEL_SESSION_ENDED || ะšะพะฝะตั† ัะตััะธะธ -HTML - LABEL_SESSION_MEDIAN || ะกั€ะตะดะฝัั ัะตััะธั -HTML - LABEL_TIMES_KICKED || ะšะพะป-ะฒะพ ะบะธะบะพะฒ -HTML - LABEL_TOTAL_PLAYERS || ะ’ัะตะณะพ ะธะณั€ะพะบะพะฒ -HTML - LABEL_TOTAL_PLAYTIME || ะžะฑั‰ะตะต ะฒั€ะตะผั ะธะณั€ั‹ -HTML - LABEL_UNIQUE_PLAYERS || ะฃะฝะธะบะฐะปัŒะฝั‹ะต ะธะณั€ะพะบะธ -HTML - LABEL_WEEK_DAYS || 'ะŸะพะฝะตะดะตะปัŒะฝะธะบ', 'ะ’ั‚ะพั€ะฝะธะบ', 'ะกั€ะตะดะฐ', 'ะงะตั‚ะฒะตั€ะณ', 'ะŸัั‚ะฝะธั†ะฐ', 'ะกัƒะฑะฑะพั‚ะฐ', 'ะ’ะพัะบั€ะตัะตะฝัŒะต' -HTML - LINK_BACK_NETWORK || ะกะตั‚ะตะฒะฐั ัั‚ั€ะฐะฝะธั†ะฐ -HTML - LINK_BACK_SERVER || ะกั‚ั€ะฐะฝะธั†ะฐ ัะตั€ะฒะตั€ะฐ -HTML - LINK_CHANGELOG || ะŸั€ะพัะผะพั‚ั€ ะถัƒั€ะฝะฐะปะฐ ะธะทะผะตะฝะตะฝะธะน -HTML - LINK_DISCORD || ะžะฑั‰ะฐั ะฟะพะดะดะตั€ะถะบะฐ ะฒ Discord -HTML - LINK_DOWNLOAD || ะกะบะฐั‡ะฐั‚ัŒ -HTML - LINK_ISSUES || ะกะพะพะฑั‰ะธั‚ัŒ ะพ ะฟั€ะพะฑะปะตะผะฐั… -HTML - LINK_NIGHT_MODE || ะะพั‡ะฝะพะน ั€ะตะถะธะผ -HTML - LINK_PLAYER_PAGE || ะกั‚ั€ะฐะฝะธั†ะฐ ะธะณั€ะพะบะฐ -HTML - LINK_QUICK_VIEW || ะ‘ั‹ัั‚ั€ั‹ะน ะฟั€ะพัะผะพั‚ั€ -HTML - LINK_SERVER_ANALYSIS || ะะฝะฐะปะธะท ัะตั€ะฒะตั€ะฐ -HTML - LINK_WIKI || Plan ะ’ะธะบะธ, ั‚ัƒั‚ะพั€ะธะฐะปั‹ ะธ ะดะพะบัƒะผะตะฝั‚ะฐั†ะธั -HTML - LOCAL_MACHINE || ะ›ะพะบะฐะปัŒะฝะฐั ะผะฐัˆะธะฝะฐ -HTML - LOGIN_CREATE_ACCOUNT || Create an Account! -HTML - LOGIN_FAILED || Login failed: -HTML - LOGIN_FORGOT_PASSWORD || Forgot Password? -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_1 || Forgot password? Unregister and register again. -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_2 || Use the following command in game to remove your current user: -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_3 || Or using console: -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_4 || After using the command, -HTML - LOGIN_LOGIN || Login -HTML - LOGIN_LOGOUT || Logout -HTML - LOGIN_PASSWORD || "Password" -HTML - LOGIN_USERNAME || "Username" -HTML - NAV_PLUGINS || ะŸะปะฐะณะธะฝั‹ -HTML - NEW_CALENDAR || ะะพะฒะพะต: -HTML - NO_KILLS || ะะตั‚ ัƒะฑะธะนัั‚ะฒ -HTML - NO_USER_PRESENT || ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะน ั„ะฐะนะป cookie ะพั‚ััƒั‚ัั‚ะฒัƒะตั‚ -HTML - NON_OPERATORS (Filters) || Non operators -HTML - NOT_BANNED (Filters) || Not banned -HTML - OFFLINE || ะะต ะฒ ัะตั‚ะธ -HTML - ONLINE || ะ’ ัะตั‚ะธ -HTML - OPERATORS (Filters) || Operators -HTML - PER_DAY || / ะ”ะตะฝัŒ -HTML - PLAYERS_TEXT || ะ˜ะณั€ะพะบะธ -HTML - QUERY || Query< -HTML - QUERY_ACTIVITY_OF_MATCHED_PLAYERS || Activity of matched players -HTML - QUERY_ACTIVITY_ON || Activity on -HTML - QUERY_ADD_FILTER || Add a filter.. -HTML - QUERY_AND || and -HTML - QUERY_ARE || `are` -HTML - QUERY_ARE_ACTIVITY_GROUP || are in Activity Groups -HTML - QUERY_ARE_PLUGIN_GROUP || are in ${plugin}'s ${group} Groups -HTML - QUERY_JOINED_WITH_ADDRESS || joined with address -HTML - QUERY_LOADING_FILTERS || Loading filters.. -HTML - QUERY_MAKE || Make a query -HTML - QUERY_MAKE_ANOTHER || Make another query -HTML - QUERY_OF_PLAYERS || of Players who -HTML - QUERY_PERFORM_QUERY || Perform Query! -HTML - QUERY_PLAYED_BETWEEN || Played between -HTML - QUERY_REGISTERED_BETWEEN || Registered between -HTML - QUERY_RESULTS || Query Results -HTML - QUERY_RESULTS_MATCH || matched ${resultCount} players -HTML - QUERY_SESSIONS_WITHIN_VIEW || Sessions within view -HTML - QUERY_SHOW_VIEW || Show a view -HTML - QUERY_TIME_FROM || >from -HTML - QUERY_TIME_TO || >to -HTML - QUERY_VIEW || View: -HTML - QUERY_ZERO_RESULTS || Query produced 0 results -HTML - REGISTER || Register -HTML - REGISTER_CHECK_FAILED || Checking registration status failed: -HTML - REGISTER_COMPLETE || Complete Registration -HTML - REGISTER_COMPLETE_INSTRUCTIONS_1 || You can now finish registering the user. -HTML - REGISTER_COMPLETE_INSTRUCTIONS_2 || Code expires in 15 minutes -HTML - REGISTER_COMPLETE_INSTRUCTIONS_3 || Use the following command in game to finish registration: -HTML - REGISTER_COMPLETE_INSTRUCTIONS_4 || Or using console: -HTML - REGISTER_CREATE_USER || Create a new user -HTML - REGISTER_FAILED || Registration failed: -HTML - REGISTER_HAVE_ACCOUNT || Already have an account? Login! -HTML - REGISTER_PASSWORD_TIP || Password should be more than 8 characters, but there are no limitations. -HTML - REGISTER_SPECIFY_PASSWORD || You need to specify a Password -HTML - REGISTER_SPECIFY_USERNAME || You need to specify a Username -HTML - REGISTER_USERNAME_LENGTH || Username can be up to 50 characters, yours is -HTML - REGISTER_USERNAME_TIP || Username can be up to 50 characters. -HTML - SESSION || ะกะตััะธั -HTML - SIDE_GEOLOCATIONS || ะ“ะตะพะปะพะบะฐั†ะธั -HTML - SIDE_INFORMATION || ะ˜ะะคะžะ ะœะะฆะ˜ะฏ -HTML - SIDE_LINKS || LINKS -HTML - SIDE_NETWORK_OVERVIEW || ะžะฑะทะพั€ ัะตั‚ะธ -HTML - SIDE_OVERVIEW || ะžะฑะทะพั€ -HTML - SIDE_PERFORMANCE || ะŸั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ัŒ -HTML - SIDE_PLAYER_LIST || ะกะฟะธัะพะบ ะธะณั€ะพะบะพะฒ -HTML - SIDE_PLAYERBASE || ะ‘ะฐะทะฐ ะธะณั€ะพะบะพะฒ -HTML - SIDE_PLAYERBASE_OVERVIEW || ะžะฑะทะพั€ ะฑะฐะทั‹ ะธะณั€ะพะบะพะฒ -HTML - SIDE_PLUGINS || ะŸะ›ะะ“ะ˜ะะซ -HTML - SIDE_PVP_PVE || PvP & PvE -HTML - SIDE_SERVERS || ะกะตั€ะฒะตั€ั‹ -HTML - SIDE_SERVERS_TITLE || ะกะ•ะ ะ’ะ•ะ ะซ -HTML - SIDE_SESSIONS || ะกะตััะธะธ -HTML - SIDE_TO_MAIN_PAGE || ะฝะฐ ะณะปะฐะฒะฝัƒัŽ ัั‚ั€ะฐะฝะธั†ัƒ -HTML - TEXT_CLICK_TO_EXPAND || ะะฐะถะผะธั‚ะต, ั‡ั‚ะพะฑั‹ ั€ะฐะทะฒะตั€ะฝัƒั‚ัŒ -HTML - TEXT_CONTRIBUTORS_CODE || ะฐะฒั‚ะพั€ ะบะพะดะฐ -HTML - TEXT_CONTRIBUTORS_LOCALE || ะฟะตั€ะตะฒะพะดั‡ะธะบ -HTML - TEXT_CONTRIBUTORS_MONEY || ะžัะพะฑะฐั ะฑะปะฐะณะพะดะฐั€ะฝะพัั‚ัŒ ั‚ะตะผ, ะบั‚ะพ ะพะบะฐะทะฐะป ั„ะธะฝะฐะฝัะพะฒัƒัŽ ะฟะพะดะดะตั€ะถะบัƒ ั€ะฐะทะฒะธั‚ะธัŽ. -HTML - TEXT_CONTRIBUTORS_THANKS || ะšั€ะพะผะต ั‚ะพะณะพ, ะดะฐะฝะฝั‹ะต ะทะฐะผะตั‡ะฐั‚ะตะปัŒะฝั‹ะต ะปัŽะดะธ ะฒะฝะตัะปะธ ัะฒะพะน ะฒะบะปะฐะด: -HTML - TEXT_DEV_VERSION || ะญั‚ะฐ ะฒะตั€ัะธั ัะฒะปัะตั‚ัั DEV ั€ะตะปะธะทะพะผ. -HTML - TEXT_DEVELOPED_BY || ั€ะฐะทั€ะฐะฑะพั‚ะฐะฝ -HTML - TEXT_FIRST_SESSION || ะŸะตั€ะฒะฐั ัะตััะธั -HTML - TEXT_LICENSED_UNDER || Player Analytics ั€ะฐะทั€ะฐะฑะพั‚ะฐะฝ ะธ ะปะธั†ะตะฝะทะธั€ะพะฒะฐะฝ ะฟะพะด -HTML - TEXT_METRICS || bStats Metrics -HTML - TEXT_NO_EXTENSION_DATA || ะะตั‚ ะดะฐะฝะฝั‹ั… ะพะฑ ั€ะฐััˆะธั€ะตะฝะธัั… -HTML - TEXT_NO_LOW_TPS || ะะตั‚ ะฝะธะทะบะธั… TPS -HTML - TEXT_NO_SERVER || ะะตั‚ ัะตั€ะฒะตั€ะฐ ะดะปั ะพั‚ะพะฑั€ะฐะถะตะฝะธั ะพะฝะปะฐะนะฝ ะฐะบั‚ะธะฒะฝะพัั‚ะธ -HTML - TEXT_NO_SERVERS || ะ’ ะฑะฐะทะต ะดะฐะฝะฝั‹ั… ะฝะต ะฝะฐะนะดะตะฝะพ ะฝะธ ะพะดะฝะพะณะพ ัะตั€ะฒะตั€ะฐ -HTML - TEXT_PLUGIN_INFORMATION || ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะพ ะฟะปะฐะณะธะฝะต -HTML - TEXT_PREDICTED_RETENTION || ะญั‚ะพ ะทะฝะฐั‡ะตะฝะธะต ัะฒะปัะตั‚ัั ะฟั€ะพะณะฝะพะทะพะผ ะฝะฐ ะพัะฝะพะฒะต ะฟั€ะตะดั‹ะดัƒั‰ะธั… ะธะณั€ะพะบะพะฒ -HTML - TEXT_SERVER_INSTRUCTIONS || It appears that Plan is not installed on any game servers or not connected to the same database. See wiki for Network tutorial. -HTML - TEXT_VERSION || ะะพะฒะฐั ะฒะตั€ัะธั ะฑั‹ะปะฐ ะฒั‹ะฟัƒั‰ะตะฝะฐ ะธ ั‚ะตะฟะตั€ัŒ ะดะพัั‚ัƒะฟะฝะฐ ะดะปั ัะบะฐั‡ะธะฒะฐะฝะธั. -HTML - TITLE_30_DAYS || 30 ะดะฝะตะน -HTML - TITLE_30_DAYS_AGO || 30 ะดะฝะตะน ะฝะฐะทะฐะด -HTML - TITLE_ALL || ะ’ัะต -HTML - TITLE_ALL_TIME || ะ’ัะต ะฒั€ะตะผั -HTML - TITLE_AS_NUMBERS || ะบะฐะบ ั‡ะธัะปะฐ -HTML - TITLE_AVG_PING || ะกั€ะตะดะฝะธะน ะฟะธะฝะณ -HTML - TITLE_BEST_PING || ะะฐะธะปัƒั‡ัˆะธะน ะฟะธะฝะณ -HTML - TITLE_CALENDAR || ะšะฐะปะตะฝะดะฐั€ัŒ -HTML - TITLE_CONNECTION_INFO || ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะพ ัะพะตะดะธะฝะตะฝะธะธ -HTML - TITLE_COUNTRY || ะกั‚ั€ะฐะฝะฐ -HTML - TITLE_CPU_RAM || ะฆะŸะฃ & ะžะ—ะฃ -HTML - TITLE_CURRENT_PLAYERBASE || ะขะตะบัƒั‰ะฐั ะฑะฐะทะฐ ะธะณั€ะพะบะพะฒ -HTML - TITLE_DISK || ะ”ะธัะบะพะฒะพะต ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒะพ -HTML - TITLE_GRAPH_DAY_BY_DAY || ะ”ะตะฝัŒ ะทะฐ ะดะฝะตะผ -HTML - TITLE_GRAPH_HOUR_BY_HOUR || Hour by Hour -HTML - TITLE_GRAPH_NETWORK_ONLINE_ACTIVITY || ะกะตั‚ะตะฒะฐั ะฐะบั‚ะธะฒะฝะพัั‚ัŒ -HTML - TITLE_GRAPH_PUNCHCARD || ะŸะตั€ั„ะพะบะฐั€ั‚ะฐ ะฝะฐ 30 ะดะฝะตะน -HTML - TITLE_INSIGHTS || ะกั‚ะฐั‚ะธัั‚ะธะบะฐ ะทะฐ 30 ะดะฝะตะน -HTML - TITLE_IS_AVAILABLE || ะดะพัั‚ัƒะฟะตะฝ -HTML - TITLE_JOIN_ADDRESSES || Join Addresses -HTML - TITLE_LAST_24_HOURS || ะŸะพัะปะตะดะฝะธะต 24 ั‡ะฐัะฐ -HTML - TITLE_LAST_30_DAYS || ะŸะพัะปะตะดะฝะธะต 30 ะดะฝะตะน -HTML - TITLE_LAST_7_DAYS || ะŸะพัะปะตะดะฝะธะต 7 ะดะฝะตะน -HTML - TITLE_LAST_CONNECTED || ะŸะพัะปะตะดะฝะตะต ะฟะพะดะบะปัŽั‡ะตะฝะธะต -HTML - TITLE_LENGTH || ะ”ะปะธะฝะฐ -HTML - TITLE_MOST_PLAYED_WORLD || ะกะฐะผั‹ะน ะฟะพะฟัƒะปัั€ะฝั‹ะน ะผะธั€ -HTML - TITLE_NETWORK || ะกะตั‚ัŒ -HTML - TITLE_NETWORK_AS_NUMBERS || ะกะตั‚ัŒ ะฒ ั‡ะธัะปะฐั… -HTML - TITLE_NOW || ะกะตะนั‡ะฐั -HTML - TITLE_ONLINE_ACTIVITY || ะกะตั‚ะตะฒะฐั ะฐะบั‚ะธะฒะฝะพัั‚ัŒ -HTML - TITLE_ONLINE_ACTIVITY_AS_NUMBERS || ะกะตั‚ะตะฒะฐั ะฐะบั‚ะธะฒะฝะพัั‚ัŒ ะฒ ั‡ะธัะปะฐั… -HTML - TITLE_ONLINE_ACTIVITY_OVERVIEW || ะžะฑะทะพั€ ัะตั‚ะตะฒะพะน ะฐะบั‚ะธะฒะฝะพัั‚ะธ -HTML - TITLE_PERFORMANCE_AS_NUMBERS || ะŸั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ัŒ ะฒ ั‡ะธัะปะฐั… -HTML - TITLE_PING || ะŸะธะฝะณ -HTML - TITLE_PLAYER || ะ˜ะณั€ะพะบ -HTML - TITLE_PLAYER_OVERVIEW || ะžะฑะทะพั€ ะธะณั€ะพะบะฐ -HTML - TITLE_PLAYERBASE_DEVELOPMENT || ะ ะฐะทั€ะฐะฑะพั‚ะบะฐ ะฑะฐะทั‹ ะธะณั€ะพะบะพะฒ -HTML - TITLE_PVP_DEATHS || ะะตะดะฐะฒะฝะธะต PvP ัะผะตั€ั‚ะธ -HTML - TITLE_PVP_KILLS || ะะตะดะฐะฒะฝะธะต PvP ัƒะฑะธะนัั‚ะฒะฐ -HTML - TITLE_PVP_PVE_NUMBERS || PvP & PvE ะฒ ั‡ะธัะปะฐั… -HTML - TITLE_RECENT_KILLS || ะะตะดะฐะฒะฝะธะต ัƒะฑะธะนัั‚ะฒะฐ -HTML - TITLE_RECENT_SESSIONS || ะะตะดะฐะฒะฝะธะต ัะตััะธะธ -HTML - TITLE_SEEN_NICKNAMES || ะฃะฒะธะดะตะฝะฝั‹ะต ะฝะธะบะฝะตะนะผั‹ -HTML - TITLE_SERVER || ะกะตั€ะฒะตั€ -HTML - TITLE_SERVER_AS_NUMBERS || ะกะตั€ะฒะตั€ ะฒ ั‡ะธัะปะฐั… -HTML - TITLE_SERVER_OVERVIEW || ะžะฑะทะพั€ ัะตั€ะฒะตั€ะฐ -HTML - TITLE_SERVER_PLAYTIME || ะ’ั€ะตะผั ะธะณั€ั‹ ะฝะฐ ัะตั€ะฒะตั€ะต -HTML - TITLE_SERVER_PLAYTIME_30 || ะ’ั€ะตะผั ะธะณั€ั‹ ะฝะฐ ัะตั€ะฒะตั€ะต ะทะฐ 30 ะดะฝะตะน -HTML - TITLE_SESSION_START || ะกะตััะธั ะฝะฐั‡ะฐะปะฐััŒ -HTML - TITLE_THEME_SELECT || ะ’ั‹ะฑะพั€ ั‚ะตะผั‹ -HTML - TITLE_TITLE_PLAYER_PUNCHCARD || ะŸะตั€ั„ะพะบะฐั€ั‚ั‹ -HTML - TITLE_TPS || TPS -HTML - TITLE_TREND || ะขะตะฝะดะตะฝั†ะธั -HTML - TITLE_TRENDS || ั‚ะตะฝะดะตะฝั†ะธั ะทะฐ 30 ะดะฝะตะน -HTML - TITLE_VERSION || ะ’ะตั€ัะธั -HTML - TITLE_WEEK_COMPARISON || ะกั€ะฐะฒะฝะตะฝะธะต ะฝะตะดะตะปะธ -HTML - TITLE_WORLD || ะ—ะฐะณั€ัƒะทะบะฐ ะผะธั€ะฐ -HTML - TITLE_WORLD_PLAYTIME || ะ’ั€ะตะผั ะธะณั€ั‹ ะฒ ะผะธั€ะต -HTML - TITLE_WORST_PING || ะะฐะธั…ัƒะดัˆะธะน ะฟะธะฝะณ -HTML - TOTAL_ACTIVE_TEXT || ะžะฑั‰ะฐั ะฐะบั‚ะธะฒะฝะพัั‚ัŒ -HTML - TOTAL_AFK || ะ’ัะตะณะพ AFK -HTML - TOTAL_PLAYERS || ะ’ัะตะณะพ ะธะณั€ะพะบะพะฒ -HTML - UNIQUE_CALENDAR || ะฃะฝะธะบะฐะปัŒะฝั‹ั…: -HTML - UNIT_CHUNKS || ะงะฐะฝะบะธ -HTML - UNIT_ENTITIES || ะžะฑัŠะตะบั‚ั‹ -HTML - UNIT_NO_DATA || ะะตั‚ ะดะฐะฝะฝั‹ั… -HTML - UNIT_THE_PLAYERS || ะ˜ะณั€ะพะบะธ -HTML - USER_AND_PASS_NOT_SPECIFIED || ะ˜ะผั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะธ ะฟะฐั€ะพะปัŒ ะฝะต ัƒะบะฐะทะฐะฝั‹ -HTML - USER_DOES_NOT_EXIST || ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะฝะต ััƒั‰ะตัั‚ะฒัƒะตั‚ -HTML - USER_INFORMATION_NOT_FOUND || Registration failed, try again (The code expires after 15 minutes) -HTML - USER_PASS_MISMATCH || ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะธ ะฟะฐั€ะพะปัŒ ะฝะต ัะพะฒะฟะฐะดะฐัŽั‚ -HTML - Version Change log || View Changelog -HTML - Version Current || You have version ${0} -HTML - Version Download || Download Plan-${0}.jar -HTML - Version Update || Update -HTML - Version Update Available || Version ${0} is Available! -HTML - Version Update Dev || This version is a DEV release. -HTML - Version Update Info || A new version has been released and is now available for download. -HTML - WARNING_NO_GAME_SERVERS || Some data requires Plan to be installed on game servers. -HTML - WARNING_NO_GEOLOCATIONS || Geolocation gathering needs to be enabled in the config (Accept GeoLite2 EULA). -HTML - WARNING_NO_SPONGE_CHUNKS || Chunks unavailable on Sponge -HTML - WITH || ะก -HTML ERRORS - ACCESS_DENIED_403 || ะ”ะพัั‚ัƒะฟ ะทะฐะบั€ั‹ั‚ -HTML ERRORS - AUTH_FAIL_TIPS_401 || - ะฃะฑะตะดะธั‚ะตััŒ, ั‡ั‚ะพ ะฒั‹ ะทะฐั€ะตะณะธัั‚ั€ะธั€ะพะฒะฐะปะธ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ั ะฟะพะผะพั‰ัŒัŽ /plan register
- ะŸั€ะพะฒะตั€ัŒั‚ะต ะฟั€ะฐะฒะธะปัŒะฝะพัั‚ัŒ ะธะผะตะฝะธ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะธ ะฟะฐั€ะพะปั
- ะ˜ะผั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะธ ะฟะฐั€ะพะปัŒ ั‡ัƒะฒัั‚ะฒะธั‚ะตะปัŒะฝั‹ ะบ ั€ะตะณะธัั‚ั€ัƒ

ะ•ัะปะธ ะฒั‹ ะทะฐะฑั‹ะปะธ ะฒะฐัˆ ะฟะฐั€ะพะปัŒ, ะฟะพะฟั€ะพัะธั‚ะต ัะพั‚ั€ัƒะดะฝะธะบะฐ ัƒะดะฐะปะธั‚ัŒ ะฒะฐัˆะตะณะพ ัั‚ะฐั€ะพะณะพ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะธ ะฟะตั€ะตั€ะตะณะธัั‚ั€ะธั€ะพะฒะฐั‚ัŒ ะฒะฐั. -HTML ERRORS - AUTHENTICATION_FAILED_401 || ะžัˆะธะฑะบะฐ ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธะธ. -HTML ERRORS - FORBIDDEN_403 || ะ—ะฐะฟั€ะตั‰ะตะฝะพ -HTML ERRORS - NO_SERVERS_404 || ะะตั‚ ัะตั€ะฒะตั€ะพะฒ ะฒ ัะตั‚ะธ ะดะปั ะฒั‹ะฟะพะปะฝะตะฝะธั ะทะฐะฟั€ะพัะฐ. -HTML ERRORS - NOT_FOUND_404 || ะะต ะพะฑะฝะฐั€ัƒะถะตะฝะฐ -HTML ERRORS - NOT_PLAYED_404 || Plan ะตั‰ะต ะฝะต ะฒะธะดะตะป ัั‚ะพะณะพ ะธะณั€ะพะบะฐ. -HTML ERRORS - PAGE_NOT_FOUND_404 || ะกั‚ั€ะฐะฝะธั†ะฐ ะฝะต ััƒั‰ะตัั‚ะฒัƒะตั‚. -HTML ERRORS - UNAUTHORIZED_401 || ะะต ะฐะฒั‚ะพั€ะธะทะพะฒะฐะฝ -HTML ERRORS - UNKNOWN_PAGE_404 || ะฃะฑะตะดะธั‚ะตััŒ, ั‡ั‚ะพ ะฒั‹ ะฟะพะปัƒั‡ะฐะตั‚ะต ะดะพัั‚ัƒะฟ ะบ ััั‹ะปะบะต, ะทะฐะดะฐะฝะฝะพะน ะบะพะผะฐะฝะดะพะน, ะŸั€ะธะผะตั€ั‹:

/player/{uuid/name}
/server/{uuid/name/id}

-HTML ERRORS - UUID_404 || UUID ะธะณั€ะพะบะฐ ะฝะต ะฝะฐะนะดะตะฝ ะฒ ะฑะฐะทะต ะดะฐะฝะฝั‹ั…. -In Depth Help - /plan db || Use different database subcommands to change the data in some way -In Depth Help - /plan db backup || Uses SQLite to backup the target database to a file. -In Depth Help - /plan db clear || Clears all Plan tables, removing all Plan-data in the process. -In Depth Help - /plan db hotswap || Reloads the plugin with the other database and changes the config to match. -In Depth Help - /plan db move || Overwrites contents in the other database with the contents in another. -In Depth Help - /plan db remove || Removes all data linked to a player from the Current database. -In Depth Help - /plan db restore || Uses SQLite backup file and overwrites contents of the target database. -In Depth Help - /plan db uninstalled || Marks a server in Plan database as uninstalled so that it will not show up in server queries. -In Depth Help - /plan disable || Disable the plugin or part of it until next reload/restart. -In Depth Help - /plan export || Performs an export to export location defined in the config. -In Depth Help - /plan import || Performs an import to load data into the database. -In Depth Help - /plan info || Display the current status of the plugin. -In Depth Help - /plan ingame || ะžั‚ะพะฑั€ะฐะถะฐะตั‚ ะฝะตะบะพั‚ะพั€ัƒัŽ ะธะฝั„ะพั€ะผะฐั†ะธัŽ ะพ ะธะณั€ะพะบะต ะฒ ะธะณั€ะต. -In Depth Help - /plan json || Allows you to download a player's data in json format. All of it. -In Depth Help - /plan logout || Give username argument to log out another user from the panel, give * as argument to log out everyone. -In Depth Help - /plan network || Obtain a link to the /network page, only does so on networks. -In Depth Help - /plan player || Obtain a link to the /player page of a specific player, or the current player. -In Depth Help - /plan players || Obtain a link to the /players page to see a list of players. -In Depth Help - /plan register || Use without arguments to get link to register page. Use --code [code] after registration to get a user. -In Depth Help - /plan reload || Disable and enable the plugin to reload any changes in config. -In Depth Help - /plan search || List all matching player names to given part of a name. -In Depth Help - /plan server || Obtain a link to the /server page of a specific server, or the current server if no arguments are given. -In Depth Help - /plan servers || List ids, names and uuids of servers in the database. -In Depth Help - /plan unregister || Use without arguments to unregister player linked user, or with username argument to unregister another user. -In Depth Help - /plan users || Lists web users as a table. -Manage - Confirm Overwrite || ะ”ะฐะฝะฝั‹ะต ะฒ ${0} ะฑัƒะดัƒั‚ ะฟะตั€ะตะทะฐะฟะธัะฐะฝั‹! -Manage - Confirm Removal || ะ”ะฐะฝะฝั‹ะต ะฒ ${0} ะฑัƒะดัƒั‚ ัƒะดะฐะปะตะฝั‹! -Manage - Fail || > ยงcะงั‚ะพ-ั‚ะพ ะฟะพัˆะปะพ ะฝะต ั‚ะฐะบ: ${0} -Manage - Fail File not found || > ยงcะคะฐะนะป ะฝะต ะฝะฐะนะดะตะฝ ะฒ ${0} -Manage - Fail Incorrect Database || > ยงc'${0}' ะฝะตะฟะพะดะดะตั€ะถะธะฒะฐะตะผะฐั ะฑะฐะทะฐ ะดะฐะฝะฝั‹ั…. -Manage - Fail No Exporter || ยงeะญะบัะฟะพั€ั‚ะตั€ '${0}' ะฝะต ััƒั‰ะตัั‚ะฒัƒะตั‚ -Manage - Fail No Importer || ยงeะ˜ะผะฟะพั€ั‚ะตั€ '${0}' ะฝะต ััƒั‰ะตัั‚ะฒัƒะตั‚ -Manage - Fail No Server || ะกะตั€ะฒะตั€ ั ะดะฐะฝะฝั‹ะผะธ ะฟะฐั€ะฐะผะตั‚ั€ะฐะผะธ ะฝะต ะฝะฐะนะดะตะฝ. -Manage - Fail Same Database || > ยงcะะตะปัŒะทั ั€ะฐะฑะพั‚ะฐั‚ัŒ ั ะพะดะฝะพะน ะธ ั‚ะพะน ะถะต ะฑะฐะทะพะน ะดะฐะฝะฝั‹ั…! -Manage - Fail Same server || ะะตะฒะพะทะผะพะถะฝะพ ะฟะพะผะตั‚ะธั‚ัŒ ัั‚ะพั‚ ัะตั€ะฒะตั€ ะบะฐะบ ัƒะดะฐะปะตะฝะฝั‹ะน (ะ’ั‹ ะฝะฐ ะฝะตะผ) -Manage - Fail, Confirmation || > ยงcะ”ะพะฑะฐะฒัŒั‚ะต ะฐั€ะณัƒะผะตะฝั‚ '-a' ะดะปั ะฟะพะดั‚ะฒะตั€ะถะดะตะฝะธั ะฒั‹ะฟะพะปะฝะตะฝะธั: ${0} -Manage - List Importers || ะ˜ะผะฟะพั€ั‚ะตั€ั‹: -Manage - Progress || ${0} / ${1} processed.. -Manage - Remind HotSwap || ยงeะะต ะทะฐะฑัƒะดัŒั‚ะต ะฟะพะผะตะฝัั‚ัŒ ะฝะฐ ะฝะพะฒัƒัŽ ะฑะฐะทัƒ ะดะฐะฝะฝั‹ั… (/plan db hotswap ${0}) ะธ ะฟะตั€ะตะทะฐะณั€ัƒะทะธั‚ัŒ ะฟะปะฐะณะธะฝ. -Manage - Start || > ยง2ะžะฑั€ะฐะฑะพั‚ะบะฐ ะดะฐะฝะฝั‹ั….. -Manage - Success || > ยงaะฃัะฟะตั…! -Negative || ะะตั‚ -Positive || ะ”ะฐ -Today || 'ะกะตะณะพะดะฝั' -Unavailable || ะะตะดะพัั‚ัƒะฟะตะฝ -Unknown || ะะตะธะทะฒะตัั‚ะฝั‹ะน -Version - DEV || ะญั‚ะพ DEV ั€ะตะปะธะท. -Version - Latest || ะ’ั‹ ะธัะฟะพะปัŒะทัƒะตั‚ะต ะฟะพัะปะตะดะฝัŽัŽ ะฒะตั€ัะธัŽ. -Version - New || ะะพะฒั‹ะน ั€ะตะปะธะท (${0}) ะดะพัั‚ัƒะฟะตะฝ ${1} -Version - New (old) || ะะพะฒะฐั ะฒะตั€ัะธั ะดะพัั‚ัƒะฟะฝะฐ ะฝะฐ ${0} -Version FAIL - Read info (old) || ะะต ัƒะดะฐะปะพััŒ ะฟั€ะพะฒะตั€ะธั‚ัŒ ะฝะพะผะตั€ ะฟะพัะปะตะดะฝะตะน ะฒะตั€ัะธะธ -Version FAIL - Read versions.txt || ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะพ ะฒะตั€ัะธะธ ะฝะต ะผะพะถะตั‚ ะฑั‹ั‚ัŒ ะทะฐะณั€ัƒะถะตะฝะฐ ะธะท Github/versions.txt -Web User Listing || ยง2${0} ยง7: ยงf${1} -WebServer - Notify HTTP || ะ’ะตะฑ-ัะตั€ะฒะตั€: ะะตั‚ ัะตั€ั‚ะธั„ะธะบะฐั‚ะฐ -> ะ˜ัะฟะพะปัŒะทะพะฒะฐะฝะธะต HTTP-ัะตั€ะฒะตั€ะฐ ะดะปั ะฒะธะทัƒะฐะปะธะทะฐั†ะธะธ. -WebServer - Notify HTTP User Auth || ะ’ะตะฑ-ัะตั€ะฒะตั€: ะฐะฒั‚ะพั€ะธะทะฐั†ะธั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะพั‚ะบะปัŽั‡ะตะฝะฐ! (ะะต ะทะฐั‰ะธั‰ะตะฝ ั‡ะตั€ะตะท HTTP) -WebServer - Notify HTTPS User Auth || ะ’ะตะฑ-ัะตั€ะฒะตั€: ะฐะฒั‚ะพั€ะธะทะฐั†ะธั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะพั‚ะบะปัŽั‡ะตะฝะฐ! (ะžั‚ะบะปัŽั‡ะตะฝะพ ะฒ ะบะพะฝั„ะธะณะต) -Webserver - Notify IP Whitelist || ะ’ะตะฑ-ัะตั€ะฒะตั€: ะ‘ะตะปั‹ะน ัะฟะธัะพะบ IP ะฐะดั€ะตัะพะฒ ะฒะบะปัŽั‡ั‘ะฝ. -Webserver - Notify IP Whitelist Block || ะ’ะตะฑ-ัะตั€ะฒะตั€: ${0} ะฑั‹ะปะพ ะพั‚ะบะฐะทะฐะฝะพ ะฒ ะดะพัั‚ัƒะฟะต ะบ '${1}'. (ะฝะต ะฒ ะฑะตะปะพะผ ัะฟะธัะบะต) -WebServer - Notify no Cert file || ะ’ะตะฑ-ัะตั€ะฒะตั€: ั„ะฐะนะป ั…ั€ะฐะฝะธะปะธั‰ะฐ ะบะปัŽั‡ะตะน ัะตั€ั‚ะธั„ะธะบะฐั‚ะฐ ะฝะต ะฝะฐะนะดะตะฝ: ${0} -WebServer - Notify Using Proxy || ะ’ะตะฑ-ัะตั€ะฒะตั€: HTTPS ะฒ ั€ะตะถะธะผะต ะฟั€ะพะบัะธ ะฒะบะปัŽั‡ะตะฝ, ัƒะฑะตะดะธั‚ะตััŒ, ั‡ั‚ะพ ะฒะฐัˆ ะพะฑั€ะฐั‚ะฝั‹ะน ะฟั€ะพะบัะธ-ัะตั€ะฒะตั€ ะฒั‹ะฟะพะปะฝัะตั‚ ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั†ะธัŽ ั ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะตะผ HTTPS, ะธ Plan Alternative_IP.Address ัƒะบะฐะทั‹ะฒะฐะตั‚ ะฝะฐ ะฟั€ะพะบัะธ -WebServer FAIL - EOF || ะ’ะตะฑ-ัะตั€ะฒะตั€: EOF ะฟั€ะธ ั‡ั‚ะตะฝะธะธ ั„ะฐะนะปะฐ ัะตั€ั‚ะธั„ะธะบะฐั‚ะฐ. (ะฃะฑะตะดะธั‚ะตััŒ, ั‡ั‚ะพ ั„ะฐะนะป ะฝะต ะฟัƒัั‚ะพะน) -WebServer FAIL - Port Bind || ะ’ะตะฑ-ัะตั€ะฒะตั€ ะฝะต ะฑั‹ะป ัƒัะฟะตัˆะฝะพ ะธะฝะธั†ะธะฐะปะธะทะธั€ะพะฒะฐะฝ. ะŸะพั€ั‚ (${0}) ะธัะฟะพะปัŒะทัƒะตั‚ัั? -WebServer FAIL - SSL Context || ะ’ะตะฑ-ัะตั€ะฒะตั€: ัะฑะพะน ะธะฝะธั†ะธะฐะปะธะทะฐั†ะธะธ ะบะพะฝั‚ะตะบัั‚ะฐ SSL. -WebServer FAIL - Store Load || ะ’ะตะฑ-ัะตั€ะฒะตั€: ัะฑะพะน ะทะฐะณั€ัƒะทะบะธ ัะตั€ั‚ะธั„ะธะบะฐั‚ะฐ SSL. -Yesterday || 'ะ’ั‡ะตั€ะฐ' diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_RU.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_RU.yml new file mode 100644 index 000000000..e58660b08 --- /dev/null +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_RU.yml @@ -0,0 +1,669 @@ +403AccessDenied: "ะ”ะพัั‚ัƒะฟ ะทะฐะบั€ั‹ั‚" +command: + argument: + backupFile: + description: "ะ˜ะผั ั€ะตะทะตั€ะฒะฝะพะน ะบะพะฟะธะธ (ั‡ัƒะฒัั‚ะฒะธั‚ะตะปัŒะฝะพ ะบ ั€ะตะณะธัั‚ั€ัƒ)" + name: "ะ ะตะทะตั€ะฒะฝะฐั ะบะพะฟะธั" + code: + description: "ะšะพะด, ะธัะฟะพะปัŒะทะพะฒะฐะฝะฝั‹ะน ะดะปั ั€ะตะณะธัั‚ั€ะฐั†ะธะธ." + name: "${code}" + dbBackup: + description: "ะขะธะฟ ะดะฐั‚ะฐะฑะฐะทั‹ ะดะปั ั€ะตะทะตั€ะฒะฝะพะน ะบะพะฟะธะธ. ะขะตะบัƒั‰ะฐั ะฑะฐะทะฐ ะดะฐะฝะฝั‹ั…, ะตัะปะธ ะฝะต ัƒะบะฐะทะฐะฝะพ." + dbRestore: + description: "ะขะธะฟ ะดะฐั‚ะฐะฑะฐะทั‹ ะดะปั ะฒะพัั‚ะฐะฝะพะฒะปะตะฝะธั. ะขะตะบัƒั‰ะฐั ะฑะฐะทะฐ ะดะฐะฝะฝั‹ั…, ะตัะปะธ ะฝะต ัƒะบะฐะทะฐะฝะพ." + dbTypeHotswap: + description: "ะขะธะฟ ะดะฐั‚ะฐะฑะฐะทั‹ ะดะปั ะฝะฐั‡ะฐะปะฐ ะธัะฟะพะปัŒะทะพะฒะฐะฝะธั." + dbTypeMoveFrom: + description: "ะขะธะฟ ะดะฐั‚ะฐะฑะฐะทั‹ ะดะปั ะฟะตั€ะตะผะตัั‚ะธั‚ัŒ ะธะฝั„ะพั€ะผะฐั†ะธัŽ ั." + dbTypeMoveTo: + description: "ะขะธะฟ ะดะฐั‚ะฐะฑะฐะทั‹ ะดะปั ะฟะตั€ะตะผะตัั‚ะธั‚ัŒ ะธะฝั„ะพั€ะผะฐั†ะธัŽ ะฒ. ะœะพะถะตั‚ ะฑั‹ั‚ัŒ ะฝะต ะพะดะธะฝะฐะบะพะฒะพะน ั ะฟั€ะตะดะธะดัƒั‰ะตะน." + dbTypeRemove: + description: "ะขะธะฟ ะดะฐั‚ะฐะฑะฐะทั‹ ะดะปั ัƒะดะฐะปะตะฝะธั ะฒัะตั… ะดะฐะฝะฝั‹ั…." + exportKind: "ะฒะธะด ัะบัะฟะพั€ั‚ะฐ" + feature: + description: "ะขะธะฟ ะดะฐั‚ะฐะฑะฐะทั‹ ะดะปั ะพั‚ะบะปัŽั‡ะตะฝะธั: ${0}" + name: "ั„ัƒะฝะบั†ะธั" + importKind: "ะฒะธะด ะธะผะฟะพั€ั‚ะฐ" + nameOrUUID: + description: "ะะธะบ ะธะปะธ UUID ะธะณั€ะพะบะฐ" + name: "ะฝะธะบ/uuid" + removeDescription: "ะ˜ะดะตะฝั‚ะธั„ะธะบะฐั‚ะพั€ ะธะณั€ะพะบะฐ, ะบะพั‚ะพั€ั‹ะน ะฑัƒะดะตั‚ ัƒะดะฐะปะตะฝ ะธะท ั‚ะตะบัƒั‰ะตะน ะฑะฐะทั‹ ะดะฐะฝะฝั‹ั…." + server: + description: "ะะธะบ, ID ะธะปะธ UUID ัะตั€ะฒะตั€ะฐ" + name: "ัะตั€ะฒะตั€" + subcommand: + description: "ะ˜ัะฟะพะปัŒะทัƒะนั‚ะต ะบะพะผะฐะฝะดัƒ ะฑะตะท ะฐั€ะณัƒะผะตะฝั‚ะพะฒ, ะดะปั ะฟะพะผะพั‰ะธ." + name: "ะฟะพะด-ะบะพะผะฐะฝะดะฐ" + username: + description: "ะะธะบ ะดั€ัƒะณะพะณะพ ะธะณั€ะพะบะฐ. ะ•ัะปะธ ะฝะต ัƒะบะฐะทะฐะฝ ะธะณั€ะพะบ, ะธัะฟะพะปัŒะทัƒะตั‚ัั ัะฒัะทะฐะฝะฝั‹ะน ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒ." + name: "ะฝะธะบ" + confirmation: + accept: "ะŸั€ะธะฝัั‚ัŒ" + cancelNoChanges: "ะžั‚ะผะตะฝะตะฝะพ. ะะธั‡ะตะณะพ ะฝะต ะฑั‹ะปะพ ะธะทะผะตะฝะตะฝะพ." + cancelNoUnregister: "ะžั‚ะผะตะฝะตะฝะพ. '${0}' ะฝะต ะฑั‹ะป ัƒะดะฐะปั‘ะฝ" + confirm: "ะŸะพะดั‚ะฒะตั€ะดะธั‚ัŒ: " + dbClear: "ะ’ั‹ ั‚ะพั‡ะฝะพ ั…ะพั‚ะธั‚ะต ัƒะดะฐะปะธั‚ัŒ ะฒััŽ Plan-ะดะฐั‚ัƒ ะฒ ${0}" + dbOverwrite: "ะ’ั‹ ั‚ะพั‡ะฝะพ ั…ะพั‚ะธั‚ะต ะฟะตั€ะตะฟะธัะฐั‚ัŒ ะฒััŽ ะธะฝั„ะพั€ะผะฐั†ะธัŽ ${0} ะฝะฐ ${1}" + dbRemovePlayer: "ะ’ั‹ ั‚ะพั‡ะฝะพ ั…ะพั‚ะธั‚ะต ัƒะดะฐะปะธั‚ัŒ ะธะณั€ะพะบะฐ ${0} ั ${1}" + deny: "ะžั‚ะผะตะฝะธั‚ัŒ" + expired: "ะกั€ะพะบ ะฟะพะดั‚ะฒะตั€ะถะดะตะฝะธั ะธัั‚ะตะบ, ะธัะฟะพะปัŒะทัƒะนั‚ะต ะบะพะผะฐะฝะดัƒ ะทะฐะฝะพะฒะพ" + unregister: "ะ’ั‹ ั‚ะพั‡ะฝะพ ั…ะพั‚ะธั‚ะต ัƒะดะฐะปะธั‚ัŒ '${0}', ะฟั€ะธะฒัะทะฐะฝะฝะพะณะพ ะบ ${1}" + database: + creatingBackup: "ะกะพะทะดะฐั‘ะผ ั€ะตะทะตั€ะฒะฝัƒัŽ ะบะพะฟะธัŽ '${0}.db' ั ะดะฐะฝะฝั‹ะผะธ ะพ ${1}" + failDbNotOpen: "ยงcะ‘ะฐะทะฐ ะดะฐะฝะฝั‹ั… ${0} - ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะฟะพะฟั€ะพะฑัƒะนั‚ะต ัะฝะพะฒะฐ ั‡ัƒั‚ัŒ ะฟะพะทะถะต." + manage: + confirm: "> ยงcะ”ะพะฑะฐะฒัŒั‚ะต ะฐั€ะณัƒะผะตะฝั‚ '-a' ะดะปั ะฟะพะดั‚ะฒะตั€ะถะดะตะฝะธั ะฒั‹ะฟะพะปะฝะตะฝะธั: ${0}" + confirmOverwrite: "ะ”ะฐะฝะฝั‹ะต ะฒ ${0} ะฑัƒะดัƒั‚ ะฟะตั€ะตะทะฐะฟะธัะฐะฝั‹!" + confirmPartialRemoval: "ะ”ะฐะฝะฝั‹ะต ะฐะดั€ะตัะฐ ะฟั€ะธัะพะตะดะธะฝะตะฝะธั ะดะปั ัะตั€ะฒะตั€ะฐ ${0} ะฒ ${1} ะฑัƒะดัƒั‚ ัƒะดะฐะปะตะฝั‹!" + confirmRemoval: "ะ”ะฐะฝะฝั‹ะต ะฒ ${0} ะฑัƒะดัƒั‚ ัƒะดะฐะปะตะฝั‹!" + fail: "> ยงcะงั‚ะพ-ั‚ะพ ะฟะพัˆะปะพ ะฝะต ั‚ะฐะบ: ${0}" + failFileNotFound: "> ยงcะคะฐะนะป ะฝะต ะฝะฐะนะดะตะฝ ะฒ ${0}" + failIncorrectDB: "> ยงc'${0}' ะฝะตะฟะพะดะดะตั€ะถะธะฒะฐะตะผะฐั ะฑะฐะทะฐ ะดะฐะฝะฝั‹ั…." + failNoServer: "ะกะตั€ะฒะตั€ ั ะดะฐะฝะฝั‹ะผะธ ะฟะฐั€ะฐะผะตั‚ั€ะฐะผะธ ะฝะต ะฝะฐะนะดะตะฝ." + failSameDB: "> ยงcะะตะปัŒะทั ั€ะฐะฑะพั‚ะฐั‚ัŒ ั ะพะดะฝะพะน ะธ ั‚ะพะน ะถะต ะฑะฐะทะพะน ะดะฐะฝะฝั‹ั…!" + failSameServer: "ะะตะฒะพะทะผะพะถะฝะพ ะฟะพะผะตั‚ะธั‚ัŒ ัั‚ะพั‚ ัะตั€ะฒะตั€ ะบะฐะบ ัƒะดะฐะปะตะฝะฝั‹ะน (ะ’ั‹ ะฝะฐ ะฝั‘ะผ)" + hotswap: "ยงeะะต ะทะฐะฑัƒะดัŒั‚ะต ะฟะพะผะตะฝัั‚ัŒ ะฝะฐ ะฝะพะฒัƒัŽ ะฑะฐะทัƒ ะดะฐะฝะฝั‹ั… (/plan db hotswap ${0}) ะธ ะฟะตั€ะตะทะฐะณั€ัƒะทะธั‚ัŒ ะฟะปะฐะณะธะฝ." + importers: "ะ˜ะผะฟะพั€ั‚ะตั€ั‹:" + preparing: "ะŸะพะดะณะพั‚ะพะฒะบะฐ.." + progress: "${0} / ${1} ัะดะตะปะฐะฝะพ.." + start: "> ยง2ะžะฑั€ะฐะฑะพั‚ะบะฐ ะดะฐะฝะฝั‹ั….." + success: "> ยงaะฃัะฟะตั…!" + playerRemoval: "ะฃะดะฐะปัะตะผ ะธะณั€ะพะบะฐ ${0} ั ${1}.." + removal: "ะฃะดะฐะปัะตะผ ะดะฐั‚ัƒ ั ${0}.." + serverUninstalled: "ยงaะ•ัะปะธ ัะตั€ะฒะตั€ ะฒัั‘ ะตั‰ะต ัƒัั‚ะฐะฝะพะฒะปะตะฝ, ะพะฝ ะดะพะฑะฐะฒะธั‚ัั ะบะฐะบ ัƒัั‚ะฐะฝะพะฒะปะตะฝะฝั‹ะน ะฒ ะฑะฐะทะต ะดะฐะฝะฝั‹ั…." + unregister: "ะฃะดะฐะปัะตะผ '${0}'.." + warnDbNotOpen: "ยงeะ‘ะฐะทะฐ ะดะฐะฝะฝั‹ั… ${0} - ะญั‚ะพ ะผะพะถะตั‚ ะทะฐะฝัั‚ัŒ ะฑะพะปัŒัˆะต ะฒั€ะตะผะตะฝะธ, ั‡ะตะผ ะพะถะธะดะฐะปะพััŒ." + write: "ะ—ะฐะฟะธัั‹ะฒะฐะตะผ ะฒ ${0}.." + fail: + emptyString: "ะกั‚ั€ะพะบะฐ ะดะปั ะฟะพะธัะบะฐ ะฝะต ะผะพะถะตั‚ ะฑั‹ั‚ัŒ ะฟัƒัั‚ะพะน" + invalidArguments: "ะŸั€ะธะฝะธะผะฐะตั‚ ัะปะตะดัƒัŽั‰ะตะต ะบะฐะบ ${0}: ${1}" + invalidUsername: "ยงcะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะฝะต ะธะผะตะตั‚ UUID." + missingArguments: "ยงcะขั€ะตะฑัƒะตั‚ัั ะฐั€ะณัƒะผะตะฝั‚ (${0}) ${1}" + missingFeature: "ยงeะžะฟั€ะตะดะตะปะธั‚ะต ั„ัƒะฝะบั†ะธัŽ ะดะปั ะพั‚ะบะปัŽั‡ะตะฝะธั! (ะฒ ะฝะฐัั‚ะพัั‰ะตะต ะฒั€ะตะผั ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ ${0})" + missingLink: "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะฝะต ัะฒัะทะฐะฝ ั ะฒะฐัˆะตะน ัƒั‡ะตั‚ะฝะพะน ะทะฐะฟะธััŒัŽ, ะธ ัƒ ะฒะฐั ะฝะตั‚ ะฟั€ะฐะฒ ะฝะฐ ัƒะดะฐะปะตะฝะธะต ัƒั‡ะตั‚ะฝั‹ั… ะทะฐะฟะธัะตะน ะดั€ัƒะณะธั… ะฟะพะปัŒะทะพะฒะฐั‚ะตะปะตะน." + noPermission: "ยงcะฃ ะฒะฐั ะฝะตั‚ ะฝะตะพะฑั…ะพะดะธะผั‹ั… ะฟั€ะฐะฒ." + onAccept: "ะ’ะพ ะฒั€ะตะผั ะฒั‹ะฟะพะปะฝะตะฝะธั ะฟะพะดั‚ะฒะตั€ะถะดะตะฝะฝะพะณะพ ะดะตะนัั‚ะฒะธั ะฒะพะทะฝะธะบะปะฐ ะพัˆะธะฑะบะฐ: ${0}" + onDeny: "ะ’ะพ ะฒั€ะตะผั ะฒั‹ะฟะพะปะฝะตะฝะธั ะพั‚ะผะตะฝะตะฝะฝะพะณะพ ะดะตะนัั‚ะฒะธั ะฒะพะทะฝะธะบะปะฐ ะพัˆะธะฑะบะฐ: ${0}" + playerNotFound: "ะ˜ะณั€ะพะบ '${0}' ะฝะต ะฑั‹ะป ะฝะฐะนะดะตะฝ, ะพะฝ ะฝะต ะธะผะตะตั‚ UUID." + playerNotInDatabase: "ะ˜ะณั€ะพะบ '${0}' ะฝะต ะฑั‹ะป ะฝะฐะนะดะตะฝ ะฒ ะฑะฐะทะต ะดะฐะฝะฝั‹ั…." + seeConfig: "ัะผะพั‚ั€ะธั‚ะต '${0}' ะฒ config.yml" + serverNotFound: "ะกะตั€ะฒะตั€ '${0}' ะฝะต ะฑั‹ะป ะฝะฐะนะดะตะฝ ะฒ ะฑะฐะทะต ะดะฐะฝะฝั‹ั…." + tooManyArguments: "ยงcะขั€ะตะฑัƒะตั‚ัั ั‚ะพะปัŒะบะพ ะพะดะธะฝ ะฐั€ะณัƒะผะตะฝั‚ ${1}" + unknownUsername: "ยงcะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะฝะต ะฑั‹ะป ะทะฐะผะตั‡ะตะฝ ะฝะฐ ัั‚ะพะผ ัะตั€ะฒะตั€ะต" + webUserExists: "ยงcะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ัƒะถะต ััƒั‰ะตัั‚ะฒัƒะตั‚!" + webUserNotFound: "ยงcะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะฝะต ััƒั‰ะตัั‚ะฒัƒะตั‚!" + footer: + help: "ยง7ะะฐะฒะตะดะธั‚ะต ัƒะบะฐะทะฐั‚ะตะปัŒ ะผั‹ัˆะธ ะฝะฐ ะบะพะผะฐะฝะดัƒ ะธะปะธ ะฐั€ะณัƒะผะตะฝั‚ั‹, ะธะปะธ ะธัะฟะพะปัŒะทัƒะนั‚ะต '/${0} ?' ะดะปั ัะฟั€ะฐะฒะบะธ." + general: + failNoExporter: "ยงeะญะบัะฟะพั€ั‚ะตั€ '${0}' ะฝะต ััƒั‰ะตัั‚ะฒัƒะตั‚" + failNoImporter: "ยงeะ˜ะผะฟะพั€ั‚ะตั€ '${0}' ะฝะต ััƒั‰ะตัั‚ะฒัƒะตั‚" + featureDisabled: "ยงaะ’ั€ะตะผะตะฝะฝะพ ะพั‚ะบะปัŽั‡ะตะฝะพ '${0}' ะดะพ ัะปะตะดัƒัŽั‰ะตะน ะฟะตั€ะตะทะฐะณั€ัƒะทะบะธ ะฟะปะฐะณะธะฝะฐ." + noAddress: "ยงeะะดั€ะตั ะฝะตะดะพัั‚ัƒะฟะตะฝ - ะธัะฟะพะปัŒะทัƒะตั‚ัั localhost ะฒ ะบะฐั‡ะตัั‚ะฒะต ะทะฐะฟะฐัะฝะพะณะพ IP. ะะฐัั‚ั€ะพะนั‚ะต 'Alternative_IP'." + noWebuser: "ะ’ะพะทะผะพะถะฝะพ, ะฒั‹ ะฝะต ะธะผะตะตั‚ะต ะฒะตะฑ-ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั, ะธัะฟะพะปัŒะทัƒะนั‚ะต /plan register <ะฟะฐั€ะพะปัŒ>" + notifyWebUserRegister: "ะ—ะฐั€ะตะณะธัั‚ั€ะธั€ะพะฒะฐะฝ ะฝะพะฒั‹ะน ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒ: '${0}' ะฃั€ะพะฒะตะฝัŒ ะฟั€ะฐะฒ: ${1}" + pluginDisabled: "ยงaPlan ัะตะนั‡ะฐั ะพั‚ะบะปัŽั‡ะตะฝ. ะ’ั‹ ะฒัั‘ ะตั‰ะต ะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ reload ะดะปั ะฟะตั€ะตะทะฐะณั€ัƒะทะบะธ ะฟะปะฐะณะธะฝะฐ." + reloadComplete: "ยงaะŸะตั€ะตะทะฐะณั€ัƒะทะบะฐ ะทะฐะฒะตั€ัˆะตะฝะฐ" + reloadFailed: "ยงcะงั‚ะพ-ั‚ะพ ะฟะพัˆะปะพ ะฝะต ั‚ะฐะบ ะฒะพ ะฒั€ะตะผั ะฟะตั€ะตะทะฐะณั€ัƒะทะบะธ ะฟะปะฐะณะธะฝะฐ, ั€ะตะบะพะผะตะฝะดัƒะตั‚ัั ะฟะตั€ะตะทะฐะฟัƒัะบ ัะตั€ะฒะตั€ะฐ." + successWebUserRegister: "ยงaะะพะฒั‹ะน ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒ (${0}) ัƒัะฟะตัˆะฝะพ ะดะพะฑะฐะฒะปะตะฝ!" + webPermissionLevels: ">\ยง70: ะ”ะพัั‚ัƒะฟ ะบะพ ะฒัะตะผ ัั‚ั€ะฐะฝะธั†ะฐะผ\ยง71: ะ”ะพัั‚ัƒะฟ ะบ '/players' ะธ ะฒัะตะผ ัั‚ั€ะฐะฝะธั†ะฐะผ ะธะณั€ะพะบะพะฒ\ยง72: ะ”ะพัั‚ัƒะฟ ะบ ัั‚ั€ะฐะฝะธั†ะต ะธะณั€ะพะบะฐ ั ั‚ะตะผ ะถะต ะธะผะตะฝะตะผ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั, ั‡ั‚ะพ ะธ ะดะปั ะฒะตะฑ-ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั\ยง73+: ะะตั‚ ะฟั€ะฐะฒ" + webUserList: " ยง2${0} ยง7: ยงf${1}" + header: + analysis: "> ยง2ะ ะตะทัƒะปัŒั‚ะฐั‚ั‹ ะฐะฝะฐะปะธะทะฐ" + help: "> ยง2/${0} ะŸะพะผะพั‰ัŒ" + info: "> ยง2ะะฝะฐะปะธั‚ะธะบะฐ ะธะณั€ะพะบะฐ" + inspect: "> ยง2ะ˜ะณั€ะพะบ: ยงf${0}" + network: "> ยง2ะกะตั‚ะตะฒะฐั ัั‚ั€ะฐะฝะธั†ะฐ" + players: "> ยง2ะ˜ะณั€ะพะบะธ" + search: "> ยง2${0} ะ ะตะทัƒะปัŒั‚ะฐั‚ั‹ ะดะปั ยงf${1}ยง2:" + serverList: "id::ะฝะฐะทะฒะฐะฝะธะต::uuid::version" + servers: "> ยง2ะกะตั€ะฒะตั€ั‹" + webUserList: "ะธะผั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั::ะฟั€ะธะฒัะทะฐะฝ ะบ::ัƒั€ะพะฒะตะฝัŒ ะฟั€ะฐะฒ" + webUsers: "> ยง2${0} ะ’ะตะฑ-ะฟะพะปัŒะทะพะฒะฐั‚ะตะปะธ" + help: + database: + description: "ะฃะฟั€ะฐะฒะปะตะฝะธะต ะดะฐั‚ะฐะฑะฐะทะพะน Plan" + inDepth: "ะ˜ัะฟะพะปัŒะทัƒะนั‚ะต ั€ะฐะทะฝั‹ะต ะฐั€ะณัƒะผะตะฝั‚ั‹, ั‡ั‚ะพะฑั‹ ะธะทะผะตะฝะธั‚ัŒ ะฑะฐะทัƒ ะดะฐะฝะฝั‹ั…." + dbBackup: + description: "ะกะดะตะปะฐั‚ัŒ ั€ะตะทะตั€ะฒะฝัƒัŽ ะบะพะฟะธัŽ ะฑะฐะทั‹ ะดะฐะฝะฝั‹ั… ะฒ ั„ะฐะนะป" + inDepth: "ะ˜ัะฟะพะปัŒะทัƒะตั‚ SQLite ะดะปั ั€ะตะทะตั€ะฒะฝะพะน ะบะพะฟะธะธ ะฝัƒะถะฝะพะน ะฑะฐะทั‹ ะดะฐะฝะฝั‹ั… ะฒ ั„ะฐะนะป." + dbClear: + description: "ะฃะดะฐะปะธั‚ัŒ ะ’ะกะฎ Plan ะธะฝั„ะพั€ะผะฐั†ะธัŽ ั ะฑะฐะทั‹ ะดะฐะฝะฝั‹ั…" + inDepth: "ะžั‡ะธั‰ะฐะตั‚ ะฒัะต ั‚ะฐะฑะปะธั†ั‹ Plan, ัƒะดะฐะปัั ะฒัะต ะดะฐะฝะฝั‹ะต Plan ะฒ ะฟั€ะพั†ะตััะต." + dbHotswap: + description: "ะ‘ั‹ัั‚ั€ะพะต ะธะทะผะตะฝะตะฝะธะต ะฑะฐะทั‹ ะดะฐะฝะฝั‹ั…" + inDepth: "ะŸะตั€ะตะทะฐะณั€ัƒะถะฐะตั‚ ะฟะปะฐะณะธะฝ ั ะดั€ัƒะณะพะน ะฑะฐะทะพะน ะดะฐะฝะฝั‹ั… ะธ ะธะทะผะตะฝัะตั‚ ะบะพะฝั„ะธะณัƒั€ะฐั†ะธัŽ ัะพะพั‚ะฒะตั‚ัั‚ะฒะตะฝะฝะพ." + dbMove: + description: "ะŸะตั€ะตะผะตั‰ะตะฝะธะต ะดะฐะฝะฝั‹ั… ะผะตะถะดัƒ ะฑะฐะทะฐะผะธ ะดะฐะฝะฝั‹ั…" + inDepth: "ะŸะตั€ะตะทะฐะฟะธัั‹ะฒะฐะตั‚ ัะพะดะตั€ะถะธะผะพะต ะพะดะฝะพะน ะฑะฐะทั‹ ะดะฐะฝะฝั‹ั… ัะพะดะตั€ะถะธะผั‹ะผ ะดั€ัƒะณะพะน ะฑะฐะทั‹ ะดะฐะฝะฝั‹ั…." + dbRemove: + description: "ะฃะดะฐะปะธั‚ัŒ ะธะณั€ะพะบะพะฒ ั ั‚ะตะบัƒั‰ะตะน ะดะฐั‚ะฐะฑะฐะทั‹" + inDepth: "ะฃะดะฐะปัะตั‚ ะฒัะต ะดะฐะฝะฝั‹ะต, ัะฒัะทะฐะฝะฝั‹ะต ั ะธะณั€ะพะบะพะผ, ะธะท ั‚ะตะบัƒั‰ะตะน ะฑะฐะทั‹ ะดะฐะฝะฝั‹ั…." + dbRestore: + description: "ะ’ะพัั‚ะฐะฝะพะฒะธั‚ัŒ ะธะฝั„ะพั€ะผะฐั†ะธัŽ ั ั„ะฐะนะปะฐ ะฒ ะฑะฐะทัƒ ะดะฐะฝะฝั‹ั…" + inDepth: "ะ˜ัะฟะพะปัŒะทัƒะตั‚ ั„ะฐะนะป ั€ะตะทะตั€ะฒะฝะพะน ะบะพะฟะธะธ SQLite ะธ ะฟะตั€ะตะทะฐะฟะธัั‹ะฒะฐะตั‚ ัะพะดะตั€ะถะธะผะพะต ั†ะตะปะตะฒะพะน ะฑะฐะทั‹ ะดะฐะฝะฝั‹ั…." + dbUninstalled: + description: "ะฃัั‚ะฐะฝะพะฒะธั‚ัŒ ัะตั€ะฒะตั€ ะบะฐะบ ะฝะต ัƒัั‚ะฐะฝะพะฒะปะตะฝะฝั‹ะน." + inDepth: "ะŸะพะผะตั‡ะฐะตั‚ ัะตั€ะฒะตั€ ะฒ ะฑะฐะทะต ะดะฐะฝะฝั‹ั… Plan ะบะฐะบ ัƒะดะฐะปั‘ะฝะฝั‹ะน, ั‡ั‚ะพะฑั‹ ะพะฝ ะฝะต ะพั‚ะพะฑั€ะฐะถะฐะปัั ะฒ ะทะฐะฟั€ะพัะฐั… ัะตั€ะฒะตั€ะฐ.." + disable: + description: "ะžั‚ะบะปัŽั‡ะธั‚ัŒ ะฟะปะฐะณะธะฝ ะธะปะธ ะตะณะพ ั‡ะฐัั‚ัŒ" + inDepth: "ะžั‚ะบะปัŽั‡ะธั‚ะต ะฟะปะฐะณะธะฝ ะธะปะธ ะตะณะพ ั‡ะฐัั‚ัŒ ะดะพ ัะปะตะดัƒัŽั‰ะตะน ะฟะตั€ะตะทะฐะณั€ัƒะทะบะธ/ะฟะตั€ะตะทะฐะฟัƒัะบะฐ." + export: + description: "ะญะบัะฟะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ HTML ะธะปะธ JSON ั„ะฐะนะปั‹ ัะฐะผะพัั‚ะพัั‚ะตะปัŒะฝะพ" + inDepth: "ะ’ั‹ะฟะพะปะฝัะตั‚ ัะบัะฟะพั€ั‚ ะฒ ะผะตัั‚ะพ ัะบัะฟะพั€ั‚ะฐ, ัƒะบะฐะทะฐะฝะฝะพะต ะฒ ะบะพะฝั„ะธะณัƒั€ะฐั†ะธะธ." + import: + description: "ะ˜ะผะฟะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ะดะฐั‚ัƒ" + inDepth: "ะ’ั‹ะฟะพะปะฝัะตั‚ ะธะผะฟะพั€ั‚ ะดะปั ะทะฐะณั€ัƒะทะบะธ ะดะฐะฝะฝั‹ั… ะฒ ะดะฐั‚ะฐ ะฑะฐะทัƒ." + info: + description: "ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะพ ะฟะปะฐะณะธะฝะต" + inDepth: "ะŸะพะบะฐะทะฐั‚ัŒ ั‚ะตะบัƒั‰ะธะน ัั‚ะฐั‚ัƒั ะฟะปะฐะณะธะฝะฐ." + ingame: + description: "ะŸั€ะพัะผะพั‚ั€ ะธะฝั„ะพั€ะผะฐั†ะธะธ ะพะฑ ะธะณั€ะพะบะต ะฒ ะธะณั€ะต" + inDepth: "ะžั‚ะพะฑั€ะฐะถะฐะตั‚ ะฝะตะบะพั‚ะพั€ัƒัŽ ะธะฝั„ะพั€ะผะฐั†ะธัŽ ะพ ะธะณั€ะพะบะต ะฒ ะธะณั€ะต." + json: + description: "ะŸะพัะผะพั‚ั€ะตั‚ัŒ ะดะฐั‚ัƒ ะธะณั€ะพะบะฐ ะฒ ะฒะธะดะต JSON." + inDepth: "ะŸะพะทะฒะพะปัะตั‚ ะทะฐะณั€ัƒะถะฐั‚ัŒ ะดะฐะฝะฝั‹ะต ะธะณั€ะพะบะฐ ะฒ ั„ะพั€ะผะฐั‚ะต JSON. ะะฐ ัั‚ะพะผ ะฒัั‘!" + logout: + description: "ะ ะฐะทะปะพะณะธะฝะธั‚ัŒ ะดั€ัƒะณะธั… ะฟะพะปัŒะทะพะฒะฐั‚ะตะปะตะน ะฟะฐะฝะตะปะธ." + inDepth: "ะฃะบะฐะถะธั‚ะต ะฐั€ะณัƒะผะตะฝั‚ ะธะผะตะฝะธ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะดะปั ะตะณะพ ะฟั€ะธะฝัƒะดะธั‚ะตะปัŒะฝะพะณะพ ะฒั‹ั…ะพะดะฐ ะธะท ะฟะฐะฝะตะปะธ, ะธะปะธ ัƒะบะฐะถะธั‚ะต ะฐั€ะณัƒะผะตะฝั‚ * (ะดะปั ะฒัะตั…)." + migrateToOnlineUuids: + description: "ะŸะตั€ะตะฝะพั ะพั„ะปะฐะนะฝ-ะดะฐะฝะฝั‹ั… uuid ะฒ ะพะฝะปะฐะนะฝ-uuid" + network: + description: "ะŸั€ะพัะผะพั‚ั€ ะฒะตะฑ-ัั‚ั€ะฐะฝะธั†ั‹" + inDepth: "ะŸะพะปัƒั‡ะธั‚ัŒ ััั‹ะปะบัƒ ะฝะฐ ัั‚ั€ะฐะฝะธั†ัƒ /network, ั‚ะพะปัŒะบะพ ะฒ ัะตั‚ัั…." + player: + description: "ะŸั€ะพัะผะพั‚ั€ ัั‚ั€ะฐะฝะธั†ั‹ ะธะณั€ะพะบะฐ" + inDepth: "ะŸะพะปัƒั‡ะธั‚ะต ััั‹ะปะบัƒ ะฝะฐ ัั‚ั€ะฐะฝะธั†ัƒ /player ะพะฟั€ะตะดะตะปะตะฝะฝะพะณะพ ะธะณั€ะพะบะฐ ะธะปะธ ั‚ะตะบัƒั‰ะตะณะพ ะธะณั€ะพะบะฐ." + players: + description: "ะŸั€ะพัะผะพั‚ั€ ัั‚ั€ะฐะฝะธั†ั‹ ั ะธะณั€ะพะบะฐะผะธ" + inDepth: "ะŸะพะปัƒั‡ะธั‚ะต ััั‹ะปะบัƒ ะฝะฐ ัั‚ั€ะฐะฝะธั†ัƒ /players, ั‡ั‚ะพะฑั‹ ัƒะฒะธะดะตั‚ัŒ ัะฟะธัะพะบ ะธะณั€ะพะบะพะฒ." + register: + description: "ะ—ะฐั€ะตะณะธัั‚ั€ะธั€ะพะฒะฐั‚ัŒ ะฒะตะฑ-ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั" + inDepth: "ะ˜ัะฟะพะปัŒะทัƒะนั‚ะต ะฑะตะท ะฐั€ะณัƒะผะตะฝั‚ะพะฒ, ั‡ั‚ะพะฑั‹ ะฟะพะปัƒั‡ะธั‚ัŒ ััั‹ะปะบัƒ ะฝะฐ ัั‚ั€ะฐะฝะธั†ัƒ ั€ะตะณะธัั‚ั€ะฐั†ะธะธ. ะ˜ัะฟะพะปัŒะทัƒะนั‚ะต --code [ะบะพะด] ะฟะพัะปะต ั€ะตะณะธัั‚ั€ะฐั†ะธะธ, ั‡ั‚ะพะฑั‹ ะฟะพะปัƒั‡ะธั‚ัŒ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั." + reload: + description: "ะŸะตั€ะตะทะฐะฟัƒัั‚ะธั‚ัŒ Plan" + inDepth: "ะŸะตั€ะตะทะฐะณั€ัƒะทะธั‚ัŒ ะฟะปะฐะณะธะฝ, ั‡ั‚ะพะฑั‹ ะฟะตั€ะตะฟั€ะพั‡ะธั‚ะฐั‚ัŒ ะบะพะฝั„ะธะณัƒั€ะฐั†ะธัŽ." + removejoinaddresses: + description: "ะฃะดะฐะปะธั‚ัŒ ะฐะดั€ะตัะฐ ะฟั€ะธัะพะตะดะธะฝะตะฝะธั ัƒะบะฐะทะฐะฝะฝะพะณะพ ัะตั€ะฒะตั€ะฐ" + search: + description: "ะŸะพะธัะบ ะฟะพ ะธะผะตะฝะธ ะธะณั€ะพะบะฐ" + inDepth: "ะŸะตั€ะตั‡ะธัะปะธั‚ะต ะฒัะต ะธะผะตะฝะฐ ะธะณั€ะพะบะพะฒ, ัะพะพั‚ะฒะตั‚ัั‚ะฒัƒัŽั‰ะธะต ะทะฐะดะฐะฝะฝะพะน ั‡ะฐัั‚ะธ ะธะผะตะฝะธ." + server: + description: "ะŸั€ะพัะผะพั‚ั€ ัั‚ั€ะฐะฝะธั†ั‹ ัะตั€ะฒะตั€ะฐ" + inDepth: "ะŸะพะปัƒั‡ะธั‚ะต ััั‹ะปะบัƒ ะฝะฐ ัั‚ั€ะฐะฝะธั†ัƒ /server, ะพะฟั€ะตะดะตะปะตะฝะฝะพะณะพ ัะตั€ะฒะตั€ะฐ ะธะปะธ ะฝะฐ ั‚ะตะบัƒั‰ะธะน ัะตั€ะฒะตั€, ะตัะปะธ ะฝะต ัƒะบะฐะทะฐะฝั‹ ะฐั€ะณัƒะผะตะฝั‚ั‹." + servers: + description: "ะกะฟะธัะพะบ ัะตั€ะฒะตั€ะพะฒ ะฒ ะฑะฐะทะต ะดะฐะฝะฝั‹ั…" + inDepth: "ะกะฟะธัะพะบ ะฐะนะดะธ, ะฝะธะบะพะฒ ะธ UUID ัะตั€ะฒะตั€ะพะฒ ะฒ ะฑะฐะทะต ะดะฐะฝะฝั‹ั…." + unregister: + description: "ะฃะดะฐะปะธั‚ัŒ ะฒะตะฑ-ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั" + inDepth: "ะ˜ัะฟะพะปัŒะทัƒะนั‚ะต ะฑะตะท ะฐั€ะณัƒะผะตะฝั‚ะพะฒ, ั‡ั‚ะพะฑั‹ ะพั‚ะผะตะฝะธั‚ัŒ ั€ะตะณะธัั‚ั€ะฐั†ะธัŽ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั." + users: + description: "ะกะฟะธัะพะบ ะฒัะตั… ะฒะตะฑ-ะฟะพะปัŒะทะพะฒะฐั‚ะตะปะตะน" + inDepth: "ะกะฟะธัะพะบ ะฒะตะฑ-ะฟะพะปัŒะทะพะฒะฐั‚ะตะปะตะน ะฒ ะฒะธะดะต ั‚ะฐะฑะปะธั†ั‹." + ingame: + activePlaytime: " ยง2ะะบั‚ะธะฒะฝะพะต ะฒั€ะตะผั ะธะณั€ั‹: ยงf${0}" + activityIndex: " ยง2ะ˜ะฝะดะตะบั ะฐะบั‚ะธะฒะฝะพัั‚ะธ: ยงf${0} | ${1}" + afkPlaytime: " ยง2ะ’ั€ะตะผั AFK: ยงf${0}" + deaths: " ยง2ะกะผะตั€ั‚ะธ: ยงf${0}" + geolocation: " ยง2ะŸั€ะธัะพะตะดะธะฝะธะปัั ะธะท: ยงf${0}" + lastSeen: " ยง2ะŸะพัะปะตะดะฝะตะต ะฟะพัะตั‰ะตะฝะธะต: ยงf${0}" + longestSession: " ยง2ะกะฐะผะฐั ะดะปะธะฝะฝะฐั ัะตััะธั: ยงf${0}" + mobKills: " ยง2ะฃะฑะธั‚ะพ ะผะพะฑะพะฒ: ยงf${0}" + playerKills: " ยง2ะฃะฑะธั‚ะพ ะธะณั€ะพะบะพะฒ: ยงf${0}" + playtime: " ยง2ะ’ั€ะตะผั ะธะณั€ั‹: ยงf${0}" + registered: " ยง2ะ—ะฐั€ะตะณะธัั‚ั€ะธั€ะพะฒะฐะปัั: ยงf${0}" + timesKicked: " ยง2ะšะพะป-ะฒะพ ะบะธะบะพะฒ: ยงf${0}" + link: + clickMe: "ะะฐะถะผะธ ะฝะฐ ะผะตะฝั" + link: "ะกัั‹ะปะบะฐ" + network: "ะกั‚ั€ะฐะฝะธั†ะฐ ัะตั‚ะธ: " + noNetwork: "ะกะตั€ะฒะตั€ ะฝะต ะฟะพะดะบะปัŽั‡ะตะฝ ะบ ัะตั‚ะธ. ะกัั‹ะปะบะฐ ะฟะตั€ะตะพั‚ะฟั€ะฐะฒะปัะตั‚ ะฝะฐ ะณะปะฐะฒะฝัƒัŽ ัั‚ั€ะฐะฝะธั†ัƒ." + player: "ะกั‚ั€ะฐะฝะธั†ะฐ ะธะณั€ะพะบะฐ: " + playerJson: "JSON ะกั‚ั€ะฐะฝะธั†ะฐ ะธะณั€ะพะบะฐ: " + players: "ะกั‚ั€ะฐะฝะธั†ะฐ ะธะณั€ะพะบะพะฒ: " + register: "ะ ะตะณะธัั‚ั€ะฐั†ะธะพะฝะฝะฐั ัั‚ั€ะฐะฝะธั†ะฐ: " + server: "ะกั‚ั€ะฐะฝะธั†ะฐ ัะตั€ะฒะตั€ะพะฒ: " + subcommand: + info: + database: " ยง2ะขะตะบัƒั‰ะฐั ะฑะฐะทะฐ ะดะฐะฝะฝั‹ั…: ยงf${0}" + proxy: " ยง2ะŸะพะดะบะปัŽั‡ะตะฝ ะบ ะฟั€ะพะบัะธ: ยงf${0}" + update: " ยง2ะ”ะพัั‚ัƒะฟะฝะพ ะพะฑะฝะพะฒะปะตะฝะธะต: ยงf${0}" + version: " ยง2ะ’ะตั€ัะธั: ยงf${0}" +generic: + noData: "ะะตั‚ ะดะฐะฝะฝั‹ั…" +html: + button: + nightMode: "ะะพั‡ะฝะพะน ั€ะตะถะธะผ" + calendar: + new: "ะะพะฒะพะต:" + unique: "ะฃะฝะธะบะฐะปัŒะฝั‹ั…:" + description: + newPlayerRetention: "ะญั‚ะพ ะทะฝะฐั‡ะตะฝะธะต ัะฒะปัะตั‚ัั ะฟั€ะพะณะฝะพะทะพะผ, ะพัะฝะพะฒะฐะฝะฝั‹ะผ ะฝะฐ ะฟั€ะตะดั‹ะดัƒั‰ะธั… ะธะณั€ะพะบะฐั….." + noGameServers: "ะ”ะปั ะฝะตะบะพั‚ะพั€ั‹ั… ะดะฐะฝะฝั‹ั… ั‚ั€ะตะฑัƒะตั‚ัั ัƒัั‚ะฐะฝะพะฒะบะฐ Plan ะฝะฐ ะธะณั€ะพะฒั‹ะต ัะตั€ะฒะตั€ะฐ." + noGeolocations: "ะกะฑะพั€ ะณะตะพะปะพะบะฐั†ะธะธ ะดะพะปะถะตะฝ ะฑั‹ั‚ัŒ ะฒะบะปัŽั‡ะตะฝ ะฒ ะบะพะฝั„ะธะณัƒั€ะฐั†ะธะธ (ะŸะ ะžะงะ˜ะขะะ™ะขะ• ะธ ะฟั€ะธะผะธั‚ะต GeoLite2 EULA)." + noServerOnlinActivity: "ะะตั‚ ัะตั€ะฒะตั€ะฐ ะดะปั ะพั‚ะพะฑั€ะฐะถะตะฝะธั ะพะฝะปะฐะนะฝ ะฐะบั‚ะธะฒะฝะพัั‚ะธ" + noServers: "ะ’ ะฑะฐะทะต ะดะฐะฝะฝั‹ั… ะฝะต ะฝะฐะนะดะตะฝะพ ะฝะธ ะพะดะฝะพะณะพ ัะตั€ะฒะตั€ะฐ" + noServersLong: 'ะŸะพั…ะพะถะต, ั‡ั‚ะพ Plan ะฝะต ัƒัั‚ะฐะฝะพะฒะปะตะฝ ะฝะธ ะฝะฐ ะพะดะฝะพะผ ะธะณั€ะพะฒะพะผ ัะตั€ะฒะตั€ะต ะธะปะธ ะฝะต ะฟะพะดะบะปัŽั‡ะตะฝ ะบ ั‚ะพะน ะถะต ะฑะฐะทะต ะดะฐะฝะฝั‹ั…. ะกะผะพั‚ั€ะธั‚ะต ะฒะธะบะธ ะดะปั ะฟะพะผะพั‰ะธ.' + noSpongeChunks: "ะงะฐะฝะบะธ ะฝะต ะดะพัั‚ัƒะฟะฝั‹ ะฝะฐ Sponge" + predictedNewPlayerRetention: "ะญั‚ะพ ะทะฝะฐั‡ะตะฝะธะต ัะฒะปัะตั‚ัั ะฟั€ะพะณะฝะพะทะพะผ ะฝะฐ ะพัะฝะพะฒะต ะฟั€ะตะดั‹ะดัƒั‰ะธั… ะธะณั€ะพะบะพะฒ" + error: + 401Unauthorized: "ะะต ะฐะฒั‚ะพั€ะธะทะพะฒะฐะฝ" + 403Forbidden: "ะ—ะฐะฟั€ะตั‰ะตะฝะพ" + 404NotFound: "ะะต ะพะฑะฝะฐั€ัƒะถะตะฝะฐ" + 404PageNotFound: "ะกั‚ั€ะฐะฝะธั†ะฐ ะฝะต ััƒั‰ะตัั‚ะฒัƒะตั‚." + 404UnknownPage: "ะฃะฑะตะดะธั‚ะตััŒ, ั‡ั‚ะพ ะฒั‹ ะฟะพะปัƒั‡ะฐะตั‚ะต ะดะพัั‚ัƒะฟ ะบ ััั‹ะปะบะต, ะทะฐะดะฐะฝะฝะพะน ะบะพะผะฐะฝะดะพะน, ะŸั€ะธะผะตั€ั‹:

/player/{uuid/name}
/server/{uuid/name/id}

" + UUIDNotFound: "UUID ะธะณั€ะพะบะฐ ะฝะต ะฝะฐะนะดะตะฝ ะฒ ะฑะฐะทะต ะดะฐะฝะฝั‹ั…." + auth: + dbClosed: "ะ‘ะฐะทะฐ ะดะฐะฝะฝั‹ั… ะฝะต ะพั‚ะบั€ั‹ั‚ะฐ, ะฟั€ะพะฒะตั€ัŒั‚ะต ัั‚ะฐั‚ัƒั ะ‘ะ” ั ะฟะพะผะพั‰ัŒัŽ /plan info" + emptyForm: "ะ˜ะผั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะธ ะฟะฐั€ะพะปัŒ ะฝะต ัƒะบะฐะทะฐะฝั‹" + expiredCookie: "ะกั€ะพะบ ะดะตะนัั‚ะฒะธั ั„ะฐะนะปะฐ cookie ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะธัั‚ะตะบ" + generic: "ะžัˆะธะฑะบะฐ ะฟั€ะธ ะฐะฒั‚ะพั€ะธะทะฐั†ะธะธ" + loginFailed: "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะธ ะฟะฐั€ะพะปัŒ ะฝะต ัะพะฒะฟะฐะดะฐัŽั‚" + noCookie: "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะน ั„ะฐะนะป cookie ะพั‚ััƒั‚ัั‚ะฒัƒะตั‚" + registrationFailed: "ะ ะตะณะธัั‚ั€ะฐั†ะธั ะฝะต ัƒะดะฐะปะฐััŒ, ะฟะพะฟั€ะพะฑัƒะนั‚ะต ะตั‰ั‘ ั€ะฐะท (ะšะพะด ะฟะตั€ะตัั‚ะฐะฝะตั‚ ะดะตะนัั‚ะฒะพะฒะฐั‚ัŒ ั‡ะตั€ะตะท 15 ะผะธะฝัƒั‚)" + userNotFound: "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะฝะต ััƒั‰ะตัั‚ะฒัƒะตั‚" + authFailed: "ะžัˆะธะฑะบะฐ ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธะธ." + authFailedTips: "- ะฃะฑะตะดะธั‚ะตััŒ, ั‡ั‚ะพ ะฒั‹ ะทะฐั€ะตะณะธัั‚ั€ะธั€ะพะฒะฐะปะธ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ั ะฟะพะผะพั‰ัŒัŽ /plan register
- ะŸั€ะพะฒะตั€ัŒั‚ะต ะฟั€ะฐะฒะธะปัŒะฝะพัั‚ัŒ ะธะผะตะฝะธ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะธ ะฟะฐั€ะพะปั
- ะ˜ะผั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะธ ะฟะฐั€ะพะปัŒ ั‡ัƒะฒัั‚ะฒะธั‚ะตะปัŒะฝั‹ ะบ ั€ะตะณะธัั‚ั€ัƒ

ะ•ัะปะธ ะฒั‹ ะทะฐะฑั‹ะปะธ ะฒะฐัˆ ะฟะฐั€ะพะปัŒ, ะฟะพะฟั€ะพัะธั‚ะต ัะพั‚ั€ัƒะดะฝะธะบะฐ ัƒะดะฐะปะธั‚ัŒ ะฒะฐัˆะตะณะพ ัั‚ะฐั€ะพะณะพ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะธ ะฟะตั€ะตั€ะตะณะธัั‚ั€ะธั€ะพะฒะฐั‚ัŒ ะฒะฐั." + noServersOnline: "ะะตั‚ ัะตั€ะฒะตั€ะพะฒ ะฒ ัะตั‚ะธ ะดะปั ะฒั‹ะฟะพะปะฝะตะฝะธั ะทะฐะฟั€ะพัะฐ." + playerNotSeen: "Plan ะตั‰ั‘ ะฝะต ะฒะธะดะตะป ัั‚ะพะณะพ ะธะณั€ะพะบะฐ." + serverNotSeen: "ะกะตั€ะฒะตั€ ะฝะต ััƒั‰ะตัั‚ะฒัƒะตั‚" + generic: + none: "ะะธะบั‚ะพ" + label: + active: "ะะบั‚ะธะฒะฝั‹ะน" + activePlaytime: "ะะบั‚ะธะฒะฝะพะต ะฒั€ะตะผั ะธะณั€ั‹" + activityIndex: "ะ˜ะฝะดะตะบั ะฐะบั‚ะธะฒะฝะพัั‚ะธ" + afk: "AFK" + afkTime: "ะ’ั€ะตะผั AFK" + all: "ะ’ัะต" + allTime: "ะ’ัะต ะฒั€ะตะผั" + alphabetical: "Alphabetical" + apply: "Apply" + asNumbers: "ะ’ ั‡ะธัะปะฐั…" + average: "ะกั€ะตะด." + averageActivePlaytime: "ะกั€ะตะดะฝะตะต ะฒั€ะตะผั ะฐะบั‚ะธะฒะฝะพะน ะธะณั€ั‹" + averageAfkTime: "ะกั€ะตะดะฝะตะต ะฒั€ะตะผั AFK" + averageChunks: "ะกั€ะตะด. ะงะฐะฝะบะพะฒ" + averageCpuUsage: "ะกั€ะตะดะฝัั ะทะฐะณั€ัƒะทะบะฐ ะฆะŸ" + averageEntities: "ะกั€ะตะด. ะกัƒั‰ะตัั‚ะฒ" + averageKdr: "ะกั€ะตะด. KDR" + averageMobKdr: "ะกั€ะตะด. ะผะพะฑ KDR" + averagePing: "ะกั€ะตะดะฝะธะน ะฟะธะฝะณ" + averagePlayers: "ะกั€ะตะดะฝะตะต ะบะพะป-ะฒะพ ะธะณั€ะพะบะพะฒ" + averagePlaytime: "ะกั€ะตะดะฝะตะต ะฒั€ะตะผั ะธะณั€ั‹" + averageRamUsage: "ะกั€ะตะดะฝะตะต ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะต ะฟะฐะผัั‚ะธ" + averageServerDowntime: "Average Downtime / Server" + averageSessionLength: "ะกั€ะตะดะฝัั ะฟั€ะพะดะพะปะถะธั‚ะตะปัŒะฝะพัั‚ัŒ ัะตััะธะธ" + averageSessions: "ะกั€ะตะดะฝัั ัะตััะธั" + averageTps: "ะกั€ะตะดะฝะธะน TPS" + averageTps7days: "Average TPS (7 days)" + banned: "ะ—ะฐะฑะฐะฝะตะฝ" + bestPeak: "ะœะฐะบัะธะผะฐะปัŒะฝั‹ะน ะŸะธะบ" + bestPing: "ะะฐะธะปัƒั‡ัˆะธะน ะฟะธะฝะณ" + calendar: " ะšะฐะปะตะฝะดะฐั€ัŒ" + comparing7days: "ะกั€ะฐะฒะฝะตะฝะธะต 7 ะดะฝะตะน" + connectionInfo: "ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะพ ัะพะตะดะธะฝะตะฝะธะธ" + country: "ะกั‚ั€ะฐะฝะฐ" + cpu: "ะฆะŸ" + cpuRam: "ะฆะŸ & ะžะ—ะฃ" + cpuUsage: "ะ˜ัะฟะพะปัŒะทะพะฒะฐะฝะธะต ะฆะŸ" + currentPlayerbase: "ะขะตะบัƒั‰ะฐั ะฑะฐะทะฐ ะธะณั€ะพะบะพะฒ" + currentUptime: "ะ’ั€ะตะผั ะฑะตะทะพั‚ะบะฐะทะฝะพะน ั€ะฐะฑะพั‚ั‹" + dayByDay: "ะกั‚ะฐั‚ะธัั‚ะธะบะฐ ะฟะพ ะดะฝัะผ" + dayOfweek: "ะ”ะตะฝัŒ ะฝะตะดะตะปะธ" + deadliestWeapon: "ะกะฐะผะพะต ัะผะตั€ั‚ะพะฝะพัะฝะพะต ะพั€ัƒะถะธะต ะฒ PvP" + deaths: "ะกะผะตั€ั‚ะธ" + disk: "ะ”ะธัะบะพะฒะพะต ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒะพ" + diskSpace: "ะกะฒะพะฑะพะดะฝะพะต ะดะธัะบะพะฒะพะต ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒะพ" + downtime: "ะ’ั€ะตะผั ะฟั€ะพัั‚ะพั" + duringLowTps: "ะ’ะพ ะฒั€ะตะผั ะฝะธะทะบะพะณะพ TPS:" + entities: "ะžะฑัŠะตะบั‚ั‹" + favoriteServer: "ะ›ัŽะฑะธะผั‹ะน ัะตั€ะฒะตั€" + firstSession: "ะŸะตั€ะฒะฐั ัะตััะธั" + firstSessionLength: + average: "ะกั€ะตะดะฝัั ะฟั€ะพะดะพะปะถะธั‚ะตะปัŒะฝะพัั‚ัŒ ะฟะตั€ะฒะพะณะพ ัะตะฐะฝัะฐ" + median: "ะกั€ะตะดะฝัั ะฟั€ะพะดะพะปะถะธั‚ะตะปัŒะฝะพัั‚ัŒ ะฟะตั€ะฒะพะณะพ ัะตะฐะฝัะฐ" + geoProjection: + dropdown: "Select projection" + equalEarth: "Equal Earth" + mercator: "Mercator" + miller: "Miller" + ortographic: "Ortographic" + geolocations: "ะ“ะตะพะปะพะบะฐั†ะธั" + hourByHour: "ะกั‚ะฐั‚ะธัั‚ะธะบะฐ ะฟะพ ั‡ะฐัะฐะผ" + inactive: "ะะตะฐะบั‚ะธะฒะฝั‹ะน" + indexInactive: "ะะตะฐะบั‚ะธะฒะฝั‹ะน" + indexRegular: "ะ ะตะณัƒะปัั€ะฝั‹ะน" + information: "ะ˜ะะคะžะ ะœะะฆะ˜ะฏ" + insights: "ะ˜ะฝัะฐะนั‚ั‹" + insights30days: "ะกั‚ะฐั‚ะธัั‚ะธะบะฐ ะทะฐ 30 ะดะฝะตะน" + irregular: "ะะตั€ะตะณัƒะปัั€ะฝั‹ะน" + joinAddress: "ะะดั€ะตั ะฒั…ะพะดะฐ" + joinAddresses: "ะะดั€ะตัั‹ ะ’ั…ะพะดะฐ" + kdr: "KDR" + killed: "ะฃะฑะธั‚" + last24hours: "ะŸะพัะปะตะดะฝะธะต 24 ั‡ะฐัะฐ" + last30days: "ะŸะพัะปะตะดะฝะธะต 30 ะดะฝะตะน" + last7days: "ะŸะพัะปะตะดะฝะธะต 7 ะดะฝะตะน" + lastConnected: "ะŸะพัะปะตะดะฝะตะต ะฟะพะดะบะปัŽั‡ะตะฝะธะต" + lastPeak: "ะŸะพัะปะตะดะฝะธะน ะŸะธะบ" + lastSeen: "ะŸะพัะปะตะดะฝะตะต ะฟะพัะตั‰ะตะฝะธะต" + latestJoinAddresses: "Latest Join Addresses" + length: " ะ”ะปะธะฝะฐ" + links: "ะกะกะซะ›ะšะ˜" + loadedChunks: "ะ—ะฐะณั€ัƒะถะตะฝะฝั‹ะต ั‡ะฐะฝะบะธ" + loadedEntities: "ะ—ะฐะณั€ัƒะถะตะฝะฝั‹ะต ะพะฑัŠะตะบั‚ั‹" + loneJoins: "ะžะดะธะฝะพั‡ะบะฐ ะฟั€ะธัะพะตะดะตะฝะธะปัั" + loneNewbieJoins: "ะžะดะธะฝะพะบะธะน ะฝะพะฒะธั‡ะพะบ ะฟั€ะธัะพะตะดะธะฝัะตั‚ัั" + longestSession: "ะกะฐะผะฐั ะดะปะธะฝะฝะฐั ัะตััะธั" + lowTpsSpikes: "ะะธะทะบะธะน TPS" + lowTpsSpikes7days: "Low TPS Spikes (7 days)" + maxFreeDisk: "ะœะฐะบั. ัะฒะพะฑะพะดะฝั‹ะน ะดะธัะบ" + medianSessionLength: "ะกั€ะตะดะฝัั ะฟั€ะพะดะพะปะถะธั‚ะตะปัŒะฝะพัั‚ัŒ ัะตะฐะฝัะฐ" + minFreeDisk: "ะœะธะฝ. ัะฒะพะฑะพะดะฝั‹ะน ะดะธัะบ" + mobDeaths: "ะกะผะตั€ั‚ัŒ ะธะท-ะทะฐ ะผะพะฑะพะฒ" + mobKdr: "ะœะพะฑ KDR" + mobKills: "ะฃะฑะธะนัั‚ะฒะฐ ะผะพะฑะพะฒ" + mostActiveGamemode: "ะกะฐะผั‹ะน ะฐะบั‚ะธะฒะฝั‹ะน ะธะณั€ะพะฒะพะน ั€ะตะถะธะผ" + mostPlayedWorld: "ะกะฐะผั‹ะน ะฟะพะฟัƒะปัั€ะฝั‹ะน ะผะธั€" + name: "ะ˜ะผั" + network: "ะกะตั‚ัŒ" + networkAsNumbers: "ะกะตั‚ัŒ ะฒ ั‡ะธัะปะฐั…" + networkOnlineActivity: "ะกะตั‚ะตะฒะฐั ะฐะบั‚ะธะฒะฝะพัั‚ัŒ" + networkOverview: "ะžะฑะทะพั€ ัะตั‚ะธ" + networkPage: "ะกะตั‚ะตะฒะฐั ัั‚ั€ะฐะฝะธั†ะฐ" + new: "ะะพะฒั‹ะน" + newPlayerRetention: "ะกะพั…ั€ะฐะฝะตะฝะธะต ะฝะพะฒะพะณะพ ะธะณั€ะพะบะฐ" + newPlayers: "ะะพะฒั‹ะต ะธะณั€ะพะบะธ" + newPlayers7days: "New Players (7 days)" + nickname: "ะะธะบะฝะตะนะผ" + noDataToDisplay: "ะะตั‚ ะดะฐะฝะฝั‹ั… ะดะปั ะพั‚ะพะฑั€ะฐะถะตะฝะธั" + now: "ะกะตะนั‡ะฐั" + onlineActivity: "ะกะตั‚ะตะฒะฐั ะฐะบั‚ะธะฒะฝะพัั‚ัŒ" + onlineActivityAsNumbers: "ะกะตั‚ะตะฒะฐั ะฐะบั‚ะธะฒะฝะพัั‚ัŒ ะฒ ั‡ะธัะปะฐั…" + onlineOnFirstJoin: "ะšะพะป-ะฒะพ ะธะณั€ะพะบะพะฒ ะพะฝะปะฐะนะฝ ะฟั€ะธ ะฟะตั€ะฒะพะผ ะฟั€ะธัะพะตะดะธะฝะตะฝะธะธ" + operator: "ะžะฟะตั€ะฐั‚ะพั€" + overview: "ะžะฑะทะพั€" + perDay: "/ ะ”ะตะฝัŒ" + perPlayer: "/ ะ˜ะณั€ะพะบ" + perRegularPlayer: "/ ะŸะพัั‚ะพัะฝะฝั‹ะน ะธะณั€ะพะบ" + performance: "ะŸั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ัŒ" + performanceAsNumbers: "ะŸั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ัŒ ะฒ ั‡ะธัะปะฐั…" + ping: "ะŸะธะฝะณ" + player: "ะ˜ะณั€ะพะบ" + playerDeaths: "ะกะผะตั€ั‚ัŒ ะธะท-ะทะฐ ะธะณั€ะพะบะพะฒ" + playerKills: "ะฃะฑะธั‚ะพ ะธะณั€ะพะบะพะฒ" + playerList: "ะกะฟะธัะพะบ ะธะณั€ะพะบะพะฒ" + playerOverview: "ะžะฑะทะพั€ ะธะณั€ะพะบะฐ" + playerPage: "ะกั‚ั€ะฐะฝะธั†ะฐ ะธะณั€ะพะบะฐ" + playerRetention: "ะฃะดะตั€ะถะฐะฝะธะต ะธะณั€ะพะบะฐ" + playerbase: "ะ‘ะฐะทะฐ ะธะณั€ะพะบะพะฒ" + playerbaseDevelopment: "ะ ะฐะทะฒะธั‚ะธะต ะฑะฐะทั‹ ะธะณั€ะพะบะพะฒ" + playerbaseOverview: "ะžะฑะทะพั€ ะฑะฐะทั‹ ะธะณั€ะพะบะพะฒ" + players: "ะ˜ะณั€ะพะบะธ" + playersOnline: "ะ˜ะณั€ะพะบะธ ะพะฝะปะฐะนะฝ" + playersOnlineNow: "Players Online (Now)" + playersOnlineOverview: "ะžะฑะทะพั€ ัะตั‚ะตะฒะพะน ะฐะบั‚ะธะฒะฝะพัั‚ะธ" + playtime: "ะ’ั€ะตะผั ะธะณั€ั‹" + plugins: "ะŸะปะฐะณะธะฝั‹" + pluginsOverview: "ะžะฑะทะพั€ ะฟะปะฐะณะธะฝะพะฒ" + punchcard: "ะŸะตั€ั„ะพะบะฐั€ั‚ั‹" + punchcard30days: "ะŸะตั€ั„ะพะบะฐั€ั‚ะฐ ะฝะฐ 30 ะดะฝะตะน" + pvpPve: "PvP ะ˜ PvE" + pvpPveAsNumbers: "PvP ะธ PvE ะฒ ั‡ะธัะปะฐั…" + query: "ะกะดะตะปะฐั‚ัŒ ะทะฐะฟั€ะพั" + quickView: "ะ‘ั‹ัั‚ั€ั‹ะน ะฟั€ะพัะผะพั‚ั€" + ram: "ะŸะฐะผัั‚ัŒ" + ramUsage: "ะ˜ัะฟะพะปัŒะทะพะฒะฐะฝะธะต ะฟะฐะผัั‚ะธ" + recentKills: "ะะตะดะฐะฒะฝะธะต ัƒะฑะธะนัั‚ะฒะฐ" + recentPvpDeaths: "ะะตะดะฐะฒะฝะธะต PvP ัะผะตั€ั‚ะธ" + recentPvpKills: "ะะตะดะฐะฒะฝะธะต PvP ัƒะฑะธะนัั‚ะฒะฐ" + recentSessions: "ะะตะดะฐะฒะฝะธะต ัะตััะธะธ" + registered: "ะ—ะฐั€ะตะณะธัั‚ั€ะธั€ะพะฒะฐะฝ" + registeredPlayers: "ะ—ะฐั€ะตะณะธัั‚ั€ะธั€ะพะฒะฐะฝะฝั‹ะต ะธะณั€ะพะบะธ" + regular: "ะŸะพัั‚ะพัะฝะฝั‹ะน" + regularPlayers: "ะŸะพัั‚ะพัะฝะฝั‹ะต ะธะณั€ะพะบะธ" + relativeJoinActivity: "ะกั€ะฐะฒะฝะธั‚ะตะปัŒะฝะฐั ะฐะบั‚ะธะฒะฝะพัั‚ัŒ ะฟั€ะธัะพะตะดะธะฝะตะฝะธั" + secondDeadliestWeapon: "2-ะต PvP ะพั€ัƒะถะธะต" + seenNicknames: "ะฃะฒะธะดะตะฝะฝั‹ะต ะฝะธะบะฝะตะนะผั‹" + server: "ะกะตั€ะฒะตั€" + serverAnalysis: "ะะฝะฐะปะธะท ัะตั€ะฒะตั€ะฐ" + serverAsNumberse: "ะกะตั€ะฒะตั€ ะฒ ั‡ะธัะปะฐั…" + serverCalendar: "ะšะฐะปะตะฝะดะฐั€ัŒ ัะตั€ะฒะตั€ะฐ" + serverDowntime: "ะ’ั€ะตะผั ะฟั€ะพัั‚ะพั ัะตั€ะฒะตั€ะฐ" + serverOccupied: "ะกะตั€ะฒะตั€ ะทะฐะฝัั‚" + serverOverview: "ะžะฑะทะพั€ ัะตั€ะฒะตั€ะฐ" + serverPage: "ะกั‚ั€ะฐะฝะธั†ะฐ ัะตั€ะฒะตั€ะฐ" + serverPlaytime: "ะ’ั€ะตะผั ะธะณั€ั‹ ะฝะฐ ัะตั€ะฒะตั€ะต" + serverPlaytime30days: "ะ’ั€ะตะผั ะธะณั€ั‹ ะฝะฐ ัะตั€ะฒะตั€ะต ะทะฐ 30 ะดะฝะตะน" + serverSelector: "Server selector" + servers: "ะกะตั€ะฒะตั€ั‹" + serversTitle: "ะกะ•ะ ะ’ะ•ะ ะซ" + session: "ะกะตััะธั" + sessionCalendar: "ะšะฐะปะตะฝะดะฐั€ัŒ ัะตััะธะธ" + sessionEnded: "ะšะพะฝะตั† ัะตััะธะธ" + sessionMedian: "ะกั€ะตะดะฝัั ัะตััะธั" + sessionStart: "ะกะตััะธั ะฝะฐั‡ะฐะปะฐััŒ" + sessions: "ะกะตััะธะธ" + sortBy: "Sort By" + stacked: "Stacked" + themeSelect: "ะ’ั‹ะฑะพั€ ั‚ะตะผั‹" + thirdDeadliestWeapon: "3-ะต PvP ะพั€ัƒะถะธะต" + thirtyDays: "30 ะดะฝะตะน" + thirtyDaysAgo: "30 ะดะฝะตะน ะฝะฐะทะฐะด" + timesKicked: "ะšะพะป-ะฒะพ ะบะธะบะพะฒ" + toMainPage: "ะะฐ ะณะปะฐะฒะฝัƒัŽ ัั‚ั€ะฐะฝะธั†ัƒ" + total: "Total" + totalActive: "ะžะฑั‰ะฐั ะฐะบั‚ะธะฒะฝะพัั‚ัŒ" + totalAfk: "ะ’ัะตะณะพ AFK" + totalPlayers: "ะ’ัะตะณะพ ะธะณั€ะพะบะพะฒ" + totalPlayersOld: "ะ’ัะตะณะพ ะธะณั€ะพะบะพะฒ" + totalPlaytime: "ะžะฑั‰ะตะต ะฒั€ะตะผั ะธะณั€ั‹" + totalServerDowntime: "Total Server Downtime" + tps: "TPS" + trend: "ะขะตะฝะดะตะฝั†ะธั" + trends30days: "ั‚ะตะฝะดะตะฝั†ะธั ะทะฐ 30 ะดะฝะตะน" + uniquePlayers: "ะฃะฝะธะบะฐะปัŒะฝั‹ะต ะธะณั€ะพะบะธ" + uniquePlayers7days: "Unique Players (7 days)" + veryActive: "ะžั‡ะตะฝัŒ ะฐะบั‚ะธะฒะฝั‹ะน" + weekComparison: "ะกั€ะฐะฒะฝะตะฝะธะต ะทะฐ ะฝะตะดะตะปัŽ" + weekdays: "'ะŸะพะฝะตะดะตะปัŒะฝะธะบ', 'ะ’ั‚ะพั€ะฝะธะบ', 'ะกั€ะตะดะฐ', 'ะงะตั‚ะฒะตั€ะณ', 'ะŸัั‚ะฝะธั†ะฐ', 'ะกัƒะฑะฑะพั‚ะฐ', 'ะ’ะพัะบั€ะตัะตะฝัŒะต'" + world: "ะ—ะฐะณั€ัƒะทะบะฐ ะผะธั€ะฐ" + worldPlaytime: "ะ’ั€ะตะผั ะธะณั€ั‹ ะฒ ะผะธั€ะต" + worstPing: "ะะฐะธั…ัƒะดัˆะธะน ะฟะธะฝะณ" + login: + failed: "ะ›ะพะณะธะฝ ะฝะตัƒะดะฐั‡ะตะฝ: " + forgotPassword: "ะ—ะฐะฑั‹ะปะธ ะฟะฐั€ะพะปัŒ?" + forgotPassword1: "ะ—ะฐะฑั‹ะปะธ ะฟะฐั€ะพะปัŒ? ะฃะดะฐะปะธั‚ะต ัะตะฑั ะธ ะทะฐั€ะตะณะธัั‚ั€ะธั€ัƒะนั‚ะตััŒ ะทะฐะฝะพะฒะพ." + forgotPassword2: "ะ˜ัะฟะพะปัŒะทัƒะนั‚ะต ัะปะตะดัƒัŽั‰ัƒัŽ ะบะพะผะฐะฝะดัƒ ะฒ ะธะณั€ะต, ั‡ั‚ะพะฑั‹ ัƒะดะฐะปะธั‚ัŒ ั‚ะตะบัƒั‰ะตะณะพ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั:" + forgotPassword3: "ะ˜ะปะธ ะธัะฟะพะปัŒะทัƒะนั‚ะต ะบะพะฝัะพะปัŒ:" + forgotPassword4: "ะŸะพัะปะต ะธัะฟะพะปัŒะทะพะฒะฐะฝะธั ะบะพะผะฐะฝะดั‹, " + login: "ะ’ั…ะพะด" + logout: "ะ’ั‹ั…ะพะด" + password: "ะŸะฐั€ะพะปัŒ" + register: "ะกะพะทะดะฐะนั‚ะต ะฐะบะบะฐัƒะฝั‚!" + username: "ะ˜ะผั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั" + modal: + info: + bugs: "ะกะพะพะฑั‰ะธั‚ัŒ ะพ ะฟั€ะพะฑะปะตะผะฐั…" + contributors: + bugreporters: "& ะ‘ะฐะณ ั€ะตะฟะพั€ั‚ะตั€ั‹!" + code: "ะฐะฒั‚ะพั€ ะบะพะดะฐ" + donate: "ะžัะพะฑะฐั ะฑะปะฐะณะพะดะฐั€ะฝะพัั‚ัŒ ั‚ะตะผ, ะบั‚ะพ ะพะบะฐะทะฐะป ั„ะธะฝะฐะฝัะพะฒัƒัŽ ะฟะพะดะดะตั€ะถะบัƒ." + text: 'ะšั€ะพะผะต ั‚ะพะณะพ, ะดะฐะฝะฝั‹ะต ะทะฐะผะตั‡ะฐั‚ะตะปัŒะฝั‹ะต ะปัŽะดะธ ะฒะฝะตัะปะธ ัะฒะพะน ะฒะบะปะฐะด:' + translator: "ะฟะตั€ะตะฒะพะดั‡ะธะบ" + developer: "ั€ะฐะทั€ะฐะฑะพั‚ะฐะฝ" + discord: "ะžะฑั‰ะฐั ะฟะพะดะดะตั€ะถะบะฐ ะฒ Discord" + license: "Player Analytics ั€ะฐะทั€ะฐะฑะพั‚ะฐะฝ ะธ ะปะธั†ะตะฝะทะธั€ะพะฒะฐะฝ ะฟะพะด" + metrics: "bStats ะผะตั‚ั€ะธะบะธ" + text: "ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะพ ะฟะปะฐะณะธะฝะต" + wiki: "Plan ะ’ะธะบะธ, ั‚ัƒั‚ะพั€ะธะฐะปั‹ ะธ ะดะพะบัƒะผะตะฝั‚ะฐั†ะธั" + version: + available: "ะดะพัั‚ัƒะฟะตะฝ" + changelog: "ะŸั€ะพัะผะพั‚ั€ ะถัƒั€ะฝะฐะปะฐ ะธะทะผะตะฝะตะฝะธะน" + dev: "ะญั‚ะฐ ะฒะตั€ัะธั ัะฒะปัะตั‚ัั DEV ั€ะตะปะธะทะพะผ." + download: "ะกะบะฐั‡ะฐั‚ัŒ" + text: "ะะพะฒะฐั ะฒะตั€ัะธั ะฑั‹ะปะฐ ะฒั‹ะฟัƒั‰ะตะฝะฐ ะธ ั‚ะตะฟะตั€ัŒ ะดะพัั‚ัƒะฟะฝะฐ ะดะปั ัะบะฐั‡ะธะฒะฐะฝะธั." + title: "ะ’ะตั€ัะธั" + query: + filter: + activity: + text: "ะฒ ะะบั‚ะธะฒะฝั‹ั… ะ“ั€ัƒะฟะฟะฐั…" + banStatus: + name: "ะ‘ะฐะฝ ัั‚ะฐั‚ัƒั" + banned: "ะ—ะฐะฑะฐะฝะตะฝ" + country: + text: "ะฟั€ะธัะพะตะดะธะฝะธะปัั ะธะท" + generic: + allPlayers: "ะ’ัะต ะธะณั€ะพะบะธ" + and: "ะดะพะฑะฐะฒะธั‚ัŒ " + start: "ั ะธะณั€ะพะบะพะฒ, ะบั‚ะพ " + joinAddress: + text: "ะทะฐัˆะตะป ะฟะพ ะฐะดั€ะตัะพะผ" + nonOperators: "ะะตั‚ ะพะฟะตั€ะฐั‚ะพั€ะพะฒ" + notBanned: "ะะตั‚ ะทะฐะฑะฐะฝะตะฝั‹ั…" + operatorStatus: + name: "ะžะฟ-ัั‚ะฐั‚ัƒั" + operators: "ะžะฟะตั€ะฐั‚ะพั€ั‹" + playedBetween: + text: "ะ˜ะณั€ะฐะป ะผะตะถะดัƒ" + pluginGroup: + name: "ะ“ั€ัƒะฟะฟะฐ: " + text: "ะฒ ${plugin} ${group} ะ“ั€ัƒะฟะฟะฐั…" + registeredBetween: + text: "ะ—ะฐั€ะตะณะธัั‚ั€ะธั€ะพะฒะฐะปัั ะผะตะถะดัƒ" + title: + activityGroup: "ะ“ั€ัƒะฟะฟะฐ ั‚ะตะบัƒั‰ะตะน ะดะตัั‚ะตะปัŒะฝะพัั‚ะธ" + view: " ะŸั€ะพัะผะพั‚ั€:" + filters: + add: "ะ”ะพะฑะฐะฒะธั‚ัŒ ั„ะธะปัŒั‚ั€.." + loading: "ะ—ะฐะณั€ัƒะทะบะฐ ั„ะธะปัŒั‚ั€ะพะฒ.." + generic: + are: "``" + label: + from: ">ั" + makeAnother: "ะกะดะตะปะฐั‚ัŒ ะดั€ัƒะณะพะน ะทะฐะฟั€ะพั" + to: ">ะฒ" + view: "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ั€ะตะทัƒะปัŒั‚ะฐั‚" + performQuery: "ะ’ั‹ะฟะพะปะฝะธั‚ัŒ ะทะฐะฟั€ะพั!" + results: + match: "ะฝะฐะนะดะตะฝะพ ${resultCount} ะธะณั€ะพะบะพะฒ" + none: "ะ ะตะทัƒะปัŒั‚ะฐะป ะดะฐะป 0 ั€ะตะทัƒะปัŒั‚ะฐั‚ะพะฒ" + title: "ะ ะตะทัƒะปัŒั‚ะฐั‚ั‹ ะทะฐะฟั€ะพัะฐ" + title: + activity: "ะะบั‚ะธะฒะฝะพัั‚ัŒ ะฒั‹ะฑั€ะฐะฝั‹ั… ะธะณั€ะพะบะพะฒ" + activityOnDate: 'ะะบั‚ะธะฒะฝะพัั‚ัŒ ะฝะฐ ' + sessionsWithinView: "ะกะตะฐะฝัั‹ ะฒ ะฟั€ะตะดะตะปะฐั… ะฒะธะดะธะผะพัั‚ะธ" + text: "ะ—ะฐะฟั€ะพั<" + register: + completion: "ะ ะตะณะธัั‚ั€ะฐั†ะธั ะทะฐะฒะตั€ัˆะตะฝะฐ" + completion1: "ะ’ั‹ ะดะพะปะถะฝั‹ ะทะฐะบะพะฝั‡ะธั‚ัŒ ั€ะตะณะธัั‚ั€ะฐั†ะธัŽ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั." + completion2: "ะšะพะด ะฟะตั€ะตัั‚ะฐะฝะตั‚ ะดะตะนัั‚ะฒะพะฒะฐั‚ัŒ ั‡ะตั€ะตะท 15 ะผะธะฝัƒั‚" + completion3: "ะ˜ัะฟะพะปัŒะทัƒะนั‚ะต ัะปะตะดัƒัŽั‰ัƒัŽ ะบะพะผะฐะฝะดัƒ ะดะปั ะพะบะพะฝั‡ะฐะฝะธั ั€ะตะณะธัั‚ั€ะฐั†ะธะธ:" + completion4: "ะ˜ะปะธ ะธัะฟะพะปัŒะทัƒะนั‚ะต ะบะพะฝัะพะปัŒ:" + createNewUser: "ะกะพะทะดะฐั‘ะผ ะฝะพะฒะพะณะพ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั" + error: + checkFailed: "ะะต ัƒะดะฐะปะพััŒ ะฟั€ะพะฒะตั€ะธั‚ัŒ ัั‚ะฐั‚ัƒั ั€ะตะณะธัั‚ั€ะฐั†ะธะธ: " + failed: "ะ ะตะณะธัั‚ั€ะฐั†ะธั ะฝะต ัƒะดะฐะปะฐััŒ: " + noPassword: "ะ’ะฐะผ ะฝัƒะถะฝะพ ัƒะบะฐะทะฐั‚ัŒ ะฟะฐั€ะพะปัŒ" + noUsername: "ะ’ะฐะผ ะฝัƒะถะฝะพ ัƒะบะฐะทะฐั‚ัŒ ะธะผั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั" + usernameLength: "ะะธะบ ะดะพะปะถะตะฝ ะฑั‹ั‚ัŒ ะฝะต ะดะปะธะฝะฝะตะต 50 ัะธะผะฒะพะปะพะฒ, ัƒ ะฒะฐั " + login: "ะฃะถะต ะตัั‚ัŒ ะฐะบะบะฐัƒะฝั‚? ะ’ะพะนะดะธั‚ะต!" + passwordTip: "ะŸะฐั€ะพะปัŒ ะดะพะปะถะตะฝ ะฑั‹ั‚ัŒ ะฝะต ะผะตะฝะตะต 8 ัะธะผะฒะพะปะพะฒ." + register: "ะ ะตะณะธัั‚ั€ะฐั†ะธั" + usernameTip: "ะะธะบ ะดะพะปะถะตะฝ ะฑั‹ั‚ัŒ ะฝะต ะดะปะธะฝะฝะตะต 50 ัะธะผะฒะพะปะพะฒ." + text: + clickToExpand: "ะะฐะถะผะธั‚ะต, ั‡ั‚ะพะฑั‹ ั€ะฐะทะฒะตั€ะฝัƒั‚ัŒ" + comparing15days: "ะกั€ะฐะฒะฝะตะฝะธะต 15 ะดะฝะตะน" + comparing30daysAgo: "ะกั€ะฐะฒะฝะตะฝะธะต 30 ะดะฝะตะน ะฝะฐะทะฐะด ะธ ัะตะนั‡ะฐั" + noExtensionData: "ะะตั‚ ะดะฐะฝะฝั‹ั… ะพะฑ ั€ะฐััˆะธั€ะตะฝะธัั…" + noLowTps: "ะะตั‚ ะฝะธะทะบะธั… TPS" + unit: + chunks: "ะงะฐะฝะบะธ" + players: "ะ˜ะณั€ะพะบะธ" + value: + localMachine: "ะ›ะพะบะฐะปัŒะฝะฐั ะผะฐัˆะธะฝะฐ" + noKills: "ะะตั‚ ัƒะฑะธะนัั‚ะฒ" + offline: " ะะต ะฒ ัะตั‚ะธ" + online: " ะ’ ัะตั‚ะธ" + with: "ะก" + version: + changelog: "ะŸะพัะผะพั‚ั€ะตั‚ัŒ ะถัƒั€ะฝะฐะป ะธะทะผะตะฝะตะฝะธะน" + current: "ะ’ะตั€ัะธั ${0}" + download: "ะกะบะฐั‡ะฐั‚ัŒ Plan-${0}.jar" + isDev: "ะญั‚ะพ DEV ะฒะตั€ัะธั." + updateButton: "ะžะฑะฝะพะฒะธั‚ัŒ" + updateModal: + text: "ะ’ั‹ัˆะปะฐ ะฝะพะฒะฐั ะฒะตั€ัะธั, ะพะฝะฐ ะดะพัั‚ัƒะฟะฝะฐ ะดะปั ัะบะฐั‡ะธะฒะฐะฝะธั." + title: "ะ’ะตั€ัะธั ${0} ะดะพัั‚ัƒะฟะฝะฐ!" +plugin: + apiCSSAdded: "PageExtension: ${0} ะดะพะฑะฐะฒะธะป ั‚ะฐะฑะปะธั†ั‹ ัั‚ะธะปะตะน ะฒ ${1}, ${2}" + apiJSAdded: "PageExtension: ${0} ะดะพะฑะฐะฒะธะป JavaScript(ั‹) ะบ ${1}, ${2}" + disable: + database: "ะžะฑั€ะฐะฑะพั‚ะบะฐ ะบั€ะธั‚ะธั‡ะตัะบะธั… ะฝะตะพะฑั€ะฐะฑะพั‚ะฐะฝะฝั‹ั… ะทะฐะดะฐั‡. (${0})" + disabled: "ะะฝะฐะปะธั‚ะธะบะฐ ะธะณั€ะพะบะฐ ะพั‚ะบะปัŽั‡ะตะฝะฐ." + processingComplete: "ะžะฑั€ะฐะฑะพั‚ะบะฐ ะทะฐะฒะตั€ัˆะตะฝะฐ." + savingSessions: "ะกะพั…ั€ะฐะฝะตะฝะธะต ะฝะตะทะฐะฒะตั€ัˆะตะฝะฝั‹ั… ัะตััะธะน.." + savingSessionsTimeout: "ะŸั€ะตะฒั‹ัˆะตะฝ ั‚ะฐะนะผ-ะฐัƒั‚, ะฒะผะตัั‚ะพ ัั‚ะพะณะพ ะฝะตะทะฐะฒะตั€ัˆะตะฝะฝั‹ะต ัะตะฐะฝัั‹ ัะพั…ั€ะฐะฝััŽั‚ัั ะฟั€ะธ ัะปะตะดัƒัŽั‰ะตะผ ะฒะบะปัŽั‡ะตะฝะธะธ." + waitingDb: "ะ–ะดะตะผ ะพะบะพะฝั‡ะฐะฝะธั ะทะฐะฟั€ะพัะพะฒ, ั‡ั‚ะพะฑั‹ SQLite ะฝะต ะบั€ะฐัˆะฝัƒะป JVM..." + waitingDbComplete: "ะ—ะฐะบั€ั‹ั‚ะพ SQLite ัะพะตะดะธะฝะตะฝะธะต." + waitingTransactions: "ะžะถะธะดะฐะฝะธะต ะฝะตะทะฐะฒะตั€ัˆะตะฝะฝั‹ั… ั‚ั€ะฐะฝะทะฐะบั†ะธะน, ั‡ั‚ะพะฑั‹ ะธะทะฑะตะถะฐั‚ัŒ ะฟะพั‚ะตั€ะธ ะดะฐะฝะฝั‹ั….." + waitingTransactionsComplete: "ะ—ะฐะฟั€ะพัั‹ ั‚ั€ะฐะฝะทะฐะบั†ะธะน ะทะฐะบั€ั‹ั‚ั‹." + webserver: "ะ’ะตะฑ-ัะตั€ะฒะตั€ ะฑั‹ะป ะพั‚ะบะปัŽั‡ะตะฝ." + enable: + database: "${0}-ัะพะตะดะธะฝะตะฝะธะต ั ะฑะฐะทะพะน ะดะฐะฝะฝั‹ั… ัƒัั‚ะฐะฝะพะฒะปะตะฝะพ." + enabled: "ะะฝะฐะปะธั‚ะธะบะฐ ะธะณั€ะพะบะฐ ะฒะบะปัŽั‡ะตะฝะฐ." + fail: + database: "${0}-ะžัˆะธะฑะบะฐ ะฟะพะดะบะปัŽั‡ะตะฝะธั ะบ ะฑะฐะทะต ะดะฐะฝะฝั‹ั…: ${1}" + databasePatch: "ะžัˆะธะฑะบะฐ ะพะฑะฝะพะฒะปะตะฝะธั ะฑะฐะทั‹ ะดะฐะฝะฝั‹ั…, ะฟะปะฐะณะธะฝ ะดะพะปะถะตะฝ ะฑั‹ั‚ัŒ ะพั‚ะบะปัŽั‡ะตะฝ. ะŸะพะถะฐะปัƒะนัั‚ะฐ, ัะพะพะฑั‰ะธั‚ะต ะพะฑ ัั‚ะพะน ะฟั€ะพะฑะปะตะผะต" + databaseType: "${0} ะฝะตะฟะพะดะดะตั€ะถะธะฒะฐะตะผะฐั ะฑะฐะทะฐ ะดะฐะฝะฝั‹ั…" + geoDBWrite: "ะงั‚ะพ-ั‚ะพ ะฟะพัˆะปะพ ะฝะต ั‚ะฐะบ ะฟั€ะธ ัะพั…ั€ะฐะฝะตะฝะธะธ ะทะฐะณั€ัƒะถะตะฝะฝะพะน ะฑะฐะทั‹ ะณะตะพะปะพะบะฐั†ะธะธ GeoLite2" + webServer: "ะ’ะตะฑ-ัะตั€ะฒะตั€ ะฝะต ะธะฝะธั†ะธะฐะปะธะทะธั€ะพะฒะฐะฝ!" + notify: + badIP: "0.0.0.0 ะฝะต ัะฒะปัะตั‚ัั ะดะพะฟัƒัั‚ะธะผั‹ะผ ะฐะดั€ะตัะพะผ, ะฝะฐัั‚ั€ะพะนั‚ะต ะฟะฐั€ะฐะผะตั‚ั€ั‹ Alternative_IP. ะœะพะณัƒั‚ ะฑั‹ั‚ัŒ ะดะฐะฝั‹ ะฝะตะฒะตั€ะฝั‹ะต ััั‹ะปะบะธ!" + emptyIP: "IP ะฒ server.properties ะฟัƒัั‚, ะฐ Alternative_IP ะฝะต ะธัะฟะพะปัŒะทัƒะตั‚ัั. ะœะพะณัƒั‚ ะฑั‹ั‚ัŒ ะดะฐะฝั‹ ะฝะตะฒะตั€ะฝั‹ะต ััั‹ะปะบะธ!" + geoDisabled: "ะ“ะตะพะปะพะบะฐั†ะธะพะฝะฝั‹ะน ัะฑะพั€ ะฝะต ะฐะบั‚ะธะฒะตะฝ. (Data.Geolocations: false)" + geoInternetRequired: "Plan'ัƒ ะขั€ะตะฑัƒะตั‚ัั ะดะพัั‚ัƒะฟ ะฒ ะ˜ะฝั‚ะตั€ะฝะตั‚ ะฟั€ะธ ะฟะตั€ะฒะพะผ ะทะฐะฟัƒัะบะต, ั‡ั‚ะพะฑั‹ ะทะฐะณั€ัƒะทะธั‚ัŒ ะฑะฐะทัƒ ะณะตะพะปะพะบะฐั†ะธะธ GeoLite2." + storeSessions: "ะฅั€ะฐะฝะตะฝะธะต ัะตะฐะฝัะพะฒ, ะบะพั‚ะพั€ั‹ะต ะฑั‹ะปะธ ัะพั…ั€ะฐะฝะตะฝั‹ ะดะพ ะฟั€ะตะดั‹ะดัƒั‰ะตะณะพ ะฒั‹ะบะปัŽั‡ะตะฝะธั." + webserverDisabled: "ะ’ะตะฑ-ัะตั€ะฒะตั€ ะฝะต ะฑั‹ะป ะธะฝะธั†ะธะฐะปะธะทะธั€ะพะฒะฐะฝ. (WebServer.DisableWebServer: true)" + webserver: "ะ’ะตะฑ-ัะตั€ะฒะตั€ ั€ะฐะฑะพั‚ะฐะตั‚ ะฝะฐ ะฟะพั€ั‚ะต ${0} ( ${1} )" + generic: + dbApplyingPatch: "ะŸั€ะธะผะตะฝะตะฝะธะต ะธัะฟั€ะฐะฒะปะตะฝะธะน: ${0}.." + dbFaultyLaunchOptions: "ะŸะฐั€ะฐะผะตั‚ั€ั‹ ะทะฐะฟัƒัะบะฐ ะฑั‹ะปะธ ะพัˆะธะฑะพั‡ะฝั‹ะผะธ, ะธัะฟะพะปัŒะทัƒัŽั‚ัั ัั‚ะฐะฝะดะฐั€ั‚ะฝั‹ะต (${0})" + dbNotifyClean: "ะฃะดะฐะปะตะฝั‹ ะดะฐะฝะฝั‹ะต ${0} ะธะณั€ะพะบะพะฒ." + dbNotifySQLiteWAL: "ะ ะตะถะธะผ SQLite WAL ะฝะต ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ัั ะฝะฐ ัั‚ะพะน ะฒะตั€ัะธะธ ัะตั€ะฒะตั€ะฐ, ะธัะฟะพะปัŒะทัƒะตั‚ัั ัั‚ะฐะฝะดะฐั€ั‚ะฝั‹ะน. ะ’ะพะทะผะพะถะฝะพ, ัั‚ะพ ะฟะพะฒะปะธะตั‚ ะฝะฐ ะฟั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ัŒ." + dbPatchesAlreadyApplied: "ะ’ัะต ะธัะฟั€ะฐะฒะปะตะฝะธั ะฑะฐะทั‹ ะดะฐะฝะฝั‹ั… ัƒะถะต ะฟั€ะธะผะตะฝะตะฝั‹." + dbPatchesApplied: "ะ’ัะต ะธัะฟั€ะฐะฒะปะตะฝะธั ะฑะฐะทั‹ ะดะฐะฝะฝั‹ั… ัƒัะฟะตัˆะฝะพ ะฟั€ะธะผะตะฝะตะฝั‹." + dbSchemaPatch: "ะ‘ะฐะทะฐ ะดะฐะฝะฝั‹ั…: ัƒะฑะตะดะธั‚ะตััŒ, ั‡ั‚ะพ ัั…ะตะผะฐ ะพะฑะฝะพะฒะปะตะฝะฐ." + loadedServerInfo: "ะ˜ะดะตะฝั‚ะธั„ะธะบะฐั‚ะพั€ ัะตั€ะฒะตั€ะฐ ะทะฐะณั€ัƒะถะตะฝ: ${0}" + loadingServerInfo: "ะ—ะฐะณั€ัƒะทะบะฐ ะธะดะตะฝั‚ะธั„ะธะบะฐั†ะธะพะฝะฝะพะน ะธะฝั„ะพั€ะผะฐั†ะธะธ ัะตั€ะฒะตั€ะฐ" + no: "ะะตั‚" + today: "'ะกะตะณะพะดะฝั'" + unavailable: "ะะตะดะพัั‚ัƒะฟะตะฝ" + unknown: "ะะตะธะทะฒะตัั‚ะฝั‹ะน" + yes: "ะ”ะฐ" + yesterday: "'ะ’ั‡ะตั€ะฐ'" + version: + checkFail: "ะะต ัƒะดะฐะปะพััŒ ะฟั€ะพะฒะตั€ะธั‚ัŒ ะฝะพะผะตั€ ะฟะพัะปะตะดะฝะตะน ะฒะตั€ัะธะธ" + checkFailGithub: "ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะพ ะฒะตั€ัะธะธ ะฝะต ะผะพะถะตั‚ ะฑั‹ั‚ัŒ ะทะฐะณั€ัƒะถะตะฝะฐ ะธะท Github/versions.txt" + isDev: " ะญั‚ะพ DEV ั€ะตะปะธะท." + isLatest: "ะ’ั‹ ะธัะฟะพะปัŒะทัƒะตั‚ะต ะฟะพัะปะตะดะฝัŽัŽ ะฒะตั€ัะธัŽ." + updateAvailable: "ะะพะฒั‹ะน ั€ะตะปะธะท (${0}) ะดะพัั‚ัƒะฟะตะฝ ${1}" + updateAvailableSpigot: "ะะพะฒะฐั ะฒะตั€ัะธั ะดะพัั‚ัƒะฟะฝะฐ ะฝะฐ ${0}" + webserver: + fail: + SSLContext: "ะ’ะตะฑ-ัะตั€ะฒะตั€: ัะฑะพะน ะธะฝะธั†ะธะฐะปะธะทะฐั†ะธะธ ะบะพะฝั‚ะตะบัั‚ะฐ SSL." + certFileEOF: "ะ’ะตะฑ-ัะตั€ะฒะตั€: EOF ะฟั€ะธ ั‡ั‚ะตะฝะธะธ ั„ะฐะนะปะฐ ัะตั€ั‚ะธั„ะธะบะฐั‚ะฐ. (ะฃะฑะตะดะธั‚ะตััŒ, ั‡ั‚ะพ ั„ะฐะนะป ะฝะต ะฟัƒัั‚ะพะน)" + certStoreLoad: "ะ’ะตะฑ-ัะตั€ะฒะตั€: ัะฑะพะน ะทะฐะณั€ัƒะทะบะธ ัะตั€ั‚ะธั„ะธะบะฐั‚ะฐ SSL." + portInUse: "ะ’ะตะฑ-ัะตั€ะฒะตั€ ะฝะต ะฑั‹ะป ัƒัะฟะตัˆะฝะพ ะธะฝะธั†ะธะฐะปะธะทะธั€ะพะฒะฐะฝ. ะŸะพั€ั‚ (${0}) ะธัะฟะพะปัŒะทัƒะตั‚ัั?" + notify: + authDisabledConfig: "ะ’ะตะฑ-ัะตั€ะฒะตั€: ะฐะฒั‚ะพั€ะธะทะฐั†ะธั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะพั‚ะบะปัŽั‡ะตะฝะฐ! (ะžั‚ะบะปัŽั‡ะตะฝะพ ะฒ ะบะพะฝั„ะธะณะต)" + authDisabledNoHTTPS: "ะ’ะตะฑ-ัะตั€ะฒะตั€: ะฐะฒั‚ะพั€ะธะทะฐั†ะธั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะพั‚ะบะปัŽั‡ะตะฝะฐ! (ะะต ะทะฐั‰ะธั‰ะตะฝ ั‡ะตั€ะตะท HTTP)" + certificateExpiresOn: "ะ’ะตะฑ-ัะตั€ะฒะตั€: ะทะฐะณั€ัƒะถะตะฝะฝั‹ะน ัะตั€ั‚ะธั„ะธะบะฐั‚ ะดะตะนัั‚ะฒะธั‚ะตะปะตะฝ ะดะพ ${0}." + certificateExpiresPassed: "ะ’ะตะฑ-ัะตั€ะฒะตั€: ะกั€ะพะบ ะดะตะนัั‚ะฒะธั ัะตั€ั‚ะธั„ะธะบะฐั‚ะฐ ะธัั‚ะตะบ, ั€ะฐััะผะพั‚ั€ะธั‚ะต ะฒะพะทะผะพะถะฝะพัั‚ัŒ ะฟั€ะพะดะปะตะฝะธั ัะตั€ั‚ะธั„ะธะบะฐั‚ะฐ." + certificateExpiresSoon: "ะ’ะตะฑ-ัะตั€ะฒะตั€: ัั€ะพะบ ะดะตะนัั‚ะฒะธั ัะตั€ั‚ะธั„ะธะบะฐั‚ะฐ ะธัั‚ะตะบะฐะตั‚ ั‡ะตั€ะตะท ${0}, ั€ะฐััะผะพั‚ั€ะธั‚ะต ะฒะพะทะผะพะถะฝะพัั‚ัŒ ะฟั€ะพะดะปะตะฝะธั ัะตั€ั‚ะธั„ะธะบะฐั‚ะฐ." + certificateNoSuchAlias: "ะ’ะตะฑ-ัะตั€ะฒะตั€: ัะตั€ั‚ะธั„ะธะบะฐั‚ ั ะฟัะตะฒะดะพะฝะธะผะพะผ '${0}' ะฝะต ะฝะฐะนะดะตะฝ ะฒ ั„ะฐะนะปะต ั…ั€ะฐะฝะธะปะธั‰ะฐ ะบะปัŽั‡ะตะน '${1}'." + http: "ะ’ะตะฑ-ัะตั€ะฒะตั€: ะะตั‚ ัะตั€ั‚ะธั„ะธะบะฐั‚ะฐ -> ะ˜ัะฟะพะปัŒะทะพะฒะฐะฝะธะต HTTP-ัะตั€ะฒะตั€ะฐ ะดะปั ะฒะธะทัƒะฐะปะธะทะฐั†ะธะธ." + ipWhitelist: "ะ’ะตะฑ-ัะตั€ะฒะตั€: ะ‘ะตะปั‹ะน ัะฟะธัะพะบ IP ะฐะดั€ะตัะพะฒ ะฒะบะปัŽั‡ั‘ะฝ." + ipWhitelistBlock: "ะ’ะตะฑ-ัะตั€ะฒะตั€: ${0} ะฑั‹ะปะพ ะพั‚ะบะฐะทะฐะฝะพ ะฒ ะดะพัั‚ัƒะฟะต ะบ '${1}'. (ะฝะต ะฒ ะฑะตะปะพะผ ัะฟะธัะบะต)" + noCertFile: "ะ’ะตะฑ-ัะตั€ะฒะตั€: ั„ะฐะนะป ั…ั€ะฐะฝะธะปะธั‰ะฐ ะบะปัŽั‡ะตะน ัะตั€ั‚ะธั„ะธะบะฐั‚ะฐ ะฝะต ะฝะฐะนะดะตะฝ: ${0}" + reverseProxy: "ะ’ะตะฑ-ัะตั€ะฒะตั€: HTTPS ะฒ ั€ะตะถะธะผะต ะฟั€ะพะบัะธ ะฒะบะปัŽั‡ะตะฝ, ัƒะฑะตะดะธั‚ะตััŒ, ั‡ั‚ะพ ะฒะฐัˆ ะพะฑั€ะฐั‚ะฝั‹ะน ะฟั€ะพะบัะธ-ัะตั€ะฒะตั€ ะฒั‹ะฟะพะปะฝัะตั‚ ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั†ะธัŽ ั ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะตะผ HTTPS, ะธ Plan Alternative_IP.Address ัƒะบะฐะทั‹ะฒะฐะตั‚ ะฝะฐ ะฟั€ะพะบัะธ" diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_TR.txt b/Plan/common/src/main/resources/assets/plan/locale/locale_TR.txt deleted file mode 100644 index 65a2817ea..000000000 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_TR.txt +++ /dev/null @@ -1,517 +0,0 @@ -API - css+ || SayfaUzantฤฑsฤฑ: ${0} stil sayfasฤฑ(larฤฑ) eklendi ${1}, ${2} -API - js+ || SayfaUzantฤฑsฤฑ: ${0} javascript(leri) eklendi${1}, ${2} -Cmd - Click Me || Tฤฑkla bana -Cmd - Link || Link -Cmd - Link Network || AฤŸ sayfasฤฑ: -Cmd - Link Player || Oyuncu sayfasฤฑ: -Cmd - Link Player JSON || Oyuncu json: -Cmd - Link Players || Oyuncular sayfasฤฑ: -Cmd - Link Register || Kayฤฑt sayfasฤฑ: -Cmd - Link Server || Sunucu sayfasฤฑ: -CMD Arg - backup-file || Yedekleme dosyasฤฑnฤฑn adฤฑ (bรผyรผk/kรผรงรผk harfe duyarlฤฑdฤฑr.) -CMD Arg - code || Kaydฤฑ tamamlamak iรงin kullanฤฑlan kod. -CMD Arg - db type backup || Yedeklenecek veritabanฤฑnฤฑn tรผrรผ. Belirtilmezse mevcut veritabanฤฑ kullanฤฑlฤฑr. -CMD Arg - db type clear || Tรผm verilerin kaldฤฑrฤฑlacaฤŸฤฑ veritabanฤฑnฤฑn tรผrรผ. -CMD Arg - db type hotswap || Kullanฤฑlmaya baลŸlanacak veritabanฤฑnฤฑn tรผrรผ. -CMD Arg - db type move from || Verilerin taลŸฤฑnacaฤŸฤฑ veritabanฤฑnฤฑn tรผrรผ. -CMD Arg - db type move to || Verilerin taลŸฤฑnacaฤŸฤฑ veritabanฤฑnฤฑn tรผrรผ. ร–ncekiyle aynฤฑ olamaz. -CMD Arg - db type restore || Geri yรผklenecek veritabanฤฑnฤฑn tรผrรผ. Belirtilmezse mevcut veritabanฤฑ kullanฤฑlฤฑr. -CMD Arg - feature || Devre dฤฑลŸฤฑ bฤฑrakฤฑlacak รถzelliฤŸin adฤฑ: ${0} -CMD Arg - player identifier || Bir oyuncunun adฤฑ veya UUID'si -CMD Arg - player identifier remove || Identifier for a player that will be removed from current database. -CMD Arg - server identifier || Sunucunun UUID, ID veya ismi -CMD Arg - subcommand || Yardฤฑmฤฑ gรถrmek iรงin komutu alt komut olmadan kullanฤฑn. -CMD Arg - username || BaลŸka bir kullanฤฑcฤฑnฤฑn kullanฤฑcฤฑ adฤฑ. BelirtilmemiลŸse, oyuncu baฤŸlantฤฑlฤฑ kullanฤฑcฤฑ kullanฤฑlฤฑr. -CMD Arg Name - backup-file || yedek dosya -CMD Arg Name - code || ${code} -CMD Arg Name - export kind || tรผrรผ dฤฑลŸa aktar -CMD Arg Name - feature || รถzellik -CMD Arg Name - import kind || tรผrรผ iรงe aktar -CMD Arg Name - name or uuid || isim/uuid -CMD Arg Name - server || sunucu -CMD Arg Name - subcommand || altkomut -CMD Arg Name - username || kullanฤฑcฤฑ adฤฑ -Cmd Confirm - accept || Kabul et -Cmd Confirm - cancelled, no data change || ฤฐptal edildi. Hiรงbir veri deฤŸiลŸtirilmedi. -Cmd Confirm - cancelled, unregister || ฤฐptal Edildi. '${0}' kayฤฑt edilmemii -Cmd Confirm - clearing db || ${0} konumundaki tรผm Plan verilerini kaldฤฑrmak รผzeresiniz -Cmd Confirm - confirmation || Onayla: -Cmd Confirm - deny || ฤฐptal et -Cmd Confirm - Expired || Onay sรผresi doldu, komutu tekrar kullanฤฑn -Cmd Confirm - Fail on accept || Kabul edilen iลŸlem yรผrรผtme sฤฑrasฤฑnda hata verdi: ${0} -Cmd Confirm - Fail on deny || Reddedilen eylem yรผrรผtme sฤฑrasฤฑnda hata verdi: ${0} -Cmd Confirm - overwriting db || ${1} verileriyle ${0} Planฤฑndaki verilerin รผzerine yazmak รผzeresiniz -Cmd Confirm - remove player db || ${0} verilerini ${1} hesabฤฑndan kaldฤฑrmak รผzeresiniz -Cmd Confirm - unregister || ${1} ile baฤŸlantฤฑlฤฑ '${0}' kaydฤฑnฤฑ iptal etmek รผzeresiniz -Cmd db - creating backup || ${1} iรงeriฤŸine sahip bir yedek dosyasฤฑ '${0} .db' oluลŸturma -Cmd db - removal || ${0} dan Plan verileri siliniyor... -Cmd db - removal player || ${0} verileri ${1} 'dan kaldฤฑrฤฑlฤฑyor .. -Cmd db - server uninstalled || ยงaSunucu hala kuruluysa, kendisini otomatik olarak veritabanฤฑnda kurulu olarak ayarlayacaktฤฑr. -Cmd db - write || ${0}'a yazฤฑlฤฑyor... -Cmd Disable - Disabled || ยงaPlan sistemi ลŸuanda kapandฤฑ. Plugini yeniden baลŸlatmak iรงin reload komutunu kullanabilirsin. -Cmd FAIL - Accepts only these arguments || AลŸaฤŸฤฑdakileri ${0} olarak kabul eder: ${1} -Cmd FAIL - Database not open || ยงcVeritabanฤฑ ${0} - Lรผtfen bir sรผre sonra tekrar deneyin. -Cmd FAIL - Empty search string || Arama dizesi boลŸ olamaz. -Cmd FAIL - Invalid Username || ยงcKullanฤฑcฤฑnฤฑn Bir UUID si yok. -Cmd FAIL - No Feature || ยงeDevre dฤฑลŸฤฑ bฤฑrakฤฑlacak รถzelliฤŸi seรงin! (ลžuanda desteklenen ${0}) -Cmd FAIL - No Permission || ยงcBunu gerekli izne sahip deฤŸilsin. -Cmd FAIL - No player || Oyuncu '${0}' bulunamadฤฑ, UUID'leri yok. -Cmd FAIL - No player register || Oyuncu '${0}' veritabanฤฑnda bulunamadฤฑ. -Cmd FAIL - No server || Veritabanฤฑnda '${0}' sunucusu bulunamadฤฑ. -Cmd FAIL - Require only one Argument || ยงcTek Argรผman gerekli ${1} -Cmd FAIL - Requires Arguments || ยงcArgรผmanlar Gerekli: (${0}) ${1} -Cmd FAIL - see config || '${0}' ฤฑ config.yml de gรถr -Cmd FAIL - Unknown Username || ยงcKullanฤฑcฤฑ bu sunucuda hiรง gรถrรผlmemiลŸ -Cmd FAIL - Users not linked || Kullanฤฑcฤฑ hesabฤฑnฤฑza baฤŸlฤฑ deฤŸil ve diฤŸer kullanฤฑcฤฑnฤฑn hesaplarฤฑnฤฑ kaldฤฑrma izniniz yok. -Cmd FAIL - WebUser does not exists || ยงcBรถyle bir kullanฤฑcฤฑ yok! -Cmd FAIL - WebUser exists || ยงcBรถyle bir kullanฤฑcฤฑ zaten var! -Cmd Footer - Help || ยง7BaฤŸฤฑmsฤฑz deฤŸiลŸkenler komutunun รผzerine gelin veya '/${0}?' onlar hakkฤฑnda daha fazla bilgi edinmek iรงin. -Cmd Header - Analysis || > ยง2Analiz sonuรงlarฤฑ -Cmd Header - Help || > ยง2/${0} Help -Cmd Header - Info || > ยง2Oyuncu Analizi -Cmd Header - Inspect || > ยง2Oyuncu: ยงf${0} -Cmd Header - Network || > ยง2AฤŸ Sayfasฤฑ -Cmd Header - Players || > ยง2Oyuncular -Cmd Header - Search || > ยง2${0} ยงf${1} ยง2iรงin sonuรงlar: -Cmd Header - server list || id::isim::uuid -Cmd Header - Servers || > ยง2Sunucular -Cmd Header - web user list || kullanฤฑcฤฑ::baฤŸlandฤฑ::yetki seviyesi -Cmd Header - Web Users || > ยง2${0} Web kullanฤฑcฤฑlarฤฑ -Cmd Info - Bungee Connection || ยง2Bungee ye baฤŸlan: ยงf${0} -Cmd Info - Database || ยง2Mevcut veritabanฤฑ: ยงf${0} -Cmd Info - Reload Complete || ยงaYeniden baลŸlatma tamamlandฤฑ -Cmd Info - Reload Failed || ยงcPlugini yeniden baลŸlatฤฑrken bir ลŸeyler ters gitti,yeniden baลŸlatmanฤฑz tavsiye edilir. -Cmd Info - Update || ยง2Gรผncelleme mevcut: ยงf${0} -Cmd Info - Version || ยง2Versiyon: ยงf${0} -Cmd network - No network || Sunucu bir aฤŸa baฤŸlฤฑ deฤŸil. BaฤŸlantฤฑ, sunucu sayfasฤฑna yรถnlendirir. -Cmd Notify - No Address || ยงeKullanฤฑlabilir adres yok - yedek olarak localhost kullanฤฑlฤฑyor. "Alternative_IP" ayarlarฤฑnฤฑ yapฤฑn. -Cmd Notify - No WebUser || Belki Web kullanฤฑcฤฑsฤฑ deฤŸilsinizdir, Kayฤฑt olmak iรงin /plan register -Cmd Notify - WebUser register || Yeni bir kullanฤฑcฤฑ kayฤฑt oldu: '${0}' Yetki seviyesi: ${1} -Cmd Qinspect - Active Playtime || ยง2Aktif Oyun Sรผresi: ยงf${0} -Cmd Qinspect - Activity Index || ยง2Aktivite gรถstergesi: ยงf${0} | ${1} -Cmd Qinspect - AFK Playtime || ยง2AFK Saati: ยงf${0} -Cmd Qinspect - Deaths || ยง2ร–lรผmler: ยงf${0} -Cmd Qinspect - Geolocation || ยง2Bรถlgesinden giriลŸ yapฤฑldฤฑ: ยงf${0} -Cmd Qinspect - Last Seen || ยง2En son gรถrรผlme: ยงf${0} -Cmd Qinspect - Longest Session || ยง2En uzun giriลŸ: ยงf${0} -Cmd Qinspect - Mob Kills || ยง2ร–ldรผrรผlen canlฤฑlar: ยงf${0} -Cmd Qinspect - Player Kills || ยง2Oyuncu รถldรผrme: ยงf${0} -Cmd Qinspect - Playtime || ยง2Oynama sรผresi: ยงf${0} -Cmd Qinspect - Registered || ยง2Kayฤฑtlฤฑ: ยงf${0} -Cmd Qinspect - Times Kicked || ยง2Atฤฑlma sayฤฑsฤฑ: ยงf${0} -Cmd SUCCESS - Feature disabled || ยงaKapatฤฑldฤฑ '${0}' Plugin yeniden baลŸlatฤฑlana kadar. -Cmd SUCCESS - WebUser register || ยงaYeni kullanฤฑcฤฑ (${0}) baลŸarฤฑyla eklendi! -Cmd unregister - unregistering || '${0}' kaydฤฑ iptal ediliyor.. -Cmd WARN - Database not open || ยงeVeritabanฤฑ ${0} - Bu, beklenenden daha uzun sรผrebilir .. -Cmd Web - Permission Levels || >\ยง70: Tรผm sayfalara eriลŸin \ ยง71: '/ oyuncular' ve tรผm oynatฤฑcฤฑ sayfalarฤฑna eriลŸin \ ยง72: Web kullanฤฑcฤฑsฤฑ ile aynฤฑ kullanฤฑcฤฑ adฤฑna sahip oynatฤฑcฤฑ sayfasฤฑna eriลŸin \ ยง73 +: ฤฐzin yok -Command Help - /plan db || Plan veritabanฤฑnฤฑ yรถnet -Command Help - /plan db backup || Bir veritabanฤฑnฤฑn verilerini bir dosyaya yedekleyin -Command Help - /plan db clear || Veritabanฤฑndan TรœM Plan verilerini kaldฤฑrฤฑn -Command Help - /plan db hotswap || Veritabฤฑnฤฑn hฤฑzlฤฑca deฤŸiลŸtirir -Command Help - /plan db move || Veriyi Veritabanlarฤฑ arasฤฑnda taลŸฤฑr -Command Help - /plan db remove || Oyuncunun verilerini Mevcut veritabanฤฑndan kaldฤฑr -Command Help - /plan db restore || Bir dosyadaki verileri bir veritabanฤฑna geri yรผkleyin -Command Help - /plan db uninstalled || Veritabanฤฑnda bir sunucuyu kaldฤฑrฤฑlmฤฑลŸ olarak ayarlayฤฑn. -Command Help - /plan disable || Eklentiyi veya bir kฤฑsmฤฑnฤฑ devre dฤฑลŸฤฑ bฤฑrakฤฑn -Command Help - /plan export || Html veya json dosyalarฤฑnฤฑ manuel olarak dฤฑลŸa aktarฤฑn -Command Help - /plan import || Verileri iรงe aktar -Command Help - /plan info || Eklenti hakkฤฑnda bilgiler -Command Help - /plan ingame || Oyuncu bilgilerini oyun iรงi gรถsterir -Command Help - /plan json || Player'ฤฑn ham verilerinin json'unu gรถrรผntรผleyin. -Command Help - /plan logout || Paneldeki diฤŸer kullanฤฑcฤฑlarฤฑn oturumunu kapatฤฑn -Command Help - /plan network || AฤŸ sayfasฤฑnฤฑ gรถrรผntรผler -Command Help - /plan player || Oyunucunun sayfasฤฑnฤฑ gรถsterir -Command Help - /plan players || Oyuncu sayfasฤฑnฤฑ gรถrรผntรผler -Command Help - /plan register || Web kullanฤฑcฤฑsna kayฤฑt yapar -Command Help - /plan reload || Plugini yeniden baลŸlatฤฑr -Command Help - /plan search || Bir oyuncu adฤฑ arar -Command Help - /plan server || Sunucu Sayfasฤฑnฤฑ gรถsterir -Command Help - /plan servers || Sunucun tรผm veritabanฤฑnฤฑ listeler -Command Help - /plan unregister || Plan web sitesinin bir kullanฤฑcฤฑsฤฑnฤฑn kaydฤฑnฤฑ iptal edin -Command Help - /plan users || Tรผm web kullanฤฑcฤฑlarฤฑnฤฑ listeleyin -Database - Apply Patch || Yama uygulanฤฑyor: ${0}.. -Database - Patches Applied || Yama tรผm veritabanlarฤฑna baลŸarฤฑyla uygulandฤฑ. -Database - Patches Applied Already || Veritabanlarฤฑna yama zaten uygulanmฤฑลŸ. -Database MySQL - Launch Options Error || BaลŸlatma seรงenekleri hatalฤฑ, varsayฤฑlan olarak kullanฤฑn (${0}) -Database Notify - Clean || ${0} oyuncunun verileri kaldฤฑrฤฑldฤฑ. -Database Notify - SQLite No WAL || SQLite WAL modu bu sunucu versiyonunda desteklenmiyor, varsayฤฑlanฤฑ kullanฤฑn. Bu performansฤฑnฤฑzฤฑ etkiliye bilir veya etkilemezde. -Disable || Oyuncu analizi kapatฤฑldฤฑ. -Disable - Processing || ร–nceden iลŸlenmemiลŸ kritik gรถrevler iลŸleniyor. (${0}) -Disable - Processing Complete || ฤฐลŸlenme tamamlandฤฑ. -Disable - Unsaved Session Save || BitmemiลŸ oturumlar kaydediliyor .. -Disable - Unsaved Session Save Timeout || Zaman aลŸฤฑmฤฑ isabeti, bunun yerine bir sonraki etkinleลŸtirmede bitmemiลŸ oturumlarฤฑ depolar. -Disable - Waiting SQLite || SQLite'ฤฑn JVM'nin รงรถkmesini รถnlemek iรงin sorgularฤฑn bitmesi bekleniyor .. -Disable - Waiting SQLite Complete || Kapalฤฑ SQLite baฤŸlantฤฑsฤฑ. -Disable - Waiting Transactions || Veri kaybฤฑnฤฑ รถnlemek iรงin tamamlanmamฤฑลŸ iลŸlemler bekleniyor .. -Disable - Waiting Transactions Complete || ฤฐลŸlem kuyruฤŸu kapatฤฑldฤฑ. -Disable - WebServer || Websunucusu kapatฤฑldฤฑ. -Enable || Oyuncu analizi aktif edildi. -Enable - Database || ${0}- Veritabanฤฑ baฤŸlantฤฑsฤฑ kurulmuลŸ. -Enable - Notify Bad IP || 0.0.0.0 geรงerli bir adres deฤŸil, Alternatif_IP ayarlarฤฑnฤฑ yapฤฑn. YanlฤฑลŸ baฤŸlantฤฑlar verilebilir! -Enable - Notify Empty IP || server.properties IP adresi kฤฑsmฤฑ boลŸ & AlternatifIP kullanฤฑlmฤฑyor. Bu yรผzden yanlฤฑลŸ linkler verilecektir! -Enable - Notify Geolocations disabled || CoฤŸrafi konum toplama etkin deฤŸil. (Data.Geolocations: false) -Enable - Notify Geolocations Internet Required || Plan GeoLite2 Geolocation veritabanฤฑnฤฑ indirmek iรงin ilk รงalฤฑลŸtฤฑrmada internet eriลŸimi gerektir. -Enable - Notify Webserver disabled || WebServer baลŸlatฤฑlmadฤฑ. (WebServer.DisableWebServer: true) -Enable - Storing preserved sessions || ร–nceki kapatmadan รถnce korunan oturumlarฤฑ saklama. -Enable - WebServer || Webserver Bu port รผzerinden รงalฤฑลŸฤฑyor ${0} ( ${1} ) -Enable FAIL - Database || ${0}-Veritabanฤฑ baฤŸlantฤฑsฤฑ baลŸarฤฑsฤฑz: ${1} -Enable FAIL - Database Patch || VeriTabanฤฑ yamasฤฑ baลŸarฤฑsฤฑz, plugin devre dฤฑลŸฤฑ bฤฑrakฤฑlmฤฑลŸ olmalฤฑ. Lรผtfen bu sorunu bildirin. -Enable FAIL - GeoDB Write || ฤฐndirilen GeoLite2 Geolocation veritabanฤฑnฤฑ kaydederken bir ลŸeyler ters gitti. -Enable FAIL - WebServer (Proxy) || WebServer baลŸlatฤฑlmadฤฑ! -Enable FAIL - Wrong Database Type || ${0} Desteklenmeyen bir veritabanฤฑ -HTML - AND_BUG_REPORTERS || & Bug reporters! -HTML - BANNED (Filters) || Yasaklandฤฑ -HTML - COMPARING_15_DAYS || 15 gรผn karลŸฤฑlaลŸtฤฑrฤฑlฤฑyor -HTML - COMPARING_60_DAYS || 30 gรผn รถncesiyle ลŸimdi karลŸฤฑlaลŸtฤฑrฤฑlฤฑyor. -HTML - COMPARING_7_DAYS || 7 gรผn karลŸฤฑlaลŸtฤฑrฤฑlฤฑyor -HTML - DATABASE_NOT_OPEN || Veritabanฤฑ aรงฤฑk deฤŸil, / plan bilgisi ile db durumunu kontrol edin -HTML - DESCRIBE_RETENTION_PREDICTION || Bu deฤŸer, รถnceki oyunculara dayalฤฑ bir tahmindir. -HTML - ERROR || Kimlik doฤŸrulama hata nedeniyle baลŸarฤฑsฤฑz oldu -HTML - EXPIRED_COOKIE || Kullanฤฑcฤฑ รงerezinin sรผresi doldu -HTML - FILTER_ACTIVITY_INDEX_NOW || Mevcut aktivite grubu -HTML - FILTER_ALL_PLAYERS || Tรผm oyuncular -HTML - FILTER_BANNED || Yasaklama durumu -HTML - FILTER_GROUP || Grup: -HTML - FILTER_OPS || Yetkili durumu -HTML - INDEX_ACTIVE || Aktivite -HTML - INDEX_INACTIVE || Etkisiz -HTML - INDEX_IRREGULAR || Dรผzensiz -HTML - INDEX_REGULAR || Dรผzenli -HTML - INDEX_VERY_ACTIVE || ร‡ok Aktif -HTML - KILLED || ร–ldรผrรผlen -HTML - LABEL_1ST_WEAPON || En ร–lรผmcรผl PvP Silahฤฑ -HTML - LABEL_2ND_WEAPON || 2. PvP Silahฤฑ -HTML - LABEL_3RD_WEAPON || 3. PvP Silahฤฑ -HTML - LABEL_ACTIVE_PLAYTIME || Aktif Oyun Sรผresi -HTML - LABEL_ACTIVITY_INDEX || Aktivite gรถstergesi -HTML - LABEL_AFK || AFK -HTML - LABEL_AFK_TIME || AFK Sรผresi -HTML - LABEL_AVG || Ortalama -HTML - LABEL_AVG_ACTIVE_PLAYTIME || Ortalama Aktif Oyun Sรผresi -HTML - LABEL_AVG_AFK_TIME || Ortalama AFK Sรผresi -HTML - LABEL_AVG_CHUNKS || Ortalama Chunks -HTML - LABEL_AVG_ENTITIES || Ortalama Yaratฤฑklar -HTML - LABEL_AVG_KDR || Ortalama KDR -HTML - LABEL_AVG_MOB_KDR || Ortalama Mob KDR -HTML - LABEL_AVG_PLAYTIME || Ortalama Oyun Sรผresi -HTML - LABEL_AVG_SESSION_LENGTH || Ortalama Oturum UzunluฤŸu -HTML - LABEL_AVG_SESSIONS || Ortalama Oturumlar -HTML - LABEL_AVG_TPS || Ortalama TPS -HTML - LABEL_BANNED || YasaklanmฤฑลŸ -HTML - LABEL_BEST_PEAK || Tรผm Zamanlarฤฑn Zirvesi -HTML - LABEL_DAY_OF_WEEK || Day of the Week -HTML - LABEL_DEATHS || ร–lรผmler -HTML - LABEL_DOWNTIME || Arฤฑza Sรผresi -HTML - LABEL_DURING_LOW_TPS || DรผลŸรผk TPS ArtฤฑลŸlarฤฑ Sฤฑrasฤฑnda: -HTML - LABEL_ENTITIES || Varlฤฑklar -HTML - LABEL_FAVORITE_SERVER || Favori Sunucu -HTML - LABEL_FIRST_SESSION_LENGTH || ฤฐlk Oturum UzunluฤŸu -HTML - LABEL_FREE_DISK_SPACE || BoลŸ Disk Alanฤฑ -HTML - LABEL_INACTIVE || Etkin deฤŸil -HTML - LABEL_LAST_PEAK || Son Zirve -HTML - LABEL_LAST_SEEN || Son Gรถrรผlme -HTML - LABEL_LOADED_CHUNKS || YรผklenmiลŸ Chunks lar -HTML - LABEL_LOADED_ENTITIES || YรผklenmiลŸ Varlฤฑklar -HTML - LABEL_LONE_JOINS || Lone joins -HTML - LABEL_LONE_NEW_JOINS || Yalnฤฑz acemi katฤฑlฤฑyor -HTML - LABEL_LONGEST_SESSION || En Uzun Oturum -HTML - LABEL_LOW_TPS || Low TPS Spikes -HTML - LABEL_MAX_FREE_DISK || Maksimum BoลŸ Disk -HTML - LABEL_MIN_FREE_DISK || Minimum BoลŸ Disk -HTML - LABEL_MOB_DEATHS || Yaratฤฑk Yรผzรผnden รถlรผmler -HTML - LABEL_MOB_KDR || Mob ฤฐstatistiฤŸi -HTML - LABEL_MOB_KILLS || ร–ldรผrรผlen Mob -HTML - LABEL_MOST_ACTIVE_GAMEMODE || En Aktif Oyun Modu -HTML - LABEL_NAME || ฤฐsim -HTML - LABEL_NEW || Yeni -HTML - LABEL_NEW_PLAYERS || Yeni Oyuncular -HTML - LABEL_NICKNAME || Takma ad -HTML - LABEL_NO_SESSION_KILLS || Yok -HTML - LABEL_ONLINE_FIRST_JOIN || ฤฐlk katฤฑlฤฑmda รงevrimiรงi oyuncular -HTML - LABEL_OPERATOR || Yetkililer -HTML - LABEL_PER_PLAYER || / Player -HTML - LABEL_PER_REGULAR_PLAYER || / Regular Player -HTML - LABEL_PLAYER_DEATHS || Oyuncu รถlรผme sebep oldu -HTML - LABEL_PLAYER_KILLS || Oyuncu ร–ldรผrรผldรผ -HTML - LABEL_PLAYERS_ONLINE || Oyuncu ร‡evrimiรงi -HTML - LABEL_PLAYTIME || Oyun Sรผresi -HTML - LABEL_REGISTERED || Kayฤฑtlฤฑ -HTML - LABEL_REGISTERED_PLAYERS || Kayฤฑtlฤฑ Oyuncular -HTML - LABEL_REGULAR || Dรผzenli -HTML - LABEL_REGULAR_PLAYERS || Normal Oyuncular -HTML - LABEL_RELATIVE_JOIN_ACTIVITY || Gรถreli BirleลŸtirme EtkinliฤŸi -HTML - LABEL_RETENTION || Yeni Oyuncu Elde Tutma -HTML - LABEL_SERVER_DOWNTIME || Sunucu Kapalฤฑ Kalma Sรผresi -HTML - LABEL_SERVER_OCCUPIED || Sunucu iลŸgal edildi -HTML - LABEL_SESSION_ENDED || Oturum Sona Erdi -HTML - LABEL_SESSION_MEDIAN || Session Median -HTML - LABEL_TIMES_KICKED || Kere AtฤฑlmฤฑลŸ -HTML - LABEL_TOTAL_PLAYERS || Toplam Oyuncu -HTML - LABEL_TOTAL_PLAYTIME || Toplam Oyun Sรผresi -HTML - LABEL_UNIQUE_PLAYERS || Sunucuya ฤฐlk Defa Girenler -HTML - LABEL_WEEK_DAYS || 'Pazartesi', 'Salฤฑ', 'ร‡arลŸamba', 'PerลŸembe', 'Cuma', 'Cumartesi', 'Pazar' -HTML - LINK_BACK_NETWORK || AฤŸ sayfasฤฑ -HTML - LINK_BACK_SERVER || Sunucu sayfasฤฑ -HTML - LINK_CHANGELOG || DeฤŸiลŸim gรผnlรผฤŸรผnรผ incele -HTML - LINK_DISCORD || Discord'da Genel Destek -HTML - LINK_DOWNLOAD || ฤฐndir -HTML - LINK_ISSUES || Sorunlarฤฑ Bildir -HTML - LINK_NIGHT_MODE || Gece modu -HTML - LINK_PLAYER_PAGE || Oyuncu Sayfasฤฑ -HTML - LINK_QUICK_VIEW || Hฤฑzlฤฑ Gรถrรผnรผm -HTML - LINK_SERVER_ANALYSIS || Sunucu analizi -HTML - LINK_WIKI || Plan Wiki, ร–ฤŸreticiler ve Belgeler -HTML - LOCAL_MACHINE || Yerel makine -HTML - LOGIN_CREATE_ACCOUNT || Hesap oluลŸtur! -HTML - LOGIN_FAILED || GiriลŸ baลŸarฤฑsฤฑz: -HTML - LOGIN_FORGOT_PASSWORD || Parolanฤฑzฤฑ mฤฑ unuttunuz? -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_1 || Parolanฤฑzฤฑ mฤฑ unuttunuz? Kaydฤฑ iptal edin ve tekrar kaydolun. -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_2 || Mevcut kullanฤฑcฤฑnฤฑzฤฑ kaldฤฑrmak iรงin oyunda aลŸaฤŸฤฑdaki komutu kullanฤฑn: -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_3 || Veya konsol kullanarak: -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_4 || Komutu kullandฤฑktan sonra, -HTML - LOGIN_LOGIN || Oturum aรง -HTML - LOGIN_LOGOUT || ร‡ฤฑkฤฑลŸ Yap -HTML - LOGIN_PASSWORD || "Parola" -HTML - LOGIN_USERNAME || "Kullanฤฑcฤฑ adฤฑ" -HTML - NAV_PLUGINS || Pluginler -HTML - NEW_CALENDAR || Yeni: -HTML - NO_KILLS || ร–ldรผrmesi yok -HTML - NO_USER_PRESENT || Kullanฤฑcฤฑ รงerezi mevcut deฤŸil -HTML - NON_OPERATORS (Filters) || Operatรถr olmayanlar -HTML - NOT_BANNED (Filters) || Yasaklฤฑ deฤŸil -HTML - OFFLINE || ร‡evrimdฤฑลŸฤฑ -HTML - ONLINE || ร‡evrimiรงi -HTML - OPERATORS (Filters) || Operators -HTML - PER_DAY || / Gรผn -HTML - PLAYERS_TEXT || Oyuncular -HTML - QUERY || Sorgu< -HTML - QUERY_ACTIVITY_OF_MATCHED_PLAYERS || EลŸleลŸen oyuncularฤฑn etkinliฤŸi -HTML - QUERY_ACTIVITY_ON || tarihindeki etkinlik -HTML - QUERY_ADD_FILTER || Filtre ekle -HTML - QUERY_AND || ve -HTML - QUERY_ARE || `vardฤฑr` -HTML - QUERY_ARE_ACTIVITY_GROUP || Etkinlik Gruplarฤฑnda -HTML - QUERY_ARE_PLUGIN_GROUP || ${plugin} adlฤฑ kiลŸinin ${group} Grubunda -HTML - QUERY_JOINED_WITH_ADDRESS || adresle katฤฑldฤฑ -HTML - QUERY_LOADING_FILTERS || Filtreler yรผkleniyor.. -HTML - QUERY_MAKE || Sorgu yap -HTML - QUERY_MAKE_ANOTHER || BaลŸka bir sorgu yap -HTML - QUERY_OF_PLAYERS || Oyuncularฤฑn -HTML - QUERY_PERFORM_QUERY || Sorgu GerรงekleลŸtirin! -HTML - QUERY_PLAYED_BETWEEN || Arasฤฑnda oynanan -HTML - QUERY_REGISTERED_BETWEEN || Arasฤฑnda kayฤฑtlฤฑ -HTML - QUERY_RESULTS || Sorgu Sonuรงlarฤฑ -HTML - QUERY_RESULTS_MATCH || ${resultCount} oyuncuyla eลŸleลŸti -HTML - QUERY_SESSIONS_WITHIN_VIEW || Gรถrรผnรผm iรงindeki oturumlar -HTML - QUERY_SHOW_VIEW || Bir gรถrรผnรผm gรถster -HTML - QUERY_TIME_FROM || >dan -HTML - QUERY_TIME_TO || >a -HTML - QUERY_VIEW || Gรถrรผnรผm: -HTML - QUERY_ZERO_RESULTS || Sorgu 0 sonuรง รผretti -HTML - REGISTER || Kayฤฑt ol -HTML - REGISTER_CHECK_FAILED || Kayฤฑt durumu kontrol edilemedi: -HTML - REGISTER_COMPLETE || Kaydฤฑ Tamamla -HTML - REGISTER_COMPLETE_INSTRUCTIONS_1 || Artฤฑk kullanฤฑcฤฑyฤฑ kaydetmeyi bitirebilirsiniz. -HTML - REGISTER_COMPLETE_INSTRUCTIONS_2 || Kod 15 dakika iรงinde sona eriyor -HTML - REGISTER_COMPLETE_INSTRUCTIONS_3 || Kaydฤฑ bitirmek iรงin oyunda aลŸaฤŸฤฑdaki komutu kullanฤฑn: -HTML - REGISTER_COMPLETE_INSTRUCTIONS_4 || Veya konsol kullanarak: -HTML - REGISTER_CREATE_USER || Yeni bir kullanฤฑcฤฑ oluลŸturun -HTML - REGISTER_FAILED || Kayฤฑt baลŸarฤฑsฤฑz: -HTML - REGISTER_HAVE_ACCOUNT || Zaten hesabฤฑnฤฑz var mฤฑ? Oturum aรง! -HTML - REGISTER_PASSWORD_TIP || Parola 8 karakterden fazla olmalฤฑdฤฑr, ancak herhangi bir sฤฑnฤฑrlama yoktur. -HTML - REGISTER_SPECIFY_PASSWORD || Bir Parola belirlemeniz gerekiyor -HTML - REGISTER_SPECIFY_USERNAME || Kullanฤฑcฤฑ adฤฑ belirlemeniz gerekiyor -HTML - REGISTER_USERNAME_LENGTH || Kullanฤฑcฤฑ adฤฑ 50 karaktere kadar olabilir, sizinki -HTML - REGISTER_USERNAME_TIP || Kullanฤฑcฤฑ adฤฑ 50 karaktere kadar olabilir. -HTML - SESSION || Oturum -HTML - SIDE_GEOLOCATIONS || CoฤŸrafi Konumlar -HTML - SIDE_INFORMATION || DANIลžMA -HTML - SIDE_LINKS || BAฤžLANTILAR -HTML - SIDE_NETWORK_OVERVIEW || AฤŸ Gรถrรผnรผmรผ -HTML - SIDE_OVERVIEW || Genel BakฤฑลŸ -HTML - SIDE_PERFORMANCE || Performans -HTML - SIDE_PLAYER_LIST || Oyuncu Listesi -HTML - SIDE_PLAYERBASE || Oyuncu tabanฤฑ -HTML - SIDE_PLAYERBASE_OVERVIEW || Playerbase'e Genel BakฤฑลŸ -HTML - SIDE_PLUGINS || PLUGINLER -HTML - SIDE_PVP_PVE || PvP & PvE -HTML - SIDE_SERVERS || Sunucular -HTML - SIDE_SERVERS_TITLE || SUNUCULAR -HTML - SIDE_SESSIONS || Oturumlar -HTML - SIDE_TO_MAIN_PAGE || Ana Sayfaya -HTML - TEXT_CLICK_TO_EXPAND || GeniลŸletmek iรงin tฤฑklayฤฑn -HTML - TEXT_CONTRIBUTORS_CODE || kod katkฤฑda bulunan -HTML - TEXT_CONTRIBUTORS_LOCALE || รงevirmen -HTML - TEXT_CONTRIBUTORS_MONEY || GeliลŸimi parasal olarak destekleyenlere ekstra รถzel teลŸekkรผrler. -HTML - TEXT_CONTRIBUTORS_THANKS || Ayrฤฑca harika insanlarฤฑ takip ederek katkฤฑda bulundu: -HTML - TEXT_DEV_VERSION || Bu sรผrรผm bir DEV sรผrรผmรผdรผr. -HTML - TEXT_DEVELOPED_BY || tarafฤฑndan geliลŸtirilmiลŸtir -HTML - TEXT_FIRST_SESSION || ฤฐlk seans -HTML - TEXT_LICENSED_UNDER || Player Analytics, altฤฑnda geliลŸtirilir ve lisanslanฤฑr -HTML - TEXT_METRICS || bStats Metrics -HTML - TEXT_NO_EXTENSION_DATA || Uzantฤฑ Verisi Yok -HTML - TEXT_NO_LOW_TPS || DรผลŸรผk tps artฤฑลŸlarฤฑ yok -HTML - TEXT_NO_SERVER || ร‡evrimiรงi etkinliฤŸi gรถsterecek sunucu yok -HTML - TEXT_NO_SERVERS || Veritabanฤฑnda sunucu bulunamadฤฑ -HTML - TEXT_PLUGIN_INFORMATION || Eklenti hakkฤฑnda bilgiler -HTML - TEXT_PREDICTED_RETENTION || Bu deฤŸer, รถnceki oyunculara dayalฤฑ bir tahmindir -HTML - TEXT_SERVER_INSTRUCTIONS || Plan'ฤฑn herhangi bir oyun sunucusuna yรผklenmediฤŸi veya aynฤฑ veritabanฤฑna baฤŸlฤฑ olmadฤฑฤŸฤฑ anlaลŸฤฑlฤฑyor. AฤŸ eฤŸitimi iรงin wiki 'ye bakฤฑn. -HTML - TEXT_VERSION || Yeni bir sรผrรผm yayฤฑnlandฤฑ ve ลŸimdi indirilebilir. -HTML - TITLE_30_DAYS || 30 gรผn -HTML - TITLE_30_DAYS_AGO || 30 gรผn รถnce -HTML - TITLE_ALL || Tamamฤฑ -HTML - TITLE_ALL_TIME || Tรผm zamanlar -HTML - TITLE_AS_NUMBERS || Sayฤฑlar olarak -HTML - TITLE_AVG_PING || Ortalama Ping -HTML - TITLE_BEST_PING || En iyi Ping -HTML - TITLE_CALENDAR || Takvim -HTML - TITLE_CONNECTION_INFO || BaฤŸlantฤฑ Bilgisi -HTML - TITLE_COUNTRY || รœlke -HTML - TITLE_CPU_RAM || CPU & RAM -HTML - TITLE_CURRENT_PLAYERBASE || ลžuanki Oyuncu Tabanฤฑ -HTML - TITLE_DISK || Disk Alanฤฑ -HTML - TITLE_GRAPH_DAY_BY_DAY || Gรผn gรผn -HTML - TITLE_GRAPH_HOUR_BY_HOUR || Saat saat -HTML - TITLE_GRAPH_NETWORK_ONLINE_ACTIVITY || AฤŸ ร‡evrimiรงi EtkinliฤŸi -HTML - TITLE_GRAPH_PUNCHCARD || 30 gรผnlรผk Punchcard -HTML - TITLE_INSIGHTS || 30 gรผnlรผk bilgiler -HTML - TITLE_IS_AVAILABLE || mรผsait -HTML - TITLE_JOIN_ADDRESSES || Adreslere Katฤฑl -HTML - TITLE_LAST_24_HOURS || Son 24 saat -HTML - TITLE_LAST_30_DAYS || Son 30 gรผn -HTML - TITLE_LAST_7_DAYS || Son 7 gรผn -HTML - TITLE_LAST_CONNECTED || Son baฤŸlantฤฑ -HTML - TITLE_LENGTH || Uzunluk -HTML - TITLE_MOST_PLAYED_WORLD || En รงok oynanan Dรผnya -HTML - TITLE_NETWORK || AฤŸ -HTML - TITLE_NETWORK_AS_NUMBERS || Sayฤฑlarla AฤŸ -HTML - TITLE_NOW || ลžimdi -HTML - TITLE_ONLINE_ACTIVITY || ร‡evrimiรงi Aktivite -HTML - TITLE_ONLINE_ACTIVITY_AS_NUMBERS || Sayฤฑlarla ร‡evrimiรงi Etkinlik -HTML - TITLE_ONLINE_ACTIVITY_OVERVIEW || ร‡evrimiรงi EtkinliฤŸe Genel BakฤฑลŸ -HTML - TITLE_PERFORMANCE_AS_NUMBERS || Rakamlarla Performans -HTML - TITLE_PING || Ping -HTML - TITLE_PLAYER || Oyuncu -HTML - TITLE_PLAYER_OVERVIEW || Oyuncuya Genel BakฤฑลŸ -HTML - TITLE_PLAYERBASE_DEVELOPMENT || Oyuncu Etkinlik GrafiฤŸi -HTML - TITLE_PVP_DEATHS || Son PvP ร–lรผmleri -HTML - TITLE_PVP_KILLS || Son PvP ร–ldรผrmeleri -HTML - TITLE_PVP_PVE_NUMBERS || Sayฤฑlarla PvP & PvE -HTML - TITLE_RECENT_KILLS || Son ร–ldรผrmeler -HTML - TITLE_RECENT_SESSIONS || En Son Oturumlar -HTML - TITLE_SEEN_NICKNAMES || Gรถrรผlen takma adlar -HTML - TITLE_SERVER || Sunucu -HTML - TITLE_SERVER_AS_NUMBERS || Server as Numbers -HTML - TITLE_SERVER_OVERVIEW || Sunucuya Genel BakฤฑลŸ -HTML - TITLE_SERVER_PLAYTIME || Sunucu Oynatma Sรผresi -HTML - TITLE_SERVER_PLAYTIME_30 || 30 gรผnlรผk Sunucu Oynatma Sรผresi -HTML - TITLE_SESSION_START || Oturum BaลŸladฤฑ -HTML - TITLE_THEME_SELECT || Tema Seรงimi -HTML - TITLE_TITLE_PLAYER_PUNCHCARD || Punchcard -HTML - TITLE_TPS || TPS -HTML - TITLE_TREND || Trend -HTML - TITLE_TRENDS || 30 gรผnlรผk trendler -HTML - TITLE_VERSION || Versiyon -HTML - TITLE_WEEK_COMPARISON || Hafta KarลŸฤฑlaลŸtฤฑrmasฤฑ -HTML - TITLE_WORLD || Dรผnya Yรผkle -HTML - TITLE_WORLD_PLAYTIME || Dรผnya Oyun Sรผresi -HTML - TITLE_WORST_PING || En kรถtรผ Ping -HTML - TOTAL_ACTIVE_TEXT || Toplam Aktiflik -HTML - TOTAL_AFK || Toplam AFKlฤฑk -HTML - TOTAL_PLAYERS || Toplam Oyuncular -HTML - UNIQUE_CALENDAR || Benzersiz: -HTML - UNIT_CHUNKS || Chunks -HTML - UNIT_ENTITIES || Yaratฤฑklar -HTML - UNIT_NO_DATA || Veri yok -HTML - UNIT_THE_PLAYERS || Oyuncular -HTML - USER_AND_PASS_NOT_SPECIFIED || Kullanฤฑcฤฑ ve ลžifre belirtilmedi -HTML - USER_DOES_NOT_EXIST || Bรถyle Bir Kullanฤฑcฤฑ Yok -HTML - USER_INFORMATION_NOT_FOUND || Kayฤฑt baลŸarฤฑsฤฑz oldu, tekrar deneyin (Kodun sรผresi 15 dakika sonra dolar) -HTML - USER_PASS_MISMATCH || Kullanฤฑcฤฑ adฤฑ ve ลŸifre uyuลŸmuyor -HTML - Version Change log || DeฤŸiลŸim gรผnlรผฤŸรผnรผ incele -HTML - Version Current || You have version ${0} -HTML - Version Download || Download Plan-${0}.jar -HTML - Version Update || Gรผncelleme -HTML - Version Update Available || Version ${0} is Available! -HTML - Version Update Dev || Bu sรผrรผm bir DEV sรผrรผmรผdรผr. -HTML - Version Update Info || Yeni bir sรผrรผm yayฤฑnlandฤฑ ve artฤฑk indirilebilir. -HTML - WARNING_NO_GAME_SERVERS || Bazฤฑ veriler, Plan'ฤฑn oyun sunucularฤฑna yรผklenmesini gerektirir. -HTML - WARNING_NO_GEOLOCATIONS || Konum belirleme toplama yapฤฑlandฤฑrmada etkinleลŸtirilmelidir (GeoLite2 EULA'yฤฑ Kabul Et). -HTML - WARNING_NO_SPONGE_CHUNKS || Chunklar Spongeda mevcut deฤŸil -HTML - WITH || Birlikte -HTML ERRORS - ACCESS_DENIED_403 || GiriลŸ reddedildi -HTML ERRORS - AUTH_FAIL_TIPS_401 || - Bir kullanฤฑcฤฑyฤฑ /plan register
- ile kayฤฑt ettiฤŸinize emin olun ismin ve ลŸifrenin doฤŸru olup olmadฤฑฤŸฤฑnฤฑ kontol edin
- Kullanฤฑcฤฑ adฤฑ ve ลŸifre bรผyรผk / kรผรงรผk harf duyarlฤฑdฤฑr

EฤŸer ลŸifreni unuttuysan, Yetkiliden sizi tekrar kayฤฑt etmesini isteyin. -HTML ERRORS - AUTHENTICATION_FAILED_401 || Kimlik doฤŸrulama baลŸarฤฑsฤฑz oldu. -HTML ERRORS - FORBIDDEN_403 || YasaklanmฤฑลŸ. -HTML ERRORS - NO_SERVERS_404 || ฤฐsteฤŸi gerรงekleลŸtirecek รงevrimiรงi sunucu yok. -HTML ERRORS - NOT_FOUND_404 || Bulunamadฤฑ -HTML ERRORS - NOT_PLAYED_404 || Oyuncu bu sunucuda hiรง oynamadฤฑ. -HTML ERRORS - PAGE_NOT_FOUND_404 || Bรถyle bir sayfa mevcut deฤŸil. -HTML ERRORS - UNAUTHORIZED_401 || Yetkisiz -HTML ERRORS - UNKNOWN_PAGE_404 || Bir komutla verilen baฤŸlantฤฑya eriลŸtiฤŸinizden emin olun, ร–rnekler:

/ player / {uuid / name}
/ server / {uuid / name / id}

-HTML ERRORS - UUID_404 || Oyuncunun UUID si veritabanฤฑnda bulunamadฤฑ. -In Depth Help - /plan db || Verileri bir ลŸekilde deฤŸiลŸtirmek iรงin farklฤฑ veritabanฤฑ alt komutlarฤฑ kullanฤฑn -In Depth Help - /plan db backup || Hedef veritabanฤฑnฤฑ bir dosyaya yedeklemek iรงin SQLite kullanฤฑr. -In Depth Help - /plan db clear || Sรผreรงteki tรผm Plan verilerini kaldฤฑrarak tรผm Plan tablolarฤฑnฤฑ temizler. -In Depth Help - /plan db hotswap || Eklentiyi diฤŸer veritabanฤฑyla yeniden yรผkler ve eลŸleลŸecek ลŸekilde yapฤฑlandฤฑrmayฤฑ deฤŸiลŸtirir. -In Depth Help - /plan db move || DiฤŸer veritabanฤฑndaki iรงeriklerin รผzerine baลŸka bir veritabanฤฑndaki iรงeriklerin รผzerine yazar. -In Depth Help - /plan db remove || Bir oyuncuyu baฤŸlฤฑ tรผm verileri Geรงerli veritabanฤฑndan kaldฤฑrฤฑr. -In Depth Help - /plan db restore || SQLite yedekleme dosyasฤฑnฤฑ kullanฤฑr ve hedef veritabanฤฑnฤฑn iรงeriฤŸinin รผzerine yazar. -In Depth Help - /plan db uninstalled || Plan veritabanฤฑndaki bir sunucuyu, sunucu sorgularฤฑnda gรถrรผnmemesi iรงin kaldฤฑrฤฑldฤฑ olarak iลŸaretler. -In Depth Help - /plan disable || Bir sonraki yeniden yรผklemeye / yeniden baลŸlatmaya kadar eklentiyi veya bir kฤฑsmฤฑnฤฑ devre dฤฑลŸฤฑ bฤฑrakฤฑn. -In Depth Help - /plan export || Yapฤฑlandฤฑrmada tanฤฑmlanan dฤฑลŸa aktarma iรงin bir dฤฑลŸa aktarma gerรงekleลŸtirir. -In Depth Help - /plan import || Veritabanฤฑna veri yรผklemek iรงin bir iรงe aktarma gerรงekleลŸtirir. -In Depth Help - /plan info || Eklentinin mevcut durumunu gรถrรผntรผleyin. -In Depth Help - /plan ingame || Oyun iรงindeyken oyuncu hakkฤฑnda bilgi verir. -In Depth Help - /plan json || Bir oyuncunun verilerini json formatฤฑnda indirmenize izin verir. Hepsi. -In Depth Help - /plan logout || Panelden baลŸka bir kullanฤฑcฤฑnฤฑn oturumunu kapatmak iรงin kullanฤฑcฤฑ adฤฑ baฤŸฤฑmsฤฑz deฤŸiลŸkeni verin, herkesi kapatmak iรงin baฤŸฤฑmsฤฑz deฤŸiลŸken olarak * verin. -In Depth Help - /plan network || /network sayfasฤฑna bir baฤŸlantฤฑ edinin, bunu yalnฤฑzca aฤŸlarda yapar. -In Depth Help - /plan player || Belirli bir oyuncunun veya mevcut oyuncunun /player sayfasฤฑna bir baฤŸlantฤฑ edinin. -In Depth Help - /plan players || Oyuncularฤฑn bir listesini gรถrmek iรงin /players sayfasฤฑna bir baฤŸlantฤฑ edinin. -In Depth Help - /plan register || Kayฤฑt sayfasฤฑna baฤŸlantฤฑ almak iรงin baฤŸฤฑmsฤฑz deฤŸiลŸken olmadan kullanฤฑn. Kullanฤฑcฤฑ edinmek iรงin kayฤฑttan sonra --code [kod] kullanฤฑn. -In Depth Help - /plan reload || Yapฤฑlandฤฑrmadaki deฤŸiลŸiklikleri yeniden yรผklemek iรงin eklentiyi devre dฤฑลŸฤฑ bฤฑrakฤฑn ve etkinleลŸtirin. -In Depth Help - /plan search || Bir adฤฑn belirli bir kฤฑsmฤฑyla eลŸleลŸen tรผm oyuncu adlarฤฑnฤฑ listeleyin. -In Depth Help - /plan server || Belirli bir sunucunun /server sayfasฤฑna veya herhangi bir baฤŸฤฑmsฤฑz deฤŸiลŸken verilmemiลŸse geรงerli sunucuya bir baฤŸlantฤฑ edinin. -In Depth Help - /plan servers || Veritabanฤฑndaki sunucularฤฑn kimliklerini, adlarฤฑnฤฑ ve kullanฤฑcฤฑlarฤฑnฤฑ listeleyin. -In Depth Help - /plan unregister || Oyuncu baฤŸlantฤฑlฤฑ kullanฤฑcฤฑnฤฑn kaydฤฑnฤฑ silmek iรงin baฤŸฤฑmsฤฑz deฤŸiลŸken olmadan veya baลŸka bir kullanฤฑcฤฑnฤฑn kaydฤฑnฤฑ silmek iรงin kullanฤฑcฤฑ adฤฑ baฤŸฤฑmsฤฑz deฤŸiลŸkeniyle kullanฤฑn. -In Depth Help - /plan users || Web kullanฤฑcฤฑlarฤฑnฤฑ tablo olarak listeler. -Manage - Confirm Overwrite || ${0} iรงindeki verilen รผzerinden yazฤฑlacak! -Manage - Confirm Removal || ${0} ฤฐรงindeki Veri Silinecek! -Manage - Fail || > ยงcBirลŸey yanlฤฑลŸ gidiyor: ${0} -Manage - Fail File not found || > ยงcBurada bir dosya bulunamadฤฑ ${0} -Manage - Fail Incorrect Database || > ยงc'${0}' Desteklenmeyen bir VeriTabanฤฑ. -Manage - Fail No Exporter || ยงe"${0}" dฤฑลŸa aktarฤฑcฤฑsฤฑ mevcut deฤŸil -Manage - Fail No Importer || ยงeAlฤฑcฤฑ '${0}' yok -Manage - Fail No Server || Verilen parametrelere sahip sunucu bulunamadฤฑ. -Manage - Fail Same Database || > ยงcAynฤฑ veritabanฤฑnda veya benzerinde รงalฤฑลŸamaz! -Manage - Fail Same server || Bu sunucuyu kaldฤฑrฤฑlmฤฑลŸ olarak iลŸaretleyemezsiniz (Siz buradasฤฑnฤฑz) -Manage - Fail, Confirmation || > ยงcKomutu onaylamak iรงin '-a' komuta ekle: ${0} -Manage - List Importers || ฤฐรงe aktarฤฑcฤฑlar: -Manage - Progress || ${0} / ${1} iลŸlendi.. -Manage - Remind HotSwap || ยงeYeni veritabanฤฑna geรงmeyi (/plan db hotswap ${0}) ve eklentiyi yeniden yรผklemeyi unutmayฤฑn. -Manage - Start || > ยง2Veri iลŸleniyor.. -Manage - Success || > ยงaBaลŸarฤฑlฤฑ! -Negative || Hayฤฑr -Positive || Evet -Today || 'Bugรผn' -Unavailable || Kullanฤฑm dฤฑลŸฤฑ -Unknown || Bilinmeyen -Version - DEV || Bu bir GELฤฐลžTฤฐRฤฐCฤฐ sรผrรผmรผdรผr. -Version - Latest || En son sรผrรผmรผ kullanฤฑyorsunuz. -Version - New || Yeni sรผrรผm (${0}) mevcut ${1} -Version - New (old) || Yeni sรผrรผm ${0} -Version FAIL - Read info (old) || En yeni sรผrรผm numarasฤฑ kontrol edilemedi -Version FAIL - Read versions.txt || Sรผrรผm bilgileri Github/versions.txt kฤฑsmฤฑndan indirilemedi -Web User Listing || ยง2${0} ยง7: ยงf${1} -WebServer - Notify HTTP || WebServer: Sertifika yok -> Gรถrรผntรผlemek iรงin HTTP-server kullanฤฑlฤฑyor. -WebServer - Notify HTTP User Auth || WebServer: Kullanฤฑcฤฑ Yetkisi Devre DฤฑลŸฤฑ! (HTTP Gรผvenli deฤŸil) -WebServer - Notify HTTPS User Auth || WebServer: Kullanฤฑcฤฑ Yetkilendirmesi Devre DฤฑลŸฤฑ! (Yapฤฑlandฤฑrmada devre dฤฑลŸฤฑ bฤฑrakฤฑldฤฑ) -Webserver - Notify IP Whitelist || Webserver: IP Whitelist etkinleลŸtirildi.. -Webserver - Notify IP Whitelist Block || Webserver: ${0} was denied access to '${1}'. (not whitelisted) -WebServer - Notify no Cert file || WebServer: Setrifikasฤฑ Dosyasฤฑ Bulunamadฤฑ: ${0} -WebServer - Notify Using Proxy || WebServer: Proxy-mode HTTPS enabled, make sure that your reverse-proxy is routing using HTTPS and Plan Alternative_IP.Address points to the Proxy -WebServer FAIL - EOF || WebServer: EOF when reading Certificate file. (Dosyanฤฑn boลŸ olmadฤฑฤŸฤฑnฤฑ kontrol edin.) -WebServer FAIL - Port Bind || Web Sunucusu baลŸarฤฑyla baลŸlatฤฑlmadฤฑ. Bu (${0}) port mu kullanฤฑlฤฑyor ? -WebServer FAIL - SSL Context || WebServer: SSL ฤฐรงeriฤŸi BaลŸlatma BaลŸarฤฑsฤฑz Oldu. -WebServer FAIL - Store Load || WebServer: SSL Sertifikasฤฑ yรผklenirken sorun oluลŸtu. -Yesterday || 'Dรผn' \ No newline at end of file diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_TR.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_TR.yml new file mode 100644 index 000000000..b18397d57 --- /dev/null +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_TR.yml @@ -0,0 +1,669 @@ +403AccessDenied: "GiriลŸ reddedildi" +command: + argument: + backupFile: + description: "Yedekleme dosyasฤฑnฤฑn adฤฑ (bรผyรผk/kรผรงรผk harfe duyarlฤฑdฤฑr.)" + name: "yedek dosya" + code: + description: "Kaydฤฑ tamamlamak iรงin kullanฤฑlan kod." + name: "${code}" + dbBackup: + description: "Yedeklenecek veritabanฤฑnฤฑn tรผrรผ. Belirtilmezse mevcut veritabanฤฑ kullanฤฑlฤฑr." + dbRestore: + description: "Geri yรผklenecek veritabanฤฑnฤฑn tรผrรผ. Belirtilmezse mevcut veritabanฤฑ kullanฤฑlฤฑr." + dbTypeHotswap: + description: "Kullanฤฑlmaya baลŸlanacak veritabanฤฑnฤฑn tรผrรผ." + dbTypeMoveFrom: + description: "Verilerin taลŸฤฑnacaฤŸฤฑ veritabanฤฑnฤฑn tรผrรผ." + dbTypeMoveTo: + description: "Verilerin taลŸฤฑnacaฤŸฤฑ veritabanฤฑnฤฑn tรผrรผ. ร–ncekiyle aynฤฑ olamaz." + dbTypeRemove: + description: "Tรผm verilerin kaldฤฑrฤฑlacaฤŸฤฑ veritabanฤฑnฤฑn tรผrรผ." + exportKind: "tรผrรผ dฤฑลŸa aktar" + feature: + description: "Devre dฤฑลŸฤฑ bฤฑrakฤฑlacak รถzelliฤŸin adฤฑ: ${0}" + name: "รถzellik" + importKind: "tรผrรผ iรงe aktar" + nameOrUUID: + description: "Bir oyuncunun adฤฑ veya UUID'si" + name: "isim/uuid" + removeDescription: "Identifier for a player that will be removed from current database." + server: + description: "Sunucunun UUID, ID veya ismi" + name: "sunucu" + subcommand: + description: "Yardฤฑmฤฑ gรถrmek iรงin komutu alt komut olmadan kullanฤฑn." + name: "altkomut" + username: + description: "BaลŸka bir kullanฤฑcฤฑnฤฑn kullanฤฑcฤฑ adฤฑ. BelirtilmemiลŸse, oyuncu baฤŸlantฤฑlฤฑ kullanฤฑcฤฑ kullanฤฑlฤฑr." + name: "kullanฤฑcฤฑ adฤฑ" + confirmation: + accept: "Kabul et" + cancelNoChanges: "ฤฐptal edildi. Hiรงbir veri deฤŸiลŸtirilmedi." + cancelNoUnregister: "ฤฐptal Edildi. '${0}' kayฤฑt edilmemii" + confirm: "Onayla:" + dbClear: "${0} konumundaki tรผm Plan verilerini kaldฤฑrmak รผzeresiniz" + dbOverwrite: "${1} verileriyle ${0} Planฤฑndaki verilerin รผzerine yazmak รผzeresiniz" + dbRemovePlayer: "${0} verilerini ${1} hesabฤฑndan kaldฤฑrmak รผzeresiniz" + deny: "ฤฐptal et" + expired: "Onay sรผresi doldu, komutu tekrar kullanฤฑn" + unregister: "${1} ile baฤŸlantฤฑlฤฑ '${0}' kaydฤฑnฤฑ iptal etmek รผzeresiniz" + database: + creatingBackup: "${1} iรงeriฤŸine sahip bir yedek dosyasฤฑ '${0} .db' oluลŸturma" + failDbNotOpen: "ยงcVeritabanฤฑ ${0} - Lรผtfen bir sรผre sonra tekrar deneyin." + manage: + confirm: "> ยงcKomutu onaylamak iรงin '-a' komuta ekle: ${0}" + confirmOverwrite: "${0} iรงindeki verilen รผzerinden yazฤฑlacak!" + confirmPartialRemoval: "Join Address Data for Server ${0} in ${1} will be removed!" + confirmRemoval: "${0} ฤฐรงindeki Veri Silinecek!" + fail: "> ยงcBirลŸey yanlฤฑลŸ gidiyor: ${0}" + failFileNotFound: "> ยงcBurada bir dosya bulunamadฤฑ ${0}" + failIncorrectDB: "> ยงc'${0}' Desteklenmeyen bir VeriTabanฤฑ." + failNoServer: "Verilen parametrelere sahip sunucu bulunamadฤฑ." + failSameDB: "> ยงcAynฤฑ veritabanฤฑnda veya benzerinde รงalฤฑลŸamaz!" + failSameServer: "Bu sunucuyu kaldฤฑrฤฑlmฤฑลŸ olarak iลŸaretleyemezsiniz (Siz buradasฤฑnฤฑz)" + hotswap: "ยงeYeni veritabanฤฑna geรงmeyi (/plan db hotswap ${0}) ve eklentiyi yeniden yรผklemeyi unutmayฤฑn." + importers: "ฤฐรงe aktarฤฑcฤฑlar:" + preparing: "Preparing.." + progress: "${0} / ${1} iลŸlendi.." + start: "> ยง2Veri iลŸleniyor.." + success: "> ยงaBaลŸarฤฑlฤฑ!" + playerRemoval: "${0} verileri ${1} 'dan kaldฤฑrฤฑlฤฑyor .." + removal: "${0} dan Plan verileri siliniyor..." + serverUninstalled: "ยงaSunucu hala kuruluysa, kendisini otomatik olarak veritabanฤฑnda kurulu olarak ayarlayacaktฤฑr." + unregister: "'${0}' kaydฤฑ iptal ediliyor.." + warnDbNotOpen: "ยงeVeritabanฤฑ ${0} - Bu, beklenenden daha uzun sรผrebilir .." + write: "${0}'a yazฤฑlฤฑyor..." + fail: + emptyString: "Arama dizesi boลŸ olamaz." + invalidArguments: "AลŸaฤŸฤฑdakileri ${0} olarak kabul eder: ${1}" + invalidUsername: "ยงcKullanฤฑcฤฑnฤฑn Bir UUID si yok." + missingArguments: "ยงcArgรผmanlar Gerekli: (${0}) ${1}" + missingFeature: "ยงeDevre dฤฑลŸฤฑ bฤฑrakฤฑlacak รถzelliฤŸi seรงin! (ลžuanda desteklenen ${0})" + missingLink: "Kullanฤฑcฤฑ hesabฤฑnฤฑza baฤŸlฤฑ deฤŸil ve diฤŸer kullanฤฑcฤฑnฤฑn hesaplarฤฑnฤฑ kaldฤฑrma izniniz yok." + noPermission: "ยงcBunu gerekli izne sahip deฤŸilsin." + onAccept: "Kabul edilen iลŸlem yรผrรผtme sฤฑrasฤฑnda hata verdi: ${0}" + onDeny: "Reddedilen eylem yรผrรผtme sฤฑrasฤฑnda hata verdi: ${0}" + playerNotFound: "Oyuncu '${0}' bulunamadฤฑ, UUID'leri yok." + playerNotInDatabase: "Oyuncu '${0}' veritabanฤฑnda bulunamadฤฑ." + seeConfig: "'${0}' ฤฑ config.yml de gรถr" + serverNotFound: "Veritabanฤฑnda '${0}' sunucusu bulunamadฤฑ." + tooManyArguments: "ยงcTek Argรผman gerekli ${1}" + unknownUsername: "ยงcKullanฤฑcฤฑ bu sunucuda hiรง gรถrรผlmemiลŸ" + webUserExists: "ยงcBรถyle bir kullanฤฑcฤฑ zaten var!" + webUserNotFound: "ยงcBรถyle bir kullanฤฑcฤฑ yok!" + footer: + help: "ยง7BaฤŸฤฑmsฤฑz deฤŸiลŸkenler komutunun รผzerine gelin veya '/${0}?' onlar hakkฤฑnda daha fazla bilgi edinmek iรงin." + general: + failNoExporter: 'ยงe"${0}" dฤฑลŸa aktarฤฑcฤฑsฤฑ mevcut deฤŸil' + failNoImporter: "ยงeAlฤฑcฤฑ '${0}' yok" + featureDisabled: "ยงaKapatฤฑldฤฑ '${0}' Plugin yeniden baลŸlatฤฑlana kadar." + noAddress: 'ยงeKullanฤฑlabilir adres yok - yedek olarak localhost kullanฤฑlฤฑyor. "Alternative_IP" ayarlarฤฑnฤฑ yapฤฑn.' + noWebuser: "Belki Web kullanฤฑcฤฑsฤฑ deฤŸilsinizdir, Kayฤฑt olmak iรงin /plan register " + notifyWebUserRegister: "Yeni bir kullanฤฑcฤฑ kayฤฑt oldu: '${0}' Yetki seviyesi: ${1}" + pluginDisabled: "ยงaPlan sistemi ลŸuanda kapandฤฑ. Plugini yeniden baลŸlatmak iรงin reload komutunu kullanabilirsin." + reloadComplete: "ยงaYeniden baลŸlatma tamamlandฤฑ" + reloadFailed: "ยงcPlugini yeniden baลŸlatฤฑrken bir ลŸeyler ters gitti,yeniden baลŸlatmanฤฑz tavsiye edilir." + successWebUserRegister: "ยงaYeni kullanฤฑcฤฑ (${0}) baลŸarฤฑyla eklendi!" + webPermissionLevels: ">\ยง70: Tรผm sayfalara eriลŸin \ ยง71: '/ oyuncular' ve tรผm oynatฤฑcฤฑ sayfalarฤฑna eriลŸin \ ยง72: Web kullanฤฑcฤฑsฤฑ ile aynฤฑ kullanฤฑcฤฑ adฤฑna sahip oynatฤฑcฤฑ sayfasฤฑna eriลŸin \ ยง73 +: ฤฐzin yok" + webUserList: " ยง2${0} ยง7: ยงf${1}" + header: + analysis: "> ยง2Analiz sonuรงlarฤฑ" + help: "> ยง2/${0} Help" + info: "> ยง2Oyuncu Analizi" + inspect: "> ยง2Oyuncu: ยงf${0}" + network: "> ยง2AฤŸ Sayfasฤฑ" + players: "> ยง2Oyuncular" + search: "> ยง2${0} ยงf${1} ยง2iรงin sonuรงlar:" + serverList: "id::isim::uuid::version" + servers: "> ยง2Sunucular" + webUserList: "kullanฤฑcฤฑ::baฤŸlandฤฑ::yetki seviyesi" + webUsers: "> ยง2${0} Web kullanฤฑcฤฑlarฤฑ" + help: + database: + description: "Plan veritabanฤฑnฤฑ yรถnet" + inDepth: "Verileri bir ลŸekilde deฤŸiลŸtirmek iรงin farklฤฑ veritabanฤฑ alt komutlarฤฑ kullanฤฑn" + dbBackup: + description: "Bir veritabanฤฑnฤฑn verilerini bir dosyaya yedekleyin" + inDepth: "Hedef veritabanฤฑnฤฑ bir dosyaya yedeklemek iรงin SQLite kullanฤฑr." + dbClear: + description: "Veritabanฤฑndan TรœM Plan verilerini kaldฤฑrฤฑn" + inDepth: "Sรผreรงteki tรผm Plan verilerini kaldฤฑrarak tรผm Plan tablolarฤฑnฤฑ temizler." + dbHotswap: + description: "Veritabฤฑnฤฑn hฤฑzlฤฑca deฤŸiลŸtirir" + inDepth: "Eklentiyi diฤŸer veritabanฤฑyla yeniden yรผkler ve eลŸleลŸecek ลŸekilde yapฤฑlandฤฑrmayฤฑ deฤŸiลŸtirir." + dbMove: + description: "Veriyi Veritabanlarฤฑ arasฤฑnda taลŸฤฑr" + inDepth: "DiฤŸer veritabanฤฑndaki iรงeriklerin รผzerine baลŸka bir veritabanฤฑndaki iรงeriklerin รผzerine yazar." + dbRemove: + description: "Oyuncunun verilerini Mevcut veritabanฤฑndan kaldฤฑr" + inDepth: "Bir oyuncuyu baฤŸlฤฑ tรผm verileri Geรงerli veritabanฤฑndan kaldฤฑrฤฑr." + dbRestore: + description: "Bir dosyadaki verileri bir veritabanฤฑna geri yรผkleyin" + inDepth: "SQLite yedekleme dosyasฤฑnฤฑ kullanฤฑr ve hedef veritabanฤฑnฤฑn iรงeriฤŸinin รผzerine yazar." + dbUninstalled: + description: "Veritabanฤฑnda bir sunucuyu kaldฤฑrฤฑlmฤฑลŸ olarak ayarlayฤฑn." + inDepth: "Plan veritabanฤฑndaki bir sunucuyu, sunucu sorgularฤฑnda gรถrรผnmemesi iรงin kaldฤฑrฤฑldฤฑ olarak iลŸaretler." + disable: + description: "Eklentiyi veya bir kฤฑsmฤฑnฤฑ devre dฤฑลŸฤฑ bฤฑrakฤฑn" + inDepth: "Bir sonraki yeniden yรผklemeye / yeniden baลŸlatmaya kadar eklentiyi veya bir kฤฑsmฤฑnฤฑ devre dฤฑลŸฤฑ bฤฑrakฤฑn." + export: + description: "Html veya json dosyalarฤฑnฤฑ manuel olarak dฤฑลŸa aktarฤฑn" + inDepth: "Yapฤฑlandฤฑrmada tanฤฑmlanan dฤฑลŸa aktarma iรงin bir dฤฑลŸa aktarma gerรงekleลŸtirir." + import: + description: "Verileri iรงe aktar" + inDepth: "Veritabanฤฑna veri yรผklemek iรงin bir iรงe aktarma gerรงekleลŸtirir." + info: + description: "Eklenti hakkฤฑnda bilgiler" + inDepth: "Eklentinin mevcut durumunu gรถrรผntรผleyin." + ingame: + description: "Oyuncu bilgilerini oyun iรงi gรถsterir" + inDepth: "Oyun iรงindeyken oyuncu hakkฤฑnda bilgi verir." + json: + description: "Player'ฤฑn ham verilerinin json'unu gรถrรผntรผleyin." + inDepth: "Bir oyuncunun verilerini json formatฤฑnda indirmenize izin verir. Hepsi." + logout: + description: "Paneldeki diฤŸer kullanฤฑcฤฑlarฤฑn oturumunu kapatฤฑn" + inDepth: "Panelden baลŸka bir kullanฤฑcฤฑnฤฑn oturumunu kapatmak iรงin kullanฤฑcฤฑ adฤฑ baฤŸฤฑmsฤฑz deฤŸiลŸkeni verin, herkesi kapatmak iรงin baฤŸฤฑmsฤฑz deฤŸiลŸken olarak * verin." + migrateToOnlineUuids: + description: "Migrate offline uuid data to online uuids" + network: + description: "AฤŸ sayfasฤฑnฤฑ gรถrรผntรผler" + inDepth: "/network sayfasฤฑna bir baฤŸlantฤฑ edinin, bunu yalnฤฑzca aฤŸlarda yapar." + player: + description: "Oyunucunun sayfasฤฑnฤฑ gรถsterir" + inDepth: "Belirli bir oyuncunun veya mevcut oyuncunun /player sayfasฤฑna bir baฤŸlantฤฑ edinin." + players: + description: "Oyuncu sayfasฤฑnฤฑ gรถrรผntรผler" + inDepth: "Oyuncularฤฑn bir listesini gรถrmek iรงin /players sayfasฤฑna bir baฤŸlantฤฑ edinin." + register: + description: "Web kullanฤฑcฤฑsna kayฤฑt yapar" + inDepth: "Kayฤฑt sayfasฤฑna baฤŸlantฤฑ almak iรงin baฤŸฤฑmsฤฑz deฤŸiลŸken olmadan kullanฤฑn. Kullanฤฑcฤฑ edinmek iรงin kayฤฑttan sonra --code [kod] kullanฤฑn." + reload: + description: "Plugini yeniden baลŸlatฤฑr" + inDepth: "Yapฤฑlandฤฑrmadaki deฤŸiลŸiklikleri yeniden yรผklemek iรงin eklentiyi devre dฤฑลŸฤฑ bฤฑrakฤฑn ve etkinleลŸtirin." + removejoinaddresses: + description: "Remove join addresses of a specified server" + search: + description: "Bir oyuncu adฤฑ arar" + inDepth: "Bir adฤฑn belirli bir kฤฑsmฤฑyla eลŸleลŸen tรผm oyuncu adlarฤฑnฤฑ listeleyin." + server: + description: "Sunucu Sayfasฤฑnฤฑ gรถsterir" + inDepth: "Belirli bir sunucunun /server sayfasฤฑna veya herhangi bir baฤŸฤฑmsฤฑz deฤŸiลŸken verilmemiลŸse geรงerli sunucuya bir baฤŸlantฤฑ edinin." + servers: + description: "Sunucun tรผm veritabanฤฑnฤฑ listeler" + inDepth: "Veritabanฤฑndaki sunucularฤฑn kimliklerini, adlarฤฑnฤฑ ve kullanฤฑcฤฑlarฤฑnฤฑ listeleyin." + unregister: + description: "Plan web sitesinin bir kullanฤฑcฤฑsฤฑnฤฑn kaydฤฑnฤฑ iptal edin" + inDepth: "Oyuncu baฤŸlantฤฑlฤฑ kullanฤฑcฤฑnฤฑn kaydฤฑnฤฑ silmek iรงin baฤŸฤฑmsฤฑz deฤŸiลŸken olmadan veya baลŸka bir kullanฤฑcฤฑnฤฑn kaydฤฑnฤฑ silmek iรงin kullanฤฑcฤฑ adฤฑ baฤŸฤฑmsฤฑz deฤŸiลŸkeniyle kullanฤฑn." + users: + description: "Tรผm web kullanฤฑcฤฑlarฤฑnฤฑ listeleyin" + inDepth: "Web kullanฤฑcฤฑlarฤฑnฤฑ tablo olarak listeler." + ingame: + activePlaytime: " ยง2Aktif Oyun Sรผresi: ยงf${0}" + activityIndex: " ยง2Aktivite gรถstergesi: ยงf${0} | ${1}" + afkPlaytime: " ยง2AFK Saati: ยงf${0}" + deaths: " ยง2ร–lรผmler: ยงf${0}" + geolocation: " ยง2Bรถlgesinden giriลŸ yapฤฑldฤฑ: ยงf${0}" + lastSeen: " ยง2En son gรถrรผlme: ยงf${0}" + longestSession: " ยง2En uzun giriลŸ: ยงf${0}" + mobKills: " ยง2ร–ldรผrรผlen canlฤฑlar: ยงf${0}" + playerKills: " ยง2Oyuncu รถldรผrme: ยงf${0}" + playtime: " ยง2Oynama sรผresi: ยงf${0}" + registered: " ยง2Kayฤฑtlฤฑ: ยงf${0} " + timesKicked: " ยง2Atฤฑlma sayฤฑsฤฑ: ยงf${0}" + link: + clickMe: "Tฤฑkla bana" + link: "Link" + network: "AฤŸ sayfasฤฑ: " + noNetwork: "Sunucu bir aฤŸa baฤŸlฤฑ deฤŸil. BaฤŸlantฤฑ, sunucu sayfasฤฑna yรถnlendirir." + player: "Oyuncu sayfasฤฑ: " + playerJson: "Oyuncu json: " + players: "Oyuncular sayfasฤฑ: " + register: "Kayฤฑt sayfasฤฑ: " + server: "Sunucu sayfasฤฑ: " + subcommand: + info: + database: " ยง2Mevcut veritabanฤฑ: ยงf${0}" + proxy: " ยง2Bungee ye baฤŸlan: ยงf${0}" + update: " ยง2Gรผncelleme mevcut: ยงf${0}" + version: " ยง2Versiyon: ยงf${0}" +generic: + noData: "Veri yok" +html: + button: + nightMode: "Gece modu" + calendar: + new: "Yeni:" + unique: "Benzersiz:" + description: + newPlayerRetention: "Bu deฤŸer, รถnceki oyunculara dayalฤฑ bir tahmindir." + noGameServers: "Bazฤฑ veriler, Plan'ฤฑn oyun sunucularฤฑna yรผklenmesini gerektirir." + noGeolocations: "Konum belirleme toplama yapฤฑlandฤฑrmada etkinleลŸtirilmelidir (GeoLite2 EULA'yฤฑ Kabul Et)." + noServerOnlinActivity: "ร‡evrimiรงi etkinliฤŸi gรถsterecek sunucu yok" + noServers: "Veritabanฤฑnda sunucu bulunamadฤฑ" + noServersLong: 'Plan'ฤฑn herhangi bir oyun sunucusuna yรผklenmediฤŸi veya aynฤฑ veritabanฤฑna baฤŸlฤฑ olmadฤฑฤŸฤฑ anlaลŸฤฑlฤฑyor. AฤŸ eฤŸitimi iรงin wiki 'ye bakฤฑn.' + noSpongeChunks: "Chunklar Spongeda mevcut deฤŸil" + predictedNewPlayerRetention: "Bu deฤŸer, รถnceki oyunculara dayalฤฑ bir tahmindir" + error: + 401Unauthorized: "Yetkisiz" + 403Forbidden: "YasaklanmฤฑลŸ." + 404NotFound: "Bulunamadฤฑ" + 404PageNotFound: "Bรถyle bir sayfa mevcut deฤŸil." + 404UnknownPage: "Bir komutla verilen baฤŸlantฤฑya eriลŸtiฤŸinizden emin olun, ร–rnekler:

/ player / {uuid / name}
/ server / {uuid / name / id}

" + UUIDNotFound: "Oyuncunun UUID si veritabanฤฑnda bulunamadฤฑ." + auth: + dbClosed: "Veritabanฤฑ aรงฤฑk deฤŸil, / plan bilgisi ile db durumunu kontrol edin" + emptyForm: "Kullanฤฑcฤฑ ve ลžifre belirtilmedi" + expiredCookie: "Kullanฤฑcฤฑ รงerezinin sรผresi doldu" + generic: "Kimlik doฤŸrulama hata nedeniyle baลŸarฤฑsฤฑz oldu" + loginFailed: "Kullanฤฑcฤฑ adฤฑ ve ลŸifre uyuลŸmuyor" + noCookie: "Kullanฤฑcฤฑ รงerezi mevcut deฤŸil" + registrationFailed: "Kayฤฑt baลŸarฤฑsฤฑz oldu, tekrar deneyin (Kodun sรผresi 15 dakika sonra dolar)" + userNotFound: "Bรถyle Bir Kullanฤฑcฤฑ Yok" + authFailed: "Kimlik doฤŸrulama baลŸarฤฑsฤฑz oldu." + authFailedTips: "- Bir kullanฤฑcฤฑyฤฑ /plan register
- ile kayฤฑt ettiฤŸinize emin olun ismin ve ลŸifrenin doฤŸru olup olmadฤฑฤŸฤฑnฤฑ kontol edin
- Kullanฤฑcฤฑ adฤฑ ve ลŸifre bรผyรผk / kรผรงรผk harf duyarlฤฑdฤฑr

EฤŸer ลŸifreni unuttuysan, Yetkiliden sizi tekrar kayฤฑt etmesini isteyin." + noServersOnline: "ฤฐsteฤŸi gerรงekleลŸtirecek รงevrimiรงi sunucu yok." + playerNotSeen: "Oyuncu bu sunucuda hiรง oynamadฤฑ." + serverNotSeen: "Server doesn't exist" + generic: + none: "Yok" + label: + active: "Aktivite" + activePlaytime: "Aktif Oyun Sรผresi" + activityIndex: "Aktivite gรถstergesi" + afk: "AFK" + afkTime: "AFK Sรผresi" + all: "Tamamฤฑ" + allTime: "Tรผm zamanlar" + alphabetical: "Alphabetical" + apply: "Apply" + asNumbers: "Sayฤฑlar olarak" + average: "Ortalama" + averageActivePlaytime: "Ortalama Aktif Oyun Sรผresi" + averageAfkTime: "Ortalama AFK Sรผresi" + averageChunks: "Ortalama Chunks" + averageCpuUsage: "Average CPU Usage" + averageEntities: "Ortalama Yaratฤฑklar" + averageKdr: "Ortalama KDR" + averageMobKdr: "Ortalama Mob KDR" + averagePing: "Ortalama Ping" + averagePlayers: "Average Players" + averagePlaytime: "Ortalama Oyun Sรผresi" + averageRamUsage: "Average RAM Usage" + averageServerDowntime: "Average Downtime / Server" + averageSessionLength: "Ortalama Oturum UzunluฤŸu" + averageSessions: "Ortalama Oturumlar" + averageTps: "Ortalama TPS" + averageTps7days: "Average TPS (7 days)" + banned: "YasaklanmฤฑลŸ" + bestPeak: "Tรผm Zamanlarฤฑn Zirvesi" + bestPing: "En iyi Ping" + calendar: " Takvim" + comparing7days: "7 gรผn karลŸฤฑlaลŸtฤฑrฤฑlฤฑyor" + connectionInfo: "BaฤŸlantฤฑ Bilgisi" + country: "รœlke" + cpu: "CPU" + cpuRam: "CPU & RAM" + cpuUsage: "CPU Usage" + currentPlayerbase: "ลžuanki Oyuncu Tabanฤฑ" + currentUptime: "Current Uptime" + dayByDay: "Gรผn gรผn" + dayOfweek: "Day of the Week" + deadliestWeapon: "En ร–lรผmcรผl PvP Silahฤฑ" + deaths: "ร–lรผmler" + disk: "Disk Alanฤฑ" + diskSpace: "BoลŸ Disk Alanฤฑ" + downtime: "Arฤฑza Sรผresi" + duringLowTps: "DรผลŸรผk TPS ArtฤฑลŸlarฤฑ Sฤฑrasฤฑnda:" + entities: "Varlฤฑklar" + favoriteServer: "Favori Sunucu" + firstSession: "ฤฐlk seans" + firstSessionLength: + average: "Average first session length" + median: "Median first session length" + geoProjection: + dropdown: "Select projection" + equalEarth: "Equal Earth" + mercator: "Mercator" + miller: "Miller" + ortographic: "Ortographic" + geolocations: "CoฤŸrafi Konumlar" + hourByHour: "Saat saat" + inactive: "Etkin deฤŸil" + indexInactive: "Etkisiz" + indexRegular: "Dรผzenli" + information: "DANIลžMA" + insights: "Insights" + insights30days: "30 gรผnlรผk bilgiler" + irregular: "Dรผzensiz" + joinAddress: "Join Address" + joinAddresses: "Adreslere Katฤฑl" + kdr: "KDR" + killed: "ร–ldรผrรผlen" + last24hours: "Son 24 saat" + last30days: "Son 30 gรผn" + last7days: "Son 7 gรผn" + lastConnected: "Son baฤŸlantฤฑ" + lastPeak: "Son Zirve" + lastSeen: "Son Gรถrรผlme" + latestJoinAddresses: "Latest Join Addresses" + length: " Uzunluk" + links: "BAฤžLANTILAR" + loadedChunks: "YรผklenmiลŸ Chunks lar" + loadedEntities: "YรผklenmiลŸ Varlฤฑklar" + loneJoins: "Lone joins" + loneNewbieJoins: "Yalnฤฑz acemi katฤฑlฤฑyor" + longestSession: "En Uzun Oturum" + lowTpsSpikes: "Low TPS Spikes" + lowTpsSpikes7days: "Low TPS Spikes (7 days)" + maxFreeDisk: "Maksimum BoลŸ Disk" + medianSessionLength: "Median Session Length" + minFreeDisk: "Minimum BoลŸ Disk" + mobDeaths: "Yaratฤฑk Yรผzรผnden รถlรผmler" + mobKdr: "Mob ฤฐstatistiฤŸi" + mobKills: "ร–ldรผrรผlen Mob" + mostActiveGamemode: "En Aktif Oyun Modu" + mostPlayedWorld: "En รงok oynanan Dรผnya" + name: "ฤฐsim" + network: "AฤŸ" + networkAsNumbers: "Sayฤฑlarla AฤŸ" + networkOnlineActivity: "AฤŸ ร‡evrimiรงi EtkinliฤŸi" + networkOverview: "AฤŸ Gรถrรผnรผmรผ" + networkPage: "AฤŸ sayfasฤฑ" + new: "Yeni" + newPlayerRetention: "Yeni Oyuncu Elde Tutma" + newPlayers: "Yeni Oyuncular" + newPlayers7days: "New Players (7 days)" + nickname: "Takma ad" + noDataToDisplay: "No Data to Display" + now: "ลžimdi" + onlineActivity: "ร‡evrimiรงi Aktivite" + onlineActivityAsNumbers: "Sayฤฑlarla ร‡evrimiรงi Etkinlik" + onlineOnFirstJoin: "ฤฐlk katฤฑlฤฑmda รงevrimiรงi oyuncular" + operator: "Yetkililer" + overview: "Genel BakฤฑลŸ" + perDay: "/ Gรผn" + perPlayer: "/ Player" + perRegularPlayer: "/ Regular Player" + performance: "Performans" + performanceAsNumbers: "Rakamlarla Performans" + ping: "Ping" + player: "Oyuncu" + playerDeaths: "Oyuncu รถlรผme sebep oldu" + playerKills: "Oyuncu ร–ldรผrรผldรผ" + playerList: "Oyuncu Listesi" + playerOverview: "Oyuncuya Genel BakฤฑลŸ" + playerPage: "Oyuncu Sayfasฤฑ" + playerRetention: "Player Retention" + playerbase: "Oyuncu tabanฤฑ" + playerbaseDevelopment: "Oyuncu Etkinlik GrafiฤŸi" + playerbaseOverview: "Playerbase Overview" + players: "Oyuncular" + playersOnline: "Oyuncu ร‡evrimiรงi" + playersOnlineNow: "Players Online (Now)" + playersOnlineOverview: "ร‡evrimiรงi EtkinliฤŸe Genel BakฤฑลŸ" + playtime: "Oyun Sรผresi" + plugins: "Pluginler" + pluginsOverview: "Plugins Overview" + punchcard: "Punchcard" + punchcard30days: "30 gรผnlรผk Punchcard" + pvpPve: "PvP & PvE" + pvpPveAsNumbers: "Sayฤฑlarla PvP & PvE" + query: "Sorgu yap" + quickView: "Hฤฑzlฤฑ Gรถrรผnรผm" + ram: "RAM" + ramUsage: "RAM Usage" + recentKills: "Son ร–ldรผrmeler" + recentPvpDeaths: "Son PvP ร–lรผmleri" + recentPvpKills: "Son PvP ร–ldรผrmeleri" + recentSessions: "En Son Oturumlar" + registered: "Kayฤฑtlฤฑ" + registeredPlayers: "Kayฤฑtlฤฑ Oyuncular" + regular: "Dรผzenli" + regularPlayers: "Normal Oyuncular" + relativeJoinActivity: "Gรถreli BirleลŸtirme EtkinliฤŸi" + secondDeadliestWeapon: "2. PvP Silahฤฑ" + seenNicknames: "Gรถrรผlen takma adlar" + server: "Sunucu" + serverAnalysis: "Sunucu analizi" + serverAsNumberse: "Server as Numbers" + serverCalendar: "Server Calendar" + serverDowntime: "Sunucu Kapalฤฑ Kalma Sรผresi" + serverOccupied: "Sunucu iลŸgal edildi" + serverOverview: "Sunucuya Genel BakฤฑลŸ" + serverPage: "Sunucu sayfasฤฑ" + serverPlaytime: "Sunucu Oynatma Sรผresi" + serverPlaytime30days: "30 gรผnlรผk Sunucu Oynatma Sรผresi" + serverSelector: "Server selector" + servers: "Sunucular" + serversTitle: "SUNUCULAR" + session: "Oturum" + sessionCalendar: "Session Calendar" + sessionEnded: " Oturum Sona Erdi" + sessionMedian: "Session Median" + sessionStart: "Oturum BaลŸladฤฑ" + sessions: "Oturumlar" + sortBy: "Sort By" + stacked: "Stacked" + themeSelect: "Tema Seรงimi" + thirdDeadliestWeapon: "3. PvP Silahฤฑ" + thirtyDays: "30 gรผn" + thirtyDaysAgo: "30 gรผn รถnce" + timesKicked: "Kere AtฤฑlmฤฑลŸ" + toMainPage: "Ana Sayfaya" + total: "Total" + totalActive: "Toplam Aktiflik" + totalAfk: "Toplam AFKlฤฑk" + totalPlayers: "Toplam Oyuncu" + totalPlayersOld: "Toplam Oyuncular" + totalPlaytime: "Toplam Oyun Sรผresi" + totalServerDowntime: "Total Server Downtime" + tps: "TPS" + trend: "Trend" + trends30days: "30 gรผnlรผk trendler" + uniquePlayers: "Sunucuya ฤฐlk Defa Girenler" + uniquePlayers7days: "Unique Players (7 days)" + veryActive: "ร‡ok Aktif" + weekComparison: "Hafta KarลŸฤฑlaลŸtฤฑrmasฤฑ" + weekdays: "'Pazartesi', 'Salฤฑ', 'ร‡arลŸamba', 'PerลŸembe', 'Cuma', 'Cumartesi', 'Pazar'" + world: "Dรผnya Yรผkle" + worldPlaytime: "Dรผnya Oyun Sรผresi" + worstPing: "En kรถtรผ Ping" + login: + failed: "GiriลŸ baลŸarฤฑsฤฑz:" + forgotPassword: "Parolanฤฑzฤฑ mฤฑ unuttunuz?" + forgotPassword1: "Parolanฤฑzฤฑ mฤฑ unuttunuz? Kaydฤฑ iptal edin ve tekrar kaydolun." + forgotPassword2: "Mevcut kullanฤฑcฤฑnฤฑzฤฑ kaldฤฑrmak iรงin oyunda aลŸaฤŸฤฑdaki komutu kullanฤฑn:" + forgotPassword3: "Veya konsol kullanarak:" + forgotPassword4: "Komutu kullandฤฑktan sonra, " + login: "Oturum aรง" + logout: "ร‡ฤฑkฤฑลŸ Yap" + password: "Parola" + register: "Hesap oluลŸtur!" + username: "Kullanฤฑcฤฑ adฤฑ" + modal: + info: + bugs: "Sorunlarฤฑ Bildir" + contributors: + bugreporters: "& Bug reporters!" + code: "kod katkฤฑda bulunan" + donate: "GeliลŸimi parasal olarak destekleyenlere ekstra รถzel teลŸekkรผrler." + text: 'Ayrฤฑca harika insanlarฤฑ takip ederek katkฤฑda bulundu:' + translator: "รงevirmen" + developer: "tarafฤฑndan geliลŸtirilmiลŸtir" + discord: "Discord'da Genel Destek" + license: "Player Analytics, altฤฑnda geliลŸtirilir ve lisanslanฤฑr" + metrics: "bStats Metrics" + text: "Eklenti hakkฤฑnda bilgiler" + wiki: "Plan Wiki, ร–ฤŸreticiler ve Belgeler" + version: + available: "mรผsait" + changelog: "DeฤŸiลŸim gรผnlรผฤŸรผnรผ incele" + dev: "Bu sรผrรผm bir DEV sรผrรผmรผdรผr." + download: "ฤฐndir" + text: "Yeni bir sรผrรผm yayฤฑnlandฤฑ ve ลŸimdi indirilebilir." + title: "Versiyon" + query: + filter: + activity: + text: "Etkinlik Gruplarฤฑnda" + banStatus: + name: "Yasaklama durumu" + banned: "Yasaklandฤฑ" + country: + text: "have joined from country" + generic: + allPlayers: "Tรผm oyuncular" + and: "ve" + start: "Oyuncularฤฑn" + joinAddress: + text: "adresle katฤฑldฤฑ" + nonOperators: "Operatรถr olmayanlar" + notBanned: "Yasaklฤฑ deฤŸil" + operatorStatus: + name: "Yetkili durumu" + operators: "Operators" + playedBetween: + text: "Arasฤฑnda oynanan" + pluginGroup: + name: "Grup: " + text: "${plugin} adlฤฑ kiลŸinin ${group} Grubunda" + registeredBetween: + text: "Arasฤฑnda kayฤฑtlฤฑ" + title: + activityGroup: "Mevcut aktivite grubu" + view: " Gรถrรผnรผm:" + filters: + add: "Filtre ekle" + loading: "Filtreler yรผkleniyor.." + generic: + are: "`vardฤฑr`" + label: + from: ">dan" + makeAnother: "BaลŸka bir sorgu yap" + to: ">a" + view: "Bir gรถrรผnรผm gรถster" + performQuery: "Sorgu GerรงekleลŸtirin!" + results: + match: "${resultCount} oyuncuyla eลŸleลŸti" + none: "Sorgu 0 sonuรง รผretti" + title: "Sorgu Sonuรงlarฤฑ" + title: + activity: "EลŸleลŸen oyuncularฤฑn etkinliฤŸi" + activityOnDate: ' tarihindeki etkinlik' + sessionsWithinView: "Gรถrรผnรผm iรงindeki oturumlar" + text: "Sorgu<" + register: + completion: "Kaydฤฑ Tamamla" + completion1: "Artฤฑk kullanฤฑcฤฑyฤฑ kaydetmeyi bitirebilirsiniz." + completion2: "Kod 15 dakika iรงinde sona eriyor" + completion3: "Kaydฤฑ bitirmek iรงin oyunda aลŸaฤŸฤฑdaki komutu kullanฤฑn:" + completion4: "Veya konsol kullanarak:" + createNewUser: "Yeni bir kullanฤฑcฤฑ oluลŸturun" + error: + checkFailed: "Kayฤฑt durumu kontrol edilemedi:" + failed: "Kayฤฑt baลŸarฤฑsฤฑz:" + noPassword: "Bir Parola belirlemeniz gerekiyor" + noUsername: "Kullanฤฑcฤฑ adฤฑ belirlemeniz gerekiyor" + usernameLength: "Kullanฤฑcฤฑ adฤฑ 50 karaktere kadar olabilir, sizinki" + login: "Zaten hesabฤฑnฤฑz var mฤฑ? Oturum aรง!" + passwordTip: "Parola 8 karakterden fazla olmalฤฑdฤฑr, ancak herhangi bir sฤฑnฤฑrlama yoktur." + register: "Kayฤฑt ol" + usernameTip: "Kullanฤฑcฤฑ adฤฑ 50 karaktere kadar olabilir." + text: + clickToExpand: "GeniลŸletmek iรงin tฤฑklayฤฑn" + comparing15days: "15 gรผn karลŸฤฑlaลŸtฤฑrฤฑlฤฑyor" + comparing30daysAgo: "30 gรผn รถncesiyle ลŸimdi karลŸฤฑlaลŸtฤฑrฤฑlฤฑyor." + noExtensionData: "Uzantฤฑ Verisi Yok" + noLowTps: "DรผลŸรผk tps artฤฑลŸlarฤฑ yok" + unit: + chunks: "Chunks" + players: "Oyuncular" + value: + localMachine: "Yerel makine" + noKills: "ร–ldรผrmesi yok" + offline: " ร‡evrimdฤฑลŸฤฑ" + online: " ร‡evrimiรงi" + with: "Birlikte" + version: + changelog: "DeฤŸiลŸim gรผnlรผฤŸรผnรผ incele" + current: "You have version ${0}" + download: "Download Plan-${0}.jar" + isDev: "Bu sรผrรผm bir DEV sรผrรผmรผdรผr." + updateButton: "Gรผncelleme" + updateModal: + text: "Yeni bir sรผrรผm yayฤฑnlandฤฑ ve artฤฑk indirilebilir." + title: "Version ${0} is Available!" +plugin: + apiCSSAdded: "SayfaUzantฤฑsฤฑ: ${0} stil sayfasฤฑ(larฤฑ) eklendi ${1}, ${2}" + apiJSAdded: "SayfaUzantฤฑsฤฑ: ${0} javascript(leri) eklendi${1}, ${2}" + disable: + database: "ร–nceden iลŸlenmemiลŸ kritik gรถrevler iลŸleniyor. (${0})" + disabled: "Oyuncu analizi kapatฤฑldฤฑ." + processingComplete: "ฤฐลŸlenme tamamlandฤฑ." + savingSessions: "BitmemiลŸ oturumlar kaydediliyor .." + savingSessionsTimeout: "Zaman aลŸฤฑmฤฑ isabeti, bunun yerine bir sonraki etkinleลŸtirmede bitmemiลŸ oturumlarฤฑ depolar." + waitingDb: "SQLite'ฤฑn JVM'nin รงรถkmesini รถnlemek iรงin sorgularฤฑn bitmesi bekleniyor .." + waitingDbComplete: "Kapalฤฑ SQLite baฤŸlantฤฑsฤฑ." + waitingTransactions: "Veri kaybฤฑnฤฑ รถnlemek iรงin tamamlanmamฤฑลŸ iลŸlemler bekleniyor .." + waitingTransactionsComplete: "ฤฐลŸlem kuyruฤŸu kapatฤฑldฤฑ." + webserver: "Websunucusu kapatฤฑldฤฑ." + enable: + database: "${0}- Veritabanฤฑ baฤŸlantฤฑsฤฑ kurulmuลŸ." + enabled: "Oyuncu analizi aktif edildi." + fail: + database: "${0}-Veritabanฤฑ baฤŸlantฤฑsฤฑ baลŸarฤฑsฤฑz: ${1}" + databasePatch: "VeriTabanฤฑ yamasฤฑ baลŸarฤฑsฤฑz, plugin devre dฤฑลŸฤฑ bฤฑrakฤฑlmฤฑลŸ olmalฤฑ. Lรผtfen bu sorunu bildirin." + databaseType: "${0} Desteklenmeyen bir veritabanฤฑ" + geoDBWrite: "ฤฐndirilen GeoLite2 Geolocation veritabanฤฑnฤฑ kaydederken bir ลŸeyler ters gitti." + webServer: "WebServer baลŸlatฤฑlmadฤฑ!" + notify: + badIP: "0.0.0.0 geรงerli bir adres deฤŸil, Alternatif_IP ayarlarฤฑnฤฑ yapฤฑn. YanlฤฑลŸ baฤŸlantฤฑlar verilebilir!" + emptyIP: "server.properties IP adresi kฤฑsmฤฑ boลŸ & AlternatifIP kullanฤฑlmฤฑyor. Bu yรผzden yanlฤฑลŸ linkler verilecektir!" + geoDisabled: "CoฤŸrafi konum toplama etkin deฤŸil. (Data.Geolocations: false)" + geoInternetRequired: "Plan GeoLite2 Geolocation veritabanฤฑnฤฑ indirmek iรงin ilk รงalฤฑลŸtฤฑrmada internet eriลŸimi gerektir." + storeSessions: "ร–nceki kapatmadan รถnce korunan oturumlarฤฑ saklama." + webserverDisabled: "WebServer baลŸlatฤฑlmadฤฑ. (WebServer.DisableWebServer: true)" + webserver: "Webserver Bu port รผzerinden รงalฤฑลŸฤฑyor ${0} ( ${1} )" + generic: + dbApplyingPatch: "Yama uygulanฤฑyor: ${0}.." + dbFaultyLaunchOptions: "BaลŸlatma seรงenekleri hatalฤฑ, varsayฤฑlan olarak kullanฤฑn (${0})" + dbNotifyClean: "${0} oyuncunun verileri kaldฤฑrฤฑldฤฑ." + dbNotifySQLiteWAL: "SQLite WAL modu bu sunucu versiyonunda desteklenmiyor, varsayฤฑlanฤฑ kullanฤฑn. Bu performansฤฑnฤฑzฤฑ etkiliye bilir veya etkilemezde." + dbPatchesAlreadyApplied: "Veritabanlarฤฑna yama zaten uygulanmฤฑลŸ." + dbPatchesApplied: "Yama tรผm veritabanlarฤฑna baลŸarฤฑyla uygulandฤฑ." + dbSchemaPatch: "Database: Making sure schema is up to date.." + loadedServerInfo: "Server identifier loaded: ${0}" + loadingServerInfo: "Loading server identifying information" + no: "Hayฤฑr" + today: "'Bugรผn'" + unavailable: "Kullanฤฑm dฤฑลŸฤฑ" + unknown: "Bilinmeyen" + yes: "Evet" + yesterday: "'Dรผn'" + version: + checkFail: "En yeni sรผrรผm numarasฤฑ kontrol edilemedi" + checkFailGithub: "Sรผrรผm bilgileri Github/versions.txt kฤฑsmฤฑndan indirilemedi" + isDev: " Bu bir GELฤฐลžTฤฐRฤฐCฤฐ sรผrรผmรผdรผr." + isLatest: "En son sรผrรผmรผ kullanฤฑyorsunuz." + updateAvailable: "Yeni sรผrรผm (${0}) mevcut ${1}" + updateAvailableSpigot: "Yeni sรผrรผm ${0}" + webserver: + fail: + SSLContext: "WebServer: SSL ฤฐรงeriฤŸi BaลŸlatma BaลŸarฤฑsฤฑz Oldu." + certFileEOF: "WebServer: EOF when reading Certificate file. (Dosyanฤฑn boลŸ olmadฤฑฤŸฤฑnฤฑ kontrol edin.)" + certStoreLoad: "WebServer: SSL Sertifikasฤฑ yรผklenirken sorun oluลŸtu." + portInUse: "Web Sunucusu baลŸarฤฑyla baลŸlatฤฑlmadฤฑ. Bu (${0}) port mu kullanฤฑlฤฑyor ?" + notify: + authDisabledConfig: "WebServer: Kullanฤฑcฤฑ Yetkilendirmesi Devre DฤฑลŸฤฑ! (Yapฤฑlandฤฑrmada devre dฤฑลŸฤฑ bฤฑrakฤฑldฤฑ)" + authDisabledNoHTTPS: "WebServer: Kullanฤฑcฤฑ Yetkisi Devre DฤฑลŸฤฑ! (HTTP Gรผvenli deฤŸil)" + certificateExpiresOn: "Webserver: Loaded certificate is valid until ${0}." + certificateExpiresPassed: "Webserver: Certificate has expired, consider renewing the certificate." + certificateExpiresSoon: "Webserver: Certificate expires in ${0}, consider renewing the certificate." + certificateNoSuchAlias: "Webserver: Certificate with alias '${0}' was not found inside the keystore file '${1}'." + http: "WebServer: Sertifika yok -> Gรถrรผntรผlemek iรงin HTTP-server kullanฤฑlฤฑyor." + ipWhitelist: "Webserver: IP Whitelist etkinleลŸtirildi.." + ipWhitelistBlock: "Webserver: ${0} was denied access to '${1}'. (not whitelisted)" + noCertFile: "WebServer: Setrifikasฤฑ Dosyasฤฑ Bulunamadฤฑ: ${0}" + reverseProxy: "WebServer: Proxy-mode HTTPS enabled, make sure that your reverse-proxy is routing using HTTPS and Plan Alternative_IP.Address points to the Proxy" diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_ZH_TW.txt b/Plan/common/src/main/resources/assets/plan/locale/locale_ZH_TW.txt deleted file mode 100644 index 3109e265c..000000000 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_ZH_TW.txt +++ /dev/null @@ -1,517 +0,0 @@ -API - css+ || ้ ้ขๆ“ดๅฑ•๏ผš ${0} ๆทปๅŠ ๆจฃๅผ่กจ(s) ๅˆฐ ${1}, ${2} -API - js+ || ้ ้ขๆ“ดๅฑ•๏ผš ${0} ๆทปๅŠ  javascript(s) ๅˆฐ ${1}, ${2} -Cmd - Click Me || ้ปžๆ“Šๆญค่™• -Cmd - Link || ้ˆๆŽฅ -Cmd - Link Network || ็พค็ต„็ถฒ็ตก้ ้ข: -Cmd - Link Player || ็Žฉๅฎถๅ€‹ไบบ้ ้ข: -Cmd - Link Player JSON || ็Žฉๅฎถ json: -Cmd - Link Players || ๅ…จ้ซ”็Žฉๅฎถ้ ้ข: -Cmd - Link Register || ๆณจๅ†Š้ ้ข page: -Cmd - Link Server || ๆœๅ‹™ๅ™จ้ ้ข page: -CMD Arg - backup-file || ๅ‚™ไปฝๆ–‡ไปถ็š„ๅ็จฑ๏ผˆๅ€ๅˆ†ๅคงๅฐๅฏซ๏ผ‰ -CMD Arg - code || ๆณจๅ†Š้œ€่ฆ็”จๅˆฐ็š„ไปฃ็ขผใ€‚ -CMD Arg - db type backup || ่ฆๅ‚™ไปฝ็š„ๆ•ธๆ“šๅบซ็š„้กžๅž‹ใ€‚ๅฆ‚ๆžœๆœชๆŒ‡ๅฎš๏ผŒๅ‰‡ไฝฟ็”จ็•ถๅ‰ๆ•ธๆ“šๅบซใ€‚ -CMD Arg - db type clear || ่ฆๆธ…็ฉบๆ•ธๆ“š็š„ๆ•ธๆ“šๅบซ้กžๅž‹ใ€‚ -CMD Arg - db type hotswap || ่ฆ้–‹ๅง‹ไฝฟ็”จ็š„ๆ–ฐๆ•ธๆ“šๅบซ้กžๅž‹ใ€‚ -CMD Arg - db type move from || ่ฆๅพž็งปๅ‡บๆ•ธๆ“š็š„ๆ•ธๆ“šๅบซ้กžๅž‹ใ€‚ -CMD Arg - db type move to || ่ฆๅฐ‡ๆ•ธๆ“š็งปๅ…ฅ็š„ๆ•ธๆ“šๅบซ้กžๅž‹ใ€‚ไธ่ƒฝๅ’Œไน‹ๅ‰ไธ€ๆจฃใ€‚ -CMD Arg - db type restore || ่ฆ้‚„ๅŽŸ็š„ๆ•ธๆ“šๅบซ็š„้กžๅž‹ใ€‚ๅฆ‚ๆžœๆœชๆŒ‡ๅฎš๏ผŒๅ‰‡ไฝฟ็”จ็•ถๅ‰ๆ•ธๆ“šๅบซใ€‚ -CMD Arg - feature || ่ฆ็ฆ็”จ็š„ๅŠŸ่ƒฝๅ็จฑ๏ผš${0} -CMD Arg - player identifier || ็Žฉๅฎถ็š„ๅ็จฑๆˆ– UUID -CMD Arg - player identifier remove || ่ฆๅพž็•ถๅ‰ๆ•ธๆ“šๅบซๅˆช้™ค็š„็Žฉๅฎถๆจ™่ญ˜็ฌฆ -CMD Arg - server identifier || ๆœๅ‹™ๅ™จ็š„ๅ็จฑ๏ผŒID ๆˆ– UUID -CMD Arg - subcommand || ไฝฟ็”จไธๅธถๅญๅ‘ฝไปค็š„ๅ‘ฝไปคๅณๅฏๆŸฅ็œ‹ๅนซๅŠฉใ€‚๏ผˆ็›ดๆŽฅ่ผธๅ…ฅ๏ผ‰ -CMD Arg - username || ๅฆไธ€ๅ€‹็”จๆˆถ็š„็”จๆˆถๅใ€‚ๅฆ‚ๆžœๆœชๆŒ‡ๅฎš๏ผŒๅ‰‡ไฝฟ็”จ็Žฉๅฎถ็ถๅฎš็š„็”จๆˆถใ€‚ -CMD Arg Name - backup-file || ๅ‚™ไปฝๆ–‡ไปถ -CMD Arg Name - code || ${code} -CMD Arg Name - export kind || ๅฐŽๅ‡บ้กžๅž‹ -CMD Arg Name - feature || ๅŠŸ่ƒฝ -CMD Arg Name - import kind || ๅฐŽๅ…ฅ้กžๅž‹ -CMD Arg Name - name or uuid || ๅ็จฑ/uuid -CMD Arg Name - server || ๆœๅ‹™ๅ™จ -CMD Arg Name - subcommand || ๅญๅ‘ฝไปค -CMD Arg Name - username || ็”จๆˆถๅ -Cmd Confirm - accept || ๆŽฅๅ— -Cmd Confirm - cancelled, no data change || ๅทฒๅ–ๆถˆใ€‚ๆฒ’ๆœ‰ๆ•ธๆ“š่ขซๆ›ดๆ”นใ€‚ -Cmd Confirm - cancelled, unregister || ๅทฒๅ–ๆถˆใ€‚ '${0}' ๅฐšๆœช่จปๅ†Š -Cmd Confirm - clearing db || ไฝ ๅฐ‡่ฆๅˆช้™ค ${0} ไธญ็š„ๆ‰€ๆœ‰ Plan ็š„ๆ•ธๆ“š -Cmd Confirm - confirmation || ็ขบ่ช: -Cmd Confirm - deny || ๅ–ๆถˆ -Cmd Confirm - Expired || ็ขบ่ชๅทฒ้ŽๆœŸ๏ผŒ่ซ‹ๅ†ๆฌกไฝฟ็”จๅ‘ฝไปค -Cmd Confirm - Fail on accept || ๆŽฅๅ—ๆ“ไฝœๅœจๅŸท่กŒๆ™‚ๅ‡บ้Œฏ๏ผš ${0} -Cmd Confirm - Fail on deny || ๆ‹’็ต•ๆ“ไฝœๅœจๅŸท่กŒๆ™‚ๅ‡บ้Œฏ๏ผš ${0} -Cmd Confirm - overwriting db || ไฝ ๅฐ‡่ฆ็”จ ${1} ไธญ็š„ๆ•ธๆ“š่ฆ†่“‹ Plan ${0} ไธญ็š„ๆ•ธๆ“šใ€‚ -Cmd Confirm - remove player db || ไฝ ๅฐ‡ๅพž ${1} ไธญๅˆช้™ค ${0} ็š„ๆ•ธๆ“šใ€‚ -Cmd Confirm - unregister || ๆ‚จๅณๅฐ‡่งฃ้™ค่ˆ‡ ${1} ้ˆๆŽฅ็š„ '${0}' ็š„ๆณจๅ†Šใ€‚ -Cmd db - creating backup || ๅ‰ตๅปบไธ€ๅ€‹ๅ‚™ไปฝๆ–‡ไปถ '${0}.db'๏ผŒๅ…งๅฎน็‚บ ${1}ใ€‚ -Cmd db - removal || ๅพž ${0} ไธญๅˆช้™ค Plan ็š„ๆ•ธๆ“š... -Cmd db - removal player || ๅพž ${1} ไธญๅˆช้™ค ${0} ็š„ๆ•ธๆ“š... -Cmd db - server uninstalled || ยงaๅฆ‚ๆžœๆœๅ‹™ๅ™จๆฒ’ๆœ‰็œŸ็š„ๅธ่ผ‰๏ผŒๅ‰‡ๅฎƒๅฐ‡่‡ชๅ‹•ๅœจๆ•ธๆ“šๅบซไธญๆŠŠ่‡ชๅทฑ่จญ็ฝฎ็‚บๅทฒๅฎ‰่ฃใ€‚ -Cmd db - write || ๆญฃๅœจๅฏซๅ…ฅ${0}... -Cmd Disable - Disabled || ยงa Plan ็ณป็ตฑ็พๅœจๅทฒ่ขซ็ฆ็”จใ€‚ไฝ ไป็„ถๅฏไปฅไฝฟ็”จ reload ไพ†้‡ๆ–ฐๅ•Ÿๅ‹•ๆ’ไปถใ€‚ -Cmd FAIL - Accepts only these arguments || ๆŽฅๅ—ไปฅไธ‹ๅ…งๅฎน ${0}: ${1} -Cmd FAIL - Database not open || ยงcๆ•ธๆ“šๅบซ็‚บ ${0} - ่ซ‹็จๅพŒๅ†่ฉฆใ€‚ -Cmd FAIL - Empty search string || ๆœ็ดขๅญ—็ฌฆไธฒไธ่ƒฝ็‚บ็ฉบ -Cmd FAIL - Invalid Username || ยงc่ฉฒ็”จๆˆถๆฒ’ๆœ‰ UUIDใ€‚ -Cmd FAIL - No Feature || ยงe่ซ‹่จญ็ฝฎ่ฆ็ฆ็”จ็š„ๅŠŸ่ƒฝ๏ผ๏ผˆ็•ถๅ‰ๆ”ฏๆŒ ${0}๏ผ‰ -Cmd FAIL - No Permission || ยงcไฝ ๆฒ’ๆœ‰ๆ‰€้œ€็š„ๆฌŠ้™ใ€‚ -Cmd FAIL - No player || ๆ‰พไธๅˆฐ็Žฉๅฎถ '${0}'๏ผŒไป–ๅ€‘ๆฒ’ๆœ‰ UUIDใ€‚ -Cmd FAIL - No player register || ๅœจๆ•ธๆ“šๅบซไธญๆ‰พไธๅˆฐ็Žฉๅฎถ '${0}'ใ€‚ -Cmd FAIL - No server || ๅœจๆ•ธๆ“šๅบซไธญๆ‰พไธๅˆฐๆœๅ‹™ๅ™จ '${0}'ใ€‚ -Cmd FAIL - Require only one Argument || ยงc้œ€่ฆๅ–ฎๅ€‹ๅƒๆ•ธ ${1} -Cmd FAIL - Requires Arguments || ยงc้œ€่ฆๅƒๆ•ธ (${0}) ${1} -Cmd FAIL - see config || ๆŸฅ็œ‹้…็ฝฎๆ–‡ไปถไธญ็š„ '${0}' -Cmd FAIL - Unknown Username || ยงcๅœจๆญคๆœๅ‹™ๅ™จไธŠๆœชๆ‰พๅˆฐ่ฉฒ็”จๆˆถ -Cmd FAIL - Users not linked || ๆญค็”จๆˆถๆœช็ถๅฎšๅˆฐไฝ ็š„ๅธณๆˆถ๏ผŒไธ”ไฝ ็„กๆฌŠๅˆช้™คๅ…ถไป–็”จๆˆถ็š„ๅธณๆˆถใ€‚ -Cmd FAIL - WebUser does not exists || ยงc็”จๆˆถไธๅญ˜ๅœจ๏ผ -Cmd FAIL - WebUser exists || ยงc็”จๆˆถๅทฒๅญ˜ๅœจ๏ผ -Cmd Footer - Help || ยง7ๅฐ‡้ผ ๆจ™ๆ‡ธๅœๅœจๅƒๆ•ธๆˆ–ๅ‘ฝไปคไธŠไพ†ไบ†่งฃๆ›ดๅคšๆœ‰้—œๅฎƒๅ€‘็š„ไฟกๆฏ๏ผŒๆˆ–่€…ไฝฟ็”จ '/${0} ?'ใ€‚ -Cmd Header - Analysis || > ยง2ๅˆ†ๆž็ตๆžœ -Cmd Header - Help || > ยง2/${0} ๅนซๅŠฉ -Cmd Header - Info || > ยง2็Žฉๅฎถๅˆ†ๆž -Cmd Header - Inspect || > ยง2็Žฉๅฎถ: ยงf${0} -Cmd Header - Network || > ยง2็พค็ต„็ถฒ็ตก้ ้ข -Cmd Header - Players || > ยง2ๅ…จ้ซ”็Žฉๅฎถ -Cmd Header - Search || > ยง2${0} ๅฐๆ–ผ ยงf${1}ยง2 ็š„็ตๆžœ: -Cmd Header - server list || id::ๅ็จฑ::uuid -Cmd Header - Servers || > ยง2ๅ…จ้ƒจๆœๅ‹™ๅ™จ -Cmd Header - web user list || ็”จๆˆถๅ::็ถๅฎšๅˆฐ::ๆฌŠ้™็ญ‰็ดš -Cmd Header - Web Users || > ยง2${0} ็ถฒ้ ็”จๆˆถ -Cmd Info - Bungee Connection || ยง2้€ฃๆŽฅ่‡ณไปฃ็†๏ผšยงf${0} -Cmd Info - Database || ยง2็•ถๅ‰ๆ•ธๆ“šๅบซ๏ผšยงf${0} -Cmd Info - Reload Complete || ยงa้‡่ผ‰ๅฎŒๆˆ -Cmd Info - Reload Failed || ยงc้‡ๆ–ฐๅŠ ่ผ‰ๆ’ไปถๅ‡บไบ†้ปžๅ•้กŒ๏ผŒๅปบ่ญฐ้‡ๆ–ฐๅ•Ÿๅ‹•ใ€‚ -Cmd Info - Update || ยง2ๆœ‰ๅฏ็”จๆ›ดๆ–ฐ๏ผšยงf${0} -Cmd Info - Version || ยง2็‰ˆๆœฌ๏ผšยงf${0} -Cmd network - No network || ๆœๅ‹™ๅ™จๆœช้€ฃๆŽฅๅˆฐ็พค็ต„ใ€‚ๆญค้ˆๆŽฅๅทฒ้‡ๅฎšๅ‘ๅˆฐๆœๅ‹™ๅ™จ้ ้ขใ€‚ -Cmd Notify - No Address || ยงeๆฒ’ๆœ‰ๅฏ็”จ็š„ๅœฐๅ€ - ๅทฒไฝฟ็”จ localhost ไฝœ็‚บๅพŒๅ‚™ๅœฐๅ€ใ€‚ๅœจ้…็ฝฎๆ–‡ไปถไธญ็š„ 'Alternative_IP' ่จญ็ฝฎๅœฐๅ€ใ€‚ -Cmd Notify - No WebUser || ไฝ ๅฏ่ƒฝๆฒ’ๆœ‰็ถฒ้ ่ณฌๆˆถ๏ผŒ่ซ‹ไฝฟ็”จ /plan register ไพ†ๆณจๅ†Š -Cmd Notify - WebUser register || ๆ–ฐ็”จๆˆถๅทฒๆณจๅ†Š๏ผš '${0}' ๆฌŠ้™็ญ‰็ดš๏ผš ${1} -Cmd Qinspect - Active Playtime || ยง2ๆดป่บๆ™‚้–“๏ผšยงf${0} -Cmd Qinspect - Activity Index || ยง2ๆดป่บๆŒ‡ๆ•ธ๏ผšยงf${0} | ${1} -Cmd Qinspect - AFK Playtime || ยง2ๆŽ›ๆฉŸๆ™‚้–“๏ผšยงf${0} -Cmd Qinspect - Deaths || ยง2ๆญปไบกๆ•ธ๏ผšยงf${0} -Cmd Qinspect - Geolocation || ยง2็™ป้Œ„ไฝ็ฝฎ๏ผšยงf${0} -Cmd Qinspect - Last Seen || ยง2ไธŠๆฌกๅœจ็ทš๏ผšยงf${0} -Cmd Qinspect - Longest Session || ยง2ๆœ€้•ท็š„ไธ€ๆฌก้Š็Žฉ๏ผšยงf${0} -Cmd Qinspect - Mob Kills || ยง2็”Ÿ็‰ฉๆ“Šๆฎบๆ•ธ๏ผšยงf${0} -Cmd Qinspect - Player Kills || ยง2็Žฉๅฎถๆ“Šๆฎบๆ•ธ๏ผšยงf${0} -Cmd Qinspect - Playtime || ยง2้Š็Žฉๆ™‚้–“๏ผšยงf${0} -Cmd Qinspect - Registered || ยง2ๆณจๅ†Šๆ™‚้–“๏ผšยงf${0} -Cmd Qinspect - Times Kicked || ยง2่ขซ่ธขๆฌกๆ•ธ๏ผšยงf${0} -Cmd SUCCESS - Feature disabled || ยงaๆšซๆ™‚็ฆ็”จ '${0}' ็›ดๅˆฐไธ‹ไธ€ๆฌก้‡่ผ‰ๆ’ไปถใ€‚ -Cmd SUCCESS - WebUser register || ยงaๆˆๅŠŸๆทปๅŠ ไบ†ๆ–ฐ็”จๆˆถ(${0})๏ผ -Cmd unregister - unregistering || ่จปๅ†Š '${0}' ไธญ... -Cmd WARN - Database not open || ยงeๆ•ธๆ“šๅบซ็‹€ๆ…‹็‚บ ${0} - ้€™ๅฏ่ƒฝ้œ€่ฆๆฏ”้ ๆœŸๆ›ด้•ท็š„ๆ™‚้–“... -Cmd Web - Permission Levels || >\ยง70: ่จชๅ•ๆ‰€ๆœ‰้ ้ข\ยง71: ่จชๅ• '/players' ๅ’Œๅ…จ้ซ”็Žฉๅฎถ้ ้ข\ยง72: ่จชๅ•็”จๆˆถๅ่ˆ‡็ถฒ้ ็”จๆˆถๅไธ€่‡ด็š„็Žฉๅฎถ้ \ยง73+: ๆฒ’ๆœ‰ๆฌŠ้™ -Command Help - /plan db || ็ฎก็† Plan ๆ•ธๆ“šๅบซ -Command Help - /plan db backup || ๅฐ‡ๆ•ธๆ“šๅบซ็š„ๆ•ธๆ“šๅ‚™ไปฝๅˆฐไธ€ๅ€‹ๆ–‡ไปถไธญ -Command Help - /plan db clear || ๅพžๆ•ธๆ“šๅบซไธญๅˆช้™คๆ‰€ๆœ‰ Plan ๆ•ธๆ“š -Command Help - /plan db hotswap || ็†ฑไบคๆ›ๆ•ธๆ“šๅบซไธฆ้‡ๅ•Ÿๆ’ไปถ -Command Help - /plan db move || ๅœจๆ•ธๆ“šๅบซ้–“็งปๅ‹•ๆ•ธๆ“š -Command Help - /plan db remove || ๅพž็•ถๅ‰ๆ•ธๆ“šๅบซไธญๅˆช้™ค็Žฉๅฎถ็š„ๆ•ธๆ“š -Command Help - /plan db restore || ๅฐ‡ๆ•ธๆ“šๅพžๆ–‡ไปถๆข่ฆ†ๅˆฐๆ•ธๆ“šๅบซ -Command Help - /plan db uninstalled || ๅœจๆ•ธๆ“šๅบซไธญๆŠŠไธ€ๅ€‹ๆœๅ‹™ๅ™จ่จญ็ฝฎ็‚บๅทฒๅธ่ผ‰ใ€‚ -Command Help - /plan disable || ็ฆ็”จๆ•ดๅ€‹ๆ’ไปถๆˆ–็ฆ็”จๆ’ไปถ็š„้ƒจๅˆ†ๅŠŸ่ƒฝ -Command Help - /plan export || ๆ‰‹ๅ‹•ๅฐŽๅ‡บ html ๆˆ– json ๆ–‡ไปถ -Command Help - /plan import || ๅฐŽๅ…ฅๆ•ธๆ“š -Command Help - /plan info || ้—œๆ–ผๆญคๆ’ไปถ็š„ไฟกๆฏ -Command Help - /plan ingame || ๅœจ้ŠๆˆฒไธญๆŸฅ็œ‹็Žฉๅฎถไฟกๆฏ -Command Help - /plan json || ๆŸฅ็œ‹็Žฉๅฎถ็š„ๅŽŸๅง‹ๆ•ธๆ“š jsonใ€‚ -Command Help - /plan logout || ๅฐ‡ๅ…ถไป–็”จๆˆถๅพž้ขๆฟไธŠ็™ปๅ‡บใ€‚ -Command Help - /plan network || ๆŸฅ็œ‹็พค็ต„็ถฒ็ตก้ ้ข -Command Help - /plan player || ๆŸฅ็œ‹็Žฉๅฎถ้ ้ข -Command Help - /plan players || ๆŸฅ็œ‹ๅ…จ้ซ”็Žฉๅฎถ้ ้ข -Command Help - /plan register || ๆณจๅ†Šไธ€ๅ€‹็ถฒ้ ็”จๆˆถ -Command Help - /plan reload || ้‡ๅ•Ÿ Plan -Command Help - /plan search || ๆœ็ดข็Žฉๅฎถ -Command Help - /plan server || ๆŸฅ็œ‹ๆœๅ‹™ๅ™จ้ ้ข -Command Help - /plan servers || ๅˆ—ๅ‡บๆ•ธๆ“šๅบซไธญ็š„ๆœๅ‹™ๅ™จ -Command Help - /plan unregister || ่จปๅ†Šไธ€ๅ€‹ Plan ็ถฒ้ ่ณฌๆˆถ -Command Help - /plan users || ๅˆ—ๅ‡บๆ‰€ๆœ‰็ถฒ้ ่ณฌๆˆถ -Database - Apply Patch || ๆญฃๅœจๆ‡‰็”จ่ฃœไธ๏ผš${0}... -Database - Patches Applied || ๅทฒๆˆๅŠŸๆ‡‰็”จๆ‰€ๆœ‰ๆ•ธๆ“šๅบซ่ฃœไธใ€‚ -Database - Patches Applied Already || ๅทฒๆ‡‰็”จๆ‰€ๆœ‰ๆ•ธๆ“šๅบซ่ฃœไธใ€‚ -Database MySQL - Launch Options Error || ๅ•Ÿๅ‹•ๅƒๆ•ธๅ‡บ้Œฏ๏ผŒๆญฃไฝฟ็”จ้ป˜่ชๅƒๆ•ธ๏ผˆ${0}๏ผ‰ -Database Notify - Clean || ็งป้™คไบ† ${0} ไฝ็”จๆˆถ็š„ๆ•ธๆ“šใ€‚ -Database Notify - SQLite No WAL || ๆญคๆœๅ‹™ๅ™จ็‰ˆๆœฌไธๆ”ฏๆŒ SQLite WAL ๆจกๅผ๏ผŒๆญฃไฝฟ็”จ้ป˜่ชๆจกๅผใ€‚้€™ๅฏ่ƒฝๆœƒๅฝฑ้Ÿฟๆ€ง่ƒฝใ€‚ -Disable || Plan ๆ’ไปถๅทฒ็ฆ็”จใ€‚ -Disable - Processing || ๆญฃๅœจ่™•็†ๆœช่™•็†็š„้—œ้ตไปปๅ‹™ใ€‚(${0}) -Disable - Processing Complete || ่™•็†ๅฎŒ็•ขใ€‚ -Disable - Unsaved Session Save || ไฟๅญ˜ๆœชๅฎŒๆˆ็š„ๆœƒ่ฉฑไธญ... -Disable - Unsaved Session Save Timeout || ่ถ…ๆ™‚๏ผŒๅฐ‡ๅœจไธ‹ไธ€ๆฌกๅ•Ÿๅ‹•ๅ„ฒๅญ˜ๆœชๅฎŒๆˆ็š„ๆœƒ่ฉฑใ€‚ -Disable - Waiting SQLite || ๆญฃๅœจ็ญ‰ๅพ…ๆŸฅ่ฉขๅฎŒๆˆ๏ผŒไปฅ้ฟๅ… SQLite ไฝฟ JVM ๅดฉๆฝฐ... -Disable - Waiting SQLite Complete || SQLite ้€ฃๆŽฅๅทฒ้—œ้–‰ใ€‚ -Disable - Waiting Transactions || ๆญฃๅœจ็ญ‰ๅพ…ๆœชๅฎŒๆˆ็š„ไบ‹ๅ‹™ไปฅ้ฟๅ…ๆ•ธๆ“šไธŸๅคฑ... -Disable - Waiting Transactions Complete || ไบ‹ๅ‹™้šŠๅˆ—ๅทฒ้—œ้–‰ใ€‚ -Disable - WebServer || ็ถฒ้ ๆœๅ‹™ๅ™จๅทฒ้—œ้–‰ใ€‚ -Enable || Plan ๆ’ไปถๅทฒๅ•Ÿ็”จใ€‚ -Enable - Database || ${0} - ๅทฒ้€ฃๆŽฅๅˆฐๆ•ธๆ“šๅบซใ€‚ -Enable - Notify Bad IP || 0.0.0.0 ไธๆ˜ฏๆœ‰ๆ•ˆ็š„ๅœฐๅ€๏ผŒ่ซ‹ไฟฎๆ”น Alternative_IP ่จญ็ฝฎ. ๅฆๅ‰‡ๅฏ่ƒฝๆœƒๅฐŽ่‡ด็ถฒ้ ๅœฐๅ€้Œฏ่ชค! -Enable - Notify Empty IP || server.properties ไธญ็š„ IP ็‚บ็ฉบไธ”ๆœชไฝฟ็”จๅ‚™็”จ IPใ€‚้€™ๅฏ่ƒฝๆœƒๅฐŽ่‡ดๅœฐๅ€ๅ‡บ้Œฏ๏ผ -Enable - Notify Geolocations disabled || ๅทฒ้—œ้–‰ๅœฐ็†ไฝ็ฝฎๆ”ถ้›†ใ€‚(Data.Geolocations: false) -Enable - Notify Geolocations Internet Required || Plan ้œ€่ฆๅœจ้ฆ–ๆฌก้‹่กŒๆ™‚่จชๅ•ไบ’่ฏ็ถฒไปฅไธ‹่ผ‰ GeoLite2 ๅœฐ็†ไฝ็ฝฎๆ•ธๆ“šๅบซใ€‚ -Enable - Notify Webserver disabled || ็ถฒ้ ๆœๅ‹™ๅ™จๆœชๅˆๅง‹ๅŒ–ใ€‚(WebServer.DisableWebServer: true) -Enable - Storing preserved sessions || ๆญฃๅœจๅ„ฒๅญ˜ไน‹ๅ‰้—œๆฉŸๅ‰็•™ไธ‹็š„ๆœƒ่ฉฑใ€‚ -Enable - WebServer || ็ถฒ้ ๆœๅ‹™ๅ™จๅทฒๅœจ ${0} ( ${1} ) ็ซฏๅฃไธŠ้‹่กŒ -Enable FAIL - Database || ${0} - ้€ฃๆŽฅๅˆฐๆ•ธๆ“šๅบซๅคฑๆ•—๏ผš${1} -Enable FAIL - Database Patch || ๆ•ธๆ“šๅบซ่ฃœไธๅคฑๆ•—๏ผŒๆ’ไปถๅฟ…้ ˆ่ขซ็ฆ็”จใ€‚่ซ‹ๅ ฑๅ‘Šๆญคๅ•้กŒ -Enable FAIL - GeoDB Write || ไฟๅญ˜ๅทฒไธ‹่ผ‰็š„ GeoLite2 ๅœฐ็†ไฝ็ฝฎๆ•ธๆ“šๅบซๆ™‚ๅ‘็”Ÿๅ•้กŒ -Enable FAIL - WebServer (Proxy) || ็ถฒ้ ๆœๅ‹™ๅ™จๆฒ’ๆœ‰ๅˆๅง‹ๅŒ–! -Enable FAIL - Wrong Database Type || ${0} ๆ˜ฏไธๆ”ฏๆŒ็š„ๆ•ธๆ“šๅบซ้กžๅž‹ -HTML - AND_BUG_REPORTERS || ๅ’Œๅ…ถไป–ๅ•้กŒๅ ฑๅ‘Š่€…๏ผ -HTML - BANNED (Filters) || ่ขซๅฐ็ฆ -HTML - COMPARING_15_DAYS || ๅฐๆฏ” 15 ๅคฉ็š„ๆƒ…ๆณ -HTML - COMPARING_60_DAYS || ๅฐๆฏ” 30 ๅคฉๅ‰ๅ’Œ็พๅœจ็š„ๆƒ…ๆณ -HTML - COMPARING_7_DAYS || ๅฐๆฏ” 7 ๅคฉ็š„ๆƒ…ๆณ -HTML - DATABASE_NOT_OPEN || ๆ•ธๆ“šๅบซๆœช้–‹ๆ”พ, ไฝฟ็”จ /plan info ๆŸฅ็œ‹ๆ•ธๆ“šๅบซ็‹€ๆ…‹ -HTML - DESCRIBE_RETENTION_PREDICTION || ้€™ๅ€‹ๆ•ธๅ€ผๆ˜ฏๅŸบๆ–ผไน‹ๅ‰็š„็Žฉๅฎถๆ•ธๆ“š้ ๆธฌ็š„ใ€‚ -HTML - ERROR || ่ช่ญ‰ๆ™‚ๅ‘็”Ÿ้Œฏ่ชค -HTML - EXPIRED_COOKIE || ็”จๆˆถ Cookie ๅทฒ้ŽๆœŸ -HTML - FILTER_ACTIVITY_INDEX_NOW || ๆดป่บๅบฆๅˆ†็ต„ -HTML - FILTER_ALL_PLAYERS || ๅ…จ้ซ”็Žฉๅฎถ -HTML - FILTER_BANNED || ๅฐ็ฆ็‹€ๆ…‹ -HTML - FILTER_GROUP || ๅฐ็ต„๏ผš -HTML - FILTER_OPS || ็ฎก็†ๅ“ก็‹€ๆ…‹ -HTML - INDEX_ACTIVE || ๆดป่บ -HTML - INDEX_INACTIVE || ไธๆดป่บ -HTML - INDEX_IRREGULAR || ๅถ็ˆพไธŠ็ทš -HTML - INDEX_REGULAR || ็ถ“ๅธธไธŠ็ทš -HTML - INDEX_VERY_ACTIVE || ้žๅธธๆดป่บ -HTML - KILLED || ่ขซๆ“Šๆฎบๆ•ธ -HTML - LABEL_1ST_WEAPON || ๆœ€่‡ดๅ‘ฝ็š„ PVP ๆญฆๅ™จ -HTML - LABEL_2ND_WEAPON || ็ฌฌไบŒ่‡ดๅ‘ฝ็š„ PVP ๆญฆๅ™จ -HTML - LABEL_3RD_WEAPON || ็ฌฌไธ‰่‡ดๅ‘ฝ็š„ PVP ๆญฆๅ™จ -HTML - LABEL_ACTIVE_PLAYTIME || ๆดป่บๆ™‚้–“ -HTML - LABEL_ACTIVITY_INDEX || ๆดป่บๆŒ‡ๆ•ธ -HTML - LABEL_AFK || ๆŽ›ๆฉŸ -HTML - LABEL_AFK_TIME || ๆŽ›ๆฉŸๆ™‚้–“ -HTML - LABEL_AVG || ๅนณๅ‡ -HTML - LABEL_AVG_ACTIVE_PLAYTIME || ๅนณๅ‡ๆดป่บๆ™‚้–“ -HTML - LABEL_AVG_AFK_TIME || ๅนณๅ‡ๆŽ›ๆฉŸๆ™‚้–“ -HTML - LABEL_AVG_CHUNKS || ๅนณๅ‡ๅ€ๅกŠๆ•ธ -HTML - LABEL_AVG_ENTITIES || ๅนณๅ‡ๅฏฆ้ซ”ๆ›ธ -HTML - LABEL_AVG_KDR || ๅนณๅ‡ KDR -HTML - LABEL_AVG_MOB_KDR || ๅนณๅ‡็”Ÿ็‰ฉ KDR -HTML - LABEL_AVG_PLAYTIME || ๅนณๅ‡้Š็Žฉๆ™‚้–“ -HTML - LABEL_AVG_SESSION_LENGTH || ๅนณๅ‡ๆœƒ่ฉฑๆ™‚้•ท -HTML - LABEL_AVG_SESSIONS || ๅนณๅ‡ๆœƒ่ฉฑ -HTML - LABEL_AVG_TPS || ๅนณๅ‡ TPS -HTML - LABEL_BANNED || ๅทฒ่ขซๅฐ็ฆ -HTML - LABEL_BEST_PEAK || ๆ‰€ๆœ‰ๆ™‚้–“ๅณฐๅ€ผ -HTML - LABEL_DAY_OF_WEEK || ๆ˜ŸๆœŸ -HTML - LABEL_DEATHS || ๆญปไบกๆ•ธ -HTML - LABEL_DOWNTIME || ๅœๆฉŸๆ™‚้–“ -HTML - LABEL_DURING_LOW_TPS || ๆŒ็บŒไฝŽ TPS ๆ™‚้–“ -HTML - LABEL_ENTITIES || ๅฏฆ้ซ” -HTML - LABEL_FAVORITE_SERVER || ๆœ€ๅ–œๆ„›็š„ๆœๅ‹™ๅ™จ -HTML - LABEL_FIRST_SESSION_LENGTH || ็ฌฌไธ€ๆฌกๆœƒ่ฉฑๆ™‚้•ท -HTML - LABEL_FREE_DISK_SPACE || ๅ‰ฉไฝ™็กฌ็›ค็ฉบ้–“ -HTML - LABEL_INACTIVE || ไธๆดป่บ -HTML - LABEL_LAST_PEAK || ไธŠๆฌกๅœจ็ทšๅณฐๅ€ผ -HTML - LABEL_LAST_SEEN || ๆœ€ๅพŒๅœจ็ทšๆ™‚้–“ -HTML - LABEL_LOADED_CHUNKS || ๅทฒๅŠ ่ผ‰ๅ€ๅกŠ -HTML - LABEL_LOADED_ENTITIES || ๅทฒๅŠ ่ผ‰ๅฏฆ้ซ” -HTML - LABEL_LONE_JOINS || ๅ–ฎ็จๅŠ ๅ…ฅ -HTML - LABEL_LONE_NEW_JOINS || ๅ–ฎ็จๆ–ฐ็ŽฉๅฎถๅŠ ๅ…ฅ -HTML - LABEL_LONGEST_SESSION || ๆœ€้•ทๆœƒ่ฉฑๆ™‚้–“ -HTML - LABEL_LOW_TPS || ไฝŽ TPS ๆ™‚้–“ -HTML - LABEL_MAX_FREE_DISK || ๆœ€ๅคงๅฏ็”จ็กฌ็›ค็ฉบ้–“ -HTML - LABEL_MIN_FREE_DISK || ๆœ€ๅฐๅฏ็”จ็กฌ็›ค็ฉบ้–“ -HTML - LABEL_MOB_DEATHS || ่ขซ็”Ÿ็‰ฉๆ“Šๆฎบๆ•ธ -HTML - LABEL_MOB_KDR || ็”Ÿ็‰ฉ KDR -HTML - LABEL_MOB_KILLS || ็”Ÿ็‰ฉๆ“Šๆฎบๆ•ธ -HTML - LABEL_MOST_ACTIVE_GAMEMODE || ๆœ€ๅธธ็Žฉ็š„้Šๆˆฒๆจกๅผ -HTML - LABEL_NAME || ๅ็จฑ -HTML - LABEL_NEW || ๆ–ฐ -HTML - LABEL_NEW_PLAYERS || ๆ–ฐ็Žฉๅฎถ -HTML - LABEL_NICKNAME || ๆ˜ต็จฑ -HTML - LABEL_NO_SESSION_KILLS || ็„ก -HTML - LABEL_ONLINE_FIRST_JOIN || ็ฌฌไธ€ๆฌก้€ฒๅ…ฅๆœๅ‹™ๅ™จ็š„ๅœจ็ทš็Žฉๅฎถ -HTML - LABEL_OPERATOR || ็ฎก็†ๅ“ก -HTML - LABEL_PER_PLAYER || / ็Žฉๅฎถ -HTML - LABEL_PER_REGULAR_PLAYER || / ๆ™ฎ้€š็Žฉๅฎถ -HTML - LABEL_PLAYER_DEATHS || ่ขซ็Žฉๅฎถๆ“Šๆฎบๆฌกๆ•ธ -HTML - LABEL_PLAYER_KILLS || ๆ“Šๆฎบ็Žฉๅฎถๆ•ธ -HTML - LABEL_PLAYERS_ONLINE || ๅœจ็ทš็Žฉๅฎถ -HTML - LABEL_PLAYTIME || ้Š็Žฉๆ™‚้–“ -HTML - LABEL_REGISTERED || ๆณจๅ†Šๆ™‚้–“ -HTML - LABEL_REGISTERED_PLAYERS || ๅทฒๆณจๅ†Š็š„็Žฉๅฎถ -HTML - LABEL_REGULAR || ๆ™ฎ้€š -HTML - LABEL_REGULAR_PLAYERS || ๆ™ฎ้€š็Žฉๅฎถ -HTML - LABEL_RELATIVE_JOIN_ACTIVITY || ๆœ€่ฟ‘ๅŠ ๅ…ฅๆดปๅ‹• -HTML - LABEL_RETENTION || ๆ–ฐ็Žฉๅฎถ็•™ๅ‘็Ž‡ -HTML - LABEL_SERVER_DOWNTIME || ๆœๅ‹™ๅ™จๅœๆฉŸๆ™‚้–“ -HTML - LABEL_SERVER_OCCUPIED || ๆœๅ‹™ๅ™จๅœจ็ทšๆ™‚้–“ -HTML - LABEL_SESSION_ENDED || ๆœƒ่ฉฑ็ตๆŸ -HTML - LABEL_SESSION_MEDIAN || ๅนณๅ‡ๆœƒ่ฉฑ้•ทๅบฆ -HTML - LABEL_TIMES_KICKED || ่ขซ่ธขๅ‡บๆฌกๆ•ธ -HTML - LABEL_TOTAL_PLAYERS || ็ธฝ็Žฉๅฎถๆ•ธ -HTML - LABEL_TOTAL_PLAYTIME || ็ธฝ้Š็Žฉๆ™‚้–“ -HTML - LABEL_UNIQUE_PLAYERS || ็จ็ซ‹็Žฉๅฎถ -HTML - LABEL_WEEK_DAYS || 'ๆ˜ŸๆœŸไธ€', 'ๆ˜ŸๆœŸไบŒ', 'ๆ˜ŸๆœŸไธ‰', 'ๆ˜ŸๆœŸๅ››', 'ๆ˜ŸๆœŸไบ”', 'ๆ˜ŸๆœŸๅ…ญ', 'ๆ˜ŸๆœŸๆ—ฅ' -HTML - LINK_BACK_NETWORK || ็พค็ต„็ถฒ็ตก้ ้ข -HTML - LINK_BACK_SERVER || ๆœๅ‹™ๅ™จ้ ้ข -HTML - LINK_CHANGELOG || ๆŸฅ็œ‹ๆ›ดๆ–ฐๆ—ฅๅฟ— -HTML - LINK_DISCORD || ไธ€่ˆฌๅ•้กŒๆ”ฏๆŒ๏ผšDiscord -HTML - LINK_DOWNLOAD || ไธ‹่ผ‰ -HTML - LINK_ISSUES || ๅ ฑๅ‘Šๅ•้กŒ -HTML - LINK_NIGHT_MODE || ๅคœ้–“ๆจกๅผ -HTML - LINK_PLAYER_PAGE || ็Žฉๅฎถ้ ้ข -HTML - LINK_QUICK_VIEW || ๅฟซ้€Ÿ็€่ฆฝ -HTML - LINK_SERVER_ANALYSIS || ๆœๅ‹™ๅ™จๅˆ†ๆž -HTML - LINK_WIKI || Plan Wiki,ๆ•™็จ‹ๅ’Œๆ–‡ๆช” -HTML - LOCAL_MACHINE || ๆœฌๅœฐไธปๆฉŸ -HTML - LOGIN_CREATE_ACCOUNT || ๅ‰ตๅปบไธ€ๅ€‹่ณฌๆˆถ๏ผ -HTML - LOGIN_FAILED || ็™ป้Œ„ๅคฑๆ•—๏ผš -HTML - LOGIN_FORGOT_PASSWORD || ๅฟ˜่จ˜ๅฏ†็ขผ๏ผŸ -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_1 || ๅฟ˜่จ˜ๅฏ†็ขผ๏ผŸ ่จปๅ†Šไธฆๅ†ๆฌกๆณจๅ†Šใ€‚ -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_2 || ๅœจ้Šๆˆฒไธญไฝฟ็”จไปฅไธ‹ๅ‘ฝไปคไพ†ๅˆช้™ค็•ถๅ‰่ณฌๆˆถ๏ผš -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_3 || ๆˆ–ไฝฟ็”จๆŽงๅˆถๅฐๅ‘ฝไปค๏ผš -HTML - LOGIN_FORGOT_PASSWORD_INSTRUCTIONS_4 || ไฝฟ็”จๅ‘ฝไปคๅพŒ๏ผŒ -HTML - LOGIN_LOGIN || ็™ป้Œ„ -HTML - LOGIN_LOGOUT || ็™ปๅ‡บ -HTML - LOGIN_PASSWORD || "ๅฏ†็ขผ" -HTML - LOGIN_USERNAME || "็”จๆˆถๅ" -HTML - NAV_PLUGINS || ๆ’ไปถ -HTML - NEW_CALENDAR || ๆ–ฐ๏ผš -HTML - NO_KILLS || ๆฒ’ๆœ‰ๆ“Šๆฎบๆ•ธ -HTML - NO_USER_PRESENT || ็”จๆˆถ cookie ไธๅญ˜ๅœจ -HTML - NON_OPERATORS (Filters) || ้ž็ฎก็†ๅ“ก -HTML - NOT_BANNED (Filters) || ๆœช่ขซๅฐ็ฆ -HTML - OFFLINE || ้›ข็ทš -HTML - ONLINE || ๅœจ็ทš -HTML - OPERATORS (Filters) || ็ฎก็†ๅ“ก -HTML - PER_DAY || / ๅคฉ -HTML - PLAYERS_TEXT || ็Žฉๅฎถ -HTML - QUERY || ๆŸฅ่ฉข< -HTML - QUERY_ACTIVITY_OF_MATCHED_PLAYERS || ๅŒน้…็Žฉๅฎถ็š„ๆดป่บๅบฆ -HTML - QUERY_ACTIVITY_ON || ๆดป่บๅœจ -HTML - QUERY_ADD_FILTER || ๆทปๅŠ ้Žๆฟพๅ™จ.. -HTML - QUERY_AND || ๅค–ๅŠ  -HTML - QUERY_ARE || `ๆ˜ฏ` -HTML - QUERY_ARE_ACTIVITY_GROUP || ๅœจๆดป่บๅบฆๅˆ†็ต„ไธญ -HTML - QUERY_ARE_PLUGIN_GROUP || ๅœจ ${plugin} ๆ’ไปถ็š„ ${group} ๅˆ†็ต„ไธญ -HTML - QUERY_JOINED_WITH_ADDRESS || ๅŠ ๅ…ฅๅœฐๅ€ -HTML - QUERY_LOADING_FILTERS || ๅŠ ่ผ‰้Žๆฟพๅ™จไธญ... -HTML - QUERY_MAKE || ้€ฒ่กŒๆŸฅ่ฉข -HTML - QUERY_MAKE_ANOTHER || ้€ฒ่กŒๅฆไธ€ๅ€‹ๆŸฅ่ฉข -HTML - QUERY_OF_PLAYERS || ๆŸฅ่ฉข็Žฉๅฎถ๏ผš -HTML - QUERY_PERFORM_QUERY || ๅŸท่กŒๆŸฅ่ฉข๏ผ -HTML - QUERY_PLAYED_BETWEEN || ๅœจๆญคๆœŸ้–“้Š็Žฉ้Ž -HTML - QUERY_REGISTERED_BETWEEN || ๅœจๆญคๆœŸ้–“ๆณจๅ†Š -HTML - QUERY_RESULTS || ๆŸฅ่ฉข็ตๆžœ -HTML - QUERY_RESULTS_MATCH || ๅŒน้…ๅˆฐ ${resultCount} ๅ€‹็Žฉๅฎถ -HTML - QUERY_SESSIONS_WITHIN_VIEW || ๆŸฅ็œ‹็ฏ„ๅœๅ…ง็š„ๆœƒ่ฉฑ -HTML - QUERY_SHOW_VIEW || ๆ—ฅๆœŸ็ฏ„ๅœ -HTML - QUERY_TIME_FROM || >ๅพž -HTML - QUERY_TIME_TO || >ๅˆฐ -HTML - QUERY_VIEW || ๆ—ฅๆœŸ็ฏ„ๅœ: -HTML - QUERY_ZERO_RESULTS || ๆŸฅ่ฉขๅˆฐ 0 ๅ€‹็ตๆžœ -HTML - REGISTER || ๆณจๅ†Š -HTML - REGISTER_CHECK_FAILED || ๆชขๆŸฅๆณจๅ†Š็‹€ๆ…‹ๅคฑๆ•—๏ผš -HTML - REGISTER_COMPLETE || ๆณจๅ†ŠๅฎŒๆˆ -HTML - REGISTER_COMPLETE_INSTRUCTIONS_1 || ๆ‚จ็พๅœจๅฏไปฅๅฎŒๆˆ็”จๆˆถๆณจๅ†Šๆต็จ‹ใ€‚ -HTML - REGISTER_COMPLETE_INSTRUCTIONS_2 || ่จปๅ†Šไปฃ็ขผๅฐ‡ๅœจ 15 ๅˆ†้˜ๅพŒ้ŽๆœŸ -HTML - REGISTER_COMPLETE_INSTRUCTIONS_3 || ๅœจ้Šๆˆฒไธญไฝฟ็”จไปฅไธ‹ๅ‘ฝไปคๅฎŒๆˆๆณจๅ†Š๏ผš -HTML - REGISTER_COMPLETE_INSTRUCTIONS_4 || ๆˆ–ไฝฟ็”จๆŽงๅˆถๅฐ๏ผš -HTML - REGISTER_CREATE_USER || ๅ‰ตๅปบไธ€ๅ€‹ๆ–ฐ็”จๆˆถ -HTML - REGISTER_FAILED || ๆณจๅ†Šๅคฑๆ•—๏ผš -HTML - REGISTER_HAVE_ACCOUNT || ๅทฒ็ถ“ๆœ‰ๅธณ่™Ÿไบ†๏ผŸ ็™ป้Œ„๏ผ -HTML - REGISTER_PASSWORD_TIP || ๅฏ†็ขผไธ่ƒฝ่ถ…้Ž8ๅ€‹ๅญ—็ฌฆ๏ผŒๆฒ’ๆœ‰ๅ…ถไป–้™ๅˆถใ€‚ -HTML - REGISTER_SPECIFY_PASSWORD || ไฝ ้œ€่ฆๅกซๅฏซๅฏ†็ขผ -HTML - REGISTER_SPECIFY_USERNAME || ไฝ ้œ€่ฆๅกซๅฏซ็”จๆˆถๅ -HTML - REGISTER_USERNAME_LENGTH || ็”จๆˆถๅๆœ€ๅคšๅฏไปฅๅŒ…ๅซ 50 ๅ€‹ๅญ—็ฌฆ๏ผŒไฝ ็š„็”จๆˆถๅๆœ‰ -HTML - REGISTER_USERNAME_TIP || ็”จๆˆถๅๆœ€ๅคšๅฏไปฅๅŒ…ๅซ 50 ๅ€‹ๅญ—็ฌฆใ€‚ -HTML - SESSION || ๆœƒ่ฉฑๆฌกๆ•ธ -HTML - SIDE_GEOLOCATIONS || ๅœฐ็†ไฝ็ฝฎ -HTML - SIDE_INFORMATION || ไฟกๆฏ -HTML - SIDE_LINKS || ้ˆๆŽฅ -HTML - SIDE_NETWORK_OVERVIEW || ็พค็ต„็ถฒ็ตก็ธฝ่ฆฝ -HTML - SIDE_OVERVIEW || ็ธฝ่ฆฝ -HTML - SIDE_PERFORMANCE || ๆ€ง่ƒฝ -HTML - SIDE_PLAYER_LIST || ็Žฉๅฎถๅˆ—่กจ -HTML - SIDE_PLAYERBASE || ็Žฉๅฎถๆ•ธๆ“š -HTML - SIDE_PLAYERBASE_OVERVIEW || ็Žฉๅฎถๆ•ธๆ“š็ธฝ่ฆฝ -HTML - SIDE_PLUGINS || ๆ’ไปถ -HTML - SIDE_PVP_PVE || PvP ๅ’Œ PvE -HTML - SIDE_SERVERS || ๆœๅ‹™ๅ™จ -HTML - SIDE_SERVERS_TITLE || ๆœๅ‹™ๅ™จ -HTML - SIDE_SESSIONS || ๆœƒ่ฉฑ -HTML - SIDE_TO_MAIN_PAGE || ๅ›žๅˆฐไธป้ ้ข -HTML - TEXT_CLICK_TO_EXPAND || ้ปžๆ“Šๅฑ•้–‹ -HTML - TEXT_CONTRIBUTORS_CODE || ไปฃ็ขผ่ฒข็ป่€… -HTML - TEXT_CONTRIBUTORS_LOCALE || ็ฟป่ญฏ่€… -HTML - TEXT_CONTRIBUTORS_MONEY || ็‰นๅˆฅๆ„Ÿ่ฌ้‚ฃไบ›ๅœจ็ถ“ๆฟŸไธŠๆ”ฏๆŒ้–‹ๅ‘็š„ไบบๅ€‘ใ€‚ -HTML - TEXT_CONTRIBUTORS_THANKS || ไปฅไธ‹ ๅ„ช็ง€ไบบ็‰ฉ ไนŸๅšๅ‡บไบ†่ฒข็ป๏ผš -HTML - TEXT_DEV_VERSION || ้€™ๆ˜ฏไธ€ๅ€‹้–‹ๅ‘็‰ˆๆœฌใ€‚ -HTML - TEXT_DEVELOPED_BY || ็š„้–‹ๅ‘่€…ๆ˜ฏ -HTML - TEXT_FIRST_SESSION || ็ฌฌไธ€ๆญคๆœƒ่ฉฑ -HTML - TEXT_LICENSED_UNDER || Player Analytics ้–‹ๅ‘ๅ’ŒๆŽˆๆฌŠๆ–ผ -HTML - TEXT_METRICS || bStats ็ตฑ่จˆ -HTML - TEXT_NO_EXTENSION_DATA || ๆฒ’ๆœ‰ๆ“ดๅฑ•ๆ•ธๆ“š -HTML - TEXT_NO_LOW_TPS || ๆฒ’ๆœ‰ไฝŽ TPS ๆ™‚้–“ -HTML - TEXT_NO_SERVER || ๆฒ’ๆœ‰ๅฏ้กฏ็คบๅœจ็ทšๆดปๅ‹•็š„ๆœๅ‹™ๅ™จ -HTML - TEXT_NO_SERVERS || ๆ•ธๆ“šๅบซไธญๆ‰พไธๅˆฐๆœๅ‹™ๅ™จ -HTML - TEXT_PLUGIN_INFORMATION || ๆ’ไปถไฟกๆฏ -HTML - TEXT_PREDICTED_RETENTION || ้€™ๅ€‹ๆ•ธๅ€ผๆ˜ฏๅŸบๆ–ผไน‹ๅ‰็š„็Žฉๅฎถๆ•ธๆ“š้ ๆธฌ็š„ -HTML - TEXT_SERVER_INSTRUCTIONS || ็œ‹่ตทไพ† Plan ๆฒ’ๆœ‰ๅฎ‰่ฃๅœจไปปไฝ•้Šๆˆฒๆœๅ‹™ๅ™จไธŠๆˆ–่€…้Šๆˆฒๆœๅ‹™ๅ™จๆœช้€ฃๆŽฅๅˆฐ็›ธๅŒ็š„ๆ•ธๆ“šๅบซใ€‚ ็พค็ต„็ถฒ็ตกๆ•™็จ‹่ซ‹ๅƒ่ฆ‹๏ผšwiki -HTML - TEXT_VERSION || ๆœ‰ๆ–ฐ็‰ˆๆœฌๅฏไพ›ไธ‹่ผ‰ใ€‚ -HTML - TITLE_30_DAYS || 30 ๅคฉ -HTML - TITLE_30_DAYS_AGO || 30 ๅคฉๅ‰ -HTML - TITLE_ALL || ๅ…จ้ƒจ -HTML - TITLE_ALL_TIME || ๆ‰€ๆœ‰ๆ™‚้–“ -HTML - TITLE_AS_NUMBERS || ๆ•ธๆ“š -HTML - TITLE_AVG_PING || ๅนณๅ‡ๅปถ้ฒ -HTML - TITLE_BEST_PING || ๆœ€ไฝŽๅปถ้ฒ -HTML - TITLE_CALENDAR || ๆ—ฅๆญท -HTML - TITLE_CONNECTION_INFO || ้€ฃๆŽฅไฟกๆฏ -HTML - TITLE_COUNTRY || ๅœ‹ๅฎถๅ’Œๅœฐๅ€ -HTML - TITLE_CPU_RAM || CPU ๅ’Œๅ…งๅญ˜ -HTML - TITLE_CURRENT_PLAYERBASE || ็•ถๅ‰็Žฉๅฎถๆ•ธ -HTML - TITLE_DISK || ็กฌ็›ค็ฉบ้–“ -HTML - TITLE_GRAPH_DAY_BY_DAY || ๆŒ‰ๅคฉๆŸฅ็œ‹ -HTML - TITLE_GRAPH_HOUR_BY_HOUR || ๆŒ‰ๅฐๆ™‚ๆŸฅ็œ‹ -HTML - TITLE_GRAPH_NETWORK_ONLINE_ACTIVITY || ็พค็ต„็ถฒ็ตกๅœจ็ทšๆดปๅ‹• -HTML - TITLE_GRAPH_PUNCHCARD || 30 ๅคฉๆ‰“ๅก -HTML - TITLE_INSIGHTS || 30 ๅคฉๅˆ†ๆž -HTML - TITLE_IS_AVAILABLE || ๅฏ็”จ -HTML - TITLE_JOIN_ADDRESSES || ๅŠ ๅ…ฅๅœฐๅ€ -HTML - TITLE_LAST_24_HOURS || ้ŽๅŽป 24 ๅฐๆ™‚ -HTML - TITLE_LAST_30_DAYS || ้ŽๅŽป 30 ๅคฉ -HTML - TITLE_LAST_7_DAYS || ้ŽๅŽป 7 ๅคฉ -HTML - TITLE_LAST_CONNECTED || ๆœ€ๅพŒ้€ฃๆŽฅๆ™‚้–“ -HTML - TITLE_LENGTH || ้Š็Žฉๆ™‚้•ท -HTML - TITLE_MOST_PLAYED_WORLD || ็Žฉ็š„ๆœ€ๅคš็š„ไธ–็•Œ -HTML - TITLE_NETWORK || ็พค็ต„็ถฒ็ตก -HTML - TITLE_NETWORK_AS_NUMBERS || ็พค็ต„็ถฒ็ตกๆ•ธๆ“š -HTML - TITLE_NOW || ็พๅœจ -HTML - TITLE_ONLINE_ACTIVITY || ๅœจ็ทšๆดปๅ‹• -HTML - TITLE_ONLINE_ACTIVITY_AS_NUMBERS || ๅœจ็ทšๆดปๅ‹•ๆ•ธๆ“š -HTML - TITLE_ONLINE_ACTIVITY_OVERVIEW || ๅœจ็ทšๆดปๅ‹•็ธฝ่ฆฝ -HTML - TITLE_PERFORMANCE_AS_NUMBERS || ๆ€ง่ƒฝๆ•ธๆ“š -HTML - TITLE_PING || ๅปถ้ฒ -HTML - TITLE_PLAYER || ็Žฉๅฎถ -HTML - TITLE_PLAYER_OVERVIEW || ็Žฉๅฎถ็ธฝ่ฆฝ -HTML - TITLE_PLAYERBASE_DEVELOPMENT || ็Žฉๅฎถๅ‘ๅฑ• -HTML - TITLE_PVP_DEATHS || ๆœ€่ฟ‘็š„ PVP ๆญปไบก -HTML - TITLE_PVP_KILLS || ๆœ€่ฟ‘็š„ PVP ๆ“Šๆฎบ -HTML - TITLE_PVP_PVE_NUMBERS || PvP ๅ’Œ PvE ๆ•ธๆ“š -HTML - TITLE_RECENT_KILLS || ๆœ€่ฟ‘ๆ“Šๆฎบ -HTML - TITLE_RECENT_SESSIONS || ๆœ€่ฟ‘ๆœƒ่ฉฑ -HTML - TITLE_SEEN_NICKNAMES || ็”จ้Ž็š„ๆ˜ต็จฑ -HTML - TITLE_SERVER || ๆœๅ‹™ๅ™จ -HTML - TITLE_SERVER_AS_NUMBERS || ๆœๅ‹™ๅ™จๆ•ธๆ“š -HTML - TITLE_SERVER_OVERVIEW || ๆœๅ‹™ๅ™จ็ธฝ่ฆฝ -HTML - TITLE_SERVER_PLAYTIME || ๆœๅ‹™ๅ™จ้Šๆˆฒๆ™‚้–“ -HTML - TITLE_SERVER_PLAYTIME_30 || ๆœ€่ฟ‘ 30 ๅคฉๅ…ง็š„ๆœๅ‹™ๅ™จ้Š็Žฉๆ™‚้–“ -HTML - TITLE_SESSION_START || ๆœƒ่ฉฑ้–‹ๅง‹ๆ–ผ -HTML - TITLE_THEME_SELECT || ไธป้กŒ้ธๆ“‡ -HTML - TITLE_TITLE_PLAYER_PUNCHCARD || ๆ‰“ๅก -HTML - TITLE_TPS || TPS -HTML - TITLE_TREND || ่ถจๅ‹ข -HTML - TITLE_TRENDS || 30 ๅคฉ่ถจๅ‹ข -HTML - TITLE_VERSION || ็‰ˆๆœฌ -HTML - TITLE_WEEK_COMPARISON || ๆฏๅ‘จๅฐๆฏ” -HTML - TITLE_WORLD || ไธ–็•ŒๅŠ ่ผ‰ -HTML - TITLE_WORLD_PLAYTIME || ไธ–็•Œ้Š็Žฉๆ™‚้–“ -HTML - TITLE_WORST_PING || ๆœ€้ซ˜ๅปถ้ฒ -HTML - TOTAL_ACTIVE_TEXT || ็ธฝๆดป่บๆ™‚้•ท -HTML - TOTAL_AFK || ็ธฝๆŽ›ๆฉŸๆ™‚้•ท -HTML - TOTAL_PLAYERS || ็ธฝ้Š็Žฉๆ™‚้•ท -HTML - UNIQUE_CALENDAR || ็จ็ซ‹๏ผš -HTML - UNIT_CHUNKS || ๅ€ๅกŠ -HTML - UNIT_ENTITIES || ๅฏฆ้ซ” -HTML - UNIT_NO_DATA || ๆฒ’ๆœ‰ๆ•ธๆ“š -HTML - UNIT_THE_PLAYERS || ็Žฉๅฎถ -HTML - USER_AND_PASS_NOT_SPECIFIED || ๆœชๆŒ‡ๅฎš็”จๆˆถๅ่ˆ‡ๅฏ†็ขผ -HTML - USER_DOES_NOT_EXIST || ็”จๆˆถไธๅญ˜ๅœจ -HTML - USER_INFORMATION_NOT_FOUND || ๆณจๅ†Šๅคฑๆ•—๏ผŒ่ซ‹้‡่ฉฆ๏ผˆๆณจๅ†Šไปฃ็ขผๆœ‰ๆ•ˆๆœŸ 15 ๅˆ†้˜๏ผ‰ -HTML - USER_PASS_MISMATCH || ็”จๆˆถๅๅ’Œๅฏ†็ขผไธๅŒน้… -HTML - Version Change log || ๆŸฅ็œ‹ๆ›ดๆ–ฐๆ—ฅๅฟ— -HTML - Version Current || ไฝ ็š„็‰ˆๆœฌๆ˜ฏ ${0} -HTML - Version Download || ไธ‹่ผ‰ Plan - ${0}.jar -HTML - Version Update || ๆ›ดๆ–ฐ -HTML - Version Update Available || ๆ–ฐ็‰ˆๆœฌ ${0} ็พๅœจๅฏ็”จ๏ผ -HTML - Version Update Dev || ้€™ๆ˜ฏไธ€ๅ€‹้–‹็™ผ็‰ˆๆœฌใ€‚ -HTML - Version Update Info || ๆœ‰ๆ–ฐ็‰ˆๆœฌๅฏไพ›ไธ‹่ผ‰ใ€‚ -HTML - WARNING_NO_GAME_SERVERS || ่ฆ็ฒๅ–ๆŸไบ›ๆ•ธๆ“š๏ผŒไฝ ้œ€่ฆๅฐ‡ Plan ๅฎ‰่ฃๅœจ้Šๆˆฒๆœๅ‹™ๅ™จไธŠใ€‚ -HTML - WARNING_NO_GEOLOCATIONS || ้œ€่ฆๅœจ้…็ฝฎๆ–‡ไปถไธญๅ•Ÿ็”จๅœฐ็†ไฝ็ฝฎๆ”ถ้›†(Accept GeoLite2 EULA)ใ€‚ -HTML - WARNING_NO_SPONGE_CHUNKS || ๅ€ๅกŠๆ•ธๆ“šๅœจ Sponge ๆœๅ‹™็ซฏไธๅฏ็”จ -HTML - WITH || ่ˆ‡ -HTML ERRORS - ACCESS_DENIED_403 || ๆ‹’็ต•่จชๅ• -HTML ERRORS - AUTH_FAIL_TIPS_401 || - ็ขบไฟไฝ ๅทฒไฝฟ็”จ /plan register ไพ†ๆณจๅ†Š็”จๆˆถ
- ๆชขๆŸฅ็”จๆˆถๅ่ˆ‡ๅฏ†็ขผๆ˜ฏๅฆๆญฃ็ขบ
- ็”จๆˆถๅ่ˆ‡ๅฏ†็ขผๅ€ๅˆ†ๅคงๅฐๅฏซ

่‹ฅๆ‚จๅฟ˜่จ˜ไบ†ๅฏ†็ขผ๏ผŒ่ซ‹่ฎ“ๅทฅไฝœไบบๅ“กๅˆช้™คๆ‚จ็š„่ˆŠๅฏ†็ขผไธฆ้‡ๆ–ฐๆณจๅ†Šใ€‚ -HTML ERRORS - AUTHENTICATION_FAILED_401 || ่ช่ญ‰ๅคฑๆ•—ใ€‚ -HTML ERRORS - FORBIDDEN_403 || ็ฆๆญข่จชๅ• -HTML ERRORS - NO_SERVERS_404 || ็„กๅฏๅŸท่กŒๆญค่ซ‹ๆฑ‚็š„ๅœจ็ทšๆœๅ‹™ๅ™จใ€‚ -HTML ERRORS - NOT_FOUND_404 || ๆœชๆ‰พๅˆฐ -HTML ERRORS - NOT_PLAYED_404 || Plan ๆฒ’ๆœ‰ๆ‰พๅˆฐๆญค็Žฉๅฎถใ€‚ -HTML ERRORS - PAGE_NOT_FOUND_404 || ้ ้ขไธๅญ˜ๅœจใ€‚ -HTML ERRORS - UNAUTHORIZED_401 || ๆœช่ช่ญ‰ -HTML ERRORS - UNKNOWN_PAGE_404 || ่ซ‹็ขบไฟๆ‚จๆญฃ้€š้Žๅ‘ฝไปคๆ‰€็ตฆๅ‡บ็š„้ˆๆŽฅ่จชๅ•๏ผŒ็คบไพ‹๏ผš

/player/็Žฉๅฎถๅ
/server/ๆœๅ‹™ๅ™จๅ

-HTML ERRORS - UUID_404 || ๆœชๅœจๆ•ธๆ“šๅบซไธญๆ‰พๅˆฐๆญค็Žฉๅฎถ็š„ UUIDใ€‚ -In Depth Help - /plan db || ไฝฟ็”จไธๅŒ็š„ๆ•ธๆ“šๅบซๅญๅ‘ฝไปคไพ†ๆŸ็จฎๆ–นๅผๆ›ดๆ”นๆ•ธๆ“š -In Depth Help - /plan db backup || ไฝฟ็”จ SQLite ๅฐ‡็›ฎๆจ™ๆ•ธๆ“šๅบซๅ‚™ไปฝๅˆฐๆ–‡ไปถไธญใ€‚ -In Depth Help - /plan db clear || ๆธ…้™คๆ‰€ๆœ‰ Plan ๆ•ธๆ“š่กจ๏ผŒไธฆๅˆช้™คๆ‰€ๆœ‰่™•็†ไธญ็š„ Plan ๆ•ธๆ“šใ€‚ -In Depth Help - /plan db hotswap || ็”จๅฆไธ€ๅ€‹ๆ•ธๆ“šๅบซ้‡ๆ–ฐๅŠ ่ผ‰ๆ’ไปถ๏ผŒไธฆๆ”น่ฎŠ้…็ฝฎไฝฟๅ…ถๅŒน้…ใ€‚ -In Depth Help - /plan db move || ็”จไธ€ๅ€‹ๆ•ธๆ“šๅบซไธญ็š„ๅ…งๅฎน่ฆ†่“‹ๅฆไธ€ๅ€‹ๆ•ธๆ“šๅบซไธญ็š„ๅ…งๅฎนใ€‚ -In Depth Help - /plan db remove || ๅพž็•ถๅ‰ๆ•ธๆ“šๅบซไธญๅˆช้™ค่ˆ‡ๆŸๅ€‹็Žฉๅฎถ็›ธ้—œ็š„ๆ‰€ๆœ‰ๆ•ธๆ“šใ€‚ -In Depth Help - /plan db restore || ไฝฟ็”จ SQLite ๅ‚™ไปฝๆ–‡ไปถไธฆ่ฆ†่“‹็›ฎๆจ™ๆ•ธๆ“šๅบซ็š„ๅ…งๅฎนใ€‚ -In Depth Help - /plan db uninstalled || ๅฐ‡ Plan ๆ•ธๆ“šๅบซไธญ็š„ไธ€ๅ€‹ๆœๅ‹™ๅ™จๆจ™่จ˜็‚บๅทฒๅธ่ผ‰๏ผŒ้€™ๆจฃๅฎƒๅฐฑไธๆœƒๅœจๆœๅ‹™ๅ™จๆŸฅ่ฉข้ ้ขไธญ้กฏ็คบๅ‡บไพ†ใ€‚ -In Depth Help - /plan disable || ็ฆ็”จๆ•ดๅ€‹ๆ’ไปถๆˆ–็ฆ็”จๆ’ไปถ็š„้ƒจๅˆ†ๅŠŸ่ƒฝ๏ผŒ็›ดๅˆฐไธ‹ๆฌก้‡ๆ–ฐๅŠ ่ผ‰/้‡ๆ–ฐๅ•Ÿๅ‹•ใ€‚ -In Depth Help - /plan export || ๆŠŠๆ•ธๆ“šๅฐŽๅ‡บๅˆฐ้…็ฝฎๆ–‡ไปถไธญๆŒ‡ๅฎš็š„ๅฐŽๅ‡บไฝ็ฝฎใ€‚ -In Depth Help - /plan import || ๅŸท่กŒๅฐŽๅ…ฅ๏ผŒๅฐ‡ๆ•ธๆ“šๅŠ ่ผ‰ๅˆฐๆ•ธๆ“šๅบซใ€‚ -In Depth Help - /plan info || ้กฏ็คบๆ’ไปถ็š„็•ถๅ‰็‹€ๆ…‹ใ€‚ -In Depth Help - /plan ingame || ้กฏ็คบๆญฃๅœจ้Šๆˆฒไธญ็š„็Žฉๅฎถ็š„ไธ€ไบ›ไฟกๆฏใ€‚ -In Depth Help - /plan json || ๅ…่จฑไฝ ไธ‹่ผ‰ json ๆ ผๅผ็š„็Žฉๅฎถๆ•ธๆ“šใ€‚ๆ‰€ๆœ‰็š„ๆ•ธๆ“š้ƒฝๅœจ้‡Œ้ขใ€‚ -In Depth Help - /plan logout || ่ผธๅ…ฅ็”จๆˆถๅไฝœ็‚บๅƒๆ•ธๅฏไปฅ่จปๅ†Š Plan ไธŠ็š„ไธ€ๅ€‹็”จๆˆถ๏ผŒ่ผธๅ…ฅ * ไฝœ็‚บๅƒๆ•ธๅฏไปฅ่จปๅ†Šๆ‰€ๆœ‰็”จๆˆถใ€‚ -In Depth Help - /plan network || ็ฒๅพ—ไธ€ๅ€‹ๆŒ‡ๅ‘ /network page๏ผˆ็พค็ต„็ถฒ็ตก๏ผ‰ ็š„้ˆๆŽฅ๏ผŒๅช่ƒฝๅœจ็พค็ต„็ถฒ็ตกไธŠ้€™ๆจฃๅšใ€‚ -In Depth Help - /plan player || ็ฒๅพ—ไธ€ๅ€‹ๆŒ‡ๅ‘็‰นๅฎš็Žฉๅฎถๆˆ–็•ถๅ‰็Žฉๅฎถ็š„ /player page๏ผˆ็Žฉๅฎถ้ ้ข๏ผ‰ ็š„้ˆๆŽฅใ€‚ -In Depth Help - /plan players || ็ฒๅพ—ไธ€ๅ€‹ๆŒ‡ๅ‘ /players page๏ผˆๅ…จ้ซ”็Žฉๅฎถ้ ้ข๏ผ‰ ็š„้ˆๆŽฅ๏ผŒไปฅๆŸฅ็œ‹็Žฉๅฎถๅˆ—่กจใ€‚ -In Depth Help - /plan register || ็›ดๆŽฅไฝฟ็”จๆœƒ็ฒๅพ—ๆณจๅ†Š้ ้ข็š„้ˆๆŽฅใ€‚ๆทปๅŠ  --code[ๆณจๅ†Šไปฃ็ขผ] ๅƒๆ•ธๅฏไปฅๆณจๅ†Šไธ€ๅ€‹่ณฌๆˆถใ€‚ -In Depth Help - /plan reload || ็ฆ็”จ็„ถๅพŒ้‡ๆ–ฐๅ•Ÿ็”จๆœฌๆ’ไปถ๏ผŒๆœƒ้‡ๆ–ฐๅŠ ่ผ‰้…็ฝฎไธญ็š„่จญ็ฝฎใ€‚ -In Depth Help - /plan search || ๅˆ—ๅ‡บๆ‰€ๆœ‰่ˆ‡็ตฆๅฎšๅๅญ—้ƒจๅˆ†็›ธๅŒน้…็š„็Žฉๅฎถๅๅญ—ใ€‚ -In Depth Help - /plan server || ็ฒๅ–ไธ€ๅ€‹ๆŒ‡ๅ‘็‰นๅฎšๆœๅ‹™ๅ™จ็š„ /server page๏ผˆๆœๅ‹™ๅ™จ้ ้ข๏ผ‰ ็š„้ˆๆŽฅ๏ผŒๅฆ‚ๆžœๆฒ’ๆœ‰็ตฆๅ‡บๅƒๆ•ธ๏ผŒๅ‰‡็ฒๅ–็•ถๅ‰ๆœๅ‹™ๅ™จ็š„้ˆๆŽฅใ€‚ -In Depth Help - /plan servers || ๅˆ—ๅ‡บๆ•ธๆ“šๅบซไธญๆ‰€ๆœ‰ๆœๅ‹™ๅ™จ็š„IDใ€ๅ็จฑๅ’ŒUUIDใ€‚ -In Depth Help - /plan unregister || ไธๅซๅƒๆ•ธไฝฟ็”จๆœƒ่จปๅ†Š็•ถๅ‰็ถๅฎš็š„่ณฌๆˆถ๏ผŒไฝฟ็”จ็”จๆˆถๅไฝœ็‚บๅƒๆ•ธ่ƒฝ่จปๅ†Šๅฆไธ€ๅ€‹็”จๆˆถใ€‚ -In Depth Help - /plan users || ไปฅ่กจๆ ผๅฝขๅผๅˆ—ๅ‡บ็ถฒ้ ็”จๆˆถใ€‚ -Manage - Confirm Overwrite || ๆ•ธๆ“šๅบซ ${0} ไธญ็š„ๆ•ธๆ“šๅฐ‡่ขซ่ฆ†่“‹! -Manage - Confirm Removal || ๆ•ธๆ“šๅบซ ${0} ไธญ็š„ๆ•ธๆ“šๅฐ‡่ขซๅˆช้™ค! -Manage - Fail || > ยงcๅ‘็”Ÿไบ†้Œฏ่ชค๏ผš${0} -Manage - Fail File not found || > ยงcๆฒ’ๆœ‰ๅœจ ${0} ๅ‘็พๆ–‡ไปถ -Manage - Fail Incorrect Database || > ยงc'${0}' ๆ˜ฏไธ€ๅ€‹ไธๆ”ฏๆŒ็š„ๆ•ธๆ“šๅบซ -Manage - Fail No Exporter || ยงeๅฐŽๅ‡บๅ™จ '${0}' ไธๅญ˜ๅœจ -Manage - Fail No Importer || ยงeๅฐŽๅ…ฅๅ™จ '${0}' ไธๅญ˜ๅœจ -Manage - Fail No Server || ๆฒ’ๆœ‰ๆ‰พๅˆฐๅ…ทๆœ‰็ตฆๅฎšๅƒๆ•ธ็š„ๆœๅ‹™ๅ™จใ€‚ -Manage - Fail Same Database || > ยงcไธ่ƒฝๅœจๅŒไธ€ๅ€‹ๆ•ธๆ“šๅบซไธญๆ“ไฝœ! -Manage - Fail Same server || ไธ่ƒฝๅฐ‡ๆญคๆœๅ‹™ๅ™จๆจ™่จ˜็‚บๅทฒๅธ่ผ‰๏ผˆไฝ ๅœจ้€™ๅ€‹ๆœๅ‹™ๅ™จไธŠ๏ผ‰ใ€‚ -Manage - Fail, Confirmation || > ยงcๆทปๅŠ  '-a' ๅƒๆ•ธไพ†็ขบ่ชๅŸท่กŒ๏ผš${0} -Manage - List Importers || ๅฐŽๅ…ฅๅ™จ๏ผš -Manage - Progress || ${0} / ${1} ่™•็†ไธญ... -Manage - Remind HotSwap || ยงe่ซ‹ๅˆ‡ๆ›ๅˆฐๆ–ฐ็š„ๆ•ธๆ“šๅบซ(/plan db hotswap ${0})ไธฆ้‡ๆ–ฐๅŠ ่ผ‰ๆ’ไปถใ€‚ -Manage - Start || > ยง2่™•็†ๆ•ธๆ“šไธญ... -Manage - Success || > ยงaๆˆๅŠŸ๏ผ -Negative || ๅฆ -Positive || ๆ˜ฏ -Today || 'ไปŠๅคฉ' -Unavailable || ไธๅฏ็”จ -Unknown || ไฝ็ฝฎ -Version - DEV || ้€™ๆ˜ฏไธ€ๅ€‹้–‹ๅ‘็‰ˆๆœฌใ€‚ -Version - Latest || ไฝ ๆญฃๅœจไฝฟ็”จๆœ€ๆ–ฐ็‰ˆๆœฌใ€‚ -Version - New || ๆœ‰ๆ–ฐ็‰ˆๆœฌ (${0}) ๅฏ็”จ ${1} -Version - New (old) || ๆœ‰ๆ–ฐ็‰ˆๆœฌๅฏ็”จ๏ผš${0} -Version FAIL - Read info (old) || ็„กๆณ•ๆชขๆŸฅๆœ€ๆ–ฐ็‰ˆๆœฌ่™Ÿ -Version FAIL - Read versions.txt || ็„กๆณ•ๅพž Github/versions.txt ๅŠ ่ผ‰็‰ˆๆœฌไฟกๆฏ -Web User Listing || ยง2${0} ยง7: ยงf${1} -WebServer - Notify HTTP || ็ถฒ้ ๆœๅ‹™ๅ™จ๏ผš็„ก่ญ‰ๆ›ธ -> ๆญฃไฝฟ็”จ HTTP ๆœๅ‹™ๅ™จๆไพ›ๅฏ่ฆ–ๅŒ–ๆ•ˆๆžœใ€‚ -WebServer - Notify HTTP User Auth || ็ถฒ้ ๆœๅ‹™ๅ™จ๏ผšๅทฒ็ฆ็”จ็”จๆˆถ็™ป้Œ„๏ผ๏ผˆHTTP ๆ–นๅผไธๅฎ‰ๅ…จ๏ผ‰ -WebServer - Notify HTTPS User Auth || ็ถฒ้ ๆœๅ‹™ๅ™จ: ็”จๆˆถ็™ป้Œ„ๅทฒ้—œ้–‰! ๏ผˆๅทฒๅœจ้…็ฝฎๆ–‡ไปถไธญ็ฆ็”จ๏ผ‰ -Webserver - Notify IP Whitelist || ็ถฒ้ ๆœๅ‹™ๅ™จ: IP ็™ฝๅๅ–ฎๅทฒๅ•Ÿ็”จใ€‚ -Webserver - Notify IP Whitelist Block || ็ถฒ้ ๆœๅ‹™ๅ™จ๏ผš${0} ่ขซๆ‹’็ต•่จชๅ• '${1}'. ๏ผˆไธๅœจ็™ฝๅๅ–ฎไธญ๏ผ‰ -WebServer - Notify no Cert file || ็ถฒ้ ๆœๅ‹™ๅ™จ๏ผšๆ‰พไธๅˆฐ่ญ‰ๆ›ธๅฏ†้‘ฐๅบซๆ–‡ไปถ๏ผš${0} -WebServer - Notify Using Proxy || ็ถฒ้ ๆœๅ‹™ๅ™จ: HTTPS ไปฃ็†ๆจกๅผๅทฒ้–‹ๅ•Ÿ, ่ซ‹็ขบไฟไฝ ็š„ๅๅ‘ไปฃ็†ๅทฒ็ถ“้…็ฝฎ็‚บ HTTPS ๆจกๅผไธฆไธ” Plan ็š„ Alternative_IP.Address ้ธ้ …ๅทฒ็ถ“ๆŒ‡ๅ‘ไปฃ็† -WebServer FAIL - EOF || ็ถฒ้ ๆœๅ‹™ๅ™จ: ๅœจ่ฎ€ๅ–่ญ‰ๆ›ธๆ–‡ไปถๆ™‚ๅ‡บ็พไบ†EOF็•ฐๅธธ. ๏ผˆ่ซ‹ๆชขๆŸฅ่ญ‰ๆ›ธๆ–‡ไปถๅฎŒๆ•ดๆ€ง๏ผ‰ -WebServer FAIL - Port Bind || ๆœชๆˆๅŠŸๅˆๅง‹ๅŒ–็ถฒ้ ๆœๅ‹™ๅ™จใ€‚็ซฏๅฃ(${0})ๆ˜ฏๅฆ่ขซๅทฒ่ขซๅ ็”จ๏ผŸ -WebServer FAIL - SSL Context || ็ถฒ้ ๆœๅ‹™ๅ™จ๏ผšSSL ็’ฐๅขƒๅˆๅง‹ๅŒ–ๅคฑๆ•—ใ€‚ -WebServer FAIL - Store Load || ็ถฒ้ ๆœๅ‹™ๅ™จ๏ผšSSL ่ญ‰ๆ›ธ่ผ‰ๅ…ฅๅคฑๆ•—ใ€‚ -Yesterday || 'ๆ˜จๅคฉ' \ No newline at end of file diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_ZH_TW.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_ZH_TW.yml new file mode 100644 index 000000000..30f2d9b2c --- /dev/null +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_ZH_TW.yml @@ -0,0 +1,669 @@ +403AccessDenied: "ๆ‹’็ต•่จชๅ•" +command: + argument: + backupFile: + description: "ๅ‚™ไปฝๆช”ๆกˆ็š„ๅ็จฑ๏ผˆๅ€ๅˆ†ๅคงๅฐๅฏซ๏ผ‰" + name: "ๅ‚™ไปฝๆช”ๆกˆ" + code: + description: "่จปๅ†Š้œ€่ฆ็”จๅˆฐ็š„ไปฃ็ขผใ€‚" + name: "${code}" + dbBackup: + description: "่ฆๅ‚™ไปฝ็š„่ณ‡ๆ–™ๅบซ็š„้กžๅž‹ใ€‚ๅฆ‚ๆžœๆœชๆŒ‡ๅฎš๏ผŒๅ‰‡ไฝฟ็”จ็›ฎๅ‰่ณ‡ๆ–™ๅบซใ€‚" + dbRestore: + description: "่ฆ้‚„ๅŽŸ็š„่ณ‡ๆ–™ๅบซ็š„้กžๅž‹ใ€‚ๅฆ‚ๆžœๆœชๆŒ‡ๅฎš๏ผŒๅ‰‡ไฝฟ็”จ็›ฎๅ‰่ณ‡ๆ–™ๅบซใ€‚" + dbTypeHotswap: + description: "่ฆ้–‹ๅง‹ไฝฟ็”จ็š„ๆ–ฐ่ณ‡ๆ–™ๅบซ้กžๅž‹ใ€‚" + dbTypeMoveFrom: + description: "่ฆๅพž็งปๅ‡บ่ณ‡ๆ–™็š„่ณ‡ๆ–™ๅบซ้กžๅž‹ใ€‚" + dbTypeMoveTo: + description: "่ฆๅฐ‡่ณ‡ๆ–™็งปๅ…ฅ็š„่ณ‡ๆ–™ๅบซ้กžๅž‹ใ€‚ไธ่ƒฝๅ’Œไน‹ๅ‰ไธ€ๆจฃใ€‚" + dbTypeRemove: + description: "่ฆๆธ…็ฉบ่ณ‡ๆ–™็š„่ณ‡ๆ–™ๅบซ้กžๅž‹ใ€‚" + exportKind: "ๅฐŽๅ‡บ้กžๅž‹" + feature: + description: "่ฆ้—œ้–‰็š„ๅŠŸ่ƒฝๅ็จฑ๏ผš${0}" + name: "ๅŠŸ่ƒฝ" + importKind: "ๅฐŽๅ…ฅ้กžๅž‹" + nameOrUUID: + description: "็Žฉๅฎถ็š„ๅ็จฑๆˆ– UUID" + name: "ๅ็จฑ/uuid" + removeDescription: "่ฆๅพž็›ฎๅ‰่ณ‡ๆ–™ๅบซๅˆช้™ค็š„็Žฉๅฎถๆจ™่ญ˜็ฌฆ" + server: + description: "ไผบๆœๅ™จ็š„ๅ็จฑ๏ผŒID ๆˆ– UUID" + name: "ไผบๆœๅ™จ" + subcommand: + description: "ไฝฟ็”จไธๅธถๅญๆŒ‡ไปค็š„ๆŒ‡ไปคๅณๅฏๆŸฅ็œ‹ๅนซๅŠฉใ€‚๏ผˆ็›ดๆŽฅ่ผธๅ…ฅ๏ผ‰" + name: "ๅญๆŒ‡ไปค" + username: + description: "ๅฆไธ€ๅ€‹ไฝฟ็”จ่€…็š„ไฝฟ็”จ่€…ๅ็จฑใ€‚ๅฆ‚ๆžœๆœชๆŒ‡ๅฎš๏ผŒๅ‰‡ไฝฟ็”จ็Žฉๅฎถ็ถๅฎš็š„ไฝฟ็”จ่€…ใ€‚" + name: "ไฝฟ็”จ่€…ๅ็จฑ" + confirmation: + accept: "ๆŽฅๅ—" + cancelNoChanges: "ๅทฒๅ–ๆถˆใ€‚ๆฒ’ๆœ‰่ณ‡ๆ–™่ขซๆ›ดๆ”นใ€‚" + cancelNoUnregister: "ๅทฒๅ–ๆถˆใ€‚ '${0}' ๅฐšๆœช่จปๅ†Š" + confirm: "็ขบ่ช:" + dbClear: "ไฝ ๅฐ‡่ฆๅˆช้™ค ${0} ไธญ็š„ๆ‰€ๆœ‰ Plan ็š„่ณ‡ๆ–™" + dbOverwrite: "ไฝ ๅฐ‡่ฆ็”จ ${1} ไธญ็š„่ณ‡ๆ–™่ฆ†่“‹ Plan ${0} ไธญ็š„่ณ‡ๆ–™ใ€‚" + dbRemovePlayer: "ไฝ ๅฐ‡ๅพž ${1} ไธญๅˆช้™ค ${0} ็š„่ณ‡ๆ–™ใ€‚" + deny: "ๅ–ๆถˆ" + expired: "็ขบ่ชๅทฒ้ŽๆœŸ๏ผŒ่ซ‹ๅ†ๆฌกไฝฟ็”จๆŒ‡ไปค" + unregister: "ๆ‚จๅณๅฐ‡่งฃ้™ค่ˆ‡ ${1} ้€ฃๆŽฅ็š„ '${0}' ็š„่จปๅ†Šใ€‚" + database: + creatingBackup: "ๅปบ็ซ‹ไธ€ๅ€‹ๅ‚™ไปฝๆช”ๆกˆ '${0}.db'๏ผŒๅ…งๅฎน็‚บ ${1}ใ€‚" + failDbNotOpen: "ยงc่ณ‡ๆ–™ๅบซ็‚บ ${0} - ่ซ‹็จๅพŒๅ†่ฉฆใ€‚" + manage: + confirm: "> ยงcๅขžๅŠ  '-a' ๅƒๆ•ธไพ†็ขบ่ชๅŸท่กŒ๏ผš${0}" + confirmOverwrite: "่ณ‡ๆ–™ๅบซ ${0} ไธญ็š„่ณ‡ๆ–™ๅฐ‡่ขซ่ฆ†่“‹!" + confirmPartialRemoval: "Join Address Data for Server ${0} in ${1} will be removed!" + confirmRemoval: "่ณ‡ๆ–™ๅบซ ${0} ไธญ็š„่ณ‡ๆ–™ๅฐ‡่ขซๅˆช้™ค!" + fail: "> ยงc็™ผ็”Ÿไบ†้Œฏ่ชค๏ผš${0}" + failFileNotFound: "> ยงcๆฒ’ๆœ‰ๅœจ ${0} ็™ผ็พๆช”ๆกˆ" + failIncorrectDB: "> ยงc'${0}' ๆ˜ฏไธ€ๅ€‹ไธๆ”ฏๆด็š„่ณ‡ๆ–™ๅบซ" + failNoServer: "ๆฒ’ๆœ‰ๆ‰พๅˆฐๅ…ทๆœ‰็ตฆๅฎšๅƒๆ•ธ็š„ไผบๆœๅ™จใ€‚" + failSameDB: "> ยงcไธ่ƒฝๅœจๅŒไธ€ๅ€‹่ณ‡ๆ–™ๅบซไธญๆ“ไฝœ!" + failSameServer: "ไธ่ƒฝๅฐ‡ๆญคไผบๆœๅ™จๆจ™่จ˜็‚บๅทฒ่งฃ้™คๅฎ‰่ฃ๏ผˆไฝ ๅœจ้€™ๅ€‹ไผบๆœๅ™จไธŠ๏ผ‰ใ€‚" + hotswap: "ยงe่ซ‹ๅˆ‡ๆ›ๅˆฐๆ–ฐ็š„่ณ‡ๆ–™ๅบซ(/plan db hotswap ${0})ไธฆ้‡ๆ–ฐ่ผ‰ๅ…ฅๆ’ไปถใ€‚" + importers: "ๅฐŽๅ…ฅๅ™จ๏ผš" + preparing: "Preparing.." + progress: "${0} / ${1} ่™•็†ไธญ..." + start: "> ยง2่ณ‡ๆ–™่™•็†ไธญ..." + success: "> ยงaๆˆๅŠŸ๏ผ" + playerRemoval: "ๅพž ${1} ไธญๅˆช้™ค ${0} ็š„่ณ‡ๆ–™..." + removal: "ๅพž ${0} ไธญๅˆช้™ค Plan ็š„่ณ‡ๆ–™..." + serverUninstalled: "ยงaๅฆ‚ๆžœไผบๆœๅ™จๆฒ’ๆœ‰็œŸ็š„่งฃ้™คๅฎ‰่ฃ๏ผŒๅ‰‡ๅฎƒๅฐ‡่‡ชๅ‹•ๅœจ่ณ‡ๆ–™ๅบซไธญๆŠŠ่‡ชๅทฑ่จญๅฎš็‚บๅทฒๅฎ‰่ฃใ€‚" + unregister: "่จปๅ†Š '${0}' ไธญ..." + warnDbNotOpen: "ยงe่ณ‡ๆ–™ๅบซ็‹€ๆ…‹็‚บ ${0} - ้€™ๅฏ่ƒฝ้œ€่ฆๆฏ”้ ๆœŸๆ›ด้•ท็š„ๆ™‚้–“..." + write: "ๆญฃๅœจๅฏซๅ…ฅ${0}..." + fail: + emptyString: "ๆœๅฐ‹ๅญ—ไธฒไธ่ƒฝ็‚บ็ฉบ" + invalidArguments: "ๆŽฅๅ—ไปฅไธ‹ๅ…งๅฎน ${0}: ${1}" + invalidUsername: "ยงc่ฉฒไฝฟ็”จ่€…ๆฒ’ๆœ‰ UUIDใ€‚" + missingArguments: "ยงc้œ€่ฆๅƒๆ•ธ (${0}) ${1}" + missingFeature: "ยงe่ซ‹่จญๅฎš่ฆๅœ็”จ็š„ๅŠŸ่ƒฝ๏ผ๏ผˆ็›ฎๅ‰ๆ”ฏๆด ${0}๏ผ‰" + missingLink: "ๆญคไฝฟ็”จ่€…ๆœช็ถๅฎšๅˆฐไฝ ็š„ๅธณๆˆถ๏ผŒไธ”ไฝ ็„กๆฌŠๅˆช้™คๅ…ถไป–ไฝฟ็”จ่€…็š„ๅธณๆˆถใ€‚" + noPermission: "ยงcไฝ ๆฒ’ๆœ‰ๆ‰€้œ€็š„ๆฌŠ้™ใ€‚" + onAccept: "ๆŽฅๅ—ๆ“ไฝœๅœจๅŸท่กŒๆ™‚ๅ‡บ้Œฏ๏ผš ${0}" + onDeny: "ๆ‹’็ต•ๆ“ไฝœๅœจๅŸท่กŒๆ™‚ๅ‡บ้Œฏ๏ผš ${0}" + playerNotFound: "ๆ‰พไธๅˆฐ็Žฉๅฎถ '${0}'๏ผŒไป–ๅ€‘ๆฒ’ๆœ‰ UUIDใ€‚" + playerNotInDatabase: "ๅœจ่ณ‡ๆ–™ๅบซไธญๆ‰พไธๅˆฐ็Žฉๅฎถ '${0}'ใ€‚" + seeConfig: "ๆŸฅ็œ‹่จญๅฎšๆช”ๆกˆไธญ็š„ '${0}'" + serverNotFound: "ๅœจ่ณ‡ๆ–™ๅบซไธญๆ‰พไธๅˆฐไผบๆœๅ™จ '${0}'ใ€‚" + tooManyArguments: "ยงc้œ€่ฆๅ–ฎๅ€‹ๅƒๆ•ธ ${1}" + unknownUsername: "ยงcๅœจๆญคไผบๆœๅ™จไธŠๆœชๆ‰พๅˆฐ่ฉฒไฝฟ็”จ่€…" + webUserExists: "ยงcไฝฟ็”จ่€…ๅทฒๅญ˜ๅœจ๏ผ" + webUserNotFound: "ยงcไฝฟ็”จ่€…ไธๅญ˜ๅœจ๏ผ" + footer: + help: "ยง7ๅฐ‡ๆป‘้ผ ๆธธๆจ™ๆ‡ธๅœๅœจๅƒๆ•ธๆˆ–ๆŒ‡ไปคไธŠไพ†ไบ†่งฃๆ›ดๅคšๆœ‰้—œๅฎƒๅ€‘็š„่จŠๆฏ๏ผŒๆˆ–่€…ไฝฟ็”จ '/${0} ?'ใ€‚" + general: + failNoExporter: "ยงeๅฐŽๅ‡บๅ™จ '${0}' ไธๅญ˜ๅœจ" + failNoImporter: "ยงeๅฐŽๅ…ฅๅ™จ '${0}' ไธๅญ˜ๅœจ" + featureDisabled: "ยงaๆšซๆ™‚ๅœ็”จ '${0}' ็›ดๅˆฐไธ‹ไธ€ๆฌก้‡่ผ‰ๆ’ไปถใ€‚" + noAddress: "ยงeๆฒ’ๆœ‰ๅฏ็”จ็š„ๅœฐๅ€ - ๅทฒไฝฟ็”จ localhost ไฝœ็‚บๅพŒๅ‚™ๅœฐๅ€ใ€‚ๅœจ่จญๅฎšๆช”ๆกˆไธญ็š„ 'Alternative_IP' ่จญๅฎšๅœฐๅ€ใ€‚" + noWebuser: "ไฝ ๅฏ่ƒฝๆฒ’ๆœ‰็ถฒ้ ๅธณๆˆถ๏ผŒ่ซ‹ไฝฟ็”จ /plan register ไพ†่จปๅ†Š" + notifyWebUserRegister: "ๆ–ฐไฝฟ็”จ่€…ๅทฒ่จปๅ†Š๏ผš '${0}' ๆฌŠ้™็ญ‰็ดš๏ผš ${1}" + pluginDisabled: "ยงa Plan ็ณป็ตฑ็พๅœจๅทฒ่ขซๅœ็”จใ€‚ไฝ ไป็„ถๅฏไปฅไฝฟ็”จ reload ไพ†้‡ๆ–ฐๅ•Ÿๅ‹•ๆ’ไปถใ€‚" + reloadComplete: "ยงa้‡่ผ‰ๅฎŒๆˆ" + reloadFailed: "ยงc้‡ๆ–ฐ่ผ‰ๅ…ฅๆ’ไปถๅ‡บไบ†้ปžๅ•้กŒ๏ผŒๅปบ่ญฐ้‡ๆ–ฐๅ•Ÿๅ‹•ใ€‚" + successWebUserRegister: "ยงaๆˆๅŠŸๆ–ฐๅขžไบ†ๆ–ฐไฝฟ็”จ่€…(${0})๏ผ" + webPermissionLevels: ">\ยง70: ่จชๅ•ๆ‰€ๆœ‰้ ้ข\ยง71: ่จชๅ• '/players' ๅ’Œๅ…จ้ƒจ็Žฉๅฎถ้ ้ข\ยง72: ่จชๅ•ไฝฟ็”จ่€…ๅ็จฑ่ˆ‡็ถฒ้ ไฝฟ็”จ่€…ๅ็จฑไธ€่‡ด็š„็Žฉๅฎถ้ \ยง73+: ๆฒ’ๆœ‰ๆฌŠ้™" + webUserList: " ยง2${0} ยง7: ยงf${1}" + header: + analysis: "> ยง2ๅˆ†ๆž็ตๆžœ" + help: "> ยง2/${0} ๅนซๅŠฉ" + info: "> ยง2็Žฉๅฎถๅˆ†ๆž" + inspect: "> ยง2็Žฉๅฎถ: ยงf${0}" + network: "> ยง2็พค็ต„็ถฒ่ทฏ้ ้ข" + players: "> ยง2ๅ…จ้ƒจ็Žฉๅฎถ" + search: "> ยง2${0} ๅฐๆ–ผ ยงf${1}ยง2 ็š„็ตๆžœ:" + serverList: "id::ๅ็จฑ::uuid::version" + servers: "> ยง2ๅ…จ้ƒจไผบๆœๅ™จ" + webUserList: "ไฝฟ็”จ่€…ๅ็จฑ::็ถๅฎšๅˆฐ::ๆฌŠ้™็ญ‰็ดš" + webUsers: "> ยง2${0} ็ถฒ้ ไฝฟ็”จ่€…" + help: + database: + description: "็ฎก็† Plan ่ณ‡ๆ–™ๅบซ" + inDepth: "ไฝฟ็”จไธๅŒ็š„่ณ‡ๆ–™ๅบซๅญๆŒ‡ไปคไพ†ๆŸ็จฎๆ–นๅผๆ›ดๆ”น่ณ‡ๆ–™" + dbBackup: + description: "ๅฐ‡่ณ‡ๆ–™ๅบซ็š„่ณ‡ๆ–™ๅ‚™ไปฝๅˆฐไธ€ๅ€‹ๆช”ๆกˆไธญ" + inDepth: "ไฝฟ็”จ SQLite ๅฐ‡็›ฎๆจ™่ณ‡ๆ–™ๅบซๅ‚™ไปฝๅˆฐๆช”ๆกˆไธญใ€‚" + dbClear: + description: "ๅพž่ณ‡ๆ–™ๅบซไธญๅˆช้™คๆ‰€ๆœ‰ Plan ่ณ‡ๆ–™" + inDepth: "ๆธ…้™คๆ‰€ๆœ‰ Plan ่ณ‡ๆ–™่กจ๏ผŒไธฆๅˆช้™คๆ‰€ๆœ‰่™•็†ไธญ็š„ Plan ่ณ‡ๆ–™ใ€‚" + dbHotswap: + description: "็†ฑไบคๆ›่ณ‡ๆ–™ๅบซไธฆ้‡ๅ•Ÿๆ’ไปถ" + inDepth: "็”จๅฆไธ€ๅ€‹่ณ‡ๆ–™ๅบซ้‡ๆ–ฐ่ผ‰ๅ…ฅๆ’ไปถ๏ผŒไธฆๆ”น่ฎŠ่จญๅฎšไฝฟๅ…ถ็ฌฆๅˆใ€‚" + dbMove: + description: "ๅœจ่ณ‡ๆ–™ๅบซ้–“็งปๅ‹•่ณ‡ๆ–™" + inDepth: "็”จไธ€ๅ€‹่ณ‡ๆ–™ๅบซไธญ็š„ๅ…งๅฎน่ฆ†่“‹ๅฆไธ€ๅ€‹่ณ‡ๆ–™ๅบซไธญ็š„ๅ…งๅฎนใ€‚" + dbRemove: + description: "ๅพž็›ฎๅ‰่ณ‡ๆ–™ๅบซไธญๅˆช้™ค็Žฉๅฎถ็š„่ณ‡ๆ–™" + inDepth: "ๅพž็›ฎๅ‰่ณ‡ๆ–™ๅบซไธญๅˆช้™ค่ˆ‡ๆŸๅ€‹็Žฉๅฎถ็›ธ้—œ็š„ๆ‰€ๆœ‰่ณ‡ๆ–™ใ€‚" + dbRestore: + description: "ๅฐ‡่ณ‡ๆ–™ๅพžๆช”ๆกˆ้‚„ๅŽŸ่‡ณ่ณ‡ๆ–™ๅบซ" + inDepth: "ไฝฟ็”จ SQLite ๅ‚™ไปฝๆช”ๆกˆไธฆ่ฆ†่“‹็›ฎๆจ™่ณ‡ๆ–™ๅบซ็š„ๅ…งๅฎนใ€‚" + dbUninstalled: + description: "ๅœจ่ณ‡ๆ–™ๅบซไธญๆŠŠไธ€ๅ€‹ไผบๆœๅ™จ่จญๅฎš็‚บๅทฒ่งฃ้™คๅฎ‰่ฃใ€‚" + inDepth: "ๅฐ‡ Plan ่ณ‡ๆ–™ๅบซไธญ็š„ไธ€ๅ€‹ไผบๆœๅ™จๆจ™่จ˜็‚บๅทฒ่งฃ้™คๅฎ‰่ฃ๏ผŒ้€™ๆจฃๅฎƒๅฐฑไธๆœƒๅœจไผบๆœๅ™จๆŸฅ่ฉข้ ้ขไธญ้กฏ็คบๅ‡บไพ†ใ€‚" + disable: + description: "ๅœ็”จๆ•ดๅ€‹ๆ’ไปถๆˆ–ๅœ็”จๆ’ไปถ็š„้ƒจๅˆ†ๅŠŸ่ƒฝ" + inDepth: "ๅœ็”จๆ•ดๅ€‹ๆ’ไปถๆˆ–ๅœ็”จๆ’ไปถ็š„้ƒจๅˆ†ๅŠŸ่ƒฝ๏ผŒ็›ดๅˆฐไธ‹ๆฌก้‡ๆ–ฐ่ผ‰ๅ…ฅ/้‡ๆ–ฐๅ•Ÿๅ‹•ใ€‚" + export: + description: "ๆ‰‹ๅ‹•ๅฐŽๅ‡บ html ๆˆ– json ๆช”ๆกˆ" + inDepth: "ๆŠŠ่ณ‡ๆ–™ๅฐŽๅ‡บๅˆฐ่จญๅฎšๆช”ๆกˆไธญๆŒ‡ๅฎš็š„ๅฐŽๅ‡บไฝ็ฝฎใ€‚" + import: + description: "ๅฐŽๅ…ฅ่ณ‡ๆ–™" + inDepth: "ๅŸท่กŒๅฐŽๅ…ฅ๏ผŒๅฐ‡่ณ‡ๆ–™่ผ‰ๅ…ฅๅˆฐ่ณ‡ๆ–™ๅบซใ€‚" + info: + description: "้—œๆ–ผๆญคๆ’ไปถ็š„่จŠๆฏ" + inDepth: "้กฏ็คบๆ’ไปถ็š„็›ฎๅ‰็‹€ๆ…‹ใ€‚" + ingame: + description: "ๅœจ้ŠๆˆฒไธญๆŸฅ็œ‹็Žฉๅฎถ่จŠๆฏ" + inDepth: "้กฏ็คบๆญฃๅœจ้Šๆˆฒไธญ็š„็Žฉๅฎถ็š„ไธ€ไบ›่จŠๆฏใ€‚" + json: + description: "ๆŸฅ็œ‹็Žฉๅฎถ็š„ๅŽŸๅง‹่ณ‡ๆ–™ jsonใ€‚" + inDepth: "ๅ…่จฑไฝ ไธ‹่ผ‰ json ๆ ผๅผ็š„็Žฉๅฎถ่ณ‡ๆ–™ใ€‚ๆ‰€ๆœ‰็š„่ณ‡ๆ–™้ƒฝๅœจ้‡Œ้ขใ€‚" + logout: + description: "ๅฐ‡ๅ…ถไป–ไฝฟ็”จ่€…ๅพž้ขๆฟไธŠ็™ปๅ‡บใ€‚" + inDepth: "่ผธๅ…ฅไฝฟ็”จ่€…ๅ็จฑไฝœ็‚บๅƒๆ•ธๅฏไปฅ่จปๅ†Š Plan ไธŠ็š„ไธ€ๅ€‹ไฝฟ็”จ่€…๏ผŒ่ผธๅ…ฅ * ไฝœ็‚บๅƒๆ•ธๅฏไปฅ่จปๅ†Šๆ‰€ๆœ‰ไฝฟ็”จ่€…ใ€‚" + migrateToOnlineUuids: + description: "Migrate offline uuid data to online uuids" + network: + description: "ๆŸฅ็œ‹็พค็ต„็ถฒ่ทฏ้ ้ข" + inDepth: "็ฒๅพ—ไธ€ๅ€‹ๆŒ‡ๅ‘ /network page๏ผˆ็พค็ต„็ถฒ่ทฏ๏ผ‰ ็š„้€ฃๆŽฅ๏ผŒๅช่ƒฝๅœจ็พค็ต„็ถฒ่ทฏไธŠ้€™ๆจฃๅšใ€‚" + player: + description: "ๆŸฅ็œ‹็Žฉๅฎถ้ ้ข" + inDepth: "็ฒๅพ—ไธ€ๅ€‹ๆŒ‡ๅ‘็‰นๅฎš็Žฉๅฎถๆˆ–็›ฎๅ‰็Žฉๅฎถ็š„ /player page๏ผˆ็Žฉๅฎถ้ ้ข๏ผ‰ ็š„้€ฃๆŽฅใ€‚" + players: + description: "ๆŸฅ็œ‹ๅ…จ้ƒจ็Žฉๅฎถ้ ้ข" + inDepth: "็ฒๅพ—ไธ€ๅ€‹ๆŒ‡ๅ‘ /players page๏ผˆๅ…จ้ƒจ็Žฉๅฎถ้ ้ข๏ผ‰ ็š„้€ฃๆŽฅ๏ผŒไปฅๆŸฅ็œ‹็Žฉๅฎถๅˆ—่กจใ€‚" + register: + description: "่จปๅ†Šไธ€ๅ€‹็ถฒ้ ไฝฟ็”จ่€…" + inDepth: "็›ดๆŽฅไฝฟ็”จๆœƒ็ฒๅพ—่จปๅ†Š้ ้ข็š„้€ฃๆŽฅใ€‚ๅขžๅŠ  --code[่จปๅ†Šไปฃ็ขผ] ๅƒๆ•ธๅฏไปฅ่จปๅ†Šไธ€ๅ€‹ๅธณๆˆถใ€‚" + reload: + description: "้‡ๅ•Ÿ Plan" + inDepth: "้—œ้–‰็„ถๅพŒ้‡ๆ–ฐ้–‹ๅ•Ÿๆœฌๆ’ไปถ๏ผŒๆœƒ้‡ๆ–ฐ่ผ‰ๅ…ฅๅทฒๆ›ดๆ”น็š„่จญๅฎšใ€‚" + removejoinaddresses: + description: "Remove join addresses of a specified server" + search: + description: "ๆœๅฐ‹็Žฉๅฎถ" + inDepth: "ๅˆ—ๅ‡บๆ‰€ๆœ‰่ˆ‡็ตฆๅฎšๅ็จฑ้ƒจๅˆ†็›ธ็ฌฆ็š„็Žฉๅฎถๅ็จฑใ€‚" + server: + description: "ๆŸฅ็œ‹ไผบๆœๅ™จ้ ้ข" + inDepth: "็ฒๅพ—ไธ€ๅ€‹ๆŒ‡ๅ‘็‰นๅฎšไผบๆœๅ™จ็š„ /server page๏ผˆไผบๆœๅ™จ้ ้ข๏ผ‰ ็š„้€ฃๆŽฅ๏ผŒๅฆ‚ๆžœๆฒ’ๆœ‰็ตฆๅ‡บๅƒๆ•ธ๏ผŒๅ‰‡็ฒๅพ—็›ฎๅ‰ไผบๆœๅ™จ็š„้€ฃๆŽฅใ€‚" + servers: + description: "ๅˆ—ๅ‡บ่ณ‡ๆ–™ๅบซไธญ็š„ไผบๆœๅ™จ" + inDepth: "ๅˆ—ๅ‡บ่ณ‡ๆ–™ๅบซไธญๆ‰€ๆœ‰ไผบๆœๅ™จ็š„IDใ€ๅ็จฑๅ’ŒUUIDใ€‚" + unregister: + description: "่จปๅ†Šไธ€ๅ€‹ Plan ็ถฒ้ ๅธณๆˆถ" + inDepth: "ไธๅซๅƒๆ•ธไฝฟ็”จๆœƒ่จปๅ†Š็›ฎๅ‰็ถๅฎš็š„ๅธณๆˆถ๏ผŒไฝฟ็”จไฝฟ็”จ่€…ๅ็จฑไฝœ็‚บๅƒๆ•ธ่ƒฝ่จปๅ†Šๅฆไธ€ๅ€‹ไฝฟ็”จ่€…ใ€‚" + users: + description: "ๅˆ—ๅ‡บๆ‰€ๆœ‰็ถฒ้ ๅธณๆˆถ" + inDepth: "ไปฅ่กจๆ ผๅฝขๅผๅˆ—ๅ‡บ็ถฒ้ ไฝฟ็”จ่€…ใ€‚" + ingame: + activePlaytime: " ยง2ๆดป่บๆ™‚้–“๏ผšยงf${0}" + activityIndex: " ยง2ๆดป่บๆŒ‡ๆ•ธ๏ผšยงf${0} | ${1}" + afkPlaytime: " ยง2ๆŽ›ๆฉŸๆ™‚้–“๏ผšยงf${0}" + deaths: " ยง2ๆญปไบกๆฌกๆ•ธ๏ผšยงf${0}" + geolocation: " ยง2็™ปๅ…ฅไฝ็ฝฎ๏ผšยงf${0}" + lastSeen: " ยง2ไธŠๆฌกไธŠ็ทš๏ผšยงf${0}" + longestSession: " ยง2้Š็Žฉๆœ€้•ท็š„ไธ€ๆฌก๏ผšยงf${0}" + mobKills: " ยง2็”Ÿ็‰ฉๆ“Šๆฎบๆ•ธ๏ผšยงf${0}" + playerKills: " ยง2็Žฉๅฎถๆ“Šๆฎบๆ•ธ๏ผšยงf${0}" + playtime: " ยง2้Š็Žฉๆ™‚้–“๏ผšยงf${0}" + registered: " ยง2่จปๅ†Šๆ™‚้–“๏ผšยงf${0}" + timesKicked: " ยง2่ธข้™คๆฌกๆ•ธ๏ผšยงf${0}" + link: + clickMe: "้ปžๆ“Šๆญค่™•" + link: "้€ฃ็ต" + network: "็พค็ต„็ถฒ่ทฏ้ ้ข:" + noNetwork: "ไผบๆœๅ™จๆœช้€ฃๆŽฅๅˆฐ็พค็ต„ใ€‚ๆญค้€ฃๆŽฅๅทฒ้‡ๅฎšๅ‘ๅˆฐไผบๆœๅ™จ้ ้ขใ€‚" + player: "็Žฉๅฎถๅ€‹ไบบ้ ้ข:" + playerJson: "็Žฉๅฎถ json:" + players: "ๅ…จ้ƒจ็Žฉๅฎถ้ ้ข:" + register: "่จปๅ†Š้ ้ข:" + server: "ไผบๆœๅ™จ้ ้ข:" + subcommand: + info: + database: " ยง2็›ฎๅ‰่ณ‡ๆ–™ๅบซ๏ผšยงf${0}" + proxy: " ยง2้€ฃๆŽฅ่‡ณไปฃ็†๏ผšยงf${0}" + update: " ยง2ๆœ‰ๅฏ็”จๆ›ดๆ–ฐ๏ผšยงf${0}" + version: " ยง2็‰ˆๆœฌ๏ผšยงf${0}" +generic: + noData: "ๆฒ’ๆœ‰่ณ‡ๆ–™" +html: + button: + nightMode: "ๅคœ้–“ๆจกๅผ" + calendar: + new: "ๆ–ฐ๏ผš" + unique: "็จ็ซ‹๏ผš" + description: + newPlayerRetention: "้€™ๅ€‹ๆ•ธๅ€ผๆ˜ฏๅŸบๆ–ผไน‹ๅ‰็š„็Žฉๅฎถ่ณ‡ๆ–™้ ๆธฌ็š„ใ€‚" + noGameServers: "่ฆ็ฒๅพ—ๆŸไบ›่ณ‡ๆ–™๏ผŒไฝ ้œ€่ฆๅฐ‡ Plan ๅฎ‰่ฃๅœจ้Šๆˆฒไผบๆœๅ™จไธŠใ€‚" + noGeolocations: "้œ€่ฆๅœจ่จญๅฎšๆช”ๆกˆไธญๅ•Ÿ็”จๅœฐ็†ไฝ็ฝฎๆ”ถ้›†(Accept GeoLite2 EULA)ใ€‚" + noServerOnlinActivity: "ๆฒ’ๆœ‰ๅฏ้กฏ็คบ็ทšไธŠๆดปๅ‹•็š„ไผบๆœๅ™จ" + noServers: "่ณ‡ๆ–™ๅบซไธญๆ‰พไธๅˆฐไผบๆœๅ™จ" + noServersLong: '็œ‹่ตทไพ† Plan ๆฒ’ๆœ‰ๅฎ‰่ฃๅœจไปปไฝ•้Šๆˆฒไผบๆœๅ™จไธŠๆˆ–่€…้Šๆˆฒไผบๆœๅ™จๆœช้€ฃๆŽฅๅˆฐ็›ธๅŒ็š„่ณ‡ๆ–™ๅบซใ€‚ ็พค็ต„็ถฒ่ทฏๆ•™็จ‹่ซ‹ๅƒ่ฆ‹๏ผšwiki' + noSpongeChunks: "ๅ€ๅกŠ่ณ‡ๆ–™ๅœจ Sponge ไผบๆœ็ซฏ็„กๆณ•ไฝฟ็”จ" + predictedNewPlayerRetention: "้€™ๅ€‹ๆ•ธๅ€ผๆ˜ฏๅŸบๆ–ผไน‹ๅ‰็š„็Žฉๅฎถ่ณ‡ๆ–™้ ๆธฌ็š„" + error: + 401Unauthorized: "ๆœช่ช่ญ‰" + 403Forbidden: "็ฆๆญข่จชๅ•" + 404NotFound: "ๆ‰พไธๅˆฐ" + 404PageNotFound: "้ ้ขไธๅญ˜ๅœจใ€‚" + 404UnknownPage: "่ซ‹็ขบไฟๆ‚จๆญฃ้€š้ŽๆŒ‡ไปคๆ‰€็ตฆๅ‡บ็š„้€ฃๆŽฅ่จชๅ•๏ผŒ็คบไพ‹๏ผš

/player/็Žฉๅฎถๅ
/server/ไผบๆœๅ™จๅ็จฑ

" + UUIDNotFound: "ๆœชๅœจ่ณ‡ๆ–™ๅบซไธญๆ‰พๅˆฐๆญค็Žฉๅฎถ็š„ UUIDใ€‚" + auth: + dbClosed: "่ณ‡ๆ–™ๅบซๆœช้–‹ๆ”พ, ไฝฟ็”จ /plan info ๆŸฅ็œ‹่ณ‡ๆ–™ๅบซ็‹€ๆ…‹" + emptyForm: "ๆœชๆŒ‡ๅฎšไฝฟ็”จ่€…ๅ็จฑ่ˆ‡ๅฏ†็ขผ" + expiredCookie: "ไฝฟ็”จ่€… Cookie ๅทฒ้ŽๆœŸ" + generic: "่ช่ญ‰ๆ™‚็™ผ็”Ÿ้Œฏ่ชค" + loginFailed: "ไฝฟ็”จ่€…ๅ็จฑๅ’Œๅฏ†็ขผ็„กๆณ•้…ๅˆ" + noCookie: "ไฝฟ็”จ่€… cookie ไธๅญ˜ๅœจ" + registrationFailed: "่จปๅ†Šๅคฑๆ•—๏ผŒ่ซ‹้‡่ฉฆ๏ผˆ่จปๅ†Šไปฃ็ขผๆœ‰ๆ•ˆๆœŸ 15 ๅˆ†้˜๏ผ‰" + userNotFound: "ไฝฟ็”จ่€…ไธๅญ˜ๅœจ" + authFailed: "่ช่ญ‰ๅคฑๆ•—ใ€‚" + authFailedTips: "- ็ขบไฟไฝ ๅทฒไฝฟ็”จ /plan register ไพ†่จปๅ†Šไฝฟ็”จ่€…
- ๆชขๆŸฅไฝฟ็”จ่€…ๅ็จฑ่ˆ‡ๅฏ†็ขผๆ˜ฏๅฆๆญฃ็ขบ
- ไฝฟ็”จ่€…ๅ็จฑ่ˆ‡ๅฏ†็ขผๅ€ๅˆ†ๅคงๅฐๅฏซ

่‹ฅๆ‚จๅฟ˜่จ˜ไบ†ๅฏ†็ขผ๏ผŒ่ซ‹่ฎ“ๅทฅไฝœไบบๅ“กๅˆช้™คๆ‚จ็š„่ˆŠๅฏ†็ขผไธฆ้‡ๆ–ฐ่จปๅ†Šใ€‚" + noServersOnline: "็„กๅฏๅŸท่กŒๆญค่ซ‹ๆฑ‚็š„็ทšไธŠไผบๆœๅ™จใ€‚" + playerNotSeen: "Plan ๆฒ’ๆœ‰ๆ‰พๅˆฐๆญค็Žฉๅฎถใ€‚" + serverNotSeen: "Server doesn't exist" + generic: + none: "็„ก" + label: + active: "ๆดป่บ" + activePlaytime: "ๆดป่บๆ™‚้–“" + activityIndex: "ๆดป่บๆŒ‡ๆ•ธ" + afk: "ๆŽ›ๆฉŸ" + afkTime: "ๆŽ›ๆฉŸๆ™‚้–“" + all: "ๅ…จ้ƒจ" + allTime: "ๆ‰€ๆœ‰ๆ™‚้–“" + alphabetical: "Alphabetical" + apply: "Apply" + asNumbers: "่ณ‡ๆ–™" + average: "ๅนณๅ‡" + averageActivePlaytime: "ๅนณๅ‡ๆดป่บๆ™‚้–“" + averageAfkTime: "ๅนณๅ‡ๆŽ›ๆฉŸๆ™‚้–“" + averageChunks: "ๅนณๅ‡ๅ€ๅกŠๆ•ธ" + averageCpuUsage: "Average CPU Usage" + averageEntities: "ๅนณๅ‡ๅฏฆ้ซ”ๆ›ธ" + averageKdr: "ๅนณๅ‡ KDR" + averageMobKdr: "ๅนณๅ‡็”Ÿ็‰ฉ KDR" + averagePing: "ๅนณๅ‡ๅปถ้ฒ" + averagePlayers: "Average Players" + averagePlaytime: "ๅนณๅ‡้Š็Žฉๆ™‚้–“" + averageRamUsage: "Average RAM Usage" + averageServerDowntime: "Average Downtime / Server" + averageSessionLength: "ๅนณๅ‡ๆœƒ่ฉฑๆ™‚้•ท" + averageSessions: "ๅนณๅ‡ๆœƒ่ฉฑ" + averageTps: "ๅนณๅ‡ TPS" + averageTps7days: "Average TPS (7 days)" + banned: "ๅทฒ่ขซๅฐ็ฆ" + bestPeak: "ๆ‰€ๆœ‰ๆ™‚้–“ๅณฐๅ€ผ" + bestPing: "ๆœ€ไฝŽๅปถ้ฒ" + calendar: " ๆ—ฅๆญท" + comparing7days: "ๅฐๆฏ” 7 ๅคฉ็š„ๆƒ…ๆณ" + connectionInfo: "้€ฃๆŽฅ่จŠๆฏ" + country: "ๅœ‹ๅฎถๅ’Œๅœฐๅ€" + cpu: "CPU" + cpuRam: "CPU ๅ’Œ่จ˜ๆ†ถ้ซ”" + cpuUsage: "CPU Usage" + currentPlayerbase: "็›ฎๅ‰็Žฉๅฎถๆ•ธ้‡" + currentUptime: "Current Uptime" + dayByDay: "ๆŒ‰ๅคฉๆŸฅ็œ‹" + dayOfweek: "ๆ˜ŸๆœŸ" + deadliestWeapon: "ๆœ€่‡ดๅ‘ฝ็š„ PvP ๆญฆๅ™จ" + deaths: "ๆญปไบกๆ•ธ" + disk: "็กฌ็ขŸ็ฉบ้–“" + diskSpace: "ๅ‰ฉ้ค˜็กฌ็ขŸ็ฉบ้–“" + downtime: "้—œๆฉŸๆ™‚้–“" + duringLowTps: "ๆŒ็บŒไฝŽ TPS ๆ™‚้–“" + entities: "ๅฏฆ้ซ”" + favoriteServer: "ๆœ€ๅ–œๆ„›็š„ไผบๆœๅ™จ" + firstSession: "็ฌฌไธ€ๆญคๆœƒ่ฉฑ" + firstSessionLength: + average: "Average first session length" + median: "Median first session length" + geoProjection: + dropdown: "Select projection" + equalEarth: "Equal Earth" + mercator: "Mercator" + miller: "Miller" + ortographic: "Ortographic" + geolocations: "ๅœฐ็†ไฝ็ฝฎ" + hourByHour: "ๆŒ‰ๅฐๆ™‚ๆŸฅ็œ‹" + inactive: "ไธๆดป่บ" + indexInactive: "ไธๆดป่บ" + indexRegular: "็ถ“ๅธธไธŠ็ทš" + information: "่จŠๆฏ" + insights: "Insights" + insights30days: "30 ๅคฉๅˆ†ๆž" + irregular: "ๅถ็ˆพไธŠ็ทš" + joinAddress: "Join Address" + joinAddresses: "ๅŠ ๅ…ฅๅœฐๅ€" + kdr: "KDR" + killed: "่ขซๆ“Šๆฎบๆ•ธ" + last24hours: "้ŽๅŽป 24 ๅฐๆ™‚" + last30days: "้ŽๅŽป 30 ๅคฉ" + last7days: "้ŽๅŽป 7 ๅคฉ" + lastConnected: "ๆœ€ๅพŒ้€ฃๆŽฅๆ™‚้–“" + lastPeak: "ไธŠๆฌก็ทšไธŠๅณฐๅ€ผ" + lastSeen: "ๆœ€ๅพŒ็ทšไธŠๆ™‚้–“" + latestJoinAddresses: "Latest Join Addresses" + length: " ้Š็Žฉๆ™‚้•ท" + links: "้€ฃๆŽฅ" + loadedChunks: "ๅทฒ่ผ‰ๅ…ฅๅ€ๅกŠ" + loadedEntities: "ๅทฒ่ผ‰ๅ…ฅๅฏฆ้ซ”" + loneJoins: "ๅ–ฎ็จๅŠ ๅ…ฅ" + loneNewbieJoins: "ๅ–ฎ็จๆ–ฐ็ŽฉๅฎถๅŠ ๅ…ฅ" + longestSession: "ๆœ€้•ทๆœƒ่ฉฑๆ™‚้–“" + lowTpsSpikes: "ไฝŽ TPS ๆ™‚้–“" + lowTpsSpikes7days: "Low TPS Spikes (7 days)" + maxFreeDisk: "ๆœ€ๅคงๅฏ็”จ็กฌ็ขŸ็ฉบ้–“" + medianSessionLength: "Median Session Length" + minFreeDisk: "ๆœ€ๅฐๅฏ็”จ็กฌ็ขŸ็ฉบ้–“" + mobDeaths: "่ขซ็”Ÿ็‰ฉๆ“Šๆฎบๆ•ธ" + mobKdr: "็”Ÿ็‰ฉ KDR" + mobKills: "็”Ÿ็‰ฉๆ“Šๆฎบๆ•ธ" + mostActiveGamemode: "ๆœ€ๅธธ็Žฉ็š„้Šๆˆฒๆจกๅผ" + mostPlayedWorld: "็Žฉ็š„ๆœ€ๅคš็š„ไธ–็•Œ" + name: "ๅ็จฑ" + network: "็พค็ต„็ถฒ่ทฏ" + networkAsNumbers: "็พค็ต„็ถฒ่ทฏ่ณ‡ๆ–™" + networkOnlineActivity: "็พค็ต„็ถฒ่ทฏ็ทšไธŠๆดปๅ‹•" + networkOverview: "็พค็ต„็ถฒ่ทฏ็ฐกไป‹" + networkPage: "็พค็ต„็ถฒ่ทฏ้ ้ข" + new: "ๆ–ฐ" + newPlayerRetention: "ๆ–ฐ็Žฉๅฎถ็•™ๅ‘็Ž‡" + newPlayers: "ๆ–ฐ็Žฉๅฎถ" + newPlayers7days: "New Players (7 days)" + nickname: "ๆšฑ็จฑ" + noDataToDisplay: "No Data to Display" + now: "็พๅœจ" + onlineActivity: "็ทšไธŠๆดปๅ‹•" + onlineActivityAsNumbers: "็ทšไธŠๆดปๅ‹•่ณ‡ๆ–™" + onlineOnFirstJoin: "็ฌฌไธ€ๆฌก้€ฒๅ…ฅไผบๆœๅ™จ็š„็ทšไธŠ็Žฉๅฎถ" + operator: "็ฎก็†ๅ“ก" + overview: "็ฐกไป‹" + perDay: "/ ๅคฉ" + perPlayer: "/ ็Žฉๅฎถ" + perRegularPlayer: "/ ๆ™ฎ้€š็Žฉๅฎถ" + performance: "ๆ€ง่ƒฝ" + performanceAsNumbers: "ๆ€ง่ƒฝ่ณ‡ๆ–™" + ping: "ๅปถ้ฒ" + player: "็Žฉๅฎถ" + playerDeaths: "่ขซ็Žฉๅฎถๆ“Šๆฎบๆฌกๆ•ธ" + playerKills: "ๆ“Šๆฎบ็Žฉๅฎถๆ•ธ" + playerList: "็Žฉๅฎถๅˆ—่กจ" + playerOverview: "็Žฉๅฎถ็ฐกไป‹" + playerPage: "็Žฉๅฎถ้ ้ข" + playerRetention: "Player Retention" + playerbase: "็Žฉๅฎถ่ณ‡ๆ–™" + playerbaseDevelopment: "็Žฉๅฎถ็™ผๅฑ•" + playerbaseOverview: "Playerbase Overview" + players: "็Žฉๅฎถ" + playersOnline: "็ทšไธŠ็Žฉๅฎถ" + playersOnlineNow: "Players Online (Now)" + playersOnlineOverview: "็ทšไธŠๆดปๅ‹•็ฐกไป‹" + playtime: "้Š็Žฉๆ™‚้–“" + plugins: "ๆ’ไปถ" + pluginsOverview: "Plugins Overview" + punchcard: "ๆ‰“ๅก" + punchcard30days: "30 ๅคฉๆ‰“ๅก" + pvpPve: "PvP ๅ’Œ PvE" + pvpPveAsNumbers: "PvP ๅ’Œ PvE ่ณ‡ๆ–™" + query: "้€ฒ่กŒๆŸฅ่ฉข" + quickView: "ๅฟซ้€Ÿ็€่ฆฝ" + ram: "RAM" + ramUsage: "RAM Usage" + recentKills: "ๆœ€่ฟ‘ๆ“Šๆฎบ" + recentPvpDeaths: "ๆœ€่ฟ‘็š„ PVP ๆญปไบก" + recentPvpKills: "ๆœ€่ฟ‘็š„ PVP ๆ“Šๆฎบ" + recentSessions: "ๆœ€่ฟ‘ๆœƒ่ฉฑ" + registered: "่จปๅ†Šๆ™‚้–“" + registeredPlayers: "ๅทฒ่จปๅ†Š็š„็Žฉๅฎถ" + regular: "ๆ™ฎ้€š" + regularPlayers: "ๆ™ฎ้€š็Žฉๅฎถ" + relativeJoinActivity: "ๆœ€่ฟ‘ๅŠ ๅ…ฅๆดปๅ‹•" + secondDeadliestWeapon: "็ฌฌไบŒ่‡ดๅ‘ฝ็š„ PvP ๆญฆๅ™จ" + seenNicknames: "ไฝฟ็”จ้Ž็š„ๆšฑ็จฑ" + server: "ไผบๆœๅ™จ" + serverAnalysis: "ไผบๆœๅ™จๅˆ†ๆž" + serverAsNumberse: "ไผบๆœๅ™จ่ณ‡ๆ–™" + serverCalendar: "Server Calendar" + serverDowntime: "ไผบๆœๅ™จ้—œๆฉŸๆ™‚้–“" + serverOccupied: "ไผบๆœๅ™จ็ทšไธŠๆ™‚้–“" + serverOverview: "ไผบๆœๅ™จ็ฐกไป‹" + serverPage: "ไผบๆœๅ™จ้ ้ข" + serverPlaytime: "ไผบๆœๅ™จ้Šๆˆฒๆ™‚้–“" + serverPlaytime30days: "ๆœ€่ฟ‘ 30 ๅคฉๅ…ง็š„ไผบๆœๅ™จ้Š็Žฉๆ™‚้–“" + serverSelector: "Server selector" + servers: "ไผบๆœๅ™จ" + serversTitle: "ไผบๆœๅ™จ" + session: "ๆœƒ่ฉฑๆฌกๆ•ธ" + sessionCalendar: "Session Calendar" + sessionEnded: " ๆœƒ่ฉฑ็ตๆŸ" + sessionMedian: "ๅนณๅ‡ๆœƒ่ฉฑ้•ทๅบฆ" + sessionStart: "ๆœƒ่ฉฑ้–‹ๅง‹ๆ–ผ" + sessions: "ๆœƒ่ฉฑ" + sortBy: "Sort By" + stacked: "Stacked" + themeSelect: "ไธป้กŒ้ธๆ“‡" + thirdDeadliestWeapon: "็ฌฌไธ‰่‡ดๅ‘ฝ็š„ PvP ๆญฆๅ™จ" + thirtyDays: "30 ๅคฉ" + thirtyDaysAgo: "30 ๅคฉๅ‰" + timesKicked: "่ขซ่ธขๅ‡บๆฌกๆ•ธ" + toMainPage: "ๅ›žๅˆฐไธป้ ้ข" + total: "Total" + totalActive: "็ธฝๆดป่บๆ™‚้•ท" + totalAfk: "็ธฝๆŽ›ๆฉŸๆ™‚้•ท" + totalPlayers: "็ธฝ็Žฉๅฎถๆ•ธ" + totalPlayersOld: "็ธฝ้Š็Žฉๆ™‚้•ท" + totalPlaytime: "็ธฝ้Š็Žฉๆ™‚้–“" + totalServerDowntime: "Total Server Downtime" + tps: "TPS" + trend: "่ถจๅ‹ข" + trends30days: "30 ๅคฉ่ถจๅ‹ข" + uniquePlayers: "็จ็ซ‹็Žฉๅฎถ" + uniquePlayers7days: "Unique Players (7 days)" + veryActive: "้žๅธธๆดป่บ" + weekComparison: "ๆฏ้€ฑๅฐๆฏ”" + weekdays: "'ๆ˜ŸๆœŸไธ€', 'ๆ˜ŸๆœŸไบŒ', 'ๆ˜ŸๆœŸไธ‰', 'ๆ˜ŸๆœŸๅ››', 'ๆ˜ŸๆœŸไบ”', 'ๆ˜ŸๆœŸๅ…ญ', 'ๆ˜ŸๆœŸๆ—ฅ'" + world: "ไธ–็•Œ่ผ‰ๅ…ฅ" + worldPlaytime: "ไธ–็•Œ้Š็Žฉๆ™‚้–“" + worstPing: "ๆœ€้ซ˜ๅปถ้ฒ" + login: + failed: "็™ปๅ…ฅๅคฑๆ•—๏ผš" + forgotPassword: "ๅฟ˜่จ˜ๅฏ†็ขผ๏ผŸ" + forgotPassword1: "ๅฟ˜่จ˜ๅฏ†็ขผ๏ผŸ ่จปๅ†Šไธฆๅ†ๆฌก่จปๅ†Šใ€‚" + forgotPassword2: "ๅœจ้Šๆˆฒไธญไฝฟ็”จไปฅไธ‹ๆŒ‡ไปคไพ†ๅˆช้™ค็›ฎๅ‰ๅธณๆˆถ๏ผš" + forgotPassword3: "ๆˆ–ไฝฟ็”จๆŽงๅˆถๅฐๆŒ‡ไปค๏ผš" + forgotPassword4: "ไฝฟ็”จๆŒ‡ไปคๅพŒ๏ผŒ" + login: "็™ปๅ…ฅ" + logout: "็™ปๅ‡บ" + password: "ๅฏ†็ขผ" + register: "ๅปบ็ซ‹ไธ€ๅ€‹ๅธณๆˆถ๏ผ" + username: "ไฝฟ็”จ่€…ๅ็จฑ" + modal: + info: + bugs: "ๅ ฑๅ‘Šๅ•้กŒ" + contributors: + bugreporters: "ๅ’Œๅ…ถไป–ๅ•้กŒๅ ฑๅ‘Š่€…๏ผ" + code: "ไปฃ็ขผ่ฒข็ป่€…" + donate: "็‰นๅˆฅๆ„Ÿ่ฌ้‚ฃไบ›ๅœจ็ถ“ๆฟŸไธŠๆ”ฏๆด้–‹็™ผ็š„ไบบๅ€‘ใ€‚" + text: 'ไปฅไธ‹ ๅ„ช็ง€ไบบ็‰ฉ ไนŸๅšๅ‡บไบ†่ฒข็ป๏ผš' + translator: "็ฟป่ญฏ่€…" + developer: "็š„้–‹็™ผ่€…ๆ˜ฏ" + discord: "ไธ€่ˆฌๅ•้กŒๆ”ฏๆด๏ผšDiscord" + license: "Player Analytics ้–‹็™ผๅ’ŒๆŽˆๆฌŠๆ–ผ" + metrics: "bStats ็ตฑ่จˆ" + text: "ๆ’ไปถ่จŠๆฏ" + wiki: "Plan Wiki,ๆ•™็จ‹ๅ’Œๆ–‡ๆช”" + version: + available: "ๅฏไปฅไฝฟ็”จ" + changelog: "ๆŸฅ็œ‹ๆ›ดๆ–ฐๆ—ฅ่ชŒ" + dev: "้€™ๆ˜ฏไธ€ๅ€‹้–‹็™ผ็‰ˆๆœฌใ€‚" + download: "ไธ‹่ผ‰" + text: "ๆœ‰ๆ–ฐ็‰ˆๆœฌๅฏไพ›ไธ‹่ผ‰ใ€‚" + title: "็‰ˆๆœฌ" + query: + filter: + activity: + text: "ๅœจๆดป่บๅบฆๅˆ†็ต„ไธญ" + banStatus: + name: "ๅฐ็ฆ็‹€ๆ…‹" + banned: "่ขซๅฐ็ฆ" + country: + text: "ๅŠ ๅ…ฅไพ†่‡ชๅœ‹ๅฎถ" + generic: + allPlayers: "ๅ…จ้ƒจ็Žฉๅฎถ" + and: "ๅค–ๅŠ " + start: "ๆŸฅ่ฉข็Žฉๅฎถ๏ผš" + joinAddress: + text: "ๅŠ ๅ…ฅๅœฐๅ€" + nonOperators: "้ž็ฎก็†ๅ“ก" + notBanned: "ๆœช่ขซๅฐ็ฆ" + operatorStatus: + name: "็ฎก็†ๅ“ก็‹€ๆ…‹" + operators: "็ฎก็†ๅ“ก" + playedBetween: + text: "ๅœจๆญคๆœŸ้–“้Š็Žฉ้Ž" + pluginGroup: + name: "ๅฐ็ต„๏ผš" + text: "ๅœจ ${plugin} ๆ’ไปถ็š„ ${group} ๅˆ†็ต„ไธญ" + registeredBetween: + text: "ๅœจๆญคๆœŸ้–“่จปๅ†Š" + title: + activityGroup: "ๆดป่บๅบฆๅˆ†็ต„" + view: " ๆ—ฅๆœŸ็ฏ„ๅœ:" + filters: + add: "ๅขžๅŠ ้Žๆฟพๅ™จ.." + loading: "่ผ‰ๅ…ฅ้Žๆฟพๅ™จไธญ..." + generic: + are: "`ๆ˜ฏ`" + label: + from: ">ๅพž " + makeAnother: "้€ฒ่กŒๅฆไธ€ๅ€‹ๆŸฅ่ฉข" + to: ">ๅˆฐ " + view: "ๆ—ฅๆœŸ็ฏ„ๅœ" + performQuery: "ๅŸท่กŒๆŸฅ่ฉข๏ผ" + results: + match: "ๆœๅฐ‹ๅˆฐ ${resultCount} ๅ€‹็Žฉๅฎถ" + none: "ๆŸฅ่ฉขๅˆฐ 0 ๅ€‹็ตๆžœ" + title: "ๆŸฅ่ฉข็ตๆžœ" + title: + activity: "ๆŸฅ่ฉข็Žฉๅฎถ็š„ๆดป่บๅบฆ" + activityOnDate: 'ๆดป่บๅœจ ' + sessionsWithinView: "ๆŸฅ็œ‹็ฏ„ๅœๅ…ง็š„ๆœƒ่ฉฑ" + text: "ๆŸฅ่ฉข<" + register: + completion: "่จปๅ†ŠๅฎŒๆˆ" + completion1: "ๆ‚จ็พๅœจๅฏไปฅๅฎŒๆˆไฝฟ็”จ่€…่จปๅ†Šๆต็จ‹ใ€‚" + completion2: "่จปๅ†Šไปฃ็ขผๅฐ‡ๅœจ 15 ๅˆ†้˜ๅพŒ้ŽๆœŸ" + completion3: "ๅœจ้Šๆˆฒไธญไฝฟ็”จไปฅไธ‹ๆŒ‡ไปคๅฎŒๆˆ่จปๅ†Š๏ผš" + completion4: "ๆˆ–ไฝฟ็”จๆŽงๅˆถๅฐ๏ผš" + createNewUser: "ๅปบ็ซ‹ไธ€ๅ€‹ๆ–ฐไฝฟ็”จ่€…" + error: + checkFailed: "ๆชขๆŸฅ่จปๅ†Š็‹€ๆ…‹ๅคฑๆ•—๏ผš" + failed: "่จปๅ†Šๅคฑๆ•—๏ผš" + noPassword: "ไฝ ้œ€่ฆๅกซๅฏซๅฏ†็ขผ" + noUsername: "ไฝ ้œ€่ฆๅกซๅฏซไฝฟ็”จ่€…ๅ็จฑ" + usernameLength: "ไฝฟ็”จ่€…ๅ็จฑๆœ€ๅคšๅฏไปฅๅŒ…ๅซ 50 ๅ€‹ๅญ—็ฌฆ๏ผŒไฝ ็š„ไฝฟ็”จ่€…ๅ็จฑๆœ‰" + login: "ๅทฒ็ถ“ๆœ‰ๅธณ่™Ÿไบ†๏ผŸ ็™ปๅ…ฅ๏ผ" + passwordTip: "ๅฏ†็ขผไธ่ƒฝ่ถ…้Ž8ๅ€‹ๅญ—็ฌฆ๏ผŒๆฒ’ๆœ‰ๅ…ถไป–้™ๅˆถใ€‚" + register: "่จปๅ†Š" + usernameTip: "ไฝฟ็”จ่€…ๅ็จฑๆœ€ๅคšๅฏไปฅๅŒ…ๅซ 50 ๅ€‹ๅญ—็ฌฆใ€‚" + text: + clickToExpand: "้ปžๆ“Šๅฑ•้–‹" + comparing15days: "ๅฐๆฏ” 15 ๅคฉ็š„ๆƒ…ๆณ" + comparing30daysAgo: "ๅฐๆฏ” 30 ๅคฉๅ‰ๅ’Œ็พๅœจ็š„ๆƒ…ๆณ" + noExtensionData: "ๆฒ’ๆœ‰ๆ“ดๅ……่ณ‡ๆ–™" + noLowTps: "ๆฒ’ๆœ‰ไฝŽ TPS ๆ™‚้–“" + unit: + chunks: "ๅ€ๅกŠ" + players: "็Žฉๅฎถ" + value: + localMachine: "ๆœฌๅœฐไธปๆฉŸ" + noKills: "ๆฒ’ๆœ‰ๆ“Šๆฎบๆ•ธ" + offline: " ้›ข็ทš" + online: " ็ทšไธŠ" + with: " ่ˆ‡" + version: + changelog: "ๆŸฅ็œ‹ๆ›ดๆ–ฐๆ—ฅ่ชŒ" + current: "ไฝ ็š„็‰ˆๆœฌๆ˜ฏ ${0}" + download: "ไธ‹่ผ‰ Plan - ${0}.jar" + isDev: "้€™ๆ˜ฏไธ€ๅ€‹้–‹็™ผ็‰ˆๆœฌใ€‚" + updateButton: "ๆ›ดๆ–ฐ" + updateModal: + text: "ๆœ‰ๆ–ฐ็‰ˆๆœฌๅฏไพ›ไธ‹่ผ‰ใ€‚" + title: "ๆ–ฐ็‰ˆๆœฌ ${0} ็พๅœจๅฏ็”จ๏ผ" +plugin: + apiCSSAdded: "้ ้ขๆ“ดๅ……๏ผš ${0} ๅขžๅŠ ๆจฃๅผ่กจ(s) ๅˆฐ ${1}, ${2}" + apiJSAdded: "้ ้ขๆ“ดๅ……๏ผš ${0} ๅขžๅŠ  javascript(s) ๅˆฐ ${1}, ${2}" + disable: + database: "ๆญฃๅœจ่™•็†ๆœช่™•็†็š„้—œ้ตไปปๅ‹™ใ€‚(${0})" + disabled: "Plan ๆ’ไปถๅทฒ้—œ้–‰ใ€‚" + processingComplete: "่™•็†ๅฎŒ็•ขใ€‚" + savingSessions: "ๅ„ฒๅญ˜ๆœชๅฎŒๆˆ็š„ๆœƒ่ฉฑไธญ..." + savingSessionsTimeout: "่ถ…ๆ™‚๏ผŒๅฐ‡ๅœจไธ‹ไธ€ๆฌกๅ•Ÿๅ‹•ๅ„ฒๅญ˜ๆœชๅฎŒๆˆ็š„ๆœƒ่ฉฑใ€‚" + waitingDb: "ๆญฃๅœจ็ญ‰ๅพ…ๆŸฅ่ฉขๅฎŒๆˆ๏ผŒไปฅ้ฟๅ… SQLite ไฝฟ JVM ๅดฉๆฝฐ..." + waitingDbComplete: "SQLite ้€ฃๆŽฅๅทฒ้—œ้–‰ใ€‚" + waitingTransactions: "ๆญฃๅœจ็ญ‰ๅพ…ๆœชๅฎŒๆˆ็š„ไบ‹ๅ‹™ไปฅ้ฟๅ…่ณ‡ๆ–™ไธŸๅคฑ..." + waitingTransactionsComplete: "ไบ‹ๅ‹™้šŠๅˆ—ๅทฒ้—œ้–‰ใ€‚" + webserver: "็ถฒ้ ไผบๆœๅ™จๅทฒ้—œ้–‰ใ€‚" + enable: + database: "${0} - ๅทฒ้€ฃๆŽฅๅˆฐ่ณ‡ๆ–™ๅบซใ€‚" + enabled: "Plan ๆ’ไปถๅทฒๅ•Ÿ็”จใ€‚" + fail: + database: "${0} - ้€ฃๆŽฅๅˆฐ่ณ‡ๆ–™ๅบซๅคฑๆ•—๏ผš${1}" + databasePatch: "่ณ‡ๆ–™ๅบซ่ฃœไธๅคฑๆ•—๏ผŒๆ’ไปถๅฟ…้ ˆ่ขซๅœ็”จใ€‚่ซ‹ๅ ฑๅ‘Šๆญคๅ•้กŒ" + databaseType: "${0} ๆ˜ฏไธๆ”ฏๆด็š„่ณ‡ๆ–™ๅบซ้กžๅž‹" + geoDBWrite: "ๅ„ฒๅญ˜ๅทฒไธ‹่ผ‰็š„ GeoLite2 ๅœฐ็†ไฝ็ฝฎ่ณ‡ๆ–™ๅบซๆ™‚็™ผ็”Ÿๅ•้กŒ" + webServer: "็ถฒ้ ไผบๆœๅ™จๆฒ’ๆœ‰ๅˆๅง‹ๅŒ–!" + notify: + badIP: "0.0.0.0 ไธๆ˜ฏๆœ‰ๆ•ˆ็š„ๅœฐๅ€๏ผŒ่ซ‹ไฟฎๆ”น Alternative_IP ่จญๅฎš. ๅฆๅ‰‡ๅฏ่ƒฝๆœƒๅฐŽ่‡ด็ถฒ้ ๅœฐๅ€้Œฏ่ชค!" + emptyIP: "server.properties ไธญ็š„ IP ็‚บ็ฉบไธ”ๆœชไฝฟ็”จๅ‚™็”จ IPใ€‚้€™ๅฏ่ƒฝๆœƒๅฐŽ่‡ดๅœฐๅ€ๅ‡บ้Œฏ๏ผ" + geoDisabled: "ๅทฒ้—œ้–‰ๅœฐ็†ไฝ็ฝฎๆ”ถ้›†ใ€‚(Data.Geolocations: false)" + geoInternetRequired: "Plan ้œ€่ฆๅœจ้ฆ–ๆฌกๅŸท่กŒๆ™‚้€ฃๆŽฅ็ถฒ่ทฏไปฅไธ‹่ผ‰ GeoLite2 ๅœฐ็†ไฝ็ฝฎ่ณ‡ๆ–™ๅบซใ€‚" + storeSessions: "ๆญฃๅœจๅ„ฒๅญ˜ไน‹ๅ‰้—œๆฉŸๅ‰็•™ไธ‹็š„ๆœƒ่ฉฑใ€‚" + webserverDisabled: "็ถฒ้ ไผบๆœๅ™จๆœชๅˆๅง‹ๅŒ–ใ€‚(WebServer.DisableWebServer: true)" + webserver: "็ถฒ้ ไผบๆœๅ™จๅทฒๅœจ ${0} ( ${1} ) ็ซฏๅฃไธŠๅŸท่กŒ" + generic: + dbApplyingPatch: "ๆญฃๅœจๆ‡‰็”จ่ฃœไธ๏ผš${0}..." + dbFaultyLaunchOptions: "ๅ•Ÿๅ‹•ๅƒๆ•ธๅ‡บ้Œฏ๏ผŒๆญฃไฝฟ็”จ้ ่จญๅƒๆ•ธ๏ผˆ${0}๏ผ‰" + dbNotifyClean: "็งป้™คไบ† ${0} ไฝไฝฟ็”จ่€…็š„่ณ‡ๆ–™ใ€‚" + dbNotifySQLiteWAL: "ๆญคไผบๆœๅ™จ็‰ˆๆœฌไธๆ”ฏๆด SQLite WAL ๆจกๅผ๏ผŒๆญฃไฝฟ็”จ้ ่จญๆจกๅผใ€‚้€™ๅฏ่ƒฝๆœƒๅฝฑ้Ÿฟๆ€ง่ƒฝใ€‚" + dbPatchesAlreadyApplied: "ๅทฒๆ‡‰็”จๆ‰€ๆœ‰่ณ‡ๆ–™ๅบซ่ฃœไธใ€‚" + dbPatchesApplied: "ๅทฒๆˆๅŠŸๆ‡‰็”จๆ‰€ๆœ‰่ณ‡ๆ–™ๅบซ่ฃœไธใ€‚" + dbSchemaPatch: "Database: Making sure schema is up to date.." + loadedServerInfo: "Server identifier loaded: ${0}" + loadingServerInfo: "ๆญฃๅœจ่ผ‰ๅ…ฅไผบๆœๅ™จ่ญ˜ๅˆฅ่ณ‡่จŠ" + no: "ๅฆ" + today: "'ไปŠๅคฉ'" + unavailable: "็„กๆณ•ไฝฟ็”จ" + unknown: "ๆœช็Ÿฅ" + yes: "ๆ˜ฏ" + yesterday: "'ๆ˜จๅคฉ'" + version: + checkFail: "็„กๆณ•ๆชขๆŸฅๆœ€ๆ–ฐ็‰ˆๆœฌ่™Ÿ" + checkFailGithub: "็„กๆณ•ๅพž Github/versions.txt ่ผ‰ๅ…ฅ็‰ˆๆœฌ่จŠๆฏ" + isDev: " ้€™ๆ˜ฏไธ€ๅ€‹้–‹็™ผ็‰ˆๆœฌใ€‚" + isLatest: "ไฝ ๆญฃๅœจไฝฟ็”จๆœ€ๆ–ฐ็‰ˆๆœฌใ€‚" + updateAvailable: "ๆœ‰ๆ–ฐ็‰ˆๆœฌ (${0}) ๅฏ็”จ ${1}" + updateAvailableSpigot: "ๆœ‰ๆ–ฐ็‰ˆๆœฌๅฏ็”จ๏ผš${0}" + webserver: + fail: + SSLContext: "็ถฒ้ ไผบๆœๅ™จ๏ผšSSL ็’ฐๅขƒๅˆๅง‹ๅŒ–ๅคฑๆ•—ใ€‚" + certFileEOF: "็ถฒ้ ไผบๆœๅ™จ: ๅœจ่ฎ€ๅ–่ญ‰ๆ›ธๆช”ๆกˆๆ™‚ๅ‡บ็พไบ†EOF็•ฐๅธธ. ๏ผˆ่ซ‹ๆชขๆŸฅ่ญ‰ๆ›ธๆช”ๆกˆๅฎŒๆ•ดๆ€ง๏ผ‰" + certStoreLoad: "็ถฒ้ ไผบๆœๅ™จ๏ผšSSL ่ญ‰ๆ›ธ่ผ‰ๅ…ฅๅคฑๆ•—ใ€‚" + portInUse: "ๆœชๆˆๅŠŸๅˆๅง‹ๅŒ–็ถฒ้ ไผบๆœๅ™จใ€‚็ซฏๅฃ(${0})ๆ˜ฏๅฆ่ขซๅทฒ่ขซๅ ็”จ๏ผŸ" + notify: + authDisabledConfig: "็ถฒ้ ไผบๆœๅ™จ: ไฝฟ็”จ่€…็™ปๅ…ฅๅทฒ้—œ้–‰! ๏ผˆๅทฒๅœจ่จญๅฎšๆช”ๆกˆไธญ็ฆๆญข๏ผ‰" + authDisabledNoHTTPS: "็ถฒ้ ไผบๆœๅ™จ๏ผšๅทฒ็ฆๆญขไฝฟ็”จ่€…็™ปๅ…ฅ๏ผ๏ผˆHTTP ๆ–นๅผไธๅฎ‰ๅ…จ๏ผ‰" + certificateExpiresOn: "Webserver: Loaded certificate is valid until ${0}." + certificateExpiresPassed: "Webserver: Certificate has expired, consider renewing the certificate." + certificateExpiresSoon: "Webserver: Certificate expires in ${0}, consider renewing the certificate." + certificateNoSuchAlias: "Webserver: Certificate with alias '${0}' was not found inside the keystore file '${1}'." + http: "็ถฒ้ ไผบๆœๅ™จ๏ผš็„ก่ญ‰ๆ›ธ -> ๆญฃไฝฟ็”จ HTTP ไผบๆœๅ™จๆไพ›ๅฏ่ฆ–ๅŒ–ๆ•ˆๆžœใ€‚" + ipWhitelist: "็ถฒ้ ไผบๆœๅ™จ: IP ็™ฝๅๅ–ฎๅทฒๅ•Ÿ็”จใ€‚" + ipWhitelistBlock: "็ถฒ้ ไผบๆœๅ™จ๏ผš${0} ่ขซๆ‹’็ต•่จชๅ• '${1}'. ๏ผˆไธๅœจ็™ฝๅๅ–ฎไธญ๏ผ‰" + noCertFile: "็ถฒ้ ไผบๆœๅ™จ๏ผšๆ‰พไธๅˆฐ่ญ‰ๆ›ธๅฏ†้‘ฐๆช”ๆกˆ๏ผš${0}" + reverseProxy: "็ถฒ้ ไผบๆœๅ™จ: HTTPS ไปฃ็†ๆจกๅผๅทฒ้–‹ๅ•Ÿ, ่ซ‹็ขบไฟไฝ ็š„ๅๅ‘ไปฃ็†ๅทฒ็ถ“่จญๅฎš็‚บ HTTPS ๆจกๅผไธฆไธ” Plan ็š„ Alternative_IP.Address ้ธ้ …ๅทฒ็ถ“ๆŒ‡ๅ‘ไปฃ็†" diff --git a/Plan/common/src/main/resources/assets/plan/web/css/sb-admin-2.css b/Plan/common/src/main/resources/assets/plan/web/css/sb-admin-2.css index c86fe89d9..b58acd7cd 100644 --- a/Plan/common/src/main/resources/assets/plan/web/css/sb-admin-2.css +++ b/Plan/common/src/main/resources/assets/plan/web/css/sb-admin-2.css @@ -1866,6 +1866,7 @@ a.text-dark:hover, a.text-dark:focus { #wrapper { display: flex; + min-height: 100vh; } #wrapper #content-wrapper { @@ -2529,8 +2530,9 @@ a.text-dark:hover, a.text-dark:focus { display: none; } -.sidebar hr.sidebar-divider { +.sidebar .sidebar-divider { margin: 0 1rem 1rem; + border-top: 1px solid rgba(255, 255, 255, 0.2); } .sidebar .sidebar-heading { diff --git a/Plan/common/src/main/resources/assets/plan/web/css/style.css b/Plan/common/src/main/resources/assets/plan/web/css/style.css index 78c8d42c4..e6605818d 100644 --- a/Plan/common/src/main/resources/assets/plan/web/css/style.css +++ b/Plan/common/src/main/resources/assets/plan/web/css/style.css @@ -869,6 +869,30 @@ div#navSrvContainer::-webkit-scrollbar-thumb { color: #fff; } +.btn.bg-plan:hover, +.btn.bg-pink:hover, +.btn.bg-red:hover, +.btn.bg-purple:hover, +.btn.bg-deep-purple:hover, +.btn.bg-indigo:hover, +.btn.bg-light-blue:hover, +.btn.bg-black:hover, +.btn.bg-blue:hover, +.btn.bg-cyan:hover, +.btn.bg-teal:hover, +.btn.bg-green:hover, +.btn.bg-light-green:hover, +.btn.bg-lime:hover, +.btn.bg-yellow:hover, +.btn.bg-amber:hover, +.btn.bg-orange:hover, +.btn.bg-deep-orange:hover, +.btn.bg-brown:hover, +.btn.bg-grey:hover, +.btn.bg-blue-grey:hover { + color: #ccc; +} + .bg-night, body.theme-night .fc-toolbar-chunk .btn.btn-primary { background-color: #44475a; color: #eee8d5; @@ -1253,4 +1277,11 @@ body.sidebar-hidden .navbar-nav { .container, .container-fluid, .container-sm, .container-md, .container-lg, .container-xl { padding-left: 1.5rem; padding-right: 1.5rem; +} + +.topbar-divider { + width: 0; + border-right: 1px solid #e3e6f0; + height: calc(4.375rem - 2rem); + margin: auto 1rem; } \ No newline at end of file diff --git a/Plan/common/src/main/resources/assets/plan/web/error.html b/Plan/common/src/main/resources/assets/plan/web/error.html index eeb345bfd..cfd78f6ee 100644 --- a/Plan/common/src/main/resources/assets/plan/web/error.html +++ b/Plan/common/src/main/resources/assets/plan/web/error.html @@ -1,4 +1,4 @@ - +๏ปฟ @@ -12,7 +12,7 @@ ${titleText} - + @@ -20,8 +20,8 @@ - - + + @@ -59,7 +59,7 @@
- ${version} + ${versionButton}
@@ -236,8 +236,8 @@ crossorigin="anonymous"> - - + + diff --git a/Plan/common/src/main/resources/assets/plan/web/js/color-selector.js b/Plan/common/src/main/resources/assets/plan/web/js/color-selector.js index 43cc5bde9..837f62890 100644 --- a/Plan/common/src/main/resources/assets/plan/web/js/color-selector.js +++ b/Plan/common/src/main/resources/assets/plan/web/js/color-selector.js @@ -473,7 +473,11 @@ Highcharts.setOptions(Highcharts.theme); updateGraphs(); } catch (e) { - console.error(e); + if ("Highcharts is not defined" === e.message || "updateGraphs is not defined" === e.message) { + // Highcharts isn't loaded, can be ignored + } else { + console.error(e); + } } } diff --git a/Plan/common/src/main/resources/assets/plan/web/js/domUtils.js b/Plan/common/src/main/resources/assets/plan/web/js/domUtils.js index 59d68d13a..258dab53d 100644 --- a/Plan/common/src/main/resources/assets/plan/web/js/domUtils.js +++ b/Plan/common/src/main/resources/assets/plan/web/js/domUtils.js @@ -16,4 +16,23 @@ function insertElementAfter(elementSelector, createElementFunction) { function insertElementAfterElement(placeBefore, createElementFunction) { const element = createElementFunction(); placeBefore.insertAdjacentElement('afterend', element); -} \ No newline at end of file +} + +/** + * Shows an error using the page loader. + * @param {string} [message] The message to be shown. + */ +function loaderError(message) { + let loader = document.querySelector(".page-loader"); + let loaderText = document.querySelector('.loader-text'); + + if (loader.style.display === "none") { + loader.style.display = "block"; + } + + if (message !== undefined) { + loaderText.innerText = message; + } else { + loaderText.innerText = "Error occurred, see the Developer Console (Ctrl+Shift+I) for details." + } +} diff --git a/Plan/common/src/main/resources/assets/plan/web/js/filters.js b/Plan/common/src/main/resources/assets/plan/web/js/filters.js index 20624c443..cc6fb40cd 100644 --- a/Plan/common/src/main/resources/assets/plan/web/js/filters.js +++ b/Plan/common/src/main/resources/assets/plan/web/js/filters.js @@ -102,6 +102,14 @@ class geolocationsFilter extends MultipleChoiceFilter { } } +class PluginBooleanGroupsFilter extends MultipleChoiceFilter { + constructor( + id, options + ) { + super(id, "pluginsBooleanGroups", `have Plugin boolean value`, options); + } +} + class PluginGroupsFilter extends MultipleChoiceFilter { constructor( id, kind, options @@ -201,6 +209,12 @@ class RegisteredBetweenFilter extends BetweenDateFilter { } } +class PlayedOnServerFilter extends MultipleChoiceFilter { + constructor(id, options) { + super(id, "playedOnServer", "have played on at least one of", options); + } +} + function createFilter(filter, id) { if (filter.kind.startsWith("pluginGroups-")) { return new PluginGroupsFilter(id, filter.kind, filter.options); @@ -220,6 +234,10 @@ function createFilter(filter, id) { return new PlayedBetweenFilter(id, filter.options); case "registeredBetween": return new RegisteredBetweenFilter(id, filter.options); + case "pluginsBooleanGroups": + return new PluginBooleanGroupsFilter(id, filter.options); + case "playedOnServer": + return new PlayedOnServerFilter(id, filter.options); default: throw new Error("Unsupported filter kind: '" + filter.kind + "'"); } @@ -246,6 +264,10 @@ function getReadableFilterName(filter) { return "Played between"; case "registeredBetween": return "Registered between"; + case "pluginsBooleanGroups": + return "Has plugin boolean value"; + case "playedOnServer": + return "Has played on one of servers"; default: return filter.kind; } diff --git a/Plan/common/src/main/resources/assets/plan/web/js/graphs.js b/Plan/common/src/main/resources/assets/plan/web/js/graphs.js index 3f3eda04d..0a6674150 100644 --- a/Plan/common/src/main/resources/assets/plan/web/js/graphs.js +++ b/Plan/common/src/main/resources/assets/plan/web/js/graphs.js @@ -290,6 +290,80 @@ function performanceChart(id, playersOnlineSeries, tpsSeries, cpuSeries, ramSeri } function playersChart(id, playersOnlineSeries, sel) { + function groupByIntervalStartingFrom(startDate, interval) { + let previousGroupStart = startDate; + const groupByInterval = [[]]; + + for (let point of playersOnlineSeries.data) { + const date = point[0]; + if (date < startDate) { + continue; + } + + if (previousGroupStart + interval < date) { + previousGroupStart = date; + groupByInterval.push([]); + } + + const currentGroup = groupByInterval[groupByInterval.length - 1]; + currentGroup.push(point); + } + return groupByInterval; + } + + function averageGroupPoints(groupByInterval, minDate) { + const averages = []; + for (let group of groupByInterval) { + let totalDate = 0; + let total = 0; + let count = group.length; + for (let point of group) { + totalDate += (point[0] - minDate); // Remove the minDate from dates to calculate a smaller total + total += point[1]; + } + + if (count !== 0) { + const middleDate = Math.trunc((totalDate / count) + minDate); + const average = Math.trunc(total / count); + averages.push([middleDate, average]); + } + } + return averages; + } + + function getAveragePlayersSeries(minDate, twentyPointInterval) { + const groupByInterval = groupByIntervalStartingFrom(minDate, twentyPointInterval); + + return { + name: s.name.averagePlayersOnline, + type: s.type.spline, + tooltip: s.tooltip.zeroDecimals, + data: averageGroupPoints(groupByInterval, minDate), + color: "#02458d", + yAxis: 0 + }; + } + + function updateAveragePlayers(event) { + const minDate = event.min; + const maxDate = event.max; + const twentyPointInterval = (maxDate - minDate) / 20; + + const averagePlayersSeries = getAveragePlayersSeries(minDate, twentyPointInterval); + + const playersOnlineGraph = graphs.find(graph => graph && graph.renderTo && graph.renderTo.id === id); + playersOnlineGraph.series[1].update(averagePlayersSeries); + } + + const emptyAveragePlayersSeries = { + name: s.name.averagePlayersOnline, + type: s.type.spline, + tooltip: s.tooltip.zeroDecimals, + data: [], + color: "#02458d", + yAxis: 0 + }; + graphs.push(Highcharts.stockChart(id, { rangeSelector: { selected: sel, @@ -299,13 +373,20 @@ function playersChart(id, playersOnlineSeries, sel) { softMax: 2, softMin: 0 }, + /* Average online players graph Disabled + xAxis: { + events: { + afterSetExtremes: updateAveragePlayers + } + }, + */ title: {text: ''}, plotOptions: { areaspline: { fillOpacity: 0.4 } }, - series: [playersOnlineSeries] + series: [playersOnlineSeries, /*emptyAveragePlayersSeries*/] })); } @@ -551,7 +632,8 @@ function stackChart(id, categories, series, label) { tickmarkPlacement: 'on', title: { enabled: false - } + }, + ordinal: false }, yAxis: { title: { diff --git a/Plan/common/src/main/resources/assets/plan/web/js/loadErrors.js b/Plan/common/src/main/resources/assets/plan/web/js/loadErrors.js deleted file mode 100644 index 4eccba4ed..000000000 --- a/Plan/common/src/main/resources/assets/plan/web/js/loadErrors.js +++ /dev/null @@ -1,36 +0,0 @@ -const errorLogs = document.getElementById('error-logs'); - -function renderErrorLog(i, errorLog) { - return createErrorAccordionTitle(i, errorLog) + createErrorAccordionBody(i, errorLog); -} - -function createErrorAccordionTitle(i, errorLog) { - let style = 'bg-amber-outline'; - const tagLine = errorLog.contents[0]; - return ` - ${errorLog.fileName} - ${tagLine} - ` -} - -function createErrorAccordionBody(i, errorLog) { - return ` - -
${errorLog.contents.join('\n')}
- - `; -} - -jsonRequest("./v1/errors", (json, error) => { - if (error) { - return errorLogs.innerText = `Failed to load /v1/errors: ${error}`; - } - - let html = ``; - for (let i = 0; i < json.length; i++) { - html += renderErrorLog(i, json[i]); - } - - errorLogs.innerHTML = html; -}) \ No newline at end of file diff --git a/Plan/common/src/main/resources/assets/plan/web/js/localeSystem.js b/Plan/common/src/main/resources/assets/plan/web/js/localeSystem.js new file mode 100644 index 000000000..ecc7dddf9 --- /dev/null +++ b/Plan/common/src/main/resources/assets/plan/web/js/localeSystem.js @@ -0,0 +1,126 @@ +/** + * A locale system for localizing the website. + */ +const localeSystem = { + /** + * @function + * Localizes an element. + * @param {Element} element Element to localize + * @param {Object} [options] Options + */ + localize: {}, + + /** + * The current default language reported by the server. + * @type {string} + * @readonly + */ + defaultLanguage: "", + + /** + * The current available languages reported by the server. + * @type {Object.} + * @readonly + */ + availableLanguages: {}, + clientLocale: "", + + /** + * Initializes the locale system. Gets the default & available languages from `/v1/locale`, and initializes i18next. + */ + init: function () { + jsonRequest("./v1/locale", (json, error) => { + if (error) { + throw "Error occurred when downloading language information: " + error; + } + + this.defaultLanguage = json.defaultLanguage; + this.availableLanguages = json.languages; + + this.clientLocale = window.localStorage.getItem("locale"); + if (!this.clientLocale) { + this.clientLocale = this.defaultLanguage; + } + + i18next.use(i18nextChainedBackend) + .init({ + lng: this.clientLocale, + fallbackLng: false, + supportedLngs: Object.keys(this.availableLanguages), + backend: { + backends: [ + i18nextLocalStorageBackend, + i18nextHttpBackend + ], + backendOptions: [{ + expirationTime: 7 * 24 * 60 * 60 * 1000 // 7 days + }, { + loadPath: './v1/locale/{{lng}}' + }] + }, + }, () => { + this.loadSelector(); + this.initLoci18next(); + }); + }) + }, + + initLoci18next: function () { + this.localize = locI18next.init(i18next, { + selectorAttr: 'data-i18n', // selector for translating elements + targetAttr: 'i18n-target', + optionsAttr: 'i18n-options', + useOptionsAttr: false, + parseDefaultValueFromContent: false // show key as fallback if locale fails to load + }); + this.localize("title"); + this.localize("body"); + }, + + /** + * Loads a locale and translates the page. + * + * @param {string} langCode The two-character code for the language to be loaded, e.g. EN + * @throws Error if an invalid langCode is given + * @see /v1/language endpoint for available language codes + */ + loadLocale: function (langCode) { + if (i18next.language === langCode) { + return; + } + if (!(langCode in this.availableLanguages)) { + throw `The locale ${langCode} isn't available!`; + } + + window.localStorage.setItem("locale", langCode); + i18next.changeLanguage(langCode, () => { + this.localize("title"); + this.localize("body"); + }) + }, + + loadSelector: function () { + const selector = document.getElementById("langSelector"); + + let languages = Object.fromEntries(Object.entries(this.availableLanguages).sort()); + if ('CUSTOM' in languages) { + // Move "Custom" to first in list + delete languages["CUSTOM"] + languages = Object.assign({"CUSTOM": "Custom"}, languages); + } + + for (let lang in languages) { + let option = document.createElement("option"); + option.value = lang; + option.innerText = languages[lang]; + if (this.clientLocale === lang) { + option.setAttribute("selected", "") + } + selector.append(option); + } + + selector.addEventListener('change', event => { + this.loadLocale(event.target.value); + }) + } +} diff --git a/Plan/common/src/main/resources/assets/plan/web/js/logonsine.js b/Plan/common/src/main/resources/assets/plan/web/js/logonsine.js index 984766578..445dae676 100644 --- a/Plan/common/src/main/resources/assets/plan/web/js/logonsine.js +++ b/Plan/common/src/main/resources/assets/plan/web/js/logonsine.js @@ -46,6 +46,7 @@ function drawSine(canvasId) { function draw() { const canvas = document.getElementById(canvasId); + if (canvas == null) return; const context = canvas.getContext("2d"); context.clearRect(0, 0, 1000, 150); @@ -60,6 +61,7 @@ function drawSine(canvasId) { function fix_dpi() { const canvas = document.getElementById(canvasId); + if (canvas == null) return; let dpi = window.devicePixelRatio; canvas.getContext('2d'); const style_width = getComputedStyle(canvas).getPropertyValue("width").slice(0, -2); diff --git a/Plan/common/src/main/resources/assets/plan/web/js/network-values.js b/Plan/common/src/main/resources/assets/plan/web/js/network-values.js index 3360fb5d6..fec870817 100644 --- a/Plan/common/src/main/resources/assets/plan/web/js/network-values.js +++ b/Plan/common/src/main/resources/assets/plan/web/js/network-values.js @@ -69,6 +69,7 @@ function loadNetworkOverviewValues(json, error) { data = json.numbers; element = tab.querySelector('#data_numbers'); + element.querySelector('#data_current_uptime').innerText = data.current_uptime; element.querySelector('#data_total').innerText = data.total_players; element.querySelector('#data_regular').innerText = data.regular_players; element.querySelector('#data_online').innerText = data.online_players; @@ -242,7 +243,9 @@ function loadservers(json, error) { if (!servers || !servers.length) { let elements = document.getElementsByClassName('nav-servers'); - for (let i = 0; i < elements.length; i++) { elements[i].style.display = 'none'; } + for (let i = 0; i < elements.length; i++) { + elements[i].style.display = 'none'; + } document.getElementById('game-server-warning').classList.remove('hidden'); document.getElementById('data_server_list').innerHTML = `

No servers found in the database.

It appears that Plan is not installed on any game servers or not connected to the same database. See wiki for Network tutorial.

` @@ -250,14 +253,14 @@ function loadservers(json, error) { return; } - let navServersHtml = ''; + let navserversHtml = ''; let serversHtml = ''; for (let i = 0; i < servers.length; i++) { - navServersHtml += addserverToNav(servers[i]); + navserversHtml += addserverToNav(servers[i]); serversHtml += createnetworkserverBox(i, servers[i]); } - document.getElementById("navSrvContainer").innerHTML = navServersHtml; + document.getElementById("navSrvContainer").innerHTML = navserversHtml; document.getElementById("data_server_list").innerHTML = serversHtml; for (let i = 0; i < servers.length; i++) { @@ -323,6 +326,7 @@ function onViewserver(i, servers) { quickView.querySelector('#data_avg_tps').innerText = server.avg_tps; quickView.querySelector('#data_low_tps_spikes').innerText = server.low_tps_spikes; quickView.querySelector('#data_downtime').innerText = server.downtime; + quickView.querySelector('#data_current_uptime').innerText = server.current_uptime; }, 0); } } @@ -444,4 +448,230 @@ function loadJoinAddressPie(json, error) { } else if (error) { document.getElementById('joinAddressPie').innerText = `Failed to load graph data: ${error}`; } +} + +function loadperformanceserverOptions() { + const refreshElement = document.querySelector(`#performance .refresh-element`); + refreshElement.querySelector('i').addEventListener('click', () => { + if (refreshElement.querySelector('.refresh-notice').innerHTML.length) { + return; + } + onSelectperformanceservers(); + refreshElement.querySelector('.refresh-notice').innerHTML = ' Updating..'; + }); + const selector = document.getElementById('performance-server-selector'); + jsonRequest('./v1/network/serverOptions', function (json, error) { + if (json) { + let options = ``; + for (let server of json.servers) { + options += `${server.serverName}` + } + selector.innerHTML = options; + onSelectperformanceservers(); + } else if (error) { + selector.innerText = `Failed to load server list: ${error}` + } + }); +} + +async function onSelectperformanceservers() { + const selector = document.getElementById('performance-server-selector'); + const selectedServerUUIDs = []; + + for (const option of selector.selectedOptions) { + selectedServerUUIDs.push(option.getAttribute('data-plan-server-uuid')); + } + + const serverUUIDs = encodeURIComponent(JSON.stringify(selectedServerUUIDs)); + const loadedJson = { + servers: [], + errors: [], + zones: {}, + colors: {}, + timestamp_f: '' + } + const time = new Date().getTime(); + const monthMs = 2592000000; + const after = time - monthMs; + for (const serverUUID of selectedServerUUIDs) { + jsonRequest(`./v1/graph?type=optimizedPerformance&server=${serverUUID}&after=${after}`, (json, error) => { + if (json) { + loadedJson.servers.push(json); + loadedJson.zones = json.zones; + loadedJson.colors = json.colors; + loadedJson.timestamp_f = json.timestamp_f; + } else if (error) { + loadedJson.errors.push(error); + } + }); + } + await awaitUntil(() => selectedServerUUIDs.length === (loadedJson.servers.length + loadedJson.errors.length)); + + jsonRequest(`./v1/network/performanceOverview?servers=${serverUUIDs}`, loadPerformanceValues); + if (loadedJson.errors.length) { + await loadPerformanceGraph(undefined, loadedJson.errors[0]); + } else { + await loadPerformanceGraph({ + servers: loadedJson.servers, + zones: loadedJson.zones, + colors: loadedJson.colors + }, undefined); + } + const refreshElement = document.querySelector(`#performance .refresh-element`); + refreshElement.querySelector('.refresh-time').innerText = loadedJson.timestamp_f; + refreshElement.querySelector('.refresh-notice').innerHTML = ""; +} + +async function loadPerformanceGraph(json, error) { + if (json) { + const zones = { + tps: [{ + value: json.zones.tpsThresholdMed, + color: json.colors.low + }, { + value: json.zones.tpsThresholdHigh, + color: json.colors.med + }, { + value: 30, + color: json.colors.high + }], + disk: [{ + value: json.zones.diskThresholdMed, + color: json.colors.low + }, { + value: json.zones.diskThresholdHigh, + color: json.colors.med + }, { + value: Number.MAX_VALUE, + color: json.colors.high + }] + }; + const serverData = []; + for (const server of json.servers) { + serverData.push({ + serverName: server.serverName, + values: await mapToDataSeries(server.values) + }); + } + + const series = { + tps: [], + cpu: [], + ram: [], + entities: [], + chunks: [], + disk: [] + } + for (const server of serverData) { + series.tps.push({ + name: server.serverName, type: s.type.spline, tooltip: s.tooltip.twoDecimals, + data: server.values.tps, color: json.colors.high, zones: zones.tps, yAxis: 0 + }); + series.cpu.push({ + name: server.serverName, type: s.type.spline, tooltip: s.tooltip.twoDecimals, + data: server.values.cpu, color: json.colors.cpu, yAxis: 0 + }); + series.ram.push({ + name: server.serverName, type: s.type.spline, tooltip: s.tooltip.zeroDecimals, + data: server.values.ram, color: json.colors.ram, yAxis: 0 + }); + series.entities.push({ + name: server.serverName, type: s.type.spline, tooltip: s.tooltip.zeroDecimals, + data: server.values.entities, color: json.colors.entities, yAxis: 0 + }); + series.chunks.push({ + name: server.serverName, type: s.type.spline, tooltip: s.tooltip.zeroDecimals, + data: server.values.chunks, color: json.colors.chunks, yAxis: 0 + }); + series.disk.push({ + name: server.serverName, type: s.type.spline, tooltip: s.tooltip.zeroDecimals, + data: server.values.disk, color: json.colors.high, zones: zones.disk, yAxis: 0 + }); + } + + setTimeout(() => lineChart('tpsGraph', series.tps), 10); + setTimeout(() => lineChart('cpuGraph', series.cpu), 20); + setTimeout(() => lineChart('ramGraph', series.ram), 30); + setTimeout(() => lineChart('entityGraph', series.entities), 40); + setTimeout(() => lineChart('chunkGraph', series.chunks), 50); + setTimeout(() => lineChart('diskGraph', series.disk), 60); + } else if (error) { + const errorMessage = `Failed to load graph data: ${error}`; + document.getElementById('tpsGraph').innerText = errorMessage; + document.getElementById('cpuGraph').innerText = errorMessage; + document.getElementById('ramGraph').innerText = errorMessage; + document.getElementById('entityGraph').innerText = errorMessage; + document.getElementById('chunkGraph').innerText = errorMessage; + document.getElementById('diskGraph').innerText = errorMessage; + } +} + + +/* This function loads Performance tab */ +function loadPerformanceValues(json, error) { + const tab = document.getElementById('performance'); + if (error) { + displayError(tab, error); + return; + } + + // as Numbers + let data = json.numbers; + let element = tab.querySelector('#data_numbers'); + + element.querySelector('#data_low_tps_spikes_30d').innerText = data.low_tps_spikes_30d; + element.querySelector('#data_low_tps_spikes_7d').innerText = data.low_tps_spikes_7d; + element.querySelector('#data_low_tps_spikes_24h').innerText = data.low_tps_spikes_24h; + element.querySelector('#data_server_downtime_30d').innerText = data.server_downtime_30d; + element.querySelector('#data_server_downtime_7d').innerText = data.server_downtime_7d; + element.querySelector('#data_server_downtime_24h').innerText = data.server_downtime_24h; + element.querySelector('#data_avg_server_downtime_30d').innerText = data.avg_server_downtime_30d; + element.querySelector('#data_avg_server_downtime_7d').innerText = data.avg_server_downtime_7d; + element.querySelector('#data_avg_server_downtime_24h').innerText = data.avg_server_downtime_24h; + element.querySelector('#data_tps_30d').innerText = data.tps_30d; + element.querySelector('#data_tps_7d').innerText = data.tps_7d; + element.querySelector('#data_tps_24h').innerText = data.tps_24h; + element.querySelector('#data_cpu_30d').innerText = data.cpu_30d; + element.querySelector('#data_cpu_7d').innerText = data.cpu_7d; + element.querySelector('#data_cpu_24h').innerText = data.cpu_24h; + element.querySelector('#data_ram_30d').innerText = data.ram_30d; + element.querySelector('#data_ram_7d').innerText = data.ram_7d; + element.querySelector('#data_ram_24h').innerText = data.ram_24h; + element.querySelector('#data_entities_30d').innerText = data.entities_30d; + element.querySelector('#data_entities_7d').innerText = data.entities_7d; + element.querySelector('#data_entities_24h').innerText = data.entities_24h; + element.querySelector('#data_chunks_30d').innerText = data.chunks_30d; + element.querySelector('#data_chunks_7d').innerText = data.chunks_7d; + element.querySelector('#data_chunks_24h').innerText = data.chunks_24h; +} + +function loadPingGraph(json, error) { + if (json) { + const series = { + avgPing: { + name: s.name.avgPing, + type: s.type.spline, + tooltip: s.tooltip.twoDecimals, + data: json.avg_ping_series, + color: json.colors.avg + }, + maxPing: { + name: s.name.maxPing, + type: s.type.spline, + tooltip: s.tooltip.zeroDecimals, + data: json.max_ping_series, + color: json.colors.max + }, + minPing: { + name: s.name.minPing, + type: s.type.spline, + tooltip: s.tooltip.zeroDecimals, + data: json.min_ping_series, + color: json.colors.min + } + }; + lineChart('pingGraph', [series.avgPing, series.maxPing, series.minPing]); + } else if (error) { + document.getElementById('pingGraph').innerText = `Failed to load graph data: ${error}`; + } } \ No newline at end of file diff --git a/Plan/common/src/main/resources/assets/plan/web/js/player-values.js b/Plan/common/src/main/resources/assets/plan/web/js/player-values.js index 7c1af3aae..459daee68 100644 --- a/Plan/common/src/main/resources/assets/plan/web/js/player-values.js +++ b/Plan/common/src/main/resources/assets/plan/web/js/player-values.js @@ -30,6 +30,7 @@ function loadPlayerOverviewValues(json, error) { $(element).find("#data_activity_index").text(data.activity_index); $(element).find("#data_activity_index_group").text(data.activity_index_group); $(element).find("#data_favorite_server").text(data.favorite_server); + $(element).find("#data_latest_join_address").text(data.latest_join_address); $(element).find("#data_average_ping").text(data.average_ping); $(element).find("#data_best_ping").text(data.best_ping); $(element).find("#data_worst_ping").text(data.worst_ping); @@ -210,9 +211,9 @@ function createserverAccordionTitle(i, server) { // Lowercase due to locale translation: Server function createserverAccordionBody(i, server) { - return `` + + return `` + `` + - `
` + + `
` + `
` + (server.operator ? `

Operator

` : ``) + (server.banned ? `

Banned

` : ``) + @@ -223,6 +224,8 @@ function createserverAccordionBody(i, server) { `

Longest Session` + server.longest_session_length + `

` + `

Session Median` + server.session_median + `

` + `
` + + `

Join Address` + server.join_address + `

` + + `
` + `

Player Kills` + server.player_kills + `

` + `

Mob Kills` + server.mob_kills + `

` + `

Deaths` + server.deaths + `

` + diff --git a/Plan/common/src/main/resources/assets/plan/web/js/query.js b/Plan/common/src/main/resources/assets/plan/web/js/query.js index 90a5cc410..01b4223e7 100644 --- a/Plan/common/src/main/resources/assets/plan/web/js/query.js +++ b/Plan/common/src/main/resources/assets/plan/web/js/query.js @@ -6,7 +6,8 @@ const queryState = { afterDate: null, afterTime: null, beforeDate: null, - beforeTime: null + beforeTime: null, + servers: [] }, invalidFormFields: { ids: [], @@ -34,6 +35,8 @@ const queryState = { let timestamp = undefined; +let serverMap = {}; + function loadView(json) { queryState.view = json.view; @@ -42,11 +45,36 @@ function loadView(json) { document.getElementById('viewToDateField').setAttribute('placeholder', json.view.beforeDate); document.getElementById('viewToTimeField').setAttribute('placeholder', json.view.beforeTime); + // Load server selector or hide it + if (json.view.servers.length >= 2) { + let options = ``; + for (let server of json.view.servers) { + if (server.proxy) continue; + serverMap[server.serverUUID] = server; + options += `` + } + const serverSelector = document.getElementById("server-selector"); + serverSelector.innerHTML = options; + + serverSelector.addEventListener('click', () => { + queryState.view.servers = []; + if (serverSelector.selectedOptions.length !== serverSelector.options.length) { + for (const option of serverSelector.selectedOptions) { + queryState.view.servers.push(serverMap[option.getAttribute('data-plan-server-uuid')]); + } + } + document.getElementById("serverDropdown").innerText = queryState.view.servers.length + ? `using data of ${queryState.view.servers.length} server(s)` + : "using data of all servers" + }) + } else { + document.getElementById("serverDropdown").classList.add("hidden"); + } + const playersOnlineSeries = { name: 'Players Online', type: 'areaspline', tooltip: {valueDecimals: 0}, data: json.viewPoints, color: '#9E9E9E', yAxis: 0 } - graphs.push(Highcharts.stockChart('viewChart', { rangeSelector: { selected: 3, @@ -345,12 +373,13 @@ function displayResults(json) { /* Player table */ $('.player-table').DataTable({ responsive: true, + deferRender: true, columns: json.data.players.columns, data: json.data.players.data, order: [[5, "desc"]] }); - if (nightmode) { + if ('undefined' !== typeof nightmode && nightmode == true) { document.querySelector('.table').classList.add('table-dark'); } @@ -424,7 +453,8 @@ function displayDataResultScreen(resultCount, view) {
- View: ${afterDate} - ${beforeDate}
+ View: ${afterDate} - ${beforeDate}, +${view.servers.length ? "using data of servers: " + view.servers.map(server => server.serverName).join(', ') : "using data of all servers"}
@@ -508,4 +538,4 @@ function displayDataResultScreen(resultCount, view) { `; -} \ No newline at end of file +} diff --git a/Plan/common/src/main/resources/assets/plan/web/js/server-values.js b/Plan/common/src/main/resources/assets/plan/web/js/server-values.js index 62cadcea6..e657b3d55 100644 --- a/Plan/common/src/main/resources/assets/plan/web/js/server-values.js +++ b/Plan/common/src/main/resources/assets/plan/web/js/server-values.js @@ -74,6 +74,7 @@ function loadserverOverviewValues(json, error) { data = json.numbers; element = tab.querySelector('#data_numbers'); + element.querySelector('#data_current_uptime').innerText = data.current_uptime; element.querySelector('#data_total').innerText = data.total_players; element.querySelector('#data_regular').innerText = data.regular_players; element.querySelector('#data_online').innerText = data.online_players; @@ -157,9 +158,9 @@ function loadOnlineActivityOverviewValues(json, error) { element.querySelector('#data_new_players_7d_avg').innerText = data.new_players_7d_avg; element.querySelector('#data_new_players_24h_avg').innerText = data.new_players_24h_avg; - element.querySelector('#data_new_players_retention_30d').innerText = '(' + data.new_players_retention_30d + '/' + data.new_players_30d + ') ' + data.new_players_retention_30d_perc; - element.querySelector('#data_new_players_retention_7d').innerText = '(' + data.new_players_retention_7d + '/' + data.new_players_7d + ') ' + data.new_players_retention_7d_perc; - element.querySelector('#data_new_players_retention_24h').innerHTML = '(' + data.new_players_retention_24h + '/' + data.new_players_24h + ') ' + data.new_players_retention_24h_perc + ' '; + element.querySelector('#data_new_players_retention_30d').innerText = `(${data.new_players_retention_30d}/${data.new_players_30d}) ${data.new_players_retention_30d_perc}`; + element.querySelector('#data_new_players_retention_7d').innerText = `(${data.new_players_retention_7d}/${data.new_players_7d}) ${data.new_players_retention_7d_perc}`; + element.querySelector('#data_new_players_retention_24h').innerHTML = `(${data.new_players_retention_24h}/${data.new_players_24h}) ${data.new_players_retention_24h_perc} `; element.querySelector('#data_playtime_30d').innerHTML = data.playtime_30d + smallTrend(data.playtime_30d_trend); element.querySelector('#data_playtime_7d').innerText = data.playtime_7d; @@ -316,6 +317,9 @@ function loadPerformanceValues(json, error) { element.querySelector('#data_server_downtime_30d').innerText = data.server_downtime_30d; element.querySelector('#data_server_downtime_7d').innerText = data.server_downtime_7d; element.querySelector('#data_server_downtime_24h').innerText = data.server_downtime_24h; + element.querySelector('#data_players_30d').innerText = data.players_30d; + element.querySelector('#data_players_7d').innerText = data.players_7d; + element.querySelector('#data_players_24h').innerText = data.players_24h; element.querySelector('#data_tps_30d').innerText = data.tps_30d; element.querySelector('#data_tps_7d').innerText = data.tps_7d; element.querySelector('#data_tps_24h').innerText = data.tps_24h; @@ -369,7 +373,7 @@ async function loadOptimizedPerformanceGraph(json, error) { value: json.zones.diskThresholdMed, color: json.colors.low }, { - value: json.zones.tpsThresholdHigh, + value: json.zones.diskThresholdHigh, color: json.colors.med }, { value: Number.MAX_VALUE, @@ -558,7 +562,7 @@ function loadHourlyUniqueAndNewGraph(json, error) { } } -function loadServerCalendar(json, error) { +function loadserverCalendar(json, error) { if (json) { document.getElementById('calendar').innerText = ''; if (window.calendars.online_activity) window.calendars.online_activity.destroy(); diff --git a/Plan/common/src/main/resources/assets/plan/web/js/sessionAccordion.js b/Plan/common/src/main/resources/assets/plan/web/js/sessionAccordion.js index fb7e72c2b..1bedea23c 100644 --- a/Plan/common/src/main/resources/assets/plan/web/js/sessionAccordion.js +++ b/Plan/common/src/main/resources/assets/plan/web/js/sessionAccordion.js @@ -75,9 +75,9 @@ function createAccordionTitle(i, session) { } function createAccordionBody(i, session) { - return ` + return `
-
+

Ended${session.end}

Length${session.length}

@@ -109,18 +109,19 @@ function createKillsTable(player_kills) { let table = ''; if (!player_kills.length) { - table += `` + table += `` } for (const kill of player_kills) { table += ` - + } ${kill.victimName} + ` } diff --git a/Plan/common/src/main/resources/assets/plan/web/js/xmlhttprequests.js b/Plan/common/src/main/resources/assets/plan/web/js/xmlhttprequests.js index 8dcc6bd9a..611a50463 100644 --- a/Plan/common/src/main/resources/assets/plan/web/js/xmlhttprequests.js +++ b/Plan/common/src/main/resources/assets/plan/web/js/xmlhttprequests.js @@ -56,40 +56,81 @@ function refreshingJsonRequest(address, callback, tabID, skipOldData) { } /** - * Make an XMLHttpRequest for JSON data. + * Make a GET XMLHttpRequest for JSON data. * @param address Address to request from * @param callback function with (json, error) parameters to call after the request. */ function jsonRequest(address, callback) { setTimeout(function () { - const xhr = new XMLHttpRequest(); - xhr.withCredentials = true; - xhr.onreadystatechange = function () { - if (this.readyState === 4) { - try { - if (this.status === 200 || (this.status === 0 && this.responseText)) { - var json = JSON.parse(this.responseText); - setTimeout(function () { - callback(json, null) - }, 0); - } else if (this.status === 404 || this.status === 403 || this.status === 500) { - callback(null, "HTTP " + this.status + " (See " + address + ")") - } else if (this.status === 400) { - const json = JSON.parse(this.responseText); - callback(json, json.error) - } else if (this.status === 0) { - callback(null, "Request did not reach the server. (Server offline / Adblocker?)") - } - } catch (e) { - callback(null, e.message + " (See " + address + ")") - } - } - }; - xhr.timeout = 45000; - xhr.ontimeout = function () { - callback(null, "Timed out after 45 seconds. (" + address + ")") - }; + const xhr = newConfiguredXHR(callback); + xhr.open("GET", address, true); xhr.send(); }, 0); +} + +/** + * Make a POST XMLHttpRequest for JSON data. + * @param address Address to request from + * @param postBody POST body (form). + * @param callback function with (json, error) parameters to call after the request. + */ +function jsonPostRequest(address, postBody, callback) { + setTimeout(function () { + const xhr = newConfiguredXHR(callback, address); + + xhr.open("POST", address, true); + xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + xhr.send(postBody); + }, 0); +} + +/** + * Create new XMLHttpRequest configured for methods such as jsonRequest + * @param callback function with (json, error) parameters to call after the request. + */ +function newConfiguredXHR(callback, address) { + const xhr = new XMLHttpRequest(); + + xhr.withCredentials = true; + xhr.onreadystatechange = function () { + if (this.readyState === 4) { + try { + if (this.status === 200 || (this.status === 0 && this.responseText)) { + var json = JSON.parse(this.responseText); + setTimeout(function () { + callback(json, null) + }, 0); + } else if (this.status === 404 || this.status === 403 || this.status === 500) { + callback(null, "HTTP " + this.status + " (See " + address + ")") + } else if (this.status === 400) { + const json = JSON.parse(this.responseText); + callback(json, json.error) + } else if (this.status === 0) { + callback(null, "Request did not reach the server. (Server offline / Adblocker?)") + } + } catch (e) { + callback(null, e.message) + } + } + }; + xhr.timeout = 45000; + xhr.ontimeout = function () { + callback(null, "Timed out after 45 seconds.") + }; + + return xhr; +} + +function awaitUntil(predicateFunction) { + return new Promise((resolve => { + const handlerFunction = () => { + if (predicateFunction.apply()) { + resolve(); + } else { + setTimeout(handlerFunction, 10) + } + }; + handlerFunction(); + })) } \ No newline at end of file diff --git a/Plan/common/src/main/resources/assets/plan/web/login.html b/Plan/common/src/main/resources/assets/plan/web/login.html index 53e3f0db1..061589014 100644 --- a/Plan/common/src/main/resources/assets/plan/web/login.html +++ b/Plan/common/src/main/resources/assets/plan/web/login.html @@ -1,4 +1,4 @@ - +๏ปฟ @@ -12,7 +12,7 @@ Plan | Login - + @@ -20,8 +20,8 @@ - - + + @@ -41,7 +41,7 @@ if (!password || password.length < 1) { return displayError('You need to specify a Password'); } - jsonRequest(`./auth/login?user=${encodeURIComponent(user)}&password=${encodeURIComponent(password)}`, (json, error) => { + jsonPostRequest(`./auth/login`, `user=${encodeURIComponent(user)}&password=${encodeURIComponent(password)}`, (json, error) => { if (error) { if (error.includes("HTTP 403")) { location.reload(); @@ -74,7 +74,7 @@
logo
-
+
@@ -218,10 +218,10 @@ crossorigin="anonymous"> - - + + - + - + + src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.bundle.min.js"> - - - - - - - - - + + + + + + + + - - - - + + + + - - - - + + + + - + + src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.bundle.min.js"> - - - - - - - - - - + + + + + + + + + - - - + + + - - - + + + - - + + - - - + + + - + + src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.bundle.min.js"> - - - - - + + + + + + - - - - + + + + - - - + + + - - + + - + + src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.bundle.min.js"> - - - - - - - - - - - - + + + + + + + + + + + - - - - + + + + - - - - + + + +
None--
None---
${kill.date}${kill.killer} ${ - kill.killer === kill.victim + ${kill.killerName} ${ + kill.killerUUID === kill.victimUUID ? '' : '' - } ${kill.victim} ${kill.weapon}${kill.serverName}