From 8f34cac665bbd64a61035785071df6d92d34f657 Mon Sep 17 00:00:00 2001 From: Brutus5000 Date: Fri, 22 Oct 2021 00:13:46 +0200 Subject: [PATCH] Add chat logs to moderation report view Closes #140 --- .idea/compiler.xml | 6 +- .../moderatorclient/ui/ViewHelper.java | 27 +- .../ModerationReportController.java | 308 +++++++++++------- src/main/resources/application.yml | 4 +- src/main/resources/ui/main_window/report.fxml | 52 +-- 5 files changed, 251 insertions(+), 146 deletions(-) diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 7faa439..693602e 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -15,9 +15,9 @@ - - - + + + diff --git a/src/main/java/com/faforever/moderatorclient/ui/ViewHelper.java b/src/main/java/com/faforever/moderatorclient/ui/ViewHelper.java index 04342ee..e5c8df5 100644 --- a/src/main/java/com/faforever/moderatorclient/ui/ViewHelper.java +++ b/src/main/java/com/faforever/moderatorclient/ui/ViewHelper.java @@ -1830,7 +1830,11 @@ public static void buildSubjectTable(TableView tableView, Votin applyCopyContextMenus(tableView, extractors); } - public static void buildModerationReportTableView(TableView tableView, ObservableList items) { + public static void buildModerationReportTableView( + TableView tableView, + ObservableList items, + Consumer onChatLog + ) { tableView.setItems(items); tableView.setEditable(true); HashMap, Function> extractors = new HashMap<>(); @@ -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 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 privateNoteColumn = new TableColumn<>("Private Notice"); privateNoteColumn.setMinWidth(150); privateNoteColumn.setCellValueFactory(param -> param.getValue().moderatorPrivateNoteProperty()); diff --git a/src/main/java/com/faforever/moderatorclient/ui/moderation_reports/ModerationReportController.java b/src/main/java/com/faforever/moderatorclient/ui/moderation_reports/ModerationReportController.java index 5300953..aa26e08 100644 --- a/src/main/java/com/faforever/moderatorclient/ui/moderation_reports/ModerationReportController.java +++ b/src/main/java/com/faforever/moderatorclient/ui/moderation_reports/ModerationReportController.java @@ -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; @@ -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 { - private final ModerationReportService moderationReportService; - private final UiService uiService; - private final FafApiCommunicationService communicationService; - private final PlatformService platformService; - private final ObservableList reportedPlayersOfCurrentlySelectedReport = FXCollections.observableArrayList(); - - public Region root; - public ChoiceBox statusChoiceBox; - public TextField playerNameFilterTextField; - public TableView reportTableView; - public Button editReportButton; - public TableView reportedPlayerView; - - private FilteredList filteredItemList; - private ObservableList 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 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 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 statusChoiceBox; + public TextField playerNameFilterTextField; + public TableView reportTableView; + public Button editReportButton; + public TableView reportedPlayerView; + public TextArea chatLogTextArea; + + private FilteredList filteredItemList; + private ObservableList 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 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); + } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 92ff569..65570f1 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -11,8 +11,8 @@ faforever: query-versions-url: https://api.github.com/repos/${faforever.map-generator.repo-and-owner-name}/releases min-supported-version: 1.4.3 vault: - base-url: http://content.faforever.com - replay-download-url-format: ${faforever.vault.base-url}/faf/vault/replay_vault/replay.php?id=%s + base-url: https://content.faforever.com + replay-download-url-format: http://replay.faforever.com/%s environments: "[faforever.com]": base-url: https://api.faforever.com diff --git a/src/main/resources/ui/main_window/report.fxml b/src/main/resources/ui/main_window/report.fxml index aecb498..1022ad2 100644 --- a/src/main/resources/ui/main_window/report.fxml +++ b/src/main/resources/ui/main_window/report.fxml @@ -1,11 +1,21 @@ - - + + + + + + + + + + + + - @@ -37,24 +47,20 @@ HBox.hgrow="ALWAYS"/> - - - - - - - - - - - - - - + + + + + + + + + +