mirror of
https://github.com/BenCodez/VotingPlugin.git
synced 2026-11-04 09:02:00 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f898d46c95
|
||
|
|
05c0c298ec
|
||
|
|
b6d0d224d8 | ||
|
|
26afe69a95 | ||
|
|
d38e70e012 | ||
|
|
12b77c50ce | ||
|
|
0fbcd8ca11 | ||
|
|
2e68f4e058 | ||
|
|
8b538ad86e | ||
|
|
1d4973f550 | ||
|
|
b13e4f9635 | ||
|
|
b075866f83 | ||
|
|
68896b32c3 | ||
|
|
8e81dc99c5 | ||
|
|
1b202f246f |
No files matched your search
@@ -0,0 +1 @@
|
||||
.gitea/CODEOWNERS linguist-detectable=false
|
||||
@@ -0,0 +1,3 @@
|
||||
# Pull requests request review from the eligible staff team(s).
|
||||
# One approval from either an Admin or Developer satisfies branch protection.
|
||||
* @trueog/Admins @trueog/Developers
|
||||
@@ -1,14 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: BenCodez # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
#patreon: # Replace with a single Patreon username
|
||||
#open_collective: # Replace with a single Open Collective username
|
||||
#ko_fi: # Replace with a single Ko-fi username
|
||||
#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
#liberapay: # Replace with a single Liberapay username
|
||||
#issuehunt: # Replace with a single IssueHunt username
|
||||
#lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
#polar: # Replace with a single Polar username
|
||||
#buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
|
||||
custom: ["https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YQBX25RTXJ5DG"]
|
||||
@@ -1,30 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Bug report for VotingPlugin
|
||||
title: ''
|
||||
labels: Possible Bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Versions**
|
||||
Plugin version and spigot version.
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots/Configs**
|
||||
If applicable, add screenshots to help explain your problem and/or configs relating to the issue.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea
|
||||
title: ''
|
||||
labels: Possible Feature
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -1,17 +0,0 @@
|
||||
---
|
||||
name: Question/Concerns
|
||||
about: Ask about a question or a concern about the plugin
|
||||
title: ''
|
||||
labels: Question/Concern
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Versions**
|
||||
Plugin version and spigot version
|
||||
|
||||
**Relevant information**
|
||||
Configs that relate to the issue in a pastebin.
|
||||
|
||||
**Context**
|
||||
Context about the question/concern
|
||||
@@ -1,11 +0,0 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "maven" # See documentation for possible values
|
||||
directory: "/VotingPlugin/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
@@ -1,41 +0,0 @@
|
||||
# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
|
||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven
|
||||
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support documentation.
|
||||
|
||||
name: Java CI with Maven
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
|
||||
permissions:
|
||||
contents: write # required for dependency submission
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '21'
|
||||
distribution: 'temurin'
|
||||
cache: maven
|
||||
|
||||
- name: Build with Maven
|
||||
run: mvn -B -f VotingPlugin/pom.xml package
|
||||
|
||||
# Only submit dependency snapshot on push (not on pull_request)
|
||||
- name: Submit Dependency Snapshot
|
||||
if: github.event_name == 'push'
|
||||
uses: advanced-security/maven-dependency-submission-action@v5
|
||||
with:
|
||||
directory: VotingPlugin # path to pom.xml
|
||||
# optional: maven-args: "-DskipTests"
|
||||
@@ -1,24 +0,0 @@
|
||||
name: Deploy Javadoc
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Deploy JavaDoc 🚀
|
||||
uses: MathieuSoysal/Javadoc-publisher.yml@main
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
javadoc-branch: gh-pages
|
||||
java-version: 21
|
||||
target-folder: ''
|
||||
project: maven # or gradle
|
||||
# subdirectories: moduleA moduleB #for subdirectories support, needs to be run with custom command
|
||||
custom-command: mvn -f VotingPlugin/pom.xml deploy -P javadoc javadoc:aggregate
|
||||
javadoc-source-folder: 'VotingPlugin/target/reports/apidocs'
|
||||
@@ -1,27 +0,0 @@
|
||||
# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time.
|
||||
#
|
||||
# You can adjust the behavior by modifying this file.
|
||||
# For more information, see:
|
||||
# https://github.com/actions/stale
|
||||
name: Mark stale issues and pull requests
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '19 16 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v5
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: 'Stale issue message'
|
||||
stale-pr-message: 'Stale pull request message'
|
||||
stale-issue-label: 'no-issue-activity'
|
||||
stale-pr-label: 'no-pr-activity'
|
||||
@@ -0,0 +1,5 @@
|
||||
.gradle/
|
||||
.classpath
|
||||
.project
|
||||
.settings/
|
||||
build/
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
---
|
||||
description: Describe what this custom agent does and when to use it.
|
||||
tools: []
|
||||
---
|
||||
Write instructions for this custom agent. These will guide Copilot on how to respond when this mode is selected.
|
||||
@@ -1,9 +0,0 @@
|
||||
/bin/
|
||||
/target/
|
||||
/doc/
|
||||
/.idea/
|
||||
/*.iml
|
||||
/.settings/
|
||||
/.classpath
|
||||
/.factorypath
|
||||
/.apt_generated_tests/
|
||||
@@ -1,33 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>VotingPlugin</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.wst.validation.validationbuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
@@ -1,663 +0,0 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.bencodez</groupId>
|
||||
<artifactId>votingplugin</artifactId>
|
||||
<version>7.1</version>
|
||||
<packaging>jar</packaging>
|
||||
<name>VotingPlugin</name>
|
||||
<properties>
|
||||
<github.global.server>github</github.global.server>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<build.number>NOTSET</build.number>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<maven.compiler.release>21</maven.compiler.release>
|
||||
<velocity.version>3.4.0</velocity.version>
|
||||
</properties>
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
<includes>
|
||||
<include>plugin.yml</include>
|
||||
<include>bungee.yml</include>
|
||||
<include>votingpluginversion.yml</include>
|
||||
</includes>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>false</filtering>
|
||||
<excludes>
|
||||
<exclude>plugin.yml</exclude>
|
||||
<exclude>bungee.yml</exclude>
|
||||
<exclude>votingpluginversion.yml</exclude>
|
||||
</excludes>
|
||||
</resource>
|
||||
</resources>
|
||||
<sourceDirectory>src/main/java</sourceDirectory>
|
||||
<testSourceDirectory>src/test/java</testSourceDirectory>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.eclipse.m2e</groupId>
|
||||
<artifactId>lifecycle-mapping</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<configuration>
|
||||
<lifecycleMappingMetadata>
|
||||
<pluginExecutions>
|
||||
<pluginExecution>
|
||||
<pluginExecutionFilter>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok-maven-plugin</artifactId>
|
||||
<versionRange>[1,)</versionRange>
|
||||
<goals>
|
||||
<goal>delombok</goal>
|
||||
</goals>
|
||||
</pluginExecutionFilter>
|
||||
<action>
|
||||
<ignore />
|
||||
</action>
|
||||
</pluginExecution>
|
||||
</pluginExecutions>
|
||||
</lifecycleMappingMetadata>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
<configuration>
|
||||
<release>21</release>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.42</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>com.velocitypowered</groupId>
|
||||
<artifactId>velocity-api</artifactId>
|
||||
<version>${velocity.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.6.2</version>
|
||||
<configuration>
|
||||
<shadedArtifactAttached>false</shadedArtifactAttached>
|
||||
<minimizeJar>false</minimizeJar>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>mysql-connector-java</artifact>
|
||||
<includes>
|
||||
<include>**</include>
|
||||
</includes>
|
||||
</filter>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/*.SF</exclude>
|
||||
<exclude>META-INF/*.DSA</exclude>
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>com.tcoded.folialib</pattern>
|
||||
<shadedPattern>
|
||||
${project.groupId}.votingplugin.simpleapi.folialib</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>com.bencodez.simpleapi</pattern>
|
||||
<shadedPattern>
|
||||
${project.groupId}.votingplugin.simpleapi</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>com.bencodez.advancedcore</pattern>
|
||||
<shadedPattern>
|
||||
${project.groupId}.votingplugin.advancedcore</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>net.pl3x.bukkit.chatapi</pattern>
|
||||
<shadedPattern>${project.groupId}.votingplugin</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>me.mrten.mysqlapi</pattern>
|
||||
<shadedPattern>
|
||||
${project.groupId}.votingplugin.mysqlapi</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>com.zaxxer.hikari</pattern>
|
||||
<shadedPattern>${project.groupId}.votingplugin.simpleapi.hikari</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.bstats</pattern>
|
||||
<shadedPattern>
|
||||
${project.groupId}.votingplugin.bstats</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>xyz.upperlevel.spigot</pattern>
|
||||
<shadedPattern>
|
||||
${project.groupId}.votingplugin.advancedcore.xyz.upperlevel.spigot</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.spongepowered.configurate</pattern>
|
||||
<shadedPattern>${project.groupId}.simpleapi.configurate</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>io.leangen.geantyref</pattern>
|
||||
<shadedPattern>${project.groupId}.simpleapi.geantyref</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<artifactSet>
|
||||
<!-- Lets exclude google code, because the
|
||||
server already has this
|
||||
code in it (guava, etc) -->
|
||||
<excludes>
|
||||
<exclude>com.google.*:*</exclude>
|
||||
</excludes>
|
||||
</artifactSet>
|
||||
<minimizeJar>false</minimizeJar>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/*.SF</exclude>
|
||||
<exclude>META-INF/*.DSA</exclude>
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
<exclude>module-info.class</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer" />
|
||||
</transformers>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spigot-repo</id>
|
||||
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>maven-central</id>
|
||||
<url>https://oss.sonatype.org/content/groups/public</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>bungeecord-repo</id>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>velocity</id>
|
||||
<url>https://nexus.velocitypowered.com/repository/maven-public/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>bencodez repo</id>
|
||||
<url>https://nexus.bencodez.com/repository/maven-public/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>papermc</id>
|
||||
<url>https://repo.papermc.io/repository/maven-public/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>placeholderapi</id>
|
||||
<url>
|
||||
https://repo.extendedclip.com/content/repositories/placeholderapi/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>scarsz</id>
|
||||
<url>https://nexus.scarsz.me/content/groups/public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>26.1.2-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.42</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-api</artifactId>
|
||||
<version>1.21-R0.4</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<artifactId>nuvotifier-universal</artifactId>
|
||||
<version>2.7.2</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>be.maximvdw</groupId>
|
||||
<artifactId>mvdwplaceholderapi</artifactId>
|
||||
<version>3.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.velocitypowered</groupId>
|
||||
<artifactId>velocity-api</artifactId>
|
||||
<version>${velocity.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bstats</groupId>
|
||||
<artifactId>bstats-velocity</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>me.clip</groupId>
|
||||
<artifactId>placeholderapi</artifactId>
|
||||
<version>2.12.2</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.bencodez</groupId>
|
||||
<artifactId>advancedcore</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<version>5.12.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>5.21.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>5.12.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-junit-jupiter</artifactId>
|
||||
<version>5.23.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.discordsrv</groupId>
|
||||
<artifactId>discordsrv</artifactId>
|
||||
<version>1.30.4</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spongepowered</groupId>
|
||||
<artifactId>configurate-core</artifactId>
|
||||
<version>4.2.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spongepowered</groupId>
|
||||
<artifactId>configurate-yaml</artifactId>
|
||||
<version>4.2.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spongepowered</groupId>
|
||||
<artifactId>configurate-gson</artifactId>
|
||||
<version>4.2.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>default</id>
|
||||
<properties>
|
||||
<build.profile.id>default</build.profile.id>
|
||||
</properties>
|
||||
<activation>
|
||||
<activeByDefault>true</activeByDefault>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.5.0</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
<paperweight-mappings-namespace>mojang</paperweight-mappings-namespace>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
<finalName>${project.name}</finalName>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>deploy-snapshot</id>
|
||||
<properties>
|
||||
<build.profile.id>dev-snapshot</build.profile.id>
|
||||
</properties>
|
||||
<distributionManagement>
|
||||
<snapshotRepository>
|
||||
<id>nexus</id>
|
||||
<url>https://nexus.bencodez.com/repository/maven-snapshots/</url>
|
||||
</snapshotRepository>
|
||||
<repository>
|
||||
<id>nexus</id>
|
||||
<url>https://nexus.bencodez.com/repository/maven-releases/</url>
|
||||
</repository>
|
||||
</distributionManagement>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.5.0</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
<paperweight-mappings-namespace>mojang</paperweight-mappings-namespace>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
<finalName>
|
||||
${project.name}-${build.number}</finalName>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.12.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
<phase>deploy</phase>
|
||||
<configuration>
|
||||
<failOnError>false</failOnError>
|
||||
<doclint>none</doclint>
|
||||
<sourcepath>
|
||||
${project.basedir}/target/delombok</sourcepath>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<version>3.1.4</version>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.sonatype.plugins</groupId>
|
||||
<artifactId>nexus-staging-maven-plugin</artifactId>
|
||||
<version>1.7.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>default-deploy</id>
|
||||
<phase>deploy</phase>
|
||||
<goals>
|
||||
<goal>deploy</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<serverId>nexus</serverId>
|
||||
<nexusUrl>https://nexus.bencodez.com/nexus/</nexusUrl>
|
||||
<skipStaging>true</skipStaging>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>javadoc</id>
|
||||
<properties>
|
||||
<build.profile.id>javadoc</build.profile.id>
|
||||
</properties>
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>internal.repo</id>
|
||||
<name>Temporary Staging Repository</name>
|
||||
<url>file://${project.build.directory}/mvn-repo</url>
|
||||
</repository>
|
||||
</distributionManagement>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.5.0</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
<paperweight-mappings-namespace>mojang</paperweight-mappings-namespace>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
<finalName>${project.name}</finalName>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.12.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
<phase>deploy</phase>
|
||||
<configuration>
|
||||
<sourcepath>
|
||||
${project.basedir}/target/delombok</sourcepath>
|
||||
<outputDirectory>
|
||||
${project.basedir}/target/apidocs</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.coderplus.maven.plugins</groupId>
|
||||
<artifactId>copy-rename-maven-plugin</artifactId>
|
||||
<version>1.0.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-file</id>
|
||||
<phase>deploy</phase>
|
||||
<goals>
|
||||
<goal>copy</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<sourceFile>
|
||||
${project.basedir}/target/${project.name}.jar</sourceFile>
|
||||
<destinationFile>
|
||||
${project.basedir}/target/${project.name}-${project.version}.jar</destinationFile>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>dev</id>
|
||||
<properties>
|
||||
<build.profile.id>dev</build.profile.id>
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.5.0</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
<paperweight-mappings-namespace>mojang</paperweight-mappings-namespace>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
<finalName>${project.name}</finalName>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>3.4.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-resources</id>
|
||||
<phase>install</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>
|
||||
${user.home}/Documents/Test_Server/plugins</outputDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>${basedir}/target</directory>
|
||||
<includes>
|
||||
<include>VotingPlugin.jar</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
<overwrite>true</overwrite>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>deploy</id>
|
||||
<properties>
|
||||
<build.profile.id>deploy</build.profile.id>
|
||||
</properties>
|
||||
<distributionManagement>
|
||||
<snapshotRepository>
|
||||
<id>nexus</id>
|
||||
<url>https://nexus.bencodez.com/repository/maven-snapshots/</url>
|
||||
</snapshotRepository>
|
||||
<repository>
|
||||
<id>nexus</id>
|
||||
<url>https://nexus.bencodez.com/repository/maven-releases/</url>
|
||||
</repository>
|
||||
</distributionManagement>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.5.0</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
<paperweight-mappings-namespace>mojang</paperweight-mappings-namespace>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
<finalName>${project.name}</finalName>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.coderplus.maven.plugins</groupId>
|
||||
<artifactId>copy-rename-maven-plugin</artifactId>
|
||||
<version>1.0.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-file</id>
|
||||
<phase>deploy</phase>
|
||||
<goals>
|
||||
<goal>copy</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<sourceFile>
|
||||
${project.basedir}/target/${project.name}.jar</sourceFile>
|
||||
<destinationFile>
|
||||
${project.basedir}/target/${project.name}-${project.version}.jar</destinationFile>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.12.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
<phase>deploy</phase>
|
||||
<configuration>
|
||||
<sourcepath>
|
||||
${project.basedir}/target/delombok</sourcepath>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<version>3.1.4</version>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.sonatype.plugins</groupId>
|
||||
<artifactId>nexus-staging-maven-plugin</artifactId>
|
||||
<version>1.7.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>default-deploy</id>
|
||||
<phase>deploy</phase>
|
||||
<goals>
|
||||
<goal>deploy</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<serverId>nexus</serverId>
|
||||
<nexusUrl>https://nexus.bencodez.com/nexus/</nexusUrl>
|
||||
<skipStaging>true</skipStaging>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
@@ -1,734 +0,0 @@
|
||||
// File: com/bencodez/votingplugin/BungeeHandler.java
|
||||
package com.bencodez.votingplugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.SQLException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.eclipse.paho.client.mqttv3.MqttException;
|
||||
|
||||
import com.bencodez.advancedcore.api.misc.MiscUtils;
|
||||
import com.bencodez.advancedcore.api.rewards.RewardBuilder;
|
||||
import com.bencodez.advancedcore.api.time.TimeType;
|
||||
import com.bencodez.advancedcore.api.user.UserStorage;
|
||||
import com.bencodez.advancedcore.bungeeapi.globaldata.GlobalDataHandler;
|
||||
import com.bencodez.advancedcore.bungeeapi.globaldata.GlobalMySQL;
|
||||
import com.bencodez.simpleapi.encryption.EncryptionHandler;
|
||||
import com.bencodez.simpleapi.servercomm.codec.JsonEnvelope;
|
||||
import com.bencodez.simpleapi.servercomm.global.GlobalMessageHandler;
|
||||
import com.bencodez.simpleapi.servercomm.global.GlobalMessageListener;
|
||||
import com.bencodez.simpleapi.servercomm.mqtt.MqttHandler;
|
||||
import com.bencodez.simpleapi.servercomm.mqtt.MqttServerComm;
|
||||
import com.bencodez.simpleapi.servercomm.mysql.MySqlMessenger;
|
||||
import com.bencodez.simpleapi.servercomm.pluginmessage.PluginMessageHandler;
|
||||
import com.bencodez.simpleapi.servercomm.redis.RedisHandler;
|
||||
import com.bencodez.simpleapi.servercomm.redis.RedisListener;
|
||||
import com.bencodez.simpleapi.servercomm.sockets.ClientHandler;
|
||||
import com.bencodez.simpleapi.servercomm.sockets.SocketHandler;
|
||||
import com.bencodez.simpleapi.servercomm.sockets.SocketReceiver;
|
||||
import com.bencodez.simpleapi.sql.data.DataValue;
|
||||
import com.bencodez.simpleapi.sql.data.DataValueBoolean;
|
||||
import com.bencodez.simpleapi.sql.mysql.config.MysqlConfigSpigot;
|
||||
import com.bencodez.votingplugin.proxy.BungeeMethod;
|
||||
import com.bencodez.votingplugin.proxy.VoteTotalsSnapshot;
|
||||
import com.bencodez.votingplugin.proxy.VotingPluginWire;
|
||||
import com.bencodez.votingplugin.user.VotingPluginUser;
|
||||
import com.bencodez.votingplugin.votesites.VoteSite;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Handler for Bungee/proxy server integration.
|
||||
*/
|
||||
public class BungeeHandler implements Listener {
|
||||
@Getter
|
||||
private ClientHandler clientHandler;
|
||||
|
||||
private EncryptionHandler encryptionHandler;
|
||||
|
||||
@Getter
|
||||
private BungeeMethod method;
|
||||
|
||||
private VotingPluginMain plugin;
|
||||
|
||||
@Getter
|
||||
private int bungeeVotePartyCurrent = -2;
|
||||
|
||||
@Getter
|
||||
private int bungeeVotePartyRequired = -2;
|
||||
|
||||
@Getter
|
||||
private SocketHandler socketHandler;
|
||||
|
||||
private GlobalDataHandler globalDataHandler;
|
||||
|
||||
@Getter
|
||||
private ScheduledExecutorService timer;
|
||||
|
||||
@Getter
|
||||
private RedisHandler redisHandler;
|
||||
|
||||
@Getter
|
||||
private GlobalMessageHandler globalMessageHandler;
|
||||
|
||||
private Thread redisThread;
|
||||
|
||||
@Getter
|
||||
private MySqlMessenger backendMysqlMessenger;
|
||||
|
||||
@Getter
|
||||
private MqttHandler mqttHandler;
|
||||
|
||||
/**
|
||||
* Constructs a new BungeeHandler.
|
||||
*
|
||||
* @param plugin the main plugin instance
|
||||
*/
|
||||
public BungeeHandler(VotingPluginMain plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks and processes global data from the global data handler.
|
||||
*/
|
||||
public void checkGlobalData() {
|
||||
HashMap<String, DataValue> data = globalDataHandler.getExact(plugin.getBungeeSettings().getServer());
|
||||
|
||||
if (data.containsKey("ForceUpdate")) {
|
||||
boolean b = checkGlobalDataTimeValue(data.get("ForceUpdate"));
|
||||
if (b) {
|
||||
if (plugin.getStorageType().equals(UserStorage.MYSQL)) {
|
||||
plugin.getMysql().clearCacheBasic();
|
||||
}
|
||||
plugin.getUserManager().getDataManager().clearCache();
|
||||
plugin.setUpdate(true);
|
||||
plugin.update();
|
||||
globalDataHandler.setBoolean(plugin.getBungeeSettings().getServer(), "ForceUpdate", false);
|
||||
}
|
||||
}
|
||||
|
||||
boolean forceUpdate = false;
|
||||
|
||||
if (checkGlobalDataTime(TimeType.MONTH, data)) {
|
||||
forceUpdate = true;
|
||||
}
|
||||
if (checkGlobalDataTime(TimeType.WEEK, data)) {
|
||||
forceUpdate = true;
|
||||
}
|
||||
if (checkGlobalDataTime(TimeType.DAY, data)) {
|
||||
forceUpdate = true;
|
||||
}
|
||||
|
||||
if (forceUpdate) {
|
||||
HashMap<String, DataValue> dataToSet = new HashMap<>();
|
||||
dataToSet.put("FinishedProcessing", new DataValueBoolean(true));
|
||||
dataToSet.put("Processing", new DataValueBoolean(false));
|
||||
globalDataHandler.setData(plugin.getBungeeSettings().getServer(), dataToSet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks global data for a time type change.
|
||||
*
|
||||
* @param type the time type to check
|
||||
* @param data the global data map
|
||||
* @return true if currently processing a time change
|
||||
*/
|
||||
public boolean checkGlobalDataTime(TimeType type, HashMap<String, DataValue> data) {
|
||||
boolean isProcessing = false;
|
||||
if (data.containsKey(type.toString())) {
|
||||
|
||||
DataValue value = data.get(type.toString());
|
||||
boolean b = checkGlobalDataTimeValue(value);
|
||||
if (b) {
|
||||
long lastUpdated = Long.valueOf(data.get("LastUpdated").getString()).longValue();
|
||||
plugin.debug("LastUpdated: " + lastUpdated);
|
||||
if (LocalDateTime.now().atZone(ZoneOffset.UTC).toInstant().toEpochMilli() - lastUpdated > 1000 * 60 * 60
|
||||
* 2) {
|
||||
plugin.getLogger().warning("Ignoring bungee time change since it was more than 2 hours ago");
|
||||
globalDataHandler.setBoolean(plugin.getBungeeSettings().getServer(), type.toString(), false);
|
||||
return false;
|
||||
}
|
||||
|
||||
globalDataHandler.setBoolean(plugin.getBungeeSettings().getServer(), "Processing", true);
|
||||
isProcessing = true;
|
||||
|
||||
plugin.debug("Detected time change from bungee: " + type.toString());
|
||||
plugin.getTimeChecker().forceChanged(type, false, true, true);
|
||||
globalDataHandler.setBoolean(plugin.getBungeeSettings().getServer(), type.toString(), false);
|
||||
|
||||
HashMap<String, Object> fields = new HashMap<>();
|
||||
fields.put("server", plugin.getBungeeSettings().getServer());
|
||||
sendSubChannel("TimeChangeFinished", fields);
|
||||
}
|
||||
}
|
||||
return isProcessing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks and extracts the boolean value from a DataValue.
|
||||
*
|
||||
* @param data the data value
|
||||
* @return the boolean value
|
||||
*/
|
||||
public boolean checkGlobalDataTimeValue(DataValue data) {
|
||||
if (data.isBoolean()) {
|
||||
return data.getBoolean();
|
||||
}
|
||||
return Boolean.valueOf(data.getString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes and cleans up all handlers and connections.
|
||||
*/
|
||||
public void close() {
|
||||
if (backendMysqlMessenger != null) {
|
||||
backendMysqlMessenger.shutdown();
|
||||
}
|
||||
|
||||
if (socketHandler != null) {
|
||||
socketHandler.closeConnection();
|
||||
}
|
||||
if (clientHandler != null) {
|
||||
clientHandler.stopConnection();
|
||||
}
|
||||
plugin.getServerData().setBungeeVotePartyCurrent(bungeeVotePartyCurrent);
|
||||
plugin.getServerData().setBungeeVotePartyRequired(bungeeVotePartyRequired);
|
||||
if (globalDataHandler != null) {
|
||||
globalDataHandler.getGlobalMysql().close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and initializes the bungee handler with the configured method.
|
||||
*/
|
||||
public void load() {
|
||||
plugin.debug("Loading bungee handler");
|
||||
|
||||
method = BungeeMethod.getByName(plugin.getBungeeSettings().getBungeeMethod());
|
||||
|
||||
plugin.getLogger().info("Using BungeeMethod: " + method.toString());
|
||||
|
||||
loadGlobalMysql();
|
||||
|
||||
globalMessageHandler = new GlobalMessageHandler() {
|
||||
@Override
|
||||
public void sendMessage(JsonEnvelope envelope) {
|
||||
if (method.equals(BungeeMethod.MYSQL)) {
|
||||
try {
|
||||
backendMysqlMessenger.sendToProxy(envelope);
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else if (method.equals(BungeeMethod.PLUGINMESSAGING)) {
|
||||
plugin.getPluginMessaging().sendEnvelope(envelope);
|
||||
} else if (method.equals(BungeeMethod.SOCKETS)) {
|
||||
sendEnvelopeSocket(envelope);
|
||||
} else if (method.equals(BungeeMethod.REDIS)) {
|
||||
redisHandler.publishEnvelope(plugin.getBungeeSettings().getRedisPrefix() + "VotingPlugin",
|
||||
envelope);
|
||||
} else if (method.equals(BungeeMethod.MQTT)) {
|
||||
try {
|
||||
mqttHandler.publishEnvelope(
|
||||
plugin.getBungeeSettings().getMqttPrefix() + "votingplugin/servers/proxy", envelope);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ==========================
|
||||
// Vote / VoteOnline (wire decode)
|
||||
// ==========================
|
||||
|
||||
globalMessageHandler.addListener(new GlobalMessageListener(VotingPluginWire.SUB_VOTE) {
|
||||
@Override
|
||||
public void onReceive(JsonEnvelope msg) {
|
||||
handleWireVote(msg);
|
||||
}
|
||||
});
|
||||
|
||||
globalMessageHandler.addListener(new GlobalMessageListener(VotingPluginWire.SUB_VOTE_ONLINE) {
|
||||
@Override
|
||||
public void onReceive(JsonEnvelope msg) {
|
||||
handleWireVote(msg);
|
||||
}
|
||||
});
|
||||
|
||||
globalMessageHandler.addListener(new GlobalMessageListener(VotingPluginWire.SUB_VOTE_UPDATE) {
|
||||
@Override
|
||||
public void onReceive(JsonEnvelope msg) {
|
||||
// Wire decode
|
||||
VotingPluginWire.VoteUpdate v = VotingPluginWire.readVoteUpdate(msg);
|
||||
|
||||
String playerUuid = v.uuid;
|
||||
if (playerUuid == null || playerUuid.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.debug("pluginmessaging voteupdate received for " + playerUuid + ": " + v.votePartyCurrent + "/"
|
||||
+ v.votePartyRequired + " on " + v.service);
|
||||
|
||||
// Vote party cache update
|
||||
if (v.votePartyCurrent >= 0 || bungeeVotePartyCurrent == -2) {
|
||||
bungeeVotePartyCurrent = v.votePartyCurrent;
|
||||
}
|
||||
if (v.votePartyRequired >= 0 || bungeeVotePartyRequired == -2) {
|
||||
bungeeVotePartyRequired = v.votePartyRequired;
|
||||
}
|
||||
plugin.getServerData().setBungeeVotePartyCurrent(bungeeVotePartyCurrent);
|
||||
plugin.getServerData().setBungeeVotePartyRequired(bungeeVotePartyRequired);
|
||||
|
||||
VotingPluginUser user = plugin.getVotingPluginUserManager()
|
||||
.getVotingPluginUser(UUID.fromString(playerUuid));
|
||||
user.cache();
|
||||
|
||||
user.offVote();
|
||||
|
||||
// Optional: update last vote time for a service
|
||||
String service = v.service;
|
||||
long time = v.time;
|
||||
|
||||
if (service != null && !service.isEmpty() && time > 0) {
|
||||
user.setTime(plugin.getVoteSiteManager().getVoteSite(service, true), time);
|
||||
} else if (service != null && !service.isEmpty() && time <= 0
|
||||
&& plugin.getBungeeSettings().isBungeeDebug()) {
|
||||
plugin.debug("Invalid last vote time received from bungee: " + time);
|
||||
}
|
||||
|
||||
plugin.setUpdate(true);
|
||||
}
|
||||
});
|
||||
|
||||
globalMessageHandler.addListener(new GlobalMessageListener(VotingPluginWire.SUB_BUNGEE_TIME_CHANGE) {
|
||||
@Override
|
||||
public void onReceive(JsonEnvelope msg) {
|
||||
checkGlobalData();
|
||||
}
|
||||
});
|
||||
|
||||
globalMessageHandler.addListener(new GlobalMessageListener(VotingPluginWire.SUB_VOTE_BROADCAST) {
|
||||
@Override
|
||||
public void onReceive(JsonEnvelope msg) {
|
||||
Map<String, String> f = msg.getFields();
|
||||
|
||||
final String uuidStr = nvl(f.get(VotingPluginWire.K_UUID));
|
||||
final String playerNameRaw = nvl(f.get(VotingPluginWire.K_PLAYER));
|
||||
final String service = nvl(f.get(VotingPluginWire.K_SERVICE));
|
||||
|
||||
if (uuidStr.isEmpty() || service.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
UUID javaUuid;
|
||||
try {
|
||||
javaUuid = UUID.fromString(uuidStr);
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning("Invalid UUID in VoteBroadcast: " + uuidStr);
|
||||
return;
|
||||
}
|
||||
|
||||
// New fields (May use later)
|
||||
@SuppressWarnings("unused")
|
||||
final long time = readLongSafe(f.get(VotingPluginWire.K_TIME), 0L);
|
||||
@SuppressWarnings("unused")
|
||||
final String totals = nvl(f.get(VotingPluginWire.K_TOTALS));
|
||||
|
||||
VoteSite voteSite = plugin.getVoteSiteManager()
|
||||
.getVoteSite(plugin.getVoteSiteManager().getVoteSiteName(true, service), true);
|
||||
|
||||
if (voteSite == null) {
|
||||
plugin.getLogger().warning("No voting site with the service site: '" + service + "'");
|
||||
return;
|
||||
}
|
||||
if (!voteSite.isEnabled()) {
|
||||
plugin.debug("Votesite: " + voteSite.getKey() + " is not enabled (VoteBroadcast)");
|
||||
return;
|
||||
}
|
||||
|
||||
// Same user retrieval strategy: UUID + (possibly empty) name
|
||||
VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(javaUuid,
|
||||
playerNameRaw);
|
||||
|
||||
// Keep cache/name current like normal vote path does
|
||||
user.cache();
|
||||
user.updateName(true);
|
||||
|
||||
// Same broadcast logic as PlayerVoteListener
|
||||
if (plugin.getBroadcastHandler() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (user.isVanished()) {
|
||||
plugin.debug("Not broadcasting vote for vanished user: " + user.getPlayerName());
|
||||
return;
|
||||
}
|
||||
|
||||
final boolean online = user.isOnline(); // same behavior as non-bungee vote path
|
||||
plugin.getBroadcastHandler().broadcastVote(user.getJavaUUID(), user.getPlayerName(),
|
||||
voteSite.getDisplayName(), online);
|
||||
}
|
||||
});
|
||||
|
||||
globalMessageHandler.addListener(new GlobalMessageListener("Status") {
|
||||
@Override
|
||||
public void onReceive(JsonEnvelope msg) {
|
||||
String server = nvl(msg.getFields().get("server"));
|
||||
HashMap<String, Object> out = new HashMap<>();
|
||||
out.put("server", server);
|
||||
sendSubChannel("statusokay", out);
|
||||
}
|
||||
});
|
||||
|
||||
globalMessageHandler.addListener(new GlobalMessageListener("ServerName") {
|
||||
@Override
|
||||
public void onReceive(JsonEnvelope msg) {
|
||||
String server = nvl(msg.getFields().get("server"));
|
||||
if (!plugin.getOptions().getServer().equals(server)) {
|
||||
plugin.getLogger().warning("Server name doesn't match in BungeeSettings.yml, should be " + server);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
globalMessageHandler.addListener(new GlobalMessageListener("VotePartyBungee") {
|
||||
@Override
|
||||
public void onReceive(JsonEnvelope msg) {
|
||||
for (final String cmd : plugin.getBungeeSettings().getBungeeVotePartyGlobalCommands()) {
|
||||
plugin.getBukkitScheduler().runTask(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), cmd);
|
||||
}
|
||||
});
|
||||
}
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
new RewardBuilder(plugin.getBungeeSettings().getData(), "BungeeVotePartyRewards").send(p);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
globalMessageHandler.addListener(new GlobalMessageListener("VotePartyBroadcast") {
|
||||
@Override
|
||||
public void onReceive(JsonEnvelope msg) {
|
||||
String broadcast = nvl(msg.getFields().get("broadcast"));
|
||||
MiscUtils.getInstance().broadcast(broadcast);
|
||||
}
|
||||
});
|
||||
|
||||
if (method.equals(BungeeMethod.MYSQL)) {
|
||||
plugin.registerBungeeChannels(plugin.getBungeeSettings().getPluginMessagingChannel());
|
||||
|
||||
try {
|
||||
backendMysqlMessenger = new MySqlMessenger("VotingPlugin",
|
||||
plugin.getMysql().getMysql().getConnectionManager().getDataSource(),
|
||||
MySqlMessenger.Mode.BACKEND, plugin.getOptions().getServer(), msg -> {
|
||||
if (plugin.getBungeeSettings().isBungeeDebug()) {
|
||||
plugin.debug("Proxy sent envelope: " + msg.envelope.getSubChannel() + " "
|
||||
+ msg.envelope.getFields());
|
||||
}
|
||||
globalMessageHandler.onMessage(msg.envelope);
|
||||
});
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else if (method.equals(BungeeMethod.REDIS)) {
|
||||
redisHandler = new RedisHandler(plugin.getBungeeSettings().getRedisHost(),
|
||||
plugin.getBungeeSettings().getRedisPort(), plugin.getBungeeSettings().getRedisUsername(),
|
||||
plugin.getBungeeSettings().getRedisPassword(), plugin.getBungeeSettings().getRedisdbindex()) {
|
||||
|
||||
@Override
|
||||
public void debug(String message) {
|
||||
if (plugin.getBungeeSettings().isBungeeDebug()) {
|
||||
plugin.debug(message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
redisThread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (plugin.isEnabled()) {
|
||||
RedisListener listener = redisHandler.createEnvelopeListener(
|
||||
plugin.getBungeeSettings().getRedisPrefix() + "VotingPlugin_"
|
||||
+ plugin.getBungeeSettings().getServer(),
|
||||
(ch, env) -> globalMessageHandler.onMessage(env));
|
||||
redisHandler.loadListener(listener);
|
||||
}
|
||||
}
|
||||
});
|
||||
redisThread.start();
|
||||
|
||||
} else if (method.equals(BungeeMethod.PLUGINMESSAGING)) {
|
||||
plugin.registerBungeeChannels(plugin.getBungeeSettings().getPluginMessagingChannel());
|
||||
|
||||
if (plugin.getBungeeSettings().isPluginMessageEncryption()) {
|
||||
encryptionHandler = new EncryptionHandler(plugin.getName(),
|
||||
new File(plugin.getDataFolder(), "secretkey.key"));
|
||||
plugin.getPluginMessaging().setEncryptionHandler(encryptionHandler);
|
||||
}
|
||||
|
||||
plugin.getPluginMessaging().setDebug(plugin.getBungeeSettings().isBungeeDebug());
|
||||
|
||||
plugin.getPluginMessaging().add(new PluginMessageHandler() {
|
||||
@Override
|
||||
public void onReceive(JsonEnvelope envelope) {
|
||||
globalMessageHandler.onMessage(envelope);
|
||||
}
|
||||
});
|
||||
|
||||
} else if (method.equals(BungeeMethod.SOCKETS)) {
|
||||
encryptionHandler = new EncryptionHandler(plugin.getName(),
|
||||
new File(plugin.getDataFolder(), "secretkey.key"));
|
||||
|
||||
clientHandler = new ClientHandler(plugin.getBungeeSettings().getBungeeServerHost(),
|
||||
plugin.getBungeeSettings().getBungeeServerPort(), encryptionHandler,
|
||||
plugin.getBungeeSettings().isBungeeDebug());
|
||||
|
||||
socketHandler = new SocketHandler("vp-socket", plugin.getBungeeSettings().getSpigotServerHost(),
|
||||
plugin.getBungeeSettings().getSpigotServerPort(), encryptionHandler,
|
||||
plugin.getBungeeSettings().isBungeeDebug()) {
|
||||
|
||||
@Override
|
||||
public void log(String str) {
|
||||
plugin.getLogger().info(str);
|
||||
}
|
||||
};
|
||||
|
||||
socketHandler.add(new SocketReceiver() {
|
||||
@Override
|
||||
public void onReceiveEnvelope(JsonEnvelope envelope) {
|
||||
globalMessageHandler.onMessage(envelope);
|
||||
}
|
||||
});
|
||||
|
||||
} else if (method.equals(BungeeMethod.MQTT)) {
|
||||
try {
|
||||
String id = plugin.getBungeeSettings().getMqttClientID();
|
||||
if (id.isEmpty()) {
|
||||
id = plugin.getOptions().getServer();
|
||||
}
|
||||
mqttHandler = new MqttHandler(new MqttServerComm(id, plugin.getBungeeSettings().getMqttBrokerURL(),
|
||||
plugin.getBungeeSettings().getMqttUsername(), plugin.getBungeeSettings().getMqttPassword()), 2);
|
||||
|
||||
mqttHandler.subscribeEnvelopes(
|
||||
plugin.getBungeeSettings().getMqttPrefix() + "votingplugin/servers/"
|
||||
+ plugin.getOptions().getServer(),
|
||||
(topic, envelope) -> globalMessageHandler.onMessage(envelope));
|
||||
|
||||
} catch (MqttException e) {
|
||||
e.printStackTrace();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
bungeeVotePartyCurrent = plugin.getServerData().getBungeeVotePartyCurrent();
|
||||
bungeeVotePartyRequired = plugin.getServerData().getBungeeVotePartyRequired();
|
||||
|
||||
if (plugin.getOptions().getServer().equalsIgnoreCase("pleaseset")) {
|
||||
plugin.getLogger().warning("Server name for bungee voting is not set, please set it");
|
||||
}
|
||||
}
|
||||
|
||||
private static long readLongSafe(String v, long def) {
|
||||
if (v == null)
|
||||
return def;
|
||||
try {
|
||||
return Long.parseLong(v);
|
||||
} catch (Exception ignored) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wire vote handler (Vote + VoteOnline).
|
||||
*/
|
||||
private void handleWireVote(JsonEnvelope msg) {
|
||||
// Strict schema check (wire uses envelope schema, not a "bungeeVersion" field)
|
||||
int schema = msg.getSchema();
|
||||
if (schema != VotingPluginWire.SCHEMA_VERSION) {
|
||||
plugin.getLogger().warning("Incompatible version with bungee/proxy, please update all servers: " + schema
|
||||
+ " != " + VotingPluginWire.SCHEMA_VERSION);
|
||||
return;
|
||||
}
|
||||
|
||||
VotingPluginWire.Vote v = VotingPluginWire.readVote(msg);
|
||||
|
||||
String uuidStr = v.uuid;
|
||||
String player = v.player;
|
||||
String service = v.service;
|
||||
|
||||
if (uuidStr == null || uuidStr.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.debug("wire vote received from " + player + "/" + uuidStr + " on " + service);
|
||||
|
||||
VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(UUID.fromString(uuidStr),
|
||||
player);
|
||||
|
||||
VoteTotalsSnapshot text = VoteTotalsSnapshot.parseStorage(v.totals == null ? "" : v.totals);
|
||||
|
||||
bungeeVotePartyCurrent = text.getVotePartyCurrent();
|
||||
bungeeVotePartyRequired = text.getVotePartyRequired();
|
||||
plugin.getServerData().setBungeeVotePartyCurrent(bungeeVotePartyCurrent);
|
||||
plugin.getServerData().setBungeeVotePartyRequired(bungeeVotePartyRequired);
|
||||
|
||||
user.cache();
|
||||
|
||||
boolean bungeeBroadcast = v.broadcast;
|
||||
|
||||
// Wire has no "setTotals" flag; treat as always true (so doNotSetTotals =
|
||||
// false)
|
||||
boolean doNotSetTotals = false;
|
||||
|
||||
user.bungeeVotePluginMessaging(service, v.time, text, doNotSetTotals, v.wasOnline, bungeeBroadcast, v.num);
|
||||
|
||||
if (plugin.getBungeeSettings().isPerServerPoints()) {
|
||||
user.addPoints(plugin.getConfigFile().getPointsOnVote());
|
||||
}
|
||||
|
||||
if (v.manageTotals) {
|
||||
plugin.getServerData().addServiceSite(service);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
int _ignored = v.numberOfVotes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the global MySQL handler for cross-server data synchronization.
|
||||
*/
|
||||
public void loadGlobalMysql() {
|
||||
if (plugin.getBungeeSettings().isGloblalDataEnabled()) {
|
||||
if (timer != null) {
|
||||
timer.shutdown();
|
||||
try {
|
||||
timer.awaitTermination(5, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
timer.shutdownNow();
|
||||
}
|
||||
timer = Executors.newScheduledThreadPool(1);
|
||||
timer.scheduleWithFixedDelay(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
checkGlobalData();
|
||||
}
|
||||
}, 60, 10, TimeUnit.SECONDS);
|
||||
timer.scheduleWithFixedDelay(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
globalDataHandler.setString(plugin.getBungeeSettings().getServer(), "LastOnline",
|
||||
"" + LocalDateTime.now().atZone(ZoneOffset.UTC).toInstant().toEpochMilli());
|
||||
}
|
||||
}, 1, 60, TimeUnit.MINUTES);
|
||||
|
||||
if (globalDataHandler != null) {
|
||||
globalDataHandler.getGlobalMysql().close();
|
||||
}
|
||||
|
||||
if (plugin.getBungeeSettings().isGloblalDataUseMainMySQL()
|
||||
&& plugin.getStorageType().equals(UserStorage.MYSQL)) {
|
||||
globalDataHandler = new GlobalDataHandler(
|
||||
new GlobalMySQL("VotingPlugin_GlobalData", plugin.getMysql().getMysql()) {
|
||||
@Override
|
||||
public void debugEx(Exception e) {
|
||||
plugin.debug(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debugLog(String text) {
|
||||
plugin.debug(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String text) {
|
||||
plugin.getLogger().info(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logSevere(String text) {
|
||||
plugin.getLogger().severe(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warning(String text) {
|
||||
plugin.getLogger().warning(text);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
globalDataHandler = new GlobalDataHandler(
|
||||
new GlobalMySQL("VotingPlugin_GlobalData", new MysqlConfigSpigot(
|
||||
plugin.getBungeeSettings().getData().getConfigurationSection("GlobalData"))) {
|
||||
@Override
|
||||
public void debugEx(Exception e) {
|
||||
plugin.debug(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debugLog(String text) {
|
||||
plugin.debug(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String text) {
|
||||
plugin.getLogger().info(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logSevere(String text) {
|
||||
plugin.getLogger().severe(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warning(String text) {
|
||||
plugin.getLogger().warning(text);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
globalDataHandler.getGlobalMysql().alterColumnType("IgnoreTime", "VARCHAR(5)");
|
||||
globalDataHandler.getGlobalMysql().alterColumnType("MONTH", "VARCHAR(5)");
|
||||
globalDataHandler.getGlobalMysql().alterColumnType("WEEK", "VARCHAR(5)");
|
||||
globalDataHandler.getGlobalMysql().alterColumnType("DAY", "VARCHAR(5)");
|
||||
globalDataHandler.getGlobalMysql().alterColumnType("FinishedProcessing", "VARCHAR(5)");
|
||||
globalDataHandler.getGlobalMysql().alterColumnType("Processing", "VARCHAR(5)");
|
||||
globalDataHandler.getGlobalMysql().alterColumnType("LastUpdated", "MEDIUMTEXT");
|
||||
globalDataHandler.getGlobalMysql().alterColumnType("ForceUpdate", "VARCHAR(5)");
|
||||
plugin.getTimeChecker().setProcessingEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendEnvelopeSocket(JsonEnvelope envelope) {
|
||||
if (clientHandler != null) {
|
||||
clientHandler.sendEnvelope(envelope);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendSubChannel(String subChannel, HashMap<String, Object> fields) {
|
||||
JsonEnvelope.Builder b = JsonEnvelope.builder(subChannel).schema(VotingPluginWire.SCHEMA_VERSION);
|
||||
if (fields != null) {
|
||||
for (Map.Entry<String, Object> e : fields.entrySet()) {
|
||||
b.put(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
globalMessageHandler.sendMessage(b.build());
|
||||
}
|
||||
|
||||
private static String nvl(String s) {
|
||||
return s == null ? "" : s;
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
package com.bencodez.votingplugin.broadcast;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
/**
|
||||
* Defines HOW vote broadcasts are rendered.
|
||||
*
|
||||
* Terminology:
|
||||
* - BroadcastMsg : used when EXACTLY ONE item exists (single-site vote)
|
||||
* - Header : used ONCE when MULTIPLE items exist (batch/interval)
|
||||
* - ListLine : used ONCE PER ITEM when MULTIPLE items exist
|
||||
*
|
||||
* Placeholders (always available):
|
||||
* - %player% : player name (or "Server" for interval summaries)
|
||||
* - %site% : the current site/item for this line
|
||||
* - %sites_count% : number of items in the list
|
||||
* - %sites% : csv of items in the list
|
||||
* - %reason% : internal reason string ("vote", "batch", "interval", etc)
|
||||
*
|
||||
* Extra context placeholders (INTERVAL_SUMMARY_GLOBAL):
|
||||
* - %players% / %numberofplayers%
|
||||
* - %sites% / %numberofsites% (note: %sites% is csv; %numberofsites% is count)
|
||||
*/
|
||||
public final class BroadcastFormat {
|
||||
|
||||
private final String broadcastMsg;
|
||||
private final String header;
|
||||
private final String listLine;
|
||||
|
||||
/**
|
||||
* Constructor for BroadcastFormat.
|
||||
*
|
||||
* @param broadcastMsg the broadcast message template
|
||||
* @param header the header template
|
||||
* @param listLine the list line template
|
||||
*/
|
||||
public BroadcastFormat(String broadcastMsg, String header, String listLine) {
|
||||
this.broadcastMsg = broadcastMsg == null ? "" : broadcastMsg;
|
||||
this.header = header == null ? "" : header;
|
||||
this.listLine = listLine == null ? "" : listLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the broadcast with player name, items, and reason.
|
||||
*
|
||||
* @param playerName the player name
|
||||
* @param items the list of items
|
||||
* @param reason the reason string
|
||||
* @return the rendered list of strings
|
||||
*/
|
||||
public List<String> render(String playerName, List<String> items, String reason) {
|
||||
return render(playerName, items, reason, Collections.<String, String>emptyMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the broadcast with player name, items, reason, and context.
|
||||
*
|
||||
* @param playerName the player name
|
||||
* @param items the list of items
|
||||
* @param reason the reason string
|
||||
* @param context additional context placeholders
|
||||
* @return the rendered list of strings
|
||||
*/
|
||||
public List<String> render(String playerName, List<String> items, String reason, Map<String, String> context) {
|
||||
List<String> output = new ArrayList<String>();
|
||||
int count = items == null ? 0 : items.size();
|
||||
|
||||
String csv = "";
|
||||
if (count > 0) {
|
||||
StringJoiner joiner = new StringJoiner(", ");
|
||||
for (String s : items) {
|
||||
if (s != null && !s.isEmpty()) {
|
||||
joiner.add(s);
|
||||
}
|
||||
}
|
||||
csv = joiner.toString();
|
||||
}
|
||||
|
||||
// SINGLE ITEM → BroadcastMsg
|
||||
if (count == 1 && !broadcastMsg.isEmpty()) {
|
||||
output.add(apply(broadcastMsg, playerName, items.get(0), count, csv, reason, context));
|
||||
return output;
|
||||
}
|
||||
|
||||
// MULTI ITEM → Header + ListLine per item
|
||||
if (!header.isEmpty()) {
|
||||
output.add(apply(header, playerName, "", count, csv, reason, context));
|
||||
}
|
||||
|
||||
if (count > 0 && !listLine.isEmpty()) {
|
||||
for (String s : items) {
|
||||
output.add(apply(listLine, playerName, s, count, csv, reason, context));
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private String apply(String template,
|
||||
String player,
|
||||
String item,
|
||||
int count,
|
||||
String csv,
|
||||
String reason,
|
||||
Map<String, String> context) {
|
||||
|
||||
String out = template
|
||||
.replace("%player%", player == null ? "" : player)
|
||||
.replace("%site%", item == null ? "" : item)
|
||||
.replace("%sites_count%", String.valueOf(count))
|
||||
.replace("%sites%", csv == null ? "" : csv)
|
||||
.replace("%reason%", reason == null ? "" : reason);
|
||||
|
||||
if (context != null && !context.isEmpty()) {
|
||||
for (Map.Entry<String, String> e : context.entrySet()) {
|
||||
out = out.replace("%" + e.getKey() + "%", e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
||||
@@ -1,443 +0,0 @@
|
||||
package com.bencodez.votingplugin.broadcast;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import com.bencodez.advancedcore.api.misc.MiscUtils;
|
||||
import com.bencodez.simpleapi.messages.MessageAPI;
|
||||
import com.bencodez.simpleapi.time.ParsedDuration;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.user.VotingPluginUser;
|
||||
|
||||
/**
|
||||
* Core broadcast logic.
|
||||
*
|
||||
* Decides WHEN to broadcast and builds lines/messages. Uses your existing
|
||||
* "eligible online players" behavior (disable broadcast respected).
|
||||
*
|
||||
* NOTE: Backend servers only.
|
||||
*/
|
||||
public final class BroadcastHandler {
|
||||
|
||||
private final VotingPluginMain plugin;
|
||||
|
||||
/**
|
||||
* Only used for day-boundary logic (FIRST_VOTE_OF_DAY).
|
||||
*/
|
||||
private final ZoneId zoneId;
|
||||
|
||||
private volatile BroadcastSettings settings;
|
||||
|
||||
// Per-player cooldown tracking
|
||||
private final ConcurrentHashMap<UUID, Instant> lastBroadcastAt = new ConcurrentHashMap<UUID, Instant>();
|
||||
|
||||
// Batch window tracking
|
||||
private final ConcurrentHashMap<UUID, LinkedHashSet<String>> pendingSites = new ConcurrentHashMap<UUID, LinkedHashSet<String>>();
|
||||
private final ConcurrentHashMap<UUID, BukkitTask> pendingFlush = new ConcurrentHashMap<UUID, BukkitTask>();
|
||||
|
||||
// First vote of day tracking
|
||||
private final ConcurrentHashMap<UUID, LocalDate> firstVoteDay = new ConcurrentHashMap<UUID, LocalDate>();
|
||||
|
||||
// Interval summary tracking (uuid -> set of sites during interval)
|
||||
private final ConcurrentHashMap<UUID, LinkedHashSet<String>> intervalSites = new ConcurrentHashMap<UUID, LinkedHashSet<String>>();
|
||||
private volatile BukkitTask intervalTask;
|
||||
|
||||
/**
|
||||
* Constructs a new BroadcastHandler.
|
||||
*
|
||||
* @param plugin the main plugin instance
|
||||
* @param settings the broadcast settings
|
||||
* @param zoneId the time zone ID, or null to use system default
|
||||
*/
|
||||
public BroadcastHandler(VotingPluginMain plugin, BroadcastSettings settings, ZoneId zoneId) {
|
||||
this.plugin = plugin;
|
||||
this.settings = settings;
|
||||
this.zoneId = zoneId == null ? ZoneId.systemDefault() : zoneId;
|
||||
|
||||
rescheduleIntervalIfNeeded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the broadcast settings and reschedules interval task if needed.
|
||||
*
|
||||
* @param settings the new broadcast settings
|
||||
*/
|
||||
public void setSettings(BroadcastSettings settings) {
|
||||
this.settings = settings;
|
||||
rescheduleIntervalIfNeeded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Call whenever a vote is received.
|
||||
*
|
||||
* @param uuid player's uuid
|
||||
* @param playerName player name (optional; if null/empty, resolved from Bukkit)
|
||||
* @param siteName vote site name (display name)
|
||||
* @param wasOnline whether the player was online when the vote was received
|
||||
*/
|
||||
public void broadcastVote(UUID uuid, String playerName, String siteName, boolean wasOnline) {
|
||||
BroadcastSettings s = settings;
|
||||
if (s == null || s.isDisabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Always collect for interval summaries (even if Type isn't interval right
|
||||
// now).
|
||||
recordInterval(uuid, siteName);
|
||||
|
||||
String name = playerName == null || playerName.isEmpty() ? resolveName(uuid) : playerName;
|
||||
|
||||
VoteBroadcastType type = s.getType();
|
||||
|
||||
// New: types that only broadcast if the voting player is online
|
||||
if (type == VoteBroadcastType.EVERY_VOTE_ONLINE_ONLY) {
|
||||
if (!wasOnline) {
|
||||
return;
|
||||
}
|
||||
broadcastNow(name, single(siteName), "vote_online_only", null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == VoteBroadcastType.EVERY_VOTE) {
|
||||
broadcastNow(name, single(siteName), "vote", null);
|
||||
|
||||
} else if (type == VoteBroadcastType.COOLDOWN_PER_PLAYER) {
|
||||
if (checkAndMarkCooldown(uuid, s.getDuration())) {
|
||||
broadcastNow(name, single(siteName), "cooldown", null);
|
||||
}
|
||||
|
||||
} else if (type == VoteBroadcastType.BATCH_WINDOW_PER_PLAYER) {
|
||||
bufferBatch(uuid, name, siteName, s.getDuration());
|
||||
|
||||
} else if (type == VoteBroadcastType.FIRST_VOTE_OF_DAY) {
|
||||
if (markFirstVoteOfDay(uuid)) {
|
||||
broadcastNow(name, single(siteName), "first_day", null);
|
||||
}
|
||||
|
||||
} else if (type == VoteBroadcastType.INTERVAL_SUMMARY_GLOBAL) {
|
||||
// handled by scheduled task
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ======================= Broadcast helpers =======================
|
||||
*/
|
||||
|
||||
private void broadcastNow(String playerName, List<String> sites, String reason, Map<String, String> extraContext) {
|
||||
BroadcastSettings s = settings;
|
||||
if (s == null || s.isDisabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> cleaned = applyMaxSites(sites, s);
|
||||
cleaned.sort(new Comparator<String>() {
|
||||
@Override
|
||||
public int compare(String a, String b) {
|
||||
String aa = a == null ? "" : a.toLowerCase(Locale.ROOT);
|
||||
String bb = b == null ? "" : b.toLowerCase(Locale.ROOT);
|
||||
return aa.compareTo(bb);
|
||||
}
|
||||
});
|
||||
|
||||
List<String> lines;
|
||||
if (extraContext == null) {
|
||||
lines = s.getFormat().render(playerName, cleaned, reason);
|
||||
} else {
|
||||
lines = s.getFormat().render(playerName, cleaned, reason, extraContext);
|
||||
}
|
||||
|
||||
for (String line : lines) {
|
||||
broadcastToEligiblePlayers(line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Your reliable broadcast style: - only sends to online players - respects
|
||||
* per-player disable broadcast toggle - runs PlaceholderAPI style per-player
|
||||
* placeholders (if your PlaceholderUtils does) - supports %newline% / %NewLine%
|
||||
* splitting
|
||||
*/
|
||||
private void broadcastToEligiblePlayers(String broadcastMsg) {
|
||||
if (broadcastMsg == null || broadcastMsg.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayList<Player> players = new ArrayList<Player>();
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
VotingPluginUser u = plugin.getVotingPluginUserManager().getVotingPluginUser(p);
|
||||
if (u != null && !u.getDisableBroadcast()) {
|
||||
players.add(p);
|
||||
}
|
||||
}
|
||||
|
||||
// If you prefer your manual loop, keep it here (not in main)
|
||||
MiscUtils.getInstance().broadcast(broadcastMsg, players);
|
||||
|
||||
Bukkit.getServer().getConsoleSender().sendMessage(MessageAPI.colorize(broadcastMsg));
|
||||
}
|
||||
|
||||
/*
|
||||
* ======================= Cooldown logic (fixed millis) =======================
|
||||
*/
|
||||
|
||||
private boolean checkAndMarkCooldown(UUID uuid, ParsedDuration cooldown) {
|
||||
if (cooldown == null || cooldown.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Instant now = Instant.now();
|
||||
Instant last = lastBroadcastAt.get(uuid);
|
||||
|
||||
if (last == null) {
|
||||
lastBroadcastAt.put(uuid, now);
|
||||
return true;
|
||||
}
|
||||
|
||||
Instant next = cooldown.addTo(last);
|
||||
if (!now.isBefore(next)) {
|
||||
lastBroadcastAt.put(uuid, now);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* ======================= Batch window logic (fixed millis)
|
||||
* =======================
|
||||
*/
|
||||
|
||||
private void bufferBatch(UUID uuid, String playerName, String siteName, ParsedDuration window) {
|
||||
if (siteName != null && !siteName.isEmpty()) {
|
||||
LinkedHashSet<String> set = pendingSites.get(uuid);
|
||||
if (set == null) {
|
||||
LinkedHashSet<String> created = new LinkedHashSet<String>();
|
||||
set = pendingSites.putIfAbsent(uuid, created);
|
||||
if (set == null) {
|
||||
set = created;
|
||||
}
|
||||
}
|
||||
synchronized (set) {
|
||||
set.add(siteName);
|
||||
}
|
||||
}
|
||||
|
||||
if (!pendingFlush.containsKey(uuid)) {
|
||||
BukkitTask task = scheduleBatchFlush(uuid, playerName, window);
|
||||
BukkitTask prev = pendingFlush.putIfAbsent(uuid, task);
|
||||
if (prev != null) {
|
||||
task.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BukkitTask scheduleBatchFlush(final UUID uuid, final String playerName, ParsedDuration window) {
|
||||
long delayMs = window == null ? 1L : window.delayMillisFromNow();
|
||||
long ticks = Math.max(1L, delayMs / 50L);
|
||||
|
||||
return Bukkit.getScheduler().runTaskLater(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
flushBatch(uuid, playerName);
|
||||
} finally {
|
||||
pendingFlush.remove(uuid);
|
||||
}
|
||||
}
|
||||
}, ticks);
|
||||
}
|
||||
|
||||
private void flushBatch(UUID uuid, String playerName) {
|
||||
Set<String> set = pendingSites.remove(uuid);
|
||||
if (set == null || set.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> list;
|
||||
synchronized (set) {
|
||||
list = new ArrayList<String>(set);
|
||||
}
|
||||
broadcastNow(playerName, list, "batch", null);
|
||||
}
|
||||
|
||||
/*
|
||||
* ======================= First vote of day (calendar) =======================
|
||||
*/
|
||||
|
||||
private boolean markFirstVoteOfDay(UUID uuid) {
|
||||
LocalDate today = LocalDate.now(zoneId);
|
||||
LocalDate last = firstVoteDay.putIfAbsent(uuid, today);
|
||||
|
||||
if (last == null) {
|
||||
return true;
|
||||
}
|
||||
if (!last.equals(today)) {
|
||||
firstVoteDay.put(uuid, today);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* ======================= Interval summary =======================
|
||||
*/
|
||||
|
||||
private void rescheduleIntervalIfNeeded() {
|
||||
if (intervalTask != null) {
|
||||
intervalTask.cancel();
|
||||
intervalTask = null;
|
||||
}
|
||||
|
||||
BroadcastSettings s = settings;
|
||||
if (s == null || s.getType() != VoteBroadcastType.INTERVAL_SUMMARY_GLOBAL) {
|
||||
return;
|
||||
}
|
||||
|
||||
long delayMs = s.getDuration() == null ? 1L : s.getDuration().delayMillisFromNow();
|
||||
long ticks = Math.max(1L, delayMs / 50L);
|
||||
|
||||
intervalTask = Bukkit.getScheduler().runTaskTimer(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
broadcastIntervalSummary();
|
||||
}
|
||||
}, ticks, ticks);
|
||||
}
|
||||
|
||||
private void recordInterval(UUID uuid, String siteName) {
|
||||
if (siteName == null || siteName.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LinkedHashSet<String> set = intervalSites.get(uuid);
|
||||
if (set == null) {
|
||||
LinkedHashSet<String> created = new LinkedHashSet<String>();
|
||||
set = intervalSites.putIfAbsent(uuid, created);
|
||||
if (set == null) {
|
||||
set = created;
|
||||
}
|
||||
}
|
||||
synchronized (set) {
|
||||
set.add(siteName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interval summary supports extra context placeholders: - %players% /
|
||||
* %numberofplayers% - %sites% / %numberofsites%
|
||||
*/
|
||||
private void broadcastIntervalSummary() {
|
||||
BroadcastSettings s = settings;
|
||||
if (s == null || s.isDisabled() || s.getType() != VoteBroadcastType.INTERVAL_SUMMARY_GLOBAL) {
|
||||
return;
|
||||
}
|
||||
|
||||
ConcurrentHashMap<UUID, LinkedHashSet<String>> snapshot = new ConcurrentHashMap<UUID, LinkedHashSet<String>>(
|
||||
intervalSites);
|
||||
intervalSites.clear();
|
||||
|
||||
if (snapshot.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> entries = new ArrayList<String>();
|
||||
List<String> players = new ArrayList<String>();
|
||||
LinkedHashSet<String> uniqueSites = new LinkedHashSet<String>();
|
||||
|
||||
for (Map.Entry<UUID, LinkedHashSet<String>> entry : snapshot.entrySet()) {
|
||||
LinkedHashSet<String> siteSet = entry.getValue();
|
||||
if (siteSet == null || siteSet.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String name = resolveName(entry.getKey());
|
||||
players.add(name);
|
||||
|
||||
int siteCount;
|
||||
synchronized (siteSet) {
|
||||
siteCount = siteSet.size();
|
||||
for (String site : siteSet) {
|
||||
if (site != null && !site.isEmpty()) {
|
||||
uniqueSites.add(site);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
entries.add(name + " (" + siteCount + ")");
|
||||
}
|
||||
|
||||
if (entries.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String playersCsv = join(players);
|
||||
String sitesCsv = join(new ArrayList<String>(uniqueSites));
|
||||
|
||||
ConcurrentHashMap<String, String> context = new ConcurrentHashMap<String, String>();
|
||||
context.put("players", playersCsv);
|
||||
context.put("numberofplayers", String.valueOf(players.size()));
|
||||
context.put("sites", sitesCsv);
|
||||
context.put("numberofsites", String.valueOf(uniqueSites.size()));
|
||||
|
||||
broadcastNow("Server", entries, "interval", context);
|
||||
}
|
||||
|
||||
/*
|
||||
* ======================= Utilities =======================
|
||||
*/
|
||||
|
||||
private List<String> single(String site) {
|
||||
List<String> list = new ArrayList<String>();
|
||||
if (site != null && !site.isEmpty()) {
|
||||
list.add(site);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private List<String> applyMaxSites(List<String> sites, BroadcastSettings s) {
|
||||
if (sites == null) {
|
||||
return new ArrayList<String>();
|
||||
}
|
||||
int max = s.getMaxSitesListed();
|
||||
if (max <= 0 || sites.size() <= max) {
|
||||
return new ArrayList<String>(sites);
|
||||
}
|
||||
return new ArrayList<String>(sites.subList(0, max));
|
||||
}
|
||||
|
||||
private String resolveName(UUID uuid) {
|
||||
try {
|
||||
OfflinePlayer op = Bukkit.getOfflinePlayer(uuid);
|
||||
String name = op == null ? null : op.getName();
|
||||
return name == null || name.isEmpty() ? "Player" : name;
|
||||
} catch (Exception e) {
|
||||
return "Player";
|
||||
}
|
||||
}
|
||||
|
||||
private String join(List<String> list) {
|
||||
StringJoiner joiner = new StringJoiner(", ");
|
||||
for (String s : list) {
|
||||
if (s != null && !s.isEmpty()) {
|
||||
joiner.add(s);
|
||||
}
|
||||
}
|
||||
return joiner.toString();
|
||||
}
|
||||
}
|
||||
-112
@@ -1,112 +0,0 @@
|
||||
package com.bencodez.votingplugin.broadcast;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
import com.bencodez.simpleapi.time.ParsedDuration;
|
||||
|
||||
/**
|
||||
* Immutable broadcast configuration.
|
||||
*
|
||||
* Type: NONE disables broadcasting.
|
||||
*/
|
||||
public final class BroadcastSettings {
|
||||
|
||||
private final VoteBroadcastType type;
|
||||
private final ParsedDuration duration;
|
||||
private final int maxSitesListed;
|
||||
private final BroadcastFormat format;
|
||||
|
||||
/**
|
||||
* Constructs a BroadcastSettings with the specified parameters.
|
||||
*
|
||||
* @param type the broadcast type
|
||||
* @param duration the broadcast duration
|
||||
* @param maxSitesListed the maximum number of sites to list
|
||||
* @param format the broadcast format
|
||||
*/
|
||||
public BroadcastSettings(VoteBroadcastType type, ParsedDuration duration, int maxSitesListed,
|
||||
BroadcastFormat format) {
|
||||
this.type = type == null ? VoteBroadcastType.NONE : type;
|
||||
this.duration = duration == null ? ParsedDuration.empty() : duration;
|
||||
this.maxSitesListed = Math.max(0, maxSitesListed);
|
||||
this.format = format == null ? new BroadcastFormat("", "", "") : format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the broadcast type.
|
||||
*
|
||||
* @return the broadcast type
|
||||
*/
|
||||
public VoteBroadcastType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the broadcast duration.
|
||||
*
|
||||
* @return the broadcast duration
|
||||
*/
|
||||
public ParsedDuration getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum number of sites to list.
|
||||
*
|
||||
* @return the maximum number of sites to list
|
||||
*/
|
||||
public int getMaxSitesListed() {
|
||||
return maxSitesListed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the broadcast format.
|
||||
*
|
||||
* @return the broadcast format
|
||||
*/
|
||||
public BroadcastFormat getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether broadcasting is disabled.
|
||||
*
|
||||
* @return true if broadcasting is disabled
|
||||
*/
|
||||
public boolean isDisabled() {
|
||||
return type == VoteBroadcastType.NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads VoteBroadcast settings from config.yml.
|
||||
*
|
||||
* Expected structure: VoteBroadcast: Type: EVERY_VOTE Duration: 2m
|
||||
* MaxSitesListed: 0 Format: BroadcastMsg: '...' Header: '...' ListLine: '...'
|
||||
*
|
||||
* @param sec the configuration section to load from
|
||||
* @return the loaded broadcast settings
|
||||
*/
|
||||
public static BroadcastSettings load(ConfigurationSection sec) {
|
||||
VoteBroadcastType type = VoteBroadcastType.parse(sec == null ? null : sec.getString("Type"),
|
||||
VoteBroadcastType.NONE);
|
||||
|
||||
ParsedDuration duration = ParsedDuration.parse(sec == null ? "2m" : sec.getString("Duration", "2m"),
|
||||
TimeUnit.MINUTES);
|
||||
|
||||
int maxSites = sec == null ? 0 : sec.getInt("MaxSitesListed", 0);
|
||||
|
||||
ConfigurationSection fmt = sec == null ? null : sec.getConfigurationSection("Format");
|
||||
|
||||
String broadcastMsg = fmt == null ? "&6[Vote] &aThanks &e%player% &afor voting on &e%site%&a!"
|
||||
: fmt.getString("BroadcastMsg", "&6[Vote] &aThanks &e%player% &afor voting on &e%site%&a!");
|
||||
|
||||
String header = fmt == null ? "&6[Vote] &a%player% voted! &7(%sites_count%)"
|
||||
: fmt.getString("Header", "&6[Vote] &a%player% voted! &7(%sites_count%)");
|
||||
|
||||
String listLine = fmt == null ? " &7- &e%site%" : fmt.getString("ListLine", " &7- &e%site%");
|
||||
|
||||
return new BroadcastSettings(type, duration, maxSites, new BroadcastFormat(broadcastMsg, header, listLine));
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package com.bencodez.votingplugin.broadcast;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Defines WHEN vote broadcasts occur.
|
||||
*
|
||||
* Set Type: NONE to disable broadcasting entirely.
|
||||
*/
|
||||
public enum VoteBroadcastType {
|
||||
|
||||
/**
|
||||
* No vote broadcasting.
|
||||
*/
|
||||
NONE,
|
||||
|
||||
/**
|
||||
* Broadcast on every vote (even if the player is offline). Uses
|
||||
* Format.BroadcastMsg (+ optional header/list if you choose).
|
||||
*/
|
||||
EVERY_VOTE,
|
||||
|
||||
/**
|
||||
* Broadcast on every vote ONLY if the voting player is online. Useful when
|
||||
* processing offline votes but you don't want global spam for them.
|
||||
*/
|
||||
EVERY_VOTE_ONLINE_ONLY,
|
||||
|
||||
/**
|
||||
* Broadcast at most once per Duration per player. (Even if they vote multiple
|
||||
* sites inside the cooldown.)
|
||||
*/
|
||||
COOLDOWN_PER_PLAYER,
|
||||
|
||||
/**
|
||||
* Batch sites per player for Duration, then broadcast one message listing
|
||||
* sites.
|
||||
*/
|
||||
BATCH_WINDOW_PER_PLAYER,
|
||||
|
||||
/**
|
||||
* Broadcast only the first vote of the day per player (calendar day).
|
||||
*/
|
||||
FIRST_VOTE_OF_DAY,
|
||||
|
||||
/**
|
||||
* Every Duration, broadcast a global summary of who voted and how many sites.
|
||||
* Uses context placeholders: %players%, %numberofplayers%, %sites%,
|
||||
* %numberofsites%.
|
||||
*/
|
||||
INTERVAL_SUMMARY_GLOBAL;
|
||||
|
||||
/**
|
||||
* Parses a vote broadcast type from a string.
|
||||
* @param s the string to parse
|
||||
* @param def the default value if parsing fails
|
||||
* @return the parsed broadcast type or default
|
||||
*/
|
||||
public static VoteBroadcastType parse(String s, VoteBroadcastType def) {
|
||||
if (s == null || s.trim().isEmpty()) {
|
||||
return def;
|
||||
}
|
||||
try {
|
||||
return VoteBroadcastType.valueOf(s.trim().toUpperCase(Locale.ROOT));
|
||||
} catch (Exception ignored) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
}
|
||||
-120
@@ -1,120 +0,0 @@
|
||||
package com.bencodez.votingplugin.commands.gui.admin.milestones;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.bencodez.advancedcore.api.gui.GUIHandler;
|
||||
import com.bencodez.advancedcore.api.gui.GUIMethod;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory.ClickEvent;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventoryButton;
|
||||
import com.bencodez.advancedcore.api.item.ItemBuilder;
|
||||
import com.bencodez.advancedcore.command.gui.RewardEditGUI;
|
||||
import com.bencodez.simpleapi.valuerequest.InputMethod;
|
||||
import com.bencodez.simpleapi.valuerequest.NumberListener;
|
||||
import com.bencodez.simpleapi.valuerequest.StringListener;
|
||||
import com.bencodez.simpleapi.valuerequest.ValueRequest;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
|
||||
/**
|
||||
* Admin vote milestones GUI handler.
|
||||
*/
|
||||
public class AdminVoteVoteMilestones extends GUIHandler {
|
||||
|
||||
private VotingPluginMain plugin;
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteVoteMilestones.
|
||||
*
|
||||
* @param plugin the VotingPluginMain instance
|
||||
* @param player the command sender
|
||||
*/
|
||||
public AdminVoteVoteMilestones(VotingPluginMain plugin, CommandSender player) {
|
||||
super(plugin, player);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<String> getChat(CommandSender sender) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBook(Player player) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChat(CommandSender sender) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDialog(Player player) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChest(Player player) {
|
||||
BInventory inv = new BInventory("Edit VoteMilestones");
|
||||
inv.requirePermission("VotingPlugin.Commands.AdminVote.Edit.VoteMilestones");
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.PAPER)
|
||||
.setName("&cEdit existing vote milestone rewards (Legacy milestones not included)")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
Set<String> keys = plugin.getVoteMilestonesManager().getConfig().getMilestones().keySet();
|
||||
ArrayList<String> milestoneKeys = new ArrayList<String>();
|
||||
for (String key : keys) {
|
||||
if (!key.startsWith("Legacy_")) {
|
||||
milestoneKeys.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
new ValueRequest(plugin, plugin.getDialogService(), InputMethod.INVENTORY).requestString(
|
||||
clickEvent.getPlayer(), "", milestoneKeys, false, "Select milestone", new StringListener() {
|
||||
|
||||
@Override
|
||||
public void onInput(Player player, String value) {
|
||||
RewardEditGUI.getInstance().openRewardGUI(clickEvent.getPlayer(),
|
||||
plugin.getRewardHandler().getDirectlyDefined("VoteMilestones." + value + ".Rewards"));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.EMERALD_BLOCK).setName("&aAdd milestone")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new ValueRequest(plugin, plugin.getDialogService(), null).requestNumber(player, 0, null, true,
|
||||
"Enter milestone", new NumberListener() {
|
||||
|
||||
@Override
|
||||
public void onInput(Player player, Number value) {
|
||||
plugin.getSpecialRewardsConfig().setVoteMilestone(value.intValue());
|
||||
plugin.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.REDSTONE_BLOCK).setName("&cRemove milestone")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteVoteMilestoneRemove(plugin, clickEvent.getPlayer()).open();
|
||||
}
|
||||
});
|
||||
inv.openInventory(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open() {
|
||||
open(GUIMethod.CHEST);
|
||||
}
|
||||
|
||||
}
|
||||
-169
@@ -1,169 +0,0 @@
|
||||
package com.bencodez.votingplugin.commands.gui.admin.votelog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.bencodez.advancedcore.api.gui.GUIHandler;
|
||||
import com.bencodez.advancedcore.api.gui.GUIMethod;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory.ClickEvent;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventoryButton;
|
||||
import com.bencodez.advancedcore.api.item.ItemBuilder;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.user.VotingPluginUser;
|
||||
import com.bencodez.votingplugin.votelog.VoteLogMysqlTable;
|
||||
|
||||
/**
|
||||
* Admin GUI: Select an event filter, then open AdminVoteLogRecent (filtered).
|
||||
*/
|
||||
public class AdminVoteLogEventSelect extends GUIHandler {
|
||||
|
||||
private final VotingPluginMain plugin;
|
||||
private final VoteLogMysqlTable table;
|
||||
private VotingPluginUser user;
|
||||
|
||||
private final int days;
|
||||
private final int page;
|
||||
|
||||
private static final int PAGE_SIZE = 45;
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteLogEventSelect.
|
||||
*
|
||||
* @param plugin the VotingPluginMain instance
|
||||
* @param sender the command sender
|
||||
* @param table the vote log table
|
||||
* @param user the voting plugin user
|
||||
* @param days the number of days to filter
|
||||
*/
|
||||
public AdminVoteLogEventSelect(VotingPluginMain plugin, CommandSender sender, VoteLogMysqlTable table,
|
||||
VotingPluginUser user, int days) {
|
||||
this(plugin, sender, table, user, days, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDialog(Player player) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteLogEventSelect with page.
|
||||
*
|
||||
* @param plugin the VotingPluginMain instance
|
||||
* @param sender the command sender
|
||||
* @param table the vote log table
|
||||
* @param user the voting plugin user
|
||||
* @param days the number of days to filter
|
||||
* @param page the page number
|
||||
*/
|
||||
public AdminVoteLogEventSelect(VotingPluginMain plugin, CommandSender sender, VoteLogMysqlTable table,
|
||||
VotingPluginUser user, int days, int page) {
|
||||
super(plugin, sender);
|
||||
this.plugin = plugin;
|
||||
this.table = table;
|
||||
this.user = user;
|
||||
this.days = days;
|
||||
this.page = Math.max(0, page);
|
||||
|
||||
if (this.user == null && sender instanceof Player) {
|
||||
this.user = plugin.getVotingPluginUserManager().getVotingPluginUser((Player) sender);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<String> getChat(CommandSender sender) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBook(Player player) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChat(CommandSender sender) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChest(Player player) {
|
||||
try {
|
||||
String title = "&aVoteLog - Events";
|
||||
if (days > 0) {
|
||||
title += " &7(" + days + "d)";
|
||||
}
|
||||
|
||||
BInventory inv = new BInventory(title);
|
||||
inv.requirePermission("VotingPlugin.Commands.AdminVote.VoteLog");
|
||||
|
||||
if (!plugin.getConfigFile().isAlwaysCloseInventory()) {
|
||||
inv.dontClose();
|
||||
}
|
||||
|
||||
if (table == null) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.BARRIER).setName("&cVoteLog unavailable")
|
||||
.addLoreLine("&7VoteLog MySQL table is not loaded/enabled.")) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
}
|
||||
});
|
||||
|
||||
inv.setPages(true);
|
||||
inv.setMaxInvSize(54);
|
||||
inv.openInventory(player);
|
||||
return;
|
||||
}
|
||||
|
||||
List<VoteLogMysqlTable.VoteLogEvent> events = Arrays.asList(VoteLogMysqlTable.VoteLogEvent.values());
|
||||
|
||||
int start = page * PAGE_SIZE;
|
||||
int end = Math.min(events.size(), start + PAGE_SIZE);
|
||||
|
||||
if (start >= events.size()) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.PAPER).setName("&eNo events")
|
||||
.addLoreLine("&7No events on this page.").addLoreLine("&7Try going back a page.")) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
for (int i = start; i < end; i++) {
|
||||
VoteLogMysqlTable.VoteLogEvent ev = events.get(i);
|
||||
inv.addButton(makeEventButton(ev));
|
||||
}
|
||||
}
|
||||
|
||||
// back button
|
||||
if (user != null && plugin.getGui().isChestVoteTopBackButton()) {
|
||||
inv.getPageButtons().add(plugin.getCommandLoader().getBackButton(user).setSlot(7));
|
||||
}
|
||||
|
||||
inv.setPages(true);
|
||||
inv.setMaxInvSize(54);
|
||||
inv.openInventory(player);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private BInventoryButton makeEventButton(VoteLogMysqlTable.VoteLogEvent ev) {
|
||||
ItemBuilder item = new ItemBuilder(Material.BOOK).setName("&a" + ev.name())
|
||||
.addLoreLine("&7Click to view recent logs filtered by this event")
|
||||
.addLoreLine(days > 0 ? "&7Window: &fLast " + days + " days" : "&7Window: &fAll time");
|
||||
|
||||
return new BInventoryButton(item) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteLogRecent(plugin, clickEvent.getPlayer(), table, user, 0, days, ev).open();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open() {
|
||||
open(GUIMethod.CHEST);
|
||||
}
|
||||
}
|
||||
-125
@@ -1,125 +0,0 @@
|
||||
package com.bencodez.votingplugin.commands.gui.admin.votelog;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import org.bukkit.Material;
|
||||
|
||||
import com.bencodez.votingplugin.votelog.VoteLogMysqlTable;
|
||||
|
||||
/**
|
||||
* Helper class for admin vote log GUI operations.
|
||||
*/
|
||||
public class AdminVoteLogHelpers {
|
||||
/**
|
||||
* Get the material icon for a vote log event.
|
||||
*
|
||||
* @param event the vote log event
|
||||
* @param status the status of the vote
|
||||
* @return the material to use for the icon
|
||||
*/
|
||||
public static Material getMaterialForEvent(VoteLogMysqlTable.VoteLogEvent event, String status) {
|
||||
// Cached entries still override to chest (keeps your current behavior)
|
||||
if ("CACHED".equalsIgnoreCase(status)) {
|
||||
return Material.CHEST;
|
||||
}
|
||||
|
||||
if (event == null) {
|
||||
return Material.PAPER;
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case VOTE_RECEIVED:
|
||||
return Material.EMERALD;
|
||||
|
||||
case VOTEMILESTONE:
|
||||
return Material.ANVIL;
|
||||
|
||||
case VOTE_STREAK_REWARD:
|
||||
return Material.FIREWORK_ROCKET;
|
||||
|
||||
case TOP_VOTER_REWARD:
|
||||
return Material.GOLD_BLOCK;
|
||||
|
||||
default:
|
||||
return Material.PAPER;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the color code for a vote log event.
|
||||
*
|
||||
* @param event the vote log event
|
||||
* @return the color code string
|
||||
*/
|
||||
public static String getEventColor(VoteLogMysqlTable.VoteLogEvent event) {
|
||||
if (event == null) {
|
||||
return "&7";
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case VOTE_RECEIVED:
|
||||
return "&a";
|
||||
case VOTEMILESTONE:
|
||||
return "&6";
|
||||
|
||||
case VOTE_STREAK_REWARD:
|
||||
return "&d";
|
||||
|
||||
case TOP_VOTER_REWARD:
|
||||
return "&c";
|
||||
|
||||
default:
|
||||
return "&7";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a safe string, converting null to empty string.
|
||||
*
|
||||
* @param s the string to check
|
||||
* @return the string or empty string if null
|
||||
*/
|
||||
public static String safe(String s) {
|
||||
return s == null ? "" : s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a string is not null and not empty.
|
||||
*
|
||||
* @param s the string to check
|
||||
* @return true if the string is not empty
|
||||
*/
|
||||
public static boolean notEmpty(String s) {
|
||||
return s != null && !s.trim().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a timestamp in milliseconds to a readable date string.
|
||||
*
|
||||
* @param millis the timestamp in milliseconds
|
||||
* @return the formatted date string
|
||||
*/
|
||||
public static String formatTime(long millis) {
|
||||
try {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
return sdf.format(new Date(millis));
|
||||
} catch (Exception e) {
|
||||
return String.valueOf(millis);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a shortened version of a UUID string.
|
||||
*
|
||||
* @param uuid the UUID string
|
||||
* @return the first 8 characters of the UUID or "unknown"
|
||||
*/
|
||||
public static String shortUuid(String uuid) {
|
||||
if (uuid == null || uuid.isEmpty()) {
|
||||
return "unknown";
|
||||
}
|
||||
return uuid.length() > 8 ? uuid.substring(0, 8) : uuid;
|
||||
}
|
||||
|
||||
}
|
||||
-208
@@ -1,208 +0,0 @@
|
||||
package com.bencodez.votingplugin.commands.gui.admin.votelog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.bencodez.advancedcore.api.gui.GUIHandler;
|
||||
import com.bencodez.advancedcore.api.gui.GUIMethod;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory.ClickEvent;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventoryButton;
|
||||
import com.bencodez.advancedcore.api.item.ItemBuilder;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.user.VotingPluginUser;
|
||||
import com.bencodez.votingplugin.votelog.VoteLogMysqlTable;
|
||||
|
||||
/**
|
||||
* Admin vote log menu GUI handler.
|
||||
*/
|
||||
public class AdminVoteLogMenu extends GUIHandler {
|
||||
|
||||
private final VotingPluginMain plugin;
|
||||
private final VoteLogMysqlTable table;
|
||||
private VotingPluginUser user;
|
||||
|
||||
private final int days; // window used by selectors/stats (0 = all time)
|
||||
|
||||
// Default to "voting" (Vote received)
|
||||
private final VoteLogMysqlTable.VoteLogEvent defaultEvent = VoteLogMysqlTable.VoteLogEvent.VOTE_RECEIVED;
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteLogMenu.
|
||||
*
|
||||
* @param plugin the VotingPluginMain instance
|
||||
* @param sender the command sender
|
||||
* @param table the vote log table
|
||||
* @param days the number of days to filter
|
||||
*/
|
||||
public AdminVoteLogMenu(VotingPluginMain plugin, CommandSender sender, VoteLogMysqlTable table, int days) {
|
||||
this(plugin, sender, table, null, days);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDialog(Player player) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteLogMenu with user.
|
||||
*
|
||||
* @param plugin the VotingPluginMain instance
|
||||
* @param sender the command sender
|
||||
* @param table the vote log table
|
||||
* @param user the voting plugin user
|
||||
* @param days the number of days to filter
|
||||
*/
|
||||
public AdminVoteLogMenu(VotingPluginMain plugin, CommandSender sender, VoteLogMysqlTable table,
|
||||
VotingPluginUser user, int days) {
|
||||
super(plugin, sender);
|
||||
this.plugin = plugin;
|
||||
this.table = table;
|
||||
this.user = user;
|
||||
if (this.user == null && sender instanceof Player) {
|
||||
this.user = plugin.getVotingPluginUserManager().getVotingPluginUser((Player) sender);
|
||||
}
|
||||
this.days = Math.max(0, days);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<String> getChat(CommandSender sender) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBook(Player player) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChat(CommandSender sender) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChest(Player player) {
|
||||
try {
|
||||
String title = "&aVoteLog - Admin";
|
||||
if (days > 0) {
|
||||
title += " &7(" + days + "d)";
|
||||
}
|
||||
|
||||
BInventory inv = new BInventory(title);
|
||||
inv.requirePermission("VotingPlugin.Commands.AdminVote.VoteLog");
|
||||
|
||||
if (!plugin.getConfigFile().isAlwaysCloseInventory()) {
|
||||
inv.dontClose();
|
||||
}
|
||||
|
||||
if (table == null) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.BARRIER).setName("&cVoteLog unavailable")
|
||||
.addLoreLine("&7VoteLog MySQL table is not loaded/enabled.")) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
// no-op
|
||||
}
|
||||
});
|
||||
inv.setPages(true);
|
||||
inv.setMaxInvSize(54);
|
||||
inv.openInventory(player);
|
||||
return;
|
||||
}
|
||||
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.NAME_TAG).setName("&aVotes by Player")
|
||||
.addLoreLine("&7List stored player names").addLoreLine("&7Click a player to view their votelog")) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteLogPlayerSelect(plugin, clickEvent.getPlayer(), table, user, days, 0).open();
|
||||
}
|
||||
});
|
||||
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.NAME_TAG).setName("&aVotes by Player (Online)")
|
||||
.addLoreLine("&7List online players").addLoreLine("&7Click a player to view their votelog")) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteLogPlayerSelectOnline(plugin, clickEvent.getPlayer(), table, user, days, 0).open();
|
||||
}
|
||||
});
|
||||
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.PAPER).setName("&aVotes by Service")
|
||||
.addLoreLine("&7List all services found").addLoreLine("&7Click a service to view votelog")) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteLogServiceSelect(plugin, clickEvent.getPlayer(), table, user, days, 0).open();
|
||||
}
|
||||
});
|
||||
|
||||
// NEW: Votes by Server
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.COMPASS).setName("&aVotes by Server")
|
||||
.addLoreLine("&7List all servers found").addLoreLine("&7Click a server to view votelog")) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteLogServerSelect(plugin, clickEvent.getPlayer(), table, user, days, 0).open();
|
||||
}
|
||||
});
|
||||
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.MAP).setName("&aStats")
|
||||
.addLoreLine("&7Overview counts/uniques/top services")
|
||||
.addLoreLine(days > 0 ? "&7Window: &fLast " + days + " days" : "&7Window: &fAll time")) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteLogStats(plugin, clickEvent.getPlayer(), table, user, days, 10).open();
|
||||
}
|
||||
});
|
||||
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.CLOCK).setName("&aRecent Logs")
|
||||
.addLoreLine("&7Recent logs filtered by event").addLoreLine("&7Default: &f" + defaultEvent.name())
|
||||
.addLoreLine("&8(Use bottom buttons to switch event)")) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
// Open recent with default event filter
|
||||
new AdminVoteLogRecent(plugin, clickEvent.getPlayer(), table, user, 0, days, defaultEvent).open();
|
||||
}
|
||||
});
|
||||
|
||||
addEventSwitchButtons(inv);
|
||||
|
||||
inv.setPages(true);
|
||||
inv.setMaxInvSize(54);
|
||||
inv.openInventory(player);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void addEventSwitchButtons(BInventory inv) {
|
||||
inv.getPageButtons().add(new BInventoryButton(new ItemBuilder(Material.LIME_DYE)
|
||||
.setName("&aEvent: &fVOTE_RECEIVED").addLoreLine("&7Show recent vote received events")) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteLogRecent(plugin, clickEvent.getPlayer(), table, user, 0, days,
|
||||
VoteLogMysqlTable.VoteLogEvent.VOTE_RECEIVED).open();
|
||||
}
|
||||
}.setSlot(3));
|
||||
|
||||
// More... (open selector)
|
||||
inv.getPageButtons().add(new BInventoryButton(
|
||||
new ItemBuilder(Material.BOOK).setName("&aEvent: &fMore...").addLoreLine("&7Pick any event filter")) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteLogEventSelect(plugin, clickEvent.getPlayer(), table, user, days).open();
|
||||
}
|
||||
}.setSlot(4));
|
||||
|
||||
// All events
|
||||
inv.getPageButtons().add(new BInventoryButton(new ItemBuilder(Material.GRAY_DYE).setName("&aEvent: &fALL")
|
||||
.addLoreLine("&7Show recent logs for all events")) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteLogRecent(plugin, clickEvent.getPlayer(), table, user, 0, days, null).open();
|
||||
}
|
||||
}.setSlot(5));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open() {
|
||||
open(GUIMethod.CHEST);
|
||||
}
|
||||
}
|
||||
-185
@@ -1,185 +0,0 @@
|
||||
package com.bencodez.votingplugin.commands.gui.admin.votelog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.bencodez.advancedcore.api.gui.GUIHandler;
|
||||
import com.bencodez.advancedcore.api.gui.GUIMethod;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory.ClickEvent;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventoryButton;
|
||||
import com.bencodez.advancedcore.api.item.ItemBuilder;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.votelog.VoteLogMysqlTable;
|
||||
|
||||
/**
|
||||
* Admin GUI: VoteLog entries for a given player (paged).
|
||||
*
|
||||
* Uses VoteLogMysqlTable instance passed in from CommandLoader.
|
||||
*
|
||||
* Supports input as player name OR uuid string (you decide in CommandLoader
|
||||
* which constructor to call).
|
||||
*/
|
||||
public class AdminVoteLogPlayer extends GUIHandler {
|
||||
|
||||
private final VotingPluginMain plugin;
|
||||
private final VoteLogMysqlTable table;
|
||||
private final int page;
|
||||
|
||||
private final String playerName;
|
||||
|
||||
private final int days;
|
||||
|
||||
private static final int PAGE_SIZE = 45;
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteLogPlayer.
|
||||
*
|
||||
* @param plugin the VotingPluginMain instance
|
||||
* @param sender the command sender
|
||||
* @param table the vote log table
|
||||
* @param playerName the player name or UUID
|
||||
* @param days the number of days to filter
|
||||
* @param page the page number
|
||||
*/
|
||||
public AdminVoteLogPlayer(VotingPluginMain plugin, CommandSender sender, VoteLogMysqlTable table, String playerName,
|
||||
int days, int page) {
|
||||
super(plugin, sender);
|
||||
this.plugin = plugin;
|
||||
this.table = table;
|
||||
this.playerName = playerName;
|
||||
this.days = days;
|
||||
this.page = Math.max(0, page);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDialog(Player player) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<String> getChat(CommandSender sender) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBook(Player player) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChat(CommandSender sender) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChest(Player player) {
|
||||
try {
|
||||
String title = "&aVoteLog - Player";
|
||||
if (playerName != null && !playerName.isEmpty()) {
|
||||
title += " &7(" + playerName + ")";
|
||||
}
|
||||
|
||||
BInventory inv = new BInventory(title);
|
||||
inv.requirePermission("VotingPlugin.Commands.AdminVote.VoteLog");
|
||||
|
||||
if (!plugin.getConfigFile().isAlwaysCloseInventory()) {
|
||||
inv.dontClose();
|
||||
}
|
||||
|
||||
if (table == null) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.BARRIER).setName("&cVoteLog unavailable")
|
||||
.addLoreLine("&7VoteLog MySQL table is not loaded/enabled.")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
// no-op
|
||||
}
|
||||
});
|
||||
|
||||
inv.setPages(true);
|
||||
inv.setMaxInvSize(54);
|
||||
inv.openInventory(player);
|
||||
return;
|
||||
}
|
||||
|
||||
int needed = (page + 1) * PAGE_SIZE;
|
||||
|
||||
List<VoteLogMysqlTable.VoteLogEntry> rows = table.getByPlayerName(playerName, days, needed);
|
||||
|
||||
int start = page * PAGE_SIZE;
|
||||
int end = Math.min(rows.size(), start + PAGE_SIZE);
|
||||
|
||||
if (start >= rows.size()) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.PAPER).setName("&eNo entries")
|
||||
.addLoreLine("&7No vote log entries on this page.").addLoreLine("&7Try going back a page.")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
// no-op
|
||||
}
|
||||
});
|
||||
} else {
|
||||
for (int i = start; i < end; i++) {
|
||||
inv.addButton(makeEntryButton(rows.get(i)));
|
||||
}
|
||||
}
|
||||
|
||||
inv.setPages(true);
|
||||
inv.setMaxInvSize(54);
|
||||
inv.openInventory(player);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private BInventoryButton makeEntryButton(final VoteLogMysqlTable.VoteLogEntry entry) {
|
||||
Material mat = "CACHED".equalsIgnoreCase(entry.status) ? Material.CHEST : Material.EMERALD;
|
||||
|
||||
String timeStr = AdminVoteLogHelpers.formatTime(entry.voteTime);
|
||||
String uuidShort = AdminVoteLogHelpers.shortUuid(entry.playerUuid);
|
||||
String voteShort = AdminVoteLogHelpers.shortUuid(entry.voteId);
|
||||
|
||||
// keep same name/lore layout you already have
|
||||
ItemBuilder item = new ItemBuilder(mat)
|
||||
.setName("&a" + AdminVoteLogHelpers.safe(entry.event) + " &7(" + voteShort + ")");
|
||||
|
||||
if (AdminVoteLogHelpers.notEmpty(entry.playerName) || AdminVoteLogHelpers.notEmpty(entry.playerUuid)) {
|
||||
item.addLoreLine("&7Player: &f" + AdminVoteLogHelpers.safe(entry.playerName) + " &7(" + uuidShort + ")");
|
||||
}
|
||||
if (entry.voteTime > 0) {
|
||||
item.addLoreLine("&7Time: &f" + timeStr);
|
||||
}
|
||||
if (AdminVoteLogHelpers.notEmpty(entry.service)) {
|
||||
item.addLoreLine("&7Service: &f" + AdminVoteLogHelpers.safe(entry.service));
|
||||
}
|
||||
if (AdminVoteLogHelpers.notEmpty(entry.context)) {
|
||||
item.addLoreLine("&7Context: &f" + AdminVoteLogHelpers.safe(entry.context));
|
||||
}
|
||||
if (AdminVoteLogHelpers.notEmpty(entry.status)) {
|
||||
item.addLoreLine("&7Status: &f" + AdminVoteLogHelpers.safe(entry.status));
|
||||
}
|
||||
if (entry.proxyCachedTotal != 0) {
|
||||
item.addLoreLine("&7Cached Total: &f" + entry.proxyCachedTotal);
|
||||
}
|
||||
if (AdminVoteLogHelpers.notEmpty(entry.voteId)) {
|
||||
item.addLoreLine("&7VoteId: &f" + AdminVoteLogHelpers.safe(entry.voteId));
|
||||
}
|
||||
|
||||
return new BInventoryButton(item) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
// vote_id is now the correlation id entry.voteId
|
||||
new AdminVoteLogVoteId(plugin, clickEvent.getPlayer(), table, entry.voteId, days, 0).open();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open() {
|
||||
open(GUIMethod.CHEST);
|
||||
}
|
||||
}
|
||||
-155
@@ -1,155 +0,0 @@
|
||||
package com.bencodez.votingplugin.commands.gui.admin.votelog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.bencodez.advancedcore.api.gui.GUIHandler;
|
||||
import com.bencodez.advancedcore.api.gui.GUIMethod;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory.ClickEvent;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventoryButton;
|
||||
import com.bencodez.advancedcore.api.item.ItemBuilder;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.user.VotingPluginUser;
|
||||
import com.bencodez.votingplugin.votelog.VoteLogMysqlTable;
|
||||
|
||||
/**
|
||||
* Admin GUI: Select an ONLINE player, then open AdminVoteLogPlayer.
|
||||
*/
|
||||
public class AdminVoteLogPlayerSelect extends GUIHandler {
|
||||
|
||||
private final VotingPluginMain plugin;
|
||||
private final VoteLogMysqlTable table;
|
||||
private VotingPluginUser user;
|
||||
|
||||
private final int days;
|
||||
private final int page;
|
||||
|
||||
private static final int PAGE_SIZE = 45;
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteLogPlayerSelect.
|
||||
*
|
||||
* @param plugin the VotingPluginMain instance
|
||||
* @param sender the command sender
|
||||
* @param table the vote log table
|
||||
* @param user the voting plugin user
|
||||
* @param days the number of days to filter
|
||||
* @param page the page number
|
||||
*/
|
||||
public AdminVoteLogPlayerSelect(VotingPluginMain plugin, CommandSender sender, VoteLogMysqlTable table,
|
||||
VotingPluginUser user, int days, int page) {
|
||||
super(plugin, sender);
|
||||
this.plugin = plugin;
|
||||
this.table = table;
|
||||
this.user = user;
|
||||
this.days = Math.max(0, days);
|
||||
this.page = Math.max(0, page);
|
||||
|
||||
if (this.user == null && sender instanceof Player) {
|
||||
this.user = plugin.getVotingPluginUserManager().getVotingPluginUser((Player) sender);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<String> getChat(CommandSender sender) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDialog(Player player) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBook(Player player) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChat(CommandSender sender) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChest(Player player) {
|
||||
try {
|
||||
String title = "&aVoteLog - Players";
|
||||
if (days > 0) {
|
||||
title += " &7(" + days + "d)";
|
||||
}
|
||||
|
||||
BInventory inv = new BInventory(title);
|
||||
inv.requirePermission("VotingPlugin.Commands.AdminVote.VoteLog");
|
||||
|
||||
if (!plugin.getConfigFile().isAlwaysCloseInventory()) {
|
||||
inv.dontClose();
|
||||
}
|
||||
|
||||
if (table == null) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.BARRIER).setName("&cVoteLog unavailable")
|
||||
.addLoreLine("&7VoteLog MySQL table is not loaded/enabled.")) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
// no-op
|
||||
}
|
||||
});
|
||||
|
||||
inv.setPages(true);
|
||||
inv.setMaxInvSize(54);
|
||||
inv.openInventory(player);
|
||||
return;
|
||||
}
|
||||
|
||||
List<Player> online = new ArrayList<>(Bukkit.getOnlinePlayers());
|
||||
|
||||
int start = page * PAGE_SIZE;
|
||||
int end = Math.min(online.size(), start + PAGE_SIZE);
|
||||
|
||||
if (start >= online.size()) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.PAPER).setName("&eNo players")
|
||||
.addLoreLine("&7No online players on this page.").addLoreLine("&7Try going back a page.")) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
// no-op
|
||||
}
|
||||
});
|
||||
} else {
|
||||
for (int i = start; i < end; i++) {
|
||||
Player p = online.get(i);
|
||||
inv.addButton(makePlayerButton(p));
|
||||
}
|
||||
}
|
||||
|
||||
inv.setPages(true);
|
||||
inv.setMaxInvSize(54);
|
||||
inv.openInventory(player);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private BInventoryButton makePlayerButton(final Player target) {
|
||||
String name = target.getName();
|
||||
|
||||
ItemBuilder item = new ItemBuilder(Material.NAME_TAG).setName("&a" + name)
|
||||
.addLoreLine("&7Click to open votelog for this player")
|
||||
.addLoreLine(days > 0 ? "&7Window: &fLast " + days + " days" : "&7Window: &fAll time");
|
||||
|
||||
return new BInventoryButton(item) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteLogPlayer(plugin, clickEvent.getPlayer(), table, name, days, 0).open();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open() {
|
||||
open(GUIMethod.CHEST);
|
||||
}
|
||||
}
|
||||
-151
@@ -1,151 +0,0 @@
|
||||
package com.bencodez.votingplugin.commands.gui.admin.votelog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.bencodez.advancedcore.api.gui.GUIHandler;
|
||||
import com.bencodez.advancedcore.api.gui.GUIMethod;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory.ClickEvent;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventoryButton;
|
||||
import com.bencodez.advancedcore.api.item.ItemBuilder;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.user.VotingPluginUser;
|
||||
import com.bencodez.votingplugin.votelog.VoteLogMysqlTable;
|
||||
|
||||
/**
|
||||
* Admin GUI: Select an ONLINE player, then open AdminVoteLogPlayer.
|
||||
*/
|
||||
public class AdminVoteLogPlayerSelectOnline extends GUIHandler {
|
||||
|
||||
private final VotingPluginMain plugin;
|
||||
private final VoteLogMysqlTable table;
|
||||
private VotingPluginUser user;
|
||||
|
||||
private final int days;
|
||||
private final int page;
|
||||
|
||||
private static final int PAGE_SIZE = 45;
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteLogPlayerSelectOnline.
|
||||
*
|
||||
* @param plugin the VotingPluginMain instance
|
||||
* @param sender the command sender
|
||||
* @param table the vote log table
|
||||
* @param user the voting plugin user
|
||||
* @param days the number of days to filter
|
||||
* @param page the page number
|
||||
*/
|
||||
public AdminVoteLogPlayerSelectOnline(VotingPluginMain plugin, CommandSender sender, VoteLogMysqlTable table,
|
||||
VotingPluginUser user, int days, int page) {
|
||||
super(plugin, sender);
|
||||
this.plugin = plugin;
|
||||
this.table = table;
|
||||
this.user = user;
|
||||
this.days = days;
|
||||
this.page = Math.max(0, page);
|
||||
|
||||
if (this.user == null && sender instanceof Player) {
|
||||
this.user = plugin.getVotingPluginUserManager().getVotingPluginUser((Player) sender);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDialog(Player player) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<String> getChat(CommandSender sender) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBook(Player player) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChat(CommandSender sender) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChest(Player player) {
|
||||
try {
|
||||
String title = "&aVoteLog - Players";
|
||||
if (days > 0) {
|
||||
title += " &7(" + days + "d)";
|
||||
}
|
||||
|
||||
BInventory inv = new BInventory(title);
|
||||
inv.requirePermission("VotingPlugin.Commands.AdminVote.VoteLog");
|
||||
|
||||
if (!plugin.getConfigFile().isAlwaysCloseInventory()) {
|
||||
inv.dontClose();
|
||||
}
|
||||
|
||||
if (table == null) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.BARRIER).setName("&cVoteLog unavailable")
|
||||
.addLoreLine("&7VoteLog MySQL table is not loaded/enabled.")) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
}
|
||||
});
|
||||
inv.setPages(true);
|
||||
inv.setMaxInvSize(54);
|
||||
inv.openInventory(player);
|
||||
return;
|
||||
}
|
||||
|
||||
List<Player> online = new ArrayList<>(Bukkit.getOnlinePlayers());
|
||||
|
||||
int start = page * PAGE_SIZE;
|
||||
int end = Math.min(online.size(), start + PAGE_SIZE);
|
||||
|
||||
if (start >= online.size()) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.PAPER).setName("&eNo players")
|
||||
.addLoreLine("&7No online players on this page.").addLoreLine("&7Try going back a page.")) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
for (int i = start; i < end; i++) {
|
||||
Player p = online.get(i);
|
||||
inv.addButton(makePlayerButton(p));
|
||||
}
|
||||
}
|
||||
|
||||
inv.setPages(true);
|
||||
inv.setMaxInvSize(54);
|
||||
inv.openInventory(player);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private BInventoryButton makePlayerButton(Player target) {
|
||||
String name = target.getName();
|
||||
|
||||
ItemBuilder item = new ItemBuilder(Material.NAME_TAG).setName("&a" + name)
|
||||
.addLoreLine("&7Click to open votelog for this player")
|
||||
.addLoreLine(days > 0 ? "&7Window: &fLast " + days + " days" : "&7Window: &fAll time");
|
||||
|
||||
return new BInventoryButton(item) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteLogPlayer(plugin, clickEvent.getPlayer(), table, name, days, 0).open();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open() {
|
||||
open(GUIMethod.CHEST);
|
||||
}
|
||||
}
|
||||
-255
@@ -1,255 +0,0 @@
|
||||
package com.bencodez.votingplugin.commands.gui.admin.votelog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.bencodez.advancedcore.api.gui.GUIHandler;
|
||||
import com.bencodez.advancedcore.api.gui.GUIMethod;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory.ClickEvent;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventoryButton;
|
||||
import com.bencodez.advancedcore.api.item.ItemBuilder;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.user.VotingPluginUser;
|
||||
import com.bencodez.votingplugin.votelog.VoteLogMysqlTable;
|
||||
|
||||
/**
|
||||
* Admin GUI: Recent VoteLog entries (paged).
|
||||
*
|
||||
* Now supports optional event filter + days window.
|
||||
*/
|
||||
public class AdminVoteLogRecent extends GUIHandler {
|
||||
|
||||
private final VotingPluginMain plugin;
|
||||
private final VoteLogMysqlTable table;
|
||||
private VotingPluginUser user;
|
||||
|
||||
private final int page;
|
||||
|
||||
private final int days; // 0 = all time
|
||||
private final VoteLogMysqlTable.VoteLogEvent event; // null = all events
|
||||
|
||||
private static final int PAGE_SIZE = 45;
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteLogRecent.
|
||||
*
|
||||
* @param plugin the VotingPluginMain instance
|
||||
* @param sender the command sender
|
||||
* @param table the vote log table
|
||||
* @param page the page number
|
||||
*/
|
||||
public AdminVoteLogRecent(VotingPluginMain plugin, CommandSender sender, VoteLogMysqlTable table, int page) {
|
||||
this(plugin, sender, table, null, page, 0, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteLogRecent with user.
|
||||
*
|
||||
* @param plugin the VotingPluginMain instance
|
||||
* @param sender the command sender
|
||||
* @param table the vote log table
|
||||
* @param user the voting plugin user
|
||||
* @param page the page number
|
||||
*/
|
||||
public AdminVoteLogRecent(VotingPluginMain plugin, CommandSender sender, VoteLogMysqlTable table,
|
||||
VotingPluginUser user, int page) {
|
||||
this(plugin, sender, table, user, page, 0, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteLogRecent with full parameters.
|
||||
*
|
||||
* @param plugin the VotingPluginMain instance
|
||||
* @param sender the command sender
|
||||
* @param table the vote log table
|
||||
* @param user the voting plugin user
|
||||
* @param page the page number
|
||||
* @param days the number of days to filter
|
||||
* @param event the event type to filter
|
||||
*/
|
||||
// NEW constructor used by Menu/EventSelect
|
||||
public AdminVoteLogRecent(VotingPluginMain plugin, CommandSender sender, VoteLogMysqlTable table,
|
||||
VotingPluginUser user, int page, int days, VoteLogMysqlTable.VoteLogEvent event) {
|
||||
super(plugin, sender);
|
||||
this.plugin = plugin;
|
||||
this.table = table;
|
||||
this.user = user;
|
||||
if (this.user == null && sender instanceof Player) {
|
||||
this.user = plugin.getVotingPluginUserManager().getVotingPluginUser((Player) sender);
|
||||
}
|
||||
this.page = Math.max(0, page);
|
||||
this.days = Math.max(0, days);
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<String> getChat(CommandSender sender) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBook(Player player) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChat(CommandSender sender) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDialog(Player player) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChest(Player player) {
|
||||
try {
|
||||
String title = "&aVoteLog - Recent";
|
||||
if (event != null) {
|
||||
title += " &7(" + event.name() + ")";
|
||||
}
|
||||
if (days > 0) {
|
||||
title += " &7(" + days + "d)";
|
||||
}
|
||||
|
||||
BInventory inv = new BInventory(title);
|
||||
inv.requirePermission("VotingPlugin.Commands.AdminVote.VoteLog");
|
||||
|
||||
if (!plugin.getConfigFile().isAlwaysCloseInventory()) {
|
||||
inv.dontClose();
|
||||
}
|
||||
|
||||
if (table == null) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.BARRIER).setName("&cVoteLog unavailable")
|
||||
.addLoreLine("&7VoteLog MySQL table is not loaded/enabled.")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
}
|
||||
});
|
||||
|
||||
inv.setPages(true);
|
||||
inv.setMaxInvSize(54);
|
||||
inv.openInventory(player);
|
||||
return;
|
||||
}
|
||||
|
||||
int needed = (page + 1) * PAGE_SIZE;
|
||||
|
||||
List<VoteLogMysqlTable.VoteLogEntry> rows;
|
||||
if (event == null) {
|
||||
rows = (days > 0) ? table.getRecentAll(days, needed) : table.getRecent(needed);
|
||||
} else {
|
||||
rows = table.getRecentByEvent(event, days, needed);
|
||||
}
|
||||
|
||||
int start = page * PAGE_SIZE;
|
||||
int end = Math.min(rows.size(), start + PAGE_SIZE);
|
||||
|
||||
if (start >= rows.size()) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.PAPER).setName("&eNo entries")
|
||||
.addLoreLine("&7No vote log entries on this page.").addLoreLine("&7Try going back a page.")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
for (int i = start; i < end; i++) {
|
||||
inv.addButton(makeEntryButton(rows.get(i)));
|
||||
}
|
||||
}
|
||||
|
||||
addEventSwitchButtons(inv);
|
||||
|
||||
inv.setPages(true);
|
||||
inv.setMaxInvSize(54);
|
||||
inv.openInventory(player);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void addEventSwitchButtons(BInventory inv) {
|
||||
inv.getPageButtons().add(new BInventoryButton(new ItemBuilder(Material.LIME_DYE)
|
||||
.setName("&aEvent: &fVOTE_RECEIVED").addLoreLine("&7Show recent vote received events")) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteLogRecent(plugin, clickEvent.getPlayer(), table, user, 0, days,
|
||||
VoteLogMysqlTable.VoteLogEvent.VOTE_RECEIVED).open();
|
||||
}
|
||||
}.setSlot(3));
|
||||
|
||||
// More... (open selector)
|
||||
inv.getPageButtons().add(new BInventoryButton(
|
||||
new ItemBuilder(Material.BOOK).setName("&aEvent: &fMore...").addLoreLine("&7Pick any event filter")) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteLogEventSelect(plugin, clickEvent.getPlayer(), table, user, days).open();
|
||||
}
|
||||
}.setSlot(4));
|
||||
|
||||
// All events
|
||||
inv.getPageButtons().add(new BInventoryButton(new ItemBuilder(Material.GRAY_DYE).setName("&aEvent: &fALL")
|
||||
.addLoreLine("&7Show recent logs for all events")) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteLogRecent(plugin, clickEvent.getPlayer(), table, user, 0, days, null).open();
|
||||
}
|
||||
}.setSlot(5));
|
||||
}
|
||||
|
||||
private BInventoryButton makeEntryButton(final VoteLogMysqlTable.VoteLogEntry entry) {
|
||||
VoteLogMysqlTable.VoteLogEvent event = entry.event != null ? VoteLogMysqlTable.VoteLogEvent.valueOf(entry.event)
|
||||
: null;
|
||||
|
||||
Material mat = AdminVoteLogHelpers.getMaterialForEvent(event, entry.status);
|
||||
|
||||
String eventColor = AdminVoteLogHelpers.getEventColor(event);
|
||||
|
||||
String timeStr = AdminVoteLogHelpers.formatTime(entry.voteTime);
|
||||
String uuidShort = AdminVoteLogHelpers.shortUuid(entry.playerUuid);
|
||||
String voteShort = AdminVoteLogHelpers.shortUuid(entry.voteId);
|
||||
|
||||
ItemBuilder item = new ItemBuilder(mat)
|
||||
.setName(eventColor + AdminVoteLogHelpers.safe(entry.event) + " &7(" + voteShort + ")");
|
||||
|
||||
if (AdminVoteLogHelpers.notEmpty(entry.playerName) || AdminVoteLogHelpers.notEmpty(entry.playerUuid)) {
|
||||
item.addLoreLine("&7Player: &f" + AdminVoteLogHelpers.safe(entry.playerName) + " &7(" + uuidShort + ")");
|
||||
}
|
||||
if (entry.voteTime > 0) {
|
||||
item.addLoreLine("&7Time: &f" + timeStr);
|
||||
}
|
||||
if (AdminVoteLogHelpers.notEmpty(entry.service)) {
|
||||
item.addLoreLine("&7Service: &f" + AdminVoteLogHelpers.safe(entry.service));
|
||||
}
|
||||
if (AdminVoteLogHelpers.notEmpty(entry.context)) {
|
||||
item.addLoreLine("&7Context: &f" + AdminVoteLogHelpers.safe(entry.context));
|
||||
}
|
||||
if (AdminVoteLogHelpers.notEmpty(entry.status)) {
|
||||
item.addLoreLine("&7Status: &f" + AdminVoteLogHelpers.safe(entry.status));
|
||||
}
|
||||
if (entry.proxyCachedTotal != 0) {
|
||||
item.addLoreLine("&7Cached Total: &f" + entry.proxyCachedTotal);
|
||||
}
|
||||
if (AdminVoteLogHelpers.notEmpty(entry.voteId)) {
|
||||
item.addLoreLine("&7VoteId: &f" + AdminVoteLogHelpers.safe(entry.voteId));
|
||||
}
|
||||
|
||||
return new BInventoryButton(item) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteLogVoteId(plugin, clickEvent.getPlayer(), table, entry.voteId, days, 0).open();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open() {
|
||||
open(GUIMethod.CHEST);
|
||||
}
|
||||
}
|
||||
-204
@@ -1,204 +0,0 @@
|
||||
package com.bencodez.votingplugin.commands.gui.admin.votelog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.bencodez.advancedcore.api.gui.GUIHandler;
|
||||
import com.bencodez.advancedcore.api.gui.GUIMethod;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory.ClickEvent;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventoryButton;
|
||||
import com.bencodez.advancedcore.api.item.ItemBuilder;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.user.VotingPluginUser;
|
||||
import com.bencodez.votingplugin.votelog.VoteLogMysqlTable;
|
||||
|
||||
/**
|
||||
* Admin GUI: VoteLog entries for a given server (paged).
|
||||
*
|
||||
* Uses VoteLogMysqlTable instance passed in from CommandLoader.
|
||||
*/
|
||||
public class AdminVoteLogServer extends GUIHandler {
|
||||
|
||||
private final VotingPluginMain plugin;
|
||||
private final VoteLogMysqlTable table;
|
||||
private VotingPluginUser user;
|
||||
private final int page;
|
||||
|
||||
private final String server;
|
||||
private final int days;
|
||||
|
||||
private static final int PAGE_SIZE = 45;
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteLogServer.
|
||||
*
|
||||
* @param plugin the VotingPluginMain instance
|
||||
* @param sender the command sender
|
||||
* @param table the vote log table
|
||||
* @param server the server name
|
||||
* @param days the number of days to filter
|
||||
* @param page the page number
|
||||
*/
|
||||
public AdminVoteLogServer(VotingPluginMain plugin, CommandSender sender, VoteLogMysqlTable table, String server,
|
||||
int days, int page) {
|
||||
super(plugin, sender);
|
||||
this.plugin = plugin;
|
||||
this.table = table;
|
||||
this.server = server;
|
||||
this.days = Math.max(0, days);
|
||||
this.page = Math.max(0, page);
|
||||
|
||||
if (sender instanceof Player) {
|
||||
this.user = plugin.getVotingPluginUserManager().getVotingPluginUser((Player) sender);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteLogServer with user.
|
||||
*
|
||||
* @param plugin the VotingPluginMain instance
|
||||
* @param sender the command sender
|
||||
* @param table the vote log table
|
||||
* @param user the voting plugin user
|
||||
* @param server the server name
|
||||
* @param days the number of days to filter
|
||||
* @param page the page number
|
||||
*/
|
||||
public AdminVoteLogServer(VotingPluginMain plugin, CommandSender sender, VoteLogMysqlTable table,
|
||||
VotingPluginUser user, String server, int days, int page) {
|
||||
this(plugin, sender, table, server, days, page);
|
||||
this.user = user;
|
||||
if (this.user == null && sender instanceof Player) {
|
||||
this.user = plugin.getVotingPluginUserManager().getVotingPluginUser((Player) sender);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<String> getChat(CommandSender sender) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBook(Player player) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChat(CommandSender sender) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDialog(Player player) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChest(Player player) {
|
||||
try {
|
||||
BInventory inv = new BInventory("&aVoteLog - Server &7(" + AdminVoteLogHelpers.safe(server) + ")");
|
||||
inv.requirePermission("VotingPlugin.Commands.AdminVote.VoteLog");
|
||||
|
||||
if (!plugin.getConfigFile().isAlwaysCloseInventory()) {
|
||||
inv.dontClose();
|
||||
}
|
||||
|
||||
if (table == null) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.BARRIER).setName("&cVoteLog unavailable")
|
||||
.addLoreLine("&7VoteLog MySQL table is not loaded/enabled.")) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
// no-op
|
||||
}
|
||||
});
|
||||
|
||||
inv.setPages(true);
|
||||
inv.setMaxInvSize(54);
|
||||
inv.openInventory(player);
|
||||
return;
|
||||
}
|
||||
|
||||
int needed = (page + 1) * PAGE_SIZE;
|
||||
List<VoteLogMysqlTable.VoteLogEntry> rows = table.getByServer(server, days, needed);
|
||||
|
||||
int start = page * PAGE_SIZE;
|
||||
int end = Math.min(rows.size(), start + PAGE_SIZE);
|
||||
|
||||
if (start >= rows.size()) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.PAPER).setName("&eNo entries")
|
||||
.addLoreLine("&7No vote log entries on this page.").addLoreLine("&7Try going back a page.")) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
// no-op
|
||||
}
|
||||
});
|
||||
} else {
|
||||
for (int i = start; i < end; i++) {
|
||||
inv.addButton(makeEntryButton(rows.get(i)));
|
||||
}
|
||||
}
|
||||
|
||||
inv.setPages(true);
|
||||
inv.setMaxInvSize(54);
|
||||
inv.openInventory(player);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private BInventoryButton makeEntryButton(final VoteLogMysqlTable.VoteLogEntry entry) {
|
||||
VoteLogMysqlTable.VoteLogEvent event = entry.event != null ? VoteLogMysqlTable.VoteLogEvent.valueOf(entry.event)
|
||||
: null;
|
||||
|
||||
Material mat = AdminVoteLogHelpers.getMaterialForEvent(event, entry.status);
|
||||
String eventColor = AdminVoteLogHelpers.getEventColor(event);
|
||||
|
||||
String timeStr = AdminVoteLogHelpers.formatTime(entry.voteTime);
|
||||
String uuidShort = AdminVoteLogHelpers.shortUuid(entry.playerUuid);
|
||||
String voteShort = AdminVoteLogHelpers.shortUuid(entry.voteId);
|
||||
|
||||
ItemBuilder item = new ItemBuilder(mat)
|
||||
.setName(eventColor + AdminVoteLogHelpers.safe(entry.event) + " &7(" + voteShort + ")");
|
||||
|
||||
if (AdminVoteLogHelpers.notEmpty(entry.playerName) || AdminVoteLogHelpers.notEmpty(entry.playerUuid)) {
|
||||
item.addLoreLine("&7Player: &f" + AdminVoteLogHelpers.safe(entry.playerName) + " &7(" + uuidShort + ")");
|
||||
}
|
||||
if (entry.voteTime > 0) {
|
||||
item.addLoreLine("&7Time: &f" + timeStr);
|
||||
}
|
||||
if (AdminVoteLogHelpers.notEmpty(entry.server)) {
|
||||
item.addLoreLine("&7Server: &f" + AdminVoteLogHelpers.safe(entry.server));
|
||||
}
|
||||
if (AdminVoteLogHelpers.notEmpty(entry.service)) {
|
||||
item.addLoreLine("&7Service: &f" + AdminVoteLogHelpers.safe(entry.service));
|
||||
}
|
||||
if (AdminVoteLogHelpers.notEmpty(entry.context)) {
|
||||
item.addLoreLine("&7Context: &f" + AdminVoteLogHelpers.safe(entry.context));
|
||||
}
|
||||
if (AdminVoteLogHelpers.notEmpty(entry.status)) {
|
||||
item.addLoreLine("&7Status: &f" + AdminVoteLogHelpers.safe(entry.status));
|
||||
}
|
||||
if (entry.proxyCachedTotal != 0) {
|
||||
item.addLoreLine("&7Cached Total: &f" + entry.proxyCachedTotal);
|
||||
}
|
||||
if (AdminVoteLogHelpers.notEmpty(entry.voteId)) {
|
||||
item.addLoreLine("&7VoteId: &f" + AdminVoteLogHelpers.safe(entry.voteId));
|
||||
}
|
||||
|
||||
return new BInventoryButton(item) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
// vote_id is the correlation id
|
||||
new AdminVoteLogVoteId(plugin, clickEvent.getPlayer(), table, entry.voteId, days, 0).open();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open() {
|
||||
open(GUIMethod.CHEST);
|
||||
}
|
||||
}
|
||||
-152
@@ -1,152 +0,0 @@
|
||||
package com.bencodez.votingplugin.commands.gui.admin.votelog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.bencodez.advancedcore.api.gui.GUIHandler;
|
||||
import com.bencodez.advancedcore.api.gui.GUIMethod;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory.ClickEvent;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventoryButton;
|
||||
import com.bencodez.advancedcore.api.item.ItemBuilder;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.user.VotingPluginUser;
|
||||
import com.bencodez.votingplugin.votelog.VoteLogMysqlTable;
|
||||
|
||||
/**
|
||||
* Admin GUI: Select a server (distinct list), then open AdminVoteLogServer.
|
||||
*/
|
||||
public class AdminVoteLogServerSelect extends GUIHandler {
|
||||
|
||||
private final VotingPluginMain plugin;
|
||||
private final VoteLogMysqlTable table;
|
||||
private VotingPluginUser user;
|
||||
|
||||
private final int days;
|
||||
private final int page;
|
||||
|
||||
private static final int PAGE_SIZE = 45;
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteLogServerSelect.
|
||||
*
|
||||
* @param plugin the VotingPluginMain instance
|
||||
* @param sender the command sender
|
||||
* @param table the vote log table
|
||||
* @param user the voting plugin user
|
||||
* @param days the number of days to filter
|
||||
* @param page the page number
|
||||
*/
|
||||
public AdminVoteLogServerSelect(VotingPluginMain plugin, CommandSender sender, VoteLogMysqlTable table,
|
||||
VotingPluginUser user, int days, int page) {
|
||||
super(plugin, sender);
|
||||
this.plugin = plugin;
|
||||
this.table = table;
|
||||
this.user = user;
|
||||
this.days = Math.max(0, days);
|
||||
this.page = Math.max(0, page);
|
||||
|
||||
if (this.user == null && sender instanceof Player) {
|
||||
this.user = plugin.getVotingPluginUserManager().getVotingPluginUser((Player) sender);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<String> getChat(CommandSender sender) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDialog(Player player) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBook(Player player) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChat(CommandSender sender) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChest(Player player) {
|
||||
try {
|
||||
String title = "&aVoteLog - Servers";
|
||||
if (days > 0) {
|
||||
title += " &7(" + days + "d)";
|
||||
}
|
||||
|
||||
BInventory inv = new BInventory(title);
|
||||
inv.requirePermission("VotingPlugin.Commands.AdminVote.VoteLog");
|
||||
|
||||
if (!plugin.getConfigFile().isAlwaysCloseInventory()) {
|
||||
inv.dontClose();
|
||||
}
|
||||
|
||||
if (table == null) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.BARRIER).setName("&cVoteLog unavailable")
|
||||
.addLoreLine("&7VoteLog MySQL table is not loaded/enabled.")) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
// no-op
|
||||
}
|
||||
});
|
||||
|
||||
inv.setPages(true);
|
||||
inv.setMaxInvSize(54);
|
||||
inv.openInventory(player);
|
||||
return;
|
||||
}
|
||||
|
||||
int needed = (page + 1) * PAGE_SIZE;
|
||||
List<String> servers = table.getDistinctServers(days, needed);
|
||||
|
||||
int start = page * PAGE_SIZE;
|
||||
int end = Math.min(servers.size(), start + PAGE_SIZE);
|
||||
|
||||
if (start >= servers.size()) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.PAPER).setName("&eNo servers")
|
||||
.addLoreLine("&7No servers on this page.").addLoreLine("&7Try going back a page.")) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
// no-op
|
||||
}
|
||||
});
|
||||
} else {
|
||||
for (int i = start; i < end; i++) {
|
||||
String srv = servers.get(i);
|
||||
inv.addButton(makeServerButton(srv));
|
||||
}
|
||||
}
|
||||
|
||||
inv.setPages(true);
|
||||
inv.setMaxInvSize(54);
|
||||
inv.openInventory(player);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private BInventoryButton makeServerButton(final String server) {
|
||||
ItemBuilder item = new ItemBuilder(Material.PAPER).setName("&a" + AdminVoteLogHelpers.safe(server))
|
||||
.addLoreLine("&7Click to open votelog for this server")
|
||||
.addLoreLine(days > 0 ? "&7Window: &fLast " + days + " days" : "&7Window: &fAll time");
|
||||
|
||||
return new BInventoryButton(item) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteLogServer(plugin, clickEvent.getPlayer(), table, server, days, 0).open();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open() {
|
||||
open(GUIMethod.CHEST);
|
||||
}
|
||||
}
|
||||
-204
@@ -1,204 +0,0 @@
|
||||
package com.bencodez.votingplugin.commands.gui.admin.votelog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.bencodez.advancedcore.api.gui.GUIHandler;
|
||||
import com.bencodez.advancedcore.api.gui.GUIMethod;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory.ClickEvent;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventoryButton;
|
||||
import com.bencodez.advancedcore.api.item.ItemBuilder;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.user.VotingPluginUser;
|
||||
import com.bencodez.votingplugin.votelog.VoteLogMysqlTable;
|
||||
|
||||
/**
|
||||
* Admin GUI: VoteLog entries for a given service/site (paged).
|
||||
*
|
||||
* Uses VoteLogMysqlTable instance passed in from CommandLoader.
|
||||
*/
|
||||
public class AdminVoteLogService extends GUIHandler {
|
||||
|
||||
private final VotingPluginMain plugin;
|
||||
private final VoteLogMysqlTable table;
|
||||
private VotingPluginUser user;
|
||||
private final int page;
|
||||
|
||||
private final String service;
|
||||
private final int days;
|
||||
|
||||
private static final int PAGE_SIZE = 45;
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteLogService.
|
||||
*
|
||||
* @param plugin the VotingPluginMain instance
|
||||
* @param sender the command sender
|
||||
* @param table the vote log table
|
||||
* @param service the service name
|
||||
* @param days the number of days to filter
|
||||
* @param page the page number
|
||||
*/
|
||||
public AdminVoteLogService(VotingPluginMain plugin, CommandSender sender, VoteLogMysqlTable table, String service,
|
||||
int days, int page) {
|
||||
super(plugin, sender);
|
||||
this.plugin = plugin;
|
||||
this.table = table;
|
||||
this.service = service;
|
||||
this.days = days;
|
||||
this.page = Math.max(0, page);
|
||||
|
||||
if (sender instanceof Player) {
|
||||
this.user = plugin.getVotingPluginUserManager().getVotingPluginUser((Player) sender);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteLogService with user.
|
||||
*
|
||||
* @param plugin the VotingPluginMain instance
|
||||
* @param sender the command sender
|
||||
* @param table the vote log table
|
||||
* @param user the voting plugin user
|
||||
* @param service the service name
|
||||
* @param days the number of days to filter
|
||||
* @param page the page number
|
||||
*/
|
||||
public AdminVoteLogService(VotingPluginMain plugin, CommandSender sender, VoteLogMysqlTable table,
|
||||
VotingPluginUser user, String service, int days, int page) {
|
||||
this(plugin, sender, table, service, days, page);
|
||||
this.user = user;
|
||||
if (this.user == null && sender instanceof Player) {
|
||||
this.user = plugin.getVotingPluginUserManager().getVotingPluginUser((Player) sender);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<String> getChat(CommandSender sender) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBook(Player player) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChat(CommandSender sender) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDialog(Player player) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChest(Player player) {
|
||||
try {
|
||||
BInventory inv = new BInventory("&aVoteLog - Service &7(" + AdminVoteLogHelpers.safe(service) + ")");
|
||||
inv.requirePermission("VotingPlugin.Commands.AdminVote.VoteLog");
|
||||
|
||||
if (!plugin.getConfigFile().isAlwaysCloseInventory()) {
|
||||
inv.dontClose();
|
||||
}
|
||||
|
||||
if (table == null) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.BARRIER).setName("&cVoteLog unavailable")
|
||||
.addLoreLine("&7VoteLog MySQL table is not loaded/enabled.")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
// no-op
|
||||
}
|
||||
});
|
||||
|
||||
inv.setPages(true);
|
||||
inv.setMaxInvSize(54);
|
||||
inv.openInventory(player);
|
||||
return;
|
||||
}
|
||||
|
||||
int needed = (page + 1) * PAGE_SIZE;
|
||||
List<VoteLogMysqlTable.VoteLogEntry> rows = table.getByService(service, days, needed);
|
||||
|
||||
int start = page * PAGE_SIZE;
|
||||
int end = Math.min(rows.size(), start + PAGE_SIZE);
|
||||
|
||||
if (start >= rows.size()) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.PAPER).setName("&eNo entries")
|
||||
.addLoreLine("&7No vote log entries on this page.").addLoreLine("&7Try going back a page.")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
// no-op
|
||||
}
|
||||
});
|
||||
} else {
|
||||
for (int i = start; i < end; i++) {
|
||||
inv.addButton(makeEntryButton(rows.get(i)));
|
||||
}
|
||||
}
|
||||
|
||||
inv.setPages(true);
|
||||
inv.setMaxInvSize(54);
|
||||
inv.openInventory(player);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private BInventoryButton makeEntryButton(final VoteLogMysqlTable.VoteLogEntry entry) {
|
||||
VoteLogMysqlTable.VoteLogEvent event = entry.event != null ? VoteLogMysqlTable.VoteLogEvent.valueOf(entry.event)
|
||||
: null;
|
||||
|
||||
Material mat = AdminVoteLogHelpers.getMaterialForEvent(event, entry.status);
|
||||
|
||||
String eventColor = AdminVoteLogHelpers.getEventColor(event);
|
||||
|
||||
String timeStr = AdminVoteLogHelpers.formatTime(entry.voteTime);
|
||||
String uuidShort = AdminVoteLogHelpers.shortUuid(entry.playerUuid);
|
||||
String voteShort = AdminVoteLogHelpers.shortUuid(entry.voteId);
|
||||
|
||||
ItemBuilder item = new ItemBuilder(mat)
|
||||
.setName(eventColor + AdminVoteLogHelpers.safe(entry.event) + " &7(" + voteShort + ")");
|
||||
|
||||
if (AdminVoteLogHelpers.notEmpty(entry.playerName) || AdminVoteLogHelpers.notEmpty(entry.playerUuid)) {
|
||||
item.addLoreLine("&7Player: &f" + AdminVoteLogHelpers.safe(entry.playerName) + " &7(" + uuidShort + ")");
|
||||
}
|
||||
if (entry.voteTime > 0) {
|
||||
item.addLoreLine("&7Time: &f" + timeStr);
|
||||
}
|
||||
if (AdminVoteLogHelpers.notEmpty(entry.service)) {
|
||||
item.addLoreLine("&7Service: &f" + AdminVoteLogHelpers.safe(entry.service));
|
||||
}
|
||||
if (AdminVoteLogHelpers.notEmpty(entry.context)) {
|
||||
item.addLoreLine("&7Context: &f" + AdminVoteLogHelpers.safe(entry.context));
|
||||
}
|
||||
if (AdminVoteLogHelpers.notEmpty(entry.status)) {
|
||||
item.addLoreLine("&7Status: &f" + AdminVoteLogHelpers.safe(entry.status));
|
||||
}
|
||||
if (entry.proxyCachedTotal != 0) {
|
||||
item.addLoreLine("&7Cached Total: &f" + entry.proxyCachedTotal);
|
||||
}
|
||||
if (AdminVoteLogHelpers.notEmpty(entry.voteId)) {
|
||||
item.addLoreLine("&7VoteId: &f" + AdminVoteLogHelpers.safe(entry.voteId));
|
||||
}
|
||||
|
||||
return new BInventoryButton(item) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
// vote_id is the correlation id
|
||||
new AdminVoteLogVoteId(plugin, clickEvent.getPlayer(), table, entry.voteId, days, 0).open();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open() {
|
||||
open(GUIMethod.CHEST);
|
||||
}
|
||||
}
|
||||
-152
@@ -1,152 +0,0 @@
|
||||
package com.bencodez.votingplugin.commands.gui.admin.votelog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.bencodez.advancedcore.api.gui.GUIHandler;
|
||||
import com.bencodez.advancedcore.api.gui.GUIMethod;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory.ClickEvent;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventoryButton;
|
||||
import com.bencodez.advancedcore.api.item.ItemBuilder;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.user.VotingPluginUser;
|
||||
import com.bencodez.votingplugin.votelog.VoteLogMysqlTable;
|
||||
|
||||
/**
|
||||
* Admin GUI: Select a service (distinct list), then open AdminVoteLogService.
|
||||
*/
|
||||
public class AdminVoteLogServiceSelect extends GUIHandler {
|
||||
|
||||
private final VotingPluginMain plugin;
|
||||
private final VoteLogMysqlTable table;
|
||||
private VotingPluginUser user;
|
||||
|
||||
private final int days;
|
||||
private final int page;
|
||||
|
||||
private static final int PAGE_SIZE = 45;
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteLogServiceSelect.
|
||||
*
|
||||
* @param plugin the VotingPluginMain instance
|
||||
* @param sender the command sender
|
||||
* @param table the vote log table
|
||||
* @param user the voting plugin user
|
||||
* @param days the number of days to filter
|
||||
* @param page the page number
|
||||
*/
|
||||
public AdminVoteLogServiceSelect(VotingPluginMain plugin, CommandSender sender, VoteLogMysqlTable table,
|
||||
VotingPluginUser user, int days, int page) {
|
||||
super(plugin, sender);
|
||||
this.plugin = plugin;
|
||||
this.table = table;
|
||||
this.user = user;
|
||||
this.days = Math.max(0, days);
|
||||
this.page = Math.max(0, page);
|
||||
|
||||
if (this.user == null && sender instanceof Player) {
|
||||
this.user = plugin.getVotingPluginUserManager().getVotingPluginUser((Player) sender);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<String> getChat(CommandSender sender) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBook(Player player) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChat(CommandSender sender) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDialog(Player player) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChest(Player player) {
|
||||
try {
|
||||
String title = "&aVoteLog - Services";
|
||||
if (days > 0) {
|
||||
title += " &7(" + days + "d)";
|
||||
}
|
||||
|
||||
BInventory inv = new BInventory(title);
|
||||
inv.requirePermission("VotingPlugin.Commands.AdminVote.VoteLog");
|
||||
|
||||
if (!plugin.getConfigFile().isAlwaysCloseInventory()) {
|
||||
inv.dontClose();
|
||||
}
|
||||
|
||||
if (table == null) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.BARRIER).setName("&cVoteLog unavailable")
|
||||
.addLoreLine("&7VoteLog MySQL table is not loaded/enabled.")) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
// no-op
|
||||
}
|
||||
});
|
||||
|
||||
inv.setPages(true);
|
||||
inv.setMaxInvSize(54);
|
||||
inv.openInventory(player);
|
||||
return;
|
||||
}
|
||||
|
||||
int needed = (page + 1) * PAGE_SIZE;
|
||||
List<String> services = table.getDistinctServices(days, needed);
|
||||
|
||||
int start = page * PAGE_SIZE;
|
||||
int end = Math.min(services.size(), start + PAGE_SIZE);
|
||||
|
||||
if (start >= services.size()) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.PAPER).setName("&eNo services")
|
||||
.addLoreLine("&7No services on this page.").addLoreLine("&7Try going back a page.")) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
// no-op
|
||||
}
|
||||
});
|
||||
} else {
|
||||
for (int i = start; i < end; i++) {
|
||||
String svc = services.get(i);
|
||||
inv.addButton(makeServiceButton(svc));
|
||||
}
|
||||
}
|
||||
|
||||
inv.setPages(true);
|
||||
inv.setMaxInvSize(54);
|
||||
inv.openInventory(player);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private BInventoryButton makeServiceButton(final String service) {
|
||||
ItemBuilder item = new ItemBuilder(Material.PAPER).setName("&a" + AdminVoteLogHelpers.safe(service))
|
||||
.addLoreLine("&7Click to open votelog for this service")
|
||||
.addLoreLine(days > 0 ? "&7Window: &fLast " + days + " days" : "&7Window: &fAll time");
|
||||
|
||||
return new BInventoryButton(item) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteLogService(plugin, clickEvent.getPlayer(), table, service, days, 0).open();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open() {
|
||||
open(GUIMethod.CHEST);
|
||||
}
|
||||
}
|
||||
-150
@@ -1,150 +0,0 @@
|
||||
package com.bencodez.votingplugin.commands.gui.admin.votelog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.bencodez.advancedcore.api.gui.GUIHandler;
|
||||
import com.bencodez.advancedcore.api.gui.GUIMethod;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory.ClickEvent;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventoryButton;
|
||||
import com.bencodez.advancedcore.api.item.ItemBuilder;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.user.VotingPluginUser;
|
||||
import com.bencodez.votingplugin.votelog.VoteLogMysqlTable;
|
||||
|
||||
/**
|
||||
* Admin GUI: Select a service from DB (distinct services), then open
|
||||
* AdminVoteLogService.
|
||||
*/
|
||||
public class AdminVoteLogServiceSelectAll extends GUIHandler {
|
||||
|
||||
private final VotingPluginMain plugin;
|
||||
private final VoteLogMysqlTable table;
|
||||
private VotingPluginUser user;
|
||||
|
||||
private final int days;
|
||||
private final int page;
|
||||
|
||||
private static final int PAGE_SIZE = 45;
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteLogServiceSelectAll.
|
||||
*
|
||||
* @param plugin the VotingPluginMain instance
|
||||
* @param sender the command sender
|
||||
* @param table the vote log table
|
||||
* @param user the voting plugin user
|
||||
* @param days the number of days to filter
|
||||
* @param page the page number
|
||||
*/
|
||||
public AdminVoteLogServiceSelectAll(VotingPluginMain plugin, CommandSender sender, VoteLogMysqlTable table,
|
||||
VotingPluginUser user, int days, int page) {
|
||||
super(plugin, sender);
|
||||
this.plugin = plugin;
|
||||
this.table = table;
|
||||
this.user = user;
|
||||
this.days = days;
|
||||
this.page = Math.max(0, page);
|
||||
|
||||
if (this.user == null && sender instanceof Player) {
|
||||
this.user = plugin.getVotingPluginUserManager().getVotingPluginUser((Player) sender);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<String> getChat(CommandSender sender) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBook(Player player) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChat(CommandSender sender) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDialog(Player player) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChest(Player player) {
|
||||
try {
|
||||
String title = "&aVoteLog - Services";
|
||||
if (days > 0) {
|
||||
title += " &7(" + days + "d)";
|
||||
}
|
||||
|
||||
BInventory inv = new BInventory(title);
|
||||
inv.requirePermission("VotingPlugin.Commands.AdminVote.VoteLog");
|
||||
|
||||
if (!plugin.getConfigFile().isAlwaysCloseInventory()) {
|
||||
inv.dontClose();
|
||||
}
|
||||
|
||||
if (table == null) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.BARRIER).setName("&cVoteLog unavailable")
|
||||
.addLoreLine("&7VoteLog MySQL table is not loaded/enabled.")) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
}
|
||||
});
|
||||
inv.setPages(true);
|
||||
inv.setMaxInvSize(54);
|
||||
inv.openInventory(player);
|
||||
return;
|
||||
}
|
||||
|
||||
int needed = (page + 1) * PAGE_SIZE;
|
||||
List<String> services = table.getDistinctServices(days, needed);
|
||||
|
||||
int start = page * PAGE_SIZE;
|
||||
int end = Math.min(services.size(), start + PAGE_SIZE);
|
||||
|
||||
if (start >= services.size()) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.PAPER).setName("&eNo services")
|
||||
.addLoreLine("&7No services on this page.").addLoreLine("&7Try going back a page.")) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
for (int i = start; i < end; i++) {
|
||||
String svc = services.get(i);
|
||||
inv.addButton(makeServiceButton(svc));
|
||||
}
|
||||
}
|
||||
|
||||
inv.setPages(true);
|
||||
inv.setMaxInvSize(54);
|
||||
inv.openInventory(player);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private BInventoryButton makeServiceButton(String service) {
|
||||
ItemBuilder item = new ItemBuilder(Material.PAPER).setName("&a" + AdminVoteLogHelpers.safe(service))
|
||||
.addLoreLine("&7Click to open votelog for this service")
|
||||
.addLoreLine(days > 0 ? "&7Window: &fLast " + days + " days" : "&7Window: &fAll time");
|
||||
|
||||
return new BInventoryButton(item) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
new AdminVoteLogService(plugin, clickEvent.getPlayer(), table, service, days, 0).open();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open() {
|
||||
open(GUIMethod.CHEST);
|
||||
}
|
||||
}
|
||||
-239
@@ -1,239 +0,0 @@
|
||||
package com.bencodez.votingplugin.commands.gui.admin.votelog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.bencodez.advancedcore.api.gui.GUIHandler;
|
||||
import com.bencodez.advancedcore.api.gui.GUIMethod;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory.ClickEvent;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventoryButton;
|
||||
import com.bencodez.advancedcore.api.item.ItemBuilder;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.user.VotingPluginUser;
|
||||
import com.bencodez.votingplugin.votelog.VoteLogMysqlTable;
|
||||
|
||||
/**
|
||||
* Admin GUI: VoteLog stats overview.
|
||||
*
|
||||
* Uses VoteLogMysqlTable instance passed in from CommandLoader.
|
||||
*
|
||||
* Shows: - total / immediate / cached counts (VoteLogCounts) - unique voters -
|
||||
* top services - top servers
|
||||
*/
|
||||
public class AdminVoteLogStats extends GUIHandler {
|
||||
|
||||
private final VotingPluginMain plugin;
|
||||
private final VoteLogMysqlTable table;
|
||||
private VotingPluginUser user;
|
||||
|
||||
private final int days;
|
||||
|
||||
private final int topServicesLimit;
|
||||
private final int topServersLimit;
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteLogStats.
|
||||
*
|
||||
* @param plugin the VotingPluginMain instance
|
||||
* @param sender the command sender
|
||||
* @param table the vote log table
|
||||
* @param days the number of days to filter
|
||||
*/
|
||||
public AdminVoteLogStats(VotingPluginMain plugin, CommandSender sender, VoteLogMysqlTable table, int days) {
|
||||
this(plugin, sender, table, null, days, 10, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteLogStats with user and top services limit.
|
||||
*
|
||||
* @param plugin the VotingPluginMain instance
|
||||
* @param sender the command sender
|
||||
* @param table the vote log table
|
||||
* @param user the voting plugin user
|
||||
* @param days the number of days to filter
|
||||
* @param topServicesLimit the maximum number of top services to display
|
||||
*/
|
||||
public AdminVoteLogStats(VotingPluginMain plugin, CommandSender sender, VoteLogMysqlTable table,
|
||||
VotingPluginUser user, int days, int topServicesLimit) {
|
||||
this(plugin, sender, table, user, days, topServicesLimit, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteLogStats with full parameters.
|
||||
*
|
||||
* @param plugin the VotingPluginMain instance
|
||||
* @param sender the command sender
|
||||
* @param table the vote log table
|
||||
* @param user the voting plugin user
|
||||
* @param days the number of days to filter
|
||||
* @param topServicesLimit the maximum number of top services to display
|
||||
* @param topServersLimit the maximum number of top servers to display
|
||||
*/
|
||||
public AdminVoteLogStats(VotingPluginMain plugin, CommandSender sender, VoteLogMysqlTable table,
|
||||
VotingPluginUser user, int days, int topServicesLimit, int topServersLimit) {
|
||||
super(plugin, sender);
|
||||
this.plugin = plugin;
|
||||
this.table = table;
|
||||
this.user = user;
|
||||
if (this.user == null && sender instanceof Player) {
|
||||
this.user = plugin.getVotingPluginUserManager().getVotingPluginUser((Player) sender);
|
||||
}
|
||||
this.days = Math.max(0, days);
|
||||
this.topServicesLimit = topServicesLimit <= 0 ? 10 : topServicesLimit;
|
||||
this.topServersLimit = topServersLimit <= 0 ? 10 : topServersLimit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<String> getChat(CommandSender sender) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBook(Player player) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChat(CommandSender sender) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDialog(Player player) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChest(Player player) {
|
||||
try {
|
||||
String title = "&aVoteLog - Stats";
|
||||
if (days > 0) {
|
||||
title += " &7(" + days + "d)";
|
||||
}
|
||||
|
||||
BInventory inv = new BInventory(title);
|
||||
inv.requirePermission("VotingPlugin.Commands.AdminVote.VoteLog");
|
||||
|
||||
if (!plugin.getConfigFile().isAlwaysCloseInventory()) {
|
||||
inv.dontClose();
|
||||
}
|
||||
|
||||
if (table == null) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.BARRIER).setName("&cVoteLog unavailable")
|
||||
.addLoreLine("&7VoteLog MySQL table is not loaded/enabled.")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
// no-op
|
||||
}
|
||||
});
|
||||
|
||||
inv.setPages(true);
|
||||
inv.setMaxInvSize(54);
|
||||
inv.openInventory(player);
|
||||
return;
|
||||
}
|
||||
|
||||
// counts
|
||||
VoteLogMysqlTable.VoteLogCounts counts = table.getCounts(days);
|
||||
long uniques = table.getUniqueVoters(days);
|
||||
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.BOOK).setName("&aOverview")
|
||||
.addLoreLine("&7Total Votes: &f" + counts.total).addLoreLine("&7Immediate: &f" + counts.immediate)
|
||||
.addLoreLine("&7Cached: &f" + counts.cached).addLoreLine("&7Unique Voters: &f" + uniques)
|
||||
.addLoreLine(days > 0 ? "&7Window: &fLast " + days + " days" : "&7Window: &fAll time")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
// no-op
|
||||
}
|
||||
});
|
||||
|
||||
// top services header
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.MAP).setName("&aTop Services")
|
||||
.addLoreLine("&7Top " + topServicesLimit + " services/sites:")
|
||||
.addLoreLine("&8(Click does nothing)")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
// no-op
|
||||
}
|
||||
});
|
||||
|
||||
int shownServices = 0;
|
||||
for (VoteLogMysqlTable.ServiceCount sc : table.getTopServices(days, topServicesLimit)) {
|
||||
shownServices++;
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.PAPER)
|
||||
.setName("&a#" + shownServices + " &f" + AdminVoteLogHelpers.safe(sc.service))
|
||||
.addLoreLine("&7Votes: &f" + sc.votes)
|
||||
.addLoreLine(days > 0 ? "&7Window: &fLast " + days + " days" : "&7Window: &fAll time")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
// no-op (could open AdminVoteLogService in future)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (shownServices == 0) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.PAPER).setName("&eNo service data")
|
||||
.addLoreLine("&7No services found in the log.")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
// no-op
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// top servers header
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.COMPASS).setName("&aTop Servers")
|
||||
.addLoreLine("&7Top " + topServersLimit + " servers:").addLoreLine("&8(Click does nothing)")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
// no-op
|
||||
}
|
||||
});
|
||||
|
||||
int shownServers = 0;
|
||||
for (VoteLogMysqlTable.ServerCount sc : table.getTopServers(days, topServersLimit)) {
|
||||
shownServers++;
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.PAPER)
|
||||
.setName("&a#" + shownServers + " &f" + AdminVoteLogHelpers.safe(sc.server))
|
||||
.addLoreLine("&7Votes: &f" + sc.votes)
|
||||
.addLoreLine(days > 0 ? "&7Window: &fLast " + days + " days" : "&7Window: &fAll time")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
// no-op (could open AdminVoteLogServer in future)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (shownServers == 0) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.PAPER).setName("&eNo server data")
|
||||
.addLoreLine("&7No servers found in the log.")) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
// no-op
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
inv.setPages(true);
|
||||
inv.setMaxInvSize(54);
|
||||
inv.openInventory(player);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open() {
|
||||
open(GUIMethod.CHEST);
|
||||
}
|
||||
}
|
||||
-257
@@ -1,257 +0,0 @@
|
||||
package com.bencodez.votingplugin.commands.gui.admin.votelog;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.bencodez.advancedcore.api.gui.GUIHandler;
|
||||
import com.bencodez.advancedcore.api.gui.GUIMethod;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory.ClickEvent;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventoryButton;
|
||||
import com.bencodez.advancedcore.api.item.ItemBuilder;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.user.VotingPluginUser;
|
||||
import com.bencodez.votingplugin.votelog.VoteLogMysqlTable;
|
||||
|
||||
/**
|
||||
* Admin GUI: VoteLog entries for a given voteId (paged).
|
||||
*
|
||||
* vote_id is NOT unique; this shows ALL rows for the correlation id.
|
||||
*/
|
||||
public class AdminVoteLogVoteId extends GUIHandler {
|
||||
|
||||
private final VotingPluginMain plugin;
|
||||
private final VoteLogMysqlTable table;
|
||||
private VotingPluginUser user;
|
||||
private final int page;
|
||||
|
||||
private final String voteId;
|
||||
private final int days;
|
||||
|
||||
private static final int PAGE_SIZE = 45;
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteLogVoteId.
|
||||
*
|
||||
* @param plugin the VotingPluginMain instance
|
||||
* @param sender the command sender
|
||||
* @param table the vote log table
|
||||
* @param voteId the vote ID to filter
|
||||
* @param days the number of days to filter
|
||||
* @param page the page number
|
||||
*/
|
||||
public AdminVoteLogVoteId(VotingPluginMain plugin, CommandSender sender, VoteLogMysqlTable table, String voteId,
|
||||
int days, int page) {
|
||||
super(plugin, sender);
|
||||
this.plugin = plugin;
|
||||
this.table = table;
|
||||
this.voteId = voteId;
|
||||
this.days = days;
|
||||
this.page = Math.max(0, page);
|
||||
|
||||
if (sender instanceof Player) {
|
||||
this.user = plugin.getVotingPluginUserManager().getVotingPluginUser((Player) sender);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for AdminVoteLogVoteId with user.
|
||||
*
|
||||
* @param plugin the VotingPluginMain instance
|
||||
* @param sender the command sender
|
||||
* @param table the vote log table
|
||||
* @param user the voting plugin user
|
||||
* @param voteId the vote ID to filter
|
||||
* @param days the number of days to filter
|
||||
* @param page the page number
|
||||
*/
|
||||
public AdminVoteLogVoteId(VotingPluginMain plugin, CommandSender sender, VoteLogMysqlTable table,
|
||||
VotingPluginUser user, String voteId, int days, int page) {
|
||||
this(plugin, sender, table, voteId, days, page);
|
||||
this.user = user;
|
||||
if (this.user == null && sender instanceof Player) {
|
||||
this.user = plugin.getVotingPluginUserManager().getVotingPluginUser((Player) sender);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<String> getChat(CommandSender sender) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBook(Player player) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChat(CommandSender sender) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDialog(Player player) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChest(Player player) {
|
||||
try {
|
||||
String title = "&aVoteLog - VoteId";
|
||||
if (voteId != null && !voteId.isEmpty()) {
|
||||
title += " &7(" + shortId(voteId) + ")";
|
||||
}
|
||||
|
||||
BInventory inv = new BInventory(title);
|
||||
inv.requirePermission("VotingPlugin.Commands.AdminVote.VoteLog");
|
||||
|
||||
if (!plugin.getConfigFile().isAlwaysCloseInventory()) {
|
||||
inv.dontClose();
|
||||
}
|
||||
|
||||
if (table == null) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.BARRIER).setName("&cVoteLog unavailable")
|
||||
.addLoreLine("&7VoteLog MySQL table is not loaded/enabled.")) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
// no-op
|
||||
}
|
||||
});
|
||||
|
||||
inv.setPages(true);
|
||||
inv.setMaxInvSize(54);
|
||||
inv.openInventory(player);
|
||||
return;
|
||||
}
|
||||
|
||||
if (voteId == null || voteId.isEmpty()) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.PAPER).setName("&eNo voteId provided")
|
||||
.addLoreLine("&7Missing voteId.")) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
// no-op
|
||||
}
|
||||
});
|
||||
|
||||
inv.setPages(true);
|
||||
inv.setMaxInvSize(54);
|
||||
inv.openInventory(player);
|
||||
return;
|
||||
}
|
||||
|
||||
int needed = (page + 1) * PAGE_SIZE;
|
||||
|
||||
// vote_id is not unique -> show ALL rows for it (paged)
|
||||
List<VoteLogMysqlTable.VoteLogEntry> rows = table.getByVoteIdAll(voteId, days, needed);
|
||||
|
||||
int start = page * PAGE_SIZE;
|
||||
int end = Math.min(rows.size(), start + PAGE_SIZE);
|
||||
|
||||
if (start >= rows.size()) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(Material.PAPER).setName("&eNo entries")
|
||||
.addLoreLine("&7No vote log entries on this page.").addLoreLine("&7Try going back a page.")) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
// no-op
|
||||
}
|
||||
});
|
||||
} else {
|
||||
for (int i = start; i < end; i++) {
|
||||
inv.addButton(makeEntryButton(rows.get(i)));
|
||||
}
|
||||
}
|
||||
|
||||
inv.setPages(true);
|
||||
inv.setMaxInvSize(54);
|
||||
inv.openInventory(player);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private BInventoryButton makeEntryButton(VoteLogMysqlTable.VoteLogEntry entry) {
|
||||
VoteLogMysqlTable.VoteLogEvent event = entry.event != null ? VoteLogMysqlTable.VoteLogEvent.valueOf(entry.event)
|
||||
: null;
|
||||
|
||||
Material mat = AdminVoteLogHelpers.getMaterialForEvent(event, entry.status);
|
||||
|
||||
String eventColor = AdminVoteLogHelpers.getEventColor(event);
|
||||
|
||||
String timeStr = formatTime(entry.voteTime);
|
||||
String uuidShort = shortId(entry.playerUuid);
|
||||
String voteShort = shortId(entry.voteId);
|
||||
|
||||
ItemBuilder item = new ItemBuilder(mat)
|
||||
.setName(eventColor + AdminVoteLogHelpers.safe(entry.event) + " &7(" + voteShort + ")");
|
||||
|
||||
// Only add lore lines when the value is actually set / meaningful
|
||||
// (prevents blank/0/default noise)
|
||||
if (notEmpty(entry.playerName) || notEmpty(entry.playerUuid)) {
|
||||
item.addLoreLine("&7Player: &f" + safe(entry.playerName) + " &7(" + uuidShort + ")");
|
||||
}
|
||||
|
||||
if (entry.voteTime > 0) {
|
||||
item.addLoreLine("&7Time: &f" + timeStr);
|
||||
}
|
||||
|
||||
if (notEmpty(entry.service)) {
|
||||
item.addLoreLine("&7Service: &f" + safe(entry.service));
|
||||
}
|
||||
|
||||
if (notEmpty(entry.context)) {
|
||||
item.addLoreLine("&7Context: &f" + safe(entry.context));
|
||||
}
|
||||
|
||||
if (notEmpty(entry.status)) {
|
||||
item.addLoreLine("&7Status: &f" + safe(entry.status));
|
||||
}
|
||||
|
||||
if (entry.proxyCachedTotal != 0) {
|
||||
item.addLoreLine("&7Cached Total: &f" + entry.proxyCachedTotal);
|
||||
}
|
||||
|
||||
if (notEmpty(entry.voteId)) {
|
||||
item.addLoreLine("&7VoteId: &f" + safe(entry.voteId));
|
||||
}
|
||||
|
||||
return new BInventoryButton(item) {
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
// read-only
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private String formatTime(long millis) {
|
||||
try {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
return sdf.format(new Date(millis));
|
||||
} catch (Exception e) {
|
||||
return String.valueOf(millis);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean notEmpty(String s) {
|
||||
return s != null && !s.trim().isEmpty();
|
||||
}
|
||||
|
||||
private String shortId(String s) {
|
||||
if (s == null || s.isEmpty()) {
|
||||
return "unknown";
|
||||
}
|
||||
return s.length() > 8 ? s.substring(0, 8) : s;
|
||||
}
|
||||
|
||||
private String safe(String s) {
|
||||
return s == null ? "" : s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open() {
|
||||
open(GUIMethod.CHEST);
|
||||
}
|
||||
}
|
||||
-260
@@ -1,260 +0,0 @@
|
||||
package com.bencodez.votingplugin.commands.gui.player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.bencodez.advancedcore.api.gui.GUIHandler;
|
||||
import com.bencodez.advancedcore.api.gui.GUIMethod;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory.ClickEvent;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventoryButton;
|
||||
import com.bencodez.advancedcore.api.item.ItemBuilder;
|
||||
import com.bencodez.advancedcore.api.rewards.RewardBuilder;
|
||||
import com.bencodez.simpleapi.messages.MessageAPI;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.user.VotingPluginUser;
|
||||
import com.bencodez.votingplugin.voteshop.VoteShopManager;
|
||||
import com.bencodez.votingplugin.voteshop.service.VoteShopPurchaseResult;
|
||||
import com.bencodez.votingplugin.voteshop.shop.VoteShopCategory;
|
||||
import com.bencodez.votingplugin.voteshop.shop.VoteShopCategoryButton;
|
||||
import com.bencodez.votingplugin.voteshop.shop.VoteShopDefinition;
|
||||
import com.bencodez.votingplugin.voteshop.shop.VoteShopEntry;
|
||||
import com.bencodez.votingplugin.voteshop.shop.VoteShopExtraItem;
|
||||
import com.bencodez.votingplugin.voteshop.shop.VoteShopItem;
|
||||
|
||||
/**
|
||||
* Main vote shop GUI.
|
||||
*/
|
||||
public class VoteShop extends GUIHandler {
|
||||
|
||||
private VotingPluginMain plugin;
|
||||
|
||||
private VotingPluginUser user;
|
||||
|
||||
/**
|
||||
* Creates the GUI.
|
||||
*
|
||||
* @param plugin the plugin
|
||||
* @param player the sender
|
||||
* @param user the user
|
||||
*/
|
||||
public VoteShop(VotingPluginMain plugin, CommandSender player, VotingPluginUser user) {
|
||||
super(plugin, player);
|
||||
this.plugin = plugin;
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<String> getChat(CommandSender sender) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBook(Player player) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChat(CommandSender sender) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDialog(Player player) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChest(Player player) {
|
||||
VoteShopManager manager = plugin.getVoteShopManager();
|
||||
VoteShopDefinition definition = manager.getDefinition();
|
||||
|
||||
if (!definition.isEnabled()) {
|
||||
player.sendMessage(MessageAPI.colorize(definition.getDisabledMessage()));
|
||||
return;
|
||||
}
|
||||
|
||||
VotingPluginUser currentUser = getUser(player);
|
||||
BInventory inv = new BInventory(definition.getTitle());
|
||||
inv.addPlaceholder("points", String.valueOf(currentUser.getPoints()));
|
||||
inv.addPlaceholder("sitesavailable", String.valueOf(currentUser.getSitesNotVotedOn()));
|
||||
inv.dontClose();
|
||||
|
||||
for (VoteShopEntry entry : definition.getMainEntries().values()) {
|
||||
if (entry instanceof VoteShopItem) {
|
||||
addItemButton(inv, player, currentUser, (VoteShopItem) entry, null);
|
||||
} else if (entry instanceof VoteShopCategoryButton) {
|
||||
addCategoryButton(inv, player, currentUser, (VoteShopCategoryButton) entry);
|
||||
}
|
||||
}
|
||||
|
||||
for (VoteShopExtraItem extraItem : definition.getExtraItems().values()) {
|
||||
addExtraItemButton(inv, player, currentUser, extraItem);
|
||||
}
|
||||
|
||||
if (definition.isBackButton()) {
|
||||
inv.addButton(plugin.getCommandLoader().getBackButton(currentUser));
|
||||
}
|
||||
|
||||
inv.openInventory(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an item button.
|
||||
*
|
||||
* @param inv the inventory
|
||||
* @param player the player
|
||||
* @param currentUser the user
|
||||
* @param item the item
|
||||
* @param category the category, null for main shop
|
||||
*/
|
||||
protected void addItemButton(BInventory inv, final Player player, final VotingPluginUser currentUser,
|
||||
final VoteShopItem item, final VoteShopCategory category) {
|
||||
VoteShopPurchaseResult validation = plugin.getVoteShopManager().getPurchaseService().validatePurchase(player,
|
||||
currentUser, item);
|
||||
boolean hasPermission = plugin.getVoteShopManager().getPurchaseService().hasPermission(player,
|
||||
item.getPermission());
|
||||
|
||||
if ((!hasPermission && item.isHideOnNoPermission()) || (validation == VoteShopPurchaseResult.LIMIT_REACHED
|
||||
&& plugin.getVoteShopManager().getDefinition().isHideLimitedReached())) {
|
||||
return;
|
||||
}
|
||||
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(item.getDisplaySection())) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent event) {
|
||||
VotingPluginUser clickedUser = getUser(event.getPlayer());
|
||||
if (plugin.getConfigFile().isExtraVoteShopCheck()) {
|
||||
clickedUser.cache();
|
||||
}
|
||||
|
||||
if (item.isNotBuyable()) {
|
||||
clickedUser.sendMessage(plugin.getConfigFile().getFormatShopNotPurchasable());
|
||||
return;
|
||||
}
|
||||
|
||||
VoteShopPurchaseResult clickValidation = plugin.getVoteShopManager().getPurchaseService()
|
||||
.validatePurchase(event.getPlayer(), clickedUser, item);
|
||||
if (clickValidation != VoteShopPurchaseResult.SUCCESS) {
|
||||
plugin.getVoteShopManager().getPurchaseService().sendFailureMessage(event.getPlayer(), clickedUser,
|
||||
item, clickValidation);
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.isRequireConfirmation()) {
|
||||
if (plugin.getShopFile().isShopConfirmPurchaseUseDialog()) {
|
||||
new VoteShopConfirm(plugin, event.getPlayer(), clickedUser, item, category)
|
||||
.open(GUIMethod.DIALOG);
|
||||
return;
|
||||
} else {
|
||||
new VoteShopConfirm(plugin, event.getPlayer(), clickedUser, item, category)
|
||||
.open(GUIMethod.CHEST);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
handlePurchase(event.getPlayer(), clickedUser, item, category);
|
||||
}
|
||||
}.addData("identifier", item.getIdentifier()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a category button.
|
||||
*
|
||||
* @param inv the inventory
|
||||
* @param player the player
|
||||
* @param currentUser the user
|
||||
* @param button the button
|
||||
*/
|
||||
protected void addCategoryButton(BInventory inv, final Player player, final VotingPluginUser currentUser,
|
||||
final VoteShopCategoryButton button) {
|
||||
boolean hasPermission = plugin.getVoteShopManager().getPurchaseService().hasPermission(player,
|
||||
button.getPermission());
|
||||
if (!hasPermission && button.isHideOnNoPermission()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final VoteShopCategory category = plugin.getVoteShopManager().getCategory(button.getCategoryId());
|
||||
if (category == null) {
|
||||
plugin.extraDebug(
|
||||
"VoteShop: missing category '" + button.getCategoryId() + "' for entry " + button.getIdentifier());
|
||||
return;
|
||||
}
|
||||
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(button.getDisplaySection())) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent event) {
|
||||
new VoteShopCategoryMenu(plugin, event.getPlayer(), currentUser, category).open(GUIMethod.CHEST);
|
||||
}
|
||||
}.addData("identifier", button.getIdentifier()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an extra item button.
|
||||
*
|
||||
* @param inv the inventory
|
||||
* @param player the player
|
||||
* @param currentUser the user
|
||||
* @param extraItem the extra item
|
||||
*/
|
||||
protected void addExtraItemButton(BInventory inv, final Player player, final VotingPluginUser currentUser,
|
||||
final VoteShopExtraItem extraItem) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(extraItem.getDisplaySection())) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent clickEvent) {
|
||||
plugin.getCommandLoader().processSlotClick(player, currentUser, extraItem.getIdentifier());
|
||||
new RewardBuilder(plugin.getShopFile().getData(),
|
||||
"ExtraItems." + extraItem.getIdentifier() + "."
|
||||
+ clickEvent.getButton().getLastRewardsPath(player))
|
||||
.setGiveOffline(false).send(clickEvent.getPlayer());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a completed purchase.
|
||||
*
|
||||
* @param player the player
|
||||
* @param currentUser the user
|
||||
* @param item the item
|
||||
* @param category the category or null
|
||||
*/
|
||||
protected void handlePurchase(Player player, VotingPluginUser currentUser, VoteShopItem item,
|
||||
VoteShopCategory category) {
|
||||
VoteShopPurchaseResult result = plugin.getVoteShopManager().purchase(player, currentUser, item);
|
||||
if (result != VoteShopPurchaseResult.SUCCESS) {
|
||||
plugin.getVoteShopManager().getPurchaseService().sendFailureMessage(player, currentUser, item, result);
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getCommandLoader().processSlotClick(player, currentUser, item.getIdentifier());
|
||||
if (plugin.getVoteShopManager().getDefinition().isReopenGuiOnPurchase()) {
|
||||
if (category != null) {
|
||||
new VoteShopCategoryMenu(plugin, player, currentUser, category).open(GUIMethod.CHEST);
|
||||
} else {
|
||||
plugin.getCommandLoader().processSlotClick(player, currentUser, "shop");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current user.
|
||||
*
|
||||
* @param player the player
|
||||
* @return the user
|
||||
*/
|
||||
protected VotingPluginUser getUser(Player player) {
|
||||
if (user == null) {
|
||||
user = plugin.getVotingPluginUserManager().getVotingPluginUser(player);
|
||||
}
|
||||
return plugin.getVotingPluginUserManager().getVotingPluginUser(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open() {
|
||||
open(GUIMethod.CHEST);
|
||||
}
|
||||
}
|
||||
-167
@@ -1,167 +0,0 @@
|
||||
package com.bencodez.votingplugin.commands.gui.player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.bencodez.advancedcore.api.gui.GUIHandler;
|
||||
import com.bencodez.advancedcore.api.gui.GUIMethod;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory.ClickEvent;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventoryButton;
|
||||
import com.bencodez.advancedcore.api.item.ItemBuilder;
|
||||
import com.bencodez.simpleapi.messages.MessageAPI;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.user.VotingPluginUser;
|
||||
import com.bencodez.votingplugin.voteshop.shop.VoteShopCategory;
|
||||
import com.bencodez.votingplugin.voteshop.shop.VoteShopCategoryButton;
|
||||
import com.bencodez.votingplugin.voteshop.shop.VoteShopEntry;
|
||||
import com.bencodez.votingplugin.voteshop.shop.VoteShopItem;
|
||||
|
||||
/**
|
||||
* Vote shop category GUI.
|
||||
*/
|
||||
public class VoteShopCategoryMenu extends GUIHandler {
|
||||
|
||||
private VoteShopCategory category;
|
||||
|
||||
private VotingPluginMain plugin;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private VotingPluginUser user;
|
||||
|
||||
/**
|
||||
* Creates the GUI.
|
||||
*
|
||||
* @param plugin the plugin
|
||||
* @param player the sender
|
||||
* @param user the user
|
||||
* @param category the category
|
||||
*/
|
||||
public VoteShopCategoryMenu(VotingPluginMain plugin, CommandSender player, VotingPluginUser user,
|
||||
VoteShopCategory category) {
|
||||
super(plugin, player);
|
||||
this.plugin = plugin;
|
||||
this.user = user;
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<String> getChat(CommandSender sender) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBook(Player player) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChat(CommandSender sender) {
|
||||
}
|
||||
@Override
|
||||
public void onDialog(Player player) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChest(Player player) {
|
||||
if (!plugin.getVoteShopManager().getDefinition().isEnabled()) {
|
||||
player.sendMessage(MessageAPI.colorize(plugin.getVoteShopManager().getDefinition().getDisabledMessage()));
|
||||
return;
|
||||
}
|
||||
|
||||
VotingPluginUser currentUser = plugin.getVotingPluginUserManager().getVotingPluginUser(player);
|
||||
BInventory inv = new BInventory(category.getName());
|
||||
inv.addPlaceholder("points", String.valueOf(currentUser.getPoints()));
|
||||
inv.addPlaceholder("sitesavailable", String.valueOf(currentUser.getSitesNotVotedOn()));
|
||||
inv.dontClose();
|
||||
|
||||
for (VoteShopEntry entry : category.getEntries().values()) {
|
||||
if (entry instanceof VoteShopItem) {
|
||||
addItemButton(inv, player, currentUser, (VoteShopItem) entry);
|
||||
} else if (entry instanceof VoteShopCategoryButton) {
|
||||
addCategoryButton(inv, player, currentUser, (VoteShopCategoryButton) entry);
|
||||
}
|
||||
}
|
||||
|
||||
if (category.isBackButton()) {
|
||||
addBackButton(inv, player, currentUser, category.getId());
|
||||
}
|
||||
|
||||
inv.openInventory(player);
|
||||
}
|
||||
|
||||
private void addBackButton(BInventory inv, Player player, VotingPluginUser user, String category) {
|
||||
|
||||
if (!plugin.getShopFile().isCategoryBackButtonEnabled(category)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ConfigurationSection section = plugin.getShopFile().getCategoryBackButtonItem(category);
|
||||
|
||||
if (section == null) {
|
||||
section = plugin.getShopFile().getDefaultBackButtonItem();
|
||||
}
|
||||
|
||||
ItemBuilder builder;
|
||||
|
||||
if (section != null) {
|
||||
builder = new ItemBuilder(section);
|
||||
} else {
|
||||
builder = new ItemBuilder(Material.ARROW);
|
||||
builder.setName("&cBack");
|
||||
}
|
||||
|
||||
inv.addButton(new BInventoryButton(builder) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent event) {
|
||||
new VoteShop(plugin, player, user).open();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an item button.
|
||||
*
|
||||
* @param inv the inventory
|
||||
* @param player the player
|
||||
* @param currentUser the user
|
||||
* @param item the item
|
||||
*/
|
||||
protected void addItemButton(BInventory inv, final Player player, final VotingPluginUser currentUser,
|
||||
final VoteShopItem item) {
|
||||
new VoteShop(plugin, player, currentUser).addItemButton(inv, player, currentUser, item, category);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a category button.
|
||||
*
|
||||
* @param inv the inventory
|
||||
* @param player the player
|
||||
* @param currentUser the user
|
||||
* @param button the button
|
||||
*/
|
||||
protected void addCategoryButton(BInventory inv, final Player player, final VotingPluginUser currentUser,
|
||||
final VoteShopCategoryButton button) {
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(button.getDisplaySection())) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent event) {
|
||||
VoteShopCategory target = plugin.getVoteShopManager().getCategory(button.getCategoryId());
|
||||
if (target != null) {
|
||||
new VoteShopCategoryMenu(plugin, event.getPlayer(), currentUser, target).open(GUIMethod.CHEST);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open() {
|
||||
open(GUIMethod.CHEST);
|
||||
}
|
||||
}
|
||||
-158
@@ -1,158 +0,0 @@
|
||||
package com.bencodez.votingplugin.commands.gui.player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.bencodez.advancedcore.api.gui.GUIHandler;
|
||||
import com.bencodez.advancedcore.api.gui.GUIMethod;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventory.ClickEvent;
|
||||
import com.bencodez.advancedcore.api.inventory.BInventoryButton;
|
||||
import com.bencodez.advancedcore.api.item.ItemBuilder;
|
||||
import com.bencodez.simpleapi.player.PlayerUtils;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.user.VotingPluginUser;
|
||||
import com.bencodez.votingplugin.voteshop.service.VoteShopPurchaseResult;
|
||||
import com.bencodez.votingplugin.voteshop.shop.VoteShopCategory;
|
||||
import com.bencodez.votingplugin.voteshop.shop.VoteShopItem;
|
||||
|
||||
/**
|
||||
* Confirmation GUI for vote shop purchases.
|
||||
*/
|
||||
public class VoteShopConfirm extends GUIHandler {
|
||||
|
||||
private VoteShopCategory category;
|
||||
|
||||
private VoteShopItem item;
|
||||
|
||||
private VotingPluginMain plugin;
|
||||
|
||||
private VotingPluginUser user;
|
||||
|
||||
/**
|
||||
* Creates the GUI.
|
||||
*
|
||||
* @param plugin the plugin
|
||||
* @param player the sender
|
||||
* @param user the user
|
||||
* @param item the item
|
||||
* @param category the category or null
|
||||
*/
|
||||
public VoteShopConfirm(VotingPluginMain plugin, CommandSender player, VotingPluginUser user, VoteShopItem item,
|
||||
VoteShopCategory category) {
|
||||
super(plugin, player);
|
||||
this.plugin = plugin;
|
||||
this.user = user;
|
||||
this.item = item;
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<String> getChat(CommandSender sender) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBook(Player player) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChat(CommandSender sender) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChest(final Player player) {
|
||||
PlayerUtils.setPlayerMeta(plugin, player, "ident", item.getIdentifier());
|
||||
BInventory inv = new BInventory(plugin.getShopFile().getShopConfirmPurchaseTitle());
|
||||
inv.dontClose();
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(plugin.getShopFile().getShopConfirmPurchaseYesItem())) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent event) {
|
||||
user.cache();
|
||||
VoteShopPurchaseResult result = plugin.getVoteShopManager().purchase(player, user, item);
|
||||
if (result != VoteShopPurchaseResult.SUCCESS) {
|
||||
plugin.getVoteShopManager().getPurchaseService().sendFailureMessage(player, user, item, result);
|
||||
returnToPrevious(event.getPlayer());
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getCommandLoader().processSlotClick(player, user, item.getIdentifier());
|
||||
if (item.isCloseGUI()) {
|
||||
event.closeInventory();
|
||||
} else {
|
||||
returnToPrevious(event.getPlayer());
|
||||
}
|
||||
}
|
||||
});
|
||||
inv.addButton(new BInventoryButton(new ItemBuilder(plugin.getShopFile().getShopConfirmPurchaseNoItem())) {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent event) {
|
||||
if (item.isCloseGUI()) {
|
||||
event.closeInventory();
|
||||
} else {
|
||||
returnToPrevious(event.getPlayer());
|
||||
}
|
||||
}
|
||||
});
|
||||
inv.openInventory(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDialog(Player player) {
|
||||
PlayerUtils.setPlayerMeta(plugin, player, "ident", item.getIdentifier());
|
||||
|
||||
plugin.getDialogService().confirmation(player).placeholder("player", user.getPlayerName())
|
||||
.placeholder("identifier", item.getIdentifier())
|
||||
.title(plugin.getShopFile().getShopConfirmPurchaseTitle())
|
||||
.body("&7Confirm purchase of &e%identifier%&7?")
|
||||
.yesText(new ItemBuilder(plugin.getShopFile().getShopConfirmPurchaseYesItem()).getName())
|
||||
.noText(new ItemBuilder(plugin.getShopFile().getShopConfirmPurchaseNoItem()).getName())
|
||||
.onYes(payload -> {
|
||||
Player clicked = player.getServer().getPlayer(payload.owner());
|
||||
if (clicked == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
user.cache();
|
||||
VoteShopPurchaseResult result = plugin.getVoteShopManager().purchase(clicked, user, item);
|
||||
if (result != VoteShopPurchaseResult.SUCCESS) {
|
||||
plugin.getVoteShopManager().getPurchaseService().sendFailureMessage(clicked, user, item,
|
||||
result);
|
||||
returnToPrevious(clicked);
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getCommandLoader().processSlotClick(clicked, user, item.getIdentifier());
|
||||
if (!item.isCloseGUI()) {
|
||||
returnToPrevious(clicked);
|
||||
}
|
||||
}).onNo(payload -> {
|
||||
Player clicked = player.getServer().getPlayer(payload.owner());
|
||||
if (clicked != null) {
|
||||
returnToPrevious(clicked);
|
||||
}
|
||||
}).open();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns to the previous GUI.
|
||||
*
|
||||
* @param player the player
|
||||
*/
|
||||
protected void returnToPrevious(Player player) {
|
||||
if (category != null) {
|
||||
new VoteShopCategoryMenu(plugin, player, user, category).open(GUIMethod.CHEST);
|
||||
} else {
|
||||
new VoteShop(plugin, player, user).open(GUIMethod.CHEST);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open() {
|
||||
open(GUIMethod.CHEST);
|
||||
}
|
||||
}
|
||||
-141
@@ -1,141 +0,0 @@
|
||||
package com.bencodez.votingplugin.commands.tabcompleter;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerCommandSendEvent;
|
||||
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
|
||||
/**
|
||||
* Filters VotingPlugin command aliases from tab completion when aliases are
|
||||
* disabled.
|
||||
* <p>
|
||||
* This listener ONLY hides aliases that are explicitly listed in VotingPlugin's
|
||||
* plugin.yml under the "aliases:" section for the "vote" and "adminvote"
|
||||
* commands.
|
||||
* <p>
|
||||
* It will NOT hide unrelated commands such as "/votechallenge" unless that
|
||||
* exact command name is an alias defined in VotingPlugin's plugin.yml.
|
||||
*/
|
||||
public class AliasCommandFilterListener implements Listener {
|
||||
|
||||
private final VotingPluginMain plugin;
|
||||
|
||||
/**
|
||||
* Cached lowercase alias names (without '/') that should be hidden when alias
|
||||
* loading is disabled.
|
||||
*/
|
||||
private final Set<String> aliasesToHide;
|
||||
|
||||
/**
|
||||
* Creates a new listener and caches aliases from VotingPlugin's plugin.yml.
|
||||
*
|
||||
* @param plugin VotingPlugin main instance
|
||||
*/
|
||||
public AliasCommandFilterListener(VotingPluginMain plugin) {
|
||||
this.plugin = plugin;
|
||||
this.aliasesToHide = buildAliasesToHide(plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes VotingPlugin aliases from the player's available command list when
|
||||
* alias loading is disabled.
|
||||
*
|
||||
* @param event Bukkit command send event
|
||||
*/
|
||||
@EventHandler
|
||||
public void onPlayerCommandSend(PlayerCommandSendEvent event) {
|
||||
// Only hide when aliases feature is disabled
|
||||
if (plugin.getConfigFile().isLoadCommandAliases()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove ONLY exact alias command names (not "vote*" prefixes)
|
||||
event.getCommands().removeIf(this::shouldHideCommand);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a specific command should be hidden.
|
||||
*
|
||||
* @param cmd Command name (without '/'), possibly namespaced (e.g.
|
||||
* "otherplugin:vote")
|
||||
* @return true if the command should be hidden
|
||||
*/
|
||||
private boolean shouldHideCommand(String cmd) {
|
||||
String name = cmd.toLowerCase(Locale.ROOT);
|
||||
|
||||
// Never touch namespaced commands (otherplugin:vote)
|
||||
if (name.contains(":")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Always keep the primary commands visible
|
||||
if (name.equals("vote") || name.equals("adminvote")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Hide only exact aliases defined in VotingPlugin plugin.yml
|
||||
return aliasesToHide.contains(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the set of aliases to hide from VotingPlugin's plugin.yml.
|
||||
* <p>
|
||||
* Bukkit exposes the plugin.yml "commands" section via
|
||||
* {@code plugin.getDescription().getCommands()}. Each command entry is a map
|
||||
* that may contain an "aliases" key.
|
||||
*
|
||||
* @param plugin VotingPlugin main instance
|
||||
* @return a lowercase set of alias command names
|
||||
*/
|
||||
private Set<String> buildAliasesToHide(VotingPluginMain plugin) {
|
||||
Set<String> aliases = new HashSet<>();
|
||||
|
||||
Map<String, Map<String, Object>> commands = plugin.getDescription().getCommands();
|
||||
if (commands == null || commands.isEmpty()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
// Only consider aliases for these main commands
|
||||
addAliasesFor(commands, "vote", aliases);
|
||||
addAliasesFor(commands, "adminvote", aliases);
|
||||
|
||||
return Collections.unmodifiableSet(aliases);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds aliases for a given command from the plugin.yml commands map into the
|
||||
* provided set.
|
||||
*
|
||||
* @param commandsMap full commands map from PluginDescriptionFile
|
||||
* @param commandName the primary command to read aliases for (e.g. "vote")
|
||||
* @param out output set to add lowercase alias names into
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void addAliasesFor(Map<String, Map<String, Object>> commandsMap, String commandName,
|
||||
Set<String> out) {
|
||||
Map<String, Object> cmdInfo = commandsMap.get(commandName);
|
||||
if (cmdInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object aliasesObj = cmdInfo.get("aliases");
|
||||
if (aliasesObj instanceof String) {
|
||||
// plugin.yml also allows a single alias as a string
|
||||
out.add(((String) aliasesObj).toLowerCase(Locale.ROOT));
|
||||
} else if (aliasesObj instanceof Collection) {
|
||||
for (Object o : (Collection<Object>) aliasesObj) {
|
||||
if (o != null) {
|
||||
out.add(o.toString().toLowerCase(Locale.ROOT));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,435 +0,0 @@
|
||||
package com.bencodez.votingplugin.cooldown;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
import com.bencodez.advancedcore.api.rewards.RewardOptions;
|
||||
import com.bencodez.advancedcore.api.user.AdvancedCoreUser;
|
||||
import com.bencodez.advancedcore.api.user.UserDataFetchMode;
|
||||
import com.bencodez.advancedcore.api.user.UserStartup;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.events.PlayerVoteCoolDownEndEvent;
|
||||
import com.bencodez.votingplugin.events.PlayerVoteSiteCoolDownEndEvent;
|
||||
import com.bencodez.votingplugin.user.VotingPluginUser;
|
||||
import com.bencodez.votingplugin.votesites.NextSite;
|
||||
import com.bencodez.votingplugin.votesites.VoteSite;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Manages cooldown checks for vote rewards.
|
||||
*/
|
||||
public class CoolDownCheck implements Listener {
|
||||
|
||||
private final VotingPluginMain plugin;
|
||||
|
||||
private final Map<UUID, ScheduledFuture<?>> perSiteTasks = new ConcurrentHashMap<>();
|
||||
private final Map<UUID, ScheduledFuture<?>> allSiteTasks = new ConcurrentHashMap<>();
|
||||
|
||||
@Getter
|
||||
private final ScheduledExecutorService timer;
|
||||
|
||||
private volatile boolean cooldownCheckEnabled = false;
|
||||
|
||||
/**
|
||||
* Constructs a new cooldown check manager.
|
||||
*
|
||||
* @param plugin the plugin instance
|
||||
*/
|
||||
public CoolDownCheck(VotingPluginMain plugin) {
|
||||
this.plugin = plugin;
|
||||
|
||||
// Better behavior when you cancel+reschedule a lot (removes cancelled tasks from queue)
|
||||
ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(1);
|
||||
exec.setRemoveOnCancelPolicy(true);
|
||||
this.timer = exec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the cooldown for a player by UUID.
|
||||
*
|
||||
* @param uuid the player UUID
|
||||
*/
|
||||
public void check(UUID uuid) {
|
||||
VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(uuid);
|
||||
if (!user.isOnline()) {
|
||||
user.userDataFetechMode(UserDataFetchMode.NO_CACHE);
|
||||
}
|
||||
check(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts down the cooldown check timer.
|
||||
*/
|
||||
public void shutdown() {
|
||||
timer.shutdownNow();
|
||||
|
||||
for (ScheduledFuture<?> f : perSiteTasks.values()) {
|
||||
if (f != null) {
|
||||
f.cancel(false);
|
||||
}
|
||||
}
|
||||
for (ScheduledFuture<?> f : allSiteTasks.values()) {
|
||||
if (f != null) {
|
||||
f.cancel(false);
|
||||
}
|
||||
}
|
||||
|
||||
perSiteTasks.clear();
|
||||
allSiteTasks.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the cooldown for a user.
|
||||
*
|
||||
* @param user the voting plugin user
|
||||
*/
|
||||
public synchronized void check(VotingPluginUser user) {
|
||||
if (!user.getCoolDownCheck()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (user.canVoteAll()) {
|
||||
user.setCoolDownCheck(false);
|
||||
|
||||
PlayerVoteCoolDownEndEvent event = new PlayerVoteCoolDownEndEvent(user);
|
||||
plugin.getServer().getPluginManager().callEvent(event);
|
||||
|
||||
ScheduledFuture<?> f = allSiteTasks.remove(user.getJavaUUID());
|
||||
if (f != null) {
|
||||
f.cancel(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep per-site scheduling going while waiting for "all sites available"
|
||||
schedulePerSite(user, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check ONLY a specific votesite for all users (uses streaming keys/cols).
|
||||
*
|
||||
* @param site the vote site to check
|
||||
*/
|
||||
public void checkAllVoteSite(VoteSite site) {
|
||||
if (site == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getLogger().info("Checking vote cooldown rewards for all players: " + site.getKey());
|
||||
|
||||
plugin.getUserManager().forEachUserKeys((uuid, cols) -> {
|
||||
if (!plugin.isEnabled() || uuid == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(uuid, false);
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
user.userDataFetechMode(UserDataFetchMode.TEMP_ONLY);
|
||||
|
||||
try {
|
||||
if (cols != null) {
|
||||
user.updateTempCacheWithColumns(cols);
|
||||
cols.clear(); // help GC (only useful if reused)
|
||||
}
|
||||
|
||||
checkPerSite(site, user);
|
||||
} finally {
|
||||
user.clearTempCache();
|
||||
}
|
||||
}, (count) -> plugin.getLogger().info("Finished checking vote cooldown rewards for all players: " + site.getKey()
|
||||
+ " (processed: " + count + ")"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if cooldown check is enabled.
|
||||
*/
|
||||
public void checkEnabled() {
|
||||
cooldownCheckEnabled = !plugin.getConfigFile().isDisableCoolDownCheck()
|
||||
&& plugin.getRewardHandler().hasRewards(plugin.getSpecialRewardsConfig().getData(),
|
||||
"VoteCoolDownEndedReward");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks per-site cooldown for a player by UUID.
|
||||
*
|
||||
* @param uuid the player UUID
|
||||
*/
|
||||
public void checkPerSite(UUID uuid) {
|
||||
VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(uuid);
|
||||
if (!user.isOnline()) {
|
||||
user.userDataFetechMode(UserDataFetchMode.NO_CACHE);
|
||||
}
|
||||
checkPerSite(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks per-site cooldown for a user.
|
||||
*
|
||||
* @param user the voting plugin user
|
||||
*/
|
||||
public synchronized void checkPerSite(VotingPluginUser user) {
|
||||
if (!plugin.getConfigFile().isPerSiteCoolDownEvents()) {
|
||||
return;
|
||||
}
|
||||
|
||||
HashMap<String, Boolean> coolDownChecks = user.getCoolDownCheckSiteList();
|
||||
boolean changed = false;
|
||||
|
||||
for (VoteSite site : plugin.getVoteSiteManager().getVoteSitesEnabled()) {
|
||||
if (site == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Boolean.TRUE.equals(coolDownChecks.get(site.getKey()))) {
|
||||
if (user.canVoteSite(site)) {
|
||||
coolDownChecks.put(site.getKey(), Boolean.TRUE);
|
||||
changed = true;
|
||||
|
||||
plugin.extraDebug("Triggering votesitecooldownend event for " + user.getUUID());
|
||||
PlayerVoteSiteCoolDownEndEvent event = new PlayerVoteSiteCoolDownEndEvent(user, site);
|
||||
plugin.getServer().getPluginManager().callEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
user.setCoolDownCheckSite(coolDownChecks);
|
||||
}
|
||||
|
||||
schedulePerSite(user, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Site-specific version used by batch checks / checkAllVoteSite.
|
||||
*/
|
||||
private void checkPerSite(VoteSite site, VotingPluginUser user) {
|
||||
if (!plugin.getConfigFile().isPerSiteCoolDownEvents() || site == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
HashMap<String, Boolean> coolDownChecks = user.getCoolDownCheckSiteList();
|
||||
if (Boolean.TRUE.equals(coolDownChecks.get(site.getKey()))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (user.canVoteSite(site)) {
|
||||
coolDownChecks.put(site.getKey(), Boolean.TRUE);
|
||||
user.setCoolDownCheckSite(coolDownChecks);
|
||||
|
||||
plugin.extraDebug(
|
||||
"Triggering votesitecooldownend event on site " + site.getKey() + " for " + user.getUUID());
|
||||
PlayerVoteSiteCoolDownEndEvent event = new PlayerVoteSiteCoolDownEndEvent(user, site);
|
||||
plugin.getServer().getPluginManager().callEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the cooldown check system.
|
||||
*/
|
||||
public void load() {
|
||||
plugin.addUserStartup(new UserStartup() {
|
||||
|
||||
@Override
|
||||
public void onFinish() {
|
||||
checkAllVoteSites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartUp(AdvancedCoreUser advancedcoreUser) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the cooldown end event.
|
||||
*
|
||||
* @param event the cooldown end event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onCoolDownEnd(PlayerVoteCoolDownEndEvent event) {
|
||||
plugin.getRewardHandler().giveReward(event.getPlayer(), plugin.getSpecialRewardsConfig().getData(),
|
||||
"VoteCoolDownEndedReward", new RewardOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the per-site cooldown end event.
|
||||
*
|
||||
* @param event the per-site cooldown end event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onCoolDownEnd(PlayerVoteSiteCoolDownEndEvent event) {
|
||||
plugin.getRewardHandler().giveReward(event.getPlayer(), event.getSite().getSiteData(), "CoolDownEndRewards",
|
||||
new RewardOptions().addPlaceholder("sitename", event.getSite().getDisplayName())
|
||||
.addPlaceholder("url", event.getSite().getVoteURL(false)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a cooldown check for a user.
|
||||
*
|
||||
* @param user the voting plugin user
|
||||
* @param force whether to force the schedule
|
||||
*/
|
||||
public void schedule(VotingPluginUser user, boolean force) {
|
||||
if (user.getSitesVotedOn() <= 0 && !force) {
|
||||
return;
|
||||
}
|
||||
|
||||
final UUID uuid = user.getJavaUUID();
|
||||
final long time = user.getNextTimeAllSitesAvailable();
|
||||
|
||||
allSiteTasks.compute(uuid, (k, existing) -> {
|
||||
if (existing != null) {
|
||||
existing.cancel(false);
|
||||
}
|
||||
|
||||
if (time <= 0) {
|
||||
plugin.extraDebug(user.getUUID() + "/" + user.getPlayerName() + " not scheduling cooldown check");
|
||||
return null; // remove entry
|
||||
}
|
||||
|
||||
user.setCoolDownCheck(true);
|
||||
|
||||
return timer.schedule(() -> {
|
||||
if (plugin.isEnabled()) {
|
||||
check(uuid);
|
||||
}
|
||||
}, time + 2, TimeUnit.SECONDS);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules per-site cooldown checks for a user.
|
||||
*
|
||||
* @param user the voting plugin user
|
||||
* @param force whether to force the schedule
|
||||
*/
|
||||
public void schedulePerSite(VotingPluginUser user, boolean force) {
|
||||
if (!plugin.getConfigFile().isPerSiteCoolDownEvents()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (user.getSitesVotedOn() <= 0 && !force) {
|
||||
return;
|
||||
}
|
||||
|
||||
final UUID uuid = user.getJavaUUID();
|
||||
|
||||
final NextSite next = user.getNextSiteAvailable();
|
||||
|
||||
perSiteTasks.compute(uuid, (k, existing) -> {
|
||||
if (existing != null) {
|
||||
existing.cancel(false);
|
||||
}
|
||||
|
||||
if (next == null || next.getSite() == null || next.getSecondsUntilAvailable() <= 0) {
|
||||
plugin.extraDebug(user.getUUID() + "/" + user.getPlayerName() + " not scheduling per-site cooldown check");
|
||||
return null; // remove entry
|
||||
}
|
||||
|
||||
plugin.devDebug("PerSiteCoolDownEvent schedule: site=" + next.getSite().getKey() + " time="
|
||||
+ next.getSecondsUntilAvailable() + " seconds");
|
||||
|
||||
return timer.schedule(() -> {
|
||||
if (!plugin.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only check the next site; this will set the cooldown-end flag + fire event if ready
|
||||
checkPerSite(next.getSite(), user);
|
||||
|
||||
// Schedule the following site (if any)
|
||||
schedulePerSite(user, false);
|
||||
|
||||
}, next.getSecondsUntilAvailable() + 2, TimeUnit.SECONDS);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a vote event for cooldown scheduling.
|
||||
*
|
||||
* @param user the voting plugin user
|
||||
* @param site the vote site
|
||||
*/
|
||||
public void vote(VotingPluginUser user, VoteSite site) {
|
||||
if (cooldownCheckEnabled) {
|
||||
schedule(user, true);
|
||||
}
|
||||
|
||||
if (plugin.getConfigFile().isPerSiteCoolDownEvents()) {
|
||||
user.setCoolDownCheckSite(site, Boolean.FALSE);
|
||||
schedulePerSite(user, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performance-optimized: scan users ONCE, process all daily-delay sites.
|
||||
*/
|
||||
public void checkAllVoteSites() {
|
||||
final List<VoteSite> sites = plugin.getVoteSiteManager().getVoteSites();
|
||||
if (sites == null || sites.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ArrayList<VoteSite> dailySites = new ArrayList<>();
|
||||
for (int i = 0; i < sites.size(); i++) {
|
||||
VoteSite site = sites.get(i);
|
||||
if (site != null && site.isVoteDelayDaily()) {
|
||||
dailySites.add(site);
|
||||
}
|
||||
}
|
||||
|
||||
if (dailySites.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.debug("Checking vote cooldown rewards for all players across " + dailySites.size() + " sites...");
|
||||
|
||||
plugin.getUserManager().forEachUserKeys((uuid, cols) -> {
|
||||
if (!plugin.isEnabled() || uuid == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(uuid, false);
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
user.userDataFetechMode(UserDataFetchMode.TEMP_ONLY);
|
||||
|
||||
try {
|
||||
if (cols != null) {
|
||||
user.updateTempCacheWithColumns(cols);
|
||||
cols.clear();
|
||||
}
|
||||
|
||||
for (int i = 0; i < dailySites.size(); i++) {
|
||||
checkPerSite(dailySites.get(i), user);
|
||||
}
|
||||
|
||||
check(user);
|
||||
} finally {
|
||||
user.clearTempCache();
|
||||
}
|
||||
}, (count) -> plugin.debug("Finished checking vote cooldown rewards (processed: " + count + " users)"));
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package com.bencodez.votingplugin.events;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Enumeration for different special reward types.
|
||||
*/
|
||||
public enum SpecialRewardType {
|
||||
/** @deprecated All site reward. */
|
||||
@Deprecated
|
||||
ALLSITE,
|
||||
/** @deprecated Cumulative reward. */
|
||||
@Deprecated
|
||||
CUMMULATIVE,
|
||||
/** @deprecated First vote reward. */
|
||||
@Deprecated
|
||||
FIRSTVOTE,
|
||||
/** @deprecated First vote today reward. */
|
||||
@Deprecated
|
||||
FIRSTVOTETODAY,
|
||||
/** @deprecated Milestone reward. */
|
||||
@Deprecated
|
||||
MILESTONE,
|
||||
/** Vote streak reward. */
|
||||
VOTESTREAK,
|
||||
/** @deprecated Almost all sites reward. */
|
||||
@Deprecated
|
||||
ALMOSTALLSITES,
|
||||
/** Top voter reward. */
|
||||
TOPVOTER,
|
||||
/** Vote streaks reward. */
|
||||
VOTESTREAKS;
|
||||
|
||||
@Getter
|
||||
private int amount = -1;
|
||||
|
||||
@Getter
|
||||
private String type = "";
|
||||
|
||||
/**
|
||||
* Sets the amount for this special reward.
|
||||
*
|
||||
* @param num the amount
|
||||
* @return this special reward type
|
||||
*/
|
||||
public SpecialRewardType setAmount(int num) {
|
||||
amount = num;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the type for this special reward.
|
||||
*
|
||||
* @param type the type
|
||||
* @return this special reward type
|
||||
*/
|
||||
public SpecialRewardType setType(String type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder(name());
|
||||
|
||||
if (amount >= 0) {
|
||||
sb.append("(amount=").append(amount);
|
||||
}
|
||||
|
||||
if (type != null && !type.isEmpty()) {
|
||||
if (amount >= 0) {
|
||||
sb.append(", ");
|
||||
} else {
|
||||
sb.append("(");
|
||||
}
|
||||
sb.append("type=").append(type).append(")");
|
||||
} else if (amount >= 0) {
|
||||
sb.append(")");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
-159
@@ -1,159 +0,0 @@
|
||||
package com.bencodez.votingplugin.events;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
import com.bencodez.votingplugin.specialrewards.votemilestones.VoteMilestone;
|
||||
import com.bencodez.votingplugin.user.VotingPluginUser;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Fired after a VoteMilestone reward has been executed successfully.
|
||||
*/
|
||||
public class VoteMilestoneRewardEvent extends Event {
|
||||
|
||||
private static final HandlerList HANDLERS = new HandlerList();
|
||||
|
||||
private final VotingPluginUser user;
|
||||
private final UUID uuid;
|
||||
private final String playerName;
|
||||
|
||||
private final VoteMilestone milestone;
|
||||
private final long value;
|
||||
|
||||
private final String groupId;
|
||||
private final String rewardPath;
|
||||
@Getter
|
||||
private final UUID voteUUID;
|
||||
private final Map<String, String> placeholders;
|
||||
|
||||
/**
|
||||
* Constructs a new VoteMilestoneRewardEvent.
|
||||
*
|
||||
* @param user VotingPluginUser who received the reward
|
||||
* @param milestone milestone that triggered
|
||||
* @param value resolved total value used for matching
|
||||
* @param groupId resolved group id used for selection
|
||||
* @param rewardPath path used to execute rewards
|
||||
* @param placeholders placeholders passed to the reward handler
|
||||
* @param voteUUID UUID of the vote that triggered this reward (may be null)
|
||||
*/
|
||||
public VoteMilestoneRewardEvent(VotingPluginUser user, VoteMilestone milestone, long value, String groupId,
|
||||
String rewardPath, Map<String, String> placeholders, UUID voteUUID) {
|
||||
super(true);
|
||||
this.user = user;
|
||||
this.uuid = user == null ? null : user.getJavaUUID();
|
||||
this.playerName = user == null ? null : user.getPlayerName();
|
||||
this.milestone = milestone;
|
||||
this.value = value;
|
||||
this.groupId = groupId;
|
||||
this.rewardPath = rewardPath;
|
||||
this.voteUUID = voteUUID;
|
||||
if (placeholders == null || placeholders.isEmpty()) {
|
||||
this.placeholders = Collections.emptyMap();
|
||||
} else {
|
||||
this.placeholders = Collections.unmodifiableMap(new HashMap<>(placeholders));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the voting plugin user associated with this event.
|
||||
*
|
||||
* @return the voting plugin user
|
||||
*/
|
||||
public VotingPluginUser getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player UUID.
|
||||
*
|
||||
* @return the player UUID
|
||||
*/
|
||||
public UUID getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player name.
|
||||
*
|
||||
* @return the player name
|
||||
*/
|
||||
public String getPlayerName() {
|
||||
return playerName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the milestone that triggered this event.
|
||||
*
|
||||
* @return milestone instance
|
||||
*/
|
||||
public VoteMilestone getMilestone() {
|
||||
return milestone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the milestone ID.
|
||||
*
|
||||
* @return milestone id string
|
||||
*/
|
||||
public String getMilestoneId() {
|
||||
return milestone == null ? null : milestone.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value that triggered the milestone.
|
||||
*
|
||||
* @return the milestone value
|
||||
*/
|
||||
public long getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the group ID used for selection.
|
||||
*
|
||||
* @return group id string
|
||||
*/
|
||||
public String getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configuration path for the rewards.
|
||||
*
|
||||
* @return the reward path
|
||||
*/
|
||||
public String getRewardPath() {
|
||||
return rewardPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the placeholders map for this milestone reward.
|
||||
*
|
||||
* @return map of placeholder names to values
|
||||
*/
|
||||
public Map<String, String> getPlaceholders() {
|
||||
return placeholders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLERS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the handler list for this event.
|
||||
*
|
||||
* @return handler list
|
||||
*/
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLERS;
|
||||
}
|
||||
}
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
package com.bencodez.votingplugin.events;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
import com.bencodez.votingplugin.user.VotingPluginUser;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Fired when a player attempts to purchase an item from VotingPlugin's
|
||||
* VoteShop.
|
||||
*/
|
||||
public class VoteShopPurchaseEvent extends Event {
|
||||
|
||||
private static final HandlerList HANDLERS = new HandlerList();
|
||||
|
||||
@Getter
|
||||
private UUID playerUuid;
|
||||
@Getter
|
||||
private String playerName;
|
||||
@Getter
|
||||
private VotingPluginUser user;
|
||||
|
||||
@Getter
|
||||
private String identifier;
|
||||
|
||||
@Getter
|
||||
private int cost;
|
||||
|
||||
public VoteShopPurchaseEvent(UUID playerUUID, String playerName, VotingPluginUser user, String identifier,
|
||||
int cost) {
|
||||
super(true);
|
||||
this.playerName = playerName;
|
||||
this.user = user;
|
||||
this.playerUuid = playerUUID;
|
||||
this.identifier = identifier;
|
||||
this.cost = cost;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLERS;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLERS;
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
package com.bencodez.votingplugin.listeners;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
import com.bencodez.advancedcore.api.player.UuidLookup;
|
||||
import com.bencodez.advancedcore.api.user.UserDataFetchMode;
|
||||
import com.bencodez.advancedcore.listeners.AdvancedCoreLoginEvent;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.proxy.VotingPluginWire;
|
||||
import com.bencodez.votingplugin.user.VotingPluginUser;
|
||||
|
||||
/**
|
||||
* The Class PlayerJoinEvent.
|
||||
*/
|
||||
public class PlayerJoinEvent implements Listener {
|
||||
|
||||
/** The plugin. */
|
||||
private final VotingPluginMain plugin;
|
||||
|
||||
/**
|
||||
* Instantiates a new player join event.
|
||||
*
|
||||
* @param plugin the plugin
|
||||
*/
|
||||
public PlayerJoinEvent(VotingPluginMain plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
private static boolean isBlank(String s) {
|
||||
return s == null || s.trim().isEmpty() || "null".equalsIgnoreCase(s.trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* On AdvancedCore login event (post-auth / delayed login).
|
||||
*
|
||||
* @param event the event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onPlayerLogin(AdvancedCoreLoginEvent event) {
|
||||
if (event == null || !plugin.isMySQLOkay() || event.isCancelled() || event.getUser() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// UUID is authoritative here (String)
|
||||
String uuid = event.getUuid();
|
||||
if (isBlank(uuid)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// "Has data" should come from AdvancedCore event (storage presence)
|
||||
boolean hasData = event.isUserInStorage();
|
||||
|
||||
// Resolve VotingPluginUser by the storage UUID string (offline-mode
|
||||
// name-derived UUID)
|
||||
VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(uuid);
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = event.getPlayer();
|
||||
|
||||
if (player != null && player.isOp() && plugin.isYmlError()) {
|
||||
user.sendMessage("&cVotingPlugin: Detected yml error, please check console for details");
|
||||
}
|
||||
|
||||
if (hasData) {
|
||||
// give offline vote (if they voted offline)
|
||||
user.offVote();
|
||||
} else {
|
||||
plugin.debug("No data detected for " + user.getUUID() + "/" + user.getPlayerName());
|
||||
}
|
||||
|
||||
user.loginRewards();
|
||||
|
||||
plugin.getPlaceholders().onUpdate(user, true);
|
||||
|
||||
if (plugin.getBungeeSettings().isUseBungeecoord()) {
|
||||
plugin.getBungeeHandler().getGlobalMessageHandler().sendMessage(VotingPluginWire.login(user.getPlayerName(),
|
||||
user.getUUID(), plugin.getBungeeSettings().getServer()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles player quit events.
|
||||
*
|
||||
* @param event the player quit event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
if (plugin == null || !plugin.isEnabled() || event == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Player player = event.getPlayer();
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getLoginTimer().execute(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
VotingPluginMain.plugin.getAdvancedTab().remove(player.getUniqueId());
|
||||
|
||||
// Use AdvancedCore UUIDLookup to derive the correct UUID (online/offline aware)
|
||||
String uuid = UuidLookup.getInstance().getUUID(player.getName());
|
||||
if (isBlank(uuid)) {
|
||||
// Fallback to online UUID if lookup fails
|
||||
uuid = player.getUniqueId().toString();
|
||||
}
|
||||
|
||||
VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(uuid);
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
user.userDataFetechMode(UserDataFetchMode.NO_CACHE);
|
||||
user.logoutRewards();
|
||||
plugin.getPlaceholders().onLogout(user);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
-75
@@ -1,75 +0,0 @@
|
||||
package com.bencodez.votingplugin.placeholders;
|
||||
|
||||
/**
|
||||
* Enum for placeholder cache levels.
|
||||
*/
|
||||
public enum PlaceholderCacheLevel {
|
||||
/** No caching. */
|
||||
NONE,
|
||||
/** Specific placeholders cached. */
|
||||
SPECIFIC,
|
||||
/** Auto-cache for online players. */
|
||||
AUTO,
|
||||
/** Auto-cache for all players. */
|
||||
AUTOALL,
|
||||
/** Specific cache for all players. */
|
||||
SPECIFICALL;
|
||||
|
||||
/**
|
||||
* Get cache level from string.
|
||||
* @param str the string to parse
|
||||
* @return the cache level
|
||||
*/
|
||||
public static PlaceholderCacheLevel getCache(String str) {
|
||||
for (PlaceholderCacheLevel v : PlaceholderCacheLevel.values()) {
|
||||
if (v.toString().equalsIgnoreCase(str)) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
return AUTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if cache is for online players only.
|
||||
* @return true if online only
|
||||
*/
|
||||
public boolean onlineOnly() {
|
||||
if ((this == AUTOALL) || (this == SPECIFICALL)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if caching should be enabled.
|
||||
* @return true if should cache
|
||||
*/
|
||||
public boolean shouldCache() {
|
||||
switch (this) {
|
||||
case AUTO:
|
||||
return true;
|
||||
case NONE:
|
||||
return false;
|
||||
case SPECIFIC:
|
||||
return false;
|
||||
case SPECIFICALL:
|
||||
return false;
|
||||
case AUTOALL:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if cache should always be used.
|
||||
* @return true if cache always
|
||||
*/
|
||||
public boolean isCacheAlways() {
|
||||
if ((this == AUTOALL) || (this == SPECIFICALL)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package com.bencodez.votingplugin.presets;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Represents display information for a preset. Display information
|
||||
* includes a name and a description used to describe the preset to
|
||||
* users. This class is used when parsing meta JSON files.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class DisplayInfo {
|
||||
|
||||
/**
|
||||
* The human‑readable name of the preset. This name will appear in
|
||||
* preset selection UIs.
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* A brief description of the preset. This description helps users
|
||||
* understand what the preset is intended for.
|
||||
*/
|
||||
private String description;
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package com.bencodez.votingplugin.presets;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Represents a fragment defined in a preset meta JSON file. A
|
||||
* fragment contains the path to a YAML file and a path indicating
|
||||
* where the fragment should be merged into when applying the preset.
|
||||
* In the context of using only values from JSON (without YAML
|
||||
* fragments), this class is unused but included for completeness.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class Fragment {
|
||||
|
||||
/**
|
||||
* The relative path to the fragment file. This file typically
|
||||
* contains a YAML snippet defining a portion of the configuration.
|
||||
*/
|
||||
private String path;
|
||||
|
||||
/**
|
||||
* The dot‑separated path within the configuration to merge the
|
||||
* fragment into. For example, {@code VoteSites} indicates the
|
||||
* fragment should be merged into the {@code VoteSites} section of
|
||||
* {@code VoteSites.yml}.
|
||||
*/
|
||||
private String mergeInto;
|
||||
}
|
||||
-199
@@ -1,199 +0,0 @@
|
||||
package com.bencodez.votingplugin.presets;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpClient.Redirect;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
/**
|
||||
* Utility class for loading vote site presets from GitHub.
|
||||
*/
|
||||
public class GitHubVoteSitePresetLoader {
|
||||
|
||||
private final String owner;
|
||||
private final String repository;
|
||||
private final String branch;
|
||||
private final String token;
|
||||
private final Gson gson;
|
||||
|
||||
/**
|
||||
* Shared HTTP client.
|
||||
*/
|
||||
private final HttpClient httpClient = HttpClient.newBuilder()
|
||||
.followRedirects(Redirect.NORMAL)
|
||||
.connectTimeout(Duration.ofSeconds(5))
|
||||
.build();
|
||||
|
||||
public GitHubVoteSitePresetLoader(String owner, String repository, String branch) {
|
||||
this(owner, repository, branch, null);
|
||||
}
|
||||
|
||||
public GitHubVoteSitePresetLoader(String owner, String repository, String branch, String token) {
|
||||
this.owner = Objects.requireNonNull(owner);
|
||||
this.repository = Objects.requireNonNull(repository);
|
||||
this.branch = Objects.requireNonNull(branch);
|
||||
this.token = token;
|
||||
this.gson = new Gson();
|
||||
}
|
||||
|
||||
public List<String> listPresetPaths() throws IOException, InterruptedException {
|
||||
String apiUrl = String.format(
|
||||
"https://api.github.com/repos/%s/%s/contents/presets/votesites?ref=%s",
|
||||
owner, repository, branch);
|
||||
|
||||
HttpRequest.Builder builder = HttpRequest.newBuilder()
|
||||
.uri(URI.create(apiUrl))
|
||||
.GET()
|
||||
.timeout(Duration.ofSeconds(7))
|
||||
.header("User-Agent", "VotingPlugin-PresetLoader");
|
||||
|
||||
if (token != null && !token.isEmpty()) {
|
||||
builder.header("Authorization", "token " + token);
|
||||
}
|
||||
|
||||
HttpResponse<String> response = httpClient.send(builder.build(),
|
||||
HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
||||
|
||||
if (response.statusCode() != 200) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
JsonElement root = JsonParser.parseString(response.body());
|
||||
if (!root.isJsonArray()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
JsonArray array = root.getAsJsonArray();
|
||||
List<String> paths = new ArrayList<>();
|
||||
|
||||
for (JsonElement element : array) {
|
||||
if (!element.isJsonObject()) continue;
|
||||
|
||||
JsonObject obj = element.getAsJsonObject();
|
||||
String type = obj.has("type") ? obj.get("type").getAsString() : null;
|
||||
String path = obj.has("path") ? obj.get("path").getAsString() : null;
|
||||
|
||||
if ("file".equals(type) && path != null && path.endsWith(".meta.json")) {
|
||||
paths.add(path);
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
public VoteSitePreset loadPreset(String path) throws IOException, InterruptedException {
|
||||
Objects.requireNonNull(path, "path must not be null");
|
||||
|
||||
String rawUrl = String.format(
|
||||
"https://raw.githubusercontent.com/%s/%s/%s/%s",
|
||||
owner, repository, branch, path);
|
||||
|
||||
HttpRequest.Builder builder = HttpRequest.newBuilder()
|
||||
.uri(URI.create(rawUrl))
|
||||
.GET()
|
||||
.timeout(Duration.ofSeconds(7))
|
||||
.header("User-Agent", "VotingPlugin-PresetLoader");
|
||||
|
||||
if (token != null && !token.isEmpty()) {
|
||||
builder.header("Authorization", "token " + token);
|
||||
}
|
||||
|
||||
HttpResponse<String> response = httpClient.send(builder.build(),
|
||||
HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
||||
|
||||
if (response.statusCode() != 200) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try (Reader reader = new InputStreamReader(
|
||||
new java.io.ByteArrayInputStream(response.body().getBytes(StandardCharsets.UTF_8)),
|
||||
StandardCharsets.UTF_8)) {
|
||||
return gson.fromJson(reader, VoteSitePreset.class);
|
||||
}
|
||||
}
|
||||
|
||||
public List<VoteSitePreset> listAllVoteSitePresets() throws IOException, InterruptedException {
|
||||
List<String> paths = listPresetPaths();
|
||||
if (paths.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<VoteSitePreset> result = new ArrayList<>();
|
||||
|
||||
for (String path : paths) {
|
||||
try {
|
||||
VoteSitePreset preset = loadPreset(path);
|
||||
if (preset != null) {
|
||||
result.add(preset);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public VoteSitePreset findVoteSitePresetForURL(String voteURL) throws IOException, InterruptedException {
|
||||
if (voteURL == null || voteURL.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String host;
|
||||
try {
|
||||
java.net.URI uri = new java.net.URI(voteURL.trim());
|
||||
host = uri.getHost();
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (host == null || host.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
host = host.toLowerCase();
|
||||
if (host.startsWith("www.")) {
|
||||
host = host.substring(4);
|
||||
}
|
||||
|
||||
List<VoteSitePreset> presets = listAllVoteSitePresets();
|
||||
|
||||
for (VoteSitePreset preset : presets) {
|
||||
if (preset == null || preset.getMatch() == null || preset.getMatch().getDomains() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (String domain : preset.getMatch().getDomains()) {
|
||||
if (domain == null || domain.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String normalizedDomain = domain.toLowerCase();
|
||||
if (normalizedDomain.startsWith("www.")) {
|
||||
normalizedDomain = normalizedDomain.substring(4);
|
||||
}
|
||||
|
||||
if (host.equals(normalizedDomain) || host.endsWith("." + normalizedDomain)) {
|
||||
return preset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package com.bencodez.votingplugin.presets;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Represents matching information for a preset. Match information
|
||||
* identifies which domains or keywords should match this preset. This
|
||||
* class is used for deserializing preset meta JSON files but is
|
||||
* otherwise optional when programmatically applying the preset.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class MatchInfo {
|
||||
|
||||
/**
|
||||
* Domains associated with the preset. These are used by the preset
|
||||
* generator to automatically select a preset based on a vote site
|
||||
* hostname.
|
||||
*/
|
||||
private java.util.List<String> domains;
|
||||
|
||||
/**
|
||||
* Keywords associated with the preset. These can be used to search
|
||||
* presets in user interfaces.
|
||||
*/
|
||||
private java.util.List<String> keywords;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package com.bencodez.votingplugin.presets;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* Represents a placeholder definition loaded from a preset meta JSON file.
|
||||
*
|
||||
* The VotingPlugin presets define a set of placeholders that may be
|
||||
* substituted when creating vote site entries. Each placeholder has a
|
||||
* type (for example, {@code string}), a human‑readable label, and a
|
||||
* default value. This class is a simple container for these fields so
|
||||
* they can be deserialized via Gson and accessed via Lombok‑generated
|
||||
* getters and setters.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class PlaceholderDef {
|
||||
/**
|
||||
* The data type of this placeholder (e.g. {@code string}). The type
|
||||
* information is primarily for user interfaces and may be ignored when
|
||||
* programmatically applying the preset.
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* A human‑readable label describing the placeholder. This is used by
|
||||
* external tools (such as the web preset generator) to prompt for
|
||||
* values.
|
||||
*/
|
||||
private String label;
|
||||
|
||||
/**
|
||||
* The default value for the placeholder. If the user does not supply a
|
||||
* value when applying the preset, this value will be used instead.
|
||||
* <p>
|
||||
* The JSON meta files use the key {@code "default"} for this value. Because
|
||||
* {@code default} is a reserved keyword in Java, we cannot name this
|
||||
* field {@code default}. Instead, we annotate this field with
|
||||
* {@link SerializedName @SerializedName("default")} so that Gson
|
||||
* will map the {@code "default"} property in the JSON to this field.
|
||||
*/
|
||||
@SerializedName("default")
|
||||
private String defaultValue;
|
||||
}
|
||||
-234
@@ -1,234 +0,0 @@
|
||||
package com.bencodez.votingplugin.presets;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.config.ConfigVoteSites;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
/**
|
||||
* Manager responsible for loading vote site presets defined in JSON and
|
||||
* applying them to the VotingPlugin configuration. Unlike the web preset
|
||||
* generator, this manager ignores YAML fragments and relies solely on values
|
||||
* defined in the meta JSON file.
|
||||
*/
|
||||
public class VoteSiteJsonPresetManager {
|
||||
|
||||
/**
|
||||
* The main plugin instance used to access the configuration.
|
||||
*/
|
||||
private final VotingPluginMain plugin;
|
||||
|
||||
/**
|
||||
* Cached reference to the vote sites configuration helper.
|
||||
*/
|
||||
private final ConfigVoteSites configVoteSites;
|
||||
|
||||
/**
|
||||
* Constructs a new manager for applying vote site presets.
|
||||
*
|
||||
* @param plugin the main plugin instance
|
||||
*/
|
||||
public VoteSiteJsonPresetManager(VotingPluginMain plugin) {
|
||||
this.plugin = Objects.requireNonNull(plugin, "plugin must not be null");
|
||||
this.configVoteSites = plugin.getConfigVoteSites();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a vote site preset from the specified resource path.
|
||||
*
|
||||
* @param resourcePath the path to the meta JSON file within the JAR
|
||||
* @return the deserialized preset
|
||||
* @throws IOException if the resource cannot be read or parsed
|
||||
*/
|
||||
public VoteSitePreset loadPreset(String resourcePath) throws IOException {
|
||||
InputStream in = plugin.getResource(resourcePath);
|
||||
if (in == null) {
|
||||
throw new IOException("Preset resource not found: " + resourcePath);
|
||||
}
|
||||
try (Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
|
||||
Gson gson = new GsonBuilder().create();
|
||||
return gson.fromJson(reader, VoteSitePreset.class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the given vote site preset to the VotingPlugin configuration.
|
||||
* Placeholder values will be populated using the defaults defined in the
|
||||
* preset and overridden by any values provided in the {@code overrides} map.
|
||||
*
|
||||
* @param preset the preset to apply
|
||||
* @param overrides a map of placeholder names to override values
|
||||
*/
|
||||
public void applyPreset(VoteSitePreset preset, Map<String, Object> overrides) {
|
||||
if (preset == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Object> values = new HashMap<String, Object>();
|
||||
|
||||
if (preset.getPlaceholders() != null) {
|
||||
for (Map.Entry<String, PlaceholderDef> entry : preset.getPlaceholders().entrySet()) {
|
||||
PlaceholderDef def = entry.getValue();
|
||||
if ((def != null) && (def.getDefaultValue() != null)) {
|
||||
values.put(entry.getKey(), def.getDefaultValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (overrides != null) {
|
||||
for (Map.Entry<String, Object> entry : overrides.entrySet()) {
|
||||
if (entry.getValue() != null) {
|
||||
values.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String siteKey = getString(values.get("siteKey")).trim();
|
||||
String displayName = getString(values.get("displayName"));
|
||||
String serviceSite = getString(values.get("serviceSite"));
|
||||
String voteURL = getString(values.get("voteURL"));
|
||||
String voteDelay = getString(values.get("voteDelay"));
|
||||
|
||||
Boolean waitUntilDelay = getBoolean(values.get("WaitUnitVoteDelay"));
|
||||
if (waitUntilDelay == null) {
|
||||
waitUntilDelay = getBoolean(values.get("waitUntilVoteDelay"));
|
||||
}
|
||||
|
||||
Boolean voteDelayDaily = getBoolean(values.get("VoteDelayDaily"));
|
||||
if (voteDelayDaily == null) {
|
||||
voteDelayDaily = getBoolean(values.get("voteDelayDaily"));
|
||||
}
|
||||
|
||||
Integer voteDelayDailyHour = getInteger(values.get("voteDelayDailyHour"));
|
||||
if (voteDelayDailyHour == null) {
|
||||
voteDelayDailyHour = getInteger(values.get("VoteDelayDailyHour"));
|
||||
}
|
||||
|
||||
if (siteKey.isEmpty()) {
|
||||
plugin.getLogger().warning("Cannot apply preset: siteKey is missing");
|
||||
return;
|
||||
}
|
||||
|
||||
configVoteSites.setEnabled(siteKey, true);
|
||||
|
||||
if (!displayName.isEmpty()) {
|
||||
configVoteSites.setDisplayName(siteKey, displayName);
|
||||
}
|
||||
|
||||
if (!serviceSite.isEmpty()) {
|
||||
configVoteSites.setServiceSite(siteKey, serviceSite);
|
||||
}
|
||||
|
||||
if (!voteURL.isEmpty()) {
|
||||
configVoteSites.setVoteURL(siteKey, voteURL);
|
||||
}
|
||||
|
||||
if (!voteDelay.isEmpty()) {
|
||||
configVoteSites.setVoteDelay(siteKey, voteDelay);
|
||||
}
|
||||
|
||||
if (waitUntilDelay != null) {
|
||||
configVoteSites.set(siteKey, "WaitUntilVoteDelay", waitUntilDelay.booleanValue());
|
||||
}
|
||||
|
||||
if (voteDelayDaily != null) {
|
||||
configVoteSites.setVoteDelayDaily(siteKey, voteDelayDaily.booleanValue());
|
||||
}
|
||||
|
||||
if (voteDelayDailyHour != null) {
|
||||
configVoteSites.setVoteDelayDailyHour(siteKey, voteDelayDailyHour.intValue());
|
||||
}
|
||||
|
||||
if (configVoteSites.getItem(siteKey) == null) {
|
||||
configVoteSites.set(siteKey, "DisplayItem.Material", "STONE");
|
||||
configVoteSites.set(siteKey, "DisplayItem.Amount", 1);
|
||||
}
|
||||
|
||||
if (configVoteSites.getData(siteKey).getConfigurationSection("Rewards") == null) {
|
||||
configVoteSites.set(siteKey, "Rewards.Messages.Player", "&aThanks for voting on %ServiceSite%!");
|
||||
}
|
||||
|
||||
configVoteSites.saveData();
|
||||
plugin.loadVoteSites();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an object to a string.
|
||||
*
|
||||
* @param value the value
|
||||
* @return the string form, or an empty string if null
|
||||
*/
|
||||
private String getString(Object value) {
|
||||
return value != null ? String.valueOf(value) : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an object to a boolean if possible.
|
||||
*
|
||||
* @param value the value
|
||||
* @return the parsed boolean, or null if not set
|
||||
*/
|
||||
private Boolean getBoolean(Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value instanceof Boolean) {
|
||||
return (Boolean) value;
|
||||
}
|
||||
|
||||
String text = String.valueOf(value).trim().toLowerCase();
|
||||
|
||||
if (text.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (text.equals("true") || text.equals("yes") || text.equals("1")) {
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
if (text.equals("false") || text.equals("no") || text.equals("0")) {
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an object to an integer if possible.
|
||||
*
|
||||
* @param value the value
|
||||
* @return the parsed integer, or null if not set or invalid
|
||||
*/
|
||||
private Integer getInteger(Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value instanceof Number) {
|
||||
return Integer.valueOf(((Number) value).intValue());
|
||||
}
|
||||
|
||||
String text = String.valueOf(value).trim();
|
||||
|
||||
if (text.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return Integer.valueOf(Integer.parseInt(text));
|
||||
} catch (NumberFormatException e) {
|
||||
plugin.getLogger().warning("Invalid integer value: " + text);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package com.bencodez.votingplugin.presets;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a vote site preset as defined in a meta JSON file.
|
||||
*
|
||||
* The meta JSON file defines metadata about the preset such as display
|
||||
* information, matching criteria, placeholder definitions and optional YAML
|
||||
* fragments. This class provides a simple POJO for deserialization via
|
||||
* Gson. Only the {@code placeholders} field is strictly necessary when
|
||||
* applying the preset programmatically; the other fields are included
|
||||
* for completeness.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class VoteSitePreset {
|
||||
|
||||
/**
|
||||
* The schema version of the preset definition. This may be used to
|
||||
* handle future format changes.
|
||||
*/
|
||||
private int schemaVersion;
|
||||
|
||||
/**
|
||||
* The unique identifier of the preset (e.g. {@code votesite:generic}).
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* Human‑friendly display information for the preset.
|
||||
*/
|
||||
private DisplayInfo display;
|
||||
|
||||
/**
|
||||
* Matching information that can be used to automatically select a
|
||||
* preset based on domain or keywords. Optional when applying
|
||||
* programmatically.
|
||||
*/
|
||||
private MatchInfo match;
|
||||
|
||||
/**
|
||||
* Definitions of placeholders used by this preset. Each entry in
|
||||
* the map corresponds to a placeholder name and defines its type,
|
||||
* label and default value.
|
||||
*/
|
||||
private Map<String, PlaceholderDef> placeholders;
|
||||
|
||||
/**
|
||||
* A list of YAML fragments referenced by this preset. When not using
|
||||
* YAML, this field is unused but retained for completeness.
|
||||
*/
|
||||
private List<Fragment> fragments;
|
||||
|
||||
/**
|
||||
* Whether the preset has been verified by the project maintainers.
|
||||
*/
|
||||
private boolean verified;
|
||||
}
|
||||
-210
@@ -1,210 +0,0 @@
|
||||
package com.bencodez.votingplugin.presets;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.bencodez.simpleapi.valuerequest.MultiValueField;
|
||||
import com.bencodez.simpleapi.valuerequest.MultiValueField.FieldType;
|
||||
import com.bencodez.simpleapi.valuerequest.MultiValueListener;
|
||||
import com.bencodez.simpleapi.valuerequest.MultiValueResult;
|
||||
import com.bencodez.simpleapi.valuerequest.StringListener;
|
||||
import com.bencodez.simpleapi.valuerequest.ValueRequest;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Handler class that guides a player through selecting a vote site preset and
|
||||
* configuring its placeholder values using the SimpleAPI {@link ValueRequest}
|
||||
* system.
|
||||
*/
|
||||
public class VoteSitePresetSetupHandler {
|
||||
|
||||
/**
|
||||
* Main plugin instance.
|
||||
*/
|
||||
private final VotingPluginMain plugin;
|
||||
|
||||
/**
|
||||
* Loader used to fetch vote site presets.
|
||||
*/
|
||||
@Getter
|
||||
private final GitHubVoteSitePresetLoader loader;
|
||||
|
||||
/**
|
||||
* Creates a new setup handler.
|
||||
*
|
||||
* @param plugin plugin instance
|
||||
*/
|
||||
public VoteSitePresetSetupHandler(VotingPluginMain plugin) {
|
||||
this.plugin = Objects.requireNonNull(plugin, "plugin must not be null");
|
||||
this.loader = new GitHubVoteSitePresetLoader("BenCodez", "VotingPlugin-Presets", "main");
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the preset setup process.
|
||||
*
|
||||
* @param player player running setup
|
||||
*/
|
||||
public void startSetup(Player player) {
|
||||
List<VoteSitePreset> presets;
|
||||
|
||||
try {
|
||||
presets = loader.listAllVoteSitePresets();
|
||||
} catch (Exception e) {
|
||||
player.sendMessage("§cUnable to load vote site presets.");
|
||||
plugin.getLogger().warning("Failed to load vote site presets: " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
if (presets == null || presets.isEmpty()) {
|
||||
player.sendMessage("§cNo vote site presets are available.");
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> options = new ArrayList<String>();
|
||||
for (VoteSitePreset preset : presets) {
|
||||
options.add(preset.getId());
|
||||
}
|
||||
|
||||
ValueRequest request = new ValueRequest(plugin, plugin.getDialogService());
|
||||
|
||||
request.requestString(player, null, options, false, "Select a vote site preset", new StringListener() {
|
||||
@Override
|
||||
public void onInput(Player p, String value) {
|
||||
|
||||
VoteSitePreset selected = null;
|
||||
|
||||
for (VoteSitePreset preset : presets) {
|
||||
if (preset.getId() != null && preset.getId().equalsIgnoreCase(value)) {
|
||||
selected = preset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (selected == null) {
|
||||
p.sendMessage("§cInvalid preset: " + value);
|
||||
return;
|
||||
}
|
||||
|
||||
promptPlaceholders(p, selected);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompts placeholder values using the multi-value request system.
|
||||
*
|
||||
* @param player player
|
||||
* @param preset preset
|
||||
*/
|
||||
public void promptPlaceholders(Player player, VoteSitePreset preset) {
|
||||
|
||||
Map<String, PlaceholderDef> defs = preset.getPlaceholders();
|
||||
|
||||
if (defs == null || defs.isEmpty()) {
|
||||
new VoteSiteJsonPresetManager(plugin).applyPreset(preset, new HashMap<String, Object>());
|
||||
player.sendMessage("§aPreset applied successfully.");
|
||||
return;
|
||||
}
|
||||
|
||||
List<MultiValueField> fields = new ArrayList<MultiValueField>();
|
||||
List<String> keys = new ArrayList<String>();
|
||||
|
||||
for (Map.Entry<String, PlaceholderDef> entry : defs.entrySet()) {
|
||||
|
||||
String key = entry.getKey();
|
||||
|
||||
if (key == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// skip auto values
|
||||
if (key.equalsIgnoreCase("siteKey") || key.equalsIgnoreCase("serviceSite")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
PlaceholderDef def = entry.getValue();
|
||||
|
||||
String label = def != null && def.getLabel() != null ? def.getLabel() : key;
|
||||
String defaultValue = def != null && def.getDefaultValue() != null ? def.getDefaultValue() : "";
|
||||
|
||||
MultiValueField field = createField(key, label, defaultValue);
|
||||
|
||||
fields.add(field);
|
||||
keys.add(key);
|
||||
}
|
||||
|
||||
if (fields.isEmpty()) {
|
||||
new VoteSiteJsonPresetManager(plugin).applyPreset(preset, new HashMap<String, Object>());
|
||||
player.sendMessage("§aPreset applied successfully.");
|
||||
return;
|
||||
}
|
||||
|
||||
ValueRequest request = new ValueRequest(plugin, plugin.getDialogService());
|
||||
|
||||
request.requestMultipleValues(player, fields, new MultiValueListener() {
|
||||
|
||||
@Override
|
||||
public void onInput(Player p, MultiValueResult result) {
|
||||
|
||||
Map<String, Object> overrides = new HashMap<String, Object>();
|
||||
|
||||
for (String key : keys) {
|
||||
|
||||
if (isBooleanField(key)) {
|
||||
Boolean value = result.getBoolean(key);
|
||||
overrides.put(key, value != null ? value : Boolean.FALSE);
|
||||
} else {
|
||||
String value = result.getString(key);
|
||||
overrides.put(key, value != null ? value : "");
|
||||
}
|
||||
}
|
||||
|
||||
new VoteSiteJsonPresetManager(plugin).applyPreset(preset, overrides);
|
||||
|
||||
p.sendMessage("§aVote site configured and preset applied.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a field based on placeholder key.
|
||||
*
|
||||
* @param key key
|
||||
* @param label label
|
||||
* @param defaultValue default value
|
||||
* @return field
|
||||
*/
|
||||
private MultiValueField createField(String key, String label, String defaultValue) {
|
||||
|
||||
if (isBooleanField(key)) {
|
||||
|
||||
boolean boolValue = Boolean.parseBoolean(defaultValue);
|
||||
|
||||
return new MultiValueField(key, label + " (default: " + boolValue + ")", FieldType.BOOLEAN)
|
||||
.booleanValue(Boolean.valueOf(boolValue))
|
||||
.required(false);
|
||||
}
|
||||
|
||||
return new MultiValueField(key, label + " (default: " + defaultValue + ")", FieldType.STRING)
|
||||
.stringValue(defaultValue)
|
||||
.required(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the placeholder should be treated as a boolean.
|
||||
*
|
||||
* @param key placeholder key
|
||||
* @return true if boolean field
|
||||
*/
|
||||
private boolean isBooleanField(String key) {
|
||||
return key.equalsIgnoreCase("WaitUntilVoteDelay")
|
||||
|| key.equalsIgnoreCase("VoteDelayDaily");
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package com.bencodez.votingplugin.proxy;
|
||||
|
||||
/**
|
||||
* Enumeration of available Bungee connection methods.
|
||||
*/
|
||||
public enum BungeeMethod {
|
||||
/** MySQL database connection. */
|
||||
MYSQL,
|
||||
/** Plugin messaging channel. */
|
||||
PLUGINMESSAGING,
|
||||
/** Socket connection. */
|
||||
SOCKETS,
|
||||
/** Redis connection. */
|
||||
REDIS,
|
||||
/** MQTT message broker. */
|
||||
MQTT;
|
||||
|
||||
/**
|
||||
* Checks if this method requires a player to be online.
|
||||
*
|
||||
* @return true if player must be online
|
||||
*/
|
||||
public boolean requiresPlayerOnline() {
|
||||
return this == PLUGINMESSAGING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a BungeeMethod by name.
|
||||
*
|
||||
* @param str the method name
|
||||
* @return the matching BungeeMethod, or PLUGINMESSAGING if not found
|
||||
*/
|
||||
public static BungeeMethod getByName(String str) {
|
||||
for (BungeeMethod method : values()) {
|
||||
if (method.toString().equalsIgnoreCase(str)) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
return PLUGINMESSAGING;
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package com.bencodez.votingplugin.proxy;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Represents an offline bungee vote.
|
||||
*/
|
||||
public class OfflineBungeeVote {
|
||||
|
||||
@Getter
|
||||
private String playerName;
|
||||
@Getter
|
||||
private boolean realVote;
|
||||
@Getter
|
||||
private String service;
|
||||
@Getter
|
||||
@Setter
|
||||
private String text;
|
||||
@Getter
|
||||
private long time;
|
||||
@Getter
|
||||
private String uuid;
|
||||
@Getter
|
||||
private UUID voteId;
|
||||
|
||||
/**
|
||||
* Constructor with UUID voteId.
|
||||
* @param voteId the vote ID
|
||||
* @param playerName the player name
|
||||
* @param uuid the player UUID
|
||||
* @param service the vote service
|
||||
* @param time the vote time
|
||||
* @param realVote whether this is a real vote
|
||||
* @param text additional text
|
||||
*/
|
||||
public OfflineBungeeVote(UUID voteId, String playerName, String uuid, String service, long time, boolean realVote,
|
||||
String text) {
|
||||
this.playerName = playerName;
|
||||
this.uuid = uuid;
|
||||
this.service = service;
|
||||
this.time = time;
|
||||
this.realVote = realVote;
|
||||
this.text = text;
|
||||
this.voteId = voteId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with String voteId.
|
||||
* @param voteId the vote ID as string
|
||||
* @param playerName the player name
|
||||
* @param uuid the player UUID
|
||||
* @param service the vote service
|
||||
* @param time the vote time
|
||||
* @param realVote whether this is a real vote
|
||||
* @param text additional text
|
||||
*/
|
||||
public OfflineBungeeVote(String voteId, String playerName, String uuid, String service, long time, boolean realVote,
|
||||
String text) {
|
||||
this.playerName = playerName;
|
||||
this.uuid = uuid;
|
||||
this.service = service;
|
||||
this.time = time;
|
||||
this.realVote = realVote;
|
||||
this.text = text;
|
||||
if (voteId != null && !voteId.isEmpty()) {
|
||||
this.voteId = UUID.fromString(voteId);
|
||||
} else {
|
||||
this.voteId = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VoteCache:" + playerName + "/" + uuid + "/" + service + "/" + time + "/" + realVote + "/" + text + "/"
|
||||
+ voteId;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,580 +0,0 @@
|
||||
package com.bencodez.votingplugin.proxy;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.bencodez.simpleapi.sql.Column;
|
||||
import com.bencodez.simpleapi.sql.DataType;
|
||||
import com.bencodez.simpleapi.sql.data.DataValue;
|
||||
import com.bencodez.simpleapi.sql.data.DataValueInt;
|
||||
import com.bencodez.simpleapi.sql.data.DataValueString;
|
||||
import com.bencodez.simpleapi.sql.mysql.AbstractSqlTable;
|
||||
import com.bencodez.simpleapi.sql.mysql.DbType;
|
||||
import com.bencodez.simpleapi.sql.mysql.MySQL;
|
||||
import com.bencodez.simpleapi.sql.mysql.config.MysqlConfig;
|
||||
import com.bencodez.simpleapi.sql.mysql.queries.Query;
|
||||
|
||||
/**
|
||||
* Proxy MySQL user table for managing voting plugin user data.
|
||||
*/
|
||||
public abstract class ProxyMysqlUserTable extends AbstractSqlTable {
|
||||
|
||||
// Keep this so existing callers relying on getName() still work
|
||||
private final String name;
|
||||
|
||||
// For int-vs-string decode in getExactQuery (same pattern as before)
|
||||
private final List<String> intColumns = new ArrayList<>();
|
||||
|
||||
// Backwards-compat wrapper (your old code expects getUuids())
|
||||
private final Set<String> uuidsView;
|
||||
|
||||
// ---- Required subclass hooks ----
|
||||
|
||||
@Override
|
||||
public String getPrimaryKeyColumn() {
|
||||
return "uuid";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String buildCreateTableSql(DbType dbType) {
|
||||
// NOTE: AbstractSqlTable.qi(...) already quotes identifiers properly for
|
||||
// DbType.
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("CREATE TABLE IF NOT EXISTS ").append(qi(getTableName())).append(" (");
|
||||
|
||||
// Best-case uuid type:
|
||||
// - Postgres: UUID
|
||||
// - MySQL/MariaDB: VARCHAR(37) (matches existing behavior)
|
||||
sb.append(qi("uuid")).append(" ").append(bestUuidType()).append(", ");
|
||||
sb.append("PRIMARY KEY ( ").append(qi("uuid")).append(" )");
|
||||
sb.append(");");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
// ---- Logging hooks required by AbstractSqlTable ----
|
||||
|
||||
@Override
|
||||
public abstract void logSevere(String msg);
|
||||
|
||||
@Override
|
||||
public abstract void logInfo(String msg);
|
||||
|
||||
@Override
|
||||
public abstract void debug(Throwable t);
|
||||
|
||||
// Keep these existing abstract methods for compatibility with your old
|
||||
// subclasses
|
||||
/**
|
||||
* Debug log for SQL exceptions.
|
||||
* @param e the SQL exception
|
||||
*/
|
||||
public abstract void debug(SQLException e);
|
||||
|
||||
// ---- Constructors ----
|
||||
|
||||
/**
|
||||
* Constructor with new MySQL connection.
|
||||
* @param tableName the table name
|
||||
* @param config the MySQL configuration
|
||||
* @param debug enable debug mode
|
||||
*/
|
||||
public ProxyMysqlUserTable(String tableName, MysqlConfig config, boolean debug) {
|
||||
super(tableName, config, debug);
|
||||
|
||||
this.name = super.getTableName();
|
||||
|
||||
// keep uuids view
|
||||
this.uuidsView = super.getPrimaryKeys();
|
||||
|
||||
// keep old "uuid column type tweak" behavior (but now uses base helper)
|
||||
alterColumnType("uuid", bestUuidType());
|
||||
|
||||
// load intColumns behavior if you rely on it elsewhere
|
||||
// (no-op: it's only used if you add to it)
|
||||
}
|
||||
|
||||
/**
|
||||
* Use an already-connected pool (shared).
|
||||
* @param tableName the table name
|
||||
* @param existingMysql existing MySQL connection
|
||||
* @param debug enable debug mode
|
||||
*/
|
||||
public ProxyMysqlUserTable(String tableName, MySQL existingMysql, boolean debug) {
|
||||
super(tableName, existingMysql, debug);
|
||||
|
||||
this.name = super.getTableName();
|
||||
this.uuidsView = super.getPrimaryKeys();
|
||||
|
||||
alterColumnType("uuid", bestUuidType());
|
||||
}
|
||||
|
||||
// ---- Backwards-compatible API ----
|
||||
|
||||
/**
|
||||
* Get table name.
|
||||
* @return the table name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep old method name and behavior.
|
||||
* @return set of UUIDs
|
||||
*/
|
||||
public Set<String> getUuids() {
|
||||
return getPrimaryKeys();
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep old behavior: query uuids fresh.
|
||||
* @return list of UUIDs from database
|
||||
*/
|
||||
public ArrayList<String> getUuidsQuery() {
|
||||
return new ArrayList<>(getPrimaryKeysQuery());
|
||||
}
|
||||
|
||||
/**
|
||||
* Load data from database.
|
||||
*/
|
||||
public void loadData() {
|
||||
// old name -> new behavior
|
||||
loadBasicCaches();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cache.
|
||||
*/
|
||||
public void clearCache() {
|
||||
clearCacheBasic();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear basic cache.
|
||||
*/
|
||||
public void clearCacheBasic() {
|
||||
clearCaches();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown and close connections.
|
||||
*/
|
||||
public void shutdown() {
|
||||
close();
|
||||
}
|
||||
|
||||
// Keep old containsKeyQuery signature
|
||||
public boolean containsKeyQuery(String index) {
|
||||
return super.containsKeyQuery(index);
|
||||
}
|
||||
|
||||
// ---- Required old utility methods (kept) ----
|
||||
|
||||
/**
|
||||
* Get columns from query.
|
||||
* @return list of column names
|
||||
*/
|
||||
public ArrayList<String> getColumnsQueury() {
|
||||
return new ArrayList<>(getColumnsQuery());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rows with player names from query.
|
||||
* @return list of columns with player names
|
||||
*/
|
||||
public ArrayList<Column> getRowsNameQuery() {
|
||||
ArrayList<Column> result = new ArrayList<>();
|
||||
checkColumn("PlayerName", DataType.STRING);
|
||||
|
||||
String sqlStr = "SELECT " + qi("PlayerName") + " FROM " + qi(getTableName()) + ";";
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sqlStr);
|
||||
ResultSet rs = ps.executeQuery()) {
|
||||
|
||||
while (rs.next()) {
|
||||
result.add(new Column("PlayerName", new DataValueString(rs.getString(1))));
|
||||
}
|
||||
} catch (SQLException ignored) {
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get player names from query.
|
||||
* @return list of player names
|
||||
*/
|
||||
public ArrayList<String> getNamesQuery() {
|
||||
ArrayList<String> names = new ArrayList<>();
|
||||
checkColumn("PlayerName", DataType.STRING);
|
||||
|
||||
ArrayList<Column> rows = getRowsNameQuery();
|
||||
if (rows != null) {
|
||||
for (Column c : rows) {
|
||||
if (c.getValue() != null && c.getValue().isString()) {
|
||||
String v = c.getValue().getString();
|
||||
if (v != null) {
|
||||
names.add(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all rows from query.
|
||||
* @return list of columns with UUID data
|
||||
*/
|
||||
public ArrayList<Column> getRowsQuery() {
|
||||
ArrayList<Column> result = new ArrayList<>();
|
||||
String sqlStr = "SELECT " + qi("uuid") + " FROM " + qi(getTableName()) + ";";
|
||||
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sqlStr);
|
||||
ResultSet rs = ps.executeQuery()) {
|
||||
|
||||
while (rs.next()) {
|
||||
result.add(new Column("uuid", new DataValueString(rs.getString(1))));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get UUID to name mapping from query.
|
||||
* @return map of UUID to player name
|
||||
*/
|
||||
public ConcurrentHashMap<UUID, String> getRowsUUIDNameQuery() {
|
||||
ConcurrentHashMap<UUID, String> uuidNames = new ConcurrentHashMap<>();
|
||||
|
||||
checkColumn("PlayerName", DataType.STRING);
|
||||
|
||||
String sqlStr = "SELECT " + qi("uuid") + ", " + qi("PlayerName") + " FROM " + qi(getTableName()) + ";";
|
||||
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sqlStr);
|
||||
ResultSet rs = ps.executeQuery()) {
|
||||
|
||||
while (rs.next()) {
|
||||
String uuidStr = rs.getString(1);
|
||||
String playerName = rs.getString(2);
|
||||
|
||||
if (uuidStr != null && !uuidStr.isEmpty() && !"null".equalsIgnoreCase(uuidStr) && playerName != null) {
|
||||
try {
|
||||
uuidNames.put(UUID.fromString(uuidStr), playerName);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SQLException ignored) {
|
||||
}
|
||||
|
||||
return uuidNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get UUID by player name.
|
||||
* @param playerName the player name
|
||||
* @return the UUID or null if not found
|
||||
*/
|
||||
public String getUUID(String playerName) {
|
||||
checkColumn("PlayerName", DataType.STRING);
|
||||
|
||||
String query = "SELECT " + qi("uuid") + " FROM " + qi(getTableName()) + " WHERE " + qi("PlayerName") + " = ?;";
|
||||
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(query)) {
|
||||
|
||||
ps.setString(1, playerName);
|
||||
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
String uuid = rs.getString(1);
|
||||
if (uuid != null && !uuid.isEmpty()) {
|
||||
return uuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
debug(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get exact match query.
|
||||
* @param column the column to match
|
||||
* @return list of matching columns
|
||||
*/
|
||||
public ArrayList<Column> getExactQuery(Column column) {
|
||||
ArrayList<Column> result = new ArrayList<>();
|
||||
|
||||
String query = "SELECT * FROM " + qi(getTableName()) + " WHERE " + qi(column.getName()) + " = ?;";
|
||||
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(query)) {
|
||||
|
||||
String v = column.getValue() == null ? null : column.getValue().getString();
|
||||
if (getDbType() == DbType.POSTGRESQL && "uuid".equalsIgnoreCase(column.getName()) && v != null) {
|
||||
ps.setObject(1, UUID.fromString(v));
|
||||
} else {
|
||||
ps.setString(1, v);
|
||||
}
|
||||
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
for (int i = 1; i <= rs.getMetaData().getColumnCount(); i++) {
|
||||
String columnName = rs.getMetaData().getColumnLabel(i);
|
||||
Column rCol;
|
||||
if (intColumns.contains(columnName)) {
|
||||
rCol = new Column(columnName, DataType.INTEGER);
|
||||
rCol.setValue(new DataValueInt(rs.getInt(i)));
|
||||
} else {
|
||||
rCol = new Column(columnName, DataType.STRING);
|
||||
rCol.setValue(new DataValueString(rs.getString(i)));
|
||||
}
|
||||
result.add(rCol);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
debug(e);
|
||||
} catch (ArrayIndexOutOfBoundsException ignored) {
|
||||
}
|
||||
|
||||
for (String col : getColumns()) {
|
||||
result.add(new Column(col, DataType.STRING));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if column is integer type.
|
||||
* @param key the column name
|
||||
* @return true if integer column
|
||||
*/
|
||||
public boolean isIntColumn(String key) {
|
||||
return intColumns.contains(key);
|
||||
}
|
||||
|
||||
// ---- INSERT/UPDATE/WIPE (kept similar to your existing behavior) ----
|
||||
|
||||
/**
|
||||
* Insert a value.
|
||||
* @param index the UUID
|
||||
* @param column the column name
|
||||
* @param value the value to insert
|
||||
*/
|
||||
public void insert(String index, String column, DataValue value) {
|
||||
insertQuery(index, Arrays.asList(new Column(column, value)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert multiple columns.
|
||||
* @param index the UUID
|
||||
* @param cols the columns to insert
|
||||
*/
|
||||
public void insertQuery(String index, List<Column> cols) {
|
||||
for (Column c : cols) {
|
||||
checkColumn(c.getName(), c.getDataType());
|
||||
}
|
||||
|
||||
// Keep your previous cross-db behavior:
|
||||
// - Postgres: ON CONFLICT upsert
|
||||
// - MySQL/MariaDB: INSERT IGNORE ... SET ...
|
||||
if (getDbType() == DbType.POSTGRESQL) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("INSERT INTO ").append(qi(getTableName())).append(" (").append(qi("uuid"));
|
||||
|
||||
for (Column col : cols) {
|
||||
sb.append(", ").append(qi(col.getName()));
|
||||
}
|
||||
sb.append(") VALUES (?");
|
||||
|
||||
for (int i = 0; i < cols.size(); i++) {
|
||||
sb.append(", ?");
|
||||
}
|
||||
sb.append(") ON CONFLICT (").append(qi("uuid")).append(") DO UPDATE SET ");
|
||||
|
||||
for (int i = 0; i < cols.size(); i++) {
|
||||
Column col = cols.get(i);
|
||||
String c = qi(col.getName());
|
||||
sb.append(c).append(" = EXCLUDED.").append(c);
|
||||
if (i != cols.size() - 1) {
|
||||
sb.append(", ");
|
||||
}
|
||||
}
|
||||
sb.append(";");
|
||||
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sb.toString())) {
|
||||
|
||||
ps.setObject(1, UUID.fromString(index));
|
||||
int p = 2;
|
||||
for (Column col : cols) {
|
||||
ps.setString(p++, col.getValue().toString());
|
||||
}
|
||||
ps.executeUpdate();
|
||||
uuidsView.add(index);
|
||||
} catch (SQLException | IllegalArgumentException e) {
|
||||
debug(e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder query = new StringBuilder();
|
||||
query.append("INSERT IGNORE ").append(qi(getTableName())).append(" SET ").append(qi("uuid")).append("=?, ");
|
||||
|
||||
for (int i = 0; i < cols.size(); i++) {
|
||||
Column col = cols.get(i);
|
||||
query.append(qi(col.getName())).append("=?");
|
||||
query.append(i == cols.size() - 1 ? ";" : ", ");
|
||||
}
|
||||
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(query.toString())) {
|
||||
|
||||
ps.setString(1, index);
|
||||
int p = 2;
|
||||
for (Column col : cols) {
|
||||
ps.setString(p++, col.getValue().toString());
|
||||
}
|
||||
ps.executeUpdate();
|
||||
uuidsView.add(index);
|
||||
} catch (SQLException e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update multiple columns.
|
||||
* @param index the UUID
|
||||
* @param cols the columns to update
|
||||
*/
|
||||
public void update(String index, List<Column> cols) {
|
||||
for (Column col : cols) {
|
||||
checkColumn(col.getName(), col.getDataType());
|
||||
}
|
||||
|
||||
// keep old behavior: if missing, insert
|
||||
if (!getUuids().contains(index) && !containsKeyQuery(index)) {
|
||||
insertQuery(index, cols);
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (updateLock) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("UPDATE ").append(qi(getTableName())).append(" SET ");
|
||||
|
||||
for (int i = 0; i < cols.size(); i++) {
|
||||
Column col = cols.get(i);
|
||||
sb.append(qi(col.getName())).append("=?");
|
||||
if (i != cols.size() - 1) {
|
||||
sb.append(", ");
|
||||
}
|
||||
}
|
||||
|
||||
sb.append(" WHERE ").append(qi("uuid")).append("=?;");
|
||||
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sb.toString())) {
|
||||
|
||||
int p = 1;
|
||||
for (Column col : cols) {
|
||||
bindValue(ps, p++, col);
|
||||
}
|
||||
|
||||
if (getDbType() == DbType.POSTGRESQL) {
|
||||
ps.setObject(p, UUID.fromString(index));
|
||||
} else {
|
||||
ps.setString(p, index);
|
||||
}
|
||||
|
||||
int updated = ps.executeUpdate();
|
||||
if (updated == 0) {
|
||||
// row missing (wiped) even though uuid cache thought it existed
|
||||
insertQuery(index, cols);
|
||||
}
|
||||
} catch (SQLException | IllegalArgumentException e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void bindValue(PreparedStatement ps, int index, Column col) throws SQLException {
|
||||
DataValue v = col.getValue();
|
||||
if (getDbType() == DbType.POSTGRESQL && v != null && v.isInt()) {
|
||||
ps.setInt(index, v.getInt());
|
||||
return;
|
||||
}
|
||||
ps.setString(index, v == null ? null : v.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a single column value.
|
||||
* @param index the UUID
|
||||
* @param column the column name
|
||||
* @param value the value to update
|
||||
*/
|
||||
public void update(String index, String column, DataValue value) {
|
||||
checkColumn(column, value.getType());
|
||||
|
||||
if (!getUuids().contains(index) && !containsKeyQuery(index)) {
|
||||
insert(index, column, value);
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (updateLock) {
|
||||
String sql = "UPDATE " + qi(getTableName()) + " SET " + qi(column) + "=? WHERE " + qi("uuid") + "=?;";
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
|
||||
if (getDbType() == DbType.POSTGRESQL && value != null && value.isInt()) {
|
||||
ps.setInt(1, value.getInt());
|
||||
} else {
|
||||
ps.setString(1, value == null ? null : value.toString());
|
||||
}
|
||||
|
||||
if (getDbType() == DbType.POSTGRESQL) {
|
||||
ps.setObject(2, UUID.fromString(index));
|
||||
} else {
|
||||
ps.setString(2, index);
|
||||
}
|
||||
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException | IllegalArgumentException e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wipe all data from a column.
|
||||
* @param columnName the column name
|
||||
* @param dataType the data type
|
||||
*/
|
||||
public void wipeColumnData(String columnName, DataType dataType) {
|
||||
checkColumn(columnName, dataType);
|
||||
|
||||
String sql = "UPDATE " + qi(getTableName()) + " SET " + qi(columnName) + " = " + dataType.getNoValue() + ";";
|
||||
try {
|
||||
new Query(mysql, sql).executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
package com.bencodez.votingplugin.proxy;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
public class VoteTotalsSnapshot {
|
||||
|
||||
private static final String SEP = "//";
|
||||
private static final Pattern SEP_PATTERN = Pattern.compile(Pattern.quote(SEP));
|
||||
|
||||
// Current storage version
|
||||
private static final int CURRENT_VERSION = 2;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private int allTimeTotal;
|
||||
@Getter
|
||||
@Setter
|
||||
private int monthTotal;
|
||||
@Getter
|
||||
@Setter
|
||||
private int weeklyTotal;
|
||||
@Getter
|
||||
@Setter
|
||||
private int dailyTotal;
|
||||
@Getter
|
||||
@Setter
|
||||
private int points;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private int votePartyCurrent;
|
||||
@Getter
|
||||
@Setter
|
||||
private int votePartyRequired;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private int dateMonthTotal;
|
||||
@Getter
|
||||
@Setter
|
||||
private UUID voteUUID;
|
||||
|
||||
public VoteTotalsSnapshot() {
|
||||
}
|
||||
|
||||
public VoteTotalsSnapshot(int allTimeTotal, int monthTotal, int weeklyTotal, int dailyTotal, int points,
|
||||
int votePartyCurrent, int votePartyRequired, int dateMonthTotal, UUID voteUUID) {
|
||||
this.allTimeTotal = allTimeTotal;
|
||||
this.monthTotal = monthTotal;
|
||||
this.weeklyTotal = weeklyTotal;
|
||||
this.dailyTotal = dailyTotal;
|
||||
this.points = points;
|
||||
this.votePartyCurrent = votePartyCurrent;
|
||||
this.votePartyRequired = votePartyRequired;
|
||||
this.dateMonthTotal = dateMonthTotal;
|
||||
this.voteUUID = voteUUID;
|
||||
}
|
||||
|
||||
public static VoteTotalsSnapshot parseStorage(String raw) {
|
||||
VoteTotalsSnapshot out = new VoteTotalsSnapshot();
|
||||
if (raw == null || raw.isEmpty()) {
|
||||
return out;
|
||||
}
|
||||
|
||||
String[] t = SEP_PATTERN.split(raw, -1);
|
||||
if (t.length < 5) {
|
||||
return out;
|
||||
}
|
||||
|
||||
int idx = 0;
|
||||
int version = 1; // legacy default
|
||||
|
||||
// Versioned?
|
||||
if (t[0].startsWith("v")) {
|
||||
version = parseInt(t[0].substring(1), 1);
|
||||
idx = 1;
|
||||
}
|
||||
|
||||
switch (version) {
|
||||
case 1:
|
||||
parseV1(out, t, idx);
|
||||
break;
|
||||
case 2:
|
||||
parseV2(out, t, idx);
|
||||
break;
|
||||
default:
|
||||
// Unknown future version → best-effort using v2 layout
|
||||
parseV2(out, t, idx);
|
||||
break;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/*
|
||||
* ========================= Writers (always v2) =========================
|
||||
*/
|
||||
|
||||
public String toStorageString() {
|
||||
return "v" + CURRENT_VERSION + SEP + allTimeTotal + SEP + monthTotal + SEP + weeklyTotal + SEP + dailyTotal
|
||||
+ SEP + points + SEP + votePartyCurrent + SEP + votePartyRequired + SEP + dateMonthTotal + SEP
|
||||
+ (voteUUID != null ? voteUUID.toString() : "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toStorageString();
|
||||
}
|
||||
|
||||
// Legacy (milestoneCount exists, but ignored)
|
||||
private static void parseV1(VoteTotalsSnapshot out, String[] t, int i) {
|
||||
out.allTimeTotal = parseInt(t, i++, 0);
|
||||
out.monthTotal = parseInt(t, i++, 0);
|
||||
out.weeklyTotal = parseInt(t, i++, 0);
|
||||
out.dailyTotal = parseInt(t, i++, 0);
|
||||
out.points = parseInt(t, i++, 0);
|
||||
|
||||
i++; // skip milestoneCount
|
||||
|
||||
out.votePartyCurrent = parseInt(t, i++, 0);
|
||||
out.votePartyRequired = parseInt(t, i++, 0);
|
||||
out.dateMonthTotal = parseInt(t, i++, 0);
|
||||
out.voteUUID = parseUUID(t, i);
|
||||
}
|
||||
|
||||
// Current (no milestoneCount)
|
||||
private static void parseV2(VoteTotalsSnapshot out, String[] t, int i) {
|
||||
out.allTimeTotal = parseInt(t, i++, 0);
|
||||
out.monthTotal = parseInt(t, i++, 0);
|
||||
out.weeklyTotal = parseInt(t, i++, 0);
|
||||
out.dailyTotal = parseInt(t, i++, 0);
|
||||
out.points = parseInt(t, i++, 0);
|
||||
|
||||
out.votePartyCurrent = parseInt(t, i++, 0);
|
||||
out.votePartyRequired = parseInt(t, i++, 0);
|
||||
out.dateMonthTotal = parseInt(t, i++, 0);
|
||||
out.voteUUID = parseUUID(t, i);
|
||||
}
|
||||
|
||||
/*
|
||||
* ========================= Helpers =========================
|
||||
*/
|
||||
|
||||
private static int parseInt(String[] t, int idx, int def) {
|
||||
if (idx < 0 || idx >= t.length)
|
||||
return def;
|
||||
try {
|
||||
return Integer.parseInt(t[idx].trim());
|
||||
} catch (Exception e) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
private static int parseInt(String s, int def) {
|
||||
try {
|
||||
return Integer.parseInt(s);
|
||||
} catch (Exception e) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
private static UUID parseUUID(String[] t, int idx) {
|
||||
if (idx < 0 || idx >= t.length)
|
||||
return null;
|
||||
String s = t[idx].trim();
|
||||
if (s.isEmpty() || "null".equalsIgnoreCase(s))
|
||||
return null;
|
||||
try {
|
||||
return UUID.fromString(s);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
-628
@@ -1,628 +0,0 @@
|
||||
package com.bencodez.votingplugin.proxy;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Configuration interface for proxy server integration.
|
||||
*/
|
||||
public interface VotingPluginProxyConfig {
|
||||
|
||||
/**
|
||||
* Gets whether proxy broadcast is enabled.
|
||||
*
|
||||
* @return true if proxy broadcast is enabled
|
||||
*/
|
||||
default boolean getProxyBroadcastEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Routing scope mode.
|
||||
*
|
||||
* Expected values: PLAYER_SERVER | ALL_SERVERS | SERVERS | ALL_EXCEPT
|
||||
*
|
||||
* @return the scope mode
|
||||
*/
|
||||
default String getProxyBroadcastScopeMode() {
|
||||
return "ALL_SERVERS";
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional server list used by scope modes SERVERS and ALL_EXCEPT.
|
||||
*
|
||||
* @return the list of scope servers
|
||||
*/
|
||||
default List<String> getProxyBroadcastScopeServers() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* What to do when the voting player is offline and scope is PLAYER_SERVER.
|
||||
*
|
||||
* Expected values: NONE | QUEUE | FORWARD
|
||||
*
|
||||
* @return the offline mode
|
||||
*/
|
||||
default String getProxyBroadcastOfflineMode() {
|
||||
return "QUEUE";
|
||||
}
|
||||
|
||||
/**
|
||||
* Backend server list used when OfflineMode is FORWARD and Scope.Mode is
|
||||
* PLAYER_SERVER.
|
||||
*
|
||||
* Ignored when Scope.Mode is ALL_SERVERS or ALL_EXCEPT.
|
||||
*
|
||||
* @return the list of offline forward servers
|
||||
*/
|
||||
default List<String> getProxyBroadcastOfflineForwardServers() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether unjoined players are allowed.
|
||||
*
|
||||
* @return true if unjoined players are allowed
|
||||
*/
|
||||
public boolean getAllowUnJoined();
|
||||
|
||||
/**
|
||||
* Gets the Bedrock player prefix.
|
||||
*
|
||||
* @return the Bedrock player prefix
|
||||
*/
|
||||
public String getBedrockPlayerPrefix();
|
||||
|
||||
/**
|
||||
* Gets the list of blocked servers.
|
||||
*
|
||||
* @return the list of blocked servers
|
||||
*/
|
||||
public List<String> getBlockedServers();
|
||||
|
||||
/**
|
||||
* Gets the Bungee host.
|
||||
*
|
||||
* @return the Bungee host
|
||||
*/
|
||||
public String getBungeeHost();
|
||||
|
||||
/**
|
||||
* Gets whether Bungee manages totals.
|
||||
*
|
||||
* @return true if Bungee manages totals
|
||||
*/
|
||||
public boolean getBungeeManageTotals();
|
||||
|
||||
/**
|
||||
* Gets the Bungee method.
|
||||
*
|
||||
* @return the Bungee method
|
||||
*/
|
||||
public String getBungeeMethod();
|
||||
|
||||
/**
|
||||
* Gets the Bungee port.
|
||||
*
|
||||
* @return the Bungee port
|
||||
*/
|
||||
public int getBungeePort();
|
||||
|
||||
/**
|
||||
* Gets the plugin message channel.
|
||||
*
|
||||
* @return the plugin message channel
|
||||
*/
|
||||
public String getPluginMessageChannel();
|
||||
|
||||
/**
|
||||
* Gets whether plugin message encryption is enabled.
|
||||
*
|
||||
* @return true if plugin message encryption is enabled
|
||||
*/
|
||||
public boolean getPluginMessageEncryption();
|
||||
|
||||
/**
|
||||
* Gets whether debug mode is enabled.
|
||||
*
|
||||
* @return true if debug mode is enabled
|
||||
*/
|
||||
public boolean getDebug();
|
||||
|
||||
/**
|
||||
* Gets whether vote cache uses MySQL.
|
||||
*
|
||||
* @return true if vote cache uses MySQL
|
||||
*/
|
||||
public boolean getVoteCacheUseMySQL();
|
||||
|
||||
/**
|
||||
* Gets whether vote cache uses main MySQL.
|
||||
*
|
||||
* @return true if vote cache uses main MySQL
|
||||
*/
|
||||
public boolean getVoteCacheUseMainMySQL();
|
||||
|
||||
/**
|
||||
* Gets whether non-voted cache uses MySQL.
|
||||
*
|
||||
* @return true if non-voted cache uses MySQL
|
||||
*/
|
||||
public boolean getNonVotedCacheUseMySQL();
|
||||
|
||||
/**
|
||||
* Gets whether non-voted cache uses main MySQL.
|
||||
*
|
||||
* @return true if non-voted cache uses main MySQL
|
||||
*/
|
||||
public boolean getNonVotedCacheUseMainMySQL();
|
||||
|
||||
/**
|
||||
* Gets the MQTT client ID.
|
||||
*
|
||||
* @return the MQTT client ID
|
||||
*/
|
||||
public String getMqttClientID();
|
||||
|
||||
/**
|
||||
* Gets the MQTT broker URL.
|
||||
*
|
||||
* @return the MQTT broker URL
|
||||
*/
|
||||
public String getMqttBrokerURL();
|
||||
|
||||
/**
|
||||
* Gets the MQTT username.
|
||||
*
|
||||
* @return the MQTT username
|
||||
*/
|
||||
public String getMqttUsername();
|
||||
|
||||
/**
|
||||
* Gets the MQTT password.
|
||||
*
|
||||
* @return the MQTT password
|
||||
*/
|
||||
public String getMqttPassword();
|
||||
|
||||
/**
|
||||
* Gets the MQTT prefix.
|
||||
*
|
||||
* @return the MQTT prefix
|
||||
*/
|
||||
public String getMqttPrefix();
|
||||
|
||||
/**
|
||||
* Gets the fallback server.
|
||||
*
|
||||
* @return the fallback server
|
||||
*/
|
||||
public String getFallBack();
|
||||
|
||||
/**
|
||||
* Gets whether global data is enabled.
|
||||
*
|
||||
* @return true if global data is enabled
|
||||
*/
|
||||
public boolean getGlobalDataEnabled();
|
||||
|
||||
/**
|
||||
* Gets whether global data uses main MySQL.
|
||||
*
|
||||
* @return true if global data uses main MySQL
|
||||
*/
|
||||
public boolean getGlobalDataUseMainMySQL();
|
||||
|
||||
/**
|
||||
* Gets the limit for vote points.
|
||||
*
|
||||
* @return the vote points limit
|
||||
*/
|
||||
public int getLimitVotePoints();
|
||||
|
||||
/**
|
||||
* Gets the maximum amount of votes per day.
|
||||
*
|
||||
* @return the maximum votes per day
|
||||
*/
|
||||
public int getMaxAmountOfVotesPerDay();
|
||||
|
||||
/**
|
||||
* Gets the multi-proxy method.
|
||||
*
|
||||
* @return the multi-proxy method
|
||||
*/
|
||||
public String getMultiProxyMethod();
|
||||
|
||||
/**
|
||||
* Gets whether multi-proxy uses one global reward.
|
||||
*
|
||||
* @return true if multi-proxy uses one global reward
|
||||
*/
|
||||
public boolean getMultiProxyOneGlobalReward();
|
||||
|
||||
/**
|
||||
* Gets the multi-proxy Redis host.
|
||||
*
|
||||
* @return the Redis host
|
||||
*/
|
||||
public String getMultiProxyRedisHost();
|
||||
|
||||
/**
|
||||
* Gets the multi-proxy Redis password.
|
||||
*
|
||||
* @return the Redis password
|
||||
*/
|
||||
public String getMultiProxyRedisPassword();
|
||||
|
||||
/**
|
||||
* Gets the multi-proxy Redis port.
|
||||
*
|
||||
* @return the Redis port
|
||||
*/
|
||||
public int getMultiProxyRedisPort();
|
||||
|
||||
/**
|
||||
* Gets whether multi-proxy Redis uses existing connection.
|
||||
*
|
||||
* @return true if using existing connection
|
||||
*/
|
||||
public boolean getMultiProxyRedisUseExistingConnection();
|
||||
|
||||
/**
|
||||
* Gets the multi-proxy Redis username.
|
||||
*
|
||||
* @return the Redis username
|
||||
*/
|
||||
public String getMultiProxyRedisUsername();
|
||||
|
||||
/**
|
||||
* Gets the collection of multi-proxy servers.
|
||||
*
|
||||
* @return the collection of server names
|
||||
*/
|
||||
public Collection<String> getMultiProxyServers();
|
||||
|
||||
/**
|
||||
* Gets the configuration for a specific multi-proxy server.
|
||||
*
|
||||
* @param s the server name
|
||||
* @return the server configuration as a map
|
||||
*/
|
||||
public Map<String, Object> getMultiProxyServersConfiguration(String s);
|
||||
|
||||
/**
|
||||
* Gets the multi-proxy socket host.
|
||||
*
|
||||
* @return the socket host
|
||||
*/
|
||||
public String getMultiProxySocketHostHost();
|
||||
|
||||
/**
|
||||
* Gets the multi-proxy socket host port.
|
||||
*
|
||||
* @return the socket port
|
||||
*/
|
||||
public int getMultiProxySocketHostPort();
|
||||
|
||||
/**
|
||||
* Gets whether multi-proxy support is enabled.
|
||||
*
|
||||
* @return true if multi-proxy support is enabled
|
||||
*/
|
||||
public boolean getMultiProxySupport();
|
||||
|
||||
/**
|
||||
* Gets whether online mode is enabled.
|
||||
*
|
||||
* @return true if online mode is enabled
|
||||
*/
|
||||
public boolean getOnlineMode();
|
||||
|
||||
/**
|
||||
* Gets the points awarded per vote.
|
||||
*
|
||||
* @return the points per vote
|
||||
*/
|
||||
public int getPointsOnVote();
|
||||
|
||||
/**
|
||||
* Gets whether this is the primary server.
|
||||
*
|
||||
* @return true if this is the primary server
|
||||
*/
|
||||
public boolean getPrimaryServer();
|
||||
|
||||
/**
|
||||
* Gets the proxy server name.
|
||||
*
|
||||
* @return the proxy server name
|
||||
*/
|
||||
public String getProxyServerName();
|
||||
|
||||
/**
|
||||
* Gets the list of proxy servers.
|
||||
*
|
||||
* @return the list of proxy servers
|
||||
*/
|
||||
public List<String> getProxyServers();
|
||||
|
||||
/**
|
||||
* Gets the Redis host.
|
||||
*
|
||||
* @return the Redis host
|
||||
*/
|
||||
public String getRedisHost();
|
||||
|
||||
/**
|
||||
* Gets the Redis password.
|
||||
*
|
||||
* @return the Redis password
|
||||
*/
|
||||
public String getRedisPassword();
|
||||
|
||||
/**
|
||||
* Gets the Redis port.
|
||||
*
|
||||
* @return the Redis port
|
||||
*/
|
||||
public int getRedisPort();
|
||||
|
||||
/**
|
||||
* Gets the Redis database index.
|
||||
*
|
||||
* @return the Redis database index
|
||||
*/
|
||||
public int getRedisDbIndex();
|
||||
|
||||
/**
|
||||
* Gets the Redis prefix.
|
||||
*
|
||||
* @return the Redis prefix
|
||||
*/
|
||||
public String getRedisPrefix();
|
||||
|
||||
/**
|
||||
* Gets the Redis username.
|
||||
*
|
||||
* @return the Redis username
|
||||
*/
|
||||
public String getRedisUsername();
|
||||
|
||||
/**
|
||||
* Gets whether votes should be sent to all servers.
|
||||
*
|
||||
* @return true if votes should be sent to all servers
|
||||
*/
|
||||
public boolean getSendVotesToAllServers();
|
||||
|
||||
/**
|
||||
* Gets the configuration for a specific Spigot server.
|
||||
*
|
||||
* @param s the server name
|
||||
* @return the server configuration as a map
|
||||
*/
|
||||
public Map<String, Object> getSpigotServerConfiguration(String s);
|
||||
|
||||
/**
|
||||
* Gets the collection of Spigot servers.
|
||||
*
|
||||
* @return the collection of server names
|
||||
*/
|
||||
public Collection<String> getSpigotServers();
|
||||
|
||||
/**
|
||||
* Gets whether to store month totals with date.
|
||||
*
|
||||
* @return true if month totals are stored with date
|
||||
*/
|
||||
public boolean getStoreMonthTotalsWithDate();
|
||||
|
||||
/**
|
||||
* Gets whether time change fail-safe bypass is enabled.
|
||||
*
|
||||
* @return true if time change fail-safe bypass is enabled
|
||||
*/
|
||||
public boolean getTimeChangeFailSafeBypass();
|
||||
|
||||
/**
|
||||
* Gets the time hour offset.
|
||||
*
|
||||
* @return the hour offset
|
||||
*/
|
||||
public int getTimeHourOffSet();
|
||||
|
||||
/**
|
||||
* Gets the time week offset.
|
||||
*
|
||||
* @return the week offset
|
||||
*/
|
||||
public int getTimeWeekOffSet();
|
||||
|
||||
/**
|
||||
* Gets the time zone.
|
||||
*
|
||||
* @return the time zone
|
||||
*/
|
||||
public String getTimeZone();
|
||||
|
||||
/**
|
||||
* Gets whether to use month date totals as primary total.
|
||||
*
|
||||
* @return true if month date totals are used as primary total
|
||||
*/
|
||||
public boolean getUseMonthDateTotalsAsPrimaryTotal();
|
||||
|
||||
/**
|
||||
* Gets whether UUID lookup is enabled.
|
||||
*
|
||||
* @return true if UUID lookup is enabled
|
||||
*/
|
||||
public boolean getUUIDLookup();
|
||||
|
||||
/**
|
||||
* Gets the vote cache time.
|
||||
*
|
||||
* @return the vote cache time
|
||||
*/
|
||||
public int getVoteCacheTime();
|
||||
|
||||
/**
|
||||
* Gets the vote party broadcast message.
|
||||
*
|
||||
* @return the vote party broadcast message
|
||||
*/
|
||||
public String getVotePartyBroadcast();
|
||||
|
||||
/**
|
||||
* Gets the list of vote party Bungee commands.
|
||||
*
|
||||
* @return the list of commands
|
||||
*/
|
||||
public List<String> getVotePartyBungeeCommands();
|
||||
|
||||
/**
|
||||
* Gets whether vote party is enabled.
|
||||
*
|
||||
* @return true if vote party is enabled
|
||||
*/
|
||||
public boolean getVotePartyEnabled();
|
||||
|
||||
/**
|
||||
* Gets the vote party increase votes required.
|
||||
*
|
||||
* @return the increase in votes required
|
||||
*/
|
||||
public int getVotePartyIncreaseVotesRequired();
|
||||
|
||||
/**
|
||||
* Gets whether to send vote party to all servers.
|
||||
*
|
||||
* @return true if vote party is sent to all servers
|
||||
*/
|
||||
public boolean getVotePartySendToAllServers();
|
||||
|
||||
/**
|
||||
* Gets the list of servers to send vote party to.
|
||||
*
|
||||
* @return the list of servers
|
||||
*/
|
||||
public List<String> getVotePartyServersToSend();
|
||||
|
||||
/**
|
||||
* Gets the number of votes required for vote party.
|
||||
*
|
||||
* @return the votes required
|
||||
*/
|
||||
public int getVotePartyVotesRequired();
|
||||
|
||||
/**
|
||||
* Gets whether to wait for user to be online.
|
||||
*
|
||||
* @return true if waiting for user to be online
|
||||
*/
|
||||
public boolean getWaitForUserOnline();
|
||||
|
||||
/**
|
||||
* Gets the list of whitelisted servers.
|
||||
*
|
||||
* @return the list of whitelisted servers
|
||||
*/
|
||||
public List<String> getWhiteListedServers();
|
||||
|
||||
/**
|
||||
* Gets the collection of sites with vote delay configured.
|
||||
*
|
||||
* @return the collection of site names
|
||||
*/
|
||||
public Collection<String> getWaitUntilVoteDelaySites();
|
||||
|
||||
/**
|
||||
* Gets the vote delay service for a specific site.
|
||||
*
|
||||
* @param site the site name
|
||||
* @return the service name
|
||||
*/
|
||||
public String getWaitUntilVoteDelayService(String site);
|
||||
|
||||
/**
|
||||
* Gets the vote delay in seconds for a specific site.
|
||||
*
|
||||
* @param site the site name
|
||||
* @return the vote delay in seconds
|
||||
*/
|
||||
public int getWaitUntilVoteDelayVoteDelay(String site);
|
||||
|
||||
/**
|
||||
* Gets the minimum vote delay for a specific site.
|
||||
*
|
||||
* @param site the site name
|
||||
* @return the minimum vote delay
|
||||
*/
|
||||
public int getWaitUntilVoteDelayVoteDelayMin(String site);
|
||||
|
||||
/**
|
||||
* Gets the hourly vote delay for a specific site.
|
||||
*
|
||||
* @param site the site name
|
||||
* @return the hourly vote delay
|
||||
*/
|
||||
public int getWaitUntilVoteDelayVoteDelayHour(String site);
|
||||
|
||||
/**
|
||||
* Gets whether the vote delay is daily for a specific site.
|
||||
*
|
||||
* @param site the site name
|
||||
* @return true if the delay is daily
|
||||
*/
|
||||
public boolean getWaitUntilVoteDelayVoteDelayDaily(String site);
|
||||
|
||||
/**
|
||||
* Loads the configuration.
|
||||
*/
|
||||
public void load();
|
||||
|
||||
/**
|
||||
* Saves the configuration.
|
||||
*/
|
||||
public void save();
|
||||
|
||||
/**
|
||||
* Gets whether vote logging is enabled.
|
||||
*
|
||||
* @return true if vote logging is enabled
|
||||
*/
|
||||
public boolean getVoteLoggingEnabled();
|
||||
|
||||
/**
|
||||
* Gets the number of days to keep vote logs.
|
||||
*
|
||||
* @return the purge days
|
||||
*/
|
||||
public int getVoteLoggingPurgeDays();
|
||||
|
||||
/**
|
||||
* Gets whether vote logging uses main MySQL.
|
||||
*
|
||||
* @return true if vote logging uses main MySQL
|
||||
*/
|
||||
public boolean getVoteLoggingUseMainMySQL();
|
||||
|
||||
/**
|
||||
* Gets whether a database is configured.
|
||||
*
|
||||
* @return true if a database is configured
|
||||
*/
|
||||
public boolean hasDatabaseConfigured();
|
||||
|
||||
/**
|
||||
* Gets the multi-proxy Redis database index.
|
||||
*
|
||||
* @return the database index
|
||||
*/
|
||||
public int getMultiProxyRedisDbIndex();
|
||||
|
||||
}
|
||||
@@ -1,308 +0,0 @@
|
||||
// File: com/bencodez/votingplugin/proxy/VotingPluginWire.java
|
||||
package com.bencodez.votingplugin.proxy;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.bencodez.simpleapi.servercomm.codec.JsonEnvelope;
|
||||
|
||||
/**
|
||||
* Canonical wire format for proxy - backend messages.
|
||||
*
|
||||
* Rules: - Only JsonEnvelope is sent over the wire (no legacy String[] /
|
||||
* delimiters). - All fields are named keys (no numeric indices). - Versioning
|
||||
* uses the JsonEnvelope schema as the source of truth. - A "bungeeVersion"
|
||||
* field is also included for legacy compatibility checks. - Values are written
|
||||
* as native types where possible, but readers tolerate Strings.
|
||||
*
|
||||
* Note: Some keys are public because other classes (e.g. MultiProxyHandler)
|
||||
* reference them directly.
|
||||
*/
|
||||
public final class VotingPluginWire {
|
||||
|
||||
private VotingPluginWire() {
|
||||
}
|
||||
|
||||
public static final int SCHEMA_VERSION = 1;
|
||||
|
||||
// =========================
|
||||
// Subchannels (canonical)
|
||||
// =========================
|
||||
public static final String SUB_VOTE = "Vote";
|
||||
public static final String SUB_VOTE_ONLINE = "VoteOnline";
|
||||
public static final String SUB_VOTE_UPDATE = "VoteUpdate";
|
||||
public static final String SUB_VOTE_BROADCAST = "VoteBroadcast";
|
||||
public static final String SUB_BUNGEE_TIME_CHANGE = "BungeeTimeChange";
|
||||
|
||||
public static final String SUB_STATUS = "Status";
|
||||
public static final String SUB_STATUS_OKAY = "statusokay";
|
||||
public static final String SUB_SERVER_NAME = "ServerName";
|
||||
|
||||
public static final String SUB_VOTE_PARTY = "VotePartyBungee";
|
||||
|
||||
public static final String SUB_LOGIN = "login";
|
||||
public static final String SUB_VOTEUPDATE_RELAY = "voteupdate";
|
||||
|
||||
// =========================
|
||||
// Multi-proxy subchannels (REQUIRED by MultiProxyHandler)
|
||||
// =========================
|
||||
public static final String SUB_CLEAR_VOTE = "ClearVote";
|
||||
public static final String SUB_CLEAR_VOTE_PRIMARY = "ClearVotePrimary";
|
||||
|
||||
// =========================
|
||||
// Field keys (public where referenced externally)
|
||||
// =========================
|
||||
public static final String K_VERSION = "version";
|
||||
|
||||
public static final String K_PLAYER = "player";
|
||||
public static final String K_UUID = "uuid";
|
||||
public static final String K_SERVER = "server";
|
||||
public static final String K_SERVICE = "service";
|
||||
public static final String K_TIME = "time";
|
||||
|
||||
// Vote/VoteOnline extras
|
||||
public static final String K_WAS_ONLINE = "wasOnline";
|
||||
public static final String K_REAL_VOTE = "realVote";
|
||||
public static final String K_TOTALS = "totals";
|
||||
public static final String K_SET_TOTALS = "setTotals";
|
||||
public static final String K_MANAGE_TOTALS = "manageTotals";
|
||||
public static final String K_BUNGEE_BROADCAST = "bungeeBroadcast";
|
||||
public static final String K_NUM = "num";
|
||||
public static final String K_NUMBER_OF_VOTES = "numberOfVotes";
|
||||
|
||||
// VoteUpdate extras
|
||||
public static final String K_PLAYER_UUID = "playerUuid";
|
||||
public static final String K_VOTE_PARTY_CURRENT = "votePartyCurrent";
|
||||
public static final String K_VOTE_PARTY_REQUIRED = "votePartyRequired";
|
||||
public static final String K_LAST_VOTE_TIME = "lastVoteTime";
|
||||
|
||||
// =========================
|
||||
// Builders
|
||||
// =========================
|
||||
|
||||
public static JsonEnvelope bungeeTimeChange() {
|
||||
return base(SUB_BUNGEE_TIME_CHANGE).build();
|
||||
}
|
||||
|
||||
public static JsonEnvelope vote(String player, String uuid, String service, long time, boolean wasOnline,
|
||||
boolean realVote, String totals, boolean manageTotals, boolean bungeeBroadcast, int num,
|
||||
int numberOfVotes) {
|
||||
|
||||
return base(SUB_VOTE).put(K_PLAYER, safe(player)).put(K_UUID, safe(uuid)).put(K_SERVICE, safe(service))
|
||||
.put(K_TIME, time).put(K_WAS_ONLINE, wasOnline).put(K_REAL_VOTE, realVote).put(K_TOTALS, safe(totals))
|
||||
.put(K_SET_TOTALS, true) // backend historically defaulted to true
|
||||
.put(K_MANAGE_TOTALS, manageTotals).put(K_BUNGEE_BROADCAST, bungeeBroadcast).put(K_NUM, num)
|
||||
.put(K_NUMBER_OF_VOTES, numberOfVotes).build();
|
||||
}
|
||||
|
||||
public static JsonEnvelope voteOnline(String player, String uuid, String service, long time, boolean wasOnline,
|
||||
boolean realVote, String totals, boolean manageTotals, boolean bungeeBroadcast, int num,
|
||||
int numberOfVotes) {
|
||||
|
||||
return base(SUB_VOTE_ONLINE).put(K_PLAYER, safe(player)).put(K_UUID, safe(uuid)).put(K_SERVICE, safe(service))
|
||||
.put(K_TIME, time).put(K_WAS_ONLINE, wasOnline).put(K_REAL_VOTE, realVote).put(K_TOTALS, safe(totals))
|
||||
.put(K_SET_TOTALS, true).put(K_MANAGE_TOTALS, manageTotals).put(K_BUNGEE_BROADCAST, bungeeBroadcast)
|
||||
.put(K_NUM, num).put(K_NUMBER_OF_VOTES, numberOfVotes).build();
|
||||
}
|
||||
|
||||
public static JsonEnvelope voteBroadcast(String uuid, String player, String service, long time, String totals) {
|
||||
return base(SUB_VOTE_BROADCAST).put(K_UUID, safe(uuid)).put(K_PLAYER, safe(player))
|
||||
.put(K_SERVICE, safe(service)).put(K_TIME, time).put(K_TOTALS, safe(totals)).build();
|
||||
}
|
||||
|
||||
public static JsonEnvelope voteUpdate(String playerUuid, int votePartyCurrent, int votePartyRequired,
|
||||
String service, long lastVoteTime, String totals) {
|
||||
|
||||
final String u = safe(playerUuid);
|
||||
return base(SUB_VOTE_UPDATE).put(K_PLAYER_UUID, u).put(K_PLAYER, u) // legacy fallback key used by older
|
||||
// handlers
|
||||
.put(K_VOTE_PARTY_CURRENT, votePartyCurrent).put(K_VOTE_PARTY_REQUIRED, votePartyRequired)
|
||||
.put(K_SERVICE, safe(service)).put(K_LAST_VOTE_TIME, lastVoteTime).put(K_TOTALS, safe(totals)).build();
|
||||
}
|
||||
|
||||
public static JsonEnvelope votePartyBungee() {
|
||||
return base(SUB_VOTE_PARTY).build();
|
||||
}
|
||||
|
||||
public static JsonEnvelope status(String server) {
|
||||
return base(SUB_STATUS).put(K_SERVER, safe(server)).build();
|
||||
}
|
||||
|
||||
public static JsonEnvelope statusOkay(String server) {
|
||||
return base(SUB_STATUS_OKAY).put(K_SERVER, safe(server)).build();
|
||||
}
|
||||
|
||||
public static JsonEnvelope serverName(String server) {
|
||||
return base(SUB_SERVER_NAME).put(K_SERVER, safe(server)).build();
|
||||
}
|
||||
|
||||
public static JsonEnvelope login(String player, String uuid, String server) {
|
||||
return base(SUB_LOGIN).put(K_PLAYER, safe(player)).put(K_UUID, safe(uuid)).put(K_SERVER, safe(server)).build();
|
||||
}
|
||||
|
||||
// =========================
|
||||
// Multi-proxy builders (REQUIRED by MultiProxyHandler)
|
||||
// =========================
|
||||
|
||||
/**
|
||||
* Sent by non-primary servers to ask the primary to clear vote state. Primary
|
||||
* responds by broadcasting ClearVotePrimary.
|
||||
*/
|
||||
public static JsonEnvelope clearVote(String uuid, String player, String server) {
|
||||
return base(SUB_CLEAR_VOTE).put(K_UUID, safe(uuid)).put(K_PLAYER, safe(player)).put(K_SERVER, safe(server))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast by the primary server so all proxies clear vote state.
|
||||
*/
|
||||
public static JsonEnvelope clearVotePrimary(String uuid, String player, String server) {
|
||||
return base(SUB_CLEAR_VOTE_PRIMARY).put(K_UUID, safe(uuid)).put(K_PLAYER, safe(player))
|
||||
.put(K_SERVER, safe(server)).build();
|
||||
}
|
||||
|
||||
// =========================
|
||||
// Readers (decode)
|
||||
// =========================
|
||||
|
||||
public static final class Vote {
|
||||
public final String subChannel;
|
||||
public final String player;
|
||||
public final String uuid;
|
||||
public final String service;
|
||||
public final long time;
|
||||
|
||||
public final boolean wasOnline;
|
||||
public final boolean realVote;
|
||||
public final String totals;
|
||||
|
||||
public final boolean setTotals;
|
||||
public final boolean manageTotals;
|
||||
public final boolean broadcast; // historically bungeeBroadcast
|
||||
public final int num;
|
||||
public final int numberOfVotes;
|
||||
|
||||
private Vote(String subChannel, String player, String uuid, String service, long time, boolean wasOnline,
|
||||
boolean realVote, String totals, boolean setTotals, boolean manageTotals, boolean broadcast, int num,
|
||||
int numberOfVotes) {
|
||||
this.subChannel = subChannel;
|
||||
this.player = player;
|
||||
this.uuid = uuid;
|
||||
this.service = service;
|
||||
this.time = time;
|
||||
this.wasOnline = wasOnline;
|
||||
this.realVote = realVote;
|
||||
this.totals = totals;
|
||||
this.setTotals = setTotals;
|
||||
this.manageTotals = manageTotals;
|
||||
this.broadcast = broadcast;
|
||||
this.num = num;
|
||||
this.numberOfVotes = numberOfVotes;
|
||||
}
|
||||
}
|
||||
|
||||
public static Vote readVote(JsonEnvelope env) {
|
||||
Map<String, String> f = env.getFields();
|
||||
|
||||
final String sub = safe(env.getSubChannel());
|
||||
final String player = safe(f.get(K_PLAYER));
|
||||
final String uuid = safe(f.get(K_UUID));
|
||||
final String service = safe(f.get(K_SERVICE));
|
||||
final long time = readLong(f, K_TIME, 0L);
|
||||
|
||||
final boolean wasOnline = readBool(f, K_WAS_ONLINE, false);
|
||||
final boolean realVote = readBool(f, K_REAL_VOTE, false);
|
||||
final String totals = safe(f.get(K_TOTALS));
|
||||
|
||||
final boolean setTotals = readBool(f, K_SET_TOTALS, true);
|
||||
final boolean manageTotals = readBool(f, K_MANAGE_TOTALS, false);
|
||||
|
||||
final boolean broadcast = readBool(f, K_BUNGEE_BROADCAST, false);
|
||||
|
||||
final int num = readInt(f, K_NUM, 1);
|
||||
final int numberOfVotes = readInt(f, K_NUMBER_OF_VOTES, 1);
|
||||
|
||||
return new Vote(sub, player, uuid, service, time, wasOnline, realVote, totals, setTotals, manageTotals,
|
||||
broadcast, num, numberOfVotes);
|
||||
}
|
||||
|
||||
public static final class VoteUpdate {
|
||||
public final String uuid;
|
||||
public final int votePartyCurrent;
|
||||
public final int votePartyRequired;
|
||||
public final String service;
|
||||
public final long time; // lastVoteTime
|
||||
|
||||
private VoteUpdate(String uuid, int votePartyCurrent, int votePartyRequired, String service, long time) {
|
||||
this.uuid = uuid;
|
||||
this.votePartyCurrent = votePartyCurrent;
|
||||
this.votePartyRequired = votePartyRequired;
|
||||
this.service = service;
|
||||
this.time = time;
|
||||
}
|
||||
}
|
||||
|
||||
public static VoteUpdate readVoteUpdate(JsonEnvelope env) {
|
||||
Map<String, String> f = env.getFields();
|
||||
|
||||
String uuid = safe(f.get(K_PLAYER_UUID));
|
||||
if (uuid.isEmpty()) {
|
||||
uuid = safe(f.get(K_PLAYER));
|
||||
}
|
||||
|
||||
int cur = readInt(f, K_VOTE_PARTY_CURRENT, 0);
|
||||
int req = readInt(f, K_VOTE_PARTY_REQUIRED, 0);
|
||||
String service = safe(f.get(K_SERVICE));
|
||||
|
||||
long t = readLong(f, K_LAST_VOTE_TIME, 0L);
|
||||
if (t == 0L) {
|
||||
t = readLong(f, K_TIME, 0L);
|
||||
}
|
||||
|
||||
return new VoteUpdate(uuid, cur, req, service, t);
|
||||
}
|
||||
|
||||
// =========================
|
||||
// Internals
|
||||
// =========================
|
||||
|
||||
private static JsonEnvelope.Builder base(String subChannel) {
|
||||
int ver = SCHEMA_VERSION;
|
||||
return JsonEnvelope.builder(subChannel).schema(ver);
|
||||
}
|
||||
|
||||
private static String safe(String s) {
|
||||
return s == null ? "" : s;
|
||||
}
|
||||
|
||||
private static boolean readBool(Map<String, String> f, String key, boolean def) {
|
||||
String v = f.get(key);
|
||||
if (v == null) {
|
||||
return def;
|
||||
}
|
||||
return Boolean.parseBoolean(v);
|
||||
}
|
||||
|
||||
private static int readInt(Map<String, String> f, String key, int def) {
|
||||
String v = f.get(key);
|
||||
if (v == null) {
|
||||
return def;
|
||||
}
|
||||
try {
|
||||
return Integer.parseInt(v);
|
||||
} catch (Exception ignored) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
private static long readLong(Map<String, String> f, String key, long def) {
|
||||
String v = f.get(key);
|
||||
if (v == null) {
|
||||
return def;
|
||||
}
|
||||
try {
|
||||
return Long.parseLong(v);
|
||||
} catch (Exception ignored) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package com.bencodez.votingplugin.proxy.broadcast;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Enum for offline broadcast modes.
|
||||
*/
|
||||
public enum OfflineMode {
|
||||
/** No offline handling. */
|
||||
NONE,
|
||||
/** Queue votes for offline players. */
|
||||
QUEUE,
|
||||
/** Forward to other servers. */
|
||||
FORWARD;
|
||||
|
||||
/**
|
||||
* Parse offline mode from string.
|
||||
* @param value the string to parse
|
||||
* @param def the default value
|
||||
* @return the offline mode
|
||||
*/
|
||||
public static OfflineMode parse(String value, OfflineMode def) {
|
||||
if (value == null)
|
||||
return def;
|
||||
String v = value.trim();
|
||||
if (v.isEmpty())
|
||||
return def;
|
||||
try {
|
||||
return OfflineMode.valueOf(v.toUpperCase(Locale.ROOT));
|
||||
} catch (IllegalArgumentException e) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
}
|
||||
-161
@@ -1,161 +0,0 @@
|
||||
package com.bencodez.votingplugin.proxy.broadcast;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.bencodez.votingplugin.proxy.VotingPluginProxyConfig;
|
||||
|
||||
/**
|
||||
* Decides where vote broadcasts should execute (proxy-side routing only).
|
||||
*
|
||||
* Backend servers still decide WHAT to broadcast (VoteBroadcast.Type/Format).
|
||||
*
|
||||
* QUEUE behavior:
|
||||
* - This class returns no immediate targets when player is offline and OfflineMode=QUEUE.
|
||||
* - The "queue" is the existing online vote cache (addOnlineVote + checkOnlineVotes on login).
|
||||
*/
|
||||
public final class ProxyBroadcastDecider {
|
||||
|
||||
private final Supplier<VotingPluginProxyConfig> config;
|
||||
private final Supplier<Set<String>> allServers;
|
||||
private final Predicate<String> isServerValid;
|
||||
private final Predicate<String> isBlockedServer;
|
||||
|
||||
/**
|
||||
* Constructor for ProxyBroadcastDecider.
|
||||
* @param config configuration supplier
|
||||
* @param allServers all servers supplier
|
||||
* @param isServerValid server validation predicate
|
||||
* @param isBlockedServer blocked server predicate
|
||||
*/
|
||||
public ProxyBroadcastDecider(Supplier<VotingPluginProxyConfig> config,
|
||||
Supplier<Set<String>> allServers,
|
||||
Predicate<String> isServerValid,
|
||||
Predicate<String> isBlockedServer) {
|
||||
this.config = config;
|
||||
this.allServers = allServers;
|
||||
this.isServerValid = isServerValid;
|
||||
this.isBlockedServer = isBlockedServer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute which backend servers should broadcast for this vote.
|
||||
*
|
||||
* @param playerOnline whether the voting player is currently online
|
||||
* @param currentPlayerServer the server the player is on (only used when playerOnline=true)
|
||||
* @return set of target servers
|
||||
*/
|
||||
public Set<String> resolveTargets(boolean playerOnline, String currentPlayerServer) {
|
||||
VotingPluginProxyConfig cfg = config.get();
|
||||
if (cfg == null || !cfg.getProxyBroadcastEnabled()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
ScopeMode scope = ScopeMode.parse(cfg.getProxyBroadcastScopeMode(), ScopeMode.ALL_SERVERS);
|
||||
List<String> scopeServers = cfg.getProxyBroadcastScopeServers();
|
||||
|
||||
Set<String> eligible = eligibleServers();
|
||||
|
||||
switch (scope) {
|
||||
case ALL_SERVERS:
|
||||
return eligible;
|
||||
|
||||
case SERVERS:
|
||||
return intersect(scopeServers, eligible);
|
||||
|
||||
case ALL_EXCEPT:
|
||||
return subtract(scopeServers, eligible);
|
||||
|
||||
case PLAYER_SERVER:
|
||||
default:
|
||||
if (playerOnline) {
|
||||
if (currentPlayerServer != null && eligible.contains(currentPlayerServer)) {
|
||||
Set<String> out = new LinkedHashSet<>();
|
||||
out.add(currentPlayerServer);
|
||||
return out;
|
||||
}
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
// player offline + PLAYER_SERVER scope
|
||||
OfflineMode offlineMode = OfflineMode.parse(cfg.getProxyBroadcastOfflineMode(), OfflineMode.QUEUE);
|
||||
|
||||
if (offlineMode == OfflineMode.FORWARD) {
|
||||
// FORWARD uses OfflineForward.Servers (list only; mode is implicit SERVERS)
|
||||
return intersect(cfg.getProxyBroadcastOfflineForwardServers(), eligible);
|
||||
}
|
||||
|
||||
// NONE or QUEUE => no immediate targets
|
||||
// QUEUE is handled by existing vote cache + checkOnlineVotes() on login
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience check for "should this specific server broadcast?"
|
||||
* @param server the server name
|
||||
* @param targets the target servers
|
||||
* @return true if should broadcast
|
||||
*/
|
||||
public boolean shouldBroadcast(String server, Set<String> targets) {
|
||||
return targets != null && server != null && targets.contains(server);
|
||||
}
|
||||
|
||||
private Set<String> eligibleServers() {
|
||||
Set<String> out = new LinkedHashSet<>();
|
||||
for (String s : allServers.get()) {
|
||||
if (s == null || s.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (!isServerValid.test(s)) {
|
||||
continue;
|
||||
}
|
||||
if (isBlockedServer.test(s)) {
|
||||
continue;
|
||||
}
|
||||
out.add(s);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static Set<String> intersect(List<String> list, Set<String> eligible) {
|
||||
if (list == null || list.isEmpty() || eligible.isEmpty()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
Set<String> out = new LinkedHashSet<>();
|
||||
for (String s : list) {
|
||||
if (s == null) {
|
||||
continue;
|
||||
}
|
||||
String v = s.trim();
|
||||
if (!v.isEmpty() && eligible.contains(v)) {
|
||||
out.add(v);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static Set<String> subtract(List<String> list, Set<String> eligible) {
|
||||
if (eligible.isEmpty()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
Set<String> out = new LinkedHashSet<>(eligible);
|
||||
if (list == null || list.isEmpty()) {
|
||||
return out;
|
||||
}
|
||||
for (String s : list) {
|
||||
if (s == null) {
|
||||
continue;
|
||||
}
|
||||
String v = s.trim();
|
||||
if (!v.isEmpty()) {
|
||||
out.remove(v);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package com.bencodez.votingplugin.proxy.broadcast;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Proxy-side routing scope for broadcast triggers.
|
||||
*/
|
||||
public enum ScopeMode {
|
||||
/**
|
||||
* Send only to the backend server the player is currently on.
|
||||
*/
|
||||
PLAYER_SERVER,
|
||||
|
||||
/**
|
||||
* Send to all connected backend servers.
|
||||
*/
|
||||
ALL_SERVERS,
|
||||
|
||||
/**
|
||||
* Send only to the configured server list.
|
||||
*/
|
||||
SERVERS,
|
||||
|
||||
/**
|
||||
* Send to all servers except the configured server list.
|
||||
*/
|
||||
ALL_EXCEPT;
|
||||
|
||||
/**
|
||||
* Parses a string value to a ScopeMode with a default fallback.
|
||||
*
|
||||
* @param value the string value to parse
|
||||
* @param def the default value if parsing fails
|
||||
* @return the parsed ScopeMode or the default
|
||||
*/
|
||||
public static ScopeMode parse(String value, ScopeMode def) {
|
||||
if (value == null) {
|
||||
return def;
|
||||
}
|
||||
String v = value.trim();
|
||||
if (v.isEmpty()) {
|
||||
return def;
|
||||
}
|
||||
v = v.toUpperCase(Locale.ROOT);
|
||||
|
||||
// allow a couple common aliases
|
||||
if (v.equals("ALL")) {
|
||||
return ALL_SERVERS;
|
||||
}
|
||||
if (v.equals("PLAYER")) {
|
||||
return PLAYER_SERVER;
|
||||
}
|
||||
|
||||
try {
|
||||
return ScopeMode.valueOf(v);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a string value to a ScopeMode with ALL_SERVERS as default.
|
||||
*
|
||||
* @param value the string value to parse
|
||||
* @return the parsed ScopeMode or ALL_SERVERS
|
||||
*/
|
||||
public static ScopeMode parse(String value) {
|
||||
return parse(value, ALL_SERVERS);
|
||||
}
|
||||
}
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
package com.bencodez.votingplugin.proxy.bungee;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.bencodez.simpleapi.file.BungeeJsonFile;
|
||||
import com.bencodez.votingplugin.proxy.cache.nonvoted.INonVotedPlayersStorage;
|
||||
|
||||
/**
|
||||
* JSON file-based storage for non-voted players cache on Bungee.
|
||||
*/
|
||||
public class BungeeJsonNonVotedPlayersCache extends BungeeJsonFile
|
||||
implements INonVotedPlayersStorage {
|
||||
|
||||
/**
|
||||
* Constructs a new BungeeJsonNonVotedPlayersCache.
|
||||
*
|
||||
* @param file the file to store data in
|
||||
*/
|
||||
public BungeeJsonNonVotedPlayersCache(File file) {
|
||||
super(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void upsertPlayer(String uuid, String playerName, long lastTime) {
|
||||
setString("NonVotedPlayers." + playerName + ".UUID", uuid);
|
||||
setLong("NonVotedPlayers." + playerName + ".LastTime", lastTime);
|
||||
save();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUuidByPlayerName(String playerName) {
|
||||
String uuid = getString("NonVotedPlayers." + playerName + ".UUID", "");
|
||||
return uuid != null ? uuid : "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeByPlayerName(String playerName) {
|
||||
remove("NonVotedPlayers." + playerName);
|
||||
save();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(Consumer<NonVotedPlayerEntry> consumer) {
|
||||
for (String player : getKeys("NonVotedPlayers")) {
|
||||
long time = getLong("NonVotedPlayers." + player + ".LastTime", 0);
|
||||
String uuid = getString("NonVotedPlayers." + player + ".UUID", "");
|
||||
consumer.accept(new NonVotedPlayerEntry(player, uuid, time));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
}
|
||||
-887
@@ -1,887 +0,0 @@
|
||||
package com.bencodez.votingplugin.proxy.bungee;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.URL;
|
||||
import java.security.CodeSource;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import com.bencodez.simpleapi.sql.mysql.config.MysqlConfig;
|
||||
import com.bencodez.simpleapi.sql.mysql.config.MysqlConfigBungee;
|
||||
import com.bencodez.votingplugin.proxy.VotingPluginProxy;
|
||||
import com.bencodez.votingplugin.proxy.VotingPluginProxyConfig;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.connection.Server;
|
||||
import net.md_5.bungee.api.event.PluginMessageEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.api.scheduler.ScheduledTask;
|
||||
import net.md_5.bungee.config.Configuration;
|
||||
import net.md_5.bungee.config.ConfigurationProvider;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
/**
|
||||
* VotingPlugin proxy implementation for BungeeCord.
|
||||
*
|
||||
* Supports a true hot reload (no proxy restart required) by:
|
||||
* <ul>
|
||||
* <li>Stopping the old {@link VotingPluginProxy} runtime</li>
|
||||
* <li>Reloading configuration</li>
|
||||
* <li>Re-registering the plugin message channel if it changed</li>
|
||||
* <li>Creating a fresh {@link VotingPluginProxy} instance (constructor-time
|
||||
* state refreshed)</li>
|
||||
* <li>Re-initializing caches and rescheduling tasks</li>
|
||||
* </ul>
|
||||
*
|
||||
*/
|
||||
public class VotingPluginBungee extends Plugin implements Listener {
|
||||
|
||||
@Getter
|
||||
private BungeeConfig config;
|
||||
|
||||
private BungeeJsonNonVotedPlayersCache nonVotedPlayersCache;
|
||||
private BungeeJsonVoteCache voteCacheFile;
|
||||
|
||||
private String buildNumber = "NOTSET";
|
||||
|
||||
/**
|
||||
* Votifier listener (registered only once).
|
||||
*/
|
||||
private VoteEventBungee voteEventBungee;
|
||||
|
||||
@Getter
|
||||
private volatile VotingPluginProxy votingPluginProxy;
|
||||
|
||||
@Getter
|
||||
private ScheduledExecutorService timer;
|
||||
|
||||
private ScheduledTask voteCheckTask;
|
||||
private ScheduledTask cacheSaveTask;
|
||||
|
||||
/**
|
||||
* Reload synchronization lock.
|
||||
*/
|
||||
private final Object reloadLock = new Object();
|
||||
|
||||
/**
|
||||
* True while reload is in progress.
|
||||
*/
|
||||
private volatile boolean reloading = false;
|
||||
|
||||
/**
|
||||
* Plugin messages received during reload are queued and replayed after reload.
|
||||
*/
|
||||
private final Queue<byte[]> queuedPluginMessages = new ConcurrentLinkedQueue<byte[]>();
|
||||
|
||||
/**
|
||||
* Logs debug output if enabled in config.
|
||||
*
|
||||
* @param msg message to log
|
||||
*/
|
||||
public void debug(String msg) {
|
||||
if (config != null && config.getDebug()) {
|
||||
getLogger().info("Debug: " + msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for debug for existing call sites.
|
||||
*
|
||||
* @param msg message to log
|
||||
*/
|
||||
public void debug2(String msg) {
|
||||
debug(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets servers eligible for proxy operations.
|
||||
*
|
||||
* @return server names
|
||||
*/
|
||||
public Set<String> getAvailableAllServers() {
|
||||
Set<String> servers = new HashSet<String>();
|
||||
if (config.getWhiteListedServers().isEmpty()) {
|
||||
for (String s : getProxy().getServers().keySet()) {
|
||||
if (!config.getBlockedServers().contains(s)) {
|
||||
servers.add(s);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
servers.addAll(config.getWhiteListedServers());
|
||||
}
|
||||
return servers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets proper player name if online, otherwise returns stored name.
|
||||
*
|
||||
* @param uuid uuid string
|
||||
* @param currentName current name
|
||||
* @return proper name if online
|
||||
*/
|
||||
public String getProperPlayerName(String uuid, String currentName) {
|
||||
ProxiedPlayer p = getProxy().getPlayer(UUID.fromString(uuid));
|
||||
if (p != null && p.isConnected()) {
|
||||
return p.getName();
|
||||
}
|
||||
return currentName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads votingpluginversion.yml from jar if present.
|
||||
*
|
||||
* @return configuration or null
|
||||
*/
|
||||
private Configuration getVersionFile() {
|
||||
try {
|
||||
CodeSource src = this.getClass().getProtectionDomain().getCodeSource();
|
||||
if (src != null) {
|
||||
URL jar = src.getLocation();
|
||||
ZipInputStream zip = new ZipInputStream(jar.openStream());
|
||||
while (true) {
|
||||
ZipEntry e = zip.getNextEntry();
|
||||
if (e != null) {
|
||||
if ("votingpluginversion.yml".equals(e.getName())) {
|
||||
Reader defConfigStream = new InputStreamReader(zip);
|
||||
if (defConfigStream != null) {
|
||||
Configuration conf = ConfigurationProvider
|
||||
.getProvider(net.md_5.bungee.config.YamlConfiguration.class)
|
||||
.load(defConfigStream);
|
||||
defConfigStream.close();
|
||||
return conf;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads build number from embedded version file.
|
||||
*/
|
||||
public void loadVersionFile() {
|
||||
Configuration conf = getVersionFile();
|
||||
if (conf != null) {
|
||||
buildNumber = conf.getString("buildnumber", "NOTSET");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
timer = Executors.newScheduledThreadPool(1);
|
||||
|
||||
getProxy().getPluginManager().registerListener(this, this);
|
||||
|
||||
config = new BungeeConfig(this);
|
||||
config.load();
|
||||
|
||||
getProxy().getPluginManager().registerCommand(this, new VotingPluginBungeeCommand(this));
|
||||
|
||||
// Ensure channel registered
|
||||
try {
|
||||
getProxy().registerChannel(config.getPluginMessageChannel());
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// Create initial runtime (fresh instance)
|
||||
votingPluginProxy = createProxyRuntime();
|
||||
|
||||
// Full init using the same pathway as reloadall
|
||||
reloadPlugin(true);
|
||||
|
||||
loadVersionFile();
|
||||
getLogger().info("VotingPlugin loaded, using method: " + getVotingPluginProxy().getMethod().toString());
|
||||
if (!"NOTSET".equals(buildNumber)) {
|
||||
getLogger().info("Detected using dev build number: " + buildNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
synchronized (reloadLock) {
|
||||
reloading = true;
|
||||
|
||||
cancelPlatformTasks();
|
||||
|
||||
try {
|
||||
if (votingPluginProxy != null) {
|
||||
votingPluginProxy.onDisable();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
try {
|
||||
if (voteCacheFile != null) {
|
||||
voteCacheFile.save();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
try {
|
||||
if (nonVotedPlayersCache != null) {
|
||||
nonVotedPlayersCache.save();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
try {
|
||||
if (timer != null) {
|
||||
timer.shutdownNow();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
reloading = false;
|
||||
}
|
||||
|
||||
getLogger().info("VotingPlugin disabled");
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles plugin messages from backend servers.
|
||||
*
|
||||
* @param ev plugin message event
|
||||
*/
|
||||
@EventHandler
|
||||
public void onPluginMessage(PluginMessageEvent ev) {
|
||||
if (config == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ev.getTag().equalsIgnoreCase(getConfig().getPluginMessageChannel())) {
|
||||
return;
|
||||
}
|
||||
|
||||
ev.setCancelled(true);
|
||||
|
||||
if (!(ev.getSender() instanceof Server)) {
|
||||
debug("Ignore plugin message (not from server)");
|
||||
return;
|
||||
}
|
||||
|
||||
// During reload, queue and replay later to avoid calling into disposed runtime
|
||||
if (reloading) {
|
||||
byte[] copy = new byte[ev.getData().length];
|
||||
System.arraycopy(ev.getData(), 0, copy, 0, ev.getData().length);
|
||||
queuedPluginMessages.add(copy);
|
||||
return;
|
||||
}
|
||||
|
||||
handlePluginMessageBytes(ev.getData());
|
||||
}
|
||||
|
||||
/**
|
||||
* Hot-reloads the proxy runtime.
|
||||
*
|
||||
* <p>
|
||||
* Two modes:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li><b>Soft reload</b> ({@code loadMysql=false}): keeps the existing
|
||||
* {@link VotingPluginProxy} instance (and its MySQL pool) alive. Only reloads
|
||||
* config/channel and applies runtime-only changes via
|
||||
* {@link VotingPluginProxy#reload()}.</li>
|
||||
* <li><b>Full reload</b> ({@code loadMysql=true}): fully tears down the old
|
||||
* runtime (including MySQL), recreates the proxy instance, reconnects MySQL
|
||||
* from config, then calls
|
||||
* {@link VotingPluginProxy#load(com.bencodez.votingplugin.proxy.cache.IVoteCache, com.bencodez.votingplugin.proxy.cache.nonvoted.INonVotedPlayersStorage)}.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param loadMysql true to fully reinitialize MySQL and rebuild the proxy
|
||||
* runtime, false for a config/runtime-only reload
|
||||
*/
|
||||
public void reloadPlugin(boolean loadMysql) {
|
||||
synchronized (reloadLock) {
|
||||
reloading = true;
|
||||
|
||||
final String oldChannel = (config != null) ? config.getPluginMessageChannel() : null;
|
||||
|
||||
// Always stop platform tasks while we touch state
|
||||
cancelPlatformTasks();
|
||||
|
||||
// Always reload config first
|
||||
try {
|
||||
config.load();
|
||||
} catch (Exception e) {
|
||||
getLogger().severe("Failed to reload bungeeconfig.yml");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// Update channel registration if changed (safe for both modes)
|
||||
try {
|
||||
final String newChannel = config.getPluginMessageChannel();
|
||||
if (oldChannel != null && newChannel != null && !oldChannel.equalsIgnoreCase(newChannel)) {
|
||||
try {
|
||||
getProxy().unregisterChannel(oldChannel);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (newChannel != null && !newChannel.isEmpty()) {
|
||||
getProxy().registerChannel(newChannel);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// keep going; channel mismatch should not kill reload
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// =========================
|
||||
// SOFT RELOAD (NO MYSQL)
|
||||
// =========================
|
||||
if (!loadMysql) {
|
||||
try {
|
||||
if (votingPluginProxy != null) {
|
||||
// Applies runtime-only config (method selection, multiproxy settings, vote
|
||||
// party settings, etc.)
|
||||
votingPluginProxy.reload();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
getLogger().severe("Error while applying soft reload");
|
||||
t.printStackTrace();
|
||||
}
|
||||
|
||||
// Restart tasks (they rely on already-loaded handlers on the existing proxy
|
||||
// instance)
|
||||
try {
|
||||
schedulePlatformTasks();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
reloading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// =========================
|
||||
// FULL RELOAD (WITH MYSQL)
|
||||
// =========================
|
||||
|
||||
// Save caches best-effort before teardown
|
||||
try {
|
||||
if (voteCacheFile != null) {
|
||||
voteCacheFile.save();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
try {
|
||||
if (nonVotedPlayersCache != null) {
|
||||
nonVotedPlayersCache.save();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// Tear down old runtime (this WILL shutdown MySQL because proxy owns it)
|
||||
try {
|
||||
if (votingPluginProxy != null) {
|
||||
votingPluginProxy.onDisable();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// Recreate runtime
|
||||
votingPluginProxy = createProxyRuntime();
|
||||
|
||||
// Initialize MySQL BEFORE calling proxy.load(...)
|
||||
try {
|
||||
if (config.hasDatabaseConfigured()) {
|
||||
votingPluginProxy.loadMysql(getMysqlConfig(), getGlobalDataMysqlConfig());
|
||||
} else {
|
||||
getLogger().severe("MySQL settings not set in bungeeconfig.yml");
|
||||
votingPluginProxy.setProxyMySQL(null);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
getLogger().severe("Failed to initialize MySQL during reload");
|
||||
t.printStackTrace();
|
||||
votingPluginProxy.setProxyMySQL(null);
|
||||
}
|
||||
|
||||
// Abort cleanly if MySQL did not initialize (prevents NPE inside
|
||||
// VotingPluginProxy.load)
|
||||
if (votingPluginProxy.getProxyMySQL() == null) {
|
||||
getLogger().severe("Reload aborted: Proxy MySQL is not initialized (see logs above).");
|
||||
reloading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure caches exist and load into runtime
|
||||
try {
|
||||
if (voteCacheFile == null) {
|
||||
voteCacheFile = new BungeeJsonVoteCache(this);
|
||||
}
|
||||
if (nonVotedPlayersCache == null) {
|
||||
nonVotedPlayersCache = new BungeeJsonNonVotedPlayersCache(
|
||||
new File(getDataFolder(), "nonvotedplayerscache.json"));
|
||||
}
|
||||
|
||||
// Load proxy state (requires MySQL)
|
||||
votingPluginProxy.load(voteCacheFile, nonVotedPlayersCache);
|
||||
|
||||
// Apply runtime-only config too
|
||||
votingPluginProxy.reload();
|
||||
} catch (Throwable t) {
|
||||
getLogger().severe("Reload aborted while loading proxy state");
|
||||
t.printStackTrace();
|
||||
reloading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Restart tasks after a successful swap
|
||||
try {
|
||||
schedulePlatformTasks();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
reloading = false;
|
||||
}
|
||||
|
||||
// Replay queued plugin messages after swap
|
||||
drainQueuedPluginMessages();
|
||||
|
||||
initVotifierListenerIfNeeded();
|
||||
|
||||
// Send server name message again (safe)
|
||||
try {
|
||||
getVotingPluginProxy().sendServerNameMessage();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new VotingPluginProxy instance wired to this platform.
|
||||
*
|
||||
* @return new proxy runtime
|
||||
*/
|
||||
private VotingPluginProxy createProxyRuntime() {
|
||||
return new VotingPluginProxy() {
|
||||
|
||||
@Override
|
||||
public void broadcast(String message) {
|
||||
getProxy().broadcast(new TextComponent(ChatColor.translateAlternateColorCodes('&', message)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(String str) {
|
||||
debug2(str);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getAllAvailableServers() {
|
||||
return getAvailableAllServers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public VotingPluginProxyConfig getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCurrentPlayerServer(String player) {
|
||||
ProxiedPlayer p = getProxy().getPlayer(player);
|
||||
if (p != null && p.getServer() != null) {
|
||||
return p.getServer().getInfo().getName();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getDataFolderPlugin() {
|
||||
return getDataFolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProperName(String uuid, String playerName) {
|
||||
return getProperPlayerName(uuid, playerName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUUID(String playerName) {
|
||||
if (playerName == null || playerName.isEmpty() || "null".equalsIgnoreCase(playerName)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!config.getOnlineMode()) {
|
||||
return UUID.nameUUIDFromBytes(("OfflinePlayer:" + playerName.toLowerCase(Locale.ROOT).trim())
|
||||
.getBytes(java.nio.charset.StandardCharsets.UTF_8)).toString();
|
||||
}
|
||||
|
||||
ProxiedPlayer p = getProxy().getPlayer(playerName);
|
||||
if (p != null && p.isConnected()) {
|
||||
playerName = p.getName();
|
||||
}
|
||||
|
||||
for (Entry<UUID, String> entry : getVotingPluginProxy().getUuidPlayerNameCache().entrySet()) {
|
||||
String cachedName = entry.getValue();
|
||||
if (cachedName != null && cachedName.equalsIgnoreCase(playerName)) {
|
||||
playerName = cachedName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (p != null && p.isConnected()) {
|
||||
return p.getUniqueId().toString();
|
||||
}
|
||||
|
||||
for (Entry<UUID, String> entry : getVotingPluginProxy().getUuidPlayerNameCache().entrySet()) {
|
||||
if (entry.getValue() != null && entry.getValue().equalsIgnoreCase(playerName)) {
|
||||
return entry.getKey().toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (getVotingPluginProxy().getProxyMySQL() != null) {
|
||||
String str = getVotingPluginProxy().getProxyMySQL().getUUID(playerName);
|
||||
if (str != null) {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
return getVotingPluginProxy().getNonVotedPlayersCache().getUUID(playerName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPluginVersion() {
|
||||
return getDescription().getVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVoteCacheCurrentVotePartyVotes() {
|
||||
return voteCacheFile.getVotePartyCurrentVotes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getVoteCacheLastUpdated() {
|
||||
return voteCacheFile.getLong("Time.LastUpdated", 0L);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVoteCachePrevDay() {
|
||||
return voteCacheFile.getInt("Time.Day", 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVoteCachePrevMonth() {
|
||||
return voteCacheFile.getString("Time.Month", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVoteCachePrevWeek() {
|
||||
return voteCacheFile.getInt("Time.Week", 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVoteCacheVotePartyIncreaseVotesRequired() {
|
||||
return voteCacheFile.getVotePartyInreaseVotesRequired();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlayerOnline(String playerName) {
|
||||
ProxiedPlayer player = getProxy().getPlayer(playerName);
|
||||
return player != null && player.isConnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isServerValid(String server) {
|
||||
return getProxy().getServerInfo(server) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSomeoneOnlineServer(String server) {
|
||||
return getProxy().getServerInfo(server) != null
|
||||
&& !getProxy().getServerInfo(server).getPlayers().isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVoteCacheIgnoreTime() {
|
||||
return voteCacheFile.getBoolean("Time.IgnoreTime", false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String message) {
|
||||
getLogger().info(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logSevere(String message) {
|
||||
getLogger().severe(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runAsync(Runnable run) {
|
||||
runAsyncNow(run);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runConsoleCommand(String command) {
|
||||
getProxy().getPluginManager().dispatchCommand(getProxy().getConsole(), command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveVoteCacheFile() {
|
||||
voteCacheFile.save();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPluginMessageData(String server, String channel, byte[] data, boolean queue) {
|
||||
if (getProxy().getServerInfo(server) != null) {
|
||||
getProxy().getServerInfo(server).sendData(channel, data, queue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVoteCacheLastUpdated() {
|
||||
voteCacheFile.setLong("Time.LastUpdated", System.currentTimeMillis());
|
||||
voteCacheFile.save();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVoteCachePrevDay(int day) {
|
||||
voteCacheFile.setInt("Time.Day", day);
|
||||
voteCacheFile.save();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVoteCachePrevMonth(String text) {
|
||||
voteCacheFile.setString("Time.Month", text);
|
||||
voteCacheFile.save();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVoteCachePrevWeek(int week) {
|
||||
voteCacheFile.setInt("Time.Week", week);
|
||||
voteCacheFile.save();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVoteCacheVoteCacheIgnoreTime(boolean ignore) {
|
||||
voteCacheFile.setBoolean("Time.IgnoreTime", ignore);
|
||||
voteCacheFile.save();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVoteCacheVotePartyCurrentVotes(int votes) {
|
||||
voteCacheFile.setVotePartyCurrentVotes(votes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVoteCacheVotePartyIncreaseVotesRequired(int votes) {
|
||||
voteCacheFile.setVotePartyInreaseVotesRequired(votes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String message) {
|
||||
getLogger().warning(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reloadCore(boolean mysql) {
|
||||
// mysql==true should do full reloadall behavior on the platform
|
||||
reloadPlugin(mysql);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledExecutorService getScheduler() {
|
||||
return timer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MysqlConfig getVoteCacheMySQLConfig() {
|
||||
return new MysqlConfigBungee(config.getData().getSection("VoteCache"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MysqlConfig getNonVotedCacheMySQLConfig() {
|
||||
return new MysqlConfigBungee(config.getData().getSection("NonVotedCache"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MysqlConfig getVoteLoggingMySQLConfig() {
|
||||
return new MysqlConfigBungee(config.getData().getSection("VoteLogging"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadTaskTimer(Runnable runnable, long delaySeconds, long repeatSeconds) {
|
||||
timer.scheduleAtFixedRate(runnable, delaySeconds, repeatSeconds, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the MySQL configuration for Bungee.
|
||||
*
|
||||
* <p>
|
||||
* Prefers the "Database" section if it exists and contains keys; otherwise
|
||||
* falls back to legacy root keys.
|
||||
* </p>
|
||||
*
|
||||
* @return mysql config (never null)
|
||||
*/
|
||||
public MysqlConfig getMysqlConfig() {
|
||||
Configuration data = config.getData();
|
||||
Configuration db = config.sectionOrNull(data, "Database");
|
||||
|
||||
if (isMissingOrEmptySection(db)) {
|
||||
// legacy root keys
|
||||
return new MysqlConfigBungee(data);
|
||||
}
|
||||
|
||||
return new MysqlConfigBungee(db);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a configuration section is missing or empty.
|
||||
*
|
||||
* @param section config section (may be null)
|
||||
* @return true if null or contains no keys
|
||||
*/
|
||||
private static boolean isMissingOrEmptySection(Configuration section) {
|
||||
if (section == null) {
|
||||
return true;
|
||||
}
|
||||
return section.getKeys() == null || section.getKeys().isEmpty();
|
||||
}
|
||||
|
||||
public MysqlConfig getGlobalDataMysqlConfig() {
|
||||
return new MysqlConfigBungee(config.getData().getSection("GlobalData"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes votifier listener if enabled and available. Registers the
|
||||
* listener only once.
|
||||
*/
|
||||
private void initVotifierListenerIfNeeded() {
|
||||
boolean eventFound = true;
|
||||
try {
|
||||
Class.forName("com.vexsoftware.votifier.bungee.events.VotifierEvent");
|
||||
} catch (ClassNotFoundException e) {
|
||||
eventFound = false;
|
||||
}
|
||||
|
||||
if (!eventFound) {
|
||||
getVotingPluginProxy().setVotifierEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (getVotingPluginProxy().isVotifierEnabled()) {
|
||||
if (voteEventBungee == null) {
|
||||
try {
|
||||
voteEventBungee = new VoteEventBungee(this);
|
||||
getProxy().getPluginManager().registerListener(this, voteEventBungee);
|
||||
} catch (Exception e) {
|
||||
getVotingPluginProxy().setVotifierEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels Bungee scheduled tasks owned by this plugin.
|
||||
*/
|
||||
private void cancelPlatformTasks() {
|
||||
try {
|
||||
if (voteCheckTask != null) {
|
||||
voteCheckTask.cancel();
|
||||
voteCheckTask = null;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
try {
|
||||
if (cacheSaveTask != null) {
|
||||
cacheSaveTask.cancel();
|
||||
cacheSaveTask = null;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules platform periodic tasks.
|
||||
*/
|
||||
private void schedulePlatformTasks() {
|
||||
voteCheckTask = getProxy().getScheduler().schedule(this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (String server : getVotingPluginProxy().getVoteCacheHandler().getCachedVotesServers()) {
|
||||
getVotingPluginProxy().checkCachedVotes(server);
|
||||
}
|
||||
for (ProxiedPlayer player : getProxy().getPlayers()) {
|
||||
getVotingPluginProxy().checkOnlineVotes(player.getName(), player.getUniqueId().toString(), null);
|
||||
}
|
||||
}
|
||||
}, 120L, 60L, TimeUnit.SECONDS);
|
||||
|
||||
cacheSaveTask = getProxy().getScheduler().schedule(this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (nonVotedPlayersCache != null) {
|
||||
debug("Checking nonvotedplayerscache...");
|
||||
getVotingPluginProxy().getNonVotedPlayersCache().check();
|
||||
}
|
||||
if (voteCacheFile != null) {
|
||||
voteCacheFile.save();
|
||||
}
|
||||
}
|
||||
}, 1L, 60L, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a task asynchronously on the Bungee scheduler.
|
||||
*
|
||||
* @param runnable task
|
||||
*/
|
||||
private void runAsyncNow(Runnable runnable) {
|
||||
getProxy().getScheduler().runAsync(this, runnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses and handles a plugin message payload.
|
||||
*
|
||||
* @param data payload bytes
|
||||
*/
|
||||
private void handlePluginMessageBytes(byte[] data) {
|
||||
ByteArrayInputStream instream = new ByteArrayInputStream(data);
|
||||
DataInputStream in = new DataInputStream(instream);
|
||||
try {
|
||||
getVotingPluginProxy().onPluginMessageReceived(in);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replays queued plugin messages after reload completes.
|
||||
*/
|
||||
private void drainQueuedPluginMessages() {
|
||||
byte[] msg;
|
||||
while ((msg = queuedPluginMessages.poll()) != null) {
|
||||
try {
|
||||
handlePluginMessageBytes(msg);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-117
@@ -1,117 +0,0 @@
|
||||
package com.bencodez.votingplugin.proxy.cache;
|
||||
|
||||
/**
|
||||
* Interface for data node operations.
|
||||
*/
|
||||
public interface DataNode {
|
||||
/**
|
||||
* Checks if this node is an object.
|
||||
*
|
||||
* @return true if this is an object node
|
||||
*/
|
||||
boolean isObject();
|
||||
|
||||
/**
|
||||
* Checks if this node is an array.
|
||||
*
|
||||
* @return true if this is an array node
|
||||
*/
|
||||
boolean isArray();
|
||||
|
||||
/**
|
||||
* Checks if this node is a primitive value.
|
||||
*
|
||||
* @return true if this is a primitive node
|
||||
*/
|
||||
boolean isPrimitive();
|
||||
|
||||
/**
|
||||
* Gets a child node by key.
|
||||
*
|
||||
* @param key the key
|
||||
* @return the child data node
|
||||
*/
|
||||
DataNode get(String key);
|
||||
|
||||
/**
|
||||
* Gets a child node by index.
|
||||
*
|
||||
* @param index the index
|
||||
* @return the child data node
|
||||
*/
|
||||
DataNode get(int index);
|
||||
|
||||
/**
|
||||
* Sets a value for a key.
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value to set
|
||||
*/
|
||||
void set(String key, Object value);
|
||||
|
||||
/**
|
||||
* Sets a value at an index.
|
||||
*
|
||||
* @param index the index
|
||||
* @param value the value to set
|
||||
*/
|
||||
void set(int index, Object value);
|
||||
|
||||
/**
|
||||
* Gets the primitive value.
|
||||
*
|
||||
* @return the primitive object
|
||||
*/
|
||||
Object asPrimitive();
|
||||
|
||||
/**
|
||||
* Gets the value as a string.
|
||||
*
|
||||
* @return the string value
|
||||
*/
|
||||
String asString();
|
||||
|
||||
/**
|
||||
* Gets the value as a long.
|
||||
*
|
||||
* @return the long value
|
||||
*/
|
||||
long asLong();
|
||||
|
||||
/**
|
||||
* Checks if a key exists.
|
||||
*
|
||||
* @param key the key to check
|
||||
* @return true if the key exists
|
||||
*/
|
||||
boolean has(String key);
|
||||
|
||||
/**
|
||||
* Checks if an index exists.
|
||||
*
|
||||
* @param index the index to check
|
||||
* @return true if the index exists
|
||||
*/
|
||||
boolean has(int index);
|
||||
|
||||
/**
|
||||
* Gets the value as an int.
|
||||
*
|
||||
* @return the int value
|
||||
*/
|
||||
int asInt();
|
||||
|
||||
/**
|
||||
* Gets the value as a boolean.
|
||||
*
|
||||
* @return the boolean value
|
||||
*/
|
||||
boolean asBoolean();
|
||||
|
||||
/**
|
||||
* Returns the internal representation (JsonElement or ConfigurationNode).
|
||||
*
|
||||
* @return the internal object
|
||||
*/
|
||||
Object toInternal();
|
||||
}
|
||||
-197
@@ -1,197 +0,0 @@
|
||||
package com.bencodez.votingplugin.proxy.cache;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import com.bencodez.votingplugin.proxy.OfflineBungeeVote;
|
||||
import com.bencodez.votingplugin.timequeue.VoteTimeQueue;
|
||||
|
||||
/**
|
||||
* Interface for vote caching operations.
|
||||
*/
|
||||
public interface IVoteCache {
|
||||
|
||||
/**
|
||||
* Adds a timed vote to the cache.
|
||||
*
|
||||
* @param num the vote number
|
||||
* @param voteTimedQueue the vote time queue
|
||||
*/
|
||||
void addTimedVote(int num, VoteTimeQueue voteTimedQueue);
|
||||
|
||||
/**
|
||||
* Adds a vote for a specific server.
|
||||
*
|
||||
* @param server the server name
|
||||
* @param num the vote number
|
||||
* @param voteData the vote data
|
||||
*/
|
||||
void addVote(String server, int num, OfflineBungeeVote voteData);
|
||||
|
||||
/**
|
||||
* Adds a vote for an online player.
|
||||
*
|
||||
* @param player the player name
|
||||
* @param num the vote number
|
||||
* @param voteData the vote data
|
||||
*/
|
||||
void addVoteOnline(String player, int num, OfflineBungeeVote voteData);
|
||||
|
||||
/**
|
||||
* Clears all cached data.
|
||||
*/
|
||||
void clearData();
|
||||
|
||||
/**
|
||||
* Gets all online votes for a player.
|
||||
*
|
||||
* @param name the player name
|
||||
* @return collection of vote identifiers
|
||||
*/
|
||||
Collection<String> getOnlineVotes(String name);
|
||||
|
||||
/**
|
||||
* Gets a specific online vote for a player.
|
||||
*
|
||||
* @param name the player name
|
||||
* @param num the vote number
|
||||
* @return the vote data node
|
||||
*/
|
||||
DataNode getOnlineVotes(String name, String num);
|
||||
|
||||
/**
|
||||
* Gets all cached players.
|
||||
*
|
||||
* @return collection of player names
|
||||
*/
|
||||
Collection<String> getPlayers();
|
||||
|
||||
/**
|
||||
* Gets all cached servers.
|
||||
*
|
||||
* @return collection of server names
|
||||
*/
|
||||
Collection<String> getServers();
|
||||
|
||||
/**
|
||||
* Gets all votes for a server.
|
||||
*
|
||||
* @param server the server name
|
||||
* @return collection of vote identifiers
|
||||
*/
|
||||
Collection<String> getServerVotes(String server);
|
||||
|
||||
/**
|
||||
* Gets a specific vote for a server.
|
||||
*
|
||||
* @param server the server name
|
||||
* @param num the vote number
|
||||
* @return the vote data node
|
||||
*/
|
||||
DataNode getServerVotes(String server, String num);
|
||||
|
||||
/**
|
||||
* Gets all timed vote cache entries.
|
||||
*
|
||||
* @return collection of cache keys
|
||||
*/
|
||||
Collection<String> getTimedVoteCache();
|
||||
|
||||
/**
|
||||
* Gets a specific timed vote cache entry.
|
||||
*
|
||||
* @param key the cache key
|
||||
* @return the vote data node
|
||||
*/
|
||||
DataNode getTimedVoteCache(String key);
|
||||
|
||||
/**
|
||||
* Gets the vote party cache for a server.
|
||||
*
|
||||
* @param server the server name
|
||||
* @return the cached vote count
|
||||
*/
|
||||
int getVotePartyCache(String server);
|
||||
|
||||
/**
|
||||
* Gets the current vote party votes.
|
||||
*
|
||||
* @return the current vote count
|
||||
*/
|
||||
int getVotePartyCurrentVotes();
|
||||
|
||||
/**
|
||||
* Gets the vote party increase votes required.
|
||||
*
|
||||
* @return the increase amount
|
||||
*/
|
||||
int getVotePartyInreaseVotesRequired();
|
||||
|
||||
/**
|
||||
* Sets the vote party cache for a server.
|
||||
*
|
||||
* @param server the server name
|
||||
* @param amount the vote amount
|
||||
*/
|
||||
void setVotePartyCache(String server, int amount);
|
||||
|
||||
/**
|
||||
* Sets the current vote party votes.
|
||||
*
|
||||
* @param amount the vote amount
|
||||
*/
|
||||
void setVotePartyCurrentVotes(int amount);
|
||||
|
||||
/**
|
||||
* Sets the vote party increase votes required.
|
||||
*
|
||||
* @param amount the increase amount
|
||||
*/
|
||||
void setVotePartyInreaseVotesRequired(int amount);
|
||||
|
||||
/**
|
||||
* Saves the cache data.
|
||||
*/
|
||||
void save();
|
||||
|
||||
/**
|
||||
* Reloads the cache data.
|
||||
*/
|
||||
void reload();
|
||||
|
||||
/**
|
||||
* Removes all online votes for a player.
|
||||
*
|
||||
* @param player the player name
|
||||
*/
|
||||
void removeOnlineVotes(String player);
|
||||
|
||||
/**
|
||||
* Removes all votes for a server.
|
||||
*
|
||||
* @param server the server name
|
||||
*/
|
||||
void removeServerVotes(String server);
|
||||
|
||||
/**
|
||||
* Removes a specific vote for a server.
|
||||
*
|
||||
* @param server the server name
|
||||
* @param uuid the vote UUID
|
||||
*/
|
||||
void removeServerVote(String server, String uuid);
|
||||
|
||||
/**
|
||||
* Removes a vote from a server.
|
||||
*
|
||||
* @param server the server name
|
||||
* @param vote the vote to remove
|
||||
*/
|
||||
void removeVote(String server, OfflineBungeeVote vote);
|
||||
|
||||
/**
|
||||
* Removes an online vote.
|
||||
*
|
||||
* @param vote the vote to remove
|
||||
*/
|
||||
void removeOnlineVote(OfflineBungeeVote vote);
|
||||
}
|
||||
Vendored
-458
@@ -1,458 +0,0 @@
|
||||
package com.bencodez.votingplugin.proxy.cache;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement; // NEW
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set; // NEW
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap; // NEW
|
||||
|
||||
import com.bencodez.simpleapi.sql.mysql.AbstractSqlTable;
|
||||
import com.bencodez.simpleapi.sql.mysql.DbType;
|
||||
import com.bencodez.simpleapi.sql.mysql.MySQL;
|
||||
import com.bencodez.simpleapi.sql.mysql.config.MysqlConfig;
|
||||
import com.bencodez.simpleapi.sql.mysql.queries.Query;
|
||||
import com.bencodez.votingplugin.proxy.OfflineBungeeVote;
|
||||
|
||||
/**
|
||||
* Table for caching online votes in the proxy.
|
||||
*/
|
||||
public abstract class ProxyOnlineVoteCacheTable extends AbstractSqlTable {
|
||||
|
||||
// Prevent repeated startup DDL across multiple instances/subclasses
|
||||
private static final Set<String> MIGRATED_VOTEID = ConcurrentHashMap.newKeySet();
|
||||
private static final Set<String> ENSURED_INDEXES = ConcurrentHashMap.newKeySet();
|
||||
|
||||
@Override
|
||||
public String getPrimaryKeyColumn() {
|
||||
return "id";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String buildCreateTableSql(DbType dbType) {
|
||||
if (dbType == DbType.POSTGRESQL) {
|
||||
return "CREATE TABLE IF NOT EXISTS " + qi(getTableName()) + " (" + qi("id") + " BIGSERIAL PRIMARY KEY, "
|
||||
+ qi("uuid") + " " + bestUuidType() + ", " + qi("voteid") + " VARCHAR(36), " + qi("playerName")
|
||||
+ " VARCHAR(100), " + qi("service") + " VARCHAR(100), " + qi("time") + " BIGINT, " + qi("realVote")
|
||||
+ " BOOLEAN, " + qi("text") + " TEXT" + ");";
|
||||
}
|
||||
|
||||
return "CREATE TABLE IF NOT EXISTS " + qi(getTableName()) + " (" + qi("id") + " INT AUTO_INCREMENT PRIMARY KEY,"
|
||||
+ qi("uuid") + " VARCHAR(37)," + qi("voteid") + " VARCHAR(36)," + qi("playerName") + " VARCHAR(100),"
|
||||
+ qi("service") + " VARCHAR(100)," + qi("time") + " BIGINT," + qi("realVote") + " TINYINT(1),"
|
||||
+ qi("text") + " TEXT," + "INDEX idx_uuid (" + qi("uuid") + ")," + "INDEX idx_time (" + qi("time") + ")"
|
||||
+ ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all votes for a specific UUID.
|
||||
* @param uuid the player UUID
|
||||
*/
|
||||
public void removeVotesByUuid(String uuid) {
|
||||
String sql = "DELETE FROM " + qi(getTableName()) + " WHERE " + qi("uuid") + " = ?;";
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
|
||||
if (getDbType() == DbType.POSTGRESQL) {
|
||||
ps.setObject(1, UUID.fromString(uuid));
|
||||
} else {
|
||||
ps.setString(1, uuid);
|
||||
}
|
||||
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException | IllegalArgumentException e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract void logSevere(String msg);
|
||||
|
||||
@Override
|
||||
public abstract void logInfo(String msg);
|
||||
|
||||
@Override
|
||||
public abstract void debug(Throwable t);
|
||||
|
||||
/**
|
||||
* Constructor using an existing MySQL connection.
|
||||
* @param existingMysql the existing MySQL instance
|
||||
* @param tablePrefix the table prefix
|
||||
* @param debug whether debug mode is enabled
|
||||
*/
|
||||
public ProxyOnlineVoteCacheTable(MySQL existingMysql, String tablePrefix, boolean debug) {
|
||||
super((tablePrefix != null ? tablePrefix : "") + "votingplugin_onlinevotecache", existingMysql, debug);
|
||||
|
||||
// best-effort migrations (no nested connections)
|
||||
alterColumnType("uuid", bestUuidType());
|
||||
addVoteIdColumnIfMissingOnce();
|
||||
ensureIndexesOnce();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor using a MySQL configuration.
|
||||
* @param config the MySQL configuration
|
||||
* @param debug whether debug mode is enabled
|
||||
*/
|
||||
public ProxyOnlineVoteCacheTable(MysqlConfig config, boolean debug) {
|
||||
super("votingplugin_onlinevotecache", config, debug);
|
||||
|
||||
alterColumnType("uuid", bestUuidType());
|
||||
addVoteIdColumnIfMissingOnce();
|
||||
ensureIndexesOnce();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the text field of a vote.
|
||||
* @param vote the vote to update
|
||||
* @param newText the new text
|
||||
*/
|
||||
public void updateVoteText(OfflineBungeeVote vote, String newText) {
|
||||
if (vote == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String sql = "UPDATE " + qi(getTableName()) + " SET " + qi("text") + " = ?" + " WHERE " + qi("uuid")
|
||||
+ " = ? AND " + qi("service") + " = ? AND " + qi("time") + " = ?;";
|
||||
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
|
||||
ps.setString(1, newText);
|
||||
|
||||
if (getDbType() == DbType.POSTGRESQL) {
|
||||
ps.setObject(2, UUID.fromString(vote.getUuid()));
|
||||
} else {
|
||||
ps.setString(2, vote.getUuid());
|
||||
}
|
||||
|
||||
ps.setString(3, vote.getService());
|
||||
ps.setLong(4, vote.getTime());
|
||||
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException | IllegalArgumentException e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureIndexesOnce() {
|
||||
if (getDbType() != DbType.POSTGRESQL) {
|
||||
return;
|
||||
}
|
||||
|
||||
String key = getDbType() + ":" + getTableName() + ":indexes";
|
||||
if (!ENSURED_INDEXES.add(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use ONE connection for all index DDL to avoid pool starvation (pool=1 safe)
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection(); Statement st = conn.createStatement()) {
|
||||
|
||||
st.executeUpdate("CREATE INDEX IF NOT EXISTS idx_uuid ON " + qi(getTableName()) + " (" + qi("uuid") + ");");
|
||||
st.executeUpdate("CREATE INDEX IF NOT EXISTS idx_time ON " + qi(getTableName()) + " (" + qi("time") + ");");
|
||||
|
||||
} catch (SQLException e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void addVoteIdColumnIfMissingOnce() {
|
||||
String key = getDbType() + ":" + getTableName() + ":voteid";
|
||||
if (!MIGRATED_VOTEID.add(key)) {
|
||||
return;
|
||||
}
|
||||
addVoteIdColumnIfMissing();
|
||||
}
|
||||
|
||||
private void addVoteIdColumnIfMissing() {
|
||||
final boolean pg = getDbType() == DbType.POSTGRESQL;
|
||||
|
||||
final String schemaFilter = pg ? "table_schema = current_schema()" : "TABLE_SCHEMA = DATABASE()";
|
||||
|
||||
// Lowercase compare avoids casing issues on some setups
|
||||
final String checkSql = "SELECT 1 FROM information_schema.columns WHERE " + schemaFilter
|
||||
+ " AND LOWER(table_name) = LOWER(?) AND LOWER(column_name) = LOWER(?) LIMIT 1;";
|
||||
|
||||
// IMPORTANT: do check + alter on SAME connection to avoid deadlock when
|
||||
// poolSize=1
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(checkSql)) {
|
||||
|
||||
ps.setString(1, getTableName());
|
||||
ps.setString(2, "voteid");
|
||||
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
return; // exists
|
||||
}
|
||||
}
|
||||
|
||||
String alter = "ALTER TABLE " + qi(getTableName()) + " ADD COLUMN " + qi("voteid") + " VARCHAR(36);";
|
||||
try (Statement st = conn.createStatement()) {
|
||||
st.executeUpdate(alter);
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
logSevere("Failed to add voteid column to " + getTableName() + ": " + e.getMessage());
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
// --- INSERT ---
|
||||
|
||||
/**
|
||||
* Inserts a new vote into the cache.
|
||||
* @param voteId the vote ID
|
||||
* @param uuid the player UUID
|
||||
* @param playerName the player name
|
||||
* @param service the voting service
|
||||
* @param time the vote time
|
||||
* @param real whether this is a real vote
|
||||
* @param text the vote text/payload
|
||||
*/
|
||||
public void insertVote(UUID voteId, String uuid, String playerName, String service, long time, boolean real,
|
||||
String text) {
|
||||
|
||||
String sql = "INSERT INTO " + qi(getTableName()) + " (" + qi("uuid") + ", " + qi("voteid") + ", "
|
||||
+ qi("playerName") + ", " + qi("service") + ", " + qi("time") + ", " + qi("realVote") + ", "
|
||||
+ qi("text") + ") VALUES (?, ?, ?, ?, ?, ?, ?);";
|
||||
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
|
||||
if (getDbType() == DbType.POSTGRESQL) {
|
||||
ps.setObject(1, UUID.fromString(uuid));
|
||||
} else {
|
||||
ps.setString(1, uuid);
|
||||
}
|
||||
|
||||
ps.setString(2, voteId != null ? voteId.toString() : null);
|
||||
ps.setString(3, playerName);
|
||||
ps.setString(4, service);
|
||||
ps.setLong(5, time);
|
||||
|
||||
if (getDbType() == DbType.POSTGRESQL) {
|
||||
ps.setBoolean(6, real);
|
||||
} else {
|
||||
ps.setInt(6, real ? 1 : 0);
|
||||
}
|
||||
|
||||
ps.setString(7, text);
|
||||
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException | IllegalArgumentException e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
// --- GET ---
|
||||
|
||||
/**
|
||||
* Gets all votes from the table.
|
||||
* @return list of all vote rows
|
||||
*/
|
||||
public List<VoteRow> getAllVotes() {
|
||||
return selectVotes("SELECT * FROM " + qi(getTableName()) + ";", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all votes for a specific UUID.
|
||||
* @param uuid the player UUID
|
||||
* @return list of vote rows
|
||||
*/
|
||||
public List<VoteRow> getVotesByUUID(String uuid) {
|
||||
String sql = "SELECT * FROM " + qi(getTableName()) + " WHERE " + qi("uuid") + " = ?;";
|
||||
Object param = (getDbType() == DbType.POSTGRESQL) ? UUID.fromString(uuid) : uuid;
|
||||
return selectVotes(sql, new Object[] { param });
|
||||
}
|
||||
|
||||
// --- DELETE ---
|
||||
|
||||
/**
|
||||
* Removes a vote by its ID.
|
||||
* @param id the vote ID
|
||||
*/
|
||||
public void removeVoteById(int id) {
|
||||
String sql = "DELETE FROM " + qi(getTableName()) + " WHERE " + qi("id") + " = ?;";
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setInt(1, id);
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a specific vote.
|
||||
* @param vote the vote to remove
|
||||
*/
|
||||
public void removeVote(OfflineBungeeVote vote) {
|
||||
String sql = "DELETE FROM " + qi(getTableName()) + " WHERE " + qi("uuid") + " = ? AND " + qi("service")
|
||||
+ " = ? AND " + qi("time") + " = ?;";
|
||||
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
|
||||
if (getDbType() == DbType.POSTGRESQL) {
|
||||
ps.setObject(1, UUID.fromString(vote.getUuid()));
|
||||
} else {
|
||||
ps.setString(1, vote.getUuid());
|
||||
}
|
||||
|
||||
ps.setString(2, vote.getService());
|
||||
ps.setLong(3, vote.getTime());
|
||||
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException | IllegalArgumentException e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all votes from the table.
|
||||
*/
|
||||
public void clearTable() {
|
||||
try {
|
||||
if (getDbType() == DbType.POSTGRESQL) {
|
||||
new Query(mysql, "TRUNCATE TABLE " + qi(getTableName()) + " RESTART IDENTITY;").executeUpdate();
|
||||
} else {
|
||||
new Query(mysql, "TRUNCATE TABLE " + qi(getTableName()) + ";").executeUpdate();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Internals ---
|
||||
|
||||
private List<VoteRow> selectVotes(String sql, Object[] params) {
|
||||
List<VoteRow> list = new ArrayList<>();
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
|
||||
if (params != null) {
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
ps.setObject(i + 1, params[i]);
|
||||
}
|
||||
}
|
||||
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
list.add(new VoteRow(rs.getInt("id"), rs.getString("voteid"), rs.getString("uuid"),
|
||||
rs.getString("playerName"), rs.getString("service"), rs.getLong("time"),
|
||||
(getDbType() == DbType.POSTGRESQL ? rs.getBoolean("realVote") : rs.getInt("realVote") == 1),
|
||||
rs.getString("text")));
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
debug(e);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a row in the online vote cache table.
|
||||
*/
|
||||
public static class VoteRow {
|
||||
private final int id;
|
||||
private final String voteId;
|
||||
private final String uuid;
|
||||
private final String playerName;
|
||||
private final String service;
|
||||
private final long time;
|
||||
private final boolean realVote;
|
||||
private final String text;
|
||||
|
||||
/**
|
||||
* Constructor for VoteRow.
|
||||
* @param id the row ID
|
||||
* @param voteId the vote ID
|
||||
* @param uuid the player UUID
|
||||
* @param playerName the player name
|
||||
* @param service the voting service
|
||||
* @param time the vote time
|
||||
* @param realVote whether this is a real vote
|
||||
* @param text the vote text
|
||||
*/
|
||||
public VoteRow(int id, String voteId, String uuid, String playerName, String service, long time,
|
||||
boolean realVote, String text) {
|
||||
this.id = id;
|
||||
this.voteId = voteId;
|
||||
this.uuid = uuid;
|
||||
this.playerName = playerName;
|
||||
this.service = service;
|
||||
this.time = time;
|
||||
this.realVote = realVote;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the row ID.
|
||||
* @return the ID
|
||||
*/
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the vote ID.
|
||||
* @return the vote ID
|
||||
*/
|
||||
public String getVoteId() {
|
||||
return voteId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player UUID.
|
||||
* @return the UUID
|
||||
*/
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player name.
|
||||
* @return the player name
|
||||
*/
|
||||
public String getPlayerName() {
|
||||
return playerName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the voting service.
|
||||
* @return the service name
|
||||
*/
|
||||
public String getService() {
|
||||
return service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the vote time.
|
||||
* @return the time in milliseconds
|
||||
*/
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this is a real vote.
|
||||
* @return true if real vote
|
||||
*/
|
||||
public boolean isRealVote() {
|
||||
return realVote;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the vote text.
|
||||
* @return the text/payload
|
||||
*/
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
-257
@@ -1,257 +0,0 @@
|
||||
package com.bencodez.votingplugin.proxy.cache;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.bencodez.simpleapi.sql.mysql.AbstractSqlTable;
|
||||
import com.bencodez.simpleapi.sql.mysql.DbType;
|
||||
import com.bencodez.simpleapi.sql.mysql.MySQL;
|
||||
import com.bencodez.simpleapi.sql.mysql.config.MysqlConfig;
|
||||
import com.bencodez.simpleapi.sql.mysql.queries.Query;
|
||||
|
||||
/**
|
||||
* Table for caching timed votes in the proxy.
|
||||
*/
|
||||
public abstract class ProxyTimedVoteCacheTable extends AbstractSqlTable {
|
||||
|
||||
@Override
|
||||
public String getPrimaryKeyColumn() {
|
||||
return "id";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String buildCreateTableSql(DbType dbType) {
|
||||
if (dbType == DbType.POSTGRESQL) {
|
||||
return "CREATE TABLE IF NOT EXISTS " + qi(getTableName()) + " ("
|
||||
+ qi("id") + " BIGSERIAL PRIMARY KEY, "
|
||||
+ qi("playerName") + " VARCHAR(100), "
|
||||
+ qi("service") + " VARCHAR(100), "
|
||||
+ qi("time") + " BIGINT"
|
||||
+ ");";
|
||||
}
|
||||
|
||||
return "CREATE TABLE IF NOT EXISTS " + qi(getTableName()) + " ("
|
||||
+ qi("id") + " INT AUTO_INCREMENT PRIMARY KEY,"
|
||||
+ qi("playerName") + " VARCHAR(100),"
|
||||
+ qi("service") + " VARCHAR(100),"
|
||||
+ qi("time") + " BIGINT,"
|
||||
+ "INDEX idx_time (" + qi("time") + ")"
|
||||
+ ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract void logSevere(String msg);
|
||||
|
||||
@Override
|
||||
public abstract void logInfo(String msg);
|
||||
|
||||
@Override
|
||||
public abstract void debug(Throwable t);
|
||||
|
||||
/**
|
||||
* Constructor using an existing MySQL connection.
|
||||
* @param existingMysql the existing MySQL instance
|
||||
* @param tablePrefix the table prefix
|
||||
* @param debug whether debug mode is enabled
|
||||
*/
|
||||
public ProxyTimedVoteCacheTable(MySQL existingMysql, String tablePrefix, boolean debug) {
|
||||
super((tablePrefix != null ? tablePrefix : "") + "votingplugin_timedvotecache",
|
||||
existingMysql,
|
||||
debug);
|
||||
ensureIndexes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor using a MySQL configuration.
|
||||
* @param config the MySQL configuration
|
||||
* @param debug whether debug mode is enabled
|
||||
*/
|
||||
public ProxyTimedVoteCacheTable(MysqlConfig config, boolean debug) {
|
||||
super("votingplugin_timedvotecache", config, debug);
|
||||
ensureIndexes();
|
||||
}
|
||||
|
||||
private void ensureIndexes() {
|
||||
if (getDbType() == DbType.POSTGRESQL) {
|
||||
try {
|
||||
new Query(mysql, "CREATE INDEX IF NOT EXISTS idx_time ON " + qi(getTableName()) + " (" + qi("time")
|
||||
+ ");").executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- INSERT ---
|
||||
/**
|
||||
* Inserts a timed vote.
|
||||
* @param playerName the player name
|
||||
* @param service the voting service
|
||||
* @param time the vote time
|
||||
*/
|
||||
public void insertTimedVote(String playerName, String service, long time) {
|
||||
String sql = "INSERT INTO " + qi(getTableName()) + " (" + qi("playerName") + ", " + qi("service") + ", "
|
||||
+ qi("time") + ") VALUES (?, ?, ?);";
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setString(1, playerName);
|
||||
ps.setString(2, service);
|
||||
ps.setLong(3, time);
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
// --- GET ---
|
||||
/**
|
||||
* Gets all timed votes.
|
||||
* @return list of all timed vote rows
|
||||
*/
|
||||
public List<TimedVoteRow> getAllVotes() {
|
||||
return selectVotes("SELECT * FROM " + qi(getTableName()) + ";", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets expired votes before a given time.
|
||||
* @param now the current time in milliseconds
|
||||
* @return list of expired vote rows
|
||||
*/
|
||||
public List<TimedVoteRow> getExpiredVotes(long now) {
|
||||
return selectVotes("SELECT * FROM " + qi(getTableName()) + " WHERE " + qi("time") + " <= ?;",
|
||||
new Object[] { now });
|
||||
}
|
||||
|
||||
// --- DELETE ---
|
||||
/**
|
||||
* Removes a vote by its ID.
|
||||
* @param id the vote ID
|
||||
*/
|
||||
public void removeVoteById(int id) {
|
||||
String sql = "DELETE FROM " + qi(getTableName()) + " WHERE " + qi("id") + " = ?;";
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setInt(1, id);
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes expired votes before a given time.
|
||||
* @param now the current time in milliseconds
|
||||
*/
|
||||
public void removeExpiredVotes(long now) {
|
||||
String sql = "DELETE FROM " + qi(getTableName()) + " WHERE " + qi("time") + " <= ?;";
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setLong(1, now);
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all votes from the table.
|
||||
*/
|
||||
public void clearTable() {
|
||||
try {
|
||||
if (getDbType() == DbType.POSTGRESQL) {
|
||||
new Query(mysql, "TRUNCATE TABLE " + qi(getTableName()) + " RESTART IDENTITY;").executeUpdate();
|
||||
} else {
|
||||
new Query(mysql, "TRUNCATE TABLE " + qi(getTableName()) + ";").executeUpdate();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
private List<TimedVoteRow> selectVotes(String sql, Object[] params) {
|
||||
List<TimedVoteRow> list = new ArrayList<>();
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
|
||||
if (params != null) {
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
ps.setObject(i + 1, params[i]);
|
||||
}
|
||||
}
|
||||
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
list.add(new TimedVoteRow(
|
||||
rs.getInt("id"),
|
||||
rs.getString("playerName"),
|
||||
rs.getString("service"),
|
||||
rs.getLong("time")
|
||||
));
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
debug(e);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a row in the timed vote cache table.
|
||||
*/
|
||||
public static class TimedVoteRow {
|
||||
private final int id;
|
||||
private final String playerName;
|
||||
private final String service;
|
||||
private final long time;
|
||||
|
||||
/**
|
||||
* Constructor for TimedVoteRow.
|
||||
* @param id the row ID
|
||||
* @param playerName the player name
|
||||
* @param service the voting service
|
||||
* @param time the vote time
|
||||
*/
|
||||
public TimedVoteRow(int id, String playerName, String service, long time) {
|
||||
this.id = id;
|
||||
this.playerName = playerName;
|
||||
this.service = service;
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the row ID.
|
||||
* @return the ID
|
||||
*/
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player name.
|
||||
* @return the player name
|
||||
*/
|
||||
public String getPlayerName() {
|
||||
return playerName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the voting service.
|
||||
* @return the service name
|
||||
*/
|
||||
public String getService() {
|
||||
return service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the vote time.
|
||||
* @return the time in milliseconds
|
||||
*/
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
}
|
||||
}
|
||||
-581
@@ -1,581 +0,0 @@
|
||||
package com.bencodez.votingplugin.proxy.cache;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.bencodez.simpleapi.sql.mysql.AbstractSqlTable;
|
||||
import com.bencodez.simpleapi.sql.mysql.DbType;
|
||||
import com.bencodez.simpleapi.sql.mysql.MySQL;
|
||||
import com.bencodez.simpleapi.sql.mysql.config.MysqlConfig;
|
||||
import com.bencodez.simpleapi.sql.mysql.queries.Query;
|
||||
import com.bencodez.votingplugin.proxy.OfflineBungeeVote;
|
||||
|
||||
/**
|
||||
* Proxy vote cache (per-server cached votes).
|
||||
*
|
||||
* Columns: - id (auto increment primary key) - uuid (player uuid) - voteid
|
||||
* (vote uuid, nullable) - playerName - service - time (millis) - realVote
|
||||
* (bool) - text (payload) - server
|
||||
*
|
||||
* Indexes: - server - uuid - time
|
||||
*/
|
||||
public abstract class ProxyVoteCacheTable extends AbstractSqlTable {
|
||||
|
||||
// Ensure we don't run the same migration repeatedly during startup (or for each
|
||||
// subclass).
|
||||
private static final Set<String> MIGRATED_VOTEID = ConcurrentHashMap.newKeySet();
|
||||
private static final Set<String> ENSURED_INDEXES = ConcurrentHashMap.newKeySet();
|
||||
|
||||
// ---- Required hooks ----
|
||||
|
||||
@Override
|
||||
public String getPrimaryKeyColumn() {
|
||||
// This table's actual PK is numeric id; AbstractSqlTable caches keys as
|
||||
// Strings.
|
||||
return "id";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String buildCreateTableSql(DbType dbType) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (dbType == DbType.POSTGRESQL) {
|
||||
sb.append("CREATE TABLE IF NOT EXISTS ").append(qi(getTableName())).append(" (").append(qi("id"))
|
||||
.append(" BIGSERIAL PRIMARY KEY, ").append(qi("uuid")).append(" ").append(bestUuidType())
|
||||
.append(", ").append(qi("voteid")).append(" VARCHAR(36), ").append(qi("playerName"))
|
||||
.append(" VARCHAR(100), ").append(qi("service")).append(" VARCHAR(100), ").append(qi("time"))
|
||||
.append(" BIGINT, ").append(qi("realVote")).append(" BOOLEAN, ").append(qi("text"))
|
||||
.append(" TEXT, ").append(qi("server")).append(" VARCHAR(100)").append(");");
|
||||
} else {
|
||||
sb.append("CREATE TABLE IF NOT EXISTS ").append(qi(getTableName())).append(" (").append(qi("id"))
|
||||
.append(" INT AUTO_INCREMENT PRIMARY KEY,").append(qi("uuid")).append(" VARCHAR(37),")
|
||||
.append(qi("voteid")).append(" VARCHAR(36),").append(qi("playerName")).append(" VARCHAR(100),")
|
||||
.append(qi("service")).append(" VARCHAR(100),").append(qi("time")).append(" BIGINT,")
|
||||
.append(qi("realVote")).append(" TINYINT(1),").append(qi("text")).append(" TEXT,")
|
||||
.append(qi("server")).append(" VARCHAR(100),").append("INDEX idx_server (").append(qi("server"))
|
||||
.append("),").append("INDEX idx_uuid (").append(qi("uuid")).append("),").append("INDEX idx_time (")
|
||||
.append(qi("time")).append(")").append(") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the text field of a vote.
|
||||
* @param vote the vote to update
|
||||
* @param server the server name
|
||||
* @param newText the new text
|
||||
*/
|
||||
public void updateVoteText(OfflineBungeeVote vote, String server, String newText) {
|
||||
if (vote == null || server == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String sql = "UPDATE " + qi(getTableName()) + " SET " + qi("text") + " = ?" + " WHERE " + qi("uuid")
|
||||
+ " = ? AND " + qi("service") + " = ? AND " + qi("time") + " = ? AND " + qi("server") + " = ?;";
|
||||
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
|
||||
ps.setString(1, newText);
|
||||
|
||||
if (getDbType() == DbType.POSTGRESQL) {
|
||||
ps.setObject(2, UUID.fromString(vote.getUuid()));
|
||||
} else {
|
||||
ps.setString(2, vote.getUuid());
|
||||
}
|
||||
|
||||
ps.setString(3, vote.getService());
|
||||
ps.setLong(4, vote.getTime());
|
||||
ps.setString(5, server);
|
||||
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException | IllegalArgumentException e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract void logSevere(String msg);
|
||||
|
||||
@Override
|
||||
public abstract void logInfo(String msg);
|
||||
|
||||
@Override
|
||||
public abstract void debug(Throwable t);
|
||||
|
||||
// ---- Constructors ----
|
||||
|
||||
/**
|
||||
* Constructor using an existing MySQL connection.
|
||||
* @param existingMysql the existing MySQL instance
|
||||
* @param tablePrefix the table prefix
|
||||
* @param debug whether debug mode is enabled
|
||||
*/
|
||||
public ProxyVoteCacheTable(MySQL existingMysql, String tablePrefix, boolean debug) {
|
||||
super((tablePrefix != null ? tablePrefix : "") + "votingplugin_votecache", existingMysql, debug);
|
||||
|
||||
// best-effort migrations (safe for pool size 1; no nested connections)
|
||||
alterColumnType("uuid", bestUuidType());
|
||||
addVoteIdColumnIfMissingOnce();
|
||||
ensureIndexesOnce();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor using a MySQL configuration.
|
||||
* @param config the MySQL configuration
|
||||
* @param debug whether debug mode is enabled
|
||||
*/
|
||||
public ProxyVoteCacheTable(MysqlConfig config, boolean debug) {
|
||||
super("votingplugin_votecache", config, debug);
|
||||
|
||||
alterColumnType("uuid", bestUuidType());
|
||||
addVoteIdColumnIfMissingOnce();
|
||||
ensureIndexesOnce();
|
||||
}
|
||||
|
||||
// ---- Migrations / indexes ----
|
||||
|
||||
private void ensureIndexesOnce() {
|
||||
if (getDbType() != DbType.POSTGRESQL) {
|
||||
return; // MySQL indexes are already in CREATE TABLE
|
||||
}
|
||||
|
||||
final String key = getDbType() + ":" + getTableName();
|
||||
if (!ENSURED_INDEXES.add(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use ONE connection for all index statements (no pool churn).
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection(); Statement st = conn.createStatement()) {
|
||||
|
||||
st.executeUpdate(
|
||||
"CREATE INDEX IF NOT EXISTS idx_server ON " + qi(getTableName()) + " (" + qi("server") + ");");
|
||||
st.executeUpdate("CREATE INDEX IF NOT EXISTS idx_uuid ON " + qi(getTableName()) + " (" + qi("uuid") + ");");
|
||||
st.executeUpdate("CREATE INDEX IF NOT EXISTS idx_time ON " + qi(getTableName()) + " (" + qi("time") + ");");
|
||||
|
||||
} catch (SQLException e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void addVoteIdColumnIfMissingOnce() {
|
||||
final String key = getDbType() + ":" + getTableName() + ":voteid";
|
||||
if (!MIGRATED_VOTEID.add(key)) {
|
||||
return;
|
||||
}
|
||||
addVoteIdColumnIfMissing();
|
||||
}
|
||||
|
||||
private void addVoteIdColumnIfMissing() {
|
||||
// Avoid nested connections: do check + alter using the SAME connection.
|
||||
final boolean pg = getDbType() == DbType.POSTGRESQL;
|
||||
|
||||
final String schemaFilter = pg ? "table_schema = current_schema()" : "TABLE_SCHEMA = DATABASE()";
|
||||
|
||||
// Case-insensitive match helps with MySQL lower_case_table_names / quoting
|
||||
// differences.
|
||||
final String checkSql = "SELECT 1 FROM information_schema.columns WHERE " + schemaFilter
|
||||
+ " AND LOWER(table_name) = LOWER(?) AND LOWER(column_name) = LOWER(?) LIMIT 1;";
|
||||
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(checkSql)) {
|
||||
|
||||
ps.setString(1, getTableName());
|
||||
ps.setString(2, "voteid");
|
||||
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
return; // exists
|
||||
}
|
||||
}
|
||||
|
||||
// Execute ALTER using same connection to avoid deadlock when pool size is
|
||||
// small.
|
||||
final String alter = "ALTER TABLE " + qi(getTableName()) + " ADD COLUMN " + qi("voteid") + " VARCHAR(36);";
|
||||
|
||||
try (Statement st = conn.createStatement()) {
|
||||
st.executeUpdate(alter);
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
// don't hard fail startup
|
||||
logSevere("Failed to add voteid column to " + getTableName() + ": " + e.getMessage());
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- INSERT ----
|
||||
|
||||
/**
|
||||
* Inserts a new vote into the cache.
|
||||
* @param voteId the vote ID
|
||||
* @param uuid the player UUID
|
||||
* @param playerName the player name
|
||||
* @param service the voting service
|
||||
* @param time the vote time
|
||||
* @param real whether this is a real vote
|
||||
* @param text the vote text/payload
|
||||
* @param server the server name
|
||||
*/
|
||||
public void insertVote(UUID voteId, String uuid, String playerName, String service, long time, boolean real,
|
||||
String text, String server) {
|
||||
|
||||
String sql = "INSERT INTO " + qi(getTableName()) + " (" + qi("uuid") + ", " + qi("voteid") + ", "
|
||||
+ qi("playerName") + ", " + qi("service") + ", " + qi("time") + ", " + qi("realVote") + ", "
|
||||
+ qi("text") + ", " + qi("server") + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?);";
|
||||
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
|
||||
if (getDbType() == DbType.POSTGRESQL) {
|
||||
ps.setObject(1, UUID.fromString(uuid));
|
||||
} else {
|
||||
ps.setString(1, uuid);
|
||||
}
|
||||
|
||||
ps.setString(2, voteId != null ? voteId.toString() : null);
|
||||
ps.setString(3, playerName);
|
||||
ps.setString(4, service);
|
||||
ps.setLong(5, time);
|
||||
|
||||
if (getDbType() == DbType.POSTGRESQL) {
|
||||
ps.setBoolean(6, real);
|
||||
} else {
|
||||
ps.setInt(6, real ? 1 : 0);
|
||||
}
|
||||
|
||||
ps.setString(7, text);
|
||||
ps.setString(8, server);
|
||||
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException | IllegalArgumentException e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- GET ----
|
||||
|
||||
/**
|
||||
* Gets all votes from the table.
|
||||
* @return list of all vote rows
|
||||
*/
|
||||
public List<VoteRow> getAllVotes() {
|
||||
return selectVotes("SELECT * FROM " + qi(getTableName()) + ";", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all votes for a specific server.
|
||||
* @param server the server name
|
||||
* @return list of vote rows
|
||||
*/
|
||||
public List<VoteRow> getVotesForServer(String server) {
|
||||
return selectVotes("SELECT * FROM " + qi(getTableName()) + " WHERE " + qi("server") + " = ?;",
|
||||
new Object[] { server });
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a vote by its ID.
|
||||
* @param id the vote ID
|
||||
* @return the vote row or null if not found
|
||||
*/
|
||||
public VoteRow getVoteById(int id) {
|
||||
List<VoteRow> list = selectVotes("SELECT * FROM " + qi(getTableName()) + " WHERE " + qi("id") + " = ?;",
|
||||
new Object[] { id });
|
||||
return list.isEmpty() ? null : list.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all votes for a specific UUID.
|
||||
* @param uuid the player UUID
|
||||
* @return list of vote rows
|
||||
*/
|
||||
public List<VoteRow> getVotesByUUID(String uuid) {
|
||||
String sql = "SELECT * FROM " + qi(getTableName()) + " WHERE " + qi("uuid") + " = ?;";
|
||||
Object[] params = (getDbType() == DbType.POSTGRESQL) ? new Object[] { UUID.fromString(uuid) }
|
||||
: new Object[] { uuid };
|
||||
return selectVotes(sql, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all votes for a specific player name.
|
||||
* @param playerName the player name
|
||||
* @return list of vote rows
|
||||
*/
|
||||
public List<VoteRow> getVotesByPlayerName(String playerName) {
|
||||
return selectVotes("SELECT * FROM " + qi(getTableName()) + " WHERE " + qi("playerName") + " = ?;",
|
||||
new Object[] { playerName });
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all distinct servers that have votes.
|
||||
* @return set of server names
|
||||
*/
|
||||
public Set<String> getServers() {
|
||||
Set<String> servers = new HashSet<>();
|
||||
String sql = "SELECT DISTINCT " + qi("server") + " FROM " + qi(getTableName()) + " WHERE " + qi("server")
|
||||
+ " IS NOT NULL;";
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sql);
|
||||
ResultSet rs = ps.executeQuery()) {
|
||||
|
||||
while (rs.next()) {
|
||||
String s = rs.getString(1);
|
||||
if (s != null) {
|
||||
servers.add(s);
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
debug(e);
|
||||
}
|
||||
return servers;
|
||||
}
|
||||
|
||||
// ---- DELETE ----
|
||||
|
||||
/**
|
||||
* Removes a vote by its ID.
|
||||
* @param id the vote ID
|
||||
*/
|
||||
public void removeVoteById(int id) {
|
||||
String sql = "DELETE FROM " + qi(getTableName()) + " WHERE " + qi("id") + " = ?;";
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setInt(1, id);
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all votes for a specific server and UUID.
|
||||
* @param server the server name
|
||||
* @param uuid the player UUID
|
||||
*/
|
||||
public void removeVotesByServerAndUUID(String server, String uuid) {
|
||||
String sql = "DELETE FROM " + qi(getTableName()) + " WHERE " + qi("server") + " = ? AND " + qi("uuid")
|
||||
+ " = ?;";
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
|
||||
ps.setString(1, server);
|
||||
if (getDbType() == DbType.POSTGRESQL) {
|
||||
ps.setObject(2, UUID.fromString(uuid));
|
||||
} else {
|
||||
ps.setString(2, uuid);
|
||||
}
|
||||
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException | IllegalArgumentException e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a specific vote.
|
||||
* @param vote the vote to remove
|
||||
* @param server the server name
|
||||
*/
|
||||
public void removeVote(OfflineBungeeVote vote, String server) {
|
||||
String sql = "DELETE FROM " + qi(getTableName()) + " WHERE " + qi("uuid") + " = ? AND " + qi("service")
|
||||
+ " = ? AND " + qi("time") + " = ? AND " + qi("server") + " = ?;";
|
||||
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
|
||||
if (getDbType() == DbType.POSTGRESQL) {
|
||||
ps.setObject(1, UUID.fromString(vote.getUuid()));
|
||||
} else {
|
||||
ps.setString(1, vote.getUuid());
|
||||
}
|
||||
|
||||
ps.setString(2, vote.getService());
|
||||
ps.setLong(3, vote.getTime());
|
||||
ps.setString(4, server);
|
||||
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException | IllegalArgumentException e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all votes for a specific server.
|
||||
* @param server the server name
|
||||
*/
|
||||
public void removeVotesByServer(String server) {
|
||||
String sql = "DELETE FROM " + qi(getTableName()) + " WHERE " + qi("server") + " = ?;";
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setString(1, server);
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all votes from the table.
|
||||
*/
|
||||
public void clearTable() {
|
||||
try {
|
||||
if (getDbType() == DbType.POSTGRESQL) {
|
||||
new Query(mysql, "TRUNCATE TABLE " + qi(getTableName()) + " RESTART IDENTITY;").executeUpdate();
|
||||
} else {
|
||||
new Query(mysql, "TRUNCATE TABLE " + qi(getTableName()) + ";").executeUpdate();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Internals ----
|
||||
|
||||
private List<VoteRow> selectVotes(String sql, Object[] params) {
|
||||
List<VoteRow> list = new ArrayList<>();
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
|
||||
if (params != null) {
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
ps.setObject(i + 1, params[i]);
|
||||
}
|
||||
}
|
||||
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
VoteRow v = new VoteRow(rs.getInt("id"), rs.getString("voteid"), rs.getString("uuid"),
|
||||
rs.getString("playerName"), rs.getString("service"), rs.getLong("time"),
|
||||
(getDbType() == DbType.POSTGRESQL ? rs.getBoolean("realVote") : rs.getInt("realVote") == 1),
|
||||
rs.getString("text"), rs.getString("server"));
|
||||
list.add(v);
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
debug(e);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a row in the vote cache table.
|
||||
*/
|
||||
public static class VoteRow {
|
||||
private final int id;
|
||||
private final String voteId;
|
||||
private final String uuid;
|
||||
private final String playerName;
|
||||
private final String service;
|
||||
private final long time;
|
||||
private final boolean realVote;
|
||||
private final String text;
|
||||
private final String server;
|
||||
|
||||
/**
|
||||
* Constructor for VoteRow.
|
||||
* @param id the row ID
|
||||
* @param voteId the vote ID
|
||||
* @param uuid the player UUID
|
||||
* @param playerName the player name
|
||||
* @param service the voting service
|
||||
* @param time the vote time
|
||||
* @param realVote whether this is a real vote
|
||||
* @param text the vote text
|
||||
* @param server the server name
|
||||
*/
|
||||
public VoteRow(int id, String voteId, String uuid, String playerName, String service, long time,
|
||||
boolean realVote, String text, String server) {
|
||||
this.id = id;
|
||||
this.voteId = voteId;
|
||||
this.uuid = uuid;
|
||||
this.playerName = playerName;
|
||||
this.service = service;
|
||||
this.time = time;
|
||||
this.realVote = realVote;
|
||||
this.text = text;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the row ID.
|
||||
* @return the ID
|
||||
*/
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the vote ID.
|
||||
* @return the vote ID
|
||||
*/
|
||||
public String getVoteId() {
|
||||
return voteId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player UUID.
|
||||
* @return the UUID
|
||||
*/
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player name.
|
||||
* @return the player name
|
||||
*/
|
||||
public String getPlayerName() {
|
||||
return playerName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the voting service.
|
||||
* @return the service name
|
||||
*/
|
||||
public String getService() {
|
||||
return service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the vote time.
|
||||
* @return the time in milliseconds
|
||||
*/
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this is a real vote.
|
||||
* @return true if real vote
|
||||
*/
|
||||
public boolean isRealVote() {
|
||||
return realVote;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the vote text.
|
||||
* @return the text/payload
|
||||
*/
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the server name.
|
||||
* @return the server name
|
||||
*/
|
||||
public String getServer() {
|
||||
return server;
|
||||
}
|
||||
}
|
||||
}
|
||||
-92
@@ -1,92 +0,0 @@
|
||||
package com.bencodez.votingplugin.proxy.cache.nonvoted;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Interface for storing non-voted players data.
|
||||
*/
|
||||
public interface INonVotedPlayersStorage {
|
||||
|
||||
/**
|
||||
* Insert or update a player entry.
|
||||
*
|
||||
* @param uuid the player UUID
|
||||
* @param playerName the player name
|
||||
* @param lastTime the last recorded time
|
||||
*/
|
||||
void upsertPlayer(String uuid, String playerName, long lastTime);
|
||||
|
||||
/**
|
||||
* Returns UUID for the player or empty string if not found.
|
||||
*
|
||||
* @param playerName the player name
|
||||
* @return the UUID or empty string
|
||||
*/
|
||||
String getUuidByPlayerName(String playerName);
|
||||
|
||||
/**
|
||||
* Remove a player from storage.
|
||||
*
|
||||
* @param playerName the player name
|
||||
*/
|
||||
void removeByPlayerName(String playerName);
|
||||
|
||||
/**
|
||||
* Iterate snapshot of all entries.
|
||||
*
|
||||
* @param consumer the consumer to process each entry
|
||||
*/
|
||||
void forEach(Consumer<NonVotedPlayerEntry> consumer);
|
||||
|
||||
/** Close resources if needed (MySQL); no-op for JSON. */
|
||||
void close();
|
||||
|
||||
/**
|
||||
* Entry for a non-voted player.
|
||||
*/
|
||||
final class NonVotedPlayerEntry {
|
||||
private final String playerName;
|
||||
private final String uuid;
|
||||
private final long lastTime;
|
||||
|
||||
/**
|
||||
* Constructs a new non-voted player entry.
|
||||
*
|
||||
* @param playerName the player name
|
||||
* @param uuid the UUID
|
||||
* @param lastTime the last time
|
||||
*/
|
||||
public NonVotedPlayerEntry(String playerName, String uuid, long lastTime) {
|
||||
this.playerName = playerName;
|
||||
this.uuid = uuid;
|
||||
this.lastTime = lastTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player name.
|
||||
*
|
||||
* @return the player name
|
||||
*/
|
||||
public String getPlayerName() {
|
||||
return playerName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the UUID.
|
||||
*
|
||||
* @return the UUID
|
||||
*/
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last time.
|
||||
*
|
||||
* @return the last time
|
||||
*/
|
||||
public long getLastTime() {
|
||||
return lastTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
-70
@@ -1,70 +0,0 @@
|
||||
package com.bencodez.votingplugin.proxy.cache.nonvoted;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.bencodez.simpleapi.sql.mysql.MySQL;
|
||||
import com.bencodez.simpleapi.sql.mysql.config.MysqlConfig;
|
||||
|
||||
/**
|
||||
* MySQL storage for non-voted players.
|
||||
*/
|
||||
public abstract class MySQLNonVotedPlayersStorage extends ProxyNonVotedPlayersTable
|
||||
implements INonVotedPlayersStorage {
|
||||
|
||||
private final boolean ownsConnection;
|
||||
|
||||
/**
|
||||
* Constructor with existing MySQL connection.
|
||||
* @param existingMysql existing MySQL connection
|
||||
* @param tablePrefix table prefix
|
||||
* @param debug enable debug mode
|
||||
*/
|
||||
public MySQLNonVotedPlayersStorage(MySQL existingMysql, String tablePrefix, boolean debug) {
|
||||
super(existingMysql, tablePrefix, debug);
|
||||
this.ownsConnection = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with new MySQL configuration.
|
||||
* @param config MySQL configuration
|
||||
* @param debug enable debug mode
|
||||
*/
|
||||
public MySQLNonVotedPlayersStorage(MysqlConfig config, boolean debug) {
|
||||
super(config, debug);
|
||||
this.ownsConnection = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void upsertPlayer(String uuid, String playerName, long lastTime) {
|
||||
super.upsertPlayer(uuid, playerName, lastTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUuidByPlayerName(String playerName) {
|
||||
return super.getUuidByPlayerName(playerName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeByPlayerName(String playerName) {
|
||||
super.removeByPlayerName(playerName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(Consumer<NonVotedPlayerEntry> consumer) {
|
||||
List<NonVotedPlayerRow> rows = getAllRows();
|
||||
for (NonVotedPlayerRow row : rows) {
|
||||
consumer.accept(new NonVotedPlayerEntry(
|
||||
row.getPlayerName(),
|
||||
row.getUuid(),
|
||||
row.getLastTime()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (ownsConnection) {
|
||||
getMysql().disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
-176
@@ -1,176 +0,0 @@
|
||||
package com.bencodez.votingplugin.proxy.cache.nonvoted;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.bencodez.simpleapi.sql.mysql.MySQL;
|
||||
import com.bencodez.simpleapi.sql.mysql.config.MysqlConfig;
|
||||
|
||||
/**
|
||||
* Cache for players who haven't voted yet.
|
||||
*/
|
||||
public abstract class NonVotedPlayersCache {
|
||||
|
||||
private INonVotedPlayersStorage storage;
|
||||
|
||||
// 5 days in millis (same as your current logic)
|
||||
private static final long MAX_AGE_MILLIS = TimeUnit.DAYS.toMillis(5);
|
||||
|
||||
/**
|
||||
* Constructor for NonVotedPlayersCache.
|
||||
* @param mysqlConfig MySQL configuration
|
||||
* @param useMysql whether to use MySQL
|
||||
* @param useExistingConnection whether to use existing connection
|
||||
* @param mysql existing MySQL connection
|
||||
* @param storage storage implementation
|
||||
* @param debug enable debug mode
|
||||
*/
|
||||
public NonVotedPlayersCache(MysqlConfig mysqlConfig, boolean useMysql, boolean useExistingConnection, MySQL mysql,
|
||||
INonVotedPlayersStorage storage, boolean debug) {
|
||||
if (useMysql) {
|
||||
if (useExistingConnection) {
|
||||
this.storage = new MySQLNonVotedPlayersStorage(mysql, mysqlConfig.getTablePrefix(), debug) {
|
||||
|
||||
@Override
|
||||
public void logSevere(String string) {
|
||||
logSevere1(string);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logInfo(String string) {
|
||||
logInfo1(string);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(Exception e) {
|
||||
if (debug)
|
||||
debug1(e);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
this.storage = new MySQLNonVotedPlayersStorage(mysqlConfig, debug) {
|
||||
|
||||
@Override
|
||||
public void logSevere(String string) {
|
||||
logSevere1(string);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logInfo(String string) {
|
||||
logInfo1(string);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(Exception e) {
|
||||
if (debug)
|
||||
debug1(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
} else {
|
||||
this.storage = storage;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user exists.
|
||||
* @param uuid the player UUID
|
||||
* @return true if user exists
|
||||
*/
|
||||
public abstract boolean userExists(String uuid);
|
||||
|
||||
/**
|
||||
* Add a player directly by uuid + name, only if they don't already have votes.
|
||||
* @param uuid the player UUID
|
||||
* @param playerName the player name
|
||||
*/
|
||||
public void addPlayer(String uuid, String playerName) {
|
||||
if (uuid == null || uuid.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (!userExists(uuid)) {
|
||||
debug1("Adding nonvotedplayer: " + playerName + " / " + uuid);
|
||||
storage.upsertPlayer(uuid, playerName, System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all UUIDs in cache.
|
||||
* @return set of UUIDs
|
||||
*/
|
||||
public abstract Set<String> getAllUUIDs();
|
||||
|
||||
/**
|
||||
* Cleanup: - remove entries older than 5 days - remove entries whose UUID has
|
||||
* now appeared in proxy MySQL - remove entries with empty/invalid UUID
|
||||
*/
|
||||
public void check() {
|
||||
final long now = System.currentTimeMillis();
|
||||
Set<String> uuids = getAllUUIDs();
|
||||
|
||||
storage.forEach(entry -> {
|
||||
boolean remove = false;
|
||||
|
||||
long time = entry.getLastTime();
|
||||
// too old?
|
||||
if ((now - time) > MAX_AGE_MILLIS) {
|
||||
remove = true;
|
||||
} else {
|
||||
String uuid = entry.getUuid();
|
||||
if (uuid == null || uuid.isEmpty()) {
|
||||
remove = true;
|
||||
} else if (uuids.contains(uuid)) {
|
||||
// player has now voted in the main proxy MySQL
|
||||
remove = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (remove) {
|
||||
debug1("Removing nonvotedplayer: " + entry.getPlayerName());
|
||||
storage.removeByPlayerName(entry.getPlayerName());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the UUID for a cached non-voted player, or empty string if not
|
||||
* present.
|
||||
* @param playerName the player name
|
||||
* @return the UUID or empty string
|
||||
*/
|
||||
public String getUUID(String playerName) {
|
||||
return storage.getUuidByPlayerName(playerName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close storage connections.
|
||||
*/
|
||||
public void close() {
|
||||
storage.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Log info message.
|
||||
* @param msg the message
|
||||
*/
|
||||
public abstract void logInfo1(String msg);
|
||||
|
||||
/**
|
||||
* Log severe message.
|
||||
* @param msg the message
|
||||
*/
|
||||
public abstract void logSevere1(String msg);
|
||||
|
||||
/**
|
||||
* Debug log exception.
|
||||
* @param e the exception
|
||||
*/
|
||||
public abstract void debug1(Exception e);
|
||||
|
||||
/**
|
||||
* Debug log message.
|
||||
* @param msg the message
|
||||
*/
|
||||
public abstract void debug1(String msg);
|
||||
}
|
||||
-262
@@ -1,262 +0,0 @@
|
||||
package com.bencodez.votingplugin.proxy.cache.nonvoted;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.bencodez.simpleapi.sql.mysql.MySQL;
|
||||
import com.bencodez.simpleapi.sql.mysql.config.MysqlConfig;
|
||||
import com.bencodez.simpleapi.sql.mysql.queries.Query;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Table for tracking non-voted players in the proxy cache.
|
||||
*/
|
||||
public abstract class ProxyNonVotedPlayersTable {
|
||||
|
||||
@Getter
|
||||
private final MySQL mysql;
|
||||
private final String tableName;
|
||||
|
||||
/**
|
||||
* Logs a severe message.
|
||||
* @param msg the message to log
|
||||
*/
|
||||
public abstract void logSevere(String msg);
|
||||
|
||||
/**
|
||||
* Logs an info message.
|
||||
* @param msg the message to log
|
||||
*/
|
||||
public abstract void logInfo(String msg);
|
||||
|
||||
/**
|
||||
* Logs debug information for an exception.
|
||||
* @param e the exception to debug
|
||||
*/
|
||||
public abstract void debug(Exception e);
|
||||
|
||||
/**
|
||||
* Constructor using an existing MySQL connection.
|
||||
* @param existingMysql the existing MySQL instance
|
||||
* @param tablePrefix the table prefix
|
||||
* @param debug whether debug mode is enabled
|
||||
*/
|
||||
public ProxyNonVotedPlayersTable(MySQL existingMysql, String tablePrefix, boolean debug) {
|
||||
this.mysql = existingMysql;
|
||||
this.tableName = (tablePrefix != null ? tablePrefix : "") + "votingplugin_nonvotedplayers";
|
||||
createTableIfNeeded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor using a MySQL configuration.
|
||||
* @param config the MySQL configuration
|
||||
* @param debug whether debug mode is enabled
|
||||
*/
|
||||
public ProxyNonVotedPlayersTable(MysqlConfig config, boolean debug) {
|
||||
String prefix = config.getTablePrefix() != null ? config.getTablePrefix() : "";
|
||||
this.tableName = prefix + "votingplugin_nonvotedplayers";
|
||||
|
||||
this.mysql = new MySQL(config.getMaxThreads()) {
|
||||
@Override
|
||||
public void debug(SQLException e) {
|
||||
if (debug) {
|
||||
ProxyNonVotedPlayersTable.this.debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void severe(String msg) {
|
||||
logSevere(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(String msg) {
|
||||
if (debug) {
|
||||
logInfo("MYSQL DEBUG: " + msg);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!mysql.connect(config)) {
|
||||
logSevere("Failed to connect to MySQL for non-voted players cache!");
|
||||
}
|
||||
try {
|
||||
new Query(mysql, "USE `" + config.getDatabase() + "`;").executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
logSevere("Failed to select database: " + config.getDatabase());
|
||||
debug(e);
|
||||
}
|
||||
|
||||
createTableIfNeeded();
|
||||
}
|
||||
|
||||
private void createTableIfNeeded() {
|
||||
String sql = "CREATE TABLE IF NOT EXISTS `" + tableName + "` (" + "id INT AUTO_INCREMENT PRIMARY KEY,"
|
||||
+ "uuid VARCHAR(36) NOT NULL," + "playerName VARCHAR(100) NOT NULL," + "`lastTime` BIGINT NOT NULL,"
|
||||
+ "UNIQUE KEY uniq_playerName (`playerName`)," + "KEY idx_uuid (`uuid`),"
|
||||
+ "KEY idx_lastTime (`lastTime`)" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
|
||||
|
||||
try {
|
||||
new Query(mysql, sql).executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the table name.
|
||||
* @return the table name
|
||||
*/
|
||||
public String getTableName() {
|
||||
return tableName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upsert player record.
|
||||
* @param uuid the player UUID
|
||||
* @param playerName the player name
|
||||
* @param lastTime the last time
|
||||
*/
|
||||
public void upsertPlayer(String uuid, String playerName, long lastTime) {
|
||||
String sql = "INSERT INTO `" + tableName + "` (uuid, playerName, lastTime) " + "VALUES (?, ?, ?) "
|
||||
+ "ON DUPLICATE KEY UPDATE uuid = VALUES(uuid), lastTime = VALUES(lastTime);";
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setString(1, uuid);
|
||||
ps.setString(2, playerName);
|
||||
ps.setLong(3, lastTime);
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get UUID by player name.
|
||||
* @param playerName the player name
|
||||
* @return the UUID or empty string
|
||||
*/
|
||||
public String getUuidByPlayerName(String playerName) {
|
||||
String sql = "SELECT uuid FROM `" + tableName + "` WHERE playerName = ?;";
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setString(1, playerName);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
String uuid = rs.getString("uuid");
|
||||
return uuid != null ? uuid : "";
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
debug(e);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove player by name.
|
||||
* @param playerName the player name
|
||||
*/
|
||||
public void removeByPlayerName(String playerName) {
|
||||
String sql = "DELETE FROM `" + tableName + "` WHERE playerName = ?;";
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setString(1, playerName);
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all rows from the table.
|
||||
* @return list of all non-voted player rows
|
||||
*/
|
||||
public List<NonVotedPlayerRow> getAllRows() {
|
||||
List<NonVotedPlayerRow> list = new ArrayList<>();
|
||||
String sql = "SELECT id, uuid, playerName, lastTime FROM `" + tableName + "`;";
|
||||
try (Connection conn = mysql.getConnectionManager().getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sql);
|
||||
ResultSet rs = ps.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
list.add(new NonVotedPlayerRow(rs.getInt("id"), rs.getString("uuid"), rs.getString("playerName"),
|
||||
rs.getLong("lastTime")));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
debug(e);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all rows from the table.
|
||||
*/
|
||||
public void clearAll() {
|
||||
try {
|
||||
new Query(mysql, "TRUNCATE TABLE `" + tableName + "`;").executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a row in the non-voted players table.
|
||||
*/
|
||||
public static class NonVotedPlayerRow {
|
||||
private final int id;
|
||||
private final String uuid;
|
||||
private final String playerName;
|
||||
private final long lastTime;
|
||||
|
||||
/**
|
||||
* Constructor for NonVotedPlayerRow.
|
||||
* @param id the row ID
|
||||
* @param uuid the player UUID
|
||||
* @param playerName the player name
|
||||
* @param lastTime the last time
|
||||
*/
|
||||
public NonVotedPlayerRow(int id, String uuid, String playerName, long lastTime) {
|
||||
this.id = id;
|
||||
this.uuid = uuid;
|
||||
this.playerName = playerName;
|
||||
this.lastTime = lastTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the row ID.
|
||||
* @return the ID
|
||||
*/
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player UUID.
|
||||
* @return the UUID
|
||||
*/
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player name.
|
||||
* @return the player name
|
||||
*/
|
||||
public String getPlayerName() {
|
||||
return playerName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last time.
|
||||
* @return the last time
|
||||
*/
|
||||
public long getLastTime() {
|
||||
return lastTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
-436
@@ -1,436 +0,0 @@
|
||||
// File: com/bencodez/votingplugin/proxy/multiproxy/MultiProxyHandler.java
|
||||
package com.bencodez.votingplugin.proxy.multiproxy;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.bencodez.simpleapi.encryption.EncryptionHandler;
|
||||
import com.bencodez.simpleapi.servercomm.codec.JsonEnvelope;
|
||||
import com.bencodez.simpleapi.servercomm.redis.RedisHandler;
|
||||
import com.bencodez.simpleapi.servercomm.redis.RedisListener;
|
||||
import com.bencodez.simpleapi.servercomm.sockets.ClientHandler;
|
||||
import com.bencodez.simpleapi.servercomm.sockets.SocketHandler;
|
||||
import com.bencodez.simpleapi.servercomm.sockets.SocketReceiver;
|
||||
import com.bencodez.votingplugin.proxy.VoteTotalsSnapshot;
|
||||
import com.bencodez.votingplugin.proxy.VotingPluginWire;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Abstract handler for multi-proxy support.
|
||||
*/
|
||||
public abstract class MultiProxyHandler {
|
||||
private HashMap<String, ClientHandler> multiproxyClientHandles;
|
||||
private SocketHandler multiproxySocketHandler;
|
||||
|
||||
@Getter
|
||||
private RedisHandler multiProxyRedis;
|
||||
|
||||
/**
|
||||
* Constructs a new multi-proxy handler.
|
||||
*/
|
||||
public MultiProxyHandler() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a non-voted player to the cache.
|
||||
*
|
||||
* @param uuid the player UUID
|
||||
* @param playerName the player name
|
||||
*/
|
||||
public abstract void addNonVotedPlayerCache(String uuid, String playerName);
|
||||
|
||||
/**
|
||||
* Clears a vote for a player.
|
||||
*
|
||||
* @param uuid the player UUID
|
||||
*/
|
||||
public abstract void clearVote(String uuid);
|
||||
|
||||
/**
|
||||
* Closes the multi-proxy handler.
|
||||
*/
|
||||
public void close() {
|
||||
if (multiproxySocketHandler != null) {
|
||||
multiproxySocketHandler.closeConnection();
|
||||
multiproxySocketHandler = null;
|
||||
}
|
||||
if (multiProxyRedis != null && !getMultiProxyRedisUseExistingConnection()) {
|
||||
multiProxyRedis.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether debug mode is enabled.
|
||||
*
|
||||
* @return true if debug mode is enabled
|
||||
*/
|
||||
public abstract boolean getDebug();
|
||||
|
||||
/**
|
||||
* Gets the encryption handler.
|
||||
*
|
||||
* @return the encryption handler
|
||||
*/
|
||||
public abstract EncryptionHandler getEncryptionHandler();
|
||||
|
||||
/**
|
||||
* Gets the multi-proxy method.
|
||||
*
|
||||
* @return the multi-proxy method
|
||||
*/
|
||||
public abstract MultiProxyMethod getMultiProxyMethod();
|
||||
|
||||
/**
|
||||
* Gets the multi-proxy password.
|
||||
*
|
||||
* @return the password
|
||||
*/
|
||||
public abstract String getMultiProxyPassword();
|
||||
|
||||
/**
|
||||
* Gets the multi-proxy Redis host.
|
||||
*
|
||||
* @return the Redis host
|
||||
*/
|
||||
public abstract String getMultiProxyRedisHost();
|
||||
|
||||
/**
|
||||
* Gets the multi-proxy Redis port.
|
||||
*
|
||||
* @return the Redis port
|
||||
*/
|
||||
public abstract int getMultiProxyRedisPort();
|
||||
|
||||
/**
|
||||
* Gets the multi-proxy Redis database index.
|
||||
*
|
||||
* @return the database index
|
||||
*/
|
||||
public abstract int getMultiProxyRedisDbIndex();
|
||||
|
||||
/**
|
||||
* Gets whether to use an existing Redis connection.
|
||||
*
|
||||
* @return true if using existing connection
|
||||
*/
|
||||
public abstract boolean getMultiProxyRedisUseExistingConnection();
|
||||
|
||||
/**
|
||||
* Gets the multi-proxy server name.
|
||||
*
|
||||
* @return the server name
|
||||
*/
|
||||
public abstract String getMultiProxyServerName();
|
||||
|
||||
/**
|
||||
* Gets the multi-proxy servers.
|
||||
*
|
||||
* @return the servers
|
||||
*/
|
||||
public abstract Collection<String> getMultiProxyServers();
|
||||
|
||||
/**
|
||||
* Gets the configuration for a multi-proxy server.
|
||||
*
|
||||
* @param s the server name
|
||||
* @return the server configuration
|
||||
*/
|
||||
public abstract MultiProxyServerSocketConfiguration getMultiProxyServersConfiguration(String s);
|
||||
|
||||
/**
|
||||
* Gets the multi-proxy socket host.
|
||||
*
|
||||
* @return the socket host
|
||||
*/
|
||||
public abstract String getMultiProxySocketHostHost();
|
||||
|
||||
/**
|
||||
* Gets the multi-proxy socket host port.
|
||||
*
|
||||
* @return the socket host port
|
||||
*/
|
||||
public abstract int getMultiProxySocketHostPort();
|
||||
|
||||
/**
|
||||
* Gets whether multi-proxy support is enabled.
|
||||
*
|
||||
* @return true if multi-proxy support is enabled
|
||||
*/
|
||||
public abstract boolean getMultiProxySupportEnabled();
|
||||
|
||||
/**
|
||||
* Gets the multi-proxy username.
|
||||
*
|
||||
* @return the username
|
||||
*/
|
||||
public abstract String getMultiProxyUsername();
|
||||
|
||||
/**
|
||||
* Gets the plugin data folder.
|
||||
*
|
||||
* @return the plugin data folder
|
||||
*/
|
||||
public abstract File getPluginDataFolder();
|
||||
|
||||
/**
|
||||
* Gets whether this is the primary server.
|
||||
*
|
||||
* @return true if this is the primary server
|
||||
*/
|
||||
public abstract boolean getPrimaryServer();
|
||||
|
||||
/**
|
||||
* Gets the proxy servers.
|
||||
*
|
||||
* @return the proxy servers
|
||||
*/
|
||||
public abstract List<String> getProxyServers();
|
||||
|
||||
/**
|
||||
* Gets the Redis handler.
|
||||
*
|
||||
* @return the Redis handler
|
||||
*/
|
||||
public abstract RedisHandler getRedisHandler();
|
||||
|
||||
/**
|
||||
* Gets the version.
|
||||
*
|
||||
* @return the version
|
||||
*/
|
||||
public abstract String getVersion();
|
||||
|
||||
/**
|
||||
* Logs an info message.
|
||||
*
|
||||
* @param msg the message to log
|
||||
*/
|
||||
public abstract void logInfo(String msg);
|
||||
|
||||
/**
|
||||
* Runs a task asynchronously.
|
||||
*
|
||||
* @param runnable the task to run
|
||||
*/
|
||||
public abstract void runAsnc(Runnable runnable);
|
||||
|
||||
/**
|
||||
* Sets the encryption handler.
|
||||
*
|
||||
* @param encryptionHandler the encryption handler
|
||||
*/
|
||||
public abstract void setEncryptionHandler(EncryptionHandler encryptionHandler);
|
||||
|
||||
/**
|
||||
* Triggers a vote.
|
||||
*
|
||||
* @param player the player name
|
||||
* @param service the service name
|
||||
* @param realVote whether this is a real vote
|
||||
* @param timeQueue whether to queue by time
|
||||
* @param queueTime the queue time
|
||||
* @param text the vote totals snapshot
|
||||
* @param uuid the player UUID
|
||||
*/
|
||||
public abstract void triggerVote(String player, String service, boolean realVote, boolean timeQueue, long queueTime,
|
||||
VoteTotalsSnapshot text, String uuid);
|
||||
|
||||
/**
|
||||
* Loads multi-proxy support.
|
||||
*/
|
||||
public void loadMultiProxySupport() {
|
||||
if (!getMultiProxySupportEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (getMultiProxyMethod().equals(MultiProxyMethod.SOCKETS)) {
|
||||
if (getEncryptionHandler() == null) {
|
||||
setEncryptionHandler(
|
||||
new EncryptionHandler("VotingPlugin", new File(getPluginDataFolder(), "secretkey.key")));
|
||||
}
|
||||
|
||||
if (multiproxySocketHandler != null) {
|
||||
multiproxySocketHandler.closeConnection();
|
||||
multiproxySocketHandler = null;
|
||||
}
|
||||
|
||||
multiproxySocketHandler = new SocketHandler(getVersion(), getMultiProxySocketHostHost(),
|
||||
getMultiProxySocketHostPort(), getEncryptionHandler(), getDebug()) {
|
||||
|
||||
@Override
|
||||
public void log(String str) {
|
||||
logInfo(str);
|
||||
}
|
||||
};
|
||||
|
||||
multiproxySocketHandler.add(new SocketReceiver() {
|
||||
@Override
|
||||
public void onReceiveEnvelope(JsonEnvelope envelope) {
|
||||
handleEnvelope(envelope);
|
||||
}
|
||||
});
|
||||
|
||||
multiproxyClientHandles = new HashMap<>();
|
||||
for (String s : getMultiProxyServers()) {
|
||||
MultiProxyServerSocketConfiguration d = getMultiProxyServersConfiguration(s);
|
||||
multiproxyClientHandles.put(s,
|
||||
new ClientHandler(d.getHost(), d.getPort(), getEncryptionHandler(), getDebug()));
|
||||
}
|
||||
|
||||
} else {
|
||||
if (getMultiProxyRedisUseExistingConnection() && getRedisHandler() != null) {
|
||||
multiProxyRedis = getRedisHandler();
|
||||
} else {
|
||||
multiProxyRedis = new RedisHandler(getMultiProxyRedisHost(), getMultiProxyRedisPort(),
|
||||
getMultiProxyUsername(), getMultiProxyPassword(), getMultiProxyRedisDbIndex()) {
|
||||
@Override
|
||||
public void debug(String message) {
|
||||
if (getDebug()) {
|
||||
logInfo("MultiProxyRedis: " + message);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
runAsnc(() -> {
|
||||
RedisListener listener = multiProxyRedis.createEnvelopeListener(
|
||||
"VotingPluginProxy_" + getMultiProxyServerName(), (ch, env) -> handleEnvelope(env));
|
||||
multiProxyRedis.loadListener(listener);
|
||||
});
|
||||
}
|
||||
|
||||
logInfo("Loaded multi-proxy support: " + getMultiProxyMethod().toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles player login.
|
||||
*
|
||||
* @param uuid the player UUID
|
||||
* @param playerName the player name
|
||||
*/
|
||||
public void login(String uuid, String playerName) {
|
||||
if (!getMultiProxySupportEnabled() || getPrimaryServer()) {
|
||||
return;
|
||||
}
|
||||
sendMultiProxyEnvelope(VotingPluginWire.login(playerName, uuid, getMultiProxyServerName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a clear vote message.
|
||||
*
|
||||
* @param uuid the player UUID
|
||||
* @param playerName the player name
|
||||
*/
|
||||
public void sendClearVote(String uuid, String playerName) {
|
||||
if (!getMultiProxySupportEnabled()) {
|
||||
return;
|
||||
}
|
||||
sendMultiProxyEnvelope(VotingPluginWire.clearVote(uuid, playerName, getMultiProxyServerName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a status message.
|
||||
*/
|
||||
public void sendStatus() {
|
||||
if (!getMultiProxySupportEnabled()) {
|
||||
return;
|
||||
}
|
||||
sendMultiProxyEnvelope(VotingPluginWire.status(getMultiProxyServerName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a multi-proxy envelope.
|
||||
*
|
||||
* @param envelope the envelope to send
|
||||
*/
|
||||
public void sendMultiProxyEnvelope(JsonEnvelope envelope) {
|
||||
if (envelope == null) {
|
||||
return;
|
||||
}
|
||||
if (getMultiProxyMethod().equals(MultiProxyMethod.SOCKETS)) {
|
||||
if (multiproxyClientHandles == null) {
|
||||
return;
|
||||
}
|
||||
for (ClientHandler h : multiproxyClientHandles.values()) {
|
||||
h.sendEnvelope(envelope);
|
||||
}
|
||||
} else if (getMultiProxyMethod().equals(MultiProxyMethod.REDIS)) {
|
||||
if (multiProxyRedis == null) {
|
||||
return;
|
||||
}
|
||||
for (String server : getProxyServers()) {
|
||||
multiProxyRedis.publishEnvelope("VotingPluginProxy_" + server, envelope);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleEnvelope(JsonEnvelope envelope) {
|
||||
if (envelope == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String sub = envelope.getSubChannel() == null ? "" : envelope.getSubChannel();
|
||||
|
||||
Map<String, String> f = envelope.getFields();
|
||||
if (f == null) {
|
||||
f = new HashMap<>();
|
||||
}
|
||||
|
||||
if (sub.equalsIgnoreCase(VotingPluginWire.SUB_STATUS)) {
|
||||
logInfo("Multi-proxy status message received");
|
||||
return;
|
||||
}
|
||||
|
||||
if (sub.equalsIgnoreCase(VotingPluginWire.SUB_CLEAR_VOTE)) {
|
||||
final String uuid = f.getOrDefault(VotingPluginWire.K_UUID, "");
|
||||
final String player = f.getOrDefault(VotingPluginWire.K_PLAYER, "");
|
||||
final String server = f.getOrDefault(VotingPluginWire.K_SERVER, "");
|
||||
|
||||
if (!uuid.isEmpty()) {
|
||||
clearVote(uuid);
|
||||
if (getPrimaryServer()) {
|
||||
sendMultiProxyEnvelope(VotingPluginWire.clearVotePrimary(uuid, player, server));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (sub.equalsIgnoreCase(VotingPluginWire.SUB_CLEAR_VOTE_PRIMARY)) {
|
||||
final String uuid = f.getOrDefault(VotingPluginWire.K_UUID, "");
|
||||
if (!uuid.isEmpty()) {
|
||||
clearVote(uuid);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (sub.equalsIgnoreCase(VotingPluginWire.SUB_LOGIN)) {
|
||||
final String player = f.getOrDefault(VotingPluginWire.K_PLAYER, "");
|
||||
final String uuid = f.getOrDefault(VotingPluginWire.K_UUID, "");
|
||||
if (!player.isEmpty() && !uuid.isEmpty()) {
|
||||
addNonVotedPlayerCache(uuid, player);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (sub.equalsIgnoreCase(VotingPluginWire.SUB_VOTE) || sub.equalsIgnoreCase(VotingPluginWire.SUB_VOTE_ONLINE)) {
|
||||
final String player = f.getOrDefault(VotingPluginWire.K_PLAYER, "");
|
||||
final String uuid = f.getOrDefault(VotingPluginWire.K_UUID, "");
|
||||
final String service = f.getOrDefault(VotingPluginWire.K_SERVICE, "");
|
||||
final String totals = f.getOrDefault(VotingPluginWire.K_TOTALS, "");
|
||||
final boolean realVote = Boolean.parseBoolean(f.getOrDefault(VotingPluginWire.K_REAL_VOTE, "false"));
|
||||
|
||||
if (!player.isEmpty() && !uuid.isEmpty() && !service.isEmpty()) {
|
||||
triggerVote(player, service, realVote, true, 0L, VoteTotalsSnapshot.parseStorage(totals), uuid);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (getDebug()) {
|
||||
logInfo("MultiProxy ignored subchannel: " + sub + " fields=" + f);
|
||||
}
|
||||
}
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
package com.bencodez.votingplugin.proxy.multiproxy;
|
||||
|
||||
/**
|
||||
* Configuration for a multi-proxy server socket.
|
||||
*/
|
||||
public interface MultiProxyServerSocketConfiguration {
|
||||
/**
|
||||
* Gets the host.
|
||||
*
|
||||
* @return the host
|
||||
*/
|
||||
String getHost();
|
||||
|
||||
/**
|
||||
* Gets the port.
|
||||
*
|
||||
* @return the port
|
||||
*/
|
||||
int getPort();
|
||||
|
||||
/**
|
||||
* Gets the server name.
|
||||
*
|
||||
* @return the server name
|
||||
*/
|
||||
String getServerName();
|
||||
}
|
||||
-68
@@ -1,68 +0,0 @@
|
||||
package com.bencodez.votingplugin.proxy.velocity;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.bencodez.simpleapi.file.velocity.VelocityJSONFile;
|
||||
import com.bencodez.votingplugin.proxy.cache.nonvoted.INonVotedPlayersStorage;
|
||||
|
||||
/**
|
||||
* JSON-based non-voted players cache for Velocity proxy.
|
||||
*/
|
||||
public class VelocityJsonNonVotedPlayersCache extends VelocityJSONFile implements INonVotedPlayersStorage {
|
||||
|
||||
/**
|
||||
* Constructs a new Velocity JSON non-voted players cache.
|
||||
* @param file the file to store cache data
|
||||
*/
|
||||
public VelocityJsonNonVotedPlayersCache(File file) {
|
||||
super(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void upsertPlayer(String uuid, String playerName, long lastTime) {
|
||||
setPath(uuid, "NonVotedPlayers", playerName, "UUID");
|
||||
setPath(lastTime, "NonVotedPlayers", playerName, "LastTime");
|
||||
save();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUuidByPlayerName(String playerName) {
|
||||
if (!getNode("NonVotedPlayers", playerName).empty()) {
|
||||
return getNode("NonVotedPlayers", playerName, "UUID").getString("");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeByPlayerName(String playerName) {
|
||||
remove("NonVotedPlayers", playerName);
|
||||
save();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(Consumer<NonVotedPlayerEntry> consumer) {
|
||||
// If "NonVotedPlayers" doesn't exist, getNode(...) will just be empty and
|
||||
// getKeys(...) will be an empty set.
|
||||
for (String player : getKeys(getNode("NonVotedPlayers"))) {
|
||||
long time = getNode("NonVotedPlayers", player, "LastTime").getLong(0);
|
||||
String uuid = getNode("NonVotedPlayers", player, "UUID").getString("");
|
||||
consumer.accept(new NonVotedPlayerEntry(player, uuid, time));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a value at a specific path.
|
||||
* @param value the value to set
|
||||
* @param path the path elements
|
||||
*/
|
||||
private void setPath(Object value, Object... path) {
|
||||
set(path, value);
|
||||
}
|
||||
|
||||
}
|
||||
-238
@@ -1,238 +0,0 @@
|
||||
package com.bencodez.votingplugin.proxy.velocity;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
|
||||
import com.bencodez.simpleapi.file.velocity.VelocityJSONFile;
|
||||
import com.bencodez.votingplugin.proxy.OfflineBungeeVote;
|
||||
import com.bencodez.votingplugin.proxy.cache.ConfigDataNode;
|
||||
import com.bencodez.votingplugin.proxy.cache.DataNode;
|
||||
import com.bencodez.votingplugin.proxy.cache.IVoteCache;
|
||||
import com.bencodez.votingplugin.timequeue.VoteTimeQueue;
|
||||
|
||||
/**
|
||||
* JSON-based vote cache implementation for Velocity proxy.
|
||||
*/
|
||||
public class VelocityJsonVoteCache extends VelocityJSONFile implements IVoteCache {
|
||||
|
||||
/**
|
||||
* Constructs a new Velocity JSON vote cache.
|
||||
* @param file the file to store cache data
|
||||
*/
|
||||
public VelocityJsonVoteCache(File file) {
|
||||
super(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTimedVote(int num, VoteTimeQueue voteTimedQueue) {
|
||||
setPath(voteTimedQueue.getName(), "TimedVoteCache", String.valueOf(num), "Name");
|
||||
setPath(voteTimedQueue.getService(), "TimedVoteCache", String.valueOf(num), "Service");
|
||||
setPath(voteTimedQueue.getTime(), "TimedVoteCache", String.valueOf(num), "Time");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addVote(String server, int num, OfflineBungeeVote voteData) {
|
||||
setPath(voteData.getPlayerName(), "VoteCache", server, String.valueOf(num), "Name");
|
||||
setPath(voteData.getService(), "VoteCache", server, String.valueOf(num), "Service");
|
||||
setPath(voteData.getUuid(), "VoteCache", server, String.valueOf(num), "UUID");
|
||||
setPath(voteData.getTime(), "VoteCache", server, String.valueOf(num), "Time");
|
||||
setPath(voteData.isRealVote(), "VoteCache", server, String.valueOf(num), "Real");
|
||||
setPath(voteData.getText(), "VoteCache", server, String.valueOf(num), "Text");
|
||||
setPath(voteData.getVoteId() != null ? voteData.getVoteId().toString() : null, "VoteCache", server, String.valueOf(num), "VoteID");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addVoteOnline(String player, int num, OfflineBungeeVote voteData) {
|
||||
setPath(voteData.getPlayerName(), "OnlineCache", player, String.valueOf(num), "Name");
|
||||
setPath(voteData.getService(), "OnlineCache", player, String.valueOf(num), "Service");
|
||||
setPath(voteData.getUuid(), "OnlineCache", player, String.valueOf(num), "UUID");
|
||||
setPath(voteData.getTime(), "OnlineCache", player, String.valueOf(num), "Time");
|
||||
setPath(voteData.isRealVote(), "OnlineCache", player, String.valueOf(num), "Real");
|
||||
setPath(voteData.getText(), "OnlineCache", player, String.valueOf(num), "Text");
|
||||
setPath(voteData.getVoteId() != null ? voteData.getVoteId().toString() : null, "OnlineCache", player, String.valueOf(num), "VoteID");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearData() {
|
||||
remove("VoteCache");
|
||||
remove("OnlineCache");
|
||||
remove("TimedVoteCache");
|
||||
save();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getOnlineVotes(String name) {
|
||||
return getKeys(getNode("OnlineCache", name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigDataNode getOnlineVotes(String name, String num) {
|
||||
return new ConfigDataNode(getNode("OnlineCache", name, num));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getPlayers() {
|
||||
return getKeys(getNode("OnlineCache"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getServers() {
|
||||
return getKeys(getNode("VoteCache"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getServerVotes(String server) {
|
||||
return getKeys(getNode("VoteCache", server));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigDataNode getServerVotes(String server, String num) {
|
||||
return new ConfigDataNode(getNode("VoteCache", server, num));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getTimedVoteCache() {
|
||||
return getKeys(getNode("TimedVoteCache"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigDataNode getTimedVoteCache(String key) {
|
||||
return new ConfigDataNode(getNode("TimedVoteCache", key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVotePartyCache(String server) {
|
||||
return getNode("VoteParty", "Cache", server).getInt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVotePartyCurrentVotes() {
|
||||
return getInt(getNode("VoteParty", "CurrentVotes"), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVotePartyInreaseVotesRequired() {
|
||||
return getInt(getNode("VoteParty", "IncreaseVotes"), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVotePartyCache(String server, int amount) {
|
||||
setPath(amount, "VoteParty", "Cache", server);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVotePartyCurrentVotes(int amount) {
|
||||
setPath(amount, "VoteParty", "CurrentVotes");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVotePartyInreaseVotesRequired(int amount) {
|
||||
setPath(amount, "VoteParty", "IncreaseVotes");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeOnlineVotes(String player) {
|
||||
remove("OnlineCache", player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeServerVotes(String server) {
|
||||
remove("VoteCache", server);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeServerVote(String server, String uuid) {
|
||||
Collection<String> votes = getServerVotes(server);
|
||||
if (votes == null) {
|
||||
return;
|
||||
}
|
||||
// search for vote with uuid and remove it
|
||||
for (String num : votes) {
|
||||
ConfigDataNode node = getServerVotes(server, num);
|
||||
if (node == null) {
|
||||
continue;
|
||||
}
|
||||
DataNode uuidNode = node.get("UUID");
|
||||
if (uuidNode == null) {
|
||||
continue;
|
||||
}
|
||||
String nodeUuid = uuidNode.asString();
|
||||
if (nodeUuid != null && nodeUuid.equals(uuid)) {
|
||||
remove("VoteCache", server, num);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeVote(String server, OfflineBungeeVote vote) {
|
||||
Collection<String> votes = getServerVotes(server);
|
||||
if (votes == null) {
|
||||
return;
|
||||
}
|
||||
for (String num : votes) {
|
||||
ConfigDataNode node = getServerVotes(server, num);
|
||||
if (node == null) {
|
||||
continue;
|
||||
}
|
||||
DataNode uuidNode = node.get("UUID");
|
||||
DataNode serviceNode = node.get("Service");
|
||||
DataNode timeNode = node.get("Time");
|
||||
if (uuidNode == null || serviceNode == null || timeNode == null) {
|
||||
continue;
|
||||
}
|
||||
String nodeUuid = uuidNode.asString();
|
||||
String nodeService = serviceNode.asString();
|
||||
Long nodeTime = timeNode.asLong();
|
||||
|
||||
if (nodeUuid != null && nodeService != null && nodeTime != null && nodeUuid.equals(vote.getUuid())
|
||||
&& nodeService.equals(vote.getService()) && nodeTime.longValue() == vote.getTime()) {
|
||||
remove("VoteCache", server, num);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeOnlineVote(OfflineBungeeVote vote) {
|
||||
Collection<String> players = getPlayers();
|
||||
if (players == null) {
|
||||
return;
|
||||
}
|
||||
for (String player : players) {
|
||||
Collection<String> onlineVotes = getOnlineVotes(player);
|
||||
if (onlineVotes == null) {
|
||||
continue;
|
||||
}
|
||||
for (String num : onlineVotes) {
|
||||
ConfigDataNode node = getOnlineVotes(player, num);
|
||||
if (node == null) {
|
||||
continue;
|
||||
}
|
||||
DataNode uuidNode = node.get("UUID");
|
||||
DataNode serviceNode = node.get("Service");
|
||||
DataNode timeNode = node.get("Time");
|
||||
if (uuidNode == null || serviceNode == null || timeNode == null) {
|
||||
continue;
|
||||
}
|
||||
String nodeUuid = uuidNode.asString();
|
||||
String nodeService = serviceNode.asString();
|
||||
Long nodeTime = timeNode.asLong();
|
||||
|
||||
if (nodeUuid != null && nodeService != null && nodeTime != null && nodeUuid.equals(vote.getUuid())
|
||||
&& nodeService.equals(vote.getService()) && nodeTime.longValue() == vote.getTime()) {
|
||||
remove("OnlineCache", player, num);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a value at a specific path.
|
||||
* @param value the value to set
|
||||
* @param path the path elements
|
||||
*/
|
||||
private void setPath(Object value, Object... path) {
|
||||
set(path, value);
|
||||
}
|
||||
|
||||
}
|
||||
-1000
@@ -1,1000 +0,0 @@
|
||||
package com.bencodez.votingplugin.proxy.velocity;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.security.CodeSource;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import org.bstats.charts.SimplePie;
|
||||
import org.bstats.velocity.Metrics;
|
||||
import org.slf4j.Logger;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
|
||||
|
||||
import com.bencodez.simpleapi.file.velocity.VelocityYMLFile;
|
||||
import com.bencodez.simpleapi.sql.mysql.config.MysqlConfig;
|
||||
import com.bencodez.simpleapi.sql.mysql.config.MysqlConfigVelocity;
|
||||
import com.bencodez.votingplugin.proxy.VotingPluginProxy;
|
||||
import com.bencodez.votingplugin.proxy.VotingPluginProxyConfig;
|
||||
import com.google.inject.Inject;
|
||||
import com.velocitypowered.api.command.CommandMeta;
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
|
||||
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
|
||||
import com.velocitypowered.api.plugin.Dependency;
|
||||
import com.velocitypowered.api.plugin.Plugin;
|
||||
import com.velocitypowered.api.plugin.annotation.DataDirectory;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.scheduler.ScheduledTask;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
|
||||
/**
|
||||
* VotingPlugin proxy implementation for Velocity.
|
||||
*
|
||||
* Reload behavior:
|
||||
* <ul>
|
||||
* <li><b>/vpp reload</b> = small reload (config.reload +
|
||||
* votingPluginProxy.reload)</li>
|
||||
* <li><b>/vpp reloadall</b> = full reload (rebuild runtime, reload mysql,
|
||||
* reload caches/tasks)</li>
|
||||
* </ul>
|
||||
*
|
||||
*/
|
||||
@Plugin(id = "votingplugin", name = "VotingPlugin", version = "1.0", url = "https://www.spigotmc.org/resources/votingplugin.15358/", description = "VotingPlugin Velocity Version", authors = {
|
||||
"BenCodez" }, dependencies = { @Dependency(id = "nuvotifier", optional = true) })
|
||||
public class VotingPluginVelocity {
|
||||
|
||||
/**
|
||||
* Current plugin message channel.
|
||||
*/
|
||||
private volatile ChannelIdentifier channel;
|
||||
|
||||
@Getter
|
||||
private VelocityConfig config;
|
||||
|
||||
private final Path dataDirectory;
|
||||
|
||||
@Getter
|
||||
private final Logger logger;
|
||||
|
||||
private final Metrics.Factory metricsFactory;
|
||||
|
||||
private final ProxyServer server;
|
||||
|
||||
private String version = "";
|
||||
private String buildNumber = "NOTSET";
|
||||
private File versionFile;
|
||||
|
||||
@Getter
|
||||
private ScheduledExecutorService timer;
|
||||
|
||||
@Getter
|
||||
private volatile VotingPluginProxy votingPluginProxy;
|
||||
|
||||
private VelocityJsonVoteCache voteCacheFile;
|
||||
private VelocityJsonNonVotedPlayersCache nonVotedPlayersCache;
|
||||
|
||||
private ScheduledTask voteCheckTask;
|
||||
private ScheduledTask cacheSaveTask;
|
||||
|
||||
/**
|
||||
* Register votifier listener only once.
|
||||
*/
|
||||
private VoteEventVelocity voteEventVelocity;
|
||||
|
||||
/**
|
||||
* Reload lock.
|
||||
*/
|
||||
private final Object reloadLock = new Object();
|
||||
|
||||
/**
|
||||
* True while reloadall is happening.
|
||||
*/
|
||||
private volatile boolean reloading = false;
|
||||
|
||||
/**
|
||||
* Plugin messages received during reload are queued.
|
||||
*/
|
||||
private final Queue<byte[]> queuedPluginMessages = new ConcurrentLinkedQueue<byte[]>();
|
||||
|
||||
@Inject
|
||||
public VotingPluginVelocity(ProxyServer server, Logger logger, Metrics.Factory metricsFactory,
|
||||
@DataDirectory Path dataDirectory) {
|
||||
this.server = server;
|
||||
this.logger = logger;
|
||||
this.dataDirectory = dataDirectory;
|
||||
this.metricsFactory = metricsFactory;
|
||||
this.timer = Executors.newScheduledThreadPool(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug logger.
|
||||
*
|
||||
* @param msg message
|
||||
*/
|
||||
public void debug(String msg) {
|
||||
if (config != null && config.getDebug()) {
|
||||
logger.info("Debug: " + msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias used by older code.
|
||||
*
|
||||
* @param msg message
|
||||
*/
|
||||
public void debug2(String msg) {
|
||||
debug(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of eligible servers.
|
||||
*
|
||||
* @return servers
|
||||
*/
|
||||
public Set<String> getAvailableAllServers() {
|
||||
Set<String> servers = new HashSet<String>();
|
||||
if (config.getWhiteListedServers().isEmpty()) {
|
||||
for (RegisteredServer s : server.getAllServers()) {
|
||||
String name = s.getServerInfo().getName();
|
||||
if (!config.getBlockedServers().contains(name)) {
|
||||
servers.add(name);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (RegisteredServer s : server.getAllServers()) {
|
||||
String name = s.getServerInfo().getName();
|
||||
if (config.getWhiteListedServers().contains(name)) {
|
||||
servers.add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return servers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer online name if possible.
|
||||
*
|
||||
* @param uuid uuid string
|
||||
* @param currentName current stored name
|
||||
* @return name
|
||||
*/
|
||||
public String getProperPlayerName(String uuid, String currentName) {
|
||||
try {
|
||||
UUID id = UUID.fromString(uuid);
|
||||
if (server.getPlayer(id).isPresent()) {
|
||||
Player p = server.getPlayer(id).get();
|
||||
if (p != null && p.isActive()) {
|
||||
return p.getUsername();
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return currentName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts internal version file.
|
||||
*/
|
||||
private void getVersionFile() {
|
||||
try {
|
||||
CodeSource src = this.getClass().getProtectionDomain().getCodeSource();
|
||||
if (src != null) {
|
||||
URL jar = src.getLocation();
|
||||
ZipInputStream zip = new ZipInputStream(jar.openStream());
|
||||
while (true) {
|
||||
ZipEntry e = zip.getNextEntry();
|
||||
if (e == null) {
|
||||
break;
|
||||
}
|
||||
if ("votingpluginversion.yml".equals(e.getName())) {
|
||||
Reader defConfigStream = new InputStreamReader(zip, StandardCharsets.UTF_8);
|
||||
versionFile = new File(dataDirectory.toFile(),
|
||||
"tmp" + File.separator + "votingpluginversion.yml");
|
||||
if (!versionFile.exists()) {
|
||||
versionFile.getParentFile().mkdirs();
|
||||
versionFile.createNewFile();
|
||||
}
|
||||
FileWriter fileWriter = new FileWriter(versionFile);
|
||||
int charVal;
|
||||
while ((charVal = defConfigStream.read()) != -1) {
|
||||
fileWriter.append((char) charVal);
|
||||
}
|
||||
fileWriter.close();
|
||||
defConfigStream.close();
|
||||
|
||||
YamlConfigurationLoader loader = YamlConfigurationLoader.builder().path(versionFile.toPath())
|
||||
.build();
|
||||
ConfigurationNode node = loader.load();
|
||||
if (node != null) {
|
||||
version = node.node("version").getString("");
|
||||
buildNumber = node.node("buildnumber").getString("NOTSET");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (config != null && config.getDebug()) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onPluginMessagingReceived(PluginMessageEvent event) {
|
||||
ChannelIdentifier ch = channel;
|
||||
if (ch == null) {
|
||||
return;
|
||||
}
|
||||
if (!event.getIdentifier().equals(ch)) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.setResult(PluginMessageEvent.ForwardResult.handled());
|
||||
|
||||
if (!(event.getSource() instanceof ServerConnection)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (reloading) {
|
||||
byte[] copy = new byte[event.getData().length];
|
||||
System.arraycopy(event.getData(), 0, copy, 0, event.getData().length);
|
||||
queuedPluginMessages.add(copy);
|
||||
return;
|
||||
}
|
||||
|
||||
handlePluginMessageBytes(event.getData());
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onProxyDisable(ProxyShutdownEvent event) {
|
||||
synchronized (reloadLock) {
|
||||
reloading = true;
|
||||
|
||||
cancelTasks();
|
||||
|
||||
try {
|
||||
if (voteCacheFile != null) {
|
||||
voteCacheFile.save();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
try {
|
||||
if (nonVotedPlayersCache != null) {
|
||||
nonVotedPlayersCache.save();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
try {
|
||||
if (votingPluginProxy != null) {
|
||||
votingPluginProxy.onDisable();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
try {
|
||||
if (timer != null) {
|
||||
timer.shutdownNow();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
reloading = false;
|
||||
}
|
||||
|
||||
logger.info("VotingPlugin disabled");
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onProxyInitialization(ProxyInitializeEvent event) {
|
||||
File configFile = new File(dataDirectory.toFile(), "bungeeconfig.yml");
|
||||
configFile.getParentFile().mkdirs();
|
||||
if (!configFile.exists()) {
|
||||
try {
|
||||
configFile.createNewFile();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
InputStream toCopyStream = VotingPluginVelocity.class.getClassLoader()
|
||||
.getResourceAsStream("bungeeconfig.yml");
|
||||
if (toCopyStream != null) {
|
||||
try (FileOutputStream fos = new FileOutputStream(configFile)) {
|
||||
byte[] buf = new byte[2048];
|
||||
int r;
|
||||
while (-1 != (r = toCopyStream.read(buf))) {
|
||||
fos.write(buf, 0, r);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config = new VelocityConfig(configFile);
|
||||
|
||||
channel = buildChannelIdentifier(config.getPluginMessageChannel());
|
||||
server.getChannelRegistrar().register(channel);
|
||||
|
||||
CommandMeta meta = server.getCommandManager().metaBuilder("votingpluginproxy").aliases("vpp").build();
|
||||
server.getCommandManager().register(meta, new VotingPluginVelocityCommand(this));
|
||||
|
||||
// create runtime
|
||||
votingPluginProxy = createProxyRuntime();
|
||||
|
||||
// full init
|
||||
reloadAllInternal(true);
|
||||
|
||||
// version
|
||||
try {
|
||||
getVersionFile();
|
||||
if (versionFile != null) {
|
||||
versionFile.delete();
|
||||
if (versionFile.getParentFile() != null) {
|
||||
versionFile.getParentFile().delete();
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// metrics (same as your original, shortened)
|
||||
Metrics metrics = metricsFactory.make(this, 11547);
|
||||
metrics.addCustomChart(new SimplePie("bungee_method", () -> getConfig().getBungeeMethod().toString()));
|
||||
metrics.addCustomChart(new SimplePie("config_onlinemode", () -> "" + getConfig().getOnlineMode()));
|
||||
metrics.addCustomChart(new SimplePie("sendtoallservers", () -> "" + getConfig().getSendVotesToAllServers()));
|
||||
metrics.addCustomChart(new SimplePie("allowunjoined", () -> "" + getConfig().getAllowUnJoined()));
|
||||
metrics.addCustomChart(new SimplePie("pointsonvote", () -> "" + getConfig().getPointsOnVote()));
|
||||
metrics.addCustomChart(new SimplePie("bungeemanagetotals", () -> "" + getConfig().getBungeeManageTotals()));
|
||||
metrics.addCustomChart(new SimplePie("waitforuseronline", () -> "" + getConfig().getWaitForUserOnline()));
|
||||
metrics.addCustomChart(new SimplePie("plugin_version", () -> "" + version));
|
||||
|
||||
logger.info("VotingPlugin velocity loaded, method: " + getVotingPluginProxy().getMethod().toString()
|
||||
+ ", Internal Jar Version: " + version);
|
||||
if (!"NOTSET".equals(buildNumber)) {
|
||||
logger.info("Detected using dev build number: " + buildNumber);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads VotingPluginProxy on Velocity.
|
||||
*
|
||||
* <p>
|
||||
* Two modes:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li><b>Full reload</b> ({@code loadMysql=true}): shuts down the old runtime,
|
||||
* recreates the proxy, reconnects MySQL, then loads caches and handlers.</li>
|
||||
* <li><b>Soft reload</b> ({@code loadMysql=false}): does NOT recreate the proxy
|
||||
* instance (because it owns MySQL). Only reloads config and applies
|
||||
* runtime-only settings via {@link VotingPluginProxy#reload()}.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param loadMysql whether to rebuild MySQL and fully reinitialize the proxy
|
||||
*/
|
||||
public void reloadAllInternal(boolean loadMysql) {
|
||||
synchronized (reloadLock) {
|
||||
reloading = true;
|
||||
|
||||
// Always stop repeating tasks while we touch state
|
||||
cancelTasks();
|
||||
|
||||
// Always reload config first
|
||||
try {
|
||||
config.reload(); // or config.load() - whichever your VelocityConfig provides
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to reload bungeeconfig.yml", e);
|
||||
}
|
||||
|
||||
// Update plugin message channel (safe for both modes)
|
||||
try {
|
||||
ChannelIdentifier old = channel;
|
||||
ChannelIdentifier next = buildChannelIdentifier(config.getPluginMessageChannel());
|
||||
|
||||
if (old != null && !old.equals(next)) {
|
||||
try {
|
||||
server.getChannelRegistrar().unregister(old);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
server.getChannelRegistrar().register(next);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
channel = next;
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to update plugin message channel", e);
|
||||
}
|
||||
|
||||
// =========================
|
||||
// SOFT RELOAD (NO MYSQL)
|
||||
// =========================
|
||||
if (!loadMysql) {
|
||||
// We intentionally do NOT call onDisable()/shutdown, because that would stop
|
||||
// MySQL.
|
||||
// We also do NOT recreate the proxy instance, because it owns the MySQL
|
||||
// pool/table.
|
||||
try {
|
||||
if (votingPluginProxy != null) {
|
||||
votingPluginProxy.reload();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
logger.error("Error while applying soft reload", t);
|
||||
}
|
||||
|
||||
// Restart tasks that don't depend on rebuilding the proxy
|
||||
scheduleTasks();
|
||||
|
||||
reloading = false;
|
||||
} else {
|
||||
|
||||
// =========================
|
||||
// FULL RELOAD (WITH MYSQL)
|
||||
// =========================
|
||||
|
||||
// Best-effort save caches before shutdown
|
||||
try {
|
||||
if (voteCacheFile != null) {
|
||||
voteCacheFile.save();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
try {
|
||||
if (nonVotedPlayersCache != null) {
|
||||
nonVotedPlayersCache.save();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// Shutdown old runtime completely (this stops old MySQL pool)
|
||||
try {
|
||||
if (votingPluginProxy != null) {
|
||||
votingPluginProxy.onDisable();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// Recreate runtime
|
||||
votingPluginProxy = createProxyRuntime();
|
||||
|
||||
// Init MySQL BEFORE calling proxy.load(...)
|
||||
try {
|
||||
if (config.hasDatabaseConfigured()) {
|
||||
votingPluginProxy.loadMysql(getMysqlConfig(), getGlobalDataMysqlConfig());
|
||||
} else {
|
||||
logger.error("MySQL settings not set in bungeeconfig.yml");
|
||||
votingPluginProxy.setProxyMySQL(null);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
logger.error("Failed to initialize MySQL during reload", t);
|
||||
votingPluginProxy.setProxyMySQL(null);
|
||||
}
|
||||
|
||||
// If MySQL is required (your proxy.load assumes it), abort cleanly
|
||||
if (votingPluginProxy.getProxyMySQL() == null) {
|
||||
logger.error("Reload aborted: Proxy MySQL is not initialized.");
|
||||
reloading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Recreate storages (or keep existing if you prefer)
|
||||
voteCacheFile = new VelocityJsonVoteCache(new File(dataDirectory.toFile(), "votecache.json"));
|
||||
nonVotedPlayersCache = new VelocityJsonNonVotedPlayersCache(
|
||||
new File(dataDirectory.toFile(), "nonvotedplayerscache.json"));
|
||||
|
||||
convertYamlCachesIfPresent();
|
||||
|
||||
// Load proxy state (requires MySQL)
|
||||
try {
|
||||
votingPluginProxy.load(voteCacheFile, nonVotedPlayersCache);
|
||||
} catch (Throwable t) {
|
||||
logger.error("Reload aborted while loading proxy state", t);
|
||||
reloading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Restart tasks
|
||||
scheduleTasks();
|
||||
|
||||
// Apply runtime-only config
|
||||
try {
|
||||
votingPluginProxy.reload();
|
||||
} catch (Throwable t) {
|
||||
logger.error("Error while reloading proxy internals", t);
|
||||
}
|
||||
|
||||
reloading = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Out of lock: flush queued messages (optional)
|
||||
drainQueuedPluginMessages();
|
||||
|
||||
initVotifierListenerIfNeeded();
|
||||
|
||||
// Optional: re-announce server names
|
||||
try {
|
||||
if (votingPluginProxy != null) {
|
||||
votingPluginProxy.sendServerNameMessage();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create proxy runtime.
|
||||
*
|
||||
* @return runtime
|
||||
*/
|
||||
private VotingPluginProxy createProxyRuntime() {
|
||||
return new VotingPluginProxy() {
|
||||
|
||||
@Override
|
||||
public void broadcast(String message) {
|
||||
server.getAllPlayers().forEach(
|
||||
player -> player.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(message)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(String str) {
|
||||
debug2(str);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getAllAvailableServers() {
|
||||
return getAvailableAllServers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public VotingPluginProxyConfig getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCurrentPlayerServer(String player) {
|
||||
if (server.getPlayer(player).isPresent()) {
|
||||
Player p = server.getPlayer(player).get();
|
||||
if (p.getCurrentServer().isPresent()) {
|
||||
return p.getCurrentServer().get().getServer().getServerInfo().getName();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getDataFolderPlugin() {
|
||||
return dataDirectory.toFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProperName(String uuid, String playerName) {
|
||||
return getProperPlayerName(uuid, playerName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUUID(String playerName) {
|
||||
if (playerName == null || playerName.isEmpty() || "null".equalsIgnoreCase(playerName)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!config.getOnlineMode()) {
|
||||
return UUID.nameUUIDFromBytes(("OfflinePlayer:" + playerName.toLowerCase(Locale.ROOT).trim())
|
||||
.getBytes(StandardCharsets.UTF_8)).toString();
|
||||
}
|
||||
|
||||
if (server.getPlayer(playerName).isPresent()) {
|
||||
Player p = server.getPlayer(playerName).get();
|
||||
if (p != null && p.isActive()) {
|
||||
playerName = p.getUsername();
|
||||
}
|
||||
}
|
||||
|
||||
for (Entry<UUID, String> entry : getVotingPluginProxy().getUuidPlayerNameCache().entrySet()) {
|
||||
if (entry.getValue() != null && entry.getValue().equalsIgnoreCase(playerName)) {
|
||||
playerName = entry.getValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (server.getPlayer(playerName).isPresent()) {
|
||||
Player p = server.getPlayer(playerName).get();
|
||||
if (p != null && p.isActive()) {
|
||||
return p.getUniqueId().toString();
|
||||
}
|
||||
}
|
||||
|
||||
for (Entry<UUID, String> entry : getVotingPluginProxy().getUuidPlayerNameCache().entrySet()) {
|
||||
if (entry.getValue() != null && entry.getValue().equalsIgnoreCase(playerName)) {
|
||||
return entry.getKey().toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (getVotingPluginProxy().getProxyMySQL() != null) {
|
||||
String str = getVotingPluginProxy().getProxyMySQL().getUUID(playerName);
|
||||
if (str != null) {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
return getVotingPluginProxy().getNonVotedPlayersCache().getUUID(playerName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPluginVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVoteCacheCurrentVotePartyVotes() {
|
||||
return voteCacheFile.getVotePartyCurrentVotes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getVoteCacheLastUpdated() {
|
||||
return voteCacheFile.getNode("Time", "LastUpdated").getLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVoteCachePrevDay() {
|
||||
return voteCacheFile.getNode("Time", "Day").getInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVoteCachePrevMonth() {
|
||||
return voteCacheFile.getNode("Time", "Month").getString("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVoteCachePrevWeek() {
|
||||
return voteCacheFile.getNode("Time", "Week").getInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVoteCacheVotePartyIncreaseVotesRequired() {
|
||||
return voteCacheFile.getVotePartyInreaseVotesRequired();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVoteCacheIgnoreTime() {
|
||||
return voteCacheFile.getNode("Time", "IgnoreTime").getBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVoteCacheLastUpdated() {
|
||||
voteCacheFile.set(new Object[] { "Time", "LastUpdated" }, System.currentTimeMillis());
|
||||
voteCacheFile.save();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVoteCachePrevDay(int day) {
|
||||
voteCacheFile.set(new Object[] { "Time", "Day" }, day);
|
||||
voteCacheFile.save();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVoteCachePrevMonth(String text) {
|
||||
voteCacheFile.set(new Object[] { "Time", "Month" }, text);
|
||||
voteCacheFile.save();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVoteCachePrevWeek(int week) {
|
||||
voteCacheFile.set(new Object[] { "Time", "Week" }, week);
|
||||
voteCacheFile.save();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVoteCacheVoteCacheIgnoreTime(boolean ignore) {
|
||||
voteCacheFile.set(new Object[] { "Time", "IgnoreTime" }, ignore);
|
||||
voteCacheFile.save();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVoteCacheVotePartyCurrentVotes(int votes) {
|
||||
voteCacheFile.setVotePartyCurrentVotes(votes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVoteCacheVotePartyIncreaseVotesRequired(int votes) {
|
||||
voteCacheFile.setVotePartyInreaseVotesRequired(votes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlayerOnline(String playerName) {
|
||||
if (playerName == null) {
|
||||
return false;
|
||||
}
|
||||
return server.getPlayer(playerName).map(Player::isActive).orElse(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isServerValid(String serverName) {
|
||||
return server.getServer(serverName).isPresent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSomeoneOnlineServer(String serverName) {
|
||||
if (server.getServer(serverName).isPresent()) {
|
||||
return !server.getServer(serverName).get().getPlayersConnected().isEmpty();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String message) {
|
||||
logger.info(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logSevere(String message) {
|
||||
logger.error(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runAsync(Runnable run) {
|
||||
runAsyncNow(run);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runConsoleCommand(String command) {
|
||||
server.getCommandManager().executeAsync(server.getConsoleCommandSource(), command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveVoteCacheFile() {
|
||||
voteCacheFile.save();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPluginMessageData(String serverName, String channelName, byte[] data, boolean queue) {
|
||||
if (!server.getServer(serverName).isPresent()) {
|
||||
return;
|
||||
}
|
||||
RegisteredServer send = server.getServer(serverName).get();
|
||||
send.sendPluginMessage(channel, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String message) {
|
||||
logger.warn(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reloadCore(boolean mysql) {
|
||||
// mysql=true is reloadall behavior
|
||||
reloadAllInternal(mysql);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledExecutorService getScheduler() {
|
||||
return timer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MysqlConfig getVoteCacheMySQLConfig() {
|
||||
return new MysqlConfigVelocity("VoteCache", config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MysqlConfig getNonVotedCacheMySQLConfig() {
|
||||
return new MysqlConfigVelocity("NonVotedCache", config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MysqlConfig getVoteLoggingMySQLConfig() {
|
||||
return new MysqlConfigVelocity("VoteLogging", config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadTaskTimer(Runnable runnable, long delaySeconds, long repeatSeconds) {
|
||||
timer.scheduleAtFixedRate(runnable, delaySeconds, repeatSeconds, TimeUnit.SECONDS);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the MySQL configuration.
|
||||
*
|
||||
* <p>
|
||||
* Prefers the "Database" section if it exists and is usable. If the "Database"
|
||||
* section is missing, not a map, or has no keys, falls back to legacy/root
|
||||
* configuration parsing.
|
||||
* </p>
|
||||
*
|
||||
* @return mysql config (never null)
|
||||
*/
|
||||
public MysqlConfig getMysqlConfig() {
|
||||
final ConfigurationNode root = config.getData();
|
||||
final ConfigurationNode db = root.node("Database");
|
||||
|
||||
// If Database section doesn't exist (virtual), isn't a section/map, or is empty
|
||||
// -> fallback
|
||||
if (isMissingOrEmptySection(db)) {
|
||||
return new MysqlConfigVelocity(config);
|
||||
}
|
||||
|
||||
return new MysqlConfigVelocity("Database", config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a configuration node represents a missing or empty section.
|
||||
*
|
||||
* @param node configuration node
|
||||
* @return true if the node is missing/virtual, not a map/section, or contains
|
||||
* no keys
|
||||
*/
|
||||
private boolean isMissingOrEmptySection(ConfigurationNode node) {
|
||||
if (node == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// "virtual" typically means it doesn't exist in the file
|
||||
try {
|
||||
if (node.virtual()) {
|
||||
return true;
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
// Some shaded/older configurate nodes may not expose virtual() consistently.
|
||||
}
|
||||
|
||||
// If it's not a map/section, it's not a usable "Database:" block
|
||||
if (!node.isMap()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Treat an empty map (Database: with no keys) as missing -> fallback
|
||||
return node.childrenMap() == null || node.childrenMap().isEmpty();
|
||||
}
|
||||
|
||||
public MysqlConfig getGlobalDataMysqlConfig() {
|
||||
return new MysqlConfigVelocity("GlobalData", config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register votifier listener once.
|
||||
*/
|
||||
private void initVotifierListenerIfNeeded() {
|
||||
try {
|
||||
Class.forName("com.vexsoftware.votifier.velocity.event.VotifierEvent");
|
||||
} catch (ClassNotFoundException e) {
|
||||
getVotingPluginProxy().setVotifierEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (getVotingPluginProxy().isVotifierEnabled()) {
|
||||
if (voteEventVelocity == null) {
|
||||
try {
|
||||
voteEventVelocity = new VoteEventVelocity(this);
|
||||
server.getEventManager().register(this, voteEventVelocity);
|
||||
} catch (Exception e) {
|
||||
getVotingPluginProxy().setVotifierEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelTasks() {
|
||||
try {
|
||||
if (voteCheckTask != null) {
|
||||
voteCheckTask.cancel();
|
||||
voteCheckTask = null;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
try {
|
||||
if (cacheSaveTask != null) {
|
||||
cacheSaveTask.cancel();
|
||||
cacheSaveTask = null;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleTasks() {
|
||||
voteCheckTask = server.getScheduler().buildTask(this, () -> {
|
||||
if (getVotingPluginProxy().getGlobalDataHandler() == null
|
||||
|| !getVotingPluginProxy().getGlobalDataHandler().isTimeChangedHappened()) {
|
||||
|
||||
for (String srv : getVotingPluginProxy().getVoteCacheHandler().getCachedVotesServers()) {
|
||||
getVotingPluginProxy().checkCachedVotes(srv);
|
||||
}
|
||||
|
||||
for (Player player : server.getAllPlayers()) {
|
||||
getVotingPluginProxy().checkOnlineVotes(player.getUsername(), player.getUniqueId().toString(),
|
||||
null);
|
||||
}
|
||||
}
|
||||
}).delay(120, TimeUnit.SECONDS).repeat(60, TimeUnit.SECONDS).schedule();
|
||||
|
||||
cacheSaveTask = server.getScheduler().buildTask(this, () -> {
|
||||
if (nonVotedPlayersCache != null) {
|
||||
debug("Checking nonvotedplayerscache...");
|
||||
getVotingPluginProxy().getNonVotedPlayersCache().check();
|
||||
}
|
||||
if (voteCacheFile != null) {
|
||||
voteCacheFile.save();
|
||||
}
|
||||
}).delay(1L, TimeUnit.MINUTES).repeat(60L, TimeUnit.MINUTES).schedule();
|
||||
}
|
||||
|
||||
private void runAsyncNow(Runnable runnable) {
|
||||
server.getScheduler().buildTask(this, runnable).schedule();
|
||||
}
|
||||
|
||||
private void handlePluginMessageBytes(byte[] data) {
|
||||
ByteArrayInputStream instream = new ByteArrayInputStream(data);
|
||||
DataInputStream in = new DataInputStream(instream);
|
||||
try {
|
||||
getVotingPluginProxy().onPluginMessageReceived(in);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void drainQueuedPluginMessages() {
|
||||
byte[] msg;
|
||||
while ((msg = queuedPluginMessages.poll()) != null) {
|
||||
handlePluginMessageBytes(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert old YAML cache files to JSON.
|
||||
*/
|
||||
private void convertYamlCachesIfPresent() {
|
||||
// vote cache
|
||||
File yamlVoteCacheFile = new File(dataDirectory.toFile(), "votecache.yml");
|
||||
if (yamlVoteCacheFile.exists()) {
|
||||
VelocityYMLFile yamlVoteCache = new VelocityYMLFile(yamlVoteCacheFile);
|
||||
voteCacheFile.setConf(yamlVoteCache.getData());
|
||||
yamlVoteCacheFile.renameTo(new File(dataDirectory.toFile(), "oldvotecache.yml"));
|
||||
voteCacheFile.save();
|
||||
}
|
||||
|
||||
// non-voted cache (FIX: write to nonVotedPlayersCache, not voteCacheFile)
|
||||
File yamlNonVotedFile = new File(dataDirectory.toFile(), "nonvotedplayerscache.yml");
|
||||
if (yamlNonVotedFile.exists()) {
|
||||
VelocityYMLFile yamlNonVoted = new VelocityYMLFile(yamlNonVotedFile);
|
||||
nonVotedPlayersCache.setConf(yamlNonVoted.getData());
|
||||
yamlNonVotedFile.renameTo(new File(dataDirectory.toFile(), "oldnonvotedplayerscache.yml"));
|
||||
nonVotedPlayersCache.save();
|
||||
}
|
||||
}
|
||||
|
||||
private MinecraftChannelIdentifier buildChannelIdentifier(String raw) {
|
||||
String[] parts = raw.split(":");
|
||||
return MinecraftChannelIdentifier.create(parts[0].toLowerCase(Locale.ROOT), parts[1].toLowerCase(Locale.ROOT));
|
||||
}
|
||||
}
|
||||
-296
@@ -1,296 +0,0 @@
|
||||
package com.bencodez.votingplugin.servicesites;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpClient.Redirect;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Handles service site name/value mappings.
|
||||
*
|
||||
* Primary runtime storage is serverdata.yml as a list of strings:
|
||||
*
|
||||
* ServiceSites:
|
||||
* - "PlanetMinecraft - planetminecraft.com"
|
||||
* - "TopG - topg.org"
|
||||
*
|
||||
* On startup this class:
|
||||
* 1. Loads the list from serverdata.yml into memory.
|
||||
* 2. Checks the web sources once for updates and overwrites
|
||||
* serverdata.yml if changed.
|
||||
*
|
||||
* Lookups are case-insensitive while preserving original casing.
|
||||
*/
|
||||
public class ServiceSiteHandler {
|
||||
|
||||
private static final String PRIMARY_URL = "https://raw.githubusercontent.com/wiki/BenCodez/VotingPlugin/Minecraft-Server-Lists.md";
|
||||
private static final String SECONDARY_URL = "https://wiki.bencodez.com/en/VotingPlugin/Minecraft-Server-Lists";
|
||||
private static final String TERTIARY_URL = "https://wiki-backup.bencodez.com/VotingPlugin/Minecraft-Server-Lists/";
|
||||
|
||||
private static final String SERVERDATA_PATH = "ServiceSites";
|
||||
|
||||
private static final Pattern LINE_PATTERN = Pattern.compile("^\\s*(.+?)\\s+-\\s+(.+?)\\s*$");
|
||||
private static final Pattern LI_PATTERN = Pattern.compile("<li>\\s*(.*?)\\s*</li>", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
private final VotingPluginMain plugin;
|
||||
|
||||
/**
|
||||
* Shared HTTP client.
|
||||
*/
|
||||
private final HttpClient httpClient = HttpClient.newBuilder()
|
||||
.followRedirects(Redirect.NORMAL)
|
||||
.connectTimeout(Duration.ofSeconds(5))
|
||||
.build();
|
||||
|
||||
private final AtomicReference<Map<String, String>> sitesRef = new AtomicReference<>(
|
||||
Collections.unmodifiableMap(new TreeMap<>(String.CASE_INSENSITIVE_ORDER)));
|
||||
|
||||
@Getter
|
||||
private Map<String, String> serviceSites = sitesRef.get();
|
||||
|
||||
public ServiceSiteHandler(VotingPluginMain plugin) {
|
||||
this.plugin = plugin;
|
||||
|
||||
loadFromServerData();
|
||||
refreshFromWebOnce(true);
|
||||
|
||||
for (Map.Entry<String, String> entry : sitesRef.get().entrySet()) {
|
||||
plugin.extraDebug(entry.getKey() + " - " + entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean contains(String service) {
|
||||
return service != null && sitesRef.get().containsKey(service);
|
||||
}
|
||||
|
||||
public String match(String service) {
|
||||
if (service == null) {
|
||||
return null;
|
||||
}
|
||||
return sitesRef.get().getOrDefault(service, service);
|
||||
}
|
||||
|
||||
public String matchReverse(String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
for (Map.Entry<String, String> e : sitesRef.get().entrySet()) {
|
||||
if (e.getValue().equalsIgnoreCase(value)) {
|
||||
return e.getKey();
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public void loadFromServerData() {
|
||||
List<String> lines = plugin.getServerData().getData().getStringList(SERVERDATA_PATH);
|
||||
|
||||
Map<String, String> parsed = parseLines(lines, true);
|
||||
|
||||
if (!parsed.isEmpty()) {
|
||||
setSites(parsed);
|
||||
plugin.extraDebug("Loaded " + parsed.size() + " service sites from serverdata.yml");
|
||||
}
|
||||
}
|
||||
|
||||
public void saveToServerData() {
|
||||
List<String> out = toListFormat(sitesRef.get());
|
||||
plugin.getServerData().getData().set(SERVERDATA_PATH, out);
|
||||
plugin.getServerData().saveData();
|
||||
}
|
||||
|
||||
public void refreshFromWebOnce(boolean logMore) {
|
||||
try {
|
||||
FetchResult res = fetchWithFallback(PRIMARY_URL, SECONDARY_URL, TERTIARY_URL);
|
||||
Map<String, String> parsed = parseFromWebBody(res.body, res.contentType);
|
||||
|
||||
if (parsed.isEmpty()) {
|
||||
if (logMore) {
|
||||
plugin.extraDebug("ServiceSite refresh returned 0 entries from " + res.sourceUrl);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (mapsEqual(sitesRef.get(), parsed)) {
|
||||
if (logMore) {
|
||||
plugin.extraDebug("ServiceSite refresh: no changes");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
setSites(parsed);
|
||||
saveToServerData();
|
||||
|
||||
plugin.extraDebug("ServiceSite refresh updated from " + res.sourceUrl + " (" + parsed.size() + " entries)");
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
plugin.debug(e);
|
||||
} catch (Exception e) {
|
||||
plugin.debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> toListFormat(Map<String, String> map) {
|
||||
List<String> out = new ArrayList<>(map.size());
|
||||
for (Map.Entry<String, String> e : map.entrySet()) {
|
||||
out.add(e.getKey() + " - " + e.getValue());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private void setSites(Map<String, String> newSites) {
|
||||
Map<String, String> unmodifiable = Collections.unmodifiableMap(
|
||||
new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER) {
|
||||
{
|
||||
putAll(newSites);
|
||||
}
|
||||
});
|
||||
sitesRef.set(unmodifiable);
|
||||
serviceSites = unmodifiable;
|
||||
}
|
||||
|
||||
private FetchResult fetchWithFallback(String... urls) throws IOException, InterruptedException {
|
||||
IOException last = null;
|
||||
for (String url : urls) {
|
||||
try {
|
||||
return fetch(url);
|
||||
} catch (IOException e) {
|
||||
last = e;
|
||||
plugin.extraDebug("ServiceSite fetch failed: " + url + " (" + e.getMessage() + ")");
|
||||
}
|
||||
}
|
||||
throw last != null ? last : new IOException("No URLs available");
|
||||
}
|
||||
|
||||
private FetchResult fetch(String urlStr) throws IOException, InterruptedException {
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(urlStr))
|
||||
.GET()
|
||||
.timeout(Duration.ofSeconds(7))
|
||||
.header("User-Agent", "VotingPlugin/ServiceSiteHandler")
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = httpClient.send(request,
|
||||
HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
||||
|
||||
int code = response.statusCode();
|
||||
if (code < 200 || code >= 300) {
|
||||
throw new IOException("HTTP " + code);
|
||||
}
|
||||
|
||||
String contentType = response.headers().firstValue("Content-Type").orElse(null);
|
||||
return new FetchResult(urlStr, response.body(), contentType);
|
||||
}
|
||||
|
||||
private Map<String, String> parseFromWebBody(String body, String contentType) {
|
||||
if (body == null || body.isEmpty()) {
|
||||
return new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
}
|
||||
|
||||
boolean isHtml = (contentType != null && contentType.toLowerCase(Locale.ROOT).contains("text/html"))
|
||||
|| body.contains("<html") || body.contains("<li");
|
||||
|
||||
if (isHtml) {
|
||||
List<String> lines = new ArrayList<>();
|
||||
Matcher m = LI_PATTERN.matcher(body);
|
||||
while (m.find()) {
|
||||
lines.add(htmlStrip(m.group(1)));
|
||||
}
|
||||
return parseLines(lines, false);
|
||||
}
|
||||
|
||||
String[] split = body.split("\n");
|
||||
List<String> lines = new ArrayList<>(split.length);
|
||||
for (String s : split) {
|
||||
lines.add(stripMarkup(s));
|
||||
}
|
||||
return parseLines(lines, false);
|
||||
}
|
||||
|
||||
private Map<String, String> parseLines(List<String> lines, boolean debugInvalid) {
|
||||
Map<String, String> parsed = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
for (String line : lines) {
|
||||
if (line == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String trimmed = line.trim();
|
||||
if (trimmed.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Matcher m = LINE_PATTERN.matcher(trimmed);
|
||||
if (!m.matches()) {
|
||||
if (debugInvalid) {
|
||||
plugin.extraDebug("Unknown ServiceSites entry: " + trimmed + ", may not be added yet");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
String key = m.group(1).trim();
|
||||
String val = m.group(2).trim();
|
||||
|
||||
if (!key.isEmpty() && !val.isEmpty()) {
|
||||
parsed.put(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
private static boolean mapsEqual(Map<String, String> a, Map<String, String> b) {
|
||||
if (a.size() != b.size()) {
|
||||
return false;
|
||||
}
|
||||
for (Map.Entry<String, String> e : a.entrySet()) {
|
||||
if (!e.getValue().equals(b.get(e.getKey()))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static String htmlStrip(String s) {
|
||||
String out = s.replaceAll("(?i)<br\\s*/?>", "\n");
|
||||
out = out.replaceAll("<[^>]+>", "");
|
||||
out = out.replace("&", "&").replace("<", "<").replace(">", ">");
|
||||
return out.trim();
|
||||
}
|
||||
|
||||
private static String stripMarkup(String s) {
|
||||
String out = s.replaceAll("\\[(.*?)]\\((.*?)\\)", "$1");
|
||||
out = out.replace("`", "");
|
||||
out = out.replaceAll("^\\s*[-*]\\s+", "");
|
||||
return out.trim();
|
||||
}
|
||||
|
||||
private static final class FetchResult {
|
||||
private final String sourceUrl;
|
||||
private final String body;
|
||||
private final String contentType;
|
||||
|
||||
private FetchResult(String sourceUrl, String body, String contentType) {
|
||||
this.sourceUrl = sourceUrl;
|
||||
this.body = body;
|
||||
this.contentType = contentType;
|
||||
}
|
||||
}
|
||||
}
|
||||
-134
@@ -1,134 +0,0 @@
|
||||
package com.bencodez.votingplugin.specialrewards;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.time.Duration;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
import com.bencodez.advancedcore.api.rewards.RewardBuilder;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.user.VotingPluginUser;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Periodically checks NameMC likes for the configured server and gives a
|
||||
* one-time reward to players who have not claimed it yet.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class NameMCLikeCheckerTask extends BukkitRunnable {
|
||||
|
||||
private VotingPluginMain plugin;
|
||||
|
||||
/**
|
||||
* Creates a new NameMC like checker task.
|
||||
*
|
||||
* @param plugin the plugin
|
||||
*/
|
||||
public NameMCLikeCheckerTask(VotingPluginMain plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (!plugin.getSpecialRewardsConfig().isNameMCLikeRewardEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String urlValue = plugin.getSpecialRewardsConfig().getNameMCLikeRewardUrl();
|
||||
if (urlValue == null || urlValue.trim().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<UUID> likedUuids = fetchLikedUuids(urlValue);
|
||||
if (likedUuids.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
for (UUID uuid : likedUuids) {
|
||||
processUuid(uuid);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a UUID returned by NameMC.
|
||||
*
|
||||
* @param uuid the uuid
|
||||
*/
|
||||
private void processUuid(UUID uuid) {
|
||||
if (uuid == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(uuid);
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (user.hasClaimedNameMCLikeReward()) {
|
||||
return;
|
||||
}
|
||||
|
||||
new RewardBuilder(plugin.getSpecialRewardsConfig().getData(),
|
||||
plugin.getSpecialRewardsConfig().getNameMCLikeRewardPath()).setOnline(user.isOnline())
|
||||
.withPlaceHolder("NameMCServer", plugin.getSpecialRewardsConfig().getNameMCLikeRewardUrl()).send(user);
|
||||
|
||||
user.setClaimedNameMCLikeReward(true);
|
||||
plugin.debug("Gave NameMC like reward to " + user.getPlayerName() + " (" + uuid + ")");
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all liked UUIDs from NameMC for the configured server.
|
||||
*
|
||||
* @param serverUrl the server URL or IP
|
||||
* @return set of liked UUIDs
|
||||
*/
|
||||
private Set<UUID> fetchLikedUuids(String serverUrl) {
|
||||
Set<UUID> uuids = new HashSet<>();
|
||||
|
||||
try {
|
||||
String url = "https://api.namemc.com/server/" + serverUrl + "/likes";
|
||||
|
||||
HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).timeout(Duration.ofSeconds(15))
|
||||
.header("Accept", "application/json").GET().build();
|
||||
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
if (response.statusCode() == 200) {
|
||||
JsonArray array = JsonParser.parseString(response.body()).getAsJsonArray();
|
||||
|
||||
for (JsonElement element : array) {
|
||||
try {
|
||||
UUID uuid = UUID.fromString(element.getAsString());
|
||||
uuids.add(uuid);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
plugin.debug("NameMC API returned status: " + response.statusCode());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
plugin.debug("Failed to fetch NameMC likes: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return uuids;
|
||||
}
|
||||
}
|
||||
-121
@@ -1,121 +0,0 @@
|
||||
package com.bencodez.votingplugin.specialrewards;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import com.bencodez.advancedcore.api.rewards.RewardBuilder;
|
||||
import com.bencodez.simpleapi.messages.MessageAPI;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.events.PlayerSpecialRewardEvent;
|
||||
import com.bencodez.votingplugin.events.SpecialRewardType;
|
||||
import com.bencodez.votingplugin.user.VotingPluginUser;
|
||||
|
||||
// TODO: Auto-generated Javadoc
|
||||
/**
|
||||
* The Class OtherVoteReward.
|
||||
*/
|
||||
public class SpecialRewards {
|
||||
|
||||
private VotingPluginMain plugin;
|
||||
|
||||
/**
|
||||
* Constructs a new SpecialRewards handler.
|
||||
*
|
||||
* @param plugin the main plugin instance
|
||||
*/
|
||||
public SpecialRewards(VotingPluginMain plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks and processes vote streak rewards for a user.
|
||||
*
|
||||
* @param voteUUID the vote UUID
|
||||
* @param user the voting user
|
||||
* @param type the streak type (day/week/month)
|
||||
* @param forceBungee whether to force bungee mode
|
||||
* @return true if a reward was given
|
||||
*/
|
||||
public boolean checkVoteStreak(UUID voteUUID, VotingPluginUser user, String type, boolean forceBungee) {
|
||||
boolean gotReward = false;
|
||||
|
||||
Set<String> streaks = plugin.getSpecialRewardsConfig().getVoteStreakVotes(type);
|
||||
for (String streak : streaks) {
|
||||
boolean multiple = false;
|
||||
if (streak.contains("-")) {
|
||||
// multiple
|
||||
multiple = true;
|
||||
|
||||
}
|
||||
String s = streak.replaceAll("-", "");
|
||||
|
||||
plugin.debug("Streak: " + streak + " multiple: " + multiple);
|
||||
if (MessageAPI.isInt(s)) {
|
||||
int streakRequired = Integer.parseInt(s);
|
||||
if (streakRequired != 0) {
|
||||
if (plugin.getSpecialRewardsConfig().getVoteStreakRewardEnabled(type, streak)
|
||||
&& plugin.getRewardHandler().hasRewards(plugin.getSpecialRewardsConfig().getData(),
|
||||
plugin.getSpecialRewardsConfig().getVoteStreakRewardsPath(type, "" + streak))) {
|
||||
int curStreak = 0;
|
||||
if (type.equalsIgnoreCase("day")) {
|
||||
curStreak = user.getDayVoteStreak();
|
||||
} else if (type.equalsIgnoreCase("week")) {
|
||||
curStreak = user.getWeekVoteStreak();
|
||||
} else if (type.equalsIgnoreCase("month")) {
|
||||
curStreak = user.getMonthVoteStreak();
|
||||
}
|
||||
if (!multiple) {
|
||||
if (curStreak == streakRequired) {
|
||||
giveVoteStreakReward(voteUUID, user, user.isOnline(), type, "" + streakRequired,
|
||||
curStreak, forceBungee);
|
||||
gotReward = true;
|
||||
plugin.debug(
|
||||
user.getPlayerName() + " got VoteStreak " + streakRequired + " for " + type);
|
||||
}
|
||||
} else {
|
||||
if (curStreak != 0 && curStreak % streakRequired == 0) {
|
||||
giveVoteStreakReward(voteUUID, user, user.isOnline(), type, streak, curStreak,
|
||||
forceBungee);
|
||||
gotReward = true;
|
||||
plugin.debug(
|
||||
user.getPlayerName() + " got VoteStreak " + streakRequired + "* for " + type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return gotReward;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives a vote streak reward to a user.
|
||||
*
|
||||
* @param voteUUID the vote UUID
|
||||
* @param user the voting user
|
||||
* @param online whether the user is online
|
||||
* @param type the streak type
|
||||
* @param string the streak value
|
||||
* @param votes the current streak count
|
||||
* @param forceBungee whether to force bungee mode
|
||||
*/
|
||||
public void giveVoteStreakReward(UUID voteUUID, VotingPluginUser user, boolean online, String type, String string,
|
||||
int votes, boolean forceBungee) {
|
||||
PlayerSpecialRewardEvent event = new PlayerSpecialRewardEvent(user,
|
||||
SpecialRewardType.VOTESTREAK.setType(type).setAmount(votes), voteUUID);
|
||||
Bukkit.getPluginManager().callEvent(event);
|
||||
|
||||
if (event.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
new RewardBuilder(plugin.getSpecialRewardsConfig().getData(),
|
||||
plugin.getSpecialRewardsConfig().getVoteStreakRewardsPath(type, string)).setOnline(online)
|
||||
.withPlaceHolder("Type", type).setServer(forceBungee).withPlaceHolder("Streak", "" + votes).send(user);
|
||||
}
|
||||
|
||||
}
|
||||
-282
@@ -1,282 +0,0 @@
|
||||
package com.bencodez.votingplugin.specialrewards.votemilestones;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
/**
|
||||
* Supports: - At: 0 - At: 50 - At: [20, 50, 100] - At: - 10..200 step 10 - 250
|
||||
*
|
||||
* Stateless evaluation: matches based solely on newTotal.
|
||||
*/
|
||||
public class AtMatcher {
|
||||
|
||||
private final boolean hasZero;
|
||||
private final Set<Long> exactTotals;
|
||||
|
||||
private AtMatcher(boolean hasZero, Set<Long> exactTotals) {
|
||||
this.hasZero = hasZero;
|
||||
this.exactTotals = exactTotals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a new total matches the criteria.
|
||||
*
|
||||
* @param newTotal the new total to check
|
||||
* @return true if the total matches
|
||||
*/
|
||||
public boolean matches(long newTotal) {
|
||||
// At: 0 => first vote in that period => newTotal == 1
|
||||
if (hasZero && newTotal == 1) {
|
||||
return true;
|
||||
}
|
||||
return exactTotals.contains(newTotal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Human-friendly debug string.
|
||||
*
|
||||
* Examples: - "0" - "20,50,100" - "0 + 10 values: [10,20,30,...,980,990,1000]"
|
||||
*
|
||||
* @return the debug string representation
|
||||
*/
|
||||
public String toDebugString() {
|
||||
return toDebugString(8, 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the matcher has zero.
|
||||
*
|
||||
* @return true if zero is included
|
||||
*/
|
||||
public boolean hasZero() {
|
||||
return hasZero;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the exact totals.
|
||||
*
|
||||
* @return an unmodifiable set of exact totals
|
||||
*/
|
||||
public Set<Long> getExactTotals() {
|
||||
return java.util.Collections.unmodifiableSet(exactTotals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a compact string for logs.
|
||||
*
|
||||
* @param maxValues the maximum number of values to display
|
||||
* @return the debug string representation
|
||||
*/
|
||||
public String toDebugString(int maxValues) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean first = true;
|
||||
|
||||
if (hasZero) {
|
||||
sb.append("0(first)");
|
||||
first = false;
|
||||
}
|
||||
|
||||
java.util.List<Long> vals = new java.util.ArrayList<>(exactTotals);
|
||||
java.util.Collections.sort(vals);
|
||||
|
||||
int shown = 0;
|
||||
for (Long v : vals) {
|
||||
if (shown >= maxValues) {
|
||||
sb.append(first ? "" : "|").append("...+").append(vals.size() - shown);
|
||||
break;
|
||||
}
|
||||
sb.append(first ? "" : "|").append(v);
|
||||
first = false;
|
||||
shown++;
|
||||
}
|
||||
|
||||
if (sb.length() == 0) {
|
||||
return "none";
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate debug string with head and tail values.
|
||||
*
|
||||
* @param head how many smallest values to show
|
||||
* @param tail how many largest values to show when truncating
|
||||
* @return the debug string representation
|
||||
*/
|
||||
public String toDebugString(int head, int tail) {
|
||||
head = Math.max(0, head);
|
||||
tail = Math.max(0, tail);
|
||||
|
||||
int size = exactTotals != null ? exactTotals.size() : 0;
|
||||
boolean zero = hasZero;
|
||||
|
||||
if (!zero && size == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// If it's small, print everything
|
||||
int inlineLimit = head + tail + 1;
|
||||
TreeSet<Long> sorted = new TreeSet<>();
|
||||
if (exactTotals != null) {
|
||||
sorted.addAll(exactTotals);
|
||||
}
|
||||
|
||||
List<String> parts = new ArrayList<>();
|
||||
if (zero) {
|
||||
parts.add("0");
|
||||
}
|
||||
|
||||
if (size == 0) {
|
||||
return String.join(",", parts);
|
||||
}
|
||||
|
||||
if (size <= inlineLimit) {
|
||||
for (Long v : sorted) {
|
||||
parts.add(String.valueOf(v));
|
||||
}
|
||||
return String.join(",", parts);
|
||||
}
|
||||
|
||||
// Truncated format: include count + head...tail
|
||||
List<Long> headVals = new ArrayList<>(head);
|
||||
List<Long> tailVals = new ArrayList<>(tail);
|
||||
|
||||
int i = 0;
|
||||
for (Long v : sorted) {
|
||||
if (i < head) {
|
||||
headVals.add(v);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
if (tail > 0) {
|
||||
List<Long> rev = new ArrayList<>(sorted);
|
||||
Collections.reverse(rev);
|
||||
for (int j = 0; j < tail && j < rev.size(); j++) {
|
||||
tailVals.add(rev.get(j));
|
||||
}
|
||||
Collections.reverse(tailVals);
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (zero) {
|
||||
sb.append("0 + ");
|
||||
}
|
||||
sb.append(size).append(" values: [");
|
||||
|
||||
boolean first = true;
|
||||
for (Long v : headVals) {
|
||||
if (!first)
|
||||
sb.append(",");
|
||||
first = false;
|
||||
sb.append(v);
|
||||
}
|
||||
|
||||
sb.append(",...");
|
||||
|
||||
for (Long v : tailVals) {
|
||||
sb.append(",").append(v);
|
||||
}
|
||||
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// make default toString() useful in logs
|
||||
return toDebugString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an AtMatcher from a configuration section.
|
||||
*
|
||||
* @param sec the configuration section
|
||||
* @param path the configuration path
|
||||
* @return the AtMatcher instance or null
|
||||
*/
|
||||
public static AtMatcher fromConfig(ConfigurationSection sec, String path) {
|
||||
if (sec == null || !sec.contains(path)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean hasZero = false;
|
||||
Set<Long> set = new HashSet<>();
|
||||
|
||||
Object raw = sec.get(path);
|
||||
|
||||
List<Object> entries = new ArrayList<>();
|
||||
if (raw instanceof List) {
|
||||
entries.addAll(sec.getList(path));
|
||||
} else {
|
||||
entries.add(raw);
|
||||
}
|
||||
|
||||
for (Object o : entries) {
|
||||
if (o == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (o instanceof Number) {
|
||||
long v = ((Number) o).longValue();
|
||||
if (v == 0) {
|
||||
hasZero = true;
|
||||
} else if (v > 0) {
|
||||
set.add(v);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
String s = String.valueOf(o).trim();
|
||||
if (s.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Allow "0" as string
|
||||
if (s.equals("0")) {
|
||||
hasZero = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Range: "A..B step S" or "A..B"
|
||||
RangeSpec r = RangeSpec.tryParse(s);
|
||||
if (r != null) {
|
||||
if (r.start == 0) {
|
||||
// if someone writes 0..something, treat 0 specially (first vote)
|
||||
hasZero = true;
|
||||
}
|
||||
for (long v : r.expand()) {
|
||||
if (v == 0) {
|
||||
hasZero = true;
|
||||
} else if (v > 0) {
|
||||
set.add(v);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fallback: parse as long
|
||||
try {
|
||||
long v = Long.parseLong(s);
|
||||
if (v == 0) {
|
||||
hasZero = true;
|
||||
} else if (v > 0) {
|
||||
set.add(v);
|
||||
}
|
||||
} catch (NumberFormatException ignored) {
|
||||
// ignore invalid entry
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasZero && set.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return new AtMatcher(hasZero, set);
|
||||
}
|
||||
}
|
||||
-114
@@ -1,114 +0,0 @@
|
||||
package com.bencodez.votingplugin.specialrewards.votemilestones;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Parses: - "10..200 step 10" - "10..200"
|
||||
*
|
||||
* Expands to discrete values (simple + predictable for server owners).
|
||||
*/
|
||||
public final class RangeSpec {
|
||||
|
||||
/** The start value of the range. */
|
||||
public final long start;
|
||||
/** The end value of the range. */
|
||||
public final long end;
|
||||
/** The step increment for the range. */
|
||||
public final long step;
|
||||
|
||||
/**
|
||||
* Constructs a new RangeSpec.
|
||||
*
|
||||
* @param start the start value
|
||||
* @param end the end value
|
||||
* @param step the step increment
|
||||
*/
|
||||
public RangeSpec(long start, long end, long step) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to parse a range specification string.
|
||||
*
|
||||
* @param s the string to parse
|
||||
* @return the parsed RangeSpec or null if parsing fails
|
||||
*/
|
||||
public static RangeSpec tryParse(String s) {
|
||||
// Normalize whitespace
|
||||
String str = s.trim().replaceAll("\\s+", " ");
|
||||
|
||||
if (!str.contains("..")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String[] parts = str.split("\\.\\.");
|
||||
if (parts.length != 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String left = parts[0].trim();
|
||||
String right = parts[1].trim();
|
||||
|
||||
long step = 1;
|
||||
|
||||
// Right side might contain "step"
|
||||
String[] rightParts = right.split(" step ");
|
||||
String endStr = rightParts[0].trim();
|
||||
if (rightParts.length == 2) {
|
||||
try {
|
||||
step = Long.parseLong(rightParts[1].trim());
|
||||
} catch (NumberFormatException ignored) {
|
||||
return null;
|
||||
}
|
||||
} else if (rightParts.length > 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
long start;
|
||||
long end;
|
||||
try {
|
||||
start = Long.parseLong(left);
|
||||
end = Long.parseLong(endStr);
|
||||
} catch (NumberFormatException ignored) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (step <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// allow reversed ranges by swapping
|
||||
if (end < start) {
|
||||
long tmp = start;
|
||||
start = end;
|
||||
end = tmp;
|
||||
}
|
||||
|
||||
return new RangeSpec(start, end, step);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands the range into a list of discrete values.
|
||||
*
|
||||
* @return the list of values in the range
|
||||
*/
|
||||
public List<Long> expand() {
|
||||
List<Long> out = new ArrayList<>();
|
||||
// Safety: avoid accidental huge expansions
|
||||
// If you want, expose this as a config value. For now keep sane.
|
||||
final int HARD_MAX = 250_000;
|
||||
|
||||
long count = 0;
|
||||
for (long v = start; v <= end; v += step) {
|
||||
out.add(v);
|
||||
count++;
|
||||
if (count >= HARD_MAX) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
-100
@@ -1,100 +0,0 @@
|
||||
package com.bencodez.votingplugin.specialrewards.votemilestones;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Represents a single VoteMilestone entry.
|
||||
*
|
||||
* rewardPath:
|
||||
* - Absolute path from SpecialRewards.yml root (e.g.
|
||||
* "VoteMilestones.MyMilestone.Rewards", "Cumulative.20.Rewards", "FirstVote")
|
||||
*/
|
||||
public class VoteMilestone {
|
||||
|
||||
@Getter
|
||||
private final String id;
|
||||
@Getter
|
||||
private final boolean enabled;
|
||||
@Getter
|
||||
private final VoteMilestoneTotal total;
|
||||
|
||||
@Getter
|
||||
private final AtMatcher atMatcher; // may be null
|
||||
@Getter
|
||||
private final Integer every; // may be null
|
||||
|
||||
@Getter
|
||||
private final String rewardPath;
|
||||
|
||||
@Getter
|
||||
private final VoteMilestoneGroupSelect groupSelect;
|
||||
|
||||
@Getter
|
||||
private final String groupKey;
|
||||
|
||||
@Getter
|
||||
private final VoteMilestoneLimit limit;
|
||||
|
||||
/**
|
||||
* Constructs a new VoteMilestone.
|
||||
*
|
||||
* @param id milestone id
|
||||
* @param enabled whether milestone is enabled
|
||||
* @param total total type to check
|
||||
* @param atMatcher matcher for specific values (may be null)
|
||||
* @param every interval for repeating milestone (may be null)
|
||||
* @param rewardPath path to rewards configuration
|
||||
* @param groupSelect group selection mode
|
||||
* @param groupKey group key
|
||||
* @param limit retrigger limit
|
||||
*/
|
||||
public VoteMilestone(String id, boolean enabled, VoteMilestoneTotal total, AtMatcher atMatcher, Integer every,
|
||||
String rewardPath, VoteMilestoneGroupSelect groupSelect, String groupKey, VoteMilestoneLimit limit) {
|
||||
this.id = id;
|
||||
this.enabled = enabled;
|
||||
this.total = total;
|
||||
this.atMatcher = atMatcher;
|
||||
this.every = every;
|
||||
this.rewardPath = rewardPath;
|
||||
this.groupSelect = groupSelect;
|
||||
this.groupKey = groupKey;
|
||||
this.limit = (limit == null ? VoteMilestoneLimit.none() : limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this milestone has an at matcher for debugging.
|
||||
*
|
||||
* @return true if at matcher exists
|
||||
*/
|
||||
public boolean hasAtMatcherDebug() {
|
||||
return atMatcher != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets debug string representation of the at matcher.
|
||||
*
|
||||
* @param maxValues maximum number of values to include
|
||||
* @return debug string or "none"
|
||||
*/
|
||||
public String getAtMatcherDebugString(int maxValues) {
|
||||
return atMatcher == null ? "none" : atMatcher.toDebugString(maxValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this milestone matches the given total.
|
||||
*
|
||||
* @param newTotal total to check against
|
||||
* @return true if milestone matches
|
||||
*/
|
||||
public boolean matches(long newTotal) {
|
||||
if (atMatcher != null && atMatcher.matches(newTotal)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (every != null && every > 0) {
|
||||
return newTotal > 0 && (newTotal % every == 0);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
package com.bencodez.votingplugin.specialrewards.votemilestones;
|
||||
|
||||
import org.bukkit.configuration.MemoryConfiguration;
|
||||
|
||||
final class VoteMilestoneFactory {
|
||||
|
||||
private VoteMilestoneFactory() {
|
||||
}
|
||||
|
||||
static VoteMilestone atZero(String id, boolean enabled, VoteMilestoneTotal total, String rewardPath,
|
||||
VoteMilestoneGroupSelect groupSelect, String voteMilestoneGroupKey, VoteMilestoneLimit limit) {
|
||||
MemoryConfiguration tmp = new MemoryConfiguration();
|
||||
tmp.set("At", 1);
|
||||
|
||||
AtMatcher at = AtMatcher.fromConfig(tmp, "At");
|
||||
return new VoteMilestone(id, enabled, total, at, null, rewardPath, groupSelect, voteMilestoneGroupKey, limit);
|
||||
}
|
||||
|
||||
static VoteMilestone atExact(String id, boolean enabled, VoteMilestoneTotal total, long atValue, String rewardPath,
|
||||
VoteMilestoneGroupSelect groupSelect, String voteMilestoneGroupKey, VoteMilestoneLimit limit) {
|
||||
MemoryConfiguration tmp = new MemoryConfiguration();
|
||||
tmp.set("At", atValue);
|
||||
|
||||
AtMatcher at = AtMatcher.fromConfig(tmp, "At");
|
||||
return new VoteMilestone(id, enabled, total, at, null, rewardPath, groupSelect, voteMilestoneGroupKey, limit);
|
||||
}
|
||||
|
||||
static VoteMilestone every(String id, boolean enabled, VoteMilestoneTotal total, int every, String rewardPath,
|
||||
VoteMilestoneGroupSelect groupSelect, String voteMilestoneGroupKey, VoteMilestoneLimit limit) {
|
||||
return new VoteMilestone(id, enabled, total, null, every, rewardPath, groupSelect, voteMilestoneGroupKey, limit);
|
||||
}
|
||||
}
|
||||
-115
@@ -1,115 +0,0 @@
|
||||
package com.bencodez.votingplugin.specialrewards.votemilestones;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
/**
|
||||
* Utility class for parsing vote milestone group configuration.
|
||||
*/
|
||||
public final class VoteMilestoneGroupParser {
|
||||
|
||||
private VoteMilestoneGroupParser() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses group selection modes from configuration.
|
||||
*
|
||||
* @param root root configuration section
|
||||
* @return map of group id to selection mode
|
||||
*/
|
||||
public static Map<String, VoteMilestoneGroupSelect> parseGroups(ConfigurationSection root) {
|
||||
HashMap<String, VoteMilestoneGroupSelect> out = new HashMap<>();
|
||||
|
||||
if (root == null) {
|
||||
out.put("default", VoteMilestoneGroupSelect.ALL);
|
||||
return out;
|
||||
}
|
||||
|
||||
ConfigurationSection opts = root.getConfigurationSection("VoteMilestonesOptions");
|
||||
if (opts == null) {
|
||||
out.put("default", VoteMilestoneGroupSelect.ALL);
|
||||
return out;
|
||||
}
|
||||
|
||||
ConfigurationSection groups = opts.getConfigurationSection("Groups");
|
||||
if (groups == null) {
|
||||
out.put("default", VoteMilestoneGroupSelect.ALL);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Default mode for groups not explicitly listed
|
||||
VoteMilestoneGroupSelect defaultMode =
|
||||
parseSelect(groups.getString("Default", "ALL"), VoteMilestoneGroupSelect.ALL);
|
||||
|
||||
out.put("default", defaultMode);
|
||||
|
||||
// Parse other group mappings
|
||||
for (String key : groups.getKeys(false)) {
|
||||
if (key == null) {
|
||||
continue;
|
||||
}
|
||||
String groupId = key.trim();
|
||||
if (groupId.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (groupId.equalsIgnoreCase("Default")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String raw = groups.getString(key, null);
|
||||
VoteMilestoneGroupSelect mode = parseSelect(raw, defaultMode);
|
||||
|
||||
// If you want case-insensitive ids everywhere, normalize here:
|
||||
// groupId = groupId.toLowerCase();
|
||||
|
||||
out.put(groupId, mode);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse selection mode with optional aliases. Falls back to fallback.
|
||||
*/
|
||||
private static VoteMilestoneGroupSelect parseSelect(String raw, VoteMilestoneGroupSelect fallback) {
|
||||
if (raw == null) {
|
||||
return fallback;
|
||||
}
|
||||
String v = raw.trim().toUpperCase();
|
||||
if (v.isEmpty()) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
// Optional aliases
|
||||
switch (v) {
|
||||
case "ALL":
|
||||
case "ANY":
|
||||
case "EVERY":
|
||||
case "0":
|
||||
return VoteMilestoneGroupSelect.ALL;
|
||||
|
||||
case "HIGHEST":
|
||||
case "MAX":
|
||||
case "TOP":
|
||||
case "2":
|
||||
return VoteMilestoneGroupSelect.HIGHEST;
|
||||
|
||||
case "FIRST":
|
||||
case "ONE":
|
||||
case "EARLIEST":
|
||||
case "1":
|
||||
return VoteMilestoneGroupSelect.FIRST;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
return VoteMilestoneGroupSelect.valueOf(v);
|
||||
} catch (Exception e) {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
}
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
package com.bencodez.votingplugin.specialrewards.votemilestones;
|
||||
|
||||
/**
|
||||
* Enum for selecting which milestones to trigger when multiple match in a group.
|
||||
*/
|
||||
public enum VoteMilestoneGroupSelect {
|
||||
/**
|
||||
* Trigger every matching milestone in the group.
|
||||
*/
|
||||
ALL,
|
||||
|
||||
/**
|
||||
* If multiple milestones in the group match, only trigger the highest
|
||||
* "priority" (largest AT value, else largest EVERY).
|
||||
*/
|
||||
HIGHEST,
|
||||
|
||||
/**
|
||||
* If multiple match, only trigger the first one encountered (stable ordering).
|
||||
*/
|
||||
FIRST;
|
||||
}
|
||||
-243
@@ -1,243 +0,0 @@
|
||||
package com.bencodez.votingplugin.specialrewards.votemilestones;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
import com.bencodez.simpleapi.time.ParsedDuration;
|
||||
|
||||
/**
|
||||
* Per-milestone retrigger limiter.
|
||||
*
|
||||
* Stored data is lastTriggeredMs (epoch millis).
|
||||
*/
|
||||
public final class VoteMilestoneLimit {
|
||||
|
||||
/**
|
||||
* Limit types for milestone retriggering.
|
||||
*/
|
||||
public enum Type {
|
||||
/**
|
||||
* No limit.
|
||||
*/
|
||||
NONE,
|
||||
/**
|
||||
* Once per calendar day window.
|
||||
*/
|
||||
WINDOW_DAY,
|
||||
/**
|
||||
* Once per week window.
|
||||
*/
|
||||
WINDOW_WEEK,
|
||||
/**
|
||||
* Once per month window.
|
||||
*/
|
||||
WINDOW_MONTH,
|
||||
/**
|
||||
* Fixed cooldown duration.
|
||||
*/
|
||||
COOLDOWN
|
||||
}
|
||||
|
||||
private final Type type;
|
||||
private final long cooldownMillis; // fixed millis cooldown
|
||||
|
||||
/**
|
||||
* Constructs a new VoteMilestoneLimit.
|
||||
*
|
||||
* @param type limit type
|
||||
* @param cooldownMillis cooldown duration in milliseconds
|
||||
*/
|
||||
public VoteMilestoneLimit(Type type, long cooldownMillis) {
|
||||
this.type = type == null ? Type.NONE : type;
|
||||
this.cooldownMillis = Math.max(0L, cooldownMillis);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a VoteMilestoneLimit with no restrictions.
|
||||
*
|
||||
* @return limit with NONE type
|
||||
*/
|
||||
public static VoteMilestoneLimit none() {
|
||||
return new VoteMilestoneLimit(Type.NONE, 0L);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the limit type.
|
||||
*
|
||||
* @return limit type
|
||||
*/
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cooldown duration in milliseconds.
|
||||
*
|
||||
* @return cooldown milliseconds
|
||||
*/
|
||||
public long getCooldownMillis() {
|
||||
return cooldownMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the limit is enabled.
|
||||
*
|
||||
* @return true if limit is enabled
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return type != Type.NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a VoteMilestoneLimit from configuration.
|
||||
*
|
||||
* @param milestoneSection milestone configuration section
|
||||
* @return parsed limit or none()
|
||||
*/
|
||||
public static VoteMilestoneLimit fromConfig(ConfigurationSection milestoneSection) {
|
||||
if (milestoneSection == null) {
|
||||
return none();
|
||||
}
|
||||
|
||||
ConfigurationSection sec = milestoneSection.getConfigurationSection("Limit");
|
||||
if (sec == null) {
|
||||
return none();
|
||||
}
|
||||
|
||||
String typeStr = sec.getString("Type", "NONE");
|
||||
Type type = parseType(typeStr);
|
||||
|
||||
if (type == Type.COOLDOWN) {
|
||||
String durRaw = sec.getString("Duration", "");
|
||||
|
||||
// Old behavior: number-only means MINUTES
|
||||
ParsedDuration pd = ParsedDuration.parse(durRaw, TimeUnit.MINUTES);
|
||||
|
||||
// Optional legacy support:
|
||||
// if no duration provided but Minutes exists
|
||||
if (pd.isEmpty()) {
|
||||
long minutes = sec.getLong("Minutes", 0L);
|
||||
if (minutes > 0) {
|
||||
return new VoteMilestoneLimit(Type.COOLDOWN, minutes * 60_000L);
|
||||
}
|
||||
return none(); // cooldown with no duration => treat as NONE
|
||||
}
|
||||
|
||||
return new VoteMilestoneLimit(Type.COOLDOWN, pd.getMillis());
|
||||
}
|
||||
|
||||
// window types or none
|
||||
return new VoteMilestoneLimit(type, 0L);
|
||||
}
|
||||
|
||||
private static Type parseType(String raw) {
|
||||
if (raw == null) {
|
||||
return Type.NONE;
|
||||
}
|
||||
String v = raw.trim().toUpperCase();
|
||||
if (v.isEmpty()) {
|
||||
return Type.NONE;
|
||||
}
|
||||
|
||||
// Allow friendly aliases
|
||||
switch (v) {
|
||||
case "DAY":
|
||||
case "DAILY":
|
||||
case "WINDOW_DAILY":
|
||||
return Type.WINDOW_DAY;
|
||||
case "WEEK":
|
||||
case "WEEKLY":
|
||||
return Type.WINDOW_WEEK;
|
||||
case "MONTH":
|
||||
case "MONTHLY":
|
||||
return Type.WINDOW_MONTH;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
return Type.valueOf(v);
|
||||
} catch (Exception ignored) {
|
||||
return Type.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------- enforcement ---------------- */
|
||||
|
||||
/**
|
||||
* Checks if the limit allows retriggering at the given time.
|
||||
*
|
||||
* @param lastTriggeredMs last triggered time in milliseconds
|
||||
* @param nowMs current time in milliseconds
|
||||
* @param zone time zone for window calculations
|
||||
* @return true if retriggering is allowed
|
||||
*/
|
||||
public boolean allows(long lastTriggeredMs, long nowMs, ZoneId zone) {
|
||||
if (type == Type.NONE) {
|
||||
return true;
|
||||
}
|
||||
if (lastTriggeredMs <= 0) {
|
||||
return true;
|
||||
}
|
||||
if (zone == null) {
|
||||
zone = ZoneId.systemDefault();
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case WINDOW_DAY:
|
||||
return !sameDay(lastTriggeredMs, nowMs, zone);
|
||||
case WINDOW_WEEK:
|
||||
return !sameWeek(lastTriggeredMs, nowMs, zone);
|
||||
case WINDOW_MONTH:
|
||||
return !sameMonth(lastTriggeredMs, nowMs, zone);
|
||||
case COOLDOWN:
|
||||
return nowMs >= nextAllowedMs(lastTriggeredMs);
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the next allowed trigger time in milliseconds.
|
||||
*
|
||||
* @param lastTriggeredMs last triggered time in milliseconds
|
||||
* @return next allowed time or 0 if not applicable
|
||||
*/
|
||||
public long nextAllowedMs(long lastTriggeredMs) {
|
||||
if (type != Type.COOLDOWN || lastTriggeredMs <= 0) {
|
||||
return 0L;
|
||||
}
|
||||
return lastTriggeredMs + cooldownMillis;
|
||||
}
|
||||
|
||||
private static boolean sameDay(long aMs, long bMs, ZoneId zone) {
|
||||
ZonedDateTime a = Instant.ofEpochMilli(aMs).atZone(zone);
|
||||
ZonedDateTime b = Instant.ofEpochMilli(bMs).atZone(zone);
|
||||
return a.toLocalDate().equals(b.toLocalDate());
|
||||
}
|
||||
|
||||
private static boolean sameMonth(long aMs, long bMs, ZoneId zone) {
|
||||
ZonedDateTime a = Instant.ofEpochMilli(aMs).atZone(zone);
|
||||
ZonedDateTime b = Instant.ofEpochMilli(bMs).atZone(zone);
|
||||
return a.getYear() == b.getYear() && a.getMonthValue() == b.getMonthValue();
|
||||
}
|
||||
|
||||
private static boolean sameWeek(long aMs, long bMs, ZoneId zone) {
|
||||
ZonedDateTime a = Instant.ofEpochMilli(aMs).atZone(zone);
|
||||
ZonedDateTime b = Instant.ofEpochMilli(bMs).atZone(zone);
|
||||
|
||||
// ISO week fields is a sane default
|
||||
java.time.temporal.WeekFields wf = java.time.temporal.WeekFields.ISO;
|
||||
int aw = a.get(wf.weekOfWeekBasedYear());
|
||||
int ay = a.get(wf.weekBasedYear());
|
||||
|
||||
int bw = b.get(wf.weekOfWeekBasedYear());
|
||||
int by = b.get(wf.weekBasedYear());
|
||||
|
||||
return aw == bw && ay == by;
|
||||
}
|
||||
}
|
||||
-156
@@ -1,156 +0,0 @@
|
||||
package com.bencodez.votingplugin.specialrewards.votemilestones;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import com.bencodez.votingplugin.proxy.VoteTotalsSnapshot;
|
||||
import com.bencodez.votingplugin.topvoter.TopVoter;
|
||||
import com.bencodez.votingplugin.user.VotingPluginUser;
|
||||
|
||||
/**
|
||||
* Defines what numeric value a VoteMilestone evaluates against.
|
||||
*/
|
||||
public enum VoteMilestoneTotal {
|
||||
|
||||
// ---- Vote totals (TopVoter-based) ----
|
||||
/** All-time votes total. */
|
||||
ALLTIME_VOTES {
|
||||
@Override
|
||||
public long getValue(VotingPluginUser user, VoteTotalsSnapshot bungeeMessageData) {
|
||||
if (bungeeMessageData != null) {
|
||||
return bungeeMessageData.getAllTimeTotal();
|
||||
}
|
||||
return user.getTotal(TopVoter.AllTime);
|
||||
}
|
||||
},
|
||||
|
||||
/** Daily votes total. */
|
||||
DAILY_VOTES {
|
||||
@Override
|
||||
public long getValue(VotingPluginUser user, VoteTotalsSnapshot bungeeMessageData) {
|
||||
if (bungeeMessageData != null) {
|
||||
return bungeeMessageData.getDailyTotal();
|
||||
}
|
||||
return user.getTotal(TopVoter.Daily);
|
||||
}
|
||||
},
|
||||
|
||||
/** Weekly votes total. */
|
||||
WEEKLY_VOTES {
|
||||
@Override
|
||||
public long getValue(VotingPluginUser user, VoteTotalsSnapshot bungeeMessageData) {
|
||||
if (bungeeMessageData != null) {
|
||||
return bungeeMessageData.getWeeklyTotal();
|
||||
}
|
||||
return user.getTotal(TopVoter.Weekly);
|
||||
}
|
||||
},
|
||||
|
||||
/** Monthly votes total. */
|
||||
MONTHLY_VOTES {
|
||||
@Override
|
||||
public long getValue(VotingPluginUser user, VoteTotalsSnapshot bungeeMessageData) {
|
||||
if (bungeeMessageData != null) {
|
||||
return bungeeMessageData.getMonthTotal();
|
||||
}
|
||||
return user.getTotal(TopVoter.Monthly);
|
||||
}
|
||||
},
|
||||
|
||||
// ---- Global points (single value only) ----
|
||||
/** Player points total. */
|
||||
POINTS {
|
||||
@Override
|
||||
public long getValue(VotingPluginUser user, VoteTotalsSnapshot bungeeMessageData) {
|
||||
if (bungeeMessageData != null) {
|
||||
return bungeeMessageData.getPoints();
|
||||
}
|
||||
return user.getPoints();
|
||||
}
|
||||
},
|
||||
/** All unique sites voted today. */
|
||||
ALLSITES_TODAY {
|
||||
@Override
|
||||
public long getValue(VotingPluginUser user, VoteTotalsSnapshot bungeeMessageData) {
|
||||
return user.getUniqueVoteSitesToday();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the value for this total type.
|
||||
*
|
||||
* @param user the voting plugin user
|
||||
* @param bungeeMessageData the bungee message data snapshot
|
||||
* @return the total value
|
||||
*/
|
||||
public abstract long getValue(VotingPluginUser user, VoteTotalsSnapshot bungeeMessageData);
|
||||
|
||||
/**
|
||||
* Parse config values flexibly.
|
||||
*
|
||||
* Supported examples: - ALLTIME_VOTES - AllTime - ALLTIME - Daily / Weekly /
|
||||
* Monthly - Points / VotePoints - Legacy_Milestone_Count
|
||||
*
|
||||
* @param input the input string to parse
|
||||
* @return the parsed VoteMilestoneTotal, or ALLTIME_VOTES as default
|
||||
*/
|
||||
public static VoteMilestoneTotal parse(String input) {
|
||||
if (input == null || input.trim().isEmpty()) {
|
||||
return ALLTIME_VOTES;
|
||||
}
|
||||
|
||||
String raw = input.trim();
|
||||
String key = raw.toUpperCase(Locale.ROOT);
|
||||
|
||||
// 1) Exact enum match (fast path)
|
||||
try {
|
||||
return VoteMilestoneTotal.valueOf(key);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
|
||||
// 2) Friendly / legacy aliases
|
||||
switch (key) {
|
||||
// Votes (AllTime)
|
||||
case "ALLTIME":
|
||||
case "ALL_TIME":
|
||||
case "ALLTIMEVOTES":
|
||||
case "ALLTIMEVOTE":
|
||||
case "VOTES":
|
||||
return ALLTIME_VOTES;
|
||||
|
||||
// Votes (Daily)
|
||||
case "DAILY":
|
||||
case "DAY":
|
||||
case "DAILYVOTES":
|
||||
case "DAILYVOTE":
|
||||
return DAILY_VOTES;
|
||||
|
||||
// Votes (Weekly)
|
||||
case "WEEKLY":
|
||||
case "WEEK":
|
||||
case "WEEKLYVOTES":
|
||||
case "WEEKLYVOTE":
|
||||
return WEEKLY_VOTES;
|
||||
|
||||
// Votes (Monthly)
|
||||
case "MONTHLY":
|
||||
case "MONTH":
|
||||
case "MONTHLYVOTES":
|
||||
case "MONTHLYVOTE":
|
||||
return MONTHLY_VOTES;
|
||||
|
||||
// Points
|
||||
case "POINT":
|
||||
case "POINTS":
|
||||
case "VOTEPOINT":
|
||||
case "VOTEPOINTS":
|
||||
return POINTS;
|
||||
|
||||
case "SITES_TODAY":
|
||||
case "ALLSITES_TODAY":
|
||||
return ALLSITES_TODAY;
|
||||
|
||||
default:
|
||||
return ALLTIME_VOTES;
|
||||
}
|
||||
}
|
||||
}
|
||||
-90
@@ -1,90 +0,0 @@
|
||||
package com.bencodez.votingplugin.specialrewards.votemilestones;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
|
||||
/**
|
||||
* Configuration for vote milestones.
|
||||
*/
|
||||
public class VoteMilestonesConfig {
|
||||
|
||||
private final Map<String, VoteMilestone> milestones;
|
||||
|
||||
private VoteMilestonesConfig(Map<String, VoteMilestone> milestones) {
|
||||
this.milestones = milestones;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the map of milestones.
|
||||
*
|
||||
* @return the milestones map
|
||||
*/
|
||||
public Map<String, VoteMilestone> getMilestones() {
|
||||
return milestones;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads vote milestones configuration from the configuration section.
|
||||
*
|
||||
* @param plugin the main plugin instance
|
||||
* @param manager the vote milestones manager
|
||||
* @param root the root configuration section
|
||||
* @return the loaded vote milestones configuration
|
||||
*/
|
||||
public static VoteMilestonesConfig load(VotingPluginMain plugin, VoteMilestonesManager manager,
|
||||
ConfigurationSection root) {
|
||||
Map<String, VoteMilestone> out = new LinkedHashMap<>();
|
||||
|
||||
// Load new format entries (VoteMilestones section)
|
||||
ConfigurationSection voteMilestonesRoot = root.getConfigurationSection("VoteMilestones");
|
||||
if (voteMilestonesRoot != null) {
|
||||
for (String id : voteMilestonesRoot.getKeys(false)) {
|
||||
ConfigurationSection sec = voteMilestonesRoot.getConfigurationSection(id);
|
||||
if (sec == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean enabled = sec.getBoolean("Enabled", true);
|
||||
|
||||
String totalStr = sec.getString("Total", "ALLTIME_VOTES");
|
||||
VoteMilestoneTotal total = VoteMilestoneTotal.parse(totalStr);
|
||||
|
||||
String groupSelectStr = sec.getString("Group", "Default");
|
||||
|
||||
AtMatcher at = AtMatcher.fromConfig(sec, "At");
|
||||
Integer every = sec.contains("Every") ? sec.getInt("Every") : null;
|
||||
|
||||
// NEW: per-milestone limit settings (default NONE)
|
||||
VoteMilestoneLimit limit = VoteMilestoneLimit.fromConfig(sec);
|
||||
|
||||
// New config: reward container is the milestone section itself
|
||||
VoteMilestone m = new VoteMilestone(id, enabled, total, at, every, "VoteMilestones." + id + ".Rewards",
|
||||
manager.getGroupModes().getOrDefault(groupSelectStr, VoteMilestoneGroupSelect.ALL),
|
||||
groupSelectStr, limit);
|
||||
|
||||
out.put(id, m);
|
||||
}
|
||||
}
|
||||
|
||||
// Merge legacy compiled milestones (new format wins on ID collision)
|
||||
ConfigurationSection legacyRoot = plugin.getSpecialRewardsConfig().getData();
|
||||
int totalSites = plugin.getVoteSiteManager().getVoteSitesEnabled() != null
|
||||
? plugin.getVoteSiteManager().getVoteSitesEnabled().size()
|
||||
: 0;
|
||||
|
||||
Map<String, VoteMilestone> legacy = VoteMilestonesMigrator.compileLegacy(legacyRoot, totalSites,
|
||||
plugin.getSpecialRewardsConfig().getData().getBoolean("OnlyOneCumulative", false),
|
||||
plugin.getSpecialRewardsConfig().getData().getBoolean("ResetMilestonesMonthly", false));
|
||||
plugin.debug(plugin.getSpecialRewardsConfig().getData().getBoolean("OnlyOneCumulative", false)
|
||||
+ " Only one cumulative");
|
||||
for (Map.Entry<String, VoteMilestone> e : legacy.entrySet()) {
|
||||
out.putIfAbsent(e.getKey(), e.getValue());
|
||||
}
|
||||
|
||||
return new VoteMilestonesConfig(out);
|
||||
}
|
||||
}
|
||||
-1493
File diff suppressed because it is too large.
Load diff
-205
@@ -1,205 +0,0 @@
|
||||
package com.bencodez.votingplugin.specialrewards.votemilestones;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
/**
|
||||
* Legacy migration: - Uses SpecialRewards.yml root as the reward container so
|
||||
* reward paths can be absolute. - FirstVote / FirstVoteToday: rewards are on
|
||||
* the section itself (no "Rewards" child) - Cumulative / MileStones: rewards
|
||||
* are under "...Rewards" - Totals are mapped into VoteMilestoneTotal (votes +
|
||||
* points + legacy counter)
|
||||
*
|
||||
* Adds per-milestone Limit support (default NONE).
|
||||
*/
|
||||
public final class VoteMilestonesMigrator {
|
||||
|
||||
private VoteMilestonesMigrator() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles legacy milestone configuration into the new format.
|
||||
*
|
||||
* @param root the configuration section containing legacy milestones
|
||||
* @param totalSites the total number of vote sites
|
||||
* @param onlyOneCumulative whether only one cumulative milestone should trigger
|
||||
* @param resetMilestonesMonthly whether milestones reset monthly
|
||||
* @return map of milestone names to VoteMilestone objects
|
||||
*/
|
||||
public static Map<String, VoteMilestone> compileLegacy(ConfigurationSection root, int totalSites,
|
||||
boolean onlyOneCumulative, boolean resetMilestonesMonthly) {
|
||||
Map<String, VoteMilestone> out = new LinkedHashMap<>();
|
||||
if (root == null) {
|
||||
return out;
|
||||
}
|
||||
|
||||
compileFirstVote(root, out);
|
||||
compileFirstVoteToday(root, out);
|
||||
compileCumulative(root, out, onlyOneCumulative);
|
||||
compileMilestones(root, out, resetMilestonesMonthly);
|
||||
|
||||
compileAllSites(root, out, totalSites);
|
||||
compileAlmostAllSites(root, out, totalSites);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
private static void compileFirstVote(ConfigurationSection root, Map<String, VoteMilestone> out) {
|
||||
ConfigurationSection sec = root.getConfigurationSection("FirstVote");
|
||||
if (sec == null || isDisabledObject(sec)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// rewards are on FirstVote section itself (legacy)
|
||||
VoteMilestone vm = VoteMilestoneFactory.atZero("Legacy_FirstVote", true, VoteMilestoneTotal.ALLTIME_VOTES,
|
||||
"FirstVote", VoteMilestoneGroupSelect.ALL, "Default", VoteMilestoneLimit.none());
|
||||
out.put(vm.getId(), vm);
|
||||
}
|
||||
|
||||
private static void compileFirstVoteToday(ConfigurationSection root, Map<String, VoteMilestone> out) {
|
||||
ConfigurationSection sec = root.getConfigurationSection("FirstVoteToday");
|
||||
if (sec == null || isDisabledObject(sec)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// rewards are on FirstVoteToday section itself (legacy)
|
||||
VoteMilestone vm = VoteMilestoneFactory.atZero("Legacy_FirstVoteToday", true, VoteMilestoneTotal.DAILY_VOTES,
|
||||
"FirstVoteToday", VoteMilestoneGroupSelect.ALL, "Default", VoteMilestoneLimit.none());
|
||||
out.put(vm.getId(), vm);
|
||||
}
|
||||
|
||||
private static void compileCumulative(ConfigurationSection root, Map<String, VoteMilestone> out,
|
||||
boolean onlyOneCumulative) {
|
||||
VoteMilestoneGroupSelect groupSelect = onlyOneCumulative ? VoteMilestoneGroupSelect.HIGHEST
|
||||
: VoteMilestoneGroupSelect.ALL;
|
||||
ConfigurationSection sec = root.getConfigurationSection("Cumulative");
|
||||
if (sec == null || isDisabledObject(sec)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (String key : sec.getKeys(false)) {
|
||||
ConfigurationSection c = sec.getConfigurationSection(key);
|
||||
if (c == null) {
|
||||
continue;
|
||||
}
|
||||
if (!c.getBoolean("Enabled", true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int every;
|
||||
try {
|
||||
every = Integer.parseInt(key.replace("'", "").trim());
|
||||
} catch (Exception e) {
|
||||
continue;
|
||||
}
|
||||
if (every <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
VoteMilestoneTotal total = mapTotalToUse(c.getString("TotalToUse", "AllTime"));
|
||||
|
||||
// rewards are under Cumulative.<key>.Rewards in legacy config
|
||||
VoteMilestone vm = VoteMilestoneFactory.every("Legacy_Cumulative_" + every + "_" + total.name(), true,
|
||||
total, every, "Cumulative." + key + ".Rewards", groupSelect, "Legacy_Cumulative",
|
||||
VoteMilestoneLimit.none());
|
||||
|
||||
out.put(vm.getId(), vm);
|
||||
}
|
||||
}
|
||||
|
||||
private static void compileMilestones(ConfigurationSection root, Map<String, VoteMilestone> out,
|
||||
boolean resetMonthly) {
|
||||
ConfigurationSection sec = root.getConfigurationSection("MileStones");
|
||||
if (sec == null || isDisabledObject(sec)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (String key : sec.getKeys(false)) {
|
||||
ConfigurationSection m = sec.getConfigurationSection(key);
|
||||
if (m == null) {
|
||||
continue;
|
||||
}
|
||||
if (!m.getBoolean("Enabled", true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
long at;
|
||||
try {
|
||||
at = Long.parseLong(key.replace("'", "").trim());
|
||||
} catch (Exception e) {
|
||||
continue;
|
||||
}
|
||||
if (at <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if resetMonthy is true use month total
|
||||
VoteMilestoneTotal totalToUse = resetMonthly ? VoteMilestoneTotal.MONTHLY_VOTES
|
||||
: VoteMilestoneTotal.ALLTIME_VOTES;
|
||||
|
||||
// rewards are under MileStones.<key>.Rewards in legacy config
|
||||
VoteMilestone vm = VoteMilestoneFactory.atExact("Legacy_Milestone_" + at + "_ALLTIME", true, totalToUse, at,
|
||||
"MileStones." + key + ".Rewards", VoteMilestoneGroupSelect.ALL, "Default",
|
||||
VoteMilestoneLimit.none());
|
||||
|
||||
out.put(vm.getId(), vm);
|
||||
}
|
||||
}
|
||||
|
||||
private static void compileAllSites(ConfigurationSection root, Map<String, VoteMilestone> out, int totalSites) {
|
||||
String name = "AllSites";
|
||||
|
||||
// Trigger when "unique sites voted today" reaches required number of sites
|
||||
long at = Math.max(1, totalSites);
|
||||
|
||||
// IMPORTANT: ALLSITES_TODAY stays true after you hit it, so legacy AllSites
|
||||
// must be once-per-day
|
||||
VoteMilestoneLimit limit = new VoteMilestoneLimit(VoteMilestoneLimit.Type.WINDOW_DAY, 0L);
|
||||
|
||||
VoteMilestone vm = VoteMilestoneFactory.atExact("Legacy_" + name, true, VoteMilestoneTotal.ALLSITES_TODAY, at,
|
||||
name, VoteMilestoneGroupSelect.ALL, "Default", limit);
|
||||
out.put(vm.getId(), vm);
|
||||
}
|
||||
|
||||
private static void compileAlmostAllSites(ConfigurationSection root, Map<String, VoteMilestone> out,
|
||||
int totalSites) {
|
||||
String name = "AlmostAllSites";
|
||||
|
||||
// "almost all" = totalSites - 1 (minimum 1)
|
||||
long at = Math.max(1, totalSites - 1);
|
||||
|
||||
// Same issue as AllSites: once reached for the day, it would keep matching
|
||||
// without a limiter
|
||||
VoteMilestoneLimit limit = new VoteMilestoneLimit(VoteMilestoneLimit.Type.WINDOW_DAY, 0L);
|
||||
|
||||
VoteMilestone vm = VoteMilestoneFactory.atExact("Legacy_" + name, true, VoteMilestoneTotal.ALLSITES_TODAY, at,
|
||||
name, VoteMilestoneGroupSelect.ALL, "Default", limit);
|
||||
out.put(vm.getId(), vm);
|
||||
}
|
||||
|
||||
private static VoteMilestoneTotal mapTotalToUse(String s) {
|
||||
String v = (s == null ? "ALLTIME" : s).trim().toUpperCase();
|
||||
switch (v) {
|
||||
case "ALLTIME":
|
||||
case "ALL_TIME":
|
||||
return VoteMilestoneTotal.ALLTIME_VOTES;
|
||||
case "DAILY":
|
||||
case "DAY":
|
||||
return VoteMilestoneTotal.DAILY_VOTES;
|
||||
case "WEEKLY":
|
||||
case "WEEK":
|
||||
return VoteMilestoneTotal.WEEKLY_VOTES;
|
||||
case "MONTHLY":
|
||||
case "MONTH":
|
||||
return VoteMilestoneTotal.MONTHLY_VOTES;
|
||||
default:
|
||||
return VoteMilestoneTotal.ALLTIME_VOTES;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isDisabledObject(ConfigurationSection sec) {
|
||||
return sec.getKeys(false).isEmpty();
|
||||
}
|
||||
}
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
package com.bencodez.votingplugin.specialrewards.votestreak;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
public final class VoteStreakDefinition {
|
||||
|
||||
@Getter
|
||||
private final String id;
|
||||
@Getter
|
||||
private final VoteStreakType type;
|
||||
@Getter
|
||||
private final boolean enabled;
|
||||
|
||||
@Getter
|
||||
private final int requiredAmount;
|
||||
@Getter
|
||||
private final int allowMissedAmount;
|
||||
@Getter
|
||||
private final int allowMissedPeriod;
|
||||
|
||||
@Getter
|
||||
private final int votesRequired;
|
||||
|
||||
@Getter
|
||||
private final boolean recurring;
|
||||
@Getter
|
||||
private final String progressGroup;
|
||||
@Getter
|
||||
private final String rewardPath;
|
||||
|
||||
public VoteStreakDefinition(String id, VoteStreakType type, boolean enabled, int requiredAmount, int votesRequired,
|
||||
int allowMissedAmount, int allowMissedPeriod, boolean recurring) {
|
||||
this(id, type, enabled, requiredAmount, votesRequired, allowMissedAmount, allowMissedPeriod, recurring, "",
|
||||
"VoteStreaks." + id + ".Rewards");
|
||||
}
|
||||
|
||||
public VoteStreakDefinition(String id, VoteStreakType type, boolean enabled, int requiredAmount, int votesRequired,
|
||||
int allowMissedAmount, int allowMissedPeriod, boolean recurring, String progressGroup) {
|
||||
this(id, type, enabled, requiredAmount, votesRequired, allowMissedAmount, allowMissedPeriod, recurring,
|
||||
progressGroup, "VoteStreaks." + id + ".Rewards");
|
||||
}
|
||||
|
||||
public VoteStreakDefinition(String id, VoteStreakType type, boolean enabled, int requiredAmount, int votesRequired,
|
||||
int allowMissedAmount, int allowMissedPeriod, boolean recurring, String progressGroup, String rewardPath) {
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
this.enabled = enabled;
|
||||
this.requiredAmount = requiredAmount;
|
||||
this.allowMissedAmount = Math.max(0, allowMissedAmount);
|
||||
this.allowMissedPeriod = Math.max(0, allowMissedPeriod);
|
||||
this.votesRequired = Math.max(1, votesRequired);
|
||||
this.recurring = recurring;
|
||||
this.progressGroup = progressGroup == null ? "" : progressGroup.trim();
|
||||
this.rewardPath = rewardPath == null || rewardPath.trim().isEmpty() ? "VoteStreaks." + id + ".Rewards"
|
||||
: rewardPath.trim();
|
||||
}
|
||||
|
||||
}
|
||||
-1404
File diff suppressed because it is too large.
Load diff
-13
@@ -1,13 +0,0 @@
|
||||
package com.bencodez.votingplugin.specialrewards.votestreak;
|
||||
|
||||
public enum VoteStreakType {
|
||||
DAILY, WEEKLY, MONTHLY;
|
||||
|
||||
public static VoteStreakType from(String s) {
|
||||
try {
|
||||
return VoteStreakType.valueOf(s.trim().toUpperCase());
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("Invalid VoteStreak Type: " + s + " (expected DAILY/WEEKLY/MONTHLY)");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
-47
@@ -1,47 +0,0 @@
|
||||
package com.bencodez.votingplugin.votelog.listeners;
|
||||
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.events.PlayerPostVoteEvent;
|
||||
import com.bencodez.votingplugin.votelog.VoteLogMysqlTable.VoteLogStatus;
|
||||
|
||||
/**
|
||||
* Listener for logging player post-vote events.
|
||||
*/
|
||||
public class PlayerPostVoteLoggerListener implements Listener {
|
||||
|
||||
private VotingPluginMain plugin;
|
||||
|
||||
/**
|
||||
* Instantiates a new votifer event.
|
||||
*
|
||||
* @param plugin the plugin
|
||||
*/
|
||||
public PlayerPostVoteLoggerListener(VotingPluginMain plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* On votifer event.
|
||||
*
|
||||
* @param event the event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onplayerVote(PlayerPostVoteEvent event) {
|
||||
if (!plugin.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
VoteLogStatus status = VoteLogStatus.IMMEDIATE;
|
||||
if (event.isCached()) {
|
||||
status = VoteLogStatus.CACHED;
|
||||
}
|
||||
if (plugin.getConfigFile().isVoteLoggingEnabled() && !event.isBungee()) {
|
||||
plugin.getVoteLogMysqlTable().logVote(event.getVoteUUID(), status, event.getService(),
|
||||
event.getUuid().toString(), event.getPlayerName(), event.getVoteTime(),
|
||||
event.getUser().getOfflineVotes().size());
|
||||
}
|
||||
}
|
||||
}
|
||||
-61
@@ -1,61 +0,0 @@
|
||||
package com.bencodez.votingplugin.votelog.listeners;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.events.PlayerSpecialRewardEvent;
|
||||
|
||||
/**
|
||||
* Listener for logging player special reward events.
|
||||
*/
|
||||
public class PlayerSpecialRewardLoggerListener implements Listener {
|
||||
|
||||
private VotingPluginMain plugin;
|
||||
|
||||
/**
|
||||
* Instantiates a new votifer event.
|
||||
*
|
||||
* @param plugin the plugin
|
||||
*/
|
||||
public PlayerSpecialRewardLoggerListener(VotingPluginMain plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* On votifer event.
|
||||
*
|
||||
* @param event the event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onplayerVote(PlayerSpecialRewardEvent event) {
|
||||
if (!plugin.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (plugin.getConfigFile().isVoteLoggingEnabled()) {
|
||||
switch (event.getType()) {
|
||||
case VOTESTREAK:
|
||||
plugin.getVoteLogMysqlTable().logVoteStreakReward(event.getVoteUUID(), event.getUser().getUUID(),
|
||||
event.getUser().getPlayerName(),
|
||||
LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(),
|
||||
event.getType().toString());
|
||||
break;
|
||||
case TOPVOTER:
|
||||
plugin.getVoteLogMysqlTable().logTopVoterReward(event.getVoteUUID(), event.getUser().getUUID(),
|
||||
event.getUser().getPlayerName(),
|
||||
LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(),
|
||||
event.getType().toString());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
-65
@@ -1,65 +0,0 @@
|
||||
package com.bencodez.votingplugin.votelog.listeners;
|
||||
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.events.VoteMilestoneRewardEvent;
|
||||
import com.bencodez.votingplugin.specialrewards.votemilestones.VoteMilestone;
|
||||
|
||||
/**
|
||||
* Logs VoteMilestone reward executions to the VoteLog table.
|
||||
*/
|
||||
public class VoteMilestoneVoteLogListener implements Listener {
|
||||
|
||||
private final VotingPluginMain plugin;
|
||||
|
||||
/**
|
||||
* Constructs a new VoteMilestoneVoteLogListener.
|
||||
*
|
||||
* @param plugin the main plugin instance
|
||||
*/
|
||||
public VoteMilestoneVoteLogListener(VotingPluginMain plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles vote milestone reward events and logs them.
|
||||
*
|
||||
* @param e the vote milestone reward event
|
||||
*/
|
||||
@EventHandler
|
||||
public void onVoteMilestoneReward(VoteMilestoneRewardEvent e) {
|
||||
if (e == null || e.getUser() == null || e.getMilestone() == null) {
|
||||
return;
|
||||
}
|
||||
if (plugin == null || !plugin.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
if (plugin.getVoteLogMysqlTable() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
VoteMilestone m = e.getMilestone();
|
||||
|
||||
String milestoneId = safe(e.getMilestoneId());
|
||||
String groupId = safe(e.getGroupId());
|
||||
String totalType = m.getTotal() == null ? "null" : m.getTotal().name();
|
||||
long value = e.getValue();
|
||||
|
||||
long timeMillis = System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
plugin.getVoteLogMysqlTable().logVoteMilestoneReward(e.getVoteUUID(),
|
||||
e.getUuid() == null ? null : e.getUuid().toString(), e.getPlayerName(), timeMillis, milestoneId,
|
||||
groupId, totalType, value);
|
||||
} catch (Exception ex) {
|
||||
plugin.debug("[VoteLog] Failed to log VoteMilestone reward: " + ex.getClass().getSimpleName() + ":"
|
||||
+ (ex.getMessage() == null ? "" : ex.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private static String safe(String s) {
|
||||
return s == null ? "" : s.replace('\n', ' ').replace('\r', ' ').trim();
|
||||
}
|
||||
}
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
package com.bencodez.votingplugin.votelog.listeners;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.events.VoteShopPurchaseEvent;
|
||||
|
||||
public class VoteShopPurchaseLoggerListener implements Listener {
|
||||
|
||||
private final VotingPluginMain plugin;
|
||||
|
||||
public VoteShopPurchaseLoggerListener(VotingPluginMain plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onVoteShopPurchase(VoteShopPurchaseEvent event) {
|
||||
if (!plugin.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!plugin.getConfigFile().isVoteLoggingEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getVoteLogMysqlTable().logVoteShopPurchase(event.getPlayerUuid().toString(), event.getPlayerName(),
|
||||
LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(), event.getIdentifier(),
|
||||
event.getCost());
|
||||
}
|
||||
}
|
||||
Loaded 100 of 310 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user