Compare commits

...
219 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
BenCodez 17574f6c50 Bump version number to 6.19.1 2025-11-09 18:46:27 -06:00
BenCodez 60377deb44 Improve configuration comments for MySQL settings 2025-11-09 16:00:35 -06:00
Ben 34614e13fa Merge pull request #1469 from BoredManCodes/patch-1
Removed color codes from DiscordSRV messages
2025-11-03 16:17:07 -06:00
Trent Buckley bc98630388 Removed color codes from config yml 2025-11-04 09:12:04 +11:00
Trent Buckley 302cc980d8 Remove color codes from DiscordSRV messages 2025-11-04 09:10:46 +11:00
BenCodez 4342fc5458 Implement monthly reset for milestone data in proxy
Should reduce rare cases with counts being out of sync
2025-11-01 19:14:19 -06:00
BenCodez 90b981af7b Improve VoteCache null checks 2025-10-28 18:43:37 -06:00
BenCodez 32879956ec Refactor PlayerVoteListener to resolve Bedrock player names 2025-10-18 19:57:57 -06:00
Ben 2b166c7760 Update Maven CI workflow with permissions and comments 2025-10-13 08:10:14 -06:00
Ben 9848172a49 Update Maven workflow for JDK setup and build 2025-10-12 20:53:22 -06:00
BenCodez 9775d06ba9 Bump version number to 1.21.10-R0.1-SNAPSHOT 2025-10-12 20:51:10 -06:00
BenCodez 9584bc0e66 Prevent broadcasting for hidden vote sites 2025-10-10 21:05:25 -06:00
Ben 00ec202140 Merge pull request #1465 from BenCodez/dependabot/maven/VotingPlugin/org.apache.maven.plugins-maven-javadoc-plugin-3.12.0
Bump org.apache.maven.plugins:maven-javadoc-plugin from 3.11.3 to 3.12.0 in /VotingPlugin
2025-10-04 20:45:47 -06:00
Ben bd57f003dd Merge pull request #1463 from BenCodez/dependabot/maven/VotingPlugin/org.apache.maven.plugins-maven-shade-plugin-3.6.1
Bump org.apache.maven.plugins:maven-shade-plugin from 3.6.0 to 3.6.1 in /VotingPlugin
2025-10-04 20:45:39 -06:00
Ben 23788be684 Merge pull request #1462 from BenCodez/dependabot/maven/VotingPlugin/com.discordsrv-discordsrv-1.30.1
Bump com.discordsrv:discordsrv from 1.30.0 to 1.30.1 in /VotingPlugin
2025-10-04 20:45:27 -06:00
BenCodez 9b33b1cde7 Bump version numbers to 1.21.9-R0.1-SNAPSHOT and 1.21-R0.4 2025-10-04 20:32:53 -06:00
BenCodez 02fb353dac Fixed proxy votecache not using existing connection unless debug is on 2025-09-28 13:42:20 -06:00
dependabot[bot] a4c470887f Bump org.apache.maven.plugins:maven-javadoc-plugin in /VotingPlugin
Bumps [org.apache.maven.plugins:maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin) from 3.11.3 to 3.12.0.
- [Release notes](https://github.com/apache/maven-javadoc-plugin/releases)
- [Commits](https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.11.3...maven-javadoc-plugin-3.12.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-22 06:09:54 +00:00
dependabot[bot] e844a1eb63 Bump org.apache.maven.plugins:maven-shade-plugin in /VotingPlugin
Bumps [org.apache.maven.plugins:maven-shade-plugin](https://github.com/apache/maven-shade-plugin) from 3.6.0 to 3.6.1.
- [Release notes](https://github.com/apache/maven-shade-plugin/releases)
- [Commits](https://github.com/apache/maven-shade-plugin/compare/maven-shade-plugin-3.6.0...v3.6.1)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-shade-plugin
  dependency-version: 3.6.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-15 06:38:19 +00:00
dependabot[bot] 1787595ea2 Bump com.discordsrv:discordsrv from 1.30.0 to 1.30.1 in /VotingPlugin
Bumps com.discordsrv:discordsrv from 1.30.0 to 1.30.1.

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-15 06:28:59 +00:00
BenCodez 79bea20039 Refactor vote caching: optimize cache handling 2025-09-14 14:37:53 -06:00
BenCodez 63905004b6 Remove commented-out code in VotingPluginProxyVoteCacheTest 2025-09-14 13:25:04 -06:00
BenCodez f326e2cbbb Refactor vote caching: streamline methods and improve performance 2025-09-11 19:04:17 -06:00
BenCodez 9f6edca531 Refactor proxy vote caching: remove VoteCache class 2025-09-11 17:45:16 -06:00
BenCodez 3e277a4a76 Update pom.xml to disable minimizeJar and add resource transformers 2025-09-06 17:58:06 -06:00
BenCodez 3b33e5e4a7 Adjust time calculation in PlaceHolders to account for timeWeekOffSet 2025-09-06 09:54:34 -06:00
BenCodez b5f35038fa Add support for timeWeekOffSet in Bungee and Velocity configurations 2025-09-06 09:52:02 -06:00
BenCodez b937d30213 Update shadedPattern in pom.xml to use simpleapi for Hikari 2025-09-05 15:52:58 -06:00
BenCodez d43cf53276 Remove unused documentation comments from vote cache table classes 2025-09-02 20:46:24 -06:00
BenCodez 6a7b677322 Fix casing for MySQL configuration in vote cache settings 2025-09-02 20:28:02 -06:00
BenCodez b309537f97 Add support for using mysql to handle proxy vote cache
Requires more testing
2025-09-02 20:25:43 -06:00
BenCodez 7e34dd89b8 Refactor player name retrieval to use proper casing from user manager 2025-08-31 14:30:31 -06:00
BenCodez 5dcc0a1b33 Improve player name retrieval to match casing from UUID cache 2025-08-31 14:04:12 -06:00
BenCodez 36f8b3fbe8 Update version number to 6.19.1-SNAPSHOT 2025-08-24 16:03:33 -06:00
BenCodez df298fd66c Temp revert version number for maven deploy 2025-08-24 16:00:05 -06:00
BenCodez c93aff6c20 Update version number to 6.19.1-SNAPSHOT 2025-08-24 15:57:21 -06:00
BenCodez b8acfe20ec Update version number to 6.19 2025-08-24 15:48:12 -06:00
BenCodez 110aaf096e Refactor player name retrieval to match casing from UUID cache 2025-08-24 13:30:53 -06:00
Ben 171975c3d3 Merge pull request #1457 from BenCodez/dependabot/maven/VotingPlugin/org.apache.maven.plugins-maven-javadoc-plugin-3.11.3
Bump org.apache.maven.plugins:maven-javadoc-plugin from 3.11.2 to 3.11.3 in /VotingPlugin
2025-08-18 18:47:52 -06:00
dependabot[bot] ac96d277c5 Bump org.apache.maven.plugins:maven-javadoc-plugin in /VotingPlugin
Bumps [org.apache.maven.plugins:maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin) from 3.11.2 to 3.11.3.
- [Release notes](https://github.com/apache/maven-javadoc-plugin/releases)
- [Commits](https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.11.2...maven-javadoc-plugin-3.11.3)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-javadoc-plugin
  dependency-version: 3.11.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-18 09:18:02 +00:00
BenCodez b1ad952950 Add debug message for DiscordSRV leaderboard updates 2025-08-17 13:37:18 -06:00
BenCodez ca30bcb183 Fix clearing discord message id with command 2025-08-15 16:56:13 -06:00
BenCodez eecd975731 Add debug logging and minor changes for discord top voters 2025-08-13 15:52:53 -06:00
BenCodez aa55b10e50 Implement delay handling for global message sending on PLUGINMESSAGING
Added to handle cases where too many pluginmessages causes messages to
be skipped, subject to change
2025-08-10 14:16:08 -06:00
BenCodez 64f59f8195 Update WaitForUserOnline setting to true for consistent reward behavior 2025-08-10 13:09:43 -06:00
BenCodez a51e041430 Add command to clear Discord message ID for top voter
This will force a new discord message to be sent
2025-08-10 12:19:42 -06:00
BenCodez b39f768a46 Add support for multiple top voter types in DiscordSRV integration 2025-08-06 19:33:44 -06:00
BenCodez e98bc49add Parse placeholders on discord message reward 2025-08-05 17:00:00 -06:00
BenCodez 8b2209aa59 Attempt fixing player name casing in offline while player is offline 2025-08-03 15:02:21 -06:00
BenCodez 58a0e85d00 Prevent redundant updates to last vote time for a specific vote site 2025-08-03 13:31:53 -06:00
BenCodez 3febfa4056 Update vote times in all possible cases 2025-08-03 13:31:28 -06:00
BenCodez e5668c862a Fix player name casing in offline mode Bungee and Velocity proxies
Will attempt to correct while player is online
2025-08-01 16:26:54 -06:00
BenCodez fd4c6a3954 Restore bungeeVoteParty data on all methods on startup 2025-08-01 16:16:14 -06:00
BenCodez 9e9472680a Remove unused command aliases from plugin configuration 2025-07-31 16:55:56 -06:00
Ben cbecd7be30 Merge pull request #1452 from BenCodez/dependabot/maven/VotingPlugin/com.discordsrv-discordsrv-1.30.0
Bump com.discordsrv:discordsrv from 1.29.0 to 1.30.0 in /VotingPlugin
2025-07-28 16:26:03 -06:00
dependabot[bot] fcb1af77cf Bump com.discordsrv:discordsrv from 1.29.0 to 1.30.0 in /VotingPlugin
Bumps com.discordsrv:discordsrv from 1.29.0 to 1.30.0.

---
updated-dependencies:
- dependency-name: com.discordsrv:discordsrv
  dependency-version: 1.30.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-28 08:27:53 +00:00
BenCodez 5d9b5b7b6c Add configurable titles and rank display for DiscordSRV top voters 2025-07-26 20:26:04 -06:00
BenCodez d26d61a6f0 Fix formatting of vote count display in Discord embed 2025-07-24 15:55:15 -06:00
BenCodez 3f885aa358 Check DiscordSRV status on initialization 2025-07-22 18:45:19 -06:00
Ben d088f2e91a Merge pull request #1449 from BenCodez/dependabot/maven/VotingPlugin/com.discordsrv-discordsrv-1.29.0
Bump com.discordsrv:discordsrv from 1.26.0 to 1.29.0 in /VotingPlugin
2025-07-21 17:27:56 -06:00
dependabot[bot] a536c53dd9 Bump com.discordsrv:discordsrv from 1.26.0 to 1.29.0 in /VotingPlugin
Bumps com.discordsrv:discordsrv from 1.26.0 to 1.29.0.

---
updated-dependencies:
- dependency-name: com.discordsrv:discordsrv
  dependency-version: 1.29.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-21 08:06:43 +00:00
Ben 5d2d6f0763 Merge pull request #1440 from BenCodez/dependabot/maven/VotingPlugin/net.md-5-bungeecord-api-1.21-R0.3
Bump net.md-5:bungeecord-api from 1.21-R0.2 to 1.21-R0.3 in /VotingPlugin
2025-07-20 20:26:50 -06:00
BenCodez 0848ed83c2 Add DiscordSRV integration for top voter updates and injected reward 2025-07-20 16:08:08 -06:00
ben 1771fe0889 Update to 1.21.8 2025-07-19 21:23:35 -06:00
ben ce5b197e8b Don't always close inventory on VoteURL 2025-07-19 15:16:43 -06:00
ben 15489d9c64 Modify how slots are set in VoteURL GUI 2025-07-18 19:37:42 -06:00
ben 758632b5a5 Fix redis messages showing up in logs without debug enabled 2025-07-17 16:05:52 -06:00
ben 3dc45b6a63 Add extra items to VoteURL first 2025-07-15 19:02:55 -06:00
ben 26c9ad98bc Change MYSQL method to use MYSQL to communicate between servers
Works like PLUGINMESSAGING, SOCKETS, REDIS, or MQTT
2025-07-13 20:28:59 -06:00
BenCodez ee2531af1e Modify shutdown order 2025-07-06 16:16:16 -06:00
BenCodez 461e5f8413 Cancel more timers on velocity shutdown 2025-07-05 20:38:34 -06:00
BenCodez 1c5acbd1a1 Improve resource cleanup in VotingPluginProxy shutdown method
- Added socketHandler connection closure
- Added redisHandler resource cleanup
- Shutdown globalDataHandler if initialized
- Ensured proper cleanup of all handlers
- Maintained existing bungeeTimeChecker shutdown logic
- Set `enabled` to false after cleanup
2025-07-05 14:48:17 -06:00
BenCodez e855c01854 Stop timer on velocity shutdown 2025-07-05 08:57:30 -06:00
BenCodez aeec7d9d17 Shutdown bungeeTimeChecker during proxy disable process.
- Added `bungeeTimeChecker.shutdown()` in `VotingPluginProxy`.
- Ensures proper cleanup of `bungeeTimeChecker`.
- Prevents potential resource leaks during proxy shutdown.
- Maintains consistency in shutdown logic.
- Improves reliability of proxy disable functionality.
- Minor code enhancement for better resource management.
2025-07-04 15:46:30 -06:00
BenCodez 4861c041e4 Add ResetMilestonesMonthly to proxy 2025-07-01 08:58:23 -06:00
BenCodez d1c6752240 Update version number to 6.18.8-SNAPSHOT 2025-06-30 18:48:11 -06:00
BenCodez caa7058363 Update version number to 6.18.7 2025-06-30 18:41:45 -06:00
BenCodez b67f6a509c Fix waituntilvotedelay on proxy with better handling 2025-06-30 16:53:00 -06:00
BenCodez 0edc09dc80 Update to 1.21.7 2025-06-30 16:14:27 -06:00
BenCodez 19e597e094 Remove version check on startup 2025-06-30 15:53:54 -06:00
BenCodez bfc838bfaa Add wait until vote delay feature on proxy
- Implemented vote delay checks in `VotingPluginProxy`.
- Added methods to handle vote delay logic in `Config` classes.
- Updated `VotingPluginProxyConfig` with new vote delay methods.
- Extended `bungeeconfig.yml` with `WaitUntilVoteDelay` settings.
- Improved vote handling to skip votes if delay conditions are unmet.
- Enhanced logging for skipped votes due to delay.
2025-06-29 15:57:32 -06:00
BenCodez 9cda17e89e Update version number to 6.18.7-SNAPSHOT 2025-06-22 15:50:59 -06:00
BenCodez 54a573a445 Update version number to 6.18.6 2025-06-22 15:32:45 -06:00
BenCodez 5432b2a0d3 Update to 1.21.6 2025-06-22 13:03:30 -06:00
Ben 13d3a614ed Merge pull request #1441 from Kawi16/master
Update AdvancedCore version
2025-06-20 15:49:55 -06:00
Alessandro f9641e158d Update plugin.yml to include NLogin 2025-06-20 06:24:14 +00:00
Alessandro a0c1f64aac Update config to include NLogin 2025-06-20 06:23:44 +00:00
BenCodez 1f88cb0261 Improve vote site generation by replacing spaces with underscores. 2025-06-19 16:03:05 -06:00
dependabot[bot] 71ccf6a7b0 Bump net.md-5:bungeecord-api in /VotingPlugin
Bumps [net.md-5:bungeecord-api](https://github.com/SpigotMC/BungeeCord) from 1.21-R0.2 to 1.21-R0.3.
- [Commits](https://github.com/SpigotMC/BungeeCord/commits)

---
updated-dependencies:
- dependency-name: net.md-5:bungeecord-api
  dependency-version: 1.21-R0.3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-16 06:57:36 +00:00
BenCodez 47a42d2bc8 Add configuration option to enable /av javascript command
- Introduced `EnableJavascriptCommand` in `Config.yml`
- Default value for `EnableJavascriptCommand` set to `false`
2025-06-03 20:04:12 -06:00
BenCodez ac41787c1e Simplify internal api for mysql on proxy 2025-05-26 20:05:35 -06:00
BenCodez ed4e3968da Add requiresPlayerOnline check to improve vote caching logic.
- Added `requiresPlayerOnline` method to `BungeeMethod` enum.
- Updated vote caching logic to use `requiresPlayerOnline` check.
- Ensured caching occurs only when required by the method.
- Improved clarity of conditions in `VotingPluginProxy`.
- Enhanced flexibility for different `BungeeMethod` implementations.
2025-05-26 18:31:54 -06:00
BenCodez 09da81cc4e Add MQTT proxy method, internal rewrites to cross server communication 2025-05-25 17:06:23 -06:00
BenCodez fd35c43c16 Update version to 6.18.6-SNAPSHOT 2025-05-23 20:54:02 -06:00
BenCodez 8060f594c3 Add filters to exclude specific META-INF files in Maven shade plugin 2025-05-23 20:37:19 -06:00
BenCodez 39aa7062a5 Update version number to 6.18.5 2025-05-23 20:18:56 -06:00
BenCodez 7ff7fbb7cb Update to AdvancedCore changes 2025-05-17 21:29:49 -06:00
BenCodez 231817314b Modify handling of nonvoted players to not cache in offline mode
- change package name of multi-proxy
2025-05-13 17:59:07 -06:00
BenCodez a20f10d978 Use mysql/sqlite from simpleapi 2025-05-13 12:58:55 -06:00
BenCodez 997beaae28 Modify handling of offline reward checking in offline mode 2025-05-11 21:07:55 -06:00
BenCodez 9acc5861b7 Modified handling of placeholder cache for offline mode 2025-05-02 11:30:17 -06:00
BenCodez f299847d46 Modify handling of caching for offline mode 2025-05-02 08:23:45 -06:00
BenCodez 87ff48366c Modofy handling of getting VotingPluginUser to account for offline mode 2025-04-26 12:26:14 -06:00
BenCodez 3dde76dcde Fix voteparty velocity broadcast 2025-04-22 14:08:42 -06:00
BenCodez 237713a799 Fix velocity voteparty broadcast not being colorized 2025-04-22 13:35:16 -06:00
BenCodez 23141d4ec1 Fix handling of allowunjoined on backend servers 2025-04-21 09:48:48 -06:00
Ben 2cfbaf4de8 Merge pull request #1434 from BenCodez/dependabot/maven/VotingPlugin/org.junit.jupiter-junit-jupiter-engine-5.12.2
Bump org.junit.jupiter:junit-jupiter-engine from 5.12.1 to 5.12.2 in /VotingPlugin
2025-04-14 12:30:47 -06:00
dependabot[bot] c31b83fcd3 Bump org.junit.jupiter:junit-jupiter-engine in /VotingPlugin
Bumps [org.junit.jupiter:junit-jupiter-engine](https://github.com/junit-team/junit5) from 5.12.1 to 5.12.2.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.12.1...r5.12.2)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-engine
  dependency-version: 5.12.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-14 06:07:25 +00:00
BenCodez 4cfd887c33 Update version number to 6.18.5-SNAPSHOT 2025-04-13 15:52:53 -06:00
195 changed files with 26697 additions and 7674 deletions

No files matched your search

+41
View File
@@ -0,0 +1,41 @@
# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support documentation.
name: Java CI with Maven
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
permissions:
contents: write # required for dependency submission
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: maven
- name: Build with Maven
run: mvn -B -f VotingPlugin/pom.xml package
# Only submit dependency snapshot on push (not on pull_request)
- name: Submit Dependency Snapshot
if: github.event_name == 'push'
uses: advanced-security/maven-dependency-submission-action@v5
with:
directory: VotingPlugin # path to pom.xml
# optional: maven-args: "-DskipTests"
+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.
+666 -607
View File
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
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;
@@ -28,6 +29,7 @@ import com.bencodez.votingplugin.user.VotingPluginUser;
public class AdminVoteTopPoints extends GUIHandler {
@SuppressWarnings("unused")
private int page;
private VotingPluginMain plugin;
private VotingPluginUser user;
@@ -62,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())
@@ -186,8 +181,26 @@ public class VoteURL extends GUIHandler {
@Override
public void onChest(Player player) {
// normal GUI
BInventory inv = new BInventory(plugin.getGui().getChestVoteURLName());
if (!plugin.getConfigFile().isAlwaysCloseInventory()) {
inv.dontClose();
}
for (final String str : plugin.getGui().getChestVoteURLExtraItems()) {
inv.addButton(new BInventoryButton(new ItemBuilder(plugin.getGui().getChestVoteURLExtraItemsItem(str))) {
@Override
public void onClick(ClickEvent clickEvent) {
new RewardBuilder(plugin.getGui().getData(),
"CHEST.VoteURL.ExtraItems." + str + "." + clickEvent.getButton().getLastRewardsPath(player))
.setGiveOffline(false).send(clickEvent.getPlayer());
}
});
}
if (plugin.getGui().isChestVoteURLViewAllUrlsButtonEnabled()) {
ItemBuilder builderAll = getItemAll();
@@ -195,6 +208,7 @@ public class VoteURL extends GUIHandler {
@Override
public void onClick(ClickEvent event) {
event.closeInventory();
VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(event.getPlayer());
json = true;
if (event.getClick().equals(ClickType.SHIFT_LEFT)
@@ -213,11 +227,20 @@ 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);
if (startSlot >= 0) {
if (inv.isSlotTaken(startSlot)) {
boolean found = false;
while (!found) {
startSlot++;
if (!inv.isSlotTaken(startSlot)) {
found = true;
}
}
}
builder.setSlot(startSlot);
startSlot++;
}
@@ -226,6 +249,7 @@ public class VoteURL extends GUIHandler {
@Override
public void onClick(ClickEvent event) {
event.closeInventory();
Player player = event.getPlayer();
if (player != null) {
VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(player);
@@ -252,21 +276,6 @@ public class VoteURL extends GUIHandler {
}
}
for (
final String str : plugin.getGui().getChestVoteURLExtraItems()) {
inv.addButton(new BInventoryButton(new ItemBuilder(plugin.getGui().getChestVoteURLExtraItemsItem(str))) {
@Override
public void onClick(ClickEvent clickEvent) {
new RewardBuilder(plugin.getGui().getData(),
"CHEST.VoteURL.ExtraItems." + str + "." + clickEvent.getButton().getLastRewardsPath(player))
.setGiveOffline(false).send(clickEvent.getPlayer());
}
});
}
if (plugin.getGui().isChestVoteURLBackButton()) {
inv.addButton(plugin.getCommandLoader().getBackButton(user));
}
@@ -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,31 +25,55 @@ 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 = "";
@ConfigDataString(path = "MQTT.BrokerURL")
@Getter
private String mqttBrokerURL = "tcp://localhost:1883";
@ConfigDataString(path = "MQTT.Username")
@Getter
private String mqttUsername = "";
@ConfigDataString(path = "MQTT.Password")
@Getter
private String mqttPassword = "";
@ConfigDataString(path = "MQTT.Prefix")
@Getter
private String mqttPrefix = "";
@ConfigDataString(path = "BungeeServer.Host")
@Getter
@@ -71,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;
@@ -111,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;
@@ -129,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() {
@@ -14,6 +14,7 @@ import com.bencodez.simpleapi.file.annotation.ConfigDataBoolean;
import com.bencodez.simpleapi.file.annotation.ConfigDataInt;
import com.bencodez.simpleapi.file.annotation.ConfigDataKeys;
import com.bencodez.simpleapi.file.annotation.ConfigDataListString;
import com.bencodez.simpleapi.file.annotation.ConfigDataLong;
import com.bencodez.simpleapi.file.annotation.ConfigDataString;
import com.bencodez.votingplugin.VotingPluginMain;
import com.bencodez.votingplugin.placeholders.PlaceholderCacheLevel;
@@ -51,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;
@@ -67,6 +72,185 @@ public class Config extends YMLFile {
@Getter
private boolean preloadSkulls = false;
@ConfigDataBoolean(path = "DiscordSRV.Enabled")
@Getter
private boolean discordSRVEnabled = false;
@ConfigDataBoolean(path = "DiscordSRV.TopVoter.AllTime.Enabled")
@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;
@ConfigDataString(path = "DiscordSRV.TopVoter.AllTime.Title")
@Getter
private String discordSRVTopVoterAllTimeTitle = "All Time Top Voters";
@ConfigDataString(path = "DiscordSRV.TopVoter.AllTime.RankDisplay")
@Getter
private String discordSRVTopVoterAllTimeRankDisplay = "%rank%: %player% - %votes% Votes";
@ConfigDataBoolean(path = "DiscordSRV.TopVoter.Monthly.Enabled")
@Getter
private boolean discordSRVTopVoterMonthlyEnabled = false;
@ConfigDataLong(path = "DiscordSRV.TopVoter.Monthly.Channel")
@Getter
private long discordSRVTopVoterMonthlyChannel = 0;
@ConfigDataString(path = "DiscordSRV.TopVoter.Monthly.Title")
@Getter
private String discordSRVTopVoterMonthlyTitle = "Top Voters of the Month";
@ConfigDataString(path = "DiscordSRV.TopVoter.Monthly.RankDisplay")
@Getter
private String discordSRVTopVoterMonthlyRankDisplay = "%rank%: %player% - %votes% Votes";
@ConfigDataBoolean(path = "DiscordSRV.TopVoter.Weekly.Enabled")
@Getter
private boolean discordSRVTopVoterWeeklyEnabled = false;
@ConfigDataLong(path = "DiscordSRV.TopVoter.Weekly.Channel")
@Getter
private long discordSRVTopVoterWeeklyChannel = 0;
@ConfigDataString(path = "DiscordSRV.TopVoter.Weekly.Title")
@Getter
private String discordSRVTopVoterWeeklyTitle = "Top Voters of the Week";
@ConfigDataString(path = "DiscordSRV.TopVoter.Weekly.RankDisplay")
@Getter
private String discordSRVTopVoterWeeklyRankDisplay = "%rank%: %player% - %votes% Votes";
@ConfigDataBoolean(path = "DiscordSRV.TopVoter.Daily.Enabled")
@Getter
private boolean discordSRVTopVoterDailyEnabled = false;
@ConfigDataLong(path = "DiscordSRV.TopVoter.Daily.Channel")
@Getter
private long discordSRVTopVoterDailyChannel = 0;
@ConfigDataString(path = "DiscordSRV.TopVoter.Daily.Title")
@Getter
private String discordSRVTopVoterDailyTitle = "Top Voters of the Day";
@ConfigDataString(path = "DiscordSRV.TopVoter.Daily.RankDisplay")
@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:
return isDiscordSRVTopVoterAllTimeEnabled();
case Monthly:
return isDiscordSRVTopVoterMonthlyEnabled();
case Weekly:
return isDiscordSRVTopVoterWeeklyEnabled();
case Daily:
return isDiscordSRVTopVoterDailyEnabled();
default:
return false;
}
}
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:
return getDiscordSRVTopVoterAllTimeChannel();
case Monthly:
return getDiscordSRVTopVoterMonthlyChannel();
case Weekly:
return getDiscordSRVTopVoterWeeklyChannel();
case Daily:
return getDiscordSRVTopVoterDailyChannel();
default:
return 0;
}
}
public String getDiscordSRVTopVoterTitle(TopVoter topVoter) {
switch (topVoter) {
case AllTime:
return getDiscordSRVTopVoterAllTimeTitle();
case Monthly:
return getDiscordSRVTopVoterMonthlyTitle();
case Weekly:
return getDiscordSRVTopVoterWeeklyTitle();
case Daily:
return getDiscordSRVTopVoterDailyTitle();
default:
return "";
}
}
public String getDiscordSRVTopVoterRankDisplay(TopVoter topVoter) {
switch (topVoter) {
case AllTime:
return getDiscordSRVTopVoterAllTimeRankDisplay();
case Monthly:
return getDiscordSRVTopVoterMonthlyRankDisplay();
case Weekly:
return getDiscordSRVTopVoterWeeklyRankDisplay();
case Daily:
return getDiscordSRVTopVoterDailyRankDisplay();
default:
return "";
}
}
@ConfigDataBoolean(path = "AllowUnJoinedCheckServer")
@Getter
private boolean allowUnJoinedCheckServer = true;
@@ -87,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;
@@ -99,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
@@ -139,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;
@@ -155,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;
@@ -287,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;
@@ -323,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;
@@ -347,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;
@@ -581,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);
}
/**
@@ -718,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);
@@ -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;
}
@@ -41,13 +44,13 @@ public class ConfigVoteSites extends YMLFile {
public void generateVoteSite(String siteName) {
if (plugin.getConfigFile().isAutoCreateVoteSites()) {
String org = siteName;
siteName = siteName.replace(".", "_");
siteName = siteName.replace(".", "_").replace(" ", "_");
plugin.getLogger().warning("VoteSite " + siteName + " does not exist with the servicesite '" + org
+ "', creating one, set AutoCreateVoteSites to false to prevent this");
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.advancedcore.api.user.userstorage.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)"));
}
}
@@ -16,6 +16,7 @@ import com.bencodez.simpleapi.array.ArrayUtils;
import com.bencodez.votingplugin.VotingPluginMain;
import com.bencodez.votingplugin.signs.SignHandler;
import com.bencodez.votingplugin.timequeue.VoteTimeQueue;
import com.bencodez.votingplugin.topvoter.TopVoter;
// TODO: Auto-generated Javadoc
/**
@@ -244,6 +245,15 @@ public class ServerData {
plugin.getServerDataFile().reloadData();
}
public long getTopVoterMessageId(TopVoter top) {
return getData().getLong("DiscordSRV.TopVoterMessageId." + top.toString(), 0);
}
public void setTopVoterMessageId(TopVoter top, long messageId) {
getData().set("DiscordSRV.TopVoterMessageId." + top.toString(), messageId);
saveData();
}
/**
* Removes the sign.
*
@@ -0,0 +1,199 @@
package com.bencodez.votingplugin.discord;
import java.awt.Color;
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;
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;
import github.scarsz.discordsrv.DiscordSRV;
import github.scarsz.discordsrv.api.Subscribe;
import github.scarsz.discordsrv.api.events.DiscordReadyEvent;
import github.scarsz.discordsrv.dependencies.jda.api.EmbedBuilder;
import github.scarsz.discordsrv.dependencies.jda.api.JDA;
import github.scarsz.discordsrv.dependencies.jda.api.entities.TextChannel;
import github.scarsz.discordsrv.util.DiscordUtil;
import lombok.Getter;
public class DiscordHandler {
private final VotingPluginMain plugin;
@Getter
private final HashMap<TopVoter, Long> topVoterMessageIds = new HashMap<TopVoter, Long>();
private final AtomicBoolean discordReady = new AtomicBoolean(false);
public DiscordHandler(VotingPluginMain plugin) {
this.plugin = plugin;
for (TopVoter top : TopVoter.values()) {
long savedId = plugin.getServerData().getTopVoterMessageId(top);
topVoterMessageIds.put(top, savedId);
}
}
/** Call this early, e.g. from onEnable() in main plugin class */
public void load() {
DiscordSRV.api.subscribe(this);
plugin.getLogger().info("DiscordHandler loaded; awaiting Discord readiness.");
// if DiscordSRV is already up and running, mark it ready now
if (DiscordUtil.getJda().getStatus() == JDA.Status.CONNECTED) {
discordReady.set(true);
plugin.getLogger().info("DiscordSRV was already ready; manual update now available.");
}
plugin.getRewardHandler().addInjectedReward(new RewardInjectConfigurationSection("DiscordSRV") {
@Override
public String onRewardRequested(Reward arg0, com.bencodez.advancedcore.api.user.AdvancedCoreUser user,
ConfigurationSection section, HashMap<String, String> placeholders) {
if (section.getBoolean("Enabled")) {
String msg = PlaceholderUtils
.replacePlaceHolder(section.getString("Message", "Please set a message"), placeholders);
msg = PlaceholderUtils.replacePlaceHolders(user.getOfflinePlayer(), msg);
sendRewardMessage(msg, section.getString("ChannelID", "0"));
}
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 */
@Subscribe
public void onDiscordReady(DiscordReadyEvent event) {
discordReady.set(true);
plugin.getLogger().info("DiscordSRV is ready; manual update now available.");
}
/**
* Public method to update the Top Voter message on demand. Checks Discord
* readiness internally.
*/
public void updateDiscordLeaderboard() {
if (!discordReady.get()) {
plugin.getLogger().warning("DiscordSRV not ready yet; cannot update Top Voters.");
return;
}
for (TopVoter top : TopVoter.values()) {
if (plugin.getConfigFile().isDiscordSRVTopVoterEnabled(top)) {
plugin.debug("Updating Discord Top Voter: " + top.toString());
updateTopVoterMessageId(top);
}
}
}
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);
boolean newMessage = plugin.getConfigFile().isDiscordSRVTopVoterNewMessageOnUpdate(top);
EmbedBuilder eb = new EmbedBuilder().setTitle(title).setColor(Color.CYAN).setTimestamp(Instant.now());
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);
return;
}
long existingId = topVoterMessageIds.getOrDefault(top, 0L);
if (existingId <= 0 || newMessage) {
channel.sendMessageEmbeds(eb.build()).queue(msg -> {
long newId = msg.getIdLong();
topVoterMessageIds.put(top, newId);
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(existingId, eb.build()).queue(
m -> plugin.debug("Edited Top Voters " + top + " (ID: " + existingId + ")"),
err -> plugin.getLogger().warning("Error editing Top Voters " + top + ": " + err.getMessage()));
}
}
public void sendRewardMessage(String message, String channelId) {
TextChannel channel = DiscordUtil.getJda().getTextChannelById(channelId);
if (channel != null) {
channel.sendMessage(message).queue();
} else {
plugin.getLogger().warning("Discord channel ID " + channelId + " not found for reward message.");
}
}
}
@@ -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,19 +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.BungeeMethod;
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.
@@ -29,66 +30,93 @@ 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();
plugin.getPlaceholders().onUpdate(user, true);
if (plugin.getBungeeSettings().isUseBungeecoord()
&& (plugin.getBungeeHandler().getMethod().equals(BungeeMethod.PLUGINMESSAGING)
|| plugin.getBungeeHandler().getMethod().equals(BungeeMethod.REDIS))) {
plugin.getBungeeHandler().getGlobalMessageHandler().sendMessage("Login", user.getPlayerName(),
user.getUUID(), plugin.getBungeeSettings().getServer());
if (plugin.getBungeeSettings().isUseBungeecoord()) {
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 java.util.UUID uuid = event.getPlayer().getUniqueId();
plugin.getLoginTimer().execute(new Runnable() {
@Override
public void run() {
VotingPluginMain.plugin.getAdvancedTab().remove(uuid);
VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(uuid);
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,6 +3,8 @@ package com.bencodez.votingplugin.listeners;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
@@ -10,15 +12,15 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import com.bencodez.advancedcore.api.bedrock.BedrockNameResolver;
import com.bencodez.advancedcore.api.misc.PlayerManager;
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.proxy.BungeeMethod;
import com.bencodez.votingplugin.topvoter.TopVoter;
import com.bencodez.votingplugin.user.VotingPluginUser;
import com.bencodez.votingplugin.votesites.VoteSite;
// TODO: Auto-generated Javadoc
/**
@@ -51,27 +53,34 @@ public class PlayerVoteListener implements Listener {
String playerName = event.getPlayer();
plugin.debug("Processing PlayerVoteEvent: " + playerName + "/" + event.getServiceSite());
if (!PlayerManager.getInstance().isValidUser(playerName, plugin.getConfigFile().isAllowUnJoinedCheckServer())) {
if (!playerName.startsWith(plugin.getOptions().getBedrockPlayerPrefix())
&& !plugin.getOptions().getBedrockPlayerPrefix().isEmpty()) {
if (!event.isBungee() && PlayerManager.getInstance().isValidUser(
plugin.getOptions().getBedrockPlayerPrefix() + playerName,
plugin.getConfigFile().isAllowUnJoinedCheckServer())) {
plugin.debug("Detected a bedrock user without bedrock prefix, adjusting");
playerName = plugin.getOptions().getBedrockPlayerPrefix() + playerName;
}
} else {
if (!plugin.getConfigFile().isAllowUnjoined()) {
plugin.getLogger().warning("Player " + playerName
+ " has not joined before, disregarding vote, set AllowUnjoined to true to prevent this");
if (event.isBungee() && plugin.getBungeeSettings().isRemoveInvalidUsers()) {
plugin.getVotingPluginUserManager().getVotingPluginUser(playerName).remove();
}
return;
// check for name casing
final String properName = plugin.getUserManager().getProperName(playerName);
// Resolve Bedrock/Java + add prefix only when appropriate
BedrockNameResolver.Result rn = plugin.getBedrockHandle().resolve(properName);
String creditedName = rn.finalName;
plugin.debug("Vote name resolved: " + properName + " -> " + creditedName + " (" + rn.rationale + ")");
// Single validity check on the final name
boolean allowUnJoinedCheckServer = plugin.getConfigFile().isAllowUnJoinedCheckServer();
boolean isValid = PlayerManager.getInstance().isValidUser(creditedName, allowUnJoinedCheckServer);
// Handle not-valid the same way you did before
if (!isValid) {
if (!plugin.getConfigFile().isAllowUnjoined()) {
plugin.getLogger().warning("Player " + creditedName + " has not joined before, disregarding vote. "
+ "Set AllowUnjoined to true to accept.");
if (event.isBungee() && plugin.getBungeeSettings().isRemoveInvalidUsers()) {
plugin.getVotingPluginUserManager().getVotingPluginUser(creditedName).remove();
}
return;
}
}
// If we get here, use the resolved/possibly-prefixed name going forward
playerName = creditedName;
if (playerName.isEmpty()) {
plugin.getLogger().warning("Empty player name from vote, ignoring");
return;
@@ -89,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
@@ -98,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));
@@ -120,6 +130,7 @@ public class PlayerVoteListener implements Listener {
// player online
user = plugin.getVotingPluginUserManager().getVotingPluginUser(p);
} else {
// attempt to get exact name from cache matching casing
user = plugin.getVotingPluginUserManager().getVotingPluginUser(playerName);
}
}
@@ -137,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
@@ -147,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) {
@@ -171,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()) {
@@ -196,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);
}
@@ -207,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 {
@@ -237,40 +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);
if (plugin.getBungeeSettings().isUseBungeecoord()) {
if (plugin.getBungeeHandler().getMethod().equals(BungeeMethod.MYSQL)) {
plugin.getBukkitScheduler().runTaskLaterAsynchronously(plugin, new Runnable() {
@Override
public void run() {
if (Bukkit.getOnlinePlayers().size() > 0) {
plugin.getBungeeHandler().getGlobalMessageHandler().sendMessage("VoteUpdate", uuid);
}
}
}, 2);
}
}
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);
}
@@ -72,9 +72,11 @@ public class VotiferEvent implements Listener {
plugin.getServerData().addServiceSite(voteSite);
if (plugin.getBungeeSettings().isUseBungeecoord() && !plugin.getBungeeSettings().isVotifierBypass()
&& (plugin.getBungeeHandler().getMethod().equals(BungeeMethod.PLUGINMESSAGING)
|| plugin.getBungeeHandler().getMethod().equals(BungeeMethod.SOCKETS))) {
|| plugin.getBungeeHandler().getMethod().equals(BungeeMethod.SOCKETS)
|| plugin.getBungeeHandler().getMethod().equals(BungeeMethod.MQTT)
|| plugin.getBungeeHandler().getMethod().equals(BungeeMethod.REDIS))) {
plugin.getLogger().severe(
"Ignoring vote from votifier since pluginmessaging or socket bungee method is enabled, this means you aren't setup correctly for those methods, please check: https://github.com/BenCodez/VotingPlugin/wiki/Bungeecord-Setups");
"Ignoring vote from votifier since pluginmessaging, socket, redis, or mqtt bungee method is enabled, this means you aren't setup correctly for those methods, please check: https://github.com/BenCodez/VotingPlugin/wiki/Bungeecord-Setups");
return;
}
String matchSite = "";
@@ -84,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;
@@ -116,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,8 @@ public class PlaceHolders {
}
}
}
if (plugin.getConfigFile().isUsePrimaryAccountForPlaceholders()) {
if (user.hasPrimaryAccount()) {
user = plugin.getVotingPluginUserManager().getVotingPluginUser(user.getPrimaryAccount());
}
}
UUID uuid = user.getJavaUUID();
for (PlaceHolder<VotingPluginUser> placeholder : placeholders) {
try {
@@ -190,11 +190,11 @@ public class PlaceHolders {
CalculatingPlaceholder<VotingPluginUser> cPlaceholder = (CalculatingPlaceholder<VotingPluginUser>) placeholder;
if (placeholder.isUsesCache() && placeholder.isCached(identifier)) {
ConcurrentHashMap<UUID, String> cache = placeholder.getCache().get(identifier);
if (cache.containsKey(p.getUniqueId())) {
if (cPlaceholder.getCacheData().containsKey(user.getJavaUUID())) {
if (cache.containsKey(uuid)) {
if (cPlaceholder.getCacheData().containsKey(uuid)) {
return cPlaceholder.placeholderRequest(user, identifier);
}
return cache.get(p.getUniqueId());
return cache.get(uuid);
} else if (!forceProcess) {
schedulePlaceholderCheck(user);
return "...";
@@ -226,8 +226,9 @@ public class PlaceHolders {
}
if (placeholder.isUsesCache() && placeholder.isCached(identifier)) {
ConcurrentHashMap<UUID, String> cache = placeholder.getCache().get(identifier);
if (cache.containsKey(p.getUniqueId())) {
return cache.get(p.getUniqueId());
if (cache.containsKey(uuid)) {
return cache.get(uuid);
} else if (!forceProcess) {
schedulePlaceholderCheck(user);
return "...";
@@ -302,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) {
@@ -513,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;
@@ -549,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
@@ -617,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
@@ -1015,7 +1020,7 @@ public class PlaceHolders {
@Override
public String placeholderRequest(String identifier) {
LocalDateTime now = plugin.getTimeChecker().getTime();
LocalDateTime now = plugin.getTimeChecker().getTime().plusDays(plugin.getOptions().getTimeWeekOffSet());
LocalDateTime newWeek = plugin.getTimeChecker().getTime().withHour(0).withMinute(0);
TemporalField woy = WeekFields.of(Locale.getDefault()).weekOfWeekBasedYear();
@@ -1118,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 +1,11 @@
package com.bencodez.votingplugin.proxy;
public enum BungeeMethod {
MYSQL, PLUGINMESSAGING, SOCKETS, REDIS;
MYSQL, PLUGINMESSAGING, SOCKETS, REDIS, MQTT;
public boolean requiresPlayerOnline() {
return this == PLUGINMESSAGING;
}
public static BungeeMethod getByName(String str) {
for (BungeeMethod method : values()) {
@@ -1,7 +0,0 @@
package com.bencodez.votingplugin.proxy;
public class BungeeVersion {
public static int getPluginMessageVersion() {
return 1;
}
}
@@ -1,6 +1,9 @@
package com.bencodez.votingplugin.proxy;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
public class OfflineBungeeVote {
@@ -11,24 +14,45 @@ public class OfflineBungeeVote {
@Getter
private String service;
@Getter
@Setter
private String text;
@Getter
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;
}
}
@@ -0,0 +1,484 @@
package com.bencodez.votingplugin.proxy;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import com.bencodez.simpleapi.sql.Column;
import com.bencodez.simpleapi.sql.DataType;
import com.bencodez.simpleapi.sql.data.DataValue;
import com.bencodez.simpleapi.sql.data.DataValueInt;
import com.bencodez.simpleapi.sql.data.DataValueString;
import com.bencodez.simpleapi.sql.mysql.AbstractSqlTable;
import com.bencodez.simpleapi.sql.mysql.DbType;
import com.bencodez.simpleapi.sql.mysql.MySQL;
import com.bencodez.simpleapi.sql.mysql.config.MysqlConfig;
import com.bencodez.simpleapi.sql.mysql.queries.Query;
public abstract class ProxyMysqlUserTable extends AbstractSqlTable {
// Keep this so existing callers relying on getName() still work
private final String name;
// For int-vs-string decode in getExactQuery (same pattern as before)
private final List<String> intColumns = new ArrayList<>();
// Backwards-compat wrapper (your old code expects getUuids())
private final Set<String> uuidsView;
// ---- Required subclass hooks ----
@Override
public String getPrimaryKeyColumn() {
return "uuid";
}
@Override
public String buildCreateTableSql(DbType dbType) {
// NOTE: AbstractSqlTable.qi(...) already quotes identifiers properly for
// DbType.
StringBuilder sb = new StringBuilder();
sb.append("CREATE TABLE IF NOT EXISTS ").append(qi(getTableName())).append(" (");
// Best-case uuid type:
// - Postgres: UUID
// - MySQL/MariaDB: VARCHAR(37) (matches existing behavior)
sb.append(qi("uuid")).append(" ").append(bestUuidType()).append(", ");
sb.append("PRIMARY KEY ( ").append(qi("uuid")).append(" )");
sb.append(");");
return sb.toString();
}
// ---- Logging hooks required by AbstractSqlTable ----
@Override
public abstract void logSevere(String msg);
@Override
public abstract void logInfo(String msg);
@Override
public abstract void debug(Throwable t);
// Keep these existing abstract methods for compatibility with your old
// subclasses
public abstract void debug(SQLException e);
// ---- Constructors ----
public ProxyMysqlUserTable(String tableName, MysqlConfig config, boolean debug) {
super(tableName, config, debug);
this.name = super.getTableName();
// keep uuids view
this.uuidsView = super.getPrimaryKeys();
// keep old "uuid column type tweak" behavior (but now uses base helper)
alterColumnType("uuid", bestUuidType());
// load intColumns behavior if you rely on it elsewhere
// (no-op: it's only used if you add to it)
}
/**
* Use an already-connected pool (shared).
*/
public ProxyMysqlUserTable(String tableName, MySQL existingMysql, boolean debug) {
super(tableName, existingMysql, debug);
this.name = super.getTableName();
this.uuidsView = super.getPrimaryKeys();
alterColumnType("uuid", bestUuidType());
}
// ---- Backwards-compatible API ----
public String getName() {
return name;
}
/**
* Keep old method name and behavior.
*/
public Set<String> getUuids() {
return getPrimaryKeys();
}
/**
* Keep old behavior: query uuids fresh.
*/
public ArrayList<String> getUuidsQuery() {
return new ArrayList<>(getPrimaryKeysQuery());
}
public void loadData() {
// old name -> new behavior
loadBasicCaches();
}
public void clearCache() {
clearCacheBasic();
}
public void clearCacheBasic() {
clearCaches();
}
public void shutdown() {
close();
}
// Keep old containsKeyQuery signature
public boolean containsKeyQuery(String index) {
return super.containsKeyQuery(index);
}
// ---- Required old utility methods (kept) ----
public ArrayList<String> getColumnsQueury() {
return new ArrayList<>(getColumnsQuery());
}
public ArrayList<Column> getRowsNameQuery() {
ArrayList<Column> result = new ArrayList<>();
checkColumn("PlayerName", DataType.STRING);
String sqlStr = "SELECT " + qi("PlayerName") + " FROM " + qi(getTableName()) + ";";
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(sqlStr);
ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
result.add(new Column("PlayerName", new DataValueString(rs.getString(1))));
}
} catch (SQLException ignored) {
}
return result;
}
public ArrayList<String> getNamesQuery() {
ArrayList<String> names = new ArrayList<>();
checkColumn("PlayerName", DataType.STRING);
ArrayList<Column> rows = getRowsNameQuery();
if (rows != null) {
for (Column c : rows) {
if (c.getValue() != null && c.getValue().isString()) {
String v = c.getValue().getString();
if (v != null) {
names.add(v);
}
}
}
}
return names;
}
public ArrayList<Column> getRowsQuery() {
ArrayList<Column> result = new ArrayList<>();
String sqlStr = "SELECT " + qi("uuid") + " FROM " + qi(getTableName()) + ";";
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(sqlStr);
ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
result.add(new Column("uuid", new DataValueString(rs.getString(1))));
}
} catch (SQLException e) {
return null;
}
return result;
}
public ConcurrentHashMap<UUID, String> getRowsUUIDNameQuery() {
ConcurrentHashMap<UUID, String> uuidNames = new ConcurrentHashMap<>();
checkColumn("PlayerName", DataType.STRING);
String sqlStr = "SELECT " + qi("uuid") + ", " + qi("PlayerName") + " FROM " + qi(getTableName()) + ";";
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(sqlStr);
ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
String uuidStr = rs.getString(1);
String playerName = rs.getString(2);
if (uuidStr != null && !uuidStr.isEmpty() && !"null".equalsIgnoreCase(uuidStr) && playerName != null) {
try {
uuidNames.put(UUID.fromString(uuidStr), playerName);
} catch (IllegalArgumentException ignored) {
}
}
}
} catch (SQLException ignored) {
}
return uuidNames;
}
public String getUUID(String playerName) {
checkColumn("PlayerName", DataType.STRING);
String query = "SELECT " + qi("uuid") + " FROM " + qi(getTableName()) + " WHERE " + qi("PlayerName") + " = ?;";
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(query)) {
ps.setString(1, playerName);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
String uuid = rs.getString(1);
if (uuid != null && !uuid.isEmpty()) {
return uuid;
}
}
}
} catch (SQLException e) {
debug(e);
}
return null;
}
public ArrayList<Column> getExactQuery(Column column) {
ArrayList<Column> result = new ArrayList<>();
String query = "SELECT * FROM " + qi(getTableName()) + " WHERE " + qi(column.getName()) + " = ?;";
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(query)) {
String v = column.getValue() == null ? null : column.getValue().getString();
if (getDbType() == DbType.POSTGRESQL && "uuid".equalsIgnoreCase(column.getName()) && v != null) {
ps.setObject(1, UUID.fromString(v));
} else {
ps.setString(1, v);
}
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
for (int i = 1; i <= rs.getMetaData().getColumnCount(); i++) {
String columnName = rs.getMetaData().getColumnLabel(i);
Column rCol;
if (intColumns.contains(columnName)) {
rCol = new Column(columnName, DataType.INTEGER);
rCol.setValue(new DataValueInt(rs.getInt(i)));
} else {
rCol = new Column(columnName, DataType.STRING);
rCol.setValue(new DataValueString(rs.getString(i)));
}
result.add(rCol);
}
return result;
}
}
} catch (SQLException e) {
debug(e);
} catch (ArrayIndexOutOfBoundsException ignored) {
}
for (String col : getColumns()) {
result.add(new Column(col, DataType.STRING));
}
return result;
}
public boolean isIntColumn(String key) {
return intColumns.contains(key);
}
// ---- INSERT/UPDATE/WIPE (kept similar to your existing behavior) ----
public void insert(String index, String column, DataValue value) {
insertQuery(index, Arrays.asList(new Column(column, value)));
}
public void insertQuery(String index, List<Column> cols) {
for (Column c : cols) {
checkColumn(c.getName(), c.getDataType());
}
// Keep your previous cross-db behavior:
// - Postgres: ON CONFLICT upsert
// - MySQL/MariaDB: INSERT IGNORE ... SET ...
if (getDbType() == DbType.POSTGRESQL) {
StringBuilder sb = new StringBuilder();
sb.append("INSERT INTO ").append(qi(getTableName())).append(" (").append(qi("uuid"));
for (Column col : cols) {
sb.append(", ").append(qi(col.getName()));
}
sb.append(") VALUES (?");
for (int i = 0; i < cols.size(); i++) {
sb.append(", ?");
}
sb.append(") ON CONFLICT (").append(qi("uuid")).append(") DO UPDATE SET ");
for (int i = 0; i < cols.size(); i++) {
Column col = cols.get(i);
String c = qi(col.getName());
sb.append(c).append(" = EXCLUDED.").append(c);
if (i != cols.size() - 1) {
sb.append(", ");
}
}
sb.append(";");
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(sb.toString())) {
ps.setObject(1, UUID.fromString(index));
int p = 2;
for (Column col : cols) {
ps.setString(p++, col.getValue().toString());
}
ps.executeUpdate();
uuidsView.add(index);
} catch (SQLException | IllegalArgumentException e) {
debug(e);
}
return;
}
StringBuilder query = new StringBuilder();
query.append("INSERT IGNORE ").append(qi(getTableName())).append(" SET ").append(qi("uuid")).append("=?, ");
for (int i = 0; i < cols.size(); i++) {
Column col = cols.get(i);
query.append(qi(col.getName())).append("=?");
query.append(i == cols.size() - 1 ? ";" : ", ");
}
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(query.toString())) {
ps.setString(1, index);
int p = 2;
for (Column col : cols) {
ps.setString(p++, col.getValue().toString());
}
ps.executeUpdate();
uuidsView.add(index);
} catch (SQLException e) {
debug(e);
}
}
public void update(String index, List<Column> cols) {
for (Column col : cols) {
checkColumn(col.getName(), col.getDataType());
}
// keep old behavior: if missing, insert
if (!getUuids().contains(index) && !containsKeyQuery(index)) {
insertQuery(index, cols);
return;
}
synchronized (updateLock) {
StringBuilder sb = new StringBuilder();
sb.append("UPDATE ").append(qi(getTableName())).append(" SET ");
for (int i = 0; i < cols.size(); i++) {
Column col = cols.get(i);
sb.append(qi(col.getName())).append("=?");
if (i != cols.size() - 1) {
sb.append(", ");
}
}
sb.append(" WHERE ").append(qi("uuid")).append("=?;");
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(sb.toString())) {
int p = 1;
for (Column col : cols) {
bindValue(ps, p++, col);
}
if (getDbType() == DbType.POSTGRESQL) {
ps.setObject(p, UUID.fromString(index));
} else {
ps.setString(p, index);
}
int updated = ps.executeUpdate();
if (updated == 0) {
// row missing (wiped) even though uuid cache thought it existed
insertQuery(index, cols);
}
} catch (SQLException | IllegalArgumentException e) {
debug(e);
}
}
}
private void bindValue(PreparedStatement ps, int index, Column col) throws SQLException {
DataValue v = col.getValue();
if (getDbType() == DbType.POSTGRESQL && v != null && v.isInt()) {
ps.setInt(index, v.getInt());
return;
}
ps.setString(index, v == null ? null : v.toString());
}
public void update(String index, String column, DataValue value) {
checkColumn(column, value.getType());
if (!getUuids().contains(index) && !containsKeyQuery(index)) {
insert(index, column, value);
return;
}
synchronized (updateLock) {
String sql = "UPDATE " + qi(getTableName()) + " SET " + qi(column) + "=? WHERE " + qi("uuid") + "=?;";
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
if (getDbType() == DbType.POSTGRESQL && value != null && value.isInt()) {
ps.setInt(1, value.getInt());
} else {
ps.setString(1, value == null ? null : value.toString());
}
if (getDbType() == DbType.POSTGRESQL) {
ps.setObject(2, UUID.fromString(index));
} else {
ps.setString(2, index);
}
ps.executeUpdate();
} catch (SQLException | IllegalArgumentException e) {
debug(e);
}
}
}
public void wipeColumnData(String columnName, DataType dataType) {
checkColumn(columnName, dataType);
String sql = "UPDATE " + qi(getTableName()) + " SET " + qi(columnName) + " = " + dataType.getNoValue() + ";";
try {
new Query(mysql, sql).executeUpdate();
} catch (SQLException e) {
debug(e);
}
}
}
@@ -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,20 +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 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();
@@ -28,6 +66,24 @@ public interface VotingPluginProxyConfig {
public boolean getDebug();
public boolean getVoteCacheUseMySQL();
public boolean getVoteCacheUseMainMySQL();
public boolean getNonVotedCacheUseMySQL();
public boolean getNonVotedCacheUseMainMySQL();
public String getMqttClientID();
public String getMqttBrokerURL();
public String getMqttUsername();
public String getMqttPassword();
public String getMqttPrefix();
public String getFallBack();
public boolean getGlobalDataEnabled();
@@ -78,6 +134,8 @@ public interface VotingPluginProxyConfig {
public int getRedisPort();
public int getRedisDbIndex();
public String getRedisPrefix();
public String getRedisUsername();
@@ -94,6 +152,8 @@ public interface VotingPluginProxyConfig {
public int getTimeHourOffSet();
public int getTimeWeekOffSet();
public String getTimeZone();
public boolean getUseMonthDateTotalsAsPrimaryTotal();
@@ -120,8 +180,30 @@ public interface VotingPluginProxyConfig {
public List<String> getWhiteListedServers();
public Collection<String> getWaitUntilVoteDelaySites();
public String getWaitUntilVoteDelayService(String site);
public int getWaitUntilVoteDelayVoteDelay(String site);
public int getWaitUntilVoteDelayVoteDelayMin(String site);
public int getWaitUntilVoteDelayVoteDelayHour(String site);
public boolean getWaitUntilVoteDelayVoteDelayDaily(String site);
public void load();
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,226 +34,339 @@ 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);
}
@Override
public String getBedrockPlayerPrefix() {
return getData().getString("BedrockPlayerPrefix", ".");
}
@Override
public List<String> getBlockedServers() {
return getData().getStringList("BlockedServers");
}
public boolean getBroadcast() {
return getData().getBoolean("Broadcast", false);
}
@Override
public String getBungeeHost() {
return getData().getString("BungeeServer.Host", "");
}
@Override
public boolean getBungeeManageTotals() {
return getData().getBoolean("BungeeManageTotals", true);
}
@Override
public String getBungeeMethod() {
return getData().getString("BungeeMethod", "SOCKETS");
}
@Override
public int getBungeePort() {
return getData().getInt("BungeeServer.Port", 1297);
}
@Override
public boolean getDebug() {
return getData().getBoolean("Debug", false);
}
@Override
public String getFallBack() {
return getData().getString("FallBackServer", "");
}
@Override
public boolean getGlobalDataEnabled() {
return getData().getBoolean("GlobalData.Enabled", false);
}
@Override
public boolean getGlobalDataUseMainMySQL() {
return getData().getBoolean("GlobalData.UseMainMySQL", true);
}
@Override
public int getLimitVotePoints() {
return getData().getInt("LimitVotePoints", -1);
}
@Override
public int getMaxAmountOfVotesPerDay() {
return getData().getInt("MaxAmountOfVotesPerDay", -1);
}
@Override
public String getMultiProxyMethod() {
return getData().getString("MultiProxyMethod", "SOCKET");
}
@Override
public boolean getMultiProxyOneGlobalReward() {
return getData().getBoolean("MultiProxyOneGlobalReward", false);
}
@Override
public String getMultiProxyRedisHost() {
return getData().getString("MultiProxyRedis.Host", "");
}
@Override
public String getMultiProxyRedisPassword() {
return getData().getString("MultiProxyRedis.Password", "");
}
@Override
public int getMultiProxyRedisPort() {
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);
}
@Override
public String getMultiProxyRedisUsername() {
return getData().getString("MultiProxyRedis.Username", "");
}
@Override
public Collection<String> getMultiProxyServers() {
return getData().getSection("MultiProxyServers").getKeys();
}
@Override
public Map<String, Object> getMultiProxyServersConfiguration(String s) {
return configToMap(getData().getSection("MultiProxyServers." + s));
}
@Override
public String getMultiProxySocketHostHost() {
return getData().getString("MultiProxySocketHost.Host", "");
}
@Override
public int getMultiProxySocketHostPort() {
return getData().getInt("MultiProxySocketHost.Port", 1297);
}
@Override
public boolean getMultiProxySupport() {
return getData().getBoolean("MultiProxySupport", false);
}
@Override
public boolean getOnlineMode() {
return getData().getBoolean("OnlineMode", true);
}
@Override
public int getPointsOnVote() {
return getData().getInt("PointsOnVote", 1);
}
@Override
public boolean getPrimaryServer() {
return getData().getBoolean("PrimaryServer", false);
}
@Override
public String getProxyServerName() {
return getData().getString("ProxyServerName", "SOCKET");
}
@Override
public List<String> getProxyServers() {
return getData().getStringList("ProxyServers");
}
@Override
public String getRedisHost() {
return getData().getString("Redis.Host", "");
}
@Override
public String getRedisPassword() {
return getData().getString("Redis.Password", "");
}
@Override
public int getRedisPort() {
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", "");
}
@Override
public String getRedisUsername() {
return getData().getString("Redis.Username", "");
}
@Override
public boolean getSendVotesToAllServers() {
return getData().getBoolean("SendVotesToAllServers");
}
@Override
public Map<String, Object> getSpigotServerConfiguration(String s) {
return configToMap(getData().getSection("SpigotServers." + s));
}
@Override
public Collection<String> getSpigotServers() {
return getData().getSection("SpigotServers").getKeys();
}
@Override
public boolean getStoreMonthTotalsWithDate() {
return getData().getBoolean("StoreMonthTotalsWithDate", false);
}
@Override
public boolean getTimeChangeFailSafeBypass() {
return getData().getBoolean("TimeChangeFailSafeBypass", false);
}
@Override
public int getTimeHourOffSet() {
return getData().getInt("TimeHourOffSet");
}
@Override
public String getTimeZone() {
return getData().getString("TimeZone", "");
}
@Override
public boolean getUseMonthDateTotalsAsPrimaryTotal() {
return getData().getBoolean("UseMonthDateTotalsAsPrimaryTotal", false);
}
@Override
public boolean getUUIDLookup() {
return getData().getBoolean("UUIDLookup", true);
}
@Override
public int getVoteCacheTime() {
return getData().getInt("VoteCacheTime", -1);
}
@Override
public String getVotePartyBroadcast() {
return getData().getString("VoteParty.Broadcast", "");
}
@Override
public List<String> getVotePartyBungeeCommands() {
return getData().getStringList("VoteParty.BungeeCommands");
}
@Override
public boolean getVotePartyEnabled() {
return getData().getBoolean("VoteParty.Enabled", false);
}
@Override
public int getVotePartyIncreaseVotesRequired() {
return getData().getInt("VoteParty.IncreaseVotesRequired", 0);
}
@Override
public boolean getVotePartySendToAllServers() {
return getData().getBoolean("VoteParty.SendToAllServers", true);
}
@Override
public List<String> getVotePartyServersToSend() {
return getData().getStringList("VoteParty.ServersToSend");
}
@Override
public int getVotePartyVotesRequired() {
return getData().getInt("VoteParty.VotesRequired", 100);
}
@Override
public boolean getWaitForUserOnline() {
return getData().getBoolean("WaitForUserOnline", false);
return getData().getBoolean("WaitForUserOnline", true);
}
@Override
public List<String> getWhiteListedServers() {
return getData().getStringList("WhiteListedServers");
}
@Override
public String getMqttClientID() {
return getData().getString("MQTT.ClientID", "proxy");
}
@Override
public String getMqttBrokerURL() {
return getData().getString("MQTT.BrokerURL", "tcp://localhost:1883");
}
@Override
public String getMqttUsername() {
return getData().getString("MQTT.Username", "");
}
@Override
public String getMqttPassword() {
return getData().getString("MQTT.Password", "");
}
@Override
public String getMqttPrefix() {
return getData().getString("MQTT.Prefix", "");
}
public void load() {
if (!bungee.getDataFolder().exists()) {
bungee.getDataFolder().mkdir();
@@ -294,4 +408,92 @@ public class Config implements VotingPluginProxyConfig {
return getData().getBoolean("PluginMessageEncryption", false);
}
@Override
public Collection<String> getWaitUntilVoteDelaySites() {
return getData().getSection("WaitUntilVoteDelay").getKeys();
}
@Override
public String getWaitUntilVoteDelayService(String site) {
return getData().getString("WaitUntilVoteDelay." + site + ".ServiceSite", "");
}
@Override
public int getWaitUntilVoteDelayVoteDelay(String site) {
return getData().getInt("WaitUntilVoteDelay." + site + ".VoteDelay", 24);
}
@Override
public boolean getWaitUntilVoteDelayVoteDelayDaily(String site) {
return getData().getBoolean("WaitUntilVoteDelay." + site + ".VoteDelayDaily", false);
}
@Override
public int getWaitUntilVoteDelayVoteDelayHour(String site) {
return getData().getInt("WaitUntilVoteDelay." + site + ".VoteDelayDailyHour", 0);
}
@Override
public int getWaitUntilVoteDelayVoteDelayMin(String site) {
return getData().getInt("WaitUntilVoteDelay." + site + ".VoteDelayMin", 0);
}
@Override
public boolean getVoteCacheUseMySQL() {
return getData().getBoolean("VoteCache.UseMySQL", false);
}
@Override
public boolean getVoteCacheUseMainMySQL() {
return getData().getBoolean("VoteCache.UseMainMySQL", true);
}
@Override
public int getTimeWeekOffSet() {
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() {
}
}
@@ -0,0 +1,225 @@
package com.bencodez.votingplugin.proxy.bungee;
import java.io.File;
import java.util.Collection;
import com.bencodez.simpleapi.file.BungeeJsonFile;
import com.bencodez.votingplugin.proxy.OfflineBungeeVote;
import com.bencodez.votingplugin.proxy.cache.DataNode;
import com.bencodez.votingplugin.proxy.cache.GsonDataNode;
import com.bencodez.votingplugin.proxy.cache.IVoteCache;
import com.bencodez.votingplugin.timequeue.VoteTimeQueue;
public class BungeeJsonVoteCache extends BungeeJsonFile implements IVoteCache {
private VotingPluginBungee bungee;
public BungeeJsonVoteCache(VotingPluginBungee bungee) {
super(new File(bungee.getDataFolder(), "votecache.json"));
this.bungee = bungee;
initialize();
}
private void initialize() {
if (!bungee.getDataFolder().exists()) {
bungee.getDataFolder().mkdir();
}
// Reload the JSON file to ensure latest data
reload();
}
public void addTimedVote(int num, VoteTimeQueue voteTimedQueue) {
String path = "TimedVoteCache." + num;
setString(path + ".Name", voteTimedQueue.getName());
setString(path + ".Service", voteTimedQueue.getService());
setLong(path + ".Time", voteTimedQueue.getTime());
}
public void addVote(String server, int num, OfflineBungeeVote voteData) {
String path = "VoteCache." + server + "." + num;
setString(path + ".Name", voteData.getPlayerName());
setString(path + ".Service", voteData.getService());
setString(path + ".UUID", voteData.getUuid());
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) {
String path = "OnlineCache." + player + "." + num;
setString(path + ".Name", voteData.getPlayerName());
setString(path + ".Service", voteData.getService());
setString(path + ".UUID", voteData.getUuid());
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() {
setString("VoteCache", null);
setString("OnlineCache", null);
setString("TimedVoteCache", null);
save();
}
public Collection<String> getOnlineVotes(String name) {
return getKeys("OnlineCache." + name);
}
public GsonDataNode getOnlineVotes(String name, String num) {
return new GsonDataNode(getNode("OnlineCache." + name + "." + num));
}
public Collection<String> getPlayers() {
return getKeys("OnlineCache");
}
public Collection<String> getServers() {
return getKeys("VoteCache");
}
public Collection<String> getServerVotes(String server) {
return getKeys("VoteCache." + server);
}
public GsonDataNode getServerVotes(String server, String num) {
return new GsonDataNode(getNode("VoteCache." + server + "." + num));
}
public Collection<String> getTimedVoteCache() {
return getKeys("TimedVoteCache");
}
public DataNode getTimedVoteCache(String key) {
return new GsonDataNode(getNode("TimedVoteCache." + key));
}
public int getVotePartyCache(String server) {
return getInt("VoteParty.Cache." + server, 0);
}
public int getVotePartyCurrentVotes() {
return getInt("VoteParty.CurrentVotes", 0);
}
public int getVotePartyInreaseVotesRequired() {
return getInt("VoteParty.IncreaseVotes", 0);
}
public void setVotePartyCache(String server, int amount) {
setInt("VoteParty.Cache." + server, amount);
}
public void setVotePartyCurrentVotes(int amount) {
setInt("VoteParty.CurrentVotes", amount);
}
public void setVotePartyInreaseVotesRequired(int amount) {
setInt("VoteParty.IncreaseVotes", amount);
}
@Override
public void save() {
super.save();
}
@Override
public void reload() {
super.reload();
}
@Override
public void removeOnlineVotes(String player) {
setString("OnlineCache." + player, null);
}
@Override
public void removeServerVotes(String server) {
setString("VoteCache." + server, null);
}
@Override
public void removeServerVote(String server, String uuid) {
Collection<String> votes = getServerVotes(server);
if (votes == null) {
return;
}
// search for vote with uuid and remove it
for (String num : votes) {
GsonDataNode node = getServerVotes(server, num);
if (node == null) {
continue;
}
DataNode uuidNode = node.get("UUID");
if (uuidNode == null) {
continue;
}
String nodeUuid = uuidNode.asString();
if (nodeUuid != null && nodeUuid.equals(uuid)) {
setString("VoteCache." + server + "." + num, null);
}
}
}
@Override
public void removeVote(String server, OfflineBungeeVote vote) {
Collection<String> votes = getServerVotes(server);
if (votes == null) {
return;
}
for (String num : votes) {
GsonDataNode node = getServerVotes(server, num);
if (node == null) {
continue;
}
DataNode uuidNode = node.get("UUID");
DataNode serviceNode = node.get("Service");
DataNode timeNode = node.get("Time");
if (uuidNode == null || serviceNode == null || timeNode == null) {
continue;
}
String nodeUuid = uuidNode.asString();
String nodeService = serviceNode.asString();
Long nodeTime = timeNode.asLong();
if (nodeUuid != null && nodeService != null && nodeTime != null && nodeUuid.equals(vote.getUuid())
&& nodeService.equals(vote.getService()) && nodeTime.longValue() == vote.getTime()) {
setString("VoteCache." + server + "." + num, null);
}
}
}
@Override
public void removeOnlineVote(OfflineBungeeVote vote) {
Collection<String> players = getPlayers();
if (players == null) {
return;
}
for (String player : players) {
Collection<String> onlineVotes = getOnlineVotes(player);
if (onlineVotes == null) {
continue;
}
for (String num : onlineVotes) {
GsonDataNode node = getOnlineVotes(player, num);
if (node == null) {
continue;
}
DataNode uuidNode = node.get("UUID");
DataNode serviceNode = node.get("Service");
DataNode timeNode = node.get("Time");
if (uuidNode == null || serviceNode == null || timeNode == null) {
continue;
}
String nodeUuid = uuidNode.asString();
String nodeService = serviceNode.asString();
Long nodeTime = timeNode.asLong();
if (nodeUuid != null && nodeService != null && nodeTime != null && nodeUuid.equals(vote.getUuid())
&& nodeService.equals(vote.getService()) && nodeTime.longValue() == vote.getTime()) {
setString("OnlineCache." + player + "." + num, null);
}
}
}
}
}
@@ -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();
}
}
@@ -1,127 +0,0 @@
package com.bencodez.votingplugin.proxy.bungee;
import java.io.File;
import java.util.Collection;
import com.bencodez.simpleapi.file.BungeeJsonFile;
import com.bencodez.votingplugin.proxy.OfflineBungeeVote;
import com.bencodez.votingplugin.timequeue.VoteTimeQueue;
import com.google.gson.JsonElement;
public class VoteCache extends BungeeJsonFile {
private VotingPluginBungee bungee;
public VoteCache(VotingPluginBungee bungee) {
super(new File(bungee.getDataFolder(), "votecache.json"));
this.bungee = bungee;
initialize();
}
private void initialize() {
if (!bungee.getDataFolder().exists()) {
bungee.getDataFolder().mkdir();
}
// Reload the JSON file to ensure latest data
reload();
}
public void addTimedVote(int num, VoteTimeQueue voteTimedQueue) {
String path = "TimedVoteCache." + num;
setString(path + ".Name", voteTimedQueue.getName());
setString(path + ".Service", voteTimedQueue.getService());
setLong(path + ".Time", voteTimedQueue.getTime());
}
public void addVote(String server, int num, OfflineBungeeVote voteData) {
String path = "VoteCache." + server + "." + num;
setString(path + ".Name", voteData.getPlayerName());
setString(path + ".Service", voteData.getService());
setString(path + ".UUID", voteData.getUuid());
setLong(path + ".Time", voteData.getTime());
setBoolean(path + ".Real", voteData.isRealVote());
setString(path + ".Text", voteData.getText());
}
public void addVoteOnline(String player, int num, OfflineBungeeVote voteData) {
String path = "OnlineCache." + player + "." + num;
setString(path + ".Name", voteData.getPlayerName());
setString(path + ".Service", voteData.getService());
setString(path + ".UUID", voteData.getUuid());
setLong(path + ".Time", voteData.getTime());
setBoolean(path + ".Real", voteData.isRealVote());
setString(path + ".Text", voteData.getText());
}
public void clearData() {
setString("VoteCache", null);
setString("OnlineCache", null);
setString("TimedVoteCache", null);
save();
}
public Collection<String> getOnlineVotes(String name) {
return getKeys("OnlineCache." + name);
}
public JsonElement getOnlineVotes(String name, String num) {
return getNode("OnlineCache." + name + "." + num);
}
public Collection<String> getPlayers() {
return getKeys("OnlineCache");
}
public Collection<String> getServers() {
return getKeys("VoteCache");
}
public Collection<String> getServerVotes(String server) {
return getKeys("VoteCache." + server);
}
public JsonElement getServerVotes(String server, String num) {
return getNode("VoteCache." + server + "." + num);
}
public Collection<String> getTimedVoteCache() {
return getKeys("TimedVoteCache");
}
public JsonElement getTimedVoteCache(String key) {
return getNode("TimedVoteCache." + key);
}
public int getVotePartyCache(String server) {
return getInt("VoteParty.Cache." + server, 0);
}
public int getVotePartyCurrentVotes() {
return getInt("VoteParty.CurrentVotes", 0);
}
public int getVotePartyInreaseVotesRequired() {
return getInt("VoteParty.IncreaseVotes", 0);
}
public void setVotePartyCache(String server, int amount) {
setInt("VoteParty.Cache." + server, amount);
}
public void setVotePartyCurrentVotes(int amount) {
setInt("VoteParty.CurrentVotes", amount);
}
public void setVotePartyInreaseVotesRequired(int amount) {
setInt("VoteParty.IncreaseVotes", amount);
}
@Override
public void save() {
super.save();
}
@Override
public void reload() {
super.reload();
}
}
@@ -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)));
}
}
@@ -0,0 +1,99 @@
package com.bencodez.votingplugin.proxy.cache;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.serialize.SerializationException;
public class ConfigDataNode implements DataNode {
private final ConfigurationNode node;
public ConfigDataNode(ConfigurationNode node) {
this.node = node;
}
@Override
public boolean isObject() {
return node.isMap();
}
@Override
public boolean isArray() {
return node.isList();
}
@Override
public boolean isPrimitive() {
return !isObject() && !isArray();
}
@Override
public DataNode get(String key) {
return new ConfigDataNode(node.node((Object) key));
}
@Override
public long asLong() {
return node.getLong();
}
@Override
public boolean has(String key) {
ConfigurationNode child = node.node((Object) key);
return child != null && child.raw() != null;
}
@Override
public boolean has(int index) {
ConfigurationNode child = node.node(index);
return child != null && child.raw() != null;
}
@Override
public DataNode get(int index) {
return new ConfigDataNode(node.node(index));
}
@Override
public void set(String key, Object value) {
try {
node.node((Object) key).set(value);
} catch (SerializationException e) {
e.printStackTrace();
}
}
@Override
public void set(int index, Object value) {
try {
node.node(index).set(value);
} catch (SerializationException e) {
e.printStackTrace();
}
}
@Override
public Object asPrimitive() {
return node.raw();
}
@Override
public String asString() {
return node.getString();
}
@Override
public int asInt() {
return node.getInt();
}
@Override
public boolean asBoolean() {
return node.getBoolean();
}
@Override
public Object toInternal() {
return node;
}
}
@@ -0,0 +1,32 @@
package com.bencodez.votingplugin.proxy.cache;
public interface DataNode {
boolean isObject();
boolean isArray();
boolean isPrimitive();
DataNode get(String key);
DataNode get(int index);
void set(String key, Object value);
void set(int index, Object value);
Object asPrimitive();
String asString();
long asLong();
boolean has(String key);
boolean has(int index);
int asInt();
boolean asBoolean();
Object toInternal(); // Returns either JsonElement or ConfigurationNode
}
@@ -0,0 +1,118 @@
package com.bencodez.votingplugin.proxy.cache;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
public class GsonDataNode implements DataNode {
private final JsonElement element;
public GsonDataNode(JsonElement element) {
this.element = element;
}
@Override
public boolean isObject() {
return element.isJsonObject();
}
@Override
public boolean isArray() {
return element.isJsonArray();
}
@Override
public boolean isPrimitive() {
return element.isJsonPrimitive();
}
@Override
public DataNode get(String key) {
if (!isObject())
return null;
return new GsonDataNode(element.getAsJsonObject().get(key));
}
@Override
public DataNode get(int index) {
if (!isArray())
return null;
return new GsonDataNode(element.getAsJsonArray().get(index));
}
@Override
public void set(String key, Object value) {
if (!isObject())
return;
element.getAsJsonObject().add(key, new Gson().toJsonTree(value));
}
@Override
public void set(int index, Object value) {
if (!isArray())
return;
element.getAsJsonArray().set(index, new Gson().toJsonTree(value));
}
@Override
public long asLong() {
if (element == null || !element.isJsonPrimitive())
return 0L;
JsonPrimitive prim = element.getAsJsonPrimitive();
if (prim.isNumber()) {
return prim.getAsLong();
}
// Try parsing from string if it's not a number
try {
return Long.parseLong(prim.getAsString());
} catch (NumberFormatException e) {
return 0L;
}
}
@Override
public Object asPrimitive() {
if (!isPrimitive())
return null;
JsonPrimitive prim = element.getAsJsonPrimitive();
if (prim.isBoolean())
return prim.getAsBoolean();
if (prim.isNumber())
return prim.getAsNumber();
return prim.getAsString();
}
@Override
public boolean has(String key) {
return isObject() && element.getAsJsonObject().has(key);
}
@Override
public boolean has(int index) {
return isArray() && index >= 0 && index < element.getAsJsonArray().size();
}
@Override
public String asString() {
return element.getAsString();
}
@Override
public int asInt() {
return element.getAsInt();
}
@Override
public boolean asBoolean() {
return element.getAsBoolean();
}
@Override
public Object toInternal() {
return element;
}
}
@@ -0,0 +1,59 @@
package com.bencodez.votingplugin.proxy.cache;
import java.util.Collection;
import com.bencodez.votingplugin.proxy.OfflineBungeeVote;
import com.bencodez.votingplugin.timequeue.VoteTimeQueue;
public interface IVoteCache {
void addTimedVote(int num, VoteTimeQueue voteTimedQueue);
void addVote(String server, int num, OfflineBungeeVote voteData);
void addVoteOnline(String player, int num, OfflineBungeeVote voteData);
void clearData();
Collection<String> getOnlineVotes(String name);
DataNode getOnlineVotes(String name, String num);
Collection<String> getPlayers();
Collection<String> getServers();
Collection<String> getServerVotes(String server);
DataNode getServerVotes(String server, String num);
Collection<String> getTimedVoteCache();
DataNode getTimedVoteCache(String key);
int getVotePartyCache(String server);
int getVotePartyCurrentVotes();
int getVotePartyInreaseVotesRequired();
void setVotePartyCache(String server, int amount);
void setVotePartyCurrentVotes(int amount);
void setVotePartyInreaseVotesRequired(int amount);
void save();
void reload();
void removeOnlineVotes(String player);
void removeServerVotes(String server);
void removeServerVote(String server, String uuid);
void removeVote(String server, OfflineBungeeVote vote);
void removeOnlineVote(OfflineBungeeVote vote);
}
@@ -0,0 +1,359 @@
package com.bencodez.votingplugin.proxy.cache;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement; // NEW
import java.util.ArrayList;
import java.util.List;
import java.util.Set; // NEW
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; // NEW
import com.bencodez.simpleapi.sql.mysql.AbstractSqlTable;
import com.bencodez.simpleapi.sql.mysql.DbType;
import com.bencodez.simpleapi.sql.mysql.MySQL;
import com.bencodez.simpleapi.sql.mysql.config.MysqlConfig;
import com.bencodez.simpleapi.sql.mysql.queries.Query;
import com.bencodez.votingplugin.proxy.OfflineBungeeVote;
public abstract class ProxyOnlineVoteCacheTable extends AbstractSqlTable {
// Prevent repeated startup DDL across multiple instances/subclasses
private static final Set<String> MIGRATED_VOTEID = ConcurrentHashMap.newKeySet();
private static final Set<String> ENSURED_INDEXES = ConcurrentHashMap.newKeySet();
@Override
public String getPrimaryKeyColumn() {
return "id";
}
@Override
public String buildCreateTableSql(DbType dbType) {
if (dbType == DbType.POSTGRESQL) {
return "CREATE TABLE IF NOT EXISTS " + qi(getTableName()) + " (" + qi("id") + " BIGSERIAL PRIMARY KEY, "
+ qi("uuid") + " " + bestUuidType() + ", " + qi("voteid") + " VARCHAR(36), " + qi("playerName")
+ " VARCHAR(100), " + qi("service") + " VARCHAR(100), " + qi("time") + " BIGINT, " + qi("realVote")
+ " BOOLEAN, " + qi("text") + " TEXT" + ");";
}
return "CREATE TABLE IF NOT EXISTS " + qi(getTableName()) + " (" + qi("id") + " INT AUTO_INCREMENT PRIMARY KEY,"
+ qi("uuid") + " VARCHAR(37)," + qi("voteid") + " VARCHAR(36)," + qi("playerName") + " VARCHAR(100),"
+ qi("service") + " VARCHAR(100)," + qi("time") + " BIGINT," + qi("realVote") + " TINYINT(1),"
+ qi("text") + " TEXT," + "INDEX idx_uuid (" + qi("uuid") + ")," + "INDEX idx_time (" + qi("time") + ")"
+ ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
}
public void removeVotesByUuid(String uuid) {
String sql = "DELETE FROM " + qi(getTableName()) + " WHERE " + qi("uuid") + " = ?;";
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
if (getDbType() == DbType.POSTGRESQL) {
ps.setObject(1, UUID.fromString(uuid));
} else {
ps.setString(1, uuid);
}
ps.executeUpdate();
} catch (SQLException | IllegalArgumentException e) {
debug(e);
}
}
@Override
public abstract void logSevere(String msg);
@Override
public abstract void logInfo(String msg);
@Override
public abstract void debug(Throwable t);
public ProxyOnlineVoteCacheTable(MySQL existingMysql, String tablePrefix, boolean debug) {
super((tablePrefix != null ? tablePrefix : "") + "votingplugin_onlinevotecache", existingMysql, debug);
// best-effort migrations (no nested connections)
alterColumnType("uuid", bestUuidType());
addVoteIdColumnIfMissingOnce();
ensureIndexesOnce();
}
public ProxyOnlineVoteCacheTable(MysqlConfig config, boolean debug) {
super("votingplugin_onlinevotecache", config, debug);
alterColumnType("uuid", bestUuidType());
addVoteIdColumnIfMissingOnce();
ensureIndexesOnce();
}
public void updateVoteText(OfflineBungeeVote vote, String newText) {
if (vote == null) {
return;
}
String sql = "UPDATE " + qi(getTableName()) + " SET " + qi("text") + " = ?" + " WHERE " + qi("uuid")
+ " = ? AND " + qi("service") + " = ? AND " + qi("time") + " = ?;";
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, newText);
if (getDbType() == DbType.POSTGRESQL) {
ps.setObject(2, UUID.fromString(vote.getUuid()));
} else {
ps.setString(2, vote.getUuid());
}
ps.setString(3, vote.getService());
ps.setLong(4, vote.getTime());
ps.executeUpdate();
} catch (SQLException | IllegalArgumentException e) {
debug(e);
}
}
private void ensureIndexesOnce() {
if (getDbType() != DbType.POSTGRESQL) {
return;
}
String key = getDbType() + ":" + getTableName() + ":indexes";
if (!ENSURED_INDEXES.add(key)) {
return;
}
// Use ONE connection for all index DDL to avoid pool starvation (pool=1 safe)
try (Connection conn = mysql.getConnectionManager().getConnection(); Statement st = conn.createStatement()) {
st.executeUpdate("CREATE INDEX IF NOT EXISTS idx_uuid ON " + qi(getTableName()) + " (" + qi("uuid") + ");");
st.executeUpdate("CREATE INDEX IF NOT EXISTS idx_time ON " + qi(getTableName()) + " (" + qi("time") + ");");
} catch (SQLException e) {
debug(e);
}
}
private void addVoteIdColumnIfMissingOnce() {
String key = getDbType() + ":" + getTableName() + ":voteid";
if (!MIGRATED_VOTEID.add(key)) {
return;
}
addVoteIdColumnIfMissing();
}
private void addVoteIdColumnIfMissing() {
final boolean pg = getDbType() == DbType.POSTGRESQL;
final String schemaFilter = pg ? "table_schema = current_schema()" : "TABLE_SCHEMA = DATABASE()";
// Lowercase compare avoids casing issues on some setups
final String checkSql = "SELECT 1 FROM information_schema.columns WHERE " + schemaFilter
+ " AND LOWER(table_name) = LOWER(?) AND LOWER(column_name) = LOWER(?) LIMIT 1;";
// IMPORTANT: do check + alter on SAME connection to avoid deadlock when
// poolSize=1
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(checkSql)) {
ps.setString(1, getTableName());
ps.setString(2, "voteid");
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return; // exists
}
}
String alter = "ALTER TABLE " + qi(getTableName()) + " ADD COLUMN " + qi("voteid") + " VARCHAR(36);";
try (Statement st = conn.createStatement()) {
st.executeUpdate(alter);
}
} catch (SQLException e) {
logSevere("Failed to add voteid column to " + getTableName() + ": " + e.getMessage());
debug(e);
}
}
// --- INSERT ---
public void insertVote(UUID voteId, String uuid, String playerName, String service, long time, boolean real,
String text) {
String sql = "INSERT INTO " + qi(getTableName()) + " (" + qi("uuid") + ", " + qi("voteid") + ", "
+ qi("playerName") + ", " + qi("service") + ", " + qi("time") + ", " + qi("realVote") + ", "
+ qi("text") + ") VALUES (?, ?, ?, ?, ?, ?, ?);";
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
if (getDbType() == DbType.POSTGRESQL) {
ps.setObject(1, UUID.fromString(uuid));
} else {
ps.setString(1, uuid);
}
ps.setString(2, voteId != null ? voteId.toString() : null);
ps.setString(3, playerName);
ps.setString(4, service);
ps.setLong(5, time);
if (getDbType() == DbType.POSTGRESQL) {
ps.setBoolean(6, real);
} else {
ps.setInt(6, real ? 1 : 0);
}
ps.setString(7, text);
ps.executeUpdate();
} catch (SQLException | IllegalArgumentException e) {
debug(e);
}
}
// --- GET ---
public List<VoteRow> getAllVotes() {
return selectVotes("SELECT * FROM " + qi(getTableName()) + ";", null);
}
public List<VoteRow> getVotesByUUID(String uuid) {
String sql = "SELECT * FROM " + qi(getTableName()) + " WHERE " + qi("uuid") + " = ?;";
Object param = (getDbType() == DbType.POSTGRESQL) ? UUID.fromString(uuid) : uuid;
return selectVotes(sql, new Object[] { param });
}
// --- DELETE ---
public void removeVoteById(int id) {
String sql = "DELETE FROM " + qi(getTableName()) + " WHERE " + qi("id") + " = ?;";
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, id);
ps.executeUpdate();
} catch (SQLException e) {
debug(e);
}
}
public void removeVote(OfflineBungeeVote vote) {
String sql = "DELETE FROM " + qi(getTableName()) + " WHERE " + qi("uuid") + " = ? AND " + qi("service")
+ " = ? AND " + qi("time") + " = ?;";
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
if (getDbType() == DbType.POSTGRESQL) {
ps.setObject(1, UUID.fromString(vote.getUuid()));
} else {
ps.setString(1, vote.getUuid());
}
ps.setString(2, vote.getService());
ps.setLong(3, vote.getTime());
ps.executeUpdate();
} catch (SQLException | IllegalArgumentException e) {
debug(e);
}
}
public void clearTable() {
try {
if (getDbType() == DbType.POSTGRESQL) {
new Query(mysql, "TRUNCATE TABLE " + qi(getTableName()) + " RESTART IDENTITY;").executeUpdate();
} else {
new Query(mysql, "TRUNCATE TABLE " + qi(getTableName()) + ";").executeUpdate();
}
} catch (SQLException e) {
debug(e);
}
}
// --- Internals ---
private List<VoteRow> selectVotes(String sql, Object[] params) {
List<VoteRow> list = new ArrayList<>();
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
if (params != null) {
for (int i = 0; i < params.length; i++) {
ps.setObject(i + 1, params[i]);
}
}
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
list.add(new VoteRow(rs.getInt("id"), rs.getString("voteid"), rs.getString("uuid"),
rs.getString("playerName"), rs.getString("service"), rs.getLong("time"),
(getDbType() == DbType.POSTGRESQL ? rs.getBoolean("realVote") : rs.getInt("realVote") == 1),
rs.getString("text")));
}
}
} catch (SQLException e) {
debug(e);
}
return list;
}
public static class VoteRow {
private final int id;
private final String voteId;
private final String uuid;
private final String playerName;
private final String service;
private final long time;
private final boolean realVote;
private final String text;
public VoteRow(int id, String voteId, String uuid, String playerName, String service, long time,
boolean realVote, String text) {
this.id = id;
this.voteId = voteId;
this.uuid = uuid;
this.playerName = playerName;
this.service = service;
this.time = time;
this.realVote = realVote;
this.text = text;
}
public int getId() {
return id;
}
public String getVoteId() {
return voteId;
}
public String getUuid() {
return uuid;
}
public String getPlayerName() {
return playerName;
}
public String getService() {
return service;
}
public long getTime() {
return time;
}
public boolean isRealVote() {
return realVote;
}
public String getText() {
return text;
}
}
}
@@ -0,0 +1,191 @@
package com.bencodez.votingplugin.proxy.cache;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import com.bencodez.simpleapi.sql.mysql.AbstractSqlTable;
import com.bencodez.simpleapi.sql.mysql.DbType;
import com.bencodez.simpleapi.sql.mysql.MySQL;
import com.bencodez.simpleapi.sql.mysql.config.MysqlConfig;
import com.bencodez.simpleapi.sql.mysql.queries.Query;
public abstract class ProxyTimedVoteCacheTable extends AbstractSqlTable {
@Override
public String getPrimaryKeyColumn() {
return "id";
}
@Override
public String buildCreateTableSql(DbType dbType) {
if (dbType == DbType.POSTGRESQL) {
return "CREATE TABLE IF NOT EXISTS " + qi(getTableName()) + " ("
+ qi("id") + " BIGSERIAL PRIMARY KEY, "
+ qi("playerName") + " VARCHAR(100), "
+ qi("service") + " VARCHAR(100), "
+ qi("time") + " BIGINT"
+ ");";
}
return "CREATE TABLE IF NOT EXISTS " + qi(getTableName()) + " ("
+ qi("id") + " INT AUTO_INCREMENT PRIMARY KEY,"
+ qi("playerName") + " VARCHAR(100),"
+ qi("service") + " VARCHAR(100),"
+ qi("time") + " BIGINT,"
+ "INDEX idx_time (" + qi("time") + ")"
+ ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
}
@Override
public abstract void logSevere(String msg);
@Override
public abstract void logInfo(String msg);
@Override
public abstract void debug(Throwable t);
public ProxyTimedVoteCacheTable(MySQL existingMysql, String tablePrefix, boolean debug) {
super((tablePrefix != null ? tablePrefix : "") + "votingplugin_timedvotecache",
existingMysql,
debug);
ensureIndexes();
}
public ProxyTimedVoteCacheTable(MysqlConfig config, boolean debug) {
super("votingplugin_timedvotecache", config, debug);
ensureIndexes();
}
private void ensureIndexes() {
if (getDbType() == DbType.POSTGRESQL) {
try {
new Query(mysql, "CREATE INDEX IF NOT EXISTS idx_time ON " + qi(getTableName()) + " (" + qi("time")
+ ");").executeUpdate();
} catch (SQLException e) {
debug(e);
}
}
}
// --- INSERT ---
public void insertTimedVote(String playerName, String service, long time) {
String sql = "INSERT INTO " + qi(getTableName()) + " (" + qi("playerName") + ", " + qi("service") + ", "
+ qi("time") + ") VALUES (?, ?, ?);";
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, playerName);
ps.setString(2, service);
ps.setLong(3, time);
ps.executeUpdate();
} catch (SQLException e) {
debug(e);
}
}
// --- GET ---
public List<TimedVoteRow> getAllVotes() {
return selectVotes("SELECT * FROM " + qi(getTableName()) + ";", null);
}
public List<TimedVoteRow> getExpiredVotes(long now) {
return selectVotes("SELECT * FROM " + qi(getTableName()) + " WHERE " + qi("time") + " <= ?;",
new Object[] { now });
}
// --- DELETE ---
public void removeVoteById(int id) {
String sql = "DELETE FROM " + qi(getTableName()) + " WHERE " + qi("id") + " = ?;";
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, id);
ps.executeUpdate();
} catch (SQLException e) {
debug(e);
}
}
public void removeExpiredVotes(long now) {
String sql = "DELETE FROM " + qi(getTableName()) + " WHERE " + qi("time") + " <= ?;";
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setLong(1, now);
ps.executeUpdate();
} catch (SQLException e) {
debug(e);
}
}
public void clearTable() {
try {
if (getDbType() == DbType.POSTGRESQL) {
new Query(mysql, "TRUNCATE TABLE " + qi(getTableName()) + " RESTART IDENTITY;").executeUpdate();
} else {
new Query(mysql, "TRUNCATE TABLE " + qi(getTableName()) + ";").executeUpdate();
}
} catch (SQLException e) {
debug(e);
}
}
private List<TimedVoteRow> selectVotes(String sql, Object[] params) {
List<TimedVoteRow> list = new ArrayList<>();
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
if (params != null) {
for (int i = 0; i < params.length; i++) {
ps.setObject(i + 1, params[i]);
}
}
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
list.add(new TimedVoteRow(
rs.getInt("id"),
rs.getString("playerName"),
rs.getString("service"),
rs.getLong("time")
));
}
}
} catch (SQLException e) {
debug(e);
}
return list;
}
public static class TimedVoteRow {
private final int id;
private final String playerName;
private final String service;
private final long time;
public TimedVoteRow(int id, String playerName, String service, long time) {
this.id = id;
this.playerName = playerName;
this.service = service;
this.time = time;
}
public int getId() {
return id;
}
public String getPlayerName() {
return playerName;
}
public String getService() {
return service;
}
public long getTime() {
return time;
}
}
}
@@ -0,0 +1,453 @@
package com.bencodez.votingplugin.proxy.cache;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import com.bencodez.simpleapi.sql.mysql.AbstractSqlTable;
import com.bencodez.simpleapi.sql.mysql.DbType;
import com.bencodez.simpleapi.sql.mysql.MySQL;
import com.bencodez.simpleapi.sql.mysql.config.MysqlConfig;
import com.bencodez.simpleapi.sql.mysql.queries.Query;
import com.bencodez.votingplugin.proxy.OfflineBungeeVote;
/**
* Proxy vote cache (per-server cached votes).
*
* Columns: - id (auto increment primary key) - uuid (player uuid) - voteid
* (vote uuid, nullable) - playerName - service - time (millis) - realVote
* (bool) - text (payload) - server
*
* Indexes: - server - uuid - time
*/
public abstract class ProxyVoteCacheTable extends AbstractSqlTable {
// Ensure we don't run the same migration repeatedly during startup (or for each
// subclass).
private static final Set<String> MIGRATED_VOTEID = ConcurrentHashMap.newKeySet();
private static final Set<String> ENSURED_INDEXES = ConcurrentHashMap.newKeySet();
// ---- Required hooks ----
@Override
public String getPrimaryKeyColumn() {
// This table's actual PK is numeric id; AbstractSqlTable caches keys as
// Strings.
return "id";
}
@Override
public String buildCreateTableSql(DbType dbType) {
StringBuilder sb = new StringBuilder();
if (dbType == DbType.POSTGRESQL) {
sb.append("CREATE TABLE IF NOT EXISTS ").append(qi(getTableName())).append(" (").append(qi("id"))
.append(" BIGSERIAL PRIMARY KEY, ").append(qi("uuid")).append(" ").append(bestUuidType())
.append(", ").append(qi("voteid")).append(" VARCHAR(36), ").append(qi("playerName"))
.append(" VARCHAR(100), ").append(qi("service")).append(" VARCHAR(100), ").append(qi("time"))
.append(" BIGINT, ").append(qi("realVote")).append(" BOOLEAN, ").append(qi("text"))
.append(" TEXT, ").append(qi("server")).append(" VARCHAR(100)").append(");");
} else {
sb.append("CREATE TABLE IF NOT EXISTS ").append(qi(getTableName())).append(" (").append(qi("id"))
.append(" INT AUTO_INCREMENT PRIMARY KEY,").append(qi("uuid")).append(" VARCHAR(37),")
.append(qi("voteid")).append(" VARCHAR(36),").append(qi("playerName")).append(" VARCHAR(100),")
.append(qi("service")).append(" VARCHAR(100),").append(qi("time")).append(" BIGINT,")
.append(qi("realVote")).append(" TINYINT(1),").append(qi("text")).append(" TEXT,")
.append(qi("server")).append(" VARCHAR(100),").append("INDEX idx_server (").append(qi("server"))
.append("),").append("INDEX idx_uuid (").append(qi("uuid")).append("),").append("INDEX idx_time (")
.append(qi("time")).append(")").append(") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
}
return sb.toString();
}
public void updateVoteText(OfflineBungeeVote vote, String server, String newText) {
if (vote == null || server == null) {
return;
}
String sql = "UPDATE " + qi(getTableName()) + " SET " + qi("text") + " = ?" + " WHERE " + qi("uuid")
+ " = ? AND " + qi("service") + " = ? AND " + qi("time") + " = ? AND " + qi("server") + " = ?;";
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, newText);
if (getDbType() == DbType.POSTGRESQL) {
ps.setObject(2, UUID.fromString(vote.getUuid()));
} else {
ps.setString(2, vote.getUuid());
}
ps.setString(3, vote.getService());
ps.setLong(4, vote.getTime());
ps.setString(5, server);
ps.executeUpdate();
} catch (SQLException | IllegalArgumentException e) {
debug(e);
}
}
@Override
public abstract void logSevere(String msg);
@Override
public abstract void logInfo(String msg);
@Override
public abstract void debug(Throwable t);
// ---- Constructors ----
public ProxyVoteCacheTable(MySQL existingMysql, String tablePrefix, boolean debug) {
super((tablePrefix != null ? tablePrefix : "") + "votingplugin_votecache", existingMysql, debug);
// best-effort migrations (safe for pool size 1; no nested connections)
alterColumnType("uuid", bestUuidType());
addVoteIdColumnIfMissingOnce();
ensureIndexesOnce();
}
public ProxyVoteCacheTable(MysqlConfig config, boolean debug) {
super("votingplugin_votecache", config, debug);
alterColumnType("uuid", bestUuidType());
addVoteIdColumnIfMissingOnce();
ensureIndexesOnce();
}
// ---- Migrations / indexes ----
private void ensureIndexesOnce() {
if (getDbType() != DbType.POSTGRESQL) {
return; // MySQL indexes are already in CREATE TABLE
}
final String key = getDbType() + ":" + getTableName();
if (!ENSURED_INDEXES.add(key)) {
return;
}
// Use ONE connection for all index statements (no pool churn).
try (Connection conn = mysql.getConnectionManager().getConnection(); Statement st = conn.createStatement()) {
st.executeUpdate(
"CREATE INDEX IF NOT EXISTS idx_server ON " + qi(getTableName()) + " (" + qi("server") + ");");
st.executeUpdate("CREATE INDEX IF NOT EXISTS idx_uuid ON " + qi(getTableName()) + " (" + qi("uuid") + ");");
st.executeUpdate("CREATE INDEX IF NOT EXISTS idx_time ON " + qi(getTableName()) + " (" + qi("time") + ");");
} catch (SQLException e) {
debug(e);
}
}
private void addVoteIdColumnIfMissingOnce() {
final String key = getDbType() + ":" + getTableName() + ":voteid";
if (!MIGRATED_VOTEID.add(key)) {
return;
}
addVoteIdColumnIfMissing();
}
private void addVoteIdColumnIfMissing() {
// Avoid nested connections: do check + alter using the SAME connection.
final boolean pg = getDbType() == DbType.POSTGRESQL;
final String schemaFilter = pg ? "table_schema = current_schema()" : "TABLE_SCHEMA = DATABASE()";
// Case-insensitive match helps with MySQL lower_case_table_names / quoting
// differences.
final String checkSql = "SELECT 1 FROM information_schema.columns WHERE " + schemaFilter
+ " AND LOWER(table_name) = LOWER(?) AND LOWER(column_name) = LOWER(?) LIMIT 1;";
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(checkSql)) {
ps.setString(1, getTableName());
ps.setString(2, "voteid");
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return; // exists
}
}
// Execute ALTER using same connection to avoid deadlock when pool size is
// small.
final String alter = "ALTER TABLE " + qi(getTableName()) + " ADD COLUMN " + qi("voteid") + " VARCHAR(36);";
try (Statement st = conn.createStatement()) {
st.executeUpdate(alter);
}
} catch (SQLException e) {
// don't hard fail startup
logSevere("Failed to add voteid column to " + getTableName() + ": " + e.getMessage());
debug(e);
}
}
// ---- INSERT ----
public void insertVote(UUID voteId, String uuid, String playerName, String service, long time, boolean real,
String text, String server) {
String sql = "INSERT INTO " + qi(getTableName()) + " (" + qi("uuid") + ", " + qi("voteid") + ", "
+ qi("playerName") + ", " + qi("service") + ", " + qi("time") + ", " + qi("realVote") + ", "
+ qi("text") + ", " + qi("server") + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?);";
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
if (getDbType() == DbType.POSTGRESQL) {
ps.setObject(1, UUID.fromString(uuid));
} else {
ps.setString(1, uuid);
}
ps.setString(2, voteId != null ? voteId.toString() : null);
ps.setString(3, playerName);
ps.setString(4, service);
ps.setLong(5, time);
if (getDbType() == DbType.POSTGRESQL) {
ps.setBoolean(6, real);
} else {
ps.setInt(6, real ? 1 : 0);
}
ps.setString(7, text);
ps.setString(8, server);
ps.executeUpdate();
} catch (SQLException | IllegalArgumentException e) {
debug(e);
}
}
// ---- GET ----
public List<VoteRow> getAllVotes() {
return selectVotes("SELECT * FROM " + qi(getTableName()) + ";", null);
}
public List<VoteRow> getVotesForServer(String server) {
return selectVotes("SELECT * FROM " + qi(getTableName()) + " WHERE " + qi("server") + " = ?;",
new Object[] { server });
}
public VoteRow getVoteById(int id) {
List<VoteRow> list = selectVotes("SELECT * FROM " + qi(getTableName()) + " WHERE " + qi("id") + " = ?;",
new Object[] { id });
return list.isEmpty() ? null : list.get(0);
}
public List<VoteRow> getVotesByUUID(String uuid) {
String sql = "SELECT * FROM " + qi(getTableName()) + " WHERE " + qi("uuid") + " = ?;";
Object[] params = (getDbType() == DbType.POSTGRESQL) ? new Object[] { UUID.fromString(uuid) }
: new Object[] { uuid };
return selectVotes(sql, params);
}
public List<VoteRow> getVotesByPlayerName(String playerName) {
return selectVotes("SELECT * FROM " + qi(getTableName()) + " WHERE " + qi("playerName") + " = ?;",
new Object[] { playerName });
}
public Set<String> getServers() {
Set<String> servers = new HashSet<>();
String sql = "SELECT DISTINCT " + qi("server") + " FROM " + qi(getTableName()) + " WHERE " + qi("server")
+ " IS NOT NULL;";
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
String s = rs.getString(1);
if (s != null) {
servers.add(s);
}
}
} catch (SQLException e) {
debug(e);
}
return servers;
}
// ---- DELETE ----
public void removeVoteById(int id) {
String sql = "DELETE FROM " + qi(getTableName()) + " WHERE " + qi("id") + " = ?;";
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, id);
ps.executeUpdate();
} catch (SQLException e) {
debug(e);
}
}
public void removeVotesByServerAndUUID(String server, String uuid) {
String sql = "DELETE FROM " + qi(getTableName()) + " WHERE " + qi("server") + " = ? AND " + qi("uuid")
+ " = ?;";
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, server);
if (getDbType() == DbType.POSTGRESQL) {
ps.setObject(2, UUID.fromString(uuid));
} else {
ps.setString(2, uuid);
}
ps.executeUpdate();
} catch (SQLException | IllegalArgumentException e) {
debug(e);
}
}
public void removeVote(OfflineBungeeVote vote, String server) {
String sql = "DELETE FROM " + qi(getTableName()) + " WHERE " + qi("uuid") + " = ? AND " + qi("service")
+ " = ? AND " + qi("time") + " = ? AND " + qi("server") + " = ?;";
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
if (getDbType() == DbType.POSTGRESQL) {
ps.setObject(1, UUID.fromString(vote.getUuid()));
} else {
ps.setString(1, vote.getUuid());
}
ps.setString(2, vote.getService());
ps.setLong(3, vote.getTime());
ps.setString(4, server);
ps.executeUpdate();
} catch (SQLException | IllegalArgumentException e) {
debug(e);
}
}
public void removeVotesByServer(String server) {
String sql = "DELETE FROM " + qi(getTableName()) + " WHERE " + qi("server") + " = ?;";
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, server);
ps.executeUpdate();
} catch (SQLException e) {
debug(e);
}
}
public void clearTable() {
try {
if (getDbType() == DbType.POSTGRESQL) {
new Query(mysql, "TRUNCATE TABLE " + qi(getTableName()) + " RESTART IDENTITY;").executeUpdate();
} else {
new Query(mysql, "TRUNCATE TABLE " + qi(getTableName()) + ";").executeUpdate();
}
} catch (SQLException e) {
debug(e);
}
}
// ---- Internals ----
private List<VoteRow> selectVotes(String sql, Object[] params) {
List<VoteRow> list = new ArrayList<>();
try (Connection conn = mysql.getConnectionManager().getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
if (params != null) {
for (int i = 0; i < params.length; i++) {
ps.setObject(i + 1, params[i]);
}
}
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
VoteRow v = new VoteRow(rs.getInt("id"), rs.getString("voteid"), rs.getString("uuid"),
rs.getString("playerName"), rs.getString("service"), rs.getLong("time"),
(getDbType() == DbType.POSTGRESQL ? rs.getBoolean("realVote") : rs.getInt("realVote") == 1),
rs.getString("text"), rs.getString("server"));
list.add(v);
}
}
} catch (SQLException e) {
debug(e);
}
return list;
}
public static class VoteRow {
private final int id;
private final String voteId;
private final String uuid;
private final String playerName;
private final String service;
private final long time;
private final boolean realVote;
private final String text;
private final String server;
public VoteRow(int id, String voteId, String uuid, String playerName, String service, long time,
boolean realVote, String text, String server) {
this.id = id;
this.voteId = voteId;
this.uuid = uuid;
this.playerName = playerName;
this.service = service;
this.time = time;
this.realVote = realVote;
this.text = text;
this.server = server;
}
public int getId() {
return id;
}
public String getVoteId() {
return voteId;
}
public String getUuid() {
return uuid;
}
public String getPlayerName() {
return playerName;
}
public String getService() {
return service;
}
public long getTime() {
return time;
}
public boolean isRealVote() {
return realVote;
}
public String getText() {
return text;
}
public String getServer() {
return server;
}
}
}
@@ -0,0 +1,522 @@
package com.bencodez.votingplugin.proxy.cache;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
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.OfflineBungeeVote;
import com.bencodez.votingplugin.timequeue.VoteTimeQueue;
import lombok.Getter;
public abstract class VoteCacheHandler {
@Getter
private Queue<VoteTimeQueue> timeChangeQueue = new ConcurrentLinkedQueue<>();
// uuid based
private ConcurrentHashMap<String, ArrayList<OfflineBungeeVote>> cachedOnlineVotes = new ConcurrentHashMap<>();
// server based
private ConcurrentHashMap<String, ArrayList<OfflineBungeeVote>> cachedVotes = new ConcurrentHashMap<>();
public boolean hasVotes(String server) {
return cachedVotes.containsKey(server);
}
public ArrayList<OfflineBungeeVote> getVotes(String server) {
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.getVoteId(), vote.getUuid(), vote.getPlayerName(), vote.getService(),
vote.getTime(), vote.isRealVote(), vote.getText(), server);
} else {
// 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();
}
}
public void removeVote(String server, String uuid) {
if (cachedVotes.containsKey(server)) {
ArrayList<OfflineBungeeVote> votes = cachedVotes.get(server);
votes.removeIf(vote -> vote.getUuid().equals(uuid));
if (useMySQL) {
voteCacheTable.removeVotesByServerAndUUID(server, uuid);
} else {
jsonStorage.removeServerVote(server, uuid);
jsonStorage.save();
}
}
}
public void removeVotes(String server) {
cachedVotes.remove(server);
if (useMySQL) {
voteCacheTable.removeVotesByServer(server);
} else {
jsonStorage.removeServerVotes(server);
jsonStorage.save();
}
}
public boolean hasOnlineVotes(String uuid) {
return cachedOnlineVotes.containsKey(uuid);
}
public ArrayList<OfflineBungeeVote> getOnlineVotes(String uuid) {
return cachedOnlineVotes.getOrDefault(uuid, new ArrayList<>());
}
public void addOnlineVote(String uuid, OfflineBungeeVote vote) {
cachedOnlineVotes.putIfAbsent(uuid, new ArrayList<>());
cachedOnlineVotes.get(uuid).add(vote);
if (useMySQL) {
onlineVoteCacheTable.insertVote(vote.getVoteId(), vote.getUuid(), vote.getPlayerName(), vote.getService(),
vote.getTime(), vote.isRealVote(), vote.getText());
} else {
// 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();
}
}
public void removeOnlineVotes(String uuid) {
cachedOnlineVotes.remove(uuid);
if (useMySQL) {
onlineVoteCacheTable.removeVotesByUuid(uuid);
} else {
jsonStorage.removeOnlineVotes(uuid);
jsonStorage.save();
}
}
public void checkVoteCacheTime(int voteCacheTime) {
long cTime = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
// Collect expired online votes
ArrayList<OfflineBungeeVote> expiredOnlineVotes = new ArrayList<>();
for (Entry<String, ArrayList<OfflineBungeeVote>> entry : cachedOnlineVotes.entrySet()) {
ArrayList<OfflineBungeeVote> votes = entry.getValue();
for (OfflineBungeeVote vote : votes) {
if (vote.getTime() + (voteCacheTime * 24 * 60 * 60 * 1000L) < cTime) {
debug1("Removing vote from cache: " + vote.toString());
expiredOnlineVotes.add(vote);
}
}
}
removeOnlineVotes(expiredOnlineVotes);
// Collect expired server votes
ArrayList<OfflineBungeeVote> expiredServerVotes = new ArrayList<>();
for (Entry<String, ArrayList<OfflineBungeeVote>> entry : cachedVotes.entrySet()) {
ArrayList<OfflineBungeeVote> votes = entry.getValue();
for (OfflineBungeeVote vote : votes) {
if (vote.getTime() + (voteCacheTime * 24 * 60 * 60 * 1000L) < cTime) {
debug1("Removing vote from cache: " + vote.toString());
expiredServerVotes.add(vote);
}
}
}
for (String server : cachedVotes.keySet()) {
removeServerVotes(server, expiredServerVotes);
}
}
public void saveVoteCache() {
if (useMySQL) {
if (!getTimeChangeQueue().isEmpty()) {
for (VoteTimeQueue vote : getTimeChangeQueue()) {
timedVoteCacheTable.insertTimedVote(vote.getName(), vote.getService(), vote.getTime());
}
}
} else {
if (!getTimeChangeQueue().isEmpty()) {
int num = 0;
for (VoteTimeQueue vote : getTimeChangeQueue()) {
jsonStorage.addTimedVote(num, vote);
num++;
}
}
jsonStorage.save();
}
}
public void addTimeVoteToCache(VoteTimeQueue vote) {
timeChangeQueue.add(vote);
}
public void load() {
if (useMySQL) {
// Load votes from MySQL
voteCacheTable.getAllVotes().forEach(voteRow -> {
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);
});
// Load online votes from MySQL
onlineVoteCacheTable.getAllVotes().forEach(voteRow -> {
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);
});
// Load timed votes from MySQL
ArrayList<VoteTimeQueue> timedVotes = new ArrayList<>();
timedVoteCacheTable.getAllVotes().forEach(timedVoteRow -> {
VoteTimeQueue voteTimeQueue = new VoteTimeQueue(timedVoteRow.getPlayerName(), timedVoteRow.getService(),
timedVoteRow.getTime());
timedVotes.add(voteTimeQueue);
});
timeChangeQueue.addAll(timedVotes);
timedVoteCacheTable.clearTable();
} else {
try {
for (String key : jsonStorage.getTimedVoteCache()) {
DataNode data = jsonStorage.getTimedVoteCache(key);
if (data != null && data.isObject()) {
String name = data.has("Name") ? data.get("Name").asString() : "";
String service = data.has("Service") ? data.get("Service").asString() : "";
long time = data.has("Time") ? data.get("Time").asLong() : 0L;
getTimeChangeQueue().add(new VoteTimeQueue(name, service, time));
}
}
} catch (Exception e) {
e.printStackTrace();
}
try {
for (String server : jsonStorage.getServers()) {
ArrayList<OfflineBungeeVote> votes = new ArrayList<>();
for (String num : jsonStorage.getServerVotes(server)) {
DataNode data = jsonStorage.getServerVotes(server, num);
if (data != null && data.isObject()) {
String name = data.has("Name") ? data.get("Name").asString() : "";
String uuid = data.has("UUID") ? data.get("UUID").asString() : "";
String service = data.has("Service") ? data.get("Service").asString() : "";
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(voteId, name, uuid, service, time, real, text));
}
}
cachedVotes.put(server, votes);
}
} catch (Exception e) {
e.printStackTrace();
}
try {
for (String player : jsonStorage.getPlayers()) {
ArrayList<OfflineBungeeVote> votes = new ArrayList<>();
for (String num : jsonStorage.getOnlineVotes(player)) {
DataNode data = jsonStorage.getOnlineVotes(player, num);
if (data != null && data.isObject()) {
String name = data.has("Name") ? data.get("Name").asString() : "";
String uuid = data.has("UUID") ? data.get("UUID").asString() : "";
String service = data.has("Service") ? data.get("Service").asString() : "";
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(voteId, name, uuid, service, time, real, text));
}
}
cachedOnlineVotes.put(player, votes);
}
} catch (Exception e) {
e.printStackTrace();
}
}
// log vote cache load summary
debug1("Loaded " + cachedVotes.size() + " server vote caches.");
int totalServerVotes = cachedVotes.values().stream().mapToInt(ArrayList::size).sum();
debug1("Loaded " + totalServerVotes + " total server votes.");
debug1("Loaded " + cachedOnlineVotes.size() + " online vote caches.");
int totalOnlineVotes = cachedOnlineVotes.values().stream().mapToInt(ArrayList::size).sum();
debug1("Loaded " + totalOnlineVotes + " total online votes.");
debug1("Loaded " + timeChangeQueue.size() + " timed votes.");
}
private final boolean useMySQL;
private ProxyVoteCacheTable voteCacheTable;
private ProxyTimedVoteCacheTable timedVoteCacheTable;
private ProxyOnlineVoteCacheTable onlineVoteCacheTable;
private IVoteCache jsonStorage;
public abstract void logInfo1(String msg);
public abstract void logSevere1(String msg);
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,
boolean debug, IVoteCache jsonStorage) {
this.useMySQL = useMySQL;
if (useMySQL) {
if (useExistingConnection) {
voteCacheTable = new ProxyVoteCacheTable(mysql, mysqlConfig.getTablePrefix(), debug) {
@Override
public void logSevere(String string) {
logSevere1(string);
}
@Override
public void logInfo(String string) {
logInfo1(string);
}
@Override
public void debug(Throwable t) {
if (debug)
debug1(t);
}
@Override
public void debug(String text) {
debug1(text);
}
};
timedVoteCacheTable = new ProxyTimedVoteCacheTable(mysql, mysqlConfig.getTablePrefix(), debug) {
@Override
public void logSevere(String string) {
logSevere1(string);
}
@Override
public void logInfo(String string) {
logInfo1(string);
}
@Override
public void debug(Throwable t) {
if (debug)
debug1(t);
}
@Override
public void debug(String text) {
debug1(text);
}
};
onlineVoteCacheTable = new ProxyOnlineVoteCacheTable(mysql, mysqlConfig.getTablePrefix(), debug) {
@Override
public void logSevere(String string) {
logSevere1(string);
}
@Override
public void logInfo(String string) {
logInfo1(string);
}
@Override
public void debug(Throwable t) {
if (debug)
debug1(t);
}
@Override
public void debug(String text) {
debug1(text);
}
};
} else {
voteCacheTable = new ProxyVoteCacheTable(mysqlConfig, debug) {
@Override
public void logSevere(String string) {
logSevere1(string);
}
@Override
public void logInfo(String string) {
logInfo1(string);
}
@Override
public void debug(Throwable t) {
if (debug)
debug1(t);
}
@Override
public void debug(String text) {
debug1(text);
}
};
timedVoteCacheTable = new ProxyTimedVoteCacheTable(voteCacheTable.getMysql(),
mysqlConfig.getTablePrefix(), debug) {
@Override
public void logSevere(String string) {
logSevere1(string);
}
@Override
public void logInfo(String string) {
logInfo1(string);
}
@Override
public void debug(Throwable t) {
if (debug)
debug1(t);
}
@Override
public void debug(String text) {
debug1(text);
}
};
onlineVoteCacheTable = new ProxyOnlineVoteCacheTable(voteCacheTable.getMysql(),
mysqlConfig.getTablePrefix(), debug) {
@Override
public void logSevere(String string) {
logSevere1(string);
}
@Override
public void logInfo(String string) {
logInfo1(string);
}
@Override
public void debug(Throwable t) {
if (debug)
debug1(t);
}
@Override
public void debug(String text) {
debug1(text);
}
};
}
} else {
this.jsonStorage = jsonStorage;
}
}
public String[] getCachedVotesServers() {
return cachedVotes.keySet().toArray(new String[0]);
}
public void removeServerVotes(String server, ArrayList<OfflineBungeeVote> removed) {
for (OfflineBungeeVote vote : removed) {
for (Map.Entry<String, ArrayList<OfflineBungeeVote>> entry : cachedVotes.entrySet()) {
if (entry.getKey().equals(server)) {
entry.getValue().removeIf(v -> v.getUuid().equals(vote.getUuid())
&& v.getService().equals(vote.getService()) && v.getTime() == vote.getTime());
}
}
if (useMySQL) {
voteCacheTable.removeVote(vote, server);
} else {
jsonStorage.removeVote(server, vote);
jsonStorage.save();
}
}
}
public void removeOnlineVotes(ArrayList<OfflineBungeeVote> removed) {
for (OfflineBungeeVote vote : removed) {
for (Map.Entry<String, ArrayList<OfflineBungeeVote>> entry : cachedOnlineVotes.entrySet()) {
entry.getValue().removeIf(v -> v.getUuid().equals(vote.getUuid())
&& v.getService().equals(vote.getService()) && v.getTime() == vote.getTime());
}
if (useMySQL) {
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,226 +0,0 @@
package com.bencodez.votingplugin.proxy.global.multiproxy;
import java.io.File;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import com.bencodez.advancedcore.api.misc.encryption.EncryptionHandler;
import com.bencodez.advancedcore.bungeeapi.redis.RedisHandler;
import com.bencodez.advancedcore.bungeeapi.redis.RedisListener;
import com.bencodez.advancedcore.bungeeapi.sockets.ClientHandler;
import com.bencodez.advancedcore.bungeeapi.sockets.SocketHandler;
import com.bencodez.advancedcore.bungeeapi.sockets.SocketReceiver;
import com.bencodez.votingplugin.proxy.BungeeMessageData;
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 clearVote(String string);
public void close() {
if (multiproxySocketHandler != null) {
multiproxySocketHandler.closeConnection();
multiproxySocketHandler = null;
}
if (multiProxyRedis != null && !getMultiProxyRedisUseExistingConnection()) {
multiProxyRedis.close();
}
}
public abstract boolean getDebug();
public abstract EncryptionHandler getEncryptionHandler();
public abstract MultiProxyMethod getMultiProxyMethod();
public abstract String getMultiProxyPassword();
public abstract String getMultiProxyRedisHost();
public abstract int getMultiProxyRedisPort();
public abstract boolean getMultiProxyRedisUseExistingConnection();
public abstract String getMultiProxyServerName();
public abstract Collection<String> getMultiProxyServers();
public abstract MultiProxyServerSocketConfiguration getMultiProxyServersConfiguration(String s);
public abstract String getMultiProxySocketHostHost();
public abstract int getMultiProxySocketHostPort();
public abstract boolean getMultiProxySupportEnabled();
public abstract String getMultiProxyUsername();
public abstract File getPluginDataFolder();
public abstract boolean getPrimaryServer();
public abstract List<String> getProxyServers();
public abstract RedisHandler getRedisHandler();
public abstract String getVersion();
public void loadMultiProxySupport() {
if (getMultiProxySupportEnabled()) {
if (getMultiProxyMethod().equals(MultiProxyMethod.SOCKETS)) {
if (getEncryptionHandler() == null) {
setEncryptionHandler(new EncryptionHandler(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 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());
}
}
public void login(String uuid, String playerName) {
if (!getMultiProxySupportEnabled() || getPrimaryServer()) {
return;
}
sendMultiProxyServerMessage("Login", uuid, playerName);
}
public abstract void logInfo(String msg);
public abstract void runAsnc(Runnable runnable);
public void sendMultiProxyServerMessage(String... messageData) {
if (getMultiProxyMethod().equals(MultiProxyMethod.SOCKETS)) {
for (ClientHandler h : multiproxyClientHandles.values()) {
h.sendMessage(messageData);
}
} else if (getMultiProxyMethod().equals(MultiProxyMethod.REDIS)) {
for (String server : getProxyServers()) {
multiProxyRedis.sendMessage("VotingPluginProxy_" + server, messageData);
}
}
}
public abstract void setEncryptionHandler(EncryptionHandler encryptionHandler);
public abstract void triggerVote(String player, String service, boolean realVote, boolean timeQueue, long queueTime,
BungeeMessageData text, String uuid);
}
@@ -0,0 +1,266 @@
// File: com/bencodez/votingplugin/proxy/multiproxy/MultiProxyHandler.java
package com.bencodez.votingplugin.proxy.multiproxy;
import java.io.File;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.bencodez.simpleapi.encryption.EncryptionHandler;
import com.bencodez.simpleapi.servercomm.codec.JsonEnvelope;
import com.bencodez.simpleapi.servercomm.redis.RedisHandler;
import com.bencodez.simpleapi.servercomm.redis.RedisListener;
import com.bencodez.simpleapi.servercomm.sockets.ClientHandler;
import com.bencodez.simpleapi.servercomm.sockets.SocketHandler;
import com.bencodez.simpleapi.servercomm.sockets.SocketReceiver;
import com.bencodez.votingplugin.proxy.VoteTotalsSnapshot;
import com.bencodez.votingplugin.proxy.VotingPluginWire;
import lombok.Getter;
public abstract class MultiProxyHandler {
private HashMap<String, ClientHandler> multiproxyClientHandles;
private SocketHandler multiproxySocketHandler;
@Getter
private RedisHandler multiProxyRedis;
public MultiProxyHandler() {
}
public abstract void addNonVotedPlayerCache(String uuid, String playerName);
public abstract void clearVote(String uuid);
public void close() {
if (multiproxySocketHandler != null) {
multiproxySocketHandler.closeConnection();
multiproxySocketHandler = null;
}
if (multiProxyRedis != null && !getMultiProxyRedisUseExistingConnection()) {
multiProxyRedis.close();
}
}
public abstract boolean getDebug();
public abstract EncryptionHandler getEncryptionHandler();
public abstract MultiProxyMethod getMultiProxyMethod();
public abstract String getMultiProxyPassword();
public abstract String getMultiProxyRedisHost();
public abstract int getMultiProxyRedisPort();
public abstract int getMultiProxyRedisDbIndex();
public abstract boolean getMultiProxyRedisUseExistingConnection();
public abstract String getMultiProxyServerName();
public abstract Collection<String> getMultiProxyServers();
public abstract MultiProxyServerSocketConfiguration getMultiProxyServersConfiguration(String s);
public abstract String getMultiProxySocketHostHost();
public abstract int getMultiProxySocketHostPort();
public abstract boolean getMultiProxySupportEnabled();
public abstract String getMultiProxyUsername();
public abstract File getPluginDataFolder();
public abstract boolean getPrimaryServer();
public abstract List<String> getProxyServers();
public abstract RedisHandler getRedisHandler();
public abstract String getVersion();
public abstract void logInfo(String msg);
public abstract void runAsnc(Runnable runnable);
public abstract void setEncryptionHandler(EncryptionHandler encryptionHandler);
public abstract void triggerVote(String player, String service, boolean realVote, boolean timeQueue, long queueTime,
VoteTotalsSnapshot text, String uuid);
public void loadMultiProxySupport() {
if (!getMultiProxySupportEnabled()) {
return;
}
if (getMultiProxyMethod().equals(MultiProxyMethod.SOCKETS)) {
if (getEncryptionHandler() == null) {
setEncryptionHandler(
new EncryptionHandler("VotingPlugin", new File(getPluginDataFolder(), "secretkey.key")));
}
if (multiproxySocketHandler != null) {
multiproxySocketHandler.closeConnection();
multiproxySocketHandler = null;
}
multiproxySocketHandler = new SocketHandler(getVersion(), getMultiProxySocketHostHost(),
getMultiProxySocketHostPort(), getEncryptionHandler(), getDebug()) {
@Override
public void log(String str) {
logInfo(str);
}
};
multiproxySocketHandler.add(new SocketReceiver() {
@Override
public void onReceiveEnvelope(JsonEnvelope envelope) {
handleEnvelope(envelope);
}
});
multiproxyClientHandles = new HashMap<>();
for (String s : getMultiProxyServers()) {
MultiProxyServerSocketConfiguration d = getMultiProxyServersConfiguration(s);
multiproxyClientHandles.put(s,
new ClientHandler(d.getHost(), d.getPort(), getEncryptionHandler(), getDebug()));
}
} else {
if (getMultiProxyRedisUseExistingConnection() && getRedisHandler() != null) {
multiProxyRedis = getRedisHandler();
} else {
multiProxyRedis = new RedisHandler(getMultiProxyRedisHost(), getMultiProxyRedisPort(),
getMultiProxyUsername(), getMultiProxyPassword(), getMultiProxyRedisDbIndex()) {
@Override
public void debug(String message) {
if (getDebug()) {
logInfo("MultiProxyRedis: " + message);
}
}
};
}
runAsnc(() -> {
RedisListener listener = multiProxyRedis.createEnvelopeListener(
"VotingPluginProxy_" + getMultiProxyServerName(), (ch, env) -> handleEnvelope(env));
multiProxyRedis.loadListener(listener);
});
}
logInfo("Loaded multi-proxy support: " + getMultiProxyMethod().toString());
}
public void login(String uuid, String playerName) {
if (!getMultiProxySupportEnabled() || getPrimaryServer()) {
return;
}
sendMultiProxyEnvelope(VotingPluginWire.login(playerName, uuid, getMultiProxyServerName()));
}
public void sendClearVote(String uuid, String playerName) {
if (!getMultiProxySupportEnabled()) {
return;
}
sendMultiProxyEnvelope(VotingPluginWire.clearVote(uuid, playerName, getMultiProxyServerName()));
}
public void sendStatus() {
if (!getMultiProxySupportEnabled()) {
return;
}
sendMultiProxyEnvelope(VotingPluginWire.status(getMultiProxyServerName()));
}
public void sendMultiProxyEnvelope(JsonEnvelope envelope) {
if (envelope == null) {
return;
}
if (getMultiProxyMethod().equals(MultiProxyMethod.SOCKETS)) {
if (multiproxyClientHandles == null) {
return;
}
for (ClientHandler h : multiproxyClientHandles.values()) {
h.sendEnvelope(envelope);
}
} else if (getMultiProxyMethod().equals(MultiProxyMethod.REDIS)) {
if (multiProxyRedis == null) {
return;
}
for (String server : getProxyServers()) {
multiProxyRedis.publishEnvelope("VotingPluginProxy_" + server, envelope);
}
}
}
private void handleEnvelope(JsonEnvelope envelope) {
if (envelope == null) {
return;
}
final String sub = envelope.getSubChannel() == null ? "" : envelope.getSubChannel();
Map<String, String> f = envelope.getFields();
if (f == null) {
f = new HashMap<>();
}
if (sub.equalsIgnoreCase(VotingPluginWire.SUB_STATUS)) {
logInfo("Multi-proxy status message received");
return;
}
if (sub.equalsIgnoreCase(VotingPluginWire.SUB_CLEAR_VOTE)) {
final String uuid = f.getOrDefault(VotingPluginWire.K_UUID, "");
final String player = f.getOrDefault(VotingPluginWire.K_PLAYER, "");
final String server = f.getOrDefault(VotingPluginWire.K_SERVER, "");
if (!uuid.isEmpty()) {
clearVote(uuid);
if (getPrimaryServer()) {
sendMultiProxyEnvelope(VotingPluginWire.clearVotePrimary(uuid, player, server));
}
}
return;
}
if (sub.equalsIgnoreCase(VotingPluginWire.SUB_CLEAR_VOTE_PRIMARY)) {
final String uuid = f.getOrDefault(VotingPluginWire.K_UUID, "");
if (!uuid.isEmpty()) {
clearVote(uuid);
}
return;
}
if (sub.equalsIgnoreCase(VotingPluginWire.SUB_LOGIN)) {
final String player = f.getOrDefault(VotingPluginWire.K_PLAYER, "");
final String uuid = f.getOrDefault(VotingPluginWire.K_UUID, "");
if (!player.isEmpty() && !uuid.isEmpty()) {
addNonVotedPlayerCache(uuid, player);
}
return;
}
if (sub.equalsIgnoreCase(VotingPluginWire.SUB_VOTE) || sub.equalsIgnoreCase(VotingPluginWire.SUB_VOTE_ONLINE)) {
final String player = f.getOrDefault(VotingPluginWire.K_PLAYER, "");
final String uuid = f.getOrDefault(VotingPluginWire.K_UUID, "");
final String service = f.getOrDefault(VotingPluginWire.K_SERVICE, "");
final String totals = f.getOrDefault(VotingPluginWire.K_TOTALS, "");
final boolean realVote = Boolean.parseBoolean(f.getOrDefault(VotingPluginWire.K_REAL_VOTE, "false"));
if (!player.isEmpty() && !uuid.isEmpty() && !service.isEmpty()) {
triggerVote(player, service, realVote, true, 0L, VoteTotalsSnapshot.parseStorage(totals), uuid);
}
return;
}
if (getDebug()) {
logInfo("MultiProxy ignored subchannel: " + sub + " fields=" + f);
}
}
}
@@ -1,4 +1,4 @@
package com.bencodez.votingplugin.proxy.global.multiproxy;
package com.bencodez.votingplugin.proxy.multiproxy;
public enum MultiProxyMethod {
SOCKETS, REDIS;
@@ -1,4 +1,4 @@
package com.bencodez.votingplugin.proxy.global.multiproxy;
package com.bencodez.votingplugin.proxy.multiproxy;
public interface MultiProxyServerSocketConfiguration {
String getHost();
Loaded 100 of 195 files, more files were not shown because too many files have changed in this diff. Show more