| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent a92dbcd commit 8299f01
13 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -15,12 +15,32 @@ public class DbActions { | |||
| 15 | 15 | // Hide the constructor. | |
| 16 | 16 | private DbActions () {} | |
| 17 | 17 | ||
| 18 | + public static void doAction(ConnectionConsumer consumer) throws SQLException { | ||
| 19 | + try (var c = Bot.dataSource.getConnection()) { | ||
| 20 | + consumer.consume(c); | ||
| 21 | + } | ||
| 22 | + } | ||
| 23 | + | ||
| 24 | + public static <T> T map(ConnectionFunction<T> function) throws SQLException { | ||
| 25 | + try (var c = Bot.dataSource.getConnection()) { | ||
| 26 | + return function.apply(c); | ||
| 27 | + } | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + public static <T> T mapQuery(String query, StatementModifier modifier, ResultSetMapper<T> mapper) throws SQLException { | ||
| 31 | + try (var c = Bot.dataSource.getConnection(); var stmt = c.prepareStatement(query)) { | ||
| 32 | + modifier.modify(stmt); | ||
| 33 | + var rs = stmt.executeQuery(); | ||
| 34 | + return mapper.map(rs); | ||
| 35 | + } | ||
| 36 | + } | ||
| 37 | + | ||
| 18 | 38 | /** | |
| 19 | 39 | * Does an asynchronous database action using the bot's async pool. | |
| 20 | 40 | * @param consumer The consumer that will use a connection. | |
| 21 | 41 | * @return A future that completes when the action is complete. | |
| 22 | 42 | */ | |
| 23 | - public static CompletableFuture<Void> doAction(ConnectionConsumer consumer) { | ||
| 43 | + public static CompletableFuture<Void> doAsyncAction(ConnectionConsumer consumer) { | ||
| 24 | 44 | CompletableFuture<Void> future = new CompletableFuture<>(); | |
| 25 | 45 | Bot.asyncPool.submit(() -> { | |
| 26 | 46 | try (var c = Bot.dataSource.getConnection()) { | |
@@ -42,7 +62,7 @@ public static CompletableFuture<Void> doAction(ConnectionConsumer consumer) { | |||
| 42 | 62 | * @param <T> The type of data access object. Usually some kind of repository. | |
| 43 | 63 | * @return A future that completes when the action is complete. | |
| 44 | 64 | */ | |
| 45 | - public static <T> CompletableFuture<Void> doDaoAction(Function<Connection, T> daoConstructor, DaoConsumer<T> consumer) { | ||
| 65 | + public static <T> CompletableFuture<Void> doAsyncDaoAction(Function<Connection, T> daoConstructor, DaoConsumer<T> consumer) { | ||
| 46 | 66 | CompletableFuture<Void> future = new CompletableFuture<>(); | |
| 47 | 67 | Bot.asyncPool.submit(() -> { | |
| 48 | 68 | try (var c = Bot.dataSource.getConnection()) { | |
@@ -56,7 +76,7 @@ public static <T> CompletableFuture<Void> doDaoAction(Function<Connection, T> da | |||
| 56 | 76 | return future; | |
| 57 | 77 | } | |
| 58 | 78 | ||
| 59 | - public static <T> CompletableFuture<T> doAction(ConnectionFunction<T> function) { | ||
| 79 | + public static <T> CompletableFuture<T> mapAsync(ConnectionFunction<T> function) { | ||
| 60 | 80 | CompletableFuture<T> future = new CompletableFuture<>(); | |
| 61 | 81 | Bot.asyncPool.submit(() -> { | |
| 62 | 82 | try (var c = Bot.dataSource.getConnection()) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,9 @@ | |||
| 1 | + package com.javadiscord.javabot.data.h2db; | ||
| 2 | + | ||
| 3 | + import java.sql.ResultSet; | ||
| 4 | + import java.sql.SQLException; | ||
| 5 | + | ||
| 6 | + @FunctionalInterface | ||
| 7 | + public interface ResultSetMapper<T> { | ||
| 8 | + T map(ResultSet rs) throws SQLException; | ||
| 9 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,9 @@ | |||
| 1 | + package com.javadiscord.javabot.data.h2db; | ||
| 2 | + | ||
| 3 | + import java.sql.PreparedStatement; | ||
| 4 | + import java.sql.SQLException; | ||
| 5 | + | ||
| 6 | + @FunctionalInterface | ||
| 7 | + public interface StatementModifier { | ||
| 8 | + void modify(PreparedStatement s) throws SQLException; | ||
| 9 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -7,6 +7,8 @@ | |||
| 7 | 7 | import net.dv8tion.jda.api.entities.Category; | |
| 8 | 8 | import net.dv8tion.jda.api.entities.Role; | |
| 9 | 9 | ||
| 10 | + import java.util.List; | ||
| 11 | + | ||
| 10 | 12 | /** | |
| 11 | 13 | * Configuration for the guild's help system. | |
| 12 | 14 | */ | |
@@ -71,15 +73,22 @@ public class HelpConfig extends GuildConfigItem { | |||
| 71 | 73 | */ | |
| 72 | 74 | private int preferredOpenChannelCount = 3; | |
| 73 | 75 | ||
| 76 | + /** | ||
| 77 | + * A list of successive timeouts (in minutes) to use when checking to see if | ||
| 78 | + * a help channel is still active. The bot waits X minutes since the last | ||
| 79 | + * human message before sending an activity check, and waits | ||
| 80 | + */ | ||
| 81 | + private List<Integer> inactivityTimeouts = List.of(30, 60, 120, 180); | ||
| 82 | + | ||
| 74 | 83 | /** | |
| 75 | 84 | * The number of minutes of inactivity before a channel is considered inactive. | |
| 76 | 85 | */ | |
| 77 | 86 | private int inactivityTimeoutMinutes = 30; | |
| 78 | 87 | ||
| 79 | 88 | /** | |
| 80 | - * The number of minutes of inactivity before a previously inactive channel | ||
| 81 | - * is removed. This is measured from the time at which the bot determined | ||
| 82 | - * the channel to be inactive. | ||
| 89 | + * The number of minutes to wait before closing an inactive channel. An | ||
| 90 | + * inactive channel is one in which the most recent message is an unanswered | ||
| 91 | + * activity check that was sent by this bot. | ||
| 83 | 92 | */ | |
| 84 | 93 | private int removeTimeoutMinutes = 60; | |
| 85 | 94 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,6 +1,7 @@ | |||
| 1 | 1 | package com.javadiscord.javabot.events; | |
| 2 | 2 | ||
| 3 | 3 | import com.javadiscord.javabot.Bot; | |
| 4 | + import com.javadiscord.javabot.commands.Responses; | ||
| 4 | 5 | import com.javadiscord.javabot.commands.staff_commands.Ban; | |
| 5 | 6 | import com.javadiscord.javabot.commands.staff_commands.Kick; | |
| 6 | 7 | import com.javadiscord.javabot.commands.staff_commands.Unban; | |
@@ -12,6 +13,8 @@ | |||
| 12 | 13 | import net.dv8tion.jda.api.events.interaction.ButtonClickEvent; | |
| 13 | 14 | import net.dv8tion.jda.api.hooks.ListenerAdapter; | |
| 14 | 15 | ||
| 16 | + import java.sql.SQLException; | ||
| 17 | + | ||
| 15 | 18 | @Slf4j | |
| 16 | 19 | public class InteractionListener extends ListenerAdapter { | |
| 17 | 20 | ||
@@ -109,10 +112,16 @@ private void handleHelpChannel(ButtonClickEvent event, String action) { | |||
| 109 | 112 | } else if (action.equals("not-done")) { | |
| 110 | 113 | log.info("Removing timeout check message in {} because it was marked as not-done.", channel.getAsMention()); | |
| 111 | 114 | event.getMessage().delete().queue(); | |
| 112 | - channel.sendMessage(String.format( | ||
| 113 | - "Okay, we'll keep this channel reserved for you, and check again in **%d** minutes.", | ||
| 114 | - config.getInactivityTimeoutMinutes() | ||
| 115 | - )).queue(); | ||
| 115 | + try { | ||
| 116 | + int nextTimeout = channelManager.getNextTimeout(channel); | ||
| 117 | + channelManager.setTimeout(channel, nextTimeout); | ||
| 118 | + channel.sendMessage(String.format( | ||
| 119 | + "Okay, we'll keep this channel reserved for you, and check again in **%d** minutes.", | ||
| 120 | + nextTimeout | ||
| 121 | + )).queue(); | ||
| 122 | + } catch (SQLException e) { | ||
| 123 | + Responses.error(event.getHook(), "An error occurred while managing this help channel.").queue(); | ||
| 124 | + } | ||
| 116 | 125 | } | |
| 117 | 126 | } | |
| 118 | 127 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,6 +8,7 @@ | |||
| 8 | 8 | import com.javadiscord.javabot.data.mongodb.Database; | |
| 9 | 9 | import com.javadiscord.javabot.events.StarboardListener; | |
| 10 | 10 | import com.javadiscord.javabot.service.help.HelpChannelUpdater; | |
| 11 | + import com.javadiscord.javabot.service.help.checks.SimpleGreetingCheck; | ||
| 11 | 12 | import com.javadiscord.javabot.utils.Misc; | |
| 12 | 13 | import com.mongodb.MongoClient; | |
| 13 | 14 | import com.mongodb.MongoClientURI; | |
@@ -19,6 +20,7 @@ | |||
| 19 | 20 | import org.slf4j.LoggerFactory; | |
| 20 | 21 | ||
| 21 | 22 | import java.util.Arrays; | |
| 23 | + import java.util.List; | ||
| 22 | 24 | import java.util.concurrent.TimeUnit; | |
| 23 | 25 | ||
| 24 | 26 | @Slf4j | |
@@ -82,7 +84,14 @@ public void onReady(ReadyEvent event) { | |||
| 82 | 84 | ||
| 83 | 85 | // Schedule the help channel updater to run periodically for each guild. | |
| 84 | 86 | var helpConfig = Bot.config.get(guild).getHelp(); | |
| 85 | - Bot.asyncPool.scheduleAtFixedRate(new HelpChannelUpdater(event.getJDA(), helpConfig), 5, helpConfig.getUpdateIntervalSeconds(), TimeUnit.SECONDS); | ||
| 87 | + Bot.asyncPool.scheduleAtFixedRate( | ||
| 88 | + new HelpChannelUpdater(event.getJDA(), helpConfig, List.of( | ||
| 89 | + new SimpleGreetingCheck() | ||
| 90 | + )), | ||
| 91 | + 5, | ||
| 92 | + helpConfig.getUpdateIntervalSeconds(), | ||
| 93 | + TimeUnit.SECONDS | ||
| 94 | + ); | ||
| 86 | 95 | } | |
| 87 | 96 | ||
| 88 | 97 | } catch (MongoException e) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,27 @@ | |||
| 1 | + package com.javadiscord.javabot.service.help; | ||
| 2 | + | ||
| 3 | + import net.dv8tion.jda.api.entities.Message; | ||
| 4 | + import net.dv8tion.jda.api.entities.TextChannel; | ||
| 5 | + import net.dv8tion.jda.api.entities.User; | ||
| 6 | + import net.dv8tion.jda.api.requests.RestAction; | ||
| 7 | + | ||
| 8 | + import java.util.List; | ||
| 9 | + | ||
| 10 | + /** | ||
| 11 | + * Defines an analysis that can be performed on a list of messages and semantic | ||
| 12 | + * data obtained from a reserved help channel, possibly in order to provide | ||
| 13 | + * contextual help or guidance to the owner of the channel. | ||
| 14 | + */ | ||
| 15 | + public interface ChannelSemanticCheck { | ||
| 16 | + /** | ||
| 17 | + * Performs a check on the given data. | ||
| 18 | + * @param channel The reserved help channel. | ||
| 19 | + * @param owner The user who reserved the help channel. | ||
| 20 | + * @param messages The list of messages sent in the channel since the user | ||
| 21 | + * reserved it, ordered from newest to oldest. | ||
| 22 | + * @param semanticData Extra semantic data that may be useful in determining | ||
| 23 | + * when to do things. | ||
| 24 | + * @return A rest action that completes when this check is done. | ||
| 25 | + */ | ||
| 26 | + RestAction<?> doCheck(TextChannel channel, User owner, List<Message> messages, ChannelSemanticData semanticData); | ||
| 27 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,20 @@ | |||
| 1 | + package com.javadiscord.javabot.service.help; | ||
| 2 | + | ||
| 3 | + import net.dv8tion.jda.api.entities.Message; | ||
| 4 | + import net.dv8tion.jda.api.entities.User; | ||
| 5 | + | ||
| 6 | + import javax.annotation.Nullable; | ||
| 7 | + import java.time.Duration; | ||
| 8 | + import java.util.List; | ||
| 9 | + | ||
| 10 | + public record ChannelSemanticData( | ||
| 11 | + @Nullable Message initialMessage, | ||
| 12 | + Duration timeSinceFirstMessage, | ||
| 13 | + List<User> nonOwnerParticipants, | ||
| 14 | + List<Message> botMessages | ||
| 15 | + ) { | ||
| 16 | + public boolean containsBotMessageContent(String content) { | ||
| 17 | + return botMessages.stream() | ||
| 18 | + .anyMatch(m -> m.getContentRaw().contains(content)); | ||
| 19 | + } | ||
| 20 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,6 +1,7 @@ | |||
| 1 | 1 | package com.javadiscord.javabot.service.help; | |
| 2 | 2 | ||
| 3 | 3 | import com.javadiscord.javabot.Bot; | |
| 4 | + import com.javadiscord.javabot.data.h2db.DbActions; | ||
| 4 | 5 | import com.javadiscord.javabot.data.properties.config.guild.HelpConfig; | |
| 5 | 6 | import lombok.extern.slf4j.Slf4j; | |
| 6 | 7 | import net.dv8tion.jda.api.entities.Message; | |
@@ -10,6 +11,7 @@ | |||
| 10 | 11 | import net.dv8tion.jda.api.requests.RestAction; | |
| 11 | 12 | ||
| 12 | 13 | import java.sql.SQLException; | |
| 14 | + import java.time.LocalDateTime; | ||
| 13 | 15 | ||
| 14 | 16 | /** | |
| 15 | 17 | * This manager is responsible for all the main interactions that affect the | |
@@ -81,9 +83,11 @@ public void openNew() { | |||
| 81 | 83 | */ | |
| 82 | 84 | public void reserve(TextChannel channel, User reservingUser, Message message) throws SQLException { | |
| 83 | 85 | try (var con = Bot.dataSource.getConnection(); | |
| 84 | - var stmt = con.prepareStatement("INSERT INTO reserved_help_channels (channel_id, user_id) VALUES (?, ?)")) { | ||
| 86 | + var stmt = con.prepareStatement("INSERT INTO reserved_help_channels (channel_id, user_id, timeout) VALUES (?, ?, ?)")) { | ||
| 85 | 87 | stmt.setLong(1, channel.getIdLong()); | |
| 86 | 88 | stmt.setLong(2, reservingUser.getIdLong()); | |
| 89 | + int timeout = config.getInactivityTimeouts().get(0); | ||
| 90 | + stmt.setInt(3, timeout); | ||
| 87 | 91 | stmt.executeUpdate(); | |
| 88 | 92 | } | |
| 89 | 93 | var target = config.getReservedChannelCategory(); | |
@@ -162,4 +166,55 @@ public RestAction<?> unreserveChannel(TextChannel channel) { | |||
| 162 | 166 | return channel.delete(); | |
| 163 | 167 | } | |
| 164 | 168 | } | |
| 169 | + | ||
| 170 | + public void setTimeout(TextChannel channel, int timeout) throws SQLException { | ||
| 171 | + try (var con = Bot.dataSource.getConnection(); | ||
| 172 | + var stmt = con.prepareStatement("UPDATE reserved_help_channels SET timeout = ? WHERE channel_id = ?") | ||
| 173 | + ) { | ||
| 174 | + stmt.setInt(1, timeout); | ||
| 175 | + stmt.setLong(2, channel.getIdLong()); | ||
| 176 | + stmt.executeUpdate(); | ||
| 177 | + } | ||
| 178 | + } | ||
| 179 | + | ||
| 180 | + public int getTimeout(TextChannel channel) throws SQLException { | ||
| 181 | + try (var con = Bot.dataSource.getConnection(); | ||
| 182 | + var stmt = con.prepareStatement("SELECT timeout FROM reserved_help_channels WHERE channel_id = ?") | ||
| 183 | + ) { | ||
| 184 | + stmt.setLong(1, channel.getIdLong()); | ||
| 185 | + var rs = stmt.executeQuery(); | ||
| 186 | + if (rs.next()) { | ||
| 187 | + return rs.getInt(1); | ||
| 188 | + } else { | ||
| 189 | + throw new SQLException("Could not get timeout for channel_id " + channel.getId()); | ||
| 190 | + } | ||
| 191 | + } | ||
| 192 | + } | ||
| 193 | + | ||
| 194 | + public LocalDateTime getReservedAt(TextChannel channel) throws SQLException { | ||
| 195 | + return DbActions.mapQuery( | ||
| 196 | + "SELECT reserved_at FROM reserved_help_channels WHERE channel_id = ?", | ||
| 197 | + s -> s.setLong(1, channel.getIdLong()), | ||
| 198 | + rs -> { | ||
| 199 | + if (!rs.next()) throw new SQLException("No data!"); | ||
| 200 | + return rs.getTimestamp(1).toLocalDateTime(); | ||
| 201 | + } | ||
| 202 | + ); | ||
| 203 | + } | ||
| 204 | + | ||
| 205 | + public int getNextTimeout(TextChannel channel) throws SQLException { | ||
| 206 | + if (config.getInactivityTimeouts().isEmpty()) { | ||
| 207 | + log.warn("No help channel inactivity timeouts have been configured!"); | ||
| 208 | + return 60; | ||
| 209 | + } | ||
| 210 | + int currentTimeout = getTimeout(channel); | ||
| 211 | + int maxTimeout = config.getInactivityTimeouts().get(0); | ||
| 212 | + for (var t : config.getInactivityTimeouts()) { | ||
| 213 | + if (t > currentTimeout) { | ||
| 214 | + return t; | ||
| 215 | + } | ||
| 216 | + if (t > maxTimeout) maxTimeout = t; | ||
| 217 | + } | ||
| 218 | + return maxTimeout; | ||
| 219 | + } | ||
| 165 | 220 | } | |
| Back | FazBrowse Home | New Git URL |
0 commit comments