Compare commits

...
110 Commits
Author SHA1 Message Date
BenCodez 300f9fd928 Update version number to 7.0 2026-02-14 14:25:38 -06:00
BenCodez cc035116e3 Refactor VoteSite handling and improve test coverage
- Updated VoteMilestoneLimit to use TimeUnit for duration parsing.
- Removed unused VoteSiteTest class and added new tests for VoteSite and
VoteSiteManager.
- Introduced VoteSiteAdditionalTest and NextSiteTest for enhanced
coverage.
- Adjusted configuration handling in tests to ensure proper mocking.
2026-02-09 18:12:36 -06:00
BenCodez 583b510636 Refactor MySQL handling and improve reload functionality
- Consolidate MySQL configuration loading for Bungee and Velocity.
- Enhance reload process to support queued plugin messages during
reload.
- Update metrics handling and ensure proper shutdown of tasks.
- Improve logging for MySQL initialization and error handling.
2026-02-08 20:26:04 -06:00
BenCodez bfd3d69704 Remove unused import for SimplePie in VotingPluginBungee 2026-02-08 15:54:35 -06:00
BenCodez 8af2dc75cd Add bStats metrics for proxy broadcasts and vote logging configurations
- Implemented metrics for online mode, proxy broadcast settings, and
vote logging.
- Added custom charts to track proxy broadcast scope and offline modes.
- Introduced bucketing for counts and purge days to reduce high
cardinality in metrics.
2026-02-08 15:54:21 -06:00
BenCodez d85640ec3c Add bStats metrics for VoteBroadcast and VoteLogging configurations
- Implemented metrics for broadcast type and duration based on
VoteBroadcast settings.
- Added logging for VoteLogging metrics, including purge days and MySQL
usage.
- Enhanced configuration handling to ensure metrics are only registered
when applicable.
2026-02-08 14:35:49 -06:00
BenCodez a1def65f2d Refactor reward handling and update milestone commands
- Removed legacy cumulative reward handling and associated classes.
- Introduced new VoteMilestones handling with updated command structure.
- Updated configuration paths for milestones and vote milestones.
- Adjusted GUI elements to reflect new command names and structures.
2026-02-08 14:20:20 -06:00
BenCodez d7387a7182 Improve voting points messages and add configurable formats
- Updated user messages for voting points given and removed to use
  configurable formats from the config file.
- Added new configuration options in Config.java for
  PlayerGiven and PlayerRemoved messages.
- Updated Config.yml to include new message formats for admin vote
  point actions.
2026-02-07 20:37:37 -06:00
BenCodez 6d389c0408 Implement webhook support with Discord integration
- Added webhook management and execution classes for handling
  webhook requests asynchronously.
- Introduced configuration options for webhooks in Config.yml,
  including URL, method, headers, and retry settings.
- Implemented Discord-specific payload formatting and rate limit
  handling.
- Added utility classes for parsing webhook configurations and
  managing webhook definitions.
2026-02-06 19:24:07 -06:00
BenCodez 3a419b5250 Enhance user feedback for voting points changes
Added online user notifications when voting points are given or removed.
2026-02-06 16:08:44 -06:00
BenCodez 6f828b6928 Refactor duration parsing to use TimeUnit for consistency
Updated duration parsing in BroadcastSettings and VoteRemindersManager
to utilize TimeUnit for improved clarity and consistency. This change
ensures that time values are handled uniformly across the codebase,
enhancing maintainability and reducing potential errors.
2026-02-01 15:44:45 -06:00
BenCodez c43eb697e3 Update dependencies and refactor configuration handling
- Replace deprecated configurate methods with updated ones
- Add relocations for new dependencies in pom.xml
- Update version to 3.4.0 and include new configurate dependencies
2026-02-01 15:41:06 -06:00
Ben 9917c85de4 Merge pull request #1483 from BenCodez/dependabot/maven/VotingPlugin/org.mockito-mockito-core-5.21.0
Bump org.mockito:mockito-core from 5.15.2 to 5.21.0 in /VotingPlugin
2026-02-01 13:43:13 -06:00
Ben 5796e0401b Merge pull request #1481 from BenCodez/dependabot/maven/VotingPlugin/com.discordsrv-discordsrv-1.30.4
Bump com.discordsrv:discordsrv from 1.30.3 to 1.30.4 in /VotingPlugin
2026-02-01 13:42:54 -06:00
Ben 3cc643e501 Merge pull request #1485 from BenCodez/dependabot/maven/VotingPlugin/org.projectlombok-lombok-1.18.42
Bump org.projectlombok:lombok from 1.18.38 to 1.18.42 in /VotingPlugin
2026-02-01 13:41:51 -06:00
BenCodez 99935133e1 Update delay configurations to use string format for time values 2026-01-31 17:49:38 -06:00
BenCodez cb90e607aa Add alias command filtering for VotingPlugin tab completion 2026-01-30 19:06:07 -06:00
BenCodez 0887810ce1 Remove PerServerMilestones configuration from BungeeSettings.yml 2026-01-29 16:20:44 -06:00
dependabot[bot] eef95ceea5 Bump org.projectlombok:lombok from 1.18.38 to 1.18.42 in /VotingPlugin
Bumps [org.projectlombok:lombok](https://github.com/projectlombok/lombok) from 1.18.38 to 1.18.42.
- [Changelog](https://github.com/projectlombok/lombok/blob/master/doc/changelog.markdown)
- [Commits](https://github.com/projectlombok/lombok/compare/v1.18.38...v1.18.42)

---
updated-dependencies:
- dependency-name: org.projectlombok:lombok
  dependency-version: 1.18.42
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-26 07:40:18 +00:00
dependabot[bot] 1611fc6cd2 Bump org.mockito:mockito-core from 5.15.2 to 5.21.0 in /VotingPlugin
Bumps [org.mockito:mockito-core](https://github.com/mockito/mockito) from 5.15.2 to 5.21.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v5.15.2...v5.21.0)

---
updated-dependencies:
- dependency-name: org.mockito:mockito-core
  dependency-version: 5.21.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-26 06:55:57 +00:00
BenCodez 6591f3a625 Implement proxy broadcast routing configuration and logic 2026-01-24 18:11:08 -06:00
BenCodez b74820d1a8 Rename BungeeMessageData to VoteTotalsSnapshot and update data logic 2026-01-24 13:49:01 -06:00
BenCodez 3a1fdf1f35 Refactor MySQL messenger implementation on backend and proxy 2026-01-24 11:38:40 -06:00
BenCodez 00875b7e6f Improve service site management and update fetching logic 2026-01-24 11:12:57 -06:00
BenCodez 0bd2801825 Change proxy command to /votingpluginproxy
Refactor to remove BungeeVersion class and use
VotingPluginWire.SCHEMA_VERSION for versioning
2026-01-23 22:26:47 -06:00
BenCodez 71e7f5c753 Refactor proxy communication to use JsonEnvelope format exclusively 2026-01-23 22:16:23 -06:00
BenCodez 7392da2c04 Remove unused command aliases from plugin.yml 2026-01-20 16:03:10 -06:00
BenCodez e49c3f2476 Remove deprecated broadcast configuration options from Config.yml 2026-01-19 19:57:02 -06:00
dependabot[bot] e9e964ca7f Bump com.discordsrv:discordsrv from 1.30.3 to 1.30.4 in /VotingPlugin
Bumps com.discordsrv:discordsrv from 1.30.3 to 1.30.4.

---
updated-dependencies:
- dependency-name: com.discordsrv:discordsrv
  dependency-version: 1.30.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-19 07:59:26 +00:00
BenCodez 5f51ccb749 Bump version to 7.0-SNAPSHOT 2026-01-18 19:52:04 -06:00
BenCodez ce7ee1da3b Refactor VoteRemindersManager to use ScheduledExecutorService 2026-01-18 19:51:32 -06:00
BenCodez a8c57e1b78 Implement new vote reminder system, added config migrator 2026-01-18 19:08:49 -06:00
BenCodez 6c2ab687bf Enhance UUID lookup by normalizing offline player names in offline mode
Added command to convert to proper uuids
2026-01-12 18:00:48 -06:00
BenCodez 3af59056a7 Refactor vote broadcasting logic and migrate configuration handling 2026-01-12 17:41:51 -06:00
BenCodez dd3bcae15f Refactor ProxyOnlineVoteCacheTable to improve migration handling 2026-01-11 16:16:32 -06:00
BenCodez 499e4c9bd9 Refactor database migration handling 2026-01-11 16:12:25 -06:00
BenCodez 59bd7895d1 Refactor vote delay handling to use ParsedDuration 2026-01-11 14:39:55 -06:00
BenCodez be78d4a717 Refactor VoteSite handling to use VoteSiteManager 2026-01-11 13:24:22 -06:00
BenCodez 16fce5f132 Add commands to process votemilestones, remove older commands 2026-01-10 15:21:33 -06:00
BenCodez 0b1c192315 Refactor player login handling and improve UUID resolution 2026-01-10 12:43:48 -06:00
BenCodez 139122be62 Add unit tests for VoteStreaks 2026-01-10 10:52:29 -06:00
BenCodez de0f511e9e Move votestreak classes 2026-01-09 21:15:38 -06:00
BenCodez 73bba25977 Implement VoteMilestones system with configuration and logging support 2026-01-09 20:59:16 -06:00
BenCodez 83ca134649 Replace logger info calls with debug for Top Voters updates 2026-01-07 16:12:52 -06:00
BenCodez 06a74371e7 Disable failOnError and doclint in javadoc settings 2026-01-06 16:27:43 -06:00
BenCodez 365f08861f Rename field to fix javadoc 2026-01-06 16:21:25 -06:00
BenCodez 9fa553e417 Add method to update vote text in cache and database 2026-01-05 18:57:24 -06:00
BenCodez 8b2d3b7f63 Set default value of alwaysProcessAsyncPlaceholders to false 2026-01-04 18:44:01 -06:00
BenCodez 0f1ccea630 Add DiscordSRV check before executing Discord handler 2026-01-04 12:51:45 -06:00
BenCodez d116fa11f7 Add config option to always process async placeholders 2026-01-03 21:55:48 -06:00
BenCodez de75dff8df Don't check permission of offline player for not voted sites placeholder 2026-01-03 21:48:24 -06:00
BenCodez 1a5f8cdc0f Update Discord Top Voter message handling and improve formatting 2026-01-03 20:35:02 -06:00
BenCodez 9674921fe3 Add scheduled task to update Discord leaderboard every 10 minutes 2026-01-03 19:22:36 -06:00
BenCodez ff21a233fe Implement case-insensitive YML file handling with config option 2026-01-03 19:15:36 -06:00
BenCodez e19a50e64a Add server selection and stats for vote logs; Improve database handling 2026-01-03 12:27:47 -06:00
BenCodez d69d08461d Improve cooldown check logic and optimize user processing for vote sites 2026-01-03 08:59:41 -06:00
BenCodez d9aaeda303 Refactor user data handling to utilize UserDataFetchMode 2026-01-03 08:44:15 -06:00
BenCodez b3cc94965a Refactor VoteLogMysqlTable to enhance database column handling 2026-01-02 22:19:37 -06:00
BenCodez a1033555fb Refactor top voter loading and optimize database column types 2026-01-02 22:08:51 -06:00
BenCodez 8ef8aa26ff Refactor top voter handling and improve database configuration 2026-01-02 21:31:29 -06:00
BenCodez ad8699cf15 Move proxy mysql data to be under Database section
Will handle older style configs without issues
2026-01-02 20:25:55 -06:00
BenCodez 84705f29f5 Refactor user data handling to use callback-based forEachUserKeys
Improves performance
2026-01-02 18:42:29 -06:00
BenCodez 058fbdfd9e Improve handling of getting all user data with optimal method 2026-01-02 18:08:22 -06:00
BenCodez c3b7747cbc Cleanup configs of old options and improve overall look 2026-01-02 11:10:08 -06:00
BenCodez 2230bd45b9 Remove primary account handling from various components 2026-01-01 22:24:40 -06:00
BenCodez d7a35d845a Add server name handling to vote logging and update database schema 2026-01-01 22:12:38 -06:00
BenCodez 0e48aae457 Generate a new UUID for vote if not present in BungeeTextTotals 2026-01-01 21:36:29 -06:00
BenCodez 0c94ba89a5 Add Javadoc comment for return value in VoteLogMysqlTable 2025-12-31 20:15:02 -06:00
BenCodez 0b8ddaccdc Add Javadoc comments for parameters in VoteLogMysqlTable 2025-12-31 20:12:08 -06:00
BenCodez 68ee77eab3 Fix typo in VoteShopPurchaseLoggerListener registration 2025-12-31 20:09:16 -06:00
BenCodez 3c38932feb Add VoteShopPurchaseEvent and logging functionality for VoteShop 2025-12-31 20:09:02 -06:00
BenCodez 8594f0f51d Add new option NewMessageOnUpdate for discord top voters 2025-12-31 19:27:37 -06:00
BenCodez c77fdb1b69 Remove Jenkins site configuration and auto-download settings from config 2025-12-31 19:21:21 -06:00
BenCodez 236812bcac Enhance command output formatting for version info 2025-12-31 15:51:32 -06:00
BenCodez 0bb6d57494 Refactor update logic and improve performance in update task 2025-12-26 09:19:56 -06:00
Ben 5e5988d0b3 Merge pull request #1471 from BenCodez/dependabot/maven/VotingPlugin/org.apache.maven.plugins-maven-jar-plugin-3.5.0
Bump org.apache.maven.plugins:maven-jar-plugin from 3.4.2 to 3.5.0 in /VotingPlugin
2025-12-25 20:51:54 -06:00
Ben 77a190574d Merge pull request #1476 from BenCodez/dependabot/maven/VotingPlugin/org.apache.maven.plugins-maven-resources-plugin-3.4.0
Bump org.apache.maven.plugins:maven-resources-plugin from 3.3.1 to 3.4.0 in /VotingPlugin
2025-12-25 20:51:41 -06:00
Ben a8bf92786f Merge pull request #1477 from BenCodez/dependabot/maven/VotingPlugin/com.discordsrv-discordsrv-1.30.3
Bump com.discordsrv:discordsrv from 1.30.1 to 1.30.3 in /VotingPlugin
2025-12-25 20:51:30 -06:00
BenCodez 7510981f60 Add null check for VoteStreaks configuration section 2025-12-25 09:54:40 -06:00
BenCodez 5dfd9481bc Update nashorn-core dependency version to 15.7 2025-12-24 19:52:46 -06:00
BenCodez 403579d7bf Added PostgreSQL support 2025-12-24 19:42:50 -06:00
BenCodez d019e54860 Fix character encoding issues in ProxyVoteCacheTable comments 2025-12-24 13:50:08 -06:00
BenCodez 34b5b34a2f Fix character encoding issues in comments and message formatting 2025-12-24 12:55:10 -06:00
BenCodez 535e2201d5 Fix VoteParty error message formatting with color codes 2025-12-24 12:00:05 -06:00
BenCodez e305e6c97c Remove outdated comments from VoteStreakHandler 2025-12-24 11:55:40 -06:00
BenCodez 564de3d653 Update project encoding to UTF-8 for better compatibility 2025-12-24 11:52:28 -06:00
BenCodez 88050c903e Add vote logging tracking via vote id. Made vote logging all one command 2025-12-23 20:58:50 -06:00
BenCodez 901d900932 Add improved vote streak system 2025-12-22 19:28:02 -06:00
BenCodez d33063dd50 Refactor MySQL vote log schema: add auto-increment primary key 2025-12-21 16:04:00 -06:00
BenCodez a5d85f4cbc Refactor index creation logic to prevent duplicate index errors in MySQL 2025-12-20 21:17:46 -06:00
BenCodez 8e03f6e9c0 Fix vote logging condition to exclude Bungee events 2025-12-20 21:05:29 -06:00
BenCodez d761238eab Add admin GUI commands for vote logs: recent, player, service, and stats
Under /av votelog
2025-12-20 21:01:18 -06:00
BenCodez 7a4a7ec1b5 Implement automatic purging of old vote logs based on config settings 2025-12-20 20:09:48 -06:00
BenCodez fdb3793145 Add MySQL vote logging of all votes, more to be implemented 2025-12-20 20:01:47 -06:00
BenCodez 8c758a3410 Enable case-insensitive settings in configuration files 2025-12-13 17:07:30 -06:00
BenCodez 9564e00460 Bump version to 1.21.11-R0.1-SNAPSHOT in pom.xml 2025-12-13 16:44:59 -06:00
BenCodez 28d4f5482d Update comments in SpecialRewards.yml for clarity on command execution 2025-12-11 16:02:54 -06:00
dependabot[bot] 5fe9bd1377 Bump com.discordsrv:discordsrv from 1.30.1 to 1.30.3 in /VotingPlugin
Bumps com.discordsrv:discordsrv from 1.30.1 to 1.30.3.

---
updated-dependencies:
- dependency-name: com.discordsrv:discordsrv
  dependency-version: 1.30.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-08 06:18:11 +00:00
BenCodez 474553b4fe Refactor command execution to improve placeholder updates for points 2025-12-07 12:50:46 -06:00
BenCodez 609e31c16c Change Redis database index configuration from string to integer type 2025-12-02 16:22:35 -06:00
dependabot[bot] 71ed79b819 Bump org.apache.maven.plugins:maven-resources-plugin in /VotingPlugin
Bumps [org.apache.maven.plugins:maven-resources-plugin](https://github.com/apache/maven-resources-plugin) from 3.3.1 to 3.4.0.
- [Release notes](https://github.com/apache/maven-resources-plugin/releases)
- [Commits](https://github.com/apache/maven-resources-plugin/compare/maven-resources-plugin-3.3.1...v3.4.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-resources-plugin
  dependency-version: 3.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 06:47:00 +00:00
BenCodez 7f1e1e1e72 Add Redis database index configuration support 2025-11-30 10:10:04 -06:00
BenCodez e22b1885ab Fix javadoc 2025-11-24 16:04:52 -06:00
BenCodez c3291e4f46 Add debug log for column type matching in ProxyMysqlUserTable 2025-11-23 16:22:57 -06:00
BenCodez 60a1428496 Implement non-voted players caching with MySQL support 2025-11-23 16:16:31 -06:00
BenCodez 17c098f1ee Add alias command filtering to hide commands when aliases are disabled 2025-11-22 17:58:23 -06:00
BenCodez fb78dc1313 Modify alias loading 2025-11-22 17:32:26 -06:00
BenCodez 382bba9990 Hide alias commands if loading is disabled 2025-11-22 17:01:28 -06:00
dependabot[bot] 8594cb8238 Bump org.apache.maven.plugins:maven-jar-plugin in /VotingPlugin
Bumps [org.apache.maven.plugins:maven-jar-plugin](https://github.com/apache/maven-jar-plugin) from 3.4.2 to 3.5.0.
- [Release notes](https://github.com/apache/maven-jar-plugin/releases)
- [Commits](https://github.com/apache/maven-jar-plugin/compare/maven-jar-plugin-3.4.2...maven-jar-plugin-3.5.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-jar-plugin
  dependency-version: 3.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-17 06:17:52 +00:00
BenCodez 1f3a4b84a1 Bump version number to 6.19.2-SNAPSHOT 2025-11-09 19:13:46 -06:00
182 changed files with 23587 additions and 7589 deletions

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
---
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.
+656 -615
View File
File diff suppressed because it is too large. Load diff
@@ -1,16 +1,16 @@
// 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.ArrayList;
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 java.util.regex.Pattern;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
@@ -23,15 +23,13 @@ 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.array.ArrayUtils;
import com.bencodez.simpleapi.encryption.EncryptionHandler;
import com.bencodez.simpleapi.messages.MessageAPI;
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.MqttHandler.MessageHandler;
import com.bencodez.simpleapi.servercomm.mqtt.MqttServerComm;
import com.bencodez.simpleapi.servercomm.mysql.BackendMessenger;
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;
@@ -41,11 +39,11 @@ 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.objects.VoteSite;
import com.bencodez.votingplugin.proxy.BungeeMessageData;
import com.bencodez.votingplugin.proxy.BungeeMethod;
import com.bencodez.votingplugin.proxy.BungeeVersion;
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;
@@ -83,7 +81,10 @@ public class BungeeHandler implements Listener {
private Thread redisThread;
@Getter
private BackendMessenger backendMysqlMessenger;
private MySqlMessenger backendMysqlMessenger;
@Getter
private MqttHandler mqttHandler;
public BungeeHandler(VotingPluginMain plugin) {
this.plugin = plugin;
@@ -91,7 +92,6 @@ public class BungeeHandler implements Listener {
public void checkGlobalData() {
HashMap<String, DataValue> data = globalDataHandler.getExact(plugin.getBungeeSettings().getServer());
// plugin.debug(data.toString());
if (data.containsKey("ForceUpdate")) {
boolean b = checkGlobalDataTimeValue(data.get("ForceUpdate"));
@@ -126,15 +126,6 @@ public class BungeeHandler implements Listener {
}
}
/*
* @EventHandler public void onDateChange(DateChangedEvent event) { if
* (method.equals(BungeeMethod.PLUGINMESSAGING)) {
* plugin.getPluginMessaging().sendPluginMessage("timeupdate",
* plugin.getServerDataFile().getPrevMonth() + "//" +
* plugin.getServerDataFile().getPrevDay() + "//" +
* plugin.getServerDataFile().getPrevWeekDay()); } }
*/
public boolean checkGlobalDataTime(TimeType type, HashMap<String, DataValue> data) {
boolean isProcessing = false;
if (data.containsKey(type.toString())) {
@@ -157,8 +148,10 @@ public class BungeeHandler implements Listener {
plugin.debug("Detected time change from bungee: " + type.toString());
plugin.getTimeChecker().forceChanged(type, false, true, true);
globalDataHandler.setBoolean(plugin.getBungeeSettings().getServer(), type.toString(), false);
getGlobalMessageHandler().sendMessage("TimeChangeFinished",
"" + plugin.getBungeeSettings().getServer());
HashMap<String, Object> fields = new HashMap<>();
fields.put("server", plugin.getBungeeSettings().getServer());
sendSubChannel("TimeChangeFinished", fields);
}
}
return isProcessing;
@@ -199,339 +192,192 @@ public class BungeeHandler implements Listener {
loadGlobalMysql();
globalMessageHandler = new GlobalMessageHandler() {
@Override
public void sendMessage(String subChannel, String... messageData) {
public void sendMessage(JsonEnvelope envelope) {
if (method.equals(BungeeMethod.MYSQL)) {
// plugin.getPluginMessaging().sendPluginMessage(subChannel, messageData);
try {
backendMysqlMessenger.sendToProxy(subChannel + "%l%" + String.join("%l%", messageData));
backendMysqlMessenger.sendToProxy(envelope);
} catch (SQLException e) {
e.printStackTrace();
}
} else if (method.equals(BungeeMethod.PLUGINMESSAGING)) {
plugin.getPluginMessaging().sendPluginMessage(subChannel, messageData);
plugin.getPluginMessaging().sendEnvelope(envelope);
} else if (method.equals(BungeeMethod.SOCKETS)) {
ArrayList<String> list = new ArrayList<>();
list.add(subChannel);
list.addAll(ArrayUtils.convert(messageData));
sendData(ArrayUtils.convert(list));
sendEnvelopeSocket(envelope);
} else if (method.equals(BungeeMethod.REDIS)) {
ArrayList<String> list = new ArrayList<>();
list.add(subChannel);
list.addAll(ArrayUtils.convert(messageData));
redisHandler.sendMessage(plugin.getBungeeSettings().getRedisPrefix() + "VotingPlugin",
ArrayUtils.convert(list));
redisHandler.publishEnvelope(plugin.getBungeeSettings().getRedisPrefix() + "VotingPlugin",
envelope);
} else if (method.equals(BungeeMethod.MQTT)) {
ArrayList<String> list = new ArrayList<>();
list.add(subChannel);
list.addAll(ArrayUtils.convert(messageData));
try {
mqttHandler.publish(plugin.getBungeeSettings().getMqttPrefix() + "votingplugin/servers/proxy",
String.join(":", list));
mqttHandler.publishEnvelope(
plugin.getBungeeSettings().getMqttPrefix() + "votingplugin/servers/proxy", envelope);
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
globalMessageHandler.addListener(new GlobalMessageListener("Vote") {
// ==========================
// Vote / VoteOnline (wire decode)
// ==========================
globalMessageHandler.addListener(new GlobalMessageListener(VotingPluginWire.SUB_VOTE) {
@Override
public void onReceive(ArrayList<String> args) {
if (args.size() > 8) {
int bungeeVersion = Integer.parseInt(args.get(8));
if (bungeeVersion != BungeeVersion.getPluginMessageVersion()) {
plugin.getLogger().warning("Incompatible version with bungee, please update all servers"
+ bungeeVersion + ":" + BungeeVersion.getPluginMessageVersion());
return;
}
String player = args.get(0);
String uuid = args.get(1);
String service = args.get(2);
long time = Long.parseLong(args.get(3));
plugin.debug("pluginmessaging vote received from " + player + "/" + uuid + " on " + service);
VotingPluginUser user = plugin.getVotingPluginUserManager()
.getVotingPluginUser(UUID.fromString(uuid), player);
boolean wasOnline = Boolean.valueOf(args.get(4));
BungeeMessageData text = new BungeeMessageData(args.get(6));
bungeeVotePartyCurrent = text.getVotePartyCurrent();
bungeeVotePartyRequired = text.getVotePartyRequired();
plugin.getPlaceholders().onBungeeVotePartyUpdate();
plugin.getServerData().setBungeeVotePartyCurrent(bungeeVotePartyCurrent);
plugin.getServerData().setBungeeVotePartyRequired(bungeeVotePartyRequired);
boolean setTotals = Boolean.valueOf(args.get(7));
user.cache();
boolean broadcast = true;
boolean bungeeBroadcast = false;
if (args.size() > 9) {
bungeeBroadcast = Boolean.valueOf(args.get(9));
}
int num = 1;
if (args.size() > 10) {
num = Integer.valueOf(args.get(10));
}
int numberOfVotes = 1;
if (args.size() > 11) {
numberOfVotes = Integer.valueOf(args.get(11));
}
if (!bungeeBroadcast) {
if (!plugin.getBungeeSettings().isBungeeBroadcast()
&& !plugin.getBungeeSettings().isDisableBroadcast()) {
if (wasOnline || plugin.getBungeeSettings().isBungeeBroadcastAlways()) {
if (plugin.getConfigFile().isFormatOnlyOneOfflineBroadcast() && !wasOnline) {
if (num == 1) {
user.offlineBroadcast(user, plugin.getBungeeSettings().isUseBungeecoord(),
numberOfVotes);
}
} else {
VoteSite site = plugin.getVoteSite(service, true);
if (site != null) {
site.broadcastVote(user, false);
broadcast = false;
} else {
plugin.getLogger().warning("No votesite for " + service);
}
}
}
}
} else {
broadcast = false;
}
user.bungeeVotePluginMessaging(service, time, text, !setTotals, wasOnline, broadcast, num);
if (plugin.getBungeeSettings().isPerServerPoints()) {
user.addPoints(plugin.getConfigFile().getPointsOnVote());
}
plugin.getSpecialRewards().bungeeAllSitesCheck(user, numberOfVotes, num);
if (Boolean.valueOf(args.get(5))) {
plugin.getServerData().addServiceSite(service);
}
} else {
plugin.getLogger().warning("Incompatible version with bungee, please update all servers");
}
public void onReceive(JsonEnvelope msg) {
handleWireVote(msg);
}
});
globalMessageHandler.addListener(new GlobalMessageListener("VoteOnline") {
globalMessageHandler.addListener(new GlobalMessageListener(VotingPluginWire.SUB_VOTE_ONLINE) {
@Override
public void onReceive(ArrayList<String> args) {
if (args.size() > 8) {
int bungeeVersion = Integer.parseInt(args.get(8));
if (bungeeVersion != BungeeVersion.getPluginMessageVersion()) {
plugin.getLogger().warning("Incompatible version with bungee, please update all servers");
return;
}
String player = args.get(0);
String uuid = args.get(1);
String service = args.get(2);
long time = Long.parseLong(args.get(3));
BungeeMessageData text = new BungeeMessageData(args.get(6));
bungeeVotePartyCurrent = text.getVotePartyCurrent();
bungeeVotePartyRequired = text.getVotePartyRequired();
plugin.getPlaceholders().onBungeeVotePartyUpdate();
plugin.getServerData().setBungeeVotePartyCurrent(bungeeVotePartyCurrent);
plugin.getServerData().setBungeeVotePartyRequired(bungeeVotePartyRequired);
plugin.debug("pluginmessaging voteonline received from " + player + "/" + uuid + " on " + service);
VotingPluginUser user = plugin.getVotingPluginUserManager()
.getVotingPluginUser(UUID.fromString(uuid), player);
user.cache();
boolean setTotals = Boolean.valueOf(args.get(7));
boolean wasOnline = Boolean.valueOf(args.get(4));
boolean broadcast = true;
boolean bungeeBroadcast = false;
if (args.size() > 9) {
bungeeBroadcast = Boolean.valueOf(args.get(9));
}
int num = 1;
if (args.size() > 10) {
num = Integer.valueOf(args.get(10));
}
int numberOfVotes = 1;
if (args.size() > 11) {
numberOfVotes = Integer.valueOf(args.get(11));
}
if (!bungeeBroadcast) {
if (!plugin.getBungeeSettings().isBungeeBroadcast()
&& !plugin.getBungeeSettings().isDisableBroadcast()) {
if (wasOnline || plugin.getBungeeSettings().isBungeeBroadcastAlways()) {
if (plugin.getConfigFile().isFormatOnlyOneOfflineBroadcast() && !wasOnline) {
if (num == 1) {
user.offlineBroadcast(user, plugin.getBungeeSettings().isUseBungeecoord(),
numberOfVotes);
}
} else {
VoteSite site = plugin.getVoteSite(service, true);
if (site != null) {
site.broadcastVote(user, false);
broadcast = false;
} else {
plugin.getLogger().warning("No votesite for " + service);
}
}
}
}
} else {
broadcast = false;
}
user.bungeeVotePluginMessaging(service, time, text, !setTotals, wasOnline, broadcast, num);
if (plugin.getBungeeSettings().isPerServerPoints()) {
user.addPoints(plugin.getConfigFile().getPointsOnVote());
}
plugin.getSpecialRewards().bungeeAllSitesCheck(user, numberOfVotes, num);
if (Boolean.valueOf(args.get(5))) {
plugin.getServerData().addServiceSite(service);
}
} else {
plugin.getLogger().warning("Incompatible version with bungee, please update all servers");
}
public void onReceive(JsonEnvelope msg) {
handleWireVote(msg);
}
});
globalMessageHandler.addListener(new GlobalMessageListener("VoteUpdate") {
globalMessageHandler.addListener(new GlobalMessageListener(VotingPluginWire.SUB_VOTE_UPDATE) {
@Override
public void onReceive(ArrayList<String> args) {
String player = args.get(0);
plugin.debug("pluginmessaging voteupdate received for " + player);
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(player));
.getVotingPluginUser(UUID.fromString(playerUuid));
user.cache();
user.offVote();
if (args.size() > 2) {
bungeeVotePartyCurrent = Integer.parseInt(args.get(1));
bungeeVotePartyRequired = Integer.parseInt(args.get(2));
plugin.getServerData().setBungeeVotePartyCurrent(bungeeVotePartyCurrent);
plugin.getServerData().setBungeeVotePartyRequired(bungeeVotePartyRequired);
// Vote party cache update
if (v.votePartyCurrent != 0 || bungeeVotePartyCurrent == -2) {
bungeeVotePartyCurrent = v.votePartyCurrent;
}
if (args.size() > 3 && plugin.getBungeeSettings().isPerServerMilestones()) {
BungeeMessageData text = new BungeeMessageData(args.get(3));
plugin.getSpecialRewards().checkMilestone(user, text, true);
if (v.votePartyRequired != 0 || bungeeVotePartyRequired == -2) {
bungeeVotePartyRequired = v.votePartyRequired;
}
plugin.getServerData().setBungeeVotePartyCurrent(bungeeVotePartyCurrent);
plugin.getServerData().setBungeeVotePartyRequired(bungeeVotePartyRequired);
if (args.size() > 5) {
String service = args.get(4);
String data = args.get(5);
if (MessageAPI.isLong(data)) {
long time = Long.valueOf(data);
if (time > 0) {
user.setTime(plugin.getVoteSite(service, true), time);
} else {
if (plugin.getBungeeSettings().isBungeeDebug()) {
plugin.debug("Invalid last vote time received from bungee: " + time);
}
}
}
// 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("BungeeTimeChange") {
globalMessageHandler.addListener(new GlobalMessageListener(VotingPluginWire.SUB_BUNGEE_TIME_CHANGE) {
@Override
public void onReceive(ArrayList<String> args) {
public void onReceive(JsonEnvelope msg) {
checkGlobalData();
}
});
globalMessageHandler.addListener(new GlobalMessageListener("VoteBroadcast") {
globalMessageHandler.addListener(new GlobalMessageListener(VotingPluginWire.SUB_VOTE_BROADCAST) {
@Override
public void onReceive(ArrayList<String> args) {
if (args.size() > 2) {
String uuid = args.get(0);
String service = args.get(2);
VotingPluginUser user = plugin.getVotingPluginUserManager()
.getVotingPluginUser(UUID.fromString(uuid), args.get(1));
VoteSite site = plugin.getVoteSite(service, true);
if (site != null) {
site.broadcastVote(user, false);
} else {
plugin.getLogger().warning("No votesite for " + service);
}
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("VoteBroadcastOffline") {
@Override
public void onReceive(ArrayList<String> args) {
if (args.size() > 2) {
String uuid = args.get(0);
String votes = args.get(2);
VotingPluginUser user = plugin.getVotingPluginUserManager()
.getVotingPluginUser(UUID.fromString(uuid), args.get(1));
user.offlineBroadcast(user, false, Integer.parseInt(votes));
}
}
});
globalMessageHandler.addListener(new GlobalMessageListener("Status") {
@Override
public void onReceive(ArrayList<String> args) {
String server = args.get(0);
sendMessage(globalMessageHandler, "statusokay", server);
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") {
globalMessageHandler.addListener(new GlobalMessageListener("ServerName") {
@Override
public void onReceive(ArrayList<String> args) {
String server = args.get(0);
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") {
globalMessageHandler.addListener(new GlobalMessageListener("VotePartyBungee") {
@Override
public void onReceive(ArrayList<String> args) {
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()) {
@@ -539,11 +385,11 @@ public class BungeeHandler implements Listener {
}
}
});
globalMessageHandler.addListener(new GlobalMessageListener("VotePartyBroadcast") {
globalMessageHandler.addListener(new GlobalMessageListener("VotePartyBroadcast") {
@Override
public void onReceive(ArrayList<String> args) {
String broadcast = args.get(0);
public void onReceive(JsonEnvelope msg) {
String broadcast = nvl(msg.getFields().get("broadcast"));
MiscUtils.getInstance().broadcast(broadcast);
}
});
@@ -552,29 +398,22 @@ public class BungeeHandler implements Listener {
plugin.registerBungeeChannels(plugin.getBungeeSettings().getPluginMessagingChannel());
try {
backendMysqlMessenger = new BackendMessenger("VotingPlugin",
backendMysqlMessenger = new MySqlMessenger("VotingPlugin",
plugin.getMysql().getMysql().getConnectionManager().getDataSource(),
plugin.getOptions().getServer(), msg -> {
plugin.debug("Proxy sent: " + msg.payload);
String[] message = msg.payload.split(Pattern.quote("%l%"));
if (message.length > 0) {
ArrayList<String> list = new ArrayList<>();
for (int i = 1; i < message.length; i++) {
list.add(message[i]);
}
globalMessageHandler.onMessage(message[0], list);
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().getRedisPassword(), plugin.getBungeeSettings().getRedisdbindex()) {
@Override
public void debug(String message) {
@@ -582,29 +421,17 @@ public class BungeeHandler implements Listener {
plugin.debug(message);
}
}
@Override
protected void onMessage(String channel, String[] message) {
if (plugin.getBungeeSettings().isBungeeDebug()) {
plugin.debug(channel + " " + ArrayUtils.makeStringList(ArrayUtils.convert(message)));
}
if (message.length > 0) {
ArrayList<String> list = new ArrayList<>();
for (int i = 1; i < message.length; i++) {
list.add(message[i]);
}
globalMessageHandler.onMessage(message[0], list);
}
}
};
redisThread = new Thread(new Runnable() {
redisThread = new Thread(new Runnable() {
@Override
public void run() {
if (plugin.isEnabled()) {
redisHandler.loadListener(
new RedisListener(redisHandler, plugin.getBungeeSettings().getRedisPrefix()
+ "VotingPlugin_" + plugin.getBungeeSettings().getServer()));
RedisListener listener = redisHandler.createEnvelopeListener(
plugin.getBungeeSettings().getRedisPrefix() + "VotingPlugin_"
+ plugin.getBungeeSettings().getServer(),
(ch, env) -> globalMessageHandler.onMessage(env));
redisHandler.loadListener(listener);
}
}
});
@@ -623,9 +450,8 @@ public class BungeeHandler implements Listener {
plugin.getPluginMessaging().add(new PluginMessageHandler() {
@Override
public void onRecieve(String subChannel, ArrayList<String> args) {
globalMessageHandler.onMessage(subChannel, args);
public void onReceive(JsonEnvelope envelope) {
globalMessageHandler.onMessage(envelope);
}
});
@@ -637,7 +463,7 @@ public class BungeeHandler implements Listener {
plugin.getBungeeSettings().getBungeeServerPort(), encryptionHandler,
plugin.getBungeeSettings().isBungeeDebug());
socketHandler = new SocketHandler(plugin.getVersion(), plugin.getBungeeSettings().getSpigotServerHost(),
socketHandler = new SocketHandler("vp-socket", plugin.getBungeeSettings().getSpigotServerHost(),
plugin.getBungeeSettings().getSpigotServerPort(), encryptionHandler,
plugin.getBungeeSettings().isBungeeDebug()) {
@@ -648,10 +474,9 @@ public class BungeeHandler implements Listener {
};
socketHandler.add(new SocketReceiver() {
@Override
public void onReceive(String[] data) {
globalMessageHandler.onMessage(data[0], ArrayUtils.convertAndRemoveFirst(data));
public void onReceiveEnvelope(JsonEnvelope envelope) {
globalMessageHandler.onMessage(envelope);
}
});
@@ -661,26 +486,17 @@ public class BungeeHandler implements Listener {
if (id.isEmpty()) {
id = plugin.getOptions().getServer();
}
mqttHandler = new MqttHandler(new MqttServerComm(plugin.getBungeeSettings().getMqttClientID(),
plugin.getBungeeSettings().getMqttBrokerURL(), plugin.getBungeeSettings().getMqttUsername(),
plugin.getBungeeSettings().getMqttPassword()), 2);
mqttHandler.subscribe(plugin.getBungeeSettings().getMqttPrefix() + "votingplugin/servers/"
+ plugin.getOptions().getServer(), new MessageHandler() {
mqttHandler = new MqttHandler(new MqttServerComm(id, plugin.getBungeeSettings().getMqttBrokerURL(),
plugin.getBungeeSettings().getMqttUsername(), plugin.getBungeeSettings().getMqttPassword()), 2);
@Override
public void onMessage(String topic, String payload) {
String[] message = payload.split(":");
mqttHandler.subscribeEnvelopes(
plugin.getBungeeSettings().getMqttPrefix() + "votingplugin/servers/"
+ plugin.getOptions().getServer(),
(topic, envelope) -> globalMessageHandler.onMessage(envelope));
if (message.length > 0) {
globalMessageHandler.onMessage(message[0],
ArrayUtils.convertAndRemoveFirst(message));
}
}
});
} catch (MqttException e) {
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@@ -693,8 +509,72 @@ public class BungeeHandler implements Listener {
}
}
@Getter
private MqttHandler mqttHandler;
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()) {
@@ -709,28 +589,27 @@ public class BungeeHandler implements Listener {
}
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);
@@ -760,7 +639,6 @@ public class BungeeHandler implements Listener {
globalDataHandler = new GlobalDataHandler(
new GlobalMySQL("VotingPlugin_GlobalData", new MysqlConfigSpigot(
plugin.getBungeeSettings().getData().getConfigurationSection("GlobalData"))) {
@Override
public void debugEx(Exception e) {
plugin.debug(e);
@@ -787,6 +665,7 @@ public class BungeeHandler implements Listener {
}
});
}
globalDataHandler.getGlobalMysql().alterColumnType("IgnoreTime", "VARCHAR(5)");
globalDataHandler.getGlobalMysql().alterColumnType("MONTH", "VARCHAR(5)");
globalDataHandler.getGlobalMysql().alterColumnType("WEEK", "VARCHAR(5)");
@@ -799,7 +678,23 @@ public class BungeeHandler implements Listener {
}
}
public void sendData(String... strings) {
clientHandler.sendMessage(strings);
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;
}
}
File diff suppressed because it is too large. Load diff
@@ -2,24 +2,36 @@ package com.bencodez.votingplugin;
import java.util.concurrent.Callable;
import org.bukkit.configuration.ConfigurationSection;
import com.bencodez.simpleapi.metrics.BStatsMetrics;
/**
* Registers bStats charts for VotingPlugin.
* <p>
* Updated for VotingPlugin 7.0:
* <ul>
* <li>Broadcast: Always logs broadcast_type if VoteBroadcast.Type exists. If Type == NONE, ignores other broadcast
* settings.</li>
* <li>VoteLogging: Only logs VoteLogging charts when VoteLogging.Enabled is true.</li>
* <li>No "Unknown" values are emitted; charts are only registered when their data exists.</li>
* <li>VoteMilestones chart is bucketed to reduce cardinality.</li>
* </ul>
*/
public class VotingPluginMetrics {
public void load(VotingPluginMain plugin) {
BStatsMetrics metrics = new BStatsMetrics(plugin, 38);
/**
* Register bStats metrics.
*
* @param plugin VotingPlugin main instance
*/
public void load(final VotingPluginMain plugin) {
if (plugin == null) {
return;
}
metrics.addCustomChart(new BStatsMetrics.SimplePie("extrarewards_firstvote", new Callable<String>() {
final BStatsMetrics metrics = new BStatsMetrics(plugin, 38);
@Override
public String call() throws Exception {
if (plugin.getRewardHandler().hasRewards(plugin.getSpecialRewardsConfig().getData(),
plugin.getSpecialRewardsConfig().getFirstVoteRewardsPath())) {
return "True";
}
return "False";
}
}));
metrics.addCustomChart(new BStatsMetrics.SimplePie("extrarewards_everysite", new Callable<String>() {
@Override
@@ -31,32 +43,7 @@ public class VotingPluginMetrics {
return "False";
}
}));
metrics.addCustomChart(new BStatsMetrics.SimplePie("extrarewards_allsites", new Callable<String>() {
@Override
public String call() throws Exception {
if (plugin.getRewardHandler().hasRewards(plugin.getSpecialRewardsConfig().getData(),
plugin.getSpecialRewardsConfig().getAllSitesRewardPath())) {
return "True";
}
return "False";
}
}));
metrics.addCustomChart(new BStatsMetrics.SimplePie("extrarewards_cumulative", new Callable<String>() {
@Override
public String call() throws Exception {
if (plugin.getSpecialRewardsConfig().getCumulativeVotes().size() == 0) {
return "False";
}
for (String cum : plugin.getSpecialRewardsConfig().getCumulativeVotes()) {
if (plugin.getSpecialRewardsConfig().getCumulativeRewardEnabled(Integer.parseInt(cum))) {
return "True";
}
}
return "False";
}
}));
metrics.addCustomChart(new BStatsMetrics.SimplePie("extrarewards_voteparty", new Callable<String>() {
@Override
@@ -67,6 +54,7 @@ public class VotingPluginMetrics {
return "True";
}
}));
metrics.addCustomChart(new BStatsMetrics.SimplePie("placeholder_cache_level", new Callable<String>() {
@Override
@@ -74,21 +62,16 @@ public class VotingPluginMetrics {
return plugin.getPlaceholders().getCacheLevel().toString();
}
}));
metrics.addCustomChart(new BStatsMetrics.SimplePie("extrarewards_milestone", new Callable<String>() {
metrics.addCustomChart(new BStatsMetrics.SimplePie("extrarewards_votemilestones", new Callable<String>() {
@Override
public String call() throws Exception {
if (plugin.getSpecialRewardsConfig().getMilestoneVotes().size() == 0) {
return "False";
}
for (String milestone : plugin.getSpecialRewardsConfig().getMilestoneVotes()) {
if (plugin.getSpecialRewardsConfig().getMilestoneRewardEnabled(Integer.parseInt(milestone))) {
return "True";
}
}
return "False";
int size = plugin.getVoteMilestonesManager().getConfig().getMilestones().size();
return bucketCount(size);
}
}));
metrics.addCustomChart(new BStatsMetrics.SimplePie("extrarewards_anysitereward", new Callable<String>() {
@Override
@@ -100,63 +83,15 @@ public class VotingPluginMetrics {
return "False";
}
}));
metrics.addCustomChart(new BStatsMetrics.SimplePie("extrarewards_votestreakday", new Callable<String>() {
@Override
public String call() throws Exception {
for (String s : plugin.getSpecialRewardsConfig().getVoteStreakVotes("Day")) {
if (plugin.getSpecialRewardsConfig().getVoteStreakRewardEnabled("Day", s)
&& plugin.getRewardHandler().hasRewards(plugin.getSpecialRewardsConfig().getData(),
plugin.getSpecialRewardsConfig().getVoteStreakRewardsPath("Day", s))) {
return "True";
}
}
return "False";
}
}));
metrics.addCustomChart(new BStatsMetrics.SimplePie("extrarewards_votestreakweek", new Callable<String>() {
@Override
public String call() throws Exception {
for (String s : plugin.getSpecialRewardsConfig().getVoteStreakVotes("Week")) {
if (plugin.getSpecialRewardsConfig().getVoteStreakRewardEnabled("Week", s)
&& plugin.getRewardHandler().hasRewards(plugin.getSpecialRewardsConfig().getData(),
plugin.getSpecialRewardsConfig().getVoteStreakRewardsPath("Week", s))) {
return "True";
}
}
return "False";
}
}));
metrics.addCustomChart(new BStatsMetrics.SimplePie("extrarewards_votestreakmonth", new Callable<String>() {
@Override
public String call() throws Exception {
for (String s : plugin.getSpecialRewardsConfig().getVoteStreakVotes("Month")) {
if (plugin.getSpecialRewardsConfig().getVoteStreakRewardEnabled("Month", s)
&& plugin.getRewardHandler().hasRewards(plugin.getSpecialRewardsConfig().getData(),
plugin.getSpecialRewardsConfig().getVoteStreakRewardsPath("Month", s))) {
return "True";
}
}
return "False";
}
}));
metrics.addCustomChart(new BStatsMetrics.SimplePie("numberofsites", new Callable<String>() {
@Override
public String call() throws Exception {
return "" + plugin.getVoteSitesEnabled().size();
return "" + plugin.getVoteSiteManager().getVoteSitesEnabled().size();
}
}));
metrics.addCustomChart(new BStatsMetrics.SimplePie("numberofrewards", new Callable<String>() {
@Override
@@ -164,6 +99,7 @@ public class VotingPluginMetrics {
return "" + plugin.getRewardHandler().getRewards().size();
}
}));
metrics.addCustomChart(new BStatsMetrics.SimplePie("autocreatevotesites", new Callable<String>() {
@Override
@@ -172,6 +108,14 @@ public class VotingPluginMetrics {
}
}));
metrics.addCustomChart(new BStatsMetrics.SimplePie("config_onlinemode", new Callable<String>() {
@Override
public String call() throws Exception {
return "" + plugin.getOptions().isOnlineMode();
}
}));
metrics.addCustomChart(new BStatsMetrics.SimplePie("numberofusers", new Callable<String>() {
@Override
@@ -209,6 +153,7 @@ public class VotingPluginMetrics {
return plugin.getOptions().getStorageType().toString();
}
}));
metrics.addCustomChart(new BStatsMetrics.SimplePie("DisableCheckOnWorldChange", new Callable<String>() {
@Override
@@ -216,13 +161,7 @@ public class VotingPluginMetrics {
return "" + plugin.getOptions().isDisableCheckOnWorldChange();
}
}));
metrics.addCustomChart(new BStatsMetrics.SimplePie("votereminding_enabled", new Callable<String>() {
@Override
public String call() throws Exception {
return "" + plugin.getConfigFile().isVoteRemindingEnabled();
}
}));
metrics.addCustomChart(new BStatsMetrics.SimplePie("LoadTopVoter_Monthly", new Callable<String>() {
@Override
@@ -230,6 +169,7 @@ public class VotingPluginMetrics {
return "" + plugin.getConfigFile().isLoadTopVoterMonthly();
}
}));
metrics.addCustomChart(new BStatsMetrics.SimplePie("LoadTopVoter_Weekly", new Callable<String>() {
@Override
@@ -237,6 +177,7 @@ public class VotingPluginMetrics {
return "" + plugin.getConfigFile().isLoadTopVoterWeekly();
}
}));
metrics.addCustomChart(new BStatsMetrics.SimplePie("LoadTopVoter_Daily", new Callable<String>() {
@Override
@@ -244,6 +185,7 @@ public class VotingPluginMetrics {
return "" + plugin.getConfigFile().isLoadTopVoterDaily();
}
}));
metrics.addCustomChart(new BStatsMetrics.SimplePie("bungeemethod", new Callable<String>() {
@Override
@@ -254,22 +196,8 @@ public class VotingPluginMetrics {
return "Disabled";
}
}));
if (plugin.getBungeeSettings().isUseBungeecoord()) {
metrics.addCustomChart(new BStatsMetrics.SimplePie("bungeebroadcast", new Callable<String>() {
@Override
public String call() throws Exception {
return "" + plugin.getBungeeSettings().isBungeeBroadcast();
}
}));
metrics.addCustomChart(new BStatsMetrics.SimplePie("bungeebroadcastalways", new Callable<String>() {
@Override
public String call() throws Exception {
return "" + plugin.getBungeeSettings().isBungeeBroadcastAlways();
}
}));
metrics.addCustomChart(new BStatsMetrics.SimplePie("perserverrewards", new Callable<String>() {
@Override
@@ -368,5 +296,188 @@ public class VotingPluginMetrics {
}
}));
}
/*
* ---------------------------------------------------------------------
* Broadcast metrics:
* - Always log broadcast_type if VoteBroadcast.Type exists (even if NONE)
* - If Type == NONE, ignore the rest of the broadcast settings
* ---------------------------------------------------------------------
*/
final ConfigurationSection root = safeConfig(plugin);
final ConfigurationSection voteBroadcast = getSection(root, "VoteBroadcast");
if (voteBroadcast != null && voteBroadcast.isString("Type")) {
metrics.addCustomChart(new BStatsMetrics.SimplePie("broadcast_type", new Callable<String>() {
@Override
public String call() throws Exception {
return voteBroadcast.getString("Type");
}
}));
final String type = voteBroadcast.getString("Type", null);
if (type != null && !"NONE".equalsIgnoreCase(type)) {
if (voteBroadcast.isString("Duration")) {
metrics.addCustomChart(new BStatsMetrics.SimplePie("broadcast_duration", new Callable<String>() {
@Override
public String call() throws Exception {
return voteBroadcast.getString("Duration");
}
}));
}
if (voteBroadcast.isInt("MaxSitesListed")) {
metrics.addCustomChart(new BStatsMetrics.SimplePie("broadcast_max_sites_listed",
new Callable<String>() {
@Override
public String call() throws Exception {
return bucketMaxSites(voteBroadcast.getInt("MaxSitesListed"));
}
}));
}
}
}
/*
* ---------------------------------------------------------------------
* VoteLogging metrics:
* - Only log when VoteLogging.Enabled is true
* ---------------------------------------------------------------------
*/
final ConfigurationSection voteLogging = getSection(root, "VoteLogging");
if (voteLogging != null && voteLogging.getBoolean("Enabled", false)) {
if (voteLogging.isInt("PurgeDays")) {
metrics.addCustomChart(new BStatsMetrics.SimplePie("votelogging_purgedays", new Callable<String>() {
@Override
public String call() throws Exception {
return bucketPurgeDays(voteLogging.getInt("PurgeDays"));
}
}));
}
if (voteLogging.isBoolean("UseMainMySQL")) {
metrics.addCustomChart(new BStatsMetrics.SimplePie("votelogging_usemainmysql", new Callable<String>() {
@Override
public String call() throws Exception {
return "" + voteLogging.getBoolean("UseMainMySQL");
}
}));
}
}
}
/**
* Safely gets the root configuration section.
*
* @param plugin plugin instance
* @return root config section, or null if inaccessible
*/
private ConfigurationSection safeConfig(final VotingPluginMain plugin) {
try {
return plugin.getConfigFile().getData();
} catch (Exception ignored) {
return null;
}
}
/**
* Gets a child section safely.
*
* @param root root section
* @param key child key
* @return child section or null
*/
private ConfigurationSection getSection(final ConfigurationSection root, final String key) {
if (root == null) {
return null;
}
try {
return root.getConfigurationSection(key);
} catch (Exception ignored) {
return null;
}
}
/**
* Buckets a milestone count to reduce bStats cardinality.
*
* @param count count
* @return bucket string
*/
private String bucketCount(final int count) {
if (count <= 0) {
return "0";
}
if (count == 1) {
return "1";
}
if (count <= 5) {
return "2-5";
}
if (count <= 10) {
return "6-10";
}
if (count <= 25) {
return "11-25";
}
if (count <= 50) {
return "26-50";
}
if (count <= 100) {
return "51-100";
}
return ">100";
}
/**
* Buckets MaxSitesListed to reduce bStats cardinality.
*
* @param maxSites max sites listed
* @return bucket string
*/
private String bucketMaxSites(final int maxSites) {
if (maxSites <= 0) {
return "0";
}
if (maxSites <= 5) {
return "1-5";
}
if (maxSites <= 10) {
return "6-10";
}
if (maxSites <= 25) {
return "11-25";
}
return ">25";
}
/**
* Buckets purge days to reduce bStats cardinality.
*
* @param purgeDays purge days
* @return bucket string
*/
private String bucketPurgeDays(final int purgeDays) {
if (purgeDays < 0) {
return "disabled";
}
if (purgeDays == 0) {
return "0";
}
if (purgeDays <= 7) {
return "1-7";
}
if (purgeDays <= 30) {
return "8-30";
}
if (purgeDays <= 90) {
return "31-90";
}
if (purgeDays <= 365) {
return "91-365";
}
return ">365";
}
}
@@ -0,0 +1,102 @@
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,76 +1,430 @@
package com.bencodez.votingplugin.broadcast;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
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.messages.PlaceholderUtils;
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;
public class BroadcastHandler {
private VotingPluginMain plugin;
private ScheduledExecutorService timer;
ConcurrentLinkedQueue<String> votedPlayers = new ConcurrentLinkedQueue<>();
/**
* 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 {
public BroadcastHandler(VotingPluginMain plugin, int delay) {
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;
schedule(delay);
this.settings = settings;
this.zoneId = zoneId == null ? ZoneId.systemDefault() : zoneId;
rescheduleIntervalIfNeeded();
}
public void check() {
int size = votedPlayers.size();
if (size > 0) {
for (int i = 0; i < size; i++) {
votedPlayers.poll();
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;
}
String bc = MessageAPI.colorize(plugin.getConfigFile().getFormatAlternateBroadcastBroadcast());
String playersText = "";
while (!votedPlayers.isEmpty()) {
playersText += votedPlayers.remove();
if (!votedPlayers.isEmpty()) {
playersText += ", ";
}
}
HashMap<String, String> placeholders = new HashMap<>();
placeholders.put("numberofplayers", "" + size);
placeholders.put("players", playersText);
bc = PlaceholderUtils.replacePlaceHolder(bc, placeholders);
ArrayList<Player> players = new ArrayList<>();
for (Player p : Bukkit.getOnlinePlayers()) {
if (!plugin.getVotingPluginUserManager().getVotingPluginUser(p).getDisableBroadcast()) {
players.add(p);
}
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);
}
MiscUtils.getInstance().broadcast(bc, players);
} 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
}
}
public void onVote(String player) {
votedPlayers.add(player);
}
/*
* ======================= Broadcast helpers =======================
*/
public void schedule(int delay) {
if (timer != null) {
timer.shutdownNow();
timer = Executors.newScheduledThreadPool(1);
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 {
timer = Executors.newScheduledThreadPool(1);
lines = s.getFormat().render(playerName, cleaned, reason, extraContext);
}
timer.scheduleWithFixedDelay(new Runnable() {
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() {
check();
try {
flushBatch(uuid, playerName);
} finally {
pendingFlush.remove(uuid);
}
}
}, 60, 60 * delay, TimeUnit.SECONDS);
}, 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();
}
}
@@ -0,0 +1,76 @@
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));
}
}
@@ -0,0 +1,60 @@
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;
}
}
}
File diff suppressed because it is too large. Load diff
@@ -29,11 +29,10 @@ import com.bencodez.simpleapi.array.ArrayUtils;
import com.bencodez.simpleapi.player.PlayerUtils;
import com.bencodez.votingplugin.VotingPluginMain;
import com.bencodez.votingplugin.commands.gui.admin.AdminVoteVoteParty;
import com.bencodez.votingplugin.commands.gui.admin.cumulative.AdminVoteCumulative;
import com.bencodez.votingplugin.commands.gui.admin.milestones.AdminVoteMilestones;
import com.bencodez.votingplugin.commands.gui.admin.milestones.AdminVoteVoteMilestones;
import com.bencodez.votingplugin.commands.gui.admin.voteshop.AdminVoteVoteShop;
import com.bencodez.votingplugin.events.PlayerVoteEvent;
import com.bencodez.votingplugin.objects.VoteSite;
import com.bencodez.votingplugin.votesites.VoteSite;
public class AdminGUI {
@@ -65,7 +64,7 @@ public class AdminGUI {
plugin.getConfigVoteSites().generateVoteSite(value);
player.sendMessage("Generated site");
plugin.reload();
openAdminGUIVoteSiteSite(player, plugin.getVoteSite(value, true));
openAdminGUIVoteSiteSite(player, plugin.getVoteSiteManager().getVoteSite(value, true));
}
});
} else {
@@ -82,19 +81,11 @@ public class AdminGUI {
}
});
buttons.add(new BInventoryButton(new ItemBuilder(Material.DIAMOND_BLOCK, 1).setName("&cEdit Milestones")) {
buttons.add(new BInventoryButton(new ItemBuilder(Material.DIAMOND_BLOCK, 1).setName("&cEdit VoteMilestones")) {
@Override
public void onClick(ClickEvent clickEvent) {
new AdminVoteMilestones(plugin, clickEvent.getPlayer()).open(GUIMethod.CHEST);
}
});
buttons.add(new BInventoryButton(new ItemBuilder(Material.COBBLESTONE, 1).setName("&cEdit Cumulative")) {
@Override
public void onClick(ClickEvent clickEvent) {
new AdminVoteCumulative(plugin, clickEvent.getPlayer()).open(GUIMethod.CHEST);
new AdminVoteVoteMilestones(plugin, clickEvent.getPlayer()).open(GUIMethod.CHEST);
}
});
@@ -240,7 +231,7 @@ public class AdminGUI {
public void openAdminGUIVoteSites(Player player) {
BInventory inv = new BInventory("VoteSites");
int count = 0;
for (VoteSite voteSite : plugin.getVoteSites()) {
for (VoteSite voteSite : plugin.getVoteSiteManager().getVoteSites()) {
ArrayList<String> lore = new ArrayList<>();
lore.add("Enabled: " + voteSite.isEnabled());
lore.add("Priority: " + voteSite.getPriority());
@@ -249,7 +240,6 @@ public class AdminGUI {
lore.add("ServiceSite: " + voteSite.getServiceSite());
lore.add("VoteURL: " + voteSite.getVoteURL());
lore.add("VoteDelay: " + voteSite.getVoteDelay());
lore.add("VoteDelayMin: " + voteSite.getVoteDelayMin());
inv.addButton(count,
new BInventoryButton(voteSite.getKey(), ArrayUtils.convert(lore), new ItemStack(Material.STONE)) {
@@ -349,24 +339,13 @@ public class AdminGUI {
}
}));
inv.addButton(new EditGUIButton(new EditGUIValueNumber("VoteDelay", voteSite.getVoteDelay()) {
inv.addButton(new EditGUIButton(new EditGUIValueString("VoteDelay", voteSite.getVoteDelay()) {
@Override
public void setValue(Player player, Number num) {
public void setValue(Player player, String value) {
VoteSite voteSite = (VoteSite) getInv().getMeta(player, "VoteSite");
String siteName = voteSite.getKey();
plugin.getConfigVoteSites().setVoteDelay(siteName, num.intValue());
plugin.reload();
}
}));
inv.addButton(new EditGUIButton(new EditGUIValueNumber("VoteDelayMin", voteSite.getVoteDelay()) {
@Override
public void setValue(Player player, Number num) {
VoteSite voteSite = (VoteSite) getInv().getMeta(player, "VoteSite");
String siteName = voteSite.getKey();
plugin.getConfigVoteSites().setVoteDelay(siteName, num.intValue());
plugin.getConfigVoteSites().setVoteDelay(siteName, value);
plugin.reload();
}
}));
@@ -377,7 +356,7 @@ public class AdminGUI {
public void setValue(Player player, Number num) {
VoteSite voteSite = (VoteSite) getInv().getMeta(player, "VoteSite");
String siteName = voteSite.getKey();
plugin.getConfigVoteSites().setVoteDelay(siteName, num.intValue());
plugin.getConfigVoteSites().setVoteDelayDailyHour(siteName, num.intValue());
plugin.reload();
}
}));
@@ -73,9 +73,6 @@ public class AdminVotePlaceholdersPlayer extends GUIHandler {
ArrayList<String> msg = new ArrayList<>();
msg.add("&cPlaceholders:");
VotingPluginUser placeholderUser = user;
if (plugin.getConfigFile().isUsePrimaryAccountForPlaceholders() && user.hasPrimaryAccount()) {
placeholderUser = plugin.getVotingPluginUserManager().getVotingPluginUser(user.getPrimaryAccount());
}
for (PlaceHolder<VotingPluginUser> placeholder : plugin.getPlaceholders().getPlaceholders()) {
String identifier = placeholder.getIdentifier().toLowerCase();
if (identifier.endsWith("_")) {
@@ -21,6 +21,7 @@ import com.bencodez.advancedcore.api.inventory.BInventory.ClickEvent;
import com.bencodez.advancedcore.api.inventory.BInventoryButton;
import com.bencodez.advancedcore.api.item.ItemBuilder;
import com.bencodez.advancedcore.api.rewards.RewardBuilder;
import com.bencodez.advancedcore.api.user.UserDataFetchMode;
import com.bencodez.votingplugin.VotingPluginMain;
import com.bencodez.votingplugin.commands.gui.player.VoteGUI;
import com.bencodez.votingplugin.topvoter.TopVoterPlayer;
@@ -63,7 +64,7 @@ public class AdminVoteTopPoints extends GUIHandler {
for (String uuid : plugin.getVotingPluginUserManager().getAllUUIDs()) {
VotingPluginUser vpUser = plugin.getVotingPluginUserManager()
.getVotingPluginUser(UUID.fromString(uuid));
vpUser.dontCache();
vpUser.userDataFetechMode(UserDataFetchMode.NO_CACHE);
int points = vpUser.getPoints();
if (points > 0) {
topPoints1.put(vpUser.getTopVoterPlayer(), points);
@@ -12,7 +12,7 @@ import com.bencodez.advancedcore.api.inventory.BInventoryButton;
import com.bencodez.advancedcore.api.inventory.editgui.EditGUI;
import com.bencodez.votingplugin.VotingPluginMain;
import com.bencodez.votingplugin.events.PlayerVoteEvent;
import com.bencodez.votingplugin.objects.VoteSite;
import com.bencodez.votingplugin.votesites.VoteSite;
public class AdminVoteVotePlayer extends GUIHandler {
@@ -44,7 +44,7 @@ public class AdminVoteVotePlayer extends GUIHandler {
EditGUI inv = new EditGUI("Trigger vote for " + playerName);
inv.requirePermission("VotingPlugin.Commands.AdminVote.Vote|VotingPlugin.Admin");
for (VoteSite site : plugin.getVoteSitesEnabled()) {
for (VoteSite site : plugin.getVoteSiteManager().getVoteSitesEnabled()) {
inv.addButton(new BInventoryButton(site.getItem().setName(site.getKey())) {
@Override
@@ -1,109 +0,0 @@
package com.bencodez.votingplugin.commands.gui.admin.cumulative;
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.advancedcore.api.valuerequest.InputMethod;
import com.bencodez.advancedcore.api.valuerequest.ValueRequest;
import com.bencodez.advancedcore.api.valuerequest.ValueRequestBuilder;
import com.bencodez.advancedcore.api.valuerequest.listeners.NumberListener;
import com.bencodez.advancedcore.command.gui.RewardEditGUI;
import com.bencodez.simpleapi.messages.MessageAPI;
import com.bencodez.votingplugin.VotingPluginMain;
public class AdminVoteCumulative extends GUIHandler {
private VotingPluginMain plugin;
public AdminVoteCumulative(VotingPluginMain plugin, CommandSender player) {
super(plugin, player);
this.plugin = plugin;
}
@Override
public ArrayList<String> getChat(CommandSender sender) {
return null;
}
@Override
public void onBook(Player player) {
}
@Override
public void onChat(CommandSender sender) {
}
@Override
public void onChest(Player player) {
BInventory inv = new BInventory("Edit Cumulative");
inv.requirePermission("VotingPlugin.Commands.AdminVote.Edit.Cumulative");
inv.addButton(
new BInventoryButton(new ItemBuilder(Material.PAPER).setName("&cEdit existing cumulative rewards")) {
@Override
public void onClick(ClickEvent clickEvent) {
ArrayList<Integer> nums = new ArrayList<>();
for (String num : plugin.getSpecialRewardsConfig().getCumulativeVotes()) {
if (MessageAPI.isInt(num)) {
nums.add(Integer.parseInt(num));
}
}
Number[] options = new Number[nums.size()];
for (int i = 0; i < nums.size(); i++) {
options[i] = nums.get(i);
}
new ValueRequestBuilder(new NumberListener() {
@Override
public void onInput(Player player, Number value) {
RewardEditGUI.getInstance().openRewardGUI(clickEvent.getPlayer(),
plugin.getRewardHandler()
.getDirectlyDefined("Cumulative." + value.intValue() + ".Rewards"));
}
}, options).allowCustomOption(false).usingMethod(InputMethod.INVENTORY)
.request(clickEvent.getPlayer());
}
});
inv.addButton(new BInventoryButton(new ItemBuilder(Material.EMERALD_BLOCK).setName("&aAdd cumulative reward")) {
@Override
public void onClick(ClickEvent clickEvent) {
new ValueRequest().requestNumber(player, new NumberListener() {
@Override
public void onInput(Player player, Number value) {
plugin.getSpecialRewardsConfig().setCumulative(value.intValue());
plugin.reload();
}
});
}
});
inv.addButton(new BInventoryButton(new ItemBuilder(Material.REDSTONE_BLOCK).setName("&cRemove cumulative")) {
@Override
public void onClick(ClickEvent clickEvent) {
new AdminVoteCumulativeRemove(plugin, clickEvent.getPlayer()).open();
}
});
inv.openInventory(player);
}
@Override
public void open() {
open(GUIMethod.CHEST);
}
}
@@ -1,77 +0,0 @@
package com.bencodez.votingplugin.commands.gui.admin.cumulative;
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.commands.gui.admin.AdminVoteConfirmation;
public class AdminVoteCumulativeRemove extends GUIHandler {
private VotingPluginMain plugin;
public AdminVoteCumulativeRemove(VotingPluginMain plugin, CommandSender player) {
super(plugin, player);
this.plugin = plugin;
}
@Override
public ArrayList<String> getChat(CommandSender sender) {
return null;
}
@Override
public void onBook(Player player) {
}
@Override
public void onChat(CommandSender sender) {
}
@Override
public void onChest(Player player) {
BInventory inv = new BInventory("Remove cumulative");
inv.requirePermission("VotingPlugin.Commands.AdminVote.Edit.Cumulative");
for (final String votes : plugin.getSpecialRewardsConfig().getCumulativeVotes()) {
inv.addButton(new BInventoryButton(
new ItemBuilder(Material.PAPER, 1).setName(votes).addLoreLine("&c&lClick to remove")) {
@Override
public void onClick(ClickEvent clickEvent) {
new AdminVoteConfirmation(plugin, clickEvent.getPlayer(), "Remove cumulative " + votes + "?") {
@Override
public void onConfirm(Player p) {
plugin.getSpecialRewardsConfig().removeCumulative(votes);
p.sendMessage("Removed cumulative " + votes);
plugin.reload();
}
@Override
public void onDeny(Player p) {
new AdminVoteCumulative(plugin, p);
}
}.open();
}
});
}
inv.openInventory(player);
}
@Override
public void open() {
open(GUIMethod.CHEST);
}
}
@@ -15,11 +15,11 @@ import com.bencodez.advancedcore.api.item.ItemBuilder;
import com.bencodez.votingplugin.VotingPluginMain;
import com.bencodez.votingplugin.commands.gui.admin.AdminVoteConfirmation;
public class AdminVoteMilestoneRemove extends GUIHandler {
public class AdminVoteVoteMilestoneRemove extends GUIHandler {
private VotingPluginMain plugin;
public AdminVoteMilestoneRemove(VotingPluginMain plugin, CommandSender player) {
public AdminVoteVoteMilestoneRemove(VotingPluginMain plugin, CommandSender player) {
super(plugin, player);
this.plugin = plugin;
}
@@ -40,9 +40,9 @@ public class AdminVoteMilestoneRemove extends GUIHandler {
@Override
public void onChest(Player player) {
BInventory inv = new BInventory("Remove milestones");
inv.requirePermission("VotingPlugin.Commands.AdminVote.Edit.MileStones");
for (final String votes : plugin.getSpecialRewardsConfig().getMilestoneVotes()) {
BInventory inv = new BInventory("Remove VoteMilestones");
inv.requirePermission("VotingPlugin.Commands.AdminVote.Edit.VoteMilestones");
for (final String votes : plugin.getVoteMilestonesManager().getConfig().getMilestones().keySet()) {
inv.addButton(new BInventoryButton(
new ItemBuilder(Material.PAPER, 1).setName(votes).addLoreLine("&c&lClick to remove")) {
@@ -52,14 +52,14 @@ public class AdminVoteMilestoneRemove extends GUIHandler {
@Override
public void onConfirm(Player p) {
plugin.getSpecialRewardsConfig().removeMilestone(votes);
plugin.getSpecialRewardsConfig().removeVoteMilestone(votes);
p.sendMessage("Removed milestone " + votes);
plugin.reload();
}
@Override
public void onDeny(Player p) {
new AdminVoteMilestones(plugin, p);
new AdminVoteVoteMilestones(plugin, p);
}
}.open();
@@ -1,6 +1,7 @@
package com.bencodez.votingplugin.commands.gui.admin.milestones;
import java.util.ArrayList;
import java.util.Set;
import org.bukkit.Material;
import org.bukkit.command.CommandSender;
@@ -16,15 +17,16 @@ import com.bencodez.advancedcore.api.valuerequest.InputMethod;
import com.bencodez.advancedcore.api.valuerequest.ValueRequest;
import com.bencodez.advancedcore.api.valuerequest.ValueRequestBuilder;
import com.bencodez.advancedcore.api.valuerequest.listeners.NumberListener;
import com.bencodez.advancedcore.api.valuerequest.listeners.StringListener;
import com.bencodez.advancedcore.command.gui.RewardEditGUI;
import com.bencodez.simpleapi.messages.MessageAPI;
import com.bencodez.simpleapi.array.ArrayUtils;
import com.bencodez.votingplugin.VotingPluginMain;
public class AdminVoteMilestones extends GUIHandler {
public class AdminVoteVoteMilestones extends GUIHandler {
private VotingPluginMain plugin;
public AdminVoteMilestones(VotingPluginMain plugin, CommandSender player) {
public AdminVoteVoteMilestones(VotingPluginMain plugin, CommandSender player) {
super(plugin, player);
this.plugin = plugin;
}
@@ -45,36 +47,31 @@ public class AdminVoteMilestones extends GUIHandler {
@Override
public void onChest(Player player) {
BInventory inv = new BInventory("Edit MileStones");
inv.requirePermission("VotingPlugin.Commands.AdminVote.Edit.MileStones");
inv.addButton(
new BInventoryButton(new ItemBuilder(Material.PAPER).setName("&cEdit existing milestone rewards")) {
BInventory inv = new BInventory("Edit VoteMilestones");
inv.requirePermission("VotingPlugin.Commands.AdminVote.Edit.VoteMilestones");
inv.addButton(new BInventoryButton(new ItemBuilder(Material.PAPER)
.setName("&cEdit existing vote milestone rewards (Legacy milestones not included)")) {
@Override
public void onClick(ClickEvent clickEvent) {
Set<String> keys = plugin.getVoteMilestonesManager().getConfig().getMilestones().keySet();
for (String key : keys) {
if (key.startsWith("Legacy_")) {
keys.remove(key);
}
}
new ValueRequestBuilder(new StringListener() {
@Override
public void onClick(ClickEvent clickEvent) {
ArrayList<Integer> nums = new ArrayList<>();
for (String num : plugin.getSpecialRewardsConfig().getMilestoneVotes()) {
if (MessageAPI.isInt(num)) {
nums.add(Integer.parseInt(num));
}
}
Number[] options = new Number[nums.size()];
for (int i = 0; i < nums.size(); i++) {
options[i] = nums.get(i);
}
new ValueRequestBuilder(new NumberListener() {
@Override
public void onInput(Player player, Number value) {
RewardEditGUI.getInstance().openRewardGUI(clickEvent.getPlayer(),
plugin.getRewardHandler()
.getDirectlyDefined("MileStones." + value.intValue() + ".Rewards"));
}
}, options).allowCustomOption(false).usingMethod(InputMethod.INVENTORY)
.request(clickEvent.getPlayer());
public void onInput(Player player, String value) {
RewardEditGUI.getInstance().openRewardGUI(clickEvent.getPlayer(),
plugin.getRewardHandler().getDirectlyDefined("VoteMilestones." + value + ".Rewards"));
}
});
}, ArrayUtils.convertSet(keys)).allowCustomOption(false).usingMethod(InputMethod.INVENTORY)
.request(clickEvent.getPlayer());
}
});
inv.addButton(new BInventoryButton(new ItemBuilder(Material.EMERALD_BLOCK).setName("&aAdd milestone")) {
@@ -84,7 +81,7 @@ public class AdminVoteMilestones extends GUIHandler {
@Override
public void onInput(Player player, Number value) {
plugin.getSpecialRewardsConfig().setMilestone(value.intValue());
plugin.getSpecialRewardsConfig().setVoteMilestone(value.intValue());
plugin.reload();
}
});
@@ -95,7 +92,7 @@ public class AdminVoteMilestones extends GUIHandler {
@Override
public void onClick(ClickEvent clickEvent) {
new AdminVoteMilestoneRemove(plugin, clickEvent.getPlayer()).open();
new AdminVoteVoteMilestoneRemove(plugin, clickEvent.getPlayer()).open();
}
});
inv.openInventory(player);
@@ -0,0 +1,145 @@
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);
}
}
@@ -0,0 +1,85 @@
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;
}
}
@@ -0,0 +1,188 @@
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);
}
}
@@ -0,0 +1,170 @@
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);
}
}
@@ -0,0 +1,140 @@
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);
}
}
@@ -0,0 +1,136 @@
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);
}
}
@@ -0,0 +1,222 @@
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);
}
}
@@ -0,0 +1,178 @@
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);
}
}
@@ -0,0 +1,137 @@
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);
}
}
@@ -0,0 +1,178 @@
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);
}
}
@@ -0,0 +1,137 @@
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);
}
}
@@ -0,0 +1,135 @@
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);
}
}
@@ -0,0 +1,211 @@
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);
}
}
@@ -0,0 +1,231 @@
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);
}
}
@@ -17,8 +17,8 @@ import com.bencodez.advancedcore.api.rewards.RewardBuilder;
import com.bencodez.simpleapi.array.ArrayUtils;
import com.bencodez.simpleapi.messages.MessageAPI;
import com.bencodez.votingplugin.VotingPluginMain;
import com.bencodez.votingplugin.objects.VoteSite;
import com.bencodez.votingplugin.user.VotingPluginUser;
import com.bencodez.votingplugin.votesites.VoteSite;
public class VoteLast extends GUIHandler {
@@ -40,7 +40,7 @@ public class VoteLast extends GUIHandler {
msg.add(MessageAPI.replaceIgnoreCase(plugin.getConfigFile().getFormatCommandsVoteLastTitle(), "%player%",
playerName));
for (VoteSite voteSite : plugin.getVoteSitesEnabled()) {
for (VoteSite voteSite : plugin.getVoteSiteManager().getVoteSitesEnabled()) {
if (!voteSite.isHidden()) {
msg.add(user.voteCommandLastLine(voteSite));
}
@@ -63,7 +63,7 @@ public class VoteLast extends GUIHandler {
public void onChest(Player player) {
BInventory inv = new BInventory(plugin.getGui().getChestVoteLastName());
inv.addPlaceholder("player", user.getPlayerName());
for (VoteSite site : plugin.getVoteSitesEnabled()) {
for (VoteSite site : plugin.getVoteSiteManager().getVoteSitesEnabled()) {
if (!site.isHidden()) {
inv.addButton(inv.getNextSlot(),
new UpdatingBInventoryButton(plugin, site.getItem().setName(site.getDisplayName())
@@ -19,8 +19,8 @@ import com.bencodez.advancedcore.api.messages.PlaceholderUtils;
import com.bencodez.advancedcore.api.rewards.RewardBuilder;
import com.bencodez.simpleapi.messages.MessageAPI;
import com.bencodez.votingplugin.VotingPluginMain;
import com.bencodez.votingplugin.objects.VoteSite;
import com.bencodez.votingplugin.user.VotingPluginUser;
import com.bencodez.votingplugin.votesites.VoteSite;
import xyz.upperlevel.spigot.book.BookUtil;
@@ -44,7 +44,7 @@ public class VoteNext extends GUIHandler {
msg.add(MessageAPI.colorize(MessageAPI
.replaceIgnoreCase(plugin.getConfigFile().getFormatCommandsVoteNextTitle(), "%player%", playerName)));
for (VoteSite voteSite : plugin.getVoteSitesEnabled()) {
for (VoteSite voteSite : plugin.getVoteSiteManager().getVoteSitesEnabled()) {
if (!voteSite.isHidden()) {
String msgLine = plugin.getConfigFile().getFormatCommandsVoteNextLayout();
@@ -62,7 +62,7 @@ public class VoteNext extends GUIHandler {
BookWrapper book = new BookWrapper(plugin.getGui().getBookVoteURLBookGUITitle());
// add colors/config options
for (VoteSite site : plugin.getVoteSitesEnabled()) {
for (VoteSite site : plugin.getVoteSiteManager().getVoteSitesEnabled()) {
if (!site.isHidden()) {
Layout nextLayout = new Layout(new ArrayList<>(Arrays.asList("[Json]")));
nextLayout.replaceTextComponent("[Json]",
@@ -86,7 +86,7 @@ public class VoteNext extends GUIHandler {
public void onChest(Player player) {
BInventory inv = new BInventory(plugin.getGui().getChestVoteNextName());
inv.addPlaceholder("player", user.getPlayerName());
for (VoteSite site : plugin.getVoteSitesEnabled()) {
for (VoteSite site : plugin.getVoteSiteManager().getVoteSitesEnabled()) {
if (!site.isHidden()) {
String siteNameDisplay = plugin.getGui().getChestVoteNextCustomSiteNamesDisplays(site.getKey());
if (siteNameDisplay.isEmpty()) {
@@ -3,6 +3,7 @@ package com.bencodez.votingplugin.commands.gui.player;
import java.util.ArrayList;
import java.util.HashMap;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
@@ -17,6 +18,7 @@ import com.bencodez.advancedcore.api.rewards.RewardBuilder;
import com.bencodez.advancedcore.api.rewards.RewardOptions;
import com.bencodez.simpleapi.messages.MessageAPI;
import com.bencodez.votingplugin.VotingPluginMain;
import com.bencodez.votingplugin.events.VoteShopPurchaseEvent;
import com.bencodez.votingplugin.user.VotingPluginUser;
public class VoteShop extends GUIHandler {
@@ -135,9 +137,6 @@ public class VoteShop extends GUIHandler {
if (user.removePoints(points, true)) {
plugin.getLogger().info("VoteShop: " + user.getPlayerName() + "/"
+ user.getUUID() + " bought " + identifier + " for " + points);
if (plugin.getConfigFile().isTrackShopPurchases()) {
plugin.getServerData().addVoteShopPurchase(identifier);
}
plugin.getRewardHandler().giveReward(user, plugin.getShopFile().getData(),
plugin.getShopFile().getShopIdentifierRewardsPath(identifier),
@@ -146,6 +145,12 @@ public class VoteShop extends GUIHandler {
user.sendMessage(PlaceholderUtils.replacePlaceHolder(
plugin.getShopFile().getVoteShopPurchase(identifier),
placeholders));
VoteShopPurchaseEvent purcahseEvent = new VoteShopPurchaseEvent(
player.getUniqueId(), player.getName(), user, identifier, points);
Bukkit.getPluginManager().callEvent(purcahseEvent);
if (limit > 0) {
user.setVoteShopIdentifierLimit(identifier,
user.getVoteShopIdentifierLimit(identifier) + 1);
@@ -3,6 +3,7 @@ package com.bencodez.votingplugin.commands.gui.player;
import java.util.ArrayList;
import java.util.HashMap;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
@@ -16,6 +17,7 @@ import com.bencodez.advancedcore.api.messages.PlaceholderUtils;
import com.bencodez.advancedcore.api.rewards.RewardOptions;
import com.bencodez.simpleapi.player.PlayerUtils;
import com.bencodez.votingplugin.VotingPluginMain;
import com.bencodez.votingplugin.events.VoteShopPurchaseEvent;
import com.bencodez.votingplugin.user.VotingPluginUser;
public class VoteShopConfirm extends GUIHandler {
@@ -63,9 +65,6 @@ public class VoteShopConfirm extends GUIHandler {
if (user.removePoints(points, true)) {
plugin.getLogger().info("VoteShop: " + user.getPlayerName() + "/" + user.getUUID() + " bought "
+ identifier + " for " + points);
if (plugin.getConfigFile().isTrackShopPurchases()) {
plugin.getServerData().addVoteShopPurchase(identifier);
}
plugin.getRewardHandler().giveReward(user, plugin.getShopFile().getData(),
plugin.getShopFile().getShopIdentifierRewardsPath(identifier),
@@ -73,6 +72,11 @@ public class VoteShopConfirm extends GUIHandler {
user.sendMessage(PlaceholderUtils
.replacePlaceHolder(plugin.getShopFile().getVoteShopPurchase(identifier), placeholders));
VoteShopPurchaseEvent purcahseEvent = new VoteShopPurchaseEvent(player.getUniqueId(),
player.getName(), user, identifier, points);
Bukkit.getPluginManager().callEvent(purcahseEvent);
if (limit > 0) {
user.setVoteShopIdentifierLimit(identifier, user.getVoteShopIdentifierLimit(identifier) + 1);
}
@@ -21,9 +21,9 @@ import com.bencodez.advancedcore.api.rewards.RewardBuilder;
import com.bencodez.simpleapi.array.ArrayUtils;
import com.bencodez.simpleapi.nms.NMSManager;
import com.bencodez.votingplugin.VotingPluginMain;
import com.bencodez.votingplugin.objects.VoteSite;
import com.bencodez.votingplugin.topvoter.TopVoterPlayer;
import com.bencodez.votingplugin.user.VotingPluginUser;
import com.bencodez.votingplugin.votesites.VoteSite;
public class VoteToday extends GUIHandler {
@@ -23,9 +23,9 @@ import com.bencodez.advancedcore.api.messages.PlaceholderUtils;
import com.bencodez.advancedcore.api.rewards.RewardBuilder;
import com.bencodez.simpleapi.array.ArrayUtils;
import com.bencodez.votingplugin.VotingPluginMain;
import com.bencodez.votingplugin.objects.VoteSite;
import com.bencodez.votingplugin.topvoter.TopVoter;
import com.bencodez.votingplugin.user.VotingPluginUser;
import com.bencodez.votingplugin.votesites.VoteSite;
import xyz.upperlevel.spigot.book.BookUtil;
@@ -43,11 +43,6 @@ public class VoteURL extends GUIHandler {
if (user == null && player instanceof Player) {
this.user = plugin.getVotingPluginUserManager().getVotingPluginUser((Player) player);
}
if (player != null && user != null && player.getName().equals(user.getPlayerName())) {
if (plugin.getConfigFile().isUsePrimaryAccountForPlaceholders() && user.hasPrimaryAccount()) {
user = plugin.getVotingPluginUserManager().getVotingPluginUser(user.getPrimaryAccount());
}
}
}
@Override
@@ -64,7 +59,7 @@ public class VoteURL extends GUIHandler {
}
if (plugin.getConfigFile().isFormatCommandsVoteAutoInputSites()) {
int counter = 0;
for (VoteSite voteSite : plugin.getVoteSitesEnabled()) {
for (VoteSite voteSite : plugin.getVoteSiteManager().getVoteSitesEnabled()) {
if (!voteSite.isHidden()) {
if (voteSite.getPermissionToView().isEmpty()
|| sender.hasPermission(voteSite.getPermissionToView())) {
@@ -154,7 +149,7 @@ public class VoteURL extends GUIHandler {
book.addLayout(layout);
}
int i = 1;
for (VoteSite site : plugin.getVoteSitesEnabled()) {
for (VoteSite site : plugin.getVoteSiteManager().getVoteSitesEnabled()) {
if (!site.isHidden()) {
if (site.getPermissionToView().isEmpty() || player.hasPermission(site.getPermissionToView())) {
Layout layout = new Layout(plugin.getGui().getBookVoteURLBookGUILayout())
@@ -232,7 +227,7 @@ public class VoteURL extends GUIHandler {
}
int startSlot = plugin.getGui().getChestVoteURLAllUrlsButtonStartSlot();
for (final VoteSite voteSite : plugin.getVoteSitesEnabled()) {
for (final VoteSite voteSite : plugin.getVoteSiteManager().getVoteSitesEnabled()) {
if (!voteSite.isHidden()) {
if (voteSite.getPermissionToView().isEmpty() || player.hasPermission(voteSite.getPermissionToView())) {
ItemBuilder builder = getItemVoteSite(voteSite);
@@ -14,8 +14,8 @@ import com.bencodez.advancedcore.api.inventory.BInventoryButton;
import com.bencodez.advancedcore.api.inventory.UpdatingBInventoryButton;
import com.bencodez.advancedcore.api.item.ItemBuilder;
import com.bencodez.votingplugin.VotingPluginMain;
import com.bencodez.votingplugin.objects.VoteSite;
import com.bencodez.votingplugin.user.VotingPluginUser;
import com.bencodez.votingplugin.votesites.VoteSite;
public class VoteURLVoteSite extends GUIHandler {
@@ -47,11 +47,11 @@ public class VoteURLVoteSite extends GUIHandler {
@Override
public void onChest(Player player) {
if (!plugin.isVoteSite(voteSite)) {
if (!plugin.getVoteSiteManager().isVoteSite(voteSite)) {
player.sendMessage("Not a valid votesite");
return;
}
VoteSite site = plugin.getVoteSite(voteSite, true);
VoteSite site = plugin.getVoteSiteManager().getVoteSite(voteSite, true);
BInventory inv = new BInventory(plugin.getGui().getChestVoteURLSiteName());
inv.addPlaceholder("site", site.getDisplayName());
inv.setMeta(player, "VoteSite", site);
@@ -0,0 +1,141 @@
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));
}
}
}
}
}
@@ -15,14 +15,6 @@ import lombok.Getter;
public class BungeeSettings extends YMLFile {
@ConfigDataBoolean(path = "BungeeBroadcast")
@Getter
private boolean bungeeBroadcast = false;
@ConfigDataBoolean(path = "BungeeBroadcastAlways")
@Getter
private boolean bungeeBroadcastAlways = false;
@ConfigDataBoolean(path = "BungeeDebug")
@Getter
private boolean bungeeDebug = false;
@@ -33,32 +25,36 @@ public class BungeeSettings extends YMLFile {
@ConfigDataString(path = "Redis.Host")
@Getter
private String RedisHost = "";
private String redisHost = "";
@ConfigDataString(path = "Redis.Username")
@Getter
private String RedisUsername = "";
private String redisUsername = "";
@ConfigDataInt(path = "Redis.Db-Index")
@Getter
private int redisdbindex = 0;
@ConfigDataString(path = "PluginMessageChannel")
@Getter
private String pluginMessagingChannel = "vp:vp";
@ConfigDataBoolean(path = "PluginMessageEncryption")
@Getter
private boolean pluginMessageEncryption = false;
@ConfigDataString(path = "Redis.Prefix")
@Getter
private String RedisPrefix = "";
private String redisPrefix = "";
@ConfigDataString(path = "Redis.Password")
@Getter
private String RedisPassword = "";
private String redisPassword = "";
@ConfigDataInt(path = "Redis.Port")
@Getter
private int RedisPort = 6379;
private int redisPort = 6379;
@ConfigDataString(path = "MQTT.ClientID")
@Getter
private String mqttClientID = "";
@@ -91,10 +87,6 @@ public class BungeeSettings extends YMLFile {
@Getter
private boolean perServerPoints = false;
@ConfigDataBoolean(path = "PerServerMilestones")
@Getter
private boolean perServerMilestones = false;
@ConfigDataBoolean(path = "GiveExtraAllSitesRewards")
@Getter
private boolean giveExtraAllSitesRewards = false;
@@ -131,10 +123,6 @@ public class BungeeSettings extends YMLFile {
@Getter
private boolean votifierBypass = false;
@ConfigDataBoolean(path = "DisableBroadcast")
@Getter
private boolean disableBroadcast = false;
@ConfigDataBoolean(path = "GlobalData.UseMainMySQL")
@Getter
private boolean globlalDataUseMainMySQL = true;
@@ -149,6 +137,7 @@ public class BungeeSettings extends YMLFile {
public BungeeSettings(VotingPluginMain plugin) {
super(plugin, new File(plugin.getDataFolder(), "BungeeSettings.yml"));
setIgnoreCase(plugin.getConfigFile().isCaseInsensitiveYMLFiles());
}
public String getServerNameStorage() {
@@ -52,6 +52,10 @@ public class Config extends YMLFile {
@Getter
private int limitVotePoints = -1;
@ConfigDataBoolean(path = "CaseInsensitiveYMLFiles")
@Getter
public boolean caseInsensitiveYMLFiles = false;
@ConfigDataBoolean(path = "QueueVotesDuringTimeChange")
@Getter
private boolean queueVotesDuringTimeChange = false;
@@ -76,6 +80,22 @@ public class Config extends YMLFile {
@Getter
private boolean discordSRVTopVoterAllTimeEnabled = false;
@ConfigDataBoolean(path = "DiscordSRV.TopVoter.AllTime.NewMessageOnUpdate")
@Getter
private boolean discordSRVTopVoterAllTimeNewMessageOnUpdate = false;
@ConfigDataBoolean(path = "DiscordSRV.TopVoter.Monthly.NewMessageOnUpdate")
@Getter
private boolean discordSRVTopVoterMonthlyNewMessageOnUpdate = false;
@ConfigDataBoolean(path = "DiscordSRV.TopVoter.Weekly.NewMessageOnUpdate")
@Getter
private boolean discordSRVTopVoterWeeklyNewMessageOnUpdate = false;
@ConfigDataBoolean(path = "DiscordSRV.TopVoter.Daily.NewMessageOnUpdate")
@Getter
private boolean discordSRVTopVoterDailyNewMessageOnUpdate = false;
@ConfigDataLong(path = "DiscordSRV.TopVoter.AllTime.Channel")
@Getter
private long discordSRVTopVoterAllTimeChannel = 0;
@@ -136,6 +156,26 @@ public class Config extends YMLFile {
@Getter
private String discordSRVTopVoterDailyRankDisplay = "%rank%: %player% - %votes% Votes";
// VoteLogging configuration section - minimal options mirror Config.yml
@ConfigDataBoolean(path = "VoteLogging.Enabled")
@Getter
private boolean voteLoggingEnabled = false;
@ConfigDataInt(path = "VoteLogging.PurgeDays")
@Getter
private int voteLoggingPurgeDays = 30;
@ConfigDataBoolean(path = "VoteLogging.UseMainMySQL")
@Getter
private boolean voteLoggingUseMainMySQL = true;
public ConfigurationSection getVoteLoggingSection() {
if (!getData().isConfigurationSection("VoteLogging")) {
getData().createSection("VoteLogging");
}
return getData().getConfigurationSection("VoteLogging");
}
public boolean isDiscordSRVTopVoterEnabled(TopVoter topVoter) {
switch (topVoter) {
case AllTime:
@@ -151,6 +191,21 @@ public class Config extends YMLFile {
}
}
public boolean isDiscordSRVTopVoterNewMessageOnUpdate(TopVoter topVoter) {
switch (topVoter) {
case AllTime:
return isDiscordSRVTopVoterAllTimeNewMessageOnUpdate();
case Monthly:
return isDiscordSRVTopVoterMonthlyNewMessageOnUpdate();
case Weekly:
return isDiscordSRVTopVoterWeeklyNewMessageOnUpdate();
case Daily:
return isDiscordSRVTopVoterDailyNewMessageOnUpdate();
default:
return false;
}
}
public long getDiscordSRVTopVoterChannel(TopVoter topVoter) {
switch (topVoter) {
case AllTime:
@@ -216,10 +271,6 @@ public class Config extends YMLFile {
@Getter
private boolean extraVoteShopCheck = true;
@ConfigDataBoolean(path = "TrackShopPurchases")
@Getter
private boolean trackShopPurchases = false;
@ConfigDataBoolean(path = "AlwaysUpdate")
@Getter
private boolean alwaysUpdate = false;
@@ -228,17 +279,13 @@ public class Config extends YMLFile {
@Getter
private boolean autoCreateVoteSites = true;
@ConfigDataBoolean(path = "Format.BroadcastVote")
@Getter
private boolean broadcastVotesEnabled = true;
@ConfigDataBoolean(path = "CountFakeVotes")
@Getter
private boolean countFakeVotes = true;
@ConfigDataInt(path = "DelayBetweenUpdates")
@ConfigDataString(path = "DelayBetweenUpdates")
@Getter
private int delayBetweenUpdates = 3;
private String delayBetweenUpdates = "3m";
@ConfigDataBoolean(path = "DisableAdvancedTab")
@Getter
@@ -268,6 +315,14 @@ public class Config extends YMLFile {
@Getter
private boolean disableNoServiceSiteMessage = false;
@ConfigDataString(path = "Format.Commands.AdminVote.Points.PlayerRemoved")
@Getter
private String formatCommandsAdminVotePointsPlayerRemoved = "&c%amount% voting points have been removed from you";
@ConfigDataString(path = "Format.Commands.AdminVote.Points.PlayerGiven")
@Getter
private String formatCommandsAdminVotePointsPlayerGiven = "&cYou have been given %amount% voting points";
@ConfigDataBoolean(path = "DisableUpdateChecking")
@Getter
private boolean disableUpdateChecking = false;
@@ -284,26 +339,6 @@ public class Config extends YMLFile {
@Getter
private boolean extraBackgroundUpdate = false;
@ConfigDataString(path = "Format.AlternateBroadcast.Broadcast")
@Getter
private String formatAlternateBroadcastBroadcast = "&6[&4Broadcast&6] &2%numberofplayers% voted in the last half hour! /vote";
@ConfigDataInt(path = "Format.AlternateBroadcast.Delay")
@Getter
private int formatAlternateBroadcastDelay = 30;
@ConfigDataBoolean(path = "Format.AlternateBroadcast.Enabled")
@Getter
private boolean formatAlternateBroadcastEnabled = false;
@ConfigDataBoolean(path = "Format.OnlyOneOfflineBroadcast")
@Getter
private boolean formatOnlyOneOfflineBroadcast = false;
@ConfigDataString(path = "Format.OfflineBroadcast")
@Getter
private String formatOfflineBroadcast = "&6[&4Broadcast&6] &2Thanks &c%player% &2for voting on %numberofvotes% times!";
@ConfigDataBoolean(path = "Format.Commands.Vote.ForceLinks")
@Getter
private boolean formatCommandsVoteForceLinks = true;
@@ -416,10 +451,6 @@ public class Config extends YMLFile {
@Getter
private boolean loadCommandAliases = true;
@ConfigDataBoolean(path = "LogVotesToFile")
@Getter
private boolean logVotesToFile = false;
@ConfigDataInt(path = "MaxiumNumberOfTopVotersToLoad")
@Getter
private int MaxiumNumberOfTopVotersToLoad = 1000;
@@ -452,10 +483,6 @@ public class Config extends YMLFile {
@Getter
private boolean useJavascriptPlaceholders = false;
@ConfigDataBoolean(path = "UsePrimaryAccountForPlaceholders")
@Getter
private boolean usePrimaryAccountForPlaceholders = false;
@ConfigDataBoolean(path = "UseVoteGUIMainCommand")
@Getter
private boolean useVoteGUIMainCommand = false;
@@ -476,14 +503,6 @@ public class Config extends YMLFile {
@Getter
private Set<String> customPlaceholderReturns = new HashSet<>();
@ConfigDataString(path = "Format.BroadcastMsg")
@Getter
private String formatBroadCastMsg = "&6[&4Broadcast&6] &2Thanks &c%player% &2for voting on %SiteName%";
@ConfigDataBoolean(path = "Format.BroadcastWhenOnline")
@Getter
private boolean formatBroadcastWhenOnline = false;
@ConfigDataBoolean(path = "Format.Commands.Vote.AutoInputSites")
@Getter
private boolean formatCommandsVoteAutoInputSites = true;
@@ -710,28 +729,17 @@ public class Config extends YMLFile {
@Getter
private boolean topVoterIgnorePermission = false;
@ConfigDataBoolean(path = "VoteReminding.Enabled")
@Getter
private boolean voteRemindingEnabled = true;
@ConfigDataInt(path = "VoteReminding.RemindDelay")
@Getter
private int voteRemindingRemindDelay = 30;
@ConfigDataBoolean(path = "VoteReminding.RemindOnLogin")
@Getter
private boolean voteRemindingRemindOnLogin = false;
@ConfigDataBoolean(path = "VoteReminding.RemindOnlyOnce")
@Getter
private boolean voteRemindingRemindOnlyOnce = false;
@ConfigDataString(path = "VoteTopDefault")
@Getter
private String voteTopDefault = "Monthly";
@ConfigDataBoolean(path = "AlwaysProcessAsyncPlaceholders")
@Getter
private boolean alwaysProcessAsyncPlaceholders = false;
public Config(VotingPluginMain plugin) {
super(plugin, new File(plugin.getDataFolder(), "Config.yml"));
setIgnoreCase(true);
}
/**
@@ -847,15 +855,6 @@ public class Config extends YMLFile {
return PlaceholderCacheLevel.getCache(getPlaceholderCacheLevelString());
}
/**
* Gets the rewards.
*
* @return the rewards
*/
public String getVoteRemindingRewardsPath() {
return "VoteReminding.Rewards";
}
@Override
public void loadValues() {
new AnnotationHandler().load(getData(), this);
@@ -867,4 +866,3 @@ public class Config extends YMLFile {
}
}
@@ -5,6 +5,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.concurrent.TimeUnit;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
@@ -17,8 +18,9 @@ import com.bencodez.advancedcore.api.rewards.DirectlyDefinedReward;
import com.bencodez.simpleapi.array.ArrayUtils;
import com.bencodez.simpleapi.file.YMLFile;
import com.bencodez.simpleapi.messages.MessageAPI;
import com.bencodez.simpleapi.time.ParsedDuration;
import com.bencodez.votingplugin.VotingPluginMain;
import com.bencodez.votingplugin.objects.VoteSite;
import com.bencodez.votingplugin.votesites.VoteSite;
// TODO: Auto-generated Javadoc
/**
@@ -30,6 +32,7 @@ public class ConfigVoteSites extends YMLFile {
public ConfigVoteSites(VotingPluginMain plugin) {
super(plugin, new File(plugin.getDataFolder(), "VoteSites.yml"));
setIgnoreCase(plugin.getConfigFile().isCaseInsensitiveYMLFiles());
this.plugin = plugin;
}
@@ -47,7 +50,7 @@ public class ConfigVoteSites extends YMLFile {
setEnabled(siteName, true);
setServiceSite(siteName, org);
setVoteURL(siteName, "VoteURL");
setVoteDelay(siteName, 24);
setVoteDelay(siteName, "24h");
set(siteName, "DisplayItem.Material", "STONE");
set(siteName, "DisplayItem.Amount", 1);
set(siteName, "Rewards.Messages.Player", "&aThanks for voting on %ServiceSite%!");
@@ -172,24 +175,27 @@ public class ConfigVoteSites extends YMLFile {
return getData(siteName).getString("ServiceSite");
}
/**
* Gets the vote delay.
*
* @param siteName the site name
* @return the vote delay
*/
public double getVoteDelay(String siteName) {
return getData(siteName).getDouble("VoteDelay", 24);
public ParsedDuration getVoteDelay(String site) {
ConfigurationSection sec = getData(site);
// NEW FORMAT (string)
if (sec.isString("VoteDelay")) {
return ParsedDuration.parse(sec.getString("VoteDelay"), TimeUnit.HOURS);
}
// LEGACY FORMAT (numbers)
double hours = sec.getDouble("VoteDelay", 0);
double minutes = sec.getDouble("VoteDelayMin", 0);
long millis = (long) (hours * 60 * 60 * 1000) + (long) (minutes * 60 * 1000);
return ParsedDuration.ofMillis(millis);
}
public int getVoteDelayDailyHour(String siteName) {
return getData(siteName).getInt("VoteDelayDailyHour", 0);
}
public double getVoteDelayMin(String siteName) {
return getData(siteName).getDouble("VoteDelayMin", 0);
}
/**
* Gets the vote site enabled.
*
@@ -447,14 +453,10 @@ public class ConfigVoteSites extends YMLFile {
* @param siteName the site name
* @param voteDelay the vote delay
*/
public void setVoteDelay(String siteName, double voteDelay) {
public void setVoteDelay(String siteName, String voteDelay) {
set(siteName, "VoteDelay", voteDelay);
}
public void setVoteDelayDaily(String siteName, boolean value) {
set(siteName, "VoteDelayDaily", value);
}
/**
* Sets the vote URL.
*
@@ -483,4 +485,12 @@ public class ConfigVoteSites extends YMLFile {
return pass;
}
public void setVoteDelayDailyHour(String siteName, int intValue) {
set(siteName, "VoteDelayDailyHour", intValue);
}
public void setVoteDelayDaily(String siteName, boolean value) {
set(siteName, "VoteDelayDaily", value);
}
}
@@ -360,6 +360,7 @@ public class GUI extends YMLFile {
public GUI(VotingPluginMain plugin) {
super(plugin, new File(plugin.getDataFolder(), "GUI.yml"));
setIgnoreCase(plugin.getConfigFile().isCaseInsensitiveYMLFiles());
this.plugin = plugin;
}
@@ -70,6 +70,7 @@ public class ShopFile extends YMLFile {
public ShopFile(VotingPluginMain plugin) {
super(plugin, new File(plugin.getDataFolder(), "Shop.yml"));
setIgnoreCase(plugin.getConfigFile().isCaseInsensitiveYMLFiles());
this.plugin = plugin;
}
@@ -3,7 +3,6 @@ package com.bencodez.votingplugin.config;
import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.bencodez.simpleapi.file.YMLFile;
@@ -16,18 +15,11 @@ import com.bencodez.simpleapi.file.annotation.ConfigDataListInt;
import com.bencodez.simpleapi.file.annotation.ConfigDataListString;
import com.bencodez.simpleapi.file.annotation.ConfigDataString;
import com.bencodez.votingplugin.VotingPluginMain;
import com.bencodez.votingplugin.topvoter.TopVoter;
import lombok.Getter;
public class SpecialRewardsConfig extends YMLFile {
@Getter
private String allSitesRewardPath = "AllSites";
@Getter
private String almostAllSitesRewardPath = "AlmostAllSites";
@Getter
private String anySiteRewardsPath = "AnySiteRewards";
@@ -43,16 +35,6 @@ public class SpecialRewardsConfig extends YMLFile {
@Getter
private boolean enableWeeklyAwards = false;
@ConfigDataBoolean(path = "OnlyOneCumulative")
@Getter
private boolean onlyOneCumulative = false;
@Getter
private String firstVoteRewardsPath = "FirstVote";
@Getter
private String firstVoteTodayRewardsPath = "FirstVoteToday";
/** The plugin. */
private VotingPluginMain plugin;
@@ -103,22 +85,10 @@ public class SpecialRewardsConfig extends YMLFile {
@Getter
private double voteStreakRequirementWeek = 50;
@ConfigDataKeys(path = "Cumulative")
@Getter
private Set<String> cumulativeVotes = new HashSet<>();
@ConfigDataKeys(path = "DailyAwards")
@Getter
private Set<String> dailyPossibleRewardPlaces = new HashSet<>();
@ConfigDataKeys(path = "MileStones")
@Getter
private Set<String> milestoneVotes = new HashSet<>();
@ConfigDataBoolean(path = "ResetMilestonesMonthly")
@Getter
private boolean resetMilestonesMonthly = false;
@ConfigDataString(path = "VoteParty.Broadcast")
@Getter
private String votePartyBroadcast = "";
@@ -185,80 +155,14 @@ public class SpecialRewardsConfig extends YMLFile {
public SpecialRewardsConfig(VotingPluginMain plugin) {
super(plugin, new File(plugin.getDataFolder(), "SpecialRewards.yml"));
setIgnoreCase(plugin.getConfigFile().isCaseInsensitiveYMLFiles());
this.plugin = plugin;
}
public List<Integer> getCumulativeBlackList(int cumulative) {
return getData().getIntegerList("Cumulative." + cumulative + ".BlackList");
}
public boolean getCumulativeRecurring(int cumulative) {
return getData().getBoolean("Cumulative." + cumulative + ".Recurring", true);
}
public boolean getCumulativeRewardEnabled(int cumulative) {
return getData().getBoolean("Cumulative." + cumulative + ".Enabled");
}
public String getCumulativeRewardsPath(int cumulative) {
return "Cumulative." + cumulative + ".Rewards";
}
@Deprecated
public boolean getCumulativeVotesInSameDay(int cumulative) {
return getData().getBoolean("Cumulative." + cumulative + ".VotesInSameDay");
}
@Deprecated
public boolean getCumulativeVotesInSameMonth(int cumulative) {
return getData().getBoolean("Cumulative." + cumulative + ".VotesInSameMonth");
}
@Deprecated
public boolean getCumulativeVotesInSameWeek(int cumulative) {
return getData().getBoolean("Cumulative." + cumulative + ".VotesInSameWeek");
}
public String getCumulativeVotesTotal(int cumulative) {
String str = getData().getString("Cumulative." + cumulative + ".TotalToUse", "");
if (str.isEmpty()) {
if (getCumulativeVotesInSameMonth(cumulative)) {
return TopVoter.Monthly.toString();
}
if (getCumulativeVotesInSameWeek(cumulative)) {
return TopVoter.Weekly.toString();
}
if (getCumulativeVotesInSameDay(cumulative)) {
return TopVoter.Daily.toString();
}
}
return str;
}
public String getDailyAwardRewardsPath(String path) {
return "DailyAwards." + path + ".Rewards";
}
/**
* Gets the milestone reward enabled.
*
* @param milestones the milestones
* @return the milestone reward enabled
*/
public boolean getMilestoneRewardEnabled(int milestones) {
return getData().getBoolean("MileStones." + milestones + ".Enabled");
}
/**
* Gets the milestone rewards.
*
* @param milestones the milestones
* @return the milestone rewards
*/
public String getMilestoneRewardsPath(int milestones) {
return "MileStones." + milestones + ".Rewards";
}
public String getMonthlyAwardRewardsPath(String path) {
return "MonthlyAwards." + path + ".Rewards";
}
@@ -318,27 +222,15 @@ public class SpecialRewardsConfig extends YMLFile {
plugin.saveResource("SpecialRewards.yml", true);
}
public void removeCumulative(String votes) {
getData().set("Cumulative." + votes, null);
public void removeVoteMilestone(String votes) {
getData().set("VoteMilestones." + votes, null);
saveData();
}
public void removeMilestone(String votes) {
getData().set("MileStones." + votes, null);
saveData();
}
public void setCumulative(int intValue) {
getData().set("Cumulative." + intValue + ".Enabled", true);
getData().set("Cumulative." + intValue + ".TotalToUse", "AllTime");
getData().set("Cumulative." + intValue + ".Rewards.Messages.Player",
"&aYou got %cumulative% cumulative votes!");
saveData();
}
public void setMilestone(int intValue) {
getData().set("MileStones." + intValue + ".Enabled", true);
getData().set("MileStones." + intValue + ".Rewards.Messages.Player", "&aYou got %milestone% milestone votes!");
public void setVoteMilestone(int intValue) {
getData().set("VoteMilestones." + intValue + ".Enabled", true);
getData().set("VoteMilestones." + intValue + ".Rewards.Messages.Player",
"&aYou got %milestone% milestone votes!");
saveData();
}
}
@@ -2,11 +2,13 @@ package com.bencodez.votingplugin.cooldown;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Executors;
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;
@@ -15,120 +17,190 @@ 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.simpleapi.sql.Column;
import com.bencodez.votingplugin.VotingPluginMain;
import com.bencodez.votingplugin.events.PlayerVoteCoolDownEndEvent;
import com.bencodez.votingplugin.events.PlayerVoteSiteCoolDownEndEvent;
import com.bencodez.votingplugin.objects.VoteSite;
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 VotingPluginMain plugin;
private final VotingPluginMain plugin;
private HashMap<UUID, ScheduledFuture<?>> perSiteTasks = new HashMap<>();
private HashMap<UUID, ScheduledFuture<?>> allSiteTasks = new HashMap<>();
private final Map<UUID, ScheduledFuture<?>> perSiteTasks = new ConcurrentHashMap<>();
private final Map<UUID, ScheduledFuture<?>> allSiteTasks = new ConcurrentHashMap<>();
@Getter
private ScheduledExecutorService timer;
private final ScheduledExecutorService timer;
private boolean cooldownCheckEnabled = false;
private volatile boolean cooldownCheckEnabled = false;
public CoolDownCheck(VotingPluginMain plugin) {
this.plugin = plugin;
this.timer = Executors.newScheduledThreadPool(1);
// 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.dontCache();
user.userDataFetechMode(UserDataFetchMode.NO_CACHE);
}
check(user);
}
public synchronized void check(VotingPluginUser user) {
boolean coolDownCheck = user.getCoolDownCheck();
if (coolDownCheck) {
if (user.canVoteAll()) {
user.setCoolDownCheck(false);
PlayerVoteCoolDownEndEvent event = new PlayerVoteCoolDownEndEvent(user);
plugin.getServer().getPluginManager().callEvent(event);
allSiteTasks.remove(user.getJavaUUID());
} else {
schedulePerSite(user, false);
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 void checkAllVoteSite(VoteSite site) {
plugin.getLogger().info("Checking vote cooldown rewards for all players: " + site.getKey());
HashMap<UUID, ArrayList<Column>> cols = plugin.getUserManager().getAllKeys();
for (Entry<UUID, ArrayList<Column>> playerData : cols.entrySet()) {
String uuid = playerData.getKey().toString();
if (plugin != null && plugin.isEnabled()) {
if (uuid != null && !uuid.isEmpty()) {
VotingPluginUser user = plugin.getVotingPluginUserManager()
.getVotingPluginUser(UUID.fromString(uuid), false);
user.dontCache();
user.updateTempCacheWithColumns(playerData.getValue());
cols.put(playerData.getKey(), null);
checkPerSite(user);
user.clearTempCache();
user = null;
}
}
public synchronized void check(VotingPluginUser user) {
if (!user.getCoolDownCheck()) {
return;
}
cols.clear();
cols = null;
plugin.getLogger().info("Finished checking vote cooldown rewards for all players: " + site.getKey());
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() {
if (!plugin.getConfigFile().isDisableCoolDownCheck() && plugin.getRewardHandler()
.hasRewards(plugin.getSpecialRewardsConfig().getData(), "VoteCoolDownEndedReward")) {
cooldownCheckEnabled = true;
} else {
cooldownCheckEnabled = false;
}
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.dontCache();
user.userDataFetechMode(UserDataFetchMode.NO_CACHE);
}
checkPerSite(user);
}
public synchronized void checkPerSite(VotingPluginUser user) {
if (plugin.getConfigFile().isPerSiteCoolDownEvents()) {
HashMap<String, Boolean> coolDownChecks = user.getCoolDownCheckSiteList();
boolean changed = false;
for (VoteSite site : plugin.getVoteSitesEnabled()) {
if (!coolDownChecks.containsKey(site.getKey()) || !coolDownChecks.get(site.getKey()).booleanValue()) {
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);
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() {
@@ -136,27 +208,17 @@ public class CoolDownCheck implements Listener {
@Override
public void onFinish() {
checkAllVoteSites();
}
@Override
public void onStart() {
}
@Override
public void onStartUp(AdvancedCoreUser advancedcoreUser) {
VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(advancedcoreUser);
user.dontCache();
if (cooldownCheckEnabled) {
check(user);
}
if (plugin.getConfigFile().isPerSiteCoolDownEvents()) {
checkPerSite(user);
}
}
});
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
@@ -168,61 +230,77 @@ public class CoolDownCheck implements Listener {
@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)));
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) {
final UUID uuid = UUID.fromString(user.getUUID());
long time = user.getNextTimeAllSitesAvailable();
if (allSiteTasks.containsKey(uuid)) {
allSiteTasks.get(uuid).cancel(false);
allSiteTasks.remove(uuid);
}
if (time > 0) {
user.setCoolDownCheck(true);
ScheduledFuture<?> scheduledFuture = timer.schedule(new Runnable() {
@Override
public void run() {
if (plugin != null && plugin.isEnabled()) {
check(uuid);
}
}
}, time + 2, TimeUnit.SECONDS);
allSiteTasks.put(uuid, scheduledFuture);
} else {
plugin.extraDebug(user.getUUID() + "/" + user.getPlayerName() + " not scheduling cooldown check");
}
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 (user.getSitesVotedOn() > 0 || force) {
final UUID uuid = UUID.fromString(user.getUUID());
long time = user.getNextTimeFirstSiteAvailable();
if (perSiteTasks.containsKey(uuid)) {
perSiteTasks.get(uuid).cancel(false);
perSiteTasks.remove(uuid);
}
if (time > 0) {
plugin.devDebug("PerSiteCoolDownEvent schedule time: " + time + " seconds");
ScheduledFuture<?> scheduledFuture = timer.schedule(new Runnable() {
@Override
public void run() {
if (plugin != null && plugin.isEnabled()) {
checkPerSite(uuid);
}
}
}, time + 2, TimeUnit.SECONDS);
perSiteTasks.put(uuid, scheduledFuture);
} else {
plugin.extraDebug(user.getUUID() + "/" + user.getPlayerName() + " not scheduling cooldown check");
}
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) {
@@ -236,4 +314,55 @@ public class CoolDownCheck implements Listener {
}
}
/**
* 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)"));
}
}
@@ -5,6 +5,7 @@ import java.time.Instant;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.bukkit.configuration.ConfigurationSection;
@@ -12,6 +13,8 @@ import org.bukkit.configuration.ConfigurationSection;
import com.bencodez.advancedcore.api.messages.PlaceholderUtils;
import com.bencodez.advancedcore.api.rewards.Reward;
import com.bencodez.advancedcore.api.rewards.injected.RewardInjectConfigurationSection;
import com.bencodez.advancedcore.api.user.AdvancedCoreUser;
import com.bencodez.advancedcore.api.user.UserStartup;
import com.bencodez.votingplugin.VotingPluginMain;
import com.bencodez.votingplugin.topvoter.TopVoter;
import com.bencodez.votingplugin.topvoter.TopVoterPlayer;
@@ -65,6 +68,37 @@ public class DiscordHandler {
return null;
}
});
plugin.addUserStartup(new UserStartup() {
@Override
public void onStartUp(AdvancedCoreUser user) {
}
@Override
public void onStart() {
}
@Override
public void onPostFinish() {
plugin.getTimer().scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
updateDiscordLeaderboard();
}
}, 130, 60 * 60, TimeUnit.SECONDS); // 10-minute interval
}
@Override
public void onFinish() {
}
});
}
/** Fired once JDA is fully initialized */
@@ -93,45 +127,64 @@ public class DiscordHandler {
}
public void updateTopVoterMessageId(TopVoter top) {
plugin.extraDebug("Updating Discord Top Voter message for: " + top);
long channelId = plugin.getConfigFile().getDiscordSRVTopVoterChannel(top);
LinkedHashMap<TopVoterPlayer, Integer> topVoters = plugin.getTopVoter(top);
String title = plugin.getConfigFile().getDiscordSRVTopVoterTitle(top);
String rankDisplay = plugin.getConfigFile().getDiscordSRVTopVoterRankDisplay(top);
boolean newMessage = plugin.getConfigFile().isDiscordSRVTopVoterNewMessageOnUpdate(top);
EmbedBuilder eb = new EmbedBuilder().setTitle(title).setColor(Color.CYAN).setTimestamp(Instant.now());
int rank = 1;
for (Entry<TopVoterPlayer, Integer> entry : topVoters.entrySet()) {
if (rank > 10)
break;
String line = rankDisplay.replace("%rank%", String.valueOf(rank))
.replace("%player%", entry.getKey().getPlayerName())
.replace("%votes%", String.valueOf(entry.getValue()));
eb.addField("", line, false);
rank++;
if (topVoters == null || topVoters.isEmpty()) {
eb.setDescription("No votes recorded yet.");
} else {
StringBuilder sb = new StringBuilder();
sb.append("```");
int rank = 1;
for (Entry<TopVoterPlayer, Integer> entry : topVoters.entrySet()) {
if (rank > 10)
break;
String prefix = rank <= 3 ? new String[] { "🥇", "🥈", "🥉" }[rank - 1] : rank + ".";
String player = entry.getKey().getPlayerName();
int votes = entry.getValue();
// simple alignment: trim long names, pad to 16 chars
if (player.length() > 16)
player = player.substring(0, 16);
sb.append(String.format("%-3s %-16s %4d votes%n", prefix, player, votes));
rank++;
}
sb.append("```");
eb.setDescription(sb.toString()).setFooter("Top 10 • Updated");
}
TextChannel channel = DiscordUtil.getJda().getTextChannelById(channelId);
if (channel == null) {
plugin.getLogger().warning("Discord channel ID " + channelId + " not found: " + top.toString());
plugin.getLogger().warning("Discord channel ID " + channelId + " not found: " + top);
return;
}
if (topVoterMessageIds.get(top) <= 0) {
long existingId = topVoterMessageIds.getOrDefault(top, 0L);
if (existingId <= 0 || newMessage) {
channel.sendMessageEmbeds(eb.build()).queue(msg -> {
long newId = msg.getIdLong();
topVoterMessageIds.put(top, newId);
plugin.getServerData().setTopVoterMessageId(top, newId);
plugin.getLogger().info("Posted new Top Voters " + top.toString() + " (ID: " + newId + ")");
}, err -> plugin.getLogger()
.warning("Error sending Top Voters " + top.toString() + ": " + err.getMessage()));
if (!newMessage)
plugin.getServerData().setTopVoterMessageId(top, newId);
plugin.debug("Posted new Top Voters " + top + " (ID: " + newId + ")");
}, err -> plugin.getLogger().warning("Error sending Top Voters " + top + ": " + err.getMessage()));
} else {
channel.editMessageEmbedsById(topVoterMessageIds.get(top), eb.build())
.queue(m -> plugin.getLogger()
.info("Edited Top Voters " + top.toString() + " (ID: " + topVoterMessageIds.get(top) + ")"),
err -> plugin.getLogger()
.warning("Error editing Top Voters" + top.toString() + ": " + err.getMessage()));
channel.editMessageEmbedsById(existingId, eb.build()).queue(
m -> plugin.debug("Edited Top Voters " + top + " (ID: " + existingId + ")"),
err -> plugin.getLogger().warning("Error editing Top Voters " + top + ": " + err.getMessage()));
}
}
@@ -1,11 +1,13 @@
package com.bencodez.votingplugin.events;
import java.util.UUID;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import com.bencodez.votingplugin.objects.VoteSite;
import com.bencodez.votingplugin.proxy.BungeeMessageData;
import com.bencodez.votingplugin.proxy.VoteTotalsSnapshot;
import com.bencodez.votingplugin.user.VotingPluginUser;
import com.bencodez.votingplugin.votesites.VoteSite;
import lombok.Getter;
import lombok.Setter;
@@ -34,7 +36,7 @@ public class PlayerPostVoteEvent extends Event {
@Getter
@Setter
private BungeeMessageData bungeeTextTotals;
private VoteTotalsSnapshot bungeeTextTotals;
@Getter
@Setter
@@ -62,16 +64,36 @@ public class PlayerPostVoteEvent extends Event {
@Getter
@Setter
private VotingPluginUser votingPluginUser;
private boolean cached;
@Getter
@Setter
private String service;
@Getter
@Setter
private UUID uuid;
@Getter
@Setter
private String playerName;
@Getter
@Setter
private UUID voteUUID;
public PlayerPostVoteEvent(VoteSite voteSite, VotingPluginUser user, boolean realVote, boolean forceBungee,
long voteTime) {
long voteTime, boolean cached, String service, UUID uuid, String playerName, UUID voteUUID) {
super(true);
this.user = user;
this.voteSite = voteSite;
this.realVote = realVote;
this.forceBungee = forceBungee;
this.voteTime = voteTime;
this.cached = cached;
this.service = service;
this.uuid = uuid;
this.playerName = playerName;
this.voteUUID = voteUUID;
}
/*
@@ -1,5 +1,7 @@
package com.bencodez.votingplugin.events;
import java.util.UUID;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
@@ -38,10 +40,15 @@ public class PlayerSpecialRewardEvent extends Event {
@Setter
private VotingPluginUser user;
public PlayerSpecialRewardEvent(VotingPluginUser user, SpecialRewardType type) {
@Getter
@Setter
private UUID voteUUID;
public PlayerSpecialRewardEvent(VotingPluginUser user, SpecialRewardType type, UUID voteUUID) {
super(true);
this.type = type;
this.user = user;
this.voteUUID = voteUUID;
}
/*
@@ -3,9 +3,9 @@ package com.bencodez.votingplugin.events;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import com.bencodez.votingplugin.objects.VoteSite;
import com.bencodez.votingplugin.proxy.BungeeMessageData;
import com.bencodez.votingplugin.proxy.VoteTotalsSnapshot;
import com.bencodez.votingplugin.user.VotingPluginUser;
import com.bencodez.votingplugin.votesites.VoteSite;
import lombok.Getter;
import lombok.Setter;
@@ -38,7 +38,7 @@ public class PlayerVoteEvent extends Event {
@Getter
@Setter
private BungeeMessageData bungeeTextTotals;
private VoteTotalsSnapshot bungeeTextTotals;
@Getter
@Setter
@@ -3,8 +3,8 @@ package com.bencodez.votingplugin.events;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import com.bencodez.votingplugin.objects.VoteSite;
import com.bencodez.votingplugin.user.VotingPluginUser;
import com.bencodez.votingplugin.votesites.VoteSite;
import lombok.Getter;
import lombok.Setter;
@@ -3,7 +3,13 @@ package com.bencodez.votingplugin.events;
import lombok.Getter;
public enum SpecialRewardType {
ALLSITE, CUMMULATIVE, FIRSTVOTE, FIRSTVOTETODAY, MILESTONE, VOTESTREAK, ALMOSTALLSITES;
@Deprecated
ALLSITE, @Deprecated
CUMMULATIVE, @Deprecated
FIRSTVOTE, @Deprecated
FIRSTVOTETODAY, @Deprecated
MILESTONE, VOTESTREAK, @Deprecated
ALMOSTALLSITES, TOPVOTER, VOTESTREAKS;
@Getter
private int amount = -1;
@@ -21,4 +27,26 @@ public enum SpecialRewardType {
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();
}
}
@@ -0,0 +1,109 @@
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;
}
}
@@ -0,0 +1,51 @@
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;
}
}
@@ -6,18 +6,20 @@ 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;
// TODO: Auto-generated Javadoc
/**
* The Class PlayerJoinEvent.
*/
public class PlayerJoinEvent implements Listener {
/** The plugin. */
private VotingPluginMain plugin;
private final VotingPluginMain plugin;
/**
* Instantiates a new player join event.
@@ -28,21 +30,48 @@ public class PlayerJoinEvent implements Listener {
this.plugin = plugin;
}
private void login(Player player) {
VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(player);
if (player.isOp() && plugin.isYmlError()) {
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");
}
boolean data = user.getData().hasData();
// run remind
user.loginMessage();
if (data) {
if (hasData) {
// give offline vote (if they voted offline)
user.offVote();
} else {
plugin.debug("No data detected for " + player.getUniqueId().toString() + "/" + player.getName());
plugin.debug("No data detected for " + user.getUUID() + "/" + user.getPlayerName());
}
user.loginRewards();
@@ -50,42 +79,44 @@ public class PlayerJoinEvent implements Listener {
plugin.getPlaceholders().onUpdate(user, true);
if (plugin.getBungeeSettings().isUseBungeecoord()) {
plugin.getBungeeHandler().getGlobalMessageHandler().sendMessage("Login", user.getPlayerName(),
user.getUUID(), plugin.getBungeeSettings().getServer());
plugin.getBungeeHandler().getGlobalMessageHandler().sendMessage(VotingPluginWire.login(user.getPlayerName(),
user.getUUID(), plugin.getBungeeSettings().getServer()));
}
}
/**
* On player login.
*
* @param event the event
*/
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPlayerLogin(AdvancedCoreLoginEvent event) {
if (event.getPlayer() == null || !plugin.isMySQLOkay()) {
return;
}
login(event.getPlayer());
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPlayerQuit(PlayerQuitEvent event) {
if (plugin != null && plugin.isEnabled()) {
final Player player = event.getPlayer();
plugin.getLoginTimer().execute(new Runnable() {
@Override
public void run() {
VotingPluginMain.plugin.getAdvancedTab().remove(player.getUniqueId());
VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(player);
user.dontCache();
user.logoutRewards();
plugin.getPlaceholders().onLogout(user);
}
});
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);
}
});
}
}
}
@@ -3,7 +3,8 @@ package com.bencodez.votingplugin.listeners;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Map.Entry;
import java.util.HashMap;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
@@ -17,9 +18,9 @@ import com.bencodez.simpleapi.array.ArrayUtils;
import com.bencodez.votingplugin.VotingPluginMain;
import com.bencodez.votingplugin.events.PlayerPostVoteEvent;
import com.bencodez.votingplugin.events.PlayerVoteEvent;
import com.bencodez.votingplugin.objects.VoteSite;
import com.bencodez.votingplugin.topvoter.TopVoter;
import com.bencodez.votingplugin.user.VotingPluginUser;
import com.bencodez.votingplugin.votesites.VoteSite;
// TODO: Auto-generated Javadoc
/**
@@ -97,7 +98,8 @@ public class PlayerVoteListener implements Listener {
VoteSite voteSite = event.getVoteSite();
if (voteSite == null) {
voteSite = plugin.getVoteSite(plugin.getVoteSiteName(true, event.getServiceSite()), true);
voteSite = plugin.getVoteSiteManager()
.getVoteSite(plugin.getVoteSiteManager().getVoteSiteName(true, event.getServiceSite()), true);
}
// check valid service sites
@@ -106,7 +108,7 @@ public class PlayerVoteListener implements Listener {
plugin.getLogger().warning("No voting site with the service site: '" + event.getServiceSite() + "'");
ArrayList<String> services = new ArrayList<>();
for (VoteSite site : plugin.getVoteSites()) {
for (VoteSite site : plugin.getVoteSiteManager().getVoteSites()) {
services.add(site.getServiceSite());
}
plugin.getLogger().warning("Currently set service sites: " + ArrayUtils.makeStringList(services));
@@ -146,6 +148,14 @@ public class PlayerVoteListener implements Listener {
}
}
UUID voteUUID = UUID.randomUUID();
if (event.isBungee() && event.getBungeeTextTotals() != null) {
voteUUID = event.getBungeeTextTotals().getVoteUUID();
if (voteUUID == null) {
voteUUID = UUID.randomUUID();
}
}
final String uuid = user.getUUID();
// reupdate cache
@@ -156,20 +166,21 @@ public class PlayerVoteListener implements Listener {
// vote party
plugin.getVoteParty().vote(user, event.isRealVote(), event.isForceBungee());
if (event.isBroadcast() && !plugin.getBungeeSettings().isDisableBroadcast()) {
// broadcast vote if enabled in config
if (plugin.getConfigFile().isBroadcastVotesEnabled()
&& (plugin.getBungeeSettings().isBungeeBroadcast() || !event.isBungee())) {
if (!plugin.getConfigFile().isFormatBroadcastWhenOnline() || user.isOnline()) {
voteSite.broadcastVote(user);
if (event.isBroadcast()) {
if (plugin.getBroadcastHandler() != null) {
boolean online = user.isOnline();
if (event.isBungee()) {
online = event.isWasOnline();
}
if (!user.isVanished()) {
plugin.getBroadcastHandler().broadcastVote(user.getJavaUUID(), playerName,
voteSite.getDisplayName(), online);
} else {
plugin.debug("Not broadcasting vote for vanished user: " + user.getPlayerName());
}
}
}
if (plugin.getBroadcastHandler() != null) {
plugin.getBroadcastHandler().onVote(playerName);
}
long voteTime = 0;
// update last vote time
if (event.getTime() != 0) {
@@ -180,24 +191,7 @@ public class PlayerVoteListener implements Listener {
voteTime = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
}
// try logging to file
if (plugin.getConfigFile().isLogVotesToFile()) {
try {
plugin.logVote(LocalDateTime.now().atZone(ZoneId.systemDefault()).toLocalDateTime(), playerName,
voteSite.getKey());
} catch (Exception e) {
e.printStackTrace();
}
}
// check first vote rewards
plugin.getSpecialRewards().checkFirstVote(user, event.isForceBungee());
plugin.getSpecialRewards().checkFirstVoteToday(user, event.isForceBungee());
if (user.isReminded() && plugin.getConfigFile().isVoteRemindingRemindOnlyOnce()) {
user.setReminded(false);
}
boolean cached = false;
// check if player has voted on all sites in one day
if (((user.isOnline() || voteSite.isGiveOffline()) && plugin.getOptions().isProcessRewards())
|| event.isBungee()) {
@@ -205,7 +199,7 @@ public class PlayerVoteListener implements Listener {
if (event.isBungee()) {
online = event.isWasOnline();
}
user.playerVote(voteSite, online, false, event.isForceBungee());
user.playerVote(voteSite, online, event.isForceBungee());
if (event.getVoteNumber() == 1) {
user.sendVoteEffects(online);
}
@@ -216,6 +210,7 @@ public class PlayerVoteListener implements Listener {
if (!plugin.getConfigFile().isOfflineVotesLimitEnabled()
|| user.getNumberOfOfflineVotes(voteSite) <= plugin.getConfigFile().getOfflineVotesLimitAmount()) {
user.addOfflineVote(voteSite.getKey());
cached = true;
plugin.debug(
"Offline vote set for " + playerName + " (" + user.getUUID() + ") on " + voteSite.getKey());
} else {
@@ -246,25 +241,36 @@ public class PlayerVoteListener implements Listener {
LocalDateTime cTime = plugin.getTimeChecker().getTime();
int days = cTime.getDayOfMonth();
plugin.extraDebug("Current day of month: " + days + " Current total: " + value);
if (value >= days * plugin.getVoteSitesEnabled().size()) {
if (value >= days * plugin.getVoteSiteManager().getVoteSitesEnabled().size()) {
plugin.debug("Detected higher month total, changing. Current Total: " + value + " Days: " + days
+ " New Total: " + days * plugin.getVoteSitesEnabled().size());
user.setTotal(TopVoter.Monthly, days * plugin.getVoteSitesEnabled().size());
+ " New Total: " + days * plugin.getVoteSiteManager().getVoteSitesEnabled().size());
user.setTotal(TopVoter.Monthly, days * plugin.getVoteSiteManager().getVoteSitesEnabled().size());
}
}
plugin.getVoteMilestonesManager().handleVote(user, event.getBungeeTextTotals(), event.isForceBungee(), voteUUID,
new HashMap<String, String>());
// other rewards
plugin.getSpecialRewards().checkAllSites(user, event.isForceBungee());
plugin.getSpecialRewards().checkAlmostAllSites(user, event.isForceBungee());
plugin.getSpecialRewards().checkCumualativeVotes(user, event.getBungeeTextTotals(), event.isForceBungee());
plugin.getSpecialRewards().checkMilestone(user, event.getBungeeTextTotals(), event.isForceBungee());
// plugin.getSpecialRewards().checkAllSites(voteUUID, user,
// event.isForceBungee());
// plugin.getSpecialRewards().checkAlmostAllSites(voteUUID, user,
// event.isForceBungee());
// plugin.getSpecialRewards().checkCumualativeVotes(voteUUID, user,
// event.getBungeeTextTotals(),
// event.isForceBungee());
// plugin.getSpecialRewards().checkMilestone(voteUUID, user,
// event.getBungeeTextTotals(), event.isForceBungee());
plugin.getCoolDownCheck().vote(user, voteSite);
plugin.getVoteStreakHandler().processVote(user, voteTime, voteUUID);
PlayerPostVoteEvent postVoteEvent = new PlayerPostVoteEvent(voteSite, user, event.isRealVote(),
event.isForceBungee(), voteTime);
event.isForceBungee(), voteTime, cached, voteSite.getServiceSite(), user.getJavaUUID(), playerName,
voteUUID);
plugin.getServer().getPluginManager().callEvent(postVoteEvent);
if (user.isOnline()) {
if (user.isOnline() || plugin.getConfigFile().getPlaceholderCacheLevel().isCacheAlways()) {
plugin.getPlaceholders().onUpdate(user, true);
}
@@ -86,7 +86,7 @@ public class VotiferEvent implements Listener {
}
}
String voteSiteNameStr = plugin.getVoteSiteName(false, voteSite, matchSite);
String voteSiteNameStr = plugin.getVoteSiteManager().getVoteSiteName(false, voteSite, matchSite);
ArrayList<String> sites = plugin.getConfigVoteSites().getVoteSitesNames(false);
boolean createSite = false;
@@ -118,9 +118,9 @@ public class VotiferEvent implements Listener {
return;
}
String voteSiteName = plugin.getVoteSiteName(true, serviceSite, matchSite);
String voteSiteName = plugin.getVoteSiteManager().getVoteSiteName(true, serviceSite, matchSite);
PlayerVoteEvent voteEvent = new PlayerVoteEvent(plugin.getVoteSite(voteSiteName, true),
PlayerVoteEvent voteEvent = new PlayerVoteEvent(plugin.getVoteSiteManager().getVoteSite(voteSiteName, true),
voteUsername, voteSite, true);
plugin.getServer().getPluginManager().callEvent(voteEvent);
@@ -42,11 +42,7 @@ public class MVdWPlaceholders {
public String onPlaceholderReplace(PlaceholderReplaceEvent event) {
VotingPluginUser user = plugin.getVotingPluginUserManager()
.getVotingPluginUser(event.getOfflinePlayer());
if (plugin.getConfigFile().isUsePrimaryAccountForPlaceholders()
&& user.hasPrimaryAccount()) {
user = plugin.getVotingPluginUserManager()
.getVotingPluginUser(user.getPrimaryAccount());
}
return place.placeholderRequest(user,
event.getPlaceholder().substring("VotingPlugin_".length()));
}
@@ -25,12 +25,14 @@ import com.bencodez.advancedcore.api.placeholder.NonPlayerPlaceHolder;
import com.bencodez.advancedcore.api.placeholder.PlaceHolder;
import com.bencodez.advancedcore.api.user.AdvancedCoreUser;
import com.bencodez.advancedcore.api.user.UserDataChanged;
import com.bencodez.advancedcore.api.user.UserDataFetchMode;
import com.bencodez.simpleapi.messages.MessageAPI;
import com.bencodez.votingplugin.VotingPluginMain;
import com.bencodez.votingplugin.objects.VoteSite;
import com.bencodez.votingplugin.specialrewards.votemilestones.VoteMilestone;
import com.bencodez.votingplugin.topvoter.TopVoter;
import com.bencodez.votingplugin.topvoter.TopVoterPlayer;
import com.bencodez.votingplugin.user.VotingPluginUser;
import com.bencodez.votingplugin.votesites.VoteSite;
import lombok.Getter;
@@ -96,7 +98,8 @@ public class PlaceHolders {
boolean useCache = true;
String identifier = identifier1.toLowerCase();
boolean custom = false;
if (identifier.endsWith("_process")) {
if (identifier.endsWith("_process")
|| (!Bukkit.isPrimaryThread() && plugin.getConfigFile().isAlwaysProcessAsyncPlaceholders())) {
forceProcess = true;
identifier = identifier.replaceAll("_process", "");
}
@@ -174,11 +177,6 @@ public class PlaceHolders {
}
}
}
if (plugin.getConfigFile().isUsePrimaryAccountForPlaceholders()) {
if (user.hasPrimaryAccount()) {
user = plugin.getVotingPluginUserManager().getVotingPluginUser(user.getPrimaryAccount());
}
}
UUID uuid = user.getJavaUUID();
@@ -305,80 +303,19 @@ public class PlaceHolders {
}
}.withDescription("Returns true/false if user has broadcast disabled").updateDataKey("DisableBroadcast"));
placeholders.add(new PlaceHolder<VotingPluginUser>("DisableReminders") {
@Override
public String placeholderRequest(VotingPluginUser user, String identifier) {
if (plugin.getVoteReminding().getRemindersEnabled().containsKey(user.getJavaUUID())) {
if (!plugin.getVoteReminding().getRemindersEnabled().get(user.getJavaUUID()).booleanValue()) {
return "True";
}
}
return "False";
}
}.withDescription("Returns true/false if user has reminders disabled"));
placeholders.add(new PlaceHolder<VotingPluginUser>("MilestoneCount") {
@Override
public String placeholderRequest(VotingPluginUser user, String identifier) {
return "" + user.getMilestoneCount();
}
}.withDescription("User milestonecount").updateDataKey("MilestoneCount"));
Set<String> mVotes = plugin.getSpecialRewardsConfig().getMilestoneVotes();
for (String vote : mVotes) {
if (MessageAPI.isInt(vote)) {
final int num = Integer.parseInt(vote);
if (plugin.getSpecialRewardsConfig().getMilestoneRewardEnabled(num)) {
placeholders.add(new PlaceHolder<VotingPluginUser>("milestone_numberofvotesuntil_" + num) {
@Override
public String placeholderRequest(VotingPluginUser user, String identifier) {
int toGo = num - user.getMilestoneCount();
if (toGo >= 0) {
return "" + toGo;
}
return "0";
}
}.withDescription("Get number of votes until milestone").updateDataKey("MilestoneCount"));
}
}
}
placeholders.add(new PlaceHolder<VotingPluginUser>("nextmilestone_votes_required") {
@Override
public String placeholderRequest(VotingPluginUser user, String identifier) {
return "" + user.getNextAvailableMileStone();
}
}.withDescription("Get number of votes required for next available milestone").updateDataKey("MilestoneCount"));
placeholders.add(new PlaceHolder<VotingPluginUser>("nextmilestone_votes_until") {
@Override
public String placeholderRequest(VotingPluginUser user, String identifier) {
int toGo = user.getNextAvailableMileStone() - user.getMilestoneCount();
if (toGo >= 0) {
return "" + toGo;
}
return "0";
}
}.withDescription("Get number of votes until next available milestone").updateDataKey("MilestoneCount"));
placeholders.add(new PlaceHolder<VotingPluginUser>("lastmilestone_votes_since") {
@Override
public String placeholderRequest(VotingPluginUser user, String identifier) {
int toGo = user.getMilestoneCount() - user.getLastGottenMilestone();
if (toGo >= 0) {
return "" + toGo;
}
return "0";
}
}.withDescription("Get number of votes since last milestone").updateDataKey("MilestoneCount"));
/*
* placeholders.add(new PlaceHolder<VotingPluginUser>("DisableReminders") {
*
* @Override public String placeholderRequest(VotingPluginUser user, String
* identifier) { if
* (plugin.getVoteReminding().getRemindersEnabled().containsKey(user.getJavaUUID
* ())) { if
* (!plugin.getVoteReminding().getRemindersEnabled().get(user.getJavaUUID()).
* booleanValue()) {
*
* return "True"; } } return "False"; }
* }.withDescription("Returns true/false if user has reminders disabled"));
*/
for (final String identifier : plugin.getShopFile().getShopIdentifiers()) {
if (plugin.getShopFile().getShopIdentifierLimit(identifier) > 0) {
@@ -516,7 +453,7 @@ public class PlaceHolders {
}
long smallest = -1;
HashMap<Long, VoteSite> times = new HashMap<>();
for (VoteSite site : plugin.getVoteSitesEnabled()) {
for (VoteSite site : plugin.getVoteSiteManager().getVoteSitesEnabled()) {
long t = user.voteNextDurationTime(site);
if (smallest == -1) {
smallest = t;
@@ -552,7 +489,7 @@ public class PlaceHolders {
}
}.withDescription("Get total number of sites available to be voted on"));
for (final VoteSite voteSite : plugin.getVoteSitesEnabled()) {
for (final VoteSite voteSite : plugin.getVoteSiteManager().getVoteSitesEnabled()) {
placeholders.add(new CalculatingPlaceholder<VotingPluginUser>("Next_" + voteSite.getKey()) {
@Override
@@ -620,6 +557,71 @@ public class PlaceHolders {
}
}.withDescription("Return true/false if player has said points").updateDataKey("Points").useStartsWith());
// VoteMilestones placeholders (explicit per group, like
// VoteShopLimit_<identifier>)
// Base/default (no group suffix)
placeholders.add(new PlaceHolder<VotingPluginUser>("VoteMilestoneNext") {
@Override
public String placeholderRequest(VotingPluginUser user, String ident) {
Long next = plugin.getVoteMilestonesManager().getNextMilestoneValue("default", user);
return next == null ? "none" : Long.toString(next);
}
}.withDescription("Next VoteMilestone value for default group").updateDataKey("LastVotes"));
placeholders.add(new PlaceHolder<VotingPluginUser>("VoteMilestoneLast") {
@Override
public String placeholderRequest(VotingPluginUser user, String ident) {
Long last = plugin.getVoteMilestonesManager().getLastMilestoneValue("default", user);
return last == null ? "none" : Long.toString(last);
}
}.withDescription("Last achieved VoteMilestone value for default group").updateDataKey("LastVotes"));
placeholders.add(new PlaceHolder<VotingPluginUser>("VoteMilestoneVotesUntilNext") {
@Override
public String placeholderRequest(VotingPluginUser user, String ident) {
return Long.toString(plugin.getVoteMilestonesManager().getVotesUntilNextMilestone("default", user));
}
}.withDescription("Votes until next VoteMilestone for default group").updateDataKey("LastVotes"));
Set<String> groups = new HashSet<>();
groups.add("default");
for (VoteMilestone m : plugin.getVoteMilestonesManager().getConfig().getMilestones().values()) {
String g = m.getGroupKey();
if (g == null)
continue;
g = g.trim().toLowerCase(Locale.ROOT);
if (!g.isEmpty())
groups.add(g);
}
for (final String groupIdRaw : groups) {
final String groupId = (groupIdRaw == null || groupIdRaw.trim().isEmpty()) ? "default" : groupIdRaw.trim();
placeholders.add(new PlaceHolder<VotingPluginUser>("VoteMilestoneNext_" + groupId) {
@Override
public String placeholderRequest(VotingPluginUser user, String ident) {
Long next = plugin.getVoteMilestonesManager().getNextMilestoneValue(groupId, user);
return next == null ? "none" : Long.toString(next);
}
}.withDescription("Next VoteMilestone value for group " + groupId).updateDataKey("LastVotes"));
placeholders.add(new PlaceHolder<VotingPluginUser>("VoteMilestoneLast_" + groupId) {
@Override
public String placeholderRequest(VotingPluginUser user, String ident) {
Long last = plugin.getVoteMilestonesManager().getLastMilestoneValue(groupId, user);
return last == null ? "none" : Long.toString(last);
}
}.withDescription("Last achieved VoteMilestone value for group " + groupId).updateDataKey("LastVotes"));
placeholders.add(new PlaceHolder<VotingPluginUser>("VoteMilestoneVotesUntilNext_" + groupId) {
@Override
public String placeholderRequest(VotingPluginUser user, String ident) {
return Long.toString(plugin.getVoteMilestonesManager().getVotesUntilNextMilestone(groupId, user));
}
}.withDescription("Votes until next VoteMilestone for group " + groupId).updateDataKey("LastVotes"));
}
nonPlayerPlaceholders.add(new NonPlayerPlaceHolder<VotingPluginUser>("Top_AllVotes_") {
@Override
@@ -1121,7 +1123,7 @@ public class PlaceHolders {
public void onChange(AdvancedCoreUser user, String... keys) {
VotingPluginUser vpUser = plugin.getVotingPluginUserManager().getVotingPluginUser(user);
if (!vpUser.isCached()) {
vpUser.dontCache();
vpUser.userDataFetechMode(UserDataFetchMode.NO_CACHE);
}
for (PlaceHolder<VotingPluginUser> placeholder : placeholders) {
if (placeholder.isUsesCache()) {
@@ -36,4 +36,11 @@ public enum PlaceholderCacheLevel {
}
}
public boolean isCacheAlways() {
if ((this == AUTOALL) || (this == SPECIFICALL)) {
return true;
}
return false;
}
}
@@ -1,79 +0,0 @@
package com.bencodez.votingplugin.proxy;
import java.util.regex.Pattern;
import lombok.Getter;
import lombok.Setter;
public class BungeeMessageData {
// Message string
// alltimetotal//monthtotal//weeklytotal//dailytotal//points//milestonecount//votepartycurrent//votepartyrequired//datemonthtotal
@Getter
@Setter
private int allTimeTotal = 0;
@Getter
@Setter
private int dailyTotal = 0;
@Getter
@Setter
private int milestoneCount = 0;
@Getter
@Setter
private int monthTotal = 0;
@Getter
@Setter
private int dateMonthTotal = 0;
@Getter
@Setter
private int points = 0;
@Getter
@Setter
private int weeklyTotal = 0;
@Getter
@Setter
private int votePartyCurrent = 0;
@Getter
@Setter
private int votePartyRequired = 0;
public BungeeMessageData(int allTimeTotal, int monthTotal, int weeklyTotal, int dailyTotal, int points,
int milestoneCount, int votePartyCurrent, int votePartyRequired, int dateMonthTotal) {
this.allTimeTotal = allTimeTotal;
this.monthTotal = monthTotal;
this.weeklyTotal = weeklyTotal;
this.dailyTotal = dailyTotal;
this.points = points;
this.milestoneCount = milestoneCount;
this.votePartyCurrent = votePartyCurrent;
this.votePartyRequired = votePartyRequired;
this.dateMonthTotal = dateMonthTotal;
}
public BungeeMessageData(String str) {
String[] data = str.split(Pattern.quote("//"));
if (data.length >= 6) {
allTimeTotal = Integer.parseInt(data[0]);
monthTotal = Integer.parseInt(data[1]);
weeklyTotal = Integer.parseInt(data[2]);
dailyTotal = Integer.parseInt(data[3]);
points = Integer.parseInt(data[4]);
milestoneCount = Integer.parseInt(data[5]);
}
if (data.length >= 8) {
votePartyCurrent = Integer.parseInt(data[6]);
votePartyRequired = Integer.parseInt(data[7]);
}
if (data.length >= 9) {
dateMonthTotal = Integer.parseInt(data[8]);
}
}
@Override
public String toString() {
return allTimeTotal + "//" + monthTotal + "//" + weeklyTotal + "//" + dailyTotal + "//" + points + "//"
+ milestoneCount + "//" + votePartyCurrent + "//" + votePartyRequired;
}
}
@@ -1,7 +0,0 @@
package com.bencodez.votingplugin.proxy;
public class BungeeVersion {
public static int getPluginMessageVersion() {
return 1;
}
}
@@ -1,5 +1,7 @@
package com.bencodez.votingplugin.proxy;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
@@ -18,19 +20,39 @@ public class OfflineBungeeVote {
private long time;
@Getter
private String uuid;
@Getter
private UUID voteId;
public OfflineBungeeVote(String playerName, String uuid, String service, long time, boolean realVote, String text) {
public OfflineBungeeVote(UUID voteId, String playerName, String uuid, String service, long time, boolean realVote,
String text) {
this.playerName = playerName;
this.uuid = uuid;
this.service = service;
this.time = time;
this.realVote = realVote;
this.text = text;
this.voteId = voteId;
}
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;
return "VoteCache:" + playerName + "/" + uuid + "/" + service + "/" + time + "/" + realVote + "/" + text + "/"
+ voteId;
}
}
@@ -6,8 +6,6 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
@@ -18,143 +16,113 @@ 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;
import lombok.Getter;
public abstract class ProxyMysqlUserTable extends AbstractSqlTable {
public abstract class ProxyMysqlUserTable {
private List<String> columns = Collections.synchronizedList(new ArrayList<String>());
// Keep this so existing callers relying on getName() still work
private final String name;
private List<String> intColumns = new ArrayList<>();
// For int-vs-string decode in getExactQuery (same pattern as before)
private final List<String> intColumns = new ArrayList<>();
// private HashMap<String, ArrayList<Column>> table;
// Backwards-compat wrapper (your old code expects getUuids())
private final Set<String> uuidsView;
// ConcurrentMap<String, ArrayList<Column>> table = new
// ConcurrentHashMap<String, ArrayList<Column>>();
// ---- Required subclass hooks ----
@Getter
private com.bencodez.simpleapi.sql.mysql.MySQL mysql;
@Override
public String getPrimaryKeyColumn() {
return "uuid";
}
private String name;
@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(" (");
private Object object2 = new Object();
// 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();
}
private Object object3 = new Object();
// ---- Logging hooks required by AbstractSqlTable ----
private Object object4 = new Object();
@Override
public abstract void logSevere(String msg);
private Set<String> uuids = Collections.synchronizedSet(new HashSet<String>());
@Override
public abstract void logInfo(String msg);
public abstract void logSevere(String string);
@Override
public abstract void debug(Throwable t);
public abstract void logInfo(String string);
// 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);
if (config.hasTableNameSet()) {
tableName = config.getTableName();
}
name = tableName;
if (config.getTablePrefix() != null) {
name = config.getTablePrefix() + tableName;
}
if (config.getPoolName().isEmpty()) {
config.setPoolName("VotingPlugin" + "-" + tableName);
}
mysql = new com.bencodez.simpleapi.sql.mysql.MySQL(config.getMaxThreads()) {
this.name = super.getTableName();
@Override
public void debug(SQLException e) {
if (debug) {
e.printStackTrace();
}
}
// keep uuids view
this.uuidsView = super.getPrimaryKeys();
@Override
public void severe(String string) {
logSevere(string);
}
// keep old "uuid column type tweak" behavior (but now uses base helper)
alterColumnType("uuid", bestUuidType());
@Override
public void debug(String msg) {
if (debug) {
logInfo("MYSQL DEBUG: " + msg);
}
}
};
if (!mysql.connect(config)) {
}
try {
Query q = new Query(mysql, "USE `" + config.getDatabase() + "`;");
q.executeUpdateAsync();
} catch (SQLException e) {
severe("Failed to send use database query: " + config.getDatabase() + " Error: " + e.getMessage());
debug(e);
}
String sql = "CREATE TABLE IF NOT EXISTS " + getName() + " (";
sql += "uuid VARCHAR(37),";
sql += "PRIMARY KEY ( uuid )";
sql += ");";
Query query;
try {
query = new Query(mysql, sql);
query.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
loadData();
// tempoary to improve performance from old tables
// addToQue("ALTER TABLE " + getName() + " MODIFY uuid VARCHAR(37);");
alterColumnType("uuid", "VARCHAR(37)");
// load intColumns behavior if you rely on it elsewhere
// (no-op: it's only used if you add to it)
}
public void addColumn(String column, DataType dataType) {
synchronized (object3) {
String sql = "ALTER TABLE " + getName() + " ADD COLUMN `" + column + "` text" + ";";
try {
Query query = new Query(mysql, sql);
query.executeUpdate();
/**
* Use an already-connected pool (shared).
*/
public ProxyMysqlUserTable(String tableName, MySQL existingMysql, boolean debug) {
super(tableName, existingMysql, debug);
getColumns().add(column);
} catch (SQLException e) {
e.printStackTrace();
}
this.name = super.getTableName();
this.uuidsView = super.getPrimaryKeys();
}
alterColumnType("uuid", bestUuidType());
}
public void alterColumnType(String column, String newType) {
checkColumn(column, DataType.STRING);
try {
new Query(mysql, "ALTER TABLE " + getName() + " MODIFY `" + column + "` " + newType + ";")
.executeUpdateAsync();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (newType.contains("INT")) {
if (!intColumns.contains(column)) {
intColumns.add(column);
}
}
// ---- Backwards-compatible API ----
public String getName() {
return name;
}
public void checkColumn(String column, DataType dataType) {
synchronized (object4) {
if (!getColumns().contains(column)) {
if (!getColumnsQueury().contains(column)) {
addColumn(column, dataType);
}
}
}
/**
* 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() {
@@ -162,176 +130,71 @@ public abstract class ProxyMysqlUserTable {
}
public void clearCacheBasic() {
columns.clear();
columns.addAll(getColumnsQueury());
uuids.clear();
uuids.addAll(getUuidsQuery());
clearCaches();
}
public void close() {
mysql.disconnect();
public void shutdown() {
close();
}
// Keep old containsKeyQuery signature
public boolean containsKeyQuery(String index) {
String sqlStr = "SELECT uuid FROM " + getName() + ";";
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement sql = conn.prepareStatement(sqlStr)) {
ResultSet rs = sql.executeQuery();
/*
* Query query = new Query(mysql, sql); ResultSet rs = query.executeQuery();
*/
while (rs.next()) {
if (rs.getString("uuid").equals(index)) {
rs.close();
return true;
}
}
rs.close();
} catch (SQLException ex) {
ex.printStackTrace();
}
return false;
return super.containsKeyQuery(index);
}
public void copyColumnData(String columnFromName, String columnToName) {
checkColumn(columnFromName, DataType.STRING);
checkColumn(columnToName, DataType.STRING);
String sql = "UPDATE `" + getName() + "` SET `" + columnToName + "` = `" + columnFromName + "`;";
try {
Query query = new Query(mysql, sql);
query.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
public abstract void debug(SQLException e);
public List<String> getColumns() {
if (columns == null || columns.size() == 0) {
loadData();
}
return columns;
}
// ---- Required old utility methods (kept) ----
public ArrayList<String> getColumnsQueury() {
ArrayList<String> columns = new ArrayList<>();
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement sql = conn.prepareStatement("SHOW COLUMNS FROM `" + getName() + "`;")) {
ResultSet rs = sql.executeQuery();
while (rs.next()) {
String columnName = rs.getString(1);
columns.add(columnName);
}
rs.close();
} catch (Exception e) {
e.printStackTrace();
}
return columns;
}
public ArrayList<Column> getExactQuery(Column column) {
ArrayList<Column> result = new ArrayList<>();
String query = "SELECT * FROM " + getName() + " WHERE `" + column.getName() + "`='"
+ column.getValue().getString() + "';";
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement sql = conn.prepareStatement(query)) {
ResultSet rs = sql.executeQuery();
/*
* Query sql = new Query(mysql, query); sql.setParameter(1,
* column.getValue().toString()); ResultSet rs = sql.executeQuery();
*/
if (rs.next()) {
for (int i = 1; i <= rs.getMetaData().getColumnCount(); i++) {
String columnName = rs.getMetaData().getColumnLabel(i);
Column rCol = null;
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);
}
}
rs.close();
return result;
} catch (SQLException e) {
e.printStackTrace();
} catch (ArrayIndexOutOfBoundsException e) {
}
for (String col : getColumns()) {
result.add(new Column(col, DataType.STRING));
}
return result;
}
public String getName() {
return name;
}
public ArrayList<String> getNamesQuery() {
ArrayList<String> uuids = new ArrayList<>();
checkColumn("PlayerName", DataType.STRING);
ArrayList<Column> rows = getRowsNameQuery();
if (rows != null) {
for (Column c : rows) {
if (c.getValue().isString()) {
uuids.add(c.getValue().getString());
}
}
}
return uuids;
return new ArrayList<>(getColumnsQuery());
}
public ArrayList<Column> getRowsNameQuery() {
ArrayList<Column> result = new ArrayList<>();
String sqlStr = "SELECT PlayerName FROM " + getName() + ";";
checkColumn("PlayerName", DataType.STRING);
String sqlStr = "SELECT " + qi("PlayerName") + " FROM " + qi(getTableName()) + ";";
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement sql = conn.prepareStatement(sqlStr)) {
ResultSet rs = sql.executeQuery();
/*
* Query query = new Query(mysql, sql); ResultSet rs = query.executeQuery();
*/
PreparedStatement ps = conn.prepareStatement(sqlStr);
ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
Column rCol = new Column("PlayerName", new DataValueString(rs.getString("PlayerName")));
result.add(rCol);
result.add(new Column("PlayerName", new DataValueString(rs.getString(1))));
}
sql.close();
conn.close();
} catch (SQLException e) {
} 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 uuid FROM " + getName() + ";";
String sqlStr = "SELECT " + qi("uuid") + " FROM " + qi(getTableName()) + ";";
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement sql = conn.prepareStatement(sqlStr)) {
ResultSet rs = sql.executeQuery();
/*
* Query query = new Query(mysql, sql); ResultSet rs = query.executeQuery();
*/
PreparedStatement ps = conn.prepareStatement(sqlStr);
ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
Column rCol = new Column("uuid", new DataValueString(rs.getString("uuid")));
result.add(rCol);
result.add(new Column("uuid", new DataValueString(rs.getString(1))));
}
rs.close();
} catch (SQLException e) {
return null;
}
@@ -341,212 +204,281 @@ public abstract class ProxyMysqlUserTable {
public ConcurrentHashMap<UUID, String> getRowsUUIDNameQuery() {
ConcurrentHashMap<UUID, String> uuidNames = new ConcurrentHashMap<>();
String sqlStr = "SELECT UUID, PlayerName FROM " + getName() + ";";
checkColumn("PlayerName", DataType.STRING);
String sqlStr = "SELECT " + qi("uuid") + ", " + qi("PlayerName") + " FROM " + qi(getTableName()) + ";";
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement sql = conn.prepareStatement(sqlStr)) {
ResultSet rs = sql.executeQuery();
/*
* Query query = new Query(mysql, sql); ResultSet rs = query.executeQuery();
*/
PreparedStatement ps = conn.prepareStatement(sqlStr);
ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
String uuid = rs.getString("uuid");
String playerName = rs.getString("PlayerName");
if (uuid != null && !uuid.isEmpty() && !uuid.equals("null") && playerName != null) {
uuidNames.put(UUID.fromString(uuid), playerName);
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) {
}
}
}
sql.close();
conn.close();
} catch (SQLException e) {
} catch (SQLException ignored) {
}
return uuidNames;
}
public String getUUID(String playerName) {
String query = "SELECT uuid FROM " + getName() + " WHERE " + "PlayerName" + "='" + playerName + "';";
checkColumn("PlayerName", DataType.STRING);
String query = "SELECT " + qi("uuid") + " FROM " + qi(getTableName()) + " WHERE " + qi("PlayerName") + " = ?;";
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement sql = conn.prepareStatement(query)) {
ResultSet rs = sql.executeQuery();
/*
* Query sql = new Query(mysql, query); ResultSet rs = sql.executeQuery();
*/
if (rs.next()) {
String uuid = rs.getString("uuid");
if (uuid != null && !uuid.isEmpty()) {
rs.close();
return uuid;
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;
}
}
}
rs.close();
} catch (SQLException e) {
e.printStackTrace();
} catch (ArrayIndexOutOfBoundsException e) {
debug(e);
}
return null;
}
public Set<String> getUuids() {
if (uuids == null || uuids.size() == 0) {
uuids.clear();
uuids.addAll(getUuidsQuery());
return uuids;
}
return uuids;
}
public ArrayList<Column> getExactQuery(Column column) {
ArrayList<Column> result = new ArrayList<>();
public ArrayList<String> getUuidsQuery() {
ArrayList<String> uuids = new ArrayList<>();
String query = "SELECT * FROM " + qi(getTableName()) + " WHERE " + qi(column.getName()) + " = ?;";
ArrayList<Column> rows = getRowsQuery();
for (Column c : rows) {
if (c.getValue().isString()) {
uuids.add(c.getValue().getString());
}
}
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(query)) {
return uuids;
}
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) {
String query = "INSERT IGNORE " + getName() + " ";
query += "set uuid='" + index + "', ";
for (int i = 0; i < cols.size(); i++) {
Column col = cols.get(i);
if (i == cols.size() - 1) {
if (col.getValue().isString()) {
query += "`" + col.getName() + "`='" + col.getValue().getString() + "';";
} else if (col.getValue().isBoolean()) {
query += "`" + col.getName() + "`='" + col.getValue().getBoolean() + "';";
} else if (col.getValue().isInt()) {
query += "`" + col.getName() + "`='" + col.getValue().getInt() + "';";
}
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 {
if (col.getValue().isString()) {
query += "`" + col.getName() + "`='" + col.getValue().getString() + "', ";
} else if (col.getValue().isBoolean()) {
query += "`" + col.getName() + "`='" + col.getValue().getBoolean() + "', ";
} else if (col.getValue().isInt()) {
query += "`" + col.getName() + "`='" + col.getValue().getInt() + "', ";
}
ps.setString(1, v);
}
}
try {
uuids.add(index);
new Query(mysql, query).executeUpdateAsync();
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) {
e.printStackTrace();
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);
}
public void loadData() {
columns = getColumnsQueury();
// ---- 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 abstract void severe(String str);
public void insertQuery(String index, List<Column> cols) {
for (Column c : cols) {
checkColumn(c.getName(), c.getDataType());
}
public void shutdown() {
mysql.disconnect();
// 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());
}
if (getUuids().contains(index)) {
synchronized (object2) {
String query = "UPDATE " + getName() + " SET ";
// keep old behavior: if missing, insert
if (!getUuids().contains(index) && !containsKeyQuery(index)) {
insertQuery(index, cols);
return;
}
for (int i = 0; i < cols.size(); i++) {
Column col = cols.get(i);
if (i == cols.size() - 1) {
if (col.getValue().isString()) {
query += "`" + col.getName() + "`='" + col.getValue().getString() + "'";
} else if (col.getValue().isBoolean()) {
query += "`" + col.getName() + "`='" + col.getValue().getBoolean() + "'";
} else if (col.getValue().isInt()) {
query += "`" + col.getName() + "`='" + col.getValue().getInt() + "'";
}
} else {
if (col.getValue().isString()) {
query += "`" + col.getName() + "`='" + col.getValue().getString() + "', ";
} else if (col.getValue().isBoolean()) {
query += "`" + col.getName() + "`='" + col.getValue().getBoolean() + "', ";
} else if (col.getValue().isInt()) {
query += "`" + col.getName() + "`='" + col.getValue().getInt() + "', ";
}
}
}
query += " WHERE `uuid`=";
query += "'" + index + "';";
synchronized (updateLock) {
StringBuilder sb = new StringBuilder();
sb.append("UPDATE ").append(qi(getTableName())).append(" SET ");
try {
Query q = new Query(mysql, query);
q.executeUpdateAsync();
} catch (SQLException e) {
e.printStackTrace();
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(", ");
}
}
} else {
insertQuery(index, cols);
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)) {
synchronized (object2) {
String query = "UPDATE " + getName() + " SET ";
if (value.isString()) {
query += column + "='" + value.getString() + "'";
} else if (value.isBoolean()) {
query += column + "='" + value.getBoolean() + "'";
} else if (value.isInt()) {
query += column + "='" + value.getInt() + "'";
}
query += " WHERE `uuid`=";
query += "'" + index + "';";
try {
Query q = new Query(mysql, query);
q.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
} else {
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 " + getName() + " SET " + columnName + " = " + dataType.getNoValue() + ";";
try {
Query query = new Query(mysql, sql);
query.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
String sql = "UPDATE " + qi(getTableName()) + " SET " + qi(columnName) + " = " + dataType.getNoValue() + ";";
try {
new Query(mysql, sql).executeUpdate();
} catch (SQLException e) {
debug(e);
}
}
}
}
@@ -0,0 +1,178 @@
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;
}
}
}
File diff suppressed because it is too large. Load diff
@@ -22,11 +22,11 @@ public class VotingPluginProxyCommand {
switch (args[0].toLowerCase()) {
case "reload":
plugin.reloadCore(false);
return "&aReloading VotingPlugin";
return "&aReloading VotingPluginProxy (basic settings)";
case "reloadmysql":
case "reloadall":
plugin.reloadCore(true);
return "&aReloading VotingPlugin including MySQL";
return "&aReloading VotingPluginProxy (full reload incl. MySQL/GlobalData)";
case "vote":
if (args.length >= 3) {
@@ -48,7 +48,7 @@ public class VotingPluginProxyCommand {
return handleStatusCommand();
case "multiproxystatus":
plugin.getMultiProxyHandler().sendMultiProxyServerMessage("Status");
plugin.getMultiProxyHandler().sendStatus();
return "&aSent status message across multi-proxy";
case "help":
@@ -64,21 +64,9 @@ public class VotingPluginProxyCommand {
private String handleStatusCommand() {
BungeeMethod method = plugin.getMethod();
switch (method) {
case SOCKETS:
plugin.status();
return "&aChecking status via sockets. Check console for details.";
case PLUGINMESSAGING:
case MYSQL:
case REDIS:
plugin.status();
plugin.sendServerNameMessage();
return "&aChecking status with method: " + method.toString().toLowerCase();
default:
return "&cStatus command unavailable for the current connection method.";
}
plugin.status();
plugin.sendServerNameMessage();
return "&aChecking status with method: " + method.toString().toLowerCase();
}
private String handleVotePartyCommand(String[] args) {
@@ -1,19 +1,57 @@
package com.bencodez.votingplugin.proxy;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public interface VotingPluginProxyConfig {
default boolean getProxyBroadcastEnabled() {
return false;
}
/**
* Routing scope mode.
*
* Expected values: PLAYER_SERVER | ALL_SERVERS | SERVERS | ALL_EXCEPT
*/
default String getProxyBroadcastScopeMode() {
return "ALL_SERVERS";
}
/**
* Optional server list used by scope modes SERVERS and ALL_EXCEPT.
*/
default List<String> getProxyBroadcastScopeServers() {
return Collections.emptyList();
}
/**
* What to do when the voting player is offline and scope is PLAYER_SERVER.
*
* Expected values: NONE | QUEUE | FORWARD
*/
default String getProxyBroadcastOfflineMode() {
return "QUEUE";
}
/**
* Backend server list used when OfflineMode is FORWARD and Scope.Mode is
* PLAYER_SERVER.
*
* Ignored when Scope.Mode is ALL_SERVERS or ALL_EXCEPT.
*/
default List<String> getProxyBroadcastOfflineForwardServers() {
return Collections.emptyList();
}
public boolean getAllowUnJoined();
public String getBedrockPlayerPrefix();
public List<String> getBlockedServers();
public boolean getBroadcast();
public String getBungeeHost();
public boolean getBungeeManageTotals();
@@ -27,11 +65,15 @@ public interface VotingPluginProxyConfig {
public boolean getPluginMessageEncryption();
public boolean getDebug();
public boolean getVoteCacheUseMySQL();
public boolean getVoteCacheUseMainMySQL();
public boolean getNonVotedCacheUseMySQL();
public boolean getNonVotedCacheUseMainMySQL();
public String getMqttClientID();
public String getMqttBrokerURL();
@@ -92,6 +134,8 @@ public interface VotingPluginProxyConfig {
public int getRedisPort();
public int getRedisDbIndex();
public String getRedisPrefix();
public String getRedisUsername();
@@ -107,7 +151,7 @@ public interface VotingPluginProxyConfig {
public boolean getTimeChangeFailSafeBypass();
public int getTimeHourOffSet();
public int getTimeWeekOffSet();
public String getTimeZone();
@@ -143,8 +187,6 @@ public interface VotingPluginProxyConfig {
public int getWaitUntilVoteDelayVoteDelay(String site);
public int getWaitUntilVoteDelayVoteDelayMin(String site);
public boolean getResetMilestonesMonthly();
public int getWaitUntilVoteDelayVoteDelayHour(String site);
@@ -154,4 +196,14 @@ public interface VotingPluginProxyConfig {
public void save();
public boolean getVoteLoggingEnabled();
public int getVoteLoggingPurgeDays();
public boolean getVoteLoggingUseMainMySQL();
public boolean hasDatabaseConfigured();
public int getMultiProxyRedisDbIndex();
}
@@ -0,0 +1,308 @@
// 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;
}
}
}
@@ -0,0 +1,20 @@
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;
}
}
}
@@ -0,0 +1,150 @@
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;
}
}
@@ -0,0 +1,57 @@
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);
}
}
@@ -622,7 +622,6 @@ public class BStatsMetricsBungee {
*
* @return The server specific data.
*/
@SuppressWarnings("deprecation")
private JsonObject getServerData() {
// Minecraft specific data
int playerAmount = Math.min(plugin.getProxy().getOnlineCount(), 500);
@@ -5,6 +5,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -16,12 +17,12 @@ import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.config.ConfigurationProvider;
import net.md_5.bungee.config.YamlConfiguration;
public class Config implements VotingPluginProxyConfig {
public class BungeeConfig implements VotingPluginProxyConfig {
private VotingPluginBungee bungee;
@Getter
private Configuration data;
public Config(VotingPluginBungee bungee) {
public BungeeConfig(VotingPluginBungee bungee) {
this.bungee = bungee;
}
@@ -33,6 +34,34 @@ public class Config implements VotingPluginProxyConfig {
return map;
}
@Override
public boolean getProxyBroadcastEnabled() {
return getData().getBoolean("ProxyBroadcast.Enabled", false);
}
@Override
public String getProxyBroadcastScopeMode() {
return getData().getString("ProxyBroadcast.Scope.Mode", "ALL_SERVERS");
}
@Override
public List<String> getProxyBroadcastScopeServers() {
List<String> list = getData().getStringList("ProxyBroadcast.Scope.Servers");
return list != null ? list : Collections.emptyList();
}
@Override
public String getProxyBroadcastOfflineMode() {
return getData().getString("ProxyBroadcast.OfflineMode", "QUEUE");
}
@Override
public List<String> getProxyBroadcastOfflineForwardServers() {
List<String> list = getData().getStringList("ProxyBroadcast.OfflineForward.Servers");
return list != null ? list : Collections.emptyList();
}
@Override
public boolean getAllowUnJoined() {
return getData().getBoolean("AllowUnJoined", false);
@@ -48,11 +77,6 @@ public class Config implements VotingPluginProxyConfig {
return getData().getStringList("BlockedServers");
}
@Override
public boolean getBroadcast() {
return getData().getBoolean("Broadcast", false);
}
@Override
public String getBungeeHost() {
return getData().getString("BungeeServer.Host", "");
@@ -128,6 +152,11 @@ public class Config implements VotingPluginProxyConfig {
return getData().getInt("MultiProxyRedis.Port", 6379);
}
@Override
public int getMultiProxyRedisDbIndex() {
return getData().getInt("MultiProxyRedis.Db-Index", 0);
}
@Override
public boolean getMultiProxyRedisUseExistingConnection() {
return getData().getBoolean("MultiProxyRedis.UseExistingConnection", false);
@@ -203,6 +232,11 @@ public class Config implements VotingPluginProxyConfig {
return getData().getInt("Redis.Port", 6379);
}
@Override
public int getRedisDbIndex() {
return getData().getInt("Redis.Db-Index", 0);
}
@Override
public String getRedisPrefix() {
return getData().getString("Redis.Prefix", "");
@@ -404,11 +438,6 @@ public class Config implements VotingPluginProxyConfig {
return getData().getInt("WaitUntilVoteDelay." + site + ".VoteDelayMin", 0);
}
@Override
public boolean getResetMilestonesMonthly() {
return getData().getBoolean("ResetMilestonesMonthly", false);
}
@Override
public boolean getVoteCacheUseMySQL() {
return getData().getBoolean("VoteCache.UseMySQL", false);
@@ -424,4 +453,47 @@ public class Config implements VotingPluginProxyConfig {
return getData().getInt("TimeWeekOffSet");
}
@Override
public boolean getNonVotedCacheUseMySQL() {
return getData().getBoolean("NonVotedCache.UseMySQL", false);
}
@Override
public boolean getNonVotedCacheUseMainMySQL() {
return getData().getBoolean("NonVotedCache.UseMainMySQL", true);
}
@Override
public boolean getVoteLoggingEnabled() {
return getData().getBoolean("VoteLogging.Enabled", false);
}
@Override
public int getVoteLoggingPurgeDays() {
return getData().getInt("VoteLogging.PurgeDays", 30);
}
@Override
public boolean getVoteLoggingUseMainMySQL() {
return getData().getBoolean("VoteLogging.UseMainMySQL", true);
}
public Configuration sectionOrNull(Configuration root, String key) {
Object v = root.get(key);
return (v instanceof Configuration) ? (Configuration) v : null;
}
@Override
public boolean hasDatabaseConfigured() {
// New-style: Database: { Host: ..., ... }
Configuration db = sectionOrNull(data, "Database");
if (db != null) {
return db.getString("Host", "") != null && !db.getString("Host", "").isEmpty();
}
// Legacy-style: Host/Port/... at root
String host = data.getString("Host", "");
return host != null && !host.isEmpty();
}
}
@@ -0,0 +1,47 @@
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() {
}
}
@@ -10,10 +10,10 @@ import com.bencodez.votingplugin.proxy.cache.GsonDataNode;
import com.bencodez.votingplugin.proxy.cache.IVoteCache;
import com.bencodez.votingplugin.timequeue.VoteTimeQueue;
public class VoteCache extends BungeeJsonFile implements IVoteCache {
public class BungeeJsonVoteCache extends BungeeJsonFile implements IVoteCache {
private VotingPluginBungee bungee;
public VoteCache(VotingPluginBungee bungee) {
public BungeeJsonVoteCache(VotingPluginBungee bungee) {
super(new File(bungee.getDataFolder(), "votecache.json"));
this.bungee = bungee;
initialize();
@@ -42,6 +42,7 @@ public class VoteCache extends BungeeJsonFile implements IVoteCache {
setLong(path + ".Time", voteData.getTime());
setBoolean(path + ".Real", voteData.isRealVote());
setString(path + ".Text", voteData.getText());
setString(path + ".VoteID", voteData.getVoteId() != null ? voteData.getVoteId().toString() : null);
}
public void addVoteOnline(String player, int num, OfflineBungeeVote voteData) {
@@ -52,6 +53,7 @@ public class VoteCache extends BungeeJsonFile implements IVoteCache {
setLong(path + ".Time", voteData.getTime());
setBoolean(path + ".Real", voteData.isRealVote());
setString(path + ".Text", voteData.getText());
setString(path + ".VoteID", voteData.getVoteId() != null ? voteData.getVoteId().toString() : null);
}
public void clearData() {
@@ -1,73 +0,0 @@
package com.bencodez.votingplugin.proxy.bungee;
import java.io.File;
import com.bencodez.simpleapi.file.BungeeJsonFile;
import com.velocitypowered.api.proxy.Player;
import lombok.Getter;
import net.md_5.bungee.config.Configuration;
public class NonVotedPlayersCache extends BungeeJsonFile {
private VotingPluginBungee plugin;
@Getter
private Configuration data;
public NonVotedPlayersCache(VotingPluginBungee plugin) {
super(new File(plugin.getDataFolder(), "nonvotedplayerscache.json"));
this.plugin = plugin;
}
public void addPlayer(Player player) {
if (!plugin.getVotingPluginProxy().getProxyMySQL().containsKeyQuery(player.getUniqueId().toString())) {
addPlayer(player.getUniqueId().toString(), player.getUsername());
}
}
public void addPlayer(String uuid, String playerName) {
setString("NonVotedPlayers." + playerName + ".UUID", uuid);
setLong("NonVotedPlayers." + playerName + ".LastTime", System.currentTimeMillis());
save();
}
public void addPlayerCheck(String uuid, String playerName) {
if (!plugin.getVotingPluginProxy().getProxyMySQL().containsKeyQuery(uuid)) {
addPlayer(uuid, playerName);
}
}
public void check() {
for (String player : getKeys("NonVotedPlayers")) {
long time = getLong("NonVotedPlayers." + player + ".LastTime", 0);
if ((System.currentTimeMillis() - time) > 1000 * 60 * 60 * 24 * 5) {
removePlayer(player);
} else {
String uuid = getString("NonVotedPlayers." + player + ".UUID", "");
if (!uuid.isEmpty()) {
if (plugin.getVotingPluginProxy().getProxyMySQL().containsKeyQuery(uuid)) {
removePlayer(player);
}
} else {
removePlayer(player);
}
}
}
save();
}
public String playerExists(String playerName) {
String uuid = getString("NonVotedPlayers." + playerName + ".UUID", "");
if (!uuid.isEmpty()) {
return uuid;
}
return "";
}
private void removePlayer(String player) {
plugin.debug("Removing nonvotedplayer: " + player);
remove("NonVotedPlayers." + player);
save();
}
}
@@ -2,6 +2,7 @@ package com.bencodez.votingplugin.proxy.bungee;
import com.bencodez.votingplugin.proxy.VotingPluginProxyCommand;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.plugin.Command;
@@ -10,19 +11,20 @@ public class VotingPluginBungeeCommand extends Command {
private VotingPluginBungee bungee;
public VotingPluginBungeeCommand(VotingPluginBungee bungee) {
super("votingpluginbungee", "votingplugin.admin");
super("votingpluginproxy", "votingpluginproxy.admin");
this.bungee = bungee;
}
@Override
public void execute(CommandSender sender, String[] args) {
if (sender.hasPermission("votingplugin.admin")) {
if (sender.hasPermission("votingpluginproxy.admin")) {
String result = new VotingPluginProxyCommand(bungee.getVotingPluginProxy()).execute(args);
if (result != null) {
sender.sendMessage(new TextComponent(result.replace("&", "§")));
sender.sendMessage(new TextComponent(result.replace("&", "" + ChatColor.COLOR_CHAR)));
}
} else {
sender.sendMessage(new TextComponent("&cYou do not have permission to do this!".replace("&", "§")));
sender.sendMessage(new TextComponent(
"&cYou do not have permission to do this!".replace("&", "" + ChatColor.COLOR_CHAR)));
}
}
@@ -1,6 +1,7 @@
package com.bencodez.votingplugin.proxy.cache;
import ninja.leaping.configurate.ConfigurationNode;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.serialize.SerializationException;
public class ConfigDataNode implements DataNode {
@@ -10,16 +11,14 @@ public class ConfigDataNode implements DataNode {
this.node = node;
}
@SuppressWarnings("deprecation")
@Override
public boolean isObject() {
return node.hasMapChildren();
return node.isMap();
}
@SuppressWarnings("deprecation")
@Override
public boolean isArray() {
return node.hasListChildren();
return node.isList();
}
@Override
@@ -29,7 +28,7 @@ public class ConfigDataNode implements DataNode {
@Override
public DataNode get(String key) {
return new ConfigDataNode(node.getNode((Object) key));
return new ConfigDataNode(node.node((Object) key));
}
@Override
@@ -40,34 +39,42 @@ public class ConfigDataNode implements DataNode {
@Override
public boolean has(String key) {
ConfigurationNode child = node.getNode((Object) key);
return child != null && child.getValue() != null;
ConfigurationNode child = node.node((Object) key);
return child != null && child.raw() != null;
}
@Override
public boolean has(int index) {
ConfigurationNode child = node.getNode(index);
return child != null && child.getValue() != null;
ConfigurationNode child = node.node(index);
return child != null && child.raw() != null;
}
@Override
public DataNode get(int index) {
return new ConfigDataNode(node.getNode(index));
return new ConfigDataNode(node.node(index));
}
@Override
public void set(String key, Object value) {
node.getNode((Object) key).setValue(value);
try {
node.node((Object) key).set(value);
} catch (SerializationException e) {
e.printStackTrace();
}
}
@Override
public void set(int index, Object value) {
node.getNode(index).setValue(value);
try {
node.node(index).set(value);
} catch (SerializationException e) {
e.printStackTrace();
}
}
@Override
public Object asPrimitive() {
return node.getValue();
return node.raw();
}
@Override
@@ -89,4 +96,4 @@ public class ConfigDataNode implements DataNode {
public Object toInternal() {
return node;
}
}
}
@@ -66,7 +66,7 @@ public class GsonDataNode implements DataNode {
return prim.getAsLong();
}
// Try parsing from string if it’s not a number
// Try parsing from string if it's not a number
try {
return Long.parseLong(prim.getAsString());
} catch (NumberFormatException e) {
@@ -4,208 +4,323 @@ 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 {
public abstract class ProxyOnlineVoteCacheTable extends AbstractSqlTable {
protected com.bencodez.simpleapi.sql.mysql.MySQL mysql;
private final String tableName;
// 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);
public abstract void debug(Exception e);
@Override
public abstract void debug(Throwable t);
public ProxyOnlineVoteCacheTable(com.bencodez.simpleapi.sql.mysql.MySQL existingMysql, String tablePrefix,
boolean debug) {
this.tableName = (tablePrefix != null ? tablePrefix : "") + "votingplugin_onlinevotecache";
public ProxyOnlineVoteCacheTable(MySQL existingMysql, String tablePrefix, boolean debug) {
super((tablePrefix != null ? tablePrefix : "") + "votingplugin_onlinevotecache", existingMysql, debug);
this.mysql = existingMysql;
// Create table if not exists
String sql = "CREATE TABLE IF NOT EXISTS `" + tableName + "` (" + "id INT AUTO_INCREMENT PRIMARY KEY,"
+ "uuid VARCHAR(36)," + "playerName VARCHAR(100)," + "service VARCHAR(100)," + "`time` BIGINT,"
+ "realvote BOOLEAN," + "text LONGTEXT," + "INDEX idx_uuid (`uuid`)," + "INDEX idx_time (`time`)"
+ ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
try {
new Query(mysql, sql).executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
// best-effort migrations (no nested connections)
alterColumnType("uuid", bestUuidType());
addVoteIdColumnIfMissingOnce();
ensureIndexesOnce();
}
public ProxyOnlineVoteCacheTable(MysqlConfig config, boolean debug) {
String prefix = config.getTablePrefix() != null ? config.getTablePrefix() : "";
this.tableName = prefix + "votingplugin_onlinevotecache";
super("votingplugin_onlinevotecache", config, debug);
mysql = new com.bencodez.simpleapi.sql.mysql.MySQL(config.getMaxThreads()) {
@Override
public void debug(SQLException e) {
if (debug)
e.printStackTrace();
}
alterColumnType("uuid", bestUuidType());
addVoteIdColumnIfMissingOnce();
ensureIndexesOnce();
}
@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 online vote cache!");
public void updateVoteText(OfflineBungeeVote vote, String newText) {
if (vote == null) {
return;
}
try {
new Query(mysql, "USE `" + config.getDatabase() + "`;").executeUpdate();
} catch (SQLException e) {
logSevere("Failed to select database: " + config.getDatabase());
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);
}
String sql = "CREATE TABLE IF NOT EXISTS `" + tableName + "` (" + "id INT AUTO_INCREMENT PRIMARY KEY,"
+ "uuid VARCHAR(36)," + "playerName VARCHAR(100)," + "service VARCHAR(100)," + "`time` BIGINT,"
+ "realvote BOOLEAN," + "text LONGTEXT," + "INDEX idx_uuid (`uuid`)," + "INDEX idx_time (`time`)"
+ ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
try {
new Query(mysql, sql).executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
public String getTableName() {
return tableName;
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 insertOnlineVote(String uuid, String playerName, String service, long time, boolean realvote,
public void insertVote(UUID voteId, String uuid, String playerName, String service, long time, boolean real,
String text) {
String sql = "INSERT INTO `" + tableName + "` (uuid, playerName, service, `time`, realvote, text) "
+ "VALUES (?, ?, ?, ?, ?, ?);";
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)) {
ps.setString(1, uuid);
ps.setString(2, playerName);
ps.setString(3, service);
ps.setLong(4, time);
ps.setBoolean(5, realvote);
ps.setString(6, text);
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 e) {
e.printStackTrace();
} catch (SQLException | IllegalArgumentException e) {
debug(e);
}
}
// --- GET ---
public List<OnlineVoteRow> getAllVotes() {
String sql = "SELECT * FROM `" + tableName + "`;";
return selectVotes(sql, null);
public List<VoteRow> getAllVotes() {
return selectVotes("SELECT * FROM " + qi(getTableName()) + ";", null);
}
public List<OnlineVoteRow> getVotesForUUID(String uuid) {
String sql = "SELECT * FROM `" + tableName + "` WHERE `uuid` = ?;";
return selectVotes(sql, new Object[] { uuid });
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 `" + tableName + "` WHERE 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) {
e.printStackTrace();
debug(e);
}
}
public void removeVotesForUUID(String uuid) {
String sql = "DELETE FROM `" + tableName + "` WHERE uuid = ?;";
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)) {
ps.setString(1, uuid);
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 e) {
e.printStackTrace();
} catch (SQLException | IllegalArgumentException e) {
debug(e);
}
}
public void clearTable() {
try {
new Query(mysql, "TRUNCATE TABLE `" + tableName + "`;").executeUpdate();
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) {
e.printStackTrace();
debug(e);
}
}
public void close() {
mysql.disconnect();
}
// --- Internals ---
private List<OnlineVoteRow> selectVotes(String sql, Object[] params) {
List<OnlineVoteRow> list = new ArrayList<>();
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()) {
OnlineVoteRow v = new OnlineVoteRow(rs.getInt("id"), rs.getString("uuid"),
list.add(new VoteRow(rs.getInt("id"), rs.getString("voteid"), rs.getString("uuid"),
rs.getString("playerName"), rs.getString("service"), rs.getLong("time"),
rs.getBoolean("realvote"), rs.getString("text"));
list.add(v);
(getDbType() == DbType.POSTGRESQL ? rs.getBoolean("realVote") : rs.getInt("realVote") == 1),
rs.getString("text")));
}
}
} catch (SQLException e) {
e.printStackTrace();
debug(e);
}
return list;
}
public void removeOnlineVote(OfflineBungeeVote vote) {
String sql = "DELETE FROM `" + tableName + "` WHERE `uuid` = ? AND `service` = ? AND `time` = ?;";
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, vote.getUuid());
ps.setString(2, vote.getService());
ps.setLong(3, vote.getTime());
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
public static class OnlineVoteRow {
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 boolean realVote;
private final String text;
public OnlineVoteRow(int id, String uuid, String playerName, String service, long time, boolean realvote,
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.realVote = realVote;
this.text = text;
}
@@ -213,6 +328,10 @@ public abstract class ProxyOnlineVoteCacheTable {
return id;
}
public String getVoteId() {
return voteId;
}
public String getUuid() {
return uuid;
}
@@ -229,8 +348,8 @@ public abstract class ProxyOnlineVoteCacheTable {
return time;
}
public boolean isRealvote() {
return realvote;
public boolean isRealVote() {
return realVote;
}
public String getText() {
@@ -7,88 +7,75 @@ 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 {
public abstract class ProxyTimedVoteCacheTable extends AbstractSqlTable {
protected com.bencodez.simpleapi.sql.mysql.MySQL mysql;
private final String tableName;
@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);
public abstract void debug(Exception e);
@Override
public abstract void debug(Throwable t);
public ProxyTimedVoteCacheTable(com.bencodez.simpleapi.sql.mysql.MySQL existingMysql, String tablePrefix,
boolean debug) {
this.tableName = (tablePrefix != null ? tablePrefix : "") + "votingplugin_timedvotecache";
this.mysql = existingMysql;
// Create table if not exists
String sql = "CREATE TABLE IF NOT EXISTS `" + tableName + "` (" + "id INT AUTO_INCREMENT PRIMARY KEY,"
+ "playerName VARCHAR(100)," + "service VARCHAR(100)," + "time BIGINT," + "INDEX idx_time (time)"
+ ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
try {
new Query(mysql, sql).executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
public ProxyTimedVoteCacheTable(MySQL existingMysql, String tablePrefix, boolean debug) {
super((tablePrefix != null ? tablePrefix : "") + "votingplugin_timedvotecache",
existingMysql,
debug);
ensureIndexes();
}
public ProxyTimedVoteCacheTable(MysqlConfig config, boolean debug) {
String prefix = config.getTablePrefix() != null ? config.getTablePrefix() : "";
this.tableName = prefix + "votingplugin_timedvotecache";
mysql = new com.bencodez.simpleapi.sql.mysql.MySQL(config.getMaxThreads()) {
@Override
public void debug(SQLException e) {
if (debug)
e.printStackTrace();
}
@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 timed vote cache!");
}
try {
new Query(mysql, "USE `" + config.getDatabase() + "`;").executeUpdate();
} catch (SQLException e) {
logSevere("Failed to select database: " + config.getDatabase());
debug(e);
}
// Updated table creation without uuid, server, realVote
String sql = "CREATE TABLE IF NOT EXISTS `" + tableName + "` (" + "id INT AUTO_INCREMENT PRIMARY KEY,"
+ "playerName VARCHAR(100)," + "service VARCHAR(100)," + "time BIGINT," + "INDEX idx_time (time)"
+ ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
try {
new Query(mysql, sql).executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
super("votingplugin_timedvotecache", config, debug);
ensureIndexes();
}
public String getTableName() {
return tableName;
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 `" + tableName + "` (playerName, service, time) VALUES (?, ?, ?);";
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);
@@ -96,74 +83,78 @@ public abstract class ProxyTimedVoteCacheTable {
ps.setLong(3, time);
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
debug(e);
}
}
// --- GET ---
public List<TimedVoteRow> getAllVotes() {
String sql = "SELECT * FROM `" + tableName + "`;";
return selectVotes(sql, null);
return selectVotes("SELECT * FROM " + qi(getTableName()) + ";", null);
}
public List<TimedVoteRow> getExpiredVotes(long now) {
String sql = "SELECT * FROM `" + tableName + "` WHERE time <= ?;";
return selectVotes(sql, new Object[] { now });
return selectVotes("SELECT * FROM " + qi(getTableName()) + " WHERE " + qi("time") + " <= ?;",
new Object[] { now });
}
// --- DELETE ---
public void removeVoteById(int id) {
String sql = "DELETE FROM `" + tableName + "` WHERE 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) {
e.printStackTrace();
debug(e);
}
}
public void removeExpiredVotes(long now) {
String sql = "DELETE FROM `" + tableName + "` WHERE time <= ?;";
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) {
e.printStackTrace();
debug(e);
}
}
public void clearTable() {
try {
new Query(mysql, "TRUNCATE TABLE `" + tableName + "`;").executeUpdate();
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) {
e.printStackTrace();
debug(e);
}
}
public void close() {
mysql.disconnect();
}
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()) {
TimedVoteRow v = new TimedVoteRow(rs.getInt("id"), rs.getString("playerName"),
rs.getString("service"), rs.getLong("time"));
list.add(v);
list.add(new TimedVoteRow(
rs.getInt("id"),
rs.getString("playerName"),
rs.getString("service"),
rs.getLong("time")
));
}
}
} catch (SQLException e) {
e.printStackTrace();
debug(e);
}
return list;
}
@@ -4,250 +4,395 @@ 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;
import lombok.Getter;
/**
* 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 {
public abstract class ProxyVoteCacheTable {
// 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();
@Getter
private com.bencodez.simpleapi.sql.mysql.MySQL mysql;
private final String tableName;
// ---- 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);
public abstract void debug(Exception e);
@Override
public abstract void debug(Throwable t);
public ProxyVoteCacheTable(com.bencodez.simpleapi.sql.mysql.MySQL existingMysql, String tablePrefix,
boolean debug) {
this.tableName = (tablePrefix != null ? tablePrefix : "") + "votingplugin_votecache";
// ---- Constructors ----
this.mysql = existingMysql;
public ProxyVoteCacheTable(MySQL existingMysql, String tablePrefix, boolean debug) {
super((tablePrefix != null ? tablePrefix : "") + "votingplugin_votecache", existingMysql, debug);
// Create table if not exists
String sql = "CREATE TABLE IF NOT EXISTS `" + tableName + "` (" + "id INT AUTO_INCREMENT PRIMARY KEY,"
+ "uuid VARCHAR(36)," + "playerName VARCHAR(100)," + "service VARCHAR(100)," + "`time` BIGINT,"
+ "realVote TINYINT(1)," + "`text` TEXT," + "`server` VARCHAR(100)," + "INDEX idx_server (`server`),"
+ "INDEX idx_uuid (`uuid`)," + "INDEX idx_time (`time`)" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
try {
new Query(mysql, sql).executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
// best-effort migrations (safe for pool size 1; no nested connections)
alterColumnType("uuid", bestUuidType());
addVoteIdColumnIfMissingOnce();
ensureIndexesOnce();
}
public ProxyVoteCacheTable(MysqlConfig config, boolean debug) {
// Always use fixed table name + optional prefix to avoid collisions with other
// tables
String prefix = config.getTablePrefix() != null ? config.getTablePrefix() : "";
this.tableName = prefix + "votingplugin_votecache";
super("votingplugin_votecache", config, debug);
mysql = new com.bencodez.simpleapi.sql.mysql.MySQL(config.getMaxThreads()) {
@Override
public void debug(SQLException e) {
if (debug)
e.printStackTrace();
}
alterColumnType("uuid", bestUuidType());
addVoteIdColumnIfMissingOnce();
ensureIndexesOnce();
}
@Override
public void severe(String msg) {
logSevere(msg);
}
// ---- Migrations / indexes ----
@Override
public void debug(String msg) {
if (debug)
logInfo("MYSQL DEBUG: " + msg);
}
};
if (!mysql.connect(config)) {
logSevere("Failed to connect to MySQL for vote cache!");
private void ensureIndexesOnce() {
if (getDbType() != DbType.POSTGRESQL) {
return; // MySQL indexes are already in CREATE TABLE
}
try {
new Query(mysql, "USE `" + config.getDatabase() + "`;").executeUpdate();
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) {
logSevere("Failed to select database: " + config.getDatabase());
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);
}
String sql = "CREATE TABLE IF NOT EXISTS `" + tableName + "` (" + "id INT AUTO_INCREMENT PRIMARY KEY,"
+ "uuid VARCHAR(36)," + "playerName VARCHAR(100)," + "service VARCHAR(100)," + "`time` BIGINT," // epoch
// millis
+ "realVote TINYINT(1)," // boolean
+ "`text` TEXT," + "`server` VARCHAR(100)," + "INDEX idx_server (`server`),"
+ "INDEX idx_uuid (`uuid`)," + "INDEX idx_time (`time`)" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
try {
new Query(mysql, sql).executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
// don't hard fail startup
logSevere("Failed to add voteid column to " + getTableName() + ": " + e.getMessage());
debug(e);
}
}
public String getTableName() {
return tableName;
}
// ---- 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 (?, ?, ?, ?, ?, ?, ?, ?);";
// --- INSERT ---
public void insertVote(String uuid, String playerName, String service, long time, boolean real, String text,
String server) {
String sql = "INSERT INTO `" + tableName + "` (uuid, playerName, service, `time`, realVote, `text`, `server`) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?);";
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, uuid);
ps.setString(2, playerName);
ps.setString(3, service);
ps.setLong(4, time);
ps.setBoolean(5, real);
ps.setString(6, text);
ps.setString(7, server);
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 e) {
e.printStackTrace();
} catch (SQLException | IllegalArgumentException e) {
debug(e);
}
}
// --- GET ---
// ---- GET ----
public List<VoteRow> getAllVotes() {
String sql = "SELECT * FROM `" + tableName + "`;";
return selectVotes(sql, null);
return selectVotes("SELECT * FROM " + qi(getTableName()) + ";", null);
}
public List<VoteRow> getVotesForServer(String server) {
String sql = "SELECT * FROM `" + tableName + "` WHERE `server` = ?;";
return selectVotes(sql, new Object[] { server });
return selectVotes("SELECT * FROM " + qi(getTableName()) + " WHERE " + qi("server") + " = ?;",
new Object[] { server });
}
public VoteRow getVoteById(int id) {
String sql = "SELECT * FROM `" + tableName + "` WHERE id = ?;";
List<VoteRow> list = selectVotes(sql, new Object[] { 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 `" + tableName + "` WHERE uuid = ?;";
return selectVotes(sql, new Object[] { 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) {
String sql = "SELECT * FROM `" + tableName + "` WHERE playerName = ?;";
return selectVotes(sql, new Object[] { playerName });
return selectVotes("SELECT * FROM " + qi(getTableName()) + " WHERE " + qi("playerName") + " = ?;",
new Object[] { playerName });
}
public Set<String> getServers() {
Set<String> servers = new HashSet<String>();
String sql = "SELECT DISTINCT `server` FROM `" + tableName + "` WHERE `server` IS NOT NULL;";
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)
if (s != null) {
servers.add(s);
}
}
} catch (SQLException e) {
e.printStackTrace();
debug(e);
}
return servers;
}
// --- DELETE ---
// ---- DELETE ----
public void removeVoteById(int id) {
String sql = "DELETE FROM `" + tableName + "` WHERE 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) {
e.printStackTrace();
debug(e);
}
}
public void removeVotesByServerAndUUID(String server, String uuid) {
String sql = "DELETE FROM `" + tableName + "` WHERE `server` = ? AND `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);
ps.setString(2, uuid);
if (getDbType() == DbType.POSTGRESQL) {
ps.setObject(2, UUID.fromString(uuid));
} else {
ps.setString(2, uuid);
}
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} catch (SQLException | IllegalArgumentException e) {
debug(e);
}
}
public void removeVote(OfflineBungeeVote vote, String server) {
String sql = "DELETE FROM `" + tableName
+ "` WHERE `uuid` = ? AND `service` = ? AND `time` = ? AND `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)) {
ps.setString(1, vote.getUuid());
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 e) {
e.printStackTrace();
} catch (SQLException | IllegalArgumentException e) {
debug(e);
}
}
public void removeVotesByServer(String server) {
String sql = "DELETE FROM `" + tableName + "` WHERE `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) {
e.printStackTrace();
debug(e);
}
}
public void clearTable() {
try {
new Query(mysql, "TRUNCATE TABLE `" + tableName + "`;").executeUpdate();
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) {
e.printStackTrace();
debug(e);
}
}
public void close() {
mysql.disconnect();
}
// ---- Internals ----
private List<VoteRow> selectVotes(String sql, Object[] params) {
List<VoteRow> list = new ArrayList<VoteRow>();
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("uuid"), rs.getString("playerName"),
rs.getString("service"), rs.getLong("time"), rs.getBoolean("realVote"),
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) {
e.printStackTrace();
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;
@@ -256,9 +401,10 @@ public abstract class ProxyVoteCacheTable {
private final String text;
private final String server;
public VoteRow(int id, String uuid, String playerName, String service, long time, boolean realVote, String text,
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;
@@ -272,6 +418,10 @@ public abstract class ProxyVoteCacheTable {
return id;
}
public String getVoteId() {
return voteId;
}
public String getUuid() {
return uuid;
}
@@ -11,7 +11,6 @@ import java.util.concurrent.ConcurrentLinkedQueue;
import com.bencodez.simpleapi.sql.mysql.MySQL;
import com.bencodez.simpleapi.sql.mysql.config.MysqlConfig;
import com.bencodez.votingplugin.proxy.BungeeMessageData;
import com.bencodez.votingplugin.proxy.OfflineBungeeVote;
import com.bencodez.votingplugin.timequeue.VoteTimeQueue;
@@ -36,14 +35,49 @@ public abstract class VoteCacheHandler {
return cachedVotes.getOrDefault(server, new ArrayList<>());
}
/**
* Get total cached votes for a UUID across: - UUID-based online vote cache -
* Server-based vote cache
*
* @param uuid player UUID (string form)
* @return total cached votes across all proxy caches
*/
public int getProxyCachedTotal(String uuid) {
if (uuid == null || uuid.isEmpty()) {
return 0;
}
int total = 0;
// 1) UUID-based cache (fast lookup)
ArrayList<OfflineBungeeVote> onlineVotes = cachedOnlineVotes.get(uuid);
if (onlineVotes != null) {
total += onlineVotes.size();
}
// 2) Server-based caches (scan)
for (ArrayList<OfflineBungeeVote> serverVotes : cachedVotes.values()) {
for (OfflineBungeeVote vote : serverVotes) {
if (vote != null && vote.getUuid() != null && vote.getUuid().equalsIgnoreCase(uuid)) {
total++;
}
}
}
return total;
}
public void addServerVote(String server, OfflineBungeeVote vote) {
cachedVotes.putIfAbsent(server, new ArrayList<>());
cachedVotes.get(server).add(vote);
if (useMySQL) {
voteCacheTable.insertVote(vote.getUuid(), vote.getPlayerName(), vote.getService(), vote.getTime(),
vote.isRealVote(), vote.getText(), server);
voteCacheTable.insertVote(vote.getVoteId(), vote.getUuid(), vote.getPlayerName(), vote.getService(),
vote.getTime(), vote.isRealVote(), vote.getText(), server);
} else {
jsonStorage.addVote(server, cachedVotes.get(server).size() - 1, vote);
// IMPORTANT: index must come from JSON, not from cachedVotes (cache can be out
// of sync with JSON)
int idx = jsonStorage.getServerVotes(server).size();
jsonStorage.addVote(server, idx, vote);
jsonStorage.save();
}
}
@@ -52,6 +86,7 @@ public abstract class VoteCacheHandler {
if (cachedVotes.containsKey(server)) {
ArrayList<OfflineBungeeVote> votes = cachedVotes.get(server);
votes.removeIf(vote -> vote.getUuid().equals(uuid));
if (useMySQL) {
voteCacheTable.removeVotesByServerAndUUID(server, uuid);
} else {
@@ -82,11 +117,15 @@ public abstract class VoteCacheHandler {
public void addOnlineVote(String uuid, OfflineBungeeVote vote) {
cachedOnlineVotes.putIfAbsent(uuid, new ArrayList<>());
cachedOnlineVotes.get(uuid).add(vote);
if (useMySQL) {
onlineVoteCacheTable.insertOnlineVote(vote.getUuid(), vote.getPlayerName(), vote.getService(),
onlineVoteCacheTable.insertVote(vote.getVoteId(), vote.getUuid(), vote.getPlayerName(), vote.getService(),
vote.getTime(), vote.isRealVote(), vote.getText());
} else {
jsonStorage.addVoteOnline(uuid, cachedOnlineVotes.get(uuid).size() - 1, vote);
// IMPORTANT: index must come from JSON, not from cachedOnlineVotes (cache can
// be out of sync with JSON)
int idx = jsonStorage.getOnlineVotes(uuid).size();
jsonStorage.addVoteOnline(uuid, idx, vote);
jsonStorage.save();
}
}
@@ -94,7 +133,7 @@ public abstract class VoteCacheHandler {
public void removeOnlineVotes(String uuid) {
cachedOnlineVotes.remove(uuid);
if (useMySQL) {
onlineVoteCacheTable.removeVotesForUUID(uuid);
onlineVoteCacheTable.removeVotesByUuid(uuid);
} else {
jsonStorage.removeOnlineVotes(uuid);
jsonStorage.save();
@@ -109,7 +148,7 @@ public abstract class VoteCacheHandler {
for (Entry<String, ArrayList<OfflineBungeeVote>> entry : cachedOnlineVotes.entrySet()) {
ArrayList<OfflineBungeeVote> votes = entry.getValue();
for (OfflineBungeeVote vote : votes) {
if (vote.getTime() + (voteCacheTime * 24 * 60 * 60 * 1000) < cTime) {
if (vote.getTime() + (voteCacheTime * 24 * 60 * 60 * 1000L) < cTime) {
debug1("Removing vote from cache: " + vote.toString());
expiredOnlineVotes.add(vote);
}
@@ -122,7 +161,7 @@ public abstract class VoteCacheHandler {
for (Entry<String, ArrayList<OfflineBungeeVote>> entry : cachedVotes.entrySet()) {
ArrayList<OfflineBungeeVote> votes = entry.getValue();
for (OfflineBungeeVote vote : votes) {
if (vote.getTime() + (voteCacheTime * 24 * 60 * 60 * 1000) < cTime) {
if (vote.getTime() + (voteCacheTime * 24 * 60 * 60 * 1000L) < cTime) {
debug1("Removing vote from cache: " + vote.toString());
expiredServerVotes.add(vote);
}
@@ -134,32 +173,6 @@ public abstract class VoteCacheHandler {
}
}
public void resetMilestoneCountInVotes() {
// Iterate through cached online votes
for (Map.Entry<String, ArrayList<OfflineBungeeVote>> entry : cachedOnlineVotes.entrySet()) {
ArrayList<OfflineBungeeVote> votes = entry.getValue();
for (OfflineBungeeVote vote : votes) {
String updatedText = updateMilestoneCount(vote.getText());
vote.setText(updatedText);
}
}
// Iterate through cached votes
for (Map.Entry<String, ArrayList<OfflineBungeeVote>> entry : cachedVotes.entrySet()) {
ArrayList<OfflineBungeeVote> votes = entry.getValue();
for (OfflineBungeeVote vote : votes) {
String updatedText = updateMilestoneCount(vote.getText());
vote.setText(updatedText);
}
}
}
private String updateMilestoneCount(String text) {
BungeeMessageData data = new BungeeMessageData(text);
data.setMilestoneCount(0); // Reset milestone count to 0
return data.toString();
}
public void saveVoteCache() {
if (useMySQL) {
@@ -189,8 +202,9 @@ public abstract class VoteCacheHandler {
if (useMySQL) {
// Load votes from MySQL
voteCacheTable.getAllVotes().forEach(voteRow -> {
OfflineBungeeVote vote = new OfflineBungeeVote(voteRow.getPlayerName(), voteRow.getUuid(),
voteRow.getService(), voteRow.getTime(), voteRow.isRealVote(), voteRow.getText());
OfflineBungeeVote vote = new OfflineBungeeVote(voteRow.getVoteId(), voteRow.getPlayerName(),
voteRow.getUuid(), voteRow.getService(), voteRow.getTime(), voteRow.isRealVote(),
voteRow.getText());
String server = voteRow.getServer();
cachedVotes.putIfAbsent(server, new ArrayList<>());
cachedVotes.get(server).add(vote);
@@ -198,8 +212,9 @@ public abstract class VoteCacheHandler {
// Load online votes from MySQL
onlineVoteCacheTable.getAllVotes().forEach(voteRow -> {
OfflineBungeeVote vote = new OfflineBungeeVote(voteRow.getPlayerName(), voteRow.getUuid(),
voteRow.getService(), voteRow.getTime(), voteRow.isRealvote(), voteRow.getText());
OfflineBungeeVote vote = new OfflineBungeeVote(voteRow.getVoteId(), voteRow.getPlayerName(),
voteRow.getUuid(), voteRow.getService(), voteRow.getTime(), voteRow.isRealVote(),
voteRow.getText());
String player = vote.getUuid();
cachedOnlineVotes.putIfAbsent(player, new ArrayList<>());
cachedOnlineVotes.get(player).add(vote);
@@ -248,8 +263,9 @@ public abstract class VoteCacheHandler {
long time = data.has("Time") ? data.get("Time").asLong() : 0L;
boolean real = data.has("Real") && data.get("Real").asBoolean();
String text = data.has("Text") ? data.get("Text").asString() : "";
String voteId = data.has("VoteId") ? data.get("VoteId").asString() : "";
votes.add(new OfflineBungeeVote(name, uuid, service, time, real, text));
votes.add(new OfflineBungeeVote(voteId, name, uuid, service, time, real, text));
}
}
cachedVotes.put(server, votes);
@@ -272,8 +288,9 @@ public abstract class VoteCacheHandler {
long time = data.has("Time") ? data.get("Time").asLong() : 0L;
boolean real = data.has("Real") && data.get("Real").asBoolean();
String text = data.has("Text") ? data.get("Text").asString() : "";
String voteId = data.has("VoteId") ? data.get("VoteId").asString() : "";
votes.add(new OfflineBungeeVote(name, uuid, service, time, real, text));
votes.add(new OfflineBungeeVote(voteId, name, uuid, service, time, real, text));
}
}
cachedOnlineVotes.put(player, votes);
@@ -311,6 +328,8 @@ public abstract class VoteCacheHandler {
public abstract void debug1(Exception e);
public abstract void debug1(Throwable e);
public abstract void debug1(String msg);
public VoteCacheHandler(MysqlConfig mysqlConfig, boolean useMySQL, boolean useExistingConnection, MySQL mysql,
@@ -331,9 +350,14 @@ public abstract class VoteCacheHandler {
}
@Override
public void debug(Exception e) {
public void debug(Throwable t) {
if (debug)
debug1(e);
debug1(t);
}
@Override
public void debug(String text) {
debug1(text);
}
};
@@ -349,9 +373,14 @@ public abstract class VoteCacheHandler {
}
@Override
public void debug(Exception e) {
public void debug(Throwable t) {
if (debug)
debug1(e);
debug1(t);
}
@Override
public void debug(String text) {
debug1(text);
}
};
@@ -367,9 +396,14 @@ public abstract class VoteCacheHandler {
}
@Override
public void debug(Exception e) {
public void debug(Throwable t) {
if (debug)
debug1(e);
debug1(t);
}
@Override
public void debug(String text) {
debug1(text);
}
};
} else {
@@ -385,9 +419,14 @@ public abstract class VoteCacheHandler {
}
@Override
public void debug(Exception e) {
public void debug(Throwable t) {
if (debug)
debug1(e);
debug1(t);
}
@Override
public void debug(String text) {
debug1(text);
}
};
@@ -404,9 +443,14 @@ public abstract class VoteCacheHandler {
}
@Override
public void debug(Exception e) {
public void debug(Throwable t) {
if (debug)
debug1(e);
debug1(t);
}
@Override
public void debug(String text) {
debug1(text);
}
};
@@ -423,9 +467,14 @@ public abstract class VoteCacheHandler {
}
@Override
public void debug(Exception e) {
public void debug(Throwable t) {
if (debug)
debug1(e);
debug1(t);
}
@Override
public void debug(String text) {
debug1(text);
}
};
}
@@ -462,7 +511,7 @@ public abstract class VoteCacheHandler {
&& v.getService().equals(vote.getService()) && v.getTime() == vote.getTime());
}
if (useMySQL) {
onlineVoteCacheTable.removeOnlineVote(vote);
onlineVoteCacheTable.removeVote(vote);
} else {
jsonStorage.removeOnlineVote(vote);
jsonStorage.save();
@@ -0,0 +1,45 @@
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;
}
}
}
@@ -0,0 +1,56 @@
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();
}
}
}
@@ -0,0 +1,132 @@
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);
}
@@ -0,0 +1,184 @@
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;
}
}
}
@@ -1,35 +1,37 @@
// 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.BungeeMessageData;
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 string, String string2);
public abstract void addNonVotedPlayerCache(String uuid, String playerName);
public abstract void clearVote(String string);
public abstract void clearVote(String uuid);
public void close() {
if (multiproxySocketHandler != null) {
@@ -53,6 +55,8 @@ public abstract class MultiProxyHandler {
public abstract int getMultiProxyRedisPort();
public abstract int getMultiProxyRedisDbIndex();
public abstract boolean getMultiProxyRedisUseExistingConnection();
public abstract String getMultiProxyServerName();
@@ -79,148 +83,184 @@ public abstract class MultiProxyHandler {
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()) {
if (getMultiProxyMethod().equals(MultiProxyMethod.SOCKETS)) {
if (getEncryptionHandler() == null) {
setEncryptionHandler(new EncryptionHandler("VotingPlugin",new File(getPluginDataFolder(), "secretkey.key")));
}
if (!getMultiProxySupportEnabled()) {
return;
}
if (multiproxySocketHandler != null) {
multiproxySocketHandler.closeConnection();
multiproxySocketHandler = null;
}
multiproxySocketHandler = new SocketHandler(getVersion(), getMultiProxySocketHostHost(),
getMultiProxySocketHostPort(), getEncryptionHandler(), getDebug()) {
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 log(String str) {
logInfo(str);
public void debug(String message) {
if (getDebug()) {
logInfo("MultiProxyRedis: " + message);
}
}
};
multiproxySocketHandler.add(new SocketReceiver() {
@Override
public void onReceive(String[] data) {
if (data.length > 0) {
if (data[0].equalsIgnoreCase("Status")) {
logInfo("Multi-proxy status message received");
} else if (data[0].equalsIgnoreCase("ClearVote")) {
clearVote(data[2]);
if (getPrimaryServer()) {
sendMultiProxyServerMessage("ClearVotePrimary", data[1], data[2]);
}
} else if (data[0].equalsIgnoreCase("ClearVotePrimary")) {
clearVote(data[2]);
} else if (data[0].equalsIgnoreCase("login")) {
addNonVotedPlayerCache(data[1], data[2]);
// nonVotedPlayersCache.addPlayer(data[1], data[2]);
}
}
if (data.length > 8) {
if (data[0].equalsIgnoreCase("Vote")) {
triggerVote(data[2], data[3], Boolean.valueOf(data[7]), true, 0,
new BungeeMessageData(data[8]), data[1]);
} else if (data[0].equalsIgnoreCase("VoteOnline")) {
triggerVote(data[2], data[3], Boolean.valueOf(data[7]), true, 0,
new BungeeMessageData(data[8]), data[1]);
}
}
}
});
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()) {
@Override
public void debug(String message) {
if (getDebug()) {
logInfo("MultiProxyRedis: " + message);
}
}
@Override
public void onMessage(String channel, String[] data) {
if (data.length > 0) {
if (data[0].equalsIgnoreCase("Status")) {
logInfo("Multi-proxy status message received");
} else if (data[0].equalsIgnoreCase("ClearVote")) {
clearVote(data[2]);
if (getPrimaryServer()) {
sendMultiProxyServerMessage("ClearVotePrimary", data[1], data[2]);
}
} else if (data[0].equalsIgnoreCase("ClearVotePrimary")) {
clearVote(data[2]);
} else if (data[0].equalsIgnoreCase("login")) {
addNonVotedPlayerCache(data[1], data[2]);
// nonVotedPlayersCache.addPlayer(data[1], data[2]);
}
}
if (data.length > 8) {
if (data[0].equalsIgnoreCase("Vote")) {
triggerVote(data[2], data[3], Boolean.valueOf(data[7]), true, 0,
new BungeeMessageData(data[8]), data[1]);
} else if (data[0].equalsIgnoreCase("VoteOnline")) {
triggerVote(data[2], data[3], Boolean.valueOf(data[7]), true, 0,
new BungeeMessageData(data[8]), data[1]);
}
}
}
};
}
runAsnc(new Runnable() {
@Override
public void run() {
multiProxyRedis.loadListener(
new RedisListener(multiProxyRedis, "VotingPluginProxy_" + getMultiProxyServerName()));
}
});
}
logInfo("Loaded multi-proxy support: " + getMultiProxyMethod().toString());
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;
}
sendMultiProxyServerMessage("Login", uuid, playerName);
sendMultiProxyEnvelope(VotingPluginWire.login(playerName, uuid, getMultiProxyServerName()));
}
public abstract void logInfo(String msg);
public void sendClearVote(String uuid, String playerName) {
if (!getMultiProxySupportEnabled()) {
return;
}
sendMultiProxyEnvelope(VotingPluginWire.clearVote(uuid, playerName, getMultiProxyServerName()));
}
public abstract void runAsnc(Runnable runnable);
public void sendStatus() {
if (!getMultiProxySupportEnabled()) {
return;
}
sendMultiProxyEnvelope(VotingPluginWire.status(getMultiProxyServerName()));
}
public void sendMultiProxyServerMessage(String... messageData) {
public void sendMultiProxyEnvelope(JsonEnvelope envelope) {
if (envelope == null) {
return;
}
if (getMultiProxyMethod().equals(MultiProxyMethod.SOCKETS)) {
if (multiproxyClientHandles == null) {
return;
}
for (ClientHandler h : multiproxyClientHandles.values()) {
h.sendMessage(messageData);
h.sendEnvelope(envelope);
}
} else if (getMultiProxyMethod().equals(MultiProxyMethod.REDIS)) {
if (multiProxyRedis == null) {
return;
}
for (String server : getProxyServers()) {
multiProxyRedis.sendMessage("VotingPluginProxy_" + server, messageData);
multiProxyRedis.publishEnvelope("VotingPluginProxy_" + server, envelope);
}
}
}
public abstract void setEncryptionHandler(EncryptionHandler encryptionHandler);
private void handleEnvelope(JsonEnvelope envelope) {
if (envelope == null) {
return;
}
public abstract void triggerVote(String player, String service, boolean realVote, boolean timeQueue, long queueTime,
BungeeMessageData text, String uuid);
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);
}
}
}
@@ -1,6 +1,6 @@
package com.bencodez.votingplugin.proxy.multiproxy;
import ninja.leaping.configurate.ConfigurationNode;
import org.spongepowered.configurate.ConfigurationNode;
public class MultiProxyServerSocketConfigurationVelocity implements MultiProxyServerSocketConfiguration {
@@ -10,8 +10,8 @@ public class MultiProxyServerSocketConfigurationVelocity implements MultiProxySe
public MultiProxyServerSocketConfigurationVelocity(String s, ConfigurationNode config) {
server = s;
host = config.getNode("Host").getString("0.0.0.0");
port = config.getNode("Port").getInt(1298);
host = config.node("Host").getString("0.0.0.0");
port = config.node("Port").getInt(1298);
}
@@ -1,69 +0,0 @@
package com.bencodez.votingplugin.proxy.velocity;
import java.io.File;
import com.bencodez.simpleapi.file.velocity.VelocityJSONFile;
import com.velocitypowered.api.proxy.Player;
public class NonVotedPlayersCache extends VelocityJSONFile {
private VotingPluginVelocity plugin;
public NonVotedPlayersCache(File file, VotingPluginVelocity plugin) {
super(file);
this.plugin = plugin;
}
public void addPlayer(Player player) {
if (!plugin.getVotingPluginProxy().getProxyMySQL().containsKeyQuery(player.getUniqueId().toString())) {
addPlayer(player.getUniqueId().toString(), player.getUsername());
}
}
public void addPlayer(String uuid, String playerName) {
getNode("NonVotedPlayers", playerName, "UUID").setValue(uuid);
getNode("NonVotedPlayers", playerName, "LastTime").setValue(System.currentTimeMillis());
save();
}
public void addPlayerCheck(String uuid, String playerName) {
if (!plugin.getVotingPluginProxy().getProxyMySQL().containsKeyQuery(uuid)) {
addPlayer(uuid, playerName);
}
}
public void check() {
for (String player : getKeys(getNode("NonVotedPlayers"))) {
long time = getNode("NonVotedPlayers", player, "LastTime").getLong(0);
if ((System.currentTimeMillis() - time) > 1000 * 60 * 60 * 24 * 5) {
remove(player);
} else {
String uuid = getNode("NonVotedPlayers", player, "UUID").getString("");
if (!uuid.isEmpty()) {
if (plugin.getVotingPluginProxy().getProxyMySQL().containsKeyQuery(uuid)) {
remove(player);
}
} else {
remove(player);
}
}
}
save();
}
public String playerExists(String playerName) {
if (!getNode("NonVotedPlayers", playerName).isEmpty()) {
return getNode("NonVotedPlayers", playerName, "UUID").getString("");
}
return "";
}
private void remove(String player) {
plugin.debug("Removing nonvotedplayer: " + player);
getNode("NonVotedPlayers", player).setValue(null);
save();
}
}
@@ -3,6 +3,7 @@ package com.bencodez.votingplugin.proxy.velocity;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -12,11 +13,11 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import com.bencodez.simpleapi.file.velocity.VelocityYMLFile;
import com.bencodez.votingplugin.proxy.VotingPluginProxyConfig;
import ninja.leaping.configurate.ConfigurationNode;
import org.spongepowered.configurate.ConfigurationNode;
public class Config extends VelocityYMLFile implements VotingPluginProxyConfig {
public class VelocityConfig extends VelocityYMLFile implements VotingPluginProxyConfig {
public Config(File file) {
public VelocityConfig(File file) {
super(file);
}
@@ -37,8 +38,30 @@ public class Config extends VelocityYMLFile implements VotingPluginProxyConfig {
}
@Override
public boolean getBroadcast() {
return getBoolean(getNode("Broadcast"), false);
public boolean getProxyBroadcastEnabled() {
return getBoolean(getNode("ProxyBroadcast", "Enabled"), false);
}
@Override
public String getProxyBroadcastScopeMode() {
return getString(getNode("ProxyBroadcast", "Scope", "Mode"), "ALL_SERVERS");
}
@Override
public List<String> getProxyBroadcastScopeServers() {
List<String> list = getStringList(getNode("ProxyBroadcast", "Scope", "Servers"), new ArrayList<>());
return list != null ? list : Collections.emptyList();
}
@Override
public String getProxyBroadcastOfflineMode() {
return getString(getNode("ProxyBroadcast", "OfflineMode"), "QUEUE");
}
@Override
public List<String> getProxyBroadcastOfflineForwardServers() {
List<String> list = getStringList(getNode("ProxyBroadcast", "OfflineForward", "Servers"), new ArrayList<>());
return list != null ? list : Collections.emptyList();
}
@Override
@@ -116,6 +139,11 @@ public class Config extends VelocityYMLFile implements VotingPluginProxyConfig {
return getInt(getNode("MultiProxyRedis", "Port"), 6379);
}
@Override
public int getMultiProxyRedisDbIndex() {
return getInt(getNode("MultiProxyRedis", "Db-Index"), 0);
}
@Override
public boolean getMultiProxyRedisUseExistingConnection() {
return getBoolean(getNode("MultiProxyRedis", "UseExistingConnection"), false);
@@ -134,7 +162,7 @@ public class Config extends VelocityYMLFile implements VotingPluginProxyConfig {
public List<String> getChildrenAsList(ConfigurationNode config) {
List<String> children = new ArrayList<>();
if (config != null) {
for (Map.Entry<Object, ? extends ConfigurationNode> entry : config.getChildrenMap().entrySet()) {
for (Map.Entry<Object, ? extends ConfigurationNode> entry : config.childrenMap().entrySet()) {
children.add(entry.getKey().toString());
}
}
@@ -205,6 +233,11 @@ public class Config extends VelocityYMLFile implements VotingPluginProxyConfig {
return getInt(getNode("Redis", "Port"), 6379);
}
@Override
public int getRedisDbIndex() {
return getInt(getNode("Redis", "Db-Index"), 0);
}
@Override
public String getRedisPrefix() {
return getString(getNode("Redis", "Prefix"), "");
@@ -253,8 +286,8 @@ public class Config extends VelocityYMLFile implements VotingPluginProxyConfig {
public Map<String, Object> configToMap(ConfigurationNode config) {
Map<String, Object> map = new HashMap<>();
if (config != null) {
for (Map.Entry<Object, ? extends ConfigurationNode> entry : config.getChildrenMap().entrySet()) {
map.put(entry.getKey().toString(), entry.getValue().getValue());
for (Map.Entry<Object, ? extends ConfigurationNode> entry : config.childrenMap().entrySet()) {
map.put(entry.getKey().toString(), entry.getValue().raw());
}
}
return map;
@@ -292,22 +325,22 @@ public class Config extends VelocityYMLFile implements VotingPluginProxyConfig {
@Override
public String getVotePartyBroadcast() {
return getString(getVotePartyNode().getNode("Broadcast"), "");
return getString(getVotePartyNode().node("Broadcast"), "");
}
@Override
public List<String> getVotePartyBungeeCommands() {
return getStringList(getVotePartyNode().getNode("BungeeCommands"), new ArrayList<>());
return getStringList(getVotePartyNode().node("BungeeCommands"), new ArrayList<>());
}
@Override
public boolean getVotePartyEnabled() {
return getBoolean(getVotePartyNode().getNode("Enabled"), false);
return getBoolean(getVotePartyNode().node("Enabled"), false);
}
@Override
public int getVotePartyIncreaseVotesRequired() {
return getInt(getVotePartyNode().getNode("IncreaseVotesRequired"), 0);
return getInt(getVotePartyNode().node("IncreaseVotesRequired"), 0);
}
public ConfigurationNode getVotePartyNode() {
@@ -316,17 +349,17 @@ public class Config extends VelocityYMLFile implements VotingPluginProxyConfig {
@Override
public boolean getVotePartySendToAllServers() {
return getBoolean(getVotePartyNode().getNode("SendToAllServers"), false);
return getBoolean(getVotePartyNode().node("SendToAllServers"), false);
}
@Override
public List<String> getVotePartyServersToSend() {
return getStringList(getVotePartyNode().getNode("ServersToSend"), new ArrayList<>());
return getStringList(getVotePartyNode().node("ServersToSend"), new ArrayList<>());
}
@Override
public int getVotePartyVotesRequired() {
return getInt(getVotePartyNode().getNode("VotesRequired"), 100);
return getInt(getVotePartyNode().node("VotesRequired"), 100);
}
@Override
@@ -395,11 +428,6 @@ public class Config extends VelocityYMLFile implements VotingPluginProxyConfig {
return getInt(getNode("WaitUntilVoteDelay", site, "VoteDelayMin"), 0);
}
@Override
public boolean getResetMilestonesMonthly() {
return getBoolean(getNode("ResetMilestonesMonthly"), false);
}
@Override
public boolean getVoteCacheUseMySQL() {
return getBoolean(getNode("VoteCache", "UseMySQL"), false);
@@ -415,4 +443,58 @@ public class Config extends VelocityYMLFile implements VotingPluginProxyConfig {
return getInt(getNode("TimeWeekOffSet"), 0);
}
}
@Override
public boolean getNonVotedCacheUseMySQL() {
return getBoolean(getNode("NonVotedCache", "UseMySQL"), false);
}
@Override
public boolean getNonVotedCacheUseMainMySQL() {
return getBoolean(getNode("NonVotedCache", "UseMainMySQL"), true);
}
@Override
public boolean getVoteLoggingEnabled() {
return getBoolean(getNode("VoteLogging", "Enabled"), false);
}
@Override
public int getVoteLoggingPurgeDays() {
return getInt(getNode("VoteLogging", "PurgeDays"), 30);
}
@Override
public boolean getVoteLoggingUseMainMySQL() {
return getBoolean(getNode("VoteLogging", "UseMainMySQL"), true);
}
private boolean isSection(ConfigurationNode node) {
return node != null && node.raw() instanceof Map;
}
@Override
public boolean hasDatabaseConfigured() {
// New-style: Database: { Host: ... }
ConfigurationNode db = getNode("Database");
if (isSection(db)) {
String host = getString(getNode("Database", "Host"), "");
return host != null && !host.isEmpty();
}
// Legacy-style: Host at root
String host = getString(getNode("Host"), "");
if (host != null && !host.isEmpty()) {
return true;
}
// Optional older style some setups used: MySQL.Host
ConfigurationNode mysql = getNode("MySQL");
if (isSection(mysql)) {
String mysqlHost = getString(getNode("MySQL", "Host"), "");
return mysqlHost != null && !mysqlHost.isEmpty();
}
return false;
}
}
@@ -0,0 +1,56 @@
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);
}
}
@@ -10,44 +10,46 @@ import com.bencodez.votingplugin.proxy.cache.DataNode;
import com.bencodez.votingplugin.proxy.cache.IVoteCache;
import com.bencodez.votingplugin.timequeue.VoteTimeQueue;
public class VoteCache extends VelocityJSONFile implements IVoteCache {
public class VelocityJsonVoteCache extends VelocityJSONFile implements IVoteCache {
public VoteCache(File file) {
public VelocityJsonVoteCache(File file) {
super(file);
}
@Override
public void addTimedVote(int num, VoteTimeQueue voteTimedQueue) {
getNode("TimedVoteCache", String.valueOf(num), "Name").setValue(voteTimedQueue.getName());
getNode("TimedVoteCache", String.valueOf(num), "Service").setValue(voteTimedQueue.getService());
getNode("TimedVoteCache", String.valueOf(num), "Time").setValue(voteTimedQueue.getTime());
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) {
getNode("VoteCache", server, String.valueOf(num), "Name").setValue(voteData.getPlayerName());
getNode("VoteCache", server, String.valueOf(num), "Service").setValue(voteData.getService());
getNode("VoteCache", server, String.valueOf(num), "UUID").setValue(voteData.getUuid());
getNode("VoteCache", server, String.valueOf(num), "Time").setValue(voteData.getTime());
getNode("VoteCache", server, String.valueOf(num), "Real").setValue(voteData.isRealVote());
getNode("VoteCache", server, String.valueOf(num), "Text").setValue(voteData.getText());
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) {
getNode("OnlineCache", player, String.valueOf(num), "Name").setValue(voteData.getPlayerName());
getNode("OnlineCache", player, String.valueOf(num), "Service").setValue(voteData.getService());
getNode("OnlineCache", player, String.valueOf(num), "UUID").setValue(voteData.getUuid());
getNode("OnlineCache", player, String.valueOf(num), "Time").setValue(voteData.getTime());
getNode("OnlineCache", player, String.valueOf(num), "Real").setValue(voteData.isRealVote());
getNode("OnlineCache", player, String.valueOf(num), "Text").setValue(voteData.getText());
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() {
getNode("VoteCache").setValue(null);
getNode("OnlineCache").setValue(null);
getNode("TimedVoteCache").setValue(null);
remove("VoteCache");
remove("OnlineCache");
remove("TimedVoteCache");
save();
}
@@ -108,27 +110,27 @@ public class VoteCache extends VelocityJSONFile implements IVoteCache {
@Override
public void setVotePartyCache(String server, int amount) {
getNode("VoteParty", "Cache", server).setValue(amount);
setPath(amount, "VoteParty", "Cache", server);
}
@Override
public void setVotePartyCurrentVotes(int amount) {
getNode("VoteParty", "CurrentVotes").setValue(amount);
setPath(amount, "VoteParty", "CurrentVotes");
}
@Override
public void setVotePartyInreaseVotesRequired(int amount) {
getNode("VoteParty", "IncreaseVotes").setValue(amount);
setPath(amount, "VoteParty", "IncreaseVotes");
}
@Override
public void removeOnlineVotes(String player) {
getNode("OnlineCache", player).setValue(null);
remove("OnlineCache", player);
}
@Override
public void removeServerVotes(String server) {
getNode("VoteCache", server).setValue(null);
remove("VoteCache", server);
}
@Override
@@ -149,7 +151,7 @@ public class VoteCache extends VelocityJSONFile implements IVoteCache {
}
String nodeUuid = uuidNode.asString();
if (nodeUuid != null && nodeUuid.equals(uuid)) {
getNode("VoteCache", server, num).setValue(null);
remove("VoteCache", server, num);
}
}
@@ -178,7 +180,7 @@ public class VoteCache extends VelocityJSONFile implements IVoteCache {
if (nodeUuid != null && nodeService != null && nodeTime != null && nodeUuid.equals(vote.getUuid())
&& nodeService.equals(vote.getService()) && nodeTime.longValue() == vote.getTime()) {
getNode("VoteCache", server, num).setValue(null);
remove("VoteCache", server, num);
}
}
}
@@ -211,9 +213,14 @@ public class VoteCache extends VelocityJSONFile implements IVoteCache {
if (nodeUuid != null && nodeService != null && nodeTime != null && nodeUuid.equals(vote.getUuid())
&& nodeService.equals(vote.getService()) && nodeTime.longValue() == vote.getTime()) {
getNode("OnlineCache", player, num).setValue(null);
remove("OnlineCache", player, num);
}
}
}
}
}
private void setPath(Object value, Object... path) {
set(path, value);
}
}
@@ -5,6 +5,7 @@ import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.SimpleCommand;
import net.kyori.adventure.text.Component;
import net.md_5.bungee.api.ChatColor;
public class VotingPluginVelocityCommand implements SimpleCommand {
private VotingPluginVelocity plugin;
@@ -21,14 +22,14 @@ public class VotingPluginVelocityCommand implements SimpleCommand {
String result = new VotingPluginProxyCommand(plugin.getVotingPluginProxy()).execute(args);
if (result != null) {
source.sendMessage(Component.text(result.replace("&", "§")));
source.sendMessage(Component.text(result.replace("&", "" + ChatColor.COLOR_CHAR)));
}
}
@Override
public boolean hasPermission(final Invocation invocation) {
return invocation.source().hasPermission("votingpluginbungee.admin");
return invocation.source().hasPermission("votingpluginproxy.admin");
}
public boolean isInt(String st) {
@@ -2,96 +2,384 @@ package com.bencodez.votingplugin.servicesites;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map.Entry;
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 {
@Getter
private HashMap<String, String> serviceSites = new HashMap<>();
private VotingPluginMain plugin;
/**
* 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;
loadFromGithub();
for (Entry<String, String> entry : serviceSites.entrySet()) {
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) {
for (Entry<String, String> entry : serviceSites.entrySet()) {
if (entry.getKey().equalsIgnoreCase(service)) {
return true;
}
}
return false;
}
public void loadFromGithub() {
try {
readFromWeb("https://github.com/BenCodez/VotingPlugin/wiki/Minecraft-Server-Lists");
} catch (IOException e) {
e.printStackTrace();
}
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) {
for (Entry<String, String> entry : serviceSites.entrySet()) {
if (entry.getKey().equalsIgnoreCase(service)) {
return entry.getValue();
}
if (service == null) {
return null;
}
return service;
return sitesRef.get().getOrDefault(service, service);
}
public String matchReverse(String service) {
for (Entry<String, String> entry : serviceSites.entrySet()) {
if (entry.getValue().equalsIgnoreCase(service)) {
return entry.getKey();
/**
* 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 service;
return value;
}
public void readFromWeb(String webURL) throws IOException {
serviceSites.clear();
URL url = new URL(webURL);
InputStream is = url.openStream();
try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
String line;
boolean rawFound = false;
while ((line = br.readLine()) != null) {
if (line.contains("<ul>")) {
rawFound = true;
} else if (!line.contains(" - ")) {
rawFound = false;
} else if (rawFound) {
String data = line.replaceAll("<li>", "").replaceAll("</li>", "");
String[] split = data.split(" - ");
if (split.length > 0) {
serviceSites.put(split[0], split[1]);
}
/**
* 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;
}
} catch (MalformedURLException e) {
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);
// e.printStackTrace();
// throw new MalformedURLException("URL is malformed!!");
} catch (IOException e) {
plugin.debug(e);
// e.printStackTrace();
// throw new IOException();
}
}
/**
* 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("&amp;", "&").replace("&lt;", "<").replace("&gt;", ">");
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;
}
}
}
@@ -1,20 +1,15 @@
package com.bencodez.votingplugin.specialrewards;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import org.bukkit.Bukkit;
import com.bencodez.advancedcore.api.rewards.RewardBuilder;
import com.bencodez.advancedcore.api.rewards.RewardOptions;
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.proxy.BungeeMessageData;
import com.bencodez.votingplugin.topvoter.TopVoter;
import com.bencodez.votingplugin.user.VotingPluginUser;
// TODO: Auto-generated Javadoc
@@ -29,236 +24,7 @@ public class SpecialRewards {
this.plugin = plugin;
}
public void bungeeAllSitesCheck(VotingPluginUser user, int numberOfVotes, int num) {
if (plugin.getBungeeSettings().isGiveExtraAllSitesRewards()) {
int numberOfSites = plugin.getVoteSitesEnabled().size();
if (numberOfVotes >= numberOfSites * 2) {
if (num > numberOfSites) {
if (num % numberOfSites == 0) {
// should give extra allsites?
if (user.checkAllVotes()) {
plugin.debug("Giving extra allsites reward from bungee");
giveAllSitesRewards(user, user.isOnline(), true);
}
}
}
}
}
}
public boolean checkAllSites(VotingPluginUser user, boolean forceBungee) {
boolean checkAllVotes = user.checkAllVotes();
if (checkAllVotes
&& (plugin.getConfigFile().isExtraAllSitesCheck() || plugin.getBungeeSettings().isUseBungeecoord())) {
int currentDay = LocalDateTime.now().getDayOfYear();
int day = user.getGottenAllSitesDay();
if (currentDay == day) {
checkAllVotes = false;
plugin.debug("Not giving allsites, already gotten today");
} else {
user.setGottenAllSitesDay(currentDay);
}
}
if (checkAllVotes) {
giveAllSitesRewards(user, user.isOnline(), forceBungee);
}
return checkAllVotes;
}
public boolean checkAlmostAllSites(VotingPluginUser user, boolean forceBungee) {
boolean checkAllVotes = user.checkAlmostAllVotes();
plugin.extraDebug("Checking almostallsites reward: " + checkAllVotes + "/" + user.getSitesNotVotedOn());
if (checkAllVotes) {
int currentDay = LocalDateTime.now().getDayOfYear();
int day = user.getGottenAlmostAllSitesDay();
if (currentDay == day) {
checkAllVotes = false;
plugin.debug("Not giving almostallsites, already gotten today");
} else {
user.setGottenAlmostAllSitesDay(currentDay);
giveAlmostAllSitesRewards(user, user.isOnline(), forceBungee);
}
}
return checkAllVotes;
}
public boolean checkCumualativeVotes(VotingPluginUser user, BungeeMessageData bungeeMessageData,
boolean forceBungee) {
boolean gotCumulativeAny = false;
Set<String> votes = plugin.getSpecialRewardsConfig().getCumulativeVotes();
for (String vote : votes) {
if (MessageAPI.isInt(vote)) {
int votesRequired = Integer.parseInt(vote);
if (votesRequired != 0) {
if (plugin.getSpecialRewardsConfig().getCumulativeRewardEnabled(votesRequired)
&& plugin.getRewardHandler().hasRewards(plugin.getSpecialRewardsConfig().getData(),
plugin.getSpecialRewardsConfig().getCumulativeRewardsPath(votesRequired))) {
boolean gotCumulative = false;
int total = 0;
boolean useBungeeTotalNum = bungeeMessageData != null;
String totalToUse = plugin.getSpecialRewardsConfig().getCumulativeVotesTotal(votesRequired);
TopVoter top = TopVoter.getTopVoter(totalToUse);
if (!useBungeeTotalNum) {
total = user.getTotal(top);
} else {
switch (top) {
case AllTime:
total = bungeeMessageData.getAllTimeTotal();
break;
case Daily:
total = bungeeMessageData.getDailyTotal();
break;
case Monthly:
if (plugin.getConfigFile().isUseMonthDateTotalsAsPrimaryTotal()) {
total = bungeeMessageData.getDateMonthTotal();
} else {
total = bungeeMessageData.getMonthTotal();
}
break;
case Weekly:
total = bungeeMessageData.getWeeklyTotal();
break;
default:
break;
}
}
List<Integer> blackList = plugin.getSpecialRewardsConfig()
.getCumulativeBlackList(votesRequired);
boolean blackListed = false;
for (Integer num : blackList) {
if (num.intValue() == total) {
blackListed = true;
plugin.extraDebug("Not giving cumulative " + votesRequired + " to "
+ user.getPlayerName() + " due to blacklist");
}
}
if (plugin.getSpecialRewardsConfig().getCumulativeRecurring(votesRequired)) {
if (total != 0 && total >= votesRequired) {
if ((total % votesRequired) == 0 && !blackListed) {
gotCumulative = true;
}
}
} else {
if (total == votesRequired) {
gotCumulative = true;
}
}
if (gotCumulative) {
if (!plugin.getSpecialRewardsConfig().isOnlyOneCumulative() || !gotCumulativeAny) {
gotCumulativeAny = true;
plugin.debug(user.getPlayerName() + " got cumulative " + votesRequired + ", total: "
+ top.toString() + ", current total: " + total);
giveCumulativeVoteReward(user, user.isOnline(), votesRequired, forceBungee);
} else {
plugin.debug("Already got one cumulative");
}
} else {
plugin.devDebug(user.getPlayerName() + " not able to get cumulative " + votesRequired
+ ", total required: " + top.toString() + ", current player total: " + total);
}
}
}
} else {
plugin.debug("Invalid cumulative number: " + vote);
}
}
return gotCumulativeAny;
}
public boolean checkFirstVote(VotingPluginUser user, boolean forceBungee) {
if (plugin.getRewardHandler().hasRewards(plugin.getSpecialRewardsConfig().getData(),
plugin.getSpecialRewardsConfig().getFirstVoteRewardsPath())) {
if (!user.hasGottenFirstVote()) {
giveFirstVoteRewards(user, user.isOnline(), forceBungee);
return true;
}
}
return false;
}
public boolean checkFirstVoteToday(VotingPluginUser user, boolean forceBungee) {
if (plugin.getRewardHandler().hasRewards(plugin.getSpecialRewardsConfig().getData(),
plugin.getSpecialRewardsConfig().getFirstVoteRewardsPath())) {
if (!user.hasGottenFirstVoteToday()) {
giveFirstVoteTodayRewards(user, user.isOnline(), forceBungee);
return true;
}
}
return false;
}
public boolean checkMilestone(VotingPluginUser user, BungeeMessageData bungeeMessageData, boolean forceBungee) {
int milestoneCount = user.getMilestoneCount();
if (bungeeMessageData != null) {
try {
milestoneCount = bungeeMessageData.getMilestoneCount();
} catch (Exception e) {
e.printStackTrace();
milestoneCount = user.getMilestoneCount();
}
}
if (plugin.getConfigFile().isPreventRepeatMilestones()) {
ArrayList<Integer> nums = new ArrayList<>();
for (String str : plugin.getSpecialRewardsConfig().getMilestoneVotes()) {
try {
nums.add(Integer.parseInt(str));
} catch (Exception e) {
plugin.getLogger().warning("Failed to get number from " + str);
}
}
for (int num : nums) {
if (milestoneCount > num) {
if (!user.hasGottenMilestone(num)) {
plugin.getLogger()
.info("Milestone " + num + " for " + user.getPlayerName()
+ " not already given when it should be, Current AllTimeTotal: "
+ user.getTotal(TopVoter.AllTime) + ", Current MileStoneCount: "
+ user.getMilestoneCount());
user.setHasGotteMilestone(num, true);
}
}
}
}
boolean gotMilestone = false;
Set<String> votes = plugin.getSpecialRewardsConfig().getMilestoneVotes();
for (String vote : votes) {
if (MessageAPI.isInt(vote)) {
int votesRequired = Integer.parseInt(vote);
if (votesRequired != 0) {
if (plugin.getSpecialRewardsConfig().getMilestoneRewardEnabled(votesRequired)
&& plugin.getRewardHandler().hasRewards(plugin.getSpecialRewardsConfig().getData(),
plugin.getSpecialRewardsConfig().getMilestoneRewardsPath(votesRequired))) {
int userVotesTotal = milestoneCount;
if (userVotesTotal >= votesRequired && !user.hasGottenMilestone(votesRequired)) {
giveMilestoneVoteReward(user, user.isOnline(), votesRequired, forceBungee);
user.setHasGotteMilestone(votesRequired, true);
plugin.debug(user.getPlayerName() + " got milestone " + votesRequired);
}
} else {
plugin.debug("Milestone " + votesRequired + " not enabled or has no rewards");
}
}
} else {
plugin.debug("Invalid milestone number: " + vote);
}
}
return gotMilestone;
}
public boolean checkVoteStreak(VotingPluginUser user, String type, boolean forceBungee) {
public boolean checkVoteStreak(UUID voteUUID, VotingPluginUser user, String type, boolean forceBungee) {
boolean gotReward = false;
Set<String> streaks = plugin.getSpecialRewardsConfig().getVoteStreakVotes(type);
@@ -288,15 +54,16 @@ public class SpecialRewards {
}
if (!multiple) {
if (curStreak == streakRequired) {
giveVoteStreakReward(user, user.isOnline(), type, "" + streakRequired, curStreak,
forceBungee);
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(user, user.isOnline(), type, streak, curStreak, forceBungee);
giveVoteStreakReward(voteUUID, user, user.isOnline(), type, streak, curStreak,
forceBungee);
gotReward = true;
plugin.debug(
user.getPlayerName() + " got VoteStreak " + streakRequired + "* for " + type);
@@ -312,85 +79,10 @@ public class SpecialRewards {
}
public void giveAllSitesRewards(VotingPluginUser user, boolean online, boolean forceBungee) {
PlayerSpecialRewardEvent event = new PlayerSpecialRewardEvent(user, SpecialRewardType.ALLSITE);
Bukkit.getPluginManager().callEvent(event);
if (event.isCancelled()) {
return;
}
plugin.getRewardHandler().giveReward(user, plugin.getSpecialRewardsConfig().getData(),
plugin.getSpecialRewardsConfig().getAllSitesRewardPath(),
new RewardOptions().setServer(forceBungee).setOnline(online));
}
public void giveAlmostAllSitesRewards(VotingPluginUser user, boolean online, boolean forceBungee) {
PlayerSpecialRewardEvent event = new PlayerSpecialRewardEvent(user, SpecialRewardType.ALMOSTALLSITES);
Bukkit.getPluginManager().callEvent(event);
if (event.isCancelled()) {
return;
}
plugin.getRewardHandler().giveReward(user, plugin.getSpecialRewardsConfig().getData(),
plugin.getSpecialRewardsConfig().getAlmostAllSitesRewardPath(),
new RewardOptions().setServer(forceBungee).setOnline(online));
}
public void giveCumulativeVoteReward(VotingPluginUser user, boolean online, int cumulative, boolean forceBungee) {
public void giveVoteStreakReward(UUID voteUUID, VotingPluginUser user, boolean online, String type, String string,
int votes, boolean forceBungee) {
PlayerSpecialRewardEvent event = new PlayerSpecialRewardEvent(user,
SpecialRewardType.CUMMULATIVE.setAmount(cumulative));
Bukkit.getPluginManager().callEvent(event);
if (event.isCancelled()) {
return;
}
new RewardBuilder(plugin.getSpecialRewardsConfig().getData(),
plugin.getSpecialRewardsConfig().getCumulativeRewardsPath(cumulative)).setServer(forceBungee)
.setOnline(online).withPlaceHolder("Cumulative", "" + cumulative).send(user);
}
public void giveFirstVoteRewards(VotingPluginUser user, boolean online, boolean forceBungee) {
PlayerSpecialRewardEvent event = new PlayerSpecialRewardEvent(user, SpecialRewardType.FIRSTVOTE);
Bukkit.getPluginManager().callEvent(event);
if (event.isCancelled()) {
return;
}
plugin.getRewardHandler().giveReward(user, plugin.getSpecialRewardsConfig().getData(),
plugin.getSpecialRewardsConfig().getFirstVoteRewardsPath(),
new RewardOptions().setServer(forceBungee).setOnline(online));
}
public void giveFirstVoteTodayRewards(VotingPluginUser user, boolean online, boolean forceBungee) {
PlayerSpecialRewardEvent event = new PlayerSpecialRewardEvent(user, SpecialRewardType.FIRSTVOTETODAY);
Bukkit.getPluginManager().callEvent(event);
if (event.isCancelled()) {
return;
}
plugin.getRewardHandler().giveReward(user, plugin.getSpecialRewardsConfig().getData(),
plugin.getSpecialRewardsConfig().getFirstVoteTodayRewardsPath(),
new RewardOptions().setServer(forceBungee).setOnline(online));
}
public void giveMilestoneVoteReward(VotingPluginUser user, boolean online, int milestone, boolean forceBungee) {
PlayerSpecialRewardEvent event = new PlayerSpecialRewardEvent(user,
SpecialRewardType.MILESTONE.setAmount(milestone));
Bukkit.getPluginManager().callEvent(event);
if (event.isCancelled()) {
return;
}
new RewardBuilder(plugin.getSpecialRewardsConfig().getData(),
plugin.getSpecialRewardsConfig().getMilestoneRewardsPath(milestone)).setOnline(online)
.withPlaceHolder("Milestone", "" + milestone).setServer(forceBungee).send(user);
}
public void giveVoteStreakReward(VotingPluginUser user, boolean online, String type, String string, int votes,
boolean forceBungee) {
PlayerSpecialRewardEvent event = new PlayerSpecialRewardEvent(user,
SpecialRewardType.VOTESTREAK.setType(type).setAmount(votes));
SpecialRewardType.VOTESTREAK.setType(type).setAmount(votes), voteUUID);
Bukkit.getPluginManager().callEvent(event);
if (event.isCancelled()) {
Loaded 100 of 182 files, more files were not shown because too many files have changed in this diff. Show more