//! `reaction [--comment ] [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 ` 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 reaction` / `pr 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. `--list-allowed` prints the *actual* instance-configured //! list (`GET /settings/ui`'s `allowed_reactions`) instead of leaving a //! caller to guess it or learn it from a 403 — this is instance-global, //! not issue/PR-scoped, so `number`/`--comment` are ignored when it's //! set (the number is still required positionally, same as every other //! generic verb, but unused on this path). 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. Ignored (but still required) when /// `--list-allowed` is set. 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, /// Print the instance's actual configured reaction shortcodes /// (`GET /settings/ui`) instead of listing/adding/removing — /// instance-global, ignores `number`/`--comment`. #[arg(long)] pub(crate) list_allowed: bool, #[command(subcommand)] action: Option, } #[derive(Subcommand)] enum Action { /// List reactions (default when no action is given). List, /// Add a reaction — a Forgejo shortcode, for example `+1`, `heart`, `rocket`. Add { content: String }, /// Remove your own reaction with this content. Remove { content: String }, } pub fn run(client: &Client, args: Args) -> Result<()> { if args.list_allowed { let settings = client.api().get_general_ui_settings().send()?; return print_json(&serde_json::json!( settings.allowed_reactions.unwrap_or_default() )); } 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)) }