Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
68169fc
Bump com.diffplug.spotless from 8.0.0 to 8.1.0 (#1348)
dependabot[bot] Nov 20, 2025
d2fc29f
Bump org.sonarqube from 7.0.1.6134 to 7.1.0.6387 (#1349)
dependabot[bot] Nov 21, 2025
9c552b6
Bump name.remal.sonarlint from 6.0.0 to 7.0.0 (#1351)
dependabot[bot] Nov 25, 2025
4497161
Bump org.flywaydb:flyway-core from 11.17.0 to 11.18.0 (#1352)
dependabot[bot] Nov 28, 2025
13d706e
Bump com.apptasticsoftware:rssreader from 3.11.0 to 3.12.0 (#1354)
dependabot[bot] Dec 2, 2025
bcb192d
Fixes "/reminder list" fails if no reminders (#1353)
Zachdehooge Dec 2, 2025
b2eacf6
Bump org.sonarqube from 7.1.0.6387 to 7.2.0.6526 (#1355)
dependabot[bot] Dec 5, 2025
eeb7f09
Bump com.gradleup.shadow from 9.2.2 to 9.3.0 (#1357)
dependabot[bot] Dec 8, 2025
c03731a
Bump org.apache.commons:commons-text from 1.14.0 to 1.15.0 (#1356)
dependabot[bot] Dec 8, 2025
1f70efa
Bump org.mockito:mockito-core from 5.20.0 to 5.21.0 (#1359)
dependabot[bot] Dec 25, 2025
72cc22f
Bump org.flywaydb:flyway-core from 11.18.0 to 11.20.0 (#1363)
dependabot[bot] Dec 25, 2025
875b8da
fix: rss failures with exponential blacklist (#1362)
ankitsmt211 Dec 29, 2025
04b94df
(removed explicit name from CLA, not needed)
Zabuzard Dec 29, 2025
4e1c132
hotfix(RSSHandlerRoutine): resolve java:S121 (#1364)
christolis Dec 29, 2025
aa12c13
pre-commit: run sonarlintMain task (#1368)
christolis Jan 2, 2026
5bc588e
Add VoiceReceiver logic (#1369)
christolis Jan 3, 2026
fb6cbf7
VoiceReceiverAdapter.java: add default missing doc (#1371)
christolis Jan 4, 2026
ffb1650
Bump org.jsoup:jsoup from 1.21.1 to 1.22.1 (#1367)
dependabot[bot] Jan 6, 2026
03baab7
Added /message commands (#1372)
Zabuzard Jan 6, 2026
954be5b
Update ChatGPT service to use `gpt-5-nano` model (#1373)
tj-wazei Jan 7, 2026
938c6c1
Bump com.openai:openai-java from 4.13.0 to 4.14.0 (#1375)
dependabot[bot] Jan 8, 2026
5009653
Update ChatGPT model to accept ChatModel & use 4.1-mini for questions…
tj-wazei Jan 10, 2026
44bb86b
Bump com.openai:openai-java from 4.14.0 to 4.15.0 (#1377)
dependabot[bot] Jan 12, 2026
ef6fa5a
Bump com.github.freva:ascii-table from 1.8.0 to 1.9.0 (#1379)
dependabot[bot] Jan 15, 2026
e43f7c3
Implement Quotes Board (#1029)
christolis Jan 15, 2026
e1f3022
Bump com.openai:openai-java from 4.15.0 to 4.16.0 (#1381)
dependabot[bot] Jan 22, 2026
c8003e7
Bump com.diffplug.spotless from 8.1.0 to 8.2.0 (#1382)
dependabot[bot] Jan 23, 2026
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
2 changes: 0 additions & 2 deletions CLA.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,6 @@ IF THE DISCLAIMER AND DAMAGE WAIVER MENTIONED IN SECTION 5. AND SECTION 6. CANNO

### Us

Name: Daniel Tischner (aka Zabuzard, acting on behalf of Together Java)

Organization: https://github.com/Together-Java

Contact: https://discord.com/invite/XXFUXzK
Expand Down
15 changes: 7 additions & 8 deletions application/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ buildscript {
plugins {
id 'application'
id 'com.google.cloud.tools.jib' version '3.5.0'
id 'com.gradleup.shadow' version '9.2.2'
id 'com.gradleup.shadow' version '9.3.0'
id 'database-settings'
}

Expand Down Expand Up @@ -57,7 +57,7 @@ dependencies {

implementation 'io.mikael:urlbuilder:2.0.9'

implementation 'org.jsoup:jsoup:1.21.1'
implementation 'org.jsoup:jsoup:1.22.1'

implementation 'org.scilab.forge:jlatexmath:1.0.7'
implementation 'org.scilab.forge:jlatexmath-font-greek:1.0.7'
Expand All @@ -69,25 +69,24 @@ dependencies {
implementation "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion"
implementation "com.sigpwned:jackson-modules-java17-sealed-classes:2.19.0.0"

implementation 'com.github.freva:ascii-table:1.8.0'
implementation 'com.github.freva:ascii-table:1.9.0'

implementation 'io.github.url-detector:url-detector:0.1.23'

implementation 'com.github.ben-manes.caffeine:caffeine:3.2.0'

implementation 'org.kohsuke:github-api:1.329'

implementation 'org.apache.commons:commons-text:1.14.0'
implementation 'com.apptasticsoftware:rssreader:3.11.0'
implementation 'org.apache.commons:commons-text:1.15.0'
implementation 'com.apptasticsoftware:rssreader:3.12.0'

testImplementation 'org.mockito:mockito-core:5.20.0'
testImplementation 'org.mockito:mockito-core:5.21.0'
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion"
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"

implementation "com.theokanning.openai-gpt3-java:api:$chatGPTVersion"
implementation "com.theokanning.openai-gpt3-java:service:$chatGPTVersion"
implementation "com.openai:openai-java:$chatGPTVersion"
}

application {
Expand Down
5 changes: 5 additions & 0 deletions application/config.json.template
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,11 @@
"videoLinkPattern": "http(s)?://www\\.youtube.com.*",
"pollIntervalInMinutes": 10
},
"quoteBoardConfig": {
"minimumReactionsToTrigger": 5,
"channel": "quotes",
"reactionEmoji": "⭐"
},
"memberCountCategoryPattern": "Info",
"topHelpers": {
"rolePattern": "Top Helper.*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public final class Config {
private final RSSFeedsConfig rssFeedsConfig;
private final String selectRolesChannelPattern;
private final String memberCountCategoryPattern;
private final QuoteBoardConfig quoteBoardConfig;
private final TopHelpersConfig topHelpers;

@SuppressWarnings("ConstructorWithTooManyParameters")
Expand Down Expand Up @@ -102,6 +103,8 @@ private Config(@JsonProperty(value = "token", required = true) String token,
@JsonProperty(value = "rssConfig", required = true) RSSFeedsConfig rssFeedsConfig,
@JsonProperty(value = "selectRolesChannelPattern",
required = true) String selectRolesChannelPattern,
@JsonProperty(value = "quoteBoardConfig",
required = true) QuoteBoardConfig quoteBoardConfig,
@JsonProperty(value = "topHelpers", required = true) TopHelpersConfig topHelpers) {
this.token = Objects.requireNonNull(token);
this.githubApiKey = Objects.requireNonNull(githubApiKey);
Expand Down Expand Up @@ -137,6 +140,7 @@ private Config(@JsonProperty(value = "token", required = true) String token,
this.featureBlacklistConfig = Objects.requireNonNull(featureBlacklistConfig);
this.rssFeedsConfig = Objects.requireNonNull(rssFeedsConfig);
this.selectRolesChannelPattern = Objects.requireNonNull(selectRolesChannelPattern);
this.quoteBoardConfig = Objects.requireNonNull(quoteBoardConfig);
this.topHelpers = Objects.requireNonNull(topHelpers);
}

Expand Down Expand Up @@ -431,6 +435,18 @@ public String getSelectRolesChannelPattern() {
return selectRolesChannelPattern;
}

/**
* The configuration of the quote messages config.
*
* <p>
* >The configuration of the quote board feature. Quotes user selected messages.
*
* @return configuration of quote messages config
*/
public QuoteBoardConfig getQuoteBoardConfig() {
return quoteBoardConfig;
}

/**
* Gets the pattern matching the category that is used to display the total member count.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.togetherjava.tjbot.config;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonRootName;
import org.apache.logging.log4j.LogManager;

import org.togetherjava.tjbot.features.basic.QuoteBoardForwarder;

import java.util.Objects;

/**
* Configuration for the quote board feature, see {@link QuoteBoardForwarder}.
*/
@JsonRootName("quoteBoardConfig")
public record QuoteBoardConfig(
@JsonProperty(value = "minimumReactionsToTrigger", required = true) int minimumReactions,
@JsonProperty(required = true) String channel,
@JsonProperty(value = "reactionEmoji", required = true) String reactionEmoji) {

/**
* Creates a QuoteBoardConfig.
*
* @param minimumReactions the minimum amount of reactions
* @param channel the pattern for the board channel
* @param reactionEmoji the emoji with which users should react to
*/
public QuoteBoardConfig {
if (minimumReactions <= 0) {
throw new IllegalArgumentException("minimumReactions must be greater than zero");
}
Objects.requireNonNull(channel);
if (channel.isBlank()) {
throw new IllegalArgumentException("channel must not be empty or blank");
}
Objects.requireNonNull(reactionEmoji);
if (reactionEmoji.isBlank()) {
throw new IllegalArgumentException("reactionEmoji must not be empty or blank");
}
LogManager.getLogger(QuoteBoardConfig.class)
.debug("Quote-Board configs loaded: minimumReactions={}, channel='{}', reactionEmoji='{}'",
minimumReactions, channel, reactionEmoji);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.togetherjava.tjbot.db.Database;
import org.togetherjava.tjbot.features.basic.MemberCountDisplayRoutine;
import org.togetherjava.tjbot.features.basic.PingCommand;
import org.togetherjava.tjbot.features.basic.QuoteBoardForwarder;
import org.togetherjava.tjbot.features.basic.RoleSelectCommand;
import org.togetherjava.tjbot.features.basic.SlashCommandEducator;
import org.togetherjava.tjbot.features.basic.SuggestionsUpDownVoter;
Expand Down Expand Up @@ -39,6 +40,7 @@
import org.togetherjava.tjbot.features.mathcommands.TeXCommand;
import org.togetherjava.tjbot.features.mathcommands.wolframalpha.WolframAlphaCommand;
import org.togetherjava.tjbot.features.mediaonly.MediaOnlyChannelListener;
import org.togetherjava.tjbot.features.messages.MessageCommand;
import org.togetherjava.tjbot.features.moderation.BanCommand;
import org.togetherjava.tjbot.features.moderation.KickCommand;
import org.togetherjava.tjbot.features.moderation.ModerationActionsStore;
Expand Down Expand Up @@ -160,6 +162,7 @@ public static Collection<Feature> createFeatures(JDA jda, Database database, Con
features.add(new CodeMessageManualDetection(codeMessageHandler));
features.add(new SlashCommandEducator());
features.add(new PinnedNotificationRemover(config));
features.add(new QuoteBoardForwarder(config));

// Event receivers
features.add(new RejoinModerationRoleListener(actionsStore, config));
Expand Down Expand Up @@ -203,6 +206,7 @@ public static Collection<Feature> createFeatures(JDA jda, Database database, Con
features.add(new BookmarksCommand(bookmarksSystem));
features.add(new ChatGptCommand(chatGptService, helpSystemHelper));
features.add(new JShellCommand(jshellEval));
features.add(new MessageCommand());

FeatureBlacklist<Class<?>> blacklist = blacklistConfig.normal();
return blacklist.filterStream(features.stream(), Object::getClass).toList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import net.dv8tion.jda.api.events.message.MessageDeleteEvent;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.events.message.MessageUpdateEvent;
import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent;

import java.util.regex.Pattern;

Expand Down Expand Up @@ -56,4 +57,13 @@ public interface MessageReceiver extends Feature {
* message that was deleted
*/
void onMessageDeleted(MessageDeleteEvent event);

/**
* Triggered by the core system whenever a new reaction was added to a message in a text channel
* of a guild the bot has been added to.
*
* @param event the event that triggered this, containing information about the corresponding
* reaction that was added
*/
void onMessageReactionAdd(MessageReactionAddEvent event);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import net.dv8tion.jda.api.events.message.MessageDeleteEvent;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.events.message.MessageUpdateEvent;
import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent;

import java.util.regex.Pattern;

Expand Down Expand Up @@ -57,4 +58,10 @@ public void onMessageUpdated(MessageUpdateEvent event) {
public void onMessageDeleted(MessageDeleteEvent event) {
// Adapter does not react by default, subclasses may change this behavior
}

@SuppressWarnings("NoopMethodInAbstractClass")
@Override
public void onMessageReactionAdd(MessageReactionAddEvent event) {
// Adapter does not react by default, subclasses may change this behavior
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.togetherjava.tjbot.features;

import net.dv8tion.jda.api.events.guild.voice.GuildVoiceDeafenEvent;
import net.dv8tion.jda.api.events.guild.voice.GuildVoiceMuteEvent;
import net.dv8tion.jda.api.events.guild.voice.GuildVoiceStreamEvent;
import net.dv8tion.jda.api.events.guild.voice.GuildVoiceUpdateEvent;
import net.dv8tion.jda.api.events.guild.voice.GuildVoiceVideoEvent;

import java.util.regex.Pattern;

/**
* Receives incoming Discord guild events from voice channels matching a given pattern.
* <p>
* All voice receivers have to implement this interface. For convenience, there is a
* {@link VoiceReceiverAdapter} available that implemented most methods already. A new receiver can
* then be registered by adding it to {@link Features}.
* <p>
* <p>
* After registration, the system will notify a receiver whenever a new event was sent or an
* existing event was updated in any channel matching the {@link #getChannelNamePattern()} the bot
* is added to.
*/
public interface VoiceReceiver extends Feature {
/**
* Retrieves the pattern matching the names of channels of which this receiver is interested in
* receiving events from. Called by the core system once during the startup in order to register
* the receiver accordingly.
* <p>
* Changes on the pattern returned by this method afterwards will not be picked up.
*
* @return the pattern matching the names of relevant channels
*/
Pattern getChannelNamePattern();

/**
* Triggered by the core system whenever a member joined, left or moved voice channels.
*
* @param event the event that triggered this
*/
void onVoiceUpdate(GuildVoiceUpdateEvent event);

/**
* Triggered by the core system whenever a member toggled their camera in a voice channel.
*
* @param event the event that triggered this
*/
void onVideoToggle(GuildVoiceVideoEvent event);

/**
* Triggered by the core system whenever a member started or stopped a stream.
*
* @param event the event that triggered this
*/
void onStreamToggle(GuildVoiceStreamEvent event);

/**
* Triggered by the core system whenever a member toggled their mute status.
*
* @param event the event that triggered this
*/
void onMuteToggle(GuildVoiceMuteEvent event);

/**
* Triggered by the core system whenever a member toggled their deafened status.
*
* @param event the event that triggered this
*/
void onDeafenToggle(GuildVoiceDeafenEvent event);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package org.togetherjava.tjbot.features;

import net.dv8tion.jda.api.events.guild.voice.GuildVoiceDeafenEvent;
import net.dv8tion.jda.api.events.guild.voice.GuildVoiceMuteEvent;
import net.dv8tion.jda.api.events.guild.voice.GuildVoiceStreamEvent;
import net.dv8tion.jda.api.events.guild.voice.GuildVoiceUpdateEvent;
import net.dv8tion.jda.api.events.guild.voice.GuildVoiceVideoEvent;

import java.util.regex.Pattern;

/**
* Adapter implementation of a {@link VoiceReceiver}. A new receiver can then be registered by
* adding it to {@link Features}.
* <p>
* {@link #onVoiceUpdate(GuildVoiceUpdateEvent)} like the other provided methods can be overridden
* if desired. The default implementation is empty, the adapter will not react to such events.
*/
public class VoiceReceiverAdapter implements VoiceReceiver {

private final Pattern channelNamePattern;

protected VoiceReceiverAdapter() {
this(Pattern.compile(".*"));
}

protected VoiceReceiverAdapter(Pattern channelNamePattern) {
this.channelNamePattern = channelNamePattern;
}

@Override
public Pattern getChannelNamePattern() {
return channelNamePattern;
}

@Override
public void onVoiceUpdate(GuildVoiceUpdateEvent event) {
// Adapter does not react by default, subclasses may change this behavior
}

@Override
public void onVideoToggle(GuildVoiceVideoEvent event) {
// Adapter does not react by default, subclasses may change this behavior
}

@Override
public void onStreamToggle(GuildVoiceStreamEvent event) {
// Adapter does not react by default, subclasses may change this behavior
}

@Override
public void onMuteToggle(GuildVoiceMuteEvent event) {
// Adapter does not react by default, subclasses may change this behavior
}

@Override
public void onDeafenToggle(GuildVoiceDeafenEvent event) {
// Adapter does not react by default, subclasses may change this behavior
}
}
Loading
Loading