From b6cb26063535870e32fcd89169ca504358bb2ee1 Mon Sep 17 00:00:00 2001 From: Xelara Networks Date: Sat, 13 Jun 2026 18:45:07 -0400 Subject: [PATCH] New --- README.md | 0 pom.xml | 52 +++ .../dirtbagmc/dirtreferrals/AdminCommand.java | 152 ++++++++ .../dirtreferrals/DatabaseManager.java | 333 ++++++++++++++++++ .../dirtreferrals/DirtReferralsPlugin.java | 96 +++++ .../dirtreferrals/MessageManager.java | 35 ++ .../dirtreferrals/PlayerListener.java | 42 +++ .../dirtreferrals/ReferralCommand.java | 35 ++ .../dirtreferrals/ReferralManager.java | 229 ++++++++++++ .../com/dirtbagmc/dirtreferrals/TopEntry.java | 28 ++ src/main/resources/config.yml | 38 ++ src/main/resources/messages.yml | 26 ++ src/main/resources/plugin.yml | 26 ++ target/DirtReferrals-1.0.0.jar | Bin 0 -> 24316 bytes .../dirtreferrals/AdminCommand.class | Bin 0 -> 4877 bytes .../dirtreferrals/DatabaseManager.class | Bin 0 -> 15018 bytes .../dirtreferrals/DirtReferralsPlugin.class | Bin 0 -> 3904 bytes .../dirtreferrals/MessageManager.class | Bin 0 -> 2160 bytes .../dirtreferrals/PlayerListener.class | Bin 0 -> 2536 bytes .../dirtreferrals/ReferralCommand.class | Bin 0 -> 2018 bytes .../ReferralManager$PendingReferral.class | Bin 0 -> 1984 bytes .../dirtreferrals/ReferralManager.class | Bin 0 -> 11584 bytes .../dirtbagmc/dirtreferrals/TopEntry.class | Bin 0 -> 791 bytes target/classes/config.yml | 38 ++ target/classes/messages.yml | 26 ++ target/classes/plugin.yml | 26 ++ target/maven-archiver/pom.properties | 5 + .../compile/default-compile/createdFiles.lst | 9 + .../compile/default-compile/inputFiles.lst | 8 + 29 files changed, 1204 insertions(+) create mode 100644 README.md create mode 100644 pom.xml create mode 100644 src/main/java/com/dirtbagmc/dirtreferrals/AdminCommand.java create mode 100644 src/main/java/com/dirtbagmc/dirtreferrals/DatabaseManager.java create mode 100644 src/main/java/com/dirtbagmc/dirtreferrals/DirtReferralsPlugin.java create mode 100644 src/main/java/com/dirtbagmc/dirtreferrals/MessageManager.java create mode 100644 src/main/java/com/dirtbagmc/dirtreferrals/PlayerListener.java create mode 100644 src/main/java/com/dirtbagmc/dirtreferrals/ReferralCommand.java create mode 100644 src/main/java/com/dirtbagmc/dirtreferrals/ReferralManager.java create mode 100644 src/main/java/com/dirtbagmc/dirtreferrals/TopEntry.java create mode 100644 src/main/resources/config.yml create mode 100644 src/main/resources/messages.yml create mode 100644 src/main/resources/plugin.yml create mode 100644 target/DirtReferrals-1.0.0.jar create mode 100644 target/classes/com/dirtbagmc/dirtreferrals/AdminCommand.class create mode 100644 target/classes/com/dirtbagmc/dirtreferrals/DatabaseManager.class create mode 100644 target/classes/com/dirtbagmc/dirtreferrals/DirtReferralsPlugin.class create mode 100644 target/classes/com/dirtbagmc/dirtreferrals/MessageManager.class create mode 100644 target/classes/com/dirtbagmc/dirtreferrals/PlayerListener.class create mode 100644 target/classes/com/dirtbagmc/dirtreferrals/ReferralCommand.class create mode 100644 target/classes/com/dirtbagmc/dirtreferrals/ReferralManager$PendingReferral.class create mode 100644 target/classes/com/dirtbagmc/dirtreferrals/ReferralManager.class create mode 100644 target/classes/com/dirtbagmc/dirtreferrals/TopEntry.class create mode 100644 target/classes/config.yml create mode 100644 target/classes/messages.yml create mode 100644 target/classes/plugin.yml create mode 100644 target/maven-archiver/pom.properties create mode 100644 target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst create mode 100644 target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..bf63c8c --- /dev/null +++ b/pom.xml @@ -0,0 +1,52 @@ + + 4.0.0 + + com.dirtbagmc + DirtReferrals + 1.0.0 + jar + + DirtReferrals + Referral tracking plugin for Paper + + + 21 + UTF-8 + + + + + papermc-repo + https://repo.papermc.io/repository/maven-public/ + + + + + + io.papermc.paper + paper-api + 1.21.1-R0.1-SNAPSHOT + provided + + + org.xerial + sqlite-jdbc + 3.46.1.3 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + 21 + + + + + diff --git a/src/main/java/com/dirtbagmc/dirtreferrals/AdminCommand.java b/src/main/java/com/dirtbagmc/dirtreferrals/AdminCommand.java new file mode 100644 index 0000000..581c4c0 --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtreferrals/AdminCommand.java @@ -0,0 +1,152 @@ +package com.dirtbagmc.dirtreferrals; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; + +import java.util.List; +import java.util.UUID; + +public class AdminCommand implements CommandExecutor { + + private final DirtReferralsPlugin plugin; + + public AdminCommand(DirtReferralsPlugin plugin) { + this.plugin = plugin; + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!sender.hasPermission("dirtreferrals.admin")) { + plugin.getMessageManager().send(sender, "no-permission"); + return true; + } + + if (args.length == 0) { + plugin.getMessageManager().send(sender, "usage-admin"); + return true; + } + + switch (args[0].toLowerCase()) { + case "reload" -> { + plugin.reloadPlugin(); + plugin.getMessageManager().send(sender, "reloaded"); + return true; + } + case "stats" -> { + if (args.length < 2) { + plugin.getMessageManager().send(sender, "usage-stats"); + return true; + } + + UUID uuid = plugin.getDatabaseManager().getPlayerUuidByName(args[1]); + if (uuid == null) { + plugin.getMessageManager().send(sender, "no-data"); + return true; + } + + String name = plugin.getDatabaseManager().getPlayerName(uuid); + int count = plugin.getDatabaseManager().getReferralCount(uuid); + + sender.sendMessage(plugin.getMessageManager().get("stats-header") + .replace("%player%", name == null ? args[1] : name)); + sender.sendMessage(plugin.getMessageManager().get("stats-count") + .replace("%count%", String.valueOf(count))); + return true; + } + case "top" -> { + List top = plugin.getDatabaseManager().getTopReferrers(10); + sender.sendMessage(plugin.getMessageManager().get("top-header")); + + if (top.isEmpty()) { + plugin.getMessageManager().send(sender, "no-data"); + return true; + } + + int position = 1; + for (TopEntry entry : top) { + sender.sendMessage(plugin.getMessageManager().get("top-entry") + .replace("%position%", String.valueOf(position)) + .replace("%player%", entry.getName()) + .replace("%count%", String.valueOf(entry.getCount()))); + position++; + } + return true; + } + case "give" -> { + if (args.length < 3) { + plugin.getMessageManager().send(sender, "usage-give"); + return true; + } + + UUID uuid = plugin.getDatabaseManager().getPlayerUuidByName(args[1]); + if (uuid == null) { + plugin.getMessageManager().send(sender, "no-data"); + return true; + } + + int amount; + try { + amount = Integer.parseInt(args[2]); + } catch (NumberFormatException e) { + plugin.getMessageManager().send(sender, "invalid-number"); + return true; + } + + if (amount < 0) { + plugin.getMessageManager().send(sender, "invalid-number"); + return true; + } + + plugin.getDatabaseManager().addReferralCount(uuid, amount); + + sender.sendMessage(plugin.getMessageManager().get("credit-added") + .replace("%amount%", String.valueOf(amount)) + .replace("%player%", plugin.getDatabaseManager().getPlayerName(uuid))); + return true; + } + case "take" -> { + if (args.length < 3) { + plugin.getMessageManager().send(sender, "usage-take"); + return true; + } + + UUID uuid = plugin.getDatabaseManager().getPlayerUuidByName(args[1]); + if (uuid == null) { + plugin.getMessageManager().send(sender, "no-data"); + return true; + } + + int amount; + try { + amount = Integer.parseInt(args[2]); + } catch (NumberFormatException e) { + plugin.getMessageManager().send(sender, "invalid-number"); + return true; + } + + if (amount < 0) { + plugin.getMessageManager().send(sender, "invalid-number"); + return true; + } + + int current = plugin.getDatabaseManager().getReferralCount(uuid); + if (current < amount) { + plugin.getMessageManager().send(sender, "not-enough-credit"); + return true; + } + + plugin.getDatabaseManager().setReferralCount(uuid, current - amount); + + sender.sendMessage(plugin.getMessageManager().get("credit-removed") + .replace("%amount%", String.valueOf(amount)) + .replace("%player%", plugin.getDatabaseManager().getPlayerName(uuid))); + return true; + } + default -> { + plugin.getMessageManager().send(sender, "unknown-subcommand"); + return true; + } + } + } +} diff --git a/src/main/java/com/dirtbagmc/dirtreferrals/DatabaseManager.java b/src/main/java/com/dirtbagmc/dirtreferrals/DatabaseManager.java new file mode 100644 index 0000000..b6bafa8 --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtreferrals/DatabaseManager.java @@ -0,0 +1,333 @@ +package com.dirtbagmc.dirtreferrals; + +import java.io.File; +import java.sql.*; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class DatabaseManager { + + private final DirtReferralsPlugin plugin; + private Connection connection; + + public DatabaseManager(DirtReferralsPlugin plugin) { + this.plugin = plugin; + } + + public void initialize() { + try { + if (!plugin.getDataFolder().exists()) { + plugin.getDataFolder().mkdirs(); + } + + String fileName = plugin.getConfig().getString("database.file", "referrals.db"); + File dbFile = new File(plugin.getDataFolder(), fileName); + connection = DriverManager.getConnection("jdbc:sqlite:" + dbFile.getAbsolutePath()); + + createTables(); + } catch (SQLException e) { + plugin.getLogger().severe("Failed to initialize SQLite database."); + e.printStackTrace(); + } + } + + private void createTables() throws SQLException { + try (Statement statement = connection.createStatement()) { + statement.executeUpdate(""" + CREATE TABLE IF NOT EXISTS players ( + uuid TEXT PRIMARY KEY, + name TEXT NOT NULL, + referral_count INTEGER NOT NULL DEFAULT 0 + ) + """); + + statement.executeUpdate(""" + CREATE TABLE IF NOT EXISTS referrals ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + referrer_uuid TEXT NOT NULL, + referred_uuid TEXT NOT NULL, + referred_name TEXT NOT NULL, + join_host TEXT, + ip_address TEXT, + counted INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL + ) + """); + + statement.executeUpdate(""" + CREATE TABLE IF NOT EXISTS player_ips ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + player_uuid TEXT NOT NULL, + ip_address TEXT NOT NULL, + created_at INTEGER NOT NULL + ) + """); + + statement.executeUpdate(""" + CREATE TABLE IF NOT EXISTS claimed_rewards ( + referrer_uuid TEXT NOT NULL, + milestone INTEGER NOT NULL, + claimed_at INTEGER NOT NULL, + PRIMARY KEY (referrer_uuid, milestone) + ) + """); + } + } + + public void close() { + if (connection != null) { + try { + connection.close(); + } catch (SQLException e) { + plugin.getLogger().severe("Failed to close database connection."); + e.printStackTrace(); + } + } + } + + public void upsertPlayer(UUID uuid, String name) { + String sql = """ + INSERT INTO players (uuid, name, referral_count) + VALUES (?, ?, 0) + ON CONFLICT(uuid) DO UPDATE SET name = excluded.name + """; + + try (PreparedStatement ps = connection.prepareStatement(sql)) { + ps.setString(1, uuid.toString()); + ps.setString(2, name); + ps.executeUpdate(); + } catch (SQLException e) { + plugin.getLogger().severe("Failed to upsert player " + name); + e.printStackTrace(); + } + } + + public String getPlayerName(UUID uuid) { + String sql = "SELECT name FROM players WHERE uuid = ?"; + try (PreparedStatement ps = connection.prepareStatement(sql)) { + ps.setString(1, uuid.toString()); + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + return rs.getString("name"); + } + } + } catch (SQLException e) { + plugin.getLogger().severe("Failed to get player name."); + e.printStackTrace(); + } + return null; + } + + public UUID getPlayerUuidByName(String name) { + String sql = "SELECT uuid FROM players WHERE LOWER(name) = LOWER(?)"; + try (PreparedStatement ps = connection.prepareStatement(sql)) { + ps.setString(1, name); + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + return UUID.fromString(rs.getString("uuid")); + } + } + } catch (SQLException e) { + plugin.getLogger().severe("Failed to get UUID by player name."); + e.printStackTrace(); + } + return null; + } + + public int getReferralCount(UUID uuid) { + String sql = "SELECT referral_count FROM players WHERE uuid = ?"; + try (PreparedStatement ps = connection.prepareStatement(sql)) { + ps.setString(1, uuid.toString()); + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + return rs.getInt("referral_count"); + } + } + } catch (SQLException e) { + plugin.getLogger().severe("Failed to get referral count."); + e.printStackTrace(); + } + return 0; + } + + public void setReferralCount(UUID uuid, int count) { + String sql = "UPDATE players SET referral_count = ? WHERE uuid = ?"; + try (PreparedStatement ps = connection.prepareStatement(sql)) { + ps.setInt(1, count); + ps.setString(2, uuid.toString()); + ps.executeUpdate(); + } catch (SQLException e) { + plugin.getLogger().severe("Failed to set referral count."); + e.printStackTrace(); + } + } + + public void addReferralCount(UUID uuid, int amount) { + setReferralCount(uuid, Math.max(0, getReferralCount(uuid) + amount)); + } + + public boolean hasReferralRecord(UUID referrerUuid, UUID referredUuid) { + String sql = "SELECT 1 FROM referrals WHERE referrer_uuid = ? AND referred_uuid = ? LIMIT 1"; + try (PreparedStatement ps = connection.prepareStatement(sql)) { + ps.setString(1, referrerUuid.toString()); + ps.setString(2, referredUuid.toString()); + try (ResultSet rs = ps.executeQuery()) { + return rs.next(); + } + } catch (SQLException e) { + plugin.getLogger().severe("Failed checking referral record."); + e.printStackTrace(); + } + return false; + } + + public boolean hasAnyReferralRecordForReferred(UUID referredUuid) { + String sql = "SELECT 1 FROM referrals WHERE referred_uuid = ? LIMIT 1"; + try (PreparedStatement ps = connection.prepareStatement(sql)) { + ps.setString(1, referredUuid.toString()); + try (ResultSet rs = ps.executeQuery()) { + return rs.next(); + } + } catch (SQLException e) { + plugin.getLogger().severe("Failed checking referred player history."); + e.printStackTrace(); + } + return false; + } + + public void insertReferral(UUID referrerUuid, UUID referredUuid, String referredName, String joinHost, String ipAddress, boolean counted) { + String sql = """ + INSERT INTO referrals (referrer_uuid, referred_uuid, referred_name, join_host, ip_address, counted, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?) + """; + + try (PreparedStatement ps = connection.prepareStatement(sql)) { + ps.setString(1, referrerUuid.toString()); + ps.setString(2, referredUuid.toString()); + ps.setString(3, referredName); + ps.setString(4, joinHost); + ps.setString(5, ipAddress); + ps.setInt(6, counted ? 1 : 0); + ps.setLong(7, System.currentTimeMillis()); + ps.executeUpdate(); + } catch (SQLException e) { + plugin.getLogger().severe("Failed to insert referral."); + e.printStackTrace(); + } + } + + public void addPlayerIp(UUID playerUuid, String ipAddress) { + String checkSql = "SELECT 1 FROM player_ips WHERE player_uuid = ? AND ip_address = ? LIMIT 1"; + String insertSql = "INSERT INTO player_ips (player_uuid, ip_address, created_at) VALUES (?, ?, ?)"; + + try (PreparedStatement check = connection.prepareStatement(checkSql)) { + check.setString(1, playerUuid.toString()); + check.setString(2, ipAddress); + try (ResultSet rs = check.executeQuery()) { + if (rs.next()) { + return; + } + } + } catch (SQLException e) { + plugin.getLogger().severe("Failed checking existing player IP."); + e.printStackTrace(); + return; + } + + try (PreparedStatement insert = connection.prepareStatement(insertSql)) { + insert.setString(1, playerUuid.toString()); + insert.setString(2, ipAddress); + insert.setLong(3, System.currentTimeMillis()); + insert.executeUpdate(); + } catch (SQLException e) { + plugin.getLogger().severe("Failed storing player IP."); + e.printStackTrace(); + } + } + + public boolean playerHasIp(UUID playerUuid, String ipAddress) { + String sql = "SELECT 1 FROM player_ips WHERE player_uuid = ? AND ip_address = ? LIMIT 1"; + try (PreparedStatement ps = connection.prepareStatement(sql)) { + ps.setString(1, playerUuid.toString()); + ps.setString(2, ipAddress); + try (ResultSet rs = ps.executeQuery()) { + return rs.next(); + } + } catch (SQLException e) { + plugin.getLogger().severe("Failed checking player IP."); + e.printStackTrace(); + } + return false; + } + + public boolean anyReferralUsesIpForReferrer(UUID referrerUuid, String ipAddress) { + String sql = "SELECT 1 FROM referrals WHERE referrer_uuid = ? AND ip_address = ? LIMIT 1"; + try (PreparedStatement ps = connection.prepareStatement(sql)) { + ps.setString(1, referrerUuid.toString()); + ps.setString(2, ipAddress); + try (ResultSet rs = ps.executeQuery()) { + return rs.next(); + } + } catch (SQLException e) { + plugin.getLogger().severe("Failed checking referral IP reuse."); + e.printStackTrace(); + } + return false; + } + + public boolean hasClaimedReward(UUID referrerUuid, int milestone) { + String sql = "SELECT 1 FROM claimed_rewards WHERE referrer_uuid = ? AND milestone = ? LIMIT 1"; + try (PreparedStatement ps = connection.prepareStatement(sql)) { + ps.setString(1, referrerUuid.toString()); + ps.setInt(2, milestone); + try (ResultSet rs = ps.executeQuery()) { + return rs.next(); + } + } catch (SQLException e) { + plugin.getLogger().severe("Failed checking claimed reward."); + e.printStackTrace(); + } + return false; + } + + public void setRewardClaimed(UUID referrerUuid, int milestone) { + String sql = """ + INSERT OR IGNORE INTO claimed_rewards (referrer_uuid, milestone, claimed_at) + VALUES (?, ?, ?) + """; + + try (PreparedStatement ps = connection.prepareStatement(sql)) { + ps.setString(1, referrerUuid.toString()); + ps.setInt(2, milestone); + ps.setLong(3, System.currentTimeMillis()); + ps.executeUpdate(); + } catch (SQLException e) { + plugin.getLogger().severe("Failed marking reward as claimed."); + e.printStackTrace(); + } + } + + public List getTopReferrers(int limit) { + List results = new ArrayList<>(); + String sql = "SELECT uuid, name, referral_count FROM players ORDER BY referral_count DESC, name ASC LIMIT ?"; + + try (PreparedStatement ps = connection.prepareStatement(sql)) { + ps.setInt(1, limit); + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + results.add(new TopEntry( + UUID.fromString(rs.getString("uuid")), + rs.getString("name"), + rs.getInt("referral_count") + )); + } + } + } catch (SQLException e) { + plugin.getLogger().severe("Failed loading top referrers."); + e.printStackTrace(); + } + + return results; + } +} diff --git a/src/main/java/com/dirtbagmc/dirtreferrals/DirtReferralsPlugin.java b/src/main/java/com/dirtbagmc/dirtreferrals/DirtReferralsPlugin.java new file mode 100644 index 0000000..c65c0f7 --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtreferrals/DirtReferralsPlugin.java @@ -0,0 +1,96 @@ +package com.dirtbagmc.dirtreferrals; + +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; +import java.io.IOException; + +public class DirtReferralsPlugin extends JavaPlugin { + + private File messagesFile; + private FileConfiguration messages; + + private DatabaseManager databaseManager; + private ReferralManager referralManager; + private MessageManager messageManager; + + @Override + public void onEnable() { + saveDefaultConfig(); + createMessagesFile(); + this.messageManager = new MessageManager(this); + this.databaseManager = new DatabaseManager(this); + this.databaseManager.initialize(); + this.referralManager = new ReferralManager(this); + + getCommand("refer").setExecutor(new ReferralCommand(this)); + getCommand("dirtreferrals").setExecutor(new AdminCommand(this)); + + Bukkit.getPluginManager().registerEvents(new PlayerListener(this), this); + + getLogger().info("DirtReferrals enabled."); + } + + @Override + public void onDisable() { + if (referralManager != null) { + referralManager.shutdown(); + } + if (databaseManager != null) { + databaseManager.close(); + } + getLogger().info("DirtReferrals disabled."); + } + + public void reloadPlugin() { + reloadConfig(); + reloadMessages(); + if (referralManager != null) { + referralManager.reload(); + } + } + + private void createMessagesFile() { + if (!getDataFolder().exists()) { + getDataFolder().mkdirs(); + } + + messagesFile = new File(getDataFolder(), "messages.yml"); + if (!messagesFile.exists()) { + saveResource("messages.yml", false); + } + messages = YamlConfiguration.loadConfiguration(messagesFile); + } + + public void reloadMessages() { + messages = YamlConfiguration.loadConfiguration(messagesFile); + } + + public FileConfiguration getMessages() { + return messages; + } + + public void saveMessages() { + try { + messages.save(messagesFile); + } catch (IOException e) { + getLogger().severe("Could not save messages.yml"); + e.printStackTrace(); + } + } + + public DatabaseManager getDatabaseManager() { + return databaseManager; + } + + public ReferralManager getReferralManager() { + return referralManager; + } + + public MessageManager getMessageManager() { + return messageManager; + } +} diff --git a/src/main/java/com/dirtbagmc/dirtreferrals/MessageManager.java b/src/main/java/com/dirtbagmc/dirtreferrals/MessageManager.java new file mode 100644 index 0000000..a1a2803 --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtreferrals/MessageManager.java @@ -0,0 +1,35 @@ +package com.dirtbagmc.dirtreferrals; + +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; + +public class MessageManager { + + private final DirtReferralsPlugin plugin; + + public MessageManager(DirtReferralsPlugin plugin) { + this.plugin = plugin; + } + + public String get(String path) { + String prefix = plugin.getMessages().getString("prefix", "&6[DirtReferrals] "); + String message = plugin.getMessages().getString(path, "&cMissing message: " + path); + return color(prefix + message); + } + + public String getRaw(String path) { + return color(plugin.getMessages().getString(path, "&cMissing message: " + path)); + } + + public String color(String input) { + return ChatColor.translateAlternateColorCodes('&', input); + } + + public void send(CommandSender sender, String path) { + sender.sendMessage(get(path)); + } + + public void sendRaw(CommandSender sender, String message) { + sender.sendMessage(color(message)); + } +} diff --git a/src/main/java/com/dirtbagmc/dirtreferrals/PlayerListener.java b/src/main/java/com/dirtbagmc/dirtreferrals/PlayerListener.java new file mode 100644 index 0000000..02d32dd --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtreferrals/PlayerListener.java @@ -0,0 +1,42 @@ +package com.dirtbagmc.dirtreferrals; + +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerLoginEvent; + +public class PlayerListener implements Listener { + + private final DirtReferralsPlugin plugin; + + public PlayerListener(DirtReferralsPlugin plugin) { + this.plugin = plugin; + } + + @EventHandler + public void onLogin(PlayerLoginEvent event) { + String joinHost = null; + + try { + if (event.getHostname() != null && !event.getHostname().isBlank()) { + joinHost = event.getHostname(); + } + } catch (NoSuchMethodError ignored) { + } + + if (joinHost != null) { + plugin.getLogger().info("Player " + event.getPlayer().getName() + " joined with hostname: " + joinHost); + } + + Player player = event.getPlayer(); + plugin.getReferralManager().queueReferral(player, joinHost); + } + + @EventHandler + public void onJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + plugin.getReferralManager().registerPlayer(player); + plugin.getReferralManager().processQueuedReferral(player); + } +} diff --git a/src/main/java/com/dirtbagmc/dirtreferrals/ReferralCommand.java b/src/main/java/com/dirtbagmc/dirtreferrals/ReferralCommand.java new file mode 100644 index 0000000..9342064 --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtreferrals/ReferralCommand.java @@ -0,0 +1,35 @@ +package com.dirtbagmc.dirtreferrals; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class ReferralCommand implements CommandExecutor { + + private final DirtReferralsPlugin plugin; + + public ReferralCommand(DirtReferralsPlugin plugin) { + this.plugin = plugin; + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!(sender instanceof Player player)) { + plugin.getMessageManager().send(sender, "player-only"); + return true; + } + + if (!player.hasPermission("dirtreferrals.use")) { + plugin.getMessageManager().send(player, "no-permission"); + return true; + } + + String address = plugin.getReferralManager().getReferralAddress(player); + String message = plugin.getMessageManager().get("referral-link") + .replace("%referral_ip%", address); + + player.sendMessage(message); + return true; + } +} diff --git a/src/main/java/com/dirtbagmc/dirtreferrals/ReferralManager.java b/src/main/java/com/dirtbagmc/dirtreferrals/ReferralManager.java new file mode 100644 index 0000000..56a7f46 --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtreferrals/ReferralManager.java @@ -0,0 +1,229 @@ +package com.dirtbagmc.dirtreferrals; + +import org.bukkit.Bukkit; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Player; + +import java.net.InetAddress; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public class ReferralManager { + + private final DirtReferralsPlugin plugin; + private final Map pendingReferrals = new ConcurrentHashMap<>(); + + public ReferralManager(DirtReferralsPlugin plugin) { + this.plugin = plugin; + } + + public void reload() { + pendingReferrals.clear(); + } + + public void shutdown() { + pendingReferrals.clear(); + } + + public String getReferralAddress(Player player) { + String baseDomain = plugin.getConfig().getString("domain.base", "dirtbagmc.com"); + boolean lowercase = plugin.getConfig().getBoolean("domain.use-lowercase-playernames", true); + String name = lowercase ? player.getName().toLowerCase(Locale.ROOT) : player.getName(); + return name + "." + baseDomain; + } + + public void registerPlayer(Player player) { + plugin.getDatabaseManager().upsertPlayer(player.getUniqueId(), player.getName()); + + String ip = getPlayerIp(player); + if (ip != null && plugin.getConfig().getBoolean("referral.store-ip-addresses", true)) { + plugin.getDatabaseManager().addPlayerIp(player.getUniqueId(), transformIp(ip)); + } + } + + public void queueReferral(Player player, String joinHost) { + if (joinHost == null || joinHost.isBlank()) { + return; + } + + String baseDomain = plugin.getConfig().getString("domain.base", "dirtbagmc.com").toLowerCase(Locale.ROOT); + String normalizedHost = joinHost.toLowerCase(Locale.ROOT); + + if (!normalizedHost.endsWith("." + baseDomain)) { + return; + } + + String subdomain = normalizedHost.substring(0, normalizedHost.length() - ("." + baseDomain).length()); + if (subdomain.isBlank()) { + return; + } + + UUID referrerUuid = plugin.getDatabaseManager().getPlayerUuidByName(subdomain); + if (referrerUuid == null) { + return; + } + + pendingReferrals.put(player.getUniqueId(), new PendingReferral(referrerUuid, normalizedHost, System.currentTimeMillis())); + } + + public void processQueuedReferral(Player player) { + PendingReferral pending = pendingReferrals.remove(player.getUniqueId()); + if (pending == null) { + return; + } + + UUID referredUuid = player.getUniqueId(); + UUID referrerUuid = pending.referrerUuid(); + + if (plugin.getConfig().getBoolean("referral.block-self-referrals", true) && referredUuid.equals(referrerUuid)) { + return; + } + + if (plugin.getConfig().getBoolean("referral.count-only-first-join", true) + && plugin.getDatabaseManager().hasAnyReferralRecordForReferred(referredUuid)) { + return; + } + + if (plugin.getDatabaseManager().hasReferralRecord(referrerUuid, referredUuid)) { + return; + } + + String rawIp = getPlayerIp(player); + String transformedIp = rawIp == null ? null : transformIp(rawIp); + + if (plugin.getConfig().getBoolean("referral.block-same-ip", true) && transformedIp != null) { + if (plugin.getConfig().getBoolean("referral.compare-against-referrer-known-ips", true) + && plugin.getDatabaseManager().playerHasIp(referrerUuid, transformedIp)) { + alertBlocked(player.getName(), plugin.getDatabaseManager().getPlayerName(referrerUuid), "same IP as referrer"); + plugin.getDatabaseManager().insertReferral(referrerUuid, referredUuid, player.getName(), pending.joinHost(), transformedIp, false); + return; + } + + if (plugin.getConfig().getBoolean("referral.compare-against-other-referred-ips", true) + && plugin.getDatabaseManager().anyReferralUsesIpForReferrer(referrerUuid, transformedIp)) { + alertBlocked(player.getName(), plugin.getDatabaseManager().getPlayerName(referrerUuid), "reused referral IP"); + plugin.getDatabaseManager().insertReferral(referrerUuid, referredUuid, player.getName(), pending.joinHost(), transformedIp, false); + return; + } + } + + int requiredMinutes = plugin.getConfig().getInt("referral.require-player-to-stay-online-minutes", 5); + if (requiredMinutes <= 0) { + countReferralNow(player, referrerUuid, pending.joinHost(), transformedIp); + return; + } + + Bukkit.getScheduler().runTaskLater(plugin, () -> { + Player online = Bukkit.getPlayer(referredUuid); + if (online == null || !online.isOnline()) { + return; + } + + if (plugin.getDatabaseManager().hasReferralRecord(referrerUuid, referredUuid)) { + return; + } + + countReferralNow(online, referrerUuid, pending.joinHost(), transformedIp); + }, requiredMinutes * 60L * 20L); + } + + private void countReferralNow(Player referredPlayer, UUID referrerUuid, String joinHost, String transformedIp) { + UUID referredUuid = referredPlayer.getUniqueId(); + + if (plugin.getDatabaseManager().hasReferralRecord(referrerUuid, referredUuid)) { + return; + } + + plugin.getDatabaseManager().insertReferral(referrerUuid, referredUuid, referredPlayer.getName(), joinHost, transformedIp, true); + plugin.getDatabaseManager().addReferralCount(referrerUuid, 1); + + String referrerName = plugin.getDatabaseManager().getPlayerName(referrerUuid); + checkMilestones(referrerUuid, referrerName); + + String message = plugin.getMessageManager().get("referral-counted") + .replace("%referrer%", referrerName == null ? "Unknown" : referrerName); + referredPlayer.sendMessage(message); + } + + private void checkMilestones(UUID referrerUuid, String referrerName) { + int count = plugin.getDatabaseManager().getReferralCount(referrerUuid); + ConfigurationSection milestones = plugin.getConfig().getConfigurationSection("rewards.milestones"); + if (milestones == null) { + return; + } + + for (String key : milestones.getKeys(false)) { + int milestone; + try { + milestone = Integer.parseInt(key); + } catch (NumberFormatException e) { + continue; + } + + if (count < milestone) { + continue; + } + + if (plugin.getDatabaseManager().hasClaimedReward(referrerUuid, milestone)) { + continue; + } + + List commands = plugin.getConfig().getStringList("rewards.milestones." + key + ".commands"); + for (String command : commands) { + String parsed = command + .replace("%referrer%", referrerName == null ? "Unknown" : referrerName) + .replace("%count%", String.valueOf(count)) + .replace("%milestone%", String.valueOf(milestone)); + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), parsed); + } + + plugin.getDatabaseManager().setRewardClaimed(referrerUuid, milestone); + + Player referrer = Bukkit.getPlayer(referrerUuid); + if (referrer != null && referrer.isOnline()) { + String rewardMessage = plugin.getConfig().getString("rewards.milestones." + key + ".message", + "&aYou reached " + milestone + " referrals and earned a reward!"); + referrer.sendMessage(plugin.getMessageManager().color( + plugin.getMessages().getString("prefix", "&6[DirtReferrals] ") + rewardMessage + )); + } + } + } + + private void alertBlocked(String referredName, String referrerName, String reason) { + if (!plugin.getConfig().getBoolean("alerts.notify-on-blocked-same-ip", true)) { + return; + } + + String permission = plugin.getConfig().getString("alerts.notify-permission", "dirtreferrals.alerts"); + String message = plugin.getMessages().getString("prefix", "&6[DirtReferrals] ") + + "&cBlocked referral: &e" + referredName + " &7-> &e" + + (referrerName == null ? "Unknown" : referrerName) + " &7(" + reason + ")"; + + String colored = plugin.getMessageManager().color(message); + for (Player online : Bukkit.getOnlinePlayers()) { + if (online.hasPermission(permission)) { + online.sendMessage(colored); + } + } + } + + private String getPlayerIp(Player player) { + if (player.getAddress() == null) { + return null; + } + InetAddress address = player.getAddress().getAddress(); + return address == null ? null : address.getHostAddress(); + } + + private String transformIp(String ip) { + boolean hash = plugin.getConfig().getBoolean("referral.hash-ip-addresses", false); + if (!hash) { + return ip; + } + return Integer.toHexString(ip.hashCode()); + } + + private record PendingReferral(UUID referrerUuid, String joinHost, long createdAt) { + } +} diff --git a/src/main/java/com/dirtbagmc/dirtreferrals/TopEntry.java b/src/main/java/com/dirtbagmc/dirtreferrals/TopEntry.java new file mode 100644 index 0000000..164aa3e --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtreferrals/TopEntry.java @@ -0,0 +1,28 @@ +package com.dirtbagmc.dirtreferrals; + +import java.util.UUID; + +public class TopEntry { + + private final UUID uuid; + private final String name; + private final int count; + + public TopEntry(UUID uuid, String name, int count) { + this.uuid = uuid; + this.name = name; + this.count = count; + } + + public UUID getUuid() { + return uuid; + } + + public String getName() { + return name; + } + + public int getCount() { + return count; + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..6d25950 --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,38 @@ +domain: + base: "dirtbagmc.com" + use-lowercase-playernames: true + +referral: + count-only-first-join: true + require-player-to-stay-online-minutes: 5 + block-self-referrals: true + block-same-ip: true + compare-against-referrer-known-ips: true + compare-against-other-referred-ips: true + store-ip-addresses: true + hash-ip-addresses: false + +rewards: + milestones: + "2": + commands: + - "say %referrer% reached 2 referrals!" + message: "&aYou reached 2 referrals and earned a reward!" + "5": + commands: + - "say %referrer% reached 5 referrals!" + message: "&aYou reached 5 referrals and earned a reward!" + "10": + commands: + - "say %referrer% reached 10 referrals!" + message: "&6You reached 10 referrals and earned a reward!" + +alerts: + notify-on-blocked-same-ip: true + notify-permission: "dirtreferrals.alerts" + +database: + file: "referrals.db" + +admin: + stats-page-size: 10 diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml new file mode 100644 index 0000000..e39541f --- /dev/null +++ b/src/main/resources/messages.yml @@ -0,0 +1,26 @@ +prefix: "&6[DirtReferrals] " + +player-only: "&cPlayers only." +no-permission: "&cYou do not have permission." +reloaded: "&aDirtReferrals reloaded." +unknown-subcommand: "&cUnknown subcommand." +usage-admin: "&eUsage: /dirtreferrals " +usage-stats: "&eUsage: /dirtreferrals stats " +usage-give: "&eUsage: /dirtreferrals give " +usage-take: "&eUsage: /dirtreferrals take " + +referral-link: "&aHere is your referral IP! Have your friends join using it to receive rewards: &e%referral_ip%" +stats-header: "&6Referral stats for &e%player%&6:" +stats-count: "&eCount: &a%count%" +top-header: "&6Top Referrals:" +top-entry: "&e#%position% &f%player% &7- &a%count%" +no-data: "&cNo data found for that player." + +credit-added: "&aAdded &e%amount% &acredit to &e%player%&a." +credit-removed: "&aRemoved &e%amount% &acredit from &e%player%&a." +not-enough-credit: "&cThat player does not have enough referral credit." +invalid-number: "&cInvalid number." +blocked-same-ip: "&cReferral blocked due to same IP." +blocked-self-referral: "&cYou cannot refer yourself." +milestone-reward: "&aYou earned a referral reward for %amount% referrals!" +referral-counted: "&aReferral counted for &e%referrer%&a." diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..d4cbb6b --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,26 @@ +name: DirtReferrals +version: 1.0.0 +main: com.dirtbagmc.dirtreferrals.DirtReferralsPlugin +api-version: '1.21' +author: DirtBagMC +description: Referral tracking plugin with subdomain-based joins and milestone rewards + +commands: + refer: + description: Show your referral address + usage: /refer + aliases: [referrals] + dirtreferrals: + description: DirtReferrals admin commands + usage: /dirtreferrals + +permissions: + dirtreferrals.use: + description: Allows use of referral commands + default: true + dirtreferrals.admin: + description: Allows admin commands + default: op + dirtreferrals.alerts: + description: Receive blocked referral alerts + default: op diff --git a/target/DirtReferrals-1.0.0.jar b/target/DirtReferrals-1.0.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..bc632cf06fb42c86c79ab3f799393cff9accd2e3 GIT binary patch literal 24316 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 O5w8Wr7MkY8xBmmbwuF2D literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtbagmc/dirtreferrals/AdminCommand.class b/target/classes/com/dirtbagmc/dirtreferrals/AdminCommand.class new file mode 100644 index 0000000000000000000000000000000000000000..1556f4c41f4fd769ca1bd2f08c1f86b65720494f GIT binary patch literal 4877 zcmcgwYj_mZ8Gg^qCNsMkAS?+Qq%|Ocgct&dfhJspBuFq^1GIu7lieX1vb(d)&T{cW zfy%`jUa%Lec%c`nV%35XY{lBzVyP`^Z}$GDefp!%)4yIGEPc=HC7TceznbUSnK@^^ z^IhKW{l0T1XaD>9X#n%__Yf4Q8UhB2P%Kco$=qVr$IL`m{j$zYR>Tu1o^2;=Z?1q^ zU9%<#9YGBt10^s7sv}OkK5DyOr`Z*c_)o62(Q;igma3m0joXQ4CmuHwQL0JC(p`2! zU|Rc7i(05`WlqI%e@T;$;R2(inY{8Trc_>nQjF9PHZTfh0+o*2Ro|KJ>9M_f+K_hZ zv-UfzMAUKxhIN~%<(3<_Qz_e_Gv(FoeGzwfuAS&=s#zPtXk4Y?Y6BG*BOsMsGRi?5 z78u!Oc}uKR%Ivb1m9=tG!@NgApR)L?Qy-egduwCA)tTP?TQOj!bD zjQ%2|rIPCn)M1K%&dhW-J5kD#Xl**Ck~6LqbIhm|n{MC+%;1!@IZbj6$Xtf2%$$w} zfufXWdMO>VsLFGaIvN>&m%YW(F`G}G*<;BJ&owYla^A>T$&gLQeEO1^v7$Pf^I`i+ z<+@e_3vshRm`QFiJ+qSm=CfRXnJf=j&=f)&7Hhc0Ks%NQ)DD%?0c%+k^k8|++-|w6 z({^;h_N8W=@J$$u(;4v7o*k=Sy}GT1^japMkzP?oAe~rYU?n<8xxCK)+WM9bn#eY+ zHn0X?Bo(M2SHaCrI>8n)D(g{!4kq|kLhplG*KL`sPD$@|25!Uc%o~*mOuTd?ev8T$ ztd2XFv#O+zM3rQVX`mAk2IyLxjS!TULRHP(rrSXxS&4{MNcVp8gM=yjjb&gXy2uJy zP}wRQz{Xr3Vb$1DP_nTuBB9XHL#L|zXW!Adfdm}RZ82kMYuQGrscjI;GDDjUxFGXK z5Svv_GJ~<@a%NP1jgN4|vvGRqQWp%g#5IU>A04_=}XxW7m*1+V&Gf&HY>`E zMh8?>*+muARztddmn9W(t*Gs>OGjn@`kt%|GcL(jrQ`X0fU*hdIL5FNjz{2}bXRv> zW~ns&eFHD~rc(ooS;s5%HEYAQ;?5RngQkH4d`JHr1>le=_^Uh;}<+WsqHEcE(uk;dUot#@3jwEyvl(fY)uaSSAl? zs_@X#L|Qq0Czy>_vUgrO+QLSY?GWoO-V$rpGORSaInzF{XUl@|JAC-!b=>v*t|B1X z`0Q5#8|aX+<_d^$7TWUL2WgY5Aa1%D0#P@y9ams{P&!kc7D>Ah{?6DLVLxRImzpAsQxv85eRZWJH^rHj5J=y z4>{sGUn|p%AK{IR&f;^_UjnEv)rND(kQV{h5e>Nl3QV|F+&P9z{g?6SuW@$ee zqP4tH&#mBSRYR~gYbE@pUaSqTuh7pRB$XXSQH9=%4W|%QkQeHfLYvBiClSlN3mnTT zEDiM{*{J1ZJ<9s4E3{MCB5>#;zXA=qe0R4%W3VE43Ol9HBKfpO;K#VHF_c&KfHeG2 zxt=jhKWXe?x)9zZpB^jl=U!bLy>P;(LvbNoU+o9QP;gZQil(2$*BkVsC~FL6@u>|z z?JFqPpDc(YGnYrSoQOS}_xU^0=c9c-=f#+YKJ-Z2ettgpfAG`Ke0i{3F9hg?eug#q zI@*gD^WnUFMd9S(`hl+wiV{&`ii5*yiCU&!g~Oyg8v%)aD=A;bqAthPYyo5Vv0KS& z9j-w=kK+H}=-(`jk1-yfqDpW#5yLS_l=2`|iCR&G>&0}`i5dJ-TYza|Ic^Yl@DnY` zdm1;2z5En=koQM1Qykn-TStQkMSPZ1;v4R{+rLx?!-$1MDu za_6uXXZTiUj4IyopEk2?|AP90#9?f&ixwa*sHw;dh*Q8I@zoJ_C2Z ziDCr*jPd2==W(5e-(O&4Aq~2QQVk#Sy@o$YDZY|7`QInL;OxW;v^|t$ATO!m&mW`e uJZ5Rw`WZ^7YuBe(`UynV<}DfypGT<}^$BJVoT69aV}AJy$Spv@-~I>1ffuO& literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtbagmc/dirtreferrals/DatabaseManager.class b/target/classes/com/dirtbagmc/dirtreferrals/DatabaseManager.class new file mode 100644 index 0000000000000000000000000000000000000000..89b9cd58dfb0c03cdc24f8ae863e4bb29f5fcb4d GIT binary patch literal 15018 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 WbyWTv-szycrCs@{j>>UA{{8>0f%we; literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtbagmc/dirtreferrals/MessageManager.class b/target/classes/com/dirtbagmc/dirtreferrals/MessageManager.class new file mode 100644 index 0000000000000000000000000000000000000000..50b9e70353978d2c245e975f0380f1c81d8bb64d GIT binary patch literal 2160 zcmb7GZFAd15Pr^fEJqe`>LxCM5Zp9%{gPCambTQT(7J@S*lCGF%1nn3XZvESktL6G zF7yNcgP#F25Fj7;0sT=7dzNwJu}CN5nbY3h?z8*s?n!?0&+*>??&CWhF~l__49p?P zu=tX{;-<}=uKBd}QnV#Q@~-7r@*YFHSlXXQ3iBFt18Eox<+j^5JC-L~yxVV&Bu^X& z&*QdlHiYlO4CWdA8PZ)L z$NBgS*NdgP>vhf6@bJ)*gytMr-J!>&_Qg_fVL@eF+AC zo42Lwy=M41-14zu5cUy`i!J<(y6YYe2N7imm3+!^@AW`Ln7;_w2k+5pc%;V=HE!EY zOA2bO@z`;MSF^eA3tz(?!{r%HQK@R6{;ZvDN(~Hn;Ue9k7`+{6hSd9;q$J66_=MJ* zwA-drb*`8H04rC1hxRM&Vz@=?0^;ff3G$pL-`ljy1=jc!cW9?h6)K}2UDGkaJ9uk zB1e&B2kZS8~NDg7hWKNtp!Nosu z`3HRNT1uCF@UaiR`gdF|_nAouF=+2_xHUi|@J1tNkFx-^7!bfbr1 z_{2Ol4cl~z#^b{io|gL(WfDzqaQj$Dlf`L!E)uHSuE%Mnahv3 z>zcM_?AYcRcQckJxx-yDRI#f?%VD^k>7@3M%*tAQSjo{ShWmd<#mPTK?+cu&J^9q;1<0_0QM zCR~QFJbsN7As>aAL)nv8lj#B;XNi$!{nw47Ule<-t zDDwl$vxtxt#}U$$7F|DvYfXlHczz6E9Q71{v$P(_;Iak-;cAQ z9uy+rIMPs5ZRnU`u0gEjoQe`R0)B(ev|;9@aL*Wiy4)#hf`RLR8*eTnM5$V7a|JJ@ zu1RtKtis!fKffd&Je}#wt*&Nb&9v=3OL9tyzU9!fxMrK4$2|@7xZG%osU1`eWrnMr zMJeGyT)0jTb_fic^hMR9O|86(=H1w)|07y$(0Pc)Na`h6YT-9%FK87)hW>*j3k)Dk zvpx(VORMof8aX_s73uj_8Cl!G6FO5i|02CGjgMG%;T!|65mm!EMt(-Gnq_}MIJfv3 zvDB~7&M{4U*MG+iD$iF)0q3}-c5=b#ou$Qd+H(gT{V&P#Ai{rRLc{2) zMm}TsiuQU){uzx7&+&CF*drPhO)?Ir5aOhdE*{vp rKo=u)-;keficDb&HTmztx8yN|@9;g1y(E{X&xA#*2+dAl!@=ag{T95w literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtbagmc/dirtreferrals/ReferralCommand.class b/target/classes/com/dirtbagmc/dirtreferrals/ReferralCommand.class new file mode 100644 index 0000000000000000000000000000000000000000..80198f77adc37e0963cad5fe77330b6d701914aa GIT binary patch literal 2018 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 EA7Ck1$p8QV literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtbagmc/dirtreferrals/TopEntry.class b/target/classes/com/dirtbagmc/dirtreferrals/TopEntry.class new file mode 100644 index 0000000000000000000000000000000000000000..6c52de259ddad52118b4ae745d437735a2ff262c GIT binary patch literal 791 zcma)3U2oGs5S&Y#xCu@_+z_BpN@;}>Q{f*_goFx6k)ra@Chr%=gsU7IUF}N!PdtFs z2#E)N06z*bcTS}$W5`f~pJ0Lk)F-%?o)Y!$hXj@Z{HGPw-TvoG({Twe<~ zMWH4XA6WGwS4lV=_TL>FwRI00*d!*Evsl0{G1kFZu2nj>vK6yY_lwq$w+Et?u(+6e^!@NAN" +usage-stats: "&eUsage: /dirtreferrals stats " +usage-give: "&eUsage: /dirtreferrals give " +usage-take: "&eUsage: /dirtreferrals take " + +referral-link: "&aHere is your referral IP! Have your friends join using it to receive rewards: &e%referral_ip%" +stats-header: "&6Referral stats for &e%player%&6:" +stats-count: "&eCount: &a%count%" +top-header: "&6Top Referrals:" +top-entry: "&e#%position% &f%player% &7- &a%count%" +no-data: "&cNo data found for that player." + +credit-added: "&aAdded &e%amount% &acredit to &e%player%&a." +credit-removed: "&aRemoved &e%amount% &acredit from &e%player%&a." +not-enough-credit: "&cThat player does not have enough referral credit." +invalid-number: "&cInvalid number." +blocked-same-ip: "&cReferral blocked due to same IP." +blocked-self-referral: "&cYou cannot refer yourself." +milestone-reward: "&aYou earned a referral reward for %amount% referrals!" +referral-counted: "&aReferral counted for &e%referrer%&a." diff --git a/target/classes/plugin.yml b/target/classes/plugin.yml new file mode 100644 index 0000000..d4cbb6b --- /dev/null +++ b/target/classes/plugin.yml @@ -0,0 +1,26 @@ +name: DirtReferrals +version: 1.0.0 +main: com.dirtbagmc.dirtreferrals.DirtReferralsPlugin +api-version: '1.21' +author: DirtBagMC +description: Referral tracking plugin with subdomain-based joins and milestone rewards + +commands: + refer: + description: Show your referral address + usage: /refer + aliases: [referrals] + dirtreferrals: + description: DirtReferrals admin commands + usage: /dirtreferrals + +permissions: + dirtreferrals.use: + description: Allows use of referral commands + default: true + dirtreferrals.admin: + description: Allows admin commands + default: op + dirtreferrals.alerts: + description: Receive blocked referral alerts + default: op diff --git a/target/maven-archiver/pom.properties b/target/maven-archiver/pom.properties new file mode 100644 index 0000000..4f60892 --- /dev/null +++ b/target/maven-archiver/pom.properties @@ -0,0 +1,5 @@ +#Generated by Maven +#Sat Jun 13 18:22:07 EDT 2026 +artifactId=DirtReferrals +groupId=com.dirtbagmc +version=1.0.0 diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..dd78400 --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1,9 @@ +com/dirtbagmc/dirtreferrals/AdminCommand.class +com/dirtbagmc/dirtreferrals/ReferralManager$PendingReferral.class +com/dirtbagmc/dirtreferrals/PlayerListener.class +com/dirtbagmc/dirtreferrals/DatabaseManager.class +com/dirtbagmc/dirtreferrals/MessageManager.class +com/dirtbagmc/dirtreferrals/ReferralCommand.class +com/dirtbagmc/dirtreferrals/TopEntry.class +com/dirtbagmc/dirtreferrals/ReferralManager.class +com/dirtbagmc/dirtreferrals/DirtReferralsPlugin.class diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..ea3599a --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,8 @@ +/home/bitnix/Desktop/DirtRewards/src/main/java/com/dirtbagmc/dirtreferrals/AdminCommand.java +/home/bitnix/Desktop/DirtRewards/src/main/java/com/dirtbagmc/dirtreferrals/DatabaseManager.java +/home/bitnix/Desktop/DirtRewards/src/main/java/com/dirtbagmc/dirtreferrals/DirtReferralsPlugin.java +/home/bitnix/Desktop/DirtRewards/src/main/java/com/dirtbagmc/dirtreferrals/MessageManager.java +/home/bitnix/Desktop/DirtRewards/src/main/java/com/dirtbagmc/dirtreferrals/PlayerListener.java +/home/bitnix/Desktop/DirtRewards/src/main/java/com/dirtbagmc/dirtreferrals/ReferralCommand.java +/home/bitnix/Desktop/DirtRewards/src/main/java/com/dirtbagmc/dirtreferrals/ReferralManager.java +/home/bitnix/Desktop/DirtRewards/src/main/java/com/dirtbagmc/dirtreferrals/TopEntry.java