mirror of https://github.com/muesli/mastotool
Initial import
This commit is contained in:
parent
3a7dcf6be8
commit
e5504270b2
77
README.md
77
README.md
|
@ -1,2 +1,77 @@
|
||||||
# statootstics
|
statootstics
|
||||||
|
============
|
||||||
|
|
||||||
Mastodon Statistics Generator
|
Mastodon Statistics Generator
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Make sure you have a working Go environment (Go 1.7 or higher is required).
|
||||||
|
See the [install instructions](http://golang.org/doc/install.html).
|
||||||
|
|
||||||
|
To install statootstics, simply run:
|
||||||
|
|
||||||
|
go get github.com/muesli/statootstics
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
$ statootstics -help
|
||||||
|
Usage of ./statootstics:
|
||||||
|
-columns int
|
||||||
|
displays tables with N columns (default 80)
|
||||||
|
-config string
|
||||||
|
uses the specified config file (default "mastodon.json")
|
||||||
|
-recent int
|
||||||
|
only account for the N most recent toots (excl replies & boosts)
|
||||||
|
-top int
|
||||||
|
shows the top N items in each category (default 10)
|
||||||
|
|
||||||
|
$ statootstics
|
||||||
|
Loading toots for some_user 100 of 100 [#>---------------------------] 100.00%
|
||||||
|
|
||||||
|
Total toots: 100 (excluding replies & boosts)
|
||||||
|
Toots per day: 1.00 (account created 100 days ago)
|
||||||
|
Ratio toots/replies: 0.33
|
||||||
|
New followers per day: 7.41
|
||||||
|
New followings per day: 3.67
|
||||||
|
Likes per toot: 9.00 (total likes: 900)
|
||||||
|
Boosts per toot: 2.50 (total boosts: 250)
|
||||||
|
|
||||||
|
Users you mentioned most Interactions
|
||||||
|
----------------------------------------------------------------------------------
|
||||||
|
abc 3
|
||||||
|
|
||||||
|
Users you boosted most Interactions
|
||||||
|
----------------------------------------------------------------------------------
|
||||||
|
xyz 7
|
||||||
|
|
||||||
|
Most replied-to toots Replies
|
||||||
|
----------------------------------------------------------------------------------
|
||||||
|
Some toot 20
|
||||||
|
|
||||||
|
Most liked toots Likes
|
||||||
|
----------------------------------------------------------------------------------
|
||||||
|
Some toot 50
|
||||||
|
|
||||||
|
Most boosted toots Boosts
|
||||||
|
----------------------------------------------------------------------------------
|
||||||
|
Some toot 10
|
||||||
|
|
||||||
|
Highest scoring toots Score
|
||||||
|
----------------------------------------------------------------------------------
|
||||||
|
Some toot 80
|
||||||
|
|
||||||
|
Tags used that got the most likes Likes
|
||||||
|
----------------------------------------------------------------------------------
|
||||||
|
Some tag 10
|
||||||
|
|
||||||
|
Tags used that got the most boosts Boosts
|
||||||
|
----------------------------------------------------------------------------------
|
||||||
|
Some tag 5
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](https://godoc.org/github.com/mueslistatootstics)
|
||||||
|
[![Build Status](https://travis-ci.org/muesli/statootstics.svg?branch=master)](https://travis-ci.org/muesli/statootstics)
|
||||||
|
[![Go ReportCard](http://goreportcard.com/badge/muesli/statootstics)](http://goreportcard.com/report/muesli/statootstics)
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Option struct {
|
||||||
|
Name string
|
||||||
|
Value interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Options []Option
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadConfig(filename string) (Config, error) {
|
||||||
|
config := Config{}
|
||||||
|
|
||||||
|
j, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return config, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(j, &config)
|
||||||
|
return config, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Config) Save(filename string) error {
|
||||||
|
j, err := json.MarshalIndent(c, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ioutil.WriteFile(filename, j, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Config) Value(name string) interface{} {
|
||||||
|
for _, v := range c.Options {
|
||||||
|
if v.Name == name {
|
||||||
|
return v.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) Set(name, value string) interface{} {
|
||||||
|
found := false
|
||||||
|
var opts []Option
|
||||||
|
for _, v := range c.Options {
|
||||||
|
if v.Name == name {
|
||||||
|
v.Value = value
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
|
||||||
|
opts = append(opts, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
opts = append(opts, Option{name, value})
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Options = opts
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,198 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
mastodon "github.com/mattn/go-mastodon"
|
||||||
|
"github.com/muesli/goprogressbar"
|
||||||
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
client *mastodon.Client
|
||||||
|
|
||||||
|
topN = flag.Int("top", 10, "shows the top N items in each category")
|
||||||
|
maxToots = flag.Int("recent", 0, "only account for the N most recent toots (excl replies & boosts)")
|
||||||
|
columns = flag.Int("columns", 80, "displays tables with N columns")
|
||||||
|
configFile = flag.String("config", "mastodon.json", "uses the specified config file")
|
||||||
|
// user = flag.String("user", "@fribbledom@mastodon.social", "shows stats for this user")
|
||||||
|
)
|
||||||
|
|
||||||
|
func readPassword(prompt string) (string, error) {
|
||||||
|
var tty io.WriteCloser
|
||||||
|
tty, err := os.OpenFile("/dev/tty", os.O_WRONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
tty = os.Stdout
|
||||||
|
} else {
|
||||||
|
defer tty.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprint(tty, prompt+" ")
|
||||||
|
buf, err := terminal.ReadPassword(int(syscall.Stdin))
|
||||||
|
fmt.Fprintln(tty)
|
||||||
|
|
||||||
|
return string(buf), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerApp(config *Config) error {
|
||||||
|
app, err := mastodon.RegisterApp(context.Background(), &mastodon.AppConfig{
|
||||||
|
Server: config.Value("instance").(string),
|
||||||
|
ClientName: "statootstics",
|
||||||
|
Scopes: "read write follow",
|
||||||
|
Website: "",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
config.Set("id", app.ClientID)
|
||||||
|
config.Set("secret", app.ClientSecret)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func initClient() {
|
||||||
|
var err error
|
||||||
|
var instance, username, password, id, secret string
|
||||||
|
config, err := LoadConfig(*configFile)
|
||||||
|
if err == nil {
|
||||||
|
instance = config.Value("instance").(string)
|
||||||
|
username = config.Value("username").(string)
|
||||||
|
secret = config.Value("secret").(string)
|
||||||
|
id = config.Value("id").(string)
|
||||||
|
if config.Value("password") != nil {
|
||||||
|
password = config.Value("password").(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
|
if len(instance) == 0 {
|
||||||
|
fmt.Print("Which instance to connect to (e.g. https://mastodon.social): ")
|
||||||
|
scanner.Scan()
|
||||||
|
if scanner.Err() != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
instance = scanner.Text()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(username) == 0 {
|
||||||
|
fmt.Print("Username (email): ")
|
||||||
|
scanner.Scan()
|
||||||
|
if scanner.Err() != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
username = scanner.Text()
|
||||||
|
}
|
||||||
|
|
||||||
|
config.Set("instance", instance)
|
||||||
|
config.Set("username", username)
|
||||||
|
|
||||||
|
if len(id) == 0 {
|
||||||
|
err = registerApp(&config)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
id = config.Value("id").(string)
|
||||||
|
secret = config.Value("secret").(string)
|
||||||
|
}
|
||||||
|
config.Save(*configFile)
|
||||||
|
|
||||||
|
if len(password) == 0 {
|
||||||
|
password, err = readPassword("Password:")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client = mastodon.NewClient(&mastodon.Config{
|
||||||
|
Server: instance,
|
||||||
|
ClientID: id,
|
||||||
|
ClientSecret: secret,
|
||||||
|
})
|
||||||
|
err = client.Authenticate(context.Background(), username, password)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
initClient()
|
||||||
|
self, err := client.GetAccountCurrentUser(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
accounts, err := client.AccountsSearch(context.Background(), *user, 1)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
self := accounts[0]
|
||||||
|
*/
|
||||||
|
|
||||||
|
stats := &stats{
|
||||||
|
DaysActive: int(time.Since(self.CreatedAt).Hours() / 24),
|
||||||
|
Followers: self.FollowersCount,
|
||||||
|
Following: self.FollowingCount,
|
||||||
|
Toots: make(map[string]*tootStat),
|
||||||
|
Tags: make(map[string]*tootStat),
|
||||||
|
Replies: make(map[string]*tootStat),
|
||||||
|
Mentions: make(map[string]int64),
|
||||||
|
Boosts: make(map[string]int64),
|
||||||
|
}
|
||||||
|
pb := &goprogressbar.ProgressBar{
|
||||||
|
Text: fmt.Sprintf("Loading toots for %s", self.Username),
|
||||||
|
Total: self.StatusesCount,
|
||||||
|
PrependTextFunc: func(p *goprogressbar.ProgressBar) string {
|
||||||
|
return fmt.Sprintf("%d of %d", p.Current, int64(math.Max(float64(p.Current), float64(self.StatusesCount))))
|
||||||
|
},
|
||||||
|
Current: 0,
|
||||||
|
Width: 40,
|
||||||
|
}
|
||||||
|
|
||||||
|
var pg mastodon.Pagination
|
||||||
|
for {
|
||||||
|
pg.Limit = 40
|
||||||
|
statuses, err := client.GetAccountStatuses(context.Background(), self.ID, &pg)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range statuses {
|
||||||
|
err = parseToot(s, stats)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pb.Current += 1
|
||||||
|
pb.LazyPrint()
|
||||||
|
|
||||||
|
if *maxToots > 0 && len(stats.Toots) >= *maxToots {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if *maxToots > 0 && len(stats.Toots) >= *maxToots {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if pg.MaxID == "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(1000 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\n\n")
|
||||||
|
printAccountStats(stats)
|
||||||
|
printInteractionStats(stats)
|
||||||
|
printTootStats(stats)
|
||||||
|
printTagStats(stats)
|
||||||
|
}
|
|
@ -0,0 +1,295 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"html"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
mastodon "github.com/mattn/go-mastodon"
|
||||||
|
"github.com/microcosm-cc/bluemonday"
|
||||||
|
"github.com/muesli/gotable"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
stripper = bluemonday.StrictPolicy()
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SortByLikes = iota
|
||||||
|
SortByBoosts
|
||||||
|
SortByScore
|
||||||
|
SortByReplies
|
||||||
|
)
|
||||||
|
|
||||||
|
type tootStat struct {
|
||||||
|
Likes int64
|
||||||
|
Boosts int64
|
||||||
|
Replies int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type stats struct {
|
||||||
|
DaysActive int
|
||||||
|
Followers int64
|
||||||
|
Following int64
|
||||||
|
Toots map[string]*tootStat
|
||||||
|
Tags map[string]*tootStat
|
||||||
|
Replies map[string]*tootStat
|
||||||
|
Mentions map[string]int64
|
||||||
|
Boosts map[string]int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseToot(status *mastodon.Status, stats *stats) error {
|
||||||
|
// handle mentions
|
||||||
|
for _, m := range status.Mentions {
|
||||||
|
stats.Mentions[m.Acct]++
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle boosts
|
||||||
|
if status.Reblog != nil {
|
||||||
|
stats.Boosts[status.Reblog.Account.Acct]++
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var replies int64
|
||||||
|
|
||||||
|
// parse tags
|
||||||
|
if status.InReplyToID == nil {
|
||||||
|
contexts, err := client.GetStatusContext(context.Background(), status.ID)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
replies = int64(len(contexts.Descendants))
|
||||||
|
|
||||||
|
for _, t := range status.Tags {
|
||||||
|
tag := strings.ToLower(t.Name)
|
||||||
|
|
||||||
|
stat, ok := stats.Tags[tag]
|
||||||
|
if ok {
|
||||||
|
stat.Likes += status.FavouritesCount
|
||||||
|
stat.Boosts += status.ReblogsCount
|
||||||
|
stat.Replies += replies
|
||||||
|
} else {
|
||||||
|
stat = &tootStat{
|
||||||
|
Likes: status.FavouritesCount,
|
||||||
|
Boosts: status.ReblogsCount,
|
||||||
|
Replies: replies,
|
||||||
|
}
|
||||||
|
|
||||||
|
stats.Tags[tag] = stat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// clean up toot for terminal output
|
||||||
|
content := strings.Replace(status.Content, "<br>", "\n", -1)
|
||||||
|
content = strings.Replace(content, "<p>", "\n", -1)
|
||||||
|
content = strings.Replace(content, "</p>", "", -1)
|
||||||
|
content = html.UnescapeString(stripper.Sanitize(content))
|
||||||
|
content = strings.TrimSpace(strings.Replace(content, "\n", " ", -1))
|
||||||
|
|
||||||
|
// handle replies
|
||||||
|
if status.InReplyToID != nil {
|
||||||
|
stats.Replies[content] = &tootStat{
|
||||||
|
Likes: status.FavouritesCount,
|
||||||
|
Boosts: status.ReblogsCount,
|
||||||
|
Replies: replies,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stats.Toots[content] = &tootStat{
|
||||||
|
Likes: status.FavouritesCount,
|
||||||
|
Boosts: status.ReblogsCount,
|
||||||
|
Replies: replies,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatSorter struct {
|
||||||
|
SortKey int
|
||||||
|
Key []string
|
||||||
|
Stats []*tootStat
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a StatSorter) Len() int {
|
||||||
|
return len(a.Stats)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a StatSorter) Swap(i, j int) {
|
||||||
|
a.Key[i], a.Key[j] = a.Key[j], a.Key[i]
|
||||||
|
a.Stats[i], a.Stats[j] = a.Stats[j], a.Stats[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a StatSorter) Less(i, j int) bool {
|
||||||
|
switch a.SortKey {
|
||||||
|
case SortByReplies:
|
||||||
|
return a.Stats[i].Replies < a.Stats[j].Replies
|
||||||
|
case SortByLikes:
|
||||||
|
return a.Stats[i].Likes < a.Stats[j].Likes
|
||||||
|
case SortByBoosts:
|
||||||
|
return a.Stats[i].Boosts < a.Stats[j].Boosts
|
||||||
|
case SortByScore:
|
||||||
|
return (a.Stats[i].Boosts*3)+a.Stats[i].Likes <
|
||||||
|
(a.Stats[j].Boosts*3)+a.Stats[j].Likes
|
||||||
|
default:
|
||||||
|
panic("SortKey is incorrect")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type kv struct {
|
||||||
|
Key string
|
||||||
|
Value int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func printTable(cols []string, emptyText string, data []kv) {
|
||||||
|
sort.Slice(data, func(i, j int) bool {
|
||||||
|
return data[i].Value > data[j].Value
|
||||||
|
})
|
||||||
|
|
||||||
|
col1 := *columns - len(cols[1])
|
||||||
|
col2 := len(cols[1])
|
||||||
|
tab := gotable.NewTable(cols,
|
||||||
|
[]int64{-int64(col1), int64(col2)},
|
||||||
|
emptyText)
|
||||||
|
|
||||||
|
for i, kv := range data {
|
||||||
|
if i >= *topN {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if len(kv.Key) > col1-4 {
|
||||||
|
kv.Key = kv.Key[:col1-4] + "..."
|
||||||
|
}
|
||||||
|
|
||||||
|
tab.AppendRow([]interface{}{kv.Key, strconv.FormatInt(int64(kv.Value), 10)})
|
||||||
|
}
|
||||||
|
tab.Print()
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
func printTootTable(cols []string, emptyText string, toots []string, tootStats []*tootStat, sortKey int) {
|
||||||
|
sort.Sort(sort.Reverse(StatSorter{sortKey, toots, tootStats}))
|
||||||
|
|
||||||
|
var ss []kv
|
||||||
|
for k, v := range toots {
|
||||||
|
switch sortKey {
|
||||||
|
case SortByReplies:
|
||||||
|
if tootStats[k].Replies == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ss = append(ss, kv{v, tootStats[k].Replies})
|
||||||
|
case SortByLikes:
|
||||||
|
if tootStats[k].Likes == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ss = append(ss, kv{v, tootStats[k].Likes})
|
||||||
|
case SortByBoosts:
|
||||||
|
if tootStats[k].Boosts == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ss = append(ss, kv{v, tootStats[k].Boosts})
|
||||||
|
case SortByScore:
|
||||||
|
score := (tootStats[k].Boosts * 3) + tootStats[k].Likes
|
||||||
|
if score == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ss = append(ss, kv{v, score})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printTable(cols, emptyText, ss)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printAccountStats(stats *stats) {
|
||||||
|
var likes, boosts int64
|
||||||
|
for _, t := range stats.Toots {
|
||||||
|
likes += t.Likes
|
||||||
|
boosts += t.Boosts
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Total toots: %d (excluding replies & boosts)\n", len(stats.Toots))
|
||||||
|
fmt.Printf("Toots per day: %.2f (account created %d days ago)\n",
|
||||||
|
float64(len(stats.Toots))/float64(stats.DaysActive),
|
||||||
|
stats.DaysActive)
|
||||||
|
fmt.Printf("Ratio toots/replies: %.2f\n",
|
||||||
|
float64(len(stats.Toots))/float64(len(stats.Replies)))
|
||||||
|
fmt.Printf("New followers per day: %.2f\n",
|
||||||
|
float64(stats.Followers)/float64(stats.DaysActive))
|
||||||
|
fmt.Printf("New followings per day: %.2f\n",
|
||||||
|
float64(stats.Following)/float64(stats.DaysActive))
|
||||||
|
fmt.Printf("Likes per toot: %.2f (total likes: %d)\n",
|
||||||
|
float64(likes)/float64(len(stats.Toots)),
|
||||||
|
likes)
|
||||||
|
fmt.Printf("Boosts per toot: %.2f (total boosts: %d)\n",
|
||||||
|
float64(boosts)/float64(len(stats.Toots)),
|
||||||
|
boosts)
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
func printInteractionStats(stats *stats) {
|
||||||
|
var ss []kv
|
||||||
|
for k, v := range stats.Mentions {
|
||||||
|
ss = append(ss, kv{k, v})
|
||||||
|
}
|
||||||
|
printTable([]string{"Users you mentioned most", "Interactions"},
|
||||||
|
"No interactions found.",
|
||||||
|
ss)
|
||||||
|
|
||||||
|
ss = []kv{}
|
||||||
|
for k, v := range stats.Boosts {
|
||||||
|
ss = append(ss, kv{k, v})
|
||||||
|
}
|
||||||
|
printTable([]string{"Users you boosted most", "Interactions"},
|
||||||
|
"No interactions found.",
|
||||||
|
ss)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printTootStats(stats *stats) {
|
||||||
|
var toots []string
|
||||||
|
var tootStats []*tootStat
|
||||||
|
for toot, s := range stats.Toots {
|
||||||
|
toots = append(toots, toot)
|
||||||
|
tootStats = append(tootStats, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// most replied-to toots
|
||||||
|
printTootTable([]string{"Most replied-to toots", "Replies"},
|
||||||
|
"No toots found.",
|
||||||
|
toots, tootStats, SortByReplies)
|
||||||
|
|
||||||
|
// most liked toots
|
||||||
|
printTootTable([]string{"Most liked toots", "Likes"},
|
||||||
|
"No toots found.",
|
||||||
|
toots, tootStats, SortByLikes)
|
||||||
|
|
||||||
|
// most boosted toots
|
||||||
|
printTootTable([]string{"Most boosted toots", "Boosts"},
|
||||||
|
"No toots found.",
|
||||||
|
toots, tootStats, SortByBoosts)
|
||||||
|
|
||||||
|
// highest scoring toots
|
||||||
|
printTootTable([]string{"Highest scoring toots", "Score"},
|
||||||
|
"No toots found.",
|
||||||
|
toots, tootStats, SortByScore)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printTagStats(stats *stats) {
|
||||||
|
var tags []string
|
||||||
|
var tagStats []*tootStat
|
||||||
|
for tag, s := range stats.Tags {
|
||||||
|
tags = append(tags, tag)
|
||||||
|
tagStats = append(tagStats, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// most liked tags
|
||||||
|
printTootTable([]string{"Tags used that got the most likes", "Likes"},
|
||||||
|
"No toots found.",
|
||||||
|
tags, tagStats, SortByLikes)
|
||||||
|
|
||||||
|
// most boosted tags
|
||||||
|
printTootTable([]string{"Tags used that got the most boosts", "Boosts"},
|
||||||
|
"No toots found.",
|
||||||
|
tags, tagStats, SortByBoosts)
|
||||||
|
}
|
Loading…
Reference in New Issue