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
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
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
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