Skip to content
Draft
Show file tree
Hide file tree
Changes from 10 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
3 changes: 0 additions & 3 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,5 @@
}
}
}
},
"postCreateCommand": {
"config": "cp application/config.json.template application/config.json"
}
}
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