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
|
|
@ -14,11 +14,9 @@
|
|||
//! always reported** — never a silent truncation.
|
||||
//! - `--since <RFC3339>` filters to comments at or after that
|
||||
//! timestamp instead of a head/tail window — a cursor: feed the
|
||||
//! last-seen row's own `created_at` back in next time to fetch only
|
||||
//! what's new. Mutually exclusive with `--tail`. No total exists for
|
||||
//! a since-filtered query, so this uses the same over-fetch-by-one
|
||||
//! trick `timeline`'s `--limit` does, and `--limit` is clamped the
|
||||
//! same way (see `crate::verbs::MAX_LIMIT`).
|
||||
//! last-seen row's own `created_at` back in next time. Mutually
|
||||
//! exclusive with `--tail`; `--limit` clamps like `timeline`'s own
|
||||
//! does (see `crate::verbs::MAX_LIMIT`).
|
||||
//!
|
||||
//! Review *bodies* on PRs are always merged in too (`pulls/<n>/reviews`
|
||||
//! isn't the issues/comments thread), tagged `[review: STATE]`, with a
|
||||
|
|
@ -27,7 +25,8 @@
|
|||
//!
|
||||
//! `--json` output is an object (`{"comments": [...], "more_before": N,
|
||||
//! "more_after": N, "since_more": bool}`), not a bare array, so a
|
||||
//! script can read the truncation info too.
|
||||
//! script can read the truncation info too. `--show-reactions` adds a
|
||||
//! per-comment reaction summary — see that flag's own doc for the cost.
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Args as ClapArgs;
|
||||
|
|
@ -37,7 +36,10 @@ use time::OffsetDateTime;
|
|||
|
||||
use crate::client::{Client, index};
|
||||
use crate::notify;
|
||||
use crate::verbs::{MAX_LIMIT, PAGE_SIZE, clamp_limit, parse_rfc3339, print_json, rfc3339};
|
||||
use crate::verbs::{
|
||||
MAX_LIMIT, PAGE_SIZE, clamp_limit, comment_reactions, parse_rfc3339, print_json,
|
||||
reaction_summary, rfc3339,
|
||||
};
|
||||
|
||||
#[derive(ClapArgs)]
|
||||
pub struct Args {
|
||||
|
|
@ -58,6 +60,10 @@ pub struct Args {
|
|||
/// `--tail`.
|
||||
#[arg(long)]
|
||||
since: Option<String>,
|
||||
/// Fetch + display each shown comment's reaction summary. Costs one
|
||||
/// extra request per comment shown — opt-in, not the default.
|
||||
#[arg(long)]
|
||||
show_reactions: bool,
|
||||
}
|
||||
|
||||
pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||
|
|
@ -90,10 +96,12 @@ pub fn run(client: &Client, args: Args) -> Result<()> {
|
|||
};
|
||||
// Merge in PR review bodies (empty for issues — degrades to a
|
||||
// no-op) so review feedback isn't silently dropped.
|
||||
let comments = merge_chronological(thread, fetch_review_bodies(client, args.number));
|
||||
let mut comments = merge_chronological(thread, fetch_review_bodies(client, args.number));
|
||||
// Reading the thread clears its unread notification so the
|
||||
// read-before-comment guard (in `comment`) lets a reply through.
|
||||
notify::mark_read_best_effort(client, repo, args.number);
|
||||
let (owner, name) = client.owner_repo()?;
|
||||
attach_reactions(client, owner, name, &mut comments, args.show_reactions);
|
||||
if client.json_mode() {
|
||||
let trimmed: Vec<Value> = comments
|
||||
.iter()
|
||||
|
|
@ -109,6 +117,7 @@ pub fn run(client: &Client, args: Args) -> Result<()> {
|
|||
"state": c.get("state"),
|
||||
"comments_count": c.get("comments_count"),
|
||||
"attachments": attachment_json_from_value(c),
|
||||
"reactions": c.get("reactions").cloned().unwrap_or_else(|| json!([])),
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
|
@ -145,6 +154,13 @@ pub fn run(client: &Client, args: Args) -> Result<()> {
|
|||
for line in attachment_lines_from_value(c) {
|
||||
println!("{line}");
|
||||
}
|
||||
if let Some(summary) = c
|
||||
.get("reactions")
|
||||
.and_then(Value::as_array)
|
||||
.and_then(|r| reaction_summary(r))
|
||||
{
|
||||
println!("[reactions: {summary}]");
|
||||
}
|
||||
println!();
|
||||
}
|
||||
if since_clamped {
|
||||
|
|
@ -184,6 +200,39 @@ fn truncation_note(more_before: usize, more_after: usize) -> Option<String> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Fetch + attach each shown comment's reaction list under a
|
||||
/// `"reactions"` key, mutating in place so both the `--json` and
|
||||
/// pretty-print branches see the same fetch. No-op when `show_reactions`
|
||||
/// is false (the default) — the whole point of gating this behind a
|
||||
/// flag is that a plain `comments` call costs nothing extra. Review
|
||||
/// entries are skipped (synthesized, never have real reactions); a
|
||||
/// per-comment fetch failure is swallowed rather than failing the whole
|
||||
/// listing over one comment.
|
||||
fn attach_reactions(
|
||||
client: &Client,
|
||||
owner: &str,
|
||||
name: &str,
|
||||
comments: &mut [Value],
|
||||
show_reactions: bool,
|
||||
) {
|
||||
if !show_reactions {
|
||||
return;
|
||||
}
|
||||
for c in comments.iter_mut() {
|
||||
if c.get("kind").and_then(Value::as_str) == Some("review") {
|
||||
continue;
|
||||
}
|
||||
let Some(id) = c.get("id").and_then(Value::as_u64) else {
|
||||
continue;
|
||||
};
|
||||
if let Ok(reactions) = comment_reactions(client, owner, name, id)
|
||||
&& let Some(obj) = c.as_object_mut()
|
||||
{
|
||||
obj.insert("reactions".to_owned(), json!(reactions));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Attachment display lines for a single comment's already-serialized
|
||||
/// `assets` field — the shape this module's merge/render pipeline works
|
||||
/// on (comments arrive as `Value`s, not the typed `Comment` struct, once
|
||||
|
|
|
|||
Loading…
Reference in a new issue