hive-forge: show + post/remove emoji reactions on issues, PRs, and comments

This commit is contained in:
damocles 2026-08-18 12:37:52 +02:00 committed by mara
commit 2c45a9960f
9 changed files with 261 additions and 13 deletions

View file

@ -0,0 +1,96 @@
//! `reaction <number> [--comment <id>] [list|add|remove] [content]` —
//! list, add, or remove emoji reactions on an issue/PR or one of its
//! comments.
//!
//! Forgejo's reaction endpoints work on the shared issue/PR index (PRs
//! are issues internally, same as `dependency`) when no `--comment` is
//! given; `--comment <id>` redirects to the separate comment-reaction
//! endpoint pair, keyed on the comment's own id (not the parent
//! issue/PR number — `number` still has to be passed so this verb slots
//! into `issue <n> reaction` / `pr <n> reaction` like every other
//! generic verb, but it's otherwise unused on that path).
//!
//! `content` is Forgejo's reaction shortcode vocabulary (`+1`, `-1`,
//! `laugh`, `confused`, `heart`, `hooray`, `rocket`, `eyes`, …), not a
//! raw emoji character — same set the forge web UI's reaction picker
//! offers.
use anyhow::Result;
use clap::{Args as ClapArgs, Subcommand};
use forgejo_api::structs::EditReactionOption;
use crate::client::{Client, index};
use crate::verbs::{comment_reactions, issue_reactions, print_json};
#[derive(ClapArgs)]
pub struct Args {
/// Issue or PR number.
pub(crate) number: u64,
/// Target a specific comment's reactions instead of the issue/PR
/// itself — the comment's own id (from `comments`/`comment-show`),
/// not its position in the thread.
#[arg(long)]
comment: Option<u64>,
#[command(subcommand)]
action: Option<Action>,
}
#[derive(Subcommand)]
enum Action {
/// List reactions (default when no action is given).
List,
/// Add a reaction — a Forgejo shortcode, e.g. `+1`, `heart`, `rocket`.
Add { content: String },
/// Remove your own reaction with this content.
Remove { content: String },
}
pub fn run(client: &Client, args: Args) -> Result<()> {
let (owner, name) = client.owner_repo()?;
match args.action.unwrap_or(Action::List) {
Action::List => {}
Action::Add { content } => {
let body = EditReactionOption {
content: Some(content),
};
match args.comment {
Some(id) => {
client
.api()
.issue_post_comment_reaction(owner, name, index(id)?, body)
.send()?;
}
None => {
client
.api()
.issue_post_issue_reaction(owner, name, index(args.number)?, body)
.send()?;
}
}
}
Action::Remove { content } => {
let body = EditReactionOption {
content: Some(content),
};
match args.comment {
Some(id) => {
client
.api()
.issue_delete_comment_reaction(owner, name, index(id)?, body)
.send()?;
}
None => {
client
.api()
.issue_delete_issue_reaction(owner, name, index(args.number)?, body)
.send()?;
}
}
}
}
let reactions = match args.comment {
Some(id) => comment_reactions(client, owner, name, id)?,
None => issue_reactions(client, owner, name, args.number)?,
};
print_json(&serde_json::json!(reactions))
}