This commit is contained in:
2026-06-13 18:45:07 -04:00
commit b6cb260635
29 changed files with 1204 additions and 0 deletions
View File
+52
View File
@@ -0,0 +1,52 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.dirtbagmc</groupId>
<artifactId>DirtReferrals</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>DirtReferrals</name>
<description>Referral tracking plugin for Paper</description>
<properties>
<java.version>21</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<repositories>
<repository>
<id>papermc-repo</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.21.1-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.46.1.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>21</release>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -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<TopEntry> 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;
}
}
}
}
@@ -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<TopEntry> getTopReferrers(int limit) {
List<TopEntry> 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;
}
}
@@ -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;
}
}
@@ -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));
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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<UUID, PendingReferral> 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<String> 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) {
}
}
@@ -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;
}
}
+38
View File
@@ -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
+26
View File
@@ -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 <reload|stats|top|give|take>"
usage-stats: "&eUsage: /dirtreferrals stats <player>"
usage-give: "&eUsage: /dirtreferrals give <player> <amount>"
usage-take: "&eUsage: /dirtreferrals take <player> <amount>"
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."
+26
View File
@@ -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 <reload|stats|top|give|take>
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
Binary file not shown.
+38
View File
@@ -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
+26
View File
@@ -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 <reload|stats|top|give|take>"
usage-stats: "&eUsage: /dirtreferrals stats <player>"
usage-give: "&eUsage: /dirtreferrals give <player> <amount>"
usage-take: "&eUsage: /dirtreferrals take <player> <amount>"
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."
+26
View File
@@ -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 <reload|stats|top|give|take>
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
+5
View File
@@ -0,0 +1,5 @@
#Generated by Maven
#Sat Jun 13 18:22:07 EDT 2026
artifactId=DirtReferrals
groupId=com.dirtbagmc
version=1.0.0
@@ -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
@@ -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