diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 7f0783c0a6..bb91b66660 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -31,8 +31,5 @@ } } } - }, - "postCreateCommand": { - "config": "cp application/config.json.template application/config.json" } } diff --git a/.gitignore b/.gitignore index 62ff123564..020eada532 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/application/config.json.template b/application/config.json similarity index 54% rename from application/config.json.template rename to application/config.json index 5cfe9ac38e..634b8acd3b 100644 --- a/application/config.json.template +++ b/application/config.json @@ -1,113 +1,45 @@ { - "token": "", - "githubApiKey": "", "databasePath": "local-database.db", "projectWebsite": "https://github.com/Together-Java/TJ-Bot", "discordGuildInvite": "https://discord.com/invite/XXFUXzK", "modAuditLogChannelPattern": "mod-audit-log", - "modMailChannelPattern": "modmail", - "projectsChannelPattern": "projects", "mutedRolePattern": "Muted", "heavyModerationRolePattern": "Moderator", "softModerationRolePattern": "Moderator|Community Ambassador", "tagManageRolePattern": "Moderator|Community Ambassador|Top Helper.*", - "excludeCodeAutoDetectionRolePattern": "Top Helper.*|Moderator|Community Ambassador|Expert", + "excludeCodeAutoDetectionRolePattern": "Moderator|Community Ambassador|Expert|Top Helper.*", "suggestions": { - "channelPattern": "tj-suggestions", - "upVoteEmoteName": "peepo_yes", - "downVoteEmoteName": "peepo_no" + "channelPattern": "server-suggestions", + "upVoteEmoteName": "upvote", + "downVoteEmoteName": "downvote" }, "quarantinedRolePattern": "Quarantined", "scamBlocker": { "mode": "AUTO_DELETE_BUT_APPROVE_QUARANTINE", - "reportChannelPattern": "commands", - "botTrapChannelPattern": "bot-trap", + "reportChannelPattern": "community-commands", "trustedUserRolePattern": "Top Helper.*|Moderator|Community Ambassador|Expert", - "suspiciousKeywords": [ - "nitro", - "boob", - "sexy", - "sexi", - "esex", - "steam", - "gift", - "onlyfans", - "bitcoin", - "btc", - "promo", - "trader", - "trading", - "whatsapp", - "crypto", - "^claim", - "^teen$", - "adobe", - "^hack$", - "hacks", - "steamcommunity", - "freenitro", - "^earn$", - "^earning", - ".exe$", - "mrbeast" - ], - "hostWhitelist": [ - "discord.com", - "discord.media", - "discordapp.com", - "discordapp.net", - "discordstatus.com", - "thehackernews.com", - "gradle.org", - "help.gradle.org", - "youtube.com", - "www.youtube.com", - "cdn.discordapp.com", - "media.discordapp.net", - "store.steampowered.com", - "help.steampowered.com", - "learn.microsoft.com" - ], - "hostBlacklist": [ - "bit.ly", - "discord.gg", - "teletype.in", - "t.me", - "corematrix.us", - "u.to", - "steamcommunity.com", - "goo.su", - "telegra.ph", - "shorturl.at", - "cheatings.xyz", - "transfer.sh", - "tobimoller.space" - ], - "suspiciousHostKeywords": [ - "discord", - "nitro", - "premium", - "free", - "cheat", - "crypto", - "telegra", - "telety" - ], + "botTrapChannelPattern": "ignore-me", + "suspiciousKeywords": ["nitro", "boob", "sexy", "sexi", "esex", "jobcord", "steam", "gift", "onlyfans", "bitcoin", "btc", "promo", "trader", "trading", "whatsapp", "crypto", "^claim", "^teen$", "adobe", "^hack$", "hacks", "steamcommunity", "freenitro", "^earn$", "^earning", ".exe$", "mrbeast"], + "hostWhitelist": ["discord.com", "discord.media", "discordapp.com", "discordapp.net", "discordstatus.com", "cwiki.apache.org", "help.gradle.org", "thehackernews.com", "gradle.org", "youtube.com", "www.youtube.com", "cdn.discordapp.com", "media.discordapp.net", "store.steampowered.com", "help.steampowered.com", "learn.microsoft.com"], + "hostBlacklist": ["bit.ly", "gg.gg", "dsaocrdgift.xyz", "twitchcsgo.cfd", "link-hub.net", "discord.gg", "teletype.in", "t.me", "corematrix.us", "u.to", "steamcommunity.com", "goo.su", "telegra.ph", "shorturl.at", "cheatings.xyz", "transfer.sh", "tobimoller.space"], + "suspiciousHostKeywords": ["discord", "nitro", "premium", "deepfake", "free", "cheat", "crypto", "telegra", "telety"], "isHostSimilarToKeywordDistanceThreshold": 2, "suspiciousAttachmentsThreshold": 3, "suspiciousAttachmentNamePattern": "(image|\\d{1,2})\\.[^.]{0,5}" }, - "wolframAlphaAppId": "79J52T-6239TVXHR7", "helpSystem": { "helpForumPattern": "questions", "categories": [ "Java", "Frameworks", + "Spring", "JavaFX|Swing", "IDE", "Build Tools", "Database", "Android", + "Minecraft", + "Kotlin", "C|C++", "Algorithms", "Math", @@ -118,7 +50,7 @@ ], "categoryRoleSuffix": " - Helper" }, - "mediaOnlyChannelPattern": "memes", + "mediaOnlyChannelPattern": "memes|educational-media|resources|ide-themes-config", "blacklistedFileExtension": [ "application", "bat", @@ -147,6 +79,7 @@ "ps2xml", "psc1", "psc2", + "rar", "scf", "scr", "vb", @@ -159,12 +92,10 @@ ], "githubReferencingEnabledChannelPattern": "server-suggestions|tjbot-discussion|modernjava-discussion", "githubRepositories": [403389278,587644974,601602394], - "logInfoChannelWebhook": "", - "logErrorChannelWebhook": "", - "openaiApiKey": "", + "modMailChannelPattern": "modmail", + "projectsChannelPattern": "projects", "sourceCodeBaseUrl": "https://github.com/Together-Java/TJ-Bot/blob/master/application/src/main/java/", "jshell": { - "baseUrl": "", "rateLimitWindowSeconds": 10, "rateLimitRequestsInWindow": 3 }, @@ -176,25 +107,28 @@ "recentlyJoinedDays": 4 }, "featureBlacklist": { - "normal": [ - ], - "special": [ - ] + "normal": [], + "special": [] }, + "memberCountCategoryPattern": "Info", "selectRolesChannelPattern": "select-your-roles", "rssConfig": { "feeds": [ { - "url": "https://blogs.oracle.com/java/rss", + "url":"https://inside.java/feed.xml", "targetChannelPattern": "java-news-and-changes", - "dateFormatterPattern": "EEE, d MMM yyyy HH:mm:ss z" + "dateFormatterPattern": "yyyy-MM-dd'T'HH:mm:ssXXX" + }, + { + "url":"https://www.youtube.com/feeds/videos.xml?playlist_id=UUSHmRtPmgnQ04CMUpSUqPfhxQ", + "targetChannelPattern": "today-i-teach", + "dateFormatterPattern": "yyyy-MM-dd'T'HH:mm:ssXXX" } ], "fallbackChannelPattern": "java-news-and-changes", "videoLinkPattern": "http(s)?://www\\.youtube.com.*", "pollIntervalInMinutes": 10 }, - "memberCountCategoryPattern": "Info", "topHelpers": { "rolePattern": "Top Helper.*", "assignmentChannelPattern": "community-commands", diff --git a/application/secrets.json.template b/application/secrets.json.template new file mode 100644 index 0000000000..1813d663a2 --- /dev/null +++ b/application/secrets.json.template @@ -0,0 +1,9 @@ +{ + "token": "", + "githubApiKey": "", + "logInfoChannelWebhook": "", + "logErrorChannelWebhook": "", + "openaiApiKey": "", + "jshellBaseUrl": "", + "wolframAlphaAppId": "79J52T-6239TVXHR7" +} diff --git a/application/src/main/java/org/togetherjava/tjbot/Application.java b/application/src/main/java/org/togetherjava/tjbot/Application.java index 4c228cb02a..6a38071491 100644 --- a/application/src/main/java/org/togetherjava/tjbot/Application.java +++ b/application/src/main/java/org/togetherjava/tjbot/Application.java @@ -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; @@ -33,45 +36,114 @@ 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_DISK = "config.json"; + private static final String DEFAULT_CONFIG_PATH_RESOURCES = "/config.json"; + private static final String DEFAULT_SECRETS_PATH = "secrets.json"; /** * Starts the application. + *

+ * Note: By default the configuration file will be loaded from a config.json unless overridden + * by either: 1. Setting the USE_INCLUDED_CONFIG environment variable to true, which will use + * the config.json packed in the built jar. 2. Passing a program argument including the path to + * the config file. * * @param args command line arguments - [the path to the configuration file (optional, by * default "config.json")] */ public static void main(final String[] args) { - if (args.length > 1) { - throw new IllegalArgumentException("Expected no or one argument but " + args.length - + " arguments were provided. The first argument is the path to the configuration file. If no argument was provided, '" - + DEFAULT_CONFIG_PATH + "' will be assumed."); + boolean useIncludedConfig; + try { + useIncludedConfig = Boolean.parseBoolean(System.getenv("USE_INCLUDED_CONFIG")); + logger.info("Using config.json included in jar"); + } catch (Exception _) { + useIncludedConfig = false; + } + + String configPath; + + if (args.length > 0) { + configPath = args[0]; + } else if (useIncludedConfig) { + configPath = DEFAULT_CONFIG_PATH_RESOURCES; + } else { + configPath = DEFAULT_CONFIG_PATH_DISK; } - Path configPath = Path.of(args.length == 1 ? args[0] : DEFAULT_CONFIG_PATH); Config config; try { - config = Config.load(configPath); + config = loadConfig(useIncludedConfig, configPath); + } catch (IOException e) { + logger.error("Unable to load the configuration file '{}'", configPath, e); + return; + } + + Path secretsPath = Path.of(args.length == 1 ? args[0] : DEFAULT_SECRETS_PATH); + Secrets secrets; + try { + 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, secrets); + } + + /** + * Attempts to load the configuration file and return a new {@code Config}. + * + * @param useIncludedConfig if the config should be loaded from the resources' directory. + * @param configPath the location of the config file. + * @return a new {@code Config} object + * @throws IOException if the configuration file could not be loaded. + */ + private static Config loadConfig(boolean useIncludedConfig, String configPath) + throws IOException { + return useIncludedConfig ? loadConfigFromResource(configPath) + : loadConfigFromFile(Path.of(configPath)); + } - runBot(config); + /** + * Loads a configuration file from the application resources directory. + * + * @param configPath the location of the configuration file + * @return a new {@code Config} object + * @throws IOException if the configuration file could not be loaded + */ + private static Config loadConfigFromResource(String configPath) throws IOException { + try (InputStream stream = Application.class.getResourceAsStream(configPath)) { + if (stream == null) { + throw new IOException("InputStream is null when loading " + configPath); + } + return Config.load(new String(stream.readAllBytes(), StandardCharsets.UTF_8)); + } + } + + /** + * Loads a configuration file from a specified path. + * + * @param configPath the location of the configuration file + * @return a new {@code Config} object + * @throws IOException if the configuration file could not be loaded + */ + private static Config loadConfigFromFile(Path configPath) throws IOException { + return Config.load(configPath); } /** * 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()); @@ -82,13 +154,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); diff --git a/application/src/main/java/org/togetherjava/tjbot/config/Config.java b/application/src/main/java/org/togetherjava/tjbot/config/Config.java index 60e6622cbc..88f84ddadd 100644 --- a/application/src/main/java/org/togetherjava/tjbot/config/Config.java +++ b/application/src/main/java/org/togetherjava/tjbot/config/Config.java @@ -13,11 +13,9 @@ /** - * 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; @@ -32,15 +30,11 @@ public final class Config { private final SuggestionsConfig suggestions; private final String quarantinedRolePattern; private final ScamBlockerConfig scamBlocker; - private final String wolframAlphaAppId; private final HelpSystemConfig helpSystem; private final List blacklistedFileExtension; private final String mediaOnlyChannelPattern; - private final String logInfoChannelWebhook; - private final String logErrorChannelWebhook; private final String githubReferencingEnabledChannelPattern; private final List githubRepositories; - private final String openaiApiKey; private final String sourceCodeBaseUrl; private final JShellConfig jshell; private final HelperPruneConfig helperPruneConfig; @@ -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", @@ -76,21 +68,15 @@ private Config(@JsonProperty(value = "token", required = true) String token, @JsonProperty(value = "quarantinedRolePattern", required = true) String quarantinedRolePattern, @JsonProperty(value = "scamBlocker", required = true) ScamBlockerConfig scamBlocker, - @JsonProperty(value = "wolframAlphaAppId", required = true) String wolframAlphaAppId, @JsonProperty(value = "helpSystem", required = true) HelpSystemConfig helpSystem, @JsonProperty(value = "mediaOnlyChannelPattern", required = true) String mediaOnlyChannelPattern, @JsonProperty(value = "blacklistedFileExtension", required = true) List 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 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", @@ -103,8 +89,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); @@ -121,16 +105,12 @@ private Config(@JsonProperty(value = "token", required = true) String token, this.suggestions = Objects.requireNonNull(suggestions); this.quarantinedRolePattern = Objects.requireNonNull(quarantinedRolePattern); this.scamBlocker = Objects.requireNonNull(scamBlocker); - this.wolframAlphaAppId = Objects.requireNonNull(wolframAlphaAppId); 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); @@ -140,10 +120,22 @@ private Config(@JsonProperty(value = "token", required = true) String token, this.topHelpers = Objects.requireNonNull(topHelpers); } + /** + * Loads the configuration from the String payload. + * + * @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(String content) throws IOException { + return new ObjectMapper().registerModule(new JavaTimeModule()) + .readValue(content, Config.class); + } + /** * Loads the configuration from the given file. * - * @param path the configuration file, as JSON object + * @param path the configuration file * @return the loaded configuration * @throws IOException if the file could not be loaded */ @@ -191,27 +183,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 Create - * a GitHub key - */ - public String getGitHubApiKey() { - return githubApiKey; - } - /** * Gets the path where the database of the application is located at. * @@ -306,15 +277,6 @@ public ScamBlockerConfig getScamBlocker() { return scamBlocker; } - /** - * Gets the application ID used to connect to the WolframAlpha API. - * - * @return the application ID for the WolframAlpha API - */ - public String getWolframAlphaAppId() { - return wolframAlphaAppId; - } - /** * Gets the config for the help system. * @@ -356,33 +318,6 @@ public List 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 diff --git a/application/src/main/java/org/togetherjava/tjbot/config/JShellConfig.java b/application/src/main/java/org/togetherjava/tjbot/config/JShellConfig.java index 91b85eb2d8..4ff45bd06e 100644 --- a/application/src/main/java/org/togetherjava/tjbot/config/JShellConfig.java +++ b/application/src/main/java/org/togetherjava/tjbot/config/JShellConfig.java @@ -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); diff --git a/application/src/main/java/org/togetherjava/tjbot/features/Features.java b/application/src/main/java/org/togetherjava/tjbot/features/Features.java index 8ed07eff6c..b05aae263f 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/Features.java @@ -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; @@ -88,7 +89,7 @@ * it with the system. *

* 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() { @@ -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 createFeatures(JDA jda, Database database, Config config) { + public static Collection 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); @@ -153,7 +157,7 @@ public static Collection 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); @@ -196,7 +200,7 @@ public static Collection createFeatures(JDA jda, Database database, Con features.add(new QuarantineCommand(actionsStore, config)); features.add(new UnquarantineCommand(actionsStore, config)); features.add(new WhoIsCommand()); - features.add(new WolframAlphaCommand(config)); + features.add(new WolframAlphaCommand(secrets)); features.add(new GitHubCommand(githubReference)); features.add(new ModMailCommand(jda, config)); features.add(new HelpThreadCommand(config, helpSystemHelper)); diff --git a/application/src/main/java/org/togetherjava/tjbot/features/chatgpt/ChatGptService.java b/application/src/main/java/org/togetherjava/tjbot/features/chatgpt/ChatGptService.java index a6fdcbcb9d..1a47661d68 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/chatgpt/ChatGptService.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/chatgpt/ChatGptService.java @@ -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; @@ -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; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/filesharing/FileSharingMessageListener.java b/application/src/main/java/org/togetherjava/tjbot/features/filesharing/FileSharingMessageListener.java index c040eaf065..ec79e8e776 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/filesharing/FileSharingMessageListener.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/filesharing/FileSharingMessageListener.java @@ -21,6 +21,7 @@ import org.togetherjava.tjbot.features.componentids.ComponentIdGenerator; import org.togetherjava.tjbot.features.componentids.ComponentIdInteractor; import org.togetherjava.tjbot.features.utils.Guilds; +import org.togetherjava.tjbot.secrets.Secrets; import java.io.IOException; import java.io.InputStream; @@ -55,12 +56,13 @@ public final class FileSharingMessageListener extends MessageReceiverAdapter /** * Creates a new instance. * - * @param config used to get api key and channel names. + * @param config used to get channel names. + * @param secrets used to get api key * @see org.togetherjava.tjbot.features.Features */ - public FileSharingMessageListener(Config config) { + public FileSharingMessageListener(Config config, Secrets secrets) { super(Pattern.compile(".*")); - githubApiKey = config.getGitHubApiKey(); + githubApiKey = secrets.getGitHubApiKey(); isHelpForumName = Pattern.compile(config.getHelpSystem().getHelpForumPattern()).asMatchPredicate(); isSoftModRole = Pattern.compile(config.getSoftModerationRolePattern()).asMatchPredicate(); diff --git a/application/src/main/java/org/togetherjava/tjbot/features/github/GitHubReference.java b/application/src/main/java/org/togetherjava/tjbot/features/github/GitHubReference.java index 960587e8a4..03854b550b 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/github/GitHubReference.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/github/GitHubReference.java @@ -21,6 +21,7 @@ import org.togetherjava.tjbot.config.Config; import org.togetherjava.tjbot.features.MessageReceiverAdapter; +import org.togetherjava.tjbot.secrets.Secrets; import java.awt.Color; import java.io.FileNotFoundException; @@ -67,6 +68,7 @@ public final class GitHubReference extends MessageReceiverAdapter { DateTimeFormatter.ofPattern("dd MMM, yyyy").withZone(ZoneOffset.UTC); private final Predicate hasGithubIssueReferenceEnabled; private final Config config; + private final Secrets secrets; /** * The repositories that are searched when looking for an issue. @@ -80,9 +82,11 @@ public final class GitHubReference extends MessageReceiverAdapter { * a predicate for matching allowed channels for feature and acquires repositories. * * @param config The Config to get allowed channel pattern for feature. + * @param secrets The Secrets to get the GitHub API key. */ - public GitHubReference(Config config) { + public GitHubReference(Config config, Secrets secrets) { this.config = config; + this.secrets = secrets; this.hasGithubIssueReferenceEnabled = Pattern.compile(config.getGitHubReferencingEnabledChannelPattern()) .asMatchPredicate(); @@ -96,7 +100,7 @@ private void acquireRepositories() { try { repositories = new ArrayList<>(); - GitHub githubApi = GitHub.connectUsingOAuth(config.getGitHubApiKey()); + GitHub githubApi = GitHub.connectUsingOAuth(secrets.getGitHubApiKey()); for (long repoId : config.getGitHubRepositories()) { repositories.add(githubApi.getRepositoryById(repoId)); @@ -104,7 +108,7 @@ private void acquireRepositories() { } catch (IOException ex) { logger.warn( "The GitHub key ({}) used in this config is invalid. Skipping GitHubReference feature – {}", - config.getGitHubApiKey(), ex.getMessage()); + secrets.getGitHubApiKey(), ex.getMessage()); } } diff --git a/application/src/main/java/org/togetherjava/tjbot/features/jshell/JShellEval.java b/application/src/main/java/org/togetherjava/tjbot/features/jshell/JShellEval.java index cd965d128d..b10ffe4a64 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/jshell/JShellEval.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/jshell/JShellEval.java @@ -36,12 +36,13 @@ public class JShellEval { * Creates a JShell evaluation instance * * @param config the JShell configuration to use + * @param baseUrl the base URL for the JSHell API * @param gistApiToken token of Gist api in case a JShell result is uploaded here */ - public JShellEval(JShellConfig config, String gistApiToken) { + public JShellEval(JShellConfig config, String baseUrl, String gistApiToken) { this.gistApiToken = gistApiToken; this.api = new JShellApi(new ObjectMapper().registerModule(new Jdk17SealedClassesModule()), - config.baseUrl()); + baseUrl); this.renderer = new ResultRenderer(); this.rateLimiter = new RateLimiter(Duration.ofSeconds(config.rateLimitWindowSeconds()), diff --git a/application/src/main/java/org/togetherjava/tjbot/features/mathcommands/wolframalpha/WolframAlphaCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/mathcommands/wolframalpha/WolframAlphaCommand.java index 7f37ab6d0a..45b4e499c2 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/mathcommands/wolframalpha/WolframAlphaCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/mathcommands/wolframalpha/WolframAlphaCommand.java @@ -6,9 +6,9 @@ import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.utils.FileUpload; -import org.togetherjava.tjbot.config.Config; import org.togetherjava.tjbot.features.CommandVisibility; import org.togetherjava.tjbot.features.SlashCommandAdapter; +import org.togetherjava.tjbot.secrets.Secrets; import java.net.http.HttpClient; import java.net.http.HttpRequest; @@ -37,14 +37,14 @@ public final class WolframAlphaCommand extends SlashCommandAdapter { /** * Creates a new instance. * - * @param config the config to use + * @param secrets containing the Wolfram app id */ - public WolframAlphaCommand(Config config) { + public WolframAlphaCommand(Secrets secrets) { super("wolfram-alpha", "Renders mathematical queries using WolframAlpha", CommandVisibility.GUILD); getData().addOption(OptionType.STRING, QUERY_OPTION, "the query to send to WolframAlpha", true); - appId = config.getWolframAlphaAppId(); + appId = secrets.getWolframAlphaAppId(); } @Override diff --git a/application/src/main/java/org/togetherjava/tjbot/features/system/BotCore.java b/application/src/main/java/org/togetherjava/tjbot/features/system/BotCore.java index 7c337e2efb..e94bc231c2 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/system/BotCore.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/system/BotCore.java @@ -45,6 +45,7 @@ import org.togetherjava.tjbot.features.componentids.ComponentIdParser; import org.togetherjava.tjbot.features.componentids.ComponentIdStore; import org.togetherjava.tjbot.features.componentids.InvalidComponentIdFormatException; +import org.togetherjava.tjbot.secrets.Secrets; import java.util.Collection; import java.util.HashMap; @@ -94,10 +95,11 @@ public final class BotCore extends ListenerAdapter implements CommandProvider { * @param jda the JDA instance that this command system will be used with * @param database the database that commands may use to persist data * @param config the configuration to use for this system + * @param secrets the secrets to use for this system */ - public BotCore(JDA jda, Database database, Config config) { + public BotCore(JDA jda, Database database, Config config, Secrets secrets) { this.config = config; - Collection features = Features.createFeatures(jda, database, config); + Collection features = Features.createFeatures(jda, database, config, secrets); // Message receivers features.stream() diff --git a/application/src/main/java/org/togetherjava/tjbot/logging/discord/DiscordLogging.java b/application/src/main/java/org/togetherjava/tjbot/logging/discord/DiscordLogging.java index f7afed7fde..44ad330505 100644 --- a/application/src/main/java/org/togetherjava/tjbot/logging/discord/DiscordLogging.java +++ b/application/src/main/java/org/togetherjava/tjbot/logging/discord/DiscordLogging.java @@ -14,6 +14,7 @@ import org.togetherjava.tjbot.config.Config; import org.togetherjava.tjbot.logging.LogMarkers; +import org.togetherjava.tjbot.secrets.Secrets; import java.net.URI; import java.util.Optional; @@ -34,23 +35,24 @@ private DiscordLogging() { *

* Disables the feature if the config is set up incorrectly. * - * @param botConfig to get the logging details from, such as the Discord webhook urls + * @param botConfig to get the logging details from + * @param secrets to get the details such as the Discord webhook urls */ - public static void startDiscordLogging(Config botConfig) { + public static void startDiscordLogging(Config botConfig, Secrets secrets) { LoggerContext context = (LoggerContext) LogManager.getContext(false); Configuration logConfig = context.getConfiguration(); - addAppenders(logConfig, botConfig); + addAppenders(logConfig, botConfig, secrets); context.updateLoggers(); } - private static void addAppenders(Configuration logConfig, Config botConfig) { - parseWebhookUri(botConfig.getLogInfoChannelWebhook(), "info") + private static void addAppenders(Configuration logConfig, Config botConfig, Secrets secrets) { + parseWebhookUri(secrets.getLogInfoChannelWebhook(), "info") .ifPresent(webhookUri -> addDiscordLogAppender("DiscordInfo", createInfoRangeFilter(), webhookUri, botConfig.getSourceCodeBaseUrl(), logConfig)); - parseWebhookUri(botConfig.getLogErrorChannelWebhook(), "error") + parseWebhookUri(secrets.getLogErrorChannelWebhook(), "error") .ifPresent(webhookUri -> addDiscordLogAppender("DiscordError", createErrorRangeFilter(), webhookUri, botConfig.getSourceCodeBaseUrl(), logConfig)); } diff --git a/application/src/main/java/org/togetherjava/tjbot/secrets/Secrets.java b/application/src/main/java/org/togetherjava/tjbot/secrets/Secrets.java new file mode 100644 index 0000000000..3fc6c52ca6 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/secrets/Secrets.java @@ -0,0 +1,121 @@ +package org.togetherjava.tjbot.secrets; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Objects; + +/** + * This class contains secrets such as API keys, tokens etc., used by the application. + */ +public class Secrets { + private final String token; + private final String githubApiKey; + private final String logInfoChannelWebhook; + private final String logErrorChannelWebhook; + private final String openaiApiKey; + private final String jshellBaseUrl; + private final String wolframAlphaAppId; + + @SuppressWarnings("ConstructorWithTooManyParameters") + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + private Secrets(@JsonProperty(value = "token", required = true) String token, + @JsonProperty(value = "githubApiKey", required = true) String githubApiKey, + @JsonProperty(value = "logInfoChannelWebhook", + required = true) String logInfoChannelWebhook, + @JsonProperty(value = "logErrorChannelWebhook", + required = true) String logErrorChannelWebhook, + @JsonProperty(value = "openaiApiKey", required = true) String openaiApiKey, + @JsonProperty(value = "jshellBaseUrl", required = true) String jshellBaseUrl, + @JsonProperty(value = "wolframAlphaAppId", required = true) String wolframAlphaAppId) { + this.token = Objects.requireNonNull(token); + this.githubApiKey = Objects.requireNonNull(githubApiKey); + this.logInfoChannelWebhook = Objects.requireNonNull(logInfoChannelWebhook); + this.logErrorChannelWebhook = Objects.requireNonNull(logErrorChannelWebhook); + this.openaiApiKey = Objects.requireNonNull(openaiApiKey); + this.jshellBaseUrl = Objects.requireNonNull(jshellBaseUrl); + this.wolframAlphaAppId = Objects.requireNonNull(wolframAlphaAppId); + } + + /** + * Loads the configuration from the given file. + * + * @param path the location to secrets file + * @return the loaded configuration + * @throws IOException if the file could not be loaded + */ + public static Secrets load(Path path) throws IOException { + return new ObjectMapper().registerModule(new JavaTimeModule()) + .readValue(path.toFile(), Secrets.class); + } + + /** + * 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 Create + * a GitHub key + */ + public String getGitHubApiKey() { + return githubApiKey; + } + + /** + * 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; + } + + /** + * Gets the application ID used to connect to the WolframAlpha API. + * + * @return the application ID for the WolframAlpha API + */ + public String getWolframAlphaAppId() { + return wolframAlphaAppId; + } + + /** + * The OpenAI token needed for communicating with OpenAI ChatGPT. + * + * @return the OpenAI API Token + */ + public String getOpenaiApiKey() { + return openaiApiKey; + } + + /** + * The base URL for the jshell REST API. + * + * @return the jshell base url + */ + public String getJshellBaseUrl() { + return jshellBaseUrl; + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/secrets/package-info.java b/application/src/main/java/org/togetherjava/tjbot/secrets/package-info.java new file mode 100644 index 0000000000..8da4481716 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/secrets/package-info.java @@ -0,0 +1,11 @@ +/** + * This package contains the secrets of the application. It revolves around the class + * {@link org.togetherjava.tjbot.secrets.Secrets}. + */ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package org.togetherjava.tjbot.secrets; + +import org.togetherjava.tjbot.annotations.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/application/src/main/resources/config.json b/application/src/main/resources/config.json new file mode 100644 index 0000000000..bd92f41bf5 --- /dev/null +++ b/application/src/main/resources/config.json @@ -0,0 +1,137 @@ +{ + "databasePath": "/home/bot/database/database.db", + "projectWebsite": "https://github.com/Together-Java/TJ-Bot", + "discordGuildInvite": "https://discord.com/invite/XXFUXzK", + "modAuditLogChannelPattern": "mod-audit-log", + "mutedRolePattern": "Muted", + "heavyModerationRolePattern": "Moderator", + "softModerationRolePattern": "Moderator|Community Ambassador", + "tagManageRolePattern": "Moderator|Community Ambassador|Top Helper.*", + "excludeCodeAutoDetectionRolePattern": "Moderator|Community Ambassador|Expert|Top Helper.*", + "suggestions": { + "channelPattern": "server-suggestions", + "upVoteEmoteName": "upvote", + "downVoteEmoteName": "downvote" + }, + "quarantinedRolePattern": "Quarantined", + "scamBlocker": { + "mode": "AUTO_DELETE_AND_QUARANTINE", + "reportChannelPattern": "community-commands", + "trustedUserRolePattern": "Top Helper.*|Moderator|Community Ambassador|Expert", + "botTrapChannelPattern": "ignore-me", + "suspiciousKeywords": ["nitro", "boob", "sexy", "sexi", "esex", "jobcord", "steam", "gift", "onlyfans", "bitcoin", "btc", "promo", "trader", "trading", "whatsapp", "crypto", "^claim", "^teen$", "adobe", "^hack$", "hacks", "steamcommunity", "freenitro", "^earn$", "^earning", ".exe$", "mrbeast"], + "hostWhitelist": ["discord.com", "discord.media", "discordapp.com", "discordapp.net", "discordstatus.com", "cwiki.apache.org", "help.gradle.org", "thehackernews.com", "gradle.org", "youtube.com", "www.youtube.com", "cdn.discordapp.com", "media.discordapp.net", "store.steampowered.com", "help.steampowered.com", "learn.microsoft.com"], + "hostBlacklist": ["bit.ly", "gg.gg", "dsaocrdgift.xyz", "twitchcsgo.cfd", "link-hub.net", "discord.gg", "teletype.in", "t.me", "corematrix.us", "u.to", "steamcommunity.com", "goo.su", "telegra.ph", "shorturl.at", "cheatings.xyz", "transfer.sh", "tobimoller.space"], + "suspiciousHostKeywords": ["discord", "nitro", "premium", "deepfake", "free", "cheat", "crypto", "telegra", "telety"], + "isHostSimilarToKeywordDistanceThreshold": 2, + "suspiciousAttachmentsThreshold": 3, + "suspiciousAttachmentNamePattern": "(image|\\d{1,2})\\.[^.]{0,5}" + }, + "helpSystem": { + "helpForumPattern": "questions", + "categories": [ + "Java", + "Frameworks", + "Spring", + "JavaFX|Swing", + "IDE", + "Build Tools", + "Database", + "Android", + "Minecraft", + "Kotlin", + "C|C++", + "Algorithms", + "Math", + "Architecture", + "Code Review", + "Together Java Bot", + "Other" + ], + "categoryRoleSuffix": " - Helper" + }, + "mediaOnlyChannelPattern": "memes|educational-media|resources|ide-themes-config", + "blacklistedFileExtension": [ + "application", + "bat", + "cmd", + "com", + "cpl", + "exe", + "gadget", + "hta", + "inf", + "jse", + "lnk", + "msc", + "msh", + "msh1", + "msh1xml", + "msh2", + "msh2xml", + "mshxml", + "msi", + "msp", + "pif", + "ps1", + "ps1xml", + "ps2", + "ps2xml", + "psc1", + "psc2", + "rar", + "scf", + "scr", + "vb", + "vbe", + "vbs", + "ws", + "wsc", + "wsf", + "wsh" + ], + "githubReferencingEnabledChannelPattern": "server-suggestions|tjbot-discussion|modernjava-discussion", + "githubRepositories": [403389278,587644974,601602394], + "modMailChannelPattern": "modmail", + "projectsChannelPattern": "projects", + "sourceCodeBaseUrl": "https://github.com/Together-Java/TJ-Bot/blob/master/application/src/main/java/", + "jshell": { + "rateLimitWindowSeconds": 10, + "rateLimitRequestsInWindow": 3 + }, + "helperPruneConfig": { + "roleFullLimit": 250, + "roleFullThreshold": 245, + "pruneMemberAmount": 7, + "inactivateAfterDays": 90, + "recentlyJoinedDays": 4 + }, + "featureBlacklist": { + "normal": [], + "special": [] + }, + "memberCountCategoryPattern": "Info", + "selectRolesChannelPattern": "select-your-roles", + "rssConfig": { + "feeds": [ + { + "url":"https://inside.java/feed.xml", + "targetChannelPattern": "java-news-and-changes", + "dateFormatterPattern": "yyyy-MM-dd'T'HH:mm:ssXXX" + }, + { + "url":"https://www.youtube.com/feeds/videos.xml?playlist_id=UUSHmRtPmgnQ04CMUpSUqPfhxQ", + "targetChannelPattern": "today-i-teach", + "dateFormatterPattern": "yyyy-MM-dd'T'HH:mm:ssXXX" + } + ], + "fallbackChannelPattern": "java-news-and-changes", + "videoLinkPattern": "http(s)?://www\\.youtube.com.*", + "pollIntervalInMinutes": 10 + }, + "topHelpers": { + "rolePattern": "Top Helper.*", + "assignmentChannelPattern": "community-commands", + "announcementChannelPattern": "hall-of-fame" + } +} diff --git a/wiki/Edit-the-Config.md b/wiki/Edit-the-Config.md index 013f29a1d2..5e1cafcc8b 100644 --- a/wiki/Edit-the-Config.md +++ b/wiki/Edit-the-Config.md @@ -1,13 +1,14 @@ # Overview -In order to edit the configuration file of the bot, one has to login to the VPS and adjust the config file manually. Only members of the [Moderator](https://github.com/orgs/Together-Java/teams/moderators)-Team can do the following steps. +## Config -See [[Access the VPS]] for details of the login process. +The configuration file for the bot is located under `/application/src/main/resources/config.json` -# Guide +Any new configuration additions made to this file must be loaded within the `Config.java` file. -1. `ssh togetherjava` to login to the VPS -2. Either `cd ~/docker-infra/master-bot` or `cd ~/docker-infra/develop-bot` to go to the directory of the corresponding bot -3. Use `cd config` -4. Edit the `config.json` file, for example `vim config.json` or `nano config.json` -4. Save the file and [[restart the bot|Shutdown or restart the bot]]. \ No newline at end of file +## Secrets + +The secrets configuration file for the bot is located under `/application/secrets.json` and is used to store sensitive +information such as API keys. + +Any new configuration additions made to this file must be loaded within the `Secrets.java` file. diff --git a/wiki/Setup-project-locally.md b/wiki/Setup-project-locally.md index b62a6430df..daea13d029 100644 --- a/wiki/Setup-project-locally.md +++ b/wiki/Setup-project-locally.md @@ -98,14 +98,13 @@ See the following guide if you still have to create a server and a bot first: ![Discord Developer Portal - Bot Token](https://i.imgur.com/IB5W8vZ.png) -To run the bot, you will need a `config.json` file with specific content. You can find a template for this file, with meaningful default values, in `application/config.json.template`. +To run the bot, you will need a `secrets.json` file with specific content. You can find a template for this file, with meaningful default values, in `application/secrets.json.template`. Replace `` with your bot token; you can also adjust the other settings if you want. ### IntelliJ -1. put the configuration file to `TJ-Bot\application\config.json` or run the program with a single argument, the path to your config file -2. in the Gradle view, click the `run` task and start it +1. in the Gradle view, click the `run` task and start it ![Bot runs](https://i.imgur.com/KdsSsx0.png)