cmd/accounts.go
changeset 267 5b91a65ba95a
parent 239 605a00e9d1ab
child 268 4dd196a4ee7c
equal deleted inserted replaced
264:8f478162d991 267:5b91a65ba95a
    18 )
    18 )
    19 
    19 
    20 var accountUpdateFlags, accountMuteFlags, accountFollowFlags *flag.FlagSet
    20 var accountUpdateFlags, accountMuteFlags, accountFollowFlags *flag.FlagSet
    21 
    21 
    22 var accountsOpts struct {
    22 var accountsOpts struct {
    23 	accountID             int64
    23 	accountID             madon.ActivityID
    24 	accountUID            string
    24 	accountUID            string
    25 	unset                 bool     // TODO remove eventually?
    25 	unset                 bool             // TODO remove eventually?
    26 	limit, keep           uint     // Limit the results
    26 	limit, keep           uint             // Limit the results
    27 	sinceID, maxID        int64    // Query boundaries
    27 	sinceID, maxID        madon.ActivityID // Query boundaries
    28 	all                   bool     // Try to fetch all results
    28 	all                   bool             // Try to fetch all results
    29 	onlyMedia, onlyPinned bool     // For acccount statuses
    29 	onlyMedia, onlyPinned bool             // For acccount statuses
    30 	excludeReplies        bool     // For acccount statuses
    30 	excludeReplies        bool             // For acccount statuses
    31 	remoteUID             string   // For account follow
    31 	remoteUID             string           // For account follow
    32 	reblogs               bool     // For account follow
    32 	reblogs               bool             // For account follow
    33 	acceptFR, rejectFR    bool     // For account follow_requests
    33 	acceptFR, rejectFR    bool             // For account follow_requests
    34 	list                  bool     // For account follow_requests/reports
    34 	list                  bool             // For account follow_requests/reports
    35 	accountIDs            string   // For account relationships
    35 	accountIDs            string           // For account relationships
    36 	statusIDs             string   // For account reports
    36 	statusIDs             string           // For account reports
    37 	comment               string   // For account reports
    37 	comment               string           // For account reports
    38 	displayName, note     string   // For account update
    38 	displayName, note     string           // For account update
    39 	profileFields         []string // For account update
    39 	profileFields         []string         // For account update
    40 	avatar, header        string   // For account update
    40 	avatar, header        string           // For account update
    41 	defaultLanguage       string   // For account update
    41 	defaultLanguage       string           // For account update
    42 	defaultPrivacy        string   // For account update
    42 	defaultPrivacy        string           // For account update
    43 	defaultSensitive      bool     // For account update
    43 	defaultSensitive      bool             // For account update
    44 	locked, bot           bool     // For account update
    44 	locked, bot           bool             // For account update
    45 	muteNotifications     bool     // For account mute
    45 	muteNotifications     bool             // For account mute
    46 	following             bool     // For account search
    46 	following             bool             // For account search
    47 }
    47 }
    48 
    48 
    49 func init() {
    49 func init() {
    50 	RootCmd.AddCommand(accountsCmd)
    50 	RootCmd.AddCommand(accountsCmd)
    51 
    51 
    52 	// Subcommands
    52 	// Subcommands
    53 	accountsCmd.AddCommand(accountSubcommands...)
    53 	accountsCmd.AddCommand(accountSubcommands...)
    54 
    54 
    55 	// Global flags
    55 	// Global flags
    56 	accountsCmd.PersistentFlags().Int64VarP(&accountsOpts.accountID, "account-id", "a", 0, "Account ID number")
    56 	accountsCmd.PersistentFlags().StringVarP(&accountsOpts.accountID, "account-id", "a", "", "Account ID number")
    57 	accountsCmd.PersistentFlags().StringVarP(&accountsOpts.accountUID, "user-id", "u", "", "Account user ID")
    57 	accountsCmd.PersistentFlags().StringVarP(&accountsOpts.accountUID, "user-id", "u", "", "Account user ID")
    58 	accountsCmd.PersistentFlags().UintVarP(&accountsOpts.limit, "limit", "l", 0, "Limit number of API results")
    58 	accountsCmd.PersistentFlags().UintVarP(&accountsOpts.limit, "limit", "l", 0, "Limit number of API results")
    59 	accountsCmd.PersistentFlags().UintVarP(&accountsOpts.keep, "keep", "k", 0, "Limit number of results")
    59 	accountsCmd.PersistentFlags().UintVarP(&accountsOpts.keep, "keep", "k", 0, "Limit number of results")
    60 	accountsCmd.PersistentFlags().Int64Var(&accountsOpts.sinceID, "since-id", 0, "Request IDs greater than a value")
    60 	accountsCmd.PersistentFlags().StringVar(&accountsOpts.sinceID, "since-id", "", "Request IDs greater than a value")
    61 	accountsCmd.PersistentFlags().Int64Var(&accountsOpts.maxID, "max-id", 0, "Request IDs less (or equal) than a value")
    61 	accountsCmd.PersistentFlags().StringVar(&accountsOpts.maxID, "max-id", "", "Request IDs less (or equal) than a value")
    62 	accountsCmd.PersistentFlags().BoolVar(&accountsOpts.all, "all", false, "Fetch all results")
    62 	accountsCmd.PersistentFlags().BoolVar(&accountsOpts.all, "all", false, "Fetch all results")
    63 
    63 
    64 	// Subcommand flags
    64 	// Subcommand flags
    65 	accountStatusesSubcommand.Flags().BoolVar(&accountsOpts.onlyPinned, "pinned", false, "Only statuses that have been pinned")
    65 	accountStatusesSubcommand.Flags().BoolVar(&accountsOpts.onlyPinned, "pinned", false, "Only statuses that have been pinned")
    66 	accountStatusesSubcommand.Flags().BoolVar(&accountsOpts.onlyMedia, "only-media", false, "Only statuses with media attachments")
    66 	accountStatusesSubcommand.Flags().BoolVar(&accountsOpts.onlyMedia, "only-media", false, "Only statuses with media attachments")
   375 		}
   375 		}
   376 	}
   376 	}
   377 
   377 
   378 	// Check account is provided in only one way
   378 	// Check account is provided in only one way
   379 	aCounter := 0
   379 	aCounter := 0
   380 	if opt.accountID > 0 {
   380 	if opt.accountID != "" {
   381 		aCounter++
   381 		aCounter++
   382 	}
   382 	}
   383 	if opt.accountUID != "" {
   383 	if opt.accountUID != "" {
   384 		aCounter++
   384 		aCounter++
   385 	}
   385 	}
   394 		return errors.New("too many account identifiers provided")
   394 		return errors.New("too many account identifiers provided")
   395 	}
   395 	}
   396 
   396 
   397 	if userInArg {
   397 	if userInArg {
   398 		// Is the argument an account ID?
   398 		// Is the argument an account ID?
   399 		if n, err := strconv.ParseInt(args[0], 10, 64); err == nil {
   399 		if _, err := strconv.ParseInt(args[0], 10, 64); err == nil {
   400 			opt.accountID = n
   400 			opt.accountID = args[0]
   401 		} else if strings.HasPrefix(args[0], "https://") || strings.HasPrefix(args[0], "http://") {
   401 		} else if strings.HasPrefix(args[0], "https://") || strings.HasPrefix(args[0], "http://") {
   402 			// That is not a remote UID scheme
   402 			// That is not a remote UID scheme
   403 			opt.accountUID = args[0]
   403 			opt.accountUID = args[0]
   404 		} else if subcmd == "follow" {
   404 		} else if subcmd == "follow" {
   405 			// For the follow API, got to be a remote UID...
   405 			// For the follow API, got to be a remote UID...
   415 			opt.accountUID = args[0]
   415 			opt.accountUID = args[0]
   416 		}
   416 		}
   417 	}
   417 	}
   418 
   418 
   419 	if opt.accountUID != "" {
   419 	if opt.accountUID != "" {
   420 		if opt.accountID > 0 {
   420 		if opt.accountID != "" {
   421 			return errors.New("cannot use both account ID and UID")
   421 			return errors.New("cannot use both account ID and UID")
   422 		}
   422 		}
   423 		// Sign in early to look the user id up
   423 		// Sign in early to look the user id up
   424 		var err error
   424 		var err error
   425 		if err = madonInit(true); err != nil {
   425 		if err = madonInit(true); err != nil {
   426 			return err
   426 			return err
   427 		}
   427 		}
   428 		opt.accountID, err = accountLookupUser(opt.accountUID)
   428 		opt.accountID, err = accountLookupUser(opt.accountUID)
   429 		if err != nil || opt.accountID < 1 {
   429 		if err != nil || opt.accountID == "" {
   430 			if err != nil {
   430 			if err != nil {
   431 				errPrint("Cannot find user '%s': %v", opt.accountUID, err)
   431 				errPrint("Cannot find user '%s': %v", opt.accountUID, err)
   432 			} else {
   432 			} else {
   433 				errPrint("Cannot find user '%s'", opt.accountUID)
   433 				errPrint("Cannot find user '%s'", opt.accountUID)
   434 			}
   434 			}
   439 	switch subcmd {
   439 	switch subcmd {
   440 	case "show", "search", "update":
   440 	case "show", "search", "update":
   441 		// These subcommands do not require an account ID
   441 		// These subcommands do not require an account ID
   442 	case "favourites", "blocks", "mutes", "pinned":
   442 	case "favourites", "blocks", "mutes", "pinned":
   443 		// Those subcommands can not use an account ID
   443 		// Those subcommands can not use an account ID
   444 		if opt.accountID > 0 {
   444 		if opt.accountID != "" {
   445 			return errors.New("useless account ID")
   445 			return errors.New("useless account ID")
   446 		}
   446 		}
   447 	case "follow", "unfollow":
   447 	case "follow", "unfollow":
   448 		// We need an account ID or a remote UID
   448 		// We need an account ID or a remote UID
   449 		if opt.accountID < 1 && opt.remoteUID == "" {
   449 		if opt.accountID == "" && opt.remoteUID == "" {
   450 			return errors.New("missing account ID or URI")
   450 			return errors.New("missing account ID or URI")
   451 		}
   451 		}
   452 		if opt.accountID > 0 && opt.remoteUID != "" {
   452 		if opt.accountID != "" && opt.remoteUID != "" {
   453 			return errors.New("cannot use both account ID and URI")
   453 			return errors.New("cannot use both account ID and URI")
   454 		}
   454 		}
   455 		if (opt.unset || subcmd == "unfollow") && opt.accountID < 1 {
   455 		if (opt.unset || subcmd == "unfollow") && opt.accountID == "" {
   456 			return errors.New("unfollowing requires an account ID")
   456 			return errors.New("unfollowing requires an account ID")
   457 		}
   457 		}
   458 	case "follow-requests":
   458 	case "follow-requests":
   459 		if opt.list {
   459 		if opt.list {
   460 			if opt.acceptFR || opt.rejectFR {
   460 			if opt.acceptFR || opt.rejectFR {
   466 			}
   466 			}
   467 			// This is a FR reply
   467 			// This is a FR reply
   468 			if opt.acceptFR && opt.rejectFR {
   468 			if opt.acceptFR && opt.rejectFR {
   469 				return errors.New("incompatible options")
   469 				return errors.New("incompatible options")
   470 			}
   470 			}
   471 			if opt.accountID < 1 {
   471 			if opt.accountID == "" {
   472 				return errors.New("missing account ID")
   472 				return errors.New("missing account ID")
   473 			}
   473 			}
   474 		}
   474 		}
   475 	case "relationships":
   475 	case "relationships":
   476 		if opt.accountID < 1 && len(opt.accountIDs) == 0 {
   476 		if opt.accountID == "" && len(opt.accountIDs) == 0 {
   477 			return errors.New("missing account IDs")
   477 			return errors.New("missing account IDs")
   478 		}
   478 		}
   479 		if opt.accountID > 0 && len(opt.accountIDs) > 0 {
   479 		if opt.accountID != "" && len(opt.accountIDs) > 0 {
   480 			return errors.New("incompatible options")
   480 			return errors.New("incompatible options")
   481 		}
   481 		}
   482 	case "reports":
   482 	case "reports":
   483 		if opt.list {
   483 		if opt.list {
   484 			break // No argument needed
   484 			break // No argument needed
   485 		}
   485 		}
   486 		if opt.accountID < 1 || len(opt.statusIDs) == 0 || opt.comment == "" {
   486 		if opt.accountID == "" || len(opt.statusIDs) == 0 || opt.comment == "" {
   487 			return errors.New("missing parameter")
   487 			return errors.New("missing parameter")
   488 		}
   488 		}
   489 	case "followers", "following", "statuses":
   489 	case "followers", "following", "statuses":
   490 		// If the user's account ID is missing, get it
   490 		// If the user's account ID is missing, get it
   491 		if opt.accountID < 1 {
   491 		if opt.accountID == "" {
   492 			// Sign in now to look the user id up
   492 			// Sign in now to look the user id up
   493 			if err := madonInit(true); err != nil {
   493 			if err := madonInit(true); err != nil {
   494 				return err
   494 				return err
   495 			}
   495 			}
   496 			account, err := gClient.GetCurrentAccount()
   496 			account, err := gClient.GetCurrentAccount()
   502 				errPrint("User account ID: %d", opt.accountID)
   502 				errPrint("User account ID: %d", opt.accountID)
   503 			}
   503 			}
   504 		}
   504 		}
   505 	default:
   505 	default:
   506 		// The other subcommands here require an account ID
   506 		// The other subcommands here require an account ID
   507 		if opt.accountID < 1 {
   507 		if opt.accountID == "" {
   508 			return errors.New("missing account ID")
   508 			return errors.New("missing account ID")
   509 		}
   509 		}
   510 	}
   510 	}
   511 
   511 
   512 	var limOpts *madon.LimitParams
   512 	var limOpts *madon.LimitParams
   513 	if opt.all || opt.limit > 0 || opt.sinceID > 0 || opt.maxID > 0 {
   513 	if opt.all || opt.limit > 0 || opt.sinceID != "" || opt.maxID != "" {
   514 		limOpts = new(madon.LimitParams)
   514 		limOpts = new(madon.LimitParams)
   515 		limOpts.All = opt.all
   515 		limOpts.All = opt.all
   516 	}
   516 	}
   517 
   517 
   518 	if opt.limit > 0 {
   518 	if opt.limit > 0 {
   519 		limOpts.Limit = int(opt.limit)
   519 		limOpts.Limit = int(opt.limit)
   520 	}
   520 	}
   521 	if opt.maxID > 0 {
   521 	if opt.maxID != "" {
   522 		limOpts.MaxID = opt.maxID
   522 		limOpts.MaxID = opt.maxID
   523 	}
   523 	}
   524 	if opt.sinceID > 0 {
   524 	if opt.sinceID != "" {
   525 		limOpts.SinceID = opt.sinceID
   525 		limOpts.SinceID = opt.sinceID
   526 	}
   526 	}
   527 
   527 
   528 	// All account subcommands need to have signed in
   528 	// All account subcommands need to have signed in
   529 	if err := madonInit(true); err != nil {
   529 	if err := madonInit(true); err != nil {
   534 	var err error
   534 	var err error
   535 
   535 
   536 	switch subcmd {
   536 	switch subcmd {
   537 	case "show":
   537 	case "show":
   538 		var account *madon.Account
   538 		var account *madon.Account
   539 		if opt.accountID > 0 {
   539 		if opt.accountID != "" {
   540 			account, err = gClient.GetAccount(opt.accountID)
   540 			account, err = gClient.GetAccount(opt.accountID)
   541 		} else {
   541 		} else {
   542 			account, err = gClient.GetCurrentAccount()
   542 			account, err = gClient.GetCurrentAccount()
   543 		}
   543 		}
   544 		obj = account
   544 		obj = account
   572 		if opt.unset || subcmd == "unfollow" {
   572 		if opt.unset || subcmd == "unfollow" {
   573 			relationship, err = gClient.UnfollowAccount(opt.accountID)
   573 			relationship, err = gClient.UnfollowAccount(opt.accountID)
   574 			obj = relationship
   574 			obj = relationship
   575 			break
   575 			break
   576 		}
   576 		}
   577 		if opt.accountID <= 0 {
   577 		if opt.accountID == "" {
   578 			if opt.remoteUID != "" {
   578 			if opt.remoteUID != "" {
   579 				// Remote account
   579 				// Remote account
   580 				var account *madon.Account
   580 				var account *madon.Account
   581 				account, err = gClient.FollowRemoteAccount(opt.remoteUID)
   581 				account, err = gClient.FollowRemoteAccount(opt.remoteUID)
   582 				obj = account
   582 				obj = account
   595 		obj = relationship
   595 		obj = relationship
   596 	case "follow-requests":
   596 	case "follow-requests":
   597 		if opt.list {
   597 		if opt.list {
   598 			var followRequests []madon.Account
   598 			var followRequests []madon.Account
   599 			followRequests, err = gClient.GetAccountFollowRequests(limOpts)
   599 			followRequests, err = gClient.GetAccountFollowRequests(limOpts)
   600 			if opt.accountID > 0 { // Display a specific request
   600 			if opt.accountID != "" { // Display a specific request
   601 				var fRequest *madon.Account
   601 				var fRequest *madon.Account
   602 				for _, fr := range followRequests {
   602 				for _, fr := range followRequests {
   603 					if fr.ID == opt.accountID {
   603 					if fr.ID == opt.accountID {
   604 						fRequest = &fr
   604 						fRequest = &fr
   605 						break
   605 						break
   674 		if opt.keep > 0 && len(accountList) > int(opt.keep) {
   674 		if opt.keep > 0 && len(accountList) > int(opt.keep) {
   675 			accountList = accountList[:opt.keep]
   675 			accountList = accountList[:opt.keep]
   676 		}
   676 		}
   677 		obj = accountList
   677 		obj = accountList
   678 	case "relationships":
   678 	case "relationships":
   679 		var ids []int64
   679 		var ids []madon.ActivityID
   680 		ids, err = splitIDs(opt.accountIDs)
   680 		ids, err = splitIDs(opt.accountIDs)
   681 		if err != nil {
   681 		if err != nil {
   682 			return errors.New("cannot parse account IDs")
   682 			return errors.New("cannot parse account IDs")
   683 		}
   683 		}
   684 		if opt.accountID > 0 { // Allow --account-id
   684 		if opt.accountID != "" { // Allow --account-id
   685 			ids = []int64{opt.accountID}
   685 			ids = []madon.ActivityID{opt.accountID}
   686 		}
   686 		}
   687 		if len(ids) < 1 {
   687 		if len(ids) < 1 {
   688 			return errors.New("missing account IDs")
   688 			return errors.New("missing account IDs")
   689 		}
   689 		}
   690 		var relationships []madon.Relationship
   690 		var relationships []madon.Relationship
   699 			}
   699 			}
   700 			obj = reports
   700 			obj = reports
   701 			break
   701 			break
   702 		}
   702 		}
   703 		// Send a report
   703 		// Send a report
   704 		var ids []int64
   704 		var ids []madon.ActivityID
   705 		ids, err = splitIDs(opt.statusIDs)
   705 		ids, err = splitIDs(opt.statusIDs)
   706 		if err != nil {
   706 		if err != nil {
   707 			return errors.New("cannot parse status IDs")
   707 			return errors.New("cannot parse status IDs")
   708 		}
   708 		}
   709 		if len(ids) < 1 {
   709 		if len(ids) < 1 {
   805 }
   805 }
   806 
   806 
   807 // accountLookupUser tries to find a (single) user matching 'user'
   807 // accountLookupUser tries to find a (single) user matching 'user'
   808 // If the user is an HTTP URL, it will use the search API, else
   808 // If the user is an HTTP URL, it will use the search API, else
   809 // it will use the accounts/search API.
   809 // it will use the accounts/search API.
   810 func accountLookupUser(user string) (int64, error) {
   810 func accountLookupUser(user string) (madon.ActivityID, error) {
   811 	var accID int64
   811 	var accID madon.ActivityID
   812 
   812 
   813 	if strings.HasPrefix(user, "https://") || strings.HasPrefix(user, "http://") {
   813 	if strings.HasPrefix(user, "https://") || strings.HasPrefix(user, "http://") {
   814 		res, err := gClient.Search(user, true)
   814 		res, err := gClient.Search(user, true)
   815 		if err != nil {
   815 		if err != nil {
   816 			return 0, err
   816 			return "", err
   817 		}
   817 		}
   818 		if res != nil {
   818 		if res != nil {
   819 			if len(res.Accounts) > 1 {
   819 			if len(res.Accounts) > 1 {
   820 				return 0, errors.New("several results")
   820 				return "", errors.New("several results")
   821 			}
   821 			}
   822 			if len(res.Accounts) == 1 {
   822 			if len(res.Accounts) == 1 {
   823 				accID = res.Accounts[0].ID
   823 				accID = res.Accounts[0].ID
   824 			}
   824 			}
   825 		}
   825 		}
   827 		// Remove leading '@'
   827 		// Remove leading '@'
   828 		user = strings.TrimLeft(user, "@")
   828 		user = strings.TrimLeft(user, "@")
   829 
   829 
   830 		accList, err := gClient.SearchAccounts(user, false, &madon.LimitParams{Limit: 2})
   830 		accList, err := gClient.SearchAccounts(user, false, &madon.LimitParams{Limit: 2})
   831 		if err != nil {
   831 		if err != nil {
   832 			return 0, err
   832 			return "", err
   833 		}
   833 		}
   834 		for _, u := range accList {
   834 		for _, u := range accList {
   835 			if u.Acct == user {
   835 			if u.Acct == user {
   836 				accID = u.ID
   836 				accID = u.ID
   837 				break
   837 				break
   838 			}
   838 			}
   839 		}
   839 		}
   840 	}
   840 	}
   841 
   841 
   842 	if accID < 1 {
   842 	if accID == "" {
   843 		return 0, errors.New("user not found")
   843 		return "", errors.New("user not found")
   844 	}
   844 	}
   845 	if verbose {
   845 	if verbose {
   846 		errPrint("User '%s' is account ID %d", user, user)
   846 		errPrint("User '%s' is account ID %d", user, user)
   847 	}
   847 	}
   848 	return accID, nil
   848 	return accID, nil