Implement SQLite database solving #1
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
A new basic database has been laid out, with support for message expiry and disabling buttons for old messages.
This commit is contained in:
parent
7ffd3442c2
commit
98a162a33b
6
pom.xml
6
pom.xml
@ -30,6 +30,12 @@
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
<version>3.39.4.1</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
@ -1,17 +1,23 @@
|
||||
package wtf.beatrice.hidekobot;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import wtf.beatrice.hidekobot.database.DatabaseManager;
|
||||
import wtf.beatrice.hidekobot.listeners.MessageLogger;
|
||||
|
||||
public class Configuration
|
||||
{
|
||||
|
||||
|
||||
private static DatabaseManager dbManager = null;
|
||||
private static boolean verbose = false;
|
||||
private static MessageLogger verbosityLogger;
|
||||
|
||||
// todo: allow people to set their own user id
|
||||
private static final long botOwnerId = 979809420714332260L;
|
||||
|
||||
private final static String expiryTimestampFormat = "yy/MM/dd HH:mm:ss";
|
||||
private final static long expiryTimeSeconds = 60L;
|
||||
|
||||
private final static String defaultInviteLink =
|
||||
"https://discord.com/api/oauth2/authorize?client_id=%userid%&scope=bot+applications.commands&permissions=8";
|
||||
|
||||
@ -95,4 +101,35 @@ public class Configuration
|
||||
return defaultInviteLink.replace("%userid%", botApplicationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the already fully-initialized DatabaseManager instance, ready to be accessed and used.
|
||||
*
|
||||
* @param databaseManagerInstance the fully-initialized DatabaseManager instance.
|
||||
*/
|
||||
public static void setDatabaseManagerInstance(DatabaseManager databaseManagerInstance)
|
||||
{
|
||||
dbManager = databaseManagerInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fully-initialized DatabaseManager instance, ready to be used.
|
||||
*
|
||||
* @return the DatabaseManager instance.
|
||||
*/
|
||||
public static @Nullable DatabaseManager getDatabaseManager() { return dbManager; }
|
||||
|
||||
/**
|
||||
* Get the DateTimeFormatter string for parsing the expired messages timestamp.
|
||||
*
|
||||
* @return the String of the DateTimeFormatter format.
|
||||
*/
|
||||
public static String getExpiryTimestampFormat(){ return expiryTimestampFormat; }
|
||||
|
||||
/**
|
||||
* Get the amount of seconds after which a message expires.
|
||||
*
|
||||
* @return long value of the expiry seconds.
|
||||
*/
|
||||
public static long getExpiryTimeSeconds() { return expiryTimeSeconds; }
|
||||
|
||||
}
|
||||
|
@ -6,17 +6,21 @@ import net.dv8tion.jda.api.OnlineStatus;
|
||||
import net.dv8tion.jda.api.entities.Activity;
|
||||
import net.dv8tion.jda.api.requests.GatewayIntent;
|
||||
import sun.misc.Signal;
|
||||
import wtf.beatrice.hidekobot.database.DatabaseManager;
|
||||
import wtf.beatrice.hidekobot.listeners.ButtonInteractionListener;
|
||||
import wtf.beatrice.hidekobot.listeners.MessageListener;
|
||||
import wtf.beatrice.hidekobot.listeners.SlashCommandCompleter;
|
||||
import wtf.beatrice.hidekobot.listeners.SlashCommandListener;
|
||||
import wtf.beatrice.hidekobot.utils.ExpiredMessageRunner;
|
||||
import wtf.beatrice.hidekobot.utils.Logger;
|
||||
import wtf.beatrice.hidekobot.utils.SlashCommandsUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class HidekoBot
|
||||
@ -93,6 +97,30 @@ public class HidekoBot
|
||||
jda.getPresence().setStatus(OnlineStatus.ONLINE);
|
||||
jda.getPresence().setActivity(Activity.playing("Hatsune Miku: Project DIVA"));
|
||||
|
||||
// connect to database
|
||||
logger.log("Connecting to database...");
|
||||
String dbFilePath = System.getProperty("user.dir") + File.separator + "db.sqlite"; // in current directory
|
||||
DatabaseManager dbManager = new DatabaseManager(dbFilePath);
|
||||
if(dbManager.connect() && dbManager.initDb())
|
||||
{
|
||||
logger.log("Database connection initialized!");
|
||||
Configuration.setDatabaseManagerInstance(dbManager);
|
||||
|
||||
// load data here...
|
||||
|
||||
logger.log("Database data loaded into memory!");
|
||||
} else {
|
||||
logger.log("Error initializing database connection!");
|
||||
}
|
||||
|
||||
// start scheduled runnables
|
||||
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
ExpiredMessageRunner task = new ExpiredMessageRunner();
|
||||
int initDelay = 5;
|
||||
int periodicDelay = 5;
|
||||
scheduler.scheduleAtFixedRate(task, initDelay, periodicDelay, TimeUnit.SECONDS);
|
||||
|
||||
|
||||
// print the bot logo.
|
||||
logger.log("\n\n" + logger.getLogo() + "\nv" + version + " - bot is ready!\n", 2);
|
||||
|
||||
|
@ -10,7 +10,9 @@ import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
|
||||
import net.dv8tion.jda.api.interactions.InteractionHook;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
|
||||
import net.dv8tion.jda.api.interactions.components.buttons.Button;
|
||||
import net.dv8tion.jda.api.requests.restaction.WebhookMessageEditAction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import wtf.beatrice.hidekobot.Configuration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@ -20,6 +22,11 @@ public class ClearChatCommand
|
||||
|
||||
public void runSlashCommand(@NotNull SlashCommandInteractionEvent event)
|
||||
{
|
||||
|
||||
// run in a new thread, so we don't block the main one
|
||||
new Thread(() ->
|
||||
{
|
||||
|
||||
MessageChannel channel = event.getChannel();
|
||||
|
||||
if(!(channel instanceof TextChannel))
|
||||
@ -140,25 +147,36 @@ public class ClearChatCommand
|
||||
|
||||
Button deleteButton = Button.danger("clear_delete", "Delete").withEmoji(Emoji.fromUnicode("❌"));
|
||||
|
||||
WebhookMessageEditAction<Message> webhookMessageEditAction;
|
||||
|
||||
// log having deleted the messages (or not).
|
||||
if(deleted < 1)
|
||||
{
|
||||
replyInteraction.editOriginal("\uD83D\uDE22 Couldn't clear any message!")
|
||||
.setActionRow(deleteButton).queue();
|
||||
webhookMessageEditAction = replyInteraction.editOriginal("\uD83D\uDE22 Couldn't clear any message!");
|
||||
} else if(deleted == 1)
|
||||
{
|
||||
replyInteraction.editOriginal("✂ Cleared 1 message!")
|
||||
.setActionRow(deleteButton).queue();
|
||||
webhookMessageEditAction = replyInteraction.editOriginal("✂ Cleared 1 message!");
|
||||
} else {
|
||||
replyInteraction.editOriginal("✂ Cleared " + deleted + " messages!")
|
||||
.setActionRow(deleteButton).queue();
|
||||
webhookMessageEditAction = replyInteraction.editOriginal("✂ Cleared " + deleted + " messages!");
|
||||
}
|
||||
|
||||
Message message = webhookMessageEditAction
|
||||
.setActionRow(deleteButton)
|
||||
.complete();
|
||||
|
||||
String replyMessageId = message.getId();
|
||||
String replyChannelId = message.getChannel().getId();
|
||||
String replyGuildId = message.getGuild().getId();
|
||||
|
||||
Configuration.getDatabaseManager().queueDisabling(replyGuildId, replyChannelId, replyMessageId);
|
||||
|
||||
}
|
||||
}).start();
|
||||
|
||||
}
|
||||
|
||||
public void deleteButton(ButtonInteractionEvent event)
|
||||
{
|
||||
// todo: permissions check
|
||||
event.getInteraction().getMessage().delete().queue();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,266 @@
|
||||
package wtf.beatrice.hidekobot.database;
|
||||
|
||||
import wtf.beatrice.hidekobot.Configuration;
|
||||
import wtf.beatrice.hidekobot.utils.Logger;
|
||||
|
||||
import java.sql.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class DatabaseManager
|
||||
{
|
||||
|
||||
private final static String sqliteURL = "jdbc:sqlite:%path%";
|
||||
private Connection dbConnection = null;
|
||||
private final String dbPath;
|
||||
private final Logger logger;
|
||||
|
||||
public DatabaseManager(String dbPath)
|
||||
{
|
||||
this.dbPath = dbPath;
|
||||
this.logger = new Logger(getClass());
|
||||
}
|
||||
|
||||
public boolean connect()
|
||||
{
|
||||
String url = sqliteURL.replace("%path%", dbPath);
|
||||
|
||||
if(!close()) return false;
|
||||
|
||||
try {
|
||||
dbConnection = DriverManager.getConnection(url);
|
||||
logger.log("Database connection established!");
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public boolean close()
|
||||
{
|
||||
if (dbConnection != null)
|
||||
{
|
||||
try {
|
||||
if(!dbConnection.isClosed())
|
||||
{
|
||||
dbConnection.close();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
dbConnection = null;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public boolean initDb()
|
||||
{
|
||||
List<String> newTables = new ArrayList<>();
|
||||
|
||||
newTables.add("CREATE TABLE IF NOT EXISTS pending_disabled_messages (" +
|
||||
"guild_id TEXT NOT NULL, " +
|
||||
"channel_id TEXT NOT NULL," +
|
||||
"message_id TEXT NOT NULL," +
|
||||
"expiry_timestamp TEXT NOT NULL" +
|
||||
");");
|
||||
|
||||
newTables.add("CREATE TABLE IF NOT EXISTS command_runners (" +
|
||||
"guild_id TEXT NOT NULL, " +
|
||||
"channel_id TEXT NOT NULL," + // channel the command was run in
|
||||
"message_id TEXT NOT NULL," + // message id of the bot's response
|
||||
"user_id TEXT NOT NULL" + // user who ran the command
|
||||
");");
|
||||
|
||||
for(String sql : newTables)
|
||||
{
|
||||
try (Statement stmt = dbConnection.createStatement()) {
|
||||
// execute the statement
|
||||
stmt.execute(sql);
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean queueDisabling(String guildId, String channelId, String messageId)
|
||||
{
|
||||
LocalDateTime expiryTime = LocalDateTime.now().plusSeconds(Configuration.getExpiryTimeSeconds());
|
||||
|
||||
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(Configuration.getExpiryTimestampFormat());
|
||||
String expiryTimeFormatted = dateTimeFormatter.format(expiryTime);
|
||||
|
||||
String query = "INSERT INTO pending_disabled_messages " +
|
||||
"(guild_id, channel_id, message_id, expiry_timestamp) VALUES " +
|
||||
" (?, ?, ?, ?);";
|
||||
|
||||
try(PreparedStatement preparedStatement = dbConnection.prepareStatement(query))
|
||||
{
|
||||
preparedStatement.setString(1, guildId);
|
||||
preparedStatement.setString(2, channelId);
|
||||
preparedStatement.setString(3, messageId);
|
||||
preparedStatement.setString(4, expiryTimeFormatted);
|
||||
|
||||
preparedStatement.executeUpdate();
|
||||
|
||||
return true;
|
||||
} catch (SQLException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public List<String> getQueuedExpiringMessages()
|
||||
{
|
||||
List<String> messages = new ArrayList<>();
|
||||
|
||||
String query = "SELECT message_id " +
|
||||
"FROM pending_disabled_messages ";
|
||||
|
||||
try (Statement statement = dbConnection.createStatement())
|
||||
{
|
||||
ResultSet resultSet = statement.executeQuery(query);
|
||||
if(resultSet.isClosed()) return messages;
|
||||
while(resultSet.next())
|
||||
{
|
||||
messages.add(resultSet.getString("message_id"));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
public boolean untrackExpiredMessage(String messageId)
|
||||
{
|
||||
String query = "DELETE FROM pending_disabled_messages WHERE message_id = ?;";
|
||||
|
||||
try(PreparedStatement preparedStatement = dbConnection.prepareStatement(query))
|
||||
{
|
||||
preparedStatement.setString(1, messageId);
|
||||
preparedStatement.execute();
|
||||
} catch (SQLException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
query = "DELETE FROM command_runners WHERE message_id = ?;";
|
||||
try(PreparedStatement preparedStatement = dbConnection.prepareStatement(query))
|
||||
{
|
||||
preparedStatement.setString(1, messageId);
|
||||
preparedStatement.execute();
|
||||
} catch (SQLException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getQueuedExpiringMessageExpiryDate(String messageId)
|
||||
{
|
||||
String query = "SELECT expiry_timestamp " +
|
||||
"FROM pending_disabled_messages " +
|
||||
"WHERE message_id = ?;";
|
||||
|
||||
try(PreparedStatement preparedStatement = dbConnection.prepareStatement(query))
|
||||
{
|
||||
preparedStatement.setString(1, messageId);
|
||||
ResultSet resultSet = preparedStatement.executeQuery();
|
||||
if(resultSet.isClosed()) return null;
|
||||
while(resultSet.next())
|
||||
{
|
||||
return resultSet.getString("expiry_timestamp");
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getQueuedExpiringMessageChannel(String messageId)
|
||||
{
|
||||
String query = "SELECT channel_id " +
|
||||
"FROM pending_disabled_messages " +
|
||||
"WHERE message_id = ?;";
|
||||
|
||||
try(PreparedStatement preparedStatement = dbConnection.prepareStatement(query))
|
||||
{
|
||||
preparedStatement.setString(1, messageId);
|
||||
ResultSet resultSet = preparedStatement.executeQuery();
|
||||
if(resultSet.isClosed()) return null;
|
||||
while(resultSet.next())
|
||||
{
|
||||
return resultSet.getString("channel_id");
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getQueuedExpiringMessageGuild(String messageId)
|
||||
{
|
||||
String query = "SELECT guild_id " +
|
||||
"FROM pending_disabled_messages " +
|
||||
"WHERE message_id = ?;";
|
||||
|
||||
try(PreparedStatement preparedStatement = dbConnection.prepareStatement(query))
|
||||
{
|
||||
preparedStatement.setString(1, messageId);
|
||||
ResultSet resultSet = preparedStatement.executeQuery();
|
||||
if(resultSet.isClosed()) return null;
|
||||
while(resultSet.next())
|
||||
{
|
||||
return resultSet.getString("guild_id");
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* DB STRUCTURE
|
||||
* TABLE 1: pending_disabled_messages
|
||||
* ----------------------------------------------------------------------------------
|
||||
* | guild_id | channel_id | message_id | expiry_timestamp |
|
||||
* ----------------------------------------------------------------------------------
|
||||
* |39402849302 | 39402849302 | 39402849302 | 2022-11-20 22:45:53:300 |
|
||||
* ----------------------------------------------------------------------------------
|
||||
*
|
||||
*
|
||||
* TABLE 2: command_runners
|
||||
* --------------------------------------------------------------------------
|
||||
* | guild_id | channel_id | message_id | user_id |
|
||||
* --------------------------------------------------------------------------
|
||||
* | 39402849302 | 39402849302 | 39402849302 | 39402849302 |
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
package wtf.beatrice.hidekobot.utils;
|
||||
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||
import net.dv8tion.jda.api.interactions.components.LayoutComponent;
|
||||
import net.dv8tion.jda.api.requests.RestAction;
|
||||
import wtf.beatrice.hidekobot.Configuration;
|
||||
import wtf.beatrice.hidekobot.HidekoBot;
|
||||
import wtf.beatrice.hidekobot.database.DatabaseManager;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ExpiredMessageRunner implements Runnable {
|
||||
|
||||
private final DateTimeFormatter formatter;
|
||||
private final Logger logger;
|
||||
private DatabaseManager databaseManager;
|
||||
|
||||
|
||||
public ExpiredMessageRunner()
|
||||
{
|
||||
String format = Configuration.getExpiryTimestampFormat();
|
||||
formatter = DateTimeFormatter.ofPattern(format);
|
||||
databaseManager = Configuration.getDatabaseManager();
|
||||
logger = new Logger(getClass());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
databaseManager = Configuration.getDatabaseManager();
|
||||
if(databaseManager == null) return;
|
||||
|
||||
List<String> expiringMessages = Configuration.getDatabaseManager().getQueuedExpiringMessages();
|
||||
if(expiringMessages == null || expiringMessages.isEmpty()) return;
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
for(String messageId : expiringMessages)
|
||||
{
|
||||
logger.log("ID: " + messageId);
|
||||
String expiryTimestamp = databaseManager.getQueuedExpiringMessageExpiryDate(messageId);
|
||||
if(expiryTimestamp == null || expiryTimestamp.equals("")) continue; //todo: idk count it as expired already?
|
||||
|
||||
|
||||
LocalDateTime expiryDate = LocalDateTime.parse(expiryTimestamp, formatter);
|
||||
if(now.isAfter(expiryDate))
|
||||
{
|
||||
disableExpired(messageId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void disableExpired(String messageId)
|
||||
{
|
||||
String guildId = databaseManager.getQueuedExpiringMessageGuild(messageId);
|
||||
String channelId = databaseManager.getQueuedExpiringMessageChannel(messageId);
|
||||
|
||||
|
||||
|
||||
Guild guild = HidekoBot.getAPI().getGuildById(guildId);
|
||||
if(guild == null) return; //todo count it as done/solved/removed? we probably got kicked
|
||||
TextChannel textChannel = guild.getTextChannelById(channelId);
|
||||
if(textChannel == null) return; //todo count it as done/solved/removed? channel was probably deleted
|
||||
|
||||
RestAction<Message> retrieveAction = textChannel.retrieveMessageById(messageId);
|
||||
|
||||
|
||||
|
||||
retrieveAction.queue(
|
||||
|
||||
message -> {
|
||||
if(message == null) return; //todo count it as done/solved/removed? message was probably deleted
|
||||
|
||||
List<LayoutComponent> components = message.getComponents();
|
||||
List<LayoutComponent> newComponents = new ArrayList<>();
|
||||
for (LayoutComponent component : components)
|
||||
{
|
||||
component = component.asDisabled();
|
||||
newComponents.add(component);
|
||||
}
|
||||
|
||||
message.editMessageComponents(newComponents).queue();
|
||||
databaseManager.untrackExpiredMessage(messageId);
|
||||
},
|
||||
|
||||
(error) -> {
|
||||
databaseManager.untrackExpiredMessage(messageId);
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user