cmd/lists.go
author rjp <zimpenfish@gmail.com>
Mon, 23 Jan 2023 16:39:02 +0000
changeset 267 5b91a65ba95a
parent 239 605a00e9d1ab
child 268 4dd196a4ee7c
permissions -rw-r--r--
Update to handle non-int64 IDs Pleroma/Akkoma and GotoSocial use opaque IDs rather than `int64`s like Mastodon which means that `madon` can't talk to either of those. This commit updates everything that can be an ID to `madon.ActivityID` which is an alias for `string` - can't create a specific type for it since there's more than a few places where they're concatenated directly to strings for URLs, etc. Which means it could just as easily be a direct `string` type itself but I find that having distinct types can often make the code more readable and understandable. One extra bit is that `statusOpts` has grown a `_hasReplyTo` boolean to indicate whether the `--in-reply-to` flag was given or not because we can't distinguish because "empty because default" or "empty because given and empty". Another way around this would be to set the default to some theoretically impossible or unlikely string but you never know when someone might spin up an instance where, e.g., admin posts have negative integer IDs.

// Copyright © 2018 Mikael Berthe <mikael@lilotux.net>
//
// Licensed under the MIT license.
// Please see the LICENSE file is this directory.

package cmd

import (
	"os"

	"github.com/pkg/errors"
	"github.com/spf13/cobra"

	"github.com/McKael/madon/v2"
)

var listsOpts struct {
	listID     madon.ActivityID
	accountID  madon.ActivityID
	accountIDs string
	title      string

	// Used for several subcommands to limit the number of results
	limit, keep uint
	all         bool
}

//listsCmd represents the lists command
var listsCmd = &cobra.Command{
	Use:     "lists",
	Aliases: []string{"list"},
	Short:   "Manage lists",
	Example: `  madonctl lists create --title "Friends"
  madonctl lists show
  madonctl lists show --list-id 3
  madonctl lists update --list-id 3 --title "Family"
  madonctl lists delete --list-id 3
  madonctl lists accounts --list-id 2
  madonctl lists add-accounts --list-id 2 --account-ids 123,456
  madonctl lists remove-accounts --list-id 2 --account-ids 456
  madonctl lists show --account-id 123`,
}

func init() {
	RootCmd.AddCommand(listsCmd)

	// Subcommands
	listsCmd.AddCommand(listsSubcommands...)

	listsCmd.PersistentFlags().UintVarP(&listsOpts.limit, "limit", "l", 0, "Limit number of API results")
	listsCmd.PersistentFlags().UintVarP(&listsOpts.keep, "keep", "k", 0, "Limit number of results")
	listsCmd.PersistentFlags().BoolVar(&listsOpts.all, "all", false, "Fetch all results")

	listsCmd.PersistentFlags().StringVarP(&listsOpts.listID, "list-id", "G", "", "List ID")

	listsGetSubcommand.Flags().StringVarP(&listsOpts.accountID, "account-id", "a", "", "Account ID number")
	// XXX accountUID?

	listsGetAccountsSubcommand.Flags().StringVarP(&listsOpts.listID, "list-id", "G", "", "List ID")

	listsCreateSubcommand.Flags().StringVar(&listsOpts.title, "title", "", "List title")
	listsUpdateSubcommand.Flags().StringVar(&listsOpts.title, "title", "", "List title")

	listsAddAccountsSubcommand.Flags().StringVar(&listsOpts.accountIDs, "account-ids", "", "Comma-separated list of account IDs")
	listsAddAccountsSubcommand.Flags().StringVarP(&listsOpts.accountID, "account-id", "a", "", "Account ID number")
	listsRemoveAccountsSubcommand.Flags().StringVar(&listsOpts.accountIDs, "account-ids", "", "Comma-separated list of account IDs")
	listsRemoveAccountsSubcommand.Flags().StringVarP(&listsOpts.accountID, "account-id", "a", "", "Account ID number")
}

var listsSubcommands = []*cobra.Command{
	listsGetSubcommand,
	listsCreateSubcommand,
	listsUpdateSubcommand,
	listsDeleteSubcommand,
	listsGetAccountsSubcommand,
	listsAddAccountsSubcommand,
	listsRemoveAccountsSubcommand,
}

var listsGetSubcommand = &cobra.Command{
	Use:   "show",
	Short: "Display one or several lists",
	// TODO Long: ``,
	Aliases: []string{"get", "display", "ls"},
	RunE:    listsGetRunE,
}

var listsGetAccountsSubcommand = &cobra.Command{
	Use:   "accounts --list-id N",
	Short: "Display a list's accounts",
	RunE:  listsGetAccountsRunE,
}

var listsCreateSubcommand = &cobra.Command{
	Use:   "create --title TITLE",
	Short: "Create a list",
	RunE:  listsSetDeleteRunE,
}

var listsUpdateSubcommand = &cobra.Command{
	Use:   "update --list-id N --title TITLE",
	Short: "Update a list",
	RunE:  listsSetDeleteRunE,
}

var listsDeleteSubcommand = &cobra.Command{
	Use:     "delete --list-id N",
	Short:   "Delete a list",
	Aliases: []string{"rm", "del"},
	RunE:    listsSetDeleteRunE,
}

var listsAddAccountsSubcommand = &cobra.Command{
	Use:     "add-accounts --list-id N --account-ids ACC1,ACC2...",
	Short:   "Add one or several accounts to a list",
	Aliases: []string{"add-account"},
	RunE:    listsAddRemoveAccountsRunE,
}

var listsRemoveAccountsSubcommand = &cobra.Command{
	Use:     "remove-accounts --list-id N --account-ids ACC1,ACC2...",
	Short:   "Remove one or several accounts from a list",
	Aliases: []string{"remove-account"},
	RunE:    listsAddRemoveAccountsRunE,
}

func listsGetRunE(cmd *cobra.Command, args []string) error {
	opt := listsOpts

	// Log in
	if err := madonInit(true); err != nil {
		return err
	}

	// Set up LimitParams
	var limOpts *madon.LimitParams
	if opt.all || opt.limit > 0 {
		limOpts = new(madon.LimitParams)
		limOpts.All = opt.all
	}
	if opt.limit > 0 {
		limOpts.Limit = int(opt.limit)
	}

	var obj interface{}
	var err error

	if opt.listID != "" {
		var list *madon.List
		list, err = gClient.GetList(opt.listID)
		obj = list
	} else {
		var lists []madon.List
		lists, err = gClient.GetLists(opt.accountID, limOpts)

		if opt.keep > 0 && len(lists) > int(opt.keep) {
			lists = lists[:opt.keep]
		}
		obj = lists
	}

	if err != nil {
		errPrint("Error: %s", err.Error())
		os.Exit(1)
	}
	if obj == nil {
		return nil
	}

	p, err := getPrinter()
	if err != nil {
		errPrint("Error: %v", err)
		os.Exit(1)
	}
	return p.printObj(obj)
}

func listsGetAccountsRunE(cmd *cobra.Command, args []string) error {
	opt := listsOpts

	if opt.listID == "" {
		return errors.New("missing list ID")
	}

	// Log in
	if err := madonInit(true); err != nil {
		return err
	}

	// Set up LimitParams
	var limOpts *madon.LimitParams
	if opt.all || opt.limit > 0 {
		limOpts = new(madon.LimitParams)
		limOpts.All = opt.all
	}
	if opt.limit > 0 {
		limOpts.Limit = int(opt.limit)
	}

	var obj interface{}
	var err error

	var accounts []madon.Account
	accounts, err = gClient.GetListAccounts(opt.listID, limOpts)

	if opt.keep > 0 && len(accounts) > int(opt.keep) {
		accounts = accounts[:opt.keep]
	}
	obj = accounts

	if err != nil {
		errPrint("Error: %s", err.Error())
		os.Exit(1)
	}
	if obj == nil {
		return nil
	}

	p, err := getPrinter()
	if err != nil {
		errPrint("Error: %v", err)
		os.Exit(1)
	}
	return p.printObj(obj)
}

func listsSetDeleteRunE(cmd *cobra.Command, args []string) error {
	const (
		actionUnknown = iota
		actionCreate
		actionUpdate
		actionDelete
	)

	var action int
	opt := listsOpts

	switch cmd.Name() {
	case "create":
		if opt.listID != "" {
			return errors.New("list ID should not be provided with create")
		}
		action = actionCreate
	case "update":
		if opt.listID == "" {
			return errors.New("list ID is required")
		}
		action = actionUpdate
	case "delete", "rm", "del":
		action = actionDelete
	}

	// Additionnal checks
	if action == actionUnknown {
		// Shouldn't happen.  If it does, might be an unrecognized alias.
		return errors.New("listsSetDeleteRunE: internal error")
	}

	if action != actionDelete && opt.title == "" {
		return errors.New("the list title is required")
	}

	// Log in
	if err := madonInit(true); err != nil {
		return err
	}

	var obj interface{}
	var err error
	var list *madon.List

	switch action {
	case actionCreate:
		list, err = gClient.CreateList(opt.title)
		obj = list
	case actionUpdate:
		list, err = gClient.UpdateList(opt.listID, opt.title)
		obj = list
	case actionDelete:
		err = gClient.DeleteList(opt.listID)
		obj = nil
	}

	if err != nil {
		errPrint("Error: %s", err.Error())
		os.Exit(1)
	}
	if obj == nil {
		return nil
	}

	p, err := getPrinter()
	if err != nil {
		errPrint("Error: %v", err)
		os.Exit(1)
	}
	return p.printObj(obj)
}

func listsAddRemoveAccountsRunE(cmd *cobra.Command, args []string) error {
	opt := listsOpts

	if opt.listID == "" {
		return errors.New("missing list ID")
	}

	var ids []madon.ActivityID
	var err error
	ids, err = splitIDs(opt.accountIDs)
	if err != nil {
		return errors.New("cannot parse account IDs")
	}

	if opt.accountID != "" { // Allow --account-id
		ids = []madon.ActivityID{opt.accountID}
	}
	if len(ids) < 1 {
		return errors.New("missing account IDs")
	}

	// Log in
	if err := madonInit(true); err != nil {
		return err
	}

	switch cmd.Name() {
	case "add-account", "add-accounts":
		err = gClient.AddListAccounts(opt.listID, ids)
	case "remove-account", "remove-accounts":
		err = gClient.RemoveListAccounts(opt.listID, ids)
	default:
		// Shouldn't happen.  If it does, might be an unrecognized alias.
		return errors.New("listsAddRemoveAccountsRunE: internal error")
	}

	if err != nil {
		errPrint("Error: %s", err.Error())
		os.Exit(1)
	}

	return nil
}