Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
e190991
Merge pull request #1 from stuffyerface/main
stuffyerface Aug 31, 2024
2c515f1
Merge pull request #2 from stuffyerface/development
stuffyerface Aug 31, 2024
e2134ce
add lastupdated field
stuffyerface Sep 3, 2024
6fca3e1
tournaments schema
stuffyerface Sep 3, 2024
83cd515
fix indents
stuffyerface Sep 3, 2024
80a0cba
fix indents
stuffyerface Sep 4, 2024
daf9b37
pretty colors
stuffyerface Sep 4, 2024
a11864e
set display name from mojang
stuffyerface Sep 4, 2024
e506fa8
Detect imminent shutdown
stuffyerface Sep 4, 2024
7ea6c45
rm todo
stuffyerface Sep 4, 2024
14bf429
log api errors
stuffyerface Sep 4, 2024
851b79d
support for username from uuid
stuffyerface Sep 4, 2024
760bd4a
Github API, GlobalData
stuffyerface Sep 4, 2024
ef4ae9f
Account Linking
stuffyerface Sep 4, 2024
b18cc94
unfinished achievement stuff
stuffyerface Sep 4, 2024
e6a558c
tournament schema
stuffyerface Sep 4, 2024
88d05a8
store bot data
stuffyerface Sep 4, 2024
0e96ce6
update bot stats
stuffyerface Sep 4, 2024
c7977d7
update every 2 hours, disregard empty updates
stuffyerface Sep 4, 2024
88b336b
log uploads.
stuffyerface Sep 4, 2024
1325683
fix updating linked database
stuffyerface Sep 5, 2024
60c4a4b
fix typo
stuffyerface Sep 5, 2024
99f518f
message content stuff
stuffyerface Sep 5, 2024
eaea3ed
PlayCommand Command
stuffyerface Sep 11, 2024
fc6c1c5
Merge pull request #3 from stuffyerface/main
stuffyerface Sep 11, 2024
75da862
HelpCommand and updated links
stuffyerface Sep 11, 2024
019d4e4
Achievement Command basic stuff
stuffyerface Sep 13, 2024
b97a6b4
easiest challenge ap
stuffyerface Sep 13, 2024
df9c0a1
easiest challenge ap
stuffyerface Sep 13, 2024
5447e00
achievement command basics
stuffyerface Sep 30, 2024
3892f00
Search command
stuffyerface Oct 27, 2024
1520e0b
Refactor Global Data
stuffyerface Mar 31, 2025
63628e7
move verify handler code
stuffyerface Mar 31, 2025
09ccae4
setup setup command
stuffyerface Mar 31, 2025
b5283ce
verification handling
stuffyerface Mar 31, 2025
b7f8740
config.properties
stuffyerface Apr 1, 2025
dcfad39
custom status
stuffyerface Apr 1, 2025
270af7f
Merge pull request #4 from stuffyerface/development
stuffyerface Apr 1, 2025
931508f
Update config.properties
stuffyerface Apr 1, 2025
3409cfe
maven stuff
stuffyerface Apr 1, 2025
f886531
procfile slashes
stuffyerface Apr 1, 2025
a707c49
global vs local registration by environment scope
stuffyerface Apr 1, 2025
fef26eb
remove message content intents
stuffyerface Apr 3, 2025
2b47b27
add total users to bot stats
stuffyerface Apr 11, 2025
23b3e09
compile stuff
stuffyerface Apr 11, 2025
0bcaafe
fix issue with mega walls skins data not working
stuffyerface Apr 16, 2025
3524872
Update to JDA 6.2.0
stuffyerface Dec 27, 2025
dafb3c9
Add /uuid Command
stuffyerface Dec 28, 2025
7fda482
Merge branch 'production' into development
stuffyerface Dec 28, 2025
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
15 changes: 15 additions & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# These are supported funding model platforms

github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: stuffydev # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
thanks_dev: # Replace with a single thanks.dev username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*

# Configuration files
*.properties
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
worker: java -jar ./target/stuffybot-java-1.0-SNAPSHOT.jar
58 changes: 56 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
</repositories>
<build>
<plugins>
<!-- Compiler plugin for Java 17 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
Expand All @@ -23,13 +24,56 @@
<target>17</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<archive>
<manifest>
<mainClass>me.stuffy.stuffybot.Bot</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>me.stuffy.stuffybot.Bot</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>2.15.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>net.dv8tion</groupId>
<artifactId>JDA</artifactId>
<version>5.0.0</version>
<version>6.2.0</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
Expand All @@ -39,7 +83,17 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1-jre</version>
<version>33.4.8-jre</version>
</dependency>
<dependency>
<groupId>org.kohsuke</groupId>
<artifactId>github-api</artifactId>
<version>1.324</version>
</dependency>
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>5.5.2</version>
</dependency>
</dependencies>
</project>
147 changes: 117 additions & 30 deletions src/main/java/me/stuffy/stuffybot/Bot.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
import me.stuffy.stuffybot.events.ActiveEvents;
import me.stuffy.stuffybot.events.UpdateBotStatsEvent;
import me.stuffy.stuffybot.interactions.InteractionHandler;
import me.stuffy.stuffybot.utils.DiscordUtils;
import me.stuffy.stuffybot.profiles.GlobalData;
import me.stuffy.stuffybot.utils.Config;
import me.stuffy.stuffybot.utils.Logger;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder;
Expand All @@ -14,62 +15,127 @@
import net.dv8tion.jda.api.events.guild.GuildJoinEvent;
import net.dv8tion.jda.api.events.guild.GuildLeaveEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.dv8tion.jda.api.interactions.IntegrationType;
import net.dv8tion.jda.api.interactions.InteractionContextType;
import net.dv8tion.jda.api.interactions.commands.Command;
import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
import org.kohsuke.github.GitHub;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;

import static me.stuffy.stuffybot.utils.APIUtils.connectToGitHub;
import static me.stuffy.stuffybot.utils.APIUtils.uploadLogs;

public class Bot extends ListenerAdapter {
private static Bot INSTANCE;
private final JDA jda;
private Guild homeGuild;
private final Guild homeGuild;
private static GitHub GITHUB;
private static GlobalData GLOBAL_DATA;


public Bot() throws InterruptedException {
INSTANCE = this;
// Get token from env variable
String token = System.getenv("BOT_TOKEN");
JDABuilder builder = JDABuilder.createDefault(token) ;
builder.setActivity(Activity.customStatus("hating slash commands"));
JDABuilder builder = JDABuilder.createDefault(token);
// builder.enableIntents(GatewayIntent.MESSAGE_CONTENT); // # TODO: Remove intents when possible
String customStatus = Config.getCustomStatus();
builder.setActivity(Activity.customStatus(customStatus));
builder.addEventListeners(this);
JDA jda = builder.build().awaitReady();
this.jda = jda;

String homeGuildID = Config.getHomeGuildId();
// Initialize home guild
this.homeGuild = jda.getGuildById("795108903733952562");
this.homeGuild = jda.getGuildById(homeGuildID);
assert this.homeGuild != null : "Failed to find home guild";


// Log startup
String time = DiscordUtils.discordTimeNow();
String self = jda.getSelfUser().getAsMention();
Logger.log("<Startup> Bot " + self + " started successfully " + time + ".");
String startupTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss"));
String self = jda.getSelfUser().getName();
Logger.setLogName(startupTime);
String environment = Config.getEnvironment();
Logger.log("<Startup> Bot " + self + " started successfully " + startupTime + ". Environment: " + environment);

// Initialize GitHub
GITHUB = connectToGitHub();

// Initialize Global Data
GLOBAL_DATA = new GlobalData();

// Listen for interactions
jda.addEventListener(
new InteractionHandler()
);

// Register commands "global"ly or "local"ly
registerCommands("local");
String environmentScope = switch (Config.getEnvironment()) {
case "development" -> "local";
case "production", "development_global" -> "global";
default -> throw new IllegalArgumentException("Invalid environment: " + Config.getEnvironment());
};
registerCommands(environmentScope);

// Start events
new UpdateBotStatsEvent().startFixedRateEvent();
new ActiveEvents().startFixedRateEvent();

// Handle SIGTERM
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
Logger.log("<Shutdown> Bot shutting down, saving data...");

// Close JDA
jda.shutdown();

// Update Bot Stats
try{
UpdateBotStatsEvent.publicExecute();
Logger.log("<Shutdown> Bot Stats saved, allowing for shutdown.");
} catch (Exception e) {
Logger.log("<Shutdown> Failed to save Bot Stats, allowing for shutdown.");
}
try {
uploadLogs();
Logger.log("<Shutdown> Logs uploaded, allowing for shutdown.");
}
catch (Exception e) {
Logger.log("<Shutdown> Failed to upload logs, allowing for shutdown.");
}
}));
}
public static Bot getInstance() {
return INSTANCE;
}

public static GitHub getGitHub() {
return GITHUB;
}

public static GlobalData getGlobalData() {
return GLOBAL_DATA;
}

public Guild getHomeGuild() {
return this.homeGuild;
}


public Role getVerifiedRole() {
return this.homeGuild.getRoleById("795118862940635216");
String roleID = Config.getVerifiedRoleId();
return this.homeGuild.getRoleById(roleID);
}

public Role getNotVerifiedRole() {
String roleID = Config.getNotVerifiedRoleId();
return this.homeGuild.getRoleById(roleID);
}

public static void main(String[] args) throws InterruptedException {
Expand All @@ -93,31 +159,53 @@ public void onGuildLeave(GuildLeaveEvent event) {
Logger.log("<Guilds> Bot left guild: " + leftGuild.getName() + " (" + leftGuild.getId() + ")");
}

public void registerCommands(String scope) {
private SlashCommandData createSlashCommand(String name, String description) {
return Commands.slash(name, description).setContexts(InteractionContextType.ALL).setIntegrationTypes(IntegrationType.ALL);
}

private void registerCommands(String scope) {
OptionData ignOption = new OptionData(OptionType.STRING, "ign", "The player's IGN", false);
OptionData ignOptionRequired = new OptionData(OptionType.STRING, "ign", "The player's IGN", true);
// Create a list of commands first
ArrayList<CommandData> commandList = new ArrayList<>();
// commandList.add(Commands.slash("help", "*Should* show a help message"));
commandList.add(Commands.slash("pit", "Get Pit stats for a player")
commandList.add(createSlashCommand("help", "Learn about the bot and its commands"));
commandList.add(createSlashCommand("pit", "Get Pit stats for a player")
.addOptions(ignOption));
commandList.add(Commands.slash("stats", "Get Hypixel stats for a player")
commandList.add(createSlashCommand("stats", "Get Hypixel stats for a player")
.addOptions(ignOption));
commandList.add(Commands.slash("tkr", "Get TKR stats for a player")
commandList.add(createSlashCommand("tkr", "Get TKR stats for a player")
.addOptions(ignOption));
commandList.add(Commands.slash("maxes", "Get maxed games for a player")
commandList.add(createSlashCommand("maxes", "Get maxed games for a player")
.addOptions(ignOption));
commandList.add(Commands.slash("blitz", "Get Blitz Ultimate Kit xp for a player")
commandList.add(createSlashCommand("blitz", "Get Blitz Ultimate Kit xp for a player")
.addOptions(ignOption));
commandList.add(Commands.slash("megawalls", "Get Mega Walls skins for a player")
commandList.add(createSlashCommand("megawalls", "Get Mega Walls skins for a player")
.addOptions(ignOption)
.addOptions(new OptionData(OptionType.STRING, "skins", "Which skins to look at", false).setAutoComplete(true)));
commandList.add(Commands.slash("tournament", "Get tournament stats for a player")
commandList.add(createSlashCommand("tournament", "Get tournament stats for a player")
.addOptions(ignOption)
.addOptions(new OptionData(OptionType.INTEGER, "tournament", "Which tournament to look at (Leave empty for latest)", false).setAutoComplete(true)));
commandList.add(createSlashCommand("achievements", "Get achievement stats for a player")
.addOptions(ignOption)
.addOptions(new OptionData(OptionType.STRING, "game", "Which game to look at", false).setAutoComplete(true))
.addOptions(new OptionData(OptionType.STRING, "type", "Which achievements to look at", false).addChoices(
new Command.Choice("All", "all"),
new Command.Choice("Challenge", "challenge"),
new Command.Choice("Tiered", "tiered")
)
));
commandList.add(createSlashCommand("link", "Link a Minecraft account so you don't have to type your IGN every time")
.addOptions(ignOptionRequired));
commandList.add(createSlashCommand("playcommand", "Lookup the command to quickly hop into a game")
.addOptions(new OptionData(OptionType.STRING, "game", "Search for a play command", true).setAutoComplete(true)));
commandList.add(createSlashCommand("search", "Search for an achievement by name, or description.")
.addOptions(new OptionData(OptionType.STRING, "search", "Search for an Achievement", true).setAutoComplete(true)));
commandList.add(createSlashCommand("uuid", "Get UUID info for a Minecraft player")
.addOptions(ignOptionRequired));


if (scope.equals("local")) {
//clearLocalCommands();
jda.updateCommands().queue();
this.homeGuild.updateCommands().addCommands(
commandList
).queue();
Expand All @@ -130,15 +218,14 @@ public void registerCommands(String scope) {
} else {
throw new IllegalArgumentException("Invalid scope: " + scope);
}
}

public void clearCommands() {
jda.updateCommands().queue();
Logger.log("<Commands> Successfully cleared commands.");
}

public void clearLocalCommands() {
this.homeGuild.updateCommands().queue();
Logger.log("<Commands> Successfully cleared local commands.");
// Setup commands for home guild only
this.homeGuild.upsertCommand(
Commands.slash("setup", "Home guild setup command")
.setDefaultPermissions(DefaultMemberPermissions.DISABLED)
.addOptions(new OptionData(OptionType.STRING, "tosetup", "Which thing you wish to Setup", true).addChoices(
new Command.Choice("Verify", "verify")
))
).queue();
}
}
Loading