Update to handle non-int64 IDs
authorrjp <zimpenfish@gmail.com>
Mon, 23 Jan 2023 16:39:02 +0000
changeset 267 5b91a65ba95a
parent 264 8f478162d991
child 268 4dd196a4ee7c
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.
cmd/accounts.go
cmd/domainblocks.go
cmd/lists.go
cmd/madon.go
cmd/media.go
cmd/notifications.go
cmd/status.go
cmd/suggestions.go
cmd/timelines.go
cmd/toot.go
printer/plainprinter.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)
--- 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
 	}
 
--- 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")
--- 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
 }
--- 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
 }
--- 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(&notificationsOpts.list, "list", false, "List all current notifications")
 	notificationsCmd.Flags().BoolVar(&notificationsOpts.clear, "clear", false, "Clear all current notifications")
 	notificationsCmd.Flags().BoolVar(&notificationsOpts.dismiss, "dismiss", false, "Delete a notification")
-	notificationsCmd.Flags().Int64Var(&notificationsOpts.notifID, "notification-id", 0, "Get a notification")
+	notificationsCmd.Flags().StringVar(&notificationsOpts.notifID, "notification-id", "", "Get a notification")
 	notificationsCmd.Flags().StringVar(&notificationsOpts.types, "notification-types", "", "Filter notifications (mention, favourite, reblog, follow)")
 	notificationsCmd.Flags().StringVar(&notificationsOpts.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 {
--- 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)
 	},
 }
--- 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")
--- 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
 	}
 
--- 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)
 		}
 	}
--- 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 != "" {