diff --git a/src/main/java/swiss/fihlon/apus/service/SocialService.java b/src/main/java/swiss/fihlon/apus/service/SocialService.java index ad87819..4c88444 100644 --- a/src/main/java/swiss/fihlon/apus/service/SocialService.java +++ b/src/main/java/swiss/fihlon/apus/service/SocialService.java @@ -53,6 +53,7 @@ public final class SocialService { private final boolean filterSensitive; private final List filterWords; private final Set manuallyHiddenId = new HashSet<>(); + private final Set manuallyHiddenProfile = new HashSet<>(); private List messages = List.of(); public SocialService(@NotNull final TaskScheduler taskScheduler, @@ -64,6 +65,7 @@ public SocialService(@NotNull final TaskScheduler taskScheduler, .map(filterWord -> filterWord.toLowerCase(DEFAULT_LOCALE).trim()) .toList(); loadHiddenMessageIds(); + loadHiddenProfiles(); updateMessages(); updateScheduler = taskScheduler.scheduleAtFixedRate(this::updateMessages, UPDATE_FREQUENCY); } @@ -76,6 +78,7 @@ public void stopUpdateScheduler() { private void updateMessages() { final var newMessages = mastodonAPI.getMessages().stream() .filter(message -> !manuallyHiddenId.contains(message.id())) + .filter(message -> !manuallyHiddenProfile.contains(message.profile())) .filter(message -> !filterSensitive || !message.isSensitive()) .filter(message -> !filterReplies || !message.isReply()) .filter(this::checkWordFilter) @@ -113,7 +116,15 @@ public void hideMessage(@NotNull final Message message) { saveHiddenMessageIds(); } - private Path getHiddenMessagesFilePath() { + public void hideProfile(@NotNull final Message message) { + LOGGER.warn("Hide profile (id={}, profile={}, author={})", + message.id(), message.profile(), message.author()); + messages.remove(message); + manuallyHiddenProfile.add(message.profile()); + saveHiddenProfiles(); + } + + private Path getConfigDir() { final Path configDir = Path.of(System.getProperty("user.home"), ".apus"); if (!configDir.toFile().exists()) { try { @@ -122,11 +133,11 @@ private Path getHiddenMessagesFilePath() { LOGGER.error("Unable to create configuration directory {}: {}", configDir, e.getMessage()); } } - return configDir.resolve("hiddenMessageIds"); + return configDir; } private void saveHiddenMessageIds() { - final var filePath = getHiddenMessagesFilePath(); + final var filePath = getConfigDir().resolve("hiddenMessageIds"); try { Files.writeString(filePath, String.join("\n", manuallyHiddenId)); } catch (final IOException e) { @@ -134,8 +145,17 @@ private void saveHiddenMessageIds() { } } + private void saveHiddenProfiles() { + final var filePath = getConfigDir().resolve("hiddenProfiles"); + try { + Files.writeString(filePath, String.join("\n", manuallyHiddenProfile)); + } catch (final IOException e) { + LOGGER.error("Unable to save hidden profiles to file '{}': {}", filePath, e.getMessage()); + } + } + private void loadHiddenMessageIds() { - final var filePath = getHiddenMessagesFilePath(); + final var filePath = getConfigDir().resolve("hiddenMessageIds"); if (filePath.toFile().exists()) { try { manuallyHiddenId.addAll(Files.readAllLines(filePath)); @@ -146,4 +166,17 @@ private void loadHiddenMessageIds() { LOGGER.info("No previously saved hidden message IDs found."); } } + + private void loadHiddenProfiles() { + final var filePath = getConfigDir().resolve("hiddenProfiles"); + if (filePath.toFile().exists()) { + try { + manuallyHiddenProfile.addAll(Files.readAllLines(filePath)); + } catch (IOException e) { + LOGGER.error("Unable to load hidden profiles from file '{}': {}", filePath, e.getMessage()); + } + } else { + LOGGER.info("No previously saved hidden profiles found."); + } + } } diff --git a/src/main/java/swiss/fihlon/apus/ui/view/MessageView.java b/src/main/java/swiss/fihlon/apus/ui/view/MessageView.java index 212833f..5ddc1d3 100644 --- a/src/main/java/swiss/fihlon/apus/ui/view/MessageView.java +++ b/src/main/java/swiss/fihlon/apus/ui/view/MessageView.java @@ -78,7 +78,8 @@ private Component createAvatarComponent(@NotNull final Message message) { final var avatar = new Avatar(message.author(), message.avatar()); if (!configuration.getAdmin().password().isBlank()) { final var menu = new ContextMenu(); - menu.addItem(getTranslation("social.message.contextmenu.hide"), event -> confirmHideMessage(message)); + menu.addItem(getTranslation("social.message.contextmenu.hide.message"), event -> confirmHideMessage(message)); + menu.addItem(getTranslation("social.message.contextmenu.hide.profile"), event -> confirmHideProfile(message)); menu.setTarget(avatar); } return avatar; @@ -86,14 +87,14 @@ private Component createAvatarComponent(@NotNull final Message message) { private void confirmHideMessage(@NotNull final Message message) { final var dialog = new ConfirmDialog(); - dialog.setHeader(getTranslation("social.message.dialog.hide.confirm.title")); - dialog.setText(getTranslation("social.message.dialog.hide.confirm.text", message.author(), message.date())); + dialog.setHeader(getTranslation("social.message.dialog.hide.message.confirm.title")); + dialog.setText(getTranslation("social.message.dialog.hide.message.confirm.text", message.author(), message.date())); dialog.setCloseOnEsc(true); dialog.setCancelable(true); - dialog.setCancelButton(getTranslation("social.message.dialog.hide.confirm.cancel"), event -> dialog.close()); + dialog.setCancelButton(getTranslation("social.message.dialog.hide.message.confirm.cancel"), event -> dialog.close()); - dialog.setConfirmText(getTranslation("social.message.dialog.hide.confirm.button")); + dialog.setConfirmText(getTranslation("social.message.dialog.hide.message.confirm.button")); dialog.addConfirmListener(event -> { dialog.close(); authorizeHideMessage(message); @@ -104,16 +105,16 @@ private void confirmHideMessage(@NotNull final Message message) { private void authorizeHideMessage(@NotNull final Message message) { final Dialog dialog = new Dialog(); - dialog.setHeaderTitle(getTranslation("social.message.dialog.hide.authorize.title")); + dialog.setHeaderTitle(getTranslation("social.message.dialog.hide.message.authorize.title")); dialog.setCloseOnEsc(true); dialog.setCloseOnOutsideClick(true); final PasswordField passwordField = new PasswordField(); - passwordField.setPlaceholder(getTranslation("social.message.dialog.hide.authorize.password")); + passwordField.setPlaceholder(getTranslation("social.message.dialog.hide.message.authorize.password")); passwordField.setRequired(true); passwordField.setValueChangeMode(ValueChangeMode.EAGER); - final Button hideButton = new Button(getTranslation("social.message.dialog.hide.authorize.button"), event -> { + final Button hideButton = new Button(getTranslation("social.message.dialog.hide.message.authorize.button"), event -> { dialog.close(); hideMessage(message, passwordField.getValue()); }); @@ -121,7 +122,7 @@ private void authorizeHideMessage(@NotNull final Message message) { hideButton.setDisableOnClick(true); hideButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); - final Button cancelButton = new Button(getTranslation("social.message.dialog.hide.authorize.cancel"), event -> dialog.close()); + final Button cancelButton = new Button(getTranslation("social.message.dialog.hide.message.authorize.cancel"), event -> dialog.close()); cancelButton.setDisableOnClick(true); cancelButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY); dialog.getFooter().add(hideButton, cancelButton); @@ -137,9 +138,68 @@ private void hideMessage(@NotNull final Message message, @NotNull final String p if (password.equals(configuration.getAdmin().password())) { socialService.hideMessage(message); removeFromParent(); - Notification.show(getTranslation("social.message.notification.hide.success", message.author())); + Notification.show(getTranslation("social.message.notification.hide.message.success", message.author())); } else { - Notification.show(getTranslation("social.message.notification.hide.rejected")); + Notification.show(getTranslation("social.message.notification.hide.message.rejected")); + } + } + + private void confirmHideProfile(@NotNull final Message message) { + final var dialog = new ConfirmDialog(); + dialog.setHeader(getTranslation("social.message.dialog.hide.profile.confirm.title")); + dialog.setText(getTranslation("social.message.dialog.hide.profile.confirm.text", message.profile(), message.author())); + dialog.setCloseOnEsc(true); + + dialog.setCancelable(true); + dialog.setCancelButton(getTranslation("social.message.dialog.hide.profile.confirm.cancel"), event -> dialog.close()); + + dialog.setConfirmText(getTranslation("social.message.dialog.hide.profile.confirm.button")); + dialog.addConfirmListener(event -> { + dialog.close(); + authorizeHideProfile(message); + }); + + dialog.open(); + } + + private void authorizeHideProfile(@NotNull final Message message) { + final Dialog dialog = new Dialog(); + dialog.setHeaderTitle(getTranslation("social.message.dialog.hide.profile.authorize.title")); + dialog.setCloseOnEsc(true); + dialog.setCloseOnOutsideClick(true); + + final PasswordField passwordField = new PasswordField(); + passwordField.setPlaceholder(getTranslation("social.message.dialog.hide.profile.authorize.password")); + passwordField.setRequired(true); + passwordField.setValueChangeMode(ValueChangeMode.EAGER); + + final Button hideButton = new Button(getTranslation("social.message.dialog.hide.profile.authorize.button"), event -> { + dialog.close(); + hideProfile(message, passwordField.getValue()); + }); + hideButton.setEnabled(false); + hideButton.setDisableOnClick(true); + hideButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + + final Button cancelButton = new Button(getTranslation("social.message.dialog.hide.profile.authorize.cancel"), event -> dialog.close()); + cancelButton.setDisableOnClick(true); + cancelButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + dialog.getFooter().add(hideButton, cancelButton); + + passwordField.addKeyDownListener(event -> hideButton.setEnabled(!passwordField.isEmpty())); + dialog.add(passwordField); + + dialog.open(); + passwordField.focus(); + } + + private void hideProfile(@NotNull final Message message, @NotNull final String password) { + if (password.equals(configuration.getAdmin().password())) { + socialService.hideProfile(message); + removeFromParent(); + Notification.show(getTranslation("social.message.notification.hide.profile.success", message.profile(), message.author())); + } else { + Notification.show(getTranslation("social.message.notification.hide.profile.rejected")); } } diff --git a/src/main/resources/vaadin-i18n/translations.properties b/src/main/resources/vaadin-i18n/translations.properties index 8259c57..1e23d58 100644 --- a/src/main/resources/vaadin-i18n/translations.properties +++ b/src/main/resources/vaadin-i18n/translations.properties @@ -8,14 +8,25 @@ conference.room.empty=closed conference.session.countdown.plural=ends in {0} minutes conference.session.countdown.singular=ends in {0} minute social.heading=Posts with #{0} on Mastodon -social.message.contextmenu.hide="Hide this message" -social.message.dialog.hide.authorize.button=Hide -social.message.dialog.hide.authorize.cancel=Cancel -social.message.dialog.hide.authorize.password=Password -social.message.dialog.hide.authorize.title=Authorize -social.message.dialog.hide.confirm.button=Hide -social.message.dialog.hide.confirm.cancel=Cancel -social.message.dialog.hide.confirm.text=Do you really want to hide this message from {0} posted at {1}? -social.message.dialog.hide.confirm.title=Confirm -social.message.notification.hide.rejected=You are not authorized to hide messages! -social.message.notification.hide.success=This message from {0} was hidden as requested. +social.message.contextmenu.hide.message=Hide this message +social.message.dialog.hide.message.authorize.button=Hide +social.message.dialog.hide.message.authorize.cancel=Cancel +social.message.dialog.hide.message.authorize.password=Password +social.message.dialog.hide.message.authorize.title=Authorize +social.message.dialog.hide.message.confirm.button=Hide +social.message.dialog.hide.message.confirm.cancel=Cancel +social.message.dialog.hide.message.confirm.text=Do you really want to hide this message from {0} posted at {1}? +social.message.dialog.hide.message.confirm.title=Confirm +social.message.notification.hide.message.rejected=You are not authorized to hide messages! +social.message.notification.hide.message.success=This message from {0} was hidden as requested. +social.message.contextmenu.hide.profile=Hide this profile +social.message.dialog.hide.profile.authorize.button=Hide +social.message.dialog.hide.profile.authorize.cancel=Cancel +social.message.dialog.hide.profile.authorize.password=Password +social.message.dialog.hide.profile.authorize.title=Authorize +social.message.dialog.hide.profile.confirm.button=Hide +social.message.dialog.hide.profile.confirm.cancel=Cancel +social.message.dialog.hide.profile.confirm.text=Do you really want to hide the profile {0} ({1})? +social.message.dialog.hide.profile.confirm.title=Confirm +social.message.notification.hide.profile.rejected=You are not authorized to hide profiles! +social.message.notification.hide.profile.success=The profile {0} ({1}) was hidden as requested. diff --git a/src/main/resources/vaadin-i18n/translations_de.properties b/src/main/resources/vaadin-i18n/translations_de.properties index 3bd8a92..1ed982b 100644 --- a/src/main/resources/vaadin-i18n/translations_de.properties +++ b/src/main/resources/vaadin-i18n/translations_de.properties @@ -8,14 +8,25 @@ conference.room.empty=geschlossen conference.session.countdown.plural=endet in {0} Minuten conference.session.countdown.singular=endet in {0} Minute social.heading=Schreibe einen Beitrag mit #{0} auf Mastodon -social.message.contextmenu.hide=Diesen Beitrag ausblenden -social.message.dialog.hide.authorize.button=Ausblenden -social.message.dialog.hide.authorize.cancel=Abbrechen -social.message.dialog.hide.authorize.password=Passwort -social.message.dialog.hide.authorize.title=Authorisieren -social.message.dialog.hide.confirm.button=Ausblenden -social.message.dialog.hide.confirm.cancel=Abbrechen -social.message.dialog.hide.confirm.text=Möchtest du diesen am {1} geschriebenen Beitrag von {0} ausblenden? -social.message.dialog.hide.confirm.title=Bestätigen -social.message.notification.hide.rejected=Du bist nicht authorisiert, Beiträge auszublenden! -social.message.notification.hide.success=Dieser Beitrag von {0} wurde wie gewünscht ausgeblendet. +social.message.contextmenu.hide.message=Diesen Beitrag ausblenden +social.message.dialog.hide.message.authorize.button=Ausblenden +social.message.dialog.hide.message.authorize.cancel=Abbrechen +social.message.dialog.hide.message.authorize.password=Passwort +social.message.dialog.hide.message.authorize.title=Authorisieren +social.message.dialog.hide.message.confirm.button=Ausblenden +social.message.dialog.hide.message.confirm.cancel=Abbrechen +social.message.dialog.hide.message.confirm.text=Möchtest du diesen am {1} geschriebenen Beitrag von {0} ausblenden? +social.message.dialog.hide.message.confirm.title=Bestätigen +social.message.notification.hide.message.rejected=Du bist nicht authorisiert, Beiträge auszublenden! +social.message.notification.hide.message.success=Dieser Beitrag von {0} wurde wie gewünscht ausgeblendet. +social.message.contextmenu.hide.profile=Dieses Profil ausblenden +social.message.dialog.hide.profile.authorize.button=Ausblenden +social.message.dialog.hide.profile.authorize.cancel=Abbrechen +social.message.dialog.hide.profile.authorize.password=Passwort +social.message.dialog.hide.profile.authorize.title=Authorisieren +social.message.dialog.hide.profile.confirm.button=Ausblenden +social.message.dialog.hide.profile.confirm.cancel=Abbrechen +social.message.dialog.hide.profile.confirm.text=Möchtest du das Profil {1} von {0} ausblenden? +social.message.dialog.hide.profile.confirm.title=Bestätigen +social.message.notification.hide.profile.rejected=Du bist nicht authorisiert, Profile auszublenden! +social.message.notification.hide.profile.success=Das Profile {0} von {1} wurde wie gewünscht ausgeblendet.