Skip to content
Draft
Show file tree
Hide file tree
Changes from 8 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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ gradle-app.setting

# End of https://www.toptal.com/developers/gitignore/api/netbeans,intellij,java,gradle,eclipse
application/db/
config.json
secrets.json
application/config.json
*.db
*.db-shm
Expand Down
8 changes: 8 additions & 0 deletions application/secrets.json.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"token": "<put_your_token_here>",
"githubApiKey": "<your_github_personal_access_token>",
"logInfoChannelWebhook": "<put_your_webhook_here>",
"logErrorChannelWebhook": "<put_your_webhook_here>",
"openaiApiKey": "<check pins in #tjbot_discussion for the key>",
"jshellBaseUrl": "<put_jshell_rest_api_url_here>"
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@
import org.togetherjava.tjbot.features.system.BotCore;
import org.togetherjava.tjbot.logging.LogMarkers;
import org.togetherjava.tjbot.logging.discord.DiscordLogging;
import org.togetherjava.tjbot.secrets.Secrets;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.SQLException;
Expand All @@ -33,7 +36,8 @@ private Application() {
}

private static final Logger logger = LoggerFactory.getLogger(Application.class);
private static final String DEFAULT_CONFIG_PATH = "config.json";
private static final String DEFAULT_CONFIG_PATH = "/config.json";
private static final String DEFAULT_SECRETS_PATH = "secrets.json";

/**
* Starts the application.
Expand All @@ -48,30 +52,46 @@ public static void main(final String[] args) {
+ DEFAULT_CONFIG_PATH + "' will be assumed.");
}

Path configPath = Path.of(args.length == 1 ? args[0] : DEFAULT_CONFIG_PATH);
String configPath = args.length == 1 ? args[0] : DEFAULT_CONFIG_PATH;
Config config;

try (InputStream stream = Application.class.getResourceAsStream(configPath)) {
if (stream == null) {
throw new IOException("InputStream is null when loading " + configPath);
}

config = Config.load(new String(stream.readAllBytes(), StandardCharsets.UTF_8));

} catch (IOException e) {
logger.error("Unable to load the configuration file from path '{}'", configPath, e);
return;
}

Path secretsPath = Path.of(args.length == 1 ? args[0] : DEFAULT_SECRETS_PATH);
Secrets secrets;
try {
config = Config.load(configPath);
secrets = Secrets.load(secretsPath);
} catch (IOException e) {
logger.error("Unable to load the configuration file from path '{}'",
configPath.toAbsolutePath(), e);
secretsPath.toAbsolutePath(), e);
return;
}

Thread.setDefaultUncaughtExceptionHandler(Application::onUncaughtException);
Runtime.getRuntime().addShutdownHook(new Thread(Application::onShutdown));
DiscordLogging.startDiscordLogging(config);
DiscordLogging.startDiscordLogging(config, secrets);

runBot(config);
runBot(config, secrets);
}

/**
* Runs an instance of the bot, connecting to the given token and using the given database.
*
* @param config the configuration to run the bot with
* @param secrets the secrets to run the bot with
*/
@SuppressWarnings("WeakerAccess")
public static void runBot(Config config) {
public static void runBot(Config config, Secrets secrets) {
logger.info("Starting bot...");

Path databasePath = Path.of(config.getDatabasePath());
Expand All @@ -82,13 +102,13 @@ public static void runBot(Config config) {
}
Database database = new Database("jdbc:sqlite:" + databasePath.toAbsolutePath());

JDA jda = JDABuilder.createDefault(config.getToken())
JDA jda = JDABuilder.createDefault(secrets.getToken())
.enableIntents(GatewayIntent.GUILD_MEMBERS, GatewayIntent.MESSAGE_CONTENT)
.build();

jda.awaitReady();

BotCore core = new BotCore(jda, database, config);
BotCore core = new BotCore(jda, database, config, secrets);
CommandReloading.reloadCommands(jda, core);
core.scheduleRoutines(jda);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,15 @@
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

import java.io.IOException;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Objects;


/**
* Configuration of the application. Create instances using {@link #load(Path)}.
* Configuration of the application. Create instances using {@link #load(String)}.
*/
public final class Config {
private final String token;
private final String githubApiKey;
private final String databasePath;
private final String projectWebsite;
private final String discordGuildInvite;
Expand All @@ -36,11 +33,8 @@ public final class Config {
private final HelpSystemConfig helpSystem;
private final List<String> blacklistedFileExtension;
private final String mediaOnlyChannelPattern;
private final String logInfoChannelWebhook;
private final String logErrorChannelWebhook;
private final String githubReferencingEnabledChannelPattern;
private final List<Long> githubRepositories;
private final String openaiApiKey;
private final String sourceCodeBaseUrl;
private final JShellConfig jshell;
private final HelperPruneConfig helperPruneConfig;
Expand All @@ -52,9 +46,7 @@ public final class Config {

@SuppressWarnings("ConstructorWithTooManyParameters")
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
private Config(@JsonProperty(value = "token", required = true) String token,
@JsonProperty(value = "githubApiKey", required = true) String githubApiKey,
@JsonProperty(value = "databasePath", required = true) String databasePath,
private Config(@JsonProperty(value = "databasePath", required = true) String databasePath,
@JsonProperty(value = "projectWebsite", required = true) String projectWebsite,
@JsonProperty(value = "discordGuildInvite", required = true) String discordGuildInvite,
@JsonProperty(value = "modAuditLogChannelPattern",
Expand Down Expand Up @@ -82,15 +74,11 @@ private Config(@JsonProperty(value = "token", required = true) String token,
required = true) String mediaOnlyChannelPattern,
@JsonProperty(value = "blacklistedFileExtension",
required = true) List<String> blacklistedFileExtension,
@JsonProperty(value = "logInfoChannelWebhook",
required = true) String logInfoChannelWebhook,
@JsonProperty(value = "logErrorChannelWebhook",
required = true) String logErrorChannelWebhook,

@JsonProperty(value = "githubReferencingEnabledChannelPattern",
required = true) String githubReferencingEnabledChannelPattern,
@JsonProperty(value = "githubRepositories",
required = true) List<Long> githubRepositories,
@JsonProperty(value = "openaiApiKey", required = true) String openaiApiKey,
@JsonProperty(value = "sourceCodeBaseUrl", required = true) String sourceCodeBaseUrl,
@JsonProperty(value = "jshell", required = true) JShellConfig jshell,
@JsonProperty(value = "memberCountCategoryPattern",
Expand All @@ -103,8 +91,6 @@ private Config(@JsonProperty(value = "token", required = true) String token,
@JsonProperty(value = "selectRolesChannelPattern",
required = true) String selectRolesChannelPattern,
@JsonProperty(value = "topHelpers", required = true) TopHelpersConfig topHelpers) {
this.token = Objects.requireNonNull(token);
this.githubApiKey = Objects.requireNonNull(githubApiKey);
this.databasePath = Objects.requireNonNull(databasePath);
this.projectWebsite = Objects.requireNonNull(projectWebsite);
this.memberCountCategoryPattern = Objects.requireNonNull(memberCountCategoryPattern);
Expand All @@ -125,12 +111,9 @@ private Config(@JsonProperty(value = "token", required = true) String token,
this.helpSystem = Objects.requireNonNull(helpSystem);
this.mediaOnlyChannelPattern = Objects.requireNonNull(mediaOnlyChannelPattern);
this.blacklistedFileExtension = Objects.requireNonNull(blacklistedFileExtension);
this.logInfoChannelWebhook = Objects.requireNonNull(logInfoChannelWebhook);
this.logErrorChannelWebhook = Objects.requireNonNull(logErrorChannelWebhook);
this.githubReferencingEnabledChannelPattern =
Objects.requireNonNull(githubReferencingEnabledChannelPattern);
this.githubRepositories = Objects.requireNonNull(githubRepositories);
this.openaiApiKey = Objects.requireNonNull(openaiApiKey);
this.sourceCodeBaseUrl = Objects.requireNonNull(sourceCodeBaseUrl);
this.jshell = Objects.requireNonNull(jshell);
this.helperPruneConfig = Objects.requireNonNull(helperPruneConfig);
Expand All @@ -143,13 +126,13 @@ private Config(@JsonProperty(value = "token", required = true) String token,
/**
* Loads the configuration from the given file.
*
* @param path the configuration file, as JSON object
* @param content the configuration file, as Stringified JSON object
* @return the loaded configuration
* @throws IOException if the file could not be loaded
*/
public static Config load(Path path) throws IOException {
public static Config load(String content) throws IOException {
return new ObjectMapper().registerModule(new JavaTimeModule())
.readValue(path.toFile(), Config.class);
.readValue(content, Config.class);
}

/**
Expand Down Expand Up @@ -191,27 +174,6 @@ public String getProjectsChannelPattern() {
return projectsChannelPattern;
}

/**
* Gets the token of the Discord bot to connect this application to.
*
* @return the Discord bot token
*/
public String getToken() {
return token;
}

/**
* Gets the API Key of GitHub.
*
* @return the API Key
* @see <a href=
* "https://docs.github.com/en/enterprise-server@3.4/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token">Create
* a GitHub key</a>
*/
public String getGitHubApiKey() {
return githubApiKey;
}

/**
* Gets the path where the database of the application is located at.
*
Expand Down Expand Up @@ -356,33 +318,6 @@ public List<Long> getGitHubRepositories() {
return githubRepositories;
}

/**
* The Discord channel webhook for posting log messages with levels INFO, DEBUG and TRACE.
*
* @return the webhook URL
*/
public String getLogInfoChannelWebhook() {
return logInfoChannelWebhook;
}

/**
* The Discord channel webhook for posting log messages with levels FATAL, ERROR and WARNING.
*
* @return the webhook URL
*/
public String getLogErrorChannelWebhook() {
return logErrorChannelWebhook;
}

/**
* The OpenAI token needed for communicating with OpenAI ChatGPT.
*
* @return the OpenAI API Token
*/
public String getOpenaiApiKey() {
return openaiApiKey;
}

/**
* The base URL of the source code of this bot. E.g.
* {@code getSourceCodeBaseUrl() + "/org/togetherjava/tjbot/config/Config.java"} would point to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,25 @@

import org.togetherjava.tjbot.features.utils.RateLimiter;

import java.util.Objects;

/**
* JShell config.
*
* @param baseUrl the base url of the JShell REST API
*
* @param rateLimitWindowSeconds the number of seconds of the {@link RateLimiter rate limiter} for
* jshell commands and code actions
* @param rateLimitRequestsInWindow the number of requests of the {@link RateLimiter rate limiter}
* for jshell commands and code actions
*/
public record JShellConfig(String baseUrl, int rateLimitWindowSeconds,
int rateLimitRequestsInWindow) {
public record JShellConfig(int rateLimitWindowSeconds, int rateLimitRequestsInWindow) {
/**
* Creates a JShell config.
*
* @param baseUrl the base url of the JShell REST API, must be not null
* @param rateLimitWindowSeconds the number of seconds of the {@link RateLimiter rate limiter}
* for jshell commands and code actions, must be higher than 0
* @param rateLimitRequestsInWindow the number of requests of the {@link RateLimiter rate
* limiter} for jshell commands and code actions, must be higher than 0
*/
public JShellConfig {
Objects.requireNonNull(baseUrl);
if (rateLimitWindowSeconds < 0) {
throw new IllegalArgumentException(
"Illegal rateLimitWindowSeconds : " + rateLimitWindowSeconds);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
import org.togetherjava.tjbot.features.tophelper.TopHelpersMessageListener;
import org.togetherjava.tjbot.features.tophelper.TopHelpersPurgeMessagesRoutine;
import org.togetherjava.tjbot.features.tophelper.TopHelpersService;
import org.togetherjava.tjbot.secrets.Secrets;

import java.util.ArrayList;
import java.util.Collection;
Expand All @@ -88,7 +89,7 @@
* it with the system.
* <p>
* To add a new slash command, extend the commands returned by
* {@link #createFeatures(JDA, Database, Config)}.
* {@link #createFeatures(JDA, Database, Config, Secrets)}.
*/
public class Features {
private Features() {
Expand All @@ -104,21 +105,24 @@ private Features() {
* @param jda the JDA instance commands will be registered at
* @param database the database of the application, which features can use to persist data
* @param config the configuration features should use
* @param secrets the secrets features may need
* @return a collection of all features
*/
public static Collection<Feature> createFeatures(JDA jda, Database database, Config config) {
public static Collection<Feature> createFeatures(JDA jda, Database database, Config config,
Secrets secrets) {
FeatureBlacklistConfig blacklistConfig = config.getFeatureBlacklistConfig();
JShellEval jshellEval = new JShellEval(config.getJshell(), config.getGitHubApiKey());
JShellEval jshellEval = new JShellEval(config.getJshell(), secrets.getJshellBaseUrl(),
secrets.getGitHubApiKey());

TagSystem tagSystem = new TagSystem(database);
BookmarksSystem bookmarksSystem = new BookmarksSystem(config, database);
ModerationActionsStore actionsStore = new ModerationActionsStore(database);
ModAuditLogWriter modAuditLogWriter = new ModAuditLogWriter(config);
ScamHistoryStore scamHistoryStore = new ScamHistoryStore(database);
GitHubReference githubReference = new GitHubReference(config);
GitHubReference githubReference = new GitHubReference(config, secrets);
CodeMessageHandler codeMessageHandler =
new CodeMessageHandler(blacklistConfig.special(), jshellEval);
ChatGptService chatGptService = new ChatGptService(config);
ChatGptService chatGptService = new ChatGptService(secrets);
HelpSystemHelper helpSystemHelper = new HelpSystemHelper(config, database, chatGptService);
HelpThreadLifecycleListener helpThreadLifecycleListener =
new HelpThreadLifecycleListener(helpSystemHelper, database);
Expand Down Expand Up @@ -153,7 +157,7 @@ public static Collection<Feature> createFeatures(JDA jda, Database database, Con
features.add(new SuggestionsUpDownVoter(config));
features.add(new ScamBlocker(actionsStore, scamHistoryStore, config));
features.add(new MediaOnlyChannelListener(config));
features.add(new FileSharingMessageListener(config));
features.add(new FileSharingMessageListener(config, secrets));
features.add(new BlacklistedAttachmentListener(config, modAuditLogWriter));
features.add(githubReference);
features.add(codeMessageHandler);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.togetherjava.tjbot.config.Config;
import org.togetherjava.tjbot.secrets.Secrets;

import javax.annotation.Nullable;

Expand Down Expand Up @@ -54,10 +54,10 @@ public class ChatGptService {
/**
* Creates instance of ChatGPTService
*
* @param config needed for token to OpenAI API.
* @param secrets needed for token to OpenAI API.
*/
public ChatGptService(Config config) {
String apiKey = config.getOpenaiApiKey();
public ChatGptService(Secrets secrets) {
String apiKey = secrets.getOpenaiApiKey();
boolean keyIsDefaultDescription = apiKey.startsWith("<") && apiKey.endsWith(">");
if (apiKey.isBlank() || keyIsDefaultDescription) {
isDisabled = true;
Expand Down
Loading
Loading