From 968a7e3d82a6001f89504522c33b38691521dcd9 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sun, 11 Aug 2019 21:46:42 +0200 Subject: [PATCH] Use cobra to parse & split functionality up into individual commands --- README.md | 45 ++++++++++++---- main.go | 158 ++++++------------------------------------------------ search.go | 72 +++++++++++++++++++++++++ stats.go | 95 +++++++++++++++++++++++++++++++- 4 files changed, 216 insertions(+), 154 deletions(-) create mode 100644 search.go diff --git a/README.md b/README.md index dca9925..85f6729 100644 --- a/README.md +++ b/README.md @@ -15,22 +15,45 @@ To install mastotool, simply run: ## Usage ``` -$ mastotool -help -Usage of ./mastotool: - -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) +Usage: + mastotool [command] + +Available Commands: + help Help about any command + search searches your toots + stats generates statistics about your account + +Flags: + -c, --config string uses the specified config file (default "mastodon.json") + -h, --help help for mastotool + +Use "mastotool [command] --help" for more information about a command. +``` + +### Generate Statistics + +``` +Usage: + mastotool stats [flags] + +Flags: + --columns int displays tables with N columns (default 80) + -h, --help help for stats + -r, --recent int only account for the N most recent toots (excl replies & boosts) + -t, --top int shows the top N items in each category (default 10) +``` + +### Search + +``` +Usage: + mastotool search ``` ## Example ``` -$ mastotool +$ mastotool stats Which instance to connect to: https://mastodon.social Username (email): some_user@domain.tld Password: ******** diff --git a/main.go b/main.go index 5bc1330..481f233 100644 --- a/main.go +++ b/main.go @@ -3,27 +3,25 @@ package main import ( "bufio" "context" - "flag" "fmt" - "math" "os" - "strings" - "time" mastodon "github.com/mattn/go-mastodon" - "github.com/muesli/goprogressbar" + "github.com/spf13/cobra" ) var ( - client *mastodon.Client - self *mastodon.Account + client *mastodon.Client + self *mastodon.Account + configFile string - 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") - // user = flag.String("user", "@fribbledom@mastodon.social", "shows stats for this user") + // RootCmd is the core command used for cli-arg parsing + RootCmd = &cobra.Command{ + Use: "mastotool", + Short: "mastotool offers a collection of tools to work with your Mastodon account", + SilenceErrors: true, + SilenceUsage: true, + } ) func registerApp(config *Config) (string, error) { @@ -47,7 +45,7 @@ func registerApp(config *Config) (string, error) { func initClient() error { var err error var instance, token, redirectURI, authURI, id, secret string - config, err := LoadConfig(*configFile) + config, err := LoadConfig(configFile) if err == nil { instance = config.Value("instance").(string) id = config.Value("id").(string) @@ -100,7 +98,7 @@ func initClient() error { } config.Set("token", mConfig.AccessToken) - err = config.Save(*configFile) + err = config.Save(configFile) if err != nil { return fmt.Errorf("Can't save config: %s", err) } @@ -109,144 +107,22 @@ func initClient() error { return nil } -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, - } - - var pg mastodon.Pagination - for { - pg.SinceID = "" - pg.MinID = "" - pg.Limit = 40 - statuses, err := client.GetAccountStatuses(context.Background(), self.ID, &pg) - if err != nil { - return fmt.Errorf("Can't retrieve statuses: %s", err) - } - - 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 { - 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), - Responses: 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.SinceID = "" - pg.MinID = "" - pg.Limit = 40 - statuses, err := client.GetAccountStatuses(context.Background(), self.ID, &pg) - if err != nil { - return fmt.Errorf("Can't retrieve statuses: %s", err) - } - - abort := false - for _, s := range statuses { - err = parseToot(s, stats) - if err != nil { - return fmt.Errorf("Can't parse toot: %s", err) - } - - pb.Current += 1 - pb.LazyPrint() - - if *maxToots > 0 && len(stats.Toots) >= *maxToots { - abort = true - 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 { - break - } - } - - // print out stats we gathered - fmt.Printf("\n\n") - printAccountStats(stats) - printInteractionStats(stats) - printTootStats(stats) - printTagStats(stats) - - return nil -} - func main() { - flag.Parse() - *search = strings.ToLower(*search) + RootCmd.PersistentFlags().StringVarP(&configFile, "config", "c", "mastodon.json", "uses the specified config file") + 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 { + if err := RootCmd.Execute(); err != nil { fmt.Println(err) - os.Exit(1) + os.Exit(-1) } } diff --git a/search.go b/search.go new file mode 100644 index 0000000..71505e0 --- /dev/null +++ b/search.go @@ -0,0 +1,72 @@ +package main + +import ( + "context" + "fmt" + "math" + "strings" + + mastodon "github.com/mattn/go-mastodon" + "github.com/muesli/goprogressbar" + "github.com/spf13/cobra" +) + +var ( + searchCmd = &cobra.Command{ + Use: "search ", + Short: "searches your toots", + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return fmt.Errorf("search requires a search token") + } + return search(args[0]) + }, + } +) + +func search(token string) error { + pb := &goprogressbar.ProgressBar{ + Text: fmt.Sprintf("Searching toots for %s", token), + 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 = "" + pg.Limit = 40 + statuses, err := client.GetAccountStatuses(context.Background(), self.ID, &pg) + if err != nil { + return fmt.Errorf("Can't retrieve statuses: %s", err) + } + + abort := false + for _, s := range statuses { + if strings.Contains(strings.ToLower(cleanupContent(s.Content)), token) { + 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 init() { + RootCmd.AddCommand(searchCmd) +} diff --git a/stats.go b/stats.go index 95af607..493c98e 100644 --- a/stats.go +++ b/stats.go @@ -4,16 +4,32 @@ import ( "context" "fmt" "html" + "math" "sort" "strconv" "strings" + "time" mastodon "github.com/mattn/go-mastodon" "github.com/microcosm-cc/bluemonday" + "github.com/muesli/goprogressbar" "github.com/muesli/gotable" + "github.com/spf13/cobra" ) var ( + topN int + maxToots int + columns int + + statsCmd = &cobra.Command{ + Use: "stats", + Short: "generates statistics about your account", + RunE: func(cmd *cobra.Command, args []string) error { + return gatherStats() + }, + } + stripper = bluemonday.StrictPolicy() ) @@ -42,6 +58,72 @@ type stats struct { Responses map[string]int64 } +func gatherStats() error { + 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), + Responses: 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.SinceID = "" + pg.MinID = "" + pg.Limit = 40 + statuses, err := client.GetAccountStatuses(context.Background(), self.ID, &pg) + if err != nil { + return fmt.Errorf("Can't retrieve statuses: %s", err) + } + + abort := false + for _, s := range statuses { + err = parseToot(s, stats) + if err != nil { + return fmt.Errorf("Can't parse toot: %s", err) + } + + pb.Current += 1 + pb.LazyPrint() + + if maxToots > 0 && len(stats.Toots) >= maxToots { + abort = true + 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 { + break + } + } + + // print out stats we gathered + fmt.Printf("\n\n") + printAccountStats(stats) + printInteractionStats(stats) + printTootStats(stats) + printTagStats(stats) + + return nil +} + func cleanupContent(content string) string { // clean up toot for terminal output content = strings.Replace(content, "
", "\n", -1) @@ -164,14 +246,14 @@ func printTable(cols []string, emptyText string, data []kv) { return data[i].Value > data[j].Value }) - col1 := *columns - len(cols[1]) + 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 { + if i >= topN { break } if len(kv.Key) > col1-4 { @@ -320,3 +402,12 @@ func printTagStats(stats *stats) { "No toots found.", tags, tagStats, SortByBoosts) } + +func init() { + statsCmd.Flags().IntVarP(&topN, "top", "t", 10, "shows the top N items in each category") + statsCmd.Flags().IntVarP(&maxToots, "recent", "r", 0, "only account for the N most recent toots (excl replies & boosts)") + statsCmd.Flags().IntVarP(&columns, "columns", "", 80, "displays tables with N columns") + // user = flag.String("user", "@fribbledom@mastodon.social", "shows stats for this user") + + RootCmd.AddCommand(statsCmd) +}