# HG changeset patch # User rjp # Date 1674491942 0 # Node ID 5b91a65ba95a3ea5d09710834e01ede404ed9cc9 # Parent 8f478162d991fb008fd5c1cb06bc1269dc10c695 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. diff -r 8f478162d991 -r 5b91a65ba95a cmd/accounts.go --- a/cmd/accounts.go Thu Sep 22 16:37:07 2022 +0200 +++ b/cmd/accounts.go Mon Jan 23 16:39:02 2023 +0000 @@ -20,30 +20,30 @@ var accountUpdateFlags, accountMuteFlags, accountFollowFlags *flag.FlagSet var accountsOpts struct { - accountID int64 + accountID madon.ActivityID accountUID string - unset bool // TODO remove eventually? - limit, keep uint // Limit the results - sinceID, maxID int64 // 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 + 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() { @@ -53,12 +53,12 @@ accountsCmd.AddCommand(accountSubcommands...) // Global flags - accountsCmd.PersistentFlags().Int64VarP(&accountsOpts.accountID, "account-id", "a", 0, "Account ID number") + 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().Int64Var(&accountsOpts.sinceID, "since-id", 0, "Request IDs greater than a value") - accountsCmd.PersistentFlags().Int64Var(&accountsOpts.maxID, "max-id", 0, "Request IDs less (or equal) than a value") + 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 @@ -377,7 +377,7 @@ // Check account is provided in only one way aCounter := 0 - if opt.accountID > 0 { + if opt.accountID != "" { aCounter++ } if opt.accountUID != "" { @@ -396,8 +396,8 @@ if userInArg { // Is the argument an account ID? - if n, err := strconv.ParseInt(args[0], 10, 64); err == nil { - opt.accountID = n + 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] @@ -417,7 +417,7 @@ } if opt.accountUID != "" { - if opt.accountID > 0 { + if opt.accountID != "" { return errors.New("cannot use both account ID and UID") } // Sign in early to look the user id up @@ -426,7 +426,7 @@ return err } opt.accountID, err = accountLookupUser(opt.accountUID) - if err != nil || opt.accountID < 1 { + if err != nil || opt.accountID == "" { if err != nil { errPrint("Cannot find user '%s': %v", opt.accountUID, err) } else { @@ -441,18 +441,18 @@ // These subcommands do not require an account ID case "favourites", "blocks", "mutes", "pinned": // Those subcommands can not use an account ID - if opt.accountID > 0 { + if opt.accountID != "" { return errors.New("useless account ID") } case "follow", "unfollow": // We need an account ID or a remote UID - if opt.accountID < 1 && opt.remoteUID == "" { + if opt.accountID == "" && opt.remoteUID == "" { return errors.New("missing account ID or URI") } - if opt.accountID > 0 && opt.remoteUID != "" { + if opt.accountID != "" && opt.remoteUID != "" { return errors.New("cannot use both account ID and URI") } - if (opt.unset || subcmd == "unfollow") && opt.accountID < 1 { + if (opt.unset || subcmd == "unfollow") && opt.accountID == "" { return errors.New("unfollowing requires an account ID") } case "follow-requests": @@ -468,27 +468,27 @@ if opt.acceptFR && opt.rejectFR { return errors.New("incompatible options") } - if opt.accountID < 1 { + if opt.accountID == "" { return errors.New("missing account ID") } } case "relationships": - if opt.accountID < 1 && len(opt.accountIDs) == 0 { + if opt.accountID == "" && len(opt.accountIDs) == 0 { return errors.New("missing account IDs") } - if opt.accountID > 0 && len(opt.accountIDs) > 0 { + if opt.accountID != "" && len(opt.accountIDs) > 0 { return errors.New("incompatible options") } case "reports": if opt.list { break // No argument needed } - if opt.accountID < 1 || len(opt.statusIDs) == 0 || opt.comment == "" { + 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 < 1 { + if opt.accountID == "" { // Sign in now to look the user id up if err := madonInit(true); err != nil { return err @@ -504,13 +504,13 @@ } default: // The other subcommands here require an account ID - if opt.accountID < 1 { + if opt.accountID == "" { return errors.New("missing account ID") } } var limOpts *madon.LimitParams - if opt.all || opt.limit > 0 || opt.sinceID > 0 || opt.maxID > 0 { + if opt.all || opt.limit > 0 || opt.sinceID != "" || opt.maxID != "" { limOpts = new(madon.LimitParams) limOpts.All = opt.all } @@ -518,10 +518,10 @@ if opt.limit > 0 { limOpts.Limit = int(opt.limit) } - if opt.maxID > 0 { + if opt.maxID != "" { limOpts.MaxID = opt.maxID } - if opt.sinceID > 0 { + if opt.sinceID != "" { limOpts.SinceID = opt.sinceID } @@ -536,7 +536,7 @@ switch subcmd { case "show": var account *madon.Account - if opt.accountID > 0 { + if opt.accountID != "" { account, err = gClient.GetAccount(opt.accountID) } else { account, err = gClient.GetCurrentAccount() @@ -574,7 +574,7 @@ obj = relationship break } - if opt.accountID <= 0 { + if opt.accountID == "" { if opt.remoteUID != "" { // Remote account var account *madon.Account @@ -597,7 +597,7 @@ if opt.list { var followRequests []madon.Account followRequests, err = gClient.GetAccountFollowRequests(limOpts) - if opt.accountID > 0 { // Display a specific request + if opt.accountID != "" { // Display a specific request var fRequest *madon.Account for _, fr := range followRequests { if fr.ID == opt.accountID { @@ -676,13 +676,13 @@ } obj = accountList case "relationships": - var ids []int64 + var ids []madon.ActivityID ids, err = splitIDs(opt.accountIDs) if err != nil { return errors.New("cannot parse account IDs") } - if opt.accountID > 0 { // Allow --account-id - ids = []int64{opt.accountID} + if opt.accountID != "" { // Allow --account-id + ids = []madon.ActivityID{opt.accountID} } if len(ids) < 1 { return errors.New("missing account IDs") @@ -701,7 +701,7 @@ break } // Send a report - var ids []int64 + var ids []madon.ActivityID ids, err = splitIDs(opt.statusIDs) if err != nil { return errors.New("cannot parse status IDs") @@ -807,17 +807,17 @@ // 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) (int64, error) { - var accID int64 +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 0, err + return "", err } if res != nil { if len(res.Accounts) > 1 { - return 0, errors.New("several results") + return "", errors.New("several results") } if len(res.Accounts) == 1 { accID = res.Accounts[0].ID @@ -829,7 +829,7 @@ accList, err := gClient.SearchAccounts(user, false, &madon.LimitParams{Limit: 2}) if err != nil { - return 0, err + return "", err } for _, u := range accList { if u.Acct == user { @@ -839,8 +839,8 @@ } } - if accID < 1 { - return 0, errors.New("user not found") + if accID == "" { + return "", errors.New("user not found") } if verbose { errPrint("User '%s' is account ID %d", user, user) diff -r 8f478162d991 -r 5b91a65ba95a cmd/domainblocks.go --- a/cmd/domainblocks.go Thu Sep 22 16:37:07 2022 +0200 +++ b/cmd/domainblocks.go Mon Jan 23 16:39:02 2023 +0000 @@ -17,9 +17,9 @@ var domainBlocksOpts struct { show, block, unblock bool - limit uint // Limit the results - sinceID, maxID int64 // Query boundaries - all bool // Try to fetch all results + limit uint // Limit the results + sinceID, maxID madon.ActivityID // Query boundaries + all bool // Try to fetch all results } // timelinesCmd represents the timelines command @@ -41,8 +41,8 @@ domainBlocksCmd.Flags().BoolVar(&domainBlocksOpts.unblock, "unblock", false, "Unblock domain") domainBlocksCmd.Flags().UintVarP(&domainBlocksOpts.limit, "limit", "l", 0, "Limit number of results") - domainBlocksCmd.Flags().Int64Var(&domainBlocksOpts.sinceID, "since-id", 0, "Request IDs greater than a value") - domainBlocksCmd.Flags().Int64Var(&domainBlocksOpts.maxID, "max-id", 0, "Request IDs less (or equal) than a value") + domainBlocksCmd.Flags().StringVar(&domainBlocksOpts.sinceID, "since-id", "", "Request IDs greater than a value") + domainBlocksCmd.Flags().StringVar(&domainBlocksOpts.maxID, "max-id", "", "Request IDs less (or equal) than a value") domainBlocksCmd.Flags().BoolVar(&domainBlocksOpts.all, "all", false, "Fetch all results") } @@ -71,17 +71,17 @@ // Set up LimitParams var limOpts *madon.LimitParams - if opt.all || opt.limit > 0 || opt.sinceID > 0 || opt.maxID > 0 { + 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 > 0 { + if opt.maxID != "" { limOpts.MaxID = opt.maxID } - if opt.sinceID > 0 { + if opt.sinceID != "" { limOpts.SinceID = opt.sinceID } diff -r 8f478162d991 -r 5b91a65ba95a cmd/lists.go --- a/cmd/lists.go Thu Sep 22 16:37:07 2022 +0200 +++ b/cmd/lists.go Mon Jan 23 16:39:02 2023 +0000 @@ -15,8 +15,8 @@ ) var listsOpts struct { - listID int64 - accountID int64 + listID madon.ActivityID + accountID madon.ActivityID accountIDs string title string @@ -51,20 +51,20 @@ listsCmd.PersistentFlags().UintVarP(&listsOpts.keep, "keep", "k", 0, "Limit number of results") listsCmd.PersistentFlags().BoolVar(&listsOpts.all, "all", false, "Fetch all results") - listsCmd.PersistentFlags().Int64VarP(&listsOpts.listID, "list-id", "G", 0, "List ID") + listsCmd.PersistentFlags().StringVarP(&listsOpts.listID, "list-id", "G", "", "List ID") - listsGetSubcommand.Flags().Int64VarP(&listsOpts.accountID, "account-id", "a", 0, "Account ID number") + listsGetSubcommand.Flags().StringVarP(&listsOpts.accountID, "account-id", "a", "", "Account ID number") // XXX accountUID? - listsGetAccountsSubcommand.Flags().Int64VarP(&listsOpts.listID, "list-id", "G", 0, "List ID") + 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().Int64VarP(&listsOpts.accountID, "account-id", "a", 0, "Account ID number") + 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().Int64VarP(&listsOpts.accountID, "account-id", "a", 0, "Account ID number") + listsRemoveAccountsSubcommand.Flags().StringVarP(&listsOpts.accountID, "account-id", "a", "", "Account ID number") } var listsSubcommands = []*cobra.Command{ @@ -145,7 +145,7 @@ var obj interface{} var err error - if opt.listID > 0 { + if opt.listID != "" { var list *madon.List list, err = gClient.GetList(opt.listID) obj = list @@ -178,7 +178,7 @@ func listsGetAccountsRunE(cmd *cobra.Command, args []string) error { opt := listsOpts - if opt.listID <= 0 { + if opt.listID == "" { return errors.New("missing list ID") } @@ -237,12 +237,12 @@ switch cmd.Name() { case "create": - if opt.listID > 0 { + if opt.listID != "" { return errors.New("list ID should not be provided with create") } action = actionCreate case "update": - if opt.listID <= 0 { + if opt.listID == "" { return errors.New("list ID is required") } action = actionUpdate @@ -300,19 +300,19 @@ func listsAddRemoveAccountsRunE(cmd *cobra.Command, args []string) error { opt := listsOpts - if opt.listID <= 0 { + if opt.listID == "" { return errors.New("missing list ID") } - var ids []int64 + 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 > 0 { // Allow --account-id - ids = []int64{opt.accountID} + if opt.accountID != "" { // Allow --account-id + ids = []madon.ActivityID{opt.accountID} } if len(ids) < 1 { return errors.New("missing account IDs") diff -r 8f478162d991 -r 5b91a65ba95a cmd/madon.go --- a/cmd/madon.go Thu Sep 22 16:37:07 2022 +0200 +++ b/cmd/madon.go Mon Jan 23 16:39:02 2023 +0000 @@ -6,7 +6,6 @@ package cmd import ( - "strconv" "strings" "github.com/McKael/madon/v2" @@ -104,18 +103,16 @@ } // splitIDs splits a list of IDs into an int64 array -func splitIDs(ids string) (list []int64, err error) { - var i int64 +func splitIDs(ids string) (list []madon.ActivityID, err error) { if ids == "" { return } l := strings.Split(ids, ",") for _, s := range l { - i, err = strconv.ParseInt(s, 10, 64) - if err != nil { + if s == "" { return } - list = append(list, i) + list = append(list, s) } return } diff -r 8f478162d991 -r 5b91a65ba95a cmd/media.go --- a/cmd/media.go Thu Sep 22 16:37:07 2022 +0200 +++ b/cmd/media.go Mon Jan 23 16:39:02 2023 +0000 @@ -18,7 +18,7 @@ var mediaFlags *flag.FlagSet var mediaOpts struct { - mediaID int64 + mediaID madon.ActivityID filePath string description string focus string @@ -48,7 +48,7 @@ RootCmd.AddCommand(mediaCmd) mediaCmd.Flags().StringVar(&mediaOpts.filePath, "file", "", "Path of the media file") - mediaCmd.Flags().Int64Var(&mediaOpts.mediaID, "update", 0, "Media to update (ID)") + mediaCmd.Flags().StringVar(&mediaOpts.mediaID, "update", "", "Media to update (ID)") mediaCmd.Flags().StringVar(&mediaOpts.description, "description", "", "Plain text description") mediaCmd.Flags().StringVar(&mediaOpts.focus, "focus", "", "Focal point") @@ -61,10 +61,10 @@ opt := mediaOpts if opt.filePath == "" { - if opt.mediaID < 1 { + if opt.mediaID == "" { return errors.New("no media file name provided") } - } else if opt.mediaID > 0 { + } else if opt.mediaID != "" { return errors.New("cannot use both --file and --update") } @@ -102,13 +102,13 @@ } // uploadFile uploads a media file and returns the attachment ID -func uploadFile(filePath string) (int64, error) { +func uploadFile(filePath string) (madon.ActivityID, error) { attachment, err := gClient.UploadMedia(filePath, "", "") if err != nil { - return 0, err + return "", err } if attachment == nil { - return 0, nil + return "", nil } return attachment.ID, nil } diff -r 8f478162d991 -r 5b91a65ba95a cmd/notifications.go --- a/cmd/notifications.go Thu Sep 22 16:37:07 2022 +0200 +++ b/cmd/notifications.go Mon Jan 23 16:39:02 2023 +0000 @@ -17,7 +17,7 @@ var notificationsOpts struct { list, clear, dismiss bool - notifID int64 + notifID madon.ActivityID types string excludeTypes string } @@ -50,7 +50,7 @@ notificationsCmd.Flags().BoolVar(¬ificationsOpts.list, "list", false, "List all current notifications") notificationsCmd.Flags().BoolVar(¬ificationsOpts.clear, "clear", false, "Clear all current notifications") notificationsCmd.Flags().BoolVar(¬ificationsOpts.dismiss, "dismiss", false, "Delete a notification") - notificationsCmd.Flags().Int64Var(¬ificationsOpts.notifID, "notification-id", 0, "Get a notification") + notificationsCmd.Flags().StringVar(¬ificationsOpts.notifID, "notification-id", "", "Get a notification") notificationsCmd.Flags().StringVar(¬ificationsOpts.types, "notification-types", "", "Filter notifications (mention, favourite, reblog, follow)") notificationsCmd.Flags().StringVar(¬ificationsOpts.excludeTypes, "exclude-types", "", "Exclude notifications types (mention, favourite, reblog, follow)") } @@ -58,7 +58,7 @@ func notificationRunE(cmd *cobra.Command, args []string) error { opt := notificationsOpts - if !opt.list && !opt.clear && opt.notifID < 1 { + if !opt.list && !opt.clear && opt.notifID == "" { return errors.New("missing parameters") } @@ -67,7 +67,7 @@ } var limOpts *madon.LimitParams - if accountsOpts.all || accountsOpts.limit > 0 || accountsOpts.sinceID > 0 || accountsOpts.maxID > 0 { + if accountsOpts.all || accountsOpts.limit > 0 || accountsOpts.sinceID != "" || accountsOpts.maxID != "" { limOpts = new(madon.LimitParams) limOpts.All = accountsOpts.all } @@ -75,11 +75,11 @@ if accountsOpts.limit > 0 { limOpts.Limit = int(accountsOpts.limit) } - if accountsOpts.maxID > 0 { - limOpts.MaxID = int64(accountsOpts.maxID) + if accountsOpts.maxID != "" { + limOpts.MaxID = accountsOpts.maxID } - if accountsOpts.sinceID > 0 { - limOpts.SinceID = int64(accountsOpts.sinceID) + if accountsOpts.sinceID != "" { + limOpts.SinceID = accountsOpts.sinceID } var filterMap *map[string]bool @@ -123,7 +123,7 @@ notifications = notifications[:accountsOpts.keep] } obj = notifications - } else if opt.notifID > 0 { + } else if opt.notifID != "" { if opt.dismiss { err = gClient.DismissNotification(opt.notifID) } else { diff -r 8f478162d991 -r 5b91a65ba95a cmd/status.go --- a/cmd/status.go Thu Sep 22 16:37:07 2022 +0200 +++ b/cmd/status.go Mon Jan 23 16:39:02 2023 +0000 @@ -20,14 +20,14 @@ var statusPostFlags *flag.FlagSet var statusOpts struct { - statusID int64 + statusID madon.ActivityID unset bool // TODO remove eventually? // The following fields are used for the post/toot command visibility string sensitive bool spoiler string - inReplyToID int64 + inReplyToID madon.ActivityID mediaIDs string mediaFilePath string textFilePath string @@ -39,6 +39,9 @@ limit, keep uint //sinceID, maxID int64 all bool + + // Used to indicate whether `in-reply-to` flag is present or not. + _hasReplyTo bool } func init() { @@ -48,7 +51,7 @@ statusCmd.AddCommand(statusSubcommands...) // Global flags - statusCmd.PersistentFlags().Int64VarP(&statusOpts.statusID, "status-id", "s", 0, "Status ID number") + statusCmd.PersistentFlags().StringVarP(&statusOpts.statusID, "status-id", "s", "", "Status ID number") statusCmd.PersistentFlags().UintVarP(&statusOpts.limit, "limit", "l", 0, "Limit number of API results") statusCmd.PersistentFlags().UintVarP(&statusOpts.keep, "keep", "k", 0, "Limit number of results") //statusCmd.PersistentFlags().Int64Var(&statusOpts.sinceID, "since-id", 0, "Request IDs greater than a value") @@ -65,7 +68,7 @@ statusPostSubcommand.Flags().StringVar(&statusOpts.mediaIDs, "media-ids", "", "Comma-separated list of media IDs") statusPostSubcommand.Flags().StringVarP(&statusOpts.mediaFilePath, "file", "f", "", "Media file name") statusPostSubcommand.Flags().StringVar(&statusOpts.textFilePath, "text-file", "", "Text file name (message content)") - statusPostSubcommand.Flags().Int64VarP(&statusOpts.inReplyToID, "in-reply-to", "r", 0, "Status ID to reply to") + statusPostSubcommand.Flags().StringVarP(&statusOpts.inReplyToID, "in-reply-to", "r", "", "Status ID to reply to") statusPostSubcommand.Flags().BoolVar(&statusOpts.stdin, "stdin", false, "Read message content from standard input") statusPostSubcommand.Flags().BoolVar(&statusOpts.addMentions, "add-mentions", false, "Add mentions when replying") statusPostSubcommand.Flags().BoolVar(&statusOpts.sameVisibility, "same-visibility", false, "Use same visibility as original message (for replies)") @@ -94,7 +97,7 @@ //Long: `TBW...`, // TODO PersistentPreRunE: func(cmd *cobra.Command, args []string) error { // This is common to status and all status subcommands but "post" - if statusOpts.statusID < 1 && cmd.Name() != "post" { + if statusOpts.statusID == "" && cmd.Name() != "post" { return errors.New("missing status ID") } return madonInit(true) @@ -241,6 +244,8 @@ The default visibility can be set in the configuration file with the option 'default_visibility' (or with an environmnent variable).`, RunE: func(cmd *cobra.Command, args []string) error { + // Update the extra flag to reflect if `in-reply-to` was present or not + statusOpts._hasReplyTo = cmd.Flags().Lookup("in-reply-to").Changed return statusSubcommandRunE(cmd.Name(), args) }, } diff -r 8f478162d991 -r 5b91a65ba95a cmd/suggestions.go --- a/cmd/suggestions.go Thu Sep 22 16:37:07 2022 +0200 +++ b/cmd/suggestions.go Mon Jan 23 16:39:02 2023 +0000 @@ -15,7 +15,7 @@ ) var suggestionsOpts struct { - accountID int64 + accountID madon.ActivityID accountIDs string //limit uint @@ -41,7 +41,7 @@ suggestionsGetSubcommand.Flags().UintVarP(&suggestionsOpts.keep, "keep", "k", 0, "Limit number of results") //suggestionsGetSubcommand.Flags().BoolVar(&suggestionsOpts.all, "all", false, "Fetch all results") - suggestionsDeleteSubcommand.Flags().Int64VarP(&suggestionsOpts.accountID, "account-id", "a", 0, "Account ID number") + suggestionsDeleteSubcommand.Flags().StringVarP(&suggestionsOpts.accountID, "account-id", "a", "", "Account ID number") suggestionsDeleteSubcommand.Flags().StringVar(&suggestionsOpts.accountIDs, "account-ids", "", "Comma-separated list of account IDs") } @@ -116,13 +116,13 @@ func suggestionsDeleteRunE(cmd *cobra.Command, args []string) error { opt := suggestionsOpts - var ids []int64 + var ids []madon.ActivityID var err error - if opt.accountID < 1 && len(opt.accountIDs) == 0 { + if opt.accountID == "" && len(opt.accountIDs) == 0 { return errors.New("missing account IDs") } - if opt.accountID > 0 && len(opt.accountIDs) > 0 { + if opt.accountID != "" && len(opt.accountIDs) > 0 { return errors.New("incompatible options") } @@ -130,8 +130,8 @@ if err != nil { return errors.New("cannot parse account IDs") } - if opt.accountID > 0 { // Allow --account-id - ids = []int64{opt.accountID} + if opt.accountID != "" { // Allow --account-id + ids = []madon.ActivityID{opt.accountID} } if len(ids) < 1 { return errors.New("missing account IDs") diff -r 8f478162d991 -r 5b91a65ba95a cmd/timelines.go --- a/cmd/timelines.go Thu Sep 22 16:37:07 2022 +0200 +++ b/cmd/timelines.go Mon Jan 23 16:39:02 2023 +0000 @@ -17,7 +17,7 @@ var timelineOpts struct { local, onlyMedia bool limit, keep uint - sinceID, maxID int64 + sinceID, maxID madon.ActivityID } // timelineCmd represents the timelines command @@ -47,25 +47,25 @@ timelineCmd.Flags().BoolVar(&timelineOpts.onlyMedia, "only-media", false, "Only statuses with media attachments") timelineCmd.Flags().UintVarP(&timelineOpts.limit, "limit", "l", 0, "Limit number of API results") timelineCmd.Flags().UintVarP(&timelineOpts.keep, "keep", "k", 0, "Limit number of results") - timelineCmd.PersistentFlags().Int64Var(&timelineOpts.sinceID, "since-id", 0, "Request IDs greater than a value") - timelineCmd.PersistentFlags().Int64Var(&timelineOpts.maxID, "max-id", 0, "Request IDs less (or equal) than a value") + timelineCmd.PersistentFlags().StringVar(&timelineOpts.sinceID, "since-id", "", "Request IDs greater than a value") + timelineCmd.PersistentFlags().StringVar(&timelineOpts.maxID, "max-id", "", "Request IDs less (or equal) than a value") } func timelineRunE(cmd *cobra.Command, args []string) error { opt := timelineOpts var limOpts *madon.LimitParams - if opt.limit > 0 || opt.sinceID > 0 || opt.maxID > 0 { + if opt.limit > 0 || opt.sinceID != "" || opt.maxID != "" { limOpts = new(madon.LimitParams) } if opt.limit > 0 { limOpts.Limit = int(opt.limit) } - if opt.maxID > 0 { + if opt.maxID != "" { limOpts.MaxID = opt.maxID } - if opt.sinceID > 0 { + if opt.sinceID != "" { limOpts.SinceID = opt.sinceID } diff -r 8f478162d991 -r 5b91a65ba95a cmd/toot.go --- a/cmd/toot.go Thu Sep 22 16:37:07 2022 +0200 +++ b/cmd/toot.go Mon Jan 23 16:39:02 2023 +0000 @@ -29,7 +29,7 @@ tootAliasCmd.Flags().StringVar(&statusOpts.mediaIDs, "media-ids", "", "Comma-separated list of media IDs") tootAliasCmd.Flags().StringVarP(&statusOpts.mediaFilePath, "file", "f", "", "Media attachment file name") tootAliasCmd.Flags().StringVar(&statusOpts.textFilePath, "text-file", "", "Text file name (message content)") - tootAliasCmd.Flags().Int64VarP(&statusOpts.inReplyToID, "in-reply-to", "r", 0, "Status ID to reply to") + tootAliasCmd.Flags().StringVarP(&statusOpts.inReplyToID, "in-reply-to", "r", "", "Status ID to reply to") tootAliasCmd.Flags().BoolVar(&statusOpts.stdin, "stdin", false, "Read message content from standard input") tootAliasCmd.Flags().BoolVar(&statusOpts.addMentions, "add-mentions", false, "Add mentions when replying") tootAliasCmd.Flags().BoolVar(&statusOpts.sameVisibility, "same-visibility", false, "Use same visibility as original message (for replies)") @@ -64,6 +64,8 @@ if err := madonInit(true); err != nil { return err } + // Update the extra flag to reflect if `in-reply-to` was present or not + statusOpts._hasReplyTo = cmd.Flags().Lookup("in-reply-to").Changed return statusSubcommandRunE("post", args) }, } @@ -85,7 +87,12 @@ return nil, errors.Errorf("invalid visibility argument value '%s'", opt.visibility) } - if opt.inReplyToID < 0 { + // Bit of a fudge but there's no easy way to tell if a string flag + // is empty by default or empty by assignment. Can't use a pointer + // and have `nil` be "unset" because Cobra will crash if we send it + // a `nil` as the recepient for a flag variable. Hence using an + // extra struct member as a flag to indicate set/unset. + if opt._hasReplyTo && opt.inReplyToID == "" { return nil, errors.New("invalid in-reply-to argument value") } @@ -98,7 +105,7 @@ return nil, errors.New("toot is empty") } - if opt.inReplyToID > 0 { + if opt.inReplyToID != "" { var initialStatus *madon.Status var preserveVis bool if opt.sameVisibility && @@ -143,7 +150,7 @@ if err != nil { return nil, errors.Wrap(err, "cannot attach media file") } - if fileMediaID > 0 { + if fileMediaID != "" { ids = append(ids, fileMediaID) } } diff -r 8f478162d991 -r 5b91a65ba95a printer/plainprinter.go --- a/printer/plainprinter.go Thu Sep 22 16:37:07 2022 +0200 +++ b/printer/plainprinter.go Mon Jan 23 16:39:02 2023 +0000 @@ -175,7 +175,7 @@ } func (p *PlainPrinter) plainPrintAccount(a *madon.Account, w io.Writer, indent string) error { - indentedPrint(w, indent, true, false, "Account ID", "%d (%s)", a.ID, a.Username) + indentedPrint(w, indent, true, false, "Account ID", "%s (%s)", a.ID, a.Username) indentedPrint(w, indent, false, false, "User ID", "%s", a.Acct) indentedPrint(w, indent, false, false, "Display name", "%s", a.DisplayName) indentedPrint(w, indent, false, false, "Creation date", "%v", a.CreatedAt.Local()) @@ -192,7 +192,7 @@ indentedPrint(w, indent, false, true, "User note", "%s", html2string(a.Note)) // XXX too long? if a.Moved != nil { m := a.Moved - indentedPrint(w, indent+p.Indent, true, false, "Moved to account ID", "%d (%s)", m.ID, m.Username) + indentedPrint(w, indent+p.Indent, true, false, "Moved to account ID", "%s (%s)", m.ID, m.Username) indentedPrint(w, indent+p.Indent, false, false, "New user ID", "%s", m.Acct) indentedPrint(w, indent+p.Indent, false, false, "New display name", "%s", m.DisplayName) } @@ -218,7 +218,7 @@ } func (p *PlainPrinter) plainPrintAttachment(a *madon.Attachment, w io.Writer, indent string) error { - indentedPrint(w, indent, true, false, "Attachment ID", "%d", a.ID) + indentedPrint(w, indent, true, false, "Attachment ID", "%s", a.ID) indentedPrint(w, indent, false, false, "Type", "%s", a.Type) indentedPrint(w, indent, false, false, "Local URL", "%s", a.URL) if a.RemoteURL != nil { @@ -269,7 +269,7 @@ indentedPrint(w, indent, false, true, "Version", "%s", i.Version) if i.ContactAccount != nil { c := i.ContactAccount - indentedPrint(w, indent+p.Indent, true, false, "Contact account ID", "%d (%s)", c.ID, c.Username) + indentedPrint(w, indent+p.Indent, true, false, "Contact account ID", "%s (%s)", c.ID, c.Username) indentedPrint(w, indent+p.Indent, false, false, "Contact user ID", "%s", c.Acct) indentedPrint(w, indent+p.Indent, false, false, "Contact display name", "%s", c.DisplayName) } @@ -282,17 +282,17 @@ } func (p *PlainPrinter) plainPrintList(l *madon.List, w io.Writer, indent string) error { - indentedPrint(w, indent, true, false, "List ID", "%d", l.ID) + indentedPrint(w, indent, true, false, "List ID", "%s", l.ID) indentedPrint(w, indent, false, false, "Title", "%s", l.Title) return nil } func (p *PlainPrinter) plainPrintNotification(n *madon.Notification, w io.Writer, indent string) error { - indentedPrint(w, indent, true, false, "Notification ID", "%d", n.ID) + indentedPrint(w, indent, true, false, "Notification ID", "%s", n.ID) indentedPrint(w, indent, false, false, "Type", "%s", n.Type) indentedPrint(w, indent, false, false, "Timestamp", "%v", n.CreatedAt.Local()) if n.Account != nil { - indentedPrint(w, indent+p.Indent, true, false, "Account", "(%d) @%s - %s", + indentedPrint(w, indent+p.Indent, true, false, "Account", "(%s) @%s - %s", n.Account.ID, n.Account.Acct, n.Account.DisplayName) } if n.Status != nil { @@ -302,7 +302,7 @@ } func (p *PlainPrinter) plainPrintRelationship(r *madon.Relationship, w io.Writer, indent string) error { - indentedPrint(w, indent, true, false, "Account ID", "%d", r.ID) + indentedPrint(w, indent, true, false, "Account ID", "%s", r.ID) indentedPrint(w, indent, false, false, "Following", "%v", r.Following) //indentedPrint(w, indent, false, false, "Showing reblogs", "%v", r.ShowingReblogs) indentedPrint(w, indent, false, false, "Followed-by", "%v", r.FollowedBy) @@ -315,7 +315,7 @@ } func (p *PlainPrinter) plainPrintReport(r *madon.Report, w io.Writer, indent string) error { - indentedPrint(w, indent, true, false, "Report ID", "%d", r.ID) + indentedPrint(w, indent, true, false, "Report ID", "%s", r.ID) indentedPrint(w, indent, false, false, "Action taken", "%s", r.ActionTaken) return nil } @@ -342,7 +342,7 @@ } func (p *PlainPrinter) plainPrintStatus(s *madon.Status, w io.Writer, indent string) error { - indentedPrint(w, indent, true, false, "Status ID", "%d", s.ID) + indentedPrint(w, indent, true, false, "Status ID", "%s", s.ID) if s.Account != nil { author := s.Account.Acct if s.Account.DisplayName != "" { @@ -370,8 +370,8 @@ } indentedPrint(w, indent, false, false, "Contents", "%s", html2string(s.Content)) - if s.InReplyToID != nil && *s.InReplyToID > 0 { - indentedPrint(w, indent, false, false, "In-Reply-To", "%d", *s.InReplyToID) + if s.InReplyToID != nil && *s.InReplyToID != "" { + indentedPrint(w, indent, false, false, "In-Reply-To", "%s", *s.InReplyToID) } if s.Reblogged { indentedPrint(w, indent, false, false, "Reblogged", "%v", s.Reblogged) @@ -380,7 +380,7 @@ // Display minimum details of attachments //return p.PrintObj(s.MediaAttachments, w, indent+p.Indent) for _, a := range s.MediaAttachments { - indentedPrint(w, indent+p.Indent, true, false, "Attachment ID", "%d", a.ID) + indentedPrint(w, indent+p.Indent, true, false, "Attachment ID", "%s", a.ID) if a.TextURL != nil && *a.TextURL != "" { indentedPrint(w, indent+p.Indent, true, false, "Text URL", "%s", *a.TextURL) } else if a.URL != "" {