-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Discord integration and push tag command
- Loading branch information
1 parent
dc7671c
commit c8bff53
Showing
15 changed files
with
551 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package net.neoforged.automation.db; | ||
|
||
import org.flywaydb.core.Flyway; | ||
import org.flywaydb.core.api.configuration.FluentConfiguration; | ||
import org.jdbi.v3.core.Jdbi; | ||
import org.jdbi.v3.core.extension.ExtensionCallback; | ||
import org.jdbi.v3.core.extension.ExtensionConsumer; | ||
import org.jdbi.v3.sqlobject.SqlObjectPlugin; | ||
import org.sqlite.SQLiteDataSource; | ||
|
||
import java.io.IOException; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.util.function.UnaryOperator; | ||
|
||
public class Database { | ||
|
||
private static Jdbi database; | ||
|
||
public static Jdbi get() { | ||
return database; | ||
} | ||
|
||
public static <R, E, X extends Exception> R withExtension(Class<E> extensionType, ExtensionCallback<R, E, X> callback) throws X { | ||
return get().withExtension(extensionType, callback); | ||
} | ||
|
||
public static <E, X extends Exception> void useExtension(Class<E> extensionType, ExtensionConsumer<E, X> callback) throws X { | ||
get().useExtension(extensionType, callback); | ||
} | ||
|
||
public static void init() { | ||
var path = Path.of("data.db"); | ||
database = createDatabaseConnection(path, "reactionable", c -> c.locations("classpath:db")); | ||
} | ||
|
||
private static Jdbi createDatabaseConnection(Path dbPath, String name, UnaryOperator<FluentConfiguration> flywayConfig) { | ||
dbPath = dbPath.toAbsolutePath(); | ||
if (!Files.exists(dbPath)) { | ||
try { | ||
Files.createDirectories(dbPath.getParent()); | ||
Files.createFile(dbPath); | ||
} catch (IOException e) { | ||
throw new RuntimeException("Exception creating database!", e); | ||
} | ||
} | ||
final String url = "jdbc:sqlite:" + dbPath; | ||
final SQLiteDataSource dataSource = new SQLiteDataSource(); | ||
dataSource.setUrl(url); | ||
dataSource.setEncoding("UTF-8"); | ||
dataSource.setDatabaseName(name); | ||
dataSource.setEnforceForeignKeys(true); | ||
dataSource.setCaseSensitiveLike(false); | ||
|
||
final var flyway = flywayConfig.apply(Flyway.configure().dataSource(dataSource)).load(); | ||
flyway.migrate(); | ||
|
||
return Jdbi.create(dataSource) | ||
.installPlugin(new SqlObjectPlugin()); | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
src/main/java/net/neoforged/automation/db/DiscordUsersDAO.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package net.neoforged.automation.db; | ||
|
||
import org.jdbi.v3.sqlobject.customizer.Bind; | ||
import org.jdbi.v3.sqlobject.statement.SqlQuery; | ||
import org.jdbi.v3.sqlobject.statement.SqlUpdate; | ||
import org.jdbi.v3.sqlobject.transaction.Transactional; | ||
import org.jetbrains.annotations.Nullable; | ||
|
||
public interface DiscordUsersDAO extends Transactional<DiscordUsersDAO> { | ||
@Nullable | ||
@SqlQuery("select github from discord_users where user = :user") | ||
String getUser(@Bind("user") long id); | ||
|
||
@SqlUpdate("insert into discord_users (user, github) values (:user, :github)") | ||
void linkUser(@Bind("user") long id, @Bind("github") String githubName); | ||
} |
104 changes: 104 additions & 0 deletions
104
src/main/java/net/neoforged/automation/discord/DiscordBot.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package net.neoforged.automation.discord; | ||
|
||
import com.jagrosh.jdautilities.command.CommandClient; | ||
import com.jagrosh.jdautilities.command.CommandClientBuilder; | ||
import io.javalin.Javalin; | ||
import net.dv8tion.jda.api.JDABuilder; | ||
import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; | ||
import net.dv8tion.jda.api.interactions.commands.Command; | ||
import net.dv8tion.jda.api.interactions.commands.OptionMapping; | ||
import net.neoforged.automation.Main; | ||
import net.neoforged.automation.StartupConfiguration; | ||
import net.neoforged.automation.discord.command.GitHubCommand; | ||
import org.kohsuke.github.GHRepository; | ||
import org.kohsuke.github.GitHub; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import java.io.IOException; | ||
import java.util.List; | ||
import java.util.Locale; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.function.Consumer; | ||
import java.util.function.Predicate; | ||
|
||
public class DiscordBot { | ||
public static final Logger LOGGER = LoggerFactory.getLogger(DiscordBot.class); | ||
|
||
private static volatile List<String> knownRepositories = List.of(); | ||
|
||
public static void create(String token, GitHub gitHub, StartupConfiguration configuration, Javalin app) throws InterruptedException { | ||
var cfg = new OauthConfig(configuration.get("githubOauthClientId", ""), configuration.get("githubOauthClientSecret", ""), | ||
configuration.get("serverUrl", "") + "/oauth2/discord/github"); | ||
|
||
var jda = JDABuilder.createLight(token) | ||
.addEventListeners(createClient(gitHub, cfg)) | ||
.build() | ||
.awaitReady(); | ||
|
||
app.get("/oauth2/discord/github", new GitHubOauthLinkHandler(cfg)); | ||
|
||
LOGGER.info("Discord bot started. Logged in as {}", jda.getSelfUser().getEffectiveName()); | ||
|
||
Main.EXECUTOR.scheduleAtFixedRate(() -> updateRepos(gitHub), 0, 1, TimeUnit.HOURS); | ||
} | ||
|
||
private static CommandClient createClient(GitHub gitHub, OauthConfig config) { | ||
var builder = new CommandClientBuilder(); | ||
builder.setOwnerId("0"); | ||
builder.addSlashCommand(new GHLinkCommand(config.clientId, config.redirectUrl)); | ||
builder.addSlashCommand(new GitHubCommand(gitHub)); | ||
return builder.build(); | ||
} | ||
|
||
private static void updateRepos(GitHub gitHub) { | ||
try { | ||
synchronized (DiscordBot.class) { | ||
knownRepositories = gitHub.getInstallation().listRepositories().toList() | ||
.stream() | ||
.filter(r -> !r.isArchived() && !r.isPrivate()) | ||
.map(GHRepository::getFullName) | ||
.sorted() | ||
.toList(); | ||
} | ||
} catch (IOException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
public static void suggestRepositories(CommandAutoCompleteInteractionEvent event) { | ||
Predicate<String> filter = filterContainsCurrent(event); | ||
event.replyChoices(knownRepositories.stream().filter(filter) | ||
.limit(25) | ||
.map(o -> new Command.Choice(o, o)) | ||
.toList()) | ||
.queue(); | ||
} | ||
|
||
public static Consumer<CommandAutoCompleteInteractionEvent> suggestBranches(GitHub gitHub) { | ||
return event -> { | ||
var repoName = event.getOption("repo", OptionMapping::getAsString); | ||
if (repoName == null || repoName.isBlank()) return; | ||
|
||
try { | ||
var repo = gitHub.getRepository(repoName); | ||
|
||
event.replyChoices(repo.getBranches().keySet() | ||
.stream().filter(filterContainsCurrent(event)) | ||
.limit(25) | ||
.map(o -> new Command.Choice(o, o)) | ||
.toList()).queue(); | ||
} catch (IOException e) { | ||
throw new RuntimeException(e); | ||
} | ||
}; | ||
} | ||
|
||
private static Predicate<String> filterContainsCurrent(CommandAutoCompleteInteractionEvent event) { | ||
var current = event.getFocusedOption().getValue().toLowerCase(Locale.ROOT); | ||
return current.isBlank() ? e -> true : e -> e.toLowerCase(Locale.ROOT).contains(current); | ||
} | ||
|
||
record OauthConfig(String clientId, String clientSecret, String redirectUrl) { | ||
} | ||
} |
27 changes: 27 additions & 0 deletions
27
src/main/java/net/neoforged/automation/discord/GHLinkCommand.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package net.neoforged.automation.discord; | ||
|
||
import com.jagrosh.jdautilities.command.SlashCommand; | ||
import com.jagrosh.jdautilities.command.SlashCommandEvent; | ||
import net.dv8tion.jda.api.interactions.components.buttons.Button; | ||
|
||
public class GHLinkCommand extends SlashCommand { | ||
private final String clientId; | ||
private final String redirectUrl; | ||
public GHLinkCommand(String clientId, String redirectUrl) { | ||
this.name = "ghlink"; | ||
this.help = "Link your Discord account to your GitHub account"; | ||
|
||
this.clientId = clientId; | ||
this.redirectUrl = redirectUrl; | ||
} | ||
|
||
@Override | ||
protected void execute(SlashCommandEvent event) { | ||
var url = "https://github.com/login/oauth/authorize?client_id=" + clientId + "&response_type=code&scope=user:read&redirect_uri=" + redirectUrl + "&state=" + event.getUser().getId(); | ||
|
||
event.reply("Please use the button below to link this Discord account to a GitHub one.") | ||
.setEphemeral(true) | ||
.addActionRow(Button.link(url, "Link account")) | ||
.queue(); | ||
} | ||
} |
63 changes: 63 additions & 0 deletions
63
src/main/java/net/neoforged/automation/discord/GitHubOauthLinkHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package net.neoforged.automation.discord; | ||
|
||
import com.fasterxml.jackson.databind.JsonNode; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import io.javalin.http.Context; | ||
import io.javalin.http.Handler; | ||
import io.javalin.http.HttpStatus; | ||
import net.neoforged.automation.Main; | ||
import net.neoforged.automation.db.Database; | ||
import net.neoforged.automation.db.DiscordUsersDAO; | ||
import org.jetbrains.annotations.NotNull; | ||
import org.kohsuke.github.GitHubBuilder; | ||
|
||
import java.net.URI; | ||
import java.net.http.HttpRequest; | ||
import java.net.http.HttpResponse; | ||
|
||
public record GitHubOauthLinkHandler(DiscordBot.OauthConfig config) implements Handler { | ||
@Override | ||
public void handle(@NotNull Context ctx) throws Exception { | ||
var code = ctx.queryParam("code"); | ||
var state = ctx.queryParam("state"); | ||
if (code == null || code.isBlank() || state == null || state.isBlank()) { | ||
ctx.result("Wrong input provided") | ||
.status(HttpStatus.BAD_REQUEST); | ||
return; | ||
} | ||
|
||
var mapper = new ObjectMapper(); | ||
var node = mapper.createObjectNode(); | ||
node.put("client_id", config.clientId()); | ||
node.put("client_secret", config.clientSecret()); | ||
node.put("grant_type", "authorization_code"); | ||
node.put("redirect_uri", config.redirectUrl()); | ||
node.put("scope", "user:read"); | ||
node.put("code", code); | ||
|
||
var req = Main.HTTP_CLIENT.send(HttpRequest.newBuilder() | ||
.POST(HttpRequest.BodyPublishers.ofString(node.toString())) | ||
.uri(URI.create("https://github.com/login/oauth/access_token")) | ||
.header("Content-Type", "application/json") | ||
.header("Accept", "application/json") | ||
.build(), HttpResponse.BodyHandlers.ofString()); | ||
|
||
var asJson = mapper.readValue(req.body(), JsonNode.class); | ||
var token = asJson.get("access_token"); | ||
if (token == null) { | ||
ctx.result("Error: " + req.body()) | ||
.status(HttpStatus.BAD_REQUEST); | ||
return; | ||
} | ||
|
||
var user = new GitHubBuilder() | ||
.withJwtToken(token.asText()) | ||
.build() | ||
.getMyself() | ||
.getLogin(); | ||
|
||
Database.useExtension(DiscordUsersDAO.class, db -> db.linkUser(Long.parseLong(state), user)); | ||
|
||
ctx.result("Authenticated as " + user + ". You may close this page.").status(HttpStatus.OK); | ||
} | ||
} |
Oops, something went wrong.