Skip to content
Closed
4 changes: 2 additions & 2 deletions PP.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ Data may be dropped from cache pre-maturely through actions such as removing the

## Removal of Data

Removal of the data can be requested through e-mail at [together.java.tjbot@gmail.com](mailto:together.java.tjbot@gmail.com).
Removal of the data can be requested through e-mail at [contact@togetherjava.org](mailto:contact@togetherjava.org).

For security reasons will we ask you to provide us with proof of ownership of the Server, that you wish the data to be removed of. Only a server owner may request removal of data and requesting it will result in the bot being removed from the Server, if still present on it.

Expand All @@ -121,6 +121,6 @@ This policy is not applicable to any information collected by **bot** instances

## Contact

People may get in contact through e-mail at [together.java.tjbot@gmail.com](mailto:together.java.tjbot@gmail.com), or through **Together Java**'s [official Discord](https://discord.com/invite/XXFUXzK).
People may get in contact through e-mail at [contact@togetherjava.org](mailto:contact@togetherjava.org), or through **Together Java**'s [official Discord](https://discord.com/invite/XXFUXzK).

Other ways of support may be provided but are not guaranteed.
2 changes: 1 addition & 1 deletion TOS.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,6 @@ Any claim related to **Together Java**'s **bot** shall be governed by the laws o

## Contact

People may get in contact through e-mail at [together.java.tjbot@gmail.com](mailto:together.java.tjbot@gmail.com), or through **Together Java**'s [official Discord](https://discord.com/invite/XXFUXzK).
People may get in contact through e-mail at [contact@togetherjava.org](mailto:contact@togetherjava.org), or through **Together Java**'s [official Discord](https://discord.com/invite/XXFUXzK).

Other ways of support may be provided but are not guaranteed.
4 changes: 3 additions & 1 deletion application/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ dependencies {
implementation project(':utils')
implementation project(':formatter')

implementation 'net.dv8tion:JDA:5.6.1'
implementation 'net.dv8tion:JDA:6.5.0'
implementation 'net.java.dev.jna:jna:5.16.0'
implementation 'club.minnced:opus-java:1.1.1'

implementation 'org.apache.logging.log4j:log4j-core:2.26.0'
runtimeOnly 'org.apache.logging.log4j:log4j-slf4j2-impl:2.26.0'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.togetherjava.tjbot.config;

import com.fasterxml.jackson.annotation.JsonProperty;
import net.dv8tion.jda.api.interactions.components.text.TextInput;
import net.dv8tion.jda.api.components.label.Label;

import java.util.Objects;

Expand Down Expand Up @@ -29,10 +29,10 @@ public record RoleApplicationSystemConfig(
Objects.requireNonNull(submissionsChannelPattern);
Objects.requireNonNull(defaultQuestion);

if (defaultQuestion.length() > TextInput.MAX_LABEL_LENGTH) {
if (defaultQuestion.length() > Label.LABEL_MAX_LENGTH) {
throw new IllegalArgumentException(
"defaultQuestion length is too long! Cannot be greater than %d"
.formatted(TextInput.MAX_LABEL_LENGTH));
.formatted(Label.LABEL_MAX_LENGTH));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ public static Collection<Feature> createFeatures(JDA jda, Database database, Con
features.add(new CodeMessageAutoDetection(config, codeMessageHandler));
features.add(new CodeMessageManualDetection(codeMessageHandler));
features.add(new PinnedNotificationRemover(config));
features.add(new QuoteBoardForwarder(config));
features.add(new QuoteBoardForwarder(config, jda));

// Voice receivers
features.add(new DynamicVoiceChat(config, metrics));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package org.togetherjava.tjbot.features;

import net.dv8tion.jda.api.components.buttons.ButtonStyle;
import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
import net.dv8tion.jda.api.events.interaction.component.EntitySelectInteractionEvent;
import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import net.dv8tion.jda.api.interactions.components.ComponentInteraction;
import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle;

import org.togetherjava.tjbot.features.componentids.ComponentIdGenerator;

Expand Down Expand Up @@ -49,8 +49,8 @@ public interface MessageContextCommand extends BotCommand {
* <p>
* Buttons or menus have to be created with a component ID (see
* {@link ComponentInteraction#getComponentId()},
* {@link net.dv8tion.jda.api.interactions.components.buttons.Button#of(ButtonStyle, String, String)})
* in a very specific format, otherwise the core system will fail to identify the command that
* {@link net.dv8tion.jda.api.components.buttons.Button#of(ButtonStyle, String, String)}) in a
* very specific format, otherwise the core system will fail to identify the command that
* corresponded to the button or menu click event and is unable to route it back.
* <p>
* See {@link #acceptComponentIdGenerator(ComponentIdGenerator)} for more info.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.togetherjava.tjbot.features;

import net.dv8tion.jda.api.components.buttons.ButtonStyle;
import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
Expand All @@ -10,7 +11,6 @@
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
import net.dv8tion.jda.api.interactions.components.ComponentInteraction;
import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle;

import org.togetherjava.tjbot.features.componentids.ComponentIdGenerator;

Expand Down Expand Up @@ -82,8 +82,8 @@ public interface SlashCommand extends BotCommand {
* <p>
* Buttons or menus have to be created with a component ID (see
* {@link ComponentInteraction#getComponentId()},
* {@link net.dv8tion.jda.api.interactions.components.buttons.Button#of(ButtonStyle, String, String)})
* in a very specific format, otherwise the core system will fail to identify the command that
* {@link net.dv8tion.jda.api.components.buttons.Button#of(ButtonStyle, String, String)}) in a
* very specific format, otherwise the core system will fail to identify the command that
* corresponded to the button or menu click event and is unable to route it back.
* <p>
* See {@link #acceptComponentIdGenerator(ComponentIdGenerator)} for more info on the ID's.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package org.togetherjava.tjbot.features;

import net.dv8tion.jda.api.components.buttons.ButtonStyle;
import net.dv8tion.jda.api.events.interaction.command.UserContextInteractionEvent;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
import net.dv8tion.jda.api.events.interaction.component.EntitySelectInteractionEvent;
import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import net.dv8tion.jda.api.interactions.components.ComponentInteraction;
import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle;

import org.togetherjava.tjbot.features.componentids.ComponentIdGenerator;

Expand Down Expand Up @@ -50,8 +50,8 @@ public interface UserContextCommand extends BotCommand {
* <p>
* Buttons or menus have to be created with a component ID (see
* {@link ComponentInteraction#getComponentId()},
* {@link net.dv8tion.jda.api.interactions.components.buttons.Button#of(ButtonStyle, String, String)})
* in a very specific format, otherwise the core system will fail to identify the command that
* {@link net.dv8tion.jda.api.components.buttons.Button#of(ButtonStyle, String, String)}) in a
* very specific format, otherwise the core system will fail to identify the command that
* corresponded to the button or menu click event and is unable to route it back.
* <p>
* See {@link #acceptComponentIdGenerator(ComponentIdGenerator)} for more info.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,27 @@
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageReaction;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.entities.emoji.EmojiUnion;
import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent;
import net.dv8tion.jda.api.requests.RestAction;
import net.dv8tion.jda.api.utils.TimeUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.togetherjava.tjbot.config.Config;
import org.togetherjava.tjbot.config.QuoteBoardConfig;
import org.togetherjava.tjbot.features.MessageReceiverAdapter;
import org.togetherjava.tjbot.features.Routine;

import java.time.OffsetDateTime;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import java.util.regex.Pattern;

Expand All @@ -34,9 +40,15 @@
* Key points: - Trigger emoji, minimum vote count and quote-board channel pattern are supplied via
* {@code QuoteBoardConfig}.
*/
public final class QuoteBoardForwarder extends MessageReceiverAdapter {
public final class QuoteBoardForwarder extends MessageReceiverAdapter implements Routine {

private static final Logger logger = LoggerFactory.getLogger(QuoteBoardForwarder.class);
private static final int CLEANUP_INTERVAL_HOURS = 1;
private static final int MAX_MESSAGE_AGE_DAYS = 7;
// MessageId with a Map of Emojis and reacted users
// <MessageId, Map<Emoji, Set<UserId>>>
private final Map<Long, Map<String, Set<Long>>> reactions = new ConcurrentHashMap<>();
Comment on lines +48 to +50

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create a Record to represent what ur doing here and get rid of the 2d-map. do you have two keys or two values? put those into a record respectively.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The initial key is the message ID, then that value is a Map with the emoji as the key, and reacted userIDs as the value

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thats not what i mean. im asking about your access patterns to the map. can you use a Map<MessageAndEmoji, User> or would you need a Map<Message, EmojiAndUser> or neither?

private final JDA jda;
private final Emoji botEmoji;
private final Predicate<String> isQuoteBoardChannelName;
private final QuoteBoardConfig config;
Expand All @@ -47,7 +59,8 @@ public final class QuoteBoardForwarder extends MessageReceiverAdapter {
* @param config the configuration containing settings specific to the cool messages board,
* including the reaction emoji and the pattern to match board channel names
*/
public QuoteBoardForwarder(Config config) {
public QuoteBoardForwarder(Config config, JDA jda) {
this.jda = jda;
this.config = config.getQuoteBoardConfig();
this.botEmoji = Emoji.fromUnicode(this.config.botEmoji());

Expand All @@ -59,6 +72,14 @@ public void onMessageReactionAdd(MessageReactionAddEvent event) {
logger.debug("Received MessageReactionAddEvent: messageId={}, channelId={}, userId={}",
event.getMessageId(), event.getChannel().getId(), event.getUserId());

var messageId = event.getMessageIdLong();
var messageTime = TimeUtil.getTimeCreated(messageId);
if (messageTime.isBefore(OffsetDateTime.now().minusDays(MAX_MESSAGE_AGE_DAYS))) {
logger.debug("Ignoring reaction as message is older than {} days",
MAX_MESSAGE_AGE_DAYS);
return;
}

if (!config.allowChannels().contains(event.getChannel().getName())) {
logger.debug("Skipping as reaction occurred in non-whitelisted channel");
return;
Expand All @@ -82,30 +103,47 @@ public void onMessageReactionAdd(MessageReactionAddEvent event) {
return;
}

event.retrieveMessage().queue(message -> {
if (hasAlreadyForwardedMessage(message)) {
logger.debug("Message has already been forwarded by the bot. Skipping.");
return;
}
var userId = event.getUserIdLong();
var emoji = event.getReaction().getEmoji().getAsReactionCode();
reactions.computeIfAbsent(messageId, _ -> new ConcurrentHashMap<>())
.computeIfAbsent(emoji, _ -> ConcurrentHashMap.newKeySet())
.add(userId);

float emojiScore = calculateMessageScore(message.getReactions());

if (emojiScore < config.minimumScoreToTrigger()) {
return;
}
// check if we've already moved this message...
if (hasBotReactedToMessage(messageId)) {
logger.debug("Message has already been forwarded by the bot. Skipping.");
return;
}
// calculate the overall score of the message reactions
var reactionScore = calcReactionScore(messageId);
if (reactionScore < config.minimumScoreToTrigger())
return;

logger.debug("Attempting to forward message to quote board channel: {}",
boardChannel.getName());
logger.debug("Attempting to forward message to quote board channel: {}",
boardChannel.getName());

markAsProcessed(message).flatMap(_ -> message.forwardTo(boardChannel))
event.retrieveMessage()
.queue(message -> markAsProcessed(message).flatMap(_ -> message.forwardTo(boardChannel))
.queue(_ -> logger.debug("Message forwarded to quote board channel: {}",
boardChannel.getName()),
e -> logger.warn(
"Unknown error while attempting to retrieve and forward message for quote-board, message is ignored.",
e));
});
e)));
}

@Override
public Schedule createSchedule() {
return new Schedule(ScheduleMode.FIXED_RATE, 0, CLEANUP_INTERVAL_HOURS, TimeUnit.HOURS);
}

@Override
public void runRoutine(JDA jda) {
OffsetDateTime cutoff = OffsetDateTime.now().minusDays(MAX_MESSAGE_AGE_DAYS);
reactions.keySet()
.removeIf(messageId -> TimeUtil.getTimeCreated(messageId).isBefore(cutoff));
}


private RestAction<Void> markAsProcessed(Message message) {
return message.addReaction(botEmoji);
}
Expand All @@ -125,7 +163,7 @@ private Optional<TextChannel> findQuoteBoardChannel(JDA jda, long guildId) {
String.format("Guild with ID '%d' not found.", guildId));
}

List<TextChannel> matchingChannels = guild.getTextChannelCache()
List<TextChannel> matchingChannels = guild.getTextChannels()
.stream()
.filter(channel -> isQuoteBoardChannelName.test(channel.getName()))
.toList();
Expand All @@ -139,26 +177,27 @@ private Optional<TextChannel> findQuoteBoardChannel(JDA jda, long guildId) {
return matchingChannels.stream().findFirst();
}

/**
* Checks whether the bot has already reacted to the given message with its marker emoji.
*/
private boolean hasAlreadyForwardedMessage(Message message) {
return message.getReactions()
.stream()
.filter(reaction -> botEmoji.equals(reaction.getEmoji()))
.anyMatch(MessageReaction::isSelf);
private boolean hasBotReactedToMessage(Long messageId) {
Map<String, Set<Long>> messageReactions = reactions.get(messageId);
if (messageReactions == null) {
return false;
}
var emojis = messageReactions.keySet();
return emojis.contains(jda.getSelfUser().getApplicationId());
}

private float calculateMessageScore(List<MessageReaction> reactions) {
return (float) reactions.stream()
.mapToDouble(reaction -> reaction.getCount() * getEmojiScore(reaction.getEmoji()))
.sum();
private float calcReactionScore(Long messageId) {
var reacts = reactions.get(messageId);
if (reacts == null) {
return 0;
}
var scores = new AtomicReference<>(0.0F);
reacts.keySet()
.forEach(emojiCode -> scores.updateAndGet(v -> v + getEmojiScore(emojiCode)));
return scores.get();
}

private float getEmojiScore(EmojiUnion emoji) {
float defaultScore = config.defaultEmojiScore();
String reactionCode = emoji.getAsReactionCode();

return config.emojiScores().getOrDefault(reactionCode, defaultScore);
private float getEmojiScore(String emojiCode) {
return config.emojiScores().getOrDefault(emojiCode, config.defaultEmojiScore());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@

import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.IMentionable;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.RoleIcon;
import net.dv8tion.jda.api.components.actionrow.ActionRow;
import net.dv8tion.jda.api.components.selections.SelectOption;
import net.dv8tion.jda.api.components.selections.StringSelectMenu;
import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionEvent;
Expand All @@ -16,8 +14,6 @@
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import net.dv8tion.jda.api.interactions.components.selections.SelectOption;
import net.dv8tion.jda.api.interactions.components.selections.StringSelectMenu;
import org.jetbrains.annotations.Contract;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -27,11 +23,7 @@
import org.togetherjava.tjbot.features.componentids.Lifespan;
import org.togetherjava.tjbot.features.utils.AmbientColors;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -195,7 +187,7 @@ private void sendRoleSelectionMenu(final CommandInteraction event,
event.getOption(TITLE_OPTION, "Select your roles:", OptionMapping::getAsString);
MessageEmbed embed = createEmbed(title, event.getOption(DESCRIPTION_OPTION).getAsString());

event.replyEmbeds(embed).addActionRow(menu.build()).queue();
event.replyEmbeds(embed).setComponents(ActionRow.of(menu.build())).queue();
}

private static SelectOption mapToSelectOption(Role role) {
Expand Down
Loading
Loading