#!/usr/bin/env bash # # ./mastotree.sh [-w] # Exibe árvore encadeada de posts de uma thread do Mastodon. # A opção -w gera a saída formatada, em um arquivo chamado tree-xxxxx.html # Sem ela, a saída é exibida em texto puro, diretamente no terminal. # # Exemplo: # ./mastotree.sh -w https://arram.senta-la.cloud/@autobrain/116579911019740570 # # Dependências: curl, jq # # Copyright (c) 2026, Augusto Campos (https://augustocampos.net/). # Licensed under the Apache License, Version 2.0. # # Default é mostrar diretamente no terminal OUTPUT_MODE="terminal" URL="" # processa os parâmetros while [[ "$#" -gt 0 ]]; do case $1 in -w) OUTPUT_MODE="html"; shift ;; *) URL="$1"; shift ;; esac done if [ -z "$URL" ]; then echo "Sintaxe: $0 [-w] " echo " -w Exporta formatado em HTML" fi DOMAIN=$(echo "$URL" | awk -F/ '{print $3}') STATUS_ID=$(echo "$URL" | awk -F/ '{print $NF}') if [ -z "$DOMAIN" ] || [ -z "$STATUS_ID" ]; then echo "Erro: não consegui processar a URL. Ela é mesmo de um post do Mastodon?" >&2 exit 1 fi CONTEXT_FILE=$(mktemp) # Baixa o post e o contexto, via API do Mastodon TARGET_JSON=$(curl -s "https://$DOMAIN/api/v1/statuses/$STATUS_ID") if [ -z "$TARGET_JSON" ] || echo "$TARGET_JSON" | jq -e '.error' >/dev/null 2>&1; then echo "Erro ao baixar da API ou interpretar o JSON do post inicial." >&2 rm -f "$CONTEXT_FILE" exit 1 fi curl -s "https://$DOMAIN/api/v1/statuses/$STATUS_ID/context" > "$CONTEXT_FILE" # Função de apoio: strip HTML tags (pra quando a saída é no terminal) clean_html() { echo "$1" | sed -e 's/]*>/\n/g' -e 's/<[^>]*>//g' -e 's/"/"/g' -e 's/&/\&/g' } # Exibe um post individual, tanto para o terminal quanto para a web print_post() { local acct="$1" local content="$2" local level="$3" if [ "$OUTPUT_MODE" = "html" ]; then # Saída em modo HTML echo "
" echo "
@${acct}
" echo "
${content}
" echo "
" else # Saída para o Terminal local indent="" for ((i=0; i/dev/null) if [ -n "$replies_json" ]; then echo "$replies_json" | while read -r reply; do [ -z "$reply" ] && continue local r_id=$(echo "$reply" | jq -r '.id') local r_acct=$(echo "$reply" | jq -r '.account.acct') local r_content=$(echo "$reply" | jq -r '.content') print_post "$r_acct" "$r_content" "$level" render_replies "$r_id" $((level + 1)) done fi } # main() ---- MAIN_AUTHOR=$(echo "$TARGET_JSON" | jq -r '.account.acct') MAIN_CONTENT=$(echo "$TARGET_JSON" | jq -r '.content') if [ "$OUTPUT_MODE" = "html" ]; then arqhtml="thread-$RANDOM.html" { # gera o cabeçalho HTML + CSS cat < Thread encadeada do Mastodon

Thread encadeada do Mastodon

EOF # Processa a thread echo "

Thread de $DOMAIN, status $STATUS_ID

" print_post "$MAIN_AUTHOR" "$MAIN_CONTENT" 0 render_replies "$STATUS_ID" 1 # Encerra o documento HTML cat < EOF } > $arqhtml echo -e "A saída formatada em HTML foi gravada no arquivo:\n$arqhtml" >&2 else # Gera a saída básica no terminal echo "🧶 Thread de $DOMAIN, status $STATUS_ID" >&2 echo -e "----------------------------------------------------------------------\n" >&2 print_post "$MAIN_AUTHOR" "$MAIN_CONTENT" 0 render_replies "$STATUS_ID" 1 fi # Cleanup context rm -f "$CONTEXT_FILE"