summaryrefslogtreecommitdiff
path: root/src/command
diff options
context:
space:
mode:
Diffstat (limited to 'src/command')
-rw-r--r--src/command/mod.rs51
-rw-r--r--src/command/ping.rs23
-rw-r--r--src/command/track.rs133
3 files changed, 207 insertions, 0 deletions
diff --git a/src/command/mod.rs b/src/command/mod.rs
new file mode 100644
index 0000000..eda4167
--- /dev/null
+++ b/src/command/mod.rs
@@ -0,0 +1,51 @@
+use eyre::{OptionExt, Result};
+use serenity::builder::{
+ CreateCommand, CreateInteractionResponse, CreateInteractionResponseMessage,
+};
+use serenity::model::application::CommandInteraction;
+use serenity::prelude::Context;
+use tracing::instrument;
+
+use crate::client::SharedClient;
+
+mod ping;
+mod track;
+
+macro_rules! cmd {
+ ($module: ident) => {
+ $module::register()
+ };
+}
+
+/// Return a list of all our [CreateCommand]s
+pub fn to_vec() -> Vec<CreateCommand> {
+ vec![cmd!(ping), cmd!(track)]
+}
+
+/// Dispatch our commands from a [CommandInteraction]
+#[instrument(skip(ctx))]
+pub async fn dispatch(ctx: &Context, command: &CommandInteraction) -> Result<()> {
+ let command_name = command.data.name.as_str();
+
+ // grab our http client from the aether
+ let http = {
+ let read = ctx.data.read().await;
+ read.get::<SharedClient>()
+ .ok_or_eyre("Couldn't get shared HTTP client! WHY??????")?
+ .clone()
+ };
+
+ match command_name {
+ "ping" => ping::respond(ctx, command).await?,
+ "track" => track::respond(ctx, &http, command).await?,
+ _ => {
+ let message = CreateInteractionResponseMessage::new().content(format!(
+ "It doesn't look like you can use `{command_name}`. Sorry :("
+ ));
+ let response = CreateInteractionResponse::Message(message);
+ command.create_response(&ctx, response).await?
+ }
+ };
+
+ Ok(())
+}
diff --git a/src/command/ping.rs b/src/command/ping.rs
new file mode 100644
index 0000000..1b1b812
--- /dev/null
+++ b/src/command/ping.rs
@@ -0,0 +1,23 @@
+use eyre::Result;
+use serenity::builder::{
+ CreateCommand, CreateInteractionResponse, CreateInteractionResponseMessage,
+};
+use serenity::model::application::{CommandInteraction, InstallationContext};
+use serenity::prelude::Context;
+use tracing::{instrument, trace};
+
+#[instrument]
+pub async fn respond(ctx: &Context, command: &CommandInteraction) -> Result<()> {
+ trace!("Responding to ping command");
+ let message = CreateInteractionResponseMessage::new().content("Pong!");
+ let response = CreateInteractionResponse::Message(message);
+ command.create_response(&ctx, response).await?;
+
+ Ok(())
+}
+
+pub fn register() -> CreateCommand {
+ CreateCommand::new("ping")
+ .description("Check if the bot is up")
+ .add_integration_type(InstallationContext::User)
+}
diff --git a/src/command/track.rs b/src/command/track.rs
new file mode 100644
index 0000000..6217043
--- /dev/null
+++ b/src/command/track.rs
@@ -0,0 +1,133 @@
+use crate::http::{Client, GithubClientExt};
+
+use eyre::Result;
+use futures::future::try_join_all;
+use serenity::builder::{CreateCommand, CreateCommandOption, CreateInteractionResponseFollowup};
+use serenity::model::application::{
+ CommandInteraction, CommandOptionType, InstallationContext, ResolvedOption, ResolvedValue,
+};
+use serenity::prelude::Context;
+use tracing::{instrument, trace};
+
+/// All of our tracked branches in nixpkgs
+const BRANCHES: [&str; 8] = [
+ "master",
+ "staging",
+ "nixos-unstable",
+ "nixos-unstable-small",
+ "nixos-24.05-small",
+ "release-24.05",
+ "nixos-23.11-small",
+ "release-23.11",
+];
+
+#[derive(Clone, Debug, Default)]
+struct BranchStatus {
+ repo_owner: String,
+ repo_name: String,
+ name: String,
+}
+
+impl BranchStatus {
+ fn new(repo_owner: String, repo_name: String, name: String) -> Self {
+ Self {
+ repo_owner,
+ repo_name,
+ name,
+ }
+ }
+
+ /// Make a nice friendly string displaying if this branch has a PR merged into it
+ fn to_status_string(&self, has_pr: bool) -> String {
+ let emoji = if has_pr { "✅" } else { "❌" };
+ format!("`{}` {emoji}", &self.name)
+ }
+
+ /// Check if this branch has the specified pull request merged into it
+ #[instrument(skip(http))]
+ async fn has_pr(&self, http: &Client, pr: u64) -> Result<bool> {
+ let commit = http
+ .merge_commit_for(
+ &self.repo_owner,
+ &self.repo_name,
+ u64::try_from(pr).unwrap(),
+ )
+ .await?;
+
+ let has_pr = http
+ .is_commit_in_branch(&self.repo_owner, &self.repo_name, &self.name, &commit)
+ .await?;
+
+ Ok(has_pr)
+ }
+}
+
+/// async wrapper for [BranchStatus::to_status_string()]
+#[instrument(skip(http))]
+async fn collect_status(
+ http: &Client,
+ repo_owner: String,
+ repo_name: String,
+ branch: String,
+ pr: u64,
+) -> Result<String> {
+ let status = BranchStatus::new(repo_owner, repo_name, branch);
+ let has_pr = status.has_pr(http, pr).await?;
+ let res = status.to_status_string(has_pr);
+
+ Ok(res)
+}
+
+#[instrument(skip_all)]
+pub async fn respond(ctx: &Context, http: &Client, command: &CommandInteraction) -> Result<()> {
+ trace!("Responding to track command");
+
+ // this will probably take a while
+ command.defer(&ctx).await?;
+
+ // TODO: make these configurable for nixpkgs forks...or other github repos ig
+ const REPO_OWNER: &str = "NixOS";
+ const REPO_NAME: &str = "nixpkgs";
+
+ let options = command.data.options();
+
+ let response = if let Some(ResolvedOption {
+ value: ResolvedValue::Integer(pr),
+ ..
+ }) = options.first()
+ {
+ if *pr < 0 {
+ CreateInteractionResponseFollowup::new().content("PR numbers aren't negative...")
+ } else {
+ // TODO: this is gross
+ let statuses = try_join_all(BRANCHES.iter().map(|&branch| {
+ collect_status(
+ http,
+ REPO_OWNER.to_string(),
+ REPO_NAME.to_string(),
+ branch.to_string(),
+ u64::try_from(*pr).unwrap(),
+ )
+ }))
+ .await?;
+
+ CreateInteractionResponseFollowup::new().content(statuses.join("\n"))
+ }
+ } else {
+ CreateInteractionResponseFollowup::new().content("Please provide a valid commit!")
+ };
+
+ command.create_followup(&ctx, response).await?;
+
+ Ok(())
+}
+
+pub fn register() -> CreateCommand {
+ CreateCommand::new("track")
+ .description("Track a nixpkgs PR")
+ .add_integration_type(InstallationContext::User)
+ .add_option(
+ CreateCommandOption::new(CommandOptionType::Integer, "pull_request", "PR to track")
+ .required(true),
+ )
+}