Something went wrong. Try again.
bluesky viewer in the terminal
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232package export
import ( "encoding/csv" "encoding/json" "fmt" "os" "strings" "time"
"github.com/stormlightlabs/skypanel/cli/internal/store")
// ExportPost represents a post structure for export operationstype ExportPost struct { ID string `json:"id"` URI string `json:"uri"` AuthorDID string `json:"author_did"` Text string `json:"text"` FeedID string `json:"feed_id"` IndexedAt time.Time `json:"indexed_at"` CreatedAt time.Time `json:"created_at"`}
// ToJSON exports posts to JSON format with pretty printingfunc ToJSON(filename string, posts []*store.PostModel) error { file, err := os.Create(filename) if err != nil { return fmt.Errorf("failed to create file: %w", err) } defer file.Close()
encoder := json.NewEncoder(file) encoder.SetIndent("", " ")
exportPosts := convertPosts(posts) if err := encoder.Encode(exportPosts); err != nil { return fmt.Errorf("failed to encode JSON: %w", err) }
return nil}
// ToCSV exports posts to CSV format with headersfunc ToCSV(filename string, posts []*store.PostModel) error { file, err := os.Create(filename) if err != nil { return fmt.Errorf("failed to create file: %w", err) } defer file.Close()
writer := csv.NewWriter(file) defer writer.Flush()
// Write header if err := writer.Write([]string{"ID", "URI", "AuthorDID", "Text", "FeedID", "IndexedAt", "CreatedAt"}); err != nil { return fmt.Errorf("failed to write CSV header: %w", err) }
// Write rows for _, post := range posts { record := []string{ post.ID(), post.URI, post.AuthorDID, post.Text, post.FeedID, post.IndexedAt.Format(time.RFC3339), post.CreatedAt().Format(time.RFC3339), } if err := writer.Write(record); err != nil { return fmt.Errorf("failed to write CSV record: %w", err) } }
return nil}
// ToTXT exports posts to plain text format with readable formattingfunc ToTXT(filename string, posts []*store.PostModel) error { file, err := os.Create(filename) if err != nil { return fmt.Errorf("failed to create file: %w", err) } defer file.Close()
for i, post := range posts { fmt.Fprintf(file, "Post #%d\n", i+1) fmt.Fprintf(file, "ID: %s\n", post.ID()) fmt.Fprintf(file, "URI: %s\n", post.URI) fmt.Fprintf(file, "Author DID: %s\n", post.AuthorDID) fmt.Fprintf(file, "Feed ID: %s\n", post.FeedID) fmt.Fprintf(file, "Indexed At: %s\n", post.IndexedAt.Format(time.RFC3339)) fmt.Fprintf(file, "Created At: %s\n", post.CreatedAt().Format(time.RFC3339)) fmt.Fprintf(file, "\nText:\n%s\n", post.Text) fmt.Fprintf(file, "\n%s\n\n", strings.Repeat("-", 80)) }
return nil}
// convertPosts transforms PostModel slice to ExportPost slicefunc convertPosts(posts []*store.PostModel) []ExportPost { exportPosts := make([]ExportPost, len(posts)) for i, post := range posts { exportPosts[i] = ExportPost{ ID: post.ID(), URI: post.URI, AuthorDID: post.AuthorDID, Text: post.Text, FeedID: post.FeedID, IndexedAt: post.IndexedAt, CreatedAt: post.CreatedAt(), } } return exportPosts}
// ProfileToJSON exports an ActorProfile to JSON formatfunc ProfileToJSON(filename string, profile *store.ActorProfile) error { file, err := os.Create(filename) if err != nil { return fmt.Errorf("failed to create file: %w", err) } defer file.Close()
encoder := json.NewEncoder(file) encoder.SetIndent("", " ")
if err := encoder.Encode(profile); err != nil { return fmt.Errorf("failed to encode JSON: %w", err) }
return nil}
// ProfileToTXT exports an ActorProfile to plain text formatfunc ProfileToTXT(filename string, profile *store.ActorProfile) error { file, err := os.Create(filename) if err != nil { return fmt.Errorf("failed to create file: %w", err) } defer file.Close()
fmt.Fprintf(file, "Profile: @%s\n", profile.Handle) fmt.Fprintf(file, "%s\n\n", strings.Repeat("=", 80))
if profile.DisplayName != "" { fmt.Fprintf(file, "Display Name: %s\n", profile.DisplayName) } fmt.Fprintf(file, "DID: %s\n", profile.Did) fmt.Fprintf(file, "Handle: @%s\n", profile.Handle)
if profile.Description != "" { fmt.Fprintf(file, "\nDescription:\n%s\n", profile.Description) }
fmt.Fprintf(file, "\nStats:\n") fmt.Fprintf(file, " Followers: %d\n", profile.FollowersCount) fmt.Fprintf(file, " Following: %d\n", profile.FollowsCount) fmt.Fprintf(file, " Posts: %d\n", profile.PostsCount)
if profile.CreatedAt != "" { fmt.Fprintf(file, "\nCreated: %s\n", profile.CreatedAt) }
return nil}
// FeedViewPostToJSON exports a single FeedViewPost to JSON formatfunc FeedViewPostToJSON(filename string, post *store.FeedViewPost) error { file, err := os.Create(filename) if err != nil { return fmt.Errorf("failed to create file: %w", err) } defer file.Close()
encoder := json.NewEncoder(file) encoder.SetIndent("", " ")
if err := encoder.Encode(post); err != nil { return fmt.Errorf("failed to encode JSON: %w", err) }
return nil}
// FeedViewPostToTXT exports a single FeedViewPost to plain text formatfunc FeedViewPostToTXT(filename string, post *store.FeedViewPost) error { file, err := os.Create(filename) if err != nil { return fmt.Errorf("failed to create file: %w", err) } defer file.Close()
if post.Post != nil { p := post.Post fmt.Fprintf(file, "Post by @%s\n", p.Author.Handle) fmt.Fprintf(file, "%s\n\n", strings.Repeat("=", 80))
fmt.Fprintf(file, "URI: %s\n", p.Uri) fmt.Fprintf(file, "CID: %s\n", p.Cid)
if p.Author.DisplayName != "" { fmt.Fprintf(file, "Author: %s (@%s)\n", p.Author.DisplayName, p.Author.Handle) } else { fmt.Fprintf(file, "Author: @%s\n", p.Author.Handle) }
// Extract text from record if recordMap, ok := p.Record.(map[string]any); ok { if text, ok := recordMap["text"].(string); ok { fmt.Fprintf(file, "\nText:\n%s\n", text) } }
fmt.Fprintf(file, "\nEngagement:\n") fmt.Fprintf(file, " Likes: %d\n", p.LikeCount) fmt.Fprintf(file, " Reposts: %d\n", p.RepostCount) fmt.Fprintf(file, " Replies: %d\n", p.ReplyCount) fmt.Fprintf(file, " Quotes: %d\n", p.QuoteCount)
fmt.Fprintf(file, "\nIndexed: %s\n", p.IndexedAt)
if post.Reason != nil && post.Reason.By != nil { fmt.Fprintf(file, "\nReposted by: @%s\n", post.Reason.By.Handle) } }
return nil}