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 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '17'
|
||||
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: 17
|
||||
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/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,681 +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.0</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>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<maven.compiler.release>8</maven.compiler.release>
|
||||
</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.projectlombok</groupId>
|
||||
<artifactId>lombok-maven-plugin</artifactId>
|
||||
<version>1.18.20.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>delombok</id>
|
||||
<phase>deploy</phase>
|
||||
<goals>
|
||||
<goal>delombok</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<addOutputDirectory>false</addOutputDirectory>
|
||||
<outputDirectory>${project.basedir}/target/delombok</outputDirectory>
|
||||
<sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.6.1</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>1.21.11-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>3.4.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bstats</groupId>
|
||||
<artifactId>bstats-velocity</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>me.clip</groupId>
|
||||
<artifactId>placeholderapi</artifactId>
|
||||
<version>2.11.6</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.bencodez</groupId>
|
||||
<artifactId>advancedcore</artifactId>
|
||||
<version>3.8</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.15.2</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.1.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spongepowered</groupId>
|
||||
<artifactId>configurate-yaml</artifactId>
|
||||
<version>4.1.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spongepowered</groupId>
|
||||
<artifactId>configurate-gson</artifactId>
|
||||
<version>4.1.2</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}</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>install</phase>
|
||||
<goals>
|
||||
<goal>copy</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<sourceFile>
|
||||
${project.basedir}/target/${project.name}.jar</sourceFile>
|
||||
<destinationFile>
|
||||
${project.basedir}/target/${project.name}-${build.number}.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>
|
||||
<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,700 +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;
|
||||
|
||||
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;
|
||||
|
||||
public BungeeHandler(VotingPluginMain plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public boolean checkGlobalDataTimeValue(DataValue data) {
|
||||
if (data.isBoolean()) {
|
||||
return data.getBoolean();
|
||||
}
|
||||
return Boolean.valueOf(data.getString());
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
VotingPluginUser user = plugin.getVotingPluginUserManager()
|
||||
.getVotingPluginUser(UUID.fromString(playerUuid));
|
||||
user.cache();
|
||||
|
||||
user.offVote();
|
||||
|
||||
// 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);
|
||||
|
||||
// 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.getPlaceholders().onBungeeVotePartyUpdate();
|
||||
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;
|
||||
}
|
||||
|
||||
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,102 +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;
|
||||
|
||||
public BroadcastFormat(String broadcastMsg, String header, String listLine) {
|
||||
this.broadcastMsg = broadcastMsg == null ? "" : broadcastMsg;
|
||||
this.header = header == null ? "" : header;
|
||||
this.listLine = listLine == null ? "" : listLine;
|
||||
}
|
||||
|
||||
public List<String> render(String playerName, List<String> items, String reason) {
|
||||
return render(playerName, items, reason, Collections.<String, String>emptyMap());
|
||||
}
|
||||
|
||||
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,430 +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;
|
||||
|
||||
public BroadcastHandler(VotingPluginMain plugin, BroadcastSettings settings, ZoneId zoneId) {
|
||||
this.plugin = plugin;
|
||||
this.settings = settings;
|
||||
this.zoneId = zoneId == null ? ZoneId.systemDefault() : zoneId;
|
||||
|
||||
rescheduleIntervalIfNeeded();
|
||||
}
|
||||
|
||||
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)
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,76 +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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public VoteBroadcastType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public ParsedDuration getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
public int getMaxSitesListed() {
|
||||
return maxSitesListed;
|
||||
}
|
||||
|
||||
public BroadcastFormat getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
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: '...'
|
||||
*/
|
||||
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,60 +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 {
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
-145
@@ -1,145 +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;
|
||||
|
||||
public AdminVoteLogEventSelect(VotingPluginMain plugin, CommandSender sender, VoteLogMysqlTable table,
|
||||
VotingPluginUser user, int days) {
|
||||
this(plugin, sender, table, user, days, 0);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
-85
@@ -1,85 +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;
|
||||
|
||||
public class AdminVoteLogHelpers {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
public static String safe(String s) {
|
||||
return s == null ? "" : s;
|
||||
}
|
||||
|
||||
public static boolean notEmpty(String s) {
|
||||
return s != null && !s.trim().isEmpty();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public static String shortUuid(String uuid) {
|
||||
if (uuid == null || uuid.isEmpty()) {
|
||||
return "unknown";
|
||||
}
|
||||
return uuid.length() > 8 ? uuid.substring(0, 8) : uuid;
|
||||
}
|
||||
|
||||
}
|
||||
-188
@@ -1,188 +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;
|
||||
|
||||
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;
|
||||
|
||||
public AdminVoteLogMenu(VotingPluginMain plugin, CommandSender sender, VoteLogMysqlTable table, int days) {
|
||||
this(plugin, sender, table, null, days);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
-170
@@ -1,170 +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;
|
||||
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
-140
@@ -1,140 +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;
|
||||
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
-136
@@ -1,136 +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;
|
||||
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
-222
@@ -1,222 +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;
|
||||
|
||||
public AdminVoteLogRecent(VotingPluginMain plugin, CommandSender sender, VoteLogMysqlTable table, int page) {
|
||||
this(plugin, sender, table, null, page, 0, null);
|
||||
}
|
||||
|
||||
public AdminVoteLogRecent(VotingPluginMain plugin, CommandSender sender, VoteLogMysqlTable table,
|
||||
VotingPluginUser user, int page) {
|
||||
this(plugin, sender, table, user, page, 0, null);
|
||||
}
|
||||
|
||||
// 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 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);
|
||||
}
|
||||
}
|
||||
-178
@@ -1,178 +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;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
-137
@@ -1,137 +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;
|
||||
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
-178
@@ -1,178 +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;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
-137
@@ -1,137 +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;
|
||||
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
-135
@@ -1,135 +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;
|
||||
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
-211
@@ -1,211 +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;
|
||||
|
||||
public AdminVoteLogStats(VotingPluginMain plugin, CommandSender sender, VoteLogMysqlTable table, int days) {
|
||||
this(plugin, sender, table, null, days, 10, 10);
|
||||
}
|
||||
|
||||
public AdminVoteLogStats(VotingPluginMain plugin, CommandSender sender, VoteLogMysqlTable table,
|
||||
VotingPluginUser user, int days, int topServicesLimit) {
|
||||
this(plugin, sender, table, user, days, topServicesLimit, 10);
|
||||
}
|
||||
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
-231
@@ -1,231 +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;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
-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,368 +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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public void check(UUID uuid) {
|
||||
VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(uuid);
|
||||
if (!user.isOnline()) {
|
||||
user.userDataFetechMode(UserDataFetchMode.NO_CACHE);
|
||||
}
|
||||
check(user);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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).
|
||||
*/
|
||||
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 + ")"));
|
||||
}
|
||||
|
||||
public void checkEnabled() {
|
||||
cooldownCheckEnabled = !plugin.getConfigFile().isDisableCoolDownCheck()
|
||||
&& plugin.getRewardHandler().hasRewards(plugin.getSpecialRewardsConfig().getData(),
|
||||
"VoteCoolDownEndedReward");
|
||||
}
|
||||
|
||||
public void checkPerSite(UUID uuid) {
|
||||
VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(uuid);
|
||||
if (!user.isOnline()) {
|
||||
user.userDataFetechMode(UserDataFetchMode.NO_CACHE);
|
||||
}
|
||||
checkPerSite(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);
|
||||
}
|
||||
}
|
||||
|
||||
public void load() {
|
||||
plugin.addUserStartup(new UserStartup() {
|
||||
|
||||
@Override
|
||||
public void onFinish() {
|
||||
checkAllVoteSites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartUp(AdvancedCoreUser advancedcoreUser) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onCoolDownEnd(PlayerVoteCoolDownEndEvent event) {
|
||||
plugin.getRewardHandler().giveReward(event.getPlayer(), plugin.getSpecialRewardsConfig().getData(),
|
||||
"VoteCoolDownEndedReward", new RewardOptions());
|
||||
}
|
||||
|
||||
@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)));
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
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,52 +0,0 @@
|
||||
package com.bencodez.votingplugin.events;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
public enum SpecialRewardType {
|
||||
@Deprecated
|
||||
ALLSITE, @Deprecated
|
||||
CUMMULATIVE, @Deprecated
|
||||
FIRSTVOTE, @Deprecated
|
||||
FIRSTVOTETODAY, @Deprecated
|
||||
MILESTONE, VOTESTREAK, @Deprecated
|
||||
ALMOSTALLSITES, TOPVOTER, VOTESTREAKS;
|
||||
|
||||
@Getter
|
||||
private int amount = -1;
|
||||
|
||||
@Getter
|
||||
private String type = "";
|
||||
|
||||
public SpecialRewardType setAmount(int num) {
|
||||
amount = num;
|
||||
return this;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
-109
@@ -1,109 +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));
|
||||
}
|
||||
}
|
||||
|
||||
public VotingPluginUser getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public UUID getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public String getPlayerName() {
|
||||
return playerName;
|
||||
}
|
||||
|
||||
public VoteMilestone getMilestone() {
|
||||
return milestone;
|
||||
}
|
||||
|
||||
public String getMilestoneId() {
|
||||
return milestone == null ? null : milestone.getId();
|
||||
}
|
||||
|
||||
public long getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public String getRewardPath() {
|
||||
return rewardPath;
|
||||
}
|
||||
|
||||
public Map<String, String> getPlaceholders() {
|
||||
return placeholders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLERS;
|
||||
}
|
||||
|
||||
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,122 +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()));
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package com.bencodez.votingplugin.proxy;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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,484 +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;
|
||||
|
||||
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
|
||||
public abstract void debug(SQLException e);
|
||||
|
||||
// ---- Constructors ----
|
||||
|
||||
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).
|
||||
*/
|
||||
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 ----
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep old method name and behavior.
|
||||
*/
|
||||
public Set<String> getUuids() {
|
||||
return getPrimaryKeys();
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep old behavior: query uuids fresh.
|
||||
*/
|
||||
public ArrayList<String> getUuidsQuery() {
|
||||
return new ArrayList<>(getPrimaryKeysQuery());
|
||||
}
|
||||
|
||||
public void loadData() {
|
||||
// old name -> new behavior
|
||||
loadBasicCaches();
|
||||
}
|
||||
|
||||
public void clearCache() {
|
||||
clearCacheBasic();
|
||||
}
|
||||
|
||||
public void clearCacheBasic() {
|
||||
clearCaches();
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
close();
|
||||
}
|
||||
|
||||
// Keep old containsKeyQuery signature
|
||||
public boolean containsKeyQuery(String index) {
|
||||
return super.containsKeyQuery(index);
|
||||
}
|
||||
|
||||
// ---- Required old utility methods (kept) ----
|
||||
|
||||
public ArrayList<String> getColumnsQueury() {
|
||||
return new ArrayList<>(getColumnsQuery());
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public boolean isIntColumn(String key) {
|
||||
return intColumns.contains(key);
|
||||
}
|
||||
|
||||
// ---- INSERT/UPDATE/WIPE (kept similar to your existing behavior) ----
|
||||
|
||||
public void insert(String index, String column, DataValue value) {
|
||||
insertQuery(index, Arrays.asList(new Column(column, value)));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,20 +0,0 @@
|
||||
package com.bencodez.votingplugin.proxy.broadcast;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public enum OfflineMode {
|
||||
NONE, QUEUE, FORWARD;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
-150
@@ -1,150 +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;
|
||||
|
||||
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)
|
||||
*/
|
||||
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?"
|
||||
*/
|
||||
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,57 +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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public static ScopeMode parse(String value) {
|
||||
return parse(value, ALL_SERVERS);
|
||||
}
|
||||
}
|
||||
-47
@@ -1,47 +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;
|
||||
|
||||
public class BungeeJsonNonVotedPlayersCache extends BungeeJsonFile
|
||||
implements INonVotedPlayersStorage {
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
-359
@@ -1,359 +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;
|
||||
|
||||
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;";
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
public ProxyOnlineVoteCacheTable(MysqlConfig config, boolean debug) {
|
||||
super("votingplugin_onlinevotecache", config, debug);
|
||||
|
||||
alterColumnType("uuid", bestUuidType());
|
||||
addVoteIdColumnIfMissingOnce();
|
||||
ensureIndexesOnce();
|
||||
}
|
||||
|
||||
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 ---
|
||||
|
||||
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 ---
|
||||
|
||||
public List<VoteRow> getAllVotes() {
|
||||
return selectVotes("SELECT * FROM " + qi(getTableName()) + ";", null);
|
||||
}
|
||||
|
||||
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 ---
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getVoteId() {
|
||||
return voteId;
|
||||
}
|
||||
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public String getPlayerName() {
|
||||
return playerName;
|
||||
}
|
||||
|
||||
public String getService() {
|
||||
return service;
|
||||
}
|
||||
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public boolean isRealVote() {
|
||||
return realVote;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
-191
@@ -1,191 +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;
|
||||
|
||||
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);
|
||||
|
||||
public ProxyTimedVoteCacheTable(MySQL existingMysql, String tablePrefix, boolean debug) {
|
||||
super((tablePrefix != null ? tablePrefix : "") + "votingplugin_timedvotecache",
|
||||
existingMysql,
|
||||
debug);
|
||||
ensureIndexes();
|
||||
}
|
||||
|
||||
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 ---
|
||||
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 ---
|
||||
public List<TimedVoteRow> getAllVotes() {
|
||||
return selectVotes("SELECT * FROM " + qi(getTableName()) + ";", null);
|
||||
}
|
||||
|
||||
public List<TimedVoteRow> getExpiredVotes(long now) {
|
||||
return selectVotes("SELECT * FROM " + qi(getTableName()) + " WHERE " + qi("time") + " <= ?;",
|
||||
new Object[] { now });
|
||||
}
|
||||
|
||||
// --- DELETE ---
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public static class TimedVoteRow {
|
||||
private final int id;
|
||||
private final String playerName;
|
||||
private final String service;
|
||||
private final long time;
|
||||
|
||||
public TimedVoteRow(int id, String playerName, String service, long time) {
|
||||
this.id = id;
|
||||
this.playerName = playerName;
|
||||
this.service = service;
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getPlayerName() {
|
||||
return playerName;
|
||||
}
|
||||
|
||||
public String getService() {
|
||||
return service;
|
||||
}
|
||||
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
}
|
||||
}
|
||||
-453
@@ -1,453 +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();
|
||||
}
|
||||
|
||||
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 ----
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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 ----
|
||||
|
||||
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 ----
|
||||
|
||||
public List<VoteRow> getAllVotes() {
|
||||
return selectVotes("SELECT * FROM " + qi(getTableName()) + ";", null);
|
||||
}
|
||||
|
||||
public List<VoteRow> getVotesForServer(String server) {
|
||||
return selectVotes("SELECT * FROM " + qi(getTableName()) + " WHERE " + qi("server") + " = ?;",
|
||||
new Object[] { server });
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public List<VoteRow> getVotesByPlayerName(String playerName) {
|
||||
return selectVotes("SELECT * FROM " + qi(getTableName()) + " WHERE " + qi("playerName") + " = ?;",
|
||||
new Object[] { playerName });
|
||||
}
|
||||
|
||||
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 ----
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getVoteId() {
|
||||
return voteId;
|
||||
}
|
||||
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public String getPlayerName() {
|
||||
return playerName;
|
||||
}
|
||||
|
||||
public String getService() {
|
||||
return service;
|
||||
}
|
||||
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public boolean isRealVote() {
|
||||
return realVote;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public String getServer() {
|
||||
return server;
|
||||
}
|
||||
}
|
||||
}
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
package com.bencodez.votingplugin.proxy.cache.nonvoted;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface INonVotedPlayersStorage {
|
||||
|
||||
/** Insert or update a player entry. */
|
||||
void upsertPlayer(String uuid, String playerName, long lastTime);
|
||||
|
||||
/** Returns UUID for the player or empty string if not found. */
|
||||
String getUuidByPlayerName(String playerName);
|
||||
|
||||
/** Remove a player from storage. */
|
||||
void removeByPlayerName(String playerName);
|
||||
|
||||
/** Iterate snapshot of all entries. */
|
||||
void forEach(Consumer<NonVotedPlayerEntry> consumer);
|
||||
|
||||
/** Close resources if needed (MySQL); no-op for JSON. */
|
||||
void close();
|
||||
|
||||
final class NonVotedPlayerEntry {
|
||||
private final String playerName;
|
||||
private final String uuid;
|
||||
private final long lastTime;
|
||||
|
||||
public NonVotedPlayerEntry(String playerName, String uuid, long lastTime) {
|
||||
this.playerName = playerName;
|
||||
this.uuid = uuid;
|
||||
this.lastTime = lastTime;
|
||||
}
|
||||
|
||||
public String getPlayerName() {
|
||||
return playerName;
|
||||
}
|
||||
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public long getLastTime() {
|
||||
return lastTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
-56
@@ -1,56 +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;
|
||||
|
||||
public abstract class MySQLNonVotedPlayersStorage extends ProxyNonVotedPlayersTable
|
||||
implements INonVotedPlayersStorage {
|
||||
|
||||
private final boolean ownsConnection;
|
||||
|
||||
public MySQLNonVotedPlayersStorage(MySQL existingMysql, String tablePrefix, boolean debug) {
|
||||
super(existingMysql, tablePrefix, debug);
|
||||
this.ownsConnection = false;
|
||||
}
|
||||
|
||||
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
-132
@@ -1,132 +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;
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract boolean userExists(String uuid);
|
||||
|
||||
/**
|
||||
* Add a player directly by uuid + name, only if they don't already have votes.
|
||||
*/
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
public String getUUID(String playerName) {
|
||||
return storage.getUuidByPlayerName(playerName);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
storage.close();
|
||||
}
|
||||
|
||||
public abstract void logInfo1(String msg);
|
||||
|
||||
public abstract void logSevere1(String msg);
|
||||
|
||||
public abstract void debug1(Exception e);
|
||||
|
||||
public abstract void debug1(String msg);
|
||||
}
|
||||
-184
@@ -1,184 +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;
|
||||
|
||||
public abstract class ProxyNonVotedPlayersTable {
|
||||
|
||||
@Getter
|
||||
private final MySQL mysql;
|
||||
private final String tableName;
|
||||
|
||||
public abstract void logSevere(String msg);
|
||||
|
||||
public abstract void logInfo(String msg);
|
||||
|
||||
public abstract void debug(Exception e);
|
||||
|
||||
public ProxyNonVotedPlayersTable(MySQL existingMysql, String tablePrefix, boolean debug) {
|
||||
this.mysql = existingMysql;
|
||||
this.tableName = (tablePrefix != null ? tablePrefix : "") + "votingplugin_nonvotedplayers";
|
||||
createTableIfNeeded();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public String getTableName() {
|
||||
return tableName;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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 "";
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public void clearAll() {
|
||||
try {
|
||||
new Query(mysql, "TRUNCATE TABLE `" + tableName + "`;").executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class NonVotedPlayerRow {
|
||||
private final int id;
|
||||
private final String uuid;
|
||||
private final String playerName;
|
||||
private final long lastTime;
|
||||
|
||||
public NonVotedPlayerRow(int id, String uuid, String playerName, long lastTime) {
|
||||
this.id = id;
|
||||
this.uuid = uuid;
|
||||
this.playerName = playerName;
|
||||
this.lastTime = lastTime;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public String getPlayerName() {
|
||||
return playerName;
|
||||
}
|
||||
|
||||
public long getLastTime() {
|
||||
return lastTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
-266
@@ -1,266 +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;
|
||||
|
||||
public abstract class MultiProxyHandler {
|
||||
private HashMap<String, ClientHandler> multiproxyClientHandles;
|
||||
private SocketHandler multiproxySocketHandler;
|
||||
|
||||
@Getter
|
||||
private RedisHandler multiProxyRedis;
|
||||
|
||||
public MultiProxyHandler() {
|
||||
}
|
||||
|
||||
public abstract void addNonVotedPlayerCache(String uuid, String playerName);
|
||||
|
||||
public abstract void clearVote(String uuid);
|
||||
|
||||
public void close() {
|
||||
if (multiproxySocketHandler != null) {
|
||||
multiproxySocketHandler.closeConnection();
|
||||
multiproxySocketHandler = null;
|
||||
}
|
||||
if (multiProxyRedis != null && !getMultiProxyRedisUseExistingConnection()) {
|
||||
multiProxyRedis.close();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract boolean getDebug();
|
||||
|
||||
public abstract EncryptionHandler getEncryptionHandler();
|
||||
|
||||
public abstract MultiProxyMethod getMultiProxyMethod();
|
||||
|
||||
public abstract String getMultiProxyPassword();
|
||||
|
||||
public abstract String getMultiProxyRedisHost();
|
||||
|
||||
public abstract int getMultiProxyRedisPort();
|
||||
|
||||
public abstract int getMultiProxyRedisDbIndex();
|
||||
|
||||
public abstract boolean getMultiProxyRedisUseExistingConnection();
|
||||
|
||||
public abstract String getMultiProxyServerName();
|
||||
|
||||
public abstract Collection<String> getMultiProxyServers();
|
||||
|
||||
public abstract MultiProxyServerSocketConfiguration getMultiProxyServersConfiguration(String s);
|
||||
|
||||
public abstract String getMultiProxySocketHostHost();
|
||||
|
||||
public abstract int getMultiProxySocketHostPort();
|
||||
|
||||
public abstract boolean getMultiProxySupportEnabled();
|
||||
|
||||
public abstract String getMultiProxyUsername();
|
||||
|
||||
public abstract File getPluginDataFolder();
|
||||
|
||||
public abstract boolean getPrimaryServer();
|
||||
|
||||
public abstract List<String> getProxyServers();
|
||||
|
||||
public abstract RedisHandler getRedisHandler();
|
||||
|
||||
public abstract String getVersion();
|
||||
|
||||
public abstract void logInfo(String msg);
|
||||
|
||||
public abstract void runAsnc(Runnable runnable);
|
||||
|
||||
public abstract void setEncryptionHandler(EncryptionHandler encryptionHandler);
|
||||
|
||||
public abstract void triggerVote(String player, String service, boolean realVote, boolean timeQueue, long queueTime,
|
||||
VoteTotalsSnapshot text, String uuid);
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
public void login(String uuid, String playerName) {
|
||||
if (!getMultiProxySupportEnabled() || getPrimaryServer()) {
|
||||
return;
|
||||
}
|
||||
sendMultiProxyEnvelope(VotingPluginWire.login(playerName, uuid, getMultiProxyServerName()));
|
||||
}
|
||||
|
||||
public void sendClearVote(String uuid, String playerName) {
|
||||
if (!getMultiProxySupportEnabled()) {
|
||||
return;
|
||||
}
|
||||
sendMultiProxyEnvelope(VotingPluginWire.clearVote(uuid, playerName, getMultiProxyServerName()));
|
||||
}
|
||||
|
||||
public void sendStatus() {
|
||||
if (!getMultiProxySupportEnabled()) {
|
||||
return;
|
||||
}
|
||||
sendMultiProxyEnvelope(VotingPluginWire.status(getMultiProxyServerName()));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-56
@@ -1,56 +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;
|
||||
|
||||
public class VelocityJsonNonVotedPlayersCache extends VelocityJSONFile implements INonVotedPlayersStorage {
|
||||
|
||||
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() {
|
||||
|
||||
}
|
||||
|
||||
private void setPath(Object value, Object... path) {
|
||||
set(path, value);
|
||||
}
|
||||
|
||||
}
|
||||
-226
@@ -1,226 +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;
|
||||
|
||||
public class VelocityJsonVoteCache extends VelocityJSONFile implements IVoteCache {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
-385
@@ -1,385 +0,0 @@
|
||||
package com.bencodez.votingplugin.servicesites;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
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.
|
||||
*
|
||||
* <p>
|
||||
* Primary runtime storage is {@code serverdata.yml} as a list of strings:
|
||||
*
|
||||
* <pre>
|
||||
* ServiceSites:
|
||||
* - "PlanetMinecraft - planetminecraft.com"
|
||||
* - "TopG - topg.org"
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* On startup this class:
|
||||
* <ol>
|
||||
* <li>Loads the list from {@code serverdata.yml} into memory.</li>
|
||||
* <li>Checks the web sources once for updates and overwrites
|
||||
* {@code serverdata.yml} if changed.</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>
|
||||
* Lookups are case-insensitive while preserving original casing.
|
||||
* </p>
|
||||
*/
|
||||
public class ServiceSiteHandler {
|
||||
|
||||
/**
|
||||
* Primary source: GitHub wiki raw markdown (most stable).
|
||||
*/
|
||||
private static final String PRIMARY_URL = "https://raw.githubusercontent.com/wiki/BenCodez/VotingPlugin/Minecraft-Server-Lists.md";
|
||||
|
||||
/**
|
||||
* Secondary source: main wiki (HTML).
|
||||
*/
|
||||
private static final String SECONDARY_URL = "https://wiki.bencodez.com/en/VotingPlugin/Minecraft-Server-Lists";
|
||||
|
||||
/**
|
||||
* Tertiary source: backup wiki (HTML).
|
||||
*/
|
||||
private static final String TERTIARY_URL = "https://wiki-backup.bencodez.com/VotingPlugin/Minecraft-Server-Lists/";
|
||||
|
||||
/**
|
||||
* YAML path used in {@code serverdata.yml}.
|
||||
*/
|
||||
private static final String SERVERDATA_PATH = "ServiceSites";
|
||||
|
||||
/**
|
||||
* Matches entries in the format {@code Key - Value}.
|
||||
*/
|
||||
private static final Pattern LINE_PATTERN = Pattern.compile("^\\s*(.+?)\\s+-\\s+(.+?)\\s*$");
|
||||
|
||||
/**
|
||||
* Extracts {@code
|
||||
*
|
||||
<li>...</li>} content from HTML.
|
||||
*/
|
||||
private static final Pattern LI_PATTERN = Pattern.compile("<li>\\s*(.*?)\\s*</li>", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
private final VotingPluginMain plugin;
|
||||
|
||||
/**
|
||||
* Case-insensitive lookup map (atomic swap on update).
|
||||
*/
|
||||
private final AtomicReference<Map<String, String>> sitesRef = new AtomicReference<>(
|
||||
Collections.unmodifiableMap(new TreeMap<>(String.CASE_INSENSITIVE_ORDER)));
|
||||
|
||||
/**
|
||||
* Exposed read-only view of current service sites.
|
||||
*/
|
||||
@Getter
|
||||
private Map<String, String> serviceSites = sitesRef.get();
|
||||
|
||||
/**
|
||||
* Creates the handler, loads from {@code serverdata.yml}, then checks the web
|
||||
* sources once for updates.
|
||||
*
|
||||
* <p>
|
||||
* Important: this performs a blocking web fetch on the current thread. If you
|
||||
* need this to be async, call {@link #refreshFromWebOnce(boolean)} from your
|
||||
* own async executor.
|
||||
* </p>
|
||||
*
|
||||
* @param plugin plugin instance
|
||||
*/
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a service key exists (case-insensitive).
|
||||
*
|
||||
* @param service service name
|
||||
* @return {@code true} if present
|
||||
*/
|
||||
public boolean contains(String service) {
|
||||
return service != null && sitesRef.get().containsKey(service);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a service name to its configured value.
|
||||
*
|
||||
* @param service service name
|
||||
* @return mapped value, or {@code service} if not found
|
||||
*/
|
||||
public String match(String service) {
|
||||
if (service == null) {
|
||||
return null;
|
||||
}
|
||||
return sitesRef.get().getOrDefault(service, service);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse lookup from value to key.
|
||||
*
|
||||
* @param value value to reverse lookup
|
||||
* @return matching key, or {@code value} if not found
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads service sites from {@code serverdata.yml}.
|
||||
*/
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves current service sites to {@code serverdata.yml}.
|
||||
*/
|
||||
public void saveToServerData() {
|
||||
List<String> out = toListFormat(sitesRef.get());
|
||||
plugin.getServerData().getData().set(SERVERDATA_PATH, out);
|
||||
plugin.getServerData().saveData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the web sources once and updates {@code serverdata.yml} if changes are
|
||||
* detected.
|
||||
*
|
||||
* <p>
|
||||
* Tries primary, secondary, then tertiary URL.
|
||||
* </p>
|
||||
*
|
||||
* @param logMore whether to log debug details
|
||||
*/
|
||||
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 (Exception e) {
|
||||
plugin.debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a map into list format used by {@code serverdata.yml}.
|
||||
*
|
||||
* @param map map to convert
|
||||
* @return list of {@code "Key - Value"} entries
|
||||
*/
|
||||
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 {
|
||||
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 {
|
||||
HttpURLConnection conn = (HttpURLConnection) new URL(urlStr).openConnection();
|
||||
conn.setRequestMethod("GET");
|
||||
conn.setConnectTimeout(5000);
|
||||
conn.setReadTimeout(7000);
|
||||
conn.setInstanceFollowRedirects(true);
|
||||
conn.setRequestProperty("User-Agent", "VotingPlugin/ServiceSiteHandler");
|
||||
|
||||
int code = conn.getResponseCode();
|
||||
if (code < 200 || code >= 300) {
|
||||
conn.disconnect();
|
||||
throw new IOException("HTTP " + code);
|
||||
}
|
||||
|
||||
String ct = conn.getContentType();
|
||||
StringBuilder sb = new StringBuilder(16 * 1024);
|
||||
|
||||
try (BufferedReader br = new BufferedReader(
|
||||
new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
sb.append(line).append('\n');
|
||||
}
|
||||
} finally {
|
||||
conn.disconnect();
|
||||
}
|
||||
|
||||
return new FetchResult(urlStr, sb.toString(), ct);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Web fetch result container.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
-96
@@ -1,96 +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;
|
||||
|
||||
public SpecialRewards(VotingPluginMain plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
-249
@@ -1,249 +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;
|
||||
}
|
||||
|
||||
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]"
|
||||
*/
|
||||
public String toDebugString() {
|
||||
return toDebugString(8, 3);
|
||||
}
|
||||
|
||||
public boolean hasZero() {
|
||||
return hasZero;
|
||||
}
|
||||
|
||||
public Set<Long> getExactTotals() {
|
||||
return java.util.Collections.unmodifiableSet(exactTotals);
|
||||
}
|
||||
|
||||
/** Returns a compact string for logs. */
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param head how many smallest values to show
|
||||
* @param tail how many largest values to show when truncating
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
-93
@@ -1,93 +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 {
|
||||
|
||||
public final long start;
|
||||
public final long end;
|
||||
public final long step;
|
||||
|
||||
public RangeSpec(long start, long end, long step) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
-70
@@ -1,70 +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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public boolean hasAtMatcherDebug() {
|
||||
return atMatcher != null;
|
||||
}
|
||||
|
||||
public String getAtMatcherDebugString(int maxValues) {
|
||||
return atMatcher == null ? "none" : atMatcher.toDebugString(maxValues);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
-106
@@ -1,106 +0,0 @@
|
||||
package com.bencodez.votingplugin.specialrewards.votemilestones;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
public final class VoteMilestoneGroupParser {
|
||||
|
||||
private VoteMilestoneGroupParser() {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
package com.bencodez.votingplugin.specialrewards.votemilestones;
|
||||
|
||||
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;
|
||||
}
|
||||
-175
@@ -1,175 +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 {
|
||||
|
||||
public enum Type {
|
||||
NONE, WINDOW_DAY, WINDOW_WEEK, WINDOW_MONTH, COOLDOWN
|
||||
}
|
||||
|
||||
private final Type type;
|
||||
private final long cooldownMillis; // fixed millis cooldown
|
||||
|
||||
public VoteMilestoneLimit(Type type, long cooldownMillis) {
|
||||
this.type = type == null ? Type.NONE : type;
|
||||
this.cooldownMillis = Math.max(0L, cooldownMillis);
|
||||
}
|
||||
|
||||
public static VoteMilestoneLimit none() {
|
||||
return new VoteMilestoneLimit(Type.NONE, 0L);
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public long getCooldownMillis() {
|
||||
return cooldownMillis;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return type != Type.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 ---------------- */
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
-140
@@ -1,140 +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) ----
|
||||
ALLTIME_VOTES {
|
||||
@Override
|
||||
public long getValue(VotingPluginUser user, VoteTotalsSnapshot bungeeMessageData) {
|
||||
if (bungeeMessageData != null) {
|
||||
return bungeeMessageData.getAllTimeTotal();
|
||||
}
|
||||
return user.getTotal(TopVoter.AllTime);
|
||||
}
|
||||
},
|
||||
|
||||
DAILY_VOTES {
|
||||
@Override
|
||||
public long getValue(VotingPluginUser user, VoteTotalsSnapshot bungeeMessageData) {
|
||||
if (bungeeMessageData != null) {
|
||||
return bungeeMessageData.getDailyTotal();
|
||||
}
|
||||
return user.getTotal(TopVoter.Daily);
|
||||
}
|
||||
},
|
||||
|
||||
WEEKLY_VOTES {
|
||||
@Override
|
||||
public long getValue(VotingPluginUser user, VoteTotalsSnapshot bungeeMessageData) {
|
||||
if (bungeeMessageData != null) {
|
||||
return bungeeMessageData.getWeeklyTotal();
|
||||
}
|
||||
return user.getTotal(TopVoter.Weekly);
|
||||
}
|
||||
},
|
||||
|
||||
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) ----
|
||||
POINTS {
|
||||
@Override
|
||||
public long getValue(VotingPluginUser user, VoteTotalsSnapshot bungeeMessageData) {
|
||||
if (bungeeMessageData != null) {
|
||||
return bungeeMessageData.getPoints();
|
||||
}
|
||||
return user.getPoints();
|
||||
}
|
||||
},
|
||||
ALLSITES_TODAY {
|
||||
@Override
|
||||
public long getValue(VotingPluginUser user, VoteTotalsSnapshot bungeeMessageData) {
|
||||
return user.getUniqueVoteSitesToday();
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
-74
@@ -1,74 +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;
|
||||
|
||||
public class VoteMilestonesConfig {
|
||||
|
||||
private final Map<String, VoteMilestone> milestones;
|
||||
|
||||
private VoteMilestonesConfig(Map<String, VoteMilestone> milestones) {
|
||||
this.milestones = milestones;
|
||||
}
|
||||
|
||||
public Map<String, VoteMilestone> getMilestones() {
|
||||
return milestones;
|
||||
}
|
||||
|
||||
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
-196
@@ -1,196 +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() {
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
-35
@@ -1,35 +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;
|
||||
|
||||
public VoteStreakDefinition(String id, VoteStreakType type, boolean enabled, int requiredAmount, int votesRequired,
|
||||
int allowMissedAmount, int allowMissedPeriod) {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
-592
@@ -1,592 +0,0 @@
|
||||
package com.bencodez.votingplugin.specialrewards.votestreak;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.YearMonth;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.time.temporal.WeekFields;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
import com.bencodez.advancedcore.api.rewards.RewardBuilder;
|
||||
import com.bencodez.advancedcore.api.time.TimeChecker;
|
||||
import com.bencodez.advancedcore.api.user.usercache.keys.UserDataKeyString;
|
||||
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;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
public class VoteStreakHandler {
|
||||
|
||||
private final VotingPluginMain plugin;
|
||||
|
||||
@Getter
|
||||
private final Map<String, VoteStreakDefinition> byId = new LinkedHashMap<>();
|
||||
private final List<VoteStreakDefinition> ordered = new ArrayList<>();
|
||||
|
||||
public VoteStreakHandler(VotingPluginMain plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload streak definitions from config.
|
||||
*/
|
||||
public void reload() {
|
||||
byId.clear();
|
||||
ordered.clear();
|
||||
|
||||
// VoteStreak definitions live in specialrewards.yml now (Option B)
|
||||
ConfigurationSection root = plugin.getSpecialRewardsConfig().getData();
|
||||
new VoteStreakConfigLoader().load(root);
|
||||
}
|
||||
|
||||
public List<VoteStreakDefinition> getDefinitions() {
|
||||
return Collections.unmodifiableList(ordered);
|
||||
}
|
||||
|
||||
public VoteStreakDefinition getDefinition(String id) {
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
return byId.get(id.toLowerCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this whenever a vote happens
|
||||
*
|
||||
* @param user voting plugin user
|
||||
* @param voteTimeMillis vote time
|
||||
* @param voteUUID vote unique id
|
||||
*/
|
||||
public void processVote(VotingPluginUser user, long voteTimeMillis, UUID voteUUID) {
|
||||
if (user == null) {
|
||||
plugin.extraDebug("[VoteStreak] processVote: user is null, returning");
|
||||
return;
|
||||
}
|
||||
if (ordered.isEmpty()) {
|
||||
plugin.extraDebug("[VoteStreak] processVote: ordered is empty (no definitions loaded), returning");
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.extraDebug("[VoteStreak] processVote: user=" + safeUser(user) + " timeMillis=" + voteTimeMillis
|
||||
+ " defs=" + ordered.size());
|
||||
|
||||
for (VoteStreakDefinition def : ordered) {
|
||||
if (!def.isEnabled()) {
|
||||
plugin.extraDebug(
|
||||
"[VoteStreak] processVote: skip disabled def id=" + def.getId() + " idKey=" + def.getId());
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
processVoteForDefinition(user, def, voteTimeMillis, voteUUID);
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning("VoteStreak processing failed for '" + def.getId() + "': " + e.getMessage());
|
||||
plugin.debug(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processVoteForDefinition(VotingPluginUser user, VoteStreakDefinition def, long voteTimeMillis,
|
||||
UUID voteUUID) {
|
||||
final String col = getColumnName(def);
|
||||
final String rawBefore = readStateString(user, col);
|
||||
StreakState state = StreakState.deserialize(rawBefore);
|
||||
|
||||
final String currentPeriodKey = periodKey(def.getType(), voteTimeMillis);
|
||||
|
||||
plugin.extraDebug("[VoteStreak] def=" + def.getId() + " idKey=" + def.getId() + " type=" + def.getType()
|
||||
+ " col=" + col + " period=" + currentPeriodKey + " votesReq=" + def.getVotesRequired() + " interval="
|
||||
+ def.getRequiredAmount() + " rawBefore='" + rawBefore + "' stateBefore={periodKey=" + state.periodKey
|
||||
+ ",streakCount=" + state.streakCount + ",votesThisPeriod=" + state.votesThisPeriod
|
||||
+ ",countedThisPeriod=" + state.countedThisPeriod + ",missesUsed=" + state.missesUsed
|
||||
+ ",missWindowStartKey=" + state.missWindowStartKey + "}");
|
||||
|
||||
// First time init
|
||||
if (state.periodKey == null || state.periodKey.isEmpty()) {
|
||||
plugin.extraDebug("[VoteStreak] init state (no prior periodKey)");
|
||||
state.periodKey = currentPeriodKey;
|
||||
state.countedThisPeriod = false;
|
||||
state.votesThisPeriod = 0;
|
||||
state.missWindowStartKey = "";
|
||||
state.missesUsed = 0;
|
||||
} else if (!state.periodKey.equals(currentPeriodKey)) {
|
||||
plugin.extraDebug("[VoteStreak] period advanced from " + state.periodKey + " -> " + currentPeriodKey);
|
||||
advancePeriodsAndApplyMisses(def, state, voteTimeMillis, currentPeriodKey);
|
||||
plugin.extraDebug("[VoteStreak] after advance state={periodKey=" + state.periodKey + ",streakCount="
|
||||
+ state.streakCount + ",votesThisPeriod=" + state.votesThisPeriod + ",countedThisPeriod="
|
||||
+ state.countedThisPeriod + ",missesUsed=" + state.missesUsed + ",missWindowStartKey="
|
||||
+ state.missWindowStartKey + "}");
|
||||
}
|
||||
|
||||
// Count votes within the period until threshold reached, then mark period
|
||||
// satisfied once.
|
||||
if (!state.countedThisPeriod) {
|
||||
state.votesThisPeriod++;
|
||||
|
||||
int votesRequired = Math.max(1, def.getVotesRequired());
|
||||
plugin.extraDebug("[VoteStreak] vote counted in period: votesThisPeriod=" + state.votesThisPeriod + "/"
|
||||
+ votesRequired);
|
||||
|
||||
if (state.votesThisPeriod >= votesRequired) {
|
||||
state.countedThisPeriod = true;
|
||||
state.streakCount++;
|
||||
|
||||
int interval = Math.max(1, def.getRequiredAmount());
|
||||
boolean shouldReward = state.streakCount > 0 && (state.streakCount % interval) == 0;
|
||||
|
||||
plugin.extraDebug("[VoteStreak] period satisfied: streakCount=" + state.streakCount + " interval="
|
||||
+ interval + " shouldReward=" + shouldReward);
|
||||
|
||||
if (shouldReward) {
|
||||
plugin.extraDebug("[VoteStreak] giving rewards for idKey=" + def.getId());
|
||||
giveRewards(user, def, voteUUID);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
plugin.extraDebug("[VoteStreak] already satisfied this period; ignoring vote. votesThisPeriod="
|
||||
+ state.votesThisPeriod);
|
||||
}
|
||||
|
||||
// Persist + verify
|
||||
final String rawAfter = state.serialize();
|
||||
plugin.extraDebug("[VoteStreak] writing col=" + col + " rawAfter='" + rawAfter + "'");
|
||||
writeStateString(user, col, rawAfter);
|
||||
|
||||
final String readBack = readStateString(user, col);
|
||||
plugin.extraDebug("[VoteStreak] read-back col=" + col + " raw='" + readBack + "' (writtenLen="
|
||||
+ rawAfter.length() + " readLen=" + readBack.length() + ")");
|
||||
}
|
||||
|
||||
/**
|
||||
* When periods change, we determine if previous periods were misses.
|
||||
*
|
||||
* @param def vote streak definition
|
||||
* @param state current streak state
|
||||
* @param nowMillis current time millis
|
||||
* @param currentPeriodKey current period key
|
||||
*/
|
||||
private void advancePeriodsAndApplyMisses(VoteStreakDefinition def, StreakState state, long nowMillis,
|
||||
String currentPeriodKey) {
|
||||
|
||||
if (!state.countedThisPeriod) {
|
||||
plugin.extraDebug("[VoteStreak] advance: previous period " + state.periodKey + " NOT satisfied -> miss");
|
||||
recordMiss(def, state, nowMillis);
|
||||
}
|
||||
|
||||
int skipped = periodsBetween(def.getType(), state.periodKey, currentPeriodKey);
|
||||
plugin.extraDebug(
|
||||
"[VoteStreak] advance: skipped=" + skipped + " between " + state.periodKey + " -> " + currentPeriodKey);
|
||||
|
||||
if (skipped > 1) {
|
||||
for (int i = 0; i < skipped - 1; i++) {
|
||||
plugin.extraDebug(
|
||||
"[VoteStreak] advance: recording intermediate missed period " + (i + 1) + "/" + (skipped - 1));
|
||||
recordMiss(def, state, nowMillis);
|
||||
}
|
||||
}
|
||||
|
||||
state.periodKey = currentPeriodKey;
|
||||
state.countedThisPeriod = false;
|
||||
state.votesThisPeriod = 0;
|
||||
|
||||
if (def.getAllowMissedAmount() <= 0) {
|
||||
if (state.missesUsed > 0) {
|
||||
plugin.extraDebug(
|
||||
"[VoteStreak] advance: no misses allowed and missesUsed=" + state.missesUsed + " -> reset");
|
||||
resetStreakAndMissTracking(state);
|
||||
}
|
||||
} else if (state.missesUsed > def.getAllowMissedAmount()) {
|
||||
plugin.extraDebug("[VoteStreak] advance: missesUsed=" + state.missesUsed + " > allowMissedAmount="
|
||||
+ def.getAllowMissedAmount() + " -> reset");
|
||||
resetStreakAndMissTracking(state);
|
||||
}
|
||||
}
|
||||
|
||||
private void recordMiss(VoteStreakDefinition def, StreakState state, long nowMillis) {
|
||||
if (def.getAllowMissedPeriod() <= 0) {
|
||||
if (state.missWindowStartKey == null || state.missWindowStartKey.isEmpty()) {
|
||||
state.missWindowStartKey = periodKey(def.getType(), nowMillis);
|
||||
state.missesUsed = 1;
|
||||
plugin.extraDebug("[VoteStreak] recordMiss: start window=" + state.missWindowStartKey
|
||||
+ " missesUsed=1 (no window)");
|
||||
} else {
|
||||
state.missesUsed++;
|
||||
plugin.extraDebug("[VoteStreak] recordMiss: missesUsed=" + state.missesUsed + " (no window)");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
String nowKey = periodKey(def.getType(), nowMillis);
|
||||
|
||||
if (state.missWindowStartKey == null || state.missWindowStartKey.isEmpty()) {
|
||||
state.missWindowStartKey = nowKey;
|
||||
state.missesUsed = 1;
|
||||
plugin.extraDebug("[VoteStreak] recordMiss: start window=" + nowKey + " missesUsed=1");
|
||||
return;
|
||||
}
|
||||
|
||||
int windowSpan = periodsBetween(def.getType(), state.missWindowStartKey, nowKey);
|
||||
|
||||
if (windowSpan >= def.getAllowMissedPeriod()) {
|
||||
plugin.extraDebug("[VoteStreak] recordMiss: window expired span=" + windowSpan + " >= allowMissedPeriod="
|
||||
+ def.getAllowMissedPeriod() + " -> reset window to " + nowKey);
|
||||
state.missWindowStartKey = nowKey;
|
||||
state.missesUsed = 1;
|
||||
} else {
|
||||
state.missesUsed++;
|
||||
plugin.extraDebug(
|
||||
"[VoteStreak] recordMiss: within window span=" + windowSpan + " missesUsed=" + state.missesUsed);
|
||||
}
|
||||
}
|
||||
|
||||
private void resetStreakAndMissTracking(StreakState state) {
|
||||
plugin.extraDebug("[VoteStreak] resetStreakAndMissTracking: reset streakCount/misses/votesThisPeriod");
|
||||
state.streakCount = 0;
|
||||
state.missesUsed = 0;
|
||||
state.missWindowStartKey = "";
|
||||
state.votesThisPeriod = 0;
|
||||
}
|
||||
|
||||
private LocalDate getPluginDateFromMillis(long millis) {
|
||||
TimeChecker tc = plugin.getTimeChecker();
|
||||
|
||||
LocalDateTime ldt = tryInvokeLocalDateTime(tc, "getTime", millis);
|
||||
if (ldt != null) {
|
||||
return ldt.toLocalDate();
|
||||
}
|
||||
ldt = tryInvokeLocalDateTime(tc, "getTimeFromMillis", millis);
|
||||
if (ldt != null) {
|
||||
return ldt.toLocalDate();
|
||||
}
|
||||
ldt = tryInvokeLocalDateTime(tc, "getTimeAt", millis);
|
||||
if (ldt != null) {
|
||||
return ldt.toLocalDate();
|
||||
}
|
||||
|
||||
LocalDateTime pluginNow = tc.getTime();
|
||||
long nowMillis = System.currentTimeMillis();
|
||||
long deltaMillis = millis - nowMillis;
|
||||
return pluginNow.plusNanos(deltaMillis * 1_000_000L).toLocalDate();
|
||||
}
|
||||
|
||||
private LocalDateTime tryInvokeLocalDateTime(Object target, String method, long millis) {
|
||||
try {
|
||||
Method m = target.getClass().getMethod(method, long.class);
|
||||
Object out = m.invoke(target, millis);
|
||||
if (out instanceof LocalDateTime) {
|
||||
return (LocalDateTime) out;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String periodKey(VoteStreakType type, long millis) {
|
||||
switch (type) {
|
||||
case DAILY: {
|
||||
LocalDate d = getPluginDateFromMillis(millis);
|
||||
return d.toString();
|
||||
}
|
||||
case WEEKLY: {
|
||||
LocalDate d = getPluginDateFromMillis(millis);
|
||||
WeekFields wf = WeekFields.ISO;
|
||||
int week = d.get(wf.weekOfWeekBasedYear());
|
||||
int year = d.get(wf.weekBasedYear());
|
||||
return year + "-W" + String.format(Locale.ROOT, "%02d", week);
|
||||
}
|
||||
case MONTHLY: {
|
||||
YearMonth ym = YearMonth.from(getPluginDateFromMillis(millis));
|
||||
return ym.toString();
|
||||
}
|
||||
default:
|
||||
return String.valueOf(millis);
|
||||
}
|
||||
}
|
||||
|
||||
private int periodsBetween(VoteStreakType type, String startKey, String endKey) {
|
||||
if (startKey == null || endKey == null) {
|
||||
return 0;
|
||||
}
|
||||
if (startKey.equals(endKey)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
switch (type) {
|
||||
case DAILY: {
|
||||
LocalDate a = LocalDate.parse(startKey);
|
||||
LocalDate b = LocalDate.parse(endKey);
|
||||
long days = ChronoUnit.DAYS.between(a, b);
|
||||
return (int) Math.max(0, days);
|
||||
}
|
||||
case MONTHLY: {
|
||||
YearMonth a = YearMonth.parse(startKey);
|
||||
YearMonth b = YearMonth.parse(endKey);
|
||||
long months = ChronoUnit.MONTHS.between(a, b);
|
||||
return (int) Math.max(0, months);
|
||||
}
|
||||
case WEEKLY: {
|
||||
WeekKey a = WeekKey.parse(startKey);
|
||||
WeekKey b = WeekKey.parse(endKey);
|
||||
return Math.max(0, b.toIndex() - a.toIndex());
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class WeekKey {
|
||||
final int year;
|
||||
final int week;
|
||||
|
||||
private WeekKey(int year, int week) {
|
||||
this.year = year;
|
||||
this.week = week;
|
||||
}
|
||||
|
||||
static WeekKey parse(String s) {
|
||||
String[] parts = s.split("-W");
|
||||
int y = Integer.parseInt(parts[0]);
|
||||
int w = Integer.parseInt(parts[1]);
|
||||
return new WeekKey(y, w);
|
||||
}
|
||||
|
||||
int toIndex() {
|
||||
return (year * 53) + week;
|
||||
}
|
||||
}
|
||||
|
||||
private void giveRewards(VotingPluginUser user, VoteStreakDefinition def, UUID voteUUID) {
|
||||
PlayerSpecialRewardEvent event = new PlayerSpecialRewardEvent(user,
|
||||
SpecialRewardType.VOTESTREAKS.setType(def.getType().toString()).setAmount(def.getVotesRequired()),
|
||||
voteUUID);
|
||||
Bukkit.getPluginManager().callEvent(event);
|
||||
|
||||
if (event.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
new RewardBuilder(plugin.getSpecialRewardsConfig().getData(), "VoteStreaks." + def.getId() + ".Rewards")
|
||||
.withPlaceHolder("id", def.getId()).send(user);
|
||||
}
|
||||
|
||||
public String getColumnName(VoteStreakDefinition def) {
|
||||
return "VoteStreak_" + def.getId();
|
||||
}
|
||||
|
||||
public StreakState readState(VotingPluginUser user, VoteStreakDefinition def) {
|
||||
String raw = readStateString(user, getColumnName(def));
|
||||
return StreakState.deserialize(raw);
|
||||
}
|
||||
|
||||
public void writeState(VotingPluginUser user, VoteStreakDefinition def, StreakState state) {
|
||||
writeStateString(user, getColumnName(def), state.serialize());
|
||||
}
|
||||
|
||||
public String readStateString(VotingPluginUser user, String columnName) {
|
||||
String val = user.getVoteStreakState(columnName);
|
||||
return val == null ? "" : val;
|
||||
}
|
||||
|
||||
private void writeStateString(VotingPluginUser user, String columnName, String value) {
|
||||
user.setVoteStreakState(columnName, value);
|
||||
}
|
||||
|
||||
private static String safeUser(VotingPluginUser user) {
|
||||
try {
|
||||
String n = user.getPlayerName();
|
||||
if (n != null && !n.isEmpty()) {
|
||||
return n;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
try {
|
||||
return String.valueOf(user.getUUID());
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
private static final class StreakState {
|
||||
String periodKey = "";
|
||||
int streakCount = 0;
|
||||
int votesThisPeriod = 0;
|
||||
boolean countedThisPeriod = false;
|
||||
String missWindowStartKey = "";
|
||||
int missesUsed = 0;
|
||||
|
||||
String serialize() {
|
||||
return safe(periodKey) + "|" + streakCount + "|" + votesThisPeriod + "|" + countedThisPeriod + "|"
|
||||
+ safe(missWindowStartKey) + "|" + missesUsed;
|
||||
}
|
||||
|
||||
static StreakState deserialize(String raw) {
|
||||
StreakState s = new StreakState();
|
||||
if (raw == null || raw.isEmpty()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
String[] p = raw.split("\\|", -1);
|
||||
|
||||
if (p.length >= 6 && MessageAPI.isInt(p[2])) {
|
||||
if (p.length > 0)
|
||||
s.periodKey = p[0];
|
||||
if (p.length > 1)
|
||||
s.streakCount = parseInt(p[1], 0);
|
||||
if (p.length > 2)
|
||||
s.votesThisPeriod = parseInt(p[2], 0);
|
||||
if (p.length > 3)
|
||||
s.countedThisPeriod = parseBool(p[3], false);
|
||||
if (p.length > 4)
|
||||
s.missWindowStartKey = p[4];
|
||||
if (p.length > 5)
|
||||
s.missesUsed = parseInt(p[5], 0);
|
||||
} else {
|
||||
if (p.length > 0)
|
||||
s.periodKey = p[0];
|
||||
if (p.length > 1)
|
||||
s.streakCount = parseInt(p[1], 0);
|
||||
if (p.length > 2)
|
||||
s.countedThisPeriod = parseBool(p[2], false);
|
||||
if (p.length > 3)
|
||||
s.missWindowStartKey = p[3];
|
||||
if (p.length > 4)
|
||||
s.missesUsed = parseInt(p[4], 0);
|
||||
|
||||
s.votesThisPeriod = s.countedThisPeriod ? 1 : 0;
|
||||
}
|
||||
|
||||
if (s.periodKey == null)
|
||||
s.periodKey = "";
|
||||
if (s.missWindowStartKey == null)
|
||||
s.missWindowStartKey = "";
|
||||
if (s.streakCount < 0)
|
||||
s.streakCount = 0;
|
||||
if (s.votesThisPeriod < 0)
|
||||
s.votesThisPeriod = 0;
|
||||
if (s.missesUsed < 0)
|
||||
s.missesUsed = 0;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
private static String safe(String s) {
|
||||
return s == null ? "" : s;
|
||||
}
|
||||
|
||||
private static int parseInt(String s, int def) {
|
||||
if (s == null)
|
||||
return def;
|
||||
if (MessageAPI.isInt(s))
|
||||
return Integer.parseInt(s);
|
||||
return def;
|
||||
}
|
||||
|
||||
private static boolean parseBool(String s, boolean def) {
|
||||
if (s == null)
|
||||
return def;
|
||||
return Boolean.parseBoolean(s);
|
||||
}
|
||||
}
|
||||
|
||||
public final class VoteStreakConfigLoader {
|
||||
|
||||
private final Pattern idPattern = Pattern.compile("^[A-Za-z0-9_\\-]+$"); // no spaces
|
||||
|
||||
public void load(ConfigurationSection root) {
|
||||
if (root == null) {
|
||||
plugin.getLogger().warning("VoteStreaks config root is null; no streaks loaded.");
|
||||
return;
|
||||
}
|
||||
|
||||
ConfigurationSection voteStreaks = root.getConfigurationSection("VoteStreaks");
|
||||
if (voteStreaks == null) {
|
||||
plugin.getLogger().warning("VoteStreaks is missing or not a section; no streaks loaded.");
|
||||
return;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
|
||||
for (String id : voteStreaks.getKeys(false)) {
|
||||
count++;
|
||||
|
||||
if (id == null || id.trim().isEmpty()) {
|
||||
plugin.getLogger().warning("VoteStreaks entry #" + count + " has empty key; skipping.");
|
||||
continue;
|
||||
}
|
||||
|
||||
id = id.trim();
|
||||
if (!idPattern.matcher(id).matches()) {
|
||||
plugin.getLogger()
|
||||
.warning("VoteStreaks ID '" + id + "' is invalid (only A-Z, 0-9, _, -). Skipping.");
|
||||
continue;
|
||||
}
|
||||
|
||||
ConfigurationSection defSec = voteStreaks.getConfigurationSection(id);
|
||||
if (defSec == null) {
|
||||
plugin.getLogger().warning("VoteStreaks '" + id + "' is not a section; skipping.");
|
||||
continue;
|
||||
}
|
||||
|
||||
String typeStr = defSec.getString("Type");
|
||||
VoteStreakType type = VoteStreakType.from(typeStr);
|
||||
if (type == null) {
|
||||
plugin.getLogger()
|
||||
.warning("VoteStreaks '" + id + "' has invalid Type '" + typeStr + "'; skipping.");
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean enabled = defSec.getBoolean("Enabled", true);
|
||||
|
||||
int amountInterval = 0;
|
||||
int votesRequired = 1;
|
||||
|
||||
ConfigurationSection req = defSec.getConfigurationSection("Requirements");
|
||||
if (req != null) {
|
||||
amountInterval = req.getInt("Amount", 0);
|
||||
votesRequired = req.getInt("VotesRequired", 1);
|
||||
}
|
||||
|
||||
if (amountInterval <= 0) {
|
||||
plugin.getLogger().warning("VoteStreaks '" + id + "' Requirements.Amount must be > 0; skipping.");
|
||||
continue;
|
||||
}
|
||||
if (votesRequired <= 0) {
|
||||
votesRequired = 1;
|
||||
}
|
||||
|
||||
int allowMissedAmount = Math.max(0, defSec.getInt("AllowMissedAmount", 0));
|
||||
int allowMissedPeriod = Math.max(0, defSec.getInt("AllowMissedPeriod", 0));
|
||||
|
||||
// ConfigurationSection editableTarget = getOrCreateVoteStreakSection(id);
|
||||
|
||||
VoteStreakDefinition def = new VoteStreakDefinition(id, type, enabled, amountInterval, votesRequired,
|
||||
allowMissedAmount, allowMissedPeriod);
|
||||
|
||||
plugin.getUserManager().getDataManager()
|
||||
.addKey(new UserDataKeyString(getColumnName(def)).setColumnType("MEDIUMTEXT"));
|
||||
|
||||
byId.put(id, def);
|
||||
ordered.add(def);
|
||||
}
|
||||
|
||||
plugin.getLogger().info("Loaded " + ordered.size() + " VoteStreak definitions.");
|
||||
}
|
||||
}
|
||||
}
|
||||
-15
@@ -1,15 +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
-44
@@ -1,44 +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;
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
-58
@@ -1,58 +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;
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
-55
@@ -1,55 +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;
|
||||
|
||||
public VoteMilestoneVoteLogListener(VotingPluginMain plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
}
|
||||
-118
@@ -1,118 +0,0 @@
|
||||
package com.bencodez.votingplugin.votereminding;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
|
||||
public final class VoteRemindersLegacyMigrator {
|
||||
|
||||
private VoteRemindersLegacyMigrator() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates legacy VoteReminding -> VoteReminderOptions + VoteReminders in
|
||||
* Config.yml.
|
||||
*
|
||||
* - NO BACKUPS (per request) - Runs once
|
||||
* (VoteReminderOptions.MigratedFromLegacy) - Skips if new sections already
|
||||
* exist
|
||||
*/
|
||||
public static boolean migrateIfNeeded(VotingPluginMain plugin, File configYmlFile, FileConfiguration cfg) {
|
||||
// already migrated
|
||||
if (cfg.getBoolean("VoteReminderOptions.MigratedFromLegacy", false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if new system exists, don't touch anything
|
||||
if (cfg.isConfigurationSection("VoteReminderOptions") || cfg.isConfigurationSection("VoteReminders")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ConfigurationSection legacy = cfg.getConfigurationSection("VoteReminding");
|
||||
if (legacy == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
plugin.getLogger().info("[VoteReminders] Migrating legacy VoteReminding from Config.yml...");
|
||||
|
||||
// Options
|
||||
cfg.set("VoteReminderOptions.Enabled", legacy.getBoolean("Enabled", true));
|
||||
cfg.set("VoteReminderOptions.StopAfterMatch", true);
|
||||
cfg.set("VoteReminderOptions.GlobalCooldown", "");
|
||||
cfg.set("VoteReminderOptions.DefaultPriority", 0);
|
||||
|
||||
cfg.set("VoteReminderOptions.Defaults.Cooldown", "");
|
||||
cfg.set("VoteReminderOptions.Defaults.Delay", "");
|
||||
|
||||
// Default rewards copy
|
||||
if (legacy.isConfigurationSection("Rewards")) {
|
||||
copySection(legacy.getConfigurationSection("Rewards"),
|
||||
ensureSection(cfg, "VoteReminderOptions.Defaults.Rewards"));
|
||||
}
|
||||
|
||||
// RemindDelay -> Interval reminder
|
||||
int remindDelayMinutes = legacy.getInt("RemindDelay", 30);
|
||||
|
||||
if (remindDelayMinutes > 0) {
|
||||
String base = "VoteReminders.Basic";
|
||||
cfg.set(base + ".Type", "INTERVAL");
|
||||
cfg.set(base + ".Interval", remindDelayMinutes + "m");
|
||||
cfg.set(base + ".Conditions.CanVoteAny", true);
|
||||
|
||||
}
|
||||
|
||||
// RemindOnLogin -> Login reminder
|
||||
if (legacy.getBoolean("RemindOnLogin", true)) {
|
||||
String base = "VoteReminders.OnLogin";
|
||||
cfg.set(base + ".Type", "LOGIN");
|
||||
cfg.set(base + ".Conditions.CanVoteAny", true);
|
||||
}
|
||||
|
||||
// mark migrated
|
||||
cfg.set("VoteReminderOptions.MigratedFromLegacy", true);
|
||||
|
||||
// save Config.yml
|
||||
try {
|
||||
cfg.save(configYmlFile);
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().severe("[VoteReminders] Failed saving Config.yml after migration: " + e.getMessage());
|
||||
}
|
||||
|
||||
plugin.getLogger().info("[VoteReminders] Legacy VoteReminding migration complete.");
|
||||
return true;
|
||||
}
|
||||
|
||||
private static ConfigurationSection ensureSection(FileConfiguration cfg, String path) {
|
||||
ConfigurationSection sec = cfg.getConfigurationSection(path);
|
||||
if (sec != null) {
|
||||
return sec;
|
||||
}
|
||||
String[] parts = path.split("\\.");
|
||||
String cur = "";
|
||||
ConfigurationSection out = null;
|
||||
for (String p : parts) {
|
||||
cur = cur.isEmpty() ? p : cur + "." + p;
|
||||
ConfigurationSection s = cfg.getConfigurationSection(cur);
|
||||
if (s == null) {
|
||||
s = cfg.createSection(cur);
|
||||
}
|
||||
out = s;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static void copySection(ConfigurationSection from, ConfigurationSection to) {
|
||||
for (String key : from.getKeys(false)) {
|
||||
Object val = from.get(key);
|
||||
if (val instanceof ConfigurationSection) {
|
||||
copySection((ConfigurationSection) val, to.createSection(key));
|
||||
} else {
|
||||
to.set(key, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
package com.bencodez.votingplugin.votereminding;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
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.listeners.AdvancedCoreLoginEvent;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.events.PlayerPostVoteEvent;
|
||||
import com.bencodez.votingplugin.events.PlayerVoteCoolDownEndEvent;
|
||||
import com.bencodez.votingplugin.events.PlayerVoteSiteCoolDownEndEvent;
|
||||
import com.bencodez.votingplugin.votereminding.VoteRemindersManager.VoteReminderType;
|
||||
|
||||
public class VoteRemindersListener implements Listener {
|
||||
|
||||
private VotingPluginMain plugin;
|
||||
|
||||
public VoteRemindersListener(VotingPluginMain plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
|
||||
public void onPostVote(PlayerPostVoteEvent event) {
|
||||
plugin.getVoteRemindersManager().onVoteCast(event.getUser(), event.getVoteSite().getDisplayName());
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
|
||||
public void onLogin(AdvancedCoreLoginEvent event) {
|
||||
plugin.getVoteRemindersManager().onJoin(event.getPlayer(),
|
||||
plugin.getVotingPluginUserManager().getVotingPluginUser(event.getUser()));
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
|
||||
public void onQuit(PlayerQuitEvent event) {
|
||||
plugin.getVoteRemindersManager().onQuit(event.getPlayer());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onCoolDownEnd(PlayerVoteCoolDownEndEvent event) {
|
||||
HashMap<String, String> placeholders = new HashMap<>();
|
||||
plugin.getVoteRemindersManager().onCooldownTrigger(event.getPlayer(), VoteReminderType.COOLDOWN_END_ALL_SITES,
|
||||
placeholders);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onCoolDownEnd(PlayerVoteSiteCoolDownEndEvent event) {
|
||||
HashMap<String, String> placeholders = new HashMap<>();
|
||||
placeholders.put("votesite", event.getSite().getDisplayName());
|
||||
placeholders.put("votesite_id", event.getSite().getKey());
|
||||
plugin.getVoteRemindersManager().onCooldownTrigger(event.getPlayer(), VoteReminderType.COOLDOWN_END_ANY_SITE,
|
||||
placeholders);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
-981
@@ -1,981 +0,0 @@
|
||||
// File: com/bencodez/votingplugin/votereminding/VoteRemindersManager.java
|
||||
package com.bencodez.votingplugin.votereminding;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.bencodez.advancedcore.api.rewards.RewardBuilder;
|
||||
import com.bencodez.simpleapi.time.ParsedDuration;
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.user.VotingPluginUser;
|
||||
import com.bencodez.votingplugin.votereminding.store.VoteReminderCooldownStore;
|
||||
|
||||
/**
|
||||
* VoteReminders core manager (no Bukkit repeating tasks).
|
||||
*
|
||||
* Uses ScheduledExecutorService for: - interval tick (aligned to minute
|
||||
* boundary) - flush coalescing - delayed evaluations
|
||||
*
|
||||
* Assumptions (per your note): - VotingPluginUser lookup + Reward giving are
|
||||
* safe off-thread for your implementation.
|
||||
*/
|
||||
public final class VoteRemindersManager {
|
||||
|
||||
/*
|
||||
* ========================= Enums / Models =========================
|
||||
*/
|
||||
|
||||
public enum VoteReminderType {
|
||||
// Player lifecycle
|
||||
LOGIN, FIRST_JOIN,
|
||||
|
||||
// Voting lifecycle
|
||||
VOTE_CAST,
|
||||
|
||||
// Cooldown-driven (from CoolDownCheck)
|
||||
COOLDOWN_END_ANY_SITE, COOLDOWN_END_ALL_SITES,
|
||||
|
||||
// Time-driven
|
||||
INTERVAL
|
||||
}
|
||||
|
||||
public static final class VoteReminderConditions {
|
||||
private Boolean canVoteAny;
|
||||
private Boolean canVoteAll;
|
||||
|
||||
private ParsedDuration minOnlineTime;
|
||||
private Boolean firstJoin;
|
||||
|
||||
public Boolean getCanVoteAny() {
|
||||
return canVoteAny;
|
||||
}
|
||||
|
||||
public void setCanVoteAny(Boolean canVoteAny) {
|
||||
this.canVoteAny = canVoteAny;
|
||||
}
|
||||
|
||||
public Boolean getCanVoteAll() {
|
||||
return canVoteAll;
|
||||
}
|
||||
|
||||
public void setCanVoteAll(Boolean canVoteAll) {
|
||||
this.canVoteAll = canVoteAll;
|
||||
}
|
||||
|
||||
public ParsedDuration getMinOnlineTime() {
|
||||
return minOnlineTime;
|
||||
}
|
||||
|
||||
public void setMinOnlineTime(ParsedDuration minOnlineTime) {
|
||||
this.minOnlineTime = minOnlineTime;
|
||||
}
|
||||
|
||||
public Boolean getFirstJoin() {
|
||||
return firstJoin;
|
||||
}
|
||||
|
||||
public void setFirstJoin(Boolean firstJoin) {
|
||||
this.firstJoin = firstJoin;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class VoteReminderOptions {
|
||||
private final boolean enabled;
|
||||
private final boolean stopAfterMatch;
|
||||
private final ParsedDuration globalCooldown;
|
||||
private final int defaultPriority;
|
||||
|
||||
private final ParsedDuration defaultCooldown;
|
||||
private final ParsedDuration defaultDelay;
|
||||
private final VoteReminderConditions defaultsConditions;
|
||||
|
||||
private final String defaultRewardsPath;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public VoteReminderOptions(boolean enabled, boolean stopAfterMatch, ParsedDuration globalCooldown,
|
||||
int defaultPriority, ParsedDuration defaultCooldown, ParsedDuration defaultDelay,
|
||||
VoteReminderConditions defaultsConditions, String defaultRewardsPath) {
|
||||
this.enabled = enabled;
|
||||
this.stopAfterMatch = stopAfterMatch;
|
||||
this.globalCooldown = globalCooldown == null ? ParsedDuration.parse("") : globalCooldown;
|
||||
this.defaultPriority = defaultPriority;
|
||||
this.defaultCooldown = defaultCooldown == null ? ParsedDuration.parse("") : defaultCooldown;
|
||||
this.defaultDelay = defaultDelay == null ? ParsedDuration.parse("") : defaultDelay;
|
||||
this.defaultsConditions = defaultsConditions == null ? new VoteReminderConditions() : defaultsConditions;
|
||||
this.defaultRewardsPath = defaultRewardsPath;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public boolean isStopAfterMatch() {
|
||||
return stopAfterMatch;
|
||||
}
|
||||
|
||||
public ParsedDuration getGlobalCooldown() {
|
||||
return globalCooldown;
|
||||
}
|
||||
|
||||
public int getDefaultPriority() {
|
||||
return defaultPriority;
|
||||
}
|
||||
|
||||
public ParsedDuration getDefaultCooldown() {
|
||||
return defaultCooldown;
|
||||
}
|
||||
|
||||
public ParsedDuration getDefaultDelay() {
|
||||
return defaultDelay;
|
||||
}
|
||||
|
||||
public VoteReminderConditions getDefaultsConditions() {
|
||||
return defaultsConditions;
|
||||
}
|
||||
|
||||
public String getDefaultRewardsPath() {
|
||||
return defaultRewardsPath;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class VoteReminderDefinition {
|
||||
private final String name;
|
||||
private final VoteReminderType type;
|
||||
private final int priority;
|
||||
|
||||
private final ParsedDuration cooldown;
|
||||
private final ParsedDuration delay;
|
||||
|
||||
private final String rewardsPath;
|
||||
private final VoteReminderConditions conditions;
|
||||
|
||||
private final ParsedDuration interval;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public VoteReminderDefinition(String name, VoteReminderType type, int priority, ParsedDuration cooldown,
|
||||
ParsedDuration delay, String rewardsPath, VoteReminderConditions conditions, ParsedDuration interval) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.priority = priority;
|
||||
this.cooldown = cooldown == null ? ParsedDuration.parse("") : cooldown;
|
||||
this.delay = delay == null ? ParsedDuration.parse("") : delay;
|
||||
this.rewardsPath = rewardsPath;
|
||||
this.conditions = conditions == null ? new VoteReminderConditions() : conditions;
|
||||
this.interval = interval == null ? ParsedDuration.parse("") : interval;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public VoteReminderType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public int getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
public ParsedDuration getCooldown() {
|
||||
return cooldown;
|
||||
}
|
||||
|
||||
public ParsedDuration getDelay() {
|
||||
return delay;
|
||||
}
|
||||
|
||||
public String getRewardsPath() {
|
||||
return rewardsPath;
|
||||
}
|
||||
|
||||
public VoteReminderConditions getConditions() {
|
||||
return conditions;
|
||||
}
|
||||
|
||||
public ParsedDuration getInterval() {
|
||||
return interval;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ========================= Cooldowns wrapper =========================
|
||||
*/
|
||||
|
||||
public static final class VoteReminderCooldowns {
|
||||
private final VoteReminderCooldownStore store;
|
||||
private final ParsedDuration globalCooldown;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public VoteReminderCooldowns(VoteReminderCooldownStore store, ParsedDuration globalCooldown) {
|
||||
this.store = store;
|
||||
this.globalCooldown = globalCooldown == null ? ParsedDuration.parse("") : globalCooldown;
|
||||
}
|
||||
|
||||
public boolean tryAcquireGlobal(UUID uuid, long nowMs) {
|
||||
long cd = ms(globalCooldown);
|
||||
if (cd <= 0) {
|
||||
return true;
|
||||
}
|
||||
return store.tryClaimGlobal(uuid, nowMs, cd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gate per-reminder using max(cooldown, interval) in millis.
|
||||
*/
|
||||
public boolean canFireReminder(UUID uuid, String reminderName, long nowMs, ParsedDuration cooldown,
|
||||
ParsedDuration interval) {
|
||||
long req = Math.max(ms(cooldown), ms(interval));
|
||||
if (req <= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Map<String, Long> map = store.getPerReminderMap(uuid);
|
||||
long last = 0L;
|
||||
Long v = map.get(reminderName);
|
||||
if (v != null) {
|
||||
last = v.longValue();
|
||||
}
|
||||
return last <= 0 || (nowMs - last) >= req;
|
||||
}
|
||||
|
||||
public void markFired(UUID uuid, String reminderName, long nowMs) {
|
||||
store.setPerReminderLast(uuid, reminderName, nowMs);
|
||||
}
|
||||
|
||||
private long ms(ParsedDuration d) {
|
||||
if (d == null || d.isEmpty()) {
|
||||
return 0L;
|
||||
}
|
||||
return Math.max(0L, d.getMillis());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ========================= Fields =========================
|
||||
*/
|
||||
|
||||
private final VotingPluginMain plugin;
|
||||
private final VoteReminderCooldownStore store;
|
||||
|
||||
private VoteReminderOptions options;
|
||||
private VoteReminderCooldowns cooldowns;
|
||||
|
||||
private List<VoteReminderDefinition> reminders = Collections.emptyList();
|
||||
private Map<String, VoteReminderDefinition> byName = Collections.emptyMap();
|
||||
|
||||
private final ConcurrentHashMap<UUID, Long> joinTimes = new ConcurrentHashMap<>();
|
||||
|
||||
// Trigger queue (smart flush so StopAfterMatch works across multiple triggers)
|
||||
private final ConcurrentHashMap<UUID, PendingTriggers> pending = new ConcurrentHashMap<>();
|
||||
|
||||
// Scheduler (timing + coalescing + delayed eval)
|
||||
private final ScheduledExecutorService scheduler;
|
||||
private volatile ScheduledFuture<?> minuteFuture;
|
||||
private final ConcurrentHashMap<UUID, ScheduledFuture<?>> flushFutures = new ConcurrentHashMap<>();
|
||||
|
||||
private static final class PendingTriggers {
|
||||
volatile boolean login;
|
||||
volatile boolean firstJoin;
|
||||
volatile boolean interval;
|
||||
volatile boolean voteCast;
|
||||
volatile boolean cooldownAny;
|
||||
volatile boolean cooldownAll;
|
||||
|
||||
final Map<String, String> placeholders = new HashMap<>();
|
||||
|
||||
void mergePlaceholders(Map<String, String> ph) {
|
||||
if (ph == null || ph.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
placeholders.putAll(ph);
|
||||
}
|
||||
|
||||
List<VoteReminderType> snapshotTypes() {
|
||||
List<VoteReminderType> out = new ArrayList<>();
|
||||
if (login)
|
||||
out.add(VoteReminderType.LOGIN);
|
||||
if (firstJoin)
|
||||
out.add(VoteReminderType.FIRST_JOIN);
|
||||
if (voteCast)
|
||||
out.add(VoteReminderType.VOTE_CAST);
|
||||
if (cooldownAll)
|
||||
out.add(VoteReminderType.COOLDOWN_END_ALL_SITES);
|
||||
if (cooldownAny)
|
||||
out.add(VoteReminderType.COOLDOWN_END_ANY_SITE);
|
||||
if (interval)
|
||||
out.add(VoteReminderType.INTERVAL);
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ========================= Constructor =========================
|
||||
*/
|
||||
|
||||
public VoteRemindersManager(VotingPluginMain plugin, VoteReminderCooldownStore store) {
|
||||
this.plugin = plugin;
|
||||
this.store = store;
|
||||
|
||||
this.scheduler = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
|
||||
private final AtomicInteger n = new AtomicInteger();
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread t = new Thread(r, "VotingPlugin-VoteReminders-" + n.incrementAndGet());
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* ========================= Lifecycle =========================
|
||||
*/
|
||||
|
||||
public void reload() {
|
||||
stopTasks();
|
||||
|
||||
loadConfig();
|
||||
this.cooldowns = new VoteReminderCooldowns(store, options.getGlobalCooldown());
|
||||
|
||||
startTasks();
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
stopTasks();
|
||||
|
||||
joinTimes.clear();
|
||||
pending.clear();
|
||||
flushFutures.clear();
|
||||
|
||||
scheduler.shutdownNow();
|
||||
}
|
||||
|
||||
private boolean isEnabled() {
|
||||
return options != null && options.isEnabled();
|
||||
}
|
||||
|
||||
/*
|
||||
* ========================= Entry points (called by ONE listener)
|
||||
* =========================
|
||||
*/
|
||||
|
||||
public void onJoin(Player player, VotingPluginUser user) {
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
}
|
||||
if (player == null || user == null) {
|
||||
return;
|
||||
}
|
||||
if (plugin.getOptions().isTreatVanishAsOffline() && user.isVanished()) {
|
||||
return;
|
||||
}
|
||||
|
||||
UUID uuid = player.getUniqueId();
|
||||
joinTimes.put(uuid, System.currentTimeMillis());
|
||||
|
||||
queueTrigger(user, VoteReminderType.LOGIN, null);
|
||||
|
||||
if (!player.hasPlayedBefore()) {
|
||||
queueTrigger(user, VoteReminderType.FIRST_JOIN, null);
|
||||
}
|
||||
|
||||
flushSoon(uuid, user.getPlayerName());
|
||||
}
|
||||
|
||||
public void onQuit(Player player) {
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
UUID uuid = player.getUniqueId();
|
||||
joinTimes.remove(uuid);
|
||||
pending.remove(uuid);
|
||||
|
||||
ScheduledFuture<?> f = flushFutures.remove(uuid);
|
||||
if (f != null) {
|
||||
f.cancel(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void onVoteCast(VotingPluginUser user, String siteKey) {
|
||||
if (!isEnabled() || user == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = Bukkit.getPlayer(user.getJavaUUID());
|
||||
if (player == null || !player.isOnline()) {
|
||||
return;
|
||||
}
|
||||
if (plugin.getOptions().isTreatVanishAsOffline() && user.isVanished()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, String> ph = new HashMap<>();
|
||||
if (siteKey != null && !siteKey.isEmpty()) {
|
||||
ph.put("site", siteKey);
|
||||
}
|
||||
|
||||
queueTrigger(user, VoteReminderType.VOTE_CAST, ph);
|
||||
flushSoon(user.getJavaUUID(), user.getPlayerName());
|
||||
}
|
||||
|
||||
public void onCooldownTrigger(VotingPluginUser user, VoteReminderType type, Map<String, String> placeholders) {
|
||||
if (!isEnabled() || user == null) {
|
||||
return;
|
||||
}
|
||||
if (type != VoteReminderType.COOLDOWN_END_ANY_SITE && type != VoteReminderType.COOLDOWN_END_ALL_SITES) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = Bukkit.getPlayer(user.getJavaUUID());
|
||||
if (player == null || !player.isOnline()) {
|
||||
return;
|
||||
}
|
||||
if (plugin.getOptions().isTreatVanishAsOffline() && user.isVanished()) {
|
||||
return;
|
||||
}
|
||||
|
||||
queueTrigger(user, type, placeholders);
|
||||
flushSoon(user.getJavaUUID(), user.getPlayerName());
|
||||
}
|
||||
|
||||
/*
|
||||
* ========================= Tasks (ScheduledExecutorService)
|
||||
* =========================
|
||||
*/
|
||||
|
||||
private void startTasks() {
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If no INTERVAL reminders exist, don't schedule anything.
|
||||
boolean hasInterval = false;
|
||||
for (VoteReminderDefinition def : reminders) {
|
||||
if (def.getType() == VoteReminderType.INTERVAL) {
|
||||
hasInterval = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasInterval) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Align to minute boundary to keep behavior stable.
|
||||
long now = System.currentTimeMillis();
|
||||
long initialDelayMs = 60_000L - (now % 60_000L);
|
||||
if (initialDelayMs <= 0L || initialDelayMs > 60_000L) {
|
||||
initialDelayMs = 60_000L;
|
||||
}
|
||||
|
||||
minuteFuture = scheduler.scheduleAtFixedRate(() -> {
|
||||
if (!isEnabled() || plugin == null || !plugin.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
fireIntervalTick();
|
||||
}, initialDelayMs, 60_000L, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
private void stopTasks() {
|
||||
ScheduledFuture<?> mf = minuteFuture;
|
||||
if (mf != null) {
|
||||
mf.cancel(false);
|
||||
minuteFuture = null;
|
||||
}
|
||||
|
||||
for (ScheduledFuture<?> f : flushFutures.values()) {
|
||||
if (f != null) {
|
||||
f.cancel(false);
|
||||
}
|
||||
}
|
||||
flushFutures.clear();
|
||||
}
|
||||
|
||||
private void fireIntervalTick() {
|
||||
long nowMs = System.currentTimeMillis();
|
||||
long nowMinute = nowMs / 60000L;
|
||||
|
||||
for (VoteReminderDefinition def : reminders) {
|
||||
if (def.getType() != VoteReminderType.INTERVAL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
long intervalMs = safeMs(def.getInterval());
|
||||
if (intervalMs <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
long intervalMinutes = Math.max(1L, intervalMs / 60000L);
|
||||
if ((nowMinute % intervalMinutes) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
VotingPluginUser u = plugin.getVotingPluginUserManager().getVotingPluginUser(p);
|
||||
if (u == null) {
|
||||
continue;
|
||||
}
|
||||
if (plugin.getOptions().isTreatVanishAsOffline() && u.isVanished()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
queueTrigger(u, VoteReminderType.INTERVAL, null);
|
||||
flushSoon(u.getJavaUUID(), u.getPlayerName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ========================= Queue + flush (smart multi-trigger handling)
|
||||
* =========================
|
||||
*/
|
||||
|
||||
private void queueTrigger(VotingPluginUser user, VoteReminderType type, Map<String, String> placeholders) {
|
||||
UUID uuid = user.getJavaUUID();
|
||||
|
||||
PendingTriggers pt = pending.computeIfAbsent(uuid, k -> new PendingTriggers());
|
||||
switch (type) {
|
||||
case LOGIN:
|
||||
pt.login = true;
|
||||
break;
|
||||
case FIRST_JOIN:
|
||||
pt.firstJoin = true;
|
||||
break;
|
||||
case VOTE_CAST:
|
||||
pt.voteCast = true;
|
||||
break;
|
||||
case COOLDOWN_END_ANY_SITE:
|
||||
pt.cooldownAny = true;
|
||||
break;
|
||||
case COOLDOWN_END_ALL_SITES:
|
||||
pt.cooldownAll = true;
|
||||
break;
|
||||
case INTERVAL:
|
||||
pt.interval = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
pt.mergePlaceholders(placeholders);
|
||||
}
|
||||
|
||||
private void flushSoon(UUID uuid, String playerName) {
|
||||
// ~1 tick coalesce without Bukkit scheduler
|
||||
flushFutures.compute(uuid, (k, existing) -> {
|
||||
if (existing != null && !existing.isDone() && !existing.isCancelled()) {
|
||||
return existing;
|
||||
}
|
||||
return scheduler.schedule(() -> {
|
||||
try {
|
||||
flush(uuid, playerName);
|
||||
} finally {
|
||||
flushFutures.remove(uuid);
|
||||
}
|
||||
}, 50L, TimeUnit.MILLISECONDS);
|
||||
});
|
||||
}
|
||||
|
||||
private void flush(UUID uuid, String playerName) {
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = Bukkit.getPlayer(uuid);
|
||||
if (player == null || !player.isOnline()) {
|
||||
pending.remove(uuid);
|
||||
return;
|
||||
}
|
||||
|
||||
VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(player);
|
||||
if (user == null) {
|
||||
pending.remove(uuid);
|
||||
return;
|
||||
}
|
||||
if (plugin.getOptions().isTreatVanishAsOffline() && user.isVanished()) {
|
||||
pending.remove(uuid);
|
||||
return;
|
||||
}
|
||||
|
||||
PendingTriggers pt = pending.remove(uuid);
|
||||
if (pt == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<VoteReminderType> types = pt.snapshotTypes();
|
||||
plugin.extraDebug("[VoteReminders] flush " + user.getPlayerName() + " triggers=" + types);
|
||||
|
||||
for (VoteReminderDefinition def : reminders) {
|
||||
if (!types.contains(def.getType())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean matched = attemptOrSchedule(user, player, def, pt.placeholders);
|
||||
|
||||
if (matched) {
|
||||
plugin.extraDebug("[VoteReminders] matched " + def.getName() + " for " + user.getPlayerName() + " type="
|
||||
+ def.getType() + " stopAfterMatch=" + options.isStopAfterMatch());
|
||||
if (options.isStopAfterMatch()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ========================= Config parsing =========================
|
||||
*/
|
||||
|
||||
private void loadConfig() {
|
||||
boolean enabled = plugin.getConfig().getBoolean("VoteReminderOptions.Enabled", true);
|
||||
boolean stopAfterMatch = plugin.getConfig().getBoolean("VoteReminderOptions.StopAfterMatch", true);
|
||||
ParsedDuration globalCooldown = ParsedDuration
|
||||
.parse(plugin.getConfig().getString("VoteReminderOptions.GlobalCooldown", ""), TimeUnit.MINUTES);
|
||||
int defaultPriority = plugin.getConfig().getInt("VoteReminderOptions.DefaultPriority", 0);
|
||||
|
||||
ParsedDuration defaultCooldown = ParsedDuration
|
||||
.parse(plugin.getConfig().getString("VoteReminderOptions.Defaults.Cooldown", ""), TimeUnit.MINUTES);
|
||||
ParsedDuration defaultDelay = ParsedDuration
|
||||
.parse(plugin.getConfig().getString("VoteReminderOptions.Defaults.Delay", ""), TimeUnit.SECONDS);
|
||||
|
||||
VoteReminderConditions defaultsConditions = new VoteReminderConditions();
|
||||
|
||||
if (plugin.getConfig().contains("VoteReminderOptions.Defaults.Conditions.CanVoteAny")) {
|
||||
defaultsConditions
|
||||
.setCanVoteAny(plugin.getConfig().getBoolean("VoteReminderOptions.Defaults.Conditions.CanVoteAny"));
|
||||
}
|
||||
if (plugin.getConfig().contains("VoteReminderOptions.Defaults.Conditions.CanVoteAll")) {
|
||||
defaultsConditions
|
||||
.setCanVoteAll(plugin.getConfig().getBoolean("VoteReminderOptions.Defaults.Conditions.CanVoteAll"));
|
||||
}
|
||||
if (plugin.getConfig().contains("VoteReminderOptions.Defaults.Conditions.MinOnlineTime")) {
|
||||
defaultsConditions.setMinOnlineTime(ParsedDuration.parse(
|
||||
plugin.getConfig().getString("VoteReminderOptions.Defaults.Conditions.MinOnlineTime", ""),
|
||||
TimeUnit.MINUTES));
|
||||
}
|
||||
if (plugin.getConfig().contains("VoteReminderOptions.Defaults.Conditions.FirstJoin")) {
|
||||
defaultsConditions
|
||||
.setFirstJoin(plugin.getConfig().getBoolean("VoteReminderOptions.Defaults.Conditions.FirstJoin"));
|
||||
}
|
||||
|
||||
String defaultRewardsPath = "VoteReminderOptions.Defaults.Rewards";
|
||||
|
||||
this.options = new VoteReminderOptions(enabled, stopAfterMatch, globalCooldown, defaultPriority,
|
||||
defaultCooldown, defaultDelay, defaultsConditions, defaultRewardsPath);
|
||||
|
||||
List<VoteReminderDefinition> defs = new ArrayList<>();
|
||||
Map<String, VoteReminderDefinition> by = new HashMap<>();
|
||||
|
||||
if (plugin.getConfig().isConfigurationSection("VoteReminders")) {
|
||||
for (String key : plugin.getConfig().getConfigurationSection("VoteReminders").getKeys(false)) {
|
||||
VoteReminderDefinition def = loadOneReminder(key);
|
||||
if (def != null) {
|
||||
defs.add(def);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defs.sort(Comparator.comparingInt(VoteReminderDefinition::getPriority).reversed()
|
||||
.thenComparing(VoteReminderDefinition::getName, String.CASE_INSENSITIVE_ORDER));
|
||||
|
||||
for (VoteReminderDefinition d : defs) {
|
||||
by.put(d.getName(), d);
|
||||
}
|
||||
|
||||
this.reminders = Collections.unmodifiableList(defs);
|
||||
this.byName = Collections.unmodifiableMap(by);
|
||||
|
||||
plugin.extraDebug("[VoteReminders] loaded reminders=" + reminders.size());
|
||||
}
|
||||
|
||||
private VoteReminderDefinition loadOneReminder(String name) {
|
||||
String base = "VoteReminders." + name + ".";
|
||||
|
||||
String typeRaw = plugin.getConfig().getString(base + "Type", "INTERVAL");
|
||||
VoteReminderType type;
|
||||
try {
|
||||
type = VoteReminderType.valueOf(typeRaw.toUpperCase(Locale.ROOT));
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning("[VoteReminders] Invalid Type for " + name + ": " + typeRaw);
|
||||
return null;
|
||||
}
|
||||
|
||||
int priority = plugin.getConfig().contains(base + "Priority") ? plugin.getConfig().getInt(base + "Priority")
|
||||
: options.getDefaultPriority();
|
||||
|
||||
ParsedDuration cooldown = ParsedDuration.parse(plugin.getConfig().getString(base + "Cooldown", ""),
|
||||
TimeUnit.MINUTES);
|
||||
if (cooldown.isEmpty()) {
|
||||
cooldown = options.getDefaultCooldown();
|
||||
}
|
||||
|
||||
ParsedDuration delay = ParsedDuration.parse(plugin.getConfig().getString(base + "Delay", ""), TimeUnit.SECONDS);
|
||||
if (delay.isEmpty()) {
|
||||
delay = options.getDefaultDelay();
|
||||
}
|
||||
|
||||
ParsedDuration interval = ParsedDuration.parse(plugin.getConfig().getString(base + "Interval", ""),
|
||||
TimeUnit.MINUTES);
|
||||
|
||||
if (type == VoteReminderType.INTERVAL && (interval == null || interval.isEmpty())) {
|
||||
plugin.getLogger().warning("[VoteReminders] INTERVAL reminder missing Interval: " + name);
|
||||
return null;
|
||||
}
|
||||
|
||||
VoteReminderConditions cond = mergeConditions(options.getDefaultsConditions(),
|
||||
loadConditions(base + "Conditions."));
|
||||
|
||||
String rewardsPath = options.getDefaultRewardsPath();
|
||||
if (plugin.getConfig().isConfigurationSection(base + "Rewards")
|
||||
|| plugin.getConfig().isList(base + "Rewards")) {
|
||||
rewardsPath = base + "Rewards";
|
||||
}
|
||||
|
||||
plugin.extraDebug("[VoteReminders] loaded " + name + " type=" + type + " priority=" + priority + " rewardsPath="
|
||||
+ rewardsPath);
|
||||
|
||||
return new VoteReminderDefinition(name, type, priority, cooldown, delay, rewardsPath, cond, interval);
|
||||
}
|
||||
|
||||
private VoteReminderConditions loadConditions(String base) {
|
||||
VoteReminderConditions c = new VoteReminderConditions();
|
||||
|
||||
if (plugin.getConfig().contains(base + "CanVoteAny")) {
|
||||
c.setCanVoteAny(plugin.getConfig().getBoolean(base + "CanVoteAny"));
|
||||
}
|
||||
if (plugin.getConfig().contains(base + "CanVoteAll")) {
|
||||
c.setCanVoteAll(plugin.getConfig().getBoolean(base + "CanVoteAll"));
|
||||
}
|
||||
if (plugin.getConfig().contains(base + "MinOnlineTime")) {
|
||||
c.setMinOnlineTime(
|
||||
ParsedDuration.parse(plugin.getConfig().getString(base + "MinOnlineTime", ""), TimeUnit.MINUTES));
|
||||
}
|
||||
if (plugin.getConfig().contains(base + "FirstJoin")) {
|
||||
c.setFirstJoin(plugin.getConfig().getBoolean(base + "FirstJoin"));
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
private VoteReminderConditions mergeConditions(VoteReminderConditions base, VoteReminderConditions override) {
|
||||
VoteReminderConditions out = new VoteReminderConditions();
|
||||
|
||||
out.setCanVoteAny(override.getCanVoteAny() != null ? override.getCanVoteAny() : base.getCanVoteAny());
|
||||
out.setCanVoteAll(override.getCanVoteAll() != null ? override.getCanVoteAll() : base.getCanVoteAll());
|
||||
out.setFirstJoin(override.getFirstJoin() != null ? override.getFirstJoin() : base.getFirstJoin());
|
||||
|
||||
if (override.getMinOnlineTime() != null && !override.getMinOnlineTime().isEmpty()) {
|
||||
out.setMinOnlineTime(override.getMinOnlineTime());
|
||||
} else {
|
||||
out.setMinOnlineTime(base.getMinOnlineTime());
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/*
|
||||
* ========================= Trigger processing =========================
|
||||
*/
|
||||
|
||||
private boolean attemptOrSchedule(VotingPluginUser user, Player player, VoteReminderDefinition def,
|
||||
Map<String, String> placeholders) {
|
||||
|
||||
if (!isEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
plugin.extraDebug("[VoteReminders] attempt " + def.getName() + " player=" + user.getPlayerName() + " type="
|
||||
+ def.getType());
|
||||
|
||||
if (!isUserReminderEnabled(user)) {
|
||||
plugin.extraDebug("[VoteReminders] gate disabled-reminders-map for " + user.getPlayerName());
|
||||
return false;
|
||||
}
|
||||
if (!hasBaseReminderPermission(user)) {
|
||||
plugin.extraDebug("[VoteReminders] gate base-permission for " + user.getPlayerName());
|
||||
return false;
|
||||
}
|
||||
|
||||
long delayMs = safeMs(def.getDelay());
|
||||
if (delayMs > 0) {
|
||||
plugin.extraDebug("[VoteReminders] schedule delay " + def.getName() + " for " + user.getPlayerName()
|
||||
+ " delayMs=" + delayMs);
|
||||
scheduleDelayedEvaluation(user.getJavaUUID(), def.getName(), placeholders, delayMs);
|
||||
return true;
|
||||
}
|
||||
|
||||
return attemptFireNow(user, player, def, placeholders);
|
||||
}
|
||||
|
||||
private void scheduleDelayedEvaluation(UUID uuid, String reminderName, Map<String, String> placeholders,
|
||||
long delayMs) {
|
||||
Map<String, String> ph = placeholders == null ? null : new HashMap<>(placeholders);
|
||||
|
||||
scheduler.schedule(() -> {
|
||||
Player p = Bukkit.getPlayer(uuid);
|
||||
if (p == null || !p.isOnline()) {
|
||||
return;
|
||||
}
|
||||
|
||||
VotingPluginUser u = plugin.getVotingPluginUserManager().getVotingPluginUser(p);
|
||||
if (u == null) {
|
||||
return;
|
||||
}
|
||||
if (plugin.getOptions().isTreatVanishAsOffline() && u.isVanished()) {
|
||||
return;
|
||||
}
|
||||
|
||||
VoteReminderDefinition def = byName.get(reminderName);
|
||||
if (def == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.extraDebug("[VoteReminders] delayed eval " + def.getName() + " for " + u.getPlayerName());
|
||||
attemptFireNow(u, p, def, ph);
|
||||
}, Math.max(1L, delayMs), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
private boolean attemptFireNow(VotingPluginUser user, Player player, VoteReminderDefinition def,
|
||||
Map<String, String> placeholders) {
|
||||
|
||||
if (!user.shouldBeReminded()) {
|
||||
plugin.extraDebug(
|
||||
"[VoteReminders] gate shouldBeReminded=false " + user.getPlayerName() + " def=" + def.getName());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!passesConditions(user, player, def.getConditions())) {
|
||||
plugin.extraDebug(
|
||||
"[VoteReminders] gate conditions=false " + user.getPlayerName() + " def=" + def.getName());
|
||||
return false;
|
||||
}
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
if (!cooldowns.tryAcquireGlobal(user.getJavaUUID(), now)) {
|
||||
plugin.extraDebug("[VoteReminders] gate globalCooldown " + user.getPlayerName() + " def=" + def.getName());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!cooldowns.canFireReminder(user.getJavaUUID(), def.getName(), now, def.getCooldown(), def.getInterval())) {
|
||||
plugin.extraDebug(
|
||||
"[VoteReminders] gate perReminderCooldown " + user.getPlayerName() + " def=" + def.getName());
|
||||
return false;
|
||||
}
|
||||
|
||||
plugin.extraDebug("[VoteReminders] FIRE " + def.getName() + " -> rewardsPath=" + def.getRewardsPath() + " user="
|
||||
+ user.getPlayerName());
|
||||
giveRewardFromPath(user, def.getRewardsPath(), placeholders);
|
||||
|
||||
cooldowns.markFired(user.getJavaUUID(), def.getName(), now);
|
||||
|
||||
plugin.extraDebug("[VoteReminders] fired " + user.getPlayerName() + " via " + def.getName());
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* ========================= Rewards =========================
|
||||
*/
|
||||
|
||||
private void giveRewardFromPath(VotingPluginUser user, String rewardsPath, Map<String, String> placeholders) {
|
||||
RewardBuilder rb = new RewardBuilder(plugin.getConfig(), rewardsPath).setGiveOffline(false)
|
||||
.disableDefaultWorlds();
|
||||
|
||||
rb.withPlaceHolder("sitesavailable", "" + user.getSitesNotVotedOn());
|
||||
|
||||
if (placeholders != null) {
|
||||
for (Map.Entry<String, String> e : placeholders.entrySet()) {
|
||||
rb.withPlaceHolder(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
rb.send(user);
|
||||
}
|
||||
|
||||
/*
|
||||
* ========================= Conditions =========================
|
||||
*/
|
||||
|
||||
private boolean passesConditions(VotingPluginUser user, Player player, VoteReminderConditions c) {
|
||||
if (c.getCanVoteAny() != null) {
|
||||
boolean canAny = user.canVoteAny();
|
||||
if (c.getCanVoteAny().booleanValue() != canAny) {
|
||||
plugin.extraDebug("[VoteReminders] gate CanVoteAny " + user.getPlayerName() + " required="
|
||||
+ c.getCanVoteAny() + " actual=" + canAny);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (c.getCanVoteAll() != null) {
|
||||
boolean canAll = user.canVoteAll();
|
||||
if (c.getCanVoteAll().booleanValue() != canAll) {
|
||||
plugin.extraDebug("[VoteReminders] gate CanVoteAll " + user.getPlayerName() + " required="
|
||||
+ c.getCanVoteAll() + " actual=" + canAll);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ParsedDuration mot = c.getMinOnlineTime();
|
||||
if (mot != null && !mot.isEmpty()) {
|
||||
Long join = joinTimes.get(user.getJavaUUID());
|
||||
if (join != null) {
|
||||
long onlineMs = System.currentTimeMillis() - join.longValue();
|
||||
long need = safeMs(mot);
|
||||
if (onlineMs < need) {
|
||||
plugin.extraDebug("[VoteReminders] gate MinOnlineTime " + user.getPlayerName() + " onlineMs="
|
||||
+ onlineMs + " needMs=" + need);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (c.getFirstJoin() != null) {
|
||||
boolean first = !player.hasPlayedBefore();
|
||||
if (c.getFirstJoin().booleanValue() != first) {
|
||||
plugin.extraDebug("[VoteReminders] gate FirstJoin " + user.getPlayerName() + " required="
|
||||
+ c.getFirstJoin() + " actual=" + first);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* ========================= Permission logic =========================
|
||||
*/
|
||||
|
||||
private boolean hasBaseReminderPermission(VotingPluginUser user) {
|
||||
return user.hasPermission("VotingPlugin.Login.RemindVotes") || user.hasPermission("VotingPlugin.Player");
|
||||
}
|
||||
|
||||
private boolean isUserReminderEnabled(VotingPluginUser user) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* ========================= Helpers =========================
|
||||
*/
|
||||
|
||||
private long safeMs(ParsedDuration d) {
|
||||
if (d == null || d.isEmpty()) {
|
||||
return 0L;
|
||||
}
|
||||
return Math.max(0L, d.getMillis());
|
||||
}
|
||||
}
|
||||
-193
@@ -1,193 +0,0 @@
|
||||
package com.bencodez.votingplugin.votereminding.store;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
import com.bencodez.votingplugin.user.VotingPluginUser;
|
||||
|
||||
/**
|
||||
* Uses VotingPluginUser.getUserData() cached columns:
|
||||
* - VoteRemindersLast (BIGINT)
|
||||
* - VoteRemindersMap (LONGTEXT) format: name=ms;name=ms
|
||||
*
|
||||
* NOTE: tryClaimGlobal is best-effort and not proxy-atomic unless your storage layer
|
||||
* supports conditional updates.
|
||||
*/
|
||||
public final class UserDataVoteReminderCooldownStore implements VoteReminderCooldownStore {
|
||||
|
||||
public static final String KEY_GLOBAL_LAST = "VoteRemindersLast";
|
||||
public static final String KEY_MAP = "VoteRemindersMap";
|
||||
|
||||
private final VotingPluginMain plugin;
|
||||
|
||||
private final ConcurrentHashMap<UUID, Map<String, Long>> mapCache = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<UUID, Object> locks = new ConcurrentHashMap<>();
|
||||
|
||||
public UserDataVoteReminderCooldownStore(VotingPluginMain plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tryClaimGlobal(UUID uuid, long nowMs, long globalCooldownMs) {
|
||||
if (globalCooldownMs <= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Object lock = locks.computeIfAbsent(uuid, k -> new Object());
|
||||
synchronized (lock) {
|
||||
VotingPluginUser user = getUser(uuid);
|
||||
if (user == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
long last = readLong(user, KEY_GLOBAL_LAST);
|
||||
if (last > 0 && (nowMs - last) < globalCooldownMs) {
|
||||
return false;
|
||||
}
|
||||
|
||||
writeLong(user, KEY_GLOBAL_LAST, nowMs);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Long> getPerReminderMap(UUID uuid) {
|
||||
Map<String, Long> cached = mapCache.get(uuid);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
VotingPluginUser user = getUser(uuid);
|
||||
if (user == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
String raw = safe(user.getUserData().getString(KEY_MAP));
|
||||
Map<String, Long> parsed = parseMap(raw);
|
||||
|
||||
mapCache.put(uuid, parsed);
|
||||
return parsed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPerReminderLast(UUID uuid, String reminderName, long nowMs) {
|
||||
if (reminderName == null || reminderName.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object lock = locks.computeIfAbsent(uuid, k -> new Object());
|
||||
synchronized (lock) {
|
||||
VotingPluginUser user = getUser(uuid);
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Long> map = getPerReminderMap(uuid);
|
||||
if (!(map instanceof HashMap)) {
|
||||
map = new HashMap<>(map);
|
||||
mapCache.put(uuid, map);
|
||||
}
|
||||
|
||||
map.put(reminderName, nowMs);
|
||||
|
||||
String encoded = encodeMap(map);
|
||||
user.getUserData().setString(KEY_MAP, encoded);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPersistent(UUID uuid) {
|
||||
VotingPluginUser user = getUser(uuid);
|
||||
if (user == null) {
|
||||
return false;
|
||||
}
|
||||
return user.getData().hasData();
|
||||
}
|
||||
|
||||
public void clearCache(UUID uuid) {
|
||||
mapCache.remove(uuid);
|
||||
locks.remove(uuid);
|
||||
}
|
||||
|
||||
private VotingPluginUser getUser(UUID uuid) {
|
||||
return plugin.getVotingPluginUserManager().getVotingPluginUser(uuid, false);
|
||||
}
|
||||
|
||||
private long readLong(VotingPluginUser user, String key) {
|
||||
String s = safe(user.getUserData().getString(key));
|
||||
if (s.isEmpty() || s.equalsIgnoreCase("null")) {
|
||||
return 0L;
|
||||
}
|
||||
try {
|
||||
return Long.parseLong(s);
|
||||
} catch (NumberFormatException e) {
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
|
||||
private void writeLong(VotingPluginUser user, String key, long value) {
|
||||
user.getUserData().setString(key, Long.toString(Math.max(0L, value)));
|
||||
}
|
||||
|
||||
private String safe(String s) {
|
||||
return s == null ? "" : s.trim();
|
||||
}
|
||||
|
||||
private Map<String, Long> parseMap(String raw) {
|
||||
HashMap<String, Long> out = new HashMap<>();
|
||||
if (raw == null || raw.isEmpty()) {
|
||||
return out;
|
||||
}
|
||||
|
||||
String[] parts = raw.split(";");
|
||||
for (String part : parts) {
|
||||
if (part == null || part.isEmpty())
|
||||
continue;
|
||||
|
||||
int idx = part.indexOf('=');
|
||||
if (idx <= 0 || idx >= part.length() - 1)
|
||||
continue;
|
||||
|
||||
String name = part.substring(0, idx).trim();
|
||||
String msStr = part.substring(idx + 1).trim();
|
||||
if (name.isEmpty() || msStr.isEmpty())
|
||||
continue;
|
||||
|
||||
try {
|
||||
long ms = Long.parseLong(msStr);
|
||||
if (ms > 0) {
|
||||
out.put(name, ms);
|
||||
}
|
||||
} catch (NumberFormatException ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
private String encodeMap(Map<String, Long> map) {
|
||||
if (map == null || map.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
map.entrySet().stream().sorted((a, b) -> String.CASE_INSENSITIVE_ORDER.compare(a.getKey(), b.getKey()))
|
||||
.forEach(e -> {
|
||||
if (e.getKey() == null)
|
||||
return;
|
||||
long v = e.getValue() == null ? 0L : e.getValue().longValue();
|
||||
if (v <= 0L)
|
||||
return;
|
||||
|
||||
if (sb.length() > 0)
|
||||
sb.append(';');
|
||||
sb.append(e.getKey()).append('=').append(v);
|
||||
});
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
package com.bencodez.votingplugin.votereminding.store;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface VoteReminderCooldownStore {
|
||||
boolean tryClaimGlobal(UUID uuid, long nowMs, long globalCooldownMs);
|
||||
|
||||
Map<String, Long> getPerReminderMap(UUID uuid);
|
||||
|
||||
void setPerReminderLast(UUID uuid, String reminderName, long nowMs);
|
||||
|
||||
boolean isPersistent(UUID uuid);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.bencodez.votingplugin.votesites;
|
||||
public final class NextSite {
|
||||
|
||||
private final VoteSite site;
|
||||
private final long secondsUntilAvailable;
|
||||
|
||||
public NextSite(VoteSite site, long secondsUntilAvailable) {
|
||||
this.site = site;
|
||||
this.secondsUntilAvailable = secondsUntilAvailable;
|
||||
}
|
||||
|
||||
public VoteSite getSite() {
|
||||
return site;
|
||||
}
|
||||
|
||||
public long getSecondsUntilAvailable() {
|
||||
return secondsUntilAvailable;
|
||||
}
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
package com.bencodez.votingplugin.votesites;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.bencodez.votingplugin.VotingPluginMain;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
public class VoteSiteManager {
|
||||
|
||||
private final VotingPluginMain plugin;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private List<VoteSite> voteSites = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
public VoteSiteManager(VotingPluginMain plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads votesites from config and returns the backing list.
|
||||
*/
|
||||
public List<VoteSite> loadVoteSites() {
|
||||
plugin.getConfigVoteSites().setup();
|
||||
|
||||
List<VoteSite> newSites = Collections.synchronizedList(new ArrayList<>());
|
||||
newSites.addAll(plugin.getConfigVoteSites().getVoteSitesLoad());
|
||||
voteSites = newSites;
|
||||
|
||||
if (voteSites.isEmpty()) {
|
||||
plugin.getLogger().warning("Detected no voting sites, this may mean something isn't properly setup");
|
||||
}
|
||||
|
||||
plugin.debug("Loaded VoteSites");
|
||||
return voteSites;
|
||||
}
|
||||
/**
|
||||
* Attempts to map a URL / display name / key to the configured "site name".
|
||||
*
|
||||
* @param checkEnabled Whether to only consider enabled vote sites.
|
||||
* @param urls One or more URLs / names to resolve.
|
||||
* @return The resolved vote site name, or the first provided value if no match
|
||||
*/
|
||||
public String getVoteSiteName(boolean checkEnabled, String... urls) {
|
||||
ArrayList<String> sites = plugin.getConfigVoteSites().getVoteSitesNames(checkEnabled);
|
||||
|
||||
for (String url : urls) {
|
||||
if (url == null) {
|
||||
return null;
|
||||
}
|
||||
if (!url.isEmpty() && sites != null) {
|
||||
for (String siteName : sites) {
|
||||
String configuredUrl = plugin.getConfigVoteSites().getServiceSite(siteName);
|
||||
if (configuredUrl != null && configuredUrl.equalsIgnoreCase(url)) {
|
||||
return siteName;
|
||||
}
|
||||
if (siteName.equalsIgnoreCase(url)) {
|
||||
return siteName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no match, return the first provided value (original behavior)
|
||||
for (String url : urls) {
|
||||
return url;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a VoteSite from an identifier.
|
||||
* - Matches on key or display name
|
||||
* - Can optionally auto-create vote sites (same behavior as VotingPluginMain)
|
||||
*/
|
||||
public VoteSite getVoteSite(String site, boolean checkEnabled) {
|
||||
String siteName = getVoteSiteName(checkEnabled, site);
|
||||
|
||||
for (VoteSite voteSite : getVoteSites()) {
|
||||
if (voteSite.getKey().equalsIgnoreCase(siteName) || voteSite.getDisplayName().equals(siteName)) {
|
||||
return voteSite;
|
||||
}
|
||||
}
|
||||
|
||||
if (plugin.getConfigFile().isAutoCreateVoteSites()
|
||||
&& !plugin.getConfigVoteSites().getVoteSitesNames(false).contains(siteName)) {
|
||||
|
||||
plugin.getConfigVoteSites().generateVoteSite(siteName);
|
||||
|
||||
// Original code used: new VoteSite(plugin, siteName.replace(".", "_"));
|
||||
// Keep identical behavior.
|
||||
return new VoteSite(plugin, siteName.replace(".", "_"));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public ArrayList<VoteSite> getVoteSitesEnabled() {
|
||||
ArrayList<VoteSite> sites = new ArrayList<>();
|
||||
for (VoteSite site : getVoteSites()) {
|
||||
if (site.isEnabled()) {
|
||||
sites.add(site);
|
||||
}
|
||||
}
|
||||
return sites;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an input name/url into the configured service site URL if possible.
|
||||
* If no mapping exists, returns the input (original behavior).
|
||||
*/
|
||||
public String getVoteSiteServiceSite(String name) {
|
||||
ArrayList<String> sites = plugin.getConfigVoteSites().getVoteSitesNames(true);
|
||||
if (name == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (sites != null) {
|
||||
for (String siteName : sites) {
|
||||
String url = plugin.getConfigVoteSites().getServiceSite(siteName);
|
||||
if (url != null) {
|
||||
if (url.equalsIgnoreCase(name) || name.equalsIgnoreCase(siteName)) {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
public boolean hasVoteSite(String site) {
|
||||
String siteName = getVoteSiteName(false, site);
|
||||
for (VoteSite voteSite : getVoteSites()) {
|
||||
if (voteSite.getKey().equalsIgnoreCase(siteName) || voteSite.getDisplayName().equals(siteName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isVoteSite(String voteSite) {
|
||||
for (VoteSite site : getVoteSites()) {
|
||||
if (site.getKey().equalsIgnoreCase(voteSite)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
-72
@@ -1,72 +0,0 @@
|
||||
package com.bencodez.votingplugin.webhook;
|
||||
|
||||
/**
|
||||
* Utility methods for handling Discord webhook rate-limit responses.
|
||||
*/
|
||||
public final class DiscordRateLimitUtil {
|
||||
|
||||
private DiscordRateLimitUtil() {
|
||||
// utility
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts Discord's "retry_after" value from a JSON string.
|
||||
*
|
||||
* <p>Discord rate-limit bodies typically look like:
|
||||
* {"message":"You are being rate limited.","retry_after":1.234,"global":false}</p>
|
||||
*
|
||||
* <p>This method avoids requiring a JSON library by doing a small string parse.
|
||||
* If parsing fails, returns 0.</p>
|
||||
*
|
||||
* @param body response body
|
||||
* @return retry_after in milliseconds, or 0 if not found/parseable
|
||||
*/
|
||||
public static long extractRetryAfterMs(String body) {
|
||||
if (body == null || body.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int idx = body.indexOf("\"retry_after\"");
|
||||
if (idx == -1) {
|
||||
idx = body.indexOf("retry_after");
|
||||
if (idx == -1) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int colon = body.indexOf(':', idx);
|
||||
if (colon == -1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int start = colon + 1;
|
||||
while (start < body.length() && Character.isWhitespace(body.charAt(start))) {
|
||||
start++;
|
||||
}
|
||||
|
||||
int end = start;
|
||||
while (end < body.length()) {
|
||||
char c = body.charAt(end);
|
||||
if ((c >= '0' && c <= '9') || c == '.' || c == '-') {
|
||||
end++;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (end <= start) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
double seconds = Double.parseDouble(body.substring(start, end));
|
||||
if (seconds <= 0) {
|
||||
return 0;
|
||||
}
|
||||
// cushion so we don't immediately collide again
|
||||
return (long) Math.ceil(seconds * 1000.0) + 250L;
|
||||
} catch (NumberFormatException ignored) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
-191
@@ -1,191 +0,0 @@
|
||||
package com.bencodez.votingplugin.webhook;
|
||||
|
||||
/**
|
||||
* Builds Discord-compatible webhook JSON payloads.
|
||||
*
|
||||
* <p>This builder only outputs fields that are set/enabled.</p>
|
||||
*/
|
||||
public final class DiscordWebhookPayloadBuilder {
|
||||
|
||||
private String username;
|
||||
private String avatarUrl;
|
||||
|
||||
private String content;
|
||||
|
||||
private boolean embedEnabled;
|
||||
private String embedTitle;
|
||||
private String embedDescription;
|
||||
private String embedColorHex;
|
||||
private String embedThumbnailUrl;
|
||||
|
||||
private boolean footerEnabled;
|
||||
private String footerText;
|
||||
|
||||
/** @return new builder */
|
||||
public static DiscordWebhookPayloadBuilder create() {
|
||||
return new DiscordWebhookPayloadBuilder();
|
||||
}
|
||||
|
||||
private DiscordWebhookPayloadBuilder() {
|
||||
// builder
|
||||
}
|
||||
|
||||
/** @param username username override */
|
||||
public DiscordWebhookPayloadBuilder username(String username) {
|
||||
this.username = username;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @param avatarUrl avatar URL override */
|
||||
public DiscordWebhookPayloadBuilder avatarUrl(String avatarUrl) {
|
||||
this.avatarUrl = avatarUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @param content message content (plain text/markdown) */
|
||||
public DiscordWebhookPayloadBuilder content(String content) {
|
||||
this.content = content;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables an embed.
|
||||
*
|
||||
* @param title embed title
|
||||
* @param description embed description
|
||||
* @param colorHex color in #RRGGBB (optional)
|
||||
* @param thumbnailUrl thumbnail URL (optional)
|
||||
* @param footerEnabled whether footer is enabled
|
||||
* @param footerText footer text (optional)
|
||||
* @return builder
|
||||
*/
|
||||
public DiscordWebhookPayloadBuilder embed(String title, String description, String colorHex, String thumbnailUrl,
|
||||
boolean footerEnabled, String footerText) {
|
||||
this.embedEnabled = true;
|
||||
this.embedTitle = title;
|
||||
this.embedDescription = description;
|
||||
this.embedColorHex = colorHex;
|
||||
this.embedThumbnailUrl = thumbnailUrl;
|
||||
this.footerEnabled = footerEnabled;
|
||||
this.footerText = footerText;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a JSON payload string suitable for Discord webhooks.
|
||||
*
|
||||
* @return JSON string
|
||||
*/
|
||||
public String buildJson() {
|
||||
StringBuilder sb = new StringBuilder(256);
|
||||
sb.append('{');
|
||||
|
||||
boolean first = true;
|
||||
|
||||
first = appendString(sb, "username", username, first);
|
||||
first = appendString(sb, "avatar_url", avatarUrl, first);
|
||||
first = appendString(sb, "content", content, first);
|
||||
|
||||
if (embedEnabled) {
|
||||
if (!first) {
|
||||
sb.append(',');
|
||||
}
|
||||
sb.append("\"embeds\":[{");
|
||||
boolean eFirst = true;
|
||||
|
||||
eFirst = appendString(sb, "title", embedTitle, eFirst);
|
||||
eFirst = appendString(sb, "description", embedDescription, eFirst);
|
||||
|
||||
Integer color = parseHexColor(embedColorHex);
|
||||
if (color != null) {
|
||||
if (!eFirst) {
|
||||
sb.append(',');
|
||||
}
|
||||
sb.append("\"color\":").append(color.intValue());
|
||||
eFirst = false;
|
||||
}
|
||||
|
||||
if (embedThumbnailUrl != null && !embedThumbnailUrl.isEmpty()) {
|
||||
if (!eFirst) {
|
||||
sb.append(',');
|
||||
}
|
||||
sb.append("\"thumbnail\":{");
|
||||
sb.append("\"url\":\"").append(escapeJson(embedThumbnailUrl)).append("\"");
|
||||
sb.append('}');
|
||||
eFirst = false;
|
||||
}
|
||||
|
||||
if (footerEnabled && footerText != null && !footerText.isEmpty()) {
|
||||
if (!eFirst) {
|
||||
sb.append(',');
|
||||
}
|
||||
sb.append("\"footer\":{");
|
||||
sb.append("\"text\":\"").append(escapeJson(footerText)).append("\"");
|
||||
sb.append('}');
|
||||
}
|
||||
|
||||
sb.append("}]");
|
||||
first = false;
|
||||
}
|
||||
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static boolean appendString(StringBuilder sb, String key, String value, boolean first) {
|
||||
if (value == null || value.isEmpty()) {
|
||||
return first;
|
||||
}
|
||||
if (!first) {
|
||||
sb.append(',');
|
||||
}
|
||||
sb.append('"').append(key).append("\":\"").append(escapeJson(value)).append('"');
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Integer parseHexColor(String hex) {
|
||||
if (hex == null || hex.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String h = hex.trim();
|
||||
if (h.startsWith("#")) {
|
||||
h = h.substring(1);
|
||||
}
|
||||
if (h.length() != 6) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Integer.valueOf(Integer.parseInt(h, 16));
|
||||
} catch (NumberFormatException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String escapeJson(String s) {
|
||||
StringBuilder out = new StringBuilder(s.length() + 16);
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
switch (c) {
|
||||
case '\\':
|
||||
out.append("\\\\");
|
||||
break;
|
||||
case '"':
|
||||
out.append("\\\"");
|
||||
break;
|
||||
case '\n':
|
||||
out.append("\\n");
|
||||
break;
|
||||
case '\r':
|
||||
out.append("\\r");
|
||||
break;
|
||||
case '\t':
|
||||
out.append("\\t");
|
||||
break;
|
||||
default:
|
||||
out.append(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return out.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.bencodez.votingplugin.webhook;
|
||||
|
||||
/**
|
||||
* Placeholder expansion abstraction.
|
||||
*
|
||||
* <p>VotingPlugin should supply an implementation that expands placeholders
|
||||
* such as {PLAYER}, {SERVICE_SITE}, etc.</p>
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface PlaceholderExpander {
|
||||
|
||||
/**
|
||||
* Expands placeholders inside a string.
|
||||
*
|
||||
* @param input input string (may be null)
|
||||
* @return expanded string (never null)
|
||||
*/
|
||||
String expand(String input);
|
||||
}
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
package com.bencodez.votingplugin.webhook;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
/**
|
||||
* Convenience wrapper to manage webhook lifecycle within VotingPlugin.
|
||||
*/
|
||||
public final class VotingPluginWebhooks {
|
||||
|
||||
private final WebhookService service;
|
||||
private volatile Map<String, WebhookDefinition> definitions = Collections.emptyMap();
|
||||
|
||||
/**
|
||||
* Creates a webhook manager.
|
||||
*
|
||||
* @param plugin plugin instance
|
||||
*/
|
||||
public VotingPluginWebhooks(JavaPlugin plugin) {
|
||||
Objects.requireNonNull(plugin, "plugin");
|
||||
this.service = new WebhookService(msg -> plugin.getLogger().warning(msg));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads webhook definitions from config and ensures the service is started.
|
||||
*
|
||||
* @param webhooksRoot root section (usually config.getConfigurationSection("Webhooks"))
|
||||
*/
|
||||
public void reload(ConfigurationSection webhooksRoot) {
|
||||
Map<String, WebhookDefinition> defs = WebhookConfigLoader.load(webhooksRoot);
|
||||
this.definitions = defs;
|
||||
service.setDefinitions(defs);
|
||||
service.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the webhook service.
|
||||
*/
|
||||
public void shutdown() {
|
||||
service.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a reward executor using the current definitions.
|
||||
*
|
||||
* @return executor
|
||||
*/
|
||||
public WebhookRewardExecutor createExecutor() {
|
||||
return new WebhookRewardExecutor(service, definitions);
|
||||
}
|
||||
}
|
||||
-133
@@ -1,133 +0,0 @@
|
||||
package com.bencodez.votingplugin.webhook;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
/**
|
||||
* Loads webhook definitions from VotingPlugin's Config.yml section.
|
||||
*
|
||||
* Expected structure under the root section you pass:
|
||||
* <pre>
|
||||
* Enabled: true
|
||||
* Definitions:
|
||||
* myhook:
|
||||
* Enabled: true
|
||||
* Url: "https://..."
|
||||
* Method: POST
|
||||
* ContentType: "application/json"
|
||||
* TimeoutMs: 8000
|
||||
* Headers:
|
||||
* Authorization: "Bearer {API_KEY}"
|
||||
* HandleDiscordRateLimits: true
|
||||
* Retry:
|
||||
* Enabled: true
|
||||
* MaxAttempts: 5
|
||||
* BackoffMs: 1500
|
||||
* MaxBackoffMs: 15000
|
||||
* Signature:
|
||||
* Enabled: false
|
||||
* Algo: "HMAC_SHA256"
|
||||
* Header: "X-Signature"
|
||||
* Secret: "secret"
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
public final class WebhookConfigLoader {
|
||||
|
||||
private WebhookConfigLoader() {
|
||||
// utility
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads webhook definitions.
|
||||
*
|
||||
* @param webhooksRoot root section for webhooks (e.g., config.getConfigurationSection("Webhooks"))
|
||||
* @return definitions by id (never null)
|
||||
*/
|
||||
public static Map<String, WebhookDefinition> load(ConfigurationSection webhooksRoot) {
|
||||
if (webhooksRoot == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
if (!webhooksRoot.getBoolean("Enabled", false)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
ConfigurationSection defsSec = webhooksRoot.getConfigurationSection("Definitions");
|
||||
if (defsSec == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
Map<String, WebhookDefinition> out = new LinkedHashMap<>();
|
||||
|
||||
for (String id : defsSec.getKeys(false)) {
|
||||
ConfigurationSection s = defsSec.getConfigurationSection(id);
|
||||
if (s == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean defEnabled = s.getBoolean("Enabled", true);
|
||||
String url = s.getString("Url", "");
|
||||
if (url == null || url.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
WebhookHttpMethod method = WebhookHttpMethod.parse(s.getString("Method", "POST"));
|
||||
String contentType = s.getString("ContentType", "application/json");
|
||||
int timeoutMs = s.getInt("TimeoutMs", 8000);
|
||||
|
||||
Map<String, String> headers = loadStringMap(s.getConfigurationSection("Headers"));
|
||||
|
||||
boolean handleDiscord = s.getBoolean("HandleDiscordRateLimits", true);
|
||||
|
||||
// Retry
|
||||
boolean retryEnabled = true;
|
||||
int maxAttempts = 5;
|
||||
long backoffMs = 1500;
|
||||
long maxBackoffMs = 15000;
|
||||
|
||||
ConfigurationSection retrySec = s.getConfigurationSection("Retry");
|
||||
if (retrySec != null) {
|
||||
retryEnabled = retrySec.getBoolean("Enabled", true);
|
||||
maxAttempts = retrySec.getInt("MaxAttempts", 5);
|
||||
backoffMs = retrySec.getLong("BackoffMs", 1500);
|
||||
maxBackoffMs = retrySec.getLong("MaxBackoffMs", 15000);
|
||||
}
|
||||
|
||||
// Signature
|
||||
WebhookSignatureConfig signature = null;
|
||||
ConfigurationSection sigSec = s.getConfigurationSection("Signature");
|
||||
if (sigSec != null) {
|
||||
boolean sigEnabled = sigSec.getBoolean("Enabled", false);
|
||||
String algo = sigSec.getString("Algo", "HMAC_SHA256");
|
||||
String header = sigSec.getString("Header", "X-Signature");
|
||||
String secret = sigSec.getString("Secret", "");
|
||||
signature = new WebhookSignatureConfig(sigEnabled, algo, header, secret);
|
||||
}
|
||||
|
||||
WebhookDefinition def = new WebhookDefinition(id, defEnabled, url, method, contentType, timeoutMs, handleDiscord,
|
||||
headers, signature, retryEnabled, maxAttempts, backoffMs, maxBackoffMs);
|
||||
|
||||
out.put(id, def);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
private static Map<String, String> loadStringMap(ConfigurationSection sec) {
|
||||
if (sec == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
Map<String, String> out = new LinkedHashMap<>();
|
||||
for (String k : sec.getKeys(false)) {
|
||||
Object v = sec.get(k);
|
||||
if (v != null) {
|
||||
out.put(k, String.valueOf(v));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
package com.bencodez.votingplugin.webhook;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Webhook endpoint definition.
|
||||
*
|
||||
* <p>These are loaded from Config.yml under Webhooks.Definitions.*</p>
|
||||
*/
|
||||
public final class WebhookDefinition {
|
||||
|
||||
private final String id;
|
||||
private final boolean enabled;
|
||||
private final String url;
|
||||
|
||||
private final WebhookHttpMethod method;
|
||||
private final String contentType;
|
||||
|
||||
private final int timeoutMs;
|
||||
|
||||
private final boolean handleDiscordRateLimits;
|
||||
|
||||
private final Map<String, String> headers;
|
||||
private final WebhookSignatureConfig signature;
|
||||
|
||||
private final boolean retryEnabled;
|
||||
private final int retryMaxAttempts;
|
||||
private final long retryBackoffMs;
|
||||
private final long retryMaxBackoffMs;
|
||||
|
||||
private volatile long lastRetryAfterMs;
|
||||
|
||||
/**
|
||||
* Constructs a webhook definition.
|
||||
*
|
||||
* @param id id/key of this webhook definition
|
||||
* @param enabled whether this definition is enabled
|
||||
* @param url webhook URL
|
||||
* @param method HTTP method
|
||||
* @param contentType request content type (usually application/json)
|
||||
* @param timeoutMs request timeout in milliseconds
|
||||
* @param handleDiscordRateLimits whether to use Discord retry_after for HTTP 429
|
||||
* @param headers extra headers (values may contain placeholders)
|
||||
* @param signature signature config (optional)
|
||||
* @param retryEnabled whether retry is enabled
|
||||
* @param retryMaxAttempts max attempts including the first attempt
|
||||
* @param retryBackoffMs initial backoff in milliseconds
|
||||
* @param retryMaxBackoffMs maximum backoff in milliseconds
|
||||
*/
|
||||
public WebhookDefinition(String id, boolean enabled, String url, WebhookHttpMethod method, String contentType,
|
||||
int timeoutMs, boolean handleDiscordRateLimits, Map<String, String> headers, WebhookSignatureConfig signature,
|
||||
boolean retryEnabled, int retryMaxAttempts, long retryBackoffMs, long retryMaxBackoffMs) {
|
||||
|
||||
this.id = id;
|
||||
this.enabled = enabled;
|
||||
this.url = url;
|
||||
|
||||
this.method = method == null ? WebhookHttpMethod.POST : method;
|
||||
this.contentType = (contentType == null || contentType.isEmpty()) ? "application/json" : contentType;
|
||||
|
||||
this.timeoutMs = timeoutMs <= 0 ? 8000 : timeoutMs;
|
||||
this.handleDiscordRateLimits = handleDiscordRateLimits;
|
||||
|
||||
this.headers = headers == null ? Collections.emptyMap()
|
||||
: Collections.unmodifiableMap(new LinkedHashMap<>(headers));
|
||||
this.signature = signature;
|
||||
|
||||
this.retryEnabled = retryEnabled;
|
||||
this.retryMaxAttempts = retryMaxAttempts <= 0 ? 1 : retryMaxAttempts;
|
||||
this.retryBackoffMs = Math.max(0, retryBackoffMs);
|
||||
this.retryMaxBackoffMs = Math.max(this.retryBackoffMs, retryMaxBackoffMs);
|
||||
}
|
||||
|
||||
/** @return definition id */
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/** @return true if enabled */
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/** @return webhook URL */
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
/** @return HTTP method */
|
||||
public WebhookHttpMethod getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
/** @return content type */
|
||||
public String getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
/** @return timeout in milliseconds */
|
||||
public int getTimeoutMs() {
|
||||
return timeoutMs;
|
||||
}
|
||||
|
||||
/** @return true if Discord 429 retry_after handling is enabled */
|
||||
public boolean isHandleDiscordRateLimits() {
|
||||
return handleDiscordRateLimits;
|
||||
}
|
||||
|
||||
/** @return extra headers (unmodifiable) */
|
||||
public Map<String, String> getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
/** @return signature config (may be null) */
|
||||
public WebhookSignatureConfig getSignature() {
|
||||
return signature;
|
||||
}
|
||||
|
||||
/** @return true if retry is enabled */
|
||||
public boolean isRetryEnabled() {
|
||||
return retryEnabled;
|
||||
}
|
||||
|
||||
/** @return max attempts */
|
||||
public int getRetryMaxAttempts() {
|
||||
return retryMaxAttempts;
|
||||
}
|
||||
|
||||
/** @return initial backoff in ms */
|
||||
public long getRetryBackoffMs() {
|
||||
return retryBackoffMs;
|
||||
}
|
||||
|
||||
/** @return max backoff in ms */
|
||||
public long getRetryMaxBackoffMs() {
|
||||
return retryMaxBackoffMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a safe URL string for logs (removes sensitive token segments).
|
||||
*
|
||||
* @return sanitized URL for logging
|
||||
*/
|
||||
public String safeUrlForLog() {
|
||||
if (url == null) {
|
||||
return "null";
|
||||
}
|
||||
// Discord webhooks: https://discord.com/api/webhooks/{id}/{token}
|
||||
int idx = url.indexOf("/api/webhooks/");
|
||||
if (idx == -1) {
|
||||
return url;
|
||||
}
|
||||
String prefix = url.substring(0, idx);
|
||||
String rest = url.substring(idx);
|
||||
String[] parts = rest.split("/");
|
||||
if (parts.length >= 5) {
|
||||
return prefix + "/api/webhooks/" + parts[3] + "/REDACTED";
|
||||
}
|
||||
return prefix + "/api/webhooks/REDACTED";
|
||||
}
|
||||
|
||||
/** @return retry_after in ms, or 0 if none */
|
||||
public long getLastRetryAfterMs() {
|
||||
return lastRetryAfterMs;
|
||||
}
|
||||
|
||||
/** @param lastRetryAfterMs retry delay in ms */
|
||||
public void setLastRetryAfterMs(long lastRetryAfterMs) {
|
||||
this.lastRetryAfterMs = lastRetryAfterMs;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.bencodez.votingplugin.webhook;
|
||||
|
||||
/**
|
||||
* Supported HTTP methods for webhook requests.
|
||||
*/
|
||||
public enum WebhookHttpMethod {
|
||||
POST, PUT, PATCH;
|
||||
|
||||
/**
|
||||
* Parses a method string safely.
|
||||
*
|
||||
* @param s method string
|
||||
* @return parsed method or POST if invalid/null
|
||||
*/
|
||||
public static WebhookHttpMethod parse(String s) {
|
||||
if (s == null) {
|
||||
return POST;
|
||||
}
|
||||
try {
|
||||
return WebhookHttpMethod.valueOf(s.trim().toUpperCase());
|
||||
} catch (Exception ignored) {
|
||||
return POST;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.bencodez.votingplugin.webhook;
|
||||
|
||||
/**
|
||||
* Logging abstraction for the webhook subsystem.
|
||||
*
|
||||
* <p>This avoids hard-coding Bukkit/SLF4J implementations into the core sender.</p>
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface WebhookLogger {
|
||||
|
||||
/**
|
||||
* Logs a warning message.
|
||||
*
|
||||
* @param message message
|
||||
*/
|
||||
void warn(String message);
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package com.bencodez.votingplugin.webhook;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A queued webhook request.
|
||||
*
|
||||
* <p>All placeholders should be expanded before creating this object.</p>
|
||||
*/
|
||||
public final class WebhookRequest {
|
||||
|
||||
private final String definitionId;
|
||||
private final String body;
|
||||
private final Map<String, String> headers;
|
||||
private final String signatureSecretOverride;
|
||||
|
||||
/**
|
||||
* Creates a webhook request.
|
||||
*
|
||||
* @param definitionId definition id
|
||||
* @param body request body (usually JSON)
|
||||
* @param headers expanded headers (may be null)
|
||||
* @param signatureSecretOverride expanded signature secret override (may be null)
|
||||
*/
|
||||
public WebhookRequest(String definitionId, String body, Map<String, String> headers, String signatureSecretOverride) {
|
||||
this.definitionId = definitionId;
|
||||
this.body = body;
|
||||
this.headers = headers;
|
||||
this.signatureSecretOverride = signatureSecretOverride;
|
||||
}
|
||||
|
||||
/** @return definition id */
|
||||
public String getDefinitionId() {
|
||||
return definitionId;
|
||||
}
|
||||
|
||||
/** @return body */
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
/** @return headers (may be null) */
|
||||
public Map<String, String> getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
/** @return signature secret override (may be null) */
|
||||
public String getSignatureSecretOverride() {
|
||||
return signatureSecretOverride;
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
package com.bencodez.votingplugin.webhook;
|
||||
|
||||
/**
|
||||
* Represents one webhook entry under a reward's "Webhooks:" list.
|
||||
*
|
||||
* Two payload styles:
|
||||
* <ul>
|
||||
* <li>Raw JSON: {@link #getJson()}</li>
|
||||
* <li>Discord convenience payload: content + optional embed fields</li>
|
||||
* </ul>
|
||||
*
|
||||
*
|
||||
* All fields may contain placeholders and are expanded at execution time.
|
||||
*/
|
||||
public final class WebhookRewardEntry {
|
||||
|
||||
private final String webhookId;
|
||||
|
||||
// Generic mode
|
||||
private final String json;
|
||||
|
||||
// Discord convenience mode
|
||||
private final String content;
|
||||
|
||||
private final boolean embedEnabled;
|
||||
private final String embedTitle;
|
||||
private final String embedDescription;
|
||||
private final String embedColorHex;
|
||||
private final String embedThumbnailUrl;
|
||||
|
||||
private final boolean footerEnabled;
|
||||
private final String footerText;
|
||||
|
||||
/**
|
||||
* Creates a reward entry.
|
||||
*
|
||||
* @param webhookId webhook definition id
|
||||
* @param json raw json payload (nullable)
|
||||
* @param content discord content (nullable)
|
||||
* @param embedEnabled embed enabled
|
||||
* @param embedTitle embed title
|
||||
* @param embedDescription embed description
|
||||
* @param embedColorHex hex color #RRGGBB (nullable)
|
||||
* @param embedThumbnailUrl thumbnail url (nullable)
|
||||
* @param footerEnabled footer enabled
|
||||
* @param footerText footer text (nullable)
|
||||
*/
|
||||
public WebhookRewardEntry(String webhookId, String json, String content, boolean embedEnabled, String embedTitle,
|
||||
String embedDescription, String embedColorHex, String embedThumbnailUrl, boolean footerEnabled,
|
||||
String footerText) {
|
||||
this.webhookId = webhookId;
|
||||
this.json = json;
|
||||
this.content = content;
|
||||
this.embedEnabled = embedEnabled;
|
||||
this.embedTitle = embedTitle;
|
||||
this.embedDescription = embedDescription;
|
||||
this.embedColorHex = embedColorHex;
|
||||
this.embedThumbnailUrl = embedThumbnailUrl;
|
||||
this.footerEnabled = footerEnabled;
|
||||
this.footerText = footerText;
|
||||
}
|
||||
|
||||
/** @return webhook definition id */
|
||||
public String getWebhookId() {
|
||||
return webhookId;
|
||||
}
|
||||
|
||||
/** @return raw json payload or null */
|
||||
public String getJson() {
|
||||
return json;
|
||||
}
|
||||
|
||||
/** @return content string or null */
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
/** @return true if embed enabled */
|
||||
public boolean isEmbedEnabled() {
|
||||
return embedEnabled;
|
||||
}
|
||||
|
||||
/** @return embed title */
|
||||
public String getEmbedTitle() {
|
||||
return embedTitle;
|
||||
}
|
||||
|
||||
/** @return embed description */
|
||||
public String getEmbedDescription() {
|
||||
return embedDescription;
|
||||
}
|
||||
|
||||
/** @return embed color hex */
|
||||
public String getEmbedColorHex() {
|
||||
return embedColorHex;
|
||||
}
|
||||
|
||||
/** @return embed thumbnail url */
|
||||
public String getEmbedThumbnailUrl() {
|
||||
return embedThumbnailUrl;
|
||||
}
|
||||
|
||||
/** @return footer enabled */
|
||||
public boolean isFooterEnabled() {
|
||||
return footerEnabled;
|
||||
}
|
||||
|
||||
/** @return footer text */
|
||||
public String getFooterText() {
|
||||
return footerText;
|
||||
}
|
||||
|
||||
/** @return true if using raw json mode */
|
||||
public boolean isJsonMode() {
|
||||
return json != null && !json.isEmpty();
|
||||
}
|
||||
}
|
||||
-129
@@ -1,129 +0,0 @@
|
||||
package com.bencodez.votingplugin.webhook;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Executes webhook rewards using configured definitions.
|
||||
*
|
||||
*
|
||||
* This class:
|
||||
* <ul>
|
||||
* <li>expands placeholders for payloads/headers/secrets</li>
|
||||
* <li>builds either raw JSON payloads or Discord-friendly payloads</li>
|
||||
* <li>queues requests on {@link WebhookService}</li>
|
||||
* </ul>
|
||||
*
|
||||
*/
|
||||
public final class WebhookRewardExecutor {
|
||||
|
||||
private final WebhookService service;
|
||||
private final Map<String, WebhookDefinition> definitions;
|
||||
|
||||
/**
|
||||
* Creates the executor.
|
||||
*
|
||||
* @param service webhook service
|
||||
* @param definitions definitions by id
|
||||
*/
|
||||
public WebhookRewardExecutor(WebhookService service, Map<String, WebhookDefinition> definitions) {
|
||||
this.service = Objects.requireNonNull(service, "service");
|
||||
this.definitions = definitions == null ? Collections.emptyMap() : definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes all entries.
|
||||
*
|
||||
* @param entries reward entries (may be empty)
|
||||
* @param expander placeholder expander
|
||||
*/
|
||||
public void executeAll(List<WebhookRewardEntry> entries, PlaceholderExpander expander) {
|
||||
if (entries == null || entries.isEmpty() || expander == null) {
|
||||
return;
|
||||
}
|
||||
for (WebhookRewardEntry e : entries) {
|
||||
executeOne(e, expander);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes one webhook entry.
|
||||
*
|
||||
* @param entry entry
|
||||
* @param expander placeholder expander
|
||||
*/
|
||||
public void executeOne(WebhookRewardEntry entry, PlaceholderExpander expander) {
|
||||
if (entry == null || expander == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
WebhookDefinition def = definitions.get(entry.getWebhookId());
|
||||
if (def == null || !def.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String body = buildBody(entry, expander);
|
||||
|
||||
Map<String, String> expandedHeaders = expandHeaders(def.getHeaders(), expander);
|
||||
String expandedSecret = expandSignatureSecret(def.getSignature(), expander);
|
||||
|
||||
service.submit(new WebhookRequest(def.getId(), body, expandedHeaders, expandedSecret));
|
||||
}
|
||||
|
||||
private static String buildBody(WebhookRewardEntry entry, PlaceholderExpander expander) {
|
||||
if (entry.isJsonMode()) {
|
||||
return expander.expand(entry.getJson());
|
||||
}
|
||||
|
||||
DiscordWebhookPayloadBuilder b = DiscordWebhookPayloadBuilder.create();
|
||||
|
||||
String content = expander.expand(entry.getContent());
|
||||
if (content != null && !content.isEmpty()) {
|
||||
b.content(content);
|
||||
}
|
||||
|
||||
if (entry.isEmbedEnabled()) {
|
||||
String title = expander.expand(entry.getEmbedTitle());
|
||||
String desc = expander.expand(entry.getEmbedDescription());
|
||||
String color = expander.expand(entry.getEmbedColorHex());
|
||||
String thumb = expander.expand(entry.getEmbedThumbnailUrl());
|
||||
String footer = expander.expand(entry.getFooterText());
|
||||
b.embed(nullToEmpty(title), nullToEmpty(desc), nullToEmpty(color), nullToEmpty(thumb),
|
||||
entry.isFooterEnabled(), nullToEmpty(footer));
|
||||
}
|
||||
|
||||
return b.buildJson();
|
||||
}
|
||||
|
||||
private static Map<String, String> expandHeaders(Map<String, String> headers, PlaceholderExpander expander) {
|
||||
if (headers == null || headers.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Map<String, String> out = new HashMap<>();
|
||||
for (Map.Entry<String, String> e : headers.entrySet()) {
|
||||
if (e.getKey() == null || e.getKey().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
out.put(e.getKey(), expander.expand(e.getValue()));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static String expandSignatureSecret(WebhookSignatureConfig sig, PlaceholderExpander expander) {
|
||||
if (sig == null || !sig.isEnabled()) {
|
||||
return null;
|
||||
}
|
||||
String secret = sig.getSecret();
|
||||
if (secret == null || secret.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return expander.expand(secret);
|
||||
}
|
||||
|
||||
private static String nullToEmpty(String s) {
|
||||
return s == null ? "" : s;
|
||||
}
|
||||
}
|
||||
-139
@@ -1,139 +0,0 @@
|
||||
package com.bencodez.votingplugin.webhook;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
/**
|
||||
* Parses reward "Webhooks:" entries from a Bukkit {@link ConfigurationSection}.
|
||||
*
|
||||
*
|
||||
* This is intentionally tolerant of missing fields, so presets won't hard-crash
|
||||
* reward parsing.
|
||||
*
|
||||
*
|
||||
*
|
||||
* Supported YAML examples:
|
||||
*
|
||||
* <pre>
|
||||
* Webhooks:
|
||||
* - webhook: "my-api"
|
||||
* json: |
|
||||
* {"event":"vote","player":"{PLAYER}"}
|
||||
*
|
||||
* - webhook: "discord-staff"
|
||||
* content:
|
||||
* enabled: true
|
||||
* message: "✅ {PLAYER} voted"
|
||||
* embed:
|
||||
* enabled: true
|
||||
* title: "Vote"
|
||||
* description: "{PLAYER} voted"
|
||||
* color: "#2ecc71"
|
||||
* thumbnail: "https://..."
|
||||
* footer:
|
||||
* enabled: true
|
||||
* message: "{TIMESTAMP}"
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
public final class WebhookRewardParser {
|
||||
|
||||
private WebhookRewardParser() {
|
||||
// utility
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses webhook reward entries from a reward section.
|
||||
*
|
||||
* @param rewardSection section that contains "Webhooks:" list
|
||||
* @return parsed entries (never null)
|
||||
*/
|
||||
public static List<WebhookRewardEntry> parse(ConfigurationSection rewardSection) {
|
||||
List<WebhookRewardEntry> out = new ArrayList<>();
|
||||
if (rewardSection == null) {
|
||||
return out;
|
||||
}
|
||||
|
||||
List<Map<?, ?>> list = rewardSection.getMapList("Webhooks");
|
||||
if (list == null || list.isEmpty()) {
|
||||
return out;
|
||||
}
|
||||
|
||||
for (Map<?, ?> raw : list) {
|
||||
if (raw == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String webhookId = string(raw.get("webhook"));
|
||||
if (webhookId == null || webhookId.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String json = string(raw.get("json"));
|
||||
|
||||
String content = null;
|
||||
boolean embedEnabled = false;
|
||||
String embedTitle = null;
|
||||
String embedDescription = null;
|
||||
String embedColor = null;
|
||||
String embedThumb = null;
|
||||
boolean footerEnabled = false;
|
||||
String footerText = null;
|
||||
|
||||
Object contentObj = raw.get("content");
|
||||
if (contentObj instanceof Map) {
|
||||
Map<?, ?> c = (Map<?, ?>) contentObj;
|
||||
boolean enabled = bool(c.get("enabled"), true);
|
||||
if (enabled) {
|
||||
content = string(c.get("message"));
|
||||
}
|
||||
}
|
||||
|
||||
Object embedObj = raw.get("embed");
|
||||
if (embedObj instanceof Map) {
|
||||
Map<?, ?> e = (Map<?, ?>) embedObj;
|
||||
embedEnabled = bool(e.get("enabled"), false);
|
||||
if (embedEnabled) {
|
||||
embedTitle = string(e.get("title"));
|
||||
embedDescription = string(e.get("description"));
|
||||
embedColor = string(e.get("color"));
|
||||
embedThumb = string(e.get("thumbnail"));
|
||||
|
||||
Object footerObj = e.get("footer");
|
||||
if (footerObj instanceof Map) {
|
||||
Map<?, ?> f = (Map<?, ?>) footerObj;
|
||||
footerEnabled = bool(f.get("enabled"), false);
|
||||
if (footerEnabled) {
|
||||
footerText = string(f.get("message"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out.add(new WebhookRewardEntry(webhookId, json, content, embedEnabled, embedTitle, embedDescription,
|
||||
embedColor, embedThumb, footerEnabled, footerText));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
private static String string(Object o) {
|
||||
if (o == null) {
|
||||
return null;
|
||||
}
|
||||
return String.valueOf(o);
|
||||
}
|
||||
|
||||
private static boolean bool(Object o, boolean def) {
|
||||
if (o == null) {
|
||||
return def;
|
||||
}
|
||||
if (o instanceof Boolean) {
|
||||
return (Boolean) o;
|
||||
}
|
||||
return Boolean.parseBoolean(String.valueOf(o));
|
||||
}
|
||||
}
|
||||
Loaded 100 of 278 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user