things might work

This commit is contained in:
allison
2026-08-26 18:06:21 -05:00
parent 9e66bfabb0
commit 3b816c4bde
14 changed files with 515 additions and 11 deletions
+2
View File
@@ -31,3 +31,5 @@ build/
### Mac OS ### ### Mac OS ###
.DS_Store .DS_Store
.env
+14
View File
@@ -16,6 +16,8 @@
<netty.version>4.2.17.Final</netty.version> <netty.version>4.2.17.Final</netty.version>
<log4j2.version>2.25.4</log4j2.version> <log4j2.version>2.25.4</log4j2.version>
<jda.version>6.5.0</jda.version> <jda.version>6.5.0</jda.version>
<dot.version>3.2.0</dot.version>
<postgresql.version>42.7.13</postgresql.version>
</properties> </properties>
<dependencies> <dependencies>
@@ -57,5 +59,17 @@
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency>
<groupId>io.github.cdimascio</groupId>
<artifactId>dotenv-java</artifactId>
<version>${dot.version}</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>${postgresql.version}</version>
</dependency>
</dependencies> </dependencies>
</project> </project>
-10
View File
@@ -1,10 +0,0 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Main {
static Logger logger = LogManager.getLogger(Main.class);
public static void main(String[] args) {
}
}
@@ -0,0 +1,6 @@
package tech.allydoes;
public class Constants {
public static final boolean PRODUCTION = false;
public static final String TESTING_GUILD = "";
}
+73
View File
@@ -0,0 +1,73 @@
package tech.allydoes;
import io.github.cdimascio.dotenv.Dotenv;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import tech.allydoes.database.DatabaseManager;
import tech.allydoes.discord.DiscordManager;
import tech.allydoes.web.HttpServerHandler;
public class Main {
private static DiscordManager discordManager;
private static DatabaseManager databaseManager;
static Logger logger = LogManager.getLogger(Main.class);
public final int port;
public Main(int port) {
this.port = port;
}
public static void main(String[] args) throws InterruptedException {
discordManager = new DiscordManager();
databaseManager = new DatabaseManager();
new Main(8080).start();
}
public void start() {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024);
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.DEBUG))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) {
ChannelPipeline channelPipeline = socketChannel.pipeline();
channelPipeline.addLast(new HttpResponseEncoder());
channelPipeline.addLast(new HttpRequestDecoder());
channelPipeline.addLast(new HttpObjectAggregator(1048576));
channelPipeline.addLast(new HttpServerHandler());
}
});
Channel ch = serverBootstrap.bind(this.port).sync().channel();
ch.closeFuture().sync();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static DiscordManager getDiscordManager() {
return discordManager;
}
public static DatabaseManager getDatabaseManager() {
return databaseManager;
}
}
@@ -0,0 +1,113 @@
package tech.allydoes.database;
import io.github.cdimascio.dotenv.Dotenv;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.sql.*;
public class DatabaseManager {
private final Logger LOGGER = LogManager.getLogger(DatabaseManager.class);
private final Dotenv dotenv = Dotenv.load();
public DatabaseManager() {
initializeDatabase();
}
private void initializeDatabase() {
String user = dotenv.get("DB_USER");
String password = dotenv.get("DB_PASSWORD");
String host = dotenv.get("DB_HOST");
String port = dotenv.get("DB_PORT");
String defaultDbUrl = String.format("jdbc:postgresql://%s:%s/postgres?sslmode=require", host, port);
try (Connection conn = DriverManager.getConnection(defaultDbUrl, user, password)) {
conn.setAutoCommit(true);
boolean dbExists = false;
try (Statement statement = conn.createStatement()) {
if (statement.execute(Queries.CHECK_IF_DATABASE_EXISTS)) {
ResultSet resultSet = statement.getResultSet();
resultSet.next();
dbExists = true;
}
}
if (!dbExists) {
LOGGER.debug("Database does not exist. Creating...");
try (Statement statement = conn.createStatement()) {
if (statement.execute(Queries.CREATE_DATABASE)) {
ResultSet resultSet = statement.getResultSet();
resultSet.next();
LOGGER.debug("Database created successfully.");
}
} catch (SQLException e) {
LOGGER.error("Failed to create database: {}", e.getMessage());
}
try (Statement statement = conn.createStatement()) {
if (statement.execute(Queries.CREATE_SERVER_TABLE)) {
ResultSet resultSet = statement.getResultSet();
resultSet.next();
LOGGER.debug("Server table created successfully.");
}
} catch (SQLException e) {
LOGGER.error("Failed to create server table: {}", e.getMessage());
}
} else {
LOGGER.debug("Database already exists.");
}
} catch (Exception e) {
LOGGER.error("Database initialization failed: {}", e.getMessage());
}
}
public void getAllNotifiers() {
String user = dotenv.get("DB_USER");
String password = dotenv.get("DB_PASSWORD");
String host = dotenv.get("DB_HOST");
String port = dotenv.get("DB_PORT");
String defaultDbUrl = String.format("jdbc:postgresql://%s:%s/postgres?sslmode=require", host, port);
try (Connection conn = DriverManager.getConnection(defaultDbUrl, user, password)) {
conn.setAutoCommit(true);
try (Statement statement = conn.createStatement()) {
statement.execute(Queries.SELECT_ALL_SERVER_CHANNELS);
ResultSet resultSet = statement.getResultSet();
while (resultSet.next()) {
String guildId = resultSet.getString("guild_id");
String channelId = resultSet.getString("channel_id");
String messageId = resultSet.getString("message_id");
LOGGER.debug("Notifier for guild {}, channel {}, message {}", guildId, channelId, messageId);
}
}
} catch (SQLException e) {
LOGGER.error("getAllNotifiers: Failed to connect: {}", e.getMessage());
}
}
public void putNotifier(String guildId, String channelId, String messageId) {
String user = dotenv.get("DB_USER");
String password = dotenv.get("DB_PASSWORD");
String host = dotenv.get("DB_HOST");
String port = dotenv.get("DB_PORT");
String defaultDbUrl = String.format("jdbc:postgresql://%s:%s/postgres?sslmode=require", host, port);
try (Connection conn = DriverManager.getConnection(defaultDbUrl, user, password)) {
conn.setAutoCommit(true);
try (PreparedStatement preparedStatement = conn.prepareStatement(Queries.INSERT_SERVER_CHANNEL)) {
preparedStatement.setString(1, guildId);
preparedStatement.setString(2, channelId);
preparedStatement.setString(3, messageId);
if (preparedStatement.execute()) {
LOGGER.debug("Notifier for guild {}, channel {}, message {} added successfully.", guildId, channelId, messageId);
}
}
} catch (SQLException e) {
LOGGER.error("putNotifier: Failed to connect: {}", e.getMessage());
}
}
}
@@ -0,0 +1,11 @@
package tech.allydoes.database;
public class Queries {
public static final String CHECK_IF_DATABASE_EXISTS = "SELECT 1 FROM pg_database WHERE datname = notifier";
public static final String CREATE_DATABASE = "CREATE DATABASE IF NOT EXISTS notifier";
public static final String CREATE_SERVER_TABLE = "CREATE TABLE IF NOT EXISTS notifier_channels (guild_id VARCHAR(22) PRIMARY KEY, channel_id VARCHAR(22), message_id VARCHAR(22))";
public static final String INSERT_SERVER_CHANNEL = "INSERT INTO notifier_channels (guild_id, channel_id, message_id) VALUES (?, ?, ?)";
public static final String DELETE_SERVER_CHANNEL = "DELETE FROM notifier_channels WHERE message_id = ?";
public static final String SELECT_ALL_SERVER_CHANNELS = "SELECT guild_id, channel_id, message_id FROM notifier_channels";
}
@@ -0,0 +1,12 @@
package tech.allydoes.discord;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
public interface Command {
SlashCommandData getCommandData();
boolean isPrivateCommand();
void processSlashCommandInteractionEvent(SlashCommandInteractionEvent event);
void processButtonInteractionEvent(ButtonInteractionEvent event);
}
@@ -0,0 +1,75 @@
package tech.allydoes.discord;
import io.github.cdimascio.dotenv.Dotenv;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.entities.Activity;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.requests.GatewayIntent;
import net.dv8tion.jda.api.requests.restaction.CommandListUpdateAction;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import tech.allydoes.Constants;
import tech.allydoes.discord.commands.SaveChannelCommand;
import java.util.HashMap;
public class DiscordManager {
public final Logger LOGGER;
private final JDA jda;
private final HashMap<String, Command> commands;
private final Dotenv dotenv = Dotenv.load();
public DiscordManager() throws InterruptedException {
LOGGER = LogManager.getLogger(DiscordManager.class);
commands = new HashMap<>();
commands.put("savechannel", new SaveChannelCommand());
String botToken = Constants.PRODUCTION ? dotenv.get("DISCORD_TOKEN") : dotenv.get("TESTING_TOKEN");
jda = JDABuilder
.create(botToken, GatewayIntent.GUILD_MESSAGES)
.build()
.awaitReady();
registerSlashCommands();
jda.getPresence().setActivity(Activity.competing("in robotics!"));
LOGGER.info("Logged in as {}", jda.getSelfUser().getName());
}
private void registerSlashCommands() {
if (Constants.PRODUCTION) {
registerSlashCommandsGlobal();
} else {
registerSlashCommandsGuild(Constants.TESTING_GUILD);
}
}
private void registerSlashCommandsGlobal() {
CommandListUpdateAction commandListUpdateAction = jda.updateCommands();
for (Command command : commands.values()) {
if (command.isPrivateCommand()) continue;
commandListUpdateAction = commandListUpdateAction.addCommands(command.getCommandData());
LOGGER.debug("Registered global command: {}", command.getCommandData().getName());
}
commandListUpdateAction.queue();
}
private void registerSlashCommandsGuild(String guildID) {
Guild guild = jda.getGuildById(guildID);
if (guild == null) return;
CommandListUpdateAction updateAction = guild.updateCommands();
for (Command command : commands.values()) {
updateAction = updateAction.addCommands(command.getCommandData());
LOGGER.debug("Registered guild command: {}", command.getCommandData().getName());
}
updateAction.queue();
}
public Command getCommand(String command) {
return commands.get(command);
}
public JDA getJDA() {
return jda;
}
}
@@ -0,0 +1,17 @@
package tech.allydoes.discord;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.MessageEmbed;
import java.time.Instant;
public class Embeds {
public static MessageEmbed createUnknownLabStatus() {
return new EmbedBuilder()
.setTitle("Unknown Status")
.setDescription("The status of the lab is unknown.")
.setColor(0xD3D3D3)
.setTimestamp(Instant.now())
.build();
}
}
@@ -0,0 +1,42 @@
package tech.allydoes.discord.commands;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
import tech.allydoes.Main;
import tech.allydoes.discord.Command;
import tech.allydoes.discord.Embeds;
public class SaveChannelCommand implements Command {
@Override
public SlashCommandData getCommandData() {
return Commands.slash("savechannel", "Saves the channel you are in to be used for the status updates")
.setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.ADMINISTRATOR));
}
@Override
public boolean isPrivateCommand() {
return false;
}
@Override
public void processSlashCommandInteractionEvent(SlashCommandInteractionEvent event) {
event.deferReply(true).queue((interactionHook) -> {
interactionHook.getInteraction().getMessageChannel().sendMessageEmbeds(Embeds.createUnknownLabStatus()).queue((embedMessage) -> {
String guildId = event.getGuild().getId();
String channelId = event.getChannel().getId();
String messageId = embedMessage.getId();
Main.getDatabaseManager().putNotifier(guildId, channelId, messageId);
});
event.getHook().editOriginal("Channel saved!").queue();
});
}
@Override
public void processButtonInteractionEvent(ButtonInteractionEvent event) {
}
}
@@ -0,0 +1,98 @@
package tech.allydoes.web;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import tech.allydoes.leaderboards.handlers.GET.*;
import tech.allydoes.leaderboards.handlers.POST.SetPlayerProfile;
import tech.allydoes.leaderboards.handlers.POST.SetScore;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static io.netty.handler.codec.http.HttpHeaderNames.*;
import static io.netty.handler.codec.http.HttpHeaderValues.CLOSE;
public class HttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
private static final Logger LOGGER = LogManager.getLogger(HttpServerHandler.class);
private final HashMap<String, RequestHandler> requestHandlers = new HashMap<>();
public HttpServerHandler() {
RequestHandler[] handlers = {
};
for (RequestHandler handler : handlers) {
requestHandlers.put(handler.getRequestName().toLowerCase(), handler);
}
}
public static ChannelFuture sendContent(String content, FullHttpRequest request, ChannelHandlerContext channelHandlerContext) {
ByteBuf byteBuf = Unpooled.copiedBuffer(content, CharsetUtil.UTF_8);
DefaultFullHttpResponse httpResponse = new DefaultFullHttpResponse(request.protocolVersion(), HttpResponseStatus.OK, byteBuf);
httpResponse.headers().set(CONTENT_TYPE, "application/json; charset=utf-8");
httpResponse.headers().set(CONTENT_LENGTH, byteBuf.readableBytes());
return channelHandlerContext.writeAndFlush(httpResponse);
}
@Override
public void channelReadComplete(ChannelHandlerContext channelHandlerContext) {
channelHandlerContext.flush();
}
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, FullHttpRequest request) {
ChannelFuture future = processRequest(channelHandlerContext, request);
request.headers().set(CONNECTION, CLOSE);
future.addListener(ChannelFutureListener.CLOSE);
}
private ChannelFuture processRequest(ChannelHandlerContext channelHandlerContext, FullHttpRequest request) {
String path = request.uri().split("\\?", 15)[0].toLowerCase();
RequestHandler requestHandler = requestHandlers.get(path);
QueryStringDecoder queryStringDecoder = new QueryStringDecoder(request.uri());
Map<String, List<String>> params = queryStringDecoder.parameters();
if (requestHandler != null && requestHandler.getRequestType().equalsIgnoreCase(request.method().name())) {
if (!hasRequiredParameters(params, requestHandler.getRequiredParameters())) {
return channelHandlerContext.writeAndFlush(new DefaultFullHttpResponse(request.protocolVersion(), HttpResponseStatus.BAD_REQUEST));
}
return requestHandler.processRequest(channelHandlerContext, request, params);
} else {
return channelHandlerContext.writeAndFlush(new DefaultFullHttpResponse(request.protocolVersion(), HttpResponseStatus.NOT_FOUND));
}
}
@Override
public void exceptionCaught(ChannelHandlerContext channelHandlerContext, Throwable cause) {
LOGGER.info("Unhandled exception in pipeline", cause);
channelHandlerContext.writeAndFlush(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR));
channelHandlerContext.close();
}
private boolean hasRequiredParameters(Map<String, List<String>> parameters, String[] requiredParameters) {
for (String requiredParameter : requiredParameters) {
if (!parameters.containsKey(requiredParameter)) {
return false;
}
}
for (List<String> parameterArray: parameters.values()) {
String parameter = parameterArray.getFirst();
if (parameter.isEmpty()) {
return false;
}
}
return true;
}
}
@@ -0,0 +1,36 @@
package tech.allydoes.web.POST;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest;
import tech.allydoes.web.RequestHandler;
import java.util.List;
import java.util.Map;
public class PostStatus implements RequestHandler {
public static String STATUS = "status";
@Override
public ChannelFuture processRequest(ChannelHandlerContext channelHandlerContext, FullHttpRequest request, Map<String, List<String>> parameters) {
int status = Integer.parseInt(parameters.get(STATUS).getFirst());
}
@Override
public String getRequestName() {
return "PostStatus";
}
@Override
public String getRequestType() {
return "POST";
}
@Override
public String[] getRequiredParameters() {
return new String[] {
STATUS
};
}
}
@@ -0,0 +1,15 @@
package tech.allydoes.web;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest;
import java.util.List;
import java.util.Map;
public interface RequestHandler {
ChannelFuture processRequest(ChannelHandlerContext channelHandlerContext, FullHttpRequest request, Map<String, List<String>> parameters);
String getRequestName();
String getRequestType();
String[] getRequiredParameters();
}