mastotool/main.go

253 lines
6.2 KiB
Go
Raw Normal View History

2018-11-22 04:35:51 +01:00
package main
import (
"bufio"
"context"
"flag"
"fmt"
"math"
"os"
"strings"
2018-11-22 04:35:51 +01:00
"time"
mastodon "github.com/mattn/go-mastodon"
"github.com/muesli/goprogressbar"
)
var (
client *mastodon.Client
2018-11-22 06:48:21 +01:00
self *mastodon.Account
2018-11-22 04:35:51 +01:00
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")
search = flag.String("search", "", "searches toots containing string")
2018-11-22 04:35:51 +01:00
// user = flag.String("user", "@fribbledom@mastodon.social", "shows stats for this user")
)
2018-11-22 22:25:08 +01:00
func registerApp(config *Config) (string, error) {
2018-11-22 04:35:51 +01:00
app, err := mastodon.RegisterApp(context.Background(), &mastodon.AppConfig{
Server: config.Value("instance").(string),
2019-08-11 04:12:37 +02:00
ClientName: "mastotool",
2018-11-22 22:25:08 +01:00
Scopes: "read",
2018-11-22 04:35:51 +01:00
Website: "",
})
if err != nil {
2018-11-22 22:25:08 +01:00
return "", err
2018-11-22 04:35:51 +01:00
}
config.Set("id", app.ClientID)
config.Set("secret", app.ClientSecret)
2018-11-22 22:25:08 +01:00
config.Set("redirectURI", app.RedirectURI)
return app.AuthURI, nil
2018-11-22 04:35:51 +01:00
}
func initClient() error {
2018-11-22 04:35:51 +01:00
var err error
2018-11-22 22:25:08 +01:00
var instance, token, redirectURI, authURI, id, secret string
2018-11-22 04:35:51 +01:00
config, err := LoadConfig(*configFile)
if err == nil {
instance = config.Value("instance").(string)
id = config.Value("id").(string)
2018-11-22 22:25:08 +01:00
secret = config.Value("secret").(string)
token = config.Value("token").(string)
redirectURI = config.Value("redirectURI").(string)
2018-11-22 04:35:51 +01:00
}
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 {
return fmt.Errorf("Can't open input: %s", err)
2018-11-22 04:35:51 +01:00
}
instance = scanner.Text()
2018-11-22 22:25:08 +01:00
config.Set("instance", instance)
2018-11-22 04:35:51 +01:00
}
if len(id) == 0 {
2018-11-22 22:25:08 +01:00
authURI, err = registerApp(&config)
2018-11-22 04:35:51 +01:00
if err != nil {
return fmt.Errorf("Can't register app: %s", err)
2018-11-22 04:35:51 +01:00
}
id = config.Value("id").(string)
secret = config.Value("secret").(string)
2018-11-22 22:25:08 +01:00
redirectURI = config.Value("redirectURI").(string)
2018-11-22 04:35:51 +01:00
}
2018-11-22 22:25:08 +01:00
mConfig := &mastodon.Config{
AccessToken: token,
Server: instance,
ClientID: id,
ClientSecret: secret,
}
client = mastodon.NewClient(mConfig)
if len(mConfig.AccessToken) == 0 {
fmt.Printf("Please visit %s and enter the generated token: ", authURI)
scanner.Scan()
if scanner.Err() != nil {
return fmt.Errorf("Can't open input: %s", err)
2018-11-22 22:25:08 +01:00
}
code := scanner.Text()
err = client.AuthenticateToken(context.Background(), code, redirectURI)
2018-11-22 04:35:51 +01:00
if err != nil {
return fmt.Errorf("Can't retrieve authentication token: %s", err)
2018-11-22 04:35:51 +01:00
}
2018-11-22 22:25:08 +01:00
config.Set("token", mConfig.AccessToken)
err = config.Save(*configFile)
if err != nil {
return fmt.Errorf("Can't save config: %s", err)
}
2018-11-22 04:35:51 +01:00
}
return nil
}
2018-11-22 06:48:21 +01:00
func searchToots() error {
pb := &goprogressbar.ProgressBar{
Text: fmt.Sprintf("Searching toots for %s", *search),
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,
2018-11-22 04:35:51 +01:00
}
var pg mastodon.Pagination
for {
pg.SinceID = ""
pg.MinID = ""
pg.Limit = 40
statuses, err := client.GetAccountStatuses(context.Background(), self.ID, &pg)
2018-11-22 04:35:51 +01:00
if err != nil {
return fmt.Errorf("Can't retrieve statuses: %s", err)
2018-11-22 04:35:51 +01:00
}
abort := false
for _, s := range statuses {
if strings.Contains(strings.ToLower(cleanupContent(s.Content)), *search) {
fmt.Println("\nFound toot:", cleanupContent(s.Content))
fmt.Println()
}
pb.Current += 1
pb.LazyPrint()
}
// For some reason, either because it's Pleroma or because I have too few toots,
// `pg.MaxID` never equals `""` and we get stuck looping forever. Add a simple
// break condition on "no statuses fetched" to avoid the issue.
if abort || pg.MaxID == "" || len(statuses) == 0 {
break
}
}
return nil
}
func gatherStats() error {
2018-11-22 04:35:51 +01:00
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),
2018-11-22 06:48:21 +01:00
Responses: make(map[string]int64),
2018-11-22 04:35:51 +01:00
}
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.SinceID = ""
pg.MinID = ""
2018-11-22 04:35:51 +01:00
pg.Limit = 40
statuses, err := client.GetAccountStatuses(context.Background(), self.ID, &pg)
if err != nil {
return fmt.Errorf("Can't retrieve statuses: %s", err)
2018-11-22 04:35:51 +01:00
}
abort := false
2018-11-22 04:35:51 +01:00
for _, s := range statuses {
err = parseToot(s, stats)
if err != nil {
return fmt.Errorf("Can't parse toot: %s", err)
2018-11-22 04:35:51 +01:00
}
pb.Current += 1
pb.LazyPrint()
if *maxToots > 0 && len(stats.Toots) >= *maxToots {
abort = true
2018-11-22 04:35:51 +01:00
break
}
}
// For some reason, either because it's Pleroma or because I have too few toots,
// `pg.MaxID` never equals `""` and we get stuck looping forever. Add a simple
// break condition on "no statuses fetched" to avoid the issue.
if abort || pg.MaxID == "" || len(statuses) == 0 {
2018-11-22 04:35:51 +01:00
break
}
}
// print out stats we gathered
2018-11-22 04:35:51 +01:00
fmt.Printf("\n\n")
printAccountStats(stats)
printInteractionStats(stats)
printTootStats(stats)
printTagStats(stats)
return nil
}
func main() {
flag.Parse()
*search = strings.ToLower(*search)
if err := initClient(); err != nil {
fmt.Println(err)
os.Exit(1)
}
var err error
self, err = client.GetAccountCurrentUser(context.Background())
if err != nil {
fmt.Printf("Can't retrieve user: %s\n", err)
os.Exit(1)
}
/*
accounts, err := client.AccountsSearch(context.Background(), *user, 1)
if err != nil {
panic(err)
}
self = accounts[0]
*/
if len(*search) > 0 {
err = searchToots()
} else {
err = gatherStats()
}
if err != nil {
fmt.Println(err)
os.Exit(1)
}
2018-11-22 04:35:51 +01:00
}