Skip to content

Commit

Permalink
♻️ refactor Message to Post closes #95
Browse files Browse the repository at this point in the history
  • Loading branch information
McPringle committed Apr 18, 2024
1 parent 3532d4d commit 0e8a4e9
Show file tree
Hide file tree
Showing 11 changed files with 131 additions and 131 deletions.
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,10 @@ You can now also attach breakpoints in code for debugging purposes, by clicking
|-------------------------|---------|------------------------------------------------------------------------|
| ADMIN_PASSWORD | | The hashed password to get admin access (empty = disabled). |
| DOAG_EVENT_ID | 0 | The ID of the DOAG event to read the conference agenda (0 = disabled). |
| FILTER_LENGTH | 500 | Hide social media messages which exceed this length (0 = disabled). |
| FILTER_REPLIES | true | Hide social media messages which are replies. |
| FILTER_SENSITIVE | true | Hide social media messages which contain sensitive information. |
| FILTER_WORDS | | Hide social media messages which contain these words. |
| FILTER_LENGTH | 500 | Hide social media posts which exceed this length (0 = disabled). |
| FILTER_REPLIES | true | Hide social media posts which are replies. |
| FILTER_SENSITIVE | true | Hide social media posts which contain sensitive information. |
| FILTER_WORDS | | Hide social media posts which contain these words. |
| MASTODON_INSTANCE | | The Mastodon instance used to read the posts from (empty = disabled). |
| MASTODON_HASHTAG | | The hashtag for the mastodon wall (empty = disabled). |
| MASTODON_IMAGES_ENABLED | true | Enable or disable images in mastodon posts. |
Expand All @@ -119,10 +119,10 @@ Hashed password for Docker Compose file: $$2a$$10$$nybQbl/iY8SRJkfHJVncS.L5.OC3K

All configuration files are completely optional and stored in an `.apus` subdirectory of the home directory of the user running *Apus*.

| File | Description |
|--------------------|----------------------------------------------------------|
| `blockedProfiles` | This file contains blocked profiles, one per line. |
| `hiddenMessageIds` | This file contains IDs of hidden messages, one per line. |
| File | Description |
|-------------------|-------------------------------------------------------|
| `blockedProfiles` | This file contains blocked profiles, one per line. |
| `hiddenPostIds` | This file contains IDs of hidden posts, one per line. |

## Production

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,33 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

.message-view {
.post-view {
background-color: #84ddee;
border-radius: var(--lumo-border-radius-s);
padding: var(--lumo-space-s);
}

.message-view img {
.post-view img {
max-width: 100%;
max-height: 430px;
border-radius: var(--lumo-border-radius-s);
}

.message-view .invisible {
.post-view .invisible {
display: none;
}

.message-view header .author-container {
.post-view header .author-container {
display: inline-block;
line-height: var(--lumo-line-height-s);
margin-left: var(--lumo-space-s);
}

.message-view header .author {
.post-view header .author {
font-weight: bold;
}

.message-view .datetime {
.post-view .datetime {
font-size: var(--lumo-font-size-xs);
font-style: italic;
text-align: right;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@
*/
package swiss.fihlon.apus.plugin.social;

import swiss.fihlon.apus.social.Message;
import swiss.fihlon.apus.social.Post;

import java.util.List;

public interface SocialPlugin {

boolean isEnabled();

List<Message> getMessages();
List<Post> getPosts();

}
82 changes: 41 additions & 41 deletions src/main/java/swiss/fihlon/apus/plugin/social/SocialService.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
import org.springframework.stereotype.Service;
import swiss.fihlon.apus.configuration.Configuration;
import swiss.fihlon.apus.plugin.social.mastodon.MastodonPlugin;
import swiss.fihlon.apus.social.Message;
import swiss.fihlon.apus.social.Post;
import swiss.fihlon.apus.util.HtmlUtil;

import java.io.IOException;
Expand Down Expand Up @@ -54,9 +54,9 @@ public final class SocialService {
private final boolean filterReplies;
private final boolean filterSensitive;
private final List<String> filterWords;
private final Set<String> hiddenMessages = new HashSet<>();
private final Set<String> hiddenPosts = new HashSet<>();
private final Set<String> blockedProfiles = new HashSet<>();
private List<Message> messages = List.of();
private List<Post> posts = List.of();

public SocialService(@NotNull final TaskScheduler taskScheduler,
@NotNull final Configuration configuration) {
Expand All @@ -67,11 +67,11 @@ public SocialService(@NotNull final TaskScheduler taskScheduler,
filterWords = configuration.getFilter().words().stream()
.map(filterWord -> filterWord.toLowerCase(DEFAULT_LOCALE).trim())
.toList();
loadHiddenMessageIds();
loadHiddenPostIds();
loadBlockedProfiles();
if (socialPlugin.isEnabled()) {
updateMessages();
updateScheduler = taskScheduler.scheduleAtFixedRate(this::updateMessages, UPDATE_FREQUENCY);
updatePosts();
updateScheduler = taskScheduler.scheduleAtFixedRate(this::updatePosts, UPDATE_FREQUENCY);
} else {
LOGGER.warn("No social plugin is enabled. No posts will be displayed.");
updateScheduler = null;
Expand All @@ -83,53 +83,53 @@ public void stopUpdateScheduler() {
updateScheduler.cancel(true);
}

private void updateMessages() {
final var newMessages = socialPlugin.getMessages().stream()
.filter(message -> !hiddenMessages.contains(message.id()))
.filter(message -> !blockedProfiles.contains(message.profile()))
.filter(message -> !filterSensitive || !message.isSensitive())
.filter(message -> !filterReplies || !message.isReply())
.filter(message -> filterLength <= 0 || HtmlUtil.extractText(message.html()).length() <= filterLength)
private void updatePosts() {
final var newPosts = socialPlugin.getPosts().stream()
.filter(post -> !hiddenPosts.contains(post.id()))
.filter(post -> !blockedProfiles.contains(post.profile()))
.filter(post -> !filterSensitive || !post.isSensitive())
.filter(post -> !filterReplies || !post.isReply())
.filter(post -> filterLength <= 0 || HtmlUtil.extractText(post.html()).length() <= filterLength)
.filter(this::checkWordFilter)
.toList();
synchronized (this) {
messages = new ArrayList<>(newMessages);
posts = new ArrayList<>(newPosts);
}
}

private boolean checkWordFilter(@NotNull final Message message) {
final String messageText = Jsoup.parse(message.html()).text().toLowerCase(DEFAULT_LOCALE);
private boolean checkWordFilter(@NotNull final Post post) {
final String postText = Jsoup.parse(post.html()).text().toLowerCase(DEFAULT_LOCALE);
for (final String filterWord : filterWords) {
if (messageText.contains(filterWord)) {
if (postText.contains(filterWord)) {
return false;
}
}
return true;
}

public List<Message> getMessages(final int limit) {
public List<Post> getPosts(final int limit) {
synchronized (this) {
if (limit <= 0 || messages.isEmpty()) {
return Collections.unmodifiableList(messages);
if (limit <= 0 || posts.isEmpty()) {
return Collections.unmodifiableList(posts);
}
final int toIndex = limit < messages.size() ? limit : messages.size() - 1;
return Collections.unmodifiableList(messages.subList(0, toIndex));
final int toIndex = limit < posts.size() ? limit : posts.size() - 1;
return Collections.unmodifiableList(posts.subList(0, toIndex));
}
}

public void hideMessage(@NotNull final Message message) {
LOGGER.warn("Hiding message (id={}, profile={}, author={})",
message.id(), message.profile(), message.author());
messages.remove(message);
hiddenMessages.add(message.id());
saveHiddenMessageIds();
public void hidePost(@NotNull final Post post) {
LOGGER.warn("Hiding post (id={}, profile={}, author={})",
post.id(), post.profile(), post.author());
posts.remove(post);
hiddenPosts.add(post.id());
saveHiddenPostIds();
}

public void hideProfile(@NotNull final Message message) {
public void hideProfile(@NotNull final Post post) {
LOGGER.warn("Hide profile (id={}, profile={}, author={})",
message.id(), message.profile(), message.author());
messages.remove(message);
blockedProfiles.add(message.profile());
post.id(), post.profile(), post.author());
posts.remove(post);
blockedProfiles.add(post.profile());
saveBlockedProfiles();
}

Expand All @@ -145,12 +145,12 @@ private Path getConfigDir() {
return configDir;
}

private void saveHiddenMessageIds() {
final var filePath = getConfigDir().resolve("hiddenMessages");
private void saveHiddenPostIds() {
final var filePath = getConfigDir().resolve("hiddenPosts");
try {
Files.writeString(filePath, String.join("\n", hiddenMessages));
Files.writeString(filePath, String.join("\n", hiddenPosts));
} catch (final IOException e) {
LOGGER.error("Unable to save hidden messages to file '{}': {}", filePath, e.getMessage());
LOGGER.error("Unable to save hidden posts to file '{}': {}", filePath, e.getMessage());
}
}

Expand All @@ -163,16 +163,16 @@ private void saveBlockedProfiles() {
}
}

private void loadHiddenMessageIds() {
final var filePath = getConfigDir().resolve("hiddenMessages");
private void loadHiddenPostIds() {
final var filePath = getConfigDir().resolve("hiddenPosts");
if (filePath.toFile().exists()) {
try {
hiddenMessages.addAll(Files.readAllLines(filePath));
hiddenPosts.addAll(Files.readAllLines(filePath));
} catch (IOException e) {
LOGGER.error("Unable to load hidden messages from file '{}': {}", filePath, e.getMessage());
LOGGER.error("Unable to load hidden posts from file '{}': {}", filePath, e.getMessage());
}
} else {
LOGGER.info("No previously saved hidden messages found.");
LOGGER.info("No previously saved hidden posts found.");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import social.bigbone.api.entity.Status;
import swiss.fihlon.apus.configuration.Configuration;
import swiss.fihlon.apus.plugin.social.SocialPlugin;
import swiss.fihlon.apus.social.Message;
import swiss.fihlon.apus.social.Post;

import java.time.Instant;
import java.time.LocalDateTime;
Expand Down Expand Up @@ -61,27 +61,27 @@ public boolean isEnabled() {
}

@Override
public List<Message> getMessages() {
public List<Post> getPosts() {
try {
LOGGER.info("Starting download of messages with hashtag '{}' from instance '{}'", hashtag, instance);
LOGGER.info("Starting download of posts with hashtag '{}' from instance '{}'", hashtag, instance);
final MastodonClient client = new MastodonClient.Builder(instance).build();
final Range range = new Range(null, null, null, 100);
final Pageable<Status> statuses = client.timelines().getTagTimeline(hashtag, LOCAL_AND_REMOTE, range).execute();
final List<Message> messages = statuses.getPart().stream()
.map(this::convertToMessage)
final List<Post> posts = statuses.getPart().stream()
.map(this::convertToPost)
.sorted()
.toList()
.reversed();
LOGGER.info("Successfully downloaded {} messages with hashtag '{}' from instance '{}'", messages.size(), hashtag, instance);
return messages;
LOGGER.info("Successfully downloaded {} posts with hashtag '{}' from instance '{}'", posts.size(), hashtag, instance);
return posts;
} catch (final Exception e) {
LOGGER.error("Unable to load statuses with hashtag '{}' from Mastodon instance '{}': {}",
LOGGER.error("Unable to load posts with hashtag '{}' from Mastodon instance '{}': {}",
hashtag, instance, e.getMessage());
return List.of();
}
}

private Message convertToMessage(@NotNull final Status status) {
private Post convertToPost(@NotNull final Status status) {
final String id = status.getId();
final Account account = status.getAccount();
final Instant instant = status.getCreatedAt().mostPreciseOrFallback(Instant.MIN);
Expand All @@ -95,7 +95,7 @@ private Message convertToMessage(@NotNull final Status status) {
final boolean isReply = inReplyToId != null && !inReplyToId.isBlank();
final boolean isSensitive = status.isSensitive();

return new Message(id, date, author, avatar, profile, html, images, isReply, isSensitive);
return new Post(id, date, author, avatar, profile, html, images, isReply, isSensitive);
}

@NotNull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@
import java.time.LocalDateTime;
import java.util.List;

public record Message(@NotNull String id, @NotNull LocalDateTime date,
@NotNull String author, @NotNull String avatar, @NotNull String profile,
@NotNull String html, @NotNull List<String> images,
boolean isReply, boolean isSensitive)
implements Comparable<Message> {
public record Post(@NotNull String id, @NotNull LocalDateTime date,
@NotNull String author, @NotNull String avatar, @NotNull String profile,
@NotNull String html, @NotNull List<String> images,
boolean isReply, boolean isSensitive)
implements Comparable<Post> {
@Override
public int compareTo(@NotNull final Message other) {
public int compareTo(@NotNull final Post other) {
final int dateCompareResult = date.compareTo(other.date);
return dateCompareResult == 0 ? id.compareTo(other.id) : dateCompareResult;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,56 +29,56 @@
import com.vaadin.flow.component.html.Image;
import org.jetbrains.annotations.NotNull;
import org.ocpsoft.prettytime.PrettyTime;
import swiss.fihlon.apus.social.Message;
import swiss.fihlon.apus.social.Post;
import swiss.fihlon.apus.util.HtmlUtil;

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

public MessageView(@NotNull final Message message) {
setId("message-" + message.id());
addClassName("message-view");
add(createHeaderComponent(message));
add(createTextComponent(message));
add(createImageComponents(message));
add(createDateTimeComponent(message));
public PostView(@NotNull final Post post) {
setId("post-" + post.id());
addClassName("post-view");
add(createHeaderComponent(post));
add(createTextComponent(post));
add(createImageComponents(post));
add(createDateTimeComponent(post));
}

@NotNull Component createHeaderComponent(@NotNull final Message message) {
final var avatar = createAvatarComponent(message);
final var author = new Div(new Text(message.author()));
@NotNull Component createHeaderComponent(@NotNull final Post post) {
final var avatar = createAvatarComponent(post);
final var author = new Div(new Text(post.author()));
author.addClassName("author");
final var profile = new Div(new Text(message.profile()));
final var profile = new Div(new Text(post.profile()));
profile.addClassName("profile");
final var authorContainer = new Div(author, profile);
authorContainer.addClassName("author-container");
return new Header(avatar, authorContainer);
}

private Component createAvatarComponent(@NotNull final Message message) {
return new Avatar(message.author(), message.avatar());
private Component createAvatarComponent(@NotNull final Post post) {
return new Avatar(post.author(), post.avatar());
}

@NotNull
private Component createTextComponent(@NotNull final Message message) {
final String unsafeHtml = message.html();
private Component createTextComponent(@NotNull final Post post) {
final String unsafeHtml = post.html();
final String saveHtml = HtmlUtil.sanitize(unsafeHtml);
return new Html(String.format("<div class=\"content\">%s</div>", saveHtml));
}

@NotNull
private Component[] createImageComponents(@NotNull final Message message) {
return message.images().stream()
private Component[] createImageComponents(@NotNull final Post post) {
return post.images().stream()
.map(image -> new Image(image, image))
.toArray(Image[]::new);
}

@NotNull
private Component createDateTimeComponent(@NotNull final Message message) {
private Component createDateTimeComponent(@NotNull final Post post) {
final var dateTimeComponent = new Footer();
dateTimeComponent.addClassName("datetime");
final var prettyTime = new PrettyTime(UI.getCurrent().getLocale());
dateTimeComponent.add(new Text(prettyTime.format(message.date())));
dateTimeComponent.add(new Text(prettyTime.format(post.date())));
return dateTimeComponent;
}
}
Loading

0 comments on commit 0e8a4e9

Please sign in to comment.