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 © 2017-2018 Mikael Berthe <mikael@lilotux.net>
//
// Licensed under the MIT license.
// Please see the LICENSE file is this directory.
package cmd
import (
"os"
"strconv"
"strings"
"github.com/pkg/errors"
"github.com/spf13/cobra"
flag "github.com/spf13/pflag"
"github.com/McKael/madon/v2"
)
var accountUpdateFlags, accountMuteFlags, accountFollowFlags *flag.FlagSet
var accountsOpts struct {
accountID madon.ActivityID
accountUID string
unset bool // TODO remove eventually?
limit, keep uint // Limit the results
sinceID, maxID madon.ActivityID // Query boundaries
all bool // Try to fetch all results
onlyMedia, onlyPinned bool // For acccount statuses
excludeReplies bool // For acccount statuses
remoteUID string // For account follow
reblogs bool // For account follow
acceptFR, rejectFR bool // For account follow_requests
list bool // For account follow_requests/reports
accountIDs string // For account relationships
statusIDs string // For account reports
comment string // For account reports
displayName, note string // For account update
profileFields []string // For account update
avatar, header string // For account update
defaultLanguage string // For account update
defaultPrivacy string // For account update
defaultSensitive bool // For account update
locked, bot bool // For account update
muteNotifications bool // For account mute
following bool // For account search
}
func init() {
RootCmd.AddCommand(accountsCmd)
// Subcommands
accountsCmd.AddCommand(accountSubcommands...)
// Global flags
accountsCmd.PersistentFlags().StringVarP(&accountsOpts.accountID, "account-id", "a", "", "Account ID number")
accountsCmd.PersistentFlags().StringVarP(&accountsOpts.accountUID, "user-id", "u", "", "Account user ID")
accountsCmd.PersistentFlags().UintVarP(&accountsOpts.limit, "limit", "l", 0, "Limit number of API results")
accountsCmd.PersistentFlags().UintVarP(&accountsOpts.keep, "keep", "k", 0, "Limit number of results")
accountsCmd.PersistentFlags().StringVar(&accountsOpts.sinceID, "since-id", "", "Request IDs greater than a value")
accountsCmd.PersistentFlags().StringVar(&accountsOpts.maxID, "max-id", "", "Request IDs less (or equal) than a value")
accountsCmd.PersistentFlags().BoolVar(&accountsOpts.all, "all", false, "Fetch all results")
// Subcommand flags
accountStatusesSubcommand.Flags().BoolVar(&accountsOpts.onlyPinned, "pinned", false, "Only statuses that have been pinned")
accountStatusesSubcommand.Flags().BoolVar(&accountsOpts.onlyMedia, "only-media", false, "Only statuses with media attachments")
accountStatusesSubcommand.Flags().BoolVar(&accountsOpts.excludeReplies, "exclude-replies", false, "Exclude replies to other statuses")
accountFollowRequestsSubcommand.Flags().BoolVar(&accountsOpts.list, "list", false, "List pending follow requests")
accountFollowRequestsSubcommand.Flags().BoolVar(&accountsOpts.acceptFR, "accept", false, "Accept the follow request from the account ID")
accountFollowRequestsSubcommand.Flags().BoolVar(&accountsOpts.rejectFR, "reject", false, "Reject the follow request from the account ID")
accountBlockSubcommand.Flags().BoolVarP(&accountsOpts.unset, "unset", "", false, "Unblock the account (deprecated)")
accountMuteSubcommand.Flags().BoolVarP(&accountsOpts.unset, "unset", "", false, "Unmute the account (deprecated)")
accountMuteSubcommand.Flags().BoolVarP(&accountsOpts.muteNotifications, "notifications", "", true, "Mute the notifications")
accountFollowSubcommand.Flags().BoolVarP(&accountsOpts.unset, "unset", "", false, "Unfollow the account (deprecated)")
accountFollowSubcommand.Flags().BoolVarP(&accountsOpts.reblogs, "show-reblogs", "", true, "Follow account's boosts")
accountFollowSubcommand.Flags().StringVarP(&accountsOpts.remoteUID, "remote", "r", "", "Follow remote account (user@domain)")
accountRelationshipsSubcommand.Flags().StringVar(&accountsOpts.accountIDs, "account-ids", "", "Comma-separated list of account IDs")
accountReportsSubcommand.Flags().StringVar(&accountsOpts.statusIDs, "status-ids", "", "Comma-separated list of status IDs")
accountReportsSubcommand.Flags().StringVar(&accountsOpts.comment, "comment", "", "Report comment")
accountReportsSubcommand.Flags().BoolVar(&accountsOpts.list, "list", false, "List current user reports")
accountSearchSubcommand.Flags().BoolVar(&accountsOpts.following, "following", false, "Restrict search to accounts you are following")
accountUpdateSubcommand.Flags().StringVar(&accountsOpts.displayName, "display-name", "", "User display name")
accountUpdateSubcommand.Flags().StringVar(&accountsOpts.note, "note", "", "User note (a.k.a. bio)")
accountUpdateSubcommand.Flags().StringVar(&accountsOpts.avatar, "avatar", "", "User avatar image")
accountUpdateSubcommand.Flags().StringVar(&accountsOpts.header, "header", "", "User header image")
accountUpdateSubcommand.Flags().StringArrayVar(&accountsOpts.profileFields, "profile-field", nil, "Profile metadata field (NAME=VALUE)")
accountUpdateSubcommand.Flags().StringVar(&accountsOpts.defaultLanguage, "default-language", "", "Default toots language (iso 639 code)")
accountUpdateSubcommand.Flags().StringVar(&accountsOpts.defaultPrivacy, "default-privacy", "", "Default toot privacy (public, unlisted, private)")
accountUpdateSubcommand.Flags().BoolVar(&accountsOpts.defaultSensitive, "default-sensitive", false, "Mark medias as sensitive by default")
accountUpdateSubcommand.Flags().BoolVar(&accountsOpts.locked, "locked", false, "Following account requires approval")
accountUpdateSubcommand.Flags().BoolVar(&accountsOpts.bot, "bot", false, "Set as service (automated) account")
// Deprecated flags
accountBlockSubcommand.Flags().MarkDeprecated("unset", "please use unblock instead")
accountMuteSubcommand.Flags().MarkDeprecated("unset", "please use unmute instead")
accountFollowSubcommand.Flags().MarkDeprecated("unset", "please use unfollow instead")
// Those variables will be used to check if the options were
// explicitly set or not
accountUpdateFlags = accountUpdateSubcommand.Flags()
accountMuteFlags = accountMuteSubcommand.Flags()
accountFollowFlags = accountFollowSubcommand.Flags()
}
// accountsCmd represents the accounts command
// This command does nothing without a subcommand
var accountsCmd = &cobra.Command{
Use: "account [--account-id ID] subcommand",
Aliases: []string{"accounts"},
Short: "Account-related functions",
//Long: `TBW...`, // TODO
}
// Note: Some account subcommands are not defined in this file.
var accountSubcommands = []*cobra.Command{
&cobra.Command{
Use: "show",
Long: `Displays the details about the requested account.
If no account ID is specified, the current user account is used.`,
Aliases: []string{"display"},
Short: "Display the account",
Example: ` madonctl account show # Display your own account
madonctl account show --account-id 1234
madonctl account show --user-id Gargron@mastodon.social
madonctl account show --user-id https://mastodon.social/@Gargron
madonctl account show 1234
madonctl account show Gargron@mastodon.social
madonctl account show https://mastodon.social/@Gargron
`,
RunE: func(cmd *cobra.Command, args []string) error {
return accountSubcommandsRunE(cmd.Name(), args)
},
},
&cobra.Command{
Use: "followers",
Short: "Display the accounts following the specified account",
RunE: func(cmd *cobra.Command, args []string) error {
return accountSubcommandsRunE(cmd.Name(), args)
},
},
&cobra.Command{
Use: "following",
Short: "Display the accounts followed by the specified account",
RunE: func(cmd *cobra.Command, args []string) error {
return accountSubcommandsRunE(cmd.Name(), args)
},
},
&cobra.Command{
Use: "favourites",
Aliases: []string{"favorites", "favourited", "favorited"},
Short: "Display the user's favourites",
RunE: func(cmd *cobra.Command, args []string) error {
return accountSubcommandsRunE(cmd.Name(), args)
},
},
&cobra.Command{
Use: "blocks",
Aliases: []string{"blocked"},
Short: "Display the user's blocked accounts",
RunE: func(cmd *cobra.Command, args []string) error {
return accountSubcommandsRunE(cmd.Name(), args)
},
},
&cobra.Command{
Use: "mutes",
Aliases: []string{"muted"},
Short: "Display the user's muted accounts",
RunE: func(cmd *cobra.Command, args []string) error {
return accountSubcommandsRunE(cmd.Name(), args)
},
},
accountSearchSubcommand,
accountStatusesSubcommand,
accountFollowRequestsSubcommand,
accountFollowSubcommand,
accountUnfollowSubcommand,
accountBlockSubcommand,
accountUnblockSubcommand,
accountMuteSubcommand,
accountUnmuteSubcommand,
accountPinSubcommand,
accountUnpinSubcommand,
accountRelationshipsSubcommand,
accountReportsSubcommand,
accountUpdateSubcommand,
accountListEndorsementsSubcommand,
}
var accountSearchSubcommand = &cobra.Command{
Use: "search TEXT",
Short: "Search for user accounts",
Long: `Search for user accounts.
This command will lookup an account remotely if the search term is in the
@domain format and not yet known to the server.`,
RunE: func(cmd *cobra.Command, args []string) error {
return accountSubcommandsRunE(cmd.Name(), args)
},
}
var accountStatusesSubcommand = &cobra.Command{
Use: "statuses",
Aliases: []string{"st"},
Short: "Display the account statuses",
Example: ` madonctl account statuses
madonctl account statuses 404 # local account numeric ID
madonctl account statuses @McKael # local account
madonctl account statuses Gargron@mastodon.social # remote (known account)
madonctl account statuses https://mastodon.social/@Gargron # any account URL
`,
RunE: func(cmd *cobra.Command, args []string) error {
return accountSubcommandsRunE(cmd.Name(), args)
},
}
var accountFollowRequestsSubcommand = &cobra.Command{
Use: "follow-requests",
Aliases: []string{"follow-request", "fr"},
Short: "List, accept or deny a follow request",
Example: ` madonctl account follow-requests --list
madonctl account follow-requests --account-id X --accept
madonctl account follow-requests --account-id Y --reject`,
RunE: func(cmd *cobra.Command, args []string) error {
return accountSubcommandsRunE(cmd.Name(), args)
},
}
var accountFollowSubcommand = &cobra.Command{
Use: "follow",
Short: "Follow an account",
Example: `# Argument type can be set explicitly:
madonctl account follow --account-id 1234
madonctl account follow --remote Gargron@mastodon.social
# Or argument type can be guessed:
madonctl account follow 4800
madonctl account follow Gargron@mastodon.social --show-reblogs=false
madonctl account follow https://mastodon.social/@Gargron
`,
RunE: func(cmd *cobra.Command, args []string) error {
return accountSubcommandsRunE(cmd.Name(), args)
},
}
var accountUnfollowSubcommand = &cobra.Command{
Use: "unfollow",
Short: "Stop following an account",
Example: ` madonctl account unfollow --account-id 1234
Same usage as madonctl follow.
`,
RunE: func(cmd *cobra.Command, args []string) error {
return accountSubcommandsRunE(cmd.Name(), args)
},
}
var accountBlockSubcommand = &cobra.Command{
Use: "block",
Short: "Block the account",
RunE: func(cmd *cobra.Command, args []string) error {
return accountSubcommandsRunE(cmd.Name(), args)
},
}
var accountUnblockSubcommand = &cobra.Command{
Use: "unblock",
Short: "Unblock the account",
RunE: func(cmd *cobra.Command, args []string) error {
return accountSubcommandsRunE(cmd.Name(), args)
},
}
var accountMuteSubcommand = &cobra.Command{
Use: "mute",
Short: "Mute the account",
RunE: func(cmd *cobra.Command, args []string) error {
return accountSubcommandsRunE(cmd.Name(), args)
},
}
var accountUnmuteSubcommand = &cobra.Command{
Use: "unmute",
Short: "Unmute the account",
RunE: func(cmd *cobra.Command, args []string) error {
return accountSubcommandsRunE(cmd.Name(), args)
},
}
var accountPinSubcommand = &cobra.Command{
Use: "pin",
Short: "Endorse (pin) the account",
Aliases: []string{"endorse"},
RunE: func(cmd *cobra.Command, args []string) error {
return accountSubcommandsRunE(cmd.Name(), args)
},
}
var accountUnpinSubcommand = &cobra.Command{
Use: "unpin",
Short: "Cancel endorsement of an account",
Aliases: []string{"disavow"},
RunE: func(cmd *cobra.Command, args []string) error {
return accountSubcommandsRunE(cmd.Name(), args)
},
}
var accountListEndorsementsSubcommand = &cobra.Command{
Use: "pinned",
Short: `Display the list of pinned (endorsed) accounts`,
Aliases: []string{"list-endorsements", "get-endorsements"},
RunE: func(cmd *cobra.Command, args []string) error {
return accountSubcommandsRunE(cmd.Name(), args)
},
}
var accountRelationshipsSubcommand = &cobra.Command{
Use: "relationships --account-ids ACC1,ACC2...",
Short: "List relationships with the accounts",
RunE: func(cmd *cobra.Command, args []string) error {
return accountSubcommandsRunE(cmd.Name(), args)
},
}
var accountReportsSubcommand = &cobra.Command{
Use: "reports",
Short: "List reports or report a user account",
Example: ` madonctl account reports --list
madonctl account reports --account-id ACCOUNT --status-ids ID... --comment TEXT`,
RunE: func(cmd *cobra.Command, args []string) error {
return accountSubcommandsRunE(cmd.Name(), args)
},
}
var accountUpdateSubcommand = &cobra.Command{
Use: "update",
Short: "Update connected user account",
Long: `Update connected user account
All flags are optional (set to an empty string if you want to delete a field).
The options --avatar and --header should be paths to image files.
Please note the avatar and header images cannot be removed, they can only be
replaced.`,
Example: ` madonctl account update --display-name "Mr President"
madonctl account update --note "I like madonctl"
madonctl account update --avatar happyface.png`,
RunE: func(cmd *cobra.Command, args []string) error {
return accountSubcommandsRunE(cmd.Name(), args)
},
}
// accountSubcommandsRunE is a generic function for status subcommands
func accountSubcommandsRunE(subcmd string, args []string) error {
opt := accountsOpts
if len(args) > 1 {
return errors.New("too many arguments")
}
userInArg := false
if len(args) == 1 {
if len(args[0]) > 0 {
userInArg = true
} else {
return errors.New("invalid argument (empty)")
}
}
// Check account is provided in only one way
aCounter := 0
if opt.accountID != "" {
aCounter++
}
if opt.accountUID != "" {
aCounter++
}
if opt.remoteUID != "" {
aCounter++
}
if userInArg {
aCounter++
}
if aCounter > 1 {
return errors.New("too many account identifiers provided")
}
if userInArg {
// Is the argument an account ID?
if _, err := strconv.ParseInt(args[0], 10, 64); err == nil {
opt.accountID = args[0]
} else if strings.HasPrefix(args[0], "https://") || strings.HasPrefix(args[0], "http://") {
// That is not a remote UID scheme
opt.accountUID = args[0]
} else if subcmd == "follow" {
// For the follow API, got to be a remote UID...
opt.remoteUID = args[0]
// ... unless it's local (i.e. no '@' in the identifier)...
fid := strings.TrimLeft(args[0], "@")
if !strings.ContainsRune(fid, '@') {
opt.accountUID = args[0]
opt.remoteUID = ""
}
} else {
// Fall back to account UID
opt.accountUID = args[0]
}
}
if opt.accountUID != "" {
if opt.accountID != "" {
return errors.New("cannot use both account ID and UID")
}
// Sign in early to look the user id up
var err error
if err = madonInit(true); err != nil {
return err
}
opt.accountID, err = accountLookupUser(opt.accountUID)
if err != nil || opt.accountID == "" {
if err != nil {
errPrint("Cannot find user '%s': %v", opt.accountUID, err)
} else {
errPrint("Cannot find user '%s'", opt.accountUID)
}
os.Exit(1)
}
}
switch subcmd {
case "show", "search", "update":
// These subcommands do not require an account ID
case "favourites", "blocks", "mutes", "pinned":
// Those subcommands can not use an account ID
if opt.accountID != "" {
return errors.New("useless account ID")
}
case "follow", "unfollow":
// We need an account ID or a remote UID
if opt.accountID == "" && opt.remoteUID == "" {
return errors.New("missing account ID or URI")
}
if opt.accountID != "" && opt.remoteUID != "" {
return errors.New("cannot use both account ID and URI")
}
if (opt.unset || subcmd == "unfollow") && opt.accountID == "" {
return errors.New("unfollowing requires an account ID")
}
case "follow-requests":
if opt.list {
if opt.acceptFR || opt.rejectFR {
return errors.New("incompatible options")
}
} else {
if !opt.acceptFR && !opt.rejectFR { // No flag
return errors.New("missing parameter (--list, --accept or --reject)")
}
// This is a FR reply
if opt.acceptFR && opt.rejectFR {
return errors.New("incompatible options")
}
if opt.accountID == "" {
return errors.New("missing account ID")
}
}
case "relationships":
if opt.accountID == "" && len(opt.accountIDs) == 0 {
return errors.New("missing account IDs")
}
if opt.accountID != "" && len(opt.accountIDs) > 0 {
return errors.New("incompatible options")
}
case "reports":
if opt.list {
break // No argument needed
}
if opt.accountID == "" || len(opt.statusIDs) == 0 || opt.comment == "" {
return errors.New("missing parameter")
}
case "followers", "following", "statuses":
// If the user's account ID is missing, get it
if opt.accountID == "" {
// Sign in now to look the user id up
if err := madonInit(true); err != nil {
return err
}
account, err := gClient.GetCurrentAccount()
if err != nil {
return err
}
opt.accountID = account.ID
if verbose {
errPrint("User account ID: %d", opt.accountID)
}
}
default:
// The other subcommands here require an account ID
if opt.accountID == "" {
return errors.New("missing account ID")
}
}
var limOpts *madon.LimitParams
if opt.all || opt.limit > 0 || opt.sinceID != "" || opt.maxID != "" {
limOpts = new(madon.LimitParams)
limOpts.All = opt.all
}
if opt.limit > 0 {
limOpts.Limit = int(opt.limit)
}
if opt.maxID != "" {
limOpts.MaxID = opt.maxID
}
if opt.sinceID != "" {
limOpts.SinceID = opt.sinceID
}
// All account subcommands need to have signed in
if err := madonInit(true); err != nil {
return err
}
var obj interface{}
var err error
switch subcmd {
case "show":
var account *madon.Account
if opt.accountID != "" {
account, err = gClient.GetAccount(opt.accountID)
} else {
account, err = gClient.GetCurrentAccount()
}
obj = account
case "search":
var accountList []madon.Account
accountList, err = gClient.SearchAccounts(strings.Join(args, " "), opt.following, limOpts)
obj = accountList
case "followers":
var accountList []madon.Account
accountList, err = gClient.GetAccountFollowers(opt.accountID, limOpts)
if opt.keep > 0 && len(accountList) > int(opt.keep) {
accountList = accountList[:opt.keep]
}
obj = accountList
case "following":
var accountList []madon.Account
accountList, err = gClient.GetAccountFollowing(opt.accountID, limOpts)
if opt.keep > 0 && len(accountList) > int(opt.keep) {
accountList = accountList[:opt.keep]
}
obj = accountList
case "statuses":
var statusList []madon.Status
statusList, err = gClient.GetAccountStatuses(opt.accountID, opt.onlyPinned, opt.onlyMedia, opt.excludeReplies, limOpts)
if opt.keep > 0 && len(statusList) > int(opt.keep) {
statusList = statusList[:opt.keep]
}
obj = statusList
case "follow", "unfollow":
var relationship *madon.Relationship
if opt.unset || subcmd == "unfollow" {
relationship, err = gClient.UnfollowAccount(opt.accountID)
obj = relationship
break
}
if opt.accountID == "" {
if opt.remoteUID != "" {
// Remote account
var account *madon.Account
account, err = gClient.FollowRemoteAccount(opt.remoteUID)
obj = account
break
}
return errors.New("error: no usable parameter")
}
// Locally-known account
var followReblogs *bool
if accountFollowFlags.Lookup("show-reblogs").Changed {
// Set followReblogs as it's been explicitly requested
followReblogs = &opt.reblogs
}
relationship, err = gClient.FollowAccount(opt.accountID, followReblogs)
obj = relationship
case "follow-requests":
if opt.list {
var followRequests []madon.Account
followRequests, err = gClient.GetAccountFollowRequests(limOpts)
if opt.accountID != "" { // Display a specific request
var fRequest *madon.Account
for _, fr := range followRequests {
if fr.ID == opt.accountID {
fRequest = &fr
break
}
}
if fRequest != nil {
followRequests = []madon.Account{*fRequest}
} else {
followRequests = []madon.Account{}
}
} else {
if opt.keep > 0 && len(followRequests) > int(opt.keep) {
followRequests = followRequests[:opt.keep]
}
}
obj = followRequests
} else {
err = gClient.FollowRequestAuthorize(opt.accountID, !opt.rejectFR)
}
case "block", "unblock":
var relationship *madon.Relationship
if opt.unset || subcmd == "unblock" {
relationship, err = gClient.UnblockAccount(opt.accountID)
} else {
relationship, err = gClient.BlockAccount(opt.accountID)
}
obj = relationship
case "mute", "unmute":
var relationship *madon.Relationship
if opt.unset || subcmd == "unmute" {
relationship, err = gClient.UnmuteAccount(opt.accountID)
} else {
var muteNotif *bool
if accountMuteFlags.Lookup("notifications").Changed {
muteNotif = &opt.muteNotifications
}
relationship, err = gClient.MuteAccount(opt.accountID, muteNotif)
}
obj = relationship
case "pin", "unpin":
var relationship *madon.Relationship
if subcmd == "unpin" {
relationship, err = gClient.UnpinAccount(opt.accountID)
} else {
relationship, err = gClient.PinAccount(opt.accountID)
}
obj = relationship
case "favourites":
var statusList []madon.Status
statusList, err = gClient.GetFavourites(limOpts)
if opt.keep > 0 && len(statusList) > int(opt.keep) {
statusList = statusList[:opt.keep]
}
obj = statusList
case "blocks":
var accountList []madon.Account
accountList, err = gClient.GetBlockedAccounts(limOpts)
if opt.keep > 0 && len(accountList) > int(opt.keep) {
accountList = accountList[:opt.keep]
}
obj = accountList
case "mutes":
var accountList []madon.Account
accountList, err = gClient.GetMutedAccounts(limOpts)
if opt.keep > 0 && len(accountList) > int(opt.keep) {
accountList = accountList[:opt.keep]
}
obj = accountList
case "pinned":
var accountList []madon.Account
accountList, err = gClient.GetEndorsements(limOpts)
if opt.keep > 0 && len(accountList) > int(opt.keep) {
accountList = accountList[:opt.keep]
}
obj = accountList
case "relationships":
var ids []madon.ActivityID
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")
}
var relationships []madon.Relationship
relationships, err = gClient.GetAccountRelationships(ids)
obj = relationships
case "reports":
if opt.list {
var reports []madon.Report
reports, err = gClient.GetReports(limOpts)
if opt.keep > 0 && len(reports) > int(opt.keep) {
reports = reports[:opt.keep]
}
obj = reports
break
}
// Send a report
var ids []madon.ActivityID
ids, err = splitIDs(opt.statusIDs)
if err != nil {
return errors.New("cannot parse status IDs")
}
if len(ids) < 1 {
return errors.New("missing status IDs")
}
var report *madon.Report
report, err = gClient.ReportUser(opt.accountID, ids, opt.comment)
obj = report
case "update":
var updateParams madon.UpdateAccountParams
var source *madon.SourceParams
change := false
if accountUpdateFlags.Lookup("display-name").Changed {
updateParams.DisplayName = &opt.displayName
change = true
}
if accountUpdateFlags.Lookup("note").Changed {
updateParams.Note = &opt.note
change = true
}
if accountUpdateFlags.Lookup("avatar").Changed {
updateParams.AvatarImagePath = &opt.avatar
change = true
}
if accountUpdateFlags.Lookup("header").Changed {
updateParams.HeaderImagePath = &opt.header
change = true
}
if accountUpdateFlags.Lookup("locked").Changed {
updateParams.Locked = &opt.locked
change = true
}
if accountUpdateFlags.Lookup("bot").Changed {
updateParams.Bot = &opt.bot
change = true
}
if accountUpdateFlags.Lookup("default-language").Changed {
if source == nil {
source = &madon.SourceParams{}
}
source.Language = &opt.defaultLanguage
change = true
}
if accountUpdateFlags.Lookup("default-privacy").Changed {
if source == nil {
source = &madon.SourceParams{}
}
source.Privacy = &opt.defaultPrivacy
change = true
}
if accountUpdateFlags.Lookup("default-sensitive").Changed {
if source == nil {
source = &madon.SourceParams{}
}
source.Sensitive = &opt.defaultSensitive
change = true
}
if accountUpdateFlags.Lookup("profile-field").Changed {
var fa = []madon.Field{}
for _, f := range opt.profileFields {
kv := strings.SplitN(f, "=", 2)
if len(kv) != 2 {
return errors.New("cannot parse field")
}
fa = append(fa, madon.Field{Name: kv[0], Value: kv[1]})
}
updateParams.FieldsAttributes = &fa
change = true
}
if !change { // We want at least one update
return errors.New("missing parameters")
}
updateParams.Source = source
var account *madon.Account
account, err = gClient.UpdateAccount(updateParams)
obj = account
default:
return errors.New("accountSubcommand: internal error")
}
if err != nil {
errPrint("Error: %s", err.Error())
os.Exit(1)
}
if obj == nil {
return nil
}
p, err := getPrinter()
if err != nil {
errPrint("Error: %s", err.Error())
os.Exit(1)
}
return p.printObj(obj)
}
// accountLookupUser tries to find a (single) user matching 'user'
// If the user is an HTTP URL, it will use the search API, else
// it will use the accounts/search API.
func accountLookupUser(user string) (madon.ActivityID, error) {
var accID madon.ActivityID
if strings.HasPrefix(user, "https://") || strings.HasPrefix(user, "http://") {
res, err := gClient.Search(user, true)
if err != nil {
return "", err
}
if res != nil {
if len(res.Accounts) > 1 {
return "", errors.New("several results")
}
if len(res.Accounts) == 1 {
accID = res.Accounts[0].ID
}
}
} else {
// Remove leading '@'
user = strings.TrimLeft(user, "@")
accList, err := gClient.SearchAccounts(user, false, &madon.LimitParams{Limit: 2})
if err != nil {
return "", err
}
for _, u := range accList {
if u.Acct == user {
accID = u.ID
break
}
}
}
if accID == "" {
return "", errors.New("user not found")
}
if verbose {
errPrint("User '%s' is account ID %d", user, user)
}
return accID, nil
}