From c69eea2f4823da476628742fbbec600ee95ac049 Mon Sep 17 00:00:00 2001 From: seth Date: Mon, 27 May 2024 04:55:45 -0400 Subject: initial commit --- src/command/mod.rs | 51 ++++++++++++++++++++ src/command/ping.rs | 23 +++++++++ src/command/track.rs | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 207 insertions(+) create mode 100644 src/command/mod.rs create mode 100644 src/command/ping.rs create mode 100644 src/command/track.rs (limited to 'src/command') 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 { + 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::() + .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 { + 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 { + 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), + ) +} -- cgit v1.2.3