Massive changes made to how the report feature functions

Re-enabled report feature (merge from develop disabled it).
Moved reports from Redis to MySQL.
Made everything asynchronous.
Many many other miscellaneous changes.

Bugs will now be worked out and then this should be ready for QA testing
This commit is contained in:
Keir Nellyer 2016-05-29 00:25:26 +01:00
parent ad486a7e22
commit 20038f9e64
23 changed files with 1060 additions and 660 deletions

View File

@ -1,12 +1,15 @@
package mineplex.core.chatsnap.publishing;
import java.lang.reflect.Type;
import java.util.Optional;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import mineplex.core.report.Report;
import mineplex.core.report.ReportMessage;
/**
* Handles serialization of {@link Report} instances.
@ -16,17 +19,39 @@ public class ReportSerializer implements JsonSerializer<Report>
@Override
public JsonElement serialize(Report report, Type type, JsonSerializationContext jsonSerializationContext)
{
// todo update report site for new json format
Optional<Integer> reportIdOptional = report.getReportId();
Optional<Integer> handlerIdOptional = report.getHandlerId();
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("id", report.getReportId());
jsonObject.addProperty("serverName", report.getServerName());
if (report.getHandler() != null)
if (reportIdOptional.isPresent())
{
jsonObject.addProperty("handler", report.getHandler().toString());
jsonObject.addProperty("id", reportIdOptional.get());
}
else
{
throw new IllegalStateException("Report has no id assigned to it.");
}
jsonObject.addProperty("suspect", report.getSuspect().toString());
jsonObject.add("reporters", jsonSerializationContext.serialize(report.getReportReasons()));
jsonObject.addProperty("suspect", report.getSuspectId());
if (handlerIdOptional.isPresent())
{
jsonObject.addProperty("handler", handlerIdOptional.get());
}
JsonArray messagesArray = new JsonArray();
for (ReportMessage reportMessage : report.getMessages().values())
{
JsonObject reportMessageObject = new JsonObject();
reportMessageObject.addProperty("reporter", reportMessage.getReporterId());
reportMessageObject.addProperty("message", reportMessage.getMessage());
reportMessageObject.addProperty("server", reportMessage.getServer());
reportMessageObject.addProperty("time", reportMessage.getTimeCreated().getTime());
messagesArray.add(reportMessageObject);
}
jsonObject.add("messages", messagesArray);
return jsonObject;
}
}

View File

@ -2,74 +2,85 @@ package mineplex.core.report;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import mineplex.serverdata.data.Data;
import org.apache.commons.lang3.RandomStringUtils;
/**
* Contains data about a report.
* Active/open reports have their instances are stored in Redis for cross-server access.
*/
public class Report implements Data
public class Report
{
private static final int TIMEOUT_MINS = 15;
private int _reportId;
public int getReportId() { return _reportId; }
private String _serverName;
public String getServerName() { return _serverName; }
private UUID _suspect;
public UUID getSuspect() { return _suspect; }
// Set of player names and the reason they reported this player
private Map<UUID, String> _reportReasons;
public Map<UUID, String> getReportReasons() { return _reportReasons; }
public Set<UUID> getReporters() { return _reportReasons.keySet(); }
public void addReporter(UUID reporter, String reason) { _reportReasons.put(reporter, reason); }
private UUID _handler = null;
public void setHandler(UUID handler) { _handler = handler; }
public UUID getHandler() { return _handler; }
private ReportCategory _category;
public ReportCategory getCategory() { return _category; }
private long _lastActivity;
public long getLastActivity() { return _lastActivity; }
protected Integer _reportId;
private final int _suspectId;
private final ReportCategory _category;
// set of player account ids and the reason they reported this player
private final Map<Integer, ReportMessage> _reportMessages = new HashMap<>();
private Integer _handlerId = null;
private ReportResult _reportResult = null;
private String _token = null;
public Report(int reportId, UUID suspect, String serverName, ReportCategory category)
public Report(int suspectId, ReportCategory category)
{
this(null, suspectId, category);
}
protected Report(Integer reportId, int suspectId, ReportCategory category)
{
_reportId = reportId;
_suspect = suspect;
_serverName = serverName;
_reportReasons = new HashMap<>();
_suspectId = suspectId;
_category = category;
updateLastActivity();
}
/**
* Checks if a report is still active.
* This is determined by checking if the last activity was within the last 15 minutes.
*
* @return true if active, false if expired
*/
public boolean isActive()
public Optional<Integer> getReportId()
{
return _lastActivity + TimeUnit.MINUTES.toMillis(TIMEOUT_MINS) >= System.currentTimeMillis();
return Optional.ofNullable(_reportId);
}
public void updateLastActivity()
public int getSuspectId()
{
_lastActivity = System.currentTimeMillis();
return _suspectId;
}
public Map<Integer, ReportMessage> getMessages()
{
return _reportMessages;
}
public void addReportReason(ReportMessage reportMessage)
{
_reportMessages.put(reportMessage.getReporterId(), reportMessage);
}
public Set<Integer> getReporterIds()
{
return _reportMessages.keySet();
}
public Optional<Integer> getHandlerId()
{
return Optional.ofNullable(_handlerId);
}
public void setHandlerId(Integer handlerId)
{
_handlerId = handlerId;
}
public ReportCategory getCategory()
{
return _category;
}
public Optional<ReportResult> getReportResult()
{
return Optional.ofNullable(_reportResult);
}
public void setReportResult(ReportResult reportResult)
{
_reportResult = reportResult;
}
public boolean hasToken()
@ -94,9 +105,18 @@ public class Report implements Data
return _reportId + "-" + _token;
}
@Override
public String getDataId()
public ReportMessage getLatestMessage()
{
return String.valueOf(_reportId);
ReportMessage latest = null;
for (ReportMessage reportMessage : _reportMessages.values())
{
if (latest == null || reportMessage.getTimeCreated().after(latest.getTimeCreated()))
{
latest = reportMessage;
}
}
return latest;
}
}

View File

@ -22,22 +22,40 @@ public enum ReportCategory
*/
CHAT_ABUSE(2);
private final int _databaseId;
private final int _id;
private final String _humanName;
ReportCategory(int databaseId)
ReportCategory(int id)
{
_databaseId = databaseId;
_id = id;
_humanName = StringUtils.capitalize(name().toLowerCase().replace("_", " "));
}
public int getDatabaseId()
/**
* Gets the id, mainly used for database
*
* @return the id
*/
public int getId()
{
return _databaseId;
return _id;
}
public String getHumanName()
{
return _humanName;
}
public static ReportCategory getById(int databaseId)
{
for (ReportCategory reportCategory : values())
{
if (reportCategory.getId() == databaseId)
{
return reportCategory;
}
}
return null;
}
}

View File

@ -1,12 +1,20 @@
package mineplex.core.report;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.function.Function;
import java.util.function.IntUnaryOperator;
import java.util.function.ToIntFunction;
import java.util.stream.Collectors;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
@ -16,8 +24,8 @@ import mineplex.core.chatsnap.SnapshotManager;
import mineplex.core.chatsnap.publishing.SnapshotPublisher;
import mineplex.core.command.CommandCenter;
import mineplex.core.common.Rank;
import mineplex.core.common.jsonchat.ClickEvent;
import mineplex.core.common.jsonchat.JsonMessage;
import mineplex.core.common.util.BukkitFuture;
import mineplex.core.common.util.C;
import mineplex.core.common.util.F;
import mineplex.core.portal.Portal;
@ -26,227 +34,222 @@ import mineplex.core.report.command.ReportHandlerNotification;
import mineplex.core.report.command.ReportNotificationCallback;
import mineplex.core.report.command.ReportNotification;
import mineplex.core.report.task.ReportHandlerMessageTask;
import mineplex.core.stats.PlayerStats;
import mineplex.core.stats.StatsManager;
import mineplex.serverdata.Region;
import mineplex.serverdata.Utility;
import mineplex.serverdata.commands.ServerCommandManager;
import mineplex.serverdata.data.DataRepository;
import mineplex.serverdata.redis.RedisDataRepository;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.plugin.java.JavaPlugin;
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
* ReportManager hooks into a synchronized network-wide report system
* with methods for updating/fetching/closing reports in real time.
*/
public class ReportManager
{
private static final String NAME = "Report";
// statistic constants
private static final String STAT_TOTAL_COUNT = "Global.TotalReportsCount";
private static final int ABUSE_BAN_THRESHOLD = 1;
private JavaPlugin _javaPlugin;
private PreferencesManager _preferencesManager;
private StatsManager _statsManager;
private SnapshotManager _snapshotManager;
private CoreClientManager _coreClientManager;
private CoreClientManager _clientManager;
private String _serverName;
// Holds active/open reports in a synchronized database.
private DataRepository<Report> _reportRepository;
private ReportRepository _reportRepository;
private ReportProfileRepository _reportProfileRepository;
// 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;
public ReportManager(JavaPlugin javaPlugin, PreferencesManager preferencesManager, StatsManager statsManager,
SnapshotManager snapshotManager, CoreClientManager coreClientManager, String serverName)
public ReportManager(JavaPlugin javaPlugin, PreferencesManager preferencesManager, SnapshotManager snapshotManager,
CoreClientManager clientManager, String serverName)
{
_javaPlugin = javaPlugin;
_preferencesManager = preferencesManager;
_statsManager = statsManager;
_snapshotManager = snapshotManager;
_coreClientManager = coreClientManager;
_clientManager = clientManager;
_serverName = serverName;
_reportRepository = new RedisDataRepository<>(Region.ALL, Report.class, "reports");
_activeReports = new HashMap<>();
_reportSqlRepository = new ReportRepository(javaPlugin);
_reportSqlRepository.initialize();
_reportRepository = new ReportRepository(javaPlugin);
_reportRepository.initialize();
_reportProfileRepository = new ReportProfileRepository(javaPlugin);
_reportProfileRepository.initialize();
ReportNotificationCallback callback = new ReportNotificationCallback(this);
ServerCommandManager.getInstance().registerCommandType("ReportNotification", ReportNotification.class, callback);
ServerCommandManager.getInstance().registerCommandType("ReportHandlerNotification", ReportHandlerNotification.class, callback);
}
public void closeReport(int reportId, Player reportCloser, String reason,
ReportResult result)
public ReportRepository getReportRepository()
{
Report report = getReport(reportId);
return _reportRepository;
}
if (report != null)
{
removeReport(reportId);
public void closeReport(int reportId, Player reportCloser, ReportResult reportResult)
{
CompletableFuture<Report> reportFuture = _reportRepository.getReport(reportId);
int closerId = reportCloser != null ? _coreClientManager.Get(reportCloser).getAccountId() : -1;
String suspectName = Bukkit.getOfflinePlayer(report.getSuspect()).getName();
int playerId = _coreClientManager.Get(suspectName).getAccountId();
_reportSqlRepository.logReport(reportId, playerId, _serverName, closerId, result, reason);
reportFuture.thenAcceptBoth(
reportFuture.thenCompose(report -> _reportRepository.getAccountUUID(report.getSuspectId())), (report, suspectUUID) ->
{
if (report != null)
{
OfflinePlayer suspect = Bukkit.getOfflinePlayer(suspectUUID);
String suspectName = suspect.getName();
// Update the reputation/profiles of all reporters on this closing report.
for (UUID reporterUUID : report.getReporters())
{
String reporterName = Bukkit.getOfflinePlayer(reporterUUID).getName();
incrementStat(reporterName, result);
}
int closerId = reportCloser != null ? _clientManager.Get(reportCloser).getAccountId() : -1;
if (reportCloser != null)
{
// Notify staff that the report was closed.
sendStaffNotification(
F.main(getReportPrefix(reportId), String.format("%s closed the report for: %s (%s).",
reportCloser.getName(), reason, result.toDisplayMessage() + C.mBody)));
report.setHandlerId(closerId);
report.setReportResult(reportResult);
_reportRepository.updateReport(report);
CommandCenter.Instance.onPlayerCommandPreprocess(
new PlayerCommandPreprocessEvent(reportCloser, "/punish " + suspectName + " " + reason));
}
if (reportCloser != null)
{
String reason = reportResult.getReason();
if (report.getCategory() == ReportCategory.CHAT_ABUSE) // only chat abuse reports have chat logs published
{
_snapshotManager.getSnapshotPublisher().unpublishChatLog(report.getToken());
}
}
// Notify staff that the report was closed.
sendStaffNotification(
F.main(getReportPrefix(reportId), String.format("%s closed the report for: %s (%s).",
reportCloser.getName(), reason, reportResult.getResultType().toDisplayMessage() + C.mBody)));
// todo new punish gui
CommandCenter.Instance.onPlayerCommandPreprocess(
new PlayerCommandPreprocessEvent(reportCloser, "/punish " + suspectName + " " + reason));
}
if (report.getCategory() == ReportCategory.CHAT_ABUSE) // only chat abuse reports have chat logs published
{
_snapshotManager.getSnapshotPublisher().unpublishChatLog(report.getToken());
}
}
}
);
}
public void handleReport(int reportId, Player reportHandler)
{
Report report = getReport(reportId);
if (report != null)
{
if (report.getHandler() != null) {
reportHandler.sendMessage(F.main(getReportPrefix(reportId), String.format("%s is already handling this report.", report.getHandler())));
} else {
String handlerName = reportHandler.getName();
report.setHandler(reportHandler.getUniqueId());
publishChatSnap(report, false); // so handler is displayed on the web side
saveReport(report);
sendStaffNotification(F.main(getReportPrefix(reportId), String.format("%s is handling this report.", handlerName)));
Portal.transferPlayer(reportHandler.getName(), report.getServerName());
// Show user details of the report every x seconds
new ReportHandlerMessageTask(this, report).runTaskTimer(_javaPlugin, 20L * 10, 20L * 10);
}
}
}
public boolean canReport(Player player)
{
PlayerStats playerStats = _statsManager.Get(player.getName());
long abusiveReportsCount = playerStats.getStat(ReportResult.ABUSIVE.getStatName());
return abusiveReportsCount < ABUSE_BAN_THRESHOLD;
}
private void incrementTotalStat(String reporter)
{
int accountId = _coreClientManager.Get(reporter).getAccountId();
_statsManager.incrementStat(accountId, STAT_TOTAL_COUNT, 1);
}
private void incrementStat(String reporter, ReportResult reportResult)
{
String statName = reportResult.getStatName();
if (statName != null)
{
int accountId = _coreClientManager.Get(reporter).getAccountId();
_statsManager.incrementStat(accountId, statName, 1);
}
}
public Report reportPlayer(Player reporter, Player reportedPlayer, ReportCategory category, String reason)
{
if (canReport(reportedPlayer))
{
Report report = getActiveReport(reportedPlayer.getName());
if (report != null && report.getCategory() == category)
{
report.addReporter(reporter.getUniqueId(), reason);
}
else
{
report = new Report(generateReportId(), reportedPlayer.getUniqueId(), _serverName, category);
report.addReporter(reporter.getUniqueId(), reason);
_activeReports.put(reportedPlayer.getName().toLowerCase(), report.getReportId());
}
incrementTotalStat(reporter.getName());
// only start notifying staff when
if (report.getReporters().size() >= category.getNotifyThreshold())
{
if (report.getCategory() == ReportCategory.CHAT_ABUSE)
_reportRepository.getReport(reportId).thenCompose(BukkitFuture.accept(report ->
{
publishChatSnap(report, true);
}
if (report != null)
{
Optional<Integer> handlerIdOptional = report.getHandlerId();
int reportId = report.getReportId();
String prefix = getReportPrefix(reportId);
String suspectName = Bukkit.getOfflinePlayer(report.getSuspect()).getName();
if (handlerIdOptional.isPresent())
{
_reportRepository.getAccountName(handlerIdOptional.get())
.thenAccept(name ->
reportHandler.sendMessage(
F.main(
getReportPrefix(reportId),
String.format("%s is already handling this report.", name)
)
)
);
}
else
{
String handlerName = reportHandler.getName();
int handlerId = _clientManager.Get(reportHandler).getAccountId();
report.setHandlerId(handlerId);
// Report #2 > iKeirNez - Flying around in arcade game (Hacking)
// Report #2 > Reported by Chiss.
// Report #2 > 5 total reporter(s).
JsonMessage message = new JsonMessage(F.main(prefix, String.format("%s - %s (%s)",
C.cGoldB + suspectName + C.mBody,
reason, C.cGoldB + report.getCategory().getHumanName() + C.mBody))
+ "\n"
+ F.main(prefix, String.format("Reported by %s.", reporter.getName()))
+ "\n"
+ F.main(prefix, String.format("%d total reporter(s).", report.getReporters().size())));
if (report.getCategory() == ReportCategory.CHAT_ABUSE)
{
publishChatSnap(report, true);
}
if (report.getHandler() == null)
_reportRepository.updateReport(report);
sendStaffNotification(F.main(getReportPrefix(reportId), String.format("%s is handling this report.", handlerName)));
Portal.transferPlayer(reportHandler.getName(), report.getLatestMessage().getServer());
// Show user details of the report every x seconds
new ReportHandlerMessageTask(this, report).runTaskTimerAsynchronously(_javaPlugin, 20L * 10, 20L * 10);
}
}
})
);
}
public CompletableFuture<Boolean> canReport(Player player)
{
int accountId = _clientManager.Get(player).getAccountId();
return _reportRepository.getResultCount(accountId, ReportResultType.ABUSIVE)
.thenApply(abusiveReportsCount -> abusiveReportsCount < ABUSE_BAN_THRESHOLD);
}
public CompletableFuture<Report> reportPlayer(Player reporter, Player reportedPlayer, ReportCategory category, String reason)
{
return CompletableFuture.supplyAsync(() -> {
if (canReport(reporter).join())
{
int reporterId = _clientManager.Get(reporter).getAccountId();
int suspectId = _clientManager.Get(reportedPlayer).getAccountId();
Report report = _reportRepository.getOngoingReport(suspectId, category)
.thenCompose(reportId -> reportId != -1 ? _reportRepository.getReport(reportId) : null)
.join();
if (report != null)
{
// this needs to be 'equals' otherwise we get errors when attempting to send this (due to incomplete JSON)
message = message.extra("\n" + F.main(prefix, "Click to handle this ticket."))
.click(ClickEvent.RUN_COMMAND, "/reporthandle " + reportId);
sendStaffNotification(message);
report.addReportReason(new ReportMessage(reporterId, reason, _serverName));
}
else
{
sendHandlerNotification(report, message);
report = new Report(suspectId, category);
report.addReportReason(new ReportMessage(reporterId, reason, _serverName));
}
// todo store token for chat site
_reportRepository.updateReport(report).join(); // join (block) so that report is assigned an id
return report;
}
// save later so that token is saved (if created)
saveReport(report);
return null;
});
}
return report;
}
public CompletableFuture<Boolean> isActiveReport(int reportId)
{
return getReportRepository().getReport(reportId).thenCompose(this::isActiveReport);
}
return null;
public CompletableFuture<Boolean> isActiveReport(Report report)
{
return calculatePriority(report).thenApply(priority -> priority > 0);
}
public CompletableFuture<Integer> calculatePriority(Report report)
{
// todo take into account age of report
return sequence(_reportProfileRepository.getProfiles(report.getReporterIds())).thenApply(reportProfiles -> reportProfiles.stream()
.mapToInt(profile -> profile.getReputation(report.getCategory()))
.map(operand -> operand * 100)
.sum());
}
// todo move to utility class
private static <T> CompletableFuture<List<T>> sequence(List<CompletableFuture<T>> futures)
{
CompletableFuture<Void> futuresCompletedFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
return futuresCompletedFuture.thenApply(v ->
futures.stream().map(CompletableFuture::join).collect(Collectors.toList()));
}
public void publishChatSnap(Report report, boolean updateChat)
{
// todo is this the best way to store Future's?
List<CompletableFuture> futureList = new ArrayList<>();
SnapshotPublisher publisher = _snapshotManager.getSnapshotPublisher();
Gson gson = SnapshotPublisher.getGson();
Set<UUID> uuids = getUUIDs(report);
Set<UUID> uniqueIds = new HashSet<>();
CompletableFuture<Collection<UUID>> reportIdsFuture = getUUIDs(report);
reportIdsFuture.thenAccept(uniqueIds::addAll);
futureList.add(reportIdsFuture);
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("timezone", SnapshotPublisher.getZoneId().getId());
@ -255,98 +258,68 @@ public class ReportManager
if (updateChat)
{
Set<Snapshot> snapshots = _snapshotManager.getSnapshots(report.getSuspect());
uuids.addAll(publisher.getUUIDs(snapshots));
jsonObject.add("snapshots", gson.toJsonTree(snapshots));
CompletableFuture<UUID> suspectIdFuture = _reportRepository.getAccountUUID(report.getSuspectId());
suspectIdFuture.thenAccept(suspectUUID -> {
Set<Snapshot> snapshots = _snapshotManager.getSnapshots(suspectUUID);
uniqueIds.addAll(publisher.getUUIDs(snapshots));
jsonObject.add("snapshots", gson.toJsonTree(snapshots));
});
futureList.add(suspectIdFuture);
}
Map<UUID, String> usernameMap = publisher.getUsernameMap(uuids);
jsonObject.add("usernames", gson.toJsonTree(usernameMap));
CompletableFuture.allOf(futureList.toArray(new CompletableFuture[futureList.size()])).thenAccept(v -> {
Map<UUID, String> usernameMap = publisher.getUsernameMap(uniqueIds);
jsonObject.add("usernames", gson.toJsonTree(usernameMap));
publisher.publishChatLog(report.getToken(), jsonObject);
publisher.publishChatLog(report.getToken(), jsonObject);
});
}
private Set<UUID> getUUIDs(Report report)
private CompletableFuture<Collection<UUID>> getUUIDs(Report report)
{
Set<UUID> uuids = new HashSet<>(report.getReporters());
uuids.add(report.getSuspect());
Set<Integer> accountIds = new HashSet<>();
accountIds.addAll(report.getReporterIds());
accountIds.add(report.getSuspectId());
if (report.getHandler() != null)
Optional<Integer> handlerIdOptional = report.getHandlerId();
if (handlerIdOptional.isPresent())
{
uuids.add(report.getHandler());
accountIds.add(handlerIdOptional.get());
}
return uuids;
return _reportRepository.getAccountUUIDs(accountIds).thenApply(Map::values);
}
public void onPlayerJoin(Player player)
{
if (hasActiveReport(player))
{
Report report = getActiveReport(player.getName());
sendHandlerNotification(report, F.main(getReportPrefix(report), String.format("%s has re-joined the game.", player.getName())));
}
int playerId = _clientManager.Get(player).getAccountId();
_reportRepository.getOngoingReport(playerId)
.thenCompose(reportId -> _reportRepository.getReport(reportId))
.thenAccept(report -> {
if (report != null)
{
sendHandlerNotification(report, F.main(getReportPrefix(report), String.format("%s has re-joined the game.", player.getName())));
}
}
);
}
public void onPlayerQuit(Player player)
{
if (hasActiveReport(player))
{
Report report = getActiveReport(player.getName());
sendHandlerNotification(report, F.main(getReportPrefix(report), String.format("%s has left the game.", player.getName())));
}
}
int playerId = _clientManager.Get(player).getAccountId();
/**
* @return a uniquely generated report id.
*/
public int generateReportId()
{
JedisPool pool = Utility.getPool(true);
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));
}
/**
* Updates the instance of a report in the repository.
* Also updates the last activity field.
*
* @param report the report to be saved
*/
public void saveReport(Report report)
{
report.updateLastActivity();
_reportRepository.addElement(report);
}
public void removeReport(int reportId)
{
_reportRepository.removeElement(String.valueOf(reportId));
_reportRepository.getOngoingReport(playerId)
.thenCompose(reportId -> _reportRepository.getReport(reportId))
.thenAccept(report -> {
if (report != null)
{
sendHandlerNotification(report, F.main(getReportPrefix(report), String.format("%s has left the game.", player.getName())));
}
}
);
}
/**
@ -386,12 +359,12 @@ public class ReportManager
* Send to the handler of a {@link Report}, regardless of whether or not the handler is currently on this server instance.
* If there is no handler for a report, it will be sent to all staff instead.
*
* @param report the report of which a message should be sent ot it's handler
* @param report the report of which a message should be sent ot it's handler
* @param jsonMessage the report notification message to send
*/
public void sendHandlerNotification(Report report, JsonMessage jsonMessage)
{
if (report.getHandler() != null)
if (report.getHandlerId().isPresent())
{
ReportHandlerNotification reportHandlerNotification = new ReportHandlerNotification(report, jsonMessage);
reportHandlerNotification.publish();
@ -407,7 +380,7 @@ public class ReportManager
* Send to the handler of a {@link Report}, regardless of whether or not the handler is currently on this server instance.
* If there is no handler for a report, it will be sent to all staff instead.
*
* @param report the report of which a message should be sent ot it's handler
* @param report the report of which a message should be sent ot it's handler
* @param message the report notification message to send
*/
public void sendHandlerNotification(Report report, String message)
@ -415,65 +388,20 @@ public class ReportManager
sendHandlerNotification(report, new JsonMessage(message));
}
/**
* @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 Report getActiveReport(String playerName)
{
Integer reportId = _activeReports.get(playerName.toLowerCase());
if (reportId != null)
{
return getReport(reportId);
}
return null;
}
public boolean hasActiveReport(Player player)
{
return getActiveReport(player.getName()) != null;
}
public boolean isActiveReport(int reportId)
{
for (Map.Entry<String, Integer> activeReport : _activeReports.entrySet())
{
if (activeReport.getValue() == reportId)
{
return true;
}
}
return false;
}
public boolean removeActiveReport(int reportId)
{
for (Map.Entry<String, Integer> activeReport : _activeReports.entrySet())
{
if (activeReport.getValue() == reportId)
{
_activeReports.remove(activeReport.getKey());
return true;
}
}
return false;
}
public Collection<Integer> getActiveReports()
{
return _activeReports.values();
}
/* STATIC HELPERS */
public static String getReportPrefix(Report report)
{
return getReportPrefix(report.getReportId());
Optional<Integer> reportIdOptional = report.getReportId();
if (reportIdOptional.isPresent())
{
return getReportPrefix(reportIdOptional.get());
}
else
{
throw new IllegalStateException("Report has not yet been assigned an id.");
}
}
public static String getReportPrefix(int reportId)

View File

@ -0,0 +1,53 @@
package mineplex.core.report;
import java.util.Date;
import static com.google.common.base.Preconditions.*;
/**
* Stores data about a single player report
*
* One or more of these make-up a {@link Report}.
*/
public class ReportMessage
{
private int _reporterId;
private String _message;
private String _server;
private Date _time;
public ReportMessage(int reporterId, String message, String server)
{
this(reporterId, message, server, new Date());
}
public ReportMessage(int reporterId, String message, String server, Date time)
{
checkNotNull(message);
_reporterId = reporterId;
_message = message;
_server = server;
_time = time;
}
public int getReporterId()
{
return _reporterId;
}
public String getMessage()
{
return _message;
}
public String getServer()
{
return _server;
}
public Date getTimeCreated()
{
return _time;
}
}

View File

@ -4,7 +4,6 @@ import mineplex.core.MiniPlugin;
import mineplex.core.report.command.ReportCloseCommand;
import mineplex.core.report.command.ReportCommand;
import mineplex.core.report.command.ReportHandleCommand;
import mineplex.core.report.task.ReportPurgeTask;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent;
@ -17,23 +16,11 @@ import org.bukkit.plugin.java.JavaPlugin;
public class ReportPlugin extends MiniPlugin
{
private final ReportManager _reportManager;
private ReportPurgeTask _reportPurgeTask;
public ReportPlugin(JavaPlugin plugin, ReportManager reportManager)
{
super("Report", plugin);
_reportManager = reportManager;
// purge old reports every minute
_reportPurgeTask = new ReportPurgeTask(_reportManager);
_reportPurgeTask.runTaskTimerAsynchronously(getPlugin(), 20L * 10, 20L * 60);
}
@Override
public void disable()
{
_reportPurgeTask.cancel();
}
public ReportManager getReportManager()

View File

@ -3,7 +3,6 @@ package mineplex.core.report;
import java.util.Collections;
import java.util.EnumMap;
import java.util.Map;
import java.util.UUID;
/**
* Holds report specific data for a user.
@ -11,24 +10,22 @@ import java.util.UUID;
*/
public class ReportProfile
{
private UUID _playerUUID;
private Map<ReportCategory, Map<ReportResult, Integer>> _reputation;
private Map<ReportCategory, Map<ReportResultType, Integer>> _reputation;
public ReportProfile(UUID playerUUID)
public ReportProfile()
{
_playerUUID = playerUUID;
_reputation = Collections.synchronizedMap(new EnumMap<>(ReportCategory.class)); // allows map to be modified in database thread
for (ReportCategory reportCategory : ReportCategory.values())
{
Map<ReportResult, Integer> resultValues = new EnumMap<>(ReportResult.class);
Map<ReportResultType, Integer> resultValues = new EnumMap<>(ReportResultType.class);
for (ReportResult reportResult : ReportResult.values())
for (ReportResultType reportResultType : ReportResultType.values())
{
// ensure global stats are only applied to GLOBAL and non-global stats are not applied to GLOBAL
if (reportResult.isGlobalStat() == (reportCategory == ReportCategory.GLOBAL))
if (reportResultType.isGlobalStat() == (reportCategory == ReportCategory.GLOBAL))
{
resultValues.put(reportResult, 0);
resultValues.put(reportResultType, 0);
}
}
@ -36,39 +33,39 @@ public class ReportProfile
}
}
public UUID getPlayerUUID()
{
return _playerUUID;
}
/**
* Sets the value of a result type within the specified category.
*
* @param reportCategory the category type
* @param reportResult the result type
* @param reportResultType the result type
* @param newValue the new value
*/
public void setValue(ReportCategory reportCategory, ReportResult reportResult, int newValue)
public void setValue(ReportCategory reportCategory, ReportResultType reportResultType, int newValue)
{
_reputation.get(reportCategory).put(reportResult, newValue);
_reputation.get(reportCategory).put(reportResultType, newValue);
}
/**
* Gets the value of a result type within the specified category.
*
* @param reportCategory the category type
* @param reportResult the result type
* @param reportResultType the result type
* @return the value
*/
public int getValue(ReportCategory reportCategory, ReportResult reportResult)
public int getValue(ReportCategory reportCategory, ReportResultType reportResultType)
{
Map<ReportResult, Integer> resultValues = _reputation.get(reportCategory);
Map<ReportResultType, Integer> resultValues = _reputation.get(reportCategory);
if (resultValues == null || !resultValues.containsKey(reportResult))
if (resultValues == null || !resultValues.containsKey(reportResultType))
{
throw new UnsupportedOperationException("Cannot retrieve " + reportResult.name() + " value for category " + reportCategory.name());
throw new UnsupportedOperationException("Cannot retrieve " + reportResultType.name() + " value for category " + reportCategory.name());
}
return resultValues.get(reportResult);
return resultValues.get(reportResultType);
}
public int getReputation(ReportCategory reportCategory)
{
return 100; // todo
}
}

View File

@ -1,19 +1,20 @@
package mineplex.core.report;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import org.bukkit.OfflinePlayer;
import org.bukkit.plugin.java.JavaPlugin;
import mineplex.core.account.CoreClient;
import mineplex.core.account.CoreClientManager;
import mineplex.core.database.MinecraftRepository;
import mineplex.serverdata.database.DBPool;
import mineplex.serverdata.database.DatabaseRunnable;
import mineplex.serverdata.database.ResultSetCallable;
import mineplex.serverdata.database.column.ColumnInt;
/**
@ -22,6 +23,7 @@ import mineplex.serverdata.database.column.ColumnInt;
public class ReportProfileRepository extends MinecraftRepository
{
// todo test this query
// todo optimize this query so it only needs run once per fetch
private static final String GRAB_RESULT_COUNT = "SELECT COUNT(*) AS count " +
" FROM reports, reportResults results, reportResults results " +
" WHERE results.reportId = reports.id " +
@ -30,38 +32,53 @@ public class ReportProfileRepository extends MinecraftRepository
" AND reports.categoryId = ?" +
" AND results.resultId = ?;";
private final CoreClientManager _coreClientManager;
public ReportProfileRepository(JavaPlugin plugin, CoreClientManager coreClientManager)
public ReportProfileRepository(JavaPlugin plugin)
{
super(plugin, DBPool.getAccount());
_coreClientManager = coreClientManager;
}
public ReportProfile loadProfile(OfflinePlayer offlinePlayer)
public List<CompletableFuture<ReportProfile>> getProfiles(Collection<Integer> accountIds)
{
CoreClient coreClient = _coreClientManager.Get(offlinePlayer.getName()); // todo is this okay?
ReportProfile reportProfile = new ReportProfile(offlinePlayer.getUniqueId());
return accountIds.stream().map(this::getProfile).collect(Collectors.toList());
}
for (ReportCategory reportCategory : ReportCategory.values())
public CompletableFuture<ReportProfile> getProfile(int accountId)
{
return CompletableFuture.supplyAsync(() ->
{
for (ReportResult reportResult : ReportResult.values())
ReportProfile reportProfile = new ReportProfile();
for (ReportCategory reportCategory : ReportCategory.values())
{
if (reportResult.isGlobalStat() == (reportCategory == ReportCategory.GLOBAL))
for (ReportResultType resultType : ReportResultType.values())
{
handleDatabaseCall(new DatabaseRunnable(() -> executeQuery(GRAB_RESULT_COUNT, resultSet -> {
int count = resultSet.getInt("count");
reportProfile.setValue(reportCategory, reportResult, count);
}, new ColumnInt("reporterId", coreClient.getAccountId()),
new ColumnInt("categoryId", reportCategory.getDatabaseId()),
new ColumnInt("resultId", reportResult.getDatabaseId()))),
"Failed to load ReportProfile for: " + offlinePlayer.toString()
);
// only get global stat if global category
// only get non-global stat if non-global category
if (resultType.isGlobalStat() == (reportCategory == ReportCategory.GLOBAL))
{
try (Connection connection = DBPool.getAccount().getConnection())
{
PreparedStatement preparedStatement = connection.prepareStatement(GRAB_RESULT_COUNT);
preparedStatement.setInt(1, accountId); // reporterId
preparedStatement.setInt(2, reportCategory.getId()); // categoryId
preparedStatement.setInt(3, resultType.getId()); // resultId
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.next())
{
reportProfile.setValue(reportCategory, resultType, resultSet.getInt("count"));
}
}
catch (SQLException e)
{
throw new RuntimeException(e);
}
}
}
}
}
return reportProfile;
return reportProfile;
});
}
@Override

View File

@ -1,11 +1,27 @@
package mineplex.core.report;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import mineplex.core.database.MinecraftRepository;
import mineplex.serverdata.database.DBPool;
import mineplex.serverdata.database.DatabaseRunnable;
import mineplex.serverdata.database.RepositoryBase;
import mineplex.serverdata.database.column.ColumnInt;
import mineplex.serverdata.database.column.ColumnVarChar;
import org.apache.commons.lang3.StringUtils;
import org.bukkit.plugin.java.JavaPlugin;
@ -13,38 +29,392 @@ import org.bukkit.plugin.java.JavaPlugin;
* Responsible for all database related operations for this module.
*/
public class ReportRepository extends MinecraftRepository
{
private static String CREATE_TICKET_TABLE = "CREATE TABLE IF NOT EXISTS reportTickets (reportId INT NOT NULL, eventDate LONG, playerId INT NOT NULL, server VARCHAR(50), closerId INT NOT NULL, result VARCHAR(25), reason VARCHAR(100), PRIMARY KEY (reportId), FOREIGN KEY (playerId) REFERENCES accounts(id), FOREIGN KEY (closerId) REFERENCES accounts(id));";
private static String INSERT_TICKET = "INSERT INTO reportTickets (reportId, eventDate, playerId, server, closerId, result, reason) VALUES (?, now(), ?, ?, ?, ?, ?);";
{
private static final String INSERT_REPORT = "INSERT INTO reports (suspectId, categoryId) VALUES (?, ?);" +
"SELECT LAST_INSERT_ID() AS id;";
private static final String SET_REPORT_MESSAGE = "REPLACE INTO reportReasons (reportId, reporterId, reason, server, time)" +
" VALUES (?, ?, ?, ?, ?);";
private static final String SET_REPORT_HANDLER = "REPLACE INTO reportHandlers (reportId, handlerId)" +
" VALUES (?, ?);";
private static final String SET_REPORT_RESULT = "REPLACE INTO reportResults (reportId, resultId, reason, closedTime)\n" +
" VALUES (?, ?, ?, ?);";
private static final String GET_REPORT = "SELECT * FROM reports" +
" LEFT JOIN reportHandlers ON reports.id = reportHandlers.reportId" +
" LEFT JOIN reportResults ON reports.id = reportResults.reportId" +
" WHERE reports.id = ?;";
private static final String GET_REPORT_REASONS = "SELECT * FROM reportReasons" +
" WHERE reportId = ?" +
" ORDER BY reportedTime ASC;";
private static final String GET_UNHANDLED_REPORTS = "SELECT reports.id FROM reports" +
" LEFT JOIN reportResults ON reports.id = reportResults.reportId" +
" LEFT JOIN reportHandlers ON reports.id = reportHandlers.reportId" +
" WHERE reportResults.reportId IS NULL" +
" AND reportHandlers.reportId IS NULL;";
private static final String GET_ONGOING_REPORT = "SELECT reports.id FROM reports" +
" LEFT JOIN reportResults ON reports.id = reportResults.reportId" +
" WHERE reportResults.reportId IS NULL" +
" AND reports.suspectId = ?;";
private static final String GET_ONGOING_REPORT_CATEGORY = "SELECT reports.id FROM reports" +
" LEFT JOIN reportResults ON reports.id = reportResults.reportId" +
" WHERE reportResults.reportId IS NULL" +
" AND reports.suspectId = ?" +
" AND reports.categoryId = ?;";
private static final String GET_USER_RESULT_COUNT = "SELECT COUNT(reports.id) AS resultCount" +
" FROM reports, reportReasons, reportResults" +
" WHERE reports.id = reportReasons.reportId" +
" AND reports.id = reportResults.reportId" +
" AND reportReasons.reporterId = ?" +
" AND reportResults.resultId = ?;";
private static final String GET_ACCOUNT_NAME = "SELECT id, name` FROM accounts" +
" WHERE id = ?" +
" LIMIT 1;";
private static final String GET_ACCOUNT_UUID = "SELECT id, uuid FROM accounts" +
" WHERE id IN (%s);";
public ReportRepository(JavaPlugin plugin)
{
super(plugin, DBPool.getAccount());
}
@Override
protected void initialize()
{
// executeUpdate(CREATE_TICKET_TABLE);
}
@Override
protected void update()
{
}
public void logReport(final int reportId, final int playerId, final String server, final int closerId, final ReportResult result, final String reason)
public CompletableFuture<List<Integer>> getUnhandledReports()
{
handleDatabaseCall(new DatabaseRunnable(new Runnable()
return CompletableFuture.supplyAsync(() ->
{
@Override
public void run()
List<Integer> unhandledReports = new ArrayList<>();
try (Connection connection = DBPool.getAccount().getConnection())
{
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));
PreparedStatement preparedStatement = connection.prepareStatement(GET_UNHANDLED_REPORTS);
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next())
{
unhandledReports.add(resultSet.getInt("id"));
}
}
}), "Error logging result for report " + reportId + ".");
catch (SQLException e)
{
throw new RuntimeException(e);
}
return unhandledReports;
});
}
public CompletableFuture<List<Report>> getReports(Collection<Integer> reportIds)
{
return CompletableFuture.supplyAsync(() -> reportIds.parallelStream()
.map(ReportRepository.this::getReport)
.map(CompletableFuture::join)
.filter(report -> report != null)
.collect(Collectors.toList()));
}
public CompletableFuture<Report> getReport(int reportId)
{
if (reportId != -1)
{
return CompletableFuture.supplyAsync(() ->
{
try (Connection connection = DBPool.getAccount().getConnection())
{
PreparedStatement preparedStatement = connection.prepareStatement(GET_REPORT);
preparedStatement.setInt(1, reportId);
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.next())
{
int suspectId = resultSet.getInt("suspectId");
ReportCategory reportCategory = ReportCategory.getById(resultSet.getInt("categoryId"));
Report report = new Report(reportId, suspectId, reportCategory);
int handlerId = resultSet.getInt("handlerId");
if (!resultSet.wasNull())
{
report.setHandlerId(handlerId);
}
Set<ReportMessage> reportMessages = getReportReasons(reportId).join();
reportMessages.forEach(report::addReportReason);
int resultId = resultSet.getInt("resultId");
if (!resultSet.wasNull())
{
ReportResultType resultType = ReportResultType.getById(resultId);
String reason = resultSet.getString("reason");
Date closedTime = new Date(resultSet.getTimestamp("closedTime").getTime());
report.setReportResult(new ReportResult(resultType, reason, closedTime));
}
return report;
}
}
catch (SQLException e)
{
throw new RuntimeException(e);
}
return null;
});
}
else
{
return CompletableFuture.completedFuture(null);
}
}
// todo check if expired
public CompletableFuture<Integer> getOngoingReport(int suspectId)
{
return CompletableFuture.supplyAsync(() ->
{
try (Connection connection = DBPool.getAccount().getConnection())
{
PreparedStatement preparedStatement = connection.prepareStatement(GET_ONGOING_REPORT);
preparedStatement.setInt(1, suspectId);
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.next())
{
return resultSet.getInt("id");
}
}
catch (SQLException e)
{
throw new RuntimeException(e);
}
return null;
});
}
public CompletableFuture<Integer> getOngoingReport(int suspectId, ReportCategory reportCategory)
{
return CompletableFuture.supplyAsync(() ->
{
try (Connection connection = DBPool.getAccount().getConnection())
{
PreparedStatement preparedStatement = connection.prepareStatement(GET_ONGOING_REPORT_CATEGORY);
preparedStatement.setInt(1, suspectId);
preparedStatement.setInt(2, reportCategory.getId());
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.next())
{
return resultSet.getInt("id");
}
}
catch (SQLException e)
{
throw new RuntimeException(e);
}
return null;
});
}
private CompletableFuture<Set<ReportMessage>> getReportReasons(int reportId)
{
return CompletableFuture.supplyAsync(() ->
{
Set<ReportMessage> reportMessages = new HashSet<>();
try (Connection connection = DBPool.getAccount().getConnection())
{
PreparedStatement preparedStatement = connection.prepareStatement(GET_REPORT_REASONS);
preparedStatement.setInt(1, reportId);
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next())
{
int reporterId = resultSet.getInt("reporterId");
String reason = resultSet.getString("reason");
String server = resultSet.getString("server");
Date date = resultSet.getTimestamp("time");
ReportMessage reportMessage = new ReportMessage(reporterId, reason, server, date);
reportMessages.add(reportMessage);
}
}
catch (SQLException e)
{
throw new RuntimeException(e);
}
return reportMessages;
});
}
public CompletableFuture<Integer> getResultCount(int userId, ReportResultType reportResultType)
{
return CompletableFuture.supplyAsync(() -> {
try (Connection connection = DBPool.getAccount().getConnection())
{
PreparedStatement preparedStatement = connection.prepareStatement(GET_USER_RESULT_COUNT);
preparedStatement.setInt(1, userId);
preparedStatement.setInt(2, reportResultType.getId());
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.next())
{
return resultSet.getInt("resultCount");
}
}
catch (SQLException e)
{
e.printStackTrace();
}
return null;
});
}
public CompletableFuture<Void> updateReport(Report report)
{
return CompletableFuture.supplyAsync(() ->
{
try (Connection connection = DBPool.getAccount().getConnection())
{
if (!report.getReportId().isPresent())
{
PreparedStatement preparedStatement = connection.prepareStatement(INSERT_REPORT);
preparedStatement.setInt(1, report.getSuspectId());
preparedStatement.setInt(2, report.getCategory().getId());
ResultSet resultSet = preparedStatement.executeQuery();
resultSet.next();
report._reportId = resultSet.getInt("id");
}
Optional<Integer> idOptional = report.getReportId();
if (idOptional.isPresent())
{
for (Map.Entry<Integer, ReportMessage> entry : report.getMessages().entrySet())
{
ReportMessage reportMessage = entry.getValue();
PreparedStatement preparedStatement = connection.prepareStatement(SET_REPORT_MESSAGE);
preparedStatement.setInt(1, idOptional.get()); // report id
preparedStatement.setInt(2, entry.getKey()); // suspect id
preparedStatement.setString(3, reportMessage.getMessage()); // reason
preparedStatement.setString(4, reportMessage.getServer()); // server
preparedStatement.setTimestamp(5, new Timestamp(reportMessage.getTimeCreated().getTime())); // time
preparedStatement.execute();
}
Optional<Integer> handlerIdOptional = report.getHandlerId();
if (handlerIdOptional.isPresent())
{
PreparedStatement preparedStatement = connection.prepareStatement(SET_REPORT_HANDLER);
preparedStatement.setInt(1, idOptional.get()); // report id
preparedStatement.setInt(2, handlerIdOptional.get()); // handler id
preparedStatement.execute();
}
Optional<ReportResult> reportResultOptional = report.getReportResult();
if (reportResultOptional.isPresent())
{
ReportResult reportResult = reportResultOptional.get();
PreparedStatement preparedStatement = connection.prepareStatement(SET_REPORT_RESULT);
preparedStatement.setInt(1, idOptional.get()); // report id
preparedStatement.setInt(2, reportResult.getResultType().getId()); // result id
preparedStatement.setString(3, reportResult.getReason()); // reason
preparedStatement.setTimestamp(4, new Timestamp(reportResult.getClosedTime().getTime())); // closed time
preparedStatement.execute();
}
}
else
{
throw new IllegalStateException("Report id is null, this shouldn't happen.");
}
}
catch (SQLException e)
{
throw new RuntimeException(e);
}
return null;
});
}
public CompletableFuture<String> getAccountName(int accountId)
{
return CompletableFuture.supplyAsync(() ->
{
try (Connection connection = DBPool.getAccount().getConnection())
{
PreparedStatement preparedStatement = connection.prepareStatement(GET_ACCOUNT_NAME);
preparedStatement.setInt(1, accountId);
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.next())
{
return resultSet.getString("name");
}
}
catch (SQLException e)
{
throw new RuntimeException(e);
}
return null;
});
}
public CompletableFuture<UUID> getAccountUUID(int accountId)
{
return getAccountUUIDs(Collections.singletonList(accountId))
.thenApply(integerUUIDMap -> integerUUIDMap.get(accountId));
}
public CompletableFuture<Map<Integer, UUID>> getAccountUUIDs(Collection<Integer> accountIds)
{
return CompletableFuture.supplyAsync(() ->
{
Map<Integer, UUID> accountUUIDs = new HashMap<>();
try (Connection connection = DBPool.getAccount().getConnection())
{
String query = String.format(GET_ACCOUNT_UUID, StringUtils.join(accountIds, ", "));
PreparedStatement preparedStatement = connection.prepareStatement(query);
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next())
{
int accountId = resultSet.getInt("id");
UUID accountUUID = UUID.fromString(resultSet.getString("uuid"));
accountUUIDs.put(accountId, accountUUID);
}
}
catch (SQLException e)
{
throw new RuntimeException(e);
}
return accountUUIDs;
});
}
}

View File

@ -1,66 +1,40 @@
package mineplex.core.report;
import org.bukkit.ChatColor;
import java.util.Date;
/**
* Contains all possible outcomes for a report.
* Stores data about the result of a report.
*/
public enum ReportResult
public class ReportResult
{
// TODO move some of these parameters elsewhere
ACCEPTED(0, false, ChatColor.GREEN, "Accept Report (Punish Player)", "Accepted (Player Received Punishment)"),
DENIED(1, false, ChatColor.YELLOW, "Deny Report", "Denied"),
ABUSIVE(2, true, ChatColor.RED, "Mark Abusive Report", "Abusive Report");
private ReportResultType _resultType;
private String _reason;
private Date _closedTime;
private final int _databaseId;
private final boolean _globalStat;
private final ChatColor _color;
private final String _actionMessage;
private final String _resultMessage;
private final String[] _lore;
ReportResult(int databaseId, boolean globalStat, ChatColor color, String actionMessage, String resultMessage, String... lore)
public ReportResult(ReportResultType resultType, String reason)
{
_databaseId = databaseId;
_globalStat = globalStat;
_color = color;
_actionMessage = actionMessage;
_resultMessage = resultMessage;
_lore = lore;
this(resultType, reason, new Date());
}
public int getDatabaseId()
public ReportResult(ReportResultType resultType, String reason, Date closedTime)
{
return _databaseId;
_resultType = resultType;
_reason = reason;
_closedTime = closedTime;
}
public boolean isGlobalStat()
public ReportResultType getResultType()
{
return _globalStat;
return _resultType;
}
public ChatColor getColor()
public String getReason()
{
return _color;
return _reason;
}
public String getActionMessage()
public Date getClosedTime()
{
return _actionMessage;
}
public String getResultMessage()
{
return _resultMessage;
}
public String[] getLore()
{
return _lore;
}
public String toDisplayMessage()
{
return _color + _resultMessage;
return _closedTime;
}
}

View File

@ -0,0 +1,79 @@
package mineplex.core.report;
import org.bukkit.ChatColor;
/**
* Contains all possible outcomes for a report.
*/
public enum ReportResultType
{
// TODO move some of these parameters elsewhere
ACCEPTED(0, false, ChatColor.GREEN, "Accept Report (Punish Player)", "Accepted (Player Received Punishment)"),
DENIED(1, false, ChatColor.YELLOW, "Deny Report", "Denied"),
ABUSIVE(2, true, ChatColor.RED, "Mark Abusive Report", "Abusive Report");
private final int _id;
private final boolean _globalStat;
private final ChatColor _color;
private final String _actionMessage;
private final String _resultMessage;
private final String[] _lore;
ReportResultType(int id, boolean globalStat, ChatColor color, String actionMessage, String resultMessage, String... lore)
{
_id = id;
_globalStat = globalStat;
_color = color;
_actionMessage = actionMessage;
_resultMessage = resultMessage;
_lore = lore;
}
public int getId()
{
return _id;
}
public boolean isGlobalStat()
{
return _globalStat;
}
public ChatColor getColor()
{
return _color;
}
public String getActionMessage()
{
return _actionMessage;
}
public String getResultMessage()
{
return _resultMessage;
}
public String[] getLore()
{
return _lore;
}
public String toDisplayMessage()
{
return _color + _resultMessage;
}
public static ReportResultType getById(int id)
{
for (ReportResultType resultType : values())
{
if (resultType.getId() == id)
{
return resultType;
}
}
return null;
}
}

View File

@ -30,15 +30,18 @@ public class ReportCloseCommand extends CommandBase<ReportPlugin>
int reportId = Integer.parseInt(args[0]);
String reason = F.combine(args, 1, null, false);
if (Plugin.getReportManager().isActiveReport(reportId))
Plugin.getReportManager().isActiveReport(reportId).thenAccept(isActive ->
{
ReportResultPage reportResultPage = new ReportResultPage(Plugin, reportId, player, reason);
reportResultPage.openInventory(); // report is closed when player selects the result
}
else
{
UtilPlayer.message(player, F.main(Plugin.getName(), C.cRed + "That report either does not exist or has been closed."));
}
if (isActive)
{
ReportResultPage reportResultPage = new ReportResultPage(Plugin, reportId, player, reason);
reportResultPage.openInventory(); // report is closed when player selects the result
}
else
{
UtilPlayer.message(player, F.main(Plugin.getName(), C.cRed + "That report either does not exist or has been closed."));
}
});
}
}
}

View File

@ -1,5 +1,7 @@
package mineplex.core.report.command;
import java.util.UUID;
import mineplex.core.command.CommandBase;
import mineplex.core.common.Rank;
import mineplex.core.common.util.C;
@ -12,7 +14,8 @@ import org.bukkit.entity.Player;
public class ReportCommand extends CommandBase<ReportPlugin>
{
private static final UUID IKEIRNEZ_UUID = UUID.fromString("e54554ed-678e-420d-a2b5-09112ce6f244");
public ReportCommand(ReportPlugin plugin)
{
super(plugin, Rank.ALL, "report");
@ -37,7 +40,8 @@ public class ReportCommand extends CommandBase<ReportPlugin>
if (reportedPlayer != null)
{
if (reportedPlayer == player)
// allow developer (iKeirNez) to report himself (for easy testing reasons)
if (reportedPlayer == player && !player.getUniqueId().equals(IKEIRNEZ_UUID))
{
UtilPlayer.message(player, F.main(Plugin.getName(), C.cRed + "You cannot report yourself."));
}

View File

@ -2,6 +2,7 @@ package mineplex.core.report.command;
import mineplex.core.common.jsonchat.JsonMessage;
import mineplex.core.report.Report;
import mineplex.core.report.ReportMessage;
/**
* A message regarding a report which is sent only to the player handling the report.
@ -9,25 +10,21 @@ import mineplex.core.report.Report;
public class ReportHandlerNotification extends ReportNotification
{
private int _reportId;
private String _server; // the server the incident took place on
public ReportHandlerNotification(Report report, String notification)
{
this(report, new JsonMessage(notification));
}
private int _handlerId;
public ReportHandlerNotification(Report report, JsonMessage notification)
{
super(notification);
if (report.getHandler() == null)
{
throw new IllegalStateException("Report has no handler.");
}
_reportId = report.getReportId().orElseThrow(() -> new IllegalStateException("Report has no id set."));
_handlerId = report.getHandlerId().orElseThrow(() -> new IllegalStateException("Report has no handler."));
_reportId = report.getReportId();
_server = report.getServerName();
setTargetServers(_server);
setTargetServers(getServersInvolved(report));
}
private String[] getServersInvolved(Report report)
{
return report.getMessages().values().stream().map(ReportMessage::getServer).distinct().toArray(String[]::new);
}
public int getReportId()
@ -35,8 +32,8 @@ public class ReportHandlerNotification extends ReportNotification
return _reportId;
}
public String getServer()
public int getHandlerId()
{
return _server;
return _handlerId;
}
}

View File

@ -1,13 +1,10 @@
package mineplex.core.report.command;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import mineplex.core.common.util.UtilServer;
import mineplex.core.report.Report;
import mineplex.core.report.ReportManager;
import mineplex.serverdata.commands.CommandCallback;
import mineplex.serverdata.commands.ServerCommand;
@ -30,22 +27,29 @@ public class ReportNotificationCallback implements CommandCallback
if (command instanceof ReportHandlerNotification)
{
ReportHandlerNotification reportNotification = (ReportHandlerNotification) command;
Report report = _reportManager.getReport(reportNotification.getReportId());
if (report != null)
{
UUID handlerUUID = report.getHandler();
_reportManager.getReportRepository().getReport(reportNotification.getReportId())
.thenAccept(report ->
{
if (report != null)
{
int handlerId = reportNotification.getHandlerId();
if (handlerUUID != null)
{
Player handler = Bukkit.getPlayer(handlerUUID);
_reportManager.getReportRepository().getAccountUUID(handlerId).thenAccept(handlerUUID ->
{
if (handlerUUID != null)
{
Player handler = Bukkit.getPlayer(handlerUUID);
if (handler != null)
{
sendRawMessage(handler, reportNotification.getNotification());
}
}
}
if (handler != null)
{
sendRawMessage(handler, reportNotification.getNotification());
}
}
});
}
}
);
}
else if (command instanceof ReportNotification)
{

View File

@ -1,9 +1,7 @@
package mineplex.core.report.task;
import java.util.Map;
import java.util.UUID;
import java.util.Collection;
import org.bukkit.Bukkit;
import org.bukkit.scheduler.BukkitRunnable;
import mineplex.core.chatsnap.publishing.SnapshotPublisher;
@ -12,6 +10,7 @@ import mineplex.core.common.jsonchat.JsonMessage;
import mineplex.core.common.util.C;
import mineplex.core.report.Report;
import mineplex.core.report.ReportManager;
import mineplex.core.report.ReportMessage;
import mineplex.core.report.command.ReportHandlerNotification;
import org.apache.commons.lang3.StringUtils;
@ -34,29 +33,31 @@ public class ReportHandlerMessageTask extends BukkitRunnable
@Override
public void run()
{
int reportId = _report.getReportId();
int reportId = _report.getReportId().orElse(-1);
if (_reportManager.isActiveReport(reportId))
if (/*_reportManager.isActiveReport(reportId)*/true)// todo
{
String suspectName = Bukkit.getOfflinePlayer(_report.getSuspect()).getName();
_reportManager.getReportRepository().getAccountName(_report.getSuspectId())
.thenAccept(suspectName ->
{
JsonMessage jsonMessage = new JsonMessage(DECORATION)
.extra("\n")
.add(C.cAqua + "Reviewing Ticket " + C.cGold + "#" + reportId)
.add("\n\n")
.add(C.cPurple + StringUtils.join(getReportReasons(), "\n" + C.cPurple))
.add("\n\n")
.add(C.cAqua + "Suspect: " + C.cGold + suspectName)
.add("\n")
.add((_report.hasToken() ? C.cAqua + "Chat Log: " + C.cGold + "Click Here" + "\n" : ""))
.click(ClickEvent.OPEN_URL, SnapshotPublisher.getURL(_report.getToken()))
.add("\n")
.add(C.cAqua + "Type " + C.cGold + "/reportclose " + reportId + " <reason>" + C.cAqua + " to close this report.")
.click(ClickEvent.SUGGEST_COMMAND, "/reportclose " + reportId)
.add("\n")
.add(DECORATION);
JsonMessage jsonMessage = new JsonMessage(DECORATION)
.extra("\n")
.add(C.cAqua + "Reviewing Ticket " + C.cGold + "#" + reportId)
.add("\n\n")
.add(C.cPurple + StringUtils.join(getReportReasons(), "\n" + C.cPurple))
.add("\n\n")
.add(C.cAqua + "Suspect: " + C.cGold + suspectName)
.add("\n")
.add((_report.hasToken() ? C.cAqua + "Chat Log: " + C.cGold + "Click Here" + "\n" : ""))
.click(ClickEvent.OPEN_URL, SnapshotPublisher.getURL(_report.getToken()))
.add("\n")
.add(C.cAqua + "Type " + C.cGold + "/reportclose " + reportId + " <reason>" + C.cAqua + " to close this report.")
.click(ClickEvent.SUGGEST_COMMAND, "/reportclose " + reportId)
.add("\n")
.add(DECORATION);
new ReportHandlerNotification(_report, jsonMessage).publish();
new ReportHandlerNotification(_report, jsonMessage).publish();
});
}
else
{
@ -65,17 +66,19 @@ public class ReportHandlerMessageTask extends BukkitRunnable
}
}
public String[] getReportReasons()
private String[] getReportReasons()
{
Map<UUID, String> reportReasons = _report.getReportReasons();
String[] output = new String[reportReasons.size()];
Collection<ReportMessage> reportMessages = _report.getMessages().values();
String[] output = new String[reportMessages.size()];
int count = 0;
for (Map.Entry<UUID, String> entry : reportReasons.entrySet())
for (ReportMessage reportMessage : reportMessages)
{
String reporterName = Bukkit.getOfflinePlayer(entry.getKey()).getName();
// this is blocking, but that's okay as it will only be called asynchronously
String reporterName = _reportManager.getReportRepository().getAccountName(reportMessage.getReporterId()).join();
// triple backslashes so this translates to valid JSON
output[count++] = "\\\"" + entry.getValue() + "\\\" - " + reporterName;
output[count++] = "\\\"" + reportMessage.getMessage() + "\\\" - " + reporterName;
}
return output;

View File

@ -1,87 +0,0 @@
package mineplex.core.report.task;
import java.util.ArrayList;
import org.bukkit.scheduler.BukkitRunnable;
import mineplex.core.common.util.C;
import mineplex.core.report.Report;
import mineplex.core.report.ReportManager;
/**
* Checks reports "owned" by this instance for inactivity, purges inactive reports.
*/
public class ReportPurgeTask extends BukkitRunnable
{
private ReportManager _reportManager;
public ReportPurgeTask(ReportManager reportManager)
{
_reportManager = reportManager;
}
@Override
public void run()
{
for (int reportId : new ArrayList<>(_reportManager.getActiveReports())) // create copy as this will be run async
{
Report report = _reportManager.getReport(reportId);
if (report != null)
{
if (checkForPurge(report))
{
notifyHandler(report);
}
}
else
{
// report has been leftover for some reason
purgeReport(reportId);
}
}
}
/**
* Checks if a report should be purged and carries it out if so.
*
* @param report the report to check for purging (and act accordingly)
* @return true if the report was purged, false otherwise
*/
public boolean checkForPurge(Report report)
{
if (!report.isActive())
{
int reportId = report.getReportId();
purgeReport(reportId);
return true;
}
return false;
}
/**
* Purges a report.
*
* @param reportId the report id to purge
*/
public void purgeReport(int reportId)
{
_reportManager.removeReport(reportId);
_reportManager.removeActiveReport(reportId);
}
/**
* Notifies the handler of a report (if any) that the report was purged.
*
* @param report the report which was purged
*/
public void notifyHandler(Report report)
{
// would result in spam if many reports with no handlers are purged (causing each member of staff to receive a message for every purged report)
if (report.getHandler() != null)
{
_reportManager.sendHandlerNotification(report, ReportManager.getReportPrefix(report) + C.cRed + "Purging report due to inactivity.");
}
}
}

View File

@ -10,7 +10,6 @@ import org.bukkit.event.HandlerList;
import mineplex.core.common.util.C;
import mineplex.core.gui.SimpleGui;
import mineplex.core.report.ReportCategory;
import mineplex.core.report.Report;
import mineplex.core.report.ReportPlugin;
/**
@ -53,10 +52,11 @@ public class ReportCategoryPage extends SimpleGui
public void addReport(ReportCategory category)
{
Report report = _reportPlugin.getReportManager().reportPlayer(_reportee, _offender, category, _reason);
_reportee.closeInventory();
unregisterListener();
_reportee.sendMessage(C.cGreen + "Report sent successfully (" + C.cGold + "#" + report.getReportId() + C.cGreen + ").");
_reportPlugin.getReportManager().reportPlayer(_reportee, _offender, category, _reason).thenAccept(report ->
_reportee.sendMessage(C.cGreen + "Report sent successfully (" + C.cGold + "#" + report.getReportId() + C.cGreen + ")."));
}
public void unregisterListener()

View File

@ -13,7 +13,7 @@ import org.bukkit.inventory.meta.ItemMeta;
import mineplex.core.common.util.C;
import mineplex.core.gui.SimpleGuiItem;
import mineplex.core.itemstack.ItemBuilder;
import mineplex.core.report.ReportResult;
import mineplex.core.report.ReportResultType;
/**
* Represents a button which can be clicked to determine the result of a report.
@ -21,43 +21,43 @@ import mineplex.core.report.ReportResult;
public class ReportResultButton extends SimpleGuiItem
{
// initialize button display itemsR
private static final Map<ReportResult, ItemStack> ITEM_STACKS = new EnumMap<ReportResult, ItemStack>(ReportResult.class)
private static final Map<ReportResultType, ItemStack> ITEM_STACKS = new EnumMap<ReportResultType, ItemStack>(ReportResultType.class)
{{
ItemStack itemAccept = new ItemBuilder(Material.WOOL)
.setData(DyeColor.GREEN.getData())
.setTitle(C.cGreen + "Accept Report")
.addLore("%suspect% is cheating without a doubt.")
.build();
put(ReportResult.ACCEPTED, itemAccept);
put(ReportResultType.ACCEPTED, itemAccept);
ItemStack itemDeny = new ItemBuilder(Material.WOOL)
.setData(DyeColor.YELLOW.getData())
.setTitle(C.cYellow + "Deny Report")
.addLore("There is not enough evidence against %suspect%.")
.build();
put(ReportResult.DENIED, itemDeny);
put(ReportResultType.DENIED, itemDeny);
ItemStack itemAbuse = new ItemBuilder(Material.WOOL)
.setData(DyeColor.RED.getData())
.setTitle(C.cRed + "Flag Abuse")
.addLore("The reporter(s) were abusing the report system.")
.build();
put(ReportResult.ABUSIVE, itemAbuse);
put(ReportResultType.ABUSIVE, itemAbuse);
}};
private ReportResultPage _reportResultPage;
private ReportResult _result;
private ReportResultType _result;
public ReportResultButton(ReportResultPage reportResultPage, ReportResult reportResult)
public ReportResultButton(ReportResultPage reportResultPage, ReportResultType reportResultType)
{
this(reportResultPage, reportResult, ITEM_STACKS.get(reportResult));
this(reportResultPage, reportResultType, ITEM_STACKS.get(reportResultType));
}
public ReportResultButton(ReportResultPage reportResultPage, ReportResult reportResult, ItemStack displayItem)
public ReportResultButton(ReportResultPage reportResultPage, ReportResultType reportResultType, ItemStack displayItem)
{
super(displayItem);
_reportResultPage = reportResultPage;
_result = reportResult;
_result = reportResultType;
}
@Override

View File

@ -7,6 +7,7 @@ import mineplex.core.gui.SimpleGui;
import mineplex.core.report.ReportManager;
import mineplex.core.report.ReportPlugin;
import mineplex.core.report.ReportResult;
import mineplex.core.report.ReportResultType;
/**
* User interface shown to a moderator when closing a report to determine the result of the report.
@ -31,16 +32,17 @@ public class ReportResultPage extends SimpleGui
private void buildPage()
{
setItem(11, new ReportResultButton(this, ReportResult.ACCEPTED));
setItem(13, new ReportResultButton(this, ReportResult.DENIED));
setItem(15, new ReportResultButton(this, ReportResult.ABUSIVE));
setItem(11, new ReportResultButton(this, ReportResultType.ACCEPTED));
setItem(13, new ReportResultButton(this, ReportResultType.DENIED));
setItem(15, new ReportResultButton(this, ReportResultType.ABUSIVE));
}
public void setResult(ReportResult result)
public void setResult(ReportResultType result)
{
_reportCloser.closeInventory();
unregisterListener();
_reportManager.closeReport(_reportId, _reportCloser, _reason, result);
ReportResult reportResult = new ReportResult(result, _reason);
_reportManager.closeReport(_reportId, _reportCloser, reportResult);
}
public void unregisterListener()

View File

@ -145,9 +145,9 @@ public class Clans extends JavaPlugin
Pair.create(MinecraftVersion.Version1_9, "http://file.mineplex.com/ResClans19.zip")
}, true);
//SnapshotManager snapshotManager = new SnapshotManager(new SnapshotPublisher(this));
//new SnapshotPlugin(this, snapshotManager);
//new ReportPlugin(this, new ReportManager(this, preferenceManager, statsManager, snapshotManager, _commandCenter.Instance.GetClientManager(), serverStatusManager.getCurrentServerName()));
SnapshotManager snapshotManager = new SnapshotManager(new SnapshotPublisher(this));
new SnapshotPlugin(this, snapshotManager);
new ReportPlugin(this, new ReportManager(this, preferenceManager, snapshotManager, _clientManager, serverStatusManager.getCurrentServerName()));
// Enable custom-gear related managers
new CustomTagFix(this, packetHandler);

View File

@ -165,6 +165,11 @@ public class Hub extends JavaPlugin implements IRelation
new GlobalPacketManager(this, clientManager, serverStatusManager, inventoryManager, donationManager, petManager, statsManager, hubManager.getBonusManager().getRewardManager());
//new Replay(this, packetHandler);
SnapshotManager snapshotManager = new SnapshotManager(new SnapshotPublisher(this));
ReportManager reportManager = new ReportManager(this, preferenceManager, snapshotManager, clientManager, serverStatusManager.getCurrentServerName());
new SnapshotPlugin(this, snapshotManager);
new ReportPlugin(this, reportManager);
AprilFoolsManager.Initialize(this, clientManager, disguiseManager);
CombatManager combatManager = new CombatManager(this);

View File

@ -148,10 +148,11 @@ public class Arcade extends JavaPlugin
FriendManager friendManager = new FriendManager(this, _clientManager, preferenceManager, portal);
Chat chat = new Chat(this, incognito, _clientManager, preferenceManager, achievementManager, serverStatusManager.getCurrentServerName());
new MessageManager(this, incognito, _clientManager, preferenceManager, ignoreManager, punish, friendManager, chat);
//SnapshotManager snapshotManager = new SnapshotManager(new SnapshotPublisher(this));
//ReportManager reportManager = new ReportManager(this, preferenceManager, statsManager, snapshotManager, _commandCenter.Instance.GetClientManager(), serverStatusManager.getCurrentServerName());
//new SnapshotPlugin(this, snapshotManager);
//new ReportPlugin(this, reportManager);
SnapshotManager snapshotManager = new SnapshotManager(new SnapshotPublisher(this));
ReportManager reportManager = new ReportManager(this, preferenceManager, snapshotManager, _clientManager, serverStatusManager.getCurrentServerName());
new SnapshotPlugin(this, snapshotManager);
new ReportPlugin(this, reportManager);
BlockRestore blockRestore = new BlockRestore(this);