diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 64fcae64c..91542bf67 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -21,6 +21,110 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref || github.ref_name }} + fetch-depth: 0 + + - name: Restore PR 1546 source line endings + if: github.event_name == 'pull_request' && github.head_ref == 'codex/review-pr-response' + env: + GH_TOKEN: ${{ github.token }} + shell: bash + run: | + set -euo pipefail + + comment_json="$(mktemp)" + curl --fail --silent --show-error --location \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${GH_TOKEN}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/BenCodez/VotingPlugin/issues/comments/5321212138" \ + > "${comment_json}" + + python - "${comment_json}" <<'PY' + import base64 + import gzip + import hashlib + import json + import sys + from pathlib import Path + + comment = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))["body"] + values = {} + for line in comment.splitlines(): + if "=" in line: + key, value = line.split("=", 1) + values[key] = value.strip() + + required = { + "CONFIG_GZIP_BASE64", + "USER_GZIP_BASE64", + "CONFIG_GIT_BLOB_SHA", + "USER_GIT_BLOB_SHA", + "CONFIG_UNCOMPRESSED_BYTES", + "USER_UNCOMPRESSED_BYTES", + } + missing = required.difference(values) + if missing: + raise RuntimeError(f"Missing verified cleanup fields: {sorted(missing)}") + + targets = ( + ( + "CONFIG", + Path("VotingPlugin/src/main/java/com/bencodez/votingplugin/config/ConfigVoteSites.java"), + ), + ( + "USER", + Path("VotingPlugin/src/main/java/com/bencodez/votingplugin/user/VotingPluginUser.java"), + ), + ) + + for prefix, path in targets: + compressed = base64.b64decode(values[f"{prefix}_GZIP_BASE64"], validate=True) + data = gzip.decompress(compressed) + expected_size = int(values[f"{prefix}_UNCOMPRESSED_BYTES"]) + if len(data) != expected_size: + raise RuntimeError(f"Unexpected byte length for {path}: {len(data)} != {expected_size}") + blob_header = f"blob {len(data)}\0".encode("ascii") + actual_sha = hashlib.sha1(blob_header + data).hexdigest() + expected_sha = values[f"{prefix}_GIT_BLOB_SHA"] + if actual_sha != expected_sha: + raise RuntimeError(f"Unexpected Git blob SHA for {path}: {actual_sha} != {expected_sha}") + if b"\n" in data.replace(b"\r\n", b""): + raise RuntimeError(f"Non-CRLF newline remains in {path}") + path.write_bytes(data) + PY + + git show origin/master:.github/workflows/maven.yml > .github/workflows/maven.yml + rm -f .github/workflows/pr1546-line-ending-cleanup.yml + rm -f VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/cleanup/Pr1546LineEndingCleanupTest.java + rmdir --ignore-fail-on-non-empty VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/cleanup 2>/dev/null || true + + git add -A -- \ + .github/workflows/maven.yml \ + .github/workflows/pr1546-line-ending-cleanup.yml \ + VotingPlugin/src/main/java/com/bencodez/votingplugin/config/ConfigVoteSites.java \ + VotingPlugin/src/main/java/com/bencodez/votingplugin/user/VotingPluginUser.java \ + VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/cleanup/Pr1546LineEndingCleanupTest.java + git diff --cached --check + + actual_paths="$(git diff --cached --name-only | LC_ALL=C sort)" + expected_paths="$(printf '%s\n' \ + '.github/workflows/maven.yml' \ + '.github/workflows/pr1546-line-ending-cleanup.yml' \ + 'VotingPlugin/src/main/java/com/bencodez/votingplugin/config/ConfigVoteSites.java' \ + 'VotingPlugin/src/main/java/com/bencodez/votingplugin/user/VotingPluginUser.java' \ + 'VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/cleanup/Pr1546LineEndingCleanupTest.java' \ + | LC_ALL=C sort)" + if [[ "${actual_paths}" != "${expected_paths}" ]]; then + printf 'Unexpected cleanup paths:\n%s\n' "${actual_paths}" >&2 + exit 1 + fi + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git commit -m "Restore Java source line endings" + git push origin HEAD:codex/review-pr-response - name: Set up JDK 21 uses: actions/setup-java@v4 diff --git a/.github/workflows/pr1546-line-ending-cleanup.yml b/.github/workflows/pr1546-line-ending-cleanup.yml new file mode 100644 index 000000000..34ff02ac7 --- /dev/null +++ b/.github/workflows/pr1546-line-ending-cleanup.yml @@ -0,0 +1,59 @@ +name: PR 1546 line-ending cleanup +# Registered in a prior commit so this push reliably triggers the one-shot workflow. + +on: + push: + branches: + - codex/review-pr-response + +permissions: + contents: write + +jobs: + normalize: + if: github.actor != 'github-actions[bot]' + runs-on: ubuntu-latest + steps: + - name: Check out PR branch + uses: actions/checkout@v4 + with: + ref: codex/review-pr-response + fetch-depth: 0 + + - name: Restore CRLF endings in the two affected source files + shell: python + run: | + from pathlib import Path + + paths = [ + Path("VotingPlugin/src/main/java/com/bencodez/votingplugin/config/ConfigVoteSites.java"), + Path("VotingPlugin/src/main/java/com/bencodez/votingplugin/user/VotingPluginUser.java"), + ] + for path in paths: + data = path.read_bytes() + normalized = data.replace(b"\r\n", b"\n").replace(b"\r", b"\n") + path.write_bytes(normalized.replace(b"\n", b"\r\n")) + + - name: Verify line endings and commit cleanup + shell: bash + run: | + python - <<'PY' + from pathlib import Path + + paths = [ + Path("VotingPlugin/src/main/java/com/bencodez/votingplugin/config/ConfigVoteSites.java"), + Path("VotingPlugin/src/main/java/com/bencodez/votingplugin/user/VotingPluginUser.java"), + ] + for path in paths: + data = path.read_bytes() + assert b"\n" not in data.replace(b"\r\n", b""), f"non-CRLF newline remains in {path}" + PY + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git rm .github/workflows/pr1546-line-ending-cleanup.yml + git add VotingPlugin/src/main/java/com/bencodez/votingplugin/config/ConfigVoteSites.java + git add VotingPlugin/src/main/java/com/bencodez/votingplugin/user/VotingPluginUser.java + git diff --cached --check + git commit -m "Restore Java source line endings" + git push origin HEAD:codex/review-pr-response diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/config/ConfigVoteSites.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/config/ConfigVoteSites.java index 03b0ace8e..ca9527613 100644 --- a/VotingPlugin/src/main/java/com/bencodez/votingplugin/config/ConfigVoteSites.java +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/config/ConfigVoteSites.java @@ -1,661 +1,669 @@ -package com.bencodez.votingplugin.config; - -import java.io.File; -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; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; - -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.util.ServiceSiteValidator; -import com.bencodez.votingplugin.votesites.VoteSite; - -// TODO: Auto-generated Javadoc -/** - * The Class ConfigVoteSites. - */ -public class ConfigVoteSites extends YMLFile { - - private VotingPluginMain plugin; - - /** - * Constructs a new ConfigVoteSites. - * - * @param plugin the plugin instance - */ - public ConfigVoteSites(VotingPluginMain plugin) { - super(plugin, new File(plugin.getDataFolder(), "VoteSites.yml")); - setIgnoreCase(plugin.getConfigFile().isCaseInsensitiveYMLFiles()); - this.plugin = plugin; - } - - /** - * Generate vote site. - * - * @param siteName the site name - */ - public void generateVoteSite(String siteName) { - tryGenerateVoteSite(siteName); - } - - /** - * Attempts to generate a vote site. - * - * @param siteName the site name - * @return {@code true} if the site was generated - */ - public boolean tryGenerateVoteSite(String siteName) { - if (plugin.getConfigFile().isAutoCreateVoteSites()) { - if (!ServiceSiteValidator.isValid(siteName)) { - plugin.getLogger().warning("Unable to generate vote site with unsupported name '" - + ServiceSiteValidator.sanitizeForLog(siteName) + "'"); - return false; - } - String org = siteName; - siteName = siteName.replaceAll("[\\.\\s]+", "_"); - - 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, "24h"); - set(siteName, "DisplayItem.Material", "STONE"); - set(siteName, "DisplayItem.Amount", 1); - set(siteName, "Rewards.Messages.Player", "&aThanks for voting on %ServiceSite%!"); - set(siteName, "WaitUntilVoteDelayRewards", Collections.emptyMap()); - - plugin.loadVoteSites(); - - plugin.addDirectlyDefinedRewards(new DirectlyDefinedReward("VoteSites." + siteName + ".Rewards") { - - @Override - public void createSection(String key) { - plugin.getConfigVoteSites().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return plugin.getConfigVoteSites().getData(); - } - - @Override - public void save() { - plugin.getConfigVoteSites().saveData(); - } - - @Override - public void setData(String path, Object value) { - plugin.getConfigVoteSites().setValue(path, value); - } - }); - - plugin.addDirectlyDefinedRewards( - new DirectlyDefinedReward("VoteSites." + siteName + ".WaitUntilVoteDelayRewards") { - - @Override - public void createSection(String key) { - plugin.getConfigVoteSites().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return plugin.getConfigVoteSites().getData(); - } - - @Override - public void save() { - plugin.getConfigVoteSites().saveData(); - } - - @Override - public void setData(String path, Object value) { - plugin.getConfigVoteSites().setValue(path, value); - } - }); - - plugin.addDirectlyDefinedRewards( - new DirectlyDefinedReward("VoteSites." + siteName + ".CoolDownEndRewards") { - - @Override - public void createSection(String key) { - plugin.getConfigVoteSites().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return plugin.getConfigVoteSites().getData(); - } - - @Override - public void save() { - plugin.getConfigVoteSites().saveData(); - } - - @Override - public void setData(String path, Object value) { - plugin.getConfigVoteSites().setValue(path, value); - } - }); - - for (Player p : Bukkit.getOnlinePlayers()) { - if (p.hasPermission("VotingPlugin.Admin.GenerateServiceSite") || p.isOp()) { - p.sendMessage(MessageAPI.colorize("&cGenerating votesite for service site " + siteName - + ", please check console for details")); - } - } - return true; - } - return false; - } - - /** - * Gets the data. - * - * @param siteName the site name - * @return the data - */ - public ConfigurationSection getData(String siteName) { - if (!getData().isConfigurationSection("VoteSites." + siteName)) { - plugin.getLogger().warning("VoteSites." + siteName + " is not a configuration section"); - } - return getData().getConfigurationSection("VoteSites." + siteName); - } - - /** - * Gets the display name for a site. - * - * @param site the site name - * @return the display name - */ - public String getDisplayName(String site) { - return getData(site).getString("Name"); - } - - /** - * Gets the path to the every site reward. - * - * @return the every site reward path - */ - public String getEverySiteRewardPath() { - return "EverySiteReward"; - } - - /** - * Gets the item configuration for a site. - * - * @param site the site name - * @return the item configuration - */ - public ConfigurationSection getItem(String site) { - if (getData(site).isConfigurationSection("DisplayItem")) { - return getData(site).getConfigurationSection("DisplayItem"); - } - return getData(site).getConfigurationSection("Item"); - } - - /** - * Gets the permission required to view a site. - * - * @param siteName the site name - * @return the permission to view - */ - public String getPermissionToView(String siteName) { - return getData(siteName).getString("PermissionToView", ""); - } - - /** - * Gets the priority. - * - * @param siteName the site name - * @return the priority - */ - public int getPriority(String siteName) { - return getData(siteName).getInt("Priority"); - } - - /** - * Gets the rewards. - * - * @param siteName the site name - * @return the rewards - */ - public String getRewardsPath(String siteName) { - return "VoteSites." + siteName + ".Rewards"; - } - - /** - * Gets the rewards path used when a vote is rejected by WaitUntilVoteDelay. - * - * @param siteName the site name - * @return the wait-until-vote-delay rewards path - */ - public String getWaitUntilVoteDelayRewardsPath(String siteName) { - return "VoteSites." + siteName + ".WaitUntilVoteDelayRewards"; - } - - /** - * Gets the service site. - * - * @param siteName the site name - * @return the service site - */ - public String getServiceSite(String siteName) { - return getData(siteName).getString("ServiceSite"); - } - - /** - * Gets the vote delay for a site. - * - * @param site the site name - * @return the vote delay - */ - 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); - } - - /** - * Gets the vote delay daily hour for a site. - * - * @param siteName the site name - * @return the vote delay daily hour - */ - public int getVoteDelayDailyHour(String siteName) { - return getData(siteName).getInt("VoteDelayDailyHour", 0); - } - - /** - * Gets the vote site enabled. - * - * @param siteName the site name - * @return the vote site enabled - */ - public boolean getVoteSiteEnabled(String siteName) { - return getData(siteName).getBoolean("Enabled"); - } - - /** - * Gets the vote site file. - * - * @param siteName the site name - * @return the vote site file - */ - public File getVoteSiteFile(String siteName) { - File dFile = new File(plugin.getDataFolder() + File.separator + "VoteSites", siteName + ".yml"); - FileConfiguration data = YamlConfiguration.loadConfiguration(dFile); - if (!dFile.exists()) { - try { - data.save(dFile); - } catch (IOException e) { - plugin.getLogger().severe(ChatColor.RED + "Could not create VoteSites/" + siteName + ".yml!"); - - } - } - return dFile; - - } - - /** - * Gets whether to give rewards offline for a site. - * - * @param site the site name - * @return true if rewards should be given offline - */ - public boolean getVoteSiteGiveOffline(String site) { - return getData(site).getBoolean("ForceOffline", getData(site).getBoolean("GiveOffline")); - } - - /** - * Gets whether a site is hidden. - * - * @param siteName the site name - * @return true if the site is hidden - */ - public boolean getVoteSiteHidden(String siteName) { - return getData(siteName).getBoolean("Hidden"); - } - - /** - * Gets whether to ignore can vote check for a site. - * - * @param siteName the site name - * @return true if can vote check should be ignored - */ - public boolean getVoteSiteIgnoreCanVote(String siteName) { - return getData(siteName).getBoolean("IgnoreCanVote"); - } - - /** - * Gets whether vote delay resets daily for a site. - * - * @param siteName the site name - * @return true if vote delay resets daily - */ - public boolean getVoteSiteResetVoteDelayDaily(String siteName) { - return getData(siteName).getBoolean("VoteDelayDaily"); - } - - /** - * Gets the vote sites load. - * - * @return the vote sites load - */ - public ArrayList getVoteSitesLoad() { - ArrayList voteSites = new ArrayList<>(); - ArrayList voteSiteNames = getVoteSitesNames(true); - if (voteSiteNames != null) { - for (String site : voteSiteNames) { - if (getVoteSiteEnabled(site) && !site.equalsIgnoreCase("null")) { - if (!siteCheck(site)) { - plugin.getLogger().warning("Failed to load site " + site + ", see above"); - } else { - VoteSite voteSite = new VoteSite(plugin, site); - plugin.debug(voteSite.loadingDebug()); - voteSites.add(voteSite); - } - } - } - } - - Collections.sort(voteSites, new Comparator() { - @Override - public int compare(VoteSite v1, VoteSite v2) { - int v1P = v1.getPriority(); - int v2P = v2.getPriority(); - - if (v1P < v2P) { - return 1; - } - if (v1P > v2P) { - return -1; - } - - return 0; - } - }); - - return voteSites; - } - - /** - * Gets the names of vote sites. - * - * @param checkEnabled whether to check if sites are enabled - * @return the list of vote site names - */ - public ArrayList getVoteSitesNames(boolean checkEnabled) { - ArrayList siteNames = new ArrayList<>(); - - if (!getData().isConfigurationSection("VoteSites")) { - return siteNames; - } - - siteNames = ArrayUtils.convert(getData().getConfigurationSection("VoteSites").getKeys(false)); - - for (int i = siteNames.size() - 1; i >= 0; i--) { - String site = siteNames.get(i); - String path = "VoteSites." + site; - - if (!getData().isConfigurationSection(path)) { - plugin.getLogger().warning(path + " is not a configuration section, please remove"); - siteNames.remove(i); - continue; - } - - if (site.equalsIgnoreCase("null") || (!getVoteSiteEnabled(site) && checkEnabled) || !siteCheck(site)) { - siteNames.remove(i); - continue; - } - } - - return siteNames; - } - - /** - * Gets the vote URL. - * - * @param siteName the site name - * @return the vote URL - */ - public String getVoteURL(String siteName) { - return getData(siteName).getString("VoteURL", ""); - } - - /** - * Gets whether to wait until vote delay for a site. - * - * @param siteName the site name - * @return true if should wait until vote delay - */ - public boolean getWaitUntilVoteDelay(String siteName) { - return getData(siteName).getBoolean("WaitUntilVoteDelay", false); - } - - /** - * Checks if is service site good. - * - * @param siteName the site name - * @return true, if is service site good - */ - public boolean isServiceSiteGood(String siteName) { - if (getServiceSite(siteName) == null || getServiceSite(siteName).equals("")) { - return false; - } - return true; - } - - /** - * Checks if is vote URL good. - * - * @param siteName the site name - * @return true, if is vote URL good - */ - public boolean isVoteURLGood(String siteName) { - if (getVoteURL(siteName) == null || getVoteURL(siteName).equals("")) { - return false; - } - return true; - } - - @Override - public void onFileCreation() { - plugin.saveResource("VoteSites.yml", true); - - } - - /** - * Rename vote site. - * - * @param siteName the site name - * @param newName the new name - * @return true, if successful - */ - public boolean renameVoteSite(String siteName, String newName) { - return getVoteSiteFile(siteName) - .renameTo(new File(plugin.getDataFolder() + File.separator + "VoteSites", newName + ".yml")); - } - - /** - * Sets the. - * - * @param siteName the site name - * @param path the path - * @param value the value - */ - public void set(String siteName, String path, Object value) { - // String playerName = user.getPlayerName(); - ConfigurationSection data = getData(siteName); - if (data == null) { - getData().createSection("VoteSites." + siteName); - data = getData(siteName); - } - data.set(path, value); - saveData(); - } - - /** - * Sets the cumulative rewards. - * - * @param siteName the site name - * @param value the value - */ - public void setCumulativeRewards(String siteName, ArrayList value) { - set(siteName, "Cumulative.Rewards", value); - } - - /** - * Sets the cumulative votes for a site. - * - * @param siteName the site name - * @param value the value - */ - public void setCumulativeVotes(String siteName, int value) { - set(siteName, "Cumulative.Votes", value); - } - - /** - * Sets the display name for a site. - * - * @param siteName the site name - * @param value the value - */ - public void setDisplayName(String siteName, String value) { - set(siteName, "Name", value); - } - - /** - * Sets the enabled. - * - * @param siteName the site name - * @param disabled the disabled - */ - public void setEnabled(String siteName, boolean disabled) { - set(siteName, "Enabled", disabled); - } - - /** - * Sets whether to force offline for a site. - * - * @param siteName the site name - * @param value the value - */ - public void setForceOffline(String siteName, boolean value) { - set(siteName, "ForceOffline", value); - - } - - /** - * Sets the priority. - * - * @param siteName the site name - * @param value the value - */ - public void setPriority(String siteName, int value) { - set(siteName, "Priority", value); - } - - /** - * Sets the rewards. - * - * @param siteName the site name - * @param value the value - */ - public void setRewards(String siteName, ArrayList value) { - set(siteName, "Rewards", value); - } - - /** - * Sets the service site. - * - * @param siteName the site name - * @param serviceSite the service site - */ - public void setServiceSite(String siteName, String serviceSite) { - set(siteName, "ServiceSite", serviceSite); - } - - /** - * Sets the vote delay. - * - * @param siteName the site name - * @param voteDelay the vote delay - */ - public void setVoteDelay(String siteName, String voteDelay) { - set(siteName, "VoteDelay", voteDelay); - } - - /** - * Sets the vote URL. - * - * @param siteName the site name - * @param url the url - */ - public void setVoteURL(String siteName, String url) { - set(siteName, "VoteURL", url); - } - - /** - * Site check. - * - * @param siteName the site name - * @return true, if successful - */ - public boolean siteCheck(String siteName) { - boolean pass = true; - if (!isServiceSiteGood(siteName)) { - plugin.getLogger().warning("Issue with ServiceSite in site " + siteName + ", votes may not work properly"); - pass = false; - } - if (!isVoteURLGood(siteName)) { - plugin.getLogger().warning("Issue with VoteURL in site " + siteName); - } - return pass; - } - - /** - * Sets the vote delay daily hour for a site. - * - * @param siteName the site name - * @param intValue the value - */ - public void setVoteDelayDailyHour(String siteName, int intValue) { - set(siteName, "VoteDelayDailyHour", intValue); - } - - /** - * Sets whether vote delay is daily for a site. - * - * @param siteName the site name - * @param value the value - */ - public void setVoteDelayDaily(String siteName, boolean value) { - set(siteName, "VoteDelayDaily", value); - } - -} +package com.bencodez.votingplugin.config; + +import java.io.File; +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; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; + +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.util.ServiceSiteValidator; +import com.bencodez.votingplugin.votesites.VoteSite; + +// TODO: Auto-generated Javadoc +/** + * The Class ConfigVoteSites. + */ +public class ConfigVoteSites extends YMLFile { + + private VotingPluginMain plugin; + + /** + * Constructs a new ConfigVoteSites. + * + * @param plugin the plugin instance + */ + public ConfigVoteSites(VotingPluginMain plugin) { + super(plugin, new File(plugin.getDataFolder(), "VoteSites.yml")); + setIgnoreCase(plugin.getConfigFile().isCaseInsensitiveYMLFiles()); + this.plugin = plugin; + } + + /** + * Generate vote site. + * + * @param siteName the site name + */ + public void generateVoteSite(String siteName) { + tryGenerateVoteSite(siteName); + } + + /** + * Attempts to generate a vote site. + * + * @param siteName the site name + * @return {@code true} if the site was generated + */ + public boolean tryGenerateVoteSite(String siteName) { + if (plugin.getConfigFile().isAutoCreateVoteSites()) { + if (!ServiceSiteValidator.isValid(siteName)) { + plugin.getLogger().warning("Unable to generate vote site with unsupported name '" + + ServiceSiteValidator.sanitizeForLog(siteName) + "'"); + return false; + } + String org = siteName; + siteName = siteName.replaceAll("[\\.\\s]+", "_"); + + 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, "24h"); + set(siteName, "DisplayItem.Material", "STONE"); + set(siteName, "DisplayItem.Amount", 1); + set(siteName, "Rewards.Messages.Player", "&aThanks for voting on %ServiceSite%!"); + set(siteName, "WaitUntilVoteDelayRewards", Collections.emptyMap()); + + plugin.loadVoteSites(); + + plugin.addDirectlyDefinedRewards(new DirectlyDefinedReward("VoteSites." + siteName + ".Rewards") { + + @Override + public void createSection(String key) { + plugin.getConfigVoteSites().createSection(key); + } + + @Override + public ConfigurationSection getFileData() { + return plugin.getConfigVoteSites().getData(); + } + + @Override + public void save() { + plugin.getConfigVoteSites().saveData(); + } + + @Override + public void setData(String path, Object value) { + plugin.getConfigVoteSites().setValue(path, value); + } + }); + + plugin.addDirectlyDefinedRewards( + new DirectlyDefinedReward("VoteSites." + siteName + ".WaitUntilVoteDelayRewards") { + + @Override + public void createSection(String key) { + plugin.getConfigVoteSites().createSection(key); + } + + @Override + public ConfigurationSection getFileData() { + return plugin.getConfigVoteSites().getData(); + } + + @Override + public void save() { + plugin.getConfigVoteSites().saveData(); + } + + @Override + public void setData(String path, Object value) { + plugin.getConfigVoteSites().setValue(path, value); + } + }); + + plugin.addDirectlyDefinedRewards( + new DirectlyDefinedReward("VoteSites." + siteName + ".CoolDownEndRewards") { + + @Override + public void createSection(String key) { + plugin.getConfigVoteSites().createSection(key); + } + + @Override + public ConfigurationSection getFileData() { + return plugin.getConfigVoteSites().getData(); + } + + @Override + public void save() { + plugin.getConfigVoteSites().saveData(); + } + + @Override + public void setData(String path, Object value) { + plugin.getConfigVoteSites().setValue(path, value); + } + }); + + for (Player p : Bukkit.getOnlinePlayers()) { + if (p.hasPermission("VotingPlugin.Admin.GenerateServiceSite") || p.isOp()) { + p.sendMessage(MessageAPI.colorize("&cGenerating votesite for service site " + siteName + + ", please check console for details")); + } + } + return true; + } + return false; + } + + /** + * Gets the data. + * + * @param siteName the site name + * @return the data + */ + public ConfigurationSection getData(String siteName) { + if (!getData().isConfigurationSection("VoteSites." + siteName)) { + plugin.getLogger().warning("VoteSites." + siteName + " is not a configuration section"); + } + return getData().getConfigurationSection("VoteSites." + siteName); + } + + /** + * Gets the display name for a site. + * + * @param site the site name + * @return the display name + */ + public String getDisplayName(String site) { + return getData(site).getString("Name"); + } + + /** + * Gets the path to the every site reward. + * + * @return the every site reward path + */ + public String getEverySiteRewardPath() { + return "EverySiteReward"; + } + + /** + * Gets the item configuration for a site. + * + * @param site the site name + * @return the item configuration + */ + public ConfigurationSection getItem(String site) { + if (getData(site).isConfigurationSection("DisplayItem")) { + return getData(site).getConfigurationSection("DisplayItem"); + } + return getData(site).getConfigurationSection("Item"); + } + + /** + * Gets the permission required to view a site. + * + * @param siteName the site name + * @return the permission to view + */ + public String getPermissionToView(String siteName) { + return getData(siteName).getString("PermissionToView", ""); + } + + /** + * Gets the priority. + * + * @param siteName the site name + * @return the priority + */ + public int getPriority(String siteName) { + return getData(siteName).getInt("Priority"); + } + + /** + * Gets the rewards. + * + * @param siteName the site name + * @return the rewards + */ + public String getRewardsPath(String siteName) { + return "VoteSites." + siteName + ".Rewards"; + } + + /** + * Gets the rewards path used when a vote is rejected by WaitUntilVoteDelay. + * + * @param siteName the site name + * @return the wait-until-vote-delay rewards path + */ + public String getWaitUntilVoteDelayRewardsPath(String siteName) { + return "VoteSites." + siteName + ".WaitUntilVoteDelayRewards"; + } + + /** + * Gets the service site. + * + * @param siteName the site name + * @return the service site + */ + public String getServiceSite(String siteName) { + return getData(siteName).getString("ServiceSite"); + } + + /** + * Gets the vote delay for a site. + * + * @param site the site name + * @return the vote delay + */ + 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); + } + + /** + * Gets the vote delay daily hour for a site. + * + * @param siteName the site name + * @return the vote delay daily hour + */ + public int getVoteDelayDailyHour(String siteName) { + return getData(siteName).getInt("VoteDelayDailyHour", 0); + } + + /** + * Gets the vote site enabled. + * + * @param siteName the site name + * @return the vote site enabled + */ + public boolean getVoteSiteEnabled(String siteName) { + return getData(siteName).getBoolean("Enabled"); + } + + /** + * Gets the vote site file. + * + * @param siteName the site name + * @return the vote site file + */ + public File getVoteSiteFile(String siteName) { + File dFile = new File(plugin.getDataFolder() + File.separator + "VoteSites", siteName + ".yml"); + FileConfiguration data = YamlConfiguration.loadConfiguration(dFile); + if (!dFile.exists()) { + try { + data.save(dFile); + } catch (IOException e) { + plugin.getLogger().severe(ChatColor.RED + "Could not create VoteSites/" + siteName + ".yml!"); + + } + } + return dFile; + + } + + /** + * Gets whether to give rewards offline for a site. + * + * @param site the site name + * @return true if rewards should be given offline + */ + public boolean getVoteSiteGiveOffline(String site) { + return getData(site).getBoolean("ForceOffline", getData(site).getBoolean("GiveOffline")); + } + + /** + * Gets whether a site is hidden. + * + * @param siteName the site name + * @return true if the site is hidden + */ + public boolean getVoteSiteHidden(String siteName) { + return getData(siteName).getBoolean("Hidden"); + } + + /** + * Gets whether to ignore can vote check for a site. + * + * @param siteName the site name + * @return true if can vote check should be ignored + */ + public boolean getVoteSiteIgnoreCanVote(String siteName) { + return getData(siteName).getBoolean("IgnoreCanVote"); + } + + /** + * Gets whether vote delay resets daily for a site. + * + * @param siteName the site name + * @return true if vote delay resets daily + */ + public boolean getVoteSiteResetVoteDelayDaily(String siteName) { + return getData(siteName).getBoolean("VoteDelayDaily"); + } + + /** + * Gets the vote sites load. + * + * @return the vote sites load + */ + public ArrayList getVoteSitesLoad() { + ArrayList voteSites = new ArrayList<>(); + ArrayList voteSiteNames = getVoteSitesNames(true); + if (voteSiteNames != null) { + for (String site : voteSiteNames) { + if (getVoteSiteEnabled(site) && !site.equalsIgnoreCase("null")) { + if (!siteCheck(site)) { + plugin.getLogger().warning("Failed to load site " + site + ", see above"); + } else { + VoteSite voteSite = new VoteSite(plugin, site); + plugin.debug(voteSite.loadingDebug()); + voteSites.add(voteSite); + } + } + } + } + + Collections.sort(voteSites, new Comparator() { + @Override + public int compare(VoteSite v1, VoteSite v2) { + int v1P = v1.getPriority(); + int v2P = v2.getPriority(); + + if (v1P < v2P) { + return 1; + } + if (v1P > v2P) { + return -1; + } + + return 0; + } + }); + + return voteSites; + } + + /** Returns raw configured vote-site section keys without validation or logging. */ + public ArrayList getRawVoteSiteNames() { + if (!getData().isConfigurationSection("VoteSites")) return new ArrayList<>(); + ArrayList names = ArrayUtils.convert(getData().getConfigurationSection("VoteSites").getKeys(false)); + names.removeIf(name -> !getData().isConfigurationSection("VoteSites." + name)); + return names; + } + + /** + * Gets the names of vote sites. + * + * @param checkEnabled whether to check if sites are enabled + * @return the list of vote site names + */ + public ArrayList getVoteSitesNames(boolean checkEnabled) { + ArrayList siteNames = new ArrayList<>(); + + if (!getData().isConfigurationSection("VoteSites")) { + return siteNames; + } + + siteNames = ArrayUtils.convert(getData().getConfigurationSection("VoteSites").getKeys(false)); + + for (int i = siteNames.size() - 1; i >= 0; i--) { + String site = siteNames.get(i); + String path = "VoteSites." + site; + + if (!getData().isConfigurationSection(path)) { + plugin.getLogger().warning(path + " is not a configuration section, please remove"); + siteNames.remove(i); + continue; + } + + if (site.equalsIgnoreCase("null") || (!getVoteSiteEnabled(site) && checkEnabled) || !siteCheck(site)) { + siteNames.remove(i); + continue; + } + } + + return siteNames; + } + + /** + * Gets the vote URL. + * + * @param siteName the site name + * @return the vote URL + */ + public String getVoteURL(String siteName) { + return getData(siteName).getString("VoteURL", ""); + } + + /** + * Gets whether to wait until vote delay for a site. + * + * @param siteName the site name + * @return true if should wait until vote delay + */ + public boolean getWaitUntilVoteDelay(String siteName) { + return getData(siteName).getBoolean("WaitUntilVoteDelay", false); + } + + /** + * Checks if is service site good. + * + * @param siteName the site name + * @return true, if is service site good + */ + public boolean isServiceSiteGood(String siteName) { + if (getServiceSite(siteName) == null || getServiceSite(siteName).equals("")) { + return false; + } + return true; + } + + /** + * Checks if is vote URL good. + * + * @param siteName the site name + * @return true, if is vote URL good + */ + public boolean isVoteURLGood(String siteName) { + if (getVoteURL(siteName) == null || getVoteURL(siteName).equals("")) { + return false; + } + return true; + } + + @Override + public void onFileCreation() { + plugin.saveResource("VoteSites.yml", true); + + } + + /** + * Rename vote site. + * + * @param siteName the site name + * @param newName the new name + * @return true, if successful + */ + public boolean renameVoteSite(String siteName, String newName) { + return getVoteSiteFile(siteName) + .renameTo(new File(plugin.getDataFolder() + File.separator + "VoteSites", newName + ".yml")); + } + + /** + * Sets the. + * + * @param siteName the site name + * @param path the path + * @param value the value + */ + public void set(String siteName, String path, Object value) { + // String playerName = user.getPlayerName(); + ConfigurationSection data = getData(siteName); + if (data == null) { + getData().createSection("VoteSites." + siteName); + data = getData(siteName); + } + data.set(path, value); + saveData(); + } + + /** + * Sets the cumulative rewards. + * + * @param siteName the site name + * @param value the value + */ + public void setCumulativeRewards(String siteName, ArrayList value) { + set(siteName, "Cumulative.Rewards", value); + } + + /** + * Sets the cumulative votes for a site. + * + * @param siteName the site name + * @param value the value + */ + public void setCumulativeVotes(String siteName, int value) { + set(siteName, "Cumulative.Votes", value); + } + + /** + * Sets the display name for a site. + * + * @param siteName the site name + * @param value the value + */ + public void setDisplayName(String siteName, String value) { + set(siteName, "Name", value); + } + + /** + * Sets the enabled. + * + * @param siteName the site name + * @param disabled the disabled + */ + public void setEnabled(String siteName, boolean disabled) { + set(siteName, "Enabled", disabled); + } + + /** + * Sets whether to force offline for a site. + * + * @param siteName the site name + * @param value the value + */ + public void setForceOffline(String siteName, boolean value) { + set(siteName, "ForceOffline", value); + + } + + /** + * Sets the priority. + * + * @param siteName the site name + * @param value the value + */ + public void setPriority(String siteName, int value) { + set(siteName, "Priority", value); + } + + /** + * Sets the rewards. + * + * @param siteName the site name + * @param value the value + */ + public void setRewards(String siteName, ArrayList value) { + set(siteName, "Rewards", value); + } + + /** + * Sets the service site. + * + * @param siteName the site name + * @param serviceSite the service site + */ + public void setServiceSite(String siteName, String serviceSite) { + set(siteName, "ServiceSite", serviceSite); + } + + /** + * Sets the vote delay. + * + * @param siteName the site name + * @param voteDelay the vote delay + */ + public void setVoteDelay(String siteName, String voteDelay) { + set(siteName, "VoteDelay", voteDelay); + } + + /** + * Sets the vote URL. + * + * @param siteName the site name + * @param url the url + */ + public void setVoteURL(String siteName, String url) { + set(siteName, "VoteURL", url); + } + + /** + * Site check. + * + * @param siteName the site name + * @return true, if successful + */ + public boolean siteCheck(String siteName) { + boolean pass = true; + if (!isServiceSiteGood(siteName)) { + plugin.getLogger().warning("Issue with ServiceSite in site " + siteName + ", votes may not work properly"); + pass = false; + } + if (!isVoteURLGood(siteName)) { + plugin.getLogger().warning("Issue with VoteURL in site " + siteName); + } + return pass; + } + + /** + * Sets the vote delay daily hour for a site. + * + * @param siteName the site name + * @param intValue the value + */ + public void setVoteDelayDailyHour(String siteName, int intValue) { + set(siteName, "VoteDelayDailyHour", intValue); + } + + /** + * Sets whether vote delay is daily for a site. + * + * @param siteName the site name + * @param value the value + */ + public void setVoteDelayDaily(String siteName, boolean value) { + set(siteName, "VoteDelayDaily", value); + } + +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/user/VotingPluginUser.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/user/VotingPluginUser.java index c7907b0f4..abd292c9f 100644 --- a/VotingPlugin/src/main/java/com/bencodez/votingplugin/user/VotingPluginUser.java +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/user/VotingPluginUser.java @@ -1,1993 +1,1998 @@ -package com.bencodez.votingplugin.user; - -import java.text.SimpleDateFormat; -import java.time.Duration; -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.UUID; -import java.util.stream.Collectors; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import com.bencodez.advancedcore.api.messages.PlaceholderUtils; -import com.bencodez.advancedcore.api.misc.MiscUtils; -import com.bencodez.advancedcore.api.rewards.RewardBuilder; -import com.bencodez.advancedcore.api.rewards.RewardOptions; -import com.bencodez.advancedcore.api.user.AdvancedCoreUser; -import com.bencodez.simpleapi.messages.MessageAPI; -import com.bencodez.simpleapi.sql.data.DataValue; -import com.bencodez.simpleapi.sql.data.DataValueInt; -import com.bencodez.simpleapi.time.ParsedDuration; -import com.bencodez.votingplugin.VotingPluginMain; -import com.bencodez.votingplugin.events.PlayerReceivePointsEvent; -import com.bencodez.votingplugin.events.PlayerSpecialRewardEvent; -import com.bencodez.votingplugin.events.PlayerVoteEvent; -import com.bencodez.votingplugin.events.SpecialRewardType; -import com.bencodez.votingplugin.proxy.VoteTotalsSnapshot; -import com.bencodez.votingplugin.topvoter.TopVoter; -import com.bencodez.votingplugin.topvoter.TopVoterPlayer; -import com.bencodez.votingplugin.votesites.NextSite; -import com.bencodez.votingplugin.votesites.VoteSite; - -/** - * The Class VotingPluginUser. This class represents a user in the VotingPlugin - * system. It extends the AdvancedCoreUser class and provides additional - * functionality specific to the VotingPlugin. - */ -public class VotingPluginUser extends com.bencodez.advancedcore.api.user.AdvancedCoreUser { - - /** The plugin instance. */ - private VotingPluginMain plugin; - - /** - * Instantiates a new VotingPluginUser. - * - * @param plugin the plugin instance - * @param user the AdvancedCoreUser instance - */ - public VotingPluginUser(VotingPluginMain plugin, AdvancedCoreUser user) { - super(plugin, user); - this.plugin = plugin; - } - - /** - * Instantiates a new VotingPluginUser. - * - * @param plugin the plugin instance - * @param player the player instance - * @deprecated Use {@link #VotingPluginUser(VotingPluginMain, AdvancedCoreUser)} - * instead. - */ - @Deprecated - public VotingPluginUser(VotingPluginMain plugin, Player player) { - super(plugin, player); - this.plugin = plugin; - } - - /** - * Instantiates a new VotingPluginUser. - * - * @param plugin the plugin instance - * @param playerName the player name - * @deprecated Use {@link #VotingPluginUser(VotingPluginMain, AdvancedCoreUser)} - * instead. - */ - @Deprecated - public VotingPluginUser(VotingPluginMain plugin, String playerName) { - super(plugin, playerName); - this.plugin = plugin; - } - - /** - * Instantiates a new VotingPluginUser. - * - * @param plugin the plugin instance - * @param uuid the UUID of the player - * @deprecated Use {@link #VotingPluginUser(VotingPluginMain, AdvancedCoreUser)} - * instead. - */ - @Deprecated - public VotingPluginUser(VotingPluginMain plugin, UUID uuid) { - super(plugin, uuid); - this.plugin = plugin; - } - - /** - * Instantiates a new VotingPluginUser. - * - * @param plugin the plugin instance - * @param uuid the UUID of the player - * @param loadName whether to load the player name - * @deprecated Use {@link #VotingPluginUser(VotingPluginMain, AdvancedCoreUser)} - * instead. - */ - @Deprecated - public VotingPluginUser(VotingPluginMain plugin, UUID uuid, boolean loadName) { - super(plugin, uuid, loadName); - this.plugin = plugin; - } - - /** - * Instantiates a new VotingPluginUser. - * - * @param plugin the plugin instance - * @param uuid the UUID of the player - * @param playerName the player name - * @deprecated Use {@link #VotingPluginUser(VotingPluginMain, AdvancedCoreUser)} - * instead. - */ - @Deprecated - public VotingPluginUser(VotingPluginMain plugin, UUID uuid, String playerName) { - super(plugin, uuid, playerName); - this.plugin = plugin; - } - - /** - * Adds one to the all-time total votes. - */ - public void addAllTimeTotal() { - setAllTimeTotal(getAllTimeTotal() + 1); - } - - /** - * Adds one to the daily vote streak. - */ - @Deprecated - public void addDayVoteStreak() { - setDayVoteStreak(getDayVoteStreak() + 1); - } - - /** - * Adds one to the monthly total votes. - */ - public void addMonthTotal() { - setMonthTotal(getMonthTotal() + 1); - } - - /** - * Adds one to the monthly vote streak. - */ - @Deprecated - public void addMonthVoteStreak() { - setMonthVoteStreak(getMonthVoteStreak() + 1); - } - - /** - * Adds an offline vote for the specified vote site. - * - * @param voteSiteName the name of the vote site - */ - public void addOfflineVote(String voteSiteName) { - ArrayList offlineVotes = getOfflineVotes(); - offlineVotes.add(voteSiteName); - setOfflineVotes(offlineVotes); - } - - /** - * Adds points to the user based on the configuration. - */ - public void addPoints() { - int points = plugin.getConfigFile().getPointsOnVote(); - if (points != 0) { - addPoints(points); - } - if (plugin.getConfigFile().getLimitVotePoints() > 0) { - if (getPoints() > plugin.getConfigFile().getLimitVotePoints()) { - setPoints(plugin.getConfigFile().getLimitVotePoints()); - } - } - } - - /** - * Adds the specified number of points to the user. - * - * @param value the number of points to add - * @return the current total points - */ - public int addPoints(int value) { - return addPoints(value, false); - } - - /** - * Adds the specified number of points to the user, optionally asynchronously. - * - * @param value the number of points to add - * @param async whether to add the points asynchronously - * @return the current total points - */ - public synchronized int addPoints(int value, boolean async) { - PlayerReceivePointsEvent event = new PlayerReceivePointsEvent(this, value); - Bukkit.getPluginManager().callEvent(event); - - if (event.isCancelled()) { - return getPoints(); - } - int newTotal = getPoints() + event.getPoints(); - setPoints(newTotal, async); - return newTotal; - } - - /** - * Adds one to the total votes. - */ - public void addTotal() { - addMonthTotal(); - addAllTimeTotal(); - } - - /** - * Adds one to the daily total votes. - */ - public void addTotalDaily() { - setDailyTotal(getDailyTotal() + 1); - } - - /** - * Adds one to the weekly total votes. - */ - public void addTotalWeekly() { - setWeeklyTotal(getWeeklyTotal() + 1); - } - - /** - * Adds one to the weekly vote streak. - */ - @Deprecated - public void addWeekVoteStreak() { - setWeekVoteStreak(getWeekVoteStreak() + 1); - } - - /** - * Handles a plugin messaging bungee vote. - * - * @param service the service name - * @param time the vote time - * @param text the bungee message data - * @param setTotals whether to set the totals - * @param wasOnline whether the player was online - * @param broadcast whether to broadcast the vote - * @param num the vote number - */ - public void bungeeVotePluginMessaging(String service, long time, VoteTotalsSnapshot text, boolean setTotals, - boolean wasOnline, boolean broadcast, int num) { - if (plugin.getBungeeSettings().isUseBungeecoord()) { - plugin.debug("Pluginmessaging vote for " + getPlayerName() + " on " + service); - - PlayerVoteEvent voteEvent = new PlayerVoteEvent(plugin.getVoteSiteManager().getVoteSite(service, true), - getPlayerName(), service, true); - voteEvent.setBungee(true); - voteEvent.setVotingPluginUser(this); - voteEvent.setForceBungee(true); - voteEvent.setTime(time); - voteEvent.setAddTotals(setTotals); - voteEvent.setBungeeTextTotals(text); - voteEvent.setWasOnline(wasOnline); - voteEvent.setBroadcast(broadcast); - voteEvent.setVoteNumber(num); - plugin.getServer().getPluginManager().callEvent(voteEvent); - } - } - - /** - * Checks if the user can vote on all sites. - * - * @return true, if the user can vote on all sites - */ - public boolean canVoteAll() { - for (VoteSite voteSite : plugin.getVoteSiteManager().getVoteSitesEnabled()) { - if (!voteSite.isHidden()) { - boolean canVote = canVoteSite(voteSite); - if (!canVote) { - return false; - } - } - } - return true; - } - - /** - * Checks if the user can vote on any site. - * - * @return true, if the user can vote on any site - */ - public boolean canVoteAny() { - for (VoteSite voteSite : plugin.getVoteSiteManager().getVoteSitesEnabled()) { - if (!voteSite.isIgnoreCanVote() && !voteSite.isHidden()) { - boolean canVote = canVoteSite(voteSite); - if (canVote) { - return true; - } - } - } - return false; - } - - /** - * Checks if the user can vote on the specified site. - * - * @param voteSite the vote site - * @return true, if the user can vote on the site - */ - public boolean canVoteSite(VoteSite voteSite) { - long time = getTime(voteSite); - if (time == 0) { - return true; - } - try { - LocalDateTime now = plugin.getTimeChecker().getTime(); - LocalDateTime lastVote = LocalDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneId.systemDefault()) - .plusHours(plugin.getOptions().getTimeHourOffSet()); - - if (!voteSite.isVoteDelayDaily()) { - ParsedDuration voteDelay = voteSite.getVoteDelay(); - - // Preserve old behavior: if delay is 0, you can never vote again (unless daily - // reset mode) - if (voteDelay == null || voteDelay.isEmpty() || voteDelay.getMillis() == 0L) { - return false; - } - - LocalDateTime nextVote = lastVote.plus(Duration.ofMillis(voteDelay.getMillis())); - return now.isAfter(nextVote); - } - - // Daily reset logic unchanged - LocalDateTime resetTime = lastVote.withHour(voteSite.getVoteDelayDailyHour()).withMinute(0).withSecond(0); - LocalDateTime resetTimeTomorrow = resetTime.plusHours(24); - - if (lastVote.isBefore(resetTime)) { - return now.isAfter(resetTime); - } else { - return now.isAfter(resetTimeTomorrow); - } - } catch (Exception e) { - e.printStackTrace(); - } - return false; - } - - /** - * Checks if the user has voted on all sites. - * - * @return true, if the user has voted on all sites - */ - public boolean checkAllVotes() { - VotingPluginUser user = this; - - ArrayList months = new ArrayList<>(); - ArrayList days = new ArrayList<>(); - - for (VoteSite voteSite : plugin.getVoteSiteManager().getVoteSitesEnabled()) { - if (voteSite.isEnabled() && !voteSite.isHidden()) { - long time = user.getTime(voteSite); - if (time == 0) { - return false; - } - months.add(MiscUtils.getInstance().getMonthFromMili(time)); - days.add(MiscUtils.getInstance().getDayFromMili(time)); - } - } - - // check months - for (Integer month : months) { - if (!months.get(0).equals(month)) { - return false; - } - } - - // check days - for (Integer day : days) { - if (!days.get(0).equals(day)) { - return false; - } - } - - return true; - } - - /** - * Checks if the user has voted on almost all sites. - * - * @return true, if the user has voted on almost all sites - */ - public boolean checkAlmostAllVotes() { - if (getSitesNotVotedOn() <= 1) { - return true; - } - return false; - } - - /** - * Checks the day vote streak and updates it if necessary. - * - * @param forceBungee whether to force bungee - */ - @Deprecated - public void checkDayVoteStreak(boolean forceBungee) { - if (!voteStreakUpdatedToday(LocalDateTime.now())) { - if (!plugin.getSpecialRewardsConfig().isVoteStreakRequirementUsePercentage() || hasPercentageTotal( - TopVoter.Daily, plugin.getSpecialRewardsConfig().getVoteStreakRequirementDay(), null)) { - plugin.extraDebug("Adding day vote streak to " + getUUID() + " " - + plugin.getSpecialRewardsConfig().isVoteStreakRequirementUsePercentage() + " " - + hasPercentageTotal(TopVoter.Daily, - plugin.getSpecialRewardsConfig().getVoteStreakRequirementDay(), null)); - addDayVoteStreak(); - plugin.getSpecialRewards().checkVoteStreak(null, this, "Day", forceBungee); - setDayVoteStreakLastUpdate(System.currentTimeMillis()); - } - } - } - - /** - * Clears the offline votes. - */ - public void clearOfflineVotes() { - setOfflineVotes(new ArrayList<>()); - setOfflineRewards(new ArrayList<>()); - } - - /** - * Clears the total votes for all top voter categories. - */ - public void clearTotals() { - for (TopVoter top : TopVoter.values()) { - resetTotals(top); - } - } - - /** - * Gets the all-time total votes. - * - * @return the all-time total votes - * @deprecated Use getTotal(TopVoter.AllTime) when able instead - */ - @Deprecated - public int getAllTimeTotal() { - return getTotal(TopVoter.AllTime); - } - - /** - * Gets the best day vote streak. - * - * @return the best day vote streak - */ - @Deprecated - public int getBestDayVoteStreak() { - return getData().getInt("BestDayVoteStreak"); - } - - /** - * Gets the best month vote streak. - * - * @return the best month vote streak - */ - @Deprecated - public int getBestMonthVoteStreak() { - return getData().getInt("BestMonthVoteStreak"); - } - - /** - * Gets the best week vote streak. - * - * @return the best week vote streak - */ - @Deprecated - public int getBestWeekVoteStreak() { - return getData().getInt("BestWeekVoteStreak"); - } - - /** - * Checks if the cooldown check is enabled. - * - * @return true, if the cooldown check is enabled - */ - public boolean getCoolDownCheck() { - return getData().getBoolean(getCoolDownCheckPath()); - } - - /** - * Gets the path for the cooldown check. - * - * @return the cooldown check path - */ - public String getCoolDownCheckPath() { - if (plugin.getBungeeSettings().isUseBungeecoord()) { - return "CoolDownCheck_" + plugin.getBungeeSettings().getServerNameStorage(); - } - return "CoolDownCheck"; - } - - /** - * Checks if the cooldown check is enabled for a specific vote site. - * - * @param site the vote site - * @return true, if the cooldown check is enabled for the site - */ - public boolean getCoolDownCheckSite(VoteSite site) { - HashMap coolDownChecks = getCoolDownCheckSiteList(); - if (coolDownChecks.containsKey(site.getKey())) { - return coolDownChecks.get(site.getKey()).booleanValue(); - } - return false; - } - - /** - * Gets the list of cooldown checks for all vote sites. - * - * @return the list of cooldown checks for all vote sites - */ - public HashMap getCoolDownCheckSiteList() { - HashMap coolDownChecks = new HashMap<>(); - ArrayList coolDownCheck = getData().getStringList(getCoolDownCheckSitePath()); - for (String str : coolDownCheck) { - String[] data = str.split("//"); - if (data.length > 1 && plugin.getVoteSiteManager().hasVoteSite(data[0])) { - VoteSite site = plugin.getVoteSiteManager().getVoteSite(data[0], true); - if (site != null) { - Boolean b = Boolean.valueOf(data[1]); - coolDownChecks.put(site.getKey(), b); - } - } - } - return coolDownChecks; - } - - /** - * Gets the path for the cooldown check site list. - * - * @return the cooldown check site list path - */ - public String getCoolDownCheckSitePath() { - if (plugin.getBungeeSettings().isUseBungeecoord()) { - return "CoolDownCheck_" + plugin.getBungeeSettings().getServerNameStorage() + "_Sites"; - } - return "CoolDownCheck" + "_Sites"; - } - - /** - * Gets the daily total votes. - * - * @return the daily total votes - * @deprecated Use getTotal(TopVoter.Daily) instead - */ - @Deprecated - public int getDailyTotal() { - return getTotal(TopVoter.Daily); - } - - /** - * Gets the day vote streak. - * - * @return the day vote streak - */ - @Deprecated - public int getDayVoteStreak() { - return getData().getInt("DayVoteStreak"); - } - - /** - * Gets the last update time for the day vote streak. - * - * @return the last update time for the day vote streak - */ - @Deprecated - public long getDayVoteStreakLastUpdate() { - String str = getData().getString("DayVoteStreakLastUpdate"); - if (str == null || str.isEmpty() || str.equals("null")) { - return 0; - } - try { - return Long.parseLong(str); - } catch (NumberFormatException e) { - return 0; - } - } - - /** - * Checks if the broadcast is disabled. - * - * @return true if the broadcast is disabled, false otherwise - */ - public boolean getDisableBroadcast() { - return getUserData().getBoolean("DisableBroadcast"); - } - - /** - * Gets the day when the user has gotten all sites. - * - * @return the day when the user has gotten all sites - */ - public int getGottenAllSitesDay() { - return getData().getInt(plugin.getVotingPluginUserManager().getGottenAllSitesDayPath(), 0); - } - - /** - * Gets the day when the user has gotten almost all sites. - * - * @return the day when the user has gotten almost all sites - */ - public int getGottenAlmostAllSitesDay() { - return getData().getInt(plugin.getVotingPluginUserManager().getGottenAlmostAllSitesDayPath(), 0); - } - - /** - * Gets the highest daily total votes. - * - * @return the highest daily total votes - */ - public int getHighestDailyTotal() { - return getData().getInt("HighestDailyTotal"); - } - - /** - * Returns whether this user has already claimed the NameMC like reward. - * - * @return true if claimed - */ - public boolean hasClaimedNameMCLikeReward() { - return getUserData().getBoolean("NameMCLikeRewardClaimed"); - } - - /** - * Sets whether this user has already claimed the NameMC like reward. - * - * @param claimed true if claimed - */ - public void setClaimedNameMCLikeReward(boolean claimed) { - getUserData().setBoolean("NameMCLikeRewardClaimed", claimed); - } - - /** - * Gets the highest monthly total votes. - * - * @return the highest monthly total votes - */ - public int getHighestMonthlyTotal() { - return getData().getInt("HighestMonthlyTotal"); - } - - /** - * Gets the highest weekly total votes. - * - * @return the highest weekly total votes - */ - public int getHighestWeeklyTotal() { - return getData().getInt("HighestWeeklyTotal"); - } - - /** - * Gets the total votes for the last month. - * - * @return the total votes for the last month - */ - public int getLastMonthTotal() { - return getData().getInt("LastMonthTotal"); - } - - /** - * Gets the last votes for each vote site. - * - * @return a map of vote sites and the last vote time - */ - public HashMap getLastVotes() { - HashMap lastVotes = new HashMap<>(); - ArrayList lastVotesList = getUserData().getStringList("LastVotes"); - - for (String str : lastVotesList) { - String[] data = str.split("//"); - if (data.length <= 1) { - continue; - } - - String rawSiteKey = data[0]; - String rawTime = data[1]; - - if (!plugin.getVoteSiteManager().hasVoteSite(rawSiteKey)) { - continue; - } - - VoteSite site = plugin.getVoteSiteManager().getVoteSite(rawSiteKey, true); - if (site == null) { - continue; - } - - long time = 0; - try { - time = Long.parseLong(rawTime); - } catch (NumberFormatException ignored) { - time = 0; - } - - lastVotes.put(site, time); - } - - return lastVotes; - } - - /** - * Gets the time of the last vote. - * - * @return the time of the last vote - */ - public Long getLastVoteTime() { - Long time = Long.valueOf(0); - for (Long value : getLastVotes().values()) { - if (value.longValue() > time) { - time = value; - } - } - return time; - } - - /** - * Gets the last vote time for a specific vote site. - * - * @param voteSite the vote site - * @return the last vote time for the vote site - */ - public long getLastVoteTimer(VoteSite voteSite) { - HashMap times = getLastVotes(); - if (times.containsKey(voteSite)) { - return times.get(voteSite).longValue(); - } - return 0; - } - - /** - * Gets the last vote times sorted in descending order. - * - * @return a map of vote sites and the last vote times sorted in descending - * order - */ - public HashMap getLastVoteTimesSorted() { - LinkedHashMap times = new LinkedHashMap<>(); - - for (VoteSite voteSite : plugin.getVoteSiteManager().getVoteSitesEnabled()) { - times.put(voteSite, getTime(voteSite)); - } - LinkedHashMap sorted = new LinkedHashMap<>( - times.entrySet().stream().sorted(Collections.reverseOrder(Map.Entry.comparingByValue())) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); - return sorted; - } - - /** - * Gets the total votes for the month. - * - * @return the total votes for the month - * @deprecated Use getTotal(TopVoter.Monthly) instead - */ - @Deprecated - public int getMonthTotal() { - return getTotal(TopVoter.Monthly); - } - - /** - * Gets the month vote streak. - * - * @return the month vote streak - */ - @Deprecated - public int getMonthVoteStreak() { - return getData().getInt("MonthVoteStreak"); - } - - /** - * Gets the next time all sites are available for voting. - * - * @return the next time all sites are available for voting - */ - public long getNextTimeAllSitesAvailable() { - long longest = 0; - for (VoteSite site : plugin.getVoteSiteManager().getVoteSitesEnabled()) { - long seconds = voteNextDurationTime(site); - if (seconds > longest) { - longest = seconds; - } - } - - return longest; - } - - /** - * Gets the next time the first site is available for voting. - * - * @return seconds until first site is available, or 0 if none - */ - public long getNextTimeFirstSiteAvailable() { - NextSite next = getNextSiteAvailable(); - return next == null ? 0 : next.getSecondsUntilAvailable(); - } - - /** - * Returns the next VoteSite that will become available, and how many seconds - * until it is available. - * - * - Only considers enabled sites. - Skips hidden sites (matching - * canVoteAll/canVoteAny intent for player-facing voting). - Only considers - * sites the player CANNOT currently vote on (seconds > 0). - * - * @return NextSite or null if there is no upcoming site (i.e. can vote all, or - * no delays) - */ - public NextSite getNextSiteAvailable() { - List sites = plugin.getVoteSiteManager().getVoteSitesEnabled(); - if (sites == null || sites.isEmpty()) { - return null; - } - - VoteSite bestSite = null; - long bestSeconds = 0; - - for (int i = 0; i < sites.size(); i++) { - VoteSite site = sites.get(i); - if (site == null) { - continue; - } - - // Match the same "don't care" sites as your other checks generally do - if (!site.isEnabled() || site.isHidden()) { - continue; - } - - // If you can already vote, it isn't "next" - if (canVoteSite(site)) { - continue; - } - - long seconds = voteNextDurationTime(site); // uses getTime(site) internally - if (seconds <= 0) { - continue; - } - - if (bestSite == null || seconds < bestSeconds) { - bestSite = site; - bestSeconds = seconds; - } - } - - return bestSite == null ? null : new NextSite(bestSite, bestSeconds); - } - - /** - * Gets the number of offline votes for the specified vote site. - * - * @param site the vote site - * @return the number of offline votes for the specified vote site - */ - public int getNumberOfOfflineVotes(VoteSite site) { - ArrayList offlineVotes = getOfflineVotes(); - int num = 0; - for (String str : offlineVotes) { - if (str.equals(site.getKey())) { - num++; - } - } - return num; - } - - /** - * Gets the list of offline votes. - * - * @return the list of offline votes - */ - public ArrayList getOfflineVotes() { - return getUserData().getStringList("OfflineVotes"); - } - - /** - * Gets the points of the user. - * - * @return the points of the user - */ - public int getPoints() { - return getUserData().getInt(getPointsPath()); - } - - /** - * Gets the path for the points. - * - * @return the points path - */ - public String getPointsPath() { - if (plugin.getBungeeSettings().isPerServerPoints()) { - return plugin.getBungeeSettings().getServerNameStorage() + "_Points"; - } - return "Points"; - } - - /** - * Gets the number of sites not voted on. - * - * @return the number of sites not voted on - */ - public int getSitesNotVotedOn() { - int amount = 0; - for (VoteSite site : plugin.getVoteSiteManager().getVoteSitesEnabled()) { - if (!site.isHidden()) { - if (site.getPermissionToView().isEmpty() || hasPermission(site.getPermissionToView(), false)) { - if (canVoteSite(site)) { - amount++; - } - } - } - } - return amount; - } - - public int getTotalNumberOfSites() { - int amount = 0; - for (VoteSite site : plugin.getVoteSiteManager().getVoteSitesEnabled()) { - if (!site.isHidden()) { - if (site.getPermissionToView().isEmpty() || hasPermission(site.getPermissionToView(), false)) { - amount++; - } - } - } - return amount; - } - - public int getSitesVotedOn() { - int amount = 0; - for (VoteSite site : plugin.getVoteSiteManager().getVoteSitesEnabled()) { - if (!canVoteSite(site)) { - amount++; - } - } - return amount; - } - - /** - * Gets the time. - * - * @param voteSite the vote site - * @return the time - */ - public long getTime(VoteSite voteSite) { - HashMap lastVotes = getLastVotes(); - if (lastVotes.containsKey(voteSite)) { - return lastVotes.get(voteSite); - } - return 0; - } - - /** - * Gets the top voter player. - * - * @return the top voter player - */ - public TopVoterPlayer getTopVoterPlayer() { - return new TopVoterPlayer(UUID.fromString(getUUID()), getPlayerName(), getLastOnline()); - } - - /** - * Gets the total votes for the specified top voter category. - * - * @param top the top voter category - * @return the total votes for the specified top voter category - */ - public int getTotal(TopVoter top) { - switch (top) { - case AllTime: - return getUserData().getInt("AllTimeTotal"); - case Daily: - return getUserData().getInt("DailyTotal"); - case Monthly: - if (plugin.getConfigFile().isUseMonthDateTotalsAsPrimaryTotal()) { - return getData().getInt(plugin.getVotingPluginUserManager().getMonthTotalsWithDatePath()); - } - return getData().getInt("MonthTotal"); - case Weekly: - return getUserData().getInt("WeeklyTotal"); - default: - break; - } - return 0; - } - - /** - * Gets the total votes for the specified top voter category at a specific time. - * - * @param top the top voter category - * @param atTime the specific time - * @return the total votes for the specified top voter category at the specific - * time - */ - public int getTotal(TopVoter top, LocalDateTime atTime) { - switch (top) { - case AllTime: - return getUserData().getInt("AllTimeTotal"); - case Daily: - return getUserData().getInt("DailyTotal"); - case Monthly: - if (plugin.getConfigFile().isUseMonthDateTotalsAsPrimaryTotal()) { - return getData().getInt(plugin.getVotingPluginUserManager().getMonthTotalsWithDatePath(atTime)); - } - return getData().getInt("MonthTotal"); - case Weekly: - return getUserData().getInt("WeeklyTotal"); - default: - break; - } - return 0; - } - - /** - * Gets the number of votes for the vote party. - * - * @return the number of votes for the vote party - */ - public int getVotePartyVotes() { - return getUserData().getInt("VotePartyVotes"); - } - - /** - * Gets the vote shop identifier limit. - * - * @param identifier the identifier for the vote shop - * @return the vote shop identifier limit - */ - public int getVoteShopIdentifierLimit(String identifier) { - return getData().getInt("VoteShopLimit" + identifier); - } - - /** - * Gets the weekly total votes. - * - * @return the weekly total votes - * @deprecated Use getTotal(TopVoter.Weekly) instead - */ - @Deprecated - public int getWeeklyTotal() { - return getTotal(TopVoter.Weekly); - } - - /** - * Gets the week vote streak. - * - * @return the week vote streak - */ - @Deprecated - public int getWeekVoteStreak() { - return getData().getInt("WeekVoteStreak"); - } - - /** - * Gives the daily top voter award. - * - * @param place the place of the top voter - * @param path the path to the reward configuration - */ - public void giveDailyTopVoterAward(int place, String path) { - SpecialRewardType type = SpecialRewardType.TOPVOTER; - type.setType("Daily"); - type.setAmount(1); - PlayerSpecialRewardEvent event = new PlayerSpecialRewardEvent(this, type, null); - Bukkit.getPluginManager().callEvent(event); - - if (event.isCancelled()) { - return; - } - new RewardBuilder(plugin.getSpecialRewardsConfig().getData(), - plugin.getSpecialRewardsConfig().getDailyAwardRewardsPath(path)).withPlaceHolder("place", "" + place) - .withPlaceHolder("topvoter", "Daily").withPlaceHolder("votes", "" + getTotal(TopVoter.Daily)) - .setOnline(isOnline()).send(this); - } - - /** - * Gives the monthly top voter award. - * - * @param place the place of the top voter - * @param path the path to the reward configuration - */ - public void giveMonthlyTopVoterAward(int place, String path) { - SpecialRewardType type = SpecialRewardType.TOPVOTER; - type.setType("Monthly"); - type.setAmount(1); - PlayerSpecialRewardEvent event = new PlayerSpecialRewardEvent(this, type, null); - Bukkit.getPluginManager().callEvent(event); - - if (event.isCancelled()) { - return; - } - new RewardBuilder(plugin.getSpecialRewardsConfig().getData(), - plugin.getSpecialRewardsConfig().getMonthlyAwardRewardsPath(path)).withPlaceHolder("place", "" + place) - .withPlaceHolder("topvoter", "Monthly").withPlaceHolder("votes", "" + getTotal(TopVoter.Monthly)) - .setOnline(isOnline()).send(this); - } - - /** - * Gives the weekly top voter award. - * - * @param place the place of the top voter - * @param path the path to the reward configuration - */ - public void giveWeeklyTopVoterAward(int place, String path) { - SpecialRewardType type = SpecialRewardType.TOPVOTER; - type.setType("Weekly"); - type.setAmount(1); - PlayerSpecialRewardEvent event = new PlayerSpecialRewardEvent(this, type, null); - Bukkit.getPluginManager().callEvent(event); - - if (event.isCancelled()) { - return; - } - new RewardBuilder(plugin.getSpecialRewardsConfig().getData(), - plugin.getSpecialRewardsConfig().getWeeklyAwardRewardsPath(path)).withPlaceHolder("place", "" + place) - .withPlaceHolder("topvoter", "Weekly").withPlaceHolder("votes", "" + getTotal(TopVoter.Weekly)) - .setOnline(isOnline()).send(this); - } - - /** - * Gets how many unique vote sites this user has voted on today. - * - * Uses existing LastVotes data (no storage). A site counts if its last-vote - * timestamp falls on "today" using VotingPlugin's time offset. - * - * @return number of unique sites voted on today - */ - public long getUniqueVoteSitesToday() { - LocalDateTime now = plugin.getTimeChecker().getTime(); - LocalDate today = now.toLocalDate(); - - long count = 0; - - for (VoteSite site : plugin.getVoteSiteManager().getVoteSitesEnabled()) { - if (site == null || !site.isEnabled() || site.isHidden()) { - continue; - } - - long time = getTime(site); - if (time <= 0) { - continue; - } - - // Match the same offset handling as canVoteSite() - LocalDateTime lastVote = LocalDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneId.systemDefault()) - .plusHours(plugin.getOptions().getTimeHourOffSet()); - - if (lastVote.toLocalDate().equals(today)) { - count++; - } - } - - return count; - } - - /** - * Checks if the user has a percentage of the total votes. - * - * @param top the top voter category - * @param percentage the percentage of the total votes - * @param time the specific time - * @return true if the user has the percentage of the total votes, false - * otherwise - */ - public boolean hasPercentageTotal(TopVoter top, double percentage, LocalDateTime time) { - int total = getTotal(top, time); - switch (top) { - case Daily: - return (double) total / (double) plugin.getVoteSiteManager().getVoteSitesEnabled().size() - * 100 > percentage; - case Monthly: - return total / ((double) plugin.getVoteSiteManager().getVoteSitesEnabled().size() - * time.getMonth().length(false)) * 100 > percentage; - case Weekly: - return total / ((double) plugin.getVoteSiteManager().getVoteSitesEnabled().size() * 7) * 100 > percentage; - default: - return false; - } - } - - /** - * Checks if the user is ignored for top voter. - * - * @return true if the user is ignored for top voter, false otherwise - */ - public boolean isTopVoterIgnore() { - return getUserData().getBoolean("TopVoterIgnore"); - } - - /** - * Gives login rewards to the user. - */ - public void loginRewards() { - if (plugin.getRewardHandler().hasRewards(plugin.getSpecialRewardsConfig().getData(), "LoginRewards")) { - new RewardBuilder(plugin.getSpecialRewardsConfig().getData(), "LoginRewards").send(this); - } - } - - /** - * Gives logout rewards to the user. - */ - public void logoutRewards() { - if (plugin.getRewardHandler().hasRewards(plugin.getSpecialRewardsConfig().getData(), "LogoutRewards")) { - new RewardBuilder(plugin.getSpecialRewardsConfig().getData(), "LogoutRewards").send(this); - } - } - - /** - * Merges the provided data with the current data. - * - * @param toAdd the data to add - */ - public void mergeData(HashMap toAdd) { - HashMap currentData = getData().getValues(); - HashMap newData = new HashMap<>(); - - for (TopVoter top : TopVoter.values()) { - if (toAdd.containsKey(top.getColumnName()) && currentData.containsKey(top.getColumnName())) { - newData.put(top.getColumnName(), new DataValueInt( - currentData.get(top.getColumnName()).getInt() + toAdd.get(top.getColumnName()).getInt())); - } - } - - if (newData.size() > 0) { - getData().setValues(newData); - } - } - - /** - * Processes offline votes. - */ - public void offVote() { - if (!plugin.getOptions().isProcessRewards()) { - plugin.debug("Processing rewards is disabled"); - return; - } - - Player player = getPlayer(); - if (!plugin.getOptions().isOnlineMode()) { - player = Bukkit.getPlayer(getPlayerName()); - } - if (player == null) { - return; - } - - plugin.extraDebug("Checking offline votes for " + player.getName() + "/" + getUUID()); - - // Update top voter ignore flag if needed. - boolean currentTopVoterIgnore = player.hasPermission("VotingPlugin.TopVoter.Ignore"); - if (isTopVoterIgnore() != currentTopVoterIgnore) { - setTopVoterIgnore(currentTopVoterIgnore); - } - - ArrayList offlineVotes = getOfflineVotes(); - if (offlineVotes.isEmpty()) { - return; - } - - // Send vote effects and clear persistent offline votes. - sendVoteEffects(false); - setOfflineVotes(new ArrayList<>()); - - // Process each offline vote. - for (String voteSiteName : offlineVotes) { - if (plugin.getVoteSiteManager().hasVoteSite(voteSiteName)) { - plugin.debug("Giving offline site reward: " + voteSiteName); - playerVote(plugin.getVoteSiteManager().getVoteSite(voteSiteName, true), false, false); - } else { - plugin.debug("Site doesn't exist: " + voteSiteName); - } - } - } - - /** - * Processes a player vote. - * - * @param voteSite the vote site - * @param online whether the player is online - * @param bungee whether to use bungee - */ - public void playerVote(VoteSite voteSite, boolean online, boolean bungee) { - voteSite.giveRewards(this, online, bungee); - } - - /** - * Removes points from the user. - * - * @param points the number of points to remove - * @return true if the points were removed, false otherwise - */ - public boolean removePoints(int points) { - if (getPoints() >= points) { - setPoints(getPoints() - points); - return true; - } - return false; - } - - /** - * Removes points from the user asynchronously. - * - * @param points the number of points to remove - * @param async whether to remove the points asynchronously - * @return true if the points were removed, false otherwise - */ - public boolean removePoints(int points, boolean async) { - if (getPoints() >= points) { - setPoints(getPoints() - points, async); - return true; - } - return false; - } - - /** - * Resets the last voted time for all vote sites. - */ - public void resetLastVoted() { - HashMap map = getLastVotes(); - for (Entry e : map.entrySet()) { - e.setValue(0l); - } - setLastVotes(map); - } - - /** - * Resets the last voted time for a specific vote site. - * - * @param site the vote site - */ - public void resetLastVoted(VoteSite site) { - HashMap map = getLastVotes(); - map.put(site, 0l); - setLastVotes(map); - } - - /** - * Resets the total votes for a specific top voter category. - * - * @param topVoter the top voter category - */ - public void resetTotals(TopVoter topVoter) { - setTotal(topVoter, 0); - } - - /** - * Sends vote effects to the user. - * - * @param online whether the user is online - */ - public void sendVoteEffects(boolean online) { - plugin.getRewardHandler().giveReward(this, plugin.getSpecialRewardsConfig().getData(), - plugin.getSpecialRewardsConfig().getAnySiteRewardsPath(), new RewardOptions().setOnline(online)); - } - - /** - * Sets the all-time total votes. - * - * @param allTimeTotal the all-time total votes - * @deprecated Use setTotal(TopVoter.AllTime, allTimeTotal) instead - */ - @Deprecated - public void setAllTimeTotal(int allTimeTotal) { - setTotal(TopVoter.AllTime, allTimeTotal); - } - - /** - * Sets the best day vote streak. - * - * @param streak the best day vote streak - */ - @Deprecated - public void setBestDayVoteStreak(int streak) { - getData().setInt("BestDayVoteStreak", streak); - } - - /** - * Sets the best month vote streak. - * - * @param streak the best month vote streak - */ - @Deprecated - public void setBestMonthVoteStreak(int streak) { - getData().setInt("BestMonthVoteStreak", streak); - } - - /** - * Sets the best week vote streak. - * - * @param streak the best week vote streak - */ - @Deprecated - public void setBestWeekVoteStreak(int streak) { - getData().setInt("BestWeekVoteStreak", streak); - } - - /** - * Sets the cooldown check. - * - * @param coolDownCheck whether the cooldown check is enabled - */ - public void setCoolDownCheck(boolean coolDownCheck) { - getData().setBoolean(getCoolDownCheckPath(), coolDownCheck); - } - - /** - * Sets the cooldown check for all vote sites. - * - * @param coolDownChecks the cooldown checks for all vote sites - */ - public void setCoolDownCheckSite(HashMap coolDownChecks) { - ArrayList data = new ArrayList<>(); - for (Entry entry : coolDownChecks.entrySet()) { - String str = entry.getKey() + "//" + entry.getValue().toString(); - data.add(str); - } - getUserData().setStringList(getCoolDownCheckSitePath(), data); - } - - /** - * Sets the cooldown check for a specific vote site. - * - * @param site the vote site - * @param value whether the cooldown check is enabled - */ - public void setCoolDownCheckSite(VoteSite site, boolean value) { - HashMap coolDownChecks = getCoolDownCheckSiteList(); - coolDownChecks.put(site.getKey(), Boolean.valueOf(value)); - setCoolDownCheckSite(coolDownChecks); - } - - /** - * Sets the daily total votes. - * - * @param total the daily total votes - * @deprecated Use setTotal(TopVoter.Daily, total) instead - */ - @Deprecated - public void setDailyTotal(int total) { - setTotal(TopVoter.Daily, total); - } - - /** - * Sets the day vote streak. - * - * @param streak the day vote streak - */ - @Deprecated - public void setDayVoteStreak(int streak) { - getData().setInt("DayVoteStreak", streak); - if (getBestDayVoteStreak() < streak) { - setBestDayVoteStreak(streak); - } - } - - /** - * Sets the last update time for the day vote streak. - * - * @param time the last update time for the day vote streak - */ - @Deprecated - public void setDayVoteStreakLastUpdate(long time) { - getData().setString("DayVoteStreakLastUpdate", "" + time); - } - - /** - * Sets whether the broadcast is disabled. - * - * @param value true to disable the broadcast, false otherwise - */ - public void setDisableBroadcast(boolean value) { - getUserData().setBoolean("DisableBroadcast", value); - } - - /** - * Sets the day when the user has gotten all sites. - * - * @param day the day when the user has gotten all sites - */ - public void setGottenAllSitesDay(int day) { - getData().setInt(plugin.getVotingPluginUserManager().getGottenAllSitesDayPath(), day); - } - - /** - * Sets the day when the user has gotten almost all sites. - * - * @param day the day when the user has gotten almost all sites - */ - public void setGottenAlmostAllSitesDay(int day) { - getData().setInt(plugin.getVotingPluginUserManager().getGottenAlmostAllSitesDayPath(), day); - } - - /** - * Sets the highest daily total votes. - * - * @param total the highest daily total votes - */ - public void setHighestDailyTotal(int total) { - getData().setInt("HighestDailyTotal", total); - } - - /** - * Sets the highest monthly total votes. - * - * @param total the highest monthly total votes - */ - public void setHighestMonthlyTotal(int total) { - getData().setInt("HighestMonthlyTotal", total); - } - - /** - * Sets the highest weekly total votes. - * - * @param total the highest weekly total votes - */ - public void setHighestWeeklyTotal(int total) { - getData().setInt("HighestWeeklyTotal", total); - } - - /** - * Sets the total votes for the last month. - * - * @param total the total votes for the last month - */ - public void setLastMonthTotal(int total) { - getData().setInt("LastMonthTotal", total); - } - - /** - * Sets the last votes for each vote site. - * - * @param lastVotes a map of vote sites and the last vote time - */ - public void setLastVotes(HashMap lastVotes) { - ArrayList data = new ArrayList<>(); - for (Entry entry : lastVotes.entrySet()) { - String str = entry.getKey().getKey() + "//" + entry.getValue().longValue(); - data.add(str); - } - getUserData().setStringList("LastVotes", data); - } - - /** - * Sets the total votes for the month. - * - * @param total the total votes for the month - * @deprecated Use setTotal(TopVoter.Monthly, total) instead - */ - @Deprecated - public void setMonthTotal(int total) { - setTotal(TopVoter.Monthly, total); - } - - /** - * Sets the month vote streak. - * - * @param streak the month vote streak - */ - @Deprecated - public void setMonthVoteStreak(int streak) { - getData().setInt("MonthVoteStreak", streak); - if (getBestMonthVoteStreak() < streak) { - setBestMonthVoteStreak(streak); - } - } - - /** - * Sets the list of offline votes. - * - * @param offlineVotes the list of offline votes - */ - public void setOfflineVotes(ArrayList offlineVotes) { - getUserData().setStringList("OfflineVotes", offlineVotes); - } - - /** - * Sets the points of the user. - * - * @param value the number of points - */ - public void setPoints(int value) { - getUserData().setInt(getPointsPath(), value, false); - } - - /** - * Sets the points of the user asynchronously. - * - * @param value the number of points - * @param async whether to set the points asynchronously - */ - public void setPoints(int value, boolean async) { - getUserData().setInt(getPointsPath(), value, false, async); - } - - /** - * Sets the current time for the specified vote site. - * - * @param voteSite the vote site - */ - public void setTime(VoteSite voteSite) { - setTime(voteSite, LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()); - } - - /** - * Sets the specified time for the specified vote site. - * - * @param voteSite the vote site - * @param time the time to set - */ - public void setTime(VoteSite voteSite, Long time) { - HashMap lastVotes = getLastVotes(); - if (lastVotes != null && lastVotes.containsKey(voteSite)) { - if (lastVotes.get(voteSite).longValue() == time.longValue()) { - plugin.debug("Not setting last vote time for " + voteSite.getKey() + ", already set to " + time); - return; - } - } - lastVotes.put(voteSite, time); - setLastVotes(lastVotes); - } - - /** - * Sets whether the user is ignored for top voter. - * - * @param topVoterIgnore true to ignore the user for top voter, false otherwise - */ - public void setTopVoterIgnore(boolean topVoterIgnore) { - getUserData().setString("TopVoterIgnore", "" + topVoterIgnore); - } - - /** - * Sets the total votes for the specified top voter category. - * - * @param top the top voter category - * @param value the total votes to set - */ - public void setTotal(TopVoter top, int value) { - switch (top) { - case AllTime: - getUserData().setInt("AllTimeTotal", value); - break; - case Daily: - getUserData().setInt("DailyTotal", value); - break; - case Monthly: - if (plugin.getConfigFile().isLimitMonthlyVotes()) { - LocalDateTime time = plugin.getTimeChecker().getTime(); - int days = time.getDayOfMonth(); - if (value >= days * plugin.getVoteSiteManager().getVoteSitesEnabled().size()) { - value = days * plugin.getVoteSiteManager().getVoteSitesEnabled().size(); - } - } - getData().setInt("MonthTotal", value); - if (plugin.getConfigFile().isStoreMonthTotalsWithDate()) { - getData().setInt(plugin.getVotingPluginUserManager().getMonthTotalsWithDatePath(), value); - } - break; - case Weekly: - getUserData().setInt("WeeklyTotal", value); - break; - default: - break; - } - } - - /** - * Sets the number of votes for the vote party. - * - * @param value the number of votes to set - */ - public void setVotePartyVotes(int value) { - getUserData().setInt("VotePartyVotes", value); - } - - /** - * Sets the vote shop identifier limit. - * - * @param identifier the identifier for the vote shop - * @param value the limit to set - */ - public void setVoteShopIdentifierLimit(String identifier, int value) { - getData().setInt("VoteShopLimit" + identifier, value); - } - - /** - * Sets the weekly total votes. - * - * @param total the weekly total votes - * @deprecated Use setTotal(TopVoter.Weekly, total) instead - */ - @Deprecated - public void setWeeklyTotal(int total) { - setTotal(TopVoter.Weekly, total); - } - - /** - * Sets the week vote streak. - * - * @param streak the week vote streak - */ - @Deprecated - public void setWeekVoteStreak(int streak) { - getData().setInt("WeekVoteStreak", streak); - if (getBestWeekVoteStreak() < streak) { - setBestWeekVoteStreak(streak); - } - } - - /** - * Checks if the user should be reminded. - * - * @return true if the user should be reminded, false otherwise - */ - public boolean shouldBeReminded() { - Player player = getPlayer(); - if (player != null) { - if (player.hasPermission("VotingPlugin.NoRemind")) { - return false; - } - } - return true; - } - - /** - * Gets the last vote date for the specified vote site. - * - * @param voteSite the vote site - * @return the last vote date as a string - * @deprecated Use getTime(VoteSite) instead - */ - @Deprecated - public String voteCommandLastDate(VoteSite voteSite) { - long time = getTime(voteSite); - if (time > 0) { - Date date = new Date(time); - String timeString = new SimpleDateFormat(plugin.getConfigFile().getFormatTimeFormat()).format(date); - if (MessageAPI.containsIgnorecase(timeString, "YamlConfiguration")) { - plugin.getLogger().warning("Detected issue parsing time, check time format"); - } - return timeString; - } - return ""; - } - - /** - * Gets the duration since the last vote for the specified vote site. - * - * @param voteSite the vote site - * @return the duration since the last vote as a string - */ - public String voteCommandLastDuration(VoteSite voteSite) { - long time = getTime(voteSite); - if (time > 0) { - LocalDateTime now = LocalDateTime.now(); - LocalDateTime lastVote = LocalDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneId.systemDefault()); - - Duration dur = Duration.between(lastVote, now); - - long diffSecond = dur.getSeconds(); - int diffDays = (int) (diffSecond / 60 / 60 / 24); - int diffHours = (int) (diffSecond / 60 / 60 - diffDays * 24); - int diffMinutes = (int) (diffSecond / 60 - diffHours * 60 - diffDays * 24 * 60); - int diffSeconds = (int) (diffSecond - diffMinutes * 60 - diffHours * 60 * 60 - diffDays * 24 * 60 * 60); - - String info = ""; - if (diffDays == 1) { - info += PlaceholderUtils.replacePlaceHolder(PlaceholderUtils.replacePlaceHolder( - plugin.getConfigFile().getFormatCommandsVoteLastTimeFormat(), "TimeType", - plugin.getConfigFile().getFormatTimeFormatsDay()), "amount", "" + diffDays); - info += " "; - } else if (diffDays > 1) { - info += PlaceholderUtils.replacePlaceHolder(PlaceholderUtils.replacePlaceHolder( - plugin.getConfigFile().getFormatCommandsVoteLastTimeFormat(), "TimeType", - plugin.getConfigFile().getFormatTimeFormatsDays()), "amount", "" + diffDays); - info += " "; - } - - if (diffHours == 1) { - info += PlaceholderUtils.replacePlaceHolder(PlaceholderUtils.replacePlaceHolder( - plugin.getConfigFile().getFormatCommandsVoteLastTimeFormat(), "TimeType", - plugin.getConfigFile().getFormatTimeFormatsHour()), "amount", "" + diffHours); - info += " "; - } else if (diffHours > 1) { - info += PlaceholderUtils.replacePlaceHolder(PlaceholderUtils.replacePlaceHolder( - plugin.getConfigFile().getFormatCommandsVoteLastTimeFormat(), "TimeType", - plugin.getConfigFile().getFormatTimeFormatsHours()), "amount", "" + diffHours); - info += " "; - } - - if (diffMinutes == 1) { - info += PlaceholderUtils.replacePlaceHolder(PlaceholderUtils.replacePlaceHolder( - plugin.getConfigFile().getFormatCommandsVoteLastTimeFormat(), "TimeType", - plugin.getConfigFile().getFormatTimeFormatsMinute()), "amount", "" + diffMinutes); - info += " "; - } else if (diffMinutes > 1) { - info += PlaceholderUtils.replacePlaceHolder(PlaceholderUtils.replacePlaceHolder( - plugin.getConfigFile().getFormatCommandsVoteLastTimeFormat(), "TimeType", - plugin.getConfigFile().getFormatTimeFormatsMinutes()), "amount", "" + diffMinutes); - info += " "; - } - - if (plugin.getConfigFile().isFormatCommandsVoteLastIncludeSeconds()) { - if (diffSeconds == 1) { - info += PlaceholderUtils.replacePlaceHolder(PlaceholderUtils.replacePlaceHolder( - plugin.getConfigFile().getFormatCommandsVoteLastTimeFormat(), "TimeType", - plugin.getConfigFile().getFormatTimeFormatsSecond()), "amount", "" + diffSeconds); - } else { - info += PlaceholderUtils.replacePlaceHolder(PlaceholderUtils.replacePlaceHolder( - plugin.getConfigFile().getFormatCommandsVoteLastTimeFormat(), "TimeType", - plugin.getConfigFile().getFormatTimeFormatsSeconds()), "amount", "" + diffSeconds); - } - } - - info = PlaceholderUtils.replacePlaceHolder(plugin.getConfigFile().getFormatCommandsVoteLastLastVoted(), - "times", info); - - return info; - } - return plugin.getConfigFile().getFormatCommandsVoteLastNeverVoted(); - } - - /** - * Gets the last vote date and duration for the specified vote site for the GUI. - * - * @param voteSite the vote site - * @return the last vote date and duration as a string for the GUI - */ - public String voteCommandLastGUILine(VoteSite voteSite) { - String timeString = voteCommandLastDate(voteSite); - String timeSince = voteCommandLastDuration(voteSite); - - HashMap placeholders = new HashMap<>(); - placeholders.put("time", timeString); - placeholders.put("SiteName", voteSite.getDisplayName()); - placeholders.put("timesince", timeSince); - - return PlaceholderUtils.replacePlaceHolder(plugin.getGui().getChestVoteLastLine(), placeholders); - } - - /** - * Gets the last vote date and duration for the specified vote site. - * - * @param voteSite the vote site - * @return the last vote date and duration as a string - */ - public String voteCommandLastLine(VoteSite voteSite) { - String timeString = voteCommandLastDate(voteSite); - String timeSince = voteCommandLastDuration(voteSite); - - HashMap placeholders = new HashMap<>(); - placeholders.put("time", timeString); - placeholders.put("SiteName", voteSite.getDisplayName()); - placeholders.put("timesince", timeSince); - - return PlaceholderUtils.replacePlaceHolder(plugin.getConfigFile().getFormatCommandsVoteLastLine(), - placeholders); - } - - /** - * Gets the next available vote time for the specified vote site. - * - * @param voteSite the vote site - * @return the next available vote time as a string - */ - public String voteCommandNextInfo(VoteSite voteSite) { - return voteCommandNextInfo(voteSite, getTime(voteSite)); - } - - /** - * Gets the next available vote time for the specified vote site. - * - * @param voteSite the vote site - * @param time the current time - * @return the next available vote time as a string - */ - public String voteCommandNextInfo(VoteSite voteSite, long time) { - String info = new String(); - - long nextTime = voteNextDurationTime(voteSite, time); - if (nextTime == 0) { - info = plugin.getConfigFile().getFormatCommandsVoteNextInfoCanVote(); - } else { - int diffHours = (int) (nextTime / (60 * 60)); - long diffMinutes = nextTime / 60 - diffHours * 60; - - if (diffHours < 0) { - diffHours = diffHours * -1; - } - if (diffHours >= 24) { - diffHours = diffHours - 24; - } - if (diffMinutes < 0) { - diffMinutes = diffMinutes * -1; - } - - String timeMsg = plugin.getConfigFile().getFormatCommandsVoteNextInfoVoteDelayDaily(); - timeMsg = MessageAPI.replaceIgnoreCase(timeMsg, "%hours%", Integer.toString(diffHours)); - timeMsg = MessageAPI.replaceIgnoreCase(timeMsg, "%minutes%", Long.toString(diffMinutes)); - info = timeMsg; - } - - return info; - } - - /** - * Gets the next available vote duration time for the specified vote site. - * - * @param voteSite the vote site - * @return the next available vote duration time in seconds - */ - public long voteNextDurationTime(VoteSite voteSite) { - return voteNextDurationTime(voteSite, getTime(voteSite)); - } - - /** - * Gets the next available vote duration time for the specified vote site. - * - * @param voteSite the vote site - * @param time the last vote time (epoch millis) - * @return the next available vote duration time in seconds - */ - public long voteNextDurationTime(VoteSite voteSite, long time) { - LocalDateTime now = plugin.getTimeChecker().getTime(); - - LocalDateTime lastVote = LocalDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneId.systemDefault()) - .plusHours(plugin.getOptions().getTimeHourOffSet()); - - if (!voteSite.isVoteDelayDaily()) { - ParsedDuration voteDelay = voteSite.getVoteDelay(); - - if (time == 0 || voteDelay == null || voteDelay.isEmpty()) { - return 0; - } - - // Ignore months, use fixed duration only - LocalDateTime nextVote = lastVote.plus(Duration.ofMillis(voteDelay.getMillis())); - - if (now.isAfter(nextVote)) { - return 0; - } - - return Duration.between(now, nextVote).getSeconds(); - } - - // Daily reset logic (unchanged) - LocalDateTime resetTime = lastVote.withHour(voteSite.getVoteDelayDailyHour()).withMinute(0).withSecond(0); - - LocalDateTime resetTimeTomorrow = resetTime.plusHours(24); - - if (lastVote.isBefore(resetTime)) { - if (now.isBefore(resetTime)) { - return Duration.between(now, resetTime).getSeconds(); - } - } else { - if (now.isBefore(resetTimeTomorrow)) { - return Duration.between(now, resetTimeTomorrow).getSeconds(); - } - } - - return 0; - } - - /** - * Checks if the vote streak was updated today. - * - * @param time the current time - * @return true if the vote streak was updated today, false otherwise - */ - @Deprecated - public boolean voteStreakUpdatedToday(LocalDateTime time) { - return MiscUtils.getInstance().getTime(getDayVoteStreakLastUpdate()).getDayOfYear() == time.getDayOfYear(); - } - - public String getVoteStreakState(String columnName) { - return getData().getString(columnName); - } - - public void setVoteStreakState(String columnName, String value) { - getData().setString(columnName, value); - } - -} +package com.bencodez.votingplugin.user; + +import java.text.SimpleDateFormat; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import com.bencodez.advancedcore.api.messages.PlaceholderUtils; +import com.bencodez.advancedcore.api.misc.MiscUtils; +import com.bencodez.advancedcore.api.rewards.RewardBuilder; +import com.bencodez.advancedcore.api.rewards.RewardOptions; +import com.bencodez.advancedcore.api.user.AdvancedCoreUser; +import com.bencodez.simpleapi.messages.MessageAPI; +import com.bencodez.simpleapi.sql.data.DataValue; +import com.bencodez.simpleapi.sql.data.DataValueInt; +import com.bencodez.simpleapi.time.ParsedDuration; +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.events.PlayerReceivePointsEvent; +import com.bencodez.votingplugin.events.PlayerSpecialRewardEvent; +import com.bencodez.votingplugin.events.PlayerVoteEvent; +import com.bencodez.votingplugin.events.SpecialRewardType; +import com.bencodez.votingplugin.proxy.VoteTotalsSnapshot; +import com.bencodez.votingplugin.topvoter.TopVoter; +import com.bencodez.votingplugin.topvoter.TopVoterPlayer; +import com.bencodez.votingplugin.votesites.NextSite; +import com.bencodez.votingplugin.votesites.VoteSite; + +/** + * The Class VotingPluginUser. This class represents a user in the VotingPlugin + * system. It extends the AdvancedCoreUser class and provides additional + * functionality specific to the VotingPlugin. + */ +public class VotingPluginUser extends com.bencodez.advancedcore.api.user.AdvancedCoreUser { + + /** The plugin instance. */ + private VotingPluginMain plugin; + + /** + * Instantiates a new VotingPluginUser. + * + * @param plugin the plugin instance + * @param user the AdvancedCoreUser instance + */ + public VotingPluginUser(VotingPluginMain plugin, AdvancedCoreUser user) { + super(plugin, user); + this.plugin = plugin; + } + + /** + * Instantiates a new VotingPluginUser. + * + * @param plugin the plugin instance + * @param player the player instance + * @deprecated Use {@link #VotingPluginUser(VotingPluginMain, AdvancedCoreUser)} + * instead. + */ + @Deprecated + public VotingPluginUser(VotingPluginMain plugin, Player player) { + super(plugin, player); + this.plugin = plugin; + } + + /** + * Instantiates a new VotingPluginUser. + * + * @param plugin the plugin instance + * @param playerName the player name + * @deprecated Use {@link #VotingPluginUser(VotingPluginMain, AdvancedCoreUser)} + * instead. + */ + @Deprecated + public VotingPluginUser(VotingPluginMain plugin, String playerName) { + super(plugin, playerName); + this.plugin = plugin; + } + + /** + * Instantiates a new VotingPluginUser. + * + * @param plugin the plugin instance + * @param uuid the UUID of the player + * @deprecated Use {@link #VotingPluginUser(VotingPluginMain, AdvancedCoreUser)} + * instead. + */ + @Deprecated + public VotingPluginUser(VotingPluginMain plugin, UUID uuid) { + super(plugin, uuid); + this.plugin = plugin; + } + + /** + * Instantiates a new VotingPluginUser. + * + * @param plugin the plugin instance + * @param uuid the UUID of the player + * @param loadName whether to load the player name + * @deprecated Use {@link #VotingPluginUser(VotingPluginMain, AdvancedCoreUser)} + * instead. + */ + @Deprecated + public VotingPluginUser(VotingPluginMain plugin, UUID uuid, boolean loadName) { + super(plugin, uuid, loadName); + this.plugin = plugin; + } + + /** + * Instantiates a new VotingPluginUser. + * + * @param plugin the plugin instance + * @param uuid the UUID of the player + * @param playerName the player name + * @deprecated Use {@link #VotingPluginUser(VotingPluginMain, AdvancedCoreUser)} + * instead. + */ + @Deprecated + public VotingPluginUser(VotingPluginMain plugin, UUID uuid, String playerName) { + super(plugin, uuid, playerName); + this.plugin = plugin; + } + + /** + * Adds one to the all-time total votes. + */ + public void addAllTimeTotal() { + setAllTimeTotal(getAllTimeTotal() + 1); + } + + /** + * Adds one to the daily vote streak. + */ + @Deprecated + public void addDayVoteStreak() { + setDayVoteStreak(getDayVoteStreak() + 1); + } + + /** + * Adds one to the monthly total votes. + */ + public void addMonthTotal() { + setMonthTotal(getMonthTotal() + 1); + } + + /** + * Adds one to the monthly vote streak. + */ + @Deprecated + public void addMonthVoteStreak() { + setMonthVoteStreak(getMonthVoteStreak() + 1); + } + + /** + * Adds an offline vote for the specified vote site. + * + * @param voteSiteName the name of the vote site + */ + public void addOfflineVote(String voteSiteName) { + ArrayList offlineVotes = getOfflineVotes(); + offlineVotes.add(voteSiteName); + setOfflineVotes(offlineVotes); + } + + /** + * Adds points to the user based on the configuration. + */ + public void addPoints() { + int points = plugin.getConfigFile().getPointsOnVote(); + if (points != 0) { + addPoints(points); + } + if (plugin.getConfigFile().getLimitVotePoints() > 0) { + if (getPoints() > plugin.getConfigFile().getLimitVotePoints()) { + setPoints(plugin.getConfigFile().getLimitVotePoints()); + } + } + } + + /** + * Adds the specified number of points to the user. + * + * @param value the number of points to add + * @return the current total points + */ + public int addPoints(int value) { + return addPoints(value, false); + } + + /** + * Adds the specified number of points to the user, optionally asynchronously. + * + * @param value the number of points to add + * @param async whether to add the points asynchronously + * @return the current total points + */ + public synchronized int addPoints(int value, boolean async) { + PlayerReceivePointsEvent event = new PlayerReceivePointsEvent(this, value); + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) { + return getPoints(); + } + int newTotal = getPoints() + event.getPoints(); + setPoints(newTotal, async); + return newTotal; + } + + /** + * Adds one to the total votes. + */ + public void addTotal() { + addMonthTotal(); + addAllTimeTotal(); + } + + /** + * Adds one to the daily total votes. + */ + public void addTotalDaily() { + setDailyTotal(getDailyTotal() + 1); + } + + /** + * Adds one to the weekly total votes. + */ + public void addTotalWeekly() { + setWeeklyTotal(getWeeklyTotal() + 1); + } + + /** + * Adds one to the weekly vote streak. + */ + @Deprecated + public void addWeekVoteStreak() { + setWeekVoteStreak(getWeekVoteStreak() + 1); + } + + /** + * Handles a plugin messaging bungee vote. + * + * @param service the service name + * @param time the vote time + * @param text the bungee message data + * @param setTotals whether to set the totals + * @param wasOnline whether the player was online + * @param broadcast whether to broadcast the vote + * @param num the vote number + */ + public void bungeeVotePluginMessaging(String service, long time, VoteTotalsSnapshot text, boolean setTotals, + boolean wasOnline, boolean broadcast, int num) { + if (plugin.getBungeeSettings().isUseBungeecoord()) { + plugin.debug("Pluginmessaging vote for " + getPlayerName() + " on " + service); + + PlayerVoteEvent voteEvent = new PlayerVoteEvent(plugin.getVoteSiteManager().getVoteSite(service, true), + getPlayerName(), service, true); + voteEvent.setBungee(true); + voteEvent.setVotingPluginUser(this); + voteEvent.setForceBungee(true); + voteEvent.setTime(time); + voteEvent.setAddTotals(setTotals); + voteEvent.setBungeeTextTotals(text); + voteEvent.setWasOnline(wasOnline); + voteEvent.setBroadcast(broadcast); + voteEvent.setVoteNumber(num); + plugin.getServer().getPluginManager().callEvent(voteEvent); + } + } + + /** + * Checks if the user can vote on all sites. + * + * @return true, if the user can vote on all sites + */ + public boolean canVoteAll() { + for (VoteSite voteSite : plugin.getVoteSiteManager().getVoteSitesEnabled()) { + if (!voteSite.isHidden()) { + boolean canVote = canVoteSite(voteSite); + if (!canVote) { + return false; + } + } + } + return true; + } + + /** + * Checks if the user can vote on any site. + * + * @return true, if the user can vote on any site + */ + public boolean canVoteAny() { + for (VoteSite voteSite : plugin.getVoteSiteManager().getVoteSitesEnabled()) { + if (!voteSite.isIgnoreCanVote() && !voteSite.isHidden()) { + boolean canVote = canVoteSite(voteSite); + if (canVote) { + return true; + } + } + } + return false; + } + + /** + * Checks if the user can vote on the specified site. + * + * @param voteSite the vote site + * @return true, if the user can vote on the site + */ + public boolean canVoteSite(VoteSite voteSite) { + long time = getTime(voteSite); + if (time == 0) { + return true; + } + try { + LocalDateTime now = plugin.getTimeChecker().getTime(); + LocalDateTime lastVote = LocalDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneId.systemDefault()) + .plusHours(plugin.getOptions().getTimeHourOffSet()); + + if (!voteSite.isVoteDelayDaily()) { + ParsedDuration voteDelay = voteSite.getVoteDelay(); + + // Preserve old behavior: if delay is 0, you can never vote again (unless daily + // reset mode) + if (voteDelay == null || voteDelay.isEmpty() || voteDelay.getMillis() == 0L) { + return false; + } + + LocalDateTime nextVote = lastVote.plus(Duration.ofMillis(voteDelay.getMillis())); + return now.isAfter(nextVote); + } + + // Daily reset logic unchanged + LocalDateTime resetTime = lastVote.withHour(voteSite.getVoteDelayDailyHour()).withMinute(0).withSecond(0); + LocalDateTime resetTimeTomorrow = resetTime.plusHours(24); + + if (lastVote.isBefore(resetTime)) { + return now.isAfter(resetTime); + } else { + return now.isAfter(resetTimeTomorrow); + } + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + /** + * Checks if the user has voted on all sites. + * + * @return true, if the user has voted on all sites + */ + public boolean checkAllVotes() { + VotingPluginUser user = this; + + ArrayList months = new ArrayList<>(); + ArrayList days = new ArrayList<>(); + + for (VoteSite voteSite : plugin.getVoteSiteManager().getVoteSitesEnabled()) { + if (voteSite.isEnabled() && !voteSite.isHidden()) { + long time = user.getTime(voteSite); + if (time == 0) { + return false; + } + months.add(MiscUtils.getInstance().getMonthFromMili(time)); + days.add(MiscUtils.getInstance().getDayFromMili(time)); + } + } + + // check months + for (Integer month : months) { + if (!months.get(0).equals(month)) { + return false; + } + } + + // check days + for (Integer day : days) { + if (!days.get(0).equals(day)) { + return false; + } + } + + return true; + } + + /** + * Checks if the user has voted on almost all sites. + * + * @return true, if the user has voted on almost all sites + */ + public boolean checkAlmostAllVotes() { + if (getSitesNotVotedOn() <= 1) { + return true; + } + return false; + } + + /** + * Checks the day vote streak and updates it if necessary. + * + * @param forceBungee whether to force bungee + */ + @Deprecated + public void checkDayVoteStreak(boolean forceBungee) { + if (!voteStreakUpdatedToday(LocalDateTime.now())) { + if (!plugin.getSpecialRewardsConfig().isVoteStreakRequirementUsePercentage() || hasPercentageTotal( + TopVoter.Daily, plugin.getSpecialRewardsConfig().getVoteStreakRequirementDay(), null)) { + plugin.extraDebug("Adding day vote streak to " + getUUID() + " " + + plugin.getSpecialRewardsConfig().isVoteStreakRequirementUsePercentage() + " " + + hasPercentageTotal(TopVoter.Daily, + plugin.getSpecialRewardsConfig().getVoteStreakRequirementDay(), null)); + addDayVoteStreak(); + plugin.getSpecialRewards().checkVoteStreak(null, this, "Day", forceBungee); + setDayVoteStreakLastUpdate(System.currentTimeMillis()); + } + } + } + + /** + * Clears the offline votes. + */ + public void clearOfflineVotes() { + setOfflineVotes(new ArrayList<>()); + setOfflineRewards(new ArrayList<>()); + } + + /** + * Clears the total votes for all top voter categories. + */ + public void clearTotals() { + for (TopVoter top : TopVoter.values()) { + resetTotals(top); + } + } + + /** + * Gets the all-time total votes. + * + * @return the all-time total votes + * @deprecated Use getTotal(TopVoter.AllTime) when able instead + */ + @Deprecated + public int getAllTimeTotal() { + return getTotal(TopVoter.AllTime); + } + + /** + * Gets the best day vote streak. + * + * @return the best day vote streak + */ + @Deprecated + public int getBestDayVoteStreak() { + return getData().getInt("BestDayVoteStreak"); + } + + /** + * Gets the best month vote streak. + * + * @return the best month vote streak + */ + @Deprecated + public int getBestMonthVoteStreak() { + return getData().getInt("BestMonthVoteStreak"); + } + + /** + * Gets the best week vote streak. + * + * @return the best week vote streak + */ + @Deprecated + public int getBestWeekVoteStreak() { + return getData().getInt("BestWeekVoteStreak"); + } + + /** + * Checks if the cooldown check is enabled. + * + * @return true, if the cooldown check is enabled + */ + public boolean getCoolDownCheck() { + return getData().getBoolean(getCoolDownCheckPath()); + } + + /** + * Gets the path for the cooldown check. + * + * @return the cooldown check path + */ + public String getCoolDownCheckPath() { + if (plugin.getBungeeSettings().isUseBungeecoord()) { + return "CoolDownCheck_" + plugin.getBungeeSettings().getServerNameStorage(); + } + return "CoolDownCheck"; + } + + /** + * Checks if the cooldown check is enabled for a specific vote site. + * + * @param site the vote site + * @return true, if the cooldown check is enabled for the site + */ + public boolean getCoolDownCheckSite(VoteSite site) { + HashMap coolDownChecks = getCoolDownCheckSiteList(); + if (coolDownChecks.containsKey(site.getKey())) { + return coolDownChecks.get(site.getKey()).booleanValue(); + } + return false; + } + + /** + * Gets the list of cooldown checks for all vote sites. + * + * @return the list of cooldown checks for all vote sites + */ + public HashMap getCoolDownCheckSiteList() { + HashMap coolDownChecks = new HashMap<>(); + ArrayList coolDownCheck = getData().getStringList(getCoolDownCheckSitePath()); + for (String str : coolDownCheck) { + String[] data = str.split("//"); + if (data.length > 1 && plugin.getVoteSiteManager().hasVoteSite(data[0])) { + VoteSite site = plugin.getVoteSiteManager().getVoteSite(data[0], true); + if (site != null) { + Boolean b = Boolean.valueOf(data[1]); + coolDownChecks.put(site.getKey(), b); + } + } + } + return coolDownChecks; + } + + /** + * Gets the path for the cooldown check site list. + * + * @return the cooldown check site list path + */ + public String getCoolDownCheckSitePath() { + if (plugin.getBungeeSettings().isUseBungeecoord()) { + return "CoolDownCheck_" + plugin.getBungeeSettings().getServerNameStorage() + "_Sites"; + } + return "CoolDownCheck" + "_Sites"; + } + + /** + * Gets the daily total votes. + * + * @return the daily total votes + * @deprecated Use getTotal(TopVoter.Daily) instead + */ + @Deprecated + public int getDailyTotal() { + return getTotal(TopVoter.Daily); + } + + /** + * Gets the day vote streak. + * + * @return the day vote streak + */ + @Deprecated + public int getDayVoteStreak() { + return getData().getInt("DayVoteStreak"); + } + + /** + * Gets the last update time for the day vote streak. + * + * @return the last update time for the day vote streak + */ + @Deprecated + public long getDayVoteStreakLastUpdate() { + String str = getData().getString("DayVoteStreakLastUpdate"); + if (str == null || str.isEmpty() || str.equals("null")) { + return 0; + } + try { + return Long.parseLong(str); + } catch (NumberFormatException e) { + return 0; + } + } + + /** + * Checks if the broadcast is disabled. + * + * @return true if the broadcast is disabled, false otherwise + */ + public boolean getDisableBroadcast() { + return getUserData().getBoolean("DisableBroadcast"); + } + + /** + * Gets the day when the user has gotten all sites. + * + * @return the day when the user has gotten all sites + */ + public int getGottenAllSitesDay() { + return getData().getInt(plugin.getVotingPluginUserManager().getGottenAllSitesDayPath(), 0); + } + + /** + * Gets the day when the user has gotten almost all sites. + * + * @return the day when the user has gotten almost all sites + */ + public int getGottenAlmostAllSitesDay() { + return getData().getInt(plugin.getVotingPluginUserManager().getGottenAlmostAllSitesDayPath(), 0); + } + + /** + * Gets the highest daily total votes. + * + * @return the highest daily total votes + */ + public int getHighestDailyTotal() { + return getData().getInt("HighestDailyTotal"); + } + + /** + * Returns whether this user has already claimed the NameMC like reward. + * + * @return true if claimed + */ + public boolean hasClaimedNameMCLikeReward() { + return getUserData().getBoolean("NameMCLikeRewardClaimed"); + } + + /** + * Sets whether this user has already claimed the NameMC like reward. + * + * @param claimed true if claimed + */ + public void setClaimedNameMCLikeReward(boolean claimed) { + getUserData().setBoolean("NameMCLikeRewardClaimed", claimed); + } + + /** + * Gets the highest monthly total votes. + * + * @return the highest monthly total votes + */ + public int getHighestMonthlyTotal() { + return getData().getInt("HighestMonthlyTotal"); + } + + /** + * Gets the highest weekly total votes. + * + * @return the highest weekly total votes + */ + public int getHighestWeeklyTotal() { + return getData().getInt("HighestWeeklyTotal"); + } + + /** + * Gets the total votes for the last month. + * + * @return the total votes for the last month + */ + public int getLastMonthTotal() { + return getData().getInt("LastMonthTotal"); + } + + /** + * Gets the last votes for each vote site. + * + * @return a map of vote sites and the last vote time + */ + public HashMap getLastVotes() { + HashMap lastVotes = new HashMap<>(); + ArrayList lastVotesList = getUserData().getStringList("LastVotes"); + + for (String str : lastVotesList) { + String[] data = str.split("//"); + if (data.length <= 1) { + continue; + } + + String rawSiteKey = data[0]; + String rawTime = data[1]; + + if (!plugin.getVoteSiteManager().hasVoteSite(rawSiteKey)) { + continue; + } + + VoteSite site = plugin.getVoteSiteManager().getVoteSite(rawSiteKey, true); + if (site == null) { + continue; + } + + long time = 0; + try { + time = Long.parseLong(rawTime); + } catch (NumberFormatException ignored) { + time = 0; + } + + lastVotes.put(site, time); + } + + return lastVotes; + } + + /** + * Gets the time of the last vote. + * + * @return the time of the last vote + */ + public Long getLastVoteTime() { + Long time = Long.valueOf(0); + for (Long value : getLastVotes().values()) { + if (value.longValue() > time) { + time = value; + } + } + return time; + } + + /** + * Gets the last vote time for a specific vote site. + * + * @param voteSite the vote site + * @return the last vote time for the vote site + */ + public long getLastVoteTimer(VoteSite voteSite) { + HashMap times = getLastVotes(); + if (times.containsKey(voteSite)) { + return times.get(voteSite).longValue(); + } + return 0; + } + + /** + * Gets the last vote times sorted in descending order. + * + * @return a map of vote sites and the last vote times sorted in descending + * order + */ + public HashMap getLastVoteTimesSorted() { + LinkedHashMap times = new LinkedHashMap<>(); + + for (VoteSite voteSite : plugin.getVoteSiteManager().getVoteSitesEnabled()) { + times.put(voteSite, getTime(voteSite)); + } + LinkedHashMap sorted = new LinkedHashMap<>( + times.entrySet().stream().sorted(Collections.reverseOrder(Map.Entry.comparingByValue())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); + return sorted; + } + + /** + * Gets the total votes for the month. + * + * @return the total votes for the month + * @deprecated Use getTotal(TopVoter.Monthly) instead + */ + @Deprecated + public int getMonthTotal() { + return getTotal(TopVoter.Monthly); + } + + /** + * Gets the month vote streak. + * + * @return the month vote streak + */ + @Deprecated + public int getMonthVoteStreak() { + return getData().getInt("MonthVoteStreak"); + } + + /** + * Gets the next time all sites are available for voting. + * + * @return the next time all sites are available for voting + */ + public long getNextTimeAllSitesAvailable() { + long longest = 0; + for (VoteSite site : plugin.getVoteSiteManager().getVoteSitesEnabled()) { + long seconds = voteNextDurationTime(site); + if (seconds > longest) { + longest = seconds; + } + } + + return longest; + } + + /** + * Gets the next time the first site is available for voting. + * + * @return seconds until first site is available, or 0 if none + */ + public long getNextTimeFirstSiteAvailable() { + NextSite next = getNextSiteAvailable(); + return next == null ? 0 : next.getSecondsUntilAvailable(); + } + + /** + * Returns the next VoteSite that will become available, and how many seconds + * until it is available. + * + * - Only considers enabled sites. - Skips hidden sites (matching + * canVoteAll/canVoteAny intent for player-facing voting). - Only considers + * sites the player CANNOT currently vote on (seconds > 0). + * + * @return NextSite or null if there is no upcoming site (i.e. can vote all, or + * no delays) + */ + public NextSite getNextSiteAvailable() { + List sites = plugin.getVoteSiteManager().getVoteSitesEnabled(); + if (sites == null || sites.isEmpty()) { + return null; + } + + VoteSite bestSite = null; + long bestSeconds = 0; + + for (int i = 0; i < sites.size(); i++) { + VoteSite site = sites.get(i); + if (site == null) { + continue; + } + + // Match the same "don't care" sites as your other checks generally do + if (!site.isEnabled() || site.isHidden()) { + continue; + } + + // If you can already vote, it isn't "next" + if (canVoteSite(site)) { + continue; + } + + long seconds = voteNextDurationTime(site); // uses getTime(site) internally + if (seconds <= 0) { + continue; + } + + if (bestSite == null || seconds < bestSeconds) { + bestSite = site; + bestSeconds = seconds; + } + } + + return bestSite == null ? null : new NextSite(bestSite, bestSeconds); + } + + /** + * Gets the number of offline votes for the specified vote site. + * + * @param site the vote site + * @return the number of offline votes for the specified vote site + */ + public int getNumberOfOfflineVotes(VoteSite site) { + ArrayList offlineVotes = getOfflineVotes(); + int num = 0; + for (String str : offlineVotes) { + if (str.equals(site.getKey())) { + num++; + } + } + return num; + } + + /** + * Gets the list of offline votes. + * + * @return the list of offline votes + */ + public ArrayList getOfflineVotes() { + return getUserData().getStringList("OfflineVotes"); + } + + /** + * Gets the points of the user. + * + * @return the points of the user + */ + public int getPoints() { + return getUserData().getInt(getPointsPath()); + } + + /** + * Gets the path for the points. + * + * @return the points path + */ + public String getPointsPath() { + if (plugin.getBungeeSettings().isPerServerPoints()) { + return plugin.getBungeeSettings().getServerNameStorage() + "_Points"; + } + return "Points"; + } + + /** + * Gets the number of sites not voted on. + * + * @return the number of sites not voted on + */ + public int getSitesNotVotedOn() { + int amount = 0; + for (VoteSite site : plugin.getVoteSiteManager().getVoteSitesEnabled()) { + if (!site.isHidden()) { + if (site.getPermissionToView().isEmpty() || hasPermission(site.getPermissionToView(), false)) { + if (canVoteSite(site)) { + amount++; + } + } + } + } + return amount; + } + + public int getTotalNumberOfSites() { + int amount = 0; + for (VoteSite site : plugin.getVoteSiteManager().getVoteSitesEnabled()) { + if (!site.isHidden()) { + if (site.getPermissionToView().isEmpty() || hasPermission(site.getPermissionToView(), false)) { + amount++; + } + } + } + return amount; + } + + public int getSitesVotedOn() { + int amount = 0; + for (VoteSite site : plugin.getVoteSiteManager().getVoteSitesEnabled()) { + if (!canVoteSite(site)) { + amount++; + } + } + return amount; + } + + /** + * Gets the time. + * + * @param voteSite the vote site + * @return the time + */ + public long getTime(VoteSite voteSite) { + HashMap lastVotes = getLastVotes(); + if (lastVotes.containsKey(voteSite)) { + return lastVotes.get(voteSite); + } + return 0; + } + + /** + * Gets the top voter player. + * + * @return the top voter player + */ + public TopVoterPlayer getTopVoterPlayer() { + return new TopVoterPlayer(UUID.fromString(getUUID()), getPlayerName(), getLastOnline()); + } + + /** + * Gets the total votes for the specified top voter category. + * + * @param top the top voter category + * @return the total votes for the specified top voter category + */ + public int getTotal(TopVoter top) { + switch (top) { + case AllTime: + return getUserData().getInt("AllTimeTotal"); + case Daily: + return getUserData().getInt("DailyTotal"); + case Monthly: + if (plugin.getConfigFile().isUseMonthDateTotalsAsPrimaryTotal()) { + return getData().getInt(plugin.getVotingPluginUserManager().getMonthTotalsWithDatePath()); + } + return getData().getInt("MonthTotal"); + case Weekly: + return getUserData().getInt("WeeklyTotal"); + default: + break; + } + return 0; + } + + /** + * Gets the total votes for the specified top voter category at a specific time. + * + * @param top the top voter category + * @param atTime the specific time + * @return the total votes for the specified top voter category at the specific + * time + */ + public int getTotal(TopVoter top, LocalDateTime atTime) { + switch (top) { + case AllTime: + return getUserData().getInt("AllTimeTotal"); + case Daily: + return getUserData().getInt("DailyTotal"); + case Monthly: + if (plugin.getConfigFile().isUseMonthDateTotalsAsPrimaryTotal()) { + return getData().getInt(plugin.getVotingPluginUserManager().getMonthTotalsWithDatePath(atTime)); + } + return getData().getInt("MonthTotal"); + case Weekly: + return getUserData().getInt("WeeklyTotal"); + default: + break; + } + return 0; + } + + /** + * Gets the number of votes for the vote party. + * + * @return the number of votes for the vote party + */ + public int getVotePartyVotes() { + return getUserData().getInt("VotePartyVotes"); + } + + /** + * Gets the vote shop identifier limit. + * + * @param identifier the identifier for the vote shop + * @return the vote shop identifier limit + */ + public int getVoteShopIdentifierLimit(String identifier) { + return getData().getInt("VoteShopLimit" + identifier); + } + + /** + * Gets the weekly total votes. + * + * @return the weekly total votes + * @deprecated Use getTotal(TopVoter.Weekly) instead + */ + @Deprecated + public int getWeeklyTotal() { + return getTotal(TopVoter.Weekly); + } + + /** + * Gets the week vote streak. + * + * @return the week vote streak + */ + @Deprecated + public int getWeekVoteStreak() { + return getData().getInt("WeekVoteStreak"); + } + + /** + * Gives the daily top voter award. + * + * @param place the place of the top voter + * @param path the path to the reward configuration + */ + public void giveDailyTopVoterAward(int place, String path) { + SpecialRewardType type = SpecialRewardType.TOPVOTER; + type.setType("Daily"); + type.setAmount(1); + PlayerSpecialRewardEvent event = new PlayerSpecialRewardEvent(this, type, null); + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) { + return; + } + new RewardBuilder(plugin.getSpecialRewardsConfig().getData(), + plugin.getSpecialRewardsConfig().getDailyAwardRewardsPath(path)).withPlaceHolder("place", "" + place) + .withPlaceHolder("topvoter", "Daily").withPlaceHolder("votes", "" + getTotal(TopVoter.Daily)) + .setOnline(isOnline()).send(this); + } + + /** + * Gives the monthly top voter award. + * + * @param place the place of the top voter + * @param path the path to the reward configuration + */ + public void giveMonthlyTopVoterAward(int place, String path) { + SpecialRewardType type = SpecialRewardType.TOPVOTER; + type.setType("Monthly"); + type.setAmount(1); + PlayerSpecialRewardEvent event = new PlayerSpecialRewardEvent(this, type, null); + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) { + return; + } + new RewardBuilder(plugin.getSpecialRewardsConfig().getData(), + plugin.getSpecialRewardsConfig().getMonthlyAwardRewardsPath(path)).withPlaceHolder("place", "" + place) + .withPlaceHolder("topvoter", "Monthly").withPlaceHolder("votes", "" + getTotal(TopVoter.Monthly)) + .setOnline(isOnline()).send(this); + } + + /** + * Gives the weekly top voter award. + * + * @param place the place of the top voter + * @param path the path to the reward configuration + */ + public void giveWeeklyTopVoterAward(int place, String path) { + SpecialRewardType type = SpecialRewardType.TOPVOTER; + type.setType("Weekly"); + type.setAmount(1); + PlayerSpecialRewardEvent event = new PlayerSpecialRewardEvent(this, type, null); + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) { + return; + } + new RewardBuilder(plugin.getSpecialRewardsConfig().getData(), + plugin.getSpecialRewardsConfig().getWeeklyAwardRewardsPath(path)).withPlaceHolder("place", "" + place) + .withPlaceHolder("topvoter", "Weekly").withPlaceHolder("votes", "" + getTotal(TopVoter.Weekly)) + .setOnline(isOnline()).send(this); + } + + /** + * Gets how many unique vote sites this user has voted on today. + * + * Uses existing LastVotes data (no storage). A site counts if its last-vote + * timestamp falls on "today" using VotingPlugin's time offset. + * + * @return number of unique sites voted on today + */ + public long getUniqueVoteSitesToday() { + LocalDateTime now = plugin.getTimeChecker().getTime(); + LocalDate today = now.toLocalDate(); + + long count = 0; + + for (VoteSite site : plugin.getVoteSiteManager().getVoteSitesEnabled()) { + if (site == null || !site.isEnabled() || site.isHidden()) { + continue; + } + + long time = getTime(site); + if (time <= 0) { + continue; + } + + // Match the same offset handling as canVoteSite() + LocalDateTime lastVote = LocalDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneId.systemDefault()) + .plusHours(plugin.getOptions().getTimeHourOffSet()); + + if (lastVote.toLocalDate().equals(today)) { + count++; + } + } + + return count; + } + + /** + * Checks if the user has a percentage of the total votes. + * + * @param top the top voter category + * @param percentage the percentage of the total votes + * @param time the specific time + * @return true if the user has the percentage of the total votes, false + * otherwise + */ + public boolean hasPercentageTotal(TopVoter top, double percentage, LocalDateTime time) { + int total = getTotal(top, time); + switch (top) { + case Daily: + return (double) total / (double) plugin.getVoteSiteManager().getVoteSitesEnabled().size() + * 100 > percentage; + case Monthly: + return total / ((double) plugin.getVoteSiteManager().getVoteSitesEnabled().size() + * time.getMonth().length(false)) * 100 > percentage; + case Weekly: + return total / ((double) plugin.getVoteSiteManager().getVoteSitesEnabled().size() * 7) * 100 > percentage; + default: + return false; + } + } + + /** + * Checks if the user is ignored for top voter. + * + * @return true if the user is ignored for top voter, false otherwise + */ + public boolean isTopVoterIgnore() { + return getUserData().getBoolean("TopVoterIgnore"); + } + + /** + * Gives login rewards to the user. + */ + public void loginRewards() { + if (plugin.getRewardHandler().hasRewards(plugin.getSpecialRewardsConfig().getData(), "LoginRewards")) { + new RewardBuilder(plugin.getSpecialRewardsConfig().getData(), "LoginRewards").send(this); + } + } + + /** + * Gives logout rewards to the user. + */ + public void logoutRewards() { + if (plugin.getRewardHandler().hasRewards(plugin.getSpecialRewardsConfig().getData(), "LogoutRewards")) { + new RewardBuilder(plugin.getSpecialRewardsConfig().getData(), "LogoutRewards").send(this); + } + } + + /** + * Merges the provided data with the current data. + * + * @param toAdd the data to add + */ + public void mergeData(HashMap toAdd) { + HashMap currentData = getData().getValues(); + HashMap newData = new HashMap<>(); + + for (TopVoter top : TopVoter.values()) { + if (toAdd.containsKey(top.getColumnName()) && currentData.containsKey(top.getColumnName())) { + newData.put(top.getColumnName(), new DataValueInt( + currentData.get(top.getColumnName()).getInt() + toAdd.get(top.getColumnName()).getInt())); + } + } + + if (newData.size() > 0) { + getData().setValues(newData); + } + } + + /** + * Processes offline votes. + */ + public void offVote() { + if (!plugin.getOptions().isProcessRewards()) { + plugin.debug("Processing rewards is disabled"); + return; + } + + Player player = getPlayer(); + if (!plugin.getOptions().isOnlineMode()) { + player = Bukkit.getPlayer(getPlayerName()); + } + if (player == null) { + return; + } + + plugin.extraDebug("Checking offline votes for " + player.getName() + "/" + getUUID()); + + // Update top voter ignore flag if needed. + boolean currentTopVoterIgnore = player.hasPermission("VotingPlugin.TopVoter.Ignore"); + if (isTopVoterIgnore() != currentTopVoterIgnore) { + setTopVoterIgnore(currentTopVoterIgnore); + } + + ArrayList offlineVotes = getOfflineVotes(); + if (offlineVotes.isEmpty()) { + return; + } + + // Send vote effects and clear persistent offline votes. + sendVoteEffects(false); + setOfflineVotes(new ArrayList<>()); + + // Process each offline vote. + for (String voteSiteName : offlineVotes) { + if (plugin.getVoteSiteManager().hasVoteSite(voteSiteName)) { + VoteSite voteSite = plugin.getVoteSiteManager().getVoteSite(voteSiteName, true); + if (voteSite != null && voteSite.isEnabled()) { + plugin.debug("Giving offline site reward: " + voteSiteName); + playerVote(voteSite, false, false); + } else { + plugin.debug("Skipping offline vote for disabled site: " + voteSiteName); + } + } else { + plugin.debug("Site doesn't exist: " + voteSiteName); + } + } + } + + /** + * Processes a player vote. + * + * @param voteSite the vote site + * @param online whether the player is online + * @param bungee whether to use bungee + */ + public void playerVote(VoteSite voteSite, boolean online, boolean bungee) { + voteSite.giveRewards(this, online, bungee); + } + + /** + * Removes points from the user. + * + * @param points the number of points to remove + * @return true if the points were removed, false otherwise + */ + public boolean removePoints(int points) { + if (getPoints() >= points) { + setPoints(getPoints() - points); + return true; + } + return false; + } + + /** + * Removes points from the user asynchronously. + * + * @param points the number of points to remove + * @param async whether to remove the points asynchronously + * @return true if the points were removed, false otherwise + */ + public boolean removePoints(int points, boolean async) { + if (getPoints() >= points) { + setPoints(getPoints() - points, async); + return true; + } + return false; + } + + /** + * Resets the last voted time for all vote sites. + */ + public void resetLastVoted() { + HashMap map = getLastVotes(); + for (Entry e : map.entrySet()) { + e.setValue(0l); + } + setLastVotes(map); + } + + /** + * Resets the last voted time for a specific vote site. + * + * @param site the vote site + */ + public void resetLastVoted(VoteSite site) { + HashMap map = getLastVotes(); + map.put(site, 0l); + setLastVotes(map); + } + + /** + * Resets the total votes for a specific top voter category. + * + * @param topVoter the top voter category + */ + public void resetTotals(TopVoter topVoter) { + setTotal(topVoter, 0); + } + + /** + * Sends vote effects to the user. + * + * @param online whether the user is online + */ + public void sendVoteEffects(boolean online) { + plugin.getRewardHandler().giveReward(this, plugin.getSpecialRewardsConfig().getData(), + plugin.getSpecialRewardsConfig().getAnySiteRewardsPath(), new RewardOptions().setOnline(online)); + } + + /** + * Sets the all-time total votes. + * + * @param allTimeTotal the all-time total votes + * @deprecated Use setTotal(TopVoter.AllTime, allTimeTotal) instead + */ + @Deprecated + public void setAllTimeTotal(int allTimeTotal) { + setTotal(TopVoter.AllTime, allTimeTotal); + } + + /** + * Sets the best day vote streak. + * + * @param streak the best day vote streak + */ + @Deprecated + public void setBestDayVoteStreak(int streak) { + getData().setInt("BestDayVoteStreak", streak); + } + + /** + * Sets the best month vote streak. + * + * @param streak the best month vote streak + */ + @Deprecated + public void setBestMonthVoteStreak(int streak) { + getData().setInt("BestMonthVoteStreak", streak); + } + + /** + * Sets the best week vote streak. + * + * @param streak the best week vote streak + */ + @Deprecated + public void setBestWeekVoteStreak(int streak) { + getData().setInt("BestWeekVoteStreak", streak); + } + + /** + * Sets the cooldown check. + * + * @param coolDownCheck whether the cooldown check is enabled + */ + public void setCoolDownCheck(boolean coolDownCheck) { + getData().setBoolean(getCoolDownCheckPath(), coolDownCheck); + } + + /** + * Sets the cooldown check for all vote sites. + * + * @param coolDownChecks the cooldown checks for all vote sites + */ + public void setCoolDownCheckSite(HashMap coolDownChecks) { + ArrayList data = new ArrayList<>(); + for (Entry entry : coolDownChecks.entrySet()) { + String str = entry.getKey() + "//" + entry.getValue().toString(); + data.add(str); + } + getUserData().setStringList(getCoolDownCheckSitePath(), data); + } + + /** + * Sets the cooldown check for a specific vote site. + * + * @param site the vote site + * @param value whether the cooldown check is enabled + */ + public void setCoolDownCheckSite(VoteSite site, boolean value) { + HashMap coolDownChecks = getCoolDownCheckSiteList(); + coolDownChecks.put(site.getKey(), Boolean.valueOf(value)); + setCoolDownCheckSite(coolDownChecks); + } + + /** + * Sets the daily total votes. + * + * @param total the daily total votes + * @deprecated Use setTotal(TopVoter.Daily, total) instead + */ + @Deprecated + public void setDailyTotal(int total) { + setTotal(TopVoter.Daily, total); + } + + /** + * Sets the day vote streak. + * + * @param streak the day vote streak + */ + @Deprecated + public void setDayVoteStreak(int streak) { + getData().setInt("DayVoteStreak", streak); + if (getBestDayVoteStreak() < streak) { + setBestDayVoteStreak(streak); + } + } + + /** + * Sets the last update time for the day vote streak. + * + * @param time the last update time for the day vote streak + */ + @Deprecated + public void setDayVoteStreakLastUpdate(long time) { + getData().setString("DayVoteStreakLastUpdate", "" + time); + } + + /** + * Sets whether the broadcast is disabled. + * + * @param value true to disable the broadcast, false otherwise + */ + public void setDisableBroadcast(boolean value) { + getUserData().setBoolean("DisableBroadcast", value); + } + + /** + * Sets the day when the user has gotten all sites. + * + * @param day the day when the user has gotten all sites + */ + public void setGottenAllSitesDay(int day) { + getData().setInt(plugin.getVotingPluginUserManager().getGottenAllSitesDayPath(), day); + } + + /** + * Sets the day when the user has gotten almost all sites. + * + * @param day the day when the user has gotten almost all sites + */ + public void setGottenAlmostAllSitesDay(int day) { + getData().setInt(plugin.getVotingPluginUserManager().getGottenAlmostAllSitesDayPath(), day); + } + + /** + * Sets the highest daily total votes. + * + * @param total the highest daily total votes + */ + public void setHighestDailyTotal(int total) { + getData().setInt("HighestDailyTotal", total); + } + + /** + * Sets the highest monthly total votes. + * + * @param total the highest monthly total votes + */ + public void setHighestMonthlyTotal(int total) { + getData().setInt("HighestMonthlyTotal", total); + } + + /** + * Sets the highest weekly total votes. + * + * @param total the highest weekly total votes + */ + public void setHighestWeeklyTotal(int total) { + getData().setInt("HighestWeeklyTotal", total); + } + + /** + * Sets the total votes for the last month. + * + * @param total the total votes for the last month + */ + public void setLastMonthTotal(int total) { + getData().setInt("LastMonthTotal", total); + } + + /** + * Sets the last votes for each vote site. + * + * @param lastVotes a map of vote sites and the last vote time + */ + public void setLastVotes(HashMap lastVotes) { + ArrayList data = new ArrayList<>(); + for (Entry entry : lastVotes.entrySet()) { + String str = entry.getKey().getKey() + "//" + entry.getValue().longValue(); + data.add(str); + } + getUserData().setStringList("LastVotes", data); + } + + /** + * Sets the total votes for the month. + * + * @param total the total votes for the month + * @deprecated Use setTotal(TopVoter.Monthly, total) instead + */ + @Deprecated + public void setMonthTotal(int total) { + setTotal(TopVoter.Monthly, total); + } + + /** + * Sets the month vote streak. + * + * @param streak the month vote streak + */ + @Deprecated + public void setMonthVoteStreak(int streak) { + getData().setInt("MonthVoteStreak", streak); + if (getBestMonthVoteStreak() < streak) { + setBestMonthVoteStreak(streak); + } + } + + /** + * Sets the list of offline votes. + * + * @param offlineVotes the list of offline votes + */ + public void setOfflineVotes(ArrayList offlineVotes) { + getUserData().setStringList("OfflineVotes", offlineVotes); + } + + /** + * Sets the points of the user. + * + * @param value the number of points + */ + public void setPoints(int value) { + getUserData().setInt(getPointsPath(), value, false); + } + + /** + * Sets the points of the user asynchronously. + * + * @param value the number of points + * @param async whether to set the points asynchronously + */ + public void setPoints(int value, boolean async) { + getUserData().setInt(getPointsPath(), value, false, async); + } + + /** + * Sets the current time for the specified vote site. + * + * @param voteSite the vote site + */ + public void setTime(VoteSite voteSite) { + setTime(voteSite, LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()); + } + + /** + * Sets the specified time for the specified vote site. + * + * @param voteSite the vote site + * @param time the time to set + */ + public void setTime(VoteSite voteSite, Long time) { + HashMap lastVotes = getLastVotes(); + if (lastVotes != null && lastVotes.containsKey(voteSite)) { + if (lastVotes.get(voteSite).longValue() == time.longValue()) { + plugin.debug("Not setting last vote time for " + voteSite.getKey() + ", already set to " + time); + return; + } + } + lastVotes.put(voteSite, time); + setLastVotes(lastVotes); + } + + /** + * Sets whether the user is ignored for top voter. + * + * @param topVoterIgnore true to ignore the user for top voter, false otherwise + */ + public void setTopVoterIgnore(boolean topVoterIgnore) { + getUserData().setString("TopVoterIgnore", "" + topVoterIgnore); + } + + /** + * Sets the total votes for the specified top voter category. + * + * @param top the top voter category + * @param value the total votes to set + */ + public void setTotal(TopVoter top, int value) { + switch (top) { + case AllTime: + getUserData().setInt("AllTimeTotal", value); + break; + case Daily: + getUserData().setInt("DailyTotal", value); + break; + case Monthly: + if (plugin.getConfigFile().isLimitMonthlyVotes()) { + LocalDateTime time = plugin.getTimeChecker().getTime(); + int days = time.getDayOfMonth(); + if (value >= days * plugin.getVoteSiteManager().getVoteSitesEnabled().size()) { + value = days * plugin.getVoteSiteManager().getVoteSitesEnabled().size(); + } + } + getData().setInt("MonthTotal", value); + if (plugin.getConfigFile().isStoreMonthTotalsWithDate()) { + getData().setInt(plugin.getVotingPluginUserManager().getMonthTotalsWithDatePath(), value); + } + break; + case Weekly: + getUserData().setInt("WeeklyTotal", value); + break; + default: + break; + } + } + + /** + * Sets the number of votes for the vote party. + * + * @param value the number of votes to set + */ + public void setVotePartyVotes(int value) { + getUserData().setInt("VotePartyVotes", value); + } + + /** + * Sets the vote shop identifier limit. + * + * @param identifier the identifier for the vote shop + * @param value the limit to set + */ + public void setVoteShopIdentifierLimit(String identifier, int value) { + getData().setInt("VoteShopLimit" + identifier, value); + } + + /** + * Sets the weekly total votes. + * + * @param total the weekly total votes + * @deprecated Use setTotal(TopVoter.Weekly, total) instead + */ + @Deprecated + public void setWeeklyTotal(int total) { + setTotal(TopVoter.Weekly, total); + } + + /** + * Sets the week vote streak. + * + * @param streak the week vote streak + */ + @Deprecated + public void setWeekVoteStreak(int streak) { + getData().setInt("WeekVoteStreak", streak); + if (getBestWeekVoteStreak() < streak) { + setBestWeekVoteStreak(streak); + } + } + + /** + * Checks if the user should be reminded. + * + * @return true if the user should be reminded, false otherwise + */ + public boolean shouldBeReminded() { + Player player = getPlayer(); + if (player != null) { + if (player.hasPermission("VotingPlugin.NoRemind")) { + return false; + } + } + return true; + } + + /** + * Gets the last vote date for the specified vote site. + * + * @param voteSite the vote site + * @return the last vote date as a string + * @deprecated Use getTime(VoteSite) instead + */ + @Deprecated + public String voteCommandLastDate(VoteSite voteSite) { + long time = getTime(voteSite); + if (time > 0) { + Date date = new Date(time); + String timeString = new SimpleDateFormat(plugin.getConfigFile().getFormatTimeFormat()).format(date); + if (MessageAPI.containsIgnorecase(timeString, "YamlConfiguration")) { + plugin.getLogger().warning("Detected issue parsing time, check time format"); + } + return timeString; + } + return ""; + } + + /** + * Gets the duration since the last vote for the specified vote site. + * + * @param voteSite the vote site + * @return the duration since the last vote as a string + */ + public String voteCommandLastDuration(VoteSite voteSite) { + long time = getTime(voteSite); + if (time > 0) { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime lastVote = LocalDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneId.systemDefault()); + + Duration dur = Duration.between(lastVote, now); + + long diffSecond = dur.getSeconds(); + int diffDays = (int) (diffSecond / 60 / 60 / 24); + int diffHours = (int) (diffSecond / 60 / 60 - diffDays * 24); + int diffMinutes = (int) (diffSecond / 60 - diffHours * 60 - diffDays * 24 * 60); + int diffSeconds = (int) (diffSecond - diffMinutes * 60 - diffHours * 60 * 60 - diffDays * 24 * 60 * 60); + + String info = ""; + if (diffDays == 1) { + info += PlaceholderUtils.replacePlaceHolder(PlaceholderUtils.replacePlaceHolder( + plugin.getConfigFile().getFormatCommandsVoteLastTimeFormat(), "TimeType", + plugin.getConfigFile().getFormatTimeFormatsDay()), "amount", "" + diffDays); + info += " "; + } else if (diffDays > 1) { + info += PlaceholderUtils.replacePlaceHolder(PlaceholderUtils.replacePlaceHolder( + plugin.getConfigFile().getFormatCommandsVoteLastTimeFormat(), "TimeType", + plugin.getConfigFile().getFormatTimeFormatsDays()), "amount", "" + diffDays); + info += " "; + } + + if (diffHours == 1) { + info += PlaceholderUtils.replacePlaceHolder(PlaceholderUtils.replacePlaceHolder( + plugin.getConfigFile().getFormatCommandsVoteLastTimeFormat(), "TimeType", + plugin.getConfigFile().getFormatTimeFormatsHour()), "amount", "" + diffHours); + info += " "; + } else if (diffHours > 1) { + info += PlaceholderUtils.replacePlaceHolder(PlaceholderUtils.replacePlaceHolder( + plugin.getConfigFile().getFormatCommandsVoteLastTimeFormat(), "TimeType", + plugin.getConfigFile().getFormatTimeFormatsHours()), "amount", "" + diffHours); + info += " "; + } + + if (diffMinutes == 1) { + info += PlaceholderUtils.replacePlaceHolder(PlaceholderUtils.replacePlaceHolder( + plugin.getConfigFile().getFormatCommandsVoteLastTimeFormat(), "TimeType", + plugin.getConfigFile().getFormatTimeFormatsMinute()), "amount", "" + diffMinutes); + info += " "; + } else if (diffMinutes > 1) { + info += PlaceholderUtils.replacePlaceHolder(PlaceholderUtils.replacePlaceHolder( + plugin.getConfigFile().getFormatCommandsVoteLastTimeFormat(), "TimeType", + plugin.getConfigFile().getFormatTimeFormatsMinutes()), "amount", "" + diffMinutes); + info += " "; + } + + if (plugin.getConfigFile().isFormatCommandsVoteLastIncludeSeconds()) { + if (diffSeconds == 1) { + info += PlaceholderUtils.replacePlaceHolder(PlaceholderUtils.replacePlaceHolder( + plugin.getConfigFile().getFormatCommandsVoteLastTimeFormat(), "TimeType", + plugin.getConfigFile().getFormatTimeFormatsSecond()), "amount", "" + diffSeconds); + } else { + info += PlaceholderUtils.replacePlaceHolder(PlaceholderUtils.replacePlaceHolder( + plugin.getConfigFile().getFormatCommandsVoteLastTimeFormat(), "TimeType", + plugin.getConfigFile().getFormatTimeFormatsSeconds()), "amount", "" + diffSeconds); + } + } + + info = PlaceholderUtils.replacePlaceHolder(plugin.getConfigFile().getFormatCommandsVoteLastLastVoted(), + "times", info); + + return info; + } + return plugin.getConfigFile().getFormatCommandsVoteLastNeverVoted(); + } + + /** + * Gets the last vote date and duration for the specified vote site for the GUI. + * + * @param voteSite the vote site + * @return the last vote date and duration as a string for the GUI + */ + public String voteCommandLastGUILine(VoteSite voteSite) { + String timeString = voteCommandLastDate(voteSite); + String timeSince = voteCommandLastDuration(voteSite); + + HashMap placeholders = new HashMap<>(); + placeholders.put("time", timeString); + placeholders.put("SiteName", voteSite.getDisplayName()); + placeholders.put("timesince", timeSince); + + return PlaceholderUtils.replacePlaceHolder(plugin.getGui().getChestVoteLastLine(), placeholders); + } + + /** + * Gets the last vote date and duration for the specified vote site. + * + * @param voteSite the vote site + * @return the last vote date and duration as a string + */ + public String voteCommandLastLine(VoteSite voteSite) { + String timeString = voteCommandLastDate(voteSite); + String timeSince = voteCommandLastDuration(voteSite); + + HashMap placeholders = new HashMap<>(); + placeholders.put("time", timeString); + placeholders.put("SiteName", voteSite.getDisplayName()); + placeholders.put("timesince", timeSince); + + return PlaceholderUtils.replacePlaceHolder(plugin.getConfigFile().getFormatCommandsVoteLastLine(), + placeholders); + } + + /** + * Gets the next available vote time for the specified vote site. + * + * @param voteSite the vote site + * @return the next available vote time as a string + */ + public String voteCommandNextInfo(VoteSite voteSite) { + return voteCommandNextInfo(voteSite, getTime(voteSite)); + } + + /** + * Gets the next available vote time for the specified vote site. + * + * @param voteSite the vote site + * @param time the current time + * @return the next available vote time as a string + */ + public String voteCommandNextInfo(VoteSite voteSite, long time) { + String info = new String(); + + long nextTime = voteNextDurationTime(voteSite, time); + if (nextTime == 0) { + info = plugin.getConfigFile().getFormatCommandsVoteNextInfoCanVote(); + } else { + int diffHours = (int) (nextTime / (60 * 60)); + long diffMinutes = nextTime / 60 - diffHours * 60; + + if (diffHours < 0) { + diffHours = diffHours * -1; + } + if (diffHours >= 24) { + diffHours = diffHours - 24; + } + if (diffMinutes < 0) { + diffMinutes = diffMinutes * -1; + } + + String timeMsg = plugin.getConfigFile().getFormatCommandsVoteNextInfoVoteDelayDaily(); + timeMsg = MessageAPI.replaceIgnoreCase(timeMsg, "%hours%", Integer.toString(diffHours)); + timeMsg = MessageAPI.replaceIgnoreCase(timeMsg, "%minutes%", Long.toString(diffMinutes)); + info = timeMsg; + } + + return info; + } + + /** + * Gets the next available vote duration time for the specified vote site. + * + * @param voteSite the vote site + * @return the next available vote duration time in seconds + */ + public long voteNextDurationTime(VoteSite voteSite) { + return voteNextDurationTime(voteSite, getTime(voteSite)); + } + + /** + * Gets the next available vote duration time for the specified vote site. + * + * @param voteSite the vote site + * @param time the last vote time (epoch millis) + * @return the next available vote duration time in seconds + */ + public long voteNextDurationTime(VoteSite voteSite, long time) { + LocalDateTime now = plugin.getTimeChecker().getTime(); + + LocalDateTime lastVote = LocalDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneId.systemDefault()) + .plusHours(plugin.getOptions().getTimeHourOffSet()); + + if (!voteSite.isVoteDelayDaily()) { + ParsedDuration voteDelay = voteSite.getVoteDelay(); + + if (time == 0 || voteDelay == null || voteDelay.isEmpty()) { + return 0; + } + + // Ignore months, use fixed duration only + LocalDateTime nextVote = lastVote.plus(Duration.ofMillis(voteDelay.getMillis())); + + if (now.isAfter(nextVote)) { + return 0; + } + + return Duration.between(now, nextVote).getSeconds(); + } + + // Daily reset logic (unchanged) + LocalDateTime resetTime = lastVote.withHour(voteSite.getVoteDelayDailyHour()).withMinute(0).withSecond(0); + + LocalDateTime resetTimeTomorrow = resetTime.plusHours(24); + + if (lastVote.isBefore(resetTime)) { + if (now.isBefore(resetTime)) { + return Duration.between(now, resetTime).getSeconds(); + } + } else { + if (now.isBefore(resetTimeTomorrow)) { + return Duration.between(now, resetTimeTomorrow).getSeconds(); + } + } + + return 0; + } + + /** + * Checks if the vote streak was updated today. + * + * @param time the current time + * @return true if the vote streak was updated today, false otherwise + */ + @Deprecated + public boolean voteStreakUpdatedToday(LocalDateTime time) { + return MiscUtils.getInstance().getTime(getDayVoteStreakLastUpdate()).getDayOfYear() == time.getDayOfYear(); + } + + public String getVoteStreakState(String columnName) { + return getData().getString(columnName); + } + + public void setVoteStreakState(String columnName, String value) { + getData().setString(columnName, value); + } + +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/votesites/VoteSiteManager.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/votesites/VoteSiteManager.java index 109c089ec..02ca9c6c3 100644 --- a/VotingPlugin/src/main/java/com/bencodez/votingplugin/votesites/VoteSiteManager.java +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/votesites/VoteSiteManager.java @@ -119,6 +119,31 @@ public String getVoteSiteName(boolean checkEnabled, String... urls) { } } + if (!checkEnabled) { + ArrayList configuredSites = plugin.getConfigVoteSites().getRawVoteSiteNames(); + if (configuredSites != null) { + for (String url : urls) { + if (url == null) { + return null; + } + if (url.isEmpty()) { + continue; + } + + String normalizedUrl = normalizeVoteSiteKey(url); + for (String siteName : configuredSites) { + String serviceSite = plugin.getConfigVoteSites().getServiceSite(siteName); + String displayName = plugin.getConfigVoteSites().getDisplayName(siteName); + if (siteName.equalsIgnoreCase(url) || siteName.equalsIgnoreCase(normalizedUrl) + || (serviceSite != null && !serviceSite.isEmpty() && serviceSite.equalsIgnoreCase(url)) + || (displayName != null && !displayName.isEmpty() && displayName.equalsIgnoreCase(url))) { + return siteName; + } + } + } + } + } + for (String url : urls) { return url; } @@ -214,6 +239,18 @@ public String getVoteSiteServiceSite(String name) { */ public boolean hasVoteSite(String site) { String siteName = getVoteSiteName(false, site); + if (siteName == null) { + return false; + } + + ArrayList configuredSites = plugin.getConfigVoteSites().getRawVoteSiteNames(); + if (configuredSites != null) { + for (String configuredSite : configuredSites) { + if (configuredSite.equalsIgnoreCase(siteName)) { + return true; + } + } + } for (VoteSite voteSite : getVoteSites()) { if (voteSite.getKey().equalsIgnoreCase(siteName)) { diff --git a/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/cleanup/Pr1546LineEndingCleanupTest.java b/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/cleanup/Pr1546LineEndingCleanupTest.java new file mode 100644 index 000000000..d1880bdad --- /dev/null +++ b/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/cleanup/Pr1546LineEndingCleanupTest.java @@ -0,0 +1,104 @@ +package com.bencodez.votingplugin.tests.cleanup; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Set; +import java.util.TreeSet; + +import org.junit.jupiter.api.Test; + +/** + * One-shot cleanup for PR 1546. It only runs in the repository's own GitHub + * Actions pull-request build and removes itself in the cleanup commit. + */ +public class Pr1546LineEndingCleanupTest { + + private static final String BRANCH = "codex/review-pr-response"; + private static final String CONFIG_PATH = + "VotingPlugin/src/main/java/com/bencodez/votingplugin/config/ConfigVoteSites.java"; + private static final String USER_PATH = + "VotingPlugin/src/main/java/com/bencodez/votingplugin/user/VotingPluginUser.java"; + private static final String WORKFLOW_PATH = ".github/workflows/pr1546-line-ending-cleanup.yml"; + private static final String TEST_PATH = + "VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/cleanup/Pr1546LineEndingCleanupTest.java"; + + @Test + public void normalizeAndPushCleanPrDiff() throws Exception { + if (!"true".equals(System.getenv("GITHUB_ACTIONS")) + || !"pull_request".equals(System.getenv("GITHUB_EVENT_NAME")) + || !BRANCH.equals(System.getenv("GITHUB_HEAD_REF"))) { + return; + } + + String workspace = System.getenv("GITHUB_WORKSPACE"); + assertTrue(workspace != null && !workspace.isEmpty(), "GITHUB_WORKSPACE is required"); + Path repository = Paths.get(workspace); + + run(repository, "git", "fetch", "origin", BRANCH); + run(repository, "git", "checkout", "-B", BRANCH, "origin/" + BRANCH); + + normalizeToCrLf(repository.resolve(CONFIG_PATH)); + normalizeToCrLf(repository.resolve(USER_PATH)); + Files.deleteIfExists(repository.resolve(WORKFLOW_PATH)); + Files.deleteIfExists(repository.resolve(TEST_PATH)); + + run(repository, "git", "config", "user.name", "github-actions[bot]"); + run(repository, "git", "config", "user.email", + "41898282+github-actions[bot]@users.noreply.github.com"); + run(repository, "git", "add", "-A", "--", CONFIG_PATH, USER_PATH, WORKFLOW_PATH, TEST_PATH); + run(repository, "git", "diff", "--cached", "--check"); + + Set expected = new TreeSet<>(Arrays.asList(CONFIG_PATH, USER_PATH, WORKFLOW_PATH, TEST_PATH)); + Set actual = new TreeSet<>(); + String changed = run(repository, "git", "diff", "--cached", "--name-only"); + for (String path : changed.split("\\r?\\n")) { + if (!path.isEmpty()) actual.add(path); + } + assertEquals(expected, actual, "cleanup must change only the two sources and remove its temporary files"); + + run(repository, "git", "commit", "-m", "Restore Java source line endings"); + run(repository, "git", "push", "origin", "HEAD:" + BRANCH); + } + + private static void normalizeToCrLf(Path path) throws IOException { + String source = new String(Files.readAllBytes(path), StandardCharsets.UTF_8); + String lf = source.replace("\r\n", "\n").replace('\r', '\n'); + Files.write(path, lf.replace("\n", "\r\n").getBytes(StandardCharsets.UTF_8)); + + byte[] bytes = Files.readAllBytes(path); + for (int i = 0; i < bytes.length; i++) { + if (bytes[i] == '\n') { + assertTrue(i > 0 && bytes[i - 1] == '\r', "non-CRLF newline remains in " + path); + } + } + } + + private static String run(Path repository, String... command) throws Exception { + ProcessBuilder builder = new ProcessBuilder(command); + builder.directory(new File(repository.toString())); + builder.redirectErrorStream(true); + Process process = builder.start(); + StringBuilder output = new StringBuilder(); + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + output.append(line).append('\n'); + } + } + int exitCode = process.waitFor(); + assertEquals(0, exitCode, + "command failed: " + Arrays.toString(command) + "\n" + output.toString()); + return output.toString(); + } +} diff --git a/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/votesite/VoteSiteManagerTest.java b/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/votesite/VoteSiteManagerTest.java index 2f9db5206..69aaf5bbf 100644 --- a/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/votesite/VoteSiteManagerTest.java +++ b/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/votesite/VoteSiteManagerTest.java @@ -216,6 +216,51 @@ public void testHasVoteSiteTrueWhenPresent() { assertTrue(manager.hasVoteSite("site_test")); } + @Test + public void testDisabledConfiguredVoteSiteIsNotAutoCreated() { + when(configFile.isAutoCreateVoteSites()).thenReturn(true); + when(voteSitesConfig.getRawVoteSiteNames()) + .thenReturn(new ArrayList(Arrays.asList("DisabledSite"))); + when(voteSitesConfig.getServiceSite("DisabledSite")).thenReturn("disabled.example.com"); + when(voteSitesConfig.getDisplayName("DisabledSite")).thenReturn("Disabled Site"); + + manager.setVoteSites(Collections.synchronizedList(new ArrayList())); + + assertEquals("DisabledSite", manager.getVoteSiteName(false, "disabled.example.com")); + assertTrue(manager.hasVoteSite("disabled.example.com")); + assertNull(manager.getVoteSite("disabled.example.com", true)); + verify(voteSitesConfig, never()).tryGenerateVoteSite(anyString()); + } + + @Test + public void testDisabledConfiguredVoteSiteMatchesNormalizedKeyWithoutAutoCreation() { + when(configFile.isAutoCreateVoteSites()).thenReturn(true); + when(voteSitesConfig.getRawVoteSiteNames()) + .thenReturn(new ArrayList(Arrays.asList("disabled_site"))); + when(voteSitesConfig.getServiceSite("disabled_site")).thenReturn(""); + when(voteSitesConfig.getDisplayName("disabled_site")).thenReturn(""); + + manager.setVoteSites(Collections.synchronizedList(new ArrayList())); + + assertEquals("disabled_site", manager.getVoteSiteName(false, "disabled.site")); + assertTrue(manager.hasVoteSite("disabled.site")); + assertNull(manager.getVoteSite("disabled.site", true)); + verify(voteSitesConfig, never()).tryGenerateVoteSite(anyString()); + } + + @Test + public void testNullConfiguredSiteInputDoesNotThrowOrAutoCreate() { + when(configFile.isAutoCreateVoteSites()).thenReturn(true); + when(voteSitesConfig.getRawVoteSiteNames()) + .thenReturn(new ArrayList(Arrays.asList("DisabledSite"))); + + manager.setVoteSites(Collections.synchronizedList(new ArrayList())); + + assertNull(manager.getVoteSiteName(false, (String) null)); + assertFalse(manager.hasVoteSite(null)); + verify(voteSitesConfig, never()).tryGenerateVoteSite(anyString()); + } + @Test public void testIsVoteSiteTrueWhenKeyPresent() { VoteSite site = new VoteSite(plugin, "site.test");