Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions application/config.json.template
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"discordGuildInvite": "https://discord.com/invite/XXFUXzK",
"modAuditLogChannelPattern": "mod-audit-log",
"modMailChannelPattern": "modmail",
"projectsChannelPattern": "projects"
"mutedRolePattern": "Muted",
"heavyModerationRolePattern": "Moderator",
"softModerationRolePattern": "Moderator|Community Ambassador",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public final class Config {
private final String discordGuildInvite;
private final String modAuditLogChannelPattern;
private final String modMailChannelPattern;
private final String projectsChannelPattern;
private final String mutedRolePattern;
private final String heavyModerationRolePattern;
private final String softModerationRolePattern;
Expand Down Expand Up @@ -58,6 +59,8 @@ private Config(@JsonProperty(value = "token", required = true) String token,
required = true) String modAuditLogChannelPattern,
@JsonProperty(value = "modMailChannelPattern",
required = true) String modMailChannelPattern,
@JsonProperty(value = "projectsChannelPattern",
required = true) String projectsChannelPattern,
@JsonProperty(value = "mutedRolePattern", required = true) String mutedRolePattern,
@JsonProperty(value = "heavyModerationRolePattern",
required = true) String heavyModerationRolePattern,
Expand Down Expand Up @@ -103,6 +106,7 @@ private Config(@JsonProperty(value = "token", required = true) String token,
this.discordGuildInvite = Objects.requireNonNull(discordGuildInvite);
this.modAuditLogChannelPattern = Objects.requireNonNull(modAuditLogChannelPattern);
this.modMailChannelPattern = Objects.requireNonNull(modMailChannelPattern);
this.projectsChannelPattern = Objects.requireNonNull(projectsChannelPattern);
this.mutedRolePattern = Objects.requireNonNull(mutedRolePattern);
this.heavyModerationRolePattern = Objects.requireNonNull(heavyModerationRolePattern);
this.softModerationRolePattern = Objects.requireNonNull(softModerationRolePattern);
Expand Down Expand Up @@ -170,6 +174,16 @@ public String getModMailChannelPattern() {
return modMailChannelPattern;
}

/**
* Gets the REGEX pattern used to identify the channel that is supposed to contain information
* about user projects
*
* @return the channel name pattern
*/
public String getProjectsChannelPattern() {
return projectsChannelPattern;
}

/**
* Gets the token of the Discord bot to connect this application to.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
import org.togetherjava.tjbot.features.moderation.scam.ScamHistoryPurgeRoutine;
import org.togetherjava.tjbot.features.moderation.scam.ScamHistoryStore;
import org.togetherjava.tjbot.features.moderation.temp.TemporaryModerationRoutine;
import org.togetherjava.tjbot.features.projects.ProjectsThreadCreatedListener;
import org.togetherjava.tjbot.features.reminder.RemindRoutine;
import org.togetherjava.tjbot.features.reminder.ReminderCommand;
import org.togetherjava.tjbot.features.system.BotCore;
Expand Down Expand Up @@ -157,6 +158,7 @@ public static Collection<Feature> createFeatures(JDA jda, Database database, Con
features.add(new LeftoverBookmarksListener(bookmarksSystem));
features.add(new HelpThreadCreatedListener(helpSystemHelper));
features.add(new HelpThreadLifecycleListener(helpSystemHelper, database));
features.add(new ProjectsThreadCreatedListener(config));

// Message context commands
features.add(new TransferQuestionCommand(config, chatGptService));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.togetherjava.tjbot.features.projects;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import net.dv8tion.jda.api.entities.channel.Channel;
import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;

import org.togetherjava.tjbot.config.Config;
import org.togetherjava.tjbot.features.EventReceiver;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;

/**
* Listens for new threads being created in the "projects" forum and pins the first message. *
* {@link Config#getProjectsChannelPattern()}.
*/
public final class ProjectsThreadCreatedListener extends ListenerAdapter implements EventReceiver {
private final String configProjectsChannelPattern;
private final Cache<Long, Instant> threadIdToCreatedAtCache = Caffeine.newBuilder()
.maximumSize(1_000)
.expireAfterAccess(2, TimeUnit.of(ChronoUnit.MINUTES))
.build();

public ProjectsThreadCreatedListener(Config config) {
configProjectsChannelPattern = config.getProjectsChannelPattern();
}

@Override
public void onMessageReceived(MessageReceivedEvent event) {
if (event.isFromThread()) {
ThreadChannel threadChannel = event.getChannel().asThreadChannel();
Channel parentChannel = threadChannel.getParentChannel();
boolean isPost = isPostMessage(threadChannel);

if (parentChannel.getName().equals(configProjectsChannelPattern) && isPost) {
handleProjectThread(event);
}
}
}

private boolean wasThreadAlreadyHandled(long threadChannelId) {
Instant now = Instant.now();
Instant createdAt = threadIdToCreatedAtCache.get(threadChannelId, any -> now);
return createdAt != now;
}

private boolean isPostMessage(ThreadChannel threadChannel) {
int messageCount = threadChannel.getMessageCount();
return messageCount <= 1 && !wasThreadAlreadyHandled(threadChannel.getIdLong());
}

private void handleProjectThread(MessageReceivedEvent event) {
// Pin the first message in the thread
event.getMessage().pin().queue();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* This packages offers all the functionality for the projects channel. The core class is
* {@link org.togetherjava.tjbot.features.projects.ProjectsThreadCreatedListener}.
*/
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package org.togetherjava.tjbot.features.projects;

import org.togetherjava.tjbot.annotations.MethodsReturnNonnullByDefault;

import javax.annotation.ParametersAreNonnullByDefault;