hive-forge: show + post/remove emoji reactions on issues, PRs, and comments
This commit is contained in:
parent
eb8387bd73
commit
2c45a9960f
9 changed files with 261 additions and 13 deletions
|
|
@ -34,6 +34,7 @@ pub mod pr_create;
|
|||
pub mod pr_merge;
|
||||
pub mod pr_reviews;
|
||||
pub mod pr_status;
|
||||
pub mod reaction;
|
||||
pub mod reopen;
|
||||
pub mod repo_add_collaborator;
|
||||
pub mod repo_create;
|
||||
|
|
@ -47,7 +48,7 @@ pub mod view;
|
|||
use std::fmt::Write as _;
|
||||
|
||||
use anyhow::Result;
|
||||
use forgejo_api::structs::Attachment;
|
||||
use forgejo_api::structs::{Attachment, Reaction};
|
||||
use serde_json::{Value, json};
|
||||
use time::OffsetDateTime;
|
||||
use time::format_description::well_known::Rfc3339;
|
||||
|
|
@ -380,6 +381,79 @@ pub(crate) fn dependency_summaries(
|
|||
.collect())
|
||||
}
|
||||
|
||||
/// The current reactions on an issue or PR itself (not a comment) — each
|
||||
/// entry's `content` is Forgejo's shortcode (`"+1"`, `"heart"`, …, the
|
||||
/// same vocabulary GitHub uses), `user` its login. Same shared-index
|
||||
/// rationale as [`dependency_summaries`]: PRs are issues internally, so
|
||||
/// `issue`/`pr`/`view` and the `reaction` verb's own listing all call
|
||||
/// this instead of duplicating the fetch-and-shape step.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Propagates the forge API errors from listing reactions.
|
||||
pub(crate) fn issue_reactions(
|
||||
client: &Client,
|
||||
owner: &str,
|
||||
name: &str,
|
||||
number: u64,
|
||||
) -> Result<Vec<Value>> {
|
||||
let (_, reactions) = client
|
||||
.api()
|
||||
.issue_get_issue_reactions(owner, name, index(number)?)
|
||||
.send()?;
|
||||
Ok(reaction_values(reactions))
|
||||
}
|
||||
|
||||
/// The current reactions on a single comment, by comment id (not the
|
||||
/// parent issue/PR number — Forgejo's comment-reaction endpoints are
|
||||
/// keyed on the comment alone, same as [`comment_show`]'s lookup).
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Propagates the forge API errors from listing reactions.
|
||||
pub(crate) fn comment_reactions(
|
||||
client: &Client,
|
||||
owner: &str,
|
||||
name: &str,
|
||||
comment_id: u64,
|
||||
) -> Result<Vec<Value>> {
|
||||
let reactions = client
|
||||
.api()
|
||||
.issue_get_comment_reactions(owner, name, index(comment_id)?)
|
||||
.send()?;
|
||||
Ok(reaction_values(reactions))
|
||||
}
|
||||
|
||||
fn reaction_values(reactions: Vec<Reaction>) -> Vec<Value> {
|
||||
reactions
|
||||
.into_iter()
|
||||
.map(|r| json!({ "content": r.content, "user": r.user.and_then(|u| u.login) }))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// A one-line `content×count` summary of a reaction list (e.g. `+1×2
|
||||
/// heart×1`), grouped and sorted by content name for a stable rendering.
|
||||
/// `None` for an empty list, so a row/section with no reactions omits the
|
||||
/// summary entirely rather than printing something empty.
|
||||
pub(crate) fn reaction_summary(reactions: &[Value]) -> Option<String> {
|
||||
let mut counts: std::collections::BTreeMap<&str, usize> = std::collections::BTreeMap::new();
|
||||
for r in reactions {
|
||||
if let Some(content) = r.get("content").and_then(Value::as_str) {
|
||||
*counts.entry(content).or_insert(0) += 1;
|
||||
}
|
||||
}
|
||||
if counts.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(
|
||||
counts
|
||||
.into_iter()
|
||||
.map(|(content, n)| format!("{content}×{n}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" "),
|
||||
)
|
||||
}
|
||||
|
||||
/// A single attachment as one display line — `[file: <name>] <url>`,
|
||||
/// mirroring the `[file: ...]` marker convention `read_room` already
|
||||
/// uses for matrix attachments. `None` when the attachment has no
|
||||
|
|
|
|||
Loading…
Reference in a new issue