Skip to content

Commit

Permalink
Merge pull request #502 from danthe1st/main
Browse files Browse the repository at this point in the history
native-image compilation, disable member cache
  • Loading branch information
danthe1st authored Nov 27, 2024
2 parents 173044b + 1d42fe3 commit 919c061
Show file tree
Hide file tree
Showing 29 changed files with 604 additions and 4,839 deletions.
12 changes: 6 additions & 6 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ jobs:
if: ${{ (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'workflow_dispatch' }}
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
- name: Set up JDK 21
uses: graalvm/setup-graalvm@v1
with:
java-version: '17'
distribution: 'temurin'
- name: Build JAR
run: ./gradlew shadowJar
java-version: '21'
distribution: 'graalvm-community'
- name: Build native-image
run: ./gradlew nativeCompile -Pprod
- name: Build Docker image
run: docker build -t javabot .
- name: Tag docker image
Expand Down
20 changes: 15 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
FROM eclipse-temurin:21-jre
RUN mkdir /work
COPY build/libs/JavaBot-1.0.0-SNAPSHOT-all.jar /work/bot.jar
FROM alpine:latest
RUN apk add --no-cache libsm libxrender libxext libxtst libxi gcompat ttf-dejavu

COPY build/native/nativeCompile /work
WORKDIR /work

RUN chown 1000:1000 /work
USER 1000
ENV HOME=/work

# https://github.com/openjdk/jdk/pull/20169
# need to create fake JAVA_HOME
RUN mkdir -p /tmp/JAVA_HOME/conf/fonts
RUN mkdir /tmp/JAVA_HOME/lib


VOLUME "/work/config"
VOLUME "/work/logs"
VOLUME "/work/db"
VOLUME "/work/purgeArchives"
USER 1000
ENTRYPOINT [ "java", "-jar", "bot.jar" ]
ENTRYPOINT [ "./javabot", "-Djava.home=/tmp/JAVA_HOME" ]
33 changes: 30 additions & 3 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import com.github.jengelman.gradle.plugins.shadow.transformers.*
plugins {
java
id("com.github.johnrengelman.shadow") version "7.1.2"
id("org.springframework.boot") version "3.2.0"
id("io.spring.dependency-management") version "1.0.15.RELEASE"
id("org.springframework.boot") version "3.3.5"
id("io.spring.dependency-management") version "1.1.6"
id("org.graalvm.buildtools.native") version "0.10.3"
checkstyle
}

Expand Down Expand Up @@ -64,6 +65,9 @@ dependencies {
// Spring
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-data-jdbc")

//required for registering native hints
implementation("org.jetbrains.kotlin:kotlin-reflect")
}

configurations {
Expand Down Expand Up @@ -101,4 +105,27 @@ tasks.withType<ShadowJar> {
checkstyle {
toolVersion = "9.1"
configDirectory.set(File("checkstyle"))
}
}

tasks.withType<Checkstyle>() {
exclude("**/generated/**")
}

tasks.checkstyleAot {
isEnabled = false
}
tasks.processTestAot {
isEnabled = false
}

graalvmNative {
binaries {
named("main") {
if (hasProperty("prod")) {
buildArgs.add("-O3")
} else {
quickBuild.set(true)
}
}
}
}
2 changes: 1 addition & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
rootProject.name = "JavaBot"
rootProject.name = "javabot"

Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package net.discordjug.javabot;

import java.nio.channels.Channel;

import club.minnced.discord.webhook.send.WebhookEmbed;
import net.discordjug.javabot.data.config.BotConfig;
import net.discordjug.javabot.data.config.GuildConfig;
import net.discordjug.javabot.data.config.GuildConfigItem;
import net.discordjug.javabot.data.config.SystemsConfig;
import net.discordjug.javabot.data.config.SystemsConfig.ApiConfig;
import net.discordjug.javabot.data.config.guild.HelpConfig;
import net.discordjug.javabot.data.config.guild.MessageCacheConfig;
import net.discordjug.javabot.data.config.guild.MetricsConfig;
import net.discordjug.javabot.data.config.guild.ModerationConfig;
import net.discordjug.javabot.data.config.guild.QOTWConfig;
import net.discordjug.javabot.data.config.guild.ServerLockConfig;
import net.discordjug.javabot.data.config.guild.StarboardConfig;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.ScheduledEvent;
import net.dv8tion.jda.api.entities.ThreadMember;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.forums.ForumTag;
import net.dv8tion.jda.api.entities.emoji.RichCustomEmoji;
import net.dv8tion.jda.api.entities.sticker.GuildSticker;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.dv8tion.jda.api.managers.AudioManager;
import net.dv8tion.jda.internal.entities.MemberPresenceImpl;
import net.dv8tion.jda.internal.requests.restaction.PermOverrideData;
import org.h2.server.TcpServer;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.annotation.RegisterReflectionForBinding;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportRuntimeHints;
import org.springframework.core.io.ClassPathResource;

/**
* Configure classes and resources to be accessible from native-image.
*/
@Configuration
@ImportRuntimeHints(RuntimeHintsConfiguration.class)
@RegisterReflectionForBinding({
//register config classes for reflection
BotConfig.class, GuildConfig.class, GuildConfigItem.class, SystemsConfig.class, ApiConfig.class,
HelpConfig.class, MessageCacheConfig.class, MetricsConfig.class, ModerationConfig.class, QOTWConfig.class, ServerLockConfig.class, StarboardConfig.class,

//ensure JDA can create necessary caches
User[].class, Guild[].class, Member[].class, Role[].class, Channel[].class, AudioManager[].class, ScheduledEvent[].class, ThreadMember[].class, ForumTag[].class, RichCustomEmoji[].class, GuildSticker[].class, MemberPresenceImpl[].class,
//needs to be serialized for channel managers etc
PermOverrideData.class,
//ensure that webhook embed authors can be serialized
WebhookEmbed.EmbedAuthor.class
})
public class RuntimeHintsConfiguration implements RuntimeHintsRegistrar {

@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {

//ensure resources are available in native-image
hints.resources().registerPattern("assets/**");
hints.resources().registerPattern("database/**");
hints.resources().registerPattern("help_guidelines/**");
hints.resources().registerPattern("help_overview/**");
hints.resources().registerResource(new ClassPathResource("quartz.properties"));

//allow H2 to create the TCP server (necessary for starting the DB)
hints.reflection().registerType(TcpServer.class, MemberCategory.INVOKE_PUBLIC_METHODS);

// JDA needs to be able to access listener methods
hints.reflection().registerType(ListenerAdapter.class, MemberCategory.INVOKE_PUBLIC_METHODS);

// caffeine
hints.reflection().registerTypeIfPresent(getClass().getClassLoader(), "com.github.benmanes.caffeine.cache.SSW", MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
}
}
5 changes: 2 additions & 3 deletions src/main/java/net/discordjug/javabot/SpringConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import xyz.dynxsty.dih4jda.DIH4JDA;
import xyz.dynxsty.dih4jda.DIH4JDABuilder;
import xyz.dynxsty.dih4jda.exceptions.DIH4JDAException;
Expand Down Expand Up @@ -71,8 +70,8 @@ JDA jda(BotConfig botConfig, ApplicationContext ctx) {
return JDABuilder.createDefault(botConfig.getSystems().getJdaBotToken())
.setStatus(OnlineStatus.DO_NOT_DISTURB)
.setChunkingFilter(ChunkingFilter.ALL)
.setMemberCachePolicy(MemberCachePolicy.ALL)
.enableCache(CacheFlag.ACTIVITY)
.setMemberCachePolicy(MemberCachePolicy.VOICE)
.enableCache(CacheFlag.ACTIVITY, CacheFlag.VOICE_STATE)
.enableIntents(GatewayIntent.GUILD_MEMBERS, GatewayIntent.GUILD_PRESENCES, GatewayIntent.MESSAGE_CONTENT)
.addEventListeners(listeners.toArray())
.build();
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/net/discordjug/javabot/api/TomcatConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.net.InetAddress;

import net.discordjug.javabot.data.config.SystemsConfig;


Expand All @@ -18,6 +20,7 @@
public class TomcatConfig {

private final int ajpPort;
private final InetAddress ajpAddress;
private final boolean tomcatAjpEnabled;
private final SystemsConfig systemsConfig;

Expand All @@ -26,11 +29,13 @@ public class TomcatConfig {
* @param ajpPort The port to run AJP under
* @param tomcatAjpEnabled <code>true</code> if AJP is enabled, else <code>false</code>
* @param systemsConfig an object representing the configuration of various systems
* @param ajpAddress the listen address for AJP
*/
public TomcatConfig(@Value("${tomcat.ajp.port}") int ajpPort, @Value("${tomcat.ajp.enabled}") boolean tomcatAjpEnabled, SystemsConfig systemsConfig) {
public TomcatConfig(@Value("${tomcat.ajp.port}") int ajpPort, @Value("${tomcat.ajp.enabled}") boolean tomcatAjpEnabled, @Value("${tomcat.ajp.address}") InetAddress ajpAddress, SystemsConfig systemsConfig) {
this.ajpPort = ajpPort;
this.tomcatAjpEnabled = tomcatAjpEnabled;
this.systemsConfig = systemsConfig;
this.ajpAddress = ajpAddress;
}

/**
Expand All @@ -46,6 +51,7 @@ TomcatServletWebServerFactory servletContainer() {
Connector ajpConnector = new Connector("org.apache.coyote.ajp.AjpNioProtocol");
AjpNioProtocol protocol= (AjpNioProtocol) ajpConnector.getProtocolHandler();
protocol.setSecret(systemsConfig.getApiConfig().getAjpSecret());
protocol.setAddress(ajpAddress);
ajpConnector.setPort(ajpPort);
ajpConnector.setSecure(true);
tomcat.addAdditionalTomcatConnectors(ajpConnector);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ public ResponseEntity<List<QOTWUserData>> getQOTWLeaderboard(
if (members == null || members.isEmpty()) {
List<QOTWAccount> topAccounts = pointsService.getTopAccounts(PAGE_AMOUNT, page);
members = topAccounts.stream()
.map(account -> new Pair<>(account, jda.retrieveUserById(account.getUserId()).complete()))
.filter(pair -> guild.isMember(pair.second()))
.map(pair -> createAPIAccount(pair.first(), pair.second(), topAccounts, page))
.map(account -> new Pair<>(account, guild.retrieveMemberById(account.getUserId()).complete()))
.filter(pair -> pair.second() != null)
.map(pair -> createAPIAccount(pair.first(), pair.second().getUser(), topAccounts, page))
.toList();
getCache().put(new Pair<>(guild.getIdLong(), page), members);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import net.discordjug.javabot.data.config.guild.ModerationConfig;
import net.discordjug.javabot.util.InteractionUtils;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.UserSnowflake;
import net.dv8tion.jda.api.entities.channel.ChannelType;
import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel;
import net.dv8tion.jda.api.events.channel.ChannelCreateEvent;
Expand Down Expand Up @@ -53,7 +54,7 @@ public void onChannelCreate(ChannelCreateEvent event) {
.setTitle("Post closed")
.setDescription("This post has been blocked because you have created other recent posts.\nPlease do not spam posts.")
.build())
.setContent(post.getOwner().getAsMention())
.setContent(UserSnowflake.fromId(post.getOwnerIdLong()).getAsMention())
.flatMap(msg -> post.getManager().setArchived(true).setLocked(true))
.queue();
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,8 @@ private void checkForumPost(@NotNull ThreadChannel post, HelpConfig config) {
private void sendDMDormantInfoIfEnabled(ThreadChannel post, HelpConfig config) {
if(Boolean.parseBoolean(preferenceService.getOrCreate(post.getOwnerIdLong(), Preference.PRIVATE_DORMANT_NOTIFICATIONS).getState())) {
post
.getOwner()
.getUser()
.openPrivateChannel()
.getJDA()
.openPrivateChannelById(post.getOwnerIdLong())
.flatMap(c -> c.sendMessageEmbeds(createDMDormantInfo(post, config)))
.queue();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback;
import net.dv8tion.jda.api.interactions.components.ActionComponent;
import net.dv8tion.jda.api.interactions.components.ActionRow;
import net.dv8tion.jda.api.interactions.components.buttons.Button;

Expand Down Expand Up @@ -254,7 +253,7 @@ private void handleHelpThanksInteraction(@NotNull ButtonInteractionEvent event,
return;
}
switch (id[2]) {
case "done" -> handleThanksCloseButton(event, manager, post);
case "done" -> handleThanksCloseButton(event, manager, post, "");
case "cancel" -> event.deferEdit().flatMap(h -> event.getMessage().delete()).queue();
default -> {
List<Button> thankButtons = event.getMessage()
Expand All @@ -266,15 +265,15 @@ private void handleHelpThanksInteraction(@NotNull ButtonInteractionEvent event,
.toList();
if (thankButtons.stream().filter(Button::isDisabled).count() ==
thankButtons.size() - 1) {
handleThanksCloseButton(event, manager, post);
handleThanksCloseButton(event, manager, post, event.getButton().getId());
} else {
event.editButton(event.getButton().asDisabled()).queue();
}
}
}
}

private void handleThanksCloseButton(@NotNull ButtonInteractionEvent event, HelpManager manager, ThreadChannel post) {
private void handleThanksCloseButton(@NotNull ButtonInteractionEvent event, HelpManager manager, ThreadChannel post, String additionalButtonId) {
List<Button> buttons = event.getMessage().getButtons();
// close post
manager.close(event, false, null);
Expand All @@ -283,8 +282,8 @@ private void handleThanksCloseButton(@NotNull ButtonInteractionEvent event, Help
experienceService.addMessageBasedHelpXP(post, true);
// thank all helpers
buttons.stream()
.filter(ActionComponent::isDisabled)
.filter(b -> b.getId() != null)
.filter(b -> b.isDisabled() || (b.getId().equals(additionalButtonId)))
.forEach(b -> manager.thankHelper(
event.getGuild(),
post,
Expand Down
25 changes: 14 additions & 11 deletions src/main/java/net/discordjug/javabot/systems/help/HelpManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,11 @@ public void close(IReplyCallback callback, boolean withHelpers, @Nullable String
.queue(s -> postThread.getManager().setLocked(true).setArchived(true).queue());
if (callback.getMember().getIdLong() != postThread.getOwnerIdLong() &&
Boolean.parseBoolean(preferenceService.getOrCreate(postThread.getOwnerIdLong(), Preference.PRIVATE_CLOSE_NOTIFICATIONS).getState())) {

postThread.getOwner().getUser().openPrivateChannel()
.flatMap(c -> createDMCloseInfoEmbed(callback.getMember(), postThread, reason, c))
.queue(success -> {}, failure -> {});
postThread
.getJDA()
.openPrivateChannelById(postThread.getOwnerIdLong())
.flatMap(c -> createDMCloseInfoEmbed(callback.getMember(), postThread, reason, c))
.queue(success -> {}, failure -> {});

botConfig.get(callback.getGuild())
.getModerationConfig()
Expand Down Expand Up @@ -191,13 +192,15 @@ public void thankHelper(@NotNull Guild guild, ThreadChannel postThread, long hel
service.performTransaction(helper.getIdLong(), config.getThankedExperience(), guild, postThread.getIdLong());
} catch (SQLException e) {
ExceptionLogger.capture(e, getClass().getSimpleName());
botConfig.get(guild).getModerationConfig().getLogChannel().sendMessageFormat(
"Could not record user %s thanking %s for help in post %s: %s",
UserUtils.getUserTag(postThread.getOwner().getUser()),
UserUtils.getUserTag(helper),
postThread.getAsMention(),
e.getMessage()
).queue();
guild.getJDA().retrieveUserById(postThread.getOwnerIdLong()).queue(owner -> {
botConfig.get(guild).getModerationConfig().getLogChannel().sendMessageFormat(
"Could not record user %s thanking %s for help in post %s: %s",
UserUtils.getUserTag(owner),
UserUtils.getUserTag(helper),
postThread.getAsMention(),
e.getMessage()
).queue();
});
}
});
}
Expand Down
Loading

0 comments on commit 919c061

Please sign in to comment.