Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,17 @@ public static void main(final String[] args) {
}

Path configPath = Path.of(args.length == 1 ? args[0] : DEFAULT_CONFIG_PATH);
Config config;
try {
Config.load(configPath);
config = Config.load(configPath);
} catch (IOException e) {
logger.error("Unable to load the configuration file from path '{}'",
configPath.toAbsolutePath(), e);
return;
}

try {
Config config = Config.getInstance();
runBot(config.getToken(), Path.of(config.getDatabasePath()));
runBot(config);
} catch (Exception t) {
logger.error("Unknown error", t);
}
Expand All @@ -63,23 +63,24 @@ public static void main(final String[] args) {
/**
* Runs an instance of the bot, connecting to the given token and using the given database.
*
* @param token the Discord Bot token to connect with
* @param databasePath the path to the database to use
* @param config the configuration to run the bot with
*/
@SuppressWarnings("WeakerAccess")
public static void runBot(String token, Path databasePath) {
public static void runBot(Config config) {
logger.info("Starting bot...");

Path databasePath = Path.of(config.getDatabasePath());
try {
Path parentDatabasePath = databasePath.toAbsolutePath().getParent();
if (parentDatabasePath != null) {
Files.createDirectories(parentDatabasePath);
}
Database database = new Database("jdbc:sqlite:" + databasePath.toAbsolutePath());

JDA jda = JDABuilder.createDefault(token)
JDA jda = JDABuilder.createDefault(config.getToken())
.enableIntents(GatewayIntent.GUILD_MEMBERS)
.build();
jda.addEventListener(new BotCore(jda, database));
jda.addEventListener(new BotCore(jda, database, config));
jda.awaitReady();
logger.info("Bot is ready");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.togetherjava.tjbot.commands.tophelper.TopHelpersCommand;
import org.togetherjava.tjbot.commands.tophelper.TopHelpersMessageListener;
import org.togetherjava.tjbot.commands.tophelper.TopHelpersPurgeMessagesRoutine;
import org.togetherjava.tjbot.config.Config;
import org.togetherjava.tjbot.db.Database;
import org.togetherjava.tjbot.routines.ModAuditLogRoutine;

Expand All @@ -29,7 +30,7 @@
* it with the system.
* <p>
* To add a new slash command, extend the commands returned by
* {@link #createFeatures(JDA, Database)}.
* {@link #createFeatures(JDA, Database, Config)}.
*/
public enum Features {
;
Expand All @@ -42,10 +43,11 @@ public enum Features {
*
* @param jda the JDA instance commands will be registered at
* @param database the database of the application, which features can use to persist data
* @param config the configuration features should use
* @return a collection of all features
*/
public static @NotNull Collection<Feature> createFeatures(@NotNull JDA jda,
@NotNull Database database) {
@NotNull Database database, @NotNull Config config) {
TagSystem tagSystem = new TagSystem(database);
ModerationActionsStore actionsStore = new ModerationActionsStore(database);

Expand All @@ -55,35 +57,35 @@ public enum Features {
Collection<Feature> features = new ArrayList<>();

// Routines
features.add(new ModAuditLogRoutine(database));
features.add(new TemporaryModerationRoutine(jda, actionsStore));
features.add(new ModAuditLogRoutine(database, config));
features.add(new TemporaryModerationRoutine(jda, actionsStore, config));
features.add(new TopHelpersPurgeMessagesRoutine(database));

// Message receivers
features.add(new TopHelpersMessageListener(database));
features.add(new TopHelpersMessageListener(database, config));

// Event receivers
features.add(new RejoinMuteListener(actionsStore));
features.add(new RejoinMuteListener(actionsStore, config));

// Slash commands
features.add(new PingCommand());
features.add(new TeXCommand());
features.add(new TagCommand(tagSystem));
features.add(new TagManageCommand(tagSystem));
features.add(new TagManageCommand(tagSystem, config));
features.add(new TagsCommand(tagSystem));
features.add(new VcActivityCommand());
features.add(new WarnCommand(actionsStore));
features.add(new KickCommand(actionsStore));
features.add(new BanCommand(actionsStore));
features.add(new UnbanCommand(actionsStore));
features.add(new AuditCommand(actionsStore));
features.add(new MuteCommand(actionsStore));
features.add(new UnmuteCommand(actionsStore));
features.add(new WarnCommand(actionsStore, config));
features.add(new KickCommand(actionsStore, config));
features.add(new BanCommand(actionsStore, config));
features.add(new UnbanCommand(actionsStore, config));
features.add(new AuditCommand(actionsStore, config));
features.add(new MuteCommand(actionsStore, config));
features.add(new UnmuteCommand(actionsStore, config));
features.add(new TopHelpersCommand(database, config));
features.add(new RoleSelectCommand());
features.add(new TopHelpersCommand(database));

// Mixtures
features.add(new FreeCommand());
features.add(new FreeCommand(config));

return features;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ public final class FreeCommand extends SlashCommandAdapter implements EventRecei
private static final String COMMAND_NAME = "free";
private static final Color MESSAGE_HIGHLIGHT_COLOR = Color.decode("#CCCC00");

private final Config config;

// Map to store channel ID's, use Guild.getChannels() to guarantee order for display
private final ChannelMonitor channelMonitor;
private final Map<Long, Long> channelIdToMessageIdForStatus;
Expand All @@ -73,11 +75,14 @@ public final class FreeCommand extends SlashCommandAdapter implements EventRecei
* <p>
* This fetches configuration information from a json configuration file (see
* {@link FreeCommandConfig}) for further details.
*
* @param config the config to use for this
*/
public FreeCommand() {
public FreeCommand(@NotNull Config config) {
super(COMMAND_NAME, "Marks this channel as free for another user to ask a question",
SlashCommandVisibility.GUILD);

this.config = config;
channelIdToMessageIdForStatus = new HashMap<>();
channelMonitor = new ChannelMonitor();

Expand Down Expand Up @@ -339,17 +344,15 @@ public void onEvent(@NotNull GenericEvent event) {
}

private void initChannelsToMonitor() {
Config.getInstance()
.getFreeCommandConfig()
config.getFreeCommandConfig()
.stream()
.map(FreeCommandConfig::getMonitoredChannels)
.flatMap(Collection::stream)
.forEach(channelMonitor::addChannelToMonitor);
}

private void initStatusMessageChannels(@NotNull final JDA jda) {
Config.getInstance()
.getFreeCommandConfig()
config.getFreeCommandConfig()
.stream()
.map(FreeCommandConfig::getStatusChannel)
// throws IllegalStateException if the id's don't match TextChannels
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,17 @@ public final class AuditCommand extends SlashCommandAdapter {
* Constructs an instance.
*
* @param actionsStore used to store actions issued by this command
* @param config the config to use for this
*/
public AuditCommand(@NotNull ModerationActionsStore actionsStore) {
public AuditCommand(@NotNull ModerationActionsStore actionsStore, @NotNull Config config) {
super(COMMAND_NAME, "Lists all moderation actions that have been taken against a user",
SlashCommandVisibility.GUILD);

getData().addOption(OptionType.USER, TARGET_OPTION, "The user who to retrieve actions for",
true);

hasRequiredRole = Pattern.compile(Config.getInstance().getHeavyModerationRolePattern())
.asMatchPredicate();
hasRequiredRole =
Pattern.compile(config.getHeavyModerationRolePattern()).asMatchPredicate();
this.actionsStore = Objects.requireNonNull(actionsStore);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@ public final class BanCommand extends SlashCommandAdapter {
* Constructs an instance.
*
* @param actionsStore used to store actions issued by this command
* @param config the config to use for this
*/
public BanCommand(@NotNull ModerationActionsStore actionsStore) {
public BanCommand(@NotNull ModerationActionsStore actionsStore, @NotNull Config config) {
super(COMMAND_NAME, "Bans the given user from the server", SlashCommandVisibility.GUILD);

OptionData durationData = new OptionData(OptionType.STRING, DURATION_OPTION,
Expand All @@ -70,8 +71,8 @@ public BanCommand(@NotNull ModerationActionsStore actionsStore) {
"the amount of days of the message history to delete, none means no messages are deleted.",
true).addChoice("none", 0).addChoice("recent", 1).addChoice("all", 7));

hasRequiredRole = Pattern.compile(Config.getInstance().getHeavyModerationRolePattern())
.asMatchPredicate();
hasRequiredRole =
Pattern.compile(config.getHeavyModerationRolePattern()).asMatchPredicate();
this.actionsStore = Objects.requireNonNull(actionsStore);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ public final class KickCommand extends SlashCommandAdapter {
* Constructs an instance.
*
* @param actionsStore used to store actions issued by this command
* @param config the config to use for this
*/
public KickCommand(@NotNull ModerationActionsStore actionsStore) {
public KickCommand(@NotNull ModerationActionsStore actionsStore, @NotNull Config config) {
super(COMMAND_NAME, "Kicks the given user from the server", SlashCommandVisibility.GUILD);

getData().addOption(OptionType.USER, TARGET_OPTION, "The user who you want to kick", true)
.addOption(OptionType.STRING, REASON_OPTION, "Why the user should be kicked", true);

hasRequiredRole = Pattern.compile(Config.getInstance().getSoftModerationRolePattern())
.asMatchPredicate();
hasRequiredRole = Pattern.compile(config.getSoftModerationRolePattern()).asMatchPredicate();
this.actionsStore = Objects.requireNonNull(actionsStore);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,6 @@ public enum ModerationUtils {
* embeds.
*/
static final Color AMBIENT_COLOR = Color.decode("#895FE8");
/**
* Matches the name of the role that is used to mute users, as used by {@link MuteCommand} and
* similar.
*/
public static final Predicate<String> isMuteRole =
Pattern.compile(Config.getInstance().getMutedRolePattern()).asMatchPredicate();

/**
* Checks whether the given reason is valid. If not, it will handle the situation and respond to
Expand Down Expand Up @@ -331,14 +325,27 @@ static boolean handleHasAuthorRole(@NotNull String actionVerb,
.build();
}

/**
* Gets a predicate that identifies the role used to mute a member in a guild.
*
* @param config the config used to identify the muted role
* @return predicate that matches the name of the muted role
*/
public static Predicate<String> getIsMutedRolePredicate(@NotNull Config config) {
return Pattern.compile(config.getMutedRolePattern()).asMatchPredicate();
}

/**
* Gets the role used to mute a member in a guild.
*
* @param guild the guild to get the muted role from
* @param config the config used to identify the muted role
* @return the muted role, if found
*/
public static @NotNull Optional<Role> getMutedRole(@NotNull Guild guild) {
return guild.getRoles().stream().filter(role -> isMuteRole.test(role.getName())).findAny();
public static @NotNull Optional<Role> getMutedRole(@NotNull Guild guild,
@NotNull Config config) {
Predicate<String> isMutedRole = getIsMutedRolePredicate(config);
return guild.getRoles().stream().filter(role -> isMutedRole.test(role.getName())).findAny();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,15 @@ public final class MuteCommand extends SlashCommandAdapter {
"3 hours", "1 day", "3 days", "7 days", ModerationUtils.PERMANENT_DURATION);
private final Predicate<String> hasRequiredRole;
private final ModerationActionsStore actionsStore;
private final Config config;

/**
* Constructs an instance.
*
* @param actionsStore used to store actions issued by this command
* @param config the config to use for this
*/
public MuteCommand(@NotNull ModerationActionsStore actionsStore) {
public MuteCommand(@NotNull ModerationActionsStore actionsStore, @NotNull Config config) {
super(COMMAND_NAME, "Mutes the given user so that they can not send messages anymore",
SlashCommandVisibility.GUILD);

Expand All @@ -61,8 +63,8 @@ public MuteCommand(@NotNull ModerationActionsStore actionsStore) {
.addOptions(durationData)
.addOption(OptionType.STRING, REASON_OPTION, "Why the user should be muted", true);

hasRequiredRole = Pattern.compile(Config.getInstance().getSoftModerationRolePattern())
.asMatchPredicate();
this.config = config;
hasRequiredRole = Pattern.compile(config.getSoftModerationRolePattern()).asMatchPredicate();
this.actionsStore = Objects.requireNonNull(actionsStore);
}

Expand Down Expand Up @@ -116,7 +118,8 @@ private AuditableRestAction<Void> muteUser(@NotNull Member target, @NotNull Memb
actionsStore.addAction(guild.getIdLong(), author.getIdLong(), target.getIdLong(),
ModerationAction.MUTE, expiresAt, reason);

return guild.addRoleToMember(target, ModerationUtils.getMutedRole(guild).orElseThrow())
return guild
.addRoleToMember(target, ModerationUtils.getMutedRole(guild, config).orElseThrow())
.reason(reason);
}

Expand All @@ -137,15 +140,15 @@ private boolean handleChecks(@NotNull Member bot, @NotNull Member author,
@Nullable Member target, @NotNull CharSequence reason, @NotNull Guild guild,
@NotNull Interaction event) {
if (!ModerationUtils.handleRoleChangeChecks(
ModerationUtils.getMutedRole(guild).orElse(null), ACTION_VERB, target, bot, author,
guild, hasRequiredRole, reason, event)) {
ModerationUtils.getMutedRole(guild, config).orElse(null), ACTION_VERB, target, bot,
author, guild, hasRequiredRole, reason, event)) {
return false;
}
if (Objects.requireNonNull(target)
.getRoles()
.stream()
.map(Role::getName)
.anyMatch(ModerationUtils.isMuteRole)) {
.anyMatch(ModerationUtils.getIsMutedRolePredicate(config))) {
handleAlreadyMutedTarget(event);
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.togetherjava.tjbot.commands.EventReceiver;
import org.togetherjava.tjbot.config.Config;

import java.time.Instant;
import java.util.Objects;
import java.util.Optional;

/**
Expand All @@ -26,23 +26,27 @@ public final class RejoinMuteListener implements EventReceiver {
private static final Logger logger = LoggerFactory.getLogger(RejoinMuteListener.class);

private final ModerationActionsStore actionsStore;
private final Config config;

/**
* Constructs an instance.
*
* @param actionsStore used to store actions issued by this command and to retrieve whether a
* user should be muted
* @param config the config to use for this
*/
public RejoinMuteListener(@NotNull ModerationActionsStore actionsStore) {
this.actionsStore = Objects.requireNonNull(actionsStore);
public RejoinMuteListener(@NotNull ModerationActionsStore actionsStore,
@NotNull Config config) {
this.actionsStore = actionsStore;
this.config = config;
}

private static void muteMember(@NotNull Member member) {
private void muteMember(@NotNull Member member) {
Guild guild = member.getGuild();
logger.info("Reapplied existing mute to user '{}' ({}) in guild '{}' after rejoining.",
member.getUser().getAsTag(), member.getId(), guild.getName());

guild.addRoleToMember(member, ModerationUtils.getMutedRole(guild).orElseThrow())
guild.addRoleToMember(member, ModerationUtils.getMutedRole(guild, config).orElseThrow())
.reason("Reapplied existing mute after rejoining the server")
.queue();
}
Expand Down
Loading