use clap::Parser; use color_eyre::Result; use jacquard::identity::JacquardResolver; use jacquard::types::ident::AtIdentifier; use jacquard::identity::resolver::{DidStep, HandleStep, MiniDoc, PlcSource, ResolverOptions}; use ratatui::layout::{Constraint, Direction, Layout}; use ratatui::style::Modifier; use ratatui::text::Span; use ratatui::widgets::{Block, Cell, Padding, Paragraph, Row, Table}; use ratatui::{Frame, Terminal, TerminalOptions, Viewport}; use ratatui::prelude::{Color, CrosstermBackend, Style}; use std::io::{stdout, Stdout}; use miette::IntoDiagnostic; pub struct App { pub did: String, pub handle: String, pub pds: String, pub signing_key: String } impl App { pub fn new(did_document: &MiniDoc<'_>) -> Self { App { did: did_document.did.to_string(), handle: did_document.handle.to_string(), pds: did_document.pds.to_string(), signing_key: did_document.signing_key.to_string() } } } #[derive(Parser, Debug)] #[command(version, about, long_about = "neofetch but for atprotocol")] struct Args { /// Your AT Protocol handle or DID #[arg(short, long)] identifier: String, } #[tokio::main] async fn main() -> miette::Result<()> { let args = Args::parse(); let identifier = AtIdentifier::new(&args.identifier)?; let request_client = reqwest::Client::new(); let mut handle_order = vec![]; handle_order.push(HandleStep::DnsTxt); handle_order.push(HandleStep::HttpsWellKnown); handle_order.push(HandleStep::PdsResolveHandle); let resolver_options = ResolverOptions::new() .plc_source(PlcSource::slingshot_default()) .handle_order(handle_order) .did_order(vec![ DidStep::DidWebHttps, DidStep::PlcHttp, DidStep::PdsResolveDid,]) .validate_doc_id(true) .public_fallback_for_handle(true) .build(); let resolver = JacquardResolver::new(request_client, resolver_options); let mini_doc_response = resolver.fetch_mini_doc_via_slingshot_identifier(&identifier).await?; let did_document= mini_doc_response.parse()?; let app = App::new(&did_document); let backend = CrosstermBackend::new(stdout()); let content_height: u16 = 40; let total_height = content_height + 2; let options = TerminalOptions { viewport: Viewport::Inline(total_height), }; let mut terminal = Terminal::with_options(backend, options).into_diagnostic()?; let _ = run(&mut terminal, &app); Ok(()) } fn run(terminal: &mut Terminal>, app: &App) -> Result<()> { terminal.draw(|frame| { render(frame, app); })?; Ok(()) } fn render(frame: &mut Frame, app: &App) { let system_data = vec![ ("DID", app.did.clone()), ("Handle", app.handle.clone()), ("PDS", app.pds.clone()), ("Signing Key", app.signing_key.clone()), ]; let constraints = [ Constraint::Percentage(35), Constraint::Percentage(65), ]; let rows: Vec = system_data .iter() .map(|(label, value)| { Row::new(vec![ Cell::from(Span::styled( format!("{}:", label), Style::default().fg(Color::Reset).add_modifier(Modifier::BOLD), )), Cell::from(Span::styled( value, Style::default().fg(Color::White), )), ]) }) .collect(); let table_constraints = [ Constraint::Length(15), Constraint::Min(0), ]; let info_block = Block::default().padding(Padding::top(1)); let info_table = Table::new(rows, table_constraints).block(info_block); let chunks = Layout::default().direction(Direction::Horizontal) .constraints(constraints).split(frame.area()); let ascii_area = chunks[0]; let info_area = chunks[1]; let ascii_art = r#" ==;;!!*;;;:;;;;; $$$!;;;;;;=;;::;;;;;;;;; $$#$*!;==;;;;;;;;=!;;;;;;;;; $#$$$$#;; ;;;;;;;;; =*###=; ;;;;;;;; ;;;;;; ;;;;;;=*$$$=;;; ;;;;;; !==;;; ;;;;;;;;;*$$$$*;= ;;;;;; =!;;;= ;;;;;;;;;;=$$$$$$$$ =;;;;; =;;;;; ;;;;;; $$$$$$ !=;;;; $#!=;; ;;==!$ $$$$$ $###!= ;===== ===!$$ $$$$$ ##!!*! ==$$== ===$$$ $$$$$# **!=!! $$$==== *###$$$$$$$##$###****!*!!=;;; ==$=== $$$$#####****!!!===;;;;==! =!==#$ $###******! ====;;===== =!$$$$# !*$$$#### $$$#*#*!=!=;::~~~~-- *!**=;;;::::~~----- :::::~:~~~----- "#; let ascii_block = Block::default().padding(Padding::vertical(0)); let ascii_widget = Paragraph::new(ascii_art) .block(ascii_block); frame.render_widget(ascii_widget, ascii_area); frame.render_widget(info_table, info_area); }