Skip to content

Commit

Permalink
Add chat logs to moderation report view
Browse files Browse the repository at this point in the history
Closes #140
  • Loading branch information
Brutus5000 committed Oct 22, 2021
1 parent e90842a commit 8f34cac
Show file tree
Hide file tree
Showing 5 changed files with 251 additions and 146 deletions.
6 changes: 3 additions & 3 deletions .idea/compiler.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 26 additions & 1 deletion src/main/java/com/faforever/moderatorclient/ui/ViewHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -1830,7 +1830,11 @@ public static void buildSubjectTable(TableView<VotingSubjectFX> tableView, Votin
applyCopyContextMenus(tableView, extractors);
}

public static void buildModerationReportTableView(TableView<ModerationReportFX> tableView, ObservableList<ModerationReportFX> items) {
public static void buildModerationReportTableView(
TableView<ModerationReportFX> tableView,
ObservableList<ModerationReportFX> items,
Consumer<ModerationReportFX> onChatLog
) {
tableView.setItems(items);
tableView.setEditable(true);
HashMap<TableColumn<ModerationReportFX, ?>, Function<ModerationReportFX, ?>> extractors = new HashMap<>();
Expand Down Expand Up @@ -1929,6 +1933,27 @@ public void updateItem(ModerationReportStatus item, boolean empty) {
tableView.getColumns().add(gameColumn);
extractors.put(gameColumn, reportFx -> reportFx.getGame() == null ? null : reportFx.getGame().getId());

if (onChatLog != null) {
TableColumn<ModerationReportFX, ModerationReportFX> chatLogColumn = new TableColumn<>("Replay");
chatLogColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue()));
chatLogColumn.setCellFactory(param -> new TableCell<>() {

@Override
protected void updateItem(ModerationReportFX item, boolean empty) {
super.updateItem(item, empty);
if (!empty && item != null && item.getGame() != null) {
Button button = new Button("Load chat log");
button.setOnMouseClicked(event -> onChatLog.accept(item));
setGraphic(button);
return;
}
setGraphic(null);
}
});
chatLogColumn.setMinWidth(120);
tableView.getColumns().add(chatLogColumn);
}

TableColumn<ModerationReportFX, String> privateNoteColumn = new TableColumn<>("Private Notice");
privateNoteColumn.setMinWidth(150);
privateNoteColumn.setCellValueFactory(param -> param.getValue().moderatorPrivateNoteProperty());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
package com.faforever.moderatorclient.ui.moderation_reports;

import com.faforever.commons.api.dto.ModerationReportStatus;
import com.faforever.commons.replay.ReplayDataParser;
import com.faforever.moderatorclient.api.FafApiCommunicationService;
import com.faforever.moderatorclient.api.domain.ModerationReportService;
import com.faforever.moderatorclient.ui.*;
import com.faforever.moderatorclient.ui.BanInfoController;
import com.faforever.moderatorclient.ui.Controller;
import com.faforever.moderatorclient.ui.PlatformService;
import com.faforever.moderatorclient.ui.UiService;
import com.faforever.moderatorclient.ui.ViewHelper;
import com.faforever.moderatorclient.ui.domain.BanInfoFX;
import com.faforever.moderatorclient.ui.domain.GameFX;
import com.faforever.moderatorclient.ui.domain.ModerationReportFX;
import com.faforever.moderatorclient.ui.domain.PlayerFX;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Strings;
import javafx.application.Platform;
import javafx.collections.FXCollections;
Expand All @@ -18,132 +25,199 @@
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.TableView;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.Region;
import javafx.stage.Stage;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DurationFormatUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Collectors;

import static java.text.MessageFormat.format;

@Component
@Slf4j
@RequiredArgsConstructor
public class ModerationReportController implements Controller<Region> {
private final ModerationReportService moderationReportService;
private final UiService uiService;
private final FafApiCommunicationService communicationService;
private final PlatformService platformService;
private final ObservableList<PlayerFX> reportedPlayersOfCurrentlySelectedReport = FXCollections.observableArrayList();

public Region root;
public ChoiceBox<ChooseableStatus> statusChoiceBox;
public TextField playerNameFilterTextField;
public TableView<ModerationReportFX> reportTableView;
public Button editReportButton;
public TableView<PlayerFX> reportedPlayerView;

private FilteredList<ModerationReportFX> filteredItemList;
private ObservableList<ModerationReportFX> itemList;
private ModerationReportFX currentlySelectedItemNotNull;

@Override
public Region getRoot() {
return root;
}

@FXML
public void initialize() {
statusChoiceBox.setItems(FXCollections.observableArrayList(ChooseableStatus.values()));
statusChoiceBox.getSelectionModel().select(ChooseableStatus.ALL);

editReportButton.disableProperty().bind(reportTableView.getSelectionModel().selectedItemProperty().isNull());

itemList = FXCollections.observableArrayList();
filteredItemList = new FilteredList<>(itemList);
renewFilter();
SortedList<ModerationReportFX> sortedItemList = new SortedList<>(filteredItemList);
sortedItemList.comparatorProperty().bind(reportTableView.comparatorProperty());
ViewHelper.buildModerationReportTableView(reportTableView, sortedItemList);
statusChoiceBox.getSelectionModel().selectedItemProperty().addListener(observable -> renewFilter());
playerNameFilterTextField.textProperty().addListener(observable -> renewFilter());

reportTableView.getSelectionModel()
.selectedItemProperty()
.addListener((observable, oldValue, newValue) -> {
if (newValue != null) {
currentlySelectedItemNotNull = newValue;
reportedPlayersOfCurrentlySelectedReport.setAll(newValue.getReportedUsers());
}
});

ViewHelper.buildUserTableView(platformService, reportedPlayerView, reportedPlayersOfCurrentlySelectedReport, this::addBan,
playerFX -> ViewHelper.loadForceRenameDialog(uiService, playerFX), communicationService);
}

private void addBan(PlayerFX accountFX) {
BanInfoController banInfoController = uiService.loadFxml("ui/banInfo.fxml");
BanInfoFX ban = new BanInfoFX();
ban.setPlayer(accountFX);
banInfoController.setBanInfo(ban);
banInfoController.addPostedListener(banInfoFX -> onRefreshAllReports());
Stage banInfoDialog = new Stage();
banInfoDialog.setTitle("Apply new ban");
banInfoDialog.setScene(new Scene(banInfoController.getRoot()));
banInfoController.preSetReportId(currentlySelectedItemNotNull.getId());
banInfoDialog.showAndWait();
}

private void renewFilter() {
filteredItemList.setPredicate(moderationReportFx -> {
String playerFilter = playerNameFilterTextField.getText().toLowerCase();
if (!Strings.isNullOrEmpty(playerFilter)) {
boolean reportedPlayerPositive = moderationReportFx.getReportedUsers().stream().anyMatch(accountFX -> accountFX.getLogin().toLowerCase().contains(playerFilter));
boolean reporterPositive = moderationReportFx.getReporter().getLogin().toLowerCase().contains(playerFilter);
if (!(reportedPlayerPositive || reporterPositive)) {
return false;
}
}
ChooseableStatus selectedItem = statusChoiceBox.getSelectionModel().getSelectedItem();
if (selectedItem != null && selectedItem.getModerationReportStatus() != null) {
ModerationReportStatus moderationReportStatus = selectedItem.getModerationReportStatus();
return moderationReportFx.getReportStatus() == moderationReportStatus;
}
return true;
});
}


public void onRefreshAllReports() {
moderationReportService.getAllReports().thenAccept(reportFxes -> Platform.runLater(() -> itemList.setAll(reportFxes))).exceptionally(throwable -> {
log.error("error loading reports", throwable);
return null;
});
}

public void onEdit() {
EditModerationReportController editModerationReportController = uiService.loadFxml("ui/edit_moderation_report.fxml");
editModerationReportController.setModerationReportFx(reportTableView.getSelectionModel().getSelectedItem());
editModerationReportController.setOnSaveRunnable(() -> Platform.runLater(this::onRefreshAllReports));

Stage newCategoryDialog = new Stage();
newCategoryDialog.setTitle("Edit Report");
newCategoryDialog.setScene(new Scene(editModerationReportController.getRoot()));
newCategoryDialog.showAndWait();
}

private enum ChooseableStatus {
ALL(null),
AWAITING(ModerationReportStatus.AWAITING),
PROCESSING(ModerationReportStatus.PROCESSING),
COMPLETED(ModerationReportStatus.COMPLETED),
DISCARDED(ModerationReportStatus.DISCARDED);

@Getter
private final ModerationReportStatus moderationReportStatus;

ChooseableStatus(ModerationReportStatus moderationReportStatus) {
this.moderationReportStatus = moderationReportStatus;
}
}
private final ObjectMapper objectMapper;
private final ModerationReportService moderationReportService;
private final UiService uiService;
private final FafApiCommunicationService communicationService;
private final PlatformService platformService;
private final ObservableList<PlayerFX> reportedPlayersOfCurrentlySelectedReport = FXCollections.observableArrayList();

private final HttpClient httpClient = HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.ALWAYS)
.build();

@Value("${faforever.vault.replay-download-url-format}")
private String replayDownLoadFormat;

public Region root;
public ChoiceBox<ChooseableStatus> statusChoiceBox;
public TextField playerNameFilterTextField;
public TableView<ModerationReportFX> reportTableView;
public Button editReportButton;
public TableView<PlayerFX> reportedPlayerView;
public TextArea chatLogTextArea;

private FilteredList<ModerationReportFX> filteredItemList;
private ObservableList<ModerationReportFX> itemList;
private ModerationReportFX currentlySelectedItemNotNull;

@Override
public Region getRoot() {
return root;
}

@FXML
public void initialize() {
statusChoiceBox.setItems(FXCollections.observableArrayList(ChooseableStatus.values()));
statusChoiceBox.getSelectionModel().select(ChooseableStatus.ALL);

editReportButton.disableProperty().bind(reportTableView.getSelectionModel().selectedItemProperty().isNull());

itemList = FXCollections.observableArrayList();
filteredItemList = new FilteredList<>(itemList);
renewFilter();
SortedList<ModerationReportFX> sortedItemList = new SortedList<>(filteredItemList);
sortedItemList.comparatorProperty().bind(reportTableView.comparatorProperty());
ViewHelper.buildModerationReportTableView(reportTableView, sortedItemList, this::showChatLog);
statusChoiceBox.getSelectionModel().selectedItemProperty().addListener(observable -> renewFilter());
playerNameFilterTextField.textProperty().addListener(observable -> renewFilter());

reportTableView.getSelectionModel()
.selectedItemProperty()
.addListener((observable, oldValue, newValue) -> {
if (newValue != null) {
currentlySelectedItemNotNull = newValue;
reportedPlayersOfCurrentlySelectedReport.setAll(newValue.getReportedUsers());

if (newValue.getGame() == null) {
chatLogTextArea.setText("not available");
} else {
chatLogTextArea.setText("not loaded yet");
}
}
});

chatLogTextArea.setText("select a report first");

ViewHelper.buildUserTableView(platformService, reportedPlayerView, reportedPlayersOfCurrentlySelectedReport, this::addBan,
playerFX -> ViewHelper.loadForceRenameDialog(uiService, playerFX), communicationService);
}

private void addBan(PlayerFX accountFX) {
BanInfoController banInfoController = uiService.loadFxml("ui/banInfo.fxml");
BanInfoFX ban = new BanInfoFX();
ban.setPlayer(accountFX);
banInfoController.setBanInfo(ban);
banInfoController.addPostedListener(banInfoFX -> onRefreshAllReports());
Stage banInfoDialog = new Stage();
banInfoDialog.setTitle("Apply new ban");
banInfoDialog.setScene(new Scene(banInfoController.getRoot()));
banInfoController.preSetReportId(currentlySelectedItemNotNull.getId());
banInfoDialog.showAndWait();
}

private void renewFilter() {
filteredItemList.setPredicate(moderationReportFx -> {
String playerFilter = playerNameFilterTextField.getText().toLowerCase();
if (!Strings.isNullOrEmpty(playerFilter)) {
boolean reportedPlayerPositive = moderationReportFx.getReportedUsers().stream().anyMatch(accountFX -> accountFX.getLogin().toLowerCase().contains(playerFilter));
boolean reporterPositive = moderationReportFx.getReporter().getLogin().toLowerCase().contains(playerFilter);
if (!(reportedPlayerPositive || reporterPositive)) {
return false;
}
}
ChooseableStatus selectedItem = statusChoiceBox.getSelectionModel().getSelectedItem();
if (selectedItem != null && selectedItem.getModerationReportStatus() != null) {
ModerationReportStatus moderationReportStatus = selectedItem.getModerationReportStatus();
return moderationReportFx.getReportStatus() == moderationReportStatus;
}
return true;
});
}


public void onRefreshAllReports() {
moderationReportService.getAllReports().thenAccept(reportFxes -> Platform.runLater(() -> itemList.setAll(reportFxes))).exceptionally(throwable -> {
log.error("error loading reports", throwable);
return null;
});
}

public void onEdit() {
EditModerationReportController editModerationReportController = uiService.loadFxml("ui/edit_moderation_report.fxml");
editModerationReportController.setModerationReportFx(reportTableView.getSelectionModel().getSelectedItem());
editModerationReportController.setOnSaveRunnable(() -> Platform.runLater(this::onRefreshAllReports));

Stage newCategoryDialog = new Stage();
newCategoryDialog.setTitle("Edit Report");
newCategoryDialog.setScene(new Scene(editModerationReportController.getRoot()));
newCategoryDialog.showAndWait();
}

private enum ChooseableStatus {
ALL(null),
AWAITING(ModerationReportStatus.AWAITING),
PROCESSING(ModerationReportStatus.PROCESSING),
COMPLETED(ModerationReportStatus.COMPLETED),
DISCARDED(ModerationReportStatus.DISCARDED);

@Getter
private final ModerationReportStatus moderationReportStatus;

ChooseableStatus(ModerationReportStatus moderationReportStatus) {
this.moderationReportStatus = moderationReportStatus;
}
}

@SneakyThrows
private void showChatLog(ModerationReportFX report) {
GameFX game = report.getGame();
String header = format("CHAT LOG -- Report ID {0} -- Replay ID {1} -- Game \"{2}\"\n\n",
report.getId(), game.getId(), game.getName());
Path tempFilePath = Files.createTempFile(format("faf_replay_{0}_", game.getId()), "");

try {
String replayUrl = game.getReplayUrl(replayDownLoadFormat);

log.info("Downloading replay from {} to {}", replayUrl, tempFilePath);

HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(replayUrl))
.build();

httpClient.send(request, HttpResponse.BodyHandlers.ofFile(tempFilePath));

log.debug("Parsing replay");

ReplayDataParser replayDataParser = new ReplayDataParser(tempFilePath, objectMapper);
String chatLog = header + replayDataParser.getChatMessages().stream()
.map(message -> format("[{0}] from {1} to {2}: {3}",
DurationFormatUtils.formatDuration(message.getTime().toMillis(), "HH:mm:ss"),
message.getSender(), message.getReceiver(), message.getMessage()))
.collect(Collectors.joining("\n"));

chatLogTextArea.setText(chatLog);
} catch (Exception e) {
log.error("Loading replay {} failed", game, e);
chatLogTextArea.setText(header + format("Loading replay failed due to {0}: \n{1}", e, e.getMessage()));
}

Files.delete(tempFilePath);
}
}
Loading

0 comments on commit 8f34cac

Please sign in to comment.