Skip to content

Commit

Permalink
✨ manually hide unwanted social media messages closes #9
Browse files Browse the repository at this point in the history
  • Loading branch information
McPringle committed Mar 31, 2024
1 parent f22acbc commit 085c59f
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 4 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ You can now also attach breakpoints in code for debugging purposes, by clicking

| Variable | Default | Description |
|-------------------|------------------------------------|-----------------------------------------------------------------|
| ADMIN_PASSWORD | | The password to get admin access (empty = disabled). |
| DOAG_EVENT_ID | 0 | The ID of the DOAG event to read the conference agenda. |
| FILTER_REPLIES | true | Hide social media messages which are replies. |
| FILTER_SENSITIVE | true | Hide social media messages which contain sensitive information. |
Expand Down
22 changes: 22 additions & 0 deletions src/main/java/swiss/fihlon/apus/configuration/Admin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Apus - A social wall for conferences with additional features.
* Copyright (C) Marcus Fihlon and the individual contributors to Apus.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package swiss.fihlon.apus.configuration;

import org.jetbrains.annotations.NotNull;

public record Admin(@NotNull String password) { }
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
public class Configuration {

private String version;
private Admin admin;
private DOAG doag;
private Mastodon mastodon;
private Filter filter;
Expand All @@ -39,6 +40,14 @@ public void setVersion(@NotNull final String version) {
this.version = version;
}

public Admin getAdmin() {
return admin;
}

public void setAdmin(@NotNull final Admin admin) {
this.admin = admin;
}

public DOAG getDoag() {
return doag;
}
Expand Down
16 changes: 15 additions & 1 deletion src/main/java/swiss/fihlon/apus/service/SocialService.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,30 +20,37 @@
import jakarta.annotation.PreDestroy;
import org.jetbrains.annotations.NotNull;
import org.jsoup.Jsoup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.stereotype.Service;
import swiss.fihlon.apus.configuration.Configuration;
import swiss.fihlon.apus.social.Message;
import swiss.fihlon.apus.social.mastodon.MastodonAPI;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;

@Service
public final class SocialService {

private static final Duration UPDATE_FREQUENCY = Duration.ofMinutes(1);
private static final Locale DEFAULT_LOCALE = Locale.getDefault();
private static final Logger LOGGER = LoggerFactory.getLogger(SocialService.class);

private final ScheduledFuture<?> updateScheduler;
private final MastodonAPI mastodonAPI;
private final String hashtag;
private final boolean filterReplies;
private final boolean filterSensitive;
private final List<String> filterWords;
private final Set<String> manuallyHiddenId = new HashSet<>();
private List<Message> messages = List.of();

public SocialService(@NotNull final TaskScheduler taskScheduler,
Expand All @@ -66,12 +73,13 @@ public void stopUpdateScheduler() {

private void updateMessages() {
final var newMessages = mastodonAPI.getMessages(hashtag).stream()
.filter(message -> !manuallyHiddenId.contains(message.id()))
.filter(message -> !filterSensitive || !message.isSensitive())
.filter(message -> !filterReplies || !message.isReply())
.filter(this::checkWordFilter)
.toList();
synchronized (this) {
messages = newMessages;
messages = new ArrayList<>(newMessages);
}
}

Expand All @@ -95,4 +103,10 @@ public List<Message> getMessages(final int limit) {
}
}

public void hideMessage(@NotNull final Message message) {
LOGGER.warn("Hiding message (id={}, profile={}, author={})",
message.id(), message.profile(), message.author());
messages.remove(message);
manuallyHiddenId.add(message.id());
}
}
87 changes: 85 additions & 2 deletions src/main/java/swiss/fihlon/apus/ui/view/MessageView.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,39 @@
import com.vaadin.flow.component.Text;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.avatar.Avatar;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.confirmdialog.ConfirmDialog;
import com.vaadin.flow.component.contextmenu.ContextMenu;
import com.vaadin.flow.component.dependency.CssImport;
import com.vaadin.flow.component.dialog.Dialog;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.Footer;
import com.vaadin.flow.component.html.Header;
import com.vaadin.flow.component.html.Image;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.textfield.PasswordField;
import com.vaadin.flow.data.value.ValueChangeMode;
import org.jetbrains.annotations.NotNull;
import org.jsoup.Jsoup;
import org.ocpsoft.prettytime.PrettyTime;
import swiss.fihlon.apus.configuration.Configuration;
import swiss.fihlon.apus.service.SocialService;
import swiss.fihlon.apus.social.Message;

@CssImport(value = "./themes/apus/views/message-view.css")
public final class MessageView extends Div {

private static final int MAX_LENGTH = 500;
private static final String TRUNC_INDICATOR = " […]";
private final transient SocialService socialService;
private final transient Configuration configuration;

public MessageView(@NotNull final Message message) {
public MessageView(@NotNull final Message message,
@NotNull final SocialService socialService,
@NotNull final Configuration configuration) {
this.socialService = socialService;
this.configuration = configuration;
setId("message-" + message.id());
addClassName("message-view");
add(createHeaderComponent(message));
add(createTextComponent(message));
Expand All @@ -47,7 +63,7 @@ public MessageView(@NotNull final Message message) {
}

@NotNull Component createHeaderComponent(@NotNull final Message message) {
final var avatar = new Avatar(message.author(), message.avatar());
final var avatar = createAvatarComponent(message);
final var author = new Div(new Text(message.author()));
author.addClassName("author");
final var profile = new Div(new Text(message.profile()));
Expand All @@ -57,6 +73,73 @@ public MessageView(@NotNull final Message message) {
return new Header(avatar, authorContainer);
}

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("Hide", event -> confirmHideMessage(message));
menu.setTarget(avatar);
}
return avatar;
}

private void confirmHideMessage(@NotNull final Message message) {
final var dialog = new ConfirmDialog();
dialog.setHeader("Confirm hide message");
dialog.setText(String.format("Do you really want to hide the message from %s posted at %s?", message.author(), message.date()));
dialog.setCloseOnEsc(true);

dialog.setCancelable(true);
dialog.addCancelListener(event -> dialog.close());

dialog.setConfirmText("Hide");
dialog.addConfirmListener(event -> {
dialog.close();
authorizeHideMessage(message);
});

dialog.open();
}

private void authorizeHideMessage(@NotNull final Message message) {
final Dialog dialog = new Dialog();
dialog.setHeaderTitle("Authorize hide message");
dialog.setCloseOnEsc(true);
dialog.setCloseOnOutsideClick(true);

final PasswordField passwordField = new PasswordField();
passwordField.setPlaceholder("password");
passwordField.setRequired(true);
passwordField.setValueChangeMode(ValueChangeMode.EAGER);

final Button hideButton = new Button("Hide", event -> {
dialog.close();
hideMessage(message, passwordField.getValue());
});
hideButton.setEnabled(false);
hideButton.setDisableOnClick(true);

final Button cancelButton = new Button("Cancel", event -> dialog.close());
cancelButton.setDisableOnClick(true);
dialog.getFooter().add(hideButton, cancelButton);

passwordField.addKeyDownListener(event -> hideButton.setEnabled(!passwordField.isEmpty()));
dialog.add(passwordField);

dialog.open();
passwordField.focus();
}

private void hideMessage(@NotNull final Message message, @NotNull final String password) {
if (password.equals(configuration.getAdmin().password())) {
socialService.hideMessage(message);
removeFromParent();
Notification.show("The message was hidden as requested.");
} else {
Notification.show("You are not authorized to hide messages!");
}
}

@NotNull
private Component createTextComponent(@NotNull final Message message) {
final String messageText = Jsoup.parse(message.html()).text();
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/swiss/fihlon/apus/ui/view/SocialView.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@ public final class SocialView extends Div {
private static final Duration UPDATE_FREQUENCY = Duration.ofMinutes(1);

private final transient SocialService socialService;
private final transient Configuration configuration;
private final Div messageContainer = new Div();

public SocialView(@NotNull final SocialService socialService,
@NotNull final TaskScheduler taskScheduler,
@NotNull final Configuration configuration) {
this.socialService = socialService;
this.configuration = configuration;

setId("social-view");
add(new H2(getTranslation("social.heading", configuration.getMastodon().hashtag())));
Expand All @@ -58,7 +60,7 @@ private void updateScheduler() {
private void updateMessages() {
messageContainer.removeAll();
for (final Message message : socialService.getMessages(30)) {
messageContainer.add(new MessageView(message));
messageContainer.add(new MessageView(message, socialService, configuration));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,10 @@
"name": "apus.filter.words",
"type": "java.lang.String",
"description": "Hide social media messages which contain these words."
},
{
"name": "apus.admin.password",
"type": "java.lang.String",
"description": "The password to get admin access (empty = disabled)."
}
] }
1 change: 1 addition & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ vaadin.launch-browser=false
vaadin.frontend.hotdeploy=true

apus.version=@project.version@
apus.admin.password=${ADMIN_PASSWORD:}
apus.doag.eventId=${DOAG_EVENT_ID:0}
apus.mastodon.instance=${MASTODON_INSTANCE:mastodon.social}
apus.mastodon.hashtag=${MASTODON_HASHTAG:java}
Expand Down

0 comments on commit 085c59f

Please sign in to comment.