initial bot and in-memory storage

This commit is contained in:
2025-07-07 19:47:52 +02:00
commit ecc9d54c3c
12 changed files with 347 additions and 0 deletions
+39
View File
@@ -0,0 +1,39 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
logs/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store
+10
View File
@@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Environment-dependent path to Maven home directory
/mavenHomeManager.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
</component>
</project>
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="openjdk-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
+67
View File
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>me.youhavetrouble.inviter</groupId>
<artifactId>Inviter</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.23.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.23.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.23.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.8</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.46.1.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.1.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.dv8tion</groupId>
<artifactId>JDA</artifactId>
<version>5.6.1</version>
<exclusions>
<exclusion>
<groupId>club.minnced</groupId>
<artifactId>opus-java</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
+3
View File
@@ -0,0 +1,3 @@
# Inviter
Discord bot that allows users to set up personalized invite links for their servers and have rotating invite codes
@@ -0,0 +1,28 @@
package me.youhavetrouble.inviter;
public record DiscordInvite(
String code,
Long guildId,
Long expiresAt
) {
public DiscordInvite {
if (code == null) {
throw new IllegalArgumentException("Code cannot be null");
}
if (guildId == null || guildId <= 0) {
throw new IllegalArgumentException("Guild ID must not be null nor be a negative number");
}
}
/**
* Checks if the invite is expired.
* The invite is considered expired if the current time is more than 5 seconds before the expiration time.
*
* @return true if the invite is expired, false otherwise
*/
public boolean isExpired() {
return System.currentTimeMillis() + 5000 > expiresAt;
}
}
@@ -0,0 +1,49 @@
package me.youhavetrouble.inviter;
import me.youhavetrouble.inviter.storage.MemoryStorage;
import me.youhavetrouble.inviter.storage.Storage;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.requests.GatewayIntent;
import net.dv8tion.jda.api.utils.cache.CacheFlag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Set;
public class Main {
private static final Logger LOGGER = LoggerFactory.getLogger("Inviter");
private static JDA jda;
private static Storage storage;
public static void main(String[] args) throws InterruptedException {
String token = System.getenv("DISCORD_TOKEN");
jda = JDABuilder.create(
token,
Set.of(GatewayIntent.GUILD_INVITES)
)
.disableCache(
CacheFlag.ACTIVITY,
CacheFlag.VOICE_STATE,
CacheFlag.EMOJI,
CacheFlag.STICKER,
CacheFlag.CLIENT_STATUS,
CacheFlag.ONLINE_STATUS,
CacheFlag.SCHEDULED_EVENTS
)
.build();
jda.awaitReady();
storage = new MemoryStorage(jda);
LOGGER.info("Welcome to the Inviter Application!");
}
}
@@ -0,0 +1,75 @@
package me.youhavetrouble.inviter.storage;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import me.youhavetrouble.inviter.DiscordInvite;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Invite;
import net.dv8tion.jda.api.entities.channel.unions.DefaultGuildChannelUnion;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
public class MemoryStorage implements Storage {
private final Cache<String, DiscordInvite> cache = Caffeine.newBuilder()
.expireAfterWrite(Duration.of(60, ChronoUnit.SECONDS))
.build();
private final JDA jda;
public MemoryStorage(JDA jda) {
this.jda = jda;
}
@Nullable
@Override
public DiscordInvite getInvite(long guildId) {
DiscordInvite discordInvite = cache.getIfPresent(String.valueOf(guildId));
if (discordInvite == null || discordInvite.isExpired()) {
Guild guild = jda.getGuildById(guildId);
if (guild == null) {
return null; // Guild not found
}
DefaultGuildChannelUnion defaultChannel = guild.getDefaultChannel();
if (defaultChannel == null) {
return null; // No default channel found
}
Invite invite = defaultChannel.createInvite()
.setMaxAge(60) // Set the invite to expire after 60 seconds
.complete();
if (invite == null) return null; // Failed to create invite
discordInvite = new DiscordInvite(
invite.getCode(),
guild.getIdLong(),
invite.getTimeCreated().toEpochSecond()
);
}
return discordInvite;
}
@NotNull
@Override
public DiscordInvite saveInvite(Invite invite) {
if (invite == null) {
throw new IllegalArgumentException("Invite cannot be null");
}
if (invite.getGuild() == null) {
throw new IllegalArgumentException("Invite must be associated with a guild");
}
DiscordInvite discordInvite = new DiscordInvite(
invite.getCode(),
invite.getGuild().getIdLong(),
invite.getTimeCreated().toEpochSecond()
);
cache.put(invite.getGuild().getId(), discordInvite);
return discordInvite;
}
}
@@ -0,0 +1,19 @@
package me.youhavetrouble.inviter.storage;
import me.youhavetrouble.inviter.DiscordInvite;
import net.dv8tion.jda.api.entities.Invite;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public interface Storage {
@Nullable DiscordInvite getInvite(long guildId);
/**
* Saves the invite to the storage and returns the saved invite.
* @param invite JDA invite object to save
* @return the saved DiscordInvite object
*/
@NotNull DiscordInvite saveInvite(Invite invite);
}
+30
View File
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration name="Console">
<Properties>
<Property name="LOG_PATTERN">[%d{yyyy-MM-dd HH:mm:ss}] [%t/%p]: %msg%n</Property>
</Properties>
<Appenders>
<Console name="ConsoleTerminal" target="SYSTEM_OUT" follow="true">
<PatternLayout pattern="[%d{HH:mm:ss}] [%t/%p]: %msg%n"/>
</Console>
<RollingRandomAccessFile name="File" fileName="logs/latest.log" filePattern="logs/%d{yyyy-MM-dd}-%i.log.gz" immediateFlush="false">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss}] [%t/%p]: %msg%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<OnStartupTriggeringPolicy/>
</Policies>
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="ConsoleTerminal"/>
<AppenderRef ref="File"/>
</Root>
</Loggers>
</Configuration>