Player report system commit, including functionality for long-form MySQL storage of report logs as well as real-time redis based report sessions.

This commit is contained in:
MrTwiggy 2014-11-10 18:44:46 -05:00
parent a3e6613a94
commit 920bdad64c
11 changed files with 714 additions and 2 deletions

View File

@ -0,0 +1,44 @@
package mineplex.core.report;
import java.util.HashSet;
import java.util.Set;
import mineplex.serverdata.Data;
public class Report implements Data
{
private int _reportId;
public int getReportId() { return _reportId; }
private String _serverName;
public String getServerName() { return _serverName; }
private String _playerName;
public String getPlayerName() { return _playerName; }
// Set of account ids of players who contributed to reporting this player
private Set<String> _reporters;
public Set<String> getReporters() { return _reporters; }
public void addReporter(String reporter) { _reporters.add(reporter); }
/**
* Class constructor
* @param reportId
* @param playerName
* @param serverName
*/
public Report(int reportId, String playerName, String serverName)
{
_reportId = reportId;
_playerName = playerName;
_serverName = serverName;
_reporters = new HashSet<String>();
}
@Override
public String getDataId()
{
return String.valueOf(_reportId);
}
}

View File

@ -0,0 +1,318 @@
package mineplex.core.report;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import mineplex.core.account.CoreClient;
import mineplex.core.command.CommandCenter;
import mineplex.core.common.util.UtilPlayer;
import mineplex.core.portal.Portal;
import mineplex.core.report.command.ReportNotification;
import mineplex.serverdata.DataRepository;
import mineplex.serverdata.RedisDataRepository;
import mineplex.serverdata.Region;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.exceptions.JedisConnectionException;
/**
* ReportManager hooks into a synchronized network-wide report system
* with methods for updating/fetching/closing reports in real time.
* @author Ty
*
*/
public class ReportManager {
private static ReportManager instance;
// Holds active/open reports in a synchronized database.
private DataRepository<Report> reportRepository;
private DataRepository<ReportProfile> reportProfiles;
// Stores/logs closed tickets, and various reporter/staff actions.
private ReportRepository reportSqlRepository;
// A mapping of PlayerName(String) to the ReportId(Integer) for all active reports on this server.
private Map<String, Integer> activeReports;
/**
* Private constructor to prevent non-singleton instances.
*/
private ReportManager()
{
this.reportRepository = new RedisDataRepository<Report>(Region.ALL, Report.class, "reports");
this.reportProfiles = new RedisDataRepository<ReportProfile>(Region.ALL, ReportProfile.class, "reportprofiles");
this.activeReports = new HashMap<String, Integer>();
// TODO: Get JavaPlugin instance and locate ConnectionString from config?
this.reportSqlRepository = new ReportRepository(ReportPlugin.getPlugin(), "CONNECTION STRING HERE");
reportSqlRepository.initialize();
}
public void retrieveReportResult(int reportId, Player reportCloser, String reason)
{
// Prompt the report closer with a menu of options to determine the result
// of the report. When confirmation is received, THEN close report.
}
public void closeReport(int reportId, Player reportCloser, String reason)
{
retrieveReportResult(reportId, reportCloser, reason);
}
public void closeReport(int reportId, Player reportCloser, String reason,
ReportResult result)
{
if (isActiveReport(reportId))
{
Report report = getReport(reportId);
reportRepository.removeElement(String.valueOf(reportId)); // Remove report from redis database
removeActiveReport(reportId);
int closerId = getPlayerAccount(reportCloser).GetAccountId();
String playerName = getReport(reportId).getPlayerName();
int playerId = getPlayerAccount(playerName).GetAccountId();
String server = null; // TODO: Get current server name
reportSqlRepository.logReport(reportId, playerId, server, closerId, result, reason);
// Update the reputation/profiles of all reporters on this closing report.
for (String reporterName : report.getReporters())
{
CoreClient reporterAccount = getPlayerAccount(reporterName);
ReportProfile reportProfile = getReportProfile(String.valueOf(reporterAccount.GetAccountId()));
reportProfile.onReportClose(result);
reportProfiles.addElement(reportProfile);
}
if (reportCloser != null)
{
// Notify staff that the report was closed.
sendReportNotification(String.format("[Report %d] %s closed this report. (%s).", reportId,
reportCloser.getName(), result.toDisplayMessage()));
}
}
}
public void handleReport(int reportId, Player reportHandler)
{
if (reportRepository.elementExists(String.valueOf(reportId)))
{
Report report = getReport(reportId);
Portal.transferPlayer(reportHandler.getName(), report.getServerName());
String handlerName = reportHandler.getName();
sendReportNotification(String.format("[Report %d] %s is handling this report.", reportId, handlerName));
// TODO: Send display message to handler when they arrive on the server
// with info about the case/report.
int handlerId = getPlayerAccount(reportHandler).GetAccountId();
reportSqlRepository.logReportHandling(reportId, handlerId); // Log handling into sql database
}
}
public void reportPlayer(Player reporter, Player reportedPlayer, String reason)
{
int reporterId = getPlayerAccount(reporter).GetAccountId();
ReportProfile reportProfile = getReportProfile(String.valueOf(reporterId));
if (reportProfile.canReport())
{
Report report = null;
if (hasActiveReport(reportedPlayer))
{
int reportId = getActiveReport(reportedPlayer.getName());
report = getReport(reportId);
report.addReporter(reporter.getName());
}
else
{
String serverName = null; // TODO: Fetch name of current server
int reportId = generateReportId();
report = new Report(reportId, reportedPlayer.getName(), serverName);
report.addReporter(reporter.getName());
activeReports.put(reportedPlayer.getName().toLowerCase(), report.getReportId());
reportRepository.addElement(report);
}
if (report != null)
{
// [Report 42] [MrTwiggy +7] [Cheater102 - 5 - Speed hacking]
String message = String.format("[Report %d] [%s %d] [%s - %d - %s]", report.getReportId(),
reporter.getName(), reportProfile.getReputation(),
reportedPlayer.getName(), report.getReporters().size(), reason);
sendReportNotification(message);
reportSqlRepository.logReportSending(report.getReportId(), reporterId, reason);
}
}
}
public void onPlayerQuit(Player player)
{
if (hasActiveReport(player))
{
int reportId = getActiveReport(player.getName());
this.closeReport(reportId, null, "Player Quit", ReportResult.UNDETERMINED);
// TODO: Handle 'null' report closer in closeReport metohd for NPEs.
sendReportNotification(String.format("[Report %d] %s has left the game.", reportId, player.getName()));
}
}
public ReportProfile getReportProfile(String playerName)
{
ReportProfile profile = reportProfiles.getElement(playerName);
if (profile == null)
{
profile = new ReportProfile(playerName, getAccountId(playerName));
saveReportProfile(profile);
}
return profile;
}
private void saveReportProfile(ReportProfile profile)
{
reportProfiles.addElement(profile);
}
/**
* @return a uniquely generated report id.
*/
public int generateReportId()
{
JedisPool pool = ((RedisDataRepository<Report>) reportRepository).getJedisPool();
Jedis jedis = pool.getResource();
long uniqueReportId = -1;
try
{
uniqueReportId = jedis.incr("reports.unique-id");
}
catch (JedisConnectionException exception)
{
exception.printStackTrace();
pool.returnBrokenResource(jedis);
jedis = null;
}
finally
{
if (jedis != null)
{
pool.returnResource(jedis);
}
}
return (int) uniqueReportId;
}
public Report getReport(int reportId)
{
return reportRepository.getElement(String.valueOf(reportId));
}
private CoreClient getPlayerAccount(Player player)
{
return getPlayerAccount(player.getName());
}
private CoreClient getPlayerAccount(String playerName)
{
return CommandCenter.Instance.GetClientManager().Get(playerName);
}
private int getAccountId(String playerName)
{
return getPlayerAccount(playerName).GetAccountId();
}
/**
* @param player - the player whose report notification settings are to be checked
* @return true, if the player should receive report notifications, false otherwise.
*/
public boolean hasReportNotifications(Player player)
{
// If player is not staff, return false.
// If player is staff but has report notifications pref disabled, return false;
// Else return true.
return false;
}
/**
* Send a network-wide {@link ReportNotification} to all online staff.
* @param message - the report notification message to send.
*/
public void sendReportNotification(String message)
{
ReportNotification reportNotification = new ReportNotification(message);
reportNotification.publish();
}
/**
* @param playerName - the name of the player whose active report id is being fetched
* @return the report id for the active report corresponding with playerName, if one
* currently exists, -1 otherwise.
*/
public int getActiveReport(String playerName)
{
if (activeReports.containsKey(playerName.toLowerCase()))
{
return activeReports.get(playerName.toLowerCase());
}
return -1;
}
public boolean hasActiveReport(Player player)
{
return getActiveReport(player.getName()) != -1;
}
public boolean isActiveReport(int reportId)
{
for (Entry<String, Integer> activeReport : activeReports.entrySet())
{
if (activeReport.getValue() == reportId)
{
return true;
}
}
return false;
}
public boolean removeActiveReport(int reportId)
{
for (Entry<String, Integer> activeReport : activeReports.entrySet())
{
if (activeReport.getValue() == reportId)
{
activeReports.remove(activeReport.getKey());
return true;
}
}
return false;
}
/**
* @return the singleton instance of {@link ReportManager}.
*/
public static ReportManager getInstance()
{
if (instance == null)
{
instance = new ReportManager();
}
return instance;
}
}

View File

@ -0,0 +1,32 @@
package mineplex.core.report;
import mineplex.core.MiniPlugin;
import mineplex.core.report.command.ReportCloseCommand;
import mineplex.core.report.command.ReportCommand;
import mineplex.core.report.command.ReportDebugCommand;
import mineplex.core.report.command.ReportHandleCommand;
import org.bukkit.plugin.java.JavaPlugin;
public class ReportPlugin extends MiniPlugin
{
private static JavaPlugin instance;
public static JavaPlugin getPlugin() { return instance; }
public ReportPlugin(JavaPlugin plugin, String serverName)
{
super("ReportPlugin", plugin);
instance = plugin;
}
@Override
public void AddCommands()
{
AddCommand(new ReportCommand(this));
AddCommand(new ReportHandleCommand(this));
AddCommand(new ReportCloseCommand(this));
AddCommand(new ReportDebugCommand(this));
}
}

View File

@ -0,0 +1,61 @@
package mineplex.core.report;
import mineplex.serverdata.Data;
public class ReportProfile implements Data
{
private String _playerName;
private int _playerId;
private int _totalReports;
private int _successfulReports;
private int _reputation;
public int getReputation() { return _reputation; }
private boolean _banned;
public ReportProfile(String playerName, int playerId)
{
_playerName = playerName;
_playerId = playerId;
_totalReports = 0;
_successfulReports = 0;
_reputation = 0;
_banned = false;
}
@Override
public String getDataId()
{
return String.valueOf(_playerId);
}
public boolean canReport()
{
return !_banned;
}
/**
* Called when a report made by this player is closed.
* @param result - the result of the closed report.
*/
public void onReportClose(ReportResult result)
{
_totalReports++;
if (result == ReportResult.MUTED || result == ReportResult.BANNED)
{
_successfulReports++;
_reputation++;
}
else if (result == ReportResult.ABUSE)
{
_reputation = -1;
_banned = true;
}
}
}

View File

@ -0,0 +1,78 @@
package mineplex.core.report;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.UUID;
import java.util.Map.Entry;
import mineplex.core.common.util.NautHashMap;
import mineplex.core.database.RepositoryBase;
import mineplex.core.database.ResultSetCallable;
import mineplex.core.database.column.ColumnInt;
import mineplex.core.database.column.ColumnVarChar;
import mineplex.core.preferences.UserPreferences;
import org.bukkit.plugin.java.JavaPlugin;
public class ReportRepository extends RepositoryBase
{
/*
* *ReportTicket
id, date, accountId reported player, server, accountId of staff who closed, result, reason
ReportSenders
id, date, reportId, accountId of Reporter, Reason for report
ReportHandlers
id, date, reportId, accountId of Staff
This will be used to determine if staff are handling
*/
private static String CREATE_TICKET_TABLE = "CREATE TABLE IF NOT EXISTS reportTickets (reportId INT NOT NULL, date LONG, eventDate LONG, playerId INT NOT NULL, server VARCHAR(50), closerId INT NOT NULL, result VARCHAR(25), reason VARCHAR(100), PRIMARY KEY (reportId), INDEX playerIdIndex (playerId), INDEX closerIdIndex (closerId));";
private static String CREATE_HANDLER_TABLE = "CREATE TABLE IF NOT EXISTS reportHandlers (id INT NOT NULL AUTO_INCREMENT, reportId INT NOT NULL, eventDate LONG, handlerId INT NOT NULL, PRIMARY KEY (id), INDEX handlerIdIndex (handlerId) );";
private static String CREATE_REPORTERS_TABLE = "CREATE TABLE IF NOT EXISTS reportSenders (id INT NOT NULL AUTO_INCREMENT, reportId INT NOT NULL, eventDate LONG, reporterId INT NOT NULL, reason VARCHAR(100), PRIMARY KEY (id), INDEX reporterIdIndex (reporterId));";
private static String INSERT_TICKET = "INSERT INTO reportTickets (reportId, eventDate, playerId, server, closerId, result, reason) VALUES (?, now(), ?, ?, ?, ?, ?);";
private static String INSERT_HANDLER = "INSERT INTO reportHandlers (eventDate, reportId, handlerId) VALUES(now(), ?, ?);";
private static String INSERT_SENDER = "INSERT INTO reportSenders (eventDate, reportId, reporterId, reason) VALUES(now(), ?, ?, ?);";
public ReportRepository(JavaPlugin plugin, String connectionString)
{
super(plugin, connectionString, "root", "tAbechAk3wR7tuTh"); // TODO: Config file for host/pass?
}
@Override
protected void initialize()
{
executeUpdate(CREATE_TICKET_TABLE);
executeUpdate(CREATE_HANDLER_TABLE);
executeUpdate(CREATE_REPORTERS_TABLE);
}
@Override
protected void update()
{
}
public void logReportHandling(int reportId, int handlerId)
{
executeUpdate(INSERT_HANDLER, new ColumnInt("reportId", reportId), new ColumnInt("handlerId", handlerId));
}
public void logReportSending(int reportId, int reporterId, String reason)
{
executeUpdate(INSERT_SENDER, new ColumnInt("reportId", reportId), new ColumnInt("reporterId", reporterId),
new ColumnVarChar("reason", 100, reason));
}
public void logReport(int reportId, int playerId, String server, int closerId, ReportResult result, String reason)
{
executeUpdate(INSERT_TICKET, new ColumnInt("reportId", reportId), new ColumnInt("playerId", playerId),
new ColumnVarChar("server", 50, server), new ColumnInt("closerId", closerId),
new ColumnVarChar("result", 25, result.toString()), new ColumnVarChar("reason", 100, reason));
}
}

View File

@ -0,0 +1,25 @@
package mineplex.core.report;
import org.bukkit.ChatColor;
public enum ReportResult
{
UNDETERMINED(ChatColor.WHITE, "Could not determine"),
MUTED(ChatColor.YELLOW, "Muted"),
BANNED(ChatColor.RED, "Banned"),
ABUSE(ChatColor.DARK_RED, "Abuse of report system");
private ChatColor color;
private String displayMessage;
private ReportResult(ChatColor color, String displayMessage)
{
this.color = color;
this.displayMessage = displayMessage;
}
public String toDisplayMessage()
{
return color + displayMessage;
}
}

View File

@ -0,0 +1,39 @@
package mineplex.core.report.command;
import mineplex.core.command.CommandBase;
import mineplex.core.common.Rank;
import mineplex.core.common.util.C;
import mineplex.core.common.util.Callback;
import mineplex.core.common.util.F;
import mineplex.core.common.util.UtilPlayer;
import mineplex.core.portal.Portal;
import mineplex.core.report.ReportManager;
import mineplex.core.report.ReportPlugin;
import org.bukkit.entity.Player;
public class ReportCloseCommand extends CommandBase<ReportPlugin>
{
public ReportCloseCommand(ReportPlugin plugin)
{
super(plugin, Rank.ADMIN, "reportclose", "rc");
}
@Override
public void Execute(final Player player, final String[] args)
{
if(args == null || args.length < 2)
{
UtilPlayer.message(player, F.main(Plugin.GetName(), C.cRed + "Your arguments are inappropriate for this command!"));
return;
}
else
{
int reportId = Integer.parseInt(args[0]);
String reason = F.combine(args, 1, null, false);
ReportManager.getInstance().closeReport(reportId, player, reason);
}
}
}

View File

@ -0,0 +1,48 @@
package mineplex.core.report.command;
import mineplex.core.command.CommandBase;
import mineplex.core.common.Rank;
import mineplex.core.common.util.C;
import mineplex.core.common.util.Callback;
import mineplex.core.common.util.F;
import mineplex.core.common.util.UtilPlayer;
import mineplex.core.portal.Portal;
import mineplex.core.report.ReportManager;
import mineplex.core.report.ReportPlugin;
import org.bukkit.entity.Player;
public class ReportCommand extends CommandBase<ReportPlugin>
{
public ReportCommand(ReportPlugin plugin)
{
super(plugin, Rank.ALL, "report");
}
@Override
public void Execute(final Player player, final String[] args)
{
if(args == null || args.length < 2)
{
UtilPlayer.message(player, F.main(Plugin.GetName(), C.cRed + "Your arguments are inappropriate for this command!"));
return;
}
else
{
String playerName = args[0];
Player reportedPlayer = UtilPlayer.searchOnline(player, playerName, false);
String reason = F.combine(args, 1, null, false);
if (reportedPlayer != null)
{
ReportManager.getInstance().reportPlayer(player, reportedPlayer, reason);
}
else
{
UtilPlayer.message(player, F.main(Plugin.GetName(), C.cRed + "Unable to find player '"
+ playerName + "'!"));
}
}
}
}

View File

@ -0,0 +1,38 @@
package mineplex.core.report.command;
import mineplex.core.command.CommandBase;
import mineplex.core.common.Rank;
import mineplex.core.common.util.C;
import mineplex.core.common.util.Callback;
import mineplex.core.common.util.F;
import mineplex.core.common.util.UtilPlayer;
import mineplex.core.portal.Portal;
import mineplex.core.report.ReportManager;
import mineplex.core.report.ReportPlugin;
import org.bukkit.entity.Player;
public class ReportHandleCommand extends CommandBase<ReportPlugin>
{
public ReportHandleCommand(ReportPlugin plugin)
{
super(plugin, Rank.ADMIN, "reporthandle", "rh");
}
@Override
public void Execute(final Player player, final String[] args)
{
if(args == null || args.length < 1)
{
UtilPlayer.message(player, F.main(Plugin.GetName(), C.cRed + "Your arguments are inappropriate for this command!"));
return;
}
else
{
int reportId = Integer.parseInt(args[0]);
ReportManager.getInstance().handleReport(reportId, player);
}
}
}

View File

@ -0,0 +1,31 @@
package mineplex.core.report.command;
import org.bukkit.entity.Player;
import mineplex.core.common.util.UtilServer;
import mineplex.core.report.ReportManager;
import mineplex.serverdata.ServerCommand;
public class ReportNotification extends ServerCommand
{
// TODO: Encode in JSON-interactive chat message
private String notification;
public ReportNotification(String notification)
{
super(); // Send to all servers
}
public void run()
{
// Message all players that can receive report notifications.
for (Player player : UtilServer.getPlayers())
{
if (ReportManager.getInstance().hasReportNotifications(player))
{
player.sendMessage(notification);
}
}
}
}

View File

@ -1,7 +1,5 @@
package mineplex.serverdata;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;
public abstract class ServerCommand
{